Renamed Play State

This commit is contained in:
2026-07-01 00:31:34 +02:00
parent 7dd6bfc40c
commit f181bf4de9
4 changed files with 15 additions and 49 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ fn prompt_for(mode: &Mode) -> &'static str {
Mode::Dialog(d) if !d.choices.is_empty() => " wahl> ",
Mode::Dialog(_) => " [Enter] ",
Mode::Menu => "menü> ",
Mode::Play => "spiel> ",
Mode::FirstPerson => "spiel> ",
Mode::Free => "> ",
}
}
+1 -1
View File
@@ -339,7 +339,7 @@ impl App {
// die Maus (relativer Blick); Menü/Dialog geben sie frei fürs UI. So
// gibt es keine parallele Lock-Logik.
let fly = matches!(self.session.mode, Mode::Free);
let play = matches!(self.session.mode, Mode::Play);
let play = matches!(self.session.mode, Mode::FirstPerson);
let grab = fly || play;
if grab != self.input.grabbed { self.set_grab(grab); }
+1 -1
View File
@@ -228,7 +228,7 @@ pub(crate) fn layout(internal: [f32; 2], fonts: &Fonts, session: &Session, cur:
match &session.mode {
// Spiel und Flycam: nur HUD/Cursor, kein Panel (beide laufen
// ohnehin gefangen — dieser Zweig greift nur im Übergang).
Mode::Play | Mode::Free => {}
Mode::FirstPerson | Mode::Free => {}
Mode::Menu => menu(&mut ui, f, internal, cur.pos, &mut hover),
Mode::Dialog(d) => dialog(&mut ui, f, internal, d, cur.pos, &mut hover),
}
+12 -46
View File
@@ -1,22 +1,3 @@
//! Session: der geteilte Anwendungszustand über allen Frontends.
//!
//! Es existiert genau eine `Session` pro Lauf. CLI und Fenster steuern
//! dieselbe — das Fenster besitzt sie, die Terminal-Eingabe schickt nur
//! Befehlszeilen hinein. Dadurch kann es keinen Mismatch geben: ein
//! `Game`-Zustand, ein `Mode`, beide Frontends stellen ihn nur dar.
//!
//! Schicht: `engine ← session ← { cli, render }`. Die Session kennt kein
//! Frontend — `exec` nimmt eine Befehlszeile und *gibt* Ausgabezeilen
//! zurück (statt zu drucken), damit jedes Frontend sie frei darstellt
//! (Terminal jetzt, In-Fenster-Konsole später).
//!
//! Die Session ist auch der alleinige Besitzer der **Lauf-Simulation**:
//! `Player` und `CollisionWorld`. Sie hat zwei Eingabe-Tore — `exec` für
//! diskrete Befehle/Dialog und `tick` für die kontinuierliche Physik pro
//! Frame. Die Frontends sammeln nur Eingaben und stellen den Zustand dar;
//! die Flycam dagegen ist reine Frontend-Sicht (kein Sim-Zustand) und bleibt
//! im `render`-Modul.
use crate::engine::collision::CollisionWorld;
use crate::engine::game::{Action, Game, ModeTarget};
use crate::engine::ink::StoryState;
@@ -27,28 +8,13 @@ use crate::engine::{kv, signals, story_ctrl};
/// Maus-Empfindlichkeit der Spieler-Sicht (Radiant/Pixel).
const LOOK_SENS: f32 = 0.0025;
/// Was die Frontends gerade darstellen sollen. Geteilt, damit CLI und GUI
/// nie uneins sind, ob ein Dialog läuft.
pub enum Mode {
/// First-Person-Spiel — der Player bewegt sich mit Physik durch die Welt
/// (siehe engine::player). Der Renderer leitet seine View aus dem Player
/// ab. Das ist der eigentliche Spielmodus.
Play,
/// Noclip-Debug-Flycam — frei schwebende Kamera ohne Physik. Per
/// `noclip`-Befehl gegen `Play` getauscht.
FirstPerson,
Free,
/// Menü offen — wie der Dialog pausiert das Fenster die Welt und gibt die
/// Maus frei; Eingaben sind weiter Engine-Befehle (das Menü ist nur eine
/// andere Darstellung). Per `menu`-Befehl getoggelt; „Fortsetzen" führt
/// ins Spiel (`Play`).
Menu,
/// Ein Dialog läuft — das Fenster pausiert die Welt und zeigt Panels;
/// Eingaben treiben den Dialog statt Befehle.
Dialog(Dialog),
}
/// Aktueller Dialogschritt, frontend-neutral. `choices` leer = reiner Text,
/// der auf „weiter" (Leerzeile/Enter) wartet.
pub struct Dialog {
// Wird vom Panel-Renderer der UI-Phase gelesen; bis dahin läuft der
// Dialogtext über die Konsolen-Ausgabe (siehe `step`).
@@ -115,7 +81,7 @@ impl Session {
/// `exec`. Nur im Spielmodus bewegt sich der Spieler; Menü/Dialog/Flycam
/// pausieren die Sim (die Flycam ist reine Frontend-Sicht).
pub fn tick(&mut self, input: &FrameInput, dt: f32) {
if matches!(self.mode, Mode::Play) {
if matches!(self.mode, Mode::FirstPerson) {
self.player.look(input.look_dx, input.look_dy, LOOK_SENS);
self.player.step(&self.collision, input.fwd, input.right, input.jump, dt);
}
@@ -135,7 +101,7 @@ impl Session {
pub fn exec(&mut self, line: &str) -> ExecResult {
match self.mode {
Mode::Dialog(_) => self.dialog_input(line),
Mode::Play | Mode::Free | Mode::Menu => self.command(line),
Mode::FirstPerson | Mode::Free | Mode::Menu => self.command(line),
}
}
@@ -159,7 +125,7 @@ impl Session {
// Debug: zwischen First-Person (Play) und freier Flycam (Free)
// umschalten.
let t = match self.mode {
Mode::Play => ModeTarget::Free,
Mode::FirstPerson => ModeTarget::Free,
_ => ModeTarget::Play,
};
self.set_mode(t);
@@ -219,7 +185,7 @@ impl Session {
// geladen haben, während die alte auf End lief (mode → Play). Falls
// das passiert ist, sofort den ersten Schritt der neuen Story ziehen
// statt den Spieler im Play-Modus hängen zu lassen.
if self.game.story.is_some() && matches!(self.mode, Mode::Play) {
if self.game.story.is_some() && matches!(self.mode, Mode::FirstPerson) {
out.extend(self.step(None));
}
ExecResult::lines(out)
@@ -252,7 +218,7 @@ impl Session {
let mut out = self.drain_actions();
match state {
None | Some(StoryState::End) => {
self.mode = Mode::Play;
self.mode = Mode::FirstPerson;
}
Some(StoryState::Text(text)) => {
out.push(text.clone());
@@ -293,7 +259,7 @@ impl Session {
/// entsteht ausschließlich in `step` aus dem Story-Ablauf.)
fn set_mode(&mut self, t: ModeTarget) {
self.mode = match t {
ModeTarget::Play => Mode::Play,
ModeTarget::Play => Mode::FirstPerson,
ModeTarget::Free => Mode::Free,
ModeTarget::Menu => Mode::Menu,
};
@@ -376,7 +342,7 @@ mod tests {
s.exec("menu");
assert!(matches!(s.mode, Mode::Menu));
s.exec("menu"); // „Fortsetzen" → ins Spiel
assert!(matches!(s.mode, Mode::Play));
assert!(matches!(s.mode, Mode::FirstPerson));
s.exec("menu"); // aus dem Spiel wieder ins Menü
assert!(matches!(s.mode, Mode::Menu));
// Im Menü laufen Engine-Befehle weiter (gleiche Route wie Play/Free).
@@ -388,7 +354,7 @@ mod tests {
fn mode_command_sets_mode_directly() {
let mut s = empty_session();
s.exec("mode play");
assert!(matches!(s.mode, Mode::Play));
assert!(matches!(s.mode, Mode::FirstPerson));
s.exec("mode free");
assert!(matches!(s.mode, Mode::Free));
s.exec("mode menu");
@@ -405,17 +371,17 @@ mod tests {
s.mode = Mode::Free;
// `mode`-Verb direkt als Action (Ink-Tag-/signals.toml-Pfad).
s.exec("signal mode play");
assert!(matches!(s.mode, Mode::Play));
assert!(matches!(s.mode, Mode::FirstPerson));
}
#[test]
fn noclip_toggles_play_and_free() {
let mut s = empty_session();
s.mode = Mode::Play;
s.mode = Mode::FirstPerson;
s.exec("noclip");
assert!(matches!(s.mode, Mode::Free));
s.exec("noclip");
assert!(matches!(s.mode, Mode::Play));
assert!(matches!(s.mode, Mode::FirstPerson));
}
#[test]