feat: 젬 이상상태 및 수치형 보정 확장

- SkillGemData와 SkillLoadoutEntry를 확장해 자기 강화/적중 이상상태와 피해·회복·보호막·위협 배율을 해석하도록 정리
- SkillController와 SkillEffect 경로를 보강해 cast start 이상상태, on-hit 이상상태, 반복 시전, 출력 보정이 실제 효과에 반영되도록 연결
- 강인함/약화 테스트 젬과 자기강화/적중이상/상태복합 프리셋, 디버그 메뉴를 추가해 젬 조합 검증 경로를 보강
- 런타임에서 약화 디버프 적용, 강인함+약화 동시 적용, 파쇄/수호/도전자 보정값 해석을 확인
This commit is contained in:
2026-03-26 13:40:06 +09:00
parent 78dbbbf88d
commit 8a1f11d134
28 changed files with 799 additions and 21 deletions

View File

@@ -0,0 +1,48 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 26d5895a89de4f24aade1ea4b5f7644e, type: 3}
m_Name: "Data_LoadoutPreset_Player_\uC0C1\uD0DC\uBCF5\uD569\uC82C\uD14C\uC2A4\uD2B8"
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.PlayerLoadoutPreset
presetName: "\uC0C1\uD0DC \uBCF5\uD569 \uC82C \uD14C\uC2A4\uD2B8"
description: "\uAC15\uC778\uD568 + \uC57D\uD654 \uC82C\uC744 \uB3D9\uC2DC\uC5D0
\uC0AC\uC6A9\uD574 \uC790\uAE30 \uAC15\uD654\uC640 \uC801\uC911 \uB514\uBC84\uD504\uB97C
\uD568\uAED8 \uAC80\uC99D\uD558\uB294 \uD504\uB9AC\uC14B"
slots:
- baseSkill: {fileID: 11400000, guid: b7f09e0e899c8fc4bb2cc9204cc6eb4a, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: b8c86399865e91144a3d6fcfddc04fd9, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: 549a9978338eb504690c3c490acc0c60, type: 2}
socketedGems:
- {fileID: 11400000, guid: e020eee86f6c97f4393672759d73602e, type: 2}
- {fileID: 11400000, guid: 2edf7687dc6caa0489ae2111499fcfab, type: 2}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: 2ed15dca92a165046b6df17b28f64874, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e73dbec0f2d31484ea06a23369e88424
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 26d5895a89de4f24aade1ea4b5f7644e, type: 3}
m_Name: "Data_LoadoutPreset_Player_\uC790\uAE30\uAC15\uD654\uC82C\uD14C\uC2A4\uD2B8"
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.PlayerLoadoutPreset
presetName: "\uC790\uAE30\uAC15\uD654 \uC82C \uD14C\uC2A4\uD2B8"
description: "\uAC15\uC778\uD568 \uC82C\uC73C\uB85C \uC2DC\uC804 \uC2DC \uC790\uAE30
\uAC15\uD654 \uC0C1\uD0DC\uB97C \uAC80\uC99D\uD558\uB294 \uD504\uB9AC\uC14B"
slots:
- baseSkill: {fileID: 11400000, guid: b7f09e0e899c8fc4bb2cc9204cc6eb4a, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: b8c86399865e91144a3d6fcfddc04fd9, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: 549a9978338eb504690c3c490acc0c60, type: 2}
socketedGems:
- {fileID: 11400000, guid: e020eee86f6c97f4393672759d73602e, type: 2}
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: 2ed15dca92a165046b6df17b28f64874, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4939a9da2935f744389326250b0edf69
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 26d5895a89de4f24aade1ea4b5f7644e, type: 3}
m_Name: "Data_LoadoutPreset_Player_\uC801\uC911\uC774\uC0C1\uC82C\uD14C\uC2A4\uD2B8"
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.PlayerLoadoutPreset
presetName: "\uC801\uC911 \uC774\uC0C1 \uC82C \uD14C\uC2A4\uD2B8"
description: "\uC57D\uD654 \uC82C\uC73C\uB85C \uC801\uC911 \uB300\uC0C1 \uB514\uBC84\uD504\uB97C
\uAC80\uC99D\uD558\uB294 \uD504\uB9AC\uC14B"
slots:
- baseSkill: {fileID: 11400000, guid: b7f09e0e899c8fc4bb2cc9204cc6eb4a, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: b8c86399865e91144a3d6fcfddc04fd9, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: 549a9978338eb504690c3c490acc0c60, type: 2}
socketedGems:
- {fileID: 11400000, guid: 2edf7687dc6caa0489ae2111499fcfab, type: 2}
- {fileID: 0}
- {fileID: 0}
- baseSkill: {fileID: 11400000, guid: 2ed15dca92a165046b6df17b28f64874, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a2403161604e6984c82d7644e4c11ac2
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e81a62ae7c7624847ab572ff37789bb8, type: 3}
m_Name: "Data_SkillGem_Player_\uAC15\uC778\uD568"
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillGemData
gemName: "\uAC15\uC778\uD568"
description: "\uC2A4\uD0AC \uC0AC\uC6A9 \uC2DC \uC790\uC2E0\uC5D0\uAC8C \uACBD\uC9C1
\uBA74\uC5ED\uC744 \uBD80\uC5EC\uD558\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uBC29\uC5B4
\uC82C"
icon: {fileID: 0}
category: 3
manaCostMultiplier: 1.05
cooldownMultiplier: 1.05
castSpeedMultiplier: 1
damageMultiplier: 1
healMultiplier: 1
shieldMultiplier: 1
threatMultiplier: 1
additionalRepeatCount: 0
castStartEffects: []
triggeredEffects: []
selfAbnormalities:
- {fileID: 11400000, guid: f4f55b61c9d04fd2b83b9c80e81fa0a2, type: 2}
onHitAbnormalities: []

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e020eee86f6c97f4393672759d73602e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -20,9 +20,15 @@ MonoBehaviour:
manaCostMultiplier: 1 manaCostMultiplier: 1
cooldownMultiplier: 1 cooldownMultiplier: 1
castSpeedMultiplier: 1 castSpeedMultiplier: 1
damageMultiplier: 1
healMultiplier: 1
shieldMultiplier: 1
threatMultiplier: 1
additionalRepeatCount: 0 additionalRepeatCount: 0
castStartEffects: [] castStartEffects: []
triggeredEffects: triggeredEffects:
- triggerIndex: 0 - triggerIndex: 0
effects: effects:
- {fileID: 11400000, guid: 2a467b25340d65f4a854350319f10d0a, type: 2} - {fileID: 11400000, guid: 2a467b25340d65f4a854350319f10d0a, type: 2}
selfAbnormalities: []
onHitAbnormalities: []

View File

@@ -20,9 +20,15 @@ MonoBehaviour:
manaCostMultiplier: 1 manaCostMultiplier: 1
cooldownMultiplier: 1 cooldownMultiplier: 1
castSpeedMultiplier: 1 castSpeedMultiplier: 1
damageMultiplier: 1
healMultiplier: 1
shieldMultiplier: 1
threatMultiplier: 1.5
additionalRepeatCount: 0 additionalRepeatCount: 0
castStartEffects: [] castStartEffects: []
triggeredEffects: triggeredEffects:
- triggerIndex: 0 - triggerIndex: 0
effects: effects:
- {fileID: 11400000, guid: f0aaa98426be3d44082a386c00ea9aea, type: 2} - {fileID: 11400000, guid: f0aaa98426be3d44082a386c00ea9aea, type: 2}
selfAbnormalities: []
onHitAbnormalities: []

