class Battle::Battler #============================================================================= # Decide whether the trainer is allowed to tell the Pokémon to use the given # move. Called when choosing a command for the round. # Also called when processing the Pokémon's action, because these effects also # prevent Pokémon action. Relevant because these effects can become active # earlier in the same round (after choosing the command but before using the # move) or an unusable move may be called by another move such as Metronome. #============================================================================= def pbCanChooseMove?(move, commandPhase, showMessages = true, specialUsage = false) # Disable if @effects[PBEffects::DisableMove] == move.id && !specialUsage if showMessages msg = _INTL("{1}'s {2} is disabled!", pbThis, move.name) (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) end return false end # Heal Block if @effects[PBEffects::HealBlock] > 0 && move.healingMove? if showMessages msg = _INTL("{1} can't use {2} because of Heal Block!", pbThis, move.name) (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) end return false end # Gravity if @battle.field.effects[PBEffects::Gravity] > 0 && move.unusableInGravity? if showMessages msg = _INTL("{1} can't use {2} because of gravity!", pbThis, move.name) (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) end return false end # Throat Chop if @effects[PBEffects::ThroatChop] > 0 && move.soundMove? if showMessages msg = _INTL("{1} can't use {2} because of Throat Chop!", pbThis, move.name) (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) end return false end # Choice Band/Gorilla Tactics @effects[PBEffects::ChoiceBand] = nil if !pbHasMove?(@effects[PBEffects::ChoiceBand]) if @effects[PBEffects::ChoiceBand] && move.id != @effects[PBEffects::ChoiceBand] choiced_move = GameData::Move.try_get(@effects[PBEffects::ChoiceBand]) if choiced_move if hasActiveItem?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) if showMessages msg = _INTL("The {1} only allows the use of {2}!", itemName, choiced_move.name) (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) end return false elsif hasActiveAbility?(:GORILLATACTICS) if showMessages msg = _INTL("{1} can only use {2}!", pbThis, choiced_move.name) (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) end return false end end end # Taunt if @effects[PBEffects::Taunt] > 0 && move.statusMove? && !specialUsage if showMessages msg = _INTL("{1} can't use {2} after the taunt!", pbThis, move.name) (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) end return false end # Torment if @effects[PBEffects::Torment] && !@effects[PBEffects::Instructed] && !specialUsage && @lastMoveUsed && move.id == @lastMoveUsed && move.id != @battle.struggle.id if showMessages msg = _INTL("{1} can't use the same move twice in a row due to the torment!", pbThis) (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) end return false end # Imprison if @battle.allOtherSideBattlers(@index).any? { |b| b.effects[PBEffects::Imprison] && b.pbHasMove?(move.id) } if showMessages msg = _INTL("{1} can't use its sealed {2}!", pbThis, move.name) (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) end return false end # Assault Vest (prevents choosing status moves but doesn't prevent # executing them) if hasActiveItem?(:ASSAULTVEST) && move.statusMove? && move.function_code != "UseMoveTargetIsAboutToUse" && commandPhase if showMessages msg = _INTL("The effects of the {1} prevent status moves from being used!", itemName) (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) end return false end # Belch return false if !move.pbCanChooseMove?(self, commandPhase, showMessages) return true end #============================================================================= # Obedience check #============================================================================= # Return true if Pokémon continues attacking (although it may have chosen to # use a different move in disobedience), or false if attack stops. def pbObedienceCheck?(choice) return true if usingMultiTurnAttack? return true if choice[0] != :UseMove return true if !@battle.internalBattle return true if !@battle.pbOwnedByPlayer?(@index) disobedient = false # Pokémon may be disobedient; calculate if it is badge_level = 10 * (@battle.pbPlayer.badge_count + 1) badge_level = GameData::GrowthRate.max_level if @battle.pbPlayer.badge_count >= 8 if Settings::ANY_HIGH_LEVEL_POKEMON_CAN_DISOBEY || (Settings::FOREIGN_HIGH_LEVEL_POKEMON_CAN_DISOBEY && @pokemon.foreign?(@battle.pbPlayer)) if @level > badge_level a = ((@level + badge_level) * @battle.pbRandom(256) / 256).floor disobedient |= (a >= badge_level) end end disobedient |= !pbHyperModeObedience(choice[2]) return true if !disobedient # Pokémon is disobedient; make it do something else return pbDisobey(choice, badge_level) end def pbDisobey(choice, badge_level) move = choice[2] PBDebug.log("[Disobedience] #{pbThis} disobeyed") @effects[PBEffects::Rage] = false # Do nothing if using Snore/Sleep Talk if @status == :SLEEP && move.usableWhenAsleep? @battle.pbDisplay(_INTL("{1} ignored orders and kept sleeping!", pbThis)) return false end b = ((@level + badge_level) * @battle.pbRandom(256) / 256).floor # Use another move if b < badge_level @battle.pbDisplay(_INTL("{1} ignored orders!", pbThis)) return false if !@battle.pbCanShowFightMenu?(@index) otherMoves = [] eachMoveWithIndex do |_m, i| next if i == choice[1] otherMoves.push(i) if @battle.pbCanChooseMove?(@index, i, false) end return false if otherMoves.length == 0 # No other move to use; do nothing newChoice = otherMoves[@battle.pbRandom(otherMoves.length)] choice[1] = newChoice choice[2] = @moves[newChoice] choice[3] = -1 return true end c = @level - badge_level r = @battle.pbRandom(256) # Fall asleep if r < c && pbCanSleep?(self, false) pbSleepSelf(_INTL("{1} began to nap!", pbThis)) return false end # Hurt self in confusion r -= c if r < c && @status != :SLEEP pbConfusionDamage(_INTL("{1} won't obey! It hurt itself in its confusion!", pbThis)) return false end # Show refusal message and do nothing case @battle.pbRandom(4) when 0 then @battle.pbDisplay(_INTL("{1} won't obey!", pbThis)) when 1 then @battle.pbDisplay(_INTL("{1} turned away!", pbThis)) when 2 then @battle.pbDisplay(_INTL("{1} is loafing around!", pbThis)) when 3 then @battle.pbDisplay(_INTL("{1} pretended not to notice!", pbThis)) end return false end #============================================================================= # Check whether the user (self) is able to take action at all. # If this returns true, and if PP isn't a problem, the move will be considered # to have been used (even if it then fails for whatever reason). #============================================================================= def pbTryUseMove(choice, move, specialUsage, skipAccuracyCheck) # Check whether it's possible for self to use the given move # NOTE: Encore has already changed the move being used, no need to have a # check for it here. if !pbCanChooseMove?(move, false, true, specialUsage) @lastMoveFailed = true return false end # Check whether it's possible for self to do anything at all if @effects[PBEffects::SkyDrop] >= 0 # Intentionally no message here PBDebug.log("[Move failed] #{pbThis} can't use #{move.name} because of being Sky Dropped") return false end if @effects[PBEffects::HyperBeam] > 0 # Intentionally before Truant PBDebug.log("[Move failed] #{pbThis} is recharging after using #{move.name}") @battle.pbDisplay(_INTL("{1} must recharge!", pbThis)) @effects[PBEffects::Truant] = !@effects[PBEffects::Truant] if hasActiveAbility?(:TRUANT) return false end if choice[1] == -2 # Battle Palace PBDebug.log("[Move failed] #{pbThis} can't act in the Battle Palace somehow") @battle.pbDisplay(_INTL("{1} appears incapable of using its power!", pbThis)) return false end # Skip checking all applied effects that could make self fail doing something return true if skipAccuracyCheck # Check status problems and continue their effects/cure them case @status when :SLEEP self.statusCount -= 1 if @statusCount <= 0 pbCureStatus else pbContinueStatus if !move.usableWhenAsleep? # Snore/Sleep Talk PBDebug.log("[Move failed] #{pbThis} is asleep") @lastMoveFailed = true return false end end when :FROZEN if !move.thawsUser? if @battle.pbRandom(100) < 20 pbCureStatus else pbContinueStatus PBDebug.log("[Move failed] #{pbThis} is frozen") @lastMoveFailed = true return false end end end # Obedience check return false if !pbObedienceCheck?(choice) # Truant if hasActiveAbility?(:TRUANT) @effects[PBEffects::Truant] = !@effects[PBEffects::Truant] if !@effects[PBEffects::Truant] # True means loafing, but was just inverted @battle.pbShowAbilitySplash(self) @battle.pbDisplay(_INTL("{1} is loafing around!", pbThis)) @lastMoveFailed = true @battle.pbHideAbilitySplash(self) PBDebug.log("[Move failed] #{pbThis} can't act because of #{abilityName}") return false end end # Flinching if @effects[PBEffects::Flinch] @battle.pbDisplay(_INTL("{1} flinched and couldn't move!", pbThis)) PBDebug.log("[Move failed] #{pbThis} flinched") if abilityActive? Battle::AbilityEffects.triggerOnFlinch(self.ability, self, @battle) end @lastMoveFailed = true return false end # Confusion if @effects[PBEffects::Confusion] > 0 @effects[PBEffects::Confusion] -= 1 if @effects[PBEffects::Confusion] <= 0 pbCureConfusion @battle.pbDisplay(_INTL("{1} snapped out of its confusion.", pbThis)) else @battle.pbCommonAnimation("Confusion", self) @battle.pbDisplay(_INTL("{1} is confused!", pbThis)) threshold = (Settings::MECHANICS_GENERATION >= 7) ? 33 : 50 # % chance if @battle.pbRandom(100) < threshold pbConfusionDamage(_INTL("It hurt itself in its confusion!")) PBDebug.log("[Move failed] #{pbThis} hurt itself in its confusion") @lastMoveFailed = true return false end end end # Paralysis if @status == :PARALYSIS && @battle.pbRandom(100) < 25 pbContinueStatus PBDebug.log("[Move failed] #{pbThis} is paralyzed") @lastMoveFailed = true return false end # Infatuation if @effects[PBEffects::Attract] >= 0 @battle.pbCommonAnimation("Attract", self) @battle.pbDisplay(_INTL("{1} is in love with {2}!", pbThis, @battle.battlers[@effects[PBEffects::Attract]].pbThis(true))) if @battle.pbRandom(100) < 50 @battle.pbDisplay(_INTL("{1} is immobilized by love!", pbThis)) PBDebug.log("[Move failed] #{pbThis} is immobilized by love") @lastMoveFailed = true return false end end return true end #============================================================================= # Initial success check against the target. Done once before the first hit. # Includes move-specific failure conditions, protections and type immunities. #============================================================================= def pbSuccessCheckAgainstTarget(move, user, target, targets) show_message = move.pbShowFailMessages?(targets) typeMod = move.pbCalcTypeMod(move.calcType, user, target) target.damageState.typeMod = typeMod # Two-turn attacks can't fail here in the charging turn return true if user.effects[PBEffects::TwoTurnAttack] # Move-specific failures if move.pbFailsAgainstTarget?(user, target, show_message) PBDebug.log(sprintf("[Move failed] In function code %s's def pbFailsAgainstTarget?", move.function_code)) return false end # Immunity to priority moves because of Psychic Terrain if @battle.field.terrain == :Psychic && target.affectedByTerrain? && target.opposes?(user) && @battle.choices[user.index][4] > 0 # Move priority saved from pbCalculatePriority @battle.pbDisplay(_INTL("{1} surrounds itself with psychic terrain!", target.pbThis)) if show_message return false end # Crafty Shield if target.pbOwnSide.effects[PBEffects::CraftyShield] && user.index != target.index && move.statusMove? && !move.pbTarget(user).targets_all if show_message @battle.pbCommonAnimation("CraftyShield", target) @battle.pbDisplay(_INTL("Crafty Shield protected {1}!", target.pbThis(true))) end target.damageState.protected = true @battle.successStates[user.index].protected = true return false end if !(user.hasActiveAbility?(:UNSEENFIST) && move.contactMove?) # Wide Guard if target.pbOwnSide.effects[PBEffects::WideGuard] && user.index != target.index && move.pbTarget(user).num_targets > 1 && (Settings::MECHANICS_GENERATION >= 7 || move.damagingMove?) if show_message @battle.pbCommonAnimation("WideGuard", target) @battle.pbDisplay(_INTL("Wide Guard protected {1}!", target.pbThis(true))) end target.damageState.protected = true @battle.successStates[user.index].protected = true return false end if move.canProtectAgainst? # Quick Guard if target.pbOwnSide.effects[PBEffects::QuickGuard] && @battle.choices[user.index][4] > 0 # Move priority saved from pbCalculatePriority if show_message @battle.pbCommonAnimation("QuickGuard", target) @battle.pbDisplay(_INTL("Quick Guard protected {1}!", target.pbThis(true))) end target.damageState.protected = true @battle.successStates[user.index].protected = true return false end # Protect if target.effects[PBEffects::Protect] if show_message @battle.pbCommonAnimation("Protect", target) @battle.pbDisplay(_INTL("{1} protected itself!", target.pbThis)) end target.damageState.protected = true @battle.successStates[user.index].protected = true return false end # King's Shield if target.effects[PBEffects::KingsShield] && move.damagingMove? if show_message @battle.pbCommonAnimation("KingsShield", target) @battle.pbDisplay(_INTL("{1} protected itself!", target.pbThis)) end target.damageState.protected = true @battle.successStates[user.index].protected = true if move.pbContactMove?(user) && user.affectedByContactEffect? && user.pbCanLowerStatStage?(:ATTACK, target) user.pbLowerStatStage(:ATTACK, (Settings::MECHANICS_GENERATION >= 8) ? 1 : 2, target) end return false end # Spiky Shield if target.effects[PBEffects::SpikyShield] if show_message @battle.pbCommonAnimation("SpikyShield", target) @battle.pbDisplay(_INTL("{1} protected itself!", target.pbThis)) end target.damageState.protected = true @battle.successStates[user.index].protected = true if move.pbContactMove?(user) && user.affectedByContactEffect? && user.takesIndirectDamage? @battle.scene.pbDamageAnimation(user) user.pbReduceHP(user.totalhp / 8, false) @battle.pbDisplay(_INTL("{1} was hurt!", user.pbThis)) user.pbItemHPHealCheck end return false end # Baneful Bunker if target.effects[PBEffects::BanefulBunker] if show_message @battle.pbCommonAnimation("BanefulBunker", target) @battle.pbDisplay(_INTL("{1} protected itself!", target.pbThis)) end target.damageState.protected = true @battle.successStates[user.index].protected = true if move.pbContactMove?(user) && user.affectedByContactEffect? && user.pbCanPoison?(target, false) user.pbPoison(target) end return false end # Obstruct if target.effects[PBEffects::Obstruct] && move.damagingMove? if show_message @battle.pbCommonAnimation("Obstruct", target) @battle.pbDisplay(_INTL("{1} protected itself!", target.pbThis)) end target.damageState.protected = true @battle.successStates[user.index].protected = true if move.pbContactMove?(user) && user.affectedByContactEffect? && user.pbCanLowerStatStage?(:DEFENSE, target) user.pbLowerStatStage(:DEFENSE, 2, target) end return false end # Mat Block if target.pbOwnSide.effects[PBEffects::MatBlock] && move.damagingMove? # NOTE: Confirmed no common animation for this effect. @battle.pbDisplay(_INTL("{1} was blocked by the kicked-up mat!", move.name)) if show_message target.damageState.protected = true @battle.successStates[user.index].protected = true return false end end end # Magic Coat/Magic Bounce if move.statusMove? && move.canMagicCoat? && !target.semiInvulnerable? && target.opposes?(user) if target.effects[PBEffects::MagicCoat] target.damageState.magicCoat = true target.effects[PBEffects::MagicCoat] = false return false end if target.hasActiveAbility?(:MAGICBOUNCE) && !@battle.moldBreaker && !target.effects[PBEffects::MagicBounce] target.damageState.magicBounce = true target.effects[PBEffects::MagicBounce] = true return false end end # Immunity because of ability (intentionally before type immunity check) return false if move.pbImmunityByAbility(user, target, show_message) # Type immunity if move.pbDamagingMove? && Effectiveness.ineffective?(typeMod) PBDebug.log("[Target immune] #{target.pbThis}'s type immunity") @battle.pbDisplay(_INTL("It doesn't affect {1}...", target.pbThis(true))) if show_message return false end # Dark-type immunity to moves made faster by Prankster if Settings::MECHANICS_GENERATION >= 7 && user.effects[PBEffects::Prankster] && target.pbHasType?(:DARK) && target.opposes?(user) PBDebug.log("[Target immune] #{target.pbThis} is Dark-type and immune to Prankster-boosted moves") @battle.pbDisplay(_INTL("It doesn't affect {1}...", target.pbThis(true))) if show_message return false end # Airborne-based immunity to Ground moves if move.damagingMove? && move.calcType == :GROUND && target.airborne? && !move.hitsFlyingTargets? if target.hasActiveAbility?(:LEVITATE) && !@battle.moldBreaker if show_message @battle.pbShowAbilitySplash(target) if Battle::Scene::USE_ABILITY_SPLASH @battle.pbDisplay(_INTL("{1} avoided the attack!", target.pbThis)) else @battle.pbDisplay(_INTL("{1} avoided the attack with {2}!", target.pbThis, target.abilityName)) end @battle.pbHideAbilitySplash(target) end return false end if target.hasActiveItem?(:AIRBALLOON) @battle.pbDisplay(_INTL("{1}'s {2} makes Ground moves miss!", target.pbThis, target.itemName)) if show_message return false end if target.effects[PBEffects::MagnetRise] > 0 @battle.pbDisplay(_INTL("{1} makes Ground moves miss with Magnet Rise!", target.pbThis)) if show_message return false end if target.effects[PBEffects::Telekinesis] > 0 @battle.pbDisplay(_INTL("{1} makes Ground moves miss with Telekinesis!", target.pbThis)) if show_message return false end end # Immunity to powder-based moves if move.powderMove? if target.pbHasType?(:GRASS) && Settings::MORE_TYPE_EFFECTS PBDebug.log("[Target immune] #{target.pbThis} is Grass-type and immune to powder-based moves") @battle.pbDisplay(_INTL("It doesn't affect {1}...", target.pbThis(true))) if show_message return false end if Settings::MECHANICS_GENERATION >= 6 if target.hasActiveAbility?(:OVERCOAT) && !@battle.moldBreaker if show_message @battle.pbShowAbilitySplash(target) if Battle::Scene::USE_ABILITY_SPLASH @battle.pbDisplay(_INTL("It doesn't affect {1}...", target.pbThis(true))) else @battle.pbDisplay(_INTL("It doesn't affect {1} because of its {2}.", target.pbThis(true), target.abilityName)) end @battle.pbHideAbilitySplash(target) end return false end if target.hasActiveItem?(:SAFETYGOGGLES) PBDebug.log("[Item triggered] #{target.pbThis} has Safety Goggles and is immune to powder-based moves") @battle.pbDisplay(_INTL("It doesn't affect {1}...", target.pbThis(true))) if show_message return false end end end # Substitute if target.effects[PBEffects::Substitute] > 0 && move.statusMove? && !move.ignoresSubstitute?(user) && user.index != target.index PBDebug.log("[Target immune] #{target.pbThis} is protected by its Substitute") @battle.pbDisplay(_INTL("{1} avoided the attack!", target.pbThis(true))) if show_message return false end return true end #============================================================================= # Per-hit success check against the target. # Includes semi-invulnerable move use and accuracy calculation. #============================================================================= def pbSuccessCheckPerHit(move, user, target, skipAccuracyCheck) # Two-turn attacks can't fail here in the charging turn return true if user.effects[PBEffects::TwoTurnAttack] # Lock-On return true if user.effects[PBEffects::LockOn] > 0 && user.effects[PBEffects::LockOnPos] == target.index # Toxic return true if move.pbOverrideSuccessCheckPerHit(user, target) miss = false hitsInvul = false # No Guard hitsInvul = true if user.hasActiveAbility?(:NOGUARD) || target.hasActiveAbility?(:NOGUARD) # Future Sight hitsInvul = true if @battle.futureSight # Helping Hand hitsInvul = true if move.function_code == "PowerUpAllyMove" if !hitsInvul # Semi-invulnerable moves if target.effects[PBEffects::TwoTurnAttack] if target.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky", "TwoTurnAttackInvulnerableInSkyParalyzeTarget", "TwoTurnAttackInvulnerableInSkyTargetCannotAct") miss = true if !move.hitsFlyingTargets? elsif target.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground") miss = true if !move.hitsDiggingTargets? elsif target.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderwater") miss = true if !move.hitsDivingTargets? elsif target.inTwoTurnAttack?("TwoTurnAttackInvulnerableRemoveProtections") miss = true end end if target.effects[PBEffects::SkyDrop] >= 0 && target.effects[PBEffects::SkyDrop] != user.index && !move.hitsFlyingTargets? miss = true end end if miss target.damageState.invulnerable = true PBDebug.log("[Move failed] Target is semi-invulnerable") else # Called by another move return true if skipAccuracyCheck # Accuracy check return true if move.pbAccuracyCheck(user, target) # Includes Counter/Mirror Coat PBDebug.log("[Move failed] Failed pbAccuracyCheck") end # Missed return false end #============================================================================= # Message shown when a move fails the per-hit success check above. #============================================================================= def pbMissMessage(move, user, target) if target.damageState.affection_missed @battle.pbDisplay(_INTL("{1} avoided the move in time with your shout!", target.pbThis)) elsif move.pbTarget(user).num_targets > 1 || target.effects[PBEffects::TwoTurnAttack] @battle.pbDisplay(_INTL("{1} avoided the attack!", target.pbThis)) elsif !move.pbMissMessage(user, target) @battle.pbDisplay(_INTL("{1}'s attack missed!", user.pbThis)) end end end