mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-09 14:14:59 +00:00
Remove Scripts folder to convert to submodule
This commit is contained in:
@@ -1,69 +0,0 @@
|
||||
# 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
|
||||
@@ -1,171 +0,0 @@
|
||||
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
|
||||
# Determine target of item (always the Pokémon choosing the action)
|
||||
useType = GameData::Item.get(item).battle_use
|
||||
if [1, 2, 3, 6, 7, 8].include?(useType) # 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 #{GameData::Item.get(item).name}")
|
||||
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 nil if !@battle.internalBattle
|
||||
items = @battle.pbGetOwnerItems(idxBattler)
|
||||
return nil 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 !Settings::RAGE_CANDY_BAR_CURES_STATUS_PROBLEMS
|
||||
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 Settings::RAGE_CANDY_BAR_CURES_STATUS_PROBLEMS
|
||||
xItems = {
|
||||
:XATTACK => [:ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
|
||||
:XATTACK2 => [:ATTACK, 2],
|
||||
:XATTACK3 => [:ATTACK, 3],
|
||||
:XATTACK6 => [:ATTACK, 6],
|
||||
:XDEFENSE => [:DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
|
||||
:XDEFENSE2 => [:DEFENSE, 2],
|
||||
:XDEFENSE3 => [:DEFENSE, 3],
|
||||
:XDEFENSE6 => [:DEFENSE, 6],
|
||||
:XDEFEND => [:DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
|
||||
:XDEFEND2 => [:DEFENSE, 2],
|
||||
:XDEFEND3 => [:DEFENSE, 3],
|
||||
:XDEFEND6 => [:DEFENSE, 6],
|
||||
:XSPATK => [:SPECIAL_ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
|
||||
:XSPATK2 => [:SPECIAL_ATTACK, 2],
|
||||
:XSPATK3 => [:SPECIAL_ATTACK, 3],
|
||||
:XSPATK6 => [:SPECIAL_ATTACK, 6],
|
||||
:XSPECIAL => [:SPECIAL_ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
|
||||
:XSPECIAL2 => [:SPECIAL_ATTACK, 2],
|
||||
:XSPECIAL3 => [:SPECIAL_ATTACK, 3],
|
||||
:XSPECIAL6 => [:SPECIAL_ATTACK, 6],
|
||||
:XSPDEF => [:SPECIAL_DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
|
||||
:XSPDEF2 => [:SPECIAL_DEFENSE, 2],
|
||||
:XSPDEF3 => [:SPECIAL_DEFENSE, 3],
|
||||
:XSPDEF6 => [:SPECIAL_DEFENSE, 6],
|
||||
:XSPEED => [:SPEED, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
|
||||
:XSPEED2 => [:SPEED, 2],
|
||||
:XSPEED3 => [:SPEED, 3],
|
||||
:XSPEED6 => [:SPEED, 6],
|
||||
:XACCURACY => [:ACCURACY, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
|
||||
:XACCURACY2 => [:ACCURACY, 2],
|
||||
:XACCURACY3 => [:ACCURACY, 3],
|
||||
:XACCURACY6 => [:ACCURACY, 6]
|
||||
}
|
||||
losthp = battler.totalhp - battler.hp
|
||||
preferFullRestore = (battler.hp <= battler.totalhp * 2 / 3 &&
|
||||
(battler.status != :NONE || battler.effects[PBEffects::Confusion] > 0))
|
||||
# Find all usable items
|
||||
usableHPItems = []
|
||||
usableStatusItems = []
|
||||
usableXItems = []
|
||||
items.each do |i|
|
||||
next if !i
|
||||
next if !@battle.pbCanUseItemOnPokemon?(i,pkmn,battler,@battle.scene,false)
|
||||
next if !ItemHandlers.triggerCanUseInBattle(i,pkmn,battler,nil,
|
||||
false,self,@battle.scene,false)
|
||||
# Log HP healing items
|
||||
if losthp > 0
|
||||
power = hpItems[i]
|
||||
if power
|
||||
usableHPItems.push([i, 5, power])
|
||||
next
|
||||
end
|
||||
end
|
||||
# Log Full Restores (HP healer and status curer)
|
||||
if losthp > 0 || battler.status != :NONE
|
||||
if fullRestoreItems.include?(i)
|
||||
usableHPItems.push([i, (preferFullRestore) ? 3 : 7, 999])
|
||||
usableStatusItems.push([i, (preferFullRestore) ? 3 : 9])
|
||||
next
|
||||
end
|
||||
end
|
||||
# Log single status-curing items
|
||||
if oneStatusItems.include?(i)
|
||||
usableStatusItems.push([i, 5])
|
||||
next
|
||||
end
|
||||
# Log Full Heal-type items
|
||||
if allStatusItems.include?(i)
|
||||
usableStatusItems.push([i, 7])
|
||||
next
|
||||
end
|
||||
# Log stat-raising items
|
||||
if xItems[i]
|
||||
data = xItems[i]
|
||||
usableXItems.push([i, battler.stages[data[0]], data[1]])
|
||||
next
|
||||
end
|
||||
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 nil
|
||||
end
|
||||
end
|
||||
@@ -1,178 +0,0 @@
|
||||
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 = nil
|
||||
skill = @battle.pbGetOwnerFromBattlerIndex(idxBattler).skill_level || 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 &&
|
||||
(target.level-battler.level).abs<=6
|
||||
moveData = GameData::Move.get(target.lastMoveUsed)
|
||||
moveType = moveData.type
|
||||
typeMod = pbCalcTypeMod(moveType,target,battler)
|
||||
if Effectiveness.super_effective?(typeMod) && moveData.base_damage > 50
|
||||
switchChance = (moveData.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 == :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 && Effectiveness.ineffective?(pbCalcTypeMod(moveType,battler,battler))
|
||||
weight = 65
|
||||
typeMod = pbCalcTypeModPokemon(pkmn,battler.pbDirectOpposing(true))
|
||||
if Effectiveness.super_effective?(typeMod)
|
||||
# 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 && Effectiveness.resistant?(pbCalcTypeMod(moveType,battler,battler))
|
||||
weight = 40
|
||||
typeMod = pbCalcTypeModPokemon(pkmn,battler.pbDirectOpposing(true))
|
||||
if Effectiveness.super_effective?(typeMod)
|
||||
# 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 true
|
||||
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
|
||||
enemies.each do |i|
|
||||
pkmn = party[i]
|
||||
sum = 0
|
||||
pkmn.moves.each do |m|
|
||||
next if m.base_damage == 0
|
||||
@battle.battlers[idxBattler].eachOpposing do |b|
|
||||
bTypes = b.pbTypes(true)
|
||||
sum += Effectiveness.calculate(m.type, bTypes[0], bTypes[1], bTypes[2])
|
||||
end
|
||||
end
|
||||
if best==-1 || sum>bestSum
|
||||
best = i
|
||||
bestSum = sum
|
||||
end
|
||||
end
|
||||
return best
|
||||
end
|
||||
end
|
||||
@@ -1,291 +0,0 @@
|
||||
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_level || 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
|
||||
# If there are no calculated choices, pick one at random
|
||||
if choices.length==0
|
||||
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) doesn't want to use any moves; picking one at random")
|
||||
user.eachMoveWithIndex do |_m,i|
|
||||
next if !@battle.pbCanChooseMove?(idxBattler,i,false)
|
||||
choices.push([i,100,-1]) # Move index, score, target
|
||||
end
|
||||
if choices.length==0 # No moves are physically possible to use; use Struggle
|
||||
@battle.pbAutoChooseMove(user.index)
|
||||
end
|
||||
end
|
||||
# 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
|
||||
# Log the result
|
||||
if @battle.choices[idxBattler][2]
|
||||
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will use #{@battle.choices[idxBattler][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]
|
||||
target_data = move.pbTarget(user)
|
||||
if target_data.num_targets > 1
|
||||
# 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,target_data)
|
||||
score = pbGetMoveScore(move,user,b,skill)
|
||||
totalScore += ((user.opposes?(b)) ? score : -score)
|
||||
end
|
||||
choices.push([idxMove,totalScore,-1]) if totalScore>0
|
||||
elsif target_data.num_targets == 0
|
||||
# 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,target_data)
|
||||
next if target_data.targets_foe && !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,:SPEED,skill)>pbRoughStat(target,: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 == :SLEEP && !move.usableWhenAsleep?
|
||||
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 == :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 == :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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,669 +0,0 @@
|
||||
class PokeBattle_AI
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
def pbTargetsMultiple?(move,user)
|
||||
target_data = move.pbTarget(user)
|
||||
return false if target_data.num_targets <= 1
|
||||
num_targets = 0
|
||||
case target_data.id
|
||||
when :UserAndAllies
|
||||
@battle.eachSameSideBattler(user) { |_b| num_targets += 1 }
|
||||
when :AllNearFoes
|
||||
@battle.eachOtherSideBattler(user) { |b| num_targets += 1 if b.near?(user) }
|
||||
when :AllFoes
|
||||
@battle.eachOtherSideBattler(user) { |_b| num_targets += 1 }
|
||||
when :AllNearOthers
|
||||
@battle.eachBattler { |b| num_targets += 1 if b.near?(user) }
|
||||
when :AllBattlers
|
||||
@battle.eachBattler { |_b| num_targets += 1 }
|
||||
end
|
||||
return num_targets > 1
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# Move's type effectiveness
|
||||
#=============================================================================
|
||||
def pbCalcTypeModSingle(moveType,defType,user,target)
|
||||
ret = Effectiveness.calculate_one(moveType,defType)
|
||||
# Ring Target
|
||||
if target.hasActiveItem?(:RINGTARGET)
|
||||
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if Effectiveness.ineffective_type?(moveType, defType)
|
||||
end
|
||||
# Foresight
|
||||
if user.hasActiveAbility?(:SCRAPPY) || target.effects[PBEffects::Foresight]
|
||||
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if defType == :GHOST &&
|
||||
Effectiveness.ineffective_type?(moveType, defType)
|
||||
end
|
||||
# Miracle Eye
|
||||
if target.effects[PBEffects::MiracleEye]
|
||||
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if defType == :DARK &&
|
||||
Effectiveness.ineffective_type?(moveType, defType)
|
||||
end
|
||||
# Delta Stream's weather
|
||||
if @battle.pbWeather == :StrongWinds
|
||||
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if defType == :FLYING &&
|
||||
Effectiveness.super_effective_type?(moveType, defType)
|
||||
end
|
||||
# Grounded Flying-type Pokémon become susceptible to Ground moves
|
||||
if !target.airborne?
|
||||
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if defType == :FLYING && moveType == :GROUND
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
def pbCalcTypeMod(moveType,user,target)
|
||||
return Effectiveness::NORMAL_EFFECTIVE if !moveType
|
||||
return Effectiveness::NORMAL_EFFECTIVE if moveType == :GROUND &&
|
||||
target.pbHasType?(:FLYING) && target.hasActiveItem?(:IRONBALL)
|
||||
# Determine types
|
||||
tTypes = target.pbTypes(true)
|
||||
# Get effectivenesses
|
||||
typeMods = [Effectiveness::NORMAL_EFFECTIVE_ONE] * 3 # 3 types max
|
||||
if moveType == :SHADOW
|
||||
if target.shadowPokemon?
|
||||
typeMods[0] = Effectiveness::NOT_VERY_EFFECTIVE_ONE
|
||||
else
|
||||
typeMods[0] = Effectiveness::SUPER_EFFECTIVE_ONE
|
||||
end
|
||||
else
|
||||
tTypes.each_with_index do |type,i|
|
||||
typeMods[i] = pbCalcTypeModSingle(moveType,type,user,target)
|
||||
end
|
||||
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,target)
|
||||
mod1 = Effectiveness.calculate(battlerThis.type1,target.type1,target.type2)
|
||||
mod2 = Effectiveness::NORMAL_EFFECTIVE
|
||||
if battlerThis.type1!=battlerThis.type2
|
||||
mod2 = Effectiveness.calculate(battlerThis.type2,target.type1,target.type2)
|
||||
mod2 = mod2.to_f / Effectiveness::NORMAL_EFFECTIVE
|
||||
end
|
||||
return mod1*mod2
|
||||
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 Effectiveness.ineffective?(typeMod) || score<=0
|
||||
# Immunity due to ability/item/other effects
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
case type
|
||||
when :GROUND
|
||||
return true if target.airborne? && !move.hitsFlyingTargets?
|
||||
when :FIRE
|
||||
return true if target.hasActiveAbility?(:FLASHFIRE)
|
||||
when :WATER
|
||||
return true if target.hasActiveAbility?([:DRYSKIN,:STORMDRAIN,:WATERABSORB])
|
||||
when :GRASS
|
||||
return true if target.hasActiveAbility?(:SAPSIPPER)
|
||||
when :ELECTRIC
|
||||
return true if target.hasActiveAbility?([:LIGHTNINGROD,:MOTORDRIVE,:VOLTABSORB])
|
||||
end
|
||||
return true if Effectiveness.not_very_effective?(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 Settings::MECHANICS_GENERATION >= 7 && user.hasActiveAbility?(:PRANKSTER) &&
|
||||
target.pbHasType?(:DARK) && target.opposes?(user)
|
||||
return true if move.priority>0 && @battle.field.terrain == :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==: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 :ATTACK then value = battler.attack
|
||||
when :DEFENSE then value = battler.defense
|
||||
when :SPECIAL_ATTACK then value = battler.spatk
|
||||
when :SPECIAL_DEFENSE then value = battler.spdef
|
||||
when :SPEED then value = battler.speed
|
||||
end
|
||||
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 || user.hasActiveItem?(:FLYINGGEM)
|
||||
when "08D" # Gyro Ball
|
||||
targetSpeed = pbRoughStat(target,:SPEED,skill)
|
||||
userSpeed = pbRoughStat(user,: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_id)
|
||||
when "09B" # Heavy Slam
|
||||
baseDmg = move.pbBaseDamage(baseDmg,user,target)
|
||||
baseDmg *= 2 if Settings::MECHANICS_GENERATION >= 7 && 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 == :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
|
||||
if GameData::Type.exists?(:FLYING)
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
targetTypes = target.pbTypes(true)
|
||||
mult = Effectiveness.calculate(:FLYING,
|
||||
targetTypes[0],targetTypes[1],targetTypes[2])
|
||||
baseDmg = (baseDmg.to_f*mult/Effectiveness::NORMAL_EFFECTIVE).round
|
||||
else
|
||||
mult = Effectiveness.calculate(:FLYING,
|
||||
target.type1,target.type2,target.effects[PBEffects::Type3])
|
||||
baseDmg = (baseDmg.to_f*mult/Effectiveness::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,:ATTACK,skill)
|
||||
if move.function=="121" # Foul Play
|
||||
atk = pbRoughStat(target,:ATTACK,skill)
|
||||
elsif move.specialMove?(type)
|
||||
if move.function=="121" # Foul Play
|
||||
atk = pbRoughStat(target,:SPECIAL_ATTACK,skill)
|
||||
else
|
||||
atk = pbRoughStat(user,:SPECIAL_ATTACK,skill)
|
||||
end
|
||||
end
|
||||
##### Calculate target's defense stat #####
|
||||
defense = pbRoughStat(target,:DEFENSE,skill)
|
||||
if move.specialMove?(type) && move.function!="122" # Psyshock
|
||||
defense = pbRoughStat(target,:SPECIAL_DEFENSE,skill)
|
||||
end
|
||||
##### Calculate all multiplier effects #####
|
||||
multipliers = {
|
||||
:base_damage_multiplier => 1.0,
|
||||
:attack_multiplier => 1.0,
|
||||
:defense_multiplier => 1.0,
|
||||
:final_damage_multiplier => 1.0
|
||||
}
|
||||
# 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 move.id != 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 move.id != 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]
|
||||
if !itemBlacklist.include?(user.item_id)
|
||||
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 target.item && !target.item.is_berry?
|
||||
BattleHandlers.triggerDamageCalcTargetItem(target.item,
|
||||
user,target,move,multipliers,baseDmg,type)
|
||||
end
|
||||
end
|
||||
# Global abilities
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if (@battle.pbCheckGlobalAbility(:DARKAURA) && type == :DARK) ||
|
||||
(@battle.pbCheckGlobalAbility(:FAIRYAURA) && type == :FAIRY)
|
||||
if @battle.pbCheckGlobalAbility(:AURABREAK)
|
||||
multipliers[:base_damage_multiplier] *= 2 / 3.0
|
||||
else
|
||||
multipliers[:base_damage_multiplier] *= 4 / 3.0
|
||||
end
|
||||
end
|
||||
end
|
||||
# Parental Bond
|
||||
if skill>=PBTrainerAI.mediumSkill && user.hasActiveAbility?(:PARENTALBOND)
|
||||
multipliers[:base_damage_multiplier] *= 1.25
|
||||
end
|
||||
# Me First
|
||||
# TODO
|
||||
# Helping Hand - n/a
|
||||
# Charge
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if user.effects[PBEffects::Charge]>0 && type == :ELECTRIC
|
||||
multipliers[:base_damage_multiplier] *= 2
|
||||
end
|
||||
end
|
||||
# Mud Sport and Water Sport
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if type == :ELECTRIC
|
||||
@battle.eachBattler do |b|
|
||||
next if !b.effects[PBEffects::MudSport]
|
||||
multipliers[:base_damage_multiplier] /= 3
|
||||
break
|
||||
end
|
||||
if @battle.field.effects[PBEffects::MudSportField]>0
|
||||
multipliers[:base_damage_multiplier] /= 3
|
||||
end
|
||||
end
|
||||
if type == :FIRE
|
||||
@battle.eachBattler do |b|
|
||||
next if !b.effects[PBEffects::WaterSport]
|
||||
multipliers[:base_damage_multiplier] /= 3
|
||||
break
|
||||
end
|
||||
if @battle.field.effects[PBEffects::WaterSportField]>0
|
||||
multipliers[:base_damage_multiplier] /= 3
|
||||
end
|
||||
end
|
||||
end
|
||||
# Terrain moves
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
case @battle.field.terrain
|
||||
when :Electric
|
||||
multipliers[:base_damage_multiplier] *= 1.5 if type == :ELECTRIC && user.affectedByTerrain?
|
||||
when :Grassy
|
||||
multipliers[:base_damage_multiplier] *= 1.5 if type == :GRASS && user.affectedByTerrain?
|
||||
when :Psychic
|
||||
multipliers[:base_damage_multiplier] *= 1.5 if type == :PSYCHIC && user.affectedByTerrain?
|
||||
when :Misty
|
||||
multipliers[:base_damage_multiplier] /= 2 if type == :DRAGON && target.affectedByTerrain?
|
||||
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.badge_count >= Settings::NUM_BADGES_BOOST_DEFENSE
|
||||
multipliers[:defense_multiplier] *= 1.1
|
||||
elsif move.specialMove?(type) && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPDEF
|
||||
multipliers[:defense_multiplier] *= 1.1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# Multi-targeting attacks
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
if pbTargetsMultiple?(move,user)
|
||||
multipliers[:final_damage_multiplier] *= 0.75
|
||||
end
|
||||
end
|
||||
# Weather
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
case @battle.pbWeather
|
||||
when :Sun, :HarshSun
|
||||
if type == :FIRE
|
||||
multipliers[:final_damage_multiplier] *= 1.5
|
||||
elsif type == :WATER
|
||||
multipliers[:final_damage_multiplier] /= 2
|
||||
end
|
||||
when :Rain, :HeavyRain
|
||||
if type == :FIRE
|
||||
multipliers[:final_damage_multiplier] /= 2
|
||||
elsif type == :WATER
|
||||
multipliers[:final_damage_multiplier] *= 1.5
|
||||
end
|
||||
when :Sandstorm
|
||||
if target.pbHasType?(:ROCK) && move.specialMove?(type) && move.function != "122" # Psyshock
|
||||
multipliers[:defense_multiplier] *= 1.5
|
||||
end
|
||||
end
|
||||
end
|
||||
# Critical hits - n/a
|
||||
# Random variance - n/a
|
||||
# STAB
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if type && user.pbHasType?(type)
|
||||
if user.hasActiveAbility?(:ADAPTABILITY)
|
||||
multipliers[:final_damage_multiplier] *= 2
|
||||
else
|
||||
multipliers[:final_damage_multiplier] *= 1.5
|
||||
end
|
||||
end
|
||||
end
|
||||
# Type effectiveness
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
typemod = pbCalcTypeMod(type,user,target)
|
||||
multipliers[:final_damage_multiplier] *= typemod.to_f / Effectiveness::NORMAL_EFFECTIVE
|
||||
end
|
||||
# Burn
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
if user.status == :BURN && move.physicalMove?(type) &&
|
||||
!user.hasActiveAbility?(:GUTS) &&
|
||||
!(Settings::MECHANICS_GENERATION >= 6 && move.function == "07E") # Facade
|
||||
multipliers[:final_damage_multiplier] /= 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_damage_multiplier] *= 2 / 3.0
|
||||
else
|
||||
multipliers[:final_damage_multiplier] /= 2
|
||||
end
|
||||
elsif target.pbOwnSide.effects[PBEffects::Reflect] > 0 && move.physicalMove?(type)
|
||||
if @battle.pbSideBattlerCount(target) > 1
|
||||
multipliers[:final_damage_multiplier] *= 2 / 3.0
|
||||
else
|
||||
multipliers[:final_damage_multiplier] /= 2
|
||||
end
|
||||
elsif target.pbOwnSide.effects[PBEffects::LightScreen] > 0 && move.specialMove?(type)
|
||||
if @battle.pbSideBattlerCount(target) > 1
|
||||
multipliers[:final_damage_multiplier] *= 2 / 3.0
|
||||
else
|
||||
multipliers[:final_damage_multiplier] /= 2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# Minimize
|
||||
if skill>=PBTrainerAI.highSkill
|
||||
if target.effects[PBEffects::Minimize] && move.tramplesMinimize?(2)
|
||||
multipliers[:final_damage_multiplier] *= 2
|
||||
end
|
||||
end
|
||||
# Move-specific base damage modifiers
|
||||
# TODO
|
||||
# Move-specific final damage modifiers
|
||||
# TODO
|
||||
##### Main damage calculation #####
|
||||
baseDmg = [(baseDmg * multipliers[:base_damage_multiplier]).round, 1].max
|
||||
atk = [(atk * multipliers[:attack_multiplier]).round, 1].max
|
||||
defense = [(defense * multipliers[:defense_multiplier]).round, 1].max
|
||||
damage = (((2.0 * user.level / 5 + 2).floor * baseDmg * atk / defense).floor / 50).floor + 2
|
||||
damage = [(damage * multipliers[:final_damage_multiplier]).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? && move.type == :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_accuracy] = baseAcc
|
||||
modifiers[:accuracy_stage] = user.stages[:ACCURACY]
|
||||
modifiers[:evasion_stage] = target.stages[:EVASION]
|
||||
modifiers[:accuracy_multiplier] = 1.0
|
||||
modifiers[:evasion_multiplier] = 1.0
|
||||
pbCalcAccuracyModifiers(user,target,modifiers,move,type,skill)
|
||||
# Check if move can't miss
|
||||
return 125 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 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 accuracy_multiplier or evasion_stage to specific values
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
if @battle.field.effects[PBEffects::Gravity] > 0
|
||||
modifiers[:accuracy_multiplier] *= 5/3.0
|
||||
end
|
||||
if user.effects[PBEffects::MicleBerry]
|
||||
modifiers[:accuracy_multiplier] *= 1.2
|
||||
end
|
||||
modifiers[:evasion_stage] = 0 if target.effects[PBEffects::Foresight] && modifiers[:evasion_stage] > 0
|
||||
modifiers[:evasion_stage] = 0 if target.effects[PBEffects::MiracleEye] && modifiers[:evasion_stage] > 0
|
||||
end
|
||||
# "AI-specific calculations below"
|
||||
if skill>=PBTrainerAI.mediumSkill
|
||||
modifiers[:evasion_stage] = 0 if move.function == "0A9" # Chip Away
|
||||
modifiers[:base_accuracy] = 0 if ["0A5", "139", "13A", "13B", "13C", # "Always hit"
|
||||
"147"].include?(move.function)
|
||||
modifiers[:base_accuracy] = 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_accuracy] = 0 if Settings::MORE_TYPE_EFFECTS && move.statusMove? &&
|
||||
user.pbHasType?(:POISON)
|
||||
end
|
||||
if move.function=="070" # OHKO moves
|
||||
modifiers[:base_accuracy] = move.accuracy + user.level - target.level
|
||||
modifiers[:accuracy_multiplier] = 0 if target.level > user.level
|
||||
if skill>=PBTrainerAI.bestSkill
|
||||
modifiers[:accuracy_multiplier] = 0 if target.hasActiveAbility?(:STURDY)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user