717 lines
22 KiB
CoffeeScript
717 lines
22 KiB
CoffeeScript
{_} = require 'underscore'
|
|
{Ability, Item, Moves, SpeciesData, FormeData} = require './data'
|
|
{Attachment, Status, Attachments} = require './attachment'
|
|
{Weather} = require '../../shared/weather'
|
|
{Protocol} = require '../../shared/protocol'
|
|
Query = require './queries'
|
|
util = require './util'
|
|
floor = Math.floor
|
|
|
|
class @Pokemon
|
|
# TODO: Take the species obj, not attributes?
|
|
constructor: (attributes = {}) ->
|
|
# Inject battle and team dependencies
|
|
@battle = attributes.battle
|
|
@team = attributes.team
|
|
@playerId = attributes.playerId
|
|
|
|
@species = attributes.species || "Missingno"
|
|
@name = attributes.name || @species
|
|
@forme = attributes.forme || "default"
|
|
@level = attributes.level || @getMaxLevel()
|
|
@gender = attributes.gender || "Genderless"
|
|
@shiny = attributes.shiny
|
|
@nfe = (SpeciesData[@species]?.evolvesInto?.length > 0)
|
|
@tempsprite = null #Used for Mega Zoruark
|
|
@originalname = @name
|
|
|
|
@attachments = new Attachments()
|
|
@resetForme()
|
|
|
|
@nature = attributes.nature
|
|
@evs = attributes.evs || {}
|
|
@ivs = attributes.ivs || {}
|
|
@currentHP = @stat('hp')
|
|
|
|
@moves = (attributes.moves || []).map (move) -> Moves[move]
|
|
@used = {}
|
|
@resetAllPP()
|
|
@item = Item[attributes.item?.replace(/\s+/g, '')]
|
|
@ability = Ability[attributes.ability?.replace(/\s+/g, '')]
|
|
@originalAbility = @ability
|
|
@originalForme = @forme
|
|
@status = null
|
|
|
|
@stages =
|
|
attack: 0
|
|
defense: 0
|
|
speed: 0
|
|
specialDefense: 0
|
|
specialAttack: 0
|
|
evasion: 0
|
|
accuracy: 0
|
|
|
|
# What moves are blocked, and is switching blocked, and is item blocked
|
|
@resetBlocks()
|
|
|
|
# a record of how long this pokemon has been in play.
|
|
@turnsActive = 0
|
|
|
|
# a record of the last move used by this pokemon.
|
|
@lastMove = null
|
|
|
|
# a record of the last item used by this pokemon.
|
|
# if the item is removed by someone else, it is not recorded.
|
|
@lastItem = null
|
|
|
|
# the time the pokemon officially fainted. 0 means never fainted.
|
|
@fainted = 0
|
|
|
|
getForme: (newForme) ->
|
|
availableFormes = FormeData[@species] || {}
|
|
availableFormes[newForme || @forme]
|
|
|
|
isInForme: (forme) ->
|
|
@forme == forme
|
|
|
|
changeForme: (newForme) ->
|
|
return false if !@getForme(newForme)
|
|
if @species == 'Zoroark' && newForme == 'mega'
|
|
alivemons = @team.getAlivePokemon()
|
|
lastalivemon = alivemons[alivemons.length-1]
|
|
possibleform = FormeData[lastalivemon.species]
|
|
if 'mega' of possibleform
|
|
@changeSprite(lastalivemon.species, 'mega')
|
|
else
|
|
@changeSprite(newForme)
|
|
@forme = newForme
|
|
@resetForme()
|
|
return true
|
|
|
|
getMaxLevel: ->
|
|
level = 100
|
|
level
|
|
|
|
resetForme: ->
|
|
forme = @getForme() || {}
|
|
@baseStats = _.clone(forme.stats) || {}
|
|
@types = _.clone(forme.types) || []
|
|
@weight = forme.weight
|
|
|
|
changeSprite: (newSpecies, newForme) ->
|
|
if arguments.length == 1
|
|
[newSpecies, newForme] = [@species, newSpecies]
|
|
@tempsprite = newSpecies
|
|
@tell(Protocol.SPRITE_CHANGE, newSpecies, newForme)
|
|
|
|
iv: (stat) -> (if stat of @ivs then @ivs[stat] else 31)
|
|
ev: (stat) -> (if stat of @evs then @evs[stat] else 0)
|
|
|
|
pp: (move) -> @ppHash[move.name]
|
|
maxPP: (move) -> @maxPPHash[move.name]
|
|
|
|
reducePP: (move, amount = 1) ->
|
|
@setPP(move, @pp(move) - amount)
|
|
|
|
setPP: (move, pp) ->
|
|
pp = Math.max(pp, 0)
|
|
pp = Math.min(pp, @maxPP(move))
|
|
@ppHash[move.name] = pp
|
|
@battle?.tellPlayer(@playerId,
|
|
Protocol.CHANGE_PP,
|
|
@battle.getPlayerIndex(@playerId),
|
|
@team.pokemon.indexOf(this),
|
|
@moves.indexOf(move),
|
|
pp)
|
|
pp
|
|
|
|
resetAllPP: (pp) ->
|
|
@ppHash = {}
|
|
@maxPPHash = {}
|
|
if @moves?
|
|
for move in @moves
|
|
try
|
|
@ppHash[move.name] = @maxPPHash[move.name] = pp || (move.pp * 8/5)
|
|
catch error
|
|
console.log(move)
|
|
|
|
setType: (newType) ->
|
|
@types = newType
|
|
|
|
# Gets the stat indexed by key.
|
|
# Ex: pokemon.stat('hp')
|
|
# TODO: Precalculate the stats in the constructor
|
|
stat: (key, options = {}) ->
|
|
base = @baseStats[key] || 100
|
|
return 1 if base == 1 # For Shedinja. key doesn't have to be hp.
|
|
iv = @iv(key)
|
|
ev = floor(@ev(key) / 4)
|
|
total = if key == 'hp'
|
|
floor((2 * base + iv + ev) * (@level / 100) + @level + 10)
|
|
else
|
|
floor(((2 * base + iv + ev) * (@level / 100) + 5) * @natureBoost(key))
|
|
capitalized = key[0].toUpperCase() + key.substr(1)
|
|
total = Query.chain("edit#{capitalized}", @attachments.all(), total)
|
|
if @team?.attachments
|
|
total = Query.chain("edit#{capitalized}", @team.attachments.all(), total)
|
|
total = @statBoost(key, total, options) if key != 'hp'
|
|
total
|
|
|
|
# Returns 1.1, 1.0, or 0.9 according to whether a Pokemon's nature corresponds
|
|
# to that stat. The default return value is 1.0.
|
|
natureBoost: (stat) ->
|
|
nature = @nature?.toLowerCase()
|
|
if nature of natures
|
|
natures[nature][stat] || 1
|
|
else
|
|
1
|
|
|
|
statBoost: (statName, total, options = {}) ->
|
|
stages = @editBoosts(options)
|
|
boost = stages[statName]
|
|
if boost >= 0
|
|
Math.floor((2 + boost) * total / 2)
|
|
else
|
|
Math.floor(2 * total / (2 - boost))
|
|
|
|
# Boosts this pokemon's stats by the given number of stages.
|
|
# Returns true whether any stat was boosted, false otherwise.
|
|
#
|
|
# Example: pokemon.boost(specialAttack: 1, evasion: 2)
|
|
#
|
|
boost: (boosts, source = this) ->
|
|
return false if @isFainted()
|
|
boosts = Query.chain('transformBoosts', @attachments.all(), _.clone(boosts), source)
|
|
deltaBoosts = {}
|
|
didBoost = false
|
|
for stat, amount of boosts
|
|
amount *= -1 if @ability == Ability.Contrary
|
|
if stat not of @stages
|
|
throw new Error("Tried to boost non-existent stat #{stat} by #{amount}")
|
|
previous = @stages[stat]
|
|
@stages[stat] += amount
|
|
@stages[stat] = Math.max(-6, @stages[stat])
|
|
@stages[stat] = Math.min(6, @stages[stat])
|
|
deltaBoosts[stat] = (@stages[stat] - previous)
|
|
didBoost ||= (deltaBoosts[stat] != 0)
|
|
@afterEachBoost(deltaBoosts[stat], source)
|
|
@tell(Protocol.BOOSTS, deltaBoosts) if didBoost
|
|
didBoost
|
|
|
|
positiveBoostCount: ->
|
|
count = 0
|
|
for stage, total of @stages
|
|
count += total if total > 0
|
|
count
|
|
|
|
hasBoosts: ->
|
|
for stage, value of @stages
|
|
return true if value != 0
|
|
return false
|
|
|
|
# Sets boosts, but also send a message to clients
|
|
setBoosts: (boosts) ->
|
|
for stat, amount of boosts
|
|
@stages[stat] = amount
|
|
@tell(Protocol.SET_BOOSTS, boosts)
|
|
|
|
resetBoosts: ->
|
|
@stages.attack = 0
|
|
@stages.defense = 0
|
|
@stages.speed = 0
|
|
@stages.specialAttack = 0
|
|
@stages.specialDefense = 0
|
|
@stages.accuracy = 0
|
|
@stages.evasion = 0
|
|
@tell(Protocol.RESET_BOOSTS)
|
|
true
|
|
|
|
hasType: (type) ->
|
|
type in @types
|
|
|
|
hasAbility: (ability) ->
|
|
return false unless @ability
|
|
return false if @isAbilityBlocked()
|
|
if typeof ability == 'string'
|
|
@ability.displayName == ability
|
|
else
|
|
@ability == ability
|
|
|
|
hasItem: (itemName) ->
|
|
if itemName?
|
|
@item?.displayName == itemName
|
|
else
|
|
@item?
|
|
|
|
hasStatus: ->
|
|
return !!@status
|
|
|
|
has: (attachment) ->
|
|
@attachments.contains(attachment)
|
|
|
|
get: (attachment) ->
|
|
@attachments.get(attachment)
|
|
|
|
cureAttachment: (attachment, options) ->
|
|
if attachment.name of Status
|
|
@cureStatus(attachment, options)
|
|
else
|
|
@cureAilment(attachment, options)
|
|
|
|
cureStatus: (status, options) ->
|
|
return false if !@status
|
|
[ status, options ] = [ @status, status ] if status?.name not of Status
|
|
return false if status != @status
|
|
@cureAilment(@status, options)
|
|
@status = null
|
|
return true
|
|
|
|
cureAilment: (ailment, options = {}) ->
|
|
return false if !@has(ailment)
|
|
shouldMessage = options.message ? true
|
|
if @battle && shouldMessage
|
|
if shouldMessage == true
|
|
message = switch ailment
|
|
when Status.Paralyze then " was cured of paralysis."
|
|
when Status.Burn then " healed its burn!"
|
|
when Status.Sleep then " woke up!"
|
|
when Status.Toxic then " was cured of its poisoning."
|
|
when Status.Poison then " was cured of its poisoning."
|
|
when Status.Freeze then " thawed out!"
|
|
when Attachment.Confusion then " snapped out of its confusion."
|
|
else
|
|
source = options.message
|
|
message = switch ailment
|
|
when Status.Paralyze then "'s #{source} cured its paralysis!"
|
|
when Status.Burn then "'s #{source} healed its burn!"
|
|
when Status.Sleep then "'s #{source} woke it up!"
|
|
when Status.Toxic then "'s #{source} cured its poison!"
|
|
when Status.Poison then "'s #{source} cured its poison!"
|
|
when Status.Freeze then "'s #{source} defrosted it!"
|
|
when Attachment.Confusion then "'s #{source} snapped it out of its confusion!"
|
|
@battle.message("#{@name}#{message}")
|
|
@unattach(ailment)
|
|
return true
|
|
|
|
setAbility: (ability) ->
|
|
@unattach(@ability) if @ability
|
|
@ability = ability
|
|
|
|
initializeAbility: ->
|
|
@attach(@ability).switchIn?() if @ability && !@isAbilityBlocked()
|
|
|
|
# TODO: really ugly copying of ability
|
|
copyAbility: (ability, options = {}) ->
|
|
shouldShow = options.reveal ? true
|
|
@activateAbility() if shouldShow
|
|
@setAbility(ability)
|
|
@activateAbility() if shouldShow
|
|
@initializeAbility()
|
|
|
|
swapAbilityWith: (target) ->
|
|
# Abilities are not revealed during the swap
|
|
# if the user and the target are on the same side
|
|
if @team != target.team
|
|
@activateAbility()
|
|
target.activateAbility()
|
|
uAbility = @ability
|
|
@setAbility(target.ability)
|
|
target.setAbility(uAbility)
|
|
if @team != target.team
|
|
@activateAbility()
|
|
target.activateAbility()
|
|
@battle.cannedText('SWAP_ABILITY', this)
|
|
@initializeAbility()
|
|
target.initializeAbility()
|
|
|
|
hasChangeableAbility: ->
|
|
!@hasAbility("Multitype")
|
|
|
|
setItem: (item, options = {}) ->
|
|
if @hasItem() then @removeItem()
|
|
@item = item
|
|
@lastItem = null if options.clearLastItem
|
|
attachment = @attach(@item)
|
|
attachment.switchIn?() if !@isItemBlocked()
|
|
|
|
getItem: ->
|
|
@item
|
|
|
|
useItem: ->
|
|
item = @item
|
|
@removeItem()
|
|
@lastItem = item
|
|
|
|
removeItem: ->
|
|
return unless @item
|
|
@attach(Attachment.Unburden) if @hasAbility("Unburden")
|
|
@get(@item).switchOut?()
|
|
@unattach(@item)
|
|
oldItem = @item
|
|
@item = null
|
|
oldItem
|
|
|
|
hasTakeableItem: ->
|
|
return false if !@hasItem()
|
|
return false if @item.type == 'mail'
|
|
return false if @item.type == 'key'
|
|
return false if @hasAbility("Multitype") && @item.plate
|
|
return false if @species == 'Giratina' && @forme == 'origin'
|
|
return false if @species == 'Genesect' && /Drive$/.test(@item.displayName)
|
|
true
|
|
|
|
# This differs from hasTakeableItem by virtue of Sticky Hold
|
|
canLoseItem: ->
|
|
@hasTakeableItem() && !@has(Ability.StickyHold)
|
|
|
|
canHeal: ->
|
|
!@has(Attachment.HealBlock)
|
|
|
|
isActive: ->
|
|
this in @team.getActiveAlivePokemon()
|
|
|
|
isAlive: ->
|
|
!@isFainted()
|
|
|
|
isFainted: ->
|
|
@currentHP <= 0
|
|
|
|
faint: ->
|
|
return if @fainted
|
|
if @battle
|
|
@battle.message "#{@name} fainted!"
|
|
@battle.tell(Protocol.FAINT, @battle.getPlayerIndex(@playerId), @battle.getSlotNumber(this))
|
|
# Remove pending actions they had.
|
|
@battle.popAction(this)
|
|
@fainted = @battle.incrementFaintedCounter()
|
|
@setHP(0) if !@isFainted()
|
|
# TODO: If a Pokemon faints in an afterFaint, should it be added to this?
|
|
Query('afterFaint', @attachments.all())
|
|
# TODO: Do fainted Pokémon need attachments in any case?
|
|
# If so, #attach will need to be revisited as well.
|
|
@unattachAll()
|
|
@team.faintedThisTurn = true
|
|
|
|
damage: (amount, options = {}) ->
|
|
amount = Math.max(1, amount)
|
|
amount = @transformHealthChange(amount, options)
|
|
@setHP(@currentHP - amount)
|
|
|
|
heal: (amount) ->
|
|
if amount > 0 && @currentHP < @stat('hp') && !@canHeal()
|
|
@battle.cannedText('HEAL_BLOCK_TRY_HEAL', this)
|
|
return false
|
|
@setHP(@currentHP + amount)
|
|
|
|
drain: (amount, source) ->
|
|
if @hasItem("Big Root") && !@isItemBlocked()
|
|
amount = util.roundHalfDown(amount * 1.3)
|
|
amount *= -1 if source != this && source?.hasAbility("Liquid Ooze")
|
|
@heal(amount)
|
|
|
|
transformHealthChange: (damage, options) ->
|
|
Query.chain('transformHealthChange', @attachments.all(), damage, options)
|
|
|
|
editPriority: (priority, move) ->
|
|
Query.chain('editPriority', @attachments.all(), priority, move)
|
|
|
|
editBoosts: (opts = {}) ->
|
|
stages = Query.chain('editBoosts', @attachments.all(), _.clone(@stages))
|
|
for stat, amt of stages
|
|
amt = 0 if opts.ignorePositiveBoosts && amt > 0
|
|
amt = 0 if opts.ignoreNegativeBoosts && amt < 0
|
|
amt = 0 if opts.ignoreEvasion && stat == 'evasion'
|
|
amt = 0 if opts.ignoreAccuracy && stat == 'accuracy'
|
|
amt = 0 if opts.ignoreOffense && stat in [ 'attack', 'specialAttack' ]
|
|
amt = 0 if opts.ignoreDefense && stat in [ 'defense', 'specialDefense' ]
|
|
stages[stat] = amt
|
|
return stages
|
|
|
|
editAccuracy: (accuracy, move, target) ->
|
|
Query.chain('editAccuracy', @attachments.all(), accuracy, move, target)
|
|
|
|
editEvasion: (accuracy, move, user) ->
|
|
Query.chain('editEvasion', @attachments.all(), accuracy, move, user)
|
|
|
|
editMoveType: (type, target) ->
|
|
Query.chain('editMoveType', @attachments.all(), type, target)
|
|
|
|
calculateWeight: ->
|
|
Query.chain('calculateWeight', @attachments.all(), @weight)
|
|
|
|
criticalModifier: ->
|
|
Query.chain('criticalModifier', @attachments.all(), 0)
|
|
|
|
afterEachBoost: (boostAmount, source = this) ->
|
|
Query('afterEachBoost', @attachments.all(), boostAmount, source)
|
|
|
|
afterAllHits: (move) ->
|
|
Query('afterAllHits', @attachments.all(), move)
|
|
|
|
afterAllHitsTarget: (move, user) ->
|
|
Query('afterAllHitsTarget', @attachments.all(), move, user)
|
|
|
|
setHP: (hp) ->
|
|
oldHP = @currentHP
|
|
@currentHP = Math.min(@stat('hp'), hp)
|
|
@currentHP = Math.max(@currentHP, 0)
|
|
delta = oldHP - @currentHP
|
|
if delta != 0
|
|
percent = Math.ceil(100 * @currentHP / @stat('hp'))
|
|
@tell(Protocol.CHANGE_HP, percent)
|
|
@tellPlayer(Protocol.CHANGE_EXACT_HP, @currentHP)
|
|
delta
|
|
|
|
recordMove: (move) ->
|
|
@lastMove = move
|
|
@used[move.name] = true
|
|
|
|
recordHit: (pokemon, damage, move, turn, direct) ->
|
|
team = pokemon.team
|
|
slot = team.indexOf(pokemon)
|
|
@lastHitBy = {team, slot, damage, move, turn, direct}
|
|
|
|
isImmune: (type, options = {}) ->
|
|
b = Query.untilNotNull('isImmune', @attachments.all(), type, options.move)
|
|
if b? then return b
|
|
|
|
return false if options.move?.ignoresImmunities()
|
|
|
|
multiplier = @effectivenessOf(type, options)
|
|
if @hasAbility("Ethereal Shroud")
|
|
ghosteffect = util.typeEffectiveness(type, ["Ghost"])
|
|
if ghosteffect < 1
|
|
multiplier *= ghosteffect
|
|
return multiplier == 0
|
|
|
|
effectivenessOf: (type, options) ->
|
|
hash = {}
|
|
if options.user
|
|
type = options.user.editMoveType(type, this)
|
|
hash.ignoreImmunities = options.user.shouldIgnoreImmunity(type, this)
|
|
hash.superEffectiveAgainst = options.move?.superEffectiveAgainst
|
|
util.typeEffectiveness(type, @types, hash)
|
|
|
|
isWeatherDamageImmune: (weather) ->
|
|
b = Query.untilNotNull('isWeatherDamageImmune', @attachments.all(), weather)
|
|
if b? then return b
|
|
|
|
return true if weather == Weather.HAIL && @hasType("Ice")
|
|
return true if weather == Weather.SAND && (@hasType("Ground") ||
|
|
@hasType("Rock") || @hasType("Steel"))
|
|
return @battle?.hasWeatherCancelAbilityOnField() || false
|
|
|
|
activate: ->
|
|
@turnsActive = 0
|
|
@attach(@ability) if @ability
|
|
@attach(@item) if @item
|
|
|
|
switchIn: ->
|
|
Query('switchIn', @attachments.all())
|
|
|
|
switchOut: ->
|
|
delete @lastMove
|
|
@used = {}
|
|
Query('switchOut', @attachments.all())
|
|
@attachments.unattachAll((a) -> a.volatile)
|
|
@resetForme()
|
|
@resetBoosts()
|
|
@resetBlocks()
|
|
@ability = @originalAbility
|
|
@changeForme(@originalForme) if @forme != @originalForme
|
|
|
|
informSwitch: (switcher) ->
|
|
Query('informSwitch', @attachments.all(), switcher)
|
|
|
|
shouldPhase: (phaser) ->
|
|
Query.untilFalse('shouldPhase', @attachments.all(), phaser) != false
|
|
|
|
shouldIgnoreImmunity: (type, target) ->
|
|
Query.untilTrue('shouldIgnoreImmunity', @attachments.all(), type, target)
|
|
|
|
informCriticalHit: ->
|
|
Query('informCriticalHit', @attachments.all())
|
|
|
|
informWeather: (weather) ->
|
|
Query('informWeather', @attachments.all(), weather)
|
|
|
|
beginTurn: ->
|
|
Query('beginTurn', @attachments.all())
|
|
|
|
beforeMove: (move, user, targets) ->
|
|
Query.untilFalse('beforeMove', @attachments.all(), move, user, targets)
|
|
|
|
afterMove: (move, user, targets) ->
|
|
Query('afterMove', @attachments.all(), move, user, targets)
|
|
|
|
shouldBlockExecution: (move, user) ->
|
|
Query.untilTrue('shouldBlockExecution', @attachments.all(), move, user)
|
|
|
|
|
|
update: ->
|
|
Query('update', @attachments.all())
|
|
|
|
afterTurnOrder: ->
|
|
Query('afterTurnOrder', @attachments.all())
|
|
|
|
calculateNumberOfHits: (move, targets) ->
|
|
Query.untilNotNull("calculateNumberOfHits", @attachments.all(), move, targets)
|
|
|
|
resetRecords: ->
|
|
@lastHitBy = null
|
|
|
|
# Hook for when the Pokemon gets hit by a move
|
|
afterBeingHit: (move, user, target, damage, isDirect) ->
|
|
Query('afterBeingHit', @attachments.all(), move, user, target, damage, isDirect)
|
|
|
|
afterSuccessfulHit: (move, user, target, damage) ->
|
|
Query('afterSuccessfulHit', @attachments.all(), move, user, target, damage)
|
|
|
|
# Adds an attachment to the list of attachments
|
|
attach: (attachment, options={}) ->
|
|
if @isFainted()
|
|
return false
|
|
options = _.clone(options)
|
|
@attachments.push(attachment, options, battle: @battle, team: @team, pokemon: this)
|
|
|
|
# Removes an attachment from the list of attachment
|
|
unattach: (klass) ->
|
|
# TODO: Do we need to remove circular dependencies?
|
|
# Removing them here will result in some unanticipated consequenes.
|
|
@attachments.unattach(klass)
|
|
|
|
unattachAll: ->
|
|
@attachments.unattachAll()
|
|
|
|
# Blocks a move for a single turn
|
|
blockMove: (move) ->
|
|
@blockedMoves.push(move)
|
|
|
|
# Blocks all moves for a single turn
|
|
blockMoves: ->
|
|
@blockMove(move) for move in @moves
|
|
|
|
isMoveBlocked: (move) ->
|
|
return (move in @blockedMoves)
|
|
|
|
isSwitchBlocked: ->
|
|
@switchBlocked
|
|
|
|
# Returns true if the Pokemon has no item or the item has been blocked.
|
|
isItemBlocked: ->
|
|
!@item? || @itemBlocked
|
|
|
|
# Blocks a switch for a single turn
|
|
blockSwitch: ->
|
|
@switchBlocked = true unless !@isItemBlocked() && @hasItem("Shed Shell")
|
|
|
|
# Blocks an item for a single turn
|
|
blockItem: ->
|
|
@itemBlocked = true
|
|
|
|
# Blocks an ability for a single turn
|
|
blockAbility: ->
|
|
@abilityBlocked = true
|
|
|
|
unblockAbility: ->
|
|
@abilityBlocked = false
|
|
|
|
isAbilityBlocked: ->
|
|
@abilityBlocked
|
|
|
|
resetBlocks: ->
|
|
@blockedMoves = []
|
|
@switchBlocked = false
|
|
@itemBlocked = false
|
|
@abilityBlocked = false
|
|
|
|
# Locks the Pokemon into a single move. Does not limit switches.
|
|
lockMove: (moveToLock) ->
|
|
for move in @validMoves()
|
|
@blockMove(move) if move != moveToLock
|
|
|
|
activateAbility: ->
|
|
@tell(Protocol.ACTIVATE_ABILITY, @ability?.displayName)
|
|
|
|
tell: (protocol, args...) ->
|
|
return unless @battle
|
|
args = [ @battle.getPlayerIndex(@playerId), @team.indexOf(this), args... ]
|
|
@battle.tell(protocol, args...)
|
|
|
|
tellPlayer: (protocol, args...) ->
|
|
return unless @battle
|
|
args = [ @battle.getPlayerIndex(@playerId), @team.indexOf(this), args... ]
|
|
@battle.tellPlayer(@playerId, protocol, args...)
|
|
|
|
# Returns whether this Pokemon has this move in its moveset.
|
|
knows: (move) ->
|
|
move in @moves
|
|
|
|
# A list of moves that this pokemon can use freely
|
|
validMoves: ->
|
|
moves = _(@moves).difference(@blockedMoves)
|
|
moves = moves.filter((move) => @pp(move) > 0)
|
|
moves
|
|
|
|
toString: ->
|
|
"[Pokemon species:#{@species} hp:#{@currentHP}/#{@stat('hp')}]"
|
|
|
|
movesetJSON: ->
|
|
return {
|
|
"moves" : @moves.map (m) -> m.name
|
|
"moveTypes" : @moves.map (m) -> m.type
|
|
"pp" : @moves.map (m) => @pp(m)
|
|
"maxPP" : @moves.map (m) => @maxPP(m)
|
|
}
|
|
|
|
toJSON: (options = {}) ->
|
|
base =
|
|
"species" : @species
|
|
"name" : @name
|
|
"level" : @level
|
|
"gender" : @gender
|
|
"boosts" : @stages
|
|
"forme" : @forme
|
|
"shiny" : @shiny == true
|
|
return base if options.hidden
|
|
_.extend base, @movesetJSON(),
|
|
"hp" : @currentHP
|
|
"maxHP" : @stat('hp')
|
|
"ivs" :
|
|
hp: @iv('hp')
|
|
attack: @iv('attack')
|
|
defense: @iv('defense')
|
|
speed: @iv('speed')
|
|
specialAttack: @iv('specialAttack')
|
|
specialDefense: @iv('specialDefense')
|
|
base["item"] = @item.displayName if @item
|
|
base["ability"] = @ability.displayName if @ability
|
|
base
|
|
|
|
# A hash that keys a nature with the stats that it boosts.
|
|
# Neutral natures are ignored.
|
|
PLUS = 1.1
|
|
MINUS = 0.9
|
|
natures =
|
|
lonely: {attack: PLUS, defense: MINUS}
|
|
brave: {attack: PLUS, speed: MINUS}
|
|
adamant: {attack: PLUS, specialAttack: MINUS}
|
|
naughty: {attack: PLUS, specialDefense: MINUS}
|
|
bold: {defense: PLUS, attack: MINUS}
|
|
relaxed: {defense: PLUS, speed: MINUS}
|
|
impish: {defense: PLUS, specialAttack: MINUS}
|
|
lax: {defense: PLUS, specialDefense: MINUS}
|
|
timid: {speed: PLUS, attack: MINUS}
|
|
hasty: {speed: PLUS, defense: MINUS}
|
|
jolly: {speed: PLUS, specialAttack: MINUS}
|
|
naive: {speed: PLUS, specialDefense: MINUS}
|
|
modest: {specialAttack: PLUS, attack: MINUS}
|
|
mild: {specialAttack: PLUS, defense: MINUS}
|
|
quiet: {specialAttack: PLUS, speed: MINUS}
|
|
rash: {specialAttack: PLUS, specialDefense: MINUS}
|
|
calm: {specialDefense: PLUS, attack: MINUS}
|
|
gentle: {specialDefense: PLUS, defense: MINUS}
|
|
sassy: {specialDefense: PLUS, speed: MINUS}
|
|
careful: {specialDefense: PLUS, specialAttack: MINUS}
|
|
|