1
0
mirror of https://gitlab.com/Deukhoofd/BattleSim.git synced 2025-10-27 18:00:03 +00:00

Lots of stuff

This commit is contained in:
Deukhoofd
2016-02-01 23:19:30 +01:00
commit d7316d5799
6681 changed files with 527969 additions and 0 deletions

92
test/alts_spec.coffee Normal file
View File

@@ -0,0 +1,92 @@
require('./helpers')
should = require('should')
alts = require('../server/alts')
describe "Alts", ->
describe '#isAltNameValid', ->
it 'allows alphanumeric and - and _ characters', ->
alts.isAltNameValid("im-DA_best 251").should.be.true
it 'blocks invalid alt names', ->
alts.isAltNameValid().should.be.false
alts.isAltNameValid("").should.be.false
alts.isAltNameValid("itsme:").should.be.false
alts.isAltNameValid("Blue Kirby").should.be.false
alts.isAltNameValid(("a" for x in [0...16]).join('')).should.be.false
describe '#createAlt', ->
it 'creates a new alt', (done) ->
alts.createAlt "player1", "TEST", (err) ->
should.not.exist(err)
alts.listUserAlts "player1", (err, results) ->
results.length.should.eql 1
done()
it 'fails if the alt name is already being used', (done) ->
alts.createAlt "player1", "TEST", ->
alts.createAlt "player1", "TEST", (err) ->
should.exist(err)
done()
it 'fails if the user already has 5 alts', (done) ->
alts.createAlt "player1", "TEST1", (err) ->
should.not.exist(err)
alts.createAlt "player1", "test2", (err) ->
should.not.exist(err)
alts.createAlt "player1", "test3", (err) ->
should.not.exist(err)
alts.createAlt "player1", "test4", (err) ->
should.not.exist(err)
alts.createAlt "player1", "test5", (err) ->
should.not.exist(err)
alts.createAlt "player1", "test6", (err) ->
should.exist(err)
done()
describe '#listUserAlts', ->
it 'returns the same number of alts that were created', (done) ->
alts.createAlt "player1", "TEST1", (err) ->
alts.createAlt "player1", "test2", (err) ->
alts.listUserAlts "player1", (err, alts) ->
["TEST1", "test2"].should.eql(alts)
done()
describe '#isAltOwnedBy', ->
it 'returns false if the user does not own the alt', (done) ->
alts.isAltOwnedBy "player1", "test", (err, result) ->
should.not.exist(err)
result.should.be.false
# make it so another user owns the alt. It should still be false
alts.createAlt "anotherguy", "test", (err) ->
should.not.exist(err)
alts.isAltOwnedBy "player1", "test", (err, result) ->
should.not.exist(err)
result.should.be.false
done()
it 'returns true if the user owns the alt', (done) ->
# make it so another user owns the alt. It should still be false
alts.createAlt "player1", "test", (err) ->
should.not.exist(err)
alts.isAltOwnedBy "player1", "test", (err, result) ->
should.not.exist(err)
result.should.be.true
done()
it 'returns true on null alt name', (done) ->
alts.isAltOwnedBy "player1", null, (err, result) ->
result.should.be.true
done()
describe '#getIdOwner', ->
it 'reverses #uniqueId', ->
id = "atestId"
uniqueId = alts.uniqueId(id, "altName")
uniqueId.should.not.equal(id)
alts.getIdOwner(uniqueId).should.equal(id)
it 'returns the given id if the id does not belong to an alt', ->
id = "atestId"
alts.getIdOwner(id).should.equal(id)

166
test/api/api_spec.coffee Normal file
View File

@@ -0,0 +1,166 @@
require '../helpers'
restify = require('restify')
{Factory} = require("../factory")
before (done) ->
@PORT = 8083
require('../../api').createServer(@PORT, done)
describe 'XY API:', ->
beforeEach ->
@client = restify.createJsonClient
version: '*'
url: "http://127.0.0.1:#{@PORT}"
describe '/xy/items', ->
it 'should get an array of items back', (done) ->
@client.get '/xy/items', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("items")
data.items.length.should.be.greaterThan(0)
done()
describe '/xy/moves', ->
it 'should get a hash of moves back', (done) ->
@client.get '/xy/moves', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("Tackle")
done()
describe '/xy/moves/:name', ->
it 'should get an array of pokemon that can learn that move', (done) ->
@client.get '/xy/moves/sketch', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("pokemon")
data.pokemon.should.eql([["Smeargle", "default"]])
done()
it 'takes into account event pokemon', (done) ->
@client.get '/xy/moves/extremespeed', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("pokemon")
data.pokemon.should.containEql(["Genesect", "default"])
done()
it 'takes into account pre-evolution moves', (done) ->
@client.get '/xy/moves/pursuit', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("pokemon")
data.pokemon.should.containEql(["Tyranitar", "default"])
done()
describe '/xy/abilities', ->
it 'should get an array of abilities back', (done) ->
@client.get '/xy/abilities', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("abilities")
data.abilities.length.should.be.greaterThan(0)
done()
describe '/xy/abilities/:name', ->
it 'should get an array of pokemon that have that ability', (done) ->
@client.get '/xy/abilities/air-lock', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("pokemon")
data.pokemon.should.eql([["Rayquaza", "default"]])
done()
it "encodes pokemon names properly", (done) ->
@client.get '/xy/abilities/poison-point', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("pokemon")
res.headers['content-type'].should.containEql("charset=utf8")
data.pokemon.should.containEql(["Nidoran♀", "default"])
data.pokemon.should.containEql(["Nidoran♂", "default"])
done()
describe '/xy/types', ->
it 'should get an array of all available types', (done) ->
@client.get '/xy/types', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("types")
data.types.should.containEql("Fairy")
done()
describe '/xy/types/:name', ->
it 'should get an array of pokemon that have that type', (done) ->
@client.get '/xy/types/water', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("pokemon")
data.pokemon.should.containEql(["Greninja", "default"])
done()
describe '/xy/pokemon', ->
it 'should get forme data for all pokemon', (done) ->
@client.get '/xy/pokemon', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("Charizard")
data.should.have.property("Mandibuzz")
data.should.have.property("Trevenant")
done()
describe '/xy/pokemon/:name', ->
it 'should get species data for that pokemon', (done) ->
@client.get '/xy/pokemon/charizard', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("default")
data.should.have.property("mega-x")
data.should.have.property("mega-y")
done()
describe '/xy/pokemon/:name/moves', ->
it 'should get all moves that pokemon can learn', (done) ->
@client.get '/xy/pokemon/charizard/moves', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("moves")
data.moves.should.be.an.instanceOf(Array)
data.moves.should.containEql("Fire Blast")
done()
describe '/xy/pokemon/:name/:forme/moves', ->
it 'should get all moves that pokemon in that forme can learn', (done) ->
@client.get '/xy/pokemon/rotom/wash/moves', (err, req, res, data) ->
throw new Error(err) if err
data.should.be.an.instanceOf(Object)
data.should.have.property("moves")
data.moves.should.be.an.instanceOf(Array)
data.moves.should.containEql("Hydro Pump")
data.moves.should.not.containEql("Overheat")
done()
describe '/xy/damagecalc', ->
createParams = ->
params =
move: "Fire Punch"
attacker: Factory("Hitmonchan", {
nature: "Adamant"
ability: "Iron Fist"
evs: {attack: 252}
item: "Life Orb"
})
defender: Factory("Hitmonlee")
it 'works for an existing move', (done) ->
params = createParams()
@client.put '/xy/damagecalc', params, (err, req, res, data) ->
throw new Error(err) if err
data.minDamage.should.equal(200)
data.maxDamage.should.equal(236)
data.basePower.should.equal(75)
data.moveType.should.equal("Fire")
data.defenderMaxHP.should.equal(241)
done()

View File

@@ -0,0 +1,224 @@
require './helpers'
should = require 'should'
async = require 'async'
{BattleQueue} = require('../server/queue')
redis = require('../server/redis')
ratings = require('../server/ratings')
alts = require('../server/alts')
async = require('async')
describe 'BattleQueue', ->
it 'should be empty by default', ->
new BattleQueue().should.have.length(0)
describe '#add', ->
it 'queues a new player', ->
queue = new BattleQueue()
queue.add('derp', 'derp', {})
queue.should.have.length 1
it 'queues two players', ->
queue = new BattleQueue()
queue.add('batman', 'batman', {})
queue.add('superman', 'superman', {})
queue.should.have.length 2
it 'cannot queue the same player twice', ->
queue = new BattleQueue()
queue.add('batman', 'batman', {})
queue.add('batman', 'batman', {})
queue.should.have.length 1
it 'cannot queue falsy references', ->
queue = new BattleQueue()
queue.add(null, null, {})
queue.add(false, false, {})
queue.add(undefined, undefined, {})
queue.should.have.length 0
describe '#remove', ->
it 'can dequeue old players', ->
queue = new BattleQueue()
player = 'abc'
queue.add(player, player, {})
queue.remove(player)
queue.size().should.equal 0
it "can take an array of players", ->
queue = new BattleQueue()
player1 = 'abc'
player2 = 'def'
queue.add(player1, player1, {})
queue.add(player2, player2, {})
queue.remove([ player1, player2 ])
queue.should.have.length 0
describe '#queuedPlayers', ->
it 'returns the players who are queued', ->
queue = new BattleQueue()
dude = 'dude'
queue.add(dude)
queue.queuedPlayers().should.containEql(dude)
queue.queuedPlayers().should.have.length 1
describe '#hasRecentlyMatched', ->
it "returns false if two players have not queued", ->
queue = new BattleQueue()
queue.hasRecentlyMatched("p1", "p2").should.be.false
it "returns true if two players have queued", ->
queue = new BattleQueue()
queue.addRecentMatch("p1", "p2")
queue.hasRecentlyMatched("p1", "p2").should.be.true
it "is not affected by ordering", ->
queue = new BattleQueue()
queue.addRecentMatch("p1", "p2")
queue.hasRecentlyMatched("p1", "p2").should.be.true
queue.hasRecentlyMatched("p2", "p1").should.be.true
it "returns false if 30 minutes has passed since two players have queued", ->
queue = new BattleQueue()
queue.addRecentMatch("p1", "p2")
queue.hasRecentlyMatched("p1", "p2").should.be.true
@clock.tick(31 * 60 * 1000)
queue.hasRecentlyMatched("p1", "p2").should.be.false
describe '#pairPlayers', ->
it 'takes players out of the queue', (done) ->
queue = new BattleQueue()
queue.add('batman')
queue.add('superman')
queue.pairPlayers ->
queue.queuedPlayers().should.be.empty
done()
it 'leaves one person out if the queue length is odd', (done) ->
queue = new BattleQueue()
queue.add('batman')
queue.add('superman')
queue.add('flash')
queue.pairPlayers ->
queue.queuedPlayers().should.have.length 1
done()
it 'returns an array of pairs', (done) ->
queue = new BattleQueue()
queue.add('batman', 'Bruce Wayne')
queue.add('superman', 'Clark Kent')
queue.add('flash', 'Wally West')
queue.add('spiderman', 'Peter Parker')
queue.pairPlayers (err, results) ->
should.not.exist(err)
should.exist(results)
results.should.be.instanceOf(Array)
results.should.have.length(2)
done()
it 'returns id/name/team/ratingkeyobjects', (done) ->
queue = new BattleQueue()
queue.add('batman', 'Bruce Wayne', [], 'bat')
queue.add('superman', 'Clark Kent', [], 'supes')
queue.pairPlayers (err, results) ->
should.not.exist(err)
should.exist(results)
results.should.eql [[
{id: 'batman', name: 'Bruce Wayne', team: [], ratingKey: 'bat' }
{id: 'superman', name: 'Clark Kent', team: [], ratingKey: 'supes' }
]]
done()
it "returns an array of pairs in the order of their rating", (done) ->
scores = [["batman", 1], ["superman", 4], ["flash", 3], ["spiderman", 2]]
callbacks = for [player, score] in scores
ratings.setRating.bind(ratings, player, score)
async.parallel callbacks, ->
queue = new BattleQueue()
queue.add(pair[0]) for pair in scores
queue.pairPlayers (err, results) ->
should.not.exist(err)
should.exist(results)
results.should.be.instanceOf(Array)
results.should.have.length(2)
results = results.map (result) ->
[result[0].id, result[1].id]
results.should.eql [[ "batman", "spiderman" ]
[ "flash", "superman" ]]
done()
it "does not match the same players twice", (done) ->
scores = [["batman", 1], ["superman", 4], ["flash", 3], ["spiderman", 2]]
callbacks = for [player, score] in scores
ratings.setRating.bind(ratings, player, score)
async.parallel callbacks, ->
queue = new BattleQueue()
queue.add(pair[0]) for pair in scores
queue.pairPlayers (err, results) ->
should.not.exist(err)
results = results.map (result) ->
[result[0].id, result[1].id]
results.should.eql [[ "batman", "spiderman" ]
[ "flash", "superman" ]]
# now perform round two: Should get different results
queue.add(pair[0]) for pair in scores
queue.pairPlayers (err, results) ->
should.not.exist(err)
results = results.map (result) ->
[result[0].id, result[1].id]
results.should.eql [[ "batman", "flash" ]
[ "spiderman", "superman" ]]
done()
it "does not match players with a large rating gap until it expands", (done) ->
scores = [["batman", 900], ["superman", 1005]]
callbacks = for [player, score] in scores
ratings.setRating.bind(ratings, player, score)
async.parallel callbacks, ->
queue = new BattleQueue()
queue.add(pair[0]) for pair in scores
# first run - no matches should be found
queue.pairPlayers (err, results) ->
should.not.exist(err)
results.should.have.length 0
# second run - should have found a match
queue.pairPlayers (err, results) ->
should.not.exist(err)
results.should.have.length 1
done()
it "returns a different ordering for alts ratings", (done) ->
users = [
["batman", "Bruce Wayne", 1, 1]
["superman", "Clark Kent", 4, 4]
["flash", "Wally West", 3, 2]
["spiderman", "Peter Parker", 2, 3]
]
ratingTasks = []
for user in users
# non-alt
ratingTasks.push ratings.setRating.bind(null, user[0], user[2])
# alt
altId = alts.uniqueId(user[0], user[1])
ratingTasks.push ratings.setRating.bind(this, altId, user[3])
async.series ratingTasks, ->
queue = new BattleQueue()
queue.add(user[0]) for user in users
queue.pairPlayers (err, results) ->
should.not.exist(err)
results = results.map((result) -> [result[0].id, result[1].id])
results.should.eql [[ "batman", "spiderman" ], [ "flash", "superman" ]]
# now test alts getting added
queue.add(user[0], user[1], null, alts.uniqueId(user[0], user[1])) for user in users
queue.pairPlayers (err, results) ->
should.not.exist(err)
results = results.map((result) -> [result[0].id, result[1].id])
results.should.eql [["batman", "flash"], ["spiderman", "superman"]]
done()

2778
test/bw/abilities.coffee Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,68 @@
require '../helpers'
{Attachments, Attachment, BaseAttachment} = require '../../server/bw/attachment'
should = require 'should'
describe "An Attachment list", ->
class TestAttachment extends BaseAttachment
name: "TestAttachment"
maxLayers: 2
class OtherAttachment extends BaseAttachment
name: "OtherAttachment"
beforeEach ->
@attachments = new Attachments()
it "will not add attachments past the maximum stack", ->
should.exist @attachments.push(TestAttachment)
should.exist @attachments.push(TestAttachment)
should.not.exist @attachments.push(TestAttachment)
describe '#unattach', ->
it "removes the current attachment", ->
@attachments.push(TestAttachment)
@attachments.unattach(TestAttachment)
@attachments.attachments.should.have.length(0)
it "does not remove other attachments if none is found", ->
@attachments.push(TestAttachment)
@attachments.unattach(OtherAttachment)
@attachments.attachments.should.have.length(1)
@attachments.attachments[0].should.be.instanceOf(TestAttachment)
describe '#unattachAll', ->
it 'is never passed an undefined attachment', ->
stub = @sandbox.stub().returns(true)
@attachments.push(TestAttachment)
@attachments.push(OtherAttachment)
(=> @attachments.unattachAll(stub)).should.not.throw()
stub.calledWithMatch(undefined).should.be.false
stub.calledWithMatch(null).should.be.false
@attachments.attachments.should.be.empty
describe '#getPassable', ->
beforeEach ->
@attachments.attachments.push(new Attachment.Embargo())
@attachments.attachments.push(new Attachment.Yawn())
@attachments.attachments.push(new Attachment.Ingrain())
@attachments.attachments.push(new Attachment.AquaRing())
@attachments.attachments.push(new Attachment.Disable())
@attachments.attachments.push(new Attachment.Torment())
@attachments.attachments.push(new Attachment.Substitute())
@attachments.attachments.push(new Attachment.Curse())
@attachments.attachments.push(new Attachment.LeechSeed())
@attachments.attachments.push(new Attachment.MagnetRise())
@attachments.attachments.push(new Attachment.Confusion())
it "returns an array of passable attachments already attached", ->
attachments = @attachments.getPassable()
attachments.should.not.containEql(Attachment.Disable)
attachments.should.not.containEql(Attachment.Torment)
attachments.should.not.containEql(Attachment.Yawn)
attachments.should.containEql(Attachment.Ingrain)
attachments.should.containEql(Attachment.AquaRing)
attachments.should.containEql(Attachment.Embargo)
attachments.should.containEql(Attachment.Substitute)
attachments.should.containEql(Attachment.Curse)
attachments.should.containEql(Attachment.LeechSeed)

477
test/bw/battle.coffee Normal file
View File

@@ -0,0 +1,477 @@
require '../helpers'
sinon = require('sinon')
{Attachment} = require('../../server/bw/attachment')
{Battle} = require('../../server/bw/battle')
{Weather} = require('../../shared/weather')
{Conditions} = require '../../shared/conditions'
{Factory} = require('../factory')
{BattleServer} = require('../../server/server')
{Protocol} = require '../../shared/protocol'
shared = require('../shared')
should = require 'should'
{_} = require 'underscore'
ratings = require('../../server/ratings')
describe 'Battle', ->
beforeEach ->
@server = new BattleServer()
shared.create.call this,
format: 'xy1000'
team1: [Factory('Hitmonchan'), Factory('Heracross')]
team2: [Factory('Hitmonchan'), Factory('Heracross')]
it 'starts at turn 1', ->
@battle.turn.should.equal 1
describe '#hasWeather(weatherName)', ->
it 'returns true if the current battle weather is weatherName', ->
@battle.weather = "Sunny"
@battle.hasWeather("Sunny").should.be.true
it 'returns false on non-None in presence of a weather-cancel ability', ->
@battle.weather = "Sunny"
@sandbox.stub(@battle, 'hasWeatherCancelAbilityOnField', -> true)
@battle.hasWeather("Sunny").should.be.false
it 'returns true on None in presence of a weather-cancel ability', ->
@battle.weather = "Sunny"
@sandbox.stub(@battle, 'hasWeatherCancelAbilityOnField', -> true)
@battle.hasWeather("None").should.be.true
describe '#recordMove', ->
it "records a player's move", ->
@battle.recordMove(@id1, @battle.getMove('Tackle'))
action = _(@battle.pokemonActions).find((a) => a.pokemon == @p1)
should.exist(action)
action.should.have.property("move")
action.move.should.equal @battle.getMove('Tackle')
it "does not record a move if player has already made an action", ->
length = @battle.pokemonActions.length
@battle.recordMove(@id1, @battle.getMove('Tackle'))
@battle.recordMove(@id1, @battle.getMove('Tackle'))
@battle.pokemonActions.length.should.equal(1 + length)
describe '#undoCompletedRequest', ->
it "fails if the player didn't make any action", ->
@battle.undoCompletedRequest(@id1).should.be.false
it "fails on the second turn as well if the player didn't make any action", ->
@controller.makeMove(@id1, "Mach Punch")
@controller.makeMove(@id2, "Mach Punch")
@battle.turn.should.equal 2
@battle.undoCompletedRequest(@id1).should.be.false
it "succeeds if the player selected an action already", ->
@battle.recordMove(@id1, @battle.getMove('Tackle'))
@battle.pokemonActions.should.not.be.empty
@battle.undoCompletedRequest(@id1).should.be.true
@battle.pokemonActions.should.be.empty
it "can cancel an action multiple times", ->
for i in [0..5]
@battle.recordMove(@id1, @battle.getMove('Tackle'))
@battle.pokemonActions.should.not.be.empty
@battle.undoCompletedRequest(@id1).should.be.true
@battle.pokemonActions.should.be.empty
describe '#recordSwitch', ->
it "records a player's switch", ->
@battle.recordSwitch(@id1, 1)
action = _(@battle.pokemonActions).find((a) => a.pokemon == @p1)
should.exist(action)
action.should.have.property("to")
action.to.should.equal 1
it "does not record a switch if player has already made an action", ->
length = @battle.pokemonActions.length
@battle.recordSwitch(@id1, 1)
@battle.recordSwitch(@id1, 1)
@battle.pokemonActions.length.should.equal(1 + length)
describe '#performSwitch', ->
it "swaps pokemon positions of a player's team", ->
[poke1, poke2] = @team1.pokemon
@battle.performSwitch(@p1, 1)
@team1.pokemon.slice(0, 2).should.eql [poke2, poke1]
it "calls the pokemon's switchOut() method", ->
pokemon = @p1
mock = @sandbox.mock(pokemon)
mock.expects('switchOut').once()
@battle.performSwitch(@p1, 1)
mock.verify()
describe "#setWeather", ->
it "can last a set number of turns", ->
@battle.setWeather(Weather.SUN, 5)
for i in [0...5]
@battle.endTurn()
@battle.weather.should.equal Weather.NONE
describe "weather", ->
it "damages pokemon who are not of a certain type", ->
@battle.setWeather(Weather.SAND)
@battle.endTurn()
maxHP = @p1.stat('hp')
(maxHP - @p1.currentHP).should.equal Math.floor(maxHP / 16)
(maxHP - @p2.currentHP).should.equal Math.floor(maxHP / 16)
@battle.setWeather(Weather.HAIL)
@battle.endTurn()
maxHP = @p1.stat('hp')
(maxHP - @p1.currentHP).should.equal 2*Math.floor(maxHP / 16)
(maxHP - @p2.currentHP).should.equal 2*Math.floor(maxHP / 16)
describe "move PP", ->
it "goes down after a pokemon uses a move", ->
pokemon = @p1
move = @p1.moves[0]
@battle.performMove(@p1, move)
@p1.pp(move).should.equal(@p1.maxPP(move) - 1)
describe "#performMove", ->
it "records this move as the battle's last move", ->
move = @p1.moves[0]
@battle.performMove(@p1, move)
should.exist @battle.lastMove
@battle.lastMove.should.equal move
describe "#bump", ->
it "bumps a pokemon to the front of its priority bracket", ->
@battle.recordMove(@id1, @battle.getMove('Tackle'))
@battle.recordMove(@id2, @battle.getMove('Splash'))
@battle.determineTurnOrder()
# Get last pokemon to move and bump it up
{pokemon} = @battle.pokemonActions[@battle.pokemonActions.length - 1]
@battle.bump(pokemon)
@battle.pokemonActions[0].pokemon.should.eql pokemon
it "bumps a pokemon to the front of a specific priority bracket", ->
@battle.recordMove(@id1, @battle.getMove('Tackle'))
@battle.recordMove(@id2, @battle.getMove('Mach Punch'))
queue = @battle.determineTurnOrder()
@battle.bump(@p1, @battle.getMove('Mach Punch').priority)
queue[0].pokemon.should.eql @p1
it "still works even if there's nothing in that bracket", ->
@battle.recordMove(@id1, @battle.getMove('Tackle'))
@battle.recordMove(@id2, @battle.getMove('Mach Punch'))
queue = @battle.determineTurnOrder()
queue.should.have.length(2)
@battle.bump(@p1)
queue.should.have.length(2)
queue[0].pokemon.should.eql @p2
queue[1].pokemon.should.eql @p1
it "is a no-op if no actions", ->
queue = @battle.determineTurnOrder()
queue.should.have.length(0)
@battle.bump(@p1)
queue.should.have.length(0)
describe "#delay", ->
it "delays a pokemon to the end of its priority bracket", ->
@battle.recordMove(@id1, @battle.getMove('Tackle'))
@battle.recordMove(@id2, @battle.getMove('Splash'))
@battle.determineTurnOrder()
# Get first pokemon to move and delay it
{pokemon} = @battle.pokemonActions[0]
@battle.delay(pokemon)
@battle.pokemonActions[1].pokemon.should.eql pokemon
it "delays a pokemon to the end of a specific priority bracket", ->
@battle.recordMove(@id1, @battle.getMove('Mach Punch'))
@battle.recordMove(@id2, @battle.getMove('Tackle'))
queue = @battle.determineTurnOrder()
@battle.delay(@p1, @battle.getMove('Tackle').priority)
queue[1].pokemon.should.eql @p1
it "still works even if there's nothing in that bracket", ->
@battle.recordMove(@id1, @battle.getMove('Tackle'))
@battle.recordMove(@id2, @battle.getMove('Mach Punch'))
queue = @battle.determineTurnOrder()
queue.should.have.length(2)
@battle.delay(@p1)
queue.should.have.length(2)
queue[0].pokemon.should.eql @p2
queue[1].pokemon.should.eql @p1
it "is a no-op if no actions", ->
queue = @battle.determineTurnOrder()
queue.should.have.length(0)
@battle.delay(@p1)
queue.should.have.length(0)
describe "#weatherUpkeep", ->
it "does not damage Pokemon if a weather-cancel ability is on the field", ->
@battle.setWeather(Weather.HAIL)
@sandbox.stub(@battle, 'hasWeatherCancelAbilityOnField', -> true)
@battle.endTurn()
@p1.currentHP.should.not.be.lessThan @p1.stat('hp')
@p2.currentHP.should.not.be.lessThan @p2.stat('hp')
it "does not damage a Pokemon who is immune to a weather", ->
@battle.setWeather(Weather.HAIL)
@sandbox.stub(@p2, 'isWeatherDamageImmune', -> true)
@battle.endTurn()
@p1.currentHP.should.be.lessThan @p1.stat('hp')
@p2.currentHP.should.not.be.lessThan @p2.stat('hp')
describe "#add", ->
it "adds the spectator to an internal array", ->
connection = @stubSpark()
spectator = @server.findOrCreateUser(id: 1, name: "derp", connection)
length = @battle.sparks.length
@battle.add(connection)
@battle.sparks.should.have.length(length + 1)
@battle.sparks.should.containEql(connection)
it "gives the spectator battle information", ->
connection = @stubSpark()
spectator = @server.findOrCreateUser(id: 1, name: "derp", connection)
spy = @sandbox.spy(connection, 'send')
@battle.add(connection)
{id, numActive, log} = @battle
spy.calledWithMatch("spectateBattle", id, sinon.match.string, numActive, null, @battle.playerNames, log).should.be.true
it "receives the correct set of initial teams", ->
connection = @stubSpark()
spectator = @server.findOrCreateUser(id: 1, name: "derp", connection)
spy = @sandbox.spy(connection, 'send')
teams = @battle.getTeams().map((team) -> team.toJSON(hidden: true))
@team1.switch(@p1, 1)
@battle.add(connection)
{id, numActive, log} = @battle
spy.calledWithMatch("spectateBattle", id, sinon.match.string, numActive, null, @battle.playerNames, log).should.be.true
it "does not add a spectator twice", ->
connection = @stubSpark()
spectator = @server.findOrCreateUser(id: 1, name: "derp", connection)
length = @battle.sparks.length
@battle.add(connection)
@battle.add(connection)
@battle.sparks.should.have.length(length + 1)
describe "#getWinner", ->
it "returns player 1 if player 2's team has all fainted", ->
pokemon.faint() for pokemon in @team2.pokemon
@battle.getWinner().should.equal(@id1)
it "returns player 2 if player 1's team has all fainted", ->
pokemon.faint() for pokemon in @team1.pokemon
@battle.getWinner().should.equal(@id2)
it "declares the pokemon dying of recoil the winner", ->
shared.create.call this,
team1: [Factory('Talonflame', moves: ["Brave Bird"])]
team2: [Factory('Magikarp')]
@p1.currentHP = @p2.currentHP = 1
spy = @sandbox.spy(@battle, 'getWinner')
@controller.makeMove(@id1, "Brave Bird")
@controller.makeMove(@id2, "Splash")
spy.returned(@id1).should.be.true
describe "#endBattle", ->
it "cannot end multiple times", ->
spy = @sandbox.spy(@battle, 'emit')
spy.withArgs("end")
@battle.endBattle()
@battle.endBattle()
spy.withArgs("end").calledOnce.should.be.true
it "marks the battle as over", ->
@battle.endBattle()
@battle.isOver().should.be.true
it "updates the winner and losers' ratings", (done) ->
shared.create.call this,
team1: [Factory('Hitmonchan')]
team2: [Factory('Mew')]
conditions: [ Conditions.RATED_BATTLE ]
@battle.on "ratingsUpdated", =>
ratings.getPlayer @id1, (err, rating1) =>
ratings.getPlayer @id2, (err, rating2) =>
rating1.rating.should.be.greaterThan(ratings.DEFAULT_RATING)
rating2.rating.should.be.lessThan(ratings.DEFAULT_RATING)
done()
@p2.currentHP = 1
mock = @sandbox.mock(@controller)
@controller.makeMove(@id1, 'Mach Punch')
@controller.makeMove(@id2, 'Psychic')
it "doesn't update ratings if an unrated battle", (done) ->
shared.create.call this,
team1: [Factory('Hitmonchan')]
team2: [Factory('Mew')]
@battle.on 'end', =>
ratings.getPlayer @id1, (err, rating1) =>
ratings.getPlayer @id2, (err, rating2) =>
rating1.rating.should.equal(ratings.DEFAULT_RATING)
rating2.rating.should.equal(ratings.DEFAULT_RATING)
done()
@p2.currentHP = 1
mock = @sandbox.mock(@controller)
@controller.makeMove(@id1, 'Mach Punch')
@controller.makeMove(@id2, 'Psychic')
describe "#forfeit", ->
it "prematurely ends the battle", ->
spy = @sandbox.spy(@battle, 'tell')
spy.withArgs(Protocol.FORFEIT_BATTLE, @battle.getPlayerIndex(@id1))
@battle.forfeit(@id1)
spy.withArgs(Protocol.FORFEIT_BATTLE, @battle.getPlayerIndex(@id1))
.called.should.be.true
it "does not forfeit if the player given is invalid", ->
mock = @sandbox.mock(@battle).expects('tell').never()
@battle.forfeit('this definitely should not work')
mock.verify()
it "cannot forfeit multiple times", ->
spy = @sandbox.spy(@battle, 'tell')
spy.withArgs(Protocol.FORFEIT_BATTLE, @battle.getPlayerIndex(@id1))
@battle.forfeit(@id1)
@battle.forfeit(@id1)
spy.withArgs(Protocol.FORFEIT_BATTLE, @battle.getPlayerIndex(@id1))
.calledOnce.should.be.true
it "marks the battle as over", ->
@battle.forfeit(@id1)
@battle.isOver().should.be.true
it "doesn't update the winner and losers' ratings if not a rated battle", (done) ->
@battle.on 'end', =>
ratings.getPlayer @id1, (err, rating1) =>
ratings.getPlayer @id2, (err, rating2) =>
rating1.rating.should.equal(ratings.DEFAULT_RATING)
rating2.rating.should.equal(ratings.DEFAULT_RATING)
done()
@battle.forfeit(@id2)
describe "#hasStarted", ->
it "returns false if the battle has not started", ->
battle = new Battle('id', [])
battle.hasStarted().should.be.false
it "returns true if the battle has started", ->
battle = new Battle('id', [])
battle.begin()
battle.hasStarted().should.be.true
describe "#getAllAttachments", ->
it "returns a list of attachments for all pokemon, teams, and battles", ->
@battle.attach(Attachment.TrickRoom)
@team2.attach(Attachment.Reflect)
@p1.attach(Attachment.Ingrain)
attachments = @battle.getAllAttachments()
should.exist(attachments)
attachments = attachments.map((a) -> a.constructor)
attachments.length.should.be.greaterThan(2)
attachments.should.containEql(Attachment.TrickRoom)
attachments.should.containEql(Attachment.Reflect)
attachments.should.containEql(Attachment.Ingrain)
describe "#query", ->
it "queries all attachments attached to a specific event", ->
@battle.attach(Attachment.TrickRoom)
@team2.attach(Attachment.Reflect)
@p1.attach(Attachment.Ingrain)
mocks = []
mocks.push @sandbox.mock(Attachment.TrickRoom.prototype)
mocks.push @sandbox.mock(Attachment.Reflect.prototype)
mocks.push @sandbox.mock(Attachment.Ingrain.prototype)
mock.expects("endTurn").once() for mock in mocks
attachments = @battle.query("endTurn")
mock.verify() for mock in mocks
describe "#getOpponents", ->
it "returns all opponents of a particular pokemon as an array", ->
@battle.getOpponents(@p1).should.be.an.instanceOf(Array)
@battle.getOpponents(@p1).should.have.length(1)
it "does not include fainted opponents", ->
@p2.faint()
@battle.getOpponents(@p1).should.have.length(0)
describe "#sendRequestTo", ->
it "sends all requests to a certain player", ->
mock = @sandbox.mock(@battle).expects('tellPlayer').once()
mock.withArgs(@id1, Protocol.REQUEST_ACTIONS)
@battle.sendRequestTo(@id1)
mock.verify()
describe "expiration", ->
beforeEach ->
shared.create.call(this)
it "ends ongoing battles after a specific amount", ->
time = Battle::ONGOING_BATTLE_TTL
@clock.tick(time)
@battle.isOver().should.be.true
it "sends BATTLE_EXPIRED after a specific amount after battle end", ->
time = Battle::ENDED_BATTLE_TTL
delta = 5000
spy = @sandbox.spy(@battle, 'tell').withArgs(Protocol.BATTLE_EXPIRED)
@clock.tick(delta)
@battle.forfeit(@id1)
@battle.isOver().should.be.true
spy.withArgs(Protocol.BATTLE_EXPIRED).called.should.be.false
@battle.tell.restore()
spy = @sandbox.spy(@battle, 'tell').withArgs(Protocol.BATTLE_EXPIRED)
@clock.tick(time)
@battle.isOver().should.be.true
spy.withArgs(Protocol.BATTLE_EXPIRED).calledOnce.should.be.true
it "doesn't call 'expire' if expiring twice", ->
firstExpire = 24 * 60 * 60 * 1000
secondExpire = 48 * 60 * 60 * 1000
spy = @sandbox.spy(@battle, 'expire')
@clock.tick(firstExpire)
@battle.isOver().should.be.true
@clock.tick(secondExpire)
spy.calledOnce.should.be.true
it "doesn't call 'end' twice", ->
longExpire = 48 * 60 * 60 * 1000
spy = @sandbox.spy()
@battle.on('end', spy)
@battle.forfeit(@id1)
@battle.isOver().should.be.true
@clock.tick(longExpire)
spy.calledOnce.should.be.true
describe "Rated battles", ->
beforeEach ->
shared.create.call this,
team1: [Factory('Hitmonchan')]
team2: [Factory('Mew')]
conditions: [ Conditions.RATED_BATTLE ]
it "updates the winner and losers' ratings", (done) ->
@battle.on "ratingsUpdated", =>
ratings.getPlayer @id1, (err, rating1) =>
ratings.getPlayer @id2, (err, rating2) =>
defaultPlayer = ratings.algorithm.createPlayer()
rating1.rating.should.be.greaterThan(defaultPlayer.rating)
rating2.rating.should.be.lessThan(defaultPlayer.rating)
done()
@battle.forfeit(@id2)

