BattleSim/shared/learnsets.coffee

228 lines
9.2 KiB
CoffeeScript

self = (module?.exports || window)
if module?.exports
EventPokemon = require('./event_pokemon')
{INT_TO_GENERATION} = require('./ladders')
{_} = require('underscore')
else
EventPokemon = window.EventPokemon ? {}
INT_TO_GENERATION = window.INT_TO_GENERATION
_ = window._
unportableGenerations = [ 1, 3 ]
unportableGenerations.sort((a, b) -> b - a) # numeric, descending
switchableFormes = [
"Deoxys"
"Rotom"
"Shaymin"
]
unsketchableMoves = [
"Chatter"
"Hyperspace Hole"
"Light of Ruin"
"Steam Eruption"
"Struggle"
"Thousand Arrows"
"Thousand Waves"
]
mustLearnMove =
"Mamoswine" : "AncientPower"
"Yanmega" : "AncientPower"
"Tangrowth" : "AncientPower"
"Mr. Mime" : "Mimic"
"Sudowoodo" : "Mimic" # Note: Only 6 egg moves exist; 4 can be learned.
"Ambipom" : "Double Hit"
"Lickilicky" : "Rollout"
getMinimumGeneration = (generation) ->
for unportableGen in unportableGenerations
if unportableGen <= generation
return unportableGen
minGeneration = Math.min(unportableGenerations...)
throw new Error("Gen. '#{generation}' must be greater than #{minGeneration}.")
getGenerationFromInt = (generationInteger) ->
INT_TO_GENERATION[generationInteger].toUpperCase()
# A helper method to loop through the learnsets for this pokemon and all
# prevolutions, and then runs an iterator function (that you give) on those
# learnsets. The iterator function takes one parameter, `learnset`, the learnset
# for a given generation for the Pokemon's current forme.
#
# All generations that can be used are taken into consideration. If the Pokemon
# has a hidden ability, but the generation doesn't support it, the iterator
# skips over that gneration.
loopLearnsets = (Generations, pokemon, forGeneration, iterator) ->
minimumGeneration = getMinimumGeneration(forGeneration)
{species, forme, ability} = pokemon
formeName = forme || "default"
# Find pre-evolutions and formes
thePokemon = []
theFormes = [ formeName ]
{SpeciesData, FormeData} = Generations[getGenerationFromInt(forGeneration)]
finalForme = FormeData[species][formeName]
while species
thePokemon.push(species)
if species in switchableFormes && species not in theFormes
theFormes.push((forme for forme of FormeData[species])...)
species = SpeciesData[species].evolvedFrom
# The Pokemon may not have a default ability (due to tests, etc)
ability ?= finalForme.abilities[0]
hasHiddenAbility = (ability == finalForme.hiddenAbility &&
ability not in finalForme.abilities)
# Loop through pre-evolutions and formes
for species in thePokemon
for formeName in theFormes
# Loop through all available generations
for generation in [minimumGeneration..forGeneration]
{FormeData} = Generations[getGenerationFromInt(generation)]
# Skip if this pokemon has no data.
continue if !FormeData[species]?
# Since we check pre-evos, the pre-evo may not have the forme that its
# parent has. We check if no forme exists; if so, we revert to default.
formeName = "default" if formeName not of FormeData[species]
# The current forme may not have a learnset (Zen mode, megas), so we
# do another check to see if it has a learnset. If not, use default.
forme = FormeData[species][formeName]
formeName = "default" if !forme.learnset
learnset = forme.learnset
# Skip if this Pokemon has no learnset for this generation.
continue if !learnset
# Skip if this Pokemon's ability is hidden and this generation has no
# hidden abilities for this forme.
continue if hasHiddenAbility && !forme.hiddenAbility?
return true if iterator(learnset, species, formeName, generation) == true
return false
# Returns an array of moves that this Pokemon can learn for a given generation.
self.learnableMoves = (Generations, pokemon, forGeneration) ->
learnable = []
loopLearnsets Generations, pokemon, forGeneration, (learnset, pokemonSpecies, formeName) ->
# Push event moves
events = EventPokemon[pokemonSpecies] || []
events = events.filter((event) -> event.forme == formeName)
for event in events
learnable.push(event.moves)
# Push learnset moves
for method, moves of learnset
if method in [ 'level-up', 'tutor', 'machine', 'egg' ] ||
((pokemon.forme || "default") == formeName) # e.g. Hydro Pump Rotom-w
learnable.push((moveName for moveName of moves))
# If the learnset includes Sketch, then we include every move.
if learnset["level-up"]?["Sketch"]
for moveName of Generations[getGenerationFromInt(forGeneration)].MoveData
if moveName not in unsketchableMoves
learnable.push(moveName)
_.chain(learnable).flatten().sort().unique().value()
# Checks the moveset of a given Pokemon for a given generation, with the given
# species and forme data for all Pokemon.
#
# Returns true if the moveset is valid, false otherwise.
self.checkMoveset = (Generations, pokemon, generation, moves) ->
looper = loopLearnsets.bind(null, Generations, pokemon, generation)
pokemonSpecies = pokemon.species
pokemonForme = pokemon.forme || "default"
pokemonLevel = (pokemon.level || generation.maxLevel)
{FormeData} = Generations[getGenerationFromInt(generation)]
forme = FormeData[pokemonSpecies][pokemonForme]
# In gen 4, pokemon must know *all* moves inside the `form-change` learnset.
if generation == 4
rsForme = Generations.DP.FormeData[pokemonSpecies]?[pokemonForme] || {}
learnset = rsForme.learnset?['form-change'] || {}
for move, level of learnset
return false if move not in moves || pokemonLevel < level
# Get a list of all moves that the Pokemon can't learn
# through level-up, tutoring, machines, or Sketch.
leftoverMoves = (m for m in moves when !checkMove(looper, pokemon, m))
# Continuing the `forme-change` learnset group.
# Get rid of leftover moves if this pokemon can learn it in this generation.
learnset = forme.learnset?['form-change'] || {}
lsetLeftovers = leftoverMoves.filter((move) -> pokemonLevel >= learnset[move])
return true if lsetLeftovers.length == leftoverMoves.length
# Check against event Pokemon
# TODO: Event Pokemon require more stringent checks, e.g. gender/ability etc.
checksOut = looper (learnset, pokemonSpecies, formeName) ->
events = EventPokemon[pokemonSpecies] || []
events = events.filter((event) -> event.forme == formeName)
for event in events
lsetLeftovers = leftoverMoves.filter (move) ->
move in event.moves && pokemonLevel >= event.level
return true if lsetLeftovers.length == leftoverMoves.length
return true if checksOut
# These learnset groups are non-standard but can be used. If a non-standard
# group completely overlaps the leftover moves, the moveset is valid.
nonstandardGroups = [ "light-ball-egg", "stadium-surfing-pikachu" ]
checksOut = looper (learnset) ->
for group in nonstandardGroups
continue if !learnset[group]
total = (m for m in leftoverMoves when m of learnset[group]).length
return true if total == leftoverMoves.length
return true if checksOut
# If the remaining moves are all dream world moves, it's a valid moveset.
checksOut = looper (learnset) ->
return if !learnset['dreamWorld']
dreamWorldMoves = []
for moveName of learnset['dreamWorld']
continue if moveName in dreamWorldMoves || moveName not in leftoverMoves
dreamWorldMoves.push(moveName)
return true if leftoverMoves.length == dreamWorldMoves.length
return true if checksOut
# If the remaining moves are all egg moves, the moveset is valid.
# TODO: Check chain-breeding for gens 5 and under.
eggMoves = []
looper (learnset) ->
return if !learnset['egg']
for moveName of learnset['egg']
continue if moveName in eggMoves || moveName not in leftoverMoves
eggMoves.push(moveName)
# If the Pokemon has to know a certain move to evolve, check the egg moves
# since you cannot have a moveset made completely of pure egg moves.
# A magic constant of 4 is used to imitate the game's maximum of 4 moves.
return false if eggMoves.length == 4 && mustLearnMove[pokemon.species]
return true if eggMoves.length == leftoverMoves.length
# This Pokemon cannot learn all these moves. Sorry.
return false
# Checks a single move to see if the Pokemon can learn it through level-up,
# tutors, machines, Sketch, or pre-evolutions.
checkMove = (looper, pokemon, move) ->
{level} = pokemon
level ||= 120
checksOut = looper (learnset) ->
# Check level-up, TM/HM, and tutors.
return true if learnset["level-up"]?[move] <= level ||
learnset["machine"]?[move] <= level ||
learnset["tutor"]?[move] <= level
# If the Pokemon can learn Sketch, then by golly, it can learn anything!
# ... Except Chatter and Struggle. NOTE: Bogus moves are considered valid,
# so you must take care of them at a higher level.
# Unreleased event moves are also unsketchable until release.
return true if learnset["level-up"]?["Sketch"] <= level &&
move not in unsketchableMoves
return true if checksOut
# TODO: Skip unavailable Pokemon (due to being a generation later).
# TODO: level-up moves can be bred.
return false