@MoveData = require('./data_moves.json') {Weather} = require('../../../shared/weather') {Move} = require('../move') {Attachment, Status} = require('../attachment') {Ability} = require ('./abilities') {Protocol} = require('../../../shared/protocol') {_} = require 'underscore' util = require '../util' HiddenPower = require '../../../shared/hidden_power' # Generate the initial versions of every single move. # Many will be overwritten later. @Moves = Moves = {} @MoveList = MoveList = [] for name, attributes of @MoveData @Moves[name] = new Move(name, attributes) MoveList.push(@Moves[name]) # Extends a move in the move list using a callback. # # name - The name of the move to extend. # callback - The function that will extend the move. 'this' # is the move object itself. # # Example: # # extendMove 'Substitute', (attributes) -> # @initialize = -> # blah # @afterMove = -> # blah # extendMove = (name, callback) -> if name not of Moves throw new Error("Cannot extend Move '#{name}' because it does not exist.") move = Moves[name] callback.call(move, move.attributes) # We add primary boost moves to the "hit" handler. for move in MoveList if move.hasPrimaryEffect() extendMove move.name, -> @hit = (battle, user, target) -> if @primaryBoostStats? pokemon = (if @primaryBoostTarget == 'self' then user else target) pokemon.boost(@primaryBoostStats, user) if @ailmentId != 'none' && @ailmentChance == 0 effect = battle.getAilmentEffect(this) if !target.attach(effect, source: user) @fail(battle, user) makeJumpKick = (name, recoilPercent=.5) -> extendMove name, -> @afterMiss = @afterFail = (battle, user, target) -> maxHP = user.stat('hp') user.damage(Math.floor(maxHP / 2)) battle.cannedText('JUMP_KICK_MISS', user) makeWeightBased = (name) -> extendMove name, -> @basePower = (battle, user, target) -> weight = target.calculateWeight() if weight <= 100 then 20 else if weight <= 250 then 40 else if weight <= 500 then 60 else if weight <= 1000 then 80 else if weight <= 2000 then 100 else 120 makeWeightRatioBased = (name) -> extendMove name, -> @basePower = (battle, user, target) -> n = target.calculateWeight() / user.calculateWeight() if n < .2 then 120 else if n < .25 then 100 else if n < 1/3 then 80 else if n < .5 then 60 else 40 makeLevelAsDamageMove = (name) -> extendMove name, -> @calculateDamage = (battle, user, target) -> user.level makeReversalMove = (name) -> extendMove name, -> @basePower = (battle, user, target) -> n = Math.floor(64 * user.currentHP / user.stat('hp')) if n <= 1 then 200 else if n <= 5 then 150 else if n <= 12 then 100 else if n <= 21 then 80 else if n <= 42 then 40 else if n <= 64 then 20 makeEruptionMove = (name) -> extendMove name, -> @basePower = (battle, user, target) -> power = Math.floor(150 * (user.currentHP / user.stat('hp'))) Math.max(power, 1) makeStatusCureAttackMove = (moveName, status) -> extendMove moveName, -> @basePower = (battle, user, target) -> if target.has(status) then 2 * @power else @power @afterSuccessfulHit = (battle, user, target) -> target.cureStatus(status) unless user.isFainted() makeOneHitKOMove = (name) -> extendMove name, -> @calculateDamage = (battle, user, target) -> # TODO: Or was this fixed? target.stat('hp') @afterSuccessfulHit = (battle, user, target, damage) -> # TODO: Is this message displayed even if the Pokemon survives? battle.message "It was a one-hit KO!" @chanceToHit = (battle, user, target) -> (user.level - target.level) + 30 makeRecoveryMove = (name) -> extendMove name, -> @use = (battle, user, target) -> hpStat = target.stat('hp') if target.currentHP == hpStat @fail(battle, user) return false amount = Math.round(hpStat / 2) if target.heal(amount) battle.cannedText('RECOVER_HP', target) makeBasePowerBoostMove = (name, rawBasePower, maxBasePower, what) -> extendMove name, -> @basePower = (battle, user, target) -> pokemon = {user, target}[what] # NOTE: the 20 is hardcoded; perhaps this will change later? power = rawBasePower + 20 * pokemon.positiveBoostCount() Math.min(power, maxBasePower) makeWeatherRecoveryMove = (name) -> extendMove name, -> @use = (battle, user, target) -> hpStat = target.stat('hp') amount = if battle.hasWeather(Weather.NONE) util.roundHalfDown(hpStat / 2) else if battle.hasWeather(Weather.SUN) util.roundHalfDown(hpStat * 2 / 3) else if battle.hasWeather(Weather.MOON) if @name == 'Moonlight' util.roundHalfDown(hpStat / 2) else util.roundHalfDown(hpStat * 1 / 3) else util.roundHalfDown(hpStat / 4) percent = Math.floor(100 * amount / hpStat) battle.cannedText('RECOVER_HP', target, percent) target.heal(amount) makeTrickMove = (name) -> extendMove name, -> @use = -> @afterSuccessfulHit = (battle, user, target) -> if (user.hasItem() && !user.hasTakeableItem()) || (target.hasItem() && !target.canLoseItem()) || (!target.hasItem() && !user.hasItem()) @fail(battle, user) return false uItem = user.removeItem() tItem = target.removeItem() battle.cannedText('TRICK_START', user) if tItem battle.cannedText('TRICK_END', user, tItem) user.setItem(tItem) if uItem battle.cannedText('TRICK_END', target, uItem) target.setItem(uItem) makeExplosionMove = (name) -> extendMove name, -> oldExecute = @execute @execute = (battle, user, targets) -> if !_.any(targets, (target) -> target.hasAbility('Damp')) user.faint() oldExecute.call(this, battle, user, targets) else @fail(battle, user) @fail = (battle, user) -> battle.message "#{user.name} cannot use #{@name}!" makeProtectCounterMove = (name, callback) -> extendMove name, -> @execute = (battle, user, targets) -> # Protect fails if the user is the last to move. if !battle.hasActionsLeft() user.unattach(Attachment.ProtectCounter) @fail(battle, user) return # Calculate and roll chance of success attachment = user.attach(Attachment.ProtectCounter) attachment.turns = 2 chance = attachment.successChance() if battle.rng.randInt(1, chance, "protect") > 1 user.unattach(Attachment.ProtectCounter) @fail(battle, user) return # Success! callback.call(this, battle, user, targets) makeOpponentFieldMove = (name, func) -> extendMove name, -> @execute = (battle, user, opponentIds) -> userId = battle.getOwner(user) for id in opponentIds team = battle.getTeam(id) continue if team.shouldBlockFieldExecution(this, userId) func.call(this, battle, user, id) makeProtectMove = (name) -> makeProtectCounterMove name, (battle, user, targets) -> user.attach(Attachment.Protect) makeIdentifyMove = (name, types) -> extendMove name, -> @use = (battle, user, target) -> if target.attach(Attachment.Identify, {types}) battle.message "#{target.name} was identified!" else @fail(battle, user) false makePluckMove = (name) -> extendMove name, -> @afterSuccessfulHit = (battle, user, target) -> item = target.getItem() if user.isAlive() && item?.type == 'berries' item.eat(battle, user) target.removeItem() makeThiefMove = (name) -> extendMove name, -> @afterSuccessfulHit = (battle, user, target) -> return if user.hasItem() || !target.canLoseItem() || user.isFainted() battle.cannedText('THIEF_START', user, target, target.item) user.setItem(target.item) target.removeItem() makeStatusCureMove = (name, message) -> extendMove name, -> @execute = (battle, user, targets) -> battle.message(message) for target in targets target.cureStatus(message: false) makePickAttackMove = (name) -> extendMove name, -> @pickAttackStat = (user, target) -> target.stat('attack', ignoreNegativeBoosts: user.crit) makePickDefenseMove = (name) -> extendMove name, -> @pickDefenseStat = (user, target) -> target.stat('defense', ignorePositiveBoosts: user.crit) makeDelayedAttackMove = (name, message) -> extendMove name, -> @execute = (battle, user, targets) -> # TODO: use slot playerId = battle.getOpponentOwners(user)[0] team = battle.getTeam(playerId) if !team.attach(Attachment.DelayedAttack, user: user, move: this) @fail(battle, user) return battle.message message.replace("$1", user.name) makeRandomSwitchMove = (name) -> extendMove name, -> @afterSuccessfulHit = (battle, user, target, damage, isDirect) -> return if !isDirect return if target.isFainted() || user.isFainted() return if target.shouldPhase(battle, user) == false {team} = target benched = team.getAliveBenchedPokemon() return if benched.length == 0 pokemon = battle.rng.choice(benched) team.switch(target, team.indexOf(pokemon)) makeRampageMove = (moveName) -> extendMove moveName, -> @afterSuccessfulHit = (battle, user, target) -> user.attach(Attachment.Rampage, move: this) makeRampageMove("Outrage") makeRampageMove("Petal Dance") makeRampageMove("Thrash") # TODO: Does it fail if using twice, but on a different target? # From PokemonLab: # TODO: Major research is required here. Does Lock On make the next move to # target the subject hit; the next move to target the subject on the # next turn; all moves targeting the subject on the turn turn; or some # other possibility? makeLockOnMove = (name) -> extendMove name, -> @afterSuccessfulHit = (battle, user, target) -> if user.attach(Attachment.LockOn, {target}) battle.message "#{user.name} locked onto #{target.name}." else @fail(battle, user) return false makeStompMove = (name) -> extendMove name, -> @basePower = (battle, user, target) -> if target.has(Attachment.Minimize) 2 * @power else @power makeMeanLookMove = (name) -> extendMove name, -> @afterSuccessfulHit = (battle, user, target) -> if target.attach(Attachment.MeanLook, user: user) battle.message "#{target.name} can no longer escape!" else @fail(battle, user) return false makeChargeMove = (name, args...) -> condition = args.pop() if typeof args[args.length - 1] == 'function' message = args.pop() vulnerable = args.pop() if args.length > 0 extendMove name, -> @beforeTurn = (battle, user) -> data = {vulnerable, message, condition} data.move = this user.attach(Attachment.Charging, data) makeChargeMove 'Skull Bash', "$1 tucked in its head!" makeChargeMove 'Razor Wind', "$1 whipped up a whirlwind!" makeChargeMove 'Sky Attack', "$1 became cloaked in a harsh light!" makeChargeMove 'Shadow Force', [], "$1 vanished instantly!", (battle) -> battle.hasWeather(Weather.MOON) makeChargeMove 'Ice Burn', "$1 became cloaked in freezing air!" makeChargeMove 'Freeze Shock', "$1 became cloaked in a freezing light!" makeChargeMove 'Fly', ["Gust", "Thunder", "Twister", "Sky Uppercut", "Hurricane", "Smack Down"], "$1 flew up high!" makeChargeMove 'Bounce', ["Gust", "Thunder", "Twister", "Sky Uppercut", "Hurricane", "Smack Down"], "$1 sprang up!" makeChargeMove 'Dig', ["Earthquake", "Magnitude"], "$1 burrowed its way under the ground!" makeChargeMove 'Dive', ["Surf", "Whirlpool"], "$1 hid underwater!" makeChargeMove 'SolarBeam', "$1 absorbed light!", (battle) -> battle.hasWeather(Weather.SUN) extendMove 'SolarBeam', -> @basePower = (battle, user, target) -> if battle.hasWeather(Weather.RAIN) || battle.hasWeather(Weather.SAND) || battle.hasWeather(Weather.HAIL) @power >> 1 else @power makeRechargeMove = (name) -> extendMove name, -> @afterSuccessfulHit = (battle, user, target) -> user.attach(Attachment.Recharge) makeMomentumMove = (name) -> extendMove name, -> @afterSuccessfulHit = (battle, user, target) -> attachment = user.attach(Attachment.Momentum, move: this) attachment.turns += 1 @basePower = (battle, user, target) -> times = user.get(Attachment.Momentum)?.layers || 0 bp = @power * Math.pow(2, times) bp *= 2 if user.has(Attachment.DefenseCurl) bp makeRevengeMove = (moveName) -> extendMove moveName, -> @basePower = (battle, user, target) -> hit = user.lastHitBy return @power if !hit? {team, slot, move, turn} = hit pokemon = team.at(slot) if target == pokemon && !move.isNonDamaging() && battle.turn == turn 2 * @power else @power makeWeatherMove = (name, weatherType) -> extendMove name, -> @execute = (battle, user) -> if !battle.hasWeather(weatherType) @changeWeather(battle, user) else @fail(battle, user) @changeWeather = (battle, user) -> length = 5 length = 8 if weatherType == user.getItem()?.lengthensWeather battle.setWeather(weatherType, length) extendWithBoost = (name, boostTarget, boosts) -> extendMove name, -> @afterSuccessfulHit = (battle, user, target) -> pokemon = (if boostTarget == 'self' then user else target) pokemon.boost(boosts, user) makeCounterMove = (name, multiplier, applies) -> extendMove name, -> @getTargets = (battle, user) -> # Return the last pokemon who hit this one, if it's alive. hit = user.lastHitBy if hit {team, slot} = hit pokemon = team.at(slot) return [ pokemon ] if !pokemon.isFainted() # Return a random target (or none). pokemon = battle.getOpponents(user) pokemon = pokemon.filter((p) -> !p.isFainted()) if pokemon.length == 0 [] else [ battle.rng.choice(pokemon) ] @calculateDamage = -> 0 oldUse = @use @use = (battle, user, target) -> return if oldUse.call(this, battle, user, target) == false hit = user.lastHitBy if hit? && applies(hit.move) && hit.turn == battle.turn target.damage(multiplier * hit.damage, direct: false, source: "move") else @fail(battle, user) return false makeTrappingMove = (name) -> extendMove name, -> @afterSuccessfulHit = (battle, user, target, damage, isDirect) -> if !target.has(Attachment.Trap) && isDirect turns = if !user.hasItem("Grip Claw") battle.rng.randInt(4, 5, "trapping move") else 7 target.attach(Attachment.Trap, user: user, moveName: name, turns: turns) makeIgnoreStagesMove = (name) -> extendMove name, -> oldExecute = @execute @execute = (battle, user, targets) -> target.attach(Attachment.ChipAway) for target in targets oldExecute.call(this, battle, user, targets) target.unattach(Attachment.ChipAway) for target in targets makeStatusCureMove 'Aromatherapy', 'A soothing aroma wafted through the area!' extendMove 'Baton Pass', -> @execute = (battle, user, targets) -> slot = user.team.indexOf(user) if !battle.forceSwitch(user) @fail(battle, user) return # Copy! passable = user.attachments.getPassable() stages = _.clone(user.stages) attachments = user.attachments.attachments attachments = attachments.filter((a) -> a.constructor in passable) for attachment in passable user.unattach(attachment) user.team.attach(Attachment.BatonPass, {slot, stages, attachments}) makeTrappingMove "Bind" makeRechargeMove 'Blast Burn' extendMove 'Blizzard', -> @getAccuracy = (battle, user, target) -> return 0 if battle.hasWeather(Weather.HAIL) return @accuracy makeMeanLookMove 'Block' makePluckMove 'Bug Bite' extendMove 'Camouflage', -> @use = (battle, user, target) -> # Camouflage changes type based on terrain # In Wi-Fi battles, the terrain always results in Ground type. target.types = [ "Normal" ] battle.message "#{user.name} transformed into a Ground type!" extendMove 'Captivate', -> oldUse = @use @use = (battle, user, target) -> if (!(user.gender == 'M' && target.gender == 'F') && !(user.gender == 'F' && target.gender == 'M')) @fail(battle, user) else target.boost(specialAttack: -2, user) extendMove 'Charge', -> oldUse = @use @use = (battle, user, target) -> user.unattach(Attachment.Charge) # Charge can be used twice in a row user.attach(Attachment.Charge) battle.message "#{user.name} began charging power!" oldUse.call(this, battle, user, target) makeIgnoreStagesMove 'Chip Away' makeRandomSwitchMove "Circle Throw" makeTrappingMove "Clamp" extendMove 'Clear Smog', -> @afterSuccessfulHit = (battle, user, target, damage, isDirect) -> if isDirect target.resetBoosts() battle.cannedText('RESET_STATS', target) extendWithBoost 'Close Combat', 'self', defense: -1, specialDefense: -1 extendMove 'Conversion', -> @use = (battle, user, target) -> {types, moves} = target moves = _.without(moves, battle.getMove(@name)) # The original type of the move is used, not its generated type. moveTypes = moves.map((move) -> move.type) types = _.difference(moveTypes, types) type = battle.rng.choice(types, "conversion types") if !type? @fail(battle, user) return false battle.message "#{user.name} transformed into #{type} Type!" target.types = [ type ] extendMove 'Conversion 2', -> @use = (battle, user, target) -> {lastMove} = target if !lastMove? @fail(battle, user) return false moveType = lastMove.type possibles = [] for type, value of util.Type possibles.push(type) if util.typeEffectiveness(moveType, [ type ]) < 1 newType = battle.rng.choice(possibles, "conversion 2") battle.message "#{user.name} transformed into #{newType} Type!" user.types = [ newType ] makeThiefMove 'Covet' extendMove 'Defense Curl', -> oldUse = @use @use = (battle, user, target) -> oldUse.call(this, battle, user, target) target.attach(Attachment.DefenseCurl) extendMove 'Defog', -> @entryHazards = [ Attachment.Spikes Attachment.StealthRock Attachment.FireRock Attachment.ToxicSpikes Attachment.Livewire ] @selectPokemon = (battle, user, target) -> [ target ] @afterSuccessfulHit = (battle, user, target) -> target.boost(evasion: -1, user) target.team.unattach(Attachment.Reflect) target.team.unattach(Attachment.LightScreen) target.team.unattach(Attachment.Safeguard) for pokemon in @selectPokemon(battle, user, target) for hazard in @entryHazards pokemon.team.unattach(hazard) makeProtectMove 'Detect' extendWithBoost 'Draco Meteor', 'self', specialAttack: -2 makeRandomSwitchMove "Dragon Tail" extendMove 'Entrainment', -> bannedSourceAbilities = "Flower Gift": true "Forecast": true "Illusion": true "Imposter": true "Trace": true "Zen Mode": true @afterSuccessfulHit = (battle, user, target) -> if user.hasChangeableAbility() && user.ability.displayName not of bannedSourceAbilities && target.hasChangeableAbility() && target.ability.displayName != 'Truant' target.copyAbility(user.ability) battle.cannedText('ACQUIRE_ABILITY', target, user.ability) else @fail(battle, user) makeEruptionMove 'Eruption' makeExplosionMove 'Explosion' extendMove 'Fake Out', -> oldUse = @use @use = (battle, user, target) -> return false if oldUse.call(this, battle, user, target) == false if user.turnsActive > 1 @fail(battle, user) return false extendMove 'Feint', -> @afterSuccessfulHit = (battle, user, target) -> # TODO: Wide Guard if target.has(Attachment.Protect) target.unattach(Attachment.Protect) battle.message "#{target.name} fell for the feint!" makeTrappingMove "Fire Spin" makeOneHitKOMove 'Fissure' makeReversalMove 'Flail' extendMove 'Flame Wheel', -> @thawsUser = true extendMove 'Flare Blitz', -> @thawsUser = true extendMove 'Focus Energy', -> @use = (battle, user, target) -> if user.attach(Attachment.FocusEnergy) # TODO: Real message battle.message "#{user.name} began to focus!" else @fail(battle, user) false extendMove 'Focus Punch', -> @beforeTurn = (battle, user) -> battle.message "#{user.name} is tightening its focus!" user.attach(Attachment.FocusPunch) makeIdentifyMove("Foresight", ["Normal", "Fighting"]) makePickAttackMove 'Foul Play' makeRechargeMove 'Frenzy Plant' extendMove 'Fusion Flare', -> @thawsUser = true makeRechargeMove 'Giga Impact' makeWeightBased 'Grass Knot' extendMove 'Growth', -> @use = (battle, user, target) -> boost = if battle.hasWeather(Weather.SUN) then 2 else 1 user.boost(attack: boost, specialAttack: boost) extendMove 'Grudge', -> @execute = (battle, user, targets) -> user.attach(Attachment.Grudge) battle.message "#{user.name} wants its target to bear a grudge!" makeOneHitKOMove 'Guillotine' extendWithBoost 'Hammer Arm', 'self', speed: -1 makeStatusCureMove 'Heal Bell', 'A bell chimed!' makeRecoveryMove 'Heal Order' makeRecoveryMove 'Heal Pulse' makeWeightRatioBased 'Heat Crash' makeWeightRatioBased 'Heavy Slam' makeJumpKick 'Hi Jump Kick' makeOneHitKOMove 'Horn Drill' makeRechargeMove 'Hydro Cannon' makeRechargeMove 'Hyper Beam' makeMomentumMove 'Ice Ball' makeWeatherMove 'Hail', Weather.HAIL extendMove 'Hurricane', -> @getAccuracy = (battle, user, target) -> return 50 if battle.hasWeather(Weather.SUN) return 0 if battle.hasWeather(Weather.RAIN) return @accuracy makeJumpKick 'Jump Kick' extendWithBoost 'Leaf Storm', 'self', specialAttack: -2 extendMove 'Leech Seed', -> oldWillMiss = @willMiss @willMiss = (battle, user, target) -> if target.hasType("Grass") true else oldWillMiss.call(this, battle, user, target) makeLockOnMove 'Lock-On' makeWeightBased 'Low Kick' extendMove 'Lucky Chant', -> @execute = (battle, user, opponents) -> if user.team.attach(Attachment.LuckyChant) battle.message "The Lucky Chant shielded #{battle.getOwner(user)}'s " + "team from critical hits!" else @fail(battle, user) makeTrappingMove "Magma Storm" makeMeanLookMove 'Mean Look' makeRecoveryMove 'Milk Drink' makeLockOnMove 'Mind Reader' extendMove 'Minimize', -> @afterSuccessfulHit = (battle, user, target) -> target.attach(Attachment.Minimize) makeIdentifyMove("Miracle Eye", ["Psychic"]) extendMove 'Mirror Move', -> @hit = (battle, user, target) -> move = target.lastMove if !move? || !move.hasFlag("mirror") @fail(battle, user) return false battle.executeMove(move, user, battle.getTargets(move, user)) makeWeatherRecoveryMove 'Moonlight' makeWeatherRecoveryMove 'Morning Sun' makeLevelAsDamageMove 'Night Shade' makeIdentifyMove("Odor Sleuth", ["Normal", "Fighting"]) extendWithBoost 'Overheat', 'self', specialAttack: -2 makePluckMove 'Pluck' makeProtectMove 'Protect' extendWithBoost 'Psycho Boost', 'self', specialAttack: -2 makePickDefenseMove 'Psyshock' makePickDefenseMove 'Psystrike' makeBasePowerBoostMove 'Punishment', 60, 200, 'target' makeWeatherMove 'Rain Dance', Weather.RAIN makeRecoveryMove 'Recover' extendMove 'Refresh', -> @afterSuccessfulHit = (battle, user, target) -> if !target.cureStatus() @fail(battle, user) makeRevengeMove 'Revenge' makeReversalMove 'Reversal' makeRandomSwitchMove "Roar" makeRechargeMove 'Roar of Time' makeRechargeMove 'Rock Wrecker' makeMomentumMove 'Rollout' makeRecoveryMove 'Roost' extendMove 'Roost', -> @afterSuccessfulHit = (battle, user, target) -> user.attach(Attachment.Roost) extendMove 'Sacred Fire', -> @thawsUser = true makeIgnoreStagesMove 'Sacred Sword' makeWeatherMove 'Sandstorm', Weather.SAND makeTrappingMove "Sand Tomb" extendMove 'Scald', -> @thawsUser = true makePickDefenseMove 'Secret Sword' makeExplosionMove 'Selfdestruct' makeLevelAsDamageMove 'Seismic Toss' makeOneHitKOMove 'Sheer Cold' makeRecoveryMove 'Slack Off' makeRecoveryMove 'Softboiled' makeMeanLookMove 'Spider Web' extendMove 'Spit Up', -> oldUse = @use @use = (battle, user, target) -> if !user.has(Attachment.Stockpile) @fail(battle, user) return false oldUse.call(this, battle, user, target) @basePower = (battle, user, target) -> attachment = user.get(Attachment.Stockpile) layers = attachment?.layers || 0 100 * layers oldExecute = @execute @execute = (battle, user, targets) -> oldExecute.call(this, battle, user, targets) attachment = user.get(Attachment.Stockpile) return if !attachment? num = -attachment.layers user.unattach(Attachment.Stockpile) user.boost(defense: num, specialDefense: num) makeStompMove 'Steamroller' extendMove 'Stockpile', -> @afterSuccessfulHit = (battle, user, target) -> if user.attach(Attachment.Stockpile) user.boost(defense: 1, specialDefense: 1) else @fail(battle, user) makeStompMove 'Stomp' makeBasePowerBoostMove 'Stored Power', 20, 860, 'user' makeWeatherMove 'Sunny Day', Weather.SUN extendWithBoost 'Superpower', 'self', attack: -1, defense: -1 extendMove 'Swallow', -> oldUse = @use @use = (battle, user, target) -> if !user.has(Attachment.Stockpile) @fail(battle, user) return false oldUse.call(this, battle, user, target) @afterSuccessfulHit = (battle, user, target) -> {layers} = target.get(Attachment.Stockpile) amount = util.roundHalfDown(target.stat('hp') / Math.pow(2, 3 - layers)) # Swallow is not a draining move, so it is not affected by Big Root. target.heal(amount) oldExecute = @execute @execute = (battle, user, targets) -> oldExecute.call(this, battle, user, targets) for target in targets attachment = target.get(Attachment.Stockpile) return if !attachment? num = -attachment.layers target.unattach(Attachment.Stockpile) user.boost(defense: num, specialDefense: num) makeTrickMove 'Switcheroo' extendMove 'Super Fang', -> @calculateDamage = (battle, user, target) -> halfHP = Math.floor(target.currentHP / 2) Math.max(1, halfHP) makeWeatherRecoveryMove 'Synthesis' extendMove 'Teleport', -> @execute = (battle, user) -> @fail(battle, user) makeThiefMove 'Thief' extendMove 'Thunder', -> @getAccuracy = (battle, user, target) -> return 50 if battle.hasWeather(Weather.SUN) return 0 if battle.hasWeather(Weather.RAIN) return @accuracy extendMove 'Thunder Wave', -> @ignoresImmunities = -> false makeTrickMove 'Trick' extendWithBoost 'V-create', 'self', defense: -1, specialDefense: -1, speed: -1 makeEruptionMove 'Water Spout' makeTrappingMove "Whirlpool" makeRandomSwitchMove "Whirlwind" makeTrappingMove "Wrap" extendMove 'Assist', -> bannedMoves = "Assist": true "Bestow": true "Chatter": true "Circle Throw": true 'Copycat': true "Counter": true "Covet": true "Destiny Bond": true "Detect": true "Dragon Tail": true "Endure": true "Feint": true "Focus Punch": true "Follow Me": true "Helping Hand": true "Me First": true "Metronome": true "Mimic": true "Mirror Coat": true "Mirror Move": true "Nature Power": true "Protect": true "Rage Powder": true "Sketch": true "Sleep Talk": true "Snatch": true "Struggle": true "Switcheroo": true "Thief": true "Transform": true "Trick": true @execute = (battle, user) -> pokemon = _.without(user.team.pokemon, user) moves = _.flatten(pokemon.map((p) -> p.moves)) moves = moves.filter((move) -> move.name not of bannedMoves) if moves.length == 0 @fail(battle, user) else move = battle.rng.choice(moves, "assist") battle.executeMove(move, user, battle.getTargets(move, user)) extendMove 'Aqua Ring', -> @afterSuccessfulHit = (battle, user, target) -> target.attach(Attachment.AquaRing) battle.message "#{target.name} surrounded itself with a veil of water!" extendMove 'Assurance', -> @basePower = (battle, user, target) -> hit = user.lastHitBy if hit?.turn == battle.turn && !hit.move.isNonDamaging() 2 * @power else @power extendMove 'Autotomize', -> @afterSuccessfulHit = (battle, user, target) -> target.attach(Attachment.Autotomize) makeRevengeMove 'Avalanche' extendMove 'Acrobatics', -> @basePower = (battle, user, target) -> if !user.hasItem() 2 * @power else if user.hasItem("Flying Gem") 2 * @power else @power extendMove 'Acupressure', -> @use = (battle, user, target) -> stats = (stat for stat, num of target.stages when num < 6) if stats.length > 0 randomStat = battle.rng.choice(stats) hash = {} hash[randomStat] = 2 target.boost(hash) else @fail(battle, user) return false extendMove 'Beat Up', -> @calculateNumberOfHits = (battle, user, target) -> teamIndex = user.team.indexOf(user) numHits = 0 for p, i in user.team.pokemon if teamIndex == i || (!p.hasStatus() && !p.isFainted()) numHits++ numHits @basePower = (battle, user, target, hitNumber=1) -> index = -1 {team} = user teamIndex = team.indexOf(user) for x in [0...hitNumber] index++ while index != teamIndex && (team.at(index).hasStatus() || team.at(index).isFainted()) index++ 5 + Math.floor(team.at(index).baseStats.attack / 10) extendMove 'Belly Drum', -> @use = (battle, user, target) -> halfHP = Math.floor(user.stat('hp') / 2) if user.currentHP <= halfHP || !user.boost(attack: 12) @fail(battle, user) return false else user.damage(halfHP, source: "move") extendMove 'Brick Break', -> oldUse = @use @use = (battle, user, target) -> return false if oldUse.call(this, battle, user, target) == false target.team.unattach(Attachment.Reflect) target.team.unattach(Attachment.LightScreen) extendMove 'Brine', -> @basePower = (battle, user, target) -> if target.currentHP <= Math.floor(target.stat('hp') / 2) 2 * @power else @power extendMove 'Copycat', -> @execute = (battle, user, targets) -> move = battle.lastMove if move? && move != battle.getMove('Copycat') battle.executeMove(move, user, battle.getTargets(move, user)) else @fail(battle, user) makeCounterMove('Counter', 2, (move) -> move.isPhysical()) makeCounterMove('Mirror Coat', 2, (move) -> move.isSpecial()) makeCounterMove('Metal Burst', 1.5, (move) -> move.isPhysical() || move.isSpecial()) extendMove 'Crush Grip', -> @basePower = (battle, user, target) -> 1 + Math.floor(120 * target.currentHP / target.stat('hp')) extendMove 'Curse', -> @getTargets = (battle, user) -> pokemon = battle.getOpponents(user) [ battle.rng.choice(pokemon, "random opponent") ] @execute = (battle, user, targets) -> if !user.hasType("Ghost") user.boost(attack: 1, defense: 1, speed: -1) return user.damage(Math.floor(user.stat('hp') / 2), source: "move") for target in targets target.attach(Attachment.Curse) battle.message "#{user.name} cut its own HP and laid a curse on #{target.name}!" extendMove 'Destiny Bond', -> @hit = (battle, user) -> user.attach(Attachment.DestinyBond) makeDelayedAttackMove("Doom Desire", "$1 chose Doom Desire as its destiny!") extendMove 'Dragon Rage', -> @calculateDamage = (battle, user, target) -> 40 extendMove 'Dream Eater', -> oldUse = @use @use = (battle, user, target) -> if !target.has(Status.Sleep) @fail(battle, user) return false oldUse.call(this, battle, user, target) extendMove 'Echoed Voice', -> @basePower = (battle, user, target) -> layers = battle.get(Attachment.EchoedVoice)?.layers || 0 @power * (layers + 1) @executing = (battle, user, targets) -> battle.attach(Attachment.EchoedVoice) attachment = battle.get(Attachment.EchoedVoice) attachment.turns = 2 extendMove 'Electro Ball', -> @basePower = (battle, user, target) -> ratio = user.stat('speed') / target.stat('speed') power = 0 if ratio >= 4 power = 150 else if ratio >= 3 power = 120 else if ratio >= 2 power = 80 else if ratio >= 1 power = 60 else power = 40 extendMove 'Encore', -> bannedMoves = 'Encore': true 'Mimic': true 'Mirror Move': true 'Sketch': true 'Struggle': true 'Transform': true @afterSuccessfulHit = (battle, user, target) -> if !target.lastMove? @fail(battle, user) else if target.lastMove.name of bannedMoves @fail(battle, user) else if target.pp(target.lastMove) == 0 @fail(battle, user) else if target.attach(Attachment.Encore) if battle.willMove(target) battle.changeMove(target, target.lastMove) else @fail(battle, user) extendMove 'Endeavor', -> oldUse = @use @use = (battle, user, target) -> return false if oldUse.call(this, battle, user, target) == false if target.currentHP < user.currentHP @fail(battle, user) return false @calculateDamage = (battle, user, target) -> target.currentHP - user.currentHP makeProtectCounterMove 'Endure', (battle, user, targets) -> battle.message "#{user.name} braced itself!" user.attach(Attachment.Endure) extendMove 'Facade', -> @basePower = (battle, user, target) -> if user.hasStatus() 2 * @power else @power extendMove 'False Swipe', -> @oldCalculateDamage = @calculateDamage @calculateDamage = (battle, user, target, hitNumber, isDirect) -> damage = @oldCalculateDamage(battle, user, target, hitNumber, isDirect) if damage >= target.currentHP && isDirect damage = target.currentHP - 1 damage extendMove 'Final Gambit', -> @afterSuccessfulHit = (battle, user, target) -> user.faint() @calculateDamage = (battle, user, target) -> user.currentHP extendMove 'Flatter', -> @afterSuccessfulHit = (battle, user, target) -> target.attach(Attachment.Confusion, {battle}) target.boost(specialAttack: 1, user) extendMove 'Fling', -> @beforeTurn = (battle, user) -> user.attach(Attachment.Fling) oldUse = @use @use = (battle, user, target) -> fling = user.get(Attachment.Fling) if !fling?.item @fail(battle, user) return false oldUse.call(this, battle, user, target) @afterSuccessfulHit = (battle, user, target, damage, isDirect) -> return unless isDirect {item} = user.get(Attachment.Fling) switch item.displayName when "Poison Barb" target.attach(Status.Poison) when "Light Ball" target.attach(Status.Paralyze) when "Flame Orb" target.attach(Status.Burn) when "Toxic Orb" target.attach(Status.Toxic) when "King's Rock", "Razor Fang" target.attach(Attachment.Flinch) when "Mental Herb", "White Herb" item.activate(battle, target) else item.eat(battle, target) if item.type == "berries" @basePower = (battle, user, target) -> fling = user.get(Attachment.Fling) fling.item.flingPower extendMove 'Frustration', -> @basePower = -> 102 extendMove 'Fury Cutter', -> @afterSuccessfulHit = (battle, user, target) -> user.attach(Attachment.FuryCutter, move: this) @basePower = (battle, user, target) -> attachment = user.get(Attachment.FuryCutter) layers = attachment?.layers || 0 @power * Math.pow(2, layers) makeDelayedAttackMove("Future Sight", "$1 foresaw an attack!") extendMove 'Gastro Acid', -> @use = (battle, user, target) -> if !target.attach(Attachment.AbilitySuppress) @fail(battle, user) return false battle.message "#{target.name}'s ability was suppressed!" extendMove 'Gravity', -> @execute = (battle, user, targets) -> if !battle.attach(Attachment.Gravity) @fail(battle, user) return battle.message "Gravity intensified!" for target in targets target.attach(Attachment.GravityPokemon) target.unattach(Attachment.MagnetRise) target.unattach(Attachment.Telekinesis) charging = target.get(Attachment.Charging) target.unattach(Attachment.Charging) if charging?.move.hasFlag("gravity") extendMove 'Guard Swap', -> @afterSuccessfulHit = (battle, user, target) -> triggered = false userBoosts = {} targetBoosts = {} for stat in [ 'defense', 'specialDefense' ] userBoosts[stat] = target.stages[stat] targetBoosts[stat] = user.stages[stat] triggered = true if target.stages[stat] != 0 || user.stages[stat] != 0 if triggered user.setBoosts(userBoosts) target.setBoosts(targetBoosts) extendMove 'Gyro Ball', -> @basePower = (battle, user, target) -> power = 1 + Math.floor(25 * target.stat('speed') / user.stat('speed')) Math.min(150, power) extendMove 'Haze', -> @execute = (battle, user, targets) -> user.resetBoosts() for target in targets target.resetBoosts() battle.cannedText('RESET_ALL_STATS') extendMove 'Heal Block', -> @afterSuccessfulHit = (battle, user, target) -> if !target.attach(Attachment.HealBlock) battle.cannedText('HEAL_BLOCK_FAIL', target) extendMove 'Heart Swap', -> @afterSuccessfulHit = (battle, user, target) -> [user.stages, target.stages] = [target.stages, user.stages] battle.message "#{user.name} switched stat changes with the target!" extendMove 'Hex', -> @basePower = (battle, user, target) -> if target.hasStatus() 2 * @power else @power extendMove 'Hidden Power', -> @basePower = (battle, user, target) -> ivs = hp: user.iv('hp') attack: user.iv('attack') defense: user.iv('defense') speed: user.iv('speed') specialAttack: user.iv('specialAttack') specialDefense: user.iv('specialDefense') HiddenPower.BW.basePower(ivs) @getType = (battle, user, target) -> ivs = hp: user.iv('hp') attack: user.iv('attack') defense: user.iv('defense') speed: user.iv('speed') specialAttack: user.iv('specialAttack') specialDefense: user.iv('specialDefense') HiddenPower.BW.type(ivs) extendMove 'Imprison', -> @hit = (battle, user, target) -> {moves} = target if target.attach(Attachment.Imprison, {battle, moves}) battle.message "#{target.name} sealed the opponent's moves!" else @fail(battle, user) extendMove 'Incinerate', -> @afterSuccessfulHit = (battle, user, target) -> if target.hasItem() && target.getItem().type == 'berries' battle.message "#{target.name}'s #{target.getItem().name} was burnt up!" target.removeItem() extendMove 'Judgment', -> @getType = (battle, user, target) -> user.getItem()?.plate || @type extendMove 'Knock Off', -> @afterSuccessfulHit = (battle, user, target, damage, isDirect) -> if user.isAlive() && target.hasItem() && target.canLoseItem() && isDirect battle.cannedText('KNOCK_OFF', user, target, target.getItem()) target.removeItem() extendMove 'Last Resort', -> oldUse = @use @use = (battle, user, target) -> if this not in user.moves || user.moves.length <= 1 @fail(battle, user) return false for moveName in _.without(user.moves, this).map((m) -> m.name) if moveName not of user.used @fail(battle, user) return false oldUse.call(this, battle, user, target) extendMove 'Light Screen', -> @execute = (battle, user, opponents) -> if !user.team.attach(Attachment.LightScreen, {user}) @fail(battle, user) extendMove 'Healing Wish', -> @afterSuccessfulHit = (battle, user, target) -> if target.team.getAliveBenchedPokemon().length > 0 target.faint() target.team.attach(Attachment.HealingWish) else @fail(battle, user) extendMove 'Lunar Dance', -> @afterSuccessfulHit = (battle, user, target) -> if target.team.getAliveBenchedPokemon().length > 0 target.faint() target.team.attach(Attachment.LunarDance) else @fail(battle, user) extendMove 'Magic Coat', -> @afterSuccessfulHit = (battle, user, target) -> target.attach(Attachment.MagicCoat) target.team.attach(Attachment.MagicCoat) extendMove 'Magnet Rise', -> @use = (battle, user, target) -> if target.attach(Attachment.MagnetRise) battle.message "#{target.name} is now floating in the air!" else @fail(battle, user) return false extendMove 'Magnitude', -> lastUse = {turn: 0, user: null} @basePower = (battle, user, target) -> if !(battle.turn == lastUse.turn && user == lastUse.user) rand = battle.rng.randInt(0, 99, "magnitude") magnitude = 0 power = 0 if rand < 5 power = 10 magnitude = 4 else if rand < 15 power = 30 magnitude = 5 else if rand < 35 power = 50 magnitude = 6 else if rand < 65 power = 70 magnitude = 7 else if rand < 85 power = 90 magnitude = 8 else if rand < 95 power = 110 magnitude = 9 else power = 150 magnitude = 10 lastUse.turn = battle.turn lastUse.user = user battle.message "Magnitude #{magnitude}!" power extendMove 'Me First', -> bannedMoves = { "Chatter" : true "Counter" : true "Covet" : true "Focus Punch": true "Me First" : true "Metal Burst": true "Mirror Coat": true "Struggle" : true "Thief" : true } @execute = (battle, user, targets) -> target = targets[0] # Me First is a single-target move m = battle.peekMove(target) if !battle.willMove(target) || m.isNonDamaging() || bannedMoves[m.name] @fail(battle, user) return false user.attach(Attachment.MeFirst) battle.executeMove(m, user, targets) extendMove 'Memento', -> @afterSuccessfulHit = (battle, user, target) -> target.boost(attack: -2, specialAttack: -2, user) user.faint() extendMove 'Metronome', -> @impossibleMoves = [ "After You" "Assist" "Bestow" "Chatter" "Copycat" "Counter" "Covet" "Destiny Bond" "Detect" "Endure" "Feint" "Focus Punch" "Follow Me" "Freeze Shock" "Helping Hand" "Ice Burn" "Me First" "Metronome" "Mimic" "Mirror Coat" "Mirror Move" "Nature Power" "Protect" "Quash" "Quick Guard" "Rage Powder" "Relic Song" "Secret Sword" "Sketch" "Sleep Talk" "Snarl" "Snatch" "Snore" "Struggle" "Switcheroo" "Techno Blast" "Thief" "Transform" "Trick" "V-create" "Wide Guard" ] for move in @impossibleMoves if move not of Moves throw new Error("The illegal Metronome move '#{move}' does not exist.") @execute = (battle, user, targets) -> index = battle.rng.randInt(0, MoveList.length - 1, "metronome") while MoveList[index].name in @impossibleMoves index = battle.rng.randInt(0, MoveList.length - 1, "metronome reselect") move = MoveList[index] battle.message "Waggling a finger let it use #{move.name}!" # Determine new targets targets = battle.getTargets(move, user) battle.executeMove(move, user, targets) extendMove 'Natural Gift', -> oldUse = @use @use = (battle, user, target) -> if !user.hasItem() || user.isItemBlocked() || !user.getItem().naturalGift @fail(battle, user) return false oldUse.call(this, battle, user, target) @basePower = (battle, user, target) -> item = user.getItem() item.naturalGift.power @getType = (battle, user, target) -> item = user.getItem() item.naturalGift.type @afterSuccessfulHit = (battle, user, target) -> user.removeItem() extendMove 'Nature Power', -> @execute = (battle, user, targets) -> # In Wi-Fi battles, Earthquake is always chosen. battle.message "#{@name} turned into Earthquake!" earthquake = battle.getMove('Earthquake') battle.executeMove(earthquake, user, targets) @getTargets = (battle, user) -> earthquake = battle.getMove('Earthquake') battle.getTargets(earthquake, user) extendMove 'Pain Split', -> @afterSuccessfulHit = (battle, user, target) -> averageHP = Math.floor((user.currentHP + target.currentHP) / 2) user.setHP(averageHP) target.setHP(averageHP) battle.cannedText('PAIN_SPLIT') extendMove 'Pay Day', -> @afterSuccessfulHit = (battle, user, target) -> battle.cannedText('PAY_DAY') extendMove 'Payback', -> @basePower = (battle, user, target) -> if !target.lastMove? || battle.willMove(target) @power else 2 * @power extendMove 'Power Swap', -> @afterSuccessfulHit = (battle, user, target) -> triggered = false userBoosts = {} targetBoosts = {} for stat in [ 'attack', 'specialAttack' ] userBoosts[stat] = target.stages[stat] targetBoosts[stat] = user.stages[stat] triggered = true if target.stages[stat] != 0 || user.stages[stat] != 0 if triggered user.setBoosts(userBoosts) target.setBoosts(targetBoosts) extendMove 'Present', -> @basePower = (battle, user, target) -> user.get(Attachment.Present).power @afterSuccessfulHit = (battle, user, target) -> if user.get(Attachment.Present).power == 0 amount = target.stat('hp') >> 2 target.heal(amount) oldExecute = @execute @execute = (battle, user, targets) -> chance = battle.rng.next("present") power = if chance < .1 120 else if chance < .3 0 else if chance < .6 80 else 40 user.attach(Attachment.Present, {power}) oldExecute.call(this, battle, user, targets) extendMove 'Psywave', -> @calculateDamage = (battle, user, target) -> fraction = battle.rng.randInt(5, 15, "psywave") / 10 Math.floor(user.level * fraction) extendMove 'Psych Up', -> @use = (battle, user, target) -> boosts = {} for stage, value of target.stages boosts[stage] = value user.setBoosts(boosts) battle.cannedText('PSYCH_UP', user, target) extendMove 'Psycho Shift', -> @afterSuccessfulHit = (battle, user, target) -> if !user.hasStatus() || target.hasStatus() @fail(battle, user) return false status = user.status user.cureStatus() target.attach(status) extendMove 'Pursuit', -> @beforeTurn = (battle, user) -> user.attach(Attachment.Pursuit) @basePower = (battle, user, target) -> if user.has(Attachment.PursuitModifiers) 2 * @power else @power extendMove 'Rage', -> @afterSuccessfulHit = (battle, user, target) -> user.attach(Attachment.Rage) extendMove 'Rapid Spin', -> @entryHazards = [ Attachment.Spikes, Attachment.StealthRock, Attachment.FireRock, Attachment.ToxicSpikes, Attachment.Livewire ] @afterSuccessfulHit = (battle, user) -> # Do not remove anything if the user is fainted. if user.isFainted() return team = user.team for hazard in @entryHazards team.unattach(hazard) # Remove trapping moves like fire-spin user.unattach(Attachment.Trap) # Remove leech seed user.unattach(Attachment.LeechSeed) extendMove 'Recycle', -> @afterSuccessfulHit = (battle, user, target) -> if !target.hasItem() && target.lastItem battle.cannedText('FOUND_ITEM', target, target.lastItem) target.setItem(target.lastItem, clearLastItem: true) else @fail(battle, user) extendMove 'Reflect', -> @execute = (battle, user, opponents) -> if !user.team.attach(Attachment.Reflect, {user}) @fail(battle, user) extendMove 'Reflect Type', -> @use = (battle, user, target) -> if user.hasAbility("Multitype") @fail(battle, user) return false user.types = target.types battle.message "#{user.name}'s type changed to match #{target.name}'s!" extendMove 'Relic Song', -> @afterSuccessfulHit = (battle, user, target) -> return if user.name != 'Meloetta' if user.isInForme("default") user.changeForme("pirouette") else user.changeForme("default") battle.cannedText('TRANSFORM', user) extendMove 'Rest', -> @hit = (battle, user, target) -> if user.currentHP >= user.stat('hp') || user.has(Status.Sleep) || !user.attach(Status.Sleep, turns: 2, force: true) @fail(battle, user) return user.setHP(user.stat('hp')) extendMove 'Retaliate', -> @basePower = (battle, user, target) -> if user.team.faintedLastTurn 140 else 70 extendMove 'Return', -> @basePower = -> 102 extendMove 'Role Play', -> bannedAbilities = "Flower Gift": true "Forecast": true "Illusion": true 'Imposter': true "Trace": true "Wonder Guard": true "Zen Mode": true @afterSuccessfulHit = (battle, user, target) -> if user.hasChangeableAbility() && target.hasChangeableAbility() && target.ability.displayName not of bannedAbilities && user.ability != target.ability user.copyAbility(target.ability) battle.message "#{user.name} copied #{target.name}'s #{target.ability.displayName}!" else @fail(battle, user) extendMove 'Safeguard', -> @execute = (battle, user, target) -> team = user.team if !team.attach(Attachment.Safeguard, {source: user}) @fail(battle) return false battle.cannedText('SAFEGUARD_START', user) extendMove 'Simple Beam', -> bannedAbilities = "Simple": true "Truant": true @afterSuccessfulHit = (battle, user, target) -> if target.hasChangeableAbility() && target.ability.displayName not of bannedAbilities target.copyAbility(Ability.Simple) battle.cannedText('ACQUIRE_ABILITY', target, 'Simple') else @fail(battle, user) extendMove 'Skill Swap', -> bannedAbilities = "Illusion": true "Wonder Guard": true @canSwapSameAbilities = false @afterSuccessfulHit = (battle, user, target) -> if user.hasChangeableAbility() && target.hasChangeableAbility() && user.ability.displayName not of bannedAbilities && target.ability.displayName not of bannedAbilities && (user.ability != target.ability || @canSwapSameAbilities) user.swapAbilityWith(target) else @fail(battle) extendMove 'Sleep Talk', -> bannedMoves = [ "Assist" "Bide" "Chatter" "Copycat" "Focus Punch" "Me First" "Metronome" "Mimic" "Mirror Move" "Nature Power" "Sketch" "Sleep Talk" "Uproar" ] @usableWhileAsleep = true @execute = (battle, user) -> viableMoves = user.moves.filter((move) -> move.name not in bannedMoves) if viableMoves.length == 0 || !user.has(Status.Sleep) @fail(battle, user) return moveIndex = battle.rng.randInt(0, viableMoves.length - 1, "sleep talk") move = viableMoves[moveIndex] battle.executeMove(move, user, battle.getTargets(move, user)) extendMove 'Smack Down', -> @afterSuccessfulHit = (battle, user, target) -> target.attach(Attachment.SmackDown) target.unattach(Attachment.MagnetRise) target.unattach(Attachment.Telekinesis) # Smack Down will miss on charge moves it cannot hit. target.unattach(Attachment.Charging) makeStatusCureAttackMove 'SmellingSalt', Status.Paralyze extendMove 'Snore', -> @usableWhileAsleep = true oldUse = @use @use = (battle, user, target) -> if !user.has(Status.Sleep) @fail(battle, user) return false else oldUse.call(this, battle, user, target) extendMove 'Soak', -> @afterSuccessfulHit = (battle, user, target) -> if (target.types.length == 1 && target.types[0] == 'Water') || target.name == 'Arceus' @fail(battle, user) else target.types = [ 'Water' ] battle.cannedText('TRANSFORM_TYPE', target, 'Water') extendMove 'SonicBoom', -> @calculateDamage = (battle, user, target) -> 20 makeOpponentFieldMove 'Spikes', (battle, user, opponentId) -> team = battle.getTeam(opponentId) if !team.attach(Attachment.Spikes) @fail(battle, user) extendMove 'Spite', -> @execute = (battle, user, opponents) -> for opponent in opponents move = opponent.lastMove if !move || !opponent.knows(move) || opponent.pp(move) == 0 @fail(battle, user) return opponent.reducePP(move, 4) battle.message "It reduced the PP of #{opponent.name}!" makeOpponentFieldMove 'Stealth Rock', (battle, user, opponentId) -> team = battle.getTeam(opponentId) if user.hasAbility('Foundry') if !team.attach(Attachment.FireRock) @fail(battle, user) else if !team.attach(Attachment.StealthRock) @fail(battle, user) extendMove 'Struggle', -> @type = '???' @typeEffectiveness = -> 1 @afterSuccessfulHit = (battle, user, target) -> user.damage(user.stat('hp') >> 2, source: "move") extendMove 'Splash', -> @execute = (battle, user, target) -> battle.message "But nothing happened!" extendMove 'Substitute', -> @execute = (battle, user, targets) -> dmg = user.stat('hp') >> 2 if dmg >= user.currentHP || dmg == 0 battle.cannedText('SUBSTITUTE_WEAK') @fail(battle, user) return if user.has(Attachment.Substitute) battle.cannedText('SUBSTITUTE_EXISTS', user) @fail(battle, user) return user.damage(dmg, source: "move") user.attach(Attachment.Substitute, hp: dmg, battle: battle) battle.cannedText('SUBSTITUTE_START', user) @fail = (battle) -> extendMove 'Sucker Punch', -> oldUse = @use @use = (battle, user, target) -> if !battle.willMove(target) || battle.peekMove(target).isNonDamaging() @fail(battle, user) return false else oldUse.call(this, battle, user, target) extendMove 'Swagger', -> @afterSuccessfulHit = (battle, user, target) -> target.attach(Attachment.Confusion, {battle}) target.boost(attack: 2, user) extendMove 'Synchronoise', -> oldUse = @use @use = (battle, user, target) -> if _.every(user.types, (type) -> type not in target.types) @fail(battle, user) return false return oldUse.call(this, battle, user, target) extendMove 'Tailwind', -> @execute = (battle, user, targets) -> team = user.team if team.has(Attachment.Tailwind) @fail(battle, user) return false team.attach(Attachment.Tailwind) battle.message "A tailwind started blowing!" extendMove 'Taunt', -> @afterSuccessfulHit = (battle, user, target) -> if !target.attach(Attachment.Taunt, battle) @fail(battle, user) extendMove 'Techno Blast', -> @getType = (battle, user, target) -> switch user.getItem()?.displayName when "Burn Drive" "Fire" when "Chill Drive" "Ice" when "Douse Drive" "Water" when "Shock Drive" "Electric" else "Normal" makeOpponentFieldMove 'Toxic Spikes', (battle, user, opponentId) -> team = battle.getTeam(opponentId) if !team.attach(Attachment.ToxicSpikes) @fail(battle, user) extendMove 'Transform', -> @afterSuccessfulHit = (battle, user, target) -> if !user.attach(Attachment.Transform, {target}) @fail(battle, user) return false battle.cannedText('TRANSFORM_INTO', user, target) extendMove 'Trick Room', -> @execute = (battle, user, targets) -> if battle.attach(Attachment.TrickRoom) battle.cannedText('TRICK_ROOM_START', user) else battle.unattach(Attachment.TrickRoom) extendMove 'Trump Card', -> @basePower = (battle, user, target) -> switch user.pp(this) when 3 50 when 2 60 when 1 80 when 0 200 else 40 makeSwitchMove = (moveName) -> extendMove moveName, -> @afterSuccessfulHit = (battle, user, target) -> battle.forceSwitch(user) makeSwitchMove 'U-turn' extendMove 'Venoshock', -> @basePower = (battle, user, target) -> if target.has(Status.Toxic) || target.has(Status.Poison) 2 * @power else @power makeSwitchMove 'Volt Switch' makeStatusCureAttackMove 'Wake-Up Slap', Status.Sleep extendMove 'Weather Ball', -> @getType = (battle, user, target) -> if battle.hasWeather(Weather.SUN) then 'Fire' else if battle.hasWeather(Weather.RAIN) then 'Water' else if battle.hasWeather(Weather.HAIL) then 'Ice' else if battle.hasWeather(Weather.SAND) then 'Rock' else 'Normal' @basePower = (battle, user, target) -> if battle.hasWeather(Weather.NONE) then 50 else 100 extendMove 'Wish', -> @hit = (battle, user) -> @fail(battle, user) unless user.team.attach(Attachment.Wish, {user}) extendMove 'Worry Seed', -> bannedAbilities = "Insomnia": true "Truant": true @afterSuccessfulHit = (battle, user, target) -> if target.hasChangeableAbility() && target.ability.displayName not of bannedAbilities target.copyAbility(Ability.Insomnia) battle.cannedText('ACQUIRE_ABILITY', target, 'Insomnia') else @fail(battle, user) extendMove 'Wring Out', -> @basePower = (battle, user, target) -> power = Math.floor(120 * user.currentHP / user.stat('hp')) Math.max(1, power) # Keep this at the bottom or look up how it affects Metronome. # TODO: Figure out a better solution Moves['Confusion Recoil'] = new Move "Confusion recoil", "accuracy": 0, "damage": "physical", "power": 40, "priority": 0, "type": "???" # Confusion never crits extendMove 'Confusion Recoil', -> @isCriticalHit = -> false Moves['Recharge'] = new Move("Recharge", target: "user") # After everything to ensure that basePower is overridden last. makeVulnerable = (moveName, byMove) -> extendMove byMove, -> oldBasePower = @basePower @basePower = (battle, user, target) -> power = oldBasePower.call(this, battle, user, target) charging = target.get(Attachment.Charging) return power if !charging? if charging.move == battle.getMove(moveName) then 2 * power else power makeVulnerable('Fly', 'Gust') makeVulnerable('Fly', 'Twister') makeVulnerable('Bounce', 'Gust') makeVulnerable('Bounce', 'Twister') makeVulnerable('Dig', 'Earthquake') makeVulnerable('Dig', 'Magnitude') makeVulnerable('Dive', 'Surf') makeVulnerable('Dive', 'Whirlpool') extendMove 'Hone Claws', -> @use = (battle, user, target) -> boost = if battle.hasWeather(Weather.MOON) then 2 else 1 user.boost(attack: boost, accuracy: boost)