BattleSim/server/bw/data/abilities.coffee

988 lines
30 KiB
CoffeeScript

{_} = require 'underscore'
{Attachment, Status, VolatileAttachment} = require '../attachment'
{Weather} = require '../../../shared/weather'
util = require '../util'
@Ability = Ability = {}
makeAbility = (name, func) ->
condensed = name.replace(/\s+/g, '')
class Ability[condensed] extends VolatileAttachment
@displayName: name
displayName: name
ability: true
func?.call(this)
# TODO: Implement.
makeAbility 'Pickup'
# Ability templates
makeWeatherPreventionAbility = (name) ->
makeAbility name, ->
@preventsWeather = true
this::switchIn = ->
@pokemon.activateAbility()
@battle.cannedText('WEATHER_DISABLED')
makeWeatherPreventionAbility("Air Lock")
makeWeatherPreventionAbility("Cloud Nine")
makeCriticalHitPreventionAbility = (name) ->
makeAbility name, ->
@preventsCriticalHits = true
makeCriticalHitPreventionAbility("Battle Armor")
makeCriticalHitPreventionAbility("Shell Armor")
makeBoostProtectionAbility = (name, protection) ->
makeAbility name, ->
this::transformBoosts = (boosts, source) ->
return boosts if source == @pokemon
didProtect = false
for stat of boosts
if (!protection || stat in protection) && boosts[stat] < 0
didProtect = true
boosts[stat] = 0
@pokemon.activateAbility() if didProtect
boosts
makeBoostProtectionAbility("Big Pecks", [ "defense" ])
makeBoostProtectionAbility("Clear Body")
makeBoostProtectionAbility("Hyper Cutter", [ "attack" ])
makeBoostProtectionAbility("Keen Eye", [ "accuracy" ])
makeBoostProtectionAbility("White Smoke")
makeWeatherSpeedAbility = (name, weather) ->
makeAbility name, ->
this::switchIn = ->
@doubleSpeed = @battle.hasWeather(weather)
this::informWeather = (newWeather) ->
@doubleSpeed = (weather == newWeather)
this::editSpeed = (speed) ->
if @doubleSpeed then 2 * speed else speed
this::isWeatherDamageImmune = (currentWeather) ->
return true if weather == currentWeather
makeWeatherSpeedAbility("Chlorophyll", Weather.SUN)
makeWeatherSpeedAbility("Swift Swim", Weather.RAIN)
makeWeatherSpeedAbility("Sand Rush", Weather.SAND)
makeLowHealthAbility = (name, type) ->
makeAbility name, ->
this::modifyBasePower = (move, target) ->
return 0x1000 if move.getType(@battle, @pokemon, target) != type
return 0x1000 if @pokemon.currentHP > Math.floor(@pokemon.stat('hp') / 3)
return 0x1800
makeLowHealthAbility("Blaze", "Fire")
makeLowHealthAbility("Torrent", "Water")
makeLowHealthAbility("Overgrow", "Grass")
makeLowHealthAbility("Swarm", "Bug")
makeWeatherAbility = makeWeatherAbility ? (name, weather) ->
makeAbility name, ->
this::switchIn = ->
@pokemon.activateAbility()
@battle.setWeather(weather)
makeWeatherAbility("Drizzle", Weather.RAIN)
makeWeatherAbility("Drought", Weather.SUN)
makeWeatherAbility("Sand Stream", Weather.SAND)
makeWeatherAbility("Snow Warning", Weather.HAIL)
makeFilterAbility = (name) ->
makeAbility name, ->
this::modifyDamageTarget = (move, user) ->
if util.typeEffectiveness(move.type, user.types) > 1
0xC00
else
0x1000
makeFilterAbility("Filter")
makeFilterAbility("Solid Rock")
makeContactStatusAbility = (name, attachment) ->
makeAbility name, ->
this::isAliveCheck = -> true
this::afterBeingHit = (move, user, target, damage, isDirect) ->
return if !move.hasFlag("contact")
return if @battle.rng.next("contact status") >= .3
return if !isDirect
@pokemon.activateAbility()
user.attach(attachment, source: @pokemon)
makeContactStatusAbility("Cute Charm", Attachment.Attract)
makeContactStatusAbility("Flame Body", Status.Burn)
makeContactStatusAbility("Poison Point", Status.Poison)
makeContactStatusAbility("Static", Status.Paralyze)
makeStatusBoostAbility = (name, statuses, spectra) ->
makeAbility name, ->
this::modifyBasePower = (move, target) ->
if move.spectra == spectra && statuses.some((s) => @pokemon.has(s))
0x1800
else
0x1000
makeStatusBoostAbility("Flare Boost", [Status.Burn], 'special')
makeStatusBoostAbility("Toxic Boost", [Status.Poison, Status.Toxic], 'physical')
makeHugePowerAbility = (name) ->
makeAbility name, ->
this::modifyAttack = (move) ->
if move.isPhysical() then 0x2000 else 0x1000
makeHugePowerAbility("Huge Power")
makeHugePowerAbility("Pure Power")
makeAttachmentImmuneAbility = (name, immuneAttachments, options = {}) ->
makeAbility name, ->
this::shouldAttach = (attachment) ->
if attachment in immuneAttachments
@pokemon.activateAbility()
return false
return true
shouldCure = options.cure ? true
if shouldCure
this::update = ->
for attachment in immuneAttachments
if @pokemon.has(attachment)
@pokemon.activateAbility()
@pokemon.unattach(attachment)
makeAttachmentImmuneAbility("Immunity", [Status.Poison, Status.Toxic])
makeAttachmentImmuneAbility("Inner Focus", [Attachment.Flinch], cure: false)
makeAttachmentImmuneAbility("Insomnia", [Status.Sleep])
makeAttachmentImmuneAbility("Limber", [Status.Paralyze])
makeAttachmentImmuneAbility("Magma Armor", [Status.Freeze])
makeAttachmentImmuneAbility("Oblivious", [Attachment.Attract])
makeAttachmentImmuneAbility("Own Tempo", [Attachment.Confusion])
makeAttachmentImmuneAbility("Vital Spirit", [Status.Sleep])
makeAttachmentImmuneAbility("Water Veil", [Status.Burn])
makeContactHurtAbility = (name) ->
makeAbility name, ->
this::isAliveCheck = -> true
this::afterBeingHit = (move, user, target, damage, isDirect) ->
return unless move.hasFlag('contact')
return unless isDirect
amount = user.stat('hp') >> 3
@pokemon.activateAbility()
if user.damage(amount)
@battle.cannedText('POKEMON_HURT', user)
makeContactHurtAbility("Iron Barbs")
makeContactHurtAbility("Rough Skin")
makeRedirectAndBoostAbility = (name, type) ->
makeAbility name, ->
# TODO: This should be implemented as isImmune instead.
# TODO: Type-immunities should come before ability immunities.
this::shouldBlockExecution = (move, user) ->
return if move.getType(@battle, user, @pokemon) != type || user == @pokemon
@pokemon.activateAbility()
@pokemon.boost(specialAttack: 1) unless @pokemon.isImmune(type)
return true
makeRedirectAndBoostAbility("Lightningrod", "Electric")
makeRedirectAndBoostAbility("Storm Drain", "Water")
makeTypeImmuneAbility = (name, type, stat) ->
makeAbility name, ->
this::shouldBlockExecution = (move, user) ->
return if move.getType(@battle, user, @pokemon) != type || user == @pokemon
@pokemon.activateAbility()
@battle.message "#{@pokemon.name}'s #{name} increased its #{stat}!"
hash = {}
hash[stat] = 1
@pokemon.boost(hash)
return true
makeTypeImmuneAbility("Motor Drive", "Electric", "speed")
makeTypeImmuneAbility("Sap Sipper", "Grass", "attack")
makeTypeAbsorbMove = (name, type) ->
makeAbility name, ->
this::shouldBlockExecution = (move, user) ->
return if move.getType(@battle, user, @pokemon) != type || user == @pokemon
@pokemon.activateAbility()
amount = @pokemon.stat('hp') >> 2
if @pokemon.heal(amount)
@battle.cannedText('RECOVER_HP', @pokemon)
return true
makeTypeAbsorbMove("Water Absorb", "Water")
makeTypeAbsorbMove("Volt Absorb", "Electric")
makeAbilityCancelAbility = (name, cannedText) ->
makeAbility name, ->
this::switchIn = ->
@pokemon.activateAbility()
@battle.cannedText(cannedText, @pokemon)
this::beforeMove = (move, pokemon, targets) ->
for target in targets
continue if !@battle.isPokemon(target)
target.attach(Attachment.AbilityCancel)
this::afterMove = (move, pokemon, targets) ->
for target in targets
continue if !@battle.isPokemon(target)
target.unattach(Attachment.AbilityCancel)
makeAbilityCancelAbility('Mold Breaker', 'MOLD_BREAKER')
makeAbilityCancelAbility('Teravolt', 'TERAVOLT')
makeAbilityCancelAbility('Turboblaze', 'TURBOBLAZE')
# Unique Abilities
makeAbility "Adaptability"
makeAbility "Aftermath", ->
this::isAliveCheck = -> true
this::afterFaint = ->
hit = @pokemon.lastHitBy
return if !hit
{team, slot, damage, move, turn} = hit
pokemon = team.at(slot)
if move.hasFlag('contact')
amount = (pokemon.stat('hp') >> 2)
@pokemon.activateAbility()
pokemon.damage(amount)
@battle.cannedText('POKEMON_HURT', pokemon)
makeAbility 'Analytic', ->
this::modifyBasePower = ->
if !@battle.hasActionsLeft() then 0x14CD else 0x1000
makeAbility "Anger Point", ->
this::informCriticalHit = ->
@pokemon.activateAbility()
@battle.message "#{@pokemon.name} maxed its Attack!"
@pokemon.boost(attack: 12)
makeAbility "Anticipation", ->
this::switchIn = ->
opponents = @battle.getOpponents(@pokemon)
moves = _(opponent.moves for opponent in opponents).flatten()
for move in moves
effectiveness = util.typeEffectiveness(move.type, @pokemon.types) > 1
if effectiveness || move.hasFlag("ohko")
@pokemon.activateAbility()
@battle.cannedText('ANTICIPATION', @pokemon)
break
makeAbility "Arena Trap", ->
this::beginTurn = this::switchIn = ->
opponents = @battle.getOpponents(@pokemon)
for opponent in opponents
opponent.blockSwitch() unless opponent.isImmune("Ground")
makeAbility "Bad Dreams", ->
this::endTurn = ->
opponents = @battle.getOpponents(@pokemon)
for opponent in opponents
continue unless opponent.has(Status.Sleep)
amount = opponent.stat('hp') >> 3
@pokemon.activateAbility()
if opponent.damage(amount)
@battle.cannedText('BAD_DREAMS', opponent)
makeAbility "Color Change", ->
this::afterBeingHit = (move, user, target, damage) ->
{type} = move
if !move.isNonDamaging() && !target.hasType(type)
@pokemon.activateAbility()
@battle.cannedText('COLOR_CHANGE', target, type)
target.types = [ type ]
makeAbility "Compoundeyes", ->
this::editAccuracy = (accuracy) ->
Math.floor(1.3 * accuracy)
# Hardcoded in Pokemon#boost
makeAbility "Contrary"
makeAbility "Cursed Body", ->
this::isAliveCheck = -> true
this::afterBeingHit = (move, user, target, damage, isDirect) ->
return if !isDirect
return if user == target
return if user.has(Attachment.Substitute)
return if @battle.rng.next("cursed body") >= .3
return if user.has(Attachment.Disable)
@pokemon.activateAbility()
user.attach(Attachment.Disable, {move})
# Implementation is done in moves.coffee, specifically makeExplosionMove.
makeAbility 'Damp'
makeAbility 'Defeatist', ->
this::modifyAttack = ->
halfHP = (@pokemon.stat('hp') >> 1)
if @pokemon.currentHP <= halfHP then 0x800 else 0x1000
makeAbility 'Defiant', ->
this::afterEachBoost = (boostAmount, source) ->
return if source.team == @pokemon.team
@pokemon.activateAbility()
@pokemon.boost(attack: 2) if boostAmount < 0
makeAbility 'Download', ->
this::switchIn = ->
opponents = @battle.getOpponents(@pokemon)
return if opponents.length == 0
totalDef = opponents.reduce(((s, p) -> s + p.stat('defense')), 0)
totalSpDef = opponents.reduce(((s, p) -> s + p.stat('specialDefense')), 0)
@pokemon.activateAbility()
if totalSpDef <= totalDef
@pokemon.boost(specialAttack: 1)
else
@pokemon.boost(attack: 1)
makeAbility 'Dry Skin', ->
this::modifyBasePowerTarget = (move, user) ->
if move.getType(@battle, user, @pokemon) == 'Fire' then 0x1400 else 0x1000
this::endTurn = ->
amount = (@pokemon.stat('hp') >> 3)
if @battle.hasWeather(Weather.SUN)
@pokemon.activateAbility()
@pokemon.damage(amount)
else if @battle.hasWeather(Weather.RAIN)
@pokemon.activateAbility()
@pokemon.heal(amount)
this::shouldBlockExecution = (move, user) ->
return if move.getType(@battle, user, @pokemon) != 'Water' || user == @pokemon
@pokemon.activateAbility()
@pokemon.heal((@pokemon.stat('hp') >> 2))
return true
# Implementation is in Attachment.Sleep
makeAbility 'Early Bird'
makeAbility 'Effect Spore', ->
this::isAliveCheck = -> true
this::afterBeingHit = (move, user, target, damage) ->
return unless move.hasFlag("contact")
switch @battle.rng.randInt(1, 10, "effect spore")
when 1
if user.attach(Status.Sleep)
@pokemon.activateAbility()
when 2
if user.attach(Status.Paralyze)
@pokemon.activateAbility()
when 3
if user.attach(Status.Poison)
@pokemon.activateAbility()
makeAbility 'Flash Fire', ->
this::shouldBlockExecution = (move, user) ->
return if move.getType(@battle, user, @pokemon) != 'Fire' || user == @pokemon
if @pokemon.attach(Attachment.FlashFire)
@pokemon.activateAbility()
@battle.cannedText('FLASH_FIRE', @pokemon)
else
@battle.cannedText('IMMUNITY', @pokemon)
return true
makeAbility 'Forecast'
makeAbility 'Forewarn', ->
VariablePowerMoves =
'Crush Grip' : true
'Dragon Rage' : true
'Endeavor' : true
'Flail' : true
'Frustration' : true
'Grass Knot' : true
'Gyro Ball' : true
'SonicBoom' : true
'Hidden Power' : true
'Low Kick' : true
'Natural Gift' : true
'Night Shade' : true
'Psywave' : true
'Return' : true
'Reversal' : true
'Seismic Toss' : true
'Trump Card' : true
'Wring Out' : true
CounterMoves =
"Counter" : true
"Mirror Coat" : true
"Metal Burst" : true
@consider = consider = (move) ->
if move.hasFlag('ohko')
160
else if CounterMoves[move.name]
120
else if VariablePowerMoves[move.name]
80
else
move.power
this::switchIn = ->
opponents = @battle.getOpponents(@pokemon)
return if opponents.length == 0
moves = _(opponent.moves for opponent in opponents).flatten()
maxPower = Math.max(moves.map((m) -> consider(m))...)
possibles = moves.filter((m) -> consider(m) == maxPower)
finalMove = @battle.rng.choice(possibles, "forewarn")
pokemon = _(opponents).find((p) -> finalMove in p.moves)
@pokemon.activateAbility()
@battle.cannedText('FOREWARN', pokemon, finalMove)
makeAbility 'Friend Guard', ->
this::modifyDamageTarget = (move, user) ->
return 0xC00 if user.team == @pokemon.team
return 0x1000
makeAbility "Frisk", ->
this::switchIn = ->
opponents = @battle.getOpponents(@pokemon)
return if opponents.length == 0
# TODO: Do you select from opponents with items, or all alive opponents?
opponent = @battle.rng.choice(opponents, "frisk")
if opponent.hasItem()
@pokemon.activateAbility()
item = opponent.getItem()
@battle.cannedText('FRISK', @pokemon, item)
# Implemented in items.coffee; makePinchBerry
makeAbility "Gluttony"
makeAbility "Guts", ->
this::modifyAttack = (move, target) ->
return 0x1800 if @pokemon.hasStatus() && move.isPhysical()
return 0x1000
makeAbility 'Harvest', ->
this::endTurn = ->
return unless @pokemon.lastItem?.type == 'berries'
shouldHarvest = @battle.hasWeather(Weather.SUN)
shouldHarvest ||= @battle.rng.randInt(0, 1, "harvest") == 1
if shouldHarvest
@pokemon.activateAbility()
@battle.cannedText('HARVEST', @pokemon, @pokemon.lastItem)
@pokemon.setItem(@pokemon.lastItem, clearLastItem: true)
makeAbility 'Healer', ->
this::endTurn = ->
for adjacent in @pokemon.team.getAdjacent(@pokemon)
if @battle.rng.randInt(1, 10, "healer") <= 3
@pokemon.activateAbility()
adjacent.cureStatus()
makeAbility 'Heatproof', ->
this::modifyBasePowerTarget = (move, user) ->
return 0x800 if move.getType(@battle, user, @pokemon) == 'Fire'
return 0x1000
makeAbility 'Heavy Metal', ->
this::calculateWeight = (weight) ->
2 * weight
makeAbility 'Honey Gather'
makeAbility 'Hustle', ->
this::modifyAttack = (move, target) ->
return 0x1800 if move.isPhysical()
return 0x1000
this::editAccuracy = (accuracy, move) ->
return Math.floor(0.8 * accuracy) if move.isPhysical()
return accuracy
makeAbility "Hydration", ->
this::endTurn = ->
if @battle.hasWeather(Weather.RAIN) && @pokemon.hasStatus()
@pokemon.activateAbility()
@pokemon.cureStatus()
makeAbility 'Ice Body', ->
this::endTurn = ->
if @battle.hasWeather(Weather.HAIL)
@pokemon.activateAbility()
amount = @pokemon.stat('hp') >> 4
@pokemon.heal(amount)
this::isWeatherDamageImmune = (weather) ->
return true if weather == Weather.HAIL
makeAbility 'Illuminate'
makeAbility 'Imposter', ->
this::switchIn = ->
opponents = @battle.getAllOpponents(@pokemon)
index = @team.indexOf(@pokemon)
opponent = opponents[index]
return if !opponent
return if opponent.isFainted() || opponent.has(Attachment.Substitute)
@pokemon.attach(Attachment.Transform, target: opponent)
# Hardcoded in Move#isDirectHit
# Hardcoded in Attachment.Reflect and Attachment.LightScreen
makeAbility 'Infiltrator'
makeAbility 'Intimidate', ->
this::switchIn = ->
opponents = @battle.getOpponents(@pokemon)
for opponent in opponents
unless opponent.has(Attachment.Substitute)
@pokemon.activateAbility()
opponent.boost(attack: -1, @pokemon)
makeAbility 'Iron Fist', ->
this::modifyBasePower = (move) ->
if move.hasFlag('punch') then 0x1333 else 0x1000
makeAbility 'Justified', ->
this::afterBeingHit = (move, user, target, damage, isDirect) ->
if !move.isNonDamaging() && move.getType(@battle, user, @pokemon) == 'Dark' && isDirect
@pokemon.activateAbility()
@pokemon.boost(attack: 1)
makeAbility 'Klutz', ->
this::beginTurn = this::switchIn = ->
@pokemon.blockItem()
makeAbility 'Leaf Guard', ->
this::shouldAttach = (attachment) ->
if attachment.status && @battle.hasWeather(Weather.SUN)
@pokemon.activateAbility()
return false
return true
makeAbility 'Levitate', ->
this::isImmune = (type) ->
return true if type == 'Ground'
makeAbility 'Light Metal', ->
this::calculateWeight = (weight) ->
weight >> 1
# Implemented in Pokemon#drain
makeAbility 'Liquid Ooze'
makeAbility 'Magic Bounce', ->
this::beginTurn = this::switchIn = ->
@pokemon.attach(Attachment.MagicCoat)
@team.attach(Attachment.MagicCoat)
makeAbility 'Magic Guard', ->
this::transformHealthChange = (damage, options) ->
switch options.source
when 'move' then return damage
else return 0
makeAbility 'Magnet Pull', ->
this::beginTurn = this::switchIn = ->
opponents = @battle.getOpponents(@pokemon)
opponents = opponents.filter((p) -> p.hasType("Steel"))
opponent.blockSwitch() for opponent in opponents
makeAbility 'Marvel Scale', ->
this::editDefense = (defense) ->
if @pokemon.hasStatus() then Math.floor(1.5 * defense) else defense
makeAbility 'Minus', ->
this::modifyAttack = (move, target) ->
allies = @team.getActiveAlivePokemon()
if move.isSpecial() && allies.some((p) -> p.has(Ability.Plus))
0x1800
else
0x1000
makeAbility 'Moody', ->
allBoosts = [ "attack", "defense", "speed", "specialAttack",
"specialDefense", "accuracy", "evasion" ]
this::endTurn = ->
possibleRaises = allBoosts.filter (stat) =>
@pokemon.stages[stat] < 6
raiseStat = @battle.rng.choice(possibleRaises, "moody raise")
possibleLowers = allBoosts.filter (stat) =>
@pokemon.stages[stat] > -6 && stat != raiseStat
lowerStat = @battle.rng.choice(possibleLowers, "moody lower")
boosts = {}
boosts[raiseStat] = 2 if raiseStat
boosts[lowerStat] = -1 if lowerStat
@pokemon.activateAbility()
@pokemon.boost(boosts)
makeAbility 'Moxie', ->
this::afterSuccessfulHit = (move, user, target) ->
if target.isFainted()
@pokemon.activateAbility()
@pokemon.boost(attack: 1)
makeAbility 'Multiscale', ->
this::modifyDamageTarget = ->
return 0x800 if @pokemon.currentHP == @pokemon.stat('hp')
return 0x1000
makeAbility 'Multitype'
makeAbility 'Mummy', ->
this::isAliveCheck = -> true
this::afterBeingHit = (move, user) ->
if move.hasFlag("contact") && user.hasChangeableAbility() && !user.hasAbility("Mummy")
@pokemon.activateAbility()
user.copyAbility(@constructor)
@battle.cannedText('MUMMY', user)
makeAbility 'Natural Cure', ->
this::switchOut = ->
@pokemon.cureStatus(message: false)
# Hardcoded in Move#willMiss
makeAbility 'No Guard'
makeAbility 'Normalize', ->
this::editMoveType = (type, target) ->
return "Normal" if @pokemon != target
return type
makeAbility 'Overcoat', ->
this::isWeatherDamageImmune = -> true
makeAbility 'Pickpocket', ->
this::afterBeingHit = (move, user, target, damage) ->
return if !move.hasFlag("contact") || target.hasItem() || !user.canLoseItem()
@pokemon.activateAbility()
@battle.cannedText('PICKPOCKET', target, user, user.item)
target.setItem(user.item)
user.removeItem()
makeAbility 'Plus', ->
this::modifyAttack = (move, target) ->
allies = @team.getActiveAlivePokemon()
if move.isSpecial() && allies.some((p) -> p.has(Ability.Minus))
0x1800
else
0x1000
makeAbility 'Poison Heal', ->
# Poison damage neutralization is hardcoded in Attachment.Poison and Toxic.
this::endTurn = ->
# Return early so that:
# 1. We don't trigger ability activation if the pokemon won't be healed.
# 2. Ability activation must happen before HP animation.
return if @pokemon.currentHP == @pokemon.stat('hp')
if @pokemon.has(Status.Poison) || @pokemon.has(Status.Toxic)
@pokemon.activateAbility()
amount = @pokemon.stat('hp') >> 3
@pokemon.heal(amount)
makeAbility 'Prankster', ->
this::editPriority = (priority, move) ->
return priority + 1 if move.isNonDamaging()
return priority
# PP deduction hardcoded in Battle
makeAbility 'Pressure', ->
this::switchIn = ->
@pokemon.activateAbility()
@battle.cannedText('PRESSURE', @pokemon)
# Speed drop negation hardcoded into Attachment.Paralyze
makeAbility 'Quick Feet', ->
this::editSpeed = (speed) ->
if @pokemon.hasStatus() then Math.floor(1.5 * speed) else speed
makeAbility 'Rain Dish', ->
this::endTurn = ->
return unless @battle.hasWeather(Weather.RAIN)
@pokemon.activateAbility()
amount = @pokemon.stat('hp') >> 4
@pokemon.heal(amount)
makeAbility 'Rattled', ->
this::afterBeingHit = (move, user, target, damage, isDirect) ->
type = move.getType(@battle, user, @pokemon)
if type in [ "Bug", "Ghost", "Dark" ] && !move.isNonDamaging() && isDirect
@pokemon.activateAbility()
@pokemon.boost(speed: 1)
makeAbility 'Reckless', ->
this::modifyBasePower = (move, target) ->
kickMoves = [ @battle.getMove("Jump Kick"), @battle.getMove("Hi Jump Kick")]
if move.recoil < 0 || move in kickMoves
0x1333
else
0x1000
makeAbility 'Rivalry', ->
this::modifyBasePower = (move, target) ->
return 0x1400 if @pokemon.gender == target.gender
return 0xC00 if (@pokemon.gender == 'F' && target.gender == 'M') ||
(@pokemon.gender == 'M' && target.gender == 'F')
return 0x1000
makeAbility 'Regenerator', ->
this::switchOut = ->
amount = Math.floor(@pokemon.stat('hp') / 3)
# Uses setHP directly to bypass Heal Block's effect
@pokemon.setHP(@pokemon.currentHP + amount)
# Hardcoded in move.coffee
makeAbility 'Rock Head'
makeAbility 'Run Away'
makeAbility 'Sand Force', ->
this::modifyBasePower = (move, target) ->
return 0x1000 unless @battle.hasWeather(Weather.SAND)
type = move.getType(@battle, @pokemon, target)
return 0x14CD if type in ['Rock', 'Ground', 'Steel']
return 0x1000
this::isWeatherDamageImmune = (weather) ->
return true if weather == Weather.SAND
makeAbility 'Sand Veil', ->
this::editEvasion = (accuracy) ->
if @battle.hasWeather(Weather.SAND)
Math.floor(.8 * accuracy)
else
accuracy
this::isWeatherDamageImmune = (weather) ->
return true if weather == Weather.SAND
makeAbility 'Scrappy', ->
this::shouldIgnoreImmunity = (moveType, target) ->
return target.hasType('Ghost') && moveType in [ 'Normal', 'Fighting' ]
# Hardcoded in server/bw/data/moves
makeAbility 'Serene Grace'
makeAbility 'Shadow Tag', ->
this::beginTurn = this::switchIn = ->
opponents = @battle.getOpponents(@pokemon)
for opponent in opponents
opponent.blockSwitch() unless opponent.hasAbility('Shadow Tag')
makeAbility 'Shed Skin', ->
this::endTurn = ->
return unless @pokemon.hasStatus()
if @battle.rng.randInt(1, 10, "shed skin") <= 3
@pokemon.cureStatus()
makeAbility 'Sheer Force', ->
this::modifyBasePower = (move, target) ->
return 0x14CD if move.hasSecondaryEffect()
return 0x1000
# Hardcoded in Move#shouldTriggerSecondary
makeAbility 'Shield Dust'
makeAbility 'Simple', ->
this::transformBoosts = (boosts) ->
newBoosts = {}
for stat, boost of boosts
newBoosts[stat] = 2 * boost
newBoosts
makeAbility "Skill Link", ->
this::calculateNumberOfHits = (move, targets) ->
move.maxHits
makeAbility 'Slow Start', ->
this::initialize = ->
@turns = 5
this::switchIn = ->
@pokemon.activateAbility()
@battle.cannedText('SLOW_START_START', @pokemon)
this::endTurn = ->
@turns -= 1
if @turns == 0
@battle.cannedText('SLOW_START_END', @pokemon)
this::modifyAttack = (move, target) ->
return 0x800 if move.isPhysical() && @turns > 0
return 0x1000
this::editSpeed = (speed) ->
return speed >> 1 if @turns > 0
return speed
makeAbility 'Sniper', ->
this::modifyDamage = (move, target) ->
return 0x1800 if @pokemon.crit
return 0x1000
makeAbility 'Snow Cloak', ->
this::editEvasion = (accuracy) ->
if @battle.hasWeather(Weather.HAIL)
Math.floor(.8 * accuracy)
else
accuracy
this::isWeatherDamageImmune = (weather) ->
return true if weather == Weather.HAIL
makeAbility 'Solar Power', ->
this::modifyAttack = (move, target) ->
return 0x1800 if move.isSpecial() && @battle.hasWeather(Weather.SUN)
return 0x1000
this::endTurn = ->
if @battle.hasWeather(Weather.SUN)
amount = (@pokemon.stat('hp') >> 3)
@pokemon.activateAbility()
@pokemon.damage(amount)
@battle.cannedText('POKEMON_HURT', @pokemon)
makeAbility 'Soundproof', ->
this::isImmune = (type, move) ->
return true if move?.hasFlag('sound')
makeAbility 'Speed Boost', ->
this::endTurn = ->
return if @pokemon.turnsActive <= 0
@pokemon.boost(speed: 1)
makeAbility 'Stall', ->
this::afterTurnOrder = ->
@battle.delay(@pokemon)
# Hardcoded in Attachment.Flinch
makeAbility 'Steadfast'
# Hardcoded in Pokemon#canLoseItem
makeAbility 'Sticky Hold'
makeAbility 'Sturdy', ->
this::transformHealthChange = (amount, options) ->
if @pokemon.currentHP == @pokemon.stat('hp')
if amount >= @pokemon.currentHP && options.source == 'move'
@pokemon.activateAbility()
@battle.cannedText('ENDURE', @pokemon)
return @pokemon.currentHP - 1
return amount
makeAbility 'Suction Cups', ->
this::shouldPhase = (phaser) ->
@pokemon.activateAbility()
@battle.cannedText('ANCHOR', @pokemon)
return false
# Hardcoded in Move#criticalHitLevel
makeAbility 'Super Luck'
# Hardcoded in status.coffee
makeAbility 'Synchronize'
makeAbility 'Tangled Feet', ->
this::editEvasion = (evasion) ->
if @pokemon.has(Attachment.Confusion) then evasion >> 1 else evasion
makeAbility 'Technician', ->
this::modifyBasePower = (move, target) ->
return 0x1800 if move.basePower(@battle, @pokemon, target) <= 60
return 0x1000
makeAbility 'Telepathy', ->
this::shouldBlockExecution = (move, user) ->
return if move.isNonDamaging() || user == @pokemon
return if user not in @team.pokemon
@pokemon.activateAbility()
@battle.cannedText('AVOID_ALLIES', @pokemon)
return true
makeAbility 'Thick Fat', ->
this::modifyAttackTarget = (move, user) ->
return 0x800 if move.getType(@battle, user, @pokemon) in [ 'Fire', 'Ice' ]
return 0x1000
makeAbility 'Tinted Lens', ->
this::modifyDamage = (move, target) ->
return 0x2000 if move.typeEffectiveness(@battle, @pokemon, target) < 1
return 0x1000
makeAbility 'Trace', ->
bannedAbilities =
"Flower Gift" : true
"Forecast" : true
"Illusion" : true
"Imposter" : true
"Multitype" : true
"Trace" : true
"Zen Mode" : true
this::switchIn = ->
opponents = @battle.getOpponents(@pokemon)
abilities = _(opponent.ability for opponent in opponents).compact()
abilities = abilities.filter((a) -> a.displayName not of bannedAbilities)
return if abilities.length == 0
ability = @battle.rng.choice(abilities, "trace")
# TODO: Display whose ability it traced.
shouldshow = {reveal: true}
shouldshow.reveal = false if @pokemon.has(Attachment.Illusion)
@pokemon.copyAbility(ability, shouldshow)
@battle.cannedText('TRACE', ability) if !@pokemon.has(Attachment.Illusion)
makeAbility 'Truant', ->
this::initialize = ->
@truanted = true
this::beforeMove = ->
@truanted = !@truanted
if @truanted
@pokemon.activateAbility()
@battle.cannedText('TRUANT', @pokemon)
return false
# Hardcoded in Move
makeAbility "Unaware"
# Hardcoded in Pokemon#removeItem
makeAbility 'Unburden'
makeAbility 'Unnerve', ->
this::beginTurn = this::switchIn = ->
opponents = @battle.getOpponents(@pokemon)
# TODO: Unnerve likely doesn't last until the end of the turn.
# More research is needed here.
for opponent in opponents
opponent.blockItem() if opponent.item?.type == 'berries'
makeAbility 'Victory Star', ->
this::editAccuracy = (accuracy) ->
Math.floor(accuracy * 1.1)
makeAbility 'Weak Armor', ->
this::afterBeingHit = (move, user) ->
if move.isPhysical() then @pokemon.boost(defense: -1, speed: 1)
makeAbility 'Wonder Guard', ->
this::shouldBlockExecution = (move, user) ->
return if move == @battle.getMove("Struggle")
return if move.isNonDamaging() || user == @pokemon
return if move.typeEffectiveness(@battle, user, @pokemon) > 1
@pokemon.activateAbility()
return true
# Hardcoded in Move#chanceToHit
makeAbility 'Wonder Skin'