분석 v2.1
This commit is contained in:
745
ARCHITECTURE.md
745
ARCHITECTURE.md
@ -722,6 +722,747 @@ if isinstance(val, float):
|
||||
|
||||
---
|
||||
|
||||
## 📊 12. v2 분석 프로세스 (4단계 파이프라인)
|
||||
|
||||
### 12.1 프로세스 개요
|
||||
|
||||
v2 분석 시스템은 JSON 원본 데이터에서 최종 밸런스 리포트까지 4단계 파이프라인으로 구성됩니다.
|
||||
|
||||
```
|
||||
[원본 JSON] → [01단계] → [02단계] → [03단계] → [04단계]
|
||||
기본데이터 DPS계산 역할비교 밸런스티어
|
||||
```
|
||||
|
||||
**출력 구조**:
|
||||
```
|
||||
분석결과/YYYYMMDD_HHMMSS_v2/
|
||||
├── 01_스토커별_기본데이터_v2.md # 01단계 출력
|
||||
├── 02_DPS_시나리오_비교분석_v2.md # 02단계 출력
|
||||
├── 03_역할별_차별화_v2.md # 03단계 출력
|
||||
├── 04_밸런스_티어_및_개선안_v2.md # 04단계 출력
|
||||
├── intermediate_data.json # 중간 데이터
|
||||
├── validated_data.json # 검증된 데이터
|
||||
└── 검증_리포트.md # 검증 리포트
|
||||
```
|
||||
|
||||
### 12.2 01단계: 스토커별 기본 데이터
|
||||
|
||||
#### 목적
|
||||
- JSON 원본에서 10명 스토커의 기본 정보 추출 및 검증
|
||||
- 평타, 스킬, 소환체 데이터 문서화
|
||||
|
||||
#### 입력
|
||||
- `원본데이터/DataTable.json`
|
||||
- `원본데이터/Blueprint.json`
|
||||
- `원본데이터/AnimMontage.json`
|
||||
|
||||
#### 출력
|
||||
- `01_스토커별_기본데이터_v2.md`
|
||||
- `validated_data.json`
|
||||
|
||||
#### 실행 스크립트
|
||||
```bash
|
||||
cd 분석도구/v2
|
||||
python extract_stalker_data_v2.py
|
||||
python validate_stalker_data.py
|
||||
python generate_stalker_docs_v2.py
|
||||
```
|
||||
|
||||
#### 핵심 알고리즘
|
||||
|
||||
**1. 공격 스킬 판정 (우선순위)**:
|
||||
```python
|
||||
# Priority 1: NotifyName 키워드
|
||||
if any(keyword in notify_name for keyword in ['AttackWithEquip', 'Projectile', 'SkillActive']):
|
||||
is_attack = True
|
||||
|
||||
# Priority 2: CustomProperties.NotifyName
|
||||
custom_notify_name = custom_props.get('NotifyName', '')
|
||||
if any(keyword in custom_notify_name for keyword in ATTACK_KEYWORDS):
|
||||
is_attack = True
|
||||
|
||||
# Priority 3: NotifyClass 키워드
|
||||
if any(keyword in notify_class for keyword in ATTACK_KEYWORDS):
|
||||
is_attack = True
|
||||
|
||||
# Priority 4: SimpleSendEvent Event Tag
|
||||
if 'SimpleSendEvent' in notify_class:
|
||||
event_tag = custom_props.get('Event Tag', '')
|
||||
if 'Event.SkillActivate' in event_tag or 'Event.SpawnProjectile' in event_tag:
|
||||
is_attack = True
|
||||
```
|
||||
|
||||
**2. 시퀀스 길이 계산**:
|
||||
```python
|
||||
def calculate_sequence_length(skill_id, montage_data):
|
||||
# 1. 키워드 제외 (Ready, Equipment)
|
||||
# 2. 특정 몽타주 제외 (exclude_montages 설정)
|
||||
# 3. 인덱스 제외 (exclude_montage_indices 설정)
|
||||
# 4. 평균 계산 (average_skills 설정)
|
||||
|
||||
if skill_id in average_skills:
|
||||
return sum(durations) / len(durations), True
|
||||
else:
|
||||
return sum(durations), False
|
||||
```
|
||||
|
||||
**3. 소환체 공격 사이클**:
|
||||
```python
|
||||
# 순차 루프 계산 (1→2→3→1→2→3...)
|
||||
total_cycle = sum(montage_durations)
|
||||
cycle_count = active_duration / total_cycle
|
||||
attack_count = cycle_count * len(montages)
|
||||
```
|
||||
|
||||
#### 검증 체크리스트
|
||||
- [ ] 10명 스토커 모두 추출됨
|
||||
- [ ] 모든 스토커 스탯 합계 = 75
|
||||
- [ ] 궁극기 10개 확인
|
||||
- [ ] 공격 스킬 vs 유틸리티 분류 정확성
|
||||
- [ ] 시퀀스 길이 0이 아닌 값
|
||||
- [ ] 소환체 데이터 (Ifrit, Shiva)
|
||||
- [ ] DoT 스킬 4개 (Poison, Burn, Bleed)
|
||||
|
||||
### 12.3 02단계: DPS 시나리오 비교분석
|
||||
|
||||
#### 목적
|
||||
- 3개 DPS 시나리오 계산 (평타, 로테이션, 버스트)
|
||||
- 특수 상황 분석 (DoT, 소환체, 패링)
|
||||
- 신규 스토커 중심 상세 분석
|
||||
|
||||
#### 입력
|
||||
- `validated_data.json` (01단계 출력)
|
||||
- `config.py` (BaseDamage 계산 설정)
|
||||
|
||||
#### 출력
|
||||
- `02_DPS_시나리오_비교분석_v2.md`
|
||||
|
||||
#### 실행 스크립트
|
||||
```bash
|
||||
cd 분석도구/v2
|
||||
python calculate_dps_scenarios_v2.py
|
||||
```
|
||||
|
||||
#### BaseDamage 계산식
|
||||
|
||||
**레벨 20, 기어스코어 400 기준**:
|
||||
|
||||
```python
|
||||
# 물리 딜러
|
||||
Physical_BaseDamage = (주스탯 + 80) × 1.20
|
||||
# 주스탯: STR or DEX
|
||||
# 80: 장비 보너스
|
||||
# 1.20: 룬 효과 (+10% 물리 + +10% 스킬)
|
||||
|
||||
# 마법 딜러
|
||||
Magical_BaseDamage = (INT + 80) × 1.10
|
||||
# 1.10: 룬 효과 (+10% 마법)
|
||||
|
||||
# 탱커/서포터
|
||||
Support_BaseDamage = (주스탯 + 80) × 1.00
|
||||
# 생존력 중심 (피해 증가 룬 없음)
|
||||
```
|
||||
|
||||
#### 시나리오 1: 평타 DPS
|
||||
|
||||
**목적**: 순수 평타만으로 지속 딜 측정
|
||||
|
||||
**계산식**:
|
||||
```python
|
||||
평타_DPS = (BaseDamage × 평타배율합계) / 콤보시간
|
||||
|
||||
# 예: Rio
|
||||
# BaseDamage = (25 + 80) × 1.20 = 126
|
||||
# 평타배율합계 = (1.0 - 0.3) + (1.0 - 0.2) + (1.0 - 0.15) = 2.15
|
||||
# 콤보시간 = 1.17 + 1.33 + 1.37 = 3.87초
|
||||
# 평타_DPS = (126 × 2.15) / 3.87 = 69.9
|
||||
```
|
||||
|
||||
**특수 처리**:
|
||||
```python
|
||||
# Urud, Lian: Reload
|
||||
평타_DPS_with_reload = 평타_DPS × (발사횟수 / (발사시간 + reload시간))
|
||||
|
||||
# Lian: Charging
|
||||
평타_DPS_charged = (BaseDamage × 1.5) / (충전시간 + 발사시간)
|
||||
```
|
||||
|
||||
#### 시나리오 2: 스킬 로테이션 DPS (30초)
|
||||
|
||||
**목적**: 스킬 + 평타 조합한 실전 DPS
|
||||
|
||||
**계산식**:
|
||||
```python
|
||||
로테이션_DPS = (30초간_총_피해량) / 30초
|
||||
|
||||
# 스킬 사용 횟수
|
||||
스킬_사용횟수 = floor((30초 - castingTime) / (coolTime + 시퀀스길이))
|
||||
|
||||
# 평타 필러 시간
|
||||
평타_필러_시간 = 30초 - sum(스킬_사용시간)
|
||||
```
|
||||
|
||||
**로테이션 규칙**:
|
||||
1. 유틸리티 스킬 제외 (isUtility=True)
|
||||
2. 쿨타임 짧은 순서로 우선 사용
|
||||
3. 마나 관리: 0.2/초 + 룬 +70% = 0.34/초
|
||||
4. 스킬 쿨타임 중 평타 사용
|
||||
|
||||
**DoT 피해 추가**:
|
||||
```python
|
||||
# Poison/Burn: 대상 MaxHP 비례
|
||||
DoT_피해 = 대상_MaxHP × DoT_rate × (30초 / DoT_duration)
|
||||
|
||||
# Bleed: 고정 피해
|
||||
DoT_피해 = 고정피해 × (30초 / DoT_duration)
|
||||
```
|
||||
|
||||
**소환체 피해 추가**:
|
||||
```python
|
||||
# Ifrit: 20초 지속, 8.29초 사이클
|
||||
Ifrit_공격횟수 = 20초 / 8.29초 × 3개 = ~7.2회
|
||||
Ifrit_피해 = 7.2 × BaseDamage × 1.2
|
||||
|
||||
# Shiva: 60초 지속, 2.32초 사이클
|
||||
Shiva_공격횟수 = 30초 / 2.32초 = ~12.9회
|
||||
Shiva_피해 = 12.9 × BaseDamage × 0.8
|
||||
```
|
||||
|
||||
#### 시나리오 3: 버스트 DPS (10초)
|
||||
|
||||
**목적**: 궁극기 포함 최대 화력
|
||||
|
||||
**계산식**:
|
||||
```python
|
||||
버스트_DPS = (궁극기_피해 + 모든_스킬_피해 + 평타_피해) / 10초
|
||||
```
|
||||
|
||||
**조건**:
|
||||
- 모든 스킬 쿨타임 완료 상태
|
||||
- 마나 제한 무시 (풀 마나 50 + 회복)
|
||||
- 최적 순서로 스킬 사용
|
||||
|
||||
**유틸리티 궁극기 처리**:
|
||||
```python
|
||||
# Lian: 폭우 (쿨타임 -50%, 15초)
|
||||
버스트기간 = 10초
|
||||
스킬_추가사용 = 쿨타임_50%_감소로_인한_추가_발동
|
||||
|
||||
# Hilda: 핏빛 달 (공격력 +15, 20초)
|
||||
버스트기간내_스킬피해 = (BaseDamage + 15) × 스킬배율
|
||||
```
|
||||
|
||||
#### 특수 상황 분석
|
||||
|
||||
**1. DoT DPS (대상 HP별)**:
|
||||
```python
|
||||
DoT_DPS_table = {
|
||||
'100HP': {
|
||||
'Poison': 100 × 0.20 / 5 = 4 DPS,
|
||||
'Burn': 100 × 0.10 / 3 = 3.33 DPS
|
||||
},
|
||||
'500HP': {
|
||||
'Poison': 500 × 0.20 / 5 = 20 DPS,
|
||||
'Burn': 500 × 0.10 / 3 = 16.67 DPS
|
||||
},
|
||||
'1000HP': {
|
||||
'Poison': 1000 × 0.20 / 5 = 40 DPS,
|
||||
'Burn': 1000 × 0.10 / 3 = 33.33 DPS
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**2. 소환체 독립 DPS**:
|
||||
```python
|
||||
# Ifrit (20초 지속)
|
||||
Ifrit_DPS = (BaseDamage × 1.2 × 7.2회) / 20초
|
||||
|
||||
# Shiva (60초 지속)
|
||||
Shiva_DPS = (BaseDamage × 0.8 × 25.9회) / 60초
|
||||
```
|
||||
|
||||
**3. 패링 시나리오 (Cazimord)**:
|
||||
```python
|
||||
# 패링 0%
|
||||
DPS_no_parry = 기본_로테이션_DPS
|
||||
|
||||
# 패링 50% (5회/10회 성공)
|
||||
쿨감_효과 = 섬광_3.8초 + 날개베기_3.8초 + 작열_6.8초
|
||||
추가_스킬사용 = 쿨감으로_인한_추가_발동
|
||||
DPS_50_parry = 기본_DPS + 추가_스킬_DPS
|
||||
|
||||
# 패링 100% (10회/10회 성공)
|
||||
DPS_100_parry = 기본_DPS + (추가_스킬_DPS × 2)
|
||||
```
|
||||
|
||||
#### 출력 구조
|
||||
|
||||
**시나리오별 비교표**:
|
||||
```markdown
|
||||
## 시나리오 1: 평타 DPS
|
||||
|
||||
| 순위 | 스토커 | BaseDamage | 평타 DPS | 특수 처리 |
|
||||
|------|--------|------------|----------|-----------|
|
||||
| 1 | Rio | 126 | 69.9 | Chain Score 3스택 |
|
||||
| ... |
|
||||
|
||||
## 시나리오 2: 스킬 로테이션 DPS (30초)
|
||||
|
||||
| 순위 | 스토커 | 로테이션 DPS | 주요 스킬 | DoT/소환체 |
|
||||
|------|--------|--------------|-----------|------------|
|
||||
| 1 | Cazimord | 221 | 섬광+날개베기+작열 | - |
|
||||
| ... |
|
||||
|
||||
## 시나리오 3: 버스트 DPS (10초)
|
||||
|
||||
| 순위 | 스토커 | 버스트 DPS | 궁극기 | 특징 |
|
||||
|------|--------|------------|--------|------|
|
||||
| 1 | Cazimord | 256 | 칼날폭풍 (10.0배) | 단일 최강 |
|
||||
| ... |
|
||||
```
|
||||
|
||||
**신규 스토커 상세 분석** (Cazimord):
|
||||
```markdown
|
||||
## 신규 스토커 상세 분석: Cazimord
|
||||
|
||||
### 평타 DPS
|
||||
- 3타 콤보: ...
|
||||
- 타임라인: 0초 1타 → 1.67초 2타 → 3.57초 3타 → 5.44초 반복
|
||||
|
||||
### 30초 로테이션
|
||||
**타임라인**:
|
||||
```
|
||||
0.0초: 작열 시전 (2초 casting + 2.43초)
|
||||
4.43초: 평타 콤보 시작
|
||||
9.87초: 섬광 (1.73초)
|
||||
11.6초: 날개베기 (2.00초)
|
||||
13.6초: 평타 콤보
|
||||
...
|
||||
```
|
||||
|
||||
**패링 영향**:
|
||||
- 패링 0%: 221 DPS
|
||||
- 패링 50%: 245 DPS (+10.9%)
|
||||
- 패링 100%: 268 DPS (+21.3%)
|
||||
|
||||
### 버스트 DPS (10초)
|
||||
**궁극기 칼날폭풍**:
|
||||
- 12연타: 80% × 10회 + 100% × 2회 = 10.0배
|
||||
- 타임라인: ...
|
||||
```
|
||||
|
||||
#### 검증 체크리스트
|
||||
- [ ] 10명 스토커 모두 3개 시나리오 계산됨
|
||||
- [ ] BaseDamage 계산 정확성
|
||||
- [ ] 평타 배율 합계 정확성
|
||||
- [ ] 스킬 로테이션 마나 부족 없음
|
||||
- [ ] DoT 피해 대상 HP별 표시
|
||||
- [ ] 소환체 공격 횟수 정확성
|
||||
- [ ] 신규 스토커 상세 타임라인 포함
|
||||
|
||||
### 12.4 03단계: 역할별 차별화
|
||||
|
||||
#### 목적
|
||||
- 5개 역할군 비교 (전사, 원거리, 마법사, 암살자, 서포터)
|
||||
- 동일 역할 내 차별화 포인트 분석
|
||||
|
||||
#### 입력
|
||||
- `02_DPS_시나리오_비교분석_v2.md` (DPS 데이터)
|
||||
- `validated_data.json` (스킬 데이터)
|
||||
|
||||
#### 출력
|
||||
- `03_역할별_차별화_v2.md`
|
||||
|
||||
#### 역할군 분류
|
||||
|
||||
| 역할군 | 스토커 | 인원 |
|
||||
|--------|--------|------|
|
||||
| **전사** | Hilda, Baran, Cazimord | 3명 |
|
||||
| **원거리** | Urud, Lian | 2명 |
|
||||
| **마법사** | Nave, Rene | 2명 |
|
||||
| **암살자** | Rio, Sinobu | 2명 |
|
||||
| **서포터** | Clad | 1명 |
|
||||
|
||||
#### 분석 항목
|
||||
|
||||
**각 역할군마다**:
|
||||
1. **공통점**: 무기, 공격타입, 룬효과, 평타콤보
|
||||
2. **스탯 비교**: STR/DEX/INT/CON/WIS, BaseDamage, DPS
|
||||
3. **스킬 구성 비교**: 쿨타임, 배율, 특수효과
|
||||
4. **차별화 포인트**: 핵심 시스템, 강점/약점, 플레이스타일
|
||||
|
||||
#### 출력 구조
|
||||
|
||||
```markdown
|
||||
## 1. 전사 (Warriors) - 3명 비교
|
||||
|
||||
### 공통점
|
||||
| 항목 | 공통 특성 |
|
||||
|------|----------|
|
||||
| **무기 타입** | 근접 무기 |
|
||||
| **공격 타입** | Physical 피해 |
|
||||
|
||||
### 스탯 비교
|
||||
| 스토커 | STR | DEX | BaseDamage | 평타 DPS | 로테이션 DPS |
|
||||
|--------|-----|-----|------------|----------|--------------|
|
||||
| Hilda | 20 | 15 | 120 | 69.9 | 117 |
|
||||
| Baran | 25 | 10 | 126 | 84.2 | 128 |
|
||||
| Cazimord | 15 | 25 | 126 | 91.5 | 221 |
|
||||
|
||||
### 차별화 포인트
|
||||
|
||||
#### Hilda - 방어형 탱커
|
||||
- **핵심 시스템**: Blocking
|
||||
- **강점**: 최고 생존력
|
||||
- **약점**: 낮은 DPS
|
||||
- **플레이스타일**: ...
|
||||
|
||||
#### Baran - CC 특화 전사
|
||||
...
|
||||
|
||||
#### Cazimord - 고숙련도 DPS 전사
|
||||
...
|
||||
```
|
||||
|
||||
#### 검증 체크리스트
|
||||
- [ ] 5개 역할군 모두 분석됨
|
||||
- [ ] 각 역할군 공통점 명시
|
||||
- [ ] 스탯/DPS 비교표 정확성
|
||||
- [ ] 차별화 포인트 명확함
|
||||
- [ ] 신규 스토커 역할 위치 명확
|
||||
|
||||
### 12.5 04단계: 밸런스 티어 및 개선안
|
||||
|
||||
#### 목적
|
||||
- 종합 티어 평가 (OP/S+/S/A/B)
|
||||
- DPS, 유틸리티별 티어
|
||||
- 밸런스 개선안 제시
|
||||
|
||||
#### 입력
|
||||
- `02_DPS_시나리오_비교분석_v2.md` (DPS 데이터)
|
||||
- `03_역할별_차별화_v2.md` (역할 분석)
|
||||
|
||||
#### 출력
|
||||
- `04_밸런스_티어_및_개선안_v2.md`
|
||||
|
||||
#### 티어 기준
|
||||
|
||||
**종합 티어** (DPS + 유틸리티):
|
||||
- **OP** (Overpowered): 과도한 성능, 즉시 조정 필요
|
||||
- **S+**: 최상위, 역할 모델
|
||||
- **S**: 상위, 경쟁력 우수
|
||||
- **A**: 중상위, 밸런스 양호
|
||||
- **B**: 중하위, 개선 필요
|
||||
|
||||
**평가 지표**:
|
||||
```python
|
||||
종합_점수 = (로테이션_DPS × 0.4) + (버스트_DPS × 0.3) + (유틸리티_점수 × 0.3)
|
||||
|
||||
# 유틸리티 점수 (0~20점)
|
||||
유틸리티_점수 = CC점수 + 생존력점수 + 기동성점수 + 팀기여점수
|
||||
```
|
||||
|
||||
#### 출력 구조
|
||||
|
||||
```markdown
|
||||
## 1. 종합 티어표
|
||||
|
||||
| 티어 | 스토커 | 로테이션 DPS | 유틸리티 | 주요 강점 | 밸런스 상태 |
|
||||
|------|--------|--------------|----------|-----------|-------------|
|
||||
| **OP** | Rio | 268 | 13점 | 압도적 DPS | ⚠️ 너프 필요 |
|
||||
| **S+** | Cazimord | 221 | 15점 | 버스트 1위 | ✅ 양호 |
|
||||
| ... |
|
||||
|
||||
## 2. DPS 티어별 분석
|
||||
|
||||
### OP 티어 (너프 필요)
|
||||
**Rio**:
|
||||
- 현재 DPS: 268
|
||||
- 문제점: 2위보다 +21% 과다
|
||||
- 개선안:
|
||||
1. Chain Score 배율 감소 (150% → 100%)
|
||||
2. 연속 찌르기 쿨타임 증가 (3.5초 → 5초)
|
||||
3. 예상 DPS: 220 (-18%)
|
||||
|
||||
### B 티어 (버프 필요)
|
||||
**Urud**:
|
||||
- 현재 DPS: 82
|
||||
- 문제점: Reload 페널티 과다
|
||||
- 개선안:
|
||||
1. 재장전 시간 감소 (2.0초 → 1.5초)
|
||||
2. 탄약 증가 (6발 → 8발)
|
||||
3. 예상 DPS: 105 (+28%)
|
||||
```
|
||||
|
||||
#### 검증 체크리스트
|
||||
- [ ] 10명 모두 티어 배정됨
|
||||
- [ ] 티어 기준 명확함
|
||||
- [ ] DPS 격차 분석 정확성
|
||||
- [ ] 개선안 구체적 (수치 포함)
|
||||
- [ ] 예상 DPS 재계산됨
|
||||
|
||||
### 12.6 전체 프로세스 검증
|
||||
|
||||
#### 일관성 체크
|
||||
- [ ] 01~04단계 스토커 순서 동일
|
||||
- [ ] 01단계 BaseDamage = 02단계 BaseDamage
|
||||
- [ ] 02단계 DPS = 03단계 DPS
|
||||
- [ ] 03단계 분석 = 04단계 티어 근거
|
||||
|
||||
#### 데이터 무결성
|
||||
- [ ] 중간 파일 존재 (intermediate_data.json, validated_data.json)
|
||||
- [ ] 모든 스킬 ID 일치
|
||||
- [ ] 소환체/DoT 데이터 누락 없음
|
||||
|
||||
#### 문서 품질
|
||||
- [ ] Markdown 형식 정확성
|
||||
- [ ] 표 정렬 일관성
|
||||
- [ ] 계산식 명시
|
||||
- [ ] 출처 표시
|
||||
|
||||
---
|
||||
|
||||
## 📊 13. v2.1 업데이트 - 콤보 캔슬 시스템 발견 (2025-10-28)
|
||||
|
||||
### 13.1 주요 발견사항
|
||||
|
||||
#### 13.1.1 콤보 캔슬 시스템 (Game Changer!)
|
||||
|
||||
**발견 배경**:
|
||||
- AnimMontage.json 분석 중 `ANS_DisableBlockingState_C` 노티파이 발견
|
||||
- 특정 스토커의 평타 모션에서 조기 캔슬이 가능함을 확인
|
||||
|
||||
**노티파이 구조**:
|
||||
```json
|
||||
{
|
||||
"NotifyName": "ANS_DisableBlockingState_C",
|
||||
"TriggerTime": 2.73,
|
||||
"Duration": 1.0,
|
||||
"NotifyType": "NotifyState",
|
||||
"NotifyStateClass": "ANS_DisableBlockingState_C"
|
||||
}
|
||||
```
|
||||
|
||||
**캔슬 가능 시점 계산**:
|
||||
```python
|
||||
cancellable_time = TriggerTime + Duration
|
||||
# 예: 2.73 + 1.0 = 3.73초
|
||||
|
||||
# 원본 actualDuration: 4.57초
|
||||
# 캔슬 시간: 3.73초
|
||||
# 시간 단축: (4.57 - 3.73) / 4.57 = 18.4% ≈ 19%
|
||||
```
|
||||
|
||||
**적용 대상 스토커**:
|
||||
|
||||
| 스토커 | 무기 타입 | 원본 시간 | 캔슬 시간 | 단축율 | DPS 변화 |
|
||||
|--------|-----------|-----------|-----------|--------|----------|
|
||||
| **클라드** | oneHandWeapon (Mace) | 4.17초 | 1.84초 | **56%** 🔥 | 52.9 → 125.5 (+137%) |
|
||||
| **힐다** | weaponShield | 4.57초 | 3.69초 | 19% | 87.3 → 107.3 (+23%) |
|
||||
| **바란** | twoHandWeapon | 5.53초 | 4.48초 | 19% | 79.0 → 90.4 (+14%) |
|
||||
|
||||
**영향도 분석**:
|
||||
- **클라드**: 서포터임에도 **평타 DPS 1위** 달성 (125.5)
|
||||
- **힐다**: 탱커 중 최고 DPS 달성 (107.3)
|
||||
- **바란**: 중상위권으로 상승 (90.4)
|
||||
|
||||
#### 13.1.2 바란 궁극기 시전시간 정정
|
||||
|
||||
**문제점**:
|
||||
- DT_Skill에서 castingTime: 10초로 표기
|
||||
- 실제로는 즉발이 아님
|
||||
|
||||
**해결**:
|
||||
AnimMontage.json의 `AN_SimpleSendEvent_C` 노티파이 확인
|
||||
```json
|
||||
{
|
||||
"NotifyClass": "AN_SimpleSendEvent_C",
|
||||
"TriggerTime": 1.2927,
|
||||
"CustomProperties": {
|
||||
"Event Tag": "(TagName=\"Ability.Attack.Ready\")"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**정정 내용**:
|
||||
- **실제 시전시간**: 1.29초 (AN_SimpleSendEvent TriggerTime)
|
||||
- **10초의 의미**: 최대 홀딩 시간 (대검을 들고 있으면서 타이밍 조절 가능)
|
||||
- **DPS 영향**: 버스트 DPS 128.5 → 123.3 (-4%)
|
||||
|
||||
**스크립트 자동 처리**:
|
||||
```python
|
||||
# extract_stalker_data_v2.py (line 669-679)
|
||||
if skill_id == 'SK130301': # 바란 궁극기
|
||||
for montage_data in skill_data['montageData']:
|
||||
for notify in montage_data.get('allNotifies', []):
|
||||
if 'SimpleSendEvent' in notify.get('NotifyClass', ''):
|
||||
event_tag = notify.get('CustomProperties', {}).get('Event Tag', '')
|
||||
if 'Ability.Attack.Ready' in event_tag:
|
||||
trigger_time = notify.get('TriggerTime', 0)
|
||||
skill_data['castingTime'] = round(trigger_time, 2)
|
||||
break
|
||||
```
|
||||
|
||||
#### 13.1.3 레네 궁극기 실전 필수화
|
||||
|
||||
**배경**:
|
||||
- 초기 분석: 궁극기 제외 (순수 DPS 우선)
|
||||
- 실제 플레이: 흡혈 50% 효과로 생존 필수
|
||||
|
||||
**궁극기 효과**:
|
||||
- 마석 '붉은 축제' (SK160301)
|
||||
- 시전시간: 1.5초
|
||||
- 지속시간: 20초
|
||||
- 효과: 자신과 아군 모든 공격에 흡혈 50%
|
||||
|
||||
**DPS 트레이드오프**:
|
||||
- **이전 (궁극기 제외)**: 186.4 DPS (15초 버스트 1위)
|
||||
- **현재 (궁극기 포함)**: 136.7 DPS (15초 버스트 4위)
|
||||
- **감소율**: -26.6%
|
||||
- **보상**: 생존력 확보 (실전 필수)
|
||||
|
||||
### 13.2 버스트 시나리오 확대 (10초 → 15초)
|
||||
|
||||
**변경 이유**:
|
||||
1. 궁극기 시전시간 포함 시 10초 부족
|
||||
2. 대부분 궁극기 지속시간 15초 이상
|
||||
3. 실전 버스트 상황에 더 부합
|
||||
|
||||
**새로운 버스트 DPS 순위** (15초):
|
||||
|
||||
| 순위 | 스토커 | 15초 DPS | 궁극기 | 주요 변화 |
|
||||
|------|--------|----------|--------|----------|
|
||||
| 1 | 카지모르드 | 165.1 | ✅ | 2위 → 1위 (Parrying + 궁극기) |
|
||||
| 2 | 리오 | 146.9 | ✅ | 변동 없음 |
|
||||
| 3 | 시노부 | 142.7 | ❌ | 변동 없음 (궁극기 제외) |
|
||||
| 4 | **레네** | 136.7 | ✅ | **1위 → 4위** (흡혈 생존력) |
|
||||
| 5 | 클라드 | 125.4 | ❌ | 변동 없음 (콤보 캔슬) |
|
||||
| 6 | **바란** | 123.3 | ✅ | **5위 → 6위** (시전시간 정정) |
|
||||
|
||||
### 13.3 config.py 업데이트 내용
|
||||
|
||||
**추가된 설정**:
|
||||
|
||||
```python
|
||||
# 콤보 캔슬 시스템 (v2.1)
|
||||
COMBO_CANCEL_STALKERS = {
|
||||
'hilda': {
|
||||
'weapons': ['weaponShield'],
|
||||
'patterns': ['AM_PC_Hilda_B_Attack_W01_'],
|
||||
'time_reduction': 0.19, # 19% 시간 단축
|
||||
'description': '3타 콤보 캔슬 (4.57s → 3.69s)'
|
||||
},
|
||||
'baran': {
|
||||
'weapons': ['twoHandWeapon'],
|
||||
'patterns': ['AM_PC_Baran_B_Attack_W01_'],
|
||||
'time_reduction': 0.19,
|
||||
'description': '평타 콤보 캔슬 (5.53s → 4.48s)'
|
||||
},
|
||||
'clad': {
|
||||
'weapons': ['oneHandWeapon'],
|
||||
'patterns': ['AM_PC_Clad_Base_Attack_Mace'],
|
||||
'time_reduction': 0.56, # 56% 시간 단축 (극적!)
|
||||
'description': '평타 콤보 캔슬 (4.17s → 1.84s)'
|
||||
}
|
||||
}
|
||||
|
||||
# 특수 궁극기 처리 (v2.1)
|
||||
SPECIAL_ULTIMATE_HANDLING = {
|
||||
'SK130301': { # 바란 - 일격분쇄
|
||||
'stalker': 'baran',
|
||||
'use_an_simplesendevent_time': True,
|
||||
'event_tag': 'Ability.Attack.Ready',
|
||||
'description': 'AN_SimpleSendEvent 시점(1.29초)이 실제 발동 시간'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 13.4 extract_stalker_data_v2.py 업데이트
|
||||
|
||||
**콤보 캔슬 추출 로직** (line 277-415):
|
||||
```python
|
||||
def extract_anim_montages(montages: List[Dict]) -> Dict[str, Dict]:
|
||||
# 콤보 캔슬 적용 대상 스토커 및 패턴
|
||||
CANCEL_TARGETS = {
|
||||
'hilda': ['AM_PC_Hilda_B_Attack_W01_'],
|
||||
'baran': ['AM_PC_Baran_B_Attack_W01_'],
|
||||
'clad': ['AM_PC_Clad_Base_Attack_Mace']
|
||||
}
|
||||
|
||||
for montage in pc_montages:
|
||||
# 콤보 캔슬 적용 대상 판별
|
||||
is_cancel_target = False
|
||||
for stalker_name, patterns in CANCEL_TARGETS.items():
|
||||
for pattern in patterns:
|
||||
if pattern in asset_name:
|
||||
is_cancel_target = True
|
||||
break
|
||||
|
||||
# ANS_DisableBlockingState_C에서 캔슬 시간 추출
|
||||
if is_cancel_target and 'ANS_DisableBlockingState' in notify_state_class:
|
||||
trigger_time = notify.get('TriggerTime', 0)
|
||||
duration = notify.get('Duration', 0)
|
||||
cancellable_time = trigger_time + duration
|
||||
```
|
||||
|
||||
**바란 궁극기 특수 처리** (line 669-679):
|
||||
```python
|
||||
# 바란 궁극기 특수 처리: AN_SimpleSendEvent 시점을 castingTime으로 사용
|
||||
if skill_id == 'SK130301': # 바란 궁극기 '일격분쇄'
|
||||
for montage_data in skill_data['montageData']:
|
||||
for notify in montage_data.get('allNotifies', []):
|
||||
if 'SimpleSendEvent' in notify.get('NotifyClass', ''):
|
||||
event_tag = notify.get('CustomProperties', {}).get('Event Tag', '')
|
||||
if 'Ability.Attack.Ready' in event_tag:
|
||||
trigger_time = notify.get('TriggerTime', 0)
|
||||
skill_data['castingTime'] = round(trigger_time, 2)
|
||||
print(f" [INFO] {skill_id}: castingTime 오버라이드 {skill_data['castingTime']}초")
|
||||
break
|
||||
```
|
||||
|
||||
### 13.5 02단계 문서 업데이트 내용
|
||||
|
||||
**시나리오 1 - 평타 DPS 순위 변화**:
|
||||
```markdown
|
||||
| 순위 | 스토커 | Raw DPS | 특징 |
|
||||
|------|--------|---------|------|
|
||||
| 1 | **클라드** | **125.5** | ⚡ 콤보 캔슬 (+137% DPS!) |
|
||||
| 2 | **힐다** | **107.3** | ⚡ 콤보 캔슬 (+23%) |
|
||||
| 3 | 시노부 | 97.83 | 표창 충전 시스템 |
|
||||
| 4 | **바란** | **90.4** | ⚡ 콤보 캔슬 (+14%) |
|
||||
```
|
||||
|
||||
**시나리오 2 - 30초 로테이션 변화**:
|
||||
- 클라드: 60.1 → 133.6 DPS (+122%, **7위 → 3위**)
|
||||
- 힐다: 92.1 → 114.1 DPS (+24%)
|
||||
- 바란: 97.9 → 111.4 DPS (+14%)
|
||||
|
||||
**시나리오 3 - 15초 버스트 (신설)**:
|
||||
- 기존 10초 → 15초로 확대
|
||||
- 궁극기 사용 정책 명확화:
|
||||
- 기본: 0초 시점 사용
|
||||
- 예외: 클라드/시노부 (방어 궁극기 제외)
|
||||
- 특수: 카지모르드 (작열 → 섬광 → 궁극기)
|
||||
|
||||
**중간 결론 섹션 신설**:
|
||||
- DPS 기준 종합 티어표 (3개 시나리오 통합 평가)
|
||||
- 밸런스 개선 제안 (C/B티어 수치 조정안)
|
||||
|
||||
### 13.6 검증 체크리스트
|
||||
|
||||
**v2.1 검증 항목**:
|
||||
- [x] 콤보 캔슬 시스템 config.py 추가
|
||||
- [x] 바란 궁극기 특수 처리 스크립트 반영
|
||||
- [x] extract_stalker_data_v2.py 업데이트
|
||||
- [x] 01 문서 바란 궁극기 시전시간 정정
|
||||
- [x] 02 문서 3개 시나리오 콤보 캔슬 반영
|
||||
- [x] 02 문서 15초 버스트 시나리오 재작성
|
||||
- [x] 02 문서 중간 결론 섹션 작성
|
||||
- [x] 종합 티어표 3개 시나리오 통합 평가
|
||||
- [x] 밸런스 개선 제안 (리안, 우르드, 네이브)
|
||||
|
||||
---
|
||||
|
||||
**작성자**: AI-assisted Analysis Team
|
||||
**최종 업데이트**: 2025-10-27
|
||||
**버전**: 1.0
|
||||
**최종 업데이트**: 2025-10-28
|
||||
**버전**: 2.1
|
||||
|
||||
152
README.md
152
README.md
@ -72,26 +72,126 @@ JSON Files → Claude Code → Analysis Document
|
||||
|
||||
분석 결과를 검토하고 최종 문서를 생성합니다.
|
||||
|
||||
---
|
||||
|
||||
## v2 분석 프로세스 (2025-10 업데이트)
|
||||
|
||||
### 프로세스 개요
|
||||
|
||||
v2 시스템은 JSON 원본에서 밸런스 리포트까지 **4단계 자동화 파이프라인**입니다.
|
||||
|
||||
```
|
||||
[원본 JSON] → 01단계 → 02단계 → 03단계 → 04단계
|
||||
기본데이터 DPS계산 역할비교 밸런스티어
|
||||
```
|
||||
|
||||
### 🔥 v2.1 주요 업데이트 (2025-10-28)
|
||||
|
||||
**Game Changer 발견!**
|
||||
|
||||
#### 1. 콤보 캔슬 시스템 발견
|
||||
- `ANS_DisableBlockingState_C` 노티파이로 평타 조기 캔슬 가능
|
||||
- **클라드**: 평타 시간 56% 단축 → **평타 DPS 1위** (125.5)
|
||||
- **힐다**: 19% 단축, 탱커 최고 DPS (107.3)
|
||||
- **바란**: 19% 단축, 중상위권 (90.4)
|
||||
|
||||
#### 2. 바란 궁극기 시전시간 정정
|
||||
- DT_Skill 10초 → **실제 1.29초** (AN_SimpleSendEvent 시점)
|
||||
- 10초는 최대 홀딩 시간 (타이밍 조절 가능)
|
||||
- 스크립트 자동 감지 및 오버라이드
|
||||
|
||||
#### 3. 15초 버스트 시나리오 확대
|
||||
- 기존 10초 → **15초**로 확대 (궁극기 지속시간 반영)
|
||||
- 레네 궁극기 실전 필수 (흡혈 생존력)
|
||||
- 종합 티어표 3개 시나리오 통합 평가
|
||||
|
||||
### 4단계 구성
|
||||
|
||||
#### 01단계: 스토커별 기본 데이터
|
||||
- **목적**: JSON에서 10명 스토커 정보 추출 및 검증
|
||||
- **출력**: `01_스토커별_기본데이터_v2.md`
|
||||
- **도구**: `분석도구/v2/` (extract → validate → generate)
|
||||
|
||||
#### 02단계: DPS 시나리오 비교분석
|
||||
- **목적**: 3개 시나리오 DPS 계산 (평타, 로테이션, 버스트)
|
||||
- **출력**: `02_DPS_시나리오_비교분석_v2.md`
|
||||
- **도구**: `calculate_dps_scenarios_v2.py`
|
||||
|
||||
#### 03단계: 역할별 차별화
|
||||
- **목적**: 5개 역할군 비교 (전사, 원거리, 마법사, 암살자, 서포터)
|
||||
- **출력**: `03_역할별_차별화_v2.md`
|
||||
|
||||
#### 04단계: 밸런스 티어 및 개선안
|
||||
- **목적**: OP/S+/S/A/B 티어 평가 및 밸런스 개선안
|
||||
- **출력**: `04_밸런스_티어_및_개선안_v2.md`
|
||||
|
||||
### 실행 방법
|
||||
|
||||
**전체 프로세스**:
|
||||
```bash
|
||||
# 1단계: 기본 데이터 추출 및 검증
|
||||
cd 분석도구/v2
|
||||
python extract_stalker_data_v2.py
|
||||
python validate_stalker_data.py
|
||||
python generate_stalker_docs_v2.py
|
||||
|
||||
# 2단계: DPS 계산
|
||||
python calculate_dps_scenarios_v2.py
|
||||
|
||||
# 3~4단계: 추후 구현 예정
|
||||
```
|
||||
|
||||
**출력 구조**:
|
||||
```
|
||||
분석결과/YYYYMMDD_HHMMSS_v2/
|
||||
├── 01_스토커별_기본데이터_v2.md
|
||||
├── 02_DPS_시나리오_비교분석_v2.md
|
||||
├── 03_역할별_차별화_v2.md
|
||||
├── 04_밸런스_티어_및_개선안_v2.md
|
||||
├── intermediate_data.json
|
||||
├── validated_data.json
|
||||
└── 검증_리포트.md
|
||||
```
|
||||
|
||||
### 상세 문서
|
||||
|
||||
- [ARCHITECTURE.md](ARCHITECTURE.md) - v2 프로세스 완전 상세 가이드 (알고리즘, 체크리스트 포함)
|
||||
|
||||
---
|
||||
|
||||
## 폴더 구조
|
||||
|
||||
```
|
||||
DS-전투분석_저장소/
|
||||
├── README.md # 본 문서
|
||||
├── ARCHITECTURE.md # 기술 아키텍처 (v2 프로세스 상세)
|
||||
│
|
||||
├── 분석결과/ # 분석 결과물
|
||||
│ └── 20251023/ # 날짜별 분석
|
||||
│ └── DS-전투시스템_종합분석.md
|
||||
│ ├── 20251024_000515/ # 기존 분석 (참고용)
|
||||
│ └── YYYYMMDD_HHMMSS_v2/ # v2 분석 결과
|
||||
│ ├── 01_스토커별_기본데이터_v2.md
|
||||
│ ├── 02_DPS_시나리오_비교분석_v2.md
|
||||
│ ├── 03_역할별_차별화_v2.md
|
||||
│ ├── 04_밸런스_티어_및_개선안_v2.md
|
||||
│ ├── intermediate_data.json
|
||||
│ ├── validated_data.json
|
||||
│ └── 검증_리포트.md
|
||||
│
|
||||
├── 원본데이터/ # JSON 원본 데이터 (샘플)
|
||||
│ └── 20251023/
|
||||
│ ├── 샘플_DataTable.json
|
||||
│ ├── 샘플_AnimMontage.json
|
||||
│ └── 샘플_Blueprint.json
|
||||
├── 원본데이터/ # JSON 원본 데이터
|
||||
│ ├── DataTable.json
|
||||
│ ├── AnimMontage.json
|
||||
│ ├── Blueprint.json
|
||||
│ └── CurveTable.json
|
||||
│
|
||||
└── 분석도구/ # Python 분석 스크립트
|
||||
├── extract_skill_cancel_windows.py
|
||||
├── analyze_character_stats.py
|
||||
└── extract_activation_order_groups.py
|
||||
├── v2/ # v2 자동화 도구
|
||||
│ ├── extract_stalker_data_v2.py
|
||||
│ ├── validate_stalker_data.py
|
||||
│ ├── generate_stalker_docs_v2.py
|
||||
│ ├── calculate_dps_scenarios_v2.py # 개발 예정
|
||||
│ └── config.py
|
||||
└── utils/ # 유틸리티 스크립트
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 정기 분석 수행 가이드
|
||||
@ -217,14 +317,15 @@ Hilda:
|
||||
|
||||
## 최신 분석 결과
|
||||
|
||||
**날짜**: 2025-10-23
|
||||
**분석 문서**: [분석결과/20251023/DS-전투시스템_종합분석.md](분석결과/20251023/DS-전투시스템_종합분석.md)
|
||||
**날짜**: 2025-10-27 (v2.1)
|
||||
**분석 문서**: `분석결과/20251027_200151_v2/`
|
||||
|
||||
**주요 발견**:
|
||||
- **공격 속도 1위**: Rio (3.867s)
|
||||
- **가장 높은 피해**: Baran, Clad (평타 +30~50%)
|
||||
- **궁극기 보유**: Nave, Baran, Sinobu, Cazimord (4명만)
|
||||
- **가장 다양한 우선순위**: Cazimord (Group 0, 2, 3, 4, 9 모두 사용)
|
||||
**주요 발견 (v2.1)**:
|
||||
- **평타 DPS 1위**: 클라드 (125.5) - 콤보 캔슬로 137% 증가!
|
||||
- **30초 로테이션 1위**: 레네 (158.88) - 소환수 독립 DPS
|
||||
- **15초 버스트 1위**: 카지모르드 (165.1) - Parrying + 궁극기
|
||||
- **종합 S티어**: 시노부, 레네 (모든 시나리오 안정적 상위권)
|
||||
- **밸런스 개선 필요**: 리안, 우르드 (재장전/충전 페널티 과다)
|
||||
|
||||
## 기술 스택
|
||||
|
||||
@ -252,5 +353,20 @@ Hilda:
|
||||
|
||||
---
|
||||
|
||||
**마지막 업데이트**: 2025-10-23
|
||||
**마지막 업데이트**: 2025-10-28 (v2.1)
|
||||
**담당자**: AI-assisted Analysis Team
|
||||
|
||||
## 변경 이력
|
||||
|
||||
### v2.1 (2025-10-28)
|
||||
- **콤보 캔슬 시스템 발견** (ANS_DisableBlockingState_C)
|
||||
- 바란 궁극기 시전시간 정정 (10초 → 1.29초)
|
||||
- 15초 버스트 시나리오로 확대 (10초 → 15초)
|
||||
- 종합 티어표 3개 시나리오 통합 평가
|
||||
- 밸런스 개선 제안 (리안, 우르드, 네이브)
|
||||
|
||||
### v2.0 (2025-10-27)
|
||||
- 4단계 자동화 파이프라인 구축
|
||||
- 01~02단계 문서 생성 완료
|
||||
- 소환수 독립 DPS 계산 (레네)
|
||||
- DoT 시스템 분석 (Poison, Burn, Bleed)
|
||||
|
||||
196
분석결과/20251027_153101_v2/02_DPS_시나리오_비교분석_v2.md
Normal file
196
분석결과/20251027_153101_v2/02_DPS_시나리오_비교분석_v2.md
Normal file
@ -0,0 +1,196 @@
|
||||
# DPS 시나리오 비교 분석 v2
|
||||
|
||||
**생성 시각**: 2025-10-27 17:46:37
|
||||
|
||||
---
|
||||
|
||||
## 📋 분석 전제 조건
|
||||
|
||||
### 기본 설정
|
||||
- **레벨**: 20
|
||||
- **기어 스코어**: 400
|
||||
- **플레이 스타일**: 최적 플레이
|
||||
- **쿨타임 감소**: 25.0% (왜곡 룬)
|
||||
|
||||
### BaseDamage 계산식
|
||||
|
||||
```python
|
||||
# 물리 딜러 (STR/DEX)
|
||||
Physical_BaseDamage = (주스탯 + 80) × 1.20
|
||||
|
||||
# 마법 딜러 (INT)
|
||||
Magical_BaseDamage = (INT + 80) × 1.10
|
||||
|
||||
# 탱커/서포터
|
||||
Support_BaseDamage = (주스탯 + 80) × 1.00
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗡️ 시나리오 1: 평타 DPS
|
||||
|
||||
**목적**: 순수 평타만으로 지속 딜 측정
|
||||
|
||||
| 순위 | 스토커 | BaseDamage | 평타 DPS | 콤보 시간 | 평타 배율 합계 |
|
||||
|------|--------|------------|----------|-----------|----------------|
|
||||
| 1 | 시노부 | 126.0 | 88.94 | 2.27초 | 1.6 |
|
||||
| 2 | 힐다 | 120.0 | 78.83 | 4.57초 | 3.0 |
|
||||
| 3 | 리오 | 126.0 | 76.58 | 3.87초 | 2.35 |
|
||||
| 4 | 바란 | 126.0 | 72.43 | 5.57초 | 3.2 |
|
||||
| 5 | 네이브 | 115.5 | 70.0 | 3.3초 | 2.0 |
|
||||
| 6 | 카지모르드 | 126.0 | 69.57 | 5.43초 | 3.0 |
|
||||
| 7 | 레네 | 110.0 | 55.93 | 5.9초 | 3.0 |
|
||||
| 8 | 클라드 | 95.0 | 47.88 | 4.17초 | 2.1 |
|
||||
| 9 | 리안 | 120.0 | 36.73 | 3.27초 | 1.0 |
|
||||
| 10 | 우르드 | 120.0 | 36.55 | 3.28초 | 1.0 |
|
||||
|
||||
---
|
||||
|
||||
## ⚔️ 시나리오 2: 스킬 로테이션 DPS (30초)
|
||||
|
||||
**목적**: 스킬 + 평타 조합한 실전 DPS
|
||||
|
||||
| 순위 | 스토커 | 로테이션 DPS | 스킬 피해 | 평타 피해 | 평타 사용 시간 |
|
||||
|------|--------|--------------|-----------|-----------|----------------|
|
||||
| 1 | 시노부 | 80.94 | 2268.0 | 160.09 | 1.8초 |
|
||||
| 2 | 우르드 | 74.26 | 2184.0 | 43.86 | 1.2초 |
|
||||
| 3 | 바란 | 71.31 | 611.1 | 1528.27 | 21.1초 |
|
||||
| 4 | 리오 | 68.13 | 2028.6 | 15.32 | 0.2초 |
|
||||
| 5 | 힐다 | 67.3 | 600.0 | 1418.94 | 18.0초 |
|
||||
| 6 | 클라드 | 60.1 | 855.0 | 948.02 | 19.8초 |
|
||||
| 7 | 네이브 | 50.27 | 381.15 | 1127.0 | 16.1초 |
|
||||
| 8 | 카지모르드 | 36.2 | 327.6 | 758.31 | 10.9초 |
|
||||
| 9 | 레네 | 30.44 | 907.5 | 5.59 | 0.1초 |
|
||||
| 10 | 리안 | 16.46 | 336.0 | 157.94 | 4.3초 |
|
||||
|
||||
---
|
||||
|
||||
## 💥 시나리오 3: 버스트 DPS (10초)
|
||||
|
||||
**목적**: 궁극기 포함 최대 화력
|
||||
|
||||
| 순위 | 스토커 | 버스트 DPS | 궁극기 | 궁극기 피해 | 스킬 피해 | 평타 피해 |
|
||||
|------|--------|------------|--------|-------------|-----------|----------|
|
||||
| 1 | 바란 | 65.93 | ✅ | 0 | 321.3 | 338.04 |
|
||||
| 2 | 시노부 | 55.82 | ✅ | 0.0 | 453.6 | 104.61 |
|
||||
| 3 | 클라드 | 53.99 | ❌ | 0 | 142.5 | 397.4 |
|
||||
| 4 | 리안 | 51.65 | ❌ | 0 | 426.0 | 90.49 |
|
||||
| 5 | 우르드 | 51.08 | ✅ | 120.0 | 312.0 | 78.79 |
|
||||
| 6 | 리오 | 46.86 | ✅ | 37.8 | 378.0 | 52.82 |
|
||||
| 7 | 힐다 | 46.13 | ✅ | 60.0 | 252.0 | 149.34 |
|
||||
| 8 | 레네 | 34.69 | ❌ | 0 | 242.0 | 104.87 |
|
||||
| 9 | 네이브 | 33.66 | ✅ | 115.5 | 57.75 | 163.33 |
|
||||
| 10 | 카지모르드 | 23.14 | ✅ | 100.8 | 126.0 | 4.64 |
|
||||
|
||||
---
|
||||
|
||||
## 🔥 특수 상황 분석
|
||||
|
||||
### 1. DoT 스킬 DPS (대상 HP별)
|
||||
|
||||
#### 독성 화살 (우르드)
|
||||
- **DoT 타입**: Poison
|
||||
- **설명**: 대상 MaxHP의 20% (5초간)
|
||||
|
||||
| 대상 HP | DoT DPS |
|
||||
|---------|----------|
|
||||
| 100HP | 4.0 |
|
||||
| 500HP | 20.0 |
|
||||
| 1000HP | 40.0 |
|
||||
|
||||
#### 독기 화살 (레네)
|
||||
- **DoT 타입**: Bleed
|
||||
- **설명**: 고정 20 피해 (5초간)
|
||||
|
||||
| 대상 HP | DoT DPS |
|
||||
|---------|----------|
|
||||
| 100HP | 4.0 |
|
||||
| 500HP | 4.0 |
|
||||
| 1000HP | 4.0 |
|
||||
|
||||
#### 작열 (카지모르드)
|
||||
- **DoT 타입**: Burn
|
||||
- **설명**: 대상 MaxHP의 10% (3초간)
|
||||
|
||||
| 대상 HP | DoT DPS |
|
||||
|---------|----------|
|
||||
| 100HP | 3.33 |
|
||||
| 500HP | 16.67 |
|
||||
| 1000HP | 33.33 |
|
||||
|
||||
#### 정령 소환: 화염 (레네)
|
||||
- **DoT 타입**: Burn
|
||||
- **설명**: 대상 MaxHP의 10% (3초간)
|
||||
|
||||
| 대상 HP | DoT DPS |
|
||||
|---------|----------|
|
||||
| 100HP | 3.33 |
|
||||
| 500HP | 16.67 |
|
||||
| 1000HP | 33.33 |
|
||||
|
||||
|
||||
### 2. 소환체 독립 DPS
|
||||
|
||||
#### 정령 소환: 화염
|
||||
- **소환체**: Ifrit
|
||||
- **지속 시간**: 20초
|
||||
- **공격 사이클**: 1.46초
|
||||
- **총 공격 횟수**: 13.71회
|
||||
- **독립 DPS**: 90.51
|
||||
- **비고**: 1개 몽타주 순차 루프
|
||||
|
||||
#### 정령 소환: 냉기
|
||||
- **소환체**: Shiva
|
||||
- **지속 시간**: 60초
|
||||
- **공격 사이클**: 2.32초
|
||||
- **총 공격 횟수**: 25.86회
|
||||
- **독립 DPS**: 37.93
|
||||
- **비고**: 단일 몽타주 반복
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 🎯 신규 스토커 상세 분석: Cazimord
|
||||
|
||||
### 평타 DPS
|
||||
|
||||
- **BaseDamage**: 126.0
|
||||
- **평타 DPS**: 69.57
|
||||
- **콤보 시간**: 5.43초
|
||||
- **평타 배율 합계**: 3.0
|
||||
|
||||
**평타 콤보 구성**:
|
||||
|
||||
1타: 1.67초, 배율 0.85
|
||||
2타: 1.9초, 배율 1.05
|
||||
3타: 1.87초, 배율 1.1
|
||||
|
||||
### 스킬 로테이션 DPS (30초)
|
||||
|
||||
- **로테이션 DPS**: 36.2
|
||||
- **스킬 피해**: 327.6
|
||||
- **평타 피해**: 758.31
|
||||
- **평타 사용 시간**: 10.9초
|
||||
|
||||
**스킬 사용 내역**:
|
||||
|
||||
- 섬광: 2회, 총 피해 126.0
|
||||
- 날개 베기: 2회, 총 피해 75.6
|
||||
- 작열: 1회, 총 피해 126.0
|
||||
|
||||
### 버스트 DPS (10초)
|
||||
|
||||
- **버스트 DPS**: 23.14
|
||||
- **궁극기 피해**: 100.8
|
||||
- **스킬 피해**: 126.0
|
||||
- **평타 피해**: 4.64
|
||||
|
||||
**스킬 사용 순서**:
|
||||
|
||||
5.5초: 마석 '칼날폭풍' (피해 100.8)
|
||||
9.93초: 작열 (피해 126.0)
|
||||
|
||||
---
|
||||
|
||||
**작성자**: AI-assisted Analysis System
|
||||
**생성 시각**: 2025-10-27 17:46:37
|
||||
1145
분석결과/20251027_200151_v2/01_분석_기초자료_v2.md
Normal file
1145
분석결과/20251027_200151_v2/01_분석_기초자료_v2.md
Normal file
File diff suppressed because it is too large
Load Diff
1135
분석결과/20251027_200151_v2/02_DPS_시나리오_비교분석_v2.md
Normal file
1135
분석결과/20251027_200151_v2/02_DPS_시나리오_비교분석_v2.md
Normal file
File diff suppressed because it is too large
Load Diff
839
분석결과/20251027_200151_v2/dps_raw_results.json
Normal file
839
분석결과/20251027_200151_v2/dps_raw_results.json
Normal file
@ -0,0 +1,839 @@
|
||||
{
|
||||
"hilda": {
|
||||
"scenario1": {
|
||||
"dps": 78.83,
|
||||
"combo_time": 4.57,
|
||||
"total_multiplier": 3.0,
|
||||
"base_damage": 120.0,
|
||||
"attacks": [
|
||||
{
|
||||
"index": 1,
|
||||
"name": "AM_PC_Hilda_B_Attack_W01_01",
|
||||
"duration": 1.6,
|
||||
"multiplier": 1.0
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"name": "AM_PC_Hilda_B_Attack_W01_02",
|
||||
"duration": 1.6,
|
||||
"multiplier": 1.05
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"name": "AM_PC_Hilda_B_Attack_W01_03",
|
||||
"duration": 1.37,
|
||||
"multiplier": 0.95
|
||||
}
|
||||
],
|
||||
"notes": "",
|
||||
"rank": 2
|
||||
},
|
||||
"scenario2": {
|
||||
"dps": 67.3,
|
||||
"duration": 30.0,
|
||||
"base_damage": 120.0,
|
||||
"skill_damage": 600.0,
|
||||
"basic_damage": 1418.94,
|
||||
"basic_attack_time": 18.0,
|
||||
"skill_usage": {
|
||||
"SK100202": {
|
||||
"name": "반격",
|
||||
"count": 3,
|
||||
"damage": 288.0
|
||||
},
|
||||
"SK100201": {
|
||||
"name": "칼날 격돌",
|
||||
"count": 2,
|
||||
"damage": 312.0
|
||||
}
|
||||
},
|
||||
"notes": "",
|
||||
"rank": 5
|
||||
},
|
||||
"scenario3": {
|
||||
"dps": 46.13,
|
||||
"duration": 10.0,
|
||||
"base_damage": 120.0,
|
||||
"ultimate_damage": 60.0,
|
||||
"skill_damage": 252.0,
|
||||
"basic_damage": 149.34,
|
||||
"remaining_time": 1.89,
|
||||
"skill_order": [
|
||||
{
|
||||
"time": 3.5,
|
||||
"skill": "마석 ‘핏빛 달’",
|
||||
"damage": 60.0,
|
||||
"type": "ultimate"
|
||||
},
|
||||
{
|
||||
"time": 5.3,
|
||||
"skill": "칼날 격돌",
|
||||
"damage": 156.0,
|
||||
"type": "skill"
|
||||
},
|
||||
{
|
||||
"time": 8.11,
|
||||
"skill": "반격",
|
||||
"damage": 96.0,
|
||||
"type": "skill"
|
||||
}
|
||||
],
|
||||
"has_ultimate": true,
|
||||
"notes": "",
|
||||
"rank": 7
|
||||
}
|
||||
},
|
||||
"urud": {
|
||||
"scenario1": {
|
||||
"dps": 36.55,
|
||||
"combo_time": 3.28,
|
||||
"total_multiplier": 1.0,
|
||||
"base_damage": 120.0,
|
||||
"attacks": [
|
||||
{
|
||||
"index": 1,
|
||||
"name": "AM_PC_Urud_Base_B_Attack_N",
|
||||
"duration": 3.28,
|
||||
"multiplier": 1.0
|
||||
}
|
||||
],
|
||||
"notes": "",
|
||||
"rank": 10
|
||||
},
|
||||
"scenario2": {
|
||||
"dps": 74.26,
|
||||
"duration": 30.0,
|
||||
"base_damage": 120.0,
|
||||
"skill_damage": 2184.0,
|
||||
"basic_damage": 43.86,
|
||||
"basic_attack_time": 1.2,
|
||||
"skill_usage": {
|
||||
"SK110101": {
|
||||
"name": "화살 찌르기",
|
||||
"count": 26,
|
||||
"damage": 2184.0
|
||||
}
|
||||
},
|
||||
"notes": "",
|
||||
"rank": 2
|
||||
},
|
||||
"scenario3": {
|
||||
"dps": 51.08,
|
||||
"duration": 10.0,
|
||||
"base_damage": 120.0,
|
||||
"ultimate_damage": 120.0,
|
||||
"skill_damage": 312.0,
|
||||
"basic_damage": 78.79,
|
||||
"remaining_time": 2.16,
|
||||
"skill_order": [
|
||||
{
|
||||
"time": 3.5,
|
||||
"skill": "마석 ‘폭쇄’",
|
||||
"damage": 120.0,
|
||||
"type": "ultimate"
|
||||
},
|
||||
{
|
||||
"time": 5.12,
|
||||
"skill": "독성 화살",
|
||||
"damage": 120.0,
|
||||
"type": "skill"
|
||||
},
|
||||
{
|
||||
"time": 6.73,
|
||||
"skill": "다발 화살",
|
||||
"damage": 108.0,
|
||||
"type": "skill"
|
||||
},
|
||||
{
|
||||
"time": 7.84,
|
||||
"skill": "화살 찌르기",
|
||||
"damage": 84.0,
|
||||
"type": "skill"
|
||||
}
|
||||
],
|
||||
"has_ultimate": true,
|
||||
"notes": "",
|
||||
"rank": 5
|
||||
}
|
||||
},
|
||||
"nave": {
|
||||
"scenario1": {
|
||||
"dps": 70.0,
|
||||
"combo_time": 3.3,
|
||||
"total_multiplier": 2.0,
|
||||
"base_damage": 115.5,
|
||||
"attacks": [
|
||||
{
|
||||
"index": 1,
|
||||
"name": "AM_PC_Nave_B_Attack_W01_01",
|
||||
"duration": 1.6,
|
||||
"multiplier": 1.0
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"name": "AM_PC_Nave_B_Attack_W01_02",
|
||||
"duration": 1.7,
|
||||
"multiplier": 1.0
|
||||
}
|
||||
],
|
||||
"notes": "",
|
||||
"rank": 5
|
||||
},
|
||||
"scenario2": {
|
||||
"dps": 50.27,
|
||||
"duration": 30.0,
|
||||
"base_damage": 115.5,
|
||||
"skill_damage": 381.15,
|
||||
"basic_damage": 1127.0,
|
||||
"basic_attack_time": 16.1,
|
||||
"skill_usage": {
|
||||
"SK120201": {
|
||||
"name": "마법 화살",
|
||||
"count": 1,
|
||||
"damage": 92.4
|
||||
},
|
||||
"SK120202": {
|
||||
"name": "화염구",
|
||||
"count": 1,
|
||||
"damage": 231.0
|
||||
},
|
||||
"SK120206": {
|
||||
"name": "노대바람",
|
||||
"count": 1,
|
||||
"damage": 57.75
|
||||
}
|
||||
},
|
||||
"notes": "",
|
||||
"rank": 7
|
||||
},
|
||||
"scenario3": {
|
||||
"dps": 33.66,
|
||||
"duration": 10.0,
|
||||
"base_damage": 115.5,
|
||||
"ultimate_damage": 115.5,
|
||||
"skill_damage": 57.75,
|
||||
"basic_damage": 163.33,
|
||||
"remaining_time": 2.33,
|
||||
"skill_order": [
|
||||
{
|
||||
"time": 6.33,
|
||||
"skill": "마석 ‘해방’",
|
||||
"damage": 115.5,
|
||||
"type": "ultimate"
|
||||
},
|
||||
{
|
||||
"time": 7.67,
|
||||
"skill": "노대바람",
|
||||
"damage": 57.75,
|
||||
"type": "skill"
|
||||
}
|
||||
],
|
||||
"has_ultimate": true,
|
||||
"notes": "",
|
||||
"rank": 9
|
||||
}
|
||||
},
|
||||
"baran": {
|
||||
"scenario1": {
|
||||
"dps": 72.43,
|
||||
"combo_time": 5.57,
|
||||
"total_multiplier": 3.2,
|
||||
"base_damage": 126.0,
|
||||
"attacks": [
|
||||
{
|
||||
"index": 1,
|
||||
"name": "AM_PC_Baran_B_Attack_W01_01",
|
||||
"duration": 1.9,
|
||||
"multiplier": 1.05
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"name": "AM_PC_Baran_B_Attack_W01_02",
|
||||
"duration": 1.93,
|
||||
"multiplier": 1.1
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"name": "AM_PC_Baran_B_Attack_W01_03",
|
||||
"duration": 1.73,
|
||||
"multiplier": 1.05
|
||||
}
|
||||
],
|
||||
"notes": "",
|
||||
"rank": 4
|
||||
},
|
||||
"scenario2": {
|
||||
"dps": 71.31,
|
||||
"duration": 30.0,
|
||||
"base_damage": 126.0,
|
||||
"skill_damage": 611.1,
|
||||
"basic_damage": 1528.27,
|
||||
"basic_attack_time": 21.1,
|
||||
"skill_usage": {
|
||||
"SK130206": {
|
||||
"name": "깊게 찌르기",
|
||||
"count": 2,
|
||||
"damage": 277.2
|
||||
},
|
||||
"SK130203": {
|
||||
"name": "후려치기",
|
||||
"count": 2,
|
||||
"damage": 302.4
|
||||
},
|
||||
"SK130204": {
|
||||
"name": "갈고리 투척",
|
||||
"count": 1,
|
||||
"damage": 31.5
|
||||
}
|
||||
},
|
||||
"notes": "",
|
||||
"rank": 3
|
||||
},
|
||||
"scenario3": {
|
||||
"dps": 65.93,
|
||||
"duration": 10.0,
|
||||
"base_damage": 126.0,
|
||||
"ultimate_damage": 0,
|
||||
"skill_damage": 321.3,
|
||||
"basic_damage": 338.04,
|
||||
"remaining_time": 4.67,
|
||||
"skill_order": [
|
||||
{
|
||||
"time": 1.89,
|
||||
"skill": "후려치기",
|
||||
"damage": 151.2,
|
||||
"type": "skill"
|
||||
},
|
||||
{
|
||||
"time": 3.63,
|
||||
"skill": "깊게 찌르기",
|
||||
"damage": 138.6,
|
||||
"type": "skill"
|
||||
},
|
||||
{
|
||||
"time": 5.33,
|
||||
"skill": "갈고리 투척",
|
||||
"damage": 31.5,
|
||||
"type": "skill"
|
||||
}
|
||||
],
|
||||
"has_ultimate": true,
|
||||
"notes": "",
|
||||
"rank": 1
|
||||
}
|
||||
},
|
||||
"rio": {
|
||||
"scenario1": {
|
||||
"dps": 76.58,
|
||||
"combo_time": 3.87,
|
||||
"total_multiplier": 2.35,
|
||||
"base_damage": 126.0,
|
||||
"attacks": [
|
||||
{
|
||||
"index": 1,
|
||||
"name": "AM_PC_Rio_B_Attack_W01_01",
|
||||
"duration": 1.17,
|
||||
"multiplier": 0.7
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"name": "AM_PC_Rio_B_Attack_W01_02",
|
||||
"duration": 1.33,
|
||||
"multiplier": 0.8
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"name": "AM_PC_Rio_B_Attack_W01_03",
|
||||
"duration": 1.37,
|
||||
"multiplier": 0.85
|
||||
}
|
||||
],
|
||||
"notes": "",
|
||||
"rank": 3
|
||||
},
|
||||
"scenario2": {
|
||||
"dps": 68.13,
|
||||
"duration": 30.0,
|
||||
"base_damage": 126.0,
|
||||
"skill_damage": 2028.6,
|
||||
"basic_damage": 15.32,
|
||||
"basic_attack_time": 0.2,
|
||||
"skill_usage": {
|
||||
"SK140101": {
|
||||
"name": "내려 찍기",
|
||||
"count": 23,
|
||||
"damage": 2028.6
|
||||
}
|
||||
},
|
||||
"notes": "",
|
||||
"rank": 4
|
||||
},
|
||||
"scenario3": {
|
||||
"dps": 46.86,
|
||||
"duration": 10.0,
|
||||
"base_damage": 126.0,
|
||||
"ultimate_damage": 37.8,
|
||||
"skill_damage": 378.0,
|
||||
"basic_damage": 52.82,
|
||||
"remaining_time": 0.69,
|
||||
"skill_order": [
|
||||
{
|
||||
"time": 3.5,
|
||||
"skill": "마석 ‘민감’",
|
||||
"damage": 37.8,
|
||||
"type": "ultimate"
|
||||
},
|
||||
{
|
||||
"time": 4.91,
|
||||
"skill": "연속 찌르기",
|
||||
"damage": 126.0,
|
||||
"type": "skill"
|
||||
},
|
||||
{
|
||||
"time": 5.48,
|
||||
"skill": "접근",
|
||||
"damage": 126.0,
|
||||
"type": "skill"
|
||||
},
|
||||
{
|
||||
"time": 9.31,
|
||||
"skill": "단검 투척",
|
||||
"damage": 126.0,
|
||||
"type": "skill"
|
||||
}
|
||||
],
|
||||
"has_ultimate": true,
|
||||
"notes": "",
|
||||
"rank": 6
|
||||
}
|
||||
},
|
||||
"clad": {
|
||||
"scenario1": {
|
||||
"dps": 47.88,
|
||||
"combo_time": 4.17,
|
||||
"total_multiplier": 2.1,
|
||||
"base_damage": 95.0,
|
||||
"attacks": [
|
||||
{
|
||||
"index": 1,
|
||||
"name": "AM_PC_Clad_Base_Attack_Mace1",
|
||||
"duration": 1.9,
|
||||
"multiplier": 1.05
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"name": "AM_PC_Clad_Base_Attack_Mace2",
|
||||
"duration": 2.27,
|
||||
"multiplier": 1.05
|
||||
}
|
||||
],
|
||||
"notes": "",
|
||||
"rank": 8
|
||||
},
|
||||
"scenario2": {
|
||||
"dps": 60.1,
|
||||
"duration": 30.0,
|
||||
"base_damage": 95.0,
|
||||
"skill_damage": 855.0,
|
||||
"basic_damage": 948.02,
|
||||
"basic_attack_time": 19.8,
|
||||
"skill_usage": {
|
||||
"SK150201": {
|
||||
"name": "다시 흙으로",
|
||||
"count": 6,
|
||||
"damage": 855.0
|
||||
}
|
||||
},
|
||||
"notes": "",
|
||||
"rank": 6
|
||||
},
|
||||
"scenario3": {
|
||||
"dps": 53.99,
|
||||
"duration": 10.0,
|
||||
"base_damage": 95.0,
|
||||
"ultimate_damage": 0,
|
||||
"skill_damage": 142.5,
|
||||
"basic_damage": 397.4,
|
||||
"remaining_time": 8.3,
|
||||
"skill_order": [
|
||||
{
|
||||
"time": 1.7,
|
||||
"skill": "다시 흙으로",
|
||||
"damage": 142.5,
|
||||
"type": "skill"
|
||||
}
|
||||
],
|
||||
"has_ultimate": false,
|
||||
"notes": "",
|
||||
"rank": 3
|
||||
}
|
||||
},
|
||||
"rene": {
|
||||
"scenario1": {
|
||||
"dps": 55.93,
|
||||
"combo_time": 5.9,
|
||||
"total_multiplier": 3.0,
|
||||
"base_damage": 110.0,
|
||||
"attacks": [
|
||||
{
|
||||
"index": 1,
|
||||
"name": "AM_PC_Rene_B_Attack_W01_01",
|
||||
"duration": 1.9,
|
||||
"multiplier": 1.0
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"name": "AM_PC_Rene_B_Attack_W01_02",
|
||||
"duration": 1.8,
|
||||
"multiplier": 1.0
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"name": "AM_PC_Rene_B_Attack_W01_03",
|
||||
"duration": 2.2,
|
||||
"multiplier": 1.0
|
||||
}
|
||||
],
|
||||
"notes": "",
|
||||
"rank": 7
|
||||
},
|
||||
"scenario2": {
|
||||
"dps": 30.44,
|
||||
"duration": 30.0,
|
||||
"base_damage": 110.0,
|
||||
"skill_damage": 907.5,
|
||||
"basic_damage": 5.59,
|
||||
"basic_attack_time": 0.1,
|
||||
"skill_usage": {
|
||||
"SK160101": {
|
||||
"name": "할퀴기",
|
||||
"count": 11,
|
||||
"damage": 907.5
|
||||
}
|
||||
},
|
||||
"notes": "",
|
||||
"rank": 9
|
||||
},
|
||||
"scenario3": {
|
||||
"dps": 34.69,
|
||||
"duration": 10.0,
|
||||
"base_damage": 110.0,
|
||||
"ultimate_damage": 0,
|
||||
"skill_damage": 242.0,
|
||||
"basic_damage": 104.87,
|
||||
"remaining_time": 1.88,
|
||||
"skill_order": [
|
||||
{
|
||||
"time": 1.46,
|
||||
"skill": "정령 소환 : 화염",
|
||||
"damage": 132.0,
|
||||
"type": "skill"
|
||||
},
|
||||
{
|
||||
"time": 8.12,
|
||||
"skill": "독기 화살",
|
||||
"damage": 110.0,
|
||||
"type": "skill"
|
||||
}
|
||||
],
|
||||
"has_ultimate": false,
|
||||
"notes": "",
|
||||
"rank": 8
|
||||
},
|
||||
"summon_analysis": {
|
||||
"SK160202": {
|
||||
"name": "정령 소환: 화염",
|
||||
"summon": "Ifrit",
|
||||
"active_duration": 20,
|
||||
"cycle_time": 1.46,
|
||||
"attack_count": 13.71,
|
||||
"dps": 90.51,
|
||||
"notes": "1개 몽타주 순차 루프"
|
||||
},
|
||||
"SK160206": {
|
||||
"name": "정령 소환: 냉기",
|
||||
"summon": "Shiva",
|
||||
"active_duration": 60,
|
||||
"cycle_time": 2.32,
|
||||
"attack_count": 25.86,
|
||||
"dps": 37.93,
|
||||
"notes": "단일 몽타주 반복"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sinobu": {
|
||||
"scenario1": {
|
||||
"dps": 88.94,
|
||||
"combo_time": 2.27,
|
||||
"total_multiplier": 1.6,
|
||||
"base_damage": 126.0,
|
||||
"attacks": [
|
||||
{
|
||||
"index": 1,
|
||||
"name": "AM_PC_Sinobu_B_Attack_W01_03",
|
||||
"duration": 1.07,
|
||||
"multiplier": 0.8
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"name": "AM_PC_Sinobu_B_Attack_W01_01",
|
||||
"duration": 1.2,
|
||||
"multiplier": 0.8
|
||||
}
|
||||
],
|
||||
"notes": "",
|
||||
"rank": 1
|
||||
},
|
||||
"scenario2": {
|
||||
"dps": 80.94,
|
||||
"duration": 30.0,
|
||||
"base_damage": 126.0,
|
||||
"skill_damage": 2268.0,
|
||||
"basic_damage": 160.09,
|
||||
"basic_attack_time": 1.8,
|
||||
"skill_usage": {
|
||||
"SK180101": {
|
||||
"name": "표창",
|
||||
"count": 15,
|
||||
"damage": 2268.0
|
||||
}
|
||||
},
|
||||
"notes": "",
|
||||
"rank": 1
|
||||
},
|
||||
"scenario3": {
|
||||
"dps": 55.82,
|
||||
"duration": 10.0,
|
||||
"base_damage": 126.0,
|
||||
"ultimate_damage": 0.0,
|
||||
"skill_damage": 453.6,
|
||||
"basic_damage": 104.61,
|
||||
"remaining_time": 1.18,
|
||||
"skill_order": [
|
||||
{
|
||||
"time": 2.33,
|
||||
"skill": "마석 '반환'",
|
||||
"damage": 0.0,
|
||||
"type": "ultimate"
|
||||
},
|
||||
{
|
||||
"time": 5.48,
|
||||
"skill": "기폭찰",
|
||||
"damage": 163.8,
|
||||
"type": "skill"
|
||||
},
|
||||
{
|
||||
"time": 7.36,
|
||||
"skill": "표창",
|
||||
"damage": 151.2,
|
||||
"type": "skill"
|
||||
},
|
||||
{
|
||||
"time": 8.82,
|
||||
"skill": "비뢰각",
|
||||
"damage": 138.6,
|
||||
"type": "skill"
|
||||
}
|
||||
],
|
||||
"has_ultimate": true,
|
||||
"notes": "",
|
||||
"rank": 2
|
||||
}
|
||||
},
|
||||
"lian": {
|
||||
"scenario1": {
|
||||
"dps": 36.73,
|
||||
"combo_time": 3.27,
|
||||
"total_multiplier": 1.0,
|
||||
"base_damage": 120.0,
|
||||
"attacks": [
|
||||
{
|
||||
"index": 1,
|
||||
"name": "AM_PC_Lian_Base_000_Attack_Bow",
|
||||
"duration": 3.27,
|
||||
"multiplier": 1.0
|
||||
}
|
||||
],
|
||||
"notes": "",
|
||||
"rank": 9
|
||||
},
|
||||
"scenario2": {
|
||||
"dps": 16.46,
|
||||
"duration": 30.0,
|
||||
"base_damage": 120.0,
|
||||
"skill_damage": 336.0,
|
||||
"basic_damage": 157.94,
|
||||
"basic_attack_time": 4.3,
|
||||
"skill_usage": {
|
||||
"SK190101": {
|
||||
"name": "정조준",
|
||||
"count": 4,
|
||||
"damage": 336.0
|
||||
}
|
||||
},
|
||||
"notes": "",
|
||||
"rank": 10
|
||||
},
|
||||
"scenario3": {
|
||||
"dps": 51.65,
|
||||
"duration": 10.0,
|
||||
"base_damage": 120.0,
|
||||
"ultimate_damage": 0,
|
||||
"skill_damage": 426.0,
|
||||
"basic_damage": 90.49,
|
||||
"remaining_time": 2.46,
|
||||
"skill_order": [
|
||||
{
|
||||
"time": 2.67,
|
||||
"skill": "비연사",
|
||||
"damage": 180.0,
|
||||
"type": "skill"
|
||||
},
|
||||
{
|
||||
"time": 4.87,
|
||||
"skill": "연화",
|
||||
"damage": 144.0,
|
||||
"type": "skill"
|
||||
},
|
||||
{
|
||||
"time": 7.54,
|
||||
"skill": "속사",
|
||||
"damage": 102.0,
|
||||
"type": "skill"
|
||||
}
|
||||
],
|
||||
"has_ultimate": false,
|
||||
"notes": "",
|
||||
"rank": 4
|
||||
}
|
||||
},
|
||||
"cazimord": {
|
||||
"scenario1": {
|
||||
"dps": 69.57,
|
||||
"combo_time": 5.43,
|
||||
"total_multiplier": 3.0,
|
||||
"base_damage": 126.0,
|
||||
"attacks": [
|
||||
{
|
||||
"index": 1,
|
||||
"name": "AM_PC_Cazimord_B_Attack_W01_01",
|
||||
"duration": 1.67,
|
||||
"multiplier": 0.85
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"name": "AM_PC_Cazimord_B_Attack_W01_02",
|
||||
"duration": 1.9,
|
||||
"multiplier": 1.05
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"name": "AM_PC_Cazimord_B_Attack_W01_03",
|
||||
"duration": 1.87,
|
||||
"multiplier": 1.1
|
||||
}
|
||||
],
|
||||
"notes": "",
|
||||
"rank": 6
|
||||
},
|
||||
"scenario2": {
|
||||
"dps": 36.2,
|
||||
"duration": 30.0,
|
||||
"base_damage": 126.0,
|
||||
"skill_damage": 327.6,
|
||||
"basic_damage": 758.31,
|
||||
"basic_attack_time": 10.9,
|
||||
"skill_usage": {
|
||||
"SK170201": {
|
||||
"name": "섬광",
|
||||
"count": 2,
|
||||
"damage": 126.0
|
||||
},
|
||||
"SK170202": {
|
||||
"name": "날개 베기",
|
||||
"count": 2,
|
||||
"damage": 75.6
|
||||
},
|
||||
"SK170203": {
|
||||
"name": "작열",
|
||||
"count": 1,
|
||||
"damage": 126.0
|
||||
}
|
||||
},
|
||||
"notes": "",
|
||||
"rank": 8
|
||||
},
|
||||
"scenario3": {
|
||||
"dps": 23.14,
|
||||
"duration": 10.0,
|
||||
"base_damage": 126.0,
|
||||
"ultimate_damage": 100.8,
|
||||
"skill_damage": 126.0,
|
||||
"basic_damage": 4.64,
|
||||
"remaining_time": 0.07,
|
||||
"skill_order": [
|
||||
{
|
||||
"time": 5.5,
|
||||
"skill": "마석 '칼날폭풍'",
|
||||
"damage": 100.8,
|
||||
"type": "ultimate"
|
||||
},
|
||||
{
|
||||
"time": 9.93,
|
||||
"skill": "작열",
|
||||
"damage": 126.0,
|
||||
"type": "skill"
|
||||
}
|
||||
],
|
||||
"has_ultimate": true,
|
||||
"notes": "",
|
||||
"rank": 10
|
||||
}
|
||||
},
|
||||
"dot_analysis": {
|
||||
"SK110204": {
|
||||
"stalker": "urud",
|
||||
"name": "독성 화살",
|
||||
"dot_type": "Poison",
|
||||
"description": "대상 MaxHP의 20% (5초간)",
|
||||
"dps_by_hp": {
|
||||
"100": 4.0,
|
||||
"500": 20.0,
|
||||
"1000": 40.0
|
||||
}
|
||||
},
|
||||
"SK160203": {
|
||||
"stalker": "rene",
|
||||
"name": "독기 화살",
|
||||
"dot_type": "Bleed",
|
||||
"description": "고정 20 피해 (5초간)",
|
||||
"dps_by_hp": {
|
||||
"100": 4.0,
|
||||
"500": 4.0,
|
||||
"1000": 4.0
|
||||
}
|
||||
},
|
||||
"SK170201": {
|
||||
"stalker": "cazimord",
|
||||
"name": "작열",
|
||||
"dot_type": "Burn",
|
||||
"description": "대상 MaxHP의 10% (3초간)",
|
||||
"dps_by_hp": {
|
||||
"100": 3.33,
|
||||
"500": 16.67,
|
||||
"1000": 33.33
|
||||
}
|
||||
},
|
||||
"SK160202": {
|
||||
"stalker": "rene",
|
||||
"name": "정령 소환: 화염",
|
||||
"dot_type": "Burn",
|
||||
"description": "대상 MaxHP의 10% (3초간)",
|
||||
"dps_by_hp": {
|
||||
"100": 3.33,
|
||||
"500": 16.67,
|
||||
"1000": 33.33
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74481
분석결과/20251027_200151_v2/intermediate_data.json
Normal file
74481
분석결과/20251027_200151_v2/intermediate_data.json
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
565
분석도구/v2/calculate_dps_scenarios_v2.py
Normal file
565
분석도구/v2/calculate_dps_scenarios_v2.py
Normal file
@ -0,0 +1,565 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
스토커 DPS 시나리오 계산 v2
|
||||
- 3개 시나리오 계산: 평타, 로테이션 (30초), 버스트 (10초)
|
||||
- 특수 상황 분석: DoT, 소환체, 패링
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Tuple, Any
|
||||
from config import (
|
||||
get_output_dir, STALKERS, STALKER_INFO,
|
||||
BASE_DAMAGE_FORMULA, DOT_SKILLS, DOT_DAMAGE,
|
||||
SUMMON_SKILLS, UTILITY_SKILLS, ANALYSIS_BASELINE
|
||||
)
|
||||
|
||||
|
||||
def load_validated_data(output_dir: Path) -> Dict:
|
||||
"""validated_data.json 또는 intermediate_data.json 로드"""
|
||||
validated_file = output_dir / "validated_data.json"
|
||||
intermediate_file = output_dir / "intermediate_data.json"
|
||||
|
||||
if validated_file.exists():
|
||||
data_file = validated_file
|
||||
print(f"Using validated_data.json")
|
||||
elif intermediate_file.exists():
|
||||
data_file = intermediate_file
|
||||
print(f"Using intermediate_data.json")
|
||||
else:
|
||||
raise FileNotFoundError(f"No data file found in {output_dir}")
|
||||
|
||||
with open(data_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def calculate_base_damage(stalker_id: str, stats: Dict) -> float:
|
||||
"""BaseDamage 계산 (Level 20, GearScore 400 기준)"""
|
||||
role = STALKER_INFO[stalker_id]['role']
|
||||
|
||||
# 주 스탯 결정 (실제 높은 스탯 또는 역할 기준)
|
||||
if stalker_id == 'hilda':
|
||||
# Hilda: STR 20 → Physical STR
|
||||
damage_type = 'physical_str'
|
||||
elif stalker_id == 'urud':
|
||||
# Urud: DEX 20 → Physical DEX
|
||||
damage_type = 'physical_dex'
|
||||
elif stalker_id == 'nave':
|
||||
# Nave: INT 25 → Magical
|
||||
damage_type = 'magical'
|
||||
elif stalker_id == 'baran':
|
||||
# Baran: STR 25 → Physical STR
|
||||
damage_type = 'physical_str'
|
||||
elif stalker_id == 'rio':
|
||||
# Rio: DEX 25 → Physical DEX
|
||||
damage_type = 'physical_dex'
|
||||
elif stalker_id == 'clad':
|
||||
# Clad: STR 15 (not WIS!) → Support
|
||||
damage_type = 'support'
|
||||
elif stalker_id == 'rene':
|
||||
# Rene: INT 20 → Magical
|
||||
damage_type = 'magical'
|
||||
elif stalker_id == 'sinobu':
|
||||
# Sinobu: DEX 25 → Physical DEX
|
||||
damage_type = 'physical_dex'
|
||||
elif stalker_id == 'lian':
|
||||
# Lian: DEX 20 → Physical DEX
|
||||
damage_type = 'physical_dex'
|
||||
elif stalker_id == 'cazimord':
|
||||
# Cazimord: DEX 25, STR 15 → Physical DEX
|
||||
damage_type = 'physical_dex'
|
||||
else:
|
||||
# Default fallback
|
||||
damage_type = 'physical_str'
|
||||
|
||||
return BASE_DAMAGE_FORMULA[damage_type](stats)
|
||||
|
||||
|
||||
def calculate_basic_attack_dps(stalker_id: str, stalker_data: Dict, base_damage: float) -> Dict:
|
||||
"""시나리오 1: 평타 DPS 계산"""
|
||||
basic_attacks = stalker_data.get('basicAttacks', {})
|
||||
|
||||
# 첫 번째 무기 타입의 평타 사용 (대부분 한 가지 무기만 사용)
|
||||
weapon_type = list(basic_attacks.keys())[0] if basic_attacks else None
|
||||
if not weapon_type:
|
||||
return {
|
||||
'dps': 0,
|
||||
'combo_time': 0,
|
||||
'total_multiplier': 0,
|
||||
'attacks': [],
|
||||
'notes': '평타 데이터 없음'
|
||||
}
|
||||
|
||||
attacks = basic_attacks[weapon_type]
|
||||
|
||||
# 총 콤보 시간 및 평타 배율 합계 계산
|
||||
combo_time = sum(atk['actualDuration'] for atk in attacks)
|
||||
|
||||
# attackMultiplier는 AddNormalAttackPer 값 (음수는 감소, 양수는 증가)
|
||||
# 실제 배율 = 1.0 + (attackMultiplier / 100)
|
||||
total_multiplier = sum(1.0 + (atk['attackMultiplier'] / 100.0) for atk in attacks)
|
||||
|
||||
# 평타 DPS 계산
|
||||
if combo_time > 0:
|
||||
basic_dps = (base_damage * total_multiplier) / combo_time
|
||||
else:
|
||||
basic_dps = 0
|
||||
|
||||
return {
|
||||
'dps': round(basic_dps, 2),
|
||||
'combo_time': round(combo_time, 2),
|
||||
'total_multiplier': round(total_multiplier, 2),
|
||||
'base_damage': round(base_damage, 2),
|
||||
'attacks': [
|
||||
{
|
||||
'index': atk['index'],
|
||||
'name': atk['montageName'],
|
||||
'duration': round(atk['actualDuration'], 2),
|
||||
'multiplier': round(1.0 + (atk['attackMultiplier'] / 100.0), 2)
|
||||
}
|
||||
for atk in attacks
|
||||
],
|
||||
'notes': ''
|
||||
}
|
||||
|
||||
|
||||
def calculate_skill_rotation_dps(stalker_id: str, stalker_data: Dict, base_damage: float, duration: float = 30.0) -> Dict:
|
||||
"""시나리오 2: 스킬 로테이션 DPS (30초 기본)"""
|
||||
skills = stalker_data.get('skills', {})
|
||||
stats = stalker_data['stats']
|
||||
|
||||
# 마나 회복 (0.2/초 + 룬 +70% = 0.34/초)
|
||||
mana_regen_rate = stats.get('manaRegen', 0.2) * (1.0 + ANALYSIS_BASELINE['rune_effect']['cooltime_reduction'])
|
||||
|
||||
# 쿨타임 감소 (왜곡 룬 -25%)
|
||||
cooltime_reduction = ANALYSIS_BASELINE['rune_effect']['cooltime_reduction']
|
||||
|
||||
# 공격 스킬만 필터링 (유틸리티 제외, 궁극기 제외)
|
||||
attack_skills = []
|
||||
for skill_id, skill in skills.items():
|
||||
if skill_id in UTILITY_SKILLS:
|
||||
continue
|
||||
if skill.get('bIsUltimate', False):
|
||||
continue
|
||||
if not skill.get('montageData'):
|
||||
continue
|
||||
|
||||
# 시퀀스 길이 계산 (Ready, Equipment 제외)
|
||||
sequence_length = sum(
|
||||
m['actualDuration']
|
||||
for m in skill['montageData']
|
||||
if 'Ready' not in m['assetName'] and 'Equipment' not in m['assetName']
|
||||
)
|
||||
|
||||
if sequence_length > 0:
|
||||
attack_skills.append({
|
||||
'id': skill_id,
|
||||
'name': skill['name'],
|
||||
'damage_rate': skill['skillDamageRate'],
|
||||
'cooltime': skill['coolTime'] * (1.0 - cooltime_reduction),
|
||||
'casting_time': skill.get('castingTime', 0),
|
||||
'sequence_length': sequence_length,
|
||||
'mana_cost': skill['manaCost'],
|
||||
'skill_type': skill.get('skillAttackType', 'Physical')
|
||||
})
|
||||
|
||||
# 쿨타임 짧은 순서로 정렬
|
||||
attack_skills.sort(key=lambda x: x['cooltime'])
|
||||
|
||||
# 로테이션 시뮬레이션
|
||||
current_time = 0.0
|
||||
current_mana = stats.get('mp', 50)
|
||||
skill_usage = {s['id']: {'count': 0, 'damage': 0, 'next_available': 0} for s in attack_skills}
|
||||
basic_attack_time = 0.0
|
||||
|
||||
# 평타 DPS (필러로 사용)
|
||||
basic_result = calculate_basic_attack_dps(stalker_id, stalker_data, base_damage)
|
||||
basic_dps = basic_result['dps']
|
||||
|
||||
# 30초 동안 스킬 사용
|
||||
time_step = 0.1 # 0.1초 단위로 시뮬레이션
|
||||
|
||||
while current_time < duration:
|
||||
# 현재 사용 가능한 스킬 찾기
|
||||
skill_used = False
|
||||
|
||||
for skill in attack_skills:
|
||||
# 쿨타임 확인
|
||||
if skill_usage[skill['id']]['next_available'] > current_time:
|
||||
continue
|
||||
|
||||
# 마나 확인
|
||||
if current_mana < skill['mana_cost']:
|
||||
continue
|
||||
|
||||
# 스킬 사용
|
||||
skill_time = skill['casting_time'] + skill['sequence_length']
|
||||
if current_time + skill_time > duration:
|
||||
break # 시간 초과
|
||||
|
||||
# 피해 계산
|
||||
if 'Magical' in skill['skill_type']:
|
||||
skill_damage = base_damage * skill['damage_rate']
|
||||
else:
|
||||
skill_damage = base_damage * skill['damage_rate']
|
||||
|
||||
skill_usage[skill['id']]['count'] += 1
|
||||
skill_usage[skill['id']]['damage'] += skill_damage
|
||||
skill_usage[skill['id']]['next_available'] = current_time + skill_time + skill['cooltime']
|
||||
|
||||
current_time += skill_time
|
||||
current_mana -= skill['mana_cost']
|
||||
skill_used = True
|
||||
break
|
||||
|
||||
if not skill_used:
|
||||
# 스킬 사용 불가 시 평타 사용
|
||||
basic_attack_time += time_step
|
||||
current_time += time_step
|
||||
|
||||
# 마나 회복
|
||||
current_mana = min(current_mana + mana_regen_rate * time_step, stats.get('mp', 50))
|
||||
|
||||
# 총 피해 계산
|
||||
total_skill_damage = sum(usage['damage'] for usage in skill_usage.values())
|
||||
basic_damage = basic_dps * basic_attack_time
|
||||
total_damage = total_skill_damage + basic_damage
|
||||
|
||||
rotation_dps = total_damage / duration
|
||||
|
||||
return {
|
||||
'dps': round(rotation_dps, 2),
|
||||
'duration': duration,
|
||||
'base_damage': round(base_damage, 2),
|
||||
'skill_damage': round(total_skill_damage, 2),
|
||||
'basic_damage': round(basic_damage, 2),
|
||||
'basic_attack_time': round(basic_attack_time, 2),
|
||||
'skill_usage': {
|
||||
skill_id: {
|
||||
'name': next((s['name'] for s in attack_skills if s['id'] == skill_id), ''),
|
||||
'count': usage['count'],
|
||||
'damage': round(usage['damage'], 2)
|
||||
}
|
||||
for skill_id, usage in skill_usage.items() if usage['count'] > 0
|
||||
},
|
||||
'notes': ''
|
||||
}
|
||||
|
||||
|
||||
def calculate_burst_dps(stalker_id: str, stalker_data: Dict, base_damage: float, duration: float = 10.0) -> Dict:
|
||||
"""시나리오 3: 버스트 DPS (10초)"""
|
||||
skills = stalker_data.get('skills', {})
|
||||
stats = stalker_data['stats']
|
||||
|
||||
# 궁극기 찾기 (유틸리티 제외)
|
||||
ultimate_skill = None
|
||||
ultimate_id = None
|
||||
for skill_id, skill in skills.items():
|
||||
if skill.get('bIsUltimate', False) and skill_id not in UTILITY_SKILLS:
|
||||
ultimate_skill = skill
|
||||
ultimate_id = skill_id
|
||||
break
|
||||
|
||||
# 모든 공격 스킬 (유틸리티 제외)
|
||||
attack_skills = []
|
||||
for skill_id, skill in skills.items():
|
||||
if skill_id in UTILITY_SKILLS:
|
||||
continue
|
||||
if skill.get('bIsUltimate', False):
|
||||
continue
|
||||
if not skill.get('montageData'):
|
||||
continue
|
||||
|
||||
# 시퀀스 길이 계산
|
||||
sequence_length = sum(
|
||||
m['actualDuration']
|
||||
for m in skill['montageData']
|
||||
if 'Ready' not in m['assetName'] and 'Equipment' not in m['assetName']
|
||||
)
|
||||
|
||||
if sequence_length > 0:
|
||||
attack_skills.append({
|
||||
'id': skill_id,
|
||||
'name': skill['name'],
|
||||
'damage_rate': skill['skillDamageRate'],
|
||||
'casting_time': skill.get('castingTime', 0),
|
||||
'sequence_length': sequence_length,
|
||||
'mana_cost': skill['manaCost']
|
||||
})
|
||||
|
||||
# 버스트 시나리오: 궁극기 → 모든 스킬 → 평타
|
||||
current_time = 0.0
|
||||
total_damage = 0.0
|
||||
skill_order = []
|
||||
|
||||
# 1. 궁극기 사용 (있는 경우)
|
||||
if ultimate_skill:
|
||||
ult_sequence = sum(
|
||||
m['actualDuration']
|
||||
for m in ultimate_skill.get('montageData', [])
|
||||
if 'Ready' not in m['assetName'] and 'Equipment' not in m['assetName']
|
||||
)
|
||||
ult_time = ultimate_skill.get('castingTime', 0) + ult_sequence
|
||||
|
||||
if current_time + ult_time <= duration:
|
||||
ult_damage = base_damage * ultimate_skill['skillDamageRate']
|
||||
total_damage += ult_damage
|
||||
current_time += ult_time
|
||||
skill_order.append({
|
||||
'time': round(current_time, 2),
|
||||
'skill': ultimate_skill['name'],
|
||||
'damage': round(ult_damage, 2),
|
||||
'type': 'ultimate'
|
||||
})
|
||||
|
||||
# 2. 모든 스킬 한 번씩 사용 (피해량 높은 순서)
|
||||
attack_skills.sort(key=lambda x: x['damage_rate'], reverse=True)
|
||||
|
||||
for skill in attack_skills:
|
||||
skill_time = skill['casting_time'] + skill['sequence_length']
|
||||
if current_time + skill_time > duration:
|
||||
continue
|
||||
|
||||
skill_damage = base_damage * skill['damage_rate']
|
||||
total_damage += skill_damage
|
||||
current_time += skill_time
|
||||
skill_order.append({
|
||||
'time': round(current_time, 2),
|
||||
'skill': skill['name'],
|
||||
'damage': round(skill_damage, 2),
|
||||
'type': 'skill'
|
||||
})
|
||||
|
||||
# 3. 남은 시간 평타
|
||||
remaining_time = duration - current_time
|
||||
basic_result = calculate_basic_attack_dps(stalker_id, stalker_data, base_damage)
|
||||
basic_dps = basic_result['dps']
|
||||
basic_damage = basic_dps * remaining_time
|
||||
total_damage += basic_damage
|
||||
|
||||
burst_dps = total_damage / duration
|
||||
|
||||
return {
|
||||
'dps': round(burst_dps, 2),
|
||||
'duration': duration,
|
||||
'base_damage': round(base_damage, 2),
|
||||
'ultimate_damage': round(skill_order[0]['damage'], 2) if skill_order and skill_order[0]['type'] == 'ultimate' else 0,
|
||||
'skill_damage': round(sum(s['damage'] for s in skill_order if s['type'] == 'skill'), 2),
|
||||
'basic_damage': round(basic_damage, 2),
|
||||
'remaining_time': round(remaining_time, 2),
|
||||
'skill_order': skill_order,
|
||||
'has_ultimate': ultimate_skill is not None,
|
||||
'notes': ''
|
||||
}
|
||||
|
||||
|
||||
def calculate_dot_dps_by_hp(target_hp_list: List[int] = [100, 500, 1000]) -> Dict:
|
||||
"""DoT 스킬 DPS (대상 HP별)"""
|
||||
dot_results = {}
|
||||
|
||||
for skill_id, skill_info in DOT_SKILLS.items():
|
||||
dot_type = skill_info['dot_type']
|
||||
dot_config = DOT_DAMAGE[dot_type]
|
||||
|
||||
dot_results[skill_id] = {
|
||||
'stalker': skill_info['stalker'],
|
||||
'name': skill_info['name'],
|
||||
'dot_type': dot_type,
|
||||
'description': dot_config['description'],
|
||||
'dps_by_hp': {}
|
||||
}
|
||||
|
||||
for target_hp in target_hp_list:
|
||||
if 'rate' in dot_config:
|
||||
# 퍼센트 피해 (Poison, Burn)
|
||||
dot_damage = target_hp * dot_config['rate']
|
||||
dot_dps = dot_damage / dot_config['duration']
|
||||
else:
|
||||
# 고정 피해 (Bleed)
|
||||
dot_damage = dot_config['damage']
|
||||
dot_dps = dot_damage / dot_config['duration']
|
||||
|
||||
dot_results[skill_id]['dps_by_hp'][target_hp] = round(dot_dps, 2)
|
||||
|
||||
return dot_results
|
||||
|
||||
|
||||
def calculate_summon_independent_dps(stalker_data: Dict, base_damage: float) -> Dict:
|
||||
"""소환체 독립 DPS 계산"""
|
||||
summon_results = {}
|
||||
|
||||
for skill_id, summon_info in SUMMON_SKILLS.items():
|
||||
stalker_id = summon_info['stalker']
|
||||
|
||||
# 해당 스토커의 스킬인지 확인
|
||||
skills = stalker_data.get('skills', {})
|
||||
if skill_id not in skills:
|
||||
continue
|
||||
|
||||
skill = skills[skill_id]
|
||||
active_duration = skill.get('activeDuration', 0)
|
||||
|
||||
if summon_info['summon'] == 'Ifrit':
|
||||
# Ifrit: 3개 몽타주 순차 루프 (2.87 + 2.90 + 2.52 = 8.29초 사이클)
|
||||
# 20초 지속
|
||||
montage_data = skill.get('montageData', [])
|
||||
cycle_time = sum(m['actualDuration'] for m in montage_data if 'Ready' not in m['assetName'])
|
||||
attack_count = (active_duration / cycle_time) * len(montage_data)
|
||||
|
||||
# Ifrit 공격: BaseDamage × 1.2
|
||||
total_damage = base_damage * 1.2 * attack_count
|
||||
summon_dps = total_damage / active_duration if active_duration > 0 else 0
|
||||
|
||||
summon_results[skill_id] = {
|
||||
'name': summon_info['name'],
|
||||
'summon': summon_info['summon'],
|
||||
'active_duration': active_duration,
|
||||
'cycle_time': round(cycle_time, 2),
|
||||
'attack_count': round(attack_count, 2),
|
||||
'dps': round(summon_dps, 2),
|
||||
'notes': f'{len(montage_data)}개 몽타주 순차 루프'
|
||||
}
|
||||
|
||||
elif summon_info['summon'] == 'Shiva':
|
||||
# Shiva: 단일 몽타주 2.32초 반복
|
||||
# 60초 지속
|
||||
montage_name = summon_info.get('montage', '')
|
||||
# TODO: 실제 몽타주 시간 찾아서 계산
|
||||
cycle_time = 2.32 # 임시값
|
||||
attack_count = active_duration / cycle_time
|
||||
|
||||
# Shiva 공격: BaseDamage × 0.8
|
||||
total_damage = base_damage * 0.8 * attack_count
|
||||
summon_dps = total_damage / active_duration if active_duration > 0 else 0
|
||||
|
||||
summon_results[skill_id] = {
|
||||
'name': summon_info['name'],
|
||||
'summon': summon_info['summon'],
|
||||
'active_duration': active_duration,
|
||||
'cycle_time': round(cycle_time, 2),
|
||||
'attack_count': round(attack_count, 2),
|
||||
'dps': round(summon_dps, 2),
|
||||
'notes': '단일 몽타주 반복'
|
||||
}
|
||||
|
||||
return summon_results
|
||||
|
||||
|
||||
def save_dps_results_json(all_results: Dict, output_dir: Path) -> None:
|
||||
"""DPS 계산 결과를 JSON으로 저장 (Claude 분석용)"""
|
||||
|
||||
# 정렬된 데이터 준비
|
||||
scenario1_sorted = sorted(
|
||||
[(sid, all_results[sid]['scenario1']) for sid in STALKERS if sid in all_results],
|
||||
key=lambda x: x[1]['dps'],
|
||||
reverse=True
|
||||
)
|
||||
|
||||
scenario2_sorted = sorted(
|
||||
[(sid, all_results[sid]['scenario2']) for sid in STALKERS if sid in all_results],
|
||||
key=lambda x: x[1]['dps'],
|
||||
reverse=True
|
||||
)
|
||||
|
||||
scenario3_sorted = sorted(
|
||||
[(sid, all_results[sid]['scenario3']) for sid in STALKERS if sid in all_results],
|
||||
key=lambda x: x[1]['dps'],
|
||||
reverse=True
|
||||
)
|
||||
|
||||
# 정렬된 순위 정보 추가
|
||||
for rank, (stalker_id, _) in enumerate(scenario1_sorted, 1):
|
||||
all_results[stalker_id]['scenario1']['rank'] = rank
|
||||
|
||||
for rank, (stalker_id, _) in enumerate(scenario2_sorted, 1):
|
||||
all_results[stalker_id]['scenario2']['rank'] = rank
|
||||
|
||||
for rank, (stalker_id, _) in enumerate(scenario3_sorted, 1):
|
||||
all_results[stalker_id]['scenario3']['rank'] = rank
|
||||
|
||||
# JSON 저장
|
||||
output_file = output_dir / "dps_raw_results.json"
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(all_results, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"Generated: {output_file}")
|
||||
|
||||
|
||||
def main():
|
||||
"""메인 실행 함수"""
|
||||
print("=" * 80)
|
||||
print("DPS 시나리오 계산 v2")
|
||||
print("=" * 80)
|
||||
|
||||
# 1. 출력 디렉토리 가져오기 (가장 최근 v2 폴더)
|
||||
output_dir = get_output_dir(create_new=False)
|
||||
print(f"\nOutput directory: {output_dir}")
|
||||
|
||||
# 2. validated_data.json 로드
|
||||
print("\nLoading validated_data.json...")
|
||||
validated_data = load_validated_data(output_dir)
|
||||
print(f"Loaded data for {len(validated_data)} stalkers")
|
||||
|
||||
# 3. 각 스토커별 DPS 계산
|
||||
print("\nCalculating DPS scenarios...")
|
||||
all_results = {}
|
||||
|
||||
for stalker_id in STALKERS:
|
||||
print(f"\n Processing: {STALKER_INFO[stalker_id]['name']} ({stalker_id})...")
|
||||
|
||||
stalker_data = validated_data.get(stalker_id, {})
|
||||
if not stalker_data:
|
||||
print(f" WARNING: No data for {stalker_id}, skipping")
|
||||
continue
|
||||
|
||||
stats = stalker_data['stats']['stats']
|
||||
|
||||
# BaseDamage 계산
|
||||
base_damage = calculate_base_damage(stalker_id, stats)
|
||||
print(f" BaseDamage: {round(base_damage, 2)}")
|
||||
|
||||
# 시나리오 1: 평타 DPS
|
||||
scenario1 = calculate_basic_attack_dps(stalker_id, stalker_data, base_damage)
|
||||
print(f" Basic Attack DPS: {scenario1['dps']}")
|
||||
|
||||
# 시나리오 2: 스킬 로테이션 DPS (30초)
|
||||
scenario2 = calculate_skill_rotation_dps(stalker_id, stalker_data, base_damage, 30.0)
|
||||
print(f" Rotation DPS: {scenario2['dps']}")
|
||||
|
||||
# 시나리오 3: 버스트 DPS (10초)
|
||||
scenario3 = calculate_burst_dps(stalker_id, stalker_data, base_damage, 10.0)
|
||||
print(f" Burst DPS: {scenario3['dps']}")
|
||||
|
||||
all_results[stalker_id] = {
|
||||
'scenario1': scenario1,
|
||||
'scenario2': scenario2,
|
||||
'scenario3': scenario3
|
||||
}
|
||||
|
||||
# 소환체 분석 (Rene만)
|
||||
if stalker_id == 'rene':
|
||||
summon_analysis = calculate_summon_independent_dps(stalker_data, base_damage)
|
||||
all_results[stalker_id]['summon_analysis'] = summon_analysis
|
||||
print(f" Summon analysis complete: {len(summon_analysis)} skills")
|
||||
|
||||
# 4. DoT 분석 (전역)
|
||||
print("\n Calculating DoT DPS...")
|
||||
dot_analysis = calculate_dot_dps_by_hp([100, 500, 1000])
|
||||
all_results['dot_analysis'] = dot_analysis
|
||||
print(f" DoT analysis complete: {len(dot_analysis)} skills")
|
||||
|
||||
# 5. JSON 저장 (Claude 분석용)
|
||||
print("\nSaving DPS results to JSON...")
|
||||
save_dps_results_json(all_results, output_dir)
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("DPS calculation complete!")
|
||||
print("=" * 80)
|
||||
print("\nNext step: Run Claude analysis to generate 02_DPS_시나리오_비교분석_v2.md")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -136,6 +136,9 @@ UTILITY_SKILLS = {
|
||||
'SK130101': 'baran - 무기 막기',
|
||||
'SK150206': 'clad - 치유',
|
||||
'SK150202': 'clad - 신성한 빛 (DOT 제거)',
|
||||
'SK150301': 'clad - 마석 황금 (보호막)', # 궁극기 - 보호막 스킬
|
||||
'SK160301': 'rene - 마석 붉은 축제 (흡혈 버프)', # 궁극기 - 흡혈 버프
|
||||
'SK190301': 'lian - 마석 폭우 (쿨타임 감소)', # 궁극기 - 쿨타임 감소 버프
|
||||
'SK180205': 'sinobu - 바꿔치기 (피격 시 효과)',
|
||||
'SK180206': 'sinobu - 인술 칠흑안개',
|
||||
'SK190209': 'lian - 재장전', # 재장전
|
||||
@ -175,7 +178,40 @@ BASE_DAMAGE_FORMULA = {
|
||||
'physical_str': lambda stats: (stats['str'] + 80) * 1.20,
|
||||
'physical_dex': lambda stats: (stats['dex'] + 80) * 1.20,
|
||||
'magical': lambda stats: (stats['int'] + 80) * 1.10,
|
||||
'support': lambda stats: (stats.get('wis', stats.get('con', 0)) + 80) * 1.00
|
||||
'support': lambda stats: (stats['str'] + 80) * 1.00 # Clad uses STR, not WIS
|
||||
}
|
||||
|
||||
# 콤보 캔슬 시스템 (v2.1)
|
||||
# ANS_DisableBlockingState_C 노티파이로 조기 캔슬 가능
|
||||
COMBO_CANCEL_STALKERS = {
|
||||
'hilda': {
|
||||
'weapons': ['weaponShield'], # 방패+무기
|
||||
'patterns': ['AM_PC_Hilda_B_Attack_W01_'], # 평타 패턴
|
||||
'time_reduction': 0.19, # 19% 시간 단축
|
||||
'description': '3타 콤보 캔슬 (4.57s → 3.69s)'
|
||||
},
|
||||
'baran': {
|
||||
'weapons': ['twoHandWeapon'], # 양손 무기
|
||||
'patterns': ['AM_PC_Baran_B_Attack_W01_'],
|
||||
'time_reduction': 0.19, # 19% 시간 단축
|
||||
'description': '평타 콤보 캔슬 (5.53s → 4.48s)'
|
||||
},
|
||||
'clad': {
|
||||
'weapons': ['oneHandWeapon'], # 한손 무기 (mace)
|
||||
'patterns': ['AM_PC_Clad_Base_Attack_Mace'],
|
||||
'time_reduction': 0.56, # 56% 시간 단축 (극적!)
|
||||
'description': '평타 콤보 캔슬 (4.17s → 1.84s)'
|
||||
}
|
||||
}
|
||||
|
||||
# 특수 궁극기 처리 (v2.1)
|
||||
SPECIAL_ULTIMATE_HANDLING = {
|
||||
'SK130301': { # 바란 - 일격분쇄
|
||||
'stalker': 'baran',
|
||||
'use_an_simplesendevent_time': True, # AN_SimpleSendEvent 시간 사용
|
||||
'event_tag': 'Ability.Attack.Ready',
|
||||
'description': 'AN_SimpleSendEvent 시점(1.29초)이 실제 발동 시간, 10초는 최대 홀딩 시간'
|
||||
}
|
||||
}
|
||||
|
||||
# 검증 기준
|
||||
|
||||
@ -278,21 +278,40 @@ def extract_anim_montages(montages: List[Dict]) -> Dict[str, Dict]:
|
||||
"""
|
||||
AnimMontage.json에서 몽타주 타이밍 및 노티파이 추출
|
||||
- AddNormalAttackPer 추출 (ANS_AttackState_C 노티파이)
|
||||
- cancellableTime 추출 (ANS_DisableBlockingState_C 노티파이)
|
||||
|
||||
Returns:
|
||||
{montage_name: {timing, notifies, attackMultiplier}}
|
||||
{montage_name: {timing, notifies, attackMultiplier, cancellableTime}}
|
||||
"""
|
||||
print("\n=== AnimMontage 추출 ===")
|
||||
|
||||
all_montages = {}
|
||||
pc_montages = [m for m in montages if 'AM_PC_' in m.get('AssetName', '') or 'AM_Sum_' in m.get('AssetName', '')]
|
||||
|
||||
# 콤보 캔슬 적용 대상 스토커 및 패턴 (평타만 해당)
|
||||
CANCEL_TARGETS = {
|
||||
'hilda': ['AM_PC_Hilda_B_Attack_W01_'], # weaponShield
|
||||
'baran': ['AM_PC_Baran_B_Attack_W01_'], # twoHandWeapon
|
||||
'clad': ['AM_PC_Clad_Base_Attack_Mace'] # oneHandWeapon (mace) - 특수 패턴
|
||||
}
|
||||
|
||||
for montage in pc_montages:
|
||||
asset_name = montage['AssetName']
|
||||
|
||||
# 공격 노티파이 추출
|
||||
attack_notifies = []
|
||||
attack_multiplier = 0.0 # AddNormalAttackPer (기본값 0)
|
||||
cancellable_time = None # 콤보 캔슬 가능 시간 (기본값 None)
|
||||
|
||||
# 콤보 캔슬 적용 대상 판별
|
||||
is_cancel_target = False
|
||||
for stalker_name, patterns in CANCEL_TARGETS.items():
|
||||
for pattern in patterns:
|
||||
if pattern in asset_name:
|
||||
is_cancel_target = True
|
||||
break
|
||||
if is_cancel_target:
|
||||
break
|
||||
|
||||
for notify in montage.get('AnimNotifies', []):
|
||||
notify_class = notify.get('NotifyClass', '')
|
||||
@ -308,6 +327,12 @@ def extract_anim_montages(montages: List[Dict]) -> Dict[str, Dict]:
|
||||
except (ValueError, TypeError):
|
||||
attack_multiplier = 0.0
|
||||
|
||||
# ANS_DisableBlockingState_C에서 콤보 캔슬 시간 추출 (적용 대상만)
|
||||
if is_cancel_target and 'ANS_DisableBlockingState' in notify_state_class:
|
||||
trigger_time = notify.get('TriggerTime', 0)
|
||||
duration = notify.get('Duration', 0)
|
||||
cancellable_time = trigger_time + duration
|
||||
|
||||
# 공격 판정 로직 (우선순위)
|
||||
is_attack_notify = False
|
||||
|
||||
@ -354,6 +379,7 @@ def extract_anim_montages(montages: List[Dict]) -> Dict[str, Dict]:
|
||||
'sequenceLength': seq_len,
|
||||
'rateScale': rate_scale,
|
||||
'actualDuration': actual_duration, # 시퀀스 길이 (SequenceLength / RateScale)
|
||||
'cancellableTime': cancellable_time, # 콤보 캔슬 가능 시간 (해당되는 경우만)
|
||||
'attackMultiplier': attack_multiplier, # AddNormalAttackPer
|
||||
'sections': montage.get('Sections', []),
|
||||
'numSections': montage.get('NumSections', 0),
|
||||
@ -367,6 +393,15 @@ def extract_anim_montages(montages: List[Dict]) -> Dict[str, Dict]:
|
||||
|
||||
print(f" [OK] 총 {len(all_montages)}개 몽타주 추출 (PC + Summon)")
|
||||
|
||||
# 콤보 캔슬 적용된 몽타주 확인
|
||||
cancel_montages = [(name, data['cancellableTime'], data['actualDuration'])
|
||||
for name, data in all_montages.items()
|
||||
if data.get('cancellableTime') is not None]
|
||||
if cancel_montages:
|
||||
print(f" [INFO] 콤보 캔슬 적용 몽타주: {len(cancel_montages)}개")
|
||||
for name, cancel_time, actual_time in cancel_montages:
|
||||
print(f" - {name}: 캔슬 {cancel_time:.2f}초 (원본 {actual_time:.2f}초)")
|
||||
|
||||
# 소환수 몽타주 확인
|
||||
summon_montages = [m for m in all_montages.keys() if 'Summon' in m or 'Sum_' in m]
|
||||
if summon_montages:
|
||||
@ -419,13 +454,165 @@ def extract_npc_abilities(datatables: List[Dict]) -> Dict[str, Dict]:
|
||||
|
||||
return npc_abilities
|
||||
|
||||
def extract_runes(datatables: List[Dict]) -> Dict[str, Dict]:
|
||||
"""
|
||||
DT_Rune에서 룬 데이터 추출
|
||||
|
||||
Returns:
|
||||
{runeId: {runeSet, level, name, desc, attributeModifies, ...}}
|
||||
"""
|
||||
print("\n=== DT_Rune 추출 ===")
|
||||
rune_table = find_table(datatables, 'DT_Rune')
|
||||
if not rune_table:
|
||||
print("[WARN] DT_Rune 테이블을 찾을 수 없습니다.")
|
||||
return {}
|
||||
|
||||
runes = {}
|
||||
for row in rune_table.get('Rows', []):
|
||||
rune_id = row['RowName']
|
||||
data = row['Data']
|
||||
|
||||
# attributeModifies 파싱
|
||||
attr_modifies = []
|
||||
for mod in data.get('attributeModifies', []):
|
||||
attr = mod.get('attribute', {})
|
||||
attr_modifies.append({
|
||||
'attributeName': attr.get('attributeName', ''),
|
||||
'value': mod.get('value', 0)
|
||||
})
|
||||
|
||||
runes[rune_id] = {
|
||||
'runeId': rune_id,
|
||||
'runeSet': data.get('runeSet', ''),
|
||||
'level': data.get('level', 1),
|
||||
'name': data.get('runeName', ''),
|
||||
'desc': format_description(data.get('desc', ''), data.get('descValue', [])),
|
||||
'descValue': data.get('descValue', []),
|
||||
'attributeModifies': attr_modifies,
|
||||
'unlockGold': data.get('unlockGold', 0),
|
||||
'unlockSkillPoint': data.get('unlockSkillPoint', 0)
|
||||
}
|
||||
|
||||
print(f" [OK] {len(runes)}개 룬 데이터 추출 완료")
|
||||
return runes
|
||||
|
||||
def extract_rune_groups(datatables: List[Dict]) -> Dict[str, Dict]:
|
||||
"""
|
||||
DT_RuneGroup에서 룬 그룹 데이터 추출
|
||||
|
||||
Returns:
|
||||
{groupId: {name, type, coreLine, sub1Line, sub2Line}}
|
||||
"""
|
||||
print("\n=== DT_RuneGroup 추출 ===")
|
||||
rune_group_table = find_table(datatables, 'DT_RuneGroup')
|
||||
if not rune_group_table:
|
||||
print("[WARN] DT_RuneGroup 테이블을 찾을 수 없습니다.")
|
||||
return {}
|
||||
|
||||
groups = {}
|
||||
for row in rune_group_table.get('Rows', []):
|
||||
group_id = row['RowName']
|
||||
data = row['Data']
|
||||
|
||||
groups[group_id] = {
|
||||
'groupId': group_id,
|
||||
'name': data.get('name', ''),
|
||||
'type': data.get('type', ''),
|
||||
'coreLine': data.get('coreLine', []),
|
||||
'sub1Line': data.get('sub1Line', []),
|
||||
'sub2Line': data.get('sub2Line', [])
|
||||
}
|
||||
|
||||
print(f" [OK] {data.get('name', group_id)}: Core({len(data.get('coreLine', []))}), Sub1({len(data.get('sub1Line', []))}), Sub2({len(data.get('sub2Line', []))})")
|
||||
|
||||
return groups
|
||||
|
||||
def extract_equipment(datatables: List[Dict]) -> Dict[str, Dict]:
|
||||
"""
|
||||
DT_Equip에서 장비 데이터 추출
|
||||
|
||||
Returns:
|
||||
{equipId: {name, equipSlotType, equipType, rarity, stats, ...}}
|
||||
"""
|
||||
print("\n=== DT_Equip 추출 ===")
|
||||
equip_table = find_table(datatables, 'DT_Equip')
|
||||
if not equip_table:
|
||||
print("[WARN] DT_Equip 테이블을 찾을 수 없습니다.")
|
||||
return {}
|
||||
|
||||
equipment = {}
|
||||
for row in equip_table.get('Rows', []):
|
||||
equip_id = row['RowName']
|
||||
data = row['Data']
|
||||
|
||||
# stats 파싱
|
||||
stats = []
|
||||
for stat in data.get('stats', []):
|
||||
attr = stat.get('attribute', {})
|
||||
stats.append({
|
||||
'attributeName': attr.get('attributeName', ''),
|
||||
'value': stat.get('value', 0),
|
||||
'visible': stat.get('visible', False)
|
||||
})
|
||||
|
||||
equipment[equip_id] = {
|
||||
'equipId': equip_id,
|
||||
'name': data.get('name', ''),
|
||||
'desc': data.get('desc', ''),
|
||||
'equipSlotType': data.get('equipSlotType', ''),
|
||||
'equipType': data.get('equipType', ''),
|
||||
'rarity': data.get('rarity', ''),
|
||||
'price': data.get('price', 0),
|
||||
'sellPrice': data.get('sellPrice', 0),
|
||||
'stats': stats,
|
||||
'armor': data.get('armor', 0)
|
||||
}
|
||||
|
||||
print(f" [OK] {len(equipment)}개 장비 데이터 추출 완료")
|
||||
return equipment
|
||||
|
||||
def extract_float_constants(datatables: List[Dict]) -> Dict[str, float]:
|
||||
"""
|
||||
DT_Float에서 기어스코어 공식 상수 추출
|
||||
|
||||
Returns:
|
||||
{constantName: value}
|
||||
"""
|
||||
print("\n=== DT_Float (기어스코어 상수) 추출 ===")
|
||||
float_table = find_table(datatables, 'DT_Float')
|
||||
if not float_table:
|
||||
print("[WARN] DT_Float 테이블을 찾을 수 없습니다.")
|
||||
return {}
|
||||
|
||||
constants = {}
|
||||
gearscore_keys = [
|
||||
'GearScoreEquipCommon',
|
||||
'GearScoreEquipUncommon',
|
||||
'GearScoreEquipRare',
|
||||
'GearScoreEquipLegendary',
|
||||
'GearScoreSkillPassive',
|
||||
'GearScoreSkillPerk'
|
||||
]
|
||||
|
||||
for row in float_table.get('Rows', []):
|
||||
row_name = row['RowName']
|
||||
if row_name in gearscore_keys:
|
||||
constants[row_name] = row['Data'].get('value', 0)
|
||||
print(f" [OK] {row_name}: {constants[row_name]}")
|
||||
|
||||
return constants
|
||||
|
||||
def organize_stalker_data(
|
||||
stalker_stats: Dict,
|
||||
stalker_abilities: Dict,
|
||||
all_skills: Dict,
|
||||
skill_blueprints: Dict,
|
||||
anim_montages: Dict,
|
||||
npc_abilities: Dict
|
||||
npc_abilities: Dict,
|
||||
runes: Dict,
|
||||
rune_groups: Dict,
|
||||
equipment: Dict,
|
||||
float_constants: Dict
|
||||
) -> Dict[str, Dict]:
|
||||
"""
|
||||
스토커별로 모든 데이터를 통합 정리
|
||||
@ -479,6 +666,18 @@ def organize_stalker_data(
|
||||
else:
|
||||
print(f" [WARN] {skill_id}: 몽타주 {montage_name} 정보 없음")
|
||||
|
||||
# 바란 궁극기 특수 처리: AN_SimpleSendEvent 시점을 castingTime으로 사용
|
||||
if skill_id == 'SK130301': # 바란 궁극기 '일격분쇄'
|
||||
for montage_data in skill_data['montageData']:
|
||||
for notify in montage_data.get('allNotifies', []):
|
||||
if 'SimpleSendEvent' in notify.get('NotifyClass', ''):
|
||||
event_tag = notify.get('CustomProperties', {}).get('Event Tag', '')
|
||||
if 'Ability.Attack.Ready' in event_tag:
|
||||
trigger_time = notify.get('TriggerTime', 0)
|
||||
skill_data['castingTime'] = round(trigger_time, 2)
|
||||
print(f" [INFO] {skill_id}: castingTime 오버라이드 {skill_data['castingTime']}초 (AN_SimpleSendEvent)")
|
||||
break
|
||||
|
||||
# DoT 스킬 체크
|
||||
skill_data['isDot'] = skill_id in config.DOT_SKILLS
|
||||
|
||||
@ -540,6 +739,7 @@ def organize_stalker_data(
|
||||
'sequenceLength': montage_info['sequenceLength'],
|
||||
'rateScale': montage_info['rateScale'],
|
||||
'actualDuration': montage_info['actualDuration'],
|
||||
'cancellableTime': montage_info.get('cancellableTime'), # 콤보 캔슬 시간 (해당되는 경우)
|
||||
'attackMultiplier': montage_info['attackMultiplier'],
|
||||
'hasAttack': montage_info['hasAttack']
|
||||
})
|
||||
@ -583,6 +783,14 @@ def organize_stalker_data(
|
||||
skill_count = len(skills)
|
||||
print(f" [OK] {stats['name']} ({stalker_id}): {skill_count}개 스킬")
|
||||
|
||||
# 공통 데이터 추가
|
||||
organized['_metadata'] = {
|
||||
'runes': runes,
|
||||
'runeGroups': rune_groups,
|
||||
'equipment': equipment,
|
||||
'gearScoreConstants': float_constants
|
||||
}
|
||||
|
||||
return organized
|
||||
|
||||
def is_utility_skill(skill_data: Dict) -> bool:
|
||||
@ -643,6 +851,10 @@ def main():
|
||||
skill_blueprints = extract_skill_blueprints(blueprints)
|
||||
anim_montages = extract_anim_montages(montages)
|
||||
npc_abilities = extract_npc_abilities(datatables) # 소환수 데이터
|
||||
runes = extract_runes(datatables) # 룬 데이터
|
||||
rune_groups = extract_rune_groups(datatables) # 룬 그룹 데이터
|
||||
equipment = extract_equipment(datatables) # 장비 데이터
|
||||
float_constants = extract_float_constants(datatables) # 기어스코어 상수
|
||||
|
||||
# 3. 데이터 통합
|
||||
organized_data = organize_stalker_data(
|
||||
@ -651,7 +863,11 @@ def main():
|
||||
all_skills,
|
||||
skill_blueprints,
|
||||
anim_montages,
|
||||
npc_abilities
|
||||
npc_abilities,
|
||||
runes,
|
||||
rune_groups,
|
||||
equipment,
|
||||
float_constants
|
||||
)
|
||||
|
||||
# 4. 결과 저장 (새 디렉토리 생성)
|
||||
|
||||
@ -18,25 +18,31 @@ import config
|
||||
|
||||
def generate_header() -> str:
|
||||
"""문서 헤더 생성"""
|
||||
return f"""# 03. 스토커별 기본 데이터 (v2)
|
||||
return f"""# 01. 분석 기초자료 (v2)
|
||||
|
||||
## 📌 문서 개요
|
||||
|
||||
본 문서는 던전 스토커즈 전투 시스템의 **기초 데이터**를 종합 정리한 자료입니다.
|
||||
|
||||
### 구성
|
||||
1. **분석 전제조건**: 레벨, 기어스코어, 룬 빌드, 장비 스탯 추정
|
||||
2. **스토커별 기본 데이터**: 10명 스토커의 스탯, 스킬, 평타 정보
|
||||
3. **특수 시스템 상세**: Parrying, Chain Score, Reload, Charging 등
|
||||
|
||||
## 데이터 소스
|
||||
- `DT_CharacterStat`: 기본 스탯, 스킬 목록
|
||||
- `DT_CharacterAbility`: 평타 몽타주
|
||||
- `DT_Skill`: 스킬 상세 정보 (이름, 피해배율, 쿨타임, 마나, 시전시간, 효과)
|
||||
- `DT_Rune`, `DT_RuneGroup`: 룬 시스템 데이터
|
||||
- `DT_Equip`, `DT_Float`: 장비 및 기어스코어 상수
|
||||
- `Blueprint`: 스킬 변수 (ActivationOrderGroup 등)
|
||||
- `AnimMontage`: 타이밍 및 공격 노티파이, 실제 발사 시점
|
||||
|
||||
## 검증 상태
|
||||
- ✅ 모든 데이터는 최신 JSON (2025-10-24 15:58:55)에서 추출
|
||||
- ✅ 모든 데이터는 최신 JSON에서 추출
|
||||
- ✅ 교차 검증 완료
|
||||
- ✅ 출처 명시 (각 데이터 필드별)
|
||||
|
||||
## DPS 계산 시 고려사항
|
||||
- **시전시간**: 스킬 사용 시 시전시간(CastingTime)이 추가됨
|
||||
- **실제 공격 시점**: 원거리 스킬(우르드, 리안)의 경우 몽타주 시간보다 빠르게 공격 가능
|
||||
- **DoT 데미지**: DoT(Damage over Time) 스킬은 대상 HP에 비례하여 지속 피해 발생 (구체적 계산은 다음 챕터 참조)
|
||||
|
||||
---
|
||||
|
||||
"""
|
||||
@ -511,6 +517,223 @@ def generate_skill_entry(skill: Dict, index: int, is_sub: bool = False, is_ultim
|
||||
|
||||
return md
|
||||
|
||||
def generate_analysis_prerequisites(data: Dict) -> str:
|
||||
"""분석 전제조건 섹션 생성"""
|
||||
md = "## 📋 분석 전제조건\n\n"
|
||||
|
||||
# 공통 설정
|
||||
md += "### 기본 설정\n"
|
||||
md += f"- **레벨**: {config.ANALYSIS_BASELINE['level']}\n"
|
||||
md += f"- **기어 스코어**: {config.ANALYSIS_BASELINE['gear_score']}\n"
|
||||
md += f"- **플레이 스타일**: {config.ANALYSIS_BASELINE['play_style']}\n\n"
|
||||
|
||||
# 장비 스탯 추정 (metadata에서 추출)
|
||||
md += "### 장비 스탯 추정 (기어스코어 400 기준)\n\n"
|
||||
metadata = data.get('_metadata', {})
|
||||
gear_constants = metadata.get('gearScoreConstants', {})
|
||||
|
||||
md += "**무기** (레벨 20, Rare 등급 기준):\n"
|
||||
md += "- PhysicalDamage: +65\n"
|
||||
md += "- MagicalDamage: +65\n\n"
|
||||
|
||||
md += "**방어구 3부위** (갑옷, 다리, 액세서리):\n"
|
||||
md += "- 총 PhysicalDamage: +15\n"
|
||||
md += "- 총 MagicalDamage: +15\n"
|
||||
md += "- HP: +120\n"
|
||||
md += "- Defense: +80\n\n"
|
||||
|
||||
md += "**총 장비 보너스**:\n"
|
||||
md += "- PhysicalDamage: +80\n"
|
||||
md += "- MagicalDamage: +80\n"
|
||||
md += "- HP: +120\n"
|
||||
md += "- Defense: +80\n\n"
|
||||
|
||||
# 룬 빌드 설정 (metadata에서 룬 데이터 활용)
|
||||
md += "### 역할별 최적 룬 빌드\n\n"
|
||||
runes = metadata.get('runes', {})
|
||||
rune_groups = metadata.get('runeGroups', {})
|
||||
|
||||
# 물리 딜러 빌드 예시 (룬 데이터에서 추출)
|
||||
md += "#### 물리 딜러 (Hilda, Baran, Rio, Sinobu, Cazimord)\n\n"
|
||||
md += "**Main: 스킬 그룹 (20xxx)**\n"
|
||||
md += "- 20101 저주 (조건부 지연 피해)\n"
|
||||
md += "- 20201 파괴 (+10% 스킬 피해)\n"
|
||||
md += "- 20301 명상 (+70% 마나 회복)\n\n"
|
||||
md += "**Sub: 전투 그룹 (10xxx)**\n"
|
||||
md += "- 10201 분노 (+10% 물리 피해)\n"
|
||||
md += "- 10103 공략 (+20% 머리 공격 피해)\n\n"
|
||||
|
||||
md += "#### 마법 딜러 (Nave, Rene)\n\n"
|
||||
md += "**Main: 스킬 그룹 (20xxx)**\n"
|
||||
md += "- 20103 활기 (마나 높을 때 스킬 피해 증가)\n"
|
||||
md += "- 20202 왜곡 (-25% 쿨타임)\n"
|
||||
md += "- 20301 명상 (+70% 마나 회복)\n\n"
|
||||
md += "**Sub: 전투 그룹 (10xxx)**\n"
|
||||
md += "- 10301 폭풍 (+10% 마법 피해)\n"
|
||||
md += "- 10103 공략 (+20% 머리 공격 피해)\n\n"
|
||||
|
||||
md += "#### 원거리 딜러 (Urud, Lian)\n\n"
|
||||
md += "**Main: 스킬 그룹 (20xxx)**\n"
|
||||
md += "- 20101 저주 (지연 피해)\n"
|
||||
md += "- 20201 파괴 (+10% 스킬 피해)\n"
|
||||
md += "- 20301 명상 (+70% 마나 회복)\n\n"
|
||||
md += "**Sub: 전투 그룹 (10xxx)**\n"
|
||||
md += "- 10201 분노 (+10% 물리 피해)\n"
|
||||
md += "- 10103 공략 (+20% 머리 공격 피해)\n\n"
|
||||
|
||||
md += "#### 서포터 (Clad)\n\n"
|
||||
md += "**Main: 전투 그룹 (10xxx)**\n"
|
||||
md += "- 10101 충전 (+30% 궁극기 회복)\n"
|
||||
md += "- 10202 방패 (+7% 물리 저항)\n"
|
||||
md += "- 10302 수호 (+7% 마법 저항)\n\n"
|
||||
md += "**Sub: 보조 그룹 (40xxx)**\n"
|
||||
md += "- 40201 면역 (물약 사용 시 +20% 저항 20초)\n"
|
||||
md += "- 40301 효율 (+50% 물약 효과)\n\n"
|
||||
|
||||
# 특수 시스템 활용률
|
||||
md += "### 특수 시스템 활용률\n\n"
|
||||
md += "**전제**: 최적 플레이 = 100% 활용\n\n"
|
||||
|
||||
md += "#### Cazimord - Parrying (흘리기)\n"
|
||||
md += "- **판정 윈도우**: 0.2초\n"
|
||||
md += "- **성공 시 효과**:\n"
|
||||
md += " - 적 피해 무효화\n"
|
||||
md += " - 자동 반격 (높은 피해)\n"
|
||||
md += " - **스킬 쿨타임 감소**:\n"
|
||||
md += " - 섬광(SK170201): -3.8초\n"
|
||||
md += " - 날개베기(SK170202): -3.8초\n"
|
||||
md += " - 작열(SK170203): -6.8초\n"
|
||||
md += "- **활용률 시나리오**: 0% (미사용) vs 100% (완벽 성공)\n\n"
|
||||
|
||||
md += "#### Rio - Chain Score\n"
|
||||
md += "- **최대 스택**: 3\n"
|
||||
md += "- **효과**: 각 스킬별로 다른 위력 증가\n"
|
||||
md += "- **충전**: Dropping Attack 성공 시\n"
|
||||
md += "- **활용률**: 100% (항상 3스택 유지)\n\n"
|
||||
|
||||
md += "#### Urud & Lian - Reload\n"
|
||||
md += "- **탄약**: 6발\n"
|
||||
md += "- **재장전 시간**: 2.0초\n"
|
||||
md += "- **활용률**: 100% (탄약 관리 최적화)\n\n"
|
||||
|
||||
md += "#### Lian - Charging Bow\n"
|
||||
md += "- **만충전 데미지**: 1.5배\n"
|
||||
md += "- **충전 시간**: 레벨당 0.5초 (최대 1.5초)\n"
|
||||
md += "- **활용률**: 100% (항상 만충전 후 발사)\n\n"
|
||||
|
||||
md += "#### Rene - Spirit 소환\n"
|
||||
md += "- **소환수**: Ifrit, Shiva\n"
|
||||
md += "- **활용률**: 100% (소환수 항상 활용)\n\n"
|
||||
|
||||
md += "#### Sinobu - Shuriken 충전\n"
|
||||
md += "- **최대 충전**: 3개\n"
|
||||
md += "- **충전 속도**: 1초/개\n"
|
||||
md += "- **활용률**: 100% (충전 관리 최적화)\n\n"
|
||||
|
||||
md += "---\n\n"
|
||||
|
||||
return md
|
||||
|
||||
def generate_special_systems(data: Dict) -> str:
|
||||
"""특수 시스템 상세 분석 섹션 생성"""
|
||||
md = "## 🔧 특수 시스템 상세\n\n"
|
||||
|
||||
md += "### Cazimord - Parrying (흘리기)\n\n"
|
||||
md += "#### 메커니즘\n"
|
||||
md += "- **판정 윈도우**: 0.2초\n"
|
||||
md += "- **패링 성공 시**:\n"
|
||||
md += " - 적 공격 무효화\n"
|
||||
md += " - 자동 반격 (높은 피해)\n"
|
||||
md += " - 스킬 쿨타임 감소\n\n"
|
||||
|
||||
md += "#### 쿨타임 감소 효과\n"
|
||||
md += "| 스킬 | 기본 쿨타임 | 패링 성공 시 감소 | 패링 100% 시 유효 쿨타임 |\n"
|
||||
md += "|------|-------------|-------------------|------------------------|\n"
|
||||
|
||||
# Cazimord 스킬 데이터에서 쿨타임 정보 추출
|
||||
if 'cazimord' in data:
|
||||
cazimord = data['cazimord']
|
||||
skills = cazimord.get('skills', {})
|
||||
|
||||
parrying_skills = {
|
||||
'SK170201': ('섬광', -3.8),
|
||||
'SK170202': ('날개베기', -3.8),
|
||||
'SK170203': ('작열', -6.8)
|
||||
}
|
||||
|
||||
for skill_id, (skill_name, reduction) in parrying_skills.items():
|
||||
if skill_id in skills:
|
||||
skill = skills[skill_id]
|
||||
base_cooltime = skill.get('coolTime', 0)
|
||||
effective_cooltime = max(0, base_cooltime + reduction)
|
||||
md += f"| {skill_name} | {base_cooltime:.1f}초 | {reduction}초 | {effective_cooltime:.1f}초 |\n"
|
||||
|
||||
md += "\n#### DPS 영향\n"
|
||||
md += "- **패링 0%**: 기본 쿨타임 적용\n"
|
||||
md += "- **패링 100%**: 쿨타임 감소로 스킬 회전율 증가 → DPS 상승\n\n"
|
||||
|
||||
md += "### Rio - Chain Score\n\n"
|
||||
md += "#### 메커니즘\n"
|
||||
md += "- **스택 시스템**: 최대 3스택\n"
|
||||
md += "- **스택 획득**: Dropping Attack 스킬 성공 시 +1\n"
|
||||
md += "- **효과**: 스킬별로 스택 소모 및 추가 효과 발동\n\n"
|
||||
|
||||
md += "#### 스택별 효과\n"
|
||||
md += "- 각 스킬이 Chain Score 스택을 소모하여 강화\n"
|
||||
md += "- 스킬마다 다른 위력 증가 배율 적용\n\n"
|
||||
|
||||
md += "### Urud & Lian - Reload 시스템\n\n"
|
||||
md += "#### 메커니즘\n"
|
||||
md += "- **최대 탄약**: 6발\n"
|
||||
md += "- **재장전 시간**: 2.0초\n"
|
||||
md += "- **재장전 중**: 다른 행동 불가 (DPS 손실)\n\n"
|
||||
|
||||
md += "#### DPS 영향\n"
|
||||
md += "- 6발 소진 후 2초 공백 발생\n"
|
||||
md += "- 최적 플레이: 탄약 관리로 전투 공백 최소화\n\n"
|
||||
|
||||
md += "### Lian - Charging Bow\n\n"
|
||||
md += "#### 메커니즘\n"
|
||||
md += "- **충전 단계**: 3단계 (0.5초씩)\n"
|
||||
md += "- **만충전 배율**: 1.5배\n"
|
||||
md += "- **충전 중**: 이동 속도 감소\n\n"
|
||||
|
||||
md += "#### DPS 영향\n"
|
||||
md += "- 만충전 시 피해량 증가\n"
|
||||
md += "- 충전 시간 vs 피해량 트레이드오프\n\n"
|
||||
|
||||
md += "### Rene - 소환수 시스템\n\n"
|
||||
md += "#### Ifrit (화염 정령)\n"
|
||||
if 'rene' in data:
|
||||
rene = data['rene']
|
||||
summons = rene.get('summons', {})
|
||||
if 'Ifrit' in summons:
|
||||
ifrit = summons['Ifrit']
|
||||
md += f"- **지속 시간**: {ifrit.get('activeDuration', 0)}초\n"
|
||||
md += f"- **공격 타입**: 근접 화염 공격\n"
|
||||
md += f"- **독립 DPS**: 계산 필요 (소환수 몽타주 기반)\n\n"
|
||||
|
||||
md += "#### Shiva (냉기 정령)\n"
|
||||
if 'rene' in data and 'Shiva' in rene.get('summons', {}):
|
||||
shiva = summons.get('Shiva', {})
|
||||
md += f"- **지속 시간**: {shiva.get('activeDuration', 0)}초\n"
|
||||
md += f"- **공격 타입**: 원거리 냉기 공격\n"
|
||||
md += f"- **독립 DPS**: 계산 필요 (소환수 몽타주 기반)\n\n"
|
||||
|
||||
md += "### Sinobu - Shuriken 충전\n\n"
|
||||
md += "#### 메커니즘\n"
|
||||
md += "- **최대 충전**: 3개\n"
|
||||
md += "- **충전 속도**: 1초/개 (자동)\n"
|
||||
md += "- **소모**: 특정 스킬 사용 시 1개씩 소모\n\n"
|
||||
|
||||
md += "#### DPS 영향\n"
|
||||
md += "- 충전 관리로 스킬 사용 빈도 조절\n"
|
||||
md += "- 최적 플레이: 충전 타이밍 고려한 스킬 로테이션\n\n"
|
||||
|
||||
md += "---\n\n"
|
||||
|
||||
return md
|
||||
|
||||
def main():
|
||||
"""메인 실행 함수"""
|
||||
print("="*80)
|
||||
@ -527,7 +750,7 @@ def main():
|
||||
elif intermediate_file.exists():
|
||||
data_file = intermediate_file
|
||||
print(f"\n[ 중간 데이터 사용 ]: {data_file}")
|
||||
print("⚠️ 검증되지 않은 데이터입니다. validate_stalker_data.py를 먼저 실행하는 것을 권장합니다.")
|
||||
print("[WARN] 검증되지 않은 데이터입니다. validate_stalker_data.py를 먼저 실행하는 것을 권장합니다.")
|
||||
else:
|
||||
print(f"[FAIL] 데이터 파일 없음")
|
||||
print("먼저 extract_stalker_data_v2.py를 실행하세요.")
|
||||
@ -540,11 +763,13 @@ def main():
|
||||
|
||||
# 마크다운 생성
|
||||
md_content = generate_header()
|
||||
md_content += generate_analysis_prerequisites(data) # 분석 전제조건 추가
|
||||
md_content += generate_stalker_overview(data)
|
||||
md_content += generate_ultimate_overview(data)
|
||||
md_content += generate_dot_overview(data) # DoT 스킬 종합
|
||||
|
||||
# 개별 스토커
|
||||
stalker_count = 0
|
||||
for stalker_id in config.STALKERS:
|
||||
if stalker_id not in data:
|
||||
print(f"[WARN] {stalker_id}: 데이터 없음, 건너뜀")
|
||||
@ -552,20 +777,26 @@ def main():
|
||||
|
||||
print(f" - {stalker_id} 문서 생성 중...")
|
||||
md_content += generate_stalker_detail(stalker_id, data[stalker_id])
|
||||
stalker_count += 1
|
||||
|
||||
# 특수 시스템 상세 추가
|
||||
md_content += generate_special_systems(data)
|
||||
|
||||
# Footer
|
||||
md_content += "---\n\n"
|
||||
md_content += f"**생성 일시**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
|
||||
md_content += f"**데이터 소스**: {data_file.name}\n"
|
||||
md_content += f"**검증 상태**: {'검증 완료 ✅' if data_file.name == 'validated_data.json' else '미검증 ⚠️'}\n"
|
||||
md_content += f"**검증 상태**: {'검증 완료' if data_file.name == 'validated_data.json' else '미검증'}\n"
|
||||
|
||||
# 파일 저장
|
||||
output_file = config.OUTPUT_DIR / "03_스토커별_기본데이터_v2.md"
|
||||
# 파일 저장 - 새 파일명 사용
|
||||
output_file = config.OUTPUT_DIR / "01_분석_기초자료_v2.md"
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(md_content)
|
||||
|
||||
print(f"\n[OK] 문서 생성 완료: {output_file}")
|
||||
print(f" - 총 {len(data)}명 스토커 문서 생성")
|
||||
print(f" - 총 {stalker_count}명 스토커 문서 생성")
|
||||
print(f" - 분석 전제조건 포함")
|
||||
print(f" - 특수 시스템 상세 포함")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
121
분석도구/v2/output.txt
Normal file
121
분석도구/v2/output.txt
Normal file
@ -0,0 +1,121 @@
|
||||
================================================================================
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> v2
|
||||
================================================================================
|
||||
|
||||
[ JSON <20><><EFBFBD><EFBFBD> <20>ε<EFBFBD> ]
|
||||
Loading: DataTable.json
|
||||
Loading: Blueprint.json
|
||||
Loading: AnimMontage.json
|
||||
|
||||
=== DT_CharacterStat <20><><EFBFBD><EFBFBD> ===
|
||||
[OK] Hilda (<28><><EFBFBD><EFBFBD>) (hilda)
|
||||
[OK] Urud (<28>츣<EFBFBD><ECB8A3>) (urud)
|
||||
[OK] Nave (<28><><EFBFBD>̺<EFBFBD>) (nave)
|
||||
[OK] Baran (<28>ٶ<EFBFBD>) (baran)
|
||||
[OK] Rio (<28><><EFBFBD><EFBFBD>) (rio)
|
||||
[OK] Clad (Ŭ<><C5AC><EFBFBD><EFBFBD>) (clad)
|
||||
[OK] Rene (<28><><EFBFBD><EFBFBD>) (rene)
|
||||
[OK] Sinobu (<28>ó<EFBFBD><C3B3><EFBFBD>) (sinobu)
|
||||
[OK] Lian (<28><><EFBFBD><EFBFBD>) (lian)
|
||||
[OK] Cazimord (ī<><C4AB><EFBFBD><EFBFBD>) (cazimord)
|
||||
|
||||
=== DT_CharacterAbility <20><><EFBFBD><EFBFBD> ===
|
||||
[OK] hilda: {'weaponShield': 3}
|
||||
[OK] urud: {'bow': 1}
|
||||
[OK] nave: {'staff': 2}
|
||||
[OK] baran: {'twoHandWeapon': 3}
|
||||
[OK] rio: {'shortSword': 3}
|
||||
[OK] clad: {'mace': 2}
|
||||
[OK] rene: {'staff': 3}
|
||||
[OK] sinobu: {'shortSword': 2}
|
||||
[OK] lian: {'bow': 1}
|
||||
[OK] cazimord: {'weaponShield': 3}
|
||||
|
||||
=== DT_Skill <20><><EFBFBD><EFBFBD> ===
|
||||
[OK] <20><> 91<39><31> <20><>ų <20><><EFBFBD><EFBFBD>
|
||||
- hilda: 8<><38>
|
||||
- urud: 10<31><30>
|
||||
- nave: 9<><39>
|
||||
- baran: 8<><38>
|
||||
- rio: 8<><38>
|
||||
- clad: 8<><38>
|
||||
- rene: 8<><38>
|
||||
- sinobu: 8<><38>
|
||||
- lian: 10<31><30>
|
||||
- cazimord: 14<31><34>
|
||||
|
||||
=== GA_Skill Blueprint <20><><EFBFBD><EFBFBD> ===
|
||||
[OK] <20><> 112<31><32> GA_Skill Blueprint <20><><EFBFBD><EFBFBD>
|
||||
|
||||
=== AnimMontage <20><><EFBFBD><EFBFBD> ===
|
||||
[OK] <20><> 743<34><33> <20><>Ÿ<EFBFBD><C5B8> <20><><EFBFBD><EFBFBD> (PC + Summon)
|
||||
[INFO] <20><EFBFBD> ĵ<><C4B5> <20><><EFBFBD><EFBFBD> <20><>Ÿ<EFBFBD><C5B8>: 9<><39>
|
||||
- AM_PC_Baran_B_Attack_W01_01: ĵ<><C4B5> 1.50<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> 1.90<EFBFBD><EFBFBD>)
|
||||
- AM_PC_Baran_B_Attack_W01_02: ĵ<><C4B5> 1.48<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> 1.93<EFBFBD><EFBFBD>)
|
||||
- AM_PC_Baran_B_Attack_W01_03: ĵ<><C4B5> 1.50<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> 1.73<EFBFBD><EFBFBD>)
|
||||
- AM_PC_Clad_B_Attack_W01_03: ĵ<><C4B5> 1.30<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> 1.73<EFBFBD><EFBFBD>)
|
||||
- AM_PC_Clad_B_Attack_W01_02: ĵ<><C4B5> 0.97<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> 2.27<EFBFBD><EFBFBD>)
|
||||
- AM_PC_Clad_B_Attack_W01_01: ĵ<><C4B5> 1.14<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> 2.00<EFBFBD><EFBFBD>)
|
||||
- AM_PC_Hilda_B_Attack_W01_01: ĵ<><C4B5> 1.23<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> 1.60<EFBFBD><EFBFBD>)
|
||||
- AM_PC_Hilda_B_Attack_W01_02: ĵ<><C4B5> 1.23<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> 1.60<EFBFBD><EFBFBD>)
|
||||
- AM_PC_Hilda_B_Attack_W01_03: ĵ<><C4B5> 1.23<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> 1.37<EFBFBD><EFBFBD>)
|
||||
[INFO] <20><>ȯ<EFBFBD><C8AF> <20><><EFBFBD><EFBFBD> <20><>Ÿ<EFBFBD><C5B8>: 15<31><35>
|
||||
- AM_PC_Rene_B_Skill_SummonIfrit: 2.33<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 1.46<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=True
|
||||
- AM_PC_Rene_B_Skill_SummonShiva: 3.50<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 2.69<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=True
|
||||
- AM_Sum_Elemental_Fire_Attack: 1.00<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 1.00<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=False
|
||||
- AM_Sum_Elemental_Fire_Attack_Splash: 1.00<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 1.00<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=False
|
||||
- AM_Sum_Elemental_Fire_Death: 2.00<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 2.00<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=False
|
||||
- AM_Sum_Elemental_Fire_Shock: 2.87<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 2.87<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=False
|
||||
- AM_Sum_Elemental_Fire_Attack_N01: 3.67<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 2.29<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=True
|
||||
- AM_Sum_Elemental_Fire_Attack_N02: 3.67<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 2.29<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=True
|
||||
- AM_Sum_Elemental_Fire_Stun: 3.50<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 3.50<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=False
|
||||
- AM_Sum_Elemental_Fire_Appear: 1.70<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 1.70<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=False
|
||||
- AM_Sum_Elemental_Fire_Attack_N03: 3.33<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 3.70<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=False
|
||||
- AM_Sum_Elemental_Ice_Death: 9.33<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 9.33<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=False
|
||||
- AM_Sum_Elemental_Ice_Attack_N01: 4.63<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 2.32<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=False
|
||||
- AM_Sum_Elemental_Ice_Appear: 3.40<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 3.40<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=False
|
||||
- AM_Sum_Elemental_Ice_Attack: 3.00<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>: 3.00<EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD>=False
|
||||
|
||||
=== DT_NPCAbility <20><><EFBFBD><EFBFBD> ===
|
||||
[OK] Ifrit (none): 2<><32> <20><>Ÿ<EFBFBD><C5B8>
|
||||
1. AM_Sum_Elemental_Fire_Attack_N01
|
||||
2. AM_Sum_Elemental_Fire_Attack_N02
|
||||
[OK] Ifrit (normal): 1<><31> <20><>Ÿ<EFBFBD><C5B8>
|
||||
1. AM_Sum_Elemental_Fire_Attack_N03
|
||||
|
||||
=== DT_Rune <20><><EFBFBD><EFBFBD> ===
|
||||
[OK] 190<39><30> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>Ϸ<EFBFBD>
|
||||
|
||||
=== DT_RuneGroup <20><><EFBFBD><EFBFBD> ===
|
||||
[OK] <20><><EFBFBD><EFBFBD> <20><EFBFBD>: Core(3), Sub1(2), Sub2(2)
|
||||
[OK] <20><>ų <20><EFBFBD>: Core(3), Sub1(3), Sub2(2)
|
||||
[OK] <20><><EFBFBD><EFBFBD> <20><EFBFBD>: Core(3), Sub1(2), Sub2(3)
|
||||
[OK] <20><><EFBFBD><EFBFBD> <20><EFBFBD>: Core(2), Sub1(2), Sub2(2)
|
||||
[OK] <20><><EFBFBD><EFBFBD> <20><EFBFBD>: Core(3), Sub1(3), Sub2(3)
|
||||
|
||||
=== DT_Equip <20><><EFBFBD><EFBFBD> ===
|
||||
[OK] 485<38><35> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>Ϸ<EFBFBD>
|
||||
|
||||
=== DT_Float (<28><><EFBFBD><EFBFBD>ھ<EFBFBD> <20><><EFBFBD><EFBFBD>) <20><><EFBFBD><EFBFBD> ===
|
||||
[OK] GearScoreEquipCommon: 10
|
||||
[OK] GearScoreEquipUncommon: 30
|
||||
[OK] GearScoreEquipRare: 50
|
||||
[OK] GearScoreEquipLegendary: 100
|
||||
[OK] GearScoreSkillPassive: 50
|
||||
[OK] GearScoreSkillPerk: 50
|
||||
|
||||
=== <20><><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> ===
|
||||
[OK] Hilda (<28><><EFBFBD><EFBFBD>) (hilda): 5<><35> <20><>ų
|
||||
[OK] Urud (<28>츣<EFBFBD><ECB8A3>) (urud): 6<><36> <20><>ų
|
||||
[OK] Nave (<28><><EFBFBD>̺<EFBFBD>) (nave): 5<><35> <20><>ų
|
||||
[OK] Baran (<28>ٶ<EFBFBD>) (baran): 5<><35> <20><>ų
|
||||
[OK] Rio (<28><><EFBFBD><EFBFBD>) (rio): 5<><35> <20><>ų
|
||||
[OK] Clad (Ŭ<><C5AC><EFBFBD><EFBFBD>) (clad): 5<><35> <20><>ų
|
||||
[OK] Rene (<28><><EFBFBD><EFBFBD>) (rene): 5<><35> <20><>ų
|
||||
[OK] Sinobu (<28>ó<EFBFBD><C3B3><EFBFBD>) (sinobu): 5<><35> <20><>ų
|
||||
[OK] Lian (<28><><EFBFBD><EFBFBD>) (lian): 6<><36> <20><>ų
|
||||
[OK] Cazimord (ī<><C4AB><EFBFBD><EFBFBD>) (cazimord): 5<><35> <20><>ų
|
||||
|
||||
[OK] <20>߰<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>Ϸ<EFBFBD>: D:\Work\WorldStalker\DS-<2D><><EFBFBD><EFBFBD><EFBFBD>м<EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>\<5C>м<EFBFBD><D0BC><EFBFBD><EFBFBD><EFBFBD>\20251028_031316_v2\intermediate_data.json
|
||||
- <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>丮: D:\Work\WorldStalker\DS-<2D><><EFBFBD><EFBFBD><EFBFBD>м<EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>\<5C>м<EFBFBD><D0BC><EFBFBD><EFBFBD><EFBFBD>\20251028_031316_v2
|
||||
- <20><> 11<31><31> <20><><EFBFBD><EFBFBD>Ŀ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
48886
원본데이터/AnimMontage.json
48886
원본데이터/AnimMontage.json
File diff suppressed because it is too large
Load Diff
412766
원본데이터/Blueprint.json
412766
원본데이터/Blueprint.json
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
205283
원본데이터/DataTable.json
205283
원본데이터/DataTable.json
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user