| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- using UnityEngine;
- namespace MazeGen
- {
- /// <summary>
- /// One participant in a battle - either an agent or a monster instance - behind a single
- /// interface so <see cref="CombatResolver"/> doesn't need to branch on which it is.
- ///
- /// Wraps the *live* <see cref="CharacterSheet"/>, not a copy: resolving a fight mutates real
- /// agent and monster health, which is exactly what "only the result of the fight has changed
- /// for that room" means. <see cref="StartHealth"/> is snapshotted before resolution purely so
- /// the arena playback can animate health draining down from where it actually started.
- /// </summary>
- public class Combatant
- {
- public const int TeamAgents = 0;
- public const int TeamMonsters = 1;
- /// <summary>Index of this combatant within its own battle. Combat events refer to these.</summary>
- public readonly int Id;
- public readonly int Team;
- public readonly string Name;
- public readonly Color Color;
- /// <summary>Set for agent combatants, null for monsters.</summary>
- public readonly MazeAgent Agent;
- /// <summary>Set for monster combatants, null for agents.</summary>
- public readonly MonsterEncounter.MonsterInstance Monster;
- public readonly CharacterSheet Sheet;
- /// <summary>Health at the moment the battle was resolved, for playback purposes.</summary>
- public readonly float StartHealth;
- /// <summary>Grid position during combat. Set by CombatGrid during Initialize.</summary>
- private Vector2Int _gridPosition = Vector2Int.zero;
- /// <summary>Size category for grid footprint (determines how many squares this combatant occupies).</summary>
- private MonsterSize _size = MonsterSize.Medium;
- public bool IsAgent => Agent != null;
- public bool IsAlive => Sheet.Stats.IsAlive;
- /// <summary>
- /// Base max health, not GetEffectiveMaxHealth(): CharacterStats.CurrentHealth is clamped
- /// to the base value, so using the effective one here would give health bars that can
- /// never fill up whenever equipment grants a health bonus.
- /// </summary>
- public float MaxHealth => Mathf.Max(1f, Sheet.Stats.MaxHealth);
- /// <summary>Current position on the combat grid.</summary>
- public Vector2Int GridPosition
- {
- get => _gridPosition;
- set => _gridPosition = value;
- }
- /// <summary>Size category of this combatant (affects grid footprint).</summary>
- public MonsterSize Size
- {
- get => _size;
- set => _size = value;
- }
- /// <summary>
- /// Grid position at the moment combatants were first placed on the grid, before any
- /// movement happened. CombatResolver.Resolve() runs the whole fight synchronously and
- /// mutates GridPosition throughout, so by the time playback starts, GridPosition already
- /// holds the *final* position - this snapshot is what BattleArenaView uses to place the
- /// combatant's sprite where it actually started, exactly like StartHealth does for health.
- /// </summary>
- public Vector2Int StartGridPosition { get; set; } = Vector2Int.zero;
- private Combatant(int id, int team, string name, Color color, MazeAgent agent, MonsterEncounter.MonsterInstance monster, CharacterSheet sheet)
- {
- Id = id;
- Team = team;
- Name = name;
- Color = color;
- Agent = agent;
- Monster = monster;
- Sheet = sheet;
- StartHealth = sheet.Stats.CurrentHealth;
- }
- public static Combatant ForAgent(int id, MazeAgent agent, Color color)
- => new Combatant(id, TeamAgents, agent.CharacterSheet.Name, color, agent, null, agent.CharacterSheet);
- public static Combatant ForMonster(int id, MonsterEncounter.MonsterInstance monster)
- {
- var combatant = new Combatant(id, TeamMonsters, monster.Template.MonsterName, monster.Template.Color, null, monster, monster.CharacterSheet);
- // Set size from monster template
- combatant.Size = monster.Template.Size;
- return combatant;
- }
- /// <summary>
- /// Calculate grid distance to another combatant using Manhattan distance.
- /// This represents how many squares apart they are.
- /// </summary>
- public int DistanceTo(Combatant other)
- {
- return Mathf.Abs(GridPosition.x - other.GridPosition.x) +
- Mathf.Abs(GridPosition.y - other.GridPosition.y);
- }
- /// <summary>
- /// Get the footprint size (in grid squares) for this combatant based on its Size category.
- /// </summary>
- public Vector2Int GetFootprintSize()
- {
- return Size switch
- {
- MonsterSize.Small => Vector2Int.one, // Small: 1x1, but multiple fit per square
- MonsterSize.Medium => Vector2Int.one, // Medium: 1x1
- MonsterSize.Large => new Vector2Int(4, 4), // Large: 4x4
- _ => Vector2Int.one
- };
- }
- /// <summary>
- /// Get the maximum number of this-sized creatures that can occupy a single grid square.
- /// </summary>
- public int GetCreaturesPerSquare()
- {
- return Size switch
- {
- MonsterSize.Small => 4, // Small: 4 per square
- MonsterSize.Medium => 1, // Medium: 1 per square
- MonsterSize.Large => 0, // Large: occupies multiple squares, not stackable
- _ => 1
- };
- }
- /// <summary>
- /// Check if this combatant is in melee range (adjacent) to a target.
- /// </summary>
- public bool IsAdjacentTo(Combatant target)
- {
- int distance = DistanceTo(target);
- return distance == 1;
- }
- /// <summary>
- /// Check if this combatant can reach a target with the given weapon.
- /// Returns true if target is within the weapon's range.
- /// </summary>
- public bool CanReachTarget(Combatant target, Weapon weapon)
- {
- if (weapon == null) return false;
- int distance = DistanceTo(target);
- // For melee weapons, must be adjacent
- if (weapon.WeaponType == WeaponType.Melee)
- {
- return IsAdjacentTo(target);
- }
- // For ranged/magic weapons, check against range attribute
- return distance <= weapon.Range;
- }
- }
- }
|