Files
wds/src/ink.rs
T

96 lines
3.2 KiB
Rust

//! 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<String> },
End,
}
pub struct Story {
inner: BladeStory,
tags: Vec<String>,
}
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<String> { 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<String> = kv.keys().cloned().collect();
for name in keys {
if let Some(v) = self.inner.get_variable(&name) {
kv.insert(name, v);
}
}
}
}