Files
infinitefusion-e18/Data/Scripts/011_Battle/005_AI/103_AIMove.rb

367 lines
14 KiB
Ruby

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