View File

@@ -20,9 +20,15 @@ MonoBehaviour:
manaCostMultiplier: 1.05 manaCostMultiplier: 1.05
cooldownMultiplier: 1.1 cooldownMultiplier: 1.1
castSpeedMultiplier: 1 castSpeedMultiplier: 1
damageMultiplier: 1
healMultiplier: 1.2
shieldMultiplier: 1.5
threatMultiplier: 1
additionalRepeatCount: 0 additionalRepeatCount: 0
castStartEffects: [] castStartEffects: []
triggeredEffects: triggeredEffects:
- triggerIndex: 0 - triggerIndex: 0
effects: effects:
- {fileID: 11400000, guid: 65ed1eabc2fb73d43b86230317222608, type: 2} - {fileID: 11400000, guid: 65ed1eabc2fb73d43b86230317222608, type: 2}
selfAbnormalities: []
onHitAbnormalities: []

View File

@@ -0,0 +1,34 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e81a62ae7c7624847ab572ff37789bb8, type: 3}
m_Name: "Data_SkillGem_Player_\uC57D\uD654"
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillGemData
gemName: "\uC57D\uD654"
description: "\uC2A4\uD0AC \uC801\uC911 \uB300\uC0C1\uC5D0\uAC8C \uD14C\uC2A4\uD2B8
\uB514\uBC84\uD504\uB97C \uBD80\uC5EC\uD558\uB294 \uC81C\uC5B4 \uC82C"
icon: {fileID: 0}
category: 5
manaCostMultiplier: 1.05
cooldownMultiplier: 1.05
castSpeedMultiplier: 1
damageMultiplier: 1
healMultiplier: 1
shieldMultiplier: 1
threatMultiplier: 1
additionalRepeatCount: 0
castStartEffects: []
triggeredEffects: []
selfAbnormalities: []
onHitAbnormalities:
- triggerIndex: 0
abnormalities:
- {fileID: 11400000, guid: c4abb0f89779f294ab99562e085e8f3b, type: 2}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2edf7687dc6caa0489ae2111499fcfab
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -20,6 +20,12 @@ MonoBehaviour:
manaCostMultiplier: 1.2 manaCostMultiplier: 1.2
cooldownMultiplier: 1.15 cooldownMultiplier: 1.15
castSpeedMultiplier: 1.1 castSpeedMultiplier: 1.1
damageMultiplier: 1
healMultiplier: 1
shieldMultiplier: 1
threatMultiplier: 1
additionalRepeatCount: 1 additionalRepeatCount: 1
castStartEffects: [] castStartEffects: []
triggeredEffects: [] triggeredEffects: []
selfAbnormalities: []
onHitAbnormalities: []

View File

@@ -20,9 +20,15 @@ MonoBehaviour:
manaCostMultiplier: 1 manaCostMultiplier: 1
cooldownMultiplier: 1 cooldownMultiplier: 1
castSpeedMultiplier: 1 castSpeedMultiplier: 1
damageMultiplier: 1
healMultiplier: 1
shieldMultiplier: 1
threatMultiplier: 1
additionalRepeatCount: 0 additionalRepeatCount: 0
castStartEffects: [] castStartEffects: []
triggeredEffects: triggeredEffects:
- triggerIndex: 0 - triggerIndex: 0
effects: effects:
- {fileID: 11400000, guid: 197e5c38a24c95044a5959bb4c67f055, type: 2} - {fileID: 11400000, guid: 197e5c38a24c95044a5959bb4c67f055, type: 2}
selfAbnormalities: []
onHitAbnormalities: []

View File

@@ -20,9 +20,15 @@ MonoBehaviour:
manaCostMultiplier: 1 manaCostMultiplier: 1
cooldownMultiplier: 1 cooldownMultiplier: 1
castSpeedMultiplier: 1 castSpeedMultiplier: 1
damageMultiplier: 1
healMultiplier: 1
shieldMultiplier: 1
threatMultiplier: 1
additionalRepeatCount: 0 additionalRepeatCount: 0
castStartEffects: [] castStartEffects: []
triggeredEffects: triggeredEffects:
- triggerIndex: 0 - triggerIndex: 0
effects: effects:
- {fileID: 11400000, guid: 1a6d1b31b640d87499ef7c9e3580ed34, type: 2} - {fileID: 11400000, guid: 1a6d1b31b640d87499ef7c9e3580ed34, type: 2}
selfAbnormalities: []
onHitAbnormalities: []

View File

@@ -20,9 +20,15 @@ MonoBehaviour:
manaCostMultiplier: 1.15 manaCostMultiplier: 1.15
cooldownMultiplier: 1.1 cooldownMultiplier: 1.1
castSpeedMultiplier: 1 castSpeedMultiplier: 1
damageMultiplier: 1.2
healMultiplier: 1
shieldMultiplier: 1
threatMultiplier: 1
additionalRepeatCount: 0 additionalRepeatCount: 0
castStartEffects: [] castStartEffects: []
triggeredEffects: triggeredEffects:
- triggerIndex: 0 - triggerIndex: 0
effects: effects:
- {fileID: 11400000, guid: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4, type: 2} - {fileID: 11400000, guid: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4, type: 2}
selfAbnormalities: []
onHitAbnormalities: []

View File

