Added more debug logging to AI, fixed some bugs in AI

This commit is contained in:
Maruno17
2023-01-16 19:29:28 +00:00
parent 98f16c2afa
commit 2627d68782
21 changed files with 280 additions and 115 deletions

View File

@@ -48,6 +48,27 @@ module PBDebug
end
end
def self.log_ai(msg)
if $DEBUG && $INTERNAL
msg = "[AI] " + msg
echoln msg.gsub("%", "%%")
@@log.push(msg + "\r\n")
PBDebug.flush # if @@log.length > 1024
end
end
def self.log_score_change(amt, msg)
return if amt == 0
if $DEBUG && $INTERNAL
sign = (amt > 0) ? "+" : "-"
amt_text = sprintf("%3d", amt.abs)
msg = " #{sign}#{amt_text}: #{msg}"
echoln msg.gsub("%", "%%")
@@log.push(msg + "\r\n")
PBDebug.flush # if @@log.length > 1024
end
end
def self.dump(msg)
if $DEBUG && $INTERNAL
File.open("Data/dumplog.txt", "a+b") { |f| f.write("#{msg}\r\n") }

View File

@@ -129,7 +129,7 @@ class Battle
# For choosing a replacement Pokémon when prompted in the middle of other
# things happening (U-turn, Baton Pass, in def pbEORSwitch).
def pbSwitchInBetween(idxBattler, checkLaxOnly = false, canCancel = false)
return pbPartyScreen(idxBattler, checkLaxOnly, canCancel) if pbOwnedByPlayer?(idxBattler)
return pbPartyScreen(idxBattler, checkLaxOnly, canCancel) if !@controlPlayer && pbOwnedByPlayer?(idxBattler)
return @battleAI.pbDefaultChooseNewEnemy(idxBattler, pbParty(idxBattler))
end

View File

@@ -207,7 +207,7 @@ class Battle
next if !@battlers[idxBattler] || pbOwnedByPlayer?(idxBattler) != isPlayer
if @choices[idxBattler][0] != :None || !pbCanShowCommands?(idxBattler)
# Action is forced, can't choose one
PBDebug.log("[AI] #{@battlers[idxBattler].pbThis} (#{idxBattler}) is forced into using a multi-turn move")
PBDebug.log_ai("#{@battlers[idxBattler].pbThis} (#{idxBattler}) is forced to use a multi-turn move")
next
end
# AI controls this battler

View File

@@ -187,7 +187,6 @@ class Battle
b.effects[PBEffects::Rage] = false if !pbChoseMoveFunctionCode?(i, "StartRaiseUserAtk1WhenDamaged")
end
# Calculate move order for this round
PBDebug.log("")
pbCalculatePriority(true)
PBDebug.log("")
# Perform actions

View File

@@ -100,11 +100,18 @@ end
# Poisons the target and decreases its Speed by 1 stage. (Toxic Thread)
#===============================================================================
class Battle::Move::PoisonTargetLowerTargetSpeed1 < Battle::Move
attr_reader :statDown
def initialize(battle, move)
super
@statDown = [:SPEED, 1]
end
def canMagicCoat?; return true; end
def pbFailsAgainstTarget?(user, target, show_message)
if !target.pbCanPoison?(user, false, self) &&
!target.pbCanLowerStatStage?(:SPEED, user, self)
!target.pbCanLowerStatStage?(@statDown[0], user, self)
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
@@ -113,8 +120,8 @@ class Battle::Move::PoisonTargetLowerTargetSpeed1 < Battle::Move
def pbEffectAgainstTarget(user, target)
target.pbPoison(user) if target.pbCanPoison?(user, false, self)
if target.pbCanLowerStatStage?(:SPEED, user, self)
target.pbLowerStatStage(:SPEED, 1, user)
if target.pbCanLowerStatStage?(@statDown[0], user, self)
target.pbLowerStatStage(@statDown[0], @statDown[1], user)
end
end
end

View File

@@ -334,13 +334,20 @@ end
# (Skull Bash)
#===============================================================================
class Battle::Move::TwoTurnAttackChargeRaiseUserDefense1 < Battle::Move::TwoTurnMove
attr_reader :statUp
def initialize(battle, move)
super
@statUp = [:DEFENSE, 1]
end
def pbChargingTurnMessage(user, targets)
@battle.pbDisplay(_INTL("{1} tucked in its head!", user.pbThis))
end
def pbChargingTurnEffect(user, target)
if user.pbCanRaiseStatStage?(:DEFENSE, user, self)
user.pbRaiseStatStage(:DEFENSE, 1, user)
if user.pbCanRaiseStatStage?(@statUp[0], user, self)
user.pbRaiseStatStage(@statUp[0], @statUp[1], user)
end
end
end

View File

