Files
Colosseum/Assets/_Game/Scripts/Editor/PlayerSkillDebugMenu.cs
dal4segno 29cb132d5d feat: 허수아비 DPS 벤치마크 씬 추가
- BalanceDummy 씬과 TrainingDummy 프리팹을 추가해 밸런싱용 허수아비 전투 공간을 구성
- TrainingDummyTarget과 DummyDpsBenchmarkRunner를 구현해 일정 시간 자동 시전 기반 DPS 측정을 지원
- 디버그 메뉴, 빌드 설정, 네트워크 프리팹 목록을 연결해 플레이 모드 검증 경로를 정리
2026-03-27 17:18:11 +09:00

1703 lines
73 KiB
C#

using System.Text;
using System.Collections.Generic;
using Colosseum.Enemy;
using Colosseum.Player;
using Colosseum.Skills;
using Colosseum.Skills.Effects;
using Colosseum.UI;
using Colosseum.Abnormalities;
using Colosseum.Combat;
using UnityEditor;
using UnityEngine;
namespace Colosseum.Editor
{
/// <summary>
/// 플레이 모드에서 로컬 플레이어 스킬과 보스 위협 상태를 빠르게 검증하는 디버그 메뉴입니다.
/// </summary>
public static class PlayerSkillDebugMenu
{
private const int TemporaryDebugSlotIndex = 5;
private const string HealSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_치유.asset";
private const string AreaHealSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_광역치유.asset";
private const string ShieldSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_보호막.asset";
private const string SlashSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_베기.asset";
private const string PierceSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_찌르기.asset";
private const string GemTestSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_젬테스트공격.asset";
private const string SpinSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_회전베기.asset";
private const string DashSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_돌진.asset";
private const string ProjectileSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_투사체.asset";
private const string TauntSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_도발.asset";
private const string GuardSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_방어태세.asset";
private const string IronWallSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_철벽.asset";
private const string EvadeSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_구르기.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 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 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 SkillGemFolderPath = "Assets/_Game/Data/SkillGems";
private const string LoadoutPresetFolderPath = "Assets/_Game/Data/Loadouts";
private const string CrushGemPath = 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 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 ImpactGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_충격.asset";
private const string BreachGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_관통.asset";
private const string EdgeDamageEffectPath = "Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_젬테스트_추가데미지A.asset";
private const string ImpactDamageEffectPath = "Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_젬테스트_추가데미지B.asset";
private const string BreachDamageEffectPath = "Assets/_Game/Data/Skills/Effects/Data_SkillEffect_Player_젬테스트_추가데미지C.asset";
private const string TankGemPresetPath = 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 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 SupportDualGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_지원_복합젬테스트.asset";
private const string DpsDualGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_딜러_복합젬테스트.asset";
private const string TankTripleGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_탱커_삼중젬테스트.asset";
private const string SupportTripleGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_지원_삼중젬테스트.asset";
private const string DpsTripleGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_딜러_삼중젬테스트.asset";
private const string DamageStackPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_공격삼중젬테스트.asset";
[MenuItem("Tools/Colosseum/Debug/Cast Local Skill 3")]
private static void CastLocalSkill3()
{
CastLocalSkill(2);
}
[MenuItem("Tools/Colosseum/Debug/Cast Local Skill R")]
private static void CastLocalSkillR()
{
CastLocalSkill(1);
}
[MenuItem("Tools/Colosseum/Debug/Cast Local Skill 4")]
private static void CastLocalSkill4()
{
CastLocalSkill(3);
}
[MenuItem("Tools/Colosseum/Debug/Cast Local Skill 5")]
private static void CastLocalSkill5()
{
CastLocalSkill(4);
}
[MenuItem("Tools/Colosseum/Debug/Cast Local Skill 6")]
private static void CastLocalSkill6()
{
CastLocalSkill(5);
}
[MenuItem("Tools/Colosseum/Debug/Cast Client1 Skill R")]
private static void CastClient1SkillR()
{
CastOwnedPlayerSkillAsServer(1, 1);
}
[MenuItem("Tools/Colosseum/Debug/Cast Client1 Skill 1")]
private static void CastClient1Skill1()
{
CastOwnedPlayerSkillAsServer(1, 2);
}
[MenuItem("Tools/Colosseum/Debug/Cast Client1 Skill 2")]
private static void CastClient1Skill2()
{
CastOwnedPlayerSkillAsServer(1, 3);
}
[MenuItem("Tools/Colosseum/Debug/Cast Client1 Skill 3")]
private static void CastClient1Skill3()
{
CastOwnedPlayerSkillAsServer(1, 4);
}
[MenuItem("Tools/Colosseum/Debug/Cast Client1 Skill 4")]
private static void CastClient1Skill4()
{
CastOwnedPlayerSkillAsServer(1, 5);
}
[MenuItem("Tools/Colosseum/Debug/Cast Local Heal")]
private static void CastLocalHeal()
{
CastLocalSkillAsset(HealSkillPath);
}
[MenuItem("Tools/Colosseum/Debug/Cast Local Area Heal")]
private static void CastLocalAreaHeal()
{
CastLocalSkillAsset(AreaHealSkillPath);
}
[MenuItem("Tools/Colosseum/Debug/Cast Local Shield")]
private static void CastLocalShield()
{
CastLocalSkillAsset(ShieldSkillPath);
}
[MenuItem("Tools/Colosseum/Debug/Damage Local Player 30")]
private static void DamageLocalPlayer30()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
PlayerNetworkController localNetworkController = FindLocalNetworkController();
if (localNetworkController == null)
{
Debug.LogWarning("[Debug] 로컬 PlayerNetworkController를 찾지 못했습니다.");
return;
}
localNetworkController.TakeDamageRpc(30f);
}
[MenuItem("Tools/Colosseum/Debug/Log Local Player Status")]
private static void LogLocalPlayerStatus()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
PlayerNetworkController localNetworkController = FindLocalNetworkController();
if (localNetworkController == null)
{
Debug.LogWarning("[Debug] 로컬 PlayerNetworkController를 찾지 못했습니다.");
return;
}
Debug.Log(
$"[Debug] 로컬 플레이어 상태 | HP {localNetworkController.Health:F1}/{localNetworkController.MaxHealth:F1} | " +
$"MP {localNetworkController.Mana:F1}/{localNetworkController.MaxMana:F1} | Shield {localNetworkController.Shield:F1}");
}
[MenuItem("Tools/Colosseum/Debug/Log Boss Threat Summary")]
private static void LogBossThreatSummary()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
EnemyBase[] enemies = Object.FindObjectsByType<EnemyBase>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
if (enemies == null || enemies.Length == 0)
{
Debug.LogWarning("[Debug] 활성 EnemyBase가 없습니다.");
return;
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < enemies.Length; i++)
{
EnemyBase enemy = enemies[i];
if (enemy == null)
continue;
if (builder.Length > 0)
builder.Append(" || ");
builder.Append(enemy.name);
builder.Append(" : ");
builder.Append(enemy.GetThreatDebugSummary().Replace("\r\n", " | ").Replace("\n", " | "));
}
Debug.Log($"[Debug] 보스 위협 요약 | {builder}");
}
[MenuItem("Tools/Colosseum/Debug/Log Boss Health")]
private static void LogBossHealth()
{
BossEnemy boss = BossEnemy.ActiveBoss != null ? BossEnemy.ActiveBoss : Object.FindFirstObjectByType<BossEnemy>();
if (boss == null)
{
Debug.LogWarning("[Debug] 활성 보스를 찾지 못했습니다.");
return;
}
Debug.Log($"[Debug] 보스 체력 | Name={boss.name} | HP={boss.CurrentHealth:0.###}/{boss.MaxHealth:0.###}");
}
[MenuItem("Tools/Colosseum/Debug/Reset Combat Balance Metrics")]
private static void ResetCombatBalanceMetrics()
{
CombatBalanceTracker.Reset();
Debug.Log("[Debug] 전투 밸런스 계측기를 초기화했습니다.");
}
[MenuItem("Tools/Colosseum/Debug/Log Combat Balance Summary")]
private static void LogCombatBalanceSummary()
{
string summary = CombatBalanceTracker.BuildSummary()
.Replace("\r\n", " || ")
.Replace("\n", " || ");
Debug.Log(summary);
}
[MenuItem("Tools/Colosseum/Debug/Start Dummy DPS Benchmark")]
private static void StartDummyDpsBenchmark()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
DummyDpsBenchmarkRunner benchmarkRunner = Object.FindFirstObjectByType<DummyDpsBenchmarkRunner>();
if (benchmarkRunner == null)
{
Debug.LogWarning("[Debug] DummyDpsBenchmarkRunner를 찾지 못했습니다.");
return;
}
benchmarkRunner.StartBenchmark();
}
[MenuItem("Tools/Colosseum/Debug/Log Last Dummy DPS Benchmark")]
private static void LogLastDummyDpsBenchmark()
{
DummyDpsBenchmarkRunner benchmarkRunner = Object.FindFirstObjectByType<DummyDpsBenchmarkRunner>();
if (benchmarkRunner == null)
{
Debug.LogWarning("[Debug] DummyDpsBenchmarkRunner를 찾지 못했습니다.");
return;
}
if (string.IsNullOrWhiteSpace(benchmarkRunner.LastSummary))
{
Debug.LogWarning("[Debug] 아직 완료된 허수아비 DPS 측정 결과가 없습니다.");
return;
}
Debug.Log(benchmarkRunner.LastSummary);
}
[MenuItem("Tools/Colosseum/Debug/Apply Local Stun")]
private static void ApplyLocalStun()
{
ApplyLocalAbnormality(StunAbnormalityPath);
}
[MenuItem("Tools/Colosseum/Debug/Apply Local Silence")]
private static void ApplyLocalSilence()
{
ApplyLocalAbnormality(SilenceAbnormalityPath);
}
[MenuItem("Tools/Colosseum/Debug/Apply Local Mark")]
private static void ApplyLocalMark()
{
ApplyLocalAbnormality(MarkAbnormalityPath);
}
[MenuItem("Tools/Colosseum/Debug/Apply Boss Stun")]
private static void ApplyBossStun()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
AbnormalityManager abnormalityManager = FindBossAbnormalityManager();
if (abnormalityManager == null)
{
Debug.LogWarning("[Debug] 보스 AbnormalityManager를 찾지 못했습니다.");
return;
}
AbnormalityData abnormality = AssetDatabase.LoadAssetAtPath<AbnormalityData>(StunAbnormalityPath);
if (abnormality == null)
{
Debug.LogWarning($"[Debug] 기절 이상상태 에셋을 찾지 못했습니다: {StunAbnormalityPath}");
return;
}
abnormalityManager.ApplyAbnormality(abnormality, abnormalityManager.gameObject);
Debug.Log($"[Debug] 보스에게 기절 적용 | Target={abnormalityManager.gameObject.name} | Abnormality={abnormality.abnormalityName}");
}
[MenuItem("Tools/Colosseum/Debug/Apply Boss Shield")]
private static void ApplyBossShield()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
BossEnemy bossEnemy = FindBossEnemy();
if (bossEnemy == null)
{
Debug.LogWarning("[Debug] 활성 보스를 찾지 못했습니다.");
return;
}
AbnormalityData shieldAbnormality = AssetDatabase.LoadAssetAtPath<AbnormalityData>(ShieldAbnormalityPath);
if (shieldAbnormality == null)
{
Debug.LogWarning($"[Debug] 보호막 이상상태 에셋을 찾지 못했습니다: {ShieldAbnormalityPath}");
return;
}
float appliedShield = bossEnemy.ApplyShield(120f, 8f, shieldAbnormality, bossEnemy.gameObject);
Debug.Log($"[Debug] 보스에게 보호막 적용 | Target={bossEnemy.name} | Applied={appliedShield:0.##} | CurrentShield={bossEnemy.Shield:0.##}");
}
[MenuItem("Tools/Colosseum/Debug/Apply Boss Shield Type A")]
private static void ApplyBossShieldTypeA()
{
ApplyBossShieldWithType(ShieldTypeAPath, 100f, 4f);
}
[MenuItem("Tools/Colosseum/Debug/Apply Boss Shield Type B")]
private static void ApplyBossShieldTypeB()
{
ApplyBossShieldWithType(ShieldTypeBPath, 150f, 8f);
}
[MenuItem("Tools/Colosseum/Debug/Force Boss Phase 2")]
private static void ForceBossPhase2()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
BossEnemy bossEnemy = FindBossEnemy();
if (bossEnemy == null)
{
Debug.LogWarning("[Debug] 활성 보스를 찾지 못했습니다.");
return;
}
bossEnemy.ForcePhaseTransition(1);
Debug.Log($"[Debug] 보스를 Phase 2로 강제 전환했습니다. | Target={bossEnemy.name}");
}
[MenuItem("Tools/Colosseum/Debug/Force Boss Signature")]
private static void ForceBossSignature()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
BossCombatBehaviorContext context = FindBossCombatContext();
if (context == null)
{
Debug.LogWarning("[Debug] 보스 전투 컨텍스트를 찾지 못했습니다.");
return;
}
if (!context.ForceStartSignaturePattern())
{
Debug.LogWarning("[Debug] 집행 개시를 강제로 시작하지 못했습니다. 이미 실행 중이거나 패턴이 비어 있을 수 있습니다.");
return;
}
Debug.Log($"[Debug] 집행 개시를 강제로 시작했습니다. | Target={context.gameObject.name}");
}
[MenuItem("Tools/Colosseum/Debug/Preview Boss Signature Telegraph")]
private static void PreviewBossSignatureTelegraph()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
BossCombatBehaviorContext context = FindBossCombatContext();
if (context == null)
{
Debug.LogWarning("[Debug] 보스 전투 컨텍스트를 찾지 못했습니다.");
return;
}
if (!context.PreviewSignatureTelegraph())
{
Debug.LogWarning("[Debug] 집행 개시 전조 프리뷰를 시작하지 못했습니다. 이미 다른 스킬이 재생 중일 수 있습니다.");
return;
}
Debug.Log($"[Debug] 집행 개시 전조 프리뷰를 재생했습니다. | Target={context.gameObject.name}");
}
private static void ApplyBossShieldWithType(string assetPath, float amount, float duration)
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
BossEnemy bossEnemy = FindBossEnemy();
if (bossEnemy == null)
{
Debug.LogWarning("[Debug] 활성 보스를 찾지 못했습니다.");
return;
}
AbnormalityData shieldAbnormality = AssetDatabase.LoadAssetAtPath<AbnormalityData>(assetPath);
if (shieldAbnormality == null)
{
Debug.LogWarning($"[Debug] 보호막 이상상태 에셋을 찾지 못했습니다: {assetPath}");
return;
}
float appliedShield = bossEnemy.ApplyShield(amount, duration, shieldAbnormality, bossEnemy.gameObject);
Debug.Log(
$"[Debug] 보스에게 타입 보호막 적용 | Target={bossEnemy.name} | ShieldType={shieldAbnormality.abnormalityName} | " +
$"Applied={appliedShield:0.##} | CurrentShield={bossEnemy.Shield:0.##} | Duration={duration:0.##}");
}
[MenuItem("Tools/Colosseum/Debug/Log HUD Abnormality Summary")]
private static void LogHudAbnormalitySummary()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
PlayerHUD playerHud = Object.FindFirstObjectByType<PlayerHUD>();
if (playerHud == null)
{
Debug.LogWarning("[Debug] PlayerHUD를 찾지 못했습니다.");
return;
}
Debug.Log($"[Debug] HUD 이상상태 요약 | {playerHud.CurrentAbnormalitySummary}");
}
[MenuItem("Tools/Colosseum/Debug/Log Boss HUD Abnormality Summary")]
private static void LogBossHudAbnormalitySummary()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
BossHealthBarUI bossHealthBarUi = Object.FindFirstObjectByType<BossHealthBarUI>();
if (bossHealthBarUi == null)
{
Debug.LogWarning("[Debug] BossHealthBarUI를 찾지 못했습니다.");
return;
}
Debug.Log($"[Debug] 보스 HUD 이상상태 요약 | {bossHealthBarUi.CurrentAbnormalitySummary}");
}
[MenuItem("Tools/Colosseum/Debug/Apply Tank Loadout")]
private static void ApplyTankLoadout()
{
ApplyLoadout(
"탱커",
SlashSkillPath,
TauntSkillPath,
GuardSkillPath,
DashSkillPath,
IronWallSkillPath,
PierceSkillPath,
EvadeSkillPath);
}
[MenuItem("Tools/Colosseum/Debug/Apply Support Loadout")]
private static void ApplySupportLoadout()
{
ApplyLoadout(
"지원",
SlashSkillPath,
HealSkillPath,
AreaHealSkillPath,
ShieldSkillPath,
DashSkillPath,
ProjectileSkillPath,
EvadeSkillPath);
}
[MenuItem("Tools/Colosseum/Debug/Apply DPS Loadout")]
private static void ApplyDpsLoadout()
{
ApplyLoadout(
"딜러",
SlashSkillPath,
PierceSkillPath,
SpinSkillPath,
DashSkillPath,
ProjectileSkillPath,
ShieldSkillPath,
EvadeSkillPath);
}
[MenuItem("Tools/Colosseum/Debug/Apply Tank Gem Loadout")]
private static void ApplyTankGemLoadout()
{
ApplyLoadoutPreset(TankGemPresetPath, "탱커 젬");
}
[MenuItem("Tools/Colosseum/Debug/Apply Support Gem Loadout")]
private static void ApplySupportGemLoadout()
{
ApplyLoadoutPreset(SupportGemPresetPath, "지원 젬");
}
[MenuItem("Tools/Colosseum/Debug/Apply DPS Gem Loadout")]
private static void ApplyDpsGemLoadout()
{
ApplyLoadoutPreset(DpsGemPresetPath, "딜러 젬");
}
[MenuItem("Tools/Colosseum/Debug/Apply Repeat Gem Loadout")]
private static void ApplyRepeatGemLoadout()
{
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")]
private static void ApplyTankDualGemLoadout()
{
ApplyLoadoutPreset(TankDualGemPresetPath, "탱커 복합 젬");
}
[MenuItem("Tools/Colosseum/Debug/Apply Support Dual Gem Loadout")]
private static void ApplySupportDualGemLoadout()
{
ApplyLoadoutPreset(SupportDualGemPresetPath, "지원 복합 젬");
}
[MenuItem("Tools/Colosseum/Debug/Apply DPS Dual Gem Loadout")]
private static void ApplyDpsDualGemLoadout()
{
ApplyLoadoutPreset(DpsDualGemPresetPath, "딜러 복합 젬");
}
[MenuItem("Tools/Colosseum/Debug/Apply Tank Triple Gem Loadout")]
private static void ApplyTankTripleGemLoadout()
{
ApplyLoadoutPreset(TankTripleGemPresetPath, "탱커 삼중 젬");
}
[MenuItem("Tools/Colosseum/Debug/Apply Support Triple Gem Loadout")]
private static void ApplySupportTripleGemLoadout()
{
ApplyLoadoutPreset(SupportTripleGemPresetPath, "지원 삼중 젬");
}
[MenuItem("Tools/Colosseum/Debug/Apply DPS Triple Gem Loadout")]
private static void ApplyDpsTripleGemLoadout()
{
ApplyLoadoutPreset(DpsTripleGemPresetPath, "딜러 삼중 젬");
}
[MenuItem("Tools/Colosseum/Debug/Apply Damage Stack Gem Loadout")]
private static void ApplyDamageStackGemLoadout()
{
ApplyLoadoutPreset(DamageStackPresetPath, "공격 삼중 젬");
}
[MenuItem("Tools/Colosseum/Setup/Create or Update Test Skill Gems")]
public static void CreateOrUpdateTestSkillGems()
{
EnsureFolder("Assets/_Game/Data", "SkillGems");
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 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 impactDamageEffect = CreateOrUpdateDamageEffectAsset(ImpactDamageEffectPath, 7f);
DamageEffect breachDamageEffect = CreateOrUpdateDamageEffectAsset(BreachDamageEffectPath, 10f);
CreateOrUpdateGemAsset(
CrushGemPath,
"파쇄",
"고위력 기술의 단일 피해를 강화하는 테스트용 젬",
SkillGemCategory.Damage,
1.15f,
1.1f,
1f,
1.2f,
1f,
1f,
1f,
0,
damageEffect,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset(
ChallengerGemPath,
"도전자",
"고위력 기술에 위협 선점 기능을 얹는 테스트용 젬",
SkillGemCategory.Special,
1f,
1f,
1f,
1f,
1f,
1f,
1.5f,
0,
tauntEffect,
allowedSkillRoles: SkillRoleType.Attack | SkillRoleType.Defense,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset(
GuardianGemPath,
"수호",
"고위력 기술에 보호막 보조를 얹는 테스트용 젬",
SkillGemCategory.Survival,
1.05f,
1.1f,
1f,
1f,
1.2f,
1.5f,
1f,
0,
shieldEffect,
allowedSkillRoles: SkillRoleType.All,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset(
RepeatGemPath,
"연속",
"붙은 스킬을 한 번 더 반복 시전하는 테스트용 젬",
SkillGemCategory.Special,
1.2f,
1.15f,
1.1f,
1f,
1f,
1f,
1f,
1,
null,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset(
FortitudeGemPath,
"강인함",
"스킬 사용 시 자신에게 경직 면역을 부여하는 테스트용 방어 젬",
SkillGemCategory.Survival,
1.05f,
1.05f,
1f,
1f,
1f,
1f,
1f,
0,
null,
new[] { hitReactionImmuneAbnormality },
allowedSkillRoles: SkillRoleType.All,
allowedSkillActivationTypes: SkillActivationType.All);
CreateOrUpdateGemAsset(
WitherGemPath,
"약화",
"스킬 적중 대상에게 테스트 디버프를 부여하는 제어 젬",
SkillGemCategory.Special,
1.05f,
1.05f,
1f,
1f,
1f,
1f,
1f,
0,
null,
null,
0,
new[] { testDebuffAbnormality },
allowedSkillRoles: SkillRoleType.All,
allowedSkillActivationTypes: SkillActivationType.Instant);
CreateOrUpdateGemAsset(
EdgeGemPath,
"예리함",
"고정 추가 피해를 부여하는 테스트용 공격 젬",
SkillGemCategory.Damage,
1f,
1f,
1f,
1f,
1f,
1f,
1f,
0,
edgeDamageEffect,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset(
ImpactGemPath,
"충격",
"중간 고정 추가 피해를 부여하는 테스트용 공격 젬",
SkillGemCategory.Damage,
1f,
1f,
1f,
1f,
1f,
1f,
1f,
0,
impactDamageEffect,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset(
BreachGemPath,
"관통",
"높은 고정 추가 피해를 부여하는 테스트용 공격 젬",
SkillGemCategory.Damage,
1f,
1f,
1f,
1f,
1f,
1f,
1f,
0,
breachDamageEffect,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("[Debug] 테스트용 젬 자산 생성/갱신 완료");
}
[MenuItem("Tools/Colosseum/Setup/Create or Update Test Loadout Presets")]
public static void CreateOrUpdateTestLoadoutPresets()
{
EnsureFolder("Assets/_Game/Data", "Loadouts");
CreateOrUpdateTestSkillGems();
SkillData slashSkill = AssetDatabase.LoadAssetAtPath<SkillData>(SlashSkillPath);
SkillData tauntSkill = AssetDatabase.LoadAssetAtPath<SkillData>(TauntSkillPath);
SkillData guardSkill = AssetDatabase.LoadAssetAtPath<SkillData>(GuardSkillPath);
SkillData dashSkill = AssetDatabase.LoadAssetAtPath<SkillData>(DashSkillPath);
SkillData ironWallSkill = AssetDatabase.LoadAssetAtPath<SkillData>(IronWallSkillPath);
SkillData pierceSkill = AssetDatabase.LoadAssetAtPath<SkillData>(PierceSkillPath);
SkillData gemTestSkill = AssetDatabase.LoadAssetAtPath<SkillData>(GemTestSkillPath);
SkillData healSkill = AssetDatabase.LoadAssetAtPath<SkillData>(HealSkillPath);
SkillData areaHealSkill = AssetDatabase.LoadAssetAtPath<SkillData>(AreaHealSkillPath);
SkillData shieldSkill = AssetDatabase.LoadAssetAtPath<SkillData>(ShieldSkillPath);
SkillData projectileSkill = AssetDatabase.LoadAssetAtPath<SkillData>(ProjectileSkillPath);
SkillData spinSkill = AssetDatabase.LoadAssetAtPath<SkillData>(SpinSkillPath);
SkillData evadeSkill = AssetDatabase.LoadAssetAtPath<SkillData>(EvadeSkillPath);
SkillGemData crushGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(CrushGemPath);
SkillGemData challengerGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(ChallengerGemPath);
SkillGemData guardianGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(GuardianGemPath);
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 impactGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(ImpactGemPath);
SkillGemData breachGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(BreachGemPath);
SetSkillClassification(slashSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillClassification(tauntSkill, SkillRoleType.Defense, SkillActivationType.Instant, SkillBaseType.Defense);
SetSkillClassification(guardSkill, SkillRoleType.Defense, SkillActivationType.Buff, SkillBaseType.Defense);
SetSkillClassification(dashSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillClassification(ironWallSkill, SkillRoleType.Defense, SkillActivationType.Buff, SkillBaseType.Defense);
SetSkillClassification(pierceSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillClassification(gemTestSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillClassification(healSkill, SkillRoleType.Support, SkillActivationType.Instant, SkillBaseType.Support);
SetSkillClassification(areaHealSkill, SkillRoleType.Support, SkillActivationType.Instant, SkillBaseType.Support);
SetSkillClassification(shieldSkill, SkillRoleType.Support, SkillActivationType.Instant, SkillBaseType.Support);
SetSkillClassification(projectileSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillClassification(spinSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillClassification(evadeSkill, SkillRoleType.Defense, SkillActivationType.Instant, SkillBaseType.Defense);
EnsureGemTestSkillSlotCount(gemTestSkill, 3);
CreateOrUpdatePresetAsset(
TankGemPresetPath,
"탱커 젬 테스트",
"도전자 젬을 사용하는 탱커 검증 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(tauntSkill),
CreateEntry(guardSkill),
CreateEntry(dashSkill),
CreateEntry(ironWallSkill),
CreateEntry(gemTestSkill, challengerGem),
CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset(
SupportGemPresetPath,
"지원 젬 테스트",
"수호 젬을 사용하는 지원 검증 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(healSkill),
CreateEntry(areaHealSkill),
CreateEntry(shieldSkill),
CreateEntry(dashSkill),
CreateEntry(gemTestSkill, guardianGem),
CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset(
DpsGemPresetPath,
"딜러 젬 테스트",
"파쇄 젬을 사용하는 딜러 검증 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(pierceSkill),
CreateEntry(spinSkill),
CreateEntry(dashSkill),
CreateEntry(projectileSkill),
CreateEntry(gemTestSkill, crushGem),
CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset(
RepeatGemPresetPath,
"반복 젬 테스트",
"연속 젬을 사용하는 반복 시전 검증 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(pierceSkill),
CreateEntry(spinSkill),
CreateEntry(dashSkill),
CreateEntry(projectileSkill),
CreateEntry(gemTestSkill, repeatGem),
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(
TankDualGemPresetPath,
"탱커 복합 젬 테스트",
"도전자 + 파쇄 젬을 동시에 사용하는 탱커 검증 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(tauntSkill),
CreateEntry(guardSkill),
CreateEntry(dashSkill),
CreateEntry(ironWallSkill),
CreateEntry(gemTestSkill, challengerGem, crushGem),
CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset(
SupportDualGemPresetPath,
"지원 복합 젬 테스트",
"수호 + 도전자 젬을 동시에 사용하는 지원 검증 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(healSkill),
CreateEntry(areaHealSkill),
CreateEntry(shieldSkill),
CreateEntry(dashSkill),
CreateEntry(gemTestSkill, guardianGem, challengerGem),
CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset(
DpsDualGemPresetPath,
"딜러 복합 젬 테스트",
"파쇄 + 수호 젬을 동시에 사용하는 딜러 검증 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(pierceSkill),
CreateEntry(spinSkill),
CreateEntry(dashSkill),
CreateEntry(projectileSkill),
CreateEntry(gemTestSkill, crushGem, guardianGem),
CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset(
TankTripleGemPresetPath,
"탱커 삼중 젬 테스트",
"도전자 + 파쇄 + 수호 젬을 동시에 사용하는 탱커 검증 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(tauntSkill),
CreateEntry(guardSkill),
CreateEntry(dashSkill),
CreateEntry(ironWallSkill),
CreateEntry(gemTestSkill, challengerGem, crushGem, guardianGem),
CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset(
SupportTripleGemPresetPath,
"지원 삼중 젬 테스트",
"수호 + 도전자 + 파쇄 젬을 동시에 사용하는 지원 검증 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(healSkill),
CreateEntry(areaHealSkill),
CreateEntry(shieldSkill),
CreateEntry(dashSkill),
CreateEntry(gemTestSkill, guardianGem, challengerGem, crushGem),
CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset(
DpsTripleGemPresetPath,
"딜러 삼중 젬 테스트",
"파쇄 + 수호 + 도전자 젬을 동시에 사용하는 딜러 검증 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(pierceSkill),
CreateEntry(spinSkill),
CreateEntry(dashSkill),
CreateEntry(projectileSkill),
CreateEntry(gemTestSkill, crushGem, guardianGem, challengerGem),
CreateEntry(evadeSkill)));
CreateOrUpdatePresetAsset(
DamageStackPresetPath,
"공격 삼중 젬 테스트",
"서로 다른 추가 피해 젬 3종을 동시에 사용하는 공격 검증 프리셋",
CreateLoadoutEntries(
CreateEntry(slashSkill),
CreateEntry(tauntSkill),
CreateEntry(guardSkill),
CreateEntry(dashSkill),
CreateEntry(ironWallSkill),
CreateEntry(gemTestSkill, edgeGem, impactGem, breachGem),
CreateEntry(evadeSkill)));
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("[Debug] 테스트용 젬 프리셋 생성/갱신 완료");
}
[MenuItem("Tools/Colosseum/Debug/Log Local Skill Loadout")]
private static void LogLocalSkillLoadout()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
PlayerSkillInput localSkillInput = FindLocalSkillInput();
if (localSkillInput == null)
{
Debug.LogWarning("[Debug] 로컬 PlayerSkillInput을 찾지 못했습니다.");
return;
}
string[] slotNames = { "L", "R", "1", "2", "3", "4", "Ctrl" };
int[] slotOrder = { 0, 1, 2, 3, 4, 5, 6 };
StringBuilder builder = new StringBuilder();
for (int i = 0; i < slotOrder.Length; i++)
{
SkillData skill = localSkillInput.GetSkill(slotOrder[i]);
SkillLoadoutEntry loadoutEntry = localSkillInput.GetSkillLoadout(slotOrder[i]);
builder.Append(slotNames[i]);
builder.Append(": ");
builder.Append(skill != null ? skill.SkillName : "(비어 있음)");
AppendGemSummary(builder, loadoutEntry);
if (i < slotOrder.Length - 1)
builder.Append(" | ");
}
Debug.Log($"[Debug] 로컬 스킬 구성 | {builder}");
}
[MenuItem("Tools/Colosseum/Debug/Log Local Skill 6 Resolved Stats")]
private static void LogLocalSkill6ResolvedStats()
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
PlayerSkillInput localSkillInput = FindLocalSkillInput();
if (localSkillInput == null)
{
Debug.LogWarning("[Debug] 로컬 PlayerSkillInput을 찾지 못했습니다.");
return;
}
SkillLoadoutEntry loadoutEntry = localSkillInput.GetSkillLoadout(5);
if (loadoutEntry == null || loadoutEntry.BaseSkill == null)
{
Debug.LogWarning("[Debug] 6번 슬롯 스킬이 비어 있습니다.");
return;
}
float resolvedManaCost = loadoutEntry.GetResolvedManaCost();
float resolvedCooldown = loadoutEntry.GetResolvedCooldown();
float resolvedAnimationSpeed = loadoutEntry.GetResolvedAnimationSpeed();
int resolvedRepeatCount = loadoutEntry.GetResolvedRepeatCount();
float resolvedDamageMultiplier = loadoutEntry.GetResolvedDamageMultiplier();
float resolvedHealMultiplier = loadoutEntry.GetResolvedHealMultiplier();
float resolvedShieldMultiplier = loadoutEntry.GetResolvedShieldMultiplier();
float resolvedThreatMultiplier = loadoutEntry.GetResolvedThreatMultiplier();
StringBuilder builder = new StringBuilder();
builder.Append("[Debug] 6번 슬롯 계산값 | ");
builder.Append(loadoutEntry.BaseSkill.SkillName);
AppendGemSummary(builder, loadoutEntry);
builder.Append(" | Mana=");
builder.Append(resolvedManaCost.ToString("0.###"));
builder.Append(" | Cooldown=");
builder.Append(resolvedCooldown.ToString("0.###"));
builder.Append(" | Speed=");
builder.Append(resolvedAnimationSpeed.ToString("0.###"));
builder.Append(" | Repeat=");
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(loadoutEntry.SocketedGems.Count);
AppendGemCategorySummary(builder, loadoutEntry);
Debug.Log(builder.ToString());
}
private static PlayerSkillInput FindLocalSkillInput()
{
PlayerSkillInput[] skillInputs = Object.FindObjectsByType<PlayerSkillInput>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
for (int i = 0; i < skillInputs.Length; i++)
{
if (skillInputs[i] != null && skillInputs[i].IsOwner)
return skillInputs[i];
}
return null;
}
private static PlayerNetworkController FindLocalNetworkController()
{
PlayerNetworkController[] networkControllers = Object.FindObjectsByType<PlayerNetworkController>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
for (int i = 0; i < networkControllers.Length; i++)
{
if (networkControllers[i] != null && networkControllers[i].IsOwner)
return networkControllers[i];
}
return null;
}
private static AbnormalityManager FindLocalAbnormalityManager()
{
PlayerNetworkController localNetworkController = FindLocalNetworkController();
if (localNetworkController == null)
{
return null;
}
return localNetworkController.GetComponent<AbnormalityManager>();
}
private static AbnormalityManager FindBossAbnormalityManager()
{
BossEnemy bossEnemy = FindBossEnemy();
if (bossEnemy == null)
return null;
return bossEnemy.GetComponent<AbnormalityManager>();
}
private static BossEnemy FindBossEnemy()
{
BossEnemy activeBoss = BossEnemy.ActiveBoss;
if (activeBoss != null)
return activeBoss;
return Object.FindFirstObjectByType<BossEnemy>();
}
private static BossCombatBehaviorContext FindBossCombatContext()
{
BossEnemy bossEnemy = FindBossEnemy();
return bossEnemy != null ? bossEnemy.GetComponent<BossCombatBehaviorContext>() : null;
}
private static void CastLocalSkill(int slotIndex)
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
PlayerSkillInput localSkillInput = FindLocalSkillInput();
if (localSkillInput == null)
{
Debug.LogWarning("[Debug] 로컬 PlayerSkillInput을 찾지 못했습니다.");
return;
}
localSkillInput.DebugCastSkill(slotIndex);
}
private static void CastLocalSkillAsset(string assetPath)
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
PlayerSkillInput localSkillInput = FindLocalSkillInput();
if (localSkillInput == null)
{
Debug.LogWarning("[Debug] 로컬 PlayerSkillInput을 찾지 못했습니다.");
return;
}
SkillData skill = AssetDatabase.LoadAssetAtPath<SkillData>(assetPath);
if (skill == null)
{
Debug.LogWarning($"[Debug] 스킬 에셋을 찾지 못했습니다: {assetPath}");
return;
}
localSkillInput.SetSkill(TemporaryDebugSlotIndex, skill);
localSkillInput.DebugCastSkill(TemporaryDebugSlotIndex);
}
private static void ApplyLocalAbnormality(string assetPath)
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
AbnormalityManager abnormalityManager = FindLocalAbnormalityManager();
if (abnormalityManager == null)
{
Debug.LogWarning("[Debug] 로컬 AbnormalityManager를 찾지 못했습니다.");
return;
}
AbnormalityData abnormality = AssetDatabase.LoadAssetAtPath<AbnormalityData>(assetPath);
if (abnormality == null)
{
Debug.LogWarning($"[Debug] 이상상태 에셋을 찾지 못했습니다: {assetPath}");
return;
}
abnormalityManager.ApplyAbnormality(abnormality, abnormalityManager.gameObject);
}
private static void ApplyLoadout(string loadoutName, params string[] skillPaths)
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
PlayerSkillInput localSkillInput = FindLocalSkillInput();
if (localSkillInput == null)
{
Debug.LogWarning("[Debug] 로컬 PlayerSkillInput을 찾지 못했습니다.");
return;
}
List<SkillData> skills = new List<SkillData>(skillPaths.Length);
for (int i = 0; i < skillPaths.Length; i++)
{
SkillData skill = AssetDatabase.LoadAssetAtPath<SkillData>(skillPaths[i]);
if (skill == null)
{
Debug.LogWarning($"[Debug] 스킬 에셋을 찾지 못했습니다: {skillPaths[i]}");
return;
}
skills.Add(skill);
}
localSkillInput.SetSkills(skills);
Debug.Log($"[Debug] {loadoutName} 프리셋을 적용했습니다.");
}
private static void ApplyLoadoutPreset(string presetPath, string presetLabel)
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
PlayerSkillInput localSkillInput = FindLocalSkillInput();
if (localSkillInput == null)
{
Debug.LogWarning("[Debug] 로컬 PlayerSkillInput을 찾지 못했습니다.");
return;
}
PlayerLoadoutPreset preset = AssetDatabase.LoadAssetAtPath<PlayerLoadoutPreset>(presetPath);
if (preset == null)
{
Debug.LogWarning($"[Debug] 프리셋 에셋을 찾지 못했습니다: {presetPath}");
return;
}
localSkillInput.ApplyLoadoutPreset(preset);
Debug.Log($"[Debug] {presetLabel} 프리셋을 적용했습니다.");
}
private static void EnsureFolder(string parentFolder, string childFolderName)
{
string combined = $"{parentFolder}/{childFolderName}";
if (AssetDatabase.IsValidFolder(combined))
return;
AssetDatabase.CreateFolder(parentFolder, childFolderName);
}
private static SkillLoadoutEntry[] CreateLoadoutEntries(params SkillLoadoutEntry[] entries)
{
return entries;
}
private static SkillLoadoutEntry CreateEntry(SkillData skill, params SkillGemData[] gems)
{
SkillLoadoutEntry entry = SkillLoadoutEntry.CreateTemporary(skill);
if (gems == null)
return entry;
for (int i = 0; i < gems.Length; i++)
{
entry.SetGem(i, gems[i]);
}
return entry;
}
private static void EnsureGemTestSkillSlotCount(SkillData skill, int slotCount)
{
if (skill == null)
return;
SerializedObject serializedSkill = new SerializedObject(skill);
SerializedProperty slotProperty = serializedSkill.FindProperty("maxGemSlotCount");
if (slotProperty == null || slotProperty.intValue == slotCount)
return;
slotProperty.intValue = slotCount;
serializedSkill.ApplyModifiedPropertiesWithoutUndo();
EditorUtility.SetDirty(skill);
}
private static void CreateOrUpdateGemAsset(
string assetPath,
string gemName,
string description,
SkillGemCategory category,
float manaCostMultiplier,
float cooldownMultiplier,
float castSpeedMultiplier,
float damageMultiplier,
float healMultiplier,
float shieldMultiplier,
float threatMultiplier,
int additionalRepeatCount,
SkillEffect triggeredEffect,
AbnormalityData[] selfAbnormalities = null,
int triggeredAbnormalityIndex = -1,
AbnormalityData[] onHitAbnormalities = null,
SkillRoleType allowedSkillRoles = SkillRoleType.All,
SkillActivationType allowedSkillActivationTypes = SkillActivationType.All,
SkillBaseType allowedSkillTypes = SkillBaseType.All,
SkillGemCategory[] incompatibleCategories = null,
SkillGemData[] incompatibleGems = null)
{
SkillGemData gem = AssetDatabase.LoadAssetAtPath<SkillGemData>(assetPath);
if (gem == null)
{
if (AssetDatabase.LoadMainAssetAtPath(assetPath) != null)
{
AssetDatabase.DeleteAsset(assetPath);
}
gem = ScriptableObject.CreateInstance<SkillGemData>();
AssetDatabase.CreateAsset(gem, assetPath);
}
SerializedObject serializedGem = new SerializedObject(gem);
serializedGem.FindProperty("gemName").stringValue = gemName;
serializedGem.FindProperty("description").stringValue = description;
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("cooldownMultiplier").floatValue = cooldownMultiplier;
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("allowedSkillTypes").intValue = (int)allowedSkillTypes;
SerializedProperty incompatibleCategoriesProperty = serializedGem.FindProperty("incompatibleCategories");
incompatibleCategoriesProperty.arraySize = incompatibleCategories != null ? incompatibleCategories.Length : 0;
for (int i = 0; i < incompatibleCategoriesProperty.arraySize; i++)
{
incompatibleCategoriesProperty.GetArrayElementAtIndex(i).enumValueIndex = (int)incompatibleCategories[i];
}
SerializedProperty incompatibleGemsProperty = serializedGem.FindProperty("incompatibleGems");
incompatibleGemsProperty.arraySize = incompatibleGems != null ? incompatibleGems.Length : 0;
for (int i = 0; i < incompatibleGemsProperty.arraySize; i++)
{
incompatibleGemsProperty.GetArrayElementAtIndex(i).objectReferenceValue = incompatibleGems[i];
}
SerializedProperty castStartEffectsProperty = serializedGem.FindProperty("castStartEffects");
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");
triggeredEffectsProperty.arraySize = triggeredEffect != null ? 1 : 0;
if (triggeredEffect != null)
{
SerializedProperty triggeredEntry = triggeredEffectsProperty.GetArrayElementAtIndex(0);
triggeredEntry.FindPropertyRelative("triggerIndex").intValue = 0;
SerializedProperty effectArray = triggeredEntry.FindPropertyRelative("effects");
effectArray.arraySize = 1;
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();
EditorUtility.SetDirty(gem);
}
private static void SetSkillClassification(
SkillData skill,
SkillRoleType skillRole,
SkillActivationType activationType,
SkillBaseType baseTypes)
{
if (skill == null)
return;
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");
if (baseTypesProperty != null && baseTypesProperty.intValue != (int)baseTypes)
{
baseTypesProperty.intValue = (int)baseTypes;
hasChanges = true;
}
if (!hasChanges)
return;
serializedSkill.ApplyModifiedPropertiesWithoutUndo();
EditorUtility.SetDirty(skill);
}
private static DamageEffect CreateOrUpdateDamageEffectAsset(string assetPath, float baseDamage)
{
DamageEffect effect = AssetDatabase.LoadAssetAtPath<DamageEffect>(assetPath);
if (effect == null)
{
if (AssetDatabase.LoadMainAssetAtPath(assetPath) != null)
{
AssetDatabase.DeleteAsset(assetPath);
}
effect = ScriptableObject.CreateInstance<DamageEffect>();
AssetDatabase.CreateAsset(effect, assetPath);
}
SerializedObject serializedEffect = new SerializedObject(effect);
serializedEffect.FindProperty("targetType").enumValueIndex = 1;
serializedEffect.FindProperty("targetTeam").enumValueIndex = 0;
serializedEffect.FindProperty("areaCenter").enumValueIndex = 1;
serializedEffect.FindProperty("areaShape").enumValueIndex = 0;
SerializedProperty targetLayers = serializedEffect.FindProperty("targetLayers");
if (targetLayers != null)
{
SerializedProperty bitsProperty = targetLayers.FindPropertyRelative("m_Bits");
if (bitsProperty != null)
bitsProperty.intValue = -1;
}
SerializedProperty includeCasterProperty = serializedEffect.FindProperty("includeCasterInArea");
if (includeCasterProperty != null)
includeCasterProperty.boolValue = false;
SerializedProperty areaRadiusProperty = serializedEffect.FindProperty("areaRadius");
if (areaRadiusProperty != null)
areaRadiusProperty.floatValue = 2f;
SerializedProperty fanOriginDistanceProperty = serializedEffect.FindProperty("fanOriginDistance");
if (fanOriginDistanceProperty != null)
fanOriginDistanceProperty.floatValue = 0f;
SerializedProperty fanRadiusProperty = serializedEffect.FindProperty("fanRadius");
if (fanRadiusProperty != null)
fanRadiusProperty.floatValue = 3f;
SerializedProperty fanHalfAngleProperty = serializedEffect.FindProperty("fanHalfAngle");
if (fanHalfAngleProperty != null)
fanHalfAngleProperty.floatValue = 45f;
serializedEffect.FindProperty("baseDamage").floatValue = baseDamage;
serializedEffect.FindProperty("damageType").enumValueIndex = (int)DamageType.True;
serializedEffect.FindProperty("statScaling").floatValue = 0f;
serializedEffect.ApplyModifiedPropertiesWithoutUndo();
EditorUtility.SetDirty(effect);
return effect;
}
private static void CreateOrUpdatePresetAsset(string assetPath, string presetName, string description, IReadOnlyList<SkillLoadoutEntry> entries)
{
PlayerLoadoutPreset preset = AssetDatabase.LoadAssetAtPath<PlayerLoadoutPreset>(assetPath);
if (preset == null)
{
if (AssetDatabase.LoadMainAssetAtPath(assetPath) != null)
{
AssetDatabase.DeleteAsset(assetPath);
}
preset = ScriptableObject.CreateInstance<PlayerLoadoutPreset>();
AssetDatabase.CreateAsset(preset, assetPath);
}
SerializedObject serializedPreset = new SerializedObject(preset);
serializedPreset.FindProperty("presetName").stringValue = presetName;
serializedPreset.FindProperty("description").stringValue = description;
SerializedProperty slotsProperty = serializedPreset.FindProperty("slots");
slotsProperty.arraySize = entries != null ? entries.Count : 0;
for (int i = 0; i < slotsProperty.arraySize; i++)
{
SkillLoadoutEntry entry = entries[i] != null ? entries[i].CreateCopy() : new SkillLoadoutEntry();
SerializedProperty slotProperty = slotsProperty.GetArrayElementAtIndex(i);
slotProperty.FindPropertyRelative("baseSkill").objectReferenceValue = entry.BaseSkill;
SerializedProperty gemsProperty = slotProperty.FindPropertyRelative("socketedGems");
gemsProperty.arraySize = entry.SocketedGems.Count;
for (int j = 0; j < gemsProperty.arraySize; j++)
{
gemsProperty.GetArrayElementAtIndex(j).objectReferenceValue = entry.GetGem(j);
}
}
serializedPreset.ApplyModifiedPropertiesWithoutUndo();
EditorUtility.SetDirty(preset);
}
private static void AppendGemSummary(StringBuilder builder, SkillLoadoutEntry loadoutEntry)
{
if (builder == null || loadoutEntry == null || loadoutEntry.SocketedGems == null)
return;
bool hasGem = false;
StringBuilder gemBuilder = new StringBuilder();
for (int i = 0; i < loadoutEntry.SocketedGems.Count; i++)
{
SkillGemData gem = loadoutEntry.SocketedGems[i];
if (gem == null)
continue;
if (hasGem)
gemBuilder.Append(", ");
gemBuilder.Append(gem.GemName);
hasGem = true;
}
if (!hasGem)
return;
builder.Append(" [");
builder.Append(gemBuilder);
builder.Append("]");
}
private static void AppendGemCategorySummary(StringBuilder builder, SkillLoadoutEntry loadoutEntry)
{
if (builder == null || loadoutEntry == null || loadoutEntry.SocketedGems == null)
return;
bool hasGem = false;
StringBuilder categoryBuilder = new StringBuilder();
for (int i = 0; i < loadoutEntry.SocketedGems.Count; i++)
{
SkillGemData gem = loadoutEntry.SocketedGems[i];
if (gem == null)
continue;
if (hasGem)
categoryBuilder.Append(", ");
categoryBuilder.Append(SkillClassificationUtility.GetGemCategoryLabel(gem.Category));
hasGem = true;
}
if (!hasGem)
return;
builder.Append(" | Category=");
builder.Append(categoryBuilder);
}
private static void CastOwnedPlayerSkillAsServer(ulong ownerClientId, int slotIndex)
{
if (!EditorApplication.isPlaying)
{
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
return;
}
PlayerSkillInput playerSkillInput = FindPlayerSkillInputByOwner(ownerClientId);
if (playerSkillInput == null)
{
Debug.LogWarning($"[Debug] OwnerClientId={ownerClientId} 인 PlayerSkillInput을 찾지 못했습니다.");
return;
}
bool executed = playerSkillInput.DebugExecuteSkillAsServer(slotIndex);
Debug.Log($"[Debug] 원격 스킬 실행 요청 | OwnerClientId={ownerClientId} | Slot={slotIndex} | Success={executed}");
}
private static PlayerSkillInput FindPlayerSkillInputByOwner(ulong ownerClientId)
{
PlayerSkillInput[] skillInputs = Object.FindObjectsByType<PlayerSkillInput>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
for (int i = 0; i < skillInputs.Length; i++)
{
PlayerSkillInput skillInput = skillInputs[i];
if (skillInput != null && skillInput.OwnerClientId == ownerClientId)
return skillInput;
}
return null;
}
}
}