using UnityEngine; using TD.Core; #if UNITY_EDITOR using UnityEditor; #endif namespace TD.Levels { /// /// Authoring volume marking the boundary where enemies leak from one player's zone into /// another's. Leak exit tiles are in the baked grid /// but remain walkable. /// /// /// Leak topology is recorded as metadata in the baked for lobby UI /// and future wave-balancing systems, but the runtime gameplay loop does not depend on it — /// enemies pathfind on the unified walkability grid and naturally cross zone boundaries. /// /// Final defenders (the player whose zone is goal-adjacent) do NOT have a LeakExitVolume. /// They are identified by zone-to-goal adjacency at bake time. /// /// Multiple leak exits with the same source zone may exist (e.g., Player 5 in the 9-player /// map splits leaks 50/50 between Player 4 and Player 6). The field /// controls the split. The bake normalizes weights to sum to 1.0 across a source zone's /// leak exits. /// public class LeakExitVolume : VolumeBase { [Tooltip("Which player's zone this leak exits FROM.")] public PlayerSlot sourceZone = PlayerSlot.Player1; [Tooltip("Which player's zone this leak feeds INTO.")] public PlayerSlot target = PlayerSlot.Player2; [Tooltip("Relative weight for split exits. Bake normalizes weights to sum to 1.0 across all " + "leak exits sharing the same source zone. Set both leaks' weights to 1.0 for a 50/50 split.")] public float weight = 1.0f; [Tooltip("Whether tiles in this volume are buildable. Defaults to Invalid.")] public PlacementValidity placementValidity = PlacementValidity.Invalid; // Leak exits draw above player zones but below spawners. private const float FillYLevel = 0.04f; private const float ArrowYLevel = 0.05f; private const float ArrowLength = 1.5f; protected override bool GetAlwaysShowToggle(LevelAuthoring authoring) { return authoring.alwaysShowLeakExits; } private void OnDrawGizmosSelected() { DrawGizmosCore(); } private void OnDrawGizmos() { if (ShouldDrawAlwaysOn()) { DrawGizmosCore(); } } private void DrawGizmosCore() { // Leak exit fill uses the SOURCE zone's color (visually attaches the leak to the zone // it leaves). The target is conveyed by the arrow direction and label. Color baseColor = PlayerColors.Get(sourceZone); DrawTileCoverageFill(baseColor, alpha: 0.40f, yLevel: FillYLevel); DrawRectangularOutline(baseColor, yLevel: FillYLevel); // Arrow points from this leak exit's center toward the target zone's center. var col = Collider; if (col != null) { Vector3 origin = new Vector3(col.bounds.center.x, ArrowYLevel, col.bounds.center.z); Vector3 targetCenter = FindTargetZoneCenter(); if (targetCenter.sqrMagnitude > 0f) // sentinel: zero means "not found" { Vector3 directionXZ = new Vector3(targetCenter.x - origin.x, 0f, targetCenter.z - origin.z); DrawArrow(origin, directionXZ, ArrowLength, baseColor); } } #if UNITY_EDITOR if (col != null) { Vector3 labelPos = new Vector3(col.bounds.center.x, ArrowYLevel + 0.1f, col.bounds.center.z); Handles.Label(labelPos, $"→ {FormatPlayerName(target)}"); } #endif } /// /// Computes the world-space center of the target zone by averaging the bounds-centers /// of all PlayerZoneVolumes whose matches /// . Returns Vector3.zero if no matching zone is found — /// the caller treats zero as a "not found" sentinel. /// /// /// Performs a scene-wide query each gizmo draw. This is fine for our worst case /// (9-player map: 8 leak exits × ~10 zone volumes = ~80 considerations per frame, only /// when gizmos are drawing). If this ever shows up as an editor-perf issue, switch to a /// cached lookup invalidated on hierarchy change. /// private Vector3 FindTargetZoneCenter() { var zones = Object.FindObjectsByType(FindObjectsInactive.Exclude); if (zones == null || zones.Length == 0) return Vector3.zero; Vector3 accumulated = Vector3.zero; int count = 0; for (int i = 0; i < zones.Length; i++) { if (zones[i] == null) continue; if (zones[i].owner != target) continue; var c = zones[i].GetComponent(); if (c == null) continue; accumulated += c.bounds.center; count++; } if (count == 0) return Vector3.zero; return accumulated / count; } } }