View File

@@ -0,0 +1,139 @@
require '../helpers'
{Factory} = require '../factory'
should = require 'should'
shared = require '../shared'
describe 'BattleController', ->
it "automatically ends the turn if all players move", ->
shared.create.call(this)
mock = @sandbox.mock(@controller)
mock.expects('continueTurn').once()
@controller.makeMove(@id1, 'Tackle')
@controller.makeMove(@id2, 'Tackle')
mock.verify()
it "automatically ends the turn if all players switch", ->
shared.create.call this,
team1: [Factory('Hitmonchan'), Factory('Heracross')]
team2: [Factory('Hitmonchan'), Factory('Heracross')]
mock = @sandbox.mock(@controller)
mock.expects('continueTurn').once()
@controller.makeSwitch(@id1, 1)
@controller.makeSwitch(@id2, 1)
mock.verify()
describe "switch validations", ->
it "rejects switches under 0", ->
shared.create.call(this, team1: (Factory("Magikarp") for x in [0..2]))
mock = @sandbox.mock(@battle).expects('recordSwitch').never()
@controller.makeSwitch(@id1, -1)
mock.verify()
it "rejects switches for pokemon who are already out", ->
shared.create.call this,
numActive: 2
team1: (Factory("Magikarp") for x in [0..2])
mock = @sandbox.mock(@battle).expects('recordSwitch').never()
@controller.makeSwitch(@id1, 0)
@controller.makeSwitch(@id1, 1)
mock.verify()
it "rejects switches over the max team party index", ->
shared.create.call(this, team1: (Factory("Magikarp") for x in [0..2]))
mock = @sandbox.mock(@battle).expects('recordSwitch').never()
@controller.makeSwitch(@id1, 3)
mock.verify()
it "accepts switches between active pokemon and max team party index", ->
shared.create.call this,
numActive: 2
team1: (Factory("Magikarp") for x in [0..2])
mock = @sandbox.mock(@battle).expects('recordSwitch').once()
@controller.makeSwitch(@id1, 2)
mock.verify()
it "rejects switches that are not part of the request action", ->
shared.create.call(this, team1: (Factory("Magikarp") for x in [0..2]))
@p1.blockSwitch()
@p1.resetBlocks = ->
@battle.removeRequest(@id1)
@battle.beginTurn()
mock = @sandbox.mock(@battle).expects('recordSwitch').never()
@controller.makeSwitch(@id1, 2)
mock.verify()
it "rejects switches if the battle has not started yet", ->
shared.build(this, team1: (Factory("Magikarp") for x in [0..2]))
mock = @sandbox.mock(@battle).expects('recordSwitch').never()
@controller.makeSwitch(@id1, 2)
mock.verify()
it "rejects switches not for a specific turn", ->
shared.create.call(this, team1: (Factory("Magikarp") for x in [0..2]))
mock = @sandbox.mock(@battle).expects('recordMove').never()
@controller.makeSwitch(@id1, 2)
mock.verify()
it "rejects switches for a player who doesn't exist", ->
shared.create.call(this, team1: (Factory("Magikarp") for x in [0..2]))
@controller.makeSwitch("fake dude", 2).should.be.false
describe "move validations", ->
it "rejects moves not part of the pokemon's valid moves", ->
shared.create.call this,
team1: [ Factory("Magikarp", moves: ["Tackle", "Splash"]) ]
mock = @sandbox.mock(@battle).expects('recordMove').never()
@controller.makeMove(@id1, "EXTERMINATE")
mock.verify()
it "accepts Struggle", ->
shared.create.call this,
team1: [ Factory("Magikarp", moves: ["Tackle", "Splash"]) ]
@p1.blockMove(move) for move in @p1.moves
@p1.resetBlocks = ->
@battle.removeRequest(@id1)
@battle.beginTurn()
mock = @sandbox.mock(@battle).expects('recordMove').once()
@controller.makeMove(@id1, "Struggle")
mock.verify()
it "rejects moves that cannot be selected", ->
shared.create.call this,
team1: [ Factory("Magikarp", moves: ["Tackle", "Splash"]) ]
move = @p1.moves[0]
@p1.blockMove(move)
@p1.resetBlocks = ->
@battle.removeRequest(@id1)
@battle.beginTurn()
mock = @sandbox.mock(@battle).expects('recordMove').never()
@controller.makeMove(@id1, move.name)
mock.verify()
it "rejects moves if the battle has not started yet", ->
shared.build this,
team1: [ Factory("Magikarp", moves: ["Tackle", "Splash"]) ]
mock = @sandbox.mock(@battle).expects('recordMove').never()
@controller.makeMove(@id1, @p1.moves[0].name)
mock.verify()
it "rejects moves not for a specific turn", ->
shared.create.call this,
team1: [ Factory("Magikarp", moves: ["Tackle", "Splash"]) ]
mock = @sandbox.mock(@battle).expects('recordMove').never()
@controller.makeMove(@id1, @p1.moves[0].name, null, @battle.turn - 1)
mock.verify()
it "rejects moves for a player who doesn't exist", ->
shared.create.call this,
team1: [ Factory("Magikarp", moves: ["Tackle", "Splash"]) ]
@controller.makeMove("fake dude", @p1.moves[0].name).should.be.false

View File

@@ -0,0 +1,567 @@
require '../helpers'
{Battle} = require('../../server/bw/battle')
{Pokemon} = require('../../server/bw/pokemon')
{Status, Attachment} = require('../../server/bw/attachment')
{Conditions} = require '../../shared/conditions'
{Factory} = require '../factory'
should = require 'should'
shared = require '../shared'
{Protocol} = require '../../shared/protocol'
describe 'Mechanics', ->
describe 'an attack missing', ->
it 'deals no damage', ->
shared.create.call this,
team1: [Factory('Celebi')]
team2: [Factory('Magikarp')]
shared.biasRNG.call(this, 'randInt', 'miss', 100)
move = @battle.getMove('Leaf Storm')
originalHP = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Leaf Storm'))
@p2.currentHP.should.equal(originalHP)
it 'triggers effects dependent on the move missing', ->
shared.create.call this,
team1: [Factory('Hitmonlee')]
team2: [Factory('Magikarp')]
shared.biasRNG.call(this, 'randInt', 'miss', 100)
hiJumpKick = @battle.getMove('Hi Jump Kick')
mock = @sandbox.mock(hiJumpKick).expects('afterMiss').once()
@battle.performMove(@p1, hiJumpKick)
mock.verify()
it 'does not trigger effects dependent on the move hitting', ->
shared.create.call this,
team1: [Factory('Celebi')]
team2: [Factory('Gyarados')]
shared.biasRNG.call(this, 'randInt', 'miss', 100)
hiJumpKick = @battle.getMove('Hi Jump Kick')
mock = @sandbox.mock(hiJumpKick).expects('afterSuccessfulHit').never()
@battle.performMove(@p1, hiJumpKick)
mock.verify()
describe 'fainting', ->
it 'forces a new pokemon to be picked', ->
shared.create.call this,
team1: [Factory('Mew'), Factory('Heracross')]
team2: [Factory('Hitmonchan'), Factory('Heracross')]
spy = @sandbox.spy(@battle, 'tellPlayer')
@p2.currentHP = 1
@controller.makeMove(@id1, 'Psychic')
@controller.makeMove(@id2, 'Mach Punch')
spy.calledWith(@id2, Protocol.REQUEST_ACTIONS).should.be.true
it 'does not increment the turn count', ->
shared.create.call this,
team1: [Factory('Mew'), Factory('Heracross')]
team2: [Factory('Hitmonchan'), Factory('Heracross')]
turn = @battle.turn
@p2.currentHP = 1
@controller.makeMove(@id1, 'Psychic')
@controller.makeMove(@id2, 'Mach Punch')
@battle.turn.should.not.equal turn + 1
it 'removes the fainted pokemon from the action priority queue', ->
shared.create.call this,
team1: [Factory('Mew'), Factory('Heracross')]
team2: [Factory('Hitmonchan'), Factory('Heracross')]
@p1.currentHP = 1
@p2.currentHP = 1
@controller.makeMove(@id1, 'Psychic')
@controller.makeMove(@id2, 'Mach Punch')
@p1.currentHP.should.be.below 1
@p2.currentHP.should.equal 1
action = @battle.getAction(@p1)
should.not.exist(action)
it 'lets the player switch in a new pokemon', ->
shared.create.call this,
team1: [Factory('Mew'), Factory('Heracross')]
team2: [Factory('Hitmonchan'), Factory('Heracross')]
@p2.currentHP = 1
@controller.makeMove(@id1, 'Psychic')
@controller.makeMove(@id2, 'Mach Punch')
@controller.makeSwitch(@id2, 1)
@team2.first().species.should.equal 'Heracross'
it "occurs when a pokemon faints from passive damage", ->
shared.create.call(this)
@p2.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Leech Seed"))
spy = @sandbox.spy(@p2, 'faint')
@battle.endTurn()
spy.calledOnce.should.be.true
it "occurs when a pokemon faints normally", ->
shared.create.call(this)
@p2.currentHP = 1
spy = @sandbox.spy(@p2, 'faint')
@battle.performMove(@p1, @battle.getMove("Tackle"))
spy.calledOnce.should.be.true
describe 'secondary effect attacks', ->
it 'can inflict effect on successful hit', ->
shared.create.call this,
team1: [Factory('Porygon-Z')]
team2: [Factory('Porygon-Z')]
shared.biasRNG.call(this, "next", 'secondary effect', 0) # 100% chance
@battle.performMove(@p1, @battle.getMove('Flamethrower'))
@p2.has(Status.Burn).should.be.true
describe 'the fang attacks', ->
it 'can inflict two effects at the same time', ->
shared.create.call this,
team1: [Factory('Gyarados')]
team2: [Factory('Gyarados')]
shared.biasRNG.call(this, "randInt", "secondary effect", 0) # 100% chance
shared.biasRNG.call(this, "randInt", "flinch", 0)
@battle.performMove(@p1, @battle.getMove("Ice Fang"))
@p2.has(Attachment.Flinch).should.be.true
@p2.has(Status.Freeze).should.be.true
describe 'a pokemon with technician', ->
it "doesn't increase damage if the move has bp > 60", ->
shared.create.call this,
team1: [Factory('Hitmontop')]
team2: [Factory('Mew')]
icePunch = @battle.getMove('Ice Punch')
icePunch.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
it "increases damage if the move has bp <= 60", ->
shared.create.call this,
team1: [Factory('Hitmontop')]
team2: [Factory('Shaymin')]
bulletPunch = @battle.getMove('Bullet Punch')
bulletPunch.modifyBasePower(@battle, @p1, @p2).should.equal(0x1800)
describe 'STAB', ->
it "gets applied if the move and user share a type", ->
shared.create.call this,
team1: [Factory('Heracross')]
team2: [Factory('Regirock')]
megahorn = @battle.getMove("Megahorn")
megahorn.stabModifier(@battle, @p1, @p2).should.equal(0x1800)
it "doesn't get applied if the move and user are of different types", ->
shared.create.call this,
team1: [Factory('Hitmonchan')]
team2: [Factory('Mew')]
icePunch = @battle.getMove("Ice Punch")
icePunch.stabModifier(@battle, @p1, @p2).should.equal(0x1000)
describe 'turn order', ->
it 'randomly decides winner if pokemon have the same speed and priority', ->
shared.create.call this,
team1: [Factory('Mew')]
team2: [Factory('Mew')]
spy = @sandbox.spy(@battle, 'determineTurnOrder')
shared.biasRNG.call(this, "next", "turn order", .6)
@battle.recordMove(@id1, @battle.getMove('Psychic'))
@battle.recordMove(@id2, @battle.getMove('Psychic'))
@battle.determineTurnOrder().map((o) -> o.pokemon).should.eql [ @p2, @p1 ]
@battle.pokemonActions = []
shared.biasRNG.call(this, "next", "turn order", .4)
@battle.recordMove(@id1, @battle.getMove('Psychic'))
@battle.recordMove(@id2, @battle.getMove('Psychic'))
@battle.determineTurnOrder().map((o) -> o.pokemon).should.eql [ @p1, @p2 ]
it 'decides winner by highest priority move', ->
shared.create.call this,
team1: [Factory('Hitmonchan')]
team2: [Factory('Hitmonchan')]
spy = @sandbox.spy(@battle, 'determineTurnOrder')
@battle.recordMove(@id1, @battle.getMove('Mach Punch'))
@battle.recordMove(@id2, @battle.getMove('Psychic'))
@battle.determineTurnOrder().map((o) -> o.pokemon).should.eql [ @p1, @p2 ]
@battle.pokemonActions = []
@battle.recordMove(@id1, @battle.getMove('Psychic'))
@battle.recordMove(@id2, @battle.getMove('Mach Punch'))
@battle.determineTurnOrder().map((o) -> o.pokemon).should.eql [ @p2, @p1 ]
it 'decides winner by speed if priority is equal', ->
shared.create.call this,
team1: [Factory('Hitmonchan')]
team2: [Factory('Hitmonchan', evs: { speed: 4 })]
@battle.recordMove(@id1, @battle.getMove('ThunderPunch'))
@battle.recordMove(@id2, @battle.getMove('ThunderPunch'))
@battle.determineTurnOrder().map((o) -> o.pokemon).should.eql [ @p2, @p1 ]
describe 'fainting all the opposing pokemon', ->
it "doesn't request any more actions from players", ->
shared.create.call this,
team1: [Factory('Hitmonchan')]
team2: [Factory('Mew')]
@p2.currentHP = 1
@controller.makeMove(@id1, 'Mach Punch')
@controller.makeMove(@id2, 'Psychic')
@battle.requests.should.not.have.property @id1.id
@battle.requests.should.not.have.property @id2.id
it 'ends the battle', ->
shared.create.call this,
team1: [Factory('Hitmonchan')]
team2: [Factory('Mew')]
@p2.currentHP = 1
mock = @sandbox.mock(@battle)
mock.expects('endBattle').once()
@controller.makeMove(@id1, 'Mach Punch')
@controller.makeMove(@id2, 'Psychic')
mock.verify()
describe 'a pokemon with a type immunity', ->
it 'cannot be damaged by a move of that type', ->
shared.create.call this,
team1: [Factory('Camerupt')]
team2: [Factory('Gyarados')]
@controller.makeMove(@id1, 'Earthquake')
@controller.makeMove(@id2, 'Dragon Dance')
@p2.currentHP.should.equal @p2.stat('hp')
describe 'a confused pokemon', ->
it "has a 50% chance of hurting itself", ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", 'confusion turns', 1) # always 1 turn
@p1.attach(Attachment.Confusion, {@battle})
shared.biasRNG.call(this, "next", 'confusion', 0) # always hits
mock = @sandbox.mock(@battle.getMove('Tackle'))
mock.expects('execute').never()
@controller.makeMove(@id1, 'Tackle')
@controller.makeMove(@id2, 'Splash')
mock.verify()
@p1.currentHP.should.be.lessThan @p1.stat('hp')
@p2.currentHP.should.equal @p2.stat('hp')
it "deals a minimum of 1 damage", ->
shared.create.call(this, team1: [Factory("Shuckle", level: 1)])
shared.biasRNG.call(this, "randInt", 'confusion turns', 1) # always 1 turn
@p1.attach(Attachment.Confusion, {@battle})
shared.biasRNG.call(this, "next", 'confusion', 0) # always hits
@sandbox.stub(@battle.confusionMove, 'calculateDamage', -> 0)
@battle.performMove(@p1, @battle.getMove('Tackle'))
@p1.currentHP.should.equal(@p1.stat('hp') - 1)
it "snaps out of confusion after a predetermined number of turns", ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", 'confusion turns', 1) # always 1 turn
@p1.attach(Attachment.Confusion)
@controller.makeMove(@id1, 'Splash')
@controller.makeMove(@id2, 'Splash')
@controller.makeMove(@id1, 'Splash')
@controller.makeMove(@id2, 'Splash')
@p1.has(Attachment.Confusion).should.be.false
it "will not crit the confusion recoil", ->
shared.create.call(this)
@p1.attach(Attachment.Confusion)
shared.biasRNG.call(this, "next", 'confusion', 0) # always recoils
shared.biasRNG.call(this, 'next', 'ch', 0) # always crits
spy = @sandbox.spy(@battle.confusionMove, 'isCriticalHit')
@controller.makeMove(@id1, 'Tackle')
@controller.makeMove(@id2, 'Tackle')
spy.returned(false).should.be.true
it "will not error for not having unusual move properties", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Iron Fist")])
@p1.attach(Attachment.Confusion)
shared.biasRNG.call(this, "next", 'confusion', 0) # always recoils
(=>
@controller.makeMove(@id1, 'Tackle')
@controller.makeMove(@id2, 'Tackle')
).should.not.throw()
describe 'a frozen pokemon', ->
it "will not execute moves", ->
shared.create.call(this)
@p1.attach(Status.Freeze)
shared.biasRNG.call(this, "next", 'unfreeze chance', 1) # always stays frozen
mock = @sandbox.mock(@battle.getMove('Tackle'))
mock.expects('execute').never()
@controller.makeMove(@id1, 'Tackle')
@controller.makeMove(@id2, 'Splash')
mock.verify()
it "has a 20% chance of unfreezing", ->
shared.create.call(this)
@p1.attach(Status.Freeze)
shared.biasRNG.call(this, "next", 'unfreeze chance', 0) # always unfreezes
@controller.makeMove(@id1, 'Splash')
@controller.makeMove(@id2, 'Splash')
@p1.has(Status.Freeze).should.be.false
it "unfreezes if hit by a fire move", ->
shared.create.call(this)
shared.biasRNG.call(this, "next", 'unfreeze chance', 1) # always stays frozen
@p1.attach(Status.Freeze)
@battle.performMove(@p2, @battle.getMove('Flamethrower'))
@p1.has(Status.Freeze).should.be.false
it "does not unfreeze if hit by a non-damaging move", ->
shared.create.call(this)
shared.biasRNG.call(this, "next", 'unfreeze chance', 1) # always stays frozen
@p1.attach(Status.Freeze)
@battle.performMove(@p2, @battle.getMove('Will-O-Wisp'))
@p1.has(Status.Freeze).should.be.true
for moveName in ["Sacred Fire", "Flare Blitz", "Flame Wheel", "Fusion Flare", "Scald"]
it "automatically unfreezes if using #{moveName}", ->
shared.create.call(this)
@p1.attach(Status.Freeze)
shared.biasRNG.call(this, "next", 'unfreeze chance', 1) # always stays frozen
@battle.performMove(@p1, @battle.getMove(moveName))
@p1.has(Status.Freeze).should.be.false
describe "a paralyzed pokemon", ->
it "has a 25% chance of being fully paralyzed", ->
shared.create.call(this)
@p1.attach(Status.Paralyze)
shared.biasRNG.call(this, "next", 'paralyze chance', 0) # always stays frozen
mock = @sandbox.mock(@battle.getMove('Tackle'))
mock.expects('execute').never()
@controller.makeMove(@id1, 'Tackle')
@controller.makeMove(@id2, 'Splash')
mock.verify()
it "has its speed quartered", ->
shared.create.call(this)
speed = @p1.stat('speed')
@p1.attach(Status.Paralyze)
@p1.stat('speed').should.equal Math.floor(speed / 4)
describe "a burned pokemon", ->
it "loses 1/8 of its HP each turn", ->
shared.create.call(this)
@p1.attach(Status.Burn)
hp = @p1.currentHP
eighth = Math.floor(hp / 8)
@battle.endTurn()
(hp - @p1.currentHP).should.equal(eighth)
@battle.endTurn()
(hp - @p1.currentHP).should.equal(2 * eighth)
it "loses 1 minimum HP", ->
shared.create.call(this, team1: [Factory("Shedinja")])
@p1.attach(Status.Burn)
@p1.currentHP.should.equal(1)
@battle.endTurn()
@p1.currentHP.should.equal(0)
describe "a sleeping pokemon", ->
it "sleeps for 1-3 turns", ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", 'sleep turns', 3)
@p1.attach(Status.Sleep)
tackle = @battle.getMove('Tackle')
for i in [0...3]
@battle.performMove(@p1, tackle)
@p1.has(Status.Sleep).should.be.true
@battle.performMove(@p1, tackle)
@p1.has(Status.Sleep).should.be.false
it "cannot move while asleep", ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", 'sleep turns', 3)
@p1.attach(Status.Sleep)
tackle = @battle.getMove('Tackle')
mock = @sandbox.mock(tackle).expects('execute').never()
for i in [0...3]
@battle.performMove(@p1, tackle)
mock.verify()
tackle.execute.restore()
mock = @sandbox.mock(tackle).expects('execute').once()
@battle.performMove(@p1, tackle)
mock.verify()
it "resets its counter when switching out", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Magikarp") ]
shared.biasRNG.call(this, "randInt", 'sleep turns', 1)
@p1.attach(Status.Sleep)
tackle = @battle.getMove('Tackle')
@battle.performMove(@p1, tackle)
@battle.performSwitch(@team1.first(), 1)
@battle.performSwitch(@team1.first(), 1)
@battle.performMove(@team1.first(), tackle)
@p1.has(Status.Sleep).should.be.true
describe "a poisoned pokemon", ->
it "loses 1/8 of its HP each turn", ->
shared.create.call(this)
@p1.attach(Status.Poison)
hp = @p1.currentHP
eighth = Math.floor(hp / 8)
@battle.endTurn()
(hp - @p1.currentHP).should.equal(eighth)
@battle.endTurn()
(hp - @p1.currentHP).should.equal(2 * eighth)
it "loses 1 minimum HP", ->
shared.create.call(this, team1: [Factory("Shedinja")])
@p1.attach(Status.Poison)
@p1.currentHP.should.equal(1)
@battle.endTurn()
@p1.currentHP.should.equal(0)
describe "a badly poisoned pokemon", ->
it "loses 1/16 of its HP the first turn", ->
shared.create.call(this)
@p1.attach(Status.Toxic)
hp = @p1.currentHP
@battle.endTurn()
(hp - @p1.currentHP).should.equal(hp >> 4)
it "loses 1/16 of its max HP, rounded down, times x where x is the number of turns up to 15", ->
shared.create.call(this)
@p1.attach(Status.Toxic)
hp = @p1.currentHP
fraction = (hp >> 4)
for i in [1..16]
@battle.endTurn()
hpFraction = Math.min(fraction * i, fraction * 15)
(hp - @p1.currentHP).should.equal(hpFraction)
@p1.currentHP = hp
it "still increases the counter with poison heal", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Poison Heal")])
@p1.attach(Status.Toxic)
hp = @p1.currentHP
fraction = (hp >> 4)
turns = 3
for i in [1...turns]
@battle.endTurn()
@p1.copyAbility(null)
@battle.endTurn()
hpFraction = Math.min(fraction * turns, fraction * 15)
(hp - @p1.currentHP).should.equal(hpFraction)
it "loses 1 minimum HP", ->
shared.create.call(this, team1: [Factory("Shedinja")])
@p1.attach(Status.Toxic)
@p1.currentHP.should.equal(1)
@battle.endTurn()
@p1.currentHP.should.equal(0)
it "resets its counter when switching out", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Magikarp") ]
@p1.attach(Status.Toxic)
hp = @p1.currentHP
@battle.endTurn()
@battle.performSwitch(@team1.first(), 1)
@p1.currentHP = hp
@battle.performSwitch(@team1.first(), 1)
@battle.endTurn()
(hp - @p1.currentHP).should.equal(hp >> 4)
describe "Pokemon#turnsActive", ->
it "is 1 on start of battle", ->
shared.create.call(this)
@p1.turnsActive.should.equal 1
it "is set to 0 when switching", ->
shared.create.call(this, team1: (Factory("Magikarp") for x in [1..2]))
@p1.turnsActive = 4
@team1.switch(@p1, 0)
@team1.first().turnsActive.should.equal 0
it "increases by 1 when a turn ends", ->
shared.create.call(this)
@p1.turnsActive.should.equal 1
@battle.endTurn()
@p1.turnsActive.should.equal 2
describe "A move with 0 PP", ->
it "will not execute", ->
shared.create.call(this)
move = @p1.moves[0]
@p1.setPP(move, 0)
@sandbox.mock(move).expects('execute').never()
@sandbox.mock(@p1).expects('beforeMove').never()
@battle.performMove(@p1, move)
describe "A pokemon with no available moves", ->
it "can struggle", ->
shared.create.call(this)
@battle.removeRequest(@id1)
# Next turn, @p1 will have no available moves.
for move in @p1.moves
@p1.blockMove(move)
@p1.resetBlocks = ->
@p1.validMoves().should.be.empty
@battle.beginTurn()
request = @battle.requestFor(@p1)
should.exist(request)
request.should.have.property('moves')
request.moves.should.eql(["Struggle"])
describe "Ability activation at the beginning of a battle", ->
it "considers Abilities that are always active", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Clear Body")]
team2: [Factory("Magikarp", ability: "Intimidate")]
@p1.stages.should.containEql(attack: 0)
xit "works the same way regardless of the entry order", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Intimidate")]
team2: [Factory("Magikarp", ability: "Clear Body")]
@p2.stages.should.containEql(attack: 0)