@@ -109,6 +109,13 @@ end
# it). (Strength Sap)
#===============================================================================
class Battle::Move::HealUserByTargetAttackLowerTargetAttack1 < Battle::Move
attr_reader :statDown
def initialize(battle, move)
super
@statDown = [:ATTACK, 1]
end
def healingMove?; return true; end
def canMagicCoat?; return true; end
@@ -119,11 +126,11 @@ class Battle::Move::HealUserByTargetAttackLowerTargetAttack1 < Battle::Move
# works even if the stat stage cannot be changed due to an ability or
# other effect.
if !@battle.moldBreaker && target.hasActiveAbility?(:CONTRARY)
if target.statStageAtMax?(:ATTACK)
if target.statStageAtMax?(@statDown[0])
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
elsif target.statStageAtMin?(:ATTACK)
elsif target.statStageAtMin?(@statDown[0])
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
@@ -135,11 +142,11 @@ class Battle::Move::HealUserByTargetAttackLowerTargetAttack1 < Battle::Move
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
atk = target.attack
atkStage = target.stages[:ATTACK] + 6
atkStage = target.stages[@statDown[0]] + 6
healAmt = (atk.to_f * stageMul[atkStage] / stageDiv[atkStage]).floor
# Reduce target's Attack stat
if target.pbCanLowerStatStage?(:ATTACK, user, self)
target.pbLowerStatStage(:ATTACK, 1, user)
if target.pbCanLowerStatStage?(@statDown[0], user, self)
target.pbLowerStatStage(@statDown[0], @statDown[1], user)
end
# Heal user
if target.hasActiveAbility?(:LIQUIDOOZE, true)

View File

@@ -57,6 +57,7 @@ class Battle::AI
@battle.pbRegisterMegaEvolution(idxBattler) if pbEnemyShouldMegaEvolve?
choices = pbGetMoveScores
pbChooseMove(choices)
PBDebug.log("")
end
end

View File

@@ -1,6 +1,8 @@
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?
item = nil
@@ -14,7 +16,7 @@ class Battle::AI
end
# Register use of item
@battle.pbRegisterItem(@user.index, item, idxTarget)
PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) will use item #{GameData::Item.get(item).name}")
PBDebug.log_ai("#{@user.name} will use item #{GameData::Item.get(item).name}")
return true
end

View File

@@ -133,12 +133,11 @@ class Battle::AI
end
if list.length > 0
if batonPass >= 0 && @battle.pbRegisterMove(battler.index, batonPass, false)
PBDebug.log("[AI] #{battler.pbThis} (#{battler.index}) will use Baton Pass to avoid Perish Song")
PBDebug.log_ai("#{@user.name} will use Baton Pass to avoid Perish Song")
return true
end
if @battle.pbRegisterSwitch(battler.index, list[0])
PBDebug.log("[AI] #{battler.pbThis} (#{battler.index}) will switch with " +
@battle.pbParty(battler.index)[list[0]].name)
PBDebug.log_ai("#{@user.name} will switch with #{@battle.pbParty(battler.index)[list[0]].name}")
return true
end
end

View File

