Support for item use scripts
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Deukhoofd 2023-07-22 21:16:19 +02:00
parent 48ff2c4536
commit e44ddf18a2
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
10 changed files with 388 additions and 72 deletions

View File

@ -10,6 +10,7 @@ fn main() {
use alloc::boxed::Box;
use pkmn_lib_interface::app_interface::{{get_hash, StringKey}};
use pkmn_lib_interface::handling::{{Script, ScriptCategory}};
use pkmn_lib_interface::handling::item_script::ItemScript;
macro_rules! resolve_match {{
(
@ -56,6 +57,8 @@ pub fn get_script(category: ScriptCategory, name: &StringKey) -> Option<Box<dyn
"#
)
.unwrap();
write_item_scripts(&mut output_file);
}
fn write_category(path: &str, category: &str, output_file: &mut std::fs::File) {
@ -122,3 +125,59 @@ fn write_scripts(path: &str, output_file: &mut std::fs::File) {
}
}
}
fn write_item_scripts(output_file: &mut std::fs::File) {
writeln!(
output_file,
r"
pub fn get_item_script(name: &StringKey) -> Option<Box<dyn ItemScript>> {{
resolve_match! {{
name.hash().unwrap(),"
)
.unwrap();
let item_files = std::fs::read_dir("src/items")
.unwrap()
.map(|f| f.unwrap().path())
.filter(|f| f.extension().unwrap() == "rs")
.map(|f| f.file_stem().unwrap().to_str().unwrap().to_string())
.collect::<Vec<_>>();
for file in item_files {
println!("cargo:rerun-if-changed=src/items/{}.rs", file);
let parsed =
syn::parse_file(&std::fs::read_to_string(format!("src/items/{}.rs", file)).unwrap())
.unwrap();
// Now we need to find every impl ItemScript for X { ... } block inside parsed
let script_impls = parsed
.items
.iter()
.filter_map(|item| match item {
syn::Item::Impl(impl_block) => match impl_block.trait_ {
Some((_, ref path, _)) => {
if path.segments[0].ident == "ItemScript" {
Some(impl_block)
} else {
None
}
}
None => None,
},
_ => None,
})
.collect::<Vec<_>>();
for script_impl in script_impls {
if let syn::Type::Path(p) = script_impl.self_ty.as_ref() {
let ident = p.path.segments[0].ident.to_string();
writeln!(output_file, " crate::items::{}::{},", file, ident).unwrap();
}
}
}
writeln!(
output_file,
r" }}
None
}}"
)
.unwrap();
}

View File

@ -0,0 +1,40 @@
use crate::common_usings::*;
use pkmn_lib_interface::handling::item_script::ItemScript;
script!(
HealingItem,
"healing_item",
amount: AtomicU32
);
impl ItemScript for HealingItem {
fn new() -> Self
where
Self: Sized,
{
Self {
amount: Default::default(),
}
}
fn get_name(&self) -> &'static str {
Self::get_const_name()
}
fn is_item_usable(&self) -> PkmnResult<bool> {
Ok(true)
}
fn requires_target(&self) -> PkmnResult<bool> {
Ok(true)
}
fn is_target_valid(&self, target: &Pokemon) -> PkmnResult<bool> {
Ok(!target.is_fainted()?)
}
fn on_use_with_target(&self, target: &Pokemon) -> PkmnResult<()> {
target.heal(self.amount.load(Ordering::Relaxed), false)?;
Ok(())
}
}

View File

@ -0,0 +1 @@
pub mod healing_item;

View File

@ -27,6 +27,7 @@ pub mod status;
pub mod util_scripts;
pub(crate) mod utils;
pub mod weather;
pub mod items;
#[no_mangle]
#[cfg(not(test))]

View File

@ -3,6 +3,7 @@
use alloc::boxed::Box;
use pkmn_lib_interface::app_interface::{get_hash, StringKey};
use pkmn_lib_interface::handling::{Script, ScriptCategory};
use pkmn_lib_interface::handling::item_script::ItemScript;
macro_rules! resolve_match {
(
@ -94,4 +95,11 @@ pub fn get_script(category: ScriptCategory, name: &StringKey) -> Option<Box<dyn
}
None
}
pub fn get_item_script(name: &StringKey) -> Option<Box<dyn ItemScript>> {
resolve_match! {
name.hash().unwrap(),
crate::items::healing_item::HealingItem,
}
None
}

View File

@ -1,20 +1,15 @@
/// The time of day. These values are the 4 different groups of time of day in Pokemon games since
/// gen 5. The exact times these correspond to differ between games.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(u8)]
pub enum TimeOfDay {
/// The morning.
Morning = 0,
/// The day.
#[default]
Day = 1,
/// The evening.
Evening = 2,
/// The night.
Night = 3,
}
impl Default for TimeOfDay {
fn default() -> Self {
TimeOfDay::Day
}
}

View File

@ -0,0 +1,55 @@
use crate::app_interface::{EffectParameter, Pokemon};
use alloc::rc::Rc;
use alloc::vec::Vec;
type Result<T> = crate::result::PkmnResult<T>;
pub trait ItemScript {
fn new() -> Self
where
Self: Sized;
fn get_name(&self) -> &'static str;
/// Initializes the script with the given parameters for a specific item
fn on_initialize(&self, _pars: Option<Vec<Rc<EffectParameter>>>) -> Result<()> {
Ok(())
}
/// Returns whether the item is usable in the current context.
/// Note that the host caches this value, so it will not be called again
fn is_item_usable(&self) -> Result<bool> {
Ok(false)
}
/// Returns whether the item requires a target to be used.
/// Note that the host caches this value, so it will not be called again
fn requires_target(&self) -> Result<bool> {
Ok(false)
}
/// Returns whether the item can be held.
/// Note that the host caches this value, so it will not be called again
fn is_holdable(&self) -> Result<bool> {
Ok(false)
}
/// Returns whether the item is usable on the given target.
fn is_target_valid(&self, _target: &Pokemon) -> Result<bool> {
Ok(false)
}
/// Returns whether the item can be held by the given target.
fn can_target_hold(&self, _target: &Pokemon) -> Result<bool> {
Ok(false)
}
/// Handles the use of the item.
fn on_use(&self) -> Result<()> {
Ok(())
}
/// Handles the use of the item on the given target.
fn on_use_with_target(&self, _target: &Pokemon) -> Result<()> {
Ok(())
}
}

View File

@ -5,6 +5,7 @@ pub(crate) mod cached_value;
pub mod capabilities;
pub mod extern_ref;
pub mod ffi_array;
pub mod item_script;
pub mod script;
#[cfg(not(feature = "mock_data"))]
pub(crate) mod temporary;

View File

@ -496,7 +496,7 @@ pub enum ScriptOwner {
impl ScriptOwner {
#[cfg(not(feature = "mock_data"))]
fn from_script(script: &dyn Script) -> Option<ScriptOwner> {
fn from_script<'a>(script: &'a dyn Script) -> Option<ScriptOwner> {
let script_ptr = crate::implementation::ScriptPtr::from_existing(script);
unsafe {
let kind = script_get_owner_kind(script_ptr);

View File

@ -42,17 +42,19 @@ pub mod utils;
pub use result::*;
pub type LoadScriptFnType = Box<dyn Fn(ScriptCategory, &StringKey) -> Option<Box<dyn Script>>>;
pub type LoadItemScriptFnType = Box<dyn Fn(&StringKey) -> Option<Box<dyn ItemScript>>>;
#[cfg(not(feature = "mock_data"))]
mod implementation {
use super::LoadScriptFnType;
use super::{LoadItemScriptFnType, LoadScriptFnType};
use crate::app_interface::{
BattleImpl, DamageSource, DynamicLibraryImpl, EffectParameter, ExecutingMoveImpl, ItemImpl,
PokemonImpl, Statistic, StringKey, TurnChoice, TypeIdentifier,
Pokemon, PokemonImpl, Statistic, StringKey, TurnChoice, TypeIdentifier,
};
use crate::handling::extern_ref::ExternRef;
use crate::handling::ffi_array::FFIArray;
use crate::handling::item_script::ItemScript;
use crate::handling::wasm_result::WasmVoidResult;
use crate::handling::{Script, ScriptCapabilities, ScriptCategory};
use alloc::boxed::Box;
@ -63,6 +65,7 @@ mod implementation {
use hashbrown::HashMap;
static mut LOAD_SCRIPT_FN: Option<LoadScriptFnType> = None;
static mut LOAD_ITEM_SCRIPT_FN: Option<LoadItemScriptFnType> = None;
pub fn set_load_script_fn(f: LoadScriptFnType) {
unsafe {
@ -70,6 +73,12 @@ mod implementation {
}
}
pub fn set_load_item_script_fn(f: LoadItemScriptFnType) {
unsafe {
LOAD_ITEM_SCRIPT_FN = Some(f);
}
}
macro_rules! exported_functions {
(
$(
@ -91,74 +100,104 @@ mod implementation {
static mut SCRIPT_PTR_REVERSE_CACHE: Option<HashMap<u32, Box<dyn Script>>> = None;
static mut SCRIPT_INDEX_COUNTER: AtomicU32 = AtomicU32::new(1);
#[repr(C)]
static mut ITEM_SCRIPT_PTR_CACHE: Option<HashMap<*const dyn ItemScript, u32>> = None;
static mut ITEM_SCRIPT_PTR_REVERSE_CACHE: Option<HashMap<u32, Box<dyn ItemScript>>> = None;
static mut ITEM_SCRIPT_INDEX_COUNTER: AtomicU32 = AtomicU32::new(1);
macro_rules! script_ptr_impl {
($name:ident, $script:ident, $cache:ident, $reverse_cache:ident, $counter:ident) => {
impl $name {
fn get_cache<'a>() -> &'a mut HashMap<*const dyn $script, u32> {
unsafe {
if let None = $cache {
$cache = Some(HashMap::new());
}
let cache = $cache.as_mut().unwrap();
cache
}
}
fn get_reverse_cache<'a>() -> &'a mut HashMap<u32, Box<dyn $script>> {
unsafe {
if let None = $reverse_cache {
$reverse_cache = Some(HashMap::new());
}
let cache = $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<dyn $script>) -> 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($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
}
}
}
};
}
#[repr(transparent)]
#[derive(Copy, Clone, Default)]
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(HashMap::new());
}
let cache = SCRIPT_PTR_CACHE.as_mut().unwrap();
cache
}
}
fn get_reverse_cache<'a>() -> &'a mut HashMap<u32, Box<dyn Script>> {
unsafe {
if let None = SCRIPT_PTR_REVERSE_CACHE {
SCRIPT_PTR_REVERSE_CACHE = Some(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<dyn Script>) -> 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
}
}
#[repr(transparent)]
#[derive(Copy, Clone, Default)]
pub struct ItemScriptPtr {
index: u32,
}
script_ptr_impl!(
ScriptPtr,
Script,
SCRIPT_PTR_CACHE,
SCRIPT_PTR_REVERSE_CACHE,
SCRIPT_INDEX_COUNTER
);
script_ptr_impl!(
ItemScriptPtr,
ItemScript,
ITEM_SCRIPT_PTR_CACHE,
ITEM_SCRIPT_PTR_REVERSE_CACHE,
ITEM_SCRIPT_INDEX_COUNTER
);
exported_functions! {
fn load_script(category: ScriptCategory, name: ExternRef<StringKey>) -> ScriptPtr {
@ -171,6 +210,16 @@ mod implementation {
ScriptPtr::new(b)
}
fn load_item_script(name: ExternRef<StringKey>) -> ItemScriptPtr {
let name_c = StringKey::new(name);
let boxed_script = unsafe { &LOAD_ITEM_SCRIPT_FN }.as_ref().unwrap()(&name_c);
if boxed_script.is_none() {
return ItemScriptPtr::null();
}
let b = boxed_script.unwrap();
ItemScriptPtr::new(b)
}
fn destroy_script(script: *mut Box<dyn Script>) {
// 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.
@ -178,6 +227,13 @@ mod implementation {
drop(boxed_script);
}
fn destroy_item_script(script: *mut Box<dyn ItemScript>) {
// 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()
@ -737,9 +793,109 @@ mod implementation {
*out = out_saturating.0;
return res;
}
// Item script functions
fn item_script_item_on_initialize(script: ItemScriptPtr, parameters: u64) -> WasmVoidResult {
let parameters : FFIArray<ExternRef<EffectParameter>> = FFIArray::from_u64(parameters);
let parameters = Vec::from_raw_parts(parameters.ptr(), parameters.len(), parameters.len());
let parameters = parameters.into_iter().map(|p| Rc::new(EffectParameter::new(p).unwrap())).collect::<Vec<_>>();
WasmVoidResult::from_res(script.val().unwrap().on_initialize(Some(parameters)))
}
fn item_script_item_is_usable(script: ItemScriptPtr, out: *mut bool) -> WasmVoidResult {
let res = script.val().unwrap().is_item_usable();
match res {
Ok(r) => {
unsafe {
*out = r;
}
WasmVoidResult::ok(())
}
Err(e) => WasmVoidResult::err(e),
}
}
fn item_script_item_requires_target(script: ItemScriptPtr, out: *mut bool) -> WasmVoidResult {
let res = script.val().unwrap().requires_target();
match res {
Ok(r) => {
unsafe {
*out = r;
}
WasmVoidResult::ok(())
}
Err(e) => WasmVoidResult::err(e),
}
}
fn item_script_item_is_holdable(script: ItemScriptPtr, out: *mut bool) -> WasmVoidResult {
let res = script.val().unwrap().is_holdable();
match res {
Ok(r) => {
unsafe {
*out = r;
}
WasmVoidResult::ok(())
}
Err(e) => WasmVoidResult::err(e),
}
}
fn item_script_item_is_target_valid(
script: ItemScriptPtr,
target: ExternRef<PokemonImpl>,
out: *mut bool
) -> WasmVoidResult {
let target : Pokemon = target.not_null_rc();
let res = script.val().unwrap().is_target_valid(&target);
match res {
Ok(r) => {
unsafe {
*out = r;
}
WasmVoidResult::ok(())
}
Err(e) => WasmVoidResult::err(e),
}
}
fn item_script_item_can_target_hold(
script: ItemScriptPtr,
target: ExternRef<PokemonImpl>,
out: *mut bool
) -> WasmVoidResult {
let target : Pokemon = target.not_null_rc();
let res = script.val().unwrap().can_target_hold(&target);
match res {
Ok(r) => {
unsafe {
*out = r;
}
WasmVoidResult::ok(())
}
Err(e) => WasmVoidResult::err(e),
}
}
fn item_script_item_on_use(
script: ItemScriptPtr,
) -> WasmVoidResult {
WasmVoidResult::from_res(script.val().unwrap().on_use())
}
fn item_script_item_on_use_with_target(
script: ItemScriptPtr,
target: ExternRef<PokemonImpl>,
) -> WasmVoidResult {
let target : Pokemon = target.not_null_rc();
WasmVoidResult::from_res(script.val().unwrap().on_use_with_target(&target))
}
}
}
use crate::handling::item_script::ItemScript;
use crate::handling::{Script, ScriptCategory};
#[cfg(not(feature = "mock_data"))]
pub use implementation::*;