//! Dünner Adapter um die `bladeink`-Crate, gekoppelt an unseren `kv::Store`. //! //! Bei jedem `cont()` läuft ein bidirektionaler Sync: //! - vor `continue`: KV-Werte ins `variables_state` der Story spiegeln //! (Skript sieht aktuelle Spielvariablen) //! - nach `continue`: alle KV-Keys, die das Skript kennt, zurücklesen //! (Ink-`~ var = …`-Writes landen im KV) //! //! Variablen die der KV kennt, das Skript aber nicht als `VAR` deklariert, //! lehnt bladeink beim `set_variable` ab — wir ignorieren das still. //! Variablen die nur im Skript existieren, werden beim Read-Back ignoriert //! (KV bleibt minimal: nur was Rust kennt). use std::fs::read_to_string; use bladeink::story::Story as BladeStory; use crate::kv::Store; pub enum StoryState { Text(String), Choice { prompt: String, options: Vec }, End, } pub struct Story { inner: BladeStory, tags: Vec, } impl Story { pub fn load(path: &str, kv: &Store) -> Self { let src = read_to_string(path) .unwrap_or_else(|e| panic!("ink load {}: {}", path, e)); let inner = BladeStory::new(&src) .unwrap_or_else(|e| panic!("ink parse {}: {:?}", path, e)); let mut s = Self { inner, tags: Vec::new() }; s.sync_from_kv(kv); s } pub fn take_tags(&mut self) -> Vec { std::mem::take(&mut self.tags) } pub fn cont(&mut self, kv: &mut Store) -> StoryState { self.sync_from_kv(kv); // continue_maximally ruft intern reset_output() vor jedem cont() — // damit überschriebe der Tag der letzten Zeile alle vorherigen. // Wir loopen selbst und sammeln Tags nach jedem Schritt ein, sonst // gehen z.B. `# set has_key true` am Knoten-Anfang bei mehrzeiligem // Text verloren. let mut text = String::new(); while self.inner.can_continue() { let chunk = self.inner.cont() .unwrap_or_else(|e| panic!("ink cont: {:?}", e)); text.push_str(&chunk); if let Ok(t) = self.inner.get_current_tags() { self.tags.extend(t); } } let text = text.trim().to_string(); self.sync_to_kv(kv); let choices = self.inner.get_current_choices(); if !choices.is_empty() { let options = choices.iter().map(|c| c.text.clone()).collect(); return StoryState::Choice { prompt: text, options }; } if !text.is_empty() { return StoryState::Text(text); } StoryState::End } pub fn choose(&mut self, i: usize) { self.inner.choose_choice_index(i) .unwrap_or_else(|e| panic!("ink choose {}: {:?}", i, e)); } fn sync_from_kv(&mut self, kv: &Store) { for (name, value) in kv { // set_variable schlägt fehl wenn der Name kein deklariertes // VAR im Skript ist — still ignorieren. let _ = self.inner.set_variable(name, value); } } fn sync_to_kv(&self, kv: &mut Store) { let keys: Vec = kv.keys().cloned().collect(); for name in keys { if let Some(v) = self.inner.get_variable(&name) { kv.insert(name, v); } } } }