mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-09 06:04:59 +00:00
Added AI objects for trainers, battlers and the move being assessed, logging battle messages now also echoes them to the console
This commit is contained in:
366
Data/Scripts/011_Battle/005_AI/103_AIMove.rb
Normal file
366
Data/Scripts/011_Battle/005_AI/103_AIMove.rb
Normal file
@@ -0,0 +1,366 @@
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
class Battle::AI::AIMove
|
||||
attr_reader :move
|
||||
|
||||
def initialize(ai)
|
||||
@ai = ai
|
||||
end
|
||||
|
||||
def set_up(move, ai_battler)
|
||||
@move = move
|
||||
@ai_battler = ai_battler
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
|
||||
# pp
|
||||
# priority
|
||||
# usableWhenAsleep?
|
||||
# thawsUser?
|
||||
# flinchingMove?
|
||||
# tramplesMinimize?
|
||||
# hitsFlyingTargets?
|
||||
# canMagicCoat?
|
||||
# soundMove?
|
||||
# bombMove?
|
||||
# powderMove?
|
||||
# ignoresSubstitute?
|
||||
# highCriticalRate?
|
||||
# ignoresReflect?
|
||||
|
||||
def id; return @move.id; end
|
||||
def physicalMove?(thisType = nil); return @move.physicalMove?(thisType); end
|
||||
def specialMove?(thisType = nil); return @move.specialMove?(thisType); end
|
||||
def damagingMove?; return @move.damagingMove?; end
|
||||
def statusMove?; return @move.statusMove?; end
|
||||
def function; return @move.function; end
|
||||
|
||||
#=============================================================================
|
||||
|
||||
# Returns whether this move targets multiple battlers.
|
||||
def targets_multiple_battlers?
|
||||
user_battler = @ai_battler.battler
|
||||
target_data = @move.pbTarget(user_battler)
|
||||
return false if target_data.num_targets <= 1
|
||||
num_targets = 0
|
||||
case target_data.id
|
||||
when :AllAllies
|
||||
@ai.battle.allSameSideBattlers(user_battler).each { |b| num_targets += 1 if b.index != user_battler.index }
|
||||
when :UserAndAllies
|
||||
@ai.battle.allSameSideBattlers(user_battler).each { |_b| num_targets += 1 }
|
||||
when :AllNearFoes
|
||||
@ai.battle.allOtherSideBattlers(user_battler).each { |b| num_targets += 1 if b.near?(user_battler) }
|
||||
when :AllFoes
|
||||
@ai.battle.allOtherSideBattlers(user_battler).each { |_b| num_targets += 1 }
|
||||
when :AllNearOthers
|
||||
@ai.battle.allBattlers.each { |b| num_targets += 1 if b.near?(user_battler) }
|
||||
when :AllBattlers
|
||||
@ai.battle.allBattlers.each { |_b| num_targets += 1 }
|
||||
end
|
||||
return num_targets > 1
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
|
||||
def type; return @move.type; end
|
||||
|
||||
def rough_type
|
||||
return @move.pbCalcType(@ai.user.battler) if @ai.trainer.high_skill?
|
||||
return @move.type
|
||||
end
|
||||
|
||||
def pbCalcType(user); return @move.pbCalcType(user); end
|
||||
|
||||
#=============================================================================
|
||||
|
||||
# Returns this move's base power, taking into account various effects that
|
||||
# modify it.
|
||||
def base_power
|
||||
ret = @move.baseDamage
|
||||
ret = 60 if ret == 1
|
||||
return ret if !@ai.trainer.medium_skill?
|
||||
user = @ai.user
|
||||
user_battler = user.battler
|
||||
target = @ai.target
|
||||
target_battler = target.battler
|
||||
# Covers all function codes which have their own def pbBaseDamage
|
||||
case @move.function
|
||||
when "FixedDamage20", "FixedDamage40", "FixedDamageHalfTargetHP",
|
||||
"FixedDamageUserLevel", "LowerTargetHPToUserHP"
|
||||
ret = @move.pbFixedDamage(user_battler, target_battler)
|
||||
when "FixedDamageUserLevelRandom"
|
||||
ret = user_battler.level
|
||||
when "OHKO", "OHKOIce", "OHKOHitsUndergroundTarget"
|
||||
ret = 200
|
||||
when "CounterPhysicalDamage", "CounterSpecialDamage", "CounterDamagePlusHalf"
|
||||
ret = 60
|
||||
when "DoublePowerIfTargetUnderwater", "DoublePowerIfTargetUnderground",
|
||||
"BindTargetDoublePowerIfTargetUnderwater"
|
||||
ret = @move.pbModifyDamage(ret, user_battler, target_battler)
|
||||
when "DoublePowerIfTargetInSky",
|
||||
"FlinchTargetDoublePowerIfTargetInSky",
|
||||
"DoublePowerIfTargetPoisoned",
|
||||
"DoublePowerIfTargetParalyzedCureTarget",
|
||||
"DoublePowerIfTargetAsleepCureTarget",
|
||||
"DoublePowerIfUserPoisonedBurnedParalyzed",
|
||||
"DoublePowerIfTargetStatusProblem",
|
||||
"DoublePowerIfTargetHPLessThanHalf",
|
||||
"DoublePowerIfAllyFaintedLastTurn",
|
||||
"TypeAndPowerDependOnWeather",
|
||||
"PowerHigherWithUserHappiness",
|
||||
"PowerLowerWithUserHappiness",
|
||||
"PowerHigherWithUserHP",
|
||||
"PowerHigherWithTargetHP",
|
||||
"PowerHigherWithUserPositiveStatStages",
|
||||
"PowerHigherWithTargetPositiveStatStages",
|
||||
"TypeDependsOnUserIVs",
|
||||
"PowerHigherWithConsecutiveUse",
|
||||
"PowerHigherWithConsecutiveUseOnUserSide",
|
||||
"PowerHigherWithLessPP",
|
||||
"PowerLowerWithUserHP",
|
||||
"PowerHigherWithUserFasterThanTarget",
|
||||
"PowerHigherWithTargetWeight",
|
||||
"ThrowUserItemAtTarget",
|
||||
"PowerDependsOnUserStockpile"
|
||||
ret = @move.pbBaseDamage(ret, user_battler, target_battler)
|
||||
when "DoublePowerIfUserHasNoItem"
|
||||
ret *= 2 if !user_battler.item || user.has_active_item?(:FLYINGGEM)
|
||||
when "PowerHigherWithTargetFasterThanUser"
|
||||
targetSpeed = target.rough_stat(:SPEED)
|
||||
userSpeed = user.rough_stat(:SPEED)
|
||||
ret = [[(25 * targetSpeed / userSpeed).floor, 150].min, 1].max
|
||||
when "RandomlyDamageOrHealTarget"
|
||||
ret = 50
|
||||
when "RandomPowerDoublePowerIfTargetUnderground"
|
||||
ret = 71
|
||||
ret *= 2 if target_battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground") # Dig
|
||||
when "TypeAndPowerDependOnUserBerry"
|
||||
ret = @move.pbNaturalGiftBaseDamage(user_battler.item_id)
|
||||
when "PowerHigherWithUserHeavierThanTarget"
|
||||
ret = @move.pbBaseDamage(ret, user_battler, target_battler)
|
||||
ret *= 2 if Settings::MECHANICS_GENERATION >= 7 && @trainer.medium_skill? &&
|
||||
target_battler.effects[PBEffects::Minimize]
|
||||
when "AlwaysCriticalHit", "HitTwoTimes", "HitTwoTimesPoisonTarget"
|
||||
ret *= 2
|
||||
when "HitThreeTimesPowersUpWithEachHit"
|
||||
ret *= 6 # Hits do x1, x2, x3 ret in turn, for x6 in total
|
||||
when "HitTwoToFiveTimes"
|
||||
if user.has_active_ability?(:SKILLLINK)
|
||||
ret *= 5
|
||||
else
|
||||
ret = (ret * 31 / 10).floor # Average damage dealt
|
||||
end
|
||||
when "HitTwoToFiveTimesOrThreeForAshGreninja"
|
||||
if user_battler.isSpecies?(:GRENINJA) && user_battler.form == 2
|
||||
ret *= 4 # 3 hits at 20 power = 4 hits at 15 power
|
||||
elsif user.has_active_ability?(:SKILLLINK)
|
||||
ret *= 5
|
||||
else
|
||||
ret = (ret * 31 / 10).floor # Average damage dealt
|
||||
end
|
||||
when "HitOncePerUserTeamMember"
|
||||
mult = 0
|
||||
@ai.battle.eachInTeamFromBattlerIndex(user.index) do |pkmn, _i|
|
||||
mult += 1 if pkmn&.able? && pkmn.status == :NONE
|
||||
end
|
||||
ret *= mult
|
||||
when "TwoTurnAttackOneTurnInSun"
|
||||
ret = @move.pbBaseDamageMultiplier(ret, user_battler, target_battler)
|
||||
when "MultiTurnAttackPowersUpEachTurn"
|
||||
ret *= 2 if user_battler.effects[PBEffects::DefenseCurl]
|
||||
when "MultiTurnAttackBideThenReturnDoubleDamage"
|
||||
ret = 40
|
||||
when "UserFaintsFixedDamageUserHP"
|
||||
ret = user_battler.hp
|
||||
when "EffectivenessIncludesFlyingType"
|
||||
if GameData::Type.exists?(:FLYING)
|
||||
if @trainer.high_skill?
|
||||
targetTypes = target_battler.pbTypes(true)
|
||||
mult = Effectiveness.calculate(
|
||||
:FLYING, targetTypes[0], targetTypes[1], targetTypes[2]
|
||||
)
|
||||
else
|
||||
mult = Effectiveness.calculate(
|
||||
:FLYING, target.types[0], target.types[1], target.effects[PBEffects::Type3]
|
||||
)
|
||||
end
|
||||
ret = (ret.to_f * mult / Effectiveness::NORMAL_EFFECTIVE).round
|
||||
end
|
||||
ret *= 2 if @trainer.medium_skill? && target_battler.effects[PBEffects::Minimize]
|
||||
when "DoublePowerIfUserLastMoveFailed"
|
||||
ret *= 2 if user_battler.lastRoundMoveFailed
|
||||
when "HitTwoTimesFlinchTarget"
|
||||
ret *= 2
|
||||
ret *= 2 if @trainer.medium_skill? && target_battler.effects[PBEffects::Minimize]
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
|
||||
def accuracy
|
||||
return @move.accuracy
|
||||
end
|
||||
|
||||
def rough_accuracy
|
||||
baseAcc = self.accuracy
|
||||
return 100 if baseAcc == 0
|
||||
# Determine user and target
|
||||
user = @ai.user
|
||||
user_battler = user.battler
|
||||
target = @ai.target
|
||||
target_battler = target.battler
|
||||
# Get better base accuracy
|
||||
if @ai.trainer.medium_skill?
|
||||
baseAcc = @move.pbBaseAccuracy(user_battler, target_battler)
|
||||
return 100 if baseAcc == 0
|
||||
end
|
||||
# "Always hit" effects and "always hit" accuracy
|
||||
if @ai.trainer.medium_skill?
|
||||
return 100 if target_battler.effects[PBEffects::Minimize] && @move.tramplesMinimize? &&
|
||||
Settings::MECHANICS_GENERATION >= 6
|
||||
return 100 if target_battler.effects[PBEffects::Telekinesis] > 0
|
||||
end
|
||||
# Get the move's type
|
||||
type = rough_type
|
||||
# Calculate all modifier effects
|
||||
modifiers = {}
|
||||
modifiers[:base_accuracy] = baseAcc
|
||||
modifiers[:accuracy_stage] = user_battler.stages[:ACCURACY]
|
||||
modifiers[:evasion_stage] = target_battler.stages[:EVASION]
|
||||
modifiers[:accuracy_multiplier] = 1.0
|
||||
modifiers[:evasion_multiplier] = 1.0
|
||||
apply_rough_accuracy_modifiers(user, target, type, modifiers)
|
||||
# Check if move certainly misses/can't miss
|
||||
return 0 if modifiers[:base_accuracy] < 0
|
||||
return 100 if modifiers[:base_accuracy] == 0
|
||||
# Calculation
|
||||
accStage = [[modifiers[:accuracy_stage], -6].max, 6].min + 6
|
||||
evaStage = [[modifiers[:evasion_stage], -6].max, 6].min + 6
|
||||
stageMul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9]
|
||||
stageDiv = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3]
|
||||
accuracy = 100.0 * stageMul[accStage] / stageDiv[accStage]
|
||||
evasion = 100.0 * stageMul[evaStage] / stageDiv[evaStage]
|
||||
accuracy = (accuracy * modifiers[:accuracy_multiplier]).round
|
||||
evasion = (evasion * modifiers[:evasion_multiplier]).round
|
||||
evasion = 1 if evasion < 1
|
||||
return modifiers[:base_accuracy] * accuracy / evasion
|
||||
end
|
||||
|
||||
def apply_rough_accuracy_modifiers(user, target, type, modifiers)
|
||||
user_battler = user.battler
|
||||
target_battler = target.battler
|
||||
mold_breaker = (@ai.trainer.medium_skill? && target_battler.hasMoldBreaker?)
|
||||
# Ability effects that alter accuracy calculation
|
||||
if user.ability_active?
|
||||
Battle::AbilityEffects.triggerAccuracyCalcFromUser(
|
||||
user_battler.ability, modifiers, user_battler, target_battler, @move, type
|
||||
)
|
||||
end
|
||||
user_battler.allAllies.each do |b|
|
||||
next if !b.abilityActive?
|
||||
Battle::AbilityEffects.triggerAccuracyCalcFromAlly(
|
||||
b.ability, modifiers, user_battler, target_battler, @move, type
|
||||
)
|
||||
end
|
||||
if !mold_breaker && target.ability_active?
|
||||
Battle::AbilityEffects.triggerAccuracyCalcFromTarget(
|
||||
target_battler.ability, modifiers, user_battler, target_battler, @move, type
|
||||
)
|
||||
end
|
||||
# Item effects that alter accuracy calculation
|
||||
if user.item_active?
|
||||
# TODO: Zoom Lens needs to be checked differently (compare speeds of
|
||||
# user and target).
|
||||
Battle::ItemEffects.triggerAccuracyCalcFromUser(
|
||||
user_battler.item, modifiers, user_battler, target_battler, @move, type
|
||||
)
|
||||
end
|
||||
if target.item_active?
|
||||
Battle::ItemEffects.triggerAccuracyCalcFromTarget(
|
||||
target_battler.item, modifiers, user_battler, target_battler, @move, type
|
||||
)
|
||||
end
|
||||
# Other effects, inc. ones that set accuracy_multiplier or evasion_stage to specific values
|
||||
if @ai.battle.field.effects[PBEffects::Gravity] > 0
|
||||
modifiers[:accuracy_multiplier] *= 5 / 3.0
|
||||
end
|
||||
if @ai.trainer.medium_skill?
|
||||
if user_battler.effects[PBEffects::MicleBerry]
|
||||
modifiers[:accuracy_multiplier] *= 1.2
|
||||
end
|
||||
modifiers[:evasion_stage] = 0 if target_battler.effects[PBEffects::Foresight] && modifiers[:evasion_stage] > 0
|
||||
modifiers[:evasion_stage] = 0 if target_battler.effects[PBEffects::MiracleEye] && modifiers[:evasion_stage] > 0
|
||||
end
|
||||
# "AI-specific calculations below"
|
||||
modifiers[:evasion_stage] = 0 if @move.function == "IgnoreTargetDefSpDefEvaStatStages" # Chip Away
|
||||
if @ai.trainer.medium_skill?
|
||||
modifiers[:base_accuracy] = 0 if user_battler.effects[PBEffects::LockOn] > 0 &&
|
||||
user_battler.effects[PBEffects::LockOnPos] == target_battler.index
|
||||
end
|
||||
if @ai.trainer.medium_skill?
|
||||
case @move.function
|
||||
when "BadPoisonTarget"
|
||||
modifiers[:base_accuracy] = 0 if Settings::MORE_TYPE_EFFECTS &&
|
||||
@move.statusMove? && @user.has_type?(:POISON)
|
||||
when "OHKO", "OHKOIce", "OHKOHitsUndergroundTarget"
|
||||
modifiers[:base_accuracy] = self.accuracy + user_battler.level - target_battler.level
|
||||
modifiers[:accuracy_multiplier] = 0 if target_battler.level > user_battler.level
|
||||
modifiers[:accuracy_multiplier] = 0 if target.has_active_ability?(:STURDY)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
|
||||
def rough_critical_hit_stage
|
||||
user = @ai.user
|
||||
user_battler = user.battler
|
||||
target = @ai.target
|
||||
target_battler = target.battler
|
||||
return -1 if target_battler.pbOwnSide.effects[PBEffects::LuckyChant] > 0
|
||||
mold_breaker = (@ai.trainer.medium_skill? && user_battler.hasMoldBreaker?)
|
||||
crit_stage = 0
|
||||
# Ability effects that alter critical hit rate
|
||||
if user.ability_active?
|
||||
crit_stage = BattleHandlers.triggerCriticalCalcUserAbility(user_battler.ability,
|
||||
user_battler, target_battler, crit_stage)
|
||||
return -1 if crit_stage < 0
|
||||
end
|
||||
if !mold_breaker && target.ability_active?
|
||||
crit_stage = BattleHandlers.triggerCriticalCalcTargetAbility(target_battler.ability,
|
||||
user_battler, target_battler, crit_stage)
|
||||
return -1 if crit_stage < 0
|
||||
end
|
||||
# Item effects that alter critical hit rate
|
||||
if user.item_active?
|
||||
crit_stage = BattleHandlers.triggerCriticalCalcUserItem(user_battler.item,
|
||||
user_battler, target_battler, crit_stage)
|
||||
return -1 if crit_stage < 0
|
||||
end
|
||||
if target.item_active?
|
||||
crit_stage = BattleHandlers.triggerCriticalCalcTargetItem(user_battler.item,
|
||||
user_battler, target_battler, crit_stage)
|
||||
return -1 if crit_stage < 0
|
||||
end
|
||||
# Other effects
|
||||
case @move.pbCritialOverride(user_battler, target_battler)
|
||||
when 1 then return 99
|
||||
when -1 then return -1
|
||||
end
|
||||
return 99 if crit_stage > 50 # Merciless
|
||||
return 99 if user_battler.effects[PBEffects::LaserFocus] > 0
|
||||
crit_stage += 1 if @move.highCriticalRate?
|
||||
crit_stage += user_battler.effects[PBEffects::FocusEnergy]
|
||||
crit_stage += 1 if user_battler.inHyperMode? && @move.type == :SHADOW
|
||||
crit_stage = [crit_stage, Battle::Move::CRITICAL_HIT_RATIOS.length - 1].min
|
||||
return crit_stage
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
|
||||
# pbBaseAccuracy(@ai.user.battler, @ai.target.battler) if @ai.trainer.medium_skill?
|
||||
# pbCriticalOverride(@ai.user.battler, @ai.target.battler)
|
||||
end
|
||||
Reference in New Issue
Block a user