Support for new error handling.
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Deukhoofd 2023-04-15 14:34:42 +02:00
parent 3058739ea0
commit feffb5f030
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
36 changed files with 466 additions and 274 deletions

View File

@ -58,6 +58,8 @@ uuid = "1.2.2"
paste = { version = "1.0.8" } paste = { version = "1.0.8" }
arcstr = { version = "1.1.4", features = ["std"] } arcstr = { version = "1.1.4", features = ["std"] }
enum-display-derive = "0.1.1" enum-display-derive = "0.1.1"
anyhow = "1.0.69"
anyhow_ext = "0.2.1"
[dev-dependencies] [dev-dependencies]
csv = "1.1.6" csv = "1.1.6"

View File

@ -2,6 +2,8 @@ use crate::dynamic_data::choices::TurnChoice;
use crate::dynamic_data::script_handling::ScriptSource; use crate::dynamic_data::script_handling::ScriptSource;
use crate::dynamic_data::Pokemon; use crate::dynamic_data::Pokemon;
use crate::{script_hook, ValueIdentifiable, ValueIdentifier}; use crate::{script_hook, ValueIdentifiable, ValueIdentifier};
use anyhow::Result;
use anyhow_ext::anyhow;
use parking_lot::lock_api::MappedRwLockReadGuard; use parking_lot::lock_api::MappedRwLockReadGuard;
use parking_lot::{RawRwLock, RwLock, RwLockReadGuard}; use parking_lot::{RawRwLock, RwLock, RwLockReadGuard};
@ -48,12 +50,16 @@ impl ChoiceQueue {
} }
/// This reads what the next choice to execute will be, without modifying state. /// This reads what the next choice to execute will be, without modifying state.
pub fn peek(&self) -> Option<MappedRwLockReadGuard<'_, RawRwLock, TurnChoice>> { pub fn peek(&self) -> Result<Option<MappedRwLockReadGuard<'_, RawRwLock, TurnChoice>>> {
let read_lock = self.queue.read(); let read_lock = self.queue.read();
if self.current >= read_lock.len() { if self.current >= read_lock.len() {
None Ok(None)
} else { } else {
Some(RwLockReadGuard::map(read_lock, |a| a[self.current].as_ref().unwrap())) let v = RwLockReadGuard::try_map(read_lock, |a| a[self.current].as_ref());
match v {
Ok(v) => Ok(Some(v)),
Err(_) => Err(anyhow!("Could not map choice")),
}
} }
} }
@ -81,7 +87,7 @@ impl ChoiceQueue {
} }
/// This moves the choice of a specific Pokemon up to the next choice to be executed. /// This moves the choice of a specific Pokemon up to the next choice to be executed.
pub fn move_pokemon_choice_next(&self, pokemon: &Pokemon) -> bool { pub fn move_pokemon_choice_next(&self, pokemon: &Pokemon) -> Result<bool> {
let mut queue_lock = self.queue.write(); let mut queue_lock = self.queue.write();
let mut desired_index = None; let mut desired_index = None;
// Find the index for the choice we want to move up. // Find the index for the choice we want to move up.
@ -93,18 +99,17 @@ impl ChoiceQueue {
} }
} }
} }
// If we couldn't find a choice, we can't execute, return. let result = match desired_index {
if desired_index.is_none() { Some(desired_index) => {
return false;
}
let desired_index = desired_index.unwrap();
// If the choice we want to move up is already the next choice, just return. // If the choice we want to move up is already the next choice, just return.
if desired_index == self.current { if desired_index == self.current {
return true; return Ok(true);
} }
// Take the choice we want to move forward out of it's place. // Take the choice we want to move forward out of it's place.
let choice = queue_lock[desired_index].take().unwrap(); let choice = queue_lock[desired_index]
.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 // Iterate backwards from the spot before the choice we want to move up, push them all back
// by 1 spot. // by 1 spot.
for index in (self.current..desired_index).rev() { for index in (self.current..desired_index).rev() {
@ -114,6 +119,10 @@ impl ChoiceQueue {
let _ = queue_lock[self.current].insert(choice); let _ = queue_lock[self.current].insert(choice);
true true
} }
None => false,
};
Ok(result)
}
/// Internal helper function to be easily able to iterate over the yet to be executed choices. /// 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<TurnChoice>]> { pub(crate) fn get_queue(&self) -> MappedRwLockReadGuard<'_, RawRwLock, [Option<TurnChoice>]> {
@ -140,7 +149,7 @@ mod tests {
fn create_empty_queue() { fn create_empty_queue() {
let queue = ChoiceQueue::new(Vec::new()); let queue = ChoiceQueue::new(Vec::new());
assert!(!queue.has_next()); assert!(!queue.has_next());
assert!(queue.peek().is_none()); assert!(queue.peek().unwrap().is_none());
} }
#[test] #[test]
@ -168,6 +177,7 @@ mod tests {
0, 0,
&"test_nature".into(), &"test_nature".into(),
) )
.unwrap()
} }
#[test] #[test]
@ -176,8 +186,8 @@ mod tests {
let queue = ChoiceQueue::new(vec![Some(TurnChoice::Pass(PassChoice::new(user)))]); let queue = ChoiceQueue::new(vec![Some(TurnChoice::Pass(PassChoice::new(user)))]);
assert!(queue.has_next()); assert!(queue.has_next());
assert!(queue.peek().is_some()); assert!(queue.peek().unwrap().is_some());
assert_eq!(7, queue.peek().unwrap().speed()); assert_eq!(7, queue.peek().unwrap().unwrap().speed());
} }
#[test] #[test]
@ -188,7 +198,7 @@ mod tests {
assert!(queue.has_next()); assert!(queue.has_next());
assert_eq!(7, queue.dequeue().unwrap().speed()); assert_eq!(7, queue.dequeue().unwrap().speed());
assert!(!queue.has_next()); assert!(!queue.has_next());
assert!(queue.peek().is_none()); assert!(queue.peek().unwrap().is_none());
} }
#[test] #[test]
@ -201,8 +211,8 @@ mod tests {
Some(TurnChoice::Pass(PassChoice::new(user2))), Some(TurnChoice::Pass(PassChoice::new(user2))),
]); ]);
assert!(queue.has_next()); assert!(queue.has_next());
assert!(queue.peek().is_some()); assert!(queue.peek().unwrap().is_some());
assert_eq!(7, queue.peek().unwrap().speed()); assert_eq!(7, queue.peek().unwrap().unwrap().speed());
} }
#[test] #[test]
@ -247,12 +257,12 @@ mod tests {
user2.change_level_by(60); user2.change_level_by(60);
assert_eq!( assert_eq!(
user1.value_identifier(), user1.value_identifier(),
queue.peek().unwrap().user().value_identifier() queue.peek().unwrap().unwrap().user().value_identifier()
); );
queue.resort(); queue.resort();
assert_eq!( assert_eq!(
user2.value_identifier(), user2.value_identifier(),
queue.peek().unwrap().user().value_identifier() queue.peek().unwrap().unwrap().user().value_identifier()
); );
} }
@ -267,9 +277,9 @@ mod tests {
]); ]);
assert_eq!( assert_eq!(
user1.value_identifier(), user1.value_identifier(),
queue.peek().unwrap().user().value_identifier() queue.peek().unwrap().unwrap().user().value_identifier()
); );
assert!(queue.move_pokemon_choice_next(user2.as_ref())); assert!(queue.move_pokemon_choice_next(user2.as_ref()).unwrap());
assert_eq!( assert_eq!(
user2.value_identifier(), user2.value_identifier(),
@ -294,13 +304,13 @@ mod tests {
user2.value_identifier(), user2.value_identifier(),
queue.dequeue().unwrap().user().value_identifier() queue.dequeue().unwrap().user().value_identifier()
); );
assert!(!queue.move_pokemon_choice_next(user2.as_ref())); assert!(!queue.move_pokemon_choice_next(user2.as_ref()).unwrap());
assert_eq!( assert_eq!(
user1.value_identifier(), user1.value_identifier(),
queue.dequeue().unwrap().user().value_identifier() queue.dequeue().unwrap().user().value_identifier()
); );
assert!(queue.peek().is_none()) assert!(queue.peek().unwrap().is_none())
} }
#[test] #[test]
@ -314,12 +324,12 @@ mod tests {
]); ]);
assert_eq!( assert_eq!(
user1.value_identifier(), user1.value_identifier(),
queue.peek().unwrap().user().value_identifier() queue.peek().unwrap().unwrap().user().value_identifier()
); );
assert!(queue.move_pokemon_choice_next(user1.as_ref())); assert!(queue.move_pokemon_choice_next(user1.as_ref()).unwrap());
assert_eq!( assert_eq!(
user1.value_identifier(), user1.value_identifier(),
queue.peek().unwrap().user().value_identifier() queue.peek().unwrap().unwrap().user().value_identifier()
); );
} }
@ -346,9 +356,9 @@ mod tests {
]); ]);
assert_eq!( assert_eq!(
users[0].value_identifier(), users[0].value_identifier(),
queue.peek().unwrap().user().value_identifier() queue.peek().unwrap().unwrap().user().value_identifier()
); );
assert!(queue.move_pokemon_choice_next(users[4].as_ref())); assert!(queue.move_pokemon_choice_next(users[4].as_ref()).unwrap());
assert_eq!( assert_eq!(
users[4].value_identifier(), users[4].value_identifier(),

View File

@ -135,45 +135,48 @@ pub fn resolve_targets(side: u8, index: u8, target: MoveTarget, battle: &Battle)
/// Checks whether a given side and index are valid for a given move target and user. /// Checks whether a given side and index are valid for a given move target and user.
pub fn is_valid_target(side: u8, index: u8, target: MoveTarget, user: &Pokemon) -> bool { pub fn is_valid_target(side: u8, index: u8, target: MoveTarget, user: &Pokemon) -> bool {
let user_side = user.get_battle_side_index();
let user_index = user.get_battle_index();
// If the user is not on the field, nothing is a valid target // If the user is not on the field, nothing is a valid target
if user_side.is_none() || user_index.is_none() { let user_side = match user.get_battle_side_index() {
return false; Some(side) => side,
} None => return false,
};
let user_index = match user.get_battle_index() {
Some(index) => index,
None => return false,
};
match target { match target {
MoveTarget::Adjacent | MoveTarget::AllAdjacent => { MoveTarget::Adjacent | MoveTarget::AllAdjacent => {
let diff = abs(index as i32 - user_index.unwrap() as i32); let diff = abs(index as i32 - user_index as i32);
if diff == 0 { if diff == 0 {
return user_side.unwrap() != side; return user_side != side;
} }
diff <= 1 diff <= 1
} }
MoveTarget::AdjacentAlly => { MoveTarget::AdjacentAlly => {
if user_side.unwrap() != side { if user_side != side {
false false
} else { } else {
abs(index as i32 - user_index.unwrap() as i32) == 1 abs(index as i32 - user_index as i32) == 1
} }
} }
MoveTarget::AdjacentAllySelf => { MoveTarget::AdjacentAllySelf => {
if user_side.unwrap() != side { if user_side != side {
false false
} else { } else {
abs(index as i32 - user_index.unwrap() as i32) <= 1 abs(index as i32 - user_index as i32) <= 1
} }
} }
MoveTarget::AdjacentOpponent | MoveTarget::AllAdjacentOpponent => { MoveTarget::AdjacentOpponent | MoveTarget::AllAdjacentOpponent => {
if user_side.unwrap() == side { if user_side == side {
false false
} else { } else {
abs(index as i32 - user_index.unwrap() as i32) <= 1 abs(index as i32 - user_index as i32) <= 1
} }
} }
MoveTarget::All | MoveTarget::Any | MoveTarget::RandomOpponent => true, MoveTarget::All | MoveTarget::Any | MoveTarget::RandomOpponent => true,
MoveTarget::AllAlly => user_side.unwrap() == side, MoveTarget::AllAlly => user_side == side,
MoveTarget::AllOpponent => user_side.unwrap() != side, MoveTarget::AllOpponent => user_side != side,
MoveTarget::SelfUse => user_side.unwrap() == side && user_index.unwrap() == index, MoveTarget::SelfUse => user_side == side && user_index == index,
} }
} }

View File

@ -1,3 +1,5 @@
use anyhow::Result;
use anyhow_ext::anyhow;
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
@ -10,11 +12,21 @@ use crate::dynamic_data::DamageSource;
use crate::dynamic_data::ExecutingMove; use crate::dynamic_data::ExecutingMove;
use crate::dynamic_data::Pokemon; use crate::dynamic_data::Pokemon;
use crate::static_data::MoveCategory; use crate::static_data::MoveCategory;
use crate::{run_scripts, script_hook, PkmnResult}; use crate::{run_scripts, script_hook};
/// 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"))?
};
}
impl Battle { impl Battle {
/// Execute the entire turn after the choices are all set. /// Execute the entire turn after the choices are all set.
pub(crate) fn run_turn(&self) -> PkmnResult<()> { pub(crate) fn run_turn(&self) -> Result<()> {
let choice_queue = self.current_turn_queue(); let choice_queue = self.current_turn_queue();
// We are now at the very beginning of a turn. We have assigned speeds and priorities to all // We are now at the very beginning of a turn. We have assigned speeds and priorities to all
@ -24,15 +36,19 @@ impl Battle {
// is primarily intended to be used to reset variables on a script (for example scripts that need // 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 // 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.) // they can then know this later on.)
for choice in choice_queue.read().as_ref().unwrap().get_queue().iter().flatten() { for choice in read_choice_queue!(choice_queue).get_queue().iter().flatten() {
script_hook!(on_before_turn, choice, choice); script_hook!(on_before_turn, choice, choice);
} }
// Now we can properly begin executing choices. // Now we can properly begin executing choices.
// One by one dequeue the turns, and run them. If the battle has ended we do not want to // One by one dequeue the turns, and run them. If the battle has ended we do not want to
// continue running however. // continue running however.
while choice_queue.read().as_ref().unwrap().has_next() && !self.has_ended() { while read_choice_queue!(choice_queue).has_next() && !self.has_ended() {
let choice = choice_queue.write().as_mut().unwrap().dequeue(); let choice = choice_queue
.write()
.as_mut()
.ok_or(anyhow!("Failed to get a write lock on choice queue"))?
.dequeue();
if let Some(choice) = choice { if let Some(choice) = choice {
self.execute_choice(&choice)?; self.execute_choice(&choice)?;
} }
@ -64,7 +80,7 @@ impl Battle {
} }
/// Executes a single choice. /// Executes a single choice.
fn execute_choice(&self, choice: &TurnChoice) -> PkmnResult<()> { fn execute_choice(&self, choice: &TurnChoice) -> Result<()> {
// A pass turn choice means the user does not intend to do anything. As such, return. // A pass turn choice means the user does not intend to do anything. As such, return.
if let TurnChoice::Pass(..) = choice { if let TurnChoice::Pass(..) = choice {
return Ok(()); return Ok(());
@ -93,7 +109,7 @@ impl Battle {
} }
/// Executes a move choice. /// Executes a move choice.
fn execute_move_choice<'func>(&'func self, choice: &'func TurnChoice) -> PkmnResult<()> { 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 used_move = move_choice.used_move();
let move_data = { let move_data = {
@ -101,7 +117,11 @@ impl Battle {
let move_data = move_data_lock.move_data(); let move_data = move_data_lock.move_data();
let mut move_name = move_data.name().clone(); let mut move_name = move_data.name().clone();
script_hook!(change_move, choice, choice, &mut move_name); script_hook!(change_move, choice, choice, &mut move_name);
self.library().static_data().moves().get(&move_name).unwrap() self.library()
.static_data()
.moves()
.get(&move_name)
.ok_or(anyhow!("Move not found"))?
}; };
// FIXME: also change the script on the choice if changed; // FIXME: also change the script on the choice if changed;
let target_type = move_data.target(); let target_type = move_data.target();
@ -150,7 +170,7 @@ impl Battle {
} }
/// Executes a move turn choice on a single target. /// Executes a move turn choice on a single target.
fn handle_move_for_target(&self, executing_move: &mut ExecutingMove, target: &Arc<Pokemon>) -> PkmnResult<()> { fn handle_move_for_target(&self, executing_move: &mut ExecutingMove, target: &Arc<Pokemon>) -> Result<()> {
{ {
let mut fail = false; let mut fail = false;
script_hook!(fail_incoming_move, target, executing_move, target, &mut fail); script_hook!(fail_incoming_move, target, executing_move, target, &mut fail);
@ -228,7 +248,7 @@ impl Battle {
let is_critical = let is_critical =
self.library() self.library()
.damage_calculator() .damage_calculator()
.is_critical(self, executing_move, target, hit_index); .is_critical(self, executing_move, target, hit_index)?;
hit_data.set_critical(is_critical); hit_data.set_critical(is_critical);
} }
let base_power = self.library().damage_calculator().get_base_power( let base_power = self.library().damage_calculator().get_base_power(
@ -243,7 +263,7 @@ impl Battle {
target, target,
hit_index, hit_index,
executing_move.get_hit_data(target, hit_index)?, executing_move.get_hit_data(target, hit_index)?,
); )?;
hit_data.set_damage(damage); hit_data.set_damage(damage);
let mut accuracy = executing_move.use_move().accuracy(); let mut accuracy = executing_move.use_move().accuracy();
@ -259,7 +279,9 @@ impl Battle {
&mut accuracy &mut accuracy
); );
} }
if accuracy < 100 && self.random().get_max(100) as u8 >= accuracy {
// TODO: Deal with accuracy/evasion stats.
if accuracy < 100 && self.random().get_max(100)? as u8 >= accuracy {
script_hook!(on_move_miss, target, executing_move, target); script_hook!(on_move_miss, target, executing_move, target);
self.event_hook().trigger(Event::Miss { self.event_hook().trigger(Event::Miss {
user: executing_move.user().deref(), user: executing_move.user().deref(),
@ -273,7 +295,7 @@ impl Battle {
if secondary_effect_chance == -1.0 if secondary_effect_chance == -1.0
|| self || self
.random() .random()
.effect_chance(secondary_effect_chance, executing_move, target, hit_index) .effect_chance(secondary_effect_chance, executing_move, target, hit_index)?
{ {
script_hook!(on_secondary_effect, executing_move, executing_move, target, hit_index); script_hook!(on_secondary_effect, executing_move, executing_move, target, hit_index);
// TODO: on fail // TODO: on fail
@ -287,7 +309,7 @@ impl Battle {
hit_data.set_damage(damage); hit_data.set_damage(damage);
} }
if damage > 0 { if damage > 0 {
target.damage(damage, DamageSource::MoveDamage); target.damage(damage, DamageSource::MoveDamage)?;
if !target.is_fainted() { if !target.is_fainted() {
script_hook!(on_incoming_hit, target, executing_move, target, hit_index); script_hook!(on_incoming_hit, target, executing_move, target, hit_index);
} else { } else {
@ -313,7 +335,7 @@ impl Battle {
executing_move, executing_move,
target, target,
hit_index, hit_index,
) )?
{ {
script_hook!( script_hook!(
on_secondary_effect, on_secondary_effect,

View File

@ -1,3 +1,5 @@
use anyhow::Result;
use anyhow_ext::anyhow;
use std::sync::Arc; use std::sync::Arc;
use crate::dynamic_data::script_handling::ScriptSource; use crate::dynamic_data::script_handling::ScriptSource;
@ -16,7 +18,7 @@ pub trait DamageLibrary: std::fmt::Debug + ValueIdentifiable {
target: &Arc<Pokemon>, target: &Arc<Pokemon>,
hit_number: u8, hit_number: u8,
hit_data: &HitData, hit_data: &HitData,
) -> u32; ) -> Result<u32>;
/// Calculate the base power for a given hit on a Pokemon. /// Calculate the base power for a given hit on a Pokemon.
fn get_base_power( fn get_base_power(
@ -34,7 +36,7 @@ pub trait DamageLibrary: std::fmt::Debug + ValueIdentifiable {
executing_move: &ExecutingMove, executing_move: &ExecutingMove,
target: &Arc<Pokemon>, target: &Arc<Pokemon>,
hit_number: u8, hit_number: u8,
) -> bool; ) -> Result<bool>;
} }
/// The implementation of a Damage Library for generation 7. /// The implementation of a Damage Library for generation 7.
@ -174,9 +176,9 @@ impl DamageLibrary for Gen7DamageLibrary {
target: &Arc<Pokemon>, target: &Arc<Pokemon>,
hit_number: u8, hit_number: u8,
hit_data: &HitData, hit_data: &HitData,
) -> u32 { ) -> Result<u32> {
if executing_move.use_move().category() == MoveCategory::Status { if executing_move.use_move().category() == MoveCategory::Status {
return 0; return Ok(0);
} }
let level_modifier = ((2.0 * executing_move.user().level() as f32) / 5.0).floor() + 2.0; let level_modifier = ((2.0 * executing_move.user().level() as f32) / 5.0).floor() + 2.0;
@ -205,7 +207,13 @@ impl DamageLibrary for Gen7DamageLibrary {
} }
if self.has_randomness { if self.has_randomness {
let random_percentage = 85 + executing_move.user().get_battle().unwrap().random().get_between(0, 16); let random_percentage = 85
+ executing_move
.user()
.get_battle()
.ok_or(anyhow!("Damage calculation was done for a Pokemon not in battle"))?
.random()
.get_between(0, 16)?;
float_damage = (float_damage * (random_percentage as f32 / 100.0)).floor(); float_damage = (float_damage * (random_percentage as f32 / 100.0)).floor();
} }
@ -247,7 +255,7 @@ impl DamageLibrary for Gen7DamageLibrary {
hit_number, hit_number,
&mut damage &mut damage
); );
damage Ok(damage)
} }
fn get_base_power( fn get_base_power(
@ -279,10 +287,10 @@ impl DamageLibrary for Gen7DamageLibrary {
executing_move: &ExecutingMove, executing_move: &ExecutingMove,
target: &Arc<Pokemon>, target: &Arc<Pokemon>,
hit_number: u8, hit_number: u8,
) -> bool { ) -> Result<bool> {
// Status moves can't be critical. // Status moves can't be critical.
if executing_move.use_move().category() == MoveCategory::Status { if executing_move.use_move().category() == MoveCategory::Status {
return false; return Ok(false);
} }
// Get the critical stage from scripts. // Get the critical stage from scripts.
let mut crit_stage = 0; let mut crit_stage = 0;
@ -296,12 +304,12 @@ impl DamageLibrary for Gen7DamageLibrary {
); );
// Crit stage is an unsigned byte, so we only care about values of 0 or higher. // Crit stage is an unsigned byte, so we only care about values of 0 or higher.
// For a critical stage of 3+ we always return true. // For a critical stage of 3+ we always return true.
match crit_stage { Ok(match crit_stage {
0 => battle.random().get_max(24) == 0, 0 => battle.random().get_max(24)? == 0,
1 => battle.random().get_max(8) == 0, 1 => battle.random().get_max(8)? == 0,
2 => battle.random().get_max(2) == 0, 2 => battle.random().get_max(2)? == 0,
_ => true, _ => true,
} })
} }
} }

View File

@ -1,3 +1,4 @@
use anyhow::Result;
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::Arc; use std::sync::Arc;
@ -9,7 +10,7 @@ use crate::dynamic_data::{ItemScript, ScriptResolver};
use crate::dynamic_data::{Script, ScriptOwnerData}; use crate::dynamic_data::{Script, ScriptOwnerData};
use crate::static_data::Item; use crate::static_data::Item;
use crate::static_data::StaticData; use crate::static_data::StaticData;
use crate::{PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier}; use crate::{StringKey, ValueIdentifiable, ValueIdentifier};
/// The dynamic library stores a static data library, as well as holding different libraries and /// The dynamic library stores a static data library, as well as holding different libraries and
/// calculators that might be customized between different generations and implementations. /// calculators that might be customized between different generations and implementations.
@ -32,11 +33,11 @@ pub trait DynamicLibrary: Debug + ValueIdentifiable {
owner: ScriptOwnerData, owner: ScriptOwnerData,
_category: ScriptCategory, _category: ScriptCategory,
_key: &StringKey, _key: &StringKey,
) -> PkmnResult<Option<Arc<dyn Script>>>; ) -> Result<Option<Arc<dyn Script>>>;
/// Loads an item script with the given unique key. If no script can be created with this /// Loads an item script with the given unique key. If no script can be created with this
/// combinations, returns None. Note that ItemScripts are immutable, as their script should be /// combinations, returns None. Note that ItemScripts are immutable, as their script should be
/// shared between all different usages. /// shared between all different usages.
fn load_item_script(&self, _key: &Arc<dyn Item>) -> PkmnResult<Option<Arc<dyn ItemScript>>>; fn load_item_script(&self, _key: &Arc<dyn Item>) -> Result<Option<Arc<dyn ItemScript>>>;
} }
/// The dynamic library stores a static data library, as well as holding different libraries and /// The dynamic library stores a static data library, as well as holding different libraries and
@ -107,13 +108,13 @@ impl DynamicLibrary for DynamicLibraryImpl {
owner: ScriptOwnerData, owner: ScriptOwnerData,
_category: ScriptCategory, _category: ScriptCategory,
_key: &StringKey, _key: &StringKey,
) -> PkmnResult<Option<Arc<dyn Script>>> { ) -> Result<Option<Arc<dyn Script>>> {
self.script_resolver.load_script(owner, _category, _key) self.script_resolver.load_script(owner, _category, _key)
} }
/// Loads an item script with the given unique key. If no script can be created with this /// Loads an item script with the given unique key. If no script can be created with this
/// combinations, returns None. Note that ItemScripts are immutable, as their script should be /// combinations, returns None. Note that ItemScripts are immutable, as their script should be
/// shared between all different usages. /// shared between all different usages.
fn load_item_script(&self, _key: &Arc<dyn Item>) -> PkmnResult<Option<Arc<dyn ItemScript>>> { fn load_item_script(&self, _key: &Arc<dyn Item>) -> Result<Option<Arc<dyn ItemScript>>> {
todo!() todo!()
} }
} }
@ -145,8 +146,8 @@ pub mod test {
owner: ScriptOwnerData, owner: ScriptOwnerData,
_category: ScriptCategory, _category: ScriptCategory,
_key: &StringKey, _key: &StringKey,
) -> PkmnResult<Option<Arc<dyn Script>>>; ) -> Result<Option<Arc<dyn Script>>>;
fn load_item_script(&self, _key: &Arc<dyn Item>) -> PkmnResult<Option<Arc<dyn ItemScript>>>; fn load_item_script(&self, _key: &Arc<dyn Item>) -> Result<Option<Arc<dyn ItemScript>>>;
} }
impl ValueIdentifiable for DynamicLibrary{ impl ValueIdentifiable for DynamicLibrary{
fn value_identifier(&self) -> ValueIdentifier{ fn value_identifier(&self) -> ValueIdentifier{

View File

@ -1,9 +1,10 @@
use anyhow::Result;
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::Arc; use std::sync::Arc;
use crate::dynamic_data::{ItemScript, Script, ScriptOwnerData}; use crate::dynamic_data::{ItemScript, Script, ScriptOwnerData};
use crate::static_data::Item; use crate::static_data::Item;
use crate::{PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier}; use crate::{StringKey, ValueIdentifiable, ValueIdentifier};
/// A script resolver deals with the resolving of scripts. These scripts are non-hardcoded /// A script resolver deals with the resolving of scripts. These scripts are non-hardcoded
/// implementations of different effects in Pokemon. This allows for things such as generational /// implementations of different effects in Pokemon. This allows for things such as generational
@ -16,12 +17,12 @@ pub trait ScriptResolver: Debug + ValueIdentifiable {
owner: ScriptOwnerData, owner: ScriptOwnerData,
category: ScriptCategory, category: ScriptCategory,
script_key: &StringKey, script_key: &StringKey,
) -> PkmnResult<Option<Arc<dyn Script>>>; ) -> Result<Option<Arc<dyn Script>>>;
/// Loads an item script with the given unique key. If no script can be created with this /// Loads an item script with the given unique key. If no script can be created with this
/// combinations, returns None. Note that ItemScripts are immutable, as their script should be /// combinations, returns None. Note that ItemScripts are immutable, as their script should be
/// shared between all different usages. /// shared between all different usages.
fn load_item_script(&self, _key: &dyn Item) -> PkmnResult<Option<Arc<dyn ItemScript>>>; fn load_item_script(&self, _key: &dyn Item) -> Result<Option<Arc<dyn ItemScript>>>;
} }
use std::fmt::Display; use std::fmt::Display;
@ -73,11 +74,11 @@ impl ScriptResolver for EmptyScriptResolver {
_owner: ScriptOwnerData, _owner: ScriptOwnerData,
_category: ScriptCategory, _category: ScriptCategory,
_script_key: &StringKey, _script_key: &StringKey,
) -> PkmnResult<Option<Arc<dyn Script>>> { ) -> Result<Option<Arc<dyn Script>>> {
Ok(None) Ok(None)
} }
fn load_item_script(&self, _key: &dyn Item) -> PkmnResult<Option<Arc<dyn ItemScript>>> { fn load_item_script(&self, _key: &dyn Item) -> Result<Option<Arc<dyn ItemScript>>> {
Ok(None) Ok(None)
} }
} }

View File

@ -2,6 +2,8 @@ use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result;
use anyhow_ext::anyhow;
use atomig::Atomic; use atomig::Atomic;
use parking_lot::RwLock; use parking_lot::RwLock;
@ -18,7 +20,7 @@ use crate::dynamic_data::VolatileScriptsOwner;
use crate::dynamic_data::{is_valid_target, ScriptWrapper}; use crate::dynamic_data::{is_valid_target, ScriptWrapper};
use crate::dynamic_data::{ChoiceQueue, ScriptContainer}; use crate::dynamic_data::{ChoiceQueue, ScriptContainer};
use crate::dynamic_data::{ScriptCategory, ScriptSource, ScriptSourceData}; use crate::dynamic_data::{ScriptCategory, ScriptSource, ScriptSourceData};
use crate::{script_hook, PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier}; use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier};
/// A pokemon battle, with any amount of sides and pokemon per side. /// A pokemon battle, with any amount of sides and pokemon per side.
#[derive(Debug)] #[derive(Debug)]
@ -169,11 +171,9 @@ impl Battle {
/// Get a Pokemon on the battlefield, on a specific side and an index on that side. /// Get a Pokemon on the battlefield, on a specific side and an index on that side.
pub fn get_pokemon(&self, side: u8, index: u8) -> Option<Arc<Pokemon>> { pub fn get_pokemon(&self, side: u8, index: u8) -> Option<Arc<Pokemon>> {
let side = self.sides.get(side as usize); let side = self.sides.get(side as usize);
side?; let pokemon_read_lock = side?.pokemon();
let pokemon_read_lock = side.unwrap().pokemon();
let pokemon = pokemon_read_lock.get(index as usize); let pokemon = pokemon_read_lock.get(index as usize);
pokemon?; pokemon?.clone()
pokemon.unwrap().clone()
} }
/// Returns whether a slot on the battlefield can still be filled. If no party is responsible /// Returns whether a slot on the battlefield can still be filled. If no party is responsible
@ -190,10 +190,10 @@ impl Battle {
/// Validates whether the battle is still in a non-ended state. If the battle has ended, this /// Validates whether the battle is still in a non-ended state. If the battle has ended, this
/// properly sets who has won etc. /// properly sets who has won etc.
pub fn validate_battle_state(&self) { pub fn validate_battle_state(&self) -> Result<()> {
// If we've already ended, we dont need to run this. // If we've already ended, we dont need to run this.
if self.has_ended() { if self.has_ended() {
return; return Ok(());
} }
let mut surviving_side_exists = false; let mut surviving_side_exists = false;
let mut winning_side = None; let mut winning_side = None;
@ -205,13 +205,13 @@ impl Battle {
self.result.data_ptr().replace(BattleResult::Inconclusive); self.result.data_ptr().replace(BattleResult::Inconclusive);
} }
self.has_ended.store(true, Ordering::SeqCst); self.has_ended.store(true, Ordering::SeqCst);
return; return Ok(());
} }
// If the side is not defeated // If the side is not defeated
if !side.is_defeated() { if !side.is_defeated() {
// More than 1 surviving side. Battle is not ended // More than 1 surviving side. Battle is not ended
if surviving_side_exists { if surviving_side_exists {
return; return Ok(());
} }
surviving_side_exists = true; surviving_side_exists = true;
winning_side = Some(side_index as u8); winning_side = Some(side_index as u8);
@ -228,12 +228,13 @@ impl Battle {
else { else {
let _w = self.result.write(); let _w = self.result.write();
unsafe { unsafe {
self.result self.result.data_ptr().replace(BattleResult::Conclusive(
.data_ptr() winning_side.ok_or(anyhow!("Winning side was not set"))?,
.replace(BattleResult::Conclusive(winning_side.unwrap())); ));
} }
} }
self.has_ended.store(true, Ordering::SeqCst); self.has_ended.store(true, Ordering::SeqCst);
Ok(())
} }
/// Checks whether a choice is actually possible. /// Checks whether a choice is actually possible.
@ -260,7 +261,7 @@ impl Battle {
} }
/// Try and set the choice for the battle. If the choice is not valid, this returns false. /// Try and set the choice for the battle. If the choice is not valid, this returns false.
pub fn try_set_choice(&self, choice: TurnChoice) -> PkmnResult<bool> { pub fn try_set_choice(&self, choice: TurnChoice) -> Result<bool> {
if !self.can_use(&choice) { if !self.can_use(&choice) {
return Ok(false); return Ok(false);
} }
@ -268,17 +269,19 @@ impl Battle {
return Ok(false); return Ok(false);
} }
let side = choice.user().get_battle_side_index(); let side = choice.user().get_battle_side_index();
if side.is_none() { match side {
return Ok(false); Some(side) => {
} self.sides[side as usize].set_choice(choice);
self.sides[side.unwrap() as usize].set_choice(choice);
self.check_choices_set_and_run()?; self.check_choices_set_and_run()?;
Ok(true) Ok(true)
} }
None => Ok(false),
}
}
/// Checks to see whether all Pokemon on the field have set their choices. If so, we then run /// Checks to see whether all Pokemon on the field have set their choices. If so, we then run
/// the turn. /// the turn.
fn check_choices_set_and_run(&self) -> PkmnResult<()> { fn check_choices_set_and_run(&self) -> Result<()> {
for side in &self.sides { for side in &self.sides {
if !side.all_choices_set() { if !side.all_choices_set() {
return Ok(()); return Ok(());
@ -292,10 +295,9 @@ impl Battle {
for side in &self.sides { for side in &self.sides {
let mut side_choices = side.choices().write(); let mut side_choices = side.choices().write();
for choice_opt in side_choices.deref_mut() { for choice_opt in side_choices.deref_mut() {
if choice_opt.is_none() { let mut choice = choice_opt
panic!("Choice was none, but all choices were set? Logic error."); .as_mut()
} .ok_or(anyhow!("Choice was none, but all choices were set? Logic error."))?;
let mut choice = choice_opt.as_mut().unwrap();
let c = choice.deref(); let c = choice.deref();
if let TurnChoice::Move(data) = c { if let TurnChoice::Move(data) = c {
let mut change_priority = data.priority(); let mut change_priority = data.priority();
@ -312,7 +314,7 @@ impl Battle {
script_hook!(change_speed, c, c, &mut speed); script_hook!(change_speed, c, c, &mut speed);
*choice.speed_mut() = speed; *choice.speed_mut() = speed;
choice.set_random_value(self.random.get() as u32); choice.set_random_value(self.random.get()? as u32);
choices.push(choice_opt.take()); choices.push(choice_opt.take());
} }
// Drop the lock guard, as we need to write into it in reset_choices. // Drop the lock guard, as we need to write into it in reset_choices.
@ -331,32 +333,41 @@ impl Battle {
self.event_hook.trigger(Event::EndTurn); self.event_hook.trigger(Event::EndTurn);
let end_time = chrono::Utc::now(); let end_time = chrono::Utc::now();
let time = end_time - start_time; let time = end_time - start_time;
self.last_turn_time match time.num_nanoseconds() {
.store(time.num_nanoseconds().unwrap() as u64, Ordering::SeqCst); None => {}
Some(v) => {
self.last_turn_time.store(v as u64, Ordering::SeqCst);
}
}
Ok(()) Ok(())
} }
/// Sets the current weather for the battle. If None is passed, this clears the weather. /// Sets the current weather for the battle. If None is passed, this clears the weather.
pub fn set_weather(&self, weather: Option<StringKey>) { pub fn set_weather(&self, weather: Option<StringKey>) -> Result<()> {
if let Some(weather) = weather { if let Some(weather) = weather {
let script = self let script = self
.library() .library()
.load_script(self.into(), ScriptCategory::Weather, &weather) .load_script(self.into(), ScriptCategory::Weather, &weather)?
.unwrap() .ok_or(anyhow!("Couldn't find weather script by name {}", weather))?;
.unwrap_or_else(|| panic!("Couldn't find weather script by name {}", weather));
self.weather.set(script); self.weather.set(script);
} else { } else {
self.weather.clear(); self.weather.clear();
} }
Ok(())
} }
/// Gets the current weather of the battle. If no weather is present, this returns None. /// Gets the current weather of the battle. If no weather is present, this returns None.
pub fn weather_name(&self) -> Option<StringKey> { pub fn weather_name(&self) -> Result<Option<StringKey>> {
if let Some(script) = self.weather.get() { if let Some(script) = self.weather.get() {
let lock = script.read(); let lock = script.read();
Some(lock.as_ref().unwrap().name().clone()) Ok(Some(
lock.as_ref()
.ok_or(anyhow!("Failed to get a lock on weather"))?
.name()
.clone(),
))
} else { } else {
None Ok(None)
} }
} }
} }
@ -366,7 +377,7 @@ impl VolatileScriptsOwner for Battle {
&self.volatile_scripts &self.volatile_scripts
} }
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> { fn load_volatile_script(&self, key: &StringKey) -> Result<Option<Arc<dyn Script>>> {
self.library.load_script(self.into(), ScriptCategory::Battle, key) self.library.load_script(self.into(), ScriptCategory::Battle, key)
} }
} }

View File

@ -1,3 +1,5 @@
use anyhow::Result;
use anyhow_ext::anyhow;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -32,16 +34,25 @@ impl BattleRandom {
} }
/// Get a random 32 bit integer. Can be any value between min int and max int. /// Get a random 32 bit integer. Can be any value between min int and max int.
pub fn get(&self) -> i32 { pub fn get(&self) -> Result<i32> {
return self.get_rng().lock().unwrap().get(); match self.get_rng().lock() {
Ok(mut l) => Ok(l.get()),
Err(_) => Err(anyhow!("Failed to get a RNG lock")),
}
} }
/// Get a random 32 bit integer between 0 and max. /// Get a random 32 bit integer between 0 and max.
pub fn get_max(&self, max: i32) -> i32 { pub fn get_max(&self, max: i32) -> Result<i32> {
return self.get_rng().lock().unwrap().get_max(max); match self.get_rng().lock() {
Ok(mut l) => Ok(l.get_max(max)),
Err(_) => Err(anyhow!("Failed to get a RNG lock")),
}
} }
/// Get a random 32 bit integer between min and max. /// Get a random 32 bit integer between min and max.
pub fn get_between(&self, min: i32, max: i32) -> i32 { pub fn get_between(&self, min: i32, max: i32) -> Result<i32> {
return self.get_rng().lock().unwrap().get_between(min, max); match self.get_rng().lock() {
Ok(mut l) => Ok(l.get_between(min, max)),
Err(_) => Err(anyhow!("Failed to get a RNG lock")),
}
} }
/// Gets whether or not a move triggers its secondary effect. This takes its chance, and /// Gets whether or not a move triggers its secondary effect. This takes its chance, and
@ -53,7 +64,7 @@ impl BattleRandom {
executing_move: &ExecutingMove, executing_move: &ExecutingMove,
target: &Arc<Pokemon>, target: &Arc<Pokemon>,
hit_number: u8, hit_number: u8,
) -> bool { ) -> Result<bool> {
script_hook!( script_hook!(
change_effect_chance, change_effect_chance,
executing_move, executing_move,
@ -72,12 +83,15 @@ impl BattleRandom {
); );
if chance < 100.0 { if chance < 100.0 {
if chance > 0.0 { if chance > 0.0 {
self.get_rng().lock().unwrap().get_float() < (chance / 100.0) match self.get_rng().lock() {
} else { Ok(mut l) => Ok(l.get_float() < (chance / 100.0)),
false Err(_) => Err(anyhow!("Failed to get a RNG lock")),
} }
} else { } else {
true Ok(false)
}
} else {
Ok(true)
} }
} }
} }
@ -92,6 +106,10 @@ impl Clone for BattleRandom {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
identifier: Default::default(), identifier: Default::default(),
// As cloning when we can't get a lock on the randomness is completely impossible, we
// should unwrap here.
#[allow(clippy::unwrap_used)]
random: Mutex::new(self.random.lock().unwrap().clone()), random: Mutex::new(self.random.lock().unwrap().clone()),
} }
} }

View File

@ -2,6 +2,7 @@ use std::ops::Deref;
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result;
use parking_lot::lock_api::RwLockReadGuard; use parking_lot::lock_api::RwLockReadGuard;
use parking_lot::{RawRwLock, RwLock}; use parking_lot::{RawRwLock, RwLock};
@ -14,7 +15,7 @@ use crate::dynamic_data::script_handling::{ScriptSource, ScriptSourceData, Scrip
use crate::dynamic_data::Script; use crate::dynamic_data::Script;
use crate::dynamic_data::ScriptSet; use crate::dynamic_data::ScriptSet;
use crate::dynamic_data::VolatileScriptsOwner; use crate::dynamic_data::VolatileScriptsOwner;
use crate::{script_hook, PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier}; use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier};
/// A side on a battle. /// A side on a battle.
#[derive(Debug)] #[derive(Debug)]
@ -248,9 +249,9 @@ impl BattleSide {
} }
/// Gets a random Pokemon on the given side. /// Gets a random Pokemon on the given side.
pub fn get_random_creature_index(&self) -> u8 { pub fn get_random_creature_index(&self) -> Result<u8> {
// TODO: Consider adding parameter to only get index for available creatures. // TODO: Consider adding parameter to only get index for available creatures.
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. /// Swap two Pokemon on a single side around.
@ -299,7 +300,7 @@ impl VolatileScriptsOwner for BattleSide {
&self.volatile_scripts &self.volatile_scripts
} }
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> { fn load_volatile_script(&self, key: &StringKey) -> Result<Option<Arc<dyn Script>>> {
self.battle() self.battle()
.library() .library()
.load_script(self.into(), crate::ScriptCategory::Side, key) .load_script(self.into(), crate::ScriptCategory::Side, key)

View File

@ -2,6 +2,8 @@ use std::ops::Deref;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU8, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU8, Ordering};
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result;
use anyhow_ext::bail;
use atomig::Atomic; use atomig::Atomic;
use parking_lot::RwLock; use parking_lot::RwLock;
@ -11,7 +13,7 @@ use crate::dynamic_data::script_handling::{ScriptSource, ScriptSourceData, Scrip
use crate::dynamic_data::ScriptContainer; use crate::dynamic_data::ScriptContainer;
use crate::dynamic_data::TargetList; use crate::dynamic_data::TargetList;
use crate::static_data::{MoveData, TypeIdentifier}; use crate::static_data::{MoveData, TypeIdentifier};
use crate::{PkmnResult, PokemonError, ValueIdentifiable, ValueIdentifier}; use crate::{ValueIdentifiable, ValueIdentifier};
/// A hit data is the data for a single hit, on a single target. /// A hit data is the data for a single hit, on a single target.
#[derive(Default, Debug)] #[derive(Default, Debug)]
@ -164,7 +166,7 @@ impl ExecutingMove {
} }
/// Gets a hit data for a target, with a specific index. /// Gets a hit data for a target, with a specific index.
pub fn get_hit_data(&self, for_target: &Pokemon, hit: u8) -> PkmnResult<&HitData> { pub fn get_hit_data(&self, for_target: &Pokemon, hit: u8) -> Result<&HitData> {
for (index, target) in self.targets.iter().enumerate() { for (index, target) in self.targets.iter().enumerate() {
if let Some(target) = target { if let Some(target) = target {
if std::ptr::eq(target.deref().deref(), for_target.deref().deref()) { if std::ptr::eq(target.deref().deref(), for_target.deref().deref()) {
@ -173,7 +175,7 @@ impl ExecutingMove {
} }
} }
} }
Err(PokemonError::InvalidTargetRequested) bail!("Invalid target requested");
} }
/// Checks whether a Pokemon is a target for this move. /// Checks whether a Pokemon is a target for this move.
@ -187,7 +189,7 @@ impl ExecutingMove {
} }
/// Gets the index of the hits in this move where the hits for a specific target start. /// Gets the index of the hits in this move where the hits for a specific target start.
pub(crate) fn get_index_of_target(&self, for_target: &Arc<Pokemon>) -> PkmnResult<usize> { pub(crate) fn get_index_of_target(&self, for_target: &Arc<Pokemon>) -> Result<usize> {
for (index, target) in self.targets.iter().enumerate() { for (index, target) in self.targets.iter().enumerate() {
if let Some(target) = target { if let Some(target) = target {
if std::ptr::eq(target.deref().deref(), for_target.deref().deref()) { if std::ptr::eq(target.deref().deref(), for_target.deref().deref()) {
@ -196,7 +198,7 @@ impl ExecutingMove {
} }
} }
} }
Err(PokemonError::InvalidTargetRequested) bail!("Invalid target requested");
} }
/// Gets a hit based on its raw index. /// Gets a hit based on its raw index.

View File

@ -22,7 +22,8 @@ use crate::static_data::TypeIdentifier;
use crate::static_data::{Ability, Statistic}; use crate::static_data::{Ability, Statistic};
use crate::static_data::{ClampedStatisticSet, StatisticSet}; use crate::static_data::{ClampedStatisticSet, StatisticSet};
use crate::utils::Random; use crate::utils::Random;
use crate::{script_hook, PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier}; use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier};
use anyhow::Result;
/// An individual Pokemon as we know and love them. /// An individual Pokemon as we know and love them.
#[derive(Debug)] #[derive(Debug)]
@ -129,12 +130,12 @@ impl Pokemon {
gender: Gender, gender: Gender,
coloring: u8, coloring: u8,
nature: &StringKey, nature: &StringKey,
) -> Self { ) -> Result<Self> {
// Calculate experience from the level for the specified growth rate. // Calculate experience from the level for the specified growth rate.
let experience = library let experience = library
.static_data() .static_data()
.growth_rates() .growth_rates()
.calculate_experience(species.growth_rate(), level); .calculate_experience(species.growth_rate(), level)?;
let weight = form.weight(); let weight = form.weight();
let height = form.height(); let height = form.height();
let nature = library let nature = library
@ -183,7 +184,7 @@ impl Pokemon {
let health = pokemon.flat_stats().hp(); let health = pokemon.flat_stats().hp();
pokemon.current_health = AtomicU32::new(health); pokemon.current_health = AtomicU32::new(health);
pokemon Ok(pokemon)
} }
/// The library data of the Pokemon. /// The library data of the Pokemon.
@ -617,12 +618,12 @@ impl Pokemon {
} }
/// Damages the Pokemon by a certain amount of damage, from a damage source. /// Damages the Pokemon by a certain amount of damage, from a damage source.
pub fn damage(&self, mut damage: u32, source: DamageSource) { pub fn damage(&self, mut damage: u32, source: DamageSource) -> Result<()> {
if damage > self.current_health() { if damage > self.current_health() {
damage = self.current_health(); damage = self.current_health();
} }
if damage == 0 { if damage == 0 {
return; return Ok(());
} }
let new_health = self.current_health() - damage; let new_health = self.current_health() - damage;
if let Some(battle_data) = &self.battle_data.read().deref() { if let Some(battle_data) = &self.battle_data.read().deref() {
@ -641,12 +642,13 @@ impl Pokemon {
self.current_health.store(new_health, Ordering::SeqCst); self.current_health.store(new_health, Ordering::SeqCst);
if self.is_fainted() && damage > 0 { if self.is_fainted() && damage > 0 {
self.on_faint(source); self.on_faint(source)?;
} }
Ok(())
} }
/// Triggers when the Pokemon faints. /// Triggers when the Pokemon faints.
fn on_faint(&self, source: DamageSource) { fn on_faint(&self, source: DamageSource) -> Result<()> {
let r = self.battle_data.read(); let r = self.battle_data.read();
if let Some(battle_data) = r.deref() { if let Some(battle_data) = r.deref() {
if let Some(battle) = battle_data.battle() { if let Some(battle) = battle_data.battle() {
@ -659,9 +661,10 @@ impl Pokemon {
.mark_slot_as_unfillable(battle_data.index()); .mark_slot_as_unfillable(battle_data.index());
} }
battle.validate_battle_state(); battle.validate_battle_state()?;
} }
} }
Ok(())
} }
/// Heals the Pokemon by a specific amount. Unless allow_revive is set to true, this will not /// Heals the Pokemon by a specific amount. Unless allow_revive is set to true, this will not
@ -803,7 +806,7 @@ impl VolatileScriptsOwner for Pokemon {
&self.volatile &self.volatile
} }
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> { fn load_volatile_script(&self, key: &StringKey) -> Result<Option<Arc<dyn Script>>> {
self.library.load_script(self.into(), ScriptCategory::Pokemon, key) self.library.load_script(self.into(), ScriptCategory::Pokemon, key)
} }
} }
@ -866,7 +869,9 @@ pub mod test {
static_lib.expect_species().return_const(Box::new(species_lib)); static_lib.expect_species().return_const(Box::new(species_lib));
let mut growth_rate_lib = MockGrowthRateLibrary::new(); let mut growth_rate_lib = MockGrowthRateLibrary::new();
growth_rate_lib.expect_calculate_experience().return_const(1000u32); growth_rate_lib
.expect_calculate_experience()
.returning(|_, _| Ok(1000u32));
let mut nature_lib = MockNatureLibrary::new(); let mut nature_lib = MockNatureLibrary::new();
nature_lib.expect_get_nature().returning(|_| { nature_lib.expect_get_nature().returning(|_| {
@ -907,7 +912,8 @@ pub mod test {
Gender::Male, Gender::Male,
0, 0,
&"test_nature".into(), &"test_nature".into(),
); )
.unwrap();
assert_eq!(pokemon.species().name(), &"test_species".into()); assert_eq!(pokemon.species().name(), &"test_species".into());
assert_eq!(pokemon.form().name(), &"default".into()); assert_eq!(pokemon.form().name(), &"default".into());
} }

View File

@ -6,6 +6,7 @@ use crate::dynamic_data::models::pokemon::Pokemon;
use crate::dynamic_data::DynamicLibrary; use crate::dynamic_data::DynamicLibrary;
use crate::static_data::{AbilityIndex, Gender}; use crate::static_data::{AbilityIndex, Gender};
use crate::{Random, StringKey}; use crate::{Random, StringKey};
use anyhow::Result;
/// This allows for the easy chain building of a Pokemon. /// This allows for the easy chain building of a Pokemon.
pub struct PokemonBuilder { pub struct PokemonBuilder {
@ -39,7 +40,7 @@ impl PokemonBuilder {
} }
/// Finally turn the builder into an actual Pokemon. /// Finally turn the builder into an actual Pokemon.
pub fn build(self) -> Pokemon { pub fn build(self) -> Result<Pokemon> {
let mut random = if let Some(seed) = self.random_seed { let mut random = if let Some(seed) = self.random_seed {
Random::new(seed) Random::new(seed)
} else { } else {
@ -61,11 +62,11 @@ impl PokemonBuilder {
Gender::Male, Gender::Male,
0, 0,
&"hardy".into(), &"hardy".into(),
); )?;
for learned_move in self.learned_moves { for learned_move in self.learned_moves {
p.learn_move(&learned_move, MoveLearnMethod::Unknown); p.learn_move(&learned_move, MoveLearnMethod::Unknown);
} }
p Ok(p)
} }
} }

View File

@ -1,11 +1,12 @@
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result;
use indexmap::IndexMap; use indexmap::IndexMap;
use parking_lot::RwLock; use parking_lot::RwLock;
use crate::dynamic_data::script_handling::script::{Script, ScriptContainer}; use crate::dynamic_data::script_handling::script::{Script, ScriptContainer};
use crate::{PkmnResult, StringKey}; use crate::StringKey;
/// A collection of unique scripts. /// A collection of unique scripts.
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -36,9 +37,9 @@ impl ScriptSet {
/// Adds a script with a name to the set. If the script with that name already exists in this /// Adds a script with a name to the set. If the script with that name already exists in this
/// set, this makes that script stack instead. The return value here is that script. /// set, this makes that script stack instead. The return value here is that script.
pub fn stack_or_add<F>(&self, key: &StringKey, instantiation: &F) -> PkmnResult<Option<ScriptContainer>> pub fn stack_or_add<F>(&self, key: &StringKey, instantiation: &F) -> Result<Option<ScriptContainer>>
where where
F: Fn() -> PkmnResult<Option<Arc<dyn Script>>>, F: Fn() -> Result<Option<Arc<dyn Script>>>,
{ {
if let Some(lock) = self.scripts.read().get(key) { if let Some(lock) = self.scripts.read().get(key) {
if let Some(existing) = lock.get() { if let Some(existing) = lock.get() {

View File

@ -1,15 +1,16 @@
use anyhow::Result;
use std::sync::Arc; use std::sync::Arc;
use crate::dynamic_data::script_handling::script::{Script, ScriptContainer}; use crate::dynamic_data::script_handling::script::{Script, ScriptContainer};
use crate::dynamic_data::script_handling::script_set::ScriptSet; use crate::dynamic_data::script_handling::script_set::ScriptSet;
use crate::{PkmnResult, StringKey}; use crate::StringKey;
/// This trait adds a bunch of helper functions to deal with volatile scripts on a struct. /// This trait adds a bunch of helper functions to deal with volatile scripts on a struct.
pub trait VolatileScriptsOwner { pub trait VolatileScriptsOwner {
/// Return the [`ScriptSet`] that are our volatile scripts. /// Return the [`ScriptSet`] that are our volatile scripts.
fn volatile_scripts(&self) -> &Arc<ScriptSet>; fn volatile_scripts(&self) -> &Arc<ScriptSet>;
/// Loads a volatile script by name. /// Loads a volatile script by name.
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>>; fn load_volatile_script(&self, key: &StringKey) -> Result<Option<Arc<dyn Script>>>;
/// Check if a volatile script with given name exists. /// Check if a volatile script with given name exists.
fn has_volatile_script(&self, key: &StringKey) -> bool { fn has_volatile_script(&self, key: &StringKey) -> bool {
@ -22,13 +23,13 @@ pub trait VolatileScriptsOwner {
} }
/// Adds a volatile script by name. /// Adds a volatile script by name.
fn add_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<ScriptContainer>> { fn add_volatile_script(&self, key: &StringKey) -> Result<Option<ScriptContainer>> {
self.volatile_scripts() self.volatile_scripts()
.stack_or_add(key, &|| self.load_volatile_script(key)) .stack_or_add(key, &|| self.load_volatile_script(key))
} }
/// Adds a volatile script by name. /// Adds a volatile script by name.
fn add_volatile_script_with_script(&self, script: Arc<dyn Script>) -> PkmnResult<Option<ScriptContainer>> { fn add_volatile_script_with_script(&self, script: Arc<dyn Script>) -> Result<Option<ScriptContainer>> {
self.volatile_scripts() self.volatile_scripts()
.stack_or_add(&script.name().clone(), &|| Ok(Some(script.clone()))) .stack_or_add(&script.name().clone(), &|| Ok(Some(script.clone())))
} }

View File

@ -2,7 +2,7 @@ use crate::dynamic_data::{
Battle, BattleParty, BattleRandom, BattleResult, BattleSide, DynamicLibrary, Pokemon, TurnChoice, Battle, BattleParty, BattleRandom, BattleResult, BattleSide, DynamicLibrary, Pokemon, TurnChoice,
}; };
use crate::ffi::dynamic_data::models::native_event_hook::NativeEventHook; use crate::ffi::dynamic_data::models::native_event_hook::NativeEventHook;
use crate::ffi::{ExternPointer, IdentifiablePointer, OwnedPtr}; use crate::ffi::{ExternPointer, IdentifiablePointer, NativeResult, OwnedPtr};
use std::ffi::{c_char, CStr, CString}; use std::ffi::{c_char, CStr, CString};
use std::sync::Arc; use std::sync::Arc;
@ -191,20 +191,20 @@ extern "C" fn battle_try_set_choice(ptr: ExternPointer<Arc<Battle>>, choice: Own
/// Sets the current weather for the battle. If nullptr is passed, this clears the weather. /// Sets the current weather for the battle. If nullptr is passed, this clears the weather.
#[no_mangle] #[no_mangle]
extern "C" fn battle_set_weather(ptr: ExternPointer<Arc<Battle>>, weather: *const c_char) { extern "C" fn battle_set_weather(ptr: ExternPointer<Arc<Battle>>, weather: *const c_char) -> NativeResult<()> {
if weather.is_null() { if weather.is_null() {
ptr.as_ref().set_weather(None) ptr.as_ref().set_weather(None).into()
} else { } else {
unsafe { ptr.as_ref().set_weather(Some(CStr::from_ptr(weather).into())) } unsafe { ptr.as_ref().set_weather(Some(CStr::from_ptr(weather).into())).into() }
} }
} }
/// Gets the current weather of the battle. If no weather is present, this returns nullptr. /// Gets the current weather of the battle. If no weather is present, this returns nullptr.
#[no_mangle] #[no_mangle]
extern "C" fn battle_weather_name(ptr: ExternPointer<Arc<Battle>>) -> *mut c_char { extern "C" fn battle_weather_name(ptr: ExternPointer<Arc<Battle>>) -> NativeResult<*mut c_char> {
if let Some(w) = ptr.as_ref().weather_name() { match ptr.as_ref().weather_name() {
CString::new(w.str()).unwrap().into_raw() Ok(Some(w)) => CString::new(w.str()).unwrap().into_raw().into(),
} else { Ok(None) => std::ptr::null_mut::<c_char>().into(),
std::ptr::null_mut() Err(e) => NativeResult::err(e),
} }
} }

View File

@ -1,6 +1,6 @@
use crate::defines::LevelInt; use crate::defines::LevelInt;
use crate::dynamic_data::{Battle, DamageSource, DynamicLibrary, LearnedMove, MoveLearnMethod, Pokemon}; use crate::dynamic_data::{Battle, DamageSource, DynamicLibrary, LearnedMove, MoveLearnMethod, Pokemon};
use crate::ffi::{ffi_arc_getter, ffi_vec_value_getters, ExternPointer, IdentifiablePointer, OwnedPtr}; use crate::ffi::{ffi_arc_getter, ffi_vec_value_getters, ExternPointer, IdentifiablePointer, NativeResult, OwnedPtr};
use crate::static_data::{ use crate::static_data::{
Ability, AbilityIndex, Form, Gender, Item, Nature, Species, Statistic, StatisticSet, TypeIdentifier, Ability, AbilityIndex, Form, Gender, Item, Nature, Species, Statistic, StatisticSet, TypeIdentifier,
}; };
@ -21,9 +21,9 @@ extern "C" fn pokemon_new(
gender: Gender, gender: Gender,
coloring: u8, coloring: u8,
nature: *const c_char, nature: *const c_char,
) -> IdentifiablePointer<Arc<Pokemon>> { ) -> NativeResult<IdentifiablePointer<Arc<Pokemon>>> {
let nature = unsafe { CStr::from_ptr(nature) }.into(); let nature = unsafe { CStr::from_ptr(nature) }.into();
Arc::new(Pokemon::new( let pokemon = Pokemon::new(
library.as_ref().clone(), library.as_ref().clone(),
species.as_ref().clone(), species.as_ref().clone(),
form.as_ref(), form.as_ref(),
@ -36,8 +36,11 @@ extern "C" fn pokemon_new(
gender, gender,
coloring, coloring,
&nature, &nature,
)) );
.into() match pokemon {
Ok(pokemon) => NativeResult::ok(Arc::new(pokemon).into()),
Err(err) => NativeResult::err(err),
}
} }
/// Drops an Arc reference held by the FFI. /// Drops an Arc reference held by the FFI.
@ -327,8 +330,8 @@ extern "C" fn pokemon_is_on_battlefield(ptr: ExternPointer<Arc<Pokemon>>) -> u8
/// Damages the Pokemon by a certain amount of damage, from a damage source. /// Damages the Pokemon by a certain amount of damage, from a damage source.
#[no_mangle] #[no_mangle]
extern "C" fn pokemon_damage(ptr: ExternPointer<Arc<Pokemon>>, damage: u32, source: DamageSource) { extern "C" fn pokemon_damage(ptr: ExternPointer<Arc<Pokemon>>, damage: u32, source: DamageSource) -> NativeResult<()> {
ptr.as_ref().damage(damage, source) ptr.as_ref().damage(damage, source).into()
} }
/// Heals the Pokemon by a specific amount. Unless allow_revive is set to true, this will not /// Heals the Pokemon by a specific amount. Unless allow_revive is set to true, this will not

View File

@ -93,6 +93,7 @@ pub(self) use ffi_arc_getter;
pub(self) use ffi_arc_stringkey_getter; pub(self) use ffi_arc_stringkey_getter;
pub(self) use ffi_vec_stringkey_getters; pub(self) use ffi_vec_stringkey_getters;
pub(self) use ffi_vec_value_getters; pub(self) use ffi_vec_value_getters;
use std::ffi::{c_char, CString};
use std::mem::transmute; use std::mem::transmute;
use std::sync::Arc; use std::sync::Arc;
@ -131,6 +132,17 @@ pub(self) struct IdentifiablePointer<T> {
pub id: usize, pub id: usize,
} }
impl<T> Clone for IdentifiablePointer<T> {
fn clone(&self) -> Self {
Self {
id: self.id,
ptr: self.ptr,
}
}
}
impl<T> Copy for IdentifiablePointer<T> {}
impl<T> IdentifiablePointer<T> { impl<T> IdentifiablePointer<T> {
/// Creates a new IdentifiablePointer. /// Creates a new IdentifiablePointer.
pub(self) fn new(ptr: *const T, id: ValueIdentifier) -> Self { pub(self) fn new(ptr: *const T, id: ValueIdentifier) -> Self {
@ -244,3 +256,55 @@ impl<T> IdentifiablePointer<T> {
} }
} }
} }
/// Helper utility class to give either the data or an error to a FFI.
#[repr(C)]
union ResultUnion<T: Copy> {
/// If the result is ok, this contains the value.
ok: T,
/// If the result is an error, this contains the error message.
err: OwnedPtr<c_char>,
}
/// The result of a FFI call that can either be an error or a value.
#[repr(C)]
pub struct NativeResult<T: Copy> {
/// If the result is ok, this is 1, otherwise 0.
ok: u8,
/// The value or error.
value: ResultUnion<T>,
}
impl<T: Copy> NativeResult<T> {
/// Creates a new NativeResult with the given value.
pub fn ok(value: T) -> Self {
Self {
ok: 1,
value: ResultUnion { ok: value },
}
}
/// Creates a new NativeResult with the given error.
pub fn err(err: anyhow::Error) -> Self {
Self {
ok: 0,
value: ResultUnion {
err: CString::new(err.to_string()).unwrap().into_raw(),
},
}
}
}
impl<T: Copy> From<anyhow::Result<T>> for NativeResult<T> {
fn from(value: anyhow::Result<T>) -> Self {
match value {
Ok(v) => Self::ok(v),
Err(e) => Self::err(e),
}
}
}
impl<T: Copy> From<T> for NativeResult<T> {
fn from(value: T) -> Self {
Self::ok(value)
}
}

View File

@ -1,5 +1,5 @@
use crate::defines::LevelInt; use crate::defines::LevelInt;
use crate::ffi::{ExternPointer, OwnedPtr}; use crate::ffi::{ExternPointer, NativeResult, OwnedPtr};
use crate::static_data::{GrowthRate, LookupGrowthRate}; use crate::static_data::{GrowthRate, LookupGrowthRate};
use std::ptr::drop_in_place; use std::ptr::drop_in_place;
@ -25,6 +25,9 @@ extern "C" fn growth_rate_calculate_level(ptr: ExternPointer<Box<dyn GrowthRate>
/// Calculate the experience something with this growth rate would have at a certain level. /// Calculate the experience something with this growth rate would have at a certain level.
#[no_mangle] #[no_mangle]
extern "C" fn growth_rate_calculate_experience(ptr: ExternPointer<Box<dyn GrowthRate>>, level: LevelInt) -> u32 { extern "C" fn growth_rate_calculate_experience(
ptr.as_ref().calculate_experience(level) ptr: ExternPointer<Box<dyn GrowthRate>>,
level: LevelInt,
) -> NativeResult<u32> {
ptr.as_ref().calculate_experience(level).into()
} }

View File

@ -1,5 +1,5 @@
use crate::defines::LevelInt; use crate::defines::LevelInt;
use crate::ffi::{BorrowedPtr, ExternPointer, IdentifiablePointer, OwnedPtr}; use crate::ffi::{BorrowedPtr, ExternPointer, IdentifiablePointer, NativeResult, OwnedPtr};
use crate::static_data::{GrowthRate, GrowthRateLibrary, GrowthRateLibraryImpl}; use crate::static_data::{GrowthRate, GrowthRateLibrary, GrowthRateLibraryImpl};
use std::ffi::{c_char, CStr}; use std::ffi::{c_char, CStr};
use std::ptr::drop_in_place; use std::ptr::drop_in_place;
@ -34,9 +34,10 @@ unsafe extern "C" fn growth_rate_library_calculate_experience(
ptr: ExternPointer<Box<dyn GrowthRateLibrary>>, ptr: ExternPointer<Box<dyn GrowthRateLibrary>>,
growth_rate: BorrowedPtr<c_char>, growth_rate: BorrowedPtr<c_char>,
level: LevelInt, level: LevelInt,
) -> u32 { ) -> NativeResult<u32> {
ptr.as_ref() ptr.as_ref()
.calculate_experience(&CStr::from_ptr(growth_rate).into(), level) .calculate_experience(&CStr::from_ptr(growth_rate).into(), level)
.into()
} }
/// Adds a new growth rate with a name and value. /// Adds a new growth rate with a name and value.

View File

@ -1,5 +1,5 @@
use crate::defines::LevelInt; use crate::defines::LevelInt;
use crate::ffi::{ExternPointer, IdentifiablePointer, OwnedPtr}; use crate::ffi::{ExternPointer, IdentifiablePointer, NativeResult, OwnedPtr};
use crate::static_data::{LibrarySettings, LibrarySettingsImpl}; use crate::static_data::{LibrarySettings, LibrarySettingsImpl};
use std::ptr::drop_in_place; use std::ptr::drop_in_place;
@ -12,9 +12,15 @@ use std::ptr::drop_in_place;
extern "C" fn library_settings_new( extern "C" fn library_settings_new(
max_level: LevelInt, max_level: LevelInt,
shiny_rate: u32, shiny_rate: u32,
) -> IdentifiablePointer<Box<dyn LibrarySettings>> { ) -> NativeResult<IdentifiablePointer<Box<dyn LibrarySettings>>> {
let b: Box<dyn LibrarySettings> = Box::new(LibrarySettingsImpl::new(max_level, shiny_rate)); match LibrarySettingsImpl::new(max_level, shiny_rate) {
b.into() Ok(settings) => {
let b: Box<dyn LibrarySettings> = Box::new(settings);
let p: IdentifiablePointer<Box<dyn LibrarySettings>> = b.into();
p.into()
}
Err(e) => NativeResult::err(e),
}
} }
/// Drop a library settings object. /// Drop a library settings object.

View File

@ -1,3 +1,4 @@
// Linter modifications
#![allow(clippy::too_many_arguments, clippy::needless_range_loop)] #![allow(clippy::too_many_arguments, clippy::needless_range_loop)]
#![allow(clippy::not_unsafe_ptr_arg_deref)] #![allow(clippy::not_unsafe_ptr_arg_deref)]
#![allow(clippy::borrowed_box)] #![allow(clippy::borrowed_box)]
@ -5,9 +6,11 @@
#![allow(ambiguous_glob_reexports)] #![allow(ambiguous_glob_reexports)]
#![deny(missing_docs)] #![deny(missing_docs)]
#![deny(clippy::missing_docs_in_private_items)] #![deny(clippy::missing_docs_in_private_items)]
// #![deny(clippy::unwrap_used)]
// #![deny(clippy::expect_used)]
// Features
#![feature(test)] #![feature(test)]
#![feature(const_option)] #![feature(const_option)]
#![feature(is_some_and)]
#![feature(new_uninit)] #![feature(new_uninit)]
#![feature(get_mut_unchecked)] #![feature(get_mut_unchecked)]
#![feature(strict_provenance)] #![feature(strict_provenance)]
@ -27,7 +30,6 @@ extern crate core;
#[macro_use] #[macro_use]
extern crate enum_display_derive; extern crate enum_display_derive;
use std::fmt::{Display, Formatter};
#[doc(hidden)] #[doc(hidden)]
pub use utils::*; pub use utils::*;
@ -48,39 +50,3 @@ pub mod script_implementations;
pub mod static_data; pub mod static_data;
/// The utils module includes misc utils that are used within PkmnLib /// The utils module includes misc utils that are used within PkmnLib
pub mod utils; pub mod utils;
/// The PokemonError enum holds all different error states that can be encountered in PkmnLib.
#[derive(Debug, Clone)]
pub enum PokemonError {
/// A script was requested, but we were unable to find it.
ScriptNotFound {
/// The category of the script we requested,
category: ScriptCategory,
/// The unique key of the requested script.
name: StringKey,
},
/// We requested data for a specific target, but that target does not exist on the battle field.
InvalidTargetRequested,
/// Misc errors. Use of this should be minimized, but it is useful for early development.
MiscError,
}
/// A simple result type.
pub type PkmnResult<T> = Result<T, PokemonError>;
impl Display for PokemonError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PokemonError::ScriptNotFound { category, name } => {
write!(f, "No script found with category `{category}` and name `{name}`")
}
PokemonError::InvalidTargetRequested => {
write!(f, "Invalid target was requested")
}
PokemonError::MiscError => {
write!(f, "An unknown error occurred")
}
}
}
}

View File

@ -59,7 +59,7 @@ register! {
env: FunctionEnvMut<WebAssemblyEnv>, env: FunctionEnvMut<WebAssemblyEnv>,
battle: ExternRef<Battle>, battle: ExternRef<Battle>,
) -> ExternRef<StringKey> { ) -> ExternRef<StringKey> {
let weather = battle.value_func(&env).unwrap().weather_name(); let weather = battle.value_func(&env).unwrap().weather_name().unwrap();
if let Some(weather) = weather { if let Some(weather) = weather {
ExternRef::func_new(&env, &weather) ExternRef::func_new(&env, &weather)
} else { } else {

View File

@ -10,14 +10,14 @@ register! {
env: FunctionEnvMut<WebAssemblyEnv>, env: FunctionEnvMut<WebAssemblyEnv>,
battle_random: ExternRef<BattleRandom>, battle_random: ExternRef<BattleRandom>,
) -> i32 { ) -> i32 {
battle_random.value_func(&env).unwrap().get() battle_random.value_func(&env).unwrap().get().unwrap()
} }
fn battle_random_get_max( fn battle_random_get_max(
env: FunctionEnvMut<WebAssemblyEnv>, env: FunctionEnvMut<WebAssemblyEnv>,
battle_random: ExternRef<BattleRandom>, battle_random: ExternRef<BattleRandom>,
max: i32 max: i32
) -> i32 { ) -> i32 {
battle_random.value_func(&env).unwrap().get_max(max) battle_random.value_func(&env).unwrap().get_max(max).unwrap()
} }
fn battle_random_get_between( fn battle_random_get_between(
env: FunctionEnvMut<WebAssemblyEnv>, env: FunctionEnvMut<WebAssemblyEnv>,
@ -25,6 +25,6 @@ register! {
min: i32, min: i32,
max: i32 max: i32
) -> i32 { ) -> i32 {
battle_random.value_func(&env).unwrap().get_between(min, max) battle_random.value_func(&env).unwrap().get_between(min, max).unwrap()
} }
} }

View File

@ -10,6 +10,6 @@ register! {
battle_random: ExternRef<ChoiceQueue>, battle_random: ExternRef<ChoiceQueue>,
pokemon: ExternRef<Pokemon> pokemon: ExternRef<Pokemon>
) -> u8 { ) -> u8 {
u8::from(battle_random.value_func(&env).unwrap().move_pokemon_choice_next(pokemon.value_func(&env).unwrap())) u8::from(battle_random.value_func(&env).unwrap().move_pokemon_choice_next(pokemon.value_func(&env).unwrap()).unwrap())
} }
} }

View File

@ -108,7 +108,7 @@ register! {
source: u8 source: u8
) { ) {
unsafe{ unsafe{
pokemon.value_func(&env).unwrap().damage(damage, transmute(source)); pokemon.value_func(&env).unwrap().damage(damage, transmute(source)).unwrap();
} }
} }

View File

@ -1,3 +1,6 @@
// allow these for now until we have a good result interface defined.
#[allow(clippy::unwrap_used)]
#[allow(clippy::expect_used)]
/// The export registry module deals with registering all functions we require in WebAssembly. /// The export registry module deals with registering all functions we require in WebAssembly.
mod export_registry; mod export_registry;
/// A hacky extern ref implementation to ensure the client does not do things it is not allowed to do. /// A hacky extern ref implementation to ensure the client does not do things it is not allowed to do.

View File

@ -3,6 +3,8 @@ use std::fmt::{Debug, Formatter};
use std::mem::{align_of, forget, size_of}; use std::mem::{align_of, forget, size_of};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use anyhow::Result;
use anyhow_ext::{anyhow, ensure};
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use parking_lot::lock_api::RwLockReadGuard; use parking_lot::lock_api::RwLockReadGuard;
use parking_lot::{RawRwLock, RwLock}; use parking_lot::{RawRwLock, RwLock};
@ -19,7 +21,7 @@ use crate::script_implementations::wasm::script_function_cache::ScriptFunctionCa
use crate::script_implementations::wasm::temp_wasm_allocator::{AllocatedObject, TempWasmAllocator}; use crate::script_implementations::wasm::temp_wasm_allocator::{AllocatedObject, TempWasmAllocator};
use crate::script_implementations::wasm::WebAssemblyScriptCapabilities; use crate::script_implementations::wasm::WebAssemblyScriptCapabilities;
use crate::static_data::Item; use crate::static_data::Item;
use crate::{PkmnResult, ScriptCategory, StringKey, ValueIdentifiable, ValueIdentifier}; use crate::{ScriptCategory, StringKey, ValueIdentifiable, ValueIdentifier};
/// A WebAssembly script resolver implements the dynamic scripts functionality with WebAssembly. /// A WebAssembly script resolver implements the dynamic scripts functionality with WebAssembly.
pub struct WebAssemblyScriptResolver { pub struct WebAssemblyScriptResolver {
@ -175,21 +177,20 @@ impl ScriptResolver for WebAssemblyScriptResolver {
owner: ScriptOwnerData, owner: ScriptOwnerData,
category: ScriptCategory, category: ScriptCategory,
script_key: &StringKey, script_key: &StringKey,
) -> PkmnResult<Option<Arc<dyn Script>>> { ) -> Result<Option<Arc<dyn Script>>> {
let script = self let script = self
.load_script_fn .load_script_fn
.as_ref() .as_ref()
.unwrap() .ok_or(anyhow!("Load script function was not found"))?
.call( .call(
&mut self.store_mut(), &mut self.store_mut(),
category as u8, category as u8,
ExternRef::new_with_resolver(self, script_key), ExternRef::new_with_resolver(self, script_key),
) )?;
.unwrap();
self.environment_data.setup_script(script, category, script_key, owner) self.environment_data.setup_script(script, category, script_key, owner)
} }
fn load_item_script(&self, _key: &dyn Item) -> PkmnResult<Option<Arc<dyn ItemScript>>> { fn load_item_script(&self, _key: &dyn Item) -> Result<Option<Arc<dyn ItemScript>>> {
todo!() todo!()
} }
} }
@ -354,7 +355,7 @@ impl WebAssemblyEnvironmentData {
.unwrap() .unwrap()
.call( .call(
&mut self.store_mut(), &mut self.store_mut(),
(size_of::<T>() * v.len()) as u32, (std::mem::size_of_val(v)) as u32,
align_of::<T>() as u32, align_of::<T>() as u32,
) )
.unwrap(); .unwrap();
@ -484,7 +485,7 @@ impl WebAssemblyEnvironmentData {
category: ScriptCategory, category: ScriptCategory,
script_key: &StringKey, script_key: &StringKey,
owner: ScriptOwnerData, owner: ScriptOwnerData,
) -> PkmnResult<Option<Arc<dyn Script>>> { ) -> Result<Option<Arc<dyn Script>>> {
if script_ptr == 0 { if script_ptr == 0 {
return Ok(None); return Ok(None);
} }
@ -502,13 +503,12 @@ impl WebAssemblyEnvironmentData {
.read() .read()
.get::<StringKey>(&"get_script_capabilities".into()) .get::<StringKey>(&"get_script_capabilities".into())
{ {
let res = get_cap let res = get_cap.call(&mut self.store_mut(), &[Value::I32(script_ptr as i32)])?;
.call(&mut self.store_mut(), &[Value::I32(script_ptr as i32)])
.unwrap();
let ptr = let ptr =
(self.memory() as *const WebAssemblyScriptCapabilities).offset(res[0].i32().unwrap() as isize); (self.memory() as *const WebAssemblyScriptCapabilities).offset(res[0].i32().unwrap() as isize);
let length = res[1].i32().unwrap() as usize; let length = res[1].i32();
for i in 0..length { ensure!(length.is_some(), "Script capabilities length was not a valid i32");
for i in 0..length.unwrap() as usize {
capabilities.insert(*ptr.add(i)); capabilities.insert(*ptr.add(i));
} }
} }

View File

@ -1,11 +1,13 @@
use crate::defines::LevelInt; use crate::defines::LevelInt;
use anyhow::Result;
use anyhow_ext::ensure;
/// A growth rate defines how much experience is required per level. /// A growth rate defines how much experience is required per level.
pub trait GrowthRate { pub trait GrowthRate {
/// Calculate the level something with this growth rate would have at a certain experience. /// Calculate the level something with this growth rate would have at a certain experience.
fn calculate_level(&self, experience: u32) -> LevelInt; fn calculate_level(&self, experience: u32) -> LevelInt;
/// Calculate the experience something with this growth rate would have at a certain level. /// Calculate the experience something with this growth rate would have at a certain level.
fn calculate_experience(&self, level: LevelInt) -> u32; fn calculate_experience(&self, level: LevelInt) -> Result<u32>;
} }
/// An implementation of the growth rate that uses a lookup table for experience. /// An implementation of the growth rate that uses a lookup table for experience.
@ -32,11 +34,12 @@ impl GrowthRate for LookupGrowthRate {
self.experience.len() as LevelInt self.experience.len() as LevelInt
} }
fn calculate_experience(&self, level: LevelInt) -> u32 { fn calculate_experience(&self, level: LevelInt) -> Result<u32> {
ensure!(level > 0, "Level must be greater than 0, but was {}", level);
if level >= self.experience.len() as LevelInt { if level >= self.experience.len() as LevelInt {
*self.experience.last().unwrap() Ok(*self.experience.last().unwrap())
} else { } else {
self.experience[(level - 1) as usize] Ok(self.experience[(level - 1) as usize])
} }
} }
} }
@ -50,7 +53,7 @@ pub(crate) mod tests {
pub GrowthRate {} pub GrowthRate {}
impl GrowthRate for GrowthRate { impl GrowthRate for GrowthRate {
fn calculate_level(&self, experience: u32) -> LevelInt; fn calculate_level(&self, experience: u32) -> LevelInt;
fn calculate_experience(&self, level: LevelInt) -> u32; fn calculate_experience(&self, level: LevelInt) -> Result<u32>;
} }
} }
} }

View File

@ -1,3 +1,4 @@
use anyhow::Result;
use std::fmt; use std::fmt;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
@ -12,7 +13,7 @@ pub trait GrowthRateLibrary: Debug + ValueIdentifiable {
/// Calculates the level for a given growth key name and a certain experience. /// Calculates the level for a given growth key name and a certain experience.
fn calculate_level(&self, growth_rate: &StringKey, experience: u32) -> LevelInt; fn calculate_level(&self, growth_rate: &StringKey, experience: u32) -> LevelInt;
/// Calculates the experience for a given growth key name and a certain level. /// Calculates the experience for a given growth key name and a certain level.
fn calculate_experience(&self, growth_rate: &StringKey, level: LevelInt) -> u32; fn calculate_experience(&self, growth_rate: &StringKey, level: LevelInt) -> Result<u32>;
/// Adds a new growth rate with a name and value. /// Adds a new growth rate with a name and value.
fn add_growth_rate(&mut self, key: &StringKey, value: Box<dyn GrowthRate>); fn add_growth_rate(&mut self, key: &StringKey, value: Box<dyn GrowthRate>);
} }
@ -41,7 +42,7 @@ impl GrowthRateLibrary for GrowthRateLibraryImpl {
self.growth_rates[growth_rate].calculate_level(experience) self.growth_rates[growth_rate].calculate_level(experience)
} }
/// Calculates the experience for a given growth key name and a certain level. /// Calculates the experience for a given growth key name and a certain level.
fn calculate_experience(&self, growth_rate: &StringKey, level: LevelInt) -> u32 { fn calculate_experience(&self, growth_rate: &StringKey, level: LevelInt) -> Result<u32> {
self.growth_rates[growth_rate].calculate_experience(level) self.growth_rates[growth_rate].calculate_experience(level)
} }
@ -89,7 +90,7 @@ pub mod tests {
pub GrowthRateLibrary{} pub GrowthRateLibrary{}
impl GrowthRateLibrary for GrowthRateLibrary { impl GrowthRateLibrary for GrowthRateLibrary {
fn calculate_level(&self, growth_rate: &StringKey, experience: u32) -> LevelInt; fn calculate_level(&self, growth_rate: &StringKey, experience: u32) -> LevelInt;
fn calculate_experience(&self, growth_rate: &StringKey, level: LevelInt) -> u32; fn calculate_experience(&self, growth_rate: &StringKey, level: LevelInt) -> Result<u32>;
fn add_growth_rate(&mut self, key: &StringKey, value: Box<dyn GrowthRate>); fn add_growth_rate(&mut self, key: &StringKey, value: Box<dyn GrowthRate>);
} }
impl ValueIdentifiable for GrowthRateLibrary { impl ValueIdentifiable for GrowthRateLibrary {
@ -109,7 +110,7 @@ pub mod tests {
#[test] #[test]
fn add_growth_rate_to_library_and_calculate_experience() { fn add_growth_rate_to_library_and_calculate_experience() {
let lib = build(); let lib = build();
assert_eq!(lib.calculate_experience(&"test_growthrate".into(), 1), 0); assert_eq!(lib.calculate_experience(&"test_growthrate".into(), 1).unwrap(), 0);
assert_eq!(lib.calculate_experience(&"test_growthrate".into(), 3), 10); assert_eq!(lib.calculate_experience(&"test_growthrate".into(), 3).unwrap(), 10);
} }
} }

View File

@ -1,5 +1,7 @@
use crate::defines::LevelInt; use crate::defines::LevelInt;
use crate::{ValueIdentifiable, ValueIdentifier}; use crate::{ValueIdentifiable, ValueIdentifier};
use anyhow::Result;
use anyhow_ext::ensure;
use std::fmt::Debug; use std::fmt::Debug;
/// This library holds several misc settings for the library. /// This library holds several misc settings for the library.
@ -30,14 +32,14 @@ impl LibrarySettingsImpl {
/// - `shiny_rate` is the chance of a Pokemon being shiny, as the denominator of a fraction, where /// - `shiny_rate` is the chance of a Pokemon being shiny, as the denominator of a fraction, where
/// the nominator is 1. For example, if this is 1000, then the chance of a Pokemon being shiny is /// the nominator is 1. For example, if this is 1000, then the chance of a Pokemon being shiny is
/// 1/1000. /// 1/1000.
pub fn new(maximum_level: LevelInt, shiny_rate: u32) -> Self { pub fn new(maximum_level: LevelInt, shiny_rate: u32) -> Result<Self> {
assert!(shiny_rate >= 1); ensure!(shiny_rate >= 1);
assert!(maximum_level >= 1); ensure!(maximum_level >= 1);
Self { Ok(Self {
identifier: Default::default(), identifier: Default::default(),
maximum_level, maximum_level,
shiny_rate, shiny_rate,
} })
} }
} }

View File

@ -140,4 +140,18 @@ pub mod tests {
let name2 = lib.get_nature_name(&n2); let name2 = lib.get_nature_name(&n2);
assert_eq!(name2, "bar".into()); assert_eq!(name2, "bar".into());
} }
#[test]
fn create_nature_library_insert_single_and_retrieve_random() {
let mut lib = NatureLibraryImpl::new(2);
lib.load_nature(
"foo".into(),
NatureImpl::new(Statistic::HP, Statistic::Attack, 1.1, 0.9),
);
let n1 = lib.get_random_nature(&mut Random::new(0));
assert_eq!(n1.increased_stat(), Statistic::HP);
assert_eq!(n1.decreased_stat(), Statistic::Attack);
assert_eq!(n1.get_stat_modifier(n1.increased_stat()), 1.1);
assert_eq!(n1.get_stat_modifier(n1.decreased_stat()), 0.9);
}
} }

View File

@ -199,7 +199,7 @@ pub mod test {
pub fn build() -> StaticDataImpl { pub fn build() -> StaticDataImpl {
StaticDataImpl { StaticDataImpl {
identifier: Default::default(), identifier: Default::default(),
settings: Box::new(LibrarySettingsImpl::new(100, 100)), settings: Box::new(LibrarySettingsImpl::new(100, 100).unwrap()),
species: crate::static_data::libraries::species_library::tests::build(), species: crate::static_data::libraries::species_library::tests::build(),
moves: crate::static_data::libraries::move_library::tests::build(), moves: crate::static_data::libraries::move_library::tests::build(),
items: crate::static_data::libraries::item_library::tests::build(), items: crate::static_data::libraries::item_library::tests::build(),

View File

@ -65,7 +65,7 @@ pub fn load_library() -> LoadResult {
let species_load_time = t2 - t1; let species_load_time = t2 - t1;
let data = StaticDataImpl::new( let data = StaticDataImpl::new(
Box::new(LibrarySettingsImpl::new(100, 100)), Box::new(LibrarySettingsImpl::new(100, 100).unwrap()),
species, species,
moves, moves,
items, items,

View File

@ -79,6 +79,6 @@ impl TestPokemon {
builder = builder.learn_move(StringKey::new(move_name)); builder = builder.learn_move(StringKey::new(move_name));
} }
builder.build() builder.build().unwrap()
} }
} }

View File

@ -45,18 +45,56 @@ fn validate_library_load() {
); );
} }
#[test]
fn load_non_existing_wasm_script() {
let start_time = chrono::Utc::now();
let result = library_loader::load_library();
let end_time = chrono::Utc::now();
println!(
"Built library in {} ms\
\n\t- Types load time: {} ms\
\n\t- Natures load time: {} ms\
\n\t- Items load time: {} ms\
\n\t- Growth Rate load time: {} ms\
\n\t- Abilities load time: {} ms\
\n\t- Moves load time: {} ms\
\n\t- Species load time: {} ms\
\n\t- WASM load time: {} ms\
",
(end_time - start_time).num_milliseconds(),
result.types_load_time.num_milliseconds(),
result.natures_load_time.num_milliseconds(),
result.items_load_time.num_milliseconds(),
result.growth_rate_load_time.num_milliseconds(),
result.abilities_load_time.num_milliseconds(),
result.moves_load_time.num_milliseconds(),
result.species_load_time.num_milliseconds(),
result.wasm_load_time.num_milliseconds(),
);
let script = result
.library
.load_script(ScriptOwnerData::None, ScriptCategory::Move, &"_____non_existing".into())
.unwrap();
assert!(script.is_none());
}
/// Assurance has the interesting properties that it creates a special data script internally, and /// Assurance has the interesting properties that it creates a special data script internally, and
/// deletes that data script through the get_owner functionality. /// deletes that data script through the get_owner functionality.
#[test] #[test]
#[cfg_attr(miri, ignore)]
fn validate_assurance() { fn validate_assurance() {
let lib = get_library(); let lib = get_library();
let p1 = Arc::new( let p1 = Arc::new(
PokemonBuilder::new(lib.clone(), "charizard".into(), 100) PokemonBuilder::new(lib.clone(), "charizard".into(), 100)
.learn_move("assurance".into()) .learn_move("assurance".into())
.build(), .build()
.unwrap(),
);
let p2 = Arc::new(
PokemonBuilder::new(lib.clone(), "venusaur".into(), 100)
.build()
.unwrap(),
); );
let p2 = Arc::new(PokemonBuilder::new(lib.clone(), "venusaur".into(), 100).build());
let party1 = BattleParty::new( let party1 = BattleParty::new(
Arc::new(PokemonParty::new_from_vec(vec![Some(p1.clone())])), Arc::new(PokemonParty::new_from_vec(vec![Some(p1.clone())])),