feat: 젬 분류 사양과 장착 UI 반영

- 옵시디언 기준의 역할/발동 타입 분류를 스킬·젬 데이터와 장착 검증 로직에 반영
- 젬 보관 UI와 퀵슬롯 표시를 새 분류 및 실제 마나/쿨타임 계산 기준으로 갱신
- 테스트 스킬/젬 자산을 에디터 메뉴로 동기화하고 Unity 컴파일 및 플레이 검증 완료
This commit is contained in:
2026-03-26 16:18:45 +09:00
parent 1261d4dc3c
commit a94daf7968
27 changed files with 1738 additions and 59 deletions

View File

@@ -17,7 +17,9 @@ MonoBehaviour:
\uBA74\uC5ED\uC744 \uBD80\uC5EC\uD558\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uBC29\uC5B4 \uBA74\uC5ED\uC744 \uBD80\uC5EC\uD558\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uBC29\uC5B4
\uC82C" \uC82C"
icon: {fileID: 0} icon: {fileID: 0}
category: 3 category: 2
allowedSkillRoles: 7
allowedSkillActivationTypes: 3
allowedSkillTypes: 63 allowedSkillTypes: 63
incompatibleCategories: incompatibleCategories:
incompatibleGems: [] incompatibleGems: []

View File

@@ -17,6 +17,8 @@ MonoBehaviour:
\uD14C\uC2A4\uD2B8\uC6A9 \uACF5\uACA9 \uC82C" \uD14C\uC2A4\uD2B8\uC6A9 \uACF5\uACA9 \uC82C"
icon: {fileID: 0} icon: {fileID: 0}
category: 1 category: 1
allowedSkillRoles: 1
allowedSkillActivationTypes: 1
allowedSkillTypes: 1 allowedSkillTypes: 1
incompatibleCategories: incompatibleCategories:
incompatibleGems: [] incompatibleGems: []

View File

@@ -16,7 +16,9 @@ MonoBehaviour:
description: "\uACE0\uC704\uB825 \uAE30\uC220\uC5D0 \uC704\uD611 \uC120\uC810 \uAE30\uB2A5\uC744 description: "\uACE0\uC704\uB825 \uAE30\uC220\uC5D0 \uC704\uD611 \uC120\uC810 \uAE30\uB2A5\uC744
\uC5B9\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uC82C" \uC5B9\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uC82C"
icon: {fileID: 0} icon: {fileID: 0}
category: 2 category: 4
allowedSkillRoles: 3
allowedSkillActivationTypes: 1
allowedSkillTypes: 1 allowedSkillTypes: 1
incompatibleCategories: incompatibleCategories:
incompatibleGems: [] incompatibleGems: []

View File

@@ -16,7 +16,9 @@ MonoBehaviour:
description: "\uACE0\uC704\uB825 \uAE30\uC220\uC5D0 \uBCF4\uD638\uB9C9 \uBCF4\uC870\uB97C description: "\uACE0\uC704\uB825 \uAE30\uC220\uC5D0 \uBCF4\uD638\uB9C9 \uBCF4\uC870\uB97C
\uC5B9\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uC82C" \uC5B9\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uC82C"
icon: {fileID: 0} icon: {fileID: 0}
category: 4 category: 2
allowedSkillRoles: 7
allowedSkillActivationTypes: 1
allowedSkillTypes: 1 allowedSkillTypes: 1
incompatibleCategories: incompatibleCategories:
incompatibleGems: [] incompatibleGems: []

View File

@@ -16,7 +16,9 @@ MonoBehaviour:
description: "\uC2A4\uD0AC \uC801\uC911 \uB300\uC0C1\uC5D0\uAC8C \uD14C\uC2A4\uD2B8 description: "\uC2A4\uD0AC \uC801\uC911 \uB300\uC0C1\uC5D0\uAC8C \uD14C\uC2A4\uD2B8
\uB514\uBC84\uD504\uB97C \uBD80\uC5EC\uD558\uB294 \uC81C\uC5B4 \uC82C" \uB514\uBC84\uD504\uB97C \uBD80\uC5EC\uD558\uB294 \uC81C\uC5B4 \uC82C"
icon: {fileID: 0} icon: {fileID: 0}
category: 5 category: 4
allowedSkillRoles: 7
allowedSkillActivationTypes: 1
allowedSkillTypes: 63 allowedSkillTypes: 63
incompatibleCategories: incompatibleCategories:
incompatibleGems: [] incompatibleGems: []

View File

@@ -16,7 +16,9 @@ MonoBehaviour:
description: "\uBD99\uC740 \uC2A4\uD0AC\uC744 \uD55C \uBC88 \uB354 \uBC18\uBCF5 description: "\uBD99\uC740 \uC2A4\uD0AC\uC744 \uD55C \uBC88 \uB354 \uBC18\uBCF5
\uC2DC\uC804\uD558\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uC82C" \uC2DC\uC804\uD558\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uC82C"
icon: {fileID: 0} icon: {fileID: 0}
category: 6 category: 4
allowedSkillRoles: 1
allowedSkillActivationTypes: 1
allowedSkillTypes: 1 allowedSkillTypes: 1
incompatibleCategories: incompatibleCategories:
incompatibleGems: [] incompatibleGems: []

View File

@@ -17,6 +17,8 @@ MonoBehaviour:
\uD14C\uC2A4\uD2B8\uC6A9 \uACF5\uACA9 \uC82C" \uD14C\uC2A4\uD2B8\uC6A9 \uACF5\uACA9 \uC82C"
icon: {fileID: 0} icon: {fileID: 0}
category: 1 category: 1
allowedSkillRoles: 1
allowedSkillActivationTypes: 1
allowedSkillTypes: 1 allowedSkillTypes: 1
incompatibleCategories: incompatibleCategories:
incompatibleGems: [] incompatibleGems: []

View File

