Remove Scripts folder to convert to submodule

This commit is contained in:
chardub
2025-04-19 15:43:57 -04:00
parent 0807a7ea79
commit 58da1023c1
429 changed files with 0 additions and 165507 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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