diff --git a/.gitignore b/.gitignore index 4901f53..5c4a9f9 100755 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target Cargo.lock .idea/ -types.toml \ No newline at end of file +types.toml +tarpaulin-report.html \ No newline at end of file diff --git a/src/dynamic_data/choices.rs b/src/dynamic_data/choices.rs index cf5efde..bf63f9b 100755 --- a/src/dynamic_data/choices.rs +++ b/src/dynamic_data/choices.rs @@ -181,6 +181,7 @@ pub struct MoveChoice { impl MoveChoice { /// Initializes the data for a new move choice. pub fn new(user: Arc, used_move: Arc, target_side: u8, target_index: u8) -> Self { + let speed = user.boosted_stats().speed(); Self { used_move, target_side, @@ -190,7 +191,7 @@ impl MoveChoice { choice_data: Box::new(CommonChoiceData { identifier: Default::default(), user, - speed: 0, + speed, random_value: 0, has_failed: Default::default(), script_source_data: Default::default(), @@ -258,11 +259,12 @@ pub struct ItemChoice { impl ItemChoice { /// Initialised a new item choice. pub fn new(user: Arc) -> Self { + let speed = user.boosted_stats().speed(); Self { choice_data: Box::new(CommonChoiceData { identifier: Default::default(), user, - speed: 0, + speed, random_value: 0, has_failed: Default::default(), script_source_data: Default::default(), @@ -297,11 +299,12 @@ pub struct SwitchChoice { impl SwitchChoice { /// Initialise the turn choice data. pub fn new(user: Arc) -> Self { + let speed = user.boosted_stats().speed(); Self { choice_data: Box::new(CommonChoiceData { identifier: Default::default(), user, - speed: 0, + speed, random_value: 0, has_failed: Default::default(), script_source_data: Default::default(), @@ -375,11 +378,12 @@ pub struct PassChoice { impl PassChoice { /// Initialised a new pass choice. pub fn new(user: Arc) -> Self { + let speed = user.boosted_stats().speed(); Self { choice_data: Box::new(CommonChoiceData { identifier: Default::default(), user, - speed: 0, + speed, random_value: 0, has_failed: Default::default(), script_source_data: Default::default(), @@ -474,7 +478,16 @@ impl Ord for TurnChoice { } std::cmp::Ordering::Less } - TurnChoice::Pass(..) => std::cmp::Ordering::Less, + TurnChoice::Pass(..) => { + if let TurnChoice::Pass { .. } = other { + let speed_compare = self.speed().cmp(&other.speed()); + if speed_compare != std::cmp::Ordering::Equal { + return speed_compare; + } + return self.random_value().cmp(&other.random_value()); + } + std::cmp::Ordering::Less + } } } } diff --git a/src/dynamic_data/flow/choice_queue.rs b/src/dynamic_data/flow/choice_queue.rs index c3d696b..477fa17 100755 --- a/src/dynamic_data/flow/choice_queue.rs +++ b/src/dynamic_data/flow/choice_queue.rs @@ -1,5 +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}; use parking_lot::lock_api::MappedRwLockReadGuard; use parking_lot::{RawRwLock, RwLock, RwLockReadGuard}; @@ -20,8 +22,9 @@ pub struct ChoiceQueue { } impl ChoiceQueue { - /// Initializes a ChoiceQueue. We expect the given queue to already be sorted here. - pub(crate) fn new(queue: Vec>) -> Self { + /// Initializes a ChoiceQueue, and sort the choices. + pub(crate) fn new(mut queue: Vec>) -> Self { + queue.sort_unstable_by(|a, b| b.cmp(a)); Self { queue: RwLock::new(queue), current: 0, @@ -31,16 +34,24 @@ 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) -> TurnChoice { - let c = self.queue.write()[self.current].take(); + pub fn dequeue(&mut self) -> Option { + let mut write_lock = self.queue.write(); + if self.current >= write_lock.len() { + return None; + } + let c = write_lock[self.current].take(); self.current += 1; - c.unwrap() + c } /// This reads what the next choice to execute will be, without modifying state. - pub fn peek(&self) -> MappedRwLockReadGuard<'_, RawRwLock, TurnChoice> { + pub fn peek(&self) -> Option> { let read_lock = self.queue.read(); - RwLockReadGuard::map(read_lock, |a| a[self.current].as_ref().unwrap()) + if self.current >= read_lock.len() { + None + } else { + Some(RwLockReadGuard::map(read_lock, |a| a[self.current].as_ref().unwrap())) + } } /// Check if we have any choices remaining. @@ -53,7 +64,17 @@ impl ChoiceQueue { /// technically already been decided. pub fn resort(&mut self) { let len = self.queue.read().len(); - self.queue.write()[self.current..len].sort_unstable_by(|a, b| b.cmp(a)); + let mut write_lock = self.queue.write(); + for index in self.current..len { + let choice = &mut write_lock[index]; + if let Some(choice) = choice { + let mut speed = choice.user().boosted_stats().speed(); + script_hook!(change_speed, (*choice), choice, &mut speed); + *choice.speed_mut() = speed; + } + } + + write_lock[self.current..len].sort_unstable_by(|a, b| b.cmp(a)); } /// This moves the choice of a specific Pokemon up to the next choice to be executed. @@ -63,7 +84,7 @@ impl ChoiceQueue { // 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 std::ptr::eq(choice.user().as_ref(), pokemon) { + if pokemon.value_identifier() == choice.user().value_identifier() { desired_index = Some(index); break; } @@ -83,7 +104,7 @@ impl ChoiceQueue { let choice = queue_lock[desired_index].take().unwrap(); // Iterate backwards from the spot before the choice we want to move up, push them all back // by 1 spot. - for index in (desired_index - 1)..self.current { + for index in (self.current..desired_index).rev() { queue_lock.swap(index, index + 1); } // Place the choice that needs to be next in the next to be executed position. @@ -97,3 +118,244 @@ impl ChoiceQueue { RwLockReadGuard::map(read_lock, |a| &a[self.current..self.queue.read().len()]) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::defines::LevelInt; + use crate::dynamic_data::PassChoice; + use crate::static_data::{AbilityIndex, DataLibrary, Gender}; + use std::sync::Arc; + + #[test] + fn create_empty_queue() { + let queue = ChoiceQueue::new(Vec::new()); + assert!(!queue.has_next()); + assert!(queue.peek().is_none()); + } + + #[test] + fn dequeue_from_empty_queue() { + let mut queue = ChoiceQueue::new(Vec::new()); + assert!(queue.dequeue().is_none()); + } + + fn get_user(level: LevelInt) -> Pokemon { + let lib = Arc::new(crate::dynamic_data::libraries::dynamic_library::test::build()); + let species = lib.static_data().species().get(&"foo".into()).unwrap().clone(); + let form = species.get_form(&"default".into()).unwrap(); + + Pokemon::new( + lib, + species, + &form, + AbilityIndex { + hidden: false, + index: 0, + }, + level, + 0, + Gender::Male, + 0, + &"test_nature".into(), + ) + } + + #[test] + fn create_queue_with_single_item() { + let user = Arc::new(get_user(10)); + + let queue = ChoiceQueue::new(vec![Some(TurnChoice::Pass(PassChoice::new(user)))]); + assert!(queue.has_next()); + assert!(queue.peek().is_some()); + assert_eq!(7, queue.peek().unwrap().speed()); + } + + #[test] + fn dequeue_from_queue_with_single_item() { + let user = Arc::new(get_user(10)); + + let mut queue = ChoiceQueue::new(vec![Some(TurnChoice::Pass(PassChoice::new(user)))]); + assert!(queue.has_next()); + assert_eq!(7, queue.dequeue().unwrap().speed()); + assert!(!queue.has_next()); + assert!(queue.peek().is_none()); + } + + #[test] + fn create_queue_with_two_items_with_equal_order() { + let user1 = Arc::new(get_user(10)); + let user2 = Arc::new(get_user(10)); + + let queue = ChoiceQueue::new(vec![ + Some(TurnChoice::Pass(PassChoice::new(user1))), + Some(TurnChoice::Pass(PassChoice::new(user2))), + ]); + assert!(queue.has_next()); + assert!(queue.peek().is_some()); + assert_eq!(7, queue.peek().unwrap().speed()); + } + + #[test] + fn create_queue_with_two_items_get_queue() { + let user1 = Arc::new(get_user(10)); + let user2 = Arc::new(get_user(5)); + + let queue = ChoiceQueue::new(vec![ + Some(TurnChoice::Pass(PassChoice::new(user1.clone()))), + Some(TurnChoice::Pass(PassChoice::new(user2))), + ]); + let inner_queue = queue.get_queue(); + assert_eq!( + inner_queue[0].as_ref().unwrap().user().value_identifier(), + user1.value_identifier() + ); + } + + #[test] + fn create_queue_with_two_items_in_wrong_order_sorts_correctly() { + let user1 = Arc::new(get_user(5)); + let user2 = Arc::new(get_user(100)); + + let mut queue = ChoiceQueue::new(vec![ + 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()); + } + + #[test] + fn resort_with_two_choices() { + let user1 = Arc::new(get_user(50)); + let user2 = Arc::new(get_user(1)); + + let mut queue = ChoiceQueue::new(vec![ + Some(TurnChoice::Pass(PassChoice::new(user1.clone()))), + Some(TurnChoice::Pass(PassChoice::new(user2.clone()))), + ]); + + user2.change_level_by(60); + assert_eq!( + user1.value_identifier(), + queue.peek().unwrap().user().value_identifier() + ); + queue.resort(); + assert_eq!( + user2.value_identifier(), + queue.peek().unwrap().user().value_identifier() + ); + } + + #[test] + fn move_pokemon_choice_first_with_two_choices() { + let user1 = Arc::new(get_user(100)); + let user2 = Arc::new(get_user(1)); + + let mut queue = ChoiceQueue::new(vec![ + Some(TurnChoice::Pass(PassChoice::new(user1.clone()))), + Some(TurnChoice::Pass(PassChoice::new(user2.clone()))), + ]); + assert_eq!( + user1.value_identifier(), + queue.peek().unwrap().user().value_identifier() + ); + assert!(queue.move_pokemon_choice_next(user2.as_ref())); + + assert_eq!( + user2.value_identifier(), + queue.dequeue().unwrap().user().value_identifier() + ); + assert_eq!( + user1.value_identifier(), + queue.dequeue().unwrap().user().value_identifier() + ); + } + + #[test] + fn move_pokemon_choice_first_when_choice_has_already_been() { + let user1 = Arc::new(get_user(10)); + let user2 = Arc::new(get_user(100)); + + let mut queue = ChoiceQueue::new(vec![ + Some(TurnChoice::Pass(PassChoice::new(user1.clone()))), + Some(TurnChoice::Pass(PassChoice::new(user2.clone()))), + ]); + assert_eq!( + user2.value_identifier(), + queue.dequeue().unwrap().user().value_identifier() + ); + assert!(!queue.move_pokemon_choice_next(user2.as_ref())); + + assert_eq!( + user1.value_identifier(), + queue.dequeue().unwrap().user().value_identifier() + ); + assert!(queue.peek().is_none()) + } + + #[test] + fn move_pokemon_choice_first_when_choice_is_next() { + let user1 = Arc::new(get_user(100)); + let user2 = Arc::new(get_user(10)); + + let queue = ChoiceQueue::new(vec![ + Some(TurnChoice::Pass(PassChoice::new(user1.clone()))), + Some(TurnChoice::Pass(PassChoice::new(user2.clone()))), + ]); + assert_eq!( + user1.value_identifier(), + queue.peek().unwrap().user().value_identifier() + ); + assert!(queue.move_pokemon_choice_next(user1.as_ref())); + assert_eq!( + user1.value_identifier(), + queue.peek().unwrap().user().value_identifier() + ); + } + + #[test] + fn move_pokemon_choice_first_with_seven_choices() { + let users = [ + Arc::new(get_user(100)), + Arc::new(get_user(90)), + Arc::new(get_user(80)), + Arc::new(get_user(70)), + Arc::new(get_user(60)), + Arc::new(get_user(50)), + Arc::new(get_user(40)), + ]; + + let mut queue = ChoiceQueue::new(vec![ + Some(TurnChoice::Pass(PassChoice::new(users[0].clone()))), + Some(TurnChoice::Pass(PassChoice::new(users[1].clone()))), + Some(TurnChoice::Pass(PassChoice::new(users[2].clone()))), + Some(TurnChoice::Pass(PassChoice::new(users[3].clone()))), + Some(TurnChoice::Pass(PassChoice::new(users[4].clone()))), + Some(TurnChoice::Pass(PassChoice::new(users[5].clone()))), + Some(TurnChoice::Pass(PassChoice::new(users[6].clone()))), + ]); + assert_eq!( + users[0].value_identifier(), + queue.peek().unwrap().user().value_identifier() + ); + assert!(queue.move_pokemon_choice_next(users[4].as_ref())); + + assert_eq!( + users[4].value_identifier(), + queue.dequeue().unwrap().user().value_identifier() + ); + for index in 0..4 { + assert_eq!( + users[index].value_identifier(), + queue.dequeue().unwrap().user().value_identifier() + ); + } + for index in 5..7 { + assert_eq!( + users[index].value_identifier(), + queue.dequeue().unwrap().user().value_identifier() + ); + } + } +} diff --git a/src/dynamic_data/flow/turn_runner.rs b/src/dynamic_data/flow/turn_runner.rs index 5189710..b976efa 100755 --- a/src/dynamic_data/flow/turn_runner.rs +++ b/src/dynamic_data/flow/turn_runner.rs @@ -33,7 +33,9 @@ impl Battle { // continue running however. while choice_queue.read().as_ref().unwrap().has_next() && !self.has_ended() { let choice = choice_queue.write().as_mut().unwrap().dequeue(); - self.execute_choice(&choice)?; + if let Some(choice) = choice { + self.execute_choice(&choice)?; + } } // If the battle is not ended, we have arrived at the normal end of a turn. and thus want diff --git a/src/dynamic_data/models/battle.rs b/src/dynamic_data/models/battle.rs index 5d580a9..7693b2c 100755 --- a/src/dynamic_data/models/battle.rs +++ b/src/dynamic_data/models/battle.rs @@ -308,7 +308,7 @@ impl Battle { } } - let mut speed = choice.speed(); + let mut speed = choice.user().boosted_stats().speed(); let c = choice.deref(); script_hook!(change_speed, c, c, &mut speed); *choice.speed_mut() = speed; @@ -322,7 +322,6 @@ impl Battle { } self.current_turn.fetch_add(1, Ordering::SeqCst); - choices.sort_unstable_by(|a, b| b.cmp(a)); self.current_turn_queue.write().replace(ChoiceQueue::new(choices)); { diff --git a/src/dynamic_data/models/pokemon.rs b/src/dynamic_data/models/pokemon.rs index e3b65f4..246057b 100755 --- a/src/dynamic_data/models/pokemon.rs +++ b/src/dynamic_data/models/pokemon.rs @@ -45,7 +45,7 @@ pub struct Pokemon { display_form: Option>, /// The current level of the Pokemon. - level: LevelInt, + level: Atomic, /// The amount of experience of the Pokemon. experience: AtomicU32, /// A unique random number for this Pokemon. @@ -151,7 +151,7 @@ impl Pokemon { form: RwLock::new(form.clone()), display_species: None, display_form: None, - level, + level: Atomic::new(level), experience: AtomicU32::new(experience), unique_identifier, gender: RwLock::new(gender), @@ -218,7 +218,7 @@ impl Pokemon { } /// The current level of the Pokemon. pub fn level(&self) -> LevelInt { - self.level + self.level.load(Ordering::Relaxed) } /// The amount of experience of the Pokemon. pub fn experience(&self) -> u32 { @@ -705,6 +705,21 @@ impl Pokemon { pub fn clear_status(&self) { self.status_script.clear() } + + /// Increases the level by a certain amount + pub fn change_level_by(&self, amount: LevelInt) { + self.level + .fetch_update(Ordering::SeqCst, Ordering::Relaxed, |x| { + let max_level = self.library().static_data().settings().maximum_level(); + if x + amount > max_level { + Some(max_level) + } else { + Some(x + amount) + } + }) + .expect("Failed to change level."); + self.recalculate_flat_stats(); + } } /// The data of the Pokemon related to being in a battle. diff --git a/src/dynamic_data/script_handling/mod.rs b/src/dynamic_data/script_handling/mod.rs index 426ca40..c990360 100755 --- a/src/dynamic_data/script_handling/mod.rs +++ b/src/dynamic_data/script_handling/mod.rs @@ -24,7 +24,7 @@ mod volatile_scripts_owner; /// to only run the script function if it is not suppressed, and can take any amount of parameters. #[macro_export] macro_rules! script_hook { - ($hook_name: ident, $source: ident, $($parameters: expr),*) => { + ($hook_name: ident, $source: expr, $($parameters: expr),*) => { let mut aggregator = $source.get_script_iterator(); while let Some(script_container) = aggregator.get_next() { let script = script_container.get(); diff --git a/src/static_data/libraries/ability_library.rs b/src/static_data/libraries/ability_library.rs index 0dd6a87..c73ac51 100755 --- a/src/static_data/libraries/ability_library.rs +++ b/src/static_data/libraries/ability_library.rs @@ -54,8 +54,6 @@ pub mod tests { &StringKey::new("test_ability"), Arc::new(Ability::new(&"test_ability".into(), &"test_ability".into(), Vec::new())), ); - // Drops borrow as mut - lib } diff --git a/src/static_data/libraries/species_library.rs b/src/static_data/libraries/species_library.rs index 1dfb0fc..c923dab 100755 --- a/src/static_data/libraries/species_library.rs +++ b/src/static_data/libraries/species_library.rs @@ -66,7 +66,7 @@ pub mod tests { 0.0, 0, Vec::new(), - StaticStatisticSet::default(), + StaticStatisticSet::new(10, 10, 10, 10, 10, 10), Vec::new(), Vec::new(), LearnableMoves::new(), diff --git a/src/static_data/libraries/type_library.rs b/src/static_data/libraries/type_library.rs index d2b95f5..2fecbf5 100755 --- a/src/static_data/libraries/type_library.rs +++ b/src/static_data/libraries/type_library.rs @@ -174,4 +174,20 @@ pub mod tests { assert_approx_eq!(r.get_effectiveness(t0, &[t1, t1]), 0.25); assert_approx_eq!(r.get_effectiveness(t1, &[t0, t0]), 4.0); } + + #[test] + fn add_two_types_get_type_name() { + let mut lib = TypeLibrary::new(2); + + // Borrow as mut so we can insert + let w = &mut lib; + let t0 = w.register_type(&"foo".into()); + let t1 = w.register_type(&"bar".into()); + // Drops borrow as mut + + // Borrow as read so we can read + let r = &lib; + assert_eq!(r.get_type_name(t0).unwrap(), "foo".into()); + assert_eq!(r.get_type_name(t1).unwrap(), "bar".into()); + } }