Rewrote AI item usage (inc. adding Revives), various fixes/changes to AI, removed Struggle from PBS files, some bug fixes

This commit is contained in:
Maruno17
2023-04-23 17:52:39 +01:00
parent da182bd98a
commit ce549ab62a
34 changed files with 497 additions and 410 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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]

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 &&

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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")
}
)

View File

@@ -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)

View File

@@ -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

View File

@@ -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
#===============================================================================

View File

@@ -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

View File

@@ -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

View File

@@ -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!"
}
})

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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