163
test/bw/battles.coffee Normal file
View File

@@ -0,0 +1,163 @@
require '../helpers'
shared = require '../shared'
{Attachment} = require '../../server/bw/attachment'
defaultTeam = [
{
species: "Pikachu"
moves: ["Substitute", "Thunderbolt", "Hidden Power", "Grass Knot"]
item: "Light Ball"
ability: "Lightningrod"
gender: "F"
}
{
species: "Hitmonchan"
moves: ["Close Combat", "Mach Punch", "Ice Punch", "ThunderPunch"]
item: "Life Orb"
ability: "Iron Fist"
gender: "M"
}
{
species: "Charizard"
item: "Choice Specs"
moves: ["Fire Blast", "Air Slash", "Hidden Power", "Focus Blast"]
ability: "Blaze"
}
{
species: "Dragonite"
item: "Leftovers"
moves: ["Dragon Dance", "Outrage", "Fire Punch", "ExtremeSpeed"]
ability: "Multiscale"
}
{
species: "Politoed"
item: "Leftovers"
moves: ["Scald", "Ice Beam", "Protect", "Toxic"]
ability: "Drizzle"
}
{
species: "Haunter"
item: "Leftovers"
moves: ["Substitute", "Disable", "Shadow Ball", "Focus Blast"]
ability: "Levitate"
}
]
describe "BW: Integration:", ->
describe "a battle", ->
it "executes fainted replacements at the proper time", ->
shared.create.call(this, team1: defaultTeam, team2: defaultTeam)
spy = @sandbox.spy(@battle, 'performReplacements')
@battle.turn.should.equal(1)
@sandbox.stub(@battle.getMove("Thunderbolt"), "baseDamage", -> 9999)
@controller.makeMove(@id1, "Thunderbolt")
@controller.makeSwitch(@id2, 4)
@battle.turn.should.equal(1)
@controller.makeSwitch(@id2, 1)
@battle.turn.should.equal(2)
(=>
@controller.makeMove(@id1, "Thunderbolt")
@controller.makeSwitch(@id2, 2)
).should.not.throw()
spy.calledOnce.should.be.true
it "can execute two fainted replacements in a row", ->
shared.create.call(this, team1: defaultTeam, team2: defaultTeam)
spy = @sandbox.spy(@battle, 'performReplacements')
@battle.turn.should.equal(1)
# Artificially set up conditions
spy1 = @sandbox.spy(@team2.at(1), "faint")
spy2 = @sandbox.spy(@team2.at(2), "faint")
@team2.at(1).currentHP = 1
@team2.at(2).currentHP = 1
@team2.attach(Attachment.StealthRock)
# Now attempt the first replacement.
@controller.makeSwitch(@id2, 1)
@controller.makeMove(@id1, "Thunderbolt")
# Nothing happens except the Pokemon faints.
@battle.turn.should.equal(1)
@team2.first().species.should.equal("Hitmonchan")
@team2.first().isFainted().should.be.true
@battle.areAllRequestsCompleted().should.be.false
# Do the second replacement.
@controller.makeSwitch(@id2, 2)
# Nothing happens except the Pokemon faints.
@battle.turn.should.equal(1)
@team2.first().species.should.equal("Charizard")
@team2.first().isFainted().should.be.true
@battle.areAllRequestsCompleted().should.be.false
# Each pokemon should have called their faint method
spy1.called.should.be.true
spy2.called.should.be.true
it "doesn't trigger weather if a switch-in faints immediately", ->
shared.create.call(this, team1: defaultTeam, team2: defaultTeam)
spy = @sandbox.spy(@battle, 'performReplacements')
@battle.turn.should.equal(1)
# There should be no weather at the start of the battle.
@battle.hasWeather().should.be.false
# Artificially set up conditions
@team2.at(4).currentHP = 1
@team2.attach(Attachment.StealthRock)
# Now attempt the switch.
@controller.makeSwitch(@id2, 4)
@controller.makeMove(@id1, "Substitute")
# Nothing happens except the Pokemon faints.
@battle.turn.should.equal(1)
@team2.first().species.should.equal("Politoed")
@team2.first().isFainted().should.be.true
@battle.areAllRequestsCompleted().should.be.false
# There should be no weather.
@battle.hasWeather().should.be.false
it "doesn't trigger weather if a replacement faints immediately", ->
shared.create.call(this, team1: defaultTeam, team2: defaultTeam)
@battle.turn.should.equal(1)
# There should be no weather at the start of the battle.
@battle.hasWeather().should.be.false
# Artificially set up conditions
@team2.at(1).currentHP = 1
@team2.at(4).currentHP = 1
@team2.attach(Attachment.StealthRock)
# Now attempt the switches.
@controller.makeSwitch(@id2, 1)
@controller.makeMove(@id1, "Substitute")
# Nothing happens except the Pokemon faints.
@battle.turn.should.equal(1)
@team2.first().species.should.equal("Hitmonchan")
@team2.first().isFainted().should.be.true
@battle.areAllRequestsCompleted().should.be.false
# Attempt the replacement
@controller.makeSwitch(@id2, 4)
# Nothing happens except the Pokemon faints, again.
@battle.turn.should.equal(1)
@team2.first().species.should.equal("Politoed")
@team2.first().isFainted().should.be.true
@battle.areAllRequestsCompleted().should.be.false
# There should be no weather.
@battle.hasWeather().should.be.false

1509
test/bw/items.coffee Normal file

File diff suppressed because it is too large Load Diff

7723
test/bw/moves.coffee Normal file

File diff suppressed because it is too large Load Diff

492
test/bw/pokemon.coffee Normal file
View File

@@ -0,0 +1,492 @@
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

35
test/bw/priorities.coffee Normal file
View File

@@ -0,0 +1,35 @@
require '../helpers'
{Ability} = require('../../server/bw/data/abilities')
{Item} = require('../../server/bw/data/items')
{Attachment, Status} = require('../../server/bw/attachment')
Priorities = require('../../server/bw/priorities')
Query = require('../../server/bw/queries')
shared = require('../shared')
describe "BW Priorities:", ->
ensureAttachments = (arrayOfAttachments, eventName) ->
attachments = (a for a in arrayOfAttachments when a.prototype[eventName]? && a not in Priorities[eventName])
attachments = attachments.map((a) -> a.displayName || a::name)
if attachments.length > 0
throw new Error("#{attachments.join(', ')} must specify their #{eventName} priority.")
it "ensures all relevant attachments have their specified event names", ->
for eventName of Priorities
ensureAttachments((klass for name, klass of Attachment), eventName)
ensureAttachments((klass for name, klass of Item), eventName)
ensureAttachments((klass for name, klass of Ability), eventName)
describe "Queries", ->
it "execute priorities in order", ->
shared.create.call(this)
@battle.attach(Attachment.TrickRoom)
@team2.attach(Attachment.Reflect)
@p1.attach(Attachment.Ingrain)
spy1 = @sandbox.spy(Attachment.TrickRoom.prototype, 'endTurn')
spy2 = @sandbox.spy(Attachment.Reflect.prototype, 'endTurn')
spy3 = @sandbox.spy(Attachment.Ingrain.prototype, 'endTurn')
Query("endTurn", @battle.getAllAttachments())
spy3.calledBefore(spy2).should.be.true
spy2.calledBefore(spy1).should.be.true

142
test/factory.coffee Normal file
View File

@@ -0,0 +1,142 @@
{_} = require 'underscore'
factories = {}
@Factory = Factory = (species, attributes={}) ->
if species not of factories
Factory.register(species)
cloned = _.clone(factories[species])
_.extend(cloned, attributes)
Factory.register = (species, attributes={}) ->
cloned = _.clone(attributes)
_.defaults(cloned, species: species)
factories[species] = cloned
Factory.register 'Hitmontop',
ability: 'Technician'
moves: ['Rapid Spin', 'Mach Punch', 'Bullet Punch', 'Close Combat' ]
evs: {attack: 252}
Factory.register 'Hitmonchan',
ability: 'Iron Fist'
moves: ['Ice Punch', 'Mach Punch', 'Bullet Punch', 'ThunderPunch' ]
evs: {attack: 252}
Factory.register 'Hitmonlee',
moves: ['Hi Jump Kick']
Factory.register 'Mew',
ability: 'Synchronize'
moves: ['Psychic', 'AncientPower']
Factory.register 'Corphish',
moves: ['Trump Card', "Crunch", "Round"]
Factory.register 'Porygon-Z',
ability: 'Adaptability'
moves: ['Tri Attack', 'Flamethrower']
evs: {specialAttack: 252}
Factory.register 'Heracross',
ability: 'Guts'
moves: ['Megahorn']
evs: {attack: 252}
Factory.register 'Magikarp',
moves: ['Splash', 'Tackle']
Factory.register 'Gyarados',
moves: ['Dragon Dance', 'Ice Fang']
Factory.register 'Conkeldurr',
moves: ['Drain Punch']
Factory.register 'Gliscor',
moves: ['Acrobatics']
Factory.register 'Blaziken',
moves: ['Brave Bird', 'Aerial Ace']
Factory.register 'Celebi',
moves: ['Leaf Storm', 'Grass Knot']
Factory.register 'Weezing',
moves: ['Haze', 'Psywave']
Factory.register 'Blissey',
moves: ['Seismic Toss', 'Aromatherapy', 'Thunder Wave', 'Softboiled']
Factory.register 'Skarmory',
moves: ['Spikes', 'Whirlwind']
Factory.register 'Zangoose',
moves: ['Facade', 'Flail']
Factory.register 'Forretress',
moves: ['Gyro Ball', 'Rapid Spin']
Factory.register 'Electrode',
moves: ['Gyro Ball', 'Explosion']
Factory.register 'Camerupt',
moves: ['Eruption', 'Yawn', 'Earthquake']
Factory.register 'Empoleon',
moves: ['Brine']
Factory.register 'Lapras',
moves: ['Sheer Cold']
Factory.register 'Audino',
moves: ['Growl']
Factory.register 'Gengar',
moves: ['Shadow Ball', 'Pain Split', 'Explosion', 'Clear Smog']
Factory.register 'Drapion',
moves: ['Knock Off', 'Swords Dance']
Factory.register 'Alakazam',
moves: ['Trick']
Factory.register 'Gastrodon',
moves: ['Recover']
ability: 'Sticky Hold'
Factory.register 'Latias',
moves: ['Memento']
Factory.register 'Poliwrath',
moves: ['Belly Drum']
Factory.register 'Shuckle',
moves: ['Acupressure']
Factory.register 'Dratini',
moves: ['Dragon Rage']
Factory.register 'Politoed',
moves: ['Perish Song', 'Endeavor']
ability: 'Drizzle'
Factory.register 'Dugtrio',
moves: ['Magnitude', 'Dig']
Factory.register 'Regirock',
moves: ['Rock Slide']
Factory.register 'Shaymin',
moves: ['Seed Flare']
Factory.register 'Ditto',
moves: ['Transform']
Factory.register 'Metagross',
moves: ['Meteor Mash']
Factory.register 'Gallade',
moves: ['Close Combat']
Factory.register 'Cloyster',
moves: ['Shell Smash']

89
test/glicko2_spec.coffee Normal file
View File

@@ -0,0 +1,89 @@
require './helpers'
glicko2 = require '../server/glicko2'
describe "Glicko2", ->
beforeEach ->
@__tau = glicko2.config.TAU
glicko2.config.TAU = 0.5
@p1 = {rating: 1500, deviation: 200, volatility: 0.06}
@p2 = {rating: 1400, deviation: 30}
@p3 = {rating: 1550, deviation: 100}
@p4 = {rating: 1700, deviation: 300}
@matches = [
{opponent: @p2, score: 1}
{opponent: @p3, score: 0}
{opponent: @p4, score: 0}
]
@opponents = @matches.map((m) -> m.opponent)
@scores = @matches.map((m) -> m.score)
afterEach ->
glicko2.config.TAU = @__tau
describe "#ratingFromGlicko", ->
it "transforms ratings to Glicko2 from Glicko", ->
glicko2.ratingFromGlicko(@p1.rating).should.equal(0)
glicko2.ratingFromGlicko(@p2.rating).should.equal(-0.57746122636595565791)
glicko2.ratingFromGlicko(@p3.rating).should.equal(0.2887306131829778)
glicko2.ratingFromGlicko(@p4.rating).should.equal(1.1549224527319113)
describe "#deviationFromGlicko", ->
it "transforms deviations to Glicko2 from Glicko", ->
glicko2.deviationFromGlicko(@p1.deviation).should.equal(1.1549224527319113)
glicko2.deviationFromGlicko(@p2.deviation).should.equal(0.1732383679097867)
glicko2.deviationFromGlicko(@p3.deviation).should.equal(0.5774612263659556)
glicko2.deviationFromGlicko(@p4.deviation).should.equal(1.732383679097867)
describe "#g", ->
it "applies a function to a deviation", ->
dfg = glicko2.deviationFromGlicko
glicko2.g(dfg(@p2.deviation)).should.equal(0.9954697656070619)
glicko2.g(dfg(@p3.deviation)).should.equal(0.9528736649355471)
glicko2.g(dfg(@p4.deviation)).should.equal(0.7231504586157951)
describe "#E", ->
it "applies a function to a deviation", ->
rfg = glicko2.ratingFromGlicko
dfg = glicko2.deviationFromGlicko
glicko2.E(rfg(@p1.rating), rfg(@p2.rating), dfg(@p2.deviation)).should.equal(0.6398804244872593)
glicko2.E(rfg(@p1.rating), rfg(@p3.rating), dfg(@p3.deviation)).should.equal(0.431649645227976)
glicko2.E(rfg(@p1.rating), rfg(@p4.rating), dfg(@p4.deviation)).should.equal(0.30255033173677587)
describe "calculations", ->
beforeEach ->
rfg = glicko2.ratingFromGlicko
dfg = glicko2.deviationFromGlicko
@gPlayers = []
@ePlayers = []
for opponent in @opponents
@gPlayers.push glicko2.g(dfg(opponent.deviation))
@ePlayers.push glicko2.E(rfg(@p1.rating), rfg(opponent.rating), dfg(opponent.deviation))
describe "#calculateEstimatedVariance", ->
it "estimates variance based on the g and E factors of opponents", ->
estimatedVariance = glicko2.calculateEstimatedVariance(@gPlayers, @ePlayers)
estimatedVariance.should.equal(1.7811042373512078)
describe "#calculateImprovementSum", ->
it "sums up improvements", ->
improvementSum = glicko2.calculateImprovementSum(@gPlayers, @ePlayers, @scores)
improvementSum.should.equal(-0.27160884114013806)
describe "#calculateNewVolatility", ->
it "creates a new volatility", ->
estimatedVariance = glicko2.calculateEstimatedVariance(@gPlayers, @ePlayers)
improvementSum = glicko2.calculateImprovementSum(@gPlayers, @ePlayers, @scores)
glicko2.calculateNewVolatility(improvementSum,
glicko2.deviationFromGlicko(@p1.deviation),
estimatedVariance,
@p1.volatility,
glicko2.config.TAU).should.equal(0.059995996477963935)
describe "#calculate", ->
it "combines all the previous steps together correctly", ->
{rating, deviation, volatility} = glicko2.calculate(@p1, @matches)
rating.should.equal(1464.0719365246412)
deviation.should.equal(151.3503881127135)
volatility.should.equal(0.059995996477963935)

19
test/helpers.coffee Normal file
View File

@@ -0,0 +1,19 @@
sinon = require 'sinon'
redis = require '../server/redis'
{EventEmitter} = require 'events'
process.env.NODE_ENV = 'test'
beforeEach ->
@sandbox = sinon.sandbox.create()
@clock = sinon.useFakeTimers()
@stubSpark = ->
ee = new EventEmitter()
ee.send = (->)
ee.end = (-> @emit('end'))
ee
afterEach (done) ->
@clock.restore()
@sandbox.restore()
redis.flushdb(done)

273
test/learnsets_spec.coffee Normal file
View File

@@ -0,0 +1,273 @@
require './helpers'
{_} = require('underscore')
learnsets = require('../shared/learnsets')
{GenerationJSON} = require '../server/generations'
describe "Learnsets:", ->
testLearnset = (pokemon, func) ->
describe pokemon, ->
beforeEach ->
@pokemon = {species: pokemon}
@checkMoveset = learnsets.checkMoveset.bind(
learnsets, GenerationJSON, @pokemon)
func.call(this)
testLearnset "Tyranitar", ->
it "can learn level-up moves once that level, in its own generation", ->
@pokemon.level = 29
@checkMoveset(3, [ "Thrash" ]).should.be.true
it "cannot learn level-up moves if the level has not been reached", ->
@pokemon.level = 28
@checkMoveset(3, [ "Thrash" ]).should.be.false
it "can learn machine moves in its own generation", ->
@pokemon.level = 1
@checkMoveset(2, [ "Attract" ]).should.be.true
it "can learn tutor moves in its own generation", ->
@pokemon.level = 1
@checkMoveset(4, [ "Spite" ]).should.be.true
it "can learn tutor moves below its generation", ->
@pokemon.level = 1
@checkMoveset(4, [ "Seismic Toss" ]).should.be.true
testLearnset "Mewtwo", ->
it "can learn machine moves below its generation", ->
@checkMoveset(2, [ "Bide" ]).should.be.true
it "cannot learn machine moves below a cutoff point", ->
@checkMoveset(3, [ "Bide" ]).should.be.false
testLearnset "Vaporeon", ->
it "can learn Take Down indirectly from Eevee", ->
@checkMoveset(3, [ "Take Down" ]).should.be.true
it "can learn Wish through cross-breeding", ->
@checkMoveset(3, [ "Wish" ]).should.be.true
testLearnset "Smeargle", ->
it "can learn almost any move", ->
@checkMoveset(3, [ "Spore" ]).should.be.true
it "cannot learn Chatter", ->
@checkMoveset(3, [ "Chatter" ]).should.be.false
it "cannot learn Struggle", ->
@checkMoveset(3, [ "Struggle" ]).should.be.false
for pokemon in [ "Pichu", "Raichu" ]
testLearnset pokemon, ->
it "can learn Volt Tackle", ->
@checkMoveset(3, [ "Volt Tackle" ]).should.be.true
it "can learn Encore", ->
@checkMoveset(3, [ "Encore" ]).should.be.true
it "cannot learn Volt Tackle and Encore", ->
@checkMoveset(3, [ "Volt Tackle", "Encore" ]).should.be.false
testLearnset "Rotom", ->
it "Gen 4: learns Overheat in its heat forme", ->
@pokemon.forme = "heat"
@checkMoveset(4, [ "Overheat" ]).should.be.true
it "Gen 5: learns Overheat in its heat forme", ->
@pokemon.forme = "heat"
@checkMoveset(5, [ "Overheat" ]).should.be.true
it "Gen 4: learns Overheat and a standard move in its heat forme", ->
@pokemon.forme = "heat"
@checkMoveset(4, [ "Overheat", "Thunderbolt" ]).should.be.true
it "Gen 5: learns Overheat and a standard move in its heat forme", ->
@pokemon.forme = "heat"
@checkMoveset(5, [ "Overheat", "Thunderbolt" ]).should.be.true
it "Gen 4: learns Air Slash in its fan forme", ->
@pokemon.forme = "fan"
@checkMoveset(4, [ "Air Slash" ]).should.be.true
it "Gen 5: learns Air Slash in its fan forme", ->
@pokemon.forme = "fan"
@checkMoveset(5, [ "Air Slash" ]).should.be.true
it "Gen 4: learns Blizzard in its frost forme", ->
@pokemon.forme = "frost"
@checkMoveset(4, [ "Blizzard" ]).should.be.true
it "Gen 5: learns Blizzard in its frost forme", ->
@pokemon.forme = "frost"
@checkMoveset(5, [ "Blizzard" ]).should.be.true
it "Gen 4: learns Leaf Storm in its mow forme", ->
@pokemon.forme = "mow"
@checkMoveset(4, [ "Leaf Storm" ]).should.be.true
it "Gen 5: learns Leaf Storm in its mow forme", ->
@pokemon.forme = "mow"
@checkMoveset(5, [ "Leaf Storm" ]).should.be.true
it "Gen 4: learns Hydro Pump in its wash forme", ->
@pokemon.forme = "wash"
@checkMoveset(4, [ "Hydro Pump" ]).should.be.true
it "Gen 5: learns Hydro Pump in its wash forme", ->
@pokemon.forme = "wash"
@checkMoveset(5, [ "Hydro Pump" ]).should.be.true
it "Gen 4: cannot learn Blizzard in its default forme", ->
@checkMoveset(4, [ "Blizzard" ]).should.be.false
it "Gen 5: cannot learn Blizzard in its default forme", ->
@checkMoveset(5, [ "Blizzard" ]).should.be.false
it "Gen 4: cannot learn Blizzard in its heat forme", ->
@pokemon.forme = "heat"
@checkMoveset(4, [ "Blizzard", "Overheat" ]).should.be.false
it "Gen 5: cannot learn Blizzard in its heat forme", ->
@pokemon.forme = "heat"
@checkMoveset(5, [ "Blizzard", "Overheat" ]).should.be.false
it "Gen 4: must know Blizzard in its frost forme", ->
@pokemon.forme = "frost"
@checkMoveset(4, [ "Thunderbolt" ]).should.be.false
it "Gen 5: doesn't need to know Blizzard in its frost forme", ->
@pokemon.forme = "frost"
@checkMoveset(5, [ "Thunderbolt" ]).should.be.true
testLearnset "Deoxys", ->
it "learns alternate forme moves due to freely switching formes", ->
@pokemon.forme = "attack"
@checkMoveset(4, [ "Spikes" ]).should.be.true
testLearnset "Shaymin", ->
it "learns alternate forme moves due to freely switching formes", ->
@checkMoveset(4, [ "Air Slash" ]).should.be.true
testLearnset "Mamoswine", ->
it "cannot know 4 egg moves", ->
moveset = [ "Bite", "Fissure", "Curse", "Icicle Spear" ]
@checkMoveset(4, moveset).should.be.false
testLearnset "Yanmega", ->
it "cannot know 4 egg moves", ->
moveset = [ "Faint Attack", "Leech Life", "Whirlwind", "Reversal" ]
@checkMoveset(4, moveset).should.be.false
testLearnset "Tangrowth", ->
it "cannot know 4 egg moves", ->
moveset = [ "Amnesia", "Endeavor", "Leaf Storm", "Power Swap" ]
@checkMoveset(4, moveset).should.be.false
testLearnset "Mr. Mime", ->
it "cannot know 4 egg moves", ->
moveset = [ "Charm", "Healing Wish", "Fake Out", "Teeter Dance" ]
@checkMoveset(4, moveset).should.be.false
testLearnset "Ambipom", ->
it "cannot know 4 egg moves", ->
moveset = [ "Slam", "Covet", "Beat Up", "Pursuit" ]
@checkMoveset(4, moveset).should.be.false
testLearnset "Lickilicky", ->
it "cannot know 4 egg moves", ->
moveset = [ "Amnesia", "Curse", "Belly Drum", "Magnitude" ]
@checkMoveset(4, moveset).should.be.false
testLearnset "Roserade", ->
xit "cannot learn Spikes and Sleep Powder", ->
@checkMoveset(4, [ "Spikes", "Sleep Powder" ]).should.be.false
testLearnset "Celebi", ->
it "can learn Nasty Plot", ->
moveset = [ "Nasty Plot" ]
@checkMoveset(4, moveset).should.be.true
testLearnset "Banette", ->
it "can learn an event move but not an egg move", ->
moveset = [ "Cotton Guard", "Pursuit" ]
@checkMoveset(4, moveset).should.be.false
testLearnset "Gothitelle", ->
it "can inherit event moves from its pre-evos", ->
moveset = [ "Imprison" ]
@checkMoveset(5, moveset).should.be.true
testLearnset "Gengar", ->
it "learns Sludge Wave from dream world", ->
moveset = [ "Sludge Wave" ]
@checkMoveset(6, moveset).should.be.true
testLearnset "Clefable", ->
it "cannot learn Softboiled if it has Unaware", ->
@pokemon.ability = "Unaware"
moveset = [ "Softboiled" ]
@checkMoveset(6, moveset).should.be.false
it "can learn Softboiled normally", ->
moveset = [ "Softboiled" ]
@checkMoveset(6, moveset).should.be.true
testLearnset "Gliscor", ->
it "cannot learn Defog if it has Poison Heal", ->
@pokemon.ability = "Poison Heal"
moveset = [ "Defog" ]
@checkMoveset(6, moveset).should.be.false
describe "learnableMoves", ->
it "returns all possible learnable moves for a pokemon", ->
learnableMoves = learnsets.learnableMoves.bind(learnsets, GenerationJSON)
moves = learnableMoves({species: "Deoxys"}, 6)
moves.should.containEql("Superpower")
it "returns event moves", ->
learnableMoves = learnsets.learnableMoves.bind(learnsets, GenerationJSON)
moves = learnableMoves({species: "Genesect"}, 6)
moves.should.containEql("Shift Gear")
it "returns dream world moves", ->
learnableMoves = learnsets.learnableMoves.bind(learnsets, GenerationJSON)
moves = learnableMoves({species: "Gengar"}, 6)
moves.should.containEql("Sludge Wave")
it "returns valid moves for battle-only formes", ->
learnableMoves = learnsets.learnableMoves.bind(learnsets, GenerationJSON)
moves = learnableMoves({species: "Darmanitan", forme: "zen"}, 6)
# Only Darmanitan can learn this move; Darumaka can't.
moves.should.containEql("Bulk Up")
# Egg move.
moves.should.containEql("Encore")
it "does not take into account nonstandard learnsets", ->
learnableMoves = learnsets.learnableMoves.bind(learnsets, GenerationJSON)
moves = learnableMoves({species: "Rotom"}, 6)
moves.should.not.containEql("Overheat")
moves.should.not.containEql("Leaf Storm")
moves.should.not.containEql("Hydro Pump")
moves.should.not.containEql("Blizzard")
moves.should.not.containEql("Air Slash")
it "lets pokemon learn nonstandard learnsets if they are of that forme", ->
learnableMoves = learnsets.learnableMoves.bind(learnsets, GenerationJSON)
moves = learnableMoves({species: "Rotom", forme: "wash"}, 6)
moves.should.not.containEql("Overheat")
moves.should.not.containEql("Leaf Storm")
moves.should.containEql("Hydro Pump")
moves.should.not.containEql("Blizzard")
moves.should.not.containEql("Air Slash")
it "returns all moves except Struggle and Chatter if can learn Sketch", ->
learnableMoves = learnsets.learnableMoves.bind(learnsets, GenerationJSON)
moves = learnableMoves({species: "Smeargle"}, 6)
moves.should.not.containEql("Struggle")
moves.should.not.containEql("Chatter")
moves.should.containEql("Sacred Fire")
moves.should.containEql("Aeroblast")

27
test/logger_spec.coffee Normal file
View File

@@ -0,0 +1,27 @@
require('./helpers')
should = require('should')
logger = require('../server/logger')
redis = require('../server/redis')
describe "Logger", ->
describe "#log", ->
it "writes to redis", ->
logger.log "hello", {a: 1}, ->
logger.log "hello2", {b: 2}, (err) ->
should.not.exist(err)
redis.llen "log", (err, result) ->
result.should.equal(2)
describe "#withContext", ->
it "creates a new logger with modified context", ->
stack1 = logger.withContext(a: 1)
result = stack1.log("Hello", b: 2)
result.should.eql(JSON.stringify({message: "Hello", context: {a: 1, b: 2}}))
it "stacks contexts", ->
stack1 = logger.withContext(a: 1)
stack2 = stack1.withContext(b: 2)
stack1.context.should.eql(a: 1)
stack2.context.should.eql(a: 1, b: 2)

3
test/mocha.opts Normal file
View File

@@ -0,0 +1,3 @@
--require should
--compilers coffee:coffee-script/register
--recursive

View File

