feat(progression): Erfahrung, Aufstieg und Punkteverteilung
Toeten gibt Erfahrung (exp_reward stand seit M3 ungelesen an jedem Gegner), give_exp als zwoelfte Aktion fuer spaetere Quests. Aufstieg in einer Schleife am Zugende, ein Punkt je Stufe, keine Obergrenze. Verteilt wird im Charakterbogen — kein zwoelfter Screen. AssignPoints nimmt alle Punkte auf einmal und kostet damit einen Zug. luck wirkt jetzt: eine Chance auf einen Zusatzzug auf jeder Beutetabelle. Drei Befunde: - strength und constitution las niemand, zwei der fuenf verteilbaren Grundwerte waren Fallen. Zwei balance-Knoepfe rechnen sie beim Verteilen um. - Rng::chance(0.0) verbrauchte eine Zahl und verschob den Zufallsstrom. - Cheat::ClearRoom ging am Todesweg vorbei.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
use ratatui::crossterm::event::KeyCode;
|
||||
|
||||
use crate::data::Registries;
|
||||
use crate::game::{Cheat, Command, CreatureRef, Direction, Game};
|
||||
use crate::game::{Attribute, Cheat, Command, CreatureRef, Direction, Game};
|
||||
|
||||
/// Popups sind gewoehnliche Screens, die sich beim Rendern als Overlay
|
||||
/// ausweisen — ein gemeinsamer `Vec<Screen>` statt `current` plus
|
||||
@@ -25,7 +25,15 @@ pub enum Screen {
|
||||
Inventory {
|
||||
pick: usize,
|
||||
},
|
||||
CharacterSheet,
|
||||
/// Zugleich der Verteil-Screen — **kein zwoelfter Screen** (Ticket 15).
|
||||
CharacterSheet {
|
||||
/// Cursor in `Attribute::ASSIGNABLE`.
|
||||
pick: usize,
|
||||
/// Vorgemerkte Punkte, noch nicht abgeschickt. Ein festes Feld statt
|
||||
/// eines `Vec`, weil die fuenf verteilbaren Grundwerte eine feste
|
||||
/// Liste sind.
|
||||
pending: [u32; 5],
|
||||
},
|
||||
/// Vollbild, kein Overlay. Bewusst nicht die lazy Variante mit einer
|
||||
/// Meldungszeile im Hauptmenue: Ein Abstiegs-Roguelike lebt davon, dass der
|
||||
/// Lauf am Ende *erzaehlt* wird (Ticket 14).
|
||||
@@ -64,6 +72,10 @@ pub enum Response {
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
pub fn sheet() -> Screen {
|
||||
Screen::CharacterSheet { pick: 0, pending: [0; 5] }
|
||||
}
|
||||
|
||||
pub fn is_overlay(&self) -> bool {
|
||||
// TODO(M2): `Confirm` ist das erste echte Overlay. Bis dahin haelt die
|
||||
// Regel „ein Command schliesst alle Overlays" nur die Luft.
|
||||
@@ -104,7 +116,7 @@ impl Screen {
|
||||
KeyCode::Char('t') => Response::Command(Command::Talk),
|
||||
KeyCode::F(12) => Response::Push(Screen::Debug { pick: 0 }),
|
||||
// Screen-Navigation ist reine UI und fasst `Game` nie an.
|
||||
KeyCode::Char('c') => Response::Push(Screen::CharacterSheet),
|
||||
KeyCode::Char('c') => Response::Push(Screen::sheet()),
|
||||
KeyCode::Char('i') => Response::Push(Screen::Inventory { pick: 0 }),
|
||||
KeyCode::Esc => Response::Pop,
|
||||
_ => Response::None,
|
||||
@@ -161,7 +173,7 @@ impl Screen {
|
||||
})
|
||||
}
|
||||
KeyCode::Char('.') => Response::Command(Command::Wait),
|
||||
KeyCode::Char('c') => Response::Push(Screen::CharacterSheet),
|
||||
KeyCode::Char('c') => Response::Push(Screen::sheet()),
|
||||
// Derselbe Screen wie draussen. `rejects` verbietet
|
||||
// `UseItem` im Kampf nicht — es fehlte nur die Tuer.
|
||||
KeyCode::Char('i') => Response::Push(Screen::Inventory { pick: 0 }),
|
||||
@@ -206,10 +218,39 @@ impl Screen {
|
||||
}
|
||||
}
|
||||
|
||||
Screen::CharacterSheet => match key {
|
||||
KeyCode::Esc | KeyCode::Char('c') | KeyCode::Char('q') => Response::Pop,
|
||||
_ => Response::None,
|
||||
},
|
||||
Screen::CharacterSheet { pick, pending } => {
|
||||
let offen = game.unspent_points - pending.iter().sum::<u32>();
|
||||
match key {
|
||||
KeyCode::Up | KeyCode::Char('k') => {
|
||||
*pick = pick.saturating_sub(1);
|
||||
Response::None
|
||||
}
|
||||
KeyCode::Down | KeyCode::Char('j') => {
|
||||
*pick = (*pick + 1).min(Attribute::ASSIGNABLE.len() - 1);
|
||||
Response::None
|
||||
}
|
||||
KeyCode::Right | KeyCode::Char('+' | 'l') if offen > 0 => {
|
||||
pending[*pick] += 1;
|
||||
Response::None
|
||||
}
|
||||
KeyCode::Left | KeyCode::Char('-' | 'h') if pending[*pick] > 0 => {
|
||||
pending[*pick] -= 1;
|
||||
Response::None
|
||||
}
|
||||
// **Alle** Punkte in einem Kommando und damit in einem Zug
|
||||
// (Ticket 15).
|
||||
KeyCode::Enter => Response::Command(Command::AssignPoints(
|
||||
Attribute::ASSIGNABLE
|
||||
.iter()
|
||||
.zip(pending.iter())
|
||||
.filter(|(_, n)| **n > 0)
|
||||
.map(|(a, n)| (*a, *n))
|
||||
.collect(),
|
||||
)),
|
||||
KeyCode::Esc | KeyCode::Char('c' | 'q') => Response::Pop,
|
||||
_ => Response::None,
|
||||
}
|
||||
}
|
||||
|
||||
Screen::Dialog { pick } => {
|
||||
let options = game.dialog_options(reg);
|
||||
|
||||
Reference in New Issue
Block a user