using System.Collections.Generic; using BumpCombat.Defense; using BumpCombat.Enemies; using UnityEngine; namespace BumpCombat.Defense { internal static class DefenseTowerArtDurations { private static readonly Dictionary Values = new() { ["Bodies/Fire/T1-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/T2-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/T3-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/Base-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/T4-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/T1-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/T2-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/T3-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/Base-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/T4-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/T1-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/T2-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/T3-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/Base-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Fire/T4-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/T1-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/T2-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/T3-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/Base-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/T4-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/T1-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/T2-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/T3-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/Base-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/T4-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/T1-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/T2-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/T3-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/Base-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Lightning/T4-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/T1-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/T2-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/T3-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/Base-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/T4-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/T1-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/T2-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/T3-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/Base-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/T4-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/T1-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/T2-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/T3-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/Base-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Ice/T4-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/T1-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/T2-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/T3-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/Base-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/T4-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/T1-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/T2-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/T3-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/Base-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/T4-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/T1-Heal"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/T2-Heal"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/T3-Heal"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/Base-Heal"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Control/T4-Heal"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/T1-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/T2-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/T3-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/Base-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/T4-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/T1-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/T2-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/T3-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/Base-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/T4-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/T1-Heal"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/T2-Heal"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/T3-Heal"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/Base-Heal"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Support/T4-Heal"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Summon/T1-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Summon/T2-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Summon/T3-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Summon/Base-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Bodies/Summon/T4-Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Effects/Fire/T1-Fireball"] = new[] { 90, 90, 90, 90 }, ["Effects/Fire/T2-Fireball"] = new[] { 90, 90, 90, 90 }, ["Effects/Fire/T3-Fireball"] = new[] { 90, 90, 90, 90 }, ["Effects/Fire/Impact"] = new[] { 45, 55, 60, 50 }, ["Effects/Fire/Meteor-Flight"] = new[] { 80, 80, 80, 80 }, ["Effects/Fire/Meteor-Impact"] = new[] { 40, 50, 60, 70, 60, 60, 60, 80 }, ["Sources/BlackKnight-NoSkull"] = new[] { 125, 125, 125, 125, 125, 125, 125 }, ["Effects/Fire/Flamethrower"] = new[] { 80, 80, 80, 70, 65, 65, 70 }, ["Effects/Fire/Eyeball Monster_Beam-Clean"] = new[] { 100, 100, 120 }, ["Effects/Lightning/T1-Bolt"] = new[] { 30, 40, 30, 30, 40, 40, 40, 50 }, ["Effects/Lightning/T2-Bolt"] = new[] { 30, 40, 30, 30, 40, 40, 40, 50 }, ["Effects/Lightning/T3-Bolt"] = new[] { 30, 40, 30, 30, 40, 40, 40, 50 }, ["Effects/Lightning/Arc"] = new[] { 30, 40, 30, 30, 40, 40, 40, 50 }, ["Effects/Lightning/ImpactWave"] = new[] { 40, 40, 50, 50, 60, 60 }, ["Effects/Ice/Blizzard-Wave-v1"] = new[] { 40, 40, 50, 50, 60, 60 }, ["Effects/Lightning/LightningStorm"] = new[] { 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 }, ["Effects/Ice/T1-Volley"] = new[] { 125 }, ["Effects/Ice/T2-Volley"] = new[] { 125 }, ["Effects/Ice/T3-Volley"] = new[] { 125 }, ["Effects/Ice/Shard"] = new[] { 125 }, ["Effects/Ice/Freezebolt"] = new[] { 85, 85, 85, 85 }, ["Effects/Ice/Impact"] = new[] { 40, 50, 60, 70 }, ["Effects/Ice/GlacialCage-Front"] = new[] { 65, 65, 90, 140, 140, 140, 70, 70 }, ["Effects/Ice/GlacialCage-Back"] = new[] { 65, 65, 90, 140, 140, 140, 70, 70 }, ["Effects/Control/T1-Aura"] = new[] { 140, 140, 140, 140, 140, 140 }, ["Effects/Control/T2-Aura"] = new[] { 140, 140, 140, 140, 140, 140 }, ["Effects/Control/T3-Aura"] = new[] { 140, 140, 140, 140, 140, 140 }, ["Effects/Support/T1-Aura"] = new[] { 140, 140, 140, 140, 140, 140 }, ["Effects/Support/T2-Aura"] = new[] { 140, 140, 140, 140, 140, 140 }, ["Effects/Support/T3-Aura"] = new[] { 140, 140, 140, 140, 140, 140 }, ["SupportRange/T1-Aura"] = new[] { 140, 140, 140, 140, 140, 140 }, ["Effects/Control/StoneGaze"] = new[] { 90, 90, 90, 90, 90, 90, 90, 90 }, ["Effects/Control/Temporal"] = new[] { 170, 170, 170, 170, 170, 170, 170, 170 }, ["Effects/Support/Imbuing"] = new[] { 130, 130, 130, 130, 130, 130, 130, 130 }, ["Effects/Support/Smothering"] = new[] { 150, 150, 150, 150, 150, 150, 150, 150 }, ["Summons/Slime/Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Summons/GiantSlime/Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Summons/Slime/Walk"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Summons/GiantSlime/Walk"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Summons/Slime/Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Summons/GiantSlime/Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Summons/Slime/Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Summons/GiantSlime/Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Summons/Slime/Hurt"] = new[] { 125, 125, 125, 125 }, ["Summons/GiantSlime/Hurt"] = new[] { 125, 125, 125, 125 }, ["Summons/Slime/Death"] = new[] { 125, 125, 125, 125 }, ["Summons/GiantSlime/Death"] = new[] { 125, 125, 125, 125 }, ["Summons/LavaSlime/Idle"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Summons/LavaSlime/Walk"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Summons/LavaSlime/Attack01"] = new[] { 125, 125, 125, 125, 125, 125 }, ["Summons/LavaSlime/Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 }, ["Summons/LavaSlime/Hurt"] = new[] { 125, 125, 125, 125 }, ["Summons/LavaSlime/Death"] = new[] { 125, 125, 125, 125 }, ["Summons/LavaSlime/GroundSpike-Segment"] = new[] { 60, 60, 60, 60, 60, 60, 60, 60, 60, 60 }, ["Summons/LavaSlime/GroundSpike-Wave"] = new[] { 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60 }, }; public static ICollection Keys => Values.Keys; public static int FrameCount(string key) { return Values.TryGetValue(key, out int[] values) ? values.Length : 0; } public static float Seconds(string key, int frame) { if (!Values.TryGetValue(key, out int[] values) || values.Length == 0) { return 0.125f; } int safeFrame = frame < 0 ? 0 : frame >= values.Length ? values.Length - 1 : frame; return values[safeFrame] / 1000f; } } } namespace BumpCombat.Constants { /// Offline pixel measurements for non-readable defense art and original shadows. public static class DefenseTowerArtBounds { public const float TowerBillboardScale = 0.72f * 2.1f; public static readonly Vector2 LightningStormGroundCenterPixels = new(61f, 34f); public const float LightningStormGroundRadiusPixels = 55.362442f; public const float SeismicGroundMarkerWidthPixels = 18f; public const int FireBeamStartX = 1; public const int FireBeamEndX = 99; public const float FireBeamCenterY = 49.5f; public const float FireBeamIgnitionSeconds = 0.1f; public const float FireBeamExtinguishSeconds = 0.12f; public const float FireT4BeamBaseWidthMultiplier = 1f; public static Vector2 StaffTipPixels(bool attack, int frame) { frame = Mathf.Clamp(frame, 0, 5); if (attack) { return frame switch { 0 => new Vector2(56.5f, 44.5f), 1 => new Vector2(57.5f, 43.5f), _ => new Vector2(58.5f, 42.5f), }; } return frame < 2 ? new Vector2(56.5f, 44.5f) : new Vector2(56.5f, 43.5f); } public const int TowerHitSortingOffset = 102; public const int BlizzardShardCount = 8; public const float BlizzardLaunchIntervalSeconds = 0.04f; public const float BlizzardLandingSeconds = 0.24f; public const float BlizzardLastLandingSeconds = BlizzardLandingSeconds + (BlizzardShardCount - 1) * BlizzardLaunchIntervalSeconds; public const float BlizzardLandingRadiusFraction = 0.65f; public const float BlizzardRangeAlpha = 0.15f; public const float BlizzardImpactRadiusAlpha = 0.18f; public const float BlizzardShardScale = 0.52f; public const float BlizzardTrailScale = 0.3f; public const float BlizzardTrailAlpha = 0.32f; public const float BlizzardTrailOffset = 0.12f; public const float BlizzardImpactScale = 0.16f; public const float BlizzardWaveScale = 0.7f; // Maximum visible half-extents measured from Blizzard-Wave-v1's alpha frames. public const float BlizzardWaveHalfWidthPixels = 22.5f; public const float BlizzardWaveHalfHeightPixels = 11f; public const float BlizzardWavePixelsPerUnit = 32f; public const float BlizzardWaveRadiusX = BlizzardWaveHalfWidthPixels / BlizzardWavePixelsPerUnit * BlizzardWaveScale; public const float BlizzardWaveRadiusY = BlizzardWaveHalfHeightPixels / BlizzardWavePixelsPerUnit * BlizzardWaveScale; public const float BlizzardDropHeight = 1.2f; public const float BlizzardDropHeightStep = 0.22f; private static readonly Vector2Int[] LightningBeamBounds = { new(8, 56), new(8, 89), new(8, 89), new(8, 89), new(8, 89), new(8, 89), new(24, 89), new(24, 89), }; private static readonly Vector2[] ImpactWaveCenters = { new(50f, 39.5f), new(50f, 40f), new(50f, 40.5f), new(49.5f, 40f), new(50.5f, 40f), }; public static Vector2Int LightningBeamXBounds(int frame) => LightningBeamBounds[Mathf.Clamp(frame, 0, LightningBeamBounds.Length - 1)]; public static Vector2 BlizzardLandingOffset(int index, float radius) { float fraction = BlizzardLandingRadiusFraction * Mathf.Sqrt((index + 0.5f) / BlizzardShardCount); float angle = index * 2.39996323f; return radius * fraction * new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)); } public static Vector2 ImpactWaveCenterPixels(int frame, Sprite sprite) { return frame >= ImpactWaveCenters.Length ? sprite.pivot : ImpactWaveCenters[Mathf.Clamp(frame, 0, ImpactWaveCenters.Length - 1)]; } public static Vector2 CharacterShadowOffset(DefenseTowerKind kind, bool flipX = false) { Vector2 shadowPixelCenter = kind switch { DefenseTowerKind.Summon => new Vector2(50.5f, 42.5f), DefenseTowerKind.Seismic or DefenseTowerKind.Empowering => new Vector2(50.5f, 42f), _ => new Vector2(50.5f, 42.5f), }; Vector2 localPixels = shadowPixelCenter - new Vector2(50f, 50f); if (flipX) localPixels.x = -localPixels.x; return Vector2.Scale(localPixels / 32f, new Vector2(2.1f, 2.1f)); } public static Vector2 EnemyShadowOffsetWorld(EnemyController enemy) { if (enemy == null) return Vector2.zero; SpriteRenderer renderer = enemy.GetComponent(); Sprite sprite = renderer != null ? renderer.sprite : null; if (sprite == null || enemy.Definition == null) return Vector2.zero; Vector2 pixelCenter = EnemyShadowCenterPixels(enemy.Definition.Kind); Vector2 localOffset = (pixelCenter - sprite.pivot) / sprite.pixelsPerUnit; if (renderer.flipX) localOffset.x = -localOffset.x; return renderer.transform.TransformVector(localOffset); } public static Vector2 EnemyShadowWorldPosition(EnemyController enemy, Vector2 fallback) => enemy != null ? (Vector2)enemy.transform.position + EnemyShadowOffsetWorld(enemy) : fallback; private static Vector2 EnemyShadowCenterPixels(EnemyKind kind) => kind switch { EnemyKind.Slime => new Vector2(48.5f, 42f), EnemyKind.Lancer => new Vector2(51f, 42.5f), EnemyKind.Necrofire => new Vector2(52.5f, 42.5f), EnemyKind.NecroGolem => new Vector2(51.5f, 42.5f), EnemyKind.Werebear => new Vector2(51.5f, 42.5f), EnemyKind.Werewolf => new Vector2(50.5f, 42f), _ => new Vector2(50.5f, 42.5f), }; } }