@@ -23,16 +23,18 @@ class Battle::AI
# Unchoosable moves aren't considered
if !@battle.pbCanChooseMove?(@user.index, idxMove, false)
if move.pp == 0 && move.total_pp > 0
PBDebug.log("[AI] #{@user.battler.pbThis} (#{@user.index}) cannot use move #{move.name} as it has no PP left")
PBDebug.log_ai("#{@user.name} cannot use #{move.name} (no PP left)")
else
PBDebug.log("[AI] #{@user.battler.pbThis} (#{@user.index}) cannot choose to use #{move.name}")
PBDebug.log_ai("#{@user.name} cannot choose to use #{move.name}")
end
next
end
PBDebug.log_ai("#{@user.name} is considering using #{move.name}...")
# Set up move in class variables
set_up_move_check(move)
# Predict whether the move will fail (generally)
if @trainer.has_skill_flag?("PredictMoveFailure") && pbPredictMoveFailure
PBDebug.log_score_change(MOVE_FAIL_SCORE - MOVE_BASE_SCORE, "move will fail")
add_move_to_choices(choices, idxMove, MOVE_FAIL_SCORE)
next
end
@@ -49,6 +51,7 @@ class Battle::AI
# TODO: Figure out first which targets are valid. Includes the call to
# pbMoveCanTarget?, but also includes move-redirecting effects
# like Lightning Rod. Skip any battlers that can't be targeted.
num_targets = 0
@battle.allBattlers.each do |b|
next if !@battle.pbMoveCanTarget?(@user.battler.index, b.index, target_data)
# TODO: Should this sometimes consider targeting an ally? See def
@@ -57,7 +60,9 @@ class Battle::AI
score = MOVE_BASE_SCORE
PBDebug.logonerr { score = pbGetMoveScore([b]) }
add_move_to_choices(choices, idxMove, score, b.index)
num_targets += 1
end
PBDebug.log(" no valid targets") if num_targets == 0
else # Multiple targets at once
# Includes: AllAllies, AllBattlers, AllFoes, AllNearFoes, AllNearOthers, UserAndAllies
targets = []
@@ -100,7 +105,7 @@ class Battle::AI
move = Battle::Move.from_pokemon_move(@battle, Pokemon::Move.new(target.battler.lastRegularMoveUsed))
end
when "UseMoveDependingOnEnvironment"
move.move.pbOnStartUse(@user.battler, []) # Determine which move is used instead
move.pbOnStartUse(@user.battler, []) # Determine which move is used instead
move = Battle::Move.from_pokemon_move(@battle, Pokemon::Move.new(move.npMove))
end
@move.set_up(move, @user)
@@ -197,13 +202,18 @@ class Battle::AI
end
# Score based on how many targets were affected
if affected_targets == 0 && @trainer.has_skill_flag?("PredictMoveFailure")
return MOVE_FAIL_SCORE if !@move.move.worksWithNoTargets?
if !@move.move.worksWithNoTargets?
PBDebug.log_score_change(MOVE_FAIL_SCORE - MOVE_BASE_SCORE, "move will fail")
return MOVE_FAIL_SCORE
end
else
# TODO: Can this accounting for multiple targets be improved somehow?
score /= affected_targets if affected_targets > 1 # Average the score against multiple targets
# Bonus for affecting multiple targets
if @trainer.has_skill_flag?("PreferMultiTargetMoves") && affected_targets > 1
old_score = score
score += (affected_targets - 1) * 10
PBDebug.log_score_change(score - old_score, "affects multiple battlers")
end
end
end
@@ -212,8 +222,10 @@ class Battle::AI
# Self-Destruct)
if @trainer.has_skill_flag?("ScoreMoves")
# Modify the score according to the move's effect
old_score = score
score = Battle::AI::Handlers.apply_move_effect_score(@move.function,
score, @move, @user, self, @battle)
PBDebug.log_score_change(score - old_score, "function code modifier (generic)")
# Modify the score according to various other effects
score = Battle::AI::Handlers.apply_general_move_score_modifiers(
score, @move, @user, self, @battle)
@@ -240,15 +252,18 @@ class Battle::AI
#=============================================================================
def pbGetMoveScoreAgainstTarget
# Predict whether the move will fail against the target
if @trainer.has_skill_flag?("PredictMoveFailure")
return -1 if pbPredictMoveFailureAgainstTarget
if @trainer.has_skill_flag?("PredictMoveFailure") && pbPredictMoveFailureAgainstTarget
PBDebug.log(" move will not affect #{@target.name}")
return -1
end
# Score the move
score = MOVE_BASE_SCORE
if @trainer.has_skill_flag?("ScoreMoves")
# Modify the score according to the move's effect against the target
old_score = score
score = Battle::AI::Handlers.apply_move_effect_against_target_score(@move.function,
MOVE_BASE_SCORE, @move, @user, @target, self, @battle)
PBDebug.log_score_change(score - old_score, "function code modifier (against target)")
# Modify the score according to various other effects against the target
score = Battle::AI::Handlers.apply_general_move_against_target_score_modifiers(
score, @move, @user, @target, self, @battle)
@@ -256,9 +271,14 @@ class Battle::AI
# Add the score against the target to the overall score
target_data = @move.pbTarget(@user.battler)
if target_data.targets_foe && !@target.opposes?(@user) && @target.index != @user.index
return -1 if score == MOVE_USELESS_SCORE
if score == MOVE_USELESS_SCORE
PBDebug.log(" move is useless against #{@target.name}")
return -1
end
# TODO: Is this reversal of the score okay?
old_score = score
score = 175 - score
PBDebug.log_score_change(score - old_score, "score inverted (move targets ally but can target foe)")
end
return score
end
@@ -272,7 +292,7 @@ class Battle::AI
# If no moves can be chosen, auto-choose a move or Struggle
if choices.length == 0
@battle.pbAutoChooseMove(user_battler.index)
PBDebug.log("[AI] #{user_battler.pbThis} (#{user_battler.index}) will auto-use a move or Struggle")
PBDebug.log_ai("#{@user.name} will auto-use a move or Struggle")
return
end
# Figure out useful information about the choices
@@ -289,9 +309,10 @@ class Battle::AI
badMoves = choices.none? { |c| user_battler.moves[c[0]].damagingMove? }
badMoves = false if badMoves && pbAIRandom(100) < 10
end
if badMoves && pbEnemyShouldWithdrawEx?(true)
PBDebug.log("[AI] #{user_battler.pbThis} (#{user_battler.index}) will switch due to terrible moves")
return
if badMoves
PBDebug.log_ai("#{@user.name} wants to switch due to terrible moves")
return if pbEnemyShouldWithdrawEx?(true)
PBDebug.log_ai("#{@user.name} won't switch after all")
end
end
# Calculate a minimum score threshold and reduce all move scores by it
@@ -300,12 +321,12 @@ class Battle::AI
total_score = choices.sum { |c| c[3] }
# Log the available choices
if $INTERNAL
PBDebug.log("[AI] Move choices for #{user_battler.pbThis(true)} (#{user_battler.index}):")
PBDebug.log_ai("Move choices for #{@user.name}:")
choices.each_with_index do |c, i|
chance = sprintf("%5.1f", (c[3] > 0) ? 100.0 * c[3] / total_score : 0)
log_msg = " * #{chance}% chance: #{user_battler.moves[c[0]].name}"
log_msg += " (against target #{c[2]})" if c[2] >= 0
log_msg += " = score #{c[1]}"
log_msg = " * #{chance}% to use #{user_battler.moves[c[0]].name}"
log_msg += " (target #{c[2]})" if c[2] >= 0
log_msg += ": score #{c[1]}"
PBDebug.log(log_msg)
end
end
@@ -320,7 +341,12 @@ class Battle::AI
end
# Log the result
if @battle.choices[user_battler.index][2]
PBDebug.log(" => will use #{@battle.choices[user_battler.index][2].name}")
move_name = @battle.choices[user_battler.index][2].name
if @battle.choices[user_battler.index][3] >= 0
PBDebug.log(" => will use #{move_name} (target #{@battle.choices[user_battler.index][3]})")
else
PBDebug.log(" => will use #{move_name}")
end
end
end
end

View File

@@ -6,7 +6,7 @@ class Battle::AI
# be.
def pbEnemyShouldMegaEvolve?
if @battle.pbCanMegaEvolve?(@user.index) # Simple "always should if possible"
PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) will Mega Evolve")
PBDebug.log_ai("#{@user.name} will Mega Evolve")
return true
end
return false

