96 lines
3.2 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|
|
}
|