Rearranged OrgBattle scripts

This commit is contained in:
Maruno17
2021-05-07 18:23:20 +01:00
parent 654be6c1de
commit 23996a4781
18 changed files with 3729 additions and 3823 deletions

View File

@@ -0,0 +1,344 @@
#===============================================================================
#
#===============================================================================
class BaseStatRestriction
def initialize(mn, mx)
@mn = mn
@mx = mx
end
def isValid?(pkmn)
bst = baseStatTotal(pkmn.species)
return bst >= @mn && bst <= @mx
end
end
#===============================================================================
#
#===============================================================================
class NonlegendaryRestriction
def isValid?(pkmn)
return true if !pkmn.genderless?
return false if pkmn.species_data.egg_groups.include?(:Undiscovered)
return true
end
end
#===============================================================================
#
#===============================================================================
class InverseRestriction
def initialize(r)
@r = r
end
def isValid?(pkmn)
return !@r.isValid?(pkmn)
end
end
#===============================================================================
#
#===============================================================================
=begin
[3/10]
0-266 - 0-500
[106]
267-372 - 380-500
[95]
373-467 - 400-555 (nonlegendary)
468-563 - 400-555 (nonlegendary)
564-659 - 400-555 (nonlegendary)
660-755 - 400-555 (nonlegendary)
756-799 - 580-600 [legendary] (compat1==15 or compat2==15, genderbyte=255)
800-849 - 500-
850-881 - 580-
=end
def withRestr(_rule, minbs, maxbs, legendary)
ret = PokemonChallengeRules.new.addPokemonRule(BaseStatRestriction.new(minbs, maxbs))
if legendary == 0
ret.addPokemonRule(NonlegendaryRestriction.new)
elsif legendary == 1
ret.addPokemonRule(InverseRestriction.new(NonlegendaryRestriction.new))
end
return ret
end
def pbArrangeByTier(pokemonlist, rule)
tiers = [
withRestr(rule, 0, 500, 0),
withRestr(rule, 380, 500, 0),
withRestr(rule, 400, 555, 0),
withRestr(rule, 400, 555, 0),
withRestr(rule, 400, 555, 0),
withRestr(rule, 400, 555, 0),
withRestr(rule, 580, 680, 1),
withRestr(rule, 500, 680, 0),
withRestr(rule, 580, 680, 2)
]
tierPokemon = []
tiers.length.times do
tierPokemon.push([])
end
# Sort each Pokémon into tiers. Which tier a Pokémon is put in deoends on the
# Pokémon's position within pokemonlist (later = higher tier). pokemonlist is
# already roughly arranged by rank from weakest to strongest.
for i in 0...pokemonlist.length
next if !rule.ruleset.isPokemonValid?(pokemonlist[i])
validtiers = []
for j in 0...tiers.length
validtiers.push(j) if tiers[j].ruleset.isPokemonValid?(pokemonlist[i])
end
if validtiers.length > 0
vt = validtiers.length * i / pokemonlist.length
tierPokemon[validtiers[vt]].push(pokemonlist[i])
end
end
# Now for each tier, sort the Pokemon in that tier by their BST (lowest first).
ret = []
for i in 0...tiers.length
tierPokemon[i].sort! { |a, b|
bstA = baseStatTotal(a.species)
bstB = baseStatTotal(b.species)
(bstA == bstB) ? a.species <=> b.species : bstA <=> bstB
}
ret.concat(tierPokemon[i])
end
return ret
end
#===============================================================================
#
#===============================================================================
def pbReplenishBattlePokemon(party, rule)
while party.length < 20
pkmn = pbRandomPokemonFromRule(rule, nil)
found = false
for pk in party
next if !isBattlePokemonDuplicate(pkmn, pk)
found = true
break
end
party.push(pkmn) if !found
end
end
def isBattlePokemonDuplicate(pk, pk2)
return false if pk.species != pk2.species
moves1 = []
moves2 = []
for i in 0...Pokemon::MAX_MOVES
moves1.push((pk.moves[i]) ? pk.moves[i].id : nil)
moves2.push((pk2.moves[i]) ? pk2.moves[i].id : nil)
end
moves1.sort!
moves2.sort!
# Accept as same if moves are same and there are four moves each
return true if moves1 == moves2 && moves1[Pokemon::MAX_MOVES - 1]
same_evs = true
GameData::Stat.each_main { |s| same_evs = false if pk.ev[s.id] != pk2.ev[s.id] }
return pk.item_id == pk2.item_id && pk.nature_id == pk2.nature_id && same_evs
end
def pbRemoveDuplicates(party)
ret = []
for pk in party
found = false
count = 0
firstIndex = -1
for i in 0...ret.length
pk2 = ret[i]
if isBattlePokemonDuplicate(pk, pk2)
found = true
break
end
if pk.species == pk2.species
firstIndex = i if count == 0
count += 1
end
end
if !found
ret.delete_at(firstIndex) if count >= 10
ret.push(pk)
end
end
return ret
end
#===============================================================================
#
#===============================================================================
def pbGenerateChallenge(rule, tag)
oldrule = rule
yield(_INTL("Preparing to generate teams"))
rule = rule.copy.setNumber(2)
yield(nil)
party = load_data(tag + ".rxdata") rescue []
teams = load_data(tag + "teams.rxdata") rescue []
if teams.length < 10
btpokemon = pbGetBTPokemon(tag)
if btpokemon && btpokemon.length != 0
suggestedLevel = rule.ruleset.suggestedLevel
for pk in btpokemon
pkmn = pk.createPokemon(suggestedLevel, 31, nil)
party.push(pkmn) if rule.ruleset.isPokemonValid?(pkmn)
end
end
end
yield(nil)
party = pbRemoveDuplicates(party)
yield(nil)
maxteams = 600
cutoffrating = 65
toolowrating = 40
iterations = 11
iterations.times do |iter|
save_data(party, tag + ".rxdata")
yield(_INTL("Generating teams ({1} of {2})", iter + 1, iterations))
i = 0
while i < teams.length
yield(nil) if i % 10 == 0
pbReplenishBattlePokemon(party, rule)
if teams[i].rating < cutoffrating && teams[i].totalGames >= 80
teams[i] = RuledTeam.new(party, rule)
elsif teams[i].length < 2
teams[i] = RuledTeam.new(party, rule)
elsif i >= maxteams
teams[i] = nil
teams.compact!
elsif teams[i].totalGames >= 250
# retire
for j in 0...teams[i].length
party.push(teams[i][j])
end
teams[i] = RuledTeam.new(party,rule)
elsif teams[i].rating < toolowrating
teams[i] = RuledTeam.new(party,rule)
end
i += 1
end
save_data(teams, tag + "teams.rxdata")
yield(nil)
while teams.length < maxteams
yield(nil) if teams.length % 10 == 0
pbReplenishBattlePokemon(party, rule)
teams.push(RuledTeam.new(party, rule))
end
save_data(party, tag + ".rxdata")
teams = teams.sort { |a, b| b.rating <=> a.rating }
yield(_INTL("Simulating battles ({1} of {2})", iter + 1, iterations))
i = 0
loop do
changed = false
teams.length.times { |j|
yield(nil)
other = j
5.times do
other = rand(teams.length)
next if other == j
end
next if other == j
changed = true
pbRuledBattle(teams[j], teams[other], rule)
}
i += 1
gameCount = 0
for team in teams
gameCount += team.games
end
yield(nil)
if gameCount / teams.length >= 12
for team in teams
games = team.games
team.updateRating
end
break
end
end
teams.sort! { |a, b| b.rating <=> a.rating }
save_data(teams, tag + "teams.rxdata")
end
party = []
yield(nil)
teams.sort! { |a, b| a.rating <=> b.rating }
for team in teams
next if team.rating <= cutoffrating
for i in 0...team.length
party.push(team[i])
end
end
rule = oldrule
yield(nil)
party = pbRemoveDuplicates(party)
yield(_INTL("Writing results"))
party = pbArrangeByTier(party, rule)
yield(nil)
pbTrainerInfo(party, tag, rule) { yield(nil) }
yield(nil)
end
#===============================================================================
#
#===============================================================================
def pbWriteCup(id, rules)
return if !$DEBUG
trlists = (load_data("Data/trainer_lists.dat") rescue [])
list = []
for i in 0...trlists.length
tr = trlists[i]
if tr[5]
list.push("*" + (tr[3].sub(/\.txt$/, "")))
else
list.push((tr[3].sub(/\.txt$/, "")))
end
end
cmd = 0
if trlists.length != 0
cmd = pbMessage(_INTL("Generate Pokémon teams for this challenge?"),
[_INTL("NO"), _INTL("YES, USE EXISTING"), _INTL("YES, USE NEW")], 1)
else
cmd = pbMessage(_INTL("Generate Pokémon teams for this challenge?"),
[_INTL("YES"), _INTL("NO")], 2)
if cmd == 0
cmd = 2
elsif cmd == 1
cmd = 0
end
end
return if cmd == 0 # No
if cmd == 1 # Yes, use existing
cmd = pbMessage(_INTL("Choose a challenge."), list, -1)
if cmd >= 0
pbMessage(_INTL("This challenge will use the Pokémon list from {1}.", list[cmd]))
for i in 0...trlists.length
tr = trlists[i]
while !tr[5] && tr[2].include?(id)
tr[2].delete(id)
end
end
trlists[cmd][2].push(id) if !trlists[cmd][5]
save_data(trlists, "Data/trainer_lists.dat")
Graphics.update
Compiler.write_trainer_lists
end
return
elsif cmd == 2 # Yes, use new
return if !pbConfirmMessage(_INTL("This may take a long time. Are you sure?"))
mw = pbCreateMessageWindow
t = Time.now
pbGenerateChallenge(rules, id) { |message|
if Time.now - t >= 5
Graphics.update
t = Time.now
end
if message
pbMessageDisplay(mw, message, false)
Graphics.update
t = Time.now
end
}
pbDisposeMessageWindow(mw)
pbMessage(_INTL("Team generation complete."))
end
end

