357 lines
10 KiB
CoffeeScript
357 lines
10 KiB
CoffeeScript
class @Pokemon extends Backbone.Model
|
|
defaults: =>
|
|
moves: []
|
|
percent: 100
|
|
ivs:
|
|
hp: 31
|
|
attack: 31
|
|
defense: 31
|
|
specialAttack: 31
|
|
specialDefense: 31
|
|
speed: 31
|
|
evs:
|
|
hp: 0
|
|
attack: 0
|
|
defense: 0
|
|
specialAttack: 0
|
|
specialDefense: 0
|
|
speed: 0
|
|
|
|
initialize: (attributes={}) ->
|
|
# History lesson: We stored species under `name`. Now that we support
|
|
# nicknames, we need the `name` freed up. However, teams are saved to the
|
|
# server using the old scheme. Therefore we need to do a simple check for
|
|
# the existence of `species`; if it exists, do nothing. If not, use `name`.
|
|
@set('species', @get('name')) if !@has('species') && @has('name')
|
|
@set('forme', 'default') unless @has('forme')
|
|
@set('illu', false)
|
|
@normalizeStats(@get('ivs'), 31)
|
|
@normalizeStats(@get('evs'), 0)
|
|
@resetBoosts()
|
|
@isNull = false
|
|
|
|
# Skip teambuilder-specific properties.
|
|
return if @get('teambuilder') != true
|
|
|
|
@on 'change:ivs', (model, ivs)=>
|
|
type = HiddenPower.BW.type(ivs).toLowerCase()
|
|
@set("hiddenPowerType", type, silent: true)
|
|
|
|
@on 'change:hiddenPowerType', (model, type) =>
|
|
hpIVs = HiddenPower.BW.ivs[type.toLowerCase()]
|
|
ivs = @get('ivs')
|
|
for stat, iv of ivs
|
|
ivs[stat] = hpIVs[stat] || 31
|
|
@set('ivs', ivs, silent: true)
|
|
|
|
@set('ability', @getAbilities()[0]) unless attributes.ability
|
|
@set('level', @getGeneration.maxLevel) unless attributes.level
|
|
@set('happiness', 100) if isNaN(attributes.happiness)
|
|
@set('nature', 'Hardy') unless attributes.nature
|
|
hiddenPowerType = HiddenPower.BW.type(@get('ivs')).toLowerCase()
|
|
@set('hiddenPowerType', hiddenPowerType, silent: true)
|
|
|
|
# If there is no gender set and only one possiblity, set the gender
|
|
unless @has('gender')
|
|
genders = @getGenders()
|
|
@set('gender', genders[0], silent: true) if genders.length == 1
|
|
|
|
resetBoosts: ->
|
|
@set 'stages',
|
|
hp: 0
|
|
attack: 0
|
|
defense: 0
|
|
specialAttack: 0
|
|
specialDefense: 0
|
|
speed: 0
|
|
accuracy: 0
|
|
evasion: 0
|
|
normalizeStats: (hash, defaultValue) ->
|
|
stats = [ "hp", "attack", "defense", "specialAttack",
|
|
"specialDefense", "speed"]
|
|
for stat in stats
|
|
hash[stat] ?= defaultValue
|
|
|
|
getName: ->
|
|
sanitizedName = $('<div/>').text(@get('name')).html()
|
|
sanitizedName = sanitizedName.replace(
|
|
/[\u0300-\u036F\u20D0-\u20FF\uFE20-\uFE2F]/g, '')
|
|
sanitizedName
|
|
|
|
getGeneration: (generation) ->
|
|
gen = generation || @collection?.generation || DEFAULT_GENERATION
|
|
gen = gen.toUpperCase()
|
|
window.Generations[gen]
|
|
|
|
getSpecies: ->
|
|
@getGeneration().SpeciesData[@get('species')]
|
|
|
|
getItem: ->
|
|
@getGeneration().ItemData[@get('item')]
|
|
|
|
getForme: (forme, generation) ->
|
|
forme ||= @get('forme')
|
|
@getGeneration(generation).FormeData[@get('species')]?[forme]
|
|
|
|
getFormes: ->
|
|
(forme for forme of @getGeneration().FormeData[@get('species')])
|
|
|
|
# Returns all non-battle only formes
|
|
getSelectableFormes: ->
|
|
_(@getFormes()).reject((forme) => @getForme(forme).isBattleOnly)
|
|
|
|
# Returns all mega formes
|
|
getMegaFormes: ->
|
|
_(@getFormes()).reject((forme) => forme.indexOf('mega') != 0)
|
|
|
|
getAbilities: ->
|
|
forme = @getForme()
|
|
abilities = _.clone(forme.abilities)
|
|
abilities.push(forme.hiddenAbility) if forme.hiddenAbility
|
|
_.unique(abilities)
|
|
|
|
getGenders: ->
|
|
species = @getSpecies()
|
|
genders = []
|
|
switch species.genderRatio
|
|
when -1
|
|
genders.push("Genderless")
|
|
when 0
|
|
genders.push("M")
|
|
when 8
|
|
genders.push("F")
|
|
else
|
|
genders.push("M", "F")
|
|
genders
|
|
|
|
hasSelectedMove: (moveName) ->
|
|
moveName && moveName in @moves
|
|
|
|
getMovepool: ->
|
|
{SpeciesData, MoveData} = @getGeneration()
|
|
generation = GENERATION_TO_INT[@collection?.generation || DEFAULT_GENERATION]
|
|
learnset = learnableMoves(window.Generations, @attributes, generation)
|
|
|
|
# Map each move name to a move object
|
|
return _(learnset).map (moveName) ->
|
|
move = _(MoveData[moveName]).clone()
|
|
if typeof move is "undefined"
|
|
console.log(moveName)
|
|
move['name'] = moveName
|
|
move
|
|
|
|
getTotalEVs: (options = {}) ->
|
|
total = 0
|
|
for stat, value of @get("evs")
|
|
total += value if stat != options.exclude
|
|
total
|
|
|
|
getTeam: =>
|
|
@collection?.parents[0]
|
|
|
|
setIv: (stat, value) ->
|
|
ivs = _.clone(@get("ivs"))
|
|
ivs[stat] = value
|
|
@set("ivs", ivs) # trigger change event
|
|
|
|
setEv: (stat, value) ->
|
|
evs = _.clone(@get("evs"))
|
|
value = value - (value % 4)
|
|
evs[stat] = value
|
|
@set("evs", evs) # trigger change event
|
|
value
|
|
|
|
iv: (stat) ->
|
|
@get("ivs")[stat] ? 31
|
|
|
|
ev: (stat) ->
|
|
@get("evs")[stat] ? 0
|
|
|
|
natureBoost: (stat) ->
|
|
nature = @get('nature')?.toLowerCase()
|
|
if nature of natures
|
|
natures[nature][stat] || 1
|
|
else
|
|
1
|
|
|
|
base: (key) ->
|
|
forme = @getForme()
|
|
base = forme["stats"][key]
|
|
|
|
stat: (key) ->
|
|
base = @base(key)
|
|
return 1 if base == 1 # For Shedinja. key doesn't have to be hp.
|
|
level = @get('level') || @getGeneration.maxLevel
|
|
iv = @iv(key)
|
|
ev = Math.floor(@ev(key) / 4)
|
|
total = if key == 'hp'
|
|
Math.floor((2 * base + iv + ev) * (level / 100) + level + 10)
|
|
else
|
|
Math.floor(((2 * base + iv + ev) * (level / 100) + 5) * @natureBoost(key))
|
|
|
|
# Returns the natures that this pokemon can use
|
|
# The natures are returned as a list of [id, value] values
|
|
# to populate a dropdown field.
|
|
# TODO: Should this be needed in more places, return Nature objects instead
|
|
getNatures: ->
|
|
natureResults = []
|
|
for nature, stats of natures
|
|
name = nature[0].toUpperCase() + nature.substr(1)
|
|
invertedStats = _(stats).invert()
|
|
|
|
label = name
|
|
if invertedStats[PLUS]
|
|
# This nature has an effect, so update the label
|
|
plusStat = statAbbreviations[invertedStats[PLUS]]
|
|
minusStat = statAbbreviations[invertedStats[MINUS]]
|
|
label = "#{name} (+#{plusStat}, -#{minusStat})"
|
|
|
|
natureResults.push [name, label]
|
|
return natureResults
|
|
|
|
getPBV: ->
|
|
gen = @getGeneration()
|
|
PokeBattle.PBV.determinePBV(gen, @attributes)
|
|
|
|
getTier: ->
|
|
gen = @getGeneration()
|
|
PokeBattle.Tier.determineTier(gen, @attributes)
|
|
|
|
setPP: (moveIndex, newPP) ->
|
|
array = _.clone(@get('pp'))
|
|
array[moveIndex] = newPP
|
|
@set('pp', array)
|
|
|
|
getPercentHP: ->
|
|
Math.max(@get('percent'), 0)
|
|
|
|
getHPColor: ->
|
|
percent = @getPercentHP()
|
|
switch
|
|
when percent <= 20 then 'red'
|
|
when percent <= 50 then 'yellow'
|
|
else 'green'
|
|
|
|
isFainted: ->
|
|
@get('percent') <= 0
|
|
|
|
getStatus: ->
|
|
status = @get('status')
|
|
if status
|
|
"#{status[0].toUpperCase()}#{status.substr(1)}"
|
|
else
|
|
"Healthy"
|
|
|
|
canMegaEvolve: ->
|
|
# TODO: Refactor this to use getPossibleMegaForme()
|
|
# I didn't feel like making the change and testing it while implementing getPossibleMegaForme()
|
|
if @get('species') == "Rayquaza" and "Dragon Ascent" in @get('moves')
|
|
return true
|
|
item = @getItem()
|
|
return false if typeof item == "undefined"
|
|
return false if item.type != 'megastone'
|
|
[ species, forme ] = item.mega
|
|
return false if @get('species') != species || @get('forme') != 'default'
|
|
return true
|
|
|
|
getPossibleMegaForme: ->
|
|
item = @getItem()
|
|
return null if item?.type != 'megastone'
|
|
[ species, forme ] = item.mega
|
|
return forme if @get('species') == species && @get('forme') == 'default'
|
|
return null
|
|
|
|
# Returns the complete web address to the pokedex link for this pokemon.
|
|
# For this project, this leads to our website at http://www.pokebattle.com,
|
|
# but if you want it to lead somewhere else, edit this function.
|
|
getPokedexUrl: ->
|
|
# todo: move this function to /shared, or use an actual slugify library
|
|
slugify = (str) ->
|
|
str.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/\-{2,}/g, '-')
|
|
|
|
slugSpecies = slugify(@get('species'))
|
|
slugForme = slugify(@get('forme'))
|
|
"/pokemon/#{@get('species')}"
|
|
|
|
getIllu: ->
|
|
@get('illu')
|
|
|
|
setIllu: (y) ->
|
|
@set('illu', y)
|
|
|
|
toJSON: ->
|
|
attributes = _.clone(@attributes)
|
|
delete attributes.gender if attributes.gender == 'Genderless'
|
|
delete attributes.hiddenPowerType
|
|
delete attributes.teambuilder
|
|
attributes
|
|
|
|
# TODO: These shortenings really should be stored somewhere else.
|
|
statAbbreviations =
|
|
'hp' : 'HP'
|
|
'attack' : 'Atk'
|
|
'defense' : 'Def'
|
|
'specialAttack' : 'SAtk'
|
|
'specialDefense' : 'SDef'
|
|
'speed' : 'Spe'
|
|
|
|
# A hash that keys a nature with the stats that it boosts.
|
|
# Neutral natures are ignored.
|
|
# TODO: .yml-ify these.
|
|
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}
|
|
hardy: {}
|
|
docile: {}
|
|
serious: {}
|
|
bashful: {}
|
|
quirky: {}
|
|
|
|
class @NullPokemon extends Pokemon
|
|
initialize: ->
|
|
@set('species', null)
|
|
@set('forme', 'default')
|
|
@isNull = true
|
|
|
|
getNatures: -> []
|
|
getPBV: -> 0
|
|
base: -> 0
|
|
stat: -> null
|
|
iv: -> null
|
|
ev: -> null
|
|
|
|
getSpecies: ->
|
|
id: 0
|
|
genderRatio: -1
|
|
generation: 1
|
|
|
|
getForme: ->
|
|
@getFormes()['default']
|
|
|
|
getFormes: ->
|
|
default:
|
|
abilities: []
|
|
hiddenAbility: null
|
|
isBattleOnly: false
|
|
learnset: {}
|
|
types: [] |