@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 makeWeatherItem 'Dark Rock', Weather.MOON 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