Die PostChain lag als engine::post im Kern, nur damit die Konsole
`post` durch den exec-Trichter schalten konnte — Zustand, der allein
das Render-Frontend angeht und nichts mit der Simulation zu tun hat.
Jetzt kennt der Kern Post-Effekte nur als deferred Action (wie
play_sound): das `post <name> on|off`-Verb queued Action::SetPost,
die Session reicht sie als take_post()-Queue durch, das Fenster wendet
sie auf seine eigene PostChain an. Damit schalten Trigger und Ink-Tags
Effekte, die vorher nur der Konsolenbefehl erreichte.
- engine/post.rs entfällt; PostChain wandert nach render::post
- Session verliert das post-Feld, bekommt pending_post + take_post
- der `post`-Konsolenbefehl ist ein Shim wie `mode`; Ketten-Listing
und nackter Toggle fallen weg (letzterer bewusst, vgl. 8bee832)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0196AM4UyAJEXVtPvp5DdKr8
105 lines
3.6 KiB
Rust
105 lines
3.6 KiB
Rust
//! Owner des dispatch-relevanten Spielzustands und der Borrow-View, durch
|
|
//! die Signal-Actions und Ink-Story-Steps darauf zugreifen.
|
|
//!
|
|
//! `Game` hält die Slots, die Actions lesen oder mutieren dürfen.
|
|
//! `ActionCtx` ist der schmale `&mut`-View; er wird über `Game::action_ctx()`
|
|
//! konstruiert und durch die Dispatch-Pipeline gereicht.
|
|
//!
|
|
//! Verben, deren Ziel-Subsystem (Szene, Audio, Renderer) den Kern nichts
|
|
//! angeht, produzieren [`Action`]-Werte statt direkt zu wirken. Frontends
|
|
//! konsumieren die Queue: die CLI druckt sie, das Fenster führt sie aus.
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use crate::engine::ink;
|
|
use crate::engine::kv::Store;
|
|
|
|
/// Signal-Table aus `assets/signals.toml`: Signal-Name → Action-Strings.
|
|
/// Geladen/geparst von [`crate::engine::signals`]; definiert ist der Typ
|
|
/// hier beim Zustand, damit `game` ← `signals` eine Einbahnstraße bleibt.
|
|
pub type Signals = HashMap<String, Vec<String>>;
|
|
|
|
/// Action, die der Kern nicht selbst ausführt, sondern ans Frontend
|
|
/// weiterreicht. Die Queue in `Game::actions` gehört nach jedem Dispatch
|
|
/// geleert (konsumiert).
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum Action {
|
|
/// `assets/sounds/{name}.wav` als Einmal-SFX abspielen.
|
|
PlaySound(String),
|
|
/// Anzeige-/Eingabemodus wechseln (Spiel, Noclip, Menü). Der Dialog-Modus
|
|
/// ist bewusst nicht wählbar — er entsteht nur aus dem Story-Ablauf.
|
|
SetMode(ModeTarget),
|
|
/// Renderer-Post-Effekt schalten. Der Kern kennt weder die Kette noch die
|
|
/// Effektnamen — das Fenster löst `name` gegen seine Kette auf (unbekannt
|
|
/// → Meldung), die CLI reicht nur die `[action]`-Zeile.
|
|
SetPost { name: String, on: bool },
|
|
}
|
|
|
|
/// Frontend-neutrales Ziel eines Moduswechsels: die nicht-Dialog-Varianten von
|
|
/// `session::Mode`, ohne dass der Kern dessen Dialog-Daten kennen muss.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum ModeTarget {
|
|
/// First-Person-Spiel.
|
|
Play,
|
|
/// Noclip: freies Fliegen ohne Kollision.
|
|
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 {
|
|
pub kv: Store,
|
|
pub signals: Signals,
|
|
pub story: Option<ink::Story>,
|
|
pub actions: Vec<Action>,
|
|
}
|
|
|
|
pub struct ActionCtx<'a> {
|
|
pub kv: &'a mut Store,
|
|
pub signals: &'a Signals,
|
|
pub story: &'a mut Option<ink::Story>,
|
|
pub actions: &'a mut Vec<Action>,
|
|
/// Auslöser-Identifier: bei Objekt-Interaktion der volle Instance-Name
|
|
/// (mit Blender-Suffix, z.B. `Mushroom.005`). `$self` in Action-Args
|
|
/// wird damit substituiert. `None` bei Signalen ohne Quell-Instance
|
|
/// (z.B. `init`, Story-Tags).
|
|
pub instance_name: Option<String>,
|
|
}
|
|
|
|
impl Game {
|
|
pub fn new(signals: Signals) -> Self {
|
|
Self { kv: Store::new(), signals, story: None, actions: Vec::new() }
|
|
}
|
|
|
|
pub fn action_ctx(&mut self, instance_name: Option<String>) -> ActionCtx<'_> {
|
|
ActionCtx {
|
|
kv: &mut self.kv,
|
|
signals: &self.signals,
|
|
story: &mut self.story,
|
|
actions: &mut self.actions,
|
|
instance_name,
|
|
}
|
|
}
|
|
}
|