use anyhow_ext::{anyhow, Result}; use std::fmt::Debug; use std::sync::{Arc, LazyLock}; use hashbrown::{HashMap, HashSet}; use parking_lot::lock_api::RwLockReadGuard; use parking_lot::{RawRwLock, RwLock}; use crate::static_data::Form; use crate::static_data::Gender; use crate::StringKey; use crate::{Random, ValueIdentifiable, ValueIdentifier}; /// The data belonging to a Pokemon with certain characteristics. pub trait Species: ValueIdentifiable + Debug { /// The national dex identifier of the Pokemon. fn id(&self) -> u16; /// The name of the Pokemon. fn name(&self) -> &StringKey; /// The chance between 0.0 and 1.0 that a Pokemon is female. fn gender_rate(&self) -> f32; /// How much experience is required for a level. fn growth_rate(&self) -> &StringKey; /// How hard it is to capture a Pokemon. 255 means this will be always caught, 0 means this is /// uncatchable. fn capture_rate(&self) -> u8; /// The forms that belong to this Pokemon. fn forms(&self) -> RwLockReadGuard<'_, RawRwLock, HashMap>>; /// The arbitrary flags that can be set on a Pokemon for script use. fn flags(&self) -> &HashSet; /// Adds a new form to the species. fn add_form(&self, id: StringKey, form: Arc); /// Gets a form by name. fn get_form(&self, id: &StringKey) -> Option>; /// Gets a form by the hash of its name. fn get_form_by_hash(&self, name_hash: u32) -> Option>; /// Gets the form the Pokemon will have by default, if no other form is specified. fn get_default_form(&self) -> Result>; /// Gets a random gender. fn get_random_gender(&self, rand: &mut Random) -> Gender; /// Check whether the Pokemon has a specific flag set. fn has_flag(&self, key: &StringKey) -> bool; /// Check whether the Pokemon has a specific flag set. fn has_flag_by_hash(&self, key_hash: u32) -> bool; } /// The data belonging to a Pokemon with certain characteristics. #[derive(Debug)] pub struct SpeciesImpl { /// A unique identifier so we know what value this is. identifier: ValueIdentifier, /// The national dex identifier of the Pokemon. id: u16, /// The name of the Pokemon. name: StringKey, /// The chance between 0.0 and 1.0 that a Pokemon is female. gender_rate: f32, /// How much experience is required for a level. growth_rate: StringKey, /// How hard it is to capture a Pokemon. 255 means this will be always caught, 0 means this is /// uncatchable. capture_rate: u8, /// The forms that belong to this Pokemon. forms: RwLock>>, /// The arbitrary flags that can be set on a Pokemon for script use. flags: HashSet, } /// A cached String Key to get the default form. static DEFAULT_KEY: LazyLock = LazyLock::new(|| StringKey::new("default")); /// Gets the StringKey for "default". Initialises it if it does not exist. fn get_default_key() -> StringKey { DEFAULT_KEY.clone() } impl SpeciesImpl { /// Creates a new species. pub fn new( id: u16, name: &StringKey, gender_rate: f32, growth_rate: &StringKey, capture_rate: u8, default_form: Arc, flags: HashSet, ) -> Self { let mut forms = HashMap::with_capacity(1); forms.insert_unique_unchecked(get_default_key(), default_form); Self { identifier: Default::default(), id, name: name.clone(), gender_rate, growth_rate: growth_rate.clone(), capture_rate, forms: RwLock::new(forms), flags, } } } impl Species for SpeciesImpl { /// The national dex identifier of the Pokemon. fn id(&self) -> u16 { self.id } /// The name of the Pokemon. fn name(&self) -> &StringKey { &self.name } /// The chance between 0.0 and 1.0 that a Pokemon is female. fn gender_rate(&self) -> f32 { self.gender_rate } /// How much experience is required for a level. fn growth_rate(&self) -> &StringKey { &self.growth_rate } /// How hard it is to capture a Pokemon. 255 means this will be always caught, 0 means this is /// uncatchable. fn capture_rate(&self) -> u8 { self.capture_rate } /// The forms that belong to this Pokemon. fn forms(&self) -> RwLockReadGuard<'_, RawRwLock, HashMap>> { self.forms.read() } /// The arbitrary flags that can be set on a Pokemon for script use. fn flags(&self) -> &HashSet { &self.flags } /// Adds a new form to the species. fn add_form(&self, id: StringKey, form: Arc) { self.forms.write().insert(id, form); } /// Gets a form by name. fn get_form(&self, id: &StringKey) -> Option> { self.forms.read().get(id).cloned() } fn get_form_by_hash(&self, name_hash: u32) -> Option> { self.forms.read().get::(&name_hash).cloned() } /// Gets the form the Pokemon will have by default, if no other form is specified. fn get_default_form(&self) -> Result> { Ok(self .forms .read() .get(&get_default_key()) .ok_or(anyhow!("No default form for species"))? .clone()) } /// Gets a random gender. fn get_random_gender(&self, rand: &mut Random) -> Gender { if self.gender_rate < 0.0 { Gender::Genderless } else if rand.get_float() >= self.gender_rate { Gender::Female } else { Gender::Male } } /// Check whether the Pokemon has a specific flag set. fn has_flag(&self, key: &StringKey) -> bool { self.flags.contains(key) } fn has_flag_by_hash(&self, key_hash: u32) -> bool { self.flags.contains::(&key_hash) } } impl ValueIdentifiable for SpeciesImpl { fn value_identifier(&self) -> ValueIdentifier { self.identifier } } #[cfg(test)] #[allow(clippy::indexing_slicing)] #[allow(clippy::unwrap_used)] pub(crate) mod tests { use super::*; mockall::mock! { #[derive(Debug)] pub Species {} impl Species for Species { fn id(&self) -> u16; fn name(&self) -> &StringKey; fn gender_rate(&self) -> f32; fn growth_rate(&self) -> &StringKey; fn capture_rate(&self) -> u8; fn forms(&self) -> RwLockReadGuard<'_, RawRwLock, HashMap>>; fn flags(&self) -> &HashSet; fn add_form(&self, id: StringKey, form: Arc); fn get_form(&self, id: &StringKey) -> Option>; fn get_form_by_hash(&self, name_hash: u32) -> Option>; fn get_default_form(&self) -> Result>; fn get_random_gender(&self, rand: &mut Random) -> Gender; fn has_flag(&self, key: &StringKey) -> bool; fn has_flag_by_hash(&self, key_hash: u32) -> bool; } impl ValueIdentifiable for Species { fn value_identifier(&self) -> ValueIdentifier { ValueIdentifier::new(0) } } } }