@@ -0,0 +1,53 @@
require './helpers'
{_} = require 'underscore'
values = require('../shared/pokebattle_values')
{GenerationJSON} = require '../server/generations'
describe "determining PBV", ->
it "returns the total PBV for a single Pokemon", ->
pokemon = {species: "Charizard"}
pbv = GenerationJSON.XY.FormeData[pokemon.species].default.pokeBattleValue
values.determinePBV(GenerationJSON.XY, pokemon).should.equal(pbv)
it "takes mega formes into account", ->
pokemon = {species: "Charizard", item: "Charizardite X"}
pbv = GenerationJSON.XY.FormeData[pokemon.species]['mega-x'].pokeBattleValue
values.determinePBV(GenerationJSON.XY, pokemon).should.equal(pbv)
it "does not count items that do not match the species", ->
pokemon = {species: "Charizard", item: "Blazikenite"}
pbv = GenerationJSON.XY.FormeData[pokemon.species].default.pokeBattleValue
values.determinePBV(GenerationJSON.XY, pokemon).should.equal(pbv)
it "adds a x1.3 multiplier with eviolite, rounded to nearest 5", ->
pokemon = {species: "Cleffa", item: "Eviolite"}
pbv = GenerationJSON.XY.FormeData[pokemon.species].default.pokeBattleValue
xy = _.clone(GenerationJSON.XY)
formes = xy.FormeData
formes['Cleffa']['default'].pokeBattleValue = 10
values.determinePBV(xy, pokemon).should.equal(15)
formes['Cleffa']['default'].pokeBattleValue = 55
values.determinePBV(xy, pokemon).should.equal(70)
it "adds +15 PBV to a baton passer", ->
pokemon = {species: "Blaziken"}
base = values.determinePBV(GenerationJSON.XY, pokemon)
pokemon.moves = [ "Baton Pass" ]
values.determinePBV(GenerationJSON.XY, pokemon).should.equal(base + 15)
it "doubles the more passers the team has", ->
pokemon = [{species: "Blaziken"}, {species: "Espeon"}]
base = values.determinePBV(GenerationJSON.XY, pokemon)
pokemon.forEach((p) -> p.moves = [ "Baton Pass" ])
values.determinePBV(GenerationJSON.XY, pokemon).should.equal(base + 30)
pokemon = ({species: "Blaziken"} for x in [0...6])
base = values.determinePBV(GenerationJSON.XY, pokemon)
pokemon.forEach((p) -> p.moves = [ "Baton Pass" ])
values.determinePBV(GenerationJSON.XY, pokemon).should.equal(base + 480)

139
test/rating_spec.coffee Normal file
View File

@@ -0,0 +1,139 @@
require('./helpers')
should = require('should')
ratings = require('../server/ratings')
async = require('async')
alts = require('../server/alts')
describe "Ratings", ->
describe "#getPlayer", ->
it "returns default information for new players", (done) ->
ratings.getPlayer "bogus player", (err, result) ->
should.not.exist(err)
should.exist(result)
result.should.be.instanceOf(Object)
result.should.containEql(rating: ratings.DEFAULT_RATING)
done()
it "returns information for an existing player", (done) ->
ratings.updatePlayers "player1", "player2", ratings.results.WIN, ->
ratings.getPlayer "player1", (err, result) ->
should.not.exist(err)
should.exist(result)
result.should.be.instanceOf(Object)
result.rating.should.be.greaterThan(ratings.DEFAULT_RATING)
done()
describe "#resetRating", ->
it "resets the rating of a player", (done) ->
ratings.updatePlayers "player1", "player2", ratings.results.WIN, ->
ratings.resetRating "player1", ->
ratings.getRatings [ "player1", "player2" ], (err, results) ->
results[0].should.equal(ratings.DEFAULT_RATING)
results[1].should.not.equal(ratings.DEFAULT_RATING)
results[1].should.be.lessThan(ratings.DEFAULT_RATING)
done()
describe "#getMaxRating", ->
it "returns the rating of a user if there's no alts", (done) ->
ratings.setRating "user", 25, (err) ->
ratings.getMaxRating "user", (err, result) ->
result.should.equal(25)
done()
it "returns the maximum rating of a user and their alts", (done) ->
altOps = ["alt1","alt2"].map (altName) ->
(callback) -> alts.createAlt("user", altName, callback)
async.parallel altOps, ->
ratings.setRating "user", 25, (err) ->
ratings.setRating alts.uniqueId("user", "alt1"), 5, (err) ->
ratings.setRating alts.uniqueId("user", "alt2"), 30, (err) ->
ratings.getMaxRating "user", (err, result) ->
result.should.equal(30)
done()
describe "#listRatings", ->
it "returns a list of ratings", (done) ->
r = []
# 1 > 3 > 2
ratings.updatePlayers "player1", "player2", ratings.results.WIN, ->
ratings.updatePlayers "player3", "player2", ratings.results.WIN, ->
ratings.getRatings [1..3].map((i) -> "player#{i}"), (err, scores) ->
ratings.listRatings 1, 2, (err, results) ->
r = r.concat(results)
ratings.listRatings 2, 2, (err, results) ->
r = r.concat(results)
r.should.eql([
{username: "player1", score: scores[0]}
{username: "player3", score: scores[2]}
{username: "player2", score: scores[1]}
])
done()
it "returns up to the maximum per page", (done) ->
ratings.updatePlayers "player1", "player2", ratings.results.WIN, ->
ratings.updatePlayers "player3", "player4", ratings.results.WIN, ->
ratings.listRatings 1, 2, (err, results) ->
should.not.exist(err)
results.should.have.length(2)
done()
it "does not include alts", (done) ->
ratings.updatePlayers "player1", "player2", ratings.results.WIN, ->
altName = alts.uniqueId('player1', 'altName')
ratings.updatePlayers altName, "player2", ratings.results.WIN, ->
ratings.listRatings 0, 100, (err, results) ->
results.length.should.equal(2)
results.some((r) -> r.username == altName).should.be.false
done()
it "returns the max rating for a user and their alts", (done) ->
# Operations to create alts and then set the rating
altOps = [["alt1", 5], ["alt2", 30]].map (pair) ->
(callback) ->
[altName, rating] = pair
alts.createAlt "user", altName, ->
ratings.setRating(alts.uniqueId("user", altName), rating, callback)
async.parallel altOps, ->
ratings.setRating "user", 15, ->
ratings.listRatings 0, 100, (err, results) ->
results.should.eql([{username: "user", score: 30}])
done()
describe '#getRatio', ->
it "returns a hash containing the win, lose, and draw counts", (done) ->
ratings.updatePlayers "player1", "player2", ratings.results.WIN, ->
ratings.updatePlayers "player1", "player2", ratings.results.WIN, ->
ratings.updatePlayers "player2", "player1", ratings.results.WIN, ->
ratings.getRatio "player1", (err, player1Ratio) ->
player1Ratio.should.eql(win: 2, lose: 1, draw: 0)
ratings.getRatio "player2", (err, player2Ratio) ->
player2Ratio.should.eql(win: 1, lose: 2, draw: 0)
done()
describe '#getRatio', ->
it "returns a hash contain the current and maximum win streaks", (done) ->
ratings.updatePlayers "player1", "player2", ratings.results.WIN, ->
ratings.updatePlayers "player1", "player2", ratings.results.WIN, ->
ratings.updatePlayers "player1", "player2", ratings.results.LOSE, ->
ratings.updatePlayers "player1", "player2", ratings.results.WIN, ->
ratings.getStreak "player1", (err, streak) ->
streak.should.eql(streak: 1, maxStreak: 2)
done()
describe '#getRank', ->
it "returns the rank of a player", (done) ->
ratings.updatePlayers "player1", "player2", ratings.results.WIN, ->
ratings.getRank "player1", (err, rank) ->
rank.should.equal(1)
ratings.getRank "player2", (err, rank) ->
rank.should.equal(2)
ratings.updatePlayers "player2", "player1", ratings.results.WIN, ->
ratings.updatePlayers "player2", "player1", ratings.results.WIN, ->
ratings.getRank "player1", (err, rank) ->
rank.should.equal(2)
ratings.getRank "player2", (err, rank) ->
rank.should.equal(1)
done()

View File

@@ -0,0 +1,16 @@
auth = require("../../server/auth")
describe "Authorization", ->
describe "setAuth", ->
it "changes/gets the auth level for a user", (done) ->
username = "a user"
auth.setAuth username, auth.levels.MOD, ->
auth.getAuth username, (err, level) ->
level.should.equal(auth.levels.MOD)
done()
describe "getAuth", ->
it "returns the default auth of a user if non-existent", (done) ->
auth.getAuth "derpa", (err, level) ->
level.should.equal(auth.levels.USER)
done()

View File

@@ -0,0 +1,481 @@
require('../helpers')
should = require('should')
async = require 'async'
primus = require('primus')
commands = require('../../server/commands')
alts = require('../../server/alts')
auth = require('../../server/auth')
{User} = require('../../server/user')
{BattleServer} = require('../../server/server')
{Room} = require('../../server/rooms')
ratings = require('../../server/ratings')
{Factory} = require '../factory'
redis = require('../../server/redis')
generateTeam = ->
[ Factory("Magikarp")
Factory("Gyarados")
Factory('Hitmonchan')
Factory("Celebi")
Factory("Blissey")
Factory("Alakazam") ]
describe "Commands", ->
beforeEach ->
@server = new BattleServer()
@room = new Room()
@spark1 = @stubSpark()
@spark2 = @stubSpark()
@user1 = @server.findOrCreateUser(id: 1, name: "Star Fox", @spark1)
@user2 = @server.findOrCreateUser(id: 2, name: "Slippy", @spark2)
@aardvark = @server.findOrCreateUser(id: 3, name: 'aardvark', @stubSpark())
@bologna = @server.findOrCreateUser(id: 3, name: 'bologna', @stubSpark())
@offlineName = "husk"
@room.add(@spark1)
@room.add(@spark2)
@emptyRoom = new Room()
@server.rooms.push(@room)
@server.join(@spark1)
@server.join(@spark2)
describe "#executeCommand", ->
describe "an invalid command", ->
it "returns an error to the user", ->
mock1 = @sandbox.mock(@user1).expects('error').once()
mock2 = @sandbox.mock(@user2).expects('error').never()
commands.executeCommand(@server, @user1, @room, "an invalid command")
mock1.verify()
mock2.verify()
describe "rating", ->
user1Rating = 9001
user2Rating = 101
beforeEach (done) ->
ratings.setRating @user1.name, user1Rating, =>
ratings.setRating(@user2.name, user2Rating, done)
it "returns the user's rating to the user without arguments", (done) ->
spy = @sandbox.spy(@user1, 'announce')
commands.executeCommand @server, @user1, @room, "rating", =>
spy.callCount.should.equal(1)
spy.firstCall.args[2].should.containEql(@user1.name)
spy.firstCall.args[2].should.containEql(user1Rating)
done()
it "returns someone's rating to the user as an argument", (done) ->
spy = @sandbox.spy(@user1, 'announce')
commands.executeCommand @server, @user1, @room, "rating", @user2.name, =>
spy.callCount.should.equal(1)
spy.callCount.should.equal(1)
spy.firstCall.args[2].should.containEql(@user2.name)
spy.firstCall.args[2].should.containEql(user2Rating)
done()
describe "voice", ->
it "returns an error if insufficient authority", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
mock2 = @sandbox.mock(@user2).expects('error').never()
commands.executeCommand @server, @user1, @room, "voice", @user2.name, ->
mock1.verify()
mock2.verify()
done()
it "voices a user if owner", (done) ->
@user1.authority = auth.levels.OWNER
commands.executeCommand @server, @user1, @room, "voice", @user2.name, =>
@user2.should.have.property("authority")
@user2.authority.should.equal(auth.levels.DRIVER)
done()
it "does not crash if user isn't on yet", (done) ->
@user1.authority = auth.levels.OWNER
commands.executeCommand @server, @user1, @room, "voice", @offlineName, =>
auth.getAuth @offlineName, (err, result) ->
result.should.equal(auth.levels.DRIVER)
done()
describe "mod", ->
it "returns an error if insufficient authority", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
mock2 = @sandbox.mock(@user2).expects('error').never()
commands.executeCommand @server, @user1, @room, "mod", @user2.name, ->
mock1.verify()
mock2.verify()
done()
it "mods a user if owner", (done) ->
@user1.authority = auth.levels.OWNER
commands.executeCommand @server, @user1, @room, "mod", @user2.name, =>
@user2.should.have.property("authority")
@user2.authority.should.equal(auth.levels.MOD)
done()
it "does not crash if user isn't on yet", (done) ->
@user1.authority = auth.levels.OWNER
commands.executeCommand @server, @user1, @room, "mod", @offlineName, =>
auth.getAuth @offlineName, (err, result) ->
result.should.equal(auth.levels.MOD)
done()
describe "admin", ->
it "returns an error if insufficient authority", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
mock2 = @sandbox.mock(@user2).expects('error').never()
commands.executeCommand @server, @user1, @room, "admin", @user2.name, ->
mock1.verify()
mock2.verify()
done()
it "admins a user if owner", (done) ->
@user1.authority = auth.levels.OWNER
commands.executeCommand @server, @user1, @room, "admin", @user2.name, =>
@user2.should.have.property("authority")
@user2.authority.should.equal(auth.levels.ADMIN)
done()
it "does not crash if user isn't on yet", (done) ->
@user1.authority = auth.levels.OWNER
commands.executeCommand @server, @user1, @room, "admin", @offlineName, =>
auth.getAuth @offlineName, (err, result) ->
result.should.equal(auth.levels.ADMIN)
done()
describe "deauth", ->
it "returns an error if insufficient authority", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
mock2 = @sandbox.mock(@user2).expects('error').never()
commands.executeCommand @server, @user1, @room, "deauth", @user2.name, ->
mock1.verify()
mock2.verify()
done()
it "deauthes a user if owner", (done) ->
@user1.authority = auth.levels.OWNER
commands.executeCommand @server, @user1, @room, "deauth", @user2.name, =>
@user2.should.have.property("authority")
@user2.authority.should.equal(auth.levels.USER)
done()
it "does not crash if user isn't on yet", (done) ->
@user1.authority = auth.levels.OWNER
commands.executeCommand @server, @user1, @room, "deauth", @offlineName, =>
auth.getAuth @offlineName, (err, result) ->
result.should.equal(auth.levels.USER)
done()
describe "ban", ->
it "returns an error if insufficient authority", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
mock2 = @sandbox.mock(@spark2).expects('end').never()
commands.executeCommand @server, @user1, @room, "ban", @user2.name, ->
mock1.verify()
mock2.verify()
done()
it "bans a user if mod", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').never()
mock2 = @sandbox.mock(@spark2).expects('end').once()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "ban", @user2.name, =>
mock1.verify()
mock2.verify()
auth.getBanTTL @user2.name, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(60 * 60)
done()
it "bans a user even if user isn't on yet", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "ban", @offlineName, =>
mock1.verify()
auth.getBanTTL @offlineName, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(60 * 60)
done()
it "bans a user for a specified amount of time", (done) ->
mock = @sandbox.mock(@user1).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "ban", @user2.name, "23h", =>
mock.verify()
auth.getBanTTL @user2.name, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(23 * 60 * 60)
done()
it "defaults to an hour if banning for zero minutes", (done) ->
mock = @sandbox.mock(@user1).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "ban", @user2.name, "0", =>
mock.verify()
auth.getBanTTL @user2.name, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(60 * 60)
done()
it "cannot ban over one day if mod", (done) ->
mock = @sandbox.mock(@user1).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "ban", @user2.name, "1y", =>
mock.verify()
auth.getBanTTL @user2.name, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(1 * 24 * 60 * 60)
done()
describe "unban", ->
it "returns an error if insufficient authority", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
mock2 = @sandbox.mock(@user2).expects('error').never()
commands.executeCommand @server, @user1, @room, "unban", @user2.name, =>
mock1.verify()
mock2.verify()
done()
it "unbans a user if mod", (done) ->
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "ban", @user2.name, =>
commands.executeCommand @server, @user1, @room, "unban", @user2.name, =>
auth.getBanTTL @user2.name, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(-2)
done()
it "returns an error if user is not banned", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
mock2 = @sandbox.mock(@user2).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "unban", @user2.name, =>
mock1.verify()
mock2.verify()
done()
it "unbans a user even if user isn't on yet", (done) ->
mock = @sandbox.mock(@user1).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "ban", @offlineName, =>
commands.executeCommand @server, @user1, @room, "unban", @offlineName, =>
mock.verify()
auth.getBanTTL @user2.name, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(-2)
done()
describe "mute", ->
it "returns an error if insufficient authority", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
mock2 = @sandbox.mock(@user2).expects('error').never()
commands.executeCommand @server, @user1, @room, "mute", @user2.name, ->
mock1.verify()
mock2.verify()
done()
it "mutes a user if mod", (done) ->
mock = @sandbox.mock(@user1).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "mute", @user2.name, =>
mock.verify()
auth.getMuteTTL @user2.name, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(10 * 60)
done()
it "mutes a user even if user isn't on yet", (done) ->
mock = @sandbox.mock(@user1).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "mute", @offlineName, =>
mock.verify()
auth.getMuteTTL @offlineName, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(10 * 60)
done()
it "mutes a user for a specified amount of time", (done) ->
mock = @sandbox.mock(@user1).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "mute", @user2.name, "23h", =>
mock.verify()
auth.getMuteTTL @user2.name, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(23 * 60 * 60)
done()
it "defaults to 10 if muting for zero minutes", (done) ->
mock = @sandbox.mock(@user1).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "mute", @user2.name, "0", =>
mock.verify()
auth.getMuteTTL @user2.name, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(10 * 60)
done()
it "cannot mute over two days if mod", (done) ->
mock = @sandbox.mock(@user1).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "mute", @user2.name, "1y", =>
mock.verify()
auth.getMuteTTL @user2.name, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(2 * 24 * 60 * 60)
done()
describe "unmute", ->
it "returns an error if insufficient authority", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
mock2 = @sandbox.mock(@user2).expects('error').never()
commands.executeCommand @server, @user1, @room, "unmute", @user2.name, =>
mock1.verify()
mock2.verify()
done()
it "unmutes a user if mod", (done) ->
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "mute", @user2.name, =>
commands.executeCommand @server, @user1, @room, "unmute", @user2.name, =>
auth.getMuteTTL @user2.name, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(-2)
done()
it "returns an error if user is not muted", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
mock2 = @sandbox.mock(@user2).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "unmute", @user2.name, =>
mock1.verify()
mock2.verify()
done()
it "unmutes a user even if user isn't on yet", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').never()
@user1.authority = auth.levels.MOD
commands.executeCommand @server, @user1, @room, "mute", @offlineName, =>
commands.executeCommand @server, @user1, @room, "unmute", @offlineName, =>
mock1.verify()
auth.getMuteTTL @user2.name, (err, ttl) ->
should.exist(ttl)
ttl.should.equal(-2)
done()
describe "battles", ->
it "returns an error if no user is passed", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
mock2 = @sandbox.mock(@user2).expects('error').never()
commands.executeCommand @server, @user1, @room, "battles", ->
mock1.verify()
mock2.verify()
done()
it "returns all battles that user is in if user is passed", (done) ->
@server.queuePlayer(@user1.name, generateTeam()).should.be.empty
@server.queuePlayer(@user2.name, generateTeam()).should.be.empty
@server.queuePlayer(@aardvark.name, generateTeam()).should.be.empty
@server.queuePlayer(@bologna.name, generateTeam()).should.be.empty
@server.beginBattles (err, battleIds) =>
if err then throw err
battleIds.length.should.equal(2)
spy = @sandbox.spy(@user1, 'announce')
commands.executeCommand @server, @user1, @room, "battles", @user2.name, =>
spy.callCount.should.equal(1)
spy.firstCall.args[2].should.containEql(@user2.name)
spy.firstCall.args[2].should.containEql(battleIds[0])
spy.firstCall.args[2].should.not.containEql(@user1.name)
spy.firstCall.args[2].should.not.containEql(battleIds[1])
done()
it "does not containEql alts in the battle list", (done) ->
@server.queuePlayer(@user1.name, [ Factory("Magikarp") ])
@server.queuePlayer(@user2.name, [ Factory("Magikarp") ], "Im an Alt")
@server.beginBattles (err, battleIds) =>
if err then throw err
spy = @sandbox.spy(@user1, 'announce')
commands.executeCommand @server, @user1, @room, "battles", @user2.name, =>
if err then throw err
spy.callCount.should.equal(1)
spy.firstCall.args[2].should.containEql(@user2.name)
done()
describe "topic", ->
it "returns an error if insufficient authority", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
commands.executeCommand @server, @user1, @room, "topic", "test topic", ->
mock1.verify()
done()
it "updates the channel topic", (done) ->
topicName = "a test"
@user1.authority = auth.levels.ADMIN
mock = @sandbox.mock(@room).expects("setTopic").withArgs(topicName).once()
commands.executeCommand @server, @user1, @room, "topic", topicName, ->
mock.verify()
done()
describe "wall", ->
it "returns an error if insufficient authority", (done) ->
mock1 = @sandbox.mock(@user1).expects('error').once()
commands.executeCommand @server, @user1, @room, "wall", "hi", ->
mock1.verify()
done()
it "messages all rooms and all battles", (done) ->
mock = @sandbox.mock(@room).expects('announce').once()
spy1 = @sandbox.spy(@spark1, 'send')
spy2 = @sandbox.spy(@spark2, 'send')
@server.queuePlayer(@user1.name, generateTeam()).should.be.empty
@server.queuePlayer(@user2.name, generateTeam()).should.be.empty
@server.beginBattles (err, battleIds) =>
if err then throw err
@user1.authority = auth.levels.ADMIN
commands.executeCommand @server, @user1, @room, "wall", "derper", =>
mock.verify()
spy1.calledWithMatch("announce", battleIds[0], 'warning', "derper").should.be.true
spy2.calledWithMatch("announce", battleIds[0], 'warning', "derper").should.be.true
done()
describe "lockdown", ->
it "returns an error if insufficient authority", (done) ->
mock = @sandbox.mock(@user1).expects('error').once()
commands.executeCommand @server, @user1, @room, "lockdown", ->
mock.verify()
done()
it "stops battles from occuring", (done) ->
mock = @sandbox.mock(@server).expects('lockdown').once()
@user1.authority = auth.levels.OWNER
commands.executeCommand @server, @user1, @room, "lockdown", ->
mock.verify()
done()
it "can start them again", (done) ->
mock = @sandbox.mock(@server).expects('unlockdown').once()
@user1.authority = auth.levels.OWNER
commands.executeCommand @server, @user1, @room, "lockdown", "off", ->
mock.verify()
done()
describe "whois", ->
beforeEach (done) ->
async.parallel([
alts.createAlt.bind(alts, @user1.name, 'alt1')
alts.createAlt.bind(alts, @user1.name, 'alt2')
], done)
it "returns an error if insufficient authority", (done) ->
mock = @sandbox.mock(@user1).expects('error').once()
commands.executeCommand @server, @user1, @room, "whois", @user1.name, =>
mock.verify()
done()
it "returns a list of alts and the main account", (done) ->
@user1.authority = auth.levels.MOD
spy = @sandbox.spy(@user1, 'announce')
commands.executeCommand @server, @user1, @room, "whois", @user1.name, =>
spy.callCount.should.equal(1)
spy.firstCall.args[2].should.containEql(@user1.name)
spy.firstCall.args[2].should.containEql("alt1")
spy.firstCall.args[2].should.containEql("alt2")
done()

View File

@@ -0,0 +1,54 @@
require '../../helpers'
{BattleServer} = require('../../../server/server')
{User} = require('../../../server/user')
{Conditions} = require '../../../shared/conditions'
{Factory} = require '../../factory'
should = require('should')
generateTeam = ->
[ Factory("Magikarp")
Factory("Gyarados")
Factory('Hitmonchan')
Factory("Celebi")
Factory("Blissey")
Factory("Alakazam") ]
describe 'Validations: Evasion Clause', ->
it "returns an error if a pokemon has an evasion move", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Umbreon", moves: [ "Double Team" ])
conditions = [ Conditions.EVASION_CLAUSE ]
server.validateTeam(team, format, conditions).should.not.be.empty
it "returns an error if a pokemon has a banned evasion ability", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Smeargle", ability: "Moody", moves: [ "Sketch" ])
conditions = [ Conditions.EVASION_CLAUSE ]
server.validateTeam(team, format, conditions).should.not.be.empty
it "returns no error if no pokemon has an evasion move", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Magikarp", moves: [ "Splash" ])
team[1] = Factory("Gyarados", moves: [ "Dragon Dance" ])
conditions = [ Conditions.EVASION_CLAUSE ]
server.validateTeam(team, format, conditions).should.be.empty
it "ignores invalid moves", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Magikarp", moves: [ "GHOSTFACE KILLAH" ])
team[1] = Factory("Gyarados", moves: [ "Dragon Dance" ])
conditions = [ Conditions.EVASION_CLAUSE ]
(-> server.validateTeam(team, format, conditions)).should.not.throw()

View File

@@ -0,0 +1,45 @@
require '../../helpers'
{BattleServer} = require('../../../server/server')
{User} = require('../../../server/user')
{Conditions} = require '../../../shared/conditions'
{Factory} = require '../../factory'
should = require('should')
generateTeam = ->
[ Factory("Magikarp")
Factory("Gyarados")
Factory('Hitmonchan')
Factory("Celebi")
Factory("Blissey")
Factory("Alakazam") ]
describe 'Validations: OHKO Clause', ->
it "returns an error if a pokemon has an OHKO move", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Lapras", moves: [ "Surf", "Sheer Cold" ])
conditions = [ Conditions.OHKO_CLAUSE ]
server.validateTeam(team, format, conditions).should.not.be.empty
it "returns no error if no pokemon has an evasion move", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Magikarp", moves: [ "Splash" ])
team[1] = Factory("Gyarados", moves: [ "Dragon Dance" ])
conditions = [ Conditions.OHKO_CLAUSE ]
server.validateTeam(team, format, conditions).should.be.empty
it "ignores invalid moves", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Magikarp", moves: [ "GHOSTFACE KILLAH" ])
team[1] = Factory("Gyarados", moves: [ "Dragon Dance" ])
conditions = [ Conditions.OHKO_CLAUSE ]
(-> server.validateTeam(team, format, conditions)).should.not.throw()

View File

@@ -0,0 +1,53 @@
require '../../helpers'
{BattleServer} = require('../../../server/server')
{User} = require('../../../server/user')
pbv = require('../../../shared/pokebattle_values')
{Factory} = require '../../factory'
should = require('should')
describe 'Validations: PBV 1000', ->
it "returns an error if the team is over 1000 PBV", ->
server = new BattleServer()
format = 'xy1000'
team = [ Factory("Arceus", move: "Recover")
Factory("Gyarados")
Factory('Hitmonchan')
Factory("Celebi")
Factory("Blissey")
Factory("Alakazam") ]
server.validateTeam(team, format).should.not.be.empty
it "returns an error if the team has under 6 pokemon", ->
server = new BattleServer()
format = 'xy1000'
team = [ Factory("Magikarp", moves: [ "Splash" ]) ]
server.validateTeam(team, format).should.not.be.empty
it "returns an error if the team has a pokemon that's over 1/3 the cap", ->
server = new BattleServer()
format = 'xy1000'
team = [ Factory("Magikarp")
Factory("Gyarados")
Factory('Hitmonchan')
Factory("Celebi")
Factory("Blissey")
Factory("Alakazam") ]
stub = @sandbox.stub(pbv, 'determinePBV', -> 335)
server.validateTeam(team, format).should.not.be.empty
it "returns no error if the team is under 1000 PBV", ->
server = new BattleServer()
format = 'xy1000'
team = [ Factory("Magikarp")
Factory("Gyarados")
Factory('Hitmonchan')
Factory("Celebi")
Factory("Blissey")
Factory("Alakazam") ]
server.validateTeam(team, format).should.be.empty

View File

@@ -0,0 +1,53 @@
require '../../helpers'
{BattleServer} = require('../../../server/server')
{User} = require('../../../server/user')
pbv = require('../../../shared/pokebattle_values')
{Factory} = require '../../factory'
should = require('should')
describe 'Validations: PBV 500', ->
it "returns an error if the team is over 500 PBV", ->
server = new BattleServer()
format = 'xy500'
team = [ Factory("Arceus", move: "Recover")
Factory("Gyarados")
Factory('Hitmonchan')
Factory("Celebi")
Factory("Blissey")
Factory("Alakazam") ]
server.validateTeam(team, format).should.not.be.empty
it "returns an error if the team has under 6 pokemon", ->
server = new BattleServer()
format = 'xy500'
team = [ Factory("Magikarp", moves: [ "Splash" ]) ]
server.validateTeam(team, format).should.not.be.empty
it "returns an error if the team has a pokemon that's over 1/3 the cap", ->
server = new BattleServer()
format = 'xy500'
team = [ Factory("Magikarp")
Factory("Gyarados")
Factory('Hitmonchan')
Factory("Celebi")
Factory("Blissey")
Factory("Alakazam") ]
stub = @sandbox.stub(pbv, 'determinePBV', -> 170)
server.validateTeam(team, format).should.not.be.empty
it "returns no error if the team is under 500 PBV", ->
server = new BattleServer()
format = 'xy500'
team = [ Factory("Magikarp")
Factory("Unown", moves: ['Hidden Power'])
Factory('Hitmonchan', moves: ['Mach Punch'])
Factory("Abra", moves: ['Psychic'])
Factory("Froakie", moves: ['Surf'])
Factory("Raticate", moves: ['Tackle']) ]
server.validateTeam(team, format).should.be.empty

View File

@@ -0,0 +1,32 @@
require '../../helpers'
{BattleServer} = require('../../../server/server')
{User} = require('../../../server/user')
{Conditions} = require '../../../shared/conditions'
{Factory} = require '../../factory'
should = require('should')
generateTeam = ->
[ Factory("Magikarp")
Factory("Gyarados")
Factory('Hitmonchan')
Factory("Celebi")
Factory("Blissey")
Factory("Alakazam") ]
describe 'Validations: Prankster + Swagger', ->
it "returns an error if the team has a Pokemon with Prankster + Swagger", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Sableye", ability: "Prankster", moves: [ "Swagger" ])
conditions = [ Conditions.PRANKSTER_SWAGGER_CLAUSE ]
server.validateTeam(team, format, conditions).should.not.be.empty
it "returns no error if the team has no Prankster + Swagger Pokemon", ->
server = new BattleServer()
format = 'xy1000'
conditions = [ Conditions.PRANKSTER_SWAGGER_CLAUSE ]
server.validateTeam(generateTeam(), format, conditions).should.be.empty

