BattleSim/test/xy/abilities.coffee

486 lines
19 KiB
CoffeeScript

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