1581 lines
43 KiB
CoffeeScript
1581 lines
43 KiB
CoffeeScript
{Protocol} = require '../../shared/protocol'
|
|
{Weather} = require '../../shared/weather'
|
|
util = require './util'
|
|
Query = require './queries'
|
|
{_} = require 'underscore'
|
|
|
|
@Attachment = Attachment = {}
|
|
@Status = Status = {}
|
|
|
|
class @Attachments
|
|
constructor: ->
|
|
@attachments = []
|
|
|
|
push: (attachmentClass, options={}, attributes={}) ->
|
|
throw new Error("Passed a non-existent Attachment.") if !attachmentClass?
|
|
return null if Query.untilFalse('shouldAttach', @all(), attachmentClass, options) == false
|
|
return null if attachmentClass.preattach?(options, attributes) == false
|
|
attachment = @get(attachmentClass)
|
|
if !attachment?
|
|
attachment = new attachmentClass()
|
|
for attribute, value of attributes
|
|
attachment[attribute] = value
|
|
@attachments.push(attachment)
|
|
attachment.initialize?(options)
|
|
|
|
return null if attachment.layers == attachment.maxLayers
|
|
attachment.layers++
|
|
return attachment
|
|
|
|
unattach: (klass) ->
|
|
index = @indexOf(klass)
|
|
if index >= 0
|
|
attachment = @attachments.splice(index, 1)[0]
|
|
attachment.unattach?()
|
|
attachment.attached = false
|
|
attachment
|
|
|
|
unattachAll: (condition) ->
|
|
condition ||= -> true
|
|
i = 0
|
|
while i < @attachments.length
|
|
attachment = @attachments[i]
|
|
if condition(attachment)
|
|
attachment.unattach?()
|
|
@attachments.splice(i, 1)
|
|
else
|
|
i++
|
|
|
|
# Returns a list of attachments that can be passed to another Pokemon.
|
|
getPassable: ->
|
|
passable = @attachments.filter((attachment) -> attachment.passable)
|
|
passable.map((a) -> a.constructor)
|
|
|
|
indexOf: (attachment) ->
|
|
@attachments.map((a) -> a.constructor).indexOf(attachment)
|
|
|
|
get: (attachment) ->
|
|
@attachments[@indexOf(attachment)]
|
|
|
|
contains: (attachment) ->
|
|
@indexOf(attachment) != -1
|
|
|
|
all: ->
|
|
_.clone(@attachments)
|
|
|
|
size: ->
|
|
@attachments.length
|
|
|
|
# Attachments represents a pokemon's state. Some examples are
|
|
# status effects, entry hazards, and fire spin's trapping effect.
|
|
# Attachments are "attached" with Pokemon.attach(), and after
|
|
# that the attachment can be retrieved with Attachment.pokemon
|
|
class @BaseAttachment
|
|
name: "BaseAttachment"
|
|
|
|
maxLayers: 1
|
|
|
|
constructor: ->
|
|
@layers = 0
|
|
@attached = true
|
|
|
|
valid: ->
|
|
return false if !@attached
|
|
return false if @battle?.isOver()
|
|
return false if @item && @pokemon?.item && @pokemon?.isItemBlocked()
|
|
return false if @ability && @pokemon?.isAbilityBlocked()
|
|
return false if @isAliveCheck() == false
|
|
return true
|
|
|
|
isAliveCheck: ->
|
|
@pokemon && @pokemon.isAlive()
|
|
|
|
# initialize: ->
|
|
# unattach: ->
|
|
# calculateWeight: (weight) -> weight
|
|
# afterBeingHit: (move, user, target, damage, isDirect) ->
|
|
# afterSuccessfulHit: (move, user, target, damage) ->
|
|
# beforeMove: (move, user, targets) ->
|
|
# isImmune: (type) ->
|
|
# switchOut: ->
|
|
# switchIn: ->
|
|
# beginTurn: ->
|
|
# endTurn: ->
|
|
# update: (owner) ->
|
|
# editBoosts: (stages) ->
|
|
# afterFaint: ->
|
|
# shouldBlockExecution: (move, user) ->
|
|
|
|
# Pokemon-specific attachments
|
|
# TODO: Turn Attachment into abstract class
|
|
# TODO: Move into own PokemonAttachment
|
|
# editHp: (stat) -> stat
|
|
# editAttack: (stat) -> stat
|
|
# editSpeed: (stat) -> stat
|
|
# editSpecialAttack: (stat) -> stat
|
|
# editDefense: (stat) -> stat
|
|
# editSpecialDefense: (stat) -> stat
|
|
|
|
# Used for effects like Tailwind or Reflect.
|
|
class @TeamAttachment extends @BaseAttachment
|
|
name: "TeamAttachment"
|
|
|
|
# Used for effects like Trick Room or Magic Room.
|
|
class @BattleAttachment extends @BaseAttachment
|
|
name: "BattleAttachment"
|
|
|
|
# An attachment that removes itself when a pokemon
|
|
# deactivates.
|
|
class @VolatileAttachment extends @BaseAttachment
|
|
name: "VolatileAttachment"
|
|
volatile: true
|
|
|
|
class @Attachment.Flinch extends @VolatileAttachment
|
|
name: "FlinchAttachment"
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
@battle.cannedText('FLINCH', @pokemon)
|
|
@pokemon.boost(speed: 1) if @pokemon.hasAbility("Steadfast")
|
|
false
|
|
|
|
endTurn: ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Confusion extends @VolatileAttachment
|
|
name: "ConfusionAttachment"
|
|
passable: true
|
|
|
|
initialize: (attributes = {}) ->
|
|
cannedText = attributes.cannedText ? 'CONFUSION_START'
|
|
@turns = @battle?.rng.randInt(1, 4, "confusion turns") || 1
|
|
@pokemon?.tell(Protocol.POKEMON_ATTACH, @name)
|
|
@battle?.cannedText(cannedText, @pokemon)
|
|
@turn = 0
|
|
|
|
@preattach: (options, attributes) ->
|
|
{pokemon} = attributes
|
|
{source} = options
|
|
return false if (pokemon.team?.has(Attachment.Safeguard) && source != pokemon)
|
|
|
|
unattach: ->
|
|
@pokemon?.tell(Protocol.POKEMON_UNATTACH, @name)
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
@battle.cannedText('IS_CONFUSED', @pokemon)
|
|
@turn++
|
|
if @turn > @turns
|
|
@battle.cannedText('CONFUSION_END', @pokemon)
|
|
@pokemon.unattach(@constructor)
|
|
else if @battle.rng.next('confusion') < 0.5
|
|
@battle.cannedText('CONFUSION_HURT_SELF', @pokemon)
|
|
damage = @battle.confusionMove.calculateDamage(@battle, user, user)
|
|
user.damage(damage, source: "move")
|
|
return false
|
|
|
|
class @Attachment.Disable extends @VolatileAttachment
|
|
name: "DisableAttachment"
|
|
|
|
@preattach: (options, attributes) ->
|
|
{pokemon} = attributes
|
|
move = options.move ? pokemon.lastMove
|
|
return false if !move? || !pokemon.knows(move) || pokemon.pp(move) <= 0
|
|
|
|
initialize: (attributes = {}) ->
|
|
@blockedMove = attributes.move ? @pokemon.lastMove
|
|
@turns = 4
|
|
@battle.cannedText('DISABLE_START', @pokemon, @blockedMove.name)
|
|
@pokemon.blockMove(@blockedMove)
|
|
|
|
beginTurn: ->
|
|
@pokemon.blockMove(@blockedMove)
|
|
|
|
beforeMove: (move, user, target) ->
|
|
if move == @blockedMove
|
|
@battle.cannedText('DISABLE_CONTINUE', @pokemon, move.name)
|
|
return false
|
|
|
|
endTurn: ->
|
|
@turns--
|
|
if @turns == 0
|
|
@battle.cannedText('DISABLE_END', @pokemon)
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Yawn extends @VolatileAttachment
|
|
name: 'YawnAttachment'
|
|
|
|
@preattach: (options, attributes) ->
|
|
{pokemon} = attributes
|
|
return false if pokemon.hasStatus()
|
|
return false if pokemon.team.has(Attachment.Safeguard)
|
|
|
|
initialize: (attributes = {}) ->
|
|
{@source} = attributes
|
|
@turn = 0
|
|
@battle.cannedText('YAWN_BEGIN', @pokemon)
|
|
|
|
endTurn: ->
|
|
@turn += 1
|
|
if @turn == 2
|
|
@pokemon.attach(Status.Sleep, {@source, bypassSafeguard: true})
|
|
@pokemon.unattach(@constructor)
|
|
|
|
# TODO: Does weight get lowered if speed does not change?
|
|
class @Attachment.Autotomize extends @VolatileAttachment
|
|
name: "AutotomizeAttachment"
|
|
|
|
maxLayers: -1
|
|
|
|
calculateWeight: (weight) ->
|
|
Math.max(weight - 100 * @layers, .1)
|
|
|
|
class @Attachment.Nightmare extends @VolatileAttachment
|
|
name: "NightmareAttachment"
|
|
|
|
@preattach: (options, attributes) ->
|
|
{pokemon} = attributes
|
|
return false if !pokemon.hasStatus(Status.Sleep)
|
|
|
|
initialize: ->
|
|
@battle.message("#{@pokemon.name} began having a nightmare!")
|
|
|
|
endTurn: ->
|
|
if @pokemon.has(Status.Sleep)
|
|
amount = Math.floor(@pokemon.stat('hp') / 4)
|
|
if @pokemon.damage(amount)
|
|
@battle.message "#{@pokemon.name} is locked in a nightmare!"
|
|
else
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Safeguard extends @TeamAttachment
|
|
name: "SafeguardAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@source} = attributes
|
|
@turns = 5
|
|
@turn = 0
|
|
|
|
endTurn: ->
|
|
@turn++
|
|
if @turn >= @turns
|
|
@battle.cannedText('SAFEGUARD_END', @source)
|
|
@team.unattach(@constructor)
|
|
|
|
class @Attachment.Taunt extends @VolatileAttachment
|
|
name: "TauntAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
@turns = 4
|
|
@turns = 3 if @battle.willMove(@pokemon)
|
|
@turn = 0
|
|
@battle.cannedText('TAUNT_START', @pokemon)
|
|
|
|
beginTurn: ->
|
|
for move in @pokemon.moves
|
|
if move.power == 0
|
|
@pokemon.blockMove(move)
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
# TODO: user is always == pokemon. Will this change?
|
|
if user == @pokemon && move.power == 0
|
|
@battle.cannedText('TAUNT_PREVENT', @pokemon, move.name)
|
|
return false
|
|
|
|
endTurn: ->
|
|
@turn++
|
|
if @turn >= @turns
|
|
@battle.cannedText('TAUNT_END', @pokemon)
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.HealBlock extends @VolatileAttachment
|
|
name: "HealBlockAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
@turns = 5
|
|
@turn = 0
|
|
@battle.cannedText('HEAL_BLOCK_START', @pokemon)
|
|
|
|
beginTurn: ->
|
|
for move in @pokemon.moves
|
|
if move.hasFlag("heal")
|
|
@pokemon.blockMove(move)
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
# TODO: user is always == pokemon. Will this change?
|
|
if user == @pokemon && move.hasFlag("heal")
|
|
@battle.cannedText('HEAL_BLOCK_PREVENT', @pokemon, move.name)
|
|
return false
|
|
|
|
endTurn: ->
|
|
@turn++
|
|
if @turn >= @turns
|
|
@battle.cannedText('HEAL_BLOCK_END', @pokemon)
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Tailwind extends @TeamAttachment
|
|
name: "TailwindAttachment"
|
|
|
|
initialize: ->
|
|
@turns = 4
|
|
@turn = 0
|
|
|
|
editSpeed: (speed) ->
|
|
2 * speed
|
|
|
|
endTurn: ->
|
|
@turn++
|
|
if @turn >= @turns
|
|
@battle.cannedText('TAILWIND_END')
|
|
@team.unattach(@constructor)
|
|
|
|
class @Attachment.Wish extends @TeamAttachment
|
|
name: "WishAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{user} = attributes
|
|
@amount = Math.round(user.stat('hp') / 2)
|
|
@wisherName = user.name
|
|
@slot = @team.indexOf(user)
|
|
@turns = 2
|
|
@turn = 0
|
|
|
|
endTurn: ->
|
|
@turn++
|
|
if @turn >= @turns
|
|
pokemon = @team.at(@slot)
|
|
if !pokemon.isFainted()
|
|
@battle.cannedText('WISH_END', @wisherName)
|
|
pokemon.heal(@amount)
|
|
@team.unattach(@constructor)
|
|
|
|
class @Attachment.PerishSong extends @VolatileAttachment
|
|
name: "PerishSongAttachment"
|
|
passable: true
|
|
|
|
initialize: ->
|
|
@turns = 4
|
|
@turn = 0
|
|
@battle.cannedText('PERISH_SONG_START')
|
|
|
|
endTurn: ->
|
|
@turn++
|
|
@battle.cannedText('PERISH_SONG_CONTINUE', @pokemon, @turns - @turn)
|
|
if @turn >= @turns
|
|
@pokemon.faint()
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Roost extends @VolatileAttachment
|
|
name: "RoostAttachment"
|
|
|
|
initialize: ->
|
|
@oldTypes = @pokemon.types
|
|
@pokemon.types = (type for type in @pokemon.types when type != 'Flying')
|
|
if @pokemon.types.length == 0 then @pokemon.types = [ 'Normal' ]
|
|
|
|
endTurn: ->
|
|
@pokemon.types = @oldTypes
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Encore extends @VolatileAttachment
|
|
name: "EncoreAttachment"
|
|
|
|
initialize: ->
|
|
@turns = 4
|
|
@turns = 3 if @battle.willMove(@pokemon)
|
|
@turn = 0
|
|
@move = @pokemon.lastMove
|
|
|
|
beginTurn: ->
|
|
@pokemon.lockMove(@move)
|
|
|
|
endTurn: ->
|
|
@turn++
|
|
if @turn >= @turns || @pokemon.pp(@move) == 0
|
|
@battle.cannedText('ENCORE_END', @pokemon)
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Torment extends @VolatileAttachment
|
|
name: "TormentAttachment"
|
|
|
|
initialize: ->
|
|
@battle.cannedText('TORMENT_START', @pokemon)
|
|
|
|
beginTurn: ->
|
|
@pokemon.blockMove(@pokemon.lastMove) if @pokemon.lastMove?
|
|
|
|
class @Attachment.Spikes extends @TeamAttachment
|
|
name: "SpikesAttachment"
|
|
|
|
maxLayers: 3
|
|
|
|
initialize: ->
|
|
id = @team.playerId
|
|
@battle.cannedText('SPIKES_START', @battle.getPlayerIndex(id))
|
|
|
|
switchIn: (pokemon) ->
|
|
return if pokemon.isImmune("Ground")
|
|
fraction = (10 - 2 * @layers)
|
|
hp = pokemon.stat('hp')
|
|
damage = Math.floor(hp / fraction)
|
|
if pokemon.damage(damage)
|
|
@battle.cannedText('SPIKES_HURT', pokemon)
|
|
|
|
unattach: ->
|
|
id = @team.playerId
|
|
@battle.cannedText('SPIKES_END', @battle.getPlayerIndex(id))
|
|
|
|
class @Attachment.StealthRock extends @TeamAttachment
|
|
name: "StealthRockAttachment"
|
|
|
|
initialize: ->
|
|
id = @team.playerId
|
|
@battle.cannedText('STEALTH_ROCK_START', @battle.getPlayerIndex(id))
|
|
|
|
switchIn: (pokemon) ->
|
|
multiplier = util.typeEffectiveness("Rock", pokemon.types)
|
|
hp = pokemon.stat('hp')
|
|
damage = ((hp * multiplier) >> 3)
|
|
if pokemon.damage(damage)
|
|
@battle.cannedText('STEALTH_ROCK_HURT', pokemon)
|
|
|
|
unattach: ->
|
|
id = @team.playerId
|
|
@battle.cannedText('STEALTH_ROCK_END', @battle.getPlayerIndex(id))
|
|
|
|
class @Attachment.ToxicSpikes extends @TeamAttachment
|
|
name: "ToxicSpikesAttachment"
|
|
|
|
maxLayers: 2
|
|
|
|
initialize: ->
|
|
id = @team.playerId
|
|
@battle.cannedText('TOXIC_SPIKES_START', @battle.getPlayerIndex(id))
|
|
|
|
switchIn: (pokemon) ->
|
|
if pokemon.hasType("Poison") && !pokemon.isImmune("Ground")
|
|
@team.unattach(@constructor)
|
|
|
|
return if pokemon.isImmune("Poison") || pokemon.isImmune("Ground")
|
|
|
|
if @layers == 1
|
|
pokemon.attach(Status.Poison)
|
|
else
|
|
pokemon.attach(Status.Toxic)
|
|
|
|
unattach: ->
|
|
id = @team.playerId
|
|
@battle.cannedText('TOXIC_SPIKES_END', @battle.getPlayerIndex(id))
|
|
|
|
# A trap created by Fire Spin, Magma Storm, Bind, Clamp, etc
|
|
class @Attachment.Trap extends @VolatileAttachment
|
|
name: "TrapAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@moveName, @user, @turns} = attributes
|
|
@user.attach(Attachment.TrapLeash, target: @pokemon)
|
|
|
|
beginTurn: ->
|
|
@pokemon.blockSwitch()
|
|
|
|
endTurn: ->
|
|
# For the first numTurns turns it will damage, and at numTurns + 1 it will wear off.
|
|
# Therefore, if @turns = 5, this attachment should actually last for 6 turns.
|
|
if @turns == 0
|
|
@pokemon.unattach(@constructor)
|
|
else
|
|
amount = Math.floor(@pokemon.stat('hp') / @getDamagePerTurn())
|
|
if @pokemon.damage(amount)
|
|
@battle.cannedText('TRAP_HURT', @pokemon, @moveName)
|
|
@turns -= 1
|
|
|
|
getDamagePerTurn: ->
|
|
if @user.hasItem("Binding Band")
|
|
8
|
|
else
|
|
16
|
|
|
|
unattach: ->
|
|
@battle.cannedText('FREE_FROM', @pokemon, @moveName)
|
|
@user.unattach(Attachment.TrapLeash)
|
|
delete @user
|
|
|
|
# If the creator if fire spin switches out, the trap will end
|
|
# TODO: What happens if another ability removes the trap, and then firespin is used again?
|
|
class @Attachment.TrapLeash extends @VolatileAttachment
|
|
name: "TrapLeashAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@target} = attributes
|
|
|
|
unattach: ->
|
|
@target.unattach(Attachment.Trap)
|
|
delete @target
|
|
|
|
# Has a 50% chance to immobilize a Pokemon before it moves.
|
|
class @Attachment.Attract extends @VolatileAttachment
|
|
name: "AttractAttachment"
|
|
|
|
@preattach: (options, attributes) ->
|
|
{source} = options
|
|
{pokemon} = attributes
|
|
return false if (!(pokemon.gender == 'M' && source.gender == 'F') &&
|
|
!(pokemon.gender == 'F' && source.gender == 'M'))
|
|
|
|
initialize: (attributes) ->
|
|
{@source} = attributes
|
|
if @pokemon.hasItem("Destiny Knot") && !@source.has(Attachment.Attract)
|
|
@pokemon.removeItem()
|
|
@source.attach(Attachment.Attract, source: @pokemon)
|
|
@battle.message("#{@pokemon.name} fell in love with #{@source.name}!")
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
if @source not in @battle.getOpponents(@pokemon)
|
|
@pokemon.unattach(@constructor)
|
|
return
|
|
if @battle.rng.next('attract chance') < .5
|
|
@battle.message "#{@pokemon.name} is immobilized by love!"
|
|
return false
|
|
|
|
class @Attachment.FocusEnergy extends @VolatileAttachment
|
|
name: "FocusEnergyAttachment"
|
|
passable: true
|
|
|
|
class @Attachment.MicleBerry extends @VolatileAttachment
|
|
name: "MicleBerryAttachment"
|
|
|
|
initialize: ->
|
|
@turns = 1
|
|
|
|
editAccuracy: (accuracy) ->
|
|
Math.floor(accuracy * 1.2)
|
|
|
|
endTurn: ->
|
|
if @turns == 0
|
|
@pokemon.unattach(@constructor)
|
|
else
|
|
@turns--
|
|
|
|
class @Attachment.Metronome extends @VolatileAttachment
|
|
name: "MetronomeAttachment"
|
|
|
|
maxLayers: 5
|
|
|
|
initialize: (attributes) ->
|
|
{@move} = attributes
|
|
|
|
beforeMove: (move) ->
|
|
@pokemon.unattach(@constructor) if move != @move
|
|
|
|
class @Attachment.Screen extends @TeamAttachment
|
|
name: "ScreenAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{user} = attributes
|
|
@turns = (if user?.hasItem("Light Clay") then 8 else 5)
|
|
|
|
endTurn: ->
|
|
@turns--
|
|
if @turns == 0
|
|
@team.unattach(@constructor)
|
|
|
|
unattach: ->
|
|
@team.tell(Protocol.TEAM_UNATTACH, @name)
|
|
|
|
class @Attachment.Reflect extends @Attachment.Screen
|
|
name: "ReflectAttachment"
|
|
|
|
modifyDamageTarget: (move, user) ->
|
|
if move.isPhysical() && !user.crit && !user.hasAbility("Infiltrator")
|
|
return 0x800
|
|
return 0x1000
|
|
|
|
class @Attachment.LightScreen extends @Attachment.Screen
|
|
name: "LightScreenAttachment"
|
|
|
|
modifyDamageTarget: (move, user) ->
|
|
if move.isSpecial() && !user.crit && !user.hasAbility("Infiltrator")
|
|
return 0x800
|
|
return 0x1000
|
|
|
|
class @Attachment.Identify extends @VolatileAttachment
|
|
name: "IdentifyAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@types} = attributes
|
|
|
|
editBoosts: (stages) ->
|
|
stages.evasion = 0
|
|
stages
|
|
|
|
isImmune: (type) ->
|
|
return false if type in @types
|
|
|
|
class @Attachment.DefenseCurl extends @VolatileAttachment
|
|
name: "DefenseCurl"
|
|
|
|
class @Attachment.FocusPunch extends @VolatileAttachment
|
|
name: "FocusPunchAttachment"
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
hit = user.lastHitBy
|
|
return if !hit?
|
|
if !hit.move.isNonDamaging() && hit.turn == @battle.turn && hit.direct
|
|
@battle.message "#{user.name} lost its focus and couldn't move!"
|
|
return false
|
|
|
|
afterMove: ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.MagnetRise extends @VolatileAttachment
|
|
name: "MagnetRiseAttachment"
|
|
passable: true
|
|
|
|
initialize: ->
|
|
@turns = 5
|
|
|
|
isImmune: (type) ->
|
|
return true if type == "Ground"
|
|
|
|
endTurn: ->
|
|
@turns -= 1
|
|
@pokemon.unattach(@constructor) if @turns == 0
|
|
|
|
class @Attachment.LockOn extends @VolatileAttachment
|
|
name: "LockOnAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@target} = attributes
|
|
@turns = 2
|
|
|
|
# Effect hardcoded in Move#willMiss
|
|
|
|
endTurn: ->
|
|
@turns -= 1
|
|
@pokemon.unattach(@constructor) if @turns == 0
|
|
|
|
class @Attachment.Minimize extends @VolatileAttachment
|
|
name: "MinimizeAttachment"
|
|
|
|
class @Attachment.MeanLook extends @VolatileAttachment
|
|
name: "MeanLookAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@user} = attributes
|
|
@user.attach(Attachment.MeanLookLeash, target: @pokemon)
|
|
|
|
beginTurn: ->
|
|
@pokemon.blockSwitch()
|
|
|
|
unattach: ->
|
|
@user.unattach(Attachment.MeanLookLeash)
|
|
delete @user
|
|
|
|
class @Attachment.MeanLookLeash extends @VolatileAttachment
|
|
name: "MeanLookAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@target} = attributes
|
|
|
|
unattach: ->
|
|
@target.unattach(Attachment.MeanLook)
|
|
delete @target
|
|
|
|
class @Attachment.Recharge extends @VolatileAttachment
|
|
name: "RechargeAttachment"
|
|
|
|
initialize: ->
|
|
@turns = 2
|
|
|
|
beginTurn: ->
|
|
@pokemon.blockSwitch()
|
|
@pokemon.blockMoves()
|
|
id = @battle.getOwner(@pokemon)
|
|
@battle.recordMove(id, @battle.getMove("Recharge"))
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
@battle.message "#{user.name} must recharge!"
|
|
return false
|
|
|
|
endTurn: ->
|
|
@turns -= 1
|
|
@pokemon.unattach(@constructor) if @turns == 0
|
|
|
|
class @Attachment.Momentum extends @VolatileAttachment
|
|
name: "MomentumAttachment"
|
|
|
|
maxLayers: 5
|
|
|
|
initialize: (attributes) ->
|
|
{@move} = attributes
|
|
@turns = 1
|
|
|
|
beginTurn: ->
|
|
@pokemon.blockSwitch()
|
|
@pokemon.lockMove(@move)
|
|
|
|
endTurn: ->
|
|
@turns -= 1
|
|
@pokemon.unattach(@constructor) if @turns == 0 || @layers == @maxLayers
|
|
|
|
class @Attachment.MeFirst extends @VolatileAttachment
|
|
name: "MeFirstAttachment"
|
|
|
|
modifyAttack: ->
|
|
0x1800
|
|
|
|
endTurn: ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Charge extends @VolatileAttachment
|
|
name: "ChargeAttachment"
|
|
|
|
initialize: ->
|
|
@turns = 2
|
|
|
|
modifyAttack: (move, target) ->
|
|
return 0x2000 if move.getType(@battle, @pokemon, target) == 'Electric'
|
|
return 0x1000
|
|
|
|
endTurn: ->
|
|
@turns -= 1
|
|
@pokemon.unattach(@constructor) if @turns == 0
|
|
|
|
class @Attachment.LeechSeed extends @VolatileAttachment
|
|
name: "LeechSeedAttachment"
|
|
passable: true
|
|
|
|
initialize: (attributes) ->
|
|
{@source} = attributes
|
|
@slot = @source.team.indexOf(@source)
|
|
@battle.cannedText('LEECH_SEED_START', @pokemon)
|
|
|
|
endTurn: ->
|
|
user = @source.team.at(@slot)
|
|
return if user.isFainted() || @pokemon.isFainted()
|
|
hp = @pokemon.stat('hp')
|
|
damage = Math.min(Math.floor(hp / 8), @pokemon.currentHP)
|
|
if @pokemon.damage(damage)
|
|
user.drain(damage, @pokemon)
|
|
@battle.cannedText('LEECH_SEED_HURT', @pokemon)
|
|
|
|
unattach: ->
|
|
@battle.cannedText('FREE_FROM', @pokemon, "Leech Seed")
|
|
|
|
class @Attachment.ProtectCounter extends @VolatileAttachment
|
|
name: "ProtectCounterAttachment"
|
|
|
|
maxLayers: -1
|
|
|
|
successMultiplier: 2
|
|
|
|
successChance: ->
|
|
x = Math.pow(@successMultiplier, @layers - 1)
|
|
if @layers > 8 then Math.pow(2, 32) else x
|
|
|
|
endTurn: ->
|
|
@turns--
|
|
@pokemon.unattach(@constructor) if @turns == 0
|
|
|
|
class @Attachment.Protect extends @VolatileAttachment
|
|
name: "ProtectAttachment"
|
|
|
|
initialize: ->
|
|
@pokemon.tell(Protocol.POKEMON_ATTACH, @name)
|
|
|
|
shouldBlockExecution: (move, user) ->
|
|
return true if move.hasFlag("protect")
|
|
|
|
endTurn: ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
unattach: ->
|
|
@pokemon.tell(Protocol.POKEMON_UNATTACH, @name)
|
|
|
|
class @Attachment.Endure extends @VolatileAttachment
|
|
name: "EndureAttachment"
|
|
|
|
endTurn: ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
transformHealthChange: (amount, options) ->
|
|
if amount >= @pokemon.currentHP
|
|
@battle.message "#{@pokemon.name} endured the hit!"
|
|
return @pokemon.currentHP - 1
|
|
return amount
|
|
|
|
class @Attachment.Curse extends @VolatileAttachment
|
|
name: "CurseAttachment"
|
|
passable: true
|
|
|
|
endTurn: ->
|
|
amount = Math.floor(@pokemon.stat('hp') / 4)
|
|
if @pokemon.damage(amount)
|
|
@battle.message "#{@pokemon.name} was afflicted by the curse!"
|
|
|
|
class @Attachment.DestinyBond extends @VolatileAttachment
|
|
name: "DestinyBondAttachment"
|
|
|
|
isAliveCheck: -> true
|
|
|
|
initialize: ->
|
|
@battle.cannedText('DESTINY_BOND_START', @pokemon)
|
|
|
|
afterFaint: ->
|
|
pokemon = @battle.currentPokemon
|
|
if pokemon? && pokemon.isAlive()
|
|
pokemon.faint()
|
|
@battle.cannedText('DESTINY_BOND_CONTINUE', @pokemon)
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Grudge extends @VolatileAttachment
|
|
name: "GrudgeAttachment"
|
|
|
|
isAliveCheck: -> true
|
|
|
|
afterFaint: ->
|
|
hit = @pokemon.lastHitBy
|
|
return if !hit
|
|
{team, slot, move, turn} = hit
|
|
pokemon = team.at(slot)
|
|
if pokemon.isAlive() && !move.isNonDamaging()
|
|
pokemon.setPP(move, 0)
|
|
@battle.message "#{pokemon.name}'s #{move.name} lost all its PP due to the grudge!"
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Pursuit extends @VolatileAttachment
|
|
name: "PursuitAttachment"
|
|
|
|
informSwitch: (switcher) ->
|
|
team = switcher.team
|
|
return if team.has(Attachment.BatonPass)
|
|
pursuit = @battle.getMove('Pursuit')
|
|
@battle.cancelAction(@pokemon)
|
|
@pokemon.attach(Attachment.PursuitModifiers)
|
|
# TODO: You will have to record the target for 2v2.
|
|
@battle.performMove(@pokemon, pursuit)
|
|
@pokemon.unattach(Attachment.PursuitModifiers)
|
|
@pokemon.unattach(@constructor)
|
|
|
|
beforeMove: ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
endTurn: ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.PursuitModifiers extends @VolatileAttachment
|
|
name: "PursuitModifiersAttachment"
|
|
|
|
editAccuracy: ->
|
|
0 # Always hits
|
|
|
|
class @Attachment.Substitute extends @VolatileAttachment
|
|
name: "SubstituteAttachment"
|
|
passable: true
|
|
reinitializeOnPass: true
|
|
|
|
initialize: (attributes) ->
|
|
{@hp} = attributes || this
|
|
@pokemon?.tell(Protocol.POKEMON_ATTACH, @name)
|
|
|
|
transformHealthChange: (damage, options = {}) ->
|
|
if options.direct != false
|
|
# Substitute does not trigger on direct damage
|
|
return damage
|
|
|
|
@hp -= damage
|
|
if @hp <= 0
|
|
@battle.cannedText('SUBSTITUTE_END', @pokemon)
|
|
@hp = 0
|
|
else
|
|
@battle.cannedText('SUBSTITUTE_HURT', @pokemon)
|
|
return 0
|
|
|
|
failsOnSub: (move, user) ->
|
|
@pokemon != user && move.isNonDamaging() &&
|
|
!move.isDirectHit(@battle, user, @pokemon)
|
|
|
|
shouldBlockExecution: (move, user) ->
|
|
if @failsOnSub(move, user)
|
|
move.fail(@battle, user)
|
|
return true
|
|
|
|
afterBeingHit: (move, user, target) ->
|
|
@pokemon.unattach(@constructor) if @hp <= 0
|
|
|
|
unattach: ->
|
|
@pokemon.tell(Protocol.POKEMON_UNATTACH, @name)
|
|
|
|
class @Attachment.Stockpile extends @VolatileAttachment
|
|
name: "StockpileAttachment"
|
|
|
|
maxLayers: 3
|
|
|
|
class @Attachment.Rage extends @VolatileAttachment
|
|
name: "RageAttachment"
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
afterBeingHit: (move, user, target) ->
|
|
return if move.isNonDamaging()
|
|
target.boost(attack: 1)
|
|
@battle.message "#{target.name}'s rage is building!"
|
|
|
|
class @Attachment.ChipAway extends @VolatileAttachment
|
|
name: "ChipAwayAttachment"
|
|
|
|
editBoosts: (stages) ->
|
|
stages.evasion = 0
|
|
stages.defense = 0
|
|
stages.specialDefense = 0
|
|
stages
|
|
|
|
class @Attachment.AquaRing extends @VolatileAttachment
|
|
name: "AquaRingAttachment"
|
|
passable: true
|
|
|
|
endTurn: ->
|
|
amount = Math.floor(@pokemon.stat('hp') / 16)
|
|
# Aqua Ring is considered a drain move for the purposes of Big Root.
|
|
@pokemon.drain(amount, @pokemon)
|
|
@battle.message "Aqua Ring restored #{@pokemon.name}'s HP!"
|
|
|
|
class @Attachment.Ingrain extends @VolatileAttachment
|
|
name: "IngrainAttachment"
|
|
passable: true
|
|
|
|
initialize: ->
|
|
@battle.message("#{@pokemon.name} planted its roots!")
|
|
|
|
endTurn: ->
|
|
amount = Math.floor(@pokemon.stat('hp') / 16)
|
|
# Ingrain is considered a drain move for the purposes of Big Root.
|
|
@pokemon.drain(amount, @pokemon)
|
|
@battle.message "#{@pokemon.name} absorbed nutrients with its roots!"
|
|
|
|
beginTurn: ->
|
|
@pokemon.blockSwitch()
|
|
|
|
shouldPhase: (phaser) ->
|
|
@battle.message "#{@pokemon.name} anchored itself with its roots!"
|
|
return false
|
|
|
|
shouldBlockExecution: (move, user) ->
|
|
if move == @battle.getMove("Telekinesis")
|
|
move.fail(@battle, user)
|
|
return true
|
|
|
|
isImmune: (type) ->
|
|
return false if type == 'Ground'
|
|
|
|
class @Attachment.Embargo extends @VolatileAttachment
|
|
name: "EmbargoAttachment"
|
|
passable: true
|
|
|
|
initialize: ->
|
|
@turns = 5
|
|
@pokemon.blockItem()
|
|
@battle.message("#{@pokemon.name} can't use items anymore!")
|
|
|
|
beginTurn: ->
|
|
@pokemon.blockItem()
|
|
|
|
endTurn: ->
|
|
@turns--
|
|
if @turns == 0
|
|
@battle.message "#{@pokemon.name} can use items again!"
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Charging extends @VolatileAttachment
|
|
name: "ChargingAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@message, @vulnerable, @move, @condition} = attributes
|
|
@charging = false
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
if user.hasItem("Power Herb")
|
|
@battle.message "#{user.name} became fully charged due to its Power Herb!"
|
|
@charging = true
|
|
user.removeItem()
|
|
|
|
if @charging || @condition?(@battle, move, user, targets)
|
|
@pokemon.unattach(@constructor)
|
|
return
|
|
|
|
@charging = true
|
|
@battle.message @message.replace("$1", user.name)
|
|
return false
|
|
|
|
beginTurn: ->
|
|
# TODO: Add targets
|
|
id = @battle.getOwner(@pokemon)
|
|
@battle.recordMove(id, @move)
|
|
|
|
editEvasion: (accuracy, move) ->
|
|
return -1 if @vulnerable && @charging &&
|
|
(move not in @vulnerable.map((v) => @battle.getMove(v)))
|
|
return accuracy
|
|
|
|
unattach: ->
|
|
delete @move
|
|
delete @message
|
|
delete @vulnerable
|
|
|
|
class @Attachment.FuryCutter extends @VolatileAttachment
|
|
name: "FuryCutterAttachment"
|
|
|
|
maxLayers: 3
|
|
|
|
initialize: (attributes) ->
|
|
{@move} = attributes
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
@pokemon.unattach(@constructor) if move != @move
|
|
|
|
class @Attachment.Imprison extends @VolatileAttachment
|
|
name: "ImprisonAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@moves} = attributes
|
|
for pokemon in @battle.getOpponents(@pokemon)
|
|
pokemon.attach(Attachment.ImprisonPrevention, {@moves})
|
|
|
|
beginTurn: ->
|
|
for pokemon in @battle.getOpponents(@pokemon)
|
|
pokemon.attach(Attachment.ImprisonPrevention, {@moves})
|
|
|
|
switchOut: ->
|
|
for pokemon in @battle.getOpponents(@pokemon)
|
|
pokemon.unattach(Attachment.ImprisonPrevention)
|
|
|
|
class @Attachment.ImprisonPrevention extends @VolatileAttachment
|
|
name: "ImprisonPreventionAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@moves} = attributes
|
|
|
|
beginTurn: ->
|
|
@pokemon.blockMove(move) for move in @moves
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
if move in @moves
|
|
@battle.message "#{user.name} can't use the sealed #{move.name}!"
|
|
return false
|
|
|
|
class @Attachment.Present extends @VolatileAttachment
|
|
name: "PresentAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@power} = attributes
|
|
|
|
endTurn: ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
# Lucky Chant's CH prevention is inside Move#isCriticalHit.
|
|
class @Attachment.LuckyChant extends @TeamAttachment
|
|
name: "LuckyChantAttachment"
|
|
|
|
initialize: ->
|
|
@turns = 5
|
|
|
|
endTurn: ->
|
|
@turns--
|
|
if @turns == 0
|
|
# TODO: Less hacky?
|
|
id = (id for id in @battle.playerIds when @battle.getTeam(id) == @team)[0]
|
|
@battle.message "#{id}'s team's Lucky Chant wore off!"
|
|
@team.unattach(@constructor)
|
|
|
|
class @Attachment.LunarDance extends @TeamAttachment
|
|
name: "LunarDanceAttachment"
|
|
|
|
switchIn: (pokemon) ->
|
|
@battle.message "#{pokemon.name} became cloaked in mystical moonlight!"
|
|
pokemon.setHP(pokemon.stat('hp'))
|
|
pokemon.cureStatus()
|
|
pokemon.resetAllPP()
|
|
@team.unattach(@constructor)
|
|
|
|
class @Attachment.HealingWish extends @TeamAttachment
|
|
name: "HealingWishAttachment"
|
|
|
|
switchIn: (pokemon) ->
|
|
@battle.message "The healing wish came true for #{pokemon.name}!"
|
|
pokemon.setHP(pokemon.stat('hp'))
|
|
pokemon.cureStatus()
|
|
@team.unattach(@constructor)
|
|
|
|
class @Attachment.MagicCoat extends @VolatileAttachment
|
|
name: "MagicCoatAttachment"
|
|
|
|
initialize: ->
|
|
@bounced = false
|
|
|
|
shouldBlockExecution: (move, user) ->
|
|
return unless move.hasFlag("reflectable")
|
|
return if user.get(Attachment.MagicCoat)?.bounced
|
|
return if @bounced
|
|
@bounced = true
|
|
@battle.cannedText('BOUNCE_MOVE', @pokemon, move.name)
|
|
move.execute(@battle, @pokemon, [ user ])
|
|
return true
|
|
|
|
shouldBlockFieldExecution: (move, userId) ->
|
|
return unless move.hasFlag("reflectable")
|
|
return if @bounced
|
|
team = @battle.getTeam(userId)
|
|
for p in team.getActiveAlivePokemon()
|
|
return if p.get(Attachment.MagicCoat)?.bounced
|
|
for p in @team.getActiveAlivePokemon()
|
|
continue unless p.has(Attachment.MagicCoat)
|
|
@bounced = true
|
|
@battle.cannedText('BOUNCE_MOVE', p, move.name)
|
|
@battle.executeMove(move, p, [ userId ])
|
|
return true
|
|
|
|
endTurn: ->
|
|
if @pokemon?
|
|
@pokemon.unattach(@constructor)
|
|
else if @team?
|
|
@team.unattach(@constructor)
|
|
|
|
class @Attachment.Telekinesis extends @VolatileAttachment
|
|
name: "TelekinesisAttachment"
|
|
|
|
initialize: ->
|
|
@turns = 3
|
|
@battle.cannedText('TELEKINESIS_START', @pokemon)
|
|
|
|
editEvasion: (accuracy, move) ->
|
|
if move.hasFlag("ohko") then accuracy else 0
|
|
|
|
isImmune: (type) ->
|
|
return true if type == 'Ground'
|
|
|
|
endTurn: ->
|
|
@turns--
|
|
if @turns == 0
|
|
@battle.cannedText('TELEKINESIS_END', @pokemon)
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.SmackDown extends @VolatileAttachment
|
|
name: "SmackDownAttachment"
|
|
|
|
isImmune: (type) ->
|
|
return false if type == 'Ground'
|
|
|
|
shouldBlockExecution: (move, user) ->
|
|
if move in [ @battle.getMove("Telekinesis"), @battle.getMove("Magnet Rise") ]
|
|
move.fail(@battle, user)
|
|
return true
|
|
|
|
class @Attachment.EchoedVoice extends @BattleAttachment
|
|
name: "EchoedVoiceAttachment"
|
|
|
|
maxLayers: 4
|
|
|
|
initialize: ->
|
|
@turns = 2
|
|
|
|
endTurn: ->
|
|
@turns--
|
|
@battle.unattach(@constructor) if @turns == 0
|
|
|
|
class @Attachment.Rampage extends @VolatileAttachment
|
|
name: "RampageAttachment"
|
|
|
|
maxLayers: -1
|
|
|
|
initialize: (attributes) ->
|
|
{@move} = attributes
|
|
@turns = @battle.rng.randInt(2, 3, "rampage turns")
|
|
@turn = 0
|
|
|
|
beginTurn: ->
|
|
@pokemon.blockSwitch()
|
|
@pokemon.lockMove(@move)
|
|
|
|
afterMove: ->
|
|
@turn++
|
|
if @turn >= @turns
|
|
@pokemon.attach(Attachment.Confusion, cannedText: 'FATIGUE')
|
|
@pokemon.unattach(@constructor)
|
|
else
|
|
# afterSuccessfulHit increases the number of layers. If the number of
|
|
# layers is not keeping up with the number of turns passed, then the
|
|
# Pokemon's move was interrupted and we should stop rampaging.
|
|
@pokemon.unattach(@constructor) if @turn > @layers
|
|
|
|
# The way Trick Room reverses turn order is implemented in Battle#sortActions.
|
|
class @Attachment.TrickRoom extends @BattleAttachment
|
|
name: "TrickRoomAttachment"
|
|
|
|
initialize: ->
|
|
@turns = 5
|
|
|
|
endTurn: ->
|
|
@turns--
|
|
if @turns == 0
|
|
@battle.unattach(@constructor)
|
|
|
|
unattach: ->
|
|
@battle.cannedText('TRICK_ROOM_END')
|
|
|
|
class @Attachment.Transform extends @VolatileAttachment
|
|
name: "TransformAttachment"
|
|
|
|
@preattach: (attributes) ->
|
|
{target} = attributes
|
|
!target.has(Attachment.Transform)
|
|
|
|
initialize: (attributes) ->
|
|
@pokemon.activateAbility()
|
|
{target} = attributes
|
|
# Save old data
|
|
{@ability, @moves, @stages, @baseStats, @evs} = @pokemon
|
|
{@types, @gender, @weight, @ppHash, @maxPPHash} = @pokemon
|
|
# This data is safe to be copied.
|
|
@pokemon.copyAbility(target.ability, reveal: false)
|
|
@pokemon.gender = target.gender
|
|
@pokemon.weight = target.weight
|
|
# The rest aren't.
|
|
@pokemon.moves = _.clone(target.moves)
|
|
@pokemon.types = _.clone(target.types)
|
|
@pokemon.evs = _.extend({}, target.evs, hp: @pokemon.evs.hp)
|
|
@pokemon.baseStats = _.extend({}, target.baseStats, hp: @pokemon.baseStats.hp)
|
|
@pokemon.setBoosts(target.stages)
|
|
@pokemon.resetAllPP(5)
|
|
# Send updated information to the client
|
|
@pokemon.changeSprite(target.species, target.forme)
|
|
@pokemon.tellPlayer(Protocol.MOVESET_UPDATE, @pokemon.movesetJSON())
|
|
|
|
unattach: ->
|
|
# Restore old data
|
|
@pokemon.ability = @ability
|
|
@pokemon.moves = @moves
|
|
@pokemon.types = @types
|
|
@pokemon.gender = @gender
|
|
@pokemon.weight = @weight
|
|
@pokemon.ppHash = @ppHash
|
|
@pokemon.maxPPHash = @maxPPHash
|
|
@pokemon.baseStats = @baseStats
|
|
@pokemon.evs = @evs
|
|
@pokemon.setBoosts(@stages)
|
|
@pokemon.changeSprite(@pokemon.species, @pokemon.forme)
|
|
@pokemon.tellPlayer(Protocol.MOVESET_UPDATE, @pokemon.movesetJSON())
|
|
|
|
class @Attachment.Illusion extends @BattleAttachment
|
|
name: "IllusionAttachment"
|
|
|
|
@preattach: (attributes) ->
|
|
#{target} = attributes
|
|
#!target.has(Attachment.Illusion)
|
|
|
|
initialize: (attributes) ->
|
|
{target} = attributes
|
|
if @pokemon.currentHP == @pokemon.stat('hp')
|
|
@originalname = @pokemon.name
|
|
@pokemon.name = target.name
|
|
# Send updated information to the client
|
|
@pokemon.tell(Protocol.ILLUSION_CHANGE, true)
|
|
@pokemon.tell(Protocol.NAME_CHANGE, @pokemon.name)
|
|
|
|
unattach: ->
|
|
@pokemon.name = @pokemon.originalname
|
|
@pokemon.tell(Protocol.NAME_CHANGE, @pokemon.originalname)
|
|
@pokemon.changeSprite(@pokemon.species, @pokemon.forme)
|
|
@pokemon.tell(Protocol.ILLUSION_CHANGE, false)
|
|
@battle.cannedText('ILLUSION_BROKE', @pokemon)
|
|
|
|
afterBeingHit: (move, user, target) ->
|
|
if move.isDirectHit(@battle, user, @pokemon) and !move.isNonDamaging()
|
|
console.log('unattached')
|
|
@pokemon.unattach(@constructor)
|
|
|
|
afterFaint: ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Fling extends @VolatileAttachment
|
|
name: "FlingAttachment"
|
|
|
|
initialize: ->
|
|
@item = null
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
# The move may be changed by something like Encore
|
|
return if move != @battle.getMove("Fling")
|
|
if user.hasItem() && user.hasTakeableItem() && !user.isItemBlocked()
|
|
@item = user.getItem()
|
|
user.removeItem()
|
|
|
|
endTurn: ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.Gravity extends @BattleAttachment
|
|
name: "GravityAttachment"
|
|
|
|
initialize: ->
|
|
@turns = 5
|
|
@beginTurn()
|
|
|
|
beginTurn: ->
|
|
for pokemon in @battle.getActivePokemon()
|
|
pokemon.attach(Attachment.GravityPokemon)
|
|
for move in pokemon.moves
|
|
pokemon.blockMove(move) if move.hasFlag("gravity")
|
|
|
|
endTurn: ->
|
|
@turns--
|
|
if @turns == 0
|
|
@battle.message "Gravity turned to normal!"
|
|
@battle.unattach(@constructor)
|
|
|
|
class @Attachment.GravityPokemon extends @VolatileAttachment
|
|
name: "GravityPokemonAttachment"
|
|
|
|
beforeMove: (move, user, target) ->
|
|
if move.hasFlag("gravity")
|
|
@battle.message "#{user.name} can't use #{move.name} because of gravity!"
|
|
return false
|
|
|
|
editAccuracy: (accuracy) ->
|
|
Math.floor(accuracy * 5 / 3)
|
|
|
|
isImmune: (type) ->
|
|
return false if type == 'Ground'
|
|
|
|
shouldIgnoreImmunity: (moveType, target) ->
|
|
return target.hasType("Flying") && moveType == 'Ground'
|
|
|
|
endTurn: ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
class @Attachment.DelayedAttack extends @TeamAttachment
|
|
name: "DelayedAttackAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@move, @user} = attributes
|
|
@slot = 0
|
|
@turns = 3
|
|
|
|
endTurn: ->
|
|
@turns--
|
|
if @turns == 0
|
|
pokemon = @team.at(@slot)
|
|
if pokemon.isAlive()
|
|
@battle.message "#{pokemon.name} took the #{@move.name} attack!"
|
|
isDirect = @move.isDirectHit(@battle, @user, pokemon)
|
|
damage = @move.hit(@battle, @user, pokemon, 1, isDirect)
|
|
@move.afterHit(@battle, @user, pokemon, damage, isDirect)
|
|
@team.unattach(@constructor)
|
|
|
|
class @Attachment.BatonPass extends @TeamAttachment
|
|
name: "BatonPassAttachment"
|
|
|
|
initialize: (attributes) ->
|
|
{@slot, @attachments, @stages} = attributes
|
|
|
|
switchIn: (pokemon) ->
|
|
return if @slot != @team.indexOf(pokemon)
|
|
# Nasty stitching of attachments to the recipient.
|
|
for attachment in @attachments
|
|
attachment.pokemon = pokemon
|
|
attachment.team = pokemon.team
|
|
attachment.battle = pokemon.battle
|
|
attachment.attached = true
|
|
index = (pokemon.attachments.attachments.push(attachment)) - 1
|
|
if attachment.reinitializeOnPass
|
|
pokemon.attachments.attachments[index]?.initialize?()
|
|
pokemon.setBoosts(@stages)
|
|
@team.unattach(@constructor)
|
|
|
|
class @Attachment.FlashFire extends @VolatileAttachment
|
|
name: "FlashFireAttachment"
|
|
|
|
modifyBasePower: (move, target) ->
|
|
return 0x1000 if move.getType(@battle, @pokemon, target) != 'Fire'
|
|
return 0x1800
|
|
|
|
class @Attachment.Unburden extends @VolatileAttachment
|
|
name: "UnburdenAttachment"
|
|
|
|
editSpeed: (speed) ->
|
|
if @pokemon.hasAbility("Unburden") && !@pokemon.hasItem()
|
|
2 * speed
|
|
else
|
|
@pokemon.unattach(@constructor)
|
|
speed
|
|
|
|
# Cancels the opponent's ability for one turn
|
|
class @Attachment.AbilityCancel extends @VolatileAttachment
|
|
name: "AbilityCancelAttachment"
|
|
|
|
initialize: ->
|
|
if !@pokemon.isAbilityBlocked()
|
|
@shouldUnblock = true
|
|
@pokemon.blockAbility()
|
|
|
|
unattach: ->
|
|
if @shouldUnblock then @pokemon.unblockAbility()
|
|
|
|
endTurn: ->
|
|
@pokemon.unattach(@constructor)
|
|
|
|
# Suppresses the opponent's ability until they switch out
|
|
class @Attachment.AbilitySuppress extends @VolatileAttachment
|
|
name: "AbilitySuppressAttachment"
|
|
passable: true
|
|
|
|
@preattach: (options, attributes) ->
|
|
{pokemon} = attributes
|
|
return false if !pokemon.hasChangeableAbility()
|
|
|
|
initialize: ->
|
|
@pokemon.blockAbility()
|
|
|
|
beginTurn: this::initialize
|
|
|
|
class @Attachment.SleepClause extends @BaseAttachment
|
|
name: "SleepClause"
|
|
|
|
shouldAttach: (attachment, options) ->
|
|
{source} = options
|
|
return if attachment != Status.Sleep
|
|
return if !source || source.team == @pokemon.team
|
|
pokemonSleptByOtherTeams = @pokemon.team.filter (p) =>
|
|
return false if p.isFainted()
|
|
source = p.get(Status.Sleep)?.source
|
|
return source && source.team != @pokemon.team
|
|
# Attach if we have no pokemon slept by other teams.
|
|
return pokemonSleptByOtherTeams.length == 0
|
|
|
|
class @StatusAttachment extends @BaseAttachment
|
|
name: "StatusAttachment"
|
|
|
|
@status: true
|
|
|
|
@preattach: (options, attributes) ->
|
|
{battle, pokemon} = attributes
|
|
{source, force, bypassSafeguard} = options
|
|
force ?= false
|
|
if !force
|
|
return false if pokemon.hasStatus()
|
|
return false if (pokemon.team?.has(Attachment.Safeguard) && source != pokemon && !bypassSafeguard)
|
|
return false unless @worksOn(battle, pokemon)
|
|
if (source && this in [ Status.Toxic, Status.Burn, Status.Poison, Status.Paralyze ] && pokemon.hasAbility("Synchronize")) or
|
|
(source && this in [ Status.Toxic, Status.Burn, Status.Poison, Status.Paralyze ] && pokemon.hasAbility("Protean Maxima") && pokemon.getForme == "mega-dark")
|
|
return false if source == pokemon
|
|
source.attach(this) # Do not attach source
|
|
battle.message "#{pokemon.name} synchronized its status with #{source.name}!"
|
|
else
|
|
pokemon.cureStatus(message: false)
|
|
pokemon.status = this
|
|
return true
|
|
|
|
initialize: (attributes = {}) ->
|
|
# We store the source for use in other places, like Sleep Clause.
|
|
{@source} = attributes
|
|
@battle?.cannedText("#{@constructor.name.toUpperCase()}_START", @pokemon)
|
|
@pokemon.tell(Protocol.POKEMON_ATTACH, @name)
|
|
|
|
@worksOn: (battle, pokemon) ->
|
|
true
|
|
|
|
unattach: ->
|
|
@pokemon.tell(Protocol.POKEMON_UNATTACH, @name)
|
|
@pokemon.status = null
|
|
|
|
class @Status.Paralyze extends @StatusAttachment
|
|
name: "Paralyze"
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
if @battle.rng.next('paralyze chance') < .25
|
|
@battle.cannedText('PARALYZE_CONTINUE', @pokemon)
|
|
return false
|
|
|
|
editSpeed: (stat) ->
|
|
if @pokemon.hasAbility("Quick Feet") then stat else stat >> 2
|
|
|
|
class @Status.Freeze extends @StatusAttachment
|
|
name: "Freeze"
|
|
|
|
@worksOn: (battle, pokemon) ->
|
|
!(pokemon.hasType("Ice") || battle?.hasWeather(Weather.SUN))
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
if move.thawsUser || @battle.rng.next('unfreeze chance') < .2
|
|
@pokemon.cureStatus()
|
|
else
|
|
@battle.cannedText('FREEZE_CONTINUE', @pokemon)
|
|
return false
|
|
|
|
afterBeingHit: (move, user, target) ->
|
|
if !move.isNonDamaging() && move.type == 'Fire'
|
|
@pokemon.cureStatus()
|
|
|
|
class @Status.Poison extends @StatusAttachment
|
|
name: "Poison"
|
|
|
|
@worksOn: (battle, pokemon) ->
|
|
!(pokemon.hasType("Poison") || pokemon.hasType("Steel"))
|
|
|
|
endTurn: ->
|
|
return if @pokemon.hasAbility("Poison Heal")
|
|
if @pokemon.damage(@pokemon.stat('hp') >> 3)
|
|
@battle.cannedText('POISON_CONTINUE', @pokemon)
|
|
|
|
class @Status.Toxic extends @StatusAttachment
|
|
name: "Toxic"
|
|
|
|
@worksOn: (battle, pokemon) ->
|
|
!(pokemon.hasType("Poison") || pokemon.hasType("Steel"))
|
|
|
|
initialize: ->
|
|
super()
|
|
@counter = 0
|
|
|
|
switchOut: ->
|
|
@counter = 0
|
|
|
|
endTurn: ->
|
|
@counter = Math.min(@counter + 1, 15)
|
|
return if @pokemon.hasAbility("Poison Heal")
|
|
if @pokemon.damage(Math.max(@pokemon.stat('hp') >> 4, 1) * @counter)
|
|
@battle.cannedText('POISON_CONTINUE', @pokemon)
|
|
|
|
class @Status.Sleep extends @StatusAttachment
|
|
name: "Sleep"
|
|
|
|
initialize: (attributes) ->
|
|
super(attributes)
|
|
@counter = 0
|
|
{@turns} = attributes
|
|
if !@turns && @battle?
|
|
@turns = @battle.rng.randInt(1, 3, "sleep turns")
|
|
|
|
switchOut: ->
|
|
@counter = 0
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
@counter += 1 if @pokemon.hasAbility("Early Bird")
|
|
@counter += 1
|
|
if @counter > @turns
|
|
@pokemon.cureStatus()
|
|
else
|
|
@battle.cannedText('SLEEP_CONTINUE', @pokemon)
|
|
return false unless move.usableWhileAsleep
|
|
|
|
class @Status.Burn extends @StatusAttachment
|
|
name: "Burn"
|
|
|
|
@worksOn: (battle, pokemon) ->
|
|
!pokemon.hasType("Fire")
|
|
|
|
endTurn: ->
|
|
if @pokemon.damage(@pokemon.stat('hp') >> 3)
|
|
@battle.cannedText('BURN_CONTINUE', @pokemon)
|