@@ -36,6 +36,8 @@ namespace Colosseum.Editor
private const string StunAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Stun.asset"; private const string StunAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Stun.asset";
private const string SilenceAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Silence.asset"; private const string SilenceAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Silence.asset";
private const string MarkAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_집행자의낙인.asset"; private const string MarkAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_집행자의낙인.asset";
private const string HitReactionImmuneAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_경직면역.asset";
private const string TestDebuffAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Test_Debuff.asset";
private const string ShieldAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Common_보호막.asset"; private const string ShieldAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Common_보호막.asset";
private const string ShieldTypeAPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Test_보호막A.asset"; private const string ShieldTypeAPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Test_보호막A.asset";
private const string ShieldTypeBPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Test_보호막B.asset"; private const string ShieldTypeBPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Test_보호막B.asset";
@@ -45,6 +47,8 @@ namespace Colosseum.Editor
private const string ChallengerGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_도전자.asset"; private const string ChallengerGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_도전자.asset";
private const string GuardianGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_수호.asset"; private const string GuardianGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_수호.asset";
private const string RepeatGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_연속.asset"; private const string RepeatGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_연속.asset";
private const string FortitudeGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_강인함.asset";
private const string WitherGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_약화.asset";
private const string EdgeGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_예리함.asset"; private const string EdgeGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_예리함.asset";
private const string ImpactGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_충격.asset"; private const string ImpactGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_충격.asset";
private const string BreachGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_관통.asset"; private const string BreachGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_관통.asset";
@@ -55,6 +59,9 @@ namespace Colosseum.Editor
private const string SupportGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_지원_젬테스트.asset"; private const string SupportGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_지원_젬테스트.asset";
private const string DpsGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_딜러_젬테스트.asset"; private const string DpsGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_딜러_젬테스트.asset";
private const string RepeatGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_반복젬테스트.asset"; private const string RepeatGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_반복젬테스트.asset";
private const string SelfAbnormalityGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_자기강화젬테스트.asset";
private const string OnHitAbnormalityGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_적중이상젬테스트.asset";
private const string AbnormalityComboGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_상태복합젬테스트.asset";
private const string TankDualGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_탱커_복합젬테스트.asset"; private const string TankDualGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_탱커_복합젬테스트.asset";
private const string SupportDualGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_지원_복합젬테스트.asset"; private const string SupportDualGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_지원_복합젬테스트.asset";
private const string DpsDualGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_딜러_복합젬테스트.asset"; private const string DpsDualGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_딜러_복합젬테스트.asset";
@@ -530,6 +537,24 @@ namespace Colosseum.Editor
ApplyLoadoutPreset(RepeatGemPresetPath, "반복 젬"); ApplyLoadoutPreset(RepeatGemPresetPath, "반복 젬");
} }
[MenuItem("Tools/Colosseum/Debug/Apply Self Abnormality Gem Loadout")]
private static void ApplySelfAbnormalityGemLoadout()
{
ApplyLoadoutPreset(SelfAbnormalityGemPresetPath, "자기강화 젬");
}
[MenuItem("Tools/Colosseum/Debug/Apply On-Hit Abnormality Gem Loadout")]
private static void ApplyOnHitAbnormalityGemLoadout()
{
ApplyLoadoutPreset(OnHitAbnormalityGemPresetPath, "적중 이상 젬");
}
[MenuItem("Tools/Colosseum/Debug/Apply Abnormality Combo Gem Loadout")]
private static void ApplyAbnormalityComboGemLoadout()
{
ApplyLoadoutPreset(AbnormalityComboGemPresetPath, "상태 복합 젬");
}
[MenuItem("Tools/Colosseum/Debug/Apply Tank Dual Gem Loadout")] [MenuItem("Tools/Colosseum/Debug/Apply Tank Dual Gem Loadout")]
private static void ApplyTankDualGemLoadout() private static void ApplyTankDualGemLoadout()
{ {
@@ -580,6 +605,8 @@ namespace Colosseum.Editor
SkillEffect damageEffect = AssetDatabase.LoadAssetAtPath<SkillEffect>("Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_찌르기_0_데미지.asset"); SkillEffect damageEffect = AssetDatabase.LoadAssetAtPath<SkillEffect>("Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_찌르기_0_데미지.asset");
SkillEffect tauntEffect = AssetDatabase.LoadAssetAtPath<SkillEffect>("Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_도발_0_도발.asset"); SkillEffect tauntEffect = AssetDatabase.LoadAssetAtPath<SkillEffect>("Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_도발_0_도발.asset");
SkillEffect shieldEffect = AssetDatabase.LoadAssetAtPath<SkillEffect>("Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_보호막_0_보호막.asset"); SkillEffect shieldEffect = AssetDatabase.LoadAssetAtPath<SkillEffect>("Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_보호막_0_보호막.asset");
AbnormalityData hitReactionImmuneAbnormality = AssetDatabase.LoadAssetAtPath<AbnormalityData>(HitReactionImmuneAbnormalityPath);
AbnormalityData testDebuffAbnormality = AssetDatabase.LoadAssetAtPath<AbnormalityData>(TestDebuffAbnormalityPath);
DamageEffect edgeDamageEffect = CreateOrUpdateDamageEffectAsset(EdgeDamageEffectPath, 4f); DamageEffect edgeDamageEffect = CreateOrUpdateDamageEffectAsset(EdgeDamageEffectPath, 4f);
DamageEffect impactDamageEffect = CreateOrUpdateDamageEffectAsset(ImpactDamageEffectPath, 7f); DamageEffect impactDamageEffect = CreateOrUpdateDamageEffectAsset(ImpactDamageEffectPath, 7f);
DamageEffect breachDamageEffect = CreateOrUpdateDamageEffectAsset(BreachDamageEffectPath, 10f); DamageEffect breachDamageEffect = CreateOrUpdateDamageEffectAsset(BreachDamageEffectPath, 10f);
@@ -592,6 +619,10 @@ namespace Colosseum.Editor
1.15f, 1.15f,
1.1f, 1.1f,
1f, 1f,
1.2f,
1f,
1f,
1f,
0, 0,
damageEffect); damageEffect);
@@ -603,6 +634,10 @@ namespace Colosseum.Editor
1f, 1f,
1f, 1f,
1f, 1f,
1f,
1f,
1f,
1.5f,
0, 0,
tauntEffect); tauntEffect);
@@ -614,6 +649,10 @@ namespace Colosseum.Editor
1.05f, 1.05f,
1.1f, 1.1f,
1f, 1f,
1f,
1.2f,
1.5f,
1f,
0, 0,
shieldEffect); shieldEffect);
@@ -625,9 +664,47 @@ namespace Colosseum.Editor
1.2f, 1.2f,
1.15f, 1.15f,
1.1f, 1.1f,
1f,
1f,
1f,
1f,
1, 1,
null); null);
CreateOrUpdateGemAsset(
FortitudeGemPath,
"강인함",
"스킬 사용 시 자신에게 경직 면역을 부여하는 테스트용 방어 젬",
SkillGemCategory.Defense,
1.05f,
1.05f,
1f,
1f,
1f,
1f,
1f,
0,
null,
new[] { hitReactionImmuneAbnormality });
CreateOrUpdateGemAsset(
WitherGemPath,
"약화",
"스킬 적중 대상에게 테스트 디버프를 부여하는 제어 젬",
SkillGemCategory.Control,
1.05f,
1.05f,
1f,
1f,
1f,
1f,
1f,
0,
null,
null,
0,
new[] { testDebuffAbnormality });
CreateOrUpdateGemAsset( CreateOrUpdateGemAsset(
EdgeGemPath, EdgeGemPath,
"예리함", "예리함",
@@ -636,6 +713,10 @@ namespace Colosseum.Editor
1f, 1f,
1f, 1f,
1f, 1f,
1f,
1f,
1f,
1f,
0, 0,
edgeDamageEffect); edgeDamageEffect);
@@ -647,6 +728,10 @@ namespace Colosseum.Editor
1f, 1f,
1f, 1f,
1f, 1f,
1f,
1f,
1f,
1f,
0, 0,
impactDamageEffect); impactDamageEffect);
@@ -658,6 +743,10 @@ namespace Colosseum.Editor
1f, 1f,
1f, 1f,
1f, 1f,
1f,
1f,
1f,
1f,
0, 0,
breachDamageEffect); breachDamageEffect);
@@ -691,6 +780,8 @@ namespace Colosseum.Editor
SkillGemData challengerGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(ChallengerGemPath); SkillGemData challengerGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(ChallengerGemPath);
SkillGemData guardianGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(GuardianGemPath); SkillGemData guardianGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(GuardianGemPath);
SkillGemData repeatGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(RepeatGemPath); SkillGemData repeatGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(RepeatGemPath);
SkillGemData fortitudeGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(FortitudeGemPath);
SkillGemData witherGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(WitherGemPath);
SkillGemData edgeGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(EdgeGemPath); SkillGemData edgeGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(EdgeGemPath);
SkillGemData impactGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(ImpactGemPath); SkillGemData impactGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(ImpactGemPath);
SkillGemData breachGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(BreachGemPath); SkillGemData breachGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(BreachGemPath);
@@ -749,6 +840,45 @@ namespace Colosseum.Editor
CreateEntry(gemTestSkill, repeatGem), CreateEntry(gemTestSkill, repeatGem),
CreateEntry(evadeSkill))); CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset(
SelfAbnormalityGemPresetPath,
"자기강화 젬 테스트",
"강인함 젬으로 시전 시 자기 강화 상태를 검증하는 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(pierceSkill),
CreateEntry(spinSkill),
CreateEntry(dashSkill),
CreateEntry(projectileSkill),
CreateEntry(gemTestSkill, fortitudeGem),
CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset(
OnHitAbnormalityGemPresetPath,
"적중 이상 젬 테스트",
"약화 젬으로 적중 대상 디버프를 검증하는 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(pierceSkill),
CreateEntry(spinSkill),
CreateEntry(dashSkill),
CreateEntry(projectileSkill),
CreateEntry(gemTestSkill, witherGem),
CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset(
AbnormalityComboGemPresetPath,
"상태 복합 젬 테스트",
"강인함 + 약화 젬을 동시에 사용해 자기 강화와 적중 디버프를 함께 검증하는 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(pierceSkill),
CreateEntry(spinSkill),
CreateEntry(dashSkill),
CreateEntry(projectileSkill),
CreateEntry(gemTestSkill, fortitudeGem, witherGem),
CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset( CreateOrUpdatePresetAsset(
TankDualGemPresetPath, TankDualGemPresetPath,
"탱커 복합 젬 테스트", "탱커 복합 젬 테스트",
@@ -907,6 +1037,10 @@ namespace Colosseum.Editor
float resolvedCooldown = loadoutEntry.GetResolvedCooldown(); float resolvedCooldown = loadoutEntry.GetResolvedCooldown();
float resolvedAnimationSpeed = loadoutEntry.GetResolvedAnimationSpeed(); float resolvedAnimationSpeed = loadoutEntry.GetResolvedAnimationSpeed();
int resolvedRepeatCount = loadoutEntry.GetResolvedRepeatCount(); int resolvedRepeatCount = loadoutEntry.GetResolvedRepeatCount();
float resolvedDamageMultiplier = loadoutEntry.GetResolvedDamageMultiplier();
float resolvedHealMultiplier = loadoutEntry.GetResolvedHealMultiplier();
float resolvedShieldMultiplier = loadoutEntry.GetResolvedShieldMultiplier();
float resolvedThreatMultiplier = loadoutEntry.GetResolvedThreatMultiplier();
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.Append("[Debug] 6번 슬롯 계산값 | "); builder.Append("[Debug] 6번 슬롯 계산값 | ");
@@ -920,6 +1054,14 @@ namespace Colosseum.Editor
builder.Append(resolvedAnimationSpeed.ToString("0.###")); builder.Append(resolvedAnimationSpeed.ToString("0.###"));
builder.Append(" | Repeat="); builder.Append(" | Repeat=");
builder.Append(resolvedRepeatCount); builder.Append(resolvedRepeatCount);
builder.Append(" | Dmg=");
builder.Append(resolvedDamageMultiplier.ToString("0.###"));
builder.Append(" | Heal=");
builder.Append(resolvedHealMultiplier.ToString("0.###"));
builder.Append(" | Shield=");
builder.Append(resolvedShieldMultiplier.ToString("0.###"));
builder.Append(" | Threat=");
builder.Append(resolvedThreatMultiplier.ToString("0.###"));
builder.Append(" | GemSlots="); builder.Append(" | GemSlots=");
builder.Append(loadoutEntry.SocketedGems.Count); builder.Append(loadoutEntry.SocketedGems.Count);
AppendGemCategorySummary(builder, loadoutEntry); AppendGemCategorySummary(builder, loadoutEntry);
@@ -1164,8 +1306,15 @@ namespace Colosseum.Editor
float manaCostMultiplier, float manaCostMultiplier,
float cooldownMultiplier, float cooldownMultiplier,
float castSpeedMultiplier, float castSpeedMultiplier,
float damageMultiplier,
float healMultiplier,
float shieldMultiplier,
float threatMultiplier,
int additionalRepeatCount, int additionalRepeatCount,
SkillEffect triggeredEffect) SkillEffect triggeredEffect,
AbnormalityData[] selfAbnormalities = null,
int triggeredAbnormalityIndex = -1,
AbnormalityData[] onHitAbnormalities = null)
{ {
SkillGemData gem = AssetDatabase.LoadAssetAtPath<SkillGemData>(assetPath); SkillGemData gem = AssetDatabase.LoadAssetAtPath<SkillGemData>(assetPath);
if (gem == null) if (gem == null)
@@ -1186,11 +1335,22 @@ namespace Colosseum.Editor
serializedGem.FindProperty("manaCostMultiplier").floatValue = manaCostMultiplier; serializedGem.FindProperty("manaCostMultiplier").floatValue = manaCostMultiplier;
serializedGem.FindProperty("cooldownMultiplier").floatValue = cooldownMultiplier; serializedGem.FindProperty("cooldownMultiplier").floatValue = cooldownMultiplier;
serializedGem.FindProperty("castSpeedMultiplier").floatValue = castSpeedMultiplier; serializedGem.FindProperty("castSpeedMultiplier").floatValue = castSpeedMultiplier;
serializedGem.FindProperty("damageMultiplier").floatValue = damageMultiplier;
serializedGem.FindProperty("healMultiplier").floatValue = healMultiplier;
serializedGem.FindProperty("shieldMultiplier").floatValue = shieldMultiplier;
serializedGem.FindProperty("threatMultiplier").floatValue = threatMultiplier;
serializedGem.FindProperty("additionalRepeatCount").intValue = additionalRepeatCount; serializedGem.FindProperty("additionalRepeatCount").intValue = additionalRepeatCount;
SerializedProperty castStartEffectsProperty = serializedGem.FindProperty("castStartEffects"); SerializedProperty castStartEffectsProperty = serializedGem.FindProperty("castStartEffects");
castStartEffectsProperty.arraySize = 0; castStartEffectsProperty.arraySize = 0;
SerializedProperty selfAbnormalitiesProperty = serializedGem.FindProperty("selfAbnormalities");
selfAbnormalitiesProperty.arraySize = selfAbnormalities != null ? selfAbnormalities.Length : 0;
for (int i = 0; i < selfAbnormalitiesProperty.arraySize; i++)
{
selfAbnormalitiesProperty.GetArrayElementAtIndex(i).objectReferenceValue = selfAbnormalities[i];
}
SerializedProperty triggeredEffectsProperty = serializedGem.FindProperty("triggeredEffects"); SerializedProperty triggeredEffectsProperty = serializedGem.FindProperty("triggeredEffects");
triggeredEffectsProperty.arraySize = triggeredEffect != null ? 1 : 0; triggeredEffectsProperty.arraySize = triggeredEffect != null ? 1 : 0;
if (triggeredEffect != null) if (triggeredEffect != null)
@@ -1203,6 +1363,24 @@ namespace Colosseum.Editor
effectArray.GetArrayElementAtIndex(0).objectReferenceValue = triggeredEffect; effectArray.GetArrayElementAtIndex(0).objectReferenceValue = triggeredEffect;
} }
SerializedProperty onHitAbnormalitiesProperty = serializedGem.FindProperty("onHitAbnormalities");
bool hasTriggeredAbnormalities = onHitAbnormalities != null &&
onHitAbnormalities.Length > 0 &&
triggeredAbnormalityIndex >= 0;
onHitAbnormalitiesProperty.arraySize = hasTriggeredAbnormalities ? 1 : 0;
if (hasTriggeredAbnormalities)
{
SerializedProperty abnormalityEntry = onHitAbnormalitiesProperty.GetArrayElementAtIndex(0);
abnormalityEntry.FindPropertyRelative("triggerIndex").intValue = triggeredAbnormalityIndex;
SerializedProperty abnormalityArray = abnormalityEntry.FindPropertyRelative("abnormalities");
abnormalityArray.arraySize = onHitAbnormalities.Length;
for (int i = 0; i < onHitAbnormalities.Length; i++)
{
abnormalityArray.GetArrayElementAtIndex(i).objectReferenceValue = onHitAbnormalities[i];
}
}
serializedGem.ApplyModifiedPropertiesWithoutUndo(); serializedGem.ApplyModifiedPropertiesWithoutUndo();
EditorUtility.SetDirty(gem); EditorUtility.SetDirty(gem);
} }

View File

@@ -2,6 +2,7 @@ using UnityEngine;
using Colosseum.Abnormalities; using Colosseum.Abnormalities;
using Colosseum.Combat; using Colosseum.Combat;
using Colosseum.Skills;
namespace Colosseum.Skills.Effects namespace Colosseum.Skills.Effects
{ {
@@ -28,7 +29,7 @@ namespace Colosseum.Skills.Effects
return; return;
ApplyAbnormality(target, caster); ApplyAbnormality(target, caster);
ApplyThreatMultiplier(target); ApplyThreatMultiplier(target, caster);
} }
private void ApplyAbnormality(GameObject target, GameObject caster) private void ApplyAbnormality(GameObject target, GameObject caster)
@@ -43,7 +44,7 @@ namespace Colosseum.Skills.Effects
abnormalityManager.ApplyAbnormality(abnormalityData, caster); abnormalityManager.ApplyAbnormality(abnormalityData, caster);
} }
private void ApplyThreatMultiplier(GameObject target) private void ApplyThreatMultiplier(GameObject target, GameObject caster)
{ {
if (threatMultiplier <= 0f || threatMultiplierDuration <= 0f) if (threatMultiplier <= 0f || threatMultiplierDuration <= 0f)
return; return;
@@ -54,7 +55,8 @@ namespace Colosseum.Skills.Effects
threatController = target.AddComponent<ThreatController>(); threatController = target.AddComponent<ThreatController>();
} }
threatController.ApplyThreatMultiplier(threatMultiplier, threatMultiplierDuration); float resolvedThreatMultiplier = SkillRuntimeModifierUtility.GetThreatMultiplier(caster);
threatController.ApplyThreatMultiplier(threatMultiplier * resolvedThreatMultiplier, threatMultiplierDuration);
} }
} }
} }

