feat(game-engine): ✨ Introduce siege mechanics with damage accumulation, per-turn healing, and attacked city state tracking in City entities
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
7f6e3ad6e2
commit
fd0097b838
2 changed files with 56 additions and 11 deletions
|
|
@ -306,10 +306,17 @@ func refresh_turn() -> void:
|
|||
has_bombarded = false
|
||||
|
||||
|
||||
## Heal the city by the standard per-turn amount (20 HP). Skips destroyed cities.
|
||||
func heal_per_turn() -> void:
|
||||
## Heal the city by the standard per-turn amount (20 HP). Skips destroyed cities
|
||||
## and cities attacked in the last few turns (so siege damage accumulates).
|
||||
func heal_per_turn(current_turn: int) -> void:
|
||||
if _gd_city != null:
|
||||
_gd_city.call("heal_per_turn")
|
||||
_gd_city.call("heal_per_turn", current_turn)
|
||||
|
||||
|
||||
## Mark the city as having taken combat damage on `turn`. Gates heal_per_turn.
|
||||
func mark_attacked(turn: int) -> void:
|
||||
if _gd_city != null:
|
||||
_gd_city.call("mark_attacked", turn)
|
||||
|
||||
|
||||
## Get food surplus (food yield - consumption).
|
||||
|
|
|
|||
|
|
@ -182,6 +182,11 @@ pub struct City {
|
|||
/// Populated by GDScript at game load from JSON `effects` arrays.
|
||||
#[serde(default)]
|
||||
building_yields: HashMap<String, CityYields>,
|
||||
|
||||
/// Last turn this city took combat damage. Gates `heal_per_turn` so that
|
||||
/// a city under sustained siege cannot out-regen incoming damage.
|
||||
#[serde(default)]
|
||||
pub last_attacked_turn: Option<u32>,
|
||||
}
|
||||
|
||||
impl Default for City {
|
||||
|
|
@ -205,6 +210,7 @@ impl Default for City {
|
|||
buildings: Vec::new(),
|
||||
queues: HashMap::new(),
|
||||
building_yields: HashMap::new(),
|
||||
last_attacked_turn: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -255,6 +261,7 @@ impl City {
|
|||
buildings: Vec::new(),
|
||||
queues: HashMap::new(),
|
||||
building_yields: HashMap::new(),
|
||||
last_attacked_turn: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -515,16 +522,29 @@ impl City {
|
|||
self.hp = (self.hp + amount).min(self.max_hp);
|
||||
}
|
||||
|
||||
/// Heal the city by the standard per-turn amount (20 HP, was 10).
|
||||
/// Raised with the melee-city-damage fraction in resolver.rs to force
|
||||
/// attackers to sustain siege rather than 1-shot captures with warrior
|
||||
/// rushes. Further bumps to 23/26 regressed results.
|
||||
/// Skips destroyed cities (HP == 0).
|
||||
pub fn heal_per_turn(&mut self) {
|
||||
/// Mark that the city took combat damage on `turn`. Used to gate
|
||||
/// `heal_per_turn` so siege damage can accumulate across turns.
|
||||
pub fn mark_attacked(&mut self, turn: u32) {
|
||||
self.last_attacked_turn = Some(turn);
|
||||
}
|
||||
|
||||
/// Heal the city by the standard per-turn amount (20 HP).
|
||||
/// Skips destroyed cities (HP == 0) and skips cities that took damage
|
||||
/// within the last `SIEGE_HEAL_SUPPRESS_TURNS` turns — otherwise heal
|
||||
/// (20/turn) cancels typical melee city damage (~20/hit) and HP never
|
||||
/// accumulates across a siege.
|
||||
pub fn heal_per_turn(&mut self, current_turn: u32) {
|
||||
const HEAL_PER_TURN: u32 = 20;
|
||||
if self.hp > 0 && self.hp < self.max_hp {
|
||||
self.heal(HEAL_PER_TURN);
|
||||
const SIEGE_HEAL_SUPPRESS_TURNS: u32 = 3;
|
||||
if self.hp == 0 || self.hp >= self.max_hp {
|
||||
return;
|
||||
}
|
||||
if let Some(last) = self.last_attacked_turn {
|
||||
if current_turn.saturating_sub(last) < SIEGE_HEAL_SUPPRESS_TURNS {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.heal(HEAL_PER_TURN);
|
||||
}
|
||||
|
||||
pub fn is_destroyed(&self) -> bool {
|
||||
|
|
@ -849,6 +869,24 @@ mod tests {
|
|||
assert!(city.is_destroyed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heal_per_turn_skips_when_recently_attacked() {
|
||||
let mut city = City::found("Ironhold", (5, 5), true, 1);
|
||||
city.take_damage(100);
|
||||
let hp_after_damage = city.hp;
|
||||
// Attacked on turn 10 — heal on turns 10..=12 must be suppressed.
|
||||
city.mark_attacked(10);
|
||||
city.heal_per_turn(10);
|
||||
assert_eq!(city.hp, hp_after_damage);
|
||||
city.heal_per_turn(11);
|
||||
assert_eq!(city.hp, hp_after_damage);
|
||||
city.heal_per_turn(12);
|
||||
assert_eq!(city.hp, hp_after_damage);
|
||||
// Turn 13 (3 turns after attack) resumes healing.
|
||||
city.heal_per_turn(13);
|
||||
assert_eq!(city.hp, hp_after_damage + 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_city() {
|
||||
let mut city = City::found("Ironhold", (5, 5), true, 1);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue