BattleSim/test/bw/abilities.coffee

2779 lines
111 KiB
CoffeeScript

require '../helpers'
{Attachment, Status} = require('../../server/bw/attachment')
{Battle} = require('../../server/bw/battle')
{Pokemon} = require('../../server/bw/pokemon')
{Weather} = require('../../shared/weather')
{Item} = require('../../server/bw/data/items')
{Ability} = require '../../server/bw/data/abilities'
util = require '../../server/bw/util'
{Factory} = require '../factory'
should = require 'should'
sinon = require 'sinon'
{_} = require 'underscore'
shared = require '../shared'
describe "BW Abilities:", ->
describe "Adaptability", ->
it "makes STAB 2x instead of 1.5", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Adaptability")]
hydroPump = @battle.getMove("Hydro Pump")
hydroPump.stabModifier(@battle, @p1, @p2).should.equal(0x2000)
it "still has 1x if the pokemon does not have STAB", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Adaptability")]
tackle = @battle.getMove("Tackle")
tackle.stabModifier(@battle, @p1, @p2).should.equal(0x1000)
describe "Aftermath", ->
it "deals 25% HP damage to the killer", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Aftermath"), Factory("Magikarp")]
@p1.currentHP = 1
@battle.performMove(@p2, @battle.getMove("Tackle"))
@battle.performFaints()
(@p2.stat('hp') - @p2.currentHP).should.equal(@p2.stat('hp') >> 2)
it "does not deal damage for non-contact moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Aftermath"), Factory("Magikarp")]
@p1.currentHP = 1
@battle.performMove(@p2, @battle.getMove("Thunderbolt"))
@battle.performFaints()
@p2.currentHP.should.equal(@p2.stat('hp'))
it "does not deal damage if pokemon died of natural causes", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Aftermath"), Factory("Magikarp")]
thunderbolt = @battle.getMove("Thunderbolt")
@p1.currentHP = 2
@p1.attach(Status.Burn)
@sandbox.stub(thunderbolt, 'calculateDamage', -> 1)
@battle.performMove(@p2, thunderbolt)
@battle.endTurn()
@p2.currentHP.should.equal(@p2.stat('hp'))
it "does not crash if the Pokemon never was hit", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Aftermath"), Factory("Magikarp")]
(=> @p1.faint()).should.not.throw()
it "deals a minimum of 1 damage", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Aftermath"), Factory("Magikarp")]
team2: [Factory("Shedinja")]
@p1.currentHP = 2
@battle.performMove(@p2, @battle.getMove("Tackle"))
@battle.performFaints()
@p2.currentHP.should.equal(0)
testWeatherPreventionAbility = (name) ->
describe name, ->
it "causes the battle to think there is no weather", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
@battle.setWeather(Weather.SAND)
@battle.hasWeather(Weather.SAND).should.be.false
@battle.setWeather(Weather.SUN)
@battle.hasWeather(Weather.SUN).should.be.false
testWeatherPreventionAbility("Air Lock")
testWeatherPreventionAbility("Cloud Nine")
describe "Analytic", ->
it "multiplies attacks by 1.3 if no one is moving afterward", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Analytic")]
tackle = @battle.getMove("Tackle")
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x14CD)
it "doesn't multiply if the user isn't the last to move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Analytic")]
tackle = @battle.getMove("Tackle")
@battle.recordMove(@id2, tackle)
@battle.determineTurnOrder()
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
describe "Anger Point", ->
it "maximizes Attack on critical hit", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Anger Point")]
tackle = @battle.getMove("Tackle")
@sandbox.stub(tackle, 'isCriticalHit', -> true)
@p1.stages.attack.should.equal(0)
@battle.performMove(@p2, tackle)
@p1.stages.should.containEql(attack: 6)
it "doesn't maximize attack on non-critical hits", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Anger Point")]
tackle = @battle.getMove("Tackle")
@sandbox.stub(tackle, 'isCriticalHit', -> false)
@p1.stages.attack.should.equal(0)
@battle.performMove(@p2, tackle)
@p1.stages.should.containEql(attack: 0)
describe "Anticipation", ->
it "shows a message if an opponent has a super-effective move", ->
shared.create.call this,
team1: [Factory("Magikarp"), Factory("Magikarp", ability: "Anticipation")]
team2: [Factory("Pikachu", moves: ["Thunderbolt"])]
spy = @sandbox.spy(@battle, 'cannedText')
@battle.performSwitch(@p1, 1)
spy.calledWithMatch('ANTICIPATION').should.be.true
it "shows a message if an opponent has an OHKO move", ->
shared.create.call this,
team1: [Factory("Magikarp"), Factory("Magikarp", ability: "Anticipation")]
team2: [Factory("Lapras", moves: ["Sheer Cold"])]
spy = @sandbox.spy(@battle, 'cannedText')
@battle.performSwitch(@p1, 1)
spy.calledWithMatch('ANTICIPATION').should.be.true
it "doesn't show a message otherwise", ->
shared.create.call this,
team1: [Factory("Magikarp"), Factory("Magikarp", ability: "Anticipation")]
spy = @sandbox.spy(@battle, 'cannedText')
@battle.performSwitch(@p1, 1)
spy.calledWithMatch('ANTICIPATION').should.be.false
it "displays nothing when no opponents left", ->
shared.create.call this,
team1: [Factory("Magikarp")]
team2: [Factory("Magikarp"), Factory("Magikarp")]
@p2.faint()
spy = @sandbox.spy(@battle, 'cannedText')
@p1.copyAbility(Ability.Anticipation)
spy.called.should.be.false
describe "Arena Trap", ->
it "blocks switch", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Arena Trap")]
team2: [Factory("Magikarp")]
@p2.isSwitchBlocked().should.be.true
it "blocks switch the next turn as well", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Arena Trap")]
team2: [Factory("Magikarp")]
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.true
it "doesn't block switch for Flying Pokemon", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Arena Trap")]
team2: [Factory("Gyarados")]
@p2.isSwitchBlocked().should.be.false
it "doesn't block switch for Pokemon immune to Ground by other means", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Arena Trap")]
team2: [Factory("Magikarp")]
@battle.performMove(@p2, @battle.getMove("Magnet Rise"))
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.false
describe "Bad Dreams", ->
it "deals 1/8 max HP end of turn if target is asleep", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Bad Dreams")]
team2: [Factory("Celebi")]
(@p2.stat('hp') - @p2.currentHP).should.equal(0)
@p2.attach(Status.Sleep)
@battle.endTurn()
(@p2.stat('hp') - @p2.currentHP).should.equal(@p2.stat('hp') >> 3)
it "deals no damage if target is awake", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Bad Dreams")]
team2: [Factory("Celebi")]
(@p2.stat('hp') - @p2.currentHP).should.equal(0)
@battle.endTurn()
(@p2.stat('hp') - @p2.currentHP).should.equal(0)
it "deals a minimum of 1 damage", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Bad Dreams")]
team2: [Factory("Shedinja")]
@p2.attach(Status.Sleep)
@battle.endTurn()
@p2.currentHP.should.equal(0)
testCriticalHitPreventionAbility = (name) ->
describe name, ->
it "prevents critical hits", ->
shared.create.call this,
team1: [Factory("Magikarp")]
team2: [Factory("Celebi", ability: name)]
shared.biasRNG.call(this, "next", "ch", .2)
tackle = @battle.getMove("Tackle")
@sandbox.stub(tackle, 'criticalHitLevel', -> 3)
tackle.isCriticalHit(@battle, @p1, @p2).should.be.false
testCriticalHitPreventionAbility("Battle Armor")
testCriticalHitPreventionAbility("Shell Armor")
testBoostProtectionAbility = (name, protection) ->
describe name, ->
allBoosts = [ "attack", "defense", "speed", "specialAttack",
"specialDefense", "accuracy", "evasion" ]
protection ||= allBoosts
it "protects against certain stat falls", ->
shared.create.call this, team1: [Factory("Celebi", ability: name)]
boosts = {}
boosts[stat] = -1 for stat in allBoosts
@p1.boost(boosts, @p2)
boosts[stat] = 0 for stat in protection
@p1.stages.should.eql(boosts)
it "does not protect against stat falls if the source is the user", ->
shared.create.call this, team1: [Factory("Celebi", ability: name)]
boosts = {}
boosts[stat] = -1 for stat in allBoosts
@p1.boost(boosts)
@p1.stages.should.containEql(boosts)
testBoostProtectionAbility("Big Pecks", [ "defense" ])
testBoostProtectionAbility("Clear Body")
testBoostProtectionAbility("Hyper Cutter", [ "attack" ])
testBoostProtectionAbility("Keen Eye", [ "accuracy" ])
testBoostProtectionAbility("White Smoke")
testLowHealthAbility = (name, type) ->
describe name, ->
it "increases power of #{type} moves by 1.5 at 1/3 health", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
@p1.currentHP = Math.floor(@p1.stat('hp') / 3)
move = _(@battle.MoveList).find (m) ->
m.type == type && !m.isNonDamaging()
move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1800)
it "doesn't increase power if move not of #{type}", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
@p1.currentHP = Math.floor(@p1.stat('hp') / 3)
move = _(@battle.MoveList).find (m) ->
m.type != type && !m.isNonDamaging()
move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
it "doesn't increase power if user isn't under 1/3 health", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
@p1.currentHP = Math.floor(@p1.stat('hp') / 3) + 1
move = _(@battle.MoveList).find (m) ->
m.type == type && !m.isNonDamaging()
move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
testLowHealthAbility("Blaze", "Fire")
testLowHealthAbility("Torrent", "Water")
testLowHealthAbility("Overgrow", "Grass")
testLowHealthAbility("Swarm", "Bug")
testWeatherSpeedAbility = (name, weather) ->
describe name, ->
it "doubles speed when the weather becomes #{weather}", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
speed = @p1.stat('speed')
@battle.setWeather(weather)
@p1.stat('speed').should.equal(2 * speed)
it "goes back to normal when the weather becomes something else", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
speed = @p1.stat('speed')
@battle.setWeather(weather)
@battle.setWeather(Weather.NONE)
@p1.stat('speed').should.equal(speed)
it "grants immunity to damage from their respective weather", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
@p1.isWeatherDamageImmune(weather).should.be.true
testWeatherSpeedAbility("Chlorophyll", Weather.SUN)
testWeatherSpeedAbility("Swift Swim", Weather.RAIN)
testWeatherSpeedAbility("Sand Rush", Weather.SAND)
describe "Color Change", ->
it "changes the owner's type to be the move's type that just hit it", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Color Change")]
ember = @battle.getMove("Ember")
@p1.hasType(ember.type).should.be.false
@battle.performMove(@p2, ember)
@p1.hasType(ember.type).should.be.true
it "does not change the owner's type if the move is non-damaging", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Color Change")]
willOWisp = @battle.getMove("Will-O-Wisp")
@p1.hasType(willOWisp.type).should.be.false
@battle.performMove(@p2, willOWisp)
@p1.hasType(willOWisp.type).should.be.false
describe "Compoundeyes", ->
it "increases accuracy of moves by 1.3x", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Compoundeyes")]
willOWisp = @battle.getMove("Will-O-Wisp")
accuracy = willOWisp.chanceToHit(@battle, @p1, @p2)
accuracy.should.equal Math.floor(willOWisp.accuracy * 1.3)
describe "Contrary", ->
it "reverses stat changes from all sources", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Contrary")]
leafStorm = @battle.getMove("Leaf Storm")
growl = @battle.getMove("Growl")
@battle.performMove(@p1, leafStorm)
@p1.stages.should.containEql(specialAttack: 2)
@battle.performMove(@p2, growl)
@p1.stages.should.containEql(attack: 1)
describe "Cursed Body", ->
it "has a 30% chance to disable an attacker's last move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Cursed Body")]
shared.biasRNG.call(this, "next", "cursed body", 0)
tackle = @battle.getMove("Tackle")
@p2.moves = [ tackle ]
@p2.resetAllPP()
@battle.performMove(@p2, tackle)
@p2.isMoveBlocked(tackle).should.be.true
it "does not disable the owner", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Cursed Body")]
shared.biasRNG.call(this, "next", "cursed body", 0)
recover = @battle.getMove("Recover")
@p1.moves = [ recover ]
@p1.resetAllPP()
@p1.currentHP = 1
@battle.performMove(@p1, recover)
@p1.isMoveBlocked(recover).should.be.false
it "does not disable if attacker is behind a substitute", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Cursed Body")]
shared.biasRNG.call(this, "next", "cursed body", 0)
tackle = @battle.getMove("Tackle")
substitute = @battle.getMove("Substitute")
@p2.moves = [ tackle ]
@p2.resetAllPP()
@battle.performMove(@p2, substitute)
@battle.performMove(@p2, tackle)
@p2.isMoveBlocked(tackle).should.be.false
it "does not disable if defender is behind a substitute", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Cursed Body")]
shared.biasRNG.call(this, "next", "cursed body", 0)
tackle = @battle.getMove("Tackle")
substitute = @battle.getMove("Substitute")
@p2.moves = [ tackle ]
@p2.resetAllPP()
@battle.performMove(@p1, substitute)
@battle.performMove(@p2, tackle)
@p2.isMoveBlocked(tackle).should.be.false
it "has a 70% chance to do nothing", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Cursed Body")]
shared.biasRNG.call(this, "next", "cursed body", .3)
tackle = @battle.getMove("Tackle")
@p2.moves = [ tackle ]
@p2.resetAllPP()
@battle.performMove(@p2, tackle)
@p2.isMoveBlocked(tackle).should.be.false
it "can work even if the defender faints", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Cursed Body"), Factory("Magikarp")]
shared.biasRNG.call(this, "next", "cursed body", 0)
@p1.currentHP = 1
tackle = @battle.getMove("Tackle")
@p2.moves = [ tackle ]
@p2.resetAllPP()
@battle.performMove(@p2, tackle)
@p2.isMoveBlocked(tackle).should.be.true
it "lasts some turns", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Cursed Body")]
shared.biasRNG.call(this, "next", "cursed body", 0)
tackle = @battle.getMove("Tackle")
@p2.moves = [ tackle ]
@p2.resetAllPP()
@battle.performMove(@p2, tackle)
for x in [1..2]
@battle.endTurn()
@battle.beginTurn()
@p2.isMoveBlocked(tackle).should.be.true
it "cannot stack with Disable", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Cursed Body")]
shared.biasRNG.call(this, "next", "cursed body", 1) # do not trigger
tackle = @battle.getMove("Tackle")
@p2.moves = [ tackle ]
@p2.resetAllPP()
@battle.performMove(@p2, tackle)
@battle.performMove(@p1, @battle.getMove('Disable'))
@p2.isMoveBlocked(tackle).should.be.true
# Disable lasts 4 turns, so go to the last turn.
for x in [1..3]
@battle.endTurn()
@battle.beginTurn()
@p2.isMoveBlocked(tackle).should.be.true
shared.biasRNG.call(this, "next", "cursed body", 0)
@battle.performMove(@p2, tackle)
@p2.isMoveBlocked(tackle).should.be.true
# Disable should end.
@battle.endTurn()
@battle.beginTurn()
@p2.isMoveBlocked(tackle).should.be.false
describe "Defeatist", ->
it "halves attack and special attack if HP goes under 50%", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Defeatist")]
tackle = @battle.getMove("Tackle")
thunderbolt = @battle.getMove("Thunderbolt")
# 50%
@p1.currentHP = (@p1.stat('hp') >> 1)
tackle.modifyAttack(@battle, @p1, @p2).should.equal(0x800)
thunderbolt.modifyAttack(@battle, @p1, @p2).should.equal(0x800)
# 50% + 1
@p1.currentHP = (@p1.stat('hp') >> 1) + 1
tackle.modifyAttack(@battle, @p1, @p2).should.equal(0x1000)
thunderbolt.modifyAttack(@battle, @p1, @p2).should.equal(0x1000)
describe "Defiant", ->
it "boosts attack by 2 every time a stat is lowered", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Defiant")]
@p1.boost(defense: -1, @p2)
@p1.stages.should.containEql(attack: 2)
@p1.boost(attack: -1, defense: -2, evasion: 1, @p2)
@p1.stages.should.containEql(attack: 5)
it "does not boost attack if the stat was self-lowered", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Defiant")]
@battle.performMove(@p1, @battle.getMove("Close Combat"))
@p1.stages.should.containEql(attack: 0, defense: -1, specialDefense: -1)
describe "Download", ->
it "raises attack if foes have total defense < total sp.def", ->
shared.create.call this,
team1: [Factory("Magikarp")]
team2: [Factory("Abomasnow")]
@p1.stages.should.containEql(attack: 0)
@p1.copyAbility(Ability.Download)
@p1.stages.should.containEql(attack: 1)
it "raises special attack if foes have total sp.def <= total def", ->
shared.create.call this,
team1: [Factory("Magikarp")]
team2: [Factory("Celebi")]
@p1.stages.should.containEql(specialAttack: 0)
@p1.copyAbility(Ability.Download)
@p1.stages.should.containEql(specialAttack: 1)
it "does not raise anything if all foes have fainted", ->
shared.create.call(this)
@p2.faint()
@p1.stages.should.containEql(attack: 0, specialAttack: 0)
@p1.copyAbility(Ability.Download)
@p1.stages.should.containEql(attack: 0, specialAttack: 0)
testWeatherAbility = (name, weather) ->
describe name, ->
it "causes unending #{weather}", ->
shared.build(this, team1: [Factory("Magikarp", ability: name)])
@battle.hasWeather().should.be.false
@controller.beginBattle()
@battle.hasWeather(weather).should.be.true
@battle.weatherDuration.should.equal(-1)
testWeatherAbility("Drizzle", Weather.RAIN)
testWeatherAbility("Drought", Weather.SUN)
testWeatherAbility("Sand Stream", Weather.SAND)
testWeatherAbility("Snow Warning", Weather.HAIL)
describe "Dry Skin", ->
it "gets healed 25% HP by water moves", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Dry Skin")])
@p1.currentHP = 1
@battle.performMove(@p2, @battle.getMove("Hydro Pump"))
@p1.currentHP.should.equal(1 + (@p1.stat('hp') >> 2))
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p1.currentHP.should.be.lessThan(1 + (@p1.stat('hp') >> 2))
it "does not get healed by self-targeting water moves", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Dry Skin")])
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove("Aqua Ring"))
@p1.currentHP.should.equal(1)
it "gets healed 1/8 HP end of turn in Rain", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Dry Skin")])
@p1.currentHP = 1
@battle.endTurn()
@p1.currentHP.should.equal(1)
@battle.setWeather(Weather.RAIN)
@battle.endTurn()
@p1.currentHP.should.equal(1 + (@p1.stat('hp') >> 3))
it "gets damaged 25% extra by fire moves", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Dry Skin")])
ember = @battle.getMove("Ember")
ember.modifyBasePower(@battle, @p2, @p1).should.equal(0x1400)
tackle = @battle.getMove("Tackle")
tackle.modifyBasePower(@battle, @p2, @p1).should.equal(0x1000)
it "gets hurt 1/8 HP end of turn in Sun", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Dry Skin")])
@battle.endTurn()
@p1.currentHP.should.equal(@p1.stat('hp'))
@battle.setWeather(Weather.SUN)
@battle.endTurn()
(@p1.stat('hp') - @p1.currentHP).should.equal(@p1.stat('hp') >> 3)
it "deals a minimum of 1 damage", ->
shared.create.call this,
team1: [Factory("Shedinja", ability: "Dry Skin")]
@battle.setWeather(Weather.SUN)
@battle.endTurn()
@p1.currentHP.should.equal(0)
it "heals a minimum of 1 hp", ->
shared.create.call this,
team1: [Factory("Magikarp", level: 1, ability: "Dry Skin")]
@p1.currentHP = 1
@battle.setWeather(Weather.RAIN)
@battle.endTurn()
@p1.currentHP.should.equal(2)
describe "Early Bird", ->
it "drops the sleep counter by 2 instead of 1", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Early Bird")])
shared.biasRNG.call(this, "randInt", 'sleep turns', 3)
@p1.attach(Status.Sleep)
@battle.performMove(@p1, @battle.getMove('Tackle'))
@p1.has(Status.Sleep).should.be.true
@battle.performMove(@p1, @battle.getMove('Tackle'))
@p1.has(Status.Sleep).should.be.false
describe "Effect Spore", ->
it "has a 30% chance to inflict poison, paralysis, or sleep on hit", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Effect Spore")])
# Sleep
shared.biasRNG.call(this, "randInt", 'effect spore', 1)
@battle.performMove(@p2, @battle.getMove('Tackle'))
@p2.has(Status.Sleep).should.be.true
# Paralysis
@p2.cureStatus()
shared.biasRNG.call(this, "randInt", 'effect spore', 2)
@battle.performMove(@p2, @battle.getMove('Tackle'))
@p2.has(Status.Paralyze).should.be.true
# Poison
@p2.cureStatus()
shared.biasRNG.call(this, "randInt", 'effect spore', 3)
@battle.performMove(@p2, @battle.getMove('Tackle'))
@p2.has(Status.Poison).should.be.true
it "has a 70% chance to do nothing", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Effect Spore")])
shared.biasRNG.call(this, "randInt", 'effect spore', 4)
@battle.performMove(@p2, @battle.getMove('Tackle'))
@p2.hasStatus().should.be.false
it "doesn't trigger if the hitting move is a non-contact move", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Effect Spore")])
shared.biasRNG.call(this, "randInt", 'effect spore', 1)
@battle.performMove(@p2, @battle.getMove('Thunderbolt'))
@p2.has(Status.Sleep).should.be.false
it "can work even if the defender faints", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Effect Spore"), Factory("Magikarp")]
shared.biasRNG.call(this, "randInt", 'effect spore', 1)
@p1.currentHP = 1
@battle.performMove(@p2, @battle.getMove('Tackle'))
@p2.has(Status.Sleep).should.be.true
testFilterAbility = (name) ->
describe name, ->
it "reduces the impact of super-effective moves by 25%", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name)])
seMove = _(@battle.MoveList).find (m) =>
!m.isNonDamaging() && util.typeEffectiveness(m.type, @p1.types) > 1
seMove.modifyDamage(@battle, @p2, @p1).should.equal(0xC00)
it "keeps non-super-effective moves as normal", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name)])
seMove = _(@battle.MoveList).find (m) =>
!m.isNonDamaging() && util.typeEffectiveness(m.type, @p1.types) <= 1
seMove.modifyDamage(@battle, @p2, @p1).should.equal(0x1000)
testFilterAbility("Filter")
testFilterAbility("Solid Rock")
testContactStatusAbility = (name, attachment) ->
describe name, ->
it "has a 30% chance to inflict #{attachment.name} on the attacker", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "F", ability: name)]
team2: [Factory("Magikarp", gender: "M")]
shared.biasRNG.call(this, "next", 'contact status', 0)
@battle.performMove(@p2, @battle.getMove('Tackle'))
@p2.has(attachment).should.be.true
it "inflicts no status if the move used is a non-contact move", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "F", ability: name)]
team2: [Factory("Magikarp", gender: "M")]
shared.biasRNG.call(this, "next", 'contact status', 0)
@battle.performMove(@p2, @battle.getMove('Thunderbolt'))
@p2.has(attachment).should.be.false
it "inflicts no status if the hit isn't direct", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
team2: [Factory("Magikarp")]
shared.biasRNG.call(this, "next", 'contact status', 0)
@p1.attach(Attachment.Substitute)
@battle.performMove(@p2, @battle.getMove('Tackle'))
@p2.has(attachment).should.be.false
it "has a 70% chance to do nothing", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "F", ability: name)]
team2: [Factory("Magikarp", gender: "M")]
shared.biasRNG.call(this, "next", 'contact status', .3)
@battle.performMove(@p2, @battle.getMove('Tackle'))
@p2.has(attachment).should.be.false
it "can work even if the defender faints", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "F", ability: name), Factory("Magikarp")]
team2: [Factory("Magikarp", gender: "M")]
shared.biasRNG.call(this, "next", 'contact status', 0)
@p1.currentHP = 1
tackle = @battle.getMove('Tackle')
@battle.performMove(@p2, tackle)
@p2.has(attachment).should.be.true
testContactStatusAbility("Cute Charm", Attachment.Attract)
testContactStatusAbility("Flame Body", Status.Burn)
testContactStatusAbility("Poison Point", Status.Poison)
testContactStatusAbility("Static", Status.Paralyze)
testStatusBoostAbility = (name, statuses, spectra) ->
statusNames = statuses.map((s) -> s.name).join(', ')
describe name, ->
it "increases #{spectra} moves by 1.5 if has #{statusNames}", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name)])
move = _(@battle.MoveList).find (m) ->
!m.isNonDamaging() && m.spectra == spectra
for status in statuses
@p1.cureStatus()
@p1.attach(status)
move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1800)
it "does not increase non-#{spectra} moves", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name)])
move = _(@battle.MoveList).find (m) ->
!m.isNonDamaging() && m.spectra != spectra
for status in statuses
@p1.cureStatus()
@p1.attach(status)
move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
testStatusBoostAbility("Flare Boost", [Status.Burn], "special")
testStatusBoostAbility("Toxic Boost", [Status.Poison, Status.Toxic], "physical")
describe "Flash Fire", ->
it "makes user invulnerable to Fire-type moves", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Flash Fire")])
ember = @battle.getMove("Ember")
mock = @sandbox.mock(ember).expects('hit').never()
@battle.performMove(@p2, ember)
mock.verify()
it "powers up user's Fire-type moves", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Flash Fire")])
ember = @battle.getMove("Ember")
tackle = @battle.getMove("Tackle")
@battle.performMove(@p2, ember)
ember.modifyBasePower(@battle, @p1, @p2).should.equal(0x1800)
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
it "doesn't activate on Protect", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Flash Fire")])
protect = @battle.getMove("Protect")
ember = @battle.getMove("Ember")
@battle.recordMove(@id1, protect)
@battle.recordMove(@id2, ember)
@battle.continueTurn()
@p1.has(Attachment.FlashFire).should.be.false
describe "Flower Gift", ->
it "changes Cherrim's forme"
it "does not change a transformed pokemon's forme"
it "grants x1.5 attack and special defense in Sun"
it "grants x1.5 attack and special defense to allies in Sun"
describe "Forecast", ->
it "changes Castform's forme"
it "does not change a transformed pokemon's forme"
describe "Forewarn", ->
it "considers OHKO moves to have 160 BP", ->
shared.create.call(this)
Ability.Forewarn.consider(@battle.getMove("Fissure")).should.equal(160)
it "considers counter moves to have 120 BP", ->
shared.create.call(this)
{consider} = Ability.Forewarn
consider(@battle.getMove("Counter")).should.equal(120)
consider(@battle.getMove("Mirror Coat")).should.equal(120)
consider(@battle.getMove("Metal Burst")).should.equal(120)
it "considers specific variable power moves to have 80 BP", ->
shared.create.call(this)
{consider} = Ability.Forewarn
consider(@battle.getMove("Crush Grip")).should.equal(80)
consider(@battle.getMove("Dragon Rage")).should.equal(80)
consider(@battle.getMove("Endeavor")).should.equal(80)
consider(@battle.getMove("Flail")).should.equal(80)
consider(@battle.getMove("Frustration")).should.equal(80)
consider(@battle.getMove("Grass Knot")).should.equal(80)
consider(@battle.getMove("Gyro Ball")).should.equal(80)
consider(@battle.getMove("Hidden Power")).should.equal(80)
consider(@battle.getMove("Low Kick")).should.equal(80)
consider(@battle.getMove("Natural Gift")).should.equal(80)
consider(@battle.getMove("Night Shade")).should.equal(80)
consider(@battle.getMove("Psywave")).should.equal(80)
consider(@battle.getMove("Return")).should.equal(80)
consider(@battle.getMove("Reversal")).should.equal(80)
consider(@battle.getMove("Seismic Toss")).should.equal(80)
consider(@battle.getMove("SonicBoom")).should.equal(80)
consider(@battle.getMove("Trump Card")).should.equal(80)
consider(@battle.getMove("Wring Out")).should.equal(80)
it "alerts user about a foe's move with the highest base power", ->
shared.build(this, team1: [Factory("Magikarp", ability: "Forewarn")])
spy = @sandbox.spy(@battle, 'cannedText')
@controller.beginBattle()
tackle = @battle.getMove("Tackle")
spy.calledWith('FOREWARN', @p2, tackle).should.be.true
it "displays nothing when no opponents left", ->
shared.create.call this,
team1: [Factory("Magikarp")]
team2: [Factory("Magikarp"), Factory("Magikarp")]
@p2.faint()
spy = @sandbox.spy(@battle, 'cannedText')
@p1.copyAbility(Ability.Forewarn)
spy.called.should.be.false
describe "Friend Guard", ->
it "weakens attacks from allies by 25%", ->
shared.create.call this,
numActive: 2
team1: [Factory("Magikarp"), Factory("Magikarp", ability: "Friend Guard")]
earthquake = @battle.getMove("Earthquake")
earthquake.modifyDamage(@battle, @p1, @team1.at(1)).should.equal(0xC00)
it "keeps attacks by enemies at normal", ->
shared.create.call this,
numActive: 2
team1: [Factory("Magikarp"), Factory("Magikarp", ability: "Friend Guard")]
earthquake = @battle.getMove("Earthquake")
earthquake.modifyDamage(@battle, @p2, @team1.at(1)).should.equal(0x1000)
describe "Frisk", ->
it "randomly selects an opponent and displays the item", ->
shared.build this,
team1: [Factory("Magikarp", ability: "Frisk")]
team2: [Factory("Magikarp", item: "Leftovers")]
spy = @sandbox.spy(@battle, 'cannedText')
@controller.beginBattle()
spy.calledWithMatch('FRISK').should.be.true
it "displays nothing when no opponents left", ->
shared.create.call this,
team1: [Factory("Magikarp")]
team2: [Factory("Magikarp"), Factory("Magikarp")]
@p2.faint()
spy = @sandbox.spy(@battle, 'cannedText')
@p1.copyAbility(Ability.Frisk)
spy.called.should.be.false
describe "Gluttony", ->
it "makes berries activate at 50% HP", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Salac Berry", ability: "Gluttony")]
@p1.stages.should.containEql(speed: 0)
@p1.currentHP >>= 1
@p1.update()
@p1.stages.should.containEql(speed: 1)
describe "Guts", ->
it "multiplies attack by x1.5 if statused", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Guts")])
tackle = @battle.getMove("Tackle")
tackle.modifyAttack(@battle, @p1, @p2).should.equal(0x1000)
@p1.attach(Status.Burn)
tackle.modifyAttack(@battle, @p1, @p2).should.equal(0x1800)
it "does not multiply attack if move is special", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Guts")])
thunderbolt = @battle.getMove("Thunderbolt")
@p1.attach(Status.Burn)
thunderbolt.modifyAttack(@battle, @p1, @p2).should.equal(0x1000)
describe "Harvest", ->
it "has a 50% chance of re-obtaining a berry it used", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Salac Berry", ability: "Harvest")]
shared.biasRNG.call(this, "randInt", "harvest", 1)
@p1.useItem()
@battle.endTurn()
@p1.hasItem("Salac Berry").should.be.true
it "has a 50% chance of doing nothing", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Salac Berry", ability: "Harvest")]
shared.biasRNG.call(this, "randInt", "harvest", 0)
@p1.useItem()
@battle.endTurn()
@p1.hasItem("Salac Berry").should.be.false
it "has a 100% chance to re-obtain the berry in Sun", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Salac Berry", ability: "Harvest")]
shared.biasRNG.call(this, "randInt", "harvest", 0)
@p1.useItem()
@battle.setWeather(Weather.SUN)
@battle.endTurn()
@p1.hasItem("Salac Berry").should.be.true
it "does not regain a normal item", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Flying Gem", ability: "Harvest")]
shared.biasRNG.call(this, "randInt", "harvest", 1)
@p1.useItem()
@battle.endTurn()
@p1.hasItem("Flying Gem").should.be.false
it "does not regain the berry if it was removed", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Salac Berry", ability: "Harvest")]
shared.biasRNG.call(this, "randInt", "harvest", 1)
@p1.useItem()
@battle.endTurn()
@p1.hasItem("Salac Berry").should.be.true
@p1.removeItem()
@battle.endTurn()
@p1.hasItem("Salac Berry").should.be.false
it "uses a regained Berry immediately if the condition is right", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Sitrus Berry", ability: "Harvest")]
shared.biasRNG.call(this, "randInt", "harvest", 1)
@p1.useItem()
@p1.currentHP = 1
@p1.hasItem("Sitrus Berry").should.be.false
@battle.endTurn()
@p1.currentHP.should.equal(1 + (@p1.stat('hp') >> 2))
@p1.hasItem("Sitrus Berry").should.be.false
describe "Healer", ->
it "has a 30% chance of healing an adjacent ally's status", ->
shared.create.call this,
numActive: 3
team1: [Factory("Magikarp", ability: "Healer"), Factory("Magikarp"), Factory("Magikarp")]
shared.biasRNG.call(this, "randInt", "healer", 3)
@team1.at(0).attach(Status.Burn)
@team1.at(1).attach(Status.Burn)
@team1.at(2).attach(Status.Burn)
@battle.endTurn()
@team1.at(0).hasStatus().should.be.true
@team1.at(1).hasStatus().should.be.false
@team1.at(2).hasStatus().should.be.true
it "has a 70% chance to do nothing", ->
shared.create.call this,
numActive: 2
team1: [Factory("Magikarp"), Factory("Magikarp", ability: "Healer")]
shared.biasRNG.call(this, "randInt", "healer", 4)
@p1.attach(Status.Burn)
@battle.endTurn()
@p1.hasStatus().should.be.true
describe "Heatproof", ->
it "receives half damage from Fire-type moves", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Heatproof")])
ember = @battle.getMove("Ember")
ember.modifyBasePower(@battle, @p2, @p1).should.equal(0x800)
it "receives normal damage from non-Fire-type moves", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Heatproof")])
tackle = @battle.getMove("Tackle")
tackle.modifyBasePower(@battle, @p2, @p1).should.equal(0x1000)
it "receives half damage from burn", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Heatproof")])
@p1.attach(Status.Burn)
burn = @p1.get(Status.Burn)
mock = @sandbox.mock(burn).expects('endTurn').returns(@p1.currentHP >> 4)
@battle.endTurn()
mock.verify()
describe "Heavy Metal", ->
it "doubles the user's weight", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Heavy Metal")])
@p1.calculateWeight().should.equal(2 * @p1.weight)
testHugePowerAbility = (name) ->
describe name, ->
it "doubles attack when using a physical move", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name)])
tackle = @battle.getMove("Tackle")
tackle.modifyAttack(@battle, @p1, @p2).should.equal(0x2000)
it "does not double attack when using a special move", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name)])
thunderbolt = @battle.getMove("Thunderbolt")
thunderbolt.modifyAttack(@battle, @p1, @p2).should.equal(0x1000)
testHugePowerAbility("Huge Power")
testHugePowerAbility("Pure Power")
describe "Hustle", ->
it "multiplies attack by x1.5", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Hustle")])
tackle = @battle.getMove("Tackle")
tackle.modifyAttack(@battle, @p1, @p2).should.equal(0x1800)
thunderbolt = @battle.getMove("Thunderbolt")
thunderbolt.modifyAttack(@battle, @p1, @p2).should.equal(0x1000)
it "makes physical moves have 20% less accuracy", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Hustle")])
tackle = @battle.getMove("Tackle")
accuracy = tackle.chanceToHit(@battle, @p1, @p2)
accuracy.should.equal Math.floor(tackle.accuracy * 0.8)
thunderbolt = @battle.getMove("Thunderbolt")
accuracy = thunderbolt.chanceToHit(@battle, @p1, @p2)
accuracy.should.equal Math.floor(thunderbolt.accuracy * 1.0)
describe "Hydration", ->
it "restores status, in Rain, at the end of the turn", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Hydration")])
@p1.attach(Status.Sleep)
@battle.setWeather(Weather.RAIN)
@battle.endTurn()
@p1.hasStatus().should.be.false
it "does not restore status if the weather is not rainy", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Hydration")])
@p1.attach(Status.Sleep)
@battle.endTurn()
@p1.hasStatus().should.be.true
describe "Ice Body", ->
it "restores 1/16 HP in Hail", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Ice Body")]
@p1.currentHP = 1
@battle.setWeather(Weather.HAIL)
@battle.endTurn()
@p1.currentHP.should.equal(1 + (@p1.stat('hp') >> 4))
it "restores no HP in other weather", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Ice Body")]
@p1.currentHP = 1
@battle.endTurn()
@p1.currentHP.should.equal(1)
it "grants immunity to Hail", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Ice Body")]
@p1.isWeatherDamageImmune(Weather.HAIL).should.be.true
describe "Illusion", ->
it "masquerades as the last unfainted pokemon in player's party"
it "does not masquerade if all pokemon are fainted"
it "is broken when the user takes direct damage"
describe "Imposter", ->
it "automatically transforms into the adjacent foe pokemon", ->
shared.create.call this,
numActive: 2
team1: (Factory("Magikarp") for x in [1..2])
team2: [Factory("Celebi"), Factory("Mew")]
@team1.at(1).copyAbility(Ability.Imposter)
@team1.at(1).has(Attachment.Transform).should.be.true
it "does not transform if target is behind a substitute", ->
shared.create.call(this)
@p2.attach(Attachment.Substitute, hp: 1)
@p1.copyAbility(Ability.Imposter)
@p1.has(Attachment.Transform).should.be.false
it "does not transform if target is already transformed", ->
shared.create.call(this)
@battle.performMove(@p2, @battle.getMove("Transform"))
@p2.has(Attachment.Transform).should.be.true
@p1.copyAbility(Ability.Imposter)
@p1.has(Attachment.Transform).should.be.false
it "does not transform if no one is alive", ->
shared.create.call this,
team1: [Factory("Magikarp")]
team2: [Factory("Magikarp"), Factory("Celebi")]
@p2.faint()
@p1.copyAbility(Ability.Imposter)
@p1.has(Attachment.Transform).should.be.false
testAttachmentImmuneAbility = (name, attachments, options = {}) ->
describe name, ->
it "prevents the pokemon from receiving a specific attachment", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "F", ability: name)]
team2: [Factory("Magikarp", gender: "M")]
for attachment in attachments
should.not.exist @p1.attach(attachment, source: @p2)
@p1.has(attachment).should.be.false
shouldCure = options.cure ? true
if shouldCure
it "removes the attachment if the pokemon already has it", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: "F")]
team2: [Factory("Magikarp", gender: "M")]
for attachment in attachments
@p1.attach(attachment, source: @p2)
@p1.has(attachment).should.be.true
@p1.copyAbility(Ability[name.replace(/\s+/g, '')])
@p1.update()
@p1.has(attachment).should.be.false
@p1.copyAbility(null)
testAttachmentImmuneAbility("Immunity", [Status.Poison, Status.Toxic])
testAttachmentImmuneAbility("Inner Focus", [Attachment.Flinch], cure: false)
testAttachmentImmuneAbility("Insomnia", [Status.Sleep])
testAttachmentImmuneAbility("Limber", [Status.Paralyze])
testAttachmentImmuneAbility("Magma Armor", [Status.Freeze])
testAttachmentImmuneAbility("Oblivious", [Attachment.Attract])
testAttachmentImmuneAbility("Own Tempo", [Attachment.Confusion])
testAttachmentImmuneAbility("Vital Spirit", [Status.Sleep])
testAttachmentImmuneAbility("Water Veil", [Status.Burn])
describe "Intimidate", ->
it "lowers the attack of all foe pokemon", ->
shared.create.call this,
team1: [ Factory("Magikarp", ability: "Intimidate") ]
@p2.stages.should.containEql(attack: -1)
it "does not lower attack of Pokemon behind Substitute", ->
shared.create.call this,
team1: [ Factory("Magikarp") ]
@p2.attach(Attachment.Substitute, hp: (@p2.currentHP >> 2))
@p1.copyAbility(Ability.Intimidate)
@p2.stages.should.containEql(attack: 0)
it "lowers attack simultaneously on all begin-turn switch-ins", ->
shared.create.call this,
team1: (Factory("Magikarp", ability: "Intimidate") for x in [1..2])
team2: (Factory("Magikarp", ability: "Intimidate") for x in [1..2])
@p1.stages.should.containEql(attack: -1)
@p2.stages.should.containEql(attack: -1)
@p1.faint()
@p2.faint()
@battle.requestFaintedReplacements()
@controller.makeSwitch(@id1, 1)
@controller.makeSwitch(@id2, 1)
@team1.first().should.not.equal(@p1)
@team2.first().should.not.equal(@p2)
@team1.first().stages.should.containEql(attack: -1)
@team2.first().stages.should.containEql(attack: -1)
describe "Infiltrator", ->
it "ignores Reflect", ->
shared.create.call this,
team1: [ Factory("Magikarp", ability: "Infiltrator") ]
@team2.attach(Attachment.Reflect)
tackle = @battle.getMove('Tackle')
tackle.modifyDamage(@battle, @p1, @p2).should.equal(0x1000)
it "ignores Light Screen", ->
shared.create.call this,
team1: [ Factory("Magikarp", ability: "Infiltrator") ]
@team2.attach(Attachment.LightScreen)
thundershock = @battle.getMove('ThunderShock')
thundershock.modifyDamage(@battle, @p1, @p2).should.equal(0x1000)
it "ignores Safeguard"
it "ignores Mist"
testContactHurtAbility = (name) ->
describe name, ->
it "damages for 1/8 HP on contact moves", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name), Factory("Magikarp")])
hp = @p2.stat('hp')
@battle.performMove(@p2, @battle.getMove("Tackle"))
(hp - @p2.currentHP).should.equal(hp >> 3)
it "does not damage for non-contact moves", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name), Factory("Magikarp")])
@battle.performMove(@p2, @battle.getMove("Thunderbolt"))
@p2.currentHP.should.equal @p2.stat('hp')
it "does not damage for non-direct hits", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name), Factory("Magikarp")])
@p1.attach(Attachment.Substitute)
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p2.currentHP.should.equal @p2.stat('hp')
it "still works even if the owner faints", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name), Factory("Magikarp")])
hp = @p2.stat('hp')
@p1.currentHP = 1
@battle.performMove(@p2, @battle.getMove("Tackle"))
(hp - @p2.currentHP).should.equal(hp >> 3)
it "deals a minimum of 1 damage", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
team2: [Factory("Shedinja")]
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p2.currentHP.should.equal(0)
testContactHurtAbility("Iron Barbs")
testContactHurtAbility("Rough Skin")
describe "Iron Fist", ->
it "increases base power of punching moves by approximately x1.3", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Iron Fist")])
icePunch = @battle.getMove("Ice Punch")
icePunch.modifyBasePower(@battle, @p1, @p2).should.equal(0x1333)
it "does not increase base power of non-punching moves", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Iron Fist")])
tackle = @battle.getMove("Tackle")
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
describe "Justified", ->
it "boosts attack by 1 after being hit by a Dark move", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Justified")])
@p1.stages.should.containEql(attack: 0)
@battle.performMove(@p2, @battle.getMove("Tackle"))
@p1.stages.should.containEql(attack: 0)
@battle.performMove(@p2, @battle.getMove("Crunch"))
@p1.stages.should.containEql(attack: 1)
it "doesn't boost attack if the move is non-damaging", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Justified")])
@p1.stages.should.containEql(attack: 0)
@battle.performMove(@p1, @battle.getMove("Nasty Plot"))
@p1.stages.should.containEql(attack: 0)
it "doesn't boost attack if the move hits a substitute", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Justified")])
@p1.attach(Attachment.Substitute, hp: 1)
@p1.stages.should.containEql(attack: 0)
@battle.performMove(@p2, @battle.getMove("Crunch"))
@p1.stages.should.containEql(attack: 0)
describe "Klutz", ->
it "disables user's item upon switch-in", ->
shared.create.call this,
team1: [Factory("Magikarp"), Factory("Magikarp", item: "Leftovers", ability: "Klutz")]
@battle.performSwitch(@p1, 1)
@team1.first().isItemBlocked().should.be.true
it "disables user's item in the beginning of the turn", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Leftovers", ability: "Klutz")]
@p1.isItemBlocked().should.be.true
@battle.beginTurn()
@p1.isItemBlocked().should.be.true
describe "Leaf Guard", ->
it "defends against statuses under Sun", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Leaf Guard")]
@battle.setWeather(Weather.SUN)
@p1.attach(Status.Burn)
@p1.has(Status.Burn).should.be.false
it "does not defend against statuses otherwise", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Leaf Guard")]
@p1.attach(Status.Burn)
@p1.has(Status.Burn).should.be.true
describe "Levitate", ->
it "adds a ground immunity", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Levitate")]
@p1.isImmune('Ground').should.be.true
describe "Light Metal", ->
it "halves the user's weight", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Light Metal")])
@p1.calculateWeight().should.equal(@p1.weight >> 1)
testRedirectAndBoostAbility = (name, type) ->
describe name, ->
it "should redirect attacks of #{type} to user"
it "makes user immune to #{type}", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
typedMove = _(@battle.MoveList).find (m) ->
!m.isNonDamaging() && m.type == type
mock = @sandbox.mock(typedMove).expects('hit').never()
@battle.performMove(@p2, typedMove)
mock.verify()
it "boosts special attack on #{type}-type moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
typedMove = _(@battle.MoveList).find (m) ->
!m.isNonDamaging() && m.type == type
@battle.performMove(@p2, typedMove)
@p1.stages.should.containEql(specialAttack: 1)
it "does not boost special attack if the user is the target", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
typedMove = _(@battle.MoveList).find (m) ->
m.target == 'user' && m.type == type
@battle.performMove(@p1, typedMove)
@p1.stages.should.containEql(specialAttack: 0)
it "does not boost special attack on #{type}-type moves if immune", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
typedMove = _(@battle.MoveList).find (m) ->
!m.isNonDamaging() && m.type == type
@sandbox.stub(@p1, 'isImmune', -> true)
@battle.performMove(@p2, typedMove)
@p1.stages.should.containEql(specialAttack: 0)
it "does nothing otherwise", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
tackle = @battle.getMove("Tackle")
mock = @sandbox.mock(tackle).expects('hit').once()
@battle.performMove(@p2, tackle)
mock.verify()
@p1.stages.should.containEql(specialAttack: 0)
testRedirectAndBoostAbility("Lightningrod", "Electric")
testRedirectAndBoostAbility("Storm Drain", "Water")
describe "Liquid Ooze", ->
it "causes drain attacks to damage the user as well", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Liquid Ooze")]
@battle.performMove(@p2, @battle.getMove("Giga Drain"))
@p2.currentHP.should.be.lessThan @p2.stat('hp')
it "causes Leech Seed to damage the user as well", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Liquid Ooze")]
@battle.performMove(@p2, @battle.getMove("Leech Seed"))
@battle.endTurn()
@p2.currentHP.should.be.lessThan @p2.stat('hp')
describe "Magic Bounce", ->
it "still has the magic coat effect next turn", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magic Bounce")]
@p1.has(Attachment.MagicCoat).should.be.true
@team1.has(Attachment.MagicCoat).should.be.true
@battle.endTurn()
@p1.has(Attachment.MagicCoat).should.be.true
@team1.has(Attachment.MagicCoat).should.be.true
describe "Magic Guard", ->
it "takes no damage from anything that isn't a move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magic Guard")]
@p1.attach(Status.Burn)
@p1.currentHP.should.equal(@p1.stat('hp'))
@battle.endTurn()
@p1.currentHP.should.equal(@p1.stat('hp'))
it "takes damage from moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magic Guard")]
@battle.performMove(@p2, @battle.getMove('Tackle'))
@p1.currentHP.should.be.lessThan(@p1.stat('hp'))
it "takes damage from Struggle", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magic Guard")]
@battle.performMove(@p1, @battle.getMove('Struggle'))
@p1.currentHP.should.be.lessThan(@p1.stat('hp'))
it "takes damage from Belly Drum", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magic Guard")]
@battle.performMove(@p1, @battle.getMove('Belly Drum'))
@p1.currentHP.should.be.lessThan(@p1.stat('hp'))
it "takes damage from confusion", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magic Guard")]
shared.biasRNG.call(this, 'next', 'confusion', 0)
@p1.attach(Attachment.Confusion)
@battle.performMove(@p1, @battle.getMove('Tackle'))
@p1.currentHP.should.be.lessThan(@p1.stat('hp'))
it "takes damage from using Curse", ->
shared.create.call this,
team1: [Factory("Gengar", ability: "Magic Guard")]
@battle.performMove(@p1, @battle.getMove('Curse'))
@p1.currentHP.should.be.lessThan(@p1.stat('hp'))
it "takes damage from Substitute", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magic Guard")]
@battle.performMove(@p1, @battle.getMove('Substitute'))
@p1.currentHP.should.be.lessThan(@p1.stat('hp'))
it "takes damage from Pain Split", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magic Guard")]
@p1.currentHP = 1
@battle.performMove(@p1, @battle.getMove('Pain Split'))
@p1.currentHP.should.be.greaterThan(1)
it "takes damage from Counter", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magic Guard", moves: ["Counter"])]
@controller.makeMove(@id1, "Counter")
@controller.makeMove(@id2, "Tackle")
@p1.currentHP.should.be.lessThan(@p1.stat('hp'))
it "restores health from Leftovers", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magic Guard", item: "Leftovers")]
@p1.currentHP = 1
@battle.endTurn()
@p1.currentHP.should.be.greaterThan(1)
it "receives no damage from Rocky Helmet", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magic Guard")]
@p1.currentHP = 1
@battle.endTurn()
@p1.currentHP.should.equal(1)
describe "Magnet Pull", ->
it "prevents Steel-type Pokemon from switching", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magnet Pull")]
team2: [Factory("Magnemite")]
@p2.isSwitchBlocked().should.be.true
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.true
it "doesn't prevent non-Steel-type Pokemon from switching", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Magnet Pull")]
team2: [Factory("Magikarp")]
@p2.isSwitchBlocked().should.be.false
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.false
describe "Marvel Scale", ->
it "multiplies defense by 1.5 when statused", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Marvel Scale")]
defense = @p1.stat('defense')
@p1.attach(Status.Burn)
@p1.stat('defense').should.equal Math.floor(1.5 * defense)
describe "Minus", ->
it "multiplies special attack by 1.5 if on the field with Plus", ->
shared.create.call this,
numActive: 2
team1: [Factory("Magikarp"), Factory("Magikarp")]
team2: [Factory("Magikarp"), Factory("Magikarp")]
specialAttack = @p1.stat('specialAttack')
@team1.at(0).copyAbility(Ability.Minus)
@team1.at(1).copyAbility(Ability.Plus)
thunderbolt = @battle.getMove("Thunderbolt")
thunderbolt.modifyAttack(@battle, @team1.at(0), @p2).should.equal(0x1800)
testAbilityCancelAbility = (name) ->
describe name, ->
it "emits a catchphrase when switching in or activating", ->
shared.create.call(this)
mock = @sandbox.mock(@battle).expects('cannedText').once()
@p1.copyAbility(Ability[name.replace(/\s+/, '')])
mock.verify()
it "cancels abilities for the duration of the user's move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
team2: [Factory("Magikarp", ability: "Levitate")]
eq = @battle.getMove("Earthquake")
mock = @sandbox.mock(eq).expects('hit').once()
@battle.performMove(@p1, eq)
mock.verify()
it "cancels modifier abilities for the duration of the user's move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
team2: [Factory("Magikarp", ability: "Thick Fat")]
ib = @battle.getMove("Ice Beam")
spy = @sandbox.spy(ib, 'modifyAttack')
spy = spy.withArgs(@battle, @p1, @p2)
@battle.performMove(@p1, ib)
spy.returnValues[0].should.equal(0x1000)
it "resets canceled abilities after the move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
team2: [Factory("Magikarp", ability: "Levitate")]
eq = @battle.getMove("Earthquake")
@p2.isAbilityBlocked().should.be.false
@battle.performMove(@p1, eq)
@p2.isAbilityBlocked().should.be.false
it "does not reset already-canceled abilities after performing move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
team2: [Factory("Magikarp", ability: "Levitate")]
@p2.blockAbility()
eq = @battle.getMove("Earthquake")
@p2.isAbilityBlocked().should.be.true
@battle.performMove(@p1, eq)
@p2.isAbilityBlocked().should.be.true
it "works when using field moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
sr = @battle.getMove("Stealth Rock")
(=> @battle.performMove(@p1, sr)).should.not.throw()
it "ignores Unaware", ->
shared.create.call this,
team1: [Factory("Haxorus", ability: name)]
team2: [Factory("Quagsire", ability: "Unaware")]
@p1.boost(attack: 2)
spy = @sandbox.spy(@p1, "editBoosts").withArgs(sinon.match(ignoreOffense: false))
@battle.performMove(@p1, @battle.getMove("Outrage"))
spy.returned(sinon.match(attack: 2)).should.be.true
testAbilityCancelAbility("Mold Breaker")
testAbilityCancelAbility("Teravolt")
testAbilityCancelAbility("Turboblaze")
describe "Moody", ->
it "at turn end, randomly raise a stat by 2 and lower another by 1", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Moody")])
shared.biasRNG.call(this, "randInt", "moody raise", 1)
shared.biasRNG.call(this, "randInt", "moody lower", 0)
@battle.endTurn()
@p1.stages.should.containEql(attack: -1, defense: 2)
it "never raises and lowers the same stat in a single turn", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Moody")])
shared.biasRNG.call(this, "randInt", "moody raise", 0)
shared.biasRNG.call(this, "randInt", "moody lower", 0)
@battle.endTurn()
@p1.stages.should.containEql(attack: 2, defense: -1)
it "does not choose individual stats at max/min", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Moody")])
shared.biasRNG.call(this, "randInt", "moody raise", 1)
shared.biasRNG.call(this, "randInt", "moody lower", 0)
@p1.boost(defense: 6)
@p1.boost(attack: -6)
@battle.endTurn()
@p1.stages.should.containEql(defense: 5, speed: 2)
it "does not try to raise a null stat", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Moody")])
shared.biasRNG.call(this, "randInt", "moody raise", 1)
shared.biasRNG.call(this, "randInt", "moody lower", 0)
@p1.boost(attack: 6, defense: 6, speed: 6, specialAttack: 6)
@p1.boost(specialDefense: 6, accuracy: 6, evasion: 6)
(=> @battle.endTurn()).should.not.throw()
testTypeImmuneAbility = (name, type, stat) ->
describe name, ->
it "makes the user immune to #{type} moves", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name)])
typedMove = _(@battle.MoveList).find (m) ->
!m.isNonDamaging() && m.type == type
mock = @sandbox.mock(typedMove).expects('hit').never()
@battle.performMove(@p2, typedMove)
mock.verify()
it "increases #{stat} by 1 if hit by a #{type}-type move", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name)])
typedMove = _(@battle.MoveList).find (m) ->
!m.isNonDamaging() && m.type == type
@battle.performMove(@p2, typedMove)
@p1.stages[stat].should.equal(1)
it "increases #{stat} by only 1 even if move is multi-hit", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name)])
typedMove = _(@battle.MoveList).find (m) ->
!m.isNonDamaging() && m.type == type
@sandbox.stub(typedMove, 'calculateNumberOfHits', -> 2)
@battle.performMove(@p2, typedMove)
@p1.stages[stat].should.equal(1)
it "does not increase #{stat} by 1 if the user is the target", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name)])
typedMove = _(@battle.MoveList).find (m) ->
m.target == 'user' && m.type == type
@battle.performMove(@p1, typedMove)
@p1.stages[stat].should.equal(0)
it "does nothing otherwise", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: name)])
typedMove = _(@battle.MoveList).find (m) ->
!m.isNonDamaging() && m.type != type
mock = @sandbox.mock(typedMove).expects('hit').once()
@battle.performMove(@p2, typedMove)
@p1.stages[stat].should.equal(0)
mock.verify()
testTypeImmuneAbility("Motor Drive", "Electric", "speed")
testTypeImmuneAbility("Sap Sipper", "Grass", "attack")
describe "Moxie", ->
it "increases attack every time it faints another target", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Moxie")]
tackle = @battle.getMove("Tackle")
@p2.currentHP = 1
@p1.stages.should.containEql(attack: 0)
@battle.performMove(@p1, tackle)
@p1.stages.should.containEql(attack: 1)
describe "Multiscale", ->
it "takes half damage at full HP", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Multiscale")]
iceBeam = @battle.getMove("Ice Beam")
iceBeam.modifyDamage(@battle, @p2, @p1).should.equal(0x800)
it "takes normal damage at other HP", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Multiscale")]
iceBeam = @battle.getMove("Ice Beam")
@p1.currentHP -= 1
iceBeam.modifyDamage(@battle, @p2, @p1).should.equal(0x1000)
describe "Multitype", ->
it "changes Arceus forme for different plates"
describe "Mummy", ->
it "changes the attacker's ability to Mummy on contact", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Mummy")]
tackle = @battle.getMove("Tackle")
@p2.hasAbility("Mummy").should.be.false
@battle.performMove(@p2, tackle)
@p2.hasAbility("Mummy").should.be.true
it "doesn't change ability if move used isn't a contact move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Mummy")]
iceBeam = @battle.getMove("Ice Beam")
@p2.hasAbility("Mummy").should.be.false
@battle.performMove(@p2, iceBeam)
@p2.hasAbility("Mummy").should.be.false
it "doesn't change ability if attacker has Multitype", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Mummy")]
team2: [Factory("Magikarp", ability: "Multitype")]
tackle = @battle.getMove("Tackle")
@p2.hasAbility("Mummy").should.be.false
@battle.performMove(@p2, tackle)
@p2.hasAbility("Mummy").should.be.false
it "works even if the defender faints", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Mummy"), Factory("Magikarp")]
@p1.currentHP = 1
tackle = @battle.getMove("Tackle")
@p2.hasAbility("Mummy").should.be.false
@battle.performMove(@p2, tackle)
@p2.hasAbility("Mummy").should.be.true
describe "Natural Cure", ->
it "cures status upon switch out", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Natural Cure")]
@p1.attach(Status.Burn)
@p1.switchOut(@battle)
@p1.hasStatus().should.be.false
describe "No Guard", ->
it "makes every move by this Pokemon never miss", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "No Guard")]
shared.biasRNG.call(this, 'randInt', 'miss', 101)
focusBlast = @battle.getMove("Focus Blast")
focusBlast.willMiss(@battle, @p1, @p2).should.be.false
it "makes every move against this Pokemon never miss", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "No Guard")]
shared.biasRNG.call(this, 'randInt', 'miss', 101)
focusBlast = @battle.getMove("Focus Blast")
focusBlast.willMiss(@battle, @p2, @p1).should.be.false
describe "Normalize", ->
it "makes every move act as if it were Normal type", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Normalize")]
team2: [Factory("Magikarp")]
thunderbolt = @battle.getMove("Thunderbolt")
thunderbolt.typeEffectiveness(@battle, @p1, @p2).should.equal(1)
it "has no effect if target is self", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Normalize")]
team2: [Factory("Magikarp")]
thunderbolt = @battle.getMove("Thunderbolt")
thunderbolt.typeEffectiveness(@battle, @p1, @p1).should.equal(2)
it "has no effect on struggle", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Normalize")]
team2: [Factory("Skarmory")]
struggle = @battle.getMove("Struggle")
struggle.typeEffectiveness(@battle, @p1, @p2).should.equal(1)
describe "Overcoat", ->
it "gives an immunity to adverse weather effects", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Overcoat")]
@p1.isWeatherDamageImmune(Weather.HAIL).should.be.true
@p1.isWeatherDamageImmune(Weather.SAND).should.be.true
describe "Pickpocket", ->
it "steals the item of an opponent that makes contact", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Pickpocket")]
team2: [Factory("Magikarp", item: "Leftovers")]
tackle = @battle.getMove("Tackle")
@battle.performMove(@p2, tackle)
@p1.hasItem("Leftovers").should.be.true
@p2.hasItem("Leftovers").should.be.false
describe "Plus", ->
it "multiplies special attack by 1.5 if on the field with Minus", ->
shared.create.call this,
numActive: 2
team1: [Factory("Magikarp"), Factory("Magikarp")]
team2: [Factory("Magikarp"), Factory("Magikarp")]
specialAttack = @p1.stat('specialAttack')
@team1.at(0).copyAbility(Ability.Minus)
@team1.at(1).copyAbility(Ability.Plus)
thunderbolt = @battle.getMove("Thunderbolt")
thunderbolt.modifyAttack(@battle, @team1.at(1), @p2).should.equal(0x1800)
describe "Poison Heal", ->
it "prevents normal poison damage", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Poison Heal")]
@p1.attach(Status.Poison)
@battle.endTurn()
@p1.currentHP.should.not.be.lessThan @p1.stat('hp')
@p1.cureStatus()
@p1.attach(Status.Toxic)
@battle.endTurn()
@p1.currentHP.should.not.be.lessThan @p1.stat('hp')
it "heals 1/8 HP end of turn while poisoned", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Poison Heal")]
@p1.attach(Status.Poison)
@p1.currentHP = 1
@battle.endTurn()
@p1.currentHP.should.equal(1 + (@p1.stat('hp') >> 3))
it "heals 1/8 HP end of turn while toxiced", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Poison Heal")]
@p1.attach(Status.Toxic)
@p1.currentHP = 1
@battle.endTurn()
@p1.currentHP.should.equal(1 + (@p1.stat('hp') >> 3))
describe "Prankster", ->
it "makes non-damaging moves have a priority (priority + 1)", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Prankster")]
spore = @battle.getMove("Spore")
tackle = @battle.getMove("Tackle")
action = {type: "move", move: spore, pokemon: @p1}
@battle.actionPriority(action).should.equal(spore.priority + 1)
action.move = tackle
@battle.actionPriority(action).should.equal(tackle.priority)
describe "Pressure", ->
it "reduces a move's PP further by 1 if targetted by foe's move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Pressure")]
tackle = @battle.getMove("Tackle")
@p2.moves = [ tackle ]
@p2.resetAllPP()
pp = @p2.pp(tackle)
@battle.performMove(@p2, tackle)
@p2.pp(tackle).should.equal(pp - 2)
it "does not reduce a move's PP by 1 if target is self", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Pressure")]
dd = @battle.getMove("Dragon Dance")
@p1.moves = [ dd ]
@p1.resetAllPP()
pp = @p1.pp(dd)
@battle.performMove(@p1, dd)
@p1.pp(dd).should.equal(pp - 1)
describe "Quick Feet", ->
it "increases speed by x1.5 when statused", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Quick Feet")]
speed = @p1.stat('speed')
@p1.attach(Status.Sleep)
@p1.stat('speed').should.equal Math.floor(1.5 * speed)
it "negates speed drop from paralysis", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Quick Feet")]
speed = @p1.stat('speed')
@p1.attach(Status.Paralyze)
@p1.stat('speed').should.equal Math.floor(1.5 * speed)
describe "Rain Dish", ->
it "restores 1/16 HP at end of turn under Rain", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Rain Dish")]
@p1.currentHP = 1
@battle.setWeather(Weather.RAIN)
@battle.endTurn()
@p1.currentHP.should.equal(1 + (@p1.stat('hp') >> 4))
it "does not restore HP at end of turn under non-Rain weather", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Rain Dish")]
@p1.currentHP = 1
@battle.endTurn()
@p1.currentHP.should.equal(1)
describe "Rattled", ->
for type in ["Ghost", "Bug", "Dark"]
do (type) ->
it "raises speed by 1 when hit by a #{type} move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Rattled")]
typedMove = _(@battle.MoveList).find (m) ->
m.type == type
@p1.stages.speed.should.equal(0)
@battle.performMove(@p2, typedMove)
@p1.stages.speed.should.equal(1)
it "does not raise speed by 1 when hit by a non-damaging #{type} move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Rattled")]
typedMove = _(@battle.MoveList).find (m) ->
m.isNonDamaging() && m.type == type
@p1.stages.speed.should.equal(0)
@battle.performMove(@p2, typedMove)
@p1.stages.speed.should.equal(0)
it "does not raise speed by 1 when a #{type} move hits a substitute", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Rattled")]
typedMove = _(@battle.MoveList).find (m) ->
m.isNonDamaging() && m.type == type
@p1.attach(Attachment.Substitute, hp: 1)
@p1.stages.speed.should.equal(0)
@battle.performMove(@p2, typedMove)
@p1.stages.speed.should.equal(0)
describe "Reckless", ->
it "gives a 1.2x boost to recoil moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Reckless")]
recoilMoves = @battle.MoveList.filter (m) ->
m.recoil < 0
for move in recoilMoves
move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1333)
it "boosts the kick moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Reckless")]
recoilMoves = [ @battle.getMove("Jump Kick"),
@battle.getMove("Hi Jump Kick") ]
for move in recoilMoves
move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1333)
it "does not boost struggle", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Reckless")]
struggle = @battle.getMove("Struggle")
struggle.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
it "does not boost drain moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Reckless")]
absorb = @battle.getMove("Absorb")
absorb.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
describe "Regenerator", ->
it "restores 1/3 of the user's HP upon switch out", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Regenerator")]
@p1.currentHP = 1
@p1.switchOut(@battle)
hp = Math.floor(@p1.stat('hp') / 3)
@p1.currentHP.should.equal(1 + hp)
describe "Rivalry", ->
it "reduces base power by 25% if user and target are opposite genders", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: 'F', ability: "Rivalry")]
team2: [Factory("Magikarp", gender: 'M')]
tackle = @battle.getMove("Tackle")
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0xC00)
it "increases base power by 25% if user and target are the same gender", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: 'F', ability: "Rivalry")]
team2: [Factory("Magikarp", gender: 'F')]
tackle = @battle.getMove("Tackle")
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x1400)
it "has normal base power if either user or target have no gender", ->
shared.create.call this,
team1: [Factory("Magikarp", gender: 'F', ability: "Rivalry")]
team2: [Factory("Magikarp")]
tackle = @battle.getMove("Tackle")
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
describe "Rock Head", ->
it "negates recoil", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Rock Head")]
@battle.performMove(@p1, @battle.getMove("Head Smash"))
@p1.currentHP.should.equal @p1.stat('hp')
describe "Sand Force", ->
it "increases BP of Ground-, Rock-, and Steel-type moves by 30% in sand", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sand Force")]
@battle.setWeather(Weather.SAND)
tackle = @battle.getMove("Tackle")
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
earthquake = @battle.getMove("Earthquake")
earthquake.modifyBasePower(@battle, @p1, @p2).should.equal(0x14CD)
rockSlide = @battle.getMove("Rock Slide")
rockSlide.modifyBasePower(@battle, @p1, @p2).should.equal(0x14CD)
meteorMash = @battle.getMove("Meteor Mash")
meteorMash.modifyBasePower(@battle, @p1, @p2).should.equal(0x14CD)
it "does not increase BP if weather is not sand", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sand Force")]
@battle.setWeather(Weather.NONE)
tackle = @battle.getMove("Tackle")
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
earthquake = @battle.getMove("Earthquake")
earthquake.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
rockSlide = @battle.getMove("Rock Slide")
rockSlide.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
meteorMash = @battle.getMove("Meteor Mash")
meteorMash.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
it "grants immunity to sandstorm", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sand Force")]
@p1.isWeatherDamageImmune(Weather.SAND).should.be.true
describe "Sand Veil", ->
it "increases evasion by 25% in Sand", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sand Veil")]
@battle.setWeather(Weather.SAND)
tackle = @battle.getMove("Tackle")
tackle.chanceToHit(@battle, @p2, @p1).should.equal(80)
it "does nothing outside of Sand", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sand Veil")]
tackle = @battle.getMove("Tackle")
tackle.chanceToHit(@battle, @p2, @p1).should.equal(100)
it "makes the user immune to Sand", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sand Veil")]
@p1.isWeatherDamageImmune(Weather.SAND).should.be.true
describe "Scrappy", ->
it "negates pokemon immunities", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Scrappy")]
team2: [Factory("Gengar")]
tackle = @battle.getMove("Tackle")
closeCombat = @battle.getMove("Close Combat")
tackle.typeEffectiveness(@battle, @p1, @p2).should.equal(1)
closeCombat.typeEffectiveness(@battle, @p1, @p2).should.equal(.5)
mock1 = @sandbox.mock(tackle).expects('hit').once()
mock2 = @sandbox.mock(closeCombat).expects('hit').once()
@battle.performMove(@p1, tackle)
@battle.performMove(@p1, closeCombat)
mock1.verify()
mock2.verify()
describe "Serene Grace", ->
it "doubles the chance of secondary effects happening", ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", "secondary effect", 10)
ember = @battle.getMove("Ember")
@sandbox.stub(ember, "baseDamage", -> 1)
@battle.performMove(@p1, ember)
@p2.has(Status.Burn).should.be.false
@p1.copyAbility(Ability.SereneGrace)
@battle.performMove(@p1, ember)
@p2.has(Status.Burn).should.be.true
it "doubles the chance of secondary boosts happening", ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", "secondary boost", 10)
acid = @battle.getMove("Acid")
@sandbox.stub(acid, "baseDamage", -> 1)
@battle.performMove(@p1, acid)
@p2.stages.should.containEql(specialDefense: 0)
@p1.copyAbility(Ability.SereneGrace)
@battle.performMove(@p1, acid)
@p2.stages.should.containEql(specialDefense: -1)
it "doubles the chance of flinches happening", ->
shared.create.call(this)
shared.biasRNG.call(this, "randInt", "flinch", 30)
ironHead = @battle.getMove("Iron Head")
@sandbox.stub(ironHead, "baseDamage", -> 1)
@battle.performMove(@p1, ironHead)
@p2.has(Attachment.Flinch).should.be.false
@p1.copyAbility(Ability.SereneGrace)
@battle.performMove(@p1, ironHead)
@p2.has(Attachment.Flinch).should.be.true
describe "Shadow Tag", ->
it "prevents foes from switching", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Shadow Tag")]
@p2.isSwitchBlocked().should.be.true
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.true
it "does not affect foes with Shadow Tag", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Shadow Tag")]
team2: [Factory("Magikarp", ability: "Shadow Tag")]
@p2.isSwitchBlocked().should.be.false
@battle.beginTurn()
@p2.isSwitchBlocked().should.be.false
describe "Shed Skin", ->
it "has a 30% chance of removing its status effect at end of turn", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Shed Skin")]
shared.biasRNG.call(this, "randInt", "shed skin", 3)
@p1.attach(Status.Burn)
@battle.endTurn()
@p1.has(Status.Burn).should.be.false
it "has a 70% chance of doing nothing at end of turn", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Shed Skin")]
shared.biasRNG.call(this, "randInt", "shed skin", 4)
@p1.attach(Status.Burn)
@battle.endTurn()
@p1.has(Status.Burn).should.be.true
describe "Sheer Force", ->
it "raises power of moves with secondary effects by 30%", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sheer Force")]
ember = @battle.getMove("Ember")
ember.modifyBasePower(@battle, @p1, @p2).should.equal(0x14CD)
it "does not raise power of moves without secondary effects", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sheer Force")]
tackle = @battle.getMove("Tackle")
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
it "nullifies secondary effects", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sheer Force", item: "Life Orb")]
shared.biasRNG.call(this, 'next', 'secondary effect', 0) # always happens
@battle.performMove(@p1, @battle.getMove("Zap Cannon"))
@p2.has(Status.Paralyze).should.be.false
it "nullifies flinch", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sheer Force", item: "Life Orb")]
shared.biasRNG.call(this, 'next', 'flinch', 0) # always happens
@battle.performMove(@p1, @battle.getMove("Iron Head"))
@p2.has(Attachment.Flinch).should.be.false
it "nullifies secondary boosts", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sheer Force", item: "Life Orb")]
@battle.performMove(@p1, @battle.getMove("Charge Beam"))
@p1.stages.should.containEql(specialAttack: 0)
it "does not apply to primary boosts", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sheer Force", item: "Life Orb")]
cc = @battle.getMove("Close Combat")
cc.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
@battle.performMove(@p1, cc)
@p1.stages.should.containEql(defense: -1, specialDefense: -1)
it "receives no life orb recoil", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sheer Force", item: "Life Orb")]
@battle.performMove(@p1, @battle.getMove("Ember"))
@p1.currentHP.should.not.be.lessThan @p1.stat('hp')
it "receives life orb recoil if no secondary effect", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sheer Force", item: "Life Orb")]
@battle.performMove(@p1, @battle.getMove("Tackle"))
@p1.currentHP.should.be.lessThan @p1.stat('hp')
it "does not activate Red Card", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sheer Force"), Factory("Magikarp")]
team2: [Factory("Magikarp", item: "Red Card"), Factory("Magikarp")]
@battle.performMove(@p1, @battle.getMove("Ember"))
@p1.should.equal @team1.first()
it "activates Red Card if no secondary effect", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sheer Force"), Factory("Magikarp")]
team2: [Factory("Magikarp", item: "Red Card"), Factory("Magikarp")]
@battle.performMove(@p1, @battle.getMove("Tackle"))
@p1.should.not.equal @team1.first()
it "does not activate Eject Button", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sheer Force"), Factory("Magikarp")]
team2: [Factory("Magikarp", item: "Eject Button"), Factory("Magikarp")]
@battle.recordMove(@id2, @battle.getMove("Tackle"))
should.not.exist @battle.requests[@id2]
@battle.performMove(@p1, @battle.getMove("Ember"))
should.not.exist @battle.requests[@id2]
it "activates Eject Button if no secondary effect", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sheer Force"), Factory("Magikarp")]
team2: [Factory("Magikarp", item: "Eject Button"), Factory("Magikarp")]
@battle.recordMove(@id2, @battle.getMove("Tackle"))
should.not.exist @battle.requests[@id2]
@battle.performMove(@p1, @battle.getMove("Tackle"))
@battle.requests[@id2].should.not.be.empty
it "does not activate Jaboca Berry?"
it "does not activate Rowap Berry?"
describe "Shield Dust", ->
it "prevents secondary effects", ->
shared.create.call this,
team2: [Factory("Magikarp", ability: "Shield Dust")]
shared.biasRNG.call(this, 'next', 'secondary effect', 0) # always happens
@battle.performMove(@p1, @battle.getMove("Zap Cannon"))
@p2.has(Status.Paralyze).should.be.false
it "prevents secondary flinches", ->
shared.create.call this,
team2: [Factory("Magikarp", ability: "Shield Dust")]
shared.biasRNG.call(this, 'randInt', 'flinch', 0) # 100% chance
@battle.performMove(@p1, @battle.getMove("Iron Head"))
@p2.has(Attachment.Flinch).should.be.false
it "prevents secondary boosts against the target", ->
shared.create.call this,
team2: [Factory("Magikarp", ability: "Shield Dust")]
@battle.performMove(@p1, @battle.getMove("Acid Spray"))
@p2.stages.should.containEql(specialDefense: 0)
it "does not prevent secondary boosts for the user", ->
shared.create.call this,
team2: [Factory("Magikarp", ability: "Shield Dust")]
@battle.performMove(@p1, @battle.getMove("Flame Charge"))
@p1.stages.should.containEql(speed: 1)
describe "Simple", ->
it "doubles stat boosts, negative and positive", ->
shared.create.call(this, team1: [Factory("Magikarp", ability: "Simple")])
@battle.performMove(@p1, @battle.getMove("Curse"))
@p1.stages.should.containEql(attack: 2, defense: 2, speed: -2)
describe "Slow Start", ->
it "halves attack and speed", ->
shared.create.call(this)
speed = @p1.stat('speed')
@p1.copyAbility(Ability.SlowStart)
tackle = @battle.getMove("Tackle")
tackle.modifyAttack(@battle, @p1, @p2).should.equal(0x800)
flamethrower = @battle.getMove("Flamethrower")
flamethrower.modifyAttack(@battle, @p1, @p2).should.equal(0x1000)
@p1.stat('speed').should.equal(speed >> 1)
it "returns attack and speed to normal after five turns", ->
shared.create.call(this)
tackle = @battle.getMove("Tackle")
flamethrower = @battle.getMove("Flamethrower")
speed = @p1.stat('speed')
@p1.copyAbility(Ability.SlowStart)
for x in [0...5]
tackle.modifyAttack(@battle, @p1, @p2).should.equal(0x800)
flamethrower.modifyAttack(@battle, @p1, @p2).should.equal(0x1000)
@p1.stat('speed').should.equal(speed >> 1)
@battle.endTurn()
tackle.modifyAttack(@battle, @p1, @p2).should.equal(0x1000)
flamethrower.modifyAttack(@battle, @p1, @p2).should.equal(0x1000)
@p1.stat('speed').should.equal(speed)
describe "Sniper", ->
it "multiplies damage by a further x1.5 if critting", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sniper")]
tackle = @battle.getMove("Tackle")
@p1.crit = true
tackle.modifyDamage(@battle, @p1, @p2).should.equal(0x1800)
it "has a normal 1x multiplier if not critting", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sniper")]
tackle = @battle.getMove("Tackle")
@p1.crit = false
tackle.modifyDamage(@battle, @p1, @p2).should.equal(0x1000)
describe "Snow Cloak", ->
it "increases evasion by 25% in hail", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Snow Cloak")]
@battle.setWeather(Weather.HAIL)
tackle = @battle.getMove("Tackle")
tackle.chanceToHit(@battle, @p2, @p1).should.equal(80)
it "does nothing outside of hail", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Snow Cloak")]
tackle = @battle.getMove("Tackle")
tackle.chanceToHit(@battle, @p2, @p1).should.equal(100)
it "makes the user immune to hail", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Snow Cloak")]
@p1.isWeatherDamageImmune(Weather.HAIL).should.be.true
describe "Solar Power", ->
it "increases special attack by 50% in Sun", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Solar Power")]
@battle.setWeather(Weather.SUN)
tackle = @battle.getMove("Tackle")
fireBlast = @battle.getMove("Fire Blast")
tackle.modifyAttack(@battle, @p1, @p2).should.equal(0x1000)
fireBlast.modifyAttack(@battle, @p1, @p2).should.equal(0x1800)
it "owner loses 1/8 max HP per end of turn", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Solar Power")]
@battle.setWeather(Weather.SUN)
@battle.endTurn()
@p1.currentHP.should.equal(@p1.stat('hp') - (@p1.stat('hp') >> 3))
it "does nothing if the weather is not Sun", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Solar Power")]
fireBlast = @battle.getMove("Fire Blast")
fireBlast.modifyAttack(@battle, @p1, @p2).should.equal(0x1000)
@battle.endTurn()
@p1.currentHP.should.equal(@p1.stat('hp'))
it "deals a minimum of 1 damage", ->
shared.create.call this,
team1: [Factory("Shedinja", ability: "Solar Power")]
@battle.setWeather(Weather.SUN)
@battle.endTurn()
@p1.currentHP.should.equal(0)
describe "Soundproof", ->
it "makes user immune to sound moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Soundproof")]
bugBuzz = @battle.getMove('Bug Buzz')
perishSong = @battle.getMove('Perish Song')
@p1.isImmune(bugBuzz.type, move: bugBuzz).should.be.true
@p1.isImmune(perishSong.type, move: perishSong).should.be.true
describe "Speed Boost", ->
it "boosts speed at the end of every turn", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Speed Boost")]
@p1.stages.should.containEql(speed: 0)
@battle.endTurn()
@p1.stages.should.containEql(speed: 1)
@battle.endTurn()
@p1.stages.should.containEql(speed: 2)
it "boosts speed the turn after a pokemon is freshly switched in", ->
shared.create.call this,
team1: [Factory("Magikarp"), Factory("Magikarp", ability: "Speed Boost")]
@battle.performSwitch(@p1, 1)
@team1.first().stages.should.containEql(speed: 0)
@battle.endTurn()
@team1.first().stages.should.containEql(speed: 0)
@battle.endTurn()
@team1.first().stages.should.containEql(speed: 1)
@battle.endTurn()
@team1.first().stages.should.containEql(speed: 2)
it "boosts speed the turn after a pokemon is replaced", ->
shared.create.call this,
team1: [Factory("Magikarp"), Factory("Magikarp", ability: "Speed Boost")]
@p1.currentHP = 1
@controller.makeMove(@id1, "Tackle")
@controller.makeMove(@id2, "Tackle")
@controller.makeSwitch(@id1, 1)
@battle.turn.should.equal(2)
@team1.first().stages.should.containEql(speed: 0)
@battle.endTurn()
@team1.first().stages.should.containEql(speed: 1)
@battle.endTurn()
@team1.first().stages.should.containEql(speed: 2)
describe "Sticky Hold", ->
it "prevents items from being taken", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Leftovers", ability: "Sticky Hold")]
@battle.performMove(@p2, @battle.getMove("Trick"))
@p1.hasItem("Leftovers").should.be.true
@p2.hasItem().should.be.false
it "does not prevent the user from using Trick", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Leftovers", ability: "Sticky Hold")]
@battle.performMove(@p1, @battle.getMove("Trick"))
@p1.hasItem().should.be.false
@p2.hasItem("Leftovers").should.be.true
describe "Stall", ->
it "forces the user to move last no matter what", ->
shared.create.call this,
team1: [Factory("Magikarp", evs: {speed: 4}, ability: "Stall")]
team2: [Factory("Magikarp")]
splash = @battle.getMove("Splash")
@battle.recordMove(@id1, splash)
@battle.recordMove(@id2, splash)
@battle.determineTurnOrder()
pokemon = @battle.pokemonActions.map((o) -> o.pokemon)
pokemon.should.eql [ @p2, @p1 ]
it "moves before pokemon with lagging tail/full incense", ->
shared.create.call this,
team1: [Factory("Magikarp", evs: {speed: 4}, ability: "Stall")]
team2: [Factory("Magikarp", item: "Lagging Tail")]
splash = @battle.getMove("Splash")
@battle.recordMove(@id1, splash)
@battle.recordMove(@id2, splash)
@battle.determineTurnOrder()
pokemon = @battle.pokemonActions.map((o) -> o.pokemon)
pokemon.should.eql [ @p1, @p2 ]
describe "Steadfast", ->
it "raises speed if the Pokemon flinches", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Steadfast")]
tackle = @battle.getMove("Tackle")
@p1.attach(Attachment.Flinch)
@p1.beforeMove(tackle, @p1, [ @p2 ])
@p1.stages.should.containEql(speed: 1)
describe "Sturdy", ->
it "prevents the user from being OHKOed at full HP", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sturdy")]
ember = @battle.getMove('Ember')
stub = @sandbox.stub(ember, 'calculateDamage', -> 9999)
@battle.performMove(@p2, ember)
@p1.currentHP.should.equal(1)
it "lets the user be KOed if not at full HP", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Sturdy")]
@p1.currentHP -= 1
ember = @battle.getMove('Ember')
stub = @sandbox.stub(ember, 'calculateDamage', -> 9999)
@battle.performMove(@p2, ember)
@p1.currentHP.should.equal(0)
describe "Suction Cups", ->
it "prevents user from being phased", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Suction Cups"), Factory("Magikarp")]
whirlwind = @battle.getMove('Whirlwind')
mock = @sandbox.mock(@team1).expects('switch').never()
@battle.performMove(@p2, whirlwind)
mock.verify()
describe "Super Luck", ->
it "adds +2 to critical hit level", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Super Luck")]
tackle = @battle.getMove("Tackle")
tackle.criticalHitLevel(@battle, @p1, @p2).should.equal(2)
describe "Synchronize", ->
it "afflicts the source of a status with the same status", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Synchronize")]
@p1.attach(Status.Paralyze, source: @p2)
@p2.has(Status.Paralyze).should.be.true
it "doesn't attempt to afflict target if target is self", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Synchronize")]
spy = @sandbox.spy(Status.Burn, 'preattach')
@p1.attach(Status.Burn, source: @p1)
spy.returned(false).should.be.true
it "activates on primary status moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Synchronize")]
@battle.performMove(@p2, @battle.getMove("Will-O-Wisp"))
@p2.has(Status.Burn).should.be.true
it "doesn't activate on Sleep", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Synchronize")]
@p1.attach(Status.Sleep, source: @p2)
@p2.has(Status.Sleep).should.be.false
it "doesn't activate on Freeze", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Synchronize")]
@p1.attach(Status.Freeze, source: @p2)
@p2.has(Status.Freeze).should.be.false
it "activates on secondary effects", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Synchronize")]
shared.biasRNG.call(this, "next", 'secondary effect', 0) # 100% chance
@battle.performMove(@p2, @battle.getMove("Flamethrower"))
@p2.has(Status.Burn).should.be.true
describe "Tangled Feet", ->
it "doubles evasion rate when confused", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Tangled Feet")]
tackle = @battle.getMove("Tackle")
tackle.chanceToHit(@battle, @p2, @p1).should.equal(100)
@p1.attach(Attachment.Confusion)
tackle.chanceToHit(@battle, @p2, @p1).should.equal(50)
@p1.unattach(Attachment.Confusion)
tackle.chanceToHit(@battle, @p2, @p1).should.equal(100)
describe "Technician", ->
it "increases base power of moves with BP <= 60 by x1.5", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Technician")]
tackle = @battle.getMove("Tackle")
tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x1800)
it "leaves moves with BP > 60 alone", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Technician")]
iceBeam = @battle.getMove("Ice Beam")
iceBeam.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)
describe "Telepathy", ->
it "cancels damaging attacks from an ally", ->
shared.create.call this,
numActive: 2
team1: [Factory("Magikarp"), Factory("Magikarp", ability: "Telepathy")]
team2: (Factory("Magikarp") for x in [1..2])
earthquake = @battle.getMove("Earthquake")
mock = @sandbox.mock(earthquake).expects('hit').exactly(2)
@battle.performMove(@p1, earthquake)
@team1.at(1).currentHP.should.equal(@team1.at(1).stat('hp'))
mock.verify()
describe "Thick Fat", ->
it "halves the base power of Fire and Ice moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Thick Fat")]
iceBeam = @battle.getMove("Ice Beam")
iceBeam.modifyAttack(@battle, @p2, @p1).should.equal(0x800)
flamethrower = @battle.getMove("Flamethrower")
flamethrower.modifyAttack(@battle, @p2, @p1).should.equal(0x800)
it "doesn't change base power of other moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Thick Fat")]
tackle = @battle.getMove("Tackle")
tackle.modifyAttack(@battle, @p2, @p1).should.equal(0x1000)
describe "Tinted Lens", ->
it "doubles damage when using a not-very-effective move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Tinted Lens")]
team2: [Factory("Magikarp")]
surf = @battle.getMove("Surf")
surf.modifyDamage(@battle, @p1, @p2).should.equal(0x2000)
it "doesn't double damage otherwise", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Tinted Lens")]
team2: [Factory("Magikarp")]
tackle = @battle.getMove("Tackle")
tackle.modifyDamage(@battle, @p1, @p2).should.equal(0x1000)
describe "Trace", ->
it "copies a random foe's ability on switch-in", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Trace")]
team2: [Factory("Magikarp", ability: "Truant")]
@p1.hasAbility("Truant").should.be.true
it "does not copy certain abilities", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Trace")]
team2: [Factory("Magikarp", ability: "Forecast")]
@p1.hasAbility("Forecast").should.be.false
it "does not crash if there are no opponents", ->
shared.create.call this,
team1: [Factory("Magikarp")]
team2: [Factory("Magikarp"), Factory("Magikarp")]
@p2.faint()
(=>
@p1.copyAbility(Ability.Trace)
).should.not.throw()
it "traces another ability upon switching out and back in", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Trace"), Factory("Magikarp")]
team2: [Factory("Magikarp", ability: "Intimidate")
Factory("Magikarp", ability: "Magic Bounce")]
@p1.hasAbility("Intimidate").should.be.true
@battle.performSwitch(@p2, 1)
@battle.performSwitch(@p1, 1)
@battle.performSwitch(@team1.first(), 1)
@p1.hasAbility("Magic Bounce").should.be.true
describe "Truant", ->
it "prevents the user from attacking every other turn", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Truant")]
splash = @battle.getMove("Splash")
mock = @sandbox.mock(splash).expects('execute').once()
@battle.performMove(@p1, splash)
mock.verify()
splash.execute.restore()
mock = @sandbox.mock(splash).expects('execute').never()
@battle.performMove(@p1, splash)
mock.verify()
splash.execute.restore()
mock = @sandbox.mock(splash).expects('execute').once()
@battle.performMove(@p1, splash)
mock.verify()
splash.execute.restore()
describe "Unaware", ->
it "ignores attackers' attack and special attack boosts", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Unaware")]
@p2.boost(attack: 1, specialAttack: 2)
spy = @sandbox.spy(@p2, "editBoosts")
@battle.performMove(@p2, @battle.getMove("Tackle"))
spy.returned(sinon.match(attack: 0, specialAttack: 0)).should.be.true
it "ignores defenders' defense and special defense boosts", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Unaware")]
@p2.boost(defense: 1, specialDefense: 2)
spy = @sandbox.spy(@p2, "editBoosts")
@battle.performMove(@p1, @battle.getMove("Tackle"))
spy.returned(sinon.match(defense: 0, specialDefense: 0)).should.be.true
it "ignores attackers' accuracy boosts", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Unaware")]
@p2.boost(accuracy: 3)
spy = @sandbox.spy(@p2, "editBoosts")
@battle.getMove("Tackle").chanceToHit(@battle, @p2, @p1)
spy.returned(sinon.match(accuracy: 0)).should.be.true
it "ignores defenders' evasion boosts", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Unaware")]
@p2.boost(evasion: 3)
spy = @sandbox.spy(@p2, "editBoosts")
@battle.getMove("Tackle").chanceToHit(@battle, @p1, @p2)
spy.returned(sinon.match(evasion: 0)).should.be.true
describe "Unburden", ->
it "doubles its speed when the owner's item is removed", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Flying Gem", ability: "Unburden")]
speed = @p1.stat('speed')
@p1.removeItem()
@p1.stat('speed').should.equal(2 * speed)
it "activates when its item is swapped for no item", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Flying Gem", ability: "Unburden")]
speed = @p1.stat('speed')
trick = @battle.getMove("Trick")
@battle.performMove(@p2, trick)
@p1.stat('speed').should.equal(2 * speed)
it "does not activate when its item is swapped for another item", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Flying Gem", ability: "Unburden")]
team2: [Factory("Magikarp", item: "Water Gem")]
speed = @p1.stat('speed')
trick = @battle.getMove("Trick")
@battle.performMove(@p2, trick)
@p1.stat('speed').should.equal(speed)
@p1.has(Attachment.Unburden).should.be.false
it "deactivates if it gains another item", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Flying Gem", ability: "Unburden")]
team2: [Factory("Magikarp", item: "Water Gem")]
speed = @p1.stat('speed')
@p1.removeItem()
@p1.stat('speed').should.equal(2 * speed)
trick = @battle.getMove("Trick")
@battle.performMove(@p2, trick)
@p1.stat('speed').should.equal(speed)
@p1.has(Attachment.Unburden).should.be.false
it "deactivates if it loses this Ability", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Flying Gem", ability: "Unburden")]
team2: [Factory("Magikarp", ability: "Run Away")]
speed = @p1.stat('speed')
@p1.removeItem()
@p1.stat('speed').should.equal(2 * speed)
rolePlay = @battle.getMove("Role Play")
@battle.performMove(@p1, rolePlay)
@p1.stat('speed').should.equal(speed)
@p1.has(Attachment.Unburden).should.be.false
describe "Unnerve", ->
it "prevents held berries from being eaten", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Unnerve")]
team2: [Factory("Magikarp", item: "Salac Berry")]
@p2.currentHP = 1
@p2.update()
@p2.stages.should.containEql(speed: 0)
@battle.beginTurn()
@p2.update()
@p2.stages.should.containEql(speed: 0)
it "does not prevent regular item usage", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Unnerve")]
team2: [Factory("Magikarp", item: "Leftovers")]
@p2.currentHP = 1
@battle.endTurn()
@p2.currentHP.should.be.greaterThan(1)
it "does not prevent Bug Bite, Pluck, etc. from working", ->
shared.create.call this,
team1: [Factory("Magikarp", item: "Salac Berry", ability: "Unnerve")]
@battle.performMove(@p2, @battle.getMove("Bug Bite"))
@p2.stages.should.containEql(speed: 1)
describe "Victory Star", ->
it "increases accuracy of moves by 10%", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Victory Star")]
willOWisp = @battle.getMove("Will-O-Wisp")
accuracy = willOWisp.chanceToHit(@battle, @p1, @p2)
accuracy.should.equal Math.floor(willOWisp.accuracy * 1.1)
it "increases accuracy of ally moves by 10%"
testTypeAbsorbMove = (name, type) ->
describe name, ->
it "is immune to #{type}-type moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
typedMove = _(@battle.MoveList).find (m) ->
m.type == type
mock = @sandbox.mock(typedMove).expects('hit').never()
@battle.performMove(@p2, typedMove)
mock.verify()
it "heals 25% HP from #{type}-type moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
typedMove = _(@battle.MoveList).find (m) ->
m.type == type
@p1.currentHP = 1
@battle.performMove(@p2, typedMove)
@p1.currentHP.should.equal(1 + (@p1.stat('hp') >> 2))
it "does not activate for the user's moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: name)]
typedMove = _(@battle.MoveList).find (m) ->
m.target == 'user' && m.type == type
@p1.currentHP = 1
@battle.performMove(@p1, typedMove)
@p1.currentHP.should.equal(1)
testTypeAbsorbMove("Water Absorb", "Water")
testTypeAbsorbMove("Volt Absorb", "Electric")
describe "Weak Armor", ->
it "lowers defense and raises speed on physical attacks", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Weak Armor")]
tackle = @battle.getMove("Tackle")
@battle.performMove(@p2, tackle)
@p1.stages.should.containEql(defense: -1, speed: 1)
it "does nothing on any other kind of move", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Weak Armor")]
willOWisp = @battle.getMove("Will-O-Wisp")
@battle.performMove(@p2, willOWisp)
@p1.stages.should.containEql(defense: 0, speed: 0)
describe "Wonder Guard", ->
it "renders the Pokemon immune to non-super-effective moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Wonder Guard")]
tackle = @battle.getMove("Tackle")
mock = @sandbox.mock(tackle).expects('hit').never()
@battle.performMove(@p2, tackle)
mock.verify()
it "is not immune to struggle", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Wonder Guard")]
struggle = @battle.getMove("Struggle")
mock = @sandbox.mock(struggle).expects('hit').once()
@battle.performMove(@p2, struggle)
mock.verify()
it "is not immune to non-damaging moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Wonder Guard")]
willOWisp = @battle.getMove("Will-O-Wisp")
mock = @sandbox.mock(willOWisp).expects('hit').once()
@battle.performMove(@p2, willOWisp)
mock.verify()
it "is not immune to super-effective moves", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Wonder Guard")]
typedMove = _(@battle.MoveList).find (m) ->
!m.isNonDamaging() && m.type == 'Electric'
mock = @sandbox.mock(typedMove).expects('hit').once()
@battle.performMove(@p2, typedMove)
mock.verify()
describe "Wonder Skin", ->
it "makes non-damaging moves with over 50% accuracy have 50% accuracy", ->
shared.create.call this,
team2: [Factory("Magikarp", ability: "Wonder Skin")]
trick = @battle.getMove("Trick")
accuracy = trick.chanceToHit(@battle, @p1, @p2)
accuracy.should.equal 50
it "adjusts accuracy before any other modifiers take effect", ->
shared.create.call this,
team1: [Factory("Magikarp", ability: "Compoundeyes")]
team2: [Factory("Magikarp", ability: "Wonder Skin")]
willOWisp = @battle.getMove("Will-O-Wisp")
accuracy = willOWisp.chanceToHit(@battle, @p1, @p2)
accuracy.should.equal Math.floor(50 * 1.3)
it "does nothing to non-damaging moves that don't check accuracy", ->
shared.create.call this,
team2: [Factory("Magikarp", ability: "Wonder Skin")]
meanLook = @battle.getMove("Mean Look")
accuracy = meanLook.chanceToHit(@battle, @p1, @p2)
accuracy.should.equal 0
it "does nothing to non-damaging moves with under 50% accuracy", ->
shared.create.call this,
team2: [Factory("Magikarp", ability: "Wonder Skin")]
trick = @battle.getMove("Trick")
trick.accuracy = 40
accuracy = trick.chanceToHit(@battle, @p1, @p2)
accuracy.should.equal 40
describe "Zen Mode", ->
it "changes Darmanitan's forme when going under or above 50% HP"