View File

@@ -18,12 +18,16 @@ class Battle::AI
# Discard status move/don't prefer damaging move if target has Contrary
# TODO: Maybe this should return get_score_for_target_stat_drop if Contrary
# applies and desire_mult < 1.
if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:CONTRARY) && desire_mult > 1
return (whole_effect) ? MOVE_USELESS_SCORE : score - 20
if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:CONTRARY) && desire_mult > 0
ret = (whole_effect) ? MOVE_USELESS_SCORE : score - 20
PBDebug.log_score_change(ret - score, "don't prefer raising target's stats (it has Contrary)")
return ret
end
# Don't make score changes if target will faint from EOR damage
if target.rough_end_of_round_damage >= target.hp
return (whole_effect) ? MOVE_USELESS_SCORE : score
ret = (whole_effect) ? MOVE_USELESS_SCORE : score
PBDebug.log(" ignore stat change (target predicted to faint this round)")
return ret
end
# Don't make score changes if foes have Unaware and target can't make use of
# extra stat stages
@@ -33,7 +37,9 @@ class Battle::AI
foe_is_aware = true if !b.has_active_ability?(:UNAWARE)
end
if !foe_is_aware
return (whole_effect) ? MOVE_USELESS_SCORE : score
ret = (whole_effect) ? MOVE_USELESS_SCORE : score
PBDebug.log(" ignore stat change (target's foes have Unaware)")
return ret
end
end
@@ -41,7 +47,14 @@ class Battle::AI
real_stat_changes = []
stat_changes.each_with_index do |stat, idx|
next if idx.odd?
next if !stat_raise_worthwhile?(target, stat, fixed_change)
if !stat_raise_worthwhile?(target, stat, fixed_change)
if target.index == @user.index
PBDebug.log(" raising the user's #{GameData::Stat.get(stat).name} isn't worthwhile")
else
PBDebug.log(" raising the target's #{GameData::Stat.get(stat).name} isn't worthwhile")
end
next
end
# Calculate amount that stat will be raised by
increment = stat_changes[idx + 1]
increment *= 2 if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:SIMPLE)
@@ -60,7 +73,13 @@ class Battle::AI
# Make score changes based on the specific changes to each stat that will be
# raised
real_stat_changes.each do |change|
old_score = score
score = get_target_stat_raise_score_one(score, target, change[0], change[1], desire_mult)
if target.index == @user.index
PBDebug.log_score_change(score - old_score, "raising the user's #{GameData::Stat.get(change[0]).name} by #{change[1]}")
else
PBDebug.log_score_change(score - old_score, "raising the target's #{GameData::Stat.get(change[0]).name} by #{change[1]}")
end
end
return score
@@ -601,12 +620,16 @@ class Battle::AI
# Discard status move/don't prefer damaging move if target has Contrary
# TODO: Maybe this should return get_score_for_target_stat_raise if Contrary
# applies and desire_mult < 1.
if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:CONTRARY) && desire_mult > 1
return (whole_effect) ? MOVE_USELESS_SCORE : score - 20
if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:CONTRARY) && desire_mult > 0
ret = (whole_effect) ? MOVE_USELESS_SCORE : score - 20
PBDebug.log_score_change(ret - score, "don't prefer lowering target's stats (it has Contrary)")
return ret
end
# Don't make score changes if target will faint from EOR damage
if target.rough_end_of_round_damage >= target.hp
return (whole_effect) ? MOVE_USELESS_SCORE : score
ret = (whole_effect) ? MOVE_USELESS_SCORE : score
PBDebug.log(" ignore stat change (target predicted to faint this round)")
return ret
end
# Don't make score changes if foes have Unaware and target can't make use of
# its lowered stat stages
@@ -615,14 +638,23 @@ class Battle::AI
foe_is_aware = true if !b.has_active_ability?(:UNAWARE)
end
if !foe_is_aware
return (whole_effect) ? MOVE_USELESS_SCORE : score
ret = (whole_effect) ? MOVE_USELESS_SCORE : score
PBDebug.log(" ignore stat change (target's foes have Unaware)")
return ret
end
# Figure out which stat raises can happen
real_stat_changes = []
stat_changes.each_with_index do |stat, idx|
next if idx.odd?
next if !stat_drop_worthwhile?(target, stat, fixed_change)
if !stat_drop_worthwhile?(target, stat, fixed_change)
if target.index == @user.index
PBDebug.log(" lowering the user's #{GameData::Stat.get(stat).name} isn't worthwhile")
else
PBDebug.log(" lowering the target's #{GameData::Stat.get(stat).name} isn't worthwhile")
end
next
end
# Calculate amount that stat will be raised by
decrement = stat_changes[idx + 1]
decrement *= 2 if !fixed_change && !@battle.moldBreaker && @user.has_active_ability?(:SIMPLE)
@@ -641,7 +673,13 @@ class Battle::AI
# Make score changes based on the specific changes to each stat that will be
# lowered
real_stat_changes.each do |change|
old_score = score
score = get_target_stat_drop_score_one(score, target, change[0], change[1], desire_mult)
if target.index == @user.index
PBDebug.log_score_change(score - old_score, "lowering the user's #{GameData::Stat.get(change[0]).name} by #{change[1]}")
else
PBDebug.log_score_change(score - old_score, "lowering the target's #{GameData::Stat.get(change[0]).name} by #{change[1]}")
end
end
return score
@@ -886,7 +924,7 @@ class Battle::AI
end
when :Psychic
# Check for priority moves
if b.check_for_move { |m| m.priority > 0 && m.pbTarget&.can_target_one_foe? }
if b.check_for_move { |m| m.priority > 0 && m.pbTarget(b.battler)&.can_target_one_foe? }
ret += (b.opposes?(move_user)) ? 10 : -10
end
# Check for Psychic moves

