diff --git a/gen_7_scripts/src/moves/assist.rs b/gen_7_scripts/src/moves/assist.rs index 2a3e09a..ecd6005 100755 --- a/gen_7_scripts/src/moves/assist.rs +++ b/gen_7_scripts/src/moves/assist.rs @@ -7,24 +7,23 @@ use pkmn_lib_interface::handling::{Script, ScriptCapabilities}; script!(Assist, "assist"); impl Assist { + /// Gets all the learned moves from the entire party, except the ones from the user, and the ones + /// that are not allowed to be copied. fn get_party_moves(party: &Party, user: &Pokemon) -> Vec { let mut possible_moves = Vec::new(); // Iterate over every mon in the party - for mon_index in 0..party.length() { - let mon = party.get_pokemon(mon_index); - if let Some(mon) = mon { - // Ignore moves from the user - if mon.equals(user) { - continue; - } - // Iterate over all moves. We make the assumption of 4 moves. - for move_index in 0..4 { - let mv = mon.get_learned_move(move_index); - if let Some(mv) = mv { - // Make sure we can copy the move, otherwise add it as possible move. - if crate::utils::copyable_moves::can_copy_move(&mv.move_data()) { - possible_moves.push(mv.move_data()) - } + for mon in party.into_iter().flatten() { + // Ignore moves from the user + if mon.equals(user) { + continue; + } + // Iterate over all moves. We make the assumption of 4 moves. + for move_index in 0..4 { + let mv = mon.get_learned_move(move_index); + if let Some(mv) = mv { + // Make sure we can copy the move, otherwise add it as possible move. + if crate::utils::copyable_moves::can_copy_move(&mv.move_data()) { + possible_moves.push(mv.move_data()) } } } @@ -49,7 +48,13 @@ impl Script for Assist { fn change_move(&self, choice: TurnChoice, move_name: &mut StringKey) { let user = choice.user(); let battle = user.battle().unwrap(); - let party = battle.find_party_for_pokemon(&user).unwrap().party(); + let party = battle.find_party_for_pokemon(&user); + if party.is_none() { + choice.fail(); + return; + } + + let party = party.unwrap().party(); let possible_moves = Self::get_party_moves(&party, &user); if possible_moves.is_empty() { @@ -64,3 +69,211 @@ impl Script for Assist { self } } + +#[cfg(test)] +mod tests { + use super::*; + use alloc::boxed::Box; + use alloc::rc::Rc; + use alloc::vec; + use pkmn_lib_interface::app_interface::{ + MockBaseTurnChoiceData, MockBattle, MockBattleParty, MockBattleRandom, MockLearnedMove, + MockMoveData, MockMoveTurnChoiceData, MockParty, MockPokemon, + }; + + fn mock_pokemon_with_moves(moves: Vec, equals_user: bool) -> Pokemon { + let mut mon = MockPokemon::new(); + let moves = Rc::new(moves); + mon.expect_get_learned_move().returning_st(move |index| { + let moves = moves.clone(); + if index < moves.len() { + let mut learned_move = MockLearnedMove::new(); + learned_move.expect_move_data().returning_st(move || { + let mut move_data = MockMoveData::new(); + let moves = moves.clone(); + move_data + .expect_name() + .returning_st(move || moves.get(index).unwrap().clone()); + Rc::new(move_data) + }); + Some(Rc::new(learned_move)) + } else { + None + } + }); + mon.expect_equals().return_const(equals_user); + Rc::new(mon) + } + + #[test] + fn get_party_moves_returns_moves_from_party_length_1() { + let mut party = MockParty::new(); + party.expect_length().once().return_const(1usize); + party + .expect_get_pokemon() + .times(1) + .returning_st(move |_| Some(mock_pokemon_with_moves(vec!["tackle".into()], false))); + + let party: Party = Rc::new(party); + let user: Pokemon = Rc::new(MockPokemon::new()); + + let moves = Assist::get_party_moves(&party, &user); + assert_eq!(1, moves.len()); + assert_eq!(moves[0].name(), "tackle".into()) + } + + #[test] + fn get_party_moves_returns_moves_from_party_length_7() { + let mut party = MockParty::new(); + party.expect_length().once().return_const(3usize); + party + .expect_get_pokemon() + .times(3) + .returning_st(move |index| { + if index == 0 { + Some(mock_pokemon_with_moves( + vec!["move1".into(), "move2".into(), "move3".into()], + false, + )) + } else if index == 1 { + Some(mock_pokemon_with_moves( + vec!["move1".into(), "move4".into()], + false, + )) + } else if index == 2 { + Some(mock_pokemon_with_moves( + vec!["move2".into(), "move5".into()], + false, + )) + } else { + None + } + }); + + let party: Party = Rc::new(party); + let user: Pokemon = Rc::new(MockPokemon::new()); + + let moves = Assist::get_party_moves(&party, &user); + assert_eq!(7, moves.len()); + assert_eq!(moves[0].name(), "move1".into()); + assert_eq!(moves[1].name(), "move2".into()); + assert_eq!(moves[2].name(), "move3".into()); + assert_eq!(moves[3].name(), "move1".into()); + assert_eq!(moves[4].name(), "move4".into()); + assert_eq!(moves[5].name(), "move2".into()); + assert_eq!(moves[6].name(), "move5".into()); + } + + #[test] + fn get_party_moves_ignores_user() { + let mut party = MockParty::new(); + party.expect_length().once().return_const(3usize); + party + .expect_get_pokemon() + .times(3) + .returning_st(move |index| { + if index == 0 { + Some(mock_pokemon_with_moves( + vec!["move1".into(), "move2".into(), "move3".into()], + false, + )) + } else if index == 1 { + Some(mock_pokemon_with_moves( + vec!["move1".into(), "move4".into()], + true, + )) + } else if index == 2 { + Some(mock_pokemon_with_moves( + vec!["move2".into(), "move5".into()], + false, + )) + } else { + None + } + }); + + let party: Party = Rc::new(party); + let user: Pokemon = Rc::new(MockPokemon::new()); + + let moves = Assist::get_party_moves(&party, &user); + assert_eq!(5, moves.len()); + assert_eq!(moves[0].name(), "move1".into()); + assert_eq!(moves[1].name(), "move2".into()); + assert_eq!(moves[2].name(), "move3".into()); + assert_eq!(moves[3].name(), "move2".into()); + assert_eq!(moves[4].name(), "move5".into()); + } + + #[test] + fn get_party_moves_ignores_non_copyable_move() { + let mut party = MockParty::new(); + party.expect_length().once().return_const(1usize); + party + .expect_get_pokemon() + .times(1) + .returning_st(move |_| Some(mock_pokemon_with_moves(vec!["chatter".into()], false))); + let party: Party = Rc::new(party); + let user: Pokemon = Rc::new(MockPokemon::new()); + + let moves = Assist::get_party_moves(&party, &user); + assert_eq!(0, moves.len()); + } + + #[test] + fn if_moves_found_assist_uses_that_move() { + let mut party = MockParty::new(); + party.expect_length().once().return_const(1usize); + party + .expect_get_pokemon() + .times(1) + .returning_st(move |_| Some(mock_pokemon_with_moves(vec!["tackle".into()], false))); + + let party: Party = Rc::new(party); + + let mut base_turn_choice = MockBaseTurnChoiceData::new(); + + base_turn_choice.expect_user().once().returning_st(move || { + let party = party.clone(); + let mut user = MockPokemon::new(); + user.expect_battle().once().returning_st(move || { + let party = party.clone(); + let mut battle = MockBattle::new(); + battle + .expect_find_party_for_pokemon() + .once() + .returning_st(move |_| { + let party = party.clone(); + let mut p = MockBattleParty::new(); + p.expect_party().returning_st(move || party.clone()); + + Some(Rc::new(p)) + }); + + battle.expect_random().returning_st(|| { + let mut random = MockBattleRandom::new(); + random.expect_get_max().return_const(0); + Rc::new(random) + }); + + Some(Rc::new(battle)) + }); + + Rc::new(user) + }); + base_turn_choice.expect_fail().never(); + + let base_turn_choice = Rc::new(base_turn_choice); + + let mut move_turn_choice = MockMoveTurnChoiceData::new(); + move_turn_choice + .expect_base() + .returning_st(move || base_turn_choice.clone()); + + let choice_data = TurnChoice::Move(Box::new(move_turn_choice)); + + let mut move_name: StringKey = "".into(); + let script = Assist::new(); + script.change_move(choice_data, &mut move_name); + assert_eq!(move_name, "tackle".into()) + } +} diff --git a/gen_7_scripts/src/utils/copyable_moves.rs b/gen_7_scripts/src/utils/copyable_moves.rs index 822c7ef..a904191 100755 --- a/gen_7_scripts/src/utils/copyable_moves.rs +++ b/gen_7_scripts/src/utils/copyable_moves.rs @@ -16,8 +16,7 @@ macro_rules! non_copyable { } pub fn can_copy_move(mv: &MoveData) -> bool { - // A list of all moves that cannot be copied. This expands to a match statement, so is fast on - // runtime. + // A list of all moves that cannot be copied. This expands to a match statement on the move name. non_copyable!( mv, "assist", diff --git a/pkmn_lib_interface/src/app_interface/dynamic_data/battle_party.rs b/pkmn_lib_interface/src/app_interface/dynamic_data/battle_party.rs index 619e621..8eb3afc 100755 --- a/pkmn_lib_interface/src/app_interface/dynamic_data/battle_party.rs +++ b/pkmn_lib_interface/src/app_interface/dynamic_data/battle_party.rs @@ -1,11 +1,14 @@ use crate::app_interface::Party; use alloc::rc::Rc; +#[cfg_attr(feature = "mock_data", mockall::automock)] pub trait BattlePartyTrait { fn party(&self) -> Party; } pub type BattleParty = Rc; +#[cfg(feature = "mock_data")] +pub type MockBattleParty = MockBattlePartyTrait; #[cfg(not(feature = "mock_data"))] mod implementation { diff --git a/pkmn_lib_interface/src/app_interface/dynamic_data/learned_move.rs b/pkmn_lib_interface/src/app_interface/dynamic_data/learned_move.rs index ec0dbf3..155cbb6 100755 --- a/pkmn_lib_interface/src/app_interface/dynamic_data/learned_move.rs +++ b/pkmn_lib_interface/src/app_interface/dynamic_data/learned_move.rs @@ -11,6 +11,7 @@ pub enum MoveLearnMethod { Level = 1, } +#[cfg_attr(feature = "mock_data", mockall::automock)] pub trait LearnedMoveTrait { fn move_data(&self) -> MoveData; fn learn_method(&self) -> MoveLearnMethod; @@ -19,6 +20,8 @@ pub trait LearnedMoveTrait { } pub type LearnedMove = Rc; +#[cfg(feature = "mock_data")] +pub type MockLearnedMove = MockLearnedMoveTrait; #[cfg(not(feature = "mock_data"))] mod implementation { diff --git a/pkmn_lib_interface/src/app_interface/dynamic_data/party.rs b/pkmn_lib_interface/src/app_interface/dynamic_data/party.rs index a6e1507..259df19 100755 --- a/pkmn_lib_interface/src/app_interface/dynamic_data/party.rs +++ b/pkmn_lib_interface/src/app_interface/dynamic_data/party.rs @@ -1,6 +1,9 @@ use crate::app_interface::Pokemon; +use alloc::boxed::Box; use alloc::rc::Rc; +use core::iter::IntoIterator; +#[cfg_attr(feature = "mock_data", mockall::automock)] pub trait PartyTrait { fn get_pokemon(&self, index: usize) -> Option; fn length(&self) -> usize; @@ -8,6 +11,18 @@ pub trait PartyTrait { pub type Party = Rc; +impl<'a> IntoIterator for &'a dyn PartyTrait { + type Item = Option; + type IntoIter = ExternIterator<'a, Self::Item>; + + fn into_iter(self) -> Self::IntoIter { + ExternIterator::new(self.length(), Box::new(move |i| self.get_pokemon(i))) + } +} + +#[cfg(feature = "mock_data")] +pub type MockParty = MockPartyTrait; + #[cfg(not(feature = "mock_data"))] mod implementation { use super::*; @@ -54,5 +69,6 @@ mod implementation { } } +use crate::utils::ExternIterator; #[cfg(not(feature = "mock_data"))] pub use implementation::*; diff --git a/pkmn_lib_interface/src/app_interface/dynamic_data/turn_choices.rs b/pkmn_lib_interface/src/app_interface/dynamic_data/turn_choices.rs index ea54ec1..70d6317 100755 --- a/pkmn_lib_interface/src/app_interface/dynamic_data/turn_choices.rs +++ b/pkmn_lib_interface/src/app_interface/dynamic_data/turn_choices.rs @@ -36,6 +36,7 @@ impl TurnChoice { } } +#[cfg_attr(feature = "mock_data", mockall::automock)] pub trait BaseTurnChoiceDataTrait { fn reference(&self) -> u32; fn user(&self) -> Pokemon; @@ -45,15 +46,20 @@ pub trait BaseTurnChoiceDataTrait { } pub type BaseTurnChoiceData = Rc; +#[cfg(feature = "mock_data")] +pub type MockBaseTurnChoiceData = MockBaseTurnChoiceDataTrait; +#[cfg_attr(feature = "mock_data", mockall::automock)] pub trait MoveTurnChoiceDataTrait { fn base(&self) -> BaseTurnChoiceData; fn used_move(&self) -> LearnedMove; fn target_side(&self) -> u8; fn target_index(&self) -> u8; fn priority(&self) -> i8; - fn move_script(&self) -> Option<&Box>; + fn move_script<'a>(&self) -> Option<&'a Box>; } +#[cfg(feature = "mock_data")] +pub type MockMoveTurnChoiceData = MockMoveTurnChoiceDataTrait; #[cfg(not(feature = "mock_data"))] mod implementation { diff --git a/pkmn_lib_interface/src/app_interface/static_data/move_data.rs b/pkmn_lib_interface/src/app_interface/static_data/move_data.rs index 8df5d6a..34b5511 100755 --- a/pkmn_lib_interface/src/app_interface/static_data/move_data.rs +++ b/pkmn_lib_interface/src/app_interface/static_data/move_data.rs @@ -29,6 +29,7 @@ pub enum MoveTarget { OnSelf, } +#[cfg_attr(feature = "mock_data", mockall::automock)] pub trait MoveDataTrait { fn name(&self) -> StringKey; fn move_type(&self) -> u8; @@ -42,6 +43,8 @@ pub trait MoveDataTrait { } pub type MoveData = Rc; +#[cfg(feature = "mock_data")] +pub type MockMoveData = MockMoveDataTrait; #[cfg(not(feature = "mock_data"))] mod implementation { diff --git a/pkmn_lib_interface/src/app_interface/string_key.rs b/pkmn_lib_interface/src/app_interface/string_key.rs index f668582..345567c 100755 --- a/pkmn_lib_interface/src/app_interface/string_key.rs +++ b/pkmn_lib_interface/src/app_interface/string_key.rs @@ -4,7 +4,7 @@ use crate::handling::Cacheable; use crate::{ExternRef, ExternalReferenceType}; use alloc::rc::Rc; use core::cell::RefCell; -use core::fmt::{Display, Formatter}; +use core::fmt::{Debug, Display, Formatter}; use cstr_core::CString; struct StringKeyInner { @@ -19,6 +19,12 @@ pub struct StringKey { data: Rc, } +impl Debug for StringKey { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "\"{}\"", self.str().to_str().unwrap()) + } +} + impl StringKey { #[cfg(not(feature = "mock_data"))] pub(crate) fn new(ptr: ExternRef) -> Self { @@ -185,4 +191,10 @@ mod test { } } } + + impl From<&str> for StringKey { + fn from(value: &str) -> Self { + StringKey::new(value) + } + } } diff --git a/pkmn_lib_interface/src/utils.rs b/pkmn_lib_interface/src/utils.rs index 2421dbe..841e7b0 100755 --- a/pkmn_lib_interface/src/utils.rs +++ b/pkmn_lib_interface/src/utils.rs @@ -1,3 +1,5 @@ +use alloc::boxed::Box; + #[macro_export] #[cfg(not(feature = "mock_data"))] macro_rules! println { ($($args:tt)*) => { crate::utils::print_raw(alloc::format!($($args)*).as_bytes()); } } @@ -77,3 +79,33 @@ mod implementation { #[cfg(not(feature = "mock_data"))] pub use implementation::*; + +pub struct ExternIterator<'a, T> { + len: usize, + index: usize, + getter: Box T + 'a>, +} + +impl<'a, T> ExternIterator<'a, T> { + pub fn new(len: usize, f: Box T + 'a>) -> Self { + Self { + len, + index: 0, + getter: f, + } + } +} + +impl<'a, T> Iterator for ExternIterator<'a, T> { + type Item = T; + + fn next(&mut self) -> Option { + let index = self.index; + if index >= self.len { + None + } else { + self.index += 1; + Some((self.getter)(index)) + } + } +}