@@ -17,6 +17,8 @@ MonoBehaviour:
\uD14C\uC2A4\uD2B8\uC6A9 \uACF5\uACA9 \uC82C" \uD14C\uC2A4\uD2B8\uC6A9 \uACF5\uACA9 \uC82C"
icon: {fileID: 0} icon: {fileID: 0}
category: 1 category: 1
allowedSkillRoles: 1
allowedSkillActivationTypes: 1
allowedSkillTypes: 1 allowedSkillTypes: 1
incompatibleCategories: incompatibleCategories:
incompatibleGems: [] incompatibleGems: []

View File

@@ -17,6 +17,8 @@ MonoBehaviour:
\uAC15\uD654\uD558\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uC82C" \uAC15\uD654\uD558\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uC82C"
icon: {fileID: 0} icon: {fileID: 0}
category: 1 category: 1
allowedSkillRoles: 1
allowedSkillActivationTypes: 1
allowedSkillTypes: 1 allowedSkillTypes: 1
incompatibleCategories: incompatibleCategories:
incompatibleGems: [] incompatibleGems: []

View File

@@ -16,6 +16,8 @@ MonoBehaviour:
description: "\uC8FC\uBCC0 \uC544\uAD70\uACFC \uC790\uC2E0\uC758 \uCCB4\uB825\uC744 description: "\uC8FC\uBCC0 \uC544\uAD70\uACFC \uC790\uC2E0\uC758 \uCCB4\uB825\uC744
\uD568\uAED8 \uD68C\uBCF5\uD55C\uB2E4." \uD568\uAED8 \uD68C\uBCF5\uD55C\uB2E4."
icon: {fileID: 0} icon: {fileID: 0}
skillRole: 4
activationType: 1
baseTypes: 4 baseTypes: 4
skillClip: {fileID: -8689311932429934276, guid: 836c26605050496b9fd07dd456e6ea82, type: 3} skillClip: {fileID: -8689311932429934276, guid: 836c26605050496b9fd07dd456e6ea82, type: 3}
endClip: {fileID: 0} endClip: {fileID: 0}

View File

@@ -15,7 +15,9 @@ MonoBehaviour:
skillName: "\uAD6C\uB974\uAE30" skillName: "\uAD6C\uB974\uAE30"
description: description:
icon: {fileID: 21300000, guid: eafcc94eae3865944b93e64c4e281aa0, type: 3} icon: {fileID: 21300000, guid: eafcc94eae3865944b93e64c4e281aa0, type: 3}
baseTypes: 16 skillRole: 2
activationType: 1
baseTypes: 2
skillClip: {fileID: -14460799136228694, guid: d6d51384d6dd17a419c1d8e2a1c0c875, type: 3} skillClip: {fileID: -14460799136228694, guid: d6d51384d6dd17a419c1d8e2a1c0c875, type: 3}
endClip: {fileID: 0} endClip: {fileID: 0}
animationSpeed: 1 animationSpeed: 1

View File

@@ -17,7 +17,9 @@ MonoBehaviour:
\uB192\uC774\uACE0 \uC9E7\uC740 \uC2DC\uAC04 \uB3D9\uC548 \uC704\uD611 \uC0DD\uC131\uB7C9\uC744 \uB192\uC774\uACE0 \uC9E7\uC740 \uC2DC\uAC04 \uB3D9\uC548 \uC704\uD611 \uC0DD\uC131\uB7C9\uC744
\uC99D\uAC00\uC2DC\uD0A8\uB2E4." \uC99D\uAC00\uC2DC\uD0A8\uB2E4."
icon: {fileID: 0} icon: {fileID: 0}
baseTypes: 40 skillRole: 2
activationType: 1
baseTypes: 2
skillClip: {fileID: -4662563244894722208, guid: de4d0153716747cd9fc90c60f5efb1ae, type: 3} skillClip: {fileID: -4662563244894722208, guid: de4d0153716747cd9fc90c60f5efb1ae, type: 3}
endClip: {fileID: 0} endClip: {fileID: 0}
animationSpeed: 1 animationSpeed: 1

View File

@@ -15,7 +15,9 @@ MonoBehaviour:
skillName: "\uB3CC\uC9C4" skillName: "\uB3CC\uC9C4"
description: description:
icon: {fileID: 0} icon: {fileID: 0}
baseTypes: 16 skillRole: 1
activationType: 1
baseTypes: 1
skillClip: {fileID: 0} skillClip: {fileID: 0}
endClip: {fileID: 0} endClip: {fileID: 0}
animationSpeed: 1 animationSpeed: 1

View File

@@ -16,6 +16,8 @@ MonoBehaviour:
description: "\uC9E7\uC740 \uC2DC\uAC04 \uB3D9\uC548 \uBC1B\uB294 \uD53C\uD574\uB97C description: "\uC9E7\uC740 \uC2DC\uAC04 \uB3D9\uC548 \uBC1B\uB294 \uD53C\uD574\uB97C
\uC904\uC774\uACE0 \uC704\uD611 \uC0DD\uC131\uB7C9\uC744 \uB192\uC778\uB2E4." \uC904\uC774\uACE0 \uC704\uD611 \uC0DD\uC131\uB7C9\uC744 \uB192\uC778\uB2E4."
icon: {fileID: 0} icon: {fileID: 0}
skillRole: 2
activationType: 2
baseTypes: 2 baseTypes: 2
skillClip: {fileID: -592826573199220879, guid: 52e14756abda46499f4739d811043b3d, type: 3} skillClip: {fileID: -592826573199220879, guid: 52e14756abda46499f4739d811043b3d, type: 3}
endClip: {fileID: 0} endClip: {fileID: 0}

View File

