Ruby-Cogs/pokemonduel/move.py
2025-04-02 22:57:51 -04:00

3752 lines
178 KiB
Python

import random
from .enums import Ability, DamageClass, ElementType, MoveTarget
from .misc import LockedMove, BatonPass, ExpiringEffect, ExpiringItem
class Move():
"""Represents an instance of a move."""
def __init__(self, **kwargs):
"""Accepts a dict from the mongo moves table."""
self.id = kwargs["id"]
self.name = kwargs["identifier"]
self.pretty_name = self.name.capitalize().replace("-", " ")
self.power = kwargs["power"]
self.pp = kwargs["pp"]
self.starting_pp = self.pp
self.accuracy = kwargs["accuracy"]
self.priority = kwargs["priority"]
self.type = kwargs["type_id"]
self.damage_class = kwargs["damage_class_id"]
self.effect = kwargs["effect_id"]
self.effect_chance = kwargs["effect_chance"]
self.target = kwargs["target_id"]
self.crit_rate = kwargs["crit_rate"]
self.min_hits = kwargs["min_hits"]
self.max_hits = kwargs["max_hits"]
self.used = False
def setup(self, attacker, defender, battle):
"""
Sets up anything this move needs to do prior to normal move execution.
Returns a formatted message.
"""
msg = ""
if self.effect == 129 and (isinstance(defender.owner.selected_action, int) or defender.owner.selected_action.effect in (128, 154, 229, 347, 493)):
msg += self.use(attacker, defender, battle)
if self.effect == 171:
msg += f"{attacker.name} is focusing on its attack!\n"
if self.effect == 404:
attacker.beak_blast = True
return msg
def use(self, attacker, defender, battle, *, use_pp=True, override_sleep=False, bounced=False):
"""
Uses this move as attacker on defender.
Returns a string of formatted results of the move.
"""
#This handles an edge case for moves that cause the target to swap out
if attacker.has_moved and use_pp:
return ""
self.used = True
if use_pp:
attacker.has_moved = True
attacker.last_move = self
attacker.beak_blast = False
attacker.destiny_bond = False
# Reset semi-invulnerable status in case this is turn 2
attacker.dive = False
attacker.dig = False
attacker.fly = False
attacker.shadow_force = False
current_type = self.get_type(attacker, defender, battle)
effect_chance = self.get_effect_chance(attacker, defender, battle)
msg = ""
if self.effect in (5, 126, 168, 254, 336, 398, 458, 500) and attacker.nv.freeze():
attacker.nv.reset()
msg += f"{attacker.name} thawed out!\n"
if attacker.nv.freeze():
if use_pp and not random.randint(0, 4):
attacker.nv.reset()
msg += f"{attacker.name} is no longer frozen!\n"
else:
msg += f"{attacker.name} is frozen solid!\n"
if self.effect == 28:
attacker.locked_move = None
return msg
if attacker.nv.paralysis() and not random.randint(0, 3):
msg += f"{attacker.name} is paralyzed! It can't move!\n"
if self.effect == 28:
attacker.locked_move = None
return msg
if attacker.infatuated is defender and not random.randint(0, 1):
msg += f"{attacker.name} is in love with {defender.name} and can't bare to hurt them!\n"
if self.effect == 28:
attacker.locked_move = None
return msg
if attacker.flinched:
msg += f"{attacker.name} flinched! It can't move!\n"
if self.effect == 28:
attacker.locked_move = None
return msg
if attacker.nv.sleep():
if use_pp and attacker.nv.sleep_timer.next_turn():
attacker.nv.reset()
msg += f"{attacker.name} woke up!\n"
elif self.effect not in (93, 98) and attacker.ability() != Ability.COMATOSE and not override_sleep:
msg += f"{attacker.name} is fast asleep!\n"
if self.effect == 28:
attacker.locked_move = None
return msg
if attacker.confusion.next_turn():
msg += f"{attacker.name} is no longer confused!\n"
if attacker.confusion.active() and not random.randint(0, 2):
msg += f"{attacker.name} hurt itself in its confusion!\n"
msgadd, numhits = self.confusion().attack(attacker, attacker, battle)
msg += msgadd
if self.effect == 28:
attacker.locked_move = None
return msg
if attacker.ability() == Ability.TRUANT and attacker.truant_turn % 2:
msg += f"{attacker.name} is loafing around!\n"
if self.effect == 28:
attacker.locked_move = None
return msg
if not bounced:
msg += f"{attacker.name} used {self.pretty_name}!\n"
attacker.metronome.use(self.name)
# PP
if attacker.locked_move is None and use_pp:
self.pp -= 1
if defender.ability(attacker=attacker, move=self) == Ability.PRESSURE and self.pp != 0:
if self.targets_opponent() or self.effect in (113, 193, 196, 250, 267):
self.pp -= 1
if self.pp == 0:
msg += "It ran out of PP!\n"
#User is using a choice item and had not used a move yet, set that as their only move.
if attacker.choice_move is None and use_pp:
if attacker.held_item in ("choice-scarf", "choice-band", "choice-specs"):
attacker.choice_move = self
elif attacker.ability() == Ability.GORILLA_TACTICS:
attacker.choice_move = self
# Stance change
if attacker.ability() == Ability.STANCE_CHANGE:
if attacker._name == "Aegislash" and self.damage_class in (DamageClass.PHYSICAL, DamageClass.SPECIAL):
if attacker.form("Aegislash-blade"):
msg += f"{attacker.name} draws its blade!\n"
if attacker._name == "Aegislash-blade" and self.effect == 356:
if attacker.form("Aegislash"):
msg += f"{attacker.name} readies its shield!\n"
# Powder damage
if attacker.powdered and current_type == ElementType.FIRE and battle.weather.get() != "h-rain":
msg += attacker.damage(attacker.starting_hp // 4, battle, source="its powder exploding")
return msg
# Snatch steal
if defender.snatching and self.selectable_by_snatch():
msg += f"{defender.name} snatched the move!\n"
msg += self.use(defender, attacker, battle, use_pp=False)
return msg
# Check Fail
if not self.check_executable(attacker, defender, battle):
msg += "But it failed!\n"
if self.effect in (28, 118):
attacker.locked_move = None
attacker.last_move_failed = True
return msg
# Setup for multi-turn moves
if attacker.locked_move is None:
# 2 turn moves
# During sun, this move does not need to charge
if self.effect == 152 and battle.weather.get() not in ("sun", "h-sun"):
attacker.locked_move = LockedMove(self, 2)
# During rain, this move does not need to charge
if self.effect == 502:
if battle.weather.get() not in ("rain", "h-rain"):
attacker.locked_move = LockedMove(self, 2)
else:
# If this move isn't charging, the spatk increase has to happen manually
msg += attacker.append_spatk(1, attacker=attacker, move=self)
if self.effect in (40, 76, 81, 146, 156, 256, 257, 264, 273, 332, 333, 366, 451):
attacker.locked_move = LockedMove(self, 2)
# 3 turn moves
if self.effect == 27:
attacker.locked_move = LockedMove(self, 3)
attacker.bide = 0
if self.effect == 160:
attacker.locked_move = LockedMove(self, 3)
attacker.uproar.set_turns(3)
if attacker.nv.sleep():
attacker.nv.reset()
msg += f"{attacker.name} woke up!\n"
if defender.nv.sleep():
defender.nv.reset()
msg += f"{defender.name} woke up!\n"
# 5 turn moves
if self.effect == 118:
attacker.locked_move = LockedMove(self, 5)
# 2-3 turn moves
if self.effect == 28:
attacker.locked_move = LockedMove(self, random.randint(2, 3))
#2-5 turn moves
if self.effect == 160:
attacker.locked_move = LockedMove(self, random.randint(2, 5))
# Semi-invulnerable
if self.effect == 256:
attacker.dive = True
if self.effect == 257:
attacker.dig = True
if self.effect in (156, 264):
attacker.fly = True
if self.effect == 273:
attacker.shadow_force = True
# Early exits for moves that hit a certain turn when it is not that turn
# Turn 1 hit moves
if self.effect == 81 and attacker.locked_move:
if attacker.locked_move.turn != 0:
msg += "It's recharging!\n"
return msg
# Turn 2 hit moves
elif self.effect in (40, 76, 146, 152, 156, 256, 257, 264, 273, 332, 333, 366, 451, 502) and attacker.locked_move:
if attacker.locked_move.turn != 1:
if self.effect == 146:
msg += attacker.append_defense(1, attacker=attacker, move=self)
elif self.effect in (451, 502):
msg += attacker.append_spatk(1, attacker=attacker, move=self)
else:
msg += "It's charging up!\n"
# Gulp Missile
if self.effect == 256 and attacker.ability() == Ability.GULP_MISSILE and attacker._name == "Cramorant":
if attacker.hp > attacker.starting_hp // 2:
if attacker.form("Cramorant-gulping"):
msg += f"{attacker.name} gulped up an arrokuda!\n"
else:
if attacker.form("Cramorant-gorging"):
msg += f"{attacker.name} gulped up a pikachu!\n"
return msg
# Turn 3 hit moves
elif self.effect == 27:
if attacker.locked_move.turn != 2:
msg += "It's storing energy!\n"
return msg
# User Faints
if self.effect in (8, 444):
msg += attacker.faint(battle)
# User takes damage
if self.effect == 420:
msg += attacker.damage(attacker.starting_hp // 2, battle, source="its head exploding (tragic)")
# User's type changes
if current_type != ElementType.TYPELESS:
if attacker.ability() == Ability.PROTEAN:
attacker.type_ids = [current_type]
t = ElementType(current_type).name.lower()
msg += f"{attacker.name} transformed into a {t} type using its protean!\n"
if attacker.ability() == Ability.LIBERO:
attacker.type_ids = [current_type]
t = ElementType(current_type).name.lower()
msg += f"{attacker.name} transformed into a {t} type using its libero!\n"
# Status effects reflected by magic coat or magic bounce.
if self.is_affected_by_magic_coat() and (defender.ability(attacker=attacker, move=self) == Ability.MAGIC_BOUNCE or defender.magic_coat) and not bounced:
msg += f"It was reflected by {defender.name}'s magic bounce!\n"
hm = defender.has_moved
msg += self.use(defender, attacker, battle, use_pp=False, bounced=True)
defender.has_moved = hm
return msg
# Check Effect
if not self.check_effective(attacker, defender, battle) and not bounced:
msg += "It had no effect...\n"
if self.effect == 120:
attacker.fury_cutter = 0
if self.effect in (46, 478):
msg += attacker.damage(attacker.starting_hp // 2, battle, source="recoil")
if self.effect in (28, 81, 118):
attacker.locked_move = None
attacker.last_move_failed = True
return msg
# Check Semi-invulnerable - treated as a miss
if not self.check_semi_invulnerable(attacker, defender, battle):
msg += f"{defender.name} avoided the attack!\n"
if self.effect == 120:
attacker.fury_cutter = 0
if self.effect in (46, 478):
msg += attacker.damage(attacker.starting_hp // 2, battle, source="recoil")
if self.effect in (28, 81, 118):
attacker.locked_move = None
return msg
# Check Protection
was_hit, msgdelta = self.check_protect(attacker, defender, battle)
if not was_hit:
msg += f"{defender.name} was protected against the attack!\n"
msg += msgdelta
if self.effect == 120:
attacker.fury_cutter = 0
if self.effect in (46, 478):
msg += attacker.damage(attacker.starting_hp // 2, battle, source="recoil")
if self.effect in (28, 81, 118):
attacker.locked_move = None
return msg
# Check Hit
if not self.check_hit(attacker, defender, battle):
msg += "But it missed!\n"
if self.effect == 120:
attacker.fury_cutter = 0
if self.effect in (46, 478):
msg += attacker.damage(attacker.starting_hp // 2, battle, source="recoil")
if self.effect in (28, 81, 118):
attacker.locked_move = None
return msg
# Absorbs
if self.targets_opponent() and self.effect != 459:
# Heal
if current_type == ElementType.ELECTRIC and defender.ability(attacker=attacker, move=self) == Ability.VOLT_ABSORB:
msg += f"{defender.name}'s volt absorb absorbed the move!\n"
msg += defender.heal(defender.starting_hp // 4, source="absorbing the move")
return msg
if current_type == ElementType.WATER and defender.ability(attacker=attacker, move=self) == Ability.WATER_ABSORB:
msg += f"{defender.name}'s water absorb absorbed the move!\n"
msg += defender.heal(defender.starting_hp // 4, source="absorbing the move")
return msg
if current_type == ElementType.WATER and defender.ability(attacker=attacker, move=self) == Ability.DRY_SKIN:
msg += f"{defender.name}'s dry skin absorbed the move!\n"
msg += defender.heal(defender.starting_hp // 4, source="absorbing the move")
return msg
if current_type == ElementType.GROUND and defender.ability(attacker=attacker, move=self) == Ability.EARTH_EATER:
msg += f"{defender.name}'s earth eater absorbed the move!\n"
msg += defender.heal(defender.starting_hp // 4, source="absorbing the move")
return msg
# Stat stage changes
if current_type == ElementType.ELECTRIC and defender.ability(attacker=attacker, move=self) == Ability.LIGHTNING_ROD:
msg += f"{defender.name}'s lightning rod absorbed the move!\n"
msg += defender.append_spatk(1, attacker=defender, move=self)
return msg
if current_type == ElementType.ELECTRIC and defender.ability(attacker=attacker, move=self) == Ability.MOTOR_DRIVE:
msg += f"{defender.name}'s motor drive absorbed the move!\n"
msg += defender.append_speed(1, attacker=defender, move=self)
return msg
if current_type == ElementType.WATER and defender.ability(attacker=attacker, move=self) == Ability.STORM_DRAIN:
msg += f"{defender.name}'s storm drain absorbed the move!\n"
msg += defender.append_spatk(1, attacker=defender, move=self)
return msg
if current_type == ElementType.GRASS and defender.ability(attacker=attacker, move=self) == Ability.SAP_SIPPER:
msg += f"{defender.name}'s sap sipper absorbed the move!\n"
msg += defender.append_attack(1, attacker=defender, move=self)
return msg
if current_type == ElementType.FIRE and defender.ability(attacker=attacker, move=self) == Ability.WELL_BAKED_BODY:
msg += f"{defender.name}'s well baked body absorbed the move!\n"
msg += defender.append_defense(2, attacker=defender, move=self)
return msg
# Other
if current_type == ElementType.FIRE and defender.ability(attacker=attacker, move=self) == Ability.FLASH_FIRE:
defender.flash_fire = True
msg += f"{defender.name} used its flash fire to buff its fire type moves!\n"
return msg
# Stat stage from type items
if not defender.substitute:
if current_type == ElementType.WATER and defender.held_item == "absorb-bulb":
msg += defender.append_spatk(1, attacker=defender, move=self, source="its absorb bulb")
defender.held_item.use()
if current_type == ElementType.ELECTRIC and defender.held_item == "cell-battery":
msg += defender.append_attack(1, attacker=defender, move=self, source="its cell battery")
defender.held_item.use()
if current_type == ElementType.WATER and defender.held_item == "luminous-moss":
msg += defender.append_spdef(1, attacker=defender, move=self, source="its luminous moss")
defender.held_item.use()
if current_type == ElementType.ICE and defender.held_item == "snowball":
msg += defender.append_attack(1, attacker=defender, move=self, source="its snowball")
defender.held_item.use()
# Metronome
if self.effect == 84:
attacker.has_moved = False
raw = random.choice(battle.metronome_moves_raw)
msg += Move(**raw).use(attacker, defender, battle)
return msg
# Brick break - runs before damage calculation
if self.effect == 187:
if defender.owner.aurora_veil.active():
defender.owner.aurora_veil.set_turns(0)
msg += f"{defender.name}'s aurora veil wore off!\n"
if defender.owner.light_screen.active():
defender.owner.light_screen.set_turns(0)
msg += f"{defender.name}'s light screen wore off!\n"
if defender.owner.reflect.active():
defender.owner.reflect.set_turns(0)
msg += f"{defender.name}'s reflect wore off!\n"
# Sleep talk
if self.effect == 98:
move = random.choice([m for m in attacker.moves if m.selectable_by_sleep_talk()])
msg += move.use(attacker, defender, battle, use_pp=False, override_sleep=True)
return msg
# Mirror Move/Copy Cat
if self.effect in (10, 243):
msg += defender.last_move.use(attacker, defender, battle, use_pp=False)
return msg
# Me First
if self.effect == 242:
msg += defender.owner.selected_action.use(attacker, defender, battle, use_pp=False)
return msg
# Assist
if self.effect == 181:
msg += attacker.get_assist_move().use(attacker, defender, battle, use_pp=False)
return msg
# Spectral Thief
if self.effect == 410:
if defender.attack_stage > 0:
stage = defender.attack_stage
defender.attack_stage = 0
msg += f"{defender.name}'s attack stage was reset!\n"
msg += attacker.append_attack(stage, attacker=attacker, move=self)
if defender.defense_stage > 0:
stage = defender.defense_stage
defender.defense_stage = 0
msg += f"{defender.name}'s defense stage was reset!\n"
msg += attacker.append_defense(stage, attacker=attacker, move=self)
if defender.spatk_stage > 0:
stage = defender.spatk_stage
defender.spatk_stage = 0
msg += f"{defender.name}'s special attack stage was reset!\n"
msg += attacker.append_spatk(stage, attacker=attacker, move=self)
if defender.spdef_stage > 0:
stage = defender.spdef_stage
defender.spdef_stage = 0
msg += f"{defender.name}'s special defense stage was reset!\n"
msg += attacker.append_spdef(stage, attacker=attacker, move=self)
if defender.speed_stage > 0:
stage = defender.speed_stage
defender.speed_stage = 0
msg += f"{defender.name}'s speed stage was reset!\n"
msg += attacker.append_speed(stage, attacker=attacker, move=self)
if defender.evasion_stage > 0:
stage = defender.evasion_stage
defender.evasion_stage = 0
msg += f"{defender.name}'s evasion stage was reset!\n"
msg += attacker.append_evasion(stage, attacker=attacker, move=self)
if defender.accuracy_stage > 0:
stage = defender.accuracy_stage
defender.accuracy_stage = 0
msg += f"{defender.name}'s accuracy stage was reset!\n"
msg += attacker.append_accuracy(stage, attacker=attacker, move=self)
# Future Sight
if self.effect == 149:
defender.owner.future_sight.set((attacker, self), 3)
msg += f"{attacker.name} foresaw an attack!\n"
return msg
# Present
if self.effect == 123:
action = random.randint(1, 4)
if action == 1:
if defender.hp == defender.starting_hp:
msg += "It had no effect!\n"
else:
msg += defender.heal(defender.starting_hp // 4, source=f"{attacker.name}'s present")
return msg
if action == 2:
power = 40
elif action == 3:
power = 80
else:
power = 120
m = self.present(power)
msgadd, _ = m.attack(attacker, defender, battle)
msg += msgadd
return msg
# Incinerate
if self.effect == 315 and defender.held_item.is_berry(only_active=False):
if defender.ability(attacker=attacker, move=self) == Ability.STICKY_HOLD:
msg += f"{defender.name}'s sticky hand kept hold of its item!\n"
else:
defender.held_item.remove()
msg += f"{defender.name}'s berry was incinerated!\n"
# Poltergeist
if self.effect == 446:
msg += f"{defender.name} is about to be attacked by its {defender.held_item.get()}!\n"
numhits = 0
# Turn 1 hit moves
if self.effect == 81 and attacker.locked_move:
if attacker.locked_move.turn == 0:
msgadd, numhits = self.attack(attacker, defender, battle)
msg += msgadd
# Turn 2 hit moves
elif self.effect in (40, 76, 146, 152, 156, 256, 257, 264, 273, 332, 333, 366, 451, 502) and attacker.locked_move:
if attacker.locked_move.turn == 1:
if self.damage_class in (DamageClass.PHYSICAL, DamageClass.SPECIAL):
msgadd, numhits = self.attack(attacker, defender, battle)
msg += msgadd
# Turn 3 hit moves
elif self.effect == 27:
if attacker.locked_move.turn == 2:
msg += defender.damage(attacker.bide * 2, battle, move=self, move_type=current_type, attacker=attacker)
attacker.bide = None
numhits = 1
# Counter attack moves
elif self.effect == 228:
msg += defender.damage(int(1.5 * attacker.last_move_damage[0]), battle, move=self, move_type=current_type, attacker=attacker)
numhits = 1
elif self.effect == 145:
msg += defender.damage(2 * attacker.last_move_damage[0], battle, move=self, move_type=current_type, attacker=attacker)
numhits = 1
elif self.effect == 90:
msg += defender.damage(2 * attacker.last_move_damage[0], battle, move=self, move_type=current_type, attacker=attacker)
numhits = 1
# Static-damage moves
elif self.effect == 41:
msg += defender.damage(defender.hp // 2, battle, move=self, move_type=current_type, attacker=attacker)
numhits = 1
elif self.effect == 42:
msg += defender.damage(40, battle, move=self, move_type=current_type, attacker=attacker)
numhits = 1
elif self.effect == 88:
msg += defender.damage(attacker.level, battle, move=self, move_type=current_type, attacker=attacker)
numhits = 1
elif self.effect == 89:
# 0.5-1.5, increments of .1
scale = (random.randint(0, 10) / 10.0) + .5
msg += defender.damage(int(attacker.level * scale), battle, move=self, move_type=current_type, attacker=attacker)
numhits = 1
elif self.effect == 131:
msg += defender.damage(20, battle, move=self, move_type=current_type, attacker=attacker)
numhits = 1
elif self.effect == 190:
msg += defender.damage(max(0, defender.hp - attacker.hp), battle, move=self, move_type=current_type, attacker=attacker)
numhits = 1
elif self.effect == 39:
msg += defender.damage(defender.hp, battle, move=self, move_type=current_type, attacker=attacker)
numhits = 1
elif self.effect == 321:
msg += defender.damage(attacker.hp, battle, move=self, move_type=current_type, attacker=attacker)
numhits = 1
elif self.effect == 413:
msg += defender.damage(3 * (defender.hp // 4), battle, move=self, move_type=current_type, attacker=attacker)
numhits = 1
# Beat up, a stupid move
elif self.effect == 155:
for poke in attacker.owner.party:
if defender.hp == 0:
break
if poke.hp == 0:
continue
if poke is attacker:
msgadd, nh = self.attack(attacker, defender, battle)
msg += msgadd
numhits += nh
else:
if poke.nv.current:
continue
fake_move = {
"id": 251,
"identifier": "beat-up",
"power": (poke.get_raw_attack() // 10) + 5,
"pp": 100,
"accuracy": 100,
"priority": 0,
"type_id": ElementType.DARK,
"damage_class_id": DamageClass.PHYSICAL,
"effect_id": 1,
"effect_chance": None,
"target_id": 10,
"crit_rate": 0,
"min_hits": None,
"max_hits": None,
}
fake_move = Move(**fake_move)
msgadd, nh = fake_move.attack(attacker, defender, battle)
msg += msgadd
numhits += nh
# Other damaging moves
elif self.damage_class in (DamageClass.PHYSICAL, DamageClass.SPECIAL):
msgadd, numhits = self.attack(attacker, defender, battle)
msg += msgadd
# Fusion Flare/Bolt effect tracking
battle.last_move_effect = self.effect
# Stockpile
if self.effect == 161:
attacker.stockpile += 1
msg += f"{attacker.name} stores energy!\n"
if self.effect == 162:
msg += attacker.append_defense(-attacker.stockpile, attacker=attacker, move=self)
msg += attacker.append_spdef(-attacker.stockpile, attacker=attacker, move=self)
attacker.stockpile = 0
# Healing
if self.effect in (33, 215):
msg += attacker.heal(attacker.starting_hp // 2)
if self.effect in (434, 457):
msg += attacker.heal(attacker.starting_hp // 4)
if self.effect == 310:
if attacker.ability() == Ability.MEGA_LAUNCHER:
msg += defender.heal((defender.starting_hp * 3) // 4)
else:
msg += defender.heal(defender.starting_hp // 2)
if self.effect == 133:
if battle.weather.get() in ("sun", "h-sun"):
msg += attacker.heal((attacker.starting_hp * 2) // 3)
elif battle.weather.get() == "h-wind":
msg += attacker.heal(attacker.starting_hp // 2)
elif battle.weather.get():
msg += attacker.heal(attacker.starting_hp // 4)
else:
msg += attacker.heal(attacker.starting_hp // 2)
if self.effect == 85:
defender.leech_seed = True
msg += f"{defender.name} was seeded!\n"
if self.effect == 163:
msg += attacker.heal(attacker.starting_hp // {1: 4, 2: 2, 3: 1}[attacker.stockpile], source="stockpiled energy")
msg += attacker.append_defense(-attacker.stockpile, attacker=attacker, move=self)
msg += attacker.append_spdef(-attacker.stockpile, attacker=attacker, move=self)
attacker.stockpile = 0
if self.effect == 180:
attacker.owner.wish.set(attacker.starting_hp // 2)
msg += f"{attacker.name} makes a wish!\n"
if self.effect == 382:
if battle.weather.get() == "sandstorm":
msg += attacker.heal((attacker.starting_hp * 2) // 3)
else:
msg += attacker.heal(attacker.starting_hp // 2)
if self.effect == 387:
if battle.terrain.item == "grassy":
msg += attacker.heal((attacker.starting_hp * 2) // 3)
else:
msg += attacker.heal(attacker.starting_hp // 2)
if self.effect == 388:
msg += attacker.heal(defender.get_attack(battle))
if self.effect == 400:
status = defender.nv.current
defender.nv.reset()
msg += f"{defender.name}'s {status} was healed!\n"
msg += attacker.heal(attacker.starting_hp // 2)
# Status effects
if self.effect in (5, 126, 201, 254, 274, 333, 365, 458, 465, 500):
if random.randint(1, 100) <= effect_chance:
msg += defender.nv.apply_status("burn", battle, attacker=attacker, move=self)
if self.effect == 168:
msg += defender.nv.apply_status("burn", battle, attacker=attacker, move=self)
if self.effect == 429 and defender.stat_increased:
msg += defender.nv.apply_status("burn", battle, attacker=attacker, move=self)
if self.effect == 37:
status = random.choice(["burn", "freeze", "paralysis"])
if random.randint(1, 100) <= effect_chance:
msg += defender.nv.apply_status(status, battle, attacker=attacker, move=self)
if self.effect == 464:
status = random.choice(["poison", "paralysis", "sleep"])
if random.randint(1, 100) <= effect_chance:
msg += defender.nv.apply_status(status, battle, attacker=attacker, move=self)
if self.effect in (6, 261, 275, 380):
if random.randint(1, 100) <= effect_chance:
msg += defender.nv.apply_status("freeze", battle, attacker=attacker, move=self)
if self.effect in (7, 153, 263, 264, 276, 332, 372, 396):
if random.randint(1, 100) <= effect_chance:
msg += defender.nv.apply_status("paralysis", battle, attacker=attacker, move=self)
if self.effect == 68:
msg += defender.nv.apply_status("paralysis", battle, attacker=attacker, move=self)
if self.effect in (3, 78, 210, 447, 461):
if random.randint(1, 100) <= effect_chance:
msg += defender.nv.apply_status("poison", battle, attacker=attacker, move=self)
if self.effect in (67, 390, 486):
msg += defender.nv.apply_status("poison", battle, attacker=attacker, move=self)
if self.effect == 203:
if random.randint(1, 100) <= effect_chance:
msg += defender.nv.apply_status("b-poison", battle, attacker=attacker, move=self)
if self.effect == 34:
msg += defender.nv.apply_status("b-poison", battle, attacker=attacker, move=self)
if self.effect == 2:
if self.id == 464 and attacker._name != "Darkrai":
msg += f"{attacker.name} can't use the move!\n"
else:
msg += defender.nv.apply_status("sleep", battle, attacker=attacker, move=self)
if self.effect == 330:
if random.randint(1, 100) <= effect_chance:
msg += defender.nv.apply_status("sleep", battle, attacker=attacker, move=self)
if self.effect == 38:
msg += attacker.nv.apply_status("sleep", battle, attacker=attacker, move=self, turns=3, force=True)
if attacker.nv.sleep():
msg += f"{attacker.name}'s slumber restores its health back to full!\n"
attacker.hp = attacker.starting_hp
if self.effect in (50, 119, 167, 200):
msg += defender.confuse(attacker=attacker, move=self)
# This checks if attacker.locked_move is not None as locked_move is cleared if the poke dies to rocky helmet or similar items
if self.effect == 28 and attacker.locked_move is not None and attacker.locked_move.is_last_turn():
msg += attacker.confuse()
if self.effect in (77, 268, 334, 478):
if random.randint(1, 100) <= effect_chance:
msg += defender.confuse(attacker=attacker, move=self)
if self.effect == 497 and defender.stat_increased:
msg += defender.confuse(attacker=attacker, move=self)
if self.effect in (194, 457, 472):
attacker.nv.reset()
msg += f"{attacker.name}'s status was cleared!\n"
if self.effect == 386:
if defender.nv.burn():
defender.nv.reset()
msg += f"{defender.name}'s burn was healed!\n"
# Stage changes
# +1
if self.effect in (11, 209, 213, 278, 313, 323, 328, 392, 414, 427, 468, 472, 487):
msg += attacker.append_attack(1, attacker=attacker, move=self)
if self.effect in (12, 157, 161, 207, 209, 323, 367, 414, 427, 467, 468, 472):
msg += attacker.append_defense(1, attacker=attacker, move=self)
if self.effect in (14, 212, 291, 328, 392, 414, 427, 472):
msg += attacker.append_spatk(1, attacker=attacker, move=self)
if self.effect in (161, 175, 207, 212, 291, 367, 414, 427, 472):
msg += attacker.append_spdef(1, attacker=attacker, move=self)
if self.effect in (130, 213, 291, 296, 414, 427, 442, 468, 469, 487):
msg += attacker.append_speed(1, attacker=attacker, move=self)
if self.effect in (17, 467):
msg += attacker.append_evasion(1, attacker=attacker, move=self)
if self.effect in (278, 323):
msg += attacker.append_accuracy(1, attacker=attacker, move=self)
if self.effect == 139:
if random.randint(1, 100) <= effect_chance:
msg += attacker.append_defense(1, attacker=attacker, move=self)
if self.effect in (140, 375):
if random.randint(1, 100) <= effect_chance:
msg += attacker.append_attack(1, attacker=attacker, move=self)
if self.effect == 277:
if random.randint(1, 100) <= effect_chance:
msg += attacker.append_spatk(1, attacker=attacker, move=self)
if self.effect == 433:
if random.randint(1, 100) <= effect_chance:
msg += attacker.append_speed(1, attacker=attacker, move=self)
if self.effect == 167:
msg += defender.append_spatk(1, attacker=attacker, move=self)
# +2
if self.effect in (51, 309):
msg += attacker.append_attack(2, attacker=attacker, move=self)
if self.effect in (52, 453):
msg += attacker.append_defense(2, attacker=attacker, move=self)
if self.effect in (53, 285, 309, 313, 366):
msg += attacker.append_speed(2, attacker=attacker, move=self)
if self.effect in (54, 309, 366):
msg += attacker.append_spatk(2, attacker=attacker, move=self)
if self.effect in (55, 366):
msg += attacker.append_spdef(2, attacker=attacker, move=self)
if self.effect == 109:
msg += attacker.append_evasion(2, attacker=attacker, move=self)
if self.effect in (119, 432, 483):
msg += defender.append_attack(2, attacker=attacker, move=self)
if self.effect == 432:
msg += defender.append_spatk(2, attacker=attacker, move=self)
if self.effect == 359:
if random.randint(1, 100) <= effect_chance:
msg += attacker.append_defense(2, attacker=attacker, move=self)
# -1
if self.effect in (19, 206, 344, 347, 357, 365, 388, 412):
msg += defender.append_attack(-1, attacker=attacker, move=self)
if self.effect in (20, 206):
msg += defender.append_defense(-1, attacker=attacker, move=self)
if self.effect in (344, 347, 358, 412):
msg += defender.append_spatk(-1, attacker=attacker, move=self)
if self.effect == 428:
msg += defender.append_spdef(-1, attacker=attacker, move=self)
if self.effect in (331, 390):
msg += defender.append_speed(-1, attacker=attacker, move=self)
if self.effect == 24:
msg += defender.append_accuracy(-1, attacker=attacker, move=self)
if self.effect in (25, 259):
msg += defender.append_evasion(-1, attacker=attacker, move=self)
if self.effect in (69, 396):
if random.randint(1, 100) <= effect_chance:
msg += defender.append_attack(-1, attacker=attacker, move=self)
if self.effect in (70, 397, 435):
if random.randint(1, 100) <= effect_chance:
msg += defender.append_defense(-1, attacker=attacker, move=self)
if self.effect == 475:
# This one has two different chance percents, one has to be hardcoded
if random.randint(1, 100) <= 50:
msg += defender.append_defense(-1, attacker=attacker, move=self)
if self.effect in (21, 71, 357, 477):
if random.randint(1, 100) <= effect_chance:
msg += defender.append_speed(-1, attacker=attacker, move=self)
if self.effect == 72:
if random.randint(1, 100) <= effect_chance:
msg += defender.append_spatk(-1, attacker=attacker, move=self)
if self.effect == 73:
if random.randint(1, 100) <= effect_chance:
msg += defender.append_spdef(-1, attacker=attacker, move=self)
if self.effect == 74:
if random.randint(1, 100) <= effect_chance:
msg += defender.append_accuracy(-1, attacker=attacker, move=self)
if self.effect == 183:
msg += attacker.append_attack(-1, attacker=attacker, move=self)
if self.effect in (183, 230, 309, 335, 405, 438, 442):
msg += attacker.append_defense(-1, attacker=attacker, move=self)
if self.effect == 480:
msg += attacker.append_spatk(-1, attacker=attacker, move=self)
if self.effect in (230, 309, 335):
msg += attacker.append_spdef(-1, attacker=attacker, move=self)
if self.effect in (219, 335):
msg += attacker.append_speed(-1, attacker=attacker, move=self)
# -2
if self.effect in (59, 169):
msg += defender.append_attack(-2, attacker=attacker, move=self)
if self.effect in (60, 483):
msg += defender.append_defense(-2, attacker=attacker, move=self)
if self.effect == 61:
msg += defender.append_speed(-2, attacker=attacker, move=self)
if self.effect in (62, 169, 266):
msg += defender.append_spatk(-2, attacker=attacker, move=self)
if self.effect == 63:
msg += defender.append_spdef(-2, attacker=attacker, move=self)
if self.effect in (272, 297):
if random.randint(1, 100) <= effect_chance:
msg += defender.append_spdef(-2, attacker=attacker, move=self)
if self.effect == 205:
msg += attacker.append_spatk(-2, attacker=attacker, move=self)
if self.effect == 479:
msg += attacker.append_speed(-2, attacker=attacker, move=self)
# other
if self.effect == 26:
attacker.attack_stage = 0
attacker.defense_stage = 0
attacker.spatk_stage = 0
attacker.spdef_stage = 0
attacker.speed_stage = 0
attacker.accuracy_stage = 0
attacker.evasion_stage = 0
defender.attack_stage = 0
defender.defense_stage = 0
defender.spatk_stage = 0
defender.spdef_stage = 0
defender.speed_stage = 0
defender.accuracy_stage = 0
defender.evasion_stage = 0
msg += "All pokemon had their stat stages reset!\n"
if self.effect == 305:
defender.attack_stage = 0
defender.defense_stage = 0
defender.spatk_stage = 0
defender.spdef_stage = 0
defender.speed_stage = 0
defender.accuracy_stage = 0
defender.evasion_stage = 0
msg += f"{defender.name} had their stat stages reset!\n"
if self.effect == 141:
if random.randint(1, 100) <= effect_chance:
msg += attacker.append_attack(1, attacker=attacker, move=self)
msg += attacker.append_defense(1, attacker=attacker, move=self)
msg += attacker.append_spatk(1, attacker=attacker, move=self)
msg += attacker.append_spdef(1, attacker=attacker, move=self)
msg += attacker.append_speed(1, attacker=attacker, move=self)
if self.effect == 143:
msg += attacker.damage(attacker.starting_hp // 2, battle)
msg += attacker.append_attack(12, attacker=attacker, move=self)
if self.effect == 317:
amount = 1
if battle.weather.get() in ("sun", "h-sun"):
amount = 2
msg += attacker.append_attack(amount, attacker=attacker, move=self)
msg += attacker.append_spatk(amount, attacker=attacker, move=self)
if self.effect == 364 and defender.nv.poison():
msg += defender.append_attack(-1, attacker=attacker, move=self)
msg += defender.append_spatk(-1, attacker=attacker, move=self)
msg += defender.append_speed(-1, attacker=attacker, move=self)
if self.effect == 329:
msg += attacker.append_defense(3, attacker=attacker, move=self)
if self.effect == 322:
msg += attacker.append_spatk(3, attacker=attacker, move=self)
if self.effect == 227:
valid_stats = []
if attacker.attack_stage < 6:
valid_stats.append(attacker.append_attack)
if attacker.defense_stage < 6:
valid_stats.append(attacker.append_defense)
if attacker.spatk_stage < 6:
valid_stats.append(attacker.append_spatk)
if attacker.spdef_stage < 6:
valid_stats.append(attacker.append_spdef)
if attacker.speed_stage < 6:
valid_stats.append(attacker.append_speed)
if attacker.evasion_stage < 6:
valid_stats.append(attacker.append_evasion)
if attacker.accuracy_stage < 6:
valid_stats.append(attacker.append_accuracy)
if valid_stats:
stat_raise_func = random.choice(valid_stats)
msg += stat_raise_func(2, attacker=attacker, move=self)
else:
msg += f"None of {attacker.name}'s stats can go any higher!\n"
if self.effect == 473:
raw_atk = attacker.get_raw_attack() + attacker.get_raw_spatk()
raw_def = attacker.get_raw_defense() + attacker.get_raw_spdef()
if raw_atk > raw_def:
msg += attacker.append_attack(1, attacker=attacker, move=self)
msg += attacker.append_spatk(1, attacker=attacker, move=self)
else:
msg += attacker.append_defense(1, attacker=attacker, move=self)
msg += attacker.append_spdef(1, attacker=attacker, move=self)
if self.effect == 485:
msg += attacker.damage(attacker.starting_hp // 2, battle)
msg += attacker.append_attack(2, attacker=attacker, move=self)
msg += attacker.append_spatk(2, attacker=attacker, move=self)
msg += attacker.append_speed(2, attacker=attacker, move=self)
# Flinch
if not defender.has_moved:
for _ in range(numhits):
if defender.flinched:
break
if self.effect in (32, 76, 93, 147, 151, 159, 274, 275, 276, 425, 475, 501):
if random.randint(1, 100) <= effect_chance:
msg += defender.flinch(move=self, attacker=attacker)
elif self.damage_class in (DamageClass.PHYSICAL, DamageClass.SPECIAL):
if attacker.ability() == Ability.STENCH:
if random.randint(1, 100) <= 10:
msg += defender.flinch(move=self, attacker=attacker, source="its stench")
elif attacker.held_item == "kings-rock":
if random.randint(1, 100) <= 10:
msg += defender.flinch(move=self, attacker=attacker, source="its kings rock")
elif attacker.held_item == "razor-fang":
if random.randint(1, 100) <= 10:
msg += defender.flinch(move=self, attacker=attacker, source="its razor fang")
# Move locking
if self.effect == 87:
if defender.ability(attacker=attacker, move=self) == Ability.AROMA_VEIL:
msg += f"{defender.name}'s aroma veil protects its move from being disabled!\n"
else:
defender.disable.set(defender.last_move, random.randint(4, 7))
msg += f"{defender.name}'s {defender.last_move.pretty_name} was disabled!\n"
if self.effect == 176:
if defender.ability(attacker=attacker, move=self) == Ability.OBLIVIOUS:
msg += f"{defender.name} is too oblivious to be taunted!\n"
elif defender.ability(attacker=attacker, move=self) == Ability.AROMA_VEIL:
msg += f"{defender.name}'s aroma veil protects it from being taunted!\n"
else:
if defender.has_moved:
defender.taunt.set_turns(4)
else:
defender.taunt.set_turns(3)
msg += f"{defender.name} is being taunted!\n"
if self.effect == 91:
if defender.ability(attacker=attacker, move=self) == Ability.AROMA_VEIL:
msg += f"{defender.name}'s aroma veil protects it from being encored!\n"
else:
defender.encore.set(defender.last_move, 4)
if not defender.has_moved:
defender.owner.selected_action = defender.last_move
msg += f"{defender.name} is giving an encore!\n"
if self.effect == 166:
if defender.ability(attacker=attacker, move=self) == Ability.AROMA_VEIL:
msg += f"{defender.name}'s aroma veil protects it from being tormented!\n"
else:
defender.torment = True
msg += f"{defender.name} is tormented!\n"
if self.effect == 193:
attacker.imprison = True
msg += f"{attacker.name} imprisons!\n"
if self.effect == 237:
if defender.ability(attacker=attacker, move=self) == Ability.AROMA_VEIL:
msg += f"{defender.name}'s aroma veil protects it from being heal blocked!\n"
else:
defender.heal_block.set_turns(5)
msg += f"{defender.name} is blocked from healing!\n"
if self.effect == 496:
if defender.ability(attacker=attacker, move=self) == Ability.AROMA_VEIL:
msg += f"{defender.name}'s aroma veil protects it from being heal blocked!\n"
else:
defender.heal_block.set_turns(2)
msg += f"{defender.name} is blocked from healing!\n"
# Weather changing
if self.effect == 116:
msg += battle.weather.set("sandstorm", attacker)
if self.effect == 137:
msg += battle.weather.set("rain", attacker)
if self.effect == 138:
msg += battle.weather.set("sun", attacker)
if self.effect == 165:
msg += battle.weather.set("hail", attacker)
# Terrain changing
if self.effect == 352:
msg += battle.terrain.set("grassy", attacker)
if self.effect == 353:
msg += battle.terrain.set("misty", attacker)
if self.effect == 369:
msg += battle.terrain.set("electric", attacker)
if self.effect == 395:
msg += battle.terrain.set("psychic", attacker)
# Protection
if self.effect in (112, 117, 279, 356, 362, 384, 454, 488, 499):
attacker.protection_used = True
attacker.protection_chance *= 3
if self.effect == 112:
attacker.protect = True
msg += f"{attacker.name} protected itself!\n"
if self.effect == 117:
attacker.endure = True
msg += f"{attacker.name} braced itself!\n"
if self.effect == 279:
attacker.wide_guard = True
msg += f"Wide guard protects {attacker.name}!\n"
if self.effect == 350:
attacker.crafty_shield = True
msg += f"A crafty shield protects {attacker.name} from status moves!\n"
if self.effect == 356:
attacker.king_shield = True
msg += f"{attacker.name} shields itself!\n"
if self.effect == 362:
attacker.spiky_shield = True
msg += f"{attacker.name} shields itself!\n"
if self.effect == 377:
attacker.mat_block = True
msg += f"{attacker.name} shields itself!\n"
if self.effect == 384:
attacker.baneful_bunker = True
msg += f"{attacker.name} bunkers down!\n"
if self.effect == 307:
attacker.quick_guard = True
msg += f"{attacker.name} guards itself!\n"
if self.effect == 454:
attacker.obstruct = True
msg += f"{attacker.name} protected itself!\n"
if self.effect == 488:
attacker.silk_trap = True
msg += f"{attacker.name} protected itself!\n"
if self.effect == 499:
attacker.burning_bulwark = True
msg += f"{attacker.name} protected itself!\n"
# Life orb
if (
attacker.held_item == "life-orb"
and defender.owner.has_alive_pokemon()
and self.damage_class != DamageClass.STATUS
and (attacker.ability() != Ability.SHEER_FORCE or self.effect_chance is None)
and self.effect != 149
):
msg += attacker.damage(attacker.starting_hp // 10, battle, source="its life orb")
# Swap outs
# A poke is force-swapped out before activating red-card
if self.effect in (29, 314):
swaps = defender.owner.valid_swaps(attacker, battle, check_trap=False)
if not swaps:
pass
elif defender.ability(attacker=attacker, move=self) == Ability.SUCTION_CUPS:
msg += f"{defender.name}'s suction cups kept it in place!\n"
elif defender.ability(attacker=attacker, move=self) == Ability.GUARD_DOG:
msg += f"{defender.name}'s guard dog kept it in place!\n"
elif defender.ingrain:
msg += f"{defender.name} is ingrained in the ground!\n"
else:
msg += f"{defender.name} fled in fear!\n"
msg += defender.remove(battle)
idx = random.choice(swaps)
defender.owner.switch_poke(idx, mid_turn=True)
msg += defender.owner.current_pokemon.send_out(attacker, battle)
# Safety in case the poke dies on send out.
if defender.owner.current_pokemon is not None:
defender.owner.current_pokemon.has_moved = True
# A red-card forces the attacker to swap to a random poke, even if they used a switch out move
elif defender.held_item == "red-card" and defender.hp > 0 and self.damage_class != DamageClass.STATUS:
swaps = attacker.owner.valid_swaps(defender, battle, check_trap=False)
if not swaps:
pass
elif attacker.ability(attacker=defender, move=self) == Ability.SUCTION_CUPS:
msg += f"{attacker.name}'s suction cups kept it in place from {defender.name}'s red card!\n"
defender.held_item.use()
elif attacker.ability(attacker=defender, move=self) == Ability.GUARD_DOG:
msg += f"{attacker.name}'s guard dog kept it in place from {defender.name}'s red card!\n"
defender.held_item.use()
elif attacker.ingrain:
msg += f"{attacker.name} is ingrained in the ground from {defender.name}'s red card!\n"
defender.held_item.use()
else:
msg += f"{defender.name} held up its red card against {attacker.name}!\n"
defender.held_item.use()
msg += attacker.remove(battle)
idx = random.choice(swaps)
attacker.owner.switch_poke(idx, mid_turn=True)
msg += attacker.owner.current_pokemon.send_out(defender, battle)
# Safety in case the poke dies on send out.
if attacker.owner.current_pokemon is not None:
attacker.owner.current_pokemon.has_moved = True
elif self.effect in (128, 154, 229, 347):
swaps = attacker.owner.valid_swaps(defender, battle, check_trap=False)
if swaps:
msg += f"{attacker.name} went back!\n"
if self.effect == 128:
attacker.owner.baton_pass = BatonPass(attacker)
msg += attacker.remove(battle)
# Force this pokemon to immediately return to be attacked
attacker.owner.mid_turn_remove = True
# Trapping
if self.effect in (107, 374, 385, 449, 452) and not defender.trapping:
defender.trapping = True
msg += f"{defender.name} can't escape!\n"
if self.effect == 449 and not attacker.trapping:
attacker.trapping = True
msg += f"{attacker.name} can't escape!\n"
# Attacker faints
if self.effect in (169, 221, 271, 321):
msg += attacker.faint(battle)
# Struggle
if self.effect == 255:
msg += attacker.damage(attacker.starting_hp // 4, battle, attacker=attacker)
# Pain Split
if self.effect == 92:
hp = (attacker.hp + defender.hp) // 2
attacker.hp = min(attacker.starting_hp, hp)
defender.hp = min(defender.starting_hp, hp)
msg += "The battlers share their pain!\n"
# Spite
if self.effect == 101:
defender.last_move.pp = max(0, defender.last_move.pp - 4)
msg += f"{defender.name}'s {defender.last_move.pretty_name} was reduced!\n"
# Eerie Spell
if self.effect == 439 and defender.last_move is not None:
defender.last_move.pp = max(0, defender.last_move.pp - 3)
msg += f"{defender.name}'s {defender.last_move.pretty_name} was reduced!\n"
# Heal Bell
if self.effect == 103:
for poke in attacker.owner.party:
poke.nv.reset()
msg += f"A bell chimed, and all of {attacker.owner.name}'s pokemon had status conditions removed!\n"
# Psycho Shift
if self.effect == 235:
transfered_status = attacker.nv.current
msg += defender.nv.apply_status(transfered_status, battle, attacker=attacker, move=self)
if defender.nv.current == transfered_status:
attacker.nv.reset()
msg += f"{attacker.name}'s {transfered_status} was transfered to {defender.name}!\n"
else:
msg += "But it failed!\n"
# Defog
if self.effect == 259:
defender.owner.spikes = 0
defender.owner.toxic_spikes = 0
defender.owner.stealth_rock = False
defender.owner.sticky_web = False
defender.owner.aurora_veil = ExpiringEffect(0)
defender.owner.light_screen = ExpiringEffect(0)
defender.owner.reflect = ExpiringEffect(0)
defender.owner.mist = ExpiringEffect(0)
defender.owner.safeguard = ExpiringEffect(0)
attacker.owner.spikes = 0
attacker.owner.toxic_spikes = 0
attacker.owner.stealth_rock = False
attacker.owner.sticky_web = False
battle.terrain.end()
msg += f"{attacker.name} blew away the fog!\n"
# Trick room
if self.effect == 260:
if battle.trick_room.active():
battle.trick_room.set_turns(0)
msg += "The Dimensions returned back to normal!\n"
else:
battle.trick_room.set_turns(5)
msg += f"{attacker.name} twisted the dimensions!\n"
# Magic Room
if self.effect == 287:
if battle.magic_room.active():
battle.magic_room.set_turns(0)
msg += "The room returns to normal, and held items regain their effect!\n"
else:
battle.magic_room.set_turns(5)
msg += "A bizzare area was created, and pokemon's held items lost their effect!\n"
# Wonder Room
if self.effect == 282:
if battle.wonder_room.active():
battle.wonder_room.set_turns(0)
msg += "The room returns to normal, and stats swap back to what they were before!\n"
else:
battle.wonder_room.set_turns(5)
msg += "A bizzare area was created, and pokemon's defense and special defense were swapped!\n"
# Perish Song
if self.effect == 115:
msg += "All pokemon hearing the song will faint after 3 turns!\n"
if attacker.perish_song.active():
msg += f"{attacker.name} is already under the effect of perish song!\n"
else:
attacker.perish_song.set_turns(4)
if defender.perish_song.active():
msg += f"{defender.name} is already under the effect of perish song!\n"
elif defender.ability(attacker=attacker, move=self) == Ability.SOUNDPROOF:
msg += f"{defender.name}'s soundproof protects it from hearing the song!\n"
else:
defender.perish_song.set_turns(4)
# Nightmare
if self.effect == 108:
defender.nightmare = True
msg += f"{defender.name} fell into a nightmare!\n"
# Gravity
if self.effect == 216:
battle.gravity.set_turns(5)
msg += "Gravity intensified!\n"
defender.telekinesis.set_turns(0)
if defender.fly:
defender.fly = False
defender.locked_move = None
msg += f"{defender.name} fell from the sky!\n"
# Spikes
if self.effect == 113:
defender.owner.spikes += 1
msg += f"Spikes were scattered around the feet of {defender.owner.name}'s team!\n"
# Toxic Spikes
if self.effect == 250:
defender.owner.toxic_spikes += 1
msg += f"Toxic spikes were scattered around the feet of {defender.owner.name}'s team!\n"
# Stealth Rock
if self.effect == 267:
defender.owner.stealth_rock = True
msg += f"Pointed stones float in the air around {defender.owner.name}'s team!\n"
# Sticky Web
if self.effect == 341:
defender.owner.sticky_web = True
msg += f"A sticky web is shot around the feet of {defender.owner.name}'s team!\n"
# Defense curl
if self.effect == 157 and not attacker.defense_curl:
attacker.defense_curl = True
# Psych Up
if self.effect == 144:
attacker.attack_stage = defender.attack_stage
attacker.defense_stage = defender.defense_stage
attacker.spatk_stage = defender.spatk_stage
attacker.spdef_stage = defender.spdef_stage
attacker.speed_stage = defender.speed_stage
attacker.accuracy_stage = defender.accuracy_stage
attacker.evasion_stage = defender.evasion_stage
attacker.focus_energy = defender.focus_energy
msg += "It psyched itself up!\n"
# Conversion
if self.effect == 31:
t = attacker.moves[0].type
if t not in ElementType.__members__.values():
t = ElementType.NORMAL
attacker.type_ids = [t]
t = ElementType(t).name.lower()
msg += f"{attacker.name} transformed into a {t} type!\n"
# Conversion 2
if self.effect == 94:
t = self.get_conversion_2(attacker, defender, battle)
attacker.type_ids = [t]
t = ElementType(t).name.lower()
msg += f"{attacker.name} transformed into a {t} type!\n"
# Burn up
if self.effect == 398:
attacker.type_ids.remove(ElementType.FIRE)
msg += f"{attacker.name} lost its fire type!\n"
# Double shock
if self.effect == 481:
attacker.type_ids.remove(ElementType.ELECTRIC)
msg += f"{attacker.name} lost its electric type!\n"
# Forest's Curse
if self.effect == 376:
defender.type_ids.append(ElementType.GRASS)
msg += f"{defender.name} added grass type!\n"
# Trick or Treat
if self.effect == 343:
defender.type_ids.append(ElementType.GHOST)
msg += f"{defender.name} added ghost type!\n"
# Soak
if self.effect == 295:
defender.type_ids = [ElementType.WATER]
msg += f"{defender.name} was transformed into a water type!\n"
# Magic Powder
if self.effect == 456:
defender.type_ids = [ElementType.PSYCHIC]
msg += f"{defender.name} was transformed into a psychic type!\n"
# Camouflage
if self.effect == 214:
if battle.terrain.item == "grassy":
attacker.type_ids = [ElementType.GRASS]
msg += f"{attacker.name} was transformed into a grass type!\n"
elif battle.terrain.item == "misty":
attacker.type_ids = [ElementType.FAIRY]
msg += f"{attacker.name} was transformed into a fairy type!\n"
elif battle.terrain.item == "electric":
attacker.type_ids = [ElementType.ELECTRIC]
msg += f"{attacker.name} was transformed into a electric type!\n"
elif battle.terrain.item == "psychic":
attacker.type_ids = [ElementType.PSYCHIC]
msg += f"{attacker.name} was transformed into a psychic type!\n"
else:
attacker.type_ids = [ElementType.NORMAL]
msg += f"{attacker.name} was transformed into a normal type!\n"
# Role Play
if self.effect == 179:
attacker.ability_id = defender.ability_id
ability_name = Ability(attacker.ability_id).pretty_name
msg += f"{attacker.name} acquired {ability_name}!\n"
msg += attacker.send_out_ability(defender, battle)
# Simple Beam
if self.effect == 299:
defender.ability_id = Ability.SIMPLE
msg += f"{defender.name} acquired simple!\n"
msg += defender.send_out_ability(attacker, battle)
# Entrainment
if self.effect == 300:
defender.ability_id = attacker.ability_id
ability_name = Ability(defender.ability_id).pretty_name
msg += f"{defender.name} acquired {ability_name}!\n"
msg += defender.send_out_ability(attacker, battle)
# Worry Seed
if self.effect == 248:
defender.ability_id = Ability.INSOMNIA
if defender.nv.sleep():
defender.nv.reset()
msg += f"{defender.name} acquired insomnia!\n"
msg += defender.send_out_ability(attacker, battle)
# Skill Swap
if self.effect == 192:
defender.ability_id, attacker.ability_id = attacker.ability_id, defender.ability_id
ability_name = Ability(defender.ability_id).pretty_name
msg += f"{defender.name} acquired {ability_name}!\n"
msg += defender.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(defender, battle)
# Aurora Veil
if self.effect == 407:
if attacker.held_item == "light-clay":
attacker.owner.aurora_veil.set_turns(8)
else:
attacker.owner.aurora_veil.set_turns(5)
msg += f"{attacker.name} put up its aurora veil!\n"
# Light Screen
if self.effect in (36, 421):
if attacker.held_item == "light-clay":
attacker.owner.light_screen.set_turns(8)
else:
attacker.owner.light_screen.set_turns(5)
msg += f"{attacker.name} put up its light screen!\n"
# Reflect
if self.effect in (66, 422):
if attacker.held_item == "light-clay":
attacker.owner.reflect.set_turns(8)
else:
attacker.owner.reflect.set_turns(5)
msg += f"{attacker.name} put up its reflect!\n"
# Mist
if self.effect == 47:
attacker.owner.mist.set_turns(5)
msg += f"{attacker.name} gained the protection of mist!\n"
# Bind
if self.effect in (43, 262) and not defender.substitute and not defender.bind.active():
if attacker.held_item == "grip-claw":
defender.bind.set_turns(7)
else:
defender.bind.set_turns(random.randint(4, 5))
msg += f"{defender.name} was squeezed!\n"
# Sketch
if self.effect == 96:
m = defender.last_move.copy()
attacker.moves[attacker.moves.index(self)] = m
msg += f"The move {m.pretty_name} was sketched!\n"
# Transform
if self.effect == 58:
msg += f"{attacker.name} transformed into {defender._name}!\n"
attacker.transform(defender)
# Substitute
if self.effect == 80:
hp = attacker.starting_hp // 4
msg += attacker.damage(hp, battle, attacker=attacker, source="building a substitute")
attacker.substitute = hp
attacker.bind = ExpiringEffect(0)
msg += f"{attacker.name} made a substitute!\n"
# Shed Tail
if self.effect == 493:
hp = attacker.starting_hp // 4
msg += attacker.damage(attacker.starting_hp // 2, battle, attacker=attacker, source="building a substitute")
attacker.owner.next_substitute = hp
attacker.bind = ExpiringEffect(0)
msg += f"{attacker.name} left behind a substitute!\n"
msg += attacker.remove(battle)
# Force this pokemon to immediately return to be attacked
attacker.owner.mid_turn_remove = True
# Throat Chop
if self.effect == 393 and not defender.silenced.active():
if random.randint(1, 100) <= effect_chance:
defender.silenced.set_turns(3)
msg += f"{defender.name} was silenced!\n"
# Speed Swap
if self.effect == 399:
attacker.speed, defender.speed = defender.speed, attacker.speed
msg += "Both pokemon exchange speed!\n"
# Mimic
if self.effect == 83:
m = defender.last_move.copy()
m.pp = m.starting_pp
attacker.moves[attacker.moves.index(self)] = m
msg += f"{attacker.name} mimicked {m.pretty_name}!\n"
# Rage
if self.effect == 82:
attacker.rage = True
msg += f"{attacker.name}'s rage is building!\n"
# Mind Reader
if self.effect == 95:
defender.mind_reader.set(attacker, 2)
msg += f"{attacker.name} took aim at {defender.name}!\n"
# Destiny Bond
if self.effect == 99:
attacker.destiny_bond = True
attacker.destiny_bond_cooldown.set_turns(2)
msg += f"{attacker.name} is trying to take its foe with it!\n"
# Ingrain
if self.effect == 182:
attacker.ingrain = True
msg += f"{attacker.name} planted its roots!\n"
# Attract
if self.effect == 121:
msg += defender.infatuate(attacker, move=self)
# Heart Swap
if self.effect == 251:
attacker.attack_stage, defender.attack_stage = defender.attack_stage, attacker.attack_stage
attacker.defense_stage, defender.defense_stage = defender.defense_stage, attacker.defense_stage
attacker.spatk_stage, defender.spatk_stage = defender.spatk_stage, attacker.spatk_stage
attacker.spdef_stage, defender.spdef_stage = defender.spdef_stage, attacker.spdef_stage
attacker.speed_stage, defender.speed_stage = defender.speed_stage, attacker.speed_stage
attacker.accuracy_stage, defender.accuracy_stage = defender.accuracy_stage, attacker.accuracy_stage
attacker.evasion_stage, defender.evasion_stage = defender.evasion_stage, attacker.evasion_stage
msg += f"{attacker.name} switched stat changes with {defender.name}!\n"
# Power Swap
if self.effect == 244:
attacker.attack_stage, defender.attack_stage = defender.attack_stage, attacker.attack_stage
attacker.spatk_stage, defender.spatk_stage = defender.spatk_stage, attacker.spatk_stage
msg += f"{attacker.name} switched attack and special attack stat changes with {defender.name}!\n"
# Guard Swap
if self.effect == 245:
attacker.defense_stage, defender.defense_stage = defender.defense_stage, attacker.defense_stage
attacker.spdef_stage, defender.spdef_stage = defender.spdef_stage, attacker.spdef_stage
msg += f"{attacker.name} switched defense and special defense stat changes with {defender.name}!\n"
# Aqua Ring
if self.effect == 252:
attacker.aqua_ring = True
msg += f"{attacker.name} surrounded itself with a veil of water!\n"
# Magnet Rise
if self.effect == 253:
attacker.magnet_rise.set_turns(5)
msg += f"{attacker.name} levitated with electromagnetism!\n"
# Healing Wish
if self.effect == 221:
attacker.owner.healing_wish = True
msg += f"{attacker.name}'s replacement will be restored!\n"
# Lunar Dance
if self.effect == 271:
attacker.owner.lunar_dance = True
msg += f"{attacker.name}'s replacement will be restored!\n"
# Gastro Acid
if self.effect == 240:
defender.ability_id = None
msg += f"{defender.name}'s ability was disabled!\n"
# Lucky Chant
if self.effect == 241:
attacker.lucky_chant.set_turns(5)
msg += f"{attacker.name} is shielded from critical hits!\n"
# Safeguard
if self.effect == 125:
attacker.owner.safeguard.set_turns(5)
msg += f"{attacker.name} is protected from status effects!\n"
# Guard Split
if self.effect == 280:
attacker.defense_split = defender.get_raw_defense()
attacker.spdef_split = defender.get_raw_spdef()
defender.defense_split = attacker.get_raw_defense()
defender.spdef_split = attacker.get_raw_spdef()
msg += f"{attacker.name} and {defender.name} shared their guard!\n"
# Power Split
if self.effect == 281:
attacker.attack_split = defender.get_raw_attack()
attacker.spatk_split = defender.get_raw_spatk()
defender.attack_split = attacker.get_raw_attack()
defender.spatk_split = attacker.get_raw_spatk()
msg += f"{attacker.name} and {defender.name} shared their power!\n"
# Smack Down/Thousand Arrows
if self.effect in (288, 373):
defender.telekinesis.set_turns(0)
if defender.fly:
defender.fly = False
defender.locked_move = None
defender.has_moved = True
msg += f"{defender.name} was shot out of the air!\n"
if not defender.grounded(battle, attacker=attacker, move=self):
defender.grounded_by_move = True
msg += f"{defender.name} was grounded!\n"
# Reflect Type
if self.effect == 319:
attacker.type_ids = defender.type_ids.copy()
msg += f"{attacker.name}'s type changed to match {defender.name}!\n"
# Charge
if self.effect == 175:
# TODO: Gen 9 makes charge last until an electric move is used
attacker.charge.set_turns(2)
msg += f"{attacker.name} charges up electric type moves!\n"
# Magic Coat
if self.effect == 184:
attacker.magic_coat = True
msg += f"{attacker.name} shrouded itself with a magic coat!\n"
# Tailwind
if self.effect == 226:
attacker.owner.tailwind.set_turns(4)
msg += f"{attacker.owner.name}'s team gets a tailwind!\n"
if attacker.ability() == Ability.WIND_RIDER:
msg += attacker.append_attack(1, attacker=attacker, source="its wind rider")
# Fling
if self.effect == 234 and attacker.held_item.can_remove():
item = attacker.held_item.name
msg += f"{attacker.name}'s {item} was flung away!\n"
if attacker.held_item.is_berry():
msg += attacker.held_item.eat_berry(consumer=defender, attacker=attacker, move=self)
else:
attacker.held_item.use()
if item == "flame-orb":
msg += defender.nv.apply_status("burn", battle, attacker=attacker, move=self)
elif item in ("kings-rock", "razor-fang"):
msg += defender.flinch(attacker=attacker, move=self)
elif item == "light-ball":
msg += defender.nv.apply_status("paralysis", battle, attacker=attacker, move=self)
elif item == "mental-herb":
defender.infatuated = None
defender.taunt = ExpiringEffect(0)
defender.encore = ExpiringItem()
defender.torment = False
defender.disable = ExpiringItem()
defender.heal_block = ExpiringEffect(0)
msg += f"{defender.name} feels refreshed!\n"
elif item == "poison-barb":
msg += defender.nv.apply_status("poison", battle, attacker=attacker, move=self)
elif item == "toxic-orb":
msg += defender.nv.apply_status("b-poison", battle, attacker=attacker, move=self)
elif item == "white-herb":
defender.attack_stage = max(0, defender.attack_stage)
defender.defense_stage = max(0, defender.defense_stage)
defender.spatk_stage = max(0, defender.spatk_stage)
defender.spdef_stage = max(0, defender.spdef_stage)
defender.speed_stage = max(0, defender.speed_stage)
defender.accuracy_stage = max(0, defender.accuracy_stage)
defender.evasion_stage = max(0, defender.evasion_stage)
msg += f"{defender.name} feels refreshed!\n"
# Thief
if self.effect == 106 and defender.held_item.has_item() and defender.held_item.can_remove() and not defender.substitute and not attacker.held_item.has_item():
if defender.ability(attacker=attacker, move=self) == Ability.STICKY_HOLD:
msg += f"{defender.name}'s sticky hand kept hold of its item!\n"
else:
defender.held_item.transfer(attacker.held_item)
msg += f"{defender.name}'s {attacker.held_item.name} was stolen!\n"
# Trick
if self.effect == 178:
attacker.held_item.swap(defender.held_item)
msg += f"{attacker.name} and {defender.name} swapped their items!\n"
if attacker.held_item.name is not None:
msg += f"{attacker.name} gained {attacker.held_item.name}!\n"
if defender.held_item.name is not None:
msg += f"{defender.name} gained {defender.held_item.name}!\n"
# Knock off
if self.effect == 189 and defender.held_item.has_item() and defender.held_item.can_remove() and not defender.substitute and attacker.hp > 0:
if defender.ability(attacker=attacker, move=self) == Ability.STICKY_HOLD:
msg += f"{defender.name}'s sticky hand kept hold of its item!\n"
else:
msg += f"{defender.name} lost its {defender.held_item.name}!\n"
defender.held_item.remove()
# Teatime
if self.effect == 476:
msgadd = ""
for poke in (attacker, defender):
msgadd += poke.held_item.eat_berry(attacker=attacker, move=self)
msg += msgadd
if not msgadd:
msg += "But nothing happened..."
# Corrosive Gas
if self.effect == 430:
if defender.ability(attacker=attacker, move=self) == Ability.STICKY_HOLD:
msg += f"{defender.name}'s sticky hand kept hold of its item!\n"
else:
msg += f"{defender.name}'s {defender.held_item.name} was corroded!\n"
defender.corrosive_gas = True
# Mud Sport
if self.effect == 202:
attacker.owner.mud_sport.set_turns(6)
msg += "Electricity's power was weakened!\n"
# Water Sport
if self.effect == 211:
attacker.owner.water_sport.set_turns(6)
msg += "Fire's power was weakened!\n"
# Power Trick
if self.effect == 239:
attacker.power_trick = not attacker.power_trick
msg += f"{attacker.name} switched its Attack and Defense!\n"
# Power Shift
if self.effect == 466:
attacker.power_shift = not attacker.power_shift
msg += f"{attacker.name} switched its offensive and defensive stats!\n"
# Yank
if self.effect == 188:
if battle.terrain.item == "electric" and defender.grounded(battle, attacker=attacker, move=self):
msg += f"{defender.name} keeps alert from being shocked by the electric terrain!\n"
else:
defender.yawn.set_turns(2)
msg += f"{defender.name} is drowsy!\n"
# Rototiller
if self.effect == 340:
for p in (attacker, defender):
if ElementType.GRASS not in p.type_ids:
continue
if not p.grounded(battle):
continue
if p.dive or p.dig or p.fly or p.shadow_force:
continue
msg += p.append_attack(1, attacker=attacker, move=self)
msg += p.append_spatk(1, attacker=attacker, move=self)
# Flower Shield
if self.effect == 351:
for p in (attacker, defender):
if ElementType.GRASS not in p.type_ids:
continue
if not p.grounded(battle):
continue
if p.dive or p.dig or p.fly or p.shadow_force:
continue
msg += p.append_defense(1, attacker=attacker, move=self)
# Ion Deluge
if self.effect == 345:
attacker.ion_deluge = True
msg += f"{attacker.name} charges up the air!\n"
# Topsy Turvy
if self.effect == 348:
defender.attack_stage = -defender.attack_stage
defender.defense_stage = -defender.defense_stage
defender.spatk_stage = -defender.spatk_stage
defender.spdef_stage = -defender.spdef_stage
defender.speed_stage = -defender.speed_stage
defender.accuracy_stage = -defender.accuracy_stage
defender.evasion_stage = -defender.evasion_stage
msg += f"{defender.name}'s stat stages were inverted!\n"
# Electrify
if self.effect == 354:
defender.electrify = True
msg += f"{defender.name}'s move was charged with electricity!\n"
# Instruct
if self.effect == 403:
hm = defender.has_moved
defender.has_moved = False
msg += defender.last_move.use(defender, attacker, battle)
defender.has_moved = hm
# Core Enforcer
if self.effect == 402 and defender.has_moved and defender.ability_changeable():
defender.ability_id = None
msg += f"{defender.name}'s ability was nullified!\n"
# Laser Focus
if self.effect == 391:
attacker.laser_focus.set_turns(2)
msg += f"{attacker.name} focuses!\n"
# Powder
if self.effect == 378:
defender.powdered = True
msg += f"{defender.name} was coated in powder!\n"
# Rapid/Mortal Spin
if self.effect in (130, 486):
attacker.bind.set_turns(0)
attacker.trapping = False
attacker.leech_seed = False
attacker.owner.spikes = 0
attacker.owner.toxic_spikes = 0
attacker.owner.stealth_rock = False
attacker.owner.sticky_web = False
msg += f"{attacker.name} was released!\n"
# Snatch
if self.effect == 196:
attacker.snatching = True
msg += f"{attacker.name} waits for a target to make a move!\n"
# Telekinesis
if self.effect == 286:
defender.telekinesis.set_turns(5)
msg += f"{defender.name} was hurled into the air!\n"
# Embargo
if self.effect == 233:
defender.embargo.set_turns(6)
msg += f"{defender.name} can't use items anymore!\n"
# Echoed Voice
if self.effect == 303:
attacker.echoed_voice_power = min(attacker.echoed_voice_power + 40, 200)
attacker.echoed_voice_used = True
msg += f"{attacker.name}'s voice echos!\n"
# Bestow
if self.effect == 324:
attacker.held_item.transfer(defender.held_item)
msg += f"{attacker.name} gave its {defender.held_item.name} to {defender.name}!\n"
# Curse
if self.effect == 110:
if ElementType.GHOST in attacker.type_ids:
msg += attacker.damage(attacker.starting_hp // 2, battle, source="inflicting the curse")
defender.curse = True
msg += f"{defender.name} was cursed!\n"
else:
msg += attacker.append_speed(-1, attacker=attacker, move=self)
msg += attacker.append_attack(1, attacker=attacker, move=self)
msg += attacker.append_defense(1, attacker=attacker, move=self)
# Autotomize
if self.effect == 285:
attacker.autotomize += 1
msg += f"{attacker.name} became nimble!\n"
# Fell Stinger
if self.effect == 342 and defender.hp == 0:
msg += attacker.append_attack(3, attacker=attacker, move=self)
# Fairy Lock
if self.effect == 355:
attacker.fairy_lock.set_turns(2)
msg += f"{attacker.name} prevents escape next turn!\n"
# Grudge
if self.effect == 195:
attacker.grudge = True
msg += f"{attacker.name} has a grudge!\n"
# Foresight
if self.effect == 114:
defender.foresight = True
msg += f"{attacker.name} identified {defender.name}!\n"
# Miracle Eye
if self.effect == 217:
defender.miracle_eye = True
msg += f"{attacker.name} identified {defender.name}!\n"
# Clangorous Soul
if self.effect == 414:
msg += attacker.damage(attacker.starting_hp // 3, battle)
# No Retreat
if self.effect == 427:
attacker.no_retreat = True
msg += f"{attacker.name} takes its last stand!\n"
# Recycle
if self.effect == 185:
attacker.held_item.recover(attacker.held_item)
msg += f"{attacker.name} recovered their {attacker.held_item.name}!\n"
if attacker.held_item.should_eat_berry(defender):
msg += attacker.held_item.eat_berry(attacker=defender, move=self)
# Court Change
if self.effect == 431:
attacker.owner.spikes, defender.owner.spikes = defender.owner.spikes, attacker.owner.spikes
attacker.owner.toxic_spikes, defender.owner.toxic_spikes = defender.owner.toxic_spikes, attacker.owner.toxic_spikes
attacker.owner.stealth_rock, defender.owner.stealth_rock = defender.owner.stealth_rock, attacker.owner.stealth_rock
attacker.owner.sticky_web, defender.owner.sticky_web = defender.owner.sticky_web, attacker.owner.sticky_web
attacker.owner.aurora_veil, defender.owner.aurora_veil = defender.owner.aurora_veil, attacker.owner.aurora_veil
attacker.owner.light_screen, defender.owner.light_screen = defender.owner.light_screen, attacker.owner.light_screen
attacker.owner.reflect, defender.owner.reflect = defender.owner.reflect, attacker.owner.reflect
attacker.owner.mist, defender.owner.mist = defender.owner.mist, attacker.owner.mist
attacker.owner.safeguard, defender.owner.safeguard = defender.owner.safeguard, attacker.owner.safeguard
attacker.owner.tailwind, defender.owner.tailwind = defender.owner.tailwind, attacker.owner.tailwind
msg += "Active battle effects swapped sides!\n"
# Roost
if self.effect == 215:
attacker.roost = True
if ElementType.FLYING in attacker.type_ids:
msg += f"{attacker.name}'s flying type is surpressed!\n"
# Pluck
if self.effect == 225 and defender.ability(attacker=attacker, move=self) != Ability.STICKY_HOLD:
msg += defender.held_item.eat_berry(consumer=attacker)
# Focus energy
if self.effect == 48:
attacker.focus_energy = True
msg += f"{attacker.name} focuses on its target!\n"
# Natural Gift
if self.effect == 223:
msg += f"{attacker.name}'s {attacker.held_item.name} was consumed!\n"
attacker.held_item.use()
# Gulp Missile
if self.effect == 258 and attacker.ability() == Ability.GULP_MISSILE and attacker._name == "Cramorant":
if attacker.hp > attacker.starting_hp // 2:
if attacker.form("Cramorant-gulping"):
msg += f"{attacker.name} gulped up an arrokuda!\n"
else:
if attacker.form("Cramorant-gorging"):
msg += f"{attacker.name} gulped up a pikachu!\n"
# Steel Roller
if self.effect in (418, 448) and battle.terrain.item is not None:
battle.terrain.end()
msg += "The terrain was cleared!\n"
# Octolock
if self.effect == 452:
defender.octolock = True
msg += f"{defender.name} is octolocked!\n"
# Stuff Cheeks
if self.effect == 453:
msg += attacker.held_item.eat_berry()
# Plasma Fists
if self.effect == 455:
if not battle.plasma_fists:
battle.plasma_fists = True
msg += f"{attacker.name} electrifies the battlefield, energizing normal type moves!\n"
# Secret Power
if self.effect == 198:
if random.randint(1, 100) <= effect_chance:
if battle.terrain.item == "grassy":
msg += defender.nv.apply_status("sleep", battle, attacker=attacker, move=self)
elif battle.terrain.item == "misty":
msg += defender.append_spatk(-1, attacker=attacker, move=self)
elif battle.terrain.item == "psychic":
msg += defender.append_speed(-1, attacker=attacker, move=self)
else:
msg += defender.nv.apply_status("paralysis", battle, attacker=attacker, move=self)
if self.is_sound_based() and attacker.held_item == "throat-spray":
msg += attacker.append_spatk(1, attacker=attacker, source="it's throat spray")
attacker.held_item.use()
# Tar Shot
if self.effect == 477 and not defender.tar_shot:
defender.tar_shot = True
msg += f"{defender.name} is covered in sticky tar!\n"
# Tidy Up
if self.effect == 487:
defender.owner.spikes = 0
defender.owner.toxic_spikes = 0
defender.owner.stealth_rock = False
defender.owner.sticky_web = False
defender.substitute = 0
attacker.owner.spikes = 0
attacker.owner.toxic_spikes = 0
attacker.owner.stealth_rock = False
attacker.owner.sticky_web = False
attacker.substitute = 0
msg += f"{attacker.name} tidied up!\n"
# Syrup Bomb
if self.effect == 503:
defender.syrup_bomb.set_turns(4)
msg += f"{defender.name} got covered in sticky candy syrup!\n"
# Dancer Ability - Runs at the end of move usage
if defender.ability(attacker=attacker, move=self) == Ability.DANCER and self.is_dance() and use_pp:
hm = defender.has_moved
msg += self.use(defender, attacker, battle, use_pp=False)
defender.has_moved = hm
return msg
def attack(self, attacker, defender, battle):
"""
Attacks the defender using this move.
Returns a string of formatted results of this attack and the number of hits this move did.
"""
#https://bulbapedia.bulbagarden.net/wiki/Damage
msg = ""
current_type = self.get_type(attacker, defender, battle)
# Move effectiveness
effectiveness = defender.effectiveness(current_type, battle, attacker=attacker, move=self)
if self.effect == 338:
effectiveness *= defender.effectiveness(ElementType.FLYING, battle, attacker=attacker, move=self)
if effectiveness <= 0:
return ("The attack had no effect!\n", 0)
if effectiveness <= .5:
msg += "It's not very effective...\n"
elif effectiveness >= 2:
msg += "It's super effective!\n"
# Calculate the number of hits for this move.
parental_bond = False
min_hits = self.min_hits
max_hits = self.max_hits
if self.effect == 361 and attacker._name == "Greninja-ash":
hits = 3
elif min_hits is not None and max_hits is not None:
# Handle hit range overrides
if attacker.ability() == Ability.SKILL_LINK:
min_hits = max_hits
elif attacker.held_item == "loaded-dice" and max_hits >= 4 and (min_hits < 4 or self.effect == 484):
min_hits = 4
# Randomly select number of hits
if min_hits == 2 and max_hits == 5:
hits = random.choice([
2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3,
4, 4, 4,
5, 5, 5
])
else:
hits = random.randint(min_hits, max_hits)
else:
if attacker.ability() == Ability.PARENTAL_BOND:
hits = 2
parental_bond = True
else:
hits = 1
for hit in range(hits):
if defender.hp == 0:
break
# Explosion faints the user first, but should still do damage after death.
# Future sight still needs to hit after the attacker dies.
# Mind blown still needs to hit after the attacker dies.
if attacker.hp == 0 and self.effect not in (8, 149, 420, 444):
break
# Critical hit chance
critical_stage = self.crit_rate
if attacker.held_item in ("scope-lens", "razor-claw"):
critical_stage += 1
if attacker.ability() == Ability.SUPER_LUCK:
critical_stage += 1
if attacker.focus_energy:
critical_stage += 2
if attacker.lansat_berry_ate:
critical_stage += 2
critical_stage = min(critical_stage, 3)
crit_map = {
0: 24,
1: 8,
2: 2,
3: 1,
}
critical = not random.randrange(crit_map[critical_stage])
if attacker.ability() == Ability.MERCILESS and defender.nv.poison():
critical = True
# Always scores a critical hit.
if self.effect == 289:
critical = True
if attacker.laser_focus.active():
critical = True
if defender.ability(attacker=attacker, move=self) in (Ability.SHELL_ARMOR, Ability.BATTLE_ARMOR):
critical = False
if defender.lucky_chant.active():
critical = False
# Confusion never crits
if self.id == 0xCFCF:
critical = False
# Stats
if self.damage_class == DamageClass.PHYSICAL:
damage_class = DamageClass.PHYSICAL
a = attacker.get_attack(
battle,
critical=critical,
ignore_stages=defender.ability(attacker=attacker, move=self) == Ability.UNAWARE
)
if self.effect == 304:
d = defender.get_raw_defense()
else:
d = defender.get_defense(
battle,
critical=critical,
ignore_stages=attacker.ability() == Ability.UNAWARE,
attacker=attacker,
move=self
)
else:
damage_class = DamageClass.SPECIAL
a = attacker.get_spatk(
battle,
critical=critical,
ignore_stages=defender.ability(attacker=attacker, move=self) == Ability.UNAWARE
)
if self.effect == 304:
d = defender.get_raw_spdef()
else:
d = defender.get_spdef(
battle,
critical=critical,
ignore_stages=attacker.ability() == Ability.UNAWARE,
attacker=attacker,
move=self
)
# Always uses defender's defense
if self.effect == 283:
d = defender.get_defense(
battle,
critical=critical,
ignore_stages=attacker.ability() == Ability.UNAWARE,
attacker=attacker,
move=self
)
# Use the user's defense instead of attack for the attack stat
if self.effect == 426:
# This does not pass critical, otherwise it would crop the wrong direction.
a = attacker.get_defense(
battle,
ignore_stages=defender.ability(attacker=attacker, move=self) == Ability.UNAWARE
)
# Use the defender's attacking stat
if self.effect == 298:
if self.damage_class == DamageClass.PHYSICAL:
a = defender.get_attack(
battle,
critical=critical,
ignore_stages=defender.ability(attacker=attacker, move=self) == Ability.UNAWARE
)
else:
a = defender.get_spatk(
battle,
critical=critical,
ignore_stages=defender.ability(attacker=attacker, move=self) == Ability.UNAWARE
)
# Use the higher of attack or special attack
if self.effect == 416:
ignore_stages = defender.ability(attacker=attacker, move=self) == Ability.UNAWARE
a = max(attacker.get_attack(battle, critical=critical, ignore_stages=ignore_stages), attacker.get_spatk(battle, critical=critical, ignore_stages=ignore_stages))
if attacker.flash_fire and current_type == ElementType.FIRE:
a *= 1.5
if defender.ability(attacker=attacker, move=self) == Ability.THICK_FAT and current_type in (ElementType.FIRE, ElementType.ICE):
a *= .5
power = self.get_power(attacker, defender, battle)
if power is None:
raise ValueError(f"{self.name} has no power and no override.")
# Check accuracy on each hit
# WARNING: If there is something BEFORE this in the loop which adds to msg (like "A critical hit")
# it MUST be after this block, or it will appear even after "misses" from this move.
if hit > 0 and not attacker.ability() == Ability.SKILL_LINK:
# Increasing damage each hit
if self.effect == 105:
if not self.check_hit(attacker, defender, battle):
# Reset the number of hits to the number of ACTUAL hits
hits = hit
break
# x2 then x3
power *= 1 + hit
# Only checks if loaded dice did not activate
if self.effect == 484 and attacker.held_item != "loaded-dice":
if not self.check_hit(attacker, defender, battle):
hits = hit
break
damage = 2 * attacker.level
damage /= 5
damage += 2
damage = damage * power * (a / d)
damage /= 50
damage += 2
# Critical hit damage
if critical:
msg += "A critical hit!\n"
damage *= 1.5
# Type buffing weather
if current_type == ElementType.WATER and battle.weather.get() in ("rain", "h-rain"):
damage *= 1.5
elif current_type == ElementType.FIRE and battle.weather.get() in ("rain", "h-rain"):
damage *= 0.5
elif current_type == ElementType.FIRE and battle.weather.get() == "sun":
damage *= 1.5
elif current_type == ElementType.WATER and battle.weather.get() == "sun":
damage *= 0.5
# Same type attack bonus - extra damage for using a move that is the same type as your poke's type.
if current_type in attacker.type_ids:
if attacker.ability() == Ability.ADAPTABILITY:
damage *= 2
else:
damage *= 1.5
# Move effectiveness
damage *= effectiveness
# Burn
if (
attacker.nv.burn()
and damage_class == DamageClass.PHYSICAL
and attacker.ability() != Ability.GUTS
and self.effect != 170
):
damage *= .5
# Aurora Veil, Light Screen, Reflect do not stack but all reduce incoming damage in some way
if not critical and attacker.ability() != Ability.INFILTRATOR:
if defender.owner.aurora_veil.active():
damage *= .5
elif defender.owner.light_screen.active() and damage_class == DamageClass.SPECIAL:
damage *= .5
elif defender.owner.reflect.active() and damage_class == DamageClass.PHYSICAL:
damage *= .5
# Moves that do extra damage to minimized pokes
if defender.minimized and self.effect == 338:
damage *= 2
# Fluffy
if defender.ability(attacker=attacker, move=self) == Ability.FLUFFY:
if self.makes_contact(attacker):
damage *= .5
if current_type == ElementType.FIRE:
damage *= 2
# Abilities that change damage
if defender.ability(attacker=attacker, move=self) in (Ability.FILTER, Ability.PRISM_ARMOR, Ability.SOLID_ROCK) and effectiveness > 1:
damage *= .75
if attacker.ability() == Ability.NEUROFORCE and effectiveness > 1:
damage *= 1.25
if defender.ability(attacker=attacker, move=self) == Ability.ICE_SCALES and damage_class == DamageClass.SPECIAL:
damage *= .5
if attacker.ability() == Ability.SNIPER and critical:
damage *= 1.5
if attacker.ability() == Ability.TINTED_LENS and effectiveness < 1:
damage *= 2
if attacker.ability() == Ability.PUNK_ROCK and self.is_sound_based():
damage *= 1.3
if defender.ability(attacker=attacker, move=self) == Ability.PUNK_ROCK and self.is_sound_based():
damage *= .5
if defender.ability(attacker=attacker, move=self) == Ability.HEATPROOF and current_type == ElementType.FIRE:
damage *= .5
if defender.ability(attacker=attacker, move=self) == Ability.PURIFYING_SALT and current_type == ElementType.GHOST:
damage *= .5
if Ability.DARK_AURA in (attacker.ability(), defender.ability(attacker=attacker, move=self)) and current_type == ElementType.DARK:
if Ability.AURA_BREAK in (attacker.ability(), defender.ability(attacker=attacker, move=self)):
damage *= .75
else:
damage *= 4/3
if Ability.FAIRY_AURA in (attacker.ability(), defender.ability(attacker=attacker, move=self)) and current_type == ElementType.FAIRY:
if Ability.AURA_BREAK in (attacker.ability(), defender.ability(attacker=attacker, move=self)):
damage *= .75
else:
damage *= 4/3
if defender.ability(attacker=attacker, move=self) == Ability.DRY_SKIN and current_type == ElementType.FIRE:
damage *= 1.25
# Items that change damage
if defender.held_item == "chilan-berry" and current_type == ElementType.NORMAL:
damage *= .5
if attacker.held_item == "expert-belt" and effectiveness > 1:
damage *= 1.2
if (
attacker.held_item == "life-orb"
and self.damage_class != DamageClass.STATUS
and self.effect != 149
):
damage *= 1.3
if attacker.held_item == "metronome":
damage *= attacker.metronome.get_buff(self.name)
# Parental bond - adds an extra low power hit
if parental_bond and hit > 0:
damage *= .25
# Reduced damage while at full hp
if defender.ability(attacker=attacker, move=self) in (Ability.MULTISCALE, Ability.SHADOW_SHIELD) and defender.hp == defender.starting_hp:
damage *= .5
# Random damage scaling
damage *= random.uniform(0.85, 1)
damage = max(1, int(damage))
# Cannot lower the target's HP below 1.
if self.effect == 102:
damage = min(damage, defender.hp - 1)
# Drain ratios
drain_heal_ratio = None
if self.effect in (4, 9, 346, 500):
drain_heal_ratio = 1/2
elif self.effect == 349:
drain_heal_ratio = 3/4
# Do the damage
msgadd, damage = defender._damage(damage, battle, move=self, move_type=current_type, attacker=attacker, critical=critical, drain_heal_ratio=drain_heal_ratio)
msg += msgadd
# Recoil
if attacker.ability() != Ability.ROCK_HEAD and defender.owner.has_alive_pokemon():
if self.effect == 49:
msg += attacker.damage(damage // 4, battle, source="recoil")
if self.effect in (199, 254, 263, 469):
msg += attacker.damage(damage // 3, battle, source="recoil")
if self.effect == 270:
msg += attacker.damage(damage // 2, battle, source="recoil")
if self.effect == 463:
msg += attacker.damage(attacker.starting_hp // 2, battle, source="recoil")
# Weakness Policy
if effectiveness > 1 and defender.held_item == "weakness-policy" and not defender.substitute:
msg += defender.append_attack(2, attacker=defender, move=self, source="its weakness policy")
msg += defender.append_spatk(2, attacker=defender, move=self, source="its weakness policy")
defender.held_item.use()
return (msg, hits)
def get_power(self, attacker, defender, battle):
"""Get the power of this move."""
current_type = self.get_type(attacker, defender, battle)
# Inflicts damage equal to the user's level.
if self.effect == 88:
power = attacker.level
# Inflicts damage between 50% and 150% of the user's level.
elif self.effect == 89:
power = random.randint(int(attacker.level * 0.5), int(attacker.level * 1.5))
# Inflicts more damage to heavier targets, with a maximum of 120 power.
elif self.effect == 197:
def_weight = defender.weight(attacker=attacker, move=self)
if def_weight <= 100:
power = 20
elif def_weight <= 250:
power = 40
elif def_weight <= 500:
power = 60
elif def_weight <= 1000:
power = 80
elif def_weight <= 2000:
power = 100
else:
power = 120
# Power is higher when the user weighs more than the target, up to a maximum of 120.
elif self.effect == 292:
weight_delta = attacker.weight() / defender.weight(attacker=attacker, move=self)
if weight_delta <= 2:
power = 40
elif weight_delta <= 3:
power = 60
elif weight_delta <= 4:
power = 80
elif weight_delta <= 5:
power = 100
else:
power = 120
# Power increases with happiness, up to a maximum of 102.
elif self.effect == 122:
power = int(attacker.happiness / 2.5)
if power > 102:
power = 102
elif power < 1:
power = 1
# Power increases as happiness **decreases**, up to a maximum of 102.
elif self.effect == 124:
power = int((255 - attacker.happiness) / 2.5)
if power > 102:
power = 102
elif power < 1:
power = 1
# Power raises when the user has lower Speed, up to a maximum of 150.
elif self.effect == 220:
power = min(150, int(1 + 25 * defender.get_speed(battle) / attacker.get_speed(battle)))
# Inflicts more damage when the user has more HP remaining, with a maximum of 150 power.
elif self.effect == 191:
power = int(150 * (attacker.hp / attacker.starting_hp))
# Power is 100 times the amount of energy Stockpiled.
elif self.effect == 162:
power = 100 * attacker.stockpile
# Inflicts more damage when the user has less HP remaining, with a maximum of 200 power.
elif self.effect == 100:
hp_percent = 64 * (attacker.hp / attacker.starting_hp)
if hp_percent <= 1:
power = 200
elif hp_percent <= 5:
power = 150
elif hp_percent <= 12:
power = 100
elif hp_percent <= 21:
power = 80
elif hp_percent <= 42:
power = 40
else:
power = 20
# Power increases when this move has less PP, up to a maximum of 200.
elif self.effect == 236:
if self.pp == 0:
power = 200
elif self.pp == 1:
power = 80
elif self.pp == 2:
power = 60
elif self.pp == 3:
power = 50
else:
power = 40
# Power increases against targets with more HP remaining, up to a maximum of 120|100 power.
elif self.effect == 238:
power = max(1, int(120 * (defender.hp / defender.starting_hp)))
elif self.effect == 495:
power = max(1, int(100 * (defender.hp / defender.starting_hp)))
# Power increases against targets with more raised stats, up to a maximum of 200.
elif self.effect == 246:
delta = 0
delta += max(0, defender.attack_stage)
delta += max(0, defender.defense_stage)
delta += max(0, defender.spatk_stage)
delta += max(0, defender.spdef_stage)
delta += max(0, defender.speed_stage)
power = min(200, 60 + (delta * 20))
# Power is higher when the user has greater Speed than the target, up to a maximum of 150.
elif self.effect == 294:
delta = attacker.get_speed(battle) // defender.get_speed(battle)
if delta <= 0:
power = 40
elif delta <= 1:
power = 60
elif delta <= 2:
power = 80
elif delta <= 3:
power = 120
else:
power = 150
# Power is higher the more the user's stats have been raised.
elif self.effect == 306:
delta = 1
delta += max(0, attacker.attack_stage)
delta += max(0, attacker.defense_stage)
delta += max(0, attacker.spatk_stage)
delta += max(0, attacker.spdef_stage)
delta += max(0, attacker.speed_stage)
delta += max(0, attacker.accuracy_stage)
delta += max(0, attacker.evasion_stage)
power = 20 * delta
# Power doubles every turn this move is used in succession after the first, maxing out after five turns.
elif self.effect == 120:
power = (2 ** attacker.fury_cutter) * 10
attacker.fury_cutter = min(4, attacker.fury_cutter + 1)
# Power doubles every turn this move is used in succession after the first, resetting after five turns.
elif self.effect == 118:
power = (2 ** attacker.locked_move.turn) * self.power
# Power varies randomly from 10 to 150.
elif self.effect == 127:
percentile = random.randint(0, 100)
if percentile <= 5:
power = 10
elif percentile <= 15:
power = 30
elif percentile <= 35:
power = 50
elif percentile <= 65:
power = 70
elif percentile <= 85:
power = 90
elif percentile <= 95:
power = 110
else:
power = 150
# Power is based on the user's held item
elif self.effect == 234:
power = attacker.held_item.power
# Power increases by 100% for each consecutive use by any friendly Pokémon, to a maximum of 200.
elif self.effect == 303:
power = attacker.echoed_voice_power
# Power is dependent on the user's held berry.
elif self.effect == 223:
if attacker.held_item.get() in (
"enigma berry", "rowap berry", "maranga berry", "jaboca berry", "belue berry", "kee berry",
"salac berry", "watmel berry", "lansat berry", "custap berry", "liechi berry", "apicot berry",
"ganlon berry", "petaya berry", "starf berry", "micle berry", "durin berry"
):
power = 100
elif attacker.held_item.get() in (
"cornn berry", "spelon berry", "nomel berry", "wepear berry", "kelpsy berry", "bluk berry",
"grepa berry", "rabuta berry", "pinap berry", "hondew berry", "pomeg berry", "qualot berry",
"tamato berry", "magost berry", "pamtre berry", "nanab berry"
):
power = 90
else:
power = 80
elif self.effect == 361 and attacker._name == "Greninja-ash":
power = 20
# Power is based on the user's base attack. Only applies when not explicitly overridden.
elif self.effect == 155 and self.power is None:
power = (attacker.get_raw_attack() // 10) + 5
# No special changes to power, return its raw value.
else:
power = self.power
if power is None:
return None
#NOTE: this needs to be first as it only applies to raw power
if attacker.ability() == Ability.TECHNICIAN and power <= 60:
power *= 1.5
if attacker.ability() == Ability.TOUGH_CLAWS and self.makes_contact(attacker):
power *= 1.3
if attacker.ability() == Ability.RIVALRY and "-x" not in (attacker.gender, defender.gender):
if attacker.gender == defender.gender:
power *= 1.25
else:
power *= .75
if attacker.ability() == Ability.IRON_FIST and self.is_punching():
power *= 1.2
if attacker.ability() == Ability.STRONG_JAW and self.is_biting():
power *= 1.5
if attacker.ability() == Ability.MEGA_LAUNCHER and self.is_aura_or_pulse():
power *= 1.5
if attacker.ability() == Ability.SHARPNESS and self.is_slicing():
power *= 1.5
if attacker.ability() == Ability.RECKLESS and self.effect in (46, 49, 199, 254, 263, 270):
power *= 1.2
if attacker.ability() == Ability.TOXIC_BOOST and self.damage_class == DamageClass.PHYSICAL and attacker.nv.poison():
power *= 1.5
if attacker.ability() == Ability.FLARE_BOOST and self.damage_class == DamageClass.SPECIAL and attacker.nv.burn():
power *= 1.5
if attacker.ability() == Ability.ANALYTIC and defender.has_moved:
power *= 1.3
if attacker.ability() == Ability.BATTERY and self.damage_class == DamageClass.SPECIAL:
power *= 1.3
if attacker.ability() == Ability.SHEER_FORCE and self.effect_chance is not None: # Not *perfect* but good enough
power *= 1.3
if attacker.ability() == Ability.STAKEOUT and defender.swapped_in:
power *= 2
if attacker.ability() == Ability.SUPREME_OVERLORD:
fainted = sum(poke.hp == 0 for poke in attacker.owner.party)
if fainted:
power *= (10 + fainted) / 10
# Type buffing abilities - Some use naive type because the type is changed.
if attacker.ability() == Ability.AERILATE and self.type == ElementType.NORMAL:
power *= 1.2
if attacker.ability() == Ability.PIXILATE and self.type == ElementType.NORMAL:
power *= 1.2
if attacker.ability() == Ability.GALVANIZE and self.type == ElementType.NORMAL:
power *= 1.2
if attacker.ability() == Ability.REFRIGERATE and self.type == ElementType.NORMAL:
power *= 1.2
if attacker.ability() == Ability.DRAGONS_MAW and current_type == ElementType.DRAGON:
power *= 1.5
if attacker.ability() == Ability.TRANSISTOR and current_type == ElementType.ELECTRIC:
power *= 1.5
if attacker.ability() == Ability.WATER_BUBBLE and current_type == ElementType.WATER:
power *= 2
if defender.ability(attacker=attacker, move=self) == Ability.WATER_BUBBLE and current_type == ElementType.FIRE:
power *= .5
if attacker.ability() == Ability.OVERGROW and current_type == ElementType.GRASS and attacker.hp <= attacker.starting_hp // 3:
power *= 1.5
if attacker.ability() == Ability.BLAZE and current_type == ElementType.FIRE and attacker.hp <= attacker.starting_hp // 3:
power *= 1.5
if attacker.ability() == Ability.TORRENT and current_type == ElementType.WATER and attacker.hp <= attacker.starting_hp // 3:
power *= 1.5
if attacker.ability() == Ability.SWARM and current_type == ElementType.BUG and attacker.hp <= attacker.starting_hp // 3:
power *= 1.5
if attacker.ability() == Ability.NORMALIZE and current_type == ElementType.NORMAL:
power *= 1.2
if attacker.ability() == Ability.SAND_FORCE and current_type in (ElementType.ROCK, ElementType.GROUND, ElementType.STEEL) and battle.weather.get() == "sandstorm":
power *= 1.3
if attacker.ability() in (Ability.STEELWORKER, Ability.STEELY_SPIRIT) and current_type == ElementType.STEEL:
power *= 1.5
if attacker.ability() == Ability.ROCKY_PAYLOAD and current_type == ElementType.ROCK:
power *= 1.5
# Type buffing items
if attacker.held_item == "black-glasses" and current_type == ElementType.DARK:
power *= 1.2
if attacker.held_item == "black-belt" and current_type == ElementType.FIGHTING:
power *= 1.2
if attacker.held_item == "hard-stone" and current_type == ElementType.ROCK:
power *= 1.2
if attacker.held_item == "magnet" and current_type == ElementType.ELECTRIC:
power *= 1.2
if attacker.held_item == "mystic-water" and current_type == ElementType.WATER:
power *= 1.2
if attacker.held_item == "never-melt-ice" and current_type == ElementType.ICE:
power *= 1.2
if attacker.held_item == "dragon-fang" and current_type == ElementType.DRAGON:
power *= 1.2
if attacker.held_item == "poison-barb" and current_type == ElementType.POISON:
power *= 1.2
if attacker.held_item == "charcoal" and current_type == ElementType.FIRE:
power *= 1.2
if attacker.held_item == "silk-scarf" and current_type == ElementType.NORMAL:
power *= 1.2
if attacker.held_item == "metal-coat" and current_type == ElementType.STEEL:
power *= 1.2
if attacker.held_item == "sharp-beak" and current_type == ElementType.FLYING:
power *= 1.2
if attacker.held_item == "draco-plate" and current_type == ElementType.DRAGON:
power *= 1.2
if attacker.held_item == "dread-plate" and current_type == ElementType.DARK:
power *= 1.2
if attacker.held_item == "earth-plate" and current_type == ElementType.GROUND:
power *= 1.2
if attacker.held_item == "fist-plate" and current_type == ElementType.FIGHTING:
power *= 1.2
if attacker.held_item == "flame-plate" and current_type == ElementType.FIRE:
power *= 1.2
if attacker.held_item == "icicle-plate" and current_type == ElementType.ICE:
power *= 1.2
if attacker.held_item == "insect-plate" and current_type == ElementType.BUG:
power *= 1.2
if attacker.held_item == "iron-plate" and current_type == ElementType.STEEL:
power *= 1.2
if attacker.held_item == "meadow-plate" and current_type == ElementType.GRASS:
power *= 1.2
if attacker.held_item == "mind-plate" and current_type == ElementType.PSYCHIC:
power *= 1.2
if attacker.held_item == "pixie-plate" and current_type == ElementType.FAIRY:
power *= 1.2
if attacker.held_item == "sky-plate" and current_type == ElementType.FLYING:
power *= 1.2
if attacker.held_item == "splash-plate" and current_type == ElementType.WATER:
power *= 1.2
if attacker.held_item == "spooky-plate" and current_type == ElementType.GHOST:
power *= 1.2
if attacker.held_item == "stone-plate" and current_type == ElementType.ROCK:
power *= 1.2
if attacker.held_item == "toxic-plate" and current_type == ElementType.POISON:
power *= 1.2
if attacker.held_item == "zap-plate" and current_type == ElementType.ELECTRIC:
power *= 1.2
if attacker.held_item == "adamant-orb" and current_type in (ElementType.DRAGON, ElementType.STEEL) and attacker._name == "Dialga":
power *= 1.2
if attacker.held_item == "griseous-orb" and current_type in (ElementType.DRAGON, ElementType.GHOST) and attacker._name == "Giratina":
power *= 1.2
if attacker.held_item == "soul-dew" and current_type in (ElementType.DRAGON, ElementType.PSYCHIC) and attacker._name in ("Latios", "Latias"):
power *= 1.2
if attacker.held_item == "lustrous-orb" and current_type in (ElementType.DRAGON, ElementType.WATER) and attacker._name == "Palkia":
power *= 1.2
# Damage class buffing items
if attacker.held_item == "wise-glasses" and self.damage_class == DamageClass.SPECIAL:
power *= 1.1
if attacker.held_item == "muscle-band" and self.damage_class == DamageClass.PHYSICAL:
power *= 1.1
# If there be weather, this move has doubled power and the weather's type.
if self.effect == 204 and battle.weather.get() in ("hail", "sandstorm", "rain", "h-rain", "sun", "h-sun"):
power *= 2
# During hail, rain-dance, or sandstorm, power is halved.
if self.effect == 152 and battle.weather.get() in ("rain", "hail"):
power *= 0.5
# Power doubles if user is burned, paralyzed, or poisoned.
if self.effect == 170 and (attacker.nv.burn() or attacker.nv.poison() or attacker.nv.paralysis()):
power *= 2
# If the target is paralyzed, power is doubled and cures the paralysis.
if self.effect == 172 and defender.nv.paralysis():
power *= 2
defender.nv.reset()
# If the target is poisoned, this move has double power.
if self.effect in (284, 461) and defender.nv.poison():
power *= 2
# If the target is sleeping, this move has double power, and the target wakes up.
if self.effect == 218 and defender.nv.sleep():
power *= 2
defender.nv.reset()
# Has double power against Pokémon that have less than half their max HP remaining.
if self.effect == 222 and defender.hp < defender.starting_hp // 2:
power *= 2
# Power is doubled if the target has already moved this turn.
if self.effect == 231 and defender.has_moved:
power *= 2
# Has double power if the target has a major status ailment.
if self.effect == 311 and defender.nv.current:
power *= 2
# If the user has used defense-curl since entering the field, this move has double power.
if self.effect == 118 and attacker.defense_curl:
power *= 2
# Has double power if the user's last move failed.
if self.effect == 409 and attacker.last_move_failed:
power *= 2
# Has double power if the target is in the first turn of dive.
if self.effect in (258, 262) and defender.dive:
power *= 2
# Has double power if the target is in the first turn of dig.
if self.effect in (127, 148) and defender.dig:
power *= 2
# Has double power if the target is in the first turn of bounce or fly.
if self.effect in (147, 150) and defender.fly:
power *= 2
# Has double power if the user takes damage before attacking this turn.
if self.effect == 186 and attacker.last_move_damage is not None:
power *= 2
# Has double power if the user has no held item.
if self.effect == 318 and not attacker.held_item.has_item():
power *= 2
# Has double power if a friendly Pokémon fainted last turn.
if self.effect == 320 and attacker.owner.retaliate.active():
power *= 2
# Has double power against, and can hit, Pokémon attempting to switch out.
if self.effect == 129 and (isinstance(defender.owner.selected_action, int) or defender.owner.selected_action.effect in (128, 154, 229, 347, 493)):
power *= 2
# Power is doubled if the target has already received damage this turn.
if self.effect == 232 and defender.dmg_this_turn:
power *= 2
# Power is doubled if the target is minimized.
if self.effect == 151 and defender.minimized:
power *= 2
# With Fusion Bolt, power is doubled.
if self.effect == 336 and battle.last_move_effect == 337:
power *= 2
# With Fusion Flare, power is doubled.
if self.effect == 337 and battle.last_move_effect == 336:
power *= 2
# Me first increases the power of the used move by 50%.
if attacker.owner.selected_action is not None and not isinstance(attacker.owner.selected_action, int) and attacker.owner.selected_action.effect == 242:
power *= 1.5
# Has 1.5x power during gravity.
if self.effect == 435 and battle.gravity.active():
power *= 1.5
# If the user attacks before the target, or if the target switched in this turn, its base power doubles.
if self.effect == 436 and (not defender.has_moved or defender.swapped_in):
power *= 2
# If the terrain is psychic and the user is grounded, this move gets 1.5x power.
if self.effect == 440 and battle.terrain.item == "psychic" and attacker.grounded(battle):
power *= 1.5
# Power is doubled if terrain is present.
if self.effect == 441 and battle.terrain.item and attacker.grounded(battle):
power *= 2
# Power is boosted by 50% if used on a Pokémon that is holding an item that can be knocked off.
if self.effect == 189 and defender.held_item.has_item() and defender.held_item.can_remove():
power *= 1.5
# If the target is under the effect of electric terrain, this move has double power.
if self.effect == 443 and battle.terrain.item == "electric" and defender.grounded(battle, attacker=attacker, move=self):
power *= 2
# Deals 1.5x damage if the user is under the effect of misty terrain.
if self.effect == 444 and battle.terrain.item == "misty" and attacker.grounded(battle):
power *= 1.5
# Power is doubled if any of the user's stats were lowered this turn.
if self.effect == 450 and attacker.stat_decreased:
power *= 2
# Power is doubled if the defender has a non volatile status effect.
if self.effect == 465 and defender.nv.current:
power *= 2
# Deals 4/3x damage if supereffective.
if self.effect == 482 and defender.effectiveness(current_type, battle, attacker=attacker, move=self) > 1:
power *= 4/3
# Power is multiplied by (1 + number of fainted party members)x, capping at 101x (100 faints).
if self.effect == 490:
power *= 1 + min(attacker.owner.num_fainted, 100)
# Power is multiplied by (1 + number of times hit)x, capping at 7x (6 hits).
if self.effect == 491:
power *= 1 + min(attacker.num_hits, 6)
# Has a 30% chance to double power
if self.effect == 498 and random.random() <= 0.3:
power *= 2
# Terrains
if battle.terrain.item == "psychic" and attacker.grounded(battle) and current_type == ElementType.PSYCHIC:
power *= 1.3
if battle.terrain.item == "grassy" and attacker.grounded(battle) and current_type == ElementType.GRASS:
power *= 1.3
if battle.terrain.item == "grassy" and defender.grounded(battle, attacker=attacker, move=self) and self.id in (89, 222, 523):
power *= 0.5
if battle.terrain.item == "electric" and attacker.grounded(battle) and current_type == ElementType.ELECTRIC:
power *= 1.3
if battle.terrain.item == "misty" and defender.grounded(battle, attacker=attacker, move=self) and current_type == ElementType.DRAGON:
power *= 0.5
# Power buffing statuses
if attacker.charge.active() and current_type == ElementType.ELECTRIC:
power *= 2
if (attacker.owner.mud_sport.active() or defender.owner.mud_sport.active()) and current_type == ElementType.ELECTRIC:
power //= 3
if (attacker.owner.water_sport.active() or defender.owner.water_sport.active()) and current_type == ElementType.FIRE:
power //= 3
return int(power)
def get_type(self, attacker, defender, battle):
"""
Calculates the element type this move will be.
"""
# Abilities are first because those are intrinsic to the poke and would "apply" to the move first
if attacker.ability() == Ability.REFRIGERATE and self.type == ElementType.NORMAL:
return ElementType.ICE
if attacker.ability() == Ability.PIXILATE and self.type == ElementType.NORMAL:
return ElementType.FAIRY
if attacker.ability() == Ability.AERILATE and self.type == ElementType.NORMAL:
return ElementType.FLYING
if attacker.ability() == Ability.GALVANIZE and self.type == ElementType.NORMAL:
return ElementType.ELECTRIC
if attacker.ability() == Ability.NORMALIZE:
return ElementType.NORMAL
if attacker.ability() == Ability.LIQUID_VOICE and self.is_sound_based():
return ElementType.WATER
if self.type == ElementType.NORMAL and (attacker.ion_deluge or defender.ion_deluge or battle.plasma_fists):
return ElementType.ELECTRIC
if attacker.electrify:
return ElementType.ELECTRIC
if self.effect == 204:
if battle.weather.get() == "hail":
return ElementType.ICE
if battle.weather.get() == "sandstorm":
return ElementType.ROCK
if battle.weather.get() in ("h-sun", "sun"):
return ElementType.FIRE
if battle.weather.get() in ("h-rain", "rain"):
return ElementType.WATER
if self.effect == 136:
# Uses starting IVs as its own IVs should be used even if transformed
type_idx = attacker.starting_hpiv % 2
type_idx += 2 * (attacker.starting_atkiv % 2)
type_idx += 4 * (attacker.starting_defiv % 2)
type_idx += 8 * (attacker.starting_speediv % 2)
type_idx += 16 * (attacker.starting_spatkiv % 2)
type_idx += 32 * (attacker.starting_spdefiv % 2)
type_idx = (type_idx * 15) // 63
type_options = {
0: ElementType.FIGHTING,
1: ElementType.FLYING,
2: ElementType.POISON,
3: ElementType.GROUND,
4: ElementType.ROCK,
5: ElementType.BUG,
6: ElementType.GHOST,
7: ElementType.STEEL,
8: ElementType.FIRE,
9: ElementType.WATER,
10: ElementType.GRASS,
11: ElementType.ELECTRIC,
12: ElementType.PSYCHIC,
13: ElementType.ICE,
14: ElementType.DRAGON,
15: ElementType.DARK,
}
return type_options[type_idx]
if self.effect == 401:
if len(attacker.type_ids) == 0:
return ElementType.TYPELESS
return attacker.type_ids[0]
if self.effect == 269:
if attacker.held_item in ("draco-plate", "dragon-memory"):
return ElementType.DRAGON
if attacker.held_item in ("dread-plate", "dark-memory"):
return ElementType.DARK
if attacker.held_item in ("earth-plate", "ground-memory"):
return ElementType.GROUND
if attacker.held_item in ("fist-plate", "fighting-memory"):
return ElementType.FIGHTING
if attacker.held_item in ("flame-plate", "burn-drive", "fire-memory"):
return ElementType.FIRE
if attacker.held_item in ("icicle-plate", "chill-drive", "ice-memory"):
return ElementType.ICE
if attacker.held_item in ("insect-plate", "bug-memory"):
return ElementType.BUG
if attacker.held_item in ("iron-plate", "steel-memory"):
return ElementType.STEEL
if attacker.held_item in ("meadow-plate", "grass-memory"):
return ElementType.GRASS
if attacker.held_item in ("mind-plate", "psychic-memory"):
return ElementType.PSYCHIC
if attacker.held_item in ("pixie-plate", "fairy-memory"):
return ElementType.FAIRY
if attacker.held_item in ("sky-plate", "flying-memory"):
return ElementType.FLYING
if attacker.held_item in ("splash-plate", "douse-drive", "water-memory"):
return ElementType.WATER
if attacker.held_item in ("spooky-plate", "ghost-memory"):
return ElementType.GHOST
if attacker.held_item in ("stone-plate", "rock-memory"):
return ElementType.ROCK
if attacker.held_item in ("toxic-plate", "poison-memory"):
return ElementType.POISON
if attacker.held_item in ("zap-plate", "shock-drive", "electric-memory"):
return ElementType.ELECTRIC
if self.effect == 223:
hi = attacker.held_item.get()
if hi in ("figy-berry", "tanga-berry", "cornn-berry", "enigma-berry"):
return ElementType.BUG
if hi in ("iapapa-berry", "colbur-berry", "spelon-berry", "rowap-berry", "maranga-berry"):
return ElementType.DARK
if hi in ("aguav-berry", "haban-berry", "nomel-berry", "jaboca-berry"):
return ElementType.DRAGON
if hi in ("pecha-berry", "wacan-berry", "wepear-berry", "belue-berry"):
return ElementType.ELECTRIC
if hi in ("roseli-berry", "kee-berry"):
return ElementType.FAIRY
if hi in ("leppa-berry", "chople-berry", "kelpsy-berry", "salac-berry"):
return ElementType.FIGHTING
if hi in ("cheri-berry", "occa-berry", "bluk-berry", "watmel-berry"):
return ElementType.FIRE
if hi in ("lum-berry", "coba-berry", "grepa-berry", "lansat-berry"):
return ElementType.FLYING
if hi in ("mago-berry", "kasib-berry", "rabuta-berry", "custap-berry"):
return ElementType.GHOST
if hi in ("rawst-berry", "rindo-berry", "pinap-berry", "liechi-berry"):
return ElementType.GRASS
if hi in ("persim-berry", "shuca-berry", "hondew-berry", "apicot-berry"):
return ElementType.GROUND
if hi in ("aspear-berry", "yache-berry", "pomeg-berry", "ganlon-berry"):
return ElementType.ICE
if hi in ("oran-berry", "kebia-berry", "qualot-berry", "petaya-berry"):
return ElementType.POISON
if hi in ("sitrus-berry", "payapa-berry", "tamato-berry", "starf-berry"):
return ElementType.PSYCHIC
if hi in ("wiki-berry", "charti-berry", "magost-berry", "micle-berry"):
return ElementType.ROCK
if hi in ("razz-berry", "babiri-berry", "pamtre-berry"):
return ElementType.STEEL
if hi in ("chesto-berry", "passho-berry", "nanab-berry", "durin-berry"):
return ElementType.WATER
if hi == "chilan-berry":
return ElementType.NORMAL
if self.effect == 433 and attacker._name == "Morpeko-hangry":
return ElementType.DARK
if self.effect == 441 and attacker.grounded(battle):
if battle.terrain.item == "electric":
return ElementType.ELECTRIC
if battle.terrain.item == "grass":
return ElementType.GRASS
if battle.terrain.item == "misty":
return ElementType.FAIRY
if battle.terrain.item == "psychic":
return ElementType.PSYCHIC
if self.id == 873:
if attacker._name == "Tauros-paldea":
return ElementType.FIGHTING
if attacker._name == "Tauros-aqua-paldea":
return ElementType.WATER
if attacker._name == "Tauros-blaze-paldea":
return ElementType.FIRE
return self.type
def get_priority(self, attacker, defender, battle):
"""
Calculates the priority value for this move.
Returns an int priority from -7 to 5.
"""
priority = self.priority
current_type = self.get_type(attacker, defender, battle)
if self.effect == 437 and attacker.grounded(battle) and battle.terrain.item == "grassy":
priority += 1
if attacker.ability() == Ability.GALE_WINGS and current_type == ElementType.FLYING and attacker.hp == attacker.starting_hp:
priority += 1
if attacker.ability() == Ability.PRANKSTER and self.damage_class == DamageClass.STATUS:
priority += 1
if attacker.ability() == Ability.TRIAGE and self.is_affected_by_heal_block():
priority += 3
return priority
def get_effect_chance(self, attacker, defender, battle):
"""
Gets the chance for secondary effects to occur.
Returns an int from 0-100.
"""
if self.effect_chance is None:
return 100
if defender.ability(attacker=attacker, move=self) == Ability.SHIELD_DUST:
return 0
if defender.held_item == "covert-cloak":
return 0
if attacker.ability() == Ability.SHEER_FORCE:
return 0
if attacker.ability() == Ability.SERENE_GRACE:
return min(100, self.effect_chance * 2)
return self.effect_chance
def check_executable(self, attacker, defender, battle):
"""
Returns True if the move can be executed, False otherwise
Checks different requirements for moves that can make them fail.
"""
if attacker.taunt.active() and self.damage_class == DamageClass.STATUS:
return False
if attacker.silenced.active() and self.is_sound_based():
return False
if self.is_affected_by_heal_block() and attacker.heal_block.active():
return False
if self.is_powder_or_spore() and (ElementType.GRASS in defender.type_ids or defender.ability(attacker=attacker, move=self) == Ability.OVERCOAT or defender.held_item == "safety-goggles"):
return False
if battle.weather.get() == "h-sun" and self.get_type(attacker, defender, battle) == ElementType.WATER and self.damage_class != DamageClass.STATUS:
return False
if battle.weather.get() == "h-rain" and self.get_type(attacker, defender, battle) == ElementType.FIRE and self.damage_class != DamageClass.STATUS:
return False
if attacker.disable.active() and attacker.disable.item is self:
return False
if attacker is not defender and defender.imprison and self.id in [x.id for x in defender.moves]:
return False
#Since we only have single battles, these moves always fail
if self.effect in (173, 301, 308, 316, 363, 445, 494):
return False
if self.effect in (93, 98) and not attacker.nv.sleep():
return False
if self.effect in (9, 108) and not defender.nv.sleep():
return False
if self.effect == 364 and not defender.nv.poison():
return False
if self.effect in (162, 163) and attacker.stockpile == 0:
return False
if self.effect == 85 and (ElementType.GRASS in defender.type_ids or defender.leech_seed):
return False
if self.effect == 193 and attacker.imprison:
return False
if self.effect == 166 and defender.torment:
return False
if self.effect == 91 and (defender.encore.active() or defender.last_move is None or defender.last_move.pp == 0):
return False
if self.effect == 87 and (defender.disable.active() or defender.last_move is None or defender.last_move.pp == 0):
return False
if self.effect in (96, 101) and (defender.last_move is None or defender.last_move.pp == 0):
return False
if self.effect == 176 and defender.taunt.active():
return False
if self.effect == 29 and not defender.owner.valid_swaps(attacker, battle, check_trap=False):
return False
if self.effect in (128, 154, 493) and not attacker.owner.valid_swaps(defender, battle, check_trap=False):
return False
if self.effect == 161 and attacker.stockpile >= 3:
return False
if self.effect in (90, 145, 228, 408) and attacker.last_move_damage is None:
return False
if self.effect == 145 and attacker.last_move_damage[1] != DamageClass.SPECIAL:
return False
if self.effect in (90, 408) and attacker.last_move_damage[1] != DamageClass.PHYSICAL:
return False
if self.effect in (10, 243) and (defender.last_move is None or not defender.last_move.selectable_by_mirror_move()):
return False
if self.effect == 83 and (defender.last_move is None or not defender.last_move.selectable_by_mimic()):
return False
if self.effect == 180 and attacker.owner.wish.active():
return False
if self.effect == 388 and defender.attack_stage == -6:
return False
if self.effect in (143, 485, 493) and attacker.hp <= attacker.starting_hp // 2:
return False
if self.effect == 414 and attacker.hp < attacker.starting_hp // 3:
return False
if self.effect == 80 and attacker.hp <= attacker.starting_hp // 4:
return False
if self.effect == 48 and attacker.focus_energy:
return False
if self.effect == 190 and attacker.hp >= defender.hp:
return False
if self.effect == 194 and not (attacker.nv.burn() or attacker.nv.paralysis() or attacker.nv.poison()):
return False
if self.effect == 235 and (not attacker.nv.current or defender.nv.current):
return False
if self.effect in (121, 266) and ("-x" in (attacker.gender, defender.gender) or attacker.gender == defender.gender or defender.ability(attacker=attacker, move=self) == Ability.OBLIVIOUS):
return False
if self.effect in (367, 392) and attacker.ability() not in (Ability.PLUS, Ability.MINUS):
return False
if self.effect == 39 and attacker.level < defender.level:
return False
if self.effect in (46, 86, 156, 264, 286) and battle.gravity.active():
return False
if self.effect == 113 and defender.owner.spikes == 3:
return False
if self.effect == 250 and defender.owner.toxic_spikes == 2:
return False
if self.effect in (159, 377, 383) and attacker.active_turns != 0:
return False
if self.effect == 98 and not any(m.selectable_by_sleep_talk() for m in attacker.moves):
return False
if self.effect == 407 and not battle.weather.get() == "hail":
return False
if self.effect == 407 and attacker.owner.aurora_veil.active():
return False
if self.effect == 47 and attacker.owner.mist.active():
return False
if self.effect in (80, 493) and attacker.substitute:
return False
if self.effect == 398 and ElementType.FIRE not in attacker.type_ids:
return False
if self.effect == 481 and ElementType.ELECTRIC not in attacker.type_ids:
return False
if self.effect == 376 and ElementType.GRASS in defender.type_ids:
return False
if self.effect == 343 and ElementType.GHOST in defender.type_ids:
return False
if self.effect == 107 and defender.trapping:
return False
if self.effect == 182 and attacker.ingrain:
return False
if self.effect == 94 and self.get_conversion_2(attacker, defender, battle) is None:
return False
if self.effect == 121 and defender.infatuated is attacker:
return False
if self.effect == 248 and defender.ability(attacker=attacker, move=self) == Ability.INSOMNIA:
return False
if self.effect in (242, 249) and (defender.has_moved or isinstance(defender.owner.selected_action, int) or defender.owner.selected_action.damage_class == DamageClass.STATUS):
return False
if self.effect == 252 and attacker.aqua_ring:
return False
if self.effect == 253 and attacker.magnet_rise.active():
return False
if self.effect == 221 and attacker.owner.healing_wish:
return False
if self.effect == 271 and attacker.owner.lunar_dance:
return False
if self.effect in (240, 248, 299, 300) and not defender.ability_changeable():
return False
if self.effect == 300 and not attacker.ability_giveable():
return False
if self.effect == 241 and attacker.lucky_chant.active():
return False
if self.effect == 125 and attacker.owner.safeguard.active():
return False
if self.effect == 293 and not set(attacker.type_ids) & set(defender.type_ids):
return False
if self.effect == 295 and defender.ability(attacker=attacker, move=self) == Ability.MULTITYPE:
return False
if self.effect == 319 and not defender.type_ids:
return False
if self.effect == 171 and attacker.last_move_damage is not None:
return False
if self.effect == 179 and not (attacker.ability_changeable() and defender.ability_giveable()):
return False
if self.effect == 181 and attacker.get_assist_move() is None:
return False
if self.effect in (112, 117, 184, 195, 196, 279, 307, 345, 350, 354, 356, 362, 378, 384, 454, 488, 499) and defender.has_moved:
return False
if self.effect == 192 and not (attacker.ability_changeable() and attacker.ability_giveable() and defender.ability_changeable() and defender.ability_giveable()):
return False
if self.effect == 226 and attacker.owner.tailwind.active():
return False
if self.effect in (90, 92, 145) and attacker.substitute:
return False
if self.effect in (85, 92, 169, 178, 188, 206, 388) and defender.substitute:
return False
if self.effect == 234 and (not attacker.held_item.power or attacker.ability() == Ability.STICKY_HOLD):
return False
if self.effect == 178 and (Ability.STICKY_HOLD in (attacker.ability(), defender.ability(attacker=attacker, move=self)) or not attacker.held_item.can_remove() or not defender.held_item.can_remove()):
return False
if self.effect == 202 and attacker.owner.mud_sport.active():
return False
if self.effect == 211 and attacker.owner.water_sport.active():
return False
if self.effect == 149 and defender.owner.future_sight.active():
return False
if self.effect == 188 and (defender.nv.current or defender.ability(attacker=attacker, move=self) in (Ability.INSOMNIA, Ability.VITAL_SPIRIT, Ability.SWEET_VEIL) or defender.yawn.active()):
return False
if self.effect == 188 and battle.terrain.item == "electric" and attacker.grounded(battle):
return False
if self.effect in (340, 351) and not any(ElementType.GRASS in p.type_ids and p.grounded(battle) and not p.dive and not p.dig and not p.fly and not p.shadow_force for p in (attacker, defender)):
return False
if self.effect == 341 and defender.owner.sticky_web:
return False
if self.effect in (112, 117, 356, 362, 384, 454, 488, 499) and random.randint(1, attacker.protection_chance) != 1:
return False
if self.effect == 403 and (defender.last_move is None or defender.last_move.pp == 0 or not defender.last_move.selectable_by_instruct() or defender.locked_move is not None):
return False
if self.effect == 378 and (ElementType.GRASS in defender.type_ids or defender.ability(attacker=attacker, move=self) == Ability.OVERCOAT or defender.held_item == "safety-goggles"):
return False
if self.effect == 233 and defender.embargo.active():
return False
if self.effect == 324 and (not attacker.held_item.has_item() or defender.held_item.has_item() or not attacker.held_item.can_remove()):
return False
if self.effect == 185 and (attacker.held_item.has_item() or attacker.held_item.last_used is None):
return False
if self.effect == 430 and (not defender.held_item.has_item() or not defender.held_item.can_remove() or defender.corrosive_gas):
return False
if self.effect == 114 and defender.foresight:
return False
if self.effect == 217 and defender.miracle_eye:
return False
if self.effect == 38 and (attacker.nv.sleep() or attacker.hp == attacker.starting_hp or attacker._name == "Minior"):
return False
if self.effect == 427 and attacker.no_retreat:
return False
if self.effect == 99 and attacker.destiny_bond_cooldown.active():
return False
if self.effect in (116, 137, 138, 165) and battle.weather.get() in ("h-rain", "h-sun", "h-wind"):
return False
if self.effect in (8, 420, 444) and Ability.DAMP in (attacker.ability(), defender.ability(attacker=attacker, move=self)):
return False
if self.effect in (223, 453) and not attacker.held_item.is_berry():
return False
if self.effect == 369 and battle.terrain.item == "electric":
return False
if self.effect == 352 and battle.terrain.item == "grassy":
return False
if self.effect == 353 and battle.terrain.item == "misty":
return False
if self.effect == 395 and battle.terrain.item == "psychic":
return False
if self.effect == 66 and attacker.owner.reflect.active():
return False
if self.effect == 36 and attacker.owner.light_screen.active():
return False
if self.effect == 110 and ElementType.GHOST in attacker.type_ids and defender.cursed:
return False
if self.effect == 58 and (defender.substitute or defender.illusion_name is not None):
return False
if self.effect == 446 and defender.held_item.get() is None:
return False
if self.effect == 448 and not battle.terrain.item:
return False
if self.effect == 452 and defender.octolock:
return False
if self.effect == 280 and any([defender.defense_split, defender.spdef_split, attacker.defense_split, attacker.spdef_split]):
return False
if self.effect == 281 and any([defender.attack_split, defender.spatk_split, attacker.attack_split, attacker.spatk_split]):
return False
if self.effect == 456 and (defender.type_ids == [ElementType.PSYCHIC] or defender.ability(attacker=attacker, move=self) == Ability.RKS_SYSTEM):
return False
if self.effect == 83 and self not in attacker.moves:
return False
if self.effect == 501 and (defender.has_moved or isinstance(defender.owner.selected_action, int) or defender.owner.selected_action.get_priority(defender, attacker, battle) <= 0):
return False
if defender.ability(attacker=attacker, move=self) in (Ability.QUEENLY_MAJESTY, Ability.DAZZLING, Ability.ARMOR_TAIL) and self.get_priority(attacker, defender, battle) > 0:
return False
return True
def check_semi_invulnerable(self, attacker, defender, battle):
"""
Returns True if this move hits, False otherwise.
Checks if a pokemon is in the semi-invulnerable turn of dive or dig.
"""
if not self.targets_opponent():
return True
if Ability.NO_GUARD in (attacker.ability(), defender.ability(attacker=attacker, move=self)):
return True
if defender.mind_reader.active() and defender.mind_reader.item is attacker:
return True
if defender.dive and self.effect not in (258, 262):
return False
if defender.dig and self.effect not in (127, 148):
return False
if defender.fly and self.effect not in (147, 150, 153, 208, 288, 334, 373):
return False
if defender.shadow_force:
return False
return True
def check_protect(self, attacker, defender, battle):
"""
Returns True if this move hits, False otherwise.
Checks if this pokemon is protected by a move like protect or wide guard.
Also returns a formatted message.
"""
msg = ""
# Moves that don't target the opponent can't be protected by the target.
if not self.targets_opponent():
return True, msg
# Moves which bypass all protection.
if self.effect in (149, 224, 273, 360, 438, 489):
return True, msg
if attacker.ability() == Ability.UNSEEN_FIST and self.makes_contact(attacker):
return True, msg
if defender.crafty_shield and self.damage_class == DamageClass.STATUS:
return False, msg
# Moves which bypass all protection except for crafty shield.
if self.effect in (29, 107, 179, 412):
return True, msg
if defender.protect:
return False, msg
if defender.spiky_shield:
if self.makes_contact(attacker) and attacker.held_item != "protective-pads":
msg += attacker.damage(attacker.starting_hp // 8, battle, source=f"{defender.name}'s spiky shield")
return False, msg
if defender.baneful_bunker:
if self.makes_contact(attacker) and attacker.held_item != "protective-pads":
msg += attacker.nv.apply_status("poison", battle, attacker=defender, source=f"{defender.name}'s baneful bunker")
return False, msg
if defender.wide_guard and self.targets_multiple():
return False, msg
if self.get_priority(attacker, defender, battle) > 0 and battle.terrain.item == "psychic" and defender.grounded(battle, attacker=attacker, move=self):
return False, msg
if defender.mat_block and self.damage_class != DamageClass.STATUS:
return False, msg
if defender.king_shield and self.damage_class != DamageClass.STATUS:
if self.makes_contact(attacker) and attacker.held_item != "protective-pads":
msg += attacker.append_attack(-1, attacker=defender, move=self, source=f"{defender.name}'s silk trap")
return False, msg
if defender.obstruct and self.damage_class != DamageClass.STATUS:
if self.makes_contact(attacker) and attacker.held_item != "protective-pads":
msg += attacker.append_defense(-2, attacker=defender, move=self, source=f"{defender.name}'s silk trap")
return False, msg
if defender.silk_trap and self.damage_class != DamageClass.STATUS:
if self.makes_contact(attacker) and attacker.held_item != "protective-pads":
msg += attacker.append_speed(-1, attacker=defender, move=self, source=f"{defender.name}'s silk trap")
return False, msg
if defender.burning_bulwark and self.damage_class != DamageClass.STATUS:
if self.makes_contact(attacker) and attacker.held_item != "protective-pads":
msg += attacker.nv.apply_status("burn", battle, attacker=defender, source=f"{defender.name}'s burning bulwark")
return False, msg
if defender.quick_guard and self.get_priority(attacker, defender, battle) > 0:
return False, msg
return True, msg
def check_hit(self, attacker, defender, battle):
"""
Returns True if this move hits, False otherwise.
Calculates the chance to hit & does an RNG check using that chance.
"""
micle_used = attacker.micle_berry_ate
attacker.micle_berry_ate = False
#Moves that have a None accuracy always hit.
if self.accuracy is None:
return True
# During hail, this bypasses accuracy checks
if self.effect == 261 and battle.weather.get() == "hail":
return True
# During rain, this bypasses accuracy checks
if self.effect in (153, 334, 357, 365, 396) and battle.weather.get() in ("rain", "h-rain"):
return True
# If used by a poison type, this bypasses accuracy checks
if self.effect == 34 and ElementType.POISON in attacker.type_ids:
return True
# If used against a minimized poke, this bypasses accuracy checks
if self.effect == 338 and defender.minimized:
return True
# These DO allow OHKO moves to bypass accuracy checks
if self.targets_opponent():
if defender.mind_reader.active() and defender.mind_reader.item is attacker:
return True
if attacker.ability() == Ability.NO_GUARD:
return True
if defender.ability(attacker=attacker, move=self) == Ability.NO_GUARD:
return True
# OHKO moves
if self.effect == 39:
accuracy = 30 + (attacker.level - defender.level)
return random.uniform(0, 100) <= accuracy
# This does NOT allow OHKO moves to bypass accuracy checks
if attacker.telekinesis.active():
return True
accuracy = self.accuracy
# When used during harsh sunlight, this has an accuracy of 50%
if self.effect in (153, 334) and battle.weather.get() in ("sun", "h-sun"):
accuracy = 50
if self.targets_opponent():
if defender.ability(attacker=attacker, move=self) == Ability.WONDER_SKIN and self.damage_class == DamageClass.STATUS:
accuracy = 50
if defender.ability(attacker=attacker, move=self) == Ability.UNAWARE:
stage = 0
else:
stage = attacker.get_accuracy(battle)
if not (
self.effect == 304
or defender.foresight
or defender.miracle_eye
or attacker.ability() in [Ability.UNAWARE, Ability.KEEN_EYE, Ability.MINDS_EYE]
):
stage -= defender.get_evasion(battle)
stage = min(6, max(-6, stage))
stage_multiplier = {
-6: 3/9,
-5: 3/8,
-4: 3/7,
-3: 3/6,
-2: 3/5,
-1: 3/4,
0: 1,
1: 4/3,
2: 5/3,
3: 2,
4: 7/3,
5: 8/3,
6: 3,
}
accuracy *= stage_multiplier[stage]
if self.targets_opponent():
if defender.ability(attacker=attacker, move=self) == Ability.TANGLED_FEET and defender.confusion.active():
accuracy *= .5
if defender.ability(attacker=attacker, move=self) == Ability.SAND_VEIL and battle.weather.get() == "sandstorm":
accuracy *= .8
if defender.ability(attacker=attacker, move=self) == Ability.SNOW_CLOAK and battle.weather.get() == "hail":
accuracy *= .8
if attacker.ability() == Ability.COMPOUND_EYES:
accuracy *= 1.3
if attacker.ability() == Ability.HUSTLE and self.damage_class == DamageClass.PHYSICAL:
accuracy *= .8
if attacker.ability() == Ability.VICTORY_STAR:
accuracy *= 1.1
if battle.gravity.active():
accuracy *= (5 / 3)
if attacker.held_item == "wide-lens":
accuracy *= 1.1
if attacker.held_item == "zoom-lens" and defender.has_moved:
accuracy *= 1.2
if defender.held_item == "bright-powder":
accuracy *= .9
if micle_used:
accuracy *= 1.2
return random.uniform(0, 100) <= accuracy
def check_effective(self, attacker, defender, battle):
"""
Returns True if a move has an effect on a poke.
Moves can have no effect based on things like type effectiveness and groundedness.
"""
# What if I :flushed: used Hold Hands :flushed: in a double battle :flushed: with you? :flushed:
# (and you weren't protected by Crafty Shield or in the semi-invulnerable turn of a move like Fly or Dig)
if self.effect in (86, 174, 368, 370, 371, 389):
return False
if not self.targets_opponent():
return True
if self.effect == 266 and defender.ability(attacker=attacker, move=self) == Ability.OBLIVIOUS:
return False
if self.effect == 39 and defender.ability(attacker=attacker, move=self) == Ability.STURDY:
return False
if self.effect == 39 and self.id == 329 and ElementType.ICE in defender.type_ids:
return False
if self.effect == 400 and not defender.nv.current:
return False
if self.is_sound_based() and defender.ability(attacker=attacker, move=self) == Ability.SOUNDPROOF:
return False
if self.is_ball_or_bomb() and defender.ability(attacker=attacker, move=self) == Ability.BULLETPROOF:
return False
if attacker.ability() == Ability.PRANKSTER and ElementType.DARK in defender.type_ids:
if self.damage_class == DamageClass.STATUS:
return False
# If the attacker used a status move that called this move, even if this move is not a status move then it should still be considered affected by prankster.
if not isinstance(attacker.owner.selected_action, int) and attacker.owner.selected_action.damage_class == DamageClass.STATUS:
return False
if defender.ability(attacker=attacker, move=self) == Ability.GOOD_AS_GOLD and self.damage_class == DamageClass.STATUS:
return False
# Status moves do not care about type effectiveness - except for thunder wave FOR SOME REASON...
if self.damage_class == DamageClass.STATUS and self.id != 86:
return True
current_type = self.get_type(attacker, defender, battle)
if current_type == ElementType.TYPELESS:
return True
effectiveness = defender.effectiveness(current_type, battle, attacker=attacker, move=self)
if self.effect == 338:
effectiveness *= defender.effectiveness(ElementType.FLYING, battle, attacker=attacker, move=self)
if effectiveness == 0:
return False
if current_type == ElementType.GROUND and not defender.grounded(battle, attacker=attacker, move=self) and self.effect != 373 and not battle.inverse_battle:
return False
if self.effect != 459:
if current_type == ElementType.ELECTRIC and defender.ability(attacker=attacker, move=self) == Ability.VOLT_ABSORB and defender.hp == defender.starting_hp:
return False
if current_type == ElementType.WATER and defender.ability(attacker=attacker, move=self) in (Ability.WATER_ABSORB, Ability.DRY_SKIN) and defender.hp == defender.starting_hp:
return False
if current_type == ElementType.FIRE and defender.ability(attacker=attacker, move=self) == Ability.FLASH_FIRE and defender.flash_fire:
return False
if effectiveness <= 1 and defender.ability(attacker=attacker, move=self) == Ability.WONDER_GUARD:
return False
return True
def is_sound_based(self):
"""Whether or not this move is sound based."""
return self.id in [
45, 46, 47, 48, 103, 173, 195, 215, 253, 304, 319, 320, 336, 405, 448, 496, 497, 547,
555, 568, 574, 575, 586, 590, 664, 691, 728, 744, 753, 826, 871, 1005, 1006
]
def is_punching(self):
"""Whether or not this move is a punching move."""
return self.id in [
4, 5, 7, 8, 9, 146, 183, 223, 264, 309, 325, 327, 359, 409, 418, 612, 665, 721, 729,
764, 765, 834, 857, 889
]
def is_biting(self):
"""Whether or not this move is a biting move."""
return self.id in [44, 158, 242, 305, 422, 423, 424, 706, 733, 742]
def is_ball_or_bomb(self):
"""Whether or not this move is a ball or bomb move."""
return self.id in [
121, 140, 188, 190, 192, 247, 296, 301, 311, 331, 350, 360, 396, 402, 411, 412, 426,
439, 443, 486, 491, 545, 676, 690, 748, 1017
]
def is_aura_or_pulse(self):
"""Whether or not this move is an aura or pulse move."""
return self.id in [352, 396, 399, 406, 505, 618, 805]
def is_powder_or_spore(self):
"""Whether or not this move is a powder or spore move."""
return self.id in [77, 78, 79, 147, 178, 476, 600, 737]
def is_dance(self):
"""Whether or not this move is a dance move."""
return self.id in [14, 80, 297, 298, 349, 461, 483, 552, 686, 744, 846, 872]
def is_slicing(self):
"""Whether or not this move is a slicing move."""
return self.id in [
15, 75, 163, 210, 314, 332, 348, 400, 403, 404, 427, 440, 533, 534, 669, 749, 830, 845,
860, 869, 891, 895, 1013, 1014
]
def is_wind(self):
"""Whether or not this move is a wind move."""
return self.id in [16, 18, 59, 196, 201, 239, 257, 314, 366, 542, 572, 584, 829, 842, 844, 849]
def is_affected_by_magic_coat(self):
"""Whether or not this move can be reflected by magic coat and magic bounce."""
return self.id in [
18, 28, 39, 43, 45, 46, 47, 48, 50, 73, 77, 78, 79, 81, 86, 92, 95, 103, 108, 109, 134,
137, 139, 142, 147, 148, 169, 178, 180, 184, 186, 191, 193, 204, 207, 212, 213, 227, 230,
259, 260, 261, 269, 281, 297, 313, 316, 319, 320, 321, 335, 357, 373, 377, 380, 388, 390,
432, 445, 446, 464, 477, 487, 493, 494, 505, 564, 567, 568, 571, 575, 576, 589, 590, 598,
599, 600, 608, 666, 668, 671, 672, 685, 715, 736, 737, 810
]
def is_affected_by_heal_block(self):
"""Whether or not this move cannot be selected during heal block."""
return self.id in [
71, 72, 105, 135, 138, 141, 156, 202, 208, 234, 235, 236, 256, 273, 303, 355, 361, 409,
456, 461, 505, 532, 570, 577, 613, 659, 666, 668, 685
]
def is_affected_by_substitute(self):
"""Whether or not this move is able to bypass a substitute."""
return self.id not in [
18, 45, 46, 47, 48, 50, 102, 103, 114, 166, 173, 174, 176, 180, 193, 195, 213, 215, 227,
244, 253, 259, 269, 270, 272, 285, 286, 304, 312, 316, 319, 320, 357, 367, 382, 384, 385,
391, 405, 448, 495, 496, 497, 513, 516, 547, 555, 568, 574, 575, 586, 587, 589, 590, 593,
597, 600, 602, 607, 621, 664, 674, 683, 689, 691, 712, 728, 753, 826, 871, 1005, 1006
]
def targets_opponent(self):
"""Whether or not this move targets the opponent."""
#Moves which don't follow normal targeting protocals, ignore them unless they are damaging.
if self.target == MoveTarget.SPECIFIC_MOVE and self.damage_class == DamageClass.STATUS:
return False
#Moves which do not target the opponent pokemon.
return self.target not in (
MoveTarget.SELECTED_POKEMON_ME_FIRST,
MoveTarget.ALLY,
MoveTarget.USERS_FIELD,
MoveTarget.USER_OR_ALLY,
MoveTarget.OPPONENTS_FIELD,
MoveTarget.USER,
MoveTarget.ENTIRE_FIELD,
MoveTarget.USER_AND_ALLIES,
MoveTarget.ALL_ALLIES,
)
def targets_multiple(self):
"""Whether or not this move targets multiple pokemon."""
return self.target in (
MoveTarget.ALL_OTHER_POKEMON,
MoveTarget.ALL_OPPONENTS,
MoveTarget.USER_AND_ALLIES,
MoveTarget.ALL_POKEMON,
MoveTarget.ALL_ALLIES,
)
def makes_contact(self, attacker):
"""Whether or not this move makes contact."""
return self.id in [
1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 15, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 44, 64, 65, 66, 67, 68, 69, 70, 80, 91, 98, 99,
117, 122, 127, 128, 130, 132, 136, 141, 146, 152, 154, 158, 162, 163, 165, 167, 168,
172, 175, 179, 183, 185, 200, 205, 206, 209, 210, 211, 216, 218, 223, 224, 228, 229,
231, 232, 233, 238, 242, 245, 249, 252, 263, 264, 265, 276, 279, 280, 282, 283, 291,
292, 299, 301, 302, 305, 306, 309, 310, 325, 327, 332, 337, 340, 342, 343, 344, 348,
358, 359, 360, 365, 369, 370, 371, 372, 376, 378, 386, 387, 389, 394, 395, 398, 400,
401, 404, 407, 409, 413, 416, 418, 419, 421, 422, 423, 424, 425, 428, 431, 438, 440,
442, 447, 450, 452, 453, 457, 458, 462, 467, 480, 484, 488, 490, 492, 498, 507, 509,
512, 514, 525, 528, 529, 530, 531, 532, 533, 534, 535, 537, 541, 543, 544, 550, 557,
560, 565, 566, 577, 583, 609, 610, 611, 612, 620, 658, 660, 663, 665, 667, 669, 675,
677, 679, 680, 681, 684, 688, 692, 693, 696, 699, 701, 706, 707, 709, 710, 712, 713,
716, 718, 721, 724, 729, 730, 733, 741, 742, 745, 747, 749, 750, 752, 756, 760, 764,
765, 766, 779, 799, 803, 806, 812, 813, 821, 830, 832, 834, 840, 845, 848, 853, 857,
859, 860, 861, 862, 866, 869, 872, 873, 878, 879, 884, 885, 887, 889, 891, 892, 894,
1003, 1010, 1012, 1013
] and not attacker.ability() == Ability.LONG_REACH
def selectable_by_mirror_move(self):
"""Whether or not this move can be selected by mirror move."""
return self.targets_opponent()
# Previously, this was a hardcoded list of values.
# I don't think there is any factor besides whether or not the move can target opponents (for single battles).
"""
return self.id not in [
10, 14, 54, 68, 74, 96, 97, 100, 102, 104, 105, 106, 107, 110, 111, 112, 113, 114, 115,
116, 117, 118, 119, 133, 135, 144, 150, 151, 156, 159, 160, 164, 165, 166, 174, 176,
182, 187, 191, 194, 195, 197, 201, 203, 208, 214, 215, 219, 226, 234, 235, 236, 240,
241, 243, 244, 248, 254, 255, 256, 258, 264, 266, 267, 268, 270, 272, 273, 274, 275,
277, 278, 286, 287, 288, 289, 293, 294, 300, 303, 312, 322, 334, 336, 339, 346, 347,
349, 353, 355, 356, 361, 366, 367, 379, 381, 382, 383, 390, 392, 393, 397, 417, 446,
455, 456, 461, 468, 469, 470, 471, 475, 476, 483, 489, 495, 501, 502, 504, 505, 508,
513, 515, 526, 538, 561, 562, 563, 564, 569, 578, 579, 580, 581, 596, 597, 601, 602,
603, 604, 606, 607, 1000
]
"""
def selectable_by_sleep_talk(self):
"""Whether or not this move can be selected by sleep talk."""
return self.id not in [
13, 19, 76, 91, 102, 117, 118, 119, 130, 143, 166, 253, 264, 274, 291, 340, 382, 383,
467, 507, 553, 554, 562, 566, 601, 669, 690, 704, 731
]
def selectable_by_assist(self):
"""Whether or not this move can be selected by assist."""
return self.id not in [
18, 19, 46, 68, 91, 102, 118, 119, 144, 165, 166, 168, 182, 194, 197, 203, 214, 243,
264, 266, 267, 270, 271, 289, 291, 340, 343, 364, 382, 383, 415, 448, 467, 476, 507,
509, 516, 525, 561, 562, 566, 588, 596, 606, 607, 661, 671, 690, 704
]
def selectable_by_mimic(self):
"""Whether or not this move can be selected by mimic."""
return self.id not in [102, 118, 165, 166, 448, 896]
def selectable_by_instruct(self):
"""Whether or not this move can be selected by instruct."""
return self.id not in [
13, 19, 63, 76, 91, 102, 117, 118, 119, 130, 143, 144, 165, 166, 214, 264, 267, 274,
289, 291, 307, 308, 338, 340, 382, 383, 408, 416, 439, 459, 467, 507, 553, 554, 566,
588, 601, 669, 689, 690, 704, 711, 761, 762, 896
]
def selectable_by_snatch(self):
"""Whether or not this move can be selected by snatch."""
return self.id in [
14, 54, 74, 96, 97, 104, 105, 106, 107, 110, 111, 112, 113, 115, 116, 133, 135, 151,
156, 159, 160, 164, 187, 208, 215, 219, 234, 235, 236, 254, 256, 268, 273, 275, 278,
286, 287, 293, 294, 303, 312, 322, 334, 336, 339, 347, 349, 355, 361, 366, 379, 381,
392, 393, 397, 417, 455, 456, 461, 468, 469, 475, 483, 489, 501, 504, 508, 526, 538,
561, 602, 659, 673, 674, 694, 0xCFCF
]
@staticmethod
def get_conversion_2(attacker, defender, battle):
"""
Gets a random new type for attacker that is resistant to defender's last move type.
Returns a random possible type id, or None if there is no valid type.
"""
if defender.last_move is None:
return None
movetype = defender.last_move.get_type(attacker, defender, battle)
newtypes = set()
for e in ElementType:
if e == ElementType.TYPELESS:
continue
if battle.inverse_battle:
if battle.type_effectiveness[(movetype, e)] > 100:
newtypes.add(e)
else:
if battle.type_effectiveness[(movetype, e)] < 100:
newtypes.add(e)
newtypes -= set(attacker.type_ids)
newtypes = list(newtypes)
if not newtypes:
return None
return random.choice(newtypes)
def copy(self):
"""Generate a copy of this move."""
return Move(
id=self.id,
identifier=self.name,
power=self.power,
pp=self.pp,
accuracy=self.accuracy,
priority=self.priority,
type_id=self.type,
damage_class_id=self.damage_class,
effect_id=self.effect,
effect_chance=self.effect_chance,
target_id=self.target,
crit_rate=self.crit_rate,
min_hits=self.min_hits,
max_hits=self.max_hits,
)
@classmethod
def struggle(cls):
"""Generate an instance of the move struggle."""
return cls(
id=165,
identifier="struggle",
power=50,
pp=999999999999,
accuracy=None,
priority=0,
type_id=ElementType.TYPELESS,
damage_class_id=2,
effect_id=255,
effect_chance=None,
target_id=10,
crit_rate=0,
min_hits=None,
max_hits=None,
)
@classmethod
def confusion(cls):
"""Generate an instance of the move confusion."""
return cls(
id=0xCFCF,
identifier="confusion",
power=40,
pp=999999999999,
accuracy=None,
priority=0,
type_id=ElementType.TYPELESS,
damage_class_id=DamageClass.PHYSICAL,
effect_id=1,
effect_chance=None,
target_id=7,
crit_rate=0,
min_hits=None,
max_hits=None,
)
@classmethod
def present(cls, power):
"""Generate an instance of the move present."""
return cls(
id=217,
identifier="present",
power=power,
pp=999999999999,
accuracy=90,
priority=0,
type_id=ElementType.NORMAL,
damage_class_id=DamageClass.PHYSICAL,
effect_id=123,
effect_chance=None,
target_id=10,
crit_rate=0,
min_hits=None,
max_hits=None,
)
def __repr__(self):
return f"Move(name={self.name!r}, power={self.power!r}, effect_id={self.effect!r})"