Initial work on rune as scripting library
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Deukhoofd 2024-04-07 18:55:41 +02:00
parent 6379abf446
commit 67b0abe59f
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
24 changed files with 1186 additions and 739 deletions

View File

@ -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"
mockall = "0.12"
walkdir = "2.3"

View File

@ -1 +1,2 @@
max_width = 120
max_width = 120
fn_single_line = true

View File

@ -47,7 +47,7 @@ impl Gen7MiscLibrary {
Some(Arc::new(SecondaryEffectImpl::new(
-1.0,
StringKey::new("struggle"),
vec![],
Default::default(),
))),
HashSet::new(),
));

View File

@ -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<LearnedMove> {
&self.chosen_move
}
pub fn chosen_move(&self) -> &Arc<LearnedMove> { &self.chosen_move }
/// The move that the user is actually going to do.
pub fn use_move(&self) -> &Arc<dyn MoveData> {
&self.use_move
}
pub fn use_move(&self) -> &Arc<dyn MoveData> { &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<HitData>> {
@ -215,17 +178,11 @@ impl ExecutingMove {
}
impl ScriptSource for ExecutingMove {
fn get_script_count(&self) -> Result<usize> {
Ok(1)
}
fn get_script_count(&self) -> Result<usize> { Ok(1) }
fn get_script_source_data(&self) -> &RwLock<ScriptSourceData> {
&self.script_source_data
}
fn get_script_source_data(&self) -> &RwLock<ScriptSourceData> { &self.script_source_data }
fn get_own_scripts(&self, scripts: &mut Vec<ScriptWrapper>) {
scripts.push((&self.script).into());
}
fn get_own_scripts(&self, scripts: &mut Vec<ScriptWrapper>) { scripts.push((&self.script).into()); }
fn collect_scripts(&self, scripts: &mut Vec<ScriptWrapper>) -> Result<()> {
self.get_own_scripts(scripts);

View File

@ -216,21 +216,13 @@ impl Pokemon {
}
/// The library data of the Pokemon.
pub fn library(&self) -> &Arc<dyn DynamicLibrary> {
&self.data.library
}
pub fn library(&self) -> &Arc<dyn DynamicLibrary> { &self.data.library }
/// The species of the Pokemon.
pub fn species(&self) -> Arc<dyn Species> {
self.data.species.read().clone()
}
pub fn species(&self) -> Arc<dyn Species> { self.data.species.read().clone() }
/// The form of the Pokemon.
pub fn form(&self) -> Arc<dyn Form> {
self.data.form.read().clone()
}
pub fn form(&self) -> Arc<dyn Form> { 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<dyn Species> {
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<dyn Form> {
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<Option<Arc<dyn Item>>> {
&self.data.held_item
}
pub fn held_item(&self) -> &RwLock<Option<Arc<dyn Item>>> { &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<Arc<dyn Item>> {
self.data.held_item.write().take()
}
pub fn remove_held_item(&self) -> Option<Arc<dyn Item>> { self.data.held_item.write().take() }
/// Makes the Pokemon uses its held item.
pub fn consume_held_item(&self) -> Result<bool> {
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<String> {
&self.data.nickname
}
pub fn nickname(&self) -> &Option<String> { &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<TypeIdentifier>> {
self.data.types.read()
}
pub fn types(&self) -> RwLockReadGuard<'_, RawRwLock, Vec<TypeIdentifier>> { 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<Arc<LearnedMove>>; MAX_MOVES]> {
&self.data.moves
}
pub fn learned_moves(&self) -> &RwLock<[Option<Arc<LearnedMove>>; MAX_MOVES]> { &self.data.moves }
/// The stats of the Pokemon when disregarding any stat boosts.
pub fn flat_stats(&self) -> &Arc<StatisticSet<u32>> {
&self.data.flat_stats
}
pub fn flat_stats(&self) -> &Arc<StatisticSet<u32>> { &self.data.flat_stats }
/// The amount of boosts on a specific stat.
pub fn stat_boosts(&self) -> &Arc<ClampedStatisticSet<i8, -6, 6>> {
&self.data.stat_boost
}
pub fn stat_boosts(&self) -> &Arc<ClampedStatisticSet<i8, -6, 6>> { &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<StatisticSet<u32>> {
&self.data.boosted_stats
}
pub fn boosted_stats(&self) -> &Arc<StatisticSet<u32>> { &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<bool> {
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<ClampedStatisticSet<u8, 0, 31>> {
&self.data.individual_values
}
pub fn individual_values(&self) -> &Arc<ClampedStatisticSet<u8, 0, 31>> { &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<ClampedStatisticSet<u8, 0, 252>> {
&self.data.effort_values
}
pub fn effort_values(&self) -> &Arc<ClampedStatisticSet<u8, 0, 252>> { &self.data.effort_values }
/// Gets the battle the battle is currently in.
pub fn get_battle(&self) -> Option<Battle> {
@ -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<u8> {
self.data.battle_data.read().as_ref().map(|data| data.index())
}
pub fn get_battle_index(&self) -> Option<u8> { 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<Arc<dyn Ability>> {
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<dyn Nature> {
&self.data.nature
}
pub fn nature(&self) -> &Arc<dyn Nature> { &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<super::serialization::SerializedPokemon> {
self.into()
}
pub fn serialize(&self) -> Result<super::serialization::SerializedPokemon> { 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<Battle> {
self.battle.upgrade()
}
pub fn battle(&self) -> Option<Battle> { 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<Vec<WeakPokemonReference>> {
&self.seen_opponents
}
pub fn seen_opponents(&self) -> &RwLock<Vec<WeakPokemonReference>> { &self.seen_opponents }
}
impl ScriptSource for Pokemon {
@ -1083,9 +987,7 @@ impl ScriptSource for Pokemon {
Ok(c)
}
fn get_script_source_data(&self) -> &RwLock<ScriptSourceData> {
&self.data.script_source_data
}
fn get_script_source_data(&self) -> &RwLock<ScriptSourceData> { &self.data.script_source_data }
fn get_own_scripts(&self, scripts: &mut Vec<ScriptWrapper>) {
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<ScriptSet> {
&self.data.volatile
}
fn volatile_scripts(&self) -> &Arc<ScriptSet> { &self.data.volatile }
fn load_volatile_script(&self, key: &StringKey) -> Result<Option<Arc<dyn Script>>> {
self.data.library.load_script(self.into(), ScriptCategory::Pokemon, key)

View File

@ -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<dyn DynamicLibrary>, _pars: Vec<Arc<Parameter>>) -> Result<()> {
fn on_initialize(
&self,
_library: &Arc<dyn DynamicLibrary>,
_pars: &HashMap<StringKey, Arc<Parameter>>,
) -> 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<TurnChoice>) -> Result<()> {
Ok(())
}
fn on_before_turn(&self, _choice: &Arc<TurnChoice>) -> 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<TurnChoice>, _speed: &mut u32) -> Result<()> {
Ok(())
}
fn change_speed(&self, _choice: &Arc<TurnChoice>, _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<TurnChoice>, _priority: &mut i8) -> Result<()> {
Ok(())
}
fn change_priority(&self, _choice: &Arc<TurnChoice>, _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<TurnChoice>, _move_name: &mut StringKey) -> Result<()> {
Ok(())
}
fn change_move(&self, _choice: &Arc<TurnChoice>, _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<TurnChoice>, _number_of_hits: &mut u8) -> Result<()> {
Ok(())
}
fn change_number_of_hits(&self, _choice: &Arc<TurnChoice>, _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<ExecutingMove>, _prevent: &mut bool) -> Result<()> {
Ok(())
}
fn prevent_move(&self, _move: &Arc<ExecutingMove>, _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<ExecutingMove>, _fail: &mut bool) -> Result<()> {
Ok(())
}
fn fail_move(&self, _move: &Arc<ExecutingMove>, _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<ExecutingMove>, _stop: &mut bool) -> Result<()> {
Ok(())
}
fn stop_before_move(&self, _move: &Arc<ExecutingMove>, _stop: &mut bool) -> Result<()> { Ok(()) }
/// This function runs just before the move starts its execution.
fn on_before_move(&self, _move: &Arc<ExecutingMove>) -> Result<()> {
Ok(())
}
fn on_before_move(&self, _move: &Arc<ExecutingMove>) -> 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<ExecutingMove>, _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<ExecutingMove>, _target: &Pokemon) -> Result<()> {
Ok(())
}
fn on_move_miss(&self, _move: &Arc<ExecutingMove>, _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<ExecutingMove>, _target: &Pokemon, _hit: u8) -> Result<()> {
Ok(())
}
fn on_incoming_hit(&self, _move: &Arc<ExecutingMove>, _target: &Pokemon, _hit: u8) -> Result<()> { Ok(()) }
/// This function triggers when an opponent on the field faints.
fn on_opponent_faints(&self, _move: &Arc<ExecutingMove>, _target: &Pokemon, _hit: u8) -> Result<()> {
Ok(())
}
fn on_opponent_faints(&self, _move: &Arc<ExecutingMove>, _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<ExecutingMove>, _target: &Pokemon, _hit: u8) -> Result<()> {
Ok(())
}
fn on_secondary_effect(&self, _move: &Arc<ExecutingMove>, _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<ExecutingMove>, _target: &Pokemon) -> Result<()> {
Ok(())
}
fn on_after_hits(&self, _move: &Arc<ExecutingMove>, _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<TurnChoice>, _prevent: &mut bool) -> Result<()> {
Ok(())
}
fn prevent_self_switch(&self, _choice: &Arc<TurnChoice>, _prevent: &mut bool) -> Result<()> { Ok(()) }
/// This function allows the prevention of switching for any opponent.
fn prevent_opponent_switch(&self, _choice: &Arc<TurnChoice>, _prevent: &mut bool) -> Result<()> {
Ok(())
}
fn prevent_opponent_switch(&self, _choice: &Arc<TurnChoice>, _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<TurnChoice>, _prevent: &mut bool) -> Result<()> {
Ok(())
}
fn prevent_self_run_away(&self, _choice: &Arc<TurnChoice>, _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<TurnChoice>, _prevent: &mut bool) -> Result<()> {
Ok(())
}
fn prevent_opponent_run_away(&self, _choice: &Arc<TurnChoice>, _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<dyn Item>) -> Result<()> {
Ok(())
}
fn on_after_held_item_consume(&self, _pokemon: &Pokemon, _item: &Arc<dyn Item>) -> 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<T: 'static>(&self) -> Result<MappedRwLockReadGuard<T>> {
@ -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()) }
}

View File

@ -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<Arc<Parameter>>,
parameters_length: usize,
) -> FFIResult<FFIHandle<Arc<dyn Ability>>> {
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<Arc<Parameter>> = Vec::with_capacity(parameters_length);
for parameter in parameters {
parameters_vec.push(parameter.from_ffi_handle());
let mut parameters_map: HashMap<StringKey, Arc<Parameter>> = 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<dyn Ability> = Arc::new(AbilityImpl::new(&name, &effect, parameters_vec));
let arc: Arc<dyn Ability> = 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<Arc<dyn Ability>>)
#[no_mangle]
unsafe extern "C" fn ability_parameter_get(
ptr: FFIHandle<Arc<dyn Ability>>,
index: usize,
name: NonOwnedPtrString,
) -> FFIHandle<Arc<Parameter>> {
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()

View File

@ -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<Arc<dyn MoveData>>, flag:
unsafe extern "C" fn secondary_effect_new(
chance: f32,
effect_name: NonOwnedPtrString,
parameter_keys: *const NonOwnedPtrString,
parameters: *mut FFIHandle<Arc<Parameter>>,
parameters_length: usize,
) -> FFIHandle<Box<dyn SecondaryEffect>> {
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<dyn SecondaryEffect> = Arc::new(SecondaryEffectImpl::new(
@ -146,9 +149,10 @@ unsafe extern "C" fn secondary_effect_parameter_length(ptr: FFIHandle<Arc<dyn Se
#[no_mangle]
unsafe extern "C" fn secondary_effect_parameter_get(
ptr: FFIHandle<Arc<dyn SecondaryEffect>>,
index: usize,
name: NonOwnedPtrString,
) -> FFIHandle<Arc<Parameter>> {
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()

View File

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

View File

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

View File

@ -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<Hash>,
pub fn_on_stack: Option<Hash>,
pub fn_on_remove: Option<Hash>,
pub fn_on_before_turn: Option<Hash>,
pub fn_change_speed: Option<Hash>,
pub fn_change_priority: Option<Hash>,
pub fn_change_move: Option<Hash>,
pub fn_change_number_of_hits: Option<Hash>,
pub fn_prevent_move: Option<Hash>,
pub fn_fail_move: Option<Hash>,
pub fn_stop_before_move: Option<Hash>,
pub fn_on_before_move: Option<Hash>,
pub fn_fail_incoming_move: Option<Hash>,
pub fn_is_invulnerable: Option<Hash>,
pub fn_on_move_miss: Option<Hash>,
pub fn_change_move_type: Option<Hash>,
pub fn_change_effectiveness: Option<Hash>,
pub fn_block_critical: Option<Hash>,
pub fn_block_incoming_critical: Option<Hash>,
pub fn_change_accuracy: Option<Hash>,
pub fn_change_critical_stage: Option<Hash>,
pub fn_change_critical_modifier: Option<Hash>,
pub fn_change_stab_modifier: Option<Hash>,
pub fn_change_base_power: Option<Hash>,
pub fn_bypass_defensive_stat_boost: Option<Hash>,
pub fn_bypass_offensive_stat_boost: Option<Hash>,
pub fn_change_offensive_stat_value: Option<Hash>,
pub fn_change_defensive_stat_value: Option<Hash>,
pub fn_change_damage_stat_modifier: Option<Hash>,
pub fn_change_damage_modifier: Option<Hash>,
pub fn_change_damage: Option<Hash>,
pub fn_change_incoming_damage: Option<Hash>,
pub fn_on_incoming_hit: Option<Hash>,
pub fn_on_opponent_faints: Option<Hash>,
pub fn_prevent_stat_boost_change: Option<Hash>,
pub fn_change_stat_boost_change: Option<Hash>,
pub fn_prevent_secondary_effect: Option<Hash>,
pub fn_change_effect_chance: Option<Hash>,
pub fn_change_incoming_effect_chance: Option<Hash>,
pub fn_on_secondary_effect: Option<Hash>,
pub fn_on_after_hits: Option<Hash>,
pub fn_prevent_self_switch: Option<Hash>,
pub fn_prevent_opponent_switch: Option<Hash>,
pub fn_on_fail: Option<Hash>,
pub fn_on_opponent_fail: Option<Hash>,
pub fn_prevent_self_run_away: Option<Hash>,
pub fn_prevent_opponent_run_away: Option<Hash>,
pub fn_on_end_turn: Option<Hash>,
pub fn_on_damage: Option<Hash>,
pub fn_on_faint: Option<Hash>,
pub fn_on_switch_in: Option<Hash>,
pub fn_on_after_held_item_consume: Option<Hash>,
pub fn_change_experience_gained: Option<Hash>,
pub fn_share_experience: Option<Hash>,
pub fn_block_weather: Option<Hash>,
pub fn_change_capture_rate_bonus: Option<Hash>,
}
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),
_ => {}
}
}
}

View File

@ -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<Shared<Object>>,
/// 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<RuneScriptType>,
runtime: Arc<RuntimeContext>,
unit: Arc<Unit>,
}
unsafe impl Send for RuneScript {}
unsafe impl Sync for RuneScript {}
impl RuneScript {
pub fn new(
name: StringKey,
object: Shared<Object>,
owner: ScriptOwnerData,
script_type: Arc<RuneScriptType>,
runtime: Arc<RuntimeContext>,
unit: Arc<Unit>,
) -> 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<dyn DynamicLibrary>,
pars: &HashMap<StringKey, Arc<Parameter>>,
) -> 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<ExecutingMove>,
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<TurnChoice>, 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<Value, VmError> {
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()),
}
}

View File

@ -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<RuntimeContext>,
unit: Arc<Unit>,
script_types: HashMap<(ScriptCategory, StringKey), Arc<RuneScriptType>>,
}
pub struct RuneScriptResolverBuilder {
context: Context,
scripts: Sources,
}
impl ScriptResolver for RuneScriptResolver {
fn load_script(
&self,
owner: ScriptOwnerData,
category: ScriptCategory,
script_key: &StringKey,
) -> anyhow::Result<Option<Arc<dyn Script>>> {
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<Option<Arc<dyn ItemScript>>> { Ok(None) }
fn as_any(&self) -> &dyn Any { self }
}
impl RuneScriptResolverBuilder {
pub fn new() -> anyhow::Result<Self> {
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<Arc<dyn ScriptResolver>> {
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::<Vec<String>>()
.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<ScriptCategory, Result<(), MetaError>> {
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(())),
})
}

View File

@ -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::<RuneExecutingMove>()?;
Ok(())
}
#[derive(Debug, Any)]
pub struct RuneExecutingMove {
inner: Arc<ExecutingMove>,
}
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<AnyObj> { self.inner.user().wrap() }
}
impl_rune_wrapper!(&Arc<ExecutingMove>, RuneExecutingMove);

View File

@ -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<AnyObj>;
}
pub fn module() -> anyhow::Result<rune::Module> {
let mut module = rune::Module::new();
module.ty::<RuneValueIntWrapper>()?;
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<Value> {
Ok(Value::Any(Shared::new(AnyObj::new(RuneValueIntWrapper::new(value))?)?))
}
pub fn get_value_reference(value: Value) -> anyhow::Result<i64> {
let obj = value.into_any().into_result()?;
let obj = obj.take()?;
let obj = obj.downcast_borrow_ref::<RuneValueIntWrapper>().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<AnyObj> { 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::AnyObj> {
rune::runtime::Shared::new(rune::runtime::AnyObj::new($wrapped_type { inner: self.clone() }).unwrap())
.unwrap()
}
}
};
}
pub(self) use impl_rune_wrapper;

View File

@ -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::<RunePokemon>()?;
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);

View File

@ -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::<RuneTurnChoice>()?;
module.function_meta(RuneTurnChoice::speed)?;
module.function_meta(RuneTurnChoice::user)?;
Ok(())
}
#[derive(Any)]
pub struct RuneTurnChoice {
inner: Arc<TurnChoice>,
}
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<TurnChoice>, RuneTurnChoice);

View File

@ -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<Arc<Parameter>>;
fn parameters(&self) -> &HashMap<StringKey, Arc<Parameter>>;
}
/// 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<Arc<Parameter>>,
parameters: HashMap<StringKey, Arc<Parameter>>,
}
impl SecondaryEffectImpl {
/// Instantiates a new Secondary Effect.
pub fn new(chance: f32, effect_name: StringKey, parameters: Vec<Arc<Parameter>>) -> Self {
pub fn new(chance: f32, effect_name: StringKey, parameters: HashMap<StringKey, Arc<Parameter>>) -> 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<Arc<Parameter>> {
fn parameters(&self) -> &HashMap<StringKey, Arc<Parameter>> {
&self.parameters
}
}

View File

@ -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<Arc<Parameter>>;
fn parameters(&self) -> &HashMap<StringKey, Arc<Parameter>>;
}
/// 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<Arc<Parameter>>,
parameters: HashMap<StringKey, Arc<Parameter>>,
}
impl AbilityImpl {
/// Instantiates a new ability.
pub fn new(name: &StringKey, effect: &StringKey, parameters: Vec<Arc<Parameter>>) -> Self {
pub fn new(name: &StringKey, effect: &StringKey, parameters: HashMap<StringKey, Arc<Parameter>>) -> 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<Arc<Parameter>> {
&self.parameters
}
fn parameters(&self) -> &HashMap<StringKey, Arc<Parameter>> { &self.parameters }
}
/// An ability index allows us to find an ability on a form. It combines a bool for whether the

View File

@ -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<dyn AbilityLibrary> {
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<dyn TypeLibrary>) -> Arc<dyn MoveLi
if let Some(chance_value) = v.get("chance") {
chance = chance_value.as_f64().unwrap() as f32;
}
let mut parameters = Vec::new();
let mut parameters = HashMap::new();
if let Some(pars) = v.get("parameters") {
let pars = pars.as_array().unwrap();
let pars = pars.as_object().unwrap();
for par in pars {
parameters.push(parse_parameter(par));
parameters.insert(par.0.as_str().into(), parse_parameter(par.1));
}
}
@ -451,10 +452,8 @@ fn parse_evolution(value: &Value) -> EvolutionData {
EvolutionData::new(method, species)
}
#[cfg(not(feature = "wasm"))]
fn load_script_resolver(path: &String) -> Arc<dyn ScriptResolver> {
Arc::new(EmptyScriptResolver::default())
}
#[cfg(not(any(feature = "wasm", feature = "rune")))]
fn load_script_resolver(path: &String) -> Arc<dyn ScriptResolver> { Arc::new(EmptyScriptResolver::default()) }
#[cfg(feature = "wasm")]
fn load_script_resolver(path: &String) -> Arc<dyn ScriptResolver> {
@ -468,6 +467,28 @@ fn load_script_resolver(path: &String) -> Arc<dyn ScriptResolver> {
resolver
}
#[cfg(feature = "rune")]
fn load_script_resolver(path: &String) -> Arc<dyn ScriptResolver> {
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,

View File

@ -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": {}
}

View File

@ -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
}
}
},
{

View File

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

View File

@ -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<Arc<dyn DynamicLibrary>> = LazyLock::new(|| library_loader::load_library().library);
fn get_library() -> Arc<dyn DynamicLibrary> {
LIBRARY.clone()
}
fn get_library() -> Arc<dyn DynamicLibrary> { 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(),