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:
@@ -8,10 +8,6 @@ use serde::Deserialize;
|
||||
/// Fester Katalog aus Ticket 01. `level` steht hier, weil Bedingungen und
|
||||
/// Formeln es lesen — die Quelle fuer Aufstiegspunkte ist aber `base.level`
|
||||
/// und nicht dieser gelesene Wert (Ticket 15).
|
||||
///
|
||||
// TODO(M3): `resistance` fehlt — es ist typ-indiziert (sieben Schadenstypen aus
|
||||
// Ticket 01) und braucht damit eine andere Form als die uebrigen.
|
||||
// Kommt mit dem Kampf.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[repr(usize)]
|
||||
@@ -30,10 +26,14 @@ pub enum Attribute {
|
||||
CriticalFactor,
|
||||
PriceModifier,
|
||||
CarryCapacity,
|
||||
/// **Typ-indiziert**: Ein Modifikator darauf traegt zusaetzlich einen
|
||||
/// `damage_type`. Die Basis in `Attributes` bleibt ungenutzt — Resistenz
|
||||
/// kommt ausschliesslich aus Modifikatoren (Ticket 01).
|
||||
Resistance,
|
||||
}
|
||||
|
||||
impl Attribute {
|
||||
pub const COUNT: usize = 14;
|
||||
pub const COUNT: usize = 15;
|
||||
|
||||
pub const ALL: [Attribute; Self::COUNT] = [
|
||||
Attribute::Strength,
|
||||
@@ -50,6 +50,7 @@ impl Attribute {
|
||||
Attribute::CriticalFactor,
|
||||
Attribute::PriceModifier,
|
||||
Attribute::CarryCapacity,
|
||||
Attribute::Resistance,
|
||||
];
|
||||
|
||||
pub fn name(self) -> &'static str {
|
||||
@@ -68,6 +69,7 @@ impl Attribute {
|
||||
Attribute::CriticalFactor => "Kritischer Faktor",
|
||||
Attribute::PriceModifier => "Preis-Modifikator",
|
||||
Attribute::CarryCapacity => "Tragfähigkeit",
|
||||
Attribute::Resistance => "Resistenz",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,12 +116,18 @@ impl Default for Attributes {
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Modifier {
|
||||
pub attribute: Attribute,
|
||||
/// Nur bei `attribute = "resistance"`: Resistenz ist typ-indiziert
|
||||
/// (Ticket 01), alle uebrigen Attribute sind Skalare.
|
||||
#[serde(default)]
|
||||
pub damage_type: Option<super::action::DamageType>,
|
||||
#[serde(default)]
|
||||
pub flat: f32,
|
||||
#[serde(default)]
|
||||
pub percent: f32,
|
||||
// TODO(M3): `duration`, `target` und `condition` aus Ticket 01/08. In M1
|
||||
// sind alle Modifikatoren dauerhaft und wirken auf den Traeger.
|
||||
// TODO(M4): `target` fuer Auren (`all_enemies`) und `condition` fuer
|
||||
// bedingte Modifikatoren („+5 gegen Untote", Ticket 08). Beide
|
||||
// brauchen `Game::attribute`s `against`-Parameter, der bereits
|
||||
// in der Signatur steht.
|
||||
}
|
||||
|
||||
/// Stapelregel aus Ticket 01: `(Basis + Σ flach) × (1 + Σ prozentual)`,
|
||||
@@ -149,11 +157,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn stapelregel_erst_flach_dann_prozentual() {
|
||||
let m = |attribute, flat, percent| Modifier {
|
||||
attribute,
|
||||
damage_type: None,
|
||||
flat,
|
||||
percent,
|
||||
};
|
||||
let mods = [
|
||||
Modifier { attribute: Attribute::Strength, flat: 2.0, percent: 0.0 },
|
||||
Modifier { attribute: Attribute::Strength, flat: 3.0, percent: 10.0 },
|
||||
m(Attribute::Strength, 2.0, 0.0),
|
||||
m(Attribute::Strength, 3.0, 10.0),
|
||||
// Fremdes Attribut darf nicht mitzaehlen:
|
||||
Modifier { attribute: Attribute::Damage, flat: 100.0, percent: 0.0 },
|
||||
m(Attribute::Damage, 100.0, 0.0),
|
||||
];
|
||||
// (10 + 5) * 1.10 == 16.5, nicht 10*1.1 + 5 == 16.0
|
||||
assert_eq!(stack(10.0, mods.iter(), Attribute::Strength), 16.5);
|
||||
|
||||
Reference in New Issue
Block a user