@@ -16,7 +16,9 @@ MonoBehaviour:
description: "\uC8FC\uBCC0 \uC544\uAD70\uACFC \uC790\uC2E0\uC5D0\uAC8C \uD53C\uD574\uB97C description: "\uC8FC\uBCC0 \uC544\uAD70\uACFC \uC790\uC2E0\uC5D0\uAC8C \uD53C\uD574\uB97C
\uD761\uC218\uD558\uB294 \uBCF4\uD638\uB9C9\uC744 \uBD80\uC5EC\uD55C\uB2E4." \uD761\uC218\uD558\uB294 \uBCF4\uD638\uB9C9\uC744 \uBD80\uC5EC\uD55C\uB2E4."
icon: {fileID: 0} icon: {fileID: 0}
baseTypes: 6 skillRole: 4
activationType: 1
baseTypes: 4
skillClip: {fileID: -1185230921767219677, guid: f2d90cfa60b04630af1dde00f4d29320, type: 3} skillClip: {fileID: -1185230921767219677, guid: f2d90cfa60b04630af1dde00f4d29320, type: 3}
endClip: {fileID: 0} endClip: {fileID: 0}
animationSpeed: 1 animationSpeed: 1

View File

@@ -16,7 +16,9 @@ MonoBehaviour:
description: "\uC9E7\uC740 \uC2DC\uAC04 \uB3D9\uC548 \uBB34\uC801\uC774 \uB418\uBA70 description: "\uC9E7\uC740 \uC2DC\uAC04 \uB3D9\uC548 \uBB34\uC801\uC774 \uB418\uBA70
\uC704\uD611 \uC0DD\uC131\uB7C9\uC774 \uC57D\uAC04 \uC99D\uAC00\uD55C\uB2E4." \uC704\uD611 \uC0DD\uC131\uB7C9\uC774 \uC57D\uAC04 \uC99D\uAC00\uD55C\uB2E4."
icon: {fileID: 0} icon: {fileID: 0}
baseTypes: 6 skillRole: 2
activationType: 2
baseTypes: 2
skillClip: {fileID: -7313196749698736815, guid: 95764ba490b24918b73fc1553e34dc1e, type: 3} skillClip: {fileID: -7313196749698736815, guid: 95764ba490b24918b73fc1553e34dc1e, type: 3}
endClip: {fileID: 0} endClip: {fileID: 0}
animationSpeed: 1 animationSpeed: 1

View File

@@ -15,6 +15,8 @@ MonoBehaviour:
skillName: "\uCE58\uC720" skillName: "\uCE58\uC720"
description: "\uC790\uC2E0\uC758 \uCCB4\uB825\uC744 \uBE60\uB974\uAC8C \uD68C\uBCF5\uD55C\uB2E4." description: "\uC790\uC2E0\uC758 \uCCB4\uB825\uC744 \uBE60\uB974\uAC8C \uD68C\uBCF5\uD55C\uB2E4."
icon: {fileID: 0} icon: {fileID: 0}
skillRole: 4
activationType: 1
baseTypes: 4 baseTypes: 4
skillClip: {fileID: -8689311932429934276, guid: 4450ee0d92144ade9f63dd601432d3bf, type: 3} skillClip: {fileID: -8689311932429934276, guid: 4450ee0d92144ade9f63dd601432d3bf, type: 3}
endClip: {fileID: 0} endClip: {fileID: 0}

View File