View File

@@ -0,0 +1,366 @@
$baseStatTotal = {}
$babySpecies = {}
$minimumLevel = {}
$evolutions = {}
$legalMoves = {} # For each species, all the moves they have access to
$legalMovesLevel = 0 # Level for which $legalMoves were calculated
$tmMoves = nil # Array of all moves teachable by a HM/TM/TR
def pbBaseStatTotal(species)
baseStats = GameData::Species.get(species).base_stats
ret = 0
baseStats.each { |s| ret += s }
return ret
end
def baseStatTotal(species)
$baseStatTotal[species] = pbBaseStatTotal(species) if !$baseStatTotal[species]
return $baseStatTotal[species]
end
def babySpecies(species)
$babySpecies[species] = GameData::Species.get(species).get_baby_species if !$babySpecies[species]
return $babySpecies[species]
end
def minimumLevel(species)
$minimumLevel[species] = GameData::Species.get(species).minimum_level if !$minimumLevel[species]
return $minimumLevel[species]
end
def evolutions(species)
$evolutions[species] = GameData::Species.get(species).get_evolutions(true) if !$evolutions[species]
return $evolutions[species]
end
#===============================================================================
#
#===============================================================================
# Used to replace Sketch with any other move.
def pbRandomMove
keys = GameData::Move::DATA.keys
loop do
move_id = keys[rand(keys.length)]
move = GameData::Move.get(move_id)
next if move.id_number > 384 || move.id == :SKETCH || move.id == :STRUGGLE
return move.id
end
end
def pbGetLegalMoves2(species, maxlevel)
species_data = GameData::Species.get(species)
moves = []
return moves if !species_data
# Populate available moves array (moves)
species_data.moves.each { |m| addMove(moves, m[1], 2) if m[0] <= maxlevel }
if !$tmMoves
$tmMoves = []
GameData::Item.each { |i| $tmMoves.push(i.move) if i.is_machine? }
end
species_data.tutor_moves.each { |m| addMove(moves, m, 0) if $tmMoves.include?(m) }
babyspecies = babySpecies(species)
GameData::Species.get(babyspecies).egg_moves.each { |m| addMove(moves, m, 2) }
#
movedatas = []
for move in moves
movedatas.push([move, GameData::Move.get(move)])
end
# Delete less powerful moves
deleteAll = proc { |a, item|
while a.include?(item)
a.delete(item)
end
}
for move in moves
md = GameData::Move.get(move)
for move2 in movedatas
# If we have a move that always hits, remove all other moves with no
# effect of the same type and <= base power
if md.function_code == "0A5" && move2[1].function_code == "000" && # Always hits
md.type == move2[1].type && md.base_damage >= move2[1].base_damage
deleteAll.call(moves, move2[0])
# If we have two status moves that have the same function code, delete the
# one with lower accuracy (Supersonic vs. Confuse Ray, etc.)
elsif md.function_code == move2[1].function_code && md.base_damage == 0 &&
move2[1].base_damage == 0 && md.accuracy > move2[1].accuracy
deleteAll.call(moves, move2[0])
# Delete poison-causing moves if we have a move that causes toxic
elsif md.function_code == "006" && move2[1].function_code == "005"
deleteAll.call(moves, move2[0])
# If we have two moves with the same function code and type, and one of
# them is damaging and has 10/15/the same PP as the other move and EITHER
# does more damage than the other move OR does the same damage but is more
# accurate, delete the other move (Surf, Flamethrower, Thunderbolt, etc.)
elsif md.function_code == move2[1].function_code && md.base_damage != 0 &&
md.type == move2[1].type &&
(md.total_pp == 15 || md.total_pp == 10 || md.total_pp == move2[1].total_pp) &&
(md.base_damage > move2[1].base_damage ||
(md.base_damage == move2[1].base_damage && md.accuracy > move2[1].accuracy))
deleteAll.call(moves, move2[0])
end
end
end
return moves
end
def addMove(moves, move, base)
data = GameData::Move.get(move)
return if moves.include?(data.id)
return if [:BUBBLE, :BUBBLEBEAM].include?(data.id) # Never add these moves
count = base + 1 # Number of times to add move to moves
count = base if data.function_code == "000" && data.base_damage <= 40
if data.base_damage <= 30 || [:GROWL, :TAILWHIP, :LEER].include?(data.id)
count = base
end
if data.base_damage >= 60 ||
[:REFLECT, :LIGHTSCREEN, :SAFEGUARD, :SUBSTITUTE, :FAKEOUT].include?(data.id)
count = base + 2
end
if data.base_damage >= 80 && data.type == :NORMAL
count = base + 3
end
if [:PROTECT, :DETECT, :TOXIC, :AERIALACE, :WILLOWISP, :SPORE, :THUNDERWAVE,
:HYPNOSIS, :CONFUSERAY, :ENDURE, :SWORDSDANCE].include?(data.id)
count = base + 3
end
count.times { moves.push(data.id) }
end
# Returns whether moves contains any move with the same type as thismove but
# with a higher base damage than it.
def hasMorePowerfulMove(moves, thismove)
thisdata = GameData::Move.get(thismove)
return false if thisdata.base_damage == 0
for move in moves
next if !move
moveData = GameData::Move.get(move)
if moveData.type == thisdata.type && moveData.base_damage > thisdata.base_damage
return true
end
end
return false
end
#===============================================================================
# Generate a random Pokémon that adheres to the given rules.
#===============================================================================
def pbRandomPokemonFromRule(rules, trainer)
pkmn = nil
iteration = -1
loop do
iteration += 1
species = nil
level = rules.ruleset.suggestedLevel
keys = GameData::Species::DATA.keys
loop do
loop do
species = keys[rand(keys.length)]
break if GameData::Species.get(species).form == 0
end
r = rand(20)
bst = baseStatTotal(species)
next if level < minimumLevel(species)
if iteration % 2 == 0
next if r < 16 && bst < 400
next if r < 13 && bst < 500
else
next if bst > 400
next if r < 10 && babySpecies(species) != species
end
next if r < 10 && babySpecies(species) == species
next if r < 7 && evolutions(species).length > 0
break
end
ev = []
GameData::Stat.each_main { |s| ev.push(s.id) if rand(100) < 50 }
nature = nil
keys = GameData::Nature::DATA.keys
loop do
nature = keys[rand(keys.length)]
nature_data = GameData::Nature.get(nature)
if [:LAX, :GENTLE].include?(nature_data.id) || nature_data.stat_changes.length == 0
next if rand(20) < 19
else
raised_emphasis = false
lowered_emphasis = false
nature_data.stat_changes.each do |change|
next if !ev.include?(change[0])
raised_emphasis = true if change[1] > 0
lowered_emphasis = true if change[1] < 0
end
next if rand(10) < 6 && !raised_emphasis
next if rand(10) < 9 && lowered_emphasis
end
break
end
$legalMoves = {} if level != $legalMovesLevel
$legalMovesLevel = level
$legalMoves[species] = pbGetLegalMoves2(species, level) if !$legalMoves[species]
itemlist = [
:ORANBERRY, :SITRUSBERRY, :ADAMANTORB, :BABIRIBERRY,
:BLACKSLUDGE, :BRIGHTPOWDER, :CHESTOBERRY, :CHOICEBAND,
:CHOICESCARF, :CHOICESPECS, :CHOPLEBERRY, :DAMPROCK,
:DEEPSEATOOTH, :EXPERTBELT, :FLAMEORB, :FOCUSSASH,
:FOCUSBAND, :HEATROCK, :LEFTOVERS, :LIFEORB, :LIGHTBALL,
:LIGHTCLAY, :LUMBERRY, :OCCABERRY, :PETAYABERRY, :SALACBERRY,
:SCOPELENS, :SHEDSHELL, :SHELLBELL, :SHUCABERRY, :LIECHIBERRY,
:SILKSCARF, :THICKCLUB, :TOXICORB, :WIDELENS, :YACHEBERRY,
:HABANBERRY, :SOULDEW, :PASSHOBERRY, :QUICKCLAW, :WHITEHERB
]
# Most used: Leftovers, Life Orb, Choice Band, Choice Scarf, Focus Sash
item = nil
loop do
if rand(40) == 0
item = :LEFTOVERS
break
end
item = itemlist[rand(itemlist.length)]
next if !item
case item
when :LIGHTBALL
next if species != :PIKACHU
when :SHEDSHELL
next if species != :FORRETRESS && species != :SKARMORY
when :SOULDEW
next if species != :LATIOS && species != :LATIAS
when :FOCUSSASH
next if baseStatTotal(species) > 450 && rand(10) < 8
when :ADAMANTORB
next if species != :DIALGA
when :PASSHOBERRY
next if species != :STEELIX
when :BABIRIBERRY
next if species != :TYRANITAR
when :HABANBERRY
next if species != :GARCHOMP
when :OCCABERRY
next if species != :METAGROSS
when :CHOPLEBERRY
next if species != :UMBREON
when :YACHEBERRY
next if ![:TORTERRA, :GLISCOR, :DRAGONAIR].include?(species)
when :SHUCABERRY
next if species != :HEATRAN
when :DEEPSEATOOTH
next if species != :CLAMPERL
when :THICKCLUB
next if ![:CUBONE, :MAROWAK].include?(species)
when :LIECHIBERRY
ev.push(:ATTACK) if !ev.include?(:ATTACK) && rand(100) < 50
when :SALACBERRY
ev.push(:SPEED) if !ev.include?(:SPEED) && rand(100) < 50
when :PETAYABERRY
ev.push(:SPECIAL_ATTACK) if !ev.include?(:SPECIAL_ATTACK) && rand(100) < 50
end
break
end
if level < 10 && GameData::Item.exists?(:ORANBERRY)
item = :ORANBERRY if rand(40) == 0 || item == :SITRUSBERRY
elsif level > 20 && GameData::Item.exists?(:SITRUSBERRY)
item = :SITRUSBERRY if rand(40) == 0 || item == :ORANBERRY
end
moves = $legalMoves[species]
sketch = false
if moves[0] == :SKETCH
sketch = true
for m in 0...Pokemon::MAX_MOVES
moves[m] = pbRandomMove
end
end
next if moves.length == 0
if (moves | []).length < Pokemon::MAX_MOVES
moves = [:TACKLE] if moves.length == 0
moves |= []
else
newmoves = []
rest = GameData::Move.exists?(:REST) ? :REST : nil
spitup = GameData::Move.exists?(:SPITUP) ? :SPITUP : nil
swallow = GameData::Move.exists?(:SWALLOW) ? :SWALLOW : nil
stockpile = GameData::Move.exists?(:STOCKPILE) ? :STOCKPILE : nil
snore = GameData::Move.exists?(:SNORE) ? :SNORE : nil
sleeptalk = GameData::Move.exists?(:SLEEPTALK) ? :SLEEPTALK : nil
loop do
newmoves.clear
while newmoves.length < [moves.length, Pokemon::MAX_MOVES].min
m = moves[rand(moves.length)]
next if rand(100) < 50 && hasMorePowerfulMove(moves, m)
newmoves.push(m) if m && !newmoves.include?(m)
end
if (newmoves.include?(spitup) || newmoves.include?(swallow)) &&
!newmoves.include?(stockpile)
next unless sketch
end
if (!newmoves.include?(spitup) && !newmoves.include?(swallow)) &&
newmoves.include?(stockpile)
next unless sketch
end
if newmoves.include?(sleeptalk) && !newmoves.include?(rest)
next unless (sketch || !moves.include?(rest)) && rand(100) < 20
end
if newmoves.include?(snore) && !newmoves.include?(rest)
next unless (sketch || !moves.include?(rest)) && rand(100) < 20
end
totalbasedamage = 0
hasPhysical = false
hasSpecial = false
hasNormal = false
for move in newmoves
d = GameData::Move.get(move)
if d.base_damage >= 1
totalbasedamage += d.base_damage
hasNormal = true if d.type == :NORMAL
hasPhysical = true if d.category == 0
hasSpecial = true if d.category == 1
end
end
if !hasPhysical && ev.include?(:ATTACK)
# No physical attack, but emphasizes Attack
next if rand(100) < 80
end
if !hasSpecial && ev.include?(:SPECIAL_ATTACK)
# No special attack, but emphasizes Special Attack
next if rand(100) < 80
end
r = rand(10)
next if r > 6 && totalbasedamage > 180
next if r > 8 && totalbasedamage > 140
next if totalbasedamage == 0 && rand(100) < 95
############
# Moves accepted
if hasPhysical && !hasSpecial
ev.push(:ATTACK) if rand(100) < 80
ev.delete(:SPECIAL_ATTACK) if rand(100) < 80
end
if !hasPhysical && hasSpecial
ev.delete(:ATTACK) if rand(100) < 80
ev.push(:SPECIAL_ATTACK) if rand(100) < 80
end
item = :LEFTOVERS if !hasNormal && item == :SILKSCARF
moves = newmoves
break
end
end
if item == :LIGHTCLAY && !moves.any? { |m| m == :LIGHTSCREEN || m = :REFLECT }
item = :LEFTOVERS
end
if item == :BLACKSLUDGE
type1 = GameData::Species.get(species).type1
type2 = GameData::Species.get(species).type2 || type1
item = :LEFTOVERS if type1 != :POISON && type2 != :POISON
end
if item == :HEATROCK && !moves.any? { |m| m == :SUNNYDAY }
item = :LEFTOVERS
end
if item == :DAMPROCK && !moves.any? { |m| m == :RAINDANCE }
item = :LEFTOVERS
end
if moves.any? { |m| m == :REST }
item = :LUMBERRY if rand(100) < 33
item = :CHESTOBERRY if rand(100) < 25
end
pk = PBPokemon.new(species, item, nature, moves[0], moves[1], moves[2], moves[3], ev)
pkmn = pk.createPokemon(level, 31, trainer)
break if rules.ruleset.isPokemonValid?(pkmn)
end
return pkmn
end

