Example #1
0
class BotClient(showdown.Client):
    health_regex = re.compile(r'(?P<numerator>[0-9]+)/(?P<denominator>[0-9]+)')

    def __init__(self,
                 name='',
                 password='',
                 loop=None,
                 max_room_logs=5000,
                 server_id='showdown',
                 server_host=None,
                 expected_opponent=None,
                 team=None,
                 challenge=False,
                 runType=RunType.Iterations,
                 runTypeData=1,
                 agent=None,
                 print_stats=False,
                 trainer=False,
                 save_model=True,
                 should_write_replay=False):
        self.should_write_replay = should_write_replay
        self.done = False

        if expected_opponent == None:
            raise Exception("No expected opponent found in arguments")
        else:
            self.expected_opponent = expected_opponent
        if team == None:
            raise Exception("No team found in arguments")
        else:
            self.team_text = team

        self.iterations_run = 0

        self.runType = runType
        if self.runType == RunType.Iterations:
            self.iterations = runTypeData
        elif self.runType == RunType.Epochs:
            self.epochs = runTypeData

        if agent == None:
            self.agent = RandomAgent()
        else:
            self.agent = agent
        self.state_vl = None
        self.action = None

        self.logs_dir = LOGS_DIR
        if not os.path.exists(self.logs_dir):
            os.mkdir(self.logs_dir)

        self.replay_memory_dir = REPLAY_MEMORY_DIR
        if not os.path.exists(self.replay_memory_dir):
            os.mkdir(self.replay_memory_dir)
        self.datestring = datetime.now().strftime('%y-%m-%d-%H-%M-%S')
        self.update_log_paths()

        self.challenge = challenge
        self.has_challenged = False

        # flag used to detect the first 'request' inp_type
        # first request is used to initialize moves for gamestate
        # Rreset to false for every battle
        self.is_first_request = True

        # Keep a track of zmoves used, as each pokemon can use z moves only once
        # per battle, we update each zmove here used per battle so it can't be used
        # again. Reset to empty list after battle ends
        # type: [{pokemon_name : zmove}]
        self.zmoves_tracker = []

        self.wins = 0
        self.losses = 0
        self.print_stats = print_stats

        self.trainer = trainer

        super().__init__(name=name,
                         password=password,
                         loop=loop,
                         max_room_logs=max_room_logs,
                         server_id=server_id,
                         server_host=server_host)

    def update_log_paths(self):
        self.log_file = os.path.join(
            self.logs_dir,
            f'{self.datestring}_Iteration{self.iterations_run}.txt')
        self.agent.log_path = self.log_file
        self.agent.replay_memory_path = os.path.join(
            self.logs_dir, 'replay_memory',
            f'{self.datestring}_Iteration{self.iterations_run}_replaymemory.txt'
        )
        self.agent.model_path = os.path.join(
            self.logs_dir,
            f'{self.datestring}_Iteration{self.iterations_run}.model')

    def write_replay_memory(self):
        self.agent.write_replay_memory()

    def log(self, *args):
        now = datetime.now()
        l = [str(arg) for arg in args]
        string = ' '.join(l)
        with open(self.log_file, 'a') as fd:
            fd.write(f'[{datetime.now()}] {string}\n')

    def should_play_new_game(self):
        if self.runType == RunType.Iterations:
            return self.iterations_run < self.iterations
        elif self.runType == RunType.Epochs:
            return self.agent.current_epoch < self.epochs
        elif self.runType == RunType.Forever:
            return True
        else:
            self.log(f'Unexpected run type {self.runType}')
            raise Exception('Unexpected run type')

    def save_replay(self, room_obj):
        replays_dir = os.path.join(BOT_DIR, 'replays')
        if not os.path.exists(replays_dir):
            os.mkdir(replays_dir)
        replay_file = f'{self.datestring}_Iteration{self.iterations_run}.html'
        with open(os.path.join(replays_dir, replay_file), 'wb') as f:
            f.write(util.get_replay_header())
            joined = '\n'.join(room_obj.logs)
            f.write('\n'.join(room_obj.logs).encode('utf-8'))
            f.write(util.get_replay_footer())

    @staticmethod
    def get_team_info(data):
        return data['side']['pokemon']

    async def switch_pokemon(self, room_obj, data):
        '''
		For use with forceswitch scenarios
		'''
        try:
            valid_actions = []
            team_info = self.get_team_info(data)
            for pokemon_index, pokemon_info in enumerate(team_info):
                fainted = 'fnt' in pokemon_info.get('condition')
                if (not pokemon_info.get('active', False) and not fainted):

                    pokemon_name = self.gs.pokemon_name_clean(
                        pokemon_info['details'])
                    valid_actions.append(
                        (pokemon_index + 1, pokemon_name, ActionType.Switch))

            self.log(f'valid_actions: {valid_actions}')

            action_index, action_string, action_type, _ = \
             self.agent.get_action(self.gs.vector_list, valid_actions)
        except Exception as err:
            self.log_error(err)

        if action_type == ActionType.Switch:
            await room_obj.switch(action_index)
        else:
            self.log(f'Unexpected action type {action_type}')

    async def take_action(self, room_obj, data, delay=0):
        await asyncio.sleep(delay)
        #NOTE: delay is here to make sure we get all the data before taking action
        try:
            moves = data.get('active')[0].get('moves')
            valid_actions = []
            for move_index, move_data in enumerate(moves):
                if ((move_data.get('pp', 0) > 0
                     and not move_data.get('disabled'))
                        or move_data.get('move') == 'Struggle'):

                    valid_actions.append(
                        (move_index + 1, clean_move_name(move_data['move']),
                         ActionType.Move))
            move_count = len(moves)

            team_info = self.get_team_info(data)
            for pokemon_index, pokemon_info in enumerate(team_info):
                fainted = 'fnt' in pokemon_info.get('condition')
                if (not pokemon_info.get('active', False) and not fainted):

                    pokemon_name = self.gs.pokemon_name_clean(
                        pokemon_info['details'])
                    valid_actions.append(
                        (pokemon_index + 1, pokemon_name, ActionType.Switch))

            self.log(f'valid_actions: {valid_actions}')

            action_index, action_string, action_type, self.action = \
             self.agent.get_action(self.gs.vector_list, valid_actions)
        except Exception as err:
            self.log_error(err)

        if action_type == ActionType.Move:
            await room_obj.move(action_index)
        elif action_type == ActionType.Switch:
            await room_obj.switch(action_index)
        else:
            self.log(f'Unexpected action type {action_type}')

    def own_pokemon(self, pokemon_data):
        return pokemon_data.startswith(self.position)

    @staticmethod
    def get_owner(pokemon_data):
        return pokemon_data.split(':')[0].strip()

    @staticmethod
    def get_pokemon(pokemon_data):
        return pokemon_data.split(':')[1].strip()

    def add_status(self, player_name, pokemon_name, status):
        self.gs.set_status(player_name, pokemon_name, status)

    def remove_status(self, player_name, pokemon_name, status):
        self.gs.remove_status(player_name, pokemon_name, status)

    async def challenge_expected(self):
        self.log("Challenging {}".format(self.expected_opponent))
        await self.cancel_challenge()
        time.sleep(1)
        await self.send_challenge(self.expected_opponent, self.team_text,
                                  'gen7ou')

    def set_and_check_team(self, player, team):
        self.gs.set_team(player, team)
        for position, member in enumerate(team):
            vector_pokemon = self.gs.check_team_position(player, position)

            if member != vector_pokemon:
                self.log('WARNING: mismatched pokemon')
            else:
                types = TYPE_MAP.get(vector_pokemon)
                self.log(f'{vector_pokemon} has types from TYPE_MAP: {types}')
                self.gs.set_types(player, vector_pokemon, types)
                has_types = self.gs.check_types(player, vector_pokemon)
                if set(has_types) != set(types):
                    self.log(f'WARNING: {vector_pokemon} has unexpected types')
                self.log(f'{vector_pokemon} has types {has_types}')

    def log_error(self, err):
        self.log('ERROR')
        self.log(''.join(traceback.format_tb(err.__traceback__)))

    def update_replay_memory(self, transition):
        self.agent.update_replay_memory(transition)

    async def on_receive(self, room_id, inp_type, params):
        try:
            self.log(f'Input type: {inp_type}')
            self.log(f'Params: {params}')
        except Exception as err:
            self.log_error(err)

        try:
            room_obj = self.rooms.get(room_id)
            if room_obj and room_obj.id.startswith('battle-'):

                if inp_type == 'poke':
                    owner = params[0]
                    pokename = GameState.pokemon_name_clean(params[1])
                    if owner == self.position:
                        self.team.append(pokename)
                    else:
                        self.opp_team.append(pokename)

                    if (len(self.team) == self.teamsize
                            and len(self.opp_team) == self.opp_teamsize):

                        self.gs = GameState()
                        self.set_and_check_team(GameState.Player.one,
                                                self.team)
                        self.set_and_check_team(GameState.Player.two,
                                                self.opp_team)

                        self.gs.init_health(GameState.Player.one)
                        self.gs.init_health(GameState.Player.two)

                        self.gs.reset_boosts(GameState.Player.one)
                        self.gs.reset_boosts(GameState.Player.two)

                        #NOTE: Select starting pokemon here
                        valid_actions = []
                        for pokemon_index, pokemon_name in enumerate(
                                self.team):
                            pokemon_name = self.gs.pokemon_name_clean(
                                pokemon_name)
                            valid_actions.append(
                                (pokemon_index + 1, pokemon_name,
                                 ActionType.Switch))

                        action_index, action_string, action_type, _ = \
                         self.agent.get_action(self.gs.vector_list, valid_actions)

                        if action_type == ActionType.Switch:
                            await room_obj.start_poke(action_index)
                        else:
                            self.log(f'Unexpected action type {action_type}')

                elif inp_type == 'teamsize':
                    position = params[0]
                    if position == self.position:
                        self.teamsize = int(params[1])
                        self.team = []
                    else:
                        self.opp_teamsize = int(params[1])
                        self.opp_team = []

                elif inp_type == 'player':
                    name = params[1]
                    position = params[0]
                    if name == self.name:
                        self.position = position

                elif inp_type == 'turn':
                    self.turn_number = int(params[0])

                    self.log(f'Weather: {self.gs.all_weather()}')

                    self.log(
                        f'P1 Boosts: {self.gs.all_boosts(GameState.Player.one)}'
                    )
                    self.log(
                        f'P2 Boosts: {self.gs.all_boosts(GameState.Player.two)}'
                    )

                    active_pokemon = self.gs.all_active(GameState.Player.one)
                    self.log(f'P1 active: {active_pokemon}')
                    if len(active_pokemon) > 1:
                        self.log('ERROR: More than one active pokemon')

                    active_pokemon = self.gs.all_active(GameState.Player.two)
                    self.log(f'P2 active: {active_pokemon}')
                    if len(active_pokemon) > 1:
                        self.log('ERROR: More than one active pokemon')

                    self.log(
                        f'P1 health: {self.gs.all_health(GameState.Player.one)}'
                    )
                    self.log(
                        f'P2 health: {self.gs.all_health(GameState.Player.two)}'
                    )

                    self.log(
                        f'P1 fainted: {self.gs.all_fainted(GameState.Player.one)}'
                    )
                    self.log(
                        f'P2 fainted: {self.gs.all_fainted(GameState.Player.two)}'
                    )

                    self.log(
                        f'P1 types: {self.gs.all_types(GameState.Player.one)}')
                    self.log(
                        f'P2 types: {self.gs.all_types(GameState.Player.two)}')

                    self.log(
                        f'P1 statuses: {self.gs.all_statuses(GameState.Player.one)}'
                    )
                    self.log(
                        f'P2 statuses: {self.gs.all_statuses(GameState.Player.two)}'
                    )

                    self.log(
                        f'P1 moves: {self.gs.all_moves(GameState.Player.one)}')
                    self.log(
                        f'P2 moves: {self.gs.all_moves(GameState.Player.two)}')

                    self.log(
                        f'P1 active slot check: {self.gs.check_active_slot(GameState.Player.one)}'
                    )
                    self.log(
                        f'P2 active slot check: {self.gs.check_active_slot(GameState.Player.two)}'
                    )

                    if self.turn_number == 1:
                        self.state_vl = self.gs.vector_list
                        reward = 0
                    else:
                        last_state = [element for element in self.state_vl]
                        self.state_vl = [
                            element for element in self.gs.vector_list
                        ]
                        done = False

                        reward = calculate_reward(self, last_state,
                                                  self.state_vl)
                        transition = (last_state, self.action, reward,
                                      self.state_vl, done)
                        self.update_replay_memory(transition)
                        self.agent.train(False)
                    self.log(f'This transition\'s reward was {reward}')

                elif inp_type == 'request':
                    json_string = params[0]
                    data = json.loads(json_string)
                    wait = data.get('wait', False)
                    if not wait:
                        team_info = self.get_team_info(data)
                        self.last_request_data = data

                        # Initialize all available pokemon moves for game stats
                        if self.is_first_request:
                            for pokemon_info in team_info:
                                pokemon_name = GameState.pokemon_name_clean(
                                    pokemon_info['details'])
                                for move_name in pokemon_info['moves']:
                                    # Initially PP = Max PP, so pseudo PP, Max PP values to set move
                                    # as PP, Max PP are available for only active Pokemons
                                    self.gs.set_move(GameState.Player.one,
                                                     pokemon_name, move_name,
                                                     1.0, 1.0)

                            self.is_first_request = False

                        # Update PP for the active pokemon only
                        else:
                            for pokemon_info in team_info:
                                pokemon_name = GameState.pokemon_name_clean(
                                    pokemon_info['details'])
                                if pokemon_info['active'] == True:
                                    if 'active' in data:
                                        moves = data['active'][0]['moves']
                                        for move in moves:
                                            self.gs.set_move(
                                                GameState.Player.one,
                                                pokemon_name, move['id'],
                                                move['pp'], move['maxpp'])

                                        if 'canZMove' in data['active'][0]:
                                            if pokemon_name not in self.zmoves_tracker:
                                                zmove_id = util.move_name_to_id(
                                                    data['active'][0]
                                                    ['canZMove'][1]['move'])
                                                self.gs.set_move(
                                                    GameState.Player.one,
                                                    pokemon_name, zmove_id,
                                                    1.0, 1.0)

                        # Update pokemon stat and items for game state
                        for pokemon_info in team_info:
                            pokemon_name = GameState.pokemon_name_clean(
                                pokemon_info['details'])
                            stats = pokemon_info['stats']
                            for stat_name in stats:
                                self.gs.set_stat(GameState.Player.one,
                                                 pokemon_name, stat_name,
                                                 stats[stat_name])

                            item = pokemon_info['item']
                            # If item key has empty string if no item possesed by Pokemon,
                            # item could have been knocked out or used my Pokemon
                            if item == '':
                                self.gs.clear_all_items(
                                    GameState.Player.one, pokemon_name)

                            # Else update item with the current item even if there is no change
                            # clear old item and set new item as a Pokemon can possess only an item at a time
                            else:
                                self.gs.clear_all_items(
                                    GameState.Player.one, pokemon_name)
                                self.gs.set_item(GameState.Player.one,
                                                 pokemon_name, item)

                        force_switch = data.get('forceSwitch', [False])[0]
                        if force_switch == True:
                            await self.switch_pokemon(room_obj, data)

                        else:
                            await self.take_action(room_obj, data, delay=0.3)

                elif inp_type == '-status':
                    pokemon_data = params[0]
                    pokemon_name = self.get_pokemon(pokemon_data)
                    #TODO: remove this hack and have a good way of handling
                    #TODO: detailed vs. non-detailed pokemon names
                    pokemon_name = hack_name(pokemon_name)
                    status = params[1]
                    if self.own_pokemon(pokemon_data):
                        self.add_status(GameState.Player.one, pokemon_name,
                                        status)

                    else:
                        self.add_status(GameState.Player.two, pokemon_name,
                                        status)

                elif inp_type == '-start':
                    pokemon_data = params[0]
                    pokemon_name = self.get_pokemon(pokemon_data)
                    status = params[1]
                    if self.own_pokemon(pokemon_data):
                        self.add_status(GameState.Player.one, pokemon_name,
                                        status)

                    else:
                        self.add_status(GameState.Player.two, pokemon_name,
                                        status)

                elif inp_type == '-end':
                    pokemon_data = params[0]
                    pokemon_name = self.get_pokemon(pokemon_data)
                    status = params[1]
                    if self.own_pokemon(pokemon_data):
                        self.remove_status(GameState.Player.one, pokemon_name,
                                           status)

                    else:
                        self.remove_status(GameState.Player.two, pokemon_name,
                                           status)

                elif inp_type == '-curestatus':
                    pokemon_data = params[0]
                    pokemon_name = self.get_pokemon(pokemon_data)
                    status = params[1]
                    if self.own_pokemon(pokemon_data):
                        self.remove_status(GameState.Player.one, pokemon_name,
                                           status)

                    else:
                        self.remove_status(GameState.Player.two, pokemon_name,
                                           status)

                elif inp_type == 'switch':
                    name_with_owner = params[0]
                    name_with_details = params[1]
                    my_pokemon = self.own_pokemon(name_with_owner)

                    volatile_statuses = ['confusion', 'curse']
                    if my_pokemon:
                        pokemon_name = self.active_pokemon
                        gs_player = GameState.Player.one
                    else:
                        pokemon_name = self.opp_active_pokemon
                        gs_player = GameState.Player.two

                    self.gs.reset_boosts(gs_player)

                    if pokemon_name != None:
                        for volatile_status in volatile_statuses:
                            self.remove_status(gs_player, pokemon_name,
                                               volatile_status)

                    new_active_name = GameState.pokemon_name_clean(
                        name_with_details)
                    if not my_pokemon:
                        self.opp_active_pokemon = new_active_name
                        self.gs.set_active(GameState.Player.two,
                                           self.opp_active_pokemon)
                        if not self.gs.check_active(GameState.Player.two,
                                                    self.opp_active_pokemon):

                            self.log(f'WARNING: {self.opp_active_pokemon}'
                                     ' was not active as expected')
                    else:
                        self.active_pokemon = new_active_name
                        self.gs.set_active(GameState.Player.one,
                                           self.active_pokemon)
                        if not self.gs.check_active(GameState.Player.one,
                                                    self.active_pokemon):

                            self.log(f'WARNING: {self.active_pokemon}'
                                     ' was not active as expected')

                elif inp_type == '-sidestart':
                    position = params[0].split(':')[0]
                    hazard = params[1].lstrip('move: ')
                    if position.startswith(self.position):
                        self.gs.increment_entry_hazard(GameState.Player.one,
                                                       hazard)

                    else:
                        self.gs.increment_entry_hazard(GameState.Player.two,
                                                       hazard)

                elif inp_type == '-sideend':
                    position = params[0].split(':')[0]
                    hazard = params[1]
                    if position.startswith(self.position):
                        self.gs.clear_entry_hazard(GameState.Player.one,
                                                   hazard)

                    else:
                        self.gs.clear_entry_hazard(GameState.Player.two,
                                                   hazard)

                elif inp_type == 'error':
                    self.save_replay(room_obj)
                    if params[0].startswith('[Invalid choice]'):
                        if ("Can't switch: You can't switch to an active Pokémon"
                                in params[0]):
                            await self.switch_pokemon(room_obj,
                                                      self.last_request_data)
                        else:
                            await self.take_action(room_obj,
                                                   self.last_request_data)

                elif inp_type == 'win':
                    done = True

                    winner = params[0]
                    if winner == self.name:
                        self.wins += 1
                        self.log("We won")
                        reward = 96
                    else:
                        self.losses += 1
                        self.log("We lost")
                        reward = -96

                    last_state = [element for element in self.state_vl]
                    self.state_vl = self.gs.vector_list

                    transition = (last_state, self.action, reward,
                                  self.state_vl, done)
                    self.update_replay_memory(transition)
                    trained = self.agent.train(True)
                    if trained and self.save_model:
                        self.log(f'Trained')
                        path = self.agent.save_model()
                        old_epoch = self.agent.current_epoch
                        epoch = self.agent.update_epoch()
                        if old_epoch < epoch:
                            self.log('Saving epoch model')
                            epoch_model_path = os.path.join(
                                self.logs_dir, f'Epoch{epoch}.model')
                            self.agent.save_model(path=epoch_model_path)
                            self.agent.restart_epoch()
                    else:
                        self.log(f'Not trained')

                    await room_obj.leave()
                    self.iterations_run += 1
                    self.update_log_paths()
                    if self.should_play_new_game():
                        self.is_first_request = True
                        self.zmoves_tracker = []
                        self.log("Starting iteration {}".format(
                            self.iterations_run))
                        if self.challenge:
                            time.sleep(5)
                            await self.challenge_expected()
                    else:
                        if self.print_stats:
                            win_ratio = (float(self.wins) /
                                         float(self.wins + self.losses))
                            print(f'Win ratio: {win_ratio}')
                        self.done = True
                        if self.should_write_replay:
                            self.write_replay_memory()
                        sys.exit(0)

                elif inp_type == '-ability':
                    # might work to track some abilities but so far weather abilities and other abilities
                    # are only made known when a specific game action is made.
                    self.log('ability')
                    #self.log(params)

                elif inp_type == 'faint':
                    player = params[0][0:2]
                    pokemon = params[0][4:].strip()

                    #TODO: remove this hack and have a good way of handling
                    #TODO: detailed vs. non-detailed pokemon names
                    pokemon = hack_name(pokemon)

                    self.log(f'{player}\'s {pokemon} has fainted')
                    if player == self.position:
                        gs_player = GameState.Player.one
                    else:
                        gs_player = GameState.Player.two

                    self.gs.set_fainted(gs_player, pokemon)
                    self.log('set_fainted called successfully')
                    if not self.gs.check_fainted(gs_player, pokemon):
                        self.log('ERROR: Gamestate fainted was not set as '
                                 f'expected for {pokemon}')

                elif inp_type == '-damage':
                    if len(params) <= 3:
                        player = params[0][0:2]
                        pokemon = params[0].lstrip('p1a: ').lstrip(
                            'p2a: ').strip()
                        pokemon = hack_name(pokemon)
                        health_data = params[1]
                        match = self.health_regex.search(health_data)
                        if match:
                            normalized_health = (
                                float(match.group('numerator')) /
                                float(match.group('denominator')))
                            self.log(
                                f'{pokemon} has health {normalized_health}')
                            if player == self.position:
                                gs_player = GameState.Player.one
                            else:
                                gs_player = GameState.Player.two

                            self.gs.set_health(gs_player, pokemon,
                                               normalized_health)
                        else:
                            self.log(
                                f'Could not track health for pokemon {pokemon}'
                            )

                    # this section to track the enemy abilities
                    elif len(params) == 4:
                        pokemon = params[3].strip('[of] p1a: ')
                        ability = params[2].strip('[from] ability: ')
                        # self.enemy_state.update_abilities(pokemon, ability)
                        self.log('Pokemon: ', pokemon, 'Enemy Ability: ',
                                 self.enemy_state.team_abilities[pokemon])

                elif inp_type == '-heal':
                    player = params[0][0:2]
                    pokemon = params[0].lstrip('p1a: ').lstrip('p2a: ').strip()
                    pokemon = hack_name(pokemon)
                    health_data = params[1]

                    match = self.health_regex.search(health_data)
                    if match:
                        normalized_health = (float(match.group('numerator')) /
                                             float(match.group('denominator')))
                        self.log(f'{pokemon} has health {normalized_health}')
                        if player == self.position:
                            gs_player = GameState.Player.one
                        else:
                            gs_player = GameState.Player.two

                        self.gs.set_health(gs_player, pokemon,
                                           normalized_health)
                    else:
                        self.log(
                            f'Could not track health for pokemon {pokemon}')

                elif inp_type == '-mega':
                    if ('p1a' in str(params[0])):
                        # Opposing player Mega
                        # TODO: Add which pokemon used
                        pokemon = params[0].strip(
                            'p1a: ')  # just easier to read this way
                        # self.enemy_state.update_team_mega(pokemon)
                        self.opp_mega = True
                        self.log('Enemy Mega Active: ',
                                 self.enemy_state.team_mega[pokemon])
                    else:
                        # AI uses mega
                        self.mega = True

                elif inp_type == '-item':
                    '''
					-item|POKEMON|ITEM
					The ITEM held by the POKEMON has been changed or revealed due to a move or ability. 
					In addition, Air Balloon reveals itself when the Pokémon holding it switches in, so it will also cause this message to appear.
					'''
                    position = self.get_owner(params)
                    pokemon_name = self.get_pokemon(params)
                    item = util.item_name_to_id(params[1])
                    if position.startswith(self.position):
                        self.gs.set_item(GameState.Player.one, pokemon_name,
                                         item)

                    else:
                        self.gs.set_item(GameState.Player.two, pokemon_name,
                                         item)

                elif inp_type == '-enditem':
                    '''
					-enditem|POKEMON|ITEM
					The ITEM held by POKEMON has been destroyed, and it now holds no item. 
					This can be because of an item's own effects (consumed Berries, Air Balloon), or by a move or ability, like Knock Off. 
					If a berry is consumed, it also has an additional modifier |[eat] to indicate that it was consumed. 
					This message does not appear if the item's ownership was changed (with a move or ability like Thief or Trick), 
					even if the move or ability would result in a Pokémon without an item.

					Note:
						Kept for legacy and inclusiveness reasons
						Actual tracking of this hook done based on changed
						Item in 'request' inp_type
					'''
                    pass

                elif inp_type == 'move':
                    '''
					move|POKEMON|MOVE|TARGET
					The specified Pokémon has used move MOVE at TARGET. 
					If a move has multiple targets or no target, TARGET should be ignored. 
					If a move targets a side, TARGET will be a (possibly fainted) Pokémon on that side.
					If |[miss] is present, the move missed.
					'''
                    if len(params) == 4:
                        if params[3] == '[zeffect]':
                            if self.get_owner(params[0]) == 'p1a':
                                pokemon_name = self.get_pokemon(params[0])
                                zmove_id = util.move_name_to_id(params[1])
                                # Add (pokemon : zmove) in the zmove_tracker to
                                # ensure, this pokemon can't re-use zmove
                                self.zmoves_tracker[pokemon_name] = zmove_name
                                self.gs.set_move(GameState.Player.one,
                                                 pokemon_name, zmove_id, 0.0,
                                                 1.0)

                elif inp_type == '-zpower':
                    '''
					|-zpower|POKEMON
					The Pokémon POKEMON has used the z-move version of its move.

					Note:
						Kept for legacy and inclusiveness reasons
						Actual tracking of zpower done in the '-move'
						hook
					'''
                    pass

                elif inp_type == '-weather':
                    weather_name = params[0]
                    if weather_name == 'none':
                        self.gs.clear_all_weather()
                    else:
                        self.gs.set_weather(weather_name)

                elif inp_type == '-boost':
                    mine = params[0].startswith(self.position)
                    if mine:
                        gs_player = GameState.Player.one
                    else:
                        gs_player = GameState.Player.two
                    boost_name = params[1]
                    stages = float(params[2])
                    self.gs.add_boost(gs_player, boost_name, stages)

                elif inp_type == '-unboost':
                    mine = params[0].startswith(self.position)
                    if mine:
                        gs_player = GameState.Player.one
                    else:
                        gs_player = GameState.Player.two
                    boost_name = params[1]
                    stages = float(params[2])
                    self.gs.add_boost(gs_player, boost_name, -1 * stages)

            else:
                if inp_type == 'updateuser':
                    if (self.name == params[0].strip() and self.challenge
                            and not self.has_challenged):
                        self.has_challenged = True
                        time.sleep(1)
                        await self.challenge_expected()
                elif inp_type == 'popup':
                    if 'Due to high load, you are limited to 12 battles and team validations every 3 minutes.' in params[
                            0]:
                        self.log('killing')
                        self.kill()
        except Exception as err:
            self.log_error(err)

    async def on_private_message(self, pm):
        if pm.recipient == self:
            await pm.reply(pm.content)

    async def on_challenge_update(self, challenge_data):
        incoming = challenge_data.get('challengesFrom', {})
        if self.expected_opponent.lower() in incoming:
            if self.trainer:
                model_paths = [
                    os.path.join(self.logs_dir, content)
                    for content in os.listdir(self.logs_dir) if
                    content.endswith('.model') and content.startswith('Epoch')
                ]
                if len(model_paths) > 0:
                    sorted_model_paths = sorted(
                        model_paths,
                        key=lambda x: int(
                            os.path.basename(x).lstrip('Epoch').rstrip('.model'
                                                                       )))
                    model_to_load = sorted_model_paths[-1]
                    self.log(f'Loading model {model_to_load}')
                    self.agent = DQNAgent(INPUT_SHAPE, training=False)
                    self.agent.load_model(model_to_load)
            await self.accept_challenge(self.expected_opponent, self.team_text)

    async def on_room_init(self, room_obj):
        if room_obj.id.startswith('battle-'):
            self.log(f'Room ID: {room_obj.id}')
            self.active_pokemon = None
            self.opp_active_pokemon = None
            self.weather = 'none'

    def kill(self):
        sys.exit(0)