@@ -12,6 +12,7 @@ GameObject:
- component: {fileID: 9156608660900377980} - component: {fileID: 9156608660900377980}
- component: {fileID: 3790117282943784127} - component: {fileID: 3790117282943784127}
- component: {fileID: 7777120333480524073} - component: {fileID: 7777120333480524073}
- component: {fileID: 110599025774604826}
m_Layer: 5 m_Layer: 5
m_Name: UI_ActionBar m_Name: UI_ActionBar
m_TagString: Untagged m_TagString: Untagged
@@ -115,6 +116,50 @@ MonoBehaviour:
- 2 - 2
- 3 - 3
- 4 - 4
--- !u!114 &110599025774604826
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 628750841697537993}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ca98554fbe3918040881b21c34fbb373, type: 3}
m_Name:
m_EditorClassIdentifier: Colosseum.Game::Colosseum.UI.SkillGemInventoryUI
toggleKey: 21
toggleButtonLabel: "\uC82C"
toggleButtonAnchoredPosition: {x: -48, y: 164}
ownedGemEntries:
- gem: {fileID: 11400000, guid: e020eee86f6c97f4393672759d73602e, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: 47a98aa9a30748a4da49455ac0fbd142, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: e86536592f45d2b49b9d25abbad1b184, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: de5e48980eba8794c93ea7168d592f8f, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: 2edf7687dc6caa0489ae2111499fcfab, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: 863dcd9e10827f94ab4574b529ffe683, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: 66ccf80cf9c50614dbe13ea7f24a6f19, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: cf3e3e1f9f1f42f499196fa819263dc1, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: 2c42bf0e90f5dd9488d534c337a44eed, type: 2}
quantity: 1
autoCollectOwnedGemsInEditor: 1
gemSearchFolder: Assets/_Game/Data/SkillGems
autoCollectedGemQuantity: 1
panelBackgroundColor: {r: 0.08, g: 0.08, b: 0.11, a: 0.96}
sectionBackgroundColor: {r: 0.14, g: 0.14, b: 0.18, a: 0.95}
buttonNormalColor: {r: 0.19, g: 0.19, b: 0.24, a: 0.96}
buttonSelectedColor: {r: 0.48, g: 0.32, b: 0.16, a: 0.96}
buttonDisabledColor: {r: 0.12, g: 0.12, b: 0.15, a: 0.65}
statusNormalColor: {r: 0.86, g: 0.85, b: 0.78, a: 1}
statusErrorColor: {r: 1, g: 0.52, b: 0.45, a: 1}
--- !u!1001 &3885202253629243258 --- !u!1001 &3885202253629243258
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -615,7 +615,7 @@ namespace Colosseum.Editor
CrushGemPath, CrushGemPath,
"파쇄", "파쇄",
"고위력 기술의 단일 피해를 강화하는 테스트용 젬", "고위력 기술의 단일 피해를 강화하는 테스트용 젬",
SkillGemCategory.Attack, SkillGemCategory.Damage,
1.15f, 1.15f,
1.1f, 1.1f,
1f, 1f,
@@ -625,13 +625,15 @@ namespace Colosseum.Editor
1f, 1f,
0, 0,
damageEffect, damageEffect,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack); allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset( CreateOrUpdateGemAsset(
ChallengerGemPath, ChallengerGemPath,
"도전자", "도전자",
"고위력 기술에 위협 선점 기능을 얹는 테스트용 젬", "고위력 기술에 위협 선점 기능을 얹는 테스트용 젬",
SkillGemCategory.Threat, SkillGemCategory.Special,
1f, 1f,
1f, 1f,
1f, 1f,
@@ -641,13 +643,15 @@ namespace Colosseum.Editor
1.5f, 1.5f,
0, 0,
tauntEffect, tauntEffect,
allowedSkillRoles: SkillRoleType.Attack | SkillRoleType.Defense,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack); allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset( CreateOrUpdateGemAsset(
GuardianGemPath, GuardianGemPath,
"수호", "수호",
"고위력 기술에 보호막 보조를 얹는 테스트용 젬", "고위력 기술에 보호막 보조를 얹는 테스트용 젬",
SkillGemCategory.Support, SkillGemCategory.Survival,
1.05f, 1.05f,
1.1f, 1.1f,
1f, 1f,
@@ -657,13 +661,15 @@ namespace Colosseum.Editor
1f, 1f,
0, 0,
shieldEffect, shieldEffect,
allowedSkillRoles: SkillRoleType.All,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack); allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset( CreateOrUpdateGemAsset(
RepeatGemPath, RepeatGemPath,
"연속", "연속",
"붙은 스킬을 한 번 더 반복 시전하는 테스트용 젬", "붙은 스킬을 한 번 더 반복 시전하는 테스트용 젬",
SkillGemCategory.Efficiency, SkillGemCategory.Special,
1.2f, 1.2f,
1.15f, 1.15f,
1.1f, 1.1f,
@@ -673,13 +679,15 @@ namespace Colosseum.Editor
1f, 1f,
1, 1,
null, null,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack); allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset( CreateOrUpdateGemAsset(
FortitudeGemPath, FortitudeGemPath,
"강인함", "강인함",
"스킬 사용 시 자신에게 경직 면역을 부여하는 테스트용 방어 젬", "스킬 사용 시 자신에게 경직 면역을 부여하는 테스트용 방어 젬",
SkillGemCategory.Defense, SkillGemCategory.Survival,
1.05f, 1.05f,
1.05f, 1.05f,
1f, 1f,
@@ -689,13 +697,15 @@ namespace Colosseum.Editor
1f, 1f,
0, 0,
null, null,
new[] { hitReactionImmuneAbnormality }); new[] { hitReactionImmuneAbnormality },
allowedSkillRoles: SkillRoleType.All,
allowedSkillActivationTypes: SkillActivationType.All);
CreateOrUpdateGemAsset( CreateOrUpdateGemAsset(
WitherGemPath, WitherGemPath,
"약화", "약화",
"스킬 적중 대상에게 테스트 디버프를 부여하는 제어 젬", "스킬 적중 대상에게 테스트 디버프를 부여하는 제어 젬",
SkillGemCategory.Control, SkillGemCategory.Special,
1.05f, 1.05f,
1.05f, 1.05f,
1f, 1f,
@@ -707,13 +717,15 @@ namespace Colosseum.Editor
null, null,
null, null,
0, 0,
new[] { testDebuffAbnormality }); new[] { testDebuffAbnormality },
allowedSkillRoles: SkillRoleType.All,
allowedSkillActivationTypes: SkillActivationType.Instant);
CreateOrUpdateGemAsset( CreateOrUpdateGemAsset(
EdgeGemPath, EdgeGemPath,
"예리함", "예리함",
"고정 추가 피해를 부여하는 테스트용 공격 젬", "고정 추가 피해를 부여하는 테스트용 공격 젬",
SkillGemCategory.Attack, SkillGemCategory.Damage,
1f, 1f,
1f, 1f,
1f, 1f,
@@ -723,13 +735,15 @@ namespace Colosseum.Editor
1f, 1f,
0, 0,
edgeDamageEffect, edgeDamageEffect,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack); allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset( CreateOrUpdateGemAsset(
ImpactGemPath, ImpactGemPath,
"충격", "충격",
"중간 고정 추가 피해를 부여하는 테스트용 공격 젬", "중간 고정 추가 피해를 부여하는 테스트용 공격 젬",
SkillGemCategory.Attack, SkillGemCategory.Damage,
1f, 1f,
1f, 1f,
1f, 1f,
@@ -739,13 +753,15 @@ namespace Colosseum.Editor
1f, 1f,
0, 0,
impactDamageEffect, impactDamageEffect,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack); allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset( CreateOrUpdateGemAsset(
BreachGemPath, BreachGemPath,
"관통", "관통",
"높은 고정 추가 피해를 부여하는 테스트용 공격 젬", "높은 고정 추가 피해를 부여하는 테스트용 공격 젬",
SkillGemCategory.Attack, SkillGemCategory.Damage,
1f, 1f,
1f, 1f,
1f, 1f,
@@ -755,6 +771,8 @@ namespace Colosseum.Editor
1f, 1f,
0, 0,
breachDamageEffect, breachDamageEffect,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack); allowedSkillTypes: SkillBaseType.Attack);
AssetDatabase.SaveAssets(); AssetDatabase.SaveAssets();
@@ -793,19 +811,19 @@ namespace Colosseum.Editor
SkillGemData impactGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(ImpactGemPath); SkillGemData impactGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(ImpactGemPath);
SkillGemData breachGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(BreachGemPath); SkillGemData breachGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(BreachGemPath);
SetSkillBaseTypes(slashSkill, SkillBaseType.Attack); SetSkillClassification(slashSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillBaseTypes(tauntSkill, SkillBaseType.Control | SkillBaseType.Utility); SetSkillClassification(tauntSkill, SkillRoleType.Defense, SkillActivationType.Instant, SkillBaseType.Defense);
SetSkillBaseTypes(guardSkill, SkillBaseType.Defense); SetSkillClassification(guardSkill, SkillRoleType.Defense, SkillActivationType.Buff, SkillBaseType.Defense);
SetSkillBaseTypes(dashSkill, SkillBaseType.Mobility); SetSkillClassification(dashSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillBaseTypes(ironWallSkill, SkillBaseType.Defense | SkillBaseType.Support); SetSkillClassification(ironWallSkill, SkillRoleType.Defense, SkillActivationType.Buff, SkillBaseType.Defense);
SetSkillBaseTypes(pierceSkill, SkillBaseType.Attack); SetSkillClassification(pierceSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillBaseTypes(gemTestSkill, SkillBaseType.Attack); SetSkillClassification(gemTestSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillBaseTypes(healSkill, SkillBaseType.Support); SetSkillClassification(healSkill, SkillRoleType.Support, SkillActivationType.Instant, SkillBaseType.Support);
SetSkillBaseTypes(areaHealSkill, SkillBaseType.Support); SetSkillClassification(areaHealSkill, SkillRoleType.Support, SkillActivationType.Instant, SkillBaseType.Support);
SetSkillBaseTypes(shieldSkill, SkillBaseType.Defense | SkillBaseType.Support); SetSkillClassification(shieldSkill, SkillRoleType.Support, SkillActivationType.Instant, SkillBaseType.Support);
SetSkillBaseTypes(projectileSkill, SkillBaseType.Attack); SetSkillClassification(projectileSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillBaseTypes(spinSkill, SkillBaseType.Attack); SetSkillClassification(spinSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillBaseTypes(evadeSkill, SkillBaseType.Mobility); SetSkillClassification(evadeSkill, SkillRoleType.Defense, SkillActivationType.Instant, SkillBaseType.Defense);
EnsureGemTestSkillSlotCount(gemTestSkill, 3); EnsureGemTestSkillSlotCount(gemTestSkill, 3);
@@ -1336,6 +1354,8 @@ namespace Colosseum.Editor
AbnormalityData[] selfAbnormalities = null, AbnormalityData[] selfAbnormalities = null,
int triggeredAbnormalityIndex = -1, int triggeredAbnormalityIndex = -1,
AbnormalityData[] onHitAbnormalities = null, AbnormalityData[] onHitAbnormalities = null,
SkillRoleType allowedSkillRoles = SkillRoleType.All,
SkillActivationType allowedSkillActivationTypes = SkillActivationType.All,
SkillBaseType allowedSkillTypes = SkillBaseType.All, SkillBaseType allowedSkillTypes = SkillBaseType.All,
SkillGemCategory[] incompatibleCategories = null, SkillGemCategory[] incompatibleCategories = null,
SkillGemData[] incompatibleGems = null) SkillGemData[] incompatibleGems = null)
@@ -1356,6 +1376,8 @@ namespace Colosseum.Editor
serializedGem.FindProperty("gemName").stringValue = gemName; serializedGem.FindProperty("gemName").stringValue = gemName;
serializedGem.FindProperty("description").stringValue = description; serializedGem.FindProperty("description").stringValue = description;
serializedGem.FindProperty("category").enumValueIndex = (int)category; serializedGem.FindProperty("category").enumValueIndex = (int)category;
serializedGem.FindProperty("allowedSkillRoles").intValue = (int)allowedSkillRoles;
serializedGem.FindProperty("allowedSkillActivationTypes").intValue = (int)allowedSkillActivationTypes;
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;
@@ -1424,17 +1446,42 @@ namespace Colosseum.Editor
EditorUtility.SetDirty(gem); EditorUtility.SetDirty(gem);
} }
private static void SetSkillBaseTypes(SkillData skill, SkillBaseType baseTypes) private static void SetSkillClassification(
SkillData skill,
SkillRoleType skillRole,
SkillActivationType activationType,
SkillBaseType baseTypes)
{ {
if (skill == null) if (skill == null)
return; return;
SerializedObject serializedSkill = new SerializedObject(skill); SerializedObject serializedSkill = new SerializedObject(skill);
bool hasChanges = false;
SerializedProperty skillRoleProperty = serializedSkill.FindProperty("skillRole");
if (skillRoleProperty != null && skillRoleProperty.intValue != (int)skillRole)
{
skillRoleProperty.intValue = (int)skillRole;
hasChanges = true;
}
SerializedProperty activationTypeProperty = serializedSkill.FindProperty("activationType");
if (activationTypeProperty != null && activationTypeProperty.intValue != (int)activationType)
{
activationTypeProperty.intValue = (int)activationType;
hasChanges = true;
}
SerializedProperty baseTypesProperty = serializedSkill.FindProperty("baseTypes"); SerializedProperty baseTypesProperty = serializedSkill.FindProperty("baseTypes");
if (baseTypesProperty == null || baseTypesProperty.intValue == (int)baseTypes) if (baseTypesProperty != null && baseTypesProperty.intValue != (int)baseTypes)
{
baseTypesProperty.intValue = (int)baseTypes;
hasChanges = true;
}
if (!hasChanges)
return; return;
baseTypesProperty.intValue = (int)baseTypes;
serializedSkill.ApplyModifiedPropertiesWithoutUndo(); serializedSkill.ApplyModifiedPropertiesWithoutUndo();
EditorUtility.SetDirty(skill); EditorUtility.SetDirty(skill);
} }
@@ -1571,7 +1618,7 @@ namespace Colosseum.Editor
if (hasGem) if (hasGem)
categoryBuilder.Append(", "); categoryBuilder.Append(", ");
categoryBuilder.Append(gem.Category); categoryBuilder.Append(SkillClassificationUtility.GetGemCategoryLabel(gem.Category));
hasGem = true; hasGem = true;
} }

View File

@@ -32,6 +32,7 @@ namespace Colosseum.Player
private Vector3 velocity; private Vector3 velocity;
private Vector2 moveInput; // 로컬 원시 입력 (IsOwner 전용) private Vector2 moveInput; // 로컬 원시 입력 (IsOwner 전용)
private InputSystem_Actions inputActions; private InputSystem_Actions inputActions;
private bool gameplayInputEnabled = true;
private bool isJumping; private bool isJumping;
private bool wasGrounded; private bool wasGrounded;
private Vector3 forcedMovementVelocity; private Vector3 forcedMovementVelocity;
@@ -97,10 +98,10 @@ namespace Colosseum.Player
private void InitializeInputActions() private void InitializeInputActions()
{ {
inputActions = new InputSystem_Actions(); inputActions = new InputSystem_Actions();
inputActions.Player.Enable();
inputActions.Player.Move.performed += OnMovePerformed; inputActions.Player.Move.performed += OnMovePerformed;
inputActions.Player.Move.canceled += OnMoveCanceled; inputActions.Player.Move.canceled += OnMoveCanceled;
inputActions.Player.Jump.performed += OnJumpPerformed; inputActions.Player.Jump.performed += OnJumpPerformed;
SetGameplayInputEnabled(true);
} }
private void CleanupInputActions() private void CleanupInputActions()
@@ -125,10 +126,7 @@ namespace Colosseum.Player
{ {
if (IsOwner && inputActions != null) if (IsOwner && inputActions != null)
{ {
inputActions.Player.Enable(); SetGameplayInputEnabled(gameplayInputEnabled);
inputActions.Player.Move.performed += OnMovePerformed;
inputActions.Player.Move.canceled += OnMoveCanceled;
inputActions.Player.Jump.performed += OnJumpPerformed;
} }
} }
@@ -153,9 +151,33 @@ namespace Colosseum.Player
private void OnMovePerformed(InputAction.CallbackContext context) => moveInput = context.ReadValue<Vector2>(); private void OnMovePerformed(InputAction.CallbackContext context) => moveInput = context.ReadValue<Vector2>();
private void OnMoveCanceled(InputAction.CallbackContext context) => moveInput = Vector2.zero; private void OnMoveCanceled(InputAction.CallbackContext context) => moveInput = Vector2.zero;
/// <summary>
/// 로컬 플레이어의 전투 입력을 일시적으로 차단하거나 복구합니다.
/// </summary>
public void SetGameplayInputEnabled(bool enabled)
{
gameplayInputEnabled = enabled;
if (!IsOwner || inputActions == null)
return;
if (enabled)
{
inputActions.Player.Enable();
return;
}
moveInput = Vector2.zero;
if (netMoveInput.Value != Vector2.zero)
netMoveInput.Value = Vector2.zero;
inputActions.Player.Disable();
}
private void OnJumpPerformed(InputAction.CallbackContext context) private void OnJumpPerformed(InputAction.CallbackContext context)
{ {
if (!IsOwner) return; if (!IsOwner) return;
if (!gameplayInputEnabled) return;
if (actionState != null && !actionState.CanJump) return; if (actionState != null && !actionState.CanJump) return;
JumpRequestRpc(); JumpRequestRpc();

View File

@@ -77,6 +77,7 @@ namespace Colosseum.Player
[SerializeField] private PlayerActionState actionState; [SerializeField] private PlayerActionState actionState;
private InputSystem_Actions inputActions; private InputSystem_Actions inputActions;
private bool gameplayInputEnabled = true;
public SkillData[] SkillSlots => skillSlots; public SkillData[] SkillSlots => skillSlots;
public SkillLoadoutEntry[] SkillLoadoutEntries => skillLoadoutEntries; public SkillLoadoutEntry[] SkillLoadoutEntries => skillLoadoutEntries;
@@ -117,7 +118,7 @@ namespace Colosseum.Player
inputActions.Player.Evade.performed += OnEvadePerformed; inputActions.Player.Evade.performed += OnEvadePerformed;
} }
inputActions.Player.Enable(); SetGameplayInputEnabled(true);
} }
public override void OnNetworkDespawn() public override void OnNetworkDespawn()
@@ -148,10 +149,29 @@ namespace Colosseum.Player
{ {
if (IsOwner && inputActions != null) if (IsOwner && inputActions != null)
{ {
inputActions.Player.Enable(); SetGameplayInputEnabled(gameplayInputEnabled);
} }
} }
/// <summary>
/// 로컬 플레이어의 스킬 입력을 일시적으로 차단하거나 복구합니다.
/// </summary>
public void SetGameplayInputEnabled(bool enabled)
{
gameplayInputEnabled = enabled;
if (!IsOwner || inputActions == null)
return;
if (enabled)
{
inputActions.Player.Enable();
return;
}
inputActions.Player.Disable();
}
/// <summary> /// <summary>
/// 기존 프리팹이나 씬 직렬화 데이터가 6칸으로 남아 있어도 /// 기존 프리팹이나 씬 직렬화 데이터가 6칸으로 남아 있어도
/// 긴급 회피 슬롯까지 포함한 7칸 구성을 항상 보장합니다. /// 긴급 회피 슬롯까지 포함한 7칸 구성을 항상 보장합니다.
@@ -244,6 +264,9 @@ namespace Colosseum.Player
/// </summary> /// </summary>
private void OnSkillInput(int slotIndex) private void OnSkillInput(int slotIndex)
{ {
if (!gameplayInputEnabled)
return;
if (slotIndex < 0 || slotIndex >= skillSlots.Length) if (slotIndex < 0 || slotIndex >= skillSlots.Length)
return; return;

View File

@@ -22,6 +22,32 @@ namespace Colosseum.Skills
All = Attack | Defense | Support | Control | Mobility | Utility, All = Attack | Defense | Support | Control | Mobility | Utility,
} }
/// <summary>
/// 스킬의 역할 분류입니다.
/// 젬 장착 조건에는 비트 마스크 형태로도 사용합니다.
/// </summary>
[Flags]
public enum SkillRoleType
{
None = 0,
Attack = 1 << 0,
Defense = 1 << 1,
Support = 1 << 2,
All = Attack | Defense | Support,
}
/// <summary>
/// 스킬의 발동 타입 분류입니다.
/// </summary>
[Flags]
public enum SkillActivationType
{
None = 0,
Instant = 1 << 0,
Buff = 1 << 1,
All = Instant | Buff,
}
/// <summary> /// <summary>
/// 스킬 데이터. 스킬의 기본 정보와 효과 목록을 관리합니다. /// 스킬 데이터. 스킬의 기본 정보와 효과 목록을 관리합니다.
/// </summary> /// </summary>
@@ -34,8 +60,14 @@ namespace Colosseum.Skills
[SerializeField] private string description; [SerializeField] private string description;
[SerializeField] private Sprite icon; [SerializeField] private Sprite icon;
[Header("기반 스킬 분류")] [Header("스킬 분류")]
[Tooltip("젬 장착 가능 조건에 사용하는 기반 스킬 분류")] [Tooltip("이 스킬의 주 역할입니다.")]
[SerializeField] private SkillRoleType skillRole = SkillRoleType.Attack;
[Tooltip("이 스킬의 발동 타입입니다.")]
[SerializeField] private SkillActivationType activationType = SkillActivationType.Instant;
[Header("레거시 기반 스킬 분류")]
[Tooltip("기존 테스트 데이터와의 호환을 위한 기반 분류입니다.")]
[SerializeField] private SkillBaseType baseTypes = SkillBaseType.None; [SerializeField] private SkillBaseType baseTypes = SkillBaseType.None;
[Header("애니메이션")] [Header("애니메이션")]
@@ -82,6 +114,8 @@ namespace Colosseum.Skills
public string SkillName => skillName; public string SkillName => skillName;
public string Description => description; public string Description => description;
public Sprite Icon => icon; public Sprite Icon => icon;
public SkillRoleType SkillRole => skillRole;
public SkillActivationType ActivationType => activationType;
public SkillBaseType BaseTypes => baseTypes; public SkillBaseType BaseTypes => baseTypes;
public AnimationClip SkillClip => skillClip; public AnimationClip SkillClip => skillClip;
public AnimationClip EndClip => endClip; public AnimationClip EndClip => endClip;
@@ -97,5 +131,104 @@ namespace Colosseum.Skills
public bool BlockOtherSkillsWhileCasting => blockOtherSkillsWhileCasting; public bool BlockOtherSkillsWhileCasting => blockOtherSkillsWhileCasting;
public IReadOnlyList<SkillEffect> CastStartEffects => castStartEffects; public IReadOnlyList<SkillEffect> CastStartEffects => castStartEffects;
public IReadOnlyList<SkillEffect> Effects => effects; public IReadOnlyList<SkillEffect> Effects => effects;
/// <summary>
/// 지정한 장착 조건과 현재 스킬 분류가 맞는지 확인합니다.
/// </summary>
public bool MatchesClassification(SkillRoleType allowedRoles, SkillActivationType allowedActivationTypes)
{
bool matchesRole = allowedRoles == SkillRoleType.None ||
allowedRoles == SkillRoleType.All ||
(allowedRoles & skillRole) != 0;
bool matchesActivationType = allowedActivationTypes == SkillActivationType.None ||
allowedActivationTypes == SkillActivationType.All ||
(allowedActivationTypes & activationType) != 0;
return matchesRole && matchesActivationType;
}
}
/// <summary>
/// 스킬/젬 분류를 UI 친화적인 문자열로 변환하는 유틸리티입니다.
/// </summary>
public static class SkillClassificationUtility
{
public static string GetRoleLabel(SkillRoleType role)
{
return role switch
{
SkillRoleType.Attack => "공격",
SkillRoleType.Defense => "방어",
SkillRoleType.Support => "지원",
SkillRoleType.All => "전체",
_ => "미분류",
};
}
public static string GetActivationTypeLabel(SkillActivationType activationType)
{
return activationType switch
{
SkillActivationType.Instant => "즉발",
SkillActivationType.Buff => "버프",
SkillActivationType.All => "전체",
_ => "미분류",
};
}
public static string GetSkillClassificationLabel(SkillData skill)
{
if (skill == null)
return "미분류";
return $"{GetRoleLabel(skill.SkillRole)}/{GetActivationTypeLabel(skill.ActivationType)}";
}
public static string GetGemCategoryLabel(SkillGemCategory category)
{
return category switch
{
SkillGemCategory.Damage => "데미지",
SkillGemCategory.Survival => "생존",
SkillGemCategory.Mana => "마나",
SkillGemCategory.Special => "특수",
SkillGemCategory.BuffPower => "효과 강화",
SkillGemCategory.Duration => "지속시간",
SkillGemCategory.Area => "범위",
SkillGemCategory.Cost => "비용",
_ => "공용",
};
}
public static string GetAllowedRoleSummary(SkillRoleType roles)
{
if (roles == SkillRoleType.None || roles == SkillRoleType.All)
return "전체";
List<string> labels = new List<string>();
if ((roles & SkillRoleType.Attack) != 0)
labels.Add("공격");
if ((roles & SkillRoleType.Defense) != 0)
labels.Add("방어");
if ((roles & SkillRoleType.Support) != 0)
labels.Add("지원");
return labels.Count > 0 ? string.Join(" + ", labels) : "미분류";
}
public static string GetAllowedActivationSummary(SkillActivationType activationTypes)
{
if (activationTypes == SkillActivationType.None || activationTypes == SkillActivationType.All)
return "전체";
List<string> labels = new List<string>();
if ((activationTypes & SkillActivationType.Instant) != 0)
labels.Add("즉발");
if ((activationTypes & SkillActivationType.Buff) != 0)
labels.Add("버프");
return labels.Count > 0 ? string.Join(" + ", labels) : "미분류";
}
} }
} }