View File

@@ -0,0 +1,95 @@
require '../../helpers'
shared = require '../../shared'
{Conditions} = require '../../../shared/conditions'
{Attachment, Status} = require '../../../server/bw/attachment'
{Protocol} = require '../../../shared/protocol'
{Factory} = require '../../factory'
describe "Sleep Clause", ->
it "prevents Sleep if the opponent was already slept by this team", ->
conditions = [ Conditions.SLEEP_CLAUSE ]
team1 = [ Factory("Magikarp"), Factory("Magikarp") ]
team2 = [ Factory("Magikarp"), Factory("Magikarp") ]
shared.create.call(this, {conditions, team1, team2})
spore = @battle.getMove("Spore")
@battle.performMove(@p1, spore)
@battle.performSwitch(@p2, 1)
mock = @sandbox.mock(spore).expects('fail').once()
@battle.performMove(@p1, spore)
mock.verify()
@team2.at(0).has(Status.Sleep).should.be.false
@team2.at(1).has(Status.Sleep).should.be.true
it "prevents Sleep from Yawn", ->
conditions = [ Conditions.SLEEP_CLAUSE ]
team1 = [ Factory("Magikarp"), Factory("Magikarp") ]
team2 = [ Factory("Magikarp"), Factory("Magikarp") ]
shared.create.call(this, {conditions, team1, team2})
yawn = @battle.getMove("Yawn")
@battle.performMove(@p1, yawn)
@battle.endTurn()
@battle.endTurn()
@battle.performSwitch(@p2, 1)
@battle.performMove(@p1, yawn)
@battle.endTurn()
@battle.endTurn()
@team2.at(0).has(Status.Sleep).should.be.false
@team2.at(1).has(Status.Sleep).should.be.true
it "doesn't prevent other statuses", ->
conditions = [ Conditions.SLEEP_CLAUSE ]
team1 = [ Factory("Magikarp"), Factory("Magikarp") ]
team2 = [ Factory("Magikarp"), Factory("Magikarp") ]
shared.create.call(this, {conditions, team1, team2})
thunderWave = @battle.getMove("Thunder Wave")
@battle.performMove(@p1, thunderWave)
@battle.performSwitch(@p2, 1)
mock = @sandbox.mock(thunderWave).expects('fail').never()
@battle.performMove(@p1, thunderWave)
mock.verify()
@team2.at(0).has(Status.Paralyze).should.be.true
@team2.at(1).has(Status.Paralyze).should.be.true
it "doesn't prevent Sleep if the opponent was slept, but not by this team", ->
conditions = [ Conditions.SLEEP_CLAUSE ]
team1 = [ Factory("Magikarp"), Factory("Magikarp") ]
team2 = [ Factory("Magikarp"), Factory("Magikarp") ]
shared.create.call(this, {conditions, team1, team2})
@team2.at(1).attach(Status.Sleep)
spore = @battle.getMove("Spore")
mock = @sandbox.mock(spore).expects('fail').never()
@battle.performMove(@p1, spore)
mock.verify()
@team2.at(0).has(Status.Sleep).should.be.true
@team2.at(1).has(Status.Sleep).should.be.true
it "doesn't prevent Sleep if the opponent was slept, but fainted", ->
conditions = [ Conditions.SLEEP_CLAUSE ]
team1 = [ Factory("Magikarp"), Factory("Magikarp") ]
team2 = [ Factory("Magikarp"), Factory("Magikarp") ]
shared.create.call(this, {conditions, team1, team2})
spore = @battle.getMove("Spore")
@battle.performMove(@p1, spore)
@battle.performSwitch(@p2, 1)
@p2.faint()
mock = @sandbox.mock(spore).expects('fail').never()
@battle.performMove(@p1, spore)
mock.verify()
@team2.at(0).has(Status.Sleep).should.be.true

View File

@@ -0,0 +1,34 @@
require '../../helpers'
{BattleServer} = require('../../../server/server')
{User} = require('../../../server/user')
{Conditions} = require '../../../shared/conditions'
{Factory} = require '../../factory'
should = require('should')
generateTeam = ->
[ Factory("Magikarp")
Factory("Gyarados")
Factory('Hitmonchan')
Factory("Celebi")
Factory("Blissey")
Factory("Alakazam") ]
describe 'Validations: Species Clause', ->
it "returns an error if the team has more than one of the same species", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Rotom", forme: "wash")
team[1] = Factory("Rotom", forme: "heat")
conditions = [ Conditions.SPECIES_CLAUSE ]
server.validateTeam(team, format, conditions).should.not.be.empty
it "returns no error if the team shares no species", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
conditions = [ Conditions.SPECIES_CLAUSE ]
server.validateTeam(team, format, conditions).should.be.empty

View File

@@ -0,0 +1,136 @@
require '../../helpers'
shared = require '../../shared'
{Conditions} = require '../../../shared/conditions'
{Protocol} = require '../../../shared/protocol'
{Factory} = require '../../factory'
describe "Team preview", ->
it "starts the battle by passing team info and requesting team order", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = [ Factory("Magikarp") ]
team2 = [ Factory("Magikarp") ]
shared.build(this, {conditions, team1, team2})
mock = @sandbox.mock(@battle).expects('startBattle').never()
spy = @sandbox.spy(@battle, 'tell')
@controller.beginBattle()
mock.verify()
spy.calledWith(Protocol.TEAM_PREVIEW).should.be.true
it "waits until all players have arranged their teams before starting", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = [ Factory("Magikarp") ]
team2 = [ Factory("Magikarp") ]
shared.build(this, {conditions, team1, team2})
mock = @sandbox.mock(@battle).expects('startBattle').never()
@controller.beginBattle()
@controller.arrangeTeam(@id1, [ 0 ])
mock.verify()
@battle.startBattle.restore()
mock = @sandbox.mock(@battle).expects('startBattle').once()
@controller.arrangeTeam(@id2, [ 0 ])
mock.verify()
it "rejects team arrangements that aren't arrays", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = [ Factory("Magikarp") ]
team2 = [ Factory("Magikarp") ]
shared.create.call(this, {conditions, team1, team2})
arrangement = true
@controller.arrangeTeam(@id1, arrangement).should.be.false
it "accepts arrays of integers (arrangements) matching team length", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = [ Factory("Magikarp") ]
team2 = [ Factory("Magikarp") ]
shared.create.call(this, {conditions, team1, team2})
arrangement = [ 0 ]
@controller.arrangeTeam(@id1, arrangement).should.be.true
it "rejects team arrangements that are smaller than the team length", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = [ Factory("Magikarp") ]
team2 = [ Factory("Magikarp") ]
shared.create.call(this, {conditions, team1, team2})
arrangement = []
@controller.arrangeTeam(@id1, arrangement).should.be.false
it "rejects team arrangements that are larger than the team length", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = [ Factory("Magikarp") ]
team2 = [ Factory("Magikarp") ]
shared.create.call(this, {conditions, team1, team2})
arrangement = [ 0, 1 ]
@controller.arrangeTeam(@id1, arrangement).should.be.false
it "rejects team arrangements containing negative indices", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = [ Factory("Magikarp") ]
team2 = [ Factory("Magikarp") ]
shared.create.call(this, {conditions, team1, team2})
arrangement = [ -1 ]
@controller.arrangeTeam(@id1, arrangement).should.be.false
it "rejects team arrangements containing indices out of bounds", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = [ Factory("Magikarp") ]
team2 = [ Factory("Magikarp") ]
shared.create.call(this, {conditions, team1, team2})
arrangement = [ 1 ]
@controller.arrangeTeam(@id1, arrangement).should.be.false
it "rejects team arrangements containing non-unique indices", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = (Factory("Magikarp") for x in [0..1])
shared.create.call(this, {conditions, team1})
arrangement = [ 1, 1 ]
@controller.arrangeTeam(@id1, arrangement).should.be.false
it "rejects team arrangements that have some non-numbers", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = (Factory("Magikarp") for x in [0..1])
shared.create.call(this, {conditions, team1})
arrangement = [ 1, "a" ]
@controller.arrangeTeam(@id1, arrangement).should.be.false
it "rejects team arrangements that don't point to a correct index", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = (Factory("Magikarp") for x in [0..1])
shared.create.call(this, {conditions, team1})
arrangement = [ 1, .5 ]
@controller.arrangeTeam(@id1, arrangement).should.be.false
it "rejects team arrangements if the battle has already begun", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = (Factory("Magikarp") for x in [0..1])
shared.create.call(this, {conditions, team1})
arrangement = [ 1, 0 ]
@controller.arrangeTeam(@id1, arrangement)
@controller.arrangeTeam(@id2, arrangement)
@controller.arrangeTeam(@id1, arrangement).should.be.false
it "rearranges team when given a valid array of indices", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = [ Factory("Magikarp"), Factory("Gyarados"), Factory("Celebi") ]
team2 = [ Factory("Magikarp"), Factory("Gyarados"), Factory("Celebi") ]
shared.create.call(this, {conditions, team1, team2})
@controller.arrangeTeam(@id1, [ 0, 2, 1 ])
@controller.arrangeTeam(@id2, [ 2, 0, 1 ])
@team1.at(0).species.should.equal("Magikarp")
@team1.at(1).species.should.equal("Celebi")
@team1.at(2).species.should.equal("Gyarados")
@team2.at(0).species.should.equal("Celebi")
@team2.at(1).species.should.equal("Magikarp")
@team2.at(2).species.should.equal("Gyarados")
it "is isomorphic", ->
conditions = [ Conditions.TEAM_PREVIEW ]
team1 = [ Factory("Magikarp"), Factory("Gyarados"), Factory("Celebi") ]
team2 = [ Factory("Magikarp"), Factory("Gyarados"), Factory("Celebi") ]
arrangedTeamNames = [ "Celebi", "Magikarp", "Gyarados" ]
shared.create.call(this, {conditions, team1, team2})
@controller.arrangeTeam(@id1, [ 2, 0, 1 ])
@controller.arrangeTeam(@id1, [ 2, 0, 1 ])
@controller.arrangeTeam(@id2, [ 2, 0, 1 ])
@team1.pokemon.map((p) -> p.species).should.eql(arrangedTeamNames)

View File

@@ -0,0 +1,190 @@
require '../../helpers'
{_} = require 'underscore'
shared = require '../../shared'
{Conditions} = require '../../../shared/conditions'
{Protocol} = require '../../../shared/protocol'
describe "Battle timer", ->
describe "without team preview", ->
beforeEach ->
@clock.tick(10000)
shared.create.call this,
conditions: [ Conditions.TIMED_BATTLE ]
@battle.TIMER_CAP = Infinity
it "starts a timer that ends the battle in 5 minutes", ->
@battle.isOver().should.be.false
delta = 100
@clock.tick(@battle.DEFAULT_TIMER - delta)
@battle.isOver().should.be.false
@clock.tick(delta)
@battle.isOver().should.be.true
it "declares a timer win for the player that didn't run out of time", ->
@battle.playerTimes[@id1] += 1000
spy = @sandbox.spy(@battle, 'tell')
@clock.tick(@battle.DEFAULT_TIMER)
index1 = @battle.getPlayerIndex(@id1)
spy.calledWith(Protocol.TIMER_WIN, index1).should.be.true
it "increases time remaining by 20 seconds for each player each turn", ->
@battle.timeRemainingFor(@id1).should.equal(@battle.DEFAULT_TIMER)
@battle.beginTurn()
delta = @battle.TIMER_PER_TURN_INCREASE
@battle.timeRemainingFor(@id1).should.equal(@battle.DEFAULT_TIMER + delta)
@battle.timeRemainingFor(@id2).should.equal(@battle.DEFAULT_TIMER + delta)
@battle.beginTurn()
delta *= 2
@battle.timeRemainingFor(@id1).should.equal(@battle.DEFAULT_TIMER + delta)
@battle.timeRemainingFor(@id2).should.equal(@battle.DEFAULT_TIMER + delta)
it "recalculates timer after increasing time remaining", ->
@battle.beginTurn()
delta = @battle.TIMER_PER_TURN_INCREASE
spy = @sandbox.spy(@battle, 'tell')
@clock.tick(@battle.DEFAULT_TIMER + delta / 2)
spy.calledWith(Protocol.TIMER_WIN).should.be.false
@clock.tick(delta / 2)
spy.calledWith(Protocol.TIMER_WIN).should.be.true
it "stops timer for players who have moved", ->
delta = 5000
@battle.timeRemainingFor(@id1).should.equal(@battle.DEFAULT_TIMER)
@battle.timeRemainingFor(@id2).should.equal(@battle.DEFAULT_TIMER)
@clock.tick(delta)
@battle.timeRemainingFor(@id1).should.equal(@battle.DEFAULT_TIMER - delta)
@battle.timeRemainingFor(@id2).should.equal(@battle.DEFAULT_TIMER - delta)
@battle.recordMove(@id1, @battle.getMove("Splash"))
@clock.tick(delta)
@battle.timeRemainingFor(@id1).should.equal(@battle.DEFAULT_TIMER - delta)
@battle.timeRemainingFor(@id2).should.equal(@battle.DEFAULT_TIMER - 2 * delta)
it "recalculates the timer after a player chooses an action", ->
delta = 4000
# give player 2 more time
@battle.playerTimes[@id2] += delta
@battle.recordMove(@id1, @battle.getMove("Splash"))
spy = @sandbox.spy(@battle, 'tell')
@clock.tick(@battle.DEFAULT_TIMER)
spy.calledWith(Protocol.TIMER_WIN).should.be.false
@clock.tick(delta)
spy.calledWith(Protocol.TIMER_WIN).should.be.true
it "grants time if player selected a move before the battle continued", ->
@clock.tick(2500)
spy = @sandbox.spy(@battle, 'requestActions')
@controller.makeMove(@id1, "Splash")
# 5 seconds after the player moves, the battle progresses
@clock.tick(5000)
@controller.makeMove(@id2, "Splash")
(@battle.DEFAULT_TIMER + @battle.TIMER_PER_TURN_INCREASE -
@battle.timeRemainingFor(@id1)).should.equal(2500)
# Turn has progressed. Make another move and check the time.
@clock.tick(2500)
@controller.makeMove(@id1, "Splash")
@clock.tick(5000)
@controller.makeMove(@id2, "Splash")
(@battle.DEFAULT_TIMER + 2 * @battle.TIMER_PER_TURN_INCREASE -
@battle.timeRemainingFor(@id1)).should.equal(5000)
it "ends battle if canceling after which they'd lose to timer", ->
# So the second player won't trigger the end condition.
@battle.playerTimes[@id2] += 4000
@clock.tick(2500)
@controller.makeMove(@id1, "Splash")
@clock.tick(@battle.DEFAULT_TIMER)
mock = @sandbox.mock(@battle).expects('timerWin').once()
@controller.undoCompletedRequest(@id1)
@battle.timeRemainingFor(@id1).should.equal(-2500)
mock.verify()
it "sends timer updates when battle enters a new turn", ->
@battle.recordMove(@id1, @battle.getMove("Splash"))
@battle.recordMove(@id2, @battle.getMove("Splash"))
spy = @sandbox.spy(@battle, 'send')
@battle.continueTurn()
spy.calledWith('updateTimers').should.be.false
@battle.beginTurn()
spy.calledWith('updateTimers').should.be.true
it "gets cleared if the battle ends prematurely", ->
@battle.endBattle()
mock = @sandbox.mock(@battle).expects('timerWin').never()
@clock.tick(@battle.DEFAULT_TIMER)
mock.verify()
it "has a cap every time a new turn begins", ->
@battle.TIMER_CAP = @battle.DEFAULT_TIMER
@clock.tick(@battle.TIMER_PER_TURN_INCREASE >> 1)
@battle.beginTurn()
@battle.timeRemainingFor(@id1).should.equal(@battle.TIMER_CAP)
it "has a cap every time a player gains time after a new action request", ->
@battle.TIMER_CAP = @battle.DEFAULT_TIMER
@battle.recordMove(@id1, @battle.getMove("U-turn"))
@clock.tick(@battle.TIMER_PER_TURN_INCREASE >> 1)
@battle.continueTurn()
@battle.timeRemainingFor(@id1).should.equal(@battle.TIMER_CAP)
it "recalculates timer every time an action is requested", ->
# Player 2 has more time.
delta = 4000
@battle.playerTimes[@id2] += delta
@battle.recordMove(@id2, @battle.getMove("U-turn"))
@battle.recordMove(@id1, @battle.getMove("Splash"))
@battle.continueTurn()
# Action requested due to U-turn
spy = @sandbox.spy(@battle, 'tell')
@clock.tick(@battle.DEFAULT_TIMER)
spy.calledWith(Protocol.TIMER_WIN).should.be.false
@clock.tick(delta)
spy.calledWith(Protocol.TIMER_WIN).should.be.true
describe "with team preview", ->
beforeEach ->
@clock.tick(10000)
shared.create.call this,
conditions: [ Conditions.TIMED_BATTLE, Conditions.TEAM_PREVIEW ]
it "starts a timer that auto-starts the battle after 1.5 mins", ->
@battle.arranging.should.be.true
spy = @sandbox.spy(@battle, 'startBattle')
@clock.tick(@battle.TEAM_PREVIEW_TIMER)
spy.calledOnce.should.be.true
@battle.arranging.should.be.false
it "arranges teams of those who already submitted arrangements", ->
@battle.arranging.should.be.true
arrangement = [0...@team1.size()]
arrangement.reverse()
pokemon = _.clone(@team1.pokemon)
pokemon.reverse()
@controller.arrangeTeam(@id1, arrangement)
@clock.tick(@battle.TEAM_PREVIEW_TIMER)
@team1.pokemon.should.eql(pokemon)

View File

@@ -0,0 +1,70 @@
require '../../helpers'
{BattleServer} = require('../../../server/server')
{User} = require('../../../server/user')
{Conditions} = require '../../../shared/conditions'
{Factory} = require '../../factory'
should = require('should')
generateTeam = ->
[ Factory("Magikarp")
Factory("Gyarados")
Factory('Hitmonchan')
Factory("Celebi")
Factory("Blissey")
Factory("Alakazam") ]
describe 'Validations: Unreleased Ban', ->
it "returns an error if a pokemon is unreleased", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Hoopa", item: "Leftovers", moves: [ "Moonblast" ])
conditions = [ Conditions.UNRELEASED_BAN ]
server.validateTeam(team, format, conditions).should.not.be.empty
it "returns an error if a pokemon has an unreleased item", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Latias", item: "Soul Dew", moves: [ "Psychic" ])
conditions = [ Conditions.UNRELEASED_BAN ]
server.validateTeam(team, format, conditions).should.not.be.empty
it "returns an error if a pokemon has an unreleased ability", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Suicune", ability: "Water Absorb", moves: [ "Surf" ])
conditions = [ Conditions.UNRELEASED_BAN ]
server.validateTeam(team, format, conditions).should.not.be.empty
it "returns no error if all pokemon have nothing unreleased", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Latias", item: "Leftovers", moves: [ "Psychic" ])
conditions = [ Conditions.UNRELEASED_BAN ]
server.validateTeam(team, format, conditions).should.be.empty
it "returns no error if a pokemon has a dream world ability that is the same as a regular ability", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Metapod", ability: "Shed Skin", moves: [ "Tackle" ])
conditions = [ Conditions.UNRELEASED_BAN ]
server.validateTeam(team, format, conditions).should.be.empty
it "ignores invalid pokemon", ->
server = new BattleServer()
format = 'xy1000'
team = generateTeam()
team[0] = {species: "I'm a totally fake Pokemon."}
conditions = [ Conditions.UNRELEASED_BAN ]
(-> server.validateTeam(team, format, conditions)).should.not.throw()

View File

@@ -0,0 +1,76 @@
require '../helpers'
{BattleServer} = require('../../server/server')
{Room} = require '../../server/rooms'
{User} = require '../../server/user'
describe "A server room:", ->
beforeEach ->
@server = new BattleServer()
@spark1 = @stubSpark()
@spark2 = @stubSpark()
@user1 = @server.findOrCreateUser(id: 1, name: 'aaaa', @spark1)
@user2 = @server.findOrCreateUser(id: 2, name: 'bbbb', @spark2)
@room = new Room('derp')
@room.add(@spark1)
@room.add(@spark2)
describe "#message", ->
it "sends a message to all users in that room", ->
mock1 = @sandbox.mock(@user1)
mock1.expects('send').withArgs("rawMessage", @room.name, "hello").once()
mock2 = @sandbox.mock(@user2)
mock2.expects('send').withArgs("rawMessage", @room.name, "hello").once()
@room.message("hello")
mock1.verify()
mock2.verify()
describe "#userMessage", ->
it "sends a message to all users in that room", ->
mock1 = @sandbox.mock(@user1).expects('send')
mock1.withArgs("userMessage", @room.name, @user1.name, "hello").once()
mock2 = @sandbox.mock(@user2).expects('send').once()
mock2.withArgs("userMessage", @room.name, @user1.name, "hello").once()
@room.userMessage(@user1, "hello")
mock1.verify()
mock2.verify()
describe "#setTopic", ->
it "sends a topic message to all users in that room", ->
mock1 = @sandbox.mock(@user1).expects('send')
mock1.withArgs("topic", "a test").once()
mock2 = @sandbox.mock(@user2).expects('send').once()
mock2.withArgs("topic", "a test").once()
@room.setTopic("a test")
mock1.verify()
mock2.verify()
describe "#userJSON", ->
it "returns an array containing the JSON of all users", ->
@room.toJSON().should.eql([ @user1.toJSON(), @user2.toJSON() ])
describe "#send", ->
it "broadcasts to every single user, including ones on the same account", ->
mock1 = @sandbox.mock(@user1).expects('send').withArgs('hello').once()
mock2 = @sandbox.mock(@user2).expects('send').withArgs('hello').once()
@room.send("hello")
mock1.verify()
mock2.verify()
it "stops broadcasting to sparks that leave", ->
spy1 = @sandbox.spy(@user1, 'send')
spy2 = @sandbox.spy(@user2, 'send')
@room.remove(@spark1)
@room.send("hello")
spy1.withArgs('hello').called.should.be.false
spy2.withArgs('hello').calledOnce.should.be.true
@room.remove(@spark2)
@room.send("hello")
spy1.withArgs('hello').called.should.be.false
spy2.withArgs('hello').calledOnce.should.be.true

View File

@@ -0,0 +1,57 @@
sinon = require('sinon')
async = require('async')
ratings = require('../../server/ratings')
scheduler = require('../../server/schedule')
require '../helpers'
describe 'Scheduler', ->
beforeEach ->
date = new Date()
date.setHours(23)
date.setMinutes(59)
date.setSeconds(59)
# Get rid of existing clock
@clock.restore()
@clock = sinon.useFakeTimers(date.getTime())
@jobs = scheduler.createScheduler()
@callbacks = @jobs.map (job) ->
(callback) -> job.on('finished', callback)
it 'decays elo ratings', (done) ->
players = ['guy', 'lady']
oldRatings = [ 2000, 4000 ]
oldGuyRating = 2000
oldLadyRating = 4000
ratings.setRatings players, oldRatings, =>
async.parallel @callbacks, =>
ratings.getRatings players, (err, newRatings) =>
throw err if err
newRatings.should.eql(oldRatings.map((r) -> r - ratings.DECAY_AMOUNT))
done()
@clock.tick(2000)
it 'does not decay the minimum rating possible', (done) ->
minRating = ratings.algorithm.createPlayer().rating
ratings.setRating 'sonic', minRating, =>
async.parallel @callbacks, =>
ratings.getRating 'sonic', (err, rating) =>
throw err if err
rating.should.equal(minRating)
done()
@clock.tick(2000)
it 'does not decay active players', (done) ->
players = ['guy', 'lady']
oldRatings = [ 2000, 4000 ]
oldGuyRating = 2000
oldLadyRating = 4000
ratings.setActive players, =>
ratings.setRatings players, oldRatings, =>
async.parallel @callbacks, =>
ratings.getRatings players, (err, newRatings) =>
throw err if err
newRatings.should.eql(oldRatings)
done()
@clock.tick(2000)

824
test/server_spec.coffee Normal file
View File

