BattleSim/server/bw/data/moves.coffee

1964 lines
58 KiB
CoffeeScript

@MoveData = require('./data_moves.json')
{Weather} = require('../../../shared/weather')
{Move} = require('../move')
{Attachment, Status} = require('../attachment')
{Ability} = require ('./abilities')
{Protocol} = require('../../../shared/protocol')
{_} = require 'underscore'
util = require '../util'
HiddenPower = require '../../../shared/hidden_power'
# Generate the initial versions of every single move.
# Many will be overwritten later.
@Moves = Moves = {}
@MoveList = MoveList = []
for name, attributes of @MoveData
@Moves[name] = new Move(name, attributes)
MoveList.push(@Moves[name])
# Extends a move in the move list using a callback.
#
# name - The name of the move to extend.
# callback - The function that will extend the move. 'this'
# is the move object itself.
#
# Example:
#
# extendMove 'Substitute', (attributes) ->
# @initialize = -> # blah
# @afterMove = -> # blah
#
extendMove = (name, callback) ->
if name not of Moves
throw new Error("Cannot extend Move '#{name}' because it does not exist.")
move = Moves[name]
callback.call(move, move.attributes)
# We add primary boost moves to the "hit" handler.
for move in MoveList
if move.hasPrimaryEffect()
extendMove move.name, ->
@hit = (battle, user, target) ->
if @primaryBoostStats?
pokemon = (if @primaryBoostTarget == 'self' then user else target)
pokemon.boost(@primaryBoostStats, user)
if @ailmentId != 'none' && @ailmentChance == 0
effect = battle.getAilmentEffect(this)
if !target.attach(effect, source: user)
@fail(battle, user)
makeJumpKick = (name, recoilPercent=.5) ->
extendMove name, ->
@afterMiss = @afterFail = (battle, user, target) ->
maxHP = user.stat('hp')
user.damage(Math.floor(maxHP / 2))
battle.cannedText('JUMP_KICK_MISS', user)
makeWeightBased = (name) ->
extendMove name, ->
@basePower = (battle, user, target) ->
weight = target.calculateWeight()
if weight <= 100 then 20
else if weight <= 250 then 40
else if weight <= 500 then 60
else if weight <= 1000 then 80
else if weight <= 2000 then 100
else 120
makeWeightRatioBased = (name) ->
extendMove name, ->
@basePower = (battle, user, target) ->
n = target.calculateWeight() / user.calculateWeight()
if n < .2 then 120
else if n < .25 then 100
else if n < 1/3 then 80
else if n < .5 then 60
else 40
makeLevelAsDamageMove = (name) ->
extendMove name, ->
@calculateDamage = (battle, user, target) ->
user.level
makeReversalMove = (name) ->
extendMove name, ->
@basePower = (battle, user, target) ->
n = Math.floor(64 * user.currentHP / user.stat('hp'))
if n <= 1 then 200
else if n <= 5 then 150
else if n <= 12 then 100
else if n <= 21 then 80
else if n <= 42 then 40
else if n <= 64 then 20
makeEruptionMove = (name) ->
extendMove name, ->
@basePower = (battle, user, target) ->
power = Math.floor(150 * (user.currentHP / user.stat('hp')))
Math.max(power, 1)
makeStatusCureAttackMove = (moveName, status) ->
extendMove moveName, ->
@basePower = (battle, user, target) ->
if target.has(status) then 2 * @power else @power
@afterSuccessfulHit = (battle, user, target) ->
target.cureStatus(status) unless user.isFainted()
makeOneHitKOMove = (name) ->
extendMove name, ->
@calculateDamage = (battle, user, target) ->
# TODO: Or was this fixed?
target.stat('hp')
@afterSuccessfulHit = (battle, user, target, damage) ->
# TODO: Is this message displayed even if the Pokemon survives?
battle.message "It was a one-hit KO!"
@chanceToHit = (battle, user, target) ->
(user.level - target.level) + 30
makeRecoveryMove = (name) ->
extendMove name, ->
@use = (battle, user, target) ->
hpStat = target.stat('hp')
if target.currentHP == hpStat
@fail(battle, user)
return false
amount = Math.round(hpStat / 2)
if target.heal(amount)
battle.cannedText('RECOVER_HP', target)
makeBasePowerBoostMove = (name, rawBasePower, maxBasePower, what) ->
extendMove name, ->
@basePower = (battle, user, target) ->
pokemon = {user, target}[what]
# NOTE: the 20 is hardcoded; perhaps this will change later?
power = rawBasePower + 20 * pokemon.positiveBoostCount()
Math.min(power, maxBasePower)
makeWeatherRecoveryMove = (name) ->
extendMove name, ->
@use = (battle, user, target) ->
hpStat = target.stat('hp')
amount = if battle.hasWeather(Weather.NONE)
util.roundHalfDown(hpStat / 2)
else if battle.hasWeather(Weather.SUN)
util.roundHalfDown(hpStat * 2 / 3)
else if battle.hasWeather(Weather.MOON)
if @name == 'Moonlight'
util.roundHalfDown(hpStat / 2)
else
util.roundHalfDown(hpStat * 1 / 3)
else
util.roundHalfDown(hpStat / 4)
percent = Math.floor(100 * amount / hpStat)
battle.cannedText('RECOVER_HP', target, percent)
target.heal(amount)
makeTrickMove = (name) ->
extendMove name, ->
@use = ->
@afterSuccessfulHit = (battle, user, target) ->
if (user.hasItem() && !user.hasTakeableItem()) ||
(target.hasItem() && !target.canLoseItem()) ||
(!target.hasItem() && !user.hasItem())
@fail(battle, user)
return false
uItem = user.removeItem()
tItem = target.removeItem()
battle.cannedText('TRICK_START', user)
if tItem
battle.cannedText('TRICK_END', user, tItem)
user.setItem(tItem)
if uItem
battle.cannedText('TRICK_END', target, uItem)
target.setItem(uItem)
makeExplosionMove = (name) ->
extendMove name, ->
oldExecute = @execute
@execute = (battle, user, targets) ->
if !_.any(targets, (target) -> target.hasAbility('Damp'))
user.faint()
oldExecute.call(this, battle, user, targets)
else
@fail(battle, user)
@fail = (battle, user) ->
battle.message "#{user.name} cannot use #{@name}!"
makeProtectCounterMove = (name, callback) ->
extendMove name, ->
@execute = (battle, user, targets) ->
# Protect fails if the user is the last to move.
if !battle.hasActionsLeft()
user.unattach(Attachment.ProtectCounter)
@fail(battle, user)
return
# Calculate and roll chance of success
attachment = user.attach(Attachment.ProtectCounter)
attachment.turns = 2
chance = attachment.successChance()
if battle.rng.randInt(1, chance, "protect") > 1
user.unattach(Attachment.ProtectCounter)
@fail(battle, user)
return
# Success!
callback.call(this, battle, user, targets)
makeOpponentFieldMove = (name, func) ->
extendMove name, ->
@execute = (battle, user, opponentIds) ->
userId = battle.getOwner(user)
for id in opponentIds
team = battle.getTeam(id)
continue if team.shouldBlockFieldExecution(this, userId)
func.call(this, battle, user, id)
makeProtectMove = (name) ->
makeProtectCounterMove name, (battle, user, targets) ->
user.attach(Attachment.Protect)
makeIdentifyMove = (name, types) ->
extendMove name, ->
@use = (battle, user, target) ->
if target.attach(Attachment.Identify, {types})
battle.message "#{target.name} was identified!"
else
@fail(battle, user)
false
makePluckMove = (name) ->
extendMove name, ->
@afterSuccessfulHit = (battle, user, target) ->
item = target.getItem()
if user.isAlive() && item?.type == 'berries'
item.eat(battle, user)
target.removeItem()
makeThiefMove = (name) ->
extendMove name, ->
@afterSuccessfulHit = (battle, user, target) ->
return if user.hasItem() || !target.canLoseItem() || user.isFainted()
battle.cannedText('THIEF_START', user, target, target.item)
user.setItem(target.item)
target.removeItem()
makeStatusCureMove = (name, message) ->
extendMove name, ->
@execute = (battle, user, targets) ->
battle.message(message)
for target in targets
target.cureStatus(message: false)
makePickAttackMove = (name) ->
extendMove name, ->
@pickAttackStat = (user, target) ->
target.stat('attack', ignoreNegativeBoosts: user.crit)
makePickDefenseMove = (name) ->
extendMove name, ->
@pickDefenseStat = (user, target) ->
target.stat('defense', ignorePositiveBoosts: user.crit)
makeDelayedAttackMove = (name, message) ->
extendMove name, ->
@execute = (battle, user, targets) ->
# TODO: use slot
playerId = battle.getOpponentOwners(user)[0]
team = battle.getTeam(playerId)
if !team.attach(Attachment.DelayedAttack, user: user, move: this)
@fail(battle, user)
return
battle.message message.replace("$1", user.name)
makeRandomSwitchMove = (name) ->
extendMove name, ->
@afterSuccessfulHit = (battle, user, target, damage, isDirect) ->
return if !isDirect
return if target.isFainted() || user.isFainted()
return if target.shouldPhase(battle, user) == false
{team} = target
benched = team.getAliveBenchedPokemon()
return if benched.length == 0
pokemon = battle.rng.choice(benched)
team.switch(target, team.indexOf(pokemon))
makeRampageMove = (moveName) ->
extendMove moveName, ->
@afterSuccessfulHit = (battle, user, target) ->
user.attach(Attachment.Rampage, move: this)
makeRampageMove("Outrage")
makeRampageMove("Petal Dance")
makeRampageMove("Thrash")
# TODO: Does it fail if using twice, but on a different target?
# From PokemonLab:
# TODO: Major research is required here. Does Lock On make the next move to
# target the subject hit; the next move to target the subject on the
# next turn; all moves targeting the subject on the turn turn; or some
# other possibility?
makeLockOnMove = (name) ->
extendMove name, ->
@afterSuccessfulHit = (battle, user, target) ->
if user.attach(Attachment.LockOn, {target})
battle.message "#{user.name} locked onto #{target.name}."
else
@fail(battle, user)
return false
makeStompMove = (name) ->
extendMove name, ->
@basePower = (battle, user, target) ->
if target.has(Attachment.Minimize)
2 * @power
else
@power
makeMeanLookMove = (name) ->
extendMove name, ->
@afterSuccessfulHit = (battle, user, target) ->
if target.attach(Attachment.MeanLook, user: user)
battle.message "#{target.name} can no longer escape!"
else
@fail(battle, user)
return false
makeChargeMove = (name, args...) ->
condition = args.pop() if typeof args[args.length - 1] == 'function'
message = args.pop()
vulnerable = args.pop() if args.length > 0
extendMove name, ->
@beforeTurn = (battle, user) ->
data = {vulnerable, message, condition}
data.move = this
user.attach(Attachment.Charging, data)
makeChargeMove 'Skull Bash', "$1 tucked in its head!"
makeChargeMove 'Razor Wind', "$1 whipped up a whirlwind!"
makeChargeMove 'Sky Attack', "$1 became cloaked in a harsh light!"
makeChargeMove 'Shadow Force', [], "$1 vanished instantly!", (battle) ->
battle.hasWeather(Weather.MOON)
makeChargeMove 'Ice Burn', "$1 became cloaked in freezing air!"
makeChargeMove 'Freeze Shock', "$1 became cloaked in a freezing light!"
makeChargeMove 'Fly', ["Gust", "Thunder", "Twister", "Sky Uppercut", "Hurricane", "Smack Down"], "$1 flew up high!"
makeChargeMove 'Bounce', ["Gust", "Thunder", "Twister", "Sky Uppercut", "Hurricane", "Smack Down"], "$1 sprang up!"
makeChargeMove 'Dig', ["Earthquake", "Magnitude"], "$1 burrowed its way under the ground!"
makeChargeMove 'Dive', ["Surf", "Whirlpool"], "$1 hid underwater!"
makeChargeMove 'SolarBeam', "$1 absorbed light!", (battle) ->
battle.hasWeather(Weather.SUN)
extendMove 'SolarBeam', ->
@basePower = (battle, user, target) ->
if battle.hasWeather(Weather.RAIN) || battle.hasWeather(Weather.SAND) ||
battle.hasWeather(Weather.HAIL)
@power >> 1
else
@power
makeRechargeMove = (name) ->
extendMove name, ->
@afterSuccessfulHit = (battle, user, target) ->
user.attach(Attachment.Recharge)
makeMomentumMove = (name) ->
extendMove name, ->
@afterSuccessfulHit = (battle, user, target) ->
attachment = user.attach(Attachment.Momentum, move: this)
attachment.turns += 1
@basePower = (battle, user, target) ->
times = user.get(Attachment.Momentum)?.layers || 0
bp = @power * Math.pow(2, times)
bp *= 2 if user.has(Attachment.DefenseCurl)
bp
makeRevengeMove = (moveName) ->
extendMove moveName, ->
@basePower = (battle, user, target) ->
hit = user.lastHitBy
return @power if !hit?
{team, slot, move, turn} = hit
pokemon = team.at(slot)
if target == pokemon && !move.isNonDamaging() && battle.turn == turn
2 * @power
else
@power
makeWeatherMove = (name, weatherType) ->
extendMove name, ->
@execute = (battle, user) ->
if !battle.hasWeather(weatherType)
@changeWeather(battle, user)
else
@fail(battle, user)
@changeWeather = (battle, user) ->
length = 5
length = 8 if weatherType == user.getItem()?.lengthensWeather
battle.setWeather(weatherType, length)
extendWithBoost = (name, boostTarget, boosts) ->
extendMove name, ->
@afterSuccessfulHit = (battle, user, target) ->
pokemon = (if boostTarget == 'self' then user else target)
pokemon.boost(boosts, user)
makeCounterMove = (name, multiplier, applies) ->
extendMove name, ->
@getTargets = (battle, user) ->
# Return the last pokemon who hit this one, if it's alive.
hit = user.lastHitBy
if hit
{team, slot} = hit
pokemon = team.at(slot)
return [ pokemon ] if !pokemon.isFainted()
# Return a random target (or none).
pokemon = battle.getOpponents(user)
pokemon = pokemon.filter((p) -> !p.isFainted())
if pokemon.length == 0
[]
else
[ battle.rng.choice(pokemon) ]
@calculateDamage = -> 0
oldUse = @use
@use = (battle, user, target) ->
return if oldUse.call(this, battle, user, target) == false
hit = user.lastHitBy
if hit? && applies(hit.move) && hit.turn == battle.turn
target.damage(multiplier * hit.damage, direct: false, source: "move")
else
@fail(battle, user)
return false
makeTrappingMove = (name) ->
extendMove name, ->
@afterSuccessfulHit = (battle, user, target, damage, isDirect) ->
if !target.has(Attachment.Trap) && isDirect
turns = if !user.hasItem("Grip Claw")
battle.rng.randInt(4, 5, "trapping move")
else
7
target.attach(Attachment.Trap, user: user, moveName: name, turns: turns)
makeIgnoreStagesMove = (name) ->
extendMove name, ->
oldExecute = @execute
@execute = (battle, user, targets) ->
target.attach(Attachment.ChipAway) for target in targets
oldExecute.call(this, battle, user, targets)
target.unattach(Attachment.ChipAway) for target in targets
makeStatusCureMove 'Aromatherapy', 'A soothing aroma wafted through the area!'
extendMove 'Baton Pass', ->
@execute = (battle, user, targets) ->
slot = user.team.indexOf(user)
if !battle.forceSwitch(user)
@fail(battle, user)
return
# Copy!
passable = user.attachments.getPassable()
stages = _.clone(user.stages)
attachments = user.attachments.attachments
attachments = attachments.filter((a) -> a.constructor in passable)
for attachment in passable
user.unattach(attachment)
user.team.attach(Attachment.BatonPass, {slot, stages, attachments})
makeTrappingMove "Bind"
makeRechargeMove 'Blast Burn'
extendMove 'Blizzard', ->
@getAccuracy = (battle, user, target) ->
return 0 if battle.hasWeather(Weather.HAIL)
return @accuracy
makeMeanLookMove 'Block'
makePluckMove 'Bug Bite'
extendMove 'Camouflage', ->
@use = (battle, user, target) ->
# Camouflage changes type based on terrain
# In Wi-Fi battles, the terrain always results in Ground type.
target.types = [ "Ground" ]
battle.message "#{user.name} transformed into a Ground type!"
extendMove 'Captivate', ->
oldUse = @use
@use = (battle, user, target) ->
if (!(user.gender == 'M' && target.gender == 'F') &&
!(user.gender == 'F' && target.gender == 'M'))
@fail(battle, user)
else
target.boost(specialAttack: -2, user)
extendMove 'Charge', ->
oldUse = @use
@use = (battle, user, target) ->
user.unattach(Attachment.Charge) # Charge can be used twice in a row
user.attach(Attachment.Charge)
battle.message "#{user.name} began charging power!"
oldUse.call(this, battle, user, target)
makeIgnoreStagesMove 'Chip Away'
makeRandomSwitchMove "Circle Throw"
makeTrappingMove "Clamp"
extendMove 'Clear Smog', ->
@afterSuccessfulHit = (battle, user, target, damage, isDirect) ->
if isDirect
target.resetBoosts()
battle.cannedText('RESET_STATS', target)
extendWithBoost 'Close Combat', 'self', defense: -1, specialDefense: -1
extendMove 'Conversion', ->
@use = (battle, user, target) ->
{types, moves} = target
moves = _.without(moves, battle.getMove(@name))
# The original type of the move is used, not its generated type.
moveTypes = moves.map((move) -> move.type)
types = _.difference(moveTypes, types)
type = battle.rng.choice(types, "conversion types")
if !type?
@fail(battle, user)
return false
battle.message "#{user.name} transformed into #{type} Type!"
target.types = [ type ]
extendMove 'Conversion 2', ->
@use = (battle, user, target) ->
{lastMove} = target
if !lastMove?
@fail(battle, user)
return false
moveType = lastMove.type
possibles = []
for type, value of util.Type
possibles.push(type) if util.typeEffectiveness(moveType, [ type ]) < 1
newType = battle.rng.choice(possibles, "conversion 2")
battle.message "#{user.name} transformed into #{newType} Type!"
user.types = [ newType ]
makeThiefMove 'Covet'
extendMove 'Defense Curl', ->
oldUse = @use
@use = (battle, user, target) ->
oldUse.call(this, battle, user, target)
target.attach(Attachment.DefenseCurl)
extendMove 'Defog', ->
@entryHazards = [
Attachment.Spikes
Attachment.StealthRock
Attachment.FireRock
Attachment.ToxicSpikes
Attachment.Livewire
]
@selectPokemon = (battle, user, target) ->
[ target ]
@afterSuccessfulHit = (battle, user, target) ->
target.boost(evasion: -1, user)
target.team.unattach(Attachment.Reflect)
target.team.unattach(Attachment.LightScreen)
target.team.unattach(Attachment.Safeguard)
for pokemon in @selectPokemon(battle, user, target)
for hazard in @entryHazards
pokemon.team.unattach(hazard)
makeProtectMove 'Detect'
extendWithBoost 'Draco Meteor', 'self', specialAttack: -2
makeRandomSwitchMove "Dragon Tail"
extendMove 'Entrainment', ->
bannedSourceAbilities =
"Flower Gift": true
"Forecast": true
"Illusion": true
"Imposter": true
"Trace": true
"Zen Mode": true
@afterSuccessfulHit = (battle, user, target) ->
if user.hasChangeableAbility() && user.ability.displayName not of bannedSourceAbilities && target.hasChangeableAbility() && target.ability.displayName != 'Truant'
target.copyAbility(user.ability)
battle.cannedText('ACQUIRE_ABILITY', target, user.ability)
else
@fail(battle, user)
makeEruptionMove 'Eruption'
makeExplosionMove 'Explosion'
extendMove 'Fake Out', ->
oldUse = @use
@use = (battle, user, target) ->
return false if oldUse.call(this, battle, user, target) == false
if user.turnsActive > 1
@fail(battle, user)
return false
extendMove 'Feint', ->
@afterSuccessfulHit = (battle, user, target) ->
# TODO: Wide Guard
if target.has(Attachment.Protect)
target.unattach(Attachment.Protect)
battle.message "#{target.name} fell for the feint!"
makeTrappingMove "Fire Spin"
makeOneHitKOMove 'Fissure'
makeReversalMove 'Flail'
extendMove 'Flame Wheel', -> @thawsUser = true
extendMove 'Flare Blitz', -> @thawsUser = true
extendMove 'Focus Energy', ->
@use = (battle, user, target) ->
if user.attach(Attachment.FocusEnergy)
# TODO: Real message
battle.message "#{user.name} began to focus!"
else
@fail(battle, user)
false
extendMove 'Focus Punch', ->
@beforeTurn = (battle, user) ->
battle.message "#{user.name} is tightening its focus!"
user.attach(Attachment.FocusPunch)
makeIdentifyMove("Foresight", ["Normal", "Fighting"])
makePickAttackMove 'Foul Play'
makeRechargeMove 'Frenzy Plant'
extendMove 'Fusion Flare', -> @thawsUser = true
makeRechargeMove 'Giga Impact'
makeWeightBased 'Grass Knot'
extendMove 'Growth', ->
@use = (battle, user, target) ->
boost = if battle.hasWeather(Weather.SUN) then 2 else 1
user.boost(attack: boost, specialAttack: boost)
extendMove 'Grudge', ->
@execute = (battle, user, targets) ->
user.attach(Attachment.Grudge)
battle.message "#{user.name} wants its target to bear a grudge!"
makeOneHitKOMove 'Guillotine'
extendWithBoost 'Hammer Arm', 'self', speed: -1
makeStatusCureMove 'Heal Bell', 'A bell chimed!'
makeRecoveryMove 'Heal Order'
makeRecoveryMove 'Heal Pulse'
makeWeightRatioBased 'Heat Crash'
makeWeightRatioBased 'Heavy Slam'
makeJumpKick 'Hi Jump Kick'
makeOneHitKOMove 'Horn Drill'
makeRechargeMove 'Hydro Cannon'
makeRechargeMove 'Hyper Beam'
makeMomentumMove 'Ice Ball'
makeWeatherMove 'Hail', Weather.HAIL
extendMove 'Hurricane', ->
@getAccuracy = (battle, user, target) ->
return 50 if battle.hasWeather(Weather.SUN)
return 0 if battle.hasWeather(Weather.RAIN)
return @accuracy
makeJumpKick 'Jump Kick'
extendWithBoost 'Leaf Storm', 'self', specialAttack: -2
extendMove 'Leech Seed', ->
oldWillMiss = @willMiss
@willMiss = (battle, user, target) ->
if target.hasType("Grass")
true
else
oldWillMiss.call(this, battle, user, target)
makeLockOnMove 'Lock-On'
makeWeightBased 'Low Kick'
extendMove 'Lucky Chant', ->
@execute = (battle, user, opponents) ->
if user.team.attach(Attachment.LuckyChant)
battle.message "The Lucky Chant shielded #{battle.getOwner(user)}'s " +
"team from critical hits!"
else
@fail(battle, user)
makeTrappingMove "Magma Storm"
makeMeanLookMove 'Mean Look'
makeRecoveryMove 'Milk Drink'
makeLockOnMove 'Mind Reader'
extendMove 'Minimize', ->
@afterSuccessfulHit = (battle, user, target) ->
target.attach(Attachment.Minimize)
makeIdentifyMove("Miracle Eye", ["Psychic"])
extendMove 'Mirror Move', ->
@hit = (battle, user, target) ->
move = target.lastMove
if !move? || !move.hasFlag("mirror")
@fail(battle, user)
return false
battle.executeMove(move, user, battle.getTargets(move, user))
makeWeatherRecoveryMove 'Moonlight'
makeWeatherRecoveryMove 'Morning Sun'
makeLevelAsDamageMove 'Night Shade'
makeIdentifyMove("Odor Sleuth", ["Normal", "Fighting"])
extendWithBoost 'Overheat', 'self', specialAttack: -2
makePluckMove 'Pluck'
makeProtectMove 'Protect'
extendWithBoost 'Psycho Boost', 'self', specialAttack: -2
makePickDefenseMove 'Psyshock'
makePickDefenseMove 'Psystrike'
makeBasePowerBoostMove 'Punishment', 60, 200, 'target'
makeWeatherMove 'Rain Dance', Weather.RAIN
makeRecoveryMove 'Recover'
extendMove 'Refresh', ->
@afterSuccessfulHit = (battle, user, target) ->
if !target.cureStatus()
@fail(battle, user)
makeRevengeMove 'Revenge'
makeReversalMove 'Reversal'
makeRandomSwitchMove "Roar"
makeRechargeMove 'Roar of Time'
makeRechargeMove 'Rock Wrecker'
makeMomentumMove 'Rollout'
makeRecoveryMove 'Roost'
extendMove 'Roost', ->
@afterSuccessfulHit = (battle, user, target) ->
user.attach(Attachment.Roost)
extendMove 'Sacred Fire', -> @thawsUser = true
makeIgnoreStagesMove 'Sacred Sword'
makeWeatherMove 'Sandstorm', Weather.SAND
makeTrappingMove "Sand Tomb"
extendMove 'Scald', -> @thawsUser = true
makePickDefenseMove 'Secret Sword'
makeExplosionMove 'Selfdestruct'
makeLevelAsDamageMove 'Seismic Toss'
makeOneHitKOMove 'Sheer Cold'
makeRecoveryMove 'Slack Off'
makeRecoveryMove 'Softboiled'
makeMeanLookMove 'Spider Web'
extendMove 'Spit Up', ->
oldUse = @use
@use = (battle, user, target) ->
if !user.has(Attachment.Stockpile)
@fail(battle, user)
return false
oldUse.call(this, battle, user, target)
@basePower = (battle, user, target) ->
attachment = user.get(Attachment.Stockpile)
layers = attachment?.layers || 0
100 * layers
oldExecute = @execute
@execute = (battle, user, targets) ->
oldExecute.call(this, battle, user, targets)
attachment = user.get(Attachment.Stockpile)
return if !attachment?
num = -attachment.layers
user.unattach(Attachment.Stockpile)
user.boost(defense: num, specialDefense: num)
makeStompMove 'Steamroller'
extendMove 'Stockpile', ->
@afterSuccessfulHit = (battle, user, target) ->
if user.attach(Attachment.Stockpile)
user.boost(defense: 1, specialDefense: 1)
else
@fail(battle, user)
makeStompMove 'Stomp'
makeBasePowerBoostMove 'Stored Power', 20, 860, 'user'
makeWeatherMove 'Sunny Day', Weather.SUN
extendWithBoost 'Superpower', 'self', attack: -1, defense: -1
extendMove 'Swallow', ->
oldUse = @use
@use = (battle, user, target) ->
if !user.has(Attachment.Stockpile)
@fail(battle, user)
return false
oldUse.call(this, battle, user, target)
@afterSuccessfulHit = (battle, user, target) ->
{layers} = target.get(Attachment.Stockpile)
amount = util.roundHalfDown(target.stat('hp') / Math.pow(2, 3 - layers))
# Swallow is not a draining move, so it is not affected by Big Root.
target.heal(amount)
oldExecute = @execute
@execute = (battle, user, targets) ->
oldExecute.call(this, battle, user, targets)
for target in targets
attachment = target.get(Attachment.Stockpile)
return if !attachment?
num = -attachment.layers
target.unattach(Attachment.Stockpile)
user.boost(defense: num, specialDefense: num)
makeTrickMove 'Switcheroo'
extendMove 'Super Fang', ->
@calculateDamage = (battle, user, target) ->
halfHP = Math.floor(target.currentHP / 2)
Math.max(1, halfHP)
makeWeatherRecoveryMove 'Synthesis'
extendMove 'Teleport', ->
@execute = (battle, user) -> @fail(battle, user)
makeThiefMove 'Thief'
extendMove 'Thunder', ->
@getAccuracy = (battle, user, target) ->
return 50 if battle.hasWeather(Weather.SUN)
return 0 if battle.hasWeather(Weather.RAIN)
return @accuracy
extendMove 'Thunder Wave', ->
@ignoresImmunities = -> false
makeTrickMove 'Trick'
extendWithBoost 'V-create', 'self', defense: -1, specialDefense: -1, speed: -1
makeEruptionMove 'Water Spout'
makeTrappingMove "Whirlpool"
makeRandomSwitchMove "Whirlwind"
makeTrappingMove "Wrap"
extendMove 'Assist', ->
bannedMoves =
"Assist": true
"Bestow": true
"Chatter": true
"Circle Throw": true
'Copycat': true
"Counter": true
"Covet": true
"Destiny Bond": true
"Detect": true
"Dragon Tail": true
"Endure": true
"Feint": true
"Focus Punch": true
"Follow Me": true
"Helping Hand": true
"Me First": true
"Metronome": true
"Mimic": true
"Mirror Coat": true
"Mirror Move": true
"Nature Power": true
"Protect": true
"Rage Powder": true
"Sketch": true
"Sleep Talk": true
"Snatch": true
"Struggle": true
"Switcheroo": true
"Thief": true
"Transform": true
"Trick": true
@execute = (battle, user) ->
pokemon = _.without(user.team.pokemon, user)
moves = _.flatten(pokemon.map((p) -> p.moves))
moves = moves.filter((move) -> move.name not of bannedMoves)
if moves.length == 0
@fail(battle, user)
else
move = battle.rng.choice(moves, "assist")
battle.executeMove(move, user, battle.getTargets(move, user))
extendMove 'Aqua Ring', ->
@afterSuccessfulHit = (battle, user, target) ->
target.attach(Attachment.AquaRing)
battle.message "#{target.name} surrounded itself with a veil of water!"
extendMove 'Assurance', ->
@basePower = (battle, user, target) ->
hit = user.lastHitBy
if hit?.turn == battle.turn && !hit.move.isNonDamaging()
2 * @power
else
@power
extendMove 'Autotomize', ->
@afterSuccessfulHit = (battle, user, target) ->
target.attach(Attachment.Autotomize)
makeRevengeMove 'Avalanche'
extendMove 'Acrobatics', ->
@basePower = (battle, user, target) ->
if !user.hasItem() then 2 * @power else @power
extendMove 'Acupressure', ->
@use = (battle, user, target) ->
stats = (stat for stat, num of target.stages when num < 6)
if stats.length > 0
randomStat = battle.rng.choice(stats)
hash = {}
hash[randomStat] = 2
target.boost(hash)
else
@fail(battle, user)
return false
extendMove 'Beat Up', ->
@calculateNumberOfHits = (battle, user, target) ->
teamIndex = user.team.indexOf(user)
numHits = 0
for p, i in user.team.pokemon
if teamIndex == i || (!p.hasStatus() && !p.isFainted())
numHits++
numHits
@basePower = (battle, user, target, hitNumber=1) ->
index = -1
{team} = user
teamIndex = team.indexOf(user)
for x in [0...hitNumber]
index++
while index != teamIndex &&
(team.at(index).hasStatus() || team.at(index).isFainted())
index++
5 + Math.floor(team.at(index).baseStats.attack / 10)
extendMove 'Belly Drum', ->
@use = (battle, user, target) ->
halfHP = Math.floor(user.stat('hp') / 2)
if user.currentHP <= halfHP || !user.boost(attack: 12)
@fail(battle, user)
return false
else
user.damage(halfHP, source: "move")
extendMove 'Brick Break', ->
oldUse = @use
@use = (battle, user, target) ->
return false if oldUse.call(this, battle, user, target) == false
target.team.unattach(Attachment.Reflect)
target.team.unattach(Attachment.LightScreen)
extendMove 'Brine', ->
@basePower = (battle, user, target) ->
if target.currentHP <= Math.floor(target.stat('hp') / 2)
2 * @power
else
@power
extendMove 'Copycat', ->
@execute = (battle, user, targets) ->
move = battle.lastMove
if move? && move != battle.getMove('Copycat')
battle.executeMove(move, user, battle.getTargets(move, user))
else
@fail(battle, user)
makeCounterMove('Counter', 2, (move) -> move.isPhysical())
makeCounterMove('Mirror Coat', 2, (move) -> move.isSpecial())
makeCounterMove('Metal Burst', 1.5, (move) -> move.isPhysical() || move.isSpecial())
extendMove 'Crush Grip', ->
@basePower = (battle, user, target) ->
1 + Math.floor(120 * target.currentHP / target.stat('hp'))
extendMove 'Curse', ->
@getTargets = (battle, user) ->
pokemon = battle.getOpponents(user)
[ battle.rng.choice(pokemon, "random opponent") ]
@execute = (battle, user, targets) ->
if !user.hasType("Ghost")
user.boost(attack: 1, defense: 1, speed: -1)
return
user.damage(Math.floor(user.stat('hp') / 2), source: "move")
for target in targets
target.attach(Attachment.Curse)
battle.message "#{user.name} cut its own HP and laid a curse on #{target.name}!"
extendMove 'Destiny Bond', ->
@hit = (battle, user) ->
user.attach(Attachment.DestinyBond)
makeDelayedAttackMove("Doom Desire", "$1 chose Doom Desire as its destiny!")
extendMove 'Dragon Rage', ->
@calculateDamage = (battle, user, target) ->
40
extendMove 'Dream Eater', ->
oldUse = @use
@use = (battle, user, target) ->
if !target.has(Status.Sleep)
@fail(battle, user)
return false
oldUse.call(this, battle, user, target)
extendMove 'Echoed Voice', ->
@basePower = (battle, user, target) ->
layers = battle.get(Attachment.EchoedVoice)?.layers || 0
@power * (layers + 1)
@executing = (battle, user, targets) ->
battle.attach(Attachment.EchoedVoice)
attachment = battle.get(Attachment.EchoedVoice)
attachment.turns = 2
extendMove 'Electro Ball', ->
@basePower = (battle, user, target) ->
ratio = user.stat('speed') / target.stat('speed')
power = 0
if ratio >= 4
power = 150
else if ratio >= 3
power = 120
else if ratio >= 2
power = 80
else if ratio >= 1
power = 60
else
power = 40
extendMove 'Encore', ->
bannedMoves =
'Encore': true
'Mimic': true
'Mirror Move': true
'Sketch': true
'Struggle': true
'Transform': true
@afterSuccessfulHit = (battle, user, target) ->
if !target.lastMove?
@fail(battle, user)
else if target.lastMove.name of bannedMoves
@fail(battle, user)
else if target.pp(target.lastMove) == 0
@fail(battle, user)
else if target.attach(Attachment.Encore)
if battle.willMove(target)
battle.changeMove(target, target.lastMove)
else
@fail(battle, user)
extendMove 'Endeavor', ->
oldUse = @use
@use = (battle, user, target) ->
return false if oldUse.call(this, battle, user, target) == false
if target.currentHP < user.currentHP
@fail(battle, user)
return false
@calculateDamage = (battle, user, target) ->
target.currentHP - user.currentHP
makeProtectCounterMove 'Endure', (battle, user, targets) ->
battle.message "#{user.name} braced itself!"
user.attach(Attachment.Endure)
extendMove 'Facade', ->
@basePower = (battle, user, target) ->
if user.hasStatus()
2 * @power
else
@power
extendMove 'False Swipe', ->
@oldCalculateDamage = @calculateDamage
@calculateDamage = (battle, user, target, hitNumber, isDirect) ->
damage = @oldCalculateDamage(battle, user, target, hitNumber, isDirect)
if damage >= target.currentHP && isDirect
damage = target.currentHP - 1
damage
extendMove 'Final Gambit', ->
@afterSuccessfulHit = (battle, user, target) ->
user.faint()
@calculateDamage = (battle, user, target) ->
user.currentHP
extendMove 'Flatter', ->
@afterSuccessfulHit = (battle, user, target) ->
target.attach(Attachment.Confusion, {battle})
target.boost(specialAttack: 1, user)
extendMove 'Fling', ->
@beforeTurn = (battle, user) ->
user.attach(Attachment.Fling)
oldUse = @use
@use = (battle, user, target) ->
fling = user.get(Attachment.Fling)
if !fling?.item
@fail(battle, user)
return false
oldUse.call(this, battle, user, target)
@afterSuccessfulHit = (battle, user, target, damage, isDirect) ->
return unless isDirect
{item} = user.get(Attachment.Fling)
switch item.displayName
when "Poison Barb"
target.attach(Status.Poison)
when "Light Ball"
target.attach(Status.Paralyze)
when "Flame Orb"
target.attach(Status.Burn)
when "Toxic Orb"
target.attach(Status.Toxic)
when "King's Rock", "Razor Fang"
target.attach(Attachment.Flinch)
when "Mental Herb", "White Herb"
item.activate(battle, target)
else
item.eat(battle, target) if item.type == "berries"
@basePower = (battle, user, target) ->
fling = user.get(Attachment.Fling)
fling.item.flingPower
extendMove 'Frustration', ->
@basePower = -> 102
extendMove 'Fury Cutter', ->
@afterSuccessfulHit = (battle, user, target) ->
user.attach(Attachment.FuryCutter, move: this)
@basePower = (battle, user, target) ->
attachment = user.get(Attachment.FuryCutter)
layers = attachment?.layers || 0
@power * Math.pow(2, layers)
makeDelayedAttackMove("Future Sight", "$1 foresaw an attack!")
extendMove 'Gastro Acid', ->
@use = (battle, user, target) ->
if !target.attach(Attachment.AbilitySuppress)
@fail(battle, user)
return false
battle.message "#{target.name}'s ability was suppressed!"
extendMove 'Gravity', ->
@execute = (battle, user, targets) ->
if !battle.attach(Attachment.Gravity)
@fail(battle, user)
return
battle.message "Gravity intensified!"
for target in targets
target.attach(Attachment.GravityPokemon)
target.unattach(Attachment.MagnetRise)
target.unattach(Attachment.Telekinesis)
charging = target.get(Attachment.Charging)
target.unattach(Attachment.Charging) if charging?.move.hasFlag("gravity")
extendMove 'Guard Swap', ->
@afterSuccessfulHit = (battle, user, target) ->
triggered = false
userBoosts = {}
targetBoosts = {}
for stat in [ 'defense', 'specialDefense' ]
userBoosts[stat] = target.stages[stat]
targetBoosts[stat] = user.stages[stat]
triggered = true if target.stages[stat] != 0 || user.stages[stat] != 0
if triggered
user.setBoosts(userBoosts)
target.setBoosts(targetBoosts)
extendMove 'Gyro Ball', ->
@basePower = (battle, user, target) ->
power = 1 + Math.floor(25 * target.stat('speed') / user.stat('speed'))
Math.min(150, power)
extendMove 'Haze', ->
@execute = (battle, user, targets) ->
user.resetBoosts()
for target in targets
target.resetBoosts()
battle.cannedText('RESET_ALL_STATS')
extendMove 'Heal Block', ->
@afterSuccessfulHit = (battle, user, target) ->
if !target.attach(Attachment.HealBlock)
battle.cannedText('HEAL_BLOCK_FAIL', target)
extendMove 'Heart Swap', ->
@afterSuccessfulHit = (battle, user, target) ->
[user.stages, target.stages] = [target.stages, user.stages]
battle.message "#{user.name} switched stat changes with the target!"
extendMove 'Hex', ->
@basePower = (battle, user, target) ->
if target.hasStatus()
2 * @power
else
@power
extendMove 'Hidden Power', ->
@basePower = (battle, user, target) ->
ivs =
hp: user.iv('hp')
attack: user.iv('attack')
defense: user.iv('defense')
speed: user.iv('speed')
specialAttack: user.iv('specialAttack')
specialDefense: user.iv('specialDefense')
HiddenPower.BW.basePower(ivs)
@getType = (battle, user, target) ->
ivs =
hp: user.iv('hp')
attack: user.iv('attack')
defense: user.iv('defense')
speed: user.iv('speed')
specialAttack: user.iv('specialAttack')
specialDefense: user.iv('specialDefense')
HiddenPower.BW.type(ivs)
extendMove 'Imprison', ->
@hit = (battle, user, target) ->
{moves} = target
if target.attach(Attachment.Imprison, {battle, moves})
battle.message "#{target.name} sealed the opponent's moves!"
else
@fail(battle, user)
extendMove 'Incinerate', ->
@afterSuccessfulHit = (battle, user, target) ->
if target.hasItem() && target.getItem().type == 'berries'
battle.message "#{target.name}'s #{target.getItem().name} was burnt up!"
target.removeItem()
extendMove 'Judgment', ->
@getType = (battle, user, target) ->
user.getItem()?.plate || @type
extendMove 'Knock Off', ->
@afterSuccessfulHit = (battle, user, target, damage, isDirect) ->
if user.isAlive() && target.hasItem() && target.canLoseItem() && isDirect
battle.cannedText('KNOCK_OFF', user, target, target.getItem())
target.removeItem()
extendMove 'Last Resort', ->
oldUse = @use
@use = (battle, user, target) ->
if this not in user.moves || user.moves.length <= 1
@fail(battle, user)
return false
for moveName in _.without(user.moves, this).map((m) -> m.name)
if moveName not of user.used
@fail(battle, user)
return false
oldUse.call(this, battle, user, target)
extendMove 'Light Screen', ->
@execute = (battle, user, opponents) ->
if !user.team.attach(Attachment.LightScreen, {user})
@fail(battle, user)
extendMove 'Healing Wish', ->
@afterSuccessfulHit = (battle, user, target) ->
if target.team.getAliveBenchedPokemon().length > 0
target.faint()
target.team.attach(Attachment.HealingWish)
else
@fail(battle, user)
extendMove 'Lunar Dance', ->
@afterSuccessfulHit = (battle, user, target) ->
if target.team.getAliveBenchedPokemon().length > 0
target.faint()
target.team.attach(Attachment.LunarDance)
else
@fail(battle, user)
extendMove 'Magic Coat', ->
@afterSuccessfulHit = (battle, user, target) ->
target.attach(Attachment.MagicCoat)
target.team.attach(Attachment.MagicCoat)
extendMove 'Magnet Rise', ->
@use = (battle, user, target) ->
if target.attach(Attachment.MagnetRise)
battle.message "#{target.name} is now floating in the air!"
else
@fail(battle, user)
return false
extendMove 'Magnitude', ->
lastUse = {turn: 0, user: null}
@basePower = (battle, user, target) ->
if !(battle.turn == lastUse.turn && user == lastUse.user)
rand = battle.rng.randInt(0, 99, "magnitude")
magnitude = 0
power = 0
if rand < 5
power = 10
magnitude = 4
else if rand < 15
power = 30
magnitude = 5
else if rand < 35
power = 50
magnitude = 6
else if rand < 65
power = 70
magnitude = 7
else if rand < 85
power = 90
magnitude = 8
else if rand < 95
power = 110
magnitude = 9
else
power = 150
magnitude = 10
lastUse.turn = battle.turn
lastUse.user = user
battle.message "Magnitude #{magnitude}!"
power
extendMove 'Me First', ->
bannedMoves = {
"Chatter" : true
"Counter" : true
"Covet" : true
"Focus Punch": true
"Me First" : true
"Metal Burst": true
"Mirror Coat": true
"Struggle" : true
"Thief" : true
}
@execute = (battle, user, targets) ->
target = targets[0] # Me First is a single-target move
m = battle.peekMove(target)
if !battle.willMove(target) || m.isNonDamaging() || bannedMoves[m.name]
@fail(battle, user)
return false
user.attach(Attachment.MeFirst)
battle.executeMove(m, user, targets)
extendMove 'Memento', ->
@afterSuccessfulHit = (battle, user, target) ->
target.boost(attack: -2, specialAttack: -2, user)
user.faint()
extendMove 'Metronome', ->
@impossibleMoves = [
"After You"
"Assist"
"Bestow"
"Chatter"
"Copycat"
"Counter"
"Covet"
"Destiny Bond"
"Detect"
"Endure"
"Feint"
"Focus Punch"
"Follow Me"
"Freeze Shock"
"Helping Hand"
"Ice Burn"
"Me First"
"Metronome"
"Mimic"
"Mirror Coat"
"Mirror Move"
"Nature Power"
"Protect"
"Quash"
"Quick Guard"
"Rage Powder"
"Relic Song"
"Secret Sword"
"Sketch"
"Sleep Talk"
"Snarl"
"Snatch"
"Snore"
"Struggle"
"Switcheroo"
"Techno Blast"
"Thief"
"Transform"
"Trick"
"V-create"
"Wide Guard"
]
for move in @impossibleMoves
if move not of Moves
throw new Error("The illegal Metronome move '#{move}' does not exist.")
@execute = (battle, user, targets) ->
index = battle.rng.randInt(0, MoveList.length - 1, "metronome")
while MoveList[index].name in @impossibleMoves
index = battle.rng.randInt(0, MoveList.length - 1, "metronome reselect")
move = MoveList[index]
battle.message "Waggling a finger let it use #{move.name}!"
# Determine new targets
targets = battle.getTargets(move, user)
battle.executeMove(move, user, targets)
extendMove 'Natural Gift', ->
oldUse = @use
@use = (battle, user, target) ->
if !user.hasItem() || user.isItemBlocked() || !user.getItem().naturalGift
@fail(battle, user)
return false
oldUse.call(this, battle, user, target)
@basePower = (battle, user, target) ->
item = user.getItem()
item.naturalGift.power
@getType = (battle, user, target) ->
item = user.getItem()
item.naturalGift.type
@afterSuccessfulHit = (battle, user, target) ->
user.removeItem()
extendMove 'Nature Power', ->
@execute = (battle, user, targets) ->
# In Wi-Fi battles, Earthquake is always chosen.
battle.message "#{@name} turned into Earthquake!"
earthquake = battle.getMove('Earthquake')
battle.executeMove(earthquake, user, targets)
@getTargets = (battle, user) ->
earthquake = battle.getMove('Earthquake')
battle.getTargets(earthquake, user)
extendMove 'Pain Split', ->
@afterSuccessfulHit = (battle, user, target) ->
averageHP = Math.floor((user.currentHP + target.currentHP) / 2)
user.setHP(averageHP)
target.setHP(averageHP)
battle.cannedText('PAIN_SPLIT')
extendMove 'Pay Day', ->
@afterSuccessfulHit = (battle, user, target) ->
battle.cannedText('PAY_DAY')
extendMove 'Payback', ->
@basePower = (battle, user, target) ->
if !target.lastMove? || battle.willMove(target)
@power
else
2 * @power
extendMove 'Power Swap', ->
@afterSuccessfulHit = (battle, user, target) ->
triggered = false
userBoosts = {}
targetBoosts = {}
for stat in [ 'attack', 'specialAttack' ]
userBoosts[stat] = target.stages[stat]
targetBoosts[stat] = user.stages[stat]
triggered = true if target.stages[stat] != 0 || user.stages[stat] != 0
if triggered
user.setBoosts(userBoosts)
target.setBoosts(targetBoosts)
extendMove 'Present', ->
@basePower = (battle, user, target) ->
user.get(Attachment.Present).power
@afterSuccessfulHit = (battle, user, target) ->
if user.get(Attachment.Present).power == 0
amount = target.stat('hp') >> 2
target.heal(amount)
oldExecute = @execute
@execute = (battle, user, targets) ->
chance = battle.rng.next("present")
power = if chance < .1
120
else if chance < .3
0
else if chance < .6
80
else
40
user.attach(Attachment.Present, {power})
oldExecute.call(this, battle, user, targets)
extendMove 'Psywave', ->
@calculateDamage = (battle, user, target) ->
fraction = battle.rng.randInt(5, 15, "psywave") / 10
Math.floor(user.level * fraction)
extendMove 'Psych Up', ->
@use = (battle, user, target) ->
boosts = {}
for stage, value of target.stages
boosts[stage] = value
user.setBoosts(boosts)
battle.cannedText('PSYCH_UP', user, target)
extendMove 'Psycho Shift', ->
@afterSuccessfulHit = (battle, user, target) ->
if !user.hasStatus() || target.hasStatus()
@fail(battle, user)
return false
status = user.status
user.cureStatus()
target.attach(status)
extendMove 'Pursuit', ->
@beforeTurn = (battle, user) ->
user.attach(Attachment.Pursuit)
@basePower = (battle, user, target) ->
if user.has(Attachment.PursuitModifiers)
2 * @power
else
@power
extendMove 'Rage', ->
@afterSuccessfulHit = (battle, user, target) ->
user.attach(Attachment.Rage)
extendMove 'Rapid Spin', ->
@entryHazards = [ Attachment.Spikes, Attachment.StealthRock, Attachment.FireRock, Attachment.ToxicSpikes, Attachment.Livewire ]
@afterSuccessfulHit = (battle, user) ->
# Do not remove anything if the user is fainted.
if user.isFainted()
return
team = user.team
for hazard in @entryHazards
team.unattach(hazard)
# Remove trapping moves like fire-spin
user.unattach(Attachment.Trap)
# Remove leech seed
user.unattach(Attachment.LeechSeed)
extendMove 'Recycle', ->
@afterSuccessfulHit = (battle, user, target) ->
if !target.hasItem() && target.lastItem
battle.cannedText('FOUND_ITEM', target, target.lastItem)
target.setItem(target.lastItem, clearLastItem: true)
else
@fail(battle, user)
extendMove 'Reflect', ->
@execute = (battle, user, opponents) ->
if !user.team.attach(Attachment.Reflect, {user})
@fail(battle, user)
extendMove 'Reflect Type', ->
@use = (battle, user, target) ->
if user.hasAbility("Multitype")
@fail(battle, user)
return false
user.types = target.types
battle.message "#{user.name}'s type changed to match #{target.name}'s!"
extendMove 'Relic Song', ->
@afterSuccessfulHit = (battle, user, target) ->
return if user.name != 'Meloetta'
if user.isInForme("default")
user.changeForme("pirouette")
else
user.changeForme("default")
battle.cannedText('TRANSFORM', user)
extendMove 'Rest', ->
@hit = (battle, user, target) ->
if user.currentHP >= user.stat('hp') || user.has(Status.Sleep) ||
!user.attach(Status.Sleep, turns: 2, force: true)
@fail(battle, user)
return
user.setHP(user.stat('hp'))
extendMove 'Retaliate', ->
@basePower = (battle, user, target) ->
if user.team.faintedLastTurn
140
else
70
extendMove 'Return', ->
@basePower = -> 102
extendMove 'Role Play', ->
bannedAbilities =
"Flower Gift": true
"Forecast": true
"Illusion": true
'Imposter': true
"Trace": true
"Wonder Guard": true
"Zen Mode": true
@afterSuccessfulHit = (battle, user, target) ->
if user.hasChangeableAbility() && target.hasChangeableAbility() && target.ability.displayName not of bannedAbilities && user.ability != target.ability
user.copyAbility(target.ability)
battle.message "#{user.name} copied #{target.name}'s #{target.ability.displayName}!"
else
@fail(battle, user)
extendMove 'Safeguard', ->
@execute = (battle, user, target) ->
team = user.team
if !team.attach(Attachment.Safeguard, {source: user})
@fail(battle)
return false
battle.cannedText('SAFEGUARD_START', user)
extendMove 'Simple Beam', ->
bannedAbilities =
"Simple": true
"Truant": true
@afterSuccessfulHit = (battle, user, target) ->
if target.hasChangeableAbility() && target.ability.displayName not of bannedAbilities
target.copyAbility(Ability.Simple)
battle.cannedText('ACQUIRE_ABILITY', target, 'Simple')
else
@fail(battle, user)
extendMove 'Skill Swap', ->
bannedAbilities =
"Illusion": true
"Wonder Guard": true
@canSwapSameAbilities = false
@afterSuccessfulHit = (battle, user, target) ->
if user.hasChangeableAbility() && target.hasChangeableAbility() &&
user.ability.displayName not of bannedAbilities &&
target.ability.displayName not of bannedAbilities &&
(user.ability != target.ability || @canSwapSameAbilities)
user.swapAbilityWith(target)
else
@fail(battle)
extendMove 'Sleep Talk', ->
bannedMoves = [
"Assist"
"Bide"
"Chatter"
"Copycat"
"Focus Punch"
"Me First"
"Metronome"
"Mimic"
"Mirror Move"
"Nature Power"
"Sketch"
"Sleep Talk"
"Uproar"
]
@usableWhileAsleep = true
@execute = (battle, user) ->
viableMoves = user.moves.filter((move) -> move.name not in bannedMoves)
if viableMoves.length == 0 || !user.has(Status.Sleep)
@fail(battle, user)
return
moveIndex = battle.rng.randInt(0, viableMoves.length - 1, "sleep talk")
move = viableMoves[moveIndex]
battle.executeMove(move, user, battle.getTargets(move, user))
extendMove 'Smack Down', ->
@afterSuccessfulHit = (battle, user, target) ->
target.attach(Attachment.SmackDown)
target.unattach(Attachment.MagnetRise)
target.unattach(Attachment.Telekinesis)
# Smack Down will miss on charge moves it cannot hit.
target.unattach(Attachment.Charging)
makeStatusCureAttackMove 'SmellingSalt', Status.Paralyze
extendMove 'Snore', ->
@usableWhileAsleep = true
oldUse = @use
@use = (battle, user, target) ->
if !user.has(Status.Sleep)
@fail(battle, user)
return false
else
oldUse.call(this, battle, user, target)
extendMove 'Soak', ->
@afterSuccessfulHit = (battle, user, target) ->
if (target.types.length == 1 && target.types[0] == 'Water') || target.name == 'Arceus'
@fail(battle, user)
else
target.types = [ 'Water' ]
battle.cannedText('TRANSFORM_TYPE', target, 'Water')
extendMove 'SonicBoom', ->
@calculateDamage = (battle, user, target) ->
20
makeOpponentFieldMove 'Spikes', (battle, user, opponentId) ->
team = battle.getTeam(opponentId)
if !team.attach(Attachment.Spikes)
@fail(battle, user)
extendMove 'Spite', ->
@execute = (battle, user, opponents) ->
for opponent in opponents
move = opponent.lastMove
if !move || !opponent.knows(move) || opponent.pp(move) == 0
@fail(battle, user)
return
opponent.reducePP(move, 4)
battle.message "It reduced the PP of #{opponent.name}!"
makeOpponentFieldMove 'Stealth Rock', (battle, user, opponentId) ->
team = battle.getTeam(opponentId)
if user.hasAbility('Foundry')
if !team.attach(Attachment.FireRock)
@fail(battle, user)
else
if !team.attach(Attachment.StealthRock)
@fail(battle, user)
extendMove 'Struggle', ->
@type = '???'
@typeEffectiveness = -> 1
@afterSuccessfulHit = (battle, user, target) ->
user.damage(user.stat('hp') >> 2, source: "move")
extendMove 'Splash', ->
@execute = (battle, user, target) ->
battle.message "But nothing happened!"
extendMove 'Substitute', ->
@execute = (battle, user, targets) ->
dmg = user.stat('hp') >> 2
if dmg >= user.currentHP || dmg == 0
battle.cannedText('SUBSTITUTE_WEAK')
@fail(battle, user)
return
if user.has(Attachment.Substitute)
battle.cannedText('SUBSTITUTE_EXISTS', user)
@fail(battle, user)
return
user.damage(dmg, source: "move")
user.attach(Attachment.Substitute, hp: dmg, battle: battle)
battle.cannedText('SUBSTITUTE_START', user)
@fail = (battle) ->
extendMove 'Sucker Punch', ->
oldUse = @use
@use = (battle, user, target) ->
if !battle.willMove(target) || battle.peekMove(target).isNonDamaging()
@fail(battle, user)
return false
else
oldUse.call(this, battle, user, target)
extendMove 'Swagger', ->
@afterSuccessfulHit = (battle, user, target) ->
target.attach(Attachment.Confusion, {battle})
target.boost(attack: 2, user)
extendMove 'Synchronoise', ->
oldUse = @use
@use = (battle, user, target) ->
if _.every(user.types, (type) -> type not in target.types)
@fail(battle, user)
return false
return oldUse.call(this, battle, user, target)
extendMove 'Tailwind', ->
@execute = (battle, user, targets) ->
team = user.team
if team.has(Attachment.Tailwind)
@fail(battle, user)
return false
team.attach(Attachment.Tailwind)
battle.message "A tailwind started blowing!"
extendMove 'Taunt', ->
@afterSuccessfulHit = (battle, user, target) ->
if !target.attach(Attachment.Taunt, battle)
@fail(battle, user)
extendMove 'Techno Blast', ->
@getType = (battle, user, target) ->
switch user.getItem()?.displayName
when "Burn Drive"
"Fire"
when "Chill Drive"
"Ice"
when "Douse Drive"
"Water"
when "Shock Drive"
"Electric"
else
"Normal"
makeOpponentFieldMove 'Toxic Spikes', (battle, user, opponentId) ->
team = battle.getTeam(opponentId)
if !team.attach(Attachment.ToxicSpikes)
@fail(battle, user)
extendMove 'Transform', ->
@afterSuccessfulHit = (battle, user, target) ->
if !user.attach(Attachment.Transform, {target})
@fail(battle, user)
return false
battle.cannedText('TRANSFORM_INTO', user, target)
extendMove 'Trick Room', ->
@execute = (battle, user, targets) ->
if battle.attach(Attachment.TrickRoom)
battle.cannedText('TRICK_ROOM_START', user)
else
battle.unattach(Attachment.TrickRoom)
extendMove 'Trump Card', ->
@basePower = (battle, user, target) ->
switch user.pp(this)
when 3
50
when 2
60
when 1
80
when 0
200
else
40
makeSwitchMove = (moveName) ->
extendMove moveName, ->
@afterSuccessfulHit = (battle, user, target) ->
battle.forceSwitch(user)
makeSwitchMove 'U-turn'
extendMove 'Venoshock', ->
@basePower = (battle, user, target) ->
if target.has(Status.Toxic) || target.has(Status.Poison)
2 * @power
else
@power
makeSwitchMove 'Volt Switch'
makeStatusCureAttackMove 'Wake-Up Slap', Status.Sleep
extendMove 'Weather Ball', ->
@getType = (battle, user, target) ->
if battle.hasWeather(Weather.SUN) then 'Fire'
else if battle.hasWeather(Weather.RAIN) then 'Water'
else if battle.hasWeather(Weather.HAIL) then 'Ice'
else if battle.hasWeather(Weather.SAND) then 'Rock'
else 'Normal'
@basePower = (battle, user, target) ->
if battle.hasWeather(Weather.NONE) then 50 else 100
extendMove 'Wish', ->
@hit = (battle, user) ->
@fail(battle, user) unless user.team.attach(Attachment.Wish, {user})
extendMove 'Worry Seed', ->
bannedAbilities =
"Insomnia": true
"Truant": true
@afterSuccessfulHit = (battle, user, target) ->
if target.hasChangeableAbility() && target.ability.displayName not of bannedAbilities
target.copyAbility(Ability.Insomnia)
battle.cannedText('ACQUIRE_ABILITY', target, 'Insomnia')
else
@fail(battle, user)
extendMove 'Wring Out', ->
@basePower = (battle, user, target) ->
power = Math.floor(120 * user.currentHP / user.stat('hp'))
Math.max(1, power)
# Keep this at the bottom or look up how it affects Metronome.
# TODO: Figure out a better solution
Moves['Confusion Recoil'] = new Move "Confusion recoil",
"accuracy": 0,
"damage": "physical",
"power": 40,
"priority": 0,
"type": "???"
# Confusion never crits
extendMove 'Confusion Recoil', ->
@isCriticalHit = -> false
Moves['Recharge'] = new Move("Recharge", target: "user")
# After everything to ensure that basePower is overridden last.
makeVulnerable = (moveName, byMove) ->
extendMove byMove, ->
oldBasePower = @basePower
@basePower = (battle, user, target) ->
power = oldBasePower.call(this, battle, user, target)
charging = target.get(Attachment.Charging)
return power if !charging?
if charging.move == battle.getMove(moveName) then 2 * power else power
makeVulnerable('Fly', 'Gust')
makeVulnerable('Fly', 'Twister')
makeVulnerable('Bounce', 'Gust')
makeVulnerable('Bounce', 'Twister')
makeVulnerable('Dig', 'Earthquake')
makeVulnerable('Dig', 'Magnitude')
makeVulnerable('Dive', 'Surf')
makeVulnerable('Dive', 'Whirlpool')
extendMove 'Hone Claws', ->
@use = (battle, user, target) ->
boost = if battle.hasWeather(Weather.MOON) then 2 else 1
user.boost(attack: boost, accuracy: boost)