BattleSim/test/bw/moves.coffee

7724 lines
270 KiB
CoffeeScript

require '../helpers'
{Attachment, Status} = require('../../server/bw/attachment')
{Battle} = require('../../server/bw/battle')
{Pokemon} = require('../../server/bw/pokemon')
{Weather} = require('../../shared/weather')
{Move} = require('../../server/bw/move')
util = require '../../server/bw/util'
{Protocol} = require '../../shared/protocol'
{Factory} = require '../factory'
should = require 'should'
{_} = require 'underscore'
shared = require '../shared'
describe 'Move:', ->
# Test every single move for their primary effects.
shared.testEveryMove(Battle::MoveList, 'bw')
it "multiplies critical hits by 2x", ->
Move::criticalMultiplier.should.equal(2)
it 'takes the name as the first parameter', ->
new Move('Smooth Move').name.should.equal 'Smooth Move'
it 'should have a priority of 0 by default', ->
new Move().priority.should.equal 0
it 'can change the default priority', ->
new Move(null, priority: -1).priority.should.equal -1
it 'should have a type of ??? by default', ->
new Move().type.should.equal '???'
it 'can change the default type', ->
new Move(null, type: 'Grass').type.should.equal 'Grass'
describe 'critical hit level', ->
it "is 1 by default", ->
battle = new Battle('1', [])
attacker = new Pokemon()
defender = new Pokemon()
attacker.activate()
new Move().criticalHitLevel(battle, attacker, defender).should.equal 1
it "can be changed from the default", ->
battle = new Battle('1', [])
attacker = new Pokemon()
defender = new Pokemon()
attacker.activate()
new Move(null, criticalHitLevel: 2)
.criticalHitLevel(battle, attacker, defender).should.equal 2
it "is 3 if the attacker has Focus Energy", ->
battle = new Battle('1', [])
attacker = new Pokemon()
defender = new Pokemon()
attacker.attach(Attachment.FocusEnergy)
attacker.activate()
new Move().criticalHitLevel(battle, attacker, defender).should.equal 3
it "is 3 if the attacker is Farfetch'd with a Stick", ->
battle = new Battle('1', [])
attacker = new Pokemon(species: "Farfetch'd", item: 'Stick')
defender = new Pokemon()
attacker.activate()
new Move().criticalHitLevel(battle, attacker, defender).should.equal 3
it "is 3 if the attacker is Chansey with a Lucky Punch", ->
battle = new Battle('1', [])
attacker = new Pokemon(species: "Chansey", item: 'Lucky Punch')
defender = new Pokemon()
attacker.activate()
new Move().criticalHitLevel(battle, attacker, defender).should.equal 3
for item in ["Razor Claw", "Scope Lens"]
do (item) ->
it "is 2 if the attacker has a #{item}", ->
battle = new Battle('1', [])
attacker = new Pokemon(item: item)
defender = new Pokemon()
attacker.activate()
new Move().criticalHitLevel(battle, attacker, defender).should.equal 2
describe "a critical hit", ->
it "occurs when the RNG output < the critical hit level", ->
battle = new Battle('1', [])
attacker = new Pokemon()
defender = new Pokemon()
move = new Move()
@sandbox.stub(move, 'criticalHitLevel', -> 3)
@sandbox.stub(battle.rng, 'next', -> 0.2)
move.isCriticalHit(battle, attacker, defender).should.be.true
it "does not occur when the RNG output >= the critical hit level", ->
battle = new Battle('1', [])
attacker = new Pokemon()
defender = new Pokemon()
@sandbox.stub(battle.rng, 'next', -> 0.0700)
new Move().isCriticalHit(battle, attacker, defender).should.be.false
it "ignores opponent defensive boosts", ->
shared.create.call(this)
defense = @p2.stat('defense')
specialDefense = @p2.stat('specialDefense')
@p1.crit = true
@p2.boost(defense: 1, specialDefense: 2)
move = new Move(null, damage: 'physical')
move.pickDefenseStat(@p1, @p2).should.equal(defense)
move = new Move(null, damage: 'special')
move.pickDefenseStat(@p1, @p2).should.equal(specialDefense)
it "does not ignore opponent defensive drops", ->
shared.create.call(this)
defense = @p2.stat('defense')
specialDefense = @p2.stat('specialDefense')
@p1.crit = true
@p2.boost(defense: -1, specialDefense: -2)
move = new Move(null, damage: 'physical')
move.pickDefenseStat(@p1, @p2).should.equal Math.floor(2 / 3 * defense)
move = new Move(null, damage: 'special')
move.pickDefenseStat(@p1, @p2).should.equal(specialDefense >> 1)
it "ignores user stat drops", ->
shared.create.call(this)
attack = @p1.stat('attack')
specialAttack = @p1.stat('specialAttack')
@p1.crit = true
@p1.boost(attack: -1, specialAttack: -2)
move = new Move(null, damage: 'physical')
move.pickAttackStat(@p1, @p2).should.equal(attack)
move = new Move(null, damage: 'special')
move.pickAttackStat(@p1, @p2).should.equal(specialAttack)
it "does not ignore user stat boosts", ->
shared.create.call(this)
attack = @p1.stat('attack')
specialAttack = @p1.stat('specialAttack')
@p1.crit = true
@p1.boost(attack: 1, specialAttack: 2)
move = new Move(null, damage: 'physical')
move.pickAttackStat(@p1, @p2).should.equal Math.floor(1.5 * attack)
move = new Move(null, damage: 'special')
move.pickAttackStat(@p1, @p2).should.equal(2 * specialAttack)
it "does not carry over in multihit moves", ->
shared.create.call(this)
move = new Move("multihit", minHits: 4, maxHits: 4, power: 20)
# Crit only once
move.isCriticalHit = ->
move.isCriticalHit = -> false
true
mock = @sandbox.mock(@p2).expects('informCriticalHit').once()
move.execute(@battle, @p1, [ @p2 ])
mock.verify()
describe "-1 crit ratios", ->
beforeEach ->
@battle = new Battle('1', [])
@attacker = new Pokemon()
@move = new Move("TestMove", criticalHitLevel: -1)
@sandbox.stub(@battle.rng, 'next', -> 1)
it "fails if the defender has Battle Armor", ->
defender = new Pokemon(ability: 'Battle Armor')
@move.isCriticalHit(@battle, @attacker, defender).should.be.false
it "fails if the defender has Shell Armor", ->
defender = new Pokemon(ability: 'Shell Armor')
@move.isCriticalHit(@battle, @attacker, defender).should.be.false
it "always succeeds if opponent does not have Battle or Shell Armor", ->
defender = new Pokemon()
@move.isCriticalHit(@battle, @attacker, defender).should.be.true
describe 'type effectiveness', ->
testTypeEffectiveness = (type, defense, expected) ->
battle = new Battle('1', [])
attacker = new Pokemon()
defender = new Pokemon()
defender.types = defense
move = new Move(null, type: type)
move.typeEffectiveness(battle, attacker, defender).should.equal expected
it 'is 0 if the enemy has an immunity to the type', ->
testTypeEffectiveness('Ground', ['Electric', 'Flying'], 0)
it 'is 1 if the enemy is neutral to the type', ->
testTypeEffectiveness('Ground', ['Normal'], 1)
it 'is 2 if the enemy is weak to the type', ->
testTypeEffectiveness('Ground', ['Electric'], 2)
it 'is 4 if the enemy is extra weak to the type', ->
testTypeEffectiveness('Rock', ['Fire', 'Flying'], 4)
it 'is 1/2 if the enemy is resistant to the type', ->
testTypeEffectiveness('Fire', ['Fire', 'Flying'], .5)
it 'is 1/4 if the enemy is extra resistant to the type', ->
testTypeEffectiveness('Fire', ['Fire', 'Water'], .25)
describe "#burnCalculation", ->
it "returns 1 normally", ->
attacker = new Pokemon()
new Move(null, damage: 'physical')
.burnCalculation(attacker).should.equal 1
it "returns .5 with a physical move, no Guts ability, and a burned user", ->
attacker = new Pokemon()
attacker.attach(Status.Burn)
new Move(null, damage: 'physical')
.burnCalculation(attacker).should.equal .5
it "returns 1 with a special move, no Guts ability, and a burned user", ->
attacker = new Pokemon()
attacker.attach(Status.Burn)
new Move(null, damage: 'special')
.burnCalculation(attacker).should.equal 1
it "returns 1 with a physical move, Guts ability, and a burned user", ->
attacker = new Pokemon(ability: "Guts")
attacker.attach(Status.Burn)
new Move(null, damage: 'physical')
.burnCalculation(attacker).should.equal 1
describe 'an attack with 0 accuracy', ->
it 'can never miss', ->
battle = new Battle('1', [])
a = new Pokemon()
d = new Pokemon()
new Move(accuracy: 0).willMiss(battle, a, d).should.be.false
describe 'accuracy and evasion boosts', ->
it 'heighten and lower the chances of a move hitting', ->
battle = new Battle('1', [])
a = new Pokemon()
d = new Pokemon()
new Move(null, accuracy: 100).chanceToHit(battle, a, d).should.eql 100
a.stages.accuracy = 3
new Move(null, accuracy: 50).chanceToHit(battle, a, d).should.eql 100
a.stages.accuracy = 3
d.stages.evasion = 3
new Move(null, accuracy: 50).chanceToHit(battle, a, d).should.eql 50
it 'keeps heightening and lowering chances when negative', ->
battle = new Battle('1', [])
a = new Pokemon()
d = new Pokemon()
new Move(null, accuracy: 100).chanceToHit(battle, a, d).should.eql 100
a.stages.accuracy = -3
new Move(null, accuracy: 50).chanceToHit(battle, a, d).should.eql 25
a.stages.accuracy = -3
d.stages.evasion = -3
new Move(null, accuracy: 50).chanceToHit(battle, a, d).should.eql 50
describe '#hasFlag', ->
it 'returns true if a move has a specific flag', ->
new Move(null, flags: ['superman', 'batman'])
.hasFlag('batman').should.be.true
it "returns false if a move doesn't have a specific flag", ->
new Move(null, flags: ['superman', 'batman'])
.hasFlag('catwoman').should.be.false
describe '#execute', ->
it 'calls hit multiple times for multihit moves', ->
shared.create.call(this)
move = new Move("multihit", minHits: 4, maxHits: 4)
mock = @sandbox.mock(move).expects('hit').exactly(4)
move.execute(@battle, @p1, [@p2])
mock.verify()
it 'only hits up until user faints', ->
shared.create.call(this, team2: [Factory('Ferrothorn', ability: 'Iron Barbs')])
move = new Move("multihit", minHits: 4, maxHits: 4, flags: ["contact"])
mock = @sandbox.mock(move).expects('hit').exactly(1)
@p1.currentHP = 1
move.execute(@battle, @p1, [@p2])
mock.verify()
it 'only hits up until user falls asleep', ->
shared.create.call(this, team2: [Factory('Shroomish', ability: 'Effect Spore')])
shared.biasRNG.call(this, "randInt", 'effect spore', 4) # Never trigger
move = new Move("multihit", minHits: 4, maxHits: 4, flags: ["contact"])
hit = move.hit
times = 0
@sandbox.stub move, 'hit', =>
times++
if times == 2
shared.biasRNG.call(this, "randInt", 'effect spore', 1) # Sleep
else
shared.biasRNG.call(this, "randInt", 'effect spore', 4) # Nothing
hit.apply(move, arguments)
@p1.currentHP = 1
move.execute(@battle, @p1, [@p2])
times.should.equal(2)
describe "#use", ->
it "returns false if the target's type is immune", ->
shared.create.call(this)
move = new Move("attacking", damage: "special", type: "Normal")
@p2.types = [ "Ghost" ]
move.use(@battle, @p1, @p2).should.be.false
it "returns true if target is immune but move is non-damaging", ->
shared.create.call(this)
move = new Move("attacking", damage: "non-damaging", type: "Normal")
@p2.types = [ "Ghost" ]
should.not.exist move.use(@battle, @p1, @p2)
describe '#hit', ->
it "deals damage no higher than the pokemon's remaining HP", ->
shared.create.call(this)
@p2.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Tackle"))
@p2.currentHP.should.equal(0)
it "deals normal damage if the Pokemon has a substitute", ->
shared.create.call(this)
spy = @sandbox.spy(@p2, 'damage')
@p2.currentHP = (@p2.currentHP >> 2) + 1
@battle.performMove(@p2, @battle.getMove("Substitute"))
@battle.performMove(@p1, @battle.getMove("Tackle"))
spy.returned(1).should.be.false
describe '#weatherModifier', ->
it "is 0x1800 when the move is Fire-type and the weather is sunny", ->
shared.create.call(this)
@battle.setWeather(Weather.SUN)
move = new Move(null, type: "Fire")
move.weatherModifier(@battle, @p1, @p2).should.equal(0x1800)
it "is 0x800 when the move is Fire-type and the weather is rainy", ->
shared.create.call(this)
@battle.setWeather(Weather.RAIN)
move = new Move(null, type: "Fire")
move.weatherModifier(@battle, @p1, @p2).should.equal(0x800)
it "is 0x1800 when the move is Water-type and the weather is rainy", ->
shared.create.call(this)
@battle.setWeather(Weather.RAIN)
move = new Move(null, type: "Water")
move.weatherModifier(@battle, @p1, @p2).should.equal(0x1800)
it "is 0x1800 when the move is Water-type and the weather is sunny", ->
shared.create.call(this)
@battle.setWeather(Weather.SUN)
move = new Move(null, type: "Water")
move.weatherModifier(@battle, @p1, @p2).should.equal(0x800)
it "is 0x1000 otherwise", ->
shared.create.call(this)
@battle.setWeather(Weather.SAND)
move = new Move(null, type: "Fire")
move.weatherModifier(@battle, @p1, @p2).should.equal(0x1000)
describe "#baseDamage", ->
it "reduces damage by ~1.5 if the weather is sandstorm", ->
shared.create.call(this)
@p2.types = [ "Rock" ]
move = new Move(null, power: 50, type: "Fire", damage: 'special')
damage = move.baseDamage(@battle, @p1, @p2)
@battle.setWeather(Weather.SAND)
move.baseDamage(@battle, @p1, @p2).should.be.lessThan(damage)
it "doesn't apply the sandstorm effect if target isn't Rock-type", ->
shared.create.call(this)
@p2.types = [ "Ground" ]
move = new Move(null, power: 50, type: "Fire", damage: 'special')
damage = move.baseDamage(@battle, @p1, @p2)
@battle.setWeather(Weather.SAND)
move.baseDamage(@battle, @p1, @p2).should.equal(damage)
it "doesn't apply the sandstorm effect if move is non-special", ->
shared.create.call(this)
@p2.types = [ "Rock" ]
move = new Move(null, power: 50, type: "Fire", damage: 'physical')
damage = move.baseDamage(@battle, @p1, @p2)
@battle.setWeather(Weather.SAND)
move.baseDamage(@battle, @p1, @p2).should.equal(damage)
describe "BW Moves:", ->
describe "Splash", ->
shared.shouldDoNoDamage('Splash')
describe 'a multi-hit move', ->
it 'potentially activates Berries after each hit', ->
shared.create.call this,
team1: [Factory('Magikarp', item: "Sitrus Berry", ability: "Skill Link")]
team2: [Factory('Magikarp', item: "Sitrus Berry", ability: "Iron Barbs")]
hp = @p1.currentHP
@p1.currentHP = (hp >> 1) + 1
@p2.currentHP = 4
move = @battle.getMove('Tail Slap')
@sandbox.stub(move, 'calculateDamage', -> 1)
@battle.performMove(@p1, move)
@p1.currentHP.should.equal (hp >> 1) + 1 - (5 * (hp >> 3)) + (hp >> 2)
@p2.currentHP.should.equal 4 + (hp >> 2) - 5
describe 'jump kick attacks', ->
it 'has 50% recoil if it misses', ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", 'miss', 100)
oldHP = @p1.stat('hp')
@battle.performMove(@p1, @battle.getMove('Hi Jump Kick'))
(oldHP - @p1.currentHP).should.equal Math.floor(oldHP / 2)
it "has 50% recoil if the target is immune", ->
shared.create.call(this, team2: [Factory("Gengar")])
oldHP = @p1.stat('hp')
@battle.performMove(@p1, @battle.getMove('Hi Jump Kick'))
(oldHP - @p1.currentHP).should.equal Math.floor(oldHP / 2)
it "has 50% recoil if the target protects itself", ->
shared.create.call(this)
oldHP = @p1.stat('hp')
@battle.recordMove(@id2, @battle.getMove("Protect"))
@battle.recordMove(@id1, @battle.getMove("Hi Jump Kick"))
@battle.continueTurn()
(oldHP - @p1.currentHP).should.equal Math.floor(oldHP / 2)
describe 'a drain attack', ->
it 'recovers a percentage of the damage dealt, rounded down', ->
shared.create.call this,
team1: [Factory('Conkeldurr')]
team2: [Factory('Hitmonchan')]
startHP = 1
@p1.currentHP = startHP
hp = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Drain Punch'))
damage = (hp - @p2.currentHP)
(@p1.currentHP - startHP).should.equal Math.ceil(damage / 2)
it 'cannot recover to over 100% HP', ->
shared.create.call this,
team1: [Factory('Conkeldurr')]
team2: [Factory('Hitmonchan')]
hp = @p1.currentHP = @p1.stat('hp')
@battle.performMove(@p1, @battle.getMove('Drain Punch'))
(@p1.currentHP - hp).should.equal 0
it "does not recover more than the damage dealt", ->
shared.create.call(this)
drainPunch = @battle.getMove('Drain Punch')
@sandbox.stub(drainPunch, 'baseDamage', -> 9999)
@p2.currentHP = 2
@p1.currentHP = 1
@battle.performMove(@p1, drainPunch)
@p2.currentHP.should.be.lessThan(1)
@p1.currentHP.should.equal(2)
describe 'weight-based attacks', ->
it 'has 80 base power if the pokemon is 50.2kg', ->
shared.create.call this,
team1: [Factory('Celebi')]
team2: [Factory('Hitmonchan')]
hp = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Grass Knot'))
(hp - @p2.currentHP).should.equal 94
it 'has 120 base power if the pokemon is >200kg', ->
shared.create.call this,
team1: [Factory('Celebi')]
team2: [Factory('Gyarados')]
hp = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Grass Knot'))
(hp - @p2.currentHP).should.equal 153
describe 'a pokemon using a primary boosting move', ->
it "doesn't do damage if base power is 0", ->
shared.create.call this,
team1: [Factory('Gyarados')]
team2: [Factory('Hitmonchan')]
@battle.performMove(@p1, @battle.getMove('Dragon Dance'))
@p2.currentHP.should.equal @p2.stat('hp')
it "boosts the pokemon's stats", ->
shared.create.call this,
team1: [Factory('Gyarados')]
team2: [Factory('Hitmonchan')]
@battle.performMove(@p1, @battle.getMove('Dragon Dance'))
@p1.stages.should.containEql attack: 1, speed: 1
it "affects type-immune pokemon", ->
shared.create.call this,
team1: [Factory('Audino')]
team2: [Factory('Gengar')]
@battle.performMove(@p1, @battle.getMove('Growl'))
@p2.stages.attack.should.equal -1
it "has the boosts removed on switch", ->
shared.create.call(this, team1: (Factory("Magikarp") for x in [1..2]))
@battle.performMove(@p1, @battle.getMove('Dragon Dance'))
@battle.performSwitch(@p1, 1)
@p1.stages.should.containEql(attack: 0, speed: 0)
describe 'a pokemon using a damaging move that also boosts stats on hit', ->
it "deals damage and boosts stats", ->
shared.create.call this,
team1: [Factory('Celebi')]
team2: [Factory('Gyarados')]
hp = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Leaf Storm'))
@p1.stages.specialAttack.should.equal -2
(hp - @p2.currentHP).should.equal 178
describe 'Low Sweep', ->
it "lowers the target's speed by 1", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove('Low Sweep'))
@p2.stages.speed.should.equal(-1)
it "does not lower the target's speed by 1 if the target faints", ->
shared.create.call(this)
@p2.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Low Sweep'))
@p2.stages.speed.should.not.equal(-1)
it "does not lower the target's speed by 1 if it hits a substitute", ->
shared.create.call(this)
@p2.attach(Attachment.Substitute)
@battle.performMove(@p1, @battle.getMove('Low Sweep'))
@p2.stages.speed.should.not.equal(-1)
describe 'Flame Charge', ->
it "raises the user's speed by 1", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove('Flame Charge'))
@p1.stages.speed.should.equal(1)
it "raises the user's speed by 1 even if the target faints", ->
shared.create.call(this)
@p2.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Flame Charge'))
@p1.stages.speed.should.equal(1)
it "raises the user's speed by 1 even if it hits a substitute", ->
shared.create.call(this)
@p2.attach(Attachment.Substitute)
@battle.performMove(@p1, @battle.getMove('Flame Charge'))
@p1.stages.speed.should.equal(1)
describe 'a pokemon using a move with a secondary boosting effect', ->
it "has a chance to activate", ->
shared.create.call this,
team1: [Factory('Mew')]
team2: [Factory('Hitmonchan')]
shared.biasRNG.call(this, "next", 'secondary boost', 0) # 100% chance
attack = @p1.stat('attack')
speed = @p1.stat('speed')
@battle.performMove(@p1, @battle.getMove('AncientPower'))
@p1.stages.should.containEql {
attack: 1, defense: 1, speed: 1, specialAttack: 1, specialDefense: 1
}
describe 'a pokemon using Acrobatics', ->
it 'gets double the base power without an item', ->
shared.create.call this,
team1: [Factory('Gliscor')]
team2: [Factory('Regirock')]
hp = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Acrobatics'))
damage = (hp - @p2.currentHP)
damage.should.equal 36
it 'has normal base power with an item', ->
shared.create.call this,
team1: [Factory('Gliscor', item: 'Leftovers')]
team2: [Factory('Regirock')]
hp = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Acrobatics'))
damage = (hp - @p2.currentHP)
damage.should.equal 18
testRecoilMove = (moveName) ->
describe "a pokemon using #{moveName}", ->
it 'receives a percentage of the damage rounded half up', ->
shared.create.call(this)
startHP = @p1.currentHP
hp = @p2.currentHP
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
recoil = -move.recoil / 100
damage = (hp - @p2.currentHP)
(startHP - @p1.currentHP).should.equal Math.round(damage * recoil)
it 'receives a minimum of 1 HP of recoil', ->
shared.create.call(this)
startHP = @p1.currentHP
hp = @p2.currentHP
move = @battle.getMove(moveName)
stub = @sandbox.stub(move, 'calculateDamage', -> .6)
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
damage = (hp - @p2.currentHP)
(startHP - @p1.currentHP).should.equal 1
it "does not recoil if no damage was dealt", ->
shared.create.call(this)
@battle.recordMove(@id2, @battle.getMove("Protect"))
@battle.recordMove(@id1, @battle.getMove(moveName))
@battle.continueTurn()
@p1.currentHP.should.equal(@p1.stat('hp'))
testRecoilMove("Brave Bird")
testRecoilMove("Double-Edge")
testRecoilMove("Flare Blitz")
testRecoilMove("Head Charge")
testRecoilMove("Head Smash")
testRecoilMove("Submission")
testRecoilMove("Take Down")
testRecoilMove("Volt Tackle")
testRecoilMove("Wild Charge")
testRecoilMove("Wood Hammer")
testDrainMove = (moveName) ->
describe moveName, ->
it "absorbs a percentage of the damage rounded up", ->
shared.create.call(this)
@p1.currentHP = 1
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
drain = move.recoil / 100
damage = (@p2.stat('hp') - @p2.currentHP)
(@p1.currentHP - 1).should.equal Math.ceil(damage * drain)
it 'receives a minimum of 1 HP of drain', ->
shared.create.call(this)
@p1.currentHP = @p2.currentHP = 1
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
@p1.currentHP.should.equal(2)
it "does not drain if no damage was dealt", ->
shared.create.call(this)
@p1.currentHP = 1
@battle.recordMove(@id2, @battle.getMove("Protect"))
@battle.recordMove(@id1, @battle.getMove(moveName))
@battle.continueTurn()
@p1.currentHP.should.equal(1)
testDrainMove("Absorb")
testDrainMove("Drain Punch")
testDrainMove("Giga Drain")
testDrainMove("Horn Leech")
testDrainMove("Leech Life")
testDrainMove("Mega Drain")
describe 'Haze', ->
shared.shouldDoNoDamage('Haze')
it 'removes all status boosts from each pokemon', ->
shared.create.call(this)
# Create artificial boosts.
@p1.stages.attack = 2
@p1.stages.evasion = -1
@p2.stages.defense = -3
@p2.stages.specialAttack = 4
@battle.performMove(@p1, @battle.getMove("Haze"))
neutralBoosts = {
attack: 0, defense: 0, specialAttack: 0, specialDefense: 0,
speed: 0, evasion: 0, accuracy: 0
}
@p1.stages.should.eql neutralBoosts
@p2.stages.should.eql neutralBoosts
describe 'Seismic Toss and Night Shade', ->
it 'does exactly the same damage as their level', ->
shared.create.call this,
team1: [Factory('Blissey')]
team2: [Factory('Mew')]
hp = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Seismic Toss'))
(hp - @p2.currentHP).should.equal 100
it 'triggers Focus Sash', ->
shared.create.call this,
team1: [Factory('Blissey')]
team2: [Factory('Mew', level: 1, item: "Focus Sash")]
hp = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Seismic Toss'))
@p2.currentHP.should.equal(1)
describe 'Psywave', ->
it 'does user.level/2 damage minimum', ->
shared.create.call this,
team1: [Factory('Weezing')]
team2: [Factory('Mew')]
shared.biasRNG.call(this, "randInt", 'psywave', 5)
hp = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Psywave'))
(hp - @p2.currentHP).should.equal 50
it 'does user.level * 1.5 damage maximum', ->
shared.create.call this,
team1: [Factory('Weezing')]
team2: [Factory('Mew')]
move = @battle.getMove('Psywave')
shared.biasRNG.call(this, "randInt", 'psywave', 15)
hp = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Psywave'))
(hp - @p2.currentHP).should.equal 150
it 'rounds down to the nearest .1 multiplier', ->
shared.create.call this,
team1: [Factory('Weezing')]
team2: [Factory('Mew')]
move = @battle.getMove('Psywave')
shared.biasRNG.call(this, "randInt", 'psywave', 6.09)
hp = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Psywave'))
(hp - @p2.currentHP).should.equal 60
describe 'facade', ->
it 'doubles the base power if burned, poisoned, or paralyzed', ->
shared.create.call this,
team1: [Factory('Zangoose')]
team2: [Factory('Magikarp')]
hp = @p2.currentHP
@p1.attach(Status.Paralyze)
move = @battle.getMove('Facade')
basePower = move.basePower(@battle, @p1, @p2)
basePower.should.equal(2 * move.power)
describe 'reversal and flail', ->
it 'have 200 base power at 1 hp', ->
shared.create.call this,
team1: [Factory('Zangoose')]
team2: [Factory('Magikarp')]
@p1.currentHP = 1
move = @battle.getMove('Flail')
basePower = move.basePower(@battle, @p1, @p2)
basePower.should.equal 200
it 'have 40 base power at 50% hp', ->
shared.create.call this,
team1: [Factory('Zangoose')]
team2: [Factory('Magikarp')]
@p1.currentHP = Math.floor(@p1.stat('hp') / 2)
move = @battle.getMove('Flail')
basePower = move.basePower(@battle, @p1, @p2)
basePower.should.equal 40
describe 'eruption and water spout', ->
beforeEach ->
shared.create.call this,
team1: [Factory('Camerupt')]
team2: [Factory('Mew')]
@attacker = @p1
@defender = @p2
@move = @battle.getMove('Eruption')
it 'has at least one base power', ->
@attacker.currentHP = 1
@move.basePower(@battle, @attacker, @defender).should.equal 1
it 'has 75 base power at 50% hp', ->
@attacker.currentHP = @attacker.stat('hp') / 2
@move.basePower(@battle, @attacker, @defender).should.equal 75
it 'has 150 base power at 100% hp', ->
@move.basePower(@battle, @attacker, @defender).should.equal 150
describe 'gyro ball', ->
it 'has 150 base power maximum', ->
shared.create.call this,
team1: [Factory('Forretress', ivs: {speed: 0})]
team2: [Factory('Jolteon', evs: {speed: 252}, nature: "Timid")]
move = @battle.getMove('Gyro Ball')
attacker = @p1
defender = @p2
attacker.stages.speed = -6
move.basePower(@battle, attacker, defender).should.equal 150
it 'has variable base power based on speed of target and user', ->
shared.create.call this,
team1: [Factory('Electrode', evs: {speed: 252}, nature: "Jolly")]
team2: [Factory('Magikarp', ivs: {speed: 0})]
move = @battle.getMove('Gyro Ball')
attacker = @p1
defender = @p2
attacker.stages.speed = -6
move.basePower(@battle, attacker, defender).should.equal 40
describe 'brine', ->
it 'has normal base power if the target has over 50% HP', ->
shared.create.call this,
team1: [Factory('Empoleon')]
team2: [Factory('Magikarp')]
move = @battle.getMove('Brine')
@p2.currentHP = Math.floor(@p2.currentHP / 2) + 1
move.basePower(@battle, @p1, @p2).should.equal 65
it 'doubles base power if the target has 50% or less HP', ->
shared.create.call this,
team1: [Factory('Empoleon')]
team2: [Factory('Magikarp')]
move = @battle.getMove('Brine')
@p2.currentHP = Math.floor(@p2.currentHP / 2)
move.basePower(@battle, @p1, @p2).should.equal 130
describe 'Disable', ->
shared.shouldDoNoDamage('Disable')
it 'gives the disabled attachment', ->
shared.create.call(this)
disable = @battle.getMove("Disable")
move = @p2.moves[0]
@battle.performMove(@p2, move)
@battle.performMove(@p1, disable)
@p2.has(Attachment.Disable).should.be.true
it "prevents the target's last move from being selected", ->
shared.create.call(this)
disable = @battle.getMove("Disable")
move = @p2.moves[0]
@battle.performMove(@p2, move)
@battle.performMove(@p1, disable)
@battle.beginTurn()
@p2.validMoves().should.not.containEql(move)
@p2.validMoves().should.have.length(@p2.moves.length - 1)
it 'wears off after 4 turns', ->
shared.create.call(this)
disable = @battle.getMove("Disable")
move = @p2.moves[0]
@battle.performMove(@p2, move)
@battle.performMove(@p1, disable)
@battle.endTurn()
@battle.endTurn()
@battle.endTurn()
@battle.endTurn()
@p2.has(Attachment.Disable).should.be.false
it 'stops the execution of a disabled move', ->
shared.create.call(this)
disable = @battle.getMove("Disable")
move = @p2.moves[0]
@battle.performMove(@p2, move)
@battle.performMove(@p1, disable)
mock = @sandbox.mock(move).expects('execute').never()
@battle.performMove(@p2, move)
mock.verify()
it 'fails if the target has not moved since it was active', ->
shared.create.call(this)
disable = @battle.getMove("Disable")
mock = @sandbox.mock(disable).expects('fail').once()
@battle.performMove(@p1, disable)
mock.verify()
it 'fails if the target does not know the move it used', ->
shared.create.call(this)
disable = @battle.getMove("Disable")
struggle = @battle.getMove("Struggle")
mock = @sandbox.mock(disable).expects('fail').once()
@battle.performMove(@p2, struggle)
@battle.performMove(@p1, disable)
mock.verify()
it "fails if the target's last move has 0 PP", ->
shared.create.call(this)
disable = @battle.getMove("Disable")
splash = @battle.getMove("Splash")
@battle.performMove(@p2, splash)
@p2.setPP(splash, 0)
mock = @sandbox.mock(disable).expects('fail').once()
@battle.performMove(@p1, disable)
mock.verify()
describe 'hidden power', ->
it 'has a max power of 70', ->
ivs = {
hp: 31, attack: 31, defense: 31,
specialAttack: 31, specialDefense: 31, speed: 31
}
shared.create.call this,
team1: [Factory('Magikarp', ivs: ivs)]
team2: [Factory('Magikarp')]
move = @battle.getMove('Hidden Power')
move.basePower(@battle, @p1, @p2).should.eql 70
it 'has a min power of 30', ->
ivs = {
hp: 0, attack: 0, defense: 0,
specialAttack: 0, specialDefense: 0, speed: 0
}
shared.create.call this,
team1: [Factory('Magikarp', ivs: ivs)]
team2: [Factory('Magikarp')]
move = @battle.getMove('Hidden Power')
move.basePower(@battle, @p1, @p2).should.eql 30
it 'is dark when all ivs are 31', ->
ivs = {
hp: 31, attack: 31, defense: 31,
specialAttack: 31, specialDefense: 31, speed: 31
}
shared.create.call this,
team1: [Factory('Magikarp', ivs: ivs)]
team2: [Factory('Magikarp')]
move = @battle.getMove('Hidden Power')
move.getType(@battle, @p1, @p2).should.eql 'Dark'
it 'is fighting when all ivs are 0', ->
ivs = {
hp: 0, attack: 0, defense: 0,
specialAttack: 0, specialDefense: 0, speed: 0
}
shared.create.call this,
team1: [Factory('Magikarp', ivs: ivs)]
team2: [Factory('Magikarp')]
move = @battle.getMove('Hidden Power')
move.getType(@battle, @p1, @p2).should.eql 'Fighting'
describe 'yawn', ->
shared.shouldDoNoDamage('Yawn')
shared.shouldFailIfUsedTwice("Yawn")
it 'gives the yawn attachment', ->
shared.create.call this,
team1: [Factory('Camerupt')]
team2: [Factory('Magikarp')]
@battle.performMove(@p1, @battle.getMove('Yawn'))
@p2.has(Attachment.Yawn).should.be.true
it 'puts the opponent to sleep at the end of the second turn', ->
shared.create.call this,
team1: [Factory('Camerupt')]
team2: [Factory('Magikarp')]
@battle.performMove(@p1, @battle.getMove('Yawn'))
@battle.endTurn()
@p2.has(Status.Sleep).should.be.false
@battle.endTurn()
@p2.has(Status.Sleep).should.be.true
it "fails if the target already is statused", ->
shared.create.call(this)
yawn = @battle.getMove('Yawn')
mock = @sandbox.mock(yawn).expects('fail').once()
@p2.attach(Status.Paralyze)
@battle.performMove(@p1, yawn)
mock.verify()
describe 'an OHKO move', ->
it 'ignores accuracy/evasion modifiers', ->
shared.create.call(this)
@p1.stages.accuracy = -6
@p2.stages.evasion = 6
acc = @battle.getMove('Sheer Cold').chanceToHit(@battle, @p1, @p2)
acc.should.equal 30
it "does damage equal to the target's total hp", ->
shared.create.call this,
team1: [Factory('Lapras')]
team2: [Factory('Magikarp')]
@p2.currentHP = Math.floor(@p2.stat('hp') / 2)
@controller.makeMove(@id1, 'Sheer Cold')
@controller.makeMove(@id2, 'Splash')
move = @battle.getMove('Sheer Cold')
damage = move.calculateDamage(@battle, @p1, @p2)
damage.should.equal @p2.stat('hp')
describe 'a recovery move', ->
shared.shouldDoNoDamage('Recover')
it "recovers 50% of the target's HP, rounded half up", ->
shared.create.call(this)
hp = @p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Softboiled'))
recoverHP = Math.round(@p1.stat('hp') / 2)
(@p1.currentHP - hp).should.equal recoverHP
it "fails if the user's HP is full", ->
shared.create.call(this)
softboiled = @battle.getMove('Softboiled')
mock = @sandbox.mock(softboiled).expects('fail').once()
@battle.performMove(@p1, softboiled)
mock.verify()
describe 'Knock Off', ->
it "deals damage", ->
shared.create.call this,
team2: [Factory('Magikarp', item: "Leftovers")]
@battle.performMove(@p1, @battle.getMove("Knock Off"))
@p2.currentHP.should.be.lessThan @p2.stat('hp')
it "removes the target's item", ->
shared.create.call this,
team2: [Factory('Magikarp', item: "Leftovers")]
@battle.performMove(@p1, @battle.getMove("Knock Off"))
should.not.exist @p2.item
it "does not remove unremovable items", ->
shared.create.call this,
team2: [Factory('Magikarp', item: "Air Mail")]
@battle.performMove(@p1, @battle.getMove("Knock Off"))
@p2.hasItem().should.be.true
it "does not knock off items from pokemon with Sticky Hold", ->
shared.create.call this,
team2: [Factory('Magikarp', ability: "Sticky Hold", item: "Leftovers")]
@battle.performMove(@p1, @battle.getMove("Knock Off"))
@p2.hasItem().should.be.true
it "takes damage from Rocky Helmet", ->
shared.create.call this,
team2: [Factory('Magikarp', item: "Rocky Helmet")]
@battle.performMove(@p1, @battle.getMove("Knock Off"))
@p2.hasItem().should.be.false
@p1.currentHP.should.be.lessThan(@p1.stat('hp'))
it "doesn't knock off if substitute fades", ->
shared.create.call this,
team2: [Factory('Magikarp', item: "Leftovers")]
@p2.attach(Attachment.Substitute, hp: 1)
@battle.performMove(@p1, @battle.getMove("Knock Off"))
@p2.hasItem().should.be.true
it "doesn't knock off if user is fainted", ->
shared.create.call this,
team2: [Factory('Magikarp', item: "Rocky Helmet")]
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Knock Off"))
@p2.hasItem().should.be.true
@p1.isFainted().should.be.true
it 'removes healing Berries before they can activate', ->
shared.create.call this,
team1: [Factory('Magikarp')]
team2: [Factory('Magikarp', item: "Sitrus Berry")]
@p2.currentHP = 2
move = @battle.getMove('Knock Off')
@sandbox.stub(move, 'calculateDamage', -> 1)
@battle.performMove(@p1, move)
@p2.currentHP.should.equal 1
describe 'trick and switcheroo', ->
shared.shouldDoNoDamage('Trick')
it "isn't affected by type-immunities", ->
shared.create.call this,
team1: [Factory('Alakazam', item: 'Stick')]
team2: [Factory('Drapion', item: "Leftovers")]
item1 = @p1.item
item2 = @p2.item
trick = @battle.getMove('Trick')
@battle.performMove(@p1, trick)
@p2.item.should.equal item1
@p1.item.should.equal item2
it "swaps the target and user's item", ->
shared.create.call this,
team1: [Factory('Alakazam', item: 'Stick')]
team2: [Factory('Magikarp', item: "Leftovers")]
item1 = @p1.item
item2 = @p2.item
@controller.makeMove(@id1, 'Trick')
@controller.makeMove(@id2, 'Splash')
@p2.item.should.equal item1
@p1.item.should.equal item2
it "fails if the user has Sticky Hold", ->
shared.create.call this,
team1: [Factory('Alakazam', ability: "Sticky Hold", item: 'Stick')]
team2: [Factory('Gastrodon', item: "Leftovers")]
trick = @battle.getMove('Trick')
mock = @sandbox.mock(trick).expects('fail').once()
@battle.performMove(@p1, trick)
mock.verify()
it "fails if the target has Sticky Hold", ->
shared.create.call this,
team1: [Factory('Alakazam', item: 'Stick')]
team2: [Factory('Magikarp', ability: "Sticky Hold", item: "Leftovers")]
trick = @battle.getMove('Trick')
mock = @sandbox.mock(trick).expects('fail').once()
@battle.performMove(@p1, trick)
mock.verify()
it "succeeds if only one pokemon has an item", ->
shared.create.call this,
team1: [Factory('Alakazam', item: 'Stick')]
team2: [Factory('Magikarp')]
trick = @battle.getMove('Trick')
item1 = @p1.item
@battle.performMove(@p1, trick)
should.not.exist(@p1.item)
@p2.item.should.equal(item1)
it "fails if neither the user nor the target has an item", ->
shared.create.call this,
team1: [Factory('Alakazam')]
team2: [Factory('Magikarp')]
trick = @battle.getMove('Trick')
mock = @sandbox.mock(trick).expects('fail').once()
@battle.performMove(@p1, trick)
mock.verify()
it "fails if the user is holding a Mail", ->
shared.create.call this,
team1: [Factory('Alakazam', item: 'Air Mail')]
team2: [Factory('Magikarp')]
trick = @battle.getMove('Trick')
mock = @sandbox.mock(trick).expects('fail').once()
@battle.performMove(@p1, trick)
mock.verify()
it "fails if the target is holding a Mail", ->
shared.create.call this,
team1: [Factory('Alakazam')]
team2: [Factory('Magikarp', item: 'Air Mail')]
trick = @battle.getMove('Trick')
mock = @sandbox.mock(trick).expects('fail').once()
@battle.performMove(@p1, trick)
mock.verify()
it "fails if the user is Giratina-O", ->
shared.create.call this,
team1: [Factory('Giratina', forme: "origin")]
trick = @battle.getMove('Trick')
mock = @sandbox.mock(trick).expects('fail').once()
@battle.performMove(@p1, trick)
mock.verify()
it "fails if the target is Giratina-O", ->
shared.create.call this,
team2: [Factory('Giratina', forme: "origin")]
trick = @battle.getMove('Trick')
mock = @sandbox.mock(trick).expects('fail').once()
@battle.performMove(@p1, trick)
mock.verify()
it "fails if the target has a Substitute", ->
shared.create.call(this)
trick = @battle.getMove('Trick')
substitute = @battle.getMove('Substitute')
mock = @sandbox.mock(trick).expects('fail').once()
@battle.performMove(@p2, substitute)
@battle.performMove(@p1, trick)
mock.verify()
it "fails if the user has Multitype with a plate item", ->
shared.create.call this,
team1: [Factory('Magikarp', ability: "Multitype", item: "Grass Plate")]
team2: [Factory('Magikarp')]
trick = @battle.getMove('Trick')
mock = @sandbox.mock(trick).expects('fail').once()
@battle.performMove(@p1, trick)
mock.verify()
it "fails if the target has Multitype with a plate item", ->
shared.create.call this,
team1: [Factory('Magikarp')]
team2: [Factory('Magikarp', ability: "Multitype", item: "Grass Plate")]
trick = @battle.getMove('Trick')
mock = @sandbox.mock(trick).expects('fail').once()
@battle.performMove(@p1, trick)
mock.verify()
it "fails if the target is Genesect with a Drive item", ->
shared.create.call this,
team1: [Factory('Magikarp')]
team2: [Factory('Genesect', item: "Burn Drive")]
trick = @battle.getMove('Trick')
mock = @sandbox.mock(trick).expects('fail').once()
@battle.performMove(@p1, trick)
mock.verify()
it "fails if the user is Genesect with a Drive item", ->
shared.create.call this,
team1: [Factory('Genesect', item: "Burn Drive")]
team2: [Factory('Magikarp')]
trick = @battle.getMove('Trick')
mock = @sandbox.mock(trick).expects('fail').once()
@battle.performMove(@p1, trick)
mock.verify()
describe 'memento', ->
shared.shouldDoNoDamage('Memento')
it "faints the user", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Memento"))
@p1.isFainted().should.be.true
it "reduces the attack and special attack of the target by two stages", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Memento"))
@p2.stages.should.containEql attack: -2, specialAttack: -2
it "doesn't reduce stats if target is protected, and doesn't faint user", ->
shared.create.call(this)
@battle.recordMove(@id2, @battle.getMove("Protect"))
@battle.recordMove(@id1, @battle.getMove("Memento"))
@battle.continueTurn()
@p2.stages.should.not.containEql attack: -2, specialAttack: -2
@p1.isFainted().should.be.false
it "doesn't reduce stats if target has a substitute, and doesn't faint user", ->
shared.create.call(this)
@battle.performMove(@p2, @battle.getMove("Substitute"))
@battle.performMove(@p1, @battle.getMove("Memento"))
@p2.stages.should.not.containEql attack: -2, specialAttack: -2
@p1.isFainted().should.be.false
it "doesn't reduce stats if target has Clear Body, but faints user", ->
shared.create.call(this, team2: [Factory('Magikarp', ability: 'Clear Body')])
@battle.performMove(@p1, @battle.getMove("Memento"))
@p2.stages.should.not.containEql attack: -2, specialAttack: -2
@p1.isFainted().should.be.true
describe 'magnitude', ->
it "has variable base power", ->
shared.create.call this,
team1: [Factory('Dugtrio')]
team2: [Factory('Magikarp')]
move = @battle.getMove('Magnitude')
shared.biasRNG.call(this, "randInt", 'magnitude', 50)
move.basePower(@battle, @p1, @p2).should.equal 70
describe 'Pain Split', ->
it "doesn't make a pokemon's HP go over their max", ->
shared.create.call this,
team1: [Factory('Gengar')]
team2: [Factory('Blissey')]
@controller.makeMove(@id1, 'Pain Split')
@controller.makeMove(@id1, 'Seismic Toss')
@p1.currentHP.should.equal @p1.stat('hp')
it "averages user and target current HP", ->
shared.create.call this,
team1: [Factory('Gengar')]
team2: [Factory('Blissey')]
@p1.currentHP = 2
@controller.makeMove(@id1, 'Pain Split')
@controller.makeMove(@id2, 'Seismic Toss')
@p1.currentHP.should.equal Math.min(326, @p1.stat('hp'))
@p2.currentHP.should.equal Math.min(326, @p2.stat('hp'))
it "affects Ghosts", ->
shared.create.call this,
team2: [Factory('Gengar')]
painSplit = @battle.getMove("Pain Split")
mock = @sandbox.mock(painSplit).expects('hit').once()
@battle.performMove(@p1, painSplit)
mock.verify()
it "is blocked by Protect", ->
shared.create.call(this)
@p1.currentHP = 1
painSplit = @battle.getMove("Pain Split")
mock = @sandbox.mock(painSplit).expects('hit').never()
@battle.recordMove(@id1, painSplit)
@battle.determineTurnOrder()
@battle.performMove(@p2, @battle.getMove("Protect"))
@battle.performMove(@p1, painSplit)
mock.verify()
@p1.currentHP.should.equal(1)
@p2.currentHP.should.equal(@p2.stat('hp'))
describe 'Belly Drum', ->
shared.shouldDoNoDamage('Belly Drum')
it "maximizes attack", ->
shared.create.call this,
team1: [Factory('Poliwrath')]
team2: [Factory('Magikarp')]
@p1.stages.attack = -6
@controller.makeMove(@id1, 'Belly Drum')
@controller.makeMove(@id2, 'Splash')
@p1.stages.attack.should.equal 6
it "cuts the pokemon's HP by half", ->
shared.create.call this,
team1: [Factory('Poliwrath')]
team2: [Factory('Magikarp')]
@controller.makeMove(@id1, 'Belly Drum')
@controller.makeMove(@id2, 'Splash')
hp = @p1.stat('hp')
(hp - @p1.currentHP).should.equal Math.floor(hp / 2)
it "fails if the pokemon's HP is less than half", ->
shared.create.call this,
team1: [Factory('Poliwrath')]
team2: [Factory('Magikarp')]
hp = @p1.currentHP = Math.floor(@p1.stat('hp') / 2) - 1
@controller.makeMove(@id1, 'Belly Drum')
@controller.makeMove(@id2, 'Splash')
@p1.currentHP.should.equal hp
@p1.stages.attack.should.equal 0
it "fails if the pokemon's attack is already maximized", ->
shared.create.call this,
team1: [Factory('Poliwrath')]
team2: [Factory('Magikarp')]
@p1.boost(attack: 12)
bd = @battle.getMove('Belly Drum')
mock = @sandbox.mock(bd).expects('fail').once()
@battle.performMove(@p1, bd)
mock.verify()
@p1.currentHP.should.equal(@p1.stat('hp'))
describe 'acupressure', ->
shared.shouldDoNoDamage('Acupressure')
it "raises a random stat that can be raised", ->
shared.create.call this,
team1: [Factory('Shuckle')]
team2: [Factory('Magikarp')]
stages = _.clone(@p1.stages)
@controller.makeMove(@id1, 'Acupressure')
@controller.makeMove(@id2, 'Splash')
@p1.stages.should.not.eql stages
it "fails if the Pokemon has maximum stats", ->
shared.create.call this,
team1: [Factory('Shuckle')]
team2: [Factory('Magikarp')]
@p1.stages.attack = 6
@p1.stages.defense = 6
@p1.stages.specialAttack = 6
@p1.stages.specialDefense = 6
@p1.stages.accuracy = 6
@p1.stages.evasion = 6
mock = @sandbox.mock(@p2)
mock.expects('boost').never()
@controller.makeMove(@id1, 'Acupressure')
@controller.makeMove(@id2, 'Splash')
mock.verify()
describe 'Dragon Rage', ->
it 'always does 40 damage', ->
shared.create.call(this)
move = @battle.getMove('Dragon Rage')
move.calculateDamage(@battle, @p1, @p2).should.equal 40
describe 'SonicBoom', ->
it 'always does 20 damage', ->
shared.create.call(this)
sonicBoom = @battle.getMove('SonicBoom')
sonicBoom.calculateDamage(@battle, @p1, @p2).should.equal 20
describe 'explosion moves', ->
it 'faints the user', ->
shared.create.call this,
team1: [Factory('Gengar')]
team2: [Factory('Blissey')]
@battle.performMove(@p1, @battle.getMove('Explosion'))
@p1.isFainted().should.be.true
it 'faints the user even if enemy is immune', ->
shared.create.call this,
team1: [Factory('Gengar')]
team2: [Factory('Gengar')]
@battle.performMove(@p1, @battle.getMove('Explosion'))
@p1.isFainted().should.be.true
it 'fails if an active Pokemon has Damp', ->
shared.create.call this,
team1: [Factory('Gengar')]
team2: [Factory('Politoed', ability: 'Damp')]
explosion = @battle.getMove('Explosion')
mock = @sandbox.mock(explosion).expects('fail').once()
@battle.performMove(@p1, explosion)
mock.verify()
describe 'endeavor', ->
it "brings the target's hp down to the user's hp", ->
shared.create.call(this)
hp = 4
@p1.currentHP = hp
@battle.performMove(@p1, @battle.getMove('Endeavor'))
@p2.currentHP.should.equal hp
it "fails if the target's hp is less than the user's hp", ->
shared.create.call(this)
move = @battle.getMove('Endeavor')
mock = @sandbox.mock(move).expects('fail').once()
@p2.currentHP = hp = 4
@battle.performMove(@p1, move)
mock.verify()
it "doesn't hit ghost pokemon", ->
shared.create.call this,
team1: [Factory('Politoed')]
team2: [Factory('Gengar')]
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Endeavor'))
@p2.currentHP.should.equal @p2.stat('hp')
describe 'a thief move', ->
it "should steal the target's item", ->
shared.create.call this,
team1: [Factory('Magikarp')]
team2: [Factory('Magikarp', item: "Leftovers")]
item2 = @p2.item
@battle.performMove(@p1, @battle.getMove('Thief'))
@p1.item.should.equal item2
should.not.exist @p2.item
it "should not steal the target's item if it has none", ->
shared.create.call this,
team1: [Factory('Magikarp')]
team2: [Factory('Magikarp')]
item2 = @p2.item
@battle.performMove(@p1, @battle.getMove('Thief'))
should.not.exist @p1.item
should.not.exist @p2.item
it "should not steal the target's item if user already has an item", ->
shared.create.call this,
team1: [Factory('Magikarp', item: "Stick")]
team2: [Factory('Magikarp', item: "Leftovers")]
item1 = @p1.item
item2 = @p2.item
@battle.performMove(@p1, @battle.getMove('Thief'))
@p1.item.should.equal item1
@p2.item.should.equal item2
it "should not steal the target's item if target has Sticky Hold", ->
shared.create.call this,
team1: [Factory('Magikarp')]
team2: [Factory('Magikarp', item: "Leftovers", ability: "Sticky Hold")]
item2 = @p2.item
@battle.performMove(@p1, @battle.getMove('Thief'))
should.not.exist @p1.item
@p2.item.should.equal item2
it "should not steal target's item if target has Multitype and a plate", ->
shared.create.call this,
team1: [Factory('Magikarp')]
team2: [Factory('Magikarp', item: "Draco Plate", ability: "Multitype")]
item2 = @p2.item
@battle.performMove(@p1, @battle.getMove('Thief'))
should.not.exist @p1.item
@p2.item.should.equal item2
it "should not steal the target's item if the target has no item", ->
shared.create.call(this)
item2 = @p2.item
@battle.performMove(@p1, @battle.getMove('Thief'))
@p1.hasItem().should.be.false
@p2.hasItem().should.be.false
it "should not steal the target's item if target is Giratina-O", ->
shared.create.call this,
team1: [Factory('Magikarp')]
team2: [Factory('Giratina', forme: "origin", item: "Griseous Orb")]
item2 = @p2.item
@battle.performMove(@p1, @battle.getMove('Thief'))
should.not.exist @p1.item
@p2.item.should.equal item2
it "should not steal the target's item if target is Genesect with Drive", ->
shared.create.call this,
team1: [Factory('Magikarp')]
team2: [Factory('Genesect', item: "Burn Drive")]
item2 = @p2.item
@battle.performMove(@p1, @battle.getMove('Thief'))
should.not.exist @p1.item
@p2.item.should.equal item2
it "should not steal the target's item if target holds Mail", ->
shared.create.call this,
team1: [Factory('Magikarp')]
team2: [Factory('Magikarp', item: "Air Mail")]
item2 = @p2.item
@battle.performMove(@p1, @battle.getMove('Thief'))
should.not.exist @p1.item
@p2.item.should.equal item2
it "should not steal the target's item if user is fainted", ->
shared.create.call this,
team1: [Factory('Magikarp')]
team2: [Factory('Magikarp', item: "Leftovers", ability: "Iron Barbs")]
item2 = @p2.item
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Thief'))
should.not.exist @p1.item
@p2.item.should.equal item2
describe 'crush grip', ->
it 'has a base power of 1 minimum', ->
shared.create.call(this)
move = @battle.getMove('Crush Grip')
@p2.currentHP = 1
move.basePower(@battle, @p1, @p2).should.equal 1
it 'has a base power of 121 maximum', ->
shared.create.call(this)
move = @battle.getMove('Crush Grip')
@p2.currentHP = @p2.stat('hp')
move.basePower(@battle, @p1, @p2).should.equal 121
describe 'hex', ->
it 'doubles the base power if target is burned, poisoned, or paralyzed', ->
shared.create.call(this)
move = @battle.getMove('Hex')
@p2.attach(Status.Paralyze)
move.basePower(@battle, @p1, @p2).should.equal 100
describe 'heavy slam and heat crash', ->
it 'has variable base power based on the difference in weight', ->
shared.create.call(this)
move = @battle.getMove('Heavy Slam')
move.basePower(@battle, @p1, @p2).should.equal 40
move.basePower(@battle, @p1, calculateWeight: -> -1000).should.equal 120
describe 'a status cure move', ->
it 'heals the entire team of status effects', ->
shared.create.call this,
team1: [Factory('Magikarp'), Factory('Magikarp'), Factory('Magikarp')]
shared.biasRNG.call(this, 'next', 'paralyze chance', 1) # never pars
@team1.pokemon.forEach((pokemon) -> pokemon.attach(Status.Paralyze))
@battle.performMove(@p1, @battle.getMove('Aromatherapy'))
_.all(@team1.pokemon, (pokemon) -> !pokemon.hasStatus()).should.be.true
describe 'copycat', ->
beforeEach ->
shared.create.call this,
team1: [Factory('Magikarp', evs: {speed: 4})] # make faster than team2
it 'copies the last move used', ->
@battle.lastMove = @battle.getMove('Tackle')
mock = @sandbox.mock(@battle.getMove('Tackle'))
mock.expects('execute').once()
@battle.performMove(@p1, @battle.getMove('Copycat'))
mock.verify()
it "changes the move's target properly", ->
tackle = @battle.getMove('Tackle')
@battle.lastMove = tackle
mock = @sandbox.mock(tackle)
mock.expects('execute').once().withArgs(@battle, @p1, [ @p2 ])
@battle.performMove(@p1, @battle.getMove('Copycat'))
mock.verify()
it 'fails if no last move was used', ->
@battle.lastMove = null
mock = @sandbox.mock(@battle.getMove('Copycat'))
mock.expects('fail').once()
@battle.performMove(@p1, @battle.getMove('Copycat'))
mock.verify()
it 'fails if the last move was copycat', ->
@battle.lastMove = @battle.getMove('Copycat')
mock = @sandbox.mock(@battle.getMove('Copycat'))
mock.expects('fail').once()
@battle.performMove(@p1, @battle.getMove('Copycat'))
mock.verify()
describe 'a move that targets a different stat', ->
it "uses the correct stat", ->
shared.create.call(this)
move = @battle.getMove('Secret Sword')
defStat = @p2.stat('defense')
move.pickDefenseStat(@p1, @p2).should.equal defStat
describe 'foul play', ->
it "uses the target's attack stat, not the user's", ->
shared.create.call this,
team1: [Factory('Celebi')]
move = @battle.getMove('Foul Play')
atkStat = @p2.stat('attack')
move.pickAttackStat(@p1, @p2).should.equal atkStat
describe 'foul play', ->
it "uses the target's attack stat, not the user's", ->
shared.create.call this,
team1: [Factory('Celebi')]
move = @battle.getMove('Foul Play')
atkStat = @p2.stat('attack')
move.pickAttackStat(@p1, @p2).should.equal atkStat
describe 'teleport', ->
it "always fails", ->
shared.create.call(this)
move = @battle.getMove('Teleport')
mock = @sandbox.mock(move).expects('fail').once()
@battle.performMove(@p1, move)
mock.verify()
describe 'Super Fang', ->
it "deals half of the target's current HP", ->
shared.create.call(this)
hp = @p2.currentHP
hp = @p2.currentHP = (hp - (1 - hp % 2)) # Always odd
@battle.performMove(@p1, @battle.getMove('Super Fang'))
@p2.currentHP.should.equal Math.ceil(hp / 2)
it "deals 1 damage minimum", ->
shared.create.call(this)
@p2.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Super Fang'))
@p2.currentHP.should.equal 0
describe "A weather-based recovery move", ->
it "heals 50% HP in no weather, rounded half down", ->
shared.create.call(this)
@battle.setWeather(Weather.NONE)
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Moonlight'))
hp = util.roundHalfDown(@p1.stat('hp') / 2)
@p1.currentHP.should.equal(1 + hp)
it "heals 25% HP in bad weather, rounded half down", ->
shared.create.call(this, team1: [Factory("Shuckle")])
@battle.setWeather(Weather.SAND)
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Moonlight'))
hp = util.roundHalfDown(@p1.stat('hp') / 4)
@p1.currentHP.should.equal(1 + hp)
it "heals 66% HP in good weather, rounded half down", ->
shared.create.call(this)
@battle.setWeather(Weather.SUN)
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Moonlight'))
hp = util.roundHalfDown(@p1.stat('hp') * 2 / 3)
@p1.currentHP.should.equal(1 + hp)
describe 'a flinching move', ->
it "prevents the other person from executing their move", ->
shared.create.call(this)
mock = @sandbox.mock(@battle.getMove('Splash'))
mock.expects('execute').never()
@battle.performMove(@p1, @battle.getMove('Fake Out'))
@battle.performMove(@p2, @battle.getMove('Splash'))
mock.verify()
it "removes the flinch attachment at the end of the turn", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove('Fake Out'))
@battle.endTurn()
@p2.has(Attachment.Flinch).should.be.false
describe 'weather ball', ->
it "is a 50 base power normal move in normal conditions", ->
shared.create.call(this)
@battle.setWeather(Weather.NONE)
move = @battle.getMove('Weather Ball')
move.getType(@battle, @p1, @p2).should.equal 'Normal'
move.basePower(@battle, @p1, @p2).should.equal 50
it "is a 100 base power Water move in rain", ->
shared.create.call(this)
@battle.setWeather(Weather.RAIN)
move = @battle.getMove('Weather Ball')
move.getType(@battle, @p1, @p2).should.equal 'Water'
move.basePower(@battle, @p1, @p2).should.equal 100
it "is a 100 base power Fire move in sun", ->
shared.create.call(this)
@battle.setWeather(Weather.SUN)
move = @battle.getMove('Weather Ball')
move.getType(@battle, @p1, @p2).should.equal 'Fire'
move.basePower(@battle, @p1, @p2).should.equal 100
it "is a 100 base power Ice move in hail", ->
shared.create.call(this)
@battle.setWeather(Weather.HAIL)
move = @battle.getMove('Weather Ball')
move.getType(@battle, @p1, @p2).should.equal 'Ice'
move.basePower(@battle, @p1, @p2).should.equal 100
it "is a 100 base power Rock move in sandstorm", ->
shared.create.call(this)
@battle.setWeather(Weather.SAND)
move = @battle.getMove('Weather Ball')
move.getType(@battle, @p1, @p2).should.equal 'Rock'
move.basePower(@battle, @p1, @p2).should.equal 100
describe 'Autotomize', ->
it 'changes your weight on success', ->
shared.create.call(this)
weight = @p1.calculateWeight()
@battle.performMove(@p1, @battle.getMove('Autotomize'))
weight.should.not.equal @p1.calculateWeight()
it 'cannot go below .1kg', ->
# Magikarp weighs 100kg.
shared.create.call this, team1: [ Factory('Magikarp')]
@battle.performMove(@p1, @battle.getMove('Autotomize'))
@p1.calculateWeight().should.not.be.lessThan .1
it 'stacks weight changes', ->
# Abomasnow weighs 1355kg.
shared.create.call this, team1: [ Factory('Abomasnow')]
@battle.performMove(@p1, @battle.getMove('Autotomize'))
@battle.performMove(@p1, @battle.getMove('Autotomize'))
@p1.calculateWeight().should.equal 1155
describe 'heart swap', ->
shared.shouldDoNoDamage('Heart Swap')
it 'swaps user and target boosts', ->
shared.create.call(this)
@p1.stages.attack = 2
@p2.stages.speed = -2
@battle.performMove(@p1, @battle.getMove('Heart Swap'))
@p1.stages.should.containEql speed: -2
@p2.stages.should.containEql attack: 2
describe 'Nightmare', ->
shared.shouldDoNoDamage('Nightmare')
it 'fails if the pokemon is awake', ->
shared.create.call(this)
mock = @sandbox.mock(@battle.getMove('Nightmare')).expects('fail').once()
@battle.performMove(@p1, @battle.getMove('Nightmare'))
mock.verify()
it 'fails if used twice', ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'sleep turns', 3)
nightmare = @battle.getMove('Nightmare')
@p2.attach(Status.Sleep)
mock = @sandbox.mock(nightmare).expects('fail').once()
@battle.performMove(@p1, nightmare)
@battle.performMove(@p1, nightmare)
mock.verify()
it "cuts the target's HP by 25% each turn", ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'sleep turns', 3)
@p2.attach(Status.Sleep)
hp = @p2.currentHP
quarter = Math.floor(hp / 4)
@battle.performMove(@p1, @battle.getMove('Nightmare'))
@battle.endTurn()
@p2.currentHP.should.equal(hp - quarter)
@battle.endTurn()
@p2.currentHP.should.equal(hp - 2*quarter)
it "deals 1 damage minimum", ->
shared.create.call(this, team2: [Factory("Shedinja")])
shared.biasRNG.call(this, 'randInt', 'sleep turns', 3)
@p2.attach(Status.Sleep)
@battle.performMove(@p1, @battle.getMove('Nightmare'))
@battle.endTurn()
@p2.currentHP.should.equal(0)
it "stops the nightmare if the target wakes up", ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'sleep turns', 3)
@p2.attach(Status.Sleep)
@battle.performMove(@p1, @battle.getMove('Nightmare'))
@p2.cureStatus()
@battle.endTurn() # The check is in endTurn()
@p2.has(Attachment.Nightmare).should.be.false
describe 'Incinerate', ->
it 'destroys the berry of the target', ->
shared.create.call this,
team2: [ Factory('Magikarp', item: 'Bluk Berry') ]
@battle.performMove(@p1, @battle.getMove('Incinerate'))
should.not.exist @p2.item
it 'does not destroy non-berries', ->
shared.create.call this,
team2: [ Factory('Magikarp', item: 'Leftovers') ]
@battle.performMove(@p1, @battle.getMove('Incinerate'))
should.exist @p2.item
describe 'Judgment', ->
it 'is normal type by default', ->
shared.create.call(this)
move = @battle.getMove('Judgment')
move.getType(@battle, @p1, @p2).should.equal 'Normal'
it 'changes type depending on the Plate held by the user', ->
shared.create.call this,
team1: [ Factory('Magikarp', item: 'Earth Plate') ]
move = @battle.getMove('Judgment')
move.getType(@battle, @p1, @p2).should.equal 'Ground'
describe 'Tailwind', ->
shared.shouldDoNoDamage("Tailwind")
shared.shouldFailIfUsedTwice("Tailwind")
it "doubles the speed of the user's team", ->
shared.create.call this
oldSpeed = @p1.stat('speed')
@battle.performMove(@p1, @battle.getMove('Tailwind'))
newSpeed = @p1.stat('speed')
newSpeed.should.equal(oldSpeed * 2)
it "lasts three turns", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove('Tailwind'))
@battle.endTurn()
@battle.endTurn()
@battle.endTurn()
@battle.endTurn()
@p1.team.has(Attachment.Tailwind).should.be.false
describe 'Taunt', ->
shared.shouldDoNoDamage("Taunt")
shared.shouldFailIfUsedTwice("Taunt")
it 'prevents the target from using a non-attacking move that turn', ->
shared.create.call(this, team1: [ Factory('Magikarp', evs: {speed: 4}) ])
move = @battle.getMove('Calm Mind')
taunt = @battle.getMove('Taunt')
mock = @sandbox.mock(move)
mock.expects('execute').never()
@battle.performMove(@p1, taunt)
@battle.performMove(@p2, move)
mock.verify()
it "lasts 4 turns if the user moved after the target", ->
shared.create.call(this, team1: [Factory("Magikarp", evs: {speed: 4})])
@battle.performMove(@p1, @battle.getMove('Splash'))
@battle.performMove(@p2, @battle.getMove('Taunt'))
@p1.has(Attachment.Taunt).should.be.true
@battle.endTurn()
@battle.endTurn()
@battle.endTurn()
@p1.has(Attachment.Taunt).should.be.true
@battle.endTurn()
@p1.has(Attachment.Taunt).should.be.false
it "lasts 3 turns if the user moved before the target", ->
shared.create.call(this, team1: [Factory("Magikarp", evs: {speed: 4})])
@battle.performMove(@p2, @battle.getMove('Splash'))
@battle.recordMove(@id1, @battle.getMove('Taunt'))
@battle.recordMove(@id2, @battle.getMove('Tackle'))
@battle.continueTurn()
@p2.has(Attachment.Taunt).should.be.true
@battle.endTurn()
@battle.endTurn()
@p2.has(Attachment.Taunt).should.be.true
@battle.endTurn()
@p2.has(Attachment.Taunt).should.be.false
it 'prevents the target from selecting that move the next turn', ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove('Taunt'))
@battle.beginTurn()
requestedMoves = @battle.requestFor(@p2).moves
requestedMoves.should.not.containEql 'Splash'
for moveName in [ "U-turn", "Volt Switch" ]
do (moveName) ->
describe moveName, ->
it 'forces the owner to switch', ->
shared.create.call(this, team1: (Factory("Magikarp") for i in [1..2]))
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.requests.should.have.property(@id1)
it "makes a request containing all the possible switches", ->
shared.create.call(this, team1: (Factory("Magikarp") for i in [1..3]))
@battle.performMove(@p1, @battle.getMove(moveName))
request = @battle.requestFor(@p1)
request.switches.should.eql([ 1, 2 ])
describe 'venoshock', ->
it 'doubles the base power if target is poisoned', ->
shared.create.call(this)
move = @battle.getMove("Venoshock")
@p2.attach(Status.Poison)
basePower = move.basePower(@battle, @p1, @p2)
basePower.should.equal(2 * move.power)
it 'doubles the base power if target is toxiced', ->
shared.create.call(this)
move = @battle.getMove("Venoshock")
@p2.attach(Status.Toxic)
basePower = move.basePower(@battle, @p1, @p2)
basePower.should.equal(2 * move.power)
describe 'Wish', ->
shared.shouldDoNoDamage("Wish")
shared.shouldFailIfUsedTwice("Wish")
it "restores half of the user's total hit points the next end of turn", ->
shared.create.call(this)
hp = @p1.currentHP
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Wish'))
@battle.endTurn()
@p1.currentHP.should.equal 1
@battle.endTurn()
@p1.currentHP.should.equal(Math.round(hp / 2) + 1)
it "restores the same total amount of HP to an ally", ->
shared.create.call(this, team1: [Factory("Magikarp"), Factory("Celebi")])
hp = @p1.currentHP
@battle.performMove(@p1, @battle.getMove('Wish'))
@battle.endTurn()
receiver = @team1.at(1)
receiver.currentHP = 1
@battle.performSwitch(@p1, 1)
@battle.endTurn()
receiver.currentHP.should.equal(Math.round(hp / 2) + 1)
it "fails if the pokemon faints", ->
shared.create.call(this, team1: [Factory("Magikarp"), Factory("Celebi")])
@battle.performMove(@p1, @battle.getMove('Wish'))
@battle.endTurn()
@p1.currentHP = 1
@battle.performMove(@p2, @battle.getMove("Tackle"))
@battle.endTurn()
@team1.has(Attachment.Wish).should.be.false
describe "Counter", ->
it "returns double the damage if attacked by a physical move", ->
shared.create.call(this)
@battle.performMove(@p2, @battle.getMove('Tackle'))
@battle.performMove(@p1, @battle.getMove('Counter'))
dhp1 = @p1.stat('hp') - @p1.currentHP
dhp2 = @p2.stat('hp') - @p2.currentHP
dhp2.should.equal 2*dhp1
it "fails if attacked by a special move", ->
shared.create.call(this)
mock = @sandbox.mock(@battle.getMove('Counter'))
mock.expects('fail').once()
@battle.performMove(@p2, @battle.getMove('ThunderShock'))
@battle.performMove(@p1, @battle.getMove('Counter'))
mock.verify()
it "fails if not hit by an attack this turn", ->
shared.create.call(this)
mock = @sandbox.mock(@battle.getMove('Counter'))
mock.expects('fail').once()
@controller.makeMove(@id1, "Splash")
@controller.makeMove(@id2, "Tackle")
@battle.performMove(@p1, @battle.getMove('Counter'))
mock.verify()
it "hits the pokemon who is currently in that slot, not who was", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove('U-turn'))
@battle.performSwitch(@p1, 1)
@battle.performMove(@p2, @battle.getMove('Counter'))
@team1.first().currentHP.should.be.lessThan(@team1.first().stat('hp'))
@team1.at(1).currentHP.should.equal(@team1.at(1).stat('hp'))
it "doesn't activate Rough Skin if it fails", ->
shared.create.call this,
team2: [Factory("Garchomp", ability: "Rough Skin")]
counter = @battle.getMove("Counter")
mock = @sandbox.mock(counter)
mock.expects('fail').once()
@battle.performMove(@p1, counter)
mock.verify()
@p1.currentHP.should.equal(@p1.stat('hp'))
describe "Perish Song", ->
it "attaches to every pokemon in the field", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove('Perish Song'))
result = _.all @battle.getActivePokemon(), (pokemon) ->
pokemon.has(Attachment.PerishSong)
result.should.be.true
it "faints pokemon at the end of 4 turns", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove('Perish Song'))
@battle.endTurn()
@battle.endTurn()
@battle.endTurn()
result = _.all @battle.getActivePokemon(), (pokemon) ->
!pokemon.isFainted()
result.should.be.true
@battle.endTurn()
result = _.all @battle.getActivePokemon(), (pokemon) ->
pokemon.isFainted()
result.should.be.true
it "fails against Pokemon with Soundproof", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Soundproof")])
@battle.performMove(@p2, @battle.getMove("Perish Song"))
@p1.has(Attachment.PerishSong).should.be.false
@p2.has(Attachment.PerishSong).should.be.true
describe "Techno Blast", ->
it "is Fire-type if the user holds a Burn Drive", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Burn Drive")]
move = @battle.getMove('Techno Blast')
type = move.getType(@battle, @p1, @p2)
type.should.equal "Fire"
it "is Water-type if the user holds a Douse Drive", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Douse Drive")]
move = @battle.getMove('Techno Blast')
type = move.getType(@battle, @p1, @p2)
type.should.equal "Water"
it "is Electric-type if the user holds a Shock Drive", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Shock Drive")]
move = @battle.getMove('Techno Blast')
type = move.getType(@battle, @p1, @p2)
type.should.equal "Electric"
it "is Ice-type if the user holds a Chill Drive", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Chill Drive")]
move = @battle.getMove('Techno Blast')
type = move.getType(@battle, @p1, @p2)
type.should.equal "Ice"
it "is Normal-type otherwise", ->
shared.create.call(this)
move = @battle.getMove('Techno Blast')
type = move.getType(@battle, @p1, @p2)
type.should.equal "Normal"
describe "Synchronoise", ->
it "fails on Pokemon not of the user's type", ->
shared.create.call this,
team1: [Factory("Magikarp")]
team2: [Factory("Celebi")]
move = @battle.getMove('Synchronoise')
mock = @sandbox.mock(move)
mock.expects('fail').once()
@battle.performMove(@p1, move)
mock.verify()
it "works on Pokemon that share one type with the user", ->
shared.create.call this,
team1: [Factory("Ferrothorn")]
team2: [Factory("Celebi")]
hp = @p2.currentHP
@battle.performMove(@p1, @battle.getMove('Synchronoise'))
@p2.currentHP.should.be.lessThan hp
describe "Roost", ->
shared.shouldDoNoDamage('Roost')
it "removes the user's flying type during the turn", ->
shared.create.call this,
team1: [Factory("Gliscor")]
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Roost"))
('Flying' in @p1.types).should.be.false
@p1.types.should.eql ['Ground']
it "turns pure-Flying pokemon into Normal pokemon", ->
shared.create.call this,
team1: [Factory("Tornadus")]
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Roost"))
('Flying' in @p1.types).should.be.false
@p1.types.should.eql ['Normal']
it "keeps the user's types the same if non-Flying", ->
shared.create.call this,
team1: [Factory("Celebi")]
oldTypes = @p1.types
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Roost"))
@p1.types.should.eql oldTypes
it "restores the user's old types after the turn", ->
shared.create.call this,
team1: [Factory("Gliscor")]
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Roost"))
@battle.endTurn()
('Flying' in @p1.types).should.be.true
('Ground' in @p1.types).should.be.true
describe 'Encore', ->
shared.shouldDoNoDamage('Encore')
it "fails if the target has not yet used a move", ->
shared.create.call this,
team1: [Factory("Magikarp", evs: {speed: 4})]
encore = @battle.getMove('Encore')
mock = @sandbox.mock(encore).expects('fail').once()
@battle.performMove(@p1, encore)
mock.verify()
it "forces the target to repeat its last used move", ->
shared.create.call(this)
@battle.performMove(@p2, @battle.getMove('Splash'))
@battle.performMove(@p1, @battle.getMove('Encore'))
@battle.beginTurn()
@p2.validMoves().should.eql [ @battle.getMove('Splash') ]
it "changes the target's decision if it has not moved yet", ->
shared.create.call(this, team1: [Factory("Magikarp", evs: {speed: 4})])
@battle.performMove(@p2, @battle.getMove('Splash'))
@battle.recordMove(@id1, @battle.getMove('Encore'))
@battle.recordMove(@id2, @battle.getMove('Tackle'))
@battle.continueTurn()
@p2.lastMove.should.equal @battle.getMove('Splash')
it "lasts 4 turns if the user moved after the target", ->
shared.create.call(this, team1: [Factory("Magikarp", evs: {speed: 4})])
@battle.performMove(@p1, @battle.getMove('Splash'))
@battle.performMove(@p2, @battle.getMove('Encore'))
@p1.has(Attachment.Encore).should.be.true
@battle.endTurn()
@battle.endTurn()
@battle.endTurn()
@p1.has(Attachment.Encore).should.be.true
@battle.endTurn()
@p1.has(Attachment.Encore).should.be.false
it "lasts 3 turns if the user moved before the target", ->
shared.create.call(this, team1: [Factory("Magikarp", evs: {speed: 4})])
@battle.performMove(@p2, @battle.getMove('Splash'))
@battle.recordMove(@id1, @battle.getMove('Encore'))
@battle.recordMove(@id2, @battle.getMove('Tackle'))
@battle.continueTurn()
@p2.has(Attachment.Encore).should.be.true
@battle.endTurn()
@battle.endTurn()
@p2.has(Attachment.Encore).should.be.true
@battle.endTurn()
@p2.has(Attachment.Encore).should.be.false
it "fails on certain moves like mimic", ->
shared.create.call(this)
encore = @battle.getMove('Encore')
mock = @sandbox.mock(encore).expects('fail').once()
@battle.performMove(@p2, @battle.getMove('Mimic'))
@battle.performMove(@p1, @battle.getMove('Encore'))
mock.verify()
it "fails if the pokemon is already encored", ->
shared.create.call(this)
encore = @battle.getMove('Encore')
mock = @sandbox.mock(encore).expects('fail').once()
@battle.performMove(@p2, @battle.getMove('Splash'))
@battle.performMove(@p1, @battle.getMove('Encore'))
@battle.performMove(@p1, @battle.getMove('Encore'))
mock.verify()
it "fails if the move has 0 PP", ->
shared.create.call(this)
encore = @battle.getMove('Encore')
mock = @sandbox.mock(encore).expects('fail').once()
@p2.setPP(@battle.getMove('Splash'), 1)
@battle.performMove(@p2, @battle.getMove('Splash'))
@battle.performMove(@p1, @battle.getMove('Encore'))
mock.verify()
it "removes itself if the pokemon's move reaches 0 PP", ->
shared.create.call(this)
@p2.setPP(@battle.getMove('Splash'), 2)
@battle.performMove(@p2, @battle.getMove('Splash'))
@battle.performMove(@p1, @battle.getMove('Encore'))
@battle.endTurn()
@p2.has(Attachment.Encore).should.be.true
@battle.performMove(@p2, @battle.getMove('Splash'))
@battle.endTurn()
@p2.has(Attachment.Encore).should.be.false
describe "Swagger", ->
it "confuses the target", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Swagger"))
@p2.has(Attachment.Confusion).should.be.true
it "boosts the target's attack by two stages", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Swagger"))
@p2.stages.attack.should.equal 2
describe "Flatter", ->
it "confuses the target", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Flatter"))
@p2.has(Attachment.Confusion).should.be.true
it "boosts the target's special attack by one stage", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Flatter"))
@p2.stages.specialAttack.should.equal 1
describe "Torment", ->
shared.shouldDoNoDamage("Torment")
shared.shouldFailIfUsedTwice("Torment")
it "prevents the target from using its last move", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Torment"))
@battle.performMove(@p2, @battle.getMove("Splash"))
@battle.beginTurn()
@p2.validMoves().should.eql [ @battle.getMove('Tackle') ]
@battle.performMove(@p2, @battle.getMove("Tackle"))
@battle.beginTurn()
@p2.validMoves().should.eql [ @battle.getMove('Splash') ]
it "still works even if a new pokemon has just switched in", ->
shared.create.call(this, team2: [Factory("Magikarp"), Factory("Magikarp")])
@battle.performSwitch(@p2, 1)
@battle.performMove(@p1, @battle.getMove("Torment"))
@battle.beginTurn()
@team2.first().validMoves().should.eql [ @battle.getMove('Splash'), @battle.getMove('Tackle') ]
xit "does not force the Outrage user to struggle", ->
xit "does not prevent consecutive use of Struggle", ->
describe "Psych Up", ->
it "copies the target's stat changes", ->
shared.create.call(this)
@p1.stages.specialAttack = 5
@p1.stages.evasion = 2
@p2.stages.attack = 6
@p2.stages.defense = -2
@p2.stages.speed = -1
@battle.performMove(@p1, @battle.getMove("Psych Up"))
@p1.stages.should.eql {
attack: 6, defense: -2, specialAttack: 0, specialDefense: 0,
speed: -1, accuracy: 0, evasion: 0
}
describe "Spikes", ->
it "puts a layer of spikes on the opponents' field", ->
shared.create.call(this)
@team2.has(Attachment.Spikes).should.be.false
@battle.performMove(@p1, @battle.getMove("Spikes"))
@team2.has(Attachment.Spikes).should.be.true
it "fails if there are 3 layers", ->
shared.create.call(this)
mock = @sandbox.mock(@battle.getMove('Spikes')).expects('fail').once()
for i in [1..4]
@battle.performMove(@p1, @battle.getMove("Spikes"))
mock.verify()
it "deals a minimum of 1 damage", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Shedinja")]
@battle.performMove(@p1, @battle.getMove("Stealth Rock"))
@battle.performSwitch(@team2.first(), 1)
pokemon = @team2.first()
(pokemon.stat('hp') - pokemon.currentHP).should.equal(1)
it "does damage to pokemon switching in according to # of layers", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Magikarp")]
hp = @team2.first().stat('hp')
@battle.performMove(@p1, @battle.getMove("Spikes"))
@battle.performSwitch(@p2, 1)
(hp - @team2.first().currentHP).should.equal Math.floor(hp / 8)
@team2.first().currentHP = hp
@battle.performMove(@p1, @battle.getMove("Spikes"))
@battle.performSwitch(@team2.first(), 1)
(hp - @team2.first().currentHP).should.equal Math.floor(hp / 6)
@team2.first().currentHP = hp
@battle.performMove(@p1, @battle.getMove("Spikes"))
@battle.performSwitch(@team2.first(), 1)
(hp - @team2.first().currentHP).should.equal Math.floor(hp / 4)
it "does not affect pokemon with immunity to ground", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Magikarp", item: "Air Balloon")]
@battle.performMove(@p1, @battle.getMove("Spikes"))
@battle.performSwitch(@team2.first(), 1)
@team2.first().currentHP.should.equal @team2.first().stat('hp')
it "affects replacements", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Magikarp")]
@battle.performMove(@p1, @battle.getMove("Spikes"))
@battle.recordSwitch(@id2, 1)
@battle.performReplacements()
hp = @team2.first().stat('hp')
(hp - @team2.first().currentHP).should.equal Math.floor(hp / 8)
describe "Stealth Rock", ->
shared.shouldDoNoDamage("Stealth Rock")
shared.shouldFailIfUsedTwice("Stealth Rock")
it "puts a layer of rocks on the opponents' field", ->
shared.create.call(this)
@team2.has(Attachment.StealthRock).should.be.false
@battle.performMove(@p1, @battle.getMove("Stealth Rock"))
@team2.has(Attachment.StealthRock).should.be.true
it "does damage to pokemon switching in according to type", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Moltres")]
@battle.performMove(@p1, @battle.getMove("Stealth Rock"))
@battle.performSwitch(@team2.first(), 1)
pokemon = @team2.first()
hp = pokemon.stat('hp')
(hp - pokemon.currentHP).should.equal Math.floor(hp / 2)
@battle.performSwitch(@team2.first(), 1)
pokemon = @team2.first()
hp = pokemon.stat('hp')
(hp - pokemon.currentHP).should.equal Math.floor(hp / 8)
it "deals a minimum of 1 damage", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Aron", level: 1)]
@battle.performMove(@p1, @battle.getMove("Stealth Rock"))
@battle.performSwitch(@team2.first(), 1)
pokemon = @team2.first()
(pokemon.stat('hp') - pokemon.currentHP).should.equal(1)
it "affects replacements", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Magikarp")]
@battle.performMove(@p1, @battle.getMove("Stealth Rock"))
@battle.recordSwitch(@id2, 1)
@battle.performReplacements()
hp = @team2.first().stat('hp')
(hp - @team2.first().currentHP).should.equal Math.floor(hp / 8)
describe "Toxic Spikes", ->
it "puts a layer of toxic spikes on the opponents' field", ->
shared.create.call(this)
@team2.has(Attachment.ToxicSpikes).should.be.false
@battle.performMove(@p1, @battle.getMove("Toxic Spikes"))
@team2.has(Attachment.ToxicSpikes).should.be.true
it "fails if there are 2 layers", ->
shared.create.call(this)
mock = @sandbox.mock(@battle.getMove('Toxic Spikes'))
mock.expects('fail').once()
for i in [1..3]
@battle.performMove(@p1, @battle.getMove("Toxic Spikes"))
mock.verify()
it "poisons or severely poisons the switch-in if not immune", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Magikarp")]
@battle.performMove(@p1, @battle.getMove("Toxic Spikes"))
@battle.performSwitch(@team2.first(), 1)
@team2.first().has(Status.Poison).should.be.true
@battle.performMove(@p1, @battle.getMove("Toxic Spikes"))
@battle.performSwitch(@team2.first(), 1)
@team2.first().has(Status.Toxic).should.be.true
it "does not affect the pokemon if it's immune to Poison", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Ferrothorn")]
@battle.performMove(@p1, @battle.getMove("Toxic Spikes"))
@battle.performSwitch(@team2.first(), 1)
@team2.first().has(Status.Poison).should.be.false
it "does not affect the pokemon if it's immune to Ground", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Gyarados")]
@battle.performMove(@p1, @battle.getMove("Toxic Spikes"))
@battle.performSwitch(@team2.first(), 1)
@team2.first().has(Status.Poison).should.be.false
it "disappears if the pokemon switching in is a grounded Poison", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Drapion")]
@battle.performMove(@p1, @battle.getMove("Toxic Spikes"))
@battle.performSwitch(@team2.first(), 1)
@team2.has(Attachment.ToxicSpikes).should.be.false
it "doesn't disappear if the pokemon switching in is a flying Poison", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Drapion", item: "Air Balloon")]
@battle.performMove(@p1, @battle.getMove("Toxic Spikes"))
@battle.performSwitch(@team2.first(), 1)
@team2.has(Attachment.ToxicSpikes).should.be.true
it "affects replacements", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Magikarp")]
@battle.performMove(@p1, @battle.getMove("Toxic Spikes"))
@battle.recordSwitch(@id2, 1)
@battle.performReplacements()
@team2.first().has(Status.Poison).should.be.true
testWeatherMove = (moveName, weather, item) ->
describe moveName, ->
it "changes the weather to #{weather.toLowerCase()} for 5 turns", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.weather.should.equal(weather)
@battle.weatherDuration.should.equal 5
it "changes the weather to #{weather.toLowerCase()} for 8 turns if user holds a #{item}", ->
shared.create.call this,
team1: [Factory("Magikarp", item: item)]
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.weather.should.equal(weather)
@battle.weatherDuration.should.equal 8
it "fails if the weather is already #{weather}", ->
shared.create.call this,
team1: [Factory("Magikarp", item: item)]
@battle.setWeather(weather)
theMove = @battle.getMove(moveName)
mock = @sandbox.mock(theMove).expects('fail').once()
@battle.performMove(@p1, theMove)
mock.verify()
testWeatherMove("Rain Dance", Weather.RAIN, "Damp Rock")
testWeatherMove("Sunny Day", Weather.SUN, "Heat Rock")
testWeatherMove("Hail", Weather.HAIL, "Icy Rock")
testWeatherMove("Sandstorm", Weather.SAND, "Smooth Rock")
testStatusMove = (moveName, status) ->
describe moveName, ->
it "changes the status on a Pokemon if it has no status", ->
shared.create.call(this)
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
@p2.has(status).should.be.true
it "does not change the status if Pokemon already has a status", ->
shared.create.call(this)
oldStatus = if status == Status.Paralyze
Status.Sleep
else
Status.Paralyze
@p2.attach(oldStatus)
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
@p2.has(status).should.be.false
@p2.has(oldStatus).should.be.true
testStatusMove("Dark Void", Status.Sleep)
testStatusMove("GrassWhistle", Status.Sleep)
testStatusMove("Hypnosis", Status.Sleep)
testStatusMove("Lovely Kiss", Status.Sleep)
testStatusMove("Poison Gas", Status.Poison)
testStatusMove("PoisonPowder", Status.Poison)
testStatusMove("Sing", Status.Sleep)
testStatusMove("Sleep Powder", Status.Sleep)
testStatusMove("Spore", Status.Sleep)
testStatusMove("Stun Spore", Status.Paralyze)
testStatusMove("Thunder Wave", Status.Paralyze)
testStatusMove("Toxic", Status.Toxic)
testStatusMove("Will-O-Wisp", Status.Burn)
testEffectMove = (moveName, Effect) ->
describe moveName, ->
it "adds the effect to the Pokemon if it doesn't have it", ->
shared.create.call(this)
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
@p2.has(Effect).should.be.true
it "fails if the Pokemon already has it", ->
shared.create.call(this)
move = @battle.getMove(moveName)
mock = @sandbox.mock(move)
mock.expects('fail').once()
shared.biasRNG.call(this, "randInt", 'confusion turns', 4)
@p2.attach(Effect, {@battle})
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
mock.verify()
testEffectMove 'Confuse Ray', Attachment.Confusion
testEffectMove 'Supersonic', Attachment.Confusion
testEffectMove 'Sweet Kiss', Attachment.Confusion
testEffectMove 'Teeter Dance', Attachment.Confusion
describe "Trump Card", ->
it "has 40 base power by default", ->
shared.create.call(this)
@battle.getMove('Trump Card').basePower(@battle, @p1, @p2).should.equal 40
it "has 50 base power if the move has 3 PP after use", ->
shared.create.call this,
team1: [Factory("Corphish")]
pp = @p1.pp(@battle.getMove('Trump Card'))
for x in [0...pp - 3]
@p1.reducePP(@battle.getMove('Trump Card'))
@p1.pp(@battle.getMove('Trump Card')).should.equal 3
@battle.getMove('Trump Card').basePower(@battle, @p1, @p2).should.equal 50
it "has 60 base power if the move has 2 PP after use", ->
shared.create.call this,
team1: [Factory("Corphish")]
pp = @p1.pp(@battle.getMove('Trump Card'))
for x in [0...pp - 2]
@p1.reducePP(@battle.getMove('Trump Card'))
@p1.pp(@battle.getMove('Trump Card')).should.equal 2
@battle.getMove('Trump Card').basePower(@battle, @p1, @p2).should.equal 60
it "has 80 base power if the move has 1 PP after use", ->
shared.create.call this,
team1: [Factory("Corphish")]
pp = @p1.pp(@battle.getMove('Trump Card'))
for x in [0...pp - 1]
@p1.reducePP(@battle.getMove('Trump Card'))
@p1.pp(@battle.getMove('Trump Card')).should.equal 1
@battle.getMove('Trump Card').basePower(@battle, @p1, @p2).should.equal 80
it "has 200 base power if the move has 0 PP after use", ->
shared.create.call this,
team1: [Factory("Corphish")]
pp = @p1.pp(@battle.getMove('Trump Card'))
for x in [0...pp - 0]
@p1.reducePP(@battle.getMove('Trump Card'))
@p1.pp(@battle.getMove('Trump Card')).should.equal 0
@battle.getMove('Trump Card').basePower(@battle, @p1, @p2).should.equal 200
testRandomSwitchMove = (moveName) ->
describe moveName, ->
it "should switch opponent out to a random member", ->
shared.create.call(this, team2: [Factory("Magikarp"), Factory("Abra")])
target = @team2.at(1)
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
@team2.first().should.equal target
it "does not force a switch if the target faints", ->
shared.create.call(this, team1: [Factory("Magikarp")], team2: [Factory("Magikarp"), Factory("Abra")])
move = @battle.getMove(moveName)
mock = @sandbox.mock(@team2)
# Whirlwind, Roar
if !move.isNonDamaging()
mock.expects("switch").never()
@team2.at(0).setHP(1)
@battle.performMove(@p1, move)
mock.verify()
it "does not force a switch if the hit was indirect", ->
shared.create.call(this, team1: [Factory("Magikarp")], team2: [Factory("Magikarp"), Factory("Abra")])
move = @battle.getMove(moveName)
mock = @sandbox.mock(@team2)
# Whirlwind, Roar
if !move.isNonDamaging()
mock.expects("switch").never()
@p2.attach(Attachment.Substitute, hp: 1)
@battle.performMove(@p1, move)
mock.verify()
it "does not force a switch if the user faints", ->
shared.create.call(this, team1: [Factory("Magikarp")], team2: [Factory("Magikarp", ability: "Iron Barbs"), Factory("Abra")])
move = @battle.getMove(moveName)
mock = @sandbox.mock(@team2)
# Whirlwind, Roar
if !move.isNonDamaging()
mock.expects("switch").never()
else
mock.expects("switch").once()
@p1.currentHP = 1
@battle.performMove(@p1, move)
mock.verify()
it "should not force switches if opponent is the last pokemon", ->
shared.create.call(this, team2: [Factory("Magikarp")])
mock = @sandbox.mock(@team2)
mock.expects("switch").never()
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
mock.verify()
it "cancels the opponent's move if user moves first", ->
shared.create.call this,
team1: [ Factory("Magikarp", moves: [ moveName ], evs: {speed: 4}), Factory("Magikarp")]
team2: [ Factory("Magikarp", moves: [ moveName ]), Factory("Magikarp")]
spy1 = @sandbox.spy(@team1, 'switch')
spy2 = @sandbox.spy(@team2, 'switch')
@controller.makeMove(@id1, moveName)
@controller.makeMove(@id2, moveName)
spy1.callCount.should.equal(0)
spy2.callCount.should.equal(1)
testRandomSwitchMove "Roar"
testRandomSwitchMove "Whirlwind"
testRandomSwitchMove "Dragon Tail"
testRandomSwitchMove "Circle Throw"
testTrappingMove = (name) ->
describe name, ->
it "blocks switching", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove(name))
@battle.endTurn()
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.true
@p2.has(Attachment.Trap).should.be.true
@p1.has(Attachment.TrapLeash).should.be.true
it "deals 1/16 of the pokemon's max hp every turn", ->
shared.create.call(this, 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 / 16)
@p2.currentHP.should.equal expected
it "deals a minimum of 1 damage", ->
shared.create.call(this, team2: [Factory("Shedinja")])
@p2.types = [ "Normal" ]
move = @battle.getMove(name)
@sandbox.stub(move, 'calculateDamage', -> 0)
@battle.performMove(@p1, move)
@battle.endTurn()
@p2.currentHP.should.equal(0)
it "lasts several turns", ->
shared.create.call(this, team2: [Factory("Blissey")])
shared.biasRNG.call(this, "randInt", 'trapping move', 5)
@battle.performMove(@p1, @battle.getMove(name))
@p2.currentHP = @p2.stat('hp')
# loop for 5 more turns. One of the turns has already passed.
# These moves hurt for 5 moves and wear off on the 6th.
for i in [1..5]
@p2.has(Attachment.Trap).should.be.true
@battle.endTurn()
# Test if the actual damage checks out. It should have damaged only 5 times
maxHP = @p2.stat('hp')
expected = maxHP - (Math.floor(maxHP / 16) * 5)
@p2.currentHP.should.equal expected
it "wears off after a certain number of turns", ->
shared.create.call(this, team2: [Factory("Blissey")])
shared.biasRNG.call(this, "randInt", 'trapping move', 5)
@battle.performMove(@p1, @battle.getMove(name))
@battle.endTurn()
# loop for 5 more turns. One of the turns has already passed.
# These moves hurt for 5 moves and wear off on the 6th. We need
# turn number 6 to pass before the attachment should wear off.
for i in [1..5]
@battle.endTurn()
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.false
@p2.has(Attachment.Trap).should.be.false
@p1.has(Attachment.TrapLeash).should.be.false
it "does not reset the duration if used twice", ->
shared.create.call(this, team2: [Factory("Blissey")])
shared.biasRNG.call(this, "randInt", 'trapping move', 5)
@battle.performMove(@p1, @battle.getMove(name))
@battle.endTurn()
@battle.performMove(@p1, @battle.getMove(name))
@battle.endTurn()
# loop for 4 more turns. These moves wear off after numTurns + 1.
# 2 have already passed.
for i in [1..4]
@battle.endTurn()
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.false
@p2.has(Attachment.Trap).should.be.false
@p1.has(Attachment.TrapLeash).should.be.false
it "wears off if the user switches", ->
shared.create.call(this, team1: [Factory("Blissey"), Factory("Magikarp")])
@battle.performMove(@p1, @battle.getMove(name))
@battle.endTurn()
@controller.makeSwitch(@id1, 1)
@controller.makeMove(@id2, "Splash")
@p2.isSwitchBlocked().should.be.false
@p2.has(Attachment.Trap).should.be.false
it "wears off if the user faints", ->
shared.create.call(this, team1: [Factory("Heatran")], team2: [Factory("Blissey")])
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove(name))
@battle.endTurn()
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.true
@p2.has(Attachment.Trap).should.be.true
@battle.recordMove(@id2, @battle.getMove("Tackle"))
@battle.continueTurn()
@battle.endTurn()
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.false
@p2.has(Attachment.Trap).should.be.false
it "properly removes from a list of attachments", ->
shared.create.call(this, team1: [Factory("Blissey"), Factory("Magikarp")])
@battle.performMove(@p1, @battle.getMove(name))
@battle.endTurn()
@battle.recordMove(@id1, @battle.getMove("Protect"))
@battle.recordMove(@id2, @battle.getMove("Splash"))
@battle.continueTurn()
@battle.endTurn()
@battle.beginTurn()
@controller.makeSwitch(@id1, 1)
@controller.makeMove(@id2, "Splash")
@p2.isSwitchBlocked().should.be.false
@p2.has(Attachment.Trap).should.be.false
it "is always 7 turns if the user is holding grip claw", ->
shared.create.call(this, team1: [Factory("Magikarp", item: "Grip Claw")])
shared.biasRNG.call(this, "randInt", 'trapping move', 5)
move = @battle.getMove(name)
@battle.performMove(@p1, move)
@battle.performMove(@p2, @battle.getMove("Recover"))
# The user is damaged 7 times, but the attachment actually lasts
# for 8 turns including the turn it is first used.
for i in [1..8]
@p2.has(Attachment.Trap).should.be.true
@battle.endTurn()
@p2.has(Attachment.Trap).should.be.false
it "does not start the effect if it hits a substitute", ->
shared.create.call(this, team2: [Factory("Blissey")])
@p2.attach(Attachment.Substitute, hp: 1)
@battle.performMove(@p1, @battle.getMove(name))
@battle.endTurn()
@p2.isSwitchBlocked().should.be.false
@p2.has(Attachment.Trap).should.be.false
@p1.has(Attachment.TrapLeash).should.be.false
testTrappingMove "Bind"
testTrappingMove "Clamp"
testTrappingMove "Fire Spin"
testTrappingMove "Magma Storm"
testTrappingMove "Sand Tomb"
testTrappingMove "Whirlpool"
testTrappingMove "Wrap"
describe "Attract", ->
shared.shouldDoNoDamage('Attract')
shared.shouldFailIfUsedTwice "Attract",
team1: [Factory("Magikarp", gender: "M", evs: {speed: 4})]
team2: [Factory("Magikarp", gender: "F")]
it "has a 50% chance to immobilize a pokemon", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "M", evs: {speed: 4})]
team2: [Factory("Magikarp", gender: "F")]
shared.biasRNG.call(this, "next", 'attract chance', 0) # 100% immobilizes
mock = @sandbox.mock(@battle.getMove('Tackle')).expects('execute').never()
@battle.performMove(@p1, @battle.getMove("Attract"))
@battle.performMove(@p2, @battle.getMove("Tackle"))
mock.verify()
it "has a 50% chance to not immobilize a pokemon", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "M", evs: {speed: 4})]
team2: [Factory("Magikarp", gender: "F")]
shared.biasRNG.call(this, "next", 'attract chance', .5) # 0% immobilizes
mock = @sandbox.mock(@battle.getMove('Tackle')).expects('execute').once()
@battle.performMove(@p1, @battle.getMove("Attract"))
@battle.performMove(@p2, @battle.getMove("Tackle"))
mock.verify()
it "fails if the Pokemon are not opposite genders", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "F")]
team2: [Factory("Magikarp", gender: "F")]
mock = @sandbox.mock(@battle.getMove('Attract')).expects('fail').once()
@battle.performMove(@p1, @battle.getMove("Attract"))
mock.verify()
it "disappears if the source is no longer active", ->
shared.create.call this,
team1: (Factory("Magikarp", gender: "M") for x in [1..2])
team2: [Factory("Magikarp", gender: "F")]
@battle.performMove(@p1, @battle.getMove('Attract'))
@battle.performMove(@p2, @battle.getMove('Splash'))
@p2.has(Attachment.Attract).should.be.true
@battle.performSwitch(@team1.first(), 1)
@battle.performMove(@p2, @battle.getMove('Splash'))
@p2.has(Attachment.Attract).should.be.false
describe "Reflect", ->
it "halves physical damage", ->
shared.create.call(this)
tackle = @battle.getMove('Tackle')
tackle.modifyDamage(@battle, @p1, @p2).should.equal(0x1000)
@team2.attach(Attachment.Reflect)
tackle.modifyDamage(@battle, @p1, @p2).should.equal(0x800)
it "does not halve non-physical damage", ->
shared.create.call(this)
thundershock = @battle.getMove('ThunderShock')
thundershock.modifyDamage(@battle, @p1, @p2).should.equal(0x1000)
@team2.attach(Attachment.Reflect)
thundershock.modifyDamage(@battle, @p1, @p2).should.equal(0x1000)
it "lasts five turns", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove('Reflect'))
for i in [1..5]
@team1.has(Attachment.Reflect).should.be.true
@battle.endTurn()
@team1.has(Attachment.Reflect).should.be.false
it "fails if the user already used it", ->
shared.create.call(this)
mock = @sandbox.mock(@battle.getMove('Reflect'))
mock.expects('fail').once()
@battle.performMove(@p1, @battle.getMove('Reflect'))
@battle.performMove(@p1, @battle.getMove('Reflect'))
mock.verify()
it "does not halve on critical hits", ->
shared.create.call(this)
tackle = @battle.getMove('Tackle')
@team2.attach(Attachment.Reflect)
@p1.crit = true
tackle.modifyDamage(@battle, @p1, @p2).should.equal(0x1000)
describe "Light Screen", ->
it "halves special damage", ->
shared.create.call(this)
thundershock = @battle.getMove('ThunderShock')
thundershock.modifyDamage(@battle, @p1, @p2).should.equal(0x1000)
@team2.attach(Attachment.LightScreen)
thundershock.modifyDamage(@battle, @p1, @p2).should.equal(0x800)
it "does not halve non-physical damage", ->
shared.create.call(this)
tackle = @battle.getMove('Tackle')
tackle.modifyDamage(@battle, @p1, @p2).should.equal(0x1000)
@team2.attach(Attachment.LightScreen)
tackle.modifyDamage(@battle, @p1, @p2).should.equal(0x1000)
it "lasts five turns", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove('Light Screen'))
for i in [1..5]
@team1.has(Attachment.LightScreen).should.be.true
@battle.endTurn()
@team1.has(Attachment.LightScreen).should.be.false
it "fails if the user already used it", ->
shared.create.call(this)
mock = @sandbox.mock(@battle.getMove('Light Screen'))
mock.expects('fail').once()
@battle.performMove(@p1, @battle.getMove('Light Screen'))
@battle.performMove(@p1, @battle.getMove('Light Screen'))
mock.verify()
it "does not halve on critical hits", ->
shared.create.call(this)
thundershock = @battle.getMove('ThunderShock')
@team2.attach(Attachment.LightScreen)
@p1.crit = true
thundershock.modifyDamage(@battle, @p1, @p2).should.equal(0x1000)
describe "Rapid Spin", ->
it "removes spikes", ->
shared.create.call this
@battle.performMove(@p1, @battle.getMove("Spikes"))
@team2.has(Attachment.Spikes).should.be.true
@battle.performMove(@p2, @battle.getMove("Rapid Spin"))
@team2.has(Attachment.Spikes).should.be.false
it "removes stealth rock", ->
shared.create.call this
@battle.performMove(@p1, @battle.getMove("Stealth Rock"))
@team2.has(Attachment.StealthRock).should.be.true
@battle.performMove(@p2, @battle.getMove("Rapid Spin"))
@team2.has(Attachment.StealthRock).should.be.false
it "removes toxic spikes", ->
shared.create.call this
@battle.performMove(@p1, @battle.getMove("Toxic Spikes"))
@team2.has(Attachment.ToxicSpikes).should.be.true
@battle.performMove(@p2, @battle.getMove("Rapid Spin"))
@team2.has(Attachment.ToxicSpikes).should.be.false
it "removes multiple layers of entry hazards", ->
shared.create.call this
@battle.performMove(@p1, @battle.getMove("Spikes"))
@battle.performMove(@p1, @battle.getMove("Spikes"))
@battle.performMove(@p1, @battle.getMove("Spikes"))
@team2.has(Attachment.Spikes).should.be.true
@battle.performMove(@p2, @battle.getMove("Rapid Spin"))
@team2.has(Attachment.Spikes).should.be.false
it "removes trapping moves", ->
shared.create.call this
@battle.performMove(@p1, @battle.getMove("Fire Spin"))
@p2.has(Attachment.Trap).should.be.true
@p1.has(Attachment.TrapLeash).should.be.true
@battle.performMove(@p2, @battle.getMove("Rapid Spin"))
@p2.has(Attachment.Trap).should.be.false
@p1.has(Attachment.TrapLeash).should.be.false
it "removes leech seed", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Leech Seed"))
@p2.has(Attachment.LeechSeed).should.be.true
@battle.performMove(@p2, @battle.getMove("Rapid Spin"))
@p2.has(Attachment.LeechSeed).should.be.false
it "does not remove entry hazards if the user faints from rough skin", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Rough Skin")])
@p2.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Spikes"))
@battle.performMove(@p2, @battle.getMove("Rapid Spin"))
@team2.has(Attachment.Spikes).should.be.true
it "does remove entry hazards if the user faints from life orb", ->
shared.create.call(this, team2: [Factory("Magikarp", item: "Life Orb")])
@p2.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Spikes"))
@battle.performMove(@p2, @battle.getMove("Rapid Spin"))
@team2.has(Attachment.Spikes).should.be.false
describe "Brick Break", ->
it "shatters Light Screen", ->
shared.create.call(this)
@team2.attach(Attachment.LightScreen)
@battle.performMove(@p1, @battle.getMove('Brick Break'))
@team2.has(Attachment.LightScreen).should.be.false
it "shatters Reflect", ->
shared.create.call(this)
@team2.attach(Attachment.Reflect)
@battle.performMove(@p1, @battle.getMove('Brick Break'))
@team2.has(Attachment.Reflect).should.be.false
it "shatters both at the same time", ->
shared.create.call(this)
@team2.attach(Attachment.Reflect)
@team2.attach(Attachment.LightScreen)
@battle.performMove(@p1, @battle.getMove('Brick Break'))
@team2.has(Attachment.Reflect).should.be.false
@team2.has(Attachment.LightScreen).should.be.false
it "shatters before damage calculation", ->
shared.create.call(this)
move = @battle.getMove("Brick Break")
damage = move.calculateDamage(@battle, @p1, @p2)
@team2.attach(Attachment.Reflect)
@team2.attach(Attachment.LightScreen)
spy = @sandbox.spy(move, 'calculateDamage')
@battle.performMove(@p1, move)
spy.returned(damage).should.be.true
it "does not shatter if the target is immune", ->
shared.create.call(this)
@team2.attach(Attachment.Reflect)
@team2.attach(Attachment.LightScreen)
@sandbox.stub(@p2, 'isImmune', -> true)
@battle.performMove(@p1, @battle.getMove('Brick Break'))
@team2.has(Attachment.Reflect).should.be.true
@team2.has(Attachment.LightScreen).should.be.true
it "does not shatter if Brick Break misses", ->
shared.create.call(this)
@team2.attach(Attachment.Reflect)
@team2.attach(Attachment.LightScreen)
shared.biasRNG.call(this, 'randInt', 'miss', 101)
@battle.performMove(@p1, @battle.getMove('Brick Break'))
@team2.has(Attachment.Reflect).should.be.true
@team2.has(Attachment.LightScreen).should.be.true
describe "Return", ->
it "has 102 base power", ->
shared.create.call(this)
move = @battle.getMove("Return")
bp = move.basePower(@battle, @p1, @p2)
bp.should.equal 102
describe "Frustration", ->
it "has 102 base power", ->
shared.create.call(this)
move = @battle.getMove("Frustration")
bp = move.basePower(@battle, @p1, @p2)
bp.should.equal 102
describe "Fake Out", ->
it "flinches the enemy", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Fake Out"))
@p2.has(Attachment.Flinch).should.be.true
it "fails if the Pokemon has been in play one turn or more", ->
shared.create.call(this)
@battle.endTurn()
@battle.beginTurn()
fakeOut = @battle.getMove("Fake Out")
mock = @sandbox.mock(fakeOut).expects('fail').once()
@battle.performMove(@p1, fakeOut)
mock.verify()
it "works if the Pokemon is newly switched", ->
shared.create.call(this, team1: (Factory("Magikarp") for x in [1..2]))
@battle.performSwitch(@team1.first(), 1)
@battle.endTurn()
@battle.beginTurn()
fakeOut = @battle.getMove("Fake Out")
mock = @sandbox.mock(fakeOut).expects('fail').never()
@battle.performMove(@team1.first(), fakeOut)
mock.verify()
describe "Focus Energy", ->
it "adds a Focus Energy attachment to the user", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Focus Energy"))
@p1.has(Attachment.FocusEnergy).should.be.true
it "fails the second time it is used", ->
shared.create.call(this)
move = @battle.getMove("Focus Energy")
mock = @sandbox.mock(move)
mock.expects('fail').once()
@battle.performMove(@p1, move)
@battle.performMove(@p1, move)
mock.verify()
testIdentifyMove = (moveName, types) ->
describe moveName, ->
shared.shouldDoNoDamage(moveName)
shared.shouldFailIfUsedTwice(moveName)
it "makes the target vulnerable to #{types} moves", ->
shared.create.call(this, team2: [Factory("Spiritomb")])
_.each(types, ((type) -> @isImmune(type).should.be.true), @p2)
@battle.performMove(@p1, @battle.getMove(moveName))
@p2.has(Attachment.Identify).should.be.true
_.each(types, ((type) -> @isImmune(type).should.be.false), @p2)
it "makes the target's evasion be ignored", ->
shared.create.call(this)
@p2.boost(evasion: 2)
@p2.editBoosts().evasion.should.equal 2
@battle.performMove(@p1, @battle.getMove(moveName))
@p2.editBoosts().evasion.should.equal 0
testIdentifyMove("Foresight", ["Normal", "Fighting"])
testIdentifyMove("Odor Sleuth", ["Normal", "Fighting"])
testIdentifyMove("Miracle Eye", ["Psychic"])
describe "Conversion", ->
it "changes the user's type to a random type based on moves", ->
shared.create.call this,
team1: [Factory("Porygon", moves: [
"Thunderbolt", "Ice Beam", "Conversion" ])]
shared.biasRNG.call(this, "randInt", 'conversion types', 0)
@p1.types.should.eql [ "Normal" ]
@battle.performMove(@p1, @battle.getMove("Conversion"))
@p1.types.should.eql [ 'Electric' ]
it "ignores Conversion as part of the move types", ->
shared.create.call this,
team1: [Factory("Porygon", moves: [ "Conversion", "Ice Beam" ])]
shared.biasRNG.call(this, "randInt", 'conversion types', 0)
@p1.types = [ "Fake Type" ]
@battle.performMove(@p1, @battle.getMove("Conversion"))
@p1.types.should.eql [ 'Ice' ]
it "fails if there is no type to convert to", ->
shared.create.call this,
team1: [Factory("Porygon", moves: [ "Conversion" ])]
move = @battle.getMove("Conversion")
mock = @sandbox.mock(move)
mock.expects('fail').once()
@battle.performMove(@p1, move)
mock.verify()
describe "Conversion 2", ->
it "fails if the target has not yet moved", ->
shared.create.call(this)
move = @battle.getMove("Conversion 2")
mock = @sandbox.mock(move).expects('fail').once()
@p2.lastMove = null
@battle.performMove(@p1, move)
mock.verify()
it "changes user's type to one resisting or is immune to target's move", ->
shared.create.call(this)
@p1.types = [ "Normal" ]
move = @battle.getMove("Ember")
type = move.type
@p2.lastMove = move
@battle.performMove(@p1, @battle.getMove("Conversion 2"))
@p1.types.should.have.length 1
util.typeEffectiveness(type, @p1.types).should.be.lessThan 1
describe "Defense Curl", ->
it "raises defense by 1 stage", ->
shared.create.call(this)
@p1.stages.defense.should.equal 0
@battle.performMove(@p1, @battle.getMove("Defense Curl"))
@p1.stages.defense.should.equal 1
it "attaches a volatile attachment", ->
shared.create.call(this)
@p1.has(Attachment.DefenseCurl).should.be.false
@battle.performMove(@p1, @battle.getMove("Defense Curl"))
@p1.has(Attachment.DefenseCurl).should.be.true
describe "Focus Punch", ->
it "causes the user to flinch if hit", ->
shared.create.call(this)
@battle.recordMove(@id1, @battle.getMove("Focus Punch"))
@battle.recordMove(@id2, @battle.getMove("Tackle"))
@battle.continueTurn()
@p2.currentHP.should.not.be.lessThan @p2.stat('hp')
it "does not cause flinching if hit by a non-damaging move", ->
shared.create.call(this)
@battle.recordMove(@id1, @battle.getMove("Focus Punch"))
@battle.recordMove(@id2, @battle.getMove("Will-O-Wisp"))
@battle.continueTurn()
@p2.currentHP.should.be.lessThan @p2.stat('hp')
it "does not cause flinching if behind a substitute", ->
shared.create.call(this)
@p1.attach(Attachment.Substitute, hp: (@p1.currentHP >> 2))
@battle.recordMove(@id1, @battle.getMove("Focus Punch"))
@battle.recordMove(@id2, @battle.getMove("Tackle"))
@battle.continueTurn()
@p2.currentHP.should.be.lessThan @p2.stat('hp')
it "does not cause flinching if sub fades", ->
shared.create.call(this)
@p1.attach(Attachment.Substitute, hp: 1)
@battle.recordMove(@id1, @battle.getMove("Focus Punch"))
@battle.recordMove(@id2, @battle.getMove("Tackle"))
@battle.continueTurn()
@p2.currentHP.should.be.lessThan @p2.stat('hp')
it "does not cause flinching if hit by a move last turn", ->
shared.create.call(this)
@battle.performMove(@p2, @battle.getMove("Tackle"))
@battle.endTurn()
@battle.beginTurn()
@battle.recordMove(@id1, @battle.getMove("Focus Punch"))
@battle.continueTurn()
@p2.currentHP.should.be.lessThan @p2.stat('hp')
it "does not cause flinching afterwards", ->
shared.create.call(this, team2: [Factory("Magikarp", evs: {speed: 4})])
focusPunch = @battle.getMove("Focus Punch")
@battle.recordMove(@id1, @battle.getMove("Focus Punch"))
@battle.recordMove(@id2, @battle.getMove("Splash"))
@battle.continueTurn()
@battle.endTurn()
@battle.beginTurn()
hp = @p2.currentHP
@battle.recordMove(@id1, @battle.getMove("Tackle"))
@battle.recordMove(@id2, @battle.getMove("Tackle"))
@battle.continueTurn()
@p2.currentHP.should.be.lessThan(hp)
describe "Magnet Rise", ->
shared.shouldDoNoDamage("Magnet Rise")
shared.shouldFailIfUsedTwice("Magnet Rise")
it "makes the user immune to ground moves", ->
shared.create.call(this)
@p1.isImmune("Ground").should.be.false
@battle.performMove(@p1, @battle.getMove("Magnet Rise"))
@p1.isImmune("Ground").should.be.true
it "lasts 5 turns", ->
shared.create.call(this)
@p1.isImmune("Ground").should.be.false
@battle.performMove(@p1, @battle.getMove("Magnet Rise"))
for i in [1..5]
@p1.isImmune("Ground").should.be.true
@battle.endTurn()
@p1.isImmune("Ground").should.be.false
testLockOnMove = (moveName) ->
describe moveName, ->
shared.shouldDoNoDamage(moveName)
shared.shouldFailIfUsedTwice(moveName)
it "makes the user's next move never miss on this target", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove(moveName))
shared.biasRNG.call(this, 'randInt', 'miss', 101)
missMove = @battle.getMove("Tackle")
missMove.willMiss(@battle, @p1, @p2)
.should.be.false
it "lasts only two turns", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove(moveName))
for i in [1..2]
@p1.has(Attachment.LockOn).should.be.true
@battle.endTurn()
@p1.has(Attachment.LockOn).should.be.false
it "hits through two-turn fade-away moves", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.performMove(@p2, @battle.getMove("Fly"))
missMove = @battle.getMove("Tackle")
missMove.willMiss(@battle, @p1, @p2)
.should.be.false
it "does not hit through Protect", ->
shared.create.call(this)
move = @battle.getMove(moveName)
mock = @sandbox.mock(move).expects('hit').never()
@battle.recordMove(@id2, move)
@battle.determineTurnOrder()
@battle.performMove(@p1, @battle.getMove("Protect"))
@battle.performMove(@p2, move)
mock.verify()
it "does not affect accuracy on another target", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Magikarp")]
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.performSwitch(@p2, 1)
shared.biasRNG.call(this, 'randInt', 'miss', 101)
missMove = @battle.getMove("Tackle")
missMove.willMiss(@battle, @p1, @team2.first())
.should.be.true
it "re-locks on when used on another target", ->
shared.create.call this,
team2: [Factory("Magikarp"), Factory("Magikarp")]
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.performSwitch(@p2, 1)
@battle.performMove(@p1, @battle.getMove(moveName))
@p1.has(Attachment.LockOn).should.be.true
testLockOnMove("Lock-On")
testLockOnMove("Mind Reader")
describe "Minimize", ->
it "boosts the user's evasion by 2", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Minimize"))
@p1.stages.evasion.should.equal 2
it "adds a Minimize volatile attachment to the user", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Minimize"))
@p1.has(Attachment.Minimize).should.be.true
testStompMove = (moveName) ->
describe moveName, ->
it "doubles its base power when the target is minimized", ->
shared.create.call(this)
@p2.attach(Attachment.Minimize)
move = @battle.getMove(moveName)
bp = move.basePower(@battle, @p1, @p2)
bp.should.equal(move.power * 2)
it "has normal base power otherwise", ->
shared.create.call(this)
move = @battle.getMove(moveName)
bp = move.basePower(@battle, @p1, @p2)
bp.should.equal(move.power)
it "has a 30% chance to flinch", ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'flinch', 0) # 100% chance
@battle.performMove(@p1, @battle.getMove(moveName))
@p2.has(Attachment.Flinch).should.be.true
testStompMove("Stomp")
testStompMove("Steamroller")
testMeanLookMove = (moveName) ->
describe moveName, ->
shared.shouldDoNoDamage(moveName)
shared.shouldFailIfUsedTwice(moveName)
it "blocks the target from switching", ->
shared.create.call(this)
@p2.isSwitchBlocked().should.be.false
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.true
it "wears off if the user faints", ->
shared.create.call(this)
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.endTurn()
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.true
@p2.has(Attachment.MeanLook).should.be.true
@battle.recordMove(@id2, @battle.getMove("Tackle"))
@battle.continueTurn()
@battle.endTurn()
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.false
@p2.has(Attachment.MeanLook).should.be.false
it "wears off if the user switches", ->
shared.create.call(this, team1: [Factory("Blissey"), Factory("Magikarp")])
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.endTurn()
@controller.makeSwitch(@id1, 1)
@controller.makeMove(@id2, "Splash")
@p2.isSwitchBlocked().should.be.false
@p2.has(Attachment.MeanLook).should.be.false
testMeanLookMove("Block")
testMeanLookMove("Mean Look")
testMeanLookMove("Spider Web")
testRechargeMove = (moveName) ->
describe moveName, ->
it "blocks the target from switching the next turn", ->
shared.create.call(this)
@p1.isSwitchBlocked().should.be.false
theMove = @battle.getMove(moveName)
@sandbox.stub(theMove, 'baseDamage', -> 2)
@battle.performMove(@p1, theMove)
@battle.endTurn()
@battle.beginTurn()
@p1.isSwitchBlocked().should.be.true
it "blocks the target from picking a new move the next turn", ->
shared.create.call(this)
theMove = @battle.getMove(moveName)
@sandbox.stub(theMove, 'baseDamage', -> 2)
@battle.performMove(@p1, theMove)
@battle.endTurn()
@battle.beginTurn()
for move in @p1.moves
@p1.isMoveBlocked(move).should.be.true
it "automatically selects a special recharge move the next turn", ->
shared.create.call(this)
specialMove = @battle.getMove("Recharge")
theMove = @battle.getMove(moveName)
@sandbox.stub(theMove, 'baseDamage', -> 2)
@battle.performMove(@p1, theMove)
@battle.endTurn()
@battle.beginTurn()
@battle.requests.should.not.have.property @id1
action = @battle.getAction(@p1)
should.exist(action)
action.should.have.property("move")
action.move.should.equal(specialMove)
it "prevents the user from moving the next turn", ->
shared.create.call(this)
spy = @sandbox.spy(@p1, 'beforeMove')
theMove = @battle.getMove(moveName)
@sandbox.stub(theMove, 'baseDamage', -> 2)
@battle.performMove(@p1, theMove)
@battle.endTurn()
@battle.beginTurn()
@battle.continueTurn()
spy.returned(false).should.be.true
testRechargeMove("Hyper Beam")
testRechargeMove("Giga Impact")
testRechargeMove("Blast Burn")
testRechargeMove("Frenzy Plant")
testRechargeMove("Hydro Cannon")
testRechargeMove("Roar of Time")
testRechargeMove("Rock Wrecker")
describe 'Clear Smog', ->
it 'removes all status boosts on the target pokemon', ->
shared.create.call(this)
# Create artificial boosts.
@p2.stages.defense = -3
@p2.stages.specialAttack = 4
@battle.performMove(@p1, @battle.getMove("Clear Smog"))
neutralBoosts = {
attack: 0, defense: 0, specialAttack: 0, specialDefense: 0,
speed: 0, evasion: 0, accuracy: 0
}
@p2.stages.should.eql neutralBoosts
it "does not reset stats if it hits a substitute", ->
shared.create.call(this)
@p2.attach(Attachment.Substitute, hp: 1)
# Create artificial boosts.
@p2.stages.defense = -3
@p2.stages.specialAttack = 4
@battle.performMove(@p1, @battle.getMove("Clear Smog"))
neutralBoosts = {
attack: 0, defense: 0, specialAttack: 0, specialDefense: 0,
speed: 0, evasion: 0, accuracy: 0
}
@p2.stages.should.not.eql neutralBoosts
testMomentumMove = (moveName) ->
describe moveName, ->
it "prevents the user from switching", ->
shared.create.call(this)
@p1.isSwitchBlocked().should.be.false
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.endTurn()
@battle.beginTurn()
@p1.isSwitchBlocked().should.be.true
it "locks the user into using this particular move", ->
shared.create.call this,
team1: [ Factory("Shuckle") ]
momentumMove = @battle.getMove(moveName)
@p1.moves = [ momentumMove, @battle.getMove("Rest") ]
@p1.resetAllPP()
@battle.performMove(@p1, momentumMove)
@battle.endTurn()
@battle.beginTurn()
for move in @p1.moves
if move == momentumMove
@p1.isMoveBlocked(move).should.be.false
else
@p1.isMoveBlocked(move).should.be.true
it "stops if it misses", ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", 'miss', 100)
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.endTurn()
@battle.beginTurn()
@p1.has(Attachment.Momentum).should.be.false
it "lasts 5 turns", ->
shared.create.call(this)
for i in [1..5]
@battle.performMove(@p1, @battle.getMove(moveName))
@p1.has(Attachment.Momentum).should.be.true
@battle.endTurn()
@battle.beginTurn()
@p1.has(Attachment.Momentum).should.be.false
it "doubles base power every time", ->
shared.create.call(this)
move = @battle.getMove(moveName)
basePower = move.power
for i in [1..5]
bp = move.basePower(@battle, @p1, @p2)
bp.should.equal(basePower * Math.pow(2, i - 1))
@battle.performMove(@p1, move)
@battle.endTurn()
@battle.beginTurn()
@p2.currentHP = @p2.stat('hp')
it "doubles base power again if user has defense curl's effect", ->
shared.create.call(this)
@p1.attach(Attachment.DefenseCurl)
move = @battle.getMove(moveName)
basePower = 2 * move.power
for i in [1..5]
bp = move.basePower(@battle, @p1, @p2)
bp.should.equal(basePower * Math.pow(2, i - 1))
@battle.performMove(@p1, move)
@battle.endTurn()
@battle.beginTurn()
@p2.currentHP = @p2.stat('hp')
testMomentumMove("Rollout")
testMomentumMove("Ice Ball")
describe "Me First", ->
it "fails if the user goes second", ->
shared.create.call(this)
move = @battle.getMove("Me First")
mock = @sandbox.mock(move).expects('fail').once()
@battle.recordMove(@id1, move)
@battle.recordMove(@id2, @battle.getMove("Tackle"))
@battle.determineTurnOrder()
@battle.delay @p1
@battle.popAction(@p2)
@battle.performMove(@p1, move)
mock.verify()
it "uses the target's move", ->
shared.create.call(this)
move = @battle.getMove("Tackle")
mock = @sandbox.mock(move).expects('execute').once()
@battle.recordMove(@id1, @battle.getMove("Me First"))
@battle.recordMove(@id2, move)
@battle.determineTurnOrder()
@battle.bump @p1
@battle.performMove(@p1, @battle.getMove("Me First"))
mock.verify()
it "has 1.5x the base power of the target's move", ->
shared.create.call(this)
move = @battle.getMove("Tackle")
@battle.recordMove(@id1, @battle.getMove("Me First"))
@battle.recordMove(@id2, move)
@battle.determineTurnOrder()
@battle.bump @p1
@battle.performMove(@p1, @battle.getMove("Me First"))
move.modifyAttack(@battle, @p1, @p2).should.equal 0x1800
for moveName in [ "Chatter", "Counter", "Covet", "Focus Punch", "Me First",
"Metal Burst", "Mirror Coat", "Struggle", "Thief" ]
do (moveName) ->
it "fails if the target is using #{moveName}", ->
shared.create.call(this)
move = @battle.getMove("Me First")
mock = @sandbox.mock(move).expects('fail').once()
@battle.recordMove(@id1, move)
@battle.recordMove(@id2, @battle.getMove(moveName))
@battle.determineTurnOrder()
@battle.bump @p1
@battle.performMove(@p1, move)
mock.verify()
it "fails if the target is using a non-damaging move", ->
shared.create.call(this)
move = @battle.getMove("Me First")
mock = @sandbox.mock(move).expects('fail').once()
@battle.recordMove(@id1, move)
@battle.recordMove(@id2, @battle.getMove("Splash"))
@battle.determineTurnOrder()
@battle.bump @p1
@battle.performMove(@p1, move)
mock.verify()
describe "Dream Eater", ->
it "fails if the target is not asleep", ->
shared.create.call(this)
move = @battle.getMove("Dream Eater")
mock = @sandbox.mock(move).expects('fail').once()
@battle.performMove(@p1, move)
mock.verify()
it "does not fail if the target is asleep", ->
shared.create.call(this)
move = @battle.getMove("Dream Eater")
mock = @sandbox.mock(move).expects('fail').never()
@p2.attach(Status.Sleep)
@battle.performMove(@p1, move)
mock.verify()
it "drains 1/2 of damage", ->
shared.create.call(this)
@p1.currentHP = initialHP = 1
@p2.attach(Status.Sleep)
@battle.performMove(@p1, @battle.getMove("Dream Eater"))
damage = @p2.stat('hp') - @p2.currentHP
healed = Math.floor(damage / 2)
@p1.currentHP.should.equal(initialHP + healed)
describe "Camouflage", ->
it "changes the user's type to Ground type in Wi-Fi battles", ->
shared.create.call(this)
@p1.types = [ "Normal" ]
@battle.performMove(@p1, @battle.getMove("Camouflage"))
@p1.types.should.eql [ "Ground" ]
describe "Charge", ->
shared.shouldDoNoDamage("Charge")
it "raises the user's special defense by 1", ->
shared.create.call(this)
@p1.stages.specialDefense.should.equal 0
@battle.performMove(@p1, @battle.getMove("Charge"))
@p1.stages.specialDefense.should.equal 1
it "doubles the base power of the user's next move", ->
shared.create.call(this)
move = @battle.getMove("Thunderbolt")
move.modifyAttack(@battle, @p1, @p2).should.equal 0x1000
@battle.performMove(@p1, @battle.getMove("Charge"))
move.modifyAttack(@battle, @p1, @p2).should.equal 0x2000
it "doesn't double the next move if it is non-electric type", ->
shared.create.call(this)
move = @battle.getMove("Flamethrower")
move.modifyAttack(@battle, @p1, @p2).should.equal 0x1000
@battle.performMove(@p1, @battle.getMove("Charge"))
move.modifyAttack(@battle, @p1, @p2).should.equal 0x1000
it "can be used twice in a row", ->
shared.create.call(this)
move = @battle.getMove("Charge")
mock = @sandbox.mock(move).expects('fail').never()
@battle.performMove(@p1, move)
@battle.endTurn()
@battle.performMove(@p1, move)
@battle.endTurn()
@p1.has(Attachment.Charge).should.be.true
mock.verify()
describe "Tri Attack", ->
it "has a 20% chance to activate its secondary effect", ->
shared.create.call(this)
shared.biasRNG.call(this, "next", 'secondary effect', 0) # 100% chance
@battle.performMove(@p1, @battle.getMove("Tri Attack"))
@p2.hasStatus().should.be.true
it "has a 1/3 chance for the secondary effect to be paralysis", ->
shared.create.call(this)
shared.biasRNG.call(this, "next", 'secondary effect', 0) # 100% chance
shared.biasRNG.call(this, "randInt", 'tri attack effect', 0) # par
@battle.performMove(@p1, @battle.getMove("Tri Attack"))
@p2.has(Status.Paralyze).should.be.true
it "has a 1/3 chance for the secondary effect to be burn", ->
shared.create.call(this)
shared.biasRNG.call(this, "next", 'secondary effect', 0) # 100% chance
shared.biasRNG.call(this, "randInt", 'tri attack effect', 1) # brn
@battle.performMove(@p1, @battle.getMove("Tri Attack"))
@p2.has(Status.Burn).should.be.true
it "has a 1/3 chance for the secondary effect to be freeze", ->
shared.create.call(this)
shared.biasRNG.call(this, "next", 'secondary effect', 0) # 100% chance
shared.biasRNG.call(this, "randInt", 'tri attack effect', 2) # frz
@battle.performMove(@p1, @battle.getMove("Tri Attack"))
@p2.has(Status.Freeze).should.be.true
describe "Mirror Move", ->
it "copies the opponent's last move", ->
shared.create.call(this)
move = @battle.getMove("Tackle")
mock = @sandbox.mock(move).expects('execute').once()
@p2.lastMove = move
@battle.performMove(@p1, @battle.getMove("Mirror Move"))
mock.verify()
it "fails if the opponent has not moved the past turn", ->
shared.create.call(this)
move = @battle.getMove("Mirror Move")
mock = @sandbox.mock(move).expects('fail').once()
@battle.performMove(@p1, move)
mock.verify()
it "fails if the move does not have a `mirror` flag", ->
shared.create.call(this)
@p2.lastMove = @battle.getMove("Dragon Dance")
move = @battle.getMove("Mirror Move")
mock = @sandbox.mock(move).expects('fail').once()
@battle.performMove(@p1, move)
mock.verify()
# TODO: Find out if this is true: when is lastMove nullified?
it "fails if the pokemon was unable to move the previous turn"
describe "Leech Seed", ->
shared.shouldFailIfUsedTwice("Leech Seed")
it "saps 1/8 of the target's max HP each turn", ->
shared.create.call(this)
move = @battle.getMove("Leech Seed")
@p1.currentHP = 1
@battle.performMove(@p1, move)
@battle.endTurn()
p = @p2
fullHP = p.stat('hp')
(fullHP - p.currentHP).should.equal Math.floor(fullHP / 8)
@p1.currentHP.should.equal(fullHP - p.currentHP + 1)
it "saps up to 1/8 HP", ->
shared.create.call(this)
move = @battle.getMove("Leech Seed")
@p1.currentHP = 1
@p2.currentHP = 1
@battle.performMove(@p1, move)
@battle.endTurn()
@p1.currentHP.should.equal 2
it "saps a minimum of 1 damage", ->
shared.create.call(this, team2: [Factory("Shedinja")])
move = @battle.getMove("Leech Seed")
@battle.performMove(@p1, move)
@battle.endTurn()
@p2.currentHP.should.equal(0)
it "always misses on Grass type Pokemon", ->
shared.create.call(this)
move = @battle.getMove("Leech Seed")
mock = @sandbox.mock(move).expects('afterMiss').once()
@p2.types = [ "Water", "Grass"]
@battle.performMove(@p1, move)
mock.verify()
it "does not trigger if the user has fainted", ->
shared.create.call(this, team1: (Factory("Magikarp") for x in [1..2]))
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Leech Seed"))
@p1.faint()
@battle.endTurn()
@p2.currentHP.should.equal @p2.stat('hp')
it "leeches next pokemon to switch in at that slot", ->
shared.create.call(this, team1: (Factory("Magikarp") for x in [1..2]))
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Leech Seed"))
@p1.faint()
@battle.endTurn()
@battle.performSwitch(@team1.first(), 1)
@p2.currentHP.should.equal @p2.stat('hp')
newFirst = @team1.first()
newFirst.currentHP = 1
@battle.endTurn()
hp = @p2.stat('hp') - @p2.currentHP
hp.should.be.greaterThan(0)
newFirst.currentHP.should.equal(hp + 1)
it "stops if the target has fainted", ->
shared.create.call(this, team2: (Factory("Magikarp") for x in [1..2]))
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Leech Seed"))
@p2.faint()
@battle.endTurn()
@battle.performSwitch(@team2.first(), 1)
@team2.first().currentHP.should.equal @team2.first().stat('hp')
@p1.currentHP.should.equal(1)
testProtectCounterMove = (moveName) ->
it "fails if the user moves last this turn", ->
shared.create.call(this)
move = @battle.getMove(moveName)
mock = @sandbox.mock(move).expects('fail').once()
@battle.determineTurnOrder()
@battle.performMove(@p1, move)
mock.verify()
it "has decreasing chances of success", ->
shared.create.call(this)
p = @p1
for x in [0..7]
attachment = p.attach(Attachment.ProtectCounter)
attachment.successChance().should.equal Math.pow(2, x)
attachment = p.attach(Attachment.ProtectCounter)
attachment.successChance().should.equal Math.pow(2, 32)
it "fails if not successful", ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", 'protect', 2)
move = @battle.getMove(moveName)
mock = @sandbox.mock(move).expects('fail').once()
@battle.recordMove(@id2, @battle.getMove("Tackle"))
@battle.determineTurnOrder()
@battle.performMove(@p1, move)
mock.verify()
it "resets to 100% chance of success if move fails", ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", 'protect', 2)
move = @battle.getMove(moveName)
@battle.recordMove(@id2, @battle.getMove("Tackle"))
@battle.determineTurnOrder()
@battle.performMove(@p1, move)
@p1.has(Attachment.ProtectCounter).should.be.false
it "resets to 100% chance of success if user selects a different move", ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", 'protect', 1)
move = @battle.getMove(moveName)
@battle.recordMove(@id2, @battle.getMove("Tackle"))
@battle.determineTurnOrder()
@battle.performMove(@p1, move)
@battle.endTurn()
@p1.has(Attachment.ProtectCounter).should.be.true
@battle.performMove(@p1, @battle.getMove('Splash'))
@battle.endTurn()
@p1.has(Attachment.ProtectCounter).should.be.false
testProtectMove = (moveName) ->
describe moveName, ->
testProtectCounterMove(moveName)
it "completely protects the user from attacks", ->
shared.create.call(this)
move = @battle.getMove("Tackle")
mock = @sandbox.mock(move).expects('hit').never()
@battle.recordMove(@id2, move)
@battle.determineTurnOrder()
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.performMove(@p2, move)
mock.verify()
it "does not carry over to the next turn", ->
shared.create.call(this)
move = @battle.getMove("Tackle")
@battle.recordMove(@id2, move)
@battle.determineTurnOrder()
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.performMove(@p2, move)
@battle.endTurn()
mock = @sandbox.mock(move).expects('hit').once()
@battle.performMove(@p2, move)
mock.verify()
it "does not protect the user from attacks without the protect flag", ->
shared.create.call(this)
move = @battle.getMove("Feint")
mock = @sandbox.mock(move).expects('hit').once()
@battle.recordMove(@id2, move)
@battle.determineTurnOrder()
@battle.performMove(@p1, @battle.getMove(moveName))
@battle.performMove(@p2, move)
mock.verify()
testProtectMove 'Protect'
testProtectMove 'Detect'
describe "Endure", ->
testProtectCounterMove("Endure")
it "always survives moves that would otherwise KO with 1 HP", ->
shared.create.call(this)
move = @battle.getMove("Tackle")
hp = @p1.currentHP
@sandbox.stub(move, 'baseDamage', -> hp)
@battle.recordMove(@id2, @battle.getMove("Tackle"))
@battle.determineTurnOrder()
@battle.performMove(@p1, @battle.getMove("Endure"))
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p1.currentHP.should.equal 1
it "disappears at the end of the turn", ->
shared.create.call(this)
@battle.recordMove(@id2, @battle.getMove("Tackle"))
@battle.determineTurnOrder()
@battle.performMove(@p1, @battle.getMove("Endure"))
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p1.has(Attachment.Endure).should.be.true
@battle.endTurn()
@p1.has(Attachment.Endure).should.be.false
describe "Feint", ->
it "removes the Protect attachment, if any, on the target", ->
shared.create.call(this)
@p1.attach(Attachment.Protect)
@p1.has(Attachment.Protect).should.be.true
@battle.performMove(@p2, @battle.getMove("Feint"))
@p1.has(Attachment.Protect).should.be.false
it "removes the Wide Guard attachment, if any, on the target"
it "removes the Quick Guard attachment, if any, on the target"
describe "Payback", ->
it "doubles base power if target has made a move this turn", ->
shared.create.call(this)
move = @battle.getMove("Payback")
spy = @sandbox.spy(move, 'basePower')
@battle.performMove(@p2, @battle.getMove("Splash"))
@battle.performMove(@p1, move)
spy.returned(2 * move.power).should.be.true
it "doesn't double base power otherwise", ->
shared.create.call(this)
move = @battle.getMove("Payback")
spy = @sandbox.spy(move, 'basePower')
@battle.recordMove(@id2, @battle.getMove("Splash"))
@battle.performMove(@p1, move)
spy.returned(move.power).should.be.true
it "doesn't double BP if target moved last turn, but not this turn", ->
shared.create.call(this)
move = @battle.getMove("Payback")
spy = @sandbox.spy(move, 'basePower')
@battle.performMove(@p2, @battle.getMove("Splash"))
@battle.endTurn()
@battle.beginTurn()
@battle.recordMove(@id2, @battle.getMove("Splash"))
@battle.performMove(@p1, move)
spy.returned(move.power).should.be.true
describe "Curse", ->
it "silently chooses a random enemy as the target", ->
shared.create.call(this)
move = @battle.getMove("Curse")
targets = move.getTargets(@battle, @p1)
should.exist targets
targets.should.eql [ @p2 ]
describe "for Ghost types", ->
it "curses the opponent", ->
shared.create.call(this)
@p1.types = [ "Ghost" ]
@battle.performMove(@p1, @battle.getMove("Curse"))
@p2.has(Attachment.Curse).should.be.true
it "damages the user for half of its HP, rounded down", ->
shared.create.call(this)
p = @p1
p.types = [ "Ghost" ]
maxHP = p.stat('hp')
@battle.performMove(@p1, @battle.getMove("Curse"))
p.currentHP.should.equal(maxHP - Math.floor(maxHP / 2))
it "causes the opponent to lose 25% of their HP at end of turns", ->
shared.create.call(this)
@p1.types = [ "Ghost" ]
p = @p2
maxHP = p.stat('hp')
quarterHP = Math.floor(maxHP / 4)
@battle.performMove(@p1, @battle.getMove("Curse"))
p.currentHP.should.equal(maxHP)
@battle.endTurn()
p.currentHP.should.equal(maxHP - quarterHP)
@battle.endTurn()
p.currentHP.should.equal(maxHP - 2 * quarterHP)
it "causes the opponent to lose a minimum of 1 HP per turn", ->
shared.create.call(this, team2: [Factory("Shedinja"), Factory("Magikarp")])
@p1.types = [ "Ghost" ]
@battle.performMove(@p1, @battle.getMove("Curse"))
@battle.endTurn()
@p2.currentHP.should.equal(0)
it "can faint the user", ->
shared.create.call(this)
p = @p1
p.types = [ "Ghost" ]
p.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Curse"))
p.isFainted().should.be.true
describe "for non-Ghost types", ->
it "raises Attack and Defense and lowers Speed", ->
shared.create.call(this)
p = @p1
p.types = [ "Normal" ]
@battle.performMove(@p1, @battle.getMove("Curse"))
p.stages.should.containEql attack: 1, defense: 1, speed: -1
testBasePowerBoostMove = (moveName, rawBasePower, maxBasePower, which) ->
describe moveName, ->
it "increases base power by 20 for each positive stat boost on #{which}", ->
shared.create.call(this)
move = @battle.getMove(moveName)
poke = { user: @p1, target: @p2 }
{user, target} = poke
move.basePower(@battle, user, target).should.equal rawBasePower
poke[which].boost(attack: 1)
move.basePower(@battle, user, target).should.equal rawBasePower + 20
poke[which].boost(defense: 1)
move.basePower(@battle, user, target).should.equal rawBasePower + 40
poke[which].boost(speed: 2, specialAttack: -1)
move.basePower(@battle, user, target).should.equal rawBasePower + 80
it "has a maximum of #{maxBasePower} base power", ->
shared.create.call(this)
move = @battle.getMove(moveName)
poke = { user: @p1, target: @p2 }
{user, target} = poke
# Total base power would theoretically be rawBasePower + 840
stats = {
speed: 6, specialAttack: 6, attack: 6,
defense: 6, specialDefense: 6, evasion: 6, accuracy: 6
}
poke[which].boost(stats)
move.basePower(@battle, user, target).should.equal maxBasePower
testBasePowerBoostMove("Stored Power", 20, 860, "user")
testBasePowerBoostMove("Punishment", 60, 200, "target")
describe "Destiny Bond", ->
it "causes the attacker to faint the turn of use if the user faints", ->
shared.create.call(this)
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Destiny Bond"))
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p2.isFainted().should.be.true
it "causes the attacker to faint any time before the user moves again", ->
shared.create.call(this)
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Destiny Bond"))
@battle.endTurn()
@battle.beginTurn()
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p2.isFainted().should.be.true
it "does not cause attacker to faint after user moves again", ->
shared.create.call(this)
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Destiny Bond"))
@battle.performMove(@p1, @battle.getMove("Splash"))
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p2.isFainted().should.be.false
it "does not cause attacker to faint after user attempts to move again", ->
shared.create.call(this)
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Destiny Bond"))
@battle.endTurn()
@battle.beginTurn()
@p1.attach(Status.Sleep)
@battle.performMove(@p1, @battle.getMove("Tackle"))
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p2.isFainted().should.be.false
it "does not faint attacker if pokemon fainted naturally", ->
shared.create.call(this)
@battle.performMove(@p2, @battle.getMove("Tackle"))
@battle.performMove(@p1, @battle.getMove("Destiny Bond"))
@p1.faint()
@battle.endTurn()
@p2.isFainted().should.be.false
it "does not faint attacker if opponent made move, but did not faint", ->
shared.create.call(this)
@battle.setWeather(Weather.SAND)
@battle.performMove(@p2, @battle.getMove("Tackle"))
@battle.performMove(@p1, @battle.getMove("Destiny Bond"))
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p1.currentHP = 1
@battle.endTurn()
@p1.isFainted().should.be.true
@p2.isFainted().should.be.false
it "does not cause a party member to faint"
it "handles 2+ pokemon destiny-bonding and all fainting at once"
describe "Pursuit", ->
it "doubles base power if the target switches", ->
shared.create.call this,
team2: [ Factory("Magikarp"), Factory("Magikarp") ]
pursuit = @battle.getMove("Pursuit")
spy = @sandbox.spy(pursuit, 'basePower')
@battle.recordSwitch(@id2, 1)
@battle.recordMove(@id1, pursuit)
@battle.continueTurn()
spy.alwaysReturned(2 * pursuit.power).should.be.true
it "doubles BP if a faster target uses a damaging switch move", ->
shared.create.call this,
team2: [ Factory("Magikarp", evs: {speed: 4}), Factory("Magikarp") ]
pursuit = @battle.getMove("Pursuit")
spy = @sandbox.spy(pursuit, 'basePower')
@battle.recordMove(@id2, @battle.getMove("U-turn"))
@battle.recordMove(@id1, pursuit)
@battle.continueTurn()
@battle.recordSwitch(@id2, 1) # battle.forceSwitch makes a request
@battle.continueTurn()
spy.alwaysReturned(2 * pursuit.power).should.be.true
it "doesn't double BP if a slower target uses a damaging switch move", ->
shared.create.call this,
team1: [ Factory("Magikarp", evs: {speed: 4}) ]
team2: [ Factory("Magikarp"), Factory("Magikarp") ]
pursuit = @battle.getMove("Pursuit")
spy = @sandbox.spy(pursuit, 'basePower')
@battle.recordMove(@id1, pursuit)
@battle.recordMove(@id2, @battle.getMove("U-turn"))
@battle.continueTurn()
@battle.recordSwitch(@id2, 1) # battle.forceSwitch makes a request
@battle.continueTurn()
spy.alwaysReturned(pursuit.power).should.be.true
it "has perfect accuracy if target is switching", ->
shared.create.call this,
team2: [ Factory("Magikarp"), Factory("Magikarp") ]
pursuit = @battle.getMove("Pursuit")
spy = @sandbox.spy(pursuit, 'chanceToHit')
pursuit = @battle.getMove("Pursuit")
@battle.recordSwitch(@id2, 1)
@battle.recordMove(@id1, pursuit)
@battle.continueTurn()
spy.alwaysReturned(0).should.be.true
it "runs only once", ->
shared.create.call this,
team2: [ Factory("Magikarp"), Factory("Magikarp") ]
pursuit = @battle.getMove("Pursuit")
mock = @sandbox.mock(pursuit).expects('execute').once()
pursuit = @battle.getMove("Pursuit")
@battle.recordSwitch(@id2, 1)
@battle.recordMove(@id1, pursuit)
@battle.continueTurn()
@battle.recordSwitch(@id2, 1) # battle.forceSwitch makes a request
@battle.continueTurn()
mock.verify()
it "doesn't double BP on a faster Baton Passer", ->
shared.create.call this,
team1: [ Factory("Magikarp") ]
team2: [ Factory("Magikarp", evs: {speed: 4}), Factory("Magikarp") ]
pursuit = @battle.getMove("Pursuit")
spy = @sandbox.spy(pursuit, 'basePower')
@battle.recordMove(@id1, pursuit)
@battle.recordMove(@id2, @battle.getMove("Baton Pass"))
@battle.continueTurn()
@battle.recordSwitch(@id2, 1) # battle.forceSwitch makes a request
@battle.continueTurn()
spy.alwaysReturned(pursuit.power).should.be.true
it "emits the move name when target switches", ->
shared.create.call this,
team1: [ Factory("Magikarp") ]
team2: [ Factory("Magikarp", evs: {speed: 4}), Factory("Magikarp") ]
pursuit = @battle.getMove("Pursuit")
spy = @sandbox.spy(@battle, 'tell')
@battle.recordMove(@id1, pursuit)
@battle.recordSwitch(@id2, 1)
@battle.continueTurn()
spy.calledWith(Protocol.MAKE_MOVE).should.be.true
it "records the move when target switches", ->
shared.create.call this,
team1: [ Factory("Magikarp") ]
team2: [ Factory("Magikarp", evs: {speed: 4}), Factory("Magikarp") ]
pursuit = @battle.getMove("Pursuit")
mock = @sandbox.mock(@p1).expects('recordMove').once()
@battle.recordMove(@id1, pursuit)
@battle.recordSwitch(@id2, 1)
@battle.continueTurn()
mock.verify()
it "does not trigger if asleep", ->
shared.create.call(this)
@p1.attach(Status.Sleep, turns: 4)
pursuit = @battle.getMove("Pursuit")
mock = @sandbox.mock(pursuit).expects('hit').never()
@battle.recordMove(@id1, pursuit)
@battle.recordSwitch(@id2, 1)
@battle.continueTurn()
mock.verify()
it "does not trigger on team members"
describe 'Power Swap', ->
it 'swaps attack and special attack boosts with the target', ->
shared.create.call(this)
@p1.boost(attack: 1, specialAttack: -3, speed: 1)
@p2.boost(attack: 2, specialAttack: 6, defense: -1)
@battle.performMove(@p1, @battle.getMove('Power Swap'))
@p1.stages.should.containEql attack: 2, specialAttack: 6, speed: 1
@p2.stages.should.containEql attack: 1, specialAttack: -3, defense: -1
describe 'Guard Swap', ->
it 'swaps defense and special defense boosts with the target', ->
shared.create.call(this)
@p1.boost(attack: 1, specialDefense: -3, defense: 1)
@p2.boost(speed: 2, specialDefense: 6, defense: -1)
@battle.performMove(@p1, @battle.getMove('Guard Swap'))
@p1.stages.should.containEql defense: -1, specialDefense: 6, attack: 1
@p2.stages.should.containEql defense: 1, specialDefense: -3, speed: 2
describe 'Spite', ->
it 'reduces the last move used by the target by 4', ->
shared.create.call(this)
move = @p2.moves[0]
pp = @p2.pp(move)
@battle.performMove(@p2, move)
@battle.performMove(@p1, @battle.getMove('Spite'))
@p2.pp(move).should.equal(pp - 4 - 1)
it 'fails if the target has not recorded their last move', ->
shared.create.call(this)
spite = @battle.getMove('Spite')
mock = @sandbox.mock(spite).expects('fail').once()
@battle.performMove(@p1, spite)
mock.verify()
it "fails if the target's move has 0 PP", ->
shared.create.call(this)
spite = @battle.getMove('Spite')
move = @p2.moves[0]
mock = @sandbox.mock(spite).expects('fail').once()
@p2.setPP(move, 0)
@battle.performMove(@p2, move)
@battle.performMove(@p1, spite)
mock.verify()
it 'fails if the target no longer knows the move', ->
shared.create.call(this)
spite = @battle.getMove('Spite')
move = @p2.moves[0]
mock = @sandbox.mock(spite).expects('fail').once()
@battle.performMove(@p2, move)
@p2.moves.splice(@p2.moves.indexOf(move), 1)
@battle.performMove(@p1, spite)
mock.verify()
describe 'Wring Out', ->
it 'has 120 power at maximum HP', ->
shared.create.call(this)
move = @battle.getMove('Wring Out')
move.basePower(@battle, @p1, @p2).should.equal 120
it 'has 59 power at half-of-odd HP (rounded down)', ->
shared.create.call(this)
@p1.currentHP >>= 1
move = @battle.getMove('Wring Out')
move.basePower(@battle, @p1, @p2).should.equal 59
it 'has 1 power minimum', ->
shared.create.call(this)
@p1.currentHP = 1
move = @battle.getMove('Wring Out')
move.basePower(@battle, @p1, @p2).should.equal 1
describe 'Assurance', ->
it 'doubles base power if the user was damaged this turn', ->
shared.create.call(this)
move = @battle.getMove('Assurance')
@battle.performMove(@p2, @battle.getMove('Tackle'))
power = move.basePower(@battle, @p1, @p2)
power.should.equal(2 * move.power)
it "doesn't double base power if the user was damaged a different turn", ->
shared.create.call(this)
move = @battle.getMove('Assurance')
@battle.performMove(@p2, @battle.getMove('Tackle'))
@battle.endTurn()
@battle.beginTurn()
power = move.basePower(@battle, @p1, @p2)
power.should.equal(move.power)
it "doesn't double base power if the user has never used a move", ->
shared.create.call(this)
move = @battle.getMove('Assurance')
power = move.basePower(@battle, @p1, @p2)
power.should.equal(move.power)
it "doesn't double base power if hit by a non-damaging move", ->
shared.create.call(this)
move = @battle.getMove('Assurance')
@battle.performMove(@p2, @battle.getMove('Will-O-Wisp'))
power = move.basePower(@battle, @p1, @p2)
power.should.equal(move.power)
describe "Substitute", ->
shared.shouldDoNoDamage("Substitute")
shared.shouldFailIfUsedTwice("Substitute")
it "removes 25% of the owner's health, rounded down", ->
shared.create.call(this)
sub = @battle.getMove('Substitute')
hp = @p1.stat('hp')
@battle.performMove(@p1, sub)
@p1.currentHP.should.equal(hp - (hp >> 2))
it "fails if the pokemon has 25% HP or less", ->
shared.create.call(this)
sub = @battle.getMove('Substitute')
hp = @p1.stat('hp')
mock = @sandbox.mock(sub).expects('fail').once()
@p1.currentHP = hp >> 2
@battle.performMove(@p1, sub)
mock.verify()
it "fails if the pokemon does not have enough total HP", ->
shared.create.call(this, team1: [Factory("Shedinja")])
sub = @battle.getMove('Substitute')
hp = @p1.stat('hp')
mock = @sandbox.mock(sub).expects('fail').once()
@battle.performMove(@p1, sub)
mock.verify()
it "takes damage for the user", ->
shared.create.call(this)
sub = @battle.getMove('Substitute')
subHP = (@p1.stat('hp') >> 2)
@battle.performMove(@p1, sub)
hp = @p1.currentHP
attachment = @p1.get(Attachment.Substitute)
attachment.hp.should.equal subHP
@battle.performMove(@p2, @battle.getMove('Tackle'))
attachment.hp.should.be.lessThan subHP
@p1.currentHP.should.equal hp
it "breaks after taking too much damage", ->
shared.create.call(this)
tackle = @battle.getMove('Tackle')
sub = @battle.getMove('Substitute')
@battle.performMove(@p1, sub)
hp = @p1.currentHP
@p1.has(Attachment.Substitute).should.be.true
@sandbox.stub(tackle, 'baseDamage', -> 9999)
@battle.performMove(@p2, tackle)
@p1.has(Attachment.Substitute).should.be.false
@p1.currentHP.should.equal hp
it "fails most non-damaging moves", ->
shared.create.call(this)
hypnosis = @battle.getMove('Hypnosis')
sub = @battle.getMove('Substitute')
@battle.performMove(@p1, sub)
@p1.has(Attachment.Substitute).should.be.true
mock = @sandbox.mock(hypnosis).expects('fail').once()
@battle.performMove(@p2, hypnosis)
mock.verify()
it "does not fail stat-up moves", ->
shared.create.call(this)
dragonDance = @battle.getMove('Dragon Dance')
sub = @battle.getMove('Substitute')
@battle.performMove(@p1, sub)
@p1.has(Attachment.Substitute).should.be.true
mock = @sandbox.mock(dragonDance).expects('fail').never()
@battle.performMove(@p1, dragonDance)
mock.verify()
it "does not fail non-damaging moves with an authentic flag", ->
shared.create.call(this)
foresight = @battle.getMove('Foresight')
sub = @battle.getMove('Substitute')
@battle.performMove(@p1, sub)
@p1.has(Attachment.Substitute).should.be.true
mock = @sandbox.mock(foresight).expects('fail').never()
@battle.performMove(@p2, foresight)
mock.verify()
it "still stores the actual damage", ->
shared.create.call(this)
gigaDrain = @battle.getMove('Giga Drain')
sub = @battle.getMove('Substitute')
@battle.performMove(@p1, sub)
@p1.has(Attachment.Substitute).should.be.true
spy = @sandbox.spy(@p2, 'drain')
@battle.performMove(@p2, gigaDrain)
spy.calledWith(0).should.be.false
@p1.lastHitBy.damage.should.be.greaterThan 0
it "blocks secondary effects", ->
shared.create.call(this)
shared.biasRNG.call(this, "next", "secondary effect", 0) # always burn
flamethrower = @battle.getMove('Flamethrower')
sub = @battle.getMove('Substitute')
@battle.performMove(@p1, sub)
@p1.has(Attachment.Substitute).should.be.true
@sandbox.stub(flamethrower, 'baseDamage', -> 1)
@battle.performMove(@p2, flamethrower)
@p1.has(Status.Burn).should.be.false
it "blocks secondary effects even if sub fades", ->
shared.create.call(this)
shared.biasRNG.call(this, "next", "secondary effect", 0) # always burn
flamethrower = @battle.getMove('Flamethrower')
sub = @battle.getMove('Substitute')
@battle.performMove(@p1, sub)
@p1.has(Attachment.Substitute).should.be.true
@sandbox.stub(flamethrower, 'baseDamage', -> 9999)
@battle.performMove(@p2, flamethrower)
@p1.has(Status.Burn).should.be.false
it "does not block sandstorm damage", ->
shared.create.call(this)
@battle.setWeather(Weather.SAND)
@battle.performMove(@p1, @battle.getMove('Substitute'))
hp = @p1.currentHP
@battle.endTurn()
@p1.currentHP.should.be.lessThan(hp)
it "does not block leech seed damage", ->
shared.create.call(this)
@battle.performMove(@p2, @battle.getMove('Leech Seed'))
@battle.performMove(@p1, @battle.getMove('Substitute'))
hp = @p1.currentHP
@battle.endTurn()
@p1.currentHP.should.be.lessThan(hp)
it "is baton-passable"
describe "Sucker Punch", ->
it "fails if the target is not moving after the user", ->
shared.create.call(this)
suckerPunch = @battle.getMove('Sucker Punch')
mock = @sandbox.mock(suckerPunch).expects('fail').once()
@battle.performMove(@p1, suckerPunch)
mock.verify()
it "executes normally if the target is moving after the user", ->
shared.create.call(this)
suckerPunch = @battle.getMove('Sucker Punch')
tackle = @battle.getMove('Tackle')
mock = @sandbox.mock(suckerPunch).expects('afterSuccessfulHit').once()
@battle.recordMove(@id2, tackle)
@battle.determineTurnOrder()
@battle.performMove(@p1, suckerPunch)
mock.verify()
it "fails if the target is using a status move", ->
shared.create.call(this)
suckerPunch = @battle.getMove('Sucker Punch')
willOWisp = @battle.getMove('Will-O-Wisp')
mock = @sandbox.mock(suckerPunch).expects('fail').once()
@battle.recordMove(@id2, willOWisp)
@battle.determineTurnOrder()
@battle.performMove(@p1, suckerPunch)
mock.verify()
describe 'Grudge', ->
it "causes the last move to lose all its PP if the user faints", ->
shared.create.call(this)
grudge = @battle.getMove("Grudge")
tackle = @battle.getMove('Tackle')
@p2.moves = [ tackle ]
@p1.currentHP = 1
@battle.performMove(@p1, grudge)
@battle.performMove(@p2, tackle)
@battle.performFaints()
@p1.isFainted().should.be.true
@p2.pp(tackle).should.equal 0
it "causes the attacker to lose PP any time before the user moves again", ->
shared.create.call(this)
grudge = @battle.getMove("Grudge")
tackle = @battle.getMove('Tackle')
@p2.moves = [ tackle ]
@p1.currentHP = 1
@battle.performMove(@p1, grudge)
@battle.endTurn()
@battle.beginTurn()
@battle.performMove(@p2, tackle)
@battle.performFaints()
@p2.pp(tackle).should.equal 0
it "does not cause attacker to lose PP after user moves again", ->
shared.create.call(this)
grudge = @battle.getMove("Grudge")
splash = @battle.getMove('Splash')
tackle = @battle.getMove('Tackle')
@p2.moves = [ tackle ]
@p1.currentHP = 1
@battle.performMove(@p1, grudge)
@battle.performMove(@p1, splash)
@battle.performMove(@p2, tackle)
@battle.performFaints()
@p2.pp(tackle).should.not.equal 0
it "does not trigger from natural causes", ->
shared.create.call(this)
grudge = @battle.getMove("Grudge")
splash = @battle.getMove('Splash')
leechSeed = @battle.getMove('Leech Seed')
@p2.moves = [ leechSeed ]
@p2.resetAllPP()
@p1.currentHP = 1
@battle.performMove(@p1, grudge)
@battle.performMove(@p2, leechSeed)
@battle.endTurn()
@p2.pp(leechSeed).should.equal(@p2.maxPP(leechSeed) - 1)
describe "Stockpile", ->
it "raises def and spdef", ->
shared.create.call(this)
stockpile = @battle.getMove("Stockpile")
@p1.stages.should.containEql defense: 0, specialDefense: 0
@battle.performMove(@p1, stockpile)
@p1.stages.should.containEql defense: 1, specialDefense: 1
it "cannot raise if stockpile is at its limit", ->
shared.create.call(this)
stockpile = @battle.getMove("Stockpile")
for i in [0...Attachment.Stockpile::maxLayers]
@p1.attach(Attachment.Stockpile)
@p1.stages.should.containEql defense: 0, specialDefense: 0
@battle.performMove(@p1, stockpile)
@p1.stages.should.containEql defense: 0, specialDefense: 0
it "fails if stockpile is at its limit", ->
shared.create.call(this)
stockpile = @battle.getMove("Stockpile")
mock = @sandbox.mock(stockpile).expects('fail').once()
for i in [0...Attachment.Stockpile::maxLayers]
@p1.attach(Attachment.Stockpile)
@battle.performMove(@p1, stockpile)
mock.verify()
describe 'Spit Up', ->
it 'fails if user has no stockpiles', ->
shared.create.call(this)
spitUp = @battle.getMove("Spit Up")
mock = @sandbox.mock(spitUp).expects('fail').once()
@battle.performMove(@p1, spitUp)
mock.verify()
it "has 100 base power with 1 layer of stockpiles", ->
shared.create.call(this)
spitUp = @battle.getMove("Spit Up")
@p1.attach(Attachment.Stockpile)
spitUp.basePower(@battle, @p1, @p2).should.equal 100
it "has 200 base power with 2 layers of stockpiles", ->
shared.create.call(this)
spitUp = @battle.getMove("Spit Up")
@p1.attach(Attachment.Stockpile)
@p1.attach(Attachment.Stockpile)
spitUp.basePower(@battle, @p1, @p2).should.equal 200
it "has 300 base power with 3 layers of stockpiles", ->
shared.create.call(this)
spitUp = @battle.getMove("Spit Up")
@p1.attach(Attachment.Stockpile)
@p1.attach(Attachment.Stockpile)
@p1.attach(Attachment.Stockpile)
spitUp.basePower(@battle, @p1, @p2).should.equal 300
it "resets stockpile count to 0", ->
shared.create.call(this)
spitUp = @battle.getMove("Spit Up")
@p1.attach(Attachment.Stockpile)
@p1.get(Attachment.Stockpile).layers.should.equal 1
@battle.performMove(@p1, spitUp)
@p1.has(Attachment.Stockpile).should.be.false
it "loses def/sp. def according to number of stockpiles", ->
shared.create.call(this)
spitUp = @battle.getMove("Spit Up")
@p1.attach(Attachment.Stockpile)
@p1.attach(Attachment.Stockpile)
@p1.stages.should.containEql defense: 0, specialDefense: 0
@battle.performMove(@p1, spitUp)
@p1.stages.should.containEql defense: -2, specialDefense: -2
describe 'Swallow', ->
it 'fails if user has no stockpiles', ->
shared.create.call(this)
swallow = @battle.getMove("Swallow")
mock = @sandbox.mock(swallow).expects('fail').once()
@battle.performMove(@p1, swallow)
mock.verify()
it "heals 25% of its HP, rounded half-down, with 1 layer of stockpiles", ->
shared.create.call(this)
swallow = @battle.getMove("Swallow")
@p1.attach(Attachment.Stockpile)
@p1.currentHP = 1
@battle.performMove(@p1, swallow)
@p1.currentHP.should.equal(1 + util.roundHalfDown(@p1.stat('hp') / 4))
it "heals 50% of its HP, rounded half-down, with 2 layers of stockpiles", ->
shared.create.call(this)
swallow = @battle.getMove("Swallow")
@p1.attach(Attachment.Stockpile)
@p1.attach(Attachment.Stockpile)
@p1.currentHP = 1
@battle.performMove(@p1, swallow)
@p1.currentHP.should.equal(1 + util.roundHalfDown(@p1.stat('hp') / 2))
it "heals all of its HP with 3 layers of stockpiles", ->
shared.create.call(this)
swallow = @battle.getMove("Swallow")
@p1.attach(Attachment.Stockpile)
@p1.attach(Attachment.Stockpile)
@p1.attach(Attachment.Stockpile)
@p1.currentHP = 1
@battle.performMove(@p1, swallow)
@p1.currentHP.should.equal @p1.stat('hp')
it "resets stockpile count to 0", ->
shared.create.call(this)
swallow = @battle.getMove("Swallow")
@p1.attach(Attachment.Stockpile)
@p1.get(Attachment.Stockpile).layers.should.equal 1
@battle.performMove(@p1, swallow)
@p1.has(Attachment.Stockpile).should.be.false
it "loses def/sp. def according to number of stockpiles", ->
shared.create.call(this)
swallow = @battle.getMove("Swallow")
@p1.attach(Attachment.Stockpile)
@p1.attach(Attachment.Stockpile)
@p1.stages.should.containEql defense: 0, specialDefense: 0
@battle.performMove(@p1, swallow)
@p1.stages.should.containEql defense: -2, specialDefense: -2
describe 'Rage', ->
it "raises the user's attack if hit by a move", ->
shared.create.call(this)
rage = @battle.getMove('Rage')
tackle = @battle.getMove('Tackle')
@p1.stages.attack.should.equal 0
@battle.performMove(@p1, rage)
@battle.performMove(@p2, tackle)
@p1.stages.attack.should.equal 1
it "doesn't raise the user's attack if user chooses another move", ->
shared.create.call(this)
rage = @battle.getMove('Rage')
tackle = @battle.getMove('Tackle')
@p1.stages.attack.should.equal 0
@battle.performMove(@p1, rage)
@battle.performMove(@p1, tackle)
@battle.performMove(@p2, tackle)
@p1.stages.attack.should.equal 0
it "doesn't raise the user's attack if hit by a non-damaging move", ->
shared.create.call(this)
rage = @battle.getMove('Rage')
willOWisp = @battle.getMove('Will-O-Wisp')
@p1.stages.attack.should.equal 0
@battle.performMove(@p1, rage)
@battle.performMove(@p2, willOWisp)
@p1.stages.attack.should.equal 0
testRevengeMove = (moveName) ->
describe moveName, ->
it "doubles base power if hit by an attack that turn", ->
shared.create.call(this)
revenge = @battle.getMove(moveName)
tackle = @battle.getMove("Tackle")
@battle.performMove(@p2, tackle)
revenge.basePower(@battle, @p1, @p2).should.equal(2 * revenge.power)
it "doesn't double base power if not hit by an attack that turn", ->
shared.create.call(this)
revenge = @battle.getMove(moveName)
revenge.basePower(@battle, @p1, @p2).should.equal(revenge.power)
it "doesn't double base power if hit by a non-damaging move", ->
shared.create.call(this)
revenge = @battle.getMove(moveName)
willOWisp = @battle.getMove("Will-O-Wisp")
@battle.performMove(@p2, willOWisp)
revenge.basePower(@battle, @p1, @p2).should.equal(revenge.power)
it "doesn't double base power if hit last turn, but not this turn", ->
shared.create.call(this)
revenge = @battle.getMove(moveName)
tackle = @battle.getMove("Tackle")
@battle.performMove(@p2, tackle)
@battle.beginTurn()
revenge.basePower(@battle, @p1, @p2).should.equal(revenge.power)
testRevengeMove("Avalanche")
testRevengeMove("Revenge")
testIgnoreStagesMove = (moveName) ->
describe moveName, ->
it "ignores the target's defensive and evasive stat stages", ->
shared.create.call(this)
move = @battle.getMove(moveName)
stats = [ 'defense', 'specialDefense', 'evasion' ]
values = ( @p2.stat(stat) for stat in stats )
spy = @sandbox.spy(@p2, 'stat')
spy.withArgs(stat) for stat in stats
@p2.boost(defense: -3, specialDefense: 2, evasion: 6)
@battle.performMove(@p1, move)
for i in [0...stats.length]
stat = stats[i]
value = values[i]
continue if spy.withArgs(stat).returnValues.length == 0
spy.withArgs(stat).alwaysReturned(value).should.be.true
testIgnoreStagesMove("Chip Away")
testIgnoreStagesMove("Sacred Sword")
describe 'Captivate', ->
it 'fails if the user and target are not opposite genders', ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "M")]
team2: [Factory("Magikarp", gender: "M")]
captivate = @battle.getMove('Captivate')
mock = @sandbox.mock(captivate).expects('fail').once()
@battle.performMove(@p1, captivate)
mock.verify()
it "lowers the target's special attack by 2", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "M")]
team2: [Factory("Magikarp", gender: "F")]
captivate = @battle.getMove('Captivate')
@p2.stages.specialAttack.should.equal 0
@battle.performMove(@p1, captivate)
@p2.stages.specialAttack.should.equal -2
testStatusCureAttackMove = (moveName, status) ->
describe moveName, ->
it "doubles base power if the target has #{status.name}", ->
shared.create.call(this)
move = @battle.getMove(moveName)
@p2.attach(status)
move.basePower(@battle, @p1, @p2).should.equal(2 * move.power)
it "doesn't double if the target doesn't have #{status.name}", ->
shared.create.call(this)
move = @battle.getMove(moveName)
move.basePower(@battle, @p1, @p2).should.equal(move.power)
it "cures the target of #{status.name}", ->
shared.create.call(this)
move = @battle.getMove(moveName)
@p2.attach(status)
@p2.has(status).should.be.true
@battle.performMove(@p1, move)
@p2.hasStatus().should.be.false
it "does not cure #{status.name} if the user faints", ->
shared.create.call(this, team2: [Factory("Magikarp", ability: "Iron Barbs")])
move = @battle.getMove(moveName)
@p1.currentHP = 1
@p2.attach(status)
@p2.has(status).should.be.true
@battle.performMove(@p1, move)
@p2.hasStatus().should.be.true
testStatusCureAttackMove("Wake-Up Slap", Status.Sleep)
testStatusCureAttackMove("SmellingSalt", Status.Paralyze)
describe 'Aqua Ring', ->
it "restores 1/16 max HP at the end of each turn", ->
shared.create.call(this)
aquaRing = @battle.getMove('Aqua Ring')
delta = @p1.currentHP >> 4
@p1.currentHP = 1
@battle.performMove(@p1, aquaRing)
@p1.currentHP.should.equal(1)
@battle.endTurn()
@p1.currentHP.should.equal(1 + delta)
describe 'Ingrain', ->
it "restores 1/16 max HP at the end of each turn", ->
shared.create.call(this)
ingrain = @battle.getMove('Ingrain')
delta = @p1.currentHP >> 4
@p1.currentHP = 1
@battle.performMove(@p1, ingrain)
@p1.currentHP.should.equal(1)
@battle.endTurn()
@p1.currentHP.should.equal(1 + delta)
it "prevents switching", ->
shared.create.call(this)
ingrain = @battle.getMove('Ingrain')
@battle.performMove(@p1, ingrain)
@battle.beginTurn()
@p1.isSwitchBlocked().should.be.true
it "prevents self from being phased", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Magikarp") ]
ingrain = @battle.getMove('Ingrain')
whirlwind = @battle.getMove('Whirlwind')
@battle.performMove(@p1, ingrain)
mock = @sandbox.mock(@team1).expects('switch').never()
@battle.performMove(@p2, whirlwind)
mock.verify()
it "lets self be switched out if using self-switching moves", ->
shared.create.call(this)
ingrain = @battle.getMove('Ingrain')
uTurn = @battle.getMove('U-turn')
@battle.performMove(@p1, ingrain)
mock = @sandbox.mock(@battle).expects('requestActions').once()
@battle.performMove(@p1, uTurn)
mock.verify()
it "makes self vulnerable to Ground moves", ->
shared.create.call(this, team1: [ Factory("Gyarados") ])
ingrain = @battle.getMove('Ingrain')
uTurn = @battle.getMove('U-turn')
@p1.isImmune('Ground').should.be.true
@battle.performMove(@p1, ingrain)
@p1.isImmune('Ground').should.be.false
it "causes Telekinesis to fail", ->
shared.create.call(this)
ingrain = @battle.getMove('Ingrain')
telekinesis = @battle.getMove('Telekinesis')
@battle.performMove(@p1, ingrain)
mock = @sandbox.mock(telekinesis).expects('fail').once()
@battle.performMove(@p2, telekinesis)
mock.verify()
describe "Embargo", ->
it "prevents the target's use of items", ->
shared.create.call this,
team2: [ Factory("Magikarp", item: "Leftovers") ]
embargo = @battle.getMove('Embargo')
mock = @sandbox.mock(@p2.get(@p2.item)).expects('endTurn').never()
@battle.performMove(@p1, embargo)
@battle.endTurn()
mock.verify()
it "lasts 5 turns", ->
shared.create.call this,
team2: [ Factory("Magikarp", item: "Leftovers") ]
embargo = @battle.getMove('Embargo')
mock = @sandbox.mock(@p2.get(@p2.item)).expects('endTurn').never()
@battle.performMove(@p1, embargo)
for i in [0...5]
@p2.has(Attachment.Embargo).should.be.true
@battle.endTurn()
@p2.has(Attachment.Embargo).should.be.false
it "prevents the target's use of items in subsequent turns", ->
shared.create.call this,
team2: [ Factory("Magikarp", item: "Leftovers") ]
embargo = @battle.getMove('Embargo')
mock = @sandbox.mock(@p2.get(@p2.item)).expects('endTurn').never()
@battle.performMove(@p1, embargo)
for i in [0...5]
@battle.endTurn()
@battle.beginTurn()
mock.verify()
testChargeMove = (moveName, vulnerable) ->
describe moveName, ->
it "chooses the player's next action for them", ->
shared.create.call(this)
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)
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,
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,
team1: [Factory("Magikarp", evs: {speed: 4})]
move = @battle.getMove(moveName)
clearSmog = @battle.getMove("Clear Smog")
@battle.recordMove(@id1, move)
@battle.recordMove(@id2, clearSmog)
mock = @sandbox.mock(clearSmog).expects('hit').never()
@battle.continueTurn()
mock.verify()
it "makes target invulnerable to moves *after* use", ->
shared.create.call this,
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,
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)
@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,
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,
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('Skull Bash')
testChargeMove('Razor Wind')
testChargeMove('Shadow Force', [])
testChargeMove('Ice Burn')
testChargeMove('Freeze Shock')
testChargeMove('Fly', ["Gust", "Thunder", "Twister", "Sky Uppercut", "Hurricane", "Smack Down"])
testChargeMove('Bounce', ["Gust", "Thunder", "Twister", "Sky Uppercut", "Hurricane", "Smack Down"]) # Additional 30% chance to par
testChargeMove('Dig', ["Earthquake", "Magnitude"])
testChargeMove('Dive', ["Surf", "Whirlpool"])
testChargeMove('SolarBeam')
testChargeMove('Sky Attack')
describe "Skull Bash", ->
it "raises defense of the user on the charge turn", ->
shared.create.call(this)
skullBash = @battle.getMove('Skull Bash')
@p1.stages.defense.should.equal(0)
@battle.performMove(@p1, skullBash)
@p1.stages.defense.should.equal(1)
describe "Sky Attack", ->
it "has a 30% chance to flinch after execution", ->
shared.create.call(this)
skyAttack = @battle.getMove('Sky Attack')
shared.biasRNG.call(this, "randInt", 'flinch', 0) # 100% chance
@battle.recordMove(@id1, skyAttack)
@battle.continueTurn()
@p2.has(Attachment.Flinch).should.be.false
@battle.beginTurn()
@battle.continueTurn()
@p2.has(Attachment.Flinch).should.be.true
for moveName in [ "Gust", "Twister" ]
for chargeMoveName in [ "Fly", "Bounce" ]
do (moveName, chargeMoveName) ->
describe moveName, ->
it "deals double damage to Pokemon using #{chargeMoveName}", ->
shared.create.call(this)
chargeMove = @battle.getMove(chargeMoveName)
move = @battle.getMove(moveName)
# Make the Pokemon charge
@battle.recordMove(@id2, chargeMove)
@battle.continueTurn()
move.basePower(@battle, @p1, @p2).should.equal(2 * move.power)
for moveName in [ "Surf", "Whirlpool" ]
do (moveName) ->
describe moveName, ->
it "deals double damage to Pokemon using Dive", ->
shared.create.call(this)
dive = @battle.getMove("Dive")
move = @battle.getMove(moveName)
# Make the Pokemon charge
@battle.recordMove(@id2, dive)
@battle.continueTurn()
move.basePower(@battle, @p1, @p2).should.equal(2 * move.power)
for moveName in [ "Earthquake", "Magnitude" ]
do (moveName) ->
describe moveName, ->
it "deals double damage to Pokemon using Dig", ->
shared.create.call(this)
dig = @battle.getMove("Dig")
move = @battle.getMove(moveName)
power = move.power
if moveName == 'Magnitude'
power = 50
shared.biasRNG.call(this, 'randInt', 'magnitude', 20)
# Make the Pokemon charge
@battle.recordMove(@id2, dig)
@battle.continueTurn()
move.basePower(@battle, @p1, @p2).should.equal(2 * power)
describe "SolarBeam", ->
it "skips the charge turn under Sun", ->
shared.create.call(this)
solarBeam = @battle.getMove("SolarBeam")
@battle.setWeather(Weather.SUN)
mock = @sandbox.mock(solarBeam).expects('execute').once()
@battle.recordMove(@id1, solarBeam)
@battle.continueTurn()
mock.verify()
for weather in [ Weather.RAIN, Weather.SAND, Weather.HAIL ]
do (weather) ->
it "halves base power under #{weather}", ->
shared.create.call(this)
move = @battle.getMove("SolarBeam")
@battle.setWeather(weather)
move.basePower(@battle, @p1, @p2).should.equal(move.power >> 1)
describe "Fury Cutter", ->
it "doubles base power after every use, to a max of 160", ->
shared.create.call(this)
furyCutter = @battle.getMove("Fury Cutter")
furyCutter.basePower(@battle, @p1, @p2).should.equal(20)
@battle.performMove(@p1, furyCutter)
furyCutter.basePower(@battle, @p1, @p2).should.equal(40)
@battle.performMove(@p1, furyCutter)
furyCutter.basePower(@battle, @p1, @p2).should.equal(80)
@battle.performMove(@p1, furyCutter)
furyCutter.basePower(@battle, @p1, @p2).should.equal(160)
@battle.performMove(@p1, furyCutter)
furyCutter.basePower(@battle, @p1, @p2).should.equal(160)
it "resets to normal base power if using a different move", ->
shared.create.call(this)
furyCutter = @battle.getMove("Fury Cutter")
splash = @battle.getMove("Splash")
@battle.performMove(@p1, furyCutter)
furyCutter.basePower(@battle, @p1, @p2).should.equal(40)
@battle.performMove(@p1, splash)
furyCutter.basePower(@battle, @p1, @p2).should.equal(20)
describe "Imprison", ->
shared.shouldDoNoDamage("Imprison")
shared.shouldFailIfUsedTwice("Imprison")
it "prevents the opponent from selecting moves that the user knows", ->
shared.create.call(this)
imprison = @battle.getMove("Imprison")
furyCutter = @battle.getMove("Fury Cutter")
tackle = @battle.getMove("Tackle")
splash = @battle.getMove("Splash")
@p1.moves = [ imprison, tackle, splash ]
@p2.moves = [ furyCutter, tackle, splash ]
@p1.resetAllPP()
@p2.resetAllPP()
@p2.validMoves().should.eql [ furyCutter, tackle, splash ]
@battle.performMove(@p1, imprison)
@battle.beginTurn()
@p2.validMoves().should.eql [ furyCutter ]
it "prevents the opponent from executing moves that the user knows", ->
shared.create.call(this)
imprison = @battle.getMove("Imprison")
furyCutter = @battle.getMove("Fury Cutter")
tackle = @battle.getMove("Tackle")
splash = @battle.getMove("Splash")
@p1.moves = [ imprison, tackle, splash ]
@p2.moves = [ furyCutter, tackle, splash ]
@p1.resetAllPP()
@p2.resetAllPP()
@battle.performMove(@p1, imprison)
mock = @sandbox.mock(tackle).expects('execute').never()
@battle.performMove(@p2, tackle)
mock.verify()
it "lets opponents use moves again after user switches out", ->
shared.create.call this,
team1: [Factory("Magikarp"), Factory("Magikarp")]
imprison = @battle.getMove("Imprison")
furyCutter = @battle.getMove("Fury Cutter")
tackle = @battle.getMove("Tackle")
splash = @battle.getMove("Splash")
@p1.moves = [ imprison, tackle, splash ]
@p2.moves = [ furyCutter, tackle, splash ]
@p1.resetAllPP()
@p2.resetAllPP()
@battle.performMove(@p1, imprison)
@battle.beginTurn()
@p2.validMoves().should.eql [ furyCutter ]
@battle.performSwitch(@team1.first(), 1)
@battle.beginTurn()
@p2.validMoves().should.eql [ furyCutter, tackle, splash ]
describe "Present", ->
it "has a 40% chance for 40 base power", ->
shared.create.call(this)
shared.biasRNG.call(this, "next", "present", .6)
present = @battle.getMove("Present")
spy = @sandbox.spy(present, 'basePower')
@battle.performMove(@p1, present)
spy.alwaysReturned(40).should.be.true
it "has a 30% chance for 80 base power", ->
shared.create.call(this)
shared.biasRNG.call(this, "next", "present", .3)
present = @battle.getMove("Present")
spy = @sandbox.spy(present, 'basePower')
@battle.performMove(@p1, present)
spy.alwaysReturned(80).should.be.true
it "has a 10% chance for 120 base power", ->
shared.create.call(this)
shared.biasRNG.call(this, "next", "present", 0)
present = @battle.getMove("Present")
spy = @sandbox.spy(present, 'basePower')
@battle.performMove(@p1, present)
spy.alwaysReturned(120).should.be.true
it "has a 20% chance to heal target by 25% HP, rounded down", ->
shared.create.call(this)
shared.biasRNG.call(this, "next", "present", .1)
present = @battle.getMove("Present")
@p2.currentHP = 1
spy = @sandbox.spy(present, 'basePower')
@battle.performMove(@p1, present)
spy.alwaysReturned(0).should.be.true
@p2.currentHP.should.equal(1 + (@p2.stat('hp') >> 2))
describe "Final Gambit", ->
it "faints the user", ->
shared.create.call(this)
finalGambit = @battle.getMove("Final Gambit")
@battle.performMove(@p1, finalGambit)
@p1.isFainted().should.be.true
it "deals damage equal to the user's HP to the target", ->
shared.create.call(this, team2: [Factory("Magikarp", evs: {hp: 4})])
finalGambit = @battle.getMove("Final Gambit")
@battle.performMove(@p1, finalGambit)
@p2.currentHP.should.equal(1)
describe "Lucky Chant", ->
shared.shouldDoNoDamage("Lucky Chant")
shared.shouldFailIfUsedTwice("Lucky Chant")
it "prevents critical hits on the defender's team", ->
shared.create.call(this)
luckyChant = @battle.getMove("Lucky Chant")
stormThrow = @battle.getMove("Storm Throw")
@battle.performMove(@p1, luckyChant)
stormThrow.isCriticalHit(@battle, @p2, @p1).should.be.false
it "lasts 5 turns", ->
shared.create.call(this)
luckyChant = @battle.getMove("Lucky Chant")
@battle.performMove(@p1, luckyChant)
for i in [0...5]
@team1.has(Attachment.LuckyChant).should.be.true
@battle.endTurn()
@team1.has(Attachment.LuckyChant).should.be.false
describe "Lunar Dance", ->
it "faints the user", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Magikarp") ]
lunarDance = @battle.getMove("Lunar Dance")
@battle.performMove(@p1, lunarDance)
@p1.isFainted().should.be.true
it "fails if the user is the last active pokemon", ->
shared.create.call(this, team1: [Factory('Magikarp')])
lunarDance = @battle.getMove("Lunar Dance")
mock = @sandbox.mock(lunarDance).expects('fail').once()
@battle.performMove(@p1, lunarDance)
mock.verify()
@p1.isFainted().should.be.false
it "completely restores the switchin's HP, PP, and status", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Magikarp") ]
lunarDance = @battle.getMove("Lunar Dance")
benched = @team1.at(1)
benched.attach(Status.Burn)
benched.currentHP = 1
benched.setPP(benched.moves[0], 1)
@battle.performMove(@p1, lunarDance)
@battle.performSwitch(@team1.first(), 1)
benched.currentHP.should.equal(benched.stat('hp'))
benched.hasStatus().should.be.false
for move in benched.moves
benched.pp(move).should.equal(benched.maxPP(move))
it "disappears afterward", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Magikarp") ]
lunarDance = @battle.getMove("Lunar Dance")
@battle.performMove(@p1, lunarDance)
@team1.has(Attachment.LunarDance).should.be.true
@battle.performSwitch(@team1.first(), 1)
@team1.has(Attachment.LunarDance).should.be.false
it "works for 2v2"
describe "Healing Wish", ->
it "faints the user", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Magikarp") ]
healingWish = @battle.getMove("Healing Wish")
@battle.performMove(@p1, healingWish)
@p1.isFainted().should.be.true
it "fails if the user is the last active pokemon", ->
shared.create.call(this, team1: [Factory('Magikarp')])
healingWish = @battle.getMove("Healing Wish")
mock = @sandbox.mock(healingWish).expects('fail').once()
@battle.performMove(@p1, healingWish)
mock.verify()
@p1.isFainted().should.be.false
it "completely restores the switchin's HP and status", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Magikarp") ]
healingWish = @battle.getMove("Healing Wish")
benched = @team1.at(1)
benched.attach(Status.Burn)
benched.currentHP = 1
benched.setPP(benched.moves[0], 1)
@battle.performMove(@p1, healingWish)
@battle.performSwitch(@team1.first(), 1)
benched.currentHP.should.equal(benched.stat('hp'))
benched.hasStatus().should.be.false
it "disappears afterward", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Magikarp") ]
healingWish = @battle.getMove("Healing Wish")
@battle.performMove(@p1, healingWish)
@team1.has(Attachment.HealingWish).should.be.true
@battle.performSwitch(@team1.first(), 1)
@team1.has(Attachment.HealingWish).should.be.false
it "works for 2v2"
describe "Last Resort", ->
it "fails if the pokemon only has one move", ->
shared.create.call(this)
lastResort = @battle.getMove("Last Resort")
@p1.moves = [ lastResort ]
mock = @sandbox.mock(lastResort).expects('fail').once()
@battle.performMove(@p1, lastResort)
mock.verify()
it "works if the pokemon uses all other moves before Last Resort", ->
shared.create.call(this)
lastResort = @battle.getMove("Last Resort")
splash = @battle.getMove("Splash")
@p1.moves = [ lastResort, splash ]
@battle.performMove(@p1, splash)
mock = @sandbox.mock(lastResort).expects('afterSuccessfulHit').once()
@battle.performMove(@p1, lastResort)
mock.verify()
it "fails if using all moves, but switches out and back in", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Magikarp") ]
lastResort = @battle.getMove("Last Resort")
splash = @battle.getMove("Splash")
@p1.moves = [ lastResort, splash ]
mock = @sandbox.mock(lastResort).expects('fail').once()
@battle.performMove(@p1, splash)
@battle.performSwitch(@team1.first(), 1)
@battle.performMove(@team1.first(), lastResort)
@battle.performSwitch(@team1.first(), 1)
mock.verify()
it "fails if the pokemon does not know Last Resort", ->
shared.create.call(this)
splash = @battle.getMove("Splash")
tackle = @battle.getMove("Tackle")
@p1.moves = [ splash, tackle ]
lastResort = @battle.getMove("Last Resort")
mock = @sandbox.mock(lastResort).expects('fail').once()
@battle.performMove(@p1, lastResort)
mock.verify()
it "fails if the pokemon has not used another move since it was active", ->
shared.create.call(this)
lastResort = @battle.getMove("Last Resort")
splash = @battle.getMove("Splash")
@p1.moves = [ lastResort, splash ]
mock = @sandbox.mock(lastResort).expects('fail').once()
@battle.performMove(@p1, lastResort)
mock.verify()
describe "Assist", ->
it "fails if no team member exists", ->
shared.create.call this,
team1: [ Factory("Magikarp") ]
assist = @battle.getMove("Assist")
mock = @sandbox.mock(assist).expects('fail').once()
@battle.performMove(@p1, assist)
mock.verify()
it "chooses a team member's move at random", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Magikarp") ]
assist = @battle.getMove("Assist")
move = @team1.at(1).moves[0]
shared.biasRNG.call(this, "randInt", "assist", 0)
mock = @sandbox.mock(move)
mock.expects('execute').once().withArgs(@battle, @p1, [ @p1 ])
@battle.performMove(@p1, assist)
mock.verify()
it "fails if all team member moves are illegal", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Magikarp") ]
assist = @battle.getMove("Assist")
@team1.at(1).moves = []
@team1.at(1).moves.push @battle.getMove("Assist")
@team1.at(1).moves.push @battle.getMove("Bestow")
@team1.at(1).moves.push @battle.getMove("Chatter")
@team1.at(1).moves.push @battle.getMove("Circle Throw")
@team1.at(1).moves.push @battle.getMove("Copycat")
@team1.at(1).moves.push @battle.getMove("Counter")
@team1.at(1).moves.push @battle.getMove("Covet")
@team1.at(1).moves.push @battle.getMove("Destiny Bond")
@team1.at(1).moves.push @battle.getMove("Detect")
@team1.at(1).moves.push @battle.getMove("Dragon Tail")
@team1.at(1).moves.push @battle.getMove("Endure")
@team1.at(1).moves.push @battle.getMove("Feint")
@team1.at(1).moves.push @battle.getMove("Focus Punch")
@team1.at(1).moves.push @battle.getMove("Follow Me")
@team1.at(1).moves.push @battle.getMove("Helping Hand")
@team1.at(1).moves.push @battle.getMove("Me First")
@team1.at(1).moves.push @battle.getMove("Metronome")
@team1.at(1).moves.push @battle.getMove("Mimic")
@team1.at(1).moves.push @battle.getMove("Mirror Coat")
@team1.at(1).moves.push @battle.getMove("Mirror Move")
@team1.at(1).moves.push @battle.getMove("Nature Power")
@team1.at(1).moves.push @battle.getMove("Protect")
@team1.at(1).moves.push @battle.getMove("Rage Powder")
@team1.at(1).moves.push @battle.getMove("Sketch")
@team1.at(1).moves.push @battle.getMove("Sleep Talk")
@team1.at(1).moves.push @battle.getMove("Snatch")
@team1.at(1).moves.push @battle.getMove("Struggle")
@team1.at(1).moves.push @battle.getMove("Switcheroo")
@team1.at(1).moves.push @battle.getMove("Thief")
@team1.at(1).moves.push @battle.getMove("Transform")
@team1.at(1).moves.push @battle.getMove("Trick")
mock = @sandbox.mock(assist).expects('fail').once()
@battle.performMove(@p1, assist)
mock.verify()
describe "Metronome", ->
it "chooses a random move to execute", ->
shared.create.call(this)
@p1.moves = []
metronome = @battle.getMove("Metronome")
tackle = @battle.getMove("Tackle")
index = @battle.MoveList.indexOf(tackle)
shared.biasRNG.call(this, 'randInt', "metronome", index)
mock = @sandbox.mock(tackle).expects('execute').once()
@battle.performMove(@p1, metronome)
mock.verify()
it "reselects if chosen an illegal move", ->
shared.create.call(this)
@p1.moves = [ metronome ]
metronome = @battle.getMove("Metronome")
technoBlast = @battle.getMove("Techno Blast")
tackle = @battle.getMove("Tackle")
index = @battle.MoveList.indexOf(technoBlast)
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()
describe "Magic Coat", ->
shared.shouldDoNoDamage("Magic Coat")
it "causes certain moves directed at the user to be bounced back", ->
shared.create.call(this)
whirlwind = @battle.getMove("Whirlwind")
magicCoat = @battle.getMove("Magic Coat")
spy = @sandbox.spy(whirlwind, 'execute')
@battle.performMove(@p1, magicCoat)
@battle.performMove(@p2, whirlwind)
spy.args.some((array) =>
[battle, user, targets] = array
battle == @battle && user == @p1 && targets[0] == @p2
).should.be.true
it "bounces back spikes", ->
shared.create.call(this)
spikes = @battle.getMove("Spikes")
magicCoat = @battle.getMove("Magic Coat")
spy = @sandbox.spy(spikes, 'execute')
@battle.performMove(@p1, magicCoat)
@battle.performMove(@p2, spikes)
spy.args.some((array) =>
[battle, user, targets] = array
battle == @battle && user == @p1 && targets[0] == @id2
).should.be.true
@team1.has(Attachment.Spikes).should.be.false
@team2.has(Attachment.Spikes).should.be.true
it "cannot bounce spikes more than once", ->
shared.create.call(this)
spikes = @battle.getMove("Spikes")
magicCoat = @battle.getMove("Magic Coat")
@battle.performMove(@p1, magicCoat)
@battle.performMove(@p2, magicCoat)
(=>
@battle.performMove(@p2, spikes)
).should.not.throw(/Maximum call stack size exceeded/)
it "does not bounce certain moves back", ->
shared.create.call(this)
tackle = @battle.getMove("Tackle")
magicCoat = @battle.getMove("Magic Coat")
spy = @sandbox.spy(tackle, 'execute')
@battle.performMove(@p1, magicCoat)
@battle.performMove(@p2, tackle)
spy.args.some((array) =>
[battle, user, targets] = array
battle == @battle && user == @p1 && targets[0] == @p2
).should.be.false
it "lasts until the end of the turn", ->
shared.create.call(this)
magicCoat = @battle.getMove("Magic Coat")
@battle.performMove(@p1, magicCoat)
@p1.has(Attachment.MagicCoat).should.be.true
@battle.endTurn()
@p1.has(Attachment.MagicCoat).should.be.false
it "cannot bounce more than once in the same turn", ->
shared.create.call(this)
willOWisp = @battle.getMove("Will-O-Wisp")
thunderWave = @battle.getMove("Thunder Wave")
magicCoat = @battle.getMove("Magic Coat")
@battle.performMove(@p1, magicCoat)
@battle.performMove(@p2, willOWisp)
spy = @sandbox.spy(thunderWave, 'execute')
@battle.performMove(@p2, thunderWave)
spy.args.some((array) =>
[battle, user, targets] = array
battle == @battle && user == @p1 && targets[0] == @p2
).should.be.false
it "cannot bounce a certain move more than once in the same turn", ->
shared.create.call(this)
willOWisp = @battle.getMove("Will-O-Wisp")
magicCoat = @battle.getMove("Magic Coat")
@battle.performMove(@p1, magicCoat)
@battle.performMove(@p2, magicCoat)
spy = @sandbox.spy(willOWisp, 'execute')
(=>
@battle.performMove(@p2, willOWisp)
).should.not.throw(/Maximum call stack size exceeded/)
spy.calledTwice.should.be.true
spy.args.some((array) =>
[battle, user, targets] = array
battle == @battle && user == @p1 && targets[0] == @p2
).should.be.true
spy.args.some((array) =>
[battle, user, targets] = array
battle == @battle && user == @p2 && targets[0] == @p1
).should.be.true
describe "Telekinesis", ->
shared.shouldDoNoDamage("Telekinesis")
shared.shouldFailIfUsedTwice("Telekinesis")
it "makes the target immune to ground moves", ->
shared.create.call(this)
telekinesis = @battle.getMove("Telekinesis")
@p1.isImmune('Ground').should.be.false
@battle.performMove(@p2, telekinesis)
@p1.isImmune('Ground').should.be.true
it "lasts 3 turns", ->
shared.create.call(this)
telekinesis = @battle.getMove("Telekinesis")
@battle.performMove(@p2, telekinesis)
for x in [0...3]
@p1.isImmune('Ground').should.be.true
@battle.endTurn()
@p1.isImmune('Ground').should.be.false
it "makes the target unable to avoid attacks other than ohko moves", ->
shared.create.call(this)
telekinesis = @battle.getMove("Telekinesis")
inferno = @battle.getMove("Inferno")
sheerCold = @battle.getMove("Sheer Cold")
inferno.chanceToHit(@battle, @p2, @p1).should.equal(50)
sheerCold.chanceToHit(@battle, @p2, @p1).should.equal(30)
@battle.performMove(@p2, telekinesis)
inferno.chanceToHit(@battle, @p2, @p1).should.equal(0)
sheerCold.chanceToHit(@battle, @p2, @p1).should.equal(30)
describe "Smack Down", ->
it "doesn't crash on secondary effect", ->
shared.create.call(this, team2: [ Factory("Gyarados") ])
shared.biasRNG.call(this, "next", "secondary effect", 0)
smackDown = @battle.getMove("Smack Down")
(=> @battle.performMove(@p1, smackDown)).should.not.throw()
it "removes target's Ground immunity", ->
shared.create.call(this, team2: [ Factory("Gyarados") ])
smackDown = @battle.getMove("Smack Down")
@p2.isImmune("Ground").should.be.true
@battle.performMove(@p1, smackDown)
@p2.isImmune("Ground").should.be.false
it "stops Fly", ->
shared.create.call(this)
smackDown = @battle.getMove("Smack Down")
fly = @battle.getMove("Fly")
@battle.recordMove(@id1, fly)
@battle.continueTurn()
@p1.has(Attachment.Charging).should.be.true
@battle.performMove(@p2, smackDown)
@p1.has(Attachment.Charging).should.be.false
it "stops Bounce", ->
shared.create.call(this)
smackDown = @battle.getMove("Smack Down")
bounce = @battle.getMove("Bounce")
@battle.recordMove(@id1, bounce)
@battle.continueTurn()
@p1.has(Attachment.Charging).should.be.true
@battle.performMove(@p2, smackDown)
@p1.has(Attachment.Charging).should.be.false
it "does not stop other charge moves like Dive", ->
shared.create.call(this)
smackDown = @battle.getMove("Smack Down")
dive = @battle.getMove("Dive")
@battle.recordMove(@id1, dive)
@battle.continueTurn()
@p1.has(Attachment.Charging).should.be.true
@battle.performMove(@p2, smackDown)
@p1.has(Attachment.Charging).should.be.true
it "stops Magnet Rise", ->
shared.create.call(this)
smackDown = @battle.getMove("Smack Down")
magnetRise = @battle.getMove("Magnet Rise")
@battle.performMove(@p1, magnetRise)
@p1.has(Attachment.MagnetRise).should.be.true
@battle.performMove(@p2, smackDown)
@p1.has(Attachment.MagnetRise).should.be.false
it "stops Telekinesis", ->
shared.create.call(this)
smackDown = @battle.getMove("Smack Down")
telekinesis = @battle.getMove("Telekinesis")
@battle.performMove(@p2, telekinesis)
@p1.has(Attachment.Telekinesis).should.be.true
@battle.performMove(@p2, smackDown)
@p1.has(Attachment.Telekinesis).should.be.false
it "makes Magnet Rise execution fail", ->
shared.create.call(this)
smackDown = @battle.getMove("Smack Down")
magnetRise = @battle.getMove("Magnet Rise")
@battle.performMove(@p1, smackDown)
mock = @sandbox.mock(magnetRise).expects('fail').once()
@battle.performMove(@p2, magnetRise)
mock.verify()
it "causes Telekinesis execution to fail", ->
shared.create.call(this)
smackDown = @battle.getMove("Smack Down")
telekinesis = @battle.getMove("Telekinesis")
@battle.performMove(@p1, smackDown)
mock = @sandbox.mock(telekinesis).expects('fail').once()
@battle.performMove(@p1, telekinesis)
mock.verify()
describe "Echoed Voice", ->
it "has 80 base power the second turn in a row it is used", ->
shared.create.call(this)
echoedVoice = @battle.getMove("Echoed Voice")
@battle.performMove(@p1, echoedVoice)
@battle.endTurn()
echoedVoice.basePower(@battle, @p1, @p2).should.equal(80)
it "has 120 base power the third turn in a row it is used", ->
shared.create.call(this)
echoedVoice = @battle.getMove("Echoed Voice")
for i in [1...3]
@battle.performMove(@p1, echoedVoice)
@p2.currentHP = @p2.stat('hp')
@battle.endTurn()
echoedVoice.basePower(@battle, @p1, @p2).should.equal(120)
it "has 160 base power the fourth turn in a row it is used", ->
shared.create.call(this)
echoedVoice = @battle.getMove("Echoed Voice")
for i in [1...4]
@battle.performMove(@p1, echoedVoice)
@p2.currentHP = @p2.stat('hp')
@battle.endTurn()
echoedVoice.basePower(@battle, @p1, @p2).should.equal(160)
it "has 200 base power the fifth turn in a row it is used", ->
shared.create.call(this)
echoedVoice = @battle.getMove("Echoed Voice")
for i in [1...5]
@battle.performMove(@p1, echoedVoice)
@p2.currentHP = @p2.stat('hp')
@battle.endTurn()
echoedVoice.basePower(@battle, @p1, @p2).should.equal(200)
it "has 200 base power the sixth and above turn in a row it is used", ->
shared.create.call(this)
echoedVoice = @battle.getMove("Echoed Voice")
for i in [1...6]
@battle.performMove(@p1, echoedVoice)
@p2.currentHP = @p2.stat('hp')
@battle.endTurn()
echoedVoice.basePower(@battle, @p1, @p2).should.equal(200)
it "resets to 40 base power if nobody uses this move the previous turn", ->
shared.create.call(this)
echoedVoice = @battle.getMove("Echoed Voice")
splash = @battle.getMove("Splash")
@battle.performMove(@p1, echoedVoice)
@battle.endTurn()
echoedVoice.basePower(@battle, @p1, @p2).should.equal(80)
@battle.performMove(@p1, splash)
@battle.endTurn()
echoedVoice.basePower(@battle, @p1, @p2).should.equal(40)
it "has the same base power that turn for all users", ->
shared.create.call(this)
echoedVoice = @battle.getMove("Echoed Voice")
splash = @battle.getMove("Splash")
@battle.performMove(@p1, echoedVoice)
@battle.endTurn()
echoedVoice.basePower(@battle, @p1, @p2).should.equal(80)
echoedVoice.basePower(@battle, @p2, @p1).should.equal(80)
it "does not reset even if missing", ->
shared.create.call(this)
echoedVoice = @battle.getMove("Echoed Voice")
@battle.performMove(@p1, echoedVoice)
@battle.endTurn()
echoedVoice.basePower(@battle, @p1, @p2).should.equal(80)
@sandbox.stub(echoedVoice, 'willMiss', -> true)
@battle.performMove(@p1, echoedVoice)
@battle.endTurn()
echoedVoice.basePower(@battle, @p1, @p2).should.equal(120)
describe "Struggle", ->
it "deals typless damage", ->
shared.create.call(this)
struggle = @battle.getMove('Struggle')
for type of util.Type
util.typeEffectiveness(struggle.type, [type]).should.equal(1)
it "deals 25% in recoil to the attacker, rounded down", ->
shared.create.call(this)
struggle = @battle.getMove('Struggle')
hp = @p1.currentHP
@p1.currentHP.should.equal @p1.stat('hp')
@battle.performMove(@p1, struggle)
(hp - @p1.currentHP).should.equal(hp >> 2)
describe "Nature Power", ->
it "uses Earthquake in Wi-Fi battles", ->
shared.create.call(this)
naturePower = @battle.getMove('Nature Power')
earthquake = @battle.getMove('Earthquake')
mock = @sandbox.mock(earthquake).expects('execute').once()
.withArgs(@battle, @p1, [ @p2 ])
@battle.performMove(@p1, naturePower)
mock.verify()
testRampageMove = (moveName) ->
describe moveName, ->
it "lasts 2-3 turns", ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'rampage turns', 2)
rampageMove = @battle.getMove(moveName)
@battle.performMove(@p1, rampageMove)
@p1.has(Attachment.Rampage).should.be.true
@battle.endTurn()
@battle.performMove(@p1, rampageMove)
@battle.endTurn()
@p1.has(Attachment.Rampage).should.be.false
it "locks the user into that move until execution ends", ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'rampage turns', 2)
rampageMove = @battle.getMove(moveName)
splash = @battle.getMove("Splash")
@p1.moves = [ rampageMove, splash ]
@p1.resetAllPP()
@battle.performMove(@p1, rampageMove)
@p2.currentHP = @p2.stat('hp')
@battle.endTurn()
@battle.beginTurn()
@p1.validMoves().should.eql [ rampageMove ]
@battle.performMove(@p1, rampageMove)
@battle.endTurn()
@battle.beginTurn()
@p1.validMoves().should.eql [ rampageMove, splash ]
it "blocks switching until execution ends", ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'rampage turns', 2)
rampageMove = @battle.getMove(moveName)
@battle.performMove(@p1, rampageMove)
@p2.currentHP = @p2.stat('hp')
@battle.endTurn()
@battle.beginTurn()
@p1.isSwitchBlocked().should.be.true
@battle.performMove(@p1, rampageMove)
@battle.endTurn()
@battle.beginTurn()
@p1.isSwitchBlocked().should.be.false
it "confuses the user after use", ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'rampage turns', 2)
rampageMove = @battle.getMove(moveName)
@battle.performMove(@p1, rampageMove)
@p2.currentHP = @p2.stat('hp')
@battle.endTurn()
@battle.beginTurn()
@p1.has(Attachment.Confusion).should.be.false
@battle.performMove(@p1, rampageMove)
@battle.endTurn()
@battle.beginTurn()
@p1.has(Attachment.Confusion).should.be.true
it "stops immediately if the user's move did not hit", ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'rampage turns', 3)
rampageMove = @battle.getMove(moveName)
protect = @battle.getMove("Protect")
@battle.performMove(@p1, rampageMove)
@battle.endTurn()
@battle.beginTurn()
@battle.recordMove(@id2, protect)
@battle.recordMove(@id1, rampageMove)
@battle.continueTurn()
@battle.endTurn()
@p1.has(Attachment.Rampage).should.be.false
testRampageMove("Outrage")
testRampageMove("Petal Dance")
testRampageMove("Thrash")
test2To5MulthitMove = (moveName) ->
describe moveName, ->
it "deals 2 to 5 hits with a probability distribution of 1/3, 1/3, 1/6, 1/6", ->
shared.create.call(this)
move = @battle.getMove(moveName)
spy = @sandbox.spy(@battle.rng, 'choice')
spy = spy.withArgs([2, 2, 3, 3, 4, 5], 'num hits')
@battle.performMove(@p1, move)
spy.calledOnce.should.be.true
it "hits 5 times if the user has Skill Link", ->
shared.create.call(this, team1: [Factory('Cloyster', ability: 'Skill Link')])
# try to "trick" the system into giving an invalid result. It should always hit
# 5 times regardless of the rng
shared.biasRNG.call(this, "choice", 'num hits', 2)
shared.biasRNG.call(this, "randInt", 'num hits', 2)
move = @battle.getMove(moveName)
move.calculateNumberOfHits(@battle, @p1, [ @p2 ]).should.equal(5)
it "only hits up until the target Pokemon faints", ->
shared.create.call(this, team1: [Factory('Cloyster', ability: 'Skill Link')])
# try to "trick" the system into giving an invalid result. It should always hit
# 5 times regardless of the rng
shared.biasRNG.call(this, "choice", 'num hits', 3)
shared.biasRNG.call(this, "randInt", 'num hits', 3)
@p2.currentHP = 1
move = @battle.getMove(moveName)
spy = @sandbox.spy(move, 'hit')
@battle.performMove(@p1, move)
spy.calledOnce.should.be.true
test2To5MulthitMove('Arm Thrust')
test2To5MulthitMove('Barrage')
test2To5MulthitMove('Bone Rush')
test2To5MulthitMove('Bullet Seed')
test2To5MulthitMove('Comet Punch')
test2To5MulthitMove('DoubleSlap')
test2To5MulthitMove('Fury Attack')
test2To5MulthitMove('Fury Swipes')
test2To5MulthitMove('Icicle Spear')
test2To5MulthitMove('Pin Missile')
test2To5MulthitMove('Rock Blast')
test2To5MulthitMove('Spike Cannon')
test2To5MulthitMove('Tail Slap')
describe "Trick Room", ->
it "lasts 5 turns", ->
shared.create.call(this)
trickRoom = @battle.getMove("Trick Room")
@battle.performMove(@p1, trickRoom)
for x in [0...5]
@battle.has(Attachment.TrickRoom).should.be.true
@battle.endTurn()
@battle.has(Attachment.TrickRoom).should.be.false
it "ends a previous use of Trick Room", ->
shared.create.call(this)
trickRoom = @battle.getMove("Trick Room")
@battle.performMove(@p1, trickRoom)
@battle.has(Attachment.TrickRoom).should.be.true
@battle.performMove(@p1, trickRoom)
@battle.has(Attachment.TrickRoom).should.be.false
it "reverses the order that moves are performed in", ->
shared.create.call(this, team1: [Factory("Magikarp", evs: {speed: 4})])
trickRoom = @battle.getMove("Trick Room")
splash = @battle.getMove("Splash")
@battle.performMove(@p1, trickRoom)
@battle.recordMove(@id1, splash)
@battle.recordMove(@id2, splash)
@battle.determineTurnOrder().map((o) -> o.pokemon).should.eql [ @p2, @p1 ]
describe "Transform", ->
shared.shouldDoNoDamage("Transform")
shared.shouldFailIfUsedTwice("Transform")
it "fails on a substitute", ->
shared.create.call(this)
substitute = @battle.getMove("Substitute")
transform = @battle.getMove("Transform")
@battle.performMove(@p2, substitute)
mock = @sandbox.mock(transform).expects('fail').once()
@battle.performMove(@p1, transform)
mock.verify()
it "changes the user's type to match the target's", ->
shared.create.call(this, team2: [Factory("Celebi")])
transform = @battle.getMove("Transform")
@battle.performMove(@p1, transform)
@p1.types.should.eql @p2.types
it "forces a sprite change", ->
shared.create.call(this, team2: [Factory("Celebi")])
transform = @battle.getMove("Transform")
mock = @sandbox.mock(@p1).expects('changeSprite').once()
@battle.performMove(@p1, transform)
mock.verify()
it "does not change species or forme", ->
shared.create.call(this, team2: [Factory("Latias")])
transform = @battle.getMove("Transform")
oldSpecies = @p1.species
oldForme = @p1.forme
@battle.performMove(@p1, transform)
@p1.species.should.equal(oldSpecies)
@p1.forme.should.equal(oldForme)
it "changes the user's stats to match the target's, except HP", ->
shared.create.call(this, team2: [Factory("Celebi", evs: {speed: 252})])
transform = @battle.getMove("Transform")
@battle.performMove(@p1, transform)
for stat of @p2.baseStats
if stat == 'hp'
@p1.stat(stat).should.not.equal(@p2.stat(stat))
else
@p1.stat(stat).should.equal(@p2.stat(stat))
it "copies the target's gender", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "F")]
team2: [Factory("Celebi")]
transform = @battle.getMove("Transform")
@battle.performMove(@p1, transform)
@p1.gender.should.equal(@p2.gender)
it "copies the target's moveset, setting each PP and max PP to 5", ->
shared.create.call(this, team2: [Factory("Celebi")])
transform = @battle.getMove("Transform")
@p2.boost(attack: 3, speed: -2, accuracy: 1)
@battle.performMove(@p1, transform)
@p1.moves.should.eql @p2.moves
for move in @p1.moves
@p1.pp(move).should.equal(5)
@p1.maxPP(move).should.equal(5)
it "copies the target's stat boosts", ->
shared.create.call(this, team2: [Factory("Celebi")])
transform = @battle.getMove("Transform")
@p2.boost(attack: 3, speed: -2, accuracy: 1)
@battle.performMove(@p1, transform)
@p1.stages.should.containEql(attack: 3, speed: -2, accuracy: 1)
it "copies the target's ability", ->
shared.create.call this,
team2: [Factory("Celebi", ability: "Natural Cure")]
transform = @battle.getMove("Transform")
@battle.performMove(@p1, transform)
@p1.hasAbility("Natural Cure").should.be.true
it "copies the target's weight", ->
shared.create.call this,
team2: [Factory("Celebi")]
transform = @battle.getMove("Transform")
@battle.performMove(@p1, transform)
@p1.weight.should.equal(@p2.weight)
it "copies the target's gender", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "F")]
team2: [Factory("Celebi")]
transform = @battle.getMove("Transform")
@battle.performMove(@p1, transform)
@p1.gender.should.equal(@p2.gender)
it "restores the original stats after switching out", ->
shared.create.call this,
team1: [Factory("Ditto"), Factory("Magikarp")]
team2: [Factory("Celebi", ability: "Natural Cure", evs: {speed: 252})]
transform = @battle.getMove("Transform")
baseStats = _.clone(@p1.baseStats)
evs = _.clone(@p1.evs)
stats = {}
for stat of baseStats
stats[stat] = @p1.stat(stat)
@battle.performMove(@p1, transform)
@p1.baseStats.should.not.eql(baseStats)
@p1.evs.should.not.eql(evs)
for stat, value of stats
@p1.stat(stat).should.not.equal(value)
@battle.performSwitch(@p1, 1)
@p1.baseStats.should.eql(baseStats)
@p1.evs.should.eql(evs)
for stat, value of stats
@p1.stat(stat).should.equal(value)
it "restores the original species and type after switching out", ->
shared.create.call this,
team1: [Factory("Ditto"), Factory("Magikarp")]
team2: [Factory("Celebi", ability: "Natural Cure")]
transform = @battle.getMove("Transform")
species = @p1.species
types = _.clone(@p1.types)
@battle.performMove(@p1, transform)
@battle.performSwitch(@team1.first(), 1)
@p1.types.should.eql(types)
@p1.species.should.eql(species)
it "restores the original ability after switching out", ->
# Ditto has a fake ability so we can test abilities being restored.
shared.create.call this,
team1: [Factory("Ditto", ability: "Sniper"), Factory("Magikarp")]
team2: [Factory("Celebi", ability: "Natural Cure")]
transform = @battle.getMove("Transform")
ability = @p1.ability
@battle.performMove(@p1, transform)
@battle.performSwitch(@team1.first(), 1)
@p1.ability.should.eql(ability)
it "restores original moveset after switch, but Transform PP decreases", ->
shared.create.call this,
team1: [Factory("Ditto"), Factory("Magikarp")]
team2: [Factory("Celebi", ability: "Natural Cure")]
transform = @battle.getMove("Transform")
moves = _.clone(@p1.moves)
ppHash = _.clone(@p1.ppHash)
maxPPHash = _.clone(@p1.maxPPHash)
@battle.performMove(@p1, transform)
@battle.performSwitch(@team1.first(), 1)
@p1.moves.should.eql(moves)
@p1.maxPPHash.should.eql(maxPPHash)
# Transform's PP should go down!
ppHash[transform.name] -= 1
@p1.ppHash.should.eql(ppHash)
it "restores original gender after switch", ->
shared.create.call this,
team1: [Factory("Ditto"), Factory("Magikarp")]
team2: [Factory("Celebi", ability: "Natural Cure")]
transform = @battle.getMove("Transform")
gender = @p1.gender
@battle.performMove(@p1, transform)
@battle.performSwitch(@team1.first(), 1)
@p1.gender.should.eql(gender)
it "restores original weight after switch", ->
shared.create.call this,
team1: [Factory("Ditto"), Factory("Magikarp")]
team2: [Factory("Celebi", ability: "Natural Cure")]
transform = @battle.getMove("Transform")
weight = @p1.weight
@battle.performMove(@p1, transform)
@battle.performSwitch(@team1.first(), 1)
@p1.weight.should.eql(weight)
it "fails if the target is transformed", ->
shared.create.call(this)
transform = @battle.getMove("Transform")
@battle.performMove(@p2, transform)
mock = @sandbox.mock(transform).expects('fail').once()
@battle.performMove(@p1, transform)
mock.verify()
it "fails if the target is under an illusion"
it "fails if the user is under an illusion"
it "cannot change formes if it has the ability to do so"
describe "Fling", ->
it "fails if the pokemon has no item", ->
shared.create.call(this)
fling = @battle.getMove("Fling")
mock = @sandbox.mock(fling).expects('fail').once()
@battle.recordMove(@id1, fling)
@battle.continueTurn()
mock.verify()
it "fails if the item is not removeable", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Acro Bike")]
fling = @battle.getMove("Fling")
mock = @sandbox.mock(fling).expects('fail').once()
@battle.recordMove(@id1, fling)
@battle.continueTurn()
mock.verify()
it "fails if the user is blocked from using items", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Armor Fossil")]
fling = @battle.getMove("Fling")
@p1.blockItem()
mock = @sandbox.mock(fling).expects('fail').once()
@battle.recordMove(@id1, fling)
@battle.continueTurn()
mock.verify()
it "has a base power depending on the item held", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Armor Fossil")]
fling = @battle.getMove("Fling")
fling.beforeTurn(@battle, @p1)
@p1.beforeMove(fling, @p1, [@p2])
fling.basePower(@battle, @p1, @p2).should.equal(100)
it "inflicts Burn on the target if Flame Orb is held", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Flame Orb")]
fling = @battle.getMove("Fling")
@battle.recordMove(@id1, fling)
@battle.continueTurn()
@p2.has(Status.Burn).should.be.true
it "inflicts Toxic on the target if Toxic Orb is held", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Toxic Orb")]
fling = @battle.getMove("Fling")
@battle.recordMove(@id1, fling)
@battle.continueTurn()
@p2.has(Status.Toxic).should.be.true
it "flinches the target if King's Rock is held", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "King's Rock")]
fling = @battle.getMove("Fling")
@battle.recordMove(@id1, fling)
@battle.continueTurn()
@p2.has(Attachment.Flinch).should.be.true
it "flinches the target if Razor Fang is held", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Razor Fang")]
fling = @battle.getMove("Fling")
@battle.recordMove(@id1, fling)
@battle.continueTurn()
@p2.has(Attachment.Flinch).should.be.true
it "paralyzes the target if Light Ball is held", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Light Ball")]
fling = @battle.getMove("Fling")
@battle.recordMove(@id1, fling)
@battle.continueTurn()
@p2.has(Status.Paralyze).should.be.true
it "removes negative status effects if Mental Herb is held", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Mental Herb")]
fling = @battle.getMove("Fling")
@p2.attach(Attachment.Torment)
@battle.recordMove(@id1, fling)
@battle.continueTurn()
@p2.has(Attachment.Torment).should.be.false
it "removes negative stat boosts if White Herb is held", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "White Herb")]
fling = @battle.getMove("Fling")
@p2.boost(attack: 1, accuracy: -2)
@battle.recordMove(@id1, fling)
@battle.continueTurn()
@p2.stages.should.containEql(attack: 1, accuracy: 0)
it "inflicts Poison on the target if Poison Barb is held", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Poison Barb")]
fling = @battle.getMove("Fling")
@battle.recordMove(@id1, fling)
@battle.continueTurn()
@p2.has(Status.Poison).should.be.true
it "causes target to eat the flung berry if a berry is held", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Salac Berry")]
fling = @battle.getMove("Fling")
@battle.recordMove(@id1, fling)
@battle.continueTurn()
@p2.stages.speed.should.equal(1)
it "does not activate an item effect when hitting a substitute", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Toxic Orb")]
fling = @battle.getMove("Fling")
@p2.attach(Attachment.Substitute, hp: 1)
@battle.recordMove(@id1, fling)
@battle.continueTurn()
@p2.has(Status.Toxic).should.be.false
it "loses the user's item", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Poison Barb")]
fling = @battle.getMove("Fling")
@battle.recordMove(@id1, fling)
@battle.continueTurn()
@p1.hasItem().should.be.false
it "loses the item even if execution is canceled by protect", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Poison Herb")]
protect = @battle.getMove("Protect")
fling = @battle.getMove("Fling")
mock = @sandbox.mock(fling).expects('hit').never()
@battle.recordMove(@id2, protect)
@battle.recordMove(@id1, fling)
@battle.continueTurn()
mock.verify()
@p1.hasItem().should.be.false
describe "Bug Bite", ->
it "eats the target's berry and gets its effects", ->
shared.create.call this,
team2: [Factory("Magikarp", item: "Salac Berry")]
bugBite = @battle.getMove("Bug Bite")
@battle.performMove(@p1, bugBite)
@p2.hasItem().should.be.false
@p1.stages.should.containEql(speed: 1)
it "does eat the target's berry if the target fainted", ->
shared.create.call this,
team2: [Factory("Magikarp", item: "Salac Berry")]
bugBite = @battle.getMove("Bug Bite")
@p2.currentHP = 1
@battle.performMove(@p1, bugBite)
@p2.isFainted().should.be.true
@p1.stages.should.containEql(speed: 1)
it "does not eat the target's berry if the user fainted", ->
shared.create.call this,
team2: [Factory("Magikarp", item: "Salac Berry", ability: "Iron Barbs")]
bugBite = @battle.getMove("Bug Bite")
@p1.currentHP = 1
@battle.performMove(@p1, bugBite)
@p1.isFainted().should.be.true
@p2.hasItem().should.be.true
describe "Beat Up", ->
it "deals 1 less hit for each unhealthy member in the user's party", ->
shared.create.call this,
team1: (Factory("Magikarp") for i in [0...6])
@team1.at(5).attach(Status.Paralyze)
@team1.at(1).faint()
beatUp = @battle.getMove("Beat Up")
mock = @sandbox.mock(beatUp).expects('hit').exactly(4)
@battle.performMove(@p1, beatUp)
mock.verify()
it "has 5 + X/10 base power for X in team, where X is base attack", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Scyther"), Factory("Celebi"),
Factory("Totodile"), Factory("Rayquaza"), Factory("Seedot") ]
beatUp = @battle.getMove("Beat Up")
spy = @sandbox.spy(beatUp, "basePower")
@battle.performMove(@p1, beatUp)
for pokemon in @team1.pokemon
basePower = 5 + Math.floor(pokemon.baseStats.attack / 10)
spy.returned(basePower).should.be.true
it "works for teams smaller than 6", ->
shared.create.call this,
team1: (Factory("Magikarp") for i in [0...4])
beatUp = @battle.getMove("Beat Up")
mock = @sandbox.mock(beatUp).expects('hit').exactly(4)
@battle.performMove(@p1, beatUp)
mock.verify()
it "doesn't crash if all pokemon are statused or fainted", ->
shared.create.call this,
team1: (Factory("Magikarp") for i in [0...2])
@team1.at(1).faint()
@team1.at(0).attach(Status.Burn)
beatUp = @battle.getMove("Beat Up")
(=>
@battle.performMove(@p1, beatUp)
).should.not.throw()
it "doesn't crash when used by a pokemon with technician", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Technician")])
beatUp = @battle.getMove("Beat Up")
(=>
@battle.performMove(@p1, beatUp)
).should.not.throw()
describe "Psycho Shift", ->
it "fails if the user doesn't have a status", ->
shared.create.call(this)
psychoShift = @battle.getMove("Psycho Shift")
mock = @sandbox.mock(psychoShift).expects('fail').once()
@battle.performMove(@p1, psychoShift)
mock.verify()
it "fails if the target already has a status", ->
shared.create.call(this)
@p1.attach(Status.Toxic)
@p2.attach(Status.Paralyze)
psychoShift = @battle.getMove("Psycho Shift")
mock = @sandbox.mock(psychoShift).expects('fail').once()
@battle.performMove(@p1, psychoShift)
mock.verify()
it "cures the user of its status", ->
shared.create.call(this)
@p1.attach(Status.Poison)
psychoShift = @battle.getMove("Psycho Shift")
@battle.performMove(@p1, psychoShift)
@p1.hasStatus().should.be.false
it "gives the user's former status to the target", ->
shared.create.call(this)
@p1.attach(Status.Burn)
psychoShift = @battle.getMove("Psycho Shift")
@battle.performMove(@p1, psychoShift)
@p2.has(Status.Burn).should.be.true
describe "Gravity", ->
shared.shouldDoNoDamage("Gravity")
shared.shouldFailIfUsedTwice("Gravity")
it "raises accuracy of all attacks by 5/3", ->
shared.create.call(this)
gravity = @battle.getMove("Gravity")
focusBlast = @battle.getMove("Focus Blast")
@battle.performMove(@p1, gravity)
accuracy = focusBlast.chanceToHit(@battle, @p1, @p2)
accuracy.should.equal Math.floor(focusBlast.accuracy * 5 / 3)
it "lasts 5 turns", ->
shared.create.call(this)
gravity = @battle.getMove("Gravity")
@battle.performMove(@p1, gravity)
for i in [0...5]
@battle.has(Attachment.Gravity).should.be.true
@battle.endTurn()
@battle.has(Attachment.Gravity).should.be.false
it "makes Flying Pokemon no longer immune to Ground", ->
shared.create.call(this)
@p2.types = [ "Flying" ]
@p2.isImmune("Ground").should.be.true
gravity = @battle.getMove("Gravity")
@battle.performMove(@p1, gravity)
@p2.isImmune("Ground").should.be.false
it "makes Levitating Pokemon no longer immune to Ground", ->
shared.create.call(this, team2: [Factory("Magikarp", ability: "Levitate")])
@p2.isImmune("Ground").should.be.true
gravity = @battle.getMove("Gravity")
@battle.performMove(@p1, gravity)
@p2.isImmune("Ground").should.be.false
it "deals damage to Flying Pokemon", ->
shared.create.call(this)
@p2.types = [ "Flying" ]
gravity = @battle.getMove("Gravity")
earthquake = @battle.getMove("Earthquake")
earthquake.typeEffectiveness(@battle, @p1, @p2).should.equal(0)
@battle.performMove(@p1, gravity)
earthquake.typeEffectiveness(@battle, @p1, @p2).should.equal(1)
# TODO: Sky Drop
for moveName in [ "Bounce", "Fly" ]
do (moveName) ->
it "grounds pokemon using #{moveName}", ->
shared.create.call(this)
move = @battle.getMove(moveName)
gravity = @battle.getMove("Gravity")
@battle.recordMove(@id2, move)
@battle.continueTurn()
@p2.has(Attachment.Charging).should.be.true
@battle.recordMove(@id1, gravity)
@battle.continueTurn()
@p2.has(Attachment.Charging).should.be.false
it "removes Telekinesis", ->
shared.create.call(this)
telekinesis = @battle.getMove("Telekinesis")
gravity = @battle.getMove("Gravity")
@battle.performMove(@p1, telekinesis)
@p2.has(Attachment.Telekinesis).should.be.true
@battle.performMove(@p1, gravity)
@p2.has(Attachment.Telekinesis).should.be.false
it "removes Magnet Rise", ->
shared.create.call(this)
magnetRise = @battle.getMove("Magnet Rise")
gravity = @battle.getMove("Gravity")
@battle.performMove(@p2, magnetRise)
@p2.has(Attachment.MagnetRise).should.be.true
@battle.performMove(@p1, gravity)
@p2.has(Attachment.MagnetRise).should.be.false
for moveName in [ "Jump Kick", "Hi Jump Kick", "Bounce", "Fly", "Sky Drop",
"Splash", "Telekinesis" ]
do (moveName) ->
it "disables #{moveName}", ->
shared.create.call(this)
move = @battle.getMove(moveName)
gravity = @battle.getMove("Gravity")
@p1.moves = [ move ]
@p2.moves = [ move ]
@battle.performMove(@p1, gravity)
@battle.beginTurn()
@p1.validMoves().should.not.containEql(move)
@p2.validMoves().should.not.containEql(move)
it "prevents the execution of #{moveName}", ->
shared.create.call(this)
move = @battle.getMove(moveName)
gravity = @battle.getMove("Gravity")
@battle.performMove(@p1, gravity)
mock = @sandbox.mock(move).expects('execute').never()
@battle.performMove(@p2, move)
mock.verify()
testDelayedAttackMove = (moveName, type) ->
describe moveName, ->
shared.shouldFailIfUsedTwice(moveName)
it "waits three turns before attacking", ->
shared.create.call(this)
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
for x in [0...3]
@p2.currentHP.should.equal @p2.stat('hp')
@battle.endTurn()
@p2.currentHP.should.be.lessThan @p2.stat('hp')
it "does not initialize and error if the target has fainted", ->
shared.create.call(this)
move = @battle.getMove(moveName)
@p2.faint()
(=> @battle.performMove(@p1, move)).should.not.throw()
it "does not hit if the target has fainted", ->
shared.create.call(this)
move = @battle.getMove(moveName)
@battle.performMove(@p1, move)
for x in [0...2]
@battle.endTurn()
@p2.faint()
mock = @sandbox.mock(move).expects('hit').never()
@battle.endTurn()
mock.verify()
it "is considered a direct attack", ->
shared.create.call(this)
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.false
it "can target multiple positions"
testDelayedAttackMove("Future Sight")
testDelayedAttackMove("Doom Desire")
describe "Baton Pass", ->
it "fails if there is no benched pokemon to BP to", ->
shared.create.call(this, team1: [Factory('Magikarp')])
batonPass = @battle.getMove("Baton Pass")
mock = @sandbox.mock(batonPass).expects('fail').once()
@battle.performMove(@p1, batonPass)
mock.verify()
it "switches to another pokemon", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Celebi") ]
batonPass = @battle.getMove("Baton Pass")
@battle.performMove(@p1, batonPass)
@battle.requests.should.have.property @id1
it "passes certain attachments to the next pokemon", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Celebi") ]
batonPass = @battle.getMove("Baton Pass")
@p1.attach(Attachment.Ingrain)
@p1.attach(Attachment.Torment)
@battle.performMove(@p1, batonPass)
@battle.performSwitch(@team1.first(), 1)
@team1.first().has(Attachment.Torment).should.be.false
@team1.first().has(Attachment.Ingrain).should.be.true
it "continues Perish Song's counter", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Celebi") ]
batonPass = @battle.getMove("Baton Pass")
perishSong = @battle.getMove("Perish Song")
@battle.performMove(@p1, perishSong)
for i in [0...3]
@battle.endTurn()
@battle.performMove(@p1, batonPass)
@battle.performSwitch(@team1.first(), 1)
@team1.first().isFainted().should.be.false
@battle.endTurn()
@team1.first().isFainted().should.be.true
it "passes status boosts to the next pokemon", ->
shared.create.call this,
team1: [ Factory("Magikarp"), Factory("Celebi") ]
batonPass = @battle.getMove("Baton Pass")
@p1.boost(attack: 1, evasion: -3)
@battle.performMove(@p1, batonPass)
@battle.performSwitch(@team1.first(), 1)
@team1.first().stages.should.containEql(attack: 1, evasion: -3)
describe "Thunder Wave", ->
it "is affected by immunities", ->
shared.create.call(this)
thunderWave = @battle.getMove("Thunder Wave")
@p2.types = [ 'Ground' ]
@battle.performMove(@p1, thunderWave)
@p2.has(Status.Paralyze).should.be.false
describe "Thunder", ->
it "has 50% accuracy in Sun", ->
shared.create.call(this)
@battle.setWeather(Weather.SUN)
thunder = @battle.getMove("Thunder")
thunder.chanceToHit(@battle, @p1, @p2).should.equal(50)
it "has perfect accuracy in Rain", ->
shared.create.call(this)
@battle.setWeather(Weather.RAIN)
thunder = @battle.getMove("Thunder")
thunder.chanceToHit(@battle, @p1, @p2).should.equal(0)
it "has normal accuracy otherwise", ->
shared.create.call(this)
@battle.setWeather(Weather.HAIL)
thunder = @battle.getMove("Thunder")
thunder.chanceToHit(@battle, @p1, @p2).should.equal(70)
describe "Hurricane", ->
it "has 50% accuracy in Sun", ->
shared.create.call(this)
@battle.setWeather(Weather.SUN)
hurricane = @battle.getMove("Hurricane")
hurricane.chanceToHit(@battle, @p1, @p2).should.equal(50)
it "has perfect accuracy in Rain", ->
shared.create.call(this)
@battle.setWeather(Weather.RAIN)
hurricane = @battle.getMove("Hurricane")
hurricane.chanceToHit(@battle, @p1, @p2).should.equal(0)
it "has normal accuracy otherwise", ->
shared.create.call(this)
@battle.setWeather(Weather.HAIL)
hurricane = @battle.getMove("Hurricane")
hurricane.chanceToHit(@battle, @p1, @p2).should.equal(70)
describe "Blizzard", ->
it "has perfect accuracy in Hail", ->
shared.create.call(this)
@battle.setWeather(Weather.HAIL)
blizzard = @battle.getMove("Blizzard")
blizzard.chanceToHit(@battle, @p1, @p2).should.equal(0)
it "has normal accuracy otherwise", ->
shared.create.call(this)
blizzard = @battle.getMove("Blizzard")
blizzard.chanceToHit(@battle, @p1, @p2).should.equal(70)
describe "Sleep Talk", ->
it "fails if the pokemon is awake", ->
shared.create.call(this)
sleepTalk = @battle.getMove("Sleep Talk")
mock = @sandbox.mock(sleepTalk).expects('fail').once()
@battle.performMove(@p1, sleepTalk)
mock.verify()
it "performs a random move when the pokemon is asleep", ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'sleep talk', 0)
tackle = @battle.getMove("Tackle")
sleepTalk = @battle.getMove("Sleep Talk")
@p1.moves = [ tackle, sleepTalk ]
@p1.resetAllPP()
@p1.attach(Status.Sleep)
spy = @sandbox.spy(@battle, 'executeMove')
@battle.performMove(@p1, sleepTalk)
spy.calledWith(tackle).should.be.true
it "does not choose sleep talk or other moves", ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'sleep talk', 0)
tackle = @battle.getMove("Tackle")
sleepTalk = @battle.getMove("Sleep Talk")
assist = @battle.getMove("Assist")
@p1.moves = [ sleepTalk, assist, tackle ]
@p1.resetAllPP()
@p1.attach(Status.Sleep)
spy = @sandbox.spy(@battle, 'executeMove')
@battle.performMove(@p1, sleepTalk)
spy.calledWith(tackle).should.be.true
it "fails if there are no viable moves", ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'sleep talk', 0)
sleepTalk = @battle.getMove("Sleep Talk")
@p1.moves = [ sleepTalk ]
@p1.resetAllPP()
@p1.attach(Status.Sleep)
mock = @sandbox.mock(sleepTalk).expects('fail').once()
@battle.performMove(@p1, sleepTalk)
mock.verify()
it "changes the move's target properly", ->
shared.create.call(this)
shared.biasRNG.call(this, 'randInt', 'sleep talk', 0)
tackle = @battle.getMove("Tackle")
sleepTalk = @battle.getMove("Sleep Talk")
@p1.moves = [ tackle, sleepTalk ]
@p1.resetAllPP()
@p1.attach(Status.Sleep)
mock = @sandbox.mock(tackle)
mock.expects('execute').once().withArgs(@battle, @p1, [ @p2 ])
@battle.performMove(@p1, sleepTalk)
mock.verify()
describe "Rest", ->
it "fails if the pokemon is at full HP", ->
shared.create.call(this)
rest = @battle.getMove("Rest")
mock = @sandbox.mock(rest).expects('fail').once()
@p1.setHP(@p1.stat('hp'))
@battle.performMove(@p1, rest)
mock.verify()
it "restores the pokemon to full HP otherwise", ->
shared.create.call(this)
rest = @battle.getMove("Rest")
@p1.setHP(1)
@battle.performMove(@p1, rest)
@p1.currentHP.should.equal @p1.stat('hp')
it "sleeps the pokemon", ->
shared.create.call(this)
rest = @battle.getMove("Rest")
@p1.setHP(1)
@p1.has(Status.Sleep).should.be.false
@battle.performMove(@p1, rest)
@p1.has(Status.Sleep).should.be.true
it "fails if the pokemon is already asleep", ->
shared.create.call(this)
rest = @battle.getMove("Rest")
mock = @sandbox.mock(rest).expects('fail').once()
@p1.setHP(1)
@p1.attach(Status.Sleep)
# execute instead of perform to avoid beforeMove callbacks.
@battle.executeMove(rest, @p1, [ @p1 ])
mock.verify()
it "cures the pokemon of adverse status effects", ->
shared.create.call(this)
rest = @battle.getMove("Rest")
@p1.setHP(1)
@p1.attach(Status.Burn)
@p1.has(Status.Burn).should.be.true
@battle.performMove(@p1, rest)
@p1.has(Status.Sleep).should.be.true
@p1.has(Status.Burn).should.be.false
it "always forces sleep turns to be 2", ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", "sleep turns", 3)
rest = @battle.getMove("Rest")
@p1.setHP(1)
@battle.performMove(@p1, rest)
attachment = @p1.get(Status.Sleep)
should.exist(attachment)
attachment.should.have.property("turns")
attachment.turns.should.equal(2)
it "fails if the Pokemon cannot be slept", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Vital Spirit")]
rest = @battle.getMove("Rest")
mock = @sandbox.mock(rest).expects('fail').once()
@p1.setHP(1)
@battle.performMove(@p1, rest)
mock.verify()
describe "Defog", ->
it "lowers target's evasion by 1", ->
shared.create.call(this, gen: 'bw')
defog = @battle.getMove("Defog")
@battle.performMove(@p1, defog)
@p2.stages.should.containEql(evasion: -1)
it "removes Reflect on target's side", ->
shared.create.call(this, gen: 'bw')
defog = @battle.getMove("Defog")
@battle.performMove(@p2, @battle.getMove("Reflect"))
@p2.team.has(Attachment.Reflect).should.be.true
@battle.performMove(@p1, defog)
@p2.team.has(Attachment.Reflect).should.be.false
it "removes Light Screen on target's side", ->
shared.create.call(this, gen: 'bw')
defog = @battle.getMove("Defog")
@battle.performMove(@p2, @battle.getMove("Light Screen"))
@p2.team.has(Attachment.LightScreen).should.be.true
@battle.performMove(@p1, defog)
@p2.team.has(Attachment.LightScreen).should.be.false
it "removes Spikes on target's side", ->
shared.create.call(this, gen: 'bw')
defog = @battle.getMove("Defog")
@battle.performMove(@p1, @battle.getMove("Spikes"))
@p2.team.has(Attachment.Spikes).should.be.true
@battle.performMove(@p1, defog)
@p2.team.has(Attachment.Spikes).should.be.false
it "removes Stealth Rock on target's side", ->
shared.create.call(this, gen: 'bw')
defog = @battle.getMove("Defog")
@battle.performMove(@p1, @battle.getMove("Stealth Rock"))
@p2.team.has(Attachment.StealthRock).should.be.true
@battle.performMove(@p1, defog)
@p2.team.has(Attachment.StealthRock).should.be.false
it "removes Toxic Spikes on target's side", ->
shared.create.call(this, gen: 'bw')
defog = @battle.getMove("Defog")
@battle.performMove(@p1, @battle.getMove("Toxic Spikes"))
@p2.team.has(Attachment.ToxicSpikes).should.be.true
@battle.performMove(@p1, defog)
@p2.team.has(Attachment.ToxicSpikes).should.be.false
it "removes Safeguard on target's side", ->
shared.create.call(this, gen: 'bw')
defog = @battle.getMove("Defog")
@battle.performMove(@p2, @battle.getMove("Safeguard"))
@p2.team.has(Attachment.Safeguard).should.be.true
@battle.performMove(@p1, defog)
@p2.team.has(Attachment.Safeguard).should.be.false
it "removes Mist on target's side"
describe "Refresh", ->
it "fails if the Pokemon has no status", ->
shared.create.call(this, gen: 'bw')
refresh = @battle.getMove("Refresh")
mock = @sandbox.mock(refresh).expects('fail').once()
@battle.performMove(@p1, refresh)
mock.verify()
it "restores a Pokemon's status", ->
shared.create.call(this, gen: 'bw')
refresh = @battle.getMove("Refresh")
@p1.attach(Status.Burn)
@p1.has(Status.Burn).should.be.true
@battle.performMove(@p1, refresh)
@p1.has(Status.Burn).should.be.false
it "sends a message about status being restored", ->
shared.create.call(this, gen: 'bw')
refresh = @battle.getMove("Refresh")
@p1.attach(Status.Burn)
spy = @sandbox.spy(@battle, 'message')
@battle.performMove(@p1, refresh)
spy.args.join(',').should.containEql("#{@p1.name} healed its burn!")
describe "Growth", ->
it "boosts the pokemon's stats", ->
shared.create.call(this, gen: 'bw')
@battle.setWeather(Weather.SUN)
@battle.performMove(@p1, @battle.getMove('Growth'))
@p1.stages.should.containEql attack: 2, specialAttack: 2
describe "Reflect Type", ->
it "copies the target's types", ->
shared.create.call(this, team1: [Factory("Magikarp")], team2: [Factory("Charmander")])
@battle.performMove(@p1, @battle.getMove('Reflect Type'))
@p1.types.should.containEql "Fire"
it "fails if the user has multitype", ->
shared.create.call(this, team1: [Factory("Arceus", ability: "Multitype")], team2: [Factory("Toxicroak")])
@battle.performMove(@p1, @battle.getMove('Reflect Type'))
@p1.types.should.containEql "Normal"
describe "Retaliate", ->
it "doubles in power if a pokemon on the user's side fainted last turn", ->
shared.create.call this,
team1: [Factory("Magikarp"), Factory("Slaking")]
@p2.currentHP = 1
@battle.beginTurn()
@battle.recordMove(@id1, @battle.getMove("Aqua Jet"))
@battle.recordMove(@id2, @battle.getMove("Splash"))
@battle.continueTurn()
@battle.endTurn()
@battle.beginTurn()
retaliate = @battle.getMove("Retaliate")
spy = @sandbox.spy(retaliate, 'basePower')
@battle.performMove(@p2, retaliate)
spy.returned(140).should.be.true
it "deals normal damage otherwise", ->
shared.create.call(this)
retaliate = @battle.getMove("Retaliate")
spy = @sandbox.spy(retaliate, 'basePower')
@battle.performMove(@p1, retaliate)
spy.returned(70).should.be.true
describe "Role Play", ->
it "copies the target's ability", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Swift Swim")]
team2: [Factory("Magikarp", ability: "Levitate")]
@battle.performMove(@p1, @battle.getMove('Role Play'))
@p1.hasAbility("Levitate").should.be.true
it "does not copy certain abilities", ->
shared.create.call this,
team1: [Factory("Magikarp"), ability: "Swift Swim"]
team2: [Factory("Arceus", ability: "Multitype")]
@battle.performMove(@p1, @battle.getMove('Role Play'))
@p1.hasAbility("Multitype").should.be.false
describe "Relic Song", ->
it "transforms Meloetta", ->
shared.create.call this,
team1: [Factory("Meloetta")]
team2: [Factory("Arceus")]
@battle.performMove(@p1, @battle.getMove('Relic Song'))
@p1.forme.should.equal("pirouette")
@battle.performMove(@p1, @battle.getMove('Relic Song'))
@p1.forme.should.equal("default")
it "changes Meloetta to default forme after switching out", ->
shared.create.call this,
team1: [Factory("Meloetta"), Factory("Magikarp")]
team2: [Factory("Arceus")]
@battle.performMove(@p1, @battle.getMove('Relic Song'))
@p1.forme.should.equal("pirouette")
@battle.performSwitch(@p1, 1)
@battle.performSwitch(@team1.first(), 1)
@p1.forme.should.equal("default")
it "does not transform Pokemon that are not Meloetta", ->
shared.create.call this,
team1: [Factory("Smeargle")]
team2: [Factory("Magikarp")]
@p1.forme.should.equal("default")
@battle.performMove(@p1, @battle.getMove('Relic Song'))
@p1.forme.should.equal("default")
describe "Safeguard", ->
it "protects the user's team from status", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Safeguard"))
@battle.performMove(@p2, @battle.getMove("Thunder Wave"))
@p1.has(Status.Paralyze).should.be.false
it "protects the user's team from confusion", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Safeguard"))
@battle.performMove(@p2, @battle.getMove("Confuse Ray"))
@p1.has(Attachment.Confusion).should.be.false
it "doesn't protect against status inflicted by the user", ->
shared.create.call(this)
@p1.team.attach(Attachment.Safeguard)
@p1.setHP(1)
@battle.performMove(@p1, @battle.getMove("Rest"))
@p1.has(Status.Sleep).should.be.true
it "protects against yawn being inflicted", ->
shared.create.call(this)
@p1.team.attach(Attachment.Safeguard)
@battle.performMove(@p2, @battle.getMove("Yawn"))
@p1.has(Attachment.Yawn).should.be.false
it "doesn't prevent yawn's effect", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove('Yawn'))
@battle.endTurn()
@p2.has(Status.Sleep).should.be.false
@p2.team.attach(Attachment.Safeguard)
@battle.endTurn()
@p2.has(Status.Sleep).should.be.true
describe "Soak", ->
it "changes the target's type to Water", ->
shared.create.call this,
team1: [Factory("Magikarp")]
team2: [Factory("Smeargle")]
@p2.types.should.not.eql ['Water']
@battle.performMove(@p1, @battle.getMove('Soak'))
@p2.types.should.eql ['Water']
it "does not affect pure Water type Pokemon", ->
shared.create.call this,
team1: [Factory("Smeargle")]
team2: [Factory("Magikarp")]
@p2.types.should.eql ['Water']
soak = @battle.getMove("Soak")
mock = @sandbox.mock(soak).expects('fail').once()
@battle.performMove(@p1, soak)
mock.verify()
it "does not affect Arceus", ->
shared.create.call this,
team1: [Factory("Smeargle")]
team2: [Factory("Arceus")]
soak = @battle.getMove("Soak")
mock = @sandbox.mock(soak).expects('fail').once()
@battle.performMove(@p1, soak)
mock.verify()
describe 'Electro Ball', ->
it 'has variable base power based on speed of user and target', ->
shared.create.call this,
team1: [Factory('Electrode', evs: {speed: 252}, nature: "Timid")]
team2: [Factory('Smeargle')]
move = @battle.getMove('Electro Ball')
move.basePower(@battle, @p1, @p2).should.equal 80
describe "Snore", ->
it "fails if the pokemon is awake", ->
shared.create.call(this)
snore = @battle.getMove("Snore")
mock = @sandbox.mock(snore).expects('fail').once()
@battle.performMove(@p1, snore)
mock.verify()
it "attacks the target when the user is asleep", ->
shared.create.call this,
snore = @battle.getMove("Snore")
@p1.attach(Status.Sleep)
mock = @sandbox.mock(snore).expects('hit').once()
@battle.performMove(@p1, snore)
mock.verify()
describe "Worry Seed", ->
it "changes the target's ability to Insomnia", ->
shared.create.call this,
team1: [Factory("Smeargle")]
team2: [Factory("Magikarp", ability: "Swift Swim")]
@battle.performMove(@p1, @battle.getMove('Worry Seed'))
@p2.hasAbility("Insomnia").should.be.true
it "does not change some abilities", ->
shared.create.call this,
team1: [Factory("Smeargle")]
team2: [Factory("Arceus", ability: "Multitype")]
worrySeed = @battle.getMove("Worry Seed")
mock = @sandbox.mock(worrySeed).expects('fail').once()
@battle.performMove(@p1, worrySeed)
mock.verify()
describe "Simple Beam", ->
it "changes the target's ability to Simple", ->
shared.create.call this,
team1: [Factory("Smeargle")]
team2: [Factory("Magikarp", ability: "Swift Swim")]
@battle.performMove(@p1, @battle.getMove('Simple Beam'))
@p2.hasAbility("Simple").should.be.true
it "does not change some abilities", ->
shared.create.call this,
team1: [Factory("Smeargle")]
team2: [Factory("Arceus", ability: "Multitype")]
simpleBeam = @battle.getMove("Simple Beam")
mock = @sandbox.mock(simpleBeam).expects('fail').once()
@battle.performMove(@p1, simpleBeam)
mock.verify()
describe "Entrainment", ->
it "copies the user's ability to the target", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Swift Swim")]
team2: [Factory("Smeargle", ability: "Own Tempo")]
@battle.performMove(@p1, @battle.getMove('Entrainment'))
@p2.hasAbility("Swift Swim").should.be.true
it "does not copy some abilities", ->
shared.create.call this,
team1: [Factory("Arceus", ability: "Multitype")]
team2: [Factory("Magikarp", ability: "Swift Swim")]
entrainment = @battle.getMove("Entrainment")
mock = @sandbox.mock(entrainment).expects('fail').once()
@battle.performMove(@p1, entrainment)
mock.verify()
describe "Natural Gift", ->
it "changes power and type depending on the berry held by the user", ->
shared.create.call this,
team1: [Factory("Smeargle", item: 'Liechi Berry')]
team2: [Factory("Magikarp")]
move = @battle.getMove('Natural Gift')
basePower = move.basePower(@battle, @p1, @p2)
basePower.should.equal(80)
move.typeEffectiveness(@battle, @p1, @p2).should.equal(2)
it "only works with certain items", ->
shared.create.call this,
team1: [Factory("Smeargle", item: 'Leftovers')]
naturalGift = @battle.getMove('Natural Gift')
mock = @sandbox.mock(naturalGift).expects('fail').once()
@battle.performMove(@p1, naturalGift)
mock.verify()
it "should remove the item even if missing"
describe "False Swipe", ->
it "leaves the target with at least 1 HP", ->
shared.create.call(this)
@p2.currentHP = 1
@battle.performMove(@p1, @battle.getMove("False Swipe"))
@p2.currentHP.should.equal 1
it "does normal damage if the target has a substitute", ->
shared.create.call(this)
@p2.currentHP = 1
@p2.attach(Attachment.Substitute)
falseSwipe = @battle.getMove("False Swipe")
spy = @sandbox.spy(falseSwipe, 'calculateDamage')
@battle.performMove(@p1, falseSwipe)
spy.returned(0).should.be.false
describe "Gastro Acid", ->
it "suppresses the target's ability", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Gastro Acid"))
@p2.has(Attachment.AbilitySuppress).should.be.true
@p2.isAbilityBlocked().should.be.true
it "continues turn by turn", ->
shared.create.call(this)
@battle.performMove(@p1, @battle.getMove("Gastro Acid"))
@battle.endTurn()
@battle.beginTurn()
@p2.has(Attachment.AbilitySuppress).should.be.true
@p2.isAbilityBlocked().should.be.true
it "fails against some abilities", ->
shared.create.call(this, team2: [Factory("Arceus", ability: "Multitype")])
gastroAcid = @battle.getMove("Gastro Acid")
mock = @sandbox.mock(gastroAcid).expects('fail').once()
@battle.performMove(@p1, gastroAcid)
mock.verify()
describe "Helping Hand", ->
it "fails if there is no target", ->
shared.create.call(this)
helpingHand = @battle.getMove("Helping Hand")
mock = @sandbox.mock(helpingHand).expects('fail').once()
@battle.performMove(@p1, helpingHand)
mock.verify()
describe "Skill Swap", ->
it "swaps the user's ability with the target's", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Swift Swim")]
team2: [Factory("Smeargle", ability: "Own Tempo")]
@battle.performMove(@p1, @battle.getMove('Skill Swap'))
@p1.hasAbility("Own Tempo").should.be.true
@p2.hasAbility("Swift Swim").should.be.true
it "cannot swap some abilities", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Swift Swim")]
team2: [Factory("Shedinja", ability: "Wonder Guard")]
skillSwap = @battle.getMove("Skill Swap")
mock = @sandbox.mock(skillSwap).expects('fail').once()
@battle.performMove(@p1, skillSwap)
mock.verify()
it "cannot swap the abilities if they are the same", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Swift Swim")]
team2: [Factory("Magikarp", ability: "Swift Swim")]
skillSwap = @battle.getMove("Skill Swap")
mock = @sandbox.mock(skillSwap).expects('fail').once()
@battle.performMove(@p1, skillSwap)
mock.verify()
it "activates abilities after swapping", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Swift Swim")]
team2: [Factory("Gyarados", ability: "Intimidate")]
@battle.performMove(@p1, @battle.getMove('Skill Swap'))
@p1.hasAbility("Intimidate").should.be.true
@p2.hasAbility("Swift Swim").should.be.true
@p2.stages.should.containEql(attack: -1)
describe "Heal Block", ->
it "lasts 5 turns", ->
shared.create.call(this)
healBlock = @battle.getMove("Heal Block")
@battle.performMove(@p1, healBlock)
for x in [0...5]
@p2.has(Attachment.HealBlock).should.be.true
@battle.endTurn()
@p2.has(Attachment.HealBlock).should.be.false
it 'prevents the target from executing a healing move', ->
shared.create.call(this, team1: [ Factory('Magikarp', evs: {speed: 4}) ])
move = @battle.getMove('Recover')
healBlock = @battle.getMove('Heal Block')
mock = @sandbox.mock(move)
mock.expects('execute').never()
@battle.performMove(@p1, healBlock)
@battle.performMove(@p2, move)
mock.verify()
it 'prevents the target from selecting a healing move', ->
shared.create.call this,
team2: [Factory("Magikarp", moves: [ "Splash", "Recover" ])]
@battle.performMove(@p1, @battle.getMove('Heal Block'))
@battle.beginTurn()
requestedMoves = @battle.requestFor(@p2).moves
requestedMoves.should.not.containEql 'Recover'
it "prevents Abilities that heal from healing", ->
shared.create.call this,
team2: [Factory("Magikarp", ability: "Water Absorb")]
@p2.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Heal Block'))
@battle.endTurn()
@battle.performMove(@p1, @battle.getMove('Water Gun'))
@p2.currentHP.should.equal 1
it "does not prevent Regenerator from healing", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Regenerator")]
@p1.currentHP = 1
@battle.performMove(@p2, @battle.getMove('Heal Block'))
@p1.switchOut(@battle)
hp = Math.floor(@p1.stat('hp') / 3)
@p1.currentHP.should.equal(1 + hp)
it "prevents items that heal from healing", ->
shared.create.call this,
team2: [Factory("Magikarp", item: "Leftovers")]
@p2.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Heal Block'))
@battle.endTurn()
@p2.currentHP.should.equal 1
it "prevents Berries that heal from attempting to activate", ->
shared.create.call this,
team2: [Factory("Magikarp", item: "Sitrus Berry")]
item = @p2.item
hp = @p2.currentHP
@p2.currentHP >>= 1
@battle.performMove(@p1, @battle.getMove('Heal Block'))
@battle.endTurn()
@battle.performMove(@p1, @battle.getMove('Tackle'))
@p2.currentHP.should.be.lessThan hp >> 1
@p2.item.should.equal item
describe "Recycle", ->
it "restores the user's last used item", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Salac Berry")]
@p1.useItem()
@battle.performMove(@p1, @battle.getMove("Recycle"))
@p1.hasItem("Salac Berry").should.be.true
it "fails if the user has an item", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Salac Berry")]
recycle = @battle.getMove("Recycle")
@p1.useItem()
@battle.performMove(@p1, recycle)
@p1.hasItem("Salac Berry").should.be.true
mock = @sandbox.mock(recycle).expects('fail').once()
@battle.performMove(@p1, recycle)
mock.verify()
it "fails if the user has no last used item", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Air Balloon")]
recycle = @battle.getMove("Recycle")
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p1.hasItem("Air Balloon").should.be.false
mock = @sandbox.mock(recycle).expects('fail').once()
@battle.performMove(@p1, recycle)
mock.verify()
it "cannot restore an item that was forcefully removed", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Salac Berry")]
recycle = @battle.getMove("Recycle")
@battle.performMove(@p2, @battle.getMove("Covet"))
@p1.hasItem("Salac Berry").should.be.false
@p2.hasItem("Salac Berry").should.be.true
mock = @sandbox.mock(recycle).expects('fail').once()
@battle.performMove(@p1, recycle)
mock.verify()
it "can restore the last used item if the current item is forcefully removed", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Salac Berry")]
team2: [Factory("Magikarp", item: "Figy Berry")]
recycle = @battle.getMove("Recycle")
@p1.useItem()
@battle.performMove(@p2, @battle.getMove("Trick"))
@p1.hasItem("Figy Berry").should.be.true
@battle.performMove(@p2, @battle.getMove("Knock Off"))
@battle.performMove(@p1, recycle)
@p1.hasItem("Salac Berry").should.be.true
it "cannot restore the last used item if it was restored and forcefully removed", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Salac Berry")]
recycle = @battle.getMove("Recycle")
@p1.useItem()
@battle.performMove(@p1, recycle)
@p1.hasItem("Salac Berry").should.be.true
@battle.performMove(@p2, @battle.getMove("Knock Off"))
mock = @sandbox.mock(recycle).expects('fail').once()
@battle.performMove(@p1, recycle)
mock.verify()