@@ -0,0 +1,824 @@
require './helpers'
{BattleServer} = require('../server/server')
{Conditions, DEFAULT_FORMAT} = require '../shared/conditions'
{Protocol} = require '../shared/protocol'
{Factory} = require './factory'
alts = require('../server/alts')
should = require('should')
generateTeam = ->
[ Factory("Magikarp")
Factory("Gyarados")
Factory('Hitmonchan')
Factory("Celebi")
Factory("Blissey")
Factory("Alakazam") ]
describe 'BattleServer', ->
it 'can create a new battle', ->
server = new BattleServer()
battleId = server.createBattle()
server.battles.should.have.ownProperty battleId
it "sends the 'spectateBattle' event for each matched player", (done) ->
server = new BattleServer()
players = []
players.push server.findOrCreateUser(id: 1, name: 'abc', @stubSpark())
players.push server.findOrCreateUser(id: 2, name: 'def', @stubSpark())
spies = []
for player in players
spy = @sandbox.spy(player.sparks[0], 'send')
spies.push(spy)
for player in players
server.queuePlayer(player.name, generateTeam()).should.be.empty
server.beginBattles (err, ids) ->
throw new Error(err.message) if err
return if ids.length == 0
for spy in spies
spy.calledWith('spectateBattle').should.be.true
done()
describe "#queuePlayer", ->
it "queues players", ->
server = new BattleServer()
derp = server.findOrCreateUser(id: 1, name: 'derp', @stubSpark())
server.queuePlayer(derp.name, generateTeam()).should.be.empty
server.queues[DEFAULT_FORMAT].size().should.equal(1)
it "does not queue players already queued", ->
server = new BattleServer()
derp = server.findOrCreateUser(id: 1, name: 'derp', @stubSpark())
server.queuePlayer(derp.name, generateTeam()).should.be.empty
server.queuePlayer(derp.name, generateTeam()).should.be.empty
server.queues[DEFAULT_FORMAT].size().should.equal(1)
describe "#getOngoingBattles", ->
it "returns one object for each queued battle", (done) ->
server = new BattleServer()
nBattles = 3
for i in [1..nBattles]
first = 2 * i
second = (2 * i) + 1
server.findOrCreateUser(id: first, name: String(first), @stubSpark())
server.findOrCreateUser(id: second, name: String(second), @stubSpark())
server.queuePlayer(String(first), generateTeam()).should.be.empty
server.queuePlayer(String(second), generateTeam()).should.be.empty
server.beginBattles ->
server.getOngoingBattles().should.have.length(nBattles)
done()
describe "#registerChallenge", ->
it "registers a challenge to a player", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
server.registerChallenge(user, other.name, format, team, conditions)
server.challenges.should.have.property(user.name)
server.challenges[user.name].should.have.property(other.name)
challenge = server.challenges[user.name][other.name]
challenge.should.have.property("team")
challenge.should.have.property("format")
challenge.should.have.property("conditions")
challenge.team.should.equal(team)
challenge.format.should.equal(format)
challenge.conditions.should.equal(conditions)
it "does not override old challenges", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
diffFormat = 'xy500'
diffTeam = generateTeam()
diffTeam[0] = Factory("Celebi")
server.registerChallenge(user, other.name, format, team)
server.registerChallenge(user, other.name, diffFormat, diffTeam)
challenge = server.challenges[user.name][other.name]
challenge.format.should.equal(format)
challenge.team.should.equal(team)
it "returns an error if the team is invalid", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
format = 'xy1000'
mock = @sandbox.mock(user).expects('error').once()
team = []
server.registerChallenge(user, other.name, format, team)
mock.verify()
it "returns an error if the team is over 1000 PBV with 1000 PBV clause", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
format = 'xy1000'
team = generateTeam()
team[0] = Factory("Arceus", moves: [ "Recover" ])
conditions = [ Conditions.PBV_1000 ]
mock = @sandbox.mock(user).expects('error').once()
server.registerChallenge(user, other.name, format, team, conditions)
mock.verify()
it "returns an error on a rated challenge", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
format = 'xy1000'
team = generateTeam()
conditions = [ Conditions.RATED_BATTLE ]
mock = @sandbox.mock(user).expects('error').once()
server.registerChallenge(user, other.name, format, team, conditions)
mock.verify()
it "returns an error if the format is invalid", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = "UNRELEASED INFERNO RED AND WEIRD YELLOWISH GREEN"
conditions = []
mock = @sandbox.mock(user).expects('error').once()
server.registerChallenge(user, other.name, format, team, conditions)
mock.verify()
it "returns an error if the challengee is offline", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
mock = @sandbox.mock(user).expects('error').once()
server.registerChallenge(user, "husk", format, team, conditions)
mock.verify()
it "returns an error if you challenge yourself", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
mock = @sandbox.mock(user).expects('error').once()
server.registerChallenge(user, user.name, format, team, conditions)
mock.verify()
it "sends an error if a challenge already exists for that pair", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
server.registerChallenge(user, other.name, format, team, conditions)
mock = @sandbox.mock(other).expects('error').once()
server.registerChallenge(other, user.name, format, team, conditions)
mock.verify()
it "sends a 'challenge' event to the challengee", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
otherSpy = @sandbox.spy(other, 'send')
otherSpy.withArgs('challenge', user.name, format, conditions)
server.registerChallenge(user, other.name, format, team, conditions)
otherSpy.withArgs('challenge', user.name, format, conditions)
.calledOnce.should.be.true
it "returns an error if the server is locked down", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
format = 'xy1000'
team = generateTeam()
conditions = [ Conditions.PBV_1000 ]
mock = @sandbox.mock(user).expects('error').once()
server.lockdown()
server.registerChallenge(user, other.name, format, team, conditions)
mock.verify()
describe "#cancelChallenge", ->
it "sends a 'cancelChallenge' to both the challengee and challenger", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
userSpy = @sandbox.spy(user, 'send')
userSpy.withArgs('cancelChallenge', other.name)
otherSpy = @sandbox.spy(other, 'send')
otherSpy.withArgs('cancelChallenge', user.name)
server.registerChallenge(user, other.name, format, team, conditions)
server.cancelChallenge(user, other.name)
userSpy.withArgs('cancelChallenge', other.name).calledOnce.should.be.true
otherSpy.withArgs('cancelChallenge', user.name).calledOnce.should.be.true
it "removes the challenge from the internal hash", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
server.registerChallenge(user, other.name, format, team, conditions)
should.exist server.challenges[user.name][other.name]
server.cancelChallenge(user, other.name)
should.not.exist server.challenges[user.name][other.name]
describe "#rejectChallenge", ->
it "sends a 'rejectChallenge' to the challengee and challenger", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
server.registerChallenge(user, other.name, format, team, conditions)
userSpy = @sandbox.spy(user, 'send')
userSpy.withArgs('rejectChallenge', other.name)
otherSpy = @sandbox.spy(other, 'send')
otherSpy.withArgs('rejectChallenge', user.name)
server.rejectChallenge(other, user.name)
userSpy.withArgs('rejectChallenge', other.name).calledOnce.should.be.true
otherSpy.withArgs('rejectChallenge', user.name).calledOnce.should.be.true
it "removes the challenge from the internal hash", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
server.registerChallenge(user, other.name, format, team, conditions)
should.exist server.challenges[user.name][other.name]
server.rejectChallenge(other, user.name)
should.not.exist server.challenges[user.name][other.name]
it "returns an error if no such challenge exists", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
server.registerChallenge(user, other.name, format, team, conditions)
mock = @sandbox.mock(other).expects('error').once()
server.rejectChallenge(other, "bogus dude")
mock.verify()
describe "#acceptChallenge", ->
initServer = ->
@server = new BattleServer()
@user = @server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
@other = @server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
it "creates a battle with the teams given by both players", ->
initServer.call(this)
team = generateTeam()
format = 'xy1000'
conditions = []
@server.registerChallenge(@user, @other.name, format, team, conditions)
mock = @sandbox.mock(@server).expects('createBattle').once()
@server.acceptChallenge(@other, @user.name, team)
mock.verify()
it "returns an error to a player if their team is invalid", ->
initServer.call(this)
team = generateTeam()
format = 'xy1000'
conditions = []
@server.registerChallenge(@user, @other.name, format, team, conditions)
mock = @sandbox.mock(@other).expects('error').once()
@server.acceptChallenge(@other, @user.name, [])
mock.verify()
it "returns an error to a player if their team violates clauses", ->
initServer.call(this)
team = generateTeam()
acceptTeam = generateTeam()
acceptTeam[0] = Factory("Mewtwo", moves: [ "Psychic" ])
format = 'xy1000'
conditions = [ Conditions.PBV_1000 ]
@server.registerChallenge(@user, @other.name, format, team, conditions)
mock = @sandbox.mock(@other).expects('error').once()
@server.acceptChallenge(@other, @user.name, acceptTeam)
mock.verify()
it "removes the challenge from the internal hash", ->
initServer.call(this)
team = generateTeam()
format = 'xy1000'
conditions = []
@server.registerChallenge(@user, @other.name, format, team, conditions)
should.exist @server.challenges[@user.name][@other.name]
@server.acceptChallenge(@other, @user.name, team)
should.not.exist @server.challenges[@user.name][@other.name]
it "sends a 'challengeSuccess' event to both players", ->
initServer.call(this)
team = generateTeam()
format = 'xy1000'
conditions = []
userSpy = @sandbox.spy(@user, 'send')
userSpy.withArgs('challengeSuccess', @other.name)
otherSpy = @sandbox.spy(@other, 'send')
otherSpy.withArgs('challengeSuccess', @user.name)
@server.registerChallenge(@user, @other.name, format, team, conditions)
@server.acceptChallenge(@other, @user.name, team)
userSpy.withArgs('challengeSuccess', @other.name).calledOnce.should.be.true
otherSpy.withArgs('challengeSuccess', @user.name).calledOnce.should.be.true
it "returns an error if no such challenge exists", ->
initServer.call(this)
team = generateTeam()
format = 'xy1000'
conditions = []
@server.registerChallenge(@user, @other.name, format, team, conditions)
mock = @sandbox.mock(@other).expects('error').once()
@server.acceptChallenge(@other, "bogus dude", team)
mock.verify()
it "overrides the user's name with the alt name in battle", ->
initServer.call(this)
team = generateTeam()
format = 'xy1000'
conditions = []
@server.registerChallenge(@user, @other.name, format, team, conditions, "Bruce Wayne")
battleId = @server.acceptChallenge(@other, @user.name, team, "Jason Todd")
battle = @server.findBattle(battleId)
battle.battle.playerNames.should.eql ["Bruce Wayne", "Jason Todd"]
it "sets the rating key to be the unique alt id if there is an alt", ->
initServer.call(this)
team = generateTeam()
format = 'xy1000'
conditions = []
@server.registerChallenge(@user, @other.name, format, team, conditions, "Bruce Wayne")
battleId = @server.acceptChallenge(@other, @user.name, team, "Jason Todd")
battle = @server.findBattle(battleId)
battle.battle.getPlayer("Batman").ratingKey.should.equal alts.uniqueId(@user.name, "Bruce Wayne")
battle.battle.getPlayer("Robin").ratingKey.should.equal alts.uniqueId(@other.name, "Jason Todd")
describe "#leave", ->
it "removes challenges by that player", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", spark = @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
server.registerChallenge(user, other.name, format, team, conditions)
should.exist server.challenges[user.name]
should.exist server.challenges[user.name][other.name]
server.leave(spark)
should.not.exist server.challenges[user.name]
it "removes challenges to that player", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", spark = @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
server.registerChallenge(other, user.name, format, team, conditions)
should.exist server.challenges[other.name]
should.exist server.challenges[other.name][user.name]
server.leave(spark)
should.exist server.challenges[other.name]
should.not.exist server.challenges[other.name][user.name]
describe "#lockdown", ->
it "cancels all challenges", ->
server = new BattleServer()
user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
team = generateTeam()
format = 'xy1000'
conditions = []
server.registerChallenge(other, user.name, format, team, conditions)
should.exist server.challenges[other.name]
should.exist server.challenges[other.name][user.name]
server.lockdown()
should.not.exist server.challenges[other.name]
describe "#validateTeam", ->
it "returns non-empty if given anything that's not an array", ->
server = new BattleServer()
server.validateTeam().should.not.be.empty
it "returns empty if given a non-empty array containing Pokemon", ->
server = new BattleServer()
server.validateTeam(generateTeam()).should.be.empty
it "returns non-empty if given an empty array", ->
server = new BattleServer()
server.validateTeam([]).should.not.be.empty
it "returns non-empty if a team member is not a valid Pokemon", ->
server = new BattleServer()
invalidPokemon = {}
server.validateTeam([ invalidPokemon ]).should.not.be.empty
it "returns non-empty if a team member has a fake species name", ->
server = new BattleServer()
team = generateTeam()
team[0] = {species: "NOTREALMON"}
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a team member has no moveset", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Hitmonchan", moves: null)
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a team member has an empty moveset", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Hitmonchan", moves: [])
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a team member has a bogus moveset", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Hitmonchan", moves: true)
server.validateTeam(team).should.not.be.empty
team[0] = Factory("Hitmonchan", moves: ["Super Powerful Punch"])
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a team member has an illegal moveset", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Raichu", moves: [ "Volt Tackle", "Encore" ])
server.validateTeam(team, 'bw').should.not.be.empty
# TODO: 4 is a magic constant
it "returns non-empty if a pokemon has more than 4 moves", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory "Hitmonchan",
moves: [
"Ice Punch"
"Fire Punch"
"Close Combat"
"Mach Punch"
"Rapid Spin"
]
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has a move it can't learn", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", moves: [ "Fissure" ])
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has a fake move", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", moves: [ "Splash", "Armageddon" ])
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has an ability it can't have", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", ability: "Wonder Guard")
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has a bogus ability", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", ability: "Being Batman")
server.validateTeam(team).should.not.be.empty
it "returns empty if a pokemon has a hidden ability", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Cloyster", ability: "Overcoat")
server.validateTeam(team).should.be.empty
it "returns non-empty if a pokemon has a level below 1", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", level: 0)
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has a bogus level", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", level: "hi")
server.validateTeam(team).should.not.be.empty
# TODO: 100 is a magic constant
it "returns non-empty if a pokemon has a level over 100", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", level: 101)
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has an iv below 0", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", ivs: { hp: -1 })
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has an iv above 31", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", ivs: { hp: 32 })
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has bogus ivs", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", ivs: true)
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has an ev below 0", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", evs: { hp: -1 })
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has an ev above 255", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", evs: { hp: 256 })
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has an ev total above 510", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", evs: { hp: 255, defense: 255, speed: 255 })
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has bogus evs", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Magikarp", evs: true)
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has an invalid gender", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Metagross", gender: "Alien")
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has a gender it can't have", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Metagross", gender: "F")
server.validateTeam(team).should.not.be.empty
team = generateTeam()
team[0] = Factory("Blissey", gender: "M")
server.validateTeam(team).should.not.be.empty
team = generateTeam()
team[0] = Factory("Gallade", gender: "F")
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has a bogus forme", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Blissey", forme: "Super Ultra Mega Blissey")
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon has a battle-only forme", ->
server = new BattleServer()
team = generateTeam()
team[0] = Factory("Meloetta", forme: "pirouette", moves: ["Relic Song"])
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon cannot have its forme"
it "returns non-empty if the format is fake", ->
server = new BattleServer()
server.validateTeam(generateTeam(), 'bogusformat').should.not.be.empty
it "returns non-empty if a pokemon's nickname matches another species", ->
server = new BattleServer()
team = generateTeam()
team[0].name = "Latios"
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon's nickname is blank", ->
server = new BattleServer()
team = generateTeam()
team[0].name = ""
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon's nickname contains illegal chars", ->
server = new BattleServer()
team = generateTeam()
team[0].name = "some chars \uFE20 are illegal"
server.validateTeam(team).should.not.be.empty
it "returns non-empty if a pokemon's nickname is past a certain length", ->
server = new BattleServer()
team = generateTeam()
team[0].name = ("A" for x in [0...50]).join('')
server.validateTeam(team).should.not.be.empty
describe "#beginBattles", ->
it "creates a battle per pair", (done) ->
server = new BattleServer()
for i in [1..4]
server.findOrCreateUser(id: i, name: "user#{i}", @stubSpark())
server.queuePlayer("user#{i}", generateTeam())
server.beginBattles (err, battleIds) ->
battleIds.length.should.equal(2)
done()
describe "users", ->
it "are recorded to be playing in which battles", (done) ->
server = new BattleServer()
[ user1, user2, user3 ] = [ "a", "b", "c" ]
for name, i in [ user1, user2, user3 ]
server.findOrCreateUser(id: i, name: name, @stubSpark())
server.queuePlayer(user1, generateTeam()).should.be.empty
server.queuePlayer(user2, generateTeam()).should.be.empty
server.queuePlayer(user3, generateTeam()).should.be.empty
server.beginBattles (err, battleIds) ->
server.getUserBattles(user1).should.eql(battleIds)
server.getUserBattles(user2).should.eql(battleIds)
server.getUserBattles(user3).should.be.empty
done()
it "no longer records battles once they end", (done) ->
server = new BattleServer()
[ user1, user2, user3 ] = [ "a", "b", "c" ]
for name, i in [ user1, user2, user3 ]
server.findOrCreateUser(id: i, name: name, @stubSpark())
server.queuePlayer(user1, generateTeam()).should.be.empty
server.queuePlayer(user2, generateTeam()).should.be.empty
server.queuePlayer(user3, generateTeam()).should.be.empty
server.beginBattles (err, battleIds) ->
for battleId in battleIds
battle = server.findBattle(battleId)
battle.endBattle()
server.getUserBattles(user1).should.be.empty
server.getUserBattles(user2).should.be.empty
server.getUserBattles(user3).should.be.empty
done()
it "can join multiple times", ->
server = new BattleServer()
server.findOrCreateUser(id: 1, name: "hey", spark1 = @stubSpark())
server.join(spark1)
(=>
server.findOrCreateUser(id: 1, name: "hey", spark2 = @stubSpark())
server.join(spark2)
).should.not.throw()
it "records battles they're under an alt in", (done) ->
server = new BattleServer()
[ user1, user2 ] = [ "a", "b" ]
for name, i in [ user1, user2 ]
server.findOrCreateUser(id: i, name: name, @stubSpark())
server.queuePlayer(user1, generateTeam(), null, 'alt1').should.be.empty
server.queuePlayer(user2, generateTeam(), null, 'alt2').should.be.empty
server.beginBattles (err, battleIds) ->
server.getUserBattles(user1).should.not.be.empty
server.getUserBattles(user2).should.not.be.empty
done()
it "auto-rejoin battles they're under an alt in", (done) ->
server = new BattleServer()
[ user1, user2 ] = [ "a", "b" ]
server.findOrCreateUser(id: 1, name: user1, spark1 = @stubSpark())
server.findOrCreateUser(id: 2, name: user2, spark2 = @stubSpark())
server.queuePlayer(user1, generateTeam(), null, 'alt1').should.be.empty
server.queuePlayer(user2, generateTeam(), null, 'alt2').should.be.empty
server.beginBattles (err, battleIds) =>
[battleId] = battleIds
battle = server.findBattle(battleId).battle
# test spark1
spy = @sandbox.spy(battle, 'tellPlayer').withArgs(user1, Protocol.RECEIVE_TEAM)
server.join(spark1)
spy.calledOnce.should.be.true
battle.tellPlayer.restore()
# test spark2
spy = @sandbox.spy(battle, 'tellPlayer').withArgs(user2, Protocol.RECEIVE_TEAM)
server.join(spark2)
spy.calledOnce.should.be.true
battle.tellPlayer.restore()
done()
it "automatically leaves a battle when leaving the server", (done) ->
server = new BattleServer()
[ user1, user2 ] = [ "a", "b" ]
server.findOrCreateUser(id: 1, name: user1, spark1 = @stubSpark())
server.findOrCreateUser(id: 2, name: user2, spark2 = @stubSpark())
server.join(spark1)
server.join(spark2)
server.queuePlayer(user1, generateTeam(), null, 'alt1').should.be.empty
server.queuePlayer(user2, generateTeam(), null, 'alt2').should.be.empty
server.beginBattles (err, battleIds) =>
[battleId] = battleIds
battle = server.findBattle(battleId).battle
# test spark1
spy = @sandbox.spy(battle, 'remove').withArgs(spark1)
broadcastSpy = @sandbox.spy(battle, 'send')
broadcastSpy = broadcastSpy.withArgs('leaveChatroom', battle.id, 'alt1')
server.leave(spark1)
spark1.end()
spy.calledOnce.should.be.true
broadcastSpy.calledOnce.should.be.true
battle.remove.restore()
battle.send.restore()
# test spark2
spy = @sandbox.spy(battle, 'remove').withArgs(spark2)
broadcastSpy = @sandbox.spy(battle, 'send')
broadcastSpy = broadcastSpy.withArgs('leaveChatroom', battle.id, 'alt2')
server.leave(spark2)
spark2.end()
spy.calledOnce.should.be.true
broadcastSpy.calledOnce.should.be.true
battle.remove.restore()
battle.send.restore()
done()
describe "a battle", ->
beforeEach (done) ->
@server = new BattleServer()
[ @user1, @user2, @user3 ] = [ "a", "b", "c" ]
for name, i in [ @user1, @user2, @user3 ]
@server.findOrCreateUser(id: i, name: name, @stubSpark())
@server.queuePlayer(@user1, generateTeam()).should.be.empty
@server.queuePlayer(@user2, generateTeam()).should.be.empty
@server.queuePlayer(@user3, generateTeam()).should.be.empty
@server.queuedPlayers().should.have.length(3)
@server.beginBattles (err, battleIds) =>
@battleIds = battleIds
@relevantUser = @server.findBattle(@battleIds[0]).battle.playerIds[0]
done()
it "removes from user battles if ended", ->
@server.getUserBattles(@relevantUser).should.not.be.empty
battle = @server.findBattle(@battleIds[0])
battle.endBattle()
@server.getUserBattles(@user1).should.be.empty
@server.getUserBattles(@user2).should.be.empty
@server.getUserBattles(@user3).should.be.empty
it "removes from user battles if forfeited", ->
@server.getUserBattles(@relevantUser).should.not.be.empty
battle = @server.findBattle(@battleIds[0])
battle.forfeit(@relevantUser)
@server.getUserBattles(@user1).should.be.empty
@server.getUserBattles(@user2).should.be.empty
@server.getUserBattles(@user3).should.be.empty

96
test/shared.coffee Normal file
View File

@@ -0,0 +1,96 @@
{Factory} = require './factory'
shouldDoNoDamage = (moveName, battleOptions) ->
it 'does no damage', ->
create.call(this, battleOptions)
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
@p2.currentHP.should.equal @p2.stat('hp')
shouldFailIfUsedTwice = (moveName, battleOptions) ->
it 'should fail if used twice', ->
create.call(this, battleOptions)
move = @battle.getMove(moveName)
mock = @sandbox.mock(move).expects('fail').once()
@battle.performMove(@p1, move)
@battle.performMove(@p1, move)
mock.verify()
build = (context, opts={}) ->
generation = opts.gen ? 'bw'
{Battle} = require("../server/#{generation}/battle")
{BattleController} = require("../server/#{generation}/battle_controller")
context.id1 = 'abcde'
context.id2 = 'fghij'
team1 = opts.team1 || [Factory('Magikarp'), Factory('Magikarp')]
team2 = opts.team2 || [Factory('Magikarp'), Factory('Magikarp')]
conditions = opts.conditions
players = [
{id: context.id1, name: context.id1, team: team1, ratingKey: context.id1}
{id: context.id2, name: context.id2, team: team2, ratingKey: context.id2}
]
numActive = opts.numActive || 1
format = opts.format
context.battle = new Battle(createId(), players, {numActive, conditions, format})
context.controller = new BattleController(context.battle)
context.team1 = context.battle.getTeam(context.id1)
context.team2 = context.battle.getTeam(context.id2)
context.p1 = context.team1.first()
context.p2 = context.team2.first()
createTestRNG.call(context)
biasRNG.call(context, 'next', 'ch', 1)
biasRNG.call(context, 'randInt', 'damage roll', 0)
biasRNG.call(context, 'randInt', 'miss', 0) # Can be overridden, of course.
biasRNG.call(context, 'next', 'secondary effect', 1) # No effect unless 100%
biasRNG.call(context, 'next', 'secondary boost', 0) # Always happens
biasRNG.call(context, 'randInt', 'flinch', 99) # No flinch (unless fake out)
# moves that call other moves also get new targets
biasRNG.call(context, 'randInt', 'selected pokemon target', 0)
createId = ->
Math.random().toString(16).substr(2)
create = (opts={}) ->
build(this, opts)
@controller.beginBattle()
createTestRNG = ->
@biasedRNGFuncs = {}
for funcName in ['next', 'randInt']
do (funcName) =>
oldFunc = @battle.rng[funcName].bind(@battle.rng)
@battle.rng[funcName] = (args...) =>
id = args[args.length - 1]
func = @biasedRNGFuncs[funcName]
return (if id of func then func[id] else oldFunc(args...))
testEveryMove = (allMoves, gen) ->
for move in allMoves
if move.hasPrimaryEffect()
do (move) ->
describe move.name, ->
# Test primary boost moves
if move.primaryBoostStats?
it "boosts properly", ->
create.call(this, gen: gen)
target = (if move.primaryBoostTarget == 'self' then @p1 else @p2)
target.stages.should.not.containEql(move.primaryBoostStats)
@battle.performMove(@p1, move)
target.stages.should.containEql(move.primaryBoostStats)
if move.primaryBoostTarget == 'self'
it "can never miss self", ->
create.call(this, gen: gen)
move.chanceToHit(@battle, @p1, @p1).should.equal(0)
biasRNG = (funcName, id, returns) ->
@biasedRNGFuncs[funcName] ||= {}
@biasedRNGFuncs[funcName][id] = returns
module.exports = {
shouldDoNoDamage, shouldFailIfUsedTwice,
build, create, biasRNG, testEveryMove
}

View File

@@ -0,0 +1,272 @@
require './helpers'
should = require 'should'
{PokeBattle} = require '../client/app/js/helpers/team_parsing'
describe "Client", ->
describe "parsing teams", ->
beforeEach ->
@teamString = """
Swampert @ Leftovers
Trait: Torrent
EVs: 252 HP / 252 Def / 4 SDef
Relaxed Nature
- Surf
- Ice Beam
- Earthquake
- Protect
Poop (Blissey) (F) @ Leftovers
Trait: Natural Cure
Shiny: Yes
EVs: 176 HP / 252 Def / 80 SDef
Bold Nature
- Seismic Toss
- Aromatherapy
Skarmory (M)
Tyranitar @ Never-Melt Ice
Ability: Lightning Rod
Level: 1
Happiness: 3
EVs: 252 HP / 80 Def / 176 SDef
- Hidden Power [Grass]
- Extreme Speed
Articuno
IVs: 30 HP / 29 Def
- Hidden Power Fire
Claydol
EVs: 252 SpA / 176 SpD
"""
it "converts a team to an array readable by this simulator", ->
team = PokeBattle.parseTeam(@teamString)
pokemon = team[0]
pokemon.species.should.equal("Swampert")
pokemon.should.not.have.property('gender')
pokemon.item.should.equal("Leftovers")
pokemon.ability.should.equal("Torrent")
pokemon.nature.should.equal("Relaxed")
pokemon.should.not.have.property('level')
pokemon.should.not.have.property('happiness')
pokemon.should.not.have.property('shiny')
pokemon.should.not.have.property('ivs')
pokemon.evs.should.eql(hp: 252, defense: 252, specialDefense: 4)
pokemon.moves.should.eql([ "Surf", "Ice Beam", "Earthquake", "Protect"])
pokemon = team[1]
pokemon.name.should.equal("Poop")
pokemon.species.should.equal("Blissey")
pokemon.gender.should.equal("F")
pokemon.item.should.equal("Leftovers")
pokemon.ability.should.equal("Natural Cure")
pokemon.nature.should.equal("Bold")
pokemon.should.not.have.property('level')
pokemon.should.not.have.property('happiness')
pokemon.shiny.should.be.true
pokemon.should.not.have.property('ivs')
pokemon.evs.should.eql(hp: 176, defense: 252, specialDefense: 80)
pokemon.moves.should.eql(["Seismic Toss", "Aromatherapy"])
pokemon = team[2]
pokemon.species.should.equal("Skarmory")
pokemon.gender.should.equal("M")
pokemon.should.not.have.property('item')
pokemon.should.not.have.property('ability')
pokemon.should.not.have.property('nature')
pokemon.should.not.have.property('level')
pokemon.should.not.have.property('happiness')
pokemon.should.not.have.property('shiny')
pokemon.should.not.have.property('ivs')
pokemon.should.not.have.property('evs')
pokemon.should.not.have.property('moves')
pokemon = team[3]
pokemon.species.should.equal("Tyranitar")
pokemon.should.not.have.property('gender')
pokemon.should.not.have.property('nature')
pokemon.level.should.equal(1)
pokemon.happiness.should.equal(3)
pokemon.should.not.have.property('shiny')
pokemon.ivs.should.eql(attack: 30, specialAttack: 30)
pokemon.evs.should.eql(hp: 252, defense: 80, specialDefense: 176)
pokemon.moves.should.eql(["Hidden Power", "ExtremeSpeed"])
pokemon.item.should.eql("NeverMeltIce")
pokemon.ability.should.eql("Lightningrod")
pokemon = team[4]
pokemon.species.should.equal("Articuno")
pokemon.should.not.have.property('gender')
pokemon.should.not.have.property('item')
pokemon.should.not.have.property('ability')
pokemon.should.not.have.property('nature')
pokemon.should.not.have.property('level')
pokemon.should.not.have.property('happiness')
pokemon.should.not.have.property('shiny')
# IVs that were explicitly set override Hidden Power!
pokemon.ivs.should.eql(hp: 30, defense: 29)
pokemon.should.not.have.property('evs')
pokemon.moves.should.eql(['Hidden Power'])
pokemon = team[5]
pokemon.species.should.equal("Claydol")
pokemon.evs.should.eql(specialAttack: 252, specialDefense: 176)
describe "parsing formes", ->
it "takes into account differing styles of formes", ->
formes =
"Thundurus-Therian": ["Thundurus", "therian"]
"Thundurus-T": ["Thundurus", "therian"]
"Thundurus": ["Thundurus", null]
"Landorus-Therian": ["Landorus", "therian"]
"Landorus-T": ["Landorus", "therian"]
"Landorus": ["Landorus", null]
"Tornadus-Therian": ["Tornadus", "therian"]
"Tornadus-T": ["Tornadus", "therian"]
"Tornadus": ["Tornadus", null]
"Shaymin-Sky": ["Shaymin", "sky"]
"Shaymin-S": ["Shaymin", "sky"]
"Shaymin": ["Shaymin", null]
"Giratina-Origin": ["Giratina", "origin"]
"Giratina-O": ["Giratina", "origin"]
"Giratina": ["Giratina", null]
"Arceus-Dark": ["Arceus", null]
"Arceus": ["Arceus", null]
"Kyurem-Black": ["Kyurem", "black"]
"Kyurem-B": ["Kyurem", "black"]
"Kyurem-White": ["Kyurem", "white"]
"Kyurem-W": ["Kyurem", "white"]
"Kyurem": ["Kyurem", null]
"Rotom-Wash": ["Rotom", "wash"]
"Rotom-W": ["Rotom", "wash"]
"Rotom-Fan": ["Rotom", "fan"]
"Rotom-S": ["Rotom", "fan"]
"Rotom-Heat": ["Rotom", "heat"]
"Rotom-H": ["Rotom", "heat"]
"Rotom-Frost": ["Rotom", "frost"]
"Rotom-F": ["Rotom", "frost"]
"Rotom-Mow": ["Rotom", "mow"]
"Rotom-C": ["Rotom", "mow"]
"Rotom": ["Rotom", null]
"Deoxys-Attack": ["Deoxys", "attack"]
"Deoxys-A": ["Deoxys", "attack"]
"Deoxys-Defense": ["Deoxys", "defense"]
"Deoxys-D": ["Deoxys", "defense"]
"Deoxys-Speed": ["Deoxys", "speed"]
"Deoxys-S": ["Deoxys", "speed"]
"Deoxys": ["Deoxys", null]
"Basculin-Blue-Striped": ["Basculin", "blue-striped"]
"Basculin-A": ["Basculin", "blue-striped"]
"Basculin": ["Basculin", null]
"Keldeo-Resolute": ["Keldeo", "resolute"]
"Keldeo-R": ["Keldeo", "resolute"]
"Keldeo": ["Keldeo", null]
# TODO: "Shellos-East": ["Shellos", "east"]
"Shellos-East": ["Shellos", "default"]
"Shellos": ["Shellos", null]
# TODO: "Gastrodon-East": ["Gastrodon", "east"]
"Gastrodon-East": ["Gastrodon", "default"]
"Gastrodon": ["Gastrodon", null]
"Wormadam-G": ["Wormadam", "sandy"]
"Wormadam-Sandy": ["Wormadam", "sandy"]
"Wormadam-S": ["Wormadam", "trash"]
"Wormadam-Trash": ["Wormadam", "trash"]
"Wormadam": ["Wormadam", null]
# TODO: fix these
"Deerling-Summer": ["Deerling", null]
"Deerling-Autumn": ["Deerling", null]
"Deerling-Spring": ["Deerling", null]
"Deerling": ["Deerling", null]
"Sawsbuck-Summer": ["Sawsbuck", null]
"Sawsbuck-Autumn": ["Sawsbuck", null]
"Sawsbuck-Spring": ["Sawsbuck", null]
"Sawsbuck": ["Sawsbuck", null]
"Unown-A": ["Unown", null]
"Unown": ["Unown", null]
teamArray = (pokemonName for pokemonName of formes)
teamFormes = (forme for forme in teamArray)
teamArray.unshift('')
teamArray.push('')
teamString = teamArray.join('\n\n')
team = PokeBattle.parseTeam(teamString)
for member, i in team
[species, forme] = formes[teamFormes[i]]
should.exist(member)
member.should.have.property('species')
member.species.should.equal(species)
if forme
member.should.have.property('forme')
member.forme.should.equal(forme)
describe "exporting teams", ->
it "exports properly", ->
team = [
{
species: "Pikachu"
moves: ["Substitute", "Thunderbolt", "Hidden Power", "Grass Knot"]
item: "Light Ball"
ability: "Lightningrod"
gender: "F"
level: 99
shiny: true
happiness: 20
ivs: { attack: 30, defense: 30 }
evs: { hp: 4, specialAttack: 252, speed: 252 }
}
{
name: "Kyboo"
species: "Kyurem"
forme: "black"
item: "Choice Band"
nature: "Adamant"
moves: ["Ice Beam", "Fusion Bolt", "Outrage", "Dragon Claw"]
ability: "Turboblaze"
}
]
PokeBattle.exportTeam(team).should.equal """
Pikachu (F) @ Light Ball
Ability: Lightningrod
EVs: 4 HP / 252 SAtk / 252 Spe
IVs: 30 Atk / 30 Def
Level: 99
Shiny: Yes
Happiness: 20
- Substitute
- Thunderbolt
- Hidden Power
- Grass Knot
Kyboo (Kyurem-B) @ Choice Band
Ability: Turboblaze
Adamant nature
- Ice Beam
- Fusion Bolt
- Outrage
- Dragon Claw
"""