View File

@@ -719,7 +719,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetRandomStat2",
score += 8
end
# Don't prefer if any foe has Punishment
each_foe_battler(target.side) do |b, i|
ai.each_foe_battler(target.side) do |b, i|
next if !b.check_for_move { |m| m.function == "PowerHigherWithTargetPositiveStatStages" }
score -= 5
end
@@ -1182,7 +1182,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaisePlusMinusUserAndAll
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseGroundedGrassBattlersAtkSpAtk1",
proc { |move, user, target, ai, battle|
next true if !b.pbHasType?(:GRASS) || b.airborne? || b.semiInvulnerable?
next true if !target.has_type?(:GRASS) || target.battler.airborne? || target.battler.semiInvulnerable?
next !target.battler.pbCanRaiseStatStage?(:ATTACK, user.battler, move.move) &&
!target.battler.pbCanRaiseStatStage?(:SPECIAL_ATTACK, user.battler, move.move)
}
@@ -1198,7 +1198,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseGroundedGrassBattle
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseGrassBattlersDef1",
proc { |move, user, target, ai, battle|
next true if !b.pbHasType?(:GRASS) || b.semiInvulnerable?
next true if !target.has_type?(:GRASS) || target.battler.semiInvulnerable?
next !target.battler.pbCanRaiseStatStage?(:DEFENSE, user.battler, move.move)
}
)
@@ -1226,10 +1226,10 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapAtkSpAtkSt
end
end
next Battle::AI::MOVE_USELESS_SCORE if raises.length == 0 # No stat raises
score += ai.get_score_for_target_stat_raise(score, user, raises, false, true) if raises.length > 0
score += ai.get_score_for_target_stat_drop(score, target, raises, false, true) if raises.length > 0
score += ai.get_score_for_target_stat_drop(score, user, drops, false, true) if drops.length > 0
score += ai.get_score_for_target_stat_raise(score, target, drops, false, true) if drops.length > 0
score = ai.get_score_for_target_stat_raise(score, user, raises, false, true) if raises.length > 0
score = ai.get_score_for_target_stat_drop(score, target, raises, false, true) if raises.length > 0
score = ai.get_score_for_target_stat_drop(score, user, drops, false, true) if drops.length > 0
score = ai.get_score_for_target_stat_raise(score, target, drops, false, true) if drops.length > 0
next score
}
)
@@ -1252,10 +1252,10 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapDefSpDefSt
end
end
next Battle::AI::MOVE_USELESS_SCORE if raises.length == 0 # No stat raises
score += ai.get_score_for_target_stat_raise(score, user, raises, false, true) if raises.length > 0
score += ai.get_score_for_target_stat_drop(score, target, raises, false, true) if raises.length > 0
score += ai.get_score_for_target_stat_drop(score, user, drops, false, true) if drops.length > 0
score += ai.get_score_for_target_stat_raise(score, target, drops, false, true) if drops.length > 0
score = ai.get_score_for_target_stat_raise(score, user, raises, false, true) if raises.length > 0
score = ai.get_score_for_target_stat_drop(score, target, raises, false, true) if raises.length > 0
score = ai.get_score_for_target_stat_drop(score, user, drops, false, true) if drops.length > 0
score = ai.get_score_for_target_stat_raise(score, target, drops, false, true) if drops.length > 0
next score
}
)
@@ -1278,10 +1278,10 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapStatStages
end
end
next Battle::AI::MOVE_USELESS_SCORE if raises.length == 0 # No stat raises
score += ai.get_score_for_target_stat_raise(score, user, raises, false, true) if raises.length > 0
score += ai.get_score_for_target_stat_drop(score, target, raises, false, true) if raises.length > 0
score += ai.get_score_for_target_stat_drop(score, user, drops, false, true) if drops.length > 0
score += ai.get_score_for_target_stat_raise(score, target, drops, false, true) if drops.length > 0
score = ai.get_score_for_target_stat_raise(score, user, raises, false, true) if raises.length > 0
score = ai.get_score_for_target_stat_drop(score, target, raises, false, true) if raises.length > 0
score = ai.get_score_for_target_stat_drop(score, user, drops, false, true) if drops.length > 0
score = ai.get_score_for_target_stat_raise(score, target, drops, false, true) if drops.length > 0
next score
}
)
@@ -1304,8 +1304,8 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserCopyTargetStatStages
end
end
next Battle::AI::MOVE_USELESS_SCORE if raises.length == 0 # No stat raises
score += ai.get_score_for_target_stat_raise(score, user, raises, false, true) if raises.length > 0
score += ai.get_score_for_target_stat_drop(score, user, drops, false, true) if drops.length > 0
score = ai.get_score_for_target_stat_raise(score, user, raises, false, true) if raises.length > 0
score = ai.get_score_for_target_stat_drop(score, user, drops, false, true) if drops.length > 0
if Settings::NEW_CRITICAL_HIT_RATE_MECHANICS
if user.effects[PBEffects::FocusEnergy] > 0 && target.effects[PBEffects::FocusEnergy] == 0
score -= 4
@@ -1337,8 +1337,8 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserStealTargetPositiveS
raises.push(target.stages[s.id])
end
if raises.length > 0
score += ai.get_score_for_target_stat_raise(score, user, raises, false)
score += ai.get_score_for_target_stat_drop(score, target, raises, false, true)
score = ai.get_score_for_target_stat_raise(score, user, raises, false)
score = ai.get_score_for_target_stat_drop(score, target, raises, false, true)
end
next score
}
@@ -1366,8 +1366,8 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("InvertTargetStatStages",
end
end
next Battle::AI::MOVE_USELESS_SCORE if drops.length == 0 # No stats will drop
score += ai.get_score_for_target_stat_raise(score, target, raises, false, true) if raises.length > 0
score += ai.get_score_for_target_stat_drop(score, target, drops, false, true) if drops.length > 0
score = ai.get_score_for_target_stat_raise(score, target, raises, false, true) if raises.length > 0
score = ai.get_score_for_target_stat_drop(score, target, drops, false, true) if drops.length > 0
next score
}
)
@@ -1388,8 +1388,8 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ResetTargetStatStages",
raises.push(target.stages[s.id])
end
end
score += ai.get_score_for_target_stat_raise(score, target, raises, false, true) if raises.length > 0
score += ai.get_score_for_target_stat_drop(score, target, drops, false, true) if drops.length > 0
score = ai.get_score_for_target_stat_raise(score, target, raises, false, true) if raises.length > 0
score = ai.get_score_for_target_stat_drop(score, target, drops, false, true) if drops.length > 0
next score
}
)
@@ -1416,8 +1416,8 @@ Battle::AI::Handlers::MoveEffectScore.add("ResetAllBattlersStatStages",
raises.push(b.stages[s.id])
end
end
score += ai.get_score_for_target_stat_raise(score, b, raises, false, true) if raises.length > 0
score += ai.get_score_for_target_stat_drop(score, b, drops, false, true) if drops.length > 0
score = ai.get_score_for_target_stat_raise(score, b, raises, false, true) if raises.length > 0
score = ai.get_score_for_target_stat_drop(score, b, drops, false, true) if drops.length > 0
end
next score
}

