diff --git a/conftest.py b/conftest.py index 36b693b8b..f94ec95f9 100644 --- a/conftest.py +++ b/conftest.py @@ -39,7 +39,7 @@ def raw_team_data(): def example_request(): with open(os.path.join(FIXTURE_DIR, "example_request.json")) as f: return orjson.loads(f.read()) - + @fixture def force_switch_example_request(): diff --git a/src/poke_env/environment/move.py b/src/poke_env/environment/move.py index cc9e78ae6..9a0ca6920 100644 --- a/src/poke_env/environment/move.py +++ b/src/poke_env/environment/move.py @@ -7,6 +7,7 @@ from poke_env.environment.field import Field from poke_env.environment.move_category import MoveCategory from poke_env.environment.pokemon_type import PokemonType +from poke_env.environment.side_condition import SideCondition from poke_env.environment.status import Status from poke_env.environment.target import Target from poke_env.environment.weather import Weather @@ -20,11 +21,13 @@ "spikyshield", "kingsshield", "banefulbunker", + "burningbulwark", "obstruct", "maxguard", + "silktrap", } _SIDE_PROTECT_MOVES = {"wideguard", "quickguard", "matblock"} -_PROTECT_COUNTER_MOVES = _PROTECT_MOVES | _SIDE_PROTECT_MOVES +_PROTECT_COUNTER_MOVES = _PROTECT_MOVES | {"wideguard", "quickguard", "endure"} class Move: @@ -587,12 +590,15 @@ def self_switch(self) -> Union[str, bool]: return self.entry.get("selfSwitch", False) @property - def side_condition(self) -> Optional[str]: + def side_condition(self) -> Optional[SideCondition]: """ :return: Side condition inflicted by the move. - :rtype: str | None + :rtype: SideCondition | None """ - return self.entry.get("sideCondition", None) + sc = self.entry.get("sideCondition", None) + if sc is not None: + sc = SideCondition.from_data(sc) + return sc @property def sleep_usable(self) -> bool: diff --git a/src/poke_env/environment/side_condition.py b/src/poke_env/environment/side_condition.py index ec0780087..dec8c9944 100644 --- a/src/poke_env/environment/side_condition.py +++ b/src/poke_env/environment/side_condition.py @@ -4,6 +4,7 @@ import logging from enum import Enum, auto, unique +from typing import Dict @unique @@ -12,6 +13,7 @@ class SideCondition(Enum): UNKNOWN = auto() AURORA_VEIL = auto() + CRAFTY_SHIELD = auto() FIRE_PLEDGE = auto() G_MAX_CANNONADE = auto() G_MAX_STEELSURGE = auto() @@ -21,7 +23,9 @@ class SideCondition(Enum): GRASS_PLEDGE = auto() LIGHT_SCREEN = auto() LUCKY_CHANT = auto() + MATBLOCK = auto() MIST = auto() + QUICK_GUARD = auto() REFLECT = auto() SAFEGUARD = auto() SPIKES = auto() @@ -30,6 +34,7 @@ class SideCondition(Enum): TAILWIND = auto() TOXIC_SPIKES = auto() WATER_PLEDGE = auto() + WIDE_GUARD = auto() def __str__(self) -> str: return f"{self.name} (side condition) object" @@ -59,6 +64,59 @@ def from_showdown_message(message: str): ) return SideCondition.UNKNOWN + @staticmethod + def from_data(message: str): + """Returns the SideCondition object corresponding to the string in static data. + + :param message: The message to convert. + :type message: str + :return: The corresponding SideCondition object. + :rtype: SideCondition + """ + message = message.replace("_", "") + message = message.replace(" ", "") + message = message.replace("-", "") + message = message.upper() + + try: + return _FROM_DATA[message] + except KeyError: + logging.getLogger("poke-env").warning( + "Unexpected SideCondition '%s' received. SideCondition.UNKNOWN will be used " + "instead. If this is unexpected, please open an issue at " + "https://github.com/hsahovic/poke-env/issues/ along with this error " + "message and a description of your program.", + message, + ) + return SideCondition.UNKNOWN + # SideCondition -> Max useful stack level STACKABLE_CONDITIONS = {SideCondition.SPIKES: 3, SideCondition.TOXIC_SPIKES: 2} + +_FROM_DATA: Dict[str, SideCondition] = { + "UNKNOWN": SideCondition.UNKNOWN, + "AURORAVEIL": SideCondition.AURORA_VEIL, + "CRAFTYSHIELD": SideCondition.CRAFTY_SHIELD, + "FIREPLEDGE": SideCondition.FIRE_PLEDGE, + "GMAXCANNONADE": SideCondition.G_MAX_CANNONADE, + "GMAXSTEELSURGE": SideCondition.G_MAX_STEELSURGE, + "GMAXVINELASH": SideCondition.G_MAX_VINE_LASH, + "GMAXVOLCALITH": SideCondition.G_MAX_VOLCALITH, + "GMAXWILDFIRE": SideCondition.G_MAX_WILDFIRE, + "GRASSPLEDGE": SideCondition.GRASS_PLEDGE, + "LIGHTSCREEN": SideCondition.LIGHT_SCREEN, + "LUCKYCHANT": SideCondition.LUCKY_CHANT, + "MATBLOCK": SideCondition.MATBLOCK, + "MIST": SideCondition.MIST, + "QUICKGUARD": SideCondition.QUICK_GUARD, + "REFLECT": SideCondition.REFLECT, + "SAFEGUARD": SideCondition.SAFEGUARD, + "SPIKES": SideCondition.SPIKES, + "STEALTHROCK": SideCondition.STEALTH_ROCK, + "STICKYWEB": SideCondition.STICKY_WEB, + "TAILWIND": SideCondition.TAILWIND, + "TOXICSPIKES": SideCondition.TOXIC_SPIKES, + "WATERPLEDGE": SideCondition.WATER_PLEDGE, + "WIDEGUARD": SideCondition.WIDE_GUARD, +} diff --git a/unit_tests/environment/test_move.py b/unit_tests/environment/test_move.py index c1f8d127d..349205e9b 100644 --- a/unit_tests/environment/test_move.py +++ b/unit_tests/environment/test_move.py @@ -1,4 +1,5 @@ import copy +import itertools from poke_env.data import GenData from poke_env.environment import ( @@ -8,16 +9,17 @@ Move, MoveCategory, PokemonType, + SideCondition, Status, Target, Weather, ) -def move_generator(): - for move in GenData.from_gen(8).moves: - yield Move(move, gen=8) - yield Move("z" + move, gen=8) +def move_generator(gen=8): + for move in GenData.from_gen(gen).moves: + yield Move(move, gen=gen) + yield Move("z" + move, gen=gen) def test_accuracy(): @@ -390,11 +392,38 @@ def test_side_condition(): flame_thrower = Move("flamethrower", gen=8) quick_guard = Move("quickguard", gen=8) - assert quick_guard.side_condition == "quickguard" + assert quick_guard.side_condition == SideCondition.QUICK_GUARD assert flame_thrower.side_condition is None - for move in move_generator(): - assert isinstance(move.side_condition, str) or move.side_condition is None + assert SideCondition.from_data("i dont know") == SideCondition.UNKNOWN + assert SideCondition.from_showdown_message("i dont know") == SideCondition.UNKNOWN + + # Make sure we know every move that has a SideCondition is one we know + side_conditions = set() + for move in itertools.chain(move_generator(8), move_generator(9)): + if move.side_condition: + assert move.side_condition != SideCondition.UNKNOWN + side_conditions.add(move.side_condition) + + # SideConditions that don't exist in moves data + to_exclude = set( + [ + SideCondition.FIRE_PLEDGE, + SideCondition.WATER_PLEDGE, + SideCondition.GRASS_PLEDGE, + SideCondition.UNKNOWN, + SideCondition.G_MAX_WILDFIRE, + SideCondition.G_MAX_VOLCALITH, + SideCondition.G_MAX_VINE_LASH, + SideCondition.G_MAX_STEELSURGE, + SideCondition.G_MAX_CANNONADE, + ] + ) + + # Make sure we don't have any SideConditions that don't exist in moves data + assert side_conditions == set( + list(filter(lambda x: x not in to_exclude, SideCondition)) + ) def test_sleep_usable():