485
test/xy/abilities.coffee Normal file
View File

@@ -0,0 +1,485 @@
{Attachment, Status} = require('../../server/xy/attachment')
{Battle} = require('../../server/xy/battle')
{Pokemon} = require('../../server/xy/pokemon')
{Weather} = require('../../shared/weather')
{Ability} = require '../../server/xy/data/abilities'
{Item} = require '../../server/xy/data/items'
util = require '../../server/xy/util'
{Factory} = require '../factory'
should = require 'should'
shared = require '../shared'
require '../helpers'
describe "XY Abilities:", ->
testWeatherAbility = (name, weather) ->
describe name, ->
it "causes #{weather} that ends after 5 turns", ->
shared.create.call(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
@battle.weatherDuration.should.equal(5)
it "does not activate if the weather is already #{weather}", ->
shared.build(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
@battle.setWeather(weather, 2)
@controller.beginBattle()
@battle.weatherDuration.should.equal(2)
it "is lengthened by rocks", ->
for itemName, item of Item
break if item.lengthensWeather == weather
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: name, item: itemName)]
@battle.weatherDuration.should.equal(8)
testWeatherAbility("Drizzle", Weather.RAIN)
testWeatherAbility("Drought", Weather.SUN)
testWeatherAbility("Sand Stream", Weather.SAND)
testWeatherAbility("Snow Warning", Weather.HAIL)
testNormalTypeChangeAbility = (name, type) ->
describe name, ->
it "changes Normal-type moves used by attacker to #{type}-type", ->
shared.create.call(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
spy = @sandbox.spy(@p1, 'editMoveType')
tackle = @battle.getMove('Tackle')
@battle.performMove(@p1, tackle)
spy.returned(type).should.be.true
it "does not change non-Normal-type moves used by attacker", ->
shared.create.call(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
spy = @sandbox.spy(@p1, 'editMoveType')
ember = @battle.getMove('Ember')
@battle.performMove(@p1, ember)
spy.returned(type).should.be.false
spy.returned(ember.type).should.be.true
it "boosts Normal-type moves by 1.3x", ->
shared.create.call(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
tackle = @battle.getMove('Tackle')
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x14CD)
it "does not boost regular #{type}-type moves", ->
shared.create.call(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
for move in @battle.MoveList
if move.type == type && !move.isNonDamaging() then break
move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
it "does not boost non-#{type}-type moves", ->
shared.create.call(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
ember = @battle.getMove('Ember')
ember.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
it "is unaffected by the original immunities", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: name)]
team2: [Factory("Gengar")]
tackle = @battle.getMove('Tackle')
mock = @sandbox.mock(tackle).expects('hit').once()
@battle.performMove(@p1, tackle)
mock.verify()
testNormalTypeChangeAbility("Aerilate", "Flying")
testNormalTypeChangeAbility("Pixilate", "Fairy")
testNormalTypeChangeAbility("Refrigerate", "Ice")
describe "Shadow Tag", ->
it "does not affect ghosts", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Gengar")]
team2: [Factory("Magikarp", ability: "Shadow Tag")]
@p1.isSwitchBlocked().should.be.false
@battle.beginTurn()
@p1.isSwitchBlocked().should.be.false
testAuraAbility = (name, type) ->
describe name, ->
it "raises base power of #{type} attacks by 1.33x", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: name)]
move = @battle.findMove (m) ->
m.type == type && !m.isNonDamaging()
move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1547)
it "decreases #{type} attacks by 3/4x if Aura Break is on the field", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: name)]
team2: [Factory("Magikarp", ability: "Aura Break")]
move = @battle.findMove (m) ->
m.type == type && !m.isNonDamaging()
move.modifyBasePower(@battle, @p1, @p2).should.equal(0xC00)
it "does nothing to moves not of #{type} type", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: name)]
move = @battle.findMove (m) ->
m.type != type && !m.isNonDamaging()
move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
testAuraAbility("Dark Aura", "Dark")
testAuraAbility("Fairy Aura", "Fairy")
describe "Gale Wings", ->
it "adds 1 to the priority of the user's Flying moves", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Gale Wings")]
gust = @battle.getMove("Gust")
@p1.editPriority(0, gust).should.equal(1)
it "does not change priority otherwise", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Gale Wings")]
tackle = @battle.getMove("Tackle")
@p1.editPriority(0, tackle).should.equal(0)
describe "Bulletproof", ->
it "makes user immune to bullet moves", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Bulletproof")]
shadowBall = @battle.getMove('Shadow Ball')
@p1.isImmune(shadowBall.type, move: shadowBall).should.be.true
describe "Competitive", ->
it "boosts special attack by 2 every time a stat is lowered", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Competitive")]
@p1.boost(defense: -1, @p2)
@p1.stages.should.containEql(specialAttack: 2)
@p1.boost(specialAttack: -1, defense: -2, evasion: 1, @p2)
@p1.stages.should.containEql(specialAttack: 5)
it "does not boost special attack if the stat was self-lowered", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Competitive")]
@battle.performMove(@p1, @battle.getMove("Close Combat"))
boosts = {specialAttack: 0, defense: -1, specialDefense: -1}
@p1.stages.should.containEql(boosts)
describe "Fur Coat", ->
it "modifies physical attacks by 0x800", ->
shared.create.call this,
gen: 'xy'
team2: [Factory("Magikarp", ability: "Fur Coat")]
tackle = @battle.getMove('Tackle')
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x800)
it "doesn't modify other attacks", ->
shared.create.call this,
gen: 'xy'
team2: [Factory("Magikarp", ability: "Fur Coat")]
thunderbolt = @battle.getMove('Thunderbolt')
thunderbolt.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
describe "Gooey", ->
it "lowers the attacker's speed by 1 on contact", ->
shared.create.call this,
gen: 'xy'
team2: [Factory("Magikarp", ability: "Gooey")]
tackle = @battle.getMove('Tackle')
@p1.stages.should.containEql(speed: 0)
@battle.performMove(@p1, tackle)
@p1.stages.should.containEql(speed: -1)
it "does not lower the attacker's speed by 1 on non-contact", ->
shared.create.call this,
gen: 'xy'
team2: [Factory("Magikarp", ability: "Gooey")]
thunderbolt = @battle.getMove('Thunderbolt')
@p1.stages.should.containEql(speed: 0)
@battle.performMove(@p1, thunderbolt)
@p1.stages.should.containEql(speed: 0)
it "works even if the defender faints", ->
shared.create.call this,
gen: 'xy'
team2: [Factory("Magikarp", ability: "Gooey"), Factory("Magikarp")]
@p2.currentHP = 1
tackle = @battle.getMove('Tackle')
@p1.stages.should.containEql(speed: 0)
@battle.performMove(@p1, tackle)
@p1.stages.should.containEql(speed: -1)
describe "Mega Launcher", ->
it "boosts pulse moves by x1.5", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Mega Launcher")]
waterPulse = @battle.getMove('Water Pulse')
waterPulse.modifyBasePower(@battle, @p1, @p2).should.equal(0x1800)
it "does not boost non-pulse moves", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Mega Launcher")]
tackle = @battle.getMove('Tackle')
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
describe "Overcoat", ->
it "makes the user immune to weather", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Overcoat")]
@p1.isWeatherDamageImmune(Weather.SAND).should.be.true
@p1.isWeatherDamageImmune(Weather.HAIL).should.be.true
it "makes the user immune to powder moves", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Overcoat")]
spore = @battle.getMove("Spore")
mock = @sandbox.mock(spore).expects('hit').never()
@battle.performMove(@p2, spore)
mock.verify()
describe "Parental Bond", ->
it "hits twice if the move has only one target", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Parental Bond")]
tackle = @battle.getMove('Tackle')
targets = @battle.getTargets(tackle, @p1)
tackle.calculateNumberOfHits(@battle, @p1, targets).should.equal(2)
it "hits once if the move has multiple targets", ->
shared.create.call this,
gen: 'xy'
numActive: 2
team1: [Factory("Magikarp", ability: "Parental Bond"), Factory("Magikarp")]
team2: (Factory("Magikarp") for x in [0..1])
earthquake = @battle.getMove('Earthquake')
targets = @battle.getTargets(earthquake, @p1)
earthquake.calculateNumberOfHits(@battle, @p1, targets).should.equal(1)
it "hits once if the move is non-damaging", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Parental Bond")]
willOWisp = @battle.getMove('Will-O-Wisp')
targets = @battle.getTargets(willOWisp, @p1)
willOWisp.calculateNumberOfHits(@battle, @p1, targets).should.equal(1)
it "hits for half damage the second hit", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Parental Bond")]
tackle = @battle.getMove('Tackle')
spy = @sandbox.spy(tackle, 'modifyDamage')
@battle.performMove(@p1, tackle)
spy.calledTwice.should.be.true
spy.returnValues[0].should.equal(0x1000)
spy.returnValues[1].should.equal(0x800)
it "hits the same number otherwise if the move is multi-hit", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Parental Bond")]
shared.biasRNG.call(this, "choice", 'num hits', 4)
shared.biasRNG.call(this, "randInt", 'num hits', 4)
pinMissile = @battle.getMove('Pin Missile')
targets = @battle.getTargets(pinMissile, @p1)
pinMissile.calculateNumberOfHits(@battle, @p1, targets).should.equal(4)
it "doesn't hit for half damage on the second hit using multi-hit moves", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Parental Bond")]
shared.biasRNG.call(this, "choice", 'num hits', 4)
shared.biasRNG.call(this, "randInt", 'num hits', 4)
pinMissile = @battle.getMove('Pin Missile')
spy = @sandbox.spy(pinMissile, 'modifyDamage')
@battle.performMove(@p1, pinMissile)
spy.callCount.should.equal(4)
spy.returnValues[0].should.equal(0x1000)
spy.returnValues[1].should.equal(0x1000)
it "recoils only once, but with both attack damages combined", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Parental Bond")]
takeDown = @battle.getMove("Take Down")
spy = @sandbox.spy(takeDown, 'hit')
@battle.performMove(@p1, takeDown)
totalDamage = spy.returnValues.reduce((a, b) -> a + b)
recoilDamage = Math.round(totalDamage * -takeDown.recoil / 100)
(@p1.stat('hp') - @p1.currentHP).should.equal(recoilDamage)
describe "Protean", ->
it "changes the Pokemon's type when using a move", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Protean")]
@p1.types.should.eql([ "Water" ])
@controller.makeMove(@id1, "Tackle")
@controller.makeMove(@id2, "Splash")
@p1.types.should.eql([ "Normal" ])
describe "Stance Change", ->
it "changes to blade forme when using an attacking move", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Aegislash", ability: "Stance Change")]
@p1.forme.should.equal("default")
@battle.performMove(@p1, @battle.getMove("Shadow Sneak"))
@p1.forme.should.equal("blade")
it "changes to shield forme when using King's Shield", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Aegislash", ability: "Stance Change")]
@battle.performMove(@p1, @battle.getMove("Shadow Sneak"))
@p1.forme.should.equal("blade")
@battle.performMove(@p1, @battle.getMove("King's Shield"))
@p1.forme.should.equal("default")
it "changes to shield forme after switching out and back in", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Aegislash", ability: "Stance Change"), Factory("Magikarp")]
@battle.performMove(@p1, @battle.getMove("Shadow Sneak"))
@p1.forme.should.equal("blade")
@battle.performSwitch(@p1, 1)
@battle.performSwitch(@team1.first(), 1)
@p1.forme.should.equal("default")
it "does not change formes when using any other move", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Aegislash", ability: "Stance Change")]
@battle.performMove(@p1, @battle.getMove("Swords Dance"))
@p1.forme.should.equal("default")
@battle.performMove(@p1, @battle.getMove("Shadow Sneak"))
@p1.forme.should.equal("blade")
@battle.performMove(@p1, @battle.getMove("Swords Dance"))
@p1.forme.should.equal("blade")
it "doesn't attempt to change forme for Pokemon who aren't Aegislash", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Ditto", ability: "Stance Change")]
@battle.performMove(@p1, @battle.getMove("Shadow Sneak"))
@p1.forme.should.not.equal("blade")
it "doesn't attempt to change forme to default for non-Aegislashes", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Rotom", forme: "wash", ability: "Stance Change")]
@battle.performMove(@p1, @battle.getMove("King's Shield"))
@p1.forme.should.not.equal("default")
it "cannot be skill-swapped"
it "cannot be replaced with another ability"
describe "Strong Jaw", ->
it "boosts bite moves by x1.5", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Strong Jaw")]
bite = @battle.getMove('Bite')
bite.modifyBasePower(@battle, @p1, @p2).should.equal(0x1800)
it "does not boost non-bite moves", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Strong Jaw")]
thunderbolt = @battle.getMove('Thunderbolt')
thunderbolt.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
describe "Tough Claws", ->
it "boosts contact moves by x1.3", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Tough Claws")]
tackle = @battle.getMove('Tackle')
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x14CD)
it "does not boost non-contact moves", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Tough Claws")]
thunderbolt = @battle.getMove('Thunderbolt')
thunderbolt.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
describe "Mummy", ->
it "doesn't change ability if attacker has Stance Change", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Mummy")]
team2: [Factory("Magikarp", ability: "Stance Change")]
tackle = @battle.getMove("Tackle")
@p2.hasAbility("Mummy").should.be.false
@battle.performMove(@p2, tackle)
@p2.hasAbility("Mummy").should.be.false
testAttachmentImmuneAbility = (name, attachments, options = {}) ->
describe name, ->
it "prevents the pokemon from receiving a specific attachment", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", gender: "F", ability: name)]
team2: [Factory("Magikarp", gender: "M")]
for attachment in attachments
should.not.exist @p1.attach(attachment, source: @p2)
@p1.has(attachment).should.be.false
shouldCure = options.cure ? true
if shouldCure
it "removes the attachment if the pokemon already has it", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", gender: "F")]
team2: [Factory("Magikarp", gender: "M")]
for attachment in attachments
@p1.attach(attachment, source: @p2)
@p1.has(attachment).should.be.true
@p1.copyAbility(Ability[name.replace(/\s+/g, '')])
@p1.update()
@p1.has(attachment).should.be.false
@p1.copyAbility(null)
testAttachmentImmuneAbility("Aroma Veil", [Attachment.Attract, Attachment.Disable,
Attachment.Encore, Attachment.Taunt, Attachment.Torment], cure: false)
testAttachmentImmuneAbility("Oblivious", [Attachment.Attract, Attachment.Taunt])
testAttachmentImmuneAbility("Sweet Veil", [Status.Sleep], cure: false)
describe "Effect Spore", ->
it "doesn't affect Grass types", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Effect Spore")]
team2: [Factory("Bulbasaur")]
# Sleep
shared.biasRNG.call(this, "randInt", 'effect spore', 1)
@battle.performMove(@p2, @battle.getMove('Tackle'))
@p2.has(Status.Sleep).should.be.false
it "doesn't affect Overcoat Pokemon", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Effect Spore")]
team2: [Factory("Magikarp", ability: "Overcoat")]
# Paralysis
shared.biasRNG.call(this, "randInt", 'effect spore', 2)
@battle.performMove(@p2, @battle.getMove('Tackle'))
@p2.has(Status.Paralyze).should.be.false
it "doesn't affect Safety Goggles holders", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Effect Spore")]
team2: [Factory("Magikarp", item: "Safety Goggles")]
# Poison
shared.biasRNG.call(this, "randInt", 'effect spore', 3)
@battle.performMove(@p2, @battle.getMove('Tackle'))
@p2.has(Status.Poison).should.be.false

View File

@@ -0,0 +1,21 @@
{Attachment, Status} = require '../../server/xy/attachment'
{Factory} = require '../factory'
shared = require '../shared'
should = require 'should'
require '../helpers'
describe "XY status:", ->
describe "Sleep", ->
it "does not reset upon switch out", ->
shared.create.call(this, gen: 'xy')
@p1.attach(Status.Sleep, turns: 3)
@p1.get(Status.Sleep).counter.should.equal(0)
@battle.performMove(@p1, @battle.getMove("Splash"))
@p1.get(Status.Sleep).counter.should.equal(1)
@p1.team.switchOut(@p1)
@p1.get(Status.Sleep).counter.should.equal(1)
describe "paralysis", ->
it "does not affect electric pokemon", ->
shared.create.call(this, team1: [Factory("Pikachu")], gen: 'xy')
should.not.exist @p1.attach(Status.Paralyze)

117
test/xy/battle.coffee Normal file
View File

@@ -0,0 +1,117 @@
{Attachment} = require('../../server/xy/attachment')
{Battle} = require('../../server/xy/battle')
{BattleController} = require('../../server/xy/battle_controller')
{Pokemon} = require('../../server/xy/pokemon')
{Weather} = require('../../shared/weather')
{Ability} = require('../../server/xy/data/abilities')
{Factory} = require('../factory')
{Player} = require('../../server/player')
{Protocol} = require '../../shared/protocol'
should = require 'should'
sinon = require 'sinon'
shared = require '../shared'
require '../helpers'
describe "XY Battle:", ->
describe "mega evolution", ->
it "gets recorded when recording a move", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Charizard", moves: ["Fire Blast"], item: "Charizardite X") ]
@battle.pokemonActions.filter((o) -> o.type == 'mega').should.have.length(0)
@battle.recordMove(@id1, @battle.getMove("Fire Blast"), 0, megaEvolve: true)
megas = @battle.pokemonActions.filter((o) -> o.type == 'mega')
megas.should.have.length(1)
megas[0].pokemon.should.equal(@p1)
it "doesn't get recorded if the pokemon can't mega evolve", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Magikarp", item: "Charizardite X") ]
@battle.pokemonActions.filter((o) -> o.type == 'mega').should.have.length(0)
@battle.recordMove(@id1, @battle.getMove("Splash"), 0, megaEvolve: true)
@battle.pokemonActions.filter((o) -> o.type == 'mega').should.have.length(0)
it "cannot happen if your partner is already going to mega-evolve", ->
shared.create.call this,
gen: 'xy'
numActive: 2
team1: (Factory("Charizard", moves: ["Fire Blast"], item: "Charizardite X") for x in [0..1])
@battle.pokemonActions.filter((o) -> o.type == 'mega').should.have.length(0)
@battle.recordMove(@id1, @battle.getMove("Fire Blast"), 0, megaEvolve: true)
@battle.pokemonActions.filter((o) -> o.type == 'mega').should.have.length(1)
@battle.recordMove(@id1, @battle.getMove("Fire Blast"), 1, megaEvolve: true)
@battle.pokemonActions.filter((o) -> o.type == 'mega').should.have.length(1)
it "happens after switches", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Charizard", moves: ["Fire Blast"], item: "Charizardite X") ]
team2: (Factory("Magikarp") for x in [0..1])
@battle.recordMove(@id1, @battle.getMove("Fire Blast"), 0, megaEvolve: true)
@battle.recordSwitch(@id2, 1)
megaSpy = @sandbox.spy(@battle, 'performMegaEvolution')
switchSpy = @sandbox.spy(@battle, 'performSwitch')
@battle.continueTurn()
switchSpy.calledBefore(megaSpy).should.be.true
it "changes the pokemon's forme", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Charizard", moves: ["Fire Blast"], item: "Charizardite X") ]
team2: (Factory("Magikarp") for x in [0..1])
@battle.recordMove(@id1, @battle.getMove("Fire Blast"), 0, megaEvolve: true)
@battle.continueTurn()
@p1.forme.should.equal('mega-x')
it "changes the pokemon's ability", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Kangaskhan", moves: ["Return"], item: "Kangaskhanite") ]
@battle.recordMove(@id1, @battle.getMove("Fire Blast"), 0, megaEvolve: true)
@battle.continueTurn()
should.exist(@p1.ability)
@p1.ability.should.equal(Ability.ParentalBond)
it "does not activate a switchout ability while changing ability", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Gardevoir", ability: "Regenerator", item: "Gardevoirite") ]
@p1.currentHP = 1
@battle.recordMove(@id1, @battle.getMove("Fire Blast"), 0, megaEvolve: true)
@battle.continueTurn()
should.exist(@p1.ability)
@p1.ability.should.equal(Ability.Pixilate)
@p1.currentHP.should.equal 1
it "retains the changed ability upon switching back in", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Kangaskhan", moves: ["Return"], item: "Kangaskhanite"), Factory("Magikarp") ]
@battle.recordMove(@id1, @battle.getMove("Fire Blast"), 0, megaEvolve: true)
@battle.continueTurn()
should.exist(@p1.ability)
@p1.ability.should.equal(Ability.ParentalBond)
@battle.performSwitch(@team1.first(), 1)
@battle.performSwitch(@team1.first(), 1)
should.exist(@p1.ability)
@p1.ability.should.equal(Ability.ParentalBond)
describe "#getAction", ->
it "does not consider mega evolutions", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Charizard", moves: ["Fire Blast"], item: "Charizardite X") ]
@battle.recordMove(@id1, @battle.getMove("Fire Blast"), 0, megaEvolve: true)
@battle.getAction(@p1).type.should.equal("move")
describe "#undoCompletedRequest", ->
it "cancels mega evolutions properly", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Charizard", moves: ["Fire Blast"], item: "Charizardite X") ]
@battle.recordMove(@id1, @battle.getMove("Fire Blast"), 0, megaEvolve: true)
@battle.pokemonActions.should.not.be.empty
(=> @battle.undoCompletedRequest(@id1)).should.not.throw()
@battle.pokemonActions.should.be.empty

View File

@@ -0,0 +1,20 @@
require '../helpers'
{Battle} = require('../../server/xy/battle')
{Pokemon} = require('../../server/xy/pokemon')
{Status, Attachment} = require('../../server/xy/attachment')
{Conditions} = require '../../shared/conditions'
{Factory} = require '../factory'
should = require 'should'
shared = require '../shared'
{Protocol} = require '../../shared/protocol'
describe 'Mechanics', ->
describe 'a frozen pokemon', ->
it "unfreezes if hit by Scald", ->
shared.create.call(this, gen: 'xy')
shared.biasRNG.call(this, "next", 'unfreeze chance', 1) # always stays frozen
@p1.attach(Status.Freeze)
@battle.performMove(@p2, @battle.getMove('Scald'))
@p1.has(Status.Freeze).should.be.false

140
test/xy/items.coffee Normal file
View File

@@ -0,0 +1,140 @@
{Item} = require('../../server/xy/data/items')
{Pokemon} = require '../../server/xy/pokemon'
{Weather} = require('../../shared/weather')
{Attachment, Status} = require '../../server/xy/attachment'
{Move} = require '../../server/xy/move'
{Factory} = require '../factory'
util = require '../../server/xy/util'
should = require 'should'
{_} = require 'underscore'
shared = require '../shared'
require '../helpers'
describe "XY Items:", ->
describe "Weakness Policy", ->
it "raises Attack and Sp. Attack by 2 if hit by a super-effective move", ->
shared.create.call this,
gen: 'xy'
team1: [Factory('Magikarp', item: 'Weakness Policy')]
thunderbolt = @battle.getMove("Thunderbolt")
@p1.stages.should.containEql(attack: 0, specialAttack: 0)
@battle.performMove(@p2, thunderbolt)
@p1.stages.should.containEql(attack: 2, specialAttack: 2)
it "is consumed after use", ->
shared.create.call this,
gen: 'xy'
team1: [Factory('Magikarp', item: 'Weakness Policy')]
thunderbolt = @battle.getMove("Thunderbolt")
@battle.performMove(@p2, thunderbolt)
@p1.hasItem().should.be.false
it "is not used if hit by a non-super-effective move", ->
shared.create.call this,
gen: 'xy'
team1: [Factory('Magikarp', item: 'Weakness Policy')]
ember = @battle.getMove("Ember")
@battle.performMove(@p2, ember)
@p1.hasItem().should.be.true
@p1.stages.should.containEql(attack: 0, specialAttack: 0)
it "is not used if hit by a super-effective move that is non-damaging", ->
shared.create.call this,
gen: 'xy'
team1: [Factory('Magikarp', item: 'Weakness Policy')]
thunderWave = @battle.getMove("Thunder Wave")
@battle.performMove(@p2, thunderWave)
@p1.hasItem().should.be.true
@p1.stages.should.containEql(attack: 0, specialAttack: 0)
it "not used if the wearer is behind a substitute", ->
shared.create.call this,
gen: 'xy'
team1: [Factory('Magikarp', item: 'Weakness Policy')]
@p1.attach(Attachment.Substitute, hp: 1)
thunderbolt = @battle.getMove("Thunderbolt")
@battle.performMove(@p2, thunderbolt)
@p1.hasItem().should.be.true
@p1.stages.should.containEql(attack: 0, specialAttack: 0)
describe "Assault Vest", ->
it "blocks non-damaging moves", ->
shared.create.call this,
gen: 'xy'
team1: [Factory('Magikarp', item: 'Assault Vest')]
splash = @battle.getMove("Splash")
@p1.isMoveBlocked(splash).should.be.true
it "doesn't block damaging moves", ->
shared.create.call this,
gen: 'xy'
team1: [Factory('Magikarp', item: 'Assault Vest')]
tackle = @battle.getMove("Tackle")
@p1.isMoveBlocked(tackle).should.be.false
it "raises special defense by 1.5", ->
shared.create.call this,
gen: 'xy'
team1: [Factory('Magikarp')]
spDef = @p1.stat('specialDefense')
@p1.setItem(Item.AssaultVest)
@p1.stat('specialDefense').should.equal Math.floor(spDef * 1.5)
describe "Gems", ->
it "now only boosts their respective type by x1.3", ->
shared.create.call(this, gen: 'xy')
move = @battle.getMove('Acrobatics')
modifier = Item.FlyingGem::modifyBasePower(move, @p1, @p2)
modifier.should.equal 0x14CD
describe "Kee Berry", ->
it "raises defense if hit by a physical attack", ->
shared.create.call this,
gen: 'xy'
team1: [Factory('Magikarp', item: 'Kee Berry')]
@p1.stages.should.containEql(defense: 0)
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p1.stages.should.containEql(defense: 1)
it "is consumed after use", ->
shared.create.call this,
gen: 'xy'
team1: [Factory('Magikarp', item: 'Kee Berry')]
@p1.hasItem().should.be.true
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p1.hasItem().should.be.false
describe "Maranga Berry", ->
it "raises defense if hit by a special attack", ->
shared.create.call this,
gen: 'xy'
team1: [Factory('Magikarp', item: 'Maranga Berry')]
@p1.stages.should.containEql(specialDefense: 0)
@battle.performMove(@p2, @battle.getMove("Ember"))
@p1.stages.should.containEql(specialDefense: 1)
it "is consumed after use", ->
shared.create.call this,
gen: 'xy'
team1: [Factory('Magikarp', item: 'Maranga Berry')]
@p1.hasItem().should.be.true
@battle.performMove(@p2, @battle.getMove("Ember"))
@p1.hasItem().should.be.false
describe "Safety Goggles", ->
it "makes the user immune to weather", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", item: "Safety Goggles")]
@p1.isWeatherDamageImmune(Weather.SAND).should.be.true
@p1.isWeatherDamageImmune(Weather.HAIL).should.be.true
it "makes the user immune to powder moves", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", item: "Safety Goggles")]
spore = @battle.getMove("Spore")
mock = @sandbox.mock(spore).expects('hit').never()
@battle.performMove(@p2, spore)
mock.verify()

723
test/xy/moves.coffee Normal file
View File