View File

@@ -0,0 +1,215 @@
#===============================================================================
#
#===============================================================================
def getTypes(species)
species_data = GameData::Species.get(species)
type1 = species_data.type1
type2 = species_data.type2
return (type1 == type2) ? [type1] : [type1, type2]
end
#===============================================================================
# If no trainers are defined for the current challenge, generate a set of random
# ones for it. If pokemonlist is given, assign Pokémon from it to all trainers.
# Save the results in the appropriate PBS files.
#===============================================================================
def pbTrainerInfo(pokemonlist, trfile, rules)
bttrainers = pbGetBTTrainers(trfile)
btpokemon = pbGetBTPokemon(trfile)
# No battle trainers found; fill bttrainers with 200 randomly chosen ones from
# all that exist (with a base money < 100)
if bttrainers.length == 0
for i in 0...200
yield(nil) if block_given? && i % 50 == 0
trainerid = nil
if GameData::TrainerType.exists?(:YOUNGSTER) && rand(30) == 0
trainerid = :YOUNGSTER
else
tr_typekeys = GameData::TrainerType::DATA.keys
loop do
tr_type = tr_typekeys[rand(tr_typekeys.length)]
tr_type_data = GameData::TrainerType.get(tr_type)
next if tr_type_data.base_money >= 100
trainerid = tr_type_data.id
end
end
# Create a random name for the trainer
gender = GameData::TrainerType.get(trainerid).gender
randomName = getRandomNameEx(gender, nil, 0, 12)
# Add the trainer to bttrainers
tr = [trainerid, randomName, _INTL("Here I come!"), _INTL("Yes, I won!"),
_INTL("Man, I lost!"), []]
bttrainers.push(tr)
end
# Sort all the randomly chosen trainers by their base money (smallest first)
bttrainers.sort! { |a, b|
money1 = GameData::TrainerType.get(a[0]).base_money
money2 = GameData::TrainerType.get(b[0]).base_money
next (money1 == money2) ? a[0].to_s <=> b[0].to_s : money1 <=> money2
}
end
yield(nil) if block_given?
# Set all Pokémon in pokemonlist to the appropriate level, and determine their
# type(s) and whether they are valid for the given rules
suggestedLevel = rules.ruleset.suggestedLevel
rulesetTeam = rules.ruleset.copy.clearPokemonRules
pkmntypes = []
validities = []
for pkmn in pokemonlist
pkmn.level = suggestedLevel if pkmn.level != suggestedLevel
pkmntypes.push(getTypes(pkmn.species))
validities.push(rules.ruleset.isPokemonValid?(pkmn))
end
# For each trainer in bttrainers, come up with a set of Pokémon taken from
# pokemonlist for that trainer, and copy the trainer and their set of Pokémon
# to newbttrainers
newbttrainers = []
for btt in 0...bttrainers.length
yield(nil) if block_given? && btt % 50 == 0
trainerdata = bttrainers[btt]
pokemonnumbers = trainerdata[5] || []
# Find all the Pokémon available to the trainer, and count up how often
# those Pokémon have each type
species = []
types = {}
GameData::Type.each { |t| types[t.id] = 0 }
for pn in pokemonnumbers
pkmn = btpokemon[pn]
species.push(pkmn.species)
t = getTypes(pkmn.species)
t.each { |typ| types[typ] += 1 }
end
species |= [] # remove duplicates
# Scale down the counts of each type to the range 0 -> 10
count = 0
GameData::Type.each do |t|
if types[t.id] >= 5
types[t.id] /= 4
types[t.id] = 10 if types[t.id] > 10
else
types[t.id] = 0
end
count += types[t.id]
end
types[:NORMAL] = 1 if count == 0 # All type counts are 0; add 1 to Normal
# Trainer had no Pokémon available to it; make all the type counts 1
if pokemonnumbers.length == 0
GameData::Type.each { |t| types[t.id] = 1 }
end
# Get Pokémon from pokemonlist, if there are any, and make sure enough are
# gotten that a valid team can be made from them
numbers = []
if pokemonlist
# For each valid Pokémon in pokemonlist, add its index within pokemonlist
# to numbers, but only if that species is available to the trainer.
# Pokémon are less likely to be added if it is positioned later in
# pokemonlist, or if the trainer is positioned earlier in bttrainers (i.e.
# later trainers get better Pokémon).
numbersPokemon = []
for index in 0...pokemonlist.length
next if !validities[index]
pkmn = pokemonlist[index]
absDiff = ((index * 8 / pokemonlist.length) - (btt * 8 / bttrainers.length)).abs
if species.include?(pkmn.species)
weight = [32, 12, 5, 2, 1, 0, 0, 0][[absDiff, 7].min]
if rand(40) < weight
numbers.push(index)
numbersPokemon.push(pokemonlist[index])
end
else
# Pokémon's species isn't available to the trainer; try adding it
# anyway (more likely to add it if the trainer has access to more
# Pokémon of the same type(s) as this Pokémon)
t = pkmntypes[index]
t.each { |typ|
weight = [32, 12, 5, 2, 1, 0, 0, 0][[absDiff, 7].min]
weight *= types[typ]
if rand(40) < weight
numbers.push(index)
numbersPokemon.push(pokemonlist[index])
end
}
end
end
numbers |= [] # Remove duplicates
# If there aren't enough Pokémon to form a full team, or a valid team
# can't be made from them, fill up numbers with Pokémon in pokemonlist
# that EITHER have the same species as one available to the trainer OR has
# a type that is available to the trainer, until a valid team can be
# formed from what's in numbers
if numbers.length < Settings::MAX_PARTY_SIZE ||
!rulesetTeam.hasValidTeam?(numbersPokemon)
for index in 0...pokemonlist.length
pkmn = pokemonlist[index]
next if !validities[index]
if species.include?(pkmn.species)
numbers.push(index)
numbersPokemon.push(pokemonlist[index])
else
t = pkmntypes[index]
t.each { |typ|
if types[typ] > 0 && !numbers.include?(index)
numbers.push(index)
numbersPokemon.push(pokemonlist[index])
break
end
}
end
break if numbers.length >= Settings::MAX_PARTY_SIZE && rules.ruleset.hasValidTeam?(numbersPokemon)
end
# If there STILL aren't enough Pokémon to form a full team, or a valid
# team can't be made from them, add random Pokémon from pokemonlist
# until a valid team can be formed from what's in numbers
if numbers.length < Settings::MAX_PARTY_SIZE || !rules.ruleset.hasValidTeam?(numbersPokemon)
while numbers.length < pokemonlist.length &&
(numbers.length < Settings::MAX_PARTY_SIZE || !rules.ruleset.hasValidTeam?(numbersPokemon))
index = rand(pokemonlist.length)
if !numbers.include?(index)
numbers.push(index)
numbersPokemon.push(pokemonlist[index])
end
end
end
end
numbers.sort!
end
# Add the trainer's data, including all Pokémon that should be available to
# it (from pokemonlist), to newbttrainers
newbttrainers.push([trainerdata[0], trainerdata[1], trainerdata[2],
trainerdata[3], trainerdata[4], numbers])
end
yield(nil) if block_given?
# Add the trainer and Pokémon data from above to trainer_lists.dat, and then
# create all PBS files from it
pbpokemonlist = []
for pkmn in pokemonlist
pbpokemonlist.push(PBPokemon.fromPokemon(pkmn))
end
trlists = (load_data("Data/trainer_lists.dat") rescue [])
hasDefault = false
trIndex = -1
for i in 0...trlists.length
next if !trlists[i][5]
hasDefault = true
break
end
for i in 0...trlists.length
if trlists[i][2].include?(trfile)
trIndex = i
trlists[i][0] = newbttrainers
trlists[i][1] = pbpokemonlist
trlists[i][5] = !hasDefault
end
end
yield(nil) if block_given?
if trIndex < 0
info = [newbttrainers, pbpokemonlist, [trfile],
trfile + "tr.txt", trfile + "pm.txt", !hasDefault]
trlists.push(info)
end
yield(nil) if block_given?
save_data(trlists, "Data/trainer_lists.dat")
yield(nil) if block_given?
Compiler.write_trainer_lists
yield(nil) if block_given?
end

