using System; using BumpCombat.Core; using BumpCombat.Enemies; using UnityEngine; namespace BumpCombat.Constants { public enum RoomScaleComparisonMode { Disabled = 0, Original = 1, Expanded = 2, } [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); [Tooltip("스테이지 방에서 사용할 플레이어 추적 시야의 반쪽 크기")] public Vector2 StageCameraViewHalfExtents = new(7.5f, 4.21875f); [Tooltip("대표 Stage 1 비교 방에서 Original 선택 시 사용할 좁은 추적 시야의 반쪽 크기")] public Vector2 StageComparisonOriginalCameraViewHalfExtents = new(5f, 2.8125f); [Tooltip("대표 Stage 1 비교 방에서 Expanded 선택 시 사용할 추적 시야의 반쪽 크기")] public Vector2 StageComparisonCameraViewHalfExtents = new(7.5f, 4.21875f); public Vector2 ArenaCameraDeadZone = new(0.5f, 0.3f); [Min(0f)] public float ArenaCameraSmoothTime = 0.15f; [Header("대표 Stage 1 방 크기 비교")] [Tooltip("Disabled는 기존 카탈로그 선택을 유지하고, Original/Expanded는 입구 직결 첫 전투 방을 S1-A로 고정합니다.")] public RoomScaleComparisonMode Stage1FirstCombatRoomScaleComparison = RoomScaleComparisonMode.Disabled; [Header("제한 시야 교전")] [Min(0.1f)] public float StagePlayerVisibilityRadius = 6f; [Range(0f, 1f)] public float StageExploredFogOpacity = 0.65f; [Range(0f, 1f)] public float StageUnseenFogOpacity = 1f; [Range(0.1f, 1f)] public float StageEncounterSightRangeMultiplier = 0.5f; 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]; } } }