diff --git a/Cargo.toml b/Cargo.toml index a11e605..fbedaf9 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ unique-type-id = { version = "1.0.0", optional = true } unique-type-id-derive = { version = "1.0.0", optional = true } paste = { version = "1.0.8" } arcstr = { version = "1.1.4", features = ["std"] } +enum-display-derive = "0.1.1" [dev-dependencies] csv = "1.1.6" diff --git a/src/dynamic_data/event_hooks.rs b/src/dynamic_data/event_hooks.rs index 9adb531..a706c83 100755 --- a/src/dynamic_data/event_hooks.rs +++ b/src/dynamic_data/event_hooks.rs @@ -1,3 +1,4 @@ +use parking_lot::RwLock; use std::fmt::{Debug, Formatter}; use std::sync::Arc; @@ -7,6 +8,11 @@ use crate::dynamic_data::Pokemon; use crate::static_data::Species; use crate::static_data::{Form, Statistic}; +/// A function that will be called when an event occured. +type EvtHookFn = Box)>; +/// A collection of event hooks. +type EvtHookCollection = Vec; + /// The event hook is used to store external functions that listen to events. /// /// Events happen in many @@ -15,22 +21,22 @@ use crate::static_data::{Form, Statistic}; #[derive(Default)] pub struct EventHook { /// All the registered event listeners on the hook. - evt_hook_function: Vec)>, + evt_hook_function: RwLock, } impl EventHook { /// Register a new listener. This will start receiving all events in the battle. Multiple event /// listeners can exist at the same time. Note that for these functions the event will be disposed /// of after the event is finished being sent. - pub fn register_listener(&mut self, func: fn(&Box<&Event>)) { - self.evt_hook_function.push(func); + pub fn register_listener(&self, func: EvtHookFn) { + self.evt_hook_function.write().push(func); } /// Run a new event. This will send the event to all externally defined event listeners. It will /// dispose of the event afterwards. pub fn trigger(&self, evt: Event) { let b = Box::new(&evt); - for f in &self.evt_hook_function { + for f in self.evt_hook_function.read().iter() { f(&b); } } diff --git a/src/dynamic_data/libraries/script_resolver.rs b/src/dynamic_data/libraries/script_resolver.rs index e494488..141ddd9 100755 --- a/src/dynamic_data/libraries/script_resolver.rs +++ b/src/dynamic_data/libraries/script_resolver.rs @@ -24,11 +24,12 @@ pub trait ScriptResolver: Debug + ValueIdentifiable { fn load_item_script(&self, _key: &Item) -> PkmnResult>>; } +use std::fmt::Display; /// A script category defines a sub-group of scripts. This can be used to have multiple scripts with /// the same name, but a different script. It should be completely valid for a move to have the same /// name as an ability, or more commonly: for a script attached to a Pokemon to have the same name as /// a move that placed it there. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Display)] #[repr(u8)] pub enum ScriptCategory { /// A script that belongs to a move. This generally is only the script that is attached to a diff --git a/src/dynamic_data/models/battle.rs b/src/dynamic_data/models/battle.rs index a1db3c8..5d580a9 100755 --- a/src/dynamic_data/models/battle.rs +++ b/src/dynamic_data/models/battle.rs @@ -158,7 +158,7 @@ impl Battle { pub fn current_turn(&self) -> u32 { self.current_turn.load(Ordering::Relaxed) } - /// The time the last turn took to run. Defaults to 0. + /// The time in nanoseconds the last turn took to run. Defaults to 0. pub fn last_turn_time(&self) -> u64 { self.last_turn_time.load(Ordering::Relaxed) } @@ -261,7 +261,7 @@ impl Battle { } /// Try and set the choice for the battle. If the choice is not valid, this returns false. - pub fn try_set_choice(&mut self, choice: TurnChoice) -> PkmnResult { + pub fn try_set_choice(&self, choice: TurnChoice) -> PkmnResult { if !self.can_use(&choice) { return Ok(false); } @@ -399,7 +399,7 @@ impl ValueIdentifiable for Battle { } /// The result of a battle. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum BattleResult { /// The battle has no winner. Either the battle has not ended, or everyone is dead, or one of /// the parties has ran away. diff --git a/src/dynamic_data/models/battle_random.rs b/src/dynamic_data/models/battle_random.rs index 073ee9b..1ccbcb5 100755 --- a/src/dynamic_data/models/battle_random.rs +++ b/src/dynamic_data/models/battle_random.rs @@ -4,13 +4,15 @@ use std::sync::{Arc, Mutex}; use crate::dynamic_data::models::executing_move::ExecutingMove; use crate::dynamic_data::models::pokemon::Pokemon; use crate::dynamic_data::script_handling::ScriptSource; -use crate::script_hook; use crate::utils::Random; +use crate::{script_hook, ValueIdentifiable, ValueIdentifier}; /// The RNG for a battle. #[derive(Default)] #[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct BattleRandom { + /// A unique identifier so we know what value this is. + identifier: ValueIdentifier, /// The actual underlying RNG. This is in a mutex, so it is thread safe, and can be ran /// predictably, with guaranteed the same outputs. random: Mutex, @@ -20,6 +22,7 @@ impl BattleRandom { /// Initializes a new RNG with a given seed. pub fn new_with_seed(seed: u128) -> Self { BattleRandom { + identifier: Default::default(), random: Mutex::new(Random::new(seed)), } } @@ -89,7 +92,14 @@ impl Debug for BattleRandom { impl Clone for BattleRandom { fn clone(&self) -> Self { Self { + identifier: Default::default(), random: Mutex::new(self.random.lock().unwrap().clone()), } } } + +impl ValueIdentifiable for BattleRandom { + fn value_identifier(&self) -> ValueIdentifier { + self.identifier + } +} diff --git a/src/dynamic_data/models/battle_side.rs b/src/dynamic_data/models/battle_side.rs index 5ed3136..5e3bc38 100755 --- a/src/dynamic_data/models/battle_side.rs +++ b/src/dynamic_data/models/battle_side.rs @@ -14,12 +14,14 @@ use crate::dynamic_data::script_handling::{ScriptSource, ScriptSourceData, Scrip use crate::dynamic_data::Script; use crate::dynamic_data::ScriptSet; use crate::dynamic_data::VolatileScriptsOwner; -use crate::{script_hook, PkmnResult, StringKey}; +use crate::{script_hook, PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier}; /// A side on a battle. #[derive(Debug)] #[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct BattleSide { + /// A unique identifier so we know what value this is. + identifier: ValueIdentifier, /// The index of the side on the battle. index: u8, /// The number of Pokemon that can be on the side. @@ -60,6 +62,7 @@ impl BattleSide { let pokemon = RwLock::new(pokemon); Self { + identifier: Default::default(), index, pokemon_per_side, pokemon, @@ -322,3 +325,9 @@ impl ScriptSource for BattleSide { self.battle().collect_scripts(scripts); } } + +impl ValueIdentifiable for BattleSide { + fn value_identifier(&self) -> ValueIdentifier { + self.identifier + } +} diff --git a/src/ffi/dynamic_data/models/battle.rs b/src/ffi/dynamic_data/models/battle.rs new file mode 100644 index 0000000..3e7a952 --- /dev/null +++ b/src/ffi/dynamic_data/models/battle.rs @@ -0,0 +1,210 @@ +use crate::dynamic_data::{ + Battle, BattleParty, BattleRandom, BattleResult, BattleSide, DynamicLibrary, Pokemon, TurnChoice, +}; +use crate::ffi::dynamic_data::models::native_event_hook::NativeEventHook; +use crate::ffi::{ExternPointer, IdentifiablePointer, OwnedPtr}; +use std::ffi::{c_char, CStr, CString}; +use std::sync::Arc; + +/// Initializes a new battle. +#[no_mangle] +extern "C" fn battle_new( + library: ExternPointer>, + parties: *const OwnedPtr, + parties_length: usize, + can_flee: u8, + number_of_sides: u8, + pokemon_per_side: u8, + // NOTE: Split into two due to u128 not being ABI safe: https://github.com/rust-lang/rust/issues/54341 + random_seed_1: u64, + random_seed_2: u64, +) -> IdentifiablePointer { + let parties = unsafe { + std::slice::from_raw_parts(parties, parties_length) + .iter() + .map(|x| *Box::from_raw(*x)) + .collect() + }; + + let random_seed = if cfg!(target_endian = "big") { + ((random_seed_1 as u128) << 64) + (random_seed_2 as u128) + } else { + (random_seed_1 as u128) + ((random_seed_2 as u128) << 64) + }; + let random_seed = if random_seed == 0 { None } else { Some(random_seed) }; + + Box::new(Battle::new( + library.as_ref().clone(), + parties, + can_flee == 1, + number_of_sides, + pokemon_per_side, + random_seed, + )) + .into() +} + +/// The library the battle uses for handling. +#[no_mangle] +extern "C" fn battle_library(ptr: ExternPointer>) -> IdentifiablePointer> { + ptr.as_ref().library().clone().into() +} + +/// The length of the list of all different parties in the battle. +#[no_mangle] +extern "C" fn battle_parties_length(ptr: ExternPointer>) -> usize { + ptr.as_ref().parties().len() +} + +/// Get a party in the battle. +#[no_mangle] +extern "C" fn battle_parties_get(ptr: ExternPointer>, index: usize) -> IdentifiablePointer { + if let Some(v) = ptr.as_ref().parties().get(index) { + (v as *const BattleParty).into() + } else { + IdentifiablePointer::none() + } +} + +/// Whether or not Pokemon can flee from the battle. +#[no_mangle] +extern "C" fn battle_can_flee(ptr: ExternPointer>) -> u8 { + u8::from(ptr.as_ref().can_flee()) +} + +/// The number of sides in the battle. Typically 2. +#[no_mangle] +extern "C" fn battle_number_of_sides(ptr: ExternPointer>) -> u8 { + ptr.as_ref().number_of_sides() +} + +/// The number of Pokemon that can be on each side. +#[no_mangle] +extern "C" fn battle_pokemon_per_side(ptr: ExternPointer>) -> u8 { + ptr.as_ref().pokemon_per_side() +} + +/// The length of the list of all different sides in the battle. +#[no_mangle] +extern "C" fn battle_sides_length(ptr: ExternPointer>) -> usize { + ptr.as_ref().sides().len() +} + +/// Get a side in the battle. +#[no_mangle] +extern "C" fn battle_sides_get(ptr: ExternPointer>, index: usize) -> IdentifiablePointer { + if let Some(v) = ptr.as_ref().sides().get(index) { + (v as *const BattleSide).into() + } else { + IdentifiablePointer::none() + } +} + +/// The RNG used for the battle. +#[no_mangle] +extern "C" fn battle_random(ptr: ExternPointer>) -> IdentifiablePointer { + (ptr.as_ref().random() as *const BattleRandom).into() +} + +/// Whether or not the battle has ended. +#[no_mangle] +extern "C" fn battle_has_ended(ptr: ExternPointer>) -> u8 { + u8::from(ptr.as_ref().has_ended()) +} + +/// Whether or not we have a conclusive winner +#[no_mangle] +extern "C" fn battle_has_conclusive_result(ptr: ExternPointer>) -> u8 { + u8::from(ptr.as_ref().result() != BattleResult::Inconclusive) +} + +/// If we have a conclusive winner, the side that has won. If we don't have a conclusive winner, this +/// always returns 0. +#[no_mangle] +extern "C" fn battle_winning_side(ptr: ExternPointer>) -> u8 { + if let BattleResult::Conclusive(winner) = ptr.as_ref().result() { + winner + } else { + 0 + } +} + +/// Register a function to be triggered when an event in a battle occurs. +#[no_mangle] +extern "C" fn battle_register_event_hook(ptr: ExternPointer>, f: NativeEventHook) { + ptr.as_ref().event_hook().register_listener(Box::new(f)) +} + +/// The index of the current turn. 0 until all choices +#[no_mangle] +extern "C" fn battle_current_turn(ptr: ExternPointer>) -> u32 { + ptr.as_ref().current_turn() +} + +/// The time in nanoseconds the last turn took to run. Defaults to 0. +#[no_mangle] +extern "C" fn battle_last_turn_time(ptr: ExternPointer>) -> u64 { + ptr.as_ref().last_turn_time() +} + +/// Get a Pokemon on the battlefield, on a specific side and an index on that side. +#[no_mangle] +extern "C" fn battle_get_pokemon( + ptr: ExternPointer>, + side: u8, + index: u8, +) -> IdentifiablePointer> { + if let Some(v) = ptr.as_ref().get_pokemon(side, index) { + v.into() + } else { + IdentifiablePointer::none() + } +} + +/// Returns whether a slot on the battlefield can still be filled. If no party is responsible +/// for that slot, or a party is responsible, but has no remaining Pokemon to throw out anymore, +/// this returns false. +#[no_mangle] +extern "C" fn battle_can_slot_be_filled(ptr: ExternPointer>, side: u8, index: u8) -> u8 { + u8::from(ptr.as_ref().can_slot_be_filled(side, index)) +} + +/// Checks whether a choice is actually possible. +#[no_mangle] +extern "C" fn battle_can_use(ptr: ExternPointer>, choice: ExternPointer) -> u8 { + u8::from(ptr.as_ref().can_use(choice.as_ref())) +} + +/// Checks to see whether all Pokemon on the field have set their choices. If so, we then run +/// the turn. +#[no_mangle] +extern "C" fn battle_try_set_choice(ptr: ExternPointer>, choice: OwnedPtr) -> u8 { + let choice = unsafe { choice.read() }; + let result = ptr.as_ref().try_set_choice(choice); + match result { + Ok(b) => u8::from(b), + Err(e) => { + panic!("Encountered error: {}", e) + } + } +} + +/// Sets the current weather for the battle. If nullptr is passed, this clears the weather. +#[no_mangle] +extern "C" fn battle_set_weather(ptr: ExternPointer>, weather: *const c_char) { + if weather.is_null() { + ptr.as_ref().set_weather(None) + } else { + unsafe { ptr.as_ref().set_weather(Some(CStr::from_ptr(weather).into())) } + } +} + +/// Gets the current weather of the battle. If no weather is present, this returns nullptr. +#[no_mangle] +extern "C" fn battle_weather_name(ptr: ExternPointer>) -> *mut c_char { + if let Some(w) = ptr.as_ref().weather_name() { + CString::new(w.str()).unwrap().into_raw() + } else { + std::ptr::null_mut() + } +} diff --git a/src/ffi/dynamic_data/models/mod.rs b/src/ffi/dynamic_data/models/mod.rs index 089d13a..344c989 100644 --- a/src/ffi/dynamic_data/models/mod.rs +++ b/src/ffi/dynamic_data/models/mod.rs @@ -1,7 +1,11 @@ +/// The foreign function interface for a battle. +mod battle; /// The foreign function interface for a battle wrapper of a party. mod battle_party; /// The foreign function interface for a Learned Move. mod learned_move; +/// Wrapper classed for the event hooks. +mod native_event_hook; /// The foreign function interface for a Pokemon. mod pokemon; /// The foreign function interface for a party of Pokemon. diff --git a/src/ffi/dynamic_data/models/native_event_hook.rs b/src/ffi/dynamic_data/models/native_event_hook.rs new file mode 100644 index 0000000..617113a --- /dev/null +++ b/src/ffi/dynamic_data/models/native_event_hook.rs @@ -0,0 +1,38 @@ +use crate::dynamic_data::Event; + +/// Wrapper class for easier use of an external function pointer. +#[repr(C)] +pub(super) struct NativeEventHook { + /// The actual C function to be called. + f: extern "C" fn(*const Event), +} + +impl NativeEventHook { + /// Calls the actual wrapped function in the correct format. + fn call_self(&self, b: &&Event<'_>) { + (self.f)(*b as *const Event) + } +} + +/// A tuple with the arguments of the event hook function +type EventHookArgs<'a, 'b, 'c> = (&'a Box<&'b Event<'c>>,); + +impl FnMut> for NativeEventHook { + extern "rust-call" fn call_mut(&mut self, args: EventHookArgs<'_, '_, '_>) -> Self::Output { + self.call_self(args.0) + } +} + +impl FnOnce> for NativeEventHook { + type Output = (); + + extern "rust-call" fn call_once(self, args: EventHookArgs<'_, '_, '_>) -> Self::Output { + self.call_self(args.0) + } +} + +impl Fn> for NativeEventHook { + extern "rust-call" fn call(&self, args: EventHookArgs<'_, '_, '_>) -> Self::Output { + self.call_self(args.0) + } +} diff --git a/src/lib.rs b/src/lib.rs index 5428696..af88a08 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,8 @@ #![feature(new_uninit)] #![feature(get_mut_unchecked)] #![feature(strict_provenance)] +#![feature(fn_traits)] +#![feature(unboxed_closures)] //! PkmnLib //! PkmnLib is a full featured implementation of Pokemon. while currently focused on implementing @@ -17,6 +19,12 @@ //! to a scripting library. //! +extern crate core; + +#[macro_use] +extern crate enum_display_derive; + +use std::fmt::{Display, Formatter}; #[doc(hidden)] pub use utils::*; @@ -57,3 +65,19 @@ pub enum PokemonError { /// A simple result type. pub type PkmnResult = Result; + +impl Display for PokemonError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PokemonError::ScriptNotFound { category, name } => { + write!(f, "No script found with category `{}` and name `{}`", category, name) + } + PokemonError::InvalidTargetRequested => { + write!(f, "Invalid target was requested") + } + PokemonError::MiscError => { + write!(f, "An unknown error occurred") + } + } + } +}