AI function code rewrites for base stat changes, electrifying moves and multi-turn moves

This commit is contained in:
Maruno17
2023-01-29 23:24:07 +00:00
parent d8f38947f4
commit 4da9a8c4e3
4 changed files with 362 additions and 94 deletions

View File

@@ -1424,88 +1424,205 @@ Battle::AI::Handlers::MoveEffectScore.add("ResetAllBattlersStatStages",
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("StartUserSideImmunityToStatStageLowering",
proc { |move, user, ai, battle|
next user.pbOwnSide.effects[PBEffects::Mist] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartUserSideImmunityToStatStageLowering",
proc { |score, move, user, ai, battle|
has_move = false
ai.each_foe_battler(user.side) do |b, i|
if b.check_for_move { |m| m.is_a?(Battle::Move::TargetStatDownMove) ||
m.is_a?(Battle::Move::TargetMultiStatDownMove) ||
["LowerPoisonedTargetAtkSpAtkSpd1",
"PoisonTargetLowerTargetSpeed1",
"HealUserByTargetAttackLowerTargetAttack1"].include?(m.function) }
score += 10
has_move = true
end
end
next Battle::AI::MOVE_USELESS_SCORE if !has_move
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("UserSwapBaseAtkDef",
proc { |score, move, user, ai, battle|
aatk = user.rough_stat(:ATTACK)
adef = user.rough_stat(:DEFENSE)
next Battle::AI::MOVE_USELESS_SCORE if aatk == adef || user.effects[PBEffects::PowerTrick] # No flip-flopping
if adef > aatk # Prefer a higher Attack
score += 20
else
score -= 20
# No flip-flopping
next Battle::AI::MOVE_USELESS_SCORE if user.effects[PBEffects::PowerTrick]
# Check stats
user_atk = user.base_stat(:ATTACK)
user_def = user.base_stat(:DEFENSE)
next Battle::AI::MOVE_USELESS_SCORE if user_atk == user_def
# NOTE: Prefer to raise Attack regardless of the drop to Defense. Only
# prefer to raise Defense if Attack is useless.
if user_def > user_atk # Attack will be raised
next Battle::AI::MOVE_USELESS_SCORE if !ai.stat_raise_worthwhile?(user, :ATTACK, true)
score += (40 * ((user_def.to_f / user_atk) - 1)).to_i
score += 5 if !ai.stat_drop_worthwhile?(user, :DEFENSE, true) # No downside
else # Defense will be raised
next Battle::AI::MOVE_USELESS_SCORE if !ai.stat_raise_worthwhile?(user, :DEFENSE, true)
# Don't want to lower user's Attack if it can make use of it
next Battle::AI::MOVE_USELESS_SCORE if ai.stat_drop_worthwhile?(user, :ATTACK, true)
score += (40 * ((user_atk.to_f / user_def) - 1)).to_i
end
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
# TODO: target should probably be treated as an enemy when deciding the score,
# since the score will be inverted elsewhere due to the target being an
# ally.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapBaseSpeed",
proc { |score, move, user, target, ai, battle|
if user.speed > target.speed
score += 25
user_speed = user.base_stat(:SPEED)
target_speed = target.base_stat(:SPEED)
next Battle::AI::MOVE_USELESS_SCORE if user_speed == target_speed
if battle.field.effects[PBEffects::TrickRoom] > 1
# User wants to be slower so it can move first
next Battle::AI::MOVE_USELESS_SCORE if target_speed > user_speed
score += (40 * ((user_speed.to_f / target_speed) - 1)).to_i
else
score -= 25
# User wants to be faster so it can move first
next Battle::AI::MOVE_USELESS_SCORE if user_speed > target_speed
score += (40 * ((target_speed.to_f / user_speed) - 1)).to_i
end
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
# TODO: target should probably be treated as an enemy when deciding the score,
# since the score will be inverted elsewhere due to the target being an
# ally.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetAverageBaseAtkSpAtk",
proc { |score, move, user, target, ai, battle|
user_atk = user.battler.attack
user_spatk = user.battler.spatk
target_atk = target.battler.attack
target_spatk = target.battler.spatk
next Battle::AI::MOVE_USELESS_SCORE if user_atk > target_atk && user_spatk > target_spatk
if user_atk + user_spatk < target_atk + target_spatk
score += 20
else
score -= 20
user_atk = user.base_stat(:ATTACK)
user_spatk = user.base_stat(:SPECIAL_ATTACK)
target_atk = target.base_stat(:ATTACK)
target_spatk = target.base_stat(:SPECIAL_ATTACK)
next Battle::AI::MOVE_USELESS_SCORE if user_atk >= target_atk && user_spatk >= target_spatk
# Score based on changes to Attack
atk_change_matters = false
if target_atk > user_atk
# User's Attack will be raised
if ai.stat_raise_worthwhile?(user, :ATTACK, true)
score += (20 * ((target_atk.to_f / user_atk) - 1)).to_i
atk_change_matters = true
end
# Target's Attack will be lowered
if ai.stat_drop_worthwhile?(target, :ATTACK, true)
score += (20 * ((target_atk.to_f / user_atk) - 1)).to_i
atk_change_matters = true
end
elsif target_atk < user_atk
# User's Attack will be lowered
if ai.stat_drop_worthwhile?(user, :ATTACK, true)
score -= (20 * ((user_atk.to_f / target_atk) - 1)).to_i
atk_change_matters = true
end
# Target's Attack will be raised
if ai.stat_raise_worthwhile?(target, :ATTACK, true)
score -= (20 * ((user_atk.to_f / target_atk) - 1)).to_i
atk_change_matters = true
end
end
# Score based on changes to Special Attack
spatk_change_matters = false
if target_spatk > user_spatk
# User's Special Attack will be raised
if ai.stat_raise_worthwhile?(user, :SPECIAL_ATTACK, true)
score += (20 * ((target_spatk.to_f / user_spatk) - 1)).to_i
spatk_change_matters = true
end
# Target's Special Attack will be lowered
if ai.stat_drop_worthwhile?(target, :SPECIAL_ATTACK, true)
score += (20 * ((target_spatk.to_f / user_spatk) - 1)).to_i
spatk_change_matters = true
end
elsif target_spatk < user_spatk
# User's Special Attack will be lowered
if ai.stat_drop_worthwhile?(user, :SPECIAL_ATTACK, true)
score -= (20 * ((user_spatk.to_f / target_spatk) - 1)).to_i
spatk_change_matters = true
end
# Target's Special Attack will be raised
if ai.stat_raise_worthwhile?(target, :SPECIAL_ATTACK, true)
score -= (20 * ((user_spatk.to_f / target_spatk) - 1)).to_i
spatk_change_matters = true
end
end
next Battle::AI::MOVE_USELESS_SCORE if !atk_change_matters && !spatk_change_matters
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
# TODO: target should probably be treated as an enemy when deciding the score,
# since the score will be inverted elsewhere due to the target being an
# ally.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetAverageBaseDefSpDef",
proc { |score, move, user, target, ai, battle|
user_def = user.rough_stat(:DEFENSE)
user_spdef = user.rough_stat(:SPECIAL_DEFENSE)
target_def = target.rough_stat(:DEFENSE)
target_spdef = target.rough_stat(:SPECIAL_DEFENSE)
next Battle::AI::MOVE_USELESS_SCORE if user_def > target_def && user_spdef > target_spdef
if user_def + user_spdef < target_def + target_spdef
score += 20
else
score -= 20
user_def = user.base_stat(:DEFENSE)
user_spdef = user.base_stat(:SPECIAL_DEFENSE)
target_def = target.base_stat(:DEFENSE)
target_spdef = target.base_stat(:SPECIAL_DEFENSE)
next Battle::AI::MOVE_USELESS_SCORE if user_def >= target_def && user_spdef >= target_spdef
# Score based on changes to Defense
def_change_matters = false
if target_def > user_def
# User's Defense will be raised
if ai.stat_raise_worthwhile?(user, :ATTACK, true)
score += (20 * ((target_def.to_f / user_def) - 1)).to_i
def_change_matters = true
end
# Target's Defense will be lowered
if ai.stat_drop_worthwhile?(target, :ATTACK, true)
score += (20 * ((target_def.to_f / user_def) - 1)).to_i
def_change_matters = true
end
elsif target_def < user_def
# User's Defense will be lowered
if ai.stat_drop_worthwhile?(user, :ATTACK, true)
score -= (20 * ((user_def.to_f / target_def) - 1)).to_i
def_change_matters = true
end
# Target's Defense will be raised
if ai.stat_raise_worthwhile?(target, :ATTACK, true)
score -= (20 * ((user_def.to_f / target_def) - 1)).to_i
def_change_matters = true
end
end
# Score based on changes to Special Defense
spdef_change_matters = false
if target_spdef > user_spdef
# User's Special Defense will be raised
if ai.stat_raise_worthwhile?(user, :SPECIAL_ATTACK, true)
score += (20 * ((target_spdef.to_f / user_spdef) - 1)).to_i
spdef_change_matters = true
end
# Target's Special Defense will be lowered
if ai.stat_drop_worthwhile?(target, :SPECIAL_ATTACK, true)
score += (20 * ((target_spdef.to_f / user_spdef) - 1)).to_i
spdef_change_matters = true
end
elsif target_spdef < user_spdef
# User's Special Defense will be lowered
if ai.stat_drop_worthwhile?(user, :SPECIAL_ATTACK, true)
score -= (20 * ((user_spdef.to_f / target_spdef) - 1)).to_i
spdef_change_matters = true
end
# Target's Special Defense will be raised
if ai.stat_raise_worthwhile?(target, :SPECIAL_ATTACK, true)
score -= (20 * ((user_spdef.to_f / target_spdef) - 1)).to_i
spdef_change_matters = true
end
end
next Battle::AI::MOVE_USELESS_SCORE if !def_change_matters && !spdef_change_matters
next score
}
)
@@ -1515,9 +1632,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetAverageBaseDef
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetAverageHP",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if user.hp >= (user.hp + target.hp) / 2
next Battle::AI::MOVE_USELESS_SCORE if user.hp >= target.hp
mult = (user.hp + target.hp) / (2.0 * user.hp)
score += 10 * mult if mult >= 1.2
score += (10 * mult).to_i if mult >= 1.2
next score
}
)

View File

@@ -1138,18 +1138,82 @@ Battle::AI::Handlers::MoveBasePower.copy("TypeAndPowerDependOnWeather",
"TypeAndPowerDependOnTerrain")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TargetMovesBecomeElectric",
proc { |move, user, target, ai, battle|
next !user.faster_than?(target)
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetMovesBecomeElectric",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if !user.faster_than?(target)
# Get Electric's effectiveness against the user
electric_eff = user.effectiveness_of_type_against_battler(:ELECTRIC, target)
electric_eff *= 1.5 if target.has_type?(:ELECTRIC) # STAB
electric_eff = 0 if user.has_active_ability?([:LIGHTNINGROD, :MOTORDRIVE, :VOLTABSORB])
# For each of target's moves, get its effectiveness against the user and
# decide whether it is better or worse than Electric's effectiveness
old_type_better = 0
electric_type_better = 0
target.battler.eachMove do |m|
next if !m.damagingMove?
m_type = m.pbCalcType(target.battler)
next if m_type == :ELECTRIC
eff = user.effectiveness_of_type_against_battler(m_type, target)
eff *= 1.5 if target.has_type?(m_type) # STAB
case m_type
when :FIRE
eff = 0 if user.has_active_ability?(:FLASHFIRE)
when :GRASS
eff = 0 if user.has_active_ability?(:SAPSIPPER)
when :WATER
eff = 0 if user.has_active_ability?([:STORMDRAIN, :WATERABSORB])
end
if eff > electric_eff
electric_type_better += 1
elsif eff < electric_eff
old_type_better += 1
end
end
next Battle::AI::MOVE_USELESS_SCORE if electric_type_better == 0
next Battle::AI::MOVE_USELESS_SCORE if electric_type_better < old_type_better
score += 10 * (electric_type_better - old_type_better)
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
# TODO: This code can be called with a single target and with no targets. Make
# sure it doesn't assume that there is a target.
# TODO: This could check all other battlers, not just foes. It could check the
# effectivenesses of their Normal and Electric moves on all their foes,
# not just on the user. I think this is overkill, particularly as the
# effect only lasts for one round.
#===============================================================================
# NormalMovesBecomeElectric
Battle::AI::Handlers::MoveEffectScore.add("NormalMovesBecomeElectric",
proc { |score, move, user, ai, battle|
# Get Electric's effectiveness against the user
electric_eff = user.effectiveness_of_type_against_battler(:ELECTRIC, target)
electric_eff *= 1.5 if target.has_type?(:ELECTRIC) # STAB
electric_eff = 0 if user.has_active_ability?([:LIGHTNINGROD, :MOTORDRIVE, :VOLTABSORB])
# Check all affected foe battlers for Normal moves, get their effectiveness
# against the user and decide whether it is better or worse than Electric's
# effectiveness
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 !b.has_damaging_move_of_type?(:NORMAL)
eff = user.effectiveness_of_type_against_battler(:NORMAL, b)
eff *= 1.5 if b.has_type?(:NORMAL) # STAB
if eff > electric_eff
electric_type_better += 1
elsif eff < electric_eff
normal_type_better += 1
end
end
if electric_type_better == 0 || electric_type_better < normal_type_better
next (move.statusMove?) ? Battle::AI::MOVE_USELESS_SCORE : score
end
score += 10 * (electric_type_better - normal_type_better)
next score
}
)

View File

@@ -206,10 +206,12 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttack",
next score if user.has_active_item?(:POWERHERB)
# Treat as a failure if user has Truant (the charging turn has no effect)
next Battle::AI::MOVE_USELESS_SCORE if user.has_active_ability?(:TRUANT)
# Useless if user will faint from EoR damage before finishing this attack
next Battle::AI::MOVE_USELESS_SCORE if user.rough_end_of_round_damage >= user.hp
# Don't prefer because it uses up two turns
score -= 15
score -= 10
# Don't prefer if user is at a low HP (time is better spent on quicker moves)
score -= 10 if user.hp < user.totalhp / 2
score -= 8 if user.hp < user.totalhp / 2
# Don't prefer if target has a protecting move
if ai.trainer.high_skill? && !(user.has_active_ability?(:UNSEENFIST) && move.move.contactMove?)
has_protect_move = false
@@ -356,27 +358,82 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackChargeRaise
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
# TwoTurnAttackInvulnerableUnderground
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerableUnderground",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
# Score for being semi-invulnerable underground
user.each_foe_battler(user.side) do |b, i|
if b.check_for_move { |m| m.hitsDiggingTargets? }
score -= 8
else
score += 5
end
end
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
# TwoTurnAttackInvulnerableUnderwater
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerableUnderwater",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
# Score for being semi-invulnerable underwater
user.each_foe_battler(user.side) do |b, i|
if b.check_for_move { |m| m.hitsDivingTargets? }
score -= 8
else
score += 5
end
end
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
# TwoTurnAttackInvulnerableInSky
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerableInSky",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
# Score for being semi-invulnerable in the sky
user.each_foe_battler(user.side) do |b, i|
if b.check_for_move { |m| m.hitsFlyingTargets? }
score -= 8
else
score += 5
end
end
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
# TwoTurnAttackInvulnerableInSkyParalyzeTarget
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerableInSkyParalyzeTarget",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack and semi-invulnerable in the sky
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttackInvulnerableInSky",
score, move, user, target, ai, battle)
# Score for paralyzing the target
score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget",
score, move, user, target, ai, battle)
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TwoTurnAttackInvulnerableInSkyTargetCannotAct",
proc { |move, user, target, ai, battle|
@@ -387,34 +444,51 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TwoTurnAttackInvulnerab
next false
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableInSkyTargetCannotAct")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
# TwoTurnAttackInvulnerableRemoveProtections
#===============================================================================
# TODO: Review score modifiers.
#===============================================================================
# MultiTurnAttackPreventSleeping
#===============================================================================
# TODO: Review score modifiers.
#===============================================================================
# MultiTurnAttackConfuseUserAtEnd
#===============================================================================
# TODO: Review score modifiers.
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("MultiTurnAttackPowersUpEachTurn",
proc { |power, move, user, target, ai, battle|
next move.move.pbBaseDamage(power, user.battler, target.battler)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerableRemoveProtections",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
# Score for being invulnerable
score += 5
# Score for removing protections
score = Battle::AI::Handlers.apply_move_effect_against_target_score("RemoveProtections",
score, move, user, target, ai, battle)
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
# TODO: This code shouldn't make use of target.
#
#===============================================================================
# MultiTurnAttackPreventSleeping
#===============================================================================
#
#===============================================================================
# MultiTurnAttackConfuseUserAtEnd
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("MultiTurnAttackPowersUpEachTurn",
proc { |power, move, user, target, ai, battle|
# NOTE: The * 2 (roughly) incorporates the higher damage done in subsequent
# rounds. It is nearly the average damage this move will do per round,
# assuming it hits for 3 rounds (hoping for hits in all 5 rounds is
# optimistic).
next move.move.pbBaseDamage(power, user.battler, target.battler) * 2
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("MultiTurnAttackBideThenReturnDoubleDamage",
proc { |power, move, user, target, ai, battle|
@@ -423,11 +497,19 @@ Battle::AI::Handlers::MoveBasePower.add("MultiTurnAttackBideThenReturnDoubleDama
)
Battle::AI::Handlers::MoveEffectScore.add("MultiTurnAttackBideThenReturnDoubleDamage",
proc { |score, move, user, ai, battle|
if user.hp <= user.totalhp / 4
score -= 90
elsif user.hp <= user.totalhp / 2
score -= 50
# Useless if no foe has any damaging moves
has_damaging_move = false
ai.each_foe_battler(user.side) do |b, i|
next if b.status == :SLEEP && b.statusCount > 2
next if b.status == :FROZEN
has_damaging_move = true if b.check_for_move { |m| m.damagingMove? }
break if has_damaging_move
end
next Battle::AI::MOVE_USELESS_SCORE if !has_damaging_move
# Don't prefer if the user isn't at high HP
next Battle::AI::MOVE_USELESS_SCORE if user.hp <= user.totalhp / 4
score -= 15 if user.hp <= user.totalhp / 2
score -= 8 if user.hp <= user.totalhp * 3 / 4
next score
}
)

View File

@@ -168,20 +168,25 @@ class Battle::AI::AIBattler
def speed; return @battler.speed; end
def base_stat(stat)
ret = 0
case stat
when :ATTACK then ret = @battler.attack
when :DEFENSE then ret = @battler.defense
when :SPECIAL_ATTACK then ret = @battler.spatk
when :SPECIAL_DEFENSE then ret = @battler.spdef
when :SPEED then ret = @battler.speed
end
return ret
end
# TODO: Cache calculated rough stats? Forget them in def refresh_battler.
def rough_stat(stat)
return @battler.pbSpeed if stat == :SPEED && @ai.trainer.high_skill?
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]
stage = @battler.stages[stat] + 6
value = 0
case stat
when :ATTACK then value = @battler.attack
when :DEFENSE then value = @battler.defense
when :SPECIAL_ATTACK then value = @battler.spatk
when :SPECIAL_DEFENSE then value = @battler.spdef
when :SPEED then value = @battler.speed
end
value = base_stat(stat)
return (value.to_f * stageMul[stage] / stageDiv[stage]).floor
end