View File

@@ -13,12 +13,14 @@ namespace Colosseum.Skills
public enum SkillGemCategory public enum SkillGemCategory
{ {
Common, Common,
Attack, Damage,
Threat, Survival,
Defense, Mana,
Support, Special,
Control, BuffPower,
Efficiency, Duration,
Area,
Cost,
} }
/// <summary> /// <summary>
@@ -66,9 +68,13 @@ namespace Colosseum.Skills
[SerializeField] private SkillGemCategory category = SkillGemCategory.Common; [SerializeField] private SkillGemCategory category = SkillGemCategory.Common;
[Header("장착 제약")] [Header("장착 제약")]
[Tooltip("장착 가능한 기반 스킬 분류입니다. None 또는 All이면 제한하지 않습니다.")] [Tooltip("장착 가능한 스킬 역할 조합입니다. None 또는 All이면 역할 제한을 두지 않습니다.")]
[SerializeField] private SkillRoleType allowedSkillRoles = SkillRoleType.All;
[Tooltip("장착 가능한 스킬 발동 타입 조합입니다. None 또는 All이면 타입 제한을 두지 않습니다.")]
[SerializeField] private SkillActivationType allowedSkillActivationTypes = SkillActivationType.All;
[Tooltip("기존 테스트 자산과의 호환을 위한 기반 스킬 분류입니다. 새 사양에서는 역할/발동 타입을 우선 사용합니다.")]
[SerializeField] private SkillBaseType allowedSkillTypes = SkillBaseType.All; [SerializeField] private SkillBaseType allowedSkillTypes = SkillBaseType.All;
[Tooltip("함께 장착할 수 없는 젬 분류")] [Tooltip("함께 장착할 수 없는 젬 효과 분류")]
[SerializeField] private SkillGemCategory[] incompatibleCategories = Array.Empty<SkillGemCategory>(); [SerializeField] private SkillGemCategory[] incompatibleCategories = Array.Empty<SkillGemCategory>();
[Tooltip("함께 장착할 수 없는 특정 젬")] [Tooltip("함께 장착할 수 없는 특정 젬")]
[SerializeField] private List<SkillGemData> incompatibleGems = new(); [SerializeField] private List<SkillGemData> incompatibleGems = new();
@@ -107,6 +113,8 @@ namespace Colosseum.Skills
public string Description => description; public string Description => description;
public Sprite Icon => icon; public Sprite Icon => icon;
public SkillGemCategory Category => category; public SkillGemCategory Category => category;
public SkillRoleType AllowedSkillRoles => allowedSkillRoles;
public SkillActivationType AllowedSkillActivationTypes => allowedSkillActivationTypes;
public SkillBaseType AllowedSkillTypes => allowedSkillTypes; public SkillBaseType AllowedSkillTypes => allowedSkillTypes;
public float ManaCostMultiplier => manaCostMultiplier; public float ManaCostMultiplier => manaCostMultiplier;
public float CooldownMultiplier => cooldownMultiplier; public float CooldownMultiplier => cooldownMultiplier;
@@ -131,6 +139,15 @@ namespace Colosseum.Skills
if (skill == null) if (skill == null)
return false; return false;
bool usesRoleRestriction = allowedSkillRoles != SkillRoleType.None && allowedSkillRoles != SkillRoleType.All;
bool usesActivationRestriction = allowedSkillActivationTypes != SkillActivationType.None &&
allowedSkillActivationTypes != SkillActivationType.All;
if (usesRoleRestriction || usesActivationRestriction)
{
return skill.MatchesClassification(allowedSkillRoles, allowedSkillActivationTypes);
}
if (allowedSkillTypes == SkillBaseType.None || allowedSkillTypes == SkillBaseType.All) if (allowedSkillTypes == SkillBaseType.None || allowedSkillTypes == SkillBaseType.All)
return true; return true;

