3752 lines
178 KiB
Python
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})"
|