wicked-waifus-rs/wicked-waifus-data/src/lib.rs
2025-01-27 14:50:38 +01:00

194 lines
No EOL
6.1 KiB
Rust

use std::fs::File;
use std::io::BufReader;
use paste::paste;
pub use misc_data::*;
mod misc_data;
#[derive(thiserror::Error, Debug)]
pub enum LoadDataError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Failed to parse json: {0}")]
Json(#[from] serde_json::Error),
}
macro_rules! json_data {
($($table_type:ident;)*) => {
$(paste! {
mod [<$table_type:snake>];
pub use [<$table_type:snake>]::[<$table_type Data>];
})*
$(paste! {
pub mod [<$table_type:snake _data>] {
use std::sync::OnceLock;
type Data = super::[<$table_type Data>];
pub(crate) static TABLE: OnceLock<Vec<Data>> = OnceLock::new();
pub fn iter() -> std::slice::Iter<'static, Data> {
TABLE.get().unwrap().iter()
}
}
})*
fn load_json_data(base_path: &str) -> Result<(), LoadDataError> {
$(paste! {
let file = File::open(&format!("{}/{}.json", base_path, stringify!($table_type)))?;
let reader = BufReader::new(file);
let _ = [<$table_type:snake _data>]::TABLE.set(serde_json::from_reader(reader)?);
})*
Ok(())
}
};
}
macro_rules! json_hash_table_data {
($($table_type:ident, $key_param:expr, $key_type:ty;)*) => {
$(paste! {
mod [<$table_type:snake>];
pub use [<$table_type:snake>]::[<$table_type Data>];
})*
$(paste! {
pub mod [<$table_type:snake _data>] {
use std::collections::HashMap;
use std::sync::OnceLock;
pub(crate) type Data = super::[<$table_type Data>];
pub(crate) static TABLE: OnceLock<HashMap<$key_type, Data>> = OnceLock::new();
pub fn iter() -> std::collections::hash_map::Iter<'static, $key_type, Data> {
TABLE.get().unwrap().iter()
}
pub fn get(k: &$key_type) -> Option<&Data> {
TABLE.get().unwrap().get(k)
}
}
})*
fn load_json_hash_table_data(base_path: &str) -> Result<(), LoadDataError> {
$(paste! {
let file = File::open(&format!("{}/{}.json", base_path, stringify!($table_type)))?;
let reader = BufReader::new(file);
let _ = [<$table_type:snake _data>]::TABLE.set(
serde_json::from_reader::<BufReader<File>, Vec<[<$table_type:snake _data>]::Data>>(reader)?
.into_iter()
.map(|element| (element.$key_param, element))
.collect::<std::collections::HashMap<_, _>>()
);
})*
Ok(())
}
};
}
pub fn load_all_json_data(base_path: &str) -> Result<(), LoadDataError> {
load_json_data(base_path)?;
load_json_hash_table_data(base_path)?;
Ok(())
}
json_data! {
AdventureTask;
Area;
BaseProperty;
ExploreTools;
FunctionCondition;
Gacha;
GachaPool;
GachaViewInfo;
GuideTutorial;
InstanceDungeon;
LordGym;
RoleInfo;
Teleporter;
WeaponConf;
}
json_hash_table_data! {
DragonPool, id, i32;
LevelEntityConfig, entity_id, i64;
// TemplateConfig, blueprint_type, String;
}
mod textmap;
pub mod text_map_data {
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::BufReader;
use std::sync::OnceLock;
use crate::LoadDataError;
use crate::textmap::TextMapData;
use crate::gacha_view_info_data;
static EMPTY: OnceLock<HashMap<String, String>> = OnceLock::new();
static TABLE: OnceLock<HashMap<String, HashMap<String, String>>> = OnceLock::new();
pub fn load_textmaps(base_path: &str) -> Result<(), LoadDataError> {
// TODO: Ideally we would expose a function here to allow other components to add to the
// filter, since right now only gacha uses it, we can do this
let filters = get_filters();
let languages = std::fs::read_dir(base_path)?
.filter_map(|entry| entry.ok())
.filter(|entry| entry.path().is_dir())
.collect::<Vec<_>>();
let mut result: HashMap<String, HashMap<String, String>> = HashMap::new();
for language in languages {
let lang_id = language.file_name().to_str().unwrap().to_string();
let file = File::open(&format!("{base_path}/{lang_id}/multi_text/MultiText.json"))?;
let reader = BufReader::new(file);
result.insert(
lang_id,
serde_json::from_reader::<BufReader<File>, Vec<TextMapData>>(reader)?
.into_iter()
.filter(|element| filters.contains(&element.id))
.map(|element| (element.id, element.content))
.collect::<HashMap<_, _>>(),
);
}
let _ = TABLE.set(result);
Ok(())
}
pub fn get_textmap(language: i32) -> &'static HashMap<String, String> {
let (text_code, _audio_code) = get_language_from_i32(language);
TABLE.get_or_init(|| HashMap::new())
.get(text_code)
.unwrap_or(EMPTY.get_or_init(|| HashMap::new()))
}
fn get_language_from_i32(language: i32) -> (&'static str, &'static str) {
match language {
0 => ("zh-Hans", "zh"),
1 => ("en", "en"),
2 => ("ja", "ja"),
3 => ("ko", "ko"),
4 => ("ru", "en"),
5 => ("zh-Hant", "zh"),
6 => ("de", "en"),
7 => ("es", "en"),
8 => ("pt", "en"),
9 => ("id", "en"),
10 => ("fr", "en"),
11 => ("vi", "en"),
12 => ("th", "en"),
_ => ("en", "en"),
}
}
fn get_filters() -> HashSet<String> {
let mut filters = HashSet::new();
for gacha_view_info in gacha_view_info_data::iter() {
filters.insert(gacha_view_info.summary_title.clone());
filters.insert(gacha_view_info.summary_describe.clone());
}
filters
}
}