View File

@@ -783,7 +783,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetUserTypesToTargetTyp
next true if !user.battler.canChangeType?
next true if target.battler.pbTypes(true).empty?
next true if user.battler.pbTypes == target.battler.pbTypes &&
user.effects[PBEffects::Type3] == target.effects[PBEffects::Type3]
user.effects[PBEffects::ExtraType] == target.effects[PBEffects::ExtraType]
next false
}
)

View File

@@ -157,7 +157,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HealUserByTargetAttackLo
proc { |score, move, user, target, ai, battle|
# Check whether lowering the target's Attack will have any impact
if ai.trainer.medium_skill?
score = ai.get_score_for_target_stat_drop(score, target, [:ATTACK, 1])
score = ai.get_score_for_target_stat_drop(score, target, move.move.statDown)
end
# Consider how much HP will be restored
heal_amt = target.rough_stat(:ATTACK)

View File

@@ -506,21 +506,24 @@ Battle::AI::Handlers::MoveFailureCheck.add("UseRandomUserMoveIfAsleep",
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("ReplaceMoveThisBattleWithTargetLastMoveUsed",
proc { |move, user, ai, battle|
next user.effects[PBEffects::Transform] || !user.battler.pbHasMove?(move.id)
}
)
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("ReplaceMoveThisBattleWithTargetLastMoveUsed",
proc { |move, user, target, ai, battle|
next true if user.effects[PBEffects::Transform] || !user.battler.pbHasMove?(move.id)
if user.faster_than?(target)
last_move_data = GameData::Move.try_get(target.battler.lastRegularMoveUsed)
next true if !last_move_data ||
user.battler.pbHasMove?(target.battler.lastRegularMoveUsed) ||
move.move.moveBlacklist.include?(last_move_data.function_code) ||
last_move_data.type == :SHADOW
end
next false if !user.faster_than?(target)
last_move_data = GameData::Move.try_get(target.battler.lastRegularMoveUsed)
next true if !last_move_data ||
user.battler.pbHasMove?(target.battler.lastRegularMoveUsed) ||
move.move.moveBlacklist.include?(last_move_data.function_code) ||
last_move_data.type == :SHADOW
next false
}
)
Battle::AI::Handlers::MoveEffectScore.add("ReplaceMoveThisBattleWithTargetLastMoveUsed",
proc { |score, move, user, ai, battle|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ReplaceMoveThisBattleWithTargetLastMoveUsed",
proc { |score, move, user, target, ai, battle|
# Generally don't prefer, as this wastes the user's turn just to gain a move
# of unknown utility
score -= 8

View File

@@ -28,7 +28,10 @@
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:shiny_target,
proc { |score, move, user, target, ai, battle|
score -= 40 if target.wild? && target.battler.shiny?
if target.wild? && target.battler.shiny?
PBDebug.log_score_change(-40, "avoid attacking a shiny wild Pokémon")
score -= 40
end
next score
}
)
@@ -47,10 +50,16 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:add_predicted_damage,
proc { |score, move, user, target, ai, battle|
if move.damagingMove?
dmg = move.rough_damage
score += [20.0 * dmg / target.hp, 25].min
score += 10 if dmg > target.hp * 1.1 # Predicted to KO the target
old_score = score
score += ([30.0 * dmg / target.hp, 35].min).to_i
PBDebug.log_score_change(score - old_score, "damaging move (predicted damage #{dmg} = #{100 * dmg / target.hp}% of target's HP)")
if dmg > target.hp * 1.1 # Predicted to KO the target
old_score = score
score += 10
PBDebug.log_score_change(score - old_score, "predicted to KO the target")
end
end
next score.to_i
next score
}
)
@@ -60,7 +69,13 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:add_predicted_damage,
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:move_accuracy,
proc { |score, move, user, target, ai, battle|
next score * move.rough_accuracy / 100.0
acc = move.rough_accuracy.to_i
if acc < 90
old_score = score
score -= (0.2 * (100 - acc)).to_i # -2 (89%) to -19 (1%)
PBDebug.log_score_change(score - old_score, "accuracy (predicted #{acc}%)")
end
next score
}
)
@@ -89,7 +104,11 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_semi_invulnerabl
miss = false if move.move.hitsDivingTargets?
end
end
next Battle::AI::MOVE_USELESS_SCORE if miss
if miss
old_score = score
score = Battle::AI::MOVE_USELESS_SCORE
PBDebug.log_score_change(score - old_score, "target is semi-invulnerable")
end
end
next score
}
@@ -109,7 +128,9 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:thawing_move_against_fr
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && target.status == :FROZEN
if move.rough_type == :FIRE || (Settings::MECHANICS_GENERATION >= 6 && move.move.thawsUser?)
score -= 30
old_score = score
score -= 15
PBDebug.log_score_change(score - old_score, "thaws the target")
end
end
next score
@@ -137,7 +158,9 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:flinching_effects,
(move.damagingMove? &&
(user.has_active_item?([:KINGSROCK, :RAZORFANG]) ||
user.has_active_ability?(:STENCH)))
score += 20
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "flinching")
end
end
end
@@ -166,7 +189,11 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:flinching_effects,
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:dance_move_against_dancer,
proc { |score, move, user, target, ai, battle|
score /= 2 if move.move.danceMove? && target.has_active_ability?(:DANCER)
if move.move.danceMove? && target.has_active_ability?(:DANCER)
old_score = score
score -= 12
PBDebug.log_score_change(score - old_score, "don't want to use a dance move on a target with Dancer")
end
next score
}
)
@@ -199,8 +226,10 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:avoid_knocking_out_dest
if ai.trainer.medium_skill? && move.damagingMove? && target.effects[PBEffects::DestinyBond]
dmg = move.rough_damage
if dmg > target.hp * 1.05 # Predicted to KO the target
score -= 25
score -= 20 if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
old_score = score
score -= 15
score -= 10 if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
PBDebug.log_score_change(score - old_score, "don't want to KO the Destiny Bonding target")
end
end
next score
@@ -243,14 +272,13 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:avoid_knocking_out_dest
Battle::AI::Handlers::GeneralMoveScore.add(:thawing_move_when_frozen,
proc { |score, move, user, ai, battle|
if ai.trainer.medium_skill? && user.status == :FROZEN
old_score = score
if move.move.thawsUser?
score += 30
else
user.battler.eachMove do |m|
next unless m.thawsUser?
score -= 30 # Don't prefer this move if user knows another move that thaws
break
end
PBDebug.log_score_change(score - old_score, "move will thaw the user")
elsif user.check_for_move { |m| m.thawsUser? }
score -= 30 # Don't prefer this move if user knows another move that thaws
PBDebug.log_score_change(score - old_score, "user knows another move will thaw it")
end
end
next score
@@ -267,7 +295,11 @@ Battle::AI::Handlers::GeneralMoveScore.add(:good_move_for_choice_item,
if user.has_active_item?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) ||
user.has_active_ability?(:GORILLATACTICS)
# Really don't prefer status moves (except Trick)
score *= 0.1 if move.statusMove? && move.function != "UserTargetSwapItems"
if move.statusMove? && move.function != "UserTargetSwapItems"
old_score = score
score -= 25
PBDebug.log_score_change(score - old_score, "move is not suitable to be Choiced into")
end
# Don't prefer moves of certain types
move_type = move.rough_type
# Most unpreferred types are 0x effective against another type, except
@@ -280,11 +312,13 @@ Battle::AI::Handlers::GeneralMoveScore.add(:good_move_for_choice_item,
# very effective against Dragon.
unpreferred_types = [:NORMAL, :FIGHTING, :POISON, :GROUND, :GHOST,
:FIRE, :WATER, :GRASS, :ELECTRIC, :PSYCHIC, :DRAGON]
score *= 0.95 if unpreferred_types.include?(move_type)
old_score = score
score -= 5 if unpreferred_types.include?(move_type)
# Don't prefer moves with lower accuracy
score *= move.accuracy / 100.0 if move.accuracy > 0
score = score * move.accuracy / 100 if move.accuracy > 0
# Don't prefer moves with low PP
score *= 0.9 if move.move.pp < 6
score -= 10 if move.move.pp < 6
PBDebug.log_score_change(score - old_score, "move is less suitable to be Choiced into")
end
end
next score
@@ -305,10 +339,13 @@ Battle::AI::Handlers::GeneralMoveScore.add(:prefer_damaging_moves_if_last_pokemo
# Don't mess with scores just because a move is damaging; need to play well
next score if ai.trainer.high_skill? && foes > reserves # AI is outnumbered
# Prefer damaging moves depending on remaining Pokémon
old_score = score
if foes == 0 # Foe is down to their last Pokémon
score *= 1.1 # => Go for the kill
score += 10 # => Go for the kill
PBDebug.log_score_change(score - old_score, "prefer damaging moves (no foe party Pokémon left)")
elsif reserves == 0 # AI is down to its last Pokémon, foe has reserves
score *= 1.05 # => Go out with a bang
score += 5 # => Go out with a bang
PBDebug.log_score_change(score - old_score, "prefer damaging moves (no ally party Pokémon left)")
end
end
next score

View File

@@ -40,6 +40,10 @@ class Battle::AI::AIBattler
return @ai.battle.wildBattle? && opposes?
end
def name
return sprintf("%s (%d)", @battler.name, @index)
end
def opposes?(other = nil)
return @side == 1 if other.nil?
return other.side != @side
@@ -184,7 +188,7 @@ class Battle::AI::AIBattler
#=============================================================================
def types; return @battler.types; end
def pbTypes(withType3 = false); return @battler.pbTypes(withType3); end
def pbTypes(withExtraType = false); return @battler.pbTypes(withExtraType); end
def has_type?(type)
return false if !type
@@ -208,7 +212,7 @@ class Battle::AI::AIBattler
# TODO: Need to check the move's pbCalcTypeModSingle.
ret *= effectiveness_of_type_against_single_battler_type(type, defend_type, user)
end
ret *= 2 if target.effects[PBEffects::TarShot] && type == :FIRE
ret *= 2 if @battler.effects[PBEffects::TarShot] && type == :FIRE
end
return ret
end

View File

@@ -396,7 +396,7 @@ class Battle::AI::AIMove
# OHKO move accuracy
if @move.is_a?(Battle::Move::OHKO)
ret = self.accuracy + user.level - target.level
ret -= 10 if function == "OHKOIce" && !user.pbHasType?(:ICE)
ret -= 10 if function == "OHKOIce" && !user.has_type?(:ICE)
return [ret, 0].max
end
# "Always hit" effects and "always hit" accuracy

View File

@@ -1,3 +1,11 @@
# TODO: Better randomisation of moves, including tracking of how many times each
# function code has been tested (note that some Pokémon may not be used in
# battle, so their moves won't be score).
# TODO: Add held items.
#===============================================================================
#
#===============================================================================
def debug_set_up_trainer
# Values to return
trainer_array = []
@@ -6,7 +14,7 @@ def debug_set_up_trainer
party_starts = [0]
# Choose random trainer type and trainer name
trainer_type = GameData::TrainerType.keys.sample
trainer_type = :CHAMPION # GameData::TrainerType.keys.sample
trainer_name = ["Alpha", "Bravo", "Charlie", "Delta", "Echo",
"Foxtrot", "Golf", "Hotel", "India", "Juliette",
"Kilo", "Lima", "Mike", "November", "Oscar",
@@ -24,8 +32,12 @@ def debug_set_up_trainer
GameData::Species.each_species { |sp| valid_species.push(sp.species) }
Settings::MAX_PARTY_SIZE.times do |i|
this_species = valid_species.sample
this_level = rand(1, Settings::MAXIMUM_LEVEL)
pkmn = Pokemon.new(this_species, this_level, trainer)
this_level = 100 # rand(1, Settings::MAXIMUM_LEVEL)
pkmn = Pokemon.new(this_species, this_level, trainer, false)
all_moves = pkmn.getMoveList.map { |m| m[1] }
all_moves.uniq!
moves = all_moves.sample(4)
moves.each { |m| pkmn.learn_move(m) }
trainer.party.push(pkmn)
pokemon_array.push(pkmn)
end
@@ -37,7 +49,7 @@ end
def debug_test_auto_battle(logging = false)
old_internal = $INTERNAL
$INTERNAL = logging
echoln "Start of testing auto battle."
echoln "Start of testing auto-battle."
echoln "" if !$INTERNAL
PBDebug.log("")
PBDebug.log("================================================================")
@@ -51,7 +63,7 @@ def debug_test_auto_battle(logging = false)
trainer_txt = "[Trainer #{index}] #{trainer.full_name} [skill: #{trainer.skill_level}]"
($INTERNAL) ? PBDebug.log_header(trainer_txt) : echoln(trainer_txt)
party.each do |pkmn|
pkmn_txt = "* #{pkmn.name}, Lv.#{pkmn.level}"
pkmn_txt = "#{pkmn.name}, Lv.#{pkmn.level}"
pkmn_txt += " [Ability: #{pkmn.ability&.name || "---"}]"
pkmn_txt += " [Item: #{pkmn.item&.name || "---"}]"
($INTERNAL) ? PBDebug.log(pkmn_txt) : echoln(pkmn_txt)
@@ -85,18 +97,19 @@ def debug_test_auto_battle(logging = false)
# Perform the battle itself
outcome = battle.pbStartBattle
# End
echoln ["Undecided",
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 ""
$INTERNAL = old_internal
end
#===============================================================================
# Add to Debug menu
# Add to Debug menu.
#===============================================================================
MenuHandlers.add(:debug_menu, :test_auto_battle, {
"name" => _INTL("Test Auto Battle"),
@@ -115,5 +128,6 @@ MenuHandlers.add(:debug_menu, :test_auto_battle_logging, {
"always_show" => false,
"effect" => proc {
debug_test_auto_battle(true)
pbMessage(_INTL("Battle transcript was logged in Data/debuglog.txt."))
}
})