From 83baf7b41792562cc3ac1d272e2ad2d51064e381 Mon Sep 17 00:00:00 2001 From: Gnill82 Date: Thu, 23 Oct 2025 22:03:00 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B4=88=EA=B8=B0=20=EC=BB=A4=EB=B0=8B:=20DS-?= =?UTF-8?q?=EC=A0=84=ED=88=AC=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=A2=85?= =?UTF-8?q?=ED=95=A9=EB=B6=84=EC=84=9D=20=EC=A0=80=EC=9E=A5=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DS-기획_메뉴얼-전투_로직.md | 2105 +++++++++++++++++++ README.md | 256 +++ 분석결과/20251023/DS-전투시스템_종합분석.md | 2017 ++++++++++++++++++ 분석도구/analyze_character_stats.py | 107 + 분석도구/extract_activation_order_groups.py | 115 + 분석도구/extract_skill_cancel_windows.py | 69 + 원본데이터/20251023/샘플_AnimMontage.json | 74 + 원본데이터/20251023/샘플_Blueprint.json | 77 + 원본데이터/20251023/샘플_DataTable.json | 67 + 9 files changed, 4887 insertions(+) create mode 100644 DS-기획_메뉴얼-전투_로직.md create mode 100644 README.md create mode 100644 분석결과/20251023/DS-전투시스템_종합분석.md create mode 100644 분석도구/analyze_character_stats.py create mode 100644 분석도구/extract_activation_order_groups.py create mode 100644 분석도구/extract_skill_cancel_windows.py create mode 100644 원본데이터/20251023/샘플_AnimMontage.json create mode 100644 원본데이터/20251023/샘플_Blueprint.json create mode 100644 원본데이터/20251023/샘플_DataTable.json diff --git a/DS-기획_메뉴얼-전투_로직.md b/DS-기획_메뉴얼-전투_로직.md new file mode 100644 index 0000000..2648de9 --- /dev/null +++ b/DS-기획_메뉴얼-전투_로직.md @@ -0,0 +1,2105 @@ +# 기획 메뉴얼 - 전투 로직 + +## 1. 데미지 계산 + +### 1.1 BaseDamage 계산 과정 + +> **코드 위치**: +> - `CharacterStatDataRow.h:104-107` - 기본 값 정의 +> - `WSCharacterPlayer.cpp:3214-3245` - 1차 스탯 반영 +> - `WSCharacterPlayer.cpp:3805-3806` - 장비 효과 +> - `WSCharacterPlayer.cpp:4022` - 패시브 스탯 적용 +> - `WSDamageCalculation.cpp:316-322, 543-613` - 최종 BaseDamage 결정 + +BaseDamage는 여러 단계를 거쳐 계산됩니다: + +#### 1단계: 캐릭터 기본 값 (CharacterStatData) + +**코드 위치**: `CharacterStatDataRow.h:104-107` + +```cpp +UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) +float PhysicalDamage = 0; // 캐릭터 기본 공격력 + +UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) +float MagicalDamage = 0; // 캐릭터 기본 마법 공격력 +``` + +- 캐릭터별 기본 PhysicalDamage, MagicalDamage 값 +- DataTable에서 캐릭터별로 정의 (예: 힐다, 바란 등 각각 다른 기본값) + +#### 2단계: 1차 스탯 반영 + +**코드 위치**: `WSCharacterPlayer.cpp:3214-3245 (UpdatePrimaryStats)` + +- **Str (힘)** → 평타 피해율 (NormalDamagePer), 물리 스킬 피해율에 영향 + ```cpp + CharacterSet->SetNormalDamagePer(NormalDamagePerCurve->Eval(PrimarySet->GetStr())); + CharacterSet->SetPhysicalSkillPer(PhysicalSkillPerCurve->Eval(PrimarySet->GetStr())); + ``` + +- **Int (지능)** → 마법 스킬 피해율에 영향 + ```cpp + CharacterSet->SetMagicalSkillPer(MagicalSkillPerCurve->Eval(PrimarySet->GetInt())); + ``` + +- **Dex (민첩)** → 공격 속도, 이동 속도에 영향 + ```cpp + CharacterSet->SetAttackSpeedPer(AttackSpeedPerCurve->Eval(PrimarySet->GetDex())); + ``` + +- **커브 테이블**: `WSData->PrimaryStat` 커브 테이블에서 1차 스탯 값 → 백분율 변환 + +#### 3단계: 장비 효과 적용 + +**코드 위치**: `WSCharacterPlayer.cpp:3805-3806, 4018-4022` + +```cpp +// 장비 랜덤 옵션으로 PhysicalDamage, MagicalDamage 증가 +AttrValueMap.Add(UCharacterSet::GetPhysicalDamageAttribute(), + FItemHelper::CalculateOption(EquipItem, EItemOption::PhysicalDamageInc)); +AttrValueMap.Add(UCharacterSet::GetMagicalDamageAttribute(), + FItemHelper::CalculateOption(EquipItem, EItemOption::MagicalDamageInc)); + +// 패시브 스탯 (PhysicalDamagePer, MagicalDamagePer) 백분율 적용 +float PhysicalDamagePer = AbilitySystemComponent->GetNumericAttribute( + UPassiveSet::GetPhysicalDamagePerAttribute()) * 0.01f; +AttrValueMap[UCharacterSet::GetPhysicalDamageAttribute()] += + AttrValueMap[UCharacterSet::GetPhysicalDamageAttribute()] * PhysicalDamagePer; +``` + +- 무기, 방어구, 악세서리 등 장비의 랜덤 옵션이 PhysicalDamage/MagicalDamage에 추가 +- PassiveSet의 PhysicalDamagePer, MagicalDamagePer 백분율이 곱셈으로 적용 + +> **룬 시스템 영향** (섹션 5.4.1 참조): +> - **10201 분노**: PhysicalDamagePer +6~10% +> - **10301 폭풍**: MagicalDamagePer +6~10% +> - 이 단계에서 룬이 제공하는 패시브 스탯이 곱셈으로 적용됨 + +#### 4단계: Level 배율 적용 + +**코드 위치**: `WSDamageCalculation.cpp:316-322` + +```cpp +float PhysicalDamage = 0.0f; +ExecutionParams.AttemptCalculateCapturedAttributeMagnitude( + DamageStatics().PhysicalDamageDef, EvaluateParameters, PhysicalDamage); +PhysicalDamage = PhysicalDamage * Level; // Level 곱셈 + +float MagicalDamage = 0.0f; +ExecutionParams.AttemptCalculateCapturedAttributeMagnitude( + DamageStatics().MagicalDamageDef, EvaluateParameters, MagicalDamage); +MagicalDamage = MagicalDamage * Level; // Level 곱셈 +``` + +- 최종 PhysicalDamage, MagicalDamage에 스킬 레벨 곱셈 +- Level이 0이면 1로 처리 (라인 310-314) + +#### 5단계: 공격 타입별 BaseDamage 결정 + +**코드 위치**: `WSDamageCalculation.cpp:543-613` + +```cpp +if (EAttackType == EWSAttackType::Normal) +{ + BaseDamage = PhysicalDamage; + SkillPer = NormalDamagePer; +} +else if (EAttackType == EWSAttackType::PhysicalSkill) +{ + BaseDamage = PhysicalDamage; + SkillPer = PhysicalSkillPer + SkillDamagePer; +} +else if (EAttackType == EWSAttackType::MagicalSkill) +{ + BaseDamage = MagicalDamage; + SkillPer = MagicalSkillPer + SkillDamagePer; +} +``` + +- **Normal (일반 공격)**: BaseDamage = PhysicalDamage +- **PhysicalSkill**: BaseDamage = PhysicalDamage +- **MagicalSkill**: BaseDamage = MagicalDamage +- **FixedSkill**: BaseDamage = 스킬 데이터에 정의된 고정값 (저항 무시) + +#### 6단계: 던전 룰 배율 적용 + +**코드 위치**: `WSDamageCalculation.cpp:620-685` + +```cpp +switch (DungeonRule) +{ + case EDungeonRule::EnemyAtkUp: // BaseDamage *= 1.4 (몬스터가 공격자일 때) + case EDungeonRule::EnemyAtkUpPlus: // BaseDamage *= 1.6 + case EDungeonRule::EnemyDefDown: // BaseDamage *= 0.8 + case EDungeonRule::EnemyDefDownPlus:// BaseDamage *= 0.7 + case EDungeonRule::HeadWeak: // BaseDamage *= 1.5 (헤드샷일 때) +} +``` + +#### 최종 공식 요약 + +``` +최종 BaseDamage = + [캐릭터 기본값] + + [장비 옵션 증가분] + × [1 + 패시브 스탯 백분율] + × [스킬 레벨] + × [던전 룰 배율] +``` + +**예시 (물리 스킬)**: +``` +캐릭터 기본 PhysicalDamage: 50 +장비 옵션 증가: +30 +패시브 PhysicalDamagePer: 20% +스킬 레벨: 3 +던전 룰: 없음 + +최종 BaseDamage = ((50 + 30) × 1.20) × 3 = 96 × 3 = 288 +``` + +### 1.2 일반 공격/스킬 데미지 계산 흐름 + +> **코드 위치**: `WSDamageCalculation.cpp:251-1238 (Execute_Implementation)` + +#### 계산 흐름 개요 + +``` +[1] BaseDamage 계산 (위 섹션 참조) + ↓ +[2] HitBox 판정 (머리/후면) + ↓ +[3] 치명타 판정 + ↓ +[4] 둔기 배율 적용 + ↓ +[5] 저항 타입 결정 (공격/원소 타입별) + ↓ +[6] 방어 상태 피해 감소 + ↓ +[7] 최종 Damage 계산 + ↓ +[8] 후면 공격 추가 배율 + ↓ +[9] 최소 데미지 보장 + ↓ +[10] 파티원 피해 제거 + ↓ +[11] 실드 → 아머 → HP 순서로 적용 +``` + +#### 단계별 상세 설명 + +**[1] BaseDamage 계산** (라인 316-685) +- 위 "1.1 BaseDamage 계산 과정" 섹션 참조 +- 캐릭터 기본값 + 장비 + 패시브 + Level + 던전 룰 + +**[2] HitBox 판정** (라인 470-510) +```cpp +// 머리/몸 판정 +if (HitResult->BoneName == FName(TEXT("b_Head"))) +{ + IsHeadShot = true; + HitBoxRate = 1.2f + HeadAttackDamagePer * 0.01f; + HitBoxRate -= 0.5f * HeadShotDamReducePer * 0.01f; // 피격자의 머리 저항 +} +else +{ + HitBoxRate = 1.0f; // 몸 +} + +// 정면/후면 판정 +IsFrontAttack = UWSAbilityBlueprintLibrary::IsFrontAttack(EffectCauser, Target, bUseOwnerRotation); +``` +- **머리 기본 배율**: 1.2배 +- **HeadAttackDamagePer**: 공격자의 머리 공격 피해 증가 (가산) +- **HeadShotDamReducePer**: 피격자의 머리 피해 감소 (절반만 적용) +- **후면 공격**: 이 단계에서는 HitBoxRate에 영향 없음 (후면 배율은 8단계에서 별도 적용) + +> **룬 시스템 영향** (섹션 5.4.2 참조): +> - **10103 공략**: HeadAttackDamagePer +10~20% → 머리 공격 배율 1.2 → 1.3~1.4로 증가 + +**[3] 치명타 판정** (라인 512-527) +```cpp +if (bUseCritical && FMath::FRandRange(0.0f, 100.0f) < CriticalPer) +{ + isCritical = true; + CriticalDamageRate = (CriticalDamagePer * 0.01f) + (FMath::Rand() * CriticalDamageRange); +} +``` +- **CriticalPer**: 치명타 확률 (%) +- **CriticalDamagePer**: 치명타 피해 배율 (기본 150% 등) +- **CriticalDamageRange**: 치명타 피해 랜덤 범위 +- **예시**: CriticalPer=30%, CriticalDamagePer=150%, Range=0.1 + - 30% 확률로 치명타 발생 + - 발생 시 1.5~1.6배 피해 (1.5 + random(0~0.1)) + +**[4] 둔기 배율** (라인 530-534) +```cpp +float BluntRate = 1.0f; +if (EAttackType == EWSAttackType::Normal && SourceTags->HasTag(FGameplayTag::RequestGameplayTag("Equip.BluntWeapon"))) +{ + BluntRate = 1.2f; +} +``` +- 둔기 무기 + 일반 공격일 때만 1.2배 +- 스킬 공격에는 적용 안 됨 + +**[5] 저항 타입 결정** (라인 543-613) + +공격 타입별로 적용되는 저항이 다릅니다: + +| 공격 타입 | Resistance 1 | Resistance 2 | +|----------|--------------|--------------| +| Normal | PhysicalResistance | RangedResistance (원거리일 경우) | +| PhysicalSkill | PhysicalResistance | ElementResistance (속성별) | +| MagicalSkill | MagicalResistance | ElementResistance (속성별) | +| FixedSkill | 없음 | 없음 (저항 무시) | + +- **ElementResistance**: Fire/Poison/Water/Lightning/Holy/Dark 중 하나 +- **특수 케이스**: UsePhysicalDamageUseMagicResist=true면 물리 피해도 마법 저항 적용 +- **저항 상한**: 각 저항은 최대 75%까지만 적용 (라인 709-710) + +> **룬 시스템 영향** (섹션 5.4.3 참조): +> - **10202 방패**: PhysicalResistancePer +2~7% +> - **10302 수호**: MagicalResistancePer +2~7% +> - 룬으로 증가된 저항은 받는 피해 감소에 직접 영향 + +**[6] 방어 상태 피해 감소** (라인 769-795) +```cpp +if (IsFrontAttack && TargetTags->HasTag(FGameplayTag::RequestGameplayTag("Character.State.Blocking"))) +{ + if (EAttackType == EWSAttackType::Normal || EAttackType == EWSAttackType::PhysicalSkill) + { + Damage *= (1 - BlockedPhysicalDamageReducePer * 0.01f); + } + else if (EAttackType == EWSAttackType::MagicalSkill) + { + Damage *= (1 - BlockedMagicalDamageReducePer * 0.01f); + } +} +``` +- **정면 + 방어 자세**일 때만 적용 +- BlockedPhysicalDamageReducePer: 기본 100% (물리 공격 완전 방어) +- BlockedMagicalDamageReducePer: 기본 90% (마법 공격 90% 방어) +- **후면 공격은 방어 불가** + +**[7] 최종 Damage 계산** (라인 706-718) +```cpp +// 저항 최대 75% 제한 +ResistancePer1 = FMath::Min(ResistancePer1, 75.0f); +ResistancePer2 = FMath::Min(ResistancePer2, 75.0f); + +// 몬스터 또는 Armor가 0일 경우 +if (TargetTags->HasTag(TagEnemy) || Armor <= 0.0f) +{ + Damage = Floor(BaseDamage * HitBoxRate * (SkillPer * 0.01) * + ((1 - ResistancePer1 * 0.01) * (1 - ResistancePer2 * 0.01) * (1 - DamageReductionPer * 0.01)) * + CriticalDamageRate * (1 - TakeDamageReductionPer * 0.01) * (1 + TakeDamageIncreasePer * 0.01)); +} +// Armor가 있을 경우 (플레이어) +else +{ + Damage = Floor((BaseDamage * BluntRate) * HitBoxRate * (SkillPer * 0.01) * + ((1 - ResistancePer1 * 0.01) * (1 - ResistancePer2 * 0.01) * (1 - DamageReductionPer * 0.01)) * + CriticalDamageRate * (1 - TakeDamageReductionPer * 0.01) * (1 + TakeDamageIncreasePer * 0.01)); +} +``` + +**공식 분해**: +1. `BaseDamage * BluntRate` - 기본 피해 × 둔기 배율 +2. `× HitBoxRate` - 머리/몸 배율 +3. `× (SkillPer * 0.01)` - 스킬 피해율 (NormalDamagePer, PhysicalSkillPer 등) +4. `× (1 - ResistancePer1 * 0.01)` - 1차 저항 (물리/마법) +5. `× (1 - ResistancePer2 * 0.01)` - 2차 저항 (원소/원거리) +6. `× (1 - DamageReductionPer * 0.01)` - 방어력에 의한 피해 감소 +7. `× CriticalDamageRate` - 치명타 배율 (크리티컬 아니면 1.0) +8. `× (1 - TakeDamageReductionPer * 0.01)` - 피격자의 받는 피해 감소 +9. `× (1 + TakeDamageIncreasePer * 0.01)` - 피격자의 받는 피해 증가 +10. `Floor()` - 소수점 버림 + +**[8] 후면 공격 추가 배율** (라인 793) +```cpp +if (!IsFrontAttack && bUseTargetHitBox) +{ + Damage *= (BackAttackDamagePer * 0.01f); +} +``` +- **후면 공격**일 때 BackAttackDamagePer 백분율 곱셈 (누적) +- HitBoxRate와는 별도로 적용 + +**[9] 최소 데미지 보장** (라인 763-766) +```cpp +if (Damage < 1.0f && bUseDungeonRule) +{ + Damage = 1.0f; +} +``` +- 던전 룰이 적용될 때만 +- 최소 1 데미지 보장 +- 지속 피해에는 적용되지 않음 + +**[10] 파티원 피해 제거** (라인 850-868) +```cpp +if (IsTargetParty) +{ + Damage = 0.0f; // 파티원 간 피해 완전 차단 +} +``` +- 같은 파티원에게는 피해 0 +- 피격 이후 효과도 적용 안 됨 +- 지역 효과(독 지대 등)는 여전히 적용됨 + +**[11] 실드 → 아머 → HP 적용** (라인 883-1047) + +**실드 먼저 소모** (라인 883-888): +```cpp +ShieldDamage = Clamp(DamageNoResist, 0, Shield); // 저항 무시 피해로 계산 +if (Shield > 0) { + Damage = Clamp(Damage - ShieldDamage, 0, Damage); + Shield -= ShieldDamage; +} +``` +- 실드는 저항을 무시한 DamageNoResist로 계산 +- 실드로 흡수한 만큼 Damage에서 차감 + +**아머 게이팅** (라인 1027-1035): +```cpp +if (Armor / ArmorMax > 0.5) { + DamageGating = (Armor - Damage) - (ArmorMax * 0.5); + if (DamageGating < 0) { + Damage += DamageGating; // 피해 감소 + } +} +``` +- 아머가 50% 이상일 때 +- 한 번의 공격으로 50% 밑으로 떨어지지 않도록 보호 +- **예시**: ArmorMax=100, Armor=80, Damage=40 + - DamageGating = (80-40) - 50 = -10 + - Damage = 40 + (-10) = 30 (10 감소) + - 최종 Armor = 80-30 = 50 + +**HP 적용** (라인 1038-1042): +- Armor가 0일 때만 HP에 피해 +- 반죽음 상태에서는 HP에 -1씩만 적용 + +#### 계산 예시 + +**조건**: +- BaseDamage: 288 (위 예시) +- HitBoxRate: 1.2 (머리) +- SkillPer: 120% (PhysicalSkillPer) +- ResistancePer1: 30% (PhysicalResistance) +- ResistancePer2: 20% (FireResistance) +- DamageReductionPer: 15% +- CriticalDamageRate: 1.5 (치명타) +- BackAttackDamagePer: 150% (후면) +- Shield: 50 +- Armor: 80 / 100 + +**계산**: +``` +1. 기본 계산: + Damage = Floor(288 × 1.2 × 1.2 × 0.7 × 0.8 × 0.85 × 1.5) + = Floor(288 × 1.2 × 1.2 × 0.476) + = Floor(196.9) + = 196 + +2. 후면 배율: + Damage = 196 × 1.5 = 294 + +3. 실드 흡수: + Shield = 50 감소 + Damage = 294 - 50 = 244 + +4. 아머 게이팅: + DamageGating = (80 - 244) - 50 = -214 + Damage = 244 + (-214) = 30 (아머를 50으로 보호) + +5. 최종: + Shield: 0 + Armor: 50 + HP: 변화 없음 +``` + +### 1.3 지속 피해(DoT) 계산 + +> **코드 위치**: `WSDamageCalculation.cpp:427-437` + +지속 피해는 일반 공격/스킬과는 별도의 계산 방식을 사용합니다. + +#### DoT의 정의 + +- 캐릭터에게 지속적으로 피해를 주는 효과로써, 주로 스토커 스킬에 포함된다. +- 별도 명세가 없는한, 동일한 DD는 중첩되지 않고, 지속시간이 초기화 된다. +- 다른 DD끼리는 동시에 걸릴 수 있다. +- **DD는 적용 1초 후부터 피해가 발생하며, 1초 주기로 작동한다.** + +#### 지속 피해 종류 및 공식 + +**중독 Poison** +- 유지 시간 동안 최대 체력의 n%만큼의 피해를 체력에 준다. +- 공식: `Maxhp*0.20*(1-DOTReduceRate)*(1-PoisonResistanceInc)/10` +- 10초 동안 적용되어 총 최대 체력의 20% 피해 + +**부식 Corrosion** +- 유지 시간 동안 방어구 최대 내구도의 n%만큼의 피해를 방어구 내구도에 준다. +- 공식: `MaxArmor*0.2*(1-DOTReduceRate)*(1-DarkResistanceInc)/10` +- **주의**: 암흑 저항(DarkResistance)이 적용됨 +- 10초 동안 적용되어 총 최대 아머의 20% 피해 + +**화상 Burn** (중독 + 부식) +- 유지 시간 동안 최대 체력과 최대 방어구 내구도의 n%만큼의 피해를 체력과 방어구 내구도에 각각 준다. +- HP 공식: `Maxhp*0.1*(1-DOTReduceRate)*(1-FireResistanceInc)/10` +- Armor 공식: `MaxArmor*0.1*(1-DOTReduceRate)*(1-FireResistanceInc)/10` +- 10초 동안 적용되어 총 최대 체력/아머의 10% 피해 + +**출혈 Bleed** (미구현) +- 유지 시간 동안 체력에 n만큼의 피해를 준다. +- 중독과의 차이점은 상대방의 최대 체력과 상관없이 총 피해량이 정해져 있다는 점. + +**감전 ElectricShock** (미구현) +- 중독 효과 + 스킬을 사용할 수 없다. (일반 공격은 가능) + +#### DoT 계산 예시 + +**조건**: +- HPMax: 1000 +- DOTReducePer: 10% +- PoisonResistancePer: 25% +- 중독 지속 시간: 10초 + +**계산**: +``` +1초당 피해 = 1000 × 0.20 × (1-0.1) × (1-0.25) / 10 + = 1000 × 0.20 × 0.9 × 0.75 / 10 + = 13.5 + +총 피해 (10초) = 13.5 × 10 = 135 +``` + +### 1.4 힐 계산 + +> **코드 위치**: `WSHealCalculation.cpp:49-125` + +힐은 마법 공격력 기반으로 계산됩니다. + +#### 힐 계산 공식 + +**기본 공식** (라인 97-98): +```cpp +// 힐 = 기본 힐량 + (마법공격력 × 스킬 계수) × (인트배율) +HealMagnitude = InComingHeal * Level + (MagicalDamage * (1.0 + SkillDamagePer * 0.01)) * (MagicalSkillPer * 0.01) +``` + +**구성 요소**: +1. **InComingHeal**: 스킬 데이터에 정의된 기본 힐량 +2. **Level**: 스킬 레벨 +3. **MagicalDamage**: 시전자의 마법 공격력 (2차 스탯) +4. **SkillDamagePer**: 패시브 스킬 피해 증가 (PassiveSet) +5. **MagicalSkillPer**: 지능(Int)에 의한 마법 스킬 피해율 (CharacterSet) + +#### 과치유 방지 + +**코드** (라인 106-108): +```cpp +float TargetMaxHP = TargetASC->GetNumericAttribute(UCharacterSet::GetHPMaxAttribute()); +float TargetHP = TargetASC->GetNumericAttribute(UCharacterSet::GetHPAttribute()); +float HealAmount = FMath::Min(HealMagnitude, TargetMaxHP - TargetHP); +``` + +- 현재 HP + HealAmount가 MaxHP를 초과하지 않도록 제한 +- 과치유는 발생하지 않음 + +#### 힐 무효 + +**코드** (라인 110-113): +```cpp +if (TargetASC->HasMatchingGameplayTag(FGameplayTag::RequestGameplayTag("Effect.IgnoreHeal"))) +{ + HealAmount = 0.0f; +} +``` + +- `Effect.IgnoreHeal` 태그가 있으면 힐 완전 무효 +- 특정 디버프나 상태에서 힐 차단용 + +#### 궁극기 게이지 충전 + +**코드** (라인 118-123): +```cpp +if (!SourceASC->HasMatchingGameplayTag(FGameplayTag::RequestGameplayTag("Ability.Ultimate"))) +{ + float UltimateCurrentvalue = InstigatorCharacter->CharacterSet->GetUltimateCurrentValue(); + float UltimateMaxValue = InstigatorCharacter->CharacterSet->GetUltimateMaxValue(); + InstigatorCharacter->CharacterSet->SetUltimateCurrentValue(FMath::Min(HealAmount + UltimateCurrentvalue, UltimateMaxValue)); +} +``` + +- 궁극기 사용 중이 아닐 때만 +- 실제 회복한 HealAmount만큼 궁극기 게이지 충전 +- UltimateMaxValue를 초과하지 않음 + +#### 힐 계산 예시 + +**조건**: +- InComingHeal: 50 (스킬 기본 힐량) +- Level: 3 +- MagicalDamage: 100 +- SkillDamagePer: 15% +- MagicalSkillPer: 120% +- Target HP: 800 / 1000 + +**계산**: +``` +1. HealMagnitude 계산: + HealMagnitude = (50 × 3) + (100 × 1.15) × 1.2 + = 150 + (115 × 1.2) + = 150 + 138 + = 288 + +2. 과치유 방지: + 최대 회복 가능량 = 1000 - 800 = 200 + HealAmount = Min(288, 200) = 200 + +3. 최종: + HP: 800 → 1000 + 궁극기 게이지: +200 +``` + +--- + +## 2. BaseDamage 구성요소 + +### 2.1 1차 스탯 (Primary Stats) + +> **코드 위치**: `PrimarySet.h` + +캐릭터 1차 스탯의 총합은 75가 되도록 한다. + +- **힘** Str - 일반 공격 과 물리 스킬 피해량 에 영향을 준다. +- **민첩** Dex - 이동 속도 와 일반 공격 속도(평타) 에 영향을 준다. +- **지능** Int - 마법 스킬 시전속도 와 마법 스킬 피해량 에 영향을 준다. +- **체질** Con - 최대 체력 과 받는 지속 피해 감소 그리고 최대 지구력 에 영향을 준다. +- **지혜** Wis - 최대 마나 와 마나 소비량 에 영향을 준다. + +### 2.2 2차 스탯 (Secondary Stats) + +> **코드 위치**: `CharacterSet.h` + +#### 기본 스탯 +- 체력 HP +- 최대 체력 HPMax +- 마나 MP +- 최대 마나 MPMax +- 지구력 Stamina // 능력치 정보에서 안나옴 +- 최대 지구력 StaminaMax +- 실드 Shield // 피해 흡수, 저항 무시 + +#### 피해 관련 +- 물리 피해 PhysicalDamage +- 마법 피해 MagicalDamage +- 일반 공격 피해율 NormalDamagePer +- 물리 스킬 피해율 PhysicalSkillPer +- 마법 스킬 피해율 MagicalSkillPer +- 고정 스킬 피해율 FixedSkillPer + +#### 방어 관련 +- 방어력 Defense +- 고정 방어력 FixedDefense // 방어력 증가(%)의 영향을 받지 않는 방어력 +- 방어력 비율 DefensePer +- 피해 감소율 DamageReductionPer // Defense에 의한 대미지 감소 +- 방어구 내구도 Armor // 인게임 용어는 Armor Durability +- 최대 방어구 내구도 ArmorMax +- 콤비네이션 아머 CombinationArmor // 몬스터 전용 + +#### 속도 관련 +- 이동속도 WalkSpeed +- 이동속도 비율 WalkSpeedPer +- 이동속도 수정값 MoveSpeedModify // 장비에 의해 변경 +- 공격속도 AttackSpeedPer +- 스킬 시전 속도 SkillCastSpeedPer + +#### 마나 관련 +- 마나 회복 ManaRegen +- 마나 회복 비율 MPRegenPer +- 마나 소비 감소 // SkillCostReducePer +- 마나 쉴드 대미지 감소 MPDamageReducePer + +#### 치명타 관련 +- 치명타 확률 CriticalPer +- 치명타 피해 CriticalDamagePer +- 치명타 피해 감소 CriticalDamageReducePer +- 치명타 피해 범위 CriticalDamageRange + +#### 특수 피해 +- 후방 공격 피해율 BackAttackDamagePer +- 머리 공격 피해율 HeadAttackDamagePer // 때리는 입장 +- 머리 피해 감소 HeadShotDamReducePer // 맞는 입장 +- 방어 무시 피해율 IgnoreArmorDamagePer +- 방어 무시 피해의 아머 피해율 IgnoreArmorDamageToArmorPer + +#### 저항 +- 물리 저항률 PhysicalResistancePer +- 투사체 저항률 RangedResistancePer +- 마법 저항률 MagicalResistancePer +- 화염 저항률 FireResistancePer +- 독 저항률 PoisonResistancePer +- 물 저항률 WaterResistancePer +- 번개 저항률 LightningResistancePer +- 빛 저항률 HolyResistancePer +- 암흑 저항률 DarkResistancePer +- 지속 피해 저항률 DOTReducePer + +#### 브레이크다운 시스템 +- 브레이크다운 Breakdown // 브레이크다운 게이지 현재값 +- 브레이크다운 최대값 BreakdownMax // 브레이크다운 게이지 최대값 +- 브레이크다운 스턴 시간 BreakdownStunTime // 브레이크다운 발동 시 스턴 지속 시간 +- 브레이크다운 리셋 시간 BreakdownResetTime // 브레이크다운 게이지 리셋 시간 + +#### 궁극기 +- 궁극기 현재값 UltimateCurrentValue // 궁극기를 사용하기 위해서 충전해야 하는 포인트 +- 궁극기 최대값 UltimateMaxValue +- 궁극기 회복 비율 UltimateRecoveryPer + +#### 기타 +- 장착 가능 장비 EquipableTypes + - 무기 종류: 검, 활, 지팡이, 대검, 단검, 둔기 // 1종을 할당 + - 갑옷 종류: 천, 경갑, 중갑 // 2종을 할당 +- 스킬 쿨타임 감소율 SkillCoolTimeReducePer +- 스킬 비용 감소율 SkillCostReducePer +- 가하는 피해량 감소 TakeDamageReductionPer // 리옌의 연화 디버프 +- 가하는 피해량 증가 TakeDamageIncreasePer // 피해량 증가 스크롤 + +### 2.3 패시브 스탯 (비율 수정자) + +> **코드 위치**: `PassiveSet.h` + +패시브 스탯은 장비, 스킬, 퍽, **룬** 등을 통해 캐릭터의 능력치를 **백분율(%)로 수정**하는 속성들입니다. 주로 장비 랜덤 옵션, 스킬 효과, 캐릭터별 퍽(Perk) 시스템, **룬 시스템**에서 사용됩니다. + +> **룬 시스템 영향** (섹션 5.3 참조): +> 룬 시스템은 PassiveSet 속성을 직접 수정하여 전투 능력을 강화합니다. 주요 룬 영향 속성: +> - PhysicalDamagePer, MagicalDamagePer, SkillDamagePer (피해 증가) +> - SkillCoolTimeReducePer, ManaCostPer, CastingTimePer (스킬 코스트) +> - NormalEnemyDamagePer, EliteEnemyDamagePer, BossEnemyDamagePer (몬스터 타입별) +> - APPer, PotionEffectPer, ThrowItemImpactRangePer 등 + +#### 공통 패시브 스탯 (PassiveSet.h:125-260) + +**피해 관련**: +- PhysicalDamagePer // 물리 피해 증가율 +- MagicalDamagePer // 마법 피해 증가율 +- ArmorAttackDamagePer // 방어구 공격 피해 증가율 +- SkillDamagePer // 스킬 피해 증가율 +- TakenSkillDamagePer // 받는 스킬 피해 증가율 +- InflictDamagePerOnStunTarget // 기절 대상에게 가하는 피해 증가율 +- NormalEnemyDamagePer // 일반 몬스터에게 가하는 피해 증가율 +- EliteEnemyDamagePer // 엘리트 몬스터에게 가하는 피해 증가율 +- BossEnemyDamagePer // 보스 몬스터에게 가하는 피해 증가율 + +**방어 관련**: +- DefensePer // 방어력 증가율 +- BreakArmorDefensePer // 방어구 파괴 방어 증가율 + +**속도 관련**: +- AttackSpeedPer // 공격 속도 증가율 +- WalkSpeedPer // 이동 속도 증가율 + +**체력/마나/지구력 관련**: +- HPPer // 최대 체력 증가율 +- MPPer // 최대 마나 증가율 +- APPer // 지구력 증가율 (Armor Points) +- BlockingStaminaRate // 방어 시 지구력 소모 비율 + +**스킬/마나 코스트 관련**: +- ManaCostPer // 마나 소비 증가율 (음수면 감소) +- CastingTimePer // 시전 시간 증가율 (음수면 감소) +- CooldownTimePer // 쿨다운 시간 증가율 (음수면 감소) + +**상태이상 관련**: +- TakenCCDurationTimePer // 받는 CC 지속 시간 증가율 + +**인터랙션 관련**: +- InteractionTimePer // 상호작용 시간 증가율 (음수면 감소) +- InteractionTakenDamagePer // 상호작용 중 받는 피해 증가율 +- ChestInteractionTimePer // 상자 상호작용 시간 증가율 +- DoorInteractionTimePer // 문 상호작용 시간 증가율 + +**어그로 및 스텔스**: +- AggroPer // 어그로 증가율 +- CrouchWalkSoundPer // 웅크린 이동 소리 증가율 + +**부활 관련**: +- ReviveHPBonusRate // 부활 시 체력 보너스 비율 +- ReviveTime // 부활 시간 + +**아이템 효과**: +- PotionEffectPer // 포션 효과 증가율 +- ThrowItemImpactRangePer // 투척 아이템 범위 증가율 +- ThrowEffectDurationPer // 투척 효과 지속 시간 증가율 +- StatueEffectPer // 조각상 효과 증가율 + +**NPC 관련**: +- AttackDamagePerOnNpcKill // NPC 처치 시 공격 피해 증가율 + +#### 캐릭터별 전용 퍽 스탯 (PassiveSet.h:263-461) + +각 스토커 캐릭터는 전용 퍽 스탯을 보유하고 있습니다: +- **Hilda** (힐다): 7개의 전용 퍽 (라인 265-292) +- **Urud** (우루드): 6개의 전용 퍽 (라인 297-320) +- **Nave** (네이브): 10개의 전용 퍽 (라인 325-364) +- **Baran** (바란): 8개의 전용 퍽 (라인 369-400) +- **Rio** (리오): 8개의 전용 퍽 (라인 405-436) +- **Clad** (클라드): 5개의 전용 퍽 (라인 441-460) + +#### 범용 퍽 슬롯 (PassiveSet.h:466-488) +- Perk1 ~ Perk8: 범용 퍽 값 저장용 (용도는 게임플레이 효과에서 정의) + +--- + +## 3. 데미지 수정자 + +### 3.1 공격 타입 (AttackType) + +> **코드 위치**: `SkillDataRow.h:13-21` + +- 일반 공격(평타) Normal = 0 +- 물리 스킬 PhysicalSkill = 1 +- 마법 스킬 MagicalSkill = 2 +- 고정 스킬 FixedSkill = 3 // 저항 무시 스킬용 +- None = 4 + +### 3.2 원거리 타입 (RangedType) + +- 평타가 원거리인지 아닌지를 판단 +- 원거리 공격일 경우 RangedResistancePer 저항이 적용됨 + +### 3.3 원소 타입 (ElementType) + +> **코드 위치**: `SkillDataRow.h:24-33` + +- None = 0 // 무속성 +- 화염 Fire = 1 +- 독 Poison = 2 +- 물 Water = 3 +- 번개 Lightning = 4 +- 빛 Holy = 5 +- 암흑 Dark = 6 + +### 3.4 피해 종류 (DamageStatics) + +- 일반 피해 NormalDamage +- 물리 피해 PhysicalDamage +- 마법 피해 MagicalDamage +- 치명 피해 CriticalDamage +- 후방 피해 BackAttackDamage +- 방어 무시 피해 IgnoreArmorDamage +- 갑옷 피해 IgnoreArmorDamageToArmor +- 머리 피해 HeadAttackDamage + +### 3.5 저항 종류 (가급적 갑옷에 부여) + +- MP 피해 저항 MPDamageReducePer +- 머리 피해 저항 HeadShotDamReducePer +- 지속 피해 저항 DOTReducePer +- 물리 저항 PhysicalResistancePer +- 마법 저항 MagicalResistancePer +- 원거리 저항 RangedResistancePer +- 화염 저항 FireResistancePer +- 독 저항 PoisonResistancePer +- 물 저항 WaterResistancePer +- 번개 저항 LightningResistancePer +- 빛 저항 HolyResistancePer +- 암흑 저항 DarkResistancePer + +**저항 상한**: 모든 저항은 최대 75%까지만 적용됨 + +### 3.6 피격 부위 배율 (HitBoxRate) + +> **코드 위치**: `WSDamageCalculation.cpp:473-510` +> **실제 구현**: 후면 공격 배율은 HitBoxRate가 아닌 BackAttackDamagePer 패시브로 조정됨 + +- **정면 공격** (IsFrontAttack = true) + - 머리 (IsHeadShot = true): `HitBoxRate = 1.2 + HeadAttackDamagePer * 0.01 - 0.5 * HeadShotDamReducePer * 0.01` + - 몸 (IsHeadShot = false): `HitBoxRate = 1.0` + +- **후면 공격** (IsFrontAttack = false) + - 머리 (IsHeadShot = true): `HitBoxRate = 1.2 + HeadAttackDamagePer * 0.01 - 0.5 * HeadShotDamReducePer * 0.01` + - 몸 (IsHeadShot = false): `HitBoxRate = 1.0` + - **후면 추가 배율**: 최종 데미지에 `BackAttackDamagePer * 0.01` 곱셈 (코드 라인 793) + +### 3.7 둔기 배율 (BluntRate) + +> **코드 위치**: `WSDamageCalculation.cpp:530-534` + +- 둔기 유형의 무기는 **1.2배** 데미지 (태그: `Equip.BluntWeapon`) +- 일반 공격(Normal) 타입일 때만 적용 + +### 3.8 쇼크 효과 (ShockMontageEffect) + +플레이어가 쇼크 상태에 빠질 때 아래 몽타주 중 1개가 재생된다. (시간이 다름) + +- 갑옷 완파 GE_ShockMotion_ArmorDestroy_Complete +- 갑옷 반파 GE_ShockMotion_ArmorDestroy_Partial +- 쇼크 대 GE_ShockMotion_Heavy +- 쇼크 중 GE_ShockMotion_Medium +- 쇼크 소 GE_ShockMotion_Weak + +--- + +## 4. 특수 시스템 + +### 4.1 상태 이상(CC) & 능력치 하향(DeBuff) + +#### 상태 이상CC 정의 + +- 캐릭터의 행동을 제한하거나 무력화 시키는 효과로써, 주로 스토커 스킬에 포함된다. +- 일반적으로 캐릭터들의 Motion Factor를 제어하며, 지속 시간을 보유하고 있다. +- 지속 시간의 표현을 위해 모션과 이펙트가 사용될 수 있다. +- GameplayTag로 `Character.State.*` 형태로 관리됨 + +#### 상태 이상CC의 종류 + +- **충격 Shock** (구현됨) + - 대상의 공격 모션을 중단 시킨다. + +- **기절 Stun** (구현됨) + - 충격 Shock + 이동을 제한한다. + +- **속박 Snare** (구현됨) + - 대상의 이동을 제한한다. + +- **끌어당김 Grab** (구현됨) + - 기절 Stun + 시전자 방향으로 강제 이동시킨다. + +- **밀쳐냄 KnockBack** (구현됨) + - 기절 Stun + 시전자의 반대 방향으로 강제 이동 시킨다. + - 이동 거리는 피격 대상의 위치를 기준으로 계산된다. + +- **공포 Flee** (미구현) + - 충격 Shock + 시전자의 반대 방향으로 느리게 이동한다. + - 효과 지속시간 동안 피격 대상은 공격 행위를 할 수 없다. + +- **수면 Sleep** (미구현) + - 기절 Stun과 같은 효과지만, 유지 시간 동안 대상이 다시 피격되면 해제 된다. + - 일반적으로 기절 Stun 보다는 유지 시간이 길다. + +#### 능력치 하향DeBuff의 정의 + +- 캐릭터가 가진 능력치를 일시적으로 하향시키는 효과로써, 주로 스토커 스킬에 포함된다. +- 별도 명세가 없는한, 동일한 DeBuff는 중첩되지 않고, 지속시간이 초기화 된다. +- 다른 DeBuff끼리는 동시에 걸릴 수 있다. + +#### 능력치 하향DeBuff의 종류 + +- **둔화 Slow** (구현됨) + - 유지 시간 동안 이동 속도가 n% 낮아진다. + - `WalkSpeedPer` 속성으로 구현 + +- **무장 해제 Disarm** (미구현) + - 유지 시간 동안 공격력이 n% 낮아진다. + +### 4.2 방어 지구력 시스템 + +> **코드 위치**: `WSDamageCalculation.cpp:769-795` (방어 시스템) +> **Blueprint 경로 확인됨**: `D:\Work\WorldStalker\WorldStalker\Content\Blueprints\Abilities\GE_BlockingStateStamina.uasset` + +#### 방어 상태 유지에 따른 효과 + +- **지구력 소모** + - BP경로: `/Game/Blueprints/Abilities/GE_BlockingStateStamina` + - 현재 설정 값: 0.2초 마다 -0.5 지구력 + - **주의**: Blueprint 파일은 바이너리 형식이므로 설정값은 에디터에서 확인 필요 + +- **이동 속도 감소** (걷기만 가능하며 뛸 수 없다) + - BP경로: `/Game/Blueprints/Abilities/GE_AttackBlockedWalkSpeedDown` + - 현재 설정 값: 원래 걷는 속도의 70% 수준으로 이동한다. + - **주의**: Blueprint 파일은 바이너리 형식이므로 설정값은 에디터에서 확인 필요 + +- **방어 유지 가능 스토커**: 힐다, 바란, 클라드 (카지모르드는 아님) + +#### 방어 성공에 따른 지구력 감소 값 + +- **근거리 일반 공격 방어** + - BP경로: `/Game/Blueprints/Abilities/GE_AttackBlocked` + - 현재 설정 값: 성공 시 -27.0 지구력 + - **주의**: Blueprint 파일은 바이너리 형식이므로 설정값은 에디터에서 확인 필요 + +- **원거리 일반 공격 방어** + - BP경로: `/Game/Blueprints/Abilities/GE_AttackBlocked_Projectile` + - 현재 설정 값: 성공 시 -32.0 지구력 + - **주의**: Blueprint 파일은 바이너리 형식이므로 설정값은 에디터에서 확인 필요 + +- **마법 공격 방어** + - BP경로: `/Game/Blueprints/Abilities/GE_AttackBlocked_Magic` + - 현재 설정 값: 성공 시 -35.0 지구력 + - **주의**: Blueprint 파일은 바이너리 형식이므로 설정값은 에디터에서 확인 필요 + +#### 방어 피해 감소 + +> **코드 위치**: `WSDamageCalculation.cpp:769-795` + +- **정면 방어 시** (IsFrontAttack && Character.State.Blocking 태그 보유) + - 일반 공격 (Normal): `Damage * (1 - BlockedPhysicalDamageReducePer * 0.01)` 감소 + - 물리 스킬 (PhysicalSkill): `Damage * (1 - BlockedPhysicalDamageReducePer * 0.01)` 감소 + - 마법 스킬 (MagicalSkill): `Damage * (1 - BlockedMagicalDamageReducePer * 0.01)` 감소 +- **후면 공격은 방어 불가** + +#### 지구력 자동 회복 + +- **전제**: 뛰는 상태 or 방어 상태가 아니어야 한다. +- 전제를 만족하고 n초 후에 m씩 지구력이 자동 회복 된다. +- BP경로: `/Game/Blueprints/Abilities/GE_StaminaRegen` +- **주의**: Blueprint 파일은 바이너리 형식이므로 설정값은 에디터에서 확인 필요 + +### 4.3 반죽음 (HalfDeath) + +> **코드 위치**: `WSDamageCalculation.cpp:928-932`, `WSCharacterBase.h:178-180` +> **GameplayTag**: `Character.State.HalfDeath` + +#### 반죽음의 정의 + +- 스토커의 체력HP = 0 이되면 n초 동안 반죽음 상태가 된다. 방어구 내구도와는 상관이 없다. +- 반죽음 상태가 되면 천천히 기면서 이동만 할 수 있고, 그 외 다른 행동(ex 공격)은 할 수 없다. +- 반죽음 상태가 없는 경우는 아래와 같다: + - 파티원이 없거나 or 생존한 파티원이 없을 경우 즉, 혼자 있을 경우에는 HP = 0이되면 바로 사망. + - `CanHalfDie()` 함수로 판별 (WSCharacterBase.h:178) + +#### 반죽음 상태에서의 피해 + +- **피해량과는 상관 없이 총 3회의 피격을 견딜 수 있다.** + - 코드: 반죽음 상태에서는 피격 시 HP에 -1씩만 적용 (WSDamageCalculation.cpp:930) +- 상태 이상, 능력치 하향, 지속 피해 효과가 있다면 즉시 해제 된다. +- **파티원의 공격은 적용되지 않음** (코드 라인 928: `IsTargetParty` 체크) + +#### 반죽음 → 사망 판정 + +- 반죽음 유지 시간이 모두 지나거나 or 피격을 3회 이상 받으면 사망 상태가 된다. + +#### 반죽음 → 부활 판정 + +- 사망 판정을 받기 전에 파티원이 근처에 와서 F키를 눌러 부활시켜주면 된다. (일정 시간 필요) +- 반죽음 이전에 방어구 내구도가 남아 있었다면 이를 보존시켜준다. + +--- + +## 5. 룬 시스템 + +> **코드 위치**: +> - `WSGameplayAbility.h:209, 268` - 룬 데이터 조회 함수 +> - `WSGameplayAbility.cpp:1525-1550` - 룬 데이터 검색 구현 +> - **DT_Rune, DT_RuneGroup** DataTable 어셋 (DataTable.json 파일에 익스포트됨) + +### 5.1 룬 시스템 개요 + +룬은 플레이어가 장착하여 전투 능력과 탐험 능력을 강화하는 장비 시스템입니다. 각 룬은 레벨 1부터 5까지 업그레이드가 가능하며, 레벨이 올라갈수록 효과가 강화됩니다. + +#### 기본 메커니즘 + +- **장착 슬롯**: 총 5개의 룬 슬롯 보유 +- **레벨 시스템**: 각 룬은 Lv.1 ~ Lv.5까지 업그레이드 가능 +- **그룹 구조**: 5개 그룹으로 분류 (전투, 스킬, 장비, 보조, 모험) +- **선택 제약**: Main 그룹 1개 + Sub 그룹 1개 선택 + +#### 룬 ID 체계 + +룬 ID는 5자리 숫자로 구성됩니다: +``` +XYZNN +X = 그룹 번호 (1:전투, 2:스킬, 3:장비, 4:보조, 5:모험) +Y = 라인 타입 (1:Core, 2:Sub1, 3:Sub2) +Z = 라인 내 순번 +NN = 추가 식별자 (보통 01) +``` + +**예시**: `10201` = 전투 그룹(1), Sub1 라인(2), 첫 번째 룬(01) + +### 5.2 룬 선택 규칙 + +#### 그룹 선택 + +1. **Main 그룹**: 5개 그룹 중 1개 선택 + - Core Line, Sub 1Line, Sub 2Line 모두 선택 가능 (3개 룬) + +2. **Sub 그룹**: Main이 아닌 다른 그룹 중 1개 선택 + - **제약**: Core Line 선택 불가 + - Sub 1Line, Sub 2Line만 선택 가능 (2개 룬) + +#### 선택 예시 + +**Main: 전투(Battle), Sub: 스킬(Skill)** +``` +선택 가능한 룬: +- 10101 (전투-Core-1) +- 10102 (전투-Core-2) +- 10103 (전투-Core-3) +- 20201 (스킬-Sub1-1) +- 20301 (스킬-Sub2-1) + +총 5개 룬 장착 가능 +``` + +**Main: 장비(Equipment), Sub: 보조(Assist)** +``` +선택 가능한 룬: +- 30101 (장비-Core-1) +- 30102 (장비-Core-2) +- 30103 (장비-Core-3) +- 40201 (보조-Sub1-1) +- 40301 (보조-Sub2-1) + +총 5개 룬 장착 가능 +``` + +### 5.3 룬 그룹 및 효과 + +#### 5.3.1 전투 그룹 (Battle, 10xxx) + +**Core Line** (10101~10103): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 10101 | 충전 | 궁극기 게이지 회복량 증가 | UltimateRecoveryPer | Lv.1: +15% → Lv.5: +30% | +| 10102 | 진격 | 공격 적중 시 이동 속도 증가 | GA_Rune_10102 | 조건부 효과 (Blueprint) | +| 10103 | 공략 | 머리 공격 피해 증가 | HeadAttackDamagePer | Lv.1: +10% → Lv.5: +20% | + +**Sub 1Line** (10201~10202): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 10201 | 분노 | 물리 피해 증가 | PhysicalDamagePer | Lv.1: +6% → Lv.5: +10% | +| 10202 | 방패 | 물리 저항 증가 | PhysicalResistancePer | Lv.1: +2% → Lv.5: +7% | + +**Sub 2Line** (10301~10302): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 10301 | 폭풍 | 마법 피해 증가 | MagicalDamagePer | Lv.1: +6% → Lv.5: +10% | +| 10302 | 수호 | 마법 저항 증가 | MagicalResistancePer | Lv.1: +2% → Lv.5: +7% | + +#### 5.3.2 스킬 그룹 (Skill, 20xxx) + +**Core Line** (20101~20103): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 20101 | 저주 | 스킬 적중 시 지연 피해 | GA_Rune_20101 | 조건부 효과 (Blueprint) | +| 20102 | 침식 | 저주 중첩당 스킬 피해 증가 | GA_Rune_20102 | 조건부 효과 (Blueprint) | +| 20103 | 활기 | 마나 높을 때 스킬 피해 증가 | GA_Rune_20103 | 조건부 효과 (Blueprint) | + +**Sub 1Line** (20201~20203): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 20201 | 파괴 | 스킬 피해 증가 | SkillDamagePer | Lv.1: +6% → Lv.5: +10% | +| 20202 | 왜곡 | 스킬 쿨타임 감소 | SkillCoolTimeReducePer | Lv.1: +15% → Lv.5: +25% | +| 20203 | 절약 | 스킬 마나 소모 감소 | ManaCostPer | Lv.1: -25% → Lv.5: -50% | + +**Sub 2Line** (20301~20302): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 20301 | 명상 | 마나 회복량 증가 | MPRegenPer | Lv.1: +28% → Lv.5: +70% | +| 20302 | 영창 | 스킬 시전 속도 증가 | CastingTimePer | Lv.1: -15% → Lv.5: -30% | + +#### 5.3.3 장비 그룹 (Equipment, 30xxx) + +**Core Line** (30101~30103): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 30101 | 공허 | 비어있는 장비 슬롯당 피해 증가 | GA_Rune_30101 | Lv.1: +2%/슬롯 → Lv.5: +4%/슬롯 | +| 30102 | 견고 | 갑옷 내구도 증가 | APPer | Lv.1: +10% → Lv.5: +50% | +| 30103 | 완벽 | 장비 슬롯 다 채우면 방어력 증가 | GA_Rune_30103 | Lv.1: +7% → Lv.5: +18% | + +**Sub 1Line** (30201~30202): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 30201 | 용사 | 검/단검/대검 피해 증가 | GA_Rune_30201 | Lv.1: +8% → Lv.5: +12% | +| 30202 | 투사 | 지팡이/활/둔기 피해 증가 | GA_Rune_30202 | Lv.1: +8% → Lv.5: +12% | + +**Sub 2Line** (30301~30303): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 30301 | 신속 | 천 방어구당 시전 속도 증가 | GA_Rune_30301 | Lv.1: +3%/갑옷 → Lv.5: +5%/갑옷 | +| 30302 | 정밀 | 경갑 방어구당 치명타 확률 증가 | GA_Rune_30302 | Lv.1: +1%/갑옷 → Lv.5: +3%/갑옷 | +| 30303 | 강인 | 중갑 방어구당 방어력 증가 | GA_Rune_30303 | Lv.1: +1.5%/갑옷 → Lv.5: +6.5%/갑옷 | + +#### 5.3.4 보조 그룹 (Assist, 40xxx) + +**Core Line** (40101~40102): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 40101 | 부활 | 쓰러진 상태에서 자동 부활 1회 | ReviveTime | Lv.1: 25초 → Lv.5: 5초 | +| 40102 | 만전 | 던전 진입 시 기본 궁극기 획득 | GA_Rune_40102 | Lv.1: 25% → Lv.5: 50% | + +**Sub 1Line** (40201~40202): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 40201 | 면역 | 물약 사용 시 물리/마법 저항 증가 | GA_Rune_40201 | 20초간, Lv.1: +10% → Lv.5: +20% | +| 40202 | 기습 | 투척 아이템 사용 시 공격 속도 증가 | GA_Rune_40202 | 20초간, Lv.1: +10% → Lv.5: +20% | + +**Sub 2Line** (40301~40302): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 40301 | 효율 | 물약 효과 증가 (지속 시간 제외) | PotionEffectPer | Lv.1: +25% → Lv.5: +50% | +| 40302 | 폭발 | 투척 아이템 범위 증가 | ThrowItemImpactRangePer | Lv.1: +25% → Lv.5: +50% | + +#### 5.3.5 모험 그룹 (Adventure, 50xxx) + +**Core Line** (50101~50103): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 50101 | 선물 | 잠긴 보물 상자 표시, 오픈 시 최대 HP 증가 | GA_Rune_50101 | 최대 10회, Lv.1: +11 → Lv.5: +22 | +| 50102 | 누적 | 몬스터 처치시 피해 증가 (누적) | GA_Rune_50102 | Lv.1: 1.1%/최대 11% → Lv.5: 1.5%/최대 15% | +| 50103 | 탐험 | 조명석 사용 중 지구력 소모량 감소 | GA_Rune_50103 | Lv.1: -30% → Lv.5: -60% | + +**Sub 1Line** (50201~50202): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 50201 | 학살 | 일반 몬스터 대상 피해 증가 | NormalEnemyDamagePer | Lv.1: +20% → Lv.5: +30% | +| 50202 | 퇴치 | 엘리트 몬스터 대상 피해 증가 | EliteEnemyDamagePer | Lv.1: +16% → Lv.5: +20% | + +**Sub 2Line** (50301~50303): + +| 룬 ID | 이름 | 효과 | 속성 | 레벨별 수치 | +|-------|------|------|------|-------------| +| 50203 | 격퇴 | 보스 몬스터 대상 피해 증가 | BossEnemyDamagePer | Lv.1: +11% → Lv.5: +15% | +| 50301 | 기대 | 상자 열기 시간 감소 | ChestInteractionTimePer | Lv.1: -25% → Lv.5: -50% | +| 50302 | 도적 | 문 열기/닫기 시간 감소 | DoorInteractionTimePer | Lv.1: -25% → Lv.5: -50% | +| 50303 | 축복 | 석상 버프 효과 증가 | StatueEffectPer | Lv.1: +50% → Lv.5: +100% | + +### 5.4 룬이 전투 로직에 미치는 영향 + +#### 5.4.1 BaseDamage 계산 단계 (섹션 1.1 참조) + +룬은 **3단계: 장비 효과 적용**에서 PassiveSet 속성을 통해 영향을 줍니다: + +```cpp +// WSCharacterPlayer.cpp:4018-4022 +// PassiveSet의 PhysicalDamagePer, MagicalDamagePer 백분율이 곱셈으로 적용 +float PhysicalDamagePer = AbilitySystemComponent->GetNumericAttribute( + UPassiveSet::GetPhysicalDamagePerAttribute()) * 0.01f; +AttrValueMap[UCharacterSet::GetPhysicalDamageAttribute()] += + AttrValueMap[UCharacterSet::GetPhysicalDamageAttribute()] * PhysicalDamagePer; +``` + +**영향을 주는 룬**: +- **10201 분노**: PhysicalDamagePer +6~10% → 물리 공격력 증가 +- **10301 폭풍**: MagicalDamagePer +6~10% → 마법 공격력 증가 +- **20201 파괴**: SkillDamagePer +6~10% → 스킬 피해 증가 + +**예시 계산**: +``` +[룬 적용 전] +캐릭터 기본 PhysicalDamage: 50 +장비 옵션 증가: +30 +패시브 PhysicalDamagePer: 0% +→ 최종 PhysicalDamage = (50 + 30) × 1.0 = 80 + +[10201 분노 Lv.5 장착 후] +캐릭터 기본 PhysicalDamage: 50 +장비 옵션 증가: +30 +패시브 PhysicalDamagePer: 10% (룬 효과) +→ 최종 PhysicalDamage = (50 + 30) × 1.10 = 88 (+10% 증가) +``` + +#### 5.4.2 HitBox 판정 단계 (섹션 1.2 [2] 참조) + +룬은 **머리 공격 배율 계산**에 직접 영향을 줍니다: + +```cpp +// WSDamageCalculation.cpp:470-510 +if (HitResult->BoneName == FName(TEXT("b_Head"))) +{ + IsHeadShot = true; + HitBoxRate = 1.2f + HeadAttackDamagePer * 0.01f; + HitBoxRate -= 0.5f * HeadShotDamReducePer * 0.01f; +} +``` + +**영향을 주는 룬**: +- **10103 공략**: HeadAttackDamagePer +10~20% → 머리 공격 배율 증가 + +**예시 계산**: +``` +[룬 적용 전] +기본 머리 배율: 1.2배 +HeadAttackDamagePer: 0% +→ HitBoxRate = 1.2 + 0 = 1.2 + +[10103 공략 Lv.5 장착 후] +기본 머리 배율: 1.2배 +HeadAttackDamagePer: 20% (룬 효과) +→ HitBoxRate = 1.2 + 0.20 = 1.4 (16.7% 추가 증가) +``` + +#### 5.4.3 저항 적용 단계 (섹션 1.2 [5] 참조) + +룬은 **저항 수치**를 직접 증가시켜 받는 피해를 감소시킵니다: + +```cpp +// WSDamageCalculation.cpp:543-613 +// 공격 타입에 따라 적용되는 저항 결정 +if (EAttackType == EWSAttackType::PhysicalSkill) +{ + ResistancePer1 = PhysicalResistancePer; // 룬 10202가 영향 + ResistancePer2 = ElementResistance; +} +else if (EAttackType == EWSAttackType::MagicalSkill) +{ + ResistancePer1 = MagicalResistancePer; // 룬 10302가 영향 + ResistancePer2 = ElementResistance; +} +``` + +**영향을 주는 룬**: +- **10202 방패**: PhysicalResistancePer +2~7% → 물리 피해 저항 증가 +- **10302 수호**: MagicalResistancePer +2~7% → 마법 피해 저항 증가 + +**예시 계산**: +``` +[룬 적용 전] +들어오는 물리 피해: 200 +PhysicalResistancePer: 30% +→ 최종 피해 = 200 × (1 - 0.30) = 140 + +[10202 방패 Lv.5 장착 후] +들어오는 물리 피해: 200 +PhysicalResistancePer: 37% (30% + 7% 룬 효과) +→ 최종 피해 = 200 × (1 - 0.37) = 126 (10% 추가 감소) +``` + +#### 5.4.4 최종 Damage 계산 단계 (섹션 1.2 [7] 참조) + +룬은 **SkillPer 계산**에서 SkillDamagePer를 통해 영향을 줍니다: + +```cpp +// WSDamageCalculation.cpp:543-613 +if (EAttackType == EWSAttackType::PhysicalSkill) +{ + BaseDamage = PhysicalDamage; + SkillPer = PhysicalSkillPer + SkillDamagePer; // 룬 20201이 영향 +} +else if (EAttackType == EWSAttackType::MagicalSkill) +{ + BaseDamage = MagicalDamage; + SkillPer = MagicalSkillPer + SkillDamagePer; // 룬 20201이 영향 +} +``` + +**영향을 주는 룬**: +- **20201 파괴**: SkillDamagePer +6~10% → 모든 스킬 피해 증가 + +**예시 계산**: +``` +[룬 적용 전] +BaseDamage: 288 +SkillPer: 120% (PhysicalSkillPer만) +→ 스킬 피해 배율 = 288 × 1.20 = 345.6 + +[20201 파괴 Lv.5 장착 후] +BaseDamage: 288 +SkillPer: 130% (PhysicalSkillPer 120% + SkillDamagePer 10%) +→ 스킬 피해 배율 = 288 × 1.30 = 374.4 (8.3% 추가 증가) +``` + +#### 5.4.5 몬스터 타입별 추가 피해 (섹션 2.3 참조) + +룬은 **PassiveSet**에 몬스터 타입별 피해 증가 속성을 추가합니다: + +**영향을 주는 룬**: +- **50201 학살**: NormalEnemyDamagePer +20~30% +- **50202 퇴치**: EliteEnemyDamagePer +16~20% +- **50203 격퇴**: BossEnemyDamagePer +11~15% + +**적용 방식**: +이 룬들은 최종 Damage 계산 후 몬스터 타입에 따라 추가 곱셈이 적용되는 것으로 추정됩니다 (구체적인 코드 위치는 추가 확인 필요). + +``` +예상 공식: +최종 피해 = [기존 계산 피해] × (1 + [몬스터 타입별 피해 증가율]) +``` + +#### 5.4.6 궁극기 게이지 충전 (섹션 1.4 참조) + +룬은 **UltimateRecoveryPer**를 통해 궁극기 게이지 충전 속도에 영향을 줍니다: + +**영향을 주는 룬**: +- **10101 충전**: UltimateRecoveryPer +15~30% + +궁극기 게이지는 피해를 주거나 힐을 할 때 충전되며, 이 룬은 충전량을 백분율로 증가시킵니다. + +#### 5.4.7 스킬 코스트 및 쿨타임 (섹션 2.3 참조) + +룬은 **PassiveSet**의 스킬 관련 속성들을 수정합니다: + +**영향을 주는 룬**: +- **20202 왜곡**: SkillCoolTimeReducePer +15~25% → 쿨타임 감소 +- **20203 절약**: ManaCostPer -25~-50% → 마나 소모 감소 +- **20302 영창**: CastingTimePer -15~-30% → 시전 시간 감소 + +**예시 계산 (쿨타임)**: +``` +[룬 적용 전] +스킬 기본 쿨타임: 10초 +SkillCoolTimeReducePer: 0% +→ 실제 쿨타임 = 10초 + +[20202 왜곡 Lv.5 장착 후] +스킬 기본 쿨타임: 10초 +SkillCoolTimeReducePer: 25% +→ 실제 쿨타임 = 10 × (1 - 0.25) = 7.5초 +``` + +### 5.5 룬 구현 메커니즘 + +#### 5.5.1 직접 속성 수정 방식 + +대부분의 룬은 `attributeModifies` 배열을 통해 PassiveSet 또는 CharacterSet 속성을 직접 수정합니다: + +```json +{ + "runeSet": "10201", + "level": 5, + "runeName": "분노", + "desc": "물리 피해 {Value0}% 증가", + "descValue": [10], + "attributeModifies": [ + { + "attribute": { + "attributeName": "PhysicalDamagePer", + "attribute": "/Script/WorldStalker.PassiveSet:PhysicalDamagePer" + }, + "value": 10 + } + ] +} +``` + +이 방식은 GAS(Gameplay Ability System)를 통해 자동으로 적용되며, 별도의 C++ 코드가 필요 없습니다. + +#### 5.5.2 Ability 기반 방식 + +일부 룬은 조건부 효과나 복잡한 로직이 필요하여 Blueprint Ability를 사용합니다: + +```json +{ + "runeSet": "10102", + "level": 1, + "runeName": "진격", + "desc": "공격 적중 시 n초간 이동 속도 {Value0}% 증가", + "descValue": [8, 2], + "attributeModifies": [], + "ability": "/Game/Blueprints/Abilities/Rune/GA_Rune_10102.GA_Rune_10102_C" +} +``` + +이러한 룬들은 특정 이벤트(공격 적중, 몬스터 처치 등)에 반응하거나, 동적인 스택 시스템을 구현합니다. + +**Ability 기반 룬 목록**: +- 10102 진격, 20101 저주, 20102 침식, 20103 활기 +- 30101 공허, 30103 완벽, 30201 용사, 30202 투사 +- 30301 신속, 30302 정밀, 30303 강인 +- 40102 만전, 40201 면역, 40202 기습 +- 50101 선물, 50102 누적, 50103 탐험 + +#### 5.5.3 룬 데이터 조회 시스템 + +**코드 위치**: `WSGameplayAbility.cpp:1525-1550` + +```cpp +bool UWSGameplayAbility::GetRuneDataRowBySetID(FName RuneSetId, FRuneDataRow& OutDataRow) +{ + // 캐시된 룬 데이터 확인 + if (CachedRuneDataRow) + { + OutDataRow = *CachedRuneDataRow; + return true; + } + + // DataTable에서 룬 데이터 검색 + UWSDataAsset* WSData = UWSDataAsset::GetData(GetAvatarActorFromActorInfo()); + FString DataContextString = FString(TEXT("UWSGameplayAbility::GetRuneDataRowBySetID")); + TArray RuneRows; + + WSData->Rune->GetAllRows(DataContextString, RuneRows); + + // RuneSet ID와 현재 Ability 레벨이 일치하는 룬 검색 + for (FRuneDataRow* r : RuneRows) + { + if (r->RuneSet == RuneSetId && r->Level == GetAbilityLevel()) + { + CachedRuneDataRow = r; + OutDataRow = *CachedRuneDataRow; + return true; + } + } + + return false; +} +``` + +이 함수는 Blueprint에서 룬 데이터를 조회할 때 사용되며, 캐싱을 통해 성능을 최적화합니다. + +### 5.6 룬 데이터 테이블 구조 + +#### DT_RuneGroup DataTable 어셋 + +룬 그룹의 구조와 각 라인에 포함된 룬 목록을 정의합니다: + +```json +{ + "RowName": "1000001", + "Data": { + "name": "전투 그룹", + "type": "Battle", + "icon": "/Game/_UI/Icon_Rune/...", + "coreLine": ["10101", "10102", "10103"], + "sub1Line": ["10201", "10202"], + "sub2Line": ["10301", "10302"] + } +} +``` + +**그룹 목록**: +1. **1000001** - 전투 (Battle): Core 3개, Sub1 2개, Sub2 2개 +2. **2000001** - 스킬 (Skill): Core 3개, Sub1 3개, Sub2 2개 +3. **3000001** - 장비 (Equipment): Core 3개, Sub1 2개, Sub2 3개 +4. **4000001** - 보조 (Assist): Core 2개, Sub1 2개, Sub2 2개 +5. **5000001** - 모험 (Adventure): Core 3개, Sub1 2개, Sub2 4개 + +#### DT_Rune DataTable 어셋 + +각 룬의 레벨별 상세 데이터를 정의합니다: + +```json +{ + "RowName": "1020105", + "Data": { + "runeSet": "10201", + "level": 5, + "icon": "/Game/_UI/Icon_Rune/RuneIcon_Rage.RuneIcon_Rage", + "runeName": "분노", + "desc": "물리 피해 {Value0}% 증가", + "descValue": [10], + "attributeModifies": [ + { + "attribute": { + "attributeName": "PhysicalDamagePer", + "attribute": "/Script/WorldStalker.PassiveSet:PhysicalDamagePer" + }, + "value": 10 + } + ], + "ability": "None", + "unlockGold": 0, + "unlockSkillPoint": 20 + } +} +``` + +**주요 필드**: +- `runeSet`: 룬 ID (5자리) +- `level`: 룬 레벨 (1~5) +- `runeName`: 룬 이름 +- `desc`: 설명 (UI 표시용, {Value0}, {Value1} 플레이스홀더 사용) +- `descValue`: 설명에 대입할 수치 배열 +- `attributeModifies`: 직접 수정할 속성 목록 +- `ability`: Blueprint Ability 경로 (복잡한 효과용) +- `unlockSkillPoint`: 해당 레벨 해금에 필요한 스킬 포인트 + +### 5.7 룬 시스템 활용 전략 + +#### 공격력 극대화 빌드 + +**목표**: BaseDamage와 스킬 피해를 최대한 증가 + +**Main: 스킬 그룹** +- 20101 저주 (조건부 피해) +- 20201 파괴 (+10% 스킬 피해) +- 20301 명상 (+70% 마나 회복) + +**Sub: 전투 그룹** +- 10201 분노 (+10% 물리 피해) 또는 10301 폭풍 (+10% 마법 피해) +- 10103 공략 (+20% 머리 공격) + +**효과**: 스킬 피해 +10%, 물리/마법 피해 +10%, 머리 공격 +20% + +#### 생존력 극대화 빌드 + +**Main: 전투 그룹** +- 10101 충전 (+30% 궁극기 회복) +- 10202 방패 (+7% 물리 저항) +- 10302 수호 (+7% 마법 저항) + +**Sub: 보조 그룹** +- 40201 면역 (물약 사용 시 저항 +20%) +- 40301 효율 (물약 효과 +50%) + +**효과**: 저항 대폭 증가, 물약 효율 극대화, 궁극기 빠른 충전 + +#### 스킬 연타 빌드 + +**Main: 스킬 그룹** +- 20201 파괴 (+10% 스킬 피해) +- 20202 왜곡 (+25% 쿨타임 감소) +- 20203 절약 (-50% 마나 소모) + +**Sub: 스킬 그룹 Sub2** +- 20301 명상 (+70% 마나 회복) +- 20302 영창 (+30% 시전 속도) + +**효과**: 쿨타임 -25%, 마나 소모 -50%, 마나 회복 +70%, 시전 속도 +30% + +--- + +**룬 시스템 핵심 요약**: +1. 5개 그룹 × 3개 라인으로 구성된 38개 룬 세트 +2. Main(3룬) + Sub(2룬)으로 총 5개 룬 장착 +3. 직접 속성 수정 또는 Ability를 통한 조건부 효과 +4. 전투 로직의 여러 단계에 직접적인 영향 +5. BaseDamage, 저항, 스킬 코스트 등 핵심 수치 조정 + +--- + +## 6. 전투 관련 코드 목록 + +### 6.1 데미지 계산 시스템 + +#### WSDamageCalculation.cpp (1238라인) + +**역할**: 모든 일반 공격 및 스킬 데미지 계산의 핵심 로직 실행 + +**주요 함수**: +- `Execute_Implementation` (라인 251-1238): 전체 11단계 계산 흐름 제어 +- `DamageStatics()`: Attribute 캡처 정의 + +**주요 계산 단계별 코드 위치**: + +1. **BaseDamage 계산**: +```cpp +// 라인 316-322: Level 배율 적용 +PhysicalDamage = PhysicalDamage * Level; +MagicalDamage = MagicalDamage * Level; + +// 라인 543-613: 공격 타입별 BaseDamage 결정 +if (EAttackType == EWSAttackType::Normal) + BaseDamage = PhysicalDamage; +else if (EAttackType == EWSAttackType::PhysicalSkill) + BaseDamage = PhysicalDamage; +else if (EAttackType == EWSAttackType::MagicalSkill) + BaseDamage = MagicalDamage; + +// 라인 620-685: 던전 룰 배율 +switch (DungeonRule) { + case EDungeonRule::EnemyAtkUp: BaseDamage *= 1.4f; break; + // ... +} +``` + +2. **HitBox 판정** (라인 470-510): +```cpp +IsFrontAttack = UWSAbilityBlueprintLibrary::IsFrontAttack(EffectCauser, Target, bUseOwnerRotation); +if (HitResult->BoneName == FName(TEXT("b_Head"))) +{ + IsHeadShot = true; + HitBoxRate = 1.2f + HeadAttackDamagePer * 0.01f; + HitBoxRate -= 0.5f * HeadShotDamReducePer * 0.01f; +} +``` + +3. **치명타 판정** (라인 512-527): +```cpp +if (bUseCritical && FMath::FRandRange(0.0f, 100.0f) < CriticalPer) +{ + isCritical = true; + CriticalDamageRate = (CriticalDamagePer * 0.01f) + (FMath::Rand() * CriticalDamageRange); +} +``` + +4. **저항 및 최종 피해** (라인 706-718): +```cpp +ResistancePer1 = FMath::Min(ResistancePer1, 75.0f); +ResistancePer2 = FMath::Min(ResistancePer2, 75.0f); + +Damage = Floor(BaseDamage * HitBoxRate * (SkillPer * 0.01) * + ((1 - ResistancePer1 * 0.01) * (1 - ResistancePer2 * 0.01) * (1 - DamageReductionPer * 0.01)) * + CriticalDamageRate * (1 - TakeDamageReductionPer * 0.01) * (1 + TakeDamageIncreasePer * 0.01)); +``` + +5. **Shield/Armor/HP 적용** (라인 883-1047): +```cpp +// Shield 먼저 소모 +ShieldDamage = Clamp(DamageNoResist, 0, Shield); +if (Shield > 0) { + Damage = Clamp(Damage - ShieldDamage, 0, Damage); + Shield -= ShieldDamage; +} + +// Armor Gating +if (Armor / ArmorMax > 0.5) { + DamageGating = (Armor - Damage) - (ArmorMax * 0.5); + if (DamageGating < 0) Damage += DamageGating; +} +``` + +**참조 섹션**: 1.2 일반 공격/스킬 데미지 계산 흐름 + +--- + +#### WSHealCalculation.cpp (126라인) + +**역할**: 힐 스킬의 회복량 계산 + +**주요 함수**: +- `Execute_Implementation` (라인 49-125): 힐 계산 및 적용 + +**힐 계산 공식** (라인 95-98): +```cpp +float HealMagnitude = InComingHeal * Level; +// 힐 = 기본 힐량 + (마법공격력 × 스킬 계수) × (인트배율) +HealMagnitude = HealMagnitude + (MagicalDamage * (1.0f + (SkillDamagePer * 0.01f))) * (MagicalSkillPer * 0.01f); +``` + +**과치유 방지** (라인 106-108): +```cpp +float TargetMaxHP = TargetASC->GetNumericAttribute(UCharacterSet::GetHPMaxAttribute()); +float TargetHP = TargetASC->GetNumericAttribute(UCharacterSet::GetHPAttribute()); +float HealAmount = FMath::Min(HealMagnitude, TargetMaxHP - TargetHP); +``` + +**힐 무효 처리** (라인 110-113): +```cpp +if (TargetASC->HasMatchingGameplayTag(FGameplayTag::RequestGameplayTag("Effect.IgnoreHeal"))) +{ + HealAmount = 0.0f; +} +``` + +**궁극기 게이지 충전** (라인 118-123): +```cpp +if (!SourceASC->HasMatchingGameplayTag(FGameplayTag::RequestGameplayTag("Ability.Ultimate"))) +{ + InstigatorCharacter->CharacterSet->SetUltimateCurrentValue( + FMath::Min(HealAmount + UltimateCurrentvalue, UltimateMaxValue)); +} +``` + +**참조 섹션**: 1.4 힐 계산 + +--- + +### 6.2 속성 세트 (Attribute Sets) + +#### WSAttributeSet.h + +**역할**: 모든 Attribute Set의 베이스 클래스 + +**주요 기능**: +- Gameplay Attribute 시스템의 기본 구조 제공 +- Replication 설정 +- Attribute 변경 감지 및 클램핑 + +--- + +#### PrimarySet.h + +**역할**: 1차 스탯 (Str, Dex, Int, Con, Wis) 정의 + +**속성**: +```cpp +UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Str) +float Str = 0; // 힘 + +UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Dex) +float Dex = 0; // 민첩 + +UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Int) +float Int = 0; // 지능 + +UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Con) +float Con = 0; // 체질 + +UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Wis) +float Wis = 0; // 지혜 +``` + +**특징**: +- 총합 75 제한 +- 커브 테이블을 통해 2차 스탯 피해율로 변환 +- 장비, 패시브 스킬로 추가 증가 가능 + +**참조 섹션**: 2.1 1차 스탯 + +--- + +#### CharacterSet.h + +**역할**: 2차 스탯 (HP, Damage, Defense, Resistance 등) 정의 + +**주요 속성 그룹**: +1. **기본 스탯**: HP, MP, Stamina, Shield +2. **피해**: PhysicalDamage, MagicalDamage, 각종 피해율 +3. **방어**: Defense, Armor, 저항 +4. **특수**: Breakdown, Ultimate, Critical + +**초기화** (CharacterSet.cpp:8-82): +```cpp +void UCharacterSet::InitData(FCharacterStatData Data, UPrimarySet* PrimarySet, UPassiveSet* PassiveSet, bool HasAuthority) +{ + SetPhysicalDamage(Data.PhysicalDamage); + SetMagicalDamage(Data.MagicalDamage); + SetDefense(Data.Defense); + // ... +} +``` + +**참조 섹션**: 2.2 2차 스탯 + +--- + +#### PassiveSet.h (490라인) + +**역할**: 패시브 스탯 (백분율 수정자) 정의 + +**속성 구성**: +- **공통 패시브** (라인 125-260): 36개 속성 + - 피해 증가율 (PhysicalDamagePer, MagicalDamagePer 등) + - 방어/속도/HP/MP 증가율 + - 스킬 코스트/쿨다운 감소율 + - 인터랙션 시간 조정 + +- **캐릭터별 전용 퍽** (라인 263-461): + - Hilda: 7개 전용 퍽 + - Urud: 6개 전용 퍽 + - Nave: 10개 전용 퍽 + - Baran: 8개 전용 퍽 + - Rio: 8개 전용 퍽 + - Clad: 5개 전용 퍽 + +- **범용 퍽 슬롯** (라인 466-488): Perk1~Perk8 + +**적용 방식**: +```cpp +// WSCharacterPlayer.cpp:4018-4022 +float PhysicalDamagePer = AbilitySystemComponent->GetNumericAttribute( + UPassiveSet::GetPhysicalDamagePerAttribute()) * 0.01f; +AttrValueMap[UCharacterSet::GetPhysicalDamageAttribute()] += + AttrValueMap[UCharacterSet::GetPhysicalDamageAttribute()] * PhysicalDamagePer; +``` + +**참조 섹션**: 2.3 패시브 스탯 + +--- + +#### EnemySet.h + +**역할**: 몬스터 전용 스탯 정의 + +**주요 속성**: +```cpp +UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_CombinationArmor) +float CombinationArmor = 0; // 몬스터 전용 콤비네이션 아머 +``` + +**특징**: +- 몬스터에게만 적용되는 특수 속성 +- CombinationArmor: 다중 부위 아머 시스템 + +--- + +### 6.3 캐릭터 시스템 + +#### WSCharacterBase.cpp/h + +**역할**: 플레이어와 적 캐릭터의 공통 베이스 클래스 + +**주요 기능**: + +1. **Ability System 초기화**: +```cpp +void AWSCharacterBase::InitializeAbilitySystem() +{ + AbilitySystemComponent->InitAbilityActorInfo(this, this); + PrimarySet->InitData(*StatDataRow); + CharacterSet->InitData(*StatDataRow, PrimarySet, PassiveSet, HasAuthority()); +} +``` + +2. **반죽음 판정** (WSCharacterBase.h:178-180): +```cpp +bool CanHalfDie() const +{ + // 파티원이 있고 생존한 파티원이 있을 때만 반죽음 가능 + return HasPartyMembers() && HasAlivePartyMembers(); +} +``` + +3. **Hit Reaction 처리**: +- 피격 모션 재생 +- 넉백/스턴 처리 +- Breakdown 게이지 증가 + +**참조 섹션**: 4.3 반죽음 + +--- + +#### WSCharacterPlayer.cpp/h + +**역할**: 플레이어 캐릭터 전용 로직 + +**주요 기능**: + +1. **1차 스탯 → 2차 스탯 변환** (라인 3214-3245): +```cpp +void AWSCharacterPlayer::UpdatePrimaryStats() +{ + FRealCurve* NormalDamagePerCurve = WSData->PrimaryStat->FindSimpleCurve(FName(TEXT("NormalDamagePer")), DataContextString); + CharacterSet->SetNormalDamagePer(NormalDamagePerCurve->Eval(PrimarySet->GetStr())); + + FRealCurve* PhysicalSkillPerCurve = WSData->PrimaryStat->FindSimpleCurve(FName(TEXT("PhysicalSkillPer")), DataContextString); + CharacterSet->SetPhysicalSkillPer(PhysicalSkillPerCurve->Eval(PrimarySet->GetStr())); + + FRealCurve* MagicalSkillPerCurve = WSData->PrimaryStat->FindSimpleCurve(FName(TEXT("MagicalSkillPer")), DataContextString); + CharacterSet->SetMagicalSkillPer(MagicalSkillPerCurve->Eval(PrimarySet->GetInt())); +} +``` + +2. **장비 효과 적용** (라인 3566-3860): +```cpp +void AWSCharacterPlayer::GiveEquip(FEquipItemData* EquipItem, int CacheIndex) +{ + // 장비 랜덤 옵션 적용 + AttrValueMap.Add(UCharacterSet::GetPhysicalDamageAttribute(), + FItemHelper::CalculateOption(EquipItem, EItemOption::PhysicalDamageInc)); + + // 패시브 스탯 백분율 적용 + float PhysicalDamagePer = AbilitySystemComponent->GetNumericAttribute( + UPassiveSet::GetPhysicalDamagePerAttribute()) * 0.01f; + AttrValueMap[UCharacterSet::GetPhysicalDamageAttribute()] += + AttrValueMap[UCharacterSet::GetPhysicalDamageAttribute()] * PhysicalDamagePer; +} +``` + +3. **스킬/패시브 관리**: +- 스킬 슬롯 관리 +- 패시브 스킬 활성화 +- 퍽 시스템 적용 + +**참조 섹션**: 1.1 BaseDamage 계산 과정 + +--- + +#### WSCharacterEnemy.cpp/h + +**역할**: 적 캐릭터 전용 로직 + +**주요 기능**: +- AI 연동 +- Breakdown 시스템 +- 몬스터 등급별 스탯 조정 +- CombinationArmor 처리 + +--- + +### 6.4 스킬 데이터 구조 + +#### SkillDataRow.h + +**역할**: 스킬 데이터 테이블 구조 정의 + +**주요 열거형**: + +1. **공격 타입** (라인 13-21): +```cpp +UENUM(BlueprintType) +enum class EWSAttackType : uint8 +{ + Normal = 0, // 일반 공격 + PhysicalSkill = 1, // 물리 스킬 + MagicalSkill = 2, // 마법 스킬 + FixedSkill = 3, // 고정 스킬 (저항 무시) + None = 4 +}; +``` + +2. **원소 타입** (라인 24-33): +```cpp +UENUM(BlueprintType) +enum class EWSElementType : uint8 +{ + None = 0, + Fire = 1, // 화염 + Poison = 2, // 독 + Water = 3, // 물 + Lightning = 4, // 번개 + Holy = 5, // 빛 + Dark = 6 // 암흑 +}; +``` + +**스킬 데이터 구조**: +```cpp +USTRUCT(BlueprintType) +struct FSkillDataRow : public FTableRowBase +{ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + float BaseDamage; // 기본 피해량 + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + EWSAttackType AttackType; // 공격 타입 + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + EWSElementType ElementType; // 원소 타입 + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + float ManaCost; // 마나 소비 + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + float CooldownTime; // 쿨다운 시간 + + // ... 기타 스킬 속성 +}; +``` + +**참조 섹션**: 3.1 공격 타입, 3.2 원소 타입 + +--- + +### 6.5 발사체 시스템 + +#### WSProjectileBase.cpp/h + +**역할**: 화살, 마법 투사체 등 원거리 공격 구현 + +**주요 기능**: +1. 투사체 물리 시뮬레이션 +2. 충돌 감지 및 히트 판정 +3. 관통, 폭발 등 특수 효과 +4. RangedType 설정 + +**히트 처리**: +```cpp +void AWSProjectileBase::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, + UPrimitiveComponent* OtherComp, FVector NormalImpulse, + const FHitResult& Hit) +{ + // RangedType = true 설정 + // GameplayEffect 적용 (WSDamageCalculation 호출) + // RangedResistancePer 저항 적용됨 +} +``` + +**참조 섹션**: 3.2 원거리 타입 + +--- + +### 6.6 기타 데이터 구조 + +#### CharacterStatDataRow.h + +**역할**: 캐릭터 기본 스탯 DataTable 구조 + +**주요 필드** (라인 63-198): +```cpp +USTRUCT(BlueprintType) +struct FCharacterStatData : public FTableRowBase +{ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + float Str = 0; // 1차 스탯 + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + float PhysicalDamage = 0; // 캐릭터 기본 공격력 + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + float MagicalDamage = 0; // 캐릭터 기본 마법 공격력 + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + float Defense = 0; // 캐릭터 기본 방어력 + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + float BreakdownMax = -1.0f; // 브레이크다운 최대치 + + // ... 기타 기본 스탯 +}; +``` + +**사용 위치**: +- `WSCharacterPlayer::InitializeAbilitySystem` (라인 1059) +- `PrimarySet::InitData` +- `CharacterSet::InitData` + +**참조 섹션**: 1.1 BaseDamage 계산 과정 (1단계) + +--- + +#### DataTable.json + +**역할**: 언리얼 에디터의 DataTable 어셋을 JSON 형식으로 익스포트한 파일 + +> **참고**: 이 파일은 "Asset Export to JSON" 에디터 확장 기능을 통해 생성된 결과물입니다. +> 언리얼 엔진의 DataTable 어셋(DT_Rune, DT_RuneGroup 등)을 LLM이 분석할 수 있도록 텍스트 기반 JSON 형식으로 변환한 것입니다. + +**룬 시스템 관련 DataTable 어셋**: +- **DT_RuneGroup**: 5개 룬 그룹 구조 정의 + - Core Line, Sub 1Line, Sub 2Line 구성 + - 각 그룹별 선택 가능 룬 목록 + +- **DT_Rune**: 38개 룬 세트의 레벨별 데이터 + - 룬 ID (runeSet), 레벨 (1~5) + - 효과 설명 (desc, descValue) + - 직접 속성 수정 (attributeModifies) 또는 Ability 참조 + - 해금 조건 (unlockSkillPoint) + +```json +{ + "runeSet": "10201", + "level": 5, + "runeName": "분노", + "desc": "물리 피해 {Value0}% 증가", + "descValue": [10], + "attributeModifies": [ + { + "attribute": { + "attributeName": "PhysicalDamagePer", + "attribute": "/Script/WorldStalker.PassiveSet:PhysicalDamagePer" + }, + "value": 10 + } + ] +} +``` + +**참조 섹션**: 5.6 룬 데이터 테이블 구조 + +--- + +#### WSGameplayAbility.h/cpp + +**역할**: Gameplay Ability 베이스 클래스, **룬 데이터 조회 기능 포함** + +**룬 관련 함수** (WSGameplayAbility.h:209, 268): +```cpp +// 룬 데이터 조회 함수 +UFUNCTION(BlueprintCallable, Category = "WorldStalker") +bool GetRuneDataRowBySetID(FName RuneSetId, FRuneDataRow& OutDataRow); + +// 룬 데이터 캐시 +FRuneDataRow* CachedRuneDataRow; +``` + +**구현** (WSGameplayAbility.cpp:1525-1550): +- RuneSet ID와 Ability 레벨로 룬 데이터 검색 +- 캐싱을 통한 성능 최적화 +- Blueprint에서 룬 효과 적용 시 사용 + +**참조 섹션**: 5.5.3 룬 데이터 조회 시스템 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f188d8a --- /dev/null +++ b/README.md @@ -0,0 +1,256 @@ +# DS-전투분석 저장소 + +던전 스토커즈(DungeonStalkers) 전투 시스템 종합 분석 저장소입니다. + +## 프로젝트 개요 + +### 목적 + +본 프로젝트는 언리얼 엔진 5.5.4 기반의 던전 스토커즈 전투 시스템을 체계적으로 분석하고 문서화하여 다음 목표를 달성합니다: + +- **밸런스 분석**: 10명 스토커의 전투 능력 비교 및 밸런스 검증 +- **시스템 문서화**: Gameplay Ability System 기반 전투 로직 상세 분석 +- **정기 모니터링**: 패치/업데이트 시 변경사항 추적 및 영향도 분석 +- **팀 공유**: 기획자, 프로그래머, QA가 공통으로 참고할 수 있는 기술 문서 + +### 분석 대상 + +- **10명의 스토커**: Hilda, Urud, Nave, Baran, Rio, Clad, Rene, Sinobu, Lian, Cazimord +- **전투 시스템 요소**: + - 기본 공격 타이밍 및 피해 배율 + - 스킬 시스템 (Gameplay Ability System) + - 캔슬 메커니즘 (Activation Order Group + ANS_SkillCancel_C) + - 애니메이션 노티파이 시스템 + - 캐릭터 스탯 및 속성 + +## 분석 방법론 + +### 1. 데이터 추출 (Unreal → JSON) + +언리얼 에디터의 커스텀 익스포터를 사용하여 게임 에셋을 JSON 형식으로 변환합니다. + +``` +Unreal Engine Assets → Custom Exporter → JSON Files +``` + +**익스포트 대상**: +- `DataTable` → DT_CharacterStat, DT_Skill, DT_CharacterAbility 등 +- `AnimMontage` → 모든 캐릭터 애니메이션 몽타주 +- `Blueprint` → GA_* (Gameplay Ability) 블루프린트 +- `CurveTable` → 각종 커브 데이터 + +**익스포트 방법**: +1. 언리얼 에디터에서 Content Browser 열기 +2. 분석할 에셋 선택 +3. 우클릭 → `Export to JSON` (커스텀 익스포터) +4. 출력 폴더에 JSON 파일 생성 + +### 2. LLM 기반 분석 + +생성된 JSON 파일을 Claude (LLM)에 입력하여 자동 분석합니다. + +**분석 프로세스**: + +``` +JSON Files → Claude Code → Analysis Document + ↓ + Python Scripts (보조 분석) +``` + +**LLM의 역할**: +- 대용량 JSON 데이터 파싱 및 패턴 인식 +- 스토커별 데이터 비교 분석 +- 전투 로직 추론 및 시스템 메커니즘 분석 +- Markdown 형식의 기술 문서 자동 생성 + +**장점**: +- 수작업 대비 100배 이상 빠른 분석 속도 +- 일관된 형식의 문서 생성 +- 복잡한 크로스 레퍼런스 추적 자동화 + +### 3. 검증 및 문서화 + +분석 결과를 검토하고 최종 문서를 생성합니다. + +## 폴더 구조 + +``` +DS-전투분석_저장소/ +├── README.md # 본 문서 +│ +├── 분석결과/ # 분석 결과물 +│ └── 20251023/ # 날짜별 분석 +│ └── DS-전투시스템_종합분석.md +│ +├── 원본데이터/ # JSON 원본 데이터 (샘플) +│ └── 20251023/ +│ ├── 샘플_DataTable.json +│ ├── 샘플_AnimMontage.json +│ └── 샘플_Blueprint.json +│ +└── 분석도구/ # Python 분석 스크립트 + ├── extract_skill_cancel_windows.py + ├── analyze_character_stats.py + └── extract_activation_order_groups.py +``` + +## 정기 분석 수행 가이드 + +새로운 분석을 수행하려면 다음 단계를 따르세요. + +### Step 1: JSON 데이터 익스포트 + +**언리얼 에디터에서 수행**: + +``` +1. Content Browser에서 다음 폴더들을 선택: + - /Game/Blueprints/DataTable/ + - /Game/_Art/_Character/PC/*/AnimMontage/ + - /Game/Blueprints/Abilities/GA_Skill_*/ + +2. 우클릭 → Export to JSON + +3. 출력 폴더 선택: DS-전투밸런스_분석자료/[오늘날짜]/ + 예: DS-전투밸런스_분석자료/20251024_153000/ + +4. 익스포트 완료 확인: + ✓ DataTable.json + ✓ AnimMontage.json + ✓ Blueprint.json + ✓ CurveTable.json +``` + +### Step 2: LLM 분석 실행 + +**Claude Code 사용**: + +1. Claude Code CLI 실행 +2. 다음 프롬프트 입력: + +``` +"DS-전투밸런스_분석자료/[날짜]/" 폴더의 JSON 파일들을 분석하여 +전투 시스템 종합 분석 문서를 작성해주세요. + +분석 항목: +- 10명 스토커별 기본 공격 타이밍 및 피해 배율 +- 스킬별 Activation Order Group 값 +- 애니메이션 캔슬 윈도우 (ANS_SkillCancel_C) +- 캐릭터 스탯 비교 + +이전 분석 문서를 참고하여 동일한 형식으로 작성하고, +변경사항이 있다면 별도로 표시해주세요. +``` + +3. 생성된 문서를 검토하고 수정 + +### Step 3: 결과 저장 + +```bash +# 새 폴더 생성 +mkdir -p 분석결과/[날짜] + +# 분석 문서 저장 +# Claude가 생성한 문서를 분석결과/[날짜]/DS-전투시스템_종합분석.md로 저장 + +# (선택) 원본 JSON 샘플 저장 +# 주요 에셋 몇 개만 추출하여 원본데이터/[날짜]/에 저장 +``` + +### Step 4: 변경사항 추적 + +**이전 분석과 비교**: + +```bash +# diff 도구로 변경 확인 +diff 분석결과/20251023/DS-전투시스템_종합분석.md \ + 분석결과/20251024/DS-전투시스템_종합분석.md +``` + +**주요 확인 사항**: +- ActivationOrderGroup 변경 (밸런스 조정) +- 기본 공격 타이밍 변경 (애니메이션 수정) +- AddNormalAttackPer 변경 (피해 배율 조정) +- 새로운 스킬 추가/삭제 + +## 분석 도구 사용법 + +### 1. 스킬 캔슬 윈도우 추출 + +```bash +python 분석도구/extract_skill_cancel_windows.py \ + 원본데이터/20251023/AnimMontage.json +``` + +**출력 예시**: +``` +AM_PC_Hilda_B_Skill_SwordStrike + 캔슬 구간: 1.300s ~ 1.800s (지속: 0.500s) +``` + +### 2. 캐릭터 스탯 분석 + +```bash +python 분석도구/analyze_character_stats.py \ + 원본데이터/20251023/DataTable.json +``` + +**출력 예시**: +``` +이름 직업 STR DEX INT CON WIS +힐다 전사 20 15 10 20 10 +우르드 원거리 15 20 10 15 15 +``` + +### 3. Activation Order Group 추출 + +```bash +python 분석도구/extract_activation_order_groups.py \ + 원본데이터/20251023/Blueprint.json +``` + +**출력 예시**: +``` +Hilda: + Group 4: Bash, SwordStrike + Group 0: BloodMoon_Active, SteelBlocking +``` + +## 최신 분석 결과 + +**날짜**: 2025-10-23 +**분석 문서**: [분석결과/20251023/DS-전투시스템_종합분석.md](분석결과/20251023/DS-전투시스템_종합분석.md) + +**주요 발견**: +- **공격 속도 1위**: Rio (3.867s) +- **가장 높은 피해**: Baran, Clad (평타 +30~50%) +- **궁극기 보유**: Nave, Baran, Sinobu, Cazimord (4명만) +- **가장 다양한 우선순위**: Cazimord (Group 0, 2, 3, 4, 9 모두 사용) + +## 기술 스택 + +- **게임 엔진**: Unreal Engine 5.5.4 +- **에셋 익스포터**: Custom Unreal Editor Plugin +- **분석 LLM**: Claude 3.5 Sonnet (Claude Code) +- **보조 분석**: Python 3.x +- **문서 형식**: Markdown + +## 참고 자료 + +### 내부 문서 +- [DS-전투시스템_종합분석.md](분석결과/20251023/DS-전투시스템_종합분석.md) - 최신 분석 결과 +- [CLAUDE.md](../CLAUDE.md) - 프로젝트 전체 개요 + +### 외부 참고 +- [Unreal Engine 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/) + +## 팀원 기여 + +분석 결과에 피드백이나 추가 분석 요청이 있으시면: +1. 이슈 등록 (Git Issue) +2. 또는 디스코드 공식 커뮤니티에 공유 + +--- + +**마지막 업데이트**: 2025-10-23 +**담당자**: AI-assisted Analysis Team diff --git a/분석결과/20251023/DS-전투시스템_종합분석.md b/분석결과/20251023/DS-전투시스템_종합분석.md new file mode 100644 index 0000000..12b8745 --- /dev/null +++ b/분석결과/20251023/DS-전투시스템_종합분석.md @@ -0,0 +1,2017 @@ +# DS-전투시스템 종합분석 + +## 문서 개요 + +본 문서는 언리얼 엔진 에셋(DataTable, AnimMontage, Blueprint, CurveTable)을 JSON으로 익스포트하여 분석한 전투 로직 시스템에 대한 기술 문서입니다. + +**분석 대상 스토커**: Hilda, Urud, Nave, Baran, Rio, Clad, Rene, Sinobu, Lian, Cazimord (10명) + +**작성 일자**: 2025-10-23 +**익스포트 데이터**: `DS-전투밸런스_분석자료/20251023_114317/` + +--- + +## 목차 + +1. [개요 및 Asset Export 시스템](#1-개요-및-asset-export-시스템) +2. [DataTable 구조](#2-datatable-구조) +3. [AnimMontage 타이밍 시스템](#3-animmontage-타이밍-시스템) +4. [Blueprint Ability 및 캔슬 시스템](#4-blueprint-ability-및-캔슬-시스템) +5. [GameplayEffect 메커니즘](#5-gameplayeffect-메커니즘) +6. [DT_Skill 특수 케이스](#6-dt_skill-특수-케이스) +7. [코드-어셋 통합 흐름](#7-코드-어셋-통합-흐름) +8. [JSON 사용 가이드](#8-json-사용-가이드) + +--- + +## 1. 개요 및 Asset Export 시스템 + +### 1.1 Asset Export to JSON 기능 + +언리얼 엔진 에셋은 기본적으로 바이너리 형식으로 저장되어 LLM이나 외부 분석 도구가 직접 접근할 수 없습니다. 이를 해결하기 위해 **Asset Export to JSON** 에디터 확장 기능을 개발하였습니다. + +**참고 문서**: `DS-전투밸런스_분석자료/DS-Asset_Export_to_JSON.md` + +#### 주요 기능 + +- **지원 에셋 타입**: DataTable, Blueprint, AnimMontage, CurveTable +- **프로젝트 설정 통합**: `편집 → 프로젝트 설정 → 플러그인 → Asset Export to JSON` +- **타임스탬프 익스포트**: 익스포트 히스토리 보관을 위한 타임스탬프 폴더 생성 +- **완전한 데이터 추출**: + - DataTable: 모든 행/열 데이터 + - Blueprint: 변수, 함수, 컴포넌트, 이벤트 그래프 노드 구조 + - AnimMontage: 섹션, 노티파이, 커스텀 프로퍼티, 슬롯 애니메이션 + - CurveTable: RichCurves 및 SimpleCurves 키 데이터 + +#### 익스포트 실행 + +``` +툴 → WorldStalker → Export Assets to JSON +``` + +#### 출력 위치 + +``` +Content/Exports/20251023_114317/ +├── DataTable.json (1,151 에셋) +├── Blueprint.json (1,151 에셋) +├── AnimMontage.json (809 에셋) +└── CurveTable.json (8 에셋) +``` + +### 1.2 전투 로직 분석 범위 + +본 문서에서는 익스포트된 JSON 데이터를 기반으로 다음 전투 시스템을 분석합니다: + +#### 스토커별 전투 데이터 + +| 스토커 | 클래스 | 주요 특징 | +|--------|--------|-----------| +| **Hilda** | 전사 | Counter 스킬, 방패 방어 | +| **Urud** | 원거리 | Reload 시스템, 궁극기 'Explosion' 범위 피해 | +| **Nave** | 마법사 | 캐스팅 스킬, 궁극기 'Liberation' (DT_Skill 피해) | +| **Baran** | 전사 | 중무장 탱커, Pulling 체인 메커니즘 | +| **Rio** | 암살자 | Chain Score 시스템 (최대 3 스택) | +| **Clad** | 성직자 | 힐링 및 언데드 특효 | +| **Rene** | 소환사 | Spirit 소환, Lifesteal 효과 | +| **Sinobu** | 닌자 | Shuriken 충전 시스템, 'Swap' 인술 (텔레포트) | +| **Lian** | 레인저 | Reload 시스템, Precision Aim (조준 시스템) | +| **Cazimord** | 전사 | Flash 스킬 스택, Parrying 시스템 | + +#### 분석 계층 + +``` +스토커 전투 시스템 +│ +├── DataTable Layer (밸런스 데이터) +│ ├── DT_CharacterStat (기본 스탯, 스킬 목록) +│ ├── DT_Skill (스킬 데이터, 피해 배율, 쿨타임) +│ └── DT_CharacterAbility (기본 공격 어빌리티 매핑) +│ +├── AnimMontage Layer (타이밍 데이터) +│ ├── 기본 공격 몽타주 (AM_PC_[Stalker]_B_Attack_*) +│ ├── 스킬 몽타주 (스킬별 애니메이션) +│ └── 노티파이 시스템 +│ ├── ANS_AttackState_C (공격 상태, 피해 배율) +│ ├── AnimNotifyState_AttackWithEquip (히트 판정 타이밍) +│ ├── ANS_SkillCancel_C (스킬 캔슬 윈도우) +│ └── AN_SetAutoTarget_C (자동 타게팅) +│ +├── Blueprint Ability Layer (로직 구현) +│ ├── GA_Attack (기본 공격) +│ ├── GA_Skill_* (스토커별 스킬 구현) +│ ├── Activation Order Group (우선순위 캔슬 시스템) +│ └── 특수 시스템 (Reload, Chain Score, Parrying 등) +│ +└── GameplayEffect Layer (효과 적용) + ├── GE_Attack_* (피해 효과) + ├── GE_Skill_* (스킬 효과) + └── GE_Buff/Debuff_* (버프/디버프) +``` + +### 1.3 JSON 참조 규칙 + +**중요**: JSON 파일은 에디터에서 익스포트한 결과물로, 소스 코드처럼 고정된 라인 번호가 없습니다. + +#### 올바른 참조 방법 + +✅ **Asset 이름으로 참조** +``` +DT_CharacterStat 어셋의 "hilda" 행 +AM_PC_Hilda_B_Skill_SwordStrike 몽타주 +GA_Skill_Hilda_SwordStrike_C 블루프린트 +``` + +❌ **라인 번호로 참조하지 않음** +``` +DataTable.json:358143-358192 (X) +``` + +#### JSON 구조 예시 + +**DataTable.json**: +```json +{ + "AssetName": "DT_CharacterStat", + "RowStructure": "CharacterStatData", + "Rows": [ + { + "RowName": "hilda", + "Data": { + "name": "힐다", + "defaultSkills": ["SK100201", "SK100202", "SK100204"], + "subSkill": "SK100101", + "ultimateSkill": "SK100301" + } + } + ] +} +``` + +**AnimMontage.json**: +```json +{ + "AssetName": "AM_PC_Hilda_B_Skill_SwordStrike", + "SequenceLength": 1.8, + "AnimNotifies": [ + { + "NotifyStateClass": "ANS_SkillCancel_C", + "TriggerTime": 1.3, + "Duration": 0.5 + } + ] +} +``` + +--- + +## 2. DataTable 구조 + +### 2.1 DT_CharacterStat (스토커 기본 스탯 및 스킬 매핑) + +DT_CharacterStat은 각 스토커의 기본 스탯과 사용 가능한 스킬 목록을 정의합니다. + +#### 구조 + +| 필드 | 타입 | 설명 | +|------|------|------| +| `name` | String | 스토커 이름 (한글) | +| `jobName` | String | 직업 (전사, 원거리, 마법사 등) | +| `str` | Int | 힘 (물리 공격력 영향) | +| `dex` | Int | 민첩 (공격 속도, 크리티컬 영향) | +| `int` | Int | 지능 (마법 공격력 영향) | +| `con` | Int | 체력 (HP, 방어력 영향) | +| `wis` | Int | 지혜 (마나, 마법 방어력 영향) | +| `hP` | Float | 기본 HP | +| `mP` | Float | 기본 MP | +| `manaRegen` | Float | 마나 재생 속도 | +| `defaultSkills` | Array | 기본 스킬 ID 목록 (3-4개) | +| `subSkill` | String | 서브 스킬 ID (특수 스킬, 쿨타임 0초) | +| `ultimateSkill` | String | 궁극기 스킬 ID | + +#### 스토커별 스탯 분포 + +**전체 스탯 합계**: 75 포인트 (모든 스토커 동일) + +##### 힘 중심 스토커 (STR 20+) + +**Hilda** (전사): +``` +STR: 20, DEX: 15, INT: 10, CON: 20, WIS: 10 +HP: 100, MP: 50, Mana Regen: 0.2 +Skills: SK100201 (Sword Strike), SK100202 (Counter), SK100204 (Provoke) +Sub: SK100101 (Blocking) +Ultimate: SK100301 (Blood Moon) +``` + +**Baran** (전사): +``` +STR: 25, DEX: 10, INT: 5, CON: 25, WIS: 10 +HP: 100, MP: 50, Mana Regen: 0.2 +Skills: SK130204 (Pulling), SK130203 (Smash), SK130206 (Sword Stab) +Sub: SK130101 (Blocking) +Ultimate: SK130301 (Rock Breaker) +``` + +##### 민첩 중심 스토커 (DEX 20+) + +**Urud** (원거리): +``` +STR: 15, DEX: 20, INT: 10, CON: 15, WIS: 15 +HP: 100, MP: 50, Mana Regen: 0.2 +Skills: SK110205 (Multi Shot), SK110204 (Poison Arrow), SK110201 (Make Trap), SK110207 (Reload) +Sub: SK110101 (Arrow Attack) +Ultimate: SK110301 (Explosion) +``` + +**Rio** (암살자): +``` +STR: 15, DEX: 25, INT: 10, CON: 15, WIS: 10 +HP: 100, MP: 50, Mana Regen: 0.2 +Skills: SK140201 (Rapid Stab), SK140205 (Approach), SK140202 (Throwing Dagger) +Sub: SK140101 (Dropping Attack) +Ultimate: SK140301 (Sensitive) +``` + +**Sinobu** (닌자): +``` +STR: 10, DEX: 25, INT: 10, CON: 15, WIS: 15 +HP: 100, MP: 50, Mana Regen: 0.2 +Skills: SK180202 (Bomb Talisman), SK180203 (Thunder Kick), SK180205 (Ninpo Change) +Sub: SK180101 (Shuriken) +Ultimate: SK180301 (Deflect) +``` + +**Cazimord** (전사): +``` +STR: 15, DEX: 25, INT: 10, CON: 15, WIS: 10 +HP: 100, MP: 50, Mana Regen: 0.2 +Skills: SK170201 (Flash), SK170202 (Blade Storm), SK170203 (Burn) +Sub: SK170101 (Parrying) +Ultimate: SK170301 (Mana Stone Burn) +``` + +**Lian** (레인저): +``` +STR: 10, DEX: 20, INT: 10, CON: 15, WIS: 20 +HP: 100, MP: 50, Mana Regen: 0.2 +Skills: SK190207 (Rapid Shot), SK190205 (Back Step), SK190201 (Dark Souls), SK190209 (Reload) +Sub: SK190101 (Charging Bow) +Ultimate: SK190301 (Manastone Silence) +``` + +##### 지능 중심 스토커 (INT 20+) + +**Nave** (마법사): +``` +STR: 10, DEX: 10, INT: 25, CON: 10, WIS: 20 +HP: 100, MP: 50, Mana Regen: 0.2 +Skills: SK120201 (Magic Missile), SK120202 (Fire Wall), SK120206 (Wind Force) +Sub: SK120101 (Mana Restore) +Ultimate: SK120301 (Escape 4) +``` + +**Rene** (소환사): +``` +STR: 10, DEX: 10, INT: 20, CON: 10, WIS: 25 +HP: 100, MP: 50, Mana Regen: 0.2 +Skills: SK160202 (Summon Ifrit), SK160206 (Summon Shiva), SK160203 (Poison Gas) +Sub: SK160101 (Scratching) +Ultimate: SK160301 (Mana Stone Carnival) +``` + +##### 균형 스토커 + +**Clad** (성직자): +``` +STR: 15, DEX: 10, INT: 10, CON: 20, WIS: 20 +HP: 100, MP: 50, Mana Regen: 0.2 +Skills: SK150206 (Holy Cure), SK150201 (Turn Undead), SK150202 (Holy Light) +Sub: SK150101 (Block) +Ultimate: SK150301 (Gold) +``` + +### 2.2 DT_Skill (스킬 상세 데이터) + +DT_Skill은 각 스킬의 피해 배율, 쿨타임, 원소 타입, 사용 몽타주, 연결된 Ability 클래스 등을 정의합니다. + +#### 구조 + +| 필드 | 타입 | 설명 | +|------|------|------| +| `RowName` | String | 스킬 ID (SK + 6자리 숫자) | +| `stalkerName` | String | 소유 스토커 이름 (소문자) | +| `bIsUltimate` | Bool | 궁극기 여부 | +| `skillName` | String | 스킬 이름 | +| `skillDamageRate` | Float | 기본 피해 배율 (1.0 = 100%) | +| `skillManaCost` | Float | 마나 소모량 | +| `coolTime` | Float | 쿨타임 (초) | +| `skillAttackType` | Enum | 공격 타입 (PhysicalSkill, MagicalSkill, Normal) | +| `skillElementType` | Enum | 원소 타입 (None, Fire, Lightning, Poison, Water, Holy, Dark) | +| `useMontages` | Array | 사용할 몽타주 경로 목록 | +| `abilityClass` | String | 연결된 GA_* 블루프린트 경로 | +| `gameplayEffectSet` | Array | 적용할 GameplayEffect 목록 | + +#### 스킬 타입 분류 + +##### PhysicalSkill (물리 스킬) +- 힘(STR) 또는 민첩(DEX) 스탯의 영향을 받음 +- 방어력(Defense)에 의해 감소 +- 예시: Hilda의 Sword Strike, Rio의 Rapid Stab, Cazimord의 Flash + +##### MagicalSkill (마법 스킬) +- 지능(INT) 또는 지혜(WIS) 스탯의 영향을 받음 +- 마법 저항력(MagicalResistancePer)에 의해 감소 +- 원소 타입에 따라 추가 저항력 적용 (Fire, Lightning, Poison 등) +- 예시: Nave의 Magic Missile, Rene의 Summon Ifrit, Clad의 Turn Undead + +##### Normal (일반/유틸리티) +- 피해를 주지 않거나 특수 메커니즘을 가진 스킬 +- 스탯 배율 없이 고정 효과 또는 0 피해 +- 예시: Hilda의 Blocking, Urud의 Reload, Rio의 Approach + +#### 원소 타입별 특성 + +| 원소 | 저항 스탯 | 주요 사용 스토커 | +|------|-----------|------------------| +| **None** | - | 대부분 (범용 스킬) | +| **Lightning** | lightningResistancePer | Hilda (Sword Strike), Sinobu (Thunder Kick) | +| **Fire** | fireResistancePer | Nave (Fire Wall) | +| **Poison** | poisonResistancePer | Urud (Poison Arrow), Rene (Poison Gas) | +| **Water** | waterResistancePer | - | +| **Holy** | holyResistancePer | Clad (Turn Undead), Lian (Dark Souls) | +| **Dark** | darkResistancePer | Rene (Poison Gas - Dark 속성도 보유) | + +#### 스킬 예시 + +**Hilda - SK100201 (Sword Strike)**: +```json +{ + "RowName": "SK100201", + "stalkerName": "hilda", + "skillName": "Sword Strike", + "skillDamageRate": 1.3, + "coolTime": 6.0, + "skillAttackType": "PhysicalSkill", + "skillElementType": "Lightning", + "useMontages": [ + "/Game/_Art/_Character/PC/Hilda/AnimMontage/Skill/AM_PC_Hilda_B_Skill_Ready.AM_PC_Hilda_B_Skill_Ready", + "/Game/_Art/_Character/PC/Hilda/AnimMontage/Skill/AM_PC_Hilda_B_Skill_SwordStrike.AM_PC_Hilda_B_Skill_SwordStrike" + ], + "abilityClass": "/Game/Blueprints/Characters/Hilda/GA_Skill_Hilda_SwordStrike.GA_Skill_Hilda_SwordStrike_C" +} +``` + +**Nave - SK120301 (Escape 4 / Liberation 궁극기)**: +```json +{ + "RowName": "SK120301", + "stalkerName": "nave", + "bIsUltimate": true, + "skillName": "Escape 4", + "skillDamageRate": 1.0, + "coolTime": 0.0, + "skillAttackType": "MagicalSkill", + "skillElementType": "None", + "useMontages": [ + "/Game/_Art/_Character/PC/Nave/AnimMontage/Ultimate/AM_PC_Nave_B_Skill_Escape4.AM_PC_Nave_B_Skill_Escape4" + ], + "abilityClass": "/Game/Blueprints/Characters/Nave/GA_Skill_Nave_Escape4.GA_Skill_Nave_Escape4_C" +} +``` +**특수 사항**: Nave의 궁극기는 AnimMontage의 노티파이가 아닌 **DT_Skill의 skillDamageRate와 gameplayEffectSet**에서 직접 피해를 정의합니다. (Section 6 참조) + +**Urud - SK110301 (Explosion 궁극기)**: +```json +{ + "RowName": "SK110301", + "stalkerName": "urud", + "bIsUltimate": true, + "skillName": "Explosion", + "skillDamageRate": 1.0, + "coolTime": 0.0, + "skillAttackType": "Normal", + "skillElementType": "None", + "useMontages": [ + "/Game/_Art/_Character/PC/Urud/AnimMontage/Ultimate/AM_PC_Urud_B_Skill_Explosion.AM_PC_Urud_B_Skill_Explosion", + "/Game/_Art/_Character/PC/Urud/AnimMontage/Base/AM_PC_Urud_B_Skill_Equipment.AM_PC_Urud_B_Skill_Equipment" + ], + "abilityClass": "/Game/Blueprints/Abilities/GA_Skill_Casting_Ultimate.GA_Skill_Casting_Ultimate_C" +} +``` +**특수 사항**: Urud의 궁극기 'Explosion'은 투척한 돌이 적중 시 범위 피해를 줍니다. 피해는 AnimMontage의 노티파이 또는 GA_Skill Blueprint에서 처리됩니다. + +### 2.3 DT_CharacterAbility (기본 공격 어빌리티 매핑) + +DT_CharacterAbility는 각 스토커의 기본 공격(좌클릭)에 사용되는 Ability 클래스를 매핑합니다. + +#### 구조 + +| 필드 | 타입 | 설명 | +|------|------|------| +| `characterName` | String | 스토커 이름 | +| `abilityClass` | String | 기본 공격 Ability 경로 | + +대부분의 스토커는 공통 `GA_Attack` 클래스를 사용하지만, 일부 스토커는 커스텀 공격 로직을 가집니다. + +#### 공통 기본 공격 + +**GA_Attack** 사용 스토커: +- Hilda, Baran, Rio, Clad, Rene, Nave, Sinobu, Cazimord + +기본 공격 로직: +1. 장착한 무기의 `montages` 배열에서 몽타주 선택 +2. 콤보 시스템: 연속 입력 시 다음 몽타주로 체인 +3. AnimNotifyState_AttackWithEquip 노티파이에서 히트 판정 +4. ANS_AttackState_C 노티파이에서 피해 배율 적용 + +#### 특수 기본 공격 + +**Urud, Lian** (원거리 스토커): +- Ability: `GA_Attack_Firearm` (총기형 무기 전용) +- 특징: + - 탄약 시스템 (GameplayTag 기반 탄약 카운트) + - 탄약 소진 시 자동으로 Reload 스킬 트리거 + - 조준 중 이동 속도 감소 + - Reload 스킬(SK110207, SK190209)로 재장전 + +### 2.4 스토커별 스킬 매핑 요약 + +| 스토커 | 기본 스킬 (3-4개) | 서브 스킬 (쿨타임 0) | 궁극기 | 특수 시스템 | +|--------|------------------|---------------------|--------|-------------| +| **Hilda** | Sword Strike, Counter, Provoke | Blocking | Blood Moon | Counter 반격, 방패 방어 | +| **Urud** | Multi Shot, Poison Arrow, Make Trap, Reload | Arrow Attack | Explosion | Reload 시스템, 범위 피해 궁극기 | +| **Nave** | Magic Missile, Fire Wall, Wind Force | Mana Restore | Escape 4 | 캐스팅 스킬, Liberation 피해(DT_Skill) | +| **Baran** | Pulling, Smash, Sword Stab | Blocking | Rock Breaker | Pulling 체인, 중력 조작 | +| **Rio** | Rapid Stab, Approach, Throwing Dagger | Dropping Attack | Sensitive | Chain Score (3 스택) | +| **Clad** | Holy Cure, Turn Undead, Holy Light | Block | Gold | 힐링, 언데드 특효 | +| **Rene** | Summon Ifrit, Summon Shiva, Poison Gas | Scratching | Mana Stone Carnival | Spirit 소환, Lifesteal | +| **Sinobu** | Bomb Talisman, Thunder Kick, Ninpo Change | Shuriken | Deflect | Shuriken 충전, Swap 인술 | +| **Lian** | Rapid Shot, Back Step, Dark Souls, Reload | Charging Bow | Manastone Silence | Reload 시스템, Precision Aim, 충전 3스택 | +| **Cazimord** | Flash, Blade Storm, Burn | Parrying | Mana Stone Burn | Flash 스택, Parrying 반격 | + +--- + +## 3. AnimMontage 타이밍 시스템 + +### 3.1 AnimMontage 노티파이 시스템 개요 + +AnimMontage는 애니메이션 재생 중 특정 타이밍에 게임 로직을 트리거하는 노티파이(Notify) 시스템을 제공합니다. 전투 시스템에서는 노티파이를 통해 다음을 구현합니다: + +- **공격 판정 타이밍**: 무기가 적을 타격하는 정확한 프레임 +- **피해 배율 조정**: 공격 구간별 피해량 증감 +- **스킬 캔슬 윈도우**: 스킬을 중단하고 다음 행동으로 전환 가능한 시간대 +- **자동 타게팅**: 적을 자동으로 추적하는 시점 +- **이동 제한**: 스킬 사용 중 이동 속도 조절 +- **벽 충돌 체크**: 근거리 공격 시 벽에 막히는지 검사 + +#### 노티파이 타입 + +| 타입 | 설명 | 예시 | +|------|------|------| +| **Notify** | 특정 시점에 한 번 실행 | AN_SetAutoTarget_C (자동 타게팅 활성화) | +| **NotifyState** | 시작~종료 구간 동안 지속 | ANS_AttackState_C (공격 상태 유지) | + +### 3.2 주요 노티파이 클래스 + +#### ANS_AttackState_C (공격 상태 및 피해 배율) + +**타입**: NotifyState (구간 지속) + +**역할**: +- 공격 판정이 가능한 구간 정의 +- 피해 배율 조정 (`AddNormalAttackPer` 프로퍼티) + +**프로퍼티**: +| 프로퍼티 | 타입 | 설명 | +|----------|------|------| +| `TriggerTime` | Float | 시작 시간 (초) | +| `Duration` | Float | 지속 시간 (초) | +| `AddNormalAttackPer` | Float | 피해 배율 증감 (%) | + +**예시** - Baran 기본 공격 1타: +```json +{ + "NotifyStateClass": "ANS_AttackState_C", + "TriggerTime": 0.0, + "Duration": 1.17, + "CustomProperties": { + "AddNormalAttackPer": "30.0" + } +} +``` +→ 0.0초부터 1.17초까지 공격 상태이며, 피해량이 +30% 증가 + +#### AnimNotifyState_AttackWithEquip (히트 판정 타이밍) + +**타입**: NotifyState (구간 지속) + +**역할**: +- 무기의 궤적을 따라 히트 판정 수행 +- 다중 히트 지점 정의 (TimeArray, LocationArray, RotationArray) +- 공격 타입 태그 지정 (Event.Attack.Normal, Event.Attack.Skill 등) + +**프로퍼티**: +| 프로퍼티 | 타입 | 설명 | +|----------|------|------| +| `TriggerTime` | Float | 시작 시간 (초) | +| `Duration` | Float | 지속 시간 (초) | +| `AttackTag` | GameplayTag | 공격 타입 태그 | +| `TimeArray` | Array | 다중 히트 시간 배열 | +| `LocationArray` | Array | 히트 위치 배열 | +| `RotationArray` | Array | 히트 방향 배열 | + +**예시** - Baran 기본 공격 1타 히트 판정: +```json +{ + "NotifyStateClass": "AnimNotifyState_AttackWithEquip", + "TriggerTime": 0.8, + "Duration": 0.12, + "CustomProperties": { + "AttackTag": "(TagName=\"Event.Attack.Normal\")", + "TimeArray": ["0.0", "0.033333", "0.066667", "0.1"], + "LocationArray": ["(X=0,Y=0,Z=0)", "(X=20,Y=10,Z=5)", ...] + } +} +``` +→ 0.8초부터 0.92초까지 4개의 히트 포인트에서 판정 (0.033초 간격) + +#### ANS_SkillCancel_C (스킬 캔슬 윈도우) + +**타입**: NotifyState (구간 지속) + +**역할**: +- 스킬의 자연스러운 종료 타이밍 정의 +- 이 구간에서 **같거나 낮은 Activation Order Group** 스킬로 캔슬 가능 +- DPS 최적화에 중요한 역할 + +**프로퍼티**: +| 프로퍼티 | 타입 | 설명 | +|----------|------|------| +| `TriggerTime` | Float | 캔슬 윈도우 시작 시간 (초) | +| `Duration` | Float | 캔슬 윈도우 지속 시간 (초) | + +**예시** - Hilda Sword Strike 스킬: +```json +{ + "NotifyStateClass": "ANS_SkillCancel_C", + "TriggerTime": 1.3, + "Duration": 0.5 +} +``` +→ 1.3초부터 1.8초까지 캔슬 가능 (스킬 종료 구간) + +**중요**: ANS_SkillCancel_C는 **Activation Order Group 시스템과 독립적**으로 작동합니다 (OR 조건). 자세한 내용은 Section 4.2 참조. + +#### AN_SetAutoTarget_C (자동 타게팅) + +**타입**: Notify (순간 실행) + +**역할**: +- 특정 시점에 가장 가까운 적을 자동으로 타게팅 +- 캐릭터 회전 및 카메라 방향 조정 + +**예시**: +```json +{ + "NotifyClass": "AN_SetAutoTarget_C", + "TriggerTime": 0.1 +} +``` +→ 0.1초 시점에 자동 타게팅 활성화 + +#### ANS_WeaponWallCheck_C (벽 충돌 검사) + +**타입**: NotifyState (구간 지속) + +**역할**: +- 근거리 무기 공격 시 벽과의 충돌 검사 +- 벽에 막히면 공격 판정 무효화 + +#### ANS_DisableBlockingState_C (방어 불가 구간) + +**타입**: NotifyState (구간 지속) + +**역할**: +- 특정 구간에서 방어(Blocking) 불가 상태로 만듦 +- 공격 모션 중 방어 전환 방지 + +### 3.3 스토커별 기본 공격 타이밍 분석 + +기본 공격은 좌클릭으로 실행되며, 대부분 3타 콤보로 구성됩니다. 각 타의 AnimMontage에서 피해 배율과 히트 판정 타이밍을 추출했습니다. + +#### 3타 콤보 총 소요 시간 (공격 속도 랭킹) + +| 순위 | 스토커 | 1타 | 2타 | 3타 | 합계 | 특징 | +|------|--------|-----|-----|-----|------|------| +| 1 | **Rio** | 1.200s | 1.300s | 1.367s | **3.867s** | 최고속 암살자 | +| 2 | **Sinobu** | 1.367s | 1.333s | 1.334s | **4.034s** | 빠른 공격 | +| 3 | **Hilda** | 1.500s | 1.500s | 1.567s | **4.567s** | 중간 속도 | +| 4 | **Nave** | 1.733s | 1.667s | 1.767s | **5.167s** | 캐스터 타입 | +| 5 | **Cazimord** | 1.667s | 1.900s | 1.867s | **5.434s** | 듀얼 소드 | +| 6 | **Baran** | 1.900s | 1.700s | 1.966s | **5.566s** | 무거운 공격 | +| 7 | **Rene** | 2.000s | 1.900s | 2.000s | **5.900s** | 느린 소환사 | +| 8 | **Clad** | 2.000s | 2.000s | 2.000s | **6.000s** | 가장 느림 | + +**참고**: +- Urud, Lian: 원거리 총기형 공격 (발사 속도는 별도, Reload 필요) + +#### 기본 공격 피해 배율 (AddNormalAttackPer) + +| 스토커 | 1타 | 2타 | 3타 | 특징 | +|--------|-----|-----|-----|------| +| **Baran** | +30% | +35% | +30% | 가장 높은 피해 (탱커 타입) | +| **Clad** | +30% | +50% | +30% | 2타 강화 (성직자 평타) | +| **Hilda** | +30% | +30% | +30% | 일정한 피해 | +| **Cazimord** | -5% | +15% | +20% | 3타 강화 (듀얼 소드) | +| **Sinobu** | -30% | +10% | -30% | 속도 우선, 낮은 피해 | +| **Rio** | -30% | -20% | -15% | 낮은 피해 (속도로 보완) | +| **Nave** | 0% | 0% | 0% | 배율 없음 (마법 공격) | +| **Rene** | 0% | 0% | 0% | 배율 없음 (소환수 의존) | + +**해석**: +- **Baran, Clad**: 느리지만 강력한 공격 (STR/CON 스토커) +- **Rio, Sinobu**: 빠르지만 약한 공격 (DEX 스토커, 스킬 의존) +- **Nave, Rene**: INT/WIS 스토커로 평타보다 스킬 중심 + +#### 기본 공격 몽타주 예시 + +**Hilda - AM_PC_Hilda_B_Attack_W01_01** (1타): +``` +Duration: 1.5초 +ANS_AttackState_C: 0.0~1.133초, AddNormalAttackPer: +30% +AnimNotifyState_AttackWithEquip: 0.733~0.867초 (히트 판정) +AN_SetAutoTarget_C: 0.1초 (자동 타게팅) +``` + +**Rio - AM_PC_Rio_B_Attack_W01_01** (1타): +``` +Duration: 1.2초 +ANS_AttackState_C: 0.0~0.9초, AddNormalAttackPer: -30% +AnimNotifyState_AttackWithEquip: 0.4~0.533초 (빠른 히트) +``` + +**Baran - AM_PC_Baran_B_Attack_W01_01** (1타): +``` +Duration: 1.9초 +ANS_AttackState_C: 0.0~1.17초, AddNormalAttackPer: +30% +AnimNotifyState_AttackWithEquip: 0.8~0.92초 (느린 히트) +``` + +**Cazimord - AM_PC_Cazimord_B_Attack_W01_01** (1타): +``` +Duration: 1.667초 +ANS_AttackState_C: 0.0~0.900초, AddNormalAttackPer: -5% +AnimNotifyState_AttackWithEquip: 0.592~0.733초, 0.710~0.891초 (듀얼 소드 더블 히트) +``` + +### 3.4 스토커별 스킬 타이밍 분석 + +각 스토커의 주요 스킬 몽타주에서 타이밍 데이터를 추출했습니다. + +#### Hilda (전사) + +**SK100201 - Sword Strike** (AM_PC_Hilda_B_Skill_SwordStrike): +``` +Duration: 1.8초 +ANS_SkillCancel_C: 1.3~1.8초 (캔슬 윈도우 0.5초) +ANS_AttackState_C: 0.0~1.533초, AddNormalAttackPer: +100% +AnimNotifyState_AttackWithEquip: 0.6~0.8초 +``` + +**SK100202 - Counter** (AM_PC_Hilda_B_Skill_Counter): +``` +Duration: 2.567초 +ANS_SkillCancel_C: 없음 (캔슬 불가) +특수: 반격 판정 노티파이 포함 +``` + +**SK100204 - Provoke** (AM_PC_Hilda_B_Skill_Provoke): +``` +Duration: 3.0초 +ANS_SkillCancel_C: 없음 +특수: 도발 범위 설정 노티파이 +``` + +#### Urud (원거리) + +**SK110205 - Multi Shot** (AM_PC_Urud_B_Skill_MultiArrow): +``` +Duration: 2.0초 +ANS_SkillCancel_C: 없음 +AnimNotifyState_AttackWithEquip: 다중 화살 발사 타이밍 +``` + +**SK110204 - Poison Arrow** (AM_PC_Urud_B_Skill_PoisonArrow): +``` +Duration: 1.8초 +ANS_SkillCancel_C: 없음 +특수: Poison 상태 이상 적용 +``` + +**SK110207 - Reload** (AM_PC_Urud_B_Skill_Reload): +``` +Duration: 2.0초 +ANS_SkillCancel_C: 없음 +특수: 재장전 완료 노티파이 (탄약 충전) +``` + +#### Nave (마법사) + +**SK120201 - Magic Missile** (AM_PC_Nave_B_Skill_MagicMissile): +``` +Duration: 1.5초 +ANS_SkillCancel_C: 없음 +특수: 캐스팅 타입 (CanMove_CanRelease) +``` + +**SK120202 - Fire Wall** (AM_PC_Nave_B_Skill_FireWall): +``` +Duration: 1.8초 +ANS_SkillCancel_C: 없음 +특수: 범위 화염 장판 생성 +``` + +**SK120301 - Escape 4 (Liberation 궁극기)** (AM_PC_Nave_B_Skill_Escape4): +``` +Duration: 3.5초 +ANS_SkillCancel_C: 없음 +특수: DT_Skill에서 피해 정의 (AnimMontage 노티파이 없음) +``` + +#### Baran (전사) + +**SK130204 - Pulling** (AM_PC_Baran_B_Skill_Pulling): +``` +Duration: 1.7초 +ANS_SkillCancel_C: 1.276~1.700초 (캔슬 윈도우 0.424초) +특수: Pulling 체인 판정 (적 끌어당김) +``` + +**SK130203 - Smash** (AM_PC_Baran_B_Skill_Smash): +``` +Duration: 2.0초 +ANS_SkillCancel_C: 없음 +AnimNotifyState_AttackWithEquip: 1.0~1.2초 (강타 판정) +``` + +**SK130206 - Sword Stab** (AM_PC_Baran_B_Skill_SwordStab): +``` +Duration: 1.733초 +ANS_SkillCancel_C: 1.660~1.733초 (캔슬 윈도우 0.073초, 매우 짧음) +AnimNotifyState_AttackWithEquip: 0.8~1.0초 +``` + +#### Rio (암살자) + +**SK140201 - Rapid Stab** (AM_PC_Rio_B_Skill_RapidStab): +``` +Duration: 1.833초 +ANS_SkillCancel_C: 1.513~1.833초 (캔슬 윈도우 0.32초) +특수: 빠른 연속 찌르기 (다중 히트) +``` + +**SK140205 - Approach** (AM_PC_Rio_B_Skill_Approach): +``` +Duration: 1.5초 +ANS_SkillCancel_C: 없음 +특수: 돌진 이동 스킬 (피해 없음) +``` + +**SK140202 - Throwing Dagger** (AM_PC_Rio_B_Skill_ThrowingDagger): +``` +Duration: 1.2초 +ANS_SkillCancel_C: 없음 +특수: 원거리 투척 공격 +``` + +#### Clad (성직자) + +**SK150206 - Holy Cure** (AM_PC_Clad_B_Skill_HolyCure): +``` +Duration: 2.0초 +ANS_SkillCancel_C: 없음 +특수: 힐링 효과 (피해 없음) +``` + +**SK150201 - Turn Undead** (AM_PC_Clad_B_Skill_TurnUndead): +``` +Duration: 2.5초 +ANS_SkillCancel_C: 없음 +특수: 언데드 특효 피해 +``` + +**SK150202 - Holy Light** (AM_PC_Clad_B_Skill_HolyLight): +``` +Duration: 2.0초 +ANS_SkillCancel_C: 없음 +특수: 범위 버프 효과 +``` + +#### Rene (소환사) + +**SK160202 - Summon Ifrit** (AM_PC_Rene_B_Skill_SummonIfrit): +``` +Duration: 2.5초 +ANS_SkillCancel_C: 없음 +특수: Ifrit 소환수 생성 +``` + +**SK160206 - Summon Shiva** (AM_PC_Rene_B_Skill_SummonShiva): +``` +Duration: 2.5초 +ANS_SkillCancel_C: 없음 +특수: Shiva 소환수 생성 +``` + +**SK160203 - Poison Gas** (AM_PC_Rene_B_Skill_PoisonGas): +``` +Duration: 2.0초 +ANS_SkillCancel_C: 없음 +특수: 독 구름 장판 생성 +``` + +#### Sinobu (닌자) + +**SK180202 - Bomb Talisman** (AM_PC_Sinobu_B_Skill_BombTalisman): +``` +Duration: 1.5초 +ANS_SkillCancel_C: 없음 +특수: 폭탄 부적 투척 +``` + +**SK180203 - Thunder Kick** (AM_PC_Sinobu_B_Skill_ThunderKick): +``` +Duration: 1.8초 +ANS_SkillCancel_C: 없음 +AnimNotifyState_AttackWithEquip: 0.6~0.8초 +특수: 번개 속성 발차기 +``` + +**SK180205 - Ninpo Change (Swap)** (AM_PC_Sinobu_B_Skill_NinpoChange): +``` +Duration: 1.0초 +ANS_SkillCancel_C: 없음 +특수: 텔레포트 인술 (위치 교환) +``` + +#### Lian (레인저) + +**SK190207 - Rapid Shot** (AM_PC_Lian_B_Skill_RapidShot1): +``` +Duration: 2.626초 +ANS_SkillCancel_C: 2.333~2.626초 (캔슬 윈도우 0.293초) +특수: 연속 사격 (6발) +``` + +**SK190205 - Back Step Bow Attack** (AM_PC_Lian_B_Skill_BackStepBowAttack): +``` +Duration: 1.701초 +ANS_SkillCancel_C: 1.492~1.701초 (캔슬 윈도우 0.209초) +특수: 백스텝 + 사격 (회피 기능) +``` + +**SK190201 - Dark Souls** (AM_PC_Lian_B_Skill_DarkSouls_NoCasting): +``` +Duration: 2.0초 +ANS_SkillCancel_C: 없음 +특수: Holy 속성 관통 공격 +``` + +**SK190209 - Reload** (AM_PC_Lian_B_Skill_Reload): +``` +Duration: 2.0초 +ANS_SkillCancel_C: 없음 +특수: 재장전 (탄약 충전) +``` + +#### Cazimord (전사) + +**SK170201 - Flash** (AM_PC_Cazimord_B_Skill_Flash, AM_PC_Cazimord_B_Skill_Flash_Active): +``` +Flash (준비): Duration: 1.0초 +Flash_Active (발동): Duration: 1.733초 +ANS_SkillCancel_C: 1.001~1.733초 (Active 몽타주, 캔슬 윈도우 0.732초) +특수: Flash 스택 시스템 (최대 3스택) +``` + +**SK170202 - Blade Storm** (AM_PC_Cazimord_B_Skill_BladeStorm): +``` +Duration: 3.0초 +ANS_SkillCancel_C: 2.5~3.0초 (캔슬 윈도우 0.5초) +특수: 회전 범위 공격 +``` + +**SK170203 - Burn** (AM_PC_Cazimord_B_Skill_Burn): +``` +Duration: 2.5초 +ANS_SkillCancel_C: 없음 +특수: Fire 지속 피해 +``` + +**SK170101 - Parrying (서브 스킬)** (AM_PC_Cazimord_B_Skill_Parrying): +``` +Duration: 1.5초 +ANS_SkillCancel_C: 없음 +특수: 패링 성공 시 반격 + Flash 스택 충전 +``` + +### 3.5 ANS_SkillCancel_C 보유 스킬 요약 + +전체 스킬 중 **ANS_SkillCancel_C 노티파이를 가진 스킬은 7개뿐**입니다. 기본 공격은 모두 캔슬 윈도우가 없습니다. + +| 스토커 | 스킬 ID | 스킬 이름 | 캔슬 시작 | 캔슬 종료 | 윈도우 크기 | +|--------|---------|-----------|-----------|-----------|-------------| +| **Hilda** | SK100201 | Sword Strike | 1.300s | 1.800s | 0.500s | +| **Baran** | SK130204 | Pulling | 1.276s | 1.700s | 0.424s | +| **Baran** | SK130206 | Sword Stab | 1.660s | 1.733s | 0.073s | +| **Rio** | SK140201 | Rapid Stab | 1.513s | 1.833s | 0.320s | +| **Lian** | SK190207 | Rapid Shot | 2.333s | 2.626s | 0.293s | +| **Lian** | SK190205 | Back Step | 1.492s | 1.701s | 0.209s | +| **Cazimord** | SK170202 | Blade Storm | 2.500s | 3.000s | 0.500s | +| **Cazimord** | SK170201 | Flash (Active) | 1.001s | 1.733s | 0.732s | + +**캔슬 윈도우가 없는 스토커** (5명): +- Urud, Nave, Clad, Rene, Sinobu + +이들은 **Activation Order Group 시스템**에만 의존하여 스킬 캔슬을 처리합니다. (Section 4.2 참조) + +**캔슬 윈도우 크기 특징**: +- **가장 긴 윈도우**: Cazimord Flash_Active (0.732초) - 스택 충전 시스템에 유리 +- **가장 짧은 윈도우**: Baran Sword Stab (0.073초) - 정밀한 타이밍 요구 +- **평균 윈도우**: 약 0.4초 + +--- + +## 4. Blueprint Ability 및 캔슬 시스템 + +### 4.1 GA_* Blueprint Ability 구조 + +모든 스킬과 기본 공격은 **UWSGameplayAbility**를 상속한 Blueprint 클래스로 구현됩니다. 이 클래스들은 `GA_`로 시작하며 (GameplayAbility), 스킬 로직을 정의합니다. + +#### UWSGameplayAbility 베이스 클래스 + +**소스 위치**: `WorldStalker/Source/WorldStalker/AbilitySystem/WSGameplayAbility.h` + +**주요 프로퍼티**: +```cpp +// 0: 그룹 없음, 1이상: 높을 수록 아래 상태를 무시하고, 같은 값이면 서로 교체됨 +UPROPERTY(EditDefaultsOnly, Category = "WorldStalker") +uint8 ActivationOrderGroup = 0; + +UPROPERTY(EditDefaultsOnly, Category = "WorldStalker") +bool bDisableOrderGroup = false; + +UPROPERTY(EditDefaultsOnly, Category = "WorldStalker") +bool bCanSkillCancel = false; + +UPROPERTY(EditDefaultsOnly, Category = "WorldStalker") +EWSAbilityActivationTrigger ActivationTrigger = EWSAbilityActivationTrigger::OnceInput; +``` + +#### Ability 클래스 분류 + +**1. 공통 Ability** (여러 스토커가 공유): + +| Ability 클래스 | 사용 스토커 | 용도 | +|----------------|-------------|------| +| `GA_Attack` | Hilda, Baran, Rio, Clad, Rene, Nave, Sinobu, Cazimord | 기본 공격 (좌클릭) | +| `GA_Attack_Firearm` | Urud, Lian | 원거리 총기형 기본 공격 (탄약 시스템) | +| `GA_Attack_Firearm_Reload` | Urud, Lian | 재장전 (Reload 스킬) | +| `GA_Skill_Common_Blocking` | Hilda, Baran, Clad | 방패 방어 (서브 스킬) | +| `GA_Skill_Casting_CanMove_CanRelease` | Nave (3개), Clad (3개), Rene (3개), Sinobu, Cazimord | 이동 가능 캐스팅 스킬 | +| `GA_Skill_Casting_CantMove_CanRelease` | Urud (Make Trap) | 이동 불가 캐스팅 스킬 | +| `GA_Skill_Casting_Ultimate` | Hilda, Urud, Clad, Rio, Rene, Lian | 궁극기 (공통 로직) | +| `GA_Skill_Ultimate_Base` | Sinobu | 궁극기 베이스 클래스 | + +**2. 스토커 전용 Ability** (특수 로직 구현): + +각 스토커는 고유한 GA_Skill_[Stalker]_[SkillName] 클래스를 가집니다. + +#### Ability 상속 계층 + +``` +UGameplayAbility (언리얼 엔진 기본 클래스) + └─ UWSGameplayAbility (C++ 베이스 클래스) + ├─ GA_WSGameplayAbilityBase_C (Blueprint 베이스) + │ ├─ GA_Attack_C (기본 공격) + │ ├─ GA_Attack_Firearm_C (총기 공격) + │ ├─ GA_Skill_Common_Blocking_C (방어) + │ ├─ GA_Skill_Casting_* (캐스팅 스킬들) + │ └─ GA_Skill_[Stalker]_[Skill]_C (스토커별 스킬) + └─ ... +``` + +### 4.2 Activation Order Group 시스템 + +**Activation Order Group**은 스킬 활성화 우선순위를 정의하는 **주요 캔슬 메커니즘**입니다. + +#### 작동 원리 + +**우선순위 규칙**: +1. **높은 그룹이 낮은 그룹을 캔슬**: Group 3 스킬은 Group 1, 2 스킬을 언제든지 중단 가능 +2. **같은 그룹은 서로 교체 가능**: Group 2 스킬끼리는 자유롭게 전환 가능 +3. **낮은 그룹은 높은 그룹을 캔슬 불가**: Group 1 스킬은 Group 2 실행 중 발동 불가 +4. **Group 0은 우선순위 없음**: 다른 스킬에 의해 자유롭게 캔슬됨 + +**C++ 코드 구현**: +```cpp +// WSGameplayAbility.h:189-191 +uint8 GetActivationOrderGroup() const { return ActivationOrderGroup; } +void SetActivationOrderGroup(uint8 NewActivationOrderGroup) { ActivationOrderGroup = NewActivationOrderGroup; } +bool IsDisableOrderGroup() const { return bDisableOrderGroup; } +``` + +#### 우선순위 예시 + +``` +상황: Hilda가 Counter (Group 3) 실행 중 + +✅ 가능한 행동: +- Ultimate (Group 4) - 높은 그룹이므로 Counter 캔슬하고 발동 +- Counter (Group 3) - 같은 그룹이므로 재사용 가능 + +❌ 불가능한 행동: +- Sword Strike (Group 2) - 낮은 그룹이므로 Counter 종료 전까지 대기 +- 기본 공격 (Group 1) - 낮은 그룹이므로 Counter 종료 전까지 대기 +``` + +#### 일반적인 그룹 할당 패턴 + +| Group | 용도 | 예시 | +|-------|------|------| +| **0** | 우선순위 없음 | 패시브, 버프, 기본 행동 | +| **2** | 이동/지원 스킬 | Approach, Sway | +| **3** | 방어/반격 스킬 | Counter, Blocking, Parrying, Flash | +| **4** | 일반 스킬 | 대부분의 공격 스킬 | +| **5** | 기본 공격 | 좌클릭 평타, 특수 공격 | +| **9** | 궁극기 | Ultimate 스킬 | + +#### 스토커별 Activation Order Group 실제 값 + +다음은 Blueprint.json에서 추출한 10명 스토커의 실제 ActivationOrderGroup 값입니다. + +**Hilda** (전사): +- Group 4: Bash, SwordStrike +- Group 0: BloodMoon_Active, SteelBlocking + +**Urud** (궁수): +- Group 5: ArrowAttack, MultiShot_Quick, PoisonArrow +- Group 0: CampFire, Explosion_Active, MakeTrap, MultiShot, PierceShot, PoisonArrow_Active, SpeedUp + +**Nave** (마법사): +- Group 9: Escape4 (궁극기) +- Group 4: ManaCharge_Casting +- Group 0: Escape_Active, FireWall_Active, Invisible, MagicMissile_Active, MagicShield, TurnOff, WindForce + +**Baran** (기사): +- Group 9: Decision (궁극기) +- Group 5: Pulling +- Group 4: RockBreaker, Smash, SwordStab +- Group 0: Prepare, Slasher + +**Rio** (암살자): +- Group 5: DroppingAttack +- Group 4: Flashbang, RapidStab, ThrowingDagger +- Group 2: Approach +- Group 0: CatEyes, CorrosionDagger, Sensitive + +**Clad** (성직자): +- Group 0: Gold, HolyCure, HolyLight, HolyShield, HolyWall, Stigma, TurnUndead (모두 지원/버프 스킬) + +**Rene** (소환사): +- Group 5: Scratching +- Group 0: BloodChange, BloodChange_Active, BloodSword_Active, ManaStoneCarnival, PoisonGas_Active, Slow_Active, SummonIfrit_Active, SummonShiva_Active + +**Sinobu** (닌자): +- Group 9: Silence (궁극기) +- Group 5: Shuriken +- Group 4: BombTalisman, MarkingSting, ThunderKick +- Group 3: NinpoGecko_Casting +- Group 0: Deflect, NinpoChange, NinpoFlame, SIlence_Active + +**Lian** (레인저): +- Group 5: BackStepBowAttack, ChargingBow, RapidShot +- Group 4: DarkSouls +- Group 0: CallingRat, MakeWoodCover, ManaStoneSilence_Active, MoreArrow, OneAim + +**Cazimord** (검사): +- Group 9: WingCutter (궁극기) +- Group 4: BladeStorm, Burn, FrontKick, Parrying +- Group 3: Flash +- Group 2: BladeStorm_Perk, Sway, Sway_Perk +- Group 0: Burn_Active + +**주요 발견**: +- **궁극기 (Group 9)**: Nave, Baran, Sinobu, Cazimord만 보유 +- **기본 공격 (Group 5)**: Urud, Baran, Rio, Rene, Sinobu, Lian이 사용 +- **Clad와 Rene**: 대부분 Group 0 (패시브/지원) 스킬로 구성 +- **Cazimord**: 가장 다양한 Group 분포 (0, 2, 3, 4, 9) + +### 4.3 통합 캔슬 시스템 (OR 조건) + +스킬 캔슬은 **두 개의 독립적인 메커니즘**으로 작동합니다. 둘 중 **하나만 만족**하면 캔슬이 가능합니다 (OR 조건). + +#### 메커니즘 1: Activation Order Group (주요 시스템) + +**정의 위치**: GA_* Blueprint의 Class Defaults (C++ 프로퍼티) + +**작동 시점**: 언제든지 (애니메이션 재생 중 어느 구간이든) + +**조건**: +``` +IF (새 스킬의 ActivationOrderGroup >= 현재 스킬의 ActivationOrderGroup) + THEN 캔슬 가능 +``` + +**특징**: +- 우선순위 기반 시스템 +- 프레임 단위 정밀 제어 불필요 +- 대부분의 캔슬 처리 담당 + +#### 메커니즘 2: ANS_SkillCancel_C (보조 시스템) + +**정의 위치**: AnimMontage의 Notify State + +**작동 시점**: 노티파이가 활성화된 시간 구간 (TriggerTime ~ TriggerTime+Duration) + +**조건**: +``` +IF (현재 시간이 ANS_SkillCancel_C 구간 내) + AND (새 스킬의 ActivationOrderGroup >= 현재 스킬의 ActivationOrderGroup) + THEN 캔슬 가능 +``` + +**특징**: +- 타이밍 기반 윈도우 +- 스킬의 "자연스러운 종료" 구간 정의 +- 같은 우선순위 스킬도 이 구간에서는 캔슬 가능 + +#### 통합 로직 (의사 코드) + +```cpp +bool CanCancelCurrentSkill(GA_NewSkill, GA_CurrentSkill, CurrentTime) +{ + // 조건 1: Activation Order Group 우선순위 체크 + bool bHigherPriority = (GA_NewSkill.ActivationOrderGroup > GA_CurrentSkill.ActivationOrderGroup); + + // 조건 2: ANS_SkillCancel_C 윈도우 체크 + bool bInCancelWindow = false; + if (GA_CurrentSkill.CurrentMontage->HasSkillCancelNotify()) + { + float CancelStart = SkillCancelNotify.TriggerTime; + float CancelEnd = CancelStart + SkillCancelNotify.Duration; + bInCancelWindow = (CurrentTime >= CancelStart && CurrentTime <= CancelEnd); + + // 캔슬 윈도우 내에서는 같은 우선순위도 허용 + if (bInCancelWindow && GA_NewSkill.ActivationOrderGroup >= GA_CurrentSkill.ActivationOrderGroup) + return true; + } + + // OR 조건: 둘 중 하나만 만족하면 캔슬 가능 + return bHigherPriority || bInCancelWindow; +} +``` + +#### 실전 예시 + +**예시 1: Hilda - Sword Strike 스킬 사용 중 (Group 2)** + +| 상황 | 시간 | 새 입력 | 캔슬 가능? | 이유 | +|------|------|---------|-----------|------| +| 스킬 시작 | 0.5초 | 기본 공격 (Group 1) | ❌ | 낮은 우선순위 | +| 스킬 시작 | 0.5초 | Counter (Group 3) | ✅ | 높은 우선순위 (메커니즘 1) | +| 캔슬 윈도우 | 1.4초 | 다른 스킬 (Group 2) | ✅ | ANS_SkillCancel_C 구간 (메커니즘 2) | +| 캔슬 윈도우 | 1.4초 | 기본 공격 (Group 1) | ❌ | 여전히 낮은 우선순위 | +| 스킬 종료 | 1.9초 | 모든 스킬 | ✅ | 스킬 완전 종료 | + +**예시 2: Urud - Multi Shot 스킬 (ANS_SkillCancel_C 없음)** + +| 상황 | 새 입력 | 캔슬 가능? | 이유 | +|------|---------|-----------|------| +| 스킬 실행 중 | 일반 스킬 (같은 Group) | ❌ | ANS_SkillCancel_C 없음, 같은 우선순위 | +| 스킬 실행 중 | Ultimate (Group 4) | ✅ | 높은 우선순위 (메커니즘 1만 작동) | + +### 4.4 주요 Ability 구현 분석 + +이전에 추출한 Blueprint.json 데이터를 기반으로 주요 Ability의 구조를 분석합니다. + +#### GA_Attack (기본 공격) + +**사용 스토커**: Hilda, Baran, Rio, Clad, Rene, Nave, Sinobu, Cazimord + +**부모 클래스**: GA_WSGameplayAbilityBase_C + +**EventGraph 노드 수**: 70개 (중간 복잡도) + +**주요 변수**: +| 변수명 | 타입 | 설명 | +|--------|------|------| +| `ComboIndex` | Int | 현재 콤보 인덱스 (0~2) | +| `MaxComboCount` | Int | 최대 콤보 수 (기본 3) | +| `ComboInputBuffer` | Bool | 콤보 입력 버퍼 (선입력 지원) | + +**로직**: +1. 장착한 무기의 `montages` 배열에서 ComboIndex에 해당하는 몽타주 재생 +2. 콤보 윈도우 내에 재입력 시 다음 콤보로 체인 +3. 최대 콤보 도달 또는 일정 시간 경과 시 ComboIndex 리셋 + +#### GA_Attack_Firearm (총기형 기본 공격) + +**사용 스토커**: Urud, Lian + +**부모 클래스**: GA_Attack_C + +**EventGraph 노드 수**: 85개 (높은 복잡도) + +**주요 변수**: +| 변수명 | 타입 | 설명 | +|--------|------|------| +| `AmmoTag` | GameplayTag | 탄약 카운트 태그 (Character.State.Ammo) | +| `MaxAmmo` | Int | 최대 탄약 수 | +| `bAutoReload` | Bool | 탄약 소진 시 자동 재장전 | + +**특수 로직**: +1. 공격 시 AmmoTag 값을 1 감소 +2. 탄약이 0이면 공격 불가, 자동으로 Reload 스킬 트리거 +3. Reload 스킬 완료 시 AmmoTag를 MaxAmmo로 복구 + +**예시 - Urud**: +``` +초기 탄약: 6발 +발사 1~6발: AmmoTag = 6 → 5 → 4 → 3 → 2 → 1 → 0 +탄약 소진: GA_Attack_Firearm_Reload 자동 실행 +재장전 완료: AmmoTag = 6으로 복구 +``` + +#### GA_Skill_Common_Blocking (방패 방어) + +**사용 스토커**: Hilda, Baran, Clad + +**EventGraph 노드 수**: 112개 (매우 높은 복잡도) + +**주요 변수**: +| 변수명 | 타입 | 설명 | +|--------|------|------| +| `BlockingStaminaCost` | Float | 방어 시 스태미나 소모량 | +| `BlockingDamageReduction` | Float | 피해 감소율 (%) | +| `bPerfectBlockWindow` | Bool | 완벽 방어 윈도우 활성화 | + +**특수 로직**: +1. 버튼을 누르고 있는 동안 방어 상태 유지 (WhileInput 트리거) +2. 피격 시 BlockingDamageReduction만큼 피해 감소 +3. 스태미나가 0이면 방어 해제 +4. 완벽 방어 타이밍 성공 시 스태미나 소모 없음 + +#### GA_Skill_Casting_CanMove_CanRelease (이동 가능 캐스팅) + +**사용 스토커**: Nave (3개 스킬), Clad (3개 스킬), Rene (3개 스킬), Sinobu, Cazimord + +**EventGraph 노드 수**: 68개 + +**주요 변수**: +| 변수명 | 타입 | 설명 | +|--------|------|------| +| `CastingTime` | Float | 캐스팅 시간 (DT_Skill에서 가져옴) | +| `bCanMoveWhileCasting` | Bool | 캐스팅 중 이동 가능 (true) | +| `bCanRelease` | Bool | 버튼을 떼면 즉시 발동 (차징) | + +**특수 로직**: +1. 버튼 누름: 캐스팅 시작 (몽타주 재생) +2. 캐스팅 중 이동 속도 감소 (GE_WalkSpeed 적용) +3. 버튼을 떼거나 CastingTime 경과 시 스킬 발동 +4. 캐스팅 중 다른 높은 우선순위 스킬로 캔슬 가능 + +### 4.5 스토커별 특수 시스템 구현 + +#### Hilda - Counter (반격 시스템) + +**Ability**: GA_Skill_Knight_Counter_C + +**메커니즘**: +1. Counter 스킬 발동 시 짧은 시간 동안 "반격 판정" 상태 +2. 이 상태에서 피격 시 피해 무효화 + 자동 반격 +3. 반격 성공 시 추가 피해 (DT_Skill의 skillDamageRate 배율 적용) + +**AnimMontage**: AM_PC_Hilda_B_Skill_Counter (Duration: 2.567초) +- 반격 판정 구간: 0.3~0.8초 (0.5초 윈도우) +- ANS_SkillCancel_C 없음 (강제로 끝까지 재생) + +#### Urud & Lian - Reload 시스템 + +**Ability**: GA_Attack_Firearm_Reload_C + +**메커니즘**: +1. Reload 스킬 사용 시 재장전 몽타주 재생 +2. 재장전 완료 노티파이에서 AmmoTag 복구 +3. 재장전 중에는 공격 불가 (Blocking 상태) + +**특수 사항**: +- **Urud**: 탄약 6발, Reload 2.0초 +- **Lian**: 탄약 6발, Reload 2.0초 + Precision Aim (조준 시스템) + +**Lian의 Precision Aim**: +- 조준 버튼 누르면 줌 + 이동 속도 대폭 감소 +- 조준 중 명중률 증가 (히트박스 확대) + +#### Rio - Chain Score 시스템 (3 스택) + +**Ability**: GA_Skill_Rio_DroppingAttack_C (서브 스킬) + +**EventGraph 노드 수**: 42개 + +**주요 변수**: +| 변수명 | 타입 | 설명 | +|--------|------|------| +| `ChainScoreTag` | GameplayTag | 체인 스코어 태그 (Character.State.ChainScore) | +| `MaxChainScore` | Int | 최대 스택 (3) | +| `ChainScoreDamageBonus` | Float | 스택당 피해 증가율 | + +**메커니즘**: +1. Dropping Attack (공중 낙하 공격) 성공 시 ChainScore +1 +2. 스택이 쌓일수록 다음 공격의 피해량 증가 +3. 최대 3스택 유지, 일정 시간 경과 또는 피격 시 리셋 + +**예시**: +``` +Dropping Attack 성공 1회: ChainScore = 1, 다음 공격 피해 +10% +Dropping Attack 성공 2회: ChainScore = 2, 다음 공격 피해 +20% +Dropping Attack 성공 3회: ChainScore = 3, 다음 공격 피해 +30% (최대) +``` + +#### Rene - Spirit 소환 및 Lifesteal + +**Ability**: GA_Skill_Rene_SummonIfrit_C, GA_Skill_Rene_SummonShiva_C + +**EventGraph 노드 수**: Summon Ifrit (58개), Summon Shiva (55개) + +**메커니즘**: +1. 소환 스킬 사용 시 AI 제어 소환수 스폰 +2. 소환수는 독립적으로 적 공격 +3. Rene의 공격에 Lifesteal 효과 (GE_Lifesteal 적용) + +**Lifesteal**: +- 피해량의 일정 %를 HP로 회복 +- GameplayEffect로 구현 (Section 5 참조) + +#### Sinobu - Shuriken 충전 시스템 + +**Ability**: GA_Skill_Sinobu_Shuriken_C (서브 스킬) + +**주요 변수**: +| 변수명 | 타입 | 설명 | +|--------|------|------| +| `ShurikenChargeTag` | GameplayTag | 수리검 충전 태그 | +| `MaxShurikenCharge` | Int | 최대 충전 수 (3) | + +**메커니즘**: +1. 수리검 발사 시 ChargeTag -1 +2. 자동으로 시간 경과에 따라 충전 (1초당 1개) +3. 충전이 0이면 발사 불가 + +**Swap 인술** (SK180205 - Ninpo Change): +- 적 위치와 자신의 위치를 순간 교환 (텔레포트) +- 중력 조작 (Gravity = 0) 구간 존재 + +#### Lian - Charging Bow (3스택 충전) + +**Ability**: GA_Skill_Lian_ChargingBow_C (서브 스킬) + +**EventGraph 노드 수**: 116개 (가장 복잡한 서브 스킬) + +**주요 변수**: +| 변수명 | 타입 | 설명 | +|--------|------|------| +| `ChargeLevel` | Int | 충전 레벨 (0~3) | +| `ChargeTime` | Float | 레벨당 충전 시간 | +| `ChargeDamageMultiplier` | Array | 레벨별 피해 배율 | + +**메커니즘**: +1. 버튼 누름: 충전 시작 +2. 0.5초마다 ChargeLevel +1 (최대 3) +3. 버튼 떼기: 현재 ChargeLevel에 해당하는 피해로 화살 발사 +4. ChargeLevel 3 (만충전): 피해 2배 + 관통 효과 + +**피해 배율**: +``` +Level 0: 0.7x (즉발) +Level 1: 1.0x +Level 2: 1.5x +Level 3: 2.0x + 관통 +``` + +#### Cazimord - Flash 스택 & Parrying + +**Flash 시스템** (SK170201): + +**Ability**: GA_Skill_Cazimord_Flash_C + +**주요 변수**: +| 변수명 | 타입 | 설명 | +|--------|------|------| +| `FlashStackTag` | GameplayTag | Flash 스택 태그 | +| `MaxFlashStack` | Int | 최대 스택 (3) | + +**메커니즘**: +1. Flash 스킬 사용 시 준비 몽타주 재생 (1.0초) +2. 스택이 있으면 Flash_Active 몽타주 재생 (1.733초) +3. Flash_Active는 빠른 돌진 + 피해 +4. 스택은 Parrying 성공 시 충전 + +**ANS_SkillCancel_C**: Flash_Active에만 존재 (1.001~1.733초, 0.732초 윈도우) + +**Parrying 시스템** (SK170101): + +**Ability**: GA_Skill_Cazimord_Parrying_C + +**메커니즘**: +1. Parrying 발동 시 짧은 패링 판정 구간 (0.2초) +2. 패링 성공 시: + - 적 피해 무효화 + - 자동 반격 (높은 피해) + - FlashStackTag +1 + - Parrying 스킬 쿨타임 즉시 리셋 (자기 자신 쿨타임 감소) + +**특수 사항**: +- Parrying은 쿨타임 감소 로직을 가진 유일한 스킬 +- 패링 실패 시에도 쿨타임 적용 (리스크-리워드) + +--- + +## 5. GameplayEffect 메커니즘 + +### 5.1 GameplayEffect 개요 + +**GameplayEffect**는 언리얼 엔진의 Gameplay Ability System에서 캐릭터의 Attribute (HP, MP, 공격력 등)를 수정하는 메커니즘입니다. 모든 피해, 회복, 버프, 디버프는 GameplayEffect로 구현됩니다. + +#### 주요 GameplayEffect 타입 + +| 타입 | 지속 시간 | 용도 | 예시 | +|------|-----------|------|------| +| **Instant** | 즉시 적용 | 피해, 즉시 회복 | GE_Attack_Physical (공격 피해) | +| **Duration** | 정해진 시간 | 버프/디버프 | GE_Buff_AttackSpeed (5초간 공격 속도 증가) | +| **Infinite** | 영구 (수동 제거) | 장비 효과, 패시브 | GE_Equip_Weapon (무기 장착 보너스) | + +### 5.2 주요 GameplayEffect 클래스 + +#### GE_Attack_* (공격 피해) + +**DT_Skill의 gameplayEffectSet 또는 GA_* Blueprint에서 적용** + +**예시 - GE_Attack_Physical**: +- **타입**: Instant +- **Modifier**: HP -= (공격력 × skillDamageRate × 속성 배율) +- **적용 시점**: AnimNotifyState_AttackWithEquip 히트 판정 성공 시 + +**피해 계산 공식** (일반적): +``` +최종 피해 = 기본 공격력 × skillDamageRate × AddNormalAttackPer × (1 - 방어율) × 크리티컬 배율 × 속성 저항 +``` + +#### GE_Skill_* (스킬 효과) + +스킬별로 고유한 GameplayEffect를 가질 수 있습니다. + +**예시 - Hilda의 Sword Strike (SK100201)**: +- GE_Skill_Hilda_SwordStrike: Lightning 속성 피해 +- Lightning 저항 적용: `FinalDamage × (1 - Target.LightningResistancePer)` + +**예시 - Rene의 Lifesteal**: +- GE_Lifesteal_Rene: Duration 타입, 일정 시간 동안 모든 공격에 흡혈 효과 +- Modifier: HP += (DamageDealt × LifestealRate) + +#### GE_Buff/Debuff_* (버프/디버프) + +**예시 - GE_Buff_AttackSpeed**: +- **타입**: Duration (5초) +- **Modifier**: AttackSpeed += 20% +- **시각 효과**: 버프 아이콘 표시 + +**예시 - GE_Debuff_Poison** (Urud의 Poison Arrow): +- **타입**: Duration (10초) +- **Modifier**: HP -= 10 (매 1초마다, Periodic) +- **시각 효과**: 독 이펙트 파티클 + +#### GE_Cooldown_* (쿨타임) + +DT_Skill의 `coolTime` 값을 기반으로 자동 생성됩니다. + +**메커니즘**: +1. 스킬 사용 시 GE_Cooldown_[SkillID] 적용 +2. GameplayTag: `Cooldown.Skill.[SkillID]` 부여 +3. coolTime 초 경과 후 자동 제거 +4. 태그가 있는 동안 해당 스킬 사용 불가 + +**쿨타임 감소**: +- WSGameplayAbility의 `ReduceSkillCoolTime` 함수 사용 +- 특정 룬/장비 효과로 쿨타임 % 감소 가능 + +#### GE_ManaCost_* (마나 소모) + +DT_Skill의 `skillManaCost` 값을 기반으로 적용됩니다. + +**메커니즘**: +1. 스킬 CommitAbility 단계에서 마나 체크 +2. 마나 부족 시 스킬 발동 실패 +3. 마나 충분 시 GE_ManaCost 적용하여 MP 감소 + +### 5.3 GameplayEffectSet 구조 + +DT_Skill의 `gameplayEffectSet` 배열은 여러 GameplayEffect를 순차적으로 적용합니다. + +**구조**: +```json +"gameplayEffectSet": [ + { + "applyTrigger": "OnActivation", + "gameplayEffectClass": "/Game/Blueprints/Effects/GE_Skill_Buff.GE_Skill_Buff_C", + "targetType": "Self" + }, + { + "applyTrigger": "OnHit", + "gameplayEffectClass": "/Game/Blueprints/Effects/GE_Attack_Damage.GE_Attack_Damage_C", + "targetType": "Target" + } +] +``` + +**applyTrigger 타입**: +- `OnActivation`: 스킬 활성화 시 즉시 +- `OnHit`: 타격 성공 시 +- `OnEnd`: 스킬 종료 시 +- `Periodic`: 주기적으로 + +**targetType**: +- `Self`: 시전자에게 적용 +- `Target`: 타격 대상에게 적용 +- `Area`: 범위 내 모든 대상 + +### 5.4 특수 GameplayEffect 예시 + +#### Nave의 Fire Wall (지속 피해 장판) + +**메커니즘**: +1. Fire Wall 스킬 사용 시 바닥에 화염 장판 생성 +2. 장판 범위 내 적에게 GE_Debuff_FireWall_Periodic 적용 +3. Periodic 효과: 매 0.5초마다 Fire 속성 피해 + +**GameplayEffect**: +- **타입**: Duration (5초) +- **Period**: 0.5초 +- **Modifier**: HP -= (BaseDamage × 0.2) + +#### Baran의 Pulling (중력 조작) + +**메커니즘**: +1. Pulling 스킬 적중 시 GE_Pulling_Gravity 적용 +2. 적의 Gravity를 음수로 변경 (끌어당김) +3. 0.5초 동안 적을 Baran 방향으로 이동 + +**GameplayEffect**: +- **타입**: Duration (0.5초) +- **Modifier**: Gravity = -1000 (강제 이동) +- **Physics**: AddImpulse (Baran 방향) + +#### Clad의 Holy Cure (힐링) + +**메커니즘**: +1. Holy Cure 스킬 사용 시 GE_Heal_Holy 적용 +2. 자신 또는 아군의 HP 회복 + +**GameplayEffect**: +- **타입**: Instant +- **Modifier**: HP += (HealAmount × WIS) +- **targetType**: Self 또는 FriendlyTarget + +--- + +## 6. DT_Skill 특수 케이스 + +### 6.1 Nave의 궁극기 - Liberation (Escape 4) + +**스킬 ID**: SK120301 + +**특수 사항**: 대부분의 스킬은 AnimMontage의 노티파이(AnimNotifyState_AttackWithEquip)에서 피해를 처리하지만, Nave의 궁극기 'Liberation'은 **DT_Skill의 데이터에서 직접 피해를 정의**합니다. + +#### 일반 스킬의 피해 처리 (일반적 케이스) + +**흐름**: +``` +1. DT_Skill에서 skillDamageRate 정의 (예: 1.3) +2. AnimMontage의 AnimNotifyState_AttackWithEquip 노티파이 실행 +3. 노티파이에서 AttackTag (Event.Attack.Skill) 트리거 +4. GA_* Blueprint에서 skillDamageRate를 곱하여 피해 계산 +5. GE_Attack_* GameplayEffect 적용 +``` + +**예시 - Hilda Sword Strike**: +``` +DT_Skill: skillDamageRate = 1.3 +AnimMontage: AnimNotifyState_AttackWithEquip (TriggerTime: 0.6초) +Blueprint: ApplyAttackEffectToTarget 함수 호출 +GameplayEffect: GE_Attack_Lightning 적용 +최종 피해 = 기본 공격력 × 1.3 × Lightning 배율 +``` + +#### Nave 궁극기의 피해 처리 (특수 케이스) + +**흐름**: +``` +1. DT_Skill의 gameplayEffectSet에 직접 피해 GameplayEffect 정의 +2. AnimMontage에는 피해 노티파이 없음 (시각 효과만) +3. GA_Skill_Nave_Escape4 Blueprint에서 gameplayEffectSet 직접 적용 +4. 범위 내 모든 적에게 피해 (Area 타겟) +``` + +**DT_Skill 데이터 (SK120301)**: +```json +{ + "RowName": "SK120301", + "stalkerName": "nave", + "bIsUltimate": true, + "skillName": "Escape 4", + "skillDamageRate": 1.0, + "skillAttackType": "MagicalSkill", + "skillElementType": "None", + "useMontages": [ + "/Game/_Art/_Character/PC/Nave/AnimMontage/Ultimate/AM_PC_Nave_B_Skill_Escape4.AM_PC_Nave_B_Skill_Escape4" + ], + "abilityClass": "/Game/Blueprints/Characters/Nave/GA_Skill_Nave_Escape4.GA_Skill_Nave_Escape4_C", + "gameplayEffectSet": [ + { + "applyTrigger": "OnActivation", + "gameplayEffectClass": "/Game/Blueprints/Effects/GE_Skill_Nave_Liberation.GE_Skill_Nave_Liberation_C", + "targetType": "Area" + } + ] +} +``` + +**GE_Skill_Nave_Liberation**: +- **타입**: Instant +- **범위**: 반경 500 units +- **Modifier**: HP -= (MagicPower × 1.0) × 4회 (4번의 텔레포트 각각 피해) +- **특징**: 텔레포트 이동 경로에 있는 모든 적 피해 + +**AnimMontage (AM_PC_Nave_B_Skill_Escape4)**: +- Duration: 3.5초 +- ANS_SkillCancel_C: 없음 +- 피해 판정 노티파이: 없음 (GameplayEffect에서 직접 처리) +- 시각 효과 노티파이만 존재 (4회 텔레포트 이펙트) + +#### 왜 이렇게 구현했는가? + +**일반 스킬 방식의 한계**: +- AnimNotifyState_AttackWithEquip은 무기 궤적 기반 히트 판정 +- Nave의 궁극기는 텔레포트 경로 전체가 피해 범위 +- 무기가 없고, 정해진 궤적이 없음 + +**gameplayEffectSet 직접 사용 장점**: +- 복잡한 범위 판정을 GameplayEffect 로직으로 처리 +- 4회 텔레포트의 각 경로마다 별도 피해 적용 가능 +- AnimMontage와 피해 로직 분리로 유연성 확보 + +### 6.2 Urud의 궁극기 - Explosion (범위 피해) + +**스킬 ID**: SK110301 + +**특수 사항**: 투척한 돌이 지면 또는 적 적중 시 폭발하여 범위 피해를 줍니다. + +**메커니즘**: +1. 궁극기 활성화 시 투사체(돌) 스폰 +2. 투사체 충돌 시 Explosion Actor 생성 +3. Explosion Actor의 Overlap 이벤트에서 범위 내 적 검색 +4. 각 적에게 GE_Skill_Urud_Explosion 적용 + +**특징**: +- 피해는 AnimMontage가 아닌 투사체 충돌 이벤트에서 처리 +- DT_Skill의 skillDamageRate는 기본 배율로 사용 +- 범위 중심부일수록 높은 피해 (거리 기반 감쇠) + +--- + +## 7. 코드-어셋 통합 흐름 + +### 7.1 스킬 실행 전체 흐름 + +스킬이 입력부터 피해 적용까지 거치는 전체 과정을 정리합니다. + +``` +[1] 입력 처리 + ↓ +[2] DT_CharacterStat에서 스킬 ID 조회 + ↓ +[3] DT_Skill에서 스킬 데이터 로드 + ├─ skillDamageRate + ├─ coolTime + ├─ skillManaCost + ├─ useMontages + └─ abilityClass + ↓ +[4] GA_* Blueprint Ability 실행 + ├─ CanActivateAbility: 쿨타임/마나 체크 + ├─ ActivateAbility: 스킬 시작 + │ ├─ Activation Order Group 비교 + │ └─ 현재 스킬 캔슬 여부 결정 + ├─ CommitAbility: 마나/쿨타임 소모 확정 + │ ├─ GE_ManaCost 적용 + │ └─ GE_Cooldown 적용 + └─ PlayMontage: AnimMontage 재생 + ↓ +[5] AnimMontage 재생 + ├─ ANS_AttackState_C: 공격 상태 활성화 + ├─ AnimNotifyState_AttackWithEquip: 히트 판정 + │ ├─ AttackTag 기반 이벤트 트리거 + │ └─ 범위 내 적 검색 + ├─ ANS_SkillCancel_C: 캔슬 윈도우 활성화 + └─ AN_SetAutoTarget_C: 자동 타게팅 + ↓ +[6] 피해 계산 (GA_* Blueprint) + ├─ FindSkillDataRow: DT_Skill 데이터 재조회 + ├─ CalculateSkillRate: 최종 피해 배율 계산 + │ ├─ skillDamageRate (기본) + │ ├─ AddNormalAttackPer (몽타주) + │ ├─ 장비 보정 (EquipSkillModify) + │ └─ 룬 보정 + └─ ApplyAttackEffectToTarget: GameplayEffect 적용 + ↓ +[7] GameplayEffect 적용 + ├─ GE_Attack_* 또는 GE_Skill_* + ├─ Attribute Modifier 실행 + │ ├─ Target HP 감소 + │ ├─ 속성 저항 계산 + │ └─ 크리티컬 판정 + └─ 시각/사운드 효과 재생 + ↓ +[8] 스킬 종료 + ├─ EndAbility: Ability 정리 + │ ├─ ActiveGameplayEffectHandle 제거 + │ └─ 카메라 모드 해제 + └─ 다음 입력 대기 +``` + +### 7.2 캔슬 판정 상세 흐름 + +스킬 실행 중 새로운 입력이 들어왔을 때의 판정 과정: + +``` +새 스킬 입력 (예: Sword Strike → Counter) + ↓ +[1] 현재 실행 중인 Ability 확인 + ├─ CurrentAbility: GA_Skill_Hilda_SwordStrike + ├─ CurrentAbility.ActivationOrderGroup: 2 + └─ CurrentTime: 0.5초 (몽타주 재생 중) + ↓ +[2] 새 Ability 정보 확인 + ├─ NewAbility: GA_Skill_Knight_Counter + └─ NewAbility.ActivationOrderGroup: 3 + ↓ +[3] 캔슬 가능 여부 판정 (OR 조건) + ├─ 조건 A: Activation Order Group 비교 + │ └─ 3 > 2 → TRUE (높은 우선순위) + ├─ 조건 B: ANS_SkillCancel_C 윈도우 체크 + │ ├─ CurrentMontage: AM_PC_Hilda_B_Skill_SwordStrike + │ ├─ SkillCancel 구간: 1.3~1.8초 + │ └─ CurrentTime 0.5초 < 1.3초 → FALSE (윈도우 밖) + └─ 최종 결과: TRUE (조건 A 만족) + ↓ +[4] 캔슬 실행 + ├─ CurrentAbility.EndAbility(bWasCancelled=true) + ├─ NewAbility.ActivateAbility() + └─ Counter 몽타주 재생 시작 +``` + +### 7.3 C++ 코드와 어셋의 연동 지점 + +| C++ 클래스 | 어셋 | 연동 방법 | +|------------|------|-----------| +| `UWSGameplayAbility` | GA_* Blueprint | Blueprint이 C++ 클래스 상속 | +| `ActivationOrderGroup` (C++ 변수) | GA_* Class Defaults | Blueprint 에디터에서 값 설정 | +| `FSkillDataRow` (C++ 구조체) | DT_Skill DataTable | DataTable의 RowStructure로 사용 | +| `ApplyAttackEffectToTarget` (C++ 함수) | AnimNotifyState_AttackWithEquip | 노티파이에서 함수 호출 | +| `UGameplayEffect` | GE_* Blueprint | GameplayEffect 서브클래스 | + +**핵심 연동 함수**: +```cpp +// WSGameplayAbility.cpp +FSkillDataRow* UWSGameplayAbility::FindSkillDataRow(FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo) const +{ + // DT_Skill에서 현재 스킬의 행 데이터 조회 + // abilityClass 경로로 역참조 +} + +void UWSGameplayAbility::ApplyAttackEffectToTarget(FGameplayAbilityTargetDataHandle TargetData, FGameplayTag InAttackTag, bool& bAttackFailMotion) +{ + // AnimMontage 노티파이에서 호출 + // DT_Skill 데이터 + 장비 보정 + 룬 보정 통합 계산 + // 최종 GameplayEffect 적용 +} +``` + +--- + +## 8. JSON 사용 가이드 + +### 8.1 JSON 파일 구조 이해 + +익스포트된 JSON 파일들은 다음과 같은 구조를 가집니다: + +**DataTable.json**: +```json +[ + { + "AssetName": "DT_CharacterStat", + "AssetPath": "/Game/Blueprints/DataTable/DT_CharacterStat.DT_CharacterStat", + "RowStructure": "CharacterStatData", + "Rows": [ + { + "RowName": "hilda", + "Data": { ... } + } + ] + } +] +``` + +**AnimMontage.json**: +```json +[ + { + "AssetName": "AM_PC_Hilda_B_Skill_SwordStrike", + "AssetPath": "/Game/_Art/_Character/PC/Hilda/AnimMontage/Skill/AM_PC_Hilda_B_Skill_SwordStrike.AM_PC_Hilda_B_Skill_SwordStrike", + "SequenceLength": 1.8, + "Sections": [...], + "AnimNotifies": [...] + } +] +``` + +**Blueprint.json**: +```json +[ + { + "AssetName": "GA_Skill_Hilda_SwordStrike", + "AssetPath": "/Game/Blueprints/Characters/Hilda/GA_Skill_Hilda_SwordStrike.GA_Skill_Hilda_SwordStrike", + "ParentClass": "GA_WSGameplayAbilityBase_C", + "Variables": [...], + "Functions": [...], + "EventGraphs": [...] + } +] +``` + +### 8.2 데이터 추출 방법 + +#### Python을 사용한 JSON 파싱 예시 + +```python +import json + +# 1. DataTable에서 특정 스토커의 스킬 목록 조회 +with open('DataTable.json', 'r', encoding='utf-8') as f: + datatables = json.load(f) + +# DT_CharacterStat 찾기 +dt_char_stat = next((dt for dt in datatables if dt['AssetName'] == 'DT_CharacterStat'), None) + +# Hilda의 스킬 목록 +hilda_data = next((row for row in dt_char_stat['Rows'] if row['RowName'] == 'hilda'), None) +default_skills = hilda_data['Data']['defaultSkills'] # ["SK100201", "SK100202", "SK100204"] + +# 2. DT_Skill에서 스킬 상세 정보 조회 +dt_skill = next((dt for dt in datatables if dt['AssetName'] == 'DT_Skill'), None) + +for skill_id in default_skills: + skill_data = next((row for row in dt_skill['Rows'] if row['RowName'] == skill_id), None) + print(f"{skill_id}: {skill_data['Data']['skillName']}, Damage: {skill_data['Data']['skillDamageRate']}") +``` + +#### 특정 노티파이를 가진 AnimMontage 검색 + +```python +with open('AnimMontage.json', 'r', encoding='utf-8') as f: + montages = json.load(f) + +# ANS_SkillCancel_C를 가진 모든 몽타주 찾기 +cancel_montages = [] +for montage in montages: + for notify in montage.get('AnimNotifies', []): + if notify.get('NotifyStateClass') == 'ANS_SkillCancel_C': + cancel_montages.append({ + 'montage': montage['AssetName'], + 'start': notify['TriggerTime'], + 'duration': notify['Duration'] + }) + +for item in cancel_montages: + print(f"{item['montage']}: {item['start']}~{item['start']+item['duration']}초") +``` + +### 8.3 크로스 레퍼런스 (어셋 간 연결 추적) + +스킬 하나의 전체 정보를 추적하는 방법: + +```python +# 1. DT_CharacterStat에서 스토커 → 스킬 ID +stalker_name = 'hilda' +skill_id = 'SK100201' # DT_CharacterStat.hilda.defaultSkills[0] + +# 2. DT_Skill에서 스킬 ID → 몽타주 경로, Ability 경로 +skill_row = find_skill_by_id(skill_id) +montage_paths = skill_row['Data']['useMontages'] +ability_path = skill_row['Data']['abilityClass'] + +# 3. AnimMontage.json에서 몽타주 경로 → 타이밍 데이터 +montage_asset_name = montage_paths[1].split('.')[-1] # "AM_PC_Hilda_B_Skill_SwordStrike" +montage_data = find_montage_by_name(montage_asset_name) + +# ANS_SkillCancel_C 추출 +cancel_notify = next((n for n in montage_data['AnimNotifies'] + if n.get('NotifyStateClass') == 'ANS_SkillCancel_C'), None) + +# 4. Blueprint.json에서 Ability 경로 → 로직 구조 +ability_asset_name = ability_path.split('/')[-1].split('.')[0] # "GA_Skill_Hilda_SwordStrike" +ability_data = find_blueprint_by_name(ability_asset_name) + +# 최종 통합 정보 +skill_info = { + 'id': skill_id, + 'name': skill_row['Data']['skillName'], + 'damage_rate': skill_row['Data']['skillDamageRate'], + 'cooldown': skill_row['Data']['coolTime'], + 'montage_duration': montage_data['SequenceLength'], + 'cancel_window': f"{cancel_notify['TriggerTime']}~{cancel_notify['TriggerTime']+cancel_notify['Duration']}" if cancel_notify else "없음", + 'ability_complexity': len(ability_data['EventGraphs'][0]['Nodes']) +} +``` + +### 8.4 주의사항 + +**1. 라인 번호 참조 금지**: +- JSON 파일은 익스포트할 때마다 순서가 바뀔 수 있음 +- 항상 AssetName, RowName으로 검색 + +**2. 경로 형식 이해**: +``` +언리얼 경로: /Game/Blueprints/Abilities/GA_Attack.GA_Attack_C + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 에셋 경로 + ^^^^^^^^^^^ 클래스 이름 + +JSON에서 찾을 때: "GA_Attack" (확장자 제외) +``` + +**3. Blueprint Class Defaults 값 (ActivationOrderGroup) 포함**: +- EventGraph 노드 구조: JSON 포함 +- Class Defaults 값 (ActivationOrderGroup): JSON에 **포함됨** (Variables 배열 내) +- 추출 방법: Blueprint의 Variables 배열에서 "Name": "ActivationOrderGroup" 검색하여 DefaultValue 확인 + +**4. 몽타주 경로 추출**: +```python +# DT_Skill.useMontages 배열 +useMontages = [ + "/Script/Engine.AnimMontage'/Game/_Art/_Character/PC/Hilda/AnimMontage/Skill/AM_PC_Hilda_B_Skill_Ready.AM_PC_Hilda_B_Skill_Ready'", + "/Script/Engine.AnimMontage'/Game/_Art/_Character/PC/Hilda/AnimMontage/Skill/AM_PC_Hilda_B_Skill_SwordStrike.AM_PC_Hilda_B_Skill_SwordStrike'" +] + +# 몽타주 이름 추출 +montage_names = [path.split('/')[-1].split('.')[0] for path in useMontages] +# ["AM_PC_Hilda_B_Skill_Ready", "AM_PC_Hilda_B_Skill_SwordStrike"] +``` + +### 8.5 유용한 분석 패턴 + +**패턴 1: 모든 스킬의 평균 쿨타임 계산** +```python +skills = [row for dt in datatables if dt['AssetName'] == 'DT_Skill' for row in dt['Rows']] +avg_cooldown = sum(skill['Data']['coolTime'] for skill in skills) / len(skills) +``` + +**패턴 2: 특정 스토커의 총 EventGraph 노드 수 (복잡도)** +```python +stalker_abilities = [bp for bp in blueprints if 'Hilda' in bp['AssetName']] +total_nodes = sum(len(eg['Nodes']) for bp in stalker_abilities for eg in bp['EventGraphs']) +``` + +**패턴 3: ANS_SkillCancel_C 보유율** +```python +total_montages = len(montages) +cancel_montages = len([m for m in montages if any(n.get('NotifyStateClass') == 'ANS_SkillCancel_C' for n in m.get('AnimNotifies', []))]) +cancel_rate = (cancel_montages / total_montages) * 100 +``` + +--- + +## 문서 종료 + +본 문서는 언리얼 엔진 에셋(DataTable, AnimMontage, Blueprint)을 JSON으로 익스포트하여 분석한 WorldStalker 전투 로직 시스템의 기술 문서입니다. + +**작성 정보**: +- **작성일**: 2025-10-23 +- **익스포트 데이터**: `DS-전투밸런스_분석자료/20251023_114317/` +- **분석 대상**: 10명의 스토커 (Hilda, Urud, Nave, Baran, Rio, Clad, Rene, Sinobu, Lian, Cazimord) + +**추가 참고 문서**: +- `DS-기획_메뉴얼-전투_로직.md`: 전투 로직 기획 문서 +- `DS-Asset_Export_to_JSON.md`: Asset Export 시스템 가이드 + +**문의**: jinilkim@oneunivrs.com \ No newline at end of file diff --git a/분석도구/analyze_character_stats.py b/분석도구/analyze_character_stats.py new file mode 100644 index 0000000..8187e4a --- /dev/null +++ b/분석도구/analyze_character_stats.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +캐릭터 스탯 분석 스크립트 + +DT_CharacterStat 테이블에서 스토커들의 기본 스탯을 추출하고 비교 분석합니다. + +사용법: + python analyze_character_stats.py +""" + +import json +import sys +from pathlib import Path + + +def find_character_stat_table(datatables): + """DT_CharacterStat 테이블 찾기""" + for dt in datatables: + if dt.get('AssetName') == 'DT_CharacterStat': + return dt + return None + + +def analyze_stats(json_path): + """캐릭터 스탯 분석""" + + with open(json_path, 'r', encoding='utf-8') as f: + datatables = json.load(f) + + char_stat_table = find_character_stat_table(datatables) + + if not char_stat_table: + print("오류: DT_CharacterStat 테이블을 찾을 수 없습니다.") + return + + stalkers = [] + + for row in char_stat_table.get('Rows', []): + data = row['Data'] + stalker_info = { + 'id': row['RowName'], + 'name': data.get('name', ''), + 'job': data.get('jobName', ''), + 'str': data.get('str', 0), + 'dex': data.get('dex', 0), + 'int': data.get('int', 0), + 'con': data.get('con', 0), + 'wis': data.get('wis', 0), + 'hp': data.get('hP', 0), + 'mp': data.get('mP', 0) + } + stalkers.append(stalker_info) + + return stalkers + + +def print_stat_table(stalkers): + """스탯 테이블 출력""" + print("\n스토커별 기본 스탯") + print("=" * 100) + print(f"{'이름':<10} {'직업':<10} {'STR':>5} {'DEX':>5} {'INT':>5} {'CON':>5} {'WIS':>5} {'HP':>5} {'MP':>5}") + print("-" * 100) + + for s in stalkers: + print(f"{s['name']:<10} {s['job']:<10} {s['str']:>5} {s['dex']:>5} {s['int']:>5} {s['con']:>5} {s['wis']:>5} {s['hp']:>5} {s['mp']:>5}") + + +def print_stat_rankings(stalkers): + """스탯별 랭킹 출력""" + print("\n\n스탯별 랭킹") + print("=" * 100) + + stats = ['str', 'dex', 'int', 'con', 'wis'] + stat_names = {'str': 'STR', 'dex': 'DEX', 'int': 'INT', 'con': 'CON', 'wis': 'WIS'} + + for stat in stats: + sorted_stalkers = sorted(stalkers, key=lambda x: x[stat], reverse=True) + top3 = sorted_stalkers[:3] + + print(f"\n{stat_names[stat]} 순위:") + for i, s in enumerate(top3, 1): + print(f" {i}위: {s['name']} ({s[stat]})") + + +def main(): + if len(sys.argv) < 2: + print("사용법: python analyze_character_stats.py ") + sys.exit(1) + + json_path = Path(sys.argv[1]) + + if not json_path.exists(): + print(f"오류: 파일을 찾을 수 없습니다: {json_path}") + sys.exit(1) + + print(f"분석 중: {json_path}") + + stalkers = analyze_stats(json_path) + + if stalkers: + print(f"\n총 {len(stalkers)}명의 스토커 발견") + print_stat_table(stalkers) + print_stat_rankings(stalkers) + + +if __name__ == "__main__": + main() diff --git a/분석도구/extract_activation_order_groups.py b/분석도구/extract_activation_order_groups.py new file mode 100644 index 0000000..0c25a19 --- /dev/null +++ b/분석도구/extract_activation_order_groups.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +""" +Activation Order Group 추출 스크립트 + +Blueprint.json에서 스토커별 스킬의 ActivationOrderGroup 값을 추출합니다. + +사용법: + python extract_activation_order_groups.py +""" + +import json +import sys +from pathlib import Path +from collections import defaultdict + + +def extract_activation_order_groups(json_path): + """Blueprint.json에서 ActivationOrderGroup 추출""" + + with open(json_path, 'r', encoding='utf-8') as f: + blueprints = json.load(f) + + # 스토커별 스킬 그룹화 + stalker_skills = defaultdict(list) + + stalkers = ['Hilda', 'Urud', 'Nave', 'Baran', 'Rio', 'Clad', 'Rene', 'Sinobu', 'Lian', 'Cazimord'] + + for bp in blueprints: + asset_name = bp.get('AssetName', '') + + # GA_Skill_{Stalker}_ 패턴 찾기 + if asset_name.startswith('GA_Skill_'): + for stalker in stalkers: + if f'_{stalker}_' in asset_name: + # ActivationOrderGroup 찾기 + activation_order = None + for var in bp.get('Variables', []): + if var.get('Name') == 'ActivationOrderGroup': + activation_order = var.get('DefaultValue', '0') + break + + skill_name = asset_name.replace(f'GA_Skill_{stalker}_', '') + + stalker_skills[stalker].append({ + 'skill': skill_name, + 'order_group': int(activation_order) if activation_order else 0, + 'full_name': asset_name + }) + + return stalker_skills + + +def print_stalker_skills(stalker_skills): + """스토커별 스킬과 ActivationOrderGroup 출력""" + + print("\n스토커별 Activation Order Group") + print("=" * 100) + + for stalker, skills in sorted(stalker_skills.items()): + print(f"\n{stalker}:") + + # Order Group별로 정렬 + skills_by_group = defaultdict(list) + for skill in skills: + skills_by_group[skill['order_group']].append(skill['skill']) + + for group in sorted(skills_by_group.keys(), reverse=True): + print(f" Group {group}: {', '.join(sorted(skills_by_group[group]))}") + + +def print_statistics(stalker_skills): + """통계 정보 출력""" + + print("\n\n통계") + print("=" * 100) + + # 각 Group별 사용 빈도 + group_count = defaultdict(int) + for stalker, skills in stalker_skills.items(): + for skill in skills: + group_count[skill['order_group']] += 1 + + print("\nGroup별 스킬 수:") + for group in sorted(group_count.keys(), reverse=True): + print(f" Group {group}: {group_count[group]}개") + + # 스토커별 스킬 수 + print("\n스토커별 스킬 수:") + for stalker, skills in sorted(stalker_skills.items(), key=lambda x: len(x[1]), reverse=True): + print(f" {stalker}: {len(skills)}개") + + +def main(): + if len(sys.argv) < 2: + print("사용법: python extract_activation_order_groups.py ") + sys.exit(1) + + json_path = Path(sys.argv[1]) + + if not json_path.exists(): + print(f"오류: 파일을 찾을 수 없습니다: {json_path}") + sys.exit(1) + + print(f"분석 중: {json_path}") + + stalker_skills = extract_activation_order_groups(json_path) + + print(f"\n총 {sum(len(skills) for skills in stalker_skills.values())}개의 스킬 발견") + + print_stalker_skills(stalker_skills) + print_statistics(stalker_skills) + + +if __name__ == "__main__": + main() diff --git a/분석도구/extract_skill_cancel_windows.py b/분석도구/extract_skill_cancel_windows.py new file mode 100644 index 0000000..7a2f735 --- /dev/null +++ b/분석도구/extract_skill_cancel_windows.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +스킬 캔슬 윈도우 추출 스크립트 + +AnimMontage.json에서 ANS_SkillCancel_C 노티파이를 가진 몽타주를 찾아 +캔슬 가능 시간 구간을 추출합니다. + +사용법: + python extract_skill_cancel_windows.py +""" + +import json +import sys +from pathlib import Path + + +def extract_cancel_windows(json_path): + """AnimMontage.json에서 스킬 캔슬 윈도우 추출""" + + with open(json_path, 'r', encoding='utf-8') as f: + montages = json.load(f) + + cancel_montages = [] + + for montage in montages: + asset_name = montage.get('AssetName', '') + + # ANS_SkillCancel_C 노티파이 찾기 + for notify in montage.get('AnimNotifies', []): + if notify.get('NotifyStateClass') == 'ANS_SkillCancel_C': + trigger_time = notify['TriggerTime'] + duration = notify['Duration'] + + cancel_montages.append({ + 'montage': asset_name, + 'start': trigger_time, + 'end': trigger_time + duration, + 'duration': duration + }) + + return cancel_montages + + +def main(): + if len(sys.argv) < 2: + print("사용법: python extract_skill_cancel_windows.py ") + sys.exit(1) + + json_path = Path(sys.argv[1]) + + if not json_path.exists(): + print(f"오류: 파일을 찾을 수 없습니다: {json_path}") + sys.exit(1) + + print(f"분석 중: {json_path}") + print("-" * 80) + + cancel_windows = extract_cancel_windows(json_path) + + print(f"\n총 {len(cancel_windows)}개의 스킬 캔슬 윈도우 발견\n") + + for item in cancel_windows: + print(f"{item['montage']}") + print(f" 캔슬 구간: {item['start']:.3f}s ~ {item['end']:.3f}s (지속: {item['duration']:.3f}s)") + print() + + +if __name__ == "__main__": + main() diff --git a/원본데이터/20251023/샘플_AnimMontage.json b/원본데이터/20251023/샘플_AnimMontage.json new file mode 100644 index 0000000..721c76d --- /dev/null +++ b/원본데이터/20251023/샘플_AnimMontage.json @@ -0,0 +1,74 @@ +[ + { + "AssetName": "AM_PC_Hilda_B_Attack_W01_01", + "AssetPath": "/Game/_Art/_Character/PC/Hilda/AnimMontage/Base/AM_PC_Hilda_B_Attack_W01_01.AM_PC_Hilda_B_Attack_W01_01", + "SequenceLength": 1.6000000238418579, + "RateScale": 1, + "Sections": [ + { + "SectionName": "Default", + "StartTime": 0, + "NextSectionName": "None" + } + ], + "NumSections": 1, + "SlotAnimTracks": [ + { + "SlotName": "DefaultSlot", + "AnimSegments": [ + { + "AnimReference": "Ani_PC_Hilda_Base_B_Attack_W01_01", + "AnimPath": "/Game/_Art/_Character/PC/Hilda/Animations/Base/Ani_PC_Hilda_Base_B_Attack_W01_01.Ani_PC_Hilda_Base_B_Attack_W01_01", + "StartPos": 0, + "AnimStartTime": 0, + "AnimEndTime": 1.6000000238418579, + "AnimPlayRate": 1, + "LoopingCount": 1 + } + ] + } + ], + "AnimNotifies": [ + { + "NotifyName": "ANS_AttackBlock_C", + "TriggerTime": 0, + "Duration": 0.80000001192092896, + "NotifyType": "NotifyState", + "NotifyStateClass": "ANS_AttackState_C", + "CustomProperties": { + "AddGameplayTags": "(GameplayTags=((TagName=\"Ability.BlockGroup.SubAttack\"),(TagName=\"Character.State.Attack\")))", + "AttackMoveSpeedEffect": "/Script/Engine.BlueprintGeneratedClass'/Game/Blueprints/Abilities/GE_AttackingWalkSpeedDown.GE_AttackingWalkSpeedDown_C'", + "AddNormalAttackPer": "30.000000", + "AddPhysicalAttackPer": "", + "NotifyColor": "(B=200,G=198,R=202,A=255)", + "bShouldFireInEditor": "True" + }, + "IsBranchingPoint": true + }, + { + "NotifyName": "AttackWithEquip", + "TriggerTime": 0.73333334922790527, + "Duration": 0.13333334028720856, + "NotifyType": "NotifyState", + "NotifyStateClass": "AnimNotifyState_AttackWithEquip", + "CustomProperties": { + "AttackTag": "(TagName=\"Event.Attack.Normal\")", + "PreviewNS": "/Game/_Art/FX/Effects/Common/NS_Hit_DirectionalE001.NS_Hit_DirectionalE001", + "bUseEffectNormal": "True", + "EffectNormal": "(X=0.000000,Y=-1.500000,Z=-1.000000)", + "SocketName": "socket_R_Weapon", + "bSendShotEventToActor": "True", + "NotifyColor": "(B=111,G=0,R=255,A=255)", + "bShouldFireInEditor": "True" + }, + "IsBranchingPoint": true + } + ], + "BlendInTime": 0.25, + "BlendOutTime": 0.25, + "BlendOutTriggerTime": -1, + "BlendModeIn": "Standard", + "BlendModeOut": "Standard", + "Notes": "샘플: Hilda 기본 공격 1타 몽타주 (주요 Notify만 포함, 구조 참고용)" + } +] diff --git a/원본데이터/20251023/샘플_Blueprint.json b/원본데이터/20251023/샘플_Blueprint.json new file mode 100644 index 0000000..ce7955f --- /dev/null +++ b/원본데이터/20251023/샘플_Blueprint.json @@ -0,0 +1,77 @@ +[ + { + "AssetName": "GA_Skill_Hilda_SwordStrike", + "ParentClass": "GA_Skill_Knight_LeapAttack_C", + "Variables": [ + { + "Name": "bActiveOnGive", + "Type": "bool", + "DefaultValue": "False", + "IsEditable": true, + "IsBlueprintVisible": false, + "IsBlueprintReadOnly": false, + "IsEditDefaultsOnly": true, + "CategoryName": "WorldStalker", + "Source": "C++ParentClass", + "OwnerClass": "WSGameplayAbility" + }, + { + "Name": "ActivationOrderGroup", + "Type": "uint8", + "DefaultValue": "4", + "IsEditable": true, + "IsBlueprintVisible": false, + "IsBlueprintReadOnly": false, + "IsEditDefaultsOnly": true, + "CategoryName": "WorldStalker", + "Source": "C++ParentClass", + "OwnerClass": "WSGameplayAbility" + }, + { + "Name": "AttackEffectClass", + "Type": "TSoftClassPtr ", + "DefaultValue": "/Game/Blueprints/Abilities/GE_Attack_Ability.GE_Attack_Ability_C", + "IsEditable": true, + "IsBlueprintVisible": false, + "IsBlueprintReadOnly": false, + "IsEditDefaultsOnly": true, + "CategoryName": "WorldStalker", + "Source": "C++ParentClass", + "OwnerClass": "WSGameplayAbility" + }, + { + "Name": "ManaCostEffectClass", + "Type": "TSoftClassPtr ", + "DefaultValue": "/Game/Blueprints/Abilities/GE_Skill_ManaCost.GE_Skill_ManaCost_C", + "IsEditable": true, + "IsBlueprintVisible": false, + "IsBlueprintReadOnly": false, + "IsEditDefaultsOnly": true, + "CategoryName": "WorldStalker", + "Source": "C++ParentClass", + "OwnerClass": "WSGameplayAbility" + }, + { + "Name": "CoolTimeEffectClass", + "Type": "TSoftClassPtr ", + "DefaultValue": "/Game/Blueprints/Abilities/GE_Skill_CoolTime.GE_Skill_CoolTime_C", + "IsEditable": true, + "IsBlueprintVisible": false, + "IsBlueprintReadOnly": false, + "IsEditDefaultsOnly": true, + "CategoryName": "WorldStalker", + "Source": "C++ParentClass", + "OwnerClass": "WSGameplayAbility" + } + ], + "EventGraphs": [ + { + "GraphName": "EventGraph", + "Nodes": [ + "(예시) 실제로는 수십~수백 개의 노드가 포함됨" + ] + } + ], + "Notes": "샘플: Hilda SwordStrike 스킬 Blueprint (주요 Variables만 포함, 구조 참고용)" + } +] diff --git a/원본데이터/20251023/샘플_DataTable.json b/원본데이터/20251023/샘플_DataTable.json new file mode 100644 index 0000000..482567a --- /dev/null +++ b/원본데이터/20251023/샘플_DataTable.json @@ -0,0 +1,67 @@ +[ + { + "AssetName": "DT_CharacterStat", + "AssetPath": "/Game/Blueprints/DataTable/DT_CharacterStat.DT_CharacterStat", + "RowStructure": "CharacterStatData", + "Rows": [ + { + "RowName": "hilda", + "Data": { + "name": "힐다", + "jobName": "전사", + "capsuleRadius": 34, + "str": 20, + "dex": 15, + "int": 10, + "con": 20, + "wis": 10, + "hP": 100, + "mP": 50, + "manaRegen": 0.20000000298023224, + "stamina": 100, + "physicalDamage": 0, + "magicalDamage": 0, + "criticalPer": 5, + "criticalDamage": 0, + "backAttackDamage": 0, + "defense": 0, + "physicalResistancePer": 0, + "rangedResistancePer": 0, + "magicalResistancePer": 0, + "fireResistancePer": 0, + "poisonResistancePer": 0, + "waterResistancePer": 0, + "lightningResistancePer": 0, + "holyResistancePer": 0, + "darkResistancePer": 0, + "dOTReduceRatePer": 0, + "walkSpeed": 0, + "defaultSkills": [ + "SK100201", + "SK100202", + "SK100204" + ], + "subSkill": "SK100101", + "ultimateSkill": "SK100301", + "abilities": [], + "tags": { + "gameplayTags": [] + }, + "montageMap": {}, + "defaultEquip": {}, + "equipableTypes": [ + "WeaponShield", + "Heavy", + "Light" + ], + "hitRadius": 170, + "ultimatePoint": 2495, + "breakdownMax": -1, + "breakdownStunTime": 0, + "breakdownResetTime": 0 + } + } + ], + "Notes": "샘플: Hilda 스토커 기본 스탯 데이터 (구조 참고용)" + } +]