diff --git a/data/pkmn_sets.py b/data/pkmn_sets.py index dcefdad6..3c4162ff 100644 --- a/data/pkmn_sets.py +++ b/data/pkmn_sets.py @@ -335,6 +335,18 @@ def get_all_remaining_sets( return remaining_sets + def get_all_possible_moves(self, pkmn: Pokemon): + if not self.pkmn_sets: + logger.warning("Called `predict_set` when pkmn_sets was empty") + return [] + + possible_moves = set() + for pkmn_set in self.get_pkmn_sets_from_pkmn_name(pkmn.name, pkmn.base_name): + for mv in pkmn_set.pkmn_moveset.moves: + possible_moves.add(mv) + + return list(possible_moves) + class _TeamDatasets(PokemonSets): def __init__(self): diff --git a/fp/battle_modifier.py b/fp/battle_modifier.py index b4d86769..f3f58821 100644 --- a/fp/battle_modifier.py +++ b/fp/battle_modifier.py @@ -573,12 +573,12 @@ def move(battle, split_msg): "zoroark" ) or side.find_pokemon_in_reserves("zoroarkhisui") - # in formats with known movesets we can deduce that there is a zoroark in front of us + # in battle factory we can deduce that there is a zoroark in front of us # if we see a move that is not in the known moveset and a zoroark is in the reserves if ( is_opponent(battle, split_msg) and zoroark_from_reserves is not None - and battle.battle_type in [constants.BATTLE_FACTORY] + and battle.battle_type == constants.BATTLE_FACTORY and move_name not in TeamDatasets.get_all_possible_moves(pkmn) and move_name in TeamDatasets.get_all_possible_moves(zoroark_from_reserves) and "from" not in split_msg[-1] @@ -593,6 +593,58 @@ def move(battle, split_msg): # the rest of this function uses `pkmn`, so we need to set it to the correct pkmn pkmn = zoroark_from_reserves + # in randombattles we can deduce that there is a zoroark in front of us + # if we see a move that is not in the known moveset, even if there is no + # zoroark is in the reserves + if ( + is_opponent(battle, split_msg) + and battle.battle_type == constants.RANDOM_BATTLE + and move_name not in RandomBattleTeamDatasets.get_all_possible_moves(pkmn) + and "from" not in split_msg[-1] + ): + actual_zoroark = None + zoroark_hisui = Pokemon("zoroarkhisui", 100) + zoroark_regular = Pokemon("zoroark", 100) + if ( + zoroark_from_reserves is not None + and move_name + in RandomBattleTeamDatasets.get_all_possible_moves(zoroark_from_reserves) + ): + actual_zoroark = zoroark_from_reserves + + elif ( + zoroark_from_reserves is None + and move_name + in RandomBattleTeamDatasets.get_all_possible_moves(zoroark_hisui) + ): + actual_zoroark = zoroark_hisui + actual_zoroark.level = RandomBattleTeamDatasets.predict_set( + actual_zoroark + ).pkmn_set.level + side.reserve.append(actual_zoroark) + + elif ( + zoroark_from_reserves is None + and move_name + in RandomBattleTeamDatasets.get_all_possible_moves(zoroark_regular) + ): + actual_zoroark = zoroark_regular + actual_zoroark.level = RandomBattleTeamDatasets.predict_set( + actual_zoroark + ).pkmn_set.level + side.reserve.append(actual_zoroark) + + if actual_zoroark is not None: + logger.info( + "{} using {} means it is {}".format( + pkmn.name, move_name, actual_zoroark.name + ) + ) + _switch_active_with_zoroark_from_reserves(side, actual_zoroark) + + # the rest of this function uses `pkmn`, so we need to set it to the correct pkmn + pkmn = actual_zoroark + if ( any(msg == "[from]Sleep Talk" for msg in split_msg) and battle.generation == "gen3" @@ -1342,7 +1394,7 @@ def immune(battle, split_msg): is_opponent(battle, split_msg) and zoroark_from_reserves is not None and not side.active.name.startswith("zoroark") - and battle.battle_type in [constants.BATTLE_FACTORY] + and battle.battle_type == constants.BATTLE_FACTORY and all_move_json[battle.user.last_used_move.move][constants.CATEGORY] != constants.STATUS and type_effectiveness_modifier( @@ -1376,6 +1428,82 @@ def immune(battle, split_msg): ) _switch_active_with_zoroark_from_reserves(side, zoroark_from_reserves) + elif ( + is_opponent(battle, split_msg) + and not side.active.name.startswith("zoroark") + and battle.battle_type == constants.RANDOM_BATTLE + and all_move_json[battle.user.last_used_move.move][constants.CATEGORY] + != constants.STATUS + and type_effectiveness_modifier( + all_move_json[battle.user.last_used_move.move][constants.TYPE], + side.active.types, + ) + != 0 + and "from" not in split_msg[-1] + ): + # check if pkmn has terastallized and gained immunity + # exit if it has + if ( + side.active.terastallized + and type_effectiveness_modifier( + all_move_json[battle.user.last_used_move.move][constants.TYPE], + [side.active.tera_type], + ) + == 0 + ): + return + + actual_zoroark = None + zoroark_hisui = Pokemon("zoroarkhisui", 100) + zoroark_regular = Pokemon("zoroark", 100) + if ( + zoroark_from_reserves is not None + and type_effectiveness_modifier( + all_move_json[battle.user.last_used_move.move][constants.TYPE], + zoroark_from_reserves.types, + ) + == 0 + ): + actual_zoroark = zoroark_from_reserves + + elif ( + zoroark_from_reserves is None + and type_effectiveness_modifier( + all_move_json[battle.user.last_used_move.move][constants.TYPE], + zoroark_hisui.types, + ) + == 0 + ): + actual_zoroark = zoroark_hisui + actual_zoroark.level = RandomBattleTeamDatasets.predict_set( + actual_zoroark + ).pkmn_set.level + side.reserve.append(actual_zoroark) + + elif ( + zoroark_from_reserves is None + and type_effectiveness_modifier( + all_move_json[battle.user.last_used_move.move][constants.TYPE], + zoroark_regular.types, + ) + == 0 + ): + actual_zoroark = zoroark_regular + actual_zoroark.level = RandomBattleTeamDatasets.predict_set( + actual_zoroark + ).pkmn_set.level + side.reserve.append(actual_zoroark) + + if actual_zoroark is not None: + logger.info( + "{} was immune to {} when it shouldn't be - it is {}".format( + pkmn.name, + battle.user.last_used_move.move, + actual_zoroark.name, + ) + ) + _switch_active_with_zoroark_from_reserves(side, actual_zoroark) + def _switch_active_with_zoroark_from_reserves( opponent_side: Battler, zoroark_from_reserves: Pokemon diff --git a/tests/test_battle_modifiers.py b/tests/test_battle_modifiers.py index eafa6071..46b66e92 100644 --- a/tests/test_battle_modifiers.py +++ b/tests/test_battle_modifiers.py @@ -3,7 +3,7 @@ from collections import defaultdict import constants -from data.pkmn_sets import TeamDatasets +from data.pkmn_sets import TeamDatasets, RandomBattleTeamDatasets from fp.helpers import calculate_stats from fp.battle import Battle @@ -1078,7 +1078,7 @@ def setUp(self): TeamDatasets.pkmn_sets = {} - def test_infer_zoroark_from_move_not_possible_on_pkmn(self): + def test_infer_zoroark_from_move_not_possible_on_pkmn_battle_factory(self): self.battle.battle_type = constants.BATTLE_FACTORY self.battle.generation = "gen9" TeamDatasets.initialize( @@ -1120,6 +1120,132 @@ def test_infer_zoroark_from_move_not_possible_on_pkmn(self): self.assertEqual([], self.battle.opponent.reserve[0].moves) self.assertEqual({}, dict(self.battle.opponent.reserve[0].boosts)) + def test_infer_zoroarkhisui_from_move_not_possible_on_pkmn_randbats_when_zoroark_unrevealed( + self, + ): + self.battle.battle_type = constants.RANDOM_BATTLE + self.battle.generation = "gen9" + RandomBattleTeamDatasets.initialize("gen9") + + self.battle.opponent.active = Pokemon("gyarados", 79) + self.battle.opponent.active.add_move("terablast") + self.battle.opponent.active.moves_used_since_switch_in.add("terablast") + self.battle.opponent.active.boosts[constants.SPECIAL_ATTACK] = 2 + + split_msg = [ + "", + "move", + "p2a: Gyarados", + "Poltergeist", + ] # Gyarados does not get Poltergeist in gen9randbats + move(self.battle, split_msg) + + self.assertEqual("zoroarkhisui", self.battle.opponent.active.name) + + # since this is randbats, just make sure we aren't setting level to 100 + # dont want to assert a specific level because the levels may change + self.assertNotEqual(100, self.battle.opponent.active.level) + + # terablast was used by gyarados since switching in, but should be re-associated with zoroarkhisui + # poltergeist is the move used to deduce it was a zoroarkhisui and should be added to zoroarkhisui's moves + self.assertEqual( + [Move("terablast"), Move("poltergeist")], + self.battle.opponent.active.moves, + ) + # the boosts that existed on gyarados should be on the active zoroarkhisui now + self.assertEqual( + {constants.SPECIAL_ATTACK: 2}, dict(self.battle.opponent.active.boosts) + ) + + self.assertEqual("gyarados", self.battle.opponent.reserve[0].name) + # terablast was used by gyarados since switching in so it should be dis-associated with gyarados + self.assertEqual([], self.battle.opponent.reserve[0].moves) + self.assertEqual({}, dict(self.battle.opponent.reserve[0].boosts)) + + def test_infer_zoroark_regular_from_move_not_possible_on_pkmn_randbats_when_zoroark_unrevealed( + self, + ): + self.battle.battle_type = constants.RANDOM_BATTLE + self.battle.generation = "gen9" + RandomBattleTeamDatasets.initialize("gen9") + + self.battle.opponent.active = Pokemon("gyarados", 79) + self.battle.opponent.active.add_move("terablast") + self.battle.opponent.active.moves_used_since_switch_in.add("terablast") + self.battle.opponent.active.boosts[constants.SPECIAL_ATTACK] = 2 + + split_msg = [ + "", + "move", + "p2a: Gyarados", + "Dark Pulse", + ] + move(self.battle, split_msg) + + self.assertEqual("zoroark", self.battle.opponent.active.name) + + # since this is randbats, just make sure we aren't setting level to 100 + # dont want to assert a specific level because the levels may change + self.assertNotEqual(100, self.battle.opponent.active.level) + + self.assertEqual( + [Move("terablast"), Move("darkpulse")], + self.battle.opponent.active.moves, + ) + self.assertEqual( + {constants.SPECIAL_ATTACK: 2}, dict(self.battle.opponent.active.boosts) + ) + + self.assertEqual("gyarados", self.battle.opponent.reserve[0].name) + self.assertEqual([], self.battle.opponent.reserve[0].moves) + self.assertEqual({}, dict(self.battle.opponent.reserve[0].boosts)) + + def test_does_not_infer_zoroark_if_move_can_be_on_active_pkmn( + self, + ): + self.battle.battle_type = constants.RANDOM_BATTLE + self.battle.generation = "gen9" + RandomBattleTeamDatasets.initialize("gen9") + + self.battle.opponent.active = Pokemon("tornadustherian", 79) + self.battle.opponent.active.add_move("terablast") + self.battle.opponent.active.moves_used_since_switch_in.add("terablast") + self.battle.opponent.active.boosts[constants.SPECIAL_ATTACK] = 2 + + split_msg = [ + "", + "move", + "p2a: Tornadus Therian", + "Nasty Plot", + ] # Tornadus Therian gets nastyplot so no inferring zoroark + move(self.battle, split_msg) + + self.assertEqual("tornadustherian", self.battle.opponent.active.name) + self.assertEqual([], self.battle.opponent.reserve) + + def test_does_not_infer_zoroark_when_struggle( + self, + ): + self.battle.battle_type = constants.RANDOM_BATTLE + self.battle.generation = "gen9" + RandomBattleTeamDatasets.initialize("gen9") + + self.battle.opponent.active = Pokemon("tornadustherian", 79) + self.battle.opponent.active.add_move("terablast") + self.battle.opponent.active.moves_used_since_switch_in.add("terablast") + self.battle.opponent.active.boosts[constants.SPECIAL_ATTACK] = 2 + + split_msg = [ + "", + "move", + "p2a: Tornadus Therian", + "Struggle", + ] + move(self.battle, split_msg) + + self.assertEqual("tornadustherian", self.battle.opponent.active.name) + self.assertEqual([], self.battle.opponent.reserve) + def test_does_not_infer_from_struggle(self): self.battle.battle_type = constants.BATTLE_FACTORY self.battle.generation = "gen9" @@ -4705,6 +4831,157 @@ def setUp(self): self.battle.username = self.username + def test_randbats_infer_zoroark_from_immunity_when_in_reserves(self): + self.battle.battle_type = constants.RANDOM_BATTLE + self.battle.generation = "gen9" + RandomBattleTeamDatasets.initialize("gen9") + + self.battle.opponent.reserve = [Pokemon("zoroarkhisui", 80)] + self.battle.opponent.reserve[0].add_move("nastyplot") + + self.battle.opponent.active = Pokemon("gyarados", 100) + self.battle.opponent.active.add_move("terablast") + self.battle.opponent.active.moves_used_since_switch_in.add("terablast") + self.battle.opponent.active.boosts[constants.SPECIAL_ATTACK] = 2 + + self.battle.user.last_used_move = LastUsedMove("weedle", "shadowball", 0) + split_msg = [ + "", + "-immune", + "p2a: Gyarados", + ] + immune(self.battle, split_msg) + + self.assertEqual("zoroarkhisui", self.battle.opponent.active.name) + self.assertNotEqual(100, self.battle.opponent.active.level) + + # nastyplot was previously revealed on zoroarkhisui + # terablast was used by gyarados since switching in, but should be re-associated with zoroarkhisui + self.assertEqual( + [Move("nastyplot"), Move("terablast")], + self.battle.opponent.active.moves, + ) + # the boosts that existed on gyarados should be on the active zoroarkhisui now + self.assertEqual( + {constants.SPECIAL_ATTACK: 2}, dict(self.battle.opponent.active.boosts) + ) + + self.assertEqual(1, len(self.battle.opponent.reserve)) + self.assertEqual("gyarados", self.battle.opponent.reserve[0].name) + # terablast was used by gyarados since switching in so it should be dis-associated with gyarados + self.assertEqual([], self.battle.opponent.reserve[0].moves) + self.assertEqual({}, dict(self.battle.opponent.reserve[0].boosts)) + + def test_randbats_infer_zoroarkhisui_from_immunity_when_not_in_reserves(self): + self.battle.battle_type = constants.RANDOM_BATTLE + self.battle.generation = "gen9" + RandomBattleTeamDatasets.initialize("gen9") + self.battle.opponent.reserve = [] + + self.battle.opponent.active = Pokemon("gyarados", 100) + self.battle.opponent.active.add_move("terablast") + self.battle.opponent.active.moves_used_since_switch_in.add("terablast") + self.battle.opponent.active.boosts[constants.SPECIAL_ATTACK] = 2 + + self.battle.user.last_used_move = LastUsedMove("weedle", "shadowball", 0) + split_msg = [ + "", + "-immune", + "p2a: Gyarados", + ] + immune(self.battle, split_msg) + + self.assertEqual("zoroarkhisui", self.battle.opponent.active.name) + self.assertNotEqual(100, self.battle.opponent.active.level) + + self.assertEqual( + [Move("terablast")], + self.battle.opponent.active.moves, + ) + self.assertEqual( + {constants.SPECIAL_ATTACK: 2}, dict(self.battle.opponent.active.boosts) + ) + + self.assertEqual(1, len(self.battle.opponent.reserve)) + self.assertEqual("gyarados", self.battle.opponent.reserve[0].name) + self.assertEqual([], self.battle.opponent.reserve[0].moves) + self.assertEqual({}, dict(self.battle.opponent.reserve[0].boosts)) + + def test_randbats_infer_zoroark_from_immunity_when_not_in_reserves(self): + self.battle.battle_type = constants.RANDOM_BATTLE + self.battle.generation = "gen9" + RandomBattleTeamDatasets.initialize("gen9") + self.battle.opponent.reserve = [] + + self.battle.opponent.active = Pokemon("gyarados", 100) + self.battle.opponent.active.add_move("terablast") + self.battle.opponent.active.moves_used_since_switch_in.add("terablast") + self.battle.opponent.active.boosts[constants.SPECIAL_ATTACK] = 2 + + self.battle.user.last_used_move = LastUsedMove("weedle", "psychic", 0) + split_msg = [ + "", + "-immune", + "p2a: Gyarados", + ] + immune(self.battle, split_msg) + + self.assertEqual("zoroark", self.battle.opponent.active.name) + self.assertNotEqual(100, self.battle.opponent.active.level) + + self.assertEqual( + [Move("terablast")], + self.battle.opponent.active.moves, + ) + self.assertEqual( + {constants.SPECIAL_ATTACK: 2}, dict(self.battle.opponent.active.boosts) + ) + + self.assertEqual(1, len(self.battle.opponent.reserve)) + self.assertEqual("gyarados", self.battle.opponent.reserve[0].name) + self.assertEqual([], self.battle.opponent.reserve[0].moves) + self.assertEqual({}, dict(self.battle.opponent.reserve[0].boosts)) + + def test_does_not_infer_zoroark_if_pkmn_terastallized_to_gain_immunity(self): + self.battle.battle_type = constants.RANDOM_BATTLE + self.battle.generation = "gen9" + RandomBattleTeamDatasets.initialize("gen9") + self.battle.opponent.reserve = [] + + self.battle.opponent.active = Pokemon("gyarados", 100) + self.battle.opponent.active.terastallized = True + self.battle.opponent.active.tera_type = "dark" + + self.battle.user.last_used_move = LastUsedMove("weedle", "psychic", 0) + split_msg = [ + "", + "-immune", + "p2a: Gyarados", + ] + immune(self.battle, split_msg) + + self.assertEqual("gyarados", self.battle.opponent.active.name) + self.assertEqual(0, len(self.battle.opponent.reserve)) + + def test_does_not_infer_zoroark_if_pkmn_naturally_immune(self): + self.battle.battle_type = constants.RANDOM_BATTLE + self.battle.generation = "gen9" + RandomBattleTeamDatasets.initialize("gen9") + self.battle.opponent.reserve = [] + + self.battle.opponent.active = Pokemon("urshifu", 100) + + self.battle.user.last_used_move = LastUsedMove("weedle", "psychic", 0) + split_msg = [ + "", + "-immune", + "p2a: Urshifu", + ] + immune(self.battle, split_msg) + + self.assertEqual("urshifu", self.battle.opponent.active.name) + self.assertEqual(0, len(self.battle.opponent.reserve)) + def test_infers_zoroark_from_immunity_that_pkmn_does_not_have(self): self.battle.battle_type = constants.BATTLE_FACTORY self.battle.generation = "gen9"