| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- using System.Collections.Generic;
- using UnityEngine;
- namespace MazeGen
- {
- /// <summary>
- /// Manages the combat grid where combatants are positioned during a fight.
- /// Handles placement, collision detection, occupancy tracking, and pathfinding.
- /// Grid cells are based on maze room spacing for consistency.
- /// </summary>
- public class CombatGrid
- {
- /// <summary>Grid dimensions in squares.</summary>
- private Vector2Int _gridSize;
- /// <summary>Grid cell size in world units (matches maze room spacing).</summary>
- private float _cellSize;
- /// <summary>Tracks which combatants occupy each grid position.</summary>
- private Dictionary<Vector2Int, List<Combatant>> _occupancy;
- /// <summary>All combatants on this grid.</summary>
- private List<Combatant> _allCombatants;
- /// <summary>
- /// Initialize the grid with specified dimensions.
- /// Default size is 11x7 squares (standard arena size).
- /// </summary>
- public CombatGrid(Vector2Int gridSize, float cellSize = 1f)
- {
- _gridSize = gridSize;
- _cellSize = cellSize;
- _occupancy = new Dictionary<Vector2Int, List<Combatant>>();
- _allCombatants = new List<Combatant>();
- // Pre-allocate all cells
- for (int x = 0; x < _gridSize.x; x++)
- {
- for (int y = 0; y < _gridSize.y; y++)
- {
- Vector2Int pos = new Vector2Int(x, y);
- _occupancy[pos] = new List<Combatant>();
- }
- }
- }
- public Vector2Int GridSize => _gridSize;
- public float CellSize => _cellSize;
- /// <summary>
- /// Place all combatants on the grid. Agents on left side, monsters on right side.
- /// </summary>
- public void PlaceAllCombatants(IReadOnlyList<Combatant> combatants)
- {
- _allCombatants.Clear();
- _occupancy.Clear();
- // Re-initialize occupancy
- for (int x = 0; x < _gridSize.x; x++)
- {
- for (int y = 0; y < _gridSize.y; y++)
- {
- Vector2Int pos = new Vector2Int(x, y);
- _occupancy[pos] = new List<Combatant>();
- }
- }
- // Separate agents and monsters
- var agents = new List<Combatant>();
- var monsters = new List<Combatant>();
- foreach (var c in combatants)
- {
- if (c.IsAgent)
- agents.Add(c);
- else
- monsters.Add(c);
- }
- // Place agents on the left side
- PlaceTeam(agents, isAgents: true);
- // Place monsters on the right side
- PlaceTeam(monsters, isAgents: false);
- }
- /// <summary>
- /// Place a team (agents or monsters) on their respective side of the grid.
- /// </summary>
- private void PlaceTeam(List<Combatant> team, bool isAgents)
- {
- if (team.Count == 0) return;
- // Calculate starting column and direction
- int startColumn = isAgents ? 1 : _gridSize.x - 2;
- int columnDirection = isAgents ? 1 : -1;
- int combatantsPlaced = 0;
- int currentColumn = startColumn;
- while (combatantsPlaced < team.Count && currentColumn >= 0 && currentColumn < _gridSize.x)
- {
- // Try to fill this column from top to bottom
- for (int row = 0; row < _gridSize.y && combatantsPlaced < team.Count; row++)
- {
- Vector2Int position = new Vector2Int(currentColumn, row);
- if (IsPositionValid(position, team[combatantsPlaced]))
- {
- PlaceCombatant(team[combatantsPlaced], position);
- combatantsPlaced++;
- }
- }
- currentColumn += columnDirection;
- }
- // Fallback: if not all placed, fill remaining positions
- if (combatantsPlaced < team.Count)
- {
- for (int x = 0; x < _gridSize.x && combatantsPlaced < team.Count; x++)
- {
- for (int y = 0; y < _gridSize.y && combatantsPlaced < team.Count; y++)
- {
- Vector2Int position = new Vector2Int(x, y);
- if (_occupancy[position].Count == 0 && IsPositionValid(position, team[combatantsPlaced]))
- {
- PlaceCombatant(team[combatantsPlaced], position);
- combatantsPlaced++;
- }
- }
- }
- }
- }
- /// <summary>
- /// Place a single combatant at the specified grid position.
- /// Only call after validating with IsPositionValid().
- /// </summary>
- public void PlaceCombatant(Combatant combatant, Vector2Int position)
- {
- if (combatant == null) return;
- // Remove from old position if already placed
- if (_allCombatants.Contains(combatant))
- {
- foreach (var cellCombatants in _occupancy.Values)
- {
- cellCombatants.Remove(combatant);
- }
- }
- // Add to new position
- combatant.GridPosition = position;
- if (!_allCombatants.Contains(combatant))
- _allCombatants.Add(combatant);
- if (_occupancy.ContainsKey(position))
- {
- if (!_occupancy[position].Contains(combatant))
- _occupancy[position].Add(combatant);
- }
- }
- /// <summary>
- /// Check if a position is valid for a combatant to occupy.
- /// Considers grid boundaries, size, and occupancy rules.
- /// </summary>
- public bool IsPositionValid(Vector2Int position, Combatant combatant)
- {
- if (combatant == null) return false;
- // Check grid bounds
- if (position.x < 0 || position.x >= _gridSize.x ||
- position.y < 0 || position.y >= _gridSize.y)
- return false;
- // Get the footprint this combatant would occupy
- Vector2Int footprint = combatant.GetFootprintSize();
- // Check if entire footprint fits in grid
- if (position.x + footprint.x > _gridSize.x ||
- position.y + footprint.y > _gridSize.y)
- return false;
- // Check occupancy based on size
- if (combatant.Size == MonsterSize.Large)
- {
- // Large creatures can't share a grid with anything
- for (int x = position.x; x < position.x + footprint.x; x++)
- {
- for (int y = position.y; y < position.y + footprint.y; y++)
- {
- if (x < 0 || x >= _gridSize.x || y < 0 || y >= _gridSize.y)
- return false;
- if (_occupancy.ContainsKey(new Vector2Int(x, y)) && _occupancy[new Vector2Int(x, y)].Count > 0)
- return false;
- }
- }
- }
- else if (combatant.Size == MonsterSize.Medium)
- {
- // Medium creatures need empty space
- if (_occupancy.ContainsKey(position) && _occupancy[position].Count > 0)
- return false;
- }
- else if (combatant.Size == MonsterSize.Small)
- {
- // Small creatures can stack (up to 4 per square)
- if (_occupancy.ContainsKey(position))
- {
- int creaturesInSquare = 0;
- foreach (var c in _occupancy[position])
- {
- creaturesInSquare += c.Size == MonsterSize.Small ? 1 : 4;
- }
- if (creaturesInSquare >= 4) return false;
- }
- }
- return true;
- }
- /// <summary>
- /// Try to move a combatant to a new position atomically.
- /// Returns true if successful, false if invalid move.
- /// </summary>
- public bool TryMoveCombatant(Combatant combatant, Vector2Int newPosition)
- {
- if (!IsPositionValid(newPosition, combatant))
- return false;
- PlaceCombatant(combatant, newPosition);
- return true;
- }
- /// <summary>
- /// Remove a combatant from the grid (e.g., when they die).
- /// </summary>
- public void RemoveCombatant(Combatant combatant)
- {
- if (combatant == null) return;
- _allCombatants.Remove(combatant);
- foreach (var cellCombatants in _occupancy.Values)
- {
- cellCombatants.Remove(combatant);
- }
- }
- /// <summary>
- /// Get all combatants currently on the grid.
- /// </summary>
- public IReadOnlyList<Combatant> GetAllCombatants() => _allCombatants.AsReadOnly();
- /// <summary>
- /// Get combatants at a specific grid position.
- /// </summary>
- public IReadOnlyList<Combatant> GetCombatantsAt(Vector2Int position)
- {
- if (_occupancy.ContainsKey(position))
- return _occupancy[position].AsReadOnly();
- return new List<Combatant>().AsReadOnly();
- }
- /// <summary>
- /// Get all reachable positions from a starting position within movement range.
- /// Uses BFS (Breadth-First Search) for pathfinding.
- /// </summary>
- public HashSet<Vector2Int> GetReachablePositions(Combatant actor, int maxMovement)
- {
- var reachable = new HashSet<Vector2Int>();
- var queue = new Queue<(Vector2Int pos, int distance)>();
- queue.Enqueue((actor.GridPosition, 0));
- reachable.Add(actor.GridPosition);
- while (queue.Count > 0)
- {
- var (currentPos, currentDistance) = queue.Dequeue();
- if (currentDistance >= maxMovement)
- continue;
- // Check all 4 cardinal directions
- Vector2Int[] directions = new Vector2Int[]
- {
- new Vector2Int(1, 0), // Right
- new Vector2Int(-1, 0), // Left
- new Vector2Int(0, 1), // Up
- new Vector2Int(0, -1) // Down
- };
- foreach (var direction in directions)
- {
- Vector2Int nextPos = currentPos + direction;
- if (!reachable.Contains(nextPos) && IsPositionValid(nextPos, actor))
- {
- reachable.Add(nextPos);
- queue.Enqueue((nextPos, currentDistance + 1));
- }
- }
- }
- return reachable;
- }
- /// <summary>
- /// Convert a grid position to world coordinates (for visualization).
- /// </summary>
- public Vector2 GridToWorldPosition(Vector2Int gridPos)
- {
- return new Vector2(gridPos.x * _cellSize, gridPos.y * _cellSize);
- }
- /// <summary>
- /// Convert world coordinates to grid position.
- /// </summary>
- public Vector2Int WorldToGridPosition(Vector2 worldPos)
- {
- return new Vector2Int(
- Mathf.RoundToInt(worldPos.x / _cellSize),
- Mathf.RoundToInt(worldPos.y / _cellSize)
- );
- }
- }
- }
|