BattleSim/server/bw/data/items.coffee

632 lines
20 KiB
CoffeeScript

@ItemData = ItemData = require './data_items.json'
{Attachment, Status, VolatileAttachment} = require('../attachment')
{Weather} = require '../../../shared/weather'
{Protocol} = require '../../../shared/protocol'
util = require '../util'
@Item = Item = {}
makeItem = (name, func) ->
if name not of ItemData
throw new Error("Cannot extend Item '#{name}' because it does not exist.")
condensed = name.replace(/\s+/g, '')
class Item[condensed] extends VolatileAttachment
@displayName: name
displayName: name
item: true
(this[property] = value for property, value of ItemData[name])
func?.call(this)
makePinchBerry = (name, hookName, func) ->
if !func?
func = hookName
hookName = "update"
makeItem name, ->
this.eat = (battle, eater) ->
func.call(this, battle, eater)
this::[hookName] = ->
fraction = (if @pokemon.hasAbility("Gluttony") then 1 else 2)
activationHP = @pokemon.stat('hp') >> fraction
if @pokemon.currentHP <= activationHP
@constructor.eat(@battle, @pokemon)
@pokemon.useItem()
# TODO: If the stat is maxed, does anything special happen?
# Is the berry still consumed?
makeStatBoostBerry = (name, boosts) ->
makePinchBerry name, (battle, eater) ->
boostedStats = eater.boost(boosts)
makeFlavorHealingBerry = (name, stat) ->
makeItem name, ->
this.eat = (battle, owner) ->
if owner.heal(Math.floor(owner.stat('hp') / 8))
battle.cannedText('BERRY_RESTORE', owner, this)
if owner.natureBoost(stat) < 1.0
owner.attach(Attachment.Confusion)
this::update = ->
if @pokemon.currentHP <= Math.floor(@pokemon.stat('hp') / 2) && @pokemon.canHeal()
@constructor.eat(@battle, @pokemon)
@pokemon.useItem()
makeHealingBerry = (name, func) ->
makeItem name, ->
this.eat = (battle, owner) ->
if owner.heal(func(owner))
battle.cannedText('BERRY_RESTORE', owner, this)
this::update = ->
if @pokemon.currentHP <= Math.floor(@pokemon.stat('hp') / 2) && @pokemon.canHeal()
@constructor.eat(@battle, @pokemon)
@pokemon.useItem()
makeTypeResistBerry = (name, type) ->
makeItem name, ->
this.eat = ->
this::modifyBasePowerTarget = (move, user) ->
return 0x1000 if move.getType(@battle, user, @pokemon) != type
return 0x1000 if util.typeEffectiveness(type, @pokemon.types) <= 1 && type != 'Normal'
@battle.cannedText('ITEM_WEAKEN', @constructor, @pokemon)
@pokemon.useItem()
return 0x800
makeFeedbackDamageBerry = (name, klass) ->
makeItem name, ->
this.eat = ->
this::afterBeingHit = (move, user, target) ->
return if !move[klass]()
return if target.isFainted()
if user.damage(Math.floor(user.stat('hp') / 8))
@battle.cannedText('POKEMON_HURT_BY_ITEM', user, target, @constructor)
target.useItem()
makeStatusCureBerry = (name, statuses...) ->
makeItem name, ->
this.eat = (battle, owner) ->
for attachment in statuses
if owner.cureAttachment(attachment, message: name)
return true
return false
this::update = ->
if @constructor.eat(@battle, @pokemon) then @pokemon.useItem()
makeOrbItem = (name, species) ->
makeItem name, ->
this::modifyBasePower = (move, target) ->
if @pokemon.species == species && move.type in @pokemon.types
0x1333
else
0x1000
makeStatusOrbItem = (name, status) ->
makeItem name, ->
this::endTurn = ->
@pokemon.attach(status)
makeTypeBoostItem = (name, type) ->
makeItem name, ->
this::modifyBasePower = (move, target) ->
if move.type == type
0x1333
else
0x1000
# Same as makeTypeBoostItem, but sets item.plate = type.
makePlateItem = (name, type) ->
makeTypeBoostItem(name, type)
makeItem(name, -> @plate = type)
# Gem items are one-time use.
GEM_BOOST_AMOUNT = GEM_BOOST_AMOUNT ? 0x1800
makeGemItem = (name, type) ->
makeItem name, ->
this::modifyBasePower = (move, target) ->
if move.type == type
GEM_BOOST_AMOUNT
else
0x1000
this::afterSuccessfulHit = (move, user, target) ->
if move.type == type
@battle.cannedText('GEM_BOOST', @constructor, move)
user.useItem()
makeChoiceItem = (name, func) ->
makeItem name, ->
this::initialize = ->
@move = null
this::beforeMove = (move, user, targets) ->
@move = move
true
this::beginTurn = ->
@pokemon.lockMove(@move) if @move?
func.call(this)
makeWeatherItem = (name, weather) ->
makeItem name, ->
@lengthensWeather = weather
makeSpeciesBoostingItem = (name, speciesArray, statsHash) ->
makeItem name, ->
for stat, boost of statsHash
capitalizedStat = stat[0].toUpperCase() + stat.substr(1)
# TODO: Use modifiers
this::["edit#{capitalizedStat}"] = (stat) ->
isTransformed = @pokemon.has(Attachment.Transform)
if @pokemon.species in speciesArray && !isTransformed
Math.floor(stat * boost)
else
stat
makeSpeciesCriticalItem = (name, species) ->
makeItem name, ->
this::criticalModifier = (sum) ->
sum + (if @pokemon.species == species then 2 else 0)
makeDelayItem = (name) ->
makeItem name, ->
this::afterTurnOrder = ->
@battle.delay(@pokemon)
makeEvasionItem = (name, ratio=0.9) ->
makeItem name, ->
this::editEvasion = (accuracy) ->
Math.floor(accuracy * ratio)
makeFlinchItem = (name) ->
makeItem name, ->
this::afterSuccessfulHit = (move, user, target) ->
multiplier = (if user.hasAbility("Serene Grace") then 2 else 1)
if move.flinchChance == 0 && !move.isNonDamaging() &&
@battle.rng.next("flinch item chance") < .1 * multiplier
target.attach(Attachment.Flinch)
makeCriticalBoostItem = (name) ->
makeItem name, ->
this::criticalModifier = (sum) -> sum + 1
makeBoostOnTypeItem = (name, type, boosts) ->
stats = Object.keys(boosts)
length = stats.length
stats = stats.map (stat) ->
stat[0].toUpperCase() + stat[1...length].replace(/[A-Z]/g, " $1")
stats[length - 1] = "and #{stats[length - 1]}" if length >= 2
stats = stats.join(", ") if length >= 3
stats = stats.join(" ") if length == 2
makeItem name, ->
this::afterBeingHit = (move, user, target) ->
if move.type == type
@battle.cannedText('BERRY_RAISE_STAT', @constructor, user, stats)
target.boost(boosts)
target.useItem()
makeBoostOnTypeItem 'Absorb Bulb', 'Water', specialAttack: 1
makeOrbItem 'Adamant Orb', 'Dialga'
makeFlavorHealingBerry 'Aguav Berry', "specialDefense"
makeItem 'Air Balloon', ->
this::initialize = ->
@pokemon.tell(Protocol.POKEMON_ATTACH, @displayName)
this::afterBeingHit = (move, user, target) ->
return if move.isNonDamaging()
@pokemon.tell(Protocol.POKEMON_UNATTACH, @displayName)
target.removeItem()
this::isImmune = (type) ->
return true if type == 'Ground'
makeStatBoostBerry 'Apicot Berry', specialDefense: 1
makeStatusCureBerry 'Aspear Berry', Status.Freeze
makeTypeResistBerry 'Babiri Berry', 'Steel'
makeHealingBerry 'Berry Juice', -> 20
makeTypeBoostItem 'Black Belt', 'Fighting'
makeTypeBoostItem 'BlackGlasses', 'Dark'
makeItem 'Black Sludge', ->
this::endTurn = ->
maxHP = @pokemon.stat('hp')
if @pokemon.hasType('Poison')
return if maxHP == @pokemon.currentHP
amount = Math.floor(maxHP / 16)
amount = 1 if amount == 0
if @pokemon.heal(amount)
@battle.cannedText('ITEM_RESTORE', @pokemon, @constructor)
else
amount = Math.floor(maxHP / 8)
amount = 1 if amount == 0
if @pokemon.damage(amount)
@battle.cannedText('ITEM_SELF_HURT', @pokemon, @constructor)
makeEvasionItem 'BrightPowder', 0.9
makeGemItem 'Bug Gem', 'Bug'
makeBoostOnTypeItem 'Cell Battery', 'Electric', attack: 1
makeTypeBoostItem 'Charcoal', 'Fire'
makeTypeResistBerry 'Charti Berry', 'Rock'
makeStatusCureBerry 'Cheri Berry', Status.Paralyze
makeStatusCureBerry 'Chesto Berry', Status.Sleep
makeTypeResistBerry 'Chilan Berry', 'Normal'
makeChoiceItem 'Choice Band', ->
this::modifyAttack = (move) ->
if move.isPhysical() then 0x1800 else 0x1000
makeChoiceItem 'Choice Specs', ->
this::modifyAttack = (move) ->
if move.isSpecial() then 0x1800 else 0x1000
makeChoiceItem 'Choice Scarf', ->
this::editSpeed = (stat) ->
Math.floor(stat * 1.5)
makeTypeResistBerry 'Chople Berry', 'Fighting'
makeTypeResistBerry 'Coba Berry', 'Flying'
makeTypeResistBerry 'Colbur Berry', 'Dark'
makePinchBerry 'Custap Berry', 'afterTurnOrder', (battle, eater) ->
battle.cannedText('MOVE_FIRST', eater, this)
battle.bump(eater)
makeWeatherItem 'Damp Rock', Weather.RAIN
makeGemItem 'Dark Gem', 'Dark'
makeTypeBoostItem 'Dragon Fang', 'Dragon'
makeGemItem 'Dragon Gem', 'Dragon'
makePlateItem 'Draco Plate', 'Dragon'
makePlateItem 'Dread Plate', 'Dark'
makePlateItem 'Earth Plate', 'Ground'
makeItem 'Eject Button', ->
this::afterAllHitsTarget = (move, user) ->
return if move.isNonDamaging()
return if !@battle.forceSwitch(@pokemon)
@battle.cannedText('EJECT_BUTTON', @pokemon)
@pokemon.useItem()
makeGemItem 'Electric Gem', 'Electric'
makeItem 'Enigma Berry', ->
this.eat = ->
this::afterBeingHit = (move, user, target) ->
return if util.typeEffectiveness(move.type, target.types) <= 1
if target.heal(Math.floor(target.stat('hp') / 4))
@battle.cannedText('BERRY_RESTORE', target, @constructor)
target.useItem()
makeItem 'Eviolite', ->
this::editDefense = this::editSpecialDefense = (defense) ->
return Math.floor(1.5 * defense) if @pokemon.nfe
return defense
makeItem 'Expert Belt', ->
this::modifyAttack = (move, target) ->
effectiveness = move.typeEffectiveness(@battle, @pokemon, target)
return 0x1333 if effectiveness > 1
return 0x1000
makeGemItem 'Fighting Gem', 'Fighting'
makeFlavorHealingBerry 'Figy Berry', "attack"
makeGemItem 'Fire Gem', 'Fire'
makePlateItem 'Fist Plate', 'Fighting'
makeStatusOrbItem 'Flame Orb', Status.Burn
makePlateItem 'Flame Plate', 'Fire'
makeItem 'Float Stone', ->
this::calculateWeight = (weight) ->
Math.floor(weight / 2)
makeGemItem 'Flying Gem', 'Flying'
makeItem 'Focus Band', ->
this::transformHealthChange = (amount, options) ->
if amount >= @pokemon.currentHP && @battle.rng.randInt(0, 9, "focus band") == 0 &&
options.source == 'move'
@battle.cannedText('HANG_ON', @pokemon, @constructor)
@pokemon.useItem()
return @pokemon.currentHP - 1
return amount
makeItem 'Focus Sash', ->
this::transformHealthChange = (amount, options) ->
maxHP = @pokemon.stat('hp')
if @pokemon.currentHP == maxHP && amount >= maxHP && options.source == 'move'
@battle.cannedText('HANG_ON', @pokemon, @constructor)
@pokemon.useItem()
return maxHP - 1
return amount
makeDelayItem 'Full Incense'
makeStatBoostBerry 'Ganlon Berry', defense: 1
makeGemItem 'Ghost Gem', 'Ghost'
makeGemItem 'Grass Gem', 'Grass'
makeOrbItem 'Griseous Orb', 'Giratina'
makeGemItem 'Ground Gem', 'Ground'
makeTypeResistBerry 'Haban Berry', 'Dragon'
makeTypeBoostItem 'Hard Stone', 'Rock'
makeWeatherItem 'Heat Rock', Weather.SUN
makeFlavorHealingBerry 'Iapapa Berry', "defense"
makeGemItem 'Ice Gem', 'Ice'
makePlateItem 'Icicle Plate', 'Ice'
makeWeatherItem 'Icy Rock', Weather.HAIL
makePlateItem 'Insect Plate', 'Bug'
makePlateItem 'Iron Plate', 'Steel'
makeFeedbackDamageBerry 'Jaboca Berry', 'isPhysical'
makeTypeResistBerry 'Kasib Berry', 'Ghost'
makeTypeResistBerry 'Kebia Berry', 'Poison'
makeFlinchItem "King's Rock"
makeDelayItem 'Lagging Tail'
# TODO: What happens if the Pokemon already has Focus Energy?
# Does the berry still get eaten? Same goes for the other stat berries.
makePinchBerry 'Lansat Berry', (battle, eater) ->
eater.attach(Attachment.FocusEnergy)
makeEvasionItem 'Lax Incense', 0.9
makeItem 'Leftovers', ->
this::endTurn = ->
maxHP = @pokemon.stat('hp')
return if maxHP == @pokemon.currentHP
amount = Math.floor(maxHP / 16)
amount = 1 if amount == 0
if @pokemon.heal(amount)
@battle.cannedText('ITEM_RESTORE', @pokemon, @constructor)
makeStatBoostBerry 'Liechi Berry', attack: 1
makeItem 'Life Orb', ->
this::modifyAttack = ->
0x14CC
this::afterAllHits = (move) ->
return if move.isNonDamaging()
if @pokemon.damage(Math.floor(@pokemon.stat('hp') / 10))
@battle.cannedText('ITEM_SELF_HURT', @pokemon, @constructor)
makeItem 'Light Clay' # Hardcoded in Attachment.Screen
makeStatusCureBerry 'Lum Berry', Status.Paralyze, Status.Sleep, Status.Poison,
Status.Toxic, Status.Burn, Status.Freeze, Attachment.Confusion
makeOrbItem 'Lustrous Orb', 'Palkia'
makeItem 'Macho Brace', ->
this::editSpeed = (stat) ->
Math.floor(stat / 2)
makeTypeBoostItem 'Magnet', 'Electric'
makeFlavorHealingBerry 'Mago Berry', "speed"
makePlateItem 'Meadow Plate', 'Grass'
makeItem 'Mental Herb', ->
this.activate = (battle, pokemon) ->
for effectName in [ 'Attract', 'Taunt', 'Encore', 'Torment', 'Disable' ]
attachment = Attachment[effectName]
if pokemon.has(attachment)
battle.cannedText('MENTAL_HERB', pokemon)
pokemon.unattach(attachment)
return true
return false
this::update = ->
if @constructor.activate(@battle, @pokemon)
@pokemon.useItem()
makeTypeBoostItem 'Metal Coat', 'Steel'
makeItem 'Metronome', ->
this::modifyBasePower = (move, target) ->
attachment = @pokemon.get(Attachment.Metronome)
layers = attachment?.layers || 0
0x1000 + layers * 0x333
this::afterSuccessfulHit = (move, user, target) ->
user.attach(Attachment.Metronome, {move})
makePinchBerry 'Micle Berry', (battle, eater) ->
eater.attach(Attachment.MicleBerry)
makePlateItem 'Mind Plate', 'Psychic'
makeTypeBoostItem 'Miracle Seed', 'Grass'
makeItem 'Muscle Band', ->
this::modifyBasePower = (move, target) ->
if move.isPhysical()
0x1199
else
0x1000
makeTypeBoostItem 'Mystic Water', 'Water'
makeTypeBoostItem 'NeverMeltIce', 'Ice'
makeGemItem 'Normal Gem', 'Normal'
makeTypeResistBerry 'Occa Berry', 'Fire'
makeTypeBoostItem 'Odd Incense', 'Psychic'
makeHealingBerry 'Oran Berry', -> 10
makeTypeResistBerry 'Passho Berry', 'Water'
makeTypeResistBerry 'Payapa Berry', 'Psychic'
makeStatusCureBerry 'Pecha Berry', Status.Toxic, Status.Poison
makeStatusCureBerry 'Persim Berry', Attachment.Confusion
makeStatBoostBerry 'Petaya Berry', specialAttack: 1
makeTypeBoostItem 'Poison Barb', 'Poison'
makeGemItem 'Poison Gem', 'Poison'
makeGemItem 'Psychic Gem', 'Psychic'
makeItem 'Quick Claw', ->
this::afterTurnOrder = ->
if @battle.rng.next("quick claw") < .2
@battle.cannedText('MOVE_FIRST', @pokemon, @constructor)
@battle.bump(@pokemon)
makeStatusCureBerry 'Rawst Berry', Status.Burn
makeFlinchItem "Razor Fang"
makeItem 'Red Card', ->
this::afterAllHitsTarget = (move, user) ->
return if move.isNonDamaging()
benched = user.team.getAliveBenchedPokemon()
return if benched.length == 0
@battle.cannedText('RED_CARD', @pokemon, user)
@pokemon.useItem()
return if user.shouldPhase(@battle, @pokemon) == false
pokemon = @battle.rng.choice(benched)
index = user.team.indexOf(pokemon)
user.team.switch(user, index)
makeTypeResistBerry 'Rindo Berry', 'Grass'
makeGemItem 'Rock Gem', 'Rock'
makeItem 'Rocky Helmet', ->
this::isAliveCheck = -> true
this::afterBeingHit = (move, user, target) ->
if move.hasFlag("contact")
amount = Math.floor(user.stat('hp') / 6)
if user.damage(amount)
@battle.cannedText('POKEMON_HURT_BY_ITEM', user, target, @constructor)
makeTypeBoostItem 'Rock Incense', 'Rock'
makeTypeBoostItem 'Rose Incense', 'Grass'
makeFeedbackDamageBerry 'Rowap Berry', 'isSpecial'
makeStatBoostBerry 'Salac Berry', speed: 1
makeTypeBoostItem 'Sea Incense', 'Water'
makeTypeBoostItem 'Sharp Beak', 'Flying'
makeItem 'Shell Bell', ->
this::afterSuccessfulHit = (move, user, target, damage) ->
return if damage == 0
if user.heal(Math.floor(damage / 8))
@battle.cannedText('ITEM_RESTORE', user, @constructor)
makeTypeResistBerry 'Shuca Berry', 'Ground'
makeTypeBoostItem 'Silk Scarf', 'Normal'
makeTypeBoostItem 'SilverPowder', 'Bug'
makeHealingBerry 'Sitrus Berry', (owner) -> Math.floor(owner.stat('hp') / 4)
makePlateItem 'Sky Plate', 'Flying'
makeWeatherItem 'Smooth Rock', Weather.SAND
makeTypeBoostItem 'Soft Sand', 'Ground'
makeSpeciesBoostingItem 'Soul Dew', ["Latias", "Latios"],
specialAttack: 1.5, specialDefense: 1.5
makeTypeBoostItem 'Spell Tag', 'Ghost'
makePlateItem 'Splash Plate', 'Water'
makePlateItem 'Spooky Plate', 'Ghost'
# TODO: If there is no stat left to boost, is it still consumed?
makePinchBerry 'Starf Berry', (battle, eater) ->
stats = ["attack", "defense", "specialAttack", "specialDefense", "speed"]
stats = stats.filter((stat) -> eater.stages[stat] != 6)
return if stats.length == 0
index = battle.rng.randInt(0, stats.length - 1, "starf berry stat")
boosts = {}
boosts[stats[index]] = 2
boostedStats = eater.boost(boosts)
makeItem 'Sticky Barb', ->
this::afterBeingHit = (move, user, target) ->
return unless move.hasFlag("contact")
return if user.hasItem()
user.setItem(@constructor)
target.useItem()
this::endTurn = ->
@pokemon.damage(Math.floor(@pokemon.stat('hp') / 8))
makeGemItem 'Steel Gem', 'Steel'
makePlateItem 'Stone Plate', 'Rock'
makeTypeResistBerry 'Tanga Berry', 'Bug'
makeStatusOrbItem 'Toxic Orb', Status.Toxic
makePlateItem 'Toxic Plate', 'Poison'
makeTypeBoostItem 'TwistedSpoon', 'Psychic'
makeTypeResistBerry 'Wacan Berry', 'Electric'
makeGemItem 'Water Gem', 'Water'
makeTypeBoostItem 'Wave Incense', 'Water'
# TODO: What if White Herb is tricked onto a Pokemon? Are all boosts negated?
makeItem 'White Herb', ->
this.activate = (battle, pokemon) ->
triggered = false
boosts = {}
for stat, boost of pokemon.stages
if boost < 0
triggered = true
boosts[stat] = 0
if triggered
pokemon.setBoosts(boosts)
battle.cannedText('WHITE_HERB', pokemon)
return triggered
this::update = ->
if @constructor.activate(@battle, @pokemon)
@pokemon.useItem()
makeItem "Wide Lens", ->
this::editAccuracy = (accuracy) ->
Math.floor(accuracy * 1.1)
makeFlavorHealingBerry 'Wiki Berry', "specialAttack"
makeItem 'Wise Glasses', ->
this::modifyBasePower = (move, target) ->
if move.isSpecial()
0x1199
else
0x1000
makeTypeResistBerry 'Yache Berry', 'Ice'
makePlateItem 'Zap Plate', 'Electric'
makeItem 'Zoom Lens', ->
this::editAccuracy = (accuracy, move, target) ->
return Math.floor(accuracy * 1.2) if @battle.willMove(target)
return accuracy
makeSpeciesBoostingItem("DeepSeaTooth", ["Clamperl"], specialAttack: 2)
makeSpeciesBoostingItem("DeepSeaScale", ["Clamperl"], specialDefense: 2)
makeSpeciesBoostingItem("Light Ball", ["Pikachu"], attack: 2, specialAttack: 2)
makeSpeciesBoostingItem("Thick Club", ["Cubone", "Marowak"], attack: 2)
makeSpeciesBoostingItem("Metal Powder", ["Ditto"],
defense: 2, specialDefense: 2)
makeSpeciesBoostingItem("Quick Powder", ["Ditto"], speed: 2)
makeSpeciesCriticalItem "Lucky Punch", "Chansey"
makeSpeciesCriticalItem "Stick", "Farfetch'd"
makeCriticalBoostItem 'Razor Claw'
makeCriticalBoostItem 'Scope Lens'
makeItem 'Iron Ball', ->
this::editSpeed = (stat) ->
Math.floor(stat / 2)
this::isImmune = (type) ->
return false if type == 'Ground'
makeItem 'Leppa Berry', ->
this.eat = (battle, eater) ->
for move in eater.moves
if eater.pp(move) == 0
eater.setPP(move, 10)
break
this::update = ->
if @pokemon.lastMove? && @pokemon.pp(@pokemon.lastMove) == 0
@constructor.eat(@battle, @pokemon)
@pokemon.useItem()
# TODO: Implement Nature Power and implement eat there.
for berry in "Belue Berry, Bluk Berry, Cornn Berry, Durin Berry, Grepa Berry,
Hondew Berry, Kelpsy Berry, Magost Berry, Nanab Berry,
Nomel Berry, Pamtre Berry, Pinap Berry, Pomeg Berry, Qualot Berry,
Rabuta Berry, Razz Berry, Spelon Berry, Tamato Berry,
Watmel Berry, Wepear Berry".split(/,\s+/)
makeItem berry, ->
this.eat = ->
# Ensure we aren't purposefully missing berries that need an `eat` function.
for name, item of Item
if item.type == 'berries' && 'eat' not of item
console.warn "Note: Item '#{item.displayName}' does not have `eat` implemented."
# Make all leftover items
for itemName of ItemData
makeItem(itemName) if itemName.replace(/\s+/, '') not of Item