View File

@@ -425,7 +425,11 @@ namespace Colosseum.Skills
if (!gem.CanAttachToSkill(baseSkill)) if (!gem.CanAttachToSkill(baseSkill))
{ {
reason = $"기반 스킬 분류 제약을 만족하지 않습니다. Skill={baseSkill.BaseTypes}, Allowed={gem.AllowedSkillTypes}"; string skillClassification = SkillClassificationUtility.GetSkillClassificationLabel(baseSkill);
string allowedClassification =
$"{SkillClassificationUtility.GetAllowedRoleSummary(gem.AllowedSkillRoles)}/" +
$"{SkillClassificationUtility.GetAllowedActivationSummary(gem.AllowedSkillActivationTypes)}";
reason = $"장착 가능한 스킬 조합이 아닙니다. Skill={skillClassification}, Allowed={allowedClassification}";
return false; return false;
} }
@@ -476,7 +480,9 @@ namespace Colosseum.Skills
if (gem.IsCategoryIncompatible(otherGem.Category) || otherGem.IsCategoryIncompatible(gem.Category)) if (gem.IsCategoryIncompatible(otherGem.Category) || otherGem.IsCategoryIncompatible(gem.Category))
{ {
reason = $"{gem.Category} / {otherGem.Category} 분류 조합은 허용되지 않습니다."; reason =
$"{SkillClassificationUtility.GetGemCategoryLabel(gem.Category)} / " +
$"{SkillClassificationUtility.GetGemCategoryLabel(otherGem.Category)} 효과 분류 조합은 허용되지 않습니다.";
return false; return false;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ca98554fbe3918040881b21c34fbb373

View File

@@ -283,9 +283,11 @@ namespace Colosseum.UI
SkillData skill = playerSkillInput.GetSkill(mappedSlotIndex); SkillData skill = playerSkillInput.GetSkill(mappedSlotIndex);
if (skill == null) continue; if (skill == null) continue;
SkillLoadoutEntry loadoutEntry = playerSkillInput.GetSkillLoadout(mappedSlotIndex);
float remainingCooldown = playerSkillInput.GetRemainingCooldown(mappedSlotIndex); float remainingCooldown = playerSkillInput.GetRemainingCooldown(mappedSlotIndex);
float totalCooldown = skill.Cooldown; float totalCooldown = loadoutEntry != null ? loadoutEntry.GetResolvedCooldown() : skill.Cooldown;
bool hasEnoughMana = networkController == null || networkController.Mana >= skill.ManaCost; float requiredMana = loadoutEntry != null ? loadoutEntry.GetResolvedManaCost() : skill.ManaCost;
bool hasEnoughMana = networkController == null || networkController.Mana >= requiredMana;
if (shouldLog && remainingCooldown > 0f) if (shouldLog && remainingCooldown > 0f)
{ {