Sorted vn module better, small coding style improvements

This commit is contained in:
p11 2025-04-21 21:52:28 +02:00
parent 8a9b5a681d
commit cd61068f06

208
src/vn.rs
View File

@ -1,6 +1,6 @@
use std::{collections::HashMap, fs::File, io::prelude::*, net::TcpStream, path::Path}; use std::{collections::HashMap, fs::File, io::prelude::*, net::TcpStream, path::Path};
use dialogi::DialogBlock; use dialogi::{DialogBlock, DialogSequence, ParsingError};
use event_simulation::Simulation; use event_simulation::Simulation;
use indexmap::IndexMap; use indexmap::IndexMap;
use maud::{Markup, html}; use maud::{Markup, html};
@ -29,12 +29,49 @@ fn render_scene(settings: &PlayerSettings, name: &str) -> Markup {
} }
} }
fn navigation_controls(total_sections: usize) -> Markup { fn render_choice(block: &DialogBlock<Parameter>, index: usize, progress: &str) -> Markup {
let mut content = Vec::new();
convert(std::iter::once(&block.lines[0].text), &mut content);
html! { html! {
nav .nav-controls { form method="POST" {
button .nav-button onclick="prev()" { "" } input type="hidden" name="progress" value=(progress);
span #section-counter { "1/" (total_sections) } input type="hidden" name="choice" value=(index);
button .nav-button onclick="next()" { "" }
button type="submit" .choice-button {
fieldset .choice-box {
@if !block.name.is_empty() {
legend .choice-name { (block.name) }
}
@match String::from_utf8(content) {
Ok(text) => (maud::PreEscaped(text)),
Err(e) => (maud::PreEscaped(format!("Error: {e}"))),
}
}
}
}
}
}
fn render_dialog_block(block: &DialogBlock<Parameter>) -> Markup {
if block.lines.is_empty() {
return html! {};
}
let mut content = Vec::new();
convert(block.lines.iter().map(|l| l.text.as_ref()), &mut content);
html! {
fieldset .visual-novel-box {
@if !block.name.is_empty() {
legend .character-name { (block.name) }
}
div .dialog-content {
@match String::from_utf8(content) {
Ok(text) => (maud::PreEscaped(text)),
Err(e) => (maud::PreEscaped(format!("Error: {e}"))),
}
}
} }
} }
} }
@ -61,6 +98,16 @@ fn generate_html(sections: Vec<Markup>) -> Markup {
} }
} }
fn navigation_controls(total_sections: usize) -> Markup {
html! {
nav .nav-controls {
button .nav-button onclick="prev()" { "" }
span #section-counter { "1/" (total_sections) }
button .nav-button onclick="next()" { "" }
}
}
}
fn global_styles() -> Markup { fn global_styles() -> Markup {
const BASE_STYLES: &str = r" const BASE_STYLES: &str = r"
:root { :root {
@ -304,38 +351,62 @@ fn interactive_script(total_sections: usize) -> Markup {
} }
} }
fn load_multilinear(mld_path: &Path) -> Option<NamedMultilinearInfo> { fn process_dialog(
let Ok(file) = File::open(mld_path) else { dialog_sequence: &DialogSequence<Change, Parameter>,
return None; player_settings: &mut PlayerSettings,
}; ) -> Vec<Markup> {
let mut sections = Vec::new();
let mut states = initialize_change_states(&dialog_sequence.changes);
parse_multilinear(file).ok() for block in &dialog_sequence.blocks {
apply_block_changes(
block,
&dialog_sequence.changes,
&mut states,
player_settings,
);
sections.push(html! {
(render_scene(player_settings, &block.name))
(render_dialog_block(block))
});
}
player_settings.reset();
sections
} }
fn render_choice(block: &DialogBlock<Parameter>, index: usize, progress: &str) -> Markup { fn initialize_change_states(
let mut content = Vec::new(); changes: &HashMap<Parameter, Vec<Change>>,
convert(std::iter::once(&block.lines[0].text), &mut content); ) -> HashMap<Parameter, usize> {
changes.keys().map(|k| (k.clone(), 0)).collect()
}
html! { fn apply_block_changes(
form method="POST" { block: &DialogBlock<Parameter>,
input type="hidden" name="progress" value=(progress); changes: &HashMap<Parameter, Vec<Change>>,
input type="hidden" name="choice" value=(index); states: &mut HashMap<Parameter, usize>,
settings: &mut PlayerSettings,
button type="submit" .choice-button { ) {
fieldset .choice-box { for parameter in block
@if !block.name.is_empty() { .lines
legend .choice-name { (block.name) } .iter()
} .flat_map(|l| &l.actions)
@match String::from_utf8(content) { .chain(&block.final_actions)
Ok(text) => (maud::PreEscaped(text)), {
Err(e) => (maud::PreEscaped(format!("Error: {e}"))), if let Some(state) = states.get_mut(parameter) {
} if let Some(change) = changes[parameter].get(*state) {
} settings.change(change);
*state += 1;
} }
} }
} }
} }
fn load_multilinear(mld_path: &Path) -> Option<NamedMultilinearInfo> {
parse_multilinear(File::open(mld_path).ok()?).ok()
}
pub fn render_novel( pub fn render_novel(
mut config_map: IndexMap<Box<str>, Box<str>>, mut config_map: IndexMap<Box<str>, Box<str>>,
pk_path: &Path, pk_path: &Path,
@ -343,7 +414,7 @@ pub fn render_novel(
stream: &mut TcpStream, stream: &mut TcpStream,
choice: usize, choice: usize,
progress: &str, progress: &str,
) -> Result<(), dialogi::ParsingError> { ) -> Result<(), ParsingError> {
let mut settings_context = SettingsContext::new(); let mut settings_context = SettingsContext::new();
extract_layers(&mut settings_context.layers, &mut config_map); extract_layers(&mut settings_context.layers, &mut config_map);
@ -401,80 +472,3 @@ pub fn render_novel(
Ok(()) Ok(())
} }
fn process_dialog(
dialog_sequence: &dialogi::DialogSequence<Change, Parameter>,
player_settings: &mut PlayerSettings,
) -> Vec<Markup> {
let mut sections = Vec::new();
let mut states = initialize_change_states(&dialog_sequence.changes);
for block in &dialog_sequence.blocks {
apply_block_changes(
block,
&dialog_sequence.changes,
&mut states,
player_settings,
);
sections.push(html! {
(render_scene(player_settings, &block.name))
(render_dialog_block(block))
});
}
player_settings.reset();
sections
}
fn initialize_change_states(
changes: &HashMap<Parameter, Vec<Change>>,
) -> HashMap<Parameter, usize> {
changes.keys().map(|k| (k.clone(), 0)).collect()
}
fn apply_block_changes(
block: &dialogi::DialogBlock<Parameter>,
changes: &HashMap<Parameter, Vec<Change>>,
states: &mut HashMap<Parameter, usize>,
settings: &mut PlayerSettings,
) {
for parameter in block
.lines
.iter()
.flat_map(|l| &l.actions)
.chain(&block.final_actions)
{
if let Some(state) = states.get_mut(parameter) {
if let Some(change) = changes[parameter].get(*state) {
settings.change(change);
*state += 1;
}
}
}
}
fn render_dialog_block(block: &dialogi::DialogBlock<Parameter>) -> Markup {
if block.lines.is_empty() {
return html! {};
}
let mut content = Vec::new();
convert(block.lines.iter().map(|l| l.text.as_ref()), &mut content);
html! {
fieldset .visual-novel-box {
@if !block.name.is_empty() {
legend .character-name { (block.name) }
}
div .dialog-content {
@match String::from_utf8(content) {
Ok(text) => (maud::PreEscaped(text)),
Err(e) => (maud::PreEscaped(format!("Error: {e}"))),
}
}
}
}
}