mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-09 14:14:59 +00:00
Initial commit
This commit is contained in:
69
Data/Scripts/011_Battle/004_AI/001_PokeBattle_AI.rb
Normal file
69
Data/Scripts/011_Battle/004_AI/001_PokeBattle_AI.rb
Normal file
@@ -0,0 +1,69 @@
|
||||
# AI skill levels:
|
||||
# 0: Wild Pokémon
|
||||
# 1-31: Basic trainer (young/inexperienced)
|
||||
# 32-47: Some skill
|
||||
# 48-99: High skill
|
||||
# 100+: Best trainers (Gym Leaders, Elite Four, Champion)
|
||||
# NOTE: A trainer's skill value can range from 0-255, but by default only four
|
||||
# distinct skill levels exist. The skill value is typically the same as
|
||||
# the trainer's base money value.
|
||||
module PBTrainerAI
|
||||
# Minimum skill level to be in each AI category.
|
||||
def self.minimumSkill; return 1; end
|
||||
def self.mediumSkill; return 32; end
|
||||
def self.highSkill; return 48; end
|
||||
def self.bestSkill; return 100; end
|
||||
end
|
||||
|
||||
|
||||
|
||||
class PokeBattle_AI
|
||||
def initialize(battle)
|
||||
@battle = battle
|
||||
end
|
||||
|
||||
def pbAIRandom(x); return rand(x); end
|
||||
|
||||
def pbStdDev(choices)
|
||||
sum = 0
|
||||
n = 0
|
||||
choices.each do |c|
|
||||
sum += c[1]
|
||||
n += 1
|
||||
end
|
||||
return 0 if n<2
|
||||
mean = sum.to_f/n.to_f
|
||||
varianceTimesN = 0
|
||||
choices.each do |c|
|
||||
next if c[1]<=0
|
||||
deviation = c[1].to_f-mean
|
||||
varianceTimesN += deviation*deviation
|
||||
end
|
||||
# Using population standard deviation
|
||||
# [(n-1) makes it a sample std dev, would be 0 with only 1 sample]
|
||||
return Math.sqrt(varianceTimesN/n)
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Decide whether the opponent should Mega Evolve their Pokémon
|
||||
#=============================================================================
|
||||
def pbEnemyShouldMegaEvolve?(idxBattler)
|
||||
battler = @battle.battlers[idxBattler]
|
||||
if @battle.pbCanMegaEvolve?(idxBattler) # Simple "always should if possible"
|
||||
PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will Mega Evolve")
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Choose an action
|
||||
#=============================================================================
|
||||
def pbDefaultChooseEnemyCommand(idxBattler)
|
||||
return if pbEnemyShouldUseItem?(idxBattler)
|
||||
return if pbEnemyShouldWithdraw?(idxBattler)
|
||||
return if @battle.pbAutoFightMenu(idxBattler)
|
||||
@battle.pbRegisterMegaEvolution(idxBattler) if pbEnemyShouldMegaEvolve?(idxBattler)
|
||||
pbChooseMoves(idxBattler)
|
||||
end
|
||||
end
|
||||
182
Data/Scripts/011_Battle/004_AI/002_AI_Item.rb
Normal file
182
Data/Scripts/011_Battle/004_AI/002_AI_Item.rb
Normal file
@@ -0,0 +1,182 @@
|
||||
class PokeBattle_AI
|
||||
#=============================================================================
|
||||
# Decide whether the opponent should use an item on the Pokémon
|
||||
#=============================================================================
|
||||
def pbEnemyShouldUseItem?(idxBattler)
|
||||
user = @battle.battlers[idxBattler]
|
||||
item, idxTarget = pbEnemyItemToUse(idxBattler)
|
||||
return false if item==0
|
||||
# Determine target of item (always the Pokémon choosing the action)
|
||||
useType = pbGetItemData(item,ITEM_BATTLE_USE)
|
||||
if useType && (useType==1 || useType==6) # Use on Pokémon
|
||||
idxTarget = @battle.battlers[idxTarget].pokemonIndex # Party Pokémon
|
||||
end
|
||||
# Register use of item
|
||||
@battle.pbRegisterItem(idxBattler,item,idxTarget)
|
||||
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will use item #{PBItems.getName(item)}")
|
||||
return true
|
||||
end
|
||||
|
||||
# NOTE: The AI will only consider using an item on the Pokémon it's currently
|
||||
# choosing an action for.
|
||||
def pbEnemyItemToUse(idxBattler)
|
||||
return 0 if !@internalBattle
|
||||
items = @battle.pbGetOwnerItems(idxBattler)
|
||||
return 0 if !items || items.length==0
|
||||
# Determine target of item (always the Pokémon choosing the action)
|
||||
idxTarget = idxBattler # Battler using the item
|
||||
battler = @battle.battlers[idxTarget]
|
||||
pkmn = battler.pokemon
|
||||
# Item categories
|
||||
hpItems = {
|
||||
:POTION => 20,
|
||||
:SUPERPOTION => 50,
|
||||
:HYPERPOTION => 200,
|
||||
:MAXPOTION => 999,
|
||||
:BERRYJUICE => 20,
|
||||
:SWEETHEART => 20,
|
||||
:FRESHWATER => 50,
|
||||
:SODAPOP => 60,
|
||||
:LEMONADE => 80,
|
||||
:MOOMOOMILK => 100,
|
||||
:ORANBERRY => 10,
|
||||
:SITRUSBERRY => battler.totalhp/4,
|
||||
:ENERGYPOWDER => 50,
|
||||
:ENERGYROOT => 200
|
||||
}
|
||||
hpItems[:RAGECANDYBAR] = 20 if !NEWEST_BATTLE_MECHANICS
|
||||
fullRestoreItems = [
|
||||
:FULLRESTORE
|
||||
]
|
||||
oneStatusItems = [ # Preferred over items that heal all status problems
|
||||
:AWAKENING,:CHESTOBERRY,:BLUEFLUTE,
|
||||
:ANTIDOTE,:PECHABERRY,
|
||||
:BURNHEAL,:RAWSTBERRY,
|
||||
:PARALYZEHEAL,:PARLYZHEAL,:CHERIBERRY,
|
||||
:ICEHEAL,:ASPEARBERRY
|
||||
]
|
||||
allStatusItems = [
|
||||
:FULLHEAL,:LAVACOOKIE,:OLDGATEAU,:CASTELIACONE,:LUMIOSEGALETTE,
|
||||
:SHALOURSABLE,:BIGMALASADA,:LUMBERRY,:HEALPOWDER
|
||||
]
|
||||
allStatusItems.push(:RAGECANDYBAR) if NEWEST_BATTLE_MECHANICS
|
||||
xItems = {
|
||||
:XATTACK => [PBStats::ATTACK,(NEWEST_BATTLE_MECHANICS) ? 2 : 1],
|
||||
:XATTACK2 => [PBStats::ATTACK,2],
|
||||
:XATTACK3 => [PBStats::ATTACK,3],
|
||||
:XATTACK6 => [PBStats::ATTACK,6],
|
||||
:XDEFENSE => [PBStats::DEFENSE,(NEWEST_BATTLE_MECHANICS) ? 2 : 1],
|
||||
:XDEFENSE2 => [PBStats::DEFENSE,2],
|
||||
:XDEFENSE3 => [PBStats::DEFENSE,3],
|
||||
:XDEFENSE6 => [PBStats::DEFENSE,6],
|
||||
:XDEFEND => [PBStats::DEFENSE,(NEWEST_BATTLE_MECHANICS) ? 2 : 1],
|
||||
:XDEFEND2 => [PBStats::DEFENSE,2],
|
||||
:XDEFEND3 => [PBStats::DEFENSE,3],
|
||||
:XDEFEND6 => [PBStats::DEFENSE,6],
|
||||
:XSPATK => [PBStats::SPATK,(NEWEST_BATTLE_MECHANICS) ? 2 : 1],
|
||||
:XSPATK2 => [PBStats::SPATK,2],
|
||||
:XSPATK3 => [PBStats::SPATK,3],
|
||||
:XSPATK6 => [PBStats::SPATK,6],
|
||||
:XSPECIAL => [PBStats::SPATK,(NEWEST_BATTLE_MECHANICS) ? 2 : 1],
|
||||
:XSPECIAL2 => [PBStats::SPATK,2],
|
||||
:XSPECIAL3 => [PBStats::SPATK,3],
|
||||
:XSPECIAL6 => [PBStats::SPATK,6],
|
||||
:XSPDEF => [PBStats::SPDEF,(NEWEST_BATTLE_MECHANICS) ? 2 : 1],
|
||||
:XSPDEF2 => [PBStats::SPDEF,2],
|
||||
:XSPDEF3 => [PBStats::SPDEF,3],
|
||||
:XSPDEF6 => [PBStats::SPDEF,6],
|
||||
:XSPEED => [PBStats::SPEED,(NEWEST_BATTLE_MECHANICS) ? 2 : 1],
|
||||
:XSPEED2 => [PBStats::SPEED,2],
|
||||
:XSPEED3 => [PBStats::SPEED,3],
|
||||
:XSPEED6 => [PBStats::SPEED,6],
|
||||
:XACCURACY => [PBStats::ACCURACY,(NEWEST_BATTLE_MECHANICS) ? 2 : 1],
|
||||
:XACCURACY2 => [PBStats::ACCURACY,2],
|
||||
:XACCURACY3 => [PBStats::ACCURACY,3],
|
||||
:XACCURACY6 => [PBStats::ACCURACY,6]
|
||||
}
|
||||
losthp = battler.totalhp-battler.hp
|
||||
preferFullRestore = (battler.hp<=battler.totalhp*2/3 &&
|
||||
(battler.status!=PBStatuses::NONE || battler.effects[PBEffects::Confusion]>0))
|
||||
# Find all usable items
|
||||
usableHPItems = []
|
||||
usableStatusItems = []
|
||||
usableXItems = []
|
||||
items.each do |i|
|
||||
next if !i || i==0
|
||||
next if !@battle.pbCanUseItemOnPokemon?(i,pkmn,battler,@battle.scene,false)
|
||||
next if !ItemHandlers.triggerCanUseInBattle(i,pkmn,battler,nil,
|
||||
false,self,@battle.scene,false)
|
||||
checkedItem = false
|
||||
# Log HP healing items
|
||||
if losthp>0
|
||||
hpItems.each do |item, power|
|
||||
next if !isConst?(i,PBItems,item)
|
||||
checkedItem = true
|
||||
usableHPItems.push([i,5,power])
|
||||
end
|
||||
next if checkedItem
|
||||
end
|
||||
# Log Full Restores (HP healer and status curer)
|
||||
if losthp>0 || battler.status!=PBStatuses::NONE
|
||||
fullRestoreItems.each do |item|
|
||||
next if !isConst?(i,PBItems,item)
|
||||
checkedItem = true
|
||||
usableHPItems.push([i,(preferFullRestore) ? 3 : 7,999])
|
||||
usableStatusItems.push([i,(preferFullRestore) ? 3 : 9])
|
||||
end
|
||||
next if checkedItem
|
||||
end
|
||||
# Log single status-curing items
|
||||
if battler.status!=PBStatuses::NONE
|
||||
oneStatusItems.each do |item|
|
||||
next if !isConst?(i,PBItems,item)
|
||||
checkedItem = true
|
||||
usableStatusItems.push([i,5])
|
||||
end
|
||||
next if checkedItem
|
||||
# Log Full Heal-type items
|
||||
allStatusItems.each do |item|
|
||||
next if !isConst?(i,PBItems,item)
|
||||
checkedItem = true
|
||||
usableStatusItems.push([i,7])
|
||||
end
|
||||
next if checkedItem
|
||||
end
|
||||
# Log stat-raising items
|
||||
xItems.each do |item, data|
|
||||
next if !isConst?(i,PBItems,item)
|
||||
checkedItem = true
|
||||
usableXItems.push([i,battler.stages[data[0]],data[1]])
|
||||
end
|
||||
next if checkedItem
|
||||
end
|
||||
# Prioritise using a HP restoration item
|
||||
if usableHPItems.length>0 && (battler.hp<=battler.totalhp/4 ||
|
||||
(battler.hp<=battler.totalhp/2 && pbAIRandom(100)<30))
|
||||
usableHPItems.sort! { |a,b| (a[1]==b[1]) ? a[2]<=>b[2] : a[1]<=>b[1] }
|
||||
prevItem = nil
|
||||
usableHPItems.each do |i|
|
||||
return i[0],idxTarget if i[2]>=losthp
|
||||
prevItem = i
|
||||
end
|
||||
return prevItem[0],idxTarget
|
||||
end
|
||||
# Next prioritise using a status-curing item
|
||||
if usableStatusItems.length>0 && pbAIRandom(100)<40
|
||||
usableStatusItems.sort! { |a,b| a[1]<=>b[1] }
|
||||
return usableStatusItems[0][0],idxTarget
|
||||
end
|
||||
# Next try using an X item
|
||||
if usableXItems.length>0 && pbAIRandom(100)<30
|
||||
usableXItems.sort! { |a,b| (a[1]==b[1]) ? a[2]<=>b[2] : a[1]<=>b[1] }
|
||||
prevItem = nil
|
||||
usableXItems.each do |i|
|
||||
break if prevItem && i[1]>prevItem[1]
|
||||
return i[0],idxTarget if i[1]+i[2]>=6
|
||||
prevItem = i
|
||||
end
|
||||
return prevItem[0],idxTarget
|
||||
end
|
||||
return 0
|
||||
end
|
||||
end
|
||||
182
Data/Scripts/011_Battle/004_AI/003_AI_Switch.rb
Normal file
182
Data/Scripts/011_Battle/004_AI/003_AI_Switch.rb
Normal file
@@ -0,0 +1,182 @@
|
||||
class PokeBattle_AI
|
||||
#=============================================================================
|
||||
# Decide whether the opponent should switch Pokémon
|
||||
#=============================================================================
|
||||
def pbEnemyShouldWithdraw?(idxBattler)
|
||||
return pbEnemyShouldWithdrawEx?(idxBattler,false)
|
||||
end
|
||||
|
||||
def pbEnemyShouldWithdrawEx?(idxBattler,forceSwitch)
|
||||
return false if @battle.wildBattle?
|
||||
shouldSwitch = forceSwitch
|
||||
batonPass = -1
|
||||
moveType = -1
|
||||
skill = @battle.pbGetOwnerFromBattlerIndex(idxBattler).skill || 0
|
||||
battler = @battle.battlers[idxBattler]
|
||||
# If Pokémon is within 6 levels of the foe, and foe's last move was
|
||||
# super-effective and powerful
|
||||
if !shouldSwitch && battler.turnCount>0 && skill>=PBTrainerAI.highSkill
|
||||
target = battler.pbDirectOpposing(true)
|
||||
if !target.fainted? && target.lastMoveUsed>0 &&
|
||||
(target.level-battler.level).abs<=6
|
||||
moveData = pbGetMoveData(target.lastMoveUsed)
|
||||
moveType = moveData[MOVE_TYPE]
|
||||
typeMod = pbCalcTypeMod(moveType,target,battler)
|
||||
if PBTypes.superEffective?(typeMod) && moveData[MOVE_BASE_DAMAGE]>50
|
||||
switchChance = (moveData[MOVE_BASE_DAMAGE]>70) ? 30 : 20
|
||||
shouldSwitch = (pbAIRandom(100)<switchChance)
|
||||
end
|
||||
end
|
||||
end
|
||||
# Pokémon can't do anything (must have been in battle for at least 5 rounds)
|
||||
if !@battle.pbCanChooseAnyMove?(idxBattler) &&
|
||||
battler.turnCount && battler.turnCount>=5
|
||||
shouldSwitch = true
|
||||
end
|
||||
# Pokémon is Perish Songed and has Baton Pass
|
||||
if skill>=PBTrainerAI.highSkill && battler.effects[PBEffects::PerishSong]==1
|
||||
battler.eachMoveWithIndex do |m,i|
|
||||
next if m.function!="0ED" # Baton Pass
|
||||
next if !@battle.pbCanChooseMove?(idxBattler,i,false)
|
||||
batonPass = i
|
||||
break
|
||||
end
|
||||
end
|
||||
# Pokémon will faint because of bad poisoning at the end of this round, but
|
||||
# would survive at least one more round if it were regular poisoning instead
|
||||
if battler.status==PBStatuses::POISON && battler.statusCount>0 &&
|
||||
skill>=PBTrainerAI.highSkill
|
||||
toxicHP = battler.totalhp/16
|
||||
nextToxicHP = toxicHP*(battler.effects[PBEffects::Toxic]+1)
|
||||
if battler.hp<=nextToxicHP && battler.hp>toxicHP*2
|
||||
shouldSwitch = true if pbAIRandom(100)<80
|
||||
end
|
||||
end
|
||||
# Pokémon is Encored into an unfavourable move
|
||||
if battler.effects[PBEffects::Encore]>0 && skill>=PBTrainerAI.mediumSkill
|
||||
idxEncoredMove = battler.pbEncoredMoveIndex
|
||||
if idxEncoredMove>=0
|
||||
scoreSum = 0
|
||||
scoreCount = 0
|
||||
battler.eachOpposing do |b|
|
||||
scoreSum += pbGetMoveScore(battler.moves[idxEncoredMove],battler,b,skill)
|
||||
scoreCount += 1
|
||||
end
|
||||
if scoreCount>0 && scoreSum/scoreCount<=20
|
||||
shouldSwitch = true if pbAIRandom(100)<80
|
||||
end
|
||||
end
|
||||
end
|
||||
# If there is a single foe and it is resting after Hyper Beam or is
|
||||
# Truanting (i.e. free turn)
|
||||
if @battle.pbSideSize(battler.index+1)==1 &&
|
||||
!battler.pbDirectOpposing.fainted? && skill>=PBTrainerAI.highSkill
|
||||
opp = battler.pbDirectOpposing
|
||||
if opp.effects[PBEffects::HyperBeam]>0 ||
|
||||
(opp.hasActiveAbility?(:TRUANT) && opp.effects[PBEffects::Truant])
|
||||
shouldSwitch = false if pbAIRandom(100)<80
|
||||
end
|
||||
end
|
||||
# Sudden Death rule - I'm not sure what this means
|
||||
if @battle.rules["suddendeath"] && battler.turnCount>0
|
||||
if battler.hp<=battler.totalhp/4 && pbAIRandom(100)<30
|
||||
shouldSwitch = true
|
||||
elsif battler.hp<=battler.totalhp/2 && pbAIRandom(100)<80
|
||||
shouldSwitch = true
|
||||
end
|
||||
end
|
||||
# Pokémon is about to faint because of Perish Song
|
||||
if battler.effects[PBEffects::PerishSong]==1
|
||||
shouldSwitch = true
|
||||
end
|
||||
if shouldSwitch
|
||||
list = []
|
||||
@battle.pbParty(idxBattler).each_with_index do |pkmn,i|
|
||||
next if !@battle.pbCanSwitch?(idxBattler,i)
|
||||
# If perish count is 1, it may be worth it to switch
|
||||
# even with Spikes, since Perish Song's effect will end
|
||||
if battler.effects[PBEffects::PerishSong]!=1
|
||||
# Will contain effects that recommend against switching
|
||||
spikes = battler.pbOwnSide.effects[PBEffects::Spikes]
|
||||
# Don't switch to this if too little HP
|
||||
if spikes>0
|
||||
spikesDmg = [8,6,4][spikes-1]
|
||||
if pkmn.hp<=pkmn.totalhp/spikesDmg
|
||||
next if !pkmn.hasType?(:FLYING) && !pkmn.hasActiveAbility?(:LEVITATE)
|
||||
end
|
||||
end
|
||||
end
|
||||
# moveType is the type of the target's last used move
|
||||
if moveType>=0 && PBTypes.ineffective?(pbCalcTypeMod(moveType,battler,battler))
|
||||
weight = 65
|
||||
typeMod = pbCalcTypeModPokemon(pkmn,battler.pbDirectOpposing(true))
|
||||
if PBTypes.superEffective?(typeMod.to_f/PBTypeEffectivenesss::NORMAL_EFFECTIVE)
|
||||
# Greater weight if new Pokemon's type is effective against target
|
||||
weight = 85
|
||||
end
|
||||
list.unshift(i) if pbAIRandom(100)<weight # Put this Pokemon first
|
||||
elsif moveType>=0 && PBTypes.resistant?(pbCalcTypeMod(moveType,battler,battler))
|
||||
weight = 40
|
||||
typeMod = pbCalcTypeModPokemon(pkmn,battler.pbDirectOpposing(true))
|
||||
if PBTypes.superEffective?(typeMod.to_f/PBTypeEffectivenesss::NORMAL_EFFECTIVE)
|
||||
# Greater weight if new Pokemon's type is effective against target
|
||||
weight = 60
|
||||
end
|
||||
list.unshift(i) if pbAIRandom(100)<weight # Put this Pokemon first
|
||||
else
|
||||
list.push(i) # put this Pokemon last
|
||||
end
|
||||
end
|
||||
if list.length>0
|
||||
if batonPass>=0 && @battle.pbRegisterMove(idxBattler,batonPass,false)
|
||||
PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will use Baton Pass to avoid Perish Song")
|
||||
return true
|
||||
end
|
||||
if @battle.pbRegisterSwitch(idxBattler,list[0])
|
||||
PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will switch with " +
|
||||
"#{@battle.pbParty(idxBattler)[list[0]].name}")
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Choose a replacement Pokémon
|
||||
#=============================================================================
|
||||
def pbDefaultChooseNewEnemy(idxBattler,party)
|
||||
enemies = []
|
||||
party.each_with_index do |p,i|
|
||||
enemies.push(i) if @battle.pbCanSwitchLax?(idxBattler,i)
|
||||
end
|
||||
return -1 if enemies.length==0
|
||||
return pbChooseBestNewEnemy(idxBattler,party,enemies)
|
||||
end
|
||||
|
||||
def pbChooseBestNewEnemy(idxBattler,party,enemies)
|
||||
return -1 if !enemies || enemies.length==0
|
||||
best = -1
|
||||
bestSum = 0
|
||||
movesData = pbLoadMovesData
|
||||
enemies.each do |i|
|
||||
pkmn = party[i]
|
||||
sum = 0
|
||||
pkmn.moves.each do |m|
|
||||
next if m.id==0
|
||||
moveData = movesData[m.id]
|
||||
next if moveData[MOVE_BASE_DAMAGE]==0
|
||||
@battle.battlers[idxBattler].eachOpposing do |b|
|
||||
bTypes = b.pbTypes(true)
|
||||
sum += PBTypes.getCombinedEffectiveness(moveData[MOVE_TYPE],
|
||||
bTypes[0],bTypes[1],bTypes[2])
|
||||
end
|
||||
end
|
||||
if best==-1 || sum>bestSum
|
||||
best = i
|
||||
bestSum = sum
|
||||
end
|
||||
end
|
||||
return best
|
||||
end
|
||||
end
|
||||
287
Data/Scripts/011_Battle/004_AI/004_AI_Move.rb
Normal file
287
Data/Scripts/011_Battle/004_AI/004_AI_Move.rb
Normal file
@@ -0,0 +1,287 @@
|
||||
class PokeBattle_AI
|
||||
#=============================================================================
|
||||
# Main move-choosing method (moves with higher scores are more likely to be
|
||||
# chosen)
|
||||
#=============================================================================
|
||||
def pbChooseMoves(idxBattler)
|
||||
user = @battle.battlers[idxBattler]
|
||||
wildBattler = (@battle.wildBattle? && @battle.opposes?(idxBattler))
|
||||
skill = 0
|
||||
if !wildBattler
|
||||
skill = @battle.pbGetOwnerFromBattlerIndex(user.index).skill || 0
|
||||
end
|
||||
# Get scores and targets for each move
|
||||
# NOTE: A move is only added to the choices array if it has a non-zero
|
||||
# score.
|
||||
choices = []
|
||||
user.eachMoveWithIndex do |m,i|
|
||||
next if !@battle.pbCanChooseMove?(idxBattler,i,false)
|
||||
if wildBattler
|
||||
pbRegisterMoveWild(user,i,choices)
|
||||
else
|
||||
pbRegisterMoveTrainer(user,i,choices,skill)
|
||||
end
|
||||
end
|
||||
# Figure out useful information about the choices
|
||||
totalScore = 0
|
||||
maxScore = 0
|
||||
choices.each do |c|
|
||||
totalScore += c[1]
|
||||
maxScore = c[1] if maxScore<c[1]
|
||||
end
|
||||
# Log the available choices
|
||||
if $INTERNAL
|
||||
logMsg = "[AI] Move choices for #{user.pbThis(true)} (#{user.index}): "
|
||||
choices.each_with_index do |c,i|
|
||||
logMsg += "#{user.moves[c[0]].name}=#{c[1]}"
|
||||
logMsg += " (target #{c[2]})" if c[2]>=0
|
||||
logMsg += ", " if i<choices.length-1
|
||||
end
|
||||
PBDebug.log(logMsg)
|
||||
end
|
||||
# Find any preferred moves and just choose from them
|
||||
if !wildBattler && skill>=PBTrainerAI.highSkill && maxScore>100
|
||||
stDev = pbStdDev(choices)
|
||||
if stDev>=40 && pbAIRandom(100)<90
|
||||
preferredMoves = []
|
||||
choices.each do |c|
|
||||
next if c[1]<200 && c[1]<maxScore*0.8
|
||||
preferredMoves.push(c)
|
||||
preferredMoves.push(c) if c[1]==maxScore # Doubly prefer the best move
|
||||
end
|
||||
if preferredMoves.length>0
|
||||
m = preferredMoves[pbAIRandom(preferredMoves.length)]
|
||||
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) prefers #{user.moves[m[0]].name}")
|
||||
@battle.pbRegisterMove(idxBattler,m[0],false)
|
||||
@battle.pbRegisterTarget(idxBattler,m[2]) if m[2]>=0
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
# Decide whether all choices are bad, and if so, try switching instead
|
||||
if !wildBattler && skill>=PBTrainerAI.highSkill
|
||||
badMoves = false
|
||||
if (maxScore<=20 && user.turnCount>2) ||
|
||||
(maxScore<=40 && user.turnCount>5)
|
||||
badMoves = true if pbAIRandom(100)<80
|
||||
end
|
||||
if !badMoves && totalScore<100 && user.turnCount>1
|
||||
badMoves = true
|
||||
choices.each do |c|
|
||||
next if !user.moves[c[0]].damagingMove?
|
||||
badMoves = false
|
||||
break
|
||||
end
|
||||
badMoves = false if badMoves && pbAIRandom(100)<10
|
||||
end
|
||||
if badMoves && pbEnemyShouldWithdrawEx?(idxBattler,true)
|
||||
if $INTERNAL
|
||||
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will switch due to terrible moves")
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
# Randomly choose a move to use
|
||||
if choices.length==0
|
||||
# If there are no calculated choices, use Struggle (or an Encored move)
|
||||
@battle.pbAutoChooseMove(idxBattler)
|
||||
else
|
||||
# Randomly choose a move from the choices and register it
|
||||
randNum = pbAIRandom(totalScore)
|
||||
choices.each do |c|
|
||||
randNum -= c[1]
|
||||
next if randNum>=0
|
||||
@battle.pbRegisterMove(idxBattler,c[0],false)
|
||||
@battle.pbRegisterTarget(idxBattler,c[2]) if c[2]>=0
|
||||
break
|
||||
end
|
||||
end
|
||||
# Log the result
|
||||
if @battle.choices[idxBattler][2]
|
||||
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will use #{@battle.choices[user.index][2].name}")
|
||||
end
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Get scores for the given move against each possible target
|
||||
#=============================================================================
|
||||
# Wild Pokémon choose their moves randomly.
|
||||
def pbRegisterMoveWild(user,idxMove,choices)
|
||||
choices.push([idxMove,100,-1]) # Move index, score, target
|
||||
end
|
||||
|
||||
# Trainer Pokémon calculate how much they want to use each of their moves.
|
||||
def pbRegisterMoveTrainer(user,idxMove,choices,skill)
|
||||
move = user.moves[idxMove]
|
||||
targetType = move.pbTarget(user)
|
||||
if PBTargets.multipleTargets?(targetType)
|
||||
# If move affects multiple battlers and you don't choose a particular one
|
||||
totalScore = 0
|
||||
@battle.eachBattler do |b|
|
||||
next if !@battle.pbMoveCanTarget?(user.index,b.index,targetType)
|
||||
score = pbGetMoveScore(move,user,b,skill)
|
||||
totalScore += ((user.opposes?(b)) ? score : -score)
|
||||
end
|
||||
choices.push([idxMove,totalScore,-1]) if totalScore>0
|
||||
elsif PBTargets.noTargets?(targetType)
|
||||
# If move has no targets, affects the user, a side or the whole field
|
||||
score = pbGetMoveScore(move,user,user,skill)
|
||||
choices.push([idxMove,score,-1]) if score>0
|
||||
else
|
||||
# If move affects one battler and you have to choose which one
|
||||
scoresAndTargets = []
|
||||
@battle.eachBattler do |b|
|
||||
next if !@battle.pbMoveCanTarget?(user.index,b.index,targetType)
|
||||
next if PBTargets.canChooseFoeTarget?(targetType) && !user.opposes?(b)
|
||||
score = pbGetMoveScore(move,user,b,skill)
|
||||
scoresAndTargets.push([score,b.index]) if score>0
|
||||
end
|
||||
if scoresAndTargets.length>0
|
||||
# Get the one best target for the move
|
||||
scoresAndTargets.sort! { |a,b| b[0]<=>a[0] }
|
||||
choices.push([idxMove,scoresAndTargets[0][0],scoresAndTargets[0][1]])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Get a score for the given move being used against the given target
|
||||
#=============================================================================
|
||||
def pbGetMoveScore(move,user,target,skill=100)
|
||||
skill = PBTrainerAI.minimumSkill if skill<PBTrainerAI.minimumSkill
|
||||
score = 100
|
||||
score = pbGetMoveScoreFunctionCode(score,move,user,target,skill)
|
||||
# A score of 0 here means it absolutely should not be used
|
||||
return 0 if score<=0
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
# Prefer damaging moves if AI has no more Pokémon or AI is less clever
|
||||
if @battle.pbAbleNonActiveCount(user.idxOwnSide)==0
|
||||
if !(skill>=PBTrainerAI.highSkill && @battle.pbAbleNonActiveCount(target.idxOwnSide)>0)
|
||||
if move.statusMove?
|
||||
score /= 1.5
|
||||
elsif target.hp<=target.totalhp/2
|
||||
score *= 1.5
|
||||
end
|
||||
end
|
||||
end
|
||||
# Don't prefer attacking the target if they'd be semi-invulnerable
|
||||
if skill>=PBTrainerAI.highSkill && move.accuracy>0 &&
|
||||
(target.semiInvulnerable? || target.effects[PBEffects::SkyDrop]>=0)
|
||||
miss = true
|
||||
miss = false if user.hasActiveAbility?(:NOGUARD) || target.hasActiveAbility?(:NOGUARD)
|
||||
if miss && pbRoughStat(user,PBStats::SPEED,skill)>pbRoughStat(target,PBStats::SPEED,skill)
|
||||
# Knows what can get past semi-invulnerability
|
||||
if target.effects[PBEffects::SkyDrop]>=0
|
||||
miss = false if move.hitsFlyingTargets?
|
||||
else
|
||||
if target.inTwoTurnAttack?("0C9","0CC","0CE") # Fly, Bounce, Sky Drop
|
||||
miss = false if move.hitsFlyingTargets?
|
||||
elsif target.inTwoTurnAttack?("0CA") # Dig
|
||||
miss = false if move.hitsDiggingTargets?
|
||||
elsif target.inTwoTurnAttack?("0CB") # Dive
|
||||
miss = false if move.hitsDivingTargets?
|
||||
end
|
||||
end
|
||||
end
|
||||
score -= 80 if miss
|
||||
end
|
||||
# Pick a good move for the Choice items
|
||||
if user.hasActiveItem?([:CHOICEBAND,:CHOICESPECS,:CHOICESCARF])
|
||||
if move.baseDamage>=60; score += 60
|
||||
elsif move.damagingMove?; score += 30
|
||||
elsif move.function=="0F2"; score += 70 # Trick
|
||||
else; score -= 60
|
||||
end
|
||||
end
|
||||
# If user is asleep, prefer moves that are usable while asleep
|
||||
if user.status==PBStatuses::SLEEP && !move.usableWhenAsleep?
|
||||
hasSleepMove = false
|
||||
user.eachMove do |m|
|
||||
next unless m.usableWhenAsleep?
|
||||
score -= 60
|
||||
break
|
||||
end
|
||||
end
|
||||
# If user is frozen, prefer a move that can thaw the user
|
||||
if user.status==PBStatuses::FROZEN
|
||||
if move.thawsUser?
|
||||
score += 40
|
||||
else
|
||||
user.eachMove do |m|
|
||||
next unless m.thawsUser?
|
||||
score -= 60
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
# If target is frozen, don't prefer moves that could thaw them
|
||||
if target.status==PBStatuses::FROZEN
|
||||
user.eachMove do |m|
|
||||
next if m.thawsUser?
|
||||
score -= 60
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
# Adjust score based on how much damage it can deal
|
||||
if move.damagingMove?
|
||||
score = pbGetMoveScoreDamage(score,move,user,target,skill)
|
||||
else # Status moves
|
||||
# Don't prefer attacks which don't deal damage
|
||||
score -= 10
|
||||
# Account for accuracy of move
|
||||
accuracy = pbRoughAccuracy(move,user,target,skill)
|
||||
score *= accuracy/100.0
|
||||
score = 0 if score<=10 && skill>=PBTrainerAI.highSkill
|
||||
end
|
||||
score = score.to_i
|
||||
score = 0 if score<0
|
||||
return score
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Add to a move's score based on how much damage it will deal (as a percentage
|
||||
# of the target's current HP)
|
||||
#=============================================================================
|
||||
def pbGetMoveScoreDamage(score,move,user,target,skill)
|
||||
# Don't prefer moves that are ineffective because of abilities or effects
|
||||
return 0 if score<=0 || pbCheckMoveImmunity(score,move,user,target,skill)
|
||||
# Calculate how much damage the move will do (roughly)
|
||||
baseDmg = pbMoveBaseDamage(move,user,target,skill)
|
||||
realDamage = pbRoughDamage(move,user,target,skill,baseDmg)
|
||||
# Account for accuracy of move
|
||||
accuracy = pbRoughAccuracy(move,user,target,skill)
|
||||
realDamage *= accuracy/100.0
|
||||
# Two-turn attacks waste 2 turns to deal one lot of damage
|
||||
if move.chargingTurnMove? || move.function=="0C2" # Hyper Beam
|
||||
realDamage *= 2/3 # Not halved because semi-invulnerable during use or hits first turn
|
||||
end
|
||||
# Prefer flinching external effects (note that move effects which cause
|
||||
# flinching are dealt with in the function code part of score calculation)
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if !target.hasActiveAbility?(:INNERFOCUS) &&
|
||||
!target.hasActiveAbility?(:SHIELDDUST) &&
|
||||
target.effects[PBEffects::Substitute]==0
|
||||
canFlinch = false
|
||||
if move.canKingsRock? && user.hasActiveItem?([:KINGSROCK,:RAZORFANG])
|
||||
canFlinch = true
|
||||
end
|
||||
if user.hasActiveAbility?(:STENCH) && !move.flinchingMove?
|
||||
canFlinch = true
|
||||
end
|
||||
realDamage *= 1.3 if canFlinch
|
||||
end
|
||||
end
|
||||
# Convert damage to percentage of target's remaining HP
|
||||
damagePercentage = realDamage*100.0/target.hp
|
||||
# Don't prefer weak attacks
|
||||
# damagePercentage /= 2 if damagePercentage<20
|
||||
# Prefer damaging attack if level difference is significantly high
|
||||
damagePercentage *= 1.2 if user.level-10>target.level
|
||||
# Adjust score
|
||||
damagePercentage = 120 if damagePercentage>120 # Treat all lethal moves the same
|
||||
damagePercentage += 40 if damagePercentage>100 # Prefer moves likely to be lethal
|
||||
score += damagePercentage.to_i
|
||||
return score
|
||||
end
|
||||
end
|
||||
3075
Data/Scripts/011_Battle/004_AI/005_AI_Move_EffectScores.rb
Normal file
3075
Data/Scripts/011_Battle/004_AI/005_AI_Move_EffectScores.rb
Normal file
File diff suppressed because it is too large
Load Diff
675
Data/Scripts/011_Battle/004_AI/006_AI_Move_Utilities.rb
Normal file
675
Data/Scripts/011_Battle/004_AI/006_AI_Move_Utilities.rb
Normal file
@@ -0,0 +1,675 @@
|
||||
class PokeBattle_AI
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
def pbTargetsMultiple?(move,user)
|
||||
numTargets = 0
|
||||
case move.pbTarget(user)
|
||||
when PBTargets::AllNearFoes
|
||||
@battle.eachOtherSideBattler(user) { |b| numTargets += 1 if b.near?(user) }
|
||||
return numTargets>1
|
||||
when PBTargets::AllNearOthers
|
||||
@battle.eachBattler { |b| numTargets += 1 if b.near?(user) }
|
||||
return numTargets>1
|
||||
when PBTargets::UserAndAllies
|
||||
@battle.eachSameSideBattler(user) { |b| numTargets += 1 }
|
||||
return numTargets>1
|
||||
when PBTargets::AllFoes
|
||||
@battle.eachOtherSideBattler(user) { |b| numTargets += 1 }
|
||||
return numTargets>1
|
||||
when PBTargets::AllBattlers
|
||||
@battle.eachBattler { |b| numTargets += 1 }
|
||||
return numTargets>1
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Move's type effectiveness
|
||||
#=============================================================================
|
||||
def pbCalcTypeModSingle(moveType,defType,user,target)
|
||||
ret = PBTypes.getEffectiveness(moveType,defType)
|
||||
# Ring Target
|
||||
if target.hasActiveItem?(:RINGTARGET)
|
||||
ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if PBTypes.ineffective?(moveType,defType)
|
||||
end
|
||||
# Foresight
|
||||
if user.hasActiveAbility?(:SCRAPPY) || target.effects[PBEffects::Foresight]
|
||||
ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:GHOST) &&
|
||||
PBTypes.ineffective?(moveType,defType)
|
||||
end
|
||||
# Miracle Eye
|
||||
if target.effects[PBEffects::MiracleEye]
|
||||
ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:DARK) &&
|
||||
PBTypes.ineffective?(moveType,defType)
|
||||
end
|
||||
# Delta Stream's weather
|
||||
if @battle.pbWeather==PBWeather::StrongWinds
|
||||
ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:FLYING) &&
|
||||
PBTypes.superEffective?(moveType,defType)
|
||||
end
|
||||
# Grounded Flying-type Pokémon become susceptible to Ground moves
|
||||
if !target.airborne?
|
||||
ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:FLYING) &&
|
||||
isConst?(moveType,PBTypes,:GROUND)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
def pbCalcTypeMod(moveType,user,target)
|
||||
return PBTypeEffectiveness::NORMAL_EFFECTIVE if moveType<0
|
||||
return PBTypeEffectiveness::NORMAL_EFFECTIVE if isConst?(moveType,PBTypes,:GROUND) &&
|
||||
target.pbHasType?(:FLYING) && target.hasActiveItem?(:IRONBALL)
|
||||
# Determine types
|
||||
tTypes = target.pbTypes(true)
|
||||
# Get effectivenesses
|
||||
typeMods = [PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE] * 3 # 3 types max
|
||||
tTypes.each_with_index do |type,i|
|
||||
typeMods[i] = pbCalcTypeModSingle(moveType,type,user,target)
|
||||
end
|
||||
# Multiply all effectivenesses together
|
||||
ret = 1
|
||||
typeMods.each { |m| ret *= m }
|
||||
return ret
|
||||
end
|
||||
|
||||
# For switching. Determines the effectiveness of a potential switch-in against
|
||||
# an opposing battler.
|
||||
def pbCalcTypeModPokemon(battlerThis,battlerOther)
|
||||
mod1 = PBTypes.getCombinedEffectiveness(battlerThis.type1,target.type1,target.type2)
|
||||
mod2 = PBTypeEffectiveness::NORMAL_EFFECTIVE
|
||||
if battlerThis.type1!=battlerThis.type2
|
||||
mod2 = PBTypes.getCombinedEffectiveness(battlerThis.type2,target.type1,target.type2)
|
||||
end
|
||||
return mod1*mod2 # Normal effectiveness is 64 here
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Immunity to a move because of the target's ability, item or other effects
|
||||
#=============================================================================
|
||||
def pbCheckMoveImmunity(score,move,user,target,skill)
|
||||
type = pbRoughType(move,user,skill)
|
||||
typeMod = pbCalcTypeMod(type,user,target)
|
||||
# Type effectiveness
|
||||
return true if PBTypes.ineffective?(typeMod) || score<=0
|
||||
# Immunity due to ability/item/other effects
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if isConst?(move.type,PBTypes,:GROUND)
|
||||
return true if target.airborne? && !move.hitsFlyingTargets?
|
||||
elsif isConst?(move.type,PBTypes,:FIRE)
|
||||
return true if target.hasActiveAbility?(:FLASHFIRE)
|
||||
elsif isConst?(move.type,PBTypes,:WATER)
|
||||
return true if target.hasActiveAbility?([:DRYSKIN,:STORMDRAIN,:WATERABSORB])
|
||||
elsif isConst?(move.type,PBTypes,:GRASS)
|
||||
return true if target.hasActiveAbility?(:SAPSIPPER)
|
||||
elsif isConst?(move.type,PBTypes,:ELECTRIC)
|
||||
return true if target.hasActiveAbility?([:LIGHTNINGROD,:MOTORDRIVE,:VOLTABSORB])
|
||||
end
|
||||
return true if PBTypes.notVeryEffective?(typeMod) &&
|
||||
target.hasActiveAbility?(:WONDERGUARD)
|
||||
return true if move.damagingMove? && user.index!=target.index && !target.opposes?(user) &&
|
||||
target.hasActiveAbility?(:TELEPATHY)
|
||||
return true if move.canMagicCoat? && target.hasActiveAbility?(:MAGICBOUNCE) &&
|
||||
target.opposes?(user)
|
||||
return true if move.soundMove? && target.hasActiveAbility?(:SOUNDPROOF)
|
||||
return true if move.bombMove? && target.hasActiveAbility?(:BULLETPROOF)
|
||||
if move.powderMove?
|
||||
return true if target.pbHasType?(:GRASS)
|
||||
return true if target.hasActiveAbility?(:OVERCOAT)
|
||||
return true if target.hasActiveItem?(:SAFETYGOGGLES)
|
||||
end
|
||||
return true if target.effects[PBEffects::Substitute]>0 && move.statusMove? &&
|
||||
!move.ignoresSubstitute?(user) && user.index!=target.index
|
||||
return true if NEWEST_BATTLE_MECHANICS && user.hasActiveAbility?(:PRANKSTER) &&
|
||||
target.pbHasType?(:DARK) && target.opposes?(user)
|
||||
return true if move.priority>0 && @battle.field.terrain==PBBattleTerrains::Psychic &&
|
||||
target.affectedByTerrain? && target.opposes?(user)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Get approximate properties for a battler
|
||||
#=============================================================================
|
||||
def pbRoughType(move,user,skill)
|
||||
ret = move.type
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
ret = move.pbCalcType(user)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
def pbRoughStat(battler,stat,skill)
|
||||
return battler.pbSpeed if skill>=PBTrainerAI.highSkill && stat==PBStats::SPEED
|
||||
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 PBStats::ATTACK; value = battler.attack
|
||||
when PBStats::DEFENSE; value = battler.defense
|
||||
when PBStats::SPATK; value = battler.spatk
|
||||
when PBStats::SPDEF; value = battler.spdef
|
||||
when PBStats::SPEED; value = battler.speed
|
||||
end
|
||||
return (value.to_f*stageMul[stage]/stageDiv[stage]).floor
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Get a better move's base damage value
|
||||
#=============================================================================
|
||||
def pbMoveBaseDamage(move,user,target,skill)
|
||||
baseDmg = move.baseDamage
|
||||
baseDmg = 60 if baseDmg==1
|
||||
return baseDmg if skill<PBTrainerAI.mediumSkill
|
||||
# Covers all function codes which have their own def pbBaseDamage
|
||||
case move.function
|
||||
when "010" # Stomp
|
||||
baseDmg *= 2 if skill>=PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize]
|
||||
# Sonic Boom, Dragon Rage, Super Fang, Night Shade, Endeavor
|
||||
when "06A", "06B", "06C", "06D", "06E"
|
||||
baseDmg = move.pbFixedDamage(user,target)
|
||||
when "06F" # Psywave
|
||||
baseDmg = user.level
|
||||
when "070" # OHKO
|
||||
baseDmg = 200
|
||||
when "071", "072", "073" # Counter, Mirror Coat, Metal Burst
|
||||
baseDmg = 60
|
||||
when "075", "076", "0D0", "12D" # Surf, Earthquake, Whirlpool, Shadow Storm
|
||||
baseDmg = move.pbModifyDamage(baseDmg,user,target)
|
||||
# Gust, Twister, Venoshock, Smelling Salts, Wake-Up Slap, Facade, Hex, Brine,
|
||||
# Retaliate, Weather Ball, Return, Frustration, Eruption, Crush Grip,
|
||||
# Stored Power, Punishment, Hidden Power, Fury Cutter, Echoed Voice,
|
||||
# Trump Card, Flail, Electro Ball, Low Kick, Fling, Spit Up
|
||||
when "077", "078", "07B", "07C", "07D", "07E", "07F", "080", "085", "087",
|
||||
"089", "08A", "08B", "08C", "08E", "08F", "090", "091", "092", "097",
|
||||
"098", "099", "09A", "0F7", "113"
|
||||
baseDmg = move.pbBaseDamage(baseDmg,user,target)
|
||||
when "086" # Acrobatics
|
||||
baseDmg *= 2 if user.item==0 || user.hasActiveItem?(:FLYINGGEM)
|
||||
when "08D" # Gyro Ball
|
||||
targetSpeed = pbRoughStat(target,PBStats::SPEED,skill)
|
||||
userSpeed = pbRoughStat(user,PBStats::SPEED,skill)
|
||||
baseDmg = [[(25*targetSpeed/userSpeed).floor,150].min,1].max
|
||||
when "094" # Present
|
||||
baseDmg = 50
|
||||
when "095" # Magnitude
|
||||
baseDmg = 71
|
||||
baseDmg *= 2 if target.inTwoTurnAttack?("0CA") # Dig
|
||||
when "096" # Natural Gift
|
||||
baseDmg = move.pbNaturalGiftBaseDamage(user.item)
|
||||
when "09B" # Heavy Slam
|
||||
baseDmg = move.pbBaseDamage(baseDmg,user,target)
|
||||
baseDmg *= 2 if NEWEST_BATTLE_MECHANICS && skill>=PBTrainerAI.mediumSkill &&
|
||||
target.effects[PBEffects::Minimize]
|
||||
when "0A0", "0BD", "0BE" # Frost Breath, Double Kick, Twineedle
|
||||
baseDmg *= 2
|
||||
when "0BF" # Triple Kick
|
||||
baseDmg *= 6 # Hits do x1, x2, x3 baseDmg in turn, for x6 in total
|
||||
when "0C0" # Fury Attack
|
||||
if user.hasActiveAbility?(:SKILLLINK)
|
||||
baseDmg *= 5
|
||||
else
|
||||
baseDmg = (baseDmg*19/6).floor # Average damage dealt
|
||||
end
|
||||
when "0C1" # Beat Up
|
||||
mult = 0
|
||||
@battle.eachInTeamFromBattlerIndex(user.index) do |pkmn,i|
|
||||
mult += 1 if pkmn && pkmn.able? && pkmn.status==PBStatuses::NONE
|
||||
end
|
||||
baseDmg *= mult
|
||||
when "0C4" # Solar Beam
|
||||
baseDmg = move.pbBaseDamageMultiplier(baseDmg,user,target)
|
||||
when "0D3" # Rollout
|
||||
baseDmg *= 2 if user.effects[PBEffects::DefenseCurl]
|
||||
when "0D4" # Bide
|
||||
baseDmg = 40
|
||||
when "0E1" # Final Gambit
|
||||
baseDmg = user.hp
|
||||
when "144" # Flying Press
|
||||
type = getConst(PBTypes,:FLYING) || -1
|
||||
if type>=0
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
targetTypes = target.pbTypes(true)
|
||||
mult = PBTypes.getCombinedEffectiveness(type,
|
||||
targetTypes[0],targetTypes[1],targetTypes[2])
|
||||
baseDmg = (baseDmg.to_f*mult/PBTypeEffectiveness::NORMAL_EFFECTIVE).round
|
||||
else
|
||||
mult = PBTypes.getCombinedEffectiveness(type,
|
||||
target.type1,target.type2,target.effects[PBEffects::Type3])
|
||||
baseDmg = (baseDmg.to_f*mult/PBTypeEffectiveness::NORMAL_EFFECTIVE).round
|
||||
end
|
||||
end
|
||||
baseDmg *= 2 if skill>=PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize]
|
||||
when "166" # Stomping Tantrum
|
||||
baseDmg *= 2 if user.lastRoundMoveFailed
|
||||
when "175" # Double Iron Bash
|
||||
baseDmg *= 2
|
||||
baseDmg *= 2 if skill>=PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize]
|
||||
end
|
||||
return baseDmg
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Damage calculation
|
||||
#=============================================================================
|
||||
def pbRoughDamage(move,user,target,skill,baseDmg)
|
||||
# Fixed damage moves
|
||||
return baseDmg if move.is_a?(PokeBattle_FixedDamageMove)
|
||||
# Get the move's type
|
||||
type = pbRoughType(move,user,skill)
|
||||
##### Calculate user's attack stat #####
|
||||
atk = pbRoughStat(user,PBStats::ATTACK,skill)
|
||||
if move.function=="121" # Foul Play
|
||||
atk = pbRoughStat(target,PBStats::ATTACK,skill)
|
||||
elsif move.specialMove?(type)
|
||||
if move.function=="121" # Foul Play
|
||||
atk = pbRoughStat(target,PBStats::SPATK,skill)
|
||||
else
|
||||
atk = pbRoughStat(user,PBStats::SPATK,skill)
|
||||
end
|
||||
end
|
||||
##### Calculate target's defense stat #####
|
||||
defense = pbRoughStat(target,PBStats::DEFENSE,skill)
|
||||
if move.specialMove?(type) && move.function!="122" # Psyshock
|
||||
defense = pbRoughStat(target,PBStats::SPDEF,skill)
|
||||
end
|
||||
##### Calculate all multiplier effects #####
|
||||
multipliers = [0x1000,0x1000,0x1000,0x1000]
|
||||
# Ability effects that alter damage
|
||||
moldBreaker = false
|
||||
if skill>=PBTrainerAI.highSkill && target.hasMoldBreaker?
|
||||
moldBreaker = true
|
||||
end
|
||||
if skill>=PBTrainerAI.mediumSkill && user.abilityActive?
|
||||
# NOTE: These abilities aren't suitable for checking at the start of the
|
||||
# round.
|
||||
abilityBlacklist = [:ANALYTIC,:SNIPER,:TINTEDLENS,:AERILATE,:PIXILATE,:REFRIGERATE]
|
||||
canCheck = true
|
||||
abilityBlacklist.each do |m|
|
||||
next if !isConst?(move.id,PBMoves,m)
|
||||
canCheck = false
|
||||
break
|
||||
end
|
||||
if canCheck
|
||||
BattleHandlers.triggerDamageCalcUserAbility(user.ability,
|
||||
user,target,move,multipliers,baseDmg,type)
|
||||
end
|
||||
end
|
||||
if skill>=PBTrainerAI.mediumSkill && !moldBreaker
|
||||
user.eachAlly do |b|
|
||||
next if !b.abilityActive?
|
||||
BattleHandlers.triggerDamageCalcUserAllyAbility(b.ability,
|
||||
user,target,move,multipliers,baseDmg,type)
|
||||
end
|
||||
end
|
||||
if skill>=PBTrainerAI.bestSkill && !moldBreaker && target.abilityActive?
|
||||
# NOTE: These abilities aren't suitable for checking at the start of the
|
||||
# round.
|
||||
abilityBlacklist = [:FILTER,:SOLIDROCK]
|
||||
canCheck = true
|
||||
abilityBlacklist.each do |m|
|
||||
next if !isConst?(move.id,PBMoves,m)
|
||||
canCheck = false
|
||||
break
|
||||
end
|
||||
if canCheck
|
||||
BattleHandlers.triggerDamageCalcTargetAbility(target.ability,
|
||||
user,target,move,multipliers,baseDmg,type)
|
||||
end
|
||||
end
|
||||
if skill>=PBTrainerAI.bestSkill && !moldBreaker
|
||||
target.eachAlly do |b|
|
||||
next if !b.abilityActive?
|
||||
BattleHandlers.triggerDamageCalcTargetAllyAbility(b.ability,
|
||||
user,target,move,multipliers,baseDmg,type)
|
||||
end
|
||||
end
|
||||
# Item effects that alter damage
|
||||
# NOTE: Type-boosting gems aren't suitable for checking at the start of the
|
||||
# round.
|
||||
if skill>=PBTrainerAI.mediumSkill && user.itemActive?
|
||||
# NOTE: These items aren't suitable for checking at the start of the
|
||||
# round.
|
||||
itemBlacklist = [:EXPERTBELT,:LIFEORB]
|
||||
canCheck = true
|
||||
itemBlacklist.each do |i|
|
||||
next if !isConst?(user.item,PBItems,i)
|
||||
canCheck = false
|
||||
break
|
||||
end
|
||||
if canCheck
|
||||
BattleHandlers.triggerDamageCalcUserItem(user.item,
|
||||
user,target,move,multipliers,baseDmg,type)
|
||||
end
|
||||
end
|
||||
if skill>=PBTrainerAI.bestSkill && target.itemActive?
|
||||
# NOTE: Type-weakening berries aren't suitable for checking at the start
|
||||
# of the round.
|
||||
if !pbIsBerry?(target.item)
|
||||
BattleHandlers.triggerDamageCalcTargetItem(target.item,
|
||||
user,target,move,multipliers,baseDmg,type)
|
||||
end
|
||||
end
|
||||
# Global abilities
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if (@battle.pbCheckGlobalAbility(:DARKAURA) && isConst?(type,PBTypes,:DARK)) ||
|
||||
(@battle.pbCheckGlobalAbility(:FAIRYAURA) && isConst?(type,PBTypes,:FAIRY))
|
||||
if @battle.pbCheckGlobalAbility(:AURABREAK)
|
||||
multipliers[BASE_DMG_MULT] *= 2/3
|
||||
else
|
||||
multipliers[BASE_DMG_MULT] *= 4/3
|
||||
end
|
||||
end
|
||||
end
|
||||
# Parental Bond
|
||||
if skill>=PBTrainerAI.mediumSkill && user.hasActiveAbility?(:PARENTALBOND)
|
||||
multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.25).floor
|
||||
end
|
||||
# Me First
|
||||
# TODO
|
||||
# Helping Hand - n/a
|
||||
# Charge
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if user.effects[PBEffects::Charge]>0 && isConst?(type,PBTypes,:ELECTRIC)
|
||||
multipliers[BASE_DMG_MULT] *= 2
|
||||
end
|
||||
end
|
||||
# Mud Sport and Water Sport
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if isConst?(type,PBTypes,:ELECTRIC)
|
||||
@battle.eachBattler do |b|
|
||||
next if !b.effects[PBEffects::MudSport]
|
||||
multipliers[BASE_DMG_MULT] /= 3
|
||||
break
|
||||
end
|
||||
if @battle.field.effects[PBEffects::MudSportField]>0
|
||||
multipliers[BASE_DMG_MULT] /= 3
|
||||
end
|
||||
end
|
||||
if isConst?(type,PBTypes,:FIRE)
|
||||
@battle.eachBattler do |b|
|
||||
next if !b.effects[PBEffects::WaterSport]
|
||||
multipliers[BASE_DMG_MULT] /= 3
|
||||
break
|
||||
end
|
||||
if @battle.field.effects[PBEffects::WaterSportField]>0
|
||||
multipliers[BASE_DMG_MULT] /= 3
|
||||
end
|
||||
end
|
||||
end
|
||||
# Terrain moves
|
||||
if user.affectedByTerrain? && skill>=PBTrainerAI.mediumSkill
|
||||
case @battle.field.terrain
|
||||
when PBBattleTerrains::Electric
|
||||
if isConst?(type,PBTypes,:ELECTRIC)
|
||||
multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round
|
||||
end
|
||||
when PBBattleTerrains::Grassy
|
||||
if isConst?(type,PBTypes,:GRASS)
|
||||
multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round
|
||||
end
|
||||
when PBBattleTerrains::Psychic
|
||||
if isConst?(type,PBTypes,:PSYCHIC)
|
||||
multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round
|
||||
end
|
||||
end
|
||||
end
|
||||
if target.affectedByTerrain? && skill>=PBTrainerAI.mediumSkill
|
||||
if @battle.field.terrain==PBBattleTerrains::Misty && isConst?(type,PBTypes,:DRAGON)
|
||||
multipliers[BASE_DMG_MULT] /= 2
|
||||
end
|
||||
end
|
||||
# Badge multipliers
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
if @battle.internalBattle
|
||||
# Don't need to check the Atk/Sp Atk-boosting badges because the AI
|
||||
# won't control the player's Pokémon.
|
||||
if target.pbOwnedByPlayer?
|
||||
if move.physicalMove?(type) && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_DEFENSE
|
||||
multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.1).round
|
||||
elsif move.specialMove?(type) && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_SPDEF
|
||||
multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.1).round
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# Multi-targeting attacks
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
if pbTargetsMultiple?(move,user)
|
||||
multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*0.75).round
|
||||
end
|
||||
end
|
||||
# Weather
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
case @battle.pbWeather
|
||||
when PBWeather::Sun, PBWeather::HarshSun
|
||||
if isConst?(type,PBTypes,:FIRE)
|
||||
multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round
|
||||
elsif isConst?(type,PBTypes,:WATER)
|
||||
multipliers[FINAL_DMG_MULT] /= 2
|
||||
end
|
||||
when PBWeather::Rain, PBWeather::HeavyRain
|
||||
if isConst?(type,PBTypes,:FIRE)
|
||||
multipliers[FINAL_DMG_MULT] /= 2
|
||||
elsif isConst?(type,PBTypes,:WATER)
|
||||
multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round
|
||||
end
|
||||
when PBWeather::Sandstorm
|
||||
if target.pbHasType?(:ROCK) && move.specialMove?(type) && move.function!="122" # Psyshock
|
||||
multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.5).round
|
||||
end
|
||||
end
|
||||
end
|
||||
# Critical hits - n/a
|
||||
# Random variance - n/a
|
||||
# STAB
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if type>=0 && user.pbHasType?(type)
|
||||
if user.hasActiveAbility?(:ADAPTABILITY)
|
||||
multipliers[FINAL_DMG_MULT] *= 2
|
||||
else
|
||||
multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round
|
||||
end
|
||||
end
|
||||
end
|
||||
# Type effectiveness
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
typemod = pbCalcTypeMod(type,user,target)
|
||||
multipliers[FINAL_DMG_MULT] *= typemod.to_f/PBTypeEffectiveness::NORMAL_EFFECTIVE
|
||||
multipliers[FINAL_DMG_MULT] = multipliers[FINAL_DMG_MULT].round
|
||||
end
|
||||
# Burn
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
if user.status==PBStatuses::BURN && move.physicalMove?(type) &&
|
||||
!user.hasActiveAbility?(:GUTS) &&
|
||||
!(NEWEST_BATTLE_MECHANICS && move.function=="07E") # Facade
|
||||
multipliers[FINAL_DMG_MULT] /= 2
|
||||
end
|
||||
end
|
||||
# Aurora Veil, Reflect, Light Screen
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
if !move.ignoresReflect? && !user.hasActiveAbility?(:INFILTRATOR)
|
||||
if target.pbOwnSide.effects[PBEffects::AuroraVeil]>0
|
||||
if @battle.pbSideBattlerCount(target)>1
|
||||
multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round
|
||||
else
|
||||
multipliers[FINAL_DMG_MULT] /= 2
|
||||
end
|
||||
elsif target.pbOwnSide.effects[PBEffects::Reflect]>0 && move.physicalMove?(type)
|
||||
if @battle.pbSideBattlerCount(target)>1
|
||||
multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round
|
||||
else
|
||||
multipliers[FINAL_DMG_MULT] /= 2
|
||||
end
|
||||
elsif target.pbOwnSide.effects[PBEffects::LightScreen]>0 && move.specialMove?(type)
|
||||
if @battle.pbSideBattlerCount(target)>1
|
||||
multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round
|
||||
else
|
||||
multipliers[FINAL_DMG_MULT] /= 2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# Minimize
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
if target.effects[PBEffects::Minimize] && move.tramplesMinimize?(2)
|
||||
multipliers[FINAL_DMG_MULT] *= 2
|
||||
end
|
||||
end
|
||||
# Move-specific base damage modifiers
|
||||
# TODO
|
||||
# Move-specific final damage modifiers
|
||||
# TODO
|
||||
##### Main damage calculation #####
|
||||
baseDmg = [(baseDmg * multipliers[BASE_DMG_MULT] / 0x1000).round,1].max
|
||||
atk = [(atk * multipliers[ATK_MULT] / 0x1000).round,1].max
|
||||
defense = [(defense * multipliers[DEF_MULT] / 0x1000).round,1].max
|
||||
damage = (((2.0*user.level/5+2).floor*baseDmg*atk/defense).floor/50).floor+2
|
||||
damage = [(damage * multipliers[FINAL_DMG_MULT] / 0x1000).round,1].max
|
||||
# "AI-specific calculations below"
|
||||
# Increased critical hit rates
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
c = 0
|
||||
# Ability effects that alter critical hit rate
|
||||
if c>=0 && user.abilityActive?
|
||||
c = BattleHandlers.triggerCriticalCalcUserAbility(user.ability,user,target,c)
|
||||
end
|
||||
if skill>=PBTrainerAI.bestSkill
|
||||
if c>=0 && !moldBreaker && target.abilityActive?
|
||||
c = BattleHandlers.triggerCriticalCalcTargetAbility(target.ability,user,target,c)
|
||||
end
|
||||
end
|
||||
# Item effects that alter critical hit rate
|
||||
if c>=0 && user.itemActive?
|
||||
c = BattleHandlers.triggerCriticalCalcUserItem(user.item,user,target,c)
|
||||
end
|
||||
if skill>=PBTrainerAI.bestSkill
|
||||
if c>=0 && target.itemActive?
|
||||
c = BattleHandlers.triggerCriticalCalcTargetItem(target.item,user,target,c)
|
||||
end
|
||||
end
|
||||
# Other efffects
|
||||
c = -1 if target.pbOwnSide.effects[PBEffects::LuckyChant]>0
|
||||
if c>=0
|
||||
c += 1 if move.highCriticalRate?
|
||||
c += user.effects[PBEffects::FocusEnergy]
|
||||
c += 1 if user.inHyperMode? && isConst?(move.type,PBTypes,:SHADOW)
|
||||
end
|
||||
if c>=0
|
||||
c = 4 if c>4
|
||||
damage += damage*0.1*c
|
||||
end
|
||||
end
|
||||
return damage.floor
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Accuracy calculation
|
||||
#=============================================================================
|
||||
def pbRoughAccuracy(move,user,target,skill)
|
||||
# "Always hit" effects and "always hit" accuracy
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
return 125 if target.effects[PBEffects::Minimize] && move.tramplesMinimize?(1)
|
||||
return 125 if target.effects[PBEffects::Telekinesis]>0
|
||||
end
|
||||
baseAcc = move.accuracy
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
baseAcc = move.pbBaseAccuracy(user,target)
|
||||
end
|
||||
return 125 if baseAcc==0 && skill>=PBTrainerAI.mediumSkill
|
||||
# Get the move's type
|
||||
type = pbRoughType(move,user,skill)
|
||||
# Calculate all modifier effects
|
||||
modifiers = []
|
||||
modifiers[BASE_ACC] = baseAcc
|
||||
modifiers[ACC_STAGE] = user.stages[PBStats::ACCURACY]
|
||||
modifiers[EVA_STAGE] = target.stages[PBStats::EVASION]
|
||||
modifiers[ACC_MULT] = 0x1000
|
||||
modifiers[EVA_MULT] = 0x1000
|
||||
pbCalcAccuracyModifiers(user,target,modifiers,move,type,skill)
|
||||
# Check if move can't miss
|
||||
return 125 if modifiers[BASE_ACC]==0
|
||||
# Calculation
|
||||
accStage = [[modifiers[ACC_STAGE],-6].max,6].min + 6
|
||||
evaStage = [[modifiers[EVA_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[ACC_MULT] / 0x1000).round
|
||||
evasion = (evasion * modifiers[EVA_MULT] / 0x1000).round
|
||||
evasion = 1 if evasion<1
|
||||
return modifiers[BASE_ACC] * accuracy / evasion
|
||||
end
|
||||
|
||||
def pbCalcAccuracyModifiers(user,target,modifiers,move,type,skill)
|
||||
moldBreaker = false
|
||||
if skill>=PBTrainerAI.highSkill && target.hasMoldBreaker?
|
||||
moldBreaker = true
|
||||
end
|
||||
# Ability effects that alter accuracy calculation
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if user.abilityActive?
|
||||
BattleHandlers.triggerAccuracyCalcUserAbility(user.ability,
|
||||
modifiers,user,target,move,type)
|
||||
end
|
||||
user.eachAlly do |b|
|
||||
next if !b.abilityActive?
|
||||
BattleHandlers.triggerAccuracyCalcUserAllyAbility(b.ability,
|
||||
modifiers,user,target,move,type)
|
||||
end
|
||||
end
|
||||
if skill>=PBTrainerAI.bestSkill
|
||||
if target.abilityActive? && !moldBreaker
|
||||
BattleHandlers.triggerAccuracyCalcTargetAbility(target.ability,
|
||||
modifiers,user,target,move,type)
|
||||
end
|
||||
end
|
||||
# Item effects that alter accuracy calculation
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if user.itemActive?
|
||||
BattleHandlers.triggerAccuracyCalcUserItem(user.item,
|
||||
modifiers,user,target,move,type)
|
||||
end
|
||||
end
|
||||
if skill>=PBTrainerAI.bestSkill
|
||||
if target.itemActive?
|
||||
BattleHandlers.triggerAccuracyCalcTargetItem(target.item,
|
||||
modifiers,user,target,move,type)
|
||||
end
|
||||
end
|
||||
# Other effects, inc. ones that set ACC_MULT or EVA_STAGE to specific values
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if @battle.field.effects[PBEffects::Gravity]>0
|
||||
modifiers[ACC_MULT] = (modifiers[ACC_MULT]*5/3).round
|
||||
end
|
||||
if user.effects[PBEffects::MicleBerry]
|
||||
modifiers[ACC_MULT] = (modifiers[ACC_MULT]*1.2).round
|
||||
end
|
||||
modifiers[EVA_STAGE] = 0 if target.effects[PBEffects::Foresight] && modifiers[EVA_STAGE]>0
|
||||
modifiers[EVA_STAGE] = 0 if target.effects[PBEffects::MiracleEye] && modifiers[EVA_STAGE]>0
|
||||
end
|
||||
# "AI-specific calculations below"
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
modifiers[EVA_STAGE] = 0 if move.function=="0A9" # Chip Away
|
||||
modifiers[BASE_ACC] = 0 if ["0A5","139","13A","13B","13C", # "Always hit"
|
||||
"147"].include?(move.function)
|
||||
modifiers[BASE_ACC] = 0 if user.effects[PBEffects::LockOn]>0 &&
|
||||
user.effects[PBEffects::LockOnPos]==target.index
|
||||
end
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
if move.function=="006" # Toxic
|
||||
modifiers[BASE_ACC] = 0 if NEWEST_BATTLE_MECHANICS && move.statusMove? &&
|
||||
user.pbHasType?(:POISON)
|
||||
end
|
||||
if move.function=="070" # OHKO moves
|
||||
modifiers[BASE_ACC] = move.accuracy+user.level-target.level
|
||||
modifiers[ACC_MULT] = 0 if target.level>user.level
|
||||
if skill>=PBTrainerAI.bestSkill
|
||||
modifiers[ACC_MULT] = 0 if target.hasActiveAbility?(:STURDY)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user