#![feature(core_panic)] #![feature(alloc_error_handler)] #![feature(fn_traits)] #![feature(const_for)] #![feature(const_mut_refs)] #![feature(inline_const)] #![feature(inline_const_pat)] #![feature(repr128)] #![feature(downcast_unchecked)] #![feature(panic_info_message)] #![feature(wasm_abi)] #![feature(thread_local)] #![feature(build_hasher_simple_hash_one)] #![feature(adt_const_params)] #![cfg_attr(not(feature = "mock_data"), no_std)] #![allow(incomplete_features)] // These give false positives too often #![allow(clippy::borrowed_box)] #![allow(clippy::needless_lifetimes)] extern crate alloc; extern crate core; extern crate dlmalloc; #[global_allocator] static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc {}; pub(crate) use crate::app_interface::StringKey; pub(crate) use crate::handling::extern_ref::*; use alloc::boxed::Box; #[macro_use] #[allow(dead_code)] pub mod app_interface; pub mod handling; pub mod utils; pub type LoadScriptFnType = Box Option>>; #[cfg(not(feature = "mock_data"))] mod implementation { use super::LoadScriptFnType; use crate::app_interface::list::{EffectParameterImmutableList, ImmutableListWasm}; use crate::app_interface::{ BattleImpl, DamageSource, DynamicLibraryImpl, EffectParameter, ExecutingMoveImpl, ItemImpl, PokemonImpl, Statistic, StringKey, TurnChoice, TypeIdentifier, }; use crate::handling::extern_ref::ExternRef; use crate::handling::extern_ref::VecExternRef; use crate::handling::ffi_array::FFIArray; use crate::handling::{Script, ScriptCapabilities, ScriptCategory}; use alloc::boxed::Box; use alloc::rc::Rc; use core::sync::atomic::{AtomicU32, Ordering}; use cstr_core::{c_char, CString}; use hashbrown::HashMap; static mut LOAD_SCRIPT_FN: Option = None; pub fn set_load_script_fn(f: LoadScriptFnType) { unsafe { LOAD_SCRIPT_FN = Some(f); } } macro_rules! exported_functions { ( $( fn $func_name:ident($($par_name:ident: $par_type:ty),*$(,)?) $(-> $return_type:ty)? $func_body:block )* ) => { $( #[no_mangle] unsafe extern "wasm" fn $func_name( $( $par_name: $par_type, )* ) $(-> $return_type)* $func_body )* }; } static mut SCRIPT_PTR_CACHE: Option> = None; static mut SCRIPT_PTR_REVERSE_CACHE: Option>> = None; static mut SCRIPT_INDEX_COUNTER: AtomicU32 = AtomicU32::new(1); #[repr(C)] #[derive(Copy, Clone)] pub struct ScriptPtr { index: u32, } impl ScriptPtr { fn get_cache<'a>() -> &'a mut HashMap<*const dyn Script, u32> { unsafe { if let None = SCRIPT_PTR_CACHE { SCRIPT_PTR_CACHE = Some(hashbrown::HashMap::new()); } let cache = SCRIPT_PTR_CACHE.as_mut().unwrap(); cache } } fn get_reverse_cache<'a>() -> &'a mut HashMap> { unsafe { if let None = SCRIPT_PTR_REVERSE_CACHE { SCRIPT_PTR_REVERSE_CACHE = Some(hashbrown::HashMap::new()); } let cache = SCRIPT_PTR_REVERSE_CACHE.as_mut().unwrap(); cache } } pub fn from_existing(ptr: &dyn Script) -> Self { let cache = Self::get_cache(); let index = *cache.get(&(ptr as *const dyn Script)).unwrap(); Self { index } } pub fn new(ptr: Box) -> Self { unsafe { let cache = Self::get_cache(); let mut index = cache.get(&(ptr.as_ref() as *const dyn Script)).cloned(); if index.is_none() { index = Some(SCRIPT_INDEX_COUNTER.fetch_add(1, Ordering::SeqCst)); let reverse_cache = Self::get_reverse_cache(); reverse_cache.insert(index.unwrap(), ptr); let v = reverse_cache.get(&index.unwrap()).unwrap(); cache.insert(v.as_ref(), index.unwrap()); } Self { index: index.unwrap(), } } } pub fn null() -> Self { Self { index: 0 } } pub fn val<'a, 'b>(&'a self) -> Option<&'b dyn Script> { if self.index == 0 { return None; } let cache = Self::get_reverse_cache(); if let Some(c) = cache.get(&self.index) { Some(c.as_ref()) } else { None } } } exported_functions! { fn load_script(category: ScriptCategory, name: ExternRef) -> ScriptPtr { let name_c = StringKey::new(name); let boxed_script = unsafe { &LOAD_SCRIPT_FN }.as_ref().unwrap()(category, &name_c); if boxed_script.is_none() { return ScriptPtr::null(); } let b = boxed_script.unwrap(); ScriptPtr::new(b) } fn destroy_script(script: *mut Box) { // By turning it from a raw pointer back into a Box with from_raw, we give ownership back to rust. // This lets Rust do the cleanup. let boxed_script = Box::from_raw(script); drop(boxed_script); } fn script_get_name(script: ScriptPtr) -> *mut c_char { let c = script.val().unwrap().get_name(); CString::new(c).unwrap().into_raw() } fn get_script_capabilities(script: ScriptPtr) -> FFIArray { let c = script.val().unwrap().get_capabilities(); FFIArray::new(c) } fn script_stack(script: ScriptPtr) { script.val().unwrap().stack(); } fn script_on_remove(script: ScriptPtr) { script.val().unwrap().on_remove(); } fn script_on_initialize( script: ScriptPtr, library: ExternRef, parameters: VecExternRef, ) { let parameters = Rc::new(EffectParameterImmutableList::from_ref(parameters)); script.val().unwrap().on_initialize(library.not_null_rc(), Some(parameters)); } fn script_on_before_turn( script: ScriptPtr, choice: ExternRef, ) { script.val().unwrap().on_before_turn(choice.not_null()) } fn script_change_speed( script:ScriptPtr, choice: ExternRef, speed: *mut u32, ) { script.val().unwrap().change_speed(choice.not_null(), speed.as_mut().unwrap()) } fn script_change_priority( script: ScriptPtr, choice: ExternRef, priority: *mut i8, ) { script.val().unwrap().change_priority(choice.not_null(), priority.as_mut().unwrap()) } fn script_change_move( script: ScriptPtr, choice: ExternRef, mv: *mut ExternRef, ) { let old = mv.as_ref().unwrap().not_null(); let mut new = old.clone(); script.val().unwrap().change_move(choice.not_null(), &mut new); if old != new { *mv = new.ptr(); } } fn script_change_number_of_hits( script: ScriptPtr, choice: ExternRef, out: *mut u8, ) { script.val().unwrap().change_number_of_hits(choice.not_null(), out.as_mut().unwrap()); } fn script_prevent_move( script: ScriptPtr, mv: ExternRef, out: *mut bool, ) { script.val().unwrap().prevent_move(mv.not_null_rc(), out.as_mut().unwrap()); } fn script_fail_move( script: ScriptPtr, mv: ExternRef, out: *mut bool, ) { script.val().unwrap().fail_move(mv.not_null_rc(), out.as_mut().unwrap()); } fn script_stop_before_move( script: ScriptPtr, mv: ExternRef, out: *mut bool, ) { script.val().unwrap().stop_before_move(mv.not_null_rc(), out.as_mut().unwrap()); } fn script_on_before_move( script: ScriptPtr, mv: ExternRef, ) { script.val().unwrap().on_before_move(mv.not_null_rc()); } fn script_fail_incoming_move( script: ScriptPtr, mv: ExternRef, target: ExternRef, out: *mut bool, ) { script.val().unwrap().fail_incoming_move(mv.not_null_rc(), target.not_null_rc(), out.as_mut().unwrap()); } fn script_is_invulnerable( script: ScriptPtr, mv: ExternRef, target: ExternRef, out: *mut bool, ) { script.val().unwrap().is_invulnerable(mv.not_null_rc(), target.not_null_rc(), out.as_mut().unwrap()); } fn script_on_move_miss( script: ScriptPtr, mv: ExternRef, target: ExternRef, ) { script.val().unwrap().on_move_miss(mv.not_null_rc(), target.not_null_rc()); } fn script_change_move_type( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut TypeIdentifier, ) { script.val().unwrap().change_move_type(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_effectiveness( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut f32, ) { script.val().unwrap().change_effectiveness(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_block_critical( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut bool, ) { script.val().unwrap().block_critical(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_block_incoming_critical( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut bool, ) { script.val().unwrap().block_incoming_critical(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_accuracy( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut u8, ) { script.val().unwrap().change_accuracy(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_critical_stage( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut u8, ) { script.val().unwrap().change_critical_stage(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_critical_modifier( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut f32, ) { script.val().unwrap().change_critical_modifier(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_stab_modifier( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut f32, ) { script.val().unwrap().change_stab_modifier(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_base_power( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut u8, ) { script.val().unwrap().change_base_power(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_bypass_defensive_stat_boost( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut bool, ) { script.val().unwrap().bypass_defensive_stat_boost(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_bypass_offensive_stat_boost( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut bool, ) { script.val().unwrap().bypass_offensive_stat_boost(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_defensive_stat_value( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut u32, ) { script.val().unwrap().change_defensive_stat_value(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_offensive_stat_value( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut u32, ) { script.val().unwrap().change_offensive_stat_value(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_damage_stat_modifier( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut f32, ) { script.val().unwrap().change_damage_stat_modifier(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_damage_modifier( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut f32, ) { script.val().unwrap().change_damage_modifier(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_damage( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut u32, ) { script.val().unwrap().change_damage(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_incoming_damage( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut u32, ) { script.val().unwrap().change_incoming_damage(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_on_incoming_hit( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, ) { script.val().unwrap().on_incoming_hit(mv.not_null_rc(), target.not_null_rc(), hit); } fn script_on_opponent_faints( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, ) { script.val().unwrap().on_opponent_faints(mv.not_null_rc(), target.not_null_rc(), hit); } fn script_prevent_stat_boost_change( script: ScriptPtr, target: ExternRef, stat: Statistic, amount: i8, self_inflicted: u8, out: *mut bool, ) { script.val().unwrap().prevent_stat_boost_change(target.not_null_rc(), stat, amount, self_inflicted == 1, out.as_mut().unwrap()); } fn script_prevent_secondary_effect( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut bool, ) { script.val().unwrap().prevent_secondary_effect(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_effect_chance( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut f32, ) { script.val().unwrap().change_effect_chance(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_change_incoming_effect_chance( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, out: *mut f32, ) { script.val().unwrap().change_incoming_effect_chance(mv.not_null_rc(), target.not_null_rc(), hit, out.as_mut().unwrap()); } fn script_on_secondary_effect( script: ScriptPtr, mv: ExternRef, target: ExternRef, hit: u8, ) { script.val().unwrap().on_secondary_effect(mv.not_null_rc(), target.not_null_rc(), hit); } fn script_on_after_hits( script: ScriptPtr, mv: ExternRef, target: ExternRef, ) { script.val().unwrap().on_after_hits(mv.not_null_rc(), target.not_null_rc()); } fn script_prevent_self_switch( script: ScriptPtr, choice: ExternRef, out: *mut bool, ) { script.val().unwrap().prevent_self_switch(choice.not_null(), out.as_mut().unwrap()); } fn script_prevent_opponent_switch( script: ScriptPtr, choice: ExternRef, out: *mut bool, ) { script.val().unwrap().prevent_opponent_switch(choice.not_null(), out.as_mut().unwrap()); } fn script_on_fail( script: ScriptPtr, pokemon: ExternRef, ) { script.val().unwrap().on_fail(pokemon.not_null_rc()); } fn script_on_opponent_fail( script: ScriptPtr, pokemon: ExternRef, ) { script.val().unwrap().on_opponent_fail(pokemon.not_null_rc()); } fn script_prevent_self_run_away( script: ScriptPtr, choice: ExternRef, out: *mut bool, ) { script.val().unwrap().prevent_self_run_away(choice.not_null(), out.as_mut().unwrap()); } fn script_prevent_opponent_run_away( script: ScriptPtr, choice: ExternRef, out: *mut bool, ) { script.val().unwrap().prevent_opponent_run_away(choice.not_null(), out.as_mut().unwrap()); } fn script_on_end_turn( script: ScriptPtr, ) { script.val().unwrap().on_end_turn(); } fn script_on_damage( script: ScriptPtr, pokemon: ExternRef, source: DamageSource, old_health: u32, new_health: u32, ) { script.val().unwrap().on_damage(pokemon.not_null_rc(), source, old_health, new_health); } fn script_on_faint( script: ScriptPtr, pokemon: ExternRef, source: DamageSource, ) { script.val().unwrap().on_faint(pokemon.not_null_rc(), source); } fn script_on_switch_in( script: ScriptPtr, pokemon: ExternRef, ) { script.val().unwrap().on_switch_in(pokemon.not_null_rc()); } fn script_on_after_held_item_consume( script: ScriptPtr, pokemon: ExternRef, item: ExternRef ) { script.val().unwrap().on_after_held_item_consume(pokemon.not_null_rc(), item.not_null_rc()); } fn script_change_experience_gained( script: ScriptPtr, fainted_pokemon: ExternRef, winning_pokemon: ExternRef, out: *mut u32 ) { script.val().unwrap().change_experience_gained(fainted_pokemon.not_null_rc(), winning_pokemon.not_null_rc(), out.as_mut().unwrap()); } fn script_share_experience( script: ScriptPtr, fainted_pokemon: ExternRef, winning_pokemon: ExternRef, out: *mut bool, ) { script.val().unwrap().share_experience(fainted_pokemon.not_null_rc(), winning_pokemon.not_null_rc(), out.as_mut().unwrap()); } fn script_block_weather( script: ScriptPtr, battle: ExternRef, out: *mut bool, ) { script.val().unwrap().block_weather(battle.not_null_rc(), out.as_mut().unwrap()); } fn script_change_capture_rate_bonus( script: ScriptPtr, target: ExternRef, pokeball: ExternRef, out: *mut u8, ) { script.val().unwrap().change_capture_rate_bonus(target.not_null_rc(), pokeball.not_null_rc(), out.as_mut().unwrap()); } } } use crate::handling::{Script, ScriptCategory}; #[cfg(not(feature = "mock_data"))] pub use implementation::*; #[cfg(feature = "mock_data")] pub fn set_load_script_fn(_f: LoadScriptFnType) {}