BattleSim/client/app/js/models/battles/pokemon.coffee

355 lines
9.9 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', 100) 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()
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') || 100
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 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'))
"//pokebattle.com/dex/pokemon/#{slugSpecies}/#{slugForme}"
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: []