From 6f1880c768370481676dc740908cdb0049617249 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sat, 15 Apr 2023 19:33:29 +0200 Subject: [PATCH] Loads of work to replace panics with results. --- Cargo.toml | 1 + src/dynamic_data/choices.rs | 78 ++++----- src/dynamic_data/flow/choice_queue.rs | 91 +++++++--- src/dynamic_data/flow/target_resolver.rs | 19 ++- src/dynamic_data/flow/turn_runner.rs | 21 +-- .../libraries/battle_stat_calculator.rs | 75 ++++---- src/dynamic_data/libraries/damage_library.rs | 20 +-- src/dynamic_data/libraries/dynamic_library.rs | 3 +- src/dynamic_data/models/battle.rs | 24 +-- src/dynamic_data/models/battle_random.rs | 11 +- src/dynamic_data/models/battle_side.rs | 160 ++++++++++++------ src/dynamic_data/models/executing_move.rs | 29 +++- src/dynamic_data/models/learned_move.rs | 8 +- src/dynamic_data/models/pokemon.rs | 132 +++++++++------ src/dynamic_data/models/pokemon_builder.rs | 2 +- src/dynamic_data/script_handling/mod.rs | 26 +-- .../libraries/battle_stat_calculator.rs | 22 ++- src/ffi/dynamic_data/models/learned_move.rs | 6 +- src/ffi/dynamic_data/models/pokemon.rs | 65 ++++--- src/lib.rs | 45 ++++- .../dynamic_data/battle_side.rs | 2 +- .../dynamic_data/learned_move.rs | 2 +- .../export_registry/dynamic_data/pokemon.rs | 8 +- tests/common/test_step.rs | 2 +- tests/integration.rs | 4 +- 25 files changed, 547 insertions(+), 309 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 19f310b..e7a503c 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ arcstr = { version = "1.1.4", features = ["std"] } enum-display-derive = "0.1.1" anyhow = "1.0.69" anyhow_ext = "0.2.1" +thiserror = "1.0.39" [dev-dependencies] csv = "1.1.6" diff --git a/src/dynamic_data/choices.rs b/src/dynamic_data/choices.rs index ec70603..2efb6cd 100755 --- a/src/dynamic_data/choices.rs +++ b/src/dynamic_data/choices.rs @@ -1,3 +1,4 @@ +use anyhow::{bail, Result}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -110,23 +111,23 @@ impl TurnChoice { /// Helper function to get the move choice data from a turn. Note that this will panic if not /// used on a move choice. - pub(crate) fn get_move_turn_data(&self) -> &MoveChoice { + pub(crate) fn get_move_turn_data(&self) -> Result<&MoveChoice> { if let TurnChoice::Move(data) = self { - return data; + return Ok(data); } - panic!("Invalid turn choice"); + bail!("Invalid turn choice"); } } impl ScriptSource for TurnChoice { - fn get_script_count(&self) -> usize { - match self { - TurnChoice::Move(data) => data.get_script_count(), - TurnChoice::Item(data) => data.get_script_count(), - TurnChoice::Switch(data) => data.get_script_count(), - TurnChoice::Flee(data) => data.get_script_count(), - TurnChoice::Pass(data) => data.get_script_count(), - } + fn get_script_count(&self) -> Result { + Ok(match self { + TurnChoice::Move(data) => data.get_script_count()?, + TurnChoice::Item(data) => data.get_script_count()?, + TurnChoice::Switch(data) => data.get_script_count()?, + TurnChoice::Flee(data) => data.get_script_count()?, + TurnChoice::Pass(data) => data.get_script_count()?, + }) } fn get_script_source_data(&self) -> &RwLock { @@ -149,14 +150,15 @@ impl ScriptSource for TurnChoice { } } - fn collect_scripts(&self, scripts: &mut Vec) { + fn collect_scripts(&self, scripts: &mut Vec) -> Result<()> { match self { - TurnChoice::Move(data) => data.collect_scripts(scripts), - TurnChoice::Item(data) => data.collect_scripts(scripts), - TurnChoice::Switch(data) => data.collect_scripts(scripts), - TurnChoice::Flee(data) => data.collect_scripts(scripts), - TurnChoice::Pass(data) => data.collect_scripts(scripts), - } + TurnChoice::Move(data) => data.collect_scripts(scripts)?, + TurnChoice::Item(data) => data.collect_scripts(scripts)?, + TurnChoice::Switch(data) => data.collect_scripts(scripts)?, + TurnChoice::Flee(data) => data.collect_scripts(scripts)?, + TurnChoice::Pass(data) => data.collect_scripts(scripts)?, + }; + Ok(()) } } @@ -230,8 +232,8 @@ impl MoveChoice { } impl ScriptSource for MoveChoice { - fn get_script_count(&self) -> usize { - 1 + self.choice_data.user.get_script_count() + fn get_script_count(&self) -> Result { + Ok(self.choice_data.user.get_script_count()? + 1) } fn get_script_source_data(&self) -> &RwLock { @@ -242,9 +244,9 @@ impl ScriptSource for MoveChoice { scripts.push((&self.script).into()); } - fn collect_scripts(&self, scripts: &mut Vec) { + fn collect_scripts(&self, scripts: &mut Vec) -> Result<()> { self.get_own_scripts(scripts); - self.choice_data.user.collect_scripts(scripts); + self.choice_data.user.collect_scripts(scripts) } } @@ -273,8 +275,8 @@ impl ItemChoice { } impl ScriptSource for ItemChoice { - fn get_script_count(&self) -> usize { - 0 + fn get_script_count(&self) -> Result { + Ok(0) } fn get_script_source_data(&self) -> &RwLock { @@ -283,8 +285,8 @@ impl ScriptSource for ItemChoice { fn get_own_scripts(&self, _scripts: &mut Vec) {} - fn collect_scripts(&self, scripts: &mut Vec) { - self.choice_data.user.collect_scripts(scripts); + fn collect_scripts(&self, scripts: &mut Vec) -> Result<()> { + self.choice_data.user.collect_scripts(scripts) } } @@ -313,8 +315,8 @@ impl SwitchChoice { } impl ScriptSource for SwitchChoice { - fn get_script_count(&self) -> usize { - 0 + fn get_script_count(&self) -> Result { + Ok(0) } fn get_script_source_data(&self) -> &RwLock { @@ -323,8 +325,8 @@ impl ScriptSource for SwitchChoice { fn get_own_scripts(&self, _scripts: &mut Vec) {} - fn collect_scripts(&self, scripts: &mut Vec) { - self.choice_data.user.collect_scripts(scripts); + fn collect_scripts(&self, scripts: &mut Vec) -> Result<()> { + self.choice_data.user.collect_scripts(scripts) } } @@ -352,8 +354,8 @@ impl FleeChoice { } impl ScriptSource for FleeChoice { - fn get_script_count(&self) -> usize { - 0 + fn get_script_count(&self) -> Result { + Ok(0) } fn get_script_source_data(&self) -> &RwLock { @@ -362,8 +364,8 @@ impl ScriptSource for FleeChoice { fn get_own_scripts(&self, _scripts: &mut Vec) {} - fn collect_scripts(&self, scripts: &mut Vec) { - self.choice_data.user.collect_scripts(scripts); + fn collect_scripts(&self, scripts: &mut Vec) -> Result<()> { + self.choice_data.user.collect_scripts(scripts) } } @@ -392,8 +394,8 @@ impl PassChoice { } impl ScriptSource for PassChoice { - fn get_script_count(&self) -> usize { - 0 + fn get_script_count(&self) -> Result { + Ok(0) } fn get_script_source_data(&self) -> &RwLock { @@ -402,8 +404,8 @@ impl ScriptSource for PassChoice { fn get_own_scripts(&self, _scripts: &mut Vec) {} - fn collect_scripts(&self, scripts: &mut Vec) { - self.choice_data.user.collect_scripts(scripts); + fn collect_scripts(&self, scripts: &mut Vec) -> Result<()> { + self.choice_data.user.collect_scripts(scripts) } } diff --git a/src/dynamic_data/flow/choice_queue.rs b/src/dynamic_data/flow/choice_queue.rs index 90d1b4a..890f431 100755 --- a/src/dynamic_data/flow/choice_queue.rs +++ b/src/dynamic_data/flow/choice_queue.rs @@ -1,7 +1,7 @@ use crate::dynamic_data::choices::TurnChoice; use crate::dynamic_data::script_handling::ScriptSource; use crate::dynamic_data::Pokemon; -use crate::{script_hook, ValueIdentifiable, ValueIdentifier}; +use crate::{script_hook, PkmnError, ValueIdentifiable, ValueIdentifier}; use anyhow::Result; use anyhow_ext::anyhow; use parking_lot::lock_api::MappedRwLockReadGuard; @@ -39,14 +39,17 @@ impl ChoiceQueue { /// Dequeues the next turn choice to be executed. This gives ownership to the callee, and replaces /// our own reference to the turn choice with an empty spot. It also increments the current position /// by one. - pub fn dequeue(&mut self) -> Option { + pub fn dequeue(&mut self) -> Result> { let mut write_lock = self.queue.write(); if self.current >= write_lock.len() { - return None; + return Ok(None); } - let c = write_lock[self.current].take(); + let c = write_lock + .get_mut(self.current) + .ok_or(anyhow!("Unable to get current turn choice"))? + .take(); self.current += 1; - c + Ok(c) } /// This reads what the next choice to execute will be, without modifying state. @@ -55,7 +58,10 @@ impl ChoiceQueue { if self.current >= read_lock.len() { Ok(None) } else { - let v = RwLockReadGuard::try_map(read_lock, |a| a[self.current].as_ref()); + let v = RwLockReadGuard::try_map(read_lock, |a| match a.get(self.current) { + Some(Some(v)) => Some(v), + _ => None, + }); match v { Ok(v) => Ok(Some(v)), Err(_) => Err(anyhow!("Could not map choice")), @@ -71,11 +77,13 @@ impl ChoiceQueue { /// This resorts the yet to be executed choices. This can be useful for dealing with situations /// such as Pokemon changing forms just after the very start of a turn, when turn order has /// technically already been decided. - pub fn resort(&mut self) { + pub fn resort(&mut self) -> Result<()> { let len = self.queue.read().len(); let mut write_lock = self.queue.write(); for index in self.current..len { - let choice = &mut write_lock[index]; + let choice = &mut write_lock + .get_mut(index) + .ok_or(PkmnError::IndexOutOfBounds { index, len })?; if let Some(choice) = choice { let mut speed = choice.user().boosted_stats().speed(); script_hook!(change_speed, (*choice), choice, &mut speed); @@ -83,7 +91,14 @@ impl ChoiceQueue { } } - write_lock[self.current..len].sort_unstable_by(|a, b| b.cmp(a)); + write_lock + .get_mut(self.current..len) + .ok_or(PkmnError::IndexOutOfBounds { + index: self.current, + len, + })? + .sort_unstable_by(|a, b| b.cmp(a)); + Ok(()) } /// This moves the choice of a specific Pokemon up to the next choice to be executed. @@ -92,7 +107,7 @@ impl ChoiceQueue { let mut desired_index = None; // Find the index for the choice we want to move up. for index in self.current..queue_lock.len() { - if let Some(choice) = &queue_lock[index] { + if let Some(Some(choice)) = &queue_lock.get(index) { if pokemon.value_identifier() == choice.user().value_identifier() { desired_index = Some(index); break; @@ -106,8 +121,14 @@ impl ChoiceQueue { return Ok(true); } + let len = queue_lock.len(); // Take the choice we want to move forward out of it's place. - let choice = queue_lock[desired_index] + let choice = queue_lock + .get_mut(desired_index) + .ok_or(PkmnError::IndexOutOfBounds { + index: self.current, + len, + })? .take() .ok_or(anyhow!("Choice was already taken"))?; // Iterate backwards from the spot before the choice we want to move up, push them all back @@ -115,8 +136,15 @@ impl ChoiceQueue { for index in (self.current..desired_index).rev() { queue_lock.swap(index, index + 1); } + let len = queue_lock.len(); // Place the choice that needs to be next in the next to be executed position. - let _ = queue_lock[self.current].insert(choice); + let _ = queue_lock + .get_mut(self.current) + .ok_or(PkmnError::IndexOutOfBounds { + index: self.current, + len, + })? + .insert(choice); true } None => false, @@ -125,9 +153,16 @@ impl ChoiceQueue { } /// Internal helper function to be easily able to iterate over the yet to be executed choices. - pub(crate) fn get_queue(&self) -> MappedRwLockReadGuard<'_, RawRwLock, [Option]> { + pub(crate) fn get_queue(&self) -> Result]>> { let read_lock = self.queue.read(); - RwLockReadGuard::map(read_lock, |a| &a[self.current..self.queue.read().len()]) + match RwLockReadGuard::try_map(read_lock, |a| a.get(self.current..self.queue.read().len())) { + Ok(v) => Ok(v), + Err(_) => Err(PkmnError::IndexOutOfBounds { + index: self.current, + len: self.queue.read().len(), + } + .into()), + } } } @@ -155,7 +190,7 @@ mod tests { #[test] fn dequeue_from_empty_queue() { let mut queue = ChoiceQueue::new(Vec::new()); - assert!(queue.dequeue().is_none()); + assert!(queue.dequeue().unwrap().is_none()); } fn get_user(level: LevelInt) -> Pokemon { @@ -196,7 +231,7 @@ mod tests { let mut queue = ChoiceQueue::new(vec![Some(TurnChoice::Pass(PassChoice::new(user)))]); assert!(queue.has_next()); - assert_eq!(7, queue.dequeue().unwrap().speed()); + assert_eq!(7, queue.dequeue().unwrap().unwrap().speed()); assert!(!queue.has_next()); assert!(queue.peek().unwrap().is_none()); } @@ -224,7 +259,7 @@ mod tests { Some(TurnChoice::Pass(PassChoice::new(user1.clone()))), Some(TurnChoice::Pass(PassChoice::new(user2))), ]); - let inner_queue = queue.get_queue(); + let inner_queue = queue.get_queue().unwrap(); assert_eq!( inner_queue[0].as_ref().unwrap().user().value_identifier(), user1.value_identifier() @@ -240,8 +275,8 @@ mod tests { Some(TurnChoice::Pass(PassChoice::new(user1))), Some(TurnChoice::Pass(PassChoice::new(user2))), ]); - assert_eq!(25, queue.dequeue().unwrap().speed()); - assert_eq!(6, queue.dequeue().unwrap().speed()); + assert_eq!(25, queue.dequeue().unwrap().unwrap().speed()); + assert_eq!(6, queue.dequeue().unwrap().unwrap().speed()); } #[test] @@ -254,12 +289,12 @@ mod tests { Some(TurnChoice::Pass(PassChoice::new(user2.clone()))), ]); - user2.change_level_by(60); + user2.change_level_by(60).unwrap(); assert_eq!( user1.value_identifier(), queue.peek().unwrap().unwrap().user().value_identifier() ); - queue.resort(); + queue.resort().unwrap(); assert_eq!( user2.value_identifier(), queue.peek().unwrap().unwrap().user().value_identifier() @@ -283,11 +318,11 @@ mod tests { assert_eq!( user2.value_identifier(), - queue.dequeue().unwrap().user().value_identifier() + queue.dequeue().unwrap().unwrap().user().value_identifier() ); assert_eq!( user1.value_identifier(), - queue.dequeue().unwrap().user().value_identifier() + queue.dequeue().unwrap().unwrap().user().value_identifier() ); } @@ -302,13 +337,13 @@ mod tests { ]); assert_eq!( user2.value_identifier(), - queue.dequeue().unwrap().user().value_identifier() + queue.dequeue().unwrap().unwrap().user().value_identifier() ); assert!(!queue.move_pokemon_choice_next(user2.as_ref()).unwrap()); assert_eq!( user1.value_identifier(), - queue.dequeue().unwrap().user().value_identifier() + queue.dequeue().unwrap().unwrap().user().value_identifier() ); assert!(queue.peek().unwrap().is_none()) } @@ -362,18 +397,18 @@ mod tests { assert_eq!( users[4].value_identifier(), - queue.dequeue().unwrap().user().value_identifier() + queue.dequeue().unwrap().unwrap().user().value_identifier() ); for index in 0..4 { assert_eq!( users[index].value_identifier(), - queue.dequeue().unwrap().user().value_identifier() + queue.dequeue().unwrap().unwrap().user().value_identifier() ); } for index in 5..7 { assert_eq!( users[index].value_identifier(), - queue.dequeue().unwrap().user().value_identifier() + queue.dequeue().unwrap().unwrap().user().value_identifier() ); } } diff --git a/src/dynamic_data/flow/target_resolver.rs b/src/dynamic_data/flow/target_resolver.rs index a41688a..cd3b2e5 100755 --- a/src/dynamic_data/flow/target_resolver.rs +++ b/src/dynamic_data/flow/target_resolver.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use std::ops::Deref; use std::sync::Arc; @@ -6,6 +7,7 @@ use num_traits::abs; use crate::dynamic_data::Battle; use crate::dynamic_data::Pokemon; use crate::static_data::MoveTarget; +use crate::PkmnError; /// Helper type for the vector of targets we will return. pub type TargetList = Vec>>; @@ -102,8 +104,8 @@ fn get_all_adjacent(side: u8, index: u8, battle: &Battle) -> TargetList { } /// Gets the target for a specific move target type, given the targeted position. -pub fn resolve_targets(side: u8, index: u8, target: MoveTarget, battle: &Battle) -> TargetList { - match target { +pub fn resolve_targets(side: u8, index: u8, target: MoveTarget, battle: &Battle) -> Result { + Ok(match target { // These all resolve to a single position. We let the client deal with where the target is, // and just return the Pokemon at that given target here. MoveTarget::Adjacent @@ -125,12 +127,21 @@ pub fn resolve_targets(side: u8, index: u8, target: MoveTarget, battle: &Battle) // the client deal with what side is passed. MoveTarget::AllAlly | MoveTarget::AllOpponent => { let mut v = Vec::new(); - for pokemon in battle.sides()[side as usize].pokemon().deref() { + for pokemon in battle + .sides() + .get(side as usize) + .ok_or(PkmnError::IndexOutOfBounds { + index: side as usize, + len: battle.sides().len(), + })? + .pokemon() + .deref() + { v.push(pokemon.as_ref().cloned()); } v } - } + }) } /// Checks whether a given side and index are valid for a given move target and user. diff --git a/src/dynamic_data/flow/turn_runner.rs b/src/dynamic_data/flow/turn_runner.rs index 882a7c3..ae1c4ee 100755 --- a/src/dynamic_data/flow/turn_runner.rs +++ b/src/dynamic_data/flow/turn_runner.rs @@ -12,15 +12,12 @@ use crate::dynamic_data::DamageSource; use crate::dynamic_data::ExecutingMove; use crate::dynamic_data::Pokemon; use crate::static_data::MoveCategory; -use crate::{run_scripts, script_hook}; +use crate::{run_scripts, script_hook, PkmnError}; /// Simple macro to get a read lock on the choice queue. macro_rules! read_choice_queue { ($choice_queue:ident) => { - $choice_queue - .read() - .as_ref() - .ok_or(anyhow!("Could not get a lock on choice queue"))? + $choice_queue.read().as_ref().ok_or(PkmnError::UnableToAcquireLock)? }; } @@ -36,7 +33,7 @@ impl Battle { // is primarily intended to be used to reset variables on a script (for example scripts that need // to check whether a pokemon was hit this turn. By resetting here, and setting a variable to true // they can then know this later on.) - for choice in read_choice_queue!(choice_queue).get_queue().iter().flatten() { + for choice in read_choice_queue!(choice_queue).get_queue()?.iter().flatten() { script_hook!(on_before_turn, choice, choice); } @@ -47,8 +44,8 @@ impl Battle { let choice = choice_queue .write() .as_mut() - .ok_or(anyhow!("Failed to get a write lock on choice queue"))? - .dequeue(); + .ok_or(PkmnError::UnableToAcquireLock)? + .dequeue()?; if let Some(choice) = choice { self.execute_choice(&choice)?; } @@ -110,7 +107,7 @@ impl Battle { /// Executes a move choice. fn execute_move_choice<'func>(&'func self, choice: &'func TurnChoice) -> Result<()> { - let move_choice = choice.get_move_turn_data(); + let move_choice = choice.get_move_turn_data()?; let used_move = move_choice.used_move(); let move_data = { let move_data_lock = used_move; @@ -125,7 +122,7 @@ impl Battle { }; // FIXME: also change the script on the choice if changed; let target_type = move_data.target(); - let targets = resolve_targets(move_choice.target_side(), move_choice.target_index(), target_type, self); + let targets = resolve_targets(move_choice.target_side(), move_choice.target_index(), target_type, self)?; let mut number_of_hits: u8 = 1; script_hook!(change_number_of_hits, choice, choice, &mut number_of_hits); @@ -210,7 +207,7 @@ impl Battle { hit_index, &mut hit_type ); - let hit_data = executing_move.get_hit_from_raw_index(target_hit_stat + hit_index as usize); + let hit_data = executing_move.get_hit_from_raw_index(target_hit_stat + hit_index as usize)?; hit_data.set_move_type(hit_type); let mut effectiveness = self .library() @@ -256,7 +253,7 @@ impl Battle { target, hit_index, executing_move.get_hit_data(target, hit_index)?, - ); + )?; hit_data.set_base_power(base_power); let damage = self.library().damage_calculator().get_damage( executing_move, diff --git a/src/dynamic_data/libraries/battle_stat_calculator.rs b/src/dynamic_data/libraries/battle_stat_calculator.rs index 7fde222..af1f227 100755 --- a/src/dynamic_data/libraries/battle_stat_calculator.rs +++ b/src/dynamic_data/libraries/battle_stat_calculator.rs @@ -1,3 +1,4 @@ +use anyhow::{bail, Result}; use std::fmt::Debug; use crate::dynamic_data::Pokemon; @@ -8,13 +9,13 @@ use crate::{ValueIdentifiable, ValueIdentifier}; /// A battle stat calculator is used to calculate stats for a Pokemon. pub trait BattleStatCalculator: Debug + ValueIdentifiable { /// Calculate all the flat stats of a Pokemon, disregarding stat boosts. - fn calculate_flat_stats(&self, pokemon: &Pokemon, stats: &StatisticSet); + fn calculate_flat_stats(&self, pokemon: &Pokemon, stats: &StatisticSet) -> Result<()>; /// Calculate a single flat stat of a Pokemon, disregarding stat boost - fn calculate_flat_stat(&self, pokemon: &Pokemon, stat: Statistic) -> u32; + fn calculate_flat_stat(&self, pokemon: &Pokemon, stat: Statistic) -> Result; /// Calculate all the boosted stats of a Pokemon, including stat boosts. - fn calculate_boosted_stats(&self, pokemon: &Pokemon, stats: &StatisticSet); + fn calculate_boosted_stats(&self, pokemon: &Pokemon, stats: &StatisticSet) -> Result<()>; /// Calculate a single boosted stat of a Pokemon, including stat boosts. - fn calculate_boosted_stat(&self, pokemon: &Pokemon, stat: Statistic) -> u32; + fn calculate_boosted_stat(&self, pokemon: &Pokemon, stat: Statistic) -> Result; } /// A basic implementation of the Gen 7 stat calculator. @@ -39,28 +40,28 @@ impl Gen7BattleStatCalculator { } /// The calculation used for health points. - fn calculate_health_stat(&self, pokemon: &Pokemon) -> u32 { + fn calculate_health_stat(&self, pokemon: &Pokemon) -> Result { let base = pokemon.form().get_base_stat(Statistic::HP) as u32; let iv = pokemon.individual_values().hp() as u32; let ev = pokemon.effort_values().hp() as u32; let level = pokemon.level() as u32; - (((2 * base + iv + (ev / 4)) * level) / 100) + level + 10 + Ok((((2 * base + iv + (ev / 4)) * level) / 100) + level + 10) } /// The calculation used for all other stats - fn calculate_other_stat(&self, pokemon: &Pokemon, stat: Statistic) -> u32 { + fn calculate_other_stat(&self, pokemon: &Pokemon, stat: Statistic) -> Result { let base = pokemon.form().get_base_stat(stat) as u32; let iv = pokemon.individual_values().get_stat(stat) as u32; let ev = pokemon.effort_values().get_stat(stat) as u32; let level = pokemon.level() as u32; let unmodified = (((2 * base + iv + (ev / 4)) * level) / 100) + 5; - return (unmodified as f32 * pokemon.nature().get_stat_modifier(stat)) as u32; + Ok((unmodified as f32 * pokemon.nature().get_stat_modifier(stat)) as u32) } /// This functions returns the modifier we need to do to a stat for a given stat boost. - fn get_stat_boost_modifier(&self, pokemon: &Pokemon, stat: Statistic) -> f32 { + fn get_stat_boost_modifier(&self, pokemon: &Pokemon, stat: Statistic) -> Result { let boost = pokemon.stat_boost(stat); - match boost { + Ok(match boost { -6 => 2.0 / 8.0, -5 => 2.0 / 7.0, -4 => 2.0 / 6.0, @@ -74,31 +75,35 @@ impl Gen7BattleStatCalculator { 4 => 6.0 / 2.0, 5 => 7.0 / 2.0, 6 => 8.0 / 2.0, - _ => panic!("Stat boost was out of expected range of -6 to 6"), - } + _ => bail!("Stat boost was out of expected range of -6 to 6"), + }) } } impl BattleStatCalculator for Gen7BattleStatCalculator { - fn calculate_flat_stats(&self, pokemon: &Pokemon, stats: &StatisticSet) { - stats.set_stat(Statistic::HP, self.calculate_health_stat(pokemon)); - stats.set_stat(Statistic::Attack, self.calculate_other_stat(pokemon, Statistic::Attack)); + fn calculate_flat_stats(&self, pokemon: &Pokemon, stats: &StatisticSet) -> Result<()> { + stats.set_stat(Statistic::HP, self.calculate_health_stat(pokemon)?); + stats.set_stat( + Statistic::Attack, + self.calculate_other_stat(pokemon, Statistic::Attack)?, + ); stats.set_stat( Statistic::Defense, - self.calculate_other_stat(pokemon, Statistic::Defense), + self.calculate_other_stat(pokemon, Statistic::Defense)?, ); stats.set_stat( Statistic::SpecialAttack, - self.calculate_other_stat(pokemon, Statistic::SpecialAttack), + self.calculate_other_stat(pokemon, Statistic::SpecialAttack)?, ); stats.set_stat( Statistic::SpecialDefense, - self.calculate_other_stat(pokemon, Statistic::SpecialDefense), + self.calculate_other_stat(pokemon, Statistic::SpecialDefense)?, ); - stats.set_stat(Statistic::Speed, self.calculate_other_stat(pokemon, Statistic::Speed)); + stats.set_stat(Statistic::Speed, self.calculate_other_stat(pokemon, Statistic::Speed)?); + Ok(()) } - fn calculate_flat_stat(&self, pokemon: &Pokemon, stat: Statistic) -> u32 { + fn calculate_flat_stat(&self, pokemon: &Pokemon, stat: Statistic) -> Result { if stat == Statistic::HP { self.calculate_health_stat(pokemon) } else { @@ -106,29 +111,33 @@ impl BattleStatCalculator for Gen7BattleStatCalculator { } } - fn calculate_boosted_stats(&self, pokemon: &Pokemon, stats: &StatisticSet) { - stats.set_stat(Statistic::HP, self.calculate_boosted_stat(pokemon, Statistic::HP)); + fn calculate_boosted_stats(&self, pokemon: &Pokemon, stats: &StatisticSet) -> Result<()> { + stats.set_stat(Statistic::HP, self.calculate_boosted_stat(pokemon, Statistic::HP)?); stats.set_stat( Statistic::Attack, - self.calculate_boosted_stat(pokemon, Statistic::Attack), + self.calculate_boosted_stat(pokemon, Statistic::Attack)?, ); stats.set_stat( Statistic::Defense, - self.calculate_boosted_stat(pokemon, Statistic::Defense), + self.calculate_boosted_stat(pokemon, Statistic::Defense)?, ); stats.set_stat( Statistic::SpecialAttack, - self.calculate_boosted_stat(pokemon, Statistic::SpecialAttack), + self.calculate_boosted_stat(pokemon, Statistic::SpecialAttack)?, ); stats.set_stat( Statistic::SpecialDefense, - self.calculate_boosted_stat(pokemon, Statistic::SpecialDefense), + self.calculate_boosted_stat(pokemon, Statistic::SpecialDefense)?, ); - stats.set_stat(Statistic::Speed, self.calculate_boosted_stat(pokemon, Statistic::Speed)); + stats.set_stat( + Statistic::Speed, + self.calculate_boosted_stat(pokemon, Statistic::Speed)?, + ); + Ok(()) } - fn calculate_boosted_stat(&self, pokemon: &Pokemon, stat: Statistic) -> u32 { - (self.calculate_flat_stat(pokemon, stat) as f32 * self.get_stat_boost_modifier(pokemon, stat)) as u32 + fn calculate_boosted_stat(&self, pokemon: &Pokemon, stat: Statistic) -> Result { + Ok((self.calculate_flat_stat(pokemon, stat)? as f32 * self.get_stat_boost_modifier(pokemon, stat)?) as u32) } } @@ -145,10 +154,10 @@ pub mod tests { #[derive(Debug)] pub BattleStatCalculator{} impl BattleStatCalculator for BattleStatCalculator { - fn calculate_flat_stats(&self, pokemon: &Pokemon, stats: &StatisticSet); - fn calculate_flat_stat(&self, pokemon: &Pokemon, stat: Statistic) -> u32; - fn calculate_boosted_stats(&self, pokemon: &Pokemon, stats: &StatisticSet); - fn calculate_boosted_stat(&self, pokemon: &Pokemon, stat: Statistic) -> u32; + fn calculate_flat_stats(&self, pokemon: &Pokemon, stats: &StatisticSet) -> Result<()>; + fn calculate_flat_stat(&self, pokemon: &Pokemon, stat: Statistic) -> Result; + fn calculate_boosted_stats(&self, pokemon: &Pokemon, stats: &StatisticSet) -> Result<()>; + fn calculate_boosted_stat(&self, pokemon: &Pokemon, stat: Statistic) -> Result; } impl ValueIdentifiable for BattleStatCalculator { fn value_identifier(&self) -> ValueIdentifier{ diff --git a/src/dynamic_data/libraries/damage_library.rs b/src/dynamic_data/libraries/damage_library.rs index e9a21f0..c632df0 100755 --- a/src/dynamic_data/libraries/damage_library.rs +++ b/src/dynamic_data/libraries/damage_library.rs @@ -27,7 +27,7 @@ pub trait DamageLibrary: std::fmt::Debug + ValueIdentifiable { target: &Arc, hit_number: u8, hit_data: &HitData, - ) -> u8; + ) -> Result; /// Returns whether a specified hit should be critical or not. fn is_critical( @@ -65,7 +65,7 @@ impl Gen7DamageLibrary { target: &Arc, hit_number: u8, hit_data: &HitData, - ) -> f32 { + ) -> Result { let user = executing_move.user(); let offensive_stat; let defensive_stat; @@ -144,7 +144,7 @@ impl Gen7DamageLibrary { &mut stat_modifier ); - stat_modifier + Ok(stat_modifier) } /// Gets the damage modifier. This is a value that defaults to 1.0, but can be modified by scripts @@ -155,7 +155,7 @@ impl Gen7DamageLibrary { target: &Arc, hit_number: u8, _hit_data: &HitData, - ) -> f32 { + ) -> Result { let mut modifier = 1.0; script_hook!( change_damage_modifier, @@ -165,7 +165,7 @@ impl Gen7DamageLibrary { hit_number, &mut modifier ); - modifier + Ok(modifier) } } @@ -183,8 +183,8 @@ impl DamageLibrary for Gen7DamageLibrary { let level_modifier = ((2.0 * executing_move.user().level() as f32) / 5.0).floor() + 2.0; let base_power = hit_data.base_power(); - let stat_modifier = self.get_stat_modifier(executing_move, target, hit_number, hit_data); - let damage_modifier = self.get_damage_modifier(executing_move, target, hit_number, hit_data); + let stat_modifier = self.get_stat_modifier(executing_move, target, hit_number, hit_data)?; + let damage_modifier = self.get_damage_modifier(executing_move, target, hit_number, hit_data)?; let mut float_damage = (level_modifier * base_power as f32).floor(); float_damage = (float_damage * stat_modifier).floor(); @@ -264,9 +264,9 @@ impl DamageLibrary for Gen7DamageLibrary { target: &Arc, hit_number: u8, _hit_data: &HitData, - ) -> u8 { + ) -> Result { if executing_move.use_move().category() == MoveCategory::Status { - return 0; + return Ok(0); } let mut base_power = executing_move.use_move().base_power(); @@ -278,7 +278,7 @@ impl DamageLibrary for Gen7DamageLibrary { hit_number, &mut base_power ); - base_power + Ok(base_power) } fn is_critical( diff --git a/src/dynamic_data/libraries/dynamic_library.rs b/src/dynamic_data/libraries/dynamic_library.rs index f26ba44..9762e9b 100755 --- a/src/dynamic_data/libraries/dynamic_library.rs +++ b/src/dynamic_data/libraries/dynamic_library.rs @@ -115,7 +115,8 @@ impl DynamicLibrary for DynamicLibraryImpl { /// combinations, returns None. Note that ItemScripts are immutable, as their script should be /// shared between all different usages. fn load_item_script(&self, _key: &Arc) -> Result>> { - todo!() + // TODO + Err(anyhow::anyhow!("Not implemented yet")) } } diff --git a/src/dynamic_data/models/battle.rs b/src/dynamic_data/models/battle.rs index f346016..4b72c1c 100755 --- a/src/dynamic_data/models/battle.rs +++ b/src/dynamic_data/models/battle.rs @@ -20,7 +20,7 @@ use crate::dynamic_data::VolatileScriptsOwner; use crate::dynamic_data::{is_valid_target, ScriptWrapper}; use crate::dynamic_data::{ChoiceQueue, ScriptContainer}; use crate::dynamic_data::{ScriptCategory, ScriptSource, ScriptSourceData}; -use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier}; +use crate::{script_hook, PkmnError, StringKey, ValueIdentifiable, ValueIdentifier}; /// A pokemon battle, with any amount of sides and pokemon per side. #[derive(Debug)] @@ -271,7 +271,13 @@ impl Battle { let side = choice.user().get_battle_side_index(); match side { Some(side) => { - self.sides[side as usize].set_choice(choice); + self.sides + .get(side as usize) + .ok_or(PkmnError::IndexOutOfBounds { + index: side as usize, + len: self.sides.len(), + })? + .set_choice(choice)?; self.check_choices_set_and_run()?; Ok(true) } @@ -286,7 +292,7 @@ impl Battle { if !side.all_choices_set() { return Ok(()); } - if !side.all_slots_filled() { + if !side.all_slots_filled()? { return Ok(()); } } @@ -361,10 +367,7 @@ impl Battle { if let Some(script) = self.weather.get() { let lock = script.read(); Ok(Some( - lock.as_ref() - .ok_or(anyhow!("Failed to get a lock on weather"))? - .name() - .clone(), + lock.as_ref().ok_or(PkmnError::UnableToAcquireLock)?.name().clone(), )) } else { Ok(None) @@ -383,8 +386,8 @@ impl VolatileScriptsOwner for Battle { } impl ScriptSource for Battle { - fn get_script_count(&self) -> usize { - 1 + fn get_script_count(&self) -> Result { + Ok(1) } fn get_script_source_data(&self) -> &RwLock { @@ -396,8 +399,9 @@ impl ScriptSource for Battle { scripts.push((&self.volatile_scripts).into()); } - fn collect_scripts(&self, scripts: &mut Vec) { + fn collect_scripts(&self, scripts: &mut Vec) -> Result<()> { self.get_own_scripts(scripts); + Ok(()) } } diff --git a/src/dynamic_data/models/battle_random.rs b/src/dynamic_data/models/battle_random.rs index fdf49f9..9a88b61 100755 --- a/src/dynamic_data/models/battle_random.rs +++ b/src/dynamic_data/models/battle_random.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use anyhow_ext::anyhow; use std::fmt::{Debug, Formatter}; use std::sync::{Arc, Mutex}; @@ -7,7 +6,7 @@ use crate::dynamic_data::models::executing_move::ExecutingMove; use crate::dynamic_data::models::pokemon::Pokemon; use crate::dynamic_data::script_handling::ScriptSource; use crate::utils::Random; -use crate::{script_hook, ValueIdentifiable, ValueIdentifier}; +use crate::{script_hook, PkmnError, ValueIdentifiable, ValueIdentifier}; /// The RNG for a battle. #[derive(Default)] @@ -37,21 +36,21 @@ impl BattleRandom { pub fn get(&self) -> Result { match self.get_rng().lock() { Ok(mut l) => Ok(l.get()), - Err(_) => Err(anyhow!("Failed to get a RNG lock")), + Err(_) => Err(PkmnError::UnableToAcquireLock.into()), } } /// Get a random 32 bit integer between 0 and max. pub fn get_max(&self, max: i32) -> Result { match self.get_rng().lock() { Ok(mut l) => Ok(l.get_max(max)), - Err(_) => Err(anyhow!("Failed to get a RNG lock")), + Err(_) => Err(PkmnError::UnableToAcquireLock.into()), } } /// Get a random 32 bit integer between min and max. pub fn get_between(&self, min: i32, max: i32) -> Result { match self.get_rng().lock() { Ok(mut l) => Ok(l.get_between(min, max)), - Err(_) => Err(anyhow!("Failed to get a RNG lock")), + Err(_) => Err(PkmnError::UnableToAcquireLock.into()), } } @@ -85,7 +84,7 @@ impl BattleRandom { if chance > 0.0 { match self.get_rng().lock() { Ok(mut l) => Ok(l.get_float() < (chance / 100.0)), - Err(_) => Err(anyhow!("Failed to get a RNG lock")), + Err(_) => Err(PkmnError::UnableToAcquireLock.into()), } } else { Ok(false) diff --git a/src/dynamic_data/models/battle_side.rs b/src/dynamic_data/models/battle_side.rs index bd21b72..1e10e54 100755 --- a/src/dynamic_data/models/battle_side.rs +++ b/src/dynamic_data/models/battle_side.rs @@ -2,20 +2,19 @@ use std::ops::Deref; use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::Arc; -use anyhow::Result; +use anyhow::{anyhow, Result}; use parking_lot::lock_api::RwLockReadGuard; use parking_lot::{RawRwLock, RwLock}; use crate::dynamic_data::choices::TurnChoice; use crate::dynamic_data::event_hooks::Event; use crate::dynamic_data::models::battle::Battle; -use crate::dynamic_data::models::battle_party::BattleParty; use crate::dynamic_data::models::pokemon::Pokemon; use crate::dynamic_data::script_handling::{ScriptSource, ScriptSourceData, ScriptWrapper}; use crate::dynamic_data::Script; use crate::dynamic_data::ScriptSet; use crate::dynamic_data::VolatileScriptsOwner; -use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier}; +use crate::{script_hook, PkmnError, StringKey, ValueIdentifiable, ValueIdentifier}; /// A side on a battle. #[derive(Debug)] @@ -107,8 +106,8 @@ impl BattleSide { self.choices_set.load(Ordering::SeqCst) } /// A reference to the battle we're part of. - pub fn battle(&self) -> &Battle { - unsafe { self.battle.as_ref().unwrap() } + pub fn battle(&self) -> Result<&Battle> { + unsafe { self.battle.as_ref().ok_or(anyhow!("Battle was not set, but requested")) } } /// Whether or not this side has fled. pub fn has_fled_battle(&self) -> bool { @@ -127,60 +126,93 @@ impl BattleSide { /// Returns true if there are slots that need to be filled with a new pokemon, that have parties /// 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 { + pub fn all_slots_filled(&self) -> Result { for (i, pokemon) in self.pokemon.read().iter().enumerate() { - if (!pokemon.is_none() || !pokemon.as_ref().unwrap().is_usable()) - && self.battle().can_slot_be_filled(self.index, i as u8) - { - return false; - } - } - true - } - - /// Sets a choice for a Pokemon on this side. - pub(crate) fn set_choice(&self, choice: TurnChoice) { - for (index, pokemon_slot) in self.pokemon.read().iter().enumerate() { - if let Some(pokemon) = pokemon_slot { - if std::ptr::eq(pokemon.deref(), choice.user().deref()) { - self.choices.write()[index] = Some(choice); - self.choices_set.fetch_add(1, Ordering::SeqCst); - return; + match pokemon { + Some(pokemon) => { + if !pokemon.is_usable() && self.battle()?.can_slot_be_filled(self.index, i as u8) { + return Ok(false); + } + } + None => { + if self.battle()?.can_slot_be_filled(self.index, i as u8) { + return Ok(false); + } } } } + Ok(true) + } + + /// Sets a choice for a Pokemon on this side. + pub(crate) fn set_choice(&self, choice: TurnChoice) -> Result<()> { + for (index, pokemon_slot) in self.pokemon.read().iter().enumerate() { + if let Some(pokemon) = pokemon_slot { + if std::ptr::eq(pokemon.deref(), choice.user().deref()) { + let len = self.choices.read().len(); + self.choices + .write() + .get_mut(index) + .ok_or(PkmnError::IndexOutOfBounds { index, len })? + .replace(choice); + self.choices_set.fetch_add(1, Ordering::SeqCst); + return Ok(()); + } + } + } + Ok(()) } /// Resets all choices on this side. pub fn reset_choices(&self) { let len = self.choices.read().len(); for i in 0..len { - self.choices.write()[i] = None; + self.choices.write().get_mut(i).take(); } } /// Forcibly removes a Pokemon from the field. pub fn force_clear_pokemon(&mut self, index: u8) { - self.pokemon.write()[index as usize] = None; + self.pokemon.write().get_mut(index as usize).take(); } /// Switches out a spot on the field for a different Pokemon. - pub fn set_pokemon(&self, index: u8, pokemon: Option>) { + pub fn set_pokemon(&self, index: u8, pokemon: Option>) -> Result<()> { { - let old = &self.pokemon.read()[index as usize]; - if let Some(old_pokemon) = old { + let mut write_lock = self.pokemon.write(); + let old = write_lock.get_mut(index as usize).ok_or(PkmnError::IndexOutOfBounds { + index: index as usize, + len: self.pokemon_per_side as usize, + })?; + let old = match pokemon { + Some(pokemon) => old.replace(pokemon), + None => old.take(), + }; + if let Some(old_pokemon) = old.clone() { + // We need to drop the lock before calling the hook, as it might try to access the + // side again. + drop(write_lock); script_hook!(on_remove, old_pokemon,); old_pokemon.set_on_battlefield(false); } } - self.pokemon.write()[index as usize] = pokemon; - let pokemon = &self.pokemon.read()[index as usize]; + + let pokemon = { + let read_lock = self.pokemon.read(); + &read_lock + .get(index as usize) + .ok_or(PkmnError::IndexOutOfBounds { + index: index as usize, + len: read_lock.len(), + })? + .clone() + }; if let Some(pokemon) = pokemon { pokemon.set_battle_data(self.battle, self.index); pokemon.set_on_battlefield(true); pokemon.set_battle_index(index); - let battle = self.battle(); + let battle = self.battle()?; for side in battle.sides() { if side.index() == self.index { continue; @@ -197,12 +229,13 @@ impl BattleSide { }); script_hook!(on_switch_in, pokemon, pokemon); } else { - self.battle().event_hook().trigger(Event::Switch { + self.battle()?.event_hook().trigger(Event::Switch { side_index: self.index, index, pokemon: None, }); } + Ok(()) } /// Checks whether a Pokemon is on the field in this side. @@ -217,20 +250,34 @@ impl BattleSide { /// Marks a slot as unfillable. This happens when no parties are able to fill the slot anymore. /// If this happens, the slot can not be used again. - pub(crate) fn mark_slot_as_unfillable(&self, index: u8) { - self.fillable_slots[index as usize].store(false, Ordering::SeqCst); + pub(crate) fn mark_slot_as_unfillable(&self, index: u8) -> Result<()> { + self.fillable_slots + .get(index as usize) + .ok_or(PkmnError::IndexOutOfBounds { + index: index as usize, + len: self.fillable_slots.len(), + })? + .store(false, Ordering::SeqCst); + Ok(()) } /// Checks whether a slot is unfillable or not. - pub fn is_slot_unfillable(&self, pokemon: Arc) -> bool { + pub fn is_slot_unfillable(&self, pokemon: Arc) -> Result { for (i, slot) in self.pokemon.read().iter().enumerate() { if let Some(p) = slot { if std::ptr::eq(p.deref().deref(), pokemon.deref()) { - return self.fillable_slots[i].load(Ordering::Relaxed); + return Ok(self + .fillable_slots + .get(i) + .ok_or(PkmnError::IndexOutOfBounds { + index: i, + len: self.fillable_slots.len(), + })? + .load(Ordering::Relaxed)); } } } - false + Ok(false) } /// Checks whether the side has been defeated. @@ -251,24 +298,24 @@ impl BattleSide { /// Gets a random Pokemon on the given side. pub fn get_random_creature_index(&self) -> Result { // TODO: Consider adding parameter to only get index for available creatures. - Ok(self.battle().random().get_max(self.pokemon_per_side as i32)? as u8) + Ok(self.battle()?.random().get_max(self.pokemon_per_side as i32)? as u8) } /// Swap two Pokemon on a single side around. - pub fn swap_positions(&mut self, a: u8, b: u8) -> bool { + pub fn swap_positions(&mut self, a: u8, b: u8) -> Result { // If out of range, don't allow swapping. if a >= self.pokemon_per_side || b >= self.pokemon_per_side { - return false; + return Ok(false); } // If the two indices are the same, don't allow swapping. if a == b { - return false; + return Ok(false); } // Fetch parties for the two indices. let mut party_a = None; let mut party_b = None; - for party in self.battle().parties() { + for party in self.battle()?.parties() { if party.is_responsible_for_index(self.index, a) { party_a = Some(party); } @@ -277,21 +324,26 @@ impl BattleSide { } } // If either of the parties does not exist, fail. - if party_a.is_none() || party_b.is_none() { - return false; - } + let party_a = match party_a { + Some(party) => party, + None => return Ok(false), + }; + let party_b = match party_b { + Some(party) => party, + None => return Ok(false), + }; // Don't allow swapping if different parties are responsible for the indices. - if party_a.unwrap() as *const BattleParty != party_b.unwrap() as *const BattleParty { - return false; + if party_a.value_identifier() != party_b.value_identifier() { + return Ok(false); } self.pokemon.write().swap(a as usize, b as usize); - self.battle().event_hook().trigger(Event::Swap { + self.battle()?.event_hook().trigger(Event::Swap { side_index: self.index, index_a: a, index_b: b, }); - true + Ok(true) } } @@ -301,15 +353,15 @@ impl VolatileScriptsOwner for BattleSide { } fn load_volatile_script(&self, key: &StringKey) -> Result>> { - self.battle() + self.battle()? .library() .load_script(self.into(), crate::ScriptCategory::Side, key) } } impl ScriptSource for BattleSide { - fn get_script_count(&self) -> usize { - self.battle().get_script_count() + 1 + fn get_script_count(&self) -> Result { + Ok(self.battle()?.get_script_count()? + 1) } fn get_script_source_data(&self) -> &RwLock { @@ -320,9 +372,9 @@ impl ScriptSource for BattleSide { scripts.push((&self.volatile_scripts).into()); } - fn collect_scripts(&self, scripts: &mut Vec) { + fn collect_scripts(&self, scripts: &mut Vec) -> Result<()> { self.get_own_scripts(scripts); - self.battle().collect_scripts(scripts); + self.battle()?.collect_scripts(scripts) } } diff --git a/src/dynamic_data/models/executing_move.rs b/src/dynamic_data/models/executing_move.rs index aa0485b..249e626 100755 --- a/src/dynamic_data/models/executing_move.rs +++ b/src/dynamic_data/models/executing_move.rs @@ -13,7 +13,7 @@ use crate::dynamic_data::script_handling::{ScriptSource, ScriptSourceData, Scrip use crate::dynamic_data::ScriptContainer; use crate::dynamic_data::TargetList; use crate::static_data::{MoveData, TypeIdentifier}; -use crate::{ValueIdentifiable, ValueIdentifier}; +use crate::{PkmnError, ValueIdentifiable, ValueIdentifier}; /// A hit data is the data for a single hit, on a single target. #[derive(Default, Debug)] @@ -171,7 +171,14 @@ impl ExecutingMove { if let Some(target) = target { if std::ptr::eq(target.deref().deref(), for_target.deref().deref()) { let i = index * self.number_of_hits as usize + hit as usize; - return Ok(&self.hits[i]); + return match self.hits.get(i) { + Some(hit) => Ok(hit), + None => Err(PkmnError::IndexOutOfBounds { + index: i, + len: self.hits.len(), + } + .into()), + }; } } } @@ -202,14 +209,21 @@ impl ExecutingMove { } /// Gets a hit based on its raw index. - pub(crate) fn get_hit_from_raw_index(&self, index: usize) -> &HitData { - &self.hits[index] + pub(crate) fn get_hit_from_raw_index(&self, index: usize) -> Result<&HitData> { + match self.hits.get(index) { + Some(hit) => Ok(hit), + None => Err(PkmnError::IndexOutOfBounds { + index, + len: self.hits.len(), + } + .into()), + } } } impl ScriptSource for ExecutingMove { - fn get_script_count(&self) -> usize { - 1 + fn get_script_count(&self) -> Result { + Ok(1) } fn get_script_source_data(&self) -> &RwLock { @@ -220,9 +234,10 @@ impl ScriptSource for ExecutingMove { scripts.push((&self.script).into()); } - fn collect_scripts(&self, scripts: &mut Vec) { + fn collect_scripts(&self, scripts: &mut Vec) -> Result<()> { self.get_own_scripts(scripts); self.user.get_own_scripts(scripts); + Ok(()) } } diff --git a/src/dynamic_data/models/learned_move.rs b/src/dynamic_data/models/learned_move.rs index e8e0d78..01513dc 100755 --- a/src/dynamic_data/models/learned_move.rs +++ b/src/dynamic_data/models/learned_move.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::Arc; @@ -80,7 +81,7 @@ impl LearnedMove { } /// Restore the remaining PP by a certain amount. Will prevent it from going above max PP. - pub fn restore_uses(&self, mut uses: u8) { + pub fn restore_uses(&self, mut uses: u8) -> Result<()> { self.remaining_pp .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| { if x + uses > self.max_pp { @@ -88,7 +89,8 @@ impl LearnedMove { } Some(x + uses) }) - .unwrap(); + .ok(); + Ok(()) } } @@ -110,7 +112,7 @@ mod tests { let data: Arc = Arc::new(mock); let learned_move = LearnedMove::new(data, MoveLearnMethod::Level); assert!(learned_move.try_use(15)); - learned_move.restore_uses(5); + learned_move.restore_uses(5).unwrap(); assert_eq!(20, learned_move.remaining_pp()); } } diff --git a/src/dynamic_data/models/pokemon.rs b/src/dynamic_data/models/pokemon.rs index 7de6298..397d1e2 100755 --- a/src/dynamic_data/models/pokemon.rs +++ b/src/dynamic_data/models/pokemon.rs @@ -22,8 +22,8 @@ use crate::static_data::TypeIdentifier; use crate::static_data::{Ability, Statistic}; use crate::static_data::{ClampedStatisticSet, StatisticSet}; use crate::utils::Random; -use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier}; -use anyhow::Result; +use crate::{script_hook, PkmnError, StringKey, ValueIdentifiable, ValueIdentifier}; +use anyhow::{anyhow, bail, Result}; /// An individual Pokemon as we know and love them. #[derive(Debug)] @@ -142,7 +142,7 @@ impl Pokemon { .static_data() .natures() .get_nature(nature) - .unwrap_or_else(|| panic!("Unknown nature name was given: {}.", &nature)); + .ok_or(PkmnError::InvalidNatureName { nature: nature.clone() })?; let mut pokemon = Self { identifier: Default::default(), library, @@ -180,7 +180,7 @@ impl Pokemon { volatile: Default::default(), script_source_data: Default::default(), }; - pokemon.recalculate_flat_stats(); + pokemon.recalculate_flat_stats()?; let health = pokemon.flat_stats().hp(); pokemon.current_health = AtomicU32::new(health); @@ -257,20 +257,19 @@ impl Pokemon { self.held_item.write().take() } /// Makes the Pokemon uses its held item. - pub fn consume_held_item(&self) -> bool { + pub fn consume_held_item(&self) -> Result { if self.held_item.read().is_none() { - return false; + return Ok(false); } let script = self .library - .load_item_script(self.held_item.read().as_ref().unwrap()) - .unwrap(); + .load_item_script(self.held_item.read().as_ref().ok_or(PkmnError::UnableToAcquireLock)?)?; if script.is_none() { - return false; + return Ok(false); } // TODO: the entire item use part. - todo!(); + bail!("Not implemented yet.") } /// The remaining health points of the Pokemon. @@ -331,7 +330,7 @@ impl Pokemon { self.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) -> bool { + pub fn change_stat_boost(&self, stat: Statistic, mut diff_amount: i8, self_inflicted: bool) -> Result { let mut prevent = false; script_hook!( prevent_stat_boost_change, @@ -343,7 +342,7 @@ impl Pokemon { &mut prevent ); if prevent { - return false; + return Ok(false); } script_hook!( change_stat_boost_change, @@ -354,7 +353,7 @@ impl Pokemon { &mut diff_amount ); if diff_amount == 0 { - return false; + return Ok(false); } let mut changed = false; @@ -378,9 +377,9 @@ impl Pokemon { new_value, }) } - self.recalculate_boosted_stats(); + self.recalculate_boosted_stats()?; } - changed + Ok(changed) } /// The [individual values](https://bulbapedia.bulbagarden.net/wiki/Individual_values) of the Pokemon. @@ -416,15 +415,21 @@ impl Pokemon { self.override_ability.is_some() } /// Returns the currently active ability. - pub fn active_ability(&self) -> Arc { + pub fn active_ability(&self) -> Result> { if let Some(v) = &self.override_ability { - return v.clone(); + return Ok(v.clone()); } - self.library + + let form = self.form(); + let ability = form.get_ability(self.ability_index); + Ok(self + .library .static_data() .abilities() - .get(self.form().get_ability(self.ability_index)) - .unwrap() + .get(ability) + .ok_or(PkmnError::InvalidAbilityName { + ability: ability.clone(), + })?) } /// The script for the status. @@ -450,22 +455,23 @@ impl Pokemon { /// 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 /// stats, as those depend on the flat stats. - pub fn recalculate_flat_stats(&self) { + pub fn recalculate_flat_stats(&self) -> Result<()> { self.library .stat_calculator() - .calculate_flat_stats(self, &self.flat_stats); - self.recalculate_boosted_stats(); + .calculate_flat_stats(self, &self.flat_stats)?; + self.recalculate_boosted_stats()?; + Ok(()) } /// Calculates the boosted stats on the Pokemon, _without_ recalculating the flat stats. /// This should be called when a stat boost changes. - pub fn recalculate_boosted_stats(&self) { + pub fn recalculate_boosted_stats(&self) -> Result<()> { self.library .stat_calculator() - .calculate_boosted_stats(self, &self.boosted_stats); + .calculate_boosted_stats(self, &self.boosted_stats) } /// Change the species of the Pokemon. - pub fn change_species(&self, species: Arc, form: Arc) { + pub fn change_species(&self, species: Arc, form: Arc) -> Result<()> { *self.species.write() = species.clone(); *self.form.write() = form.clone(); @@ -474,7 +480,16 @@ impl Pokemon { // If we're in battle, use the battle random for predictability let r = self.battle_data.read(); if let Some(data) = r.deref() { - let mut random = data.battle().unwrap().random().get_rng().lock().unwrap(); + let mut random = match data + .battle() + .ok_or(anyhow!("Battle not set"))? + .random() + .get_rng() + .lock() + { + Ok(v) => v, + Err(_) => return Err(PkmnError::UnableToAcquireLock.into()), + }; *self.gender.write() = species.get_random_gender(random.deref_mut()); } else { // If we're not in battle, just use a new random. @@ -495,12 +510,13 @@ impl Pokemon { }) } } + Ok(()) } /// Change the form of the Pokemon. - pub fn change_form(&self, form: &Arc) { + pub fn change_form(&self, form: &Arc) -> Result<()> { if self.form().value_identifier() == form.value_identifier() { - return; + return Ok(()); } *self.form.write() = form.clone(); @@ -514,21 +530,21 @@ impl Pokemon { self.weight.store(form.weight(), Ordering::SeqCst); self.height.store(form.height(), Ordering::SeqCst); + let ability = self.active_ability()?; let ability_script = self .library - .load_script(self.into(), ScriptCategory::Ability, self.active_ability().name()) - .unwrap(); + .load_script(self.into(), ScriptCategory::Ability, ability.name())?; if let Some(ability_script) = ability_script { self.ability_script .set(ability_script) .as_ref() // Ensure the ability script gets initialized with the parameters for the ability. - .on_initialize(&self.library, self.active_ability().parameters().to_vec()) + .on_initialize(&self.library, ability.parameters().to_vec()) } else { self.ability_script.clear(); } let old_health = self.max_health(); - self.recalculate_flat_stats(); + 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.store(0, Ordering::SeqCst); @@ -547,7 +563,8 @@ impl Pokemon { form: form.clone(), }) } - } + }; + Ok(()) } /// Whether or not the Pokemon is useable in a battle. @@ -657,8 +674,14 @@ impl Pokemon { script_hook!(on_remove, self,); if !battle.can_slot_be_filled(battle_data.battle_side_index(), battle_data.index()) { - battle.sides()[battle_data.battle_side_index() as usize] - .mark_slot_as_unfillable(battle_data.index()); + battle + .sides() + .get(battle_data.battle_side_index() as usize) + .ok_or(PkmnError::IndexOutOfBounds { + index: battle_data.battle_side_index() as usize, + len: battle.sides().len(), + })? + .mark_slot_as_unfillable(battle_data.index())?; } battle.validate_battle_state()?; @@ -695,14 +718,22 @@ impl Pokemon { } /// Learn a move. - pub fn learn_move(&self, move_name: &StringKey, learn_method: MoveLearnMethod) { + pub fn learn_move(&self, move_name: &StringKey, learn_method: MoveLearnMethod) -> Result<()> { let mut learned_moves = self.learned_moves().write(); let move_pos = learned_moves.iter().position(|a| a.is_none()); if move_pos.is_none() { - panic!("No more moves with an empty space found."); + bail!("No more moves with an empty space found."); } - let move_data = self.library.static_data().moves().get(move_name).unwrap(); + let move_data = self + .library + .static_data() + .moves() + .get(move_name) + .ok_or(PkmnError::InvalidMoveName { + move_name: move_name.clone(), + })?; learned_moves[move_pos.unwrap()] = Some(Arc::new(LearnedMove::new(move_data, learn_method))); + Ok(()) } /// Removes the current non-volatile status from the Pokemon. @@ -711,7 +742,7 @@ impl Pokemon { } /// Increases the level by a certain amount - pub fn change_level_by(&self, amount: LevelInt) { + pub fn change_level_by(&self, amount: LevelInt) -> Result<()> { self.level .fetch_update(Ordering::SeqCst, Ordering::Relaxed, |x| { let max_level = self.library().static_data().settings().maximum_level(); @@ -721,8 +752,8 @@ impl Pokemon { Some(x + amount) } }) - .expect("Failed to change level."); - self.recalculate_flat_stats(); + .ok(); + self.recalculate_flat_stats() } } @@ -770,14 +801,14 @@ impl PokemonBattleData { } impl ScriptSource for Pokemon { - fn get_script_count(&self) -> usize { + fn get_script_count(&self) -> Result { let mut c = 3; if let Some(battle_data) = &self.battle_data.read().deref() { if let Some(battle) = battle_data.battle() { - c += battle.sides()[battle_data.battle_side_index() as usize].get_script_count(); + c += battle.sides()[battle_data.battle_side_index() as usize].get_script_count()?; } } - c + Ok(c) } fn get_script_source_data(&self) -> &RwLock { @@ -791,13 +822,14 @@ impl ScriptSource for Pokemon { scripts.push((&self.volatile).into()); } - fn collect_scripts(&self, scripts: &mut Vec) { + fn collect_scripts(&self, scripts: &mut Vec) -> Result<()> { self.get_own_scripts(scripts); if let Some(battle_data) = &self.battle_data.read().deref() { if let Some(battle) = battle_data.battle() { - battle.sides()[battle_data.battle_side_index() as usize].collect_scripts(scripts); + battle.sides()[battle_data.battle_side_index() as usize].collect_scripts(scripts)?; } } + Ok(()) } } @@ -883,8 +915,10 @@ pub mod test { static_lib.expect_natures().return_const(Box::new(nature_lib)); let mut stat_calculator = MockBattleStatCalculator::new(); - stat_calculator.expect_calculate_flat_stats().returning(|_, _| {}); - stat_calculator.expect_calculate_boosted_stats().returning(|_, _| {}); + stat_calculator.expect_calculate_flat_stats().returning(|_, _| Ok(())); + stat_calculator + .expect_calculate_boosted_stats() + .returning(|_, _| Ok(())); let mut lib = MockDynamicLibrary::new(); lib.expect_static_data().return_const(Box::new(static_lib)); diff --git a/src/dynamic_data/models/pokemon_builder.rs b/src/dynamic_data/models/pokemon_builder.rs index 4673f29..762855b 100755 --- a/src/dynamic_data/models/pokemon_builder.rs +++ b/src/dynamic_data/models/pokemon_builder.rs @@ -64,7 +64,7 @@ impl PokemonBuilder { &"hardy".into(), )?; for learned_move in self.learned_moves { - p.learn_move(&learned_move, MoveLearnMethod::Unknown); + p.learn_move(&learned_move, MoveLearnMethod::Unknown)?; } Ok(p) diff --git a/src/dynamic_data/script_handling/mod.rs b/src/dynamic_data/script_handling/mod.rs index c990360..b5fd4b1 100755 --- a/src/dynamic_data/script_handling/mod.rs +++ b/src/dynamic_data/script_handling/mod.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use std::sync::{Arc, Weak}; use parking_lot::RwLock; @@ -25,7 +26,7 @@ mod volatile_scripts_owner; #[macro_export] macro_rules! script_hook { ($hook_name: ident, $source: expr, $($parameters: expr),*) => { - let mut aggregator = $source.get_script_iterator(); + let mut aggregator = $source.get_script_iterator()?; while let Some(script_container) = aggregator.get_next() { let script = script_container.get(); if let Some(script) = script { @@ -89,20 +90,20 @@ pub struct ScriptSourceData { pub trait ScriptSource { /// Gets an iterator over all the scripts that are relevant to this script source. If the data /// has not been initialised, it will do so here. - fn get_script_iterator(&self) -> ScriptIterator { + fn get_script_iterator(&self) -> Result { let lock = self.get_script_source_data(); if !lock.read().is_initialized { let mut data = lock.write(); - data.scripts = Vec::with_capacity(self.get_script_count()); - self.collect_scripts(&mut data.scripts); + data.scripts = Vec::with_capacity(self.get_script_count()?); + self.collect_scripts(&mut data.scripts)?; data.is_initialized = true; } - ScriptIterator::new(&lock.read().scripts as *const Vec) + Ok(ScriptIterator::new(&lock.read().scripts as *const Vec)) } /// The number of scripts that are expected to be relevant for this source. This generally is /// the number of its own scripts + the number of scripts for any parents. - fn get_script_count(&self) -> usize; + fn get_script_count(&self) -> Result; /// Returns the underlying data required for us to be a script source. fn get_script_source_data(&self) -> &RwLock; /// This should add all scripts belonging to this source to the scripts Vec, disregarding its @@ -110,7 +111,7 @@ pub trait ScriptSource { fn get_own_scripts(&self, scripts: &mut Vec); /// This should add all scripts that are relevant to the source the the scripts Vec, including /// everything that belongs to its parents. - fn collect_scripts(&self, scripts: &mut Vec); + fn collect_scripts(&self, scripts: &mut Vec) -> Result<()>; } /// Enum to store both ScriptSets and sets in a single value. @@ -475,8 +476,8 @@ mod tests { } impl ScriptSource for TestScriptSource { - fn get_script_count(&self) -> usize { - 1 + fn get_script_count(&self) -> Result { + Ok(1) } fn get_script_source_data(&self) -> &RwLock { @@ -487,8 +488,9 @@ mod tests { scripts.push((&self.script).into()); } - fn collect_scripts(&self, scripts: &mut Vec) { + fn collect_scripts(&self, scripts: &mut Vec) -> Result<()> { self.get_own_scripts(scripts); + Ok(()) } } @@ -499,7 +501,7 @@ mod tests { script: ScriptContainer::default(), }; - let mut aggregator = source.get_script_iterator(); + let mut aggregator = source.get_script_iterator().unwrap(); aggregator.reset(); assert!(aggregator.get_next().is_none()); aggregator.reset(); @@ -514,7 +516,7 @@ mod tests { script: ScriptContainer::default(), }; - let mut aggregator = source.get_script_iterator(); + let mut aggregator = source.get_script_iterator().unwrap(); source.script.set(Arc::new(TestScript::new())); assert!(aggregator.get_next().is_some()); aggregator.reset(); diff --git a/src/ffi/dynamic_data/libraries/battle_stat_calculator.rs b/src/ffi/dynamic_data/libraries/battle_stat_calculator.rs index 7715c7f..a9836b8 100644 --- a/src/ffi/dynamic_data/libraries/battle_stat_calculator.rs +++ b/src/ffi/dynamic_data/libraries/battle_stat_calculator.rs @@ -1,5 +1,5 @@ use crate::dynamic_data::{BattleStatCalculator, Gen7BattleStatCalculator, Pokemon}; -use crate::ffi::{ExternPointer, IdentifiablePointer, OwnedPtr}; +use crate::ffi::{ExternPointer, IdentifiablePointer, NativeResult, OwnedPtr}; use crate::static_data::{Statistic, StatisticSet}; use std::ptr::drop_in_place; use std::sync::Arc; @@ -25,8 +25,10 @@ extern "C" fn battle_stat_calculator_calculate_flat_stats( ptr: ExternPointer>, pokemon: ExternPointer>, mut stats: ExternPointer>>, -) { - ptr.as_ref().calculate_flat_stats(pokemon.as_ref(), stats.as_mut()); +) -> NativeResult<()> { + ptr.as_ref() + .calculate_flat_stats(pokemon.as_ref(), stats.as_mut()) + .into() } /// Calculate a single flat stat of a Pokemon, disregarding stat boost @@ -35,8 +37,8 @@ extern "C" fn battle_stat_calculator_calculate_flat_stat( ptr: ExternPointer>, pokemon: ExternPointer>, stat: Statistic, -) -> u32 { - ptr.as_ref().calculate_flat_stat(pokemon.as_ref(), stat) +) -> NativeResult { + ptr.as_ref().calculate_flat_stat(pokemon.as_ref(), stat).into() } /// Calculate all the boosted stats of a Pokemon, including stat boosts. @@ -45,8 +47,10 @@ extern "C" fn battle_stat_calculator_calculate_boosted_stats( ptr: ExternPointer>, pokemon: ExternPointer>, mut stats: ExternPointer>>, -) { - ptr.as_ref().calculate_boosted_stats(pokemon.as_ref(), stats.as_mut()); +) -> NativeResult<()> { + ptr.as_ref() + .calculate_boosted_stats(pokemon.as_ref(), stats.as_mut()) + .into() } /// Calculate a single boosted stat of a Pokemon, including stat boosts. @@ -55,6 +59,6 @@ extern "C" fn battle_stat_calculator_calculate_boosted_stat( ptr: ExternPointer>, pokemon: ExternPointer>, stat: Statistic, -) -> u32 { - ptr.as_ref().calculate_boosted_stat(pokemon.as_ref(), stat) +) -> NativeResult { + ptr.as_ref().calculate_boosted_stat(pokemon.as_ref(), stat).into() } diff --git a/src/ffi/dynamic_data/models/learned_move.rs b/src/ffi/dynamic_data/models/learned_move.rs index 803a2fa..f1ee004 100644 --- a/src/ffi/dynamic_data/models/learned_move.rs +++ b/src/ffi/dynamic_data/models/learned_move.rs @@ -1,5 +1,5 @@ use crate::dynamic_data::{LearnedMove, MoveLearnMethod}; -use crate::ffi::{ExternPointer, IdentifiablePointer, OwnedPtr}; +use crate::ffi::{ExternPointer, IdentifiablePointer, NativeResult, OwnedPtr}; use crate::static_data::MoveData; use std::ptr::drop_in_place; use std::sync::Arc; @@ -60,6 +60,6 @@ extern "C" fn learned_move_restore_all_uses(learned_move: ExternPointer>, amount: u8) { - learned_move.as_ref().restore_uses(amount); +extern "C" fn learned_move_restore_uses(learned_move: ExternPointer>, amount: u8) -> NativeResult<()> { + learned_move.as_ref().restore_uses(amount).into() } diff --git a/src/ffi/dynamic_data/models/pokemon.rs b/src/ffi/dynamic_data/models/pokemon.rs index 2571456..2253fc4 100644 --- a/src/ffi/dynamic_data/models/pokemon.rs +++ b/src/ffi/dynamic_data/models/pokemon.rs @@ -127,8 +127,11 @@ extern "C" fn pokemon_remove_held_item(ptr: ExternPointer>) -> Iden /// Makes the Pokemon uses its held item. #[no_mangle] -extern "C" fn pokemon_consume_held_item(ptr: ExternPointer>) -> u8 { - u8::from(ptr.as_ref().consume_held_item()) +extern "C" fn pokemon_consume_held_item(ptr: ExternPointer>) -> NativeResult { + match ptr.as_ref().consume_held_item() { + Ok(v) => NativeResult::ok(u8::from(v)), + Err(err) => NativeResult::err(err), + } } ffi_arc_getter!(Pokemon, current_health, u32); @@ -200,8 +203,11 @@ extern "C" fn pokemon_change_stat_boost( stat: Statistic, diff_amount: i8, self_inflicted: u8, -) -> u8 { - u8::from(ptr.as_ref().change_stat_boost(stat, diff_amount, self_inflicted == 1)) +) -> NativeResult { + match ptr.as_ref().change_stat_boost(stat, diff_amount, self_inflicted == 1) { + Ok(v) => NativeResult::ok(u8::from(v)), + Err(e) => NativeResult::err(e), + } } /// Gets a [individual value](https://bulbapedia.bulbagarden.net/wiki/Individual_values) of the Pokemon. @@ -212,9 +218,13 @@ extern "C" fn pokemon_get_individual_value(ptr: ExternPointer>, sta /// Modifies a [individual value](https://bulbapedia.bulbagarden.net/wiki/Individual_values) of the Pokemon. #[no_mangle] -extern "C" fn pokemon_set_individual_value(ptr: ExternPointer>, stat: Statistic, value: u8) { +extern "C" fn pokemon_set_individual_value( + ptr: ExternPointer>, + stat: Statistic, + value: u8, +) -> NativeResult<()> { ptr.as_ref().individual_values().set_stat(stat, value); - ptr.as_ref().recalculate_flat_stats(); + ptr.as_ref().recalculate_flat_stats().into() } /// Gets a [effort value](https://bulbapedia.bulbagarden.net/wiki/Effort_values) of the Pokemon. @@ -225,9 +235,13 @@ extern "C" fn pokemon_get_effort_value(ptr: ExternPointer>, stat: S /// Modifies a [effort value](https://bulbapedia.bulbagarden.net/wiki/Effort_values) of the Pokemon. #[no_mangle] -extern "C" fn pokemon_set_effort_value(ptr: ExternPointer>, stat: Statistic, value: u8) { +extern "C" fn pokemon_set_effort_value( + ptr: ExternPointer>, + stat: Statistic, + value: u8, +) -> NativeResult<()> { ptr.as_ref().effort_values().set_stat(stat, value); - ptr.as_ref().recalculate_flat_stats(); + ptr.as_ref().recalculate_flat_stats().into() } /// Gets the data for the battle the Pokemon is currently in. If the Pokemon is not in a battle, this @@ -263,8 +277,13 @@ extern "C" fn pokemon_is_ability_overriden(ptr: ExternPointer>) -> /// Returns the currently active ability. #[no_mangle] -extern "C" fn pokemon_active_ability(ptr: ExternPointer>) -> IdentifiablePointer> { - ptr.as_ref().active_ability().into() +extern "C" fn pokemon_active_ability( + ptr: ExternPointer>, +) -> NativeResult>> { + match ptr.as_ref().active_ability() { + Ok(v) => NativeResult::ok(v.clone().into()), + Err(e) => NativeResult::err(e), + } } /// Whether or not the Pokemon is allowed to gain experience. @@ -283,14 +302,14 @@ extern "C" fn pokemon_nature(ptr: ExternPointer>) -> IdentifiablePo /// stats, level, nature, IV, or EV changes. This has a side effect of recalculating the boosted /// stats, as those depend on the flat stats. #[no_mangle] -extern "C" fn pokemon_recalculate_flat_stats(ptr: ExternPointer>) { - ptr.as_ref().recalculate_flat_stats() +extern "C" fn pokemon_recalculate_flat_stats(ptr: ExternPointer>) -> NativeResult<()> { + ptr.as_ref().recalculate_flat_stats().into() } /// Calculates the boosted stats on the Pokemon. This should be called when a stat boost changes. #[no_mangle] -extern "C" fn pokemon_recalculate_boosted_stats(ptr: ExternPointer>) { - ptr.as_ref().recalculate_boosted_stats() +extern "C" fn pokemon_recalculate_boosted_stats(ptr: ExternPointer>) -> NativeResult<()> { + ptr.as_ref().recalculate_boosted_stats().into() } /// Change the species of the Pokemon. @@ -299,15 +318,19 @@ extern "C" fn pokemon_change_species( ptr: ExternPointer>, species: ExternPointer>, form: ExternPointer>, -) { +) -> NativeResult<()> { ptr.as_ref() .change_species(species.as_ref().clone(), form.as_ref().clone()) + .into() } /// Change the form of the Pokemon. #[no_mangle] -extern "C" fn pokemon_change_form(ptr: ExternPointer>, form: ExternPointer>) { - ptr.as_ref().change_form(form.as_ref()) +extern "C" fn pokemon_change_form( + ptr: ExternPointer>, + form: ExternPointer>, +) -> NativeResult<()> { + ptr.as_ref().change_form(form.as_ref()).into() } /// Whether or not the Pokemon is useable in a battle. @@ -347,8 +370,12 @@ extern "C" fn pokemon_learn_move( ptr: ExternPointer>, move_name: *const c_char, learn_method: MoveLearnMethod, -) { - unsafe { ptr.as_ref().learn_move(&CStr::from_ptr(move_name).into(), learn_method) } +) -> NativeResult<()> { + unsafe { + ptr.as_ref() + .learn_move(&CStr::from_ptr(move_name).into(), learn_method) + .into() + } } /// Removes the current non-volatile status from the Pokemon. diff --git a/src/lib.rs b/src/lib.rs index d7ca5bf..d5cbee7 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,8 +6,14 @@ #![allow(ambiguous_glob_reexports)] #![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)] // #![deny(clippy::expect_used)] +// #![deny(clippy::indexing_slicing)] +// #![deny(clippy::string_slice)] +// #![deny(clippy::exit)] +// #![deny(clippy::panic)] // Features #![feature(test)] #![feature(const_option)] @@ -18,7 +24,6 @@ #![feature(unboxed_closures)] #![feature(trait_upcasting)] #![feature(lazy_cell)] -#![feature(is_some_and)] //! PkmnLib //! PkmnLib is a full featured implementation of Pokemon. while currently focused on implementing @@ -51,3 +56,41 @@ pub mod script_implementations; pub mod static_data; /// The utils module includes misc utils that are used within PkmnLib pub mod utils; + +use thiserror::Error; + +/// The PkmnError enum is the error type for PkmnLib. It is used to return common errors from functions +/// that can fail. For uncommon errors, anyhow is used. +#[derive(Error, Debug)] +pub enum PkmnError { + /// The given index is out of bounds for the given length of the array. + #[error("The given index {index} is out of bounds for the given len {len}")] + IndexOutOfBounds { + /// The index that was given + index: usize, + /// The length of the array + len: usize, + }, + /// We tried to get a lock on a mutex, but the lock was poisoned. This means that another thread + /// panicked while holding the lock. This is a fatal error. + #[error("Unable to acquire a lock")] + UnableToAcquireLock, + /// Unable to get ability + #[error("Unable to get ability {ability}")] + InvalidAbilityName { + /// The ability that was requested + ability: StringKey, + }, + /// Unable to get move + #[error("Unable to get move {move_name}")] + InvalidMoveName { + /// The move that was requested + move_name: StringKey, + }, + /// Unable to get nature + #[error("Unable to get nature {nature}")] + InvalidNatureName { + /// The nature that was requested + nature: StringKey, + }, +} diff --git a/src/script_implementations/wasm/export_registry/dynamic_data/battle_side.rs b/src/script_implementations/wasm/export_registry/dynamic_data/battle_side.rs index b68a269..dbcf386 100755 --- a/src/script_implementations/wasm/export_registry/dynamic_data/battle_side.rs +++ b/src/script_implementations/wasm/export_registry/dynamic_data/battle_side.rs @@ -40,7 +40,7 @@ register! { env: FunctionEnvMut, side: ExternRef, ) -> ExternRef { - ExternRef::func_new(&env, side.value_func(&env).unwrap().battle()) + ExternRef::func_new(&env, side.value_func(&env).unwrap().battle().unwrap()) } fn battleside_get_pokemon( diff --git a/src/script_implementations/wasm/export_registry/dynamic_data/learned_move.rs b/src/script_implementations/wasm/export_registry/dynamic_data/learned_move.rs index f6ec264..82aef96 100755 --- a/src/script_implementations/wasm/export_registry/dynamic_data/learned_move.rs +++ b/src/script_implementations/wasm/export_registry/dynamic_data/learned_move.rs @@ -36,7 +36,7 @@ register! { turn_choice: ExternRef, amount: u8, ) { - turn_choice.value_func(&env).unwrap().restore_uses(amount); + turn_choice.value_func(&env).unwrap().restore_uses(amount).unwrap(); } } diff --git a/src/script_implementations/wasm/export_registry/dynamic_data/pokemon.rs b/src/script_implementations/wasm/export_registry/dynamic_data/pokemon.rs index b840f52..762b002 100755 --- a/src/script_implementations/wasm/export_registry/dynamic_data/pokemon.rs +++ b/src/script_implementations/wasm/export_registry/dynamic_data/pokemon.rs @@ -135,7 +135,7 @@ register! { self_inflicted: u8 ) -> u8 { unsafe{ - u8::from(pokemon.value_func(&env).unwrap().change_stat_boost(transmute(stat), amount, self_inflicted == 1)) + u8::from(pokemon.value_func(&env).unwrap().change_stat_boost(transmute(stat), amount, self_inflicted == 1).unwrap()) } } @@ -277,7 +277,7 @@ register! { env: FunctionEnvMut, pokemon: ExternRef, ) -> u8 { - if pokemon.value_func(&env).unwrap().consume_held_item() { 1 } else { 0 } + if pokemon.value_func(&env).unwrap().consume_held_item().unwrap() { 1 } else { 0 } } fn pokemon_get_types_length( @@ -312,7 +312,7 @@ register! { pokemon.value_func(&env).unwrap().change_species( species.value_func_arc(&env).unwrap(), form.value_func_arc(&env).unwrap(), - ); + ).unwrap(); } fn pokemon_change_form( @@ -322,7 +322,7 @@ register! { ) { pokemon.value_func(&env).unwrap().change_form( &form.value_func_arc(&env).unwrap(), - ); + ).unwrap(); } fn pokemon_get_current_health( diff --git a/tests/common/test_step.rs b/tests/common/test_step.rs index 83a5cec..2bfc280 100755 --- a/tests/common/test_step.rs +++ b/tests/common/test_step.rs @@ -35,7 +35,7 @@ impl TestStep { match self { TestStep::SetPokemon { place, from_party } => { let p = battle.parties()[from_party[0] as usize].get_pokemon(from_party[1] as usize); - battle.sides_mut()[place[0] as usize].set_pokemon(place[1], p); + battle.sides_mut()[place[0] as usize].set_pokemon(place[1], p).unwrap(); } TestStep::SetMoveChoice { for_pokemon, diff --git a/tests/integration.rs b/tests/integration.rs index d7810e7..42d4565 100755 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -107,8 +107,8 @@ fn validate_assurance() { let battle = Battle::new(lib.clone(), vec![party1, party2], false, 2, 1, None); - battle.sides()[0].set_pokemon(0, Some(p1.clone())); - battle.sides()[1].set_pokemon(0, Some(p2.clone())); + battle.sides()[0].set_pokemon(0, Some(p1.clone())).unwrap(); + battle.sides()[1].set_pokemon(0, Some(p2.clone())).unwrap(); let script = lib .load_script(ScriptOwnerData::None, ScriptCategory::Move, &"assurance".into())