More AI function code rewrites

This commit is contained in:
Maruno17
2023-01-31 22:12:42 +00:00
parent 4da9a8c4e3
commit 80bb967aad
5 changed files with 287 additions and 101 deletions

View File

@@ -281,7 +281,8 @@ class Battle::Battler
# NOTE: A Pokémon using Bug Bite/Pluck, and a Pokémon having an item thrown at
# it via Fling, will gain the effect of the item even if the Pokémon is
# affected by item-negating effects.
# item_to_use is an item ID for Bug Bite/Pluck and Fling, and nil otherwise.
# item_to_use is an item ID for Stuff Cheeks, Teatime, Bug Bite/Pluck and
# Fling, and nil otherwise.
# fling is for Fling only.
def pbHeldItemTriggerCheck(item_to_use = nil, fling = false)
return if fainted?

View File

@@ -1035,6 +1035,92 @@ class Battle::AI
return ret
end
#=============================================================================
# Items can be consumed by Stuff Cheeks, Teatime, Bug Bite/Pluck and Fling.
#=============================================================================
def get_score_change_for_consuming_item(battler, item)
ret = 0
case item
when :ORANBERRY, :BERRYJUICE, :ENIGMABERRY, :SITRUSBERRY
# Healing
ret += (battler.hp > battler.totalhp * 3 / 4) ? -8 : 8
ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && battler.has_active_ability?(:RIPEN)
when :AGUAVBERRY, :FIGYBERRY, :IAPAPABERRY, :MAGOBERRY, :WIKIBERRY
# Healing with confusion
fraction_to_heal = 8 # Gens 6 and lower
if Settings::MECHANICS_GENERATION == 7
fraction_to_heal = 2
elsif Settings::MECHANICS_GENERATION >= 8
fraction_to_heal = 3
end
ret += (battler.hp > battler.totalhp * (1 - (1 / fraction_to_heal))) ? -8 : 8
ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && battler.has_active_ability?(:RIPEN)
# TODO: Check whether the item will cause confusion?
when :ASPEARBERRY, :CHERIBERRY, :CHESTOBERRY, :PECHABERRY, :RAWSTBERRY
# Status cure
status = {
:ASPEAR => :FROZEN,
:CHERIBERRY => :PARALYSIS,
:CHESTOBERRY => :SLEEP,
:PECHABERRY => :POISON,
:RAWSTBERRY => :BURN
}[item]
ret += (status && battler.status == status) ? 8 : -8
when :PERSIMBERRY
# Confusion cure
ret += (battler.effects[PBEffects::Confusion] > 1) ? 8 : -8
when :LUMBERRY
# Any status/confusion cure
ret += (battler.status != :NONE || battler.effects[PBEffects::Confusion] > 1) ? 8 : -8
when :MENTALHERB
# Cure mental effects
ret += 8 if battler.effects[PBEffects::Attract] >= 0 ||
battler.effects[PBEffects::Taunt] > 1 ||
battler.effects[PBEffects::Encore] > 1 ||
battler.effects[PBEffects::Torment] ||
battler.effects[PBEffects::Disable] > 1 ||
battler.effects[PBEffects::HealBlock] > 1
when :APICOTBERRY, :GANLONBERRY, :LIECHIBERRY, :PETAYABERRY, :SALACBERRY,
:KEEBERRY, :MARANGABERRY
# Stat raise
stat = {
:APICOTBERRY => :SPECIAL_DEFENSE,
:GANLONBERRY => :DEFENSE,
:LIECHIBERRY => :ATTACK,
:PETAYABERRY => :SPECIAL_ATTACK,
:SALACBERRY => :SPEED,
:KEEBERRY => :DEFENSE,
:MARANGABERRY => :SPECIAL_DEFENSE
}[item]
ret += 8 if stat && ai.stat_raise_worthwhile?(battler, stat)
ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && battler.has_active_ability?(:RIPEN)
when :STARFBERRY
# Random stat raise
ret += 8
ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && battler.has_active_ability?(:RIPEN)
when :WHITEHERB
# Resets lowered stats
reduced_stats = false
GameData::Stat.each_battle do |s|
next if battler.stages[s.id] >= 0
reduced_stats = true
break
end
ret += 8 if reduced_stats
when :MICLEBERRY
# Raises accuracy of next move
ret += 8
when :LANSATBERRY
# Focus energy
ret += 8 if battler.effects[PBEffects::FocusEnergy] < 2
when :LEPPABERRY
# Restore PP
ret += 8
ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && battler.has_active_ability?(:RIPEN)
end
return ret
end
#=============================================================================
# Returns a value indicating how beneficial the given ability will be to the
# given battler if it has it.

