A lot more work on a bunch of different parts of the system.

This commit is contained in:
2022-06-11 17:22:46 +02:00
parent 10e93949e4
commit 6e8f4dd4a5
35 changed files with 735 additions and 197 deletions

View File

@@ -1,18 +1,20 @@
use crate::dynamic_data::models::damage_source::DamageSource;
use crate::dynamic_data::models::pokemon::Pokemon;
use crate::static_data::species_data::form::Form;
use crate::static_data::species_data::species::Species;
use std::fmt::{Debug, Formatter};
use std::sync::{Arc, RwLock};
#[derive(Default)]
pub struct EventHook {
evt_hook_function: Vec<fn(&Box<&Event>)>,
}
impl EventHook {
impl<'a> EventHook {
pub fn register_listener(&mut self, func: fn(&Box<&Event>)) {
self.evt_hook_function.push(func);
}
pub fn trigger(&self, evt: Event) {
pub fn trigger<'b>(&self, evt: Event<'a, 'b>) {
let b = Box::new(&evt);
for f in &self.evt_hook_function {
f(&b);
@@ -27,15 +29,33 @@ impl Debug for EventHook {
}
#[derive(Debug)]
pub enum Event<'a> {
pub enum Event<'a, 'b> {
Switch {
side_index: u8,
index: u8,
pokemon: Option<Arc<RwLock<Pokemon<'a>>>>,
pokemon: Option<&'a Pokemon<'b>>,
},
Swap {
side_index: u8,
index_a: u8,
index_b: u8,
},
SpeciesChange {
pokemon: &'a Pokemon<'b>,
species: &'a Species<'b>,
form: &'a Form<'b>,
},
FormChange {
pokemon: &'a Pokemon<'b>,
form: &'a Form<'b>,
},
Damage {
pokemon: &'a Pokemon<'b>,
source: DamageSource,
original_health: u32,
new_health: u32,
},
Faint {
pokemon: &'a Pokemon<'b>,
},
}

View File

@@ -1,2 +1,32 @@
use crate::dynamic_data::models::pokemon::Pokemon;
use std::sync::Arc;
#[derive(Debug)]
pub struct ChoiceQueue {}
pub struct ChoiceQueue {
queue: Vec<Arc<ChoiceQueue>>,
current: usize,
}
impl ChoiceQueue {
pub fn new(queue: Vec<Arc<ChoiceQueue>>) -> Self {
Self { queue, current: 0 }
}
pub fn dequeue(&mut self) -> &Arc<ChoiceQueue> {
let c = &self.queue[self.current];
self.current += 1;
c
}
pub fn peek(&mut self) -> &Arc<ChoiceQueue> {
&self.queue[self.current]
}
pub fn has_next(&self) -> bool {
self.current < self.queue.len()
}
pub fn move_pokemon_choice_next(&mut self, _pokemon: &Pokemon) {
todo!()
}
}

View File

@@ -1,8 +1,10 @@
use crate::dynamic_data::libraries::battle_stat_calculator::BattleStatCalculator;
use crate::dynamic_data::libraries::script_resolver::ScriptCategory;
use crate::dynamic_data::script_handling::item_script::ItemScript;
use crate::dynamic_data::script_handling::script::Script;
use crate::static_data::items::item::Item;
use crate::static_data::libraries::static_data::StaticData;
use crate::PkmnResult;
use crate::{PkmnResult, StringKey};
#[derive(Debug)]
pub struct DynamicLibrary<'a> {
@@ -21,8 +23,12 @@ impl<'a> DynamicLibrary<'a> {
pub fn load_script(
&self,
_category: ScriptCategory,
_key: &str,
) -> PkmnResult<Box<dyn Script>> {
_key: &StringKey,
) -> PkmnResult<Option<Box<dyn Script>>> {
todo!()
}
pub fn load_item_script(&self, _key: &Item) -> PkmnResult<Option<Box<dyn ItemScript>>> {
todo!()
}
}

View File

@@ -9,7 +9,7 @@ use crate::dynamic_data::models::battle_side::BattleSide;
use crate::dynamic_data::script_handling::script::Script;
use crate::dynamic_data::script_handling::script_set::ScriptSet;
use crate::dynamic_data::script_handling::volatile_scripts::VolatileScripts;
use crate::{PkmnResult, ScriptCategory};
use crate::{PkmnResult, ScriptCategory, StringKey};
use std::sync::{Arc, RwLock};
#[derive(Debug)]
@@ -21,7 +21,7 @@ pub struct Battle<'a> {
pokemon_per_side: u8,
sides: Vec<BattleSide<'a>>,
random: BattleRandom,
choice_queue: ChoiceQueue,
current_turn_queue: Option<ChoiceQueue>,
has_ended: bool,
result: BattleResult,
event_hook: EventHook,
@@ -54,7 +54,7 @@ impl<'a> Battle<'a> {
pokemon_per_side,
sides,
random,
choice_queue: ChoiceQueue {},
current_turn_queue: None,
has_ended: false,
result: BattleResult::Inconclusive,
event_hook: Default::default(),
@@ -89,6 +89,10 @@ impl<'a> Battle<'a> {
pub fn sides(&self) -> &Vec<BattleSide<'a>> {
&self.sides
}
pub fn sides_mut(&mut self) -> &mut Vec<BattleSide<'a>> {
&mut self.sides
}
pub fn random(&self) -> &BattleRandom {
&self.random
}
@@ -110,12 +114,51 @@ impl<'a> Battle<'a> {
pub fn last_turn_time(&self) -> i64 {
self.last_turn_time
}
pub fn choice_queue(&self) -> &ChoiceQueue {
&self.choice_queue
pub fn current_turn_queue(&self) -> &Option<ChoiceQueue> {
&self.current_turn_queue
}
pub fn can_slot_be_filled(&self) -> bool {
todo!()
pub fn can_slot_be_filled(&self, side: u8, index: u8) -> bool {
for party in &self.parties {
if party.is_responsible_for_index(side, index) && party.has_pokemon_not_in_field() {
return true;
}
}
false
}
pub fn validate_battle_state(&mut self) {
if self.has_ended {
return;
}
let mut surviving_side_exists = false;
let mut winning_side = None;
for (side_index, side) in self.sides.iter().enumerate() {
// If any side has fled, the battle end.
if side.has_fled() {
self.result = BattleResult::Inconclusive;
self.has_ended = true;
return;
}
// If the side is not defeated
if !side.is_defeated() {
// More than 1 surviving side. Battle is not ended
if surviving_side_exists {
return;
}
surviving_side_exists = true;
winning_side = Some(side_index as u8);
}
}
// Everyone died :(
if !surviving_side_exists {
self.result = BattleResult::Inconclusive;
}
// Someone survived, they won!
else {
self.result = BattleResult::Conclusive(winning_side.unwrap());
}
self.has_ended = true;
}
}
@@ -124,7 +167,7 @@ impl<'a> VolatileScripts<'a> for Battle<'a> {
&self.volatile
}
fn load_volatile_script(&self, key: &str) -> PkmnResult<Box<dyn Script>> {
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Box<dyn Script>>> {
self.library.load_script(ScriptCategory::Battle, key)
}
}

View File

@@ -7,7 +7,8 @@ use crate::dynamic_data::script_handling::script::Script;
use crate::dynamic_data::script_handling::script_set::ScriptSet;
use crate::dynamic_data::script_handling::volatile_scripts::VolatileScripts;
use crate::dynamic_data::script_handling::ScriptSource;
use crate::{script_hook, PkmnResult};
use crate::{script_hook, PkmnResult, StringKey};
use std::ops::Deref;
use std::sync::{Arc, RwLock, Weak};
#[derive(Debug)]
@@ -83,7 +84,7 @@ impl<'a> BattleSide<'a> {
/// responsible for them. Returns false if all slots are filled with usable pokemon, or slots are
/// empty, but can't be filled by any party anymore.
pub fn all_slots_filled(&self) -> bool {
for pokemon in &self.pokemon {
for (i, pokemon) in self.pokemon.iter().enumerate() {
if (!pokemon.is_none() || !pokemon.as_ref().unwrap().read().unwrap().is_usable())
&& self
.battle
@@ -91,7 +92,7 @@ impl<'a> BattleSide<'a> {
.unwrap()
.read()
.unwrap()
.can_slot_be_filled()
.can_slot_be_filled(self.index, i as u8)
{
return false;
}
@@ -146,7 +147,7 @@ impl<'a> BattleSide<'a> {
battle.event_hook().trigger(Event::Switch {
side_index: self.index,
index,
pokemon: Some(pokemon_mutex.clone()),
pokemon: Some(&pokemon),
});
script_hook!(on_switch_in, pokemon, &pokemon);
} else {
@@ -169,10 +170,10 @@ impl<'a> BattleSide<'a> {
false
}
pub fn mark_slot_as_unfillable(&mut self, pokemon: Arc<Pokemon<'a>>) {
pub fn mark_slot_as_unfillable(&mut self, pokemon: &Pokemon<'a>) {
for (i, slot) in self.pokemon.iter().enumerate() {
if let Some(p) = slot {
if p.read().unwrap().unique_identifier() == pokemon.unique_identifier() {
if p.read().unwrap().deref() as *const Pokemon == pokemon as *const Pokemon {
self.fillable_slots[i] = false;
return;
}
@@ -266,7 +267,7 @@ impl<'a> VolatileScripts<'a> for BattleSide<'a> {
&self.volatile_scripts
}
fn load_volatile_script(&self, key: &str) -> PkmnResult<Box<dyn Script>> {
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Box<dyn Script>>> {
self.battle
.upgrade()
.unwrap()

View File

@@ -0,0 +1,5 @@
#[derive(Debug, Clone, Copy)]
pub enum DamageSource {
AttackDamage = 0,
Misc = 1,
}

View File

@@ -3,6 +3,7 @@ pub mod battle_party;
pub mod battle_random;
pub mod battle_result;
pub mod battle_side;
pub mod damage_source;
pub mod learned_move;
pub mod pokemon;
pub mod pokemon_party;

View File

@@ -1,6 +1,8 @@
use crate::defines::{LevelInt, MAX_MOVES};
use crate::dynamic_data::event_hooks::event_hook::Event;
use crate::dynamic_data::libraries::dynamic_library::DynamicLibrary;
use crate::dynamic_data::models::battle::Battle;
use crate::dynamic_data::models::damage_source::DamageSource;
use crate::dynamic_data::models::learned_move::LearnedMove;
use crate::dynamic_data::script_handling::script::Script;
use crate::dynamic_data::script_handling::script_set::ScriptSet;
@@ -8,6 +10,7 @@ use crate::dynamic_data::script_handling::volatile_scripts::VolatileScripts;
use crate::dynamic_data::script_handling::ScriptSource;
use crate::static_data::items::item::Item;
use crate::static_data::natures::Nature;
use crate::static_data::species_data::ability::Ability;
use crate::static_data::species_data::ability_index::AbilityIndex;
use crate::static_data::species_data::form::Form;
use crate::static_data::species_data::gender::Gender;
@@ -15,7 +18,7 @@ use crate::static_data::species_data::species::Species;
use crate::static_data::statistic_set::{ClampedStatisticSet, StatisticSet};
use crate::static_data::statistics::Statistic;
use crate::utils::random::Random;
use crate::{PkmnResult, ScriptCategory};
use crate::{script_hook, PkmnResult, ScriptCategory, StringKey};
use std::sync::{Arc, RwLock, Weak};
#[derive(Debug)]
@@ -73,6 +76,7 @@ pub struct Pokemon<'a> {
ability_index: AbilityIndex,
is_ability_overridden: bool,
override_ability: Option<Ability>,
battle_data: Option<PokemonBattleData<'a>>,
@@ -80,8 +84,10 @@ pub struct Pokemon<'a> {
allowed_experience: bool,
types: Vec<u8>,
is_egg: bool,
is_caught: bool,
ability_script: Option<Box<dyn Script>>,
ability_script: Option<Arc<Box<dyn Script>>>,
status_script: Option<Box<dyn Script>>,
volatile: Arc<RwLock<ScriptSet>>,
}
@@ -96,7 +102,7 @@ impl<'a> Pokemon<'a> {
unique_identifier: u32,
gender: Gender,
coloring: u8,
nature: String,
nature: &StringKey,
) -> Pokemon<'a> {
// Calculate experience from the level for the specified growth rate.
let experience = library
@@ -135,16 +141,18 @@ impl<'a> Pokemon<'a> {
nickname: None,
ability_index: ability,
is_ability_overridden: false,
override_ability: None,
battle_data: None,
moves: [None, None, None, None],
allowed_experience: false,
types: form.types().to_vec(),
is_egg: false,
is_caught: false,
ability_script: None,
status_script: None,
volatile: Default::default(),
};
pokemon.recalculate_flat_stats();
pokemon
}
@@ -190,6 +198,35 @@ impl<'a> Pokemon<'a> {
pub fn held_item(&self) -> Option<&'a Item> {
self.held_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.
if let Some(v) = self.held_item {
return v.name() == name;
}
false
}
pub fn set_held_item(&mut self, item: &'a Item) {
self.held_item = Some(item);
}
pub fn remove_held_item(&mut self) {
self.held_item = None;
}
pub fn consume_held_item(&mut self) -> bool {
if self.held_item.is_none() {
return false;
}
let script = self
.library
.load_item_script(self.held_item.unwrap())
.unwrap();
if script.is_none() {
return false;
}
// TODO: the entire item use part.
todo!();
}
pub fn current_health(&self) -> u32 {
self.current_health
}
@@ -249,9 +286,19 @@ impl<'a> Pokemon<'a> {
pub fn is_ability_overriden(&self) -> bool {
self.is_ability_overridden
}
pub fn active_ability(&self) -> &Option<Box<dyn Script>> {
pub fn active_ability(&self) -> &Ability {
if self.is_ability_overridden {
if let Some(v) = &self.override_ability {
return v;
}
}
self.form.get_ability(self.ability_index)
}
pub fn ability_script(&self) -> &Option<Arc<Box<dyn Script>>> {
&self.ability_script
}
pub fn seen_opponents(&self) -> Option<&Vec<Weak<RwLock<Pokemon<'a>>>>> {
if let Some(data) = &self.battle_data {
Some(&data.seen_opponents)
@@ -281,6 +328,7 @@ impl<'a> Pokemon<'a> {
// If the pokemon is genderless, but it's new species is not, we want to set its gender
if self.gender != Gender::Genderless && species.gender_rate() < 0.0 {
// If we're in battle, use the battle random for predictability
if self.battle_data.is_some() {
let battle_data = self.battle_data.as_mut().unwrap();
self.gender = species.get_random_gender(
@@ -296,6 +344,7 @@ impl<'a> Pokemon<'a> {
.unwrap(),
);
} else {
// If we're not in battle, just use a new random.
self.gender = species.get_random_gender(&mut Random::default());
}
}
@@ -303,11 +352,78 @@ impl<'a> Pokemon<'a> {
else if species.gender_rate() < 0.0 && self.gender != Gender::Genderless {
self.gender = Gender::Genderless;
}
// TODO: Battle Event trigger
if let Some(battle_data) = &self.battle_data {
if let Some(battle) = battle_data.battle.upgrade() {
battle
.read()
.unwrap()
.event_hook()
.trigger(Event::SpeciesChange {
pokemon: self,
species,
form,
})
}
}
}
pub fn change_form(&mut self, form: &'a Form) {
if std::ptr::eq(self.form, form) {
return;
}
self.form = form;
self.types.clear();
for t in form.types() {
self.types.push(*t);
}
self.weight = form.weight();
self.height = form.height();
let ability_script = self
.library
.load_script(ScriptCategory::Ability, self.active_ability().name())
.unwrap();
if let Some(ability_script) = ability_script {
self.ability_script = Some(Arc::new(ability_script));
// Ensure the ability script gets initialized with the parameters for the ability.
self.ability_script()
.as_ref()
.unwrap()
.on_initialize(self.active_ability().parameters())
} else {
self.ability_script = None;
}
let old_health = self.max_health();
self.recalculate_flat_stats();
let diff_health = (self.max_health() - old_health) as i32;
if self.current_health == 0 && (self.current_health as i32) < -diff_health {
self.current_health = 0;
} else {
self.current_health = self.current_health() + diff_health as u32;
}
// TODO: consider form specific attacks?
if let Some(battle_data) = &self.battle_data {
if let Some(battle) = battle_data.battle.upgrade() {
battle
.read()
.unwrap()
.event_hook()
.trigger(Event::FormChange {
pokemon: self,
form,
})
}
}
}
pub fn is_usable(&self) -> bool {
todo!()
!self.is_caught && !self.is_egg && !self.is_fainted()
}
pub fn is_fainted(&self) -> bool {
self.current_health == 0
}
pub fn set_battle_data(&mut self, battle: Weak<RwLock<Battle<'a>>>, battle_side_index: u8) {
@@ -360,6 +476,67 @@ impl<'a> Pokemon<'a> {
battle_data.seen_opponents.push(pokemon);
}
}
pub fn damage(&mut self, mut damage: u32, source: DamageSource) {
if damage > self.current_health {
damage = self.current_health;
}
if damage == 0 {
return;
}
let new_health = self.current_health() - damage;
if let Some(battle_data) = &self.battle_data {
if let Some(battle) = battle_data.battle.upgrade() {
battle.read().unwrap().event_hook().trigger(Event::Damage {
pokemon: self,
source,
original_health: self.current_health(),
new_health,
});
// TODO: register history
script_hook!(
on_damage,
self,
self,
source,
self.current_health,
new_health
);
}
}
self.current_health = new_health;
if self.is_fainted() && damage > 0 {
self.on_faint(source);
}
}
pub fn on_faint(&self, source: DamageSource) {
if let Some(battle_data) = &self.battle_data {
if let Some(battle) = battle_data.battle.upgrade() {
battle
.read()
.unwrap()
.event_hook()
.trigger(Event::Faint { pokemon: self });
script_hook!(on_faint, self, self, source);
script_hook!(on_remove, self,);
}
// TODO: Experience gain
if let Some(battle) = battle_data.battle.upgrade() {
if !battle
.read()
.unwrap()
.can_slot_be_filled(battle_data.battle_side_index, battle_data.index)
{
let mut battle = battle.write().unwrap();
let side = &mut battle.sides_mut()[battle_data.battle_side_index as usize];
side.mark_slot_as_unfillable(self);
}
battle.write().unwrap().validate_battle_state();
}
}
}
}
impl<'a> ScriptSource for Pokemon<'a> {
@@ -373,7 +550,7 @@ impl<'a> VolatileScripts<'a> for Pokemon<'a> {
&self.volatile
}
fn load_volatile_script(&self, key: &str) -> PkmnResult<Box<dyn Script>> {
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Box<dyn Script>>> {
self.library.load_script(ScriptCategory::Pokemon, key)
}
}
@@ -389,8 +566,8 @@ pub mod test {
#[test]
fn construct_pokemon() {
let lib = dynamic_library::test::build();
let species = lib.static_data().species().get("foo").unwrap();
let form = species.get_form("default").unwrap();
let species = lib.static_data().species().get(&"foo".into()).unwrap();
let form = species.get_form(&"default".into()).unwrap();
let pokemon = Pokemon::new(
&lib,
@@ -404,9 +581,9 @@ pub mod test {
0,
Gender::Male,
0,
"test_nature".to_string(),
&"test_nature".into(),
);
assert_eq!(pokemon.species.name(), "foo");
assert_eq!(pokemon.form.name(), "default");
assert_eq!(pokemon.species.name(), &"foo".into());
assert_eq!(pokemon.form.name(), &"default".into());
}
}

View File

@@ -0,0 +1 @@
pub trait ItemScript {}

View File

@@ -2,6 +2,7 @@ use crate::dynamic_data::script_handling::script::Script;
use crate::dynamic_data::script_handling::script_set::ScriptSet;
use std::sync::Weak;
pub mod item_script;
pub mod script;
pub mod script_set;
pub mod volatile_scripts;

View File

@@ -1,8 +1,11 @@
use crate::dynamic_data::models::damage_source::DamageSource;
use crate::dynamic_data::models::pokemon::Pokemon;
use crate::static_data::moves::secondary_effect::EffectParameter;
use crate::StringKey;
use std::fmt::{Debug, Formatter};
pub trait Script {
fn name(&self) -> &str;
fn name(&self) -> &StringKey;
fn is_suppressed(&self) -> bool {
self.get_suppressed_count() > 0
@@ -14,7 +17,7 @@ pub trait Script {
// FIXME: add missing parameters
fn stack(&self);
fn on_remove(&self);
fn on_initialize(&self);
fn on_initialize(&self, pars: &Vec<EffectParameter>);
fn on_before_turn(&self);
fn change_speed(&self);
fn change_priority(&self);
@@ -52,9 +55,9 @@ pub trait Script {
fn prevent_self_run_away(&self);
fn prevent_opponent_run_away(&self);
fn on_end_turn(&self);
fn on_damage(&self);
fn on_faint(&self);
fn on_switch_in<'b>(&self, pokemon: &'b Pokemon);
fn on_damage(&self, pokemon: &Pokemon, source: DamageSource, old_health: u32, new_health: u32);
fn on_faint(&self, pokemon: &Pokemon, source: DamageSource);
fn on_switch_in(&self, pokemon: &Pokemon);
fn on_after_held_item_consume(&self);
}

View File

@@ -1,11 +1,11 @@
use crate::dynamic_data::script_handling::script::Script;
use crate::PkmnResult;
use crate::{PkmnResult, StringKey};
use indexmap::IndexMap;
use std::sync::Arc;
#[derive(Debug, Default)]
pub struct ScriptSet {
scripts: IndexMap<String, Arc<Box<dyn Script>>>,
scripts: IndexMap<StringKey, Arc<Box<dyn Script>>>,
}
impl ScriptSet {
@@ -15,33 +15,37 @@ impl ScriptSet {
return existing.clone();
}
let arc = Arc::new(script);
self.scripts.insert(arc.name().to_string(), arc.clone());
self.scripts.insert(arc.name().clone(), arc.clone());
self.scripts.last().unwrap().1.clone()
}
pub fn stack_or_add<'b, F>(
&mut self,
key: &str,
key: &StringKey,
instantiation: &'b F,
) -> PkmnResult<Arc<Box<dyn Script>>>
) -> PkmnResult<Option<Arc<Box<dyn Script>>>>
where
F: Fn() -> PkmnResult<Box<dyn Script>>,
F: Fn() -> PkmnResult<Option<Box<dyn Script>>>,
{
if let Some(existing) = self.scripts.get(key) {
existing.stack();
return Ok(existing.clone());
return Ok(Some(existing.clone()));
}
let script = instantiation()?;
let arc = Arc::new(script);
self.scripts.insert(arc.name().to_string(), arc.clone());
Ok(self.scripts.last().unwrap().1.clone())
if let Some(script) = script {
let arc = Arc::new(script);
self.scripts.insert(arc.name().clone(), arc.clone());
Ok(Some(self.scripts.last().unwrap().1.clone()))
} else {
Ok(None)
}
}
pub fn get(&self, key: &str) -> Option<&Arc<Box<dyn Script>>> {
pub fn get(&self, key: &StringKey) -> Option<&Arc<Box<dyn Script>>> {
self.scripts.get(key)
}
pub fn remove(&mut self, key: &str) {
pub fn remove(&mut self, key: &StringKey) {
let value = self.scripts.shift_remove(key);
if let Some(script) = value {
script.on_remove();
@@ -55,7 +59,7 @@ impl ScriptSet {
self.scripts.clear();
}
pub fn has(&self, key: &str) -> bool {
pub fn has(&self, key: &StringKey) -> bool {
self.scripts.contains_key(key)
}

View File

@@ -1,30 +1,30 @@
use crate::dynamic_data::script_handling::script::Script;
use crate::dynamic_data::script_handling::script_set::ScriptSet;
use crate::PkmnResult;
use crate::{PkmnResult, StringKey};
use std::sync::{Arc, RwLock};
pub trait VolatileScripts<'a> {
fn volatile_scripts(&self) -> &Arc<RwLock<ScriptSet>>;
fn load_volatile_script(&self, key: &str) -> PkmnResult<Box<dyn Script>>;
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Box<dyn Script>>>;
fn has_volatile_script(&self, key: &str) -> bool {
fn has_volatile_script(&self, key: &StringKey) -> bool {
self.volatile_scripts().read().unwrap().has(key)
}
fn get_volatile_script(&self, key: &str) -> Option<Arc<Box<dyn Script>>> {
fn get_volatile_script(&self, key: &StringKey) -> Option<Arc<Box<dyn Script>>> {
let scripts = self.volatile_scripts().read().unwrap();
let s = scripts.get(key);
s.cloned()
}
fn add_volatile_script(&mut self, key: &str) -> PkmnResult<Arc<Box<dyn Script>>> {
fn add_volatile_script(&mut self, key: &StringKey) -> PkmnResult<Option<Arc<Box<dyn Script>>>> {
self.volatile_scripts()
.write()
.unwrap()
.stack_or_add(key, &|| self.load_volatile_script(key))
}
fn remove_volatile_script(&mut self, key: &str) {
fn remove_volatile_script(&mut self, key: &StringKey) {
self.volatile_scripts().write().unwrap().remove(key)
}
}