View File

@@ -2,6 +2,7 @@ using UnityEngine;
using Colosseum.Stats; using Colosseum.Stats;
using Colosseum.Combat; using Colosseum.Combat;
using Colosseum.Skills;
using Colosseum.Weapons; using Colosseum.Weapons;
namespace Colosseum.Skills.Effects namespace Colosseum.Skills.Effects
@@ -73,7 +74,8 @@ namespace Colosseum.Skills.Effects
// 무기 데미지 배율 적용 // 무기 데미지 배율 적용
float damageMultiplier = GetDamageMultiplier(caster); float damageMultiplier = GetDamageMultiplier(caster);
return baseTotal * damageMultiplier; float gemMultiplier = SkillRuntimeModifierUtility.GetDamageMultiplier(caster);
return baseTotal * damageMultiplier * gemMultiplier;
} }
/// <summary> /// <summary>

View File

@@ -2,6 +2,7 @@ using UnityEngine;
using Colosseum.Stats; using Colosseum.Stats;
using Colosseum.Combat; using Colosseum.Combat;
using Colosseum.Skills;
namespace Colosseum.Skills.Effects namespace Colosseum.Skills.Effects
{ {
@@ -41,10 +42,11 @@ namespace Colosseum.Skills.Effects
var stats = caster.GetComponent<CharacterStats>(); var stats = caster.GetComponent<CharacterStats>();
if (stats == null) if (stats == null)
{ {
return baseHeal; return baseHeal * SkillRuntimeModifierUtility.GetHealMultiplier(caster);
} }
return baseHeal + (stats.HealPower * healScaling); float resolvedHeal = baseHeal + (stats.HealPower * healScaling);
return resolvedHeal * SkillRuntimeModifierUtility.GetHealMultiplier(caster);
} }
} }
} }

