20251117_104033 기준 데이터

This commit is contained in:
2025-11-17 16:56:36 +09:00
parent e23e2b6e2c
commit 9f39ff3fd2
32 changed files with 218200 additions and 180863 deletions

149
데이터수집/README.md Normal file
View File

@ -0,0 +1,149 @@
# DS 전투 데이터 수집 시스템
## 📁 폴더 구조
```
데이터수집/
├── 수집스크립트/
│ ├── collect_combat_data.py # 메인 실행 스크립트
│ ├── data_extractors.py # DataTable/AnimMontage 데이터 추출
│ ├── custom_property_parser.py # CustomProperties 파싱
│ ├── markdown_formatter.py # 마크다운 생성
│ ├── validators.py # 데이터 검증
│ └── validate_result.py # 결과 검증 스크립트
└── 수집결과/
├── all_stalkers_combat_data.json # 통합 JSON 결과
├── collection_log.txt # 수집 로그
└── markdown/ # 스토커별 마크다운 (11개)
```
## 🚀 실행 방법
### 데이터 수집 실행
```bash
cd "D:\Work\WorldStalker\DS-전투분석_저장소\데이터수집\수집스크립트"
python collect_combat_data.py
```
### 결과 검증
```bash
cd "D:\Work\WorldStalker\DS-전투분석_저장소\데이터수집\수집스크립트"
python validate_result.py
```
## 📊 수집 데이터 항목
### DT_CharacterStat (캐릭터 기본 스탯)
- 기본 스탯: Str, Dex, Int, Con, Wis (합계 75)
- 체력/마나: HP, MP, ManaRegen, Stamina
- 공격: PhysicalDamage, MagicalDamage, CriticalPer, CriticalDamage
- 방어: Defense, 각종 저항력
- 스킬 ID 목록: defaultSkills, subSkill, ultimateSkill
- 장비 타입: equipableTypes
- 궁극기 포인트: ultimatePoint
**소수점 처리**: 모든 숫자 필드를 소수점 2자리로 반올림
### DT_Skill (스킬 상세 정보)
- 기본 정보: name, desc, descValues
- 속성: skillAttackType, skillElementType, skillDamageRate
- 코스트: manaCost, coolTime, castingTime
- 몽타주: useMontages (제외 키워드: ready, Equip, Equipment, _E)
- 어빌리티: abilityClass, activeAbilityClass
- 효과: gameplayEffectSet (trigger, gEClass)
### DT_CharacterAbility (기본 공격 몽타주)
- attackMontageMap: 무기 타입별 몽타주 배열
### AnimMontage (애니메이션 타이밍)
- 기본 정보: AssetName, SequenceLength, RateScale
- 섹션: Sections (SectionName, StartTime)
- AnimNotifies (4가지 타입만 수집):
- **ANS_AttackState_C**: 공격 상태 (AddNormalAttackPer, AddPhysicalAttackPer)
- **AnimNotifyState_AttackWithEquip**: 히트 판정 (AttackTag)
- **ANS_SkillCancel_C**: 스킬 캔슬 윈도우
- **AN_Trigger_Projectile_Shot_C**: 발사체 발사 (EventTag)
## 📝 결과물 형식
### 통합 JSON (all_stalkers_combat_data.json)
```json
{
"collection_metadata": {
"collected_at": "2025-11-05T20:04:25.894098",
"total_stalkers": 11
},
"stalkers": {
"hilda": {
"basic_info": { "name": "힐다", "jobName": "전사" },
"stats": { "str": 20.0, "dex": 15.0, ... },
"skills": {
"default": ["SK100201", "SK100202", "SK100204"],
"sub": "SK100101",
"ultimate": "SK100301"
},
"skill_details": { ... },
"basic_attacks": { ... }
}
}
}
```
### 마크다운 (각 스토커별)
- 기본 정보 (직업, 궁극기 포인트)
- 기본 스탯 테이블
- 스킬 상세 정보
- 설명 (desc + descValues 치환 완료)
- 스킬 속성 테이블
- 어빌리티 클래스
- Gameplay Effects
- 몽타주 타이밍 정보
- 기본 공격 정보
## ⚠️ 주의사항
1. **원본 데이터 경로**
- `DS-전투분석_저장소/원본데이터/DataTable.json`
- `DS-전투분석_저장소/원본데이터/AnimMontage.json`
2. **스킬 몽타주 제외 규칙**
- DT_Skill의 useMontages에만 적용
- 제외 키워드: ready, Equip, Equipment, _E
- 기본 공격 몽타주는 제외 규칙 미적용
3. **descValues 처리**
- JSON: 배열 그대로 보존
- Markdown: desc와 합쳐서 완성된 문장 생성
- 줄바꿈 태그 제거: `\r\n`, `\n`, `<br>`
4. **에러 처리**
- 경고 로그만 출력하고 계속 진행
- 누락 데이터는 빈 값으로 표시
## 🔄 재실행 방법
데이터가 업데이트되면:
1. 원본데이터 폴더에 최신 JSON 파일 배치
2. 수집 스크립트 재실행
3. 수집결과 폴더의 파일들이 자동으로 덮어쓰기됨
## 📈 수집 통계 (2025-11-05)
- 총 스토커: 11명
- 평균 스킬 수: 5개 (기본 3~4 + 서브 1 + 궁극기 1)
- 총 스킬 몽타주: 64개
- 총 기본 공격 몽타주: 24개
- 스탯 합계 검증: 모든 스토커 75.0 ✓
## 🛠️ 향후 보완 계획
- [ ] Blueprint 내부 정보 수집 (변수, 함수 등)
- [ ] GameplayEffect 상세 정보 수집
- [ ] 모든 AnimNotifies 수집 (현재는 4가지만)
- [ ] CurveTable 데이터 수집
- [ ] 데이터 비교/분석 도구 추가

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,153 @@
================================================================================
DS 전투 데이터 수집 결과 요약
================================================================================
수집 시각: 2025-11-17T10:53:43.311961
총 스토커 수: 11
--------------------------------------------------------------------------------
스토커: hilda
--------------------------------------------------------------------------------
이름: 힐다
직업: 전사
스탯 합계: 75.0
기본 스킬 수: 3
서브 스킬: SK100101
궁극기: SK100301
수집된 스킬 상세 정보 수: 5
스킬 몽타주 총 수: 6
기본 공격 몽타주 총 수: 3
--------------------------------------------------------------------------------
스토커: urud
--------------------------------------------------------------------------------
이름: 우르드
직업: 원거리
스탯 합계: 75.0
기본 스킬 수: 4
서브 스킬: SK110101
궁극기: SK110301
수집된 스킬 상세 정보 수: 6
스킬 몽타주 총 수: 5
기본 공격 몽타주 총 수: 1
--------------------------------------------------------------------------------
스토커: nave
--------------------------------------------------------------------------------
이름: 네이브
직업: 마법사
스탯 합계: 75.0
기본 스킬 수: 3
서브 스킬: SK120101
궁극기: SK120301
수집된 스킬 상세 정보 수: 5
스킬 몽타주 총 수: 4
기본 공격 몽타주 총 수: 2
--------------------------------------------------------------------------------
스토커: baran
--------------------------------------------------------------------------------
이름: 바란
직업: 전사
스탯 합계: 75.0
기본 스킬 수: 3
서브 스킬: SK130101
궁극기: SK130301
수집된 스킬 상세 정보 수: 5
스킬 몽타주 총 수: 5
기본 공격 몽타주 총 수: 3
--------------------------------------------------------------------------------
스토커: rio
--------------------------------------------------------------------------------
이름: 리오
직업: 암살자
스탯 합계: 75.0
기본 스킬 수: 3
서브 스킬: SK140101
궁극기: SK140301
수집된 스킬 상세 정보 수: 5
스킬 몽타주 총 수: 5
기본 공격 몽타주 총 수: 3
--------------------------------------------------------------------------------
스토커: clad
--------------------------------------------------------------------------------
이름: 클라드
직업: 성직자
스탯 합계: 75.0
기본 스킬 수: 3
서브 스킬: SK150101
궁극기: SK150301
수집된 스킬 상세 정보 수: 5
스킬 몽타주 총 수: 8
기본 공격 몽타주 총 수: 2
--------------------------------------------------------------------------------
스토커: rene
--------------------------------------------------------------------------------
이름: 레네
직업: 소환사
스탯 합계: 75.0
기본 스킬 수: 3
서브 스킬: SK160101
궁극기: SK160301
수집된 스킬 상세 정보 수: 5
스킬 몽타주 총 수: 6
기본 공격 몽타주 총 수: 3
--------------------------------------------------------------------------------
스토커: sinobu
--------------------------------------------------------------------------------
이름: 시노부
직업: 닌자
스탯 합계: 75.0
기본 스킬 수: 3
서브 스킬: SK180101
궁극기: SK180301
수집된 스킬 상세 정보 수: 5
스킬 몽타주 총 수: 6
기본 공격 몽타주 총 수: 2
--------------------------------------------------------------------------------
스토커: lian
--------------------------------------------------------------------------------
이름: 리옌
직업: 레인저
스탯 합계: 75.0
기본 스킬 수: 4
서브 스킬: SK190101
궁극기: SK190301
수집된 스킬 상세 정보 수: 6
스킬 몽타주 총 수: 7
기본 공격 몽타주 총 수: 1
--------------------------------------------------------------------------------
스토커: cazimord
--------------------------------------------------------------------------------
이름: 카지모르드
직업: 전사
스탯 합계: 75.0
기본 스킬 수: 3
서브 스킬: SK170101
궁극기: SK170301
수집된 스킬 상세 정보 수: 5
스킬 몽타주 총 수: 6
기본 공격 몽타주 총 수: 3
--------------------------------------------------------------------------------
스토커: blackmaria
--------------------------------------------------------------------------------
이름: 블랙마리아
직업: 전사
스탯 합계: 75.0
기본 스킬 수: 3
서브 스킬: SK200101
궁극기: SK200301
수집된 스킬 상세 정보 수: 5
스킬 몽타주 총 수: 5
기본 공격 몽타주 총 수: 3
================================================================================
수집 완료
================================================================================

View File

