728 lines
21 KiB
Markdown
728 lines
21 KiB
Markdown
# ARCHITECTURE.md
|
|
|
|
던전 스토커즈 전투 분석 시스템 - 기술 아키텍처 문서
|
|
|
|
- **목적**: 데이터 구조, 추출 로직, 판정 알고리즘 등 구현 세부사항 문서화
|
|
- **대상**: 개발자, 분석 스크립트 유지보수자
|
|
- **최종 업데이트**: 2025-10-27
|
|
|
|
---
|
|
|
|
## 📁 1. 데이터 소스 구조
|
|
|
|
### 1.1 JSON 파일 형식
|
|
|
|
모든 JSON 파일은 동일한 최상위 구조를 가집니다:
|
|
|
|
```json
|
|
{
|
|
"ExportedAt": "2025-10-24T15:58:55",
|
|
"TotalCount": 107,
|
|
"Assets": [
|
|
{
|
|
"AssetName": "DT_Skill",
|
|
"AssetPath": "/Game/Blueprints/DataTable/DT_Skill.DT_Skill",
|
|
"RowStructure": "SkillDataRow",
|
|
"Rows": [...]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**중요**: `Assets`는 배열이며, 각 요소는 `AssetName`으로 식별됩니다.
|
|
|
|
---
|
|
|
|
## 📊 2. DataTable.json 구조
|
|
|
|
### 2.1 DT_Skill (스킬 정의 테이블)
|
|
|
|
**위치**: `Assets` → AssetName == "DT_Skill"
|
|
**Row 구조**: `Rows` 배열 → 각 Row는 `{ "RowName": "SK110101", "Data": {...} }`
|
|
|
|
#### Data 필드 (주요)
|
|
|
|
| 필드 | 타입 | 설명 | 예시 |
|
|
|------|------|------|------|
|
|
| `name` | string | 스킬 이름 (한글) | "독성 화살" |
|
|
| `stalkerName` | string | 소속 스토커 | "urud" |
|
|
| `skillDamageRate` | float | 피해 배율 | 1.2 |
|
|
| `coolTime` | float | 쿨타임 (초) | 7.5 |
|
|
| `manaCost` | int | 마나 소모량 | 12 |
|
|
| `castingTime` | float | 시전 시간 (초) | 2.0 |
|
|
| `useMontages` | array | 몽타주 경로 배열 | `["/Script/Engine.AnimMontage'/Game/.../AM_Urud_Shot.AM_Urud_Shot'"]` |
|
|
| `desc` | string | 설명 (템플릿) | "피해 {0}% 증가" |
|
|
| `descValues` | array | 설명 치환 값 | `[3.8, 6.8]` |
|
|
| `bIsUltimate` | bool | 궁극기 여부 | true/false |
|
|
| `skillAttackType` | string | 공격 타입 | "PhysicalSkill", "MagicSkill" |
|
|
|
|
#### 몽타주 경로 추출
|
|
|
|
```python
|
|
# useMontages에서 몽타주 이름 추출
|
|
montage_path = row_data.get('useMontages', [])[0]
|
|
# 예: "/Script/Engine.AnimMontage'/Game/.../AM_Urud_Shot.AM_Urud_Shot'"
|
|
|
|
montage_name = montage_path.split('/')[-1].replace("'", "").split('.')[0]
|
|
# 결과: "AM_Urud_Shot"
|
|
```
|
|
|
|
#### ⚠️ 주의사항
|
|
|
|
1. **descValues 소수점 오류**: JSON 추출 과정에서 `3.799999952316284` 같은 값 발생
|
|
- **해결**: 모든 float 값을 `round(val, 2)`로 소수점 둘째자리 반올림
|
|
|
|
2. **useMontages는 배열**: 대부분 1개 요소, 일부 스킬은 2개 (예: SK150201)
|
|
- 첫 번째 몽타주가 시전 준비, 두 번째가 실제 공격
|
|
|
|
### 2.2 DT_CharacterAbility (캐릭터 기본 능력)
|
|
|
|
**위치**: `Assets` → AssetName == "DT_CharacterAbility"
|
|
**Row 구조**: `Rows` 배열 → 각 Row는 `{ "RowName": "urud", "Data": {...} }`
|
|
|
|
#### Data 필드 (주요)
|
|
|
|
| 필드 | 타입 | 설명 |
|
|
|------|------|------|
|
|
| `abilities` | array | 보유 스킬 목록 |
|
|
| `effects` | array | 패시브 효과 |
|
|
| `tags` | object | 게임플레이 태그 |
|
|
| `montageMap` | dict | 스킬 몽타주 맵 |
|
|
| `attackMontageMap` | dict | **평타 몽타주 맵** ⭐ |
|
|
|
|
#### attackMontageMap 구조 (평타 추출)
|
|
|
|
```json
|
|
{
|
|
"attackMontageMap": {
|
|
"bow": {
|
|
"abilityClass": "None",
|
|
"montageArray": [
|
|
"/Script/Engine.AnimMontage'/Game/_Art/_Character/PC/Urud/AnimMontage/Base/AM_PC_Urud_Base_B_Attack_N.AM_PC_Urud_Base_B_Attack_N'"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**추출 방법**:
|
|
```python
|
|
attack_map = row_data.get('attackMontageMap', {})
|
|
|
|
# 무기 타입별로 평타 몽타주가 다를 수 있음
|
|
for weapon_type, weapon_data in attack_map.items():
|
|
montage_array = weapon_data.get('montageArray', [])
|
|
if montage_array:
|
|
basic_attack_montage = montage_array[0]
|
|
```
|
|
|
|
**모든 스토커 공통 규칙**:
|
|
- 평타는 `DT_CharacterAbility.attackMontageMap`에 정의됨
|
|
- 스킬은 `DT_Skill.useMontages`에 정의됨
|
|
|
|
### 2.3 DT_CharacterStat (캐릭터 스탯)
|
|
|
|
**위치**: `Assets` → AssetName == "DT_CharacterStat"
|
|
|
|
#### Data 필드
|
|
|
|
| 필드 | 타입 | 설명 |
|
|
|------|------|------|
|
|
| `strength` | int | 힘 |
|
|
| `dexterity` | int | 민첩 |
|
|
| `intelligence` | int | 지능 |
|
|
| `constitution` | int | 체력 |
|
|
| `wisdom` | int | 지혜 |
|
|
| `maxHP` | int | 최대 HP |
|
|
| `maxMP` | int | 최대 MP |
|
|
| `manaRegen` | float | 마나 회복/초 |
|
|
|
|
---
|
|
|
|
## 🎬 3. AnimMontage.json 구조
|
|
|
|
### 3.1 AnimMontage Asset 구조
|
|
|
|
**위치**: `Assets` → Type은 없고 AssetName으로 식별
|
|
**Asset 구조**: `{ "AssetName": "AM_Urud_Shot", "AnimNotifies": [...], ... }`
|
|
|
|
#### 주요 필드
|
|
|
|
| 필드 | 타입 | 설명 |
|
|
|------|------|------|
|
|
| `AssetName` | string | 몽타주 이름 |
|
|
| `AssetPath` | string | 전체 경로 |
|
|
| `AnimNotifies` | array | **애니메이션 노티파이 배열** ⭐ |
|
|
| `AnimNotifyStates` | array | 노티파이 스테이트 배열 |
|
|
| `SequenceLength` | float | 시퀀스 전체 길이 |
|
|
|
|
### 3.2 AnimNotifies 구조 (공격 판정 핵심)
|
|
|
|
각 노티파이는 다음 구조를 가집니다:
|
|
|
|
```json
|
|
{
|
|
"NotifyClass": "AN_WSAttack_GAS_C",
|
|
"TriggerTime": 0.85,
|
|
"CustomProperties": {
|
|
"AttackMultiplier": "3.5",
|
|
"Event Tag": "(TagName=\"Event.SkillActivate\")"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 3.2.1 주요 NotifyClass 타입
|
|
|
|
| NotifyClass | 용도 | 판정 기준 |
|
|
|-------------|------|-----------|
|
|
| `AN_WSAttack_GAS_C` | GAS 기반 공격 | **공격 스킬** ✓ |
|
|
| `AN_WSAttack_Set_C` | Set 방식 공격 | **공격 스킬** ✓ |
|
|
| `AN_Trigger_Projectile_Shot_C` | 투사체 발사 | **공격 스킬** ✓ |
|
|
| `AN_SimpleSendEvent_C` | 이벤트 전송 | CustomProperties 확인 필요 |
|
|
| `ANS_SkillCancel_C` | 스킬 캔슬 윈도우 | 유틸리티 |
|
|
| `AN_ShowFirearmProjectile_C` | 투사체 표시 | 시각 효과 (공격 아님) |
|
|
|
|
#### 3.2.2 AN_SimpleSendEvent_C CustomProperties 판정
|
|
|
|
**CustomProperties 구조**:
|
|
```json
|
|
{
|
|
"Event Tag": "(TagName=\"Event.SkillActivate\")",
|
|
"NotifyColor": "(B=200,G=200,R=255,A=255)",
|
|
"bShouldFireInEditor": ""
|
|
}
|
|
```
|
|
|
|
**Event Tag 파싱**:
|
|
```python
|
|
custom_props = notify.get('CustomProperties', {})
|
|
event_tag = custom_props.get('Event Tag', '')
|
|
|
|
# Event Tag 형식: (TagName="Event.SkillActivate")
|
|
# 추출: "Event.SkillActivate"
|
|
|
|
if 'SkillActivate' in event_tag:
|
|
# 공격 스킬
|
|
elif 'SpawnProjectile' in event_tag:
|
|
# 공격 스킬 (투사체 생성)
|
|
elif 'AttackFire' in event_tag:
|
|
# 공격 스킬 (발사)
|
|
```
|
|
|
|
**주요 공격 Event Tag**:
|
|
- `Event.SkillActivate` - 스킬 활성화 (바란 일격분쇄, 클라드 다시 흙으로)
|
|
- `Event.SpawnProjectile` - 투사체 생성 (리옌 연화)
|
|
- `Event.AttackFire` - 공격 발사
|
|
|
|
**비공격 Event Tag**:
|
|
- `Event.BlockingStart` - 방어 시작
|
|
- `Ability.Attack.Ready` - 공격 준비 (공격 아님)
|
|
|
|
#### 3.2.3 TriggerTime (애니메이션 이벤트 시점)
|
|
|
|
`TriggerTime`은 몽타주 시작부터 노티파이 발동까지의 시간(초)입니다.
|
|
|
|
**actualDuration (시퀀스 길이) 계산**:
|
|
```python
|
|
# SequenceLength와 RateScale을 사용하여 계산
|
|
sequence_length = montage.get('SequenceLength', 0)
|
|
rate_scale = montage.get('RateScale', 1.0)
|
|
|
|
actual_duration = sequence_length / rate_scale if rate_scale > 0 else sequence_length
|
|
```
|
|
|
|
**중요**:
|
|
- **모든 스킬/평타**: 시퀀스 길이(actualDuration)를 사용하여 통일
|
|
- **DPS 계산**: `skillDamageRate / (actualDuration + castingTime)`
|
|
|
|
---
|
|
|
|
## 🎯 4. 공격 스킬 판정 로직 (우선순위)
|
|
|
|
### 4.1 판정 기준
|
|
|
|
**핵심 원칙**: 실질적으로 데미지가 발생하는 시점을 나타내는 노티파이의 존재 여부로 판정
|
|
|
|
### 4.2 판정 알고리즘
|
|
|
|
```python
|
|
def is_attack_skill(montage_data):
|
|
"""
|
|
공격 스킬 여부를 판정합니다.
|
|
|
|
우선순위:
|
|
1. AnimNotify의 NotifyName에 공격 키워드 포함 (부분 매칭)
|
|
2. AN_SimpleSendEvent 노티파이의 Event Tag 확인
|
|
"""
|
|
|
|
for montage in montage_data:
|
|
for notify in montage.get('AnimNotifies', []):
|
|
notify_name = notify.get('NotifyName', '')
|
|
notify_class = notify.get('NotifyClass', '')
|
|
|
|
# 1. NotifyName에 키워드 포함 (부분 매칭)
|
|
attack_keywords = ['AttackWithEquip', 'Projectile', 'SkillActive']
|
|
if any(keyword in notify_name for keyword in attack_keywords):
|
|
return True # 공격 스킬
|
|
|
|
# 2. SimpleSendEvent의 Event Tag 확인 (1순위에 해당되지 않을 때)
|
|
if 'SimpleSendEvent' in notify_class:
|
|
custom_props = notify.get('CustomProperties', {})
|
|
event_tag = custom_props.get('Event Tag', '')
|
|
|
|
# 공격 Event Tag
|
|
if 'Event.SkillActivate' in event_tag:
|
|
return True # 스킬 활성화 (바란, 클라드 등)
|
|
if 'Event.SpawnProjectile' in event_tag:
|
|
return True # 투사체 생성 (리옌 연화 등)
|
|
|
|
# 공격 노티파이가 없으면 공격 스킬 아님
|
|
return False
|
|
```
|
|
|
|
### 4.3 NotifyName 키워드 상세
|
|
|
|
| 키워드 | 설명 | 예시 |
|
|
|--------|------|------|
|
|
| **AttackWithEquip** | 무기 공격 (근접) | AttackWithEquip |
|
|
| **Projectile** | 투사체 발사 | AN_Projectile_C, AN_Trigger_Projectile_Shot_C, AN_ShowFirearmProjectile_C |
|
|
| **SkillActive** | 스킬 활성화 | AN_Trigger_Skill_Active_C |
|
|
|
|
**부분 매칭 예시**:
|
|
- `NotifyName == "AN_Trigger_Projectile_Shot_C"` → "Projectile" 포함 → ✅ 공격
|
|
- `NotifyName == "AN_ShowFirearmProjectile_C"` → "Projectile" 포함 → ✅ 공격
|
|
- `NotifyName == "AN_Trigger_Skill_Active_C"` → "SkillActive" 포함 → ✅ 공격
|
|
|
|
### 4.2 예외 케이스
|
|
|
|
#### 4.2.1 재장전 스킬 (유틸리티)
|
|
|
|
**스킬 ID**: SK110207 (우르드), SK190209 (리옌)
|
|
|
|
**특징**:
|
|
- AssetName에 "attack" 또는 "reload" 키워드 있음
|
|
- **하지만 공격 노티파이 없음** → 유틸리티로 판정
|
|
|
|
```python
|
|
# 재장전 스킬 예외 처리
|
|
if 'Reload' in asset_name or skill_id in ['SK110207', 'SK190209']:
|
|
# 노티파이 확인 필요
|
|
if not has_attack_notify(montage):
|
|
return False # 유틸리티
|
|
```
|
|
|
|
#### 4.2.2 차징 스킬 (공격)
|
|
|
|
**스킬 ID**: SK190101 (리옌 정조준)
|
|
|
|
**특징**:
|
|
- 차징 중에는 공격하지 않음
|
|
- **하지만 `AN_Trigger_Projectile_Shot_C` 노티파이 있음** → 공격 스킬
|
|
|
|
```python
|
|
# 차징 후 발사하는 스킬도 공격 스킬
|
|
if 'Charging' in asset_name:
|
|
if has_projectile_notify(montage):
|
|
return True # 공격 스킬
|
|
```
|
|
|
|
#### 4.2.3 소환 스킬 (공격)
|
|
|
|
**스킬 ID**: SK160202 (Rene Ifrit), SK160206 (Rene Shiva)
|
|
|
|
**특징**:
|
|
- 스킬 자체는 소환 동작
|
|
- **소환된 정령이 공격함** → 소환체 데이터 별도 처리
|
|
|
|
**처리 방법**:
|
|
1. 소환 스킬 자체는 skillDamageRate에 따라 공격/유틸리티 판정
|
|
2. 소환체 데이터는 Blueprint.json에서 추출
|
|
3. **문서에서는 "소환체" 섹션 분리**
|
|
|
|
---
|
|
|
|
## 🔧 5. 특수 데이터 처리
|
|
|
|
### 5.1 DoT (Damage over Time) 스킬
|
|
|
|
**정의 위치**: `config.py`
|
|
|
|
```python
|
|
DOT_SKILLS = {
|
|
'SK110204': {'dot_type': 'Poison', 'stalker': 'urud'}, # 독성 화살
|
|
'SK160203': {'dot_type': 'Bleed', 'stalker': 'rene'}, # 독기 화살
|
|
'SK170201': {'dot_type': 'Burn', 'stalker': 'cazimord'}, # 작열
|
|
'SK160202': {'dot_type': 'Burn', 'stalker': 'rene'}, # Ifrit
|
|
}
|
|
|
|
DOT_DAMAGE = {
|
|
'Poison': {
|
|
'rate': 0.20, # 대상 MaxHP의 20%
|
|
'duration': 5, # 5초간
|
|
'description': '대상 MaxHP의 20% (5초간)'
|
|
},
|
|
'Burn': {
|
|
'rate': 0.10, # 대상 MaxHP의 10%
|
|
'duration': 3, # 3초간
|
|
'description': '대상 MaxHP의 10% (3초간)'
|
|
},
|
|
'Bleed': {
|
|
'damage': 20, # 고정 20 피해
|
|
'duration': 5, # 5초간
|
|
'description': '고정 20 피해 (5초간)'
|
|
}
|
|
}
|
|
```
|
|
|
|
**DPS 계산 시 고려**:
|
|
- 기본 DPS = `skillDamageRate / (actualDuration + castingTime)`
|
|
- DoT DPS = `DoT 피해량 / DoT 지속시간`
|
|
- 총 DPS = 기본 DPS + DoT DPS (대상 HP에 따라 변동)
|
|
|
|
### 5.2 소환체 (Summons)
|
|
|
|
**소환체가 있는 스토커**: Rene (레네) 만
|
|
|
|
**소환 스킬**:
|
|
- SK160202: 정령 소환 : 화염 (Ifrit)
|
|
- SK160206: 정령 소환 : 냉기 (Shiva)
|
|
|
|
**데이터 구조**:
|
|
```json
|
|
{
|
|
"summonClass": "/Game/Blueprints/Characters/Rene/BP_Ifrit.BP_Ifrit_C",
|
|
"skillDamageRate": 1.2, // 소환체가 이 배율로 공격
|
|
"duration": 30 // 소환 지속 시간
|
|
}
|
|
```
|
|
|
|
**문서 표시 방법**:
|
|
```markdown
|
|
### SK160202 정령 소환 : 화염
|
|
- **스킬 타입**: 소환
|
|
- **피해 배율**: 1.2 (정령이 대행)
|
|
- **마나**: 15
|
|
- **쿨타임**: 20초
|
|
|
|
## 소환체
|
|
|
|
### 🔥 화염 정령 (Ifrit)
|
|
- **소환 스킬**: SK160202 정령 소환 : 화염
|
|
- **공격력**: 1.2 (소환자 공격력 대행)
|
|
- **공격 속도**: [Blueprint에서 추출]
|
|
- **지속시간**: 30초
|
|
- **특수 효과**: Burn DoT (MaxHP 10%, 3초)
|
|
```
|
|
|
|
---
|
|
|
|
## 📐 6. DPS 계산 공식
|
|
|
|
### 6.1 기본 DPS
|
|
|
|
```python
|
|
# 평타 DPS
|
|
basic_dps = attack_damage_rate / actual_duration
|
|
|
|
# 스킬 DPS
|
|
skill_dps = skill_damage_rate / (actual_duration + casting_time)
|
|
```
|
|
|
|
### 6.2 actualDuration (시퀀스 길이) 계산
|
|
|
|
```python
|
|
def calculate_actual_duration(montage_data):
|
|
"""
|
|
시퀀스 길이를 계산합니다.
|
|
|
|
모든 스킬과 평타에 대해 통일된 방식으로 계산합니다.
|
|
"""
|
|
sequence_length = montage_data.get('SequenceLength', 0)
|
|
rate_scale = montage_data.get('RateScale', 1.0)
|
|
|
|
if rate_scale > 0:
|
|
actual_duration = sequence_length / rate_scale
|
|
else:
|
|
actual_duration = sequence_length
|
|
|
|
return actual_duration
|
|
```
|
|
|
|
### 6.3 DoT DPS
|
|
|
|
```python
|
|
def calculate_dot_dps(skill_id, target_max_hp):
|
|
"""
|
|
DoT DPS를 계산합니다. 대상 HP에 따라 변동됩니다.
|
|
"""
|
|
if skill_id not in DOT_SKILLS:
|
|
return 0
|
|
|
|
dot_info = DOT_SKILLS[skill_id]
|
|
dot_type = dot_info['dot_type']
|
|
dot_config = DOT_DAMAGE[dot_type]
|
|
|
|
if 'rate' in dot_config:
|
|
# 비율 기반 (Poison, Burn)
|
|
total_damage = target_max_hp * dot_config['rate']
|
|
else:
|
|
# 고정 피해 (Bleed)
|
|
total_damage = dot_config['damage']
|
|
|
|
duration = dot_config['duration']
|
|
return total_damage / duration
|
|
```
|
|
|
|
---
|
|
|
|
## 🛠️ 7. 구현 노하우
|
|
|
|
### 7.1 JSON 파싱 주의사항
|
|
|
|
#### 7.1.1 최상위 구조 파악
|
|
|
|
```python
|
|
# ❌ 잘못된 접근
|
|
for item in data: # data가 dict이면 에러
|
|
...
|
|
|
|
# ✅ 올바른 접근
|
|
assets = data.get('Assets', [])
|
|
for asset in assets:
|
|
...
|
|
```
|
|
|
|
#### 7.1.2 Asset 찾기
|
|
|
|
```python
|
|
# AssetName으로 찾기
|
|
dt_skill = None
|
|
for asset in data.get('Assets', []):
|
|
if asset.get('AssetName') == 'DT_Skill':
|
|
dt_skill = asset
|
|
break
|
|
|
|
# Rows 접근
|
|
rows = dt_skill.get('Rows', [])
|
|
for row in rows:
|
|
row_name = row.get('RowName') # 예: "SK110101"
|
|
row_data = row.get('Data', {}) # 실제 데이터
|
|
```
|
|
|
|
### 7.2 몽타주 경로 파싱
|
|
|
|
```python
|
|
# 전체 경로
|
|
path = "/Script/Engine.AnimMontage'/Game/_Art/_Character/PC/Urud/AnimMontage/AM_Urud_Shot.AM_Urud_Shot'"
|
|
|
|
# 몽타주 이름 추출
|
|
montage_name = path.split('/')[-1] # "AM_Urud_Shot.AM_Urud_Shot'"
|
|
montage_name = montage_name.replace("'", "") # "AM_Urud_Shot.AM_Urud_Shot"
|
|
montage_name = montage_name.split('.')[0] # "AM_Urud_Shot"
|
|
```
|
|
|
|
### 7.3 CustomProperties 파싱
|
|
|
|
```python
|
|
# Event Tag 추출
|
|
custom_props = notify.get('CustomProperties', {})
|
|
event_tag = custom_props.get('Event Tag', '')
|
|
|
|
# 형식: (TagName="Event.SkillActivate")
|
|
# 단순 포함 검사로 충분
|
|
if 'SkillActivate' in event_tag:
|
|
is_attack = True
|
|
```
|
|
|
|
### 7.4 소수점 반올림
|
|
|
|
```python
|
|
# DT_Skill의 descValues 처리
|
|
desc_values_raw = data.get('descValues', [])
|
|
desc_values = []
|
|
for val in desc_values_raw:
|
|
if isinstance(val, float):
|
|
desc_values.append(round(val, 2)) # 소수점 둘째자리
|
|
else:
|
|
desc_values.append(val)
|
|
```
|
|
|
|
### 7.5 디버깅 팁
|
|
|
|
```python
|
|
# 1. Asset 개수 확인
|
|
print(f"총 Assets: {len(data.get('Assets', []))}")
|
|
|
|
# 2. AssetName 목록 출력
|
|
for asset in data.get('Assets', []):
|
|
print(f" - {asset.get('AssetName')}")
|
|
|
|
# 3. 노티파이 타입 확인
|
|
notify_types = set()
|
|
for notify in montage.get('AnimNotifies', []):
|
|
notify_types.add(notify.get('NotifyClass', ''))
|
|
print(f"노티파이 타입: {notify_types}")
|
|
|
|
# 4. CustomProperties 전체 출력
|
|
if 'CustomProperties' in notify:
|
|
print(f"CustomProperties: {notify['CustomProperties']}")
|
|
```
|
|
|
|
---
|
|
|
|
## 📋 8. 검증 체크리스트
|
|
|
|
### 8.1 데이터 추출 검증
|
|
|
|
- [ ] 10명 스토커 모두 추출됨
|
|
- [ ] 각 스토커당 평균 5~6개 스킬
|
|
- [ ] 궁극기 보유 스토커 확인 (Nave, Baran, Sinobu, Cazimord, Urud, Rio, Rene, Clad, Hilda, Lian)
|
|
- [ ] 평타 몽타주 추출 (attackMontageMap)
|
|
- [ ] DoT 스킬 4개 확인 (SK110204, SK160203, SK170201, SK160202)
|
|
|
|
### 8.2 공격 스킬 판정 검증
|
|
|
|
**수동 확인 필요 (자주 오류 발생)**:
|
|
- [ ] SK130301 (바란 일격분쇄) → 공격 스킬
|
|
- [ ] SK150201 (클라드 다시 흙으로) → 공격 스킬
|
|
- [ ] SK190201 (리옌 연화) → 공격 스킬
|
|
- [ ] SK190101 (리옌 정조준) → 공격 스킬
|
|
- [ ] SK110207 (우르드 Reload) → 유틸리티
|
|
- [ ] SK190209 (리옌 재장전) → 유틸리티
|
|
|
|
### 8.3 DPS 계산 검증
|
|
|
|
- [ ] 평타 actualDuration이 0이 아님
|
|
- [ ] 모든 스킬의 actualDuration = SequenceLength / RateScale
|
|
- [ ] castingTime이 있는 스킬 25개 확인
|
|
- [ ] DoT 스킬의 DoT 피해량 표시
|
|
|
|
### 8.4 문서 품질 검증
|
|
|
|
- [ ] 모든 스킬에 설명 있음 (descFormatted)
|
|
- [ ] descValues 소수점 2자리 (3.8, 6.8)
|
|
- [ ] 소환체 섹션 분리 (레네)
|
|
- [ ] DoT 종합 비교 테이블
|
|
- [ ] 실제 공격 시점 표시 (투사체 스킬)
|
|
|
|
---
|
|
|
|
## 🔄 9. 일반적인 오류 및 해결
|
|
|
|
### 9.1 "공격 스킬을 유틸리티로 잘못 분류"
|
|
|
|
**원인**:
|
|
- SimpleSendEvent의 Event Tag 미확인
|
|
- Projectile 노티파이만 있고 Attack 노티파이 없음
|
|
|
|
**해결**:
|
|
1. SimpleSendEvent의 CustomProperties 확인
|
|
2. Event.SkillActivate, Event.SpawnProjectile 체크
|
|
3. Projectile 노티파이도 공격 판정에 포함
|
|
|
|
### 9.2 "평타 actualDuration이 0"
|
|
|
|
**원인**:
|
|
- DT_Skill이 아닌 DT_CharacterAbility에서 평타 찾아야 함
|
|
- attackMontageMap 파싱 실패
|
|
- SequenceLength 또는 RateScale 데이터 누락
|
|
|
|
**해결**:
|
|
```python
|
|
# DT_CharacterAbility.attackMontageMap에서 추출
|
|
attack_map = char_ability_data.get('attackMontageMap', {})
|
|
for weapon_type, weapon_data in attack_map.items():
|
|
montage_array = weapon_data.get('montageArray', [])
|
|
|
|
# actualDuration 계산 확인
|
|
sequence_length = montage.get('SequenceLength', 0)
|
|
rate_scale = montage.get('RateScale', 1.0)
|
|
actual_duration = sequence_length / rate_scale if rate_scale > 0 else sequence_length
|
|
```
|
|
|
|
### 9.3 "DoT 피해가 DPS에 반영 안됨"
|
|
|
|
**원인**:
|
|
- DoT 스킬을 일반 공격 스킬로만 처리
|
|
- DoT 별도 계산 로직 누락
|
|
|
|
**해결**:
|
|
1. config.py에 DoT 스킬 정의
|
|
2. isDot 플래그 추가
|
|
3. DoT 종합 비교 테이블 생성
|
|
4. 개별 스킬에 DoT 상세 정보 표시
|
|
|
|
### 9.4 "descValues가 너무 긴 소수점"
|
|
|
|
**원인**:
|
|
- JSON 추출 시 float 정밀도 문제
|
|
|
|
**해결**:
|
|
```python
|
|
if isinstance(val, float):
|
|
val = round(val, 2)
|
|
```
|
|
|
|
---
|
|
|
|
## 📚 10. 참고 데이터
|
|
|
|
### 10.1 스토커 내부 이름
|
|
|
|
| 표시 이름 | 내부 이름 | 영문 이름 |
|
|
|-----------|-----------|-----------|
|
|
| 힐다 | hilda | Hilda |
|
|
| 우르드 | urud | Urud |
|
|
| 네이브 | nave | Nave |
|
|
| 바란 | baran | Baran |
|
|
| 리오 | rio | Rio |
|
|
| 클라드 | clad | Clad |
|
|
| 레네 | rene | Rene |
|
|
| 시노부 | sinobu | Sinobu |
|
|
| 리옌 | lian | Lian |
|
|
| 카지모르드 | cazimord | Cazimord |
|
|
|
|
### 10.2 스킬 ID 규칙
|
|
|
|
**형식**: `SK[스토커번호][스킬타입][순번]`
|
|
|
|
- **스토커 번호**: 11=Hilda, 12=Nave, 13=Baran, 14=Rio, 15=Clad, 16=Rene, 17=Cazimord, 18=Sinobu, 19=Lian, 11=Urud
|
|
- **스킬 타입**: 01=기본스킬, 02=서브스킬, 03=궁극기
|
|
- **순번**: 01부터 시작
|
|
|
|
**예시**:
|
|
- SK110101: Hilda 기본스킬 1
|
|
- SK120202: Nave 서브스킬 2
|
|
- SK130301: Baran 궁극기 1
|
|
|
|
### 10.3 몽타주 명명 규칙
|
|
|
|
**형식**: `AM_PC_[스토커명]_[카테고리]_[설명]`
|
|
|
|
**예시**:
|
|
- `AM_PC_Urud_Base_B_Attack_N`: 우르드 기본 평타
|
|
- `AM_PC_Lian_Base_000_Skill_ChargingBow`: 리옌 차징 스킬
|
|
- `AM_PC_Rene_B_Skill_Ifrit_Summon`: 레네 이프리트 소환
|
|
|
|
---
|
|
|
|
## 🎓 11. 추가 학습 자료
|
|
|
|
### 11.1 언리얼 엔진 시스템
|
|
|
|
- [Gameplay Ability System](https://docs.unrealengine.com/5.5/en-US/gameplay-ability-system-for-unreal-engine/)
|
|
- [Animation Notify System](https://docs.unrealengine.com/5.5/en-US/animation-notifies-in-unreal-engine/)
|
|
- [DataTable](https://docs.unrealengine.com/5.5/en-US/data-driven-gameplay-elements-in-unreal-engine/)
|
|
|
|
### 11.2 프로젝트 문서
|
|
|
|
- [README.md](README.md) - 프로젝트 개요 및 사용 가이드
|
|
- [../CLAUDE.md](../CLAUDE.md) - 전체 프로젝트 정보
|
|
- [분석도구/v2/장기과제_Blueprint변수검증.md](분석도구/v2/장기과제_Blueprint변수검증.md) - Blueprint 변수 검증 계획
|
|
|
|
---
|
|
|
|
**작성자**: AI-assisted Analysis Team
|
|
**최종 업데이트**: 2025-10-27
|
|
**버전**: 1.0
|