1969 lines
58 KiB
CoffeeScript
1969 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()
|
|
2 * @power
|
|
else if user.hasItem("Flying Gem")
|
|
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)
|