View File

@@ -148,13 +148,21 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("CorrodeTargetItem",
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("StartTargetCannotUseItem",
proc { |move, user, target, ai, battle|
next target.effects[PBEffects::Embargo] > 0
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("StartTargetCannotUseItem",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if !target.item || !target.item_active?
item_score = ai.battler_wants_item?(target, target.item_id)
score += item_score * 5
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
@@ -169,7 +177,7 @@ Battle::AI::Handlers::MoveEffectScore.add("StartNegateHeldItems",
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UserConsumeBerryRaiseDefense2",
proc { |move, user, ai, battle|
@@ -179,31 +187,28 @@ Battle::AI::Handlers::MoveFailureCheck.add("UserConsumeBerryRaiseDefense2",
)
Battle::AI::Handlers::MoveEffectScore.add("UserConsumeBerryRaiseDefense2",
proc { |score, move, user, ai, battle|
if ai.trainer.high_skill?
useful_berries = [
:ORANBERRY, :SITRUSBERRY, :AGUAVBERRY, :APICOTBERRY, :CHERIBERRY,
:CHESTOBERRY, :FIGYBERRY, :GANLONBERRY, :IAPAPABERRY, :KEEBERRY,
:LANSATBERRY, :LEPPABERRY, :LIECHIBERRY, :LUMBERRY, :MAGOBERRY,
:MARANGABERRY, :PECHABERRY, :PERSIMBERRY, :PETAYABERRY, :RAWSTBERRY,
:SALACBERRY, :STARFBERRY, :WIKIBERRY
]
score += 30 if useful_berries.include?(user.item_id)
end
# Score for raising the user's stat
score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserDefense2",
score, move, user, ai, battle)
# Score for the consumed berry's effect
score += ai.get_score_change_for_consuming_item(user, user.item_id)
# Score for other results of consuming the berry
if ai.trainer.medium_skill?
score += 20 if user.battler.canHeal? && user.hp < user.totalhp / 3 &&
# Prefer if user will heal itself with Cheek Pouch
score += 5 if user.battler.canHeal? && user.hp < user.totalhp / 2 &&
user.has_active_ability?(:CHEEKPOUCH)
score += 20 if user.has_active_ability?([:HARVEST, :RIPEN]) ||
user.battler.pbHasMoveFunction?("RestoreUserConsumedItem") # Recycle
score += 20 if !user.battler.canConsumeBerry?
# Prefer if target can recover the consumed berry
score += 8 if user.has_active_ability?(:HARVEST) ||
user.has_move_with_function?("RestoreUserConsumedItem")
# Prefer if user couldn't normally consume the berry
score += 4 if !user.battler.canConsumeBerry?
end
score -= user.stages[:DEFENSE] * 20
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
# TODO: This code should be for a single battler (each is checked in turn).
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("AllBattlersConsumeBerry",
proc { |move, user, target, ai, battle|
@@ -212,36 +217,20 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("AllBattlersConsumeBerry
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("AllBattlersConsumeBerry",
proc { |score, move, user, target, ai, battle|
useful_berries = [
:ORANBERRY, :SITRUSBERRY, :AGUAVBERRY, :APICOTBERRY, :CHERIBERRY,
:CHESTOBERRY, :FIGYBERRY, :GANLONBERRY, :IAPAPABERRY, :KEEBERRY,
:LANSATBERRY, :LEPPABERRY, :LIECHIBERRY, :LUMBERRY, :MAGOBERRY,
:MARANGABERRY, :PECHABERRY, :PERSIMBERRY, :PETAYABERRY,
:RAWSTBERRY, :SALACBERRY, :STARFBERRY, :WIKIBERRY
]
battle.allSameSideBattlers(user.index).each do |b|
if ai.trainer.high_skill?
amt = 30 / battle.pbSideSize(user.index)
score += amt if useful_berries.include?(b.item_id)
end
# Score for the consumed berry's effect
score_change = ai.get_score_change_for_consuming_item(target, target.item_id)
# Score for other results of consuming the berry
if ai.trainer.medium_skill?
amt = 20 / battle.pbSideSize(user.index)
score += amt if b.canHeal? && b.hp < b.totalhp / 3 && b.hasActiveAbility?(:CHEEKPOUCH)
score += amt if b.hasActiveAbility?([:HARVEST, :RIPEN]) ||
b.pbHasMoveFunction?("RestoreUserConsumedItem") # Recycle
score += amt if !b.canConsumeBerry?
end
end
if ai.trainer.high_skill?
battle.allOtherSideBattlers(user.index).each do |b|
amt = 10 / battle.pbSideSize(target.index)
score -= amt if b.hasActiveItem?(useful_berries)
score -= amt if b.canHeal? && b.hp < b.totalhp / 3 && b.hasActiveAbility?(:CHEEKPOUCH)
score -= amt if b.hasActiveAbility?([:HARVEST, :RIPEN]) ||
b.pbHasMoveFunction?("RestoreUserConsumedItem") # Recycle
score -= amt if !b.canConsumeBerry?
end
# Prefer if target will heal itself with Cheek Pouch
score_change += 5 if target.battler.canHeal? && target.hp < target.totalhp / 2 &&
target.has_active_ability?(:CHEEKPOUCH)
# Prefer if target can recover the consumed berry
score_change += 8 if target.has_active_ability?(:HARVEST) ||
target.has_move_with_function?("RestoreUserConsumedItem")
# Prefer if target couldn't normally consume the berry
score_change += 4 if !target.battler.canConsumeBerry?
end
score += (target.opposes?(user)) ? -score_change : score_change
next score
}
)
@@ -255,7 +244,15 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserConsumeTargetBerry",
next score if user.battler.unlosableItem?(target.item)
next score if target.effects[PBEffects::Substitute] > 0
next score if target.has_active_ability?(:STICKYHOLD) && !battle.moldBreaker
# User can consume the target's berry; score it
# Score the user gaining the item's effect
score += ai.get_score_change_for_consuming_item(user, target.item_id)
# Score for other results of consuming the berry
if ai.trainer.medium_skill?
# Prefer if user will heal itself with Cheek Pouch
score += 5 if user.battler.canHeal? && user.hp < user.totalhp / 2 &&
user.has_active_ability?(:CHEEKPOUCH)
end
# Score the target no longer having the item
target_item_preference = ai.battler_wants_item?(target, target.item_id)
target_no_item_preference = ai.battler_wants_item?(target, :NONE)
score += (target_item_preference - target_no_item_preference) * 4
@@ -296,16 +293,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ThrowUserItemAtTarget",
score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget",
score, move, user, target, ai, battle)
else
# TODO: Berries/Berry Juice/Mental Herb/White Herb also have Fling
# effects. Should they be accounted for individually, or is it okay
# to consider it bad to Fling these in general? Note that they all
# do minimal damage so this move probably won't be used anyway.
if Battle::ItemEffects::HPHeal[user.item_id] ||
Battle::ItemEffects::StatusCure[user.item_id] ||
Battle::ItemEffects::OnEndOfUsingMove[user.item_id] ||
Battle::ItemEffects::OnEndOfUsingMoveStatRestore[user.item_id]
score -= 8
end
score -= ai.get_score_change_for_consuming_item(target, user.item_id)
end
# Prefer if the user doesn't want its held item/don't prefer if it wants to
# keep its held item

View File

@@ -439,9 +439,23 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("UseLastMoveUsedByTarget
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
# UseMoveTargetIsAboutToUse
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("UseMoveTargetIsAboutToUse",
proc { |move, user, target, ai, battle|
next !target.check_for_move { |m| m.damagingMove? && !move.move.moveBlacklist.include?(m.function_code) }
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UseMoveTargetIsAboutToUse",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if target.faster_than?(user)
# Don't prefer if target knows any moves that can't be copied
if target.check_for_move { |m| m.statusMove? || move.move.moveBlacklist.include?(m.function_code) }
score -= 8
end
next score
}
)
#===============================================================================
# NOTE: The move that this move will become is determined in def
@@ -455,7 +469,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("UseLastMoveUsedByTarget
# UseRandomMove
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UseRandomMoveFromUserParty",
proc { |move, user, ai, battle|
@@ -492,16 +506,47 @@ Battle::AI::Handlers::MoveFailureCheck.add("UseRandomUserMoveIfAsleep",
)
#===============================================================================
# TODO: Review score modifiers.
# TODO: This code shouldn't make use of target.
#
#===============================================================================
# BounceBackProblemCausingStatusMoves
Battle::AI::Handlers::MoveEffectScore.add("BounceBackProblemCausingStatusMoves",
proc { |score, move, user, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if user.has_active_ability?(:MAGICBOUNCE)
useless = true
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
next if !b.check_for_move { |m| m.statusMove? && m.canMagicCoat? }
score += 4
useless = false
end
next Battle::AI::MOVE_USELESS_SCORE if useless
# Don't prefer the lower the user's HP is (better to try something else)
if user.hp < user.totalhp / 2
score -= 20 * (0.75 - (user.hp.to_f / user.totalhp)) # -5 to -15
end
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
# TODO: This code shouldn't make use of target.
#
#===============================================================================
# StealAndUseBeneficialStatusMove
Battle::AI::Handlers::MoveEffectScore.add("StealAndUseBeneficialStatusMove",
proc { |score, move, user, ai, battle|
useless = true
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
next if !b.check_for_move { |m| m.statusMove? && m.canSnatch? }
score += 4
useless = false
end
next Battle::AI::MOVE_USELESS_SCORE if useless
# Don't prefer the lower the user's HP is (better to try something else)
if user.hp < user.totalhp / 2
score -= 20 * (0.75 - (user.hp.to_f / user.totalhp)) # -5 to -15
end
next score
}
)
#===============================================================================
#

View File

@@ -1,11 +1,17 @@
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("FleeFromBattle",
proc { |move, user, ai, battle|
next !battle.pbCanRun?(user.index)
}
)
Battle::AI::Handlers::MoveEffectScore.add("FleeFromBattle",
proc { |score, move, user, ai, battle|
# Generally don't prefer (don't want to end the battle too easily)
next score - 15
}
)
#===============================================================================
# TODO: Review score modifiers.
@@ -258,32 +264,50 @@ Battle::AI::Handlers::MoveFailureCheck.add("TrapAllBattlersInBattleForOneTurn",
# PursueSwitchingFoe
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("UsedAfterUserTakesPhysicalDamage",
proc { |score, move, user, ai, battle|
Battle::AI::Handlers::MoveFailureCheck.add("UsedAfterUserTakesPhysicalDamage",
proc { |move, user, ai, battle|
found_physical_move = false
ai.each_foe_battler(user.side) do |b, i|
next if !b.check_for_move { |m| m.physicalMove?(m.type) }
found_physical_move = true
break
end
next Battle::AI::MOVE_USELESS_SCORE if !found_physical_move
next score
next !found_physical_move
}
)
Battle::AI::Handlers::MoveEffectScore.add("UsedAfterUserTakesPhysicalDamage",
proc { |score, move, user, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if user.effects[PBEffects::Substitute] > 0
# Prefer if foes don't know any special moves
found_special_move = false
ai.each_foe_battler(user.side) do |b, i|
next if !b.check_for_move { |m| m.specialMove?(m.type) }
found_special_move = true
break
end
score += 10 if !found_special_move
# Generally not worth using
next score - 10
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("UsedAfterAllyRoundWithDoublePower",
proc { |score, move, user, ai, battle|
if ai.trainer.medium_skill?
user.battler.allAllies.each do |b|
next if !b.pbHasMove?(move.id)
score += 20
end
# No score change if no allies know this move
ally_has_move = false
ai.each_same_side_battler(user.side) do |b, i|
next if !b.has_move_with_function?(move.function)
ally_has_move = true
break
end
next score if !ally_has_move
# Prefer for the sake of doubling in power
score += 5
next score
}
)
@@ -297,7 +321,7 @@ Battle::AI::Handlers::MoveEffectScore.add("TargetActsNext",
next Battle::AI::MOVE_USELESS_SCORE if target.opposes?(user)
# Compare the speeds of all battlers
speeds = []
ai.each_battler { |b, i| speeds.push([i, rough_stat(:SPEED)]) }
ai.each_battler { |b, i| speeds.push([i, b.rough_stat(:SPEED)]) }
if battle.field.effects[PBEffects::TrickRoom] > 0
speeds.sort! { |a, b| a[1] <=> b[1] }
else
@@ -332,7 +356,7 @@ Battle::AI::Handlers::MoveEffectScore.add("TargetActsLast",
next Battle::AI::MOVE_USELESS_SCORE if !has_ally
# Compare the speeds of all battlers
speeds = []
ai.each_battler { |b, i| speeds.push([i, rough_stat(:SPEED)]) }
ai.each_battler { |b, i| speeds.push([i, b.rough_stat(:SPEED)]) }
if battle.field.effects[PBEffects::TrickRoom] > 0
speeds.sort! { |a, b| a[1] <=> b[1] }
else
@@ -354,35 +378,77 @@ Battle::AI::Handlers::MoveEffectScore.add("TargetActsLast",
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TargetUsesItsLastUsedMoveAgain",
proc { |move, user, target, ai, battle|
next true if !target.battler.lastRegularMoveUsed ||
!target.battler.pbHasMove?(target.battler.lastRegularMoveUsed)
next true if target.battler.usingMultiTurnAttack?
next true if move.move.moveBlacklist.include?(GameData::Move.get(target.battler.lastRegularMoveUsed).function_code)
idxMove = -1
target.battler.eachMoveWithIndex do |m, i|
idxMove = i if m.id == target.battler.lastRegularMoveUsed
end
next true if target.battler.moves[idxMove].pp == 0 && target.battler.moves[idxMove].total_pp > 0
next false
next target.battler.usingMultiTurnAttack?
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetUsesItsLastUsedMoveAgain",
proc { |score, move, user, target, ai, battle|
# Without lots of code here to determine good/bad moves and relative
# speeds, using this move is likely to just be a waste of a turn
next Battle::AI::MOVE_USELESS_SCORE
# We don't ever want to make a foe act again
next Battle::AI::MOVE_USELESS_SCORE if target.opposes?(user)
# Useless if target will act before the user, as we don't know what move
# will be instructed
next Battle::AI::MOVE_USELESS_SCORE if target.faster_than?(user)
next Battle::AI::MOVE_USELESS_SCORE if !target.battler.lastRegularMoveUsed
mov = nil
target.battler.eachMove do |m|
mov = m if m.id == target.battler.lastRegularMoveUsed
break if mov
end
next Battle::AI::MOVE_USELESS_SCORE if mov.nil? || (mov.pp == 0 && mov.total_pp > 0)
next Battle::AI::MOVE_USELESS_SCORE if move.move.moveBlacklist.include?(mov.function)
# Without lots of code here to determine good/bad moves, using this move is
# likely to just be a waste of a turn
# NOTE: Because this move can be used against a foe but is being used on an
# ally (since we're here in this code), this move's score will be
# inverted later. A higher score here means this move will be less
# preferred, which is the result we want.
score += 20
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
# TODO: This code shouldn't make use of target.
#
#===============================================================================
# StartSlowerBattlersActFirst
Battle::AI::Handlers::MoveEffectScore.add("StartSlowerBattlersActFirst",
proc { |score, move, user, ai, battle|
# Get the speeds of all battlers
ally_speeds = []
foe_speeds = []
ai.each_battler do |b, i|
if b.opposes?(user)
foe_speeds.push(rough_stat(:SPEED))
foe_speeds.last *= 2 if user.pbOpposingSide.effects[PBEffects::Tailwind] > 1
foe_speeds.last /= 2 if user.pbOpposingSide.effects[PBEffects::Swamp] > 1
else
ally_speeds.push(rough_stat(:SPEED))
ally_speeds.last *= 2 if user.pbOwnSide.effects[PBEffects::Tailwind] > 1
ally_speeds.last /= 2 if user.pbOwnSide.effects[PBEffects::Swamp] > 1
end
end
# Just in case a side has no battlers
next Battle::AI::MOVE_USELESS_SCORE if ally_speeds.length == 0 || foe_speeds.length == 0
# Invert the speeds if Trick Room applies (and will last longer than this round)
if battle.field.effects[PBEffects::TrickRoom] > 1
foe_speeds.map! { |val| 100_000 - val } # 100_000 is higher than speed can
ally_speeds.map! { |val| 100_000 - val } # possibly be; only order matters
end
# Score based on the relative speeds
next Battle::AI::MOVE_USELESS_SCORE if ally_speeds.min > foe_speeds.max
if foe_speeds.min > ally_speeds.max
score += 20
elsif ally_speeds.sum / ally_speeds.length < foe_speeds.sum / foe_speeds.length
score += 10
else
score -= 10
end
next score
}
)
#===============================================================================
#