View File

@@ -5,6 +5,7 @@ using Colosseum.Enemy;
using Colosseum.Player; using Colosseum.Player;
using Colosseum.Stats; using Colosseum.Stats;
using Colosseum.Combat; using Colosseum.Combat;
using Colosseum.Skills;
namespace Colosseum.Skills.Effects namespace Colosseum.Skills.Effects
{ {
@@ -55,9 +56,10 @@ namespace Colosseum.Skills.Effects
{ {
CharacterStats stats = caster != null ? caster.GetComponent<CharacterStats>() : null; CharacterStats stats = caster != null ? caster.GetComponent<CharacterStats>() : null;
if (stats == null) if (stats == null)
return baseShield; return baseShield * SkillRuntimeModifierUtility.GetShieldMultiplier(caster);
return baseShield + (stats.HealPower * shieldScaling); float resolvedShield = baseShield + (stats.HealPower * shieldScaling);
return resolvedShield * SkillRuntimeModifierUtility.GetShieldMultiplier(caster);
} }
} }
} }

View File

@@ -4,6 +4,7 @@ using UnityEngine;
using Colosseum.Combat; using Colosseum.Combat;
using Colosseum.Enemy; using Colosseum.Enemy;
using Colosseum.Skills;
namespace Colosseum.Skills.Effects namespace Colosseum.Skills.Effects
{ {
@@ -62,13 +63,14 @@ namespace Colosseum.Skills.Effects
if (selfThreatMultiplier <= 0f || selfThreatMultiplierDuration <= 0f) if (selfThreatMultiplier <= 0f || selfThreatMultiplierDuration <= 0f)
return; return;
float resolvedThreatMultiplier = SkillRuntimeModifierUtility.GetThreatMultiplier(caster);
ThreatController threatController = caster.GetComponent<ThreatController>(); ThreatController threatController = caster.GetComponent<ThreatController>();
if (threatController == null) if (threatController == null)
{ {
threatController = caster.AddComponent<ThreatController>(); threatController = caster.AddComponent<ThreatController>();
} }
threatController.ApplyThreatMultiplier(selfThreatMultiplier, selfThreatMultiplierDuration); threatController.ApplyThreatMultiplier(selfThreatMultiplier * resolvedThreatMultiplier, selfThreatMultiplierDuration);
} }
private void ApplyTauntThreat(EnemyBase enemy, GameObject caster) private void ApplyTauntThreat(EnemyBase enemy, GameObject caster)
@@ -76,11 +78,14 @@ namespace Colosseum.Skills.Effects
if (enemy == null || caster == null || !enemy.UseThreatSystem) if (enemy == null || caster == null || !enemy.UseThreatSystem)
return; return;
float resolvedThreatMultiplier = SkillRuntimeModifierUtility.GetThreatMultiplier(caster);
GameObject highestThreatTarget = enemy.GetHighestThreatTarget(); GameObject highestThreatTarget = enemy.GetHighestThreatTarget();
float highestThreat = highestThreatTarget != null ? enemy.GetThreat(highestThreatTarget) : 0f; float highestThreat = highestThreatTarget != null ? enemy.GetThreat(highestThreatTarget) : 0f;
float currentCasterThreat = enemy.GetThreat(caster); float currentCasterThreat = enemy.GetThreat(caster);
float desiredThreat = Mathf.Max(currentCasterThreat + flatThreatAmount, highestThreat + threatLeadBonus + flatThreatAmount); float resolvedFlatThreat = flatThreatAmount * resolvedThreatMultiplier;
float resolvedLeadBonus = threatLeadBonus * resolvedThreatMultiplier;
float desiredThreat = Mathf.Max(currentCasterThreat + resolvedFlatThreat, highestThreat + resolvedLeadBonus + resolvedFlatThreat);
enemy.SetThreat(caster, desiredThreat); enemy.SetThreat(caster, desiredThreat);
CombatBalanceTracker.RecordThreat(caster, Mathf.Max(0f, desiredThreat - currentCasterThreat)); CombatBalanceTracker.RecordThreat(caster, Mathf.Max(0f, desiredThreat - currentCasterThreat));
} }

