Mode als Action

This commit is contained in:
2026-06-15 09:46:13 +02:00
parent 201d3be322
commit 93480150e5
3 changed files with 133 additions and 13 deletions
+75 -12
View File
@@ -18,7 +18,7 @@
//! im `render`-Modul.
use crate::engine::collision::CollisionWorld;
use crate::engine::game::{Action, Game};
use crate::engine::game::{Action, Game, ModeTarget};
use crate::engine::ink::StoryState;
use crate::engine::map::{self, Map};
use crate::engine::player::Player;
@@ -148,19 +148,21 @@ impl Session {
// Menü öffnen bzw. ins Spiel zurück. „Fortsetzen" landet im
// Spielmodus (Play), nicht in der Debug-Flycam; aus Play oder
// Free öffnet es das Menü. (Dialog wird anders geroutet.)
self.mode = match self.mode {
Mode::Menu => Mode::Play,
_ => Mode::Menu,
let t = match self.mode {
Mode::Menu => ModeTarget::Play,
_ => ModeTarget::Menu,
};
self.set_mode(t);
ExecResult::empty()
}
"noclip" => {
// Debug: zwischen First-Person (Play) und freier Flycam (Free)
// umschalten.
self.mode = match self.mode {
Mode::Play => Mode::Free,
_ => Mode::Play,
let t = match self.mode {
Mode::Play => ModeTarget::Free,
_ => ModeTarget::Play,
};
self.set_mode(t);
ExecResult::empty()
}
"help" => ExecResult::lines(help()),
@@ -172,7 +174,20 @@ impl Session {
])
}
_ => {
if let Some(sig) = line.strip_prefix("signal ") {
if let Some(m) = line.strip_prefix("mode ") {
// Modus explizit setzen — über dieselbe Action wie das
// `mode`-Verb aus signals.toml/Ink-Tags, damit CLI und
// Signals genau einen Wechsel-Pfad teilen.
match ModeTarget::parse(m) {
Some(t) => {
self.game.actions.push(Action::SetMode(t));
ExecResult::lines(self.drain_actions())
}
None => ExecResult::lines(vec![
"Modus: play | free | menu".into(),
]),
}
} else if let Some(sig) = line.strip_prefix("signal ") {
ExecResult::lines(self.fire(sig.trim(), None))
} else if let Some(name) = line.strip_prefix("use ") {
// Objekt-Interaktion simulieren: Signal ist der gestrippte
@@ -247,10 +262,33 @@ impl Session {
}
fn drain_actions(&mut self) -> Vec<String> {
self.game.actions.drain(..).map(|a| match a {
Action::HideObject(n) => format!("[action] hide_object {n}"),
Action::PlaySound(n) => format!("[action] play_sound {n}"),
}).collect()
// Erst aus der Queue ziehen (gibt den `&mut self.game`-Borrow frei),
// dann anwenden — `SetMode` braucht `&mut self.mode`.
let actions: Vec<Action> = self.game.actions.drain(..).collect();
let mut out = Vec::with_capacity(actions.len());
for a in actions {
match a {
Action::HideObject(n) => out.push(format!("[action] hide_object {n}")),
Action::PlaySound(n) => out.push(format!("[action] play_sound {n}")),
Action::SetMode(t) => {
self.set_mode(t);
out.push(format!("[action] mode {}", t.label()));
}
}
}
out
}
/// Frontend-neutrales [`ModeTarget`] auf den konkreten [`Mode`] mappen.
/// Einziger Schreibpfad für die nicht-Dialog-Modi — `menu`/`noclip`/`mode`
/// und die `SetMode`-Action laufen alle hier durch. (Der Dialog-Modus
/// 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::Free => Mode::Free,
ModeTarget::Menu => Mode::Menu,
};
}
fn dump_kv(&self) -> Vec<String> {
@@ -286,6 +324,7 @@ fn help() -> Vec<String> {
" kv KV-Store anzeigen".into(),
" menu Menü öffnen / ins Spiel zurück".into(),
" noclip zwischen First-Person und Debug-Flycam wechseln".into(),
" mode <m> Modus direkt setzen: play | free | menu".into(),
" reload signals.toml neu laden".into(),
" quit beenden".into(),
]
@@ -337,6 +376,30 @@ mod tests {
assert!(s.game.kv["in_menu"].coerce_to_bool().unwrap());
}
#[test]
fn mode_command_sets_mode_directly() {
let mut s = empty_session();
s.exec("mode play");
assert!(matches!(s.mode, Mode::Play));
s.exec("mode free");
assert!(matches!(s.mode, Mode::Free));
s.exec("mode menu");
assert!(matches!(s.mode, Mode::Menu));
// Müll-Argument lässt den Modus stehen und meldet die Nutzung.
let r = s.exec("mode dialog");
assert!(matches!(s.mode, Mode::Menu));
assert_eq!(r.output.len(), 1);
}
#[test]
fn signal_can_switch_mode() {
let mut s = empty_session();
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));
}
#[test]
fn noclip_toggles_play_and_free() {
let mut s = empty_session();