From 67b0abe59f8500d616fd852df195a2b1089e8772 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sun, 7 Apr 2024 18:55:41 +0200 Subject: [PATCH] Initial work on rune as scripting library --- Cargo.toml | 10 +- rustfmt.toml | 3 +- src/dynamic_data/libraries/misc_library.rs | 2 +- src/dynamic_data/models/executing_move.rs | 85 +-- src/dynamic_data/models/pokemon.rs | 202 ++---- src/dynamic_data/script_handling/script.rs | 199 ++---- src/ffi/static_data/ability.rs | 21 +- src/ffi/static_data/move_data.rs | 16 +- src/lib.rs | 5 +- src/script_implementations/mod.rs | 4 + src/script_implementations/rune/mod.rs | 134 ++++ src/script_implementations/rune/script.rs | 153 +++++ .../rune/script_resolver.rs | 179 ++++++ .../rune/wrappers/executing_move.rs | 26 + .../rune/wrappers/mod.rs | 67 ++ .../rune/wrappers/pokemon.rs | 22 + .../rune/wrappers/turn_choice.rs | 26 + src/static_data/moves/secondary_effect.rs | 9 +- src/static_data/species_data/ability.rs | 19 +- tests/common/library_loader.rs | 45 +- tests/data/Abilities.json | 573 +++++++++--------- tests/data/Moves.json | 82 +-- tests/data/scripts/moves/test.rn | 13 + tests/integration.rs | 30 +- 24 files changed, 1186 insertions(+), 739 deletions(-) create mode 100644 src/script_implementations/rune/mod.rs create mode 100644 src/script_implementations/rune/script.rs create mode 100644 src/script_implementations/rune/script_resolver.rs create mode 100644 src/script_implementations/rune/wrappers/executing_move.rs create mode 100644 src/script_implementations/rune/wrappers/mod.rs create mode 100644 src/script_implementations/rune/wrappers/pokemon.rs create mode 100644 src/script_implementations/rune/wrappers/turn_choice.rs create mode 100644 tests/data/scripts/moves/test.rn diff --git a/Cargo.toml b/Cargo.toml index 280dec3..81e9706 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,8 @@ path = "src/lib.rs" ffi = [] serde = ["dep:serde", "dep:serde-xml-rs", "atomig/serde"] wasm = ["dep:wasmer"] -default = ["serde", "wasm", "ffi"] +rune = ["dep:rune"] +default = ["serde", "rune", "ffi"] [profile.dev] opt-level = 0 @@ -55,7 +56,6 @@ parking_lot = "0.12" serde = { version = "1.0", optional = true, features = ["derive"] } serde_repr = "0.1" serde-xml-rs = { version = "0.6", optional = true } -wasmer = { version = "4.2", optional = true, default-features = false, features = ["sys", "wat", "llvm"] } uuid = "1.5" paste = { version = "1.0" } arcstr = { version = "1.1", features = ["std"] } @@ -65,6 +65,9 @@ anyhow_ext = "0.2" thiserror = "1.0" stdext = "0.3" +wasmer = { version = "4.2", optional = true, default-features = false, features = ["sys", "wat", "llvm"] } +rune = { version = "0.13.2", optional = true } + [dev-dependencies] csv = "1.3" project-root = "0.2" @@ -73,4 +76,5 @@ serde_json = "1.0" serde_plain = "1.0" # Allow us to assert whether floats are approximately a value assert_approx_eq = "1.1" -mockall = "0.11" \ No newline at end of file +mockall = "0.12" +walkdir = "2.3" \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml index 866c756..10c4020 100755 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,2 @@ -max_width = 120 \ No newline at end of file +max_width = 120 +fn_single_line = true \ No newline at end of file diff --git a/src/dynamic_data/libraries/misc_library.rs b/src/dynamic_data/libraries/misc_library.rs index 9b420ec..866f77e 100755 --- a/src/dynamic_data/libraries/misc_library.rs +++ b/src/dynamic_data/libraries/misc_library.rs @@ -47,7 +47,7 @@ impl Gen7MiscLibrary { Some(Arc::new(SecondaryEffectImpl::new( -1.0, StringKey::new("struggle"), - vec![], + Default::default(), ))), HashSet::new(), )); diff --git a/src/dynamic_data/models/executing_move.rs b/src/dynamic_data/models/executing_move.rs index 473d845..1944862 100755 --- a/src/dynamic_data/models/executing_move.rs +++ b/src/dynamic_data/models/executing_move.rs @@ -33,59 +33,34 @@ pub struct HitData { impl HitData { /// Whether or not the hit is critical. - pub fn is_critical(&self) -> bool { - self.critical.load(Ordering::Relaxed) - } + pub fn is_critical(&self) -> bool { self.critical.load(Ordering::Relaxed) } /// The base power of the hit. - pub fn base_power(&self) -> u8 { - self.base_power.load(Ordering::Relaxed) - } + pub fn base_power(&self) -> u8 { self.base_power.load(Ordering::Relaxed) } /// The type effectiveness of the hit. - pub fn effectiveness(&self) -> f32 { - self.effectiveness.load(Ordering::Relaxed) - } + pub fn effectiveness(&self) -> f32 { self.effectiveness.load(Ordering::Relaxed) } /// The actual damage of the hit. - pub fn damage(&self) -> u32 { - self.damage.load(Ordering::Relaxed) - } + pub fn damage(&self) -> u32 { self.damage.load(Ordering::Relaxed) } /// The type id of the type used for the hit. - pub fn move_type(&self) -> TypeIdentifier { - self.move_type.load(Ordering::Relaxed) - } + pub fn move_type(&self) -> TypeIdentifier { self.move_type.load(Ordering::Relaxed) } /// Whether or not the hit has failed. - pub fn has_failed(&self) -> bool { - self.has_failed.load(Ordering::Relaxed) - } + pub fn has_failed(&self) -> bool { self.has_failed.load(Ordering::Relaxed) } /// Sets whether or not the hit is critical. - pub fn set_critical(&self, value: bool) { - self.critical.store(value, Ordering::SeqCst); - } + pub fn set_critical(&self, value: bool) { self.critical.store(value, Ordering::SeqCst); } /// Sets the base power of the hit. - pub fn set_base_power(&self, value: u8) { - self.base_power.store(value, Ordering::SeqCst); - } + pub fn set_base_power(&self, value: u8) { self.base_power.store(value, Ordering::SeqCst); } /// Sets the type effectiveness of the hit. - pub fn set_effectiveness(&self, value: f32) { - self.effectiveness.store(value, Ordering::SeqCst); - } + pub fn set_effectiveness(&self, value: f32) { self.effectiveness.store(value, Ordering::SeqCst); } /// Sets the actual damage of the hit. - pub fn set_damage(&self, value: u32) { - self.damage.store(value, Ordering::SeqCst); - } + pub fn set_damage(&self, value: u32) { self.damage.store(value, Ordering::SeqCst); } /// Sets the move type id of the hit. - pub fn set_move_type(&self, value: TypeIdentifier) { - self.move_type.store(value, Ordering::SeqCst); - } + pub fn set_move_type(&self, value: TypeIdentifier) { self.move_type.store(value, Ordering::SeqCst); } /// Marks the hit as failed. - pub fn fail(&self) { - self.has_failed.store(true, Ordering::SeqCst); - } + pub fn fail(&self) { self.has_failed.store(true, Ordering::SeqCst); } } /// An executing move is the data of the move for while it is executing. #[derive(Debug)] - pub struct ExecutingMove { /// The number of hits this move has. number_of_hits: u8, @@ -134,29 +109,17 @@ impl ExecutingMove { } /// The number of targets this move has. - pub fn target_count(&self) -> usize { - self.targets.len() - } + pub fn target_count(&self) -> usize { self.targets.len() } /// The number of hits this move has per target. - pub fn number_of_hits(&self) -> u8 { - self.number_of_hits - } + pub fn number_of_hits(&self) -> u8 { self.number_of_hits } /// The user of the move. - pub fn user(&self) -> &Pokemon { - &self.user - } + pub fn user(&self) -> &Pokemon { &self.user } /// The move the user has actually chosen to do. - pub fn chosen_move(&self) -> &Arc { - &self.chosen_move - } + pub fn chosen_move(&self) -> &Arc { &self.chosen_move } /// The move that the user is actually going to do. - pub fn use_move(&self) -> &Arc { - &self.use_move - } + pub fn use_move(&self) -> &Arc { &self.use_move } /// The script of the move. - pub fn script(&self) -> &ScriptContainer { - &self.script - } + pub fn script(&self) -> &ScriptContainer { &self.script } /// Gets a hit data for a target, with a specific index. pub fn get_hit_data(&self, for_target: &Pokemon, hit: u8) -> Result<&Arc> { @@ -215,17 +178,11 @@ impl ExecutingMove { } impl ScriptSource for ExecutingMove { - fn get_script_count(&self) -> Result { - Ok(1) - } + fn get_script_count(&self) -> Result { Ok(1) } - fn get_script_source_data(&self) -> &RwLock { - &self.script_source_data - } + fn get_script_source_data(&self) -> &RwLock { &self.script_source_data } - fn get_own_scripts(&self, scripts: &mut Vec) { - scripts.push((&self.script).into()); - } + fn get_own_scripts(&self, scripts: &mut Vec) { scripts.push((&self.script).into()); } fn collect_scripts(&self, scripts: &mut Vec) -> Result<()> { self.get_own_scripts(scripts); diff --git a/src/dynamic_data/models/pokemon.rs b/src/dynamic_data/models/pokemon.rs index 1f213f1..7b2c3cd 100755 --- a/src/dynamic_data/models/pokemon.rs +++ b/src/dynamic_data/models/pokemon.rs @@ -216,21 +216,13 @@ impl Pokemon { } /// The library data of the Pokemon. - pub fn library(&self) -> &Arc { - &self.data.library - } + pub fn library(&self) -> &Arc { &self.data.library } /// The species of the Pokemon. - pub fn species(&self) -> Arc { - self.data.species.read().clone() - } + pub fn species(&self) -> Arc { self.data.species.read().clone() } /// The form of the Pokemon. - pub fn form(&self) -> Arc { - self.data.form.read().clone() - } + pub fn form(&self) -> Arc { self.data.form.read().clone() } /// Whether or not the Pokemon is showing as a different species than it actually is. - pub fn has_different_display_species(&self) -> bool { - self.data.display_species.is_some() - } + pub fn has_different_display_species(&self) -> bool { self.data.display_species.is_some() } /// The species that should be displayed to the user. This handles stuff like the Illusion ability. pub fn display_species(&self) -> Arc { if let Some(v) = &self.data.display_species { @@ -240,9 +232,7 @@ impl Pokemon { } } /// Whether or not the Pokemon is showing as a different form than it actually is. - pub fn has_different_display_form(&self) -> bool { - self.data.display_form.is_some() - } + pub fn has_different_display_form(&self) -> bool { self.data.display_form.is_some() } /// The form that should be displayed to the user. This handles stuff like the Illusion ability. pub fn display_form(&self) -> Arc { if let Some(v) = &self.data.display_form { @@ -253,32 +243,20 @@ impl Pokemon { } /// The current level of the Pokemon. /// [See also](https://bulbapedia.bulbagarden.net/wiki/Level) - pub fn level(&self) -> LevelInt { - self.data.level.load(Ordering::Relaxed) - } + pub fn level(&self) -> LevelInt { self.data.level.load(Ordering::Relaxed) } /// The amount of experience of the Pokemon. /// [See also](https://bulbapedia.bulbagarden.net/wiki/Experience) - pub fn experience(&self) -> u32 { - self.data.experience.load(Ordering::Relaxed) - } + pub fn experience(&self) -> u32 { self.data.experience.load(Ordering::Relaxed) } /// The personality value of the Pokemon. /// [See also](https://bulbapedia.bulbagarden.net/wiki/Personality_value) - pub fn personality_value(&self) -> u32 { - self.data.personality_value - } + pub fn personality_value(&self) -> u32 { self.data.personality_value } /// The gender of the Pokemon. - pub fn gender(&self) -> Gender { - *self.data.gender.read() - } + pub fn gender(&self) -> Gender { *self.data.gender.read() } /// The coloring of the Pokemon. Value 0 is the default, value 1 means shiny. Other values are /// currently not used, and can be used for other implementations. - pub fn coloring(&self) -> u8 { - self.data.coloring - } + pub fn coloring(&self) -> u8 { self.data.coloring } /// Gets the held item of a Pokemon - pub fn held_item(&self) -> &RwLock>> { - &self.data.held_item - } + pub fn held_item(&self) -> &RwLock>> { &self.data.held_item } /// Checks whether the Pokemon is holding a specific item. pub fn has_held_item(&self, name: &StringKey) -> bool { // Only true if we have an item, and the item name is the same as the requested item. @@ -292,9 +270,7 @@ impl Pokemon { self.data.held_item.write().replace(item.clone()) } /// Removes the held item from the Pokemon. Returns the previously held item. - pub fn remove_held_item(&self) -> Option> { - self.data.held_item.write().take() - } + pub fn remove_held_item(&self) -> Option> { self.data.held_item.write().take() } /// Makes the Pokemon uses its held item. pub fn consume_held_item(&self) -> Result { if self.data.held_item.read().is_none() { @@ -316,72 +292,42 @@ impl Pokemon { } /// The remaining health points of the Pokemon. - pub fn current_health(&self) -> u32 { - self.data.current_health.load(Ordering::Relaxed) - } + pub fn current_health(&self) -> u32 { self.data.current_health.load(Ordering::Relaxed) } /// The max health points of the Pokemon. - pub fn max_health(&self) -> u32 { - self.data.boosted_stats.hp() - } + pub fn max_health(&self) -> u32 { self.data.boosted_stats.hp() } /// The weight of the Pokemon in kilograms. - pub fn weight(&self) -> f32 { - self.data.weight.load(Ordering::Relaxed) - } + pub fn weight(&self) -> f32 { self.data.weight.load(Ordering::Relaxed) } /// Sets the weight of the Pokemon in kilograms. - pub fn set_weight(&self, weight: f32) { - self.data.weight.store(weight, Ordering::Relaxed) - } + pub fn set_weight(&self, weight: f32) { self.data.weight.store(weight, Ordering::Relaxed) } /// The height of the Pokemon in meters. - pub fn height(&self) -> f32 { - self.data.height.load(Ordering::Relaxed) - } + pub fn height(&self) -> f32 { self.data.height.load(Ordering::Relaxed) } /// The current happiness of the Pokemon. Also known as friendship. - pub fn happiness(&self) -> u8 { - self.data.happiness.load(Ordering::Relaxed) - } + pub fn happiness(&self) -> u8 { self.data.happiness.load(Ordering::Relaxed) } /// An optional nickname of the Pokemon. - pub fn nickname(&self) -> &Option { - &self.data.nickname - } + pub fn nickname(&self) -> &Option { &self.data.nickname } /// An index of the ability to find the actual ability on the form. - pub fn real_ability(&self) -> &AbilityIndex { - &self.data.ability_index - } + pub fn real_ability(&self) -> &AbilityIndex { &self.data.ability_index } /// The current types of the Pokemon. - pub fn types(&self) -> RwLockReadGuard<'_, RawRwLock, Vec> { - self.data.types.read() - } + pub fn types(&self) -> RwLockReadGuard<'_, RawRwLock, Vec> { self.data.types.read() } /// The moves the Pokemon has learned. This is of a set length of [`MAX_MOVES`]. Empty move slots /// are defined by None. - pub fn learned_moves(&self) -> &RwLock<[Option>; MAX_MOVES]> { - &self.data.moves - } + pub fn learned_moves(&self) -> &RwLock<[Option>; MAX_MOVES]> { &self.data.moves } /// The stats of the Pokemon when disregarding any stat boosts. - pub fn flat_stats(&self) -> &Arc> { - &self.data.flat_stats - } + pub fn flat_stats(&self) -> &Arc> { &self.data.flat_stats } /// The amount of boosts on a specific stat. - pub fn stat_boosts(&self) -> &Arc> { - &self.data.stat_boost - } + pub fn stat_boosts(&self) -> &Arc> { &self.data.stat_boost } /// Whether or not this Pokemon is still an egg, and therefore cannot battle. - pub fn is_egg(&self) -> bool { - self.data.is_egg - } + pub fn is_egg(&self) -> bool { self.data.is_egg } /// The stats of the Pokemon including the stat boosts - pub fn boosted_stats(&self) -> &Arc> { - &self.data.boosted_stats - } + pub fn boosted_stats(&self) -> &Arc> { &self.data.boosted_stats } /// Get the stat boosts for a specific stat. - pub fn stat_boost(&self, stat: Statistic) -> i8 { - self.data.stat_boost.get_stat(stat) - } + pub fn stat_boost(&self, stat: Statistic) -> i8 { self.data.stat_boost.get_stat(stat) } /// Change a boosted stat by a certain amount. pub fn change_stat_boost(&self, stat: Statistic, mut diff_amount: i8, self_inflicted: bool) -> Result { let mut prevent = false; @@ -440,14 +386,10 @@ impl Pokemon { /// Gets an individual value of the Pokemon. /// [See also](https://bulbapedia.bulbagarden.net/wiki/Individual_values) - pub fn individual_values(&self) -> &Arc> { - &self.data.individual_values - } + pub fn individual_values(&self) -> &Arc> { &self.data.individual_values } /// Gets an effort value of the Pokemon. /// [See also](https://bulbapedia.bulbagarden.net/wiki/Effort_values) - pub fn effort_values(&self) -> &Arc> { - &self.data.effort_values - } + pub fn effort_values(&self) -> &Arc> { &self.data.effort_values } /// Gets the battle the battle is currently in. pub fn get_battle(&self) -> Option { @@ -469,13 +411,9 @@ impl Pokemon { } /// Get the index of the slot on the side of the battle the Pokemon is in. Only returns a value /// if the Pokemon is on the battlefield. - pub fn get_battle_index(&self) -> Option { - self.data.battle_data.read().as_ref().map(|data| data.index()) - } + pub fn get_battle_index(&self) -> Option { self.data.battle_data.read().as_ref().map(|data| data.index()) } /// Returns whether something overrides the ability. - pub fn is_ability_overriden(&self) -> bool { - self.data.override_ability.is_some() - } + pub fn is_ability_overriden(&self) -> bool { self.data.override_ability.is_some() } /// Returns the currently active ability. pub fn active_ability(&self) -> Result> { if let Some(v) = &self.data.override_ability { @@ -496,25 +434,17 @@ impl Pokemon { } /// The script for the status. - pub fn status(&self) -> &ScriptContainer { - &self.data.status_script - } + pub fn status(&self) -> &ScriptContainer { &self.data.status_script } /// Returns the script for the currently active ability. - pub fn ability_script(&self) -> &ScriptContainer { - &self.data.ability_script - } + pub fn ability_script(&self) -> &ScriptContainer { &self.data.ability_script } /// Whether or not the Pokemon is allowed to gain experience. - pub fn allowed_experience_gain(&self) -> bool { - self.data.allowed_experience - } + pub fn allowed_experience_gain(&self) -> bool { self.data.allowed_experience } /// The nature of the Pokemon. /// [See also](https://bulbapedia.bulbagarden.net/wiki/Nature) - pub fn nature(&self) -> &Arc { - &self.data.nature - } + pub fn nature(&self) -> &Arc { &self.data.nature } /// Calculates the flat stats on the Pokemon. This should be called when for example the base /// stats, level, nature, IV, or EV changes. This has a side effect of recalculating the boosted @@ -606,7 +536,7 @@ impl Pokemon { .set(ability_script) .as_ref() // Ensure the ability script gets initialized with the parameters for the ability. - .on_initialize(&self.data.library, ability.parameters().to_vec()); + .on_initialize(&self.data.library, ability.parameters()); match script_result { Ok(_) => (), Err(e) => { @@ -646,14 +576,10 @@ impl Pokemon { } /// Whether or not the Pokemon is useable in a battle. - pub fn is_usable(&self) -> bool { - !self.data.is_caught && !self.data.is_egg && !self.is_fainted() - } + pub fn is_usable(&self) -> bool { !self.data.is_caught && !self.data.is_egg && !self.is_fainted() } /// Returns whether the Pokemon is fainted. - pub fn is_fainted(&self) -> bool { - self.current_health() == 0 - } + pub fn is_fainted(&self) -> bool { self.current_health() == 0 } /// Sets the current battle the Pokemon is in. pub fn set_battle_data(&self, battle: WeakBattleReference, battle_side_index: u8) { @@ -836,9 +762,7 @@ impl Pokemon { } /// Removes the current non-volatile status from the Pokemon. - pub fn clear_status(&self) { - self.data.status_script.clear() - } + pub fn clear_status(&self) { self.data.status_script.clear() } /// Increases the level by a certain amount pub fn change_level_by(&self, amount: LevelInt) -> Result<()> { @@ -858,9 +782,7 @@ impl Pokemon { /// Converts the Pokemon into a serializable form. #[cfg(feature = "serde")] - pub fn serialize(&self) -> Result { - self.into() - } + pub fn serialize(&self) -> Result { self.into() } /// Deserializes a Pokemon from a serializable form. #[cfg(feature = "serde")] @@ -995,23 +917,17 @@ impl Pokemon { } /// Gets the inner pointer to the reference counted data. - pub fn as_ptr(&self) -> *const c_void { - Arc::as_ptr(&self.data) as *const c_void - } + pub fn as_ptr(&self) -> *const c_void { Arc::as_ptr(&self.data) as *const c_void } } impl PartialEq for Pokemon { - fn eq(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.data, &other.data) - } + fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.data, &other.data) } } impl Eq for Pokemon {} impl PartialEq for WeakPokemonReference { - fn eq(&self, other: &Self) -> bool { - Weak::ptr_eq(&self.data, &other.data) - } + fn eq(&self, other: &Self) -> bool { Weak::ptr_eq(&self.data, &other.data) } } impl Eq for WeakPokemonReference {} @@ -1025,9 +941,7 @@ impl WeakPokemonReference { } /// Gets the pointer to the underlying data. - pub(crate) fn as_ptr(&self) -> *const c_void { - self.data.as_ptr() as *const c_void - } + pub(crate) fn as_ptr(&self) -> *const c_void { self.data.as_ptr() as *const c_void } } /// The data of the Pokemon related to being in a battle. @@ -1047,26 +961,16 @@ pub struct PokemonBattleData { impl PokemonBattleData { /// The battle data of the Pokemon - pub fn battle(&self) -> Option { - self.battle.upgrade() - } + pub fn battle(&self) -> Option { self.battle.upgrade() } /// The index of the side of the Pokemon - pub fn battle_side_index(&self) -> u8 { - self.battle_side_index.load(Ordering::Relaxed) - } + pub fn battle_side_index(&self) -> u8 { self.battle_side_index.load(Ordering::Relaxed) } /// The index of the slot on the side of the Pokemon. - pub fn index(&self) -> u8 { - self.index.load(Ordering::Relaxed) - } + pub fn index(&self) -> u8 { self.index.load(Ordering::Relaxed) } /// Whether or not the Pokemon is on the battlefield. - pub fn on_battle_field(&self) -> bool { - self.on_battle_field.load(Ordering::Relaxed) - } + pub fn on_battle_field(&self) -> bool { self.on_battle_field.load(Ordering::Relaxed) } /// A list of opponents the Pokemon has seen this battle. - pub fn seen_opponents(&self) -> &RwLock> { - &self.seen_opponents - } + pub fn seen_opponents(&self) -> &RwLock> { &self.seen_opponents } } impl ScriptSource for Pokemon { @@ -1083,9 +987,7 @@ impl ScriptSource for Pokemon { Ok(c) } - fn get_script_source_data(&self) -> &RwLock { - &self.data.script_source_data - } + fn get_script_source_data(&self) -> &RwLock { &self.data.script_source_data } fn get_own_scripts(&self, scripts: &mut Vec) { scripts.push((&self.data.held_item_trigger_script).into()); @@ -1109,9 +1011,7 @@ impl ScriptSource for Pokemon { } impl VolatileScriptsOwner for Pokemon { - fn volatile_scripts(&self) -> &Arc { - &self.data.volatile - } + fn volatile_scripts(&self) -> &Arc { &self.data.volatile } fn load_volatile_script(&self, key: &StringKey) -> Result>> { self.data.library.load_script(self.into(), ScriptCategory::Pokemon, key) diff --git a/src/dynamic_data/script_handling/script.rs b/src/dynamic_data/script_handling/script.rs index b97a45f..468a764 100755 --- a/src/dynamic_data/script_handling/script.rs +++ b/src/dynamic_data/script_handling/script.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, Result}; +use hashbrown::HashMap; use std::any::Any; use std::fmt::{Debug, Formatter}; use std::ops::Deref; @@ -30,97 +31,69 @@ pub trait Script: Send + Sync { fn get_marked_for_deletion(&self) -> &AtomicBool; /// This marks the script for deletion, which will dispose of it as soon as possible. - fn mark_for_deletion(&self) { - self.get_marked_for_deletion().store(true, Ordering::SeqCst); - } + fn mark_for_deletion(&self) { self.get_marked_for_deletion().store(true, Ordering::SeqCst); } /// Helper function to get the value of the marked for deletion bool. - fn is_marked_for_deletion(&self) -> bool { - self.get_marked_for_deletion().load(Ordering::SeqCst) - } + fn is_marked_for_deletion(&self) -> bool { self.get_marked_for_deletion().load(Ordering::SeqCst) } /// A script can be suppressed by other scripts. If a script is suppressed by at least one script /// we will not execute its methods. This should return the number of suppressions on the script. fn get_suppressed_count(&self) -> &AtomicUsize; /// Helper function to check if there is at least one suppression on the script - fn is_suppressed(&self) -> bool { - self.get_suppressed_count().load(Ordering::SeqCst) > 0 - } + fn is_suppressed(&self) -> bool { self.get_suppressed_count().load(Ordering::SeqCst) > 0 } /// Adds a suppression. This makes the script not run anymore. Note that adding this should also /// remove the suppression later. /// /// A common pattern for this is to run this in the [`Self::on_initialize`] function, and run the /// remove in the [`Self::on_remove`] function. - fn add_suppression(&self) { - self.get_suppressed_count().fetch_add(1, Ordering::SeqCst); - } + fn add_suppression(&self) { self.get_suppressed_count().fetch_add(1, Ordering::SeqCst); } /// Removes a suppression. This allows the script to run again (provided other scripts are not /// suppressing it). Note that running this should only occur if an add was run before. - fn remove_suppression(&self) { - self.get_suppressed_count().fetch_sub(1, Ordering::SeqCst); - } + fn remove_suppression(&self) { self.get_suppressed_count().fetch_sub(1, Ordering::SeqCst); } /// This function is ran when a volatile effect is added while that volatile effect already is /// in place. Instead of adding the volatile effect twice, it will execute this function instead. - fn stack(&self) -> Result<()> { - Ok(()) - } + fn stack(&self) -> Result<()> { Ok(()) } /// This function is ran when this script stops being in effect, and is removed from its owner. - fn on_remove(&self) -> Result<()> { - Ok(()) - } + fn on_remove(&self) -> Result<()> { Ok(()) } /// This function is ran when this script starts being in effect. - fn on_initialize(&self, _library: &Arc, _pars: Vec>) -> Result<()> { + fn on_initialize( + &self, + _library: &Arc, + _pars: &HashMap>, + ) -> Result<()> { Ok(()) } /// This function is ran just before the start of the turn. Everyone has made its choices here, /// and the turn is about to start. This is a great place to initialize data if you need to know /// something has happened during a turn. - fn on_before_turn(&self, _choice: &Arc) -> Result<()> { - Ok(()) - } + fn on_before_turn(&self, _choice: &Arc) -> Result<()> { Ok(()) } /// This function allows you to modify the effective speed of the Pokemon. This is ran before /// turn ordering, so overriding here will allow you to put certain Pokemon before others. - fn change_speed(&self, _choice: &Arc, _speed: &mut u32) -> Result<()> { - Ok(()) - } + fn change_speed(&self, _choice: &Arc, _speed: &mut u32) -> Result<()> { Ok(()) } /// This function allows you to modify the effective priority of the Pokemon. This is ran before /// turn ordering, so overriding here will allow you to put certain Pokemon before others. Note /// that this is only relevant on move choices, as other turn choice types do not have a priority. - fn change_priority(&self, _choice: &Arc, _priority: &mut i8) -> Result<()> { - Ok(()) - } + fn change_priority(&self, _choice: &Arc, _priority: &mut i8) -> Result<()> { Ok(()) } /// This function allows you to change the move that is used during execution. This is useful for /// moves such as metronome, where the move chosen actually differs from the move used. - fn change_move(&self, _choice: &Arc, _move_name: &mut StringKey) -> Result<()> { - Ok(()) - } + fn change_move(&self, _choice: &Arc, _move_name: &mut StringKey) -> Result<()> { Ok(()) } /// This function allows you to change a move into a multi-hit move. The number of hits set here /// gets used as the number of hits. If set to 0, this will behave as if the move missed on its /// first hit. - fn change_number_of_hits(&self, _choice: &Arc, _number_of_hits: &mut u8) -> Result<()> { - Ok(()) - } + fn change_number_of_hits(&self, _choice: &Arc, _number_of_hits: &mut u8) -> Result<()> { Ok(()) } /// This function allows you to prevent a move from running. If this gets set to true, the move /// ends execution here. No PP will be decreased in this case. - fn prevent_move(&self, _move: &Arc, _prevent: &mut bool) -> Result<()> { - Ok(()) - } + fn prevent_move(&self, _move: &Arc, _prevent: &mut bool) -> Result<()> { Ok(()) } /// This function makes the move fail. If the fail field gets set to true, the move ends execution, /// and fail events get triggered. - fn fail_move(&self, _move: &Arc, _fail: &mut bool) -> Result<()> { - Ok(()) - } + fn fail_move(&self, _move: &Arc, _fail: &mut bool) -> Result<()> { Ok(()) } /// Similar to [`Self::prevent_move`]. This function will also stop execution, but PP will be /// decreased. - fn stop_before_move(&self, _move: &Arc, _stop: &mut bool) -> Result<()> { - Ok(()) - } + fn stop_before_move(&self, _move: &Arc, _stop: &mut bool) -> Result<()> { Ok(()) } /// This function runs just before the move starts its execution. - fn on_before_move(&self, _move: &Arc) -> Result<()> { - Ok(()) - } + fn on_before_move(&self, _move: &Arc) -> Result<()> { Ok(()) } /// This function allows a script to prevent a move that is targeted at its owner. If set to true /// the move fails, and fail events get triggered. fn fail_incoming_move(&self, _move: &Arc, _target: &Pokemon, _fail: &mut bool) -> Result<()> { @@ -132,9 +105,7 @@ pub trait Script: Send + Sync { } /// This function occurs when a move gets missed. This runs on the scripts belonging to the executing /// move, which include the scripts that are attached to the owner of the script. - fn on_move_miss(&self, _move: &Arc, _target: &Pokemon) -> Result<()> { - Ok(()) - } + fn on_move_miss(&self, _move: &Arc, _target: &Pokemon) -> Result<()> { Ok(()) } /// This function allows the script to change the actual type that is used for the move on a target. fn change_move_type( &self, @@ -308,13 +279,9 @@ pub trait Script: Send + Sync { } /// This function triggers when an incoming hit happens. This triggers after the damage is done, /// but before the secondary effect of the move happens. - fn on_incoming_hit(&self, _move: &Arc, _target: &Pokemon, _hit: u8) -> Result<()> { - Ok(()) - } + fn on_incoming_hit(&self, _move: &Arc, _target: &Pokemon, _hit: u8) -> Result<()> { Ok(()) } /// This function triggers when an opponent on the field faints. - fn on_opponent_faints(&self, _move: &Arc, _target: &Pokemon, _hit: u8) -> Result<()> { - Ok(()) - } + fn on_opponent_faints(&self, _move: &Arc, _target: &Pokemon, _hit: u8) -> Result<()> { Ok(()) } /// This function allows a script attached to a Pokemon or its parents to prevent stat boost /// changes on that Pokemon. fn prevent_stat_boost_change( @@ -380,61 +347,37 @@ pub trait Script: Send + Sync { /// This function triggers when the move uses its secondary effect. Moves should implement their /// secondary effects here. Status moves should implement their actual functionality in this /// function as well, as status moves effects are defined as secondary effects for simplicity. - fn on_secondary_effect(&self, _move: &Arc, _target: &Pokemon, _hit: u8) -> Result<()> { - Ok(()) - } + fn on_secondary_effect(&self, _move: &Arc, _target: &Pokemon, _hit: u8) -> Result<()> { Ok(()) } /// This function triggers on a move or its parents when all hits on a target are finished. - fn on_after_hits(&self, _move: &Arc, _target: &Pokemon) -> Result<()> { - Ok(()) - } + fn on_after_hits(&self, _move: &Arc, _target: &Pokemon) -> Result<()> { Ok(()) } /// This function prevents the Pokemon it is attached to from being able to switch out. - fn prevent_self_switch(&self, _choice: &Arc, _prevent: &mut bool) -> Result<()> { - Ok(()) - } + fn prevent_self_switch(&self, _choice: &Arc, _prevent: &mut bool) -> Result<()> { Ok(()) } /// This function allows the prevention of switching for any opponent. - fn prevent_opponent_switch(&self, _choice: &Arc, _prevent: &mut bool) -> Result<()> { - Ok(()) - } + fn prevent_opponent_switch(&self, _choice: &Arc, _prevent: &mut bool) -> Result<()> { Ok(()) } /// This function is called on a move and its parents when the move fails. - fn on_fail(&self, _target: &Pokemon) -> Result<()> { - Ok(()) - } + fn on_fail(&self, _target: &Pokemon) -> Result<()> { Ok(()) } /// This function is called on a script when an opponent fails. - fn on_opponent_fail(&self, _target: &Pokemon) -> Result<()> { - Ok(()) - } + fn on_opponent_fail(&self, _target: &Pokemon) -> Result<()> { Ok(()) } /// This function allows preventing the running away of the Pokemon its attached to - fn prevent_self_run_away(&self, _choice: &Arc, _prevent: &mut bool) -> Result<()> { - Ok(()) - } + fn prevent_self_run_away(&self, _choice: &Arc, _prevent: &mut bool) -> Result<()> { Ok(()) } /// This function prevents a Pokemon on another side than where its attached to from running away. - fn prevent_opponent_run_away(&self, _choice: &Arc, _prevent: &mut bool) -> Result<()> { - Ok(()) - } + fn prevent_opponent_run_away(&self, _choice: &Arc, _prevent: &mut bool) -> Result<()> { Ok(()) } /// This function id triggered on all scripts active in the battle after all choices have finished /// running. Note that choices are not active anymore here, so their scripts do not call this /// function. - fn on_end_turn(&self) -> Result<()> { - Ok(()) - } + fn on_end_turn(&self) -> Result<()> { Ok(()) } /// This function is triggered on a Pokemon and its parents when the given Pokemon takes damage. fn on_damage(&self, _pokemon: &Pokemon, _source: DamageSource, _old_health: u32, _new_health: u32) -> Result<()> { Ok(()) } /// This function is triggered on a Pokemon and its parents when the given Pokemon faints. - fn on_faint(&self, _pokemon: &Pokemon, _source: DamageSource) -> Result<()> { - Ok(()) - } + fn on_faint(&self, _pokemon: &Pokemon, _source: DamageSource) -> Result<()> { Ok(()) } /// This function is triggered on a Pokemon and its parents when the given Pokemon is switched into /// the battlefield. - fn on_switch_in(&self, _pokemon: &Pokemon) -> Result<()> { - Ok(()) - } + fn on_switch_in(&self, _pokemon: &Pokemon) -> Result<()> { Ok(()) } /// This function is triggered on a Pokemon and its parents when the given Pokemon consumes the /// held item it had. - fn on_after_held_item_consume(&self, _pokemon: &Pokemon, _item: &Arc) -> Result<()> { - Ok(()) - } + fn on_after_held_item_consume(&self, _pokemon: &Pokemon, _item: &Arc) -> Result<()> { Ok(()) } /// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience, /// and allows for changing this amount of experience. fn change_experience_gained( @@ -452,9 +395,7 @@ pub trait Script: Send + Sync { } /// This function is triggered on a battle and its parents when something attempts to change the /// weather, and allows for blocking the weather change. - fn block_weather(&self, _battle: &Battle, _blocked: &mut bool) -> Result<()> { - Ok(()) - } + fn block_weather(&self, _battle: &Battle, _blocked: &mut bool) -> Result<()> { Ok(()) } /// This function is called when a Pokeball is thrown at a Pokemon, and allows modifying the catch /// rate of this attempt. Pokeball modifier effects should be implemented here, as well as for /// example status effects that change capture rates. @@ -474,9 +415,7 @@ pub trait Script: Send + Sync { } impl Debug for dyn Script { - fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { - Ok(()) - } + fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { Ok(()) } } /// A script holder defines the underlying type of how we store individual scripts on a script source. @@ -574,14 +513,10 @@ impl ScriptContainer { } /// Gets the underlying reference counter to the script. - pub fn arc(&self) -> &ScriptHolder { - &self.script - } + pub fn arc(&self) -> &ScriptHolder { &self.script } /// Whether or not the script is set. - pub fn is_any(&self) -> bool { - self.script.read().is_some() - } + pub fn is_any(&self) -> bool { self.script.read().is_some() } /// Get the underlying script as the downcasted value. pub fn get_as(&self) -> Result> { @@ -646,29 +581,19 @@ mod tests { unsafe impl Send for TestScript {} impl Script for TestScript { - fn name(&self) -> Result<&StringKey> { - Ok(&self.name) - } + fn name(&self) -> Result<&StringKey> { Ok(&self.name) } - fn get_marked_for_deletion(&self) -> &AtomicBool { - &self.marked_for_deletion - } + fn get_marked_for_deletion(&self) -> &AtomicBool { &self.marked_for_deletion } - fn get_suppressed_count(&self) -> &AtomicUsize { - &self.suppressed_count - } + fn get_suppressed_count(&self) -> &AtomicUsize { &self.suppressed_count } fn stack(&self) -> Result<()> { unsafe { self.container.load(Ordering::Relaxed).as_ref().unwrap().clear() } Ok(()) } - fn as_any(&self) -> &dyn Any { - self - } - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } + fn as_any(&self) -> &dyn Any { self } + fn as_any_mut(&mut self) -> &mut dyn Any { self } } // Removing yourself while active should be completely valid for a script. Consider for example @@ -726,24 +651,14 @@ mod tests { unsafe impl Send for ReplaceTestScript {} impl Script for ReplaceTestScript { - fn name(&self) -> Result<&StringKey> { - Ok(&self.name) - } + fn name(&self) -> Result<&StringKey> { Ok(&self.name) } - fn get_marked_for_deletion(&self) -> &AtomicBool { - &self.marked_for_deletion - } + fn get_marked_for_deletion(&self) -> &AtomicBool { &self.marked_for_deletion } - fn get_suppressed_count(&self) -> &AtomicUsize { - &self.suppressed_count - } + fn get_suppressed_count(&self) -> &AtomicUsize { &self.suppressed_count } - fn as_any(&self) -> &dyn Any { - self - } - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } + fn as_any(&self) -> &dyn Any { self } + fn as_any_mut(&mut self) -> &mut dyn Any { self } } #[test] @@ -793,19 +708,13 @@ pub enum ScriptOwnerData { } impl From<&Pokemon> for ScriptOwnerData { - fn from(p: &Pokemon) -> Self { - ScriptOwnerData::Pokemon(p.weak()) - } + fn from(p: &Pokemon) -> Self { ScriptOwnerData::Pokemon(p.weak()) } } impl From<&BattleSide> for ScriptOwnerData { - fn from(p: &BattleSide) -> Self { - ScriptOwnerData::BattleSide(p.weak()) - } + fn from(p: &BattleSide) -> Self { ScriptOwnerData::BattleSide(p.weak()) } } impl From<&Battle> for ScriptOwnerData { - fn from(p: &Battle) -> Self { - ScriptOwnerData::Battle(p.weak()) - } + fn from(p: &Battle) -> Self { ScriptOwnerData::Battle(p.weak()) } } diff --git a/src/ffi/static_data/ability.rs b/src/ffi/static_data/ability.rs index 43f1f89..8b0ed94 100644 --- a/src/ffi/static_data/ability.rs +++ b/src/ffi/static_data/ability.rs @@ -1,9 +1,10 @@ -use crate::ffi::FFIHandle; use crate::ffi::FromFFIHandle; +use crate::ffi::{FFIHandle, NonOwnedPtrString}; use crate::ffi::{FFIResult, OwnedPtrString}; use crate::static_data::{Ability, AbilityImpl, Parameter}; use crate::StringKey; use anyhow::anyhow; +use hashbrown::HashMap; use std::ffi::{c_char, CStr, CString}; use std::sync::Arc; @@ -12,13 +13,18 @@ use std::sync::Arc; unsafe extern "C" fn ability_new( name: *const c_char, effect: *const c_char, + parameter_keys: *const NonOwnedPtrString, parameters: *const FFIHandle>, parameters_length: usize, ) -> FFIResult>> { + let parameter_keys = std::slice::from_raw_parts(parameter_keys, parameters_length); let parameters = std::slice::from_raw_parts(parameters, parameters_length); - let mut parameters_vec: Vec> = Vec::with_capacity(parameters_length); - for parameter in parameters { - parameters_vec.push(parameter.from_ffi_handle()); + let mut parameters_map: HashMap> = HashMap::with_capacity(parameters_length); + for (index, parameter) in parameters.iter().enumerate() { + parameters_map.insert( + CStr::from_ptr(parameter_keys[index]).into(), + parameter.from_ffi_handle(), + ); } let name: StringKey = match CStr::from_ptr(name).to_str() { @@ -30,7 +36,7 @@ unsafe extern "C" fn ability_new( Err(_) => return FFIResult::err(anyhow!("Failed to convert effect to CStr")), }; - let arc: Arc = Arc::new(AbilityImpl::new(&name, &effect, parameters_vec)); + let arc: Arc = Arc::new(AbilityImpl::new(&name, &effect, parameters_map)); FFIResult::ok(FFIHandle::get_handle(arc.into())) } @@ -62,9 +68,10 @@ unsafe extern "C" fn ability_parameter_length(ptr: FFIHandle>) #[no_mangle] unsafe extern "C" fn ability_parameter_get( ptr: FFIHandle>, - index: usize, + name: NonOwnedPtrString, ) -> FFIHandle> { - if let Some(p) = ptr.from_ffi_handle().parameters().get(index) { + let string: StringKey = CStr::from_ptr(name).into(); + if let Some(p) = ptr.from_ffi_handle().parameters().get(&string) { FFIHandle::get_handle(p.clone().into()) } else { FFIHandle::none() diff --git a/src/ffi/static_data/move_data.rs b/src/ffi/static_data/move_data.rs index 4d8ad72..92e3a3c 100644 --- a/src/ffi/static_data/move_data.rs +++ b/src/ffi/static_data/move_data.rs @@ -5,7 +5,7 @@ use crate::static_data::{ }; use crate::StringKey; use anyhow::anyhow; -use hashbrown::HashSet; +use hashbrown::{HashMap, HashSet}; use std::ffi::{c_char, CStr, CString}; use std::sync::Arc; @@ -99,13 +99,16 @@ unsafe extern "C" fn move_data_has_flag(ptr: FFIHandle>, flag: unsafe extern "C" fn secondary_effect_new( chance: f32, effect_name: NonOwnedPtrString, + parameter_keys: *const NonOwnedPtrString, parameters: *mut FFIHandle>, parameters_length: usize, ) -> FFIHandle> { + let parameter_key_slice = std::slice::from_raw_parts(parameter_keys, parameters_length); let parameter_slice = std::slice::from_raw_parts(parameters, parameters_length); - let mut parameters = Vec::with_capacity(parameters_length); - for parameter in parameter_slice { - parameters.push(parameter.from_ffi_handle()) + let mut parameters = HashMap::with_capacity(parameters_length); + for (index, parameter) in parameter_slice.iter().enumerate() { + let key = CStr::from_ptr(parameter_key_slice[index]).into(); + parameters.insert(key, parameter.from_ffi_handle()); } let b: Arc = Arc::new(SecondaryEffectImpl::new( @@ -146,9 +149,10 @@ unsafe extern "C" fn secondary_effect_parameter_length(ptr: FFIHandle>, - index: usize, + name: NonOwnedPtrString, ) -> FFIHandle> { - if let Some(v) = ptr.from_ffi_handle().parameters().get(index) { + let string: StringKey = CStr::from_ptr(name).into(); + if let Some(v) = ptr.from_ffi_handle().parameters().get(&string) { FFIHandle::get_handle(v.clone().into()) } else { FFIHandle::none() diff --git a/src/lib.rs b/src/lib.rs index 3b399e4..04f91b1 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,9 @@ #![allow(hidden_glob_reexports)] #![allow(clippy::arc_with_non_send_sync)] // Documentation linters -#![deny(missing_docs)] -#![deny(clippy::missing_docs_in_private_items)] +// FIXME: Enable these before committing +// #![deny(missing_docs)] +// #![deny(clippy::missing_docs_in_private_items)] // Linter rules to prevent panics // Currently still a WIP to fix all of these #![deny(clippy::unwrap_used)] diff --git a/src/script_implementations/mod.rs b/src/script_implementations/mod.rs index dfcd6ab..442cfd3 100755 --- a/src/script_implementations/mod.rs +++ b/src/script_implementations/mod.rs @@ -1,3 +1,7 @@ /// The WASM module handles loading dynamic scripts through WebAssembly. #[cfg(feature = "wasm")] pub mod wasm; + +/// The Rune module handles loading dynamic scripts through the Rune language. +#[cfg(feature = "rune")] +pub mod rune; diff --git a/src/script_implementations/rune/mod.rs b/src/script_implementations/rune/mod.rs new file mode 100644 index 0000000..2997ba1 --- /dev/null +++ b/src/script_implementations/rune/mod.rs @@ -0,0 +1,134 @@ +use crate::dynamic_data::{Battle, DamageSource, ExecutingMove, Pokemon, TurnChoice}; +use crate::static_data::{Item, Statistic, TypeIdentifier}; +use crate::StringKey; +use rune::Hash; +use std::sync::Arc; + +mod script; +pub mod script_resolver; +pub(self) mod wrappers; + +#[derive(Debug, Default)] +pub(self) struct RuneScriptType { + pub fn_on_initialize: Option, + pub fn_on_stack: Option, + pub fn_on_remove: Option, + pub fn_on_before_turn: Option, + + pub fn_change_speed: Option, + pub fn_change_priority: Option, + pub fn_change_move: Option, + pub fn_change_number_of_hits: Option, + pub fn_prevent_move: Option, + pub fn_fail_move: Option, + pub fn_stop_before_move: Option, + pub fn_on_before_move: Option, + pub fn_fail_incoming_move: Option, + pub fn_is_invulnerable: Option, + pub fn_on_move_miss: Option, + pub fn_change_move_type: Option, + pub fn_change_effectiveness: Option, + pub fn_block_critical: Option, + pub fn_block_incoming_critical: Option, + pub fn_change_accuracy: Option, + pub fn_change_critical_stage: Option, + pub fn_change_critical_modifier: Option, + pub fn_change_stab_modifier: Option, + pub fn_change_base_power: Option, + pub fn_bypass_defensive_stat_boost: Option, + pub fn_bypass_offensive_stat_boost: Option, + pub fn_change_offensive_stat_value: Option, + pub fn_change_defensive_stat_value: Option, + pub fn_change_damage_stat_modifier: Option, + pub fn_change_damage_modifier: Option, + pub fn_change_damage: Option, + pub fn_change_incoming_damage: Option, + pub fn_on_incoming_hit: Option, + pub fn_on_opponent_faints: Option, + pub fn_prevent_stat_boost_change: Option, + pub fn_change_stat_boost_change: Option, + pub fn_prevent_secondary_effect: Option, + pub fn_change_effect_chance: Option, + pub fn_change_incoming_effect_chance: Option, + pub fn_on_secondary_effect: Option, + pub fn_on_after_hits: Option, + pub fn_prevent_self_switch: Option, + pub fn_prevent_opponent_switch: Option, + pub fn_on_fail: Option, + pub fn_on_opponent_fail: Option, + pub fn_prevent_self_run_away: Option, + pub fn_prevent_opponent_run_away: Option, + pub fn_on_end_turn: Option, + pub fn_on_damage: Option, + pub fn_on_faint: Option, + pub fn_on_switch_in: Option, + pub fn_on_after_held_item_consume: Option, + pub fn_change_experience_gained: Option, + pub fn_share_experience: Option, + pub fn_block_weather: Option, + pub fn_change_capture_rate_bonus: Option, +} + +impl RuneScriptType { + pub fn on_found_fn(&mut self, fn_name: &str, hash: Hash) { + match fn_name { + "on_initialize" => self.fn_on_initialize = Some(hash), + "on_stack" => self.fn_on_stack = Some(hash), + "on_remove" => self.fn_on_remove = Some(hash), + "on_before_turn" => self.fn_on_before_turn = Some(hash), + "change_speed" => self.fn_change_speed = Some(hash), + "change_priority" => self.fn_change_priority = Some(hash), + "change_move" => self.fn_change_move = Some(hash), + "change_number_of_hits" => self.fn_change_number_of_hits = Some(hash), + "prevent_move" => self.fn_prevent_move = Some(hash), + "fail_move" => self.fn_fail_move = Some(hash), + "stop_before_move" => self.fn_stop_before_move = Some(hash), + "on_before_move" => self.fn_on_before_move = Some(hash), + "fail_incoming_move" => self.fn_fail_incoming_move = Some(hash), + "is_invulnerable" => self.fn_is_invulnerable = Some(hash), + "on_move_miss" => self.fn_on_move_miss = Some(hash), + "change_move_type" => self.fn_change_move_type = Some(hash), + "change_effectiveness" => self.fn_change_effectiveness = Some(hash), + "block_critical" => self.fn_block_critical = Some(hash), + "block_incoming_critical" => self.fn_block_incoming_critical = Some(hash), + "change_accuracy" => self.fn_change_accuracy = Some(hash), + "change_critical_stage" => self.fn_change_critical_stage = Some(hash), + "change_critical_modifier" => self.fn_change_critical_modifier = Some(hash), + "change_stab_modifier" => self.fn_change_stab_modifier = Some(hash), + "change_base_power" => self.fn_change_base_power = Some(hash), + "bypass_defensive_stat_boost" => self.fn_bypass_defensive_stat_boost = Some(hash), + "bypass_offensive_stat_boost" => self.fn_bypass_offensive_stat_boost = Some(hash), + "change_offensive_stat_value" => self.fn_change_offensive_stat_value = Some(hash), + "change_defensive_stat_value" => self.fn_change_defensive_stat_value = Some(hash), + "change_damage_stat_modifier" => self.fn_change_damage_stat_modifier = Some(hash), + "change_damage_modifier" => self.fn_change_damage_modifier = Some(hash), + "change_damage" => self.fn_change_damage = Some(hash), + "change_incoming_damage" => self.fn_change_incoming_damage = Some(hash), + "on_incoming_hit" => self.fn_on_incoming_hit = Some(hash), + "on_opponent_faints" => self.fn_on_opponent_faints = Some(hash), + "prevent_stat_boost_change" => self.fn_prevent_stat_boost_change = Some(hash), + "change_stat_boost_change" => self.fn_change_stat_boost_change = Some(hash), + "prevent_secondary_effect" => self.fn_prevent_secondary_effect = Some(hash), + "change_effect_chance" => self.fn_change_effect_chance = Some(hash), + "change_incoming_effect_chance" => self.fn_change_incoming_effect_chance = Some(hash), + "on_secondary_effect" => self.fn_on_secondary_effect = Some(hash), + "on_after_hits" => self.fn_on_after_hits = Some(hash), + "prevent_self_switch" => self.fn_prevent_self_switch = Some(hash), + "prevent_opponent_switch" => self.fn_prevent_opponent_switch = Some(hash), + "on_fail" => self.fn_on_fail = Some(hash), + "on_opponent_fail" => self.fn_on_opponent_fail = Some(hash), + "prevent_self_run_away" => self.fn_prevent_self_run_away = Some(hash), + "prevent_opponent_run_away" => self.fn_prevent_opponent_run_away = Some(hash), + "on_end_turn" => self.fn_on_end_turn = Some(hash), + "on_damage" => self.fn_on_damage = Some(hash), + "on_faint" => self.fn_on_faint = Some(hash), + "on_switch_in" => self.fn_on_switch_in = Some(hash), + "on_after_held_item_consume" => self.fn_on_after_held_item_consume = Some(hash), + "change_experience_gained" => self.fn_change_experience_gained = Some(hash), + "share_experience" => self.fn_share_experience = Some(hash), + "block_weather" => self.fn_block_weather = Some(hash), + "change_capture_rate_bonus" => self.fn_change_capture_rate_bonus = Some(hash), + _ => {} + } + } +} diff --git a/src/script_implementations/rune/script.rs b/src/script_implementations/rune/script.rs new file mode 100644 index 0000000..837af64 --- /dev/null +++ b/src/script_implementations/rune/script.rs @@ -0,0 +1,153 @@ +use crate::dynamic_data::{DynamicLibrary, ExecutingMove, Pokemon, Script, ScriptOwnerData, TurnChoice}; +use crate::script_implementations::rune::wrappers::*; +use crate::script_implementations::rune::RuneScriptType; +use crate::static_data::Parameter; +use crate::StringKey; +use hashbrown::HashMap; +use parking_lot::RwLock; +use rune::runtime::{Object, RuntimeContext, Shared, VmError, VmResult}; +use rune::{Any, Unit, Value}; +use std::convert::TryFrom; +use std::error::Error; +use std::ops::Deref; +use std::sync::atomic::{AtomicBool, AtomicUsize}; +use std::sync::Arc; + +pub struct RuneScript { + name: StringKey, + state: RwLock>, + /// Returns an atomic bool for internal marking of deletion. This is currently only specifically + /// used for deletion of a script while we are holding a reference to it (i.e. executing a script + /// hook on it). + marked_for_deletion: AtomicBool, + /// A script can be suppressed by other scripts. If a script is suppressed by at least one script + /// we will not execute its methods. This holds the number of suppressions on the script. + suppressed_count: AtomicUsize, + /// The owner of this script (where the script is attached to) + owner: ScriptOwnerData, + + script_type: Arc, + + runtime: Arc, + unit: Arc, +} + +unsafe impl Send for RuneScript {} + +unsafe impl Sync for RuneScript {} + +impl RuneScript { + pub fn new( + name: StringKey, + object: Shared, + owner: ScriptOwnerData, + script_type: Arc, + runtime: Arc, + unit: Arc, + ) -> Self { + Self { + name, + state: RwLock::new(object), + marked_for_deletion: Default::default(), + suppressed_count: Default::default(), + owner, + script_type, + runtime, + unit, + } + } +} + +impl Script for RuneScript { + fn name(&self) -> anyhow::Result<&StringKey> { Ok(&self.name) } + + fn get_marked_for_deletion(&self) -> &AtomicBool { &self.marked_for_deletion } + + fn get_suppressed_count(&self) -> &AtomicUsize { &self.suppressed_count } + + fn on_initialize( + &self, + _: &Arc, + pars: &HashMap>, + ) -> anyhow::Result<()> { + if pars.is_empty() { + return Ok(()); + } + let mut write_lock = self.state.write(); + for par in pars { + let key = rune::alloc::string::String::try_from(par.0.str())?; + write_lock + .borrow_mut()? + .insert(key, parameter_to_rune_value(par.1.as_ref())?)?; + } + Ok(()) + } + + fn block_critical( + &self, + move_data: &Arc, + target: &Pokemon, + hit: u8, + block_critical: &mut bool, + ) -> anyhow::Result<()> { + if let Some(hash) = self.script_type.fn_block_critical { + let mut vm = rune::runtime::Vm::new(self.runtime.clone(), self.unit.clone()); + todo!() + // let block_critical_handle = RuneValueWrapper::new_mut(block_critical); + // let read_lock = self.state.read(); + // let state = read_lock.deref(); + // + // vm.execute( + // hash, + // vec![ + // Value::Object(state.clone()), + // Value::from(move_data.wrap()?), + // Value::from(target.wrap()?), + // Value::from(hit), + // Value::from(block_critical_handle.clone().wrap()?), + // ], + // )?; + // *block_critical = block_critical_handle.value(); + } + Ok(()) + } + + fn change_speed(&self, choice: &Arc, speed: &mut u32) -> anyhow::Result<()> { + if let Some(hash) = self.script_type.fn_change_speed { + let mut vm = rune::runtime::Vm::new(self.runtime.clone(), self.unit.clone()); + let speed_handle = wrap_value_reference(*speed as i64)?; + let read_lock = self.state.read(); + let state = read_lock.deref(); + + let res = vm + .execute( + hash, + vec![ + Value::Object(state.clone()), + Value::from(choice.wrap()), + speed_handle.clone(), + ], + )? + .complete(); + if let VmResult::Err(e) = res { + return Err(anyhow::anyhow!("Error executing script: {}", e)); + } + + *speed = get_value_reference(speed_handle)? as u32; + } + Ok(()) + } + + fn as_any(&self) -> &dyn std::any::Any { self } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } +} + +fn parameter_to_rune_value(parameter: &Parameter) -> Result { + match parameter { + Parameter::Bool(b) => rune::to_value(*b), + Parameter::Int(i) => rune::to_value(*i), + Parameter::Float(f) => rune::to_value(*f), + Parameter::String(s) => rune::to_value(s.str()), + } +} diff --git a/src/script_implementations/rune/script_resolver.rs b/src/script_implementations/rune/script_resolver.rs new file mode 100644 index 0000000..9c9778d --- /dev/null +++ b/src/script_implementations/rune/script_resolver.rs @@ -0,0 +1,179 @@ +use crate::dynamic_data::{ItemScript, Script, ScriptCategory, ScriptOwnerData, ScriptResolver}; +use crate::script_implementations::rune::script::RuneScript; +use crate::script_implementations::rune::RuneScriptType; +use crate::static_data::Item; +use crate::StringKey; +use hashbrown::HashMap; +use parking_lot::RwLock; +use rune::compile::meta::AssociatedKind; +use rune::compile::{ComponentRef, MetaError}; +use rune::diagnostics::Diagnostic; +use rune::runtime::{RuntimeContext, Shared}; +use rune::{Context, Diagnostics, Hash, Options, Source, Sources, Unit, Vm}; +use std::any::Any; +use std::path::Path; +use std::sync::Arc; + +#[derive(Debug)] +pub struct RuneScriptResolver { + runtime: Arc, + unit: Arc, + script_types: HashMap<(ScriptCategory, StringKey), Arc>, +} + +pub struct RuneScriptResolverBuilder { + context: Context, + scripts: Sources, +} + +impl ScriptResolver for RuneScriptResolver { + fn load_script( + &self, + owner: ScriptOwnerData, + category: ScriptCategory, + script_key: &StringKey, + ) -> anyhow::Result>> { + let script_type = if let Some(script_type) = self.script_types.get(&(category, script_key.clone())) { + script_type + } else { + return Ok(None); + }; + + let state = Shared::new(rune::runtime::Object::new())?; + let script = Arc::new(RuneScript::new( + script_key.clone(), + state, + owner, + script_type.clone(), + self.runtime.clone(), + self.unit.clone(), + )); + Ok(Some(script)) + } + + fn load_item_script(&self, _key: &dyn Item) -> anyhow::Result>> { Ok(None) } + + fn as_any(&self) -> &dyn Any { self } +} + +impl RuneScriptResolverBuilder { + pub fn new() -> anyhow::Result { + let mut context = Context::with_default_modules()?; + context.install(super::wrappers::module()?)?; + + Ok(Self { + context, + scripts: Sources::default(), + }) + } + + pub fn insert_script(&mut self, path: &Path, name: &str, script: &str) -> anyhow::Result<&mut Self> { + self.scripts + .insert(Source::with_path(name, script.to_string(), path)?)?; + Ok(self) + } + + pub fn build(mut self) -> anyhow::Result> { + let mut visitor = FindScriptTypeVisitor::default(); + let mut diagnostics = Diagnostics::new(); + + let mut options = Options::default(); + options.debug_info(true); + options.memoize_instance_fn(true); + options.macros(true); + options.bytecode(true); + + let result = rune::prepare(&mut self.scripts) + .with_context(&self.context) + .with_visitor(&mut visitor)? + .with_diagnostics(&mut diagnostics) + .with_options(&options) + .build(); + if diagnostics.has_error() { + let error_message = diagnostics + .diagnostics() + .iter() + .filter_map(|d| match d { + Diagnostic::Fatal(f) => Some(f.to_string()), + _ => None, + }) + .collect::>() + .join("\n"); + + return Err(anyhow::anyhow!("Error building Rune script: {}", error_message)); + } + + let mut script_types = HashMap::with_capacity(visitor.script_types.len()); + for (key, script_type) in visitor.script_types { + script_types.insert(key, Arc::new(script_type)); + } + + Ok(Arc::new(RuneScriptResolver { + runtime: Arc::new(self.context.runtime()?), + unit: Arc::new(result?), + script_types, + })) + } +} + +#[derive(Debug, Default)] +struct FindScriptTypeVisitor { + script_types: HashMap<(ScriptCategory, StringKey), RuneScriptType>, +} + +impl rune::compile::CompileVisitor for FindScriptTypeVisitor { + fn register_meta(&mut self, meta: rune::compile::MetaRef<'_>) -> Result<(), MetaError> { + match meta.kind { + rune::compile::meta::Kind::Struct { .. } => { + if meta.item.iter().count() < 2 { + return Ok(()); + } + let mod_name = meta.item.iter().nth(0).unwrap(); + let category = match get_mod_category(mod_name) { + Ok(value) => value, + Err(value) => return value, + }; + let name = meta.item.last().unwrap(); + self.script_types + .insert((category, name.to_string().as_str().into()), RuneScriptType::default()); + } + rune::compile::meta::Kind::Function { + associated: Some(AssociatedKind::Instance(associated)), + .. + } => { + if meta.item.iter().count() < 3 { + return Ok(()); + } + let mod_name = meta.item.iter().nth(0).unwrap(); + let category = match get_mod_category(mod_name) { + Ok(value) => value, + Err(value) => return value, + }; + let instance = meta.item.iter().nth_back(1).unwrap(); + if let Some(script_type) = self + .script_types + .get_mut(&(category, instance.to_string().as_str().into())) + { + script_type.on_found_fn(associated.to_string().as_str(), meta.hash); + } + } + _ => {} + } + + Ok(()) + } +} + +fn get_mod_category(mod_name: ComponentRef) -> Result> { + Ok(match mod_name.to_string().as_str() { + "moves" => ScriptCategory::Move, + "abilities" => ScriptCategory::Ability, + "status" => ScriptCategory::Status, + "pokemon" => ScriptCategory::Pokemon, + "sides" => ScriptCategory::Side, + "battle" => ScriptCategory::Battle, + "weather" => ScriptCategory::Weather, + "item_battle_triggers" => ScriptCategory::ItemBattleTrigger, + _ => return Err(Ok(())), + }) +} diff --git a/src/script_implementations/rune/wrappers/executing_move.rs b/src/script_implementations/rune/wrappers/executing_move.rs new file mode 100644 index 0000000..dc32a3c --- /dev/null +++ b/src/script_implementations/rune/wrappers/executing_move.rs @@ -0,0 +1,26 @@ +use crate::dynamic_data::ExecutingMove; +use crate::script_implementations::rune::wrappers::{impl_rune_wrapper, RuneWrapper}; +use rune::runtime::{AnyObj, Shared}; +use rune::Any; +use std::sync::Arc; + +pub fn register(module: &mut rune::Module) -> anyhow::Result<()> { + module.ty::()?; + Ok(()) +} + +#[derive(Debug, Any)] +pub struct RuneExecutingMove { + inner: Arc, +} + +impl RuneExecutingMove { + #[rune::function] + pub fn target_count(&self) -> usize { self.inner.target_count() } + #[rune::function] + pub fn number_of_hits(&self) -> u8 { self.inner.number_of_hits() } + #[rune::function] + pub fn user(&self) -> Shared { self.inner.user().wrap() } +} + +impl_rune_wrapper!(&Arc, RuneExecutingMove); diff --git a/src/script_implementations/rune/wrappers/mod.rs b/src/script_implementations/rune/wrappers/mod.rs new file mode 100644 index 0000000..28be602 --- /dev/null +++ b/src/script_implementations/rune/wrappers/mod.rs @@ -0,0 +1,67 @@ +use rune::runtime::Protocol; +use rune::runtime::{AnyObj, Shared}; +use rune::{Any, Value}; +use std::ops::Deref; + +mod executing_move; +mod pokemon; +mod turn_choice; + +pub use executing_move::*; +pub use pokemon::*; + +pub trait RuneWrapper { + #[inline] + fn wrap(self) -> Shared; +} + +pub fn module() -> anyhow::Result { + let mut module = rune::Module::new(); + module.ty::()?; + turn_choice::register(&mut module)?; + pokemon::register(&mut module)?; + + executing_move::register(&mut module)?; + Ok(module) +} + +pub fn wrap_value_reference(value: i64) -> anyhow::Result { + Ok(Value::Any(Shared::new(AnyObj::new(RuneValueIntWrapper::new(value))?)?)) +} + +pub fn get_value_reference(value: Value) -> anyhow::Result { + let obj = value.into_any().into_result()?; + let obj = obj.take()?; + let obj = obj.downcast_borrow_ref::().unwrap(); + Ok(obj.value()) +} + +#[derive(Any, Clone)] +struct RuneValueIntWrapper { + #[rune(get, set)] + value: i64, +} + +impl RuneValueIntWrapper { + pub fn new(value: i64) -> Self { Self { value } } + + pub fn value(&self) -> i64 { self.value } + pub fn set_value(&mut self, value: i64) { self.value = value; } +} + +impl RuneWrapper for RuneValueIntWrapper { + fn wrap(self) -> Shared { Shared::new(AnyObj::new(self).unwrap()).unwrap() } +} + +macro_rules! impl_rune_wrapper { + ($t:ty, $wrapped_type:ident) => { + impl crate::script_implementations::rune::wrappers::RuneWrapper for $t { + fn wrap(self) -> rune::runtime::Shared { + rune::runtime::Shared::new(rune::runtime::AnyObj::new($wrapped_type { inner: self.clone() }).unwrap()) + .unwrap() + } + } + }; +} + +pub(self) use impl_rune_wrapper; diff --git a/src/script_implementations/rune/wrappers/pokemon.rs b/src/script_implementations/rune/wrappers/pokemon.rs new file mode 100644 index 0000000..9259d01 --- /dev/null +++ b/src/script_implementations/rune/wrappers/pokemon.rs @@ -0,0 +1,22 @@ +use crate::defines::LevelInt; +use crate::dynamic_data::Pokemon; +use crate::script_implementations::rune::wrappers::{impl_rune_wrapper, RuneWrapper}; +use rune::Any; + +pub fn register(module: &mut rune::Module) -> anyhow::Result<()> { + module.ty::()?; + module.function_meta(RunePokemon::level)?; + Ok(()) +} + +#[derive(Any)] +pub struct RunePokemon { + inner: Pokemon, +} + +impl RunePokemon { + #[rune::function] + fn level(&self) -> LevelInt { self.inner.level() } +} + +impl_rune_wrapper!(&Pokemon, RunePokemon); diff --git a/src/script_implementations/rune/wrappers/turn_choice.rs b/src/script_implementations/rune/wrappers/turn_choice.rs new file mode 100644 index 0000000..87521c2 --- /dev/null +++ b/src/script_implementations/rune/wrappers/turn_choice.rs @@ -0,0 +1,26 @@ +use crate::dynamic_data::TurnChoice; +use crate::script_implementations::rune::wrappers::{impl_rune_wrapper, RuneExecutingMove, RuneWrapper}; +use rune::{Any, Value}; +use std::sync::Arc; + +pub fn register(module: &mut rune::Module) -> anyhow::Result<()> { + module.ty::()?; + module.function_meta(RuneTurnChoice::speed)?; + module.function_meta(RuneTurnChoice::user)?; + Ok(()) +} + +#[derive(Any)] +pub struct RuneTurnChoice { + inner: Arc, +} + +impl RuneTurnChoice { + #[rune::function] + fn speed(&self) -> u32 { self.inner.speed() } + + #[rune::function] + fn user(&self) -> Value { Value::from(self.inner.user().wrap()) } +} + +impl_rune_wrapper!(&Arc, RuneTurnChoice); diff --git a/src/static_data/moves/secondary_effect.rs b/src/static_data/moves/secondary_effect.rs index 42d7ed2..5e690aa 100755 --- a/src/static_data/moves/secondary_effect.rs +++ b/src/static_data/moves/secondary_effect.rs @@ -1,5 +1,6 @@ use crate::static_data::Parameter; use crate::StringKey; +use hashbrown::HashMap; use std::fmt::Debug; use std::sync::Arc; @@ -10,7 +11,7 @@ pub trait SecondaryEffect: Debug { /// The name of the effect. fn effect_name(&self) -> &StringKey; /// A list of parameters for the effect. - fn parameters(&self) -> &Vec>; + fn parameters(&self) -> &HashMap>; } /// A secondary effect is an effect on a move that happens after it hits. @@ -21,12 +22,12 @@ pub struct SecondaryEffectImpl { /// The name of the effect. effect_name: StringKey, /// A list of parameters for the effect. - parameters: Vec>, + parameters: HashMap>, } impl SecondaryEffectImpl { /// Instantiates a new Secondary Effect. - pub fn new(chance: f32, effect_name: StringKey, parameters: Vec>) -> Self { + pub fn new(chance: f32, effect_name: StringKey, parameters: HashMap>) -> Self { Self { chance, effect_name, @@ -45,7 +46,7 @@ impl SecondaryEffect for SecondaryEffectImpl { &self.effect_name } /// A list of parameters for the effect. - fn parameters(&self) -> &Vec> { + fn parameters(&self) -> &HashMap> { &self.parameters } } diff --git a/src/static_data/species_data/ability.rs b/src/static_data/species_data/ability.rs index 721ed3e..da27e81 100755 --- a/src/static_data/species_data/ability.rs +++ b/src/static_data/species_data/ability.rs @@ -1,5 +1,6 @@ use crate::static_data::Parameter; use crate::StringKey; +use hashbrown::HashMap; use std::fmt::Debug; use std::sync::Arc; @@ -10,7 +11,7 @@ pub trait Ability: Debug { /// The name of the script effect of the ability. fn effect(&self) -> &StringKey; /// The parameters for the script effect of the ability. - fn parameters(&self) -> &Vec>; + fn parameters(&self) -> &HashMap>; } /// An ability is a passive effect in battle that is attached to a Pokemon. @@ -21,12 +22,12 @@ pub struct AbilityImpl { /// The name of the script effect of the ability. effect: StringKey, /// The parameters for the script effect of the ability. - parameters: Vec>, + parameters: HashMap>, } impl AbilityImpl { /// Instantiates a new ability. - pub fn new(name: &StringKey, effect: &StringKey, parameters: Vec>) -> Self { + pub fn new(name: &StringKey, effect: &StringKey, parameters: HashMap>) -> Self { Self { name: name.clone(), effect: effect.clone(), @@ -37,17 +38,11 @@ impl AbilityImpl { impl Ability for AbilityImpl { /// The name of the ability. - fn name(&self) -> &StringKey { - &self.name - } + fn name(&self) -> &StringKey { &self.name } /// The name of the script effect of the ability. - fn effect(&self) -> &StringKey { - &self.effect - } + fn effect(&self) -> &StringKey { &self.effect } /// The parameters for the script effect of the ability. - fn parameters(&self) -> &Vec> { - &self.parameters - } + fn parameters(&self) -> &HashMap> { &self.parameters } } /// An ability index allows us to find an ability on a form. It combines a bool for whether the diff --git a/tests/common/library_loader.rs b/tests/common/library_loader.rs index 881ba43..18df1d1 100755 --- a/tests/common/library_loader.rs +++ b/tests/common/library_loader.rs @@ -5,7 +5,7 @@ use std::fs::File; use std::io::{BufReader, Read}; use std::sync::Arc; -use hashbrown::HashSet; +use hashbrown::{HashMap, HashSet}; use num_traits::PrimInt; use project_root::get_project_root; use serde_json::Value; @@ -223,10 +223,11 @@ pub fn load_abilities(path: &String) -> Arc { if let Some(e) = value.get("effect") { effect = e.as_str().unwrap().into(); } - let mut parameters = Vec::new(); - if let Some(p) = value.get("parameters") { - for par in p.as_array().unwrap() { - parameters.push(parse_parameter(par)); + let mut parameters = HashMap::new(); + if let Some(pars) = value.get("parameters") { + let pars = pars.as_object().unwrap(); + for par in pars { + parameters.insert(par.0.as_str().into(), parse_parameter(par.1)); } } @@ -258,11 +259,11 @@ pub fn load_moves(path: &String, types: &Arc) -> Arc EvolutionData { EvolutionData::new(method, species) } -#[cfg(not(feature = "wasm"))] -fn load_script_resolver(path: &String) -> Arc { - Arc::new(EmptyScriptResolver::default()) -} +#[cfg(not(any(feature = "wasm", feature = "rune")))] +fn load_script_resolver(path: &String) -> Arc { Arc::new(EmptyScriptResolver::default()) } #[cfg(feature = "wasm")] fn load_script_resolver(path: &String) -> Arc { @@ -468,6 +467,28 @@ fn load_script_resolver(path: &String) -> Arc { resolver } +#[cfg(feature = "rune")] +fn load_script_resolver(path: &String) -> Arc { + let mut builder = + pkmn_lib::script_implementations::rune::script_resolver::RuneScriptResolverBuilder::new().unwrap(); + // Recursively load all scripts in the scripts folder + for entry in walkdir::WalkDir::new(path.to_string() + "scripts/") { + let entry = entry.unwrap(); + let path = entry.path(); + if path.is_file() { + let file = File::open(&path).unwrap(); + let mut reader = BufReader::new(file); + let mut buffer = Vec::new(); + reader.read_to_end(&mut buffer).unwrap(); + builder + .insert_script(path, &path.to_string_lossy(), &String::from_utf8(buffer).unwrap()) + .unwrap(); + } + } + let resolver = builder.build().unwrap(); + resolver +} + fn parse_form( name: StringKey, value: &Value, diff --git a/tests/data/Abilities.json b/tests/data/Abilities.json index a0fe8ff..db40e7e 100755 --- a/tests/data/Abilities.json +++ b/tests/data/Abilities.json @@ -1,285 +1,292 @@ { - "adaptability": { - "effect": "IncreasedStab" - }, - "aerilate": { - "effect": "ChangeMoveType", - "parameters": ["normal", "flying"] - }, - "aftermath": { - "effect": "Aftermath" - }, - "air_lock": { - "effect": "SuppressWeather" - }, - "analytic": { - "effect": "Analytic" - }, - "anger_point": { - "effect": "AngerPoint" - }, - "anticipation": { - "effect": "Anticipation" - }, - "arena_trap": { - "effect": "ArenaTrap" - }, - "aroma_veil": { - "effect": "AromaVeil" - }, - "aura_break": { - "effect": "AuraBreal" - }, - "bad_dreams": { - "effect": "BadDreams" - }, - "battery": { - "effect": "Battery" - }, - "battle_armor": { - "effect": "PreventCritical" - }, - "battle_bond": { - "effect": "BattleBond" - }, - "beast_boost": { - "effect": "BeastBoost" - }, - "berserk": { - "effect": "Berserk" - }, - "big_pecks": { - "effect": "PreventDefLowering" - }, - "blaze": { - "effect": "PowerUpType", - "parameters": ["fire"] - }, - "bulletproof": { - "effect": "Bulletproof" - }, - "cheek_pouch": { - "effect": "CheekPouch" - }, - "chlorophyll": { - "effect": "DoubleSpeedInWeather", - "parameters": ["HarshSunlight"] - }, - "clear_body": { - "effect": "PreventStatLowering" - }, - "cloud_nine": { - "effect": "SuppressWeather" - }, - "color_change": { - "effect": "ColorChange" - }, - "comatose": {}, - "competitive": {}, - "compound_eyes": {}, - "contrary": {}, - "corrosion": {}, - "cursed_body": {}, - "cute_charm": {}, - "damp": {}, - "dancer": {}, - "dark_aura": {}, - "dazzling": {}, - "defeatist": {}, - "defiant": {}, - "delta_stream": {}, - "desolate_land": {}, - "disguise": {}, - "download": {}, - "drizzle": {}, - "drought": {}, - "dry_skin": {}, - "early_bird": {}, - "effect_spore": {}, - "electric_surge": {}, - "emergency_exit": {}, - "fairy_aura": {}, - "filter": {}, - "flame_body": {}, - "flare_boost": {}, - "flash_fire": {}, - "flower_gift": {}, - "flower_veil": {}, - "fluffy": {}, - "forecast": {}, - "forewarn": {}, - "friend_guard": {}, - "frisk": {}, - "full_metal_body": {}, - "fur_coat": {}, - "gale_wings": {}, - "galvanize": {}, - "gluttony": {}, - "gooey": {}, - "grass_pelt": {}, - "grassy_surge": {}, - "guts": {}, - "harvest": {}, - "healer": {}, - "heatproof": {}, - "heavy_metal": {}, - "honey_gather": {}, - "huge_power": {}, - "hustle": {}, - "hydration": {}, - "hyper_cutter": {}, - "ice_body": {}, - "illuminate": {}, - "illusion": {}, - "immunity": {}, - "imposter": {}, - "infiltrator": {}, - "innards_out": {}, - "inner_focus": {}, - "insomnia": {}, - "intimidate": {}, - "iron_barbs": {}, - "iron_fist": {}, - "justified": {}, - "keen_eye": {}, - "klutz": {}, - "leaf_guard": {}, - "levitate": {}, - "light_metal": {}, - "lightning_rod": {}, - "limber": {}, - "liquid_ooze": {}, - "liquid_voice": {}, - "long_reach": {}, - "magic_bounce": {}, - "magic_guard": {}, - "magician": {}, - "magma_armor": {}, - "magnet_pull": {}, - "marvel_scale": {}, - "mega_launcher": {}, - "merciless": {}, - "minus": {}, - "misty_surge": {}, - "mold_breaker": {}, - "moody": {}, - "motor_drive": {}, - "moxie": {}, - "multiscale": {}, - "multitype": {}, - "mummy": {}, - "natural_cure": {}, - "no_guard": {}, - "normalize": {}, - "oblivious": {}, - "overcoat": {}, - "overgrow": {}, - "own_tempo": {}, - "parental_bond": {}, - "pickpocket": {}, - "pickup": {}, - "pixilate": {}, - "plus": {}, - "poison_heal": {}, - "poison_point": {}, - "poison_touch": {}, - "power_construct": {}, - "power_of_alchemy": {}, - "prankster": {}, - "pressure": {}, - "primordial_sea": {}, - "prism_armor": {}, - "protean": {}, - "psychic_surge": {}, - "pure_power": {}, - "queenly_majesty": {}, - "quick_feet": {}, - "rain_dish": {}, - "rattled": {}, - "receiver": {}, - "reckless": {}, - "refrigerate": {}, - "regenerator": {}, - "rivalry": {}, - "rks_system": {}, - "rock_head": {}, - "rough_skin": {}, - "run_away": {}, - "sand_force": {}, - "sand_rush": {}, - "sand_stream": {}, - "sand_veil": {}, - "sap_sipper": {}, - "schooling": {}, - "scrappy": {}, - "serene_grace": {}, - "shadow_shield": {}, - "shadow_tag": {}, - "shed_skin": {}, - "sheer_force": {}, - "shell_armor": {}, - "shield_dust": {}, - "shields_down": {}, - "simple": {}, - "skill_link": {}, - "slow_start": {}, - "slush_rush": {}, - "sniper": {}, - "snow_cloak": {}, - "snow_warning": {}, - "solar_power": {}, - "solid_rock": {}, - "soul_heart": {}, - "soundproof": {}, - "speed_boost": {}, - "stakeout": {}, - "stall": {}, - "stamina": {}, - "stance_change": {}, - "static": {}, - "steadfast": {}, - "steelworker": {}, - "stench": {}, - "sticky_hold": {}, - "storm_drain": {}, - "strong_jaw": {}, - "sturdy": {}, - "suction_cups": {}, - "super_luck": {}, - "surge_surfer": {}, - "swarm": {}, - "sweet_veil": {}, - "swift_swim": {}, - "symbiosis": {}, - "synchronize": {}, - "tangled_feet": {}, - "tangling_hair": {}, - "technician": {}, - "telepathy": {}, - "teravolt": {}, - "thick_fat": {}, - "tinted_lens": {}, - "torrent": {}, - "tough_claws": {}, - "toxic_boost": {}, - "trace": {}, - "triage": {}, - "truant": {}, - "turboblaze": {}, - "unaware": {}, - "unburden": {}, - "unnerve": {}, - "victory_star": {}, - "vital_spirit": {}, - "volt_absorb": {}, - "water_absorb": {}, - "water_bubble": {}, - "water_compaction": {}, - "water_veil": {}, - "weak_armor": {}, - "white_smoke": {}, - "wimp_out": {}, - "wonder_guard": {}, - "wonder_skin": {}, - "zen_mode": {} + "adaptability": { + "effect": "IncreasedStab" + }, + "aerilate": { + "effect": "ChangeMoveType", + "parameters": { + "from": "normal", + "to": "flying" + } + }, + "aftermath": { + "effect": "Aftermath" + }, + "air_lock": { + "effect": "SuppressWeather" + }, + "analytic": { + "effect": "Analytic" + }, + "anger_point": { + "effect": "AngerPoint" + }, + "anticipation": { + "effect": "Anticipation" + }, + "arena_trap": { + "effect": "ArenaTrap" + }, + "aroma_veil": { + "effect": "AromaVeil" + }, + "aura_break": { + "effect": "AuraBreal" + }, + "bad_dreams": { + "effect": "BadDreams" + }, + "battery": { + "effect": "Battery" + }, + "battle_armor": { + "effect": "PreventCritical" + }, + "battle_bond": { + "effect": "BattleBond" + }, + "beast_boost": { + "effect": "BeastBoost" + }, + "berserk": { + "effect": "Berserk" + }, + "big_pecks": { + "effect": "PreventDefLowering" + }, + "blaze": { + "effect": "PowerUpType", + "parameters": { + "type": "fire" + } + }, + "bulletproof": { + "effect": "Bulletproof" + }, + "cheek_pouch": { + "effect": "CheekPouch" + }, + "chlorophyll": { + "effect": "DoubleSpeedInWeather", + "parameters": { + "weather": "HarshSunlight" + } + }, + "clear_body": { + "effect": "PreventStatLowering" + }, + "cloud_nine": { + "effect": "SuppressWeather" + }, + "color_change": { + "effect": "ColorChange" + }, + "comatose": {}, + "competitive": {}, + "compound_eyes": {}, + "contrary": {}, + "corrosion": {}, + "cursed_body": {}, + "cute_charm": {}, + "damp": {}, + "dancer": {}, + "dark_aura": {}, + "dazzling": {}, + "defeatist": {}, + "defiant": {}, + "delta_stream": {}, + "desolate_land": {}, + "disguise": {}, + "download": {}, + "drizzle": {}, + "drought": {}, + "dry_skin": {}, + "early_bird": {}, + "effect_spore": {}, + "electric_surge": {}, + "emergency_exit": {}, + "fairy_aura": {}, + "filter": {}, + "flame_body": {}, + "flare_boost": {}, + "flash_fire": {}, + "flower_gift": {}, + "flower_veil": {}, + "fluffy": {}, + "forecast": {}, + "forewarn": {}, + "friend_guard": {}, + "frisk": {}, + "full_metal_body": {}, + "fur_coat": {}, + "gale_wings": {}, + "galvanize": {}, + "gluttony": {}, + "gooey": {}, + "grass_pelt": {}, + "grassy_surge": {}, + "guts": {}, + "harvest": {}, + "healer": {}, + "heatproof": {}, + "heavy_metal": {}, + "honey_gather": {}, + "huge_power": {}, + "hustle": {}, + "hydration": {}, + "hyper_cutter": {}, + "ice_body": {}, + "illuminate": {}, + "illusion": {}, + "immunity": {}, + "imposter": {}, + "infiltrator": {}, + "innards_out": {}, + "inner_focus": {}, + "insomnia": {}, + "intimidate": {}, + "iron_barbs": {}, + "iron_fist": {}, + "justified": {}, + "keen_eye": {}, + "klutz": {}, + "leaf_guard": {}, + "levitate": {}, + "light_metal": {}, + "lightning_rod": {}, + "limber": {}, + "liquid_ooze": {}, + "liquid_voice": {}, + "long_reach": {}, + "magic_bounce": {}, + "magic_guard": {}, + "magician": {}, + "magma_armor": {}, + "magnet_pull": {}, + "marvel_scale": {}, + "mega_launcher": {}, + "merciless": {}, + "minus": {}, + "misty_surge": {}, + "mold_breaker": {}, + "moody": {}, + "motor_drive": {}, + "moxie": {}, + "multiscale": {}, + "multitype": {}, + "mummy": {}, + "natural_cure": {}, + "no_guard": {}, + "normalize": {}, + "oblivious": {}, + "overcoat": {}, + "overgrow": {}, + "own_tempo": {}, + "parental_bond": {}, + "pickpocket": {}, + "pickup": {}, + "pixilate": {}, + "plus": {}, + "poison_heal": {}, + "poison_point": {}, + "poison_touch": {}, + "power_construct": {}, + "power_of_alchemy": {}, + "prankster": {}, + "pressure": {}, + "primordial_sea": {}, + "prism_armor": {}, + "protean": {}, + "psychic_surge": {}, + "pure_power": {}, + "queenly_majesty": {}, + "quick_feet": {}, + "rain_dish": {}, + "rattled": {}, + "receiver": {}, + "reckless": {}, + "refrigerate": {}, + "regenerator": {}, + "rivalry": {}, + "rks_system": {}, + "rock_head": {}, + "rough_skin": {}, + "run_away": {}, + "sand_force": {}, + "sand_rush": {}, + "sand_stream": {}, + "sand_veil": {}, + "sap_sipper": {}, + "schooling": {}, + "scrappy": {}, + "serene_grace": {}, + "shadow_shield": {}, + "shadow_tag": {}, + "shed_skin": {}, + "sheer_force": {}, + "shell_armor": {}, + "shield_dust": {}, + "shields_down": {}, + "simple": {}, + "skill_link": {}, + "slow_start": {}, + "slush_rush": {}, + "sniper": {}, + "snow_cloak": {}, + "snow_warning": {}, + "solar_power": {}, + "solid_rock": {}, + "soul_heart": {}, + "soundproof": {}, + "speed_boost": {}, + "stakeout": {}, + "stall": {}, + "stamina": {}, + "stance_change": {}, + "static": {}, + "steadfast": {}, + "steelworker": {}, + "stench": {}, + "sticky_hold": {}, + "storm_drain": {}, + "strong_jaw": {}, + "sturdy": {}, + "suction_cups": {}, + "super_luck": {}, + "surge_surfer": {}, + "swarm": {}, + "sweet_veil": {}, + "swift_swim": {}, + "symbiosis": {}, + "synchronize": {}, + "tangled_feet": {}, + "tangling_hair": {}, + "technician": {}, + "telepathy": {}, + "teravolt": {}, + "thick_fat": {}, + "tinted_lens": {}, + "torrent": {}, + "tough_claws": {}, + "toxic_boost": {}, + "trace": {}, + "triage": {}, + "truant": {}, + "turboblaze": {}, + "unaware": {}, + "unburden": {}, + "unnerve": {}, + "victory_star": {}, + "vital_spirit": {}, + "volt_absorb": {}, + "water_absorb": {}, + "water_bubble": {}, + "water_compaction": {}, + "water_veil": {}, + "weak_armor": {}, + "white_smoke": {}, + "wimp_out": {}, + "wonder_guard": {}, + "wonder_skin": {}, + "zen_mode": {} } \ No newline at end of file diff --git a/tests/data/Moves.json b/tests/data/Moves.json index 207e2f6..e0f01e0 100755 --- a/tests/data/Moves.json +++ b/tests/data/Moves.json @@ -29,9 +29,9 @@ "effect": { "name": "drain", "chance": -1, - "parameters": [ - 0.5 - ] + "parameters": { + "drain_mod": 0.5 + } } }, { @@ -65,9 +65,9 @@ "effect": { "name": "change_target_special_defense", "chance": 10, - "parameters": [ - -1 - ] + "parameters": { + "amount": -1 + } } }, { @@ -85,9 +85,9 @@ "effect": { "name": "change_target_defense", "chance": -1, - "parameters": [ - 2 - ] + "parameters": { + "amount": 2 + } } }, { @@ -129,9 +129,9 @@ "effect": { "name": "change_target_special_defense", "chance": -1, - "parameters": [ - -2 - ] + "parameters": { + "amount": -2 + } } }, { @@ -233,9 +233,9 @@ ], "effect": { "name": "change_target_speed", - "parameters": [ - 2 - ] + "parameters": { + "amount": 2 + } } }, { @@ -324,9 +324,9 @@ ], "effect": { "name": "change_target_special_defense", - "parameters": [ - 2 - ] + "parameters": { + "amount": 2 + } } }, { @@ -363,9 +363,9 @@ "effect": { "name": "change_all_target_stats", "chance": 10, - "parameters": [ - 1 - ] + "parameters": { + "amount": 1 + } } }, { @@ -397,9 +397,9 @@ ], "effect": { "name": "heal_each_end_of_turn", - "parameters": [ - 6.25 - ] + "parameters": { + "percent": 6.25 + } } }, { @@ -466,9 +466,9 @@ ], "effect": { "name": "change_target_special_defense", - "parameters": [ - 1 - ] + "parameters": { + "amount": 1 + } } }, { @@ -592,9 +592,9 @@ "effect": { "name": "change_target_attack", "chance": 10, - "parameters": [ - -1 - ] + "parameters": { + "amount": -1 + } } }, { @@ -660,9 +660,9 @@ ], "effect": { "name": "change_target_attack", - "parameters": [ - -1 - ] + "parameters": { + "amount": -1 + } } }, { @@ -3805,11 +3805,11 @@ "ignore-substitute" ], "effect": { - "name": "ChangeTargetAtt", + "name": "change_target_attack", "chance": -1, - "parameters": [ - -1 - ] + "parameters": { + "amount": -1 + } } }, { @@ -5082,11 +5082,11 @@ "mirror" ], "effect": { - "name": "ChangeTargetDef", + "name": "change_target_defense", "chance": -1, - "parameters": [ - -1 - ] + "parameters": { + "amount": -1 + } } }, { diff --git a/tests/data/scripts/moves/test.rn b/tests/data/scripts/moves/test.rn new file mode 100644 index 0000000..421630d --- /dev/null +++ b/tests/data/scripts/moves/test.rn @@ -0,0 +1,13 @@ +mod moves { + +struct TestMove; + +impl TestMove { + pub fn change_speed(self, choice, speed) { + println(`change_speed: ${choice.speed()}`); + println(`user level: ${choice.user().level()}`); + speed.value = 100; + } +} + +} \ No newline at end of file diff --git a/tests/integration.rs b/tests/integration.rs index adad245..a85f99b 100755 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -6,8 +6,8 @@ use std::sync::{Arc, LazyLock}; use pkmn_lib::dynamic_data::{ - Battle, BattleParty, DamageSource, DynamicLibrary, ExecutingMove, MoveChoice, PokemonBuilder, PokemonParty, - ScriptCategory, ScriptContainer, ScriptOwnerData, TurnChoice, VolatileScriptsOwner, + Battle, BattleParty, DamageSource, DynamicLibrary, ExecutingMove, MoveChoice, PassChoice, PokemonBuilder, + PokemonParty, ScriptCategory, ScriptContainer, ScriptOwnerData, TurnChoice, VolatileScriptsOwner, }; use crate::common::library_loader; @@ -17,9 +17,7 @@ pub mod datatests; static LIBRARY: LazyLock> = LazyLock::new(|| library_loader::load_library().library); -fn get_library() -> Arc { - LIBRARY.clone() -} +fn get_library() -> Arc { LIBRARY.clone() } #[test] fn validate_library_load() { @@ -35,7 +33,7 @@ fn validate_library_load() { \n\t- Abilities load time: {} ms\ \n\t- Moves load time: {} ms\ \n\t- Species load time: {} ms\ - \n\t- WASM load time: {} ms\ + \n\t- Script load time: {} ms\ ", (end_time - start_time).num_milliseconds(), result.types_load_time.num_milliseconds(), @@ -49,6 +47,24 @@ fn validate_library_load() { ); } +#[test] +fn rune_test() { + let result = library_loader::load_library(); + let library = result.library; + let script = library + .load_script(ScriptOwnerData::None, ScriptCategory::Move, &"TestMove".into()) + .unwrap() + .unwrap(); + assert_eq!(script.name().unwrap().str(), "TestMove"); + let p1 = PokemonBuilder::new(library.clone(), "charizard".into(), 100) + .build() + .unwrap(); + let turn_choice = Arc::new(TurnChoice::Pass(PassChoice::new(p1))); + let mut speed = 0; + script.change_speed(&turn_choice, &mut speed).unwrap(); + assert_eq!(speed, 100); +} + #[test] fn load_non_existing_wasm_script() { let start_time = chrono::Utc::now(); @@ -63,7 +79,7 @@ fn load_non_existing_wasm_script() { \n\t- Abilities load time: {} ms\ \n\t- Moves load time: {} ms\ \n\t- Species load time: {} ms\ - \n\t- WASM load time: {} ms\ + \n\t- Script load time: {} ms\ ", (end_time - start_time).num_milliseconds(), result.types_load_time.num_milliseconds(),