Adds support for item use scripts
continuous-integration/drone/push Build was killed Details

This commit is contained in:
Deukhoofd 2023-07-22 21:17:06 +02:00
parent 0c6a0cadfe
commit 3f91f80982
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
6 changed files with 320 additions and 60 deletions

View File

@ -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<Arc<EffectParameter>>) -> anyhow_ext::Result<()> {
Ok(())
}
/// Returns whether the item is usable in the current context.
fn is_item_usable(&self) -> anyhow_ext::Result<bool> {
Ok(false)
}
/// Returns whether the item requires a target to be used.
fn requires_target(&self) -> anyhow_ext::Result<bool> {
Ok(false)
}
/// Returns whether the item is usable on the given target.
fn is_target_valid(&self, _target: &Pokemon) -> anyhow_ext::Result<bool> {
Ok(false)
}
/// Returns whether the item can be held by the given target.
fn is_holdable(&self) -> anyhow_ext::Result<bool> {
Ok(false)
}
/// Returns whether the item can be held by the given target.
fn can_target_hold(&self, _target: &Pokemon) -> anyhow_ext::Result<bool> {
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(())
}
}

View File

@ -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<bool>,
/// Cached value for the requires_target function.
requires_target: Option<bool>,
/// Cached value for the is_holdable function.
is_holdable: Option<bool>,
/// The global runtime environment data.
environment: Arc<WebAssemblyEnvironmentData>,
}
impl WebAssemblyItemScript {
/// Create a new instance of the script.
pub fn new(name: StringKey, self_ptr: u32, environment: Arc<WebAssemblyEnvironmentData>) -> 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<Arc<EffectParameter>>) -> 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::<EffectParameter>::new(env, (&p).into()).index() as u32)
.collect::<Vec<_>>();
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<bool> {
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::<bool>(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<bool> {
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::<bool>(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<bool> {
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::<bool>(false);
call_func!(func, env, self, target, res_ptr.wasm_ptr);
return Ok(*res_ptr.value());
}
Ok(false)
}
fn is_holdable(&self) -> anyhow::Result<bool> {
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::<bool>(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<bool> {
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::<bool>(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(())
}
}

View File

@ -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

View File

@ -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 [<initialize_ $name>](&self, env: &Arc<WebAssemblyEnvironmentData>) {
let exported = env.exported_functions();
let f = exported.get::<StringKey>(&stringify!([< script_ $name >]).into());
let f = exported.get::<StringKey>(&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<T> FromToNativeWasmType for WasmPtr<T> {
}
script_function_cache! {
stack()
on_remove()
on_initialize(ExternRef<dyn DynamicLibrary>, u64)
on_before_turn(ExternRef<TurnChoice>)
change_speed(ExternRef<TurnChoice>, WasmPtr<u32>)
change_priority(ExternRef<TurnChoice>, WasmPtr<i8>)
change_move(ExternRef<TurnChoice>, WasmPtr<ExternRef<StringKey>>)
change_number_of_hits(ExternRef<TurnChoice>, WasmPtr<u8>)
prevent_move(ExternRef<ExecutingMove>, WasmPtr<bool>)
fail_move(ExternRef<ExecutingMove>, WasmPtr<bool>)
stop_before_move(ExternRef<ExecutingMove>, WasmPtr<bool>)
on_before_move(ExternRef<ExecutingMove>)
fail_incoming_move(ExternRef<ExecutingMove>, ExternRef<Pokemon>, WasmPtr<bool>)
is_invulnerable(ExternRef<ExecutingMove>, ExternRef<Pokemon>, WasmPtr<bool>)
on_move_miss(ExternRef<ExecutingMove>, ExternRef<Pokemon>)
change_move_type(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<TypeIdentifier>)
change_effectiveness(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
block_critical(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<bool>)
block_incoming_critical(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<bool>)
change_accuracy(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u8>)
change_critical_stage(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u8>)
change_critical_modifier(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
change_stab_modifier(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
change_base_power(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u8>)
bypass_defensive_stat_boost(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<bool>)
bypass_offensive_stat_boost(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<bool>)
change_offensive_stat_value(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u32>)
change_defensive_stat_value(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u32>)
change_damage_stat_modifier(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
change_damage_modifier(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
change_damage(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u32>)
change_incoming_damage(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u32>)
on_incoming_hit(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8)
on_opponent_faints(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8)
prevent_stat_boost_change(ExternRef<Pokemon>, u8, i8, u8, WasmPtr<bool>)
prevent_secondary_effect(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<bool>)
change_effect_chance(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
change_incoming_effect_chance(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
on_secondary_effect(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8)
on_after_hits(ExternRef<ExecutingMove>, ExternRef<Pokemon>)
prevent_self_switch(ExternRef<TurnChoice>, WasmPtr<bool>)
prevent_opponent_switch(ExternRef<TurnChoice>, WasmPtr<bool>)
on_fail(ExternRef<Pokemon>)
on_opponent_fail(ExternRef<Pokemon>)
prevent_self_run_away(ExternRef<TurnChoice>, WasmPtr<bool>)
prevent_opponent_run_away(ExternRef<TurnChoice>, WasmPtr<bool>)
on_end_turn()
on_damage(ExternRef<Pokemon>, u8, u32, u32)
on_faint(ExternRef<Pokemon>, u8)
on_switch_in(ExternRef<Pokemon>)
on_after_held_item_consume(ExternRef<Pokemon>, ExternRef<dyn Item>)
change_experience_gained(ExternRef<Pokemon>, ExternRef<Pokemon>, WasmPtr<u32>)
share_experience(ExternRef<Pokemon>, ExternRef<Pokemon>, WasmPtr<bool>)
block_weather(ExternRef<Battle>, WasmPtr<bool>)
change_capture_rate_bonus(ExternRef<Pokemon>, ExternRef<dyn Item>, WasmPtr<u8>)
script_, stack()
script_, on_remove()
script_, on_initialize(ExternRef<dyn DynamicLibrary>, u64)
script_, on_before_turn(ExternRef<TurnChoice>)
script_, change_speed(ExternRef<TurnChoice>, WasmPtr<u32>)
script_, change_priority(ExternRef<TurnChoice>, WasmPtr<i8>)
script_, change_move(ExternRef<TurnChoice>, WasmPtr<ExternRef<StringKey>>)
script_, change_number_of_hits(ExternRef<TurnChoice>, WasmPtr<u8>)
script_, prevent_move(ExternRef<ExecutingMove>, WasmPtr<bool>)
script_, fail_move(ExternRef<ExecutingMove>, WasmPtr<bool>)
script_, stop_before_move(ExternRef<ExecutingMove>, WasmPtr<bool>)
script_, on_before_move(ExternRef<ExecutingMove>)
script_, fail_incoming_move(ExternRef<ExecutingMove>, ExternRef<Pokemon>, WasmPtr<bool>)
script_, is_invulnerable(ExternRef<ExecutingMove>, ExternRef<Pokemon>, WasmPtr<bool>)
script_, on_move_miss(ExternRef<ExecutingMove>, ExternRef<Pokemon>)
script_, change_move_type(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<TypeIdentifier>)
script_, change_effectiveness(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
script_, block_critical(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<bool>)
script_, block_incoming_critical(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<bool>)
script_, change_accuracy(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u8>)
script_, change_critical_stage(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u8>)
script_, change_critical_modifier(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
script_, change_stab_modifier(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
script_, change_base_power(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u8>)
script_, bypass_defensive_stat_boost(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<bool>)
script_, bypass_offensive_stat_boost(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<bool>)
script_, change_offensive_stat_value(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u32>)
script_, change_defensive_stat_value(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u32>)
script_, change_damage_stat_modifier(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
script_, change_damage_modifier(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
script_, change_damage(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u32>)
script_, change_incoming_damage(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<u32>)
script_, on_incoming_hit(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8)
script_, on_opponent_faints(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8)
script_, prevent_stat_boost_change(ExternRef<Pokemon>, u8, i8, u8, WasmPtr<bool>)
script_, prevent_secondary_effect(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<bool>)
script_, change_effect_chance(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
script_, change_incoming_effect_chance(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8, WasmPtr<f32>)
script_, on_secondary_effect(ExternRef<ExecutingMove>, ExternRef<Pokemon>, u8)
script_, on_after_hits(ExternRef<ExecutingMove>, ExternRef<Pokemon>)
script_, prevent_self_switch(ExternRef<TurnChoice>, WasmPtr<bool>)
script_, prevent_opponent_switch(ExternRef<TurnChoice>, WasmPtr<bool>)
script_, on_fail(ExternRef<Pokemon>)
script_, on_opponent_fail(ExternRef<Pokemon>)
script_, prevent_self_run_away(ExternRef<TurnChoice>, WasmPtr<bool>)
script_, prevent_opponent_run_away(ExternRef<TurnChoice>, WasmPtr<bool>)
script_, on_end_turn()
script_, on_damage(ExternRef<Pokemon>, u8, u32, u32)
script_, on_faint(ExternRef<Pokemon>, u8)
script_, on_switch_in(ExternRef<Pokemon>)
script_, on_after_held_item_consume(ExternRef<Pokemon>, ExternRef<dyn Item>)
script_, change_experience_gained(ExternRef<Pokemon>, ExternRef<Pokemon>, WasmPtr<u32>)
script_, share_experience(ExternRef<Pokemon>, ExternRef<Pokemon>, WasmPtr<bool>)
script_, block_weather(ExternRef<Battle>, WasmPtr<bool>)
script_, change_capture_rate_bonus(ExternRef<Pokemon>, ExternRef<dyn Item>, WasmPtr<u8>)
// Item script functions
item_script_, item_on_initialize(u64)
item_script_, item_is_usable(WasmPtr<bool>)
item_script_, item_requires_target(WasmPtr<bool>)
item_script_, item_is_target_valid(ExternRef<Pokemon>, WasmPtr<bool>)
item_script_, item_is_holdable(WasmPtr<bool>)
item_script_, item_can_target_hold(ExternRef<Pokemon>, WasmPtr<bool>)
item_script_, item_on_use()
item_script_, item_on_use_with_target(ExternRef<Pokemon>)
}
impl ScriptFunctionCache {

View File

@ -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<TypedFunction<(u8, ExternRef<StringKey>), u32>>,
/// This is the WASM function to load an item script.
load_item_script_fn: Option<TypedFunction<ExternRef<StringKey>, u32>>,
/// The data for use in the scripting function calls.
environment_data: Arc<WebAssemblyEnvironmentData>,
/// A cache of all item scripts for faster calls.
item_scripts: RwLock<HashMap<StringKey, Arc<dyn ItemScript>>>,
}
/// 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::<StringKey>(&"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::<StringKey>(&"allocate_mem".into()) {
let _ = self
@ -194,7 +208,30 @@ impl ScriptResolver for WebAssemblyScriptResolver {
}
fn load_item_script(&self, _key: &dyn Item) -> Result<Option<Arc<dyn ItemScript>>> {
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 {

Binary file not shown.