using System; using BumpCombat.Core; using BumpCombat.Enemies; using UnityEngine; namespace BumpCombat.Constants { [Serializable] public struct SpawnDensityPhase { [Tooltip("다음 단계로 넘어가는 진행시간 (초). 마지막 단계는 무시됩니다.")] public float EndTimeSeconds; [Min(0)] public int CrowdTarget; [Min(0)] public int NormalTarget; public SpawnDensityPhase(float endTimeSeconds, int crowdTarget, int normalTarget) { EndTimeSeconds = endTimeSeconds; CrowdTarget = crowdTarget; NormalTarget = normalTarget; } } [CreateAssetMenu(menuName = "BumpCombat/Constants/Run and Spawning")] public sealed class RunConstants : ScriptableObject { [Header("런 이벤트 시간표 (초)")] public float[] DevelopmentEliteEventTimes = { 36f, 72f, 108f, 144f, 180f, 216f }; public float[] FormalEliteEventTimes = { 180f, 360f, 540f, 720f, 900f, 1080f }; public float[] ProductionEliteEventTimes = { 180f, 360f, 540f, 720f }; [Min(0f)] public float DevelopmentProgressionMultiplier = 5f; [Min(0f)] public float DevelopmentMidBossEventTime = 120f; [Min(0f)] public float DevelopmentFinalBossEventTime = 240f; [Min(0f)] public float FormalMidBossEventTime = 600f; [Min(0f)] public float FormalFinalBossEventTime = 1200f; [Min(0f)] public float ProductionMidBossEventTime = 600f; [Min(0f)] public float ProductionFinalBossEventTime = 900f; [Min(0)] public int DevelopmentStartupArtifactSelectionCount = 3; [Min(0)] public int ProductionStartupArtifactSelectionCount = 1; [Min(0)] public int LegacyStartupArtifactSelectionCount = 3; [Header("스폰 밀도 단계")] [Tooltip("순서대로 시작시점, 1분, 3분, 5분, 10분 이후의 군중형/일반형 목표 수")] public SpawnDensityPhase[] SpawnDensity = { new(60f, 3, 3), new(180f, 6, 4), new(300f, 12, 4), new(600f, 19, 5), new(float.PositiveInfinity, 27, 5), }; [Min(0)] public int TimedEventCrowdTarget = 5; [Min(0)] public int TimedEventNormalTarget = 3; [Min(0)] public int FinalBossMaximumCrowdTarget = 2; [Header("웨이브")] [Min(1)] public int MaximumAliveEnemies = 100; [Min(1)] public int MaximumBossEncounterEnemies = 8; [Min(0.1f)] public float WaveInterval = 5f; [Min(0.1f)] public float TimedEventWaveInterval = 12f; [Min(1)] public int MaximumWaveSize = 3; [Min(1)] public int MaximumTimedEventWaveSize = 1; [Min(0f)] public float InitialWaveDelay = 0.5f; [Header("실제 플레이 유입량·습격")] [Min(0)] public int ProductionWaveCrowdSize = 2; [Min(0)] public int ProductionWaveNormalSize = 1; [Min(0)] public int ProductionTimedEventCrowdSize = 1; [Min(0f)] public float ProductionSwarmInterval = 90f; [Min(0f)] public float ProductionSwarmBossExclusionWindow = 30f; [Min(0f)] public float ProductionSwarmEarlyEndTime = 300f; [Min(0f)] public float ProductionSwarmMidEndTime = 600f; [Min(0)] public int ProductionSwarmEarlyCount = 16; [Min(0)] public int ProductionSwarmMidCount = 20; [Min(0)] public int ProductionSwarmLateCount = 24; [Min(1)] public int ProductionSwarmBatchSize = 4; [Min(0.1f)] public float ProductionSwarmBatchInterval = 2f; [Header("스폰 위치")] [Min(0f)] public float SpawnEdgeInset = 0.25f; [Min(0f)] public float PlayerSafeSpawnDistance = 4f; [Min(0f)] public float SpawnCameraMargin = 0.5f; [Min(0f)] public float SummonSpawnRadius = 2.5f; [Min(0f)] public float SummonSpawnMinimumSeparation = 1.5f; [Min(0f)] public float SummonSpawnPlayerClearance = 1.25f; public Vector2 FallbackSpawnHalfExtents = new(14.5f, 7.8125f); [Tooltip("시간 중간보스의 후보 프리팹 종류")] public EnemyKind[] MidBossKinds = { EnemyKind.GreatswordSkeleton, EnemyKind.NecroGolem }; [Header("이벤트 적 기본 튜닝")] public RunEventEnemyTuning EliteTuning = RunEventEnemyTuning.Create( EnemyKind.Lancer, 3f, 30, 1.25f, Color.white, 1.05f, 1.1f, 0.85f, 1f, 0.7f, 1f, 1.1f, 1.15f, 1f, 2, 0.7f, 0.35f, 2, 0.65f, true); public RunEventEnemyTuning MidBossTuning = RunEventEnemyTuning.Create( EnemyKind.GreatswordSkeleton, 8f, 100, 1.6f, new Color(1f, 0.35f, 0.3f), 0.9f, 1.25f, 0.95f, 1f, 0.8f, 1.15f, 1f, 1f, 1.35f, 2, 0.7f, 0.4f, 3, 0.3f, false); public RunEventEnemyTuning FinalBossTuning = RunEventEnemyTuning.Create( EnemyKind.Warlock, 15f, 0, 2f, new Color(0.75f, 0.35f, 1f), 0.85f, 1.25f, 0.9f, 1f, 0.75f, 1f, 1f, 1f, 1.25f, 3, 0.75f, 0.3f, 4, 0.15f, false); [Min(1f)] public float ProductionMidBossHealthMultiplier = 7f; [Min(1f)] public float ProductionFinalBossHealthMultiplier = 12f; [Tooltip("순서대로 첫 번째, 두 번째, 세 번째, 이후 시간 엘리트 체력 배율")] public float[] ProductionEliteHealthMultipliers = { 2.5f, 3.5f, 3.5f, 5f }; [Tooltip("순서대로 첫 번째, 두 번째, 세 번째, 이후 시간 엘리트 공격 배율")] public float[] ProductionEliteAttackDamageMultipliers = { 1f, 1f, 1.1f, 1.2f }; [Tooltip("순서대로 첫 번째, 두 번째, 세 번째, 이후 시간 엘리트 연속 공격 수")] public int[] ProductionEliteAttacksPerSequence = { 1, 2, 3, 2 }; [Tooltip("엘리트 순환에 사용할 적 종류")] public EnemyKind[] EliteKinds = { EnemyKind.ArmoredSkeleton, EnemyKind.Werewolf, EnemyKind.Werebear, }; [Tooltip("개발 런에서 순서대로 적용할 엘리트 연속 공격 수")] public int[] DevelopmentEliteAttacksPerSequence = { 2, 2, 3 }; [Tooltip("생산 런에서 순서대로 적용할 엘리트 경고시간 배율")] public float[] ProductionEliteWarningDurationMultipliers = { 1f, 0.85f, 0.85f, 0.85f }; [Tooltip("생산 런에서 순서대로 적용할 엘리트 회복시간 배율")] public float[] ProductionEliteRecoveryDurationMultipliers = { 1f, 0.7f, 0.7f, 0.7f }; [Header("전장")] public Vector2 ArenaBackgroundHalfExtents = new(15f, 8.4375f); public Vector2 ArenaPlayableHalfExtents = new(15f, 8.4375f); public Vector2 ArenaPlayerPadding = new(0.5f, 0.625f); public Vector2 ArenaCameraViewHalfExtents = new(10f, 5.625f); public Vector2 ArenaCameraDeadZone = new(0.5f, 0.3f); [Min(0f)] public float ArenaCameraSmoothTime = 0.15f; public float[] EliteTimes(bool development, bool production) { float[] times = production ? ProductionEliteEventTimes : development ? DevelopmentEliteEventTimes : FormalEliteEventTimes; return times ?? Array.Empty(); } public SpawnDensityPhase GetSpawnDensity(float elapsedTime) { if (SpawnDensity == null || SpawnDensity.Length == 0) { return new SpawnDensityPhase(float.PositiveInfinity, 27, 5); } for (int i = 0; i < SpawnDensity.Length - 1; i++) { if (elapsedTime < SpawnDensity[i].EndTimeSeconds) { return SpawnDensity[i]; } } return SpawnDensity[SpawnDensity.Length - 1]; } } }