feat: m3 kampf, events und trigger

Der Dungeon hat jetzt Gegner, und man kann sterben.

- game/action.rs: sieben Schadenstypen, fuenf der zehn Aktionen, Target
- game/condition.rs: Bedingungsgrammatik (Liste = UND, not je Eintrag)
- game/event.rs: zwoelf Events, Trigger als vierte Wirkungsform
- game/combat.rs: Treffer- und Schadensformel mit drei Klemmbereichen
- Game::apply, drain_events (FIFO), Initiative, Rundenablauf aus Ticket 14
- data/: abilities-, creatures- und modifiers-Registry, [balance] feldweise
- ui/: Combat-Screen mit Faehigkeitsmenue und Sperrgrund

Drei Befunde:
- Die Trefferformel war toter Code — jeder Angriff traf. Nur der Compiler
  hat es gemeldet ('hit_chance is never used'). Test haelt sie am Leben.
- Die Warteschlange war ein Stack (pop statt pop_front), obwohl Ticket 08
  ausdruecklich 'haengt es hinten an' sagt. Jetzt VecDeque.
- Benannte Modifikatoren (core:poisoned) hatten keine Registry — Ticket 01
  laesst apply_modifier per ID referenzieren, aber keine der zwoelf
  definierte sie. Das ist die dreizehnte.

43 Tests.
This commit is contained in:
2026-08-12 17:03:00 +02:00
parent 8bcd53efa2
commit 217a7acbbc
20 changed files with 1990 additions and 75 deletions

View File

@@ -7,6 +7,7 @@
use crate::data::{FloorDef, Prefab, Registries};
use super::creature::Creature;
use super::floor::{Floor, Rect, Room, Tile};
use super::rng::Rng;
@@ -128,10 +129,24 @@ pub fn generate(def: &FloorDef, reg: &Registries, rng: &mut Rng) -> Floor {
}
}
// Ein `e` sagt „hier steht ein Gegner", **nicht welcher** — das
// entscheidet die Etage (Ticket 10). Nur so laesst sich derselbe Raum
// auf Etage 1 und Etage 5 verwenden.
let gewichte: Vec<u32> = def.creatures.iter().map(|c| c.weight).collect();
let enemies = enemy_spawns
.iter()
.filter_map(|_| {
let i = rng.weighted(&gewichte)?;
let cdef = reg.creatures.get(&def.creatures[i].id)?;
Some(Creature::from_def(cdef))
})
.collect();
rooms.push(Room {
prefab: p.prefab.clone(),
rect: p.rect,
enemy_spawns,
enemies,
loot,
cleared: false,
});