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:
92
test/alts_spec.coffee
Normal file
92
test/alts_spec.coffee
Normal 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
166
test/api/api_spec.coffee
Normal 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()
|
||||
224
test/battle_queue_spec.coffee
Normal file
224
test/battle_queue_spec.coffee
Normal 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
2778
test/bw/abilities.coffee
Normal file
File diff suppressed because it is too large
Load Diff
68
test/bw/attachments.coffee
Normal file
68
test/bw/attachments.coffee
Normal 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
477
test/bw/battle.coffee
Normal 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)
|
||||
139
test/bw/battle_controller.coffee
Normal file
139
test/bw/battle_controller.coffee
Normal 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
|
||||
567
test/bw/battle_engine.coffee
Normal file
567
test/bw/battle_engine.coffee
Normal 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
163
test/bw/battles.coffee
Normal 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
1509
test/bw/items.coffee
Normal file
File diff suppressed because it is too large
Load Diff
7723
test/bw/moves.coffee
Normal file
7723
test/bw/moves.coffee
Normal file
File diff suppressed because it is too large
Load Diff
492
test/bw/pokemon.coffee
Normal file
492
test/bw/pokemon.coffee
Normal 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
35
test/bw/priorities.coffee
Normal 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
142
test/factory.coffee
Normal 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
89
test/glicko2_spec.coffee
Normal 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
19
test/helpers.coffee
Normal 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
273
test/learnsets_spec.coffee
Normal 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
27
test/logger_spec.coffee
Normal 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
3
test/mocha.opts
Normal file
@@ -0,0 +1,3 @@
|
||||
--require should
|
||||
--compilers coffee:coffee-script/register
|
||||
--recursive
|
||||
53
test/pokebattle_values_spec.coffee
Normal file
53
test/pokebattle_values_spec.coffee
Normal 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
139
test/rating_spec.coffee
Normal 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()
|
||||
16
test/server/auth_spec.coffee
Normal file
16
test/server/auth_spec.coffee
Normal 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()
|
||||
481
test/server/commands_spec.coffee
Normal file
481
test/server/commands_spec.coffee
Normal 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()
|
||||
54
test/server/conditions/evasion_clause_spec.coffee
Normal file
54
test/server/conditions/evasion_clause_spec.coffee
Normal 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()
|
||||
45
test/server/conditions/ohko_clause_spec.coffee
Normal file
45
test/server/conditions/ohko_clause_spec.coffee
Normal 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()
|
||||
53
test/server/conditions/pbv_1000_spec.coffee
Normal file
53
test/server/conditions/pbv_1000_spec.coffee
Normal 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
|
||||
53
test/server/conditions/pbv_500_spec.coffee
Normal file
53
test/server/conditions/pbv_500_spec.coffee
Normal 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
|
||||
32
test/server/conditions/prankster_swagger_clause_spec.coffee
Normal file
32
test/server/conditions/prankster_swagger_clause_spec.coffee
Normal 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
|
||||
95
test/server/conditions/sleep_clause_spec.coffee
Normal file
95
test/server/conditions/sleep_clause_spec.coffee
Normal 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
|
||||
34
test/server/conditions/species_clause_spec.coffee
Normal file
34
test/server/conditions/species_clause_spec.coffee
Normal 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
|
||||
136
test/server/conditions/team_preview_spec.coffee
Normal file
136
test/server/conditions/team_preview_spec.coffee
Normal 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)
|
||||
190
test/server/conditions/timer_spec.coffee
Normal file
190
test/server/conditions/timer_spec.coffee
Normal 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)
|
||||
70
test/server/conditions/unreleased_ban_spec.coffee
Normal file
70
test/server/conditions/unreleased_ban_spec.coffee
Normal 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()
|
||||
76
test/server/rooms_spec.coffee
Normal file
76
test/server/rooms_spec.coffee
Normal 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
|
||||
57
test/server/scheduler_spec.coffee
Normal file
57
test/server/scheduler_spec.coffee
Normal 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
824
test/server_spec.coffee
Normal 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
96
test/shared.coffee
Normal 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
|
||||
}
|
||||
272
test/team_parsing_spec.coffee
Normal file
272
test/team_parsing_spec.coffee
Normal 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
485
test/xy/abilities.coffee
Normal 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
|
||||
21
test/xy/attachments.coffee
Normal file
21
test/xy/attachments.coffee
Normal 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
117
test/xy/battle.coffee
Normal 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
|
||||
20
test/xy/battle_engine.coffee
Normal file
20
test/xy/battle_engine.coffee
Normal 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
140
test/xy/items.coffee
Normal 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
723
test/xy/moves.coffee
Normal 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
70
test/xy/pokemon.coffee
Normal 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
35
test/xy/priorities.coffee
Normal 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
22
test/xy/util.coffee
Normal 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)
|
||||
Reference in New Issue
Block a user