View File

@@ -1,6 +1,7 @@
using UnityEngine; using UnityEngine;
using Colosseum.Combat; using Colosseum.Combat;
using Colosseum.Skills;
namespace Colosseum.Skills.Effects namespace Colosseum.Skills.Effects
{ {
@@ -28,7 +29,8 @@ namespace Colosseum.Skills.Effects
threatController = target.AddComponent<ThreatController>(); threatController = target.AddComponent<ThreatController>();
} }
threatController.ApplyThreatMultiplier(threatMultiplier, duration); float resolvedThreatMultiplier = SkillRuntimeModifierUtility.GetThreatMultiplier(caster);
threatController.ApplyThreatMultiplier(threatMultiplier * resolvedThreatMultiplier, duration);
} }
} }
} }

View File

@@ -2,6 +2,8 @@ using UnityEngine;
using System.Collections.Generic; using System.Collections.Generic;
using Unity.Netcode; using Unity.Netcode;
using Colosseum.Abnormalities;
namespace Colosseum.Skills namespace Colosseum.Skills
{ {
/// <summary> /// <summary>
@@ -56,6 +58,9 @@ namespace Colosseum.Skills
private SkillLoadoutEntry currentLoadoutEntry; private SkillLoadoutEntry currentLoadoutEntry;
private readonly List<SkillEffect> currentCastStartEffects = new(); private readonly List<SkillEffect> currentCastStartEffects = new();
private readonly Dictionary<int, List<SkillEffect>> currentTriggeredEffects = new(); private readonly Dictionary<int, List<SkillEffect>> currentTriggeredEffects = new();
private readonly List<AbnormalityData> currentCastStartAbnormalities = new();
private readonly Dictionary<int, List<AbnormalityData>> currentTriggeredAbnormalities = new();
private readonly List<GameObject> currentTriggeredTargetsBuffer = new();
private bool waitingForEndAnimation; // EndAnimation 종료 대기 중 private bool waitingForEndAnimation; // EndAnimation 종료 대기 중
private int currentRepeatCount = 1; private int currentRepeatCount = 1;
private int currentIterationIndex = 0; private int currentIterationIndex = 0;
@@ -198,7 +203,7 @@ namespace Colosseum.Skills
/// </summary> /// </summary>
private void TriggerCastStartEffects() private void TriggerCastStartEffects()
{ {
if (currentSkill == null || currentCastStartEffects.Count == 0) if (currentSkill == null)
return; return;
if (NetworkManager.Singleton != null && !NetworkManager.Singleton.IsServer) if (NetworkManager.Singleton != null && !NetworkManager.Singleton.IsServer)
@@ -213,6 +218,26 @@ namespace Colosseum.Skills
if (debugMode) Debug.Log($"[Skill] Cast start effect: {effect.name} (index {i})"); if (debugMode) Debug.Log($"[Skill] Cast start effect: {effect.name} (index {i})");
effect.ExecuteOnCast(gameObject); effect.ExecuteOnCast(gameObject);
} }
if (currentCastStartAbnormalities.Count <= 0)
return;
AbnormalityManager abnormalityManager = GetComponent<AbnormalityManager>();
if (abnormalityManager == null)
{
if (debugMode) Debug.LogWarning("[Skill] Cast start abnormality skipped - no AbnormalityManager");
return;
}
for (int i = 0; i < currentCastStartAbnormalities.Count; i++)
{
AbnormalityData abnormality = currentCastStartAbnormalities[i];
if (abnormality == null)
continue;
if (debugMode) Debug.Log($"[Skill] Cast start abnormality: {abnormality.abnormalityName} (index {i})");
abnormalityManager.ApplyAbnormality(abnormality, gameObject);
}
} }
/// <summary> /// <summary>
@@ -251,12 +276,16 @@ namespace Colosseum.Skills
{ {
currentCastStartEffects.Clear(); currentCastStartEffects.Clear();
currentTriggeredEffects.Clear(); currentTriggeredEffects.Clear();
currentCastStartAbnormalities.Clear();
currentTriggeredAbnormalities.Clear();
if (loadoutEntry == null) if (loadoutEntry == null)
return; return;
loadoutEntry.CollectCastStartEffects(currentCastStartEffects); loadoutEntry.CollectCastStartEffects(currentCastStartEffects);
loadoutEntry.CollectTriggeredEffects(currentTriggeredEffects); loadoutEntry.CollectTriggeredEffects(currentTriggeredEffects);
loadoutEntry.CollectCastStartAbnormalities(currentCastStartAbnormalities);
loadoutEntry.CollectTriggeredAbnormalities(currentTriggeredAbnormalities);
} }
/// <summary> /// <summary>
@@ -450,6 +479,8 @@ namespace Colosseum.Skills
effect.ExecuteOnCast(gameObject); effect.ExecuteOnCast(gameObject);
} }
ApplyTriggeredAbnormalities(index, effects);
} }
/// <summary> /// <summary>
@@ -529,9 +560,72 @@ namespace Colosseum.Skills
currentLoadoutEntry = null; currentLoadoutEntry = null;
currentCastStartEffects.Clear(); currentCastStartEffects.Clear();
currentTriggeredEffects.Clear(); currentTriggeredEffects.Clear();
currentCastStartAbnormalities.Clear();
currentTriggeredAbnormalities.Clear();
currentTriggeredTargetsBuffer.Clear();
waitingForEndAnimation = false; waitingForEndAnimation = false;
currentRepeatCount = 1; currentRepeatCount = 1;
currentIterationIndex = 0; currentIterationIndex = 0;
} }
/// <summary>
/// 현재 트리거 인덱스에 연결된 젬 이상상태를 적중 대상에게 적용합니다.
/// </summary>
private void ApplyTriggeredAbnormalities(int index, List<SkillEffect> referenceEffects)
{
if (!currentTriggeredAbnormalities.TryGetValue(index, out List<AbnormalityData> abnormalities) ||
abnormalities == null ||
abnormalities.Count == 0)
{
return;
}
currentTriggeredTargetsBuffer.Clear();
for (int i = 0; i < referenceEffects.Count; i++)
{
SkillEffect effect = referenceEffects[i];
if (effect == null || effect.TargetType == TargetType.Self)
continue;
effect.CollectTargets(gameObject, currentTriggeredTargetsBuffer);
}
if (currentTriggeredTargetsBuffer.Count == 0)
{
if (debugMode)
{
Debug.LogWarning($"[Skill] Trigger abnormality skipped - no hit target resolved for index {index}");
}
return;
}
for (int i = 0; i < currentTriggeredTargetsBuffer.Count; i++)
{
GameObject target = currentTriggeredTargetsBuffer[i];
if (target == null)
continue;
AbnormalityManager abnormalityManager = target.GetComponent<AbnormalityManager>();
if (abnormalityManager == null)
continue;
for (int j = 0; j < abnormalities.Count; j++)
{
AbnormalityData abnormality = abnormalities[j];
if (abnormality == null)
continue;
if (debugMode)
{
Debug.Log($"[Skill] Trigger abnormality: {abnormality.abnormalityName} -> {target.name} (index {index})");
}
abnormalityManager.ApplyAbnormality(abnormality, gameObject);
}
}
currentTriggeredTargetsBuffer.Clear();
}
} }
} }

