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"
