1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
use crate::*;
use ayaka_bindings_types::VarMap;
use fallback::Fallback;
use serde::Deserialize;
use std::collections::HashMap;
/// The paragraph in a paragraph config.
#[derive(Debug, Deserialize)]
pub struct Paragraph {
/// The tag and key of a paragraph.
/// They are referenced in `next`.
pub tag: String,
/// The title of a paragraph.
/// It can be [`None`], but better with a human-readable one.
pub title: Option<String>,
/// The texts.
/// They will be parsed into [`ayaka_primitive::Text`] later.
pub texts: Vec<Line>,
/// The next paragraph.
/// If [`None`], the game meets the end.
pub next: Option<Text>,
}
/// The Ayaka config.
/// It should be deserialized from a YAML file.
#[derive(Debug, Default, Deserialize)]
pub struct GameConfig {
/// The title of the game.
pub title: String,
/// The author of the game.
#[serde(default)]
pub author: String,
/// The paragraphs path.
pub paras: String,
/// The start paragraph tag.
pub start: String,
/// The plugin config.
#[serde(default)]
pub plugins: PluginConfig,
/// The global game properties.
#[serde(default)]
pub props: HashMap<String, String>,
/// The resources path.
pub res: Option<String>,
/// The base language.
/// If the runtime fails to choose a best match,
/// it fallbacks to this one.
pub base_lang: Locale,
}
/// The plugin config.
#[derive(Debug, Default, Deserialize)]
pub struct PluginConfig {
/// The directory of the plugins.
pub dir: String,
/// The names of the plugins, without extension.
#[serde(default)]
pub modules: Vec<String>,
}
/// The full Ayaka game.
/// It consists of global config and all paragraphs.
pub struct Game {
/// The game config.
pub config: GameConfig,
/// The paragraphs, indexed by locale.
/// The inner is the paragraphs indexed by file names.
pub paras: HashMap<Locale, HashMap<String, Vec<Paragraph>>>,
/// The resources, indexed by locale.
pub res: HashMap<Locale, VarMap>,
}
impl Game {
/// Create a [`RawContext`] at the start of the game.
pub fn start_context(&self) -> RawContext {
RawContext {
cur_base_para: self.config.start.clone(),
cur_para: self.config.start.clone(),
..Default::default()
}
}
fn choose_from_keys<'a, V>(&'a self, loc: &Locale, map: &'a HashMap<Locale, V>) -> &'a Locale {
loc.choose_from(map.keys())
.unwrap_or(&self.config.base_lang)
}
/// Find a paragraph by tag, with specified locale.
pub fn find_para(&self, loc: &Locale, base_tag: &str, tag: &str) -> Option<&Paragraph> {
if let Some(paras) = self.paras.get(loc) {
if let Some(paras) = paras.get(base_tag) {
for p in paras.iter() {
if p.tag == tag {
return Some(p);
}
}
}
}
None
}
/// Find a paragraph by tag, with specified locale.
pub fn find_para_fallback(
&self,
loc: &Locale,
base_tag: &str,
tag: &str,
) -> Fallback<&Paragraph> {
let key = self.choose_from_keys(loc, &self.paras);
let base_key = self.choose_from_keys(&self.config.base_lang, &self.paras);
Fallback::new(
if key == base_key {
None
} else {
self.find_para(key, base_tag, tag)
},
self.find_para(base_key, base_tag, tag),
)
}
fn find_res(&self, loc: &Locale) -> Option<&VarMap> {
self.res.get(loc)
}
/// Find the resource map with specified locale.
pub fn find_res_fallback(&self, loc: &Locale) -> Fallback<&VarMap> {
let key = self.choose_from_keys(loc, &self.res);
let base_key = self.choose_from_keys(&self.config.base_lang, &self.res);
Fallback::new(
if key == base_key {
None
} else {
self.find_res(key)
},
self.find_res(base_key),
)
}
}