diff --git a/src/dynamic_data/script_handling/item_script.rs b/src/dynamic_data/script_handling/item_script.rs index ffd0309..14459ce 100755 --- a/src/dynamic_data/script_handling/item_script.rs +++ b/src/dynamic_data/script_handling/item_script.rs @@ -1,2 +1,46 @@ +use crate::dynamic_data::Pokemon; +use crate::static_data::EffectParameter; +use std::sync::Arc; + /// The script functions that are relevant to item use. -pub trait ItemScript {} +pub trait ItemScript { + /// Initializes the script with the given parameters for a specific item + fn on_initialize(&self, _pars: Vec>) -> anyhow_ext::Result<()> { + Ok(()) + } + + /// Returns whether the item is usable in the current context. + fn is_item_usable(&self) -> anyhow_ext::Result { + Ok(false) + } + + /// Returns whether the item requires a target to be used. + fn requires_target(&self) -> anyhow_ext::Result { + Ok(false) + } + + /// Returns whether the item is usable on the given target. + fn is_target_valid(&self, _target: &Pokemon) -> anyhow_ext::Result { + Ok(false) + } + + /// Returns whether the item can be held by the given target. + fn is_holdable(&self) -> anyhow_ext::Result { + Ok(false) + } + + /// Returns whether the item can be held by the given target. + fn can_target_hold(&self, _target: &Pokemon) -> anyhow_ext::Result { + Ok(false) + } + + /// Handles the use of the item. + fn on_use(&self) -> anyhow_ext::Result<()> { + Ok(()) + } + + /// Handles the use of the item on the given target. + fn on_use_with_target(&self, _target: &Pokemon) -> anyhow_ext::Result<()> { + Ok(()) + } +} diff --git a/src/script_implementations/wasm/item_script.rs b/src/script_implementations/wasm/item_script.rs new file mode 100644 index 0000000..8bbcba4 --- /dev/null +++ b/src/script_implementations/wasm/item_script.rs @@ -0,0 +1,167 @@ +use crate::dynamic_data::{ItemScript, Pokemon}; +use crate::script_implementations::wasm::export_registry::WasmVoidResultExtension; +use crate::script_implementations::wasm::extern_ref::ExternRef; +use crate::script_implementations::wasm::script_resolver::WebAssemblyEnvironmentData; +use crate::static_data::EffectParameter; +use crate::StringKey; +use std::sync::Arc; + +/// A web assembly implementation of the item script. +pub struct WebAssemblyItemScript { + /// The unique identifier of the script. + name: StringKey, + + /// Pointer inside WebAssembly memory where the data is for this script. + self_ptr: u32, + + /// Cached value for the is_usable function. + is_usable: Option, + + /// Cached value for the requires_target function. + requires_target: Option, + + /// Cached value for the is_holdable function. + is_holdable: Option, + + /// The global runtime environment data. + environment: Arc, +} + +impl WebAssemblyItemScript { + /// Create a new instance of the script. + pub fn new(name: StringKey, self_ptr: u32, environment: Arc) -> Self { + Self { + name, + self_ptr, + is_usable: None, + requires_target: None, + is_holdable: None, + environment, + } + } + + /// Get the name of the script. + pub fn name(&self) -> &StringKey { + &self.name + } +} + +/// Util macro to reduce function call verbosity. +macro_rules! call_func { + ($func:ident, $env:ident, $self:ident $(, $par_name:expr)*) => { + match $func.call(&mut $env.store_mut(), $self.self_ptr $(, $par_name)*) { + Ok(res) => res.into_result_env($env)?, + Err(e) => return Err(e.into()), + }; + } +} + +/// Util macro to reduce extern ref instantiation verbosity. +macro_rules! ex_ref { + ($env:ident, $value:expr) => { + ExternRef::new($env.as_ref(), $value.into()) + }; +} + +impl ItemScript for WebAssemblyItemScript { + fn on_initialize(&self, pars: Vec>) -> anyhow_ext::Result<()> { + let env = &self.environment; + if let Some(func) = env.script_function_cache().item_on_initialize(env) { + let pars = pars + .into_iter() + .map(|p| ExternRef::::new(env, (&p).into()).index() as u32) + .collect::>(); + let wasm_ptr = env.copy_value_vec_to_wasm(&pars)?; + let r: u64 = unsafe { std::mem::transmute((wasm_ptr, pars.len() as u32)) }; + call_func!(func, env, self, r); + } + Ok(()) + } + + fn is_item_usable(&self) -> anyhow_ext::Result { + if let Some(is_usable) = self.is_usable { + return Ok(is_usable); + } + let env = &self.environment; + if let Some(func) = env.script_function_cache().item_is_usable(env) { + let res_ptr = env.allocate_temp::(false); + call_func!(func, env, self, res_ptr.wasm_ptr); + let res = *res_ptr.value(); + let self_mut = unsafe { (self as *const Self as *mut Self).as_mut().unwrap_unchecked() }; + self_mut.is_usable = Some(res); + return Ok(res); + } + Ok(true) + } + + fn requires_target(&self) -> anyhow_ext::Result { + if let Some(requires_target) = self.requires_target { + return Ok(requires_target); + } + let env = &self.environment; + if let Some(func) = env.script_function_cache().item_requires_target(env) { + let res_ptr = env.allocate_temp::(false); + call_func!(func, env, self, res_ptr.wasm_ptr); + let res = *res_ptr.value(); + let self_mut = unsafe { (self as *const Self as *mut Self).as_mut().unwrap_unchecked() }; + self_mut.requires_target = Some(res); + return Ok(res); + } + Ok(false) + } + + fn is_target_valid(&self, target: &Pokemon) -> anyhow::Result { + let env = &self.environment; + if let Some(func) = env.script_function_cache().item_is_target_valid(env) { + let target = ex_ref!(env, target); + let res_ptr = env.allocate_temp::(false); + call_func!(func, env, self, target, res_ptr.wasm_ptr); + return Ok(*res_ptr.value()); + } + Ok(false) + } + + fn is_holdable(&self) -> anyhow::Result { + if let Some(is_holdable) = self.is_holdable { + return Ok(is_holdable); + } + let env = &self.environment; + if let Some(func) = env.script_function_cache().item_is_holdable(env) { + let res_ptr = env.allocate_temp::(false); + call_func!(func, env, self, res_ptr.wasm_ptr); + let res = *res_ptr.value(); + let self_mut = unsafe { (self as *const Self as *mut Self).as_mut().unwrap_unchecked() }; + self_mut.is_holdable = Some(res); + return Ok(res); + } + Ok(false) + } + + fn can_target_hold(&self, target: &Pokemon) -> anyhow::Result { + let env = &self.environment; + if let Some(func) = env.script_function_cache().item_can_target_hold(env) { + let target = ex_ref!(env, target); + let res_ptr = env.allocate_temp::(false); + call_func!(func, env, self, target, res_ptr.wasm_ptr); + return Ok(*res_ptr.value()); + } + Ok(false) + } + + fn on_use(&self) -> anyhow::Result<()> { + let env = &self.environment; + if let Some(func) = env.script_function_cache().item_on_use(env) { + call_func!(func, env, self); + } + Ok(()) + } + + fn on_use_with_target(&self, target: &Pokemon) -> anyhow::Result<()> { + let env = &self.environment; + if let Some(func) = env.script_function_cache().item_on_use_with_target(env) { + let target = ex_ref!(env, target); + call_func!(func, env, self, target); + } + Ok(()) + } +} diff --git a/src/script_implementations/wasm/mod.rs b/src/script_implementations/wasm/mod.rs index 424fd3a..aea948c 100755 --- a/src/script_implementations/wasm/mod.rs +++ b/src/script_implementations/wasm/mod.rs @@ -1,7 +1,9 @@ /// The export registry module deals with registering all functions we require in WebAssembly. mod export_registry; -/// A hacky extern ref implementation to ensure the client does not do things it is not allowed to do. +/// A extern ref implementation to ensure the client does not do things it is not allowed to do. pub(crate) mod extern_ref; +/// The item script module deals with the actual running of WASM functions. +pub mod item_script; /// The script module deals with the actual running of WASM functions. pub mod script; /// A cache of all script functions for easy calls diff --git a/src/script_implementations/wasm/script_function_cache.rs b/src/script_implementations/wasm/script_function_cache.rs index 2bac161..88b0340 100755 --- a/src/script_implementations/wasm/script_function_cache.rs +++ b/src/script_implementations/wasm/script_function_cache.rs @@ -18,7 +18,7 @@ use wasmer::{FromToNativeWasmType, TypedFunction}; macro_rules! script_function_cache { ( $( - $name:ident($($par_type:ty),*$(,)?) + $prefix:ident, $name:ident($($par_type:ty),*$(,)?) )* ) => { #[derive(Default)] @@ -38,7 +38,7 @@ macro_rules! script_function_cache { #[allow(unused_parens)] fn [](&self, env: &Arc) { let exported = env.exported_functions(); - let f = exported.get::(&stringify!([< script_ $name >]).into()); + let f = exported.get::(&stringify!([< $prefix $name >]).into()); if let Some(f) = f { let func: TypedFunction<(u32 $(,$par_type)*), WasmVoidResult> = f.typed(&env.store_ref()).unwrap(); let _ = self.$name.write().insert(func); @@ -121,61 +121,71 @@ unsafe impl FromToNativeWasmType for WasmPtr { } script_function_cache! { - stack() - on_remove() - on_initialize(ExternRef, u64) - on_before_turn(ExternRef) - change_speed(ExternRef, WasmPtr) - change_priority(ExternRef, WasmPtr) - change_move(ExternRef, WasmPtr>) - change_number_of_hits(ExternRef, WasmPtr) - prevent_move(ExternRef, WasmPtr) - fail_move(ExternRef, WasmPtr) - stop_before_move(ExternRef, WasmPtr) - on_before_move(ExternRef) - fail_incoming_move(ExternRef, ExternRef, WasmPtr) - is_invulnerable(ExternRef, ExternRef, WasmPtr) - on_move_miss(ExternRef, ExternRef) - change_move_type(ExternRef, ExternRef, u8, WasmPtr) - change_effectiveness(ExternRef, ExternRef, u8, WasmPtr) - block_critical(ExternRef, ExternRef, u8, WasmPtr) - block_incoming_critical(ExternRef, ExternRef, u8, WasmPtr) - change_accuracy(ExternRef, ExternRef, u8, WasmPtr) - change_critical_stage(ExternRef, ExternRef, u8, WasmPtr) - change_critical_modifier(ExternRef, ExternRef, u8, WasmPtr) - change_stab_modifier(ExternRef, ExternRef, u8, WasmPtr) - change_base_power(ExternRef, ExternRef, u8, WasmPtr) - bypass_defensive_stat_boost(ExternRef, ExternRef, u8, WasmPtr) - bypass_offensive_stat_boost(ExternRef, ExternRef, u8, WasmPtr) - change_offensive_stat_value(ExternRef, ExternRef, u8, WasmPtr) - change_defensive_stat_value(ExternRef, ExternRef, u8, WasmPtr) - change_damage_stat_modifier(ExternRef, ExternRef, u8, WasmPtr) - change_damage_modifier(ExternRef, ExternRef, u8, WasmPtr) - change_damage(ExternRef, ExternRef, u8, WasmPtr) - change_incoming_damage(ExternRef, ExternRef, u8, WasmPtr) - on_incoming_hit(ExternRef, ExternRef, u8) - on_opponent_faints(ExternRef, ExternRef, u8) - prevent_stat_boost_change(ExternRef, u8, i8, u8, WasmPtr) - prevent_secondary_effect(ExternRef, ExternRef, u8, WasmPtr) - change_effect_chance(ExternRef, ExternRef, u8, WasmPtr) - change_incoming_effect_chance(ExternRef, ExternRef, u8, WasmPtr) - on_secondary_effect(ExternRef, ExternRef, u8) - on_after_hits(ExternRef, ExternRef) - prevent_self_switch(ExternRef, WasmPtr) - prevent_opponent_switch(ExternRef, WasmPtr) - on_fail(ExternRef) - on_opponent_fail(ExternRef) - prevent_self_run_away(ExternRef, WasmPtr) - prevent_opponent_run_away(ExternRef, WasmPtr) - on_end_turn() - on_damage(ExternRef, u8, u32, u32) - on_faint(ExternRef, u8) - on_switch_in(ExternRef) - on_after_held_item_consume(ExternRef, ExternRef) - change_experience_gained(ExternRef, ExternRef, WasmPtr) - share_experience(ExternRef, ExternRef, WasmPtr) - block_weather(ExternRef, WasmPtr) - change_capture_rate_bonus(ExternRef, ExternRef, WasmPtr) + script_, stack() + script_, on_remove() + script_, on_initialize(ExternRef, u64) + script_, on_before_turn(ExternRef) + script_, change_speed(ExternRef, WasmPtr) + script_, change_priority(ExternRef, WasmPtr) + script_, change_move(ExternRef, WasmPtr>) + script_, change_number_of_hits(ExternRef, WasmPtr) + script_, prevent_move(ExternRef, WasmPtr) + script_, fail_move(ExternRef, WasmPtr) + script_, stop_before_move(ExternRef, WasmPtr) + script_, on_before_move(ExternRef) + script_, fail_incoming_move(ExternRef, ExternRef, WasmPtr) + script_, is_invulnerable(ExternRef, ExternRef, WasmPtr) + script_, on_move_miss(ExternRef, ExternRef) + script_, change_move_type(ExternRef, ExternRef, u8, WasmPtr) + script_, change_effectiveness(ExternRef, ExternRef, u8, WasmPtr) + script_, block_critical(ExternRef, ExternRef, u8, WasmPtr) + script_, block_incoming_critical(ExternRef, ExternRef, u8, WasmPtr) + script_, change_accuracy(ExternRef, ExternRef, u8, WasmPtr) + script_, change_critical_stage(ExternRef, ExternRef, u8, WasmPtr) + script_, change_critical_modifier(ExternRef, ExternRef, u8, WasmPtr) + script_, change_stab_modifier(ExternRef, ExternRef, u8, WasmPtr) + script_, change_base_power(ExternRef, ExternRef, u8, WasmPtr) + script_, bypass_defensive_stat_boost(ExternRef, ExternRef, u8, WasmPtr) + script_, bypass_offensive_stat_boost(ExternRef, ExternRef, u8, WasmPtr) + script_, change_offensive_stat_value(ExternRef, ExternRef, u8, WasmPtr) + script_, change_defensive_stat_value(ExternRef, ExternRef, u8, WasmPtr) + script_, change_damage_stat_modifier(ExternRef, ExternRef, u8, WasmPtr) + script_, change_damage_modifier(ExternRef, ExternRef, u8, WasmPtr) + script_, change_damage(ExternRef, ExternRef, u8, WasmPtr) + script_, change_incoming_damage(ExternRef, ExternRef, u8, WasmPtr) + script_, on_incoming_hit(ExternRef, ExternRef, u8) + script_, on_opponent_faints(ExternRef, ExternRef, u8) + script_, prevent_stat_boost_change(ExternRef, u8, i8, u8, WasmPtr) + script_, prevent_secondary_effect(ExternRef, ExternRef, u8, WasmPtr) + script_, change_effect_chance(ExternRef, ExternRef, u8, WasmPtr) + script_, change_incoming_effect_chance(ExternRef, ExternRef, u8, WasmPtr) + script_, on_secondary_effect(ExternRef, ExternRef, u8) + script_, on_after_hits(ExternRef, ExternRef) + script_, prevent_self_switch(ExternRef, WasmPtr) + script_, prevent_opponent_switch(ExternRef, WasmPtr) + script_, on_fail(ExternRef) + script_, on_opponent_fail(ExternRef) + script_, prevent_self_run_away(ExternRef, WasmPtr) + script_, prevent_opponent_run_away(ExternRef, WasmPtr) + script_, on_end_turn() + script_, on_damage(ExternRef, u8, u32, u32) + script_, on_faint(ExternRef, u8) + script_, on_switch_in(ExternRef) + script_, on_after_held_item_consume(ExternRef, ExternRef) + script_, change_experience_gained(ExternRef, ExternRef, WasmPtr) + script_, share_experience(ExternRef, ExternRef, WasmPtr) + script_, block_weather(ExternRef, WasmPtr) + script_, change_capture_rate_bonus(ExternRef, ExternRef, WasmPtr) + + // Item script functions + item_script_, item_on_initialize(u64) + item_script_, item_is_usable(WasmPtr) + item_script_, item_requires_target(WasmPtr) + item_script_, item_is_target_valid(ExternRef, WasmPtr) + item_script_, item_is_holdable(WasmPtr) + item_script_, item_can_target_hold(ExternRef, WasmPtr) + item_script_, item_on_use() + item_script_, item_on_use_with_target(ExternRef) } impl ScriptFunctionCache { diff --git a/src/script_implementations/wasm/script_resolver.rs b/src/script_implementations/wasm/script_resolver.rs index 4756553..0489218 100755 --- a/src/script_implementations/wasm/script_resolver.rs +++ b/src/script_implementations/wasm/script_resolver.rs @@ -15,6 +15,7 @@ use wasmer::{ use crate::dynamic_data::{ItemScript, Script, ScriptOwnerData, ScriptResolver}; use crate::script_implementations::wasm::export_registry::{register_webassembly_funcs, WasmObject}; use crate::script_implementations::wasm::extern_ref::ExternRef; +use crate::script_implementations::wasm::item_script::WebAssemblyItemScript; use crate::script_implementations::wasm::script::WebAssemblyScript; use crate::script_implementations::wasm::script_function_cache::ScriptFunctionCache; use crate::script_implementations::wasm::temp_wasm_allocator::{AllocatedObject, TempWasmAllocator}; @@ -35,8 +36,14 @@ pub struct WebAssemblyScriptResolver { /// This is the WASM function to load a script. load_script_fn: Option), u32>>, + /// This is the WASM function to load an item script. + load_item_script_fn: Option, u32>>, + /// The data for use in the scripting function calls. environment_data: Arc, + + /// A cache of all item scripts for faster calls. + item_scripts: RwLock>>, } /// This struct allows us to index a hashmap with both a category and name of a script. @@ -68,7 +75,9 @@ impl WebAssemblyScriptResolver { modules: Default::default(), instances: Default::default(), load_script_fn: None, + load_item_script_fn: None, environment_data: environment, + item_scripts: Default::default(), }; Arc::new(s) @@ -148,6 +157,11 @@ impl WebAssemblyScriptResolver { let self_mut = (self as *const Self as *mut Self).as_mut().unwrap(); self_mut.load_script_fn = Some(f.typed(&self.store_ref())?) } + #[allow(clippy::unwrap_used)] // We know this is valid. + if let Some(f) = exported_functions.get::(&"load_item_script".into()) { + let self_mut = (self as *const Self as *mut Self).as_mut().unwrap(); + self_mut.load_item_script_fn = Some(f.typed(&self.store_ref())?) + } } if let Some(f) = exported_functions.get::(&"allocate_mem".into()) { let _ = self @@ -194,7 +208,30 @@ impl ScriptResolver for WebAssemblyScriptResolver { } fn load_item_script(&self, _key: &dyn Item) -> Result>> { - todo!() + if self.load_item_script_fn.is_none() { + return Ok(None); + } + + if let Some(script) = self.item_scripts.read().get(_key.name()) { + return Ok(Some(script.clone())); + } + let mut item_scripts_write_lock = self.item_scripts.write(); + let script_ptr = self + .load_item_script_fn + .as_ref() + // should never happen, as we check for this above. + .ok_or(anyhow!("Load item script function was not found"))? + .call( + &mut self.store_mut(), + ExternRef::new_with_resolver(self, WasmObject::StringKey(_key.name().clone())), + )?; + let script = Arc::new(WebAssemblyItemScript::new( + _key.name().clone(), + script_ptr, + self.environment_data.clone(), + )); + item_scripts_write_lock.insert(_key.name().clone(), script.clone()); + Ok(Some(script)) } fn as_any(&self) -> &dyn Any { diff --git a/tests/data/gen7_scripts.wasm b/tests/data/gen7_scripts.wasm index 60731f5..86fef6a 100755 Binary files a/tests/data/gen7_scripts.wasm and b/tests/data/gen7_scripts.wasm differ