use chrono::Duration; use std::convert::TryFrom; use std::fmt::Debug; use std::fs::File; use std::io::{BufReader, Read}; use std::sync::Arc; use hashbrown::HashSet; use num_traits::PrimInt; use project_root::get_project_root; use serde_json::Value; use pkmn_lib::defines::LevelInt; use pkmn_lib::dynamic_data::Gen7DamageLibrary; use pkmn_lib::dynamic_data::Gen7MiscLibrary; use pkmn_lib::dynamic_data::{DynamicLibrary, DynamicLibraryImpl}; use pkmn_lib::dynamic_data::{EmptyScriptResolver, Gen7BattleStatCalculator, ScriptResolver}; use pkmn_lib::static_data::{ AbilityImpl, AbilityLibrary, AbilityLibraryImpl, BattleItemCategory, DataLibrary, EffectParameter, Form, FormImpl, GrowthRateLibrary, GrowthRateLibraryImpl, ItemImpl, ItemLibrary, ItemLibraryImpl, LearnableMoves, LearnableMovesImpl, LibrarySettingsImpl, LookupGrowthRate, MoveDataImpl, MoveLibrary, MoveLibraryImpl, NatureImpl, NatureLibrary, NatureLibraryImpl, SecondaryEffect, SecondaryEffectImpl, SpeciesImpl, SpeciesLibrary, SpeciesLibraryImpl, StaticDataImpl, StaticStatisticSet, Statistic, TypeLibrary, TypeLibraryImpl, }; use pkmn_lib::StringKey; pub struct LoadResult { pub library: Arc, pub types_load_time: Duration, pub natures_load_time: Duration, pub items_load_time: Duration, pub growth_rate_load_time: Duration, pub abilities_load_time: Duration, pub moves_load_time: Duration, pub species_load_time: Duration, pub wasm_load_time: Duration, } pub fn load_library() -> LoadResult { let mut path = get_project_root().unwrap(); path.push("tests/data/"); let path = path.to_str().unwrap().to_string(); let t1 = chrono::Utc::now(); let types = load_types(&path); let t2 = chrono::Utc::now(); let types_load_time = t2 - t1; let natures = load_natures(&path); let t1 = chrono::Utc::now(); let natures_load_time = t1 - t2; let items = load_items(&path); let t2 = chrono::Utc::now(); let items_load_time = t2 - t1; let growth_rates = load_growth_rates(&path); let t1 = chrono::Utc::now(); let growth_rate_load_time = t1 - t2; let abilities = load_abilities(&path); let t2 = chrono::Utc::now(); let abilities_load_time = t2 - t1; let moves = load_moves(&path, &types); let t1 = chrono::Utc::now(); let moves_load_time = t1 - t2; let species = load_species(&path, &types, &moves); let t2 = chrono::Utc::now(); let species_load_time = t2 - t1; let data = StaticDataImpl::new( Box::new(LibrarySettingsImpl::new(100)), species, moves, items, growth_rates, types, natures, abilities, ); let t1 = chrono::Utc::now(); let script_resolver = load_script_resolver(&path); let t2 = chrono::Utc::now(); let wasm_load_time = t2 - t1; let library = Arc::new(DynamicLibraryImpl::new( Box::new(data), Box::new(Gen7BattleStatCalculator::new()), Box::new(Gen7DamageLibrary::new(false)), Box::new(Gen7MiscLibrary::new()), script_resolver, )); LoadResult { library, types_load_time, natures_load_time, items_load_time, growth_rate_load_time, abilities_load_time, moves_load_time, species_load_time, wasm_load_time, } } pub fn load_types(path: &String) -> Box { let mut reader = csv::ReaderBuilder::new() .delimiter(b'|') .from_path(path.to_string() + "Types.csv") .unwrap(); let mut type_library = Box::new(TypeLibraryImpl::new(20)); let headers = reader.headers().unwrap(); for header in headers.iter().skip(1) { type_library.register_type(&header.into()); } for record in reader.records() { let record = record.unwrap(); let offensive_type = record.get(0).unwrap(); let offensive_type_id = type_library.get_type_id(&offensive_type.into()).unwrap(); for (i, v) in record.iter().skip(1).enumerate() { let effectiveness = v.parse::().unwrap(); type_library.set_effectiveness(offensive_type_id, ((i + 1) as u8).into(), effectiveness); } } type_library } pub fn load_natures(path: &String) -> Box { let mut reader = csv::ReaderBuilder::new() .delimiter(b'|') .from_path(path.to_string() + "Natures.csv") .unwrap(); let mut nature_library = Box::new(NatureLibraryImpl::new(24)); for record in reader.records() { let record = record.unwrap(); let nature_name = record.get(0).unwrap().into(); let increased_statistic_str = record.get(1).unwrap(); let decreased_statistic_str = record.get(2).unwrap(); if increased_statistic_str.is_empty() || decreased_statistic_str.is_empty() { nature_library.load_nature(nature_name, NatureImpl::new(Statistic::HP, Statistic::HP, 1.0, 1.0)); } else { let increased_statistic = serde_plain::from_str(increased_statistic_str).unwrap(); let decreased_statistic = serde_plain::from_str(decreased_statistic_str).unwrap(); nature_library.load_nature( nature_name, NatureImpl::new(increased_statistic, decreased_statistic, 1.1, 0.9), ); } } nature_library } pub fn load_items(path: &String) -> Box { let mut file = File::open(path.to_string() + "Items.json").unwrap(); let mut data = String::new(); file.read_to_string(&mut data).unwrap(); let json: Value = serde_json::from_str(&data).unwrap(); let json_array = json.as_array().unwrap(); let mut item_library = Box::new(ItemLibraryImpl::new(400)); for v in json_array { let name = v.get("name").unwrap().as_str().unwrap().into(); let category = serde_json::from_value(v.get("itemType").unwrap().clone()).unwrap(); let mut battle_category = BattleItemCategory::None; if let Some(c) = v.get("battleType") { battle_category = serde_json::from_value(c.clone()).unwrap(); } let price = v.get("price").unwrap().as_i64().unwrap(); let mut flags = HashSet::new(); if let Some(f) = v.get("flags") { let a = f.as_array().unwrap(); for flag in a { flags.insert(flag.as_str().unwrap().into()); } } item_library.add( &name, Arc::new(ItemImpl::new(&name, category, battle_category, price as i32, flags)), ); } item_library } pub fn load_growth_rates(path: &String) -> Box { let mut file = File::open(path.to_string() + "GrowthRates.json").unwrap(); let mut data = String::new(); file.read_to_string(&mut data).unwrap(); let json: Value = serde_json::from_str(&data).unwrap(); let o = json.as_object().unwrap(); let mut growth_rate_library = Box::new(GrowthRateLibraryImpl::new(10)); for (key, value) in o { let name = StringKey::new(key); let experience_required_json = value.as_array().unwrap(); let mut experience_required = Vec::with_capacity(experience_required_json.len()); for v in experience_required_json { experience_required.push(v.as_i64().unwrap() as u32); } growth_rate_library.add_growth_rate(&name, Box::new(LookupGrowthRate::new(experience_required))); } growth_rate_library } pub fn load_abilities(path: &String) -> Box { let mut file = File::open(path.to_string() + "Abilities.json").unwrap(); let mut data = String::new(); file.read_to_string(&mut data).unwrap(); let json: Value = serde_json::from_str(&data).unwrap(); let o = json.as_object().unwrap(); let mut ability_library = Box::new(AbilityLibraryImpl::new(400)); for (key, value) in o { let name = StringKey::new(key); let mut effect = StringKey::empty(); if let Some(e) = value.get("effect") { effect = e.as_str().unwrap().into(); } let mut parameters = Vec::new(); if let Some(p) = value.get("parameters") { for par in p.as_array().unwrap() { parameters.push(parse_effect_parameter(par)); } } ability_library.add(&name, Arc::new(AbilityImpl::new(&name, &effect, parameters))); } ability_library } pub fn load_moves(path: &String, types: &Box) -> Box { let mut file = File::open(path.to_string() + "Moves.json").unwrap(); let mut data = String::new(); file.read_to_string(&mut data).unwrap(); let json: Value = serde_json::from_str(&data).unwrap(); let data = json.as_object().unwrap().get("data").unwrap().as_array().unwrap(); let mut move_library = Box::new(MoveLibraryImpl::new(600)); for move_data in data { let move_data = move_data.as_object().unwrap(); let move_name = move_data.get("name").unwrap().as_str().unwrap().into(); let move_type = move_data.get("type").unwrap().as_str().unwrap().into(); let move_type_id = types.get_type_id(&move_type).unwrap(); let move_category = serde_json::from_value(move_data.get("category").unwrap().clone()).unwrap(); let base_power = move_data.get("power").unwrap().as_i64().unwrap() as u8; let accuracy = move_data.get("accuracy").unwrap().as_i64().unwrap() as u8; let pp = move_data.get("pp").unwrap().as_i64().unwrap() as u8; let target = serde_json::from_value(move_data.get("target").unwrap().clone()).unwrap(); let priority = move_data.get("priority").unwrap().as_i64().unwrap() as i8; let secondary_effect: Option> = if let Some(v) = move_data.get("effect") { let mut chance = -1.0; if let Some(chance_value) = v.get("chance") { chance = chance_value.as_f64().unwrap() as f32; } let mut parameters = Vec::new(); if let Some(pars) = v.get("parameters") { let pars = pars.as_array().unwrap(); for par in pars { parameters.push(parse_effect_parameter(par)); } } Some(Box::new(SecondaryEffectImpl::new( chance, v.get("name").unwrap().as_str().unwrap().into(), parameters, ))) } else { None }; let mut flags = HashSet::new(); if let Some(f) = move_data.get("flags") { let f = f.as_array().unwrap(); for flag in f { flags.insert(flag.as_str().unwrap().into()); } } move_library.add( &move_name, Arc::new(MoveDataImpl::new( &move_name.clone(), move_type_id, move_category, base_power, accuracy, pp, target, priority, secondary_effect, flags, )), ); } move_library } pub fn load_species( path: &String, types: &Box, moves: &Box, ) -> Box { let mut file = File::open(path.to_string() + "Pokemon.json").unwrap(); let mut data = String::new(); file.read_to_string(&mut data).unwrap(); let json: Value = serde_json::from_str(&data).unwrap(); let o = json.as_object().unwrap(); let mut species_library = Box::new(SpeciesLibraryImpl::new(800)); for (key, value) in o.iter() { if key.starts_with('$') { continue; } let name = StringKey::new(key); let id = value.get("id").unwrap().as_i64().unwrap(); let gender_rate = value.get("genderRatio").unwrap().as_f64().unwrap(); let growth_rate_name = value.get("growthRate").unwrap().as_str().unwrap().into(); let _base_happiness = value.get("baseHappiness").unwrap().as_i64().unwrap(); let catch_rate = value.get("catchRate").unwrap().as_i64().unwrap(); let _color = value.get("color").unwrap().as_str().unwrap(); // let egg_groups = value.get("eggGroups").unwrap() // .as_array() // .unwrap() // .iter() // .map(|&a| a.as_str().unwrap()) // .collect(); let _egg_cycle = value.get("eggCycles").unwrap().as_i64().unwrap(); // TODO: tags // TODO: evolutions let forms = value.get("formes").unwrap().as_object().unwrap(); let default_form_value = forms.get("default").unwrap(); let default_form = parse_form("default".into(), default_form_value, types, moves); let species = SpeciesImpl::new( id as u16, &name, gender_rate as f32, &growth_rate_name, catch_rate as u8, default_form, Default::default(), ); species_library.add(&name, Arc::new(species)); } species_library } #[cfg(not(feature = "wasm"))] fn load_script_resolver(path: &String) -> Box { Box::new(EmptyScriptResolver::default()) } #[cfg(feature = "wasm")] fn load_script_resolver(path: &String) -> Box { let mut resolver = pkmn_lib::script_implementations::wasm::script_resolver::WebAssemblyScriptResolver::new(); let file = File::open(path.to_string() + "gen7_scripts.wasm").unwrap(); let mut reader = BufReader::new(file); let mut buffer = Vec::new(); reader.read_to_end(&mut buffer).unwrap(); resolver.load_wasm_from_bytes(&buffer); resolver.finalize(); resolver } fn parse_form( name: StringKey, value: &Value, types: &Box, moves: &Box, ) -> Arc { let mut abilities = Vec::new(); for a in value.get("abilities").unwrap().as_array().unwrap() { abilities.push(a.as_str().unwrap().into()); } let mut hidden_abilities = Vec::new(); for a in value.get("hiddenAbilities").unwrap().as_array().unwrap() { hidden_abilities.push(a.as_str().unwrap().into()); } let base_stats = parse_statistics(value.get("baseStats").unwrap()); // TODO: ev reward let height = value.get("height").unwrap().as_f64().unwrap(); let weight = value.get("weight").unwrap().as_f64().unwrap(); let base_experience = value.get("baseExp").unwrap().as_u64().unwrap(); let types = value .get("types") .unwrap() .as_array() .unwrap() .iter() .map(|a| types.get_type_id(&a.as_str().unwrap().into()).unwrap()) .collect(); let moves = parse_moves(value.get("moves").unwrap(), moves); Arc::new(FormImpl::new( &name, height as f32, weight as f32, base_experience as u32, types, base_stats, abilities, hidden_abilities, moves, Default::default(), )) } fn parse_statistics(value: &Value) -> StaticStatisticSet where T: PrimInt + TryFrom, >::Error: Debug, { StaticStatisticSet::new( >::try_from(value.get("hp").unwrap_or(&Value::Number(0.into())).as_u64().unwrap()).unwrap(), >::try_from( value .get("attack") .unwrap_or(&Value::Number(0.into())) .as_u64() .unwrap(), ) .unwrap(), >::try_from( value .get("defense") .unwrap_or(&Value::Number(0.into())) .as_u64() .unwrap(), ) .unwrap(), >::try_from( value .get("specialAttack") .unwrap_or(&Value::Number(0.into())) .as_u64() .unwrap(), ) .unwrap(), >::try_from( value .get("specialDefense") .unwrap_or(&Value::Number(0.into())) .as_u64() .unwrap(), ) .unwrap(), >::try_from(value.get("speed").unwrap_or(&Value::Number(0.into())).as_u64().unwrap()) .unwrap(), ) } fn parse_moves(value: &Value, move_library: &Box) -> Box { let mut moves = LearnableMovesImpl::new(100); let level_moves = value.get("levelMoves").unwrap().as_array().unwrap(); for level_move in level_moves { let name = level_move.get("name").unwrap().as_str().unwrap().into(); let level = level_move.get("level").unwrap().as_u64().unwrap() as LevelInt; assert!(move_library.get(&name).is_some()); moves.add_level_move(level, &name); } Box::new(moves) } fn parse_effect_parameter(value: &Value) -> EffectParameter { match value { Value::Null => { panic!("Unexpected type") } Value::Bool(b) => (*b).into(), Value::Number(n) => { if n.is_f64() { (n.as_f64().unwrap() as f32).into() } else { n.as_i64().unwrap().into() } } Value::String(s) => StringKey::new(s.as_str()).into(), Value::Array(_) => { panic!("Unexpected type") } Value::Object(_) => { panic!("Unexpected type") } } } #[test] #[cfg_attr(miri, ignore)] fn test_type_library_loaded() { let mut path = get_project_root().unwrap(); path.push("tests/data/"); let types = load_types(&path.to_str().unwrap().to_string()); assert_eq!( types.get_effectiveness( types.get_type_id(&"fire".into()).unwrap(), &[types.get_type_id(&"grass".into()).unwrap()], ), 2.0 ); }