Implement tick-based game loop, combat overhaul, and attack-any-NPC

Replace immediate combat with a 3-second tick engine that resolves
actions, NPC AI, status effects, respawns, and passive regeneration.
Players queue combat actions (attack/defend/flee/use) that resolve on
the next tick. Any NPC can now be attacked — non-hostile targets incur
attitude penalties instead of being blocked. Status effects persist in
the database and continue ticking while players are offline.

Made-with: Cursor
This commit is contained in:
AI Agent
2026-03-14 15:12:44 -06:00
parent a083c38326
commit 5fd2c10198
9 changed files with 890 additions and 181 deletions

View File

@@ -49,7 +49,7 @@ impl Attitude {
matches!(self, Attitude::Hostile)
}
pub fn can_be_attacked(self) -> bool {
pub fn is_hostile(self) -> bool {
matches!(self, Attitude::Hostile | Attitude::Aggressive)
}
@@ -315,7 +315,8 @@ impl World {
load_entities_from_dir(&region_path.join("npcs"), &region_name, &mut |id, content| {
let nf: NpcFile = toml::from_str(content).map_err(|e| format!("Bad npc {id}: {e}"))?;
let combat = nf.combat.map(|c| NpcCombatStats { max_hp: c.max_hp, attack: c.attack, defense: c.defense, xp_reward: c.xp_reward });
let combat = Some(nf.combat.map(|c| NpcCombatStats { max_hp: c.max_hp, attack: c.attack, defense: c.defense, xp_reward: c.xp_reward })
.unwrap_or(NpcCombatStats { max_hp: 20, attack: 4, defense: 2, xp_reward: 5 }));
let greeting = nf.dialogue.and_then(|d| d.greeting);
npcs.insert(id.clone(), Npc { id: id.clone(), name: nf.name, description: nf.description, room: nf.room, base_attitude: nf.base_attitude, faction: nf.faction, respawn_secs: nf.respawn_secs, greeting, combat });
Ok(())