View File

@@ -51,14 +51,32 @@ namespace Colosseum.Skills
/// </summary> /// </summary>
public void ExecuteOnCast(GameObject caster) public void ExecuteOnCast(GameObject caster)
{ {
List<GameObject> targets = new List<GameObject>();
CollectTargets(caster, targets);
for (int i = 0; i < targets.Count; i++)
{
ApplyEffect(caster, targets[i]);
}
}
/// <summary>
/// 현재 효과가 영향을 줄 대상 목록을 수집합니다.
/// 젬의 적중 이상상태 적용 등에서 동일한 타겟 해석을 재사용하기 위한 경로입니다.
/// </summary>
public void CollectTargets(GameObject caster, List<GameObject> destination)
{
if (caster == null || destination == null)
return;
switch (targetType) switch (targetType)
{ {
case TargetType.Self: case TargetType.Self:
ApplyEffect(caster, caster); AddUniqueTarget(destination, caster);
break; break;
case TargetType.Area: case TargetType.Area:
ExecuteArea(caster); CollectAreaTargets(caster, destination);
break; break;
} }
} }
@@ -110,17 +128,14 @@ namespace Colosseum.Skills
}; };
} }
private void ExecuteArea(GameObject caster) private void CollectAreaTargets(GameObject caster, List<GameObject> destination)
{ {
Vector3 center = GetAreaCenter(caster); Vector3 center = GetAreaCenter(caster);
Collider[] hits = Physics.OverlapSphere(center, Mathf.Max(areaRadius, fanRadius), targetLayers); Collider[] hits = Physics.OverlapSphere(center, Mathf.Max(areaRadius, fanRadius), targetLayers);
// 같은 GameObject가 여러 콜라이더를 가질 수 있으므로 중복 제거
HashSet<GameObject> processedTargets = new HashSet<GameObject>();
foreach (var hit in hits) foreach (var hit in hits)
{ {
if (!includeCasterInArea && hit.gameObject == caster) continue; if (!includeCasterInArea && hit.gameObject == caster) continue;
if (!IsCorrectTeam(caster, hit.gameObject)) continue; if (!IsCorrectTeam(caster, hit.gameObject)) continue;
if (processedTargets.Contains(hit.gameObject)) continue;
// 부채꼴 판정 // 부채꼴 판정
if (areaShape == AreaShapeType.Fan) if (areaShape == AreaShapeType.Fan)
{ {
@@ -128,11 +143,18 @@ namespace Colosseum.Skills
continue; continue;
} }
processedTargets.Add(hit.gameObject); AddUniqueTarget(destination, hit.gameObject);
ApplyEffect(caster, hit.gameObject);
} }
} }
private static void AddUniqueTarget(List<GameObject> destination, GameObject target)
{
if (target == null || destination.Contains(target))
return;
destination.Add(target);
}
/// <summary> /// <summary>
/// 타겟이 부채꼴 범위 내에 있는지 확인 /// 타겟이 부채꼴 범위 내에 있는지 확인
/// </summary> /// </summary>

View File

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using Colosseum.Abnormalities;
namespace Colosseum.Skills namespace Colosseum.Skills
{ {
/// <summary> /// <summary>
@@ -34,6 +36,21 @@ namespace Colosseum.Skills
public IReadOnlyList<SkillEffect> Effects => effects; public IReadOnlyList<SkillEffect> Effects => effects;
} }
/// <summary>
/// 젬이 적중 대상에게 부여할 이상상태 목록입니다.
/// </summary>
[Serializable]
public class SkillGemTriggeredAbnormalityEntry
{
[Tooltip("OnEffect(index)와 매칭되는 애니메이션 이벤트 인덱스")]
[Min(0)] [SerializeField] private int triggerIndex = 0;
[Tooltip("해당 인덱스에서 적중 대상에게 부여할 이상상태")]
[SerializeField] private List<AbnormalityData> abnormalities = new();
public int TriggerIndex => triggerIndex;
public IReadOnlyList<AbnormalityData> Abnormalities => abnormalities;
}
/// <summary> /// <summary>
/// 스킬의 기반 효과 위에 추가 동작을 덧붙이는 젬 데이터입니다. /// 스킬의 기반 효과 위에 추가 동작을 덧붙이는 젬 데이터입니다.
/// </summary> /// </summary>
@@ -55,6 +72,14 @@ namespace Colosseum.Skills
[Min(0f)] [SerializeField] private float cooldownMultiplier = 1f; [Min(0f)] [SerializeField] private float cooldownMultiplier = 1f;
[Tooltip("장착 시 스킬 애니메이션 재생 속도 배율")] [Tooltip("장착 시 스킬 애니메이션 재생 속도 배율")]
[Min(0.1f)] [SerializeField] private float castSpeedMultiplier = 1f; [Min(0.1f)] [SerializeField] private float castSpeedMultiplier = 1f;
[Tooltip("장착 시 스킬이 만드는 피해량 배율")]
[Min(0f)] [SerializeField] private float damageMultiplier = 1f;
[Tooltip("장착 시 스킬이 만드는 회복량 배율")]
[Min(0f)] [SerializeField] private float healMultiplier = 1f;
[Tooltip("장착 시 스킬이 만드는 보호막량 배율")]
[Min(0f)] [SerializeField] private float shieldMultiplier = 1f;
[Tooltip("장착 시 스킬이 만드는 위협량 배율")]
[Min(0f)] [SerializeField] private float threatMultiplier = 1f;
[Tooltip("기반 스킬 시전을 몇 회 더 반복할지 정의합니다. 현재는 계산/표시용으로만 사용됩니다.")] [Tooltip("기반 스킬 시전을 몇 회 더 반복할지 정의합니다. 현재는 계산/표시용으로만 사용됩니다.")]
[Min(0)] [SerializeField] private int additionalRepeatCount = 0; [Min(0)] [SerializeField] private int additionalRepeatCount = 0;
@@ -64,6 +89,12 @@ namespace Colosseum.Skills
[Tooltip("애니메이션 이벤트 인덱스별로 발동하는 추가 효과")] [Tooltip("애니메이션 이벤트 인덱스별로 발동하는 추가 효과")]
[SerializeField] private List<SkillGemTriggeredEffectEntry> triggeredEffects = new(); [SerializeField] private List<SkillGemTriggeredEffectEntry> triggeredEffects = new();
[Header("이상상태 부여")]
[Tooltip("스킬 사용 시 자신에게 즉시 부여할 이상상태")]
[SerializeField] private List<AbnormalityData> selfAbnormalities = new();
[Tooltip("애니메이션 이벤트 인덱스별로 적중 대상에게 부여할 이상상태")]
[SerializeField] private List<SkillGemTriggeredAbnormalityEntry> onHitAbnormalities = new();
public string GemName => gemName; public string GemName => gemName;
public string Description => description; public string Description => description;
public Sprite Icon => icon; public Sprite Icon => icon;
@@ -71,8 +102,14 @@ namespace Colosseum.Skills
public float ManaCostMultiplier => manaCostMultiplier; public float ManaCostMultiplier => manaCostMultiplier;
public float CooldownMultiplier => cooldownMultiplier; public float CooldownMultiplier => cooldownMultiplier;
public float CastSpeedMultiplier => castSpeedMultiplier; public float CastSpeedMultiplier => castSpeedMultiplier;
public float DamageMultiplier => damageMultiplier;
public float HealMultiplier => healMultiplier;
public float ShieldMultiplier => shieldMultiplier;
public float ThreatMultiplier => threatMultiplier;
public int AdditionalRepeatCount => additionalRepeatCount; public int AdditionalRepeatCount => additionalRepeatCount;
public IReadOnlyList<SkillEffect> CastStartEffects => castStartEffects; public IReadOnlyList<SkillEffect> CastStartEffects => castStartEffects;
public IReadOnlyList<SkillGemTriggeredEffectEntry> TriggeredEffects => triggeredEffects; public IReadOnlyList<SkillGemTriggeredEffectEntry> TriggeredEffects => triggeredEffects;
public IReadOnlyList<AbnormalityData> SelfAbnormalities => selfAbnormalities;
public IReadOnlyList<SkillGemTriggeredAbnormalityEntry> OnHitAbnormalities => onHitAbnormalities;
} }
} }

View File

@@ -2,6 +2,8 @@ using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using Colosseum.Abnormalities;
namespace Colosseum.Skills namespace Colosseum.Skills
{ {
/// <summary> /// <summary>
@@ -156,6 +158,26 @@ namespace Colosseum.Skills
return Mathf.Max(0.05f, resolved); return Mathf.Max(0.05f, resolved);
} }
public float GetResolvedDamageMultiplier()
{
return GetResolvedScalarMultiplier(gem => gem.DamageMultiplier);
}
public float GetResolvedHealMultiplier()
{
return GetResolvedScalarMultiplier(gem => gem.HealMultiplier);
}
public float GetResolvedShieldMultiplier()
{
return GetResolvedScalarMultiplier(gem => gem.ShieldMultiplier);
}
public float GetResolvedThreatMultiplier()
{
return GetResolvedScalarMultiplier(gem => gem.ThreatMultiplier);
}
public int GetResolvedRepeatCount() public int GetResolvedRepeatCount()
{ {
if (baseSkill == null) if (baseSkill == null)
@@ -210,6 +232,26 @@ namespace Colosseum.Skills
} }
} }
public void CollectCastStartAbnormalities(List<AbnormalityData> destination)
{
if (destination == null || socketedGems == null)
return;
for (int i = 0; i < socketedGems.Length; i++)
{
SkillGemData gem = socketedGems[i];
if (gem == null || gem.SelfAbnormalities == null)
continue;
for (int j = 0; j < gem.SelfAbnormalities.Count; j++)
{
AbnormalityData abnormality = gem.SelfAbnormalities[j];
if (abnormality != null)
destination.Add(abnormality);
}
}
}
public void CollectTriggeredEffects(Dictionary<int, List<SkillEffect>> destination) public void CollectTriggeredEffects(Dictionary<int, List<SkillEffect>> destination)
{ {
if (destination == null) if (destination == null)
@@ -254,6 +296,35 @@ namespace Colosseum.Skills
} }
} }
public void CollectTriggeredAbnormalities(Dictionary<int, List<AbnormalityData>> destination)
{
if (destination == null || socketedGems == null)
return;
for (int i = 0; i < socketedGems.Length; i++)
{
SkillGemData gem = socketedGems[i];
if (gem == null || gem.OnHitAbnormalities == null)
continue;
for (int j = 0; j < gem.OnHitAbnormalities.Count; j++)
{
SkillGemTriggeredAbnormalityEntry entry = gem.OnHitAbnormalities[j];
if (entry == null || entry.Abnormalities == null)
continue;
for (int k = 0; k < entry.Abnormalities.Count; k++)
{
AbnormalityData abnormality = entry.Abnormalities[k];
if (abnormality == null)
continue;
AddTriggeredAbnormality(destination, entry.TriggerIndex, abnormality);
}
}
}
}
private static void AddTriggeredEffect(Dictionary<int, List<SkillEffect>> destination, int triggerIndex, SkillEffect effect) private static void AddTriggeredEffect(Dictionary<int, List<SkillEffect>> destination, int triggerIndex, SkillEffect effect)
{ {
if (!destination.TryGetValue(triggerIndex, out List<SkillEffect> effectList)) if (!destination.TryGetValue(triggerIndex, out List<SkillEffect> effectList))
@@ -264,5 +335,75 @@ namespace Colosseum.Skills
effectList.Add(effect); effectList.Add(effect);
} }
private static void AddTriggeredAbnormality(Dictionary<int, List<AbnormalityData>> destination, int triggerIndex, AbnormalityData abnormality)
{
if (!destination.TryGetValue(triggerIndex, out List<AbnormalityData> abnormalityList))
{
abnormalityList = new List<AbnormalityData>();
destination.Add(triggerIndex, abnormalityList);
}
abnormalityList.Add(abnormality);
}
private float GetResolvedScalarMultiplier(System.Func<SkillGemData, float> selector)
{
if (baseSkill == null)
return 1f;
float resolved = 1f;
if (socketedGems == null)
return resolved;
for (int i = 0; i < socketedGems.Length; i++)
{
SkillGemData gem = socketedGems[i];
if (gem == null)
continue;
resolved *= Mathf.Max(0f, selector(gem));
}
return resolved;
}
}
/// <summary>
/// 현재 시전 중인 스킬 로드아웃의 젬 보정값을 안전하게 조회하는 유틸리티입니다.
/// </summary>
public static class SkillRuntimeModifierUtility
{
public static float GetDamageMultiplier(GameObject caster)
{
return GetCurrentLoadout(caster)?.GetResolvedDamageMultiplier() ?? 1f;
}
public static float GetHealMultiplier(GameObject caster)
{
return GetCurrentLoadout(caster)?.GetResolvedHealMultiplier() ?? 1f;
}
public static float GetShieldMultiplier(GameObject caster)
{
return GetCurrentLoadout(caster)?.GetResolvedShieldMultiplier() ?? 1f;
}
public static float GetThreatMultiplier(GameObject caster)
{
return GetCurrentLoadout(caster)?.GetResolvedThreatMultiplier() ?? 1f;
}
private static SkillLoadoutEntry GetCurrentLoadout(GameObject caster)
{
if (caster == null)
return null;
SkillController skillController = caster.GetComponent<SkillController>();
if (skillController == null)
return null;
return skillController.CurrentLoadoutEntry;
}
} }
} }