feat(kampf): verbindliche Gegner-Ansage und Zielcursor

Am Zugende legt jeder Gegner fest, was er im naechsten Zug tut; der
Kampf-Screen zeigt es unter dem Lebensbalken. Verbindlich statt Vorschau,
denn nur so entsteht ein Fenster: Wer dem Gegner zwischen Ansage und
Ausfuehrung die Ausdauer nimmt, kostet ihn den Zug.

Zielwahl bekommt einen eigenen Cursor, Tab schaltet zwischen Gegner- und
Faehigkeitsliste um — dieselbe Bedienung wie im Laden. Die Ziffern
springen nur noch aufs Ziel, ausgeloest wird mit Enter.
This commit is contained in:
2026-08-12 20:49:24 +02:00
parent 7963df5236
commit 6014471c33
8 changed files with 233 additions and 47 deletions

View File

@@ -11,10 +11,15 @@ use crate::game::{Command, CreatureRef, Direction, Game};
pub enum Screen {
MainMenu,
Explore,
/// Menue-Kampf (Charter): Faehigkeitsliste, Zielwahl mit den Zifferntasten.
/// Menue-Kampf (Charter): Faehigkeitsliste links, Gegnerliste rechts.
Combat {
/// Cursor in der Faehigkeitsliste.
pick: usize,
/// Cursor in der Gegnerliste.
target: usize,
/// `true` = die Gegnerliste ist aktiv. Ein `bool` und Tab statt zweier
/// gleichzeitig sichtbarer Cursor — dieselbe Bedienung wie im Laden.
targeting: bool,
},
/// Zwei Bereiche: Inventar mit Cursor, Ausrüstung als Registry-Schleife.
Inventory {
@@ -98,37 +103,54 @@ impl Screen {
_ => Response::None,
},
Screen::Combat { pick } => {
Screen::Combat { pick, target, targeting } => {
let abilities = game.abilities_of(CreatureRef::Player, reg);
if abilities.is_empty() {
return Response::None;
}
*pick = (*pick).min(abilities.len() - 1);
*target = (*target).min(game.enemies.len().saturating_sub(1));
// Der aktive Cursor und seine Laenge — sonst stuende dieselbe
// Klemmung viermal da.
let (cursor, len) = if *targeting {
(&mut *target, game.enemies.len())
} else {
(&mut *pick, abilities.len())
};
match key {
KeyCode::Tab | KeyCode::BackTab => {
*targeting = !*targeting;
Response::None
}
KeyCode::Up | KeyCode::Char('k') => {
*pick = pick.saturating_sub(1);
*cursor = cursor.saturating_sub(1);
Response::None
}
KeyCode::Down | KeyCode::Char('j') => {
*pick = (*pick + 1).min(abilities.len() - 1);
*cursor = (*cursor + 1).min(len.saturating_sub(1));
Response::None
}
// Ziel ueber die Zifferntasten; ohne Gegner (Flucht,
// Selbstheilung) zaehlt der Held selbst als Ziel.
KeyCode::Enter | KeyCode::Char('1'..='9') => {
let index = match key {
KeyCode::Char(c @ '1'..='9') => (c as u8 - b'1') as usize,
_ => 0,
};
let target = if game.enemies.is_empty() {
CreatureRef::Player
} else {
CreatureRef::Enemy(index.min(game.enemies.len() - 1))
// Die Ziffern springen aufs Ziel, sie feuern nicht — sonst
// haette dieselbe Taste je nach Seite zwei Bedeutungen.
KeyCode::Char(c @ '1'..='9') => {
let i = (c as u8 - b'1') as usize;
if i < game.enemies.len() {
*target = i;
}
Response::None
}
// Ohne Gegner (Flucht, Selbstheilung) zaehlt der Held
// selbst als Ziel.
KeyCode::Enter => {
let ziel = match game.enemies.is_empty() {
true => CreatureRef::Player,
false => CreatureRef::Enemy(*target),
};
Response::Command(Command::UseAbility {
ability: abilities[*pick].clone(),
target,
target: ziel,
})
}
KeyCode::Char('.') => Response::Command(Command::Wait),