BattleSim/test/bw/pokemon.coffee

493 lines
17 KiB
CoffeeScript

require '../helpers'
{Battle} = require('../../server/bw/battle')
{Weather} = require('../../shared/weather')
{Pokemon} = require('../../server/bw/pokemon')
{Status, Attachment, BaseAttachment, VolatileAttachment} = require('../../server/bw/attachment')
{Moves, SpeciesData} = require('../../server/bw/data')
{Protocol} = require '../../shared/protocol'
{Factory} = require '../factory'
should = require 'should'
shared = require '../shared'
describe 'Pokemon', ->
it 'should have a species of Missingno by default', ->
new Pokemon().species.should.equal 'Missingno'
it 'can change the default species', ->
new Pokemon(species: 'Pikachu').species.should.equal 'Pikachu'
it 'should have a level of 100 by default', ->
new Pokemon().level.should.equal 100
it 'can change the default level', ->
new Pokemon(level: 5).level.should.equal 5
it 'gets its current hp populated from its max hp', ->
new Pokemon().currentHP.should.equal 341
it 'has pp for each move', ->
pokemon = new Pokemon(moves: ["Tackle", "Splash"])
pokemon.pp(Moves['Tackle']).should.equal 35 * 8/5
pokemon.pp(Moves['Splash']).should.equal 40 * 8/5
describe '#iv', ->
it 'has default iv of 31', ->
new Pokemon().iv('hp').should.equal 31
it 'retrieves iv successfully', ->
new Pokemon(ivs: {'hp': 25}).iv('hp').should.equal 25
it "doesn't default to 31 if iv is 0", ->
new Pokemon(ivs: {'hp': 0}).iv('hp').should.equal 0
describe '#ev', ->
it 'has default ev of 0', ->
new Pokemon().ev('hp').should.equal 0
it 'retrieves ev successfully', ->
new Pokemon(evs: {hp: 25}).ev('hp').should.equal 25
describe '#stat', ->
it 'calculates hp correctly', ->
pokemon = new Pokemon(level: 100, evs: { hp: 255 })
pokemon.stat('hp').should.equal 404
pokemon = new Pokemon(level: 50, evs: { hp: 255 })
pokemon.stat('hp').should.equal 207
# todo: test other numbers later
it 'calculates 1 base HP correctly', ->
pokemon = new Pokemon(level: 100, evs: { hp: 255 })
pokemon.baseStats.hp = 1
pokemon.stat('hp').should.equal 1
it 'calculates other stats correctly', ->
pokemon = new Pokemon(level: 100, evs: { attack: 255 })
pokemon.stat('attack').should.equal 299
pokemon = new Pokemon(level: 50, evs: { attack: 255 })
pokemon.stat('attack').should.equal 152
# todo: test other numbers later
it "calculates a stat with a nature boost correctly", ->
pokemon = new Pokemon(nature: 'Adamant')
pokemon.stat('attack').should.equal 259
it "calculates a stat with a nature decrease correctly", ->
pokemon = new Pokemon(nature: 'Bold')
pokemon.stat('attack').should.equal 212
describe 'stat boosts', ->
it 'increase the stat by (n+2)/2 if positive', ->
pokemon = new Pokemon()
speed = pokemon.stat('speed')
pokemon.stages.speed = 3
pokemon.stat('speed').should.equal Math.floor(2.5 * speed)
it 'decrease the stat by 2/(n+2) if negative', ->
pokemon = new Pokemon()
speed = pokemon.stat('speed')
pokemon.stages.speed = -3
pokemon.stat('speed').should.equal Math.floor(speed / 2.5)
describe '#natureBoost', ->
it "returns 1 by default for non-existent natures", ->
new Pokemon(nature: 'Super').natureBoost('attack').should.equal 1
it "returns 1.1 for natures that boost a certain stat", ->
new Pokemon(nature: 'Adamant').natureBoost('attack').should.equal 1.1
it "returns 1.0 for natures do not affect a certain stat", ->
new Pokemon(nature: 'Adamant').natureBoost('speed').should.equal 1
it "returns 0.9 for natures that decrease a certain stat", ->
new Pokemon(nature: 'Timid').natureBoost('attack').should.equal 0.9
describe '#hasType', ->
it 'returns false if the pokemon does not have that type', ->
new Pokemon().hasType('Grass').should.be.false
it 'returns true if the pokemon has that type', ->
pokemon = new Pokemon()
pokemon.types = ['Dark', 'Grass']
pokemon.hasType('Grass').should.be.true
describe '#switchOut', ->
it 'resets stat boosts', ->
pokemon = new Pokemon()
pokemon.boost(specialAttack: 2)
pokemon.switchOut()
pokemon.stages['specialAttack'].should.equal 0
it 'removes blocked moves', ->
pokemon = new Pokemon(moves: ['Earthquake'])
pokemon.blockMove(Moves['Earthquake'])
pokemon.switchOut()
pokemon.isMoveBlocked(Moves['Earthquake']).should.be.false
it 'removes volatile attachments', ->
pokemon = new Pokemon()
pokemon.attach(VolatileAttachment)
pokemon.switchOut()
pokemon.has(VolatileAttachment).should.be.false
describe '#endTurn', ->
it 'removes blocked moves', ->
pokemon = new Pokemon(moves: ['Earthquake'])
pokemon.blockMove(Moves['Earthquake'])
pokemon.switchOut()
pokemon.isMoveBlocked(Moves['Earthquake']).should.be.false
describe '#attach', ->
it 'adds an attachment to a list of attachments', ->
pokemon = new Pokemon()
pokemon.attach(BaseAttachment)
pokemon.has(BaseAttachment).should.be.true
describe '#unattach', ->
it 'removes an attachment from the list of attachments', ->
pokemon = new Pokemon()
pokemon.attach(BaseAttachment)
pokemon.unattach(BaseAttachment)
pokemon.has(BaseAttachment).should.be.false
describe '#unattachAll', ->
it 'removes every attachment', ->
pokemon = new Pokemon()
pokemon.attach(BaseAttachment)
pokemon.attach(VolatileAttachment)
pokemon.unattachAll()
pokemon.has(BaseAttachment).should.be.false
pokemon.has(VolatileAttachment).should.be.false
describe '#blockMove', ->
it 'adds a move to a list of blocked moves', ->
pokemon = new Pokemon(moves: ['Earthquake'])
pokemon.blockMove(Moves['Earthquake'])
pokemon.blockedMoves.should.containEql Moves['Earthquake']
describe '#isMoveBlocked', ->
it 'returns true if the move is blocked', ->
pokemon = new Pokemon(moves: ['Earthquake'])
pokemon.blockMove(Moves['Earthquake'])
pokemon.isMoveBlocked(Moves['Earthquake']).should.be.true
it 'returns false if the move is not blocked', ->
pokemon = new Pokemon(moves: ['Earthquake'])
pokemon.isMoveBlocked(Moves['Earthquake']).should.be.false
describe '#validMoves', ->
it 'returns moves without blocked moves', ->
pokemon = new Pokemon(moves: ['Splash', 'Earthquake'])
pokemon.blockMove(Moves['Earthquake'])
pokemon.validMoves().should.eql([ Moves['Splash'] ])
it 'excludes moves with 0 PP', ->
pokemon = new Pokemon(moves: ['Splash', 'Earthquake'])
move = pokemon.moves[0]
otherMoves = pokemon.moves.filter((m) -> m != move)
pokemon.setPP(move, 0)
pokemon.validMoves().should.eql(otherMoves)
describe '#reducePP', ->
it 'reduces PP of a move by 1', ->
pokemon = new Pokemon(moves: ['Splash', 'Earthquake'])
move = Moves['Splash']
pokemon.reducePP(move)
pokemon.pp(move).should.equal pokemon.maxPP(move) - 1
it 'does not go below 0', ->
pokemon = new Pokemon(moves: ['Splash', 'Earthquake'])
move = Moves['Splash']
for x in [0..pokemon.maxPP(move)]
pokemon.reducePP(move)
pokemon.pp(move).should.equal 0
describe '#setPP', ->
it "sets the PP of a move", ->
pokemon = new Pokemon(moves: ['Splash', 'Earthquake'])
move = Moves['Splash']
pokemon.setPP(move, 1)
pokemon.pp(move).should.equal 1
it "cannot go below 0", ->
pokemon = new Pokemon(moves: ['Splash', 'Earthquake'])
move = Moves['Splash']
pokemon.setPP(move, -1)
pokemon.pp(move).should.equal 0
it "cannot go above the max PP possible", ->
pokemon = new Pokemon(moves: ['Splash', 'Earthquake'])
move = Moves['Splash']
pokemon.setPP(move, pokemon.maxPP(move) + 1)
pokemon.pp(move).should.equal pokemon.maxPP(move)
describe '#positiveBoostCount', ->
it "returns the number of boosts higher than 0", ->
pokemon = new Pokemon()
pokemon.boost(attack: 4, defense: -3, speed: 1)
pokemon.positiveBoostCount().should.equal 5
describe '#isItemBlocked', ->
it 'returns true if the Pokemon has no item', ->
pokemon = new Pokemon()
pokemon.isItemBlocked().should.be.true
describe "attaching statuses", ->
it "returns null if attaching too many statuses", ->
pokemon = new Pokemon()
pokemon.attach(Status.Freeze)
should.not.exist pokemon.attach(Status.Paralyze)
it "does not override statuses", ->
pokemon = new Pokemon()
pokemon.attach(Status.Freeze)
pokemon.attach(Status.Paralyze)
pokemon.has(Status.Freeze).should.be.true
pokemon.has(Status.Paralyze).should.be.false
it "sets the status of the pokemon", ->
for species, status of Status
pokemon = new Pokemon()
pokemon.attach(status)
pokemon.status.should.equal(status)
it "sets the corresponding attachment on the pokemon", ->
for species, status of Status
pokemon = new Pokemon()
pokemon.attach(status)
pokemon.has(status).should.be.true
it "doesn't poison Poison types", ->
pokemon = new Pokemon()
pokemon.types = ["Poison"]
pokemon.attach(Status.Poison)
pokemon.attach(Status.Toxic)
pokemon.has(Status.Poison).should.be.false
pokemon.has(Status.Toxic).should.be.false
it "doesn't poison Steel types", ->
pokemon = new Pokemon()
pokemon.types = ["Steel"]
pokemon.attach(Status.Poison)
pokemon.attach(Status.Toxic)
pokemon.has(Status.Poison).should.be.false
pokemon.has(Status.Toxic).should.be.false
it "doesn't burn Fire types", ->
pokemon = new Pokemon()
pokemon.types = ["Fire"]
pokemon.attach(Status.Burn)
pokemon.has(Status.Burn).should.be.false
it "doesn't freeze Ice types", ->
pokemon = new Pokemon()
pokemon.types = ["Ice"]
pokemon.attach(Status.Freeze)
pokemon.has(Status.Freeze).should.be.false
it "doesn't freeze under Sun", ->
battle = new Battle('id', {"a": [], "b": []})
battle.setWeather(Weather.SUN)
pokemon = new Pokemon(battle: battle)
pokemon.attach(Status.Freeze)
pokemon.has(Status.Freeze).should.be.false
describe "#cureStatus", ->
it "removes all statuses if no argument is passed", ->
pokemon = new Pokemon()
for species, status of Status
pokemon.attach(status)
pokemon.cureStatus()
pokemon.hasStatus().should.be.false
it "removes only a certain status if an argument is passed", ->
pokemon = new Pokemon()
pokemon.attach(Status.Freeze)
pokemon.cureStatus(Status.Paralyze)
pokemon.has(Status.Freeze).should.be.true
describe '#hasTakeableItem', ->
it "returns false if the pokemon has no item", ->
pokemon = new Pokemon()
pokemon.hasTakeableItem().should.be.false
it "returns true if the item can be taken", ->
pokemon = new Pokemon(item: "Leftovers")
pokemon.hasTakeableItem().should.be.true
it "returns false if the pokemon has a mail", ->
pokemon = new Pokemon(item: "Air Mail")
pokemon.hasTakeableItem().should.be.false
it "returns false if the pokemon has a key item", ->
pokemon = new Pokemon(item: "Acro Bike")
pokemon.hasTakeableItem().should.be.false
it "returns false if the pokemon has Multitype and a plate", ->
pokemon = new Pokemon(ability: "Multitype", item: "Draco Plate")
pokemon.hasTakeableItem().should.be.false
it "returns false if the pokemon is Giratina-O", ->
pokemon = new Pokemon(species: "Giratina", forme: "origin", item: "Griseous Orb")
pokemon.hasTakeableItem().should.be.false
it "returns false if the pokemon is Genesect with a Drive item", ->
pokemon = new Pokemon(species: "Genesect", item: "Burn Drive")
pokemon.hasTakeableItem().should.be.false
describe "#isWeatherDamageImmune", ->
it "returns true if it's hailing and the Pokemon is Ice type", ->
pokemon = new Pokemon()
pokemon.types = [ "Ice" ]
pokemon.isWeatherDamageImmune(Weather.HAIL).should.be.true
it "returns true if it's sandstorming and the Pokemon is Rock type", ->
pokemon = new Pokemon()
pokemon.types = [ "Rock" ]
pokemon.isWeatherDamageImmune(Weather.SAND).should.be.true
it "returns true if it's sandstorming and the Pokemon is Steel type", ->
pokemon = new Pokemon()
pokemon.types = [ "Steel" ]
pokemon.isWeatherDamageImmune(Weather.SAND).should.be.true
it "returns true if it's sandstorming and the Pokemon is Ground type", ->
pokemon = new Pokemon()
pokemon.types = [ "Ground" ]
pokemon.isWeatherDamageImmune(Weather.SAND).should.be.true
it "returns false otherwise", ->
pokemon = new Pokemon()
pokemon.types = [ "Grass" ]
pokemon.isWeatherDamageImmune(Weather.SAND).should.be.false
pokemon.isWeatherDamageImmune(Weather.HAIL).should.be.false
describe "#useItem", ->
it "records the item in lastItem", ->
pokemon = new Pokemon(item: "Leftovers")
pokemon.activate()
pokemon.useItem()
should.exist pokemon.lastItem
pokemon.lastItem.displayName.should.equal("Leftovers")
it "removes the item", ->
pokemon = new Pokemon(item: "Leftovers")
pokemon.activate()
pokemon.useItem()
pokemon.hasItem().should.be.false
describe "#removeItem", ->
it "removes the item", ->
pokemon = new Pokemon(item: "Leftovers")
pokemon.activate()
pokemon.removeItem()
pokemon.hasItem().should.be.false
it "does not remove prior records of an item", ->
pokemon = new Pokemon(item: "Flying Gem")
fake = new Pokemon(item: "Leftovers")
pokemon.activate()
fake.activate()
pokemon.useItem()
pokemon.setItem(fake.getItem())
pokemon.removeItem()
should.exist pokemon.lastItem
describe '#changeForme', ->
it "changes the Pokemon's forme from one to another", ->
pokemon = new Pokemon(species: 'Shaymin')
pokemon.forme.should.equal('default')
pokemon.changeForme('sky')
pokemon.forme.should.equal('sky')
it 'changes base stats if applicable', ->
pokemon = new Pokemon(species: 'Shaymin')
pokemon.baseStats.should.eql(
attack: 100, defense: 100, hp: 100,
specialAttack: 100, specialDefense: 100, speed: 100)
pokemon.changeForme('sky')
pokemon.baseStats.should.eql(
attack: 103, defense: 75, hp: 100,
specialAttack: 120, specialDefense: 75, speed: 127)
it 'changes weight if applicable', ->
pokemon = new Pokemon(species: 'Shaymin')
pokemon.weight.should.equal(21)
pokemon.changeForme('sky')
pokemon.weight.should.equal(52)
it 'changes type if applicable', ->
pokemon = new Pokemon(species: 'Shaymin')
pokemon.types.should.eql([ 'Grass' ])
pokemon.changeForme('sky')
pokemon.types.should.eql([ 'Grass', 'Flying' ])
it 'does nothing if the new forme is an invalid forme', ->
pokemon = new Pokemon(species: 'Shaymin')
pokemon.forme.should.equal('default')
pokemon.changeForme('batman')
pokemon.forme.should.equal('default')
describe "#toJSON", ->
it "can hide information", ->
pokemon = new Pokemon(species: 'Shaymin')
json = pokemon.toJSON(hidden: true)
json.should.not.have.property("hp")
json.should.not.have.property("maxHP")
json.should.not.have.property("moves")
json.should.not.have.property("moveTypes")
json.should.not.have.property("pp")
json.should.not.have.property("maxPP")
json.should.not.have.property("ivs")
it "shows all information otherwise", ->
pokemon = new Pokemon(species: 'Shaymin')
json = pokemon.toJSON()
json.should.have.property("hp")
json.should.have.property("maxHP")
json.should.have.property("moves")
json.should.have.property("moveTypes")
json.should.have.property("pp")
json.should.have.property("maxPP")
json.should.have.property("ivs")
describe "Shedinja holding Focus Sash", ->
it "faints from burn damage", ->
shared.create.call(this, team1: [Factory("Shedinja", item: "Focus Sash")])
@p1.attach(Status.Burn)
@p1.currentHP.should.equal(1)
@battle.endTurn()
@p1.currentHP.should.equal(0)
it "faints from switching into hazards", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Shedinja", item: "Focus Sash")]
@battle.performMove(@p1, @battle.getMove("Stealth Rock"))
@battle.performSwitch(@team2.first(), 1)
pokemon = @team2.first()
(pokemon.stat('hp') - pokemon.currentHP).should.equal(1)
it "faints from Perish Song", ->
shared.create.call(this, team1: [Factory("Shedinja", item: "Focus Sash")])
@battle.performMove(@p2, @battle.getMove('Perish Song'))
@battle.endTurn() for x in [0..3]
@p1.isFainted().should.be.true
it "faints from Destiny Bond", ->
shared.create.call(this, team1: [Factory("Shedinja", item: "Focus Sash")])
@p2.currentHP = 1
@battle.performMove(@p2, @battle.getMove("Destiny Bond"))
@battle.performMove(@p1, @battle.getMove("Tackle"))
@p1.isFainted().should.be.true
it "faints from using Final Gambit", ->
shared.create.call(this, team1: [Factory("Shedinja", item: "Focus Sash")])
finalGambit = @battle.getMove("Final Gambit")
@battle.performMove(@p1, finalGambit)
@p1.isFainted().should.be.true