View File

@@ -0,0 +1,433 @@
#===============================================================================
#
#===============================================================================
class RuledTeam
attr_accessor :team
def initialize(party, rule)
count = rule.ruleset.suggestedNumber
@team = []
retnum = []
loop do
for i in 0...count
retnum[i] = rand(party.length)
@team[i] = party[retnum[i]]
party.delete_at(retnum[i])
end
break if rule.ruleset.isValid?(@team)
end
@totalGames = 0
@rating = PlayerRating.new
@history = MatchHistory.new(@rating)
end
def [](i)
@team[i]
end
def length
return @team.length
end
def rating
@rating.winChancePercent
end
def ratingData
@rating
end
def ratingRaw
[@rating.rating, @rating.deviation, @rating.volatility, @rating.winChancePercent]
end
def compare(other)
@rating.compare(other.ratingData)
end
def totalGames
(@totalGames || 0) + self.games
end
def addMatch(other,score)
@history.addMatch(other.ratingData, score)
end
def games
@history.length
end
def updateRating
@totalGames = 0 if !@totalGames
oldgames = self.games
@history.updateAndClear()
newgames = self.games
@totalGames += (oldgames - newgames)
end
def toStr
return "[" + @rating.to_i.to_s + "," + @games.to_i.to_s + "]"
end
def load(party)
ret = []
for i in 0...team.length
ret.push(party[team[i]])
end
return ret
end
end
#===============================================================================
#
#===============================================================================
class SingleMatch
attr_reader :opponentRating
attr_reader :opponentDeviation
attr_reader :score
attr_reader :kValue
def initialize(opponentRating, opponentDev, score, kValue = 16)
@opponentRating = opponentRating
@opponentDeviation = opponentDev
@score = score # -1=draw, 0=lose, 1=win
@kValue = kValue
end
end
#===============================================================================
#
#===============================================================================
class MatchHistory
include Enumerable
def initialize(thisPlayer)
@matches = []
@thisPlayer = thisPlayer
end
def [](i)
@matches[i]
end
def length
@matches.length
end
def each
@matches.each { |item| yield item }
end
def addMatch(otherPlayer, result)
# 1=I won; 0=Other player won; -1: Draw
@matches.push(SingleMatch.new(otherPlayer.rating, otherPlayer.deviation, result))
end
def updateAndClear
@thisPlayer.update(@matches)
@matches.clear
end
end
#===============================================================================
#
#===============================================================================
class PlayerRatingElo
attr_reader :rating
K_VALUE = 16
def initialize
@rating = 1600.0
@deviation = 0
@volatility = 0
@estimatedRating = nil
end
def winChancePercent
return @estimatedRating if @estimatedRating
x = (1 + 10.0**((@rating - 1600.0) / 400.0))
@estimatedRating = (x == 0 ? 1.0 : 1.0 / x)
return @estimatedRating
end
def update(matches)
return if matches.length == 0
stake = 0
matches.length.times do
score = (match.score == -1) ? 0.5 : match.score
e = (1 + 10.0**((@rating - match.opponentRating) / 400.0))
stake += match.kValue * (score - e)
end
@rating += stake
end
end
#===============================================================================
#
#===============================================================================
class PlayerRating
attr_reader :volatility
attr_reader :deviation
attr_reader :rating
def initialize
@rating = 1500.0
@deviation = 350.0
@volatility = 0.9
@estimatedRating = nil
end
def winChancePercent
return @estimatedRating if @estimatedRating
if @deviation > 100
# https://www.smogon.com/forums/threads/make-sense-of-your-shoddy-battle-rating.55764/
otherRating = 1500.0
otherDeviation = 350.0
s = Math.sqrt(100000.0 + @deviation * @deviation + otherDeviation * otherDeviation)
g = 10.0**((otherRating - @rating) * 0.79 / s)
@estimatedRating = (1.0 / (1.0 + g)) * 100.0 # Percent chance that I win against opponent
else
# GLIXARE method
rds = @deviation * @deviation
sqr = Math.sqrt(15.905694331435 * (rds + 221781.21786254))
inner = (1500.0 - @rating) * Math::PI / sqr
@estimatedRating = (10000.0 / (1.0 + (10.0**inner)) + 0.5) / 100.0
end
return @estimatedRating
end
def update(matches, system = 1.2)
volatility = volatility2
deviation = deviation2
rating = rating2
if matches.length == 0
setDeviation2(Math.sqrt(deviation * deviation + volatility * volatility))
return
end
g = []
e = []
score = []
for i in 0...matches.length
match = matches[i]
g[i] = getGFactor(match.opponentDeviation)
e[i] = getEFactor(rating, match.opponentRating, g[i])
score[i] = match.score
end
# Estimated variance
variance = 0.0
for i in 0...matches.length
variance += g[i] * g[i] * e[i] * (1 - e[i])
end
variance = 1.0 / variance
# Improvement sum
sum = 0.0
for i in 0...matches.length
v = score[i]
sum += g[i] * (v.to_f - e[i]) if v != -1
end
volatility = getUpdatedVolatility(volatility, deviation, variance, sum, system)
# Update deviation
t = deviation * deviation + volatility * volatility
deviation = 1.0 / Math.sqrt(1.0 / t + 1.0 / variance)
# Update rating
rating = rating + deviation * deviation * sum
setRating2(rating)
setDeviation2(deviation)
setVolatility2(volatility)
end
private
attr_writer :volatility
alias volatility2 volatility
def rating2
return (@rating - 1500.0) / 173.7178
end
def deviation2
return @deviation / 173.7178
end
def getGFactor(deviation)
# deviation is not yet in glicko2
deviation /= 173.7178
return 1.0 / Math.sqrt(1.0 + (3.0 * deviation * deviation) / (Math::PI * Math::PI))
end
def getEFactor(rating, opponentRating, g)
# rating is already in glicko2
# opponentRating is not yet in glicko2
opponentRating = (opponentRating - 1500.0) / 173.7178
return 1.0 / (1.0 + Math.exp(-g * (rating - opponentRating)))
end
def setVolatility2(value)
@volatility = value
end
def setRating2(value)
@estimatedRating = nil
@rating = (value * 173.7178) + 1500.0
end
def setDeviation2(value)
@estimatedRating = nil
@deviation = value * 173.7178
end
def getUpdatedVolatility(volatility, deviation, variance, improvementSum, system)
improvement = improvementSum * variance
a = Math.log(volatility * volatility)
squSystem = system * system
squDeviation = deviation * deviation
squVariance = variance + variance
squDevplusVar = squDeviation + variance
x0 = a
100.times { # Up to 100 iterations to avoid potentially infinite loops
e = Math.exp(x0)
d = squDevplusVar + e
squD = d * d
i = improvement / d
h1 = -(x0 - a) / squSystem - 0.5 * e * i * i
h2 = -1.0 / squSystem - 0.5 * e * squDevplusVar / squD
h2 += 0.5 * squVariance * e * (squDevplusVar - e) / (squD * d)
x1 = x0
x0 -= h1 / h2
break if ((x1 - x0).abs < 0.000001)
}
return Math.exp(x0 / 2.0)
end
end
#===============================================================================
#
#===============================================================================
def pbDecideWinnerEffectiveness(move, otype1, otype2, ability, scores)
data = GameData::Move.get(move)
return 0 if data.base_damage == 0
atype = data.type
typemod = Effectiveness::NORMAL_EFFECTIVE_ONE ** 2
if ability != :LEVITATE || data.type != :GROUND
mod1 = Effectiveness.calculate_one(atype, otype1)
mod2 = (otype1 == otype2) ? Effectiveness::NORMAL_EFFECTIVE_ONE : Effectiveness.calculate_one(atype, otype2)
if ability == :WONDERGUARD
mod1 = Effectiveness::NORMAL_EFFECTIVE_ONE if mod1 <= Effectiveness::NORMAL_EFFECTIVE_ONE
mod2 = Effectiveness::NORMAL_EFFECTIVE_ONE if mod2 <= Effectiveness::NORMAL_EFFECTIVE_ONE
end
typemod = mod1 * mod2
end
return scores[0] if typemod == 0 # Ineffective
return scores[1] if typemod == 1 # Doubly not very effective
return scores[2] if typemod == 2 # Not very effective
return scores[3] if typemod == 4 # Normal effective
return scores[4] if typemod == 8 # Super effective
return scores[5] if typemod == 16 # Doubly super effective
return 0
end
def pbDecideWinnerScore(party0, party1, rating)
score = 0
types1 = []
types2 = []
abilities = []
for j in 0...party1.length
types1.push(party1[j].type1)
types2.push(party1[j].type2)
abilities.push(party1[j].ability_id)
end
for i in 0...party0.length
for move in party0[i].moves
next if !move
for j in 0...party1.length
score += pbDecideWinnerEffectiveness(move.id,
types1[j], types2[j], abilities[j], [-16, -8, 0, 4, 12, 20])
end
end
basestatsum = baseStatTotal(party0[i].species)
score += basestatsum / 10
score += 10 if party0[i].item # Not in Battle Dome ranking
end
score += rating + rand(32)
return score
end
def pbDecideWinner(party0, party1, rating0, rating1)
rating0 = (rating0 * 15.0 / 100).round
rating1 = (rating1 * 15.0 / 100).round
score0 = pbDecideWinnerScore(party0, party1, rating0)
score1 = pbDecideWinnerScore(party1, party0, rating1)
if score0 == score1
return 5 if rating0 == rating1
return (rating0 > rating1) ? 1 : 2
else
return (score0 > score1) ? 1 : 2
end
end
#===============================================================================
#
#===============================================================================
def pbRuledBattle(team1, team2, rule)
decision = 0
if rand(100) != 0
party1 = []
party2 = []
team1.length.times { |i| party1.push(team1[i]) }
team2.length.times { |i| party2.push(team2[i]) }
decision = pbDecideWinner(party1, party2, team1.rating, team2.rating)
else
level = rule.ruleset.suggestedLevel
t_type = nil
GameData::TrainerType.each { |t| t_type = t.id; break }
trainer1 = NPCTrainer.new("PLAYER1", t_type)
trainer2 = NPCTrainer.new("PLAYER2", t_type)
items1 = []
items2 = []
team1.each_with_index do |p, i|
next if !p
if p.level != level
p.level = level
p.calc_stats
end
items1[i] = p.item_id
trainer1.party.push(p)
end
team2.each_with_index do |p, i|
next if !p
if p.level != level
p.level = level
p.calc_stats
end
items2[i] = p.item_id
trainer2.party.push(p)
end
scene = PokeBattle_DebugSceneNoLogging.new
battle = rule.createBattle(scene, trainer1, trainer2)
battle.debug = true
battle.controlPlayer = true
battle.internalBattle = false
decision = battle.pbStartBattle
team1.each_with_index do |p, i|
next if !p
p.heal
p.item = items1[i]
end
team2.each_with_index do |p, i|
next if !p
p.heal
p.item = items2[i]
end
end
if decision == 1 # Team 1 wins
team1.addMatch(team2, 1)
team2.addMatch(team1, 0)
elsif decision == 2 # Team 2 wins
team1.addMatch(team2, 0)
team2.addMatch(team1, 1)
else
team1.addMatch(team2, -1)
team2.addMatch(team1, -1)
end
end