Signal-System mit Inky und KV-Store
This commit is contained in:
+95
@@ -0,0 +1,95 @@
|
||||
//! 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user