import random from .data import find, find_one from .enums import Ability, DamageClass, ElementType from .misc import NonVolatileEffect, Metronome, ExpiringEffect, ExpiringItem, HeldItem from .move import Move class DuelPokemon(): """ An instance of a pokemon in a duel. Contains extra duel-specific information. """ def __init__(self, **kwargs): #ID from pfile self.pokemon_id = kwargs['pokemon_id'] self._name = kwargs['name'] self._nickname = kwargs['nickname'] if self._nickname != "None": self.name = f"{self._nickname} ({self._name.replace('-', ' ')})" else: self.name = self._name.replace("-", " ") self.illusion__name = None self.illusion_name = None # Dict {pokname: List[int]} self.base_stats = kwargs['base_stats'] self.hp = kwargs['hp'] self.attack = self.base_stats[self._name][1] self.defense = self.base_stats[self._name][2] self.spatk = self.base_stats[self._name][3] self.spdef = self.base_stats[self._name][4] self.speed = self.base_stats[self._name][5] self.hpiv = min(31, kwargs['hpiv']) self.atkiv = min(31, kwargs['atkiv']) self.defiv = min(31, kwargs['defiv']) self.spatkiv = min(31, kwargs['spatkiv']) self.spdefiv = min(31, kwargs['spdefiv']) self.speediv = min(31, kwargs['speediv']) self.hpev = kwargs['hpev'] self.atkev = kwargs['atkev'] self.defev = kwargs['defev'] self.spatkev = kwargs['spatkev'] self.spdefev = kwargs['spdefev'] self.speedev = kwargs['speedev'] self.nature_stat_deltas = kwargs['nature_stat_deltas'] self.moves = kwargs['moves'] self.ability_id = kwargs['ability_id'] self.mega_ability_id = kwargs['mega_ability_id'] self.type_ids = kwargs['type_ids'] self.mega_type_ids = kwargs['mega_type_ids'] self._starting_name = self._name self.starting_hp = self.hp self.starting_hpiv = self.hpiv self.starting_atkiv = self.atkiv self.starting_defiv = self.defiv self.starting_spatkiv = self.spatkiv self.starting_spdefiv = self.spdefiv self.starting_speediv = self.speediv self.starting_hpev = self.hpev self.starting_atkev = self.atkev self.starting_defev = self.defev self.starting_spatkev = self.spatkev self.starting_spdefev = self.spdefev self.starting_speedev = self.speedev self.starting_moves = self.moves.copy() #shallow copy to keep the objects but not the list itself self.starting_ability_id = self.ability_id self.starting_type_ids = self.type_ids.copy() #10 "weight" = 1 kg self.starting_weight = max(1, kwargs['weight']) self.attack_stage = 0 self.defense_stage = 0 self.spatk_stage = 0 self.spdef_stage = 0 self.speed_stage = 0 self.accuracy_stage = 0 self.evasion_stage = 0 self.level = kwargs['level'] self.shiny = kwargs['shiny'] self.radiant = kwargs['radiant'] self.skin = kwargs['skin'] self.id = kwargs['id'] self.held_item = HeldItem(kwargs['held_item'], self) self.happiness = kwargs['happiness'] self.gender = kwargs['gender'] self.can_still_evolve = kwargs['can_still_evolve'] self.disliked_flavor = kwargs['disliked_flavor'] self.owner = None self.active_turns = 0 self.cursed = False self.switched = False self.minimized = False self.nv = NonVolatileEffect(self) self.metronome = Metronome() self.leech_seed = False self.stockpile = 0 self.flinched = False self.confusion = ExpiringEffect(0) self.can_move = True self.has_moved = False #Boolean - stores whether this poke swapped in this turn, and should not have the next_turn function be called. self.swapped_in = False #Boolean - stores whether this poke has ever been sent in. self.ever_sent_out = False #Boolean - stores whether this poke should attempt to mega evolve this turn. self.should_mega_evolve = False # Moves #Optional[Move] - stores the last move used by this poke self.last_move = None #Optional[Tuple[int, DamageClass]] - stores the damage taken and the class of that damage. #Resets at the end of a turn. self.last_move_damage = None #Boolean - stores whether or not the last move used by this poke failed. self.last_move_failed = False #Optional[LockedMove] - stores the move this poke is currently locked into using due to it being a multi turn move. self.locked_move = None #Optional[Move] - stores the move this poke is currently locked into using due to a choice item. self.choice_move = None #ExpiringItem - stores the number of turns a specific move is disabled. self.disable = ExpiringItem() #ExpiringEffect - stores the number of turns this poke is taunted. self.taunt = ExpiringEffect(0) #ExpiringItem - stores the number of turns this poke is giving an encore of a specific move. self.encore = ExpiringItem() #Boolean - stores whether or not this pokemon is affected by torment. self.torment = False #Boolean - stores whether or not this pokemon has imprison active. self.imprison = False #ExpiringEffect - stores the number of turns this poke is blocked from using healing moves. self.heal_block = ExpiringEffect(0) #Optional[int] - stores the damage takes since bide was started. self.bide = None #Boolean - stores whether or not this pokemon has used focus energy. self.focus_energy = False #ExpiringEffect - stores the number of turns until this pokemon faints. self.perish_song = ExpiringEffect(0) #Boolean - stores whether or not this pokemon is inflicted by nightmare. self.nightmare = False #Boolean - stores whether or not this pokemon has used defense curl since entering the field. self.defense_curl = False #Int - stores the number of times fury cutter has been used since entering the field. self.fury_cutter = 0 #ExpiringEffect - stores the number of turns bind is active on this poke. self.bind = ExpiringEffect(0) #Int - stores the remaining HP of this pokemon's substitute, or 0 if there is no substitute. self.substitute = 0 #ExpiringEffect - stores the number of turns this pokemon is silenced for (can't use sound based moves). self.silenced = ExpiringEffect(0) #Boolean - stores whether or not this poke is in rage, and gains attack stages after being hit. self.rage = False #ExpiringItem - stores whether or not mind reader is active ON this poke, and the poke which is reading its mind. self.mind_reader = ExpiringItem() #Boolean - stores whether any attacker that causes this poke to faint will also faint this turn. self.destiny_bond = False #ExpiringEffect - stores whether destiny bond is on cooldown and cannot be executed. self.destiny_bond_cooldown = ExpiringEffect(0) #Boolean - stores whether or not this poke is prevented from switching out. This does not prevent moves that swap the target or user. self.trapping = False #Boolean - stores whether or not this poke is under the effect of ingrain. This does not prevent moves that swap the user, but does prevent target swaps. self.ingrain = False #Optional[DuelPokemon] - stores the pokemon that this pokemon is infatuated with. self.infatuated = None #Boolean - stores whether or not this poke is under the effect of aqua ring. self.aqua_ring = False #ExpiringEffect - stores the number of turns this pokemon is ungrounded due to magnet rise. self.magnet_rise = ExpiringEffect(0) #Boolean - stores whether or not this poke is in a semi-invulnerable state from dive. self.dive = False #Boolean - stores whether or not this poke is in a semi-invulnerable state from dig. self.dig = False #Boolean - stores whether or not this poke is in a semi-invulnerable state from bounce or fly. self.fly = False #Boolean - stores whether or not this poke is in a semi-invulnerable state from shadow force or phantom force. self.shadow_force = False #ExpiringEffect - stores how long this poke cannot be hit by critical hits due to lucky chant. self.lucky_chant = ExpiringEffect(0) #Boolean - stores whether or not this poke has been hit by smack down or thousand arrows, and is grounded. self.grounded_by_move = False #ExpiringEffect - stores the number of turns electric type moves have double power from this poke. self.charge = ExpiringEffect(0) #ExpiringEffect - stores the number of turns pokes cannot fall asleep. self.uproar = ExpiringEffect(0) #Boolean - stores whether or not this poke reflects status moves due to magic coat. self.magic_coat = False #Boolean - stores whether or not this poke has used power trick. self.power_trick = False #Boolean - stores whether or not this poke has used power shift. self.power_shift = False #ExpiringEffect - stores the number of turns until this poke falls asleep due to drowsiness. self.yawn = ExpiringEffect(0) #Boolean - stores whether this poke is turning normal moves to electric this turn. self.ion_deluge = False #Boolean - stores whether this poke's move is changed to electric type due to electrify. self.electrify = False #Boolean - stores whether this poke used a protection move this turn. self.protection_used = False #Int - stores the current chance (1/x) that a poke can use a protection move. self.protection_chance = 1 #Boolean - stores whether this poke is protected by protect this turn. self.protect = False #Boolean - stores whether this poke is protected by endure this turn. self.endure = False #Boolean - stores whether this poke is protected by wide guard this turn. self.wide_guard = False #Boolean - stores whether this poke is protected by crafty shield this turn. self.crafty_shield = False #Boolean - stores whether this poke is protected by king shield this turn. self.king_shield = False #Boolean - stores whether this poke is protected by spiky shield this turn. self.spiky_shield = False #Boolean - stores whether this poke is protected by mat block this turn. self.mat_block = False #Boolean - stores whether this poke is protected by baneful bunker this turn. self.baneful_bunker = False #Boolean - stores whether this poke is protected by quick guard this turn. self.quick_guard = False #Boolean - stores whether this poke is protected by obstruct this turn. self.obstruct = False #Boolean - stores whether this poke is protected by silk trap this turn. self.silk_trap = False #Boolean - stores whether this poke is protected by burning bulwark this turn. self.burning_bulwark = False #ExpiringEffect - stores whether this poke will always crit due to laser focus. self.laser_focus = ExpiringEffect(0) #Boolean - stores whether this poke is coated with powder and will explode if it uses a fire type move. self.powdered = False #Boolean - stores whether this poke will steal the effect of certain self targeted moves this turn using snatch. self.snatching = False #ExpiringEffect - stores the number of turns this poke is ungrounded and hits to it never miss due to telekinesis. self.telekinesis = ExpiringEffect(0) #ExpiringEffect - stores the number of turns this poke is blocked from using held items due to embargo. self.embargo = ExpiringEffect(0) #Int - stores the current power of echoed voice. self.echoed_voice_power = 40 #Boolean - stores whether echoed voice was used this turn. self.echoed_voice_used = False #Boolean - stores whether this poke is inflicted with a curse. self.curse = False #ExpiringEffect - stores whether this poke is preventing swap outs this turn. self.fairy_lock = ExpiringEffect(0) #Boolean - stores whether this poke remove all PP from a move that kills it this turn. self.grudge = False #Boolean - stores whether this poke is affected by foresight. self.foresight = False #Boolean - stores whether this poke is affected by miracle_eye. self.miracle_eye = False #Boolean - stores whether this poke is currently charging beak blast. self.beak_blast = False #Boolean - stores whether this poke is unable to switch out from no retreat. self.no_retreat = False #Boolean - stores whether this poke has taken ANY damage this turn. self.dmg_this_turn = False #Boolean - stores whether this poke has eaten a berry this battle. self.ate_berry = False #Boolean - stores whether this poke is unable to use its held item for the rest of the battle due to corrosive gas. self.corrosive_gas = False #Boolean - stores whether this poke has had a stat increase this turn. self.stat_increased = False #Boolean - stores whether this poke has had a stat decrease this turn. self.stat_decreased = False #Boolean - stores whether this poke has had its flying type surpressed this turn. self.roost = False #Boolean - stores whether this poke is affected by octolock, and has its def/spdef lowered each turn. self.octolock = False #Optional[Int] - stores the raw attack value this poke's attack is split with. self.attack_split = None #Optional[Int] - stores the raw special attack value this poke's special attack is split with. self.spatk_split = None #Optional[Int] - stores the raw defense value this poke's defense is split with. self.defense_split = None #Optional[Int] - stores the raw special defense value this poke's special defense is split with. self.spdef_split = None #Int - stores the number of times autotomize has been used since switching out. self.autotomize = 0 #Boolean - stores whether or not this pokemon's critical hit ratio is increased from eating a lansat berry. self.lansat_berry_ate = False #Boolean - stores whether or not this pokemon's next move has increased accuracy from eating a micle berry. self.micle_berry_ate = False #Boolean - stores whether or not fire type moves are 2x effective on this pokemon from tar shot. self.tar_shot = False #ExpiringEffect - stores the number of turns this pokemon is lowering the opponent's speed at the end of every turn. self.syrup_bomb = ExpiringEffect(0) #Int - stores the number of times this pokemon has been hit this battle, and does NOT reset when switching out. self.num_hits = 0 # Abilities #Boolean - stores whether this poke's fire moves are boosted by flash fire. self.flash_fire = False #Int - stores the turn number relative to getting truant, % 2 == 1 -> loaf around. self.truant_turn = 0 #Boolean - stores whether the ice face of this pokemon has been repaired. self.ice_repaired = False #Optional[Item] - stores the last berry ate by this pokemon. self.last_berry = None #ExpiringEffect - stores the number of turns until this pokemon attempts to recover & eat their last eaten berry. self.cud_chew = ExpiringEffect(0) #Boolean - stores whether booster_energy has been consumed this send out to activate the effects of Protosynthesis / Quark Drive. self.booster_energy = False #Boolean - stores whether supersweet_syrup has been consumed to lower the evasion of its opponent. NOT reset on switch out. self.supersweet_syrup = False def send_out(self, otherpoke, battle): """ Initalize a poke upon first sending it out. otherpoke may be None, if two pokes are sent out at the same time and the first is killed in the send out process. Returns a formatted message. """ self.ever_sent_out = True # Emergency exit `remove`s the pokemon *in the middle of the turn* in a somewhat unsafe way. # `remove` may need to be called here, but that seems like it may have side effects. self.flinched = False # This has to go BEFORE the send out message, and not in send_out_ability as it only # applies on send out, not when abilities are changed, and it changes the send out msg. illusion_options = [x for x in self.owner.party if x is not self and x.hp > 0] if self.ability() == Ability.ILLUSION and illusion_options: self.illusion__name = self._name self.illusion_name = self.name self._name = illusion_options[-1]._name self.name = illusion_options[-1].name if self._name == "Pikachu": msg = f"{self.name}, I choose you!\n" else: msg = f"{self.owner.name} sent out {self.name}!\n" # Any time a poke switches out, certain effects it had put on its opponent end if otherpoke is not None: otherpoke.trapping = False otherpoke.octolock = False otherpoke.bind.set_turns(0) #Baton Pass if self.owner.baton_pass is not None: msg += f"{self.name} carries on the baton!\n" self.owner.baton_pass.apply(self) self.owner.baton_pass = None #Shed Tail if self.owner.next_substitute: self.substitute = self.owner.next_substitute self.owner.next_substitute = 0 #Entry hazards #Special case for clearing toxic spikes, still happens even with heavy duty boots if self.owner.toxic_spikes and self.grounded(battle) and ElementType.POISON in self.type_ids: self.owner.toxic_spikes = 0 msg += f"{self.name} absorbed the toxic spikes!\n" if self.held_item != "heavy-duty-boots": #Grounded entry hazards if self.grounded(battle): #Spikes if self.owner.spikes: #1/8 -> 1/4 damage = self.starting_hp // (10 - (2 * self.owner.spikes)) msg += self.damage(damage, battle, source="spikes") #Toxic spikes if self.owner.toxic_spikes == 1: msg += self.nv.apply_status("poison", battle, source="toxic spikes") elif self.owner.toxic_spikes == 2: msg += self.nv.apply_status("b-poison", battle, source="toxic spikes") #Sticky web if self.owner.sticky_web: msg += self.append_speed(-1, source="the sticky web") #Non-grounded entry hazards if self.owner.stealth_rock: effective = self.effectiveness(ElementType.ROCK, battle) if effective: # damage = 1/8 max hp * effectiveness damage = self.starting_hp // (32 // (4 * effective)) msg += self.damage(int(damage), battle, source="stealth rock") if self.hp > 0: msg += self.send_out_ability(otherpoke, battle) #Restoration if self.owner.healing_wish: used = False if self.hp != self.starting_hp: used = True self.hp = self.starting_hp if self.nv.current: used = True self.nv.reset() if used: self.owner.healing_wish = False msg += f"{self.name} was restored by healing wish!\n" if self.owner.lunar_dance: used = False if self.hp != self.starting_hp: used = True self.hp = self.starting_hp if self.nv.current: used = True self.nv.reset() not_at_full_pp = [move for move in self.moves if move.pp != move.starting_pp] if not_at_full_pp: used = True for move in not_at_full_pp: move.pp = move.starting_pp if used: self.owner.lunar_dance = False msg += f"{self.name} was restored by lunar dance\n" # Items if self.held_item == "air-balloon" and not self.grounded(battle): msg += f"{self.name} floats in the air with its air balloon!\n" if self.held_item == "electric-seed" and battle.terrain.item == "electric": msg += self.append_defense(1, attacker=self, source="its electric seed") self.held_item.use() if self.held_item == "psychic-seed" and battle.terrain.item == "psychic": msg += self.append_spdef(1, attacker=self, source="its psychic seed") self.held_item.use() if self.held_item == "misty-seed" and battle.terrain.item == "misty": msg += self.append_spdef(1, attacker=self, source="its misty seed") self.held_item.use() if self.held_item == "grassy-seed" and battle.terrain.item == "grassy": msg += self.append_defense(1, attacker=self, source="its grassy seed") self.held_item.use() return msg def send_out_ability(self, otherpoke, battle): """ Initalize this poke's ability. otherpoke may be None. Returns a formatted message. """ msg = "" #Imposter (sus) if self.ability() == Ability.IMPOSTER and otherpoke is not None and not otherpoke.substitute and otherpoke.illusion_name is None: msg += f"{self.name} transformed into {otherpoke._name}!\n" self.transform(otherpoke) #Weather if self.ability() == Ability.DRIZZLE: msg += battle.weather.set("rain", self) if self.ability() == Ability.PRIMORDIAL_SEA: msg += battle.weather.set("h-rain", self) if self.ability() == Ability.SAND_STREAM: msg += battle.weather.set("sandstorm", self) if self.ability() == Ability.SNOW_WARNING: msg += battle.weather.set("hail", self) if self.ability() in (Ability.DROUGHT, Ability.ORICHALCUM_PULSE): msg += battle.weather.set("sun", self) if self.ability() == Ability.DESOLATE_LAND: msg += battle.weather.set("h-sun", self) if self.ability() == Ability.DELTA_STREAM: msg += battle.weather.set("h-wind", self) #Terrain if self.ability() == Ability.GRASSY_SURGE: msg += battle.terrain.set("grassy", self) if self.ability() == Ability.MISTY_SURGE: msg += battle.terrain.set("misty", self) if self.ability() in (Ability.ELECTRIC_SURGE, Ability.HADRON_ENGINE): msg += battle.terrain.set("electric", self) if self.ability() == Ability.PSYCHIC_SURGE: msg += battle.terrain.set("psychic", self) # Message only if self.ability() == Ability.MOLD_BREAKER: msg += f"{self.name} breaks the mold!\n" if self.ability() == Ability.TURBOBLAZE: msg += f"{self.name} is radiating a blazing aura!\n" if self.ability() == Ability.TERAVOLT: msg += f"{self.name} is radiating a bursting aura!\n" if self.ability() == Ability.INTIMIDATE and otherpoke is not None: if otherpoke.ability() == Ability.OBLIVIOUS: msg += f"{otherpoke.name} is too oblivious to be intimidated!\n" elif otherpoke.ability() == Ability.OWN_TEMPO: msg += f"{otherpoke.name} keeps walking on its own tempo, and is not intimidated!\n" elif otherpoke.ability() == Ability.INNER_FOCUS: msg += f"{otherpoke.name} is too focused to be intimidated!\n" elif otherpoke.ability() == Ability.SCRAPPY: msg += f"{otherpoke.name} is too scrappy to be intimidated!\n" elif otherpoke.ability() == Ability.GUARD_DOG: msg += f"{otherpoke.name}'s guard dog keeps it from being intimidated!\n" msg += otherpoke.append_attack(1, attacker=otherpoke, source="its guard dog") else: msg += otherpoke.append_attack(-1, attacker=self, source=f"{self.name}'s Intimidate") if otherpoke.held_item == "adrenaline-orb": msg += otherpoke.append_speed(1, attacker=otherpoke, source="its adrenaline orb") if otherpoke.ability() == Ability.RATTLED: msg += otherpoke.append_speed(1, attacker=otherpoke, source="its rattled") if self.ability() == Ability.SCREEN_CLEANER: battle.trainer1.aurora_veil.set_turns(0) battle.trainer1.light_screen.set_turns(0) battle.trainer1.reflect.set_turns(0) battle.trainer2.aurora_veil.set_turns(0) battle.trainer2.light_screen.set_turns(0) battle.trainer2.reflect.set_turns(0) msg += f"{self.name}'s screen cleaner removed barriers from both sides of the field!\n" if self.ability() == Ability.INTREPID_SWORD: msg += self.append_attack(1, attacker=self, source="its intrepid sword") if self.ability() == Ability.DAUNTLESS_SHIELD: msg += self.append_defense(1, attacker=self, source="its dauntless shield") if self.ability() == Ability.TRACE and otherpoke is not None and otherpoke.ability_giveable(): self.ability_id = otherpoke.ability_id msg += f"{self.name} traced {otherpoke.name}'s ability!\n" msg += self.send_out_ability(otherpoke, battle) return msg if self.ability() == Ability.DOWNLOAD and otherpoke is not None: if otherpoke.get_spdef(battle) > otherpoke.get_defense(battle): msg += self.append_attack(1, attacker=self, source="its download") else: msg += self.append_spatk(1, attacker=self, source="its download") if self.ability() == Ability.ANTICIPATION and otherpoke is not None: for move in otherpoke.moves: if move.effect == 39: msg += f"{self.name} shuddered in anticipation!\n" break if self.effectiveness(move.type, battle) > 1: msg += f"{self.name} shuddered in anticipation!\n" break if self.ability() == Ability.FOREWARN and otherpoke is not None: bestmoves = [] bestpower = 0 for move in otherpoke.moves: if move.damage_class == DamageClass.STATUS: power = 0 if move.effect == 39: power = 150 elif move.power is None: # Good enough power = 80 else: power = move.power if power > bestpower: bestpower = power bestmoves = [move] elif power == bestpower: bestmoves.append(move) if bestmoves: move = random.choice(bestmoves) msg += f"{self.name} is forewarned about {otherpoke.name}'s {move.pretty_name}!\n" if self.ability() == Ability.FRISK and otherpoke is not None and otherpoke.held_item.has_item(): msg += f"{self.name} senses that {otherpoke.name} is holding a {otherpoke.held_item.name} using its frisk!\n" if self.ability() == Ability.MULTITYPE: e = None if self.held_item == "draco-plate": e = ElementType.DRAGON f = "Arceus-dragon" elif self.held_item == "dread-plate": e = ElementType.DARK f = "Arceus-dark" elif self.held_item == "earth-plate": e = ElementType.GROUND f = "Arceus-ground" elif self.held_item == "fist-plate": e = ElementType.FIGHTING f = "Arceus-fighting" elif self.held_item == "flame-plate": e = ElementType.FIRE f = "Arceus-fire" elif self.held_item == "icicle-plate": e = ElementType.ICE f = "Arceus-ice" elif self.held_item == "insect-plate": e = ElementType.BUG f = "Arceus-bug" elif self.held_item == "iron-plate": e = ElementType.STEEL f = "Arceus-steel" elif self.held_item == "meadow-plate": e = ElementType.GRASS f = "Arceus-grass" elif self.held_item == "mind-plate": e = ElementType.PSYCHIC f = "Arceus-psychic" elif self.held_item == "pixie-plate": e = ElementType.FAIRY f = "Arceus-fairy" elif self.held_item == "sky-plate": e = ElementType.FLYING f = "Arceus-flying" elif self.held_item == "splash-plate": e = ElementType.WATER f = "Arceus-water" elif self.held_item == "spooky-plate": e = ElementType.GHOST f = "Arceus-ghost" elif self.held_item == "stone-plate": e = ElementType.ROCK f = "Arceus-rock" elif self.held_item == "toxic-plate": e = ElementType.POISON f = "Arceus-poison" elif self.held_item == "zap-plate": e = ElementType.ELECTRIC f = "Arceus-electric" if e is not None and self.form(f): self.type_ids = [e] t = ElementType(e).name.lower() msg += f"{self.name} transformed into a {t} type using its multitype!\n" if self.ability() == Ability.RKS_SYSTEM and self._name == "Silvally": e = None if self.held_item == "dragon-memory": if self.form("Silvally-dragon"): e = ElementType.DRAGON elif self.held_item == "dark-memory": if self.form("Silvally-dark"): e = ElementType.DARK elif self.held_item == "ground-memory": if self.form("Silvally-ground"): e = ElementType.GROUND elif self.held_item == "fighting-memory": if self.form("Silvally-fighting"): e = ElementType.FIGHTING elif self.held_item == "fire-memory": if self.form("Silvally-fire"): e = ElementType.FIRE elif self.held_item == "ice-memory": if self.form("Silvally-ice"): e = ElementType.ICE elif self.held_item == "bug-memory": if self.form("Silvally-bug"): e = ElementType.BUG elif self.held_item == "steel-memory": if self.form("Silvally-steel"): e = ElementType.STEEL elif self.held_item == "grass-memory": if self.form("Silvally-grass"): e = ElementType.GRASS elif self.held_item == "psychic-memory": if self.form("Silvally-psychic"): e = ElementType.PSYCHIC elif self.held_item == "fairy-memory": if self.form("Silvally-fairy"): e = ElementType.FAIRY elif self.held_item == "flying-memory": if self.form("Silvally-flying"): e = ElementType.FLYING elif self.held_item == "water-memory": if self.form("Silvally-water"): e = ElementType.WATER elif self.held_item == "ghost-memory": if self.form("Silvally-ghost"): e = ElementType.GHOST elif self.held_item == "rock-memory": if self.form("Silvally-rock"): e = ElementType.ROCK elif self.held_item == "poison-memory": if self.form("Silvally-poison"): e = ElementType.POISON elif self.held_item == "electric-memory": if self.form("Silvally-electric"): e = ElementType.ELECTRIC if e is not None: self.type_ids = [e] t = ElementType(e).name.lower() msg += f"{self.name} transformed into a {t} type using its rks system!\n" if self.ability() == Ability.TRUANT: self.truant_turn = 0 if self.ability() == Ability.FORECAST and self._name in ("Castform", "Castform-snowy", "Castform-rainy", "Castform-sunny"): weather = battle.weather.get() element = None if weather == "hail" and self._name != "Castform-snowy": if self.form("Castform-snowy"): element = ElementType.ICE elif weather in ("sandstorm", "h-wind", None) and self._name != "Castform": if self.form("Castform"): element = ElementType.NORMAL elif weather in ("rain", "h-rain") and self._name != "Castform-rainy": if self.form("Castform-rainy"): element = ElementType.WATER elif weather in ("sun", "h-sun") and self._name != "Castform-sunny": if self.form("Castform-sunny"): element = ElementType.FIRE if element is not None: self.type_ids = [element] t = ElementType(element).name.lower() msg += f"{self.name} transformed into a {t} type using its forecast!\n" if self.ability() == Ability.MIMICRY and battle.terrain.item: terrain = battle.terrain.item if terrain == "electric": element = ElementType.ELECTRIC elif terrain == "grassy": element = ElementType.GRASS elif terrain == "misty": element = ElementType.FAIRY elif terrain == "psychic": element = ElementType.PSYCHIC self.type_ids = [element] t = ElementType(element).name.lower() msg += f"{self.name} transformed into a {t} type using its mimicry!\n" if self.ability() == Ability.WIND_RIDER and self.owner.tailwind.active(): msg += self.append_attack(1, attacker=self, source="its wind rider") if self.ability() == Ability.SUPERSWEET_SYRUP and not self.supersweet_syrup and otherpoke is not None: msg += otherpoke.append_evasion(-1, attacker=self, source=f"{self.name}'s supersweet syrup") return msg def remove(self, battle, *, fainted=False): """ Clean up a poke when it is removed from battle. Returns a formatted message of anything that happened while switching out. """ msg = "" if not fainted: if self.ability() == Ability.NATURAL_CURE and self.nv.current: msg += f"{self.name}'s {self.nv.current} was cured by its natural cure!\n" self.nv.reset() if self.ability() == Ability.REGENERATOR: msg += self.heal(self.starting_hp // 3, source="its regenerator") if self.ability() == Ability.ZERO_TO_HERO: if self.form("Palafin-hero"): msg += f"{self.name} is ready to be a hero!\n" self.nv.badly_poisoned_turn = 0 self.minimized = False self.has_moved = False self.choice_move = None self.last_move = None self.should_mega_evolve = False self.swapped_in = False self.active_turns = 0 if self.illusion__name is not None: self._name = self.illusion__name self.name = self.illusion_name self.illusion__name = None self.illusion_name = None if self._starting_name in ("Ditto", "Smeargle", "Mew", "Aegislash"): self._name = self._starting_name if self._nickname != "None": self.name = f"{self._nickname} ({self._name.replace('-', ' ')})" else: self.name = self._name.replace("-", " ") if self.owner.current_pokemon is self: self.owner.current_pokemon = None if battle.weather.recheck_ability_weather(): msg += "The weather cleared!\n" self.attack = self.base_stats[self._name][1] self.defense = self.base_stats[self._name][2] self.spatk = self.base_stats[self._name][3] self.spdef = self.base_stats[self._name][4] self.speed = self.base_stats[self._name][5] self.hpiv = self.starting_hpiv self.atkiv = self.starting_atkiv self.defiv = self.starting_defiv self.spatkiv = self.starting_spatkiv self.spdefiv = self.starting_spdefiv self.speediv = self.starting_speediv self.hpev = self.starting_hpev self.atkev = self.starting_atkev self.defev = self.starting_defev self.spatkev = self.starting_spatkev self.spdefev = self.starting_spdefev self.speedev = self.starting_speedev self.moves = self.starting_moves.copy() self.ability_id = self.starting_ability_id self.type_ids = self.starting_type_ids.copy() self.attack_stage = 0 self.defense_stage = 0 self.spatk_stage = 0 self.spdef_stage = 0 self.speed_stage = 0 self.accuracy_stage = 0 self.evasion_stage = 0 self.metronome = Metronome() self.leech_seed = False self.stockpile = 0 self.flinched = False self.confusion = ExpiringEffect(0) self.last_move_damage = None self.locked_move = None self.bide = None self.torment = False self.imprison = False self.disable = ExpiringItem() self.taunt = ExpiringEffect(0) self.encore = ExpiringItem() self.heal_block = ExpiringEffect(0) self.focus_energy = False self.perish_song = ExpiringEffect(0) self.nightmare = False self.defense_curl = False self.fury_cutter = 0 self.bind = ExpiringEffect(0) self.substitute = 0 self.silenced = ExpiringEffect(0) self.last_move_failed = False self.rage = False self.mind_reader = ExpiringItem() self.destiny_bond = False self.destiny_bond_cooldown = ExpiringEffect(0) self.trapping = False self.ingrain = False self.infatuated = None self.aqua_ring = False self.magnet_rise = ExpiringEffect(0) self.dive = False self.dig = False self.fly = False self.shadow_force = False self.lucky_chant = ExpiringEffect(0) self.grounded_by_move = False self.charge = ExpiringEffect(0) self.uproar = ExpiringEffect(0) self.magic_coat = False self.power_trick = False self.power_shift = False self.yawn = ExpiringEffect(0) self.ion_deluge = False self.electrify = False self.protection_used = False self.protection_chance = 1 self.protect = False self.endure = False self.wide_guard = False self.crafty_shield = False self.king_shield = False self.spiky_shield = False self.mat_block = False self.baneful_bunker = False self.quick_guard = False self.obstruct = False self.silk_trap = False self.burning_bulwark = False self.laser_focus = ExpiringEffect(0) self.powdered = False self.snatching = False self.telekinesis = ExpiringEffect(0) self.embargo = ExpiringEffect(0) self.echoed_voice_power = 40 self.echoed_voice_used = False self.curse = False self.fairy_lock = ExpiringEffect(0) self.grudge = False self.foresight = False self.miracle_eye = False self.beak_blast = False self.no_retreat = False self.dmg_this_turn = False self.autotomize = 0 self.lansat_berry_ate = False self.micle_berry_ate = False self.flash_fire = False self.truant_turn = 0 self.cud_chew = ExpiringEffect(0) self.booster_energy = False self.stat_increased = False self.stat_decreased = False self.roost = False self.octolock = False self.attack_split = None self.spatk_split = None self.defense_split = None self.spdef_split = None self.tar_shot = False self.syrup_bomb = ExpiringEffect(0) self.held_item.ever_had_item = self.held_item.item is not None return msg def next_turn(self, otherpoke, battle): """ Updates this pokemon for a new turn. `otherpoke` may be None if the opponent fainted the previous turn. Returns a formatted message. """ msg = "" # This needs to be here, as swapping sets this value explicitly self.has_moved = False if not self.swapped_in: self.active_turns += 1 self.last_move_damage = None self.last_move_failed = False self.should_mega_evolve = False self.rage = False self.mind_reader.next_turn() self.charge.next_turn() self.destiny_bond_cooldown.next_turn() self.magic_coat = False self.ion_deluge = False self.electrify = False if not self.protection_used: self.protection_chance = 1 self.protection_used = False self.protect = False self.endure = False self.wide_guard = False self.crafty_shield = False self.king_shield = False self.spiky_shield = False self.mat_block = False self.baneful_bunker = False self.quick_guard = False self.obstruct = False self.silk_trap = False self.burning_bulwark = False self.laser_focus.next_turn() self.powdered = False self.snatching = False if not self.echoed_voice_used: self.echoed_voice_power = 40 self.grudge = False self.beak_blast = False self.dmg_this_turn = False if self.locked_move: if self.locked_move.next_turn(): self.locked_move = None # Just in case they never actually used the move to remove it self.dive = False self.dig = False self.fly = False self.shadow_force = False self.fairy_lock.next_turn() self.flinched = False self.truant_turn += 1 self.stat_increased = False self.stat_decreased = False self.roost = False self.syrup_bomb.next_turn() msg += self.nv.next_turn(battle) # Volatile status turn progression prev_disab_move = self.disable.item if self.disable.next_turn(): msg += f"{self.name}'s {prev_disab_move.pretty_name} is no longer disabled!\n" if self.taunt.next_turn(): msg += f"{self.name}'s taunt has ended!\n" if self.heal_block.next_turn(): msg += f"{self.name}'s heal block has ended!\n" if self.silenced.next_turn(): msg += f"{self.name}'s voice returned!\n" if self.magnet_rise.next_turn(): msg += f"{self.name}'s magnet rise has ended!\n" if self.lucky_chant.next_turn(): msg += f"{self.name} is no longer shielded by lucky chant!\n" if self.uproar.next_turn(): msg += f"{self.name} calms down!\n" if self.telekinesis.next_turn(): msg += f"{self.name} was released from telekinesis!\n" if self.embargo.next_turn(): msg += f"{self.name}'s embargo was lifted!\n" if self.yawn.next_turn(): msg += self.nv.apply_status("sleep", battle, attacker=self, source="drowsiness") if self.encore.next_turn(): msg += f"{self.name}'s encore is over!\n" if self.perish_song.next_turn(): msg += self.faint(battle, source="perish song") if self.encore.active() and self.encore.item.pp == 0: self.encore.end() msg += f"{self.name}'s encore is over!\n" if self.cud_chew.next_turn() and self.held_item.last_used is not None and self.held_item.last_used.name.endswith("-berry"): self.held_item.recover(self.held_item) msg += self.held_item.eat_berry() self.held_item.last_used = None # Held Items if self.held_item == "white-herb": changed = False if self.attack_stage < 0: self.attack_stage = 0 changed = True if self.defense_stage < 0: self.defense_stage = 0 changed = True if self.spatk_stage < 0: self.spatk_stage = 0 changed = True if self.spdef_stage < 0: self.spdef_stage = 0 changed = True if self.speed_stage < 0: self.speed_stage = 0 changed = True if self.accuracy_stage < 0: self.accuracy_stage = 0 changed = True if self.evasion_stage < 0: self.evasion_stage = 0 changed = True if changed: msg += f"{self.name}'s white herb reset all negative stat stage changes.\n" self.held_item.use() if self.held_item == "toxic-orb": msg += self.nv.apply_status("b-poison", battle, attacker=self, source="its toxic orb") if self.held_item == "flame-orb": msg += self.nv.apply_status("burn", battle, attacker=self, source="its flame orb") if self.held_item == "leftovers": msg += self.heal(self.starting_hp // 16, source="its leftovers") if self.held_item == "black-sludge": if ElementType.POISON in self.type_ids: msg += self.heal(self.starting_hp // 16, source="its black sludge") else: msg += self.damage(self.starting_hp // 8, battle, source="its black sludge") # Abilities if self.ability() == Ability.SPEED_BOOST and not self.swapped_in: msg += self.append_speed(1, attacker=self, source="its Speed boost") if self.ability() == Ability.LIMBER and self.nv.paralysis(): self.nv.reset() msg += f"{self.name}'s limber cured it of its paralysis!\n" if self.ability() == Ability.INSOMNIA and self.nv.sleep(): self.nv.reset() msg += f"{self.name}'s insomnia woke it up!\n" if self.ability() == Ability.VITAL_SPIRIT and self.nv.sleep(): self.nv.reset() msg += f"{self.name}'s vital spirit woke it up!\n" if self.ability() == Ability.IMMUNITY and self.nv.poison(): self.nv.reset() msg += f"{self.name}'s immunity cured it of its poison!\n" if self.ability() == Ability.MAGMA_ARMOR and self.nv.freeze(): self.nv.reset() msg += f"{self.name}'s magma armor cured it of thawed it!\n" if self.ability() in (Ability.WATER_VEIL, Ability.WATER_BUBBLE) and self.nv.burn(): self.nv.reset() ability_name = Ability(self.ability_id).pretty_name msg += f"{self.name}'s {ability_name} cured it of its burn!\n" if self.ability() == Ability.OWN_TEMPO and self.confusion.active(): self.confusion.set_turns(0) msg += f"{self.name}'s tempo cured it of its confusion!\n" if self.ability() == Ability.OBLIVIOUS: if self.infatuated: self.infatuated = None msg += f"{self.name} fell out of love because of its obliviousness!\n" if self.taunt.active(): self.taunt.set_turns(0) msg += f"{self.name} stopped caring about being taunted because of its obliviousness!\n" if self.ability() == Ability.RAIN_DISH and battle.weather.get() in ("rain", "h-rain"): msg += self.heal(self.starting_hp // 16, source="its rain dish") if self.ability() == Ability.ICE_BODY and battle.weather.get() == "hail": msg += self.heal(self.starting_hp // 16, source="its ice body") if self.ability() == Ability.DRY_SKIN: if battle.weather.get() in ("rain", "h-rain"): msg += self.heal(self.starting_hp // 8, source="its dry skin") elif battle.weather.get() in ("sun", "h-sun"): msg += self.damage(self.starting_hp // 8, battle, source="its dry skin") if self.ability() == Ability.SOLAR_POWER and battle.weather.get() in ("sun", "h-sun"): msg += self.damage(self.starting_hp // 8, battle, source="its solar power") if self.ability() == Ability.MOODY: stats = [ (self.attack_stage, "attack"), (self.defense_stage, "defense"), (self.spatk_stage, "special attack"), (self.spdef_stage, "special defense"), (self.speed_stage, "speed") ] add_stats = stats.copy() remove_stats = stats.copy() for stat in stats: if stat[0] == 6: add_stats.remove(stat) add_stat = None if add_stats: add_stat = random.choice(add_stats) msg += self.append_stat(2, self, None, add_stat[1], "its moodiness") for stat in stats: if stat == add_stat: remove_stats.remove(stat) if stat[0] == -6: remove_stats.remove(stat) if remove_stats: remove_stat = random.choice(remove_stats) msg += self.append_stat(-1, self, None, remove_stat[1], "its moodiness") if ( self.ability() == Ability.PICKUP and not self.held_item.has_item() and otherpoke is not None and otherpoke.held_item.last_used is not None ): self.held_item.recover(otherpoke.held_item) msg += f"{self.name} picked up a {self.held_item.name}!\n" if self.ability() == Ability.ICE_FACE and not self.ice_repaired and self._name == "Eiscue-noice" and battle.weather.get() == "hail": if self.form("Eiscue"): self.ice_repaired = True msg += f"{self.name}'s ice face was restored by the hail!\n" if self.ability() == Ability.HARVEST and self.last_berry is not None and not self.held_item.has_item(): if random.randint(0, 1): self.held_item.item = self.last_berry self.last_berry = None msg += f"{self.name} harvested a {self.held_item.name}!\n" if self.ability() == Ability.ZEN_MODE and self._name == "Darmanitan" and self.hp < self.starting_hp / 2: if self.form("Darmanitan-zen"): if ElementType.PSYCHIC not in self.type_ids: self.type_ids.append(ElementType.PSYCHIC) msg += f"{self.name} enters a zen state.\n" if self.ability() == Ability.ZEN_MODE and self._name == "Darmanitan-galar" and self.hp < self.starting_hp / 2: if self.form("Darmanitan-zen-galar"): if ElementType.FIRE not in self.type_ids: self.type_ids.append(ElementType.FIRE) msg += f"{self.name} enters a zen state.\n" if self.ability() == Ability.ZEN_MODE and self._name == "Darmanitan-zen" and self.hp >= self.starting_hp / 2: if self.form("Darmanitan"): if ElementType.PSYCHIC in self.type_ids: self.type_ids.remove(ElementType.PSYCHIC) msg += f"{self.name}'s zen state ends!\n" if self.ability() == Ability.ZEN_MODE and self._name == "Darmanitan-zen-galar" and self.hp >= self.starting_hp / 2: if self.form("Darmanitan-galar"): if ElementType.FIRE in self.type_ids: self.type_ids.remove(ElementType.FIRE) msg += f"{self.name}'s zen state ends!\n" if self.ability() == Ability.SHIELDS_DOWN and self._name == "Minior" and self.hp < self.starting_hp / 2: if self.id % 7 == 0: new_form = "Minior-red" elif self.id % 7 == 1: new_form = "Minior-orange" elif self.id % 7 == 2: new_form = "Minior-yellow" elif self.id % 7 == 3: new_form = "Minior-green" elif self.id % 7 == 4: new_form = "Minior-blue" elif self.id % 7 == 5: new_form = "Minior-indigo" else: new_form = "Minior-violet" if self.form(new_form): msg += f"{self.name}'s core was exposed!\n" if ( self.ability() == Ability.SHIELDS_DOWN and self._name in ("Minior-red", "Minior-orange", "Minior-yellow", "Minior-green", "Minior-blue", "Minior-indigo", "Minior-violet") and self.hp >= self.starting_hp / 2 ): if self.form("Minior"): msg += f"{self.name}'s shell returned!\n" if self.ability() == Ability.SCHOOLING and self._name == "Wishiwashi-school" and self.hp < self.starting_hp / 4: if self.form("Wishiwashi"): msg += f"{self.name}'s school is gone!\n" if self.ability() == Ability.SCHOOLING and self._name == "Wishiwashi" and self.hp >= self.starting_hp / 4 and self.level >= 20: if self.form("Wishiwashi-school"): msg += f"{self.name} schools together!\n" if self.ability() == Ability.POWER_CONSTRUCT and self._name in ("Zygarde", "Zygarde-10") and self.hp < self.starting_hp / 2 and self.hp > 0: if self.form("Zygarde-complete"): msg += f"{self.name} is at full power!\n" # Janky way to raise the current HP of this poke, as it's new form has a higher HP stat. Note, this is NOT healing. new_hp = round((((2 * self.base_stats["Zygarde-complete"][0] + self.hpiv + (self.hpev / 4)) * self.level) / 100) + self.level + 10) self.hp = new_hp - (self.starting_hp - self.hp) self.starting_hp = new_hp if self.ability() == Ability.HUNGER_SWITCH: if self._name == "Morpeko": self.form("Morpeko-hangry") elif self._name == "Morpeko-hangry": self.form("Morpeko") if self.ability() == Ability.FLOWER_GIFT and self._name == "Cherrim" and battle.weather.get() in ("sun", "h-sun"): self.form("Cherrim-sunshine") if self.ability() == Ability.FLOWER_GIFT and self._name == "Cherrim-sunshine" and battle.weather.get() not in ("sun", "h-sun"): self.form("Cherrim") #Bad Dreams if otherpoke is not None and otherpoke.ability() == Ability.BAD_DREAMS and self.nv.sleep(): msg += self.damage(self.starting_hp // 8, battle, source=f"{otherpoke.name}'s bad dreams") #Leech seed if self.leech_seed and otherpoke is not None: damage = self.starting_hp // 8 msg += self.damage(damage, battle, attacker=otherpoke, drain_heal_ratio=1, source="leech seed") #Curse if self.curse: msg += self.damage(self.starting_hp // 4, battle, source="its curse") #Syrup bomb if self.syrup_bomb.active() and otherpoke is not None: msg += self.append_speed(-1, attacker=otherpoke, source="its syrup coating") #Weather damages if self.ability() == Ability.OVERCOAT: pass elif self.held_item == "safety-goggles": pass elif battle.weather.get() == "sandstorm": if ( ElementType.ROCK not in self.type_ids and ElementType.GROUND not in self.type_ids and ElementType.STEEL not in self.type_ids and self.ability() not in (Ability.SAND_RUSH, Ability.SAND_VEIL, Ability.SAND_FORCE) ): msg += self.damage(self.starting_hp // 16, battle, source="the sandstorm") elif battle.weather.get() == "hail": if ( ElementType.ICE not in self.type_ids and self.ability() not in (Ability.SNOW_CLOAK, Ability.ICE_BODY) ): msg += self.damage(self.starting_hp // 16, battle, source="the hail") #Bind if self.bind.next_turn(): msg += f"{self.name} is no longer bound!\n" elif self.bind.active() and otherpoke is not None: if otherpoke.held_item == "binding-band": msg += self.damage(self.starting_hp // 6, battle, source=f"{otherpoke.name}'s bind") else: msg += self.damage(self.starting_hp // 8, battle, source=f"{otherpoke.name}'s bind") #Ingrain if self.ingrain: heal = self.starting_hp // 16 if self.held_item == "big_root": heal = int(heal * 1.3) msg += self.heal(heal, source="ingrain") #Aqua Ring if self.aqua_ring: heal = self.starting_hp // 16 if self.held_item == "big_root": heal = int(heal * 1.3) msg += self.heal(heal, source="aqua ring") #Octolock if self.octolock and otherpoke is not None: msg += self.append_defense(-1, attacker=self, source=f"{otherpoke.name}'s octolock") msg += self.append_spdef(-1, attacker=self, source=f"{otherpoke.name}'s octolock") #Grassy Terrain if battle.terrain.item == "grassy" and self.grounded(battle) and not self.heal_block.active(): msg += self.heal(self.starting_hp // 16, source="grassy terrain") #Goes at the end so everything in this func that checks it handles it correctly self.swapped_in = False return msg def faint(self, battle, *, move=None, attacker=None, source: str=""): """ Sets a pokemon's HP to zero and cleans it up. If a pokemon takes damage equal to its HP, use damage instead. This method ignores focus sash and sturdy, forcing the pokemon to faint. Returns a formatted message. """ msg = "" self.hp = 0 if source: source = f" from {source}" msg += f"{self.name} fainted{source}!\n" if move is not None and attacker is not None and self.destiny_bond and self.owner.has_alive_pokemon(): msg += attacker.faint(battle, source=f"{self.name}'s destiny bond") if move is not None and attacker is not None and attacker._name == "Greninja" and attacker.ability() == Ability.BATTLE_BOND: if attacker.form("Greninja-ash"): msg += f"{attacker.name}'s bond with its trainer has strengthened it!\n" if move is not None and self.grudge: move.pp = 0 msg += f"{move.pretty_name}'s pp was depleted!\n" if attacker is not None and attacker.ability() in (Ability.CHILLING_NEIGH, Ability.AS_ONE_ICE): msg += attacker.append_attack(1, attacker=attacker, source="its chilling neigh") if attacker is not None and attacker.ability() in (Ability.GRIM_NEIGH, Ability.AS_ONE_SHADOW): msg += attacker.append_spatk(1, attacker=attacker, source="its grim neigh") for poke in (battle.trainer1.current_pokemon, battle.trainer2.current_pokemon): if poke is not None and poke is not self and poke.ability() == Ability.SOUL_HEART: msg += poke.append_spatk( 1, attacker=poke, source="its soul heart" ) self.owner.retaliate.set_turns(2) self.owner.num_fainted += 1 msg += self.remove(battle, fainted=True) return msg def damage(self, damage, battle, *, move=None, move_type=None, attacker=None, critical=False, drain_heal_ratio=None, source: str=""): """ Applies a certain amount of damage to this pokemon. Returns a formatted message. """ msgadd, _ = self._damage(damage, battle, move=move, move_type=move_type, attacker=attacker, critical=critical, drain_heal_ratio=drain_heal_ratio, source=source) return msgadd def _damage(self, damage, battle, *, move=None, move_type=None, attacker=None, critical=False, drain_heal_ratio=None, source: str=""): """ Applies a certain amount of damage to this pokemon. Returns a formatted message and the amount of damage actually dealt. """ msg = "" # Don't go through with an attack if the poke is already dead. # If this is a bad idea for *some* reason, make sure to add an `attacker is self` check to INNARDS_OUT. if self.hp <= 0: return "", 0 previous_hp = self.hp damage = max(1, damage) # Magic guard if self.ability(attacker=attacker, move=move) == Ability.MAGIC_GUARD and move is None and attacker is not self: return f"{self.name}'s magic guard protected it from damage!\n", 0 # Substitute if ( self.substitute and move is not None and move.is_affected_by_substitute() and not move.is_sound_based() and (attacker is None or attacker.ability() != Ability.INFILTRATOR) ): # is_affected_by_substitute should be a superset of is_sound_based, but it's better to check both to be sure. msg += f"{self.name}'s substitute took {damage} damage{source}!\n" new_hp = max(0, self.substitute - damage) true_damage = self.substitute - new_hp self.substitute = new_hp if self.substitute == 0: msg += f"{self.name}'s substitute broke!\n" return msg, true_damage # Damage blocking forms / abilities if move is not None: if self.ability(attacker=attacker, move=move) == Ability.DISGUISE and self._name == "Mimikyu": if self.form("Mimikyu-busted"): msg += f"{self.name}'s disguise was busted!\n" msg += self.damage(self.starting_hp // 8, battle, source="losing its disguise") return msg, 0 if self.ability(attacker=attacker, move=move) == Ability.ICE_FACE and self._name == "Eiscue" and move.damage_class == DamageClass.PHYSICAL: if self.form("Eiscue-noice"): msg += f"{self.name}'s ice face was busted!\n" return msg, 0 # OHKO protection self.dmg_this_turn = True if damage >= self.hp and move is not None: if self.endure: msg += f"{self.name} endured the hit!\n" damage = self.hp - 1 elif self.hp == self.starting_hp and self.ability(attacker=attacker, move=move) == Ability.STURDY: msg += f"{self.name} endured the hit with its Sturdy!\n" damage = self.hp - 1 elif self.hp == self.starting_hp and self.held_item == "focus-sash": msg += f"{self.name} held on using its focus sash!\n" damage = self.hp - 1 self.held_item.use() elif self.held_item == "focus-band" and not random.randrange(10): msg += f"{self.name} held on using its focus band!\n" damage = self.hp - 1 # Apply the damage dropped_below_half = self.hp > self.starting_hp / 2 dropped_below_quarter = self.hp > self.starting_hp / 4 new_hp = max(0, self.hp - damage) true_damage = self.hp - new_hp self.hp = new_hp dropped_below_half = dropped_below_half and self.hp <= self.starting_hp / 2 dropped_below_quarter = dropped_below_quarter and self.hp <= self.starting_hp / 4 if source: source = f" from {source}" msg += f"{self.name} took {damage} damage{source}!\n" self.num_hits += 1 # Drain if drain_heal_ratio is not None and attacker is not None: heal = true_damage * drain_heal_ratio if attacker.held_item == "big-root": heal = heal * 1.3 heal = int(heal) if self.ability() == Ability.LIQUID_OOZE: msg += attacker.damage(heal, battle, source=f"{self.name}'s liquid ooze") else: if not attacker.heal_block.active(): msg += attacker.heal(heal, source=source) if self.hp == 0: msg += self.faint(battle, move=move, attacker=attacker) if ( self.ability() == Ability.AFTERMATH and attacker is not None and attacker is not self and attacker.ability() != Ability.DAMP and move is not None and move.makes_contact(attacker) ): msg += attacker.damage(attacker.starting_hp // 4, battle, source=f"{self.name}'s aftermath") if attacker is not None and attacker.ability() == Ability.MOXIE: msg += attacker.append_attack(1, attacker=attacker, source="its moxie") if attacker is not None and attacker.ability() == Ability.BEAST_BOOST: stats = ( (attacker.get_raw_attack(), attacker.append_attack), (attacker.get_raw_defense(), attacker.append_defense), (attacker.get_raw_spatk(), attacker.append_spatk), (attacker.get_raw_spdef(), attacker.append_spdef), (attacker.get_raw_speed(), attacker.append_speed), ) append_func = sorted(stats, reverse=True, key=lambda s: s[0])[0][1] msg += append_func(1, attacker=attacker, source="its beast boost") if attacker is not None and self.ability() == Ability.INNARDS_OUT: msg += attacker.damage(previous_hp, battle, attacker=self, source=f"{self.name}'s innards out") elif move is not None and move_type is not None: if move_type == ElementType.FIRE and self.nv.freeze(): self.nv.reset() msg += f"{self.name} thawed out!\n" if move.effect in (458, 500) and self.nv.freeze(): self.nv.reset() msg += f"{self.name} thawed out!\n" if self.ability() == Ability.COLOR_CHANGE and move_type not in self.type_ids: self.type_ids = [move_type] t = ElementType(move_type).name.lower() msg += f"{self.name} changed its color, transforming into a {t} type!\n" if self.ability() == Ability.ANGER_POINT and critical: msg += self.append_attack(6, attacker=self, source="its anger point") if self.ability() == Ability.WEAK_ARMOR and move.damage_class == DamageClass.PHYSICAL and attacker is not self: msg += self.append_defense(-1, attacker=self, source="its weak armor") msg += self.append_speed(2, attacker=self, source="its weak armor") if self.ability() == Ability.JUSTIFIED and move_type == ElementType.DARK: msg += self.append_attack(1, attacker=self, source="justified") if self.ability() == Ability.RATTLED and move_type in (ElementType.BUG, ElementType.DARK, ElementType.GHOST): msg += self.append_speed(1, attacker=self, source="its rattled") if self.ability() == Ability.STAMINA: msg += self.append_defense(1, attacker=self, source="its stamina") if self.ability() == Ability.WATER_COMPACTION and move_type == ElementType.WATER: msg += self.append_defense(2, attacker=self, source="its water compaction") if self.ability() == Ability.BERSERK and dropped_below_half: msg += self.append_spatk(1, attacker=self, source="its berserk") if self.ability() == Ability.ANGER_SHELL and dropped_below_half: msg += self.append_attack(1, attacker=self, source="its anger shell") msg += self.append_spatk(1, attacker=self, source="its anger shell") msg += self.append_speed(1, attacker=self, source="its anger shell") msg += self.append_defense(-1, attacker=self, source="its anger shell") msg += self.append_spdef(-1, attacker=self, source="its anger shell") if self.ability() == Ability.STEAM_ENGINE and move_type in (ElementType.FIRE, ElementType.WATER): msg += self.append_speed(6, attacker=self, source="its steam engine") if self.ability() == Ability.THERMAL_EXCHANGE and move_type == ElementType.FIRE: msg += self.append_attack(1, attacker=self, source="its thermal exchange") if self.ability() == Ability.WIND_RIDER and move.is_wind(): msg += self.append_attack(1, attacker=self, source="its wind rider") if self.ability() == Ability.COTTON_DOWN and attacker is not None: msg += attacker.append_speed(-1, attacker=self, source=f"{self.name}'s cotton down") if self.ability() == Ability.SAND_SPIT: msg += battle.weather.set("sandstorm", self) if self.ability() == Ability.SEED_SOWER and battle.terrain.item is None: msg += battle.terrain.set("grassy", self) if self.ability() == Ability.ELECTROMORPHOSIS: self.charge.set_turns(2) msg += f"{self.name} became charged by its electromorphosis!\n" if self.ability() == Ability.WIND_POWER and move.is_wind(): self.charge.set_turns(2) msg += f"{self.name} became charged by its wind power!\n" if self.ability() == Ability.TOXIC_DEBRIS and move.damage_class == DamageClass.PHYSICAL: if attacker is not None and attacker is not self and attacker.owner.toxic_spikes < 2: attacker.owner.toxic_spikes += 1 msg += f"Toxic spikes were scattered around the feet of {attacker.owner.name}'s team because of {self.name}'s toxic debris!\n" if self.illusion_name is not None: self._name = self.illusion__name self.name = self.illusion_name self.illusion__name = None self.illusion_name = None msg += f"{self.name}'s illusion broke!\n" if self.held_item == "air-balloon": self.held_item.remove() msg += f"{self.name}'s air balloon popped!\n" if move is not None: self.last_move_damage = (max(1, damage), move.damage_class) if self.bide is not None: self.bide += damage if self.rage: msg += self.append_attack(1, attacker=self, source="its rage") if attacker is not None: if ( self.ability() == Ability.CURSED_BODY and not attacker.disable.active() and move in attacker.moves and random.randint(1, 100) <= 30 ): if attacker.ability() == Ability.AROMA_VEIL: msg += f"{attacker.name}'s aroma veil protects its move from being disabled!\n" else: attacker.disable.set(move, 4) msg += f"{attacker.name}'s {move.pretty_name} was disabled by {self.name}'s cursed body!\n" if attacker.ability() == Ability.MAGICIAN and not attacker.held_item.has_item() and self.held_item.can_remove(): self.held_item.transfer(attacker.held_item) msg += f"{attacker.name} stole {attacker.held_item.name} using its magician!\n" if attacker.ability() == Ability.TOXIC_CHAIN and random.randint(1, 100) <= 30: msg += self.nv.apply_status("b-poison", battle, attacker=attacker, source=f"{attacker.name}'s toxic chain") if attacker.held_item == "shell-bell": # Shell bell does not trigger when a move is buffed by sheer force. if attacker.ability() != Ability.SHEER_FORCE or move.effect_chance is None: msg += attacker.heal(damage // 8, source="its shell bell") # Retreat if dropped_below_half and sum(x.hp > 0 for x in self.owner.party) > 1: if self.ability() == Ability.WIMP_OUT: msg += f"{self.name} wimped out and retreated!\n" msg += self.remove(battle) elif self.ability() == Ability.EMERGENCY_EXIT: msg += f"{self.name} used the emergency exit and retreated!\n" msg += self.remove(battle) # Gulp Missile if attacker is not None and self._name in ("Cramorant-gulping", "Cramorant-gorging") and self.owner.has_alive_pokemon(): prey = "pikachu" if self._name == "Cramorant-gorging" else "arrokuda" if self.form("Cramorant"): msg += attacker.damage(attacker.starting_hp // 4, battle, source=f"{self.name} spitting out its {prey}") if prey == "arrokuda": msg += attacker.append_defense(-1, attacker=self, source=f"{self.name} spitting out its {prey}") elif prey == "pikachu": msg += attacker.nv.apply_status("paralysis", battle, attacker=self, source=f"{self.name} spitting out its {prey}") # Berries if self.held_item.should_eat_berry_damage(attacker): msg += self.held_item.eat_berry(attacker=attacker, move=move) # Contact if move is not None and attacker is not None and move.makes_contact(attacker): # Affects ATTACKER if attacker.held_item != "protective-pads": if self.beak_blast: msg += attacker.nv.apply_status("burn", battle, attacker=attacker, source=f"{self.name}'s charging beak blast") if self.ability() == Ability.STATIC: if random.randint(1, 100) <= 30: msg += attacker.nv.apply_status("paralysis", battle, attacker=attacker, source=f"{self.name}'s static") if self.ability() == Ability.POISON_POINT: if random.randint(1, 100) <= 30: msg += attacker.nv.apply_status("poison", battle, attacker=attacker, source=f"{self.name}'s poison point") if self.ability() == Ability.FLAME_BODY: if random.randint(1, 100) <= 30: msg += attacker.nv.apply_status("burn", battle, attacker=attacker, source=f"{self.name}'s flame body") if self.ability() == Ability.ROUGH_SKIN and self.owner.has_alive_pokemon(): msg += attacker.damage(attacker.starting_hp // 8, battle, source=f"{self.name}'s rough skin") if self.ability() == Ability.IRON_BARBS and self.owner.has_alive_pokemon(): msg += attacker.damage(attacker.starting_hp // 8, battle, source=f"{self.name}'s iron barbs") if self.ability() == Ability.EFFECT_SPORE: if ( attacker.ability() != Ability.OVERCOAT and ElementType.GRASS not in attacker.type_ids and attacker.held_item != "safety-glasses" and random.randint(1, 100) <= 30 ): status = random.choice(["paralysis", "poison", "sleep"]) msg += attacker.nv.apply_status(status, battle, attacker=attacker) if self.ability() == Ability.CUTE_CHARM and random.randint(1, 100) <= 30: msg += attacker.infatuate(self, source=f"{self.name}'s cute charm") if self.ability() == Ability.MUMMY and attacker.ability() != Ability.MUMMY and attacker.ability_changeable(): attacker.ability_id = Ability.MUMMY msg += f"{attacker.name} gained mummy from {self.name}!\n" msg += attacker.send_out_ability(self, battle) if self.ability() == Ability.LINGERING_AROMA and attacker.ability() != Ability.LINGERING_AROMA and attacker.ability_changeable(): attacker.ability_id = Ability.LINGERING_AROMA msg += f"{attacker.name} gained lingering aroma from {self.name}!\n" msg += attacker.send_out_ability(self, battle) if self.ability() == Ability.GOOEY: msg += attacker.append_speed(-1, attacker=self, source=f"touching {self.name}'s gooey body") if self.ability() == Ability.TANGLING_HAIR: msg += attacker.append_speed(-1, attacker=self, source=f"touching {self.name}'s tangled hair") if self.held_item == "rocky-helmet" and self.owner.has_alive_pokemon(): msg += attacker.damage(attacker.starting_hp // 6, battle, source=f"{self.name}'s rocky helmet") # Pickpocket is not included in the protective pads protection if ( self.ability() == Ability.PICKPOCKET and not self.held_item.has_item() and attacker.held_item.has_item() and attacker.held_item.can_remove() ): if attacker.ability() == Ability.STICKY_HOLD: msg += f"{attacker.name}'s sticky hand kept hold of its item!\n" else: attacker.held_item.transfer(self.held_item) msg += f"{attacker.name}'s {self.held_item.name} was stolen!\n" # Affects DEFENDER if attacker.ability() == Ability.POISON_TOUCH: if random.randint(1, 100) <= 30: msg += self.nv.apply_status("poison", battle, attacker=attacker, move=move, source=f"{attacker.name}'s poison touch") # Affects BOTH if attacker.held_item != "protective-pads": if self.ability() == Ability.PERISH_BODY and not attacker.perish_song.active(): attacker.perish_song.set_turns(4) self.perish_song.set_turns(4) msg += f"All pokemon will faint after 3 turns from {self.name}'s perish body!\n" if self.ability() == Ability.WANDERING_SPIRIT and attacker.ability_changeable() and attacker.ability_giveable(): msg += f"{attacker.name} swapped abilities with {self.name} because of {self.name}'s wandering spirit!\n" self.ability_id, attacker.ability_id = attacker.ability_id, self.ability_id ability_name = Ability(self.ability_id).pretty_name msg += f"{self.name} acquired {ability_name}!\n" msg += self.send_out_ability(attacker, battle) ability_name = Ability(attacker.ability_id).pretty_name msg += f"{attacker.name} acquired {ability_name}!\n" msg += attacker.send_out_ability(self, battle) return msg, true_damage def heal(self, health, *, source: str=""): """ Heals a pokemon by a certain amount. Handles heal-affecting items and abilities, and keeping health within bounds. Returns a formatted message. """ msg = "" health = max(1, int(health)) #greater than is used to prevent zygarde's hp incresing form from losing its extra health if self.hp >= self.starting_hp: return msg #safety to prevent errors from ""reviving"" pokes if self.hp == 0: return msg if self.heal_block.active(): return msg health = min(self.starting_hp - self.hp, health) self.hp += health if source: source = f" from {source}" msg += f"{self.name} healed {health} hp{source}!\n" return msg def confuse(self, *, attacker=None, move=None, source=""): """ Attempts to confuse this poke. Returns a formatted message. """ if self.substitute and (move is None or move.is_affected_by_substitute()): return "" if self.confusion.active(): return "" if self.ability(move=move, attacker=attacker) == Ability.OWN_TEMPO: return "" self.confusion.set_turns(random.randint(2, 5)) if source: source = f" from {source}" msg = f"{self.name} is confused{source}!\n" if self.held_item.should_eat_berry_status(attacker): msg += self.held_item.eat_berry(attacker=attacker, move=move) return msg def flinch(self, *, attacker=None, move=None, source=""): """ Attepts to flinch this poke. Returns a formatted message. """ msg = "" if self.substitute and (move is None or move.is_affected_by_substitute()): return "" if self.ability(move=move, attacker=attacker) == Ability.INNER_FOCUS: return f"{self.name} resisted the urge to flinch with its inner focus!\n" self.flinched = True if source: source = f" from {source}" msg += f"{self.name} flinched{source}!\n" if self.ability() == Ability.STEADFAST: msg += self.append_speed(1, attacker=self, source="its steadfast") return msg def infatuate(self, attacker, *, move=None, source=""): """ Attepts to cause attacker to infatuate this poke. Returns a formatted message. """ msg = "" if source: source = f" from {source}" if "-x" in (self.gender, attacker.gender): return "" if self.gender == attacker.gender: return "" if self.ability(move=move, attacker=attacker) == Ability.OBLIVIOUS: return f"{self.name} is too oblivious to fall in love!\n" if self.ability(move=move, attacker=attacker) == Ability.AROMA_VEIL: return f"{self.name}'s aroma veil protects it from being infatuated!\n" self.infatuated = attacker msg += f"{self.name} fell in love{source}!\n" if self.held_item == "destiny-knot": msg += attacker.infatuate(self, source=f"{self.name}'s destiny knot") return msg def calculate_raw_stat(self, base, iv, ev, nature): """ Helper function to calculate a raw stat using the base, IV, EV, level, and nature. https://bulbapedia.bulbagarden.net/wiki/Stat#Determination_of_stats "In Generation III onward" """ return round((((2 * base + iv + (ev / 4)) * self.level) / 100) + 5) * nature @staticmethod def calculate_stat(stat, stat_stage, *, crop=False): """Calculates a stat based on that stat's stage changes.""" if crop == "bottom": stat_stage = max(stat_stage, 0) elif crop == "top": stat_stage = min(stat_stage, 0) stage_multiplier = { -6: 2/8, -5: 2/7, -4: 2/6, -3: 2/5, -2: 2/4, -1: 2/3, 0: 1, 1: 3/2, 2: 2, 3: 5/2, 4: 3, 5: 7/2, 6: 4, } return stage_multiplier[stat_stage] * stat def get_raw_attack(self, *, check_power_trick=True, check_power_shift=True): """Returns the raw attack of this poke, taking into account IVs EVs and natures and forms.""" if self.power_trick and check_power_trick: return self.get_raw_defense(check_power_trick=False, check_power_shift=check_power_shift) if self.power_shift and check_power_shift: return self.get_raw_defense(check_power_trick=check_power_trick, check_power_shift=False) stat = self.calculate_raw_stat(self.attack, self.atkiv, self.atkev, self.nature_stat_deltas["Attack"]) if self.attack_split is not None: stat = (stat + self.attack_split) // 2 return stat def get_raw_defense(self, *, check_power_trick=True, check_power_shift=True): """Returns the raw attack of this poke, taking into account IVs EVs and natures and forms.""" if self.power_trick and check_power_trick: return self.get_raw_attack(check_power_trick=False, check_power_shift=check_power_shift) if self.power_shift and check_power_shift: return self.get_raw_attack(check_power_trick=check_power_trick, check_power_shift=False) stat = self.calculate_raw_stat(self.defense, self.defiv, self.defev, self.nature_stat_deltas["Defense"]) if self.defense_split is not None: stat = (stat + self.defense_split) // 2 return stat def get_raw_spatk(self, *, check_power_shift=True): """Returns the raw attack of this poke, taking into account IVs EVs and natures and forms.""" if self.power_shift and check_power_shift: return self.get_raw_spdef(check_power_shift=False) stat = self.calculate_raw_stat(self.spatk, self.spatkiv, self.spatkev, self.nature_stat_deltas["Special attack"]) if self.spatk_split is not None: stat = (stat + self.spatk_split) // 2 return stat def get_raw_spdef(self, *, check_power_shift=True): """Returns the raw attack of this poke, taking into account IVs EVs and natures and forms.""" if self.power_shift and check_power_shift: return self.get_raw_spatk(check_power_shift=False) stat = self.calculate_raw_stat(self.spdef, self.spdefiv, self.spdefev, self.nature_stat_deltas["Special defense"]) if self.spdef_split is not None: stat = (stat + self.spdef_split) // 2 return stat def get_raw_speed(self): """Returns the raw attack of this poke, taking into account IVs EVs and natures and forms.""" return self.calculate_raw_stat(self.speed, self.speediv, self.speedev, self.nature_stat_deltas["Speed"]) def get_attack(self, battle, *, critical=False, ignore_stages=False): """Helper method to call calculate_stat for attack.""" attack = self.get_raw_attack() if not ignore_stages: attack = self.calculate_stat(attack, self.attack_stage, crop="bottom" if critical else False) if self.ability() == Ability.GUTS and self.nv.current: attack *= 1.5 if self.ability() == Ability.SLOW_START and self.active_turns < 5: attack *= .5 if self.ability() in (Ability.HUGE_POWER, Ability.PURE_POWER): attack *= 2 if self.ability() == Ability.HUSTLE: attack *= 1.5 if self.ability() == Ability.DEFEATIST and self.hp <= self.starting_hp / 2: attack *= .5 if self.ability() == Ability.GORILLA_TACTICS: attack *= 1.5 if self.ability() == Ability.FLOWER_GIFT and battle.weather.get() in ("sun", "h-sun"): attack *= 1.5 if self.ability() == Ability.ORICHALCUM_PULSE and battle.weather.get() in ("sun", "h-sun"): attack *= 4/3 if self.held_item == "choice-band": attack *= 1.5 if self.held_item == "light-ball" and self._name == "Pikachu": attack *= 2 if self.held_item == "thick-club" and self._name in ("Cubone", "Marowak", "Marowak-alola"): attack *= 2 for poke in (battle.trainer1.current_pokemon, battle.trainer2.current_pokemon): if poke is not None and poke is not self and poke.ability() == Ability.TABLETS_OF_RUIN: attack *= .75 if all(self.get_raw_attack() >= x for x in (self.get_raw_defense(), self.get_raw_spatk(), self.get_raw_spdef(), self.get_raw_speed())): if ( (self.ability() == Ability.PROTOSYNTHESIS and (battle.weather.get() in ("sun", "h-sun") or self.booster_energy)) or (self.ability() == Ability.QUARK_DRIVE and (battle.terrain.item == "electric" or self.booster_energy)) ): attack *= 1.3 elif self.ability() in (Ability.PROTOSYNTHESIS, Ability.QUARK_DRIVE) and self.held_item == "booster-energy": self.held_item.use() self.booster_energy = True attack *= 1.3 return attack def get_defense(self, battle, *, critical=False, ignore_stages=False, attacker=None, move=None): """Helper method to call calculate_stat for defense.""" if battle.wonder_room.active(): defense = self.get_raw_spdef() else: defense = self.get_raw_defense() if not ignore_stages: defense = self.calculate_stat(defense, self.defense_stage, crop="top" if critical else False) if self.ability(attacker=attacker, move=move) == Ability.MARVEL_SCALE and self.nv.current: defense *= 1.5 if self.ability(attacker=attacker, move=move) == Ability.FUR_COAT: defense *= 2 if self.ability(attacker=attacker, move=move) == Ability.GRASS_PELT and battle.terrain.item == "grassy": defense *= 1.5 if self.held_item == "eviolite" and self.can_still_evolve: defense *= 1.5 for poke in (battle.trainer1.current_pokemon, battle.trainer2.current_pokemon): if poke is not None and poke is not self and poke.ability() == Ability.SWORD_OF_RUIN: defense *= .75 if ( self.get_raw_defense() > self.get_raw_attack() and all(self.get_raw_defense() >= x for x in (self.get_raw_spatk(), self.get_raw_spdef(), self.get_raw_speed())) ): if ( (self.ability() == Ability.PROTOSYNTHESIS and (battle.weather.get() in ("sun", "h-sun") or self.booster_energy)) or (self.ability() == Ability.QUARK_DRIVE and (battle.terrain.item == "electric" or self.booster_energy)) ): defense *= 1.3 elif self.ability() in (Ability.PROTOSYNTHESIS, Ability.QUARK_DRIVE) and self.held_item == "booster-energy": self.held_item.use() self.booster_energy = True defense *= 1.3 return defense def get_spatk(self, battle, *, critical=False, ignore_stages=False): """Helper method to call calculate_stat for spatk.""" spatk = self.get_raw_spatk() if not ignore_stages: spatk = self.calculate_stat(spatk, self.spatk_stage, crop="bottom" if critical else False) if self.ability() == Ability.DEFEATIST and self.hp <= self.starting_hp / 2: spatk *= .5 if self.ability() == Ability.SOLAR_POWER and battle.weather.get() in ("sun", "h-sun"): spatk *= 1.5 if self.ability() == Ability.HADRON_ENGINE and battle.terrain.item == "grassy": spatk *= 4/3 if self.held_item == "choice-specs": spatk *= 1.5 if self.held_item == "deep-sea-tooth" and self._name == "Clamperl": spatk *= 2 if self.held_item == "light-ball" and self._name == "Pikachu": spatk *= 2 for poke in (battle.trainer1.current_pokemon, battle.trainer2.current_pokemon): if poke is not None and poke is not self and poke.ability() == Ability.VESSEL_OF_RUIN: spatk *= .75 if ( all(self.get_raw_spatk() >= x for x in (self.get_raw_spdef(), self.get_raw_speed())) and all(self.get_raw_spatk() > x for x in (self.get_raw_attack(), self.get_raw_defense())) ): if ( (self.ability() == Ability.PROTOSYNTHESIS and (battle.weather.get() in ("sun", "h-sun") or self.booster_energy)) or (self.ability() == Ability.QUARK_DRIVE and (battle.terrain.item == "electric" or self.booster_energy)) ): spatk *= 1.3 elif self.ability() in (Ability.PROTOSYNTHESIS, Ability.QUARK_DRIVE) and self.held_item == "booster-energy": self.held_item.use() self.booster_energy = True spatk *= 1.3 return spatk def get_spdef(self, battle, *, critical=False, ignore_stages=False, attacker=None, move=None): """Helper method to call calculate_stat for spdef.""" if battle.wonder_room.active(): spdef = self.get_raw_defense() else: spdef = self.get_raw_spdef() if not ignore_stages: spdef = self.calculate_stat(spdef, self.spdef_stage, crop="top" if critical else False) if battle.weather.get() == "sandstorm" and ElementType.ROCK in self.type_ids: spdef *= 1.5 if self.ability(attacker=attacker, move=move) == Ability.FLOWER_GIFT and battle.weather.get() in ("sun", "h-sun"): spdef *= 1.5 if self.held_item == "deep-sea-scale" and self._name == "Clamperl": spdef *= 2 if self.held_item == "assault-vest": spdef *= 1.5 if self.held_item == "eviolite" and self.can_still_evolve: spdef *= 1.5 for poke in (battle.trainer1.current_pokemon, battle.trainer2.current_pokemon): if poke is not None and poke is not self and poke.ability() == Ability.BEADS_OF_RUIN: spdef *= .75 if ( self.get_raw_spdef() >= self.get_raw_speed() and all(self.get_raw_spdef() > x for x in (self.get_raw_attack(), self.get_raw_defense(), self.get_raw_spatk())) ): if ( (self.ability() == Ability.PROTOSYNTHESIS and (battle.weather.get() in ("sun", "h-sun") or self.booster_energy)) or (self.ability() == Ability.QUARK_DRIVE and (battle.terrain.item == "electric" or self.booster_energy)) ): spdef *= 1.3 elif self.ability() in (Ability.PROTOSYNTHESIS, Ability.QUARK_DRIVE) and self.held_item == "booster-energy": self.held_item.use() self.booster_energy = True spdef *= 1.3 return spdef def get_speed(self, battle): """Helper method to call calculate_stat for speed.""" #Always active stage changes speed = self.calculate_stat(self.get_raw_speed(), self.speed_stage) if self.nv.paralysis() and not self.ability() == Ability.QUICK_FEET: speed //= 2 if self.held_item == "iron-ball": speed //= 2 if self.owner.tailwind.active(): speed *= 2 if self.ability() == Ability.SLUSH_RUSH and battle.weather.get() == "hail": speed *= 2 if self.ability() == Ability.SAND_RUSH and battle.weather.get() == "sandstorm": speed *= 2 if self.ability() == Ability.SWIFT_SWIM and battle.weather.get() in ("rain", "h-rain"): speed *= 2 if self.ability() == Ability.CHLOROPHYLL and battle.weather.get() in ("sun", "h-sun"): speed *= 2 if self.ability() == Ability.SLOW_START and self.active_turns < 5: speed *= .5 if self.ability() == Ability.UNBURDEN and not self.held_item.has_item() and self.held_item.ever_had_item: speed *= 2 if self.ability() == Ability.QUICK_FEET and self.nv.current: speed *= 1.5 if self.ability() == Ability.SURGE_SURFER and battle.terrain.item == "electric": speed *= 2 if self.held_item == "choice-scarf": speed *= 1.5 if all(self.get_raw_speed() > x for x in (self.get_raw_attack(), self.get_raw_defense(), self.get_raw_spatk(), self.get_raw_spdef())): if ( (self.ability() == Ability.PROTOSYNTHESIS and (battle.weather.get() in ("sun", "h-sun") or self.booster_energy)) or (self.ability() == Ability.QUARK_DRIVE and (battle.terrain.item == "electric" or self.booster_energy)) ): speed *= 1.5 elif self.ability() in (Ability.PROTOSYNTHESIS, Ability.QUARK_DRIVE) and self.held_item == "booster-energy": self.held_item.use() self.booster_energy = True speed *= 1.5 return speed def get_accuracy(self, battle): """Helper method to calculate accuracy stage.""" return self.accuracy_stage def get_evasion(self, battle): """Helper method to calculate evasion stage.""" return self.evasion_stage def append_attack(self, stage_change: int, *, attacker=None, move=None, source: str="", check_looping: bool=True): """Helper method to call append_stat for attack.""" return self.append_stat(stage_change, attacker, move, "attack", source, check_looping) def append_defense(self, stage_change: int, *, attacker=None, move=None, source: str="", check_looping: bool=True): """Helper method to call append_stat for defense.""" return self.append_stat(stage_change, attacker, move, "defense", source, check_looping) def append_spatk(self, stage_change: int, *, attacker=None, move=None, source: str="", check_looping: bool=True): """Helper method to call append_stat for special attack.""" return self.append_stat(stage_change, attacker, move, "special attack", source, check_looping) def append_spdef(self, stage_change: int, *, attacker=None, move=None, source: str="", check_looping: bool=True): """Helper method to call append_stat for special defense.""" return self.append_stat(stage_change, attacker, move, "special defense", source, check_looping) def append_speed(self, stage_change: int, *, attacker=None, move=None, source: str="", check_looping: bool=True): """Helper method to call append_stat for speed.""" return self.append_stat(stage_change, attacker, move, "speed", source, check_looping) def append_accuracy(self, stage_change: int, *, attacker=None, move=None, source: str="", check_looping: bool=True): """Helper method to call append_stat for accuracy.""" return self.append_stat(stage_change, attacker, move, "accuracy", source, check_looping) def append_evasion(self, stage_change: int, *, attacker=None, move=None, source: str="", check_looping: bool=True): """Helper method to call append_stat for evasion.""" return self.append_stat(stage_change, attacker, move, "evasion", source, check_looping) def append_stat(self, stage_change: int, attacker, move, stat: str, source: str, check_looping: bool=True): """ Adds a stat stage change to this pokemon. Returns a formatted string describing the stat change. """ msg = "" if self.substitute and attacker is not self and attacker is not None and (move is None or move.is_affected_by_substitute()): return "" if source: source = f" from {source}" delta_msgs = { -3: f"{self.name}'s {stat} severely fell{source}!\n", -2: f"{self.name}'s {stat} harshly fell{source}!\n", -1: f"{self.name}'s {stat} fell{source}!\n", 1: f"{self.name}'s {stat} rose{source}!\n", 2: f"{self.name}'s {stat} rose sharply{source}!\n", 3: f"{self.name}'s {stat} rose drastically{source}!\n", } delta = stage_change if self.ability(attacker=attacker, move=move) == Ability.SIMPLE: delta *= 2 if self.ability(attacker=attacker, move=move) == Ability.CONTRARY: delta *= -1 if stat == "attack": current_stage = self.attack_stage elif stat == "defense": current_stage = self.defense_stage elif stat == "special attack": current_stage = self.spatk_stage elif stat == "special defense": current_stage = self.spdef_stage elif stat == "speed": current_stage = self.speed_stage elif stat == "accuracy": current_stage = self.accuracy_stage elif stat == "evasion": current_stage = self.evasion_stage else: raise ValueError(f"invalid stat {stat}") # Cap stat stages within -6 to 6 if delta < 0: #-6 -5 -4 .. 2 # 0 -1 -2 .. -8 cap = (current_stage * -1) - 6 delta = max(delta, cap) if delta == 0: return f"{self.name}'s {stat} won't go any lower!\n" else: # 6 5 4 .. -2 # 0 1 2 .. 8 cap = (current_stage * -1) + 6 delta = min(delta, cap) if delta == 0: return f"{self.name}'s {stat} won't go any higher!\n" # Prevent stat changes if delta < 0 and attacker is not self: if self.ability(attacker=attacker, move=move) in (Ability.CLEAR_BODY, Ability.WHITE_SMOKE, Ability.FULL_METAL_BODY): ability_name = Ability(self.ability_id).pretty_name return f"{self.name}'s {ability_name} prevented its {stat} from being lowered!\n" if self.ability(attacker=attacker, move=move) == Ability.HYPER_CUTTER and stat == "attack": return f"{self.name}'s claws stayed sharp because of its hyper cutter!\n" if self.ability(attacker=attacker, move=move) == Ability.KEEN_EYE and stat == "accuracy": return f"{self.name}'s aim stayed true because of its keen eye!\n" if self.ability(attacker=attacker, move=move) == Ability.MINDS_EYE and stat == "accuracy": return f"{self.name}'s aim stayed true because of its mind's eye!\n" if self.ability(attacker=attacker, move=move) == Ability.BIG_PECKS and stat == "defense": return f"{self.name}'s defense stayed strong because of its big pecks!\n" if self.owner.mist.active() and (attacker is None or attacker.ability() != Ability.INFILTRATOR): return f"The mist around {self.name}'s feet prevented its {stat} from being lowered!\n" if self.ability(attacker=attacker, move=move) == Ability.FLOWER_VEIL and ElementType.GRASS in self.type_ids: return "" if self.ability(attacker=attacker, move=move) == Ability.MIRROR_ARMOR and attacker is not None and check_looping: msg += f"{self.name} reflected the stat change with its mirror armor!\n" msg += attacker.append_stat(delta, self, None, stat, "", check_looping=False) return msg # Remember if stats were changed for certain moves if delta > 0: self.stat_increased = True elif delta < 0: self.stat_decreased = True if stat == "attack": self.attack_stage += delta elif stat == "defense": self.defense_stage += delta elif stat == "special attack": self.spatk_stage += delta elif stat == "special defense": self.spdef_stage += delta elif stat == "speed": self.speed_stage += delta elif stat == "accuracy": self.accuracy_stage += delta elif stat == "evasion": self.evasion_stage += delta else: raise ValueError(f"invalid stat {stat}") formatted_delta = min(max(delta, -3), 3) msg += delta_msgs[formatted_delta] # TODO: fix this hacky way of doing this, but probably not until multi battles... battle = self.held_item.battle # Effects that happen after a pokemon gains stats if delta < 0: if attacker is not self: if self.ability(attacker=attacker, move=move) == Ability.DEFIANT: msg += self.append_attack(2, attacker=self, source="its defiance") if self.ability(attacker=attacker, move=move) == Ability.COMPETITIVE: msg += self.append_spatk(2, attacker=self, source="its competitiveness") if self.held_item == "eject-pack": # This assumes that neither attacker or poke are needed if not checking traps swaps = self.owner.valid_swaps(..., ..., check_trap=False) if swaps: msg += f"{self.name} is switched out by its eject pack!\n" self.held_item.use() msg += self.remove(battle) # Force this pokemon to immediately return to be attacked self.owner.mid_turn_remove = True elif delta > 0: for poke in (battle.trainer1.current_pokemon, battle.trainer2.current_pokemon): if poke is not None and poke is not self and poke.ability() == Ability.OPPORTUNIST and check_looping: msg += f"{poke.name} seizes the opportunity to boost its stat with its opportunist!\n" msg += poke.append_stat(delta, poke, None, stat, "", check_looping=False) return msg def grounded(self, battle, *, attacker=None, move=None): """ Returns True if this pokemon is considered "grounded". Explicit grounding applies first, then explicit ungrounding, then implicit grounding. """ if battle.gravity.active(): return True if self.held_item == "iron-ball": return True if self.grounded_by_move: return True if ElementType.FLYING in self.type_ids and not self.roost: return False if self.ability(attacker=attacker, move=move) == Ability.LEVITATE: return False if self.held_item == "air-balloon": return False if self.magnet_rise.active(): return False if self.telekinesis.active(): return False return True def transform(self, otherpoke): """Transforms this poke into otherpoke.""" self.choice_move = None self._name = otherpoke._name if self._nickname != "None": self.name = f"{self._nickname} ({self._name.replace('-', ' ')})" else: self.name = self._name.replace("-", " ") self.attack = otherpoke.attack self.defense = otherpoke.defense self.spatk = otherpoke.spatk self.spdef = otherpoke.spdef self.speed = otherpoke.speed self.hpiv = otherpoke.hpiv self.atkiv = otherpoke.atkiv self.defiv = otherpoke.defiv self.spatkiv = otherpoke.spatkiv self.spdefiv = otherpoke.spdefiv self.speediv = otherpoke.speediv self.hpev = otherpoke.hpev self.atkev = otherpoke.atkev self.defev = otherpoke.defev self.spatkev = otherpoke.spatkev self.spdefev = otherpoke.spdefev self.speedev = otherpoke.speedev self.moves = [move.copy() for move in otherpoke.moves] for m in self.moves: m.pp = 5 self.ability_id = otherpoke.ability_id self.type_ids = otherpoke.type_ids.copy() self.attack_stage = otherpoke.attack_stage self.defense_stage = otherpoke.defense_stage self.spatk_stage = otherpoke.spatk_stage self.spdef_stage = otherpoke.spdef_stage self.speed_stage = otherpoke.speed_stage self.accuracy_stage = otherpoke.accuracy_stage self.evasion_stage = otherpoke.evasion_stage def form(self, form: str): """ Changes this poke's form to the provided form. This changes its name and base stats, and may affect moves, abilities, and items. Returns True if the poke successfully reformed. """ if form not in self.base_stats: return False self._name = form if self._nickname != "None": self.name = f"{self._nickname} ({self._name.replace('-', ' ')})" else: self.name = self._name.replace("-", " ") self.attack = self.base_stats[self._name][1] self.defense = self.base_stats[self._name][2] self.spatk = self.base_stats[self._name][3] self.spdef = self.base_stats[self._name][4] self.speed = self.base_stats[self._name][5] self.attack_split = None self.spatk_split = None self.defense_split = None self.spdef_split = None self.autotomize = 0 return True def effectiveness(self, attacker_type: ElementType, battle, *, attacker=None, move=None): """Calculates a float representing the effectiveness of `attacker_type` damage on this poke.""" if attacker_type == ElementType.TYPELESS: return 1 effectiveness = 1 for defender_type in self.type_ids: if defender_type == ElementType.TYPELESS: continue if move is not None and move.effect == 380 and defender_type == ElementType.WATER: effectiveness *= 2 continue if move is not None and move.effect == 373 and defender_type == ElementType.FLYING and not self.grounded(battle, attacker=attacker, move=move): return 1 # Ignores secondary types if defender is flying type and not grounded if self.roost and defender_type == ElementType.FLYING: continue if self.foresight and attacker_type in (ElementType.FIGHTING, ElementType.NORMAL) and defender_type == ElementType.GHOST: continue if self.miracle_eye and attacker_type == ElementType.PSYCHIC and defender_type == ElementType.DARK: continue if ( attacker_type in (ElementType.FIGHTING, ElementType.NORMAL) and defender_type == ElementType.GHOST and attacker is not None and attacker.ability() in [Ability.SCRAPPY, Ability.MINDS_EYE] ): continue if attacker_type == ElementType.GROUND and defender_type == ElementType.FLYING and self.grounded(battle, attacker=attacker, move=move): continue e = battle.type_effectiveness[(attacker_type, defender_type)] / 100 if defender_type == ElementType.FLYING and e > 1 and move is not None and battle.weather.get() == "h-wind": e = 1 if battle.inverse_battle: if e < 1: e = 2 elif e > 1: e = 0.5 effectiveness *= e if attacker_type == ElementType.FIRE and self.tar_shot: effectiveness *= 2 if effectiveness >= 1 and self.hp == self.starting_hp and self.ability(attacker=attacker, move=move) == Ability.TERA_SHELL: effectiveness = 0.5 return effectiveness def weight(self, *, attacker=None, move=None): """ Returns this pokemon's current weight. Dynamically modifies the weight based on the ability of this pokemon. """ cur_ability = self.ability(attacker=attacker, move=move) cur_weight = self.starting_weight if cur_ability == Ability.HEAVY_METAL: cur_weight *= 2 if cur_ability == Ability.LIGHT_METAL: cur_weight //= 2 cur_weight = max(1, cur_weight) cur_weight -= self.autotomize * 1000 cur_weight = max(1, cur_weight) return cur_weight def ability(self, *, attacker=None, move=None): """ Returns this pokemon's current ability. Returns None in cases where the ability is blocked or nullified. """ # Currently there are two categories of ability ignores, and both only apply when a move is used. # Since this could change, the method signature is flexable. However, without both present, it # should not consider the existing options. if move is None or attacker is None or attacker is self: return self.ability_id if not self.ability_ignorable(): return self.ability_id if move.effect in (411, 460): return None if attacker.ability_id in (Ability.MOLD_BREAKER, Ability.TURBOBLAZE, Ability.TERAVOLT, Ability.NEUTRALIZING_GAS): return None if attacker.ability_id == Ability.MYCELIUM_MIGHT and move.damage_class == DamageClass.STATUS: return None return self.ability_id def ability_changeable(self): """Returns True if this pokemon's current ability can be changed.""" return self.ability_id not in ( Ability.MULTITYPE, Ability.STANCE_CHANGE, Ability.SCHOOLING, Ability.COMATOSE, Ability.SHIELDS_DOWN, Ability.DISGUISE, Ability.RKS_SYSTEM, Ability.BATTLE_BOND, Ability.POWER_CONSTRUCT, Ability.ICE_FACE, Ability.GULP_MISSILE, Ability.ZERO_TO_HERO ) def ability_giveable(self): """Returns True if this pokemon's current ability can be given to another pokemon.""" return self.ability_id not in ( Ability.TRACE, Ability.FORECAST, Ability.FLOWER_GIFT, Ability.ZEN_MODE, Ability.ILLUSION, Ability.IMPOSTER, Ability.POWER_OF_ALCHEMY, Ability.RECEIVER, Ability.DISGUISE, Ability.STANCE_CHANGE, Ability.POWER_CONSTRUCT, Ability.ICE_FACE, Ability.HUNGER_SWITCH, Ability.GULP_MISSILE, Ability.ZERO_TO_HERO ) def ability_ignorable(self): """Returns True if this pokemon's current ability can be ignored.""" return self.ability_id in ( Ability.AROMA_VEIL, Ability.BATTLE_ARMOR, Ability.BIG_PECKS, Ability.BULLETPROOF, Ability.CLEAR_BODY, Ability.CONTRARY, Ability.DAMP, Ability.DAZZLING, Ability.DISGUISE, Ability.DRY_SKIN, Ability.FILTER, Ability.FLASH_FIRE, Ability.FLOWER_GIFT, Ability.FLOWER_VEIL, Ability.FLUFFY, Ability.FRIEND_GUARD, Ability.FUR_COAT, Ability.HEATPROOF, Ability.HEAVY_METAL, Ability.HYPER_CUTTER, Ability.ICE_FACE, Ability.ICE_SCALES, Ability.IMMUNITY, Ability.INNER_FOCUS, Ability.INSOMNIA, Ability.KEEN_EYE, Ability.LEAF_GUARD, Ability.LEVITATE, Ability.LIGHT_METAL, Ability.LIGHTNING_ROD, Ability.LIMBER, Ability.MAGIC_BOUNCE, Ability.MAGMA_ARMOR, Ability.MARVEL_SCALE, Ability.MIRROR_ARMOR, Ability.MOTOR_DRIVE, Ability.MULTISCALE, Ability.OBLIVIOUS, Ability.OVERCOAT, Ability.OWN_TEMPO, Ability.PASTEL_VEIL, Ability.PUNK_ROCK, Ability.QUEENLY_MAJESTY, Ability.SAND_VEIL, Ability.SAP_SIPPER, Ability.SHELL_ARMOR, Ability.SHIELD_DUST, Ability.SIMPLE, Ability.SNOW_CLOAK, Ability.SOLID_ROCK, Ability.SOUNDPROOF, Ability.STICKY_HOLD, Ability.STORM_DRAIN, Ability.STURDY, Ability.SUCTION_CUPS, Ability.SWEET_VEIL, Ability.TANGLED_FEET, Ability.TELEPATHY, Ability.THICK_FAT, Ability.UNAWARE, Ability.VITAL_SPIRIT, Ability.VOLT_ABSORB, Ability.WATER_ABSORB, Ability.WATER_BUBBLE, Ability.WATER_VEIL, Ability.WHITE_SMOKE, Ability.WONDER_GUARD, Ability.WONDER_SKIN, Ability.ARMOR_TAIL, Ability.EARTH_EATER, Ability.GOOD_AS_GOLD, Ability.PURIFYING_SALT, Ability.WELL_BAKED_BODY ) def get_assist_move(self): """ Returns a Move that can be used with assist, or None if none exists. This selects a random move from the pool of moves from pokes in the user's party that are eligable. """ moves = [] for idx, poke in enumerate(self.owner.party): if idx == self.owner.last_idx: continue for move in poke.moves: if move.selectable_by_assist(): moves.append(move) if not moves: return None return random.choice(moves) @classmethod async def create(cls, ctx, raw_data: dict): """Creates a new DuelPokemon object using the raw data provided.""" pn = raw_data["pokname"] nick = raw_data["poknick"] hpiv = min(31, raw_data["hpiv"]) atkiv = min(31, raw_data["atkiv"]) defiv = min(31, raw_data["defiv"]) spatkiv = min(31, raw_data["spatkiv"]) spdefiv = min(31, raw_data["spdefiv"]) speediv = min(31, raw_data["speediv"]) hpev = raw_data["hpev"] atkev = raw_data["atkev"] defev = raw_data["defev"] spaev = raw_data["spatkev"] spdev = raw_data["spdefev"] speedev = raw_data["speedev"] plevel = raw_data["pokelevel"] shiny = raw_data["shiny"] radiant = raw_data["radiant"] skin = raw_data["skin"] id = raw_data["id"] hitem = raw_data["hitem"] happiness = raw_data["happiness"] moves = raw_data["moves"] ab_index = raw_data["ability_index"] nature = raw_data["nature"] gender = raw_data["gender"] nature = await find_one(ctx, "natures", {"identifier": nature.lower()}) dec_stat_id = nature["decreased_stat_id"] inc_stat_id = nature["increased_stat_id"] dec_stat = await find_one(ctx, "stat_types", {"id": dec_stat_id}) inc_stat = await find_one(ctx, "stat_types", {"id": inc_stat_id}) dec_stat = dec_stat["identifier"].capitalize().replace("-", " ") inc_stat = inc_stat["identifier"].capitalize().replace("-", " ") nature_stat_deltas = {"Attack": 1, "Defense": 1, "Special attack": 1, "Special defense": 1, "Speed": 1} flavor_map = { "Attack": "spicy", "Defense": "sour", "Speed": "sweet", "Special attack": "dry", "Special defense": "bitter", } disliked_flavor = "" if dec_stat != inc_stat: nature_stat_deltas[dec_stat] = 0.9 nature_stat_deltas[inc_stat] = 1.1 disliked_flavor = flavor_map[dec_stat] #Deform pokes that are formed into battle forms that they should not start off in if pn == "Mimikyu-busted": pn = "Mimikyu" if pn in ("Cramorant-gorging", "Cramorant-gulping"): pn = "Cramorant" if pn == "Eiscue-noice": pn = "Eiscue" if pn == "Darmanitan-zen": pn = "Darmanitan" if pn == "Darmanitan-zen-galar": pn = "Darmanitan-galar" if pn == "Aegislash-blade": pn = "Aegislash" if pn in ("Minior-red", "Minior-orange", "Minior-yellow", "Minior-green", "Minior-blue", "Minior-indigo", "Minior-violet"): pn = "Minior" if pn == "Wishiwashi" and plevel >= 20: pn = "Wishiwashi-school" if pn == "Wishiwashi-school" and plevel < 20: pn = "Wishiwashi" if pn == "Greninja-ash": pn = "Greninja" if pn == "Zygarde-complete": pn = "Zygarde" if pn == "Morpeko-hangry": pn = "Morpeko" if pn == "Cherrim-sunshine": pn = "Cherrim" if pn in ("Castform-snowy", "Castform-rainy", "Castform-sunny"): pn = "Castform" if pn in ( "Arceus-dragon", "Arceus-dark", "Arceus-ground", "Arceus-fighting", "Arceus-fire", "Arceus-ice", "Arceus-bug", "Arceus-steel", "Arceus-grass", "Arceus-psychic", "Arceus-fairy", "Arceus-flying", "Arceus-water", "Arceus-ghost", "Arceus-rock", "Arceus-poison", "Arceus-electric", ): pn = "Arceus" if pn in ( "Silvally-psychic", "Silvally-fairy", "Silvally-flying", "Silvally-water", "Silvally-ghost", "Silvally-rock", "Silvally-poison", "Silvally-electric", "Silvally-dragon", "Silvally-dark", "Silvally-ground", "Silvally-fighting", "Silvally-fire", "Silvally-ice", "Silvally-bug", "Silvally-steel", "Silvally-grass", ): pn = "Silvally" if pn == "Palafin-hero": pn = "Palafin" if pn.endswith("-mega-x") or pn.endswith("-mega-y"): pn = pn[:-7] if pn.endswith("-mega"): pn = pn[:-5] #TODO: Meloetta, Shaymin form_info = await find_one(ctx, "forms", {"identifier": pn.lower()}) #List of type ids type_ids = (await find_one(ctx, "ptypes", {"id": form_info["pokemon_id"]}))["types"] #6 element list of stat values (int) stats = (await find_one(ctx, "pokemon_stats", {"pokemon_id": form_info["pokemon_id"]}))["stats"] pokemonHp = stats[0] #Store the base stats for all forms of this poke base_stats = {} base_stats[pn] = stats extra_forms = [] if pn == "Mimikyu": extra_forms = ["Mimikyu-busted"] if pn == "Cramorant": extra_forms = ["Cramorant-gorging", "Cramorant-gulping"] if pn == "Eiscue": extra_forms = ["Eiscue-noice"] if pn == "Darmanitan": extra_forms = ["Darmanitan-zen"] if pn == "Darmanitan-galar": extra_forms = ["Darmanitan-zen-galar"] if pn == "Aegislash": extra_forms = ["Aegislash-blade"] if pn == "Minior": extra_forms = ["Minior-red", "Minior-orange", "Minior-yellow", "Minior-green", "Minior-blue", "Minior-indigo", "Minior-violet"] if pn == "Wishiwashi": extra_forms = ["Wishiwashi-school"] if pn == "Wishiwashi-school": extra_forms = ["Wishiwashi"] if pn == "Greninja": extra_forms = ["Greninja-ash"] if pn == "Zygarde": extra_forms = ["Zygarde-complete"] if pn == "Zygarde-10": extra_forms = ["Zygarde-complete"] if pn == "Morpeko": extra_forms = ["Morpeko-hangry"] if pn == "Cherrim": extra_forms = ["Cherrim-sunshine"] if pn == "Castform": extra_forms = ["Castform-snowy", "Castform-rainy", "Castform-sunny"] if pn == "Arceus": extra_forms = [ "Arceus-dragon", "Arceus-dark", "Arceus-ground", "Arceus-fighting", "Arceus-fire", "Arceus-ice", "Arceus-bug", "Arceus-steel", "Arceus-grass", "Arceus-psychic", "Arceus-fairy", "Arceus-flying", "Arceus-water", "Arceus-ghost", "Arceus-rock", "Arceus-poison", "Arceus-electric", ] if pn == "Silvally": extra_forms = [ "Silvally-psychic", "Silvally-fairy", "Silvally-flying", "Silvally-water", "Silvally-ghost", "Silvally-rock", "Silvally-poison", "Silvally-electric", "Silvally-dragon", "Silvally-dark", "Silvally-ground", "Silvally-fighting", "Silvally-fire", "Silvally-ice", "Silvally-bug", "Silvally-steel", "Silvally-grass", ] if pn == "Palafin": extra_forms = ["Palafin-hero"] mega_form = None mega_ability_id = None mega_type_ids = None if pn != "Rayquaza": if hitem == "mega-stone": mega_form = pn + "-mega" elif hitem == "mega-stone-x": mega_form = pn + "-mega-x" elif hitem == "mega-stone-y": mega_form = pn + "-mega-y" else: if "dragon-ascent" in moves: mega_form = pn + "-mega" if mega_form is not None: mega_form_info = await find_one(ctx, "forms", {"identifier": mega_form.lower()}) if mega_form_info is not None: mega_ability = await find_one(ctx, "poke_abilities", {"pokemon_id": mega_form_info["pokemon_id"]}) if mega_ability is None: raise ValueError("mega form missing ability in `poke_abilities`") mega_ability_id = mega_ability["ability_id"] mega_types = (await find_one(ctx, "ptypes", {"id": mega_form_info["pokemon_id"]})) if mega_types is None: raise ValueError("mega form missing types in `ptypes`") mega_type_ids = mega_types["types"] extra_forms.append(mega_form) for f_name in extra_forms: f_info = await find_one(ctx, "forms", {"identifier": f_name.lower()}) f_stats = (await find_one(ctx, "pokemon_stats", {"pokemon_id": f_info["pokemon_id"]}))["stats"] base_stats[f_name] = f_stats #Builds a list of the possible ability ids for this poke, `ab_index` is the currently selected ability from this list ab_ids = [] for record in await find(ctx, "poke_abilities", {"pokemon_id": form_info["pokemon_id"]}): ab_ids.append(record["ability_id"]) try: ab_id = ab_ids[ab_index] #Should never happen, but better safe than sorry except IndexError: ab_id = ab_ids[0] if any( pn.endswith(suffix) for suffix in [ "-bug","-summer","-marine","-elegant","-poison","-average","-altered","-winter","-trash","-incarnate", "-baile","-rainy","-steel","-star","-ash","-diamond","-pop-star","-fan","-school","-therian","-pau", "-river","-poke-ball","-kabuki","-electric","-heat","-unbound","-chill","-archipelago","-zen","-normal", "-mega-y","-resolute","-blade","-speed","-indigo","-dusk","-sky","-west","-sun","-dandy","-solo","-high-plains", "-la-reine","-50","-unova-cap","-burn","-mega-x","-monsoon","-primal","-red-striped","-blue-striped", "-white-striped","-ground","-super","-yellow","-polar","-cosplay","-ultra","-heart","-snowy","-sensu", "-eternal","-douse","-defense","-sunshine","-psychic","-modern","-natural","-tundra","-flying","-pharaoh", "-libre","-sunny","-autumn","-10","-orange","-standard","-land","-partner","-dragon","-plant","-pirouette", "-male","-hoenn-cap","-violet","-spring","-fighting","-sandstorm","-original-cap","-neutral","-fire", "-fairy","-attack","-black","-shock","-shield","-shadow","-grass","-continental","-overcast","-disguised", "-exclamation","-origin","-garden","-blue","-matron","-red-meteor","-small","-rock-star","-belle", "-alola-cap","-green","-active","-red","-mow","-icy-snow","-debutante","-east","-midday","-jungle","-frost", "-midnight","-rock","-fancy","-busted","-ordinary","-water","-phd","-ice","-spiky-eared","-savanna","-original", "-ghost","-meadow","-dawn","-question","-pom-pom","-female","-kalos-cap","-confined","-sinnoh-cap","-aria", "-dark","-ocean","-wash","-white","-mega","-sandy","-complete","-large","-crowned","-ice-rider","-shadow-rider", "-zen-galar","-rapid-strike","-noice","-hangry", ] ): name = pn.lower().split("-")[0] pid = (await find_one(ctx, "forms", {"identifier": name}))["pokemon_id"] else: pid = form_info["pokemon_id"] #True if any possible future evo exists can_still_evolve = bool(await find_one(ctx, "pfile", {"evolves_from_species_id": pid})) #Unreleased pokemon that is treated like a form in the bot, monkeypatch fix. if pn == "Floette-eternal": can_still_evolve = False #This stat can (/has to) be calculated ahead of time, as it does not change between forms. #If transform copied HP, I would probably take up drinking... pokemonHp = round((((2 * pokemonHp + hpiv + (hpev / 4)) * plevel) / 100) + plevel + 10) hitem = await find_one(ctx, "items", {"identifier": hitem}) if pn == "Shedinja": pokemonHp = 1 weight = (await find_one(ctx, "forms", {"identifier": pn.lower()}))["weight"] if weight is None: weight = 20 object_moves = [] for move in moves: type_override = None if move.startswith("hidden-power-"): element = move.split("-")[2] move = "hidden-power" type_override = ElementType[element.upper()] move = await find_one(ctx, "moves", {"identifier": move}) if move is None: move = await find_one(ctx, "moves", {"identifier": "tackle"}) else: if type_override is not None: move["type_id"] = type_override object_moves.append(Move(**move)) p = cls( pokemon_id=pid, name=pn, nickname=nick, base_stats=base_stats, hp=pokemonHp, hpiv=hpiv, atkiv=atkiv, defiv=defiv, spatkiv=spatkiv, spdefiv=spdefiv, speediv=speediv, hpev=hpev, atkev=atkev, defev=defev, spatkev=spaev, spdefev=spdev, speedev=speedev, level=plevel, nature_stat_deltas=nature_stat_deltas, shiny=shiny, radiant=radiant, skin=skin, type_ids=type_ids, mega_type_ids=mega_type_ids, id=id, held_item=hitem, happiness=happiness, moves=object_moves, ability_id=ab_id, mega_ability_id=mega_ability_id, weight=weight, gender=gender, can_still_evolve=can_still_evolve, disliked_flavor=disliked_flavor, ) return p def __repr__(self): return f"DuelPokemon(name={self._name!r}, hp={self.hp!r})"