diff --git a/Data/Scripts/011_Battle/001_Battle/001_Battle.rb b/Data/Scripts/011_Battle/001_Battle/001_Battle.rb index 4c692ad1e..1aff162e5 100644 --- a/Data/Scripts/011_Battle/001_Battle/001_Battle.rb +++ b/Data/Scripts/011_Battle/001_Battle/001_Battle.rb @@ -165,11 +165,7 @@ class Battle @moldBreaker = false @runCommand = 0 @nextPickupUse = 0 - if GameData::Move.exists?(:STRUGGLE) - @struggle = Move.from_pokemon_move(self, Pokemon::Move.new(:STRUGGLE)) - else - @struggle = Move::Struggle.new(self, nil) - end + @struggle = Move::Struggle.new(self, nil) @mega_rings = [] GameData::Item.each { |item| @mega_rings.push(item.id) if item.has_flag?("MegaRing") } @battleAI = AI.new(self) diff --git a/Data/Scripts/011_Battle/002_Battler/004_Battler_Statuses.rb b/Data/Scripts/011_Battle/002_Battler/004_Battler_Statuses.rb index 1ebf1ccec..d92a2cbf4 100644 --- a/Data/Scripts/011_Battle/002_Battler/004_Battler_Statuses.rb +++ b/Data/Scripts/011_Battle/002_Battler/004_Battler_Statuses.rb @@ -171,7 +171,7 @@ class Battle::Battler return true end - def pbCanSynchronizeStatus?(newStatus, target) + def pbCanSynchronizeStatus?(newStatus, user) return false if fainted? # Trying to replace a status problem with another one return false if self.status != :NONE @@ -181,8 +181,8 @@ class Battle::Battler hasImmuneType = false case newStatus when :POISON - # NOTE: target will have Synchronize, so it can't have Corrosion. - if !(target && target.hasActiveAbility?(:CORROSION)) + # NOTE: user will have Synchronize, so it can't have Corrosion. + if !(user && user.hasActiveAbility?(:CORROSION)) hasImmuneType |= pbHasType?(:POISON) hasImmuneType |= pbHasType?(:STEEL) end @@ -205,6 +205,7 @@ class Battle::Battler return false end # Safeguard immunity + # NOTE: user will have Synchronize, so it can't have Infiltrator. if pbOwnSide.effects[PBEffects::Safeguard] > 0 && !(user && user.hasActiveAbility?(:INFILTRATOR)) return false diff --git a/Data/Scripts/011_Battle/002_Battler/009_Battler_UseMoveSuccessChecks.rb b/Data/Scripts/011_Battle/002_Battler/009_Battler_UseMoveSuccessChecks.rb index b1d568c0d..fc621a3d2 100644 --- a/Data/Scripts/011_Battle/002_Battler/009_Battler_UseMoveSuccessChecks.rb +++ b/Data/Scripts/011_Battle/002_Battler/009_Battler_UseMoveSuccessChecks.rb @@ -43,19 +43,21 @@ class Battle::Battler # Choice Band/Gorilla Tactics @effects[PBEffects::ChoiceBand] = nil if !pbHasMove?(@effects[PBEffects::ChoiceBand]) if @effects[PBEffects::ChoiceBand] && move.id != @effects[PBEffects::ChoiceBand] - choiced_move_name = GameData::Move.get(@effects[PBEffects::ChoiceBand]).name - 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) + 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 - 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 # Taunt @@ -85,7 +87,7 @@ class Battle::Battler end # Assault Vest (prevents choosing status moves but doesn't prevent # executing them) - if hasActiveItem?(:ASSAULTVEST) && move.statusMove? && move.id != :MEFIRST && commandPhase + if hasActiveItem?(:ASSAULTVEST) && move.statusMove? && move.function != "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) diff --git a/Data/Scripts/011_Battle/003_Move/001_Battle_Move.rb b/Data/Scripts/011_Battle/003_Move/001_Battle_Move.rb index 0e7f1dfa8..455e70240 100644 --- a/Data/Scripts/011_Battle/003_Move/001_Battle_Move.rb +++ b/Data/Scripts/011_Battle/003_Move/001_Battle_Move.rb @@ -43,11 +43,10 @@ class Battle::Move @category = move.category @accuracy = move.accuracy @pp = move.pp # Can be changed with Mimic/Transform - @addlEffect = move.effect_chance @target = move.target @priority = move.priority @flags = move.flags.clone - @calcType = nil + @addlEffect = move.effect_chance @powerBoost = false # For Aerilate, Pixilate, Refrigerate, Galvanize @snatched = false end diff --git a/Data/Scripts/011_Battle/003_Move/003_Move_UsageCalculations.rb b/Data/Scripts/011_Battle/003_Move/003_Move_UsageCalculations.rb index 5ec2a1aca..b6bb0f011 100644 --- a/Data/Scripts/011_Battle/003_Move/003_Move_UsageCalculations.rb +++ b/Data/Scripts/011_Battle/003_Move/003_Move_UsageCalculations.rb @@ -285,7 +285,7 @@ class Battle::Move if (@battle.pbCheckGlobalAbility(:DARKAURA) && type == :DARK) || (@battle.pbCheckGlobalAbility(:FAIRYAURA) && type == :FAIRY) if @battle.pbCheckGlobalAbility(:AURABREAK) - multipliers[:power_multiplier] *= 2 / 3.0 + multipliers[:power_multiplier] *= 3 / 4.0 else multipliers[:power_multiplier] *= 4 / 3.0 end diff --git a/Data/Scripts/011_Battle/003_Move/004_Move_BaseEffects.rb b/Data/Scripts/011_Battle/003_Move/004_Move_BaseEffects.rb index 9f48cfa2b..1a130f1ee 100644 --- a/Data/Scripts/011_Battle/003_Move/004_Move_BaseEffects.rb +++ b/Data/Scripts/011_Battle/003_Move/004_Move_BaseEffects.rb @@ -36,7 +36,6 @@ class Battle::Move::Confusion < Battle::Move @priority = 0 @flags = [] @addlEffect = 0 - @calcType = nil @powerBoost = false @snatched = false end @@ -53,19 +52,18 @@ class Battle::Move::Struggle < Battle::Move def initialize(battle, move) @battle = battle @realMove = nil # Not associated with a move - @id = (move) ? move.id : :STRUGGLE - @name = (move) ? move.name : _INTL("Struggle") + @id = :STRUGGLE + @name = _INTL("Struggle") @function = "Struggle" @power = 50 @type = nil @category = 0 @accuracy = 0 @pp = -1 - @target = :NearOther + @target = :RandomNearFoe @priority = 0 @flags = ["Contact", "CanProtect"] @addlEffect = 0 - @calcType = nil @powerBoost = false @snatched = false end diff --git a/Data/Scripts/011_Battle/003_Move/006_MoveEffects_BattlerStats.rb b/Data/Scripts/011_Battle/003_Move/006_MoveEffects_BattlerStats.rb index 5f4665aab..14cc022b6 100644 --- a/Data/Scripts/011_Battle/003_Move/006_MoveEffects_BattlerStats.rb +++ b/Data/Scripts/011_Battle/003_Move/006_MoveEffects_BattlerStats.rb @@ -1372,7 +1372,7 @@ class Battle::Move::LowerPoisonedTargetAtkSpAtkSpd1 < Battle::Move next if !b.poisoned? failed = true (@statDown.length / 2).times do |i| - next if !target.pbCanLowerStatStage?(@statDown[i * 2], user, self) + next if !b.pbCanLowerStatStage?(@statDown[i * 2], user, self) failed = false break end diff --git a/Data/Scripts/011_Battle/003_Move/012_MoveEffects_ChangeMoveEffect.rb b/Data/Scripts/011_Battle/003_Move/012_MoveEffects_ChangeMoveEffect.rb index 7f3a39053..91e7c2e60 100644 --- a/Data/Scripts/011_Battle/003_Move/012_MoveEffects_ChangeMoveEffect.rb +++ b/Data/Scripts/011_Battle/003_Move/012_MoveEffects_ChangeMoveEffect.rb @@ -768,6 +768,7 @@ class Battle::Move::UseLastMoveUsed < Battle::Move def pbMoveFailed?(user, targets) if !@copied_move || + !GameData::Move.exists?(@copied_move) || @moveBlacklist.include?(GameData::Move.get(@copied_move).function_code) @battle.pbDisplay(_INTL("But it failed!")) return true @@ -789,6 +790,7 @@ class Battle::Move::UseLastMoveUsedByTarget < Battle::Move def pbFailsAgainstTarget?(user, target, show_message) if !target.lastRegularMoveUsed || + !GameData::Move.exists?(target.lastRegularMoveUsed) || !GameData::Move.get(target.lastRegularMoveUsed).has_flag?("CanMirrorMove") @battle.pbDisplay(_INTL("The mirror move failed!")) if show_message return true diff --git a/Data/Scripts/011_Battle/003_Move/013_MoveEffects_SwitchingActing.rb b/Data/Scripts/011_Battle/003_Move/013_MoveEffects_SwitchingActing.rb index 3f2202bf8..e2c436ec1 100644 --- a/Data/Scripts/011_Battle/003_Move/013_MoveEffects_SwitchingActing.rb +++ b/Data/Scripts/011_Battle/003_Move/013_MoveEffects_SwitchingActing.rb @@ -606,7 +606,8 @@ class Battle::Move::TargetUsesItsLastUsedMoveAgain < Battle::Move end def pbFailsAgainstTarget?(user, target, show_message) - if !target.lastRegularMoveUsed || !target.pbHasMove?(target.lastRegularMoveUsed) + if !target.lastRegularMoveUsed || !target.pbHasMove?(target.lastRegularMoveUsed) || + !GameData::Move.exists?(target.lastRegularMoveUsed) @battle.pbDisplay(_INTL("But it failed!")) if show_message return true end @@ -814,6 +815,7 @@ class Battle::Move::DisableTargetUsingDifferentMove < Battle::Move return true end if !target.lastRegularMoveUsed || + !GameData::Move.exists?(target.lastRegularMoveUsed) || @moveBlacklist.include?(GameData::Move.get(target.lastRegularMoveUsed).function_code) @battle.pbDisplay(_INTL("But it failed!")) if show_message return true diff --git a/Data/Scripts/011_Battle/004_Scene/004_Scene_PlayAnimations.rb b/Data/Scripts/011_Battle/004_Scene/004_Scene_PlayAnimations.rb index a4c63a047..cc3e75794 100644 --- a/Data/Scripts/011_Battle/004_Scene/004_Scene_PlayAnimations.rb +++ b/Data/Scripts/011_Battle/004_Scene/004_Scene_PlayAnimations.rb @@ -410,7 +410,7 @@ class Battle::Scene # Returns the animation ID to use for a given move/user. Returns nil if that # move has no animations defined for it. def pbFindMoveAnimDetails(move2anim, moveID, idxUser, hitNum = 0) - real_move_id = GameData::Move.get(moveID).id + real_move_id = GameData::Move.try_get(moveID)&.id || moveID noFlip = false if (idxUser & 1) == 0 # On player's side anim = move2anim[0][real_move_id] diff --git a/Data/Scripts/011_Battle/005_AI/001_Battle_AI.rb b/Data/Scripts/011_Battle/005_AI/001_Battle_AI.rb index 01fbfe7d7..ae1e266a7 100644 --- a/Data/Scripts/011_Battle/005_AI/001_Battle_AI.rb +++ b/Data/Scripts/011_Battle/005_AI/001_Battle_AI.rb @@ -5,12 +5,11 @@ class Battle::AI attr_reader :battle attr_reader :trainer attr_reader :battlers + attr_reader :roles attr_reader :user, :target, :move def initialize(battle) @battle = battle - - # TODO: Move this elsewhere? @roles = [Array.new(@battle.pbParty(0).length) { |i| determine_roles(0, i) }, Array.new(@battle.pbParty(1).length) { |i| determine_roles(1, i) }] end @@ -57,7 +56,9 @@ class Battle::AI PBDebug.log("") return end - if pbEnemyShouldUseItem? + ret = false + PBDebug.logonerr { ret = pbChooseToUseItem } + if ret PBDebug.log("") return end diff --git a/Data/Scripts/011_Battle/005_AI/002_AI_Item.rb b/Data/Scripts/011_Battle/005_AI/002_AI_Item.rb index 465e939bd..c15d52ff8 100644 --- a/Data/Scripts/011_Battle/005_AI/002_AI_Item.rb +++ b/Data/Scripts/011_Battle/005_AI/002_AI_Item.rb @@ -1,173 +1,224 @@ class Battle::AI - #============================================================================= - # Decide whether the opponent should use an item on the Pokémon - # TODO: Maybe don't cure a status problem if the Pokémon has an ability or - # something that makes it benefit from having that problem. - #============================================================================= - def pbEnemyShouldUseItem? + HP_HEAL_ITEMS = { + :POTION => 20, + :SUPERPOTION => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 60 : 50, + :HYPERPOTION => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 120 : 200, + :MAXPOTION => 999, + :BERRYJUICE => 20, + :SWEETHEART => 20, + :FRESHWATER => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 30 : 50, + :SODAPOP => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 50 : 60, + :LEMONADE => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 70 : 80, + :MOOMOOMILK => 100, + :ORANBERRY => 10, + :SITRUSBERRY => 1, # Actual amount is determined below (pkmn.totalhp / 4) + :ENERGYPOWDER => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 60 : 50, + :ENERGYROOT => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 120 : 200 + } + HP_HEAL_ITEMS[:RAGECANDYBAR] = 20 if !Settings::RAGE_CANDY_BAR_CURES_STATUS_PROBLEMS + FULL_RESTORE_ITEMS = [ + :FULLRESTORE + ] + ONE_STATUS_CURE_ITEMS = [ # Preferred over items that heal all status problems + :AWAKENING, :CHESTOBERRY, :BLUEFLUTE, + :ANTIDOTE, :PECHABERRY, + :BURNHEAL, :RAWSTBERRY, + :PARALYZEHEAL, :PARLYZHEAL, :CHERIBERRY, + :ICEHEAL, :ASPEARBERRY + ] + ALL_STATUS_CURE_ITEMS = [ + :FULLHEAL, :LAVACOOKIE, :OLDGATEAU, :CASTELIACONE, :LUMIOSEGALETTE, + :SHALOURSABLE, :BIGMALASADA, :PEWTERCRUNCHIES, :LUMBERRY, :HEALPOWDER + ] + ALL_STATUS_CURE_ITEMS.push(:RAGECANDYBAR) if Settings::RAGE_CANDY_BAR_CURES_STATUS_PROBLEMS + ONE_STAT_RAISE_ITEMS = { + :XATTACK => [:ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], + :XATTACK2 => [:ATTACK, 2], + :XATTACK3 => [:ATTACK, 3], + :XATTACK6 => [:ATTACK, 6], + :XDEFENSE => [:DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], + :XDEFENSE2 => [:DEFENSE, 2], + :XDEFENSE3 => [:DEFENSE, 3], + :XDEFENSE6 => [:DEFENSE, 6], + :XDEFEND => [:DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], + :XDEFEND2 => [:DEFENSE, 2], + :XDEFEND3 => [:DEFENSE, 3], + :XDEFEND6 => [:DEFENSE, 6], + :XSPATK => [:SPECIAL_ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], + :XSPATK2 => [:SPECIAL_ATTACK, 2], + :XSPATK3 => [:SPECIAL_ATTACK, 3], + :XSPATK6 => [:SPECIAL_ATTACK, 6], + :XSPECIAL => [:SPECIAL_ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], + :XSPECIAL2 => [:SPECIAL_ATTACK, 2], + :XSPECIAL3 => [:SPECIAL_ATTACK, 3], + :XSPECIAL6 => [:SPECIAL_ATTACK, 6], + :XSPDEF => [:SPECIAL_DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], + :XSPDEF2 => [:SPECIAL_DEFENSE, 2], + :XSPDEF3 => [:SPECIAL_DEFENSE, 3], + :XSPDEF6 => [:SPECIAL_DEFENSE, 6], + :XSPEED => [:SPEED, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], + :XSPEED2 => [:SPEED, 2], + :XSPEED3 => [:SPEED, 3], + :XSPEED6 => [:SPEED, 6], + :XACCURACY => [:ACCURACY, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], + :XACCURACY2 => [:ACCURACY, 2], + :XACCURACY3 => [:ACCURACY, 3], + :XACCURACY6 => [:ACCURACY, 6] + } + ALL_STATS_RAISE_ITEMS = [ + :MAXMUSHROOMS + ] + REVIVE_ITEMS = { + :REVIVE => 5, + :MAXREVIVE => 7, + :REVIVALHERB => 7, + :MAXHONEY => 7 + } + # TODO: Add more items for the AI to use from their Bag: + # Confusion healing (Yellow Flute, Persim Berry) + # Infatuation healing (Red Flute) + # PP (Either, Max Ether, Leppa Berry, Elixir, Max Elixir) + # Dire Hit (and 2 and 3) + # Guard Spec. + # Poké Flute (awakens all battlers) + + #----------------------------------------------------------------------------- + + # Decide whether the opponent should use an item on the Pokémon. + def pbChooseToUseItem item = nil - idxTarget = nil - PBDebug.logonerr { item, idxTarget = pbEnemyItemToUse } + idxTarget = nil # Party index (battle_use type 1/2/3) or battler index + idxMove = nil + item, idxTarget, idxMove = choose_item_to_use return false if !item - # Determine target of item (always the Pokémon choosing the action) - useType = GameData::Item.get(item).battle_use - if [1, 2, 3].include?(useType) # Use on Pokémon - idxTarget = @battle.battlers[idxTarget].pokemonIndex # Party Pokémon - end # Register use of item - @battle.pbRegisterItem(@user.index, item, idxTarget) + @battle.pbRegisterItem(@user.index, item, idxTarget, idxMove) PBDebug.log_ai("#{@user.name} will use item #{GameData::Item.get(item).name}") return true end - # NOTE: The AI will only consider using an item on the Pokémon it's currently - # choosing an action for. - def pbEnemyItemToUse + # Return values are: + # item ID + # target index (party index for items with a battle use of 1/2/3, battler + # index otherwise) + # move index (for items usable on moves only) + def choose_item_to_use return nil if !@battle.internalBattle items = @battle.pbGetOwnerItems(@user.index) return nil if !items || items.length == 0 - # Determine target of item (always the Pokémon choosing the action) - idxTarget = @user.index # Battler using the item - battler = @user.battler - pkmn = battler.pokemon - # Item categories - hpItems = { - :POTION => 20, - :SUPERPOTION => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 60 : 50, - :HYPERPOTION => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 120 : 200, - :MAXPOTION => 999, - :BERRYJUICE => 20, - :SWEETHEART => 20, - :FRESHWATER => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 30 : 50, - :SODAPOP => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 50 : 60, - :LEMONADE => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 70 : 80, - :MOOMOOMILK => 100, - :ORANBERRY => 10, - :SITRUSBERRY => battler.totalhp / 4, - :ENERGYPOWDER => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 60 : 50, - :ENERGYROOT => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 120 : 200 - } - hpItems[:RAGECANDYBAR] = 20 if !Settings::RAGE_CANDY_BAR_CURES_STATUS_PROBLEMS - fullRestoreItems = [ - :FULLRESTORE - ] - oneStatusItems = [ # Preferred over items that heal all status problems - :AWAKENING, :CHESTOBERRY, :BLUEFLUTE, - :ANTIDOTE, :PECHABERRY, - :BURNHEAL, :RAWSTBERRY, - :PARALYZEHEAL, :PARLYZHEAL, :CHERIBERRY, - :ICEHEAL, :ASPEARBERRY - ] - allStatusItems = [ - :FULLHEAL, :LAVACOOKIE, :OLDGATEAU, :CASTELIACONE, :LUMIOSEGALETTE, - :SHALOURSABLE, :BIGMALASADA, :PEWTERCRUNCHIES, :LUMBERRY, :HEALPOWDER - ] - allStatusItems.push(:RAGECANDYBAR) if Settings::RAGE_CANDY_BAR_CURES_STATUS_PROBLEMS - xItems = { - :XATTACK => [:ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], - :XATTACK2 => [:ATTACK, 2], - :XATTACK3 => [:ATTACK, 3], - :XATTACK6 => [:ATTACK, 6], - :XDEFENSE => [:DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], - :XDEFENSE2 => [:DEFENSE, 2], - :XDEFENSE3 => [:DEFENSE, 3], - :XDEFENSE6 => [:DEFENSE, 6], - :XDEFEND => [:DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], - :XDEFEND2 => [:DEFENSE, 2], - :XDEFEND3 => [:DEFENSE, 3], - :XDEFEND6 => [:DEFENSE, 6], - :XSPATK => [:SPECIAL_ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], - :XSPATK2 => [:SPECIAL_ATTACK, 2], - :XSPATK3 => [:SPECIAL_ATTACK, 3], - :XSPATK6 => [:SPECIAL_ATTACK, 6], - :XSPECIAL => [:SPECIAL_ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], - :XSPECIAL2 => [:SPECIAL_ATTACK, 2], - :XSPECIAL3 => [:SPECIAL_ATTACK, 3], - :XSPECIAL6 => [:SPECIAL_ATTACK, 6], - :XSPDEF => [:SPECIAL_DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], - :XSPDEF2 => [:SPECIAL_DEFENSE, 2], - :XSPDEF3 => [:SPECIAL_DEFENSE, 3], - :XSPDEF6 => [:SPECIAL_DEFENSE, 6], - :XSPEED => [:SPEED, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], - :XSPEED2 => [:SPEED, 2], - :XSPEED3 => [:SPEED, 3], - :XSPEED6 => [:SPEED, 6], - :XACCURACY => [:ACCURACY, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1], - :XACCURACY2 => [:ACCURACY, 2], - :XACCURACY3 => [:ACCURACY, 3], - :XACCURACY6 => [:ACCURACY, 6] - } - losthp = battler.totalhp - battler.hp - preferFullRestore = (battler.hp <= battler.totalhp * 2 / 3 && - (battler.status != :NONE || battler.effects[PBEffects::Confusion] > 0)) - # Find all usable items - usableHPItems = [] - usableStatusItems = [] - usableXItems = [] - items.each do |i| - next if !i - next if !@battle.pbCanUseItemOnPokemon?(i, pkmn, battler, @battle.scene, false) - next if !ItemHandlers.triggerCanUseInBattle(i, pkmn, battler, nil, - false, self, @battle.scene, false) - # Log HP healing items - if losthp > 0 - power = hpItems[i] - if power - usableHPItems.push([i, 5, power]) - next - end - end - # Log Full Restores (HP healer and status curer) - if fullRestoreItems.include?(i) - usableHPItems.push([i, (preferFullRestore) ? 3 : 7, 999]) if losthp > 0 - usableStatusItems.push([i, (preferFullRestore) ? 3 : 9]) if battler.status != :NONE || - battler.effects[PBEffects::Confusion] > 0 - next - end - # Log single status-curing items - if oneStatusItems.include?(i) - usableStatusItems.push([i, 5]) - next - end - # Log Full Heal-type items - if allStatusItems.include?(i) - usableStatusItems.push([i, 7]) - next - end - # Log stat-raising items - if xItems[i] - data = xItems[i] - usableXItems.push([i, battler.stages[data[0]], data[1]]) - next + # Find all items usable on the Pokémon choosing this action + pkmn = @user.battler.pokemon + usable_items = {} + items.each do |item| + usage = get_usability_of_item_on_pkmn(item, @user.party_index, @user.side) + usage.each_pair do |key, vals| + usable_items[key] ||= [] + usable_items[key] += vals end end # Prioritise using a HP restoration item - if usableHPItems.length > 0 && (battler.hp <= battler.totalhp / 4 || - (battler.hp <= battler.totalhp / 2 && pbAIRandom(100) < 30)) - usableHPItems.sort! { |a, b| (a[1] == b[1]) ? a[2] <=> b[2] : a[1] <=> b[1] } - prevItem = nil - usableHPItems.each do |i| - return i[0], idxTarget if i[2] >= losthp - prevItem = i + if usable_items[:hp_heal] && (pkmn.hp <= pkmn.totalhp / 4 || + (pkmn.hp <= pkmn.totalhp / 2 && pbAIRandom(100) < 30)) + usable_items[:hp_heal].sort! { |a, b| (a[2] == b[2]) ? a[3] <=> b[3] : a[2] <=> b[2] } + usable_items[:hp_heal].each do |item| + return item[0], item[1] if item[3] >= (pkmn.totalhp - pkmn.hp) * 0.75 end - return prevItem[0], idxTarget + return usable_items[:hp_heal].last[0], usable_items[:hp_heal].last[1] end # Next prioritise using a status-curing item - if usableStatusItems.length > 0 && pbAIRandom(100) < 40 - usableStatusItems.sort! { |a, b| a[1] <=> b[1] } - return usableStatusItems[0][0], idxTarget + if usable_items[:status_cure] && + ([:SLEEP, :FROZEN].include?(pkmn.status) || pbAIRandom(100) < 40) + usable_items[:status_cure].sort! { |a, b| a[2] <=> b[2] } + return usable_items[:status_cure].first[0], usable_items[:status_cure].first[1] + end + # Next try using an item that raises all stats (Max Mushrooms) + if usable_items[:all_stats_raise] && pbAIRandom(100) < 30 + return usable_items[:stat_raise].first[0], usable_items[:stat_raise].first[1] end # Next try using an X item - if usableXItems.length > 0 && pbAIRandom(100) < 30 - usableXItems.sort! { |a, b| (a[1] == b[1]) ? a[2] <=> b[2] : a[1] <=> b[1] } - prevItem = nil - usableXItems.each do |i| - break if prevItem && i[1] > prevItem[1] - return i[0], idxTarget if i[1] + i[2] >= 6 - prevItem = i + if usable_items[:stat_raise] && pbAIRandom(100) < 30 + usable_items[:stat_raise].sort! { |a, b| (a[2] == b[2]) ? a[3] <=> b[3] : a[2] <=> b[2] } + return usable_items[:stat_raise].last[0], usable_items[:stat_raise].last[1] + end + # Find items usable on other Pokémon in the user's team + # NOTE: Currently only checks Revives. + usable_items = {} + @battle.eachInTeamFromBattlerIndex(@user.index) do |pkmn, i| + next if !pkmn.fainted? # Remove this line to check unfainted Pokémon too + items.each do |item| + usage = get_usability_of_item_on_pkmn(item, i, @user.side) + usage.each_pair do |key, vals| + usable_items[key] ||= [] + usable_items[key] += vals + end end - return prevItem[0], idxTarget + end + # Try using a Revive (prefer Max Revive-type items over Revive) + if usable_items[:revive] && + (@battle.pbAbleNonActiveCount(@user.index) == 0 || pbAIRandom(100) < 40) + usable_items[:revive].sort! { |a, b| (a[2] == b[2]) ? a[1] <=> b[1] : a[2] <=> b[2] } + return usable_items[:revive].last[0], usable_items[:revive].last[1] end return nil end + + def get_usability_of_item_on_pkmn(item, party_index, side) + pkmn = @battle.pbParty(side)[party_index] + battler = @battle.pbFindBattler(party_index, side) + ret = {} + return ret if !@battle.pbCanUseItemOnPokemon?(item, pkmn, battler, @battle.scene, false) + return ret if !ItemHandlers.triggerCanUseInBattle(item, pkmn, battler, nil, + false, self, @battle.scene, false) + want_to_cure_status = (pkmn.status != :NONE) + if battler + if want_to_cure_status + want_to_cure_status = @battlers[battler.index].wants_status_problem?(pkmn.status) + want_to_cure_status = false if pkmn.status == :SLEEP && pkmn.statusCount <= 2 + end + want_to_cure_status ||= (battler.effects[PBEffects::Confusion] > 0) + end + if HP_HEAL_ITEMS.include?(item) + if pkmn.hp < pkmn.totalhp + heal_amount = HP_HEAL_ITEMS[item] + heal_amount = pkmn.totalhp / 4 if item == :SITURUSBERRY + ret[:hp_heal] ||= [] + ret[:hp_heal].push([item, party_index, 5, heal_amount]) + end + elsif FULL_RESTORE_ITEMS.include?(item) + prefer_full_restore = (pkmn.hp <= pkmn.totalhp * 2 / 3 && want_to_cure_status) + if pkmn.hp < pkmn.totalhp + ret[:hp_heal] ||= [] + ret[:hp_heal].push([item, party_index, (prefer_full_restore) ? 3 : 7, 999]) + end + if want_to_cure_status + ret[:status_cure] ||= [] + ret[:status_cure].push([item, party_index, (prefer_full_restore) ? 3 : 9]) + end + elsif ONE_STATUS_CURE_ITEMS.include?(item) + if want_to_cure_status + ret[:status_cure] ||= [] + ret[:status_cure].push([item, party_index, 5]) + end + elsif ALL_STATUS_CURE_ITEMS.include?(item) + if want_to_cure_status + ret[:status_cure] ||= [] + ret[:status_cure].push([item, party_index, 7]) + end + elsif ONE_STAT_RAISE_ITEMS.include?(item) + stat_data = ONE_STAT_RAISE_ITEMS[item] + if battler && stat_raise_worthwhile?(@battlers[battler.index], stat_data[0]) + ret[:stat_raise] ||= [] + ret[:stat_raise].push([item, party_index, battler.stages[stat_data[0]], stat_data[1]]) + end + elsif ALL_STATS_RAISE_ITEMS.include?(item) + if battler + ret[:all_stats_raise] ||= [] + ret[:all_stats_raise].push([item, party_index]) + end + elsif REVIVE_ITEMS.include?(item) + ret[:revive] ||= [] + ret[:revive].push([item, party_index, REVIVE_ITEMS[item]]) + end + return ret + end end diff --git a/Data/Scripts/011_Battle/005_AI/003_AI_Switch.rb b/Data/Scripts/011_Battle/005_AI/003_AI_Switch.rb index ef396e8f4..cd0a96936 100644 --- a/Data/Scripts/011_Battle/005_AI/003_AI_Switch.rb +++ b/Data/Scripts/011_Battle/005_AI/003_AI_Switch.rb @@ -1,7 +1,4 @@ class Battle::AI - #============================================================================= - # Decide whether the opponent should switch Pokémon - #============================================================================= # Called by the AI's def pbDefaultChooseEnemyCommand, and by def pbChooseMove # if the only moves known are bad ones (the latter forces a switch). Also # aliased by the Battle Palace and Battle Arena. @@ -119,8 +116,8 @@ class Battle::AI # Predict effectiveness of foe's last used move against pkmn each_foe_battler(@user.side) do |b, i| next if !b.battler.lastMoveUsed - move_data = GameData::Move.get(b.battler.lastMoveUsed) - next if move_data.status? + move_data = GameData::Move.try_get(b.battler.lastMoveUsed) + next if !move_data || move_data.status? move_type = move_data.type eff = Effectiveness.calculate(move_type, *pkmn_types) score -= move_data.power * eff / 5 @@ -146,6 +143,7 @@ class Battle::AI ret = 0 # Stealth Rock if @battle.sides[side].effects[PBEffects::StealthRock] && GameData::Type.exists?(:ROCK) + pkmn_types = pkmn.types eff = Effectiveness.calculate(:ROCK, *pkmn_types) ret += pkmn.totalhp * eff / 8 if !Effectiveness.ineffective?(eff) end @@ -227,7 +225,7 @@ Battle::AI::Handlers::ShouldSwitch.add(:cure_status_problem_by_switching_out, next false if !battler.ability_active? # Don't try to cure a status problem/heal a bit of HP if entry hazards will # KO the battler if it switches back in - entry_hazard_damage = ai.calculate_entry_hazard_damage(battler.pkmn, battler.side) + entry_hazard_damage = ai.calculate_entry_hazard_damage(battler.pokemon, battler.side) next false if entry_hazard_damage >= battler.hp # Check specific abilities single_status_cure = { @@ -247,8 +245,8 @@ Battle::AI::Handlers::ShouldSwitch.add(:cure_status_problem_by_switching_out, # Don't bother curing a poisoning if Toxic Spikes will just re-poison the # battler when it switches back in if battler.status == :POISON && reserves.none? { |pkmn| pkmn.hasType?(:POISON) } - next false if battle.field.effects[PBEffectS::ToxicSpikes] == 2 - next false if battle.field.effects[PBEffectS::ToxicSpikes] == 1 && battler.statusCount == 0 + next false if battle.field.effects[PBEffects::ToxicSpikes] == 2 + next false if battle.field.effects[PBEffects::ToxicSpikes] == 1 && battler.statusCount == 0 end # Not worth curing status problems that still allow actions if at high HP next false if battler.hp >= battler.totalhp / 2 && ![:SLEEP, :FROZEN].include?(battler.status) @@ -281,7 +279,7 @@ Battle::AI::Handlers::ShouldSwitch.add(:wish_healing, next false if battler.totalhp - battler.hp >= amt * 2 / 3 reserve_wants_healing_more = false reserves.each do |pkmn| - entry_hazard_damage = calculate_entry_hazard_damage(pkmn, battler.index & 1) + entry_hazard_damage = ai.calculate_entry_hazard_damage(pkmn, battler.index & 1) next if entry_hazard_damage >= pkmn.hp reserve_wants_healing_more = (pkmn.totalhp - pkmn.hp - entry_hazard_damage >= amt * 2 / 3) break if reserve_wants_healing_more @@ -331,7 +329,7 @@ Battle::AI::Handlers::ShouldSwitch.add(:yawning, next if b.ability_active? && Battle::AbilityEffects.triggerCertainSwitching(b.ability, b, battle) next if b.item_active? && Battle::ItemEffects.triggerCertainSwitching(b.item, b, battle) next if Settings::MORE_TYPE_EFFECTS && b.has_type?(:GHOST) - next if b.trappedInBattle? # Relevant trapping effects are checked above + next if b.battler.trappedInBattle? # Relevant trapping effects are checked above if battler.ability_active? trapping = Battle::AbilityEffects.triggerTrappingByTarget(battler.ability, b, battler.battler, battle) break if trapping @@ -344,7 +342,7 @@ Battle::AI::Handlers::ShouldSwitch.add(:yawning, next false if trapping end # Doesn't have sufficiently raised stats that would be lost by switching - next false if battler.stages.any? { |val| val >= 2 } + next false if battler.stages.any? { |key, val| val >= 2 } PBDebug.log_ai("#{battler.name} wants to switch because it is yawning and can't do anything while asleep") next true } @@ -376,7 +374,7 @@ Battle::AI::Handlers::ShouldSwitch.add(:asleep, next if b.ability_active? && Battle::AbilityEffects.triggerCertainSwitching(b.ability, b, battle) next if b.item_active? && Battle::ItemEffects.triggerCertainSwitching(b.item, b, battle) next if Settings::MORE_TYPE_EFFECTS && b.has_type?(:GHOST) - next if b.trappedInBattle? # Relevant trapping effects are checked above + next if b.battler.trappedInBattle? # Relevant trapping effects are checked above if battler.ability_active? trapping = Battle::AbilityEffects.triggerTrappingByTarget(battler.ability, b, battler.battler, battle) break if trapping @@ -389,7 +387,7 @@ Battle::AI::Handlers::ShouldSwitch.add(:asleep, next false if trapping end # Doesn't have sufficiently raised stats that would be lost by switching - next false if battler.stages.any? { |val| val >= 2 } + next false if battler.stages.any? { |key, val| val >= 2 } # 50% chance to not bother next false if ai.pbAIRandom(100) < 50 PBDebug.log_ai("#{battler.name} wants to switch because it is asleep and can't do anything") @@ -430,7 +428,7 @@ Battle::AI::Handlers::ShouldSwitch.add(:foe_has_wonder_guard, next false if battler.battler.hasMoldBreaker? non_wonder_guard_foe_exists = false has_super_effective_move = false - foe_types = b.pbTypes(true) + foe_types = battler.pbTypes(true) next false if foe_types.length == 0 ai.each_foe_battler(battler.side) do |b, i| if !b.has_active_ability?(:WONDERGUARD) @@ -479,14 +477,14 @@ Battle::AI::Handlers::ShouldSwitch.add(:foe_has_wonder_guard, # Check reserves for super-effective moves; only switch if there are any reserve_has_super_effective_move = false reserves.each do |pkmn| - pkmn.moves.each do |m| - next if m.status_move? + pkmn.moves.each do |move| + next if move.status_move? if ["IgnoreTargetAbility", "CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(move.function_code) reserve_has_super_effective_move = true break end - eff = Effectiveness.calculate(m.type, *foe_types) + eff = Effectiveness.calculate(move.type, *foe_types) if Effectiveness.super_effective?(eff) reserve_has_super_effective_move = true break @@ -525,15 +523,15 @@ Battle::AI::Handlers::ShouldSwitch.add(:absorb_foe_move, # Get the foe move with the highest power (or a random damaging move) foe_moves = [] ai.each_foe_battler(battler.side) do |b, i| - b.moves.each do |m| - next if m.statusMove? + b.moves.each do |move| + next if move.statusMove? # TODO: Improve on m_power with STAB and attack stat/stages and certain # other damage-altering effects, including base power calculations # for moves with variable power. - m_power = m.power - m_power = battler.hp if m.is_a?(Battle::Move::OHKO) + m_power = move.power + m_power = battler.hp if move.is_a?(Battle::Move::OHKO) m_type = move.pbCalcType(b.battler) - foe_moves.push([m_power, m_type, m]) + foe_moves.push([m_power, m_type, move]) end end next false if foe_moves.empty? @@ -612,7 +610,7 @@ Battle::AI::Handlers::ShouldSwitch.add(:high_damage_from_foe, big_threat = false ai.each_foe_battler(battler.side) do |b, i| next if (b.level - battler.level).abs > 5 - next if !b.battler.lastMoveUsed + next if !b.battler.lastMoveUsed || !GameData::Move.exists?(b.battler.lastMoveUsed) move_data = GameData::Move.get(b.battler.lastMoveUsed) next if move_data.status? eff = battler.effectiveness_of_type_against_battler(move_data.type, b) @@ -643,7 +641,7 @@ Battle::AI::Handlers::ShouldNotSwitch.add(:lethal_entry_hazards, proc { |battler, reserves, ai, battle| next false if battle.rules["suddendeath"] # Check whether battler will faint from entry hazard(s) - entry_hazard_damage = ai.calculate_entry_hazard_damage(battler.pkmn, battler.side) + entry_hazard_damage = ai.calculate_entry_hazard_damage(battler.pokemon, battler.side) next false if entry_hazard_damage < battler.hp # Check for Rapid Spin reserve_can_remove_hazards = false @@ -668,7 +666,7 @@ Battle::AI::Handlers::ShouldNotSwitch.add(:battler_has_super_effective_move, proc { |battler, reserves, ai, battle| next false if battle.rules["suddendeath"] has_super_effective_move = false - battler.eachMove do |move| + battler.battler.eachMove do |move| next if move.pp == 0 && move.total_pp > 0 next if move.statusMove? # TODO: next if move is unusable? This would be complicated to implement. diff --git a/Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb b/Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb index a559aaacd..1f391af2b 100644 --- a/Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb +++ b/Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb @@ -109,7 +109,7 @@ class Battle::AI strength = b.effects[PBEffects::FollowMe] end end - return new_target if new_target + return new_target if new_target >= 0 calc_type = @move.rough_type priority.each do |b| next if b.index == @user.index @@ -122,7 +122,7 @@ class Battle::AI end break if new_target >= 0 end - return new_target + return (new_target >= 0) ? new_target : nil end def add_move_to_choices(choices, idxMove, score, idxTarget = -1) @@ -142,6 +142,7 @@ class Battle::AI case move.function when "UseLastMoveUsed" if @battle.lastMoveUsed && + GameData::Move.exists?(@battle.lastMoveUsed) && !move.moveBlacklist.include?(GameData::Move.get(@battle.lastMoveUsed).function_code) move = Battle::Move.from_pokemon_move(@battle, Pokemon::Move.new(@battle.lastMoveUsed)) end @@ -159,9 +160,10 @@ class Battle::AI @target&.refresh_battler if @target && @move.function == "UseLastMoveUsedByTarget" if @target.battler.lastRegularMoveUsed && + GameData::Move.exists?(@target.battler.lastRegularMoveUsed) && GameData::Move.get(@target.battler.lastRegularMoveUsed).has_flag?("CanMirrorMove") - mov = Battle::Move.from_pokemon_move(@battle, Pokemon::Move.new(@target.battler.lastRegularMoveUsed)) @battle.moldBreaker = @user.has_mold_breaker? + mov = Battle::Move.from_pokemon_move(@battle, Pokemon::Move.new(@target.battler.lastRegularMoveUsed)) @move.set_up(mov) end end diff --git a/Data/Scripts/011_Battle/005_AI/009_AI_Roles.rb b/Data/Scripts/011_Battle/005_AI/009_AI_Roles.rb index 01bd3f4da..208157ff5 100644 --- a/Data/Scripts/011_Battle/005_AI/009_AI_Roles.rb +++ b/Data/Scripts/011_Battle/005_AI/009_AI_Roles.rb @@ -6,24 +6,24 @@ class Battle::AI # opponent (only when deciding whether to switch mon in) - this # comparison should be calculated when needed instead of being a role. module BattleRole - PHAZER = 0 - CLERIC = 1 - STALLBREAKER = 2 - STATUSABSORBER = 3 - BATONPASSER = 4 - SPINNER = 5 - FIELDSETTER = 6 - WEATHERSETTER = 7 - SWEEPER = 8 - PIVOT = 9 - PHYSICALWALL = 10 - SPECIALWALL = 11 - TANK = 12 - TRAPPER = 13 - SCREENER = 14 - ACE = 15 - LEAD = 16 - SECOND = 17 + PHAZER = 0 + CLERIC = 1 + STALL_BREAKER = 2 + STATUS_ABSORBER = 3 + BATON_PASSER = 4 + SPINNER = 5 + FIELD_SETTER = 6 + WEATHER_SETTER = 7 + SWEEPER = 8 + PIVOT = 9 + PHYSICAL_WALL = 10 + SPECIAL_WALL = 11 + TANK = 12 + TRAPPER = 13 + SCREENER = 14 + ACE = 15 + LEAD = 16 + SECOND = 17 end #----------------------------------------------------------------------------- @@ -51,22 +51,22 @@ class Battle::AI when "CureUserPartyStatus" # Aromatherapy/Heal Bell ret.push(BattleRole::CLERIC) when "DisableTargetStatusMoves" # Taunt - ret.push(BattleRole::STALLBREAKER) + ret.push(BattleRole::STALL_BREAKER) when "HealUserPositionNextTurn" # Wish ret.push(BattleRole::CLERIC) if pkmn.ev[:HP] == Pokemon::EV_STAT_LIMIT when "HealUserFullyAndFallAsleep" # Rest - ret.push(BattleRole::STATUSABSORBER) + ret.push(BattleRole::STATUS_ABSORBER) when "SwitchOutUserPassOnEffects" # Baton Pass - ret.push(BattleRole::BATONPASSER) + ret.push(BattleRole::BATON_PASSER) when "SwitchOutUserStatusMove", "SwitchOutUserDamagingMove" # Teleport (Gen 8+), U-turn hasPivotMove = true when "RemoveUserBindingAndEntryHazards" # Rapid Spin ret.push(BattleRole::SPINNER) when "StartElectricTerrain", "StartGrassyTerrain", "StartMistyTerrain", "StartPsychicTerrain" # Terrain moves - ret.push(BattleRole::FIELDSETTER) + ret.push(BattleRole::FIELD_SETTER) else - ret.push(BattleRole::WEATHERSETTER) if move.is_a?(Battle::Move::WeatherMove) + ret.push(BattleRole::WEATHER_SETTER) if move.is_a?(Battle::Move::WeatherMove) end end @@ -81,10 +81,10 @@ class Battle::AI ret.push(BattleRole::PIVOT) if hasPivotMove if pkmn.nature.stat_changes.any? { |change| change[0] == :DEFENSE && change[1] > 0 } && !pkmn.nature.stat_changes.any? { |change| change[0] == :DEFENSE && change[1] < 0 } - ret.push(BattleRole::PHYSICALWALL) if pkmn.ev[:DEFENSE] == Pokemon::EV_STAT_LIMIT + ret.push(BattleRole::PHYSICAL_WALL) if pkmn.ev[:DEFENSE] == Pokemon::EV_STAT_LIMIT elsif pkmn.nature.stat_changes.any? { |change| change[0] == :SPECIAL_DEFENSE && change[1] > 0 } && !pkmn.nature.stat_changes.any? { |change| change[0] == :SPECIAL_DEFENSE && change[1] < 0 } - ret.push(BattleRole::SPECIALWALL) if pkmn.ev[:SPECIAL_DEFENSE] == Pokemon::EV_STAT_LIMIT + ret.push(BattleRole::SPECIAL_WALL) if pkmn.ev[:SPECIAL_DEFENSE] == Pokemon::EV_STAT_LIMIT end else ret.push(BattleRole::TANK) if pkmn.ev[:HP] == Pokemon::EV_STAT_LIMIT @@ -96,14 +96,14 @@ class Battle::AI ret.push(BattleRole::PIVOT) when :GUTS, :QUICKFEET, :FLAREBOOST, :TOXICBOOST, :NATURALCURE, :MAGICGUARD, :MAGICBOUNCE, :HYDRATION - ret.push(BattleRole::STATUSABSORBER) + ret.push(BattleRole::STATUS_ABSORBER) when :SHADOWTAG, :ARENATRAP, :MAGNETPULL ret.push(BattleRole::TRAPPER) when :DROUGHT, :DRIZZLE, :SANDSTREAM, :SNOWWARNING, :PRIMORDIALSEA, :DESOLATELAND, :DELTASTREAM - ret.push(BattleRole::WEATHERSETTER) + ret.push(BattleRole::WEATHER_SETTER) when :GRASSYSURGE, :ELECTRICSURGE, :MISTYSURGE, :PSYCHICSURGE - ret.push(BattleRole::FIELDSETTER) + ret.push(BattleRole::FIELD_SETTER) end # Check for items indicative of particular roles @@ -113,14 +113,14 @@ class Battle::AI when :ASSAULTVEST ret.push(BattleRole::TANK) when :CHOICEBAND, :CHOICESPECS - ret.push(BattleRole::STALLBREAKER) + ret.push(BattleRole::STALL_BREAKER) ret.push(BattleRole::SWEEPER) if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT when :CHOICESCARF ret.push(BattleRole::SWEEPER) if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT when :TOXICORB, :FLAMEORB - ret.push(BattleRole::STATUSABSORBER) + ret.push(BattleRole::STATUS_ABSORBER) when :TERRAINEXTENDER - ret.push(BattleRole::FIELDSETTER) + ret.push(BattleRole::FIELD_SETTER) end # Check for position in team, level relative to other levels in team diff --git a/Data/Scripts/011_Battle/005_AI/020_AI_Move_EffectScoresGeneric.rb b/Data/Scripts/011_Battle/005_AI/020_AI_Move_EffectScoresGeneric.rb index 5e9577451..3c34d3be5 100644 --- a/Data/Scripts/011_Battle/005_AI/020_AI_Move_EffectScoresGeneric.rb +++ b/Data/Scripts/011_Battle/005_AI/020_AI_Move_EffectScoresGeneric.rb @@ -509,9 +509,9 @@ class Battle::AI next if target_speed > b_speed * 2.5 # Much too fast to reasonably be overtaken if target_speed > b_speed if target_speed < b_speed * 2 / (decrement + 2) - score += 15 * inc_mult # Target will become slower than b + score += 15 * dec_mult # Target will become slower than b else - score += 8 * inc_mult + score += 8 * dec_mult end break end diff --git a/Data/Scripts/011_Battle/005_AI/070_AI_MoveHandlers_GeneralModifiers.rb b/Data/Scripts/011_Battle/005_AI/070_AI_MoveHandlers_GeneralModifiers.rb index 68ce892b9..ea234c7ad 100644 --- a/Data/Scripts/011_Battle/005_AI/070_AI_MoveHandlers_GeneralModifiers.rb +++ b/Data/Scripts/011_Battle/005_AI/070_AI_MoveHandlers_GeneralModifiers.rb @@ -15,8 +15,8 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:shiny_target, #=============================================================================== # Prefer Shadow moves (for flavour). #=============================================================================== -Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:shadow_moves, - proc { |score, move, user, target, ai, battle| +Battle::AI::Handlers::GeneralMoveScore.add(:shadow_moves, + proc { |score, move, user, ai, battle| if move.rough_type == :SHADOW old_score = score score += 10 @@ -51,8 +51,8 @@ Battle::AI::Handlers::GeneralMoveScore.add(:thawing_move_when_frozen, # - the target is predicted to be knocked out by the move. # TODO: Less prefer a priority move if any foe knows Quick Guard? #=============================================================================== -Battle::AI::Handlers::GeneralMoveScore.add(:priority_move_against_faster_target, - proc { |score, move, user, ai, battle| +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:priority_move_against_faster_target, + proc { |score, move, user, target, ai, battle| if ai.trainer.high_skill? && target.faster_than?(user) && move.rough_priority(user) > 0 # User is at risk of being knocked out if ai.trainer.has_skill_flag?("HPAware") && user.hp < user.totalhp / 3 @@ -61,7 +61,7 @@ Battle::AI::Handlers::GeneralMoveScore.add(:priority_move_against_faster_target, PBDebug.log_score_change(score - old_score, "user at low HP and move has priority over faster target") end # Target is predicted to be knocked out by the move - if move.damaging_move? && move.rough_damage >= target.hp + if move.damagingMove? && move.rough_damage >= target.hp old_score = score score += 8 PBDebug.log_score_change(score - old_score, "target at low HP and move has priority over faster target") @@ -221,7 +221,7 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_semi_invulnerabl if ai.trainer.medium_skill? && move.rough_accuracy > 0 && (target.battler.semiInvulnerable? || target.effects[PBEffects::SkyDrop] >= 0) next score if user.has_active_ability?(:NOGUARD) || target.has_active_ability?(:NOGUARD) - priority = move.rough_priority + priority = move.rough_priority(user) if priority > 0 || (priority == 0 && user.faster_than?(target)) # User goes first miss = true if ai.trainer.high_skill? @@ -328,6 +328,13 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:thawing_move_against_fr } ) +#=============================================================================== +# +#=============================================================================== +# TODO: Check all effects that trigger upon using a move, including per-hit +# stuff in def pbEffectsOnMakingHit and end-of-move stuff in def +# pbEffectsAfterMove. + #=============================================================================== # #=============================================================================== @@ -350,7 +357,7 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:knocking_out_a_destiny_ proc { |score, move, user, target, ai, battle| if (ai.trainer.has_skill_flag?("HPAware") || ai.trainer.high_skill?) && move.damagingMove? && (target.effects[PBEffects::DestinyBond] || target.effects[PBEffects::Grudge]) - priority = move.rough_priority + priority = move.rough_priority(user) if priority > 0 || (priority == 0 && user.faster_than?(target)) # User goes first if move.rough_damage > target.hp * 1.1 # Predicted to KO the target old_score = score diff --git a/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb b/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb index 700bc6e71..68b17dd57 100644 --- a/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb +++ b/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb @@ -60,6 +60,32 @@ class Battle::AI::AIBattler # Returns how much damage this battler will take at the end of this round. def rough_end_of_round_damage ret = 0 + # Weather + weather = battler.effectiveWeather + if @ai.battle.field.weatherDuration == 1 + weather = @ai.battle.field.defaultWeather + weather = :None if @ai.battle.allBattlers.any? { |b| b.hasActiveAbility?([:CLOUDNINE, :AIRLOCK]) } + weather = :None if [:Sun, :Rain, :HarshSun, :HeavyRain].include?(weather) && has_active_item?(:UTILITYUMBRELLA) + end + case weather + when :Sandstorm + ret += [self.totalhp / 16, 1].max if battler.takesSandstormDamage? + when :Hail + ret += [self.totalhp / 16, 1].max if battler.takesHailDamage? + when :ShadowSky + ret += [self.totalhp / 16, 1].max if battler.takesShadowSkyDamage? + end + case ability_id + when :DRYSKIN + ret += [self.totalhp / 8, 1].max if [:Sun, :HarshSun].include?(weather) && battler.takesIndirectDamage? + ret -= [self.totalhp / 8, 1].max if [:Rain, :HeavyRain].include?(weather) && battler.canHeal? + when :ICEBODY + ret -= [self.totalhp / 16, 1].max if weather == :Hail && battler.canHeal? + when :RAINDISH + ret -= [self.totalhp / 16, 1].max if [:Rain, :HeavyRain].include?(weather) && battler.canHeal? + when :SOLARPOWER + ret += [self.totalhp / 8, 1].max if [:Sun, :HarshSun].include?(weather) && battler.takesIndirectDamage? + end # Future Sight/Doom Desire # TODO # Wish @@ -69,38 +95,38 @@ class Battle::AI::AIBattler # Sea of Fire if @ai.battle.sides[@side].effects[PBEffects::SeaOfFire] > 1 && battler.takesIndirectDamage? && !has_type?(:FIRE) - ret += self.totalhp / 8 + ret += [self.totalhp / 8, 1].max end # Grassy Terrain (healing) if @ai.battle.field.terrain == :Grassy && battler.affectedByTerrain? && battler.canHeal? - ret -= [battler.totalhp / 16, 1].max + ret -= [self.totalhp / 16, 1].max end # Leftovers/Black Sludge if has_active_item?(:BLACKSLUDGE) if has_type?(:POISON) - ret -= [battler.totalhp / 16, 1].max if battler.canHeal? + ret -= [self.totalhp / 16, 1].max if battler.canHeal? else - ret += [battler.totalhp / 8, 1].max if battler.takesIndirectDamage? + ret += [self.totalhp / 8, 1].max if battler.takesIndirectDamage? end elsif has_active_item?(:LEFTOVERS) - ret -= [battler.totalhp / 16, 1].max if battler.canHeal? + ret -= [self.totalhp / 16, 1].max if battler.canHeal? end # Aqua Ring if self.effects[PBEffects::AquaRing] && battler.canHeal? - amt = battler.totalhp / 16 + amt = self.totalhp / 16 amt = (amt * 1.3).floor if has_active_item?(:BIGROOT) ret -= [amt, 1].max end # Ingrain if self.effects[PBEffects::Ingrain] && battler.canHeal? - amt = battler.totalhp / 16 + amt = self.totalhp / 16 amt = (amt * 1.3).floor if has_active_item?(:BIGROOT) ret -= [amt, 1].max end # Leech Seed if self.effects[PBEffects::LeechSeed] >= 0 if battler.takesIndirectDamage? - ret += [battler.totalhp / 8, 1].max if battler.takesIndirectDamage? + ret += [self.totalhp / 8, 1].max if battler.takesIndirectDamage? end else @ai.each_battler do |b, i| @@ -112,16 +138,16 @@ class Battle::AI::AIBattler end # Hyper Mode (Shadow Pokémon) if battler.inHyperMode? - ret += [battler.totalhp / 24, 1].max + ret += [self.totalhp / 24, 1].max end # Poison/burn/Nightmare if self.status == :POISON if has_active_ability?(:POISONHEAL) - ret -= [battler.totalhp / 8, 1].max if battler.canHeal? + ret -= [self.totalhp / 8, 1].max if battler.canHeal? elsif battler.takesIndirectDamage? mult = 2 mult = [self.effects[PBEffects::Toxic] + 1, 16].min if self.statusCount > 0 # Toxic - ret += [mult * battler.totalhp / 16, 1].max + ret += [mult * self.totalhp / 16, 1].max end elsif self.status == :BURN if battler.takesIndirectDamage? @@ -130,11 +156,11 @@ class Battle::AI::AIBattler ret += [amt, 1].max end elsif battler.asleep? && self.statusCount > 1 && self.effects[PBEffects::Nightmare] - ret += [battler.totalhp / 4, 1].max if battler.takesIndirectDamage? + ret += [self.totalhp / 4, 1].max if battler.takesIndirectDamage? end # Curse if self.effects[PBEffects::Curse] - ret += [battler.totalhp / 4, 1].max if battler.takesIndirectDamage? + ret += [self.totalhp / 4, 1].max if battler.takesIndirectDamage? end # Trapping damage if self.effects[PBEffects::Trapping] > 1 && battler.takesIndirectDamage? @@ -150,12 +176,12 @@ class Battle::AI::AIBattler if battler.asleep? && self.statusCount > 1 && battler.takesIndirectDamage? @ai.each_battler do |b, i| next if i == @index || !b.battler.near?(battler) || !b.has_active_ability?(:BADDREAMS) - ret += [battler.totalhp / 8, 1].max + ret += [self.totalhp / 8, 1].max end end # Sticky Barb if has_active_item?(:STICKYBARB) && battler.takesIndirectDamage? - ret += [battler.totalhp / 8, 1].max + ret += [self.totalhp / 8, 1].max end return ret end @@ -242,7 +268,7 @@ class Battle::AI::AIBattler end def has_mold_breaker? - return @ai.move.function == "IgnoreTargetAbility" || battler.hasMoldBreaker? + return battler.hasMoldBreaker? end #----------------------------------------------------------------------------- @@ -321,7 +347,7 @@ class Battle::AI::AIBattler # Other certain trapping effects return false if battler.trappedInBattle? # Trapping abilities/items - ai.each_foe_battler(side) do |b, i| + @ai.each_foe_battler(side) do |b, i| if b.ability_active? && Battle::AbilityEffects.triggerTrappingByTarget(b.ability, battler, b.battler, @ai.battle) return false @@ -342,20 +368,21 @@ class Battle::AI::AIBattler case ability_id when :GUTS return true if ![:SLEEP, :FROZEN].include?(new_status) && - stat_raise_worthwhile?(self, :ATTACK, true) + @ai.stat_raise_worthwhile?(self, :ATTACK, true) when :MARVELSCALE - return true if stat_raise_worthwhile?(self, :DEFENSE, true) + return true if @ai.stat_raise_worthwhile?(self, :DEFENSE, true) when :QUICKFEET return true if ![:SLEEP, :FROZEN].include?(new_status) && - stat_raise_worthwhile?(self, :SPEED, true) + @ai.stat_raise_worthwhile?(self, :SPEED, true) when :FLAREBOOST - return true if new_status == :BURN && stat_raise_worthwhile?(self, :SPECIAL_ATTACK, true) + return true if new_status == :BURN && @ai.stat_raise_worthwhile?(self, :SPECIAL_ATTACK, true) when :TOXICBOOST - return true if new_status == :POISON && stat_raise_worthwhile?(self, :ATTACK, true) + return true if new_status == :POISON && @ai.stat_raise_worthwhile?(self, :ATTACK, true) when :POISONHEAL return true if new_status == :POISON when :MAGICGUARD # Want a harmless status problem to prevent getting a harmful one - return true if new_status == :POISON || (new_status == :BURN && !stat_raise_worthwhile?(self, :ATTACK, true)) + return true if new_status == :POISON || + (new_status == :BURN && !@ai.stat_raise_worthwhile?(self, :ATTACK, true)) end end return true if new_status == :SLEEP && check_for_move { |m| m.usableWhenAsleep? } @@ -924,12 +951,12 @@ class Battle::AI::AIBattler ret = 0 if gender == 2 when :FRIENDGUARD, :HEALER, :SYMBOISIS, :TELEPATHY has_ally = false - each_ally(@side) { |b, i| has_ally = true } + @ai.each_ally(@side) { |b, i| has_ally = true } ret = 0 if !has_ally when :GALEWINGS ret = 0 if !check_for_move { |m| m.type == :FLYING } when :HUGEPOWER, :PUREPOWER - ret = 0 if !ai.stat_raise_worthwhile?(self, :ATTACK, true) + ret = 0 if !@ai.stat_raise_worthwhile?(self, :ATTACK, true) when :IRONFIST ret = 0 if !check_for_move { |m| m.punchingMove? } when :LIQUIDVOICE @@ -953,7 +980,7 @@ class Battle::AI::AIBattler when :SKILLLINK ret = 0 if !check_for_move { |m| m.is_a?(Battle::Move::HitTwoToFiveTimes) } when :STEELWORKER - ret = 0 if !has_damaging_move_of_type?(:GRASS) + ret = 0 if !has_damaging_move_of_type?(:STEEL) when :SWARM ret = 0 if !has_damaging_move_of_type?(:BUG) when :TORRENT diff --git a/Data/Scripts/011_Battle/005_AI/103_AIMove.rb b/Data/Scripts/011_Battle/005_AI/103_AIMove.rb index e0ec221a9..c35728768 100644 --- a/Data/Scripts/011_Battle/005_AI/103_AIMove.rb +++ b/Data/Scripts/011_Battle/005_AI/103_AIMove.rb @@ -149,17 +149,40 @@ class Battle::AI::AIMove ((@ai.battle.pbCheckGlobalAbility(:DARKAURA) && calc_type == :DARK) || (@ai.battle.pbCheckGlobalAbility(:FAIRYAURA) && calc_type == :FAIRY)) if @ai.battle.pbCheckGlobalAbility(:AURABREAK) - multipliers[:power_multiplier] *= 2 / 3.0 + multipliers[:power_multiplier] *= 3 / 4.0 else multipliers[:power_multiplier] *= 4 / 3.0 end end # Ability effects that alter damage if user.ability_active? - # NOTE: These abilities aren't suitable for checking at the start of the - # round. - abilityBlacklist = [:ANALYTIC, :SNIPER, :TINTEDLENS, :AERILATE, :PIXILATE, :REFRIGERATE] - if !abilityBlacklist.include?(user.ability_id) + case user.ability_id + when :AERILATE, :GALVANIZE, :PIXILATE, :REFRIGERATE + multipliers[:power_multiplier] *= 1.2 if type == :NORMAL # NOTE: Not calc_type. + when :ANALYTIC + if rough_priority(user) <= 0 + user_faster = false + @ai.each_battler do |b, i| + user_faster = (i != user.index && user.faster_than?(b)) + break if user_faster + end + multipliers[:power_multiplier] *= 1.3 if !user_faster + end + when :NEUROFORCE + if Effectiveness.super_effective_type?(calc_type, *target.pbTypes(true)) + multipliers[:final_damage_multiplier] *= 1.25 + end + when :NORMALIZE + multipliers[:power_multiplier] *= 1.2 if Settings::MECHANICS_GENERATION >= 7 + when :SNIPER + multipliers[:final_damage_multiplier] *= 1.5 if is_critical + when :STAKEOUT + # TODO: multipliers[:attack_multiplier] *= 2 if target switches out + when :TINTEDLENS + if Effectiveness.resistant_type?(calc_type, *target.pbTypes(true)) + multipliers[:final_damage_multiplier] *= 2 + end + else Battle::AbilityEffects.triggerDamageCalcFromUser( user.ability, user_battler, target_battler, @move, multipliers, base_dmg, calc_type ) @@ -173,10 +196,12 @@ class Battle::AI::AIMove ) end if target.ability_active? - # NOTE: These abilities aren't suitable for checking at the start of the - # round. - abilityBlacklist = [:FILTER, :SOLIDROCK] - if !abilityBlacklist.include?(target.ability_id) + case target.ability_id + when :FILTER, :SOLIDROCK + if Effectiveness.super_effective_type?(calc_type, *target.pbTypes(true)) + multipliers[:final_damage_multiplier] *= 0.75 + end + else Battle::AbilityEffects.triggerDamageCalcFromTarget( target.ability, user_battler, target_battler, @move, multipliers, base_dmg, calc_type ) @@ -197,13 +222,15 @@ class Battle::AI::AIMove end end # Item effects that alter damage - # NOTE: Type-boosting gems aren't suitable for checking at the start of the - # round. if user.item_active? - # NOTE: These items aren't suitable for checking at the start of the - # round. - itemBlacklist = [:EXPERTBELT, :LIFEORB] - if !itemBlacklist.include?(user.item_id) + case user.item_id + when :EXPERTBELT + if Effectiveness.super_effective_type?(calc_type, *target.pbTypes(true)) + multipliers[:final_damage_multiplier] *= 1.2 + end + when :LIFEORB + multipliers[:final_damage_multiplier] *= 1.3 + else Battle::ItemEffects.triggerDamageCalcFromUser( user.item, user_battler, target_battler, @move, multipliers, base_dmg, calc_type ) @@ -443,7 +470,9 @@ class Battle::AI::AIMove # Item effects that alter accuracy calculation if user.item_active? if user.item == :ZOOMLENS - mods[:accuracy_multiplier] *= 1.2 if target.faster_than?(user) + if rough_priority(user) <= 0 + mods[:accuracy_multiplier] *= 1.2 if target.faster_than?(user) + end else Battle::ItemEffects.triggerAccuracyCalcFromUser( user.item, modifiers, user_battler, target_battler, @move, calc_type @@ -534,12 +563,12 @@ class Battle::AI::AIMove # 0: Regular additional effect chance or isn't an additional effect # -999: Additional effect will be negated # Other: Amount to add to a move's score - def get_score_change_for_additional_effect(user, target) + def get_score_change_for_additional_effect(user, target = nil) # Doesn't have an additional effect return 0 if @move.addlEffect == 0 # Additional effect will be negated return -999 if user.has_active_ability?(:SHEERFORCE) - return -999 if user.index != target.index && + return -999 if target && user.index != target.index && target.has_active_ability?(:SHIELDDUST) && !@ai.battle.moldBreaker # Prefer if the additional effect will have an increased chance of working return 5 if @move.addlEffect < 100 && diff --git a/Data/Scripts/011_Battle/005b_AI move function codes/051_AI_MoveHandlers_Misc.rb b/Data/Scripts/011_Battle/005b_AI move function codes/051_AI_MoveHandlers_Misc.rb index a2521b756..431b1fbfa 100644 --- a/Data/Scripts/011_Battle/005b_AI move function codes/051_AI_MoveHandlers_Misc.rb +++ b/Data/Scripts/011_Battle/005b_AI move function codes/051_AI_MoveHandlers_Misc.rb @@ -1,8 +1,3 @@ -#=============================================================================== -# -#=============================================================================== -# Struggle - #=============================================================================== # #=============================================================================== @@ -574,9 +569,12 @@ Battle::AI::Handlers::MoveEffectScore.add("UserMakeSubstitute", #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("RemoveUserBindingAndEntryHazards", proc { |score, move, user, ai, battle| - add_effect = move.get_score_change_for_additional_effect(user, target) - next score if add_effect == -999 # Additional effect will be negated - score += add_effect + # Score for raising user's Speed + if Settings::MECHANICS_GENERATION >= 8 + score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserSpeed1", + score, move, user, ai, battle) + end + # Score for removing various effects score += 10 if user.effects[PBEffects::Trapping] > 0 score += 15 if user.effects[PBEffects::LeechSeed] >= 0 if battle.pbAbleNonActiveCount(user.idxOwnSide) > 0 diff --git a/Data/Scripts/011_Battle/005b_AI move function codes/054_AI_MoveHandlers_MoveAttributes.rb b/Data/Scripts/011_Battle/005b_AI move function codes/054_AI_MoveHandlers_MoveAttributes.rb index c4a40a708..be3c2c498 100644 --- a/Data/Scripts/011_Battle/005b_AI move function codes/054_AI_MoveHandlers_MoveAttributes.rb +++ b/Data/Scripts/011_Battle/005b_AI move function codes/054_AI_MoveHandlers_MoveAttributes.rb @@ -445,7 +445,7 @@ Battle::AI::Handlers::MoveEffectScore.add("EnsureNextCriticalHit", # critical hits are impossible (e.g. via Lucky Chant) crit_stage = 0 crit_stage = -1 if user.battler.pbOwnSide.effects[PBEffects::LuckyChant] > 0 - if crit_stage >= 0 && user.ability_active? && ![:MERCILESS].include?(user.ability) + if crit_stage >= 0 && user.ability_active? && ![:MERCILESS].include?(user.ability_id) crit_stage = Battle::AbilityEffects.triggerCriticalCalcFromUser(user.battler.ability, user.battler, user.battler, crit_stage) end @@ -660,6 +660,8 @@ Battle::AI::Handlers::MoveEffectScore.add("StartWeakenPhysicalDamageAgainstUserS score += 10 score += 8 if !b.check_for_move { |m| m.specialMove?(m.type) } end + # Prefer if user has Light Clay + score += 5 if user.has_active_item?(:LIGHTCLAY) next score } ) @@ -688,6 +690,8 @@ Battle::AI::Handlers::MoveEffectScore.add("StartWeakenSpecialDamageAgainstUserSi score += 10 score += 8 if !b.check_for_move { |m| m.physicalMove?(m.type) } end + # Prefer if user has Light Clay + score += 5 if user.has_active_item?(:LIGHTCLAY) next score } ) @@ -713,6 +717,8 @@ Battle::AI::Handlers::MoveEffectScore.add("StartWeakenDamageAgainstUserSideIfHai score -= (20 * (0.75 - (user.hp.to_f / user.totalhp))).to_i # -5 to -15 end end + # Prefer if user has Light Clay + score += 5 if user.has_active_item?(:LIGHTCLAY) next score + 15 } ) @@ -883,7 +889,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromDamagingMovesKingsShie # less likely to work score -= (user.effects[PBEffects::ProtectRate] - 1) * ((Settings::MECHANICS_GENERATION >= 6) ? 15 : 10) # Aegislash - score += 10 if user.battler.isSpecies?(:AEGISLASH) && user.form == 1 && + score += 10 if user.battler.isSpecies?(:AEGISLASH) && user.battler.form == 1 && user.ability == :STANCECHANGE next score } @@ -1533,7 +1539,7 @@ Battle::AI::Handlers::MoveEffectScore.add("NormalMovesBecomeElectric", normal_type_better = 0 electric_type_better = 0 ai.each_foe_battler(user.side) do |b, i| - next if move.pbPriority(b.battler) <= 0 && b.faster_than?(user) + next if move.rough_priority(b) <= 0 && b.faster_than?(user) next if !b.has_damaging_move_of_type?(:NORMAL) # Normal's effectiveness eff = user.effectiveness_of_type_against_battler(:NORMAL, b) diff --git a/Data/Scripts/011_Battle/005b_AI move function codes/057_AI_MoveHandlers_Items.rb b/Data/Scripts/011_Battle/005b_AI move function codes/057_AI_MoveHandlers_Items.rb index 413d87fed..65feab8f0 100644 --- a/Data/Scripts/011_Battle/005b_AI move function codes/057_AI_MoveHandlers_Items.rb +++ b/Data/Scripts/011_Battle/005b_AI move function codes/057_AI_MoveHandlers_Items.rb @@ -64,6 +64,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapItems", score += target_old_item_preference - target_new_item_preference # Don't prefer if user used this move in the last round score -= 15 if user.battler.lastMoveUsed && + GameData::Move.exists?(user.battler.lastMoveUsed) && GameData::Move.get(user.battler.lastMoveUsed).function_code == move.function next score } diff --git a/Data/Scripts/011_Battle/005b_AI move function codes/058_AI_MoveHandlers_ChangeMoveEffect.rb b/Data/Scripts/011_Battle/005b_AI move function codes/058_AI_MoveHandlers_ChangeMoveEffect.rb index 8cc669f10..f1797af46 100644 --- a/Data/Scripts/011_Battle/005b_AI move function codes/058_AI_MoveHandlers_ChangeMoveEffect.rb +++ b/Data/Scripts/011_Battle/005b_AI move function codes/058_AI_MoveHandlers_ChangeMoveEffect.rb @@ -265,7 +265,7 @@ Battle::AI::Handlers::MoveEffectScore.add("CounterPhysicalDamage", score += 5 if b.rough_stat(:ATTACK) > b.rough_stat(:SPECIAL_ATTACK) # Prefer if the last move the foe used was physical if ai.trainer.medium_skill? && b.battler.lastMoveUsed - score += 8 if GameData::Move.get(b.battler.lastMoveUsed).physical? + score += 8 if GameData::Move.try_get(b.battler.lastMoveUsed)&.physical? end # Prefer if the foe is taunted into using a damaging move score += 5 if b.effects[PBEffects::Taunt] > 0 @@ -297,7 +297,7 @@ Battle::AI::Handlers::MoveEffectScore.add("CounterSpecialDamage", score += 5 if b.rough_stat(:SPECIAL_ATTACK) > b.rough_stat(:ATTACK) # Prefer if the last move the foe used was special if ai.trainer.medium_skill? && b.battler.lastMoveUsed - score += 8 if GameData::Move.get(b.battler.lastMoveUsed).special? + score += 8 if GameData::Move.try_get(b.battler.lastMoveUsed)&.special? end # Prefer if the foe is taunted into using a damaging move score += 5 if b.effects[PBEffects::Taunt] > 0 @@ -327,7 +327,7 @@ Battle::AI::Handlers::MoveEffectScore.add("CounterDamagePlusHalf", has_damaging_move = true # Prefer if the last move the foe used was damaging if ai.trainer.medium_skill? && b.battler.lastMoveUsed - score += 8 if GameData::Move.get(b.battler.lastMoveUsed).damaging? + score += 8 if GameData::Move.try_get(b.battler.lastMoveUsed)&.damaging? end # Prefer if the foe is taunted into using a damaging move score += 5 if b.effects[PBEffects::Taunt] > 0 @@ -454,7 +454,7 @@ Battle::AI::Handlers::MoveEffectScore.add("WaterPledge", #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("UseLastMoveUsed", proc { |move, user, ai, battle| - next true if !battle.lastMoveUsed + next true if !battle.lastMoveUsed || !GameData::Move.exists?(battle.lastMoveUsed) next move.move.moveBlacklist.include?(GameData::Move.get(battle.lastMoveUsed).function_code) } ) @@ -468,6 +468,7 @@ Battle::AI::Handlers::MoveFailureCheck.add("UseLastMoveUsed", Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("UseLastMoveUsedByTarget", proc { |move, user, target, ai, battle| next true if !target.battler.lastRegularMoveUsed + next true if !GameData::Move.exists?(target.battler.lastRegularMoveUsed) next !GameData::Move.get(target.battler.lastRegularMoveUsed).has_flag?("CanMirrorMove") } ) diff --git a/Data/Scripts/011_Battle/005b_AI move function codes/059_AI_MoveHandlers_SwitchingActing.rb b/Data/Scripts/011_Battle/005b_AI move function codes/059_AI_MoveHandlers_SwitchingActing.rb index 4620d341e..b1b5a79ff 100644 --- a/Data/Scripts/011_Battle/005b_AI move function codes/059_AI_MoveHandlers_SwitchingActing.rb +++ b/Data/Scripts/011_Battle/005b_AI move function codes/059_AI_MoveHandlers_SwitchingActing.rb @@ -434,8 +434,8 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetActsNext", #=============================================================================== # #=============================================================================== -Battle::AI::Handlers::MoveEffectScore.add("TargetActsLast", - proc { |score, move, user, ai, battle| +Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetActsLast", + proc { |score, move, user, target, ai, battle| # Useless if the target is an ally next Battle::AI::MOVE_USELESS_SCORE if !target.opposes?(user) # Useless if the user has no ally (the point of this move is to let the ally @@ -643,6 +643,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("DisableTargetUsingDiffe proc { |move, user, target, ai, battle| next true if target.effects[PBEffects::Encore] > 0 next true if !target.battler.lastRegularMoveUsed || + !GameData::Move.exists?(target.battler.lastRegularMoveUsed) || move.move.moveBlacklist.include?(GameData::Move.get(target.battler.lastRegularMoveUsed).function_code) next true if target.effects[PBEffects::ShellTrap] next true if move.move.pbMoveFailedAromaVeil?(user.battler, target.battler, false) diff --git a/Data/Scripts/011_Battle/006_Other battle code/008_Battle_AbilityEffects.rb b/Data/Scripts/011_Battle/006_Other battle code/008_Battle_AbilityEffects.rb index 11b2b1447..6d2212765 100644 --- a/Data/Scripts/011_Battle/006_Other battle code/008_Battle_AbilityEffects.rb +++ b/Data/Scripts/011_Battle/006_Other battle code/008_Battle_AbilityEffects.rb @@ -1200,7 +1200,7 @@ Battle::AbilityEffects::DamageCalcFromUser.add(:AERILATE, } ) -Battle::AbilityEffects::DamageCalcFromUser.copy(:AERILATE, :PIXILATE, :REFRIGERATE, :GALVANIZE, :NORMALIZE) +Battle::AbilityEffects::DamageCalcFromUser.copy(:AERILATE, :GALVANIZE, :NORMALIZE, :PIXILATE, :REFRIGERATE) Battle::AbilityEffects::DamageCalcFromUser.add(:ANALYTIC, proc { |ability, user, target, move, mults, power, type| @@ -1372,6 +1372,12 @@ Battle::AbilityEffects::DamageCalcFromUser.add(:SLOWSTART, } ) +Battle::AbilityEffects::DamageCalcFromUser.add(:SNIPER, + proc { |ability, user, target, move, mults, power, type| + mults[:final_damage_multiplier] *= 1.5 if target.damageState.critical + } +) + Battle::AbilityEffects::DamageCalcFromUser.add(:SOLARPOWER, proc { |ability, user, target, move, mults, power, type| if move.specialMove? && [:Sun, :HarshSun].include?(user.effectiveWeather) @@ -1380,12 +1386,6 @@ Battle::AbilityEffects::DamageCalcFromUser.add(:SOLARPOWER, } ) -Battle::AbilityEffects::DamageCalcFromUser.add(:SNIPER, - proc { |ability, user, target, move, mults, power, type| - mults[:final_damage_multiplier] *= 1.5 if target.damageState.critical - } -) - Battle::AbilityEffects::DamageCalcFromUser.add(:STAKEOUT, proc { |ability, user, target, move, mults, power, type| mults[:attack_multiplier] *= 2 if target.battle.choices[target.index][0] == :SwitchOut @@ -1420,7 +1420,7 @@ Battle::AbilityEffects::DamageCalcFromUser.add(:SWARM, Battle::AbilityEffects::DamageCalcFromUser.add(:TECHNICIAN, proc { |ability, user, target, move, mults, power, type| - if user.index != target.index && move && move.id != :STRUGGLE && + if user.index != target.index && move && move.function != "Struggle" && power * mults[:power_multiplier] <= 60 mults[:power_multiplier] *= 1.5 end diff --git a/Data/Scripts/011_Battle/006_Other battle code/009_Battle_ItemEffects.rb b/Data/Scripts/011_Battle/006_Other battle code/009_Battle_ItemEffects.rb index 7fd08739f..5c1e69dfd 100644 --- a/Data/Scripts/011_Battle/006_Other battle code/009_Battle_ItemEffects.rb +++ b/Data/Scripts/011_Battle/006_Other battle code/009_Battle_ItemEffects.rb @@ -224,6 +224,12 @@ Battle::ItemEffects::SpeedCalc.add(:CHOICESCARF, } ) +Battle::ItemEffects::SpeedCalc.add(:IRONBALL, + proc { |item, battler, mult| + next mult / 2 + } +) + Battle::ItemEffects::SpeedCalc.add(:MACHOBRACE, proc { |item, battler, mult| next mult / 2 @@ -241,12 +247,6 @@ Battle::ItemEffects::SpeedCalc.add(:QUICKPOWDER, } ) -Battle::ItemEffects::SpeedCalc.add(:IRONBALL, - proc { |item, battler, mult| - next mult / 2 - } -) - #=============================================================================== # WeightCalc handlers #=============================================================================== diff --git a/Data/Scripts/013_Items/003_Item_BattleEffects.rb b/Data/Scripts/013_Items/003_Item_BattleEffects.rb index e9f89caf7..b0437f88d 100644 --- a/Data/Scripts/013_Items/003_Item_BattleEffects.rb +++ b/Data/Scripts/013_Items/003_Item_BattleEffects.rb @@ -143,7 +143,7 @@ ItemHandlers::CanUseInBattle.add(:FULLRESTORE, proc { |item, pokemon, battler, m }) ItemHandlers::CanUseInBattle.add(:REVIVE, proc { |item, pokemon, battler, move, firstAction, battle, scene, showMessages| - if pokemon.able? || pokemon.egg? + if !pokemon.fainted? scene.pbDisplay(_INTL("It won't have any effect.")) if showMessages next false end diff --git a/Data/Scripts/018_Alternate battle modes/003_Battle Frontier generator/002_ChallengeGenerator_Pokemon.rb b/Data/Scripts/018_Alternate battle modes/003_Battle Frontier generator/002_ChallengeGenerator_Pokemon.rb index 6fbc74776..ea9d53ec1 100644 --- a/Data/Scripts/018_Alternate battle modes/003_Battle Frontier generator/002_ChallengeGenerator_Pokemon.rb +++ b/Data/Scripts/018_Alternate battle modes/003_Battle Frontier generator/002_ChallengeGenerator_Pokemon.rb @@ -42,7 +42,7 @@ def pbRandomMove loop do move_id = keys.sample move = GameData::Move.get(move_id) - next if move.id == :SKETCH || move.id == :STRUGGLE + next if ["Struggle", "ReplaceMoveWithTargetLastMoveUsed"].include?(move.function_code) return move.id end end diff --git a/Data/Scripts/022_Maruno/debug battle tests.rb b/Data/Scripts/022_Maruno/debug battle tests.rb index 9e33c6291..98af6a6aa 100644 --- a/Data/Scripts/022_Maruno/debug battle tests.rb +++ b/Data/Scripts/022_Maruno/debug battle tests.rb @@ -9,7 +9,7 @@ def debug_set_up_trainer # Values to return trainer_array = [] - foe_items = [] # Intentionally left blank (for now) + foe_items = [] # Items can't be used except in internal battles pokemon_array = [] party_starts = [0] @@ -25,6 +25,10 @@ def debug_set_up_trainer trainer = NPCTrainer.new(trainer_name, trainer_type) trainer.id = $player.make_foreign_ID trainer.lose_text = "I lost." + # [:MAXPOTION, :FULLHEAL, :MAXREVIVE, :REVIVE].each do |item| + # trainer.items.push(item) + # end + # foe_items.push(trainer.items) trainer_array.push(trainer) # Generate party @@ -46,11 +50,13 @@ def debug_set_up_trainer return trainer_array, foe_items, pokemon_array, party_starts end -def debug_test_auto_battle(logging = false) +def debug_test_auto_battle(logging = false, console_messages = true) old_internal = $INTERNAL $INTERNAL = logging - echoln "Start of testing auto-battle." - echoln "" if !$INTERNAL + if console_messages + echoln "Start of testing auto-battle." + echoln "" if !$INTERNAL + end PBDebug.log("") PBDebug.log("================================================================") PBDebug.log("") @@ -75,11 +81,13 @@ def debug_test_auto_battle(logging = false) ($INTERNAL) ? PBDebug.log(moves_msg) : echoln(moves_msg) end end - echo_participant.call(player_trainers[0], player_party, 1) + echo_participant.call(player_trainers[0], player_party, 1) if console_messages PBDebug.log("") - echoln "" if !$INTERNAL - echo_participant.call(foe_trainers[0], foe_party, 2) - echoln "" if !$INTERNAL + if console_messages + echoln "" if !$INTERNAL + echo_participant.call(foe_trainers[0], foe_party, 2) + echoln "" if !$INTERNAL + end # Create the battle scene (the visual side of it) scene = Battle::DebugSceneNoVisuals.new(logging) # Create the battle class (the mechanics side of it) @@ -97,14 +105,16 @@ def debug_test_auto_battle(logging = false) # Perform the battle itself outcome = battle.pbStartBattle # End - text = ["Undecided", - "Trainer 1 #{player_trainers[0].name} won", - "Trainer 2 #{foe_trainers[0].name} won", - "Ran/forfeited", - "Wild Pokémon caught", - "Draw"][outcome] - echoln sprintf("%s after %d rounds", text, battle.turnCount + 1) - echoln "" + if console_messages + text = ["Undecided", + "Trainer 1 #{player_trainers[0].name} won", + "Trainer 2 #{foe_trainers[0].name} won", + "Ran/forfeited", + "Wild Pokémon caught", + "Draw"][outcome] + echoln sprintf("%s after %d rounds", text, battle.turnCount + 1) + echoln "" + end $INTERNAL = old_internal end @@ -112,9 +122,9 @@ end # Add to Debug menu. #=============================================================================== MenuHandlers.add(:debug_menu, :test_auto_battle, { - "name" => _INTL("Test Auto Battle"), + "name" => "Test Auto Battle", "parent" => :main, - "description" => _INTL("Runs an AI-controlled battle with no visuals."), + "description" => "Runs an AI-controlled battle with no visuals.", "always_show" => false, "effect" => proc { debug_test_auto_battle @@ -122,12 +132,27 @@ MenuHandlers.add(:debug_menu, :test_auto_battle, { }) MenuHandlers.add(:debug_menu, :test_auto_battle_logging, { - "name" => _INTL("Test Auto Battle with Logging"), + "name" => "Test Auto Battle with Logging", "parent" => :main, - "description" => _INTL("Runs an AI-controlled battle with no visuals. Logs messages."), + "description" => "Runs an AI-controlled battle with no visuals. Logs messages.", "always_show" => false, "effect" => proc { debug_test_auto_battle(true) - pbMessage(_INTL("Battle transcript was logged in Data/debuglog.txt.")) + pbMessage("Battle transcript was logged in Data/debuglog.txt.") + } +}) + +MenuHandlers.add(:debug_menu, :bulk_test_auto_battle, { + "name" => "Bulk Test Auto Battle", + "parent" => :main, + "description" => "Runs 50 AI-controlled battles with no visuals.", + "always_show" => false, + "effect" => proc { + echoln "Running 50 battles.." + 50.times do |i| + echoln "#{i + 1}..." + debug_test_auto_battle(false, false) + end + echoln "Done!" } }) diff --git a/PBS/Gen 5/moves.txt b/PBS/Gen 5/moves.txt index 54ecbf842..674c29114 100644 --- a/PBS/Gen 5/moves.txt +++ b/PBS/Gen 5/moves.txt @@ -3634,18 +3634,6 @@ FunctionCode = None Flags = Contact,CanProtect,CanMirrorMove Description = The target is cut with a scythe or a claw. It can also be used to cut down thin trees. #------------------------------- -[STRUGGLE] -Name = Struggle -Type = NORMAL -Category = Physical -Power = 50 -Accuracy = 0 -TotalPP = 1 -Target = RandomNearFoe -FunctionCode = Struggle -Flags = Contact,CanProtect -Description = An attack that is used in desperation only if the user has no PP. It also hurts the user slightly. -#------------------------------- [TACKLE] Name = Tackle Type = NORMAL diff --git a/PBS/Gen 6/moves.txt b/PBS/Gen 6/moves.txt index 73791c693..7e16fe5a4 100644 --- a/PBS/Gen 6/moves.txt +++ b/PBS/Gen 6/moves.txt @@ -4230,18 +4230,6 @@ Flags = CanProtect,CanMirrorMove,Sound EffectChance = 30 Description = An attack that can be used only if the user is asleep. The harsh noise may also make the target flinch. #------------------------------- -[STRUGGLE] -Name = Struggle -Type = NORMAL -Category = Physical -Power = 50 -Accuracy = 0 -TotalPP = 1 -Target = RandomNearFoe -FunctionCode = Struggle -Flags = Contact,CanProtect -Description = An attack that is used in desperation only if the user has no PP. It also hurts the user slightly. -#------------------------------- [WEATHERBALL] Name = Weather Ball Type = NORMAL diff --git a/PBS/Gen 7/moves.txt b/PBS/Gen 7/moves.txt index f2b15979a..6782167ab 100644 --- a/PBS/Gen 7/moves.txt +++ b/PBS/Gen 7/moves.txt @@ -4654,18 +4654,6 @@ Flags = CanProtect,CanMirrorMove,Sound EffectChance = 30 Description = An attack that can be used only if the user is asleep. The harsh noise may also make the target flinch. #------------------------------- -[STRUGGLE] -Name = Struggle -Type = NORMAL -Category = Physical -Power = 50 -Accuracy = 0 -TotalPP = 1 -Target = RandomNearFoe -FunctionCode = Struggle -Flags = Contact,CanProtect -Description = An attack that is used in desperation only if the user has no PP. It also hurts the user slightly. -#------------------------------- [WEATHERBALL] Name = Weather Ball Type = NORMAL diff --git a/PBS/Gen 8/moves.txt b/PBS/Gen 8/moves.txt index 03b1f612e..0bf64190e 100644 --- a/PBS/Gen 8/moves.txt +++ b/PBS/Gen 8/moves.txt @@ -5200,18 +5200,6 @@ Flags = CanProtect,CanMirrorMove,Sound EffectChance = 30 Description = An attack that can be used only if the user is asleep. The harsh noise may also make the target flinch. #------------------------------- -[STRUGGLE] -Name = Struggle -Type = NORMAL -Category = Physical -Power = 50 -Accuracy = 0 -TotalPP = 1 -Target = RandomNearFoe -FunctionCode = Struggle -Flags = Contact,CanProtect -Description = An attack that is used in desperation only if the user has no PP. It also hurts the user slightly. -#------------------------------- [TERRAINPULSE] Name = Terrain Pulse Type = NORMAL diff --git a/PBS/moves.txt b/PBS/moves.txt index 03b1f612e..0bf64190e 100644 --- a/PBS/moves.txt +++ b/PBS/moves.txt @@ -5200,18 +5200,6 @@ Flags = CanProtect,CanMirrorMove,Sound EffectChance = 30 Description = An attack that can be used only if the user is asleep. The harsh noise may also make the target flinch. #------------------------------- -[STRUGGLE] -Name = Struggle -Type = NORMAL -Category = Physical -Power = 50 -Accuracy = 0 -TotalPP = 1 -Target = RandomNearFoe -FunctionCode = Struggle -Flags = Contact,CanProtect -Description = An attack that is used in desperation only if the user has no PP. It also hurts the user slightly. -#------------------------------- [TERRAINPULSE] Name = Terrain Pulse Type = NORMAL