@ -0,0 +1,227 @@
# 바란 (Baran) - 전투 데이터
## 기본 정보
- **직업**: 전사
- **궁극기 포인트**: 2780
- **장착 가능 장비**: TwoHandWeapon, Heavy, Light
## 기본 스탯
| 스탯 | 값 |
|------|-----|
| 힘 (Str) | 25.0 |
| 민첩 (Dex) | 10.0 |
| 지능 (Int) | 5.0 |
| 체력 (Con) | 25.0 |
| 지혜 (Wis) | 10.0 |
| HP | 100.0 |
| MP | 50.0 |
| 마나 재생 | 0.2 |
| 지구력 (Stamina) | 100.0 |
| 크리티컬 확률 (%) | 5.0 |
## 스킬
### 기본 스킬
#### SK130204 - 갈고리 투척
**설명**: 갈고리를 던져 25%만큼 물리 피해를 입히고, 대상을 끌어당깁니다. 적중된 대상은 잠시 경직됩니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 25% |
| 이동 속도 배율 | 0.7x |
| 마나 비용 | 14 |
| 재사용 대기시간 | 13초 |
**어빌리티 클래스**: `GA_Skill_Baran_Pulling → GA_Skill_Baran_Pulling_C`
**Gameplay Effects**
- `OnProjectileHitTarget`: GE_ShockMotion_Weak → GE_ShockMotion_Weak_C
- `OnProjectileHitTarget`: GE_Enemy_ShockMotion → GE_Enemy_ShockMotion_C
- `OnProjectileHitTarget`: GE_Breakdown_Add → GE_Breakdown_Add_C
---
**몽타주 1: AM_PC_Baran_B_Skill_Pulling**
- 시퀀스 길이: 1.70초
- 재생 속도: 1x
**주요 타이밍**
- **AN_Trigger_Projectile_Shot**: 0.51초
- 이벤트 태그: `Event.Effect.Shot`
- **ANS_SkillCancel**: 1.28~1.70초 (지속: 0.42초)
#### SK130203 - 후려치기
**설명**: 대검을 크게 휘둘러 두 번 연속으로 120%만큼 물리 피해를 입힙니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 120% |
| 이동 속도 배율 | 0.7x |
| 마나 비용 | 9 |
| 재사용 대기시간 | 8초 |
| 지속 시간 | 5초 |
**어빌리티 클래스**: `GA_Skill_Baran_Smash → GA_Skill_Baran_Smash_C`
---
**몽타주 1: AM_PC_Baran_B_Skill_Smash**
- 시퀀스 길이: 1.89초
- 재생 속도: 1x
**주요 타이밍**
- **AttackWithEquip**: 0.49~0.70초 (지속: 0.22초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 0.78~0.97초 (지속: 0.19초)
- 공격 태그: `Event.Attack.Skill`
#### SK130206 - 깊게 찌르기
**설명**: 대검을 깊게 찔러 넣어 120%만큼 물리 피해를 입힙니다. 문을 파괴할 수 있습니다. 적중된 대상은 잠시 경직됩니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 110% |
| 이동 속도 배율 | 0.7x |
| 마나 비용 | 10 |
| 재사용 대기시간 | 7초 |
**어빌리티 클래스**: `GA_Skill_Baran_SwordStab → GA_Skill_Baran_SwordStab_C`
**Gameplay Effects**
- `OnHitTarget`: GE_Enemy_ShockMotion → GE_Enemy_ShockMotion_C
- `OnHitTarget`: GE_ShockMotion_Weak → GE_ShockMotion_Weak_C
- `OnHitTarget`: GE_Breakdown_Add → GE_Breakdown_Add_C
---
**몽타주 1: AM_PC_Baran_B_Skill_SwordStab**
- 시퀀스 길이: 1.75초
- 재생 속도: 1x
**주요 타이밍**
- **AttackWithEquip**: 0.68~0.90초 (지속: 0.22초)
- 공격 태그: `Event.Attack.Skill`
- **ANS_SkillCancel**: 1.66~1.73초 (지속: 0.07초)
### 서브 스킬
#### SK130101 - 무기 막기
**설명**: 무기로 공격을 방어합니다. 방어 유지 시 지구력이 소모됩니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.7x |
**어빌리티 클래스**: `GA_Skill_Common_Blocking → GA_Skill_Common_Blocking_C`
---
**몽타주 1: AM_PC_Baran_B_Skill_Blocking**
- 시퀀스 길이: 3.57초
- 재생 속도: 1x
### 궁극기
#### SK130301 - 마석 '일격분쇄'
**설명**: 대검을 내리찍어 4m의 균열을 생성합니다. 균열 범위 내 170%의 물리 피해를 주며, 적중된 대상은 기절합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 170% |
| 이동 속도 배율 | 0.7x |
| 시전 시간 | 10초 |
| 궁극기 | O |
| 지속 시간 | 2초 |
**어빌리티 클래스**: `GA_Skill_Baran_Decision → GA_Skill_Baran_Decision_C`
**Gameplay Effects**
- `InCasting`: GE_Ignore_Shock → GE_Ignore_Shock_C
- `InCasting`: GE_Ignore_Stun → GE_Ignore_Stun_C
- `CustomEventTarget1`: GE_StunMotion → GE_StunMotion_C
- `CustomEventTarget1`: GE_Attack_Splash_Physical → GE_Attack_Splash_Physical_C
---
**몽타주 1: AM_PC_Baran_B_Skill_RockBraker2**
- 시퀀스 길이: 2.97초
- 재생 속도: 1.5x
## 기본 공격
### 기본 공격: twoHandWeapon
#### 콤보 1: AM_PC_Baran_B_Attack_W01_01
- 시퀀스 길이: 1.90초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~1.17초 (지속: 1.17초)
- 일반 공격력 증가: 10.0%
- **AttackWithEquip**: 0.80~0.92초 (지속: 0.12초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 2: AM_PC_Baran_B_Attack_W01_02
- 시퀀스 길이: 1.93초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~1.17초 (지속: 1.17초)
- 일반 공격력 증가: 15.0%
- **AttackWithEquip**: 0.80~0.95초 (지속: 0.15초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 3: AM_PC_Baran_B_Attack_W01_03
- 시퀀스 길이: 1.73초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~1.00초 (지속: 1.00초)
- 일반 공격력 증가: 10.0%
- **AttackWithEquip**: 0.70~0.85초 (지속: 0.15초)
- 공격 태그: `Event.Attack.Normal`

View File

@ -0,0 +1,238 @@
# 블랙마리아 (Blackmaria) - 전투 데이터
## 기본 정보
- **직업**: 전사
- **궁극기 포인트**: 2780
- **장착 가능 장비**: TwoHandWeapon, Heavy, Light
## 기본 스탯
| 스탯 | 값 |
|------|-----|
| 힘 (Str) | 25.0 |
| 민첩 (Dex) | 10.0 |
| 지능 (Int) | 5.0 |
| 체력 (Con) | 25.0 |
| 지혜 (Wis) | 10.0 |
| HP | 100.0 |
| MP | 50.0 |
| 마나 재생 | 0.2 |
| 지구력 (Stamina) | 100.0 |
| 크리티컬 확률 (%) | 5.0 |
## 스킬
### 기본 스킬
#### SK200201 - 용맹의 쇄도
**설명**: 대검을 넓게 세워 정면을 방어하면서 전진합니다. 전진 중에 적에게 충돌하면 130%만큼의 물리 피해를 입히고 멈춥니다. 문을 파괴할 수 있습니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 130% |
| 마나 비용 | 11 |
| 재사용 대기시간 | 10초 |
**어빌리티 클래스**: `GA_Skill_BlackMaria_Strike → GA_Skill_BlackMaria_Strike_C`
**Gameplay Effects**
- `OnHitTarget`: GE_Breakdown_Add → GE_Breakdown_Add_C
---
**몽타주 1: AM_PC_BlackMaria_Base_000_Skill_ValiantCharge**
- 시퀀스 길이: 1.84초
- 재생 속도: 1x
**주요 타이밍**
- **AttackWithEquip**: 0.52~1.15초 (지속: 0.63초)
- 공격 태그: `Event.Attack.Skill`
#### SK200202 - 승천의 일격
**설명**: 대검을 올려쳐서 적들을 1.5초 동안 공중으로 띄워 올리고 110%만큼의 물리 피해를 입힙니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 110% |
| 마나 비용 | 8 |
| 재사용 대기시간 | 9초 |
**어빌리티 클래스**: `GA_Skill_BlackMaria_RisingSlash → GA_Skill_BlackMaria_RisingSlash_C`
**Gameplay Effects**
- `OnHitTarget`: GE_Enemy_ShockMotion → GE_Enemy_ShockMotion_C
- `OnHitTarget`: GE_ShockMotion_Weak → GE_ShockMotion_Weak_C
- `OnHitTarget`: GE_Breakdown_Add → GE_Breakdown_Add_C
---
**몽타주 1: AM_PC_BlackMaria_Base_000_Skill_AscensionStrike**
- 시퀀스 길이: 1.50초
- 재생 속도: 1x
**주요 타이밍**
- **AttackWithEquip**: 0.59~0.79초 (지속: 0.20초)
- 공격 태그: `Event.Attack.Skill`
- **ANS_SkillCancel**: 1.00~1.50초 (지속: 0.50초)
#### SK200203 - 결의의 방벽
**설명**: 10초간 피해를 입어도 방어구의 내구도가 1 이하로 떨어지지 않는 상태에 돌입합니다. 만약 방어구의 내구도가 0이라면 즉시 전체 내구도의 30%만큼을 회복합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.8x |
| 시전 시간 | 1초 |
| 마나 비용 | 11 |
| 재사용 대기시간 | 30초 |
| 지속 시간 | 10초 |
**어빌리티 클래스**: `GA_Skill_Casting_CanMove_CanRelease → GA_Skill_Casting_CanMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_BlackMaria_IgnoreArmorDamage → GA_Skill_BlackMaria_IgnoreArmorDamage_C`
---
**몽타주 1: AM_PC_BlackMaria_Base_000_Skill_AegisOfResolve**
- 시퀀스 길이: 1.50초
- 재생 속도: 1x
### 서브 스킬
#### SK200101 - 투구 깨기
**설명**: 대검으로 내리찍어 90%만큼의 물리 피해를 입힙니다. 충전 단계에 따라 피해량과 범위가 증가합니다. 1단계 충전을 하면 150%, 2단계 충전을 하면 225%만큼의 물리 피해를 입힙니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 90% |
| 이동 속도 배율 | 1x |
**어빌리티 클래스**: `GA_Skill_BlackMaria_HelmBreaker → GA_Skill_BlackMaria_HelmBreaker_C`
**Gameplay Effects**
- `CustomEventTarget1`: GE_Attack_Splash_Physical → GE_Attack_Splash_Physical_C
---
**몽타주 1: AM_PC_BlackMaria_Base_000_HelmBreaker2**
- 시퀀스 길이: 5.54초
- 재생 속도: 1x
**주요 타이밍**
- **AttackWithEquip**: 2.03~2.22초 (지속: 0.19초)
- 공격 태그: `Event.Attack.Skill`
- **ANS_SkillCancel**: 2.53~2.87초 (지속: 0.34초)
- **ANS_AttackState**: 3.14~3.72초 (지속: 0.58초)
- 물리 공격력 증가: 60.0%
- **AttackWithEquip**: 3.33~3.52초 (지속: 0.19초)
- 공격 태그: `Event.Attack.Skill`
- **ANS_SkillCancel**: 3.86~4.21초 (지속: 0.34초)
- **ANS_AttackState**: 4.52~5.10초 (지속: 0.58초)
- 물리 공격력 증가: 135.0%
- **AttackWithEquip**: 4.67~4.92초 (지속: 0.25초)
- 공격 태그: `Event.Attack.Skill`
- **ANS_SkillCancel**: 5.29~5.54초 (지속: 0.25초)
### 궁극기
#### SK200301 - 마석 '땅울림'
**설명**: 대검을 2번 땅에 내리 꽂습니다. 대검이 첫번째 꽂힐 때는 주변 6미터의 적을 대검쪽으로 끌어들이고, 두번째 꽂힐 때는 130%만큼의 물리 피해를 입힙니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 130% |
| 이동 속도 배율 | 0.7x |
| 시전 시간 | 10초 |
| 궁극기 | O |
| 지속 시간 | 2초 |
**어빌리티 클래스**: `GA_Skill_BlackMaria_ShockWave → GA_Skill_BlackMaria_ShockWave_C`
**Gameplay Effects**
- `InCasting`: GE_Ignore_Shock → GE_Ignore_Shock_C
- `InCasting`: GE_Ignore_Stun → GE_Ignore_Stun_C
- `CustomEventTarget1`: GE_Enemy_ShockMotion → GE_Enemy_ShockMotion_C
- `CustomEventTarget1`: GE_ShockMotion_Weak → GE_ShockMotion_Weak_C
- `CustomEventTarget1`: GE_Attack_Splash_Physical → GE_Attack_Splash_Physical_C
---
**몽타주 1: AM_PC_BlackMaria_Base_000_Skill_Bladequake**
- 시퀀스 길이: 2.50초
- 재생 속도: 1x
## 기본 공격
### 기본 공격: twoHandWeapon
#### 콤보 1: AM_PC_BlackMaria_Base_000_Attack_GreatSword1
- 시퀀스 길이: 1.67초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~1.00초 (지속: 1.00초)
- 일반 공격력 증가: 8.0%
- **AttackWithEquip**: 0.80~0.90초 (지속: 0.10초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 2: AM_PC_BlackMaria_Base_000_Attack_GreatSword2
- 시퀀스 길이: 1.67초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~1.27초 (지속: 1.27초)
- 일반 공격력 증가: 12.0%
- **AttackWithEquip**: 0.60~0.83초 (지속: 0.23초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 3: AM_PC_BlackMaria_Base_000_Attack_GreatSword3
- 시퀀스 길이: 1.73초
- 재생 속도: 1.2000000476837158x
**주요 타이밍**
- **ANS_AttackState**: 0.00~1.73초 (지속: 1.73초)
- 일반 공격력 증가: 40.0%
- **AttackWithEquip**: 0.68~0.83초 (지속: 0.15초)
- 공격 태그: `Event.Attack.Normal`

View File

@ -0,0 +1,257 @@
# 카지모르드 (Cazimord) - 전투 데이터
## 기본 정보
- **직업**: 전사
- **궁극기 포인트**: 2368
- **장착 가능 장비**: WeaponShield, Light, Cloth
## 기본 스탯
| 스탯 | 값 |
|------|-----|
| 힘 (Str) | 15.0 |
| 민첩 (Dex) | 25.0 |
| 지능 (Int) | 10.0 |
| 체력 (Con) | 15.0 |
| 지혜 (Wis) | 10.0 |
| HP | 100.0 |
| MP | 50.0 |
| 마나 재생 | 0.2 |
| 지구력 (Stamina) | 100.0 |
| 크리티컬 확률 (%) | 5.0 |
## 스킬
### 기본 스킬
#### SK170201 - 섬광
**설명**: 정면으로 4m 돌진하며, 베기 공격으로 공격력의 100%의 피해를 가합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 50% |
| 이동 속도 배율 | 1x |
| 마나 비용 | 5 |
| 재사용 대기시간 | 15.5초 |
| 스택 | 가능 (최대 2) |
**어빌리티 클래스**: `GA_Skill_Cazimord_Flash → GA_Skill_Cazimord_Flash_C`
**Gameplay Effects**
- `InActive`: GE_Ignore_Shock → GE_Ignore_Shock_C
---
**몽타주 1: AM_PC_Cazimord_B_Skill_Flash**
- 시퀀스 길이: 3.62초
- 재생 속도: 1x
**몽타주 2: AM_PC_Cazimord_B_Skill_Flash_Active**
- 시퀀스 길이: 1.73초
- 재생 속도: 1x
**주요 타이밍**
- **AttackWithEquip**: 0.37~0.57초 (지속: 0.20초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 0.37~0.57초 (지속: 0.20초)
- 공격 태그: `Event.Attack.Skill`
- **ANS_SkillCancel**: 1.00~1.73초 (지속: 0.73초)
#### SK170202 - 날개 베기
**설명**: 4번 베기로 공격력의 30% 피해를 입힙니다. 스킬 사용 중에는 경직에 면역 됩니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 30% |
| 이동 속도 배율 | 1x |
| 마나 비용 | 10 |
| 재사용 대기시간 | 15.5초 |
**어빌리티 클래스**: `GA_Skill_Cazimord_BladeStorm → GA_Skill_Cazimord_BladeStorm_C`
---
**몽타주 1: AM_PC_Cazimord_B_Skill_BladeStorm**
- 시퀀스 길이: 3.00초
- 재생 속도: 1.5x
**주요 타이밍**
- **AttackWithEquip**: 0.64~0.87초 (지속: 0.23초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 0.68~0.91초 (지속: 0.23초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 1.61~1.76초 (지속: 0.15초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 1.64~1.82초 (지속: 0.19초)
- 공격 태그: `Event.Attack.Skill`
- **ANS_SkillCancel**: 2.50~3.00초 (지속: 0.50초)
#### SK170203 - 작열
**설명**: 15초간 무기에 불을 붙여서 적중시킨 적에게 물리 피해의 20%만큼을 추가 마법 피해로 주고, 화상 상태로 만듭니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 1x |
| 시전 시간 | 2초 |
| 마나 비용 | 3 |
| 재사용 대기시간 | 27.5초 |
| 지속 시간 | 15초 |
**어빌리티 클래스**: `GA_Skill_Casting_CanMove_CanRelease → GA_Skill_Casting_CanMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_Cazimord_Burn_Active → GA_Skill_Cazimord_Burn_Active_C`
---
**몽타주 1: AM_PC_Cazimord_B_Skill_Burn**
- 시퀀스 길이: 2.43초
- 재생 속도: 1x
### 서브 스킬
#### SK170101 - 흘리기
**설명**: 무기로 적의 공격을 흘려냅니다. 흘리기에 성공하면 적의 공격을 막아냅니다. 그리고 스킬의 재사용 대기시간 일부가 감소됩니다. 섬광 및 날개베기는 각각 3.799999952316284초, 작열은 6.800000190734863초씩 감소 합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 1x |
**어빌리티 클래스**: `GA_Skill_Cazimord_Parrying → GA_Skill_Cazimord_Parrying_C`
---
**몽타주 1: AM_PC_Cazimord_B_Skill_Parrying**
- 시퀀스 길이: 1.61초
- 재생 속도: 1x
### 궁극기
#### SK170301 - 마석 '칼날폭풍'
**설명**: 마석의 힘을 빌려 빠르게 정면을 12회 공격해 각각 80%의 물리 피해를 입힙니다. 마지막 2회의 타격은 100%의 물리 피해를 입힙니다. 시전 중에는 천천히 이동할 수 있지만, 마지막 타격때는 이동할 수 없습니다. 또한 스킬 사용 중에는 경직에 면역 됩니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 80% |
| 이동 속도 배율 | 0.3x |
| 시전 시간 | 2초 |
| 궁극기 | O |
| 지속 시간 | 15초 |
**어빌리티 클래스**: `GA_SKill_Cazimord_ManaStoneBurn → GA_SKill_Cazimord_ManaStoneBurn_C`
---
**몽타주 1: AM_PC_Cazimord_B_Skill_ManaStoneBurn**
- 시퀀스 길이: 3.50초
- 재생 속도: 1x
**주요 타이밍**
- **AttackWithEquip**: 0.49~0.58초 (지속: 0.09초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 0.60~0.71초 (지속: 0.11초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 0.76~0.88초 (지속: 0.12초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 0.76~0.87초 (지속: 0.11초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 1.04~1.16초 (지속: 0.12초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 1.11~1.22초 (지속: 0.11초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 1.41~1.52초 (지속: 0.11초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 1.41~1.53초 (지속: 0.12초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 1.65~1.88초 (지속: 0.23초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 1.66~1.87초 (지속: 0.21초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 2.07~2.18초 (지속: 0.11초)
- 공격 태그: `Event.Attack.Ultimate`
- **AttackWithEquip**: 2.07~2.18초 (지속: 0.11초)
- 공격 태그: `Event.Attack.Ultimate`
## 기본 공격
### 기본 공격: weaponShield
#### 콤보 1: AM_PC_Cazimord_B_Attack_W01_01
- 시퀀스 길이: 1.67초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.90초 (지속: 0.90초)
- 일반 공격력 증가: -5.0%
- **AttackWithEquip**: 0.59~0.73초 (지속: 0.14초)
- 공격 태그: `Event.Attack.Normal`
- **AttackWithEquip**: 0.71~0.89초 (지속: 0.18초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 2: AM_PC_Cazimord_B_Attack_W01_02
- 시퀀스 길이: 1.90초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.94초 (지속: 0.94초)
- 일반 공격력 증가: 10.0%
- **AttackWithEquip**: 0.55~0.68초 (지속: 0.13초)
- 공격 태그: `Event.Attack.Normal`
- **AttackWithEquip**: 0.67~0.84초 (지속: 0.17초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 3: AM_PC_Cazimord_B_Attack_W01_03
- 시퀀스 길이: 1.87초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.85초 (지속: 0.85초)
- **AttackWithEquip**: 0.57~0.67초 (지속: 0.10초)
- 공격 태그: `Event.Attack.Normal`
- **AttackWithEquip**: 0.59~0.67초 (지속: 0.08초)
- 공격 태그: `Event.Attack.Normal`

View File

@ -0,0 +1,217 @@
# 클라드 (Clad) - 전투 데이터
## 기본 정보
- **직업**: 성직자
- **궁극기 포인트**: 2325
- **장착 가능 장비**: Mace, Heavy, Light
## 기본 스탯
| 스탯 | 값 |
|------|-----|
| 힘 (Str) | 15.0 |
| 민첩 (Dex) | 10.0 |
| 지능 (Int) | 10.0 |
| 체력 (Con) | 20.0 |
| 지혜 (Wis) | 20.0 |
| HP | 100.0 |
| MP | 50.0 |
| 마나 재생 | 0.2 |
| 지구력 (Stamina) | 100.0 |
| 크리티컬 확률 (%) | 5.0 |
## 스킬
### 기본 스킬
#### SK150206 - 치유
**설명**: 대상의 체력의 60 회복합니다. 대상이 없을 경우 자신에게 시전합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | MagicalSkill |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.7x |
| 시전 시간 | 1초 |
| 마나 비용 | 12 |
| 재사용 대기시간 | 3초 |
**어빌리티 클래스**: `GA_Skill_Casting_CanMove_CanRelease → GA_Skill_Casting_CanMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_Clad_HolyCure → GA_Skill_Clad_HolyCure_C`
---
**몽타주 1: AM_PC_Clad_Base_B_Skill_Ready**
- 시퀀스 길이: 2.23초
- 재생 속도: 1x
**몽타주 2: AM_PC_Clad_Base_B_Skill_HolyCure**
- 시퀀스 길이: 1.17초
- 재생 속도: 1x
#### SK150201 - 다시 흙으로
**설명**: 3m 내의 적에게 150% 빛 속성 마법 피해를 주고, 3초 동안 5 방어력을 감소 시킵니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | MagicalSkill |
| 원소 타입 | Holy |
| 피해 배율 | 150% |
| 이동 속도 배율 | 0.7x |
| 시전 시간 | 0.5초 |
| 마나 비용 | 9 |
| 재사용 대기시간 | 5초 |
| 지속 시간 | 5초 |
**어빌리티 클래스**: `GA_Skill_Casting_CanMove_CanRelease → GA_Skill_Casting_CanMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_Clad_TurnUndead → GA_Skill_Clad_TurnUndead_C`
---
**몽타주 1: AM_PC_Clad_Base_B_Skill_Ready**
- 시퀀스 길이: 2.23초
- 재생 속도: 1x
**몽타주 2: AM_PC_Clad_Base_B_Skill_TurnUndead**
- 시퀀스 길이: 1.20초
- 재생 속도: 1x
#### SK150202 - 신성한 빛
**설명**: 주변 아군의 지속 피해 효과를 제거하고. 6초 동안 면역 효과를 제공합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | MagicalSkill |
| 원소 타입 | None |
| 피해 배율 | 0% |
| 이동 속도 배율 | 0.7x |
| 시전 시간 | 1초 |
| 마나 비용 | 15 |
| 재사용 대기시간 | 7.5초 |
| 지속 시간 | 30초 |
**어빌리티 클래스**: `GA_Skill_Casting_CanMove_CanRelease → GA_Skill_Casting_CanMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_Clad_HolyLight → GA_Skill_Clad_HolyLight_C`
---
**몽타주 1: AM_PC_Clad_Base_B_Skill_Ready**
- 시퀀스 길이: 2.23초
- 재생 속도: 1x
**몽타주 2: AM_PC_Clad_Base_B_Skill_HolyLight**
- 시퀀스 길이: 1.17초
- 재생 속도: 1x
### 서브 스킬
#### SK150101 - 방패 방어
**설명**: 방패로 공격을 방어합니다. 방어 유지 시 지구력이 소모됩니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.7x |
**어빌리티 클래스**: `GA_Skill_Common_Blocking → GA_Skill_Common_Blocking_C`
---
**몽타주 1: AM_PC_Clad_Base_B_Skill_Block**
- 시퀀스 길이: 5.30초
- 재생 속도: 1x
### 궁극기
#### SK150301 - 마석 ‘황금’
**설명**: 마석의 힘을 해방하여 5초 동안 자신과 아군에게 300의 보호막을 생성합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 30000% |
| 이동 속도 배율 | 0.7x |
| 시전 시간 | 0.55초 |
| 궁극기 | O |
| 지속 시간 | 6초 |
**어빌리티 클래스**: `GA_Skill_Casting_Ultimate → GA_Skill_Casting_Ultimate_C`
**활성 어빌리티**: `GA_Skill_Clad_Gold → GA_Skill_Clad_Gold_C`
**Gameplay Effects**
- `InCasting`: GE_Ignore_Shock → GE_Ignore_Shock_C
- `InCasting`: GE_Ignore_Stun → GE_Ignore_Stun_C
---
**몽타주 1: AM_PC_Clad_Base_B_Skill_Gold**
- 시퀀스 길이: 1.50초
- 재생 속도: 1x
## 기본 공격
### 기본 공격: mace
#### 콤보 1: AM_PC_Clad_Base_Attack_Mace1
- 시퀀스 길이: 1.90초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.90초 (지속: 0.90초)
- 일반 공격력 증가: 5.0%
- **AttackWithEquip**: 0.56~0.71초 (지속: 0.15초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 2: AM_PC_Clad_Base_Attack_Mace2
- 시퀀스 길이: 2.27초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~1.00초 (지속: 1.00초)
- 일반 공격력 증가: 5.0%
- **AttackWithEquip**: 0.56~0.71초 (지속: 0.15초)
- 공격 태그: `Event.Attack.Normal`

View File

@ -0,0 +1,234 @@
# 힐다 (Hilda) - 전투 데이터
## 기본 정보
- **직업**: 전사
- **궁극기 포인트**: 2495
- **장착 가능 장비**: WeaponShield, Heavy, Light
## 기본 스탯
| 스탯 | 값 |
|------|-----|
| 힘 (Str) | 20.0 |
| 민첩 (Dex) | 15.0 |
| 지능 (Int) | 10.0 |
| 체력 (Con) | 20.0 |
| 지혜 (Wis) | 10.0 |
| HP | 100.0 |
| MP | 50.0 |
| 마나 재생 | 0.2 |
| 지구력 (Stamina) | 100.0 |
| 크리티컬 확률 (%) | 5.0 |
## 스킬
### 기본 스킬
#### SK100201 - 칼날 격돌
**설명**: 검을 휘둘러 130%만큼 번개 속성 물리 피해를 입힙니다. 적중된 대상은 잠시 경직됩니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | Lightning |
| 피해 배율 | 130% |
| 마나 비용 | 11 |
| 재사용 대기시간 | 6초 |
**어빌리티 클래스**: `GA_Skill_Hilda_SwordStrike → GA_Skill_Hilda_SwordStrike_C`
**Gameplay Effects**
- `OnHitTarget`: GE_Enemy_ShockMotion → GE_Enemy_ShockMotion_C
- `OnHitTarget`: GE_ShockMotion_Weak → GE_ShockMotion_Weak_C
- `OnHitTarget`: GE_Breakdown_Add → GE_Breakdown_Add_C
---
**몽타주 1: AM_PC_Hilda_B_Skill_Ready**
- 시퀀스 길이: 5.43초
- 재생 속도: 1x
**몽타주 2: AM_PC_Hilda_B_Skill_SwordStrike**
- 시퀀스 길이: 1.80초
- 재생 속도: 1x
**주요 타이밍**
- **AttackWithEquip**: 0.58~0.72초 (지속: 0.14초)
- 공격 태그: `Event.Attack.Skill`
- **ANS_SkillCancel**: 1.30~1.80초 (지속: 0.50초)
#### SK100202 - 반격
**설명**: 방패를 들어 5초 동안 반격 자세를 취합니다. 반격 성공 시 80%만큼 물리 피해를 줍니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 80% |
| 이동 속도 배율 | 0.8x |
| 마나 비용 | 10 |
| 재사용 대기시간 | 4초 |
| 지속 시간 | 5초 |
**어빌리티 클래스**: `GA_Skill_Knight_Counter → GA_Skill_Knight_Counter_C`
**활성 어빌리티**: `GA_Skill_ActiveBase → GA_Skill_ActiveBase_C`
**Gameplay Effects**
- `Instant`: GE_Skill_Hilda_Counter_Duration → GE_Skill_Hilda_Counter_Duration_C
---
**몽타주 1: AM_PC_Hilda_B_Skill_Counter**
- 시퀀스 길이: 3.37초
- 재생 속도: 1.2000000476837158x
**주요 타이밍**
- **AttackWithEquip**: 1.98~2.21초 (지속: 0.23초)
- 공격 태그: `Event.Attack.Skill`
#### SK100204 - 도발
**설명**: 주변 5m 내의 몬스터를 도발하여 위협 수치를 획득하고 대상의 이동속도를 3초 동안 25% 감속시킵니다.10초 동안 방어력이 15 증가합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 25000% |
| 이동 속도 배율 | 0.8x |
| 마나 비용 | 8 |
| 재사용 대기시간 | 10초 |
| 지속 시간 | 15초 |
**어빌리티 클래스**: `GA_Skill_Common_Provoke → GA_Skill_Common_Provoke_C`
**활성 어빌리티**: `GA_Skill_Common_Provoke_Active → GA_Skill_Common_Provoke_Active_C`
**Gameplay Effects**
- `InstantTarget`: GE_Provoked → GE_Provoked_C
- `InActive`: GE_HildaTauntDefense → GE_HildaTauntDefense_C
- `InstantTarget`: GE_ProvokeSlow → GE_ProvokeSlow_C
---
**몽타주 1: AM_PC_Hilda_B_Skill_Provoke**
- 시퀀스 길이: 2.00초
- 재생 속도: 1x
### 서브 스킬
#### SK100101 - 방패 방어
**설명**: 방패로 공격을 방어합니다.방어 유지 시 지구력이 소모됩니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.8x |
**어빌리티 클래스**: `GA_Skill_Common_Blocking → GA_Skill_Common_Blocking_C`
---
**몽타주 1: AM_PC_Hilda_B_Skill_Blocking**
- 시퀀스 길이: 4.34초
- 재생 속도: 1x
### 궁극기
#### SK100301 - 마석 ‘핏빛 달’
**설명**: 마석의 힘을 해방하여 20초 동안 공격력 15, 방어력 25 증가합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 50% |
| 이동 속도 배율 | 0.8x |
| 시전 시간 | 2초 |
| 궁극기 | O |
| 지속 시간 | 20초 |
**어빌리티 클래스**: `GA_Skill_Casting_Ultimate → GA_Skill_Casting_Ultimate_C`
**활성 어빌리티**: `GA_Skill_Hilda_BloodMoon_Active → GA_Skill_Hilda_BloodMoon_Active_C`
**Gameplay Effects**
- `InActive`: GE_Skill_Hilda_BloodMoon_Active → GE_Skill_Hilda_BloodMoon_Active_C
- `InCasting`: GE_Ignore_Shock → GE_Ignore_Shock_C
- `InCasting`: GE_Ignore_Stun → GE_Ignore_Stun_C
---
**몽타주 1: AM_PC_Hilda_B_Skill_BloodMoon**
- 시퀀스 길이: 1.50초
- 재생 속도: 1x
## 기본 공격
### 기본 공격: weaponShield
#### 콤보 1: AM_PC_Hilda_B_Attack_W01_01
- 시퀀스 길이: 1.60초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.80초 (지속: 0.80초)
- 일반 공격력 증가: -5.0%
- **AttackWithEquip**: 0.60~0.72초 (지속: 0.12초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 2: AM_PC_Hilda_B_Attack_W01_02
- 시퀀스 길이: 1.60초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.77초 (지속: 0.77초)
- 일반 공격력 증가: -5.0%
- **AttackWithEquip**: 0.57~0.67초 (지속: 0.09초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 3: AM_PC_Hilda_B_Attack_W01_03
- 시퀀스 길이: 1.37초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~1.00초 (지속: 1.00초)
- 일반 공격력 증가: 20.0%
- **AttackWithEquip**: 0.49~0.57초 (지속: 0.08초)
- 공격 태그: `Event.Attack.Normal`

View File

@ -0,0 +1,230 @@
# 리옌 (Lian) - 전투 데이터
## 기본 정보
- **직업**: 레인저
- **궁극기 포인트**: 2775
- **장착 가능 장비**: Bow, Light, Cloth
## 기본 스탯
| 스탯 | 값 |
|------|-----|
| 힘 (Str) | 10.0 |
| 민첩 (Dex) | 20.0 |
| 지능 (Int) | 10.0 |
| 체력 (Con) | 15.0 |
| 지혜 (Wis) | 20.0 |
| HP | 100.0 |
| MP | 50.0 |
| 마나 재생 | 0.2 |
| 지구력 (Stamina) | 100.0 |
| 크리티컬 확률 (%) | 5.0 |
## 스킬
### 기본 스킬
#### SK190207 - 속사
**설명**: 4발의 화살을 빠르게 발사하여 각각 85%만큼 물리 피해를 입힙니다. 화살을 4개 소모합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 85% |
| 이동 속도 배율 | 0.8x |
| 마나 비용 | 16 |
| 재사용 대기시간 | 7초 |
**어빌리티 클래스**: `GA_Skill_Lian_RapidShot → GA_Skill_Lian_RapidShot_C`
---
**몽타주 1: AM_PC_Lian_Base_000_Skill_RapidShot1**
- 시퀀스 길이: 2.67초
- 재생 속도: 1x
**주요 타이밍**
- **AN_Trigger_Projectile_Shot**: 0.83초
- 이벤트 태그: `Event.Effect.Shot`
- **AN_Trigger_Projectile_Shot**: 1.20초
- 이벤트 태그: `Event.Effect.Shot`
- **AN_Trigger_Projectile_Shot**: 1.59초
- 이벤트 태그: `Event.Effect.Shot`
- **AN_Trigger_Projectile_Shot**: 2.00초
- 이벤트 태그: `Event.Effect.Shot`
- **ANS_SkillCancel**: 2.33~2.63초 (지속: 0.29초)
#### SK190205 - 비연사
**설명**: 뒤로 빠지며 화살을 발사하여 150%만큼 물리 피해를 입힙니다. 화살을 1개 소모합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 150% |
| 마나 비용 | 15 |
| 재사용 대기시간 | 10초 |
**어빌리티 클래스**: `GA_Skill_Lian_BackStepBowAttack → GA_Skill_Lian_BackStepBowAttack_C`
---
**몽타주 1: AM_PC_Lian_Base_000_Skill_BackStepBowAttack**
- 시퀀스 길이: 1.74초
- 재생 속도: 1.2999999523162842x
**주요 타이밍**
- **AN_Trigger_Projectile_Shot**: 0.83초
- 이벤트 태그: `Event.Effect.Shot`
- **ANS_SkillCancel**: 1.49~1.70초 (지속: 0.21초)
**몽타주 2: AM_PC_Lian_Base_000_Skill_BackStepBowAttack**
- 시퀀스 길이: 1.74초
- 재생 속도: 1.2999999523162842x
**주요 타이밍**
- **AN_Trigger_Projectile_Shot**: 0.83초
- 이벤트 태그: `Event.Effect.Shot`
- **ANS_SkillCancel**: 1.49~1.70초 (지속: 0.21초)
#### SK190201 - 연화
**설명**: 60초 동안 적을 천천히 추적하는 연꽃을 만들어 발사합니다. 연꽃은 120%만큼 빛 속성 물리 피해를 입히며, 적중된 대상은 10초 동안 25%의 주는 피해 감소 효과를 받습니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | Holy |
| 피해 배율 | 120% |
| 이동 속도 배율 | 0.8x |
| 마나 비용 | 12 |
| 재사용 대기시간 | 7.5초 |
**어빌리티 클래스**: `GA_Skill_Lian_DarkSouls → GA_Skill_Lian_DarkSouls_C`
---
**몽타주 1: AM_PC_Lian_Base_000_Skill_DarkSouls_NoCasting**
- 시퀀스 길이: 2.20초
- 재생 속도: 1x
#### SK190209 - 재장전
**설명**: 화살을 화살통에 장전 합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.9x |
| 시전 시간 | 5초 |
| 지속 시간 | 1초 |
**어빌리티 클래스**: `GA_Attack_Firearm_Reload → GA_Attack_Firearm_Reload_C`
---
**몽타주 1: AM_PC_Lian_Base_000_Skill_Reload**
- 시퀀스 길이: 1.43초
- 재생 속도: 1x
### 서브 스킬
#### SK190101 - 정조준
**설명**: 조준하는 동안 물리 피해가 증가하는 화살을 발사합니다. 최대 150%까지 물리 피해가 증가합니다. 화살을 1개 소모합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 70% |
| 이동 속도 배율 | 0.8x |
| 시전 시간 | 1.5초 |
**어빌리티 클래스**: `GA_Skill_Lian_ChargingBow → GA_Skill_Lian_ChargingBow_C`
---
**몽타주 1: AM_PC_Lian_Base_000_Skill_ChargingBow**
- 시퀀스 길이: 4.93초
- 재생 속도: 1x
**주요 타이밍**
- **AN_Trigger_Projectile_Shot**: 4.20초
- 이벤트 태그: `Event.Effect.Shot`
### 궁극기
#### SK190301 - 마석 '폭우'
**설명**: 마석의 힘을 개방하여 15초 동안 화살을 소모하지 않으며, 쿨타임이 50% 감소합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 5000% |
| 이동 속도 배율 | 0.8x |
| 시전 시간 | 1.5초 |
| 궁극기 | O |
| 지속 시간 | 15초 |
**어빌리티 클래스**: `GA_Skill_Casting_Ultimate → GA_Skill_Casting_Ultimate_C`
**활성 어빌리티**: `GA_Skill_Lian_ManaStoneSilence_Active → GA_Skill_Lian_ManaStoneSilence_Active_C`
**Gameplay Effects**
- `InCasting`: GE_Ignore_Shock → GE_Ignore_Shock_C
- `InCasting`: GE_Ignore_Stun → GE_Ignore_Stun_C
- `InActive`: GE_Skill_Lian_ManaStoneSilence → GE_Skill_Lian_ManaStoneSilence_C
---
**몽타주 1: AM_PC_Lian_Base_000_Skill_ManastoneSilence**
- 시퀀스 길이: 1.50초
- 재생 속도: 1x
## 기본 공격
### 기본 공격: bow
#### 콤보 1: AM_PC_Lian_Base_000_Attack_Bow
- 시퀀스 길이: 3.27초
- 재생 속도: 1x
**주요 타이밍**
- **AN_Trigger_Projectile_Shot**: 2.50초

View File

@ -0,0 +1,201 @@
# 네이브 (Nave) - 전투 데이터
## 기본 정보
- **직업**: 마법사
- **궁극기 포인트**: 2728
- **장착 가능 장비**: Staff, Light, Cloth
## 기본 스탯
| 스탯 | 값 |
|------|-----|
| 힘 (Str) | 10.0 |
| 민첩 (Dex) | 10.0 |
| 지능 (Int) | 25.0 |
| 체력 (Con) | 10.0 |
| 지혜 (Wis) | 20.0 |
| HP | 100.0 |
| MP | 50.0 |
| 마나 재생 | 0.2 |
| 지구력 (Stamina) | 100.0 |
| 크리티컬 확률 (%) | 5.0 |
## 스킬
### 기본 스킬
#### SK120201 - 마법 화살
**설명**: 최대 3개의 마법 화살을 생성하여 일반 공격으로 발사합니다. 마법 화살은 각각 80%만큼 마법 피해를 입힙니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | MagicalSkill |
| 원소 타입 | None |
| 피해 배율 | 80% |
| 이동 속도 배율 | 0.8x |
| 시전 시간 | 2초 |
| 마나 비용 | 18 |
| 재사용 대기시간 | 3.5초 |
| 지속 시간 | 600초 |
**어빌리티 클래스**: `GA_Skill_Casting_CanMove_CanRelease → GA_Skill_Casting_CanMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_Nave_MagicMissile_Active → GA_Skill_Nave_MagicMissile_Active_C`
---
**몽타주 1: AM_PC_Nave_B_Skill_MagicMissile**
- 시퀀스 길이: 3.33초
- 재생 속도: 1x
#### SK120202 - 화염구
**설명**: 화염구를 생성하여 일반 공격으로 발사합니다. 화염구는 200% 화염 속성 마법 피해와 주변에 150% 추가 피해를 입힙니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | MagicalSkill |
| 원소 타입 | Fire |
| 피해 배율 | 200% |
| 이동 속도 배율 | 0.8x |
| 시전 시간 | 4초 |
| 마나 비용 | 25 |
| 재사용 대기시간 | 5초 |
| 지속 시간 | 600초 |
**어빌리티 클래스**: `GA_Skill_Casting_CanMove_CanRelease → GA_Skill_Casting_CanMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_Nave_FireWall_Active → GA_Skill_Nave_FireWall_Active_C`
**Gameplay Effects**
- `OnProjectileHitTarget`: GE_ShockMotion_Weak → GE_ShockMotion_Weak_C
- `OnProjectileHitTarget`: GE_Enemy_ShockMotion → GE_Enemy_ShockMotion_C
- `OnProjectileHitRangedTarget`: GE_Attack_Projectile_Splash_Fire → GE_Attack_Projectile_Splash_Fire_C
---
**몽타주 1: AM_PC_Nave_B_Skill_FireWall**
- 시퀀스 길이: 3.33초
- 재생 속도: 1x
#### SK120206 - 노대바람
**설명**: 강한 바람으로 밀쳐내고 50%만큼 마법 피해를 입힙니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | MagicalSkill |
| 원소 타입 | None |
| 피해 배율 | 50% |
| 이동 속도 배율 | 0.8x |
| 마나 비용 | 9 |
| 재사용 대기시간 | 7초 |
**어빌리티 클래스**: `GA_Skill_Casting_CanMove_CanRelease → GA_Skill_Casting_CanMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_Nave_WindForce → GA_Skill_Nave_WindForce_C`
**Gameplay Effects**
- `OnActiveRangedTarget`: GE_Attack_Ability → GE_Attack_Ability_C
---
**몽타주 1: AM_PC_Nave_B_Skill_WindForce**
- 시퀀스 길이: 1.33초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.05~1.11초 (지속: 1.06초)
### 서브 스킬
#### SK120101 - 마력 충전
**설명**: 시전하는 동안 1의 마나를 추가로 회복합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 500% |
| 이동 속도 배율 | 0.8x |
| 시전 시간 | 9999초 |
| 재사용 대기시간 | 1초 |
**어빌리티 클래스**: `GA_Skill_Nave_ManaCharge_Casting → GA_Skill_Nave_ManaCharge_Casting_C`
---
**몽타주 1: AM_PC_Nave_B_Skill_ManaRestore**
- 시퀀스 길이: 2.80초
- 재생 속도: 1x
### 궁극기
#### SK120301 - 마석 ‘해방’
**설명**: 마석의 힘으로 5초 동안 적을 관통하는 광선을 발사합니다. 광선은 0.5초 간격마다 100%의 마법 피해를 입힙니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | MagicalSkill |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.8x |
| 시전 시간 | 2초 |
| 궁극기 | O |
| 지속 시간 | 5초 |
**어빌리티 클래스**: `GA_Skill_Nave_Escape4 → GA_Skill_Nave_Escape4_C`
**Gameplay Effects**
- `InCasting`: GE_Ignore_Shock → GE_Ignore_Shock_C
- `InCasting`: GE_Ignore_Stun → GE_Ignore_Stun_C
- `InActive`: GE_Skill_Nave_Escape_Active → GE_Skill_Nave_Escape_Active_C
- `OnActiveRangedTarget`: GE_Skill_Nave_Escape → GE_Skill_Nave_Escape_C
## 기본 공격
### 기본 공격: staff
#### 콤보 1: AM_PC_Nave_B_Attack_W01_01
- 시퀀스 길이: 1.60초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.90초 (지속: 0.90초)
- **AttackWithEquip**: 0.49~0.59초 (지속: 0.11초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 2: AM_PC_Nave_B_Attack_W01_02
- 시퀀스 길이: 1.70초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.90초 (지속: 0.90초)
- **AttackWithEquip**: 0.41~0.48초 (지속: 0.07초)
- 공격 태그: `Event.Attack.Normal`

View File

@ -0,0 +1,221 @@
# 레네 (Rene) - 전투 데이터
## 기본 정보
- **직업**: 소환사
- **궁극기 포인트**: 2305
- **장착 가능 장비**: Staff, Light, Cloth
## 기본 스탯
| 스탯 | 값 |
|------|-----|
| 힘 (Str) | 10.0 |
| 민첩 (Dex) | 10.0 |
| 지능 (Int) | 20.0 |
| 체력 (Con) | 10.0 |
| 지혜 (Wis) | 25.0 |
| HP | 100.0 |
| MP | 50.0 |
| 마나 재생 | 0.2 |
| 지구력 (Stamina) | 100.0 |
| 크리티컬 확률 (%) | 5.0 |
## 스킬
### 기본 스킬
#### SK160202 - 정령 소환 : 화염
**설명**: 20초 동안 유지되는 화염의 정령을 소환합니다. 정령은 이동하지 않고 화염 화살을 발사하여 120% 화염 속성 마법 피해를 입힙니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | MagicalSkill |
| 원소 타입 | None |
| 피해 배율 | 120% |
| 이동 속도 배율 | 0.8x |
| 마나 비용 | 8 |
| 재사용 대기시간 | 7초 |
| 지속 시간 | 20초 |
**어빌리티 클래스**: `GA_Skill_Casting_CanMove_CanRelease → GA_Skill_Casting_CanMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_Rene_SummonIfrit_Active → GA_Skill_Rene_SummonIfrit_Active_C`
---
**몽타주 1: AM_PC_Rene_B_Skill_SummonIfrit**
- 시퀀스 길이: 2.33초
- 재생 속도: 1.600000023841858x
#### SK160206 - 정령 소환 : 냉기
**설명**: 60초 동안 유지되는 냉기의 정령을 소환합니다. 정령은 레네를 따라 이동하며 얼음 송곳을 소환합니다. 얼음 송곳은 80%만큼 물 속성 마법 피해를 입히며, 적중된 적은 둔화됩니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | MagicalSkill |
| 원소 타입 | None |
| 피해 배율 | 80% |
| 이동 속도 배율 | 0.8x |
| 마나 비용 | 15 |
| 재사용 대기시간 | 10초 |
| 지속 시간 | 60초 |
**어빌리티 클래스**: `GA_Skill_Casting_CanMove_CanRelease → GA_Skill_Casting_CanMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_Rene_SummonShiva_Active → GA_Skill_Rene_SummonShiva_Active_C`
---
**몽타주 1: AM_PC_Rene_B_Skill_SummonShiva**
- 시퀀스 길이: 3.50초
- 재생 속도: 1.2999999523162842x
#### SK160203 - 독기 화살
**설명**: 방어력을 무시하고 30만큼 암흑 속성 마법 피해를 입힙니다. 적중된 적은 출혈 상태가 됩니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | MagicalSkill |
| 원소 타입 | Dark |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.8x |
| 시전 시간 | 2초 |
| 마나 비용 | 15 |
| 재사용 대기시간 | 10초 |
| 지속 시간 | 600초 |
**어빌리티 클래스**: `GA_Skill_Casting_CanMove_CanRelease → GA_Skill_Casting_CanMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_Rene_PoisonGas_Active → GA_Skill_Rene_PoisonGas_Active_C`
---
**몽타주 1: AM_PC_Rene_B_Skill_PoisonGas**
- 시퀀스 길이: 4.67초
- 재생 속도: 1x
### 서브 스킬
#### SK160101 - 할퀴기
**설명**: 손톱을 휘둘러 75%만큼 마법 피해를 입히고 흡혈합니다. 피해의 30%만큼 체력을 회복합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | MagicalSkill |
| 원소 타입 | None |
| 피해 배율 | 75% |
| 이동 속도 배율 | 0.8x |
**어빌리티 클래스**: `GA_Skill_Rene_Scratching → GA_Skill_Rene_Scratching_C`
---
**몽타주 1: AM_PC_Rene_B_Skill_Scratching**
- 시퀀스 길이: 1.67초
- 재생 속도: 1.5x
**주요 타이밍**
- **AttackWithEquip**: 0.58~0.73초 (지속: 0.15초)
- 공격 태그: `Event.Attack.Sub`
**몽타주 2: AM_PC_Rene_B_Skill_Scratching2**
- 시퀀스 길이: 1.93초
- 재생 속도: 1.2000000476837158x
**주요 타이밍**
- **AttackWithEquip**: 0.61~0.75초 (지속: 0.14초)
- 공격 태그: `Event.Attack.Sub`
### 궁극기
#### SK160301 - 마석 ‘붉은 축제’
**설명**: 마석의 힘을 해방하여 20초 동안 자신과 아군의 모든 공격에 흡혈 효과를 부여합니다. 피해의 50%만큼 체력을 회복합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | MagicalSkill |
| 원소 타입 | None |
| 피해 배율 | 5000% |
| 이동 속도 배율 | 0.8x |
| 시전 시간 | 2초 |
| 궁극기 | O |
| 지속 시간 | 20초 |
**어빌리티 클래스**: `GA_Skill_Casting_Ultimate → GA_Skill_Casting_Ultimate_C`
**활성 어빌리티**: `GA_Skill_Rene_ManaStoneCarnival_Active → GA_Skill_Rene_ManaStoneCarnival_Active_C`
**Gameplay Effects**
- `InCasting`: GE_Ignore_Shock → GE_Ignore_Shock_C
- `InCasting`: GE_Ignore_Stun → GE_Ignore_Stun_C
---
**몽타주 1: AM_PC_Rene_B_Skill_ManaStoneCarnival**
- 시퀀스 길이: 1.50초
- 재생 속도: 1x
## 기본 공격
### 기본 공격: staff
#### 콤보 1: AM_PC_Rene_B_Attack_W01_01
- 시퀀스 길이: 1.90초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.85초 (지속: 0.85초)
- **AttackWithEquip**: 0.57~0.69초 (지속: 0.11초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 2: AM_PC_Rene_B_Attack_W01_02
- 시퀀스 길이: 1.80초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.90초 (지속: 0.90초)
- **AttackWithEquip**: 0.63~0.75초 (지속: 0.12초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 3: AM_PC_Rene_B_Attack_W01_03
- 시퀀스 길이: 2.20초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.01~0.91초 (지속: 0.90초)
- **AttackWithEquip**: 0.62~0.72초 (지속: 0.10초)
- 공격 태그: `Event.Attack.Normal`

View File

@ -0,0 +1,216 @@
# 리오 (Rio) - 전투 데이터
## 기본 정보
- **직업**: 암살자
- **궁극기 포인트**: 2368
- **장착 가능 장비**: ShortSword, Cloth, Light
## 기본 스탯
| 스탯 | 값 |
|------|-----|
| 힘 (Str) | 15.0 |
| 민첩 (Dex) | 25.0 |
| 지능 (Int) | 10.0 |
| 체력 (Con) | 15.0 |
| 지혜 (Wis) | 10.0 |
| HP | 100.0 |
| MP | 50.0 |
| 마나 재생 | 0.2 |
| 지구력 (Stamina) | 100.0 |
| 크리티컬 확률 (%) | 5.0 |
## 스킬
### 기본 스킬
#### SK140201 - 연속 찌르기
**설명**: 단검을 빠르게 2번 찔러 각각 100%만큼 암흑 속성 물리 피해를 입힙니다. 각 공격은 25%의 추가 치명타 확률을 가집니다. 타격당 연계 점수 1점을 획득합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.9x |
| 마나 비용 | 9 |
| 재사용 대기시간 | 3.5초 |
**어빌리티 클래스**: `GA_Skill_Rio_RapidStab → GA_Skill_Rio_RapidStab_C`
**Gameplay Effects**
- `Instant`: GE_Skill_Rio_RapidStab_Buff → GE_Skill_Rio_RapidStab_Buff_C
---
**몽타주 1: AM_PC_Rio_B_Skill_RapidStab**
- 시퀀스 길이: 1.83초
- 재생 속도: 1.2999999523162842x
**주요 타이밍**
- **AttackWithEquip**: 0.38~0.48초 (지속: 0.10초)
- 공격 태그: `Event.Attack.Skill`
- **AttackWithEquip**: 0.88~0.96초 (지속: 0.08초)
- 공격 태그: `Event.Attack.Skill`
- **ANS_SkillCancel**: 1.51~1.83초 (지속: 0.32초)
#### SK140205 - 접근
**설명**: 낮은 자세로 돌진합니다. 돌진 중 피격되지 않으며, 돌진 후 3초 동안 30%만큼 물리 피해가 증가합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.9x |
| 마나 비용 | 8 |
| 재사용 대기시간 | 4초 |
**어빌리티 클래스**: `GA_Skill_Rio_Approach → GA_Skill_Rio_Approach_C`
---
**몽타주 1: AM_PC_Rio_B_Skill_Approach**
- 시퀀스 길이: 0.57초
- 재생 속도: 1x
#### SK140202 - 단검 투척
**설명**: 단검을 던져 100%만큼 피해를 입힙니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.7x |
| 시전 시간 | 1초 |
| 마나 비용 | 10 |
| 재사용 대기시간 | 7초 |
**어빌리티 클래스**: `GA_Skill_Rio_ThrowingDagger → GA_Skill_Rio_ThrowingDagger_C`
---
**몽타주 1: AM_PC_Rio_B_Skill_ThrowingDagger**
- 시퀀스 길이: 1.63초
- 재생 속도: 1x
### 서브 스킬
#### SK140101 - 내려 찍기
**설명**: 단검으로 내려 찍어 70%만큼 물리 피해를 입힙니다. 연계 점수에 따라 50/100/150% 추가 피해를 입힙니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 70% |
| 이동 속도 배율 | 0.9x |
**어빌리티 클래스**: `GA_Skill_Rio_DroppingAttack → GA_Skill_Rio_DroppingAttack_C`
---
**몽타주 1: AM_PC_Rio_B_Skill_DroppingAttack**
- 시퀀스 길이: 1.30초
- 재생 속도: 1x
**주요 타이밍**
- **AttackWithEquip**: 0.47~0.56초 (지속: 0.09초)
- 공격 태그: `Event.Attack.Sub`
### 궁극기
#### SK140301 - 마석 ‘민감’
**설명**: 마석의 힘을 개방하여 연계 점수 3점을 획득하고, 15초 동안 은신 및 투시 효과를 획득합니다. 대상의 뒤를 공격 시 '약점' 판정이 적용됩니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 30% |
| 이동 속도 배율 | 0.9x |
| 시전 시간 | 2초 |
| 궁극기 | O |
| 지속 시간 | 15초 |
**어빌리티 클래스**: `GA_Skill_Casting_Ultimate → GA_Skill_Casting_Ultimate_C`
**활성 어빌리티**: `GA_Skill_Rio_Sensitive → GA_Skill_Rio_Sensitive_C`
**Gameplay Effects**
- `InCasting`: GE_Ignore_Shock → GE_Ignore_Shock_C
- `InCasting`: GE_Ignore_Stun → GE_Ignore_Stun_C
- `InActive`: GE_Skill_Rio_Sensitive_Active → GE_Skill_Rio_Sensitive_Active_C
---
**몽타주 1: AM_PC_Rio_B_Skill_Sensitive**
- 시퀀스 길이: 1.50초
- 재생 속도: 1x
## 기본 공격
### 기본 공격: shortSword
#### 콤보 1: AM_PC_Rio_B_Attack_W01_01
- 시퀀스 길이: 1.17초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.62초 (지속: 0.62초)
- 일반 공격력 증가: -30.0%
- **AttackWithEquip**: 0.36~0.44초 (지속: 0.08초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 2: AM_PC_Rio_B_Attack_W01_02
- 시퀀스 길이: 1.33초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~1.00초 (지속: 1.00초)
- 일반 공격력 증가: 10.0%
- **AttackWithEquip**: 0.52~0.62초 (지속: 0.10초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 3: AM_PC_Rio_B_Attack_W01_03
- 시퀀스 길이: 1.37초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.97초 (지속: 0.97초)
- 일반 공격력 증가: 15.0%
- **AttackWithEquip**: 0.57~0.62초 (지속: 0.06초)
- 공격 태그: `Event.Attack.Normal`

View File

@ -0,0 +1,212 @@
# 시노부 (Sinobu) - 전투 데이터
## 기본 정보
- **직업**: 닌자
- **궁극기 포인트**: 2035
- **장착 가능 장비**: ShortSword, Cloth, Light
## 기본 스탯
| 스탯 | 값 |
|------|-----|
| 힘 (Str) | 10.0 |
| 민첩 (Dex) | 25.0 |
| 지능 (Int) | 10.0 |
| 체력 (Con) | 15.0 |
| 지혜 (Wis) | 15.0 |
| HP | 100.0 |
| MP | 50.0 |
| 마나 재생 | 0.2 |
| 지구력 (Stamina) | 100.0 |
| 크리티컬 확률 (%) | 5.0 |
## 스킬
### 기본 스킬
#### SK180202 - 기폭찰
**설명**: 뒤로 점프하며 기폭찰 쿠나이를 설치합니다. 기폭찰 쿠나이는 적이 근처에 오면 폭발하여 130%만큼 물리 피해를 입힙니다. 사용 시 표창 1개를 충전합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 130% |
| 시전 시간 | 1초 |
| 마나 비용 | 10 |
| 재사용 대기시간 | 6초 |
**어빌리티 클래스**: `GA_Skill_Sinobu_BombTalisman → GA_Skill_Sinobu_BombTalisman_C`
---
**몽타주 1: AM_PC_Sinobu_B_Skill_BombTalisman**
- 시퀀스 길이: 3.00초
- 재생 속도: 1.399999976158142x
#### SK180203 - 비뢰각
**설명**: 대각선으로 날아차기를 하여 110%만큼 번개 속성 물리 피해를 입힙니다. 점프 상태에서만 사용 가능하며, 적중된 대상은 잠시 경직됩니다. 적중 시 표창 1개를 충전합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | Lightning |
| 피해 배율 | 110% |
| 이동 속도 배율 | 0.9x |
| 마나 비용 | 11 |
| 재사용 대기시간 | 8초 |
**어빌리티 클래스**: `GA_Skill_Sinobu_ThunderKick → GA_Skill_Sinobu_ThunderKick_C`
**Gameplay Effects**
- `OnHitTarget`: GE_Enemy_ShockMotion → GE_Enemy_ShockMotion_C
- `OnHitTarget`: GE_ShockMotion_Weak → GE_ShockMotion_Weak_C
- `OnHitTarget`: GE_Breakdown_Add → GE_Breakdown_Add_C
---
**몽타주 1: AM_PC_Sinobu_B_Skill_ThunderKick**
- 시퀀스 길이: 0.60초
- 재생 속도: 1x
**주요 타이밍**
- **AttackWithEquip**: 0.42~0.59초 (지속: 0.17초)
- 공격 태그: `Event.Attack.Skill`
#### SK180205 - 인술 ‘바꿔치기’
**설명**: 7초 동안 유지되는 '바꿔치기'를 사용합니다. '바꿔치기' 상태 중 피격 시 피해가 50% 감소하며, 3초 동안 투명화와 이동속도 증가 효과를 얻습니다. 효과 발동 시 표창 1개를 충전합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 90% |
| 이동 속도 배율 | 0.9x |
| 시전 시간 | 1.5초 |
| 마나 비용 | 12 |
| 재사용 대기시간 | 11초 |
| 지속 시간 | 10초 |
**어빌리티 클래스**: `GA_Skill_Casting_CanMove_CanRelease → GA_Skill_Casting_CanMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_Sinobu_NinpoChange → GA_Skill_Sinobu_NinpoChange_C`
**Gameplay Effects**
- `InActive`: GE_Skill_Sinobu_NinpoChange → GE_Skill_Sinobu_NinpoChange_C
---
**몽타주 1: AM_PC_Sinobu_B_Skill_NinpoChange**
- 시퀀스 길이: 3.57초
- 재생 속도: 1x
**몽타주 2: AM_PC_Sinobu_B_Skill_NinpoChange_Active**
- 시퀀스 길이: 1.00초
- 재생 속도: 1x
### 서브 스킬
#### SK180101 - 표창
**설명**: 표창을 던져 120%만큼 물리 피해를 입힙니다. 일반 공격이 적중하거나 스킬을 사용하면 충전됩니다. 최대 3개까지 충전 가능합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 120% |
| 이동 속도 배율 | 0.9x |
| 시전 시간 | 1초 |
| 지속 시간 | 60초 |
**어빌리티 클래스**: `GA_Skill_Sinobu_Shuriken → GA_Skill_Sinobu_Shuriken_C`
---
**몽타주 1: AM_PC_Sinobu_B_Skill_Shuriken**
- 시퀀스 길이: 1.23초
- 재생 속도: 1.399999976158142x
### 궁극기
#### SK180301 - 마석 '반환'
**설명**: 마석의 힘을 개방하여 7초 동안 전방의 투사체 공격을 튕겨내고 근접 공격을 막아냅니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 0% |
| 이동 속도 배율 | 0.9x |
| 궁극기 | O |
| 지속 시간 | 7초 |
**어빌리티 클래스**: `GA_Skill_Ultimate_Base → GA_Skill_Ultimate_Base_C`
**활성 어빌리티**: `GA_Skill_Sinobu_Deflect → GA_Skill_Sinobu_Deflect_C`
**Gameplay Effects**
- `InCasting`: GE_Ignore_Shock → GE_Ignore_Shock_C
- `InCasting`: GE_Ignore_Stun → GE_Ignore_Stun_C
- `InActive`: GE_Skill_Sinobu_Silence → GE_Skill_Sinobu_Silence_C
---
**몽타주 1: AM_PC_Sinobu_Base_Skill_Deflect**
- 시퀀스 길이: 2.33초
- 재생 속도: 1x
## 기본 공격
### 기본 공격: shortSword
#### 콤보 1: AM_PC_Sinobu_B_Attack_W01_03
- 시퀀스 길이: 1.07초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.80초 (지속: 0.80초)
- 일반 공격력 증가: -5.0%
- **AttackWithEquip**: 0.27~0.40초 (지속: 0.13초)
- 공격 태그: `Event.Attack.Normal`
#### 콤보 2: AM_PC_Sinobu_B_Attack_W01_01
- 시퀀스 길이: 1.20초
- 재생 속도: 1x
**주요 타이밍**
- **ANS_AttackState**: 0.00~0.80초 (지속: 0.80초)
- 일반 공격력 증가: -15.0%
- **AttackWithEquip**: 0.35~0.45초 (지속: 0.10초)
- 공격 태그: `Event.Attack.Normal`

View File

@ -0,0 +1,211 @@
# 우르드 (Urud) - 전투 데이터
## 기본 정보
- **직업**: 원거리
- **궁극기 포인트**: 2623
- **장착 가능 장비**: Bow, Light, Cloth
## 기본 스탯
| 스탯 | 값 |
|------|-----|
| 힘 (Str) | 15.0 |
| 민첩 (Dex) | 20.0 |
| 지능 (Int) | 10.0 |
| 체력 (Con) | 15.0 |
| 지혜 (Wis) | 15.0 |
| HP | 100.0 |
| MP | 50.0 |
| 마나 재생 | 0.2 |
| 지구력 (Stamina) | 100.0 |
| 크리티컬 확률 (%) | 5.0 |
## 스킬
### 기본 스킬
#### SK110205 - 다발 화살
**설명**: 3발의 화살을 동시에 발사하여 각각 90%만큼 물리 피해를 입힙니다. 화살 3개 소모합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 90% |
| 이동 속도 배율 | 0.8x |
| 마나 비용 | 14 |
| 재사용 대기시간 | 7초 |
**어빌리티 클래스**: `GA_Skill_Urud_MultiShot_Quick → GA_Skill_Urud_MultiShot_Quick_C`
---
**몽타주 1: AM_PC_Urud_Base_B_Skill_MultiArrow**
- 시퀀스 길이: 1.62초
- 재생 속도: 1x
**주요 타이밍**
- **AN_Trigger_Projectile_Shot**: 0.85초
- 이벤트 태그: `Event.Effect.Shot`
#### SK110204 - 독성 화살
**설명**: 화살에 독을 발라 발사합니다. 적중된 대상은 중독됩니다. 화살을 1개 소모합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | Poison |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.8x |
| 마나 비용 | 9 |
| 재사용 대기시간 | 7초 |
**어빌리티 클래스**: `GA_Skill_Urud_PoisonArrow → GA_Skill_Urud_PoisonArrow_C`
**Gameplay Effects**
- `OnProjectileHitTarget`: GE_Attach_Poison → GE_Attach_Poison_C
---
**몽타주 1: AM_PC_Urud_Base_B_Skill_PoisonArrow**
- 시퀀스 길이: 1.62초
- 재생 속도: 1x
**주요 타이밍**
- **AN_Trigger_Projectile_Shot**: 0.85초
- 이벤트 태그: `Event.Effect.Shot`
#### SK110201 - 덫 설치
**설명**: 60초 동안 유지되는 덫을 최대 4개 설치할 수 있습니다. 덫을 밟은 대상은 기절합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 0% |
| 이동 속도 배율 | 0.8x |
| 시전 시간 | 2초 |
| 마나 비용 | 9 |
| 재사용 대기시간 | 5초 |
| 지속 시간 | 3초 |
**어빌리티 클래스**: `GA_Skill_Casting_CantMove_CanRelease → GA_Skill_Casting_CantMove_CanRelease_C`
**활성 어빌리티**: `GA_Skill_Urud_MakeTrap → GA_Skill_Urud_MakeTrap_C`
---
**몽타주 1: AM_PC_Urud_B_Skill_MakeTrap**
- 시퀀스 길이: 6.15초
- 재생 속도: 1x
#### SK110207 - 재장전
**설명**: 화살을 화살통에 장전 합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.9x |
| 시전 시간 | 5초 |
| 지속 시간 | 1초 |
**어빌리티 클래스**: `GA_Attack_Firearm_Reload → GA_Attack_Firearm_Reload_C`
---
**몽타주 1: AM_PC_Urud_B_Reload**
- 시퀀스 길이: 2.53초
- 재생 속도: 1x
### 서브 스킬
#### SK110101 - 화살 찌르기
**설명**: 화살로 찔러 75%만큼 물리 피해를 입힙니다. 적중 시 다음 일반 공격이 50% 증가합니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | PhysicalSkill |
| 원소 타입 | None |
| 피해 배율 | 70% |
| 이동 속도 배율 | 0.8x |
**어빌리티 클래스**: `GA_Skill_Urud_ArrowAttack → GA_Skill_Urud_ArrowAttack_C`
---
**몽타주 1: AM_PC_Urud_Base_B_Skill_ArrowStab**
- 시퀀스 길이: 2.00초
- 재생 속도: 1.7999999523162842x
**주요 타이밍**
- **AttackWithEquip**: 0.59~0.76초 (지속: 0.17초)
- 공격 태그: `Event.Attack.Sub`
### 궁극기
#### SK110301 - 마석 ‘폭쇄’
**설명**: 마석의 힘을 해방하여 15초 동안 화살에 범위 피해 효과를 부여합니다. 적중된 대상은 30% 확률로 화상에 걸립니다.
**스킬 속성**
| 항목 | 값 |
|------|-----|
| 공격 타입 | Normal |
| 원소 타입 | None |
| 피해 배율 | 100% |
| 이동 속도 배율 | 0.8x |
| 시전 시간 | 2초 |
| 궁극기 | O |
| 지속 시간 | 15초 |
**어빌리티 클래스**: `GA_Skill_Casting_Ultimate → GA_Skill_Casting_Ultimate_C`
**활성 어빌리티**: `GA_Skill_Urud_Explosion_Active → GA_Skill_Urud_Explosion_Active_C`
**Gameplay Effects**
- `InActive`: GE_Skill_Urud_Explosion → GE_Skill_Urud_Explosion_C
- `InCasting`: GE_Ignore_Shock → GE_Ignore_Shock_C
- `InCasting`: GE_Ignore_Stun → GE_Ignore_Stun_C
- `OnProjectileHitRangedTarget`: GE_Attack_Projectile_Splash → GE_Attack_Projectile_Splash_C
## 기본 공격
### 기본 공격: bow
#### 콤보 1: AM_PC_Urud_Base_B_Attack_N
- 시퀀스 길이: 3.28초
- 재생 속도: 1x
**주요 타이밍**
- **AN_Trigger_Projectile_Shot**: 2.52초

View File

@ -0,0 +1,247 @@
# -*- coding: utf-8 -*-
"""
DS 전투 데이터 수집 메인 스크립트
실행 방법:
python collect_combat_data.py
"""
import json
import logging
import os
from datetime import datetime
from typing import Dict, List
from data_extractors import (
TARGET_STALKERS,
extract_character_stat,
extract_attack_montages,
extract_skill_data,
extract_montage_info
)
from markdown_formatter import generate_stalker_markdown
from validators import validate_collection_result, log_collection_summary
# 로깅 설정
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
# 경로 설정
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SOURCE_DATA_DIR = os.path.join(os.path.dirname(BASE_DIR), '원본데이터')
OUTPUT_DIR = os.path.join(BASE_DIR, '수집결과')
MARKDOWN_DIR = os.path.join(OUTPUT_DIR, 'markdown')
DATATABLE_JSON = os.path.join(SOURCE_DATA_DIR, 'DataTable.json')
ANIMMONTAGE_JSON = os.path.join(SOURCE_DATA_DIR, 'AnimMontage.json')
OUTPUT_JSON = os.path.join(OUTPUT_DIR, 'all_stalkers_combat_data.json')
LOG_FILE = os.path.join(OUTPUT_DIR, 'collection_log.txt')
def load_json_file(file_path: str) -> dict:
"""JSON 파일 로드"""
logger.info(f"JSON 파일 로드 중: {file_path}")
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
logger.info(f" - 로드 완료")
return data
def collect_stalker_data(
stalker_name: str,
datatable_assets: List[Dict],
animmontage_assets: List[Dict]
) -> Dict:
"""
단일 스토커의 전투 데이터 수집
Args:
stalker_name: 스토커 이름 (소문자)
datatable_assets: DataTable.json의 Assets 배열
animmontage_assets: AnimMontage.json의 Assets 배열
Returns:
스토커 전투 데이터
"""
logger.info(f" [{stalker_name}] 데이터 수집 시작")
# 1. 기본 스탯 추출
stats = extract_character_stat(datatable_assets, stalker_name)
if not stats:
logger.error(f" [{stalker_name}] 스탯 정보 추출 실패")
return {}
# 2. 기본 정보 분리
basic_info = {
'name': stats.pop('name', ''),
'jobName': stats.pop('jobName', '')
}
# 3. 스킬 정보 분리
skills = {
'default': stats.pop('defaultSkills', []),
'sub': stats.pop('subSkill', ''),
'ultimate': stats.pop('ultimateSkill', ''),
'ultimatePoint': stats.pop('ultimatePoint', 0)
}
# 4. 스킬 상세 정보 및 몽타주 수집
skill_details = {}
all_skill_ids = skills['default'] + [skills['sub'], skills['ultimate']]
for skill_id in all_skill_ids:
if not skill_id:
continue
skill_data = extract_skill_data(datatable_assets, skill_id)
if not skill_data:
logger.warning(f" [{stalker_name}] 스킬 {skill_id} 데이터 추출 실패")
continue
# 스킬 몽타주 정보 수집
montage_paths = skill_data.get('useMontages', [])
montages = []
for montage_path in montage_paths:
montage_info = extract_montage_info(animmontage_assets, montage_path)
if montage_info:
montages.append(montage_info)
skill_data['montages'] = montages
skill_details[skill_id] = skill_data
# 5. 기본 공격 몽타주 수집
attack_montages_map = extract_attack_montages(datatable_assets, stalker_name)
basic_attacks = {}
for weapon_type, montage_paths in attack_montages_map.items():
montages = []
for montage_path in montage_paths:
montage_info = extract_montage_info(animmontage_assets, montage_path)
if montage_info:
montages.append(montage_info)
if montages:
basic_attacks[weapon_type] = montages
# 6. 최종 데이터 구성
stalker_data = {
'basic_info': basic_info,
'stats': stats,
'skills': skills,
'skill_details': skill_details,
'basic_attacks': basic_attacks
}
logger.info(f" [{stalker_name}] 데이터 수집 완료")
logger.info(f" - 스킬 {len(skill_details)}")
logger.info(f" - 무기 타입 {len(basic_attacks)}")
return stalker_data
def main():
"""메인 실행 함수"""
logger.info("=" * 80)
logger.info("DS 전투 데이터 수집 시작")
logger.info("=" * 80)
# 1. 원본 데이터 로드
logger.info("1. 원본 데이터 로드 중...")
datatable_data = load_json_file(DATATABLE_JSON)
animmontage_data = load_json_file(ANIMMONTAGE_JSON)
datatable_assets = datatable_data.get('Assets', [])
animmontage_assets = animmontage_data.get('Assets', [])
logger.info(f" - DataTable 에셋 수: {len(datatable_assets)}")
logger.info(f" - AnimMontage 에셋 수: {len(animmontage_assets)}")
logger.info("")
# 2. 스토커별 데이터 수집
logger.info(f"2. 스토커 데이터 수집 중 (총 {len(TARGET_STALKERS)}명)...")
stalkers_data = {}
for stalker_name in TARGET_STALKERS:
stalker_data = collect_stalker_data(
stalker_name,
datatable_assets,
animmontage_assets
)
if stalker_data:
stalkers_data[stalker_name] = stalker_data
logger.info("")
# 3. 통합 JSON 생성
logger.info("3. 통합 JSON 생성 중...")
collection_data = {
'collection_metadata': {
'collected_at': datetime.now().isoformat(),
'source_files': [
'DataTable.json',
'AnimMontage.json'
],
'total_stalkers': len(stalkers_data)
},
'stalkers': stalkers_data
}
with open(OUTPUT_JSON, 'w', encoding='utf-8') as f:
json.dump(collection_data, f, ensure_ascii=False, indent=2)
logger.info(f" - 통합 JSON 생성 완료: {OUTPUT_JSON}")
logger.info("")
# 4. 스토커별 마크다운 생성
logger.info(f"4. 스토커별 마크다운 생성 중 ({len(stalkers_data)}명)...")
for stalker_name, stalker_data in stalkers_data.items():
markdown_content = generate_stalker_markdown(stalker_name, stalker_data)
markdown_path = os.path.join(MARKDOWN_DIR, f'{stalker_name}.md')
with open(markdown_path, 'w', encoding='utf-8') as f:
f.write(markdown_content)
logger.info(f" - {stalker_name}.md 생성 완료")
logger.info("")
# 5. 데이터 검증
logger.info("5. 데이터 검증 중...")
is_valid = validate_collection_result(collection_data)
if is_valid:
logger.info(" ✓ 모든 데이터 검증 통과")
else:
logger.warning(" ⚠ 일부 데이터 검증 실패 (상세 내용은 로그 참조)")
logger.info("")
# 6. 수집 요약 로그 생성
logger.info("6. 수집 요약 로그 생성 중...")
log_collection_summary(collection_data, LOG_FILE)
logger.info("")
# 7. 완료
logger.info("=" * 80)
logger.info("DS 전투 데이터 수집 완료!")
logger.info("=" * 80)
logger.info(f"결과 파일:")
logger.info(f" - 통합 JSON: {OUTPUT_JSON}")
logger.info(f" - 마크다운: {MARKDOWN_DIR}")
logger.info(f" - 수집 로그: {LOG_FILE}")
logger.info("")
if __name__ == '__main__':
try:
main()
except Exception as e:
logger.exception("수집 중 예외 발생:")
raise

View File

@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
"""
CustomProperties 문자열 파싱 유틸리티
AnimMontage의 AnimNotifies에 있는 CustomProperties는 모두 문자열로 저장되어 있어서
타입 변환 및 파싱이 필요합니다.
"""
import re
from typing import Any, Optional
def parse_float(value: str) -> Optional[float]:
"""문자열을 float로 변환 (실패 시 None)"""
try:
return float(value.strip())
except (ValueError, AttributeError):
return None
def parse_tag_name(value: str) -> Optional[str]:
"""
(TagName="Event.Attack.Normal") 형태에서 태그 이름 추출
Example:
'(TagName="Event.Attack.Normal")' -> 'Event.Attack.Normal'
"""
if not value:
return None
match = re.search(r'TagName="([^"]+)"', value)
if match:
return match.group(1)
return None
def parse_vector(value: str) -> Optional[dict]:
"""
(X=0.0,Y=1.0,Z=0.0) 형태를 dict로 변환
Example:
'(X=0.0,Y=1.0,Z=0.0)' -> {'X': 0.0, 'Y': 1.0, 'Z': 0.0}
"""
if not value:
return None
try:
x_match = re.search(r'X=([-\d.]+)', value)
y_match = re.search(r'Y=([-\d.]+)', value)
z_match = re.search(r'Z=([-\d.]+)', value)
if x_match and y_match and z_match:
return {
'X': float(x_match.group(1)),
'Y': float(y_match.group(1)),
'Z': float(z_match.group(1))
}
except (ValueError, AttributeError):
pass
return None
def parse_array(value: str) -> Optional[list]:
"""
(0.1,0.2,0.3) 형태를 리스트로 변환
Example:
'(0.1,0.2,0.3)' -> [0.1, 0.2, 0.3]
"""
if not value:
return None
try:
# 괄호 제거
cleaned = value.strip().strip('()')
if not cleaned:
return []
# 쉼표로 분리
parts = cleaned.split(',')
return [float(p.strip()) for p in parts if p.strip()]
except (ValueError, AttributeError):
return None
def parse_custom_property(value: str, field_name: str) -> Any:
"""
CustomProperties 필드의 값을 적절한 타입으로 변환
Args:
value: 원본 문자열 값
field_name: 필드 이름 (타입 추론에 사용)
Returns:
파싱된 값 (float, str, dict, list 등)
"""
if not isinstance(value, str):
return value
value = value.strip()
# 빈 문자열
if not value or value == '':
return None
# TagName 파싱
if 'TagName=' in value:
tag = parse_tag_name(value)
if tag and tag != 'None':
return tag
return None
# Vector 파싱
if value.startswith('(') and ('X=' in value or 'Y=' in value or 'Z=' in value):
vec = parse_vector(value)
if vec:
return vec
# Array 파싱 (TimeArray, LocationArray 등)
if 'Array' in field_name and value.startswith('('):
arr = parse_array(value)
if arr is not None:
return arr
# Boolean 파싱
if value.lower() in ['true', 'false']:
return value.lower() == 'true'
# Float 파싱 시도
float_val = parse_float(value)
if float_val is not None:
return float_val
# 기본: 문자열 그대로 반환
return value
def extract_notify_properties(notify: dict, target_fields: list) -> dict:
"""
AnimNotify에서 CustomProperties의 특정 필드만 추출
Args:
notify: AnimNotify 딕셔너리
target_fields: 추출할 필드 이름 리스트
Returns:
추출된 프로퍼티 딕셔너리
"""
custom_props = notify.get('CustomProperties', {})
result = {}
for field in target_fields:
if field in custom_props:
value = custom_props[field]
parsed = parse_custom_property(value, field)
if parsed is not None:
result[field] = parsed
return result

View File

@ -0,0 +1,377 @@
# -*- coding: utf-8 -*-
"""
DataTable 및 AnimMontage 데이터 추출 모듈
"""
import logging
from typing import Dict, List, Optional, Any
from custom_property_parser import extract_notify_properties, parse_custom_property
logger = logging.getLogger(__name__)
# 수집 대상 스토커 목록 (소문자)
TARGET_STALKERS = [
'hilda', 'urud', 'nave', 'baran', 'rio', 'clad',
'rene', 'sinobu', 'lian', 'cazimord', 'blackmaria'
]
def round_float(value: Any) -> Any:
"""소수점 2자리로 반올림 (float만 처리)"""
if isinstance(value, (int, float)):
return round(float(value), 2)
return value
def extract_character_stat(data_table_assets: List[Dict], stalker_name: str) -> Dict:
"""
DT_CharacterStat에서 스토커의 기본 스탯 추출
Args:
data_table_assets: DataTable.json의 Assets 배열
stalker_name: 스토커 이름 (소문자)
Returns:
스탯 딕셔너리
"""
# DT_CharacterStat 찾기
dt_char_stat = None
for asset in data_table_assets:
if asset.get('AssetName') == 'DT_CharacterStat':
dt_char_stat = asset
break
if not dt_char_stat:
logger.warning("DT_CharacterStat not found")
return {}
# 해당 스토커 행 찾기
rows = dt_char_stat.get('Rows', [])
stalker_row = None
for row in rows:
if row.get('RowName', '').lower() == stalker_name.lower():
stalker_row = row
break
if not stalker_row:
logger.warning(f"Stalker '{stalker_name}' not found in DT_CharacterStat")
return {}
data = stalker_row.get('Data', {})
# 모든 숫자 필드를 소수점 2자리로 반올림
stats = {
'name': data.get('name', ''),
'jobName': data.get('jobName', ''),
# 기본 스탯
'str': round_float(data.get('str', 0)),
'dex': round_float(data.get('dex', 0)),
'int': round_float(data.get('int', 0)),
'con': round_float(data.get('con', 0)),
'wis': round_float(data.get('wis', 0)),
# 체력/마나
'hp': round_float(data.get('hP', 0)),
'mp': round_float(data.get('mP', 0)),
'manaRegen': round_float(data.get('manaRegen', 0)),
'stamina': round_float(data.get('stamina', 0)),
# 공격
'physicalDamage': round_float(data.get('physicalDamage', 0)),
'magicalDamage': round_float(data.get('magicalDamage', 0)),
'criticalPer': round_float(data.get('criticalPer', 0)),
'criticalDamage': round_float(data.get('criticalDamage', 0)),
'backAttackDamage': round_float(data.get('backAttackDamage', 0)),
# 방어
'defense': round_float(data.get('defense', 0)),
'physicalResistancePer': round_float(data.get('physicalResistancePer', 0)),
'rangedResistancePer': round_float(data.get('rangedResistancePer', 0)),
'magicalResistancePer': round_float(data.get('magicalResistancePer', 0)),
# 속성 저항
'fireResistancePer': round_float(data.get('fireResistancePer', 0)),
'poisonResistancePer': round_float(data.get('poisonResistancePer', 0)),
'waterResistancePer': round_float(data.get('waterResistancePer', 0)),
'lightningResistancePer': round_float(data.get('lightningResistancePer', 0)),
'holyResistancePer': round_float(data.get('holyResistancePer', 0)),
'darkResistancePer': round_float(data.get('darkResistancePer', 0)),
'dotReduceRatePer': round_float(data.get('dOTReduceRatePer', 0)),
# 이동
'walkSpeed': round_float(data.get('walkSpeed', 0)),
# 스킬
'defaultSkills': data.get('defaultSkills', []),
'subSkill': data.get('subSkill', ''),
'ultimateSkill': data.get('ultimateSkill', ''),
'ultimatePoint': data.get('ultimatePoint', 0),
# 장비
'equipableTypes': data.get('equipableTypes', []),
# 기타
'hitRadius': round_float(data.get('hitRadius', 0))
}
return stats
def extract_attack_montages(data_table_assets: List[Dict], stalker_name: str) -> Dict[str, List[str]]:
"""
DT_CharacterAbility에서 기본 공격 몽타주 추출
Args:
data_table_assets: DataTable.json의 Assets 배열
stalker_name: 스토커 이름 (소문자)
Returns:
무기타입별 몽타주 경로 리스트 딕셔너리
"""
# DT_CharacterAbility 찾기
dt_char_ability = None
for asset in data_table_assets:
if asset.get('AssetName') == 'DT_CharacterAbility':
dt_char_ability = asset
break
if not dt_char_ability:
logger.warning("DT_CharacterAbility not found")
return {}
# 해당 스토커 행 찾기
rows = dt_char_ability.get('Rows', [])
stalker_row = None
for row in rows:
if row.get('RowName', '').lower() == stalker_name.lower():
stalker_row = row
break
if not stalker_row:
logger.warning(f"Stalker '{stalker_name}' not found in DT_CharacterAbility")
return {}
data = stalker_row.get('Data', {})
attack_montage_map = data.get('attackMontageMap', {})
# 무기타입별 몽타주 배열 추출
result = {}
for weapon_type, weapon_data in attack_montage_map.items():
if isinstance(weapon_data, dict):
montage_array = weapon_data.get('montageArray', [])
if montage_array:
result[weapon_type] = montage_array
return result
def should_exclude_skill_montage(montage_path: str) -> bool:
"""
스킬 몽타주 제외 규칙 적용
제외 키워드: ready, Equip, Equipment, _E
"""
if not montage_path:
return True
exclude_keywords = ['ready', 'Equip', 'Equipment', '_E']
montage_name = montage_path.split('/')[-1].split('.')[0]
for keyword in exclude_keywords:
if keyword in montage_name:
return True
return False
def extract_skill_data(data_table_assets: List[Dict], skill_id: str) -> Optional[Dict]:
"""
DT_Skill에서 스킬 데이터 추출
Args:
data_table_assets: DataTable.json의 Assets 배열
skill_id: 스킬 ID (예: SK100201)
Returns:
스킬 데이터 딕셔너리
"""
# DT_Skill 찾기
dt_skill = None
for asset in data_table_assets:
if asset.get('AssetName') == 'DT_Skill':
dt_skill = asset
break
if not dt_skill:
logger.warning("DT_Skill not found")
return None
# 해당 스킬 행 찾기
rows = dt_skill.get('Rows', [])
skill_row = None
for row in rows:
if row.get('RowName') == skill_id:
skill_row = row
break
if not skill_row:
logger.warning(f"Skill '{skill_id}' not found in DT_Skill")
return None
data = skill_row.get('Data', {})
# 스킬 몽타주 필터링 (제외 규칙 적용)
use_montages = data.get('useMontages', [])
filtered_montages = [m for m in use_montages if not should_exclude_skill_montage(m)]
# GameplayEffect 정보 추출 (trigger와 gEClass만)
gameplay_effects = []
for ge in data.get('gameplayEffectSet', []):
if isinstance(ge, dict):
gameplay_effects.append({
'trigger': ge.get('trigger', ''),
'gEClass': ge.get('gEClass', '')
})
skill_data = {
'skillId': skill_id,
'name': data.get('name', ''),
'desc': data.get('desc', ''),
'descValues': data.get('descValues', []),
'simpleDesc': data.get('simpleDesc', ''),
'skillAttackType': data.get('skillAttackType', ''),
'skillElementType': data.get('skillElementType', ''),
'skillDamageRate': data.get('skillDamageRate', 1.0),
'walkSpeedMultiplier': data.get('walkSpeedMultiplier', 0),
'castingTime': data.get('castingTime', 0),
'manaCost': data.get('manaCost', 0),
'coolTime': data.get('coolTime', 0),
'useMontages': filtered_montages,
'bIsStackable': data.get('bIsStackable', False),
'maxStackCount': data.get('maxStackCount', 0),
'bIsUltimate': data.get('bIsUltimate', False),
'abilityClass': data.get('abilityClass', ''),
'activeAbilityClass': data.get('activeAbilityClass', ''),
'activeDuration': data.get('activeDuration', 0),
'gameplayEffectSet': gameplay_effects
}
return skill_data
def extract_montage_asset_name(montage_path: str) -> str:
"""
몽타주 경로에서 에셋 이름 추출
Example:
'/Script/Engine.AnimMontage'/Game/.../AM_PC_Hilda_B_Attack_W01_01.AM_PC_Hilda_B_Attack_W01_01''
-> 'AM_PC_Hilda_B_Attack_W01_01'
"""
if not montage_path:
return ''
# 마지막 .과 마지막 ' 사이의 문자열 추출
parts = montage_path.split('.')
if len(parts) >= 2:
asset_name = parts[-1].rstrip("'")
return asset_name
return montage_path
def extract_anim_notifies(montage_data: Dict) -> List[Dict]:
"""
AnimMontage에서 주요 AnimNotifies 추출
수집 대상:
- ANS_AttackState_C
- AnimNotifyState_AttackWithEquip
- ANS_SkillCancel_C
- AN_Trigger_Projectile_Shot_C
Args:
montage_data: AnimMontage 에셋 딕셔너리
Returns:
추출된 AnimNotifies 리스트
"""
target_classes = {
'ANS_AttackState_C': ['AddNormalAttackPer', 'AddPhysicalAttackPer'],
'AnimNotifyState_AttackWithEquip': ['AttackTag'],
'ANS_SkillCancel_C': [],
'AN_Trigger_Projectile_Shot_C': ['EventTag']
}
anim_notifies = montage_data.get('AnimNotifies', [])
result = []
for notify in anim_notifies:
notify_class = notify.get('NotifyStateClass') or notify.get('NotifyClass')
if notify_class in target_classes:
extracted = {
'notifyClass': notify_class,
'triggerTime': notify.get('TriggerTime', 0),
'duration': notify.get('Duration', 0)
}
# CustomProperties에서 필요한 필드 추출
target_fields = target_classes[notify_class]
if target_fields:
props = extract_notify_properties(notify, target_fields)
extracted['properties'] = props
result.append(extracted)
return result
def find_montage_data(anim_montage_assets: List[Dict], montage_path: str) -> Optional[Dict]:
"""
몽타주 경로로 AnimMontage 에셋 찾기
Args:
anim_montage_assets: AnimMontage.json의 Assets 배열
montage_path: 몽타주 경로
Returns:
찾은 몽타주 데이터 또는 None
"""
asset_name = extract_montage_asset_name(montage_path)
if not asset_name:
return None
for montage in anim_montage_assets:
if montage.get('AssetName') == asset_name:
return montage
return None
def extract_montage_info(anim_montage_assets: List[Dict], montage_path: str) -> Optional[Dict]:
"""
몽타주 경로로 몽타주 정보 추출 (AnimNotifies 포함)
Args:
anim_montage_assets: AnimMontage.json의 Assets 배열
montage_path: 몽타주 경로
Returns:
몽타주 정보 딕셔너리
"""
montage_data = find_montage_data(anim_montage_assets, montage_path)
if not montage_data:
asset_name = extract_montage_asset_name(montage_path)
logger.warning(f"Montage not found: {asset_name}")
return None
sections = montage_data.get('Sections', [])
section_info = []
for section in sections:
section_info.append({
'sectionName': section.get('SectionName', ''),
'startTime': section.get('StartTime', 0)
})
montage_info = {
'assetName': montage_data.get('AssetName', ''),
'sequenceLength': montage_data.get('SequenceLength', 0),
'rateScale': montage_data.get('RateScale', 1.0),
'sections': section_info,
'animNotifies': extract_anim_notifies(montage_data)
}
return montage_info

View File

@ -0,0 +1,339 @@
# -*- coding: utf-8 -*-
"""
마크다운 문서 생성 모듈
"""
import re
from typing import Dict, List
def format_skill_description(desc: str, desc_values: List) -> str:
"""
스킬 설명 문자열의 {0}, {1} 등을 descValues로 치환하고 줄바꿈 태그 제거
Args:
desc: 원본 설명 문자열 (예: "검을 휘둘러 {0}%만큼 번개 속성 물리 피해를 입힙니다.")
desc_values: 치환할 값들의 배열 (예: [130, 0])
Returns:
완성된 설명 문자열
"""
if not desc:
return ''
result = desc
# {0}, {1}, {2} 등을 descValues로 치환
for i, value in enumerate(desc_values):
placeholder = '{' + str(i) + '}'
result = result.replace(placeholder, str(value))
# 줄바꿈 태그 제거 (\r\n, \n, <br>, <br/> 등)
result = result.replace('\\r\\n', ' ')
result = result.replace('\\n', ' ')
result = result.replace('\r\n', ' ')
result = result.replace('\n', ' ')
result = re.sub(r'<br\s*/?>', ' ', result)
# 연속된 공백을 하나로
result = re.sub(r'\s+', ' ', result)
return result.strip()
def format_stat_table(stats: Dict) -> str:
"""기본 스탯 테이블 생성"""
stat_rows = [
('힘 (Str)', stats.get('str', 0)),
('민첩 (Dex)', stats.get('dex', 0)),
('지능 (Int)', stats.get('int', 0)),
('체력 (Con)', stats.get('con', 0)),
('지혜 (Wis)', stats.get('wis', 0)),
('HP', stats.get('hp', 0)),
('MP', stats.get('mp', 0)),
('마나 재생', stats.get('manaRegen', 0)),
('지구력 (Stamina)', stats.get('stamina', 0)),
('크리티컬 확률 (%)', stats.get('criticalPer', 0))
]
lines = ['| 스탯 | 값 |', '|------|-----|']
for name, value in stat_rows:
lines.append(f'| {name} | {value} |')
return '\n'.join(lines)
def format_skill_section(skill_data: Dict, skill_montages: List[Dict]) -> str:
"""개별 스킬 섹션 생성"""
lines = []
# 스킬 기본 정보
skill_id = skill_data.get('skillId', '')
skill_name = skill_data.get('name', '')
lines.append(f"#### {skill_id} - {skill_name}")
lines.append('')
# 스킬 설명 (descValues 적용)
desc = skill_data.get('desc', '')
desc_values = skill_data.get('descValues', [])
formatted_desc = format_skill_description(desc, desc_values)
if formatted_desc:
lines.append(f'**설명**: {formatted_desc}')
lines.append('')
# 스킬 속성
lines.append('**스킬 속성**')
lines.append('| 항목 | 값 |')
lines.append('|------|-----|')
lines.append(f"| 공격 타입 | {skill_data.get('skillAttackType', '')} |")
lines.append(f"| 원소 타입 | {skill_data.get('skillElementType', '')} |")
damage_rate = skill_data.get('skillDamageRate', 1.0)
lines.append(f"| 피해 배율 | {int(damage_rate * 100)}% |")
if skill_data.get('walkSpeedMultiplier', 0) != 0:
walk_speed = skill_data.get('walkSpeedMultiplier', 0)
lines.append(f"| 이동 속도 배율 | {walk_speed}x |")
casting_time = skill_data.get('castingTime', 0)
if casting_time > 0:
lines.append(f"| 시전 시간 | {casting_time}초 |")
mana_cost = skill_data.get('manaCost', 0)
if mana_cost > 0:
lines.append(f"| 마나 비용 | {mana_cost} |")
cool_time = skill_data.get('coolTime', 0)
if cool_time > 0:
lines.append(f"| 재사용 대기시간 | {cool_time}초 |")
# 스택 정보
if skill_data.get('bIsStackable', False):
max_stack = skill_data.get('maxStackCount', 0)
lines.append(f"| 스택 | 가능 (최대 {max_stack}) |")
# 궁극기 여부
if skill_data.get('bIsUltimate', False):
lines.append(f"| 궁극기 | O |")
# 지속 시간
active_duration = skill_data.get('activeDuration', 0)
if active_duration > 0:
lines.append(f"| 지속 시간 | {active_duration}초 |")
lines.append('')
# 어빌리티 클래스
ability_class = skill_data.get('abilityClass', '')
if ability_class and ability_class != 'None':
class_name = ability_class.split('/')[-1].replace('.', '')
lines.append(f'**어빌리티 클래스**: `{class_name}`')
lines.append('')
active_ability = skill_data.get('activeAbilityClass', '')
if active_ability and active_ability != 'None':
class_name = active_ability.split('/')[-1].replace('.', '')
lines.append(f'**활성 어빌리티**: `{class_name}`')
lines.append('')
# GameplayEffect
ge_set = skill_data.get('gameplayEffectSet', [])
if ge_set:
lines.append('**Gameplay Effects**')
for ge in ge_set:
trigger = ge.get('trigger', '')
ge_class = ge.get('gEClass', '')
ge_name = ge_class.split('/')[-1].replace('.', '') if ge_class else ''
lines.append(f'- `{trigger}`: {ge_name}')
lines.append('')
# 몽타주 정보
if skill_montages:
lines.append('---')
lines.append('')
for i, montage in enumerate(skill_montages, 1):
asset_name = montage.get('assetName', '')
lines.append(f'**몽타주 {i}: {asset_name}**')
lines.append('')
seq_len = montage.get('sequenceLength', 0)
rate_scale = montage.get('rateScale', 1.0)
lines.append(f'- 시퀀스 길이: {seq_len:.2f}')
lines.append(f'- 재생 속도: {rate_scale}x')
lines.append('')
# AnimNotifies
anim_notifies = montage.get('animNotifies', [])
if anim_notifies:
lines.append('**주요 타이밍**')
for notify in anim_notifies:
notify_class = notify.get('notifyClass', '')
trigger_time = notify.get('triggerTime', 0)
duration = notify.get('duration', 0)
notify_name = notify_class.replace('_C', '').replace('AnimNotifyState_', '')
if duration > 0:
end_time = trigger_time + duration
lines.append(f'- **{notify_name}**: {trigger_time:.2f}~{end_time:.2f}초 (지속: {duration:.2f}초)')
else:
lines.append(f'- **{notify_name}**: {trigger_time:.2f}')
# Properties
props = notify.get('properties', {})
if props:
for key, value in props.items():
if key == 'AttackTag':
lines.append(f' - 공격 태그: `{value}`')
elif key == 'EventTag':
lines.append(f' - 이벤트 태그: `{value}`')
elif key == 'AddNormalAttackPer':
lines.append(f' - 일반 공격력 증가: {value}%')
elif key == 'AddPhysicalAttackPer':
lines.append(f' - 물리 공격력 증가: {value}%')
lines.append('')
lines.append('')
lines.append('')
return '\n'.join(lines)
def format_basic_attack_section(weapon_type: str, montages: List[Dict]) -> str:
"""기본 공격 섹션 생성"""
lines = []
lines.append(f'### 기본 공격: {weapon_type}')
lines.append('')
for i, montage in enumerate(montages, 1):
asset_name = montage.get('assetName', '')
lines.append(f'#### 콤보 {i}: {asset_name}')
lines.append('')
seq_len = montage.get('sequenceLength', 0)
rate_scale = montage.get('rateScale', 1.0)
lines.append(f'- 시퀀스 길이: {seq_len:.2f}')
lines.append(f'- 재생 속도: {rate_scale}x')
lines.append('')
# AnimNotifies
anim_notifies = montage.get('animNotifies', [])
if anim_notifies:
lines.append('**주요 타이밍**')
for notify in anim_notifies:
notify_class = notify.get('notifyClass', '')
trigger_time = notify.get('triggerTime', 0)
duration = notify.get('duration', 0)
notify_name = notify_class.replace('_C', '').replace('AnimNotifyState_', '')
if duration > 0:
end_time = trigger_time + duration
lines.append(f'- **{notify_name}**: {trigger_time:.2f}~{end_time:.2f}초 (지속: {duration:.2f}초)')
else:
lines.append(f'- **{notify_name}**: {trigger_time:.2f}')
# Properties
props = notify.get('properties', {})
if props:
for key, value in props.items():
if key == 'AttackTag':
lines.append(f' - 공격 태그: `{value}`')
elif key == 'AddNormalAttackPer':
lines.append(f' - 일반 공격력 증가: {value}%')
elif key == 'AddPhysicalAttackPer':
lines.append(f' - 물리 공격력 증가: {value}%')
lines.append('')
lines.append('')
return '\n'.join(lines)
def generate_stalker_markdown(stalker_name: str, stalker_data: Dict) -> str:
"""
스토커 개별 마크다운 문서 생성
Args:
stalker_name: 스토커 이름 (소문자)
stalker_data: 스토커 전투 데이터
Returns:
마크다운 문서 문자열
"""
lines = []
# 타이틀
display_name = stalker_data.get('basic_info', {}).get('name', stalker_name.capitalize())
job_name = stalker_data.get('basic_info', {}).get('jobName', '')
lines.append(f'# {display_name} ({stalker_name.capitalize()}) - 전투 데이터')
lines.append('')
# 기본 정보
lines.append('## 기본 정보')
lines.append('')
lines.append(f'- **직업**: {job_name}')
ultimate_point = stalker_data.get('skills', {}).get('ultimatePoint', 0)
lines.append(f'- **궁극기 포인트**: {ultimate_point}')
equip_types = stalker_data.get('stats', {}).get('equipableTypes', [])
if equip_types:
lines.append(f'- **장착 가능 장비**: {", ".join(equip_types)}')
lines.append('')
# 기본 스탯
lines.append('## 기본 스탯')
lines.append('')
stats = stalker_data.get('stats', {})
lines.append(format_stat_table(stats))
lines.append('')
# 스킬
lines.append('## 스킬')
lines.append('')
# 기본 스킬
default_skills = stalker_data.get('skills', {}).get('default', [])
if default_skills:
lines.append('### 기본 스킬')
lines.append('')
skill_details = stalker_data.get('skill_details', {})
for skill_id in default_skills:
skill_data = skill_details.get(skill_id)
if skill_data:
skill_montages = skill_data.get('montages', [])
lines.append(format_skill_section(skill_data, skill_montages))
# 서브 스킬
sub_skill = stalker_data.get('skills', {}).get('sub', '')
if sub_skill:
lines.append('### 서브 스킬')
lines.append('')
skill_details = stalker_data.get('skill_details', {})
skill_data = skill_details.get(sub_skill)
if skill_data:
skill_montages = skill_data.get('montages', [])
lines.append(format_skill_section(skill_data, skill_montages))
# 궁극기
ultimate_skill = stalker_data.get('skills', {}).get('ultimate', '')
if ultimate_skill:
lines.append('### 궁극기')
lines.append('')
skill_details = stalker_data.get('skill_details', {})
skill_data = skill_details.get(ultimate_skill)
if skill_data:
skill_montages = skill_data.get('montages', [])
lines.append(format_skill_section(skill_data, skill_montages))
# 기본 공격
basic_attacks = stalker_data.get('basic_attacks', {})
if basic_attacks:
lines.append('## 기본 공격')
lines.append('')
for weapon_type, montages in basic_attacks.items():
if montages:
lines.append(format_basic_attack_section(weapon_type, montages))
return '\n'.join(lines)

View File

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
"""결과 검증 스크립트"""
import json
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
JSON_FILE = os.path.join(BASE_DIR, '수집결과', 'all_stalkers_combat_data.json')
with open(JSON_FILE, 'r', encoding='utf-8') as f:
data = json.load(f)
hilda = data['stalkers']['hilda']
print('=== Hilda 기본 정보 ===')
print(f"이름: {hilda['basic_info']['name']}")
print(f"직업: {hilda['basic_info']['jobName']}")
print(f"\n=== 기본 스탯 (일부) ===")
stats = hilda['stats']
print(f"Str: {stats['str']}, Dex: {stats['dex']}, Int: {stats['int']}")
print(f"스탯 합계: {stats['str'] + stats['dex'] + stats['int'] + stats['con'] + stats['wis']}")
print(f"크리티컬: {stats['criticalPer']}%")
print(f"\n=== 스킬 ===")
skills = hilda['skills']
print(f"기본 스킬: {skills['default']}")
print(f"서브 스킬: {skills['sub']}")
print(f"궁극기: {skills['ultimate']}")
skill_201 = hilda['skill_details']['SK100201']
print(f"\n=== SK100201 정보 ===")
print(f"이름: {skill_201['name']}")
print(f"설명: {skill_201['desc']}")
print(f"descValues: {skill_201['descValues']}")
print(f"피해 배율: {skill_201['skillDamageRate']}")
print(f"몽타주 수: {len(skill_201['montages'])}")
if skill_201['montages']:
montage = skill_201['montages'][0]
print(f"첫 몽타주: {montage['assetName']}")
print(f" - 시퀀스 길이: {montage['sequenceLength']}")
print(f" - AnimNotifies 수: {len(montage['animNotifies'])}")
if montage['animNotifies']:
print(f"\n 첫 번째 AnimNotify:")
notify = montage['animNotifies'][0]
print(f" - Class: {notify['notifyClass']}")
print(f" - TriggerTime: {notify['triggerTime']}")
print(f" - Duration: {notify['duration']}")
if 'properties' in notify:
print(f" - Properties: {notify['properties']}")
print(f"\n=== 기본 공격 ===")
basic_attacks = hilda['basic_attacks']
for weapon_type, montages in basic_attacks.items():
print(f"{weapon_type}: {len(montages)}개 몽타주")
if montages:
first = montages[0]
print(f" - {first['assetName']}")
print(f" - AnimNotifies: {len(first['animNotifies'])}")

View File

@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
"""
데이터 검증 모듈
"""
import logging
from typing import Dict, List
logger = logging.getLogger(__name__)
def validate_stalker_data(stalker_name: str, stalker_data: Dict) -> bool:
"""
스토커 데이터 검증
Args:
stalker_name: 스토커 이름
stalker_data: 스토커 데이터
Returns:
검증 성공 여부
"""
warnings = []
errors = []
# 기본 정보 검증
basic_info = stalker_data.get('basic_info', {})
if not basic_info.get('name'):
warnings.append(f"[{stalker_name}] 이름 정보 누락")
# 스탯 검증
stats = stalker_data.get('stats', {})
if not stats:
errors.append(f"[{stalker_name}] 스탯 정보 누락")
else:
# 스탯 합계 확인 (75여야 함)
stat_sum = (
stats.get('str', 0) +
stats.get('dex', 0) +
stats.get('int', 0) +
stats.get('con', 0) +
stats.get('wis', 0)
)
if stat_sum != 75:
warnings.append(f"[{stalker_name}] 스탯 합계가 75가 아님: {stat_sum}")
# 스킬 검증
skills = stalker_data.get('skills', {})
default_skills = skills.get('default', [])
if not default_skills:
warnings.append(f"[{stalker_name}] 기본 스킬 정보 누락")
sub_skill = skills.get('sub', '')
if not sub_skill:
warnings.append(f"[{stalker_name}] 서브 스킬 정보 누락")
ultimate_skill = skills.get('ultimate', '')
if not ultimate_skill:
warnings.append(f"[{stalker_name}] 궁극기 정보 누락")
# 스킬 상세 정보 검증
skill_details = stalker_data.get('skill_details', {})
all_skill_ids = default_skills + [sub_skill, ultimate_skill]
for skill_id in all_skill_ids:
if skill_id and skill_id not in skill_details:
warnings.append(f"[{stalker_name}] 스킬 상세 정보 누락: {skill_id}")
# 기본 공격 검증
basic_attacks = stalker_data.get('basic_attacks', {})
if not basic_attacks:
warnings.append(f"[{stalker_name}] 기본 공격 몽타주 정보 누락")
# 경고 및 에러 출력
for warning in warnings:
logger.warning(warning)
for error in errors:
logger.error(error)
return len(errors) == 0
def validate_collection_result(collection_data: Dict) -> bool:
"""
전체 수집 결과 검증
Args:
collection_data: 전체 수집 데이터
Returns:
검증 성공 여부
"""
stalkers = collection_data.get('stalkers', {})
if not stalkers:
logger.error("수집된 스토커 데이터가 없습니다")
return False
total_stalkers = len(stalkers)
logger.info(f"{total_stalkers}명의 스토커 데이터 수집 완료")
# 각 스토커별 검증
all_valid = True
for stalker_name, stalker_data in stalkers.items():
if not validate_stalker_data(stalker_name, stalker_data):
all_valid = False
return all_valid
def log_collection_summary(collection_data: Dict, log_file_path: str):
"""
수집 결과 요약 로그 생성
Args:
collection_data: 전체 수집 데이터
log_file_path: 로그 파일 경로
"""
stalkers = collection_data.get('stalkers', {})
with open(log_file_path, 'w', encoding='utf-8') as f:
f.write("=" * 80 + "\n")
f.write("DS 전투 데이터 수집 결과 요약\n")
f.write("=" * 80 + "\n\n")
metadata = collection_data.get('collection_metadata', {})
f.write(f"수집 시각: {metadata.get('collected_at', '')}\n")
f.write(f"총 스토커 수: {len(stalkers)}\n\n")
for stalker_name, stalker_data in stalkers.items():
f.write("-" * 80 + "\n")
f.write(f"스토커: {stalker_name}\n")
f.write("-" * 80 + "\n")
basic_info = stalker_data.get('basic_info', {})
f.write(f" 이름: {basic_info.get('name', '')}\n")
f.write(f" 직업: {basic_info.get('jobName', '')}\n")
stats = stalker_data.get('stats', {})
stat_sum = (
stats.get('str', 0) +
stats.get('dex', 0) +
stats.get('int', 0) +
stats.get('con', 0) +
stats.get('wis', 0)
)
f.write(f" 스탯 합계: {stat_sum}\n")
skills = stalker_data.get('skills', {})
default_skills = skills.get('default', [])
f.write(f" 기본 스킬 수: {len(default_skills)}\n")
f.write(f" 서브 스킬: {skills.get('sub', '')}\n")
f.write(f" 궁극기: {skills.get('ultimate', '')}\n")
skill_details = stalker_data.get('skill_details', {})
f.write(f" 수집된 스킬 상세 정보 수: {len(skill_details)}\n")
# 각 스킬별 몽타주 수 집계
total_skill_montages = 0
for skill_id, skill_data in skill_details.items():
montages = skill_data.get('montages', [])
total_skill_montages += len(montages)
f.write(f" 스킬 몽타주 총 수: {total_skill_montages}\n")
basic_attacks = stalker_data.get('basic_attacks', {})
total_basic_montages = sum(len(montages) for montages in basic_attacks.values())
f.write(f" 기본 공격 몽타주 총 수: {total_basic_montages}\n")
f.write("\n")
f.write("=" * 80 + "\n")
f.write("수집 완료\n")
f.write("=" * 80 + "\n")
logger.info(f"수집 요약 로그 생성 완료: {log_file_path}")