Mode als Action
This commit is contained in:
@@ -30,6 +30,44 @@ pub enum Action {
|
||||
HideObject(String),
|
||||
/// WAV unter `assets/audio/<name>` als SFX abspielen.
|
||||
PlaySound(String),
|
||||
/// Anzeige-/Eingabemodus wechseln (Spiel, Flycam, Menü). Vom Frontend
|
||||
/// auf seinen `Mode` gemappt; der Dialog-Modus ist hier bewusst nicht
|
||||
/// wählbar — der entsteht nur aus dem Story-Ablauf.
|
||||
SetMode(ModeTarget),
|
||||
}
|
||||
|
||||
/// Frontend-neutrales Ziel eines Moduswechsels. Spiegelt die nicht-Dialog-
|
||||
/// Varianten von `session::Mode`, ohne dass der Kern die rich `Mode`-Daten
|
||||
/// (Dialog-State) kennen muss. So bleibt `game` ← `session` eine Einbahnstraße.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ModeTarget {
|
||||
/// First-Person-Spiel.
|
||||
Play,
|
||||
/// Noclip-Debug-Flycam.
|
||||
Free,
|
||||
/// Menü (pausiert die Welt, gibt die Maus frei).
|
||||
Menu,
|
||||
}
|
||||
|
||||
impl ModeTarget {
|
||||
/// `play`/`free`/`menu` (case-insensitiv) → Ziel; sonst `None`.
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
match s.trim().to_ascii_lowercase().as_str() {
|
||||
"play" => Some(Self::Play),
|
||||
"free" => Some(Self::Free),
|
||||
"menu" => Some(Self::Menu),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Kanonischer Name fürs Echo/Logging.
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Play => "play",
|
||||
Self::Free => "free",
|
||||
Self::Menu => "menu",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Game {
|
||||
|
||||
+20
-1
@@ -16,6 +16,8 @@
|
||||
//! `clear <name>` KV-Eintrag entfernen
|
||||
//! `hide_object <name>` → Action::HideObject (deferred)
|
||||
//! `play_sound <file>` → Action::PlaySound (deferred)
|
||||
//! `mode <play|free|menu>` → Action::SetMode (deferred): Anzeige-/
|
||||
//! Eingabemodus wechseln
|
||||
//!
|
||||
//! Parameter-Substitution: vor dem Parsen ersetzt `execute` `$self` in den
|
||||
//! Action-Args durch `ctx.instance_name`. Damit kann eine generische Action
|
||||
@@ -30,7 +32,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
use crate::engine::game::{Action, ActionCtx, Signals};
|
||||
use crate::engine::game::{Action, ActionCtx, ModeTarget, Signals};
|
||||
use crate::engine::kv;
|
||||
use crate::engine::story_ctrl;
|
||||
|
||||
@@ -82,6 +84,9 @@ fn execute(cmd: &str, ctx: &mut ActionCtx) {
|
||||
"clear" => kv::apply_clear(args, ctx.kv),
|
||||
"hide_object" => ctx.actions.push(Action::HideObject(args.to_string())),
|
||||
"play_sound" => ctx.actions.push(Action::PlaySound(args.to_string())),
|
||||
"mode" => if let Some(t) = ModeTarget::parse(args) {
|
||||
ctx.actions.push(Action::SetMode(t));
|
||||
},
|
||||
_ => { /* Unbekannter Verb/Tag → still ignorieren */ }
|
||||
}
|
||||
}
|
||||
@@ -176,6 +181,20 @@ mod tests {
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode_verb_queues_setmode_action() {
|
||||
let mut game = Game::new(Signals::new());
|
||||
let mut ctx = game.action_ctx(None);
|
||||
dispatch("mode free", &mut ctx);
|
||||
assert_eq!(game.actions, vec![Action::SetMode(ModeTarget::Free)]);
|
||||
|
||||
// Unbekanntes Ziel → kein Action (stilles No-Op).
|
||||
game.actions.clear();
|
||||
let mut ctx = game.action_ctx(None);
|
||||
dispatch("mode wat", &mut ctx);
|
||||
assert!(game.actions.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_signal_falls_through_to_builtin() {
|
||||
let mut game = Game::new(Signals::new());
|
||||
|
||||
Reference in New Issue
Block a user