You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
martian-chess/src/tweak.rs

161 lines
4.9 KiB
Rust

use crate::prelude::*;
/// A Tweaks is resource used to specify game customization like asset names,
/// and non-user customizations made to the game during development.
pub(crate) struct TweakPlugin;
impl Plugin for TweakPlugin {
fn build(&self, app: &mut App) {
app.add_systems(OnEnter(GameState::Loading), load_tweakfile);
app.init_asset::<Tweaks>()
.register_asset_loader(TweaksLoader);
}
}
fn load_tweakfile(server: Res<AssetServer>, mut commands: Commands) {
let handle: Handle<Tweaks> = server.load("martian.tweak.toml");
commands.insert_resource(GameTweaks { handle });
}
#[derive(Debug, Resource)]
pub(crate) struct GameTweaks {
pub handle: Handle<Tweaks>,
}
#[derive(Debug, Asset, TypePath)]
pub struct Tweaks {
table: toml::Table,
handles: HashMap<String, UntypedHandle>,
}
impl Tweaks {
fn from_table(table: &toml::Table, load_context: &mut LoadContext) -> Tweaks {
let handles = Tweaks::iter_all(table, "")
.iter()
.filter_map(|(k, v)| match v {
toml::Value::String(s) => std::path::Path::new(format!("assets/{}", s).as_str())
.exists()
.then(|| {
if s.ends_with(".gltf") || s.ends_with(".glb") {
Some(load_context.load::<Gltf>(s).untyped())
} else if s.ends_with(".png") {
Some(load_context.load::<Image>(s).untyped())
} else if s.ends_with(".ttf") || s.ends_with(".otf") {
Some(load_context.load::<Font>(s).untyped())
} else {
None
}
})
.flatten()
.map(|h| (k.clone(), h)),
_ => None,
})
.collect();
Tweaks {
table: table.clone(),
handles,
}
}
pub fn get_handle<T: Asset>(&self, key: &str) -> Option<Handle<T>> {
self.handles.get(key).map(|h| h.clone().typed::<T>())
}
pub fn get_handle_unchecked<T: Asset>(&self, key: &str) -> Option<Handle<T>> {
self.handles
.get(key)
.map(|h| h.clone().typed_unchecked::<T>())
}
pub fn get<'de, T: Deserialize<'de>>(&self, key: &str) -> Option<T> {
Tweaks::locate(&self.table, key).map(|val| match val.try_into() {
Ok(val) => val,
Err(e) => panic!("{}", e.message()),
})
}
fn iter_all(t: &toml::Table, key: &str) -> Vec<(String, toml::Value)> {
t.iter()
.flat_map(|(k, v)| {
let nk = if key == "" {
k.to_string()
} else {
format!("{}_{}", key, k)
};
match v {
toml::Value::Table(nt) => Tweaks::iter_all(nt, nk.as_str()),
_ => vec![(nk, v.clone())],
}
})
.collect()
}
fn locate(t: &toml::Table, key: &str) -> Option<toml::Value> {
t.iter().find_map(|(k, v)| {
if key == k {
Some(v.clone())
} else if key.starts_with(k) {
let prefix = format!("{}_", k);
match v {
toml::Value::Table(nt) => {
Tweaks::locate(nt, key.strip_prefix(prefix.as_str()).unwrap())
}
_ => Some(v.clone()),
}
} else {
None
}
})
}
}
#[derive(Default)]
pub struct TweaksLoader;
#[derive(Debug, Error)]
pub enum TweaksError {
#[error("Failed to read file")]
IO(#[from] std::io::Error),
#[error("Failed to decode file")]
Decode(#[from] Utf8Error),
#[error("Failed to parse file")]
Parse(#[from] toml::de::Error),
}
impl AssetLoader for TweaksLoader {
type Asset = Tweaks;
type Settings = ();
type Error = TweaksError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a Self::Settings,
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
use bevy::asset::AsyncReadExt;
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let s = std::str::from_utf8(bytes.as_slice())?;
match toml::from_str::<toml::Table>(s) {
Ok(parsed) => {
let result = Tweaks::from_table(&parsed, load_context);
Ok(result)
}
Err(e) => {
println!("Error parsing tweaks: {:?}", e.message());
Err(TweaksError::Parse(e))
}
}
})
}
fn extensions(&self) -> &[&str] {
&["tweak.toml"]
}
}