fix(kampf): geglueckte Flucht bricht die Initiativreihenfolge ab
Die Reihenfolge steht vor dem Zug fest, Flucht leert `enemies` aber mitten darin — danach zeigten die Indizes ins Leere und ein Gegner, der nach dem Helden drankam, stuerzte das Spiel ab. Zwei Zusagen aus Ticket 14, jede fuer sich richtig, nie zusammen geprueft: alle Kampftests loesten die Flucht am Ende der Reihenfolge aus.
This commit is contained in:
@@ -240,3 +240,22 @@ zusätzlichen Zug auf jeder Beutetabelle. Damit hat `[balance]` acht Felder.
|
||||
- **15 Progression**: was bei Sieg passiert (EXP), und die Zahlen hinter allen Formeln inklusive der
|
||||
fünf `[balance]`-Knöpfe.
|
||||
- **16 Loot-Tabellen**: Beute hängt an der `creatures`-Registry, die hier entstanden ist.
|
||||
|
||||
### Befund aus M4 — die Initiativreihenfolge ist ein Schnappschuss
|
||||
|
||||
Der Zugablauf legt die Reihenfolge **einmal vor dem Zug** fest und arbeitet sie danach ab. Das war
|
||||
richtig gedacht — sonst könnte ein Gegner sich durch Handeln in der Reihenfolge nach vorn schieben.
|
||||
Nur passt es nicht zu einer Zusage aus demselben Ticket: **Flucht beendet den Kampf sofort**, leert
|
||||
also `enemies` mitten in der Schleife. Die Reihenfolge zeigt danach auf Indizes, die es nicht mehr
|
||||
gibt.
|
||||
|
||||
Im Spiel sichtbar als Absturz nach mehrfachem Fluchtversuch — die Flucht muss glücken, *bevor* ein
|
||||
Gegner in der Reihenfolge an der Reihe war, sonst fällt es nicht auf. Kein Test hatte den Fall, weil
|
||||
alle Kampftests die Flucht am Ende der Reihenfolge auslösten.
|
||||
|
||||
Entschieden: Ein leerer `enemies` **bricht den Zug ab**, statt still übersprungen zu werden. Kein
|
||||
Gegner mehr da heißt, es gibt nichts mehr zu handeln — ein `continue` würde denselben Absturz nur
|
||||
verschieben, wenn später jemand die Liste wieder füllt.
|
||||
|
||||
Damit ist die Regel dieses Tickets vollständig: Die Reihenfolge steht vor dem Zug fest, *und* sie
|
||||
endet, sobald ihre Grundlage weg ist.
|
||||
|
||||
@@ -110,6 +110,11 @@ niemand hatte sie gebaut. Der Beutezieher schob für `count = "50-120"` bis zu *
|
||||
ins Inventar. Gefunden nicht beim Nachdenken, sondern beim Entwerfen des Handels: „Gold abziehen"
|
||||
braucht einen Stapel, sonst ist Bezahlen eine Schleife über Einträge.
|
||||
|
||||
Und **ein Absturz, den erst das Spielen gefunden hat**: Die Initiativreihenfolge ist ein Schnappschuss
|
||||
vor dem Zug, eine geglückte Flucht leert `enemies` aber mitten darin — die Reihenfolge zeigt danach ins
|
||||
Leere. Zwei Zusagen aus **14**, jede für sich richtig, die zusammen nicht geprüft worden waren. Ein
|
||||
leerer `enemies` bricht den Zug jetzt ab.
|
||||
|
||||
Dazu **eine bewusst offene Stelle**: `Npc.triggers` ist nicht gebaut, weil `triggers_for` NPCs nicht
|
||||
einsammelt — ein Feld ohne Leser wäre eine Zusage, die niemand einlöst. TODO steht im Code.
|
||||
|
||||
|
||||
@@ -503,7 +503,11 @@ impl Game {
|
||||
}
|
||||
match who {
|
||||
CreatureRef::Player => self.perform(&cmd, reg),
|
||||
CreatureRef::Enemy(i) => self.enemy_acts(i, reg),
|
||||
CreatureRef::Enemy(i) if i < self.enemies.len() => self.enemy_acts(i, reg),
|
||||
// Eine geglueckte Flucht leert `enemies` mitten in der
|
||||
// Reihenfolge — die stand vor dem Zug fest und zeigt danach ins
|
||||
// Leere. Kein Gegner mehr da heisst: der Zug ist vorbei.
|
||||
CreatureRef::Enemy(_) => break,
|
||||
}
|
||||
// 4. nach *jeder* Handlung leerarbeiten
|
||||
self.drain_events(reg);
|
||||
@@ -1559,4 +1563,36 @@ mod tests {
|
||||
assert_eq!(game.shop_stock().expect("Korb").len(), ware.len() - 1);
|
||||
assert!(game.player.inventory.iter().any(|i| i.def == ware[0].def));
|
||||
}
|
||||
|
||||
/// Gefunden im Spiel, nicht im Test: Wer oft genug flieht, trifft den Zug,
|
||||
/// in dem die Flucht **vor** dem Handeln eines Gegners glueckt — dann zeigt
|
||||
/// die vorher festgelegte Initiativreihenfolge auf einen geleerten `enemies`.
|
||||
#[test]
|
||||
fn geglueckte_flucht_bricht_die_initiativreihenfolge_ab() {
|
||||
let (mut game, reg) = zwergenkrieger();
|
||||
|
||||
// Zwei Gegner, damit die Reihenfolge einen Index >0 enthaelt, und ein
|
||||
// Held in der Mitte, damit nach seiner Flucht noch jemand drankaeme.
|
||||
let ratte = reg.creatures.values().next().expect("mindestens ein Gegner");
|
||||
game.enemies = vec![Creature::from_def(ratte), Creature::from_def(ratte)];
|
||||
for (i, e) in game.enemies.iter_mut().enumerate() {
|
||||
e.initiative = i as i32;
|
||||
}
|
||||
game.player.initiative = 1;
|
||||
|
||||
// Flucht liegt bei 10-90 %, nach 30 Zuegen ist sie praktisch sicher.
|
||||
for _ in 0..30 {
|
||||
if game.enemies.is_empty() || game.over.is_some() {
|
||||
break;
|
||||
}
|
||||
game.execute(
|
||||
Command::UseAbility {
|
||||
ability: String::from("core:flee"),
|
||||
target: CreatureRef::Player,
|
||||
},
|
||||
®,
|
||||
);
|
||||
}
|
||||
assert!(game.enemies.is_empty(), "in 30 Zuegen muss eine Flucht geglueckt sein");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user