mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-07 21:24:59 +00:00
367 lines
14 KiB
Ruby
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
|