@@ -0,0 +1,723 @@
{Attachment, Status} = require('../../server/xy/attachment')
{Battle} = require('../../server/xy/battle')
{Pokemon} = require('../../server/xy/pokemon')
{Weather} = require('../../shared/weather')
{Move} = require('../../server/xy/move')
util = require '../../server/xy/util'
{Protocol} = require '../../shared/protocol'
{Factory} = require '../factory'
should = require 'should'
{_} = require 'underscore'
shared = require '../shared'
require '../helpers'
describe "XY Moves:", ->
# Test every single move for their primary effects.
shared.testEveryMove(Battle::MoveList, 'xy')
describe "a critical hit", ->
it "multiplies damage by 1.5x", ->
Move::criticalMultiplier.should.equal(1.5)
it "becomes a 50% chance at a +3 CH level", ->
Move::determineCriticalHitFromLevel(3, .49).should.be.true
Move::determineCriticalHitFromLevel(3, .5).should.be.false
it "becomes a 100% chance at a +4 CH level", ->
Move::determineCriticalHitFromLevel(4, .99).should.be.true
Move::determineCriticalHitFromLevel(4, 1.0).should.be.false
describe "a powder move", ->
it "does not affect Grass-types", ->
shared.create.call(this, gen: 'xy')
powderMove = @battle.findMove((m) -> m.hasFlag("powder"))
@p2.types.push("Grass")
mock1 = @sandbox.mock(powderMove).expects('hit').never()
mock2 = @sandbox.mock(powderMove).expects('fail').once()
@battle.performMove(@p1, powderMove)
mock1.verify()
mock2.verify()
it "affects non-Grass-types", ->
shared.create.call(this, gen: 'xy')
powderMove = @battle.findMove((m) -> m.hasFlag("powder"))
@p2.types = [ "Normal" ]
mock1 = @sandbox.mock(powderMove).expects('hit').once()
mock2 = @sandbox.mock(powderMove).expects('fail').never()
@battle.performMove(@p1, powderMove)
mock1.verify()
mock2.verify()
describe "Dragon Pulse", ->
it "has 85 base power now", ->
shared.create.call(this, gen: 'xy')
@battle.getMove('Dragon Pulse').power.should.equal(85)
describe 'Hidden Power', ->
it "always has 60 base power", ->
shared.create.call(this, gen: 'xy')
hiddenPower = @battle.getMove('Hidden Power')
hiddenPower.power.should.equal(60)
hiddenPower.basePower(@battle, @p1, @p2).should.equal(60)
describe "Facade", ->
it "does not cut attack in half when burned", ->
shared.create.call(this, gen: 'xy')
facade = @battle.getMove('Facade')
facade.burnCalculation(@p1).should.equal(1)
@p1.attach(Status.Burn)
facade.burnCalculation(@p1).should.equal(1)
describe "King's Shield", ->
it "protects against attacks", ->
shared.create.call(this, gen: 'xy')
kingsShield = @battle.getMove("King's Shield")
tackle = @battle.getMove("Tackle")
mock = @sandbox.mock(tackle).expects('hit').never()
@battle.recordMove(@id2, tackle)
@battle.determineTurnOrder()
@battle.performMove(@p1, kingsShield)
@battle.performMove(@p2, tackle)
mock.verify()
it "does not protect against non-damaging moves", ->
shared.create.call(this, gen: 'xy')
kingsShield = @battle.getMove("King's Shield")
willOWisp = @battle.getMove("Will-O-Wisp")
mock = @sandbox.mock(willOWisp).expects('hit').once()
@battle.recordMove(@id2, willOWisp)
@battle.determineTurnOrder()
@battle.performMove(@p1, kingsShield)
@battle.performMove(@p2, willOWisp)
mock.verify()
it "does not protect against attacks it is immune to", ->
shared.create.call(this, gen: 'xy')
@p1.types = [ 'Ghost' ]
kingsShield = @battle.getMove("King's Shield")
tackle = @battle.getMove("Tackle")
mock = @sandbox.mock(tackle).expects('hit').never()
@battle.recordMove(@id2, tackle)
@battle.determineTurnOrder()
@battle.performMove(@p1, kingsShield)
@battle.performMove(@p2, tackle)
mock.verify()
@p2.stages.should.containEql(attack: 0)
it "sharply lowers attacker's Attack if move was a contact move", ->
shared.create.call(this, gen: 'xy')
kingsShield = @battle.getMove("King's Shield")
tackle = @battle.getMove("Tackle")
@battle.recordMove(@id2, tackle)
@battle.determineTurnOrder()
@battle.performMove(@p1, kingsShield)
@p2.stages.attack.should.equal(0)
@battle.performMove(@p2, tackle)
@p2.stages.attack.should.equal(-2)
it "does not lower attacker's Attack if move was not a contact move", ->
shared.create.call(this, gen: 'xy')
kingsShield = @battle.getMove("King's Shield")
ember = @battle.getMove("Ember")
@battle.recordMove(@id2, ember)
@battle.determineTurnOrder()
@battle.performMove(@p1, kingsShield)
@p2.stages.attack.should.equal(0)
@battle.performMove(@p2, ember)
@p2.stages.attack.should.equal(0)
describe "Spiky Shield", ->
it "protects against attacks", ->
shared.create.call(this, gen: 'xy')
spikyShield = @battle.getMove("Spiky Shield")
tackle = @battle.getMove("Tackle")
mock = @sandbox.mock(tackle).expects('hit').never()
@battle.recordMove(@id2, tackle)
@battle.determineTurnOrder()
@battle.performMove(@p1, spikyShield)
@battle.performMove(@p2, tackle)
mock.verify()
it "protects against non-damaging moves", ->
shared.create.call(this, gen: 'xy')
spikyShield = @battle.getMove("Spiky Shield")
willOWisp = @battle.getMove("Will-O-Wisp")
mock = @sandbox.mock(willOWisp).expects('hit').never()
@battle.recordMove(@id2, willOWisp)
@battle.determineTurnOrder()
@battle.performMove(@p1, spikyShield)
@battle.performMove(@p2, willOWisp)
mock.verify()
it "does not protect against attacks it is immune to", ->
shared.create.call(this, gen: 'xy')
@p1.types = [ 'Ghost' ]
spikyShield = @battle.getMove("Spiky Shield")
tackle = @battle.getMove("Tackle")
mock = @sandbox.mock(tackle).expects('hit').never()
@battle.recordMove(@id2, tackle)
@battle.determineTurnOrder()
@battle.performMove(@p1, spikyShield)
@battle.performMove(@p2, tackle)
mock.verify()
@p2.stages.should.containEql(attack: 0)
it "damages attacker by 1/8 if move was a contact move", ->
shared.create.call(this, gen: 'xy')
spikyShield = @battle.getMove("Spiky Shield")
tackle = @battle.getMove("Tackle")
@battle.recordMove(@id2, tackle)
@battle.determineTurnOrder()
@battle.performMove(@p1, spikyShield)
@p2.currentHP.should.not.be.lessThan(@p2.stat('hp'))
@battle.performMove(@p2, tackle)
(@p2.stat('hp') - @p2.currentHP).should.equal(@p2.stat('hp') >> 3)
it "does not damage attacker if move was not a contact move", ->
shared.create.call(this, gen: 'xy')
spikyShield = @battle.getMove("Spiky Shield")
ember = @battle.getMove("Ember")
@battle.recordMove(@id2, ember)
@battle.determineTurnOrder()
@battle.performMove(@p1, spikyShield)
@p2.currentHP.should.not.be.lessThan(@p2.stat('hp'))
@battle.performMove(@p2, ember)
@p2.currentHP.should.not.be.lessThan(@p2.stat('hp'))
describe "Sticky Web", ->
shared.shouldFailIfUsedTwice("Sticky Web", gen: 'xy')
it "lowers a pokemon's speed by 1 when switching in", ->
shared.create.call(this, gen: 'xy', team2: (Factory("Magikarp") for x in [0..1]))
stickyWeb = @battle.getMove("Sticky Web")
@battle.performMove(@p1, stickyWeb)
@battle.performSwitch(@p2, 1)
@team2.first().stages.should.containEql(speed: -1)
it "doesn't lower a pokemon's speed by 1 if immune to ground", ->
shared.create.call(this, gen: 'xy', team2: [ Factory("Magikarp"), Factory("Gyarados") ])
stickyWeb = @battle.getMove("Sticky Web")
@battle.performMove(@p1, stickyWeb)
@battle.performSwitch(@p2, 1)
@team2.first().stages.should.containEql(speed: 0)
describe "Rapid Spin", ->
it "removes Sticky Web", ->
shared.create.call(this, gen: 'xy')
stickyWeb = @battle.getMove("Sticky Web")
rapidSpin = @battle.getMove("Rapid Spin")
@battle.performMove(@p1, stickyWeb)
@team2.has(Attachment.StickyWeb).should.be.true
@battle.performMove(@p2, rapidSpin)
@team2.has(Attachment.StickyWeb).should.be.false
describe "Defog", ->
it "removes Sticky Web as well", ->
shared.create.call(this, gen: 'xy')
defog = @battle.getMove("Defog")
@battle.performMove(@p1, @battle.getMove("Sticky Web"))
@p2.team.has(Attachment.StickyWeb).should.be.true
@battle.performMove(@p1, defog)
@p2.team.has(Attachment.StickyWeb).should.be.false
it "removes hazards from both sides of the field now", ->
shared.create.call(this, gen: 'xy')
defog = @battle.getMove("Defog")
@battle.performMove(@p1, @battle.getMove("Sticky Web"))
@battle.performMove(@p2, @battle.getMove("Sticky Web"))
@p1.team.has(Attachment.StickyWeb).should.be.true
@p2.team.has(Attachment.StickyWeb).should.be.true
@battle.performMove(@p1, defog)
@p1.team.has(Attachment.StickyWeb).should.be.false
@p2.team.has(Attachment.StickyWeb).should.be.false
it "removes screens from only the target's side of the field", ->
shared.create.call(this, gen: 'xy')
defog = @battle.getMove("Defog")
@battle.performMove(@p1, @battle.getMove("Reflect"))
@battle.performMove(@p1, @battle.getMove("Light Screen"))
@battle.performMove(@p2, @battle.getMove("Reflect"))
@battle.performMove(@p2, @battle.getMove("Light Screen"))
@p1.team.has(Attachment.Reflect).should.be.true
@p1.team.has(Attachment.LightScreen).should.be.true
@p2.team.has(Attachment.Reflect).should.be.true
@p2.team.has(Attachment.LightScreen).should.be.true
@battle.performMove(@p1, defog)
@p1.team.has(Attachment.Reflect).should.be.true
@p1.team.has(Attachment.LightScreen).should.be.true
@p2.team.has(Attachment.Reflect).should.be.false
@p2.team.has(Attachment.LightScreen).should.be.false
describe "Knock Off", ->
it "has x1.0 power if the pokemon has no item", ->
shared.create.call(this, gen: 'xy')
knockOff = @battle.getMove("Knock Off")
knockOff.basePower(@battle, @p1, @p2).should.equal(knockOff.power)
it "has x1.5 power if the item can be knocked off", ->
shared.create.call this,
gen: 'xy'
team2: [Factory("Magikarp", item: "Leftovers")]
knockOff = @battle.getMove("Knock Off")
basePower = knockOff.basePower(@battle, @p1, @p2)
basePower.should.equal Math.floor(1.5 * knockOff.power)
it "has x1.0 power if the item cannot be knocked off", ->
shared.create.call this,
gen: 'xy'
team2: [Factory("Magikarp", item: "Air Mail")]
knockOff = @battle.getMove("Knock Off")
knockOff.basePower(@battle, @p1, @p2).should.equal(knockOff.power)
it "has x1.5 power if item can be knocked off but owner has Sticky Hold", ->
shared.create.call this,
gen: 'xy'
team2: [Factory("Magikarp", item: "Leftovers", ability: "Sticky Hold")]
knockOff = @battle.getMove("Knock Off")
basePower = knockOff.basePower(@battle, @p1, @p2)
basePower.should.equal Math.floor(1.5 * knockOff.power)
describe "Protect-like moves", ->
it "determines success chance using a power of 3 instead of 2", ->
shared.create.call(this, gen: 'xy')
for x in [0..7]
attachment = @p1.attach(Attachment.ProtectCounter)
attachment.successChance().should.equal Math.pow(3, x)
attachment = @p1.attach(Attachment.ProtectCounter)
attachment.successChance().should.equal Math.pow(2, 32)
describe "Freeze-Dry", ->
it "is 2x effective against Water-types", ->
shared.create.call(this, gen: 'xy')
@p2.types = [ "Water" ]
freezeDry = @battle.getMove('Freeze-Dry')
spy = @sandbox.spy(freezeDry, 'typeEffectiveness')
@battle.performMove(@p1, freezeDry)
spy.returned(2).should.be.true
it "is 2x effective against Water-types with Normalize", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Normalize")]
@p2.types = [ "Water" ]
freezeDry = @battle.getMove('Freeze-Dry')
spy = @sandbox.spy(freezeDry, 'typeEffectiveness')
@battle.performMove(@p1, freezeDry)
spy.returned(2).should.be.true
it "is normally effective against other types", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp")]
@p2.types = [ "Fire" ]
freezeDry = @battle.getMove('Freeze-Dry')
spy = @sandbox.spy(freezeDry, 'typeEffectiveness')
@battle.performMove(@p1, freezeDry)
spy.returned(.5).should.be.true
describe "Substitute", ->
it "is bypassed by voice moves", ->
shared.create.call(this, gen: 'xy')
@p2.attach(Attachment.Substitute, hp: (@p1.currentHP >> 2))
voiceMove = @battle.findMove (m) ->
!m.isNonDamaging() && m.hasFlag("sound")
spy = @sandbox.spy(voiceMove, 'hit')
@battle.performMove(@p1, voiceMove)
spy.calledOnce.should.be.true
@p2.currentHP.should.be.lessThan(@p2.stat('hp'))
it "is bypassed by Infiltrator", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Magikarp", ability: "Infiltrator")]
@p2.attach(Attachment.Substitute, hp: (@p1.currentHP >> 2))
tackle = @battle.getMove("Tackle")
spy = @sandbox.spy(tackle, 'hit')
@battle.performMove(@p1, tackle)
spy.calledOnce.should.be.true
@p2.currentHP.should.be.lessThan(@p2.stat('hp'))
it "is bypassed by Infiltrator even on status moves", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Magikarp", ability: "Infiltrator")]
@p2.attach(Attachment.Substitute, hp: (@p1.currentHP >> 2))
toxic = @battle.getMove("Toxic")
spy = @sandbox.spy(toxic, 'hit')
@battle.performMove(@p1, toxic)
spy.calledOnce.should.be.true
@p2.has(Status.Toxic).should.be.true
it "does not block Knock Off + Infiltrator", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Magikarp", ability: "Infiltrator")]
team2: [ Factory("Magikarp", item: "Leftovers")]
@p2.attach(Attachment.Substitute, hp: (@p1.currentHP >> 2))
knockOff = @battle.getMove("Knock Off")
spy = @sandbox.spy(knockOff, 'hit')
@battle.performMove(@p1, knockOff)
spy.calledOnce.should.be.true
@p2.hasItem().should.be.false
it "does not block secondary effects + Infiltrator", ->
shared.create.call this,
gen: 'xy'
team1: [ Factory("Magikarp", ability: "Infiltrator")]
shared.biasRNG.call(this, "next", "secondary effect", 0) # always burn
@p2.attach(Attachment.Substitute, hp: (@p1.currentHP >> 2))
flamethrower = @battle.getMove('Flamethrower')
spy = @sandbox.spy(flamethrower, 'hit')
@battle.performMove(@p1, flamethrower)
spy.calledOnce.should.be.true
@p2.has(Status.Burn).should.be.true
testChargeMove = (moveName, vulnerable) ->
describe moveName, ->
it "chooses the player's next action for them", ->
shared.create.call(this, gen: 'xy')
move = @battle.getMove(moveName)
@p1.moves = [ move ]
@battle.recordMove(@id1, move)
@battle.continueTurn()
@battle.endTurn()
@battle.beginTurn()
@battle.requests.should.not.have.property(@id1)
should.exist(@battle.getAction(@p1))
it "only spends 1 PP for the entire attack", ->
shared.create.call(this, gen: 'xy')
move = @battle.getMove(moveName)
@p1.moves = [ move ]
@p1.resetAllPP()
pp = @p1.pp(move)
@battle.recordMove(@id1, move)
@battle.continueTurn()
@p1.pp(move).should.equal(pp)
@battle.beginTurn()
@battle.continueTurn()
@p1.pp(move).should.equal(pp - 1)
it "skips the charge turn if the user is holding a Power Herb", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", item: "Power Herb")]
move = @battle.getMove(moveName)
@p1.hasItem("Power Herb").should.be.true
mock = @sandbox.mock(move).expects('execute').once()
@battle.recordMove(@id1, move)
@battle.continueTurn()
mock.verify()
@p1.hasItem().should.be.false
if vulnerable?.length?
it "makes target invulnerable to moves", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", evs: {speed: 4})]
move = @battle.getMove(moveName)
swift = @battle.getMove("Swift")
@battle.recordMove(@id1, move)
@battle.recordMove(@id2, swift)
mock = @sandbox.mock(swift).expects('hit').never()
@battle.continueTurn()
mock.verify()
it "makes target invulnerable to moves *after* use", ->
shared.create.call this,
gen: 'xy'
team2: [Factory("Magikarp", evs: {speed: 4})]
move = @battle.getMove(moveName)
tackle = @battle.getMove("Tackle")
@battle.recordMove(@id1, move)
@battle.recordMove(@id2, tackle)
mock = @sandbox.mock(tackle).expects('hit').once()
@battle.continueTurn()
mock.verify()
it "is vulnerable to attacks from a No Guard pokemon", ->
shared.create.call this,
gen: 'xy'
team2: [Factory("Magikarp", ability: "No Guard")]
move = @battle.getMove(moveName)
tackle = @battle.getMove("Tackle")
@battle.recordMove(@id1, move)
@battle.recordMove(@id2, tackle)
mock = @sandbox.mock(tackle).expects('hit').once()
@battle.continueTurn()
mock.verify()
it "is vulnerable to attacks if locked on", ->
shared.create.call(this, gen: 'xy')
@battle.performMove(@p1, @battle.getMove("Lock-On"))
@battle.performMove(@p2, @battle.getMove(moveName))
tackle = @battle.getMove("Tackle")
@battle.recordMove(@id1, tackle)
mock = @sandbox.mock(tackle).expects('hit').once()
@battle.continueTurn()
mock.verify()
for vulnerableMove in vulnerable
it "is vulnerable to #{vulnerableMove}", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", evs: {speed: 4})]
move = @battle.getMove(moveName)
vulnerable = @battle.getMove(vulnerableMove)
@battle.recordMove(@id1, move)
@battle.recordMove(@id2, vulnerable)
mock = @sandbox.mock(vulnerable).expects('hit').once()
@battle.continueTurn()
mock.verify()
else # no vulnerable moves
it "doesn't make target invulnerable to moves", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", evs: {speed: 4})]
move = @battle.getMove(moveName)
tackle = @battle.getMove("Tackle")
@battle.recordMove(@id1, move)
@battle.recordMove(@id2, tackle)
mock = @sandbox.mock(tackle).expects('hit').once()
@battle.continueTurn()
mock.verify()
testChargeMove('Fly', ["Gust", "Thunder", "Twister", "Sky Uppercut", "Hurricane", "Smack Down", "Thousand Arrows"])
testChargeMove('Bounce', ["Gust", "Thunder", "Twister", "Sky Uppercut", "Hurricane", "Smack Down", "Thousand Arrows"])
testChargeMove('Geomancy')
testChargeMove('Phantom Force', [])
describe "Toxic", ->
it "cannot miss when used by a Poison type pokemon", ->
shared.create.call(this, gen: 'xy')
@p1.types = [ "Poison" ]
@p2.types = [ "Normal" ]
shared.biasRNG.call(this, "randInt", 'miss', 101)
toxic = @battle.getMove("Toxic")
toxic.willMiss(@battle, @p1, @p2).should.be.false
describe "Parting Shot", ->
it "reduces the attack and special attack of the target by two stages", ->
shared.create.call(this, gen: 'xy')
@battle.performMove(@p1, @battle.getMove("Parting Shot"))
@p2.stages.should.containEql attack: -1, specialAttack: -1
it "forces the owner to switch", ->
shared.create.call(this, gen: 'xy')
@battle.performMove(@p1, @battle.getMove("Parting Shot"))
@battle.requests.should.have.property @id1
describe "Worry Seed", ->
it "does not change some abilities", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Smeargle")]
team2: [Factory("Aegislash", ability: "Stance Change")]
worrySeed = @battle.getMove("Worry Seed")
mock = @sandbox.mock(worrySeed).expects('fail').once()
@battle.performMove(@p1, worrySeed)
mock.verify()
describe "Simple Beam", ->
it "does not change some abilities", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Smeargle")]
team2: [Factory("Aegislash", ability: "Stance Change")]
simpleBeam = @battle.getMove("Simple Beam")
mock = @sandbox.mock(simpleBeam).expects('fail').once()
@battle.performMove(@p1, simpleBeam)
mock.verify()
testTrappingMove = (name) ->
describe name, ->
it "deals 1/8 of the pokemon's max hp every turn", ->
shared.create.call this,
gen: 'xy'
team2: [Factory("Blissey")]
@battle.performMove(@p1, @battle.getMove(name))
@p2.currentHP = @p2.stat('hp')
@battle.endTurn()
maxHP = @p2.stat('hp')
expected = maxHP - Math.floor(maxHP / 8)
@p2.currentHP.should.equal expected
it "deals 1/6 of the pokemon's max hp every turn if the user is holding a Binding Band", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", item: "Binding Band")]
team2: [Factory("Blissey")]
@battle.performMove(@p1, @battle.getMove(name))
@p2.currentHP = @p2.stat('hp')
@battle.endTurn()
maxHP = @p2.stat('hp')
expected = maxHP - Math.floor(maxHP / 6)
@p2.currentHP.should.equal expected
testTrappingMove "Bind"
testTrappingMove "Clamp"
testTrappingMove "Fire Spin"
testTrappingMove "Infestation"
testTrappingMove "Magma Storm"
testTrappingMove "Sand Tomb"
testTrappingMove "Wrap"
describe "Entrainment", ->
it "does not change some abilities", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Swift Swim")]
team2: [Factory("Aegislash", ability: "Stance Change")]
entrainment = @battle.getMove("Entrainment")
mock = @sandbox.mock(entrainment).expects('fail').once()
@battle.performMove(@p1, entrainment)
mock.verify()
describe "Nature Power", ->
it "uses Tri Attack in Wi-Fi battles", ->
shared.create.call(this, gen: 'xy')
naturePower = @battle.getMove('Nature Power')
triAttack = @battle.getMove('Tri Attack')
mock = @sandbox.mock(triAttack).expects('execute').once()
.withArgs(@battle, @p1, [ @p2 ])
@battle.performMove(@p1, naturePower)
mock.verify()
describe "Venom Drench", ->
it "lowers the target's attack, special attack, and speed by 1 stage if it is poisoned", ->
shared.create.call(this, gen: 'xy')
@p2.attach(Status.Poison)
@battle.performMove(@p1, @battle.getMove('Venom Drench'))
@p2.stages.should.containEql attack: -1, specialAttack: -1, speed: -1
it "fails if the target isn't poisoned", ->
shared.create.call(this, gen: 'xy')
venomDrench = @battle.getMove("Venom Drench")
mock = @sandbox.mock(venomDrench).expects('fail').once()
@battle.performMove(@p1, venomDrench)
mock.verify()
describe "Topsy-Turvy", ->
it "reverses the target's boosts", ->
shared.create.call(this, gen: 'xy')
@p2.stages.attack = 2
@p2.stages.defense = -3
@p2.stages.speed = 0
@battle.performMove(@p1, @battle.getMove('Topsy-Turvy'))
@p2.stages.should.containEql attack: -2
@p2.stages.should.containEql defense: 3
@p2.stages.should.containEql speed: 0
it "fails if the target has no boosts", ->
shared.create.call(this, gen: 'xy')
topsyTurvy = @battle.getMove('Topsy-Turvy')
mock = @sandbox.mock(topsyTurvy).expects('fail').once()
@battle.performMove(@p1, topsyTurvy)
mock.verify()
describe "Fell Stinger", ->
it "raises the user's Attack 2 stages if the target faints", ->
shared.create.call(this, gen: 'xy')
@p2.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Fell Stinger"))
@p1.stages.should.containEql attack: 2
it "does not raise the user's Attack 2 stages otherwise", ->
shared.create.call(this, gen: 'xy')
@battle.performMove(@p1, @battle.getMove("Fell Stinger"))
@p1.stages.should.containEql attack: 0
describe "Skill Swap", ->
it "can swap the abilities if they are the same", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Swift Swim")]
team2: [Factory("Magikarp", ability: "Swift Swim")]
skillSwap = @battle.getMove("Skill Swap")
mock = @sandbox.mock(skillSwap).expects('fail').never()
@battle.performMove(@p1, skillSwap)
mock.verify()
describe "Metronome", ->
it "reselects if chosen an illegal move", ->
shared.create.call(this, gen: 'xy')
@p1.moves = [ metronome ]
metronome = @battle.getMove("Metronome")
belch = @battle.getMove("Belch")
tackle = @battle.getMove("Tackle")
index = @battle.MoveList.indexOf(belch)
reselectIndex = @battle.MoveList.indexOf(tackle)
shared.biasRNG.call(this, 'randInt', "metronome", index)
shared.biasRNG.call(this, 'randInt', "metronome reselect", reselectIndex)
mock = @sandbox.mock(tackle).expects('execute').once()
@battle.performMove(@p1, metronome)
mock.verify()
testDelayedAttackMove = (moveName, type) ->
describe moveName, ->
it "does not hit substitutes if the user has Infiltrator and is active", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Infiltrator")]
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
@p2.attach(Attachment.Substitute, hp: 1)
@battle.endTurn()
@battle.endTurn()
@p2.has(Attachment.Substitute).should.be.true
@battle.endTurn()
@p2.has(Attachment.Substitute).should.be.true
it "always hits substitutes if the user is not on the field", ->
shared.create.call this,
gen: 'xy'
team1: [Factory("Magikarp", ability: "Infiltrator"), Factory("Magikarp")]
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
@p2.attach(Attachment.Substitute, hp: 1)
@battle.endTurn()
@battle.performSwitch(@p1, 1)
@battle.endTurn()
@p2.has(Attachment.Substitute).should.be.true
@battle.endTurn()
@p2.has(Attachment.Substitute).should.be.false
testDelayedAttackMove("Future Sight")
testDelayedAttackMove("Doom Desire")

70
test/xy/pokemon.coffee Normal file
View File

@@ -0,0 +1,70 @@
{Weather} = require('../../shared/weather')
{Pokemon} = require('../../server/xy/pokemon')
{Protocol} = require '../../shared/protocol'
{Factory} = require '../factory'
should = require 'should'
shared = require '../shared'
require '../helpers'
describe "XY Pokemon:", ->
describe '#canMegaEvolve', ->
it "returns true if holding its associated mega stone", ->
pokemon = new Pokemon(species: "Charizard", item: "Charizardite Y")
pokemon.canMegaEvolve().should.be.true
it "returns false if holding a mega stone meant for another pokemon", ->
pokemon = new Pokemon(species: "Charizard", item: "Abomasite")
pokemon.canMegaEvolve().should.be.false
it "returns false if holding a random item", ->
pokemon = new Pokemon(species: "Charizard", item: "Leftovers")
pokemon.canMegaEvolve().should.be.false
it "returns false if holding no item", ->
pokemon = new Pokemon(species: "Charizard")
pokemon.canMegaEvolve().should.be.false
it "returns false if already another forme", ->
pokemon = new Pokemon(species: "Charizard", item: "Charizardite X")
pokemon.changeForme("mega-x")
pokemon.canMegaEvolve().should.be.false
it "returns false if the team has already mega evolved", ->
shared.create.call this,
gen: 'xy'
team1: (Factory("Charizard", item: "Charizardite X") for x in [0..1])
@team1.first().changeForme("mega-x")
@team1.at(1).canMegaEvolve().should.be.false
it "returns true if the team has not already mega evolved", ->
shared.create.call this,
gen: 'xy'
team1: (Factory("Charizard", item: "Charizardite X") for x in [0..1])
@team1.at(1).canMegaEvolve().should.be.true
describe "#blockSwitch", ->
it "does not block switches if the Pokemon has a Ghost type", ->
pokemon = new Pokemon(species: "Gengar")
pokemon.blockSwitch()
pokemon.isSwitchBlocked().should.be.false
it "acts normally otherwise", ->
pokemon = new Pokemon(species: "Charizard")
pokemon.blockSwitch()
pokemon.isSwitchBlocked().should.be.true
describe "#hasTakeableItem", ->
it "returns false if the pokemon holds a mega stone for its species", ->
pokemon = new Pokemon(species: "Gengar", item: "Gengarite")
pokemon.hasTakeableItem().should.be.false
it "returns true if the pokemon holds a mega stone for another species", ->
pokemon = new Pokemon(species: "Gengar", item: "Kangaskhanite")
pokemon.hasTakeableItem().should.be.true
it "still uses old BW conditions", ->
pokemon = new Pokemon(species: "Gengar", item: "Leftovers")
pokemon.hasTakeableItem().should.be.true
pokemon = new Pokemon(species: "Gengar")
pokemon.hasTakeableItem().should.be.false

35
test/xy/priorities.coffee Normal file
View File

@@ -0,0 +1,35 @@
{Ability} = require('../../server/xy/data/abilities')
{Item} = require('../../server/xy/data/items')
{Attachment, Status} = require('../../server/xy/attachment')
Priorities = require('../../server/xy/priorities')
Query = require('../../server/xy/queries')
shared = require('../shared')
require '../helpers'
describe "XY Priorities:", ->
ensureAttachments = (arrayOfAttachments, eventName) ->
attachments = (a for a in arrayOfAttachments when a.prototype[eventName]? && a not in Priorities[eventName])
attachments = attachments.map((a) -> a.displayName || a::name)
if attachments.length > 0
throw new Error("#{attachments.join(', ')} must specify their #{eventName} priority.")
it "ensures all relevant attachments have their specified event names", ->
for eventName of Priorities
ensureAttachments((klass for name, klass of Attachment), eventName)
ensureAttachments((klass for name, klass of Item), eventName)
ensureAttachments((klass for name, klass of Ability), eventName)
describe "Queries", ->
it "execute priorities in order", ->
shared.create.call(this)
@battle.attach(Attachment.TrickRoom)
@team2.attach(Attachment.Reflect)
@p1.attach(Attachment.Ingrain)
spy1 = @sandbox.spy(Attachment.TrickRoom.prototype, 'endTurn')
spy2 = @sandbox.spy(Attachment.Reflect.prototype, 'endTurn')
spy3 = @sandbox.spy(Attachment.Ingrain.prototype, 'endTurn')
Query("endTurn", @battle.getAllAttachments())
spy3.calledBefore(spy2).should.be.true
spy2.calledBefore(spy1).should.be.true

22
test/xy/util.coffee Normal file
View File

@@ -0,0 +1,22 @@
util = require '../../server/xy/util'
should = require 'should'
require '../helpers'
describe "XY utility functions:", ->
it "inherits from BW", ->
should.exist(util)
util.should.have.property('roundHalfDown')
it "adds a new Fairy type", ->
should.exist(util.Type)
should.exist(util.Type.Fairy)
it "adds strengths and weaknesses of the new Fairy type", ->
util.typeEffectiveness("Fairy", [ "Dragon" ]).should.equal(2)
util.typeEffectiveness("Dragon", [ "Fairy" ]).should.equal(0)
it "removes Steel's resistances to Ghost", ->
util.typeEffectiveness("Ghost", [ "Steel" ]).should.equal(1)
it "removes Steel's resistances to Dark", ->
util.typeEffectiveness("Dark", [ "Steel" ]).should.equal(1)