def unsuppress_ability(self): assert self.ability == abilitydex['_suppressed_'] if __debug__: log.d("Unsuppressing %s's ability", self) self.remove_effect(ABILITY) self.ability = self._suppressed_ability self._suppressed_ability = None self.set_effect(self.ability())
def fill_in_unrevealed_attrs(foe): attrs, all_known = foe.known_attrs() if all_known: return log.d("Filling in %s's attrs: %s", foe, attrs) rb_index = rbstats_key(foe) if foe.item == itemdex['_unrevealed_']: probability = {rbstats.attr_probability(rb_index, item, attrs): item for item in rbstats[rb_index]['item']} item = itemdex[probability[max(probability)]] foe.item = item if foe.is_active: foe.set_effect(item()) attrs.append(item.name) if foe.ability == abilitydex['_unrevealed_']: probability = {rbstats.attr_probability(rb_index, ability, attrs): ability for ability in rbstats[rb_index]['ability']} ability = abilitydex[probability[max(probability)]] foe.base_ability = foe.ability = ability if foe.is_active: foe.set_effect(ability()) attrs.append(ability.name) while len(foe.moves) < 4: probability = {rbstats.attr_probability(rb_index, move, attrs): move for move in rbstats[rb_index]['moves'] if not movedex[move] in foe.moves} move = movedex[probability[max(probability)]] foe.moves[move] = move.max_pp attrs.append(move.name) log.d("Filled in: %s", attrs)
def check_accuracy(self, user, move, target): """ Return FAIL if the move misses. An accuracy value of None means to skip the accuracy check, i.e. it always hits. """ accuracy = move.accuracy accuracy = user.accumulate_effect('on_accuracy', user, move, target, self, accuracy) accuracy = target.accumulate_effect('on_foe_accuracy', user, move, target, self, accuracy) if accuracy is None: return boost_factor = (1.0, 4.0/3.0, 5.0/3.0, 2.0, 7.0/3.0, 8.0/3.0, 3.0) if not move.ignore_accuracy_boosts: acc_boost = user.boosts['acc'] if acc_boost > 0: accuracy *= boost_factor[acc_boost] elif acc_boost < 0: accuracy /= boost_factor[-acc_boost] if not move.ignore_evasion_boosts: evn_boost = target.boosts['evn'] if evn_boost > 0: accuracy /= boost_factor[evn_boost] elif evn_boost < 0: accuracy *= boost_factor[-evn_boost] if __debug__: log.d('Using accuracy of %s', accuracy) if random.randrange(100) >= int(accuracy): if __debug__: log.i('But it missed!') return FAIL
def on_break_mold(self, target, battle): if target.ability.name in MOLDS: self.suppressed = True target.suppress_ability(battle) if __debug__: log.d("%s's %s was suppressed by moldbreaker", target, target.ability) else: self.suppressed = False
def on_modify_base_power(self, user, move, target, battle, base_power): if (battle.battlefield.weather is Weather.SANDSTORM and move.type in (Type.ROCK, Type.GROUND, Type.STEEL) ): if __debug__: log.d('%s was boosted by SandForce!', move) return base_power * 1.3 return base_power
def fast_use_move(self, user, move): """ Fast path optimization out of self.use_move for moves not targeting an opponent (move.targets_user or move.targets_field). Replaces try_move_hit and move_hit and allows them to skip (if target is None) checks. Specifically skips on_foe_try_hit, {immunity,accuracy,multihit,substitute} checks, calculate_damage, drain check, move.target_status, move.secondary_effects, move.on_move_fail, move.on_after_move_secondary. """ if __debug__: log.d('%s: Fast path', move) if move.check_success(user, None, self) is FAIL: if __debug__: log.i('(check_success) But it failed') return FAIL user.damage_done_this_turn = 0 if move.user_boosts is not None: if user.apply_boosts(move.user_boosts, True) is FAIL: if __debug__: log.i('(apply_boosts) But it failed') return FAIL if move.on_success(user, None, self) is FAIL: if __debug__: log.i('(on_success) But it failed') return FAIL if not move.calls_other_moves: # don't override switch moves called via copycat/sleeptalk user.must_switch = move.switch_user if (user.hp == 0 or move.selfdestruct) and not user.status is Status.FNT: if __debug__: log.d('User has no HP after using move, fainting') self.faint(user, Cause.SELFDESTRUCT, move)
def on_start(self, pokemon, battle): foe = battle.get_foe(pokemon) if foe is None: if __debug__: log.d("%s's Imposter: couldn't transform", pokemon) return FAIL if pokemon.transform_into(foe, battle) is not FAIL: pokemon.set_effect(effects.Transformed())
def on_get_move_choices(self, pokemon, moves): if self.move not in pokemon.moves: pokemon.remove_effect(Volatile.CHOICELOCK) if __debug__: log.d("%s doesn't have the choiced move %s, removing choicelock", pokemon, self.move) return moves if __debug__: log.d('%s is choicelocked into %s', pokemon, self.move) return [self.move] if self.move in moves else []
def remove_effect(self, source, _=None): effect = self._effect_index.pop(source, None) if effect is None: if __debug__: log.d("Tried to remove nonexistent %s from side %d", source, self.index) return self._remove_handlers(effect) if __debug__: log.i('Removed %s from side %d', effect, self.index)
def cure_status(self): if self.status in (None, Status.FNT): if __debug__: log.d("Tried to cure %s's status but it was %s", self, self.status) else: if __debug__: log.i("%s's status (%s) was cured", self, self.status) self.remove_effect(self.status) self.status = None self.is_resting = False self.turns_slept = None
def remove_effect(self, source, _=None): effect = self._effect_index.pop(source, None) if effect is None: if __debug__: log.d("Tried to remove nonexistent %s from battlefield", source) return self._remove_handlers(effect) if source in Weather.values: self._weather = None if __debug__: log.i('Removed %s from battlefield', effect)
def set_effect(self, effect, override_immunity=False): assert not self.is_fainted() if effect.source in self._effect_index: if __debug__: log.d('Tried to set effect %s but %s already has it', effect, self) return FAIL if not override_immunity and self.is_immune_to(effect.source): if __debug__: log.i('%s is immune to %s!', self, effect.source) return FAIL self._effect_index[effect.source] = effect self._set_handlers(effect) if __debug__: log.i('Set effect %s on %s', effect, self)
def activate_effect(self, name, *args, **kwargs): """ Call all bound handlers for the named effect type, with *args as the handler method's arguments. Handlers are already in sorted priority order, if applicable. When failfast is passed as a keyword arg, bail out and return FAIL as soon as any handler returns FAIL. """ failfast = kwargs.get('failfast', False) for effect in self.effect_handlers[name][:]: if __debug__: log.d('effect %s of %r activated', name, effect.__self__) rv = effect(*args) if failfast and rv is FAIL: if __debug__: log.d('Effect %s was failed by %r', name, effect.__self__) return FAIL
def get_balancing_types(types): votes = EMPTY_COUNTER.copy() for type in types: votes[type] -= 100 for other_type in COMPLEMENT[type]: votes[other_type] += 1 sorted_votes = votes.most_common() log.d("votes=%s", [(k, v) for k, v in sorted_votes if v != 0]) max_votes = sorted_votes[0][1] i = -1 for i, item in enumerate(sorted_votes): if max_votes - item[1] > 1: break return [vote[0] for vote in sorted_votes[:i]]
def force_random_switch(self, pokemon, forcer): if (pokemon.is_fainted() or forcer.is_fainted() or pokemon.ability.name == 'suctioncups' ): return FAIL team_members = pokemon.get_switch_choices(forced=True) if team_members: incoming = random.choice(team_members) if __debug__: log.d('Force switching %s for %s', pokemon, incoming) self.run_switch(pokemon, incoming) forcer.get_effect(ABILITY).on_break_mold(incoming, self) self.post_switch_in(incoming) elif __debug__: log.i('Tried to force random switch; %s has no remaining teammates', pokemon)
def accumulate_effect(self, name, *args, **kwargs): """ Call all bound handlers for the named effect type, with *args as the handler method's arguments. The argument in the last position, args[-1], is the accumulator variable and will be modified and returned. Failfast works as for self.activate_effect """ failfast = kwargs.get('failfast', False) accumulator = args[-1] for effect in self.effect_handlers[name][:]: if __debug__: log.d('effect %s of %r activated', name, effect.__self__) accumulator = effect(*(args[:-1] + (accumulator,))) if failfast and accumulator is FAIL: return FAIL return accumulator
def run_queued_events(self): while self.event_queue: if __debug__: log.d('Event Queue: %r', self.event_queue) if __debug__: log.d('Next event: %s', self.event_queue[-1]) event = self.event_queue.pop() event.run_event(self, self.event_queue) if event.type is not Decision.SWITCH: self.run_update() for side in self.battlefield.sides: if (side.active_pokemon is not None and side.active_pokemon.must_switch and # e.g. from voltswitch side.remaining_pokemon_on_bench > 0 ): self.run_must_switch(side) self.resolve_faint_queue()
def change_ability(self, new_ability, battle): """ Change this pokemon's ability. This effect will only last while the pokemon is active. This pokemon's original ability remains saved in self.base_ability. """ assert self.is_active, "Tried to change inactive pokemon's ability" assert issubclass(new_ability, abilities.BaseAbility) if ((new_ability.name in ('illusion', 'stancechange', 'multitype') or self.ability.name in ('stancechange', 'multitype'))): if __debug__: log.d("Failed to change %s's %s to %s", self, self.ability, new_ability) return FAIL self.remove_effect(ABILITY, battle) self.ability = new_ability self.set_effect(new_ability()) self.get_effect(ABILITY).start(self, battle)
def create_foe_for_rollout(foe_side): foe_team = [foe for foe in foe_side.team if foe.name != UNREVEALED] foe_types = filter(None, [type for foe in foe_team for type in foe.types]) allow_mega = not any(foe.item is not None and foe.item.is_mega_stone for foe in foe_team) name = get_balancing_pokemon(foe_types) stats = rbstats[name] level = max(stats['level'], key=stats['level'].get) if allow_mega else max(stats['level']) if allow_mega: attrs = max(stats['sets'], key=stats['sets'].get) else: attrs = next(attrs for attrs, _ in stats['sets'].most_common() if not itemdex[attrs[5]].is_mega_stone) moves = [movedex[move] for move in attrs[:4]] ability = abilitydex[attrs[4]] item = itemdex[attrs[5]] pokemon = BattlePokemon(pokedex[name], level, moves, ability, item, side=foe_side) log.d("Created foe: %s (%s, %s, %s)", pokemon, moves, ability, item) return pokemon
def _use_move(self, user, move, target): """ Return value not used (except for testing) """ assert not user.is_fainted() assert user.is_active if __debug__: log.i('%s used %s! (target=%s)', user, move, target) # copy the move, so that modify_move doesn't modify the global copy in movedex move = move.__class__() move.on_modify_move(user, target, self) user.activate_effect('on_modify_move', move, user, self) if target is not None: target.activate_effect('on_modify_foe_move', move, user, self) if move.targets_user or move.targets_field: return self.fast_use_move(user, move) # fast path for moves not targeting opponent if target is None or target.is_fainted(): if __debug__: log.i('But it failed: No target') if move.selfdestruct: self.faint(user, Cause.SELFDESTRUCT, move) return FAIL damage = self.try_move_hit(user, move, target) if (user.hp == 0 or move.selfdestruct) and not user.status is Status.FNT: if __debug__: log.d('User has no HP after using move, fainting') self.faint(user, Cause.SELFDESTRUCT, move) if damage is FAIL: if __debug__: if not user.has_effect(Volatile.TWOTURNMOVE): log.i('%s: But it failed', move) move.on_move_fail(user, self) return FAIL if not user.has_effect(Volatile.SHEERFORCE): target.activate_effect('on_after_foe_move_secondary', user, move, target, self) user.activate_effect('on_after_move_secondary', user, move, target, self) return damage # for testing only
def apply_secondary_effect(self, pokemon, s_effect, user): if (random.randrange(100) >= s_effect.chance or pokemon is None or pokemon.is_fainted() or (pokemon.ability is abilitydex['shielddust'] and not s_effect.affects_user)): return if __debug__: log.d('Applying %s to %s', s_effect, pokemon) if s_effect.boosts is not None: pokemon.apply_boosts(s_effect.boosts, s_effect.affects_user) elif s_effect.status is not None: self.set_status(pokemon, s_effect.status, user) elif s_effect.volatile is Volatile.FLINCH: pokemon.set_effect(effects.Flinch()) elif s_effect.volatile is Volatile.CONFUSE: pokemon.confuse(user.ability is abilitydex['infiltrator']) elif s_effect.callback is not None: s_effect.callback(pokemon, user, self) else: assert False, 'Tried to apply secondary effect with no boosts, volatile, or status'
def remove_effect(self, source, battle=None, force=False): """ `battle` must be passed if there is a possibility that `source`'s effect has an on_end method that uses battle. `battle` may be omitted if `source` is known to be a move or status, but must be included for abilities in general. Return True if effect was removed """ effect = self._effect_index.pop(source, None) if effect is None: if __debug__: log.d("Trying to remove %s from %s, but it wasn't found!", source, self) return False self._remove_handlers(effect) if __debug__: log.i('Removed %s from %s', effect, self) if not force and 'on_end' in effect.handler_names: effect.on_end(self, battle) if source is self.status: self.status = None return True
def known_attrs(self): """ Return the names of the known move/item/ability attributes of this foe pokemon: [moves..., ability, item] Do not return the base_ability of a mega/primal pokemon, since rbstats only tracks the base_ability of the pre-formechange pokemon, so lookups will fail using the post-change ability. This doesn't reduce the accuracy of lookups, since if the pokemon is mega/primal this is seen uniquely in the item. """ attrs = [move.name for move in self.moves if move.type != Type.NOTYPE] if self.original_item != itemdex['_unrevealed_']: attrs.append(self.original_item.name) if (self.base_ability != abilitydex['_unrevealed_'] and not (self.is_mega or self.name.endswith('primal'))): attrs.append(self.base_ability.name) log.d("attrs: %s", attrs) all_known = (len(attrs) == 6 or (len(attrs) == 5 and (self.is_mega or self.name.endswith('primal'))) or (len(attrs) == 3 and self.base_species == 'ditto')) return attrs, all_known
def move_hit(self, user, move, target): assert target is not None user.activate_effect('on_move_hit', user, move, self) substitute = target.get_effect(Volatile.SUBSTITUTE) if substitute is not None: result = substitute.on_hit_substitute(user, move, target, self) if result is not None: if __debug__: log.d('Hit substitute: result=%s', result) return result elif __debug__: log.d('Bypassed substitute') damage = self.calculate_damage(user, move, target) if damage is FAIL: return FAIL if damage is not None: damage = self.damage(target, damage, Cause.MOVE, move, user, move.drain) if damage is FAIL: if __debug__: log.i('Move failed in Battle.damage: returned %s', damage) return FAIL user.damage_done_this_turn = damage if move.target_status is not None: if self.set_status(target, move.target_status, user) is FAIL: return FAIL # all target_status moves do nothing else, so fail fast if move.user_boosts is not None and not user.is_fainted(): user.apply_boosts(move.user_boosts, True) user.activate_effect('on_move_success', user, move, target) if move.on_success(user, target, self) is FAIL: if __debug__: log.i('Move "%s" failed in on_success', move) return FAIL target = self.get_foe(user) # roar, etc. could have changed the active foe if target is not None: target.activate_effect('on_after_foe_hit', user, move, target, self) for s_effect in move.secondary_effects: self.apply_secondary_effect(user if s_effect.affects_user else target, s_effect, user) user.must_switch = move.switch_user if __debug__: log.d('returning damage=%s', damage) return damage
def on_modify_move(self, move, user, battle): if move.accuracy is not None and move.category is MoveCategory.PHYSICAL: move.accuracy *= 0.8 if __debug__: log.d("%s's accuracy decreased to %s by Hustle", move, move.accuracy)
def on_modify_damage(self, user, move, effectiveness, damage): if __debug__: log.d("%s was boosted by %s's LifeOrb", move, user) return damage * 1.3
def on_modify_spe(self, pokemon, battle, spe): if battle.battlefield.weather is Weather.SANDSTORM: if __debug__: log.d("%s's SandRush boosted its speed!", pokemon) return spe * 2 return spe
def on_unbreak_mold(self, target): if self.suppressed and target is not None: target.unsuppress_ability() if __debug__: log.d("%s's %s was restored", target, target.ability) self.suppressed = False
def on_modify_spe(self, pokemon, battle, spe): if pokemon.status is not None: if __debug__: log.d("%s's QuickFeet boosted its speed!", pokemon) return spe * 1.5 return spe
def on_get_immunity(self, thing): if thing in (Weather.HAIL, Weather.SANDSTORM, POWDER): if __debug__: log.d('%s damage prevented by Overcoat', thing) return True