Renamed Play State
This commit is contained in:
+1
-1
@@ -29,7 +29,7 @@ fn prompt_for(mode: &Mode) -> &'static str {
|
|||||||
Mode::Dialog(d) if !d.choices.is_empty() => " wahl> ",
|
Mode::Dialog(d) if !d.choices.is_empty() => " wahl> ",
|
||||||
Mode::Dialog(_) => " [Enter] ",
|
Mode::Dialog(_) => " [Enter] ",
|
||||||
Mode::Menu => "menü> ",
|
Mode::Menu => "menü> ",
|
||||||
Mode::Play => "spiel> ",
|
Mode::FirstPerson => "spiel> ",
|
||||||
Mode::Free => "> ",
|
Mode::Free => "> ",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -339,7 +339,7 @@ impl App {
|
|||||||
// die Maus (relativer Blick); Menü/Dialog geben sie frei fürs UI. So
|
// die Maus (relativer Blick); Menü/Dialog geben sie frei fürs UI. So
|
||||||
// gibt es keine parallele Lock-Logik.
|
// gibt es keine parallele Lock-Logik.
|
||||||
let fly = matches!(self.session.mode, Mode::Free);
|
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;
|
let grab = fly || play;
|
||||||
if grab != self.input.grabbed { self.set_grab(grab); }
|
if grab != self.input.grabbed { self.set_grab(grab); }
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -228,7 +228,7 @@ pub(crate) fn layout(internal: [f32; 2], fonts: &Fonts, session: &Session, cur:
|
|||||||
match &session.mode {
|
match &session.mode {
|
||||||
// Spiel und Flycam: nur HUD/Cursor, kein Panel (beide laufen
|
// Spiel und Flycam: nur HUD/Cursor, kein Panel (beide laufen
|
||||||
// ohnehin gefangen — dieser Zweig greift nur im Übergang).
|
// 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::Menu => menu(&mut ui, f, internal, cur.pos, &mut hover),
|
||||||
Mode::Dialog(d) => dialog(&mut ui, f, internal, d, cur.pos, &mut hover),
|
Mode::Dialog(d) => dialog(&mut ui, f, internal, d, cur.pos, &mut hover),
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-46
@@ -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::collision::CollisionWorld;
|
||||||
use crate::engine::game::{Action, Game, ModeTarget};
|
use crate::engine::game::{Action, Game, ModeTarget};
|
||||||
use crate::engine::ink::StoryState;
|
use crate::engine::ink::StoryState;
|
||||||
@@ -27,28 +8,13 @@ use crate::engine::{kv, signals, story_ctrl};
|
|||||||
/// Maus-Empfindlichkeit der Spieler-Sicht (Radiant/Pixel).
|
/// Maus-Empfindlichkeit der Spieler-Sicht (Radiant/Pixel).
|
||||||
const LOOK_SENS: f32 = 0.0025;
|
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 {
|
pub enum Mode {
|
||||||
/// First-Person-Spiel — der Player bewegt sich mit Physik durch die Welt
|
FirstPerson,
|
||||||
/// (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.
|
|
||||||
Free,
|
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,
|
Menu,
|
||||||
/// Ein Dialog läuft — das Fenster pausiert die Welt und zeigt Panels;
|
|
||||||
/// Eingaben treiben den Dialog statt Befehle.
|
|
||||||
Dialog(Dialog),
|
Dialog(Dialog),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Aktueller Dialogschritt, frontend-neutral. `choices` leer = reiner Text,
|
|
||||||
/// der auf „weiter" (Leerzeile/Enter) wartet.
|
|
||||||
pub struct Dialog {
|
pub struct Dialog {
|
||||||
// Wird vom Panel-Renderer der UI-Phase gelesen; bis dahin läuft der
|
// Wird vom Panel-Renderer der UI-Phase gelesen; bis dahin läuft der
|
||||||
// Dialogtext über die Konsolen-Ausgabe (siehe `step`).
|
// Dialogtext über die Konsolen-Ausgabe (siehe `step`).
|
||||||
@@ -115,7 +81,7 @@ impl Session {
|
|||||||
/// `exec`. Nur im Spielmodus bewegt sich der Spieler; Menü/Dialog/Flycam
|
/// `exec`. Nur im Spielmodus bewegt sich der Spieler; Menü/Dialog/Flycam
|
||||||
/// pausieren die Sim (die Flycam ist reine Frontend-Sicht).
|
/// pausieren die Sim (die Flycam ist reine Frontend-Sicht).
|
||||||
pub fn tick(&mut self, input: &FrameInput, dt: f32) {
|
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.look(input.look_dx, input.look_dy, LOOK_SENS);
|
||||||
self.player.step(&self.collision, input.fwd, input.right, input.jump, dt);
|
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 {
|
pub fn exec(&mut self, line: &str) -> ExecResult {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Dialog(_) => self.dialog_input(line),
|
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)
|
// Debug: zwischen First-Person (Play) und freier Flycam (Free)
|
||||||
// umschalten.
|
// umschalten.
|
||||||
let t = match self.mode {
|
let t = match self.mode {
|
||||||
Mode::Play => ModeTarget::Free,
|
Mode::FirstPerson => ModeTarget::Free,
|
||||||
_ => ModeTarget::Play,
|
_ => ModeTarget::Play,
|
||||||
};
|
};
|
||||||
self.set_mode(t);
|
self.set_mode(t);
|
||||||
@@ -219,7 +185,7 @@ impl Session {
|
|||||||
// geladen haben, während die alte auf End lief (mode → Play). Falls
|
// geladen haben, während die alte auf End lief (mode → Play). Falls
|
||||||
// das passiert ist, sofort den ersten Schritt der neuen Story ziehen
|
// das passiert ist, sofort den ersten Schritt der neuen Story ziehen
|
||||||
// statt den Spieler im Play-Modus hängen zu lassen.
|
// 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));
|
out.extend(self.step(None));
|
||||||
}
|
}
|
||||||
ExecResult::lines(out)
|
ExecResult::lines(out)
|
||||||
@@ -252,7 +218,7 @@ impl Session {
|
|||||||
let mut out = self.drain_actions();
|
let mut out = self.drain_actions();
|
||||||
match state {
|
match state {
|
||||||
None | Some(StoryState::End) => {
|
None | Some(StoryState::End) => {
|
||||||
self.mode = Mode::Play;
|
self.mode = Mode::FirstPerson;
|
||||||
}
|
}
|
||||||
Some(StoryState::Text(text)) => {
|
Some(StoryState::Text(text)) => {
|
||||||
out.push(text.clone());
|
out.push(text.clone());
|
||||||
@@ -293,7 +259,7 @@ impl Session {
|
|||||||
/// entsteht ausschließlich in `step` aus dem Story-Ablauf.)
|
/// entsteht ausschließlich in `step` aus dem Story-Ablauf.)
|
||||||
fn set_mode(&mut self, t: ModeTarget) {
|
fn set_mode(&mut self, t: ModeTarget) {
|
||||||
self.mode = match t {
|
self.mode = match t {
|
||||||
ModeTarget::Play => Mode::Play,
|
ModeTarget::Play => Mode::FirstPerson,
|
||||||
ModeTarget::Free => Mode::Free,
|
ModeTarget::Free => Mode::Free,
|
||||||
ModeTarget::Menu => Mode::Menu,
|
ModeTarget::Menu => Mode::Menu,
|
||||||
};
|
};
|
||||||
@@ -376,7 +342,7 @@ mod tests {
|
|||||||
s.exec("menu");
|
s.exec("menu");
|
||||||
assert!(matches!(s.mode, Mode::Menu));
|
assert!(matches!(s.mode, Mode::Menu));
|
||||||
s.exec("menu"); // „Fortsetzen" → ins Spiel
|
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ü
|
s.exec("menu"); // aus dem Spiel wieder ins Menü
|
||||||
assert!(matches!(s.mode, Mode::Menu));
|
assert!(matches!(s.mode, Mode::Menu));
|
||||||
// Im Menü laufen Engine-Befehle weiter (gleiche Route wie Play/Free).
|
// Im Menü laufen Engine-Befehle weiter (gleiche Route wie Play/Free).
|
||||||
@@ -388,7 +354,7 @@ mod tests {
|
|||||||
fn mode_command_sets_mode_directly() {
|
fn mode_command_sets_mode_directly() {
|
||||||
let mut s = empty_session();
|
let mut s = empty_session();
|
||||||
s.exec("mode play");
|
s.exec("mode play");
|
||||||
assert!(matches!(s.mode, Mode::Play));
|
assert!(matches!(s.mode, Mode::FirstPerson));
|
||||||
s.exec("mode free");
|
s.exec("mode free");
|
||||||
assert!(matches!(s.mode, Mode::Free));
|
assert!(matches!(s.mode, Mode::Free));
|
||||||
s.exec("mode menu");
|
s.exec("mode menu");
|
||||||
@@ -405,17 +371,17 @@ mod tests {
|
|||||||
s.mode = Mode::Free;
|
s.mode = Mode::Free;
|
||||||
// `mode`-Verb direkt als Action (Ink-Tag-/signals.toml-Pfad).
|
// `mode`-Verb direkt als Action (Ink-Tag-/signals.toml-Pfad).
|
||||||
s.exec("signal mode play");
|
s.exec("signal mode play");
|
||||||
assert!(matches!(s.mode, Mode::Play));
|
assert!(matches!(s.mode, Mode::FirstPerson));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn noclip_toggles_play_and_free() {
|
fn noclip_toggles_play_and_free() {
|
||||||
let mut s = empty_session();
|
let mut s = empty_session();
|
||||||
s.mode = Mode::Play;
|
s.mode = Mode::FirstPerson;
|
||||||
s.exec("noclip");
|
s.exec("noclip");
|
||||||
assert!(matches!(s.mode, Mode::Free));
|
assert!(matches!(s.mode, Mode::Free));
|
||||||
s.exec("noclip");
|
s.exec("noclip");
|
||||||
assert!(matches!(s.mode, Mode::Play));
|
assert!(matches!(s.mode, Mode::FirstPerson));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user