6.6 update

This commit is contained in:
chardub
2025-06-07 08:16:50 -04:00
parent 295a71dbcd
commit a393ba1137
467 changed files with 171196 additions and 36566 deletions

View File

@@ -0,0 +1,33 @@
# frozen_string_literal: true
#eventType
# :EVOLVE
# :FUSE
# :UNFUSE
# :REVERSE
# :CAUGHT
class BattledTrainerRandomEvent
attr_accessor :eventType
attr_accessor :caught_pokemon #species_sym
attr_accessor :unevolved_pokemon #species_sym
attr_accessor :evolved_pokemon #species_sym
attr_accessor :fusion_head_pokemon #species_sym
attr_accessor :fusion_body_pokemon #species_sym
attr_accessor :fusion_fused_pokemon #species_sym
attr_accessor :unreversed_pokemon #species_sym
attr_accessor :reversed_pokemon #species_sym
attr_accessor :unfused_pokemon #species_sym
def initialize(eventType)
@eventType = eventType
end
end

View File

@@ -0,0 +1,217 @@
class BattledTrainer
DELAY_BETWEEN_NPC_TRADES = 180 #In seconds (3 minutes)
MAX_FRIENDSHIP = 100
attr_accessor :trainerType
attr_accessor :trainerName
attr_accessor :currentTeam #list of Pokemon. The game selects in this list for trade offers. They can increase levels & involve as you rebattle them.
#trainers will randomly find items and add them to this list. When they have the :ITEM status, they will
# give one of them at random.
#Items equipped to the Pokemon traded by the player will end up in that list.
#
# If there is an evolution that the trainer can use on one of their Pokemon in that list, they will
# instead use it to evolve their Pokemon.
#
#DNA Splicers/reversers can be used on their Pokemon if they have at least 2 unfused/1 fused
#
#Healing items that are in that list can be used by the trainer in rematches
#
attr_accessor :foundItems
attr_accessor :nb_rematches
#What the trainer currently wants to do
# :IDLE -> Nothing. Normal postbattle dialogue
# Should prompt the player to register the trainer in their phone.
# Or maybe done automatically at the end of the battle?
# :TRADE -> Trainer wants to trade one of its Pokémon with the player
# :BATTLE -> Trainer wants to rebattle the player
# :ITEM -> Trainer has an item they want to give the player
attr_accessor :current_status
attr_accessor :previous_status
attr_accessor :previous_trade_timestamp
attr_accessor :favorite_type
attr_accessor :favorite_pokemon #Used for generating trade offers. Should be set from trainer.txt (todo)
#If empty, then trade offers ask for a Pokemon of a type depending on the trainer's class
attr_accessor :previous_random_events
attr_accessor :has_pending_action
attr_accessor :custom_appearance
attr_accessor :friendship #increases the more you interact with them, unlocks more interact options
attr_accessor :friendship_level
def initialize(trainerType,trainerName,trainerVersion)
@trainerType = trainerType
@trainerName = trainerName
@currentTeam = loadOriginalTrainerTeam(trainerVersion)
@foundItems = []
@nb_rematches = 0
@currentStatus = :IDLE
@previous_status = :IDLE
@previous_trade_timestamp = Time.now-DELAY_BETWEEN_NPC_TRADES
@previous_random_events =[]
@has_pending_action=false
@favorite_type = pick_favorite_type(trainerType)
@friendship = 0
@friendship_level = 0
end
def friendship_level
@friendship_level =0 if !@friendship_level
return @friendship_level
end
def increase_friendship(amount)
@friendship=0 if !@friendship
@friendship_level=0 if !@friendship_level
gain = amount / ((@friendship + 1) ** 0.4)
@friendship += gain
@friendship = MAX_FRIENDSHIP if @friendship > MAX_FRIENDSHIP
echoln "Friendship with #{@trainerName} increased by #{gain.round(2)} (total: #{@friendship.round(2)})"
thresholds = FRIENDSHIP_LEVELS[@trainerType] || []
echoln thresholds
while @friendship_level < thresholds.length && @friendship >= thresholds[@friendship_level]
@friendship_level += 1
trainerClassName = GameData::TrainerType.get(@trainerType).real_name
pbMessage(_INTL("\\C[3]Friendship increased with #{trainerClassName} #{@trainerName}!"))
case @friendship_level
when 1
pbMessage(_INTL("You can now trade with each other!"))
when 2
pbMessage(_INTL("They will now give you items from time to time!"))
when 3
pbMessage(_INTL("You can now partner up with them!"))
end
echoln "🎉 #{@trainerName}'s friendship level increased to #{@friendship_level}!"
end
end
def set_custom_appearance(trainer_appearance)
@custom_appearance = trainer_appearance
end
def pick_favorite_type(trainer_type)
if TRAINER_CLASS_FAVORITE_TYPES.has_key?(trainer_type)
return TRAINER_CLASS_FAVORITE_TYPES[trainer_type].sample
else
return :NORMAL
end
end
def set_pending_action(value)
@has_pending_action=value
end
def log_evolution_event(unevolved_pokemon_species, evolved_pokemon_species)
echoln "NPC Trainer #{@trainerName} evolved their #{get_species_readable_internal_name(unevolved_pokemon_species)} to #{get_species_readable_internal_name(evolved_pokemon_species)}!"
event = BattledTrainerRandomEvent.new(:EVOLVE)
event.unevolved_pokemon = unevolved_pokemon_species
event.evolved_pokemon = evolved_pokemon_species
@previous_random_events = [] unless @previous_random_events
@previous_random_events << event
end
def log_fusion_event(body_pokemon_species, head_pokemon_species, fused_pokemon_species)
echoln "NPC trainer #{@trainerName} fused #{body_pokemon_species} and #{head_pokemon_species}!"
event = BattledTrainerRandomEvent.new(:FUSE)
event.fusion_body_pokemon =body_pokemon_species
event.fusion_head_pokemon =head_pokemon_species
event.fusion_fused_pokemon =fused_pokemon_species
@previous_random_events = [] unless @previous_random_events
@previous_random_events << event
end
def log_unfusion_event(original_fused_pokemon_species, unfused_body_species, unfused_body_head)
echoln "NPC trainer #{@trainerName} unfused #{get_species_readable_internal_name(original_fused_pokemon_species)}!"
event = BattledTrainerRandomEvent.new(:UNFUSE)
event.unfused_pokemon = original_fused_pokemon_species
event.fusion_body_pokemon = unfused_body_species
event.fusion_head_pokemon = unfused_body_head
@previous_random_events = [] unless @previous_random_events
@previous_random_events << event
end
def log_reverse_event(original_fused_pokemon_species, reversed_fusion_species)
echoln "NPC trainer #{@trainerName} reversed #{get_species_readable_internal_name(original_fused_pokemon_species)}!"
event = BattledTrainerRandomEvent.new(:REVERSE)
event.unreversed_pokemon = original_fused_pokemon_species
event.reversed_pokemon = reversed_fusion_species
@previous_random_events = [] unless @previous_random_events
@previous_random_events << event
end
def log_catch_event(new_pokemon_species)
echoln "NPC Trainer #{@trainerName} caught a #{new_pokemon_species}!"
event = BattledTrainerRandomEvent.new(:CATCH)
event.caught_pokemon = new_pokemon_species
@previous_random_events = [] unless @previous_random_events
@previous_random_events << event
end
def clear_previous_random_events()
@previous_random_events = []
end
def loadOriginalTrainer(trainerVersion=0)
return pbLoadTrainer(@trainerType,@trainerName,trainerVersion)
end
def loadOriginalTrainerTeam(trainerVersion=0)
original_trainer = pbLoadTrainer(@trainerType,@trainerName,trainerVersion)
return if !original_trainer
echoln "Loading Trainer #{@trainerType}"
current_party = []
original_trainer.party.each do |partyMember|
echoln "PartyMember: #{partyMember}"
if partyMember.is_a?(Pokemon)
current_party << partyMember
elsif partyMember.is_a?(Array) #normally always gonna be this
pokemon_species = partyMember[0]
pokemon_level = partyMember[1]
current_party << Pokemon.new(pokemon_species,pokemon_level)
else
echoln "Could not add Pokemon #{partyMember} to rematchable trainer's party."
end
end
return current_party
end
def getTimeSinceLastTrade()
@previous_trade_timestamp ||= Time.now - DELAY_BETWEEN_NPC_TRADES
return Time.now - @previous_trade_timestamp
end
def isNextTradeReady?()
return getTimeSinceLastTrade < DELAY_BETWEEN_NPC_TRADES
end
def list_team_unfused_pokemon
list = []
@currentTeam.each do |pokemon|
list << pokemon if !pokemon.isFusion?
end
return list
end
def list_team_fused_pokemon
list = []
@currentTeam.each do |pokemon|
list << pokemon if pokemon.isFusion?
end
return list
end
end

View File

@@ -0,0 +1,70 @@
# frozen_string_literal: true
class PokemonGlobalMetadata
#Map that keeps track of all the npc trainers the player has battled
# [map_id,event_id] =>BattledTrainer
attr_accessor :battledTrainers
end
TIME_FOR_RANDOM_EVENTS = 60#3600 #1 hour
## Extend pbTrainerBattle to call postTrainerBattleAction at the end of every trainer battle
alias original_pbTrainerBattle pbTrainerBattle
def pbTrainerBattle(trainerID, trainerName,endSpeech=nil,
doubleBattle=false, trainerPartyID=0,
*args)
result = original_pbTrainerBattle(trainerID, trainerName, endSpeech,doubleBattle,trainerPartyID, *args)
postTrainerBattleActions(trainerID, trainerName,trainerPartyID) if Settings::GAME_ID == :IF_HOENN
return result
end
def postTrainerBattleActions(trainerID, trainerName,trainerVersion)
trainer = registerBattledTrainer(@event_id,$game_map.map_id,trainerID,trainerName,trainerVersion)
makeRebattledTrainerTeamGainExp(trainer)
end
#Do NOT call this alone. Rebattlable trainers are always intialized after
# defeating them.
# Having a rematchable trainer that is not registered will cause crashes.
def registerBattledTrainer(event_id, mapId, trainerType, trainerName, trainerVersion=0)
key = [event_id,mapId]
$PokemonGlobal.battledTrainers = {} unless $PokemonGlobal.battledTrainers
trainer = BattledTrainer.new(trainerType, trainerName, trainerVersion)
$PokemonGlobal.battledTrainers[key] = trainer
return trainer
end
def unregisterBattledTrainer(event_id, mapId)
key = [event_id,mapId]
$PokemonGlobal.battledTrainers = {} unless $PokemonGlobal.battledTrainers
if $PokemonGlobal.battledTrainers.has_key?(key)
$PokemonGlobal.battledTrainers[key] =nil
echoln "Unregistered Battled Trainer #{key}"
else
echoln "Could not unregister Battled Trainer #{key}"
end
end
def resetTrainerRebattle(event_id, map_id)
trainer = getRebattledTrainer(event_id,map_id)
trainerType = trainer.trainerType
trainerName = trainer.trainerName
unregisterBattledTrainer(event_id,map_id)
registerBattledTrainer(event_id,map_id,trainerType,trainerName)
end
def updateRebattledTrainer(event_id,map_id,updated_trainer)
key = [event_id,map_id]
$PokemonGlobal.battledTrainers = {} if !$PokemonGlobal.battledTrainers
$PokemonGlobal.battledTrainers[key] = updated_trainer
end
def getRebattledTrainer(event_id,map_id)
key = [event_id,map_id]
$PokemonGlobal.battledTrainers = {} if !$PokemonGlobal.battledTrainers
return $PokemonGlobal.battledTrainers[key]
end

View File

@@ -0,0 +1,123 @@
# frozen_string_literal: true
# After each rematch, all of the trainer's Pokémon gain EXP
#
# Gained Exp is calculated from the Pokemon that is in the first slot in the player's team
# so the trainer's levels will scale with the player's.
#
# e.g. If the player uses a stronger Pokemon in the battle, the NPC will get more experience
# as a result
#
def makeRebattledTrainerTeamGainExp(trainer, playerWon=true, gained_exp=nil)
return if !trainer
updated_team = []
trainer_pokemon = $Trainer.party[0]
return if !trainer_pokemon
for pokemon in trainer.currentTeam
if !gained_exp #Set depending on first pokemon in party if not given a specific amount
gained_exp = trainer_pokemon.level * trainer_pokemon.base_exp
gained_exp /= 2 if playerWon #trainer lost so he's not getting full exp
gained_exp /= trainer.currentTeam.length
end
growth_rate = pokemon.growth_rate
new_exp = growth_rate.add_exp(pokemon.exp, gained_exp)
pokemon.exp = new_exp
updated_team.push(pokemon)
end
trainer.currentTeam = updated_team
return trainer
end
def evolveRebattledTrainerPokemon(trainer)
updated_team = []
for pokemon in trainer.currentTeam
evolution_species = pokemon.check_evolution_on_level_up(false)
if evolution_species
trainer.log_evolution_event(pokemon.species,evolution_species)
trainer.set_pending_action(true)
pokemon.species = evolution_species if evolution_species
end
updated_team.push(pokemon)
end
trainer.currentTeam = updated_team
return trainer
end
def healRebattledTrainerPokemon(trainer)
for pokemon in trainer.currentTeam
pokemon.calc_stats
pokemon.heal
end
return trainer
end
def doNPCTrainerRematch(trainer)
return generateTrainerRematch(trainer)
end
def generateTrainerRematch(trainer)
trainer_data = GameData::Trainer.try_get(trainer.trainerType, trainer.trainerName, 0)
loseDialog = trainer_data&.loseText_rematch ? trainer_data.loseText_rematch : "..."
if customTrainerBattle(trainer.trainerName,trainer.trainerType, trainer.currentTeam,nil,loseDialog)
updated_trainer = makeRebattledTrainerTeamGainExp(trainer,true)
updated_trainer = healRebattledTrainerPokemon(updated_trainer)
else
updated_trainer =makeRebattledTrainerTeamGainExp(trainer,false)
end
updated_trainer.set_pending_action(false)
updated_trainer = evolveRebattledTrainerPokemon(updated_trainer)
trainer.increase_friendship(5)
return updated_trainer
end
def showPrerematchDialog()
event = pbMapInterpreter.get_character(0)
map_id = $game_map.map_id if map_id.nil?
trainer = getRebattledTrainer(event.id,map_id)
return "" if trainer.nil?
trainer_data = GameData::Trainer.try_get(trainer.trainerType, trainer.trainerName, 0)
all_previous_random_events = trainer.previous_random_events
if all_previous_random_events
previous_random_event = getBestMatchingPreviousRandomEvent(trainer_data, trainer.previous_random_events)
if previous_random_event
event_message_map = {
CATCH: trainer_data.preRematchText_caught,
EVOLVE: trainer_data.preRematchText_evolved,
FUSE: trainer_data.preRematchText_fused,
UNFUSE: trainer_data.preRematchText_unfused,
REVERSE: trainer_data.preRematchText_reversed
}
message_text = event_message_map[previous_random_event.eventType] || trainer_data.preRematchText
else
message_text = trainer_data.preRematchText
end
end
if previous_random_event
message_text = message_text.gsub("<CAUGHT_POKEMON>", getSpeciesRealName(previous_random_event.caught_pokemon).to_s)
message_text = message_text.gsub("<UNEVOLVED_POKEMON>", getSpeciesRealName(previous_random_event.unevolved_pokemon).to_s)
message_text = message_text.gsub("<EVOLVED_POKEMON>", getSpeciesRealName(previous_random_event.evolved_pokemon).to_s)
message_text = message_text.gsub("<HEAD_POKEMON>", getSpeciesRealName(previous_random_event.fusion_head_pokemon).to_s)
message_text = message_text.gsub("<BODY_POKEMON>", getSpeciesRealName(previous_random_event.fusion_body_pokemon).to_s)
message_text = message_text.gsub("<FUSED_POKEMON>", getSpeciesRealName(previous_random_event.fusion_fused_pokemon).to_s)
message_text = message_text.gsub("<UNREVERSED_POKEMON>", getSpeciesRealName(previous_random_event.unreversed_pokemon).to_s)
message_text = message_text.gsub("<REVERSED_POKEMON>", getSpeciesRealName(previous_random_event.reversed_pokemon).to_s)
message_text = message_text.gsub("<UNFUSED_POKEMON>", getSpeciesRealName(previous_random_event.unfused_pokemon).to_s)
else
message_text = trainer_data.preRematchText
end
if message_text
split_messages = message_text.split("<br>")
split_messages.each do |msg|
pbCallBub(2,event.id)
pbMessage(msg)
end
end
end

View File

@@ -0,0 +1,203 @@
class StartersSelectionScene
POKEBALL_LEFT_X = -20; POKEBALL_LEFT_Y = 70
POKEBALL_MIDDLE_X = 125; POKEBALL_MIDDLE_Y = 100
POKEBALL_RIGHT_X = 275; POKEBALL_RIGHT_Y = 70
TEXT_POSITION_X = 100
TEXT_POSITION_Y = 10
def initialize(starters = [])
@starters_species = starters
@starter_pokemon = []
@starters_species.each do |species|
@starter_pokemon.push(Pokemon.new(species,5))
end
@spritesLoader = BattleSpriteLoader.new
@shown_starter_species=nil
end
def initializeGraphics()
@background = displayPicture("Graphics/Pictures/Trades/hoenn_starter_bag_bg.png", -20, -20)
@background.z=0
@foreground = displayPicture("Graphics/Pictures/Trades/hoenn_starter_bag_foreground.png", -20, -20)
@foreground.z=999
@pokeball_closed_left = displayPicture("Graphics/Pictures/Trades/trade_pokeball_closed_1.png", POKEBALL_LEFT_X, POKEBALL_LEFT_Y)
@pokeball_closed_left.z=2
@pokeball_closed_middle = displayPicture("Graphics/Pictures/Trades/trade_pokeball_closed_2.png", POKEBALL_MIDDLE_X, POKEBALL_MIDDLE_Y)
@pokeball_closed_middle.z=100
@pokeball_closed_right = displayPicture("Graphics/Pictures/Trades/trade_pokeball_closed_3.png", POKEBALL_RIGHT_X, POKEBALL_RIGHT_Y)
@pokeball_closed_right.z=2
end
def updateOpenPokeballPosition
case @index
when 0
@shown_pokemon_x = POKEBALL_LEFT_X
@shown_pokemon_y = POKEBALL_LEFT_Y
when 1
@shown_pokemon_x = POKEBALL_MIDDLE_X
@shown_pokemon_y = POKEBALL_MIDDLE_Y
when 2
@shown_pokemon_x = POKEBALL_RIGHT_X
@shown_pokemon_y = POKEBALL_RIGHT_Y
end
end
def startScene
initializeGraphics
@index=nil
previous_index = nil
loop do
if @index
if Input.trigger?(Input::RIGHT)
previous_index = @index
@index+=1
@index = 0 if @index == @starters_species.length
end
if Input.trigger?(Input::LEFT)
previous_index = @index
@index-=1
@index = @starters_species.length-1 if @index < 0
end
if Input.trigger?(Input::UP) || Input.trigger?(Input::DOWN)
updateOpenPokeballPosition
updateStarterSelectionGraphics
end
if Input.trigger?(Input::USE)
if pbConfirmMessage(_INTL("Do you choose this Pokémon?"))
chosenPokemon = @starter_pokemon[@index]
@spritesLoader.registerSpriteSubstitution(@pif_sprite)
disposeGraphics
pbSet(VAR_HOENN_CHOSEN_STARTER_INDEX,@index)
return chosenPokemon
end
end
else
@index = 0 if Input.trigger?(Input::LEFT)
@index = 1 if Input.trigger?(Input::DOWN)
@index = 2 if Input.trigger?(Input::RIGHT)
end
if previous_index != @index
updateOpenPokeballPosition
updateStarterSelectionGraphics
previous_index = @index
end
Input.update
Graphics.update
end
end
def disposeGraphics()
@pokeball_closed_left.dispose
@pokeball_closed_middle.dispose
@pokeball_closed_right.dispose
@pokeball_open_back.dispose
@pokeball_open_front.dispose
@background.dispose
@foreground.dispose
@pokemon_name_overlay.dispose
@pokemon_category_overlay.dispose
@pokemonSpriteWindow.dispose
end
def updateClosedBallGraphicsVisibility
case @index
when 0
@pokeball_closed_left.visible=false
@pokeball_closed_middle.visible=true
@pokeball_closed_right.visible=true
when 1
@pokeball_closed_left.visible=true
@pokeball_closed_middle.visible=false
@pokeball_closed_right.visible=true
when 2
@pokeball_closed_left.visible=true
@pokeball_closed_middle.visible=true
@pokeball_closed_right.visible=false
else
@pokeball_closed_left.visible=true
@pokeball_closed_middle.visible=true
@pokeball_closed_right.visible=true
end
end
def updateStarterSelectionGraphics()
pbSEPlay("GUI storage pick up", 80, 100)
updateClosedBallGraphicsVisibility
@pokeball_open_back.dispose if @pokeball_open_back
@pokeball_open_front.dispose if @pokeball_open_front
@shown_starter_species = @starters_species[@index]
updateOpenPokeballPosition
@pokeball_open_back = displayPicture("Graphics/Pictures/Trades/trade_pokeball_open_back",@shown_pokemon_x, @shown_pokemon_y,2)
@pokeball_open_front = displayPicture("Graphics/Pictures/Trades/trade_pokeball_open_front",@shown_pokemon_x, @shown_pokemon_y,50)
updatePokemonSprite
end
def updatePokemonSprite()
@pif_sprite = @spritesLoader.get_pif_sprite_from_species(@shown_starter_species.species)
sprite_bitmap = @spritesLoader.load_pif_sprite_directly(@pif_sprite)
pokemon = @starter_pokemon[@index]
if pokemon.shiny?
sprite_bitmap.bitmap.update_shiny_cache(pokemon.id_number, "")
sprite_bitmap.shiftAllColors(pokemon.id_number, pokemon.bodyShiny?, pokemon.headShiny?)
end
@pokemonSpriteWindow.dispose if @pokemonSpriteWindow
@pokemonSpriteWindow = PictureWindow.new(sprite_bitmap.bitmap)
@pokemonSpriteWindow.opacity = 0
@pokemonSpriteWindow.z = 2
@pokemonSpriteWindow.x = @shown_pokemon_x
@pokemonSpriteWindow.y = @shown_pokemon_y-10
updateText
end
def updateText
@pokemon_name_overlay.dispose if @pokemon_name_overlay
@pokemon_category_overlay.dispose if @pokemon_category_overlay
pokemon_name = "#{@shown_starter_species.real_name}"
pokemon_category = "#{@shown_starter_species.real_category} Pokémon"
title_position_y = TEXT_POSITION_Y
subtitle_position_y = TEXT_POSITION_Y + 30
text_x_offset=-100
label_base_color = Color.new(88,88,80)
label_shadow_color = Color.new(168,184,184)
title_base_color = Color.new(248, 248, 248)
title_shadow_color = Color.new(104, 104, 104)
@pokemon_name_overlay = BitmapSprite.new(Graphics.width, Graphics.height, @viewport).bitmap
@pokemon_category_overlay = BitmapSprite.new(Graphics.width, Graphics.height, @viewport).bitmap
@pokemon_name_overlay.font.size = 50
@pokemon_name_overlay.font.name = MessageConfig.pbGetSmallFontName
@pokemon_category_overlay.font.size = 36
@pokemon_category_overlay.font.name = MessageConfig.pbGetSmallFontName
pbDrawTextPositions(@pokemon_name_overlay, [[pokemon_name, (Graphics.width/2)+text_x_offset, title_position_y, 2, title_base_color, title_shadow_color]])
pbDrawTextPositions(@pokemon_category_overlay,[[pokemon_category, (Graphics.width/2)+text_x_offset, subtitle_position_y, 2, label_base_color, label_shadow_color]])
end
end

View File

@@ -0,0 +1,85 @@
class BattledTrainer
TRAINER_CLASS_FAVORITE_TYPES =
{
AROMALADY: [:GRASS, :FAIRY],
BEAUTY: [:FAIRY, :WATER, :NORMAL, :GRASS],
BIKER: [:POISON, :DARK],
BIRDKEEPER: [:FLYING, :NORMAL],
BUGCATCHER: [:BUG],
BURGLAR: [:FIRE, :DARK],
CHANNELER: [:GHOST, :PSYCHIC],
CUEBALL: [:FIGHTING],
ENGINEER: [:ELECTRIC, :STEEL],
FISHERMAN: [:WATER],
GAMBLER: [:NORMAL, :PSYCHIC],
GENTLEMAN: [:NORMAL, :STEEL],
HIKER: [:ROCK, :GROUND],
JUGGLER: [:PSYCHIC, :GHOST],
LADY: [:FAIRY, :NORMAL],
PAINTER: [:NORMAL, :PSYCHIC],
POKEMANIAC: [:DRAGON, :GROUND],
POKEMONBREEDER: [:NORMAL, :GRASS],
PROFESSOR: [:NORMAL, :PSYCHIC],
ROCKER: [:ELECTRIC, :FIRE],
RUINMANIAC: [:GROUND, :ROCK],
SAILOR: [:WATER, :FIGHTING],
SCIENTIST: [:ELECTRIC, :STEEL, :POISON],
SUPERNERD: [:ELECTRIC, :PSYCHIC, :STEEL],
TAMER: [:NORMAL, :DARK],
BLACKBELT: [:FIGHTING],
CRUSHGIRL: [:FIGHTING],
CAMPER: [:BUG, :NORMAL, :GRASS],
PICNICKER: [:GRASS, :NORMAL],
COOLTRAINER_M: [:DRAGON, :STEEL, :FIRE],
COOLTRAINER_F: [:ICE, :PSYCHIC, :FAIRY],
YOUNGSTER: [:NORMAL, :BUG],
LASS: [:NORMAL, :FAIRY],
POKEMONRANGER_M: [:GRASS, :GROUND],
POKEMONRANGER_F: [:GRASS, :WATER],
PSYCHIC_M: [:PSYCHIC, :GHOST],
PSYCHIC_F: [:PSYCHIC, :FAIRY],
SWIMMER_M: [:WATER],
SWIMMER_F: [:WATER, :ICE],
SWIMMER2_M: [:WATER],
SWIMMER2_F: [:WATER],
TUBER_M: [:WATER],
TUBER_F: [:WATER],
TUBER2_M: [:WATER],
TUBER2_F: [:WATER],
COOLCOUPLE: [:FIRE, :ICE],
CRUSHKIN: [:FIGHTING],
SISANDBRO: [:WATER, :GROUND],
TWINS: [:FAIRY, :NORMAL],
YOUNGCOUPLE: [:NORMAL, :PSYCHIC],
SOCIALITE: [:FAIRY, :NORMAL],
BUGCATCHER_F: [:BUG],
ROUGHNECK: [:DARK, :FIGHTING],
TEACHER: [:PSYCHIC, :NORMAL],
PRESCHOOLER_M: [:NORMAL],
PRESCHOOLER_F: [:FAIRY, :NORMAL],
HAUNTEDGIRL_YOUNG: [:GHOST],
HAUNTEDGIRL: [:GHOST, :DARK],
CLOWN: [:PSYCHIC, :FAIRY],
NURSE: [:NORMAL, :FAIRY],
WORKER: [:STEEL, :GROUND],
COOLTRAINER_M2: [:FIGHTING, :STEEL],
COOLTRAINER_F2: [:PSYCHIC, :ICE],
FARMER: [:GRASS, :GROUND, :NORMAL],
PYROMANIAC: [:FIRE],
KIMONOGIRL: [:FAIRY, :PSYCHIC, :GHOST],
SAGE: [:PSYCHIC, :GHOST],
PLAYER: [:ICE, :FIGHTING],
POLICE: [:DARK, :FIGHTING],
SKIER_F: [:ICE],
DELIVERYMAN: [:NORMAL],
RICHBOY: [],
SCHOOLBOY: [],
SCHOOLGIRL: [],
TEAM_AQUA_GRUNT_M: [],
TEAM_AQUA_GRUNT_F: [],
TEAM_MAGMA_GRUNT_M: [],
TEAM_MAGMA_GRUNT_F: [],
TEAM_MAGMAQUA_GRUNT_M: [],
TEAM_MAGMAQUA_GRUNT_F: [],
}
end

View File

@@ -0,0 +1,85 @@
class BattledTrainer
FRIENDSHIP_LEVELS = {
AROMALADY: [10, 25, 45],
BEAUTY: [15, 30, 60],
BIKER: [20, 40, 80],
BIRDKEEPER: [10, 25, 50],
BUGCATCHER: [8, 20, 35],
BURGLAR: [20, 45, 90],
CHANNELER: [15, 35, 70],
CUEBALL: [18, 38, 80],
ENGINEER: [15, 30, 65],
FISHERMAN: [12, 28, 55],
GAMBLER: [15, 30, 60],
GENTLEMAN: [12, 30, 70],
HIKER: [10, 25, 50],
JUGGLER: [15, 30, 65],
LADY: [15, 30, 60],
PAINTER: [8, 22, 40],
POKEMANIAC: [18, 35, 70],
POKEMONBREEDER: [8, 18, 35],
PROFESSOR: [10, 30, 60],
ROCKER: [15, 35, 70],
RUINMANIAC: [15, 35, 65],
SAILOR: [12, 28, 60],
SCIENTIST: [15, 35, 70],
SUPERNERD: [14, 30, 65],
TAMER: [15, 35, 75],
BLACKBELT: [20, 45, 90],
CRUSHGIRL: [18, 40, 85],
CAMPER: [10, 22, 40],
PICNICKER: [10, 22, 40],
COOLTRAINER_M: [20, 45, 95],
COOLTRAINER_F: [20, 45, 95],
YOUNGSTER: [10, 25, 40],
LASS: [10, 22, 38],
POKEMONRANGER_M:[15, 35, 80],
POKEMONRANGER_F:[15, 35, 80],
PSYCHIC_M: [15, 35, 70],
PSYCHIC_F: [15, 35, 70],
SWIMMER_M: [10, 25, 50],
SWIMMER_F: [10, 25, 50],
SWIMMER2_M: [12, 28, 55],
SWIMMER2_F: [12, 28, 55],
TUBER_M: [6, 15, 30],
TUBER_F: [6, 15, 30],
TUBER2_M: [6, 15, 30],
TUBER2_F: [6, 15, 30],
COOLCOUPLE: [15, 35, 80],
CRUSHKIN: [15, 35, 80],
SISANDBRO: [10, 25, 50],
TWINS: [10, 25, 50],
YOUNGCOUPLE: [15, 30, 65],
SOCIALITE: [12, 30, 70],
BUGCATCHER_F: [8, 20, 35],
ROUGHNECK: [18, 38, 85],
TEACHER: [12, 28, 60],
PRESCHOOLER_M: [5, 12, 25],
PRESCHOOLER_F: [5, 12, 25],
HAUNTEDGIRL_YOUNG: [10, 25, 55],
HAUNTEDGIRL: [12, 30, 65],
CLOWN: [10, 25, 50],
NURSE: [8, 20, 35],
WORKER: [12, 30, 65],
COOLTRAINER_M2: [22, 50, 100],
COOLTRAINER_F2: [22, 50, 100],
FARMER: [10, 25, 50],
PYROMANIAC: [20, 45, 90],
KIMONOGIRL: [12, 30, 60],
SAGE: [15, 30, 65],
PLAYER: [12, 30, 60],
POLICE: [20, 45, 90],
SKIER_F: [12, 28, 55],
DELIVERYMAN: [8, 20, 40],
RICHBOY: [15, 35, 70],
SCHOOLBOY: [10, 22, 40],
SCHOOLGIRL: [10, 22, 40],
TEAM_AQUA_GRUNT_M: [25, 60, 100],
TEAM_AQUA_GRUNT_F: [25, 60, 100],
TEAM_MAGMA_GRUNT_M: [25, 60, 100],
TEAM_MAGMA_GRUNT_F: [25, 60, 100],
TEAM_MAGMAQUA_GRUNT_M: [25, 60, 100],
TEAM_MAGMAQUA_GRUNT_F: [25, 60, 100],
}
end

View File

@@ -0,0 +1,118 @@
#####
# Util methods
#####
####
# Methods to be called from events
####
#actionType :
# :BATTLE
# :TRADE
# :PARTNER
def doPostBattleAction(actionType)
event = pbMapInterpreter.get_character(0)
map_id = $game_map.map_id if map_id.nil?
trainer = getRebattledTrainer(event.id,map_id)
trainer.clear_previous_random_events()
return if !trainer
case actionType
when :BATTLE
trainer = doNPCTrainerRematch(trainer)
when :TRADE
trainer = doNPCTrainerTrade(trainer)
when :PARTNER
partnerWithTrainer(event.id,map_id,trainer)
end
updateRebattledTrainer(event.id,map_id,trainer)
end
def setTrainerFriendship(trainer)
params = ChooseNumberParams.new
params.setRange(0,100)
params.setDefaultValue($game_map.map_id)
number = pbMessageChooseNumber("Frienship (0-100)?",params)
trainer.friendship = number
trainer.increase_friendship(0)
return trainer
end
#party: array of pokemon team
# [[:SPECIES,level], ... ]
#
#def customTrainerBattle(trainerName, trainerType, party_array, default_level=50, endSpeech="", sprite_override=nil,custom_appearance=nil)
def postBattleActionsMenu()
rematchCommand = "Rematch"
tradeCommand = "Trade Offer"
partnerCommand = "Partner up"
cancelCommand = "See ya!"
updateTeamDebugCommand = "(Debug) Simulate random event"
resetTrainerDebugCommand = "(Debug) Reset trainer"
setFriendshipDebugCommand = "(Debug) Set Friendship"
printTrainerTeamDebugCommand = "(Debug) Print team"
event = pbMapInterpreter.get_character(0)
map_id = $game_map.map_id if map_id.nil?
trainer = getRebattledTrainer(event.id,map_id)
options = []
options << rematchCommand
options << tradeCommand if trainer.friendship_level >= 1
options << partnerCommand if trainer.friendship_level >= 3
options << updateTeamDebugCommand if $DEBUG
options << resetTrainerDebugCommand if $DEBUG
options << setFriendshipDebugCommand if $DEBUG
options << printTrainerTeamDebugCommand if $DEBUG
options << cancelCommand
trainer = applyTrainerRandomEvents(trainer)
showPrerematchDialog
choice = optionsMenu(options,options.find_index(cancelCommand),options.find_index(cancelCommand))
case options[choice]
when rematchCommand
doPostBattleAction(:BATTLE)
when tradeCommand
doPostBattleAction(:TRADE)
when partnerCommand
doPostBattleAction(:PARTNER)
when updateTeamDebugCommand
echoln("")
echoln "---------------"
makeRebattledTrainerTeamGainExp(trainer,true)
evolveRebattledTrainerPokemon(trainer)
applyTrainerRandomEvents(trainer)
when resetTrainerDebugCommand
resetTrainerRebattle(event.id,map_id)
when setFriendshipDebugCommand
trainer = getRebattledTrainer(event.id,map_id)
trainer = setTrainerFriendship(trainer)
updateRebattledTrainer(event.id,map_id,trainer)
when printTrainerTeamDebugCommand
trainer = getRebattledTrainer(event.id,map_id)
printNPCTrainerCurrentTeam(trainer)
when cancelCommand
else
return
end
end

View File

@@ -0,0 +1,13 @@
COMMON_EVENT_TRAINER_REMATCH_PARTNER = 200
def partnerWithTrainer(eventId, mapID, trainer)
Kernel.pbAddDependency2(eventId,trainer.trainerName,COMMON_EVENT_TRAINER_REMATCH_PARTNER)
pbCancelVehicles
originalTrainer = pbLoadTrainer(trainer.trainerType, trainer.trainerName, 0)
Events.onTrainerPartyLoad.trigger(nil, originalTrainer)
for i in trainer.currentTeam
i.owner = Pokemon::Owner.new_from_trainer(originalTrainer)
i.calc_stats
end
$PokemonGlobal.partner = [trainer.trainerType, trainer.trainerName, 0, trainer.currentTeam]
end

View File

@@ -0,0 +1,195 @@
# frozen_string_literal: true
def printNPCTrainerCurrentTeam(trainer)
team_string = "["
trainer.currentTeam.each do |pokemon|
name= get_pokemon_readable_internal_name(pokemon)
level = pokemon.level
formatted_info = "#{name} (lv.#{level}), "
team_string += formatted_info
end
team_string += "]"
echoln "Trainer's current team is: #{team_string}"
end
def applyTrainerRandomEvents(trainer)
if trainer.has_pending_action
echoln "Trainer has pending action"
end
return trainer if trainer.has_pending_action
trainer.clear_previous_random_events
#time_passed = trainer.getTimeSinceLastAction
#return trainer if time_passed < TIME_FOR_RANDOM_EVENTS
# Weighted chances out of 10
weighted_events = [
[:CATCH, 3],
[:FUSE, 6],
[:REVERSE, 1],
[:UNFUSE, 20]
]
# Create a flat array of events based on weight
event_pool = weighted_events.flat_map { |event, weight| [event] * weight }
selected_event = event_pool.sample
if selected_event
echoln "Trying to do random event: #{selected_event}"
end
return trainer if selected_event.nil?
case selected_event
when :CATCH
trainer = catch_new_team_pokemon(trainer)
when :FUSE
trainer = fuse_random_team_pokemon(trainer)
when :UNFUSE
trainer = unfuse_random_team_pokemon(trainer)
when :REVERSE
trainer = reverse_random_team_pokemon(trainer)
end
trainer.set_pending_action(true)
printNPCTrainerCurrentTeam(trainer)
return trainer
end
def chooseEncounterType(trainerClass)
water_trainer_classes = [:SWIMMER_F, :SWIMMER_M, :FISHERMAN]
if water_trainer_classes.include?(trainerClass )
chance_of_land_encounter = 1
chance_of_surf_encounter= 5
chance_of_cave_encounter = 1
chance_of_fishing_encounter = 5
else
chance_of_land_encounter = 5
chance_of_surf_encounter= 1
chance_of_cave_encounter = 5
chance_of_fishing_encounter = 1
end
if pbCheckHiddenMoveBadge(Settings::BADGE_FOR_SURF, false)
chance_of_surf_encounter =0
chance_of_fishing_encounter = 0
end
possible_encounter_types = []
if $PokemonEncounters.has_land_encounters?
possible_encounter_types += [:Land] * chance_of_land_encounter
end
if $PokemonEncounters.has_cave_encounters?
possible_encounter_types += [:Cave] * chance_of_cave_encounter
end
if $PokemonEncounters.has_water_encounters?
possible_encounter_types += [:GoodRod] * chance_of_fishing_encounter
possible_encounter_types += [:Water] * chance_of_surf_encounter
end
echoln "possible_encounter_types: #{possible_encounter_types}"
return getTimeBasedEncounter(possible_encounter_types.sample)
end
def getTimeBasedEncounter(encounter_type)
time = pbGetTimeNow
return $PokemonEncounters.find_valid_encounter_type_for_time(encounter_type, time)
end
def catch_new_team_pokemon(trainer)
return trainer if trainer.currentTeam.length >= 6
encounter_type = chooseEncounterType(trainer.trainerType)
return trainer if !encounter_type
echoln "Catching a pokemon via encounter_type #{encounter_type}"
wild_pokemon = $PokemonEncounters.choose_wild_pokemon(encounter_type)
echoln wild_pokemon
if wild_pokemon
trainer.currentTeam << Pokemon.new(wild_pokemon[0],wild_pokemon[1])
trainer.log_catch_event(wild_pokemon[0])
end
return trainer
end
def reverse_random_team_pokemon(trainer)
eligible_pokemon = trainer.list_team_fused_pokemon
return trainer if eligible_pokemon.length < 1
return trainer if trainer.currentTeam.length > 5
pokemon_to_reverse = eligible_pokemon.sample
old_species = pokemon_to_reverse.species
trainer.currentTeam.delete(pokemon_to_reverse)
body_pokemon = get_body_species_from_symbol(pokemon_to_reverse.species)
head_pokemon = get_head_species_from_symbol(pokemon_to_reverse.species)
pokemon_to_reverse.species = getFusedPokemonIdFromSymbols(head_pokemon,body_pokemon)
trainer.currentTeam.push(pokemon_to_reverse)
trainer.log_reverse_event(old_species,pokemon_to_reverse.species)
return trainer
end
def unfuse_random_team_pokemon(trainer)
eligible_pokemon = trainer.list_team_fused_pokemon
return trainer if eligible_pokemon.length < 1
return trainer if trainer.currentTeam.length > 5
pokemon_to_unfuse = eligible_pokemon.sample
echoln pokemon_to_unfuse.owner.name
echoln trainer.trainerName
return trainer if pokemon_to_unfuse.owner.name != trainer.trainerName
body_pokemon = get_body_id_from_symbol(pokemon_to_unfuse.species)
head_pokemon = get_head_id_from_symbol(pokemon_to_unfuse.species)
level = calculateUnfuseLevelOldMethod(pokemon_to_unfuse,false)
trainer.currentTeam.delete(pokemon_to_unfuse)
trainer.currentTeam.push(Pokemon.new(body_pokemon,level))
trainer.currentTeam.push(Pokemon.new(head_pokemon,level))
trainer.log_unfusion_event(pokemon_to_unfuse.species, body_pokemon, head_pokemon)
return trainer
end
def fuse_random_team_pokemon(trainer)
eligible_pokemon = trainer.list_team_unfused_pokemon
return trainer if eligible_pokemon.length < 2
pokemon_to_fuse = eligible_pokemon.sample(2)
body_pokemon = pokemon_to_fuse[0]
head_pokemon = pokemon_to_fuse[1]
fusion_species = getFusedPokemonIdFromSymbols(body_pokemon.species,head_pokemon.species)
level = (body_pokemon.level + head_pokemon.level)/2
fused_pokemon = Pokemon.new(fusion_species,level)
trainer.currentTeam.delete(body_pokemon)
trainer.currentTeam.delete(head_pokemon)
trainer.currentTeam.push(fused_pokemon)
trainer.log_fusion_event(body_pokemon.species,head_pokemon.species,fusion_species)
return trainer
end
def getBestMatchingPreviousRandomEvent(trainer_data, previous_events)
return nil if trainer_data.nil? || previous_events.nil?
priority = [:CATCH, :EVOLVE, :FUSE, :UNFUSE, :REVERSE]
event_message_map = {
CATCH: trainer_data.preRematchText_caught,
EVOLVE: trainer_data.preRematchText_evolved,
FUSE: trainer_data.preRematchText_fused,
UNFUSE: trainer_data.preRematchText_unfused,
REVERSE: trainer_data.preRematchText_reversed
}
sorted_events = previous_events.sort_by do |event|
priority.index(event.eventType) || Float::INFINITY
end
sorted_events.find { |event| event_message_map[event.eventType] }
end

View File

@@ -0,0 +1,273 @@
TRAINER_CLASS_FAVORITE_TYPES =
{
AROMALADY: [:GRASS, :FAIRY],
BEAUTY: [:FAIRY, :WATER, :NORMAL, :GRASS],
BIKER: [:POISON, :DARK],
BIRDKEEPER: [:FLYING, :NORMAL],
BUGCATCHER: [:BUG],
BURGLAR: [:FIRE, :DARK],
CHANNELER: [:GHOST, :PSYCHIC],
CUEBALL: [:FIGHTING, :STEEL],
ENGINEER: [:ELECTRIC, :STEEL],
FISHERMAN: [:WATER],
GAMBLER: [:NORMAL, :PSYCHIC],
GENTLEMAN: [:NORMAL, :STEEL],
HIKER: [:ROCK, :GROUND],
JUGGLER: [:PSYCHIC, :GHOST, :NORMAL, :POISON],
LADY: [:FAIRY, :NORMAL],
PAINTER: [:NORMAL, :PSYCHIC, :GRASS],
POKEMANIAC: [:DRAGON, :GROUND],
POKEMONBREEDER: [:NORMAL, :GRASS],
PROFESSOR: [:NORMAL, :PSYCHIC],
ROCKER: [:ELECTRIC, :FIRE],
RUINMANIAC: [:GROUND, :ROCK, :PSYCHIC],
SAILOR: [:WATER, :FIGHTING],
SCIENTIST: [:ELECTRIC, :STEEL, :POISON],
SUPERNERD: [:ELECTRIC, :PSYCHIC, :STEEL],
TAMER: [:NORMAL, :DARK],
BLACKBELT: [:FIGHTING],
CRUSHGIRL: [:FIGHTING],
CAMPER: [:BUG, :NORMAL, :GRASS],
PICNICKER: [:GRASS, :NORMAL],
COOLTRAINER_M: [:DRAGON, :STEEL, :FIRE],
COOLTRAINER_F: [:ICE, :PSYCHIC, :FAIRY],
YOUNGSTER: [:NORMAL, :BUG, :GRASS, :FLYING],
LASS: [:NORMAL, :FAIRY],
POKEMONRANGER_M: [:GRASS, :GROUND],
POKEMONRANGER_F: [:GRASS, :WATER],
PSYCHIC_M: [:PSYCHIC, :GHOST],
PSYCHIC_F: [:PSYCHIC, :FAIRY],
SWIMMER_M: [:WATER, :ICE],
SWIMMER_F: [:WATER, :ICE],
SWIMMER2_M: [:WATER],
SWIMMER2_F: [:WATER],
TUBER_M: [:WATER],
TUBER_F: [:WATER],
TUBER2_M: [:WATER],
TUBER2_F: [:WATER],
COOLCOUPLE: [:FIRE, :ICE],
CRUSHKIN: [:FIGHTING],
SISANDBRO: [:WATER, :GROUND],
TWINS: [:FAIRY, :NORMAL],
YOUNGCOUPLE: [:NORMAL, :PSYCHIC],
SOCIALITE: [:FAIRY, :NORMAL],
BUGCATCHER_F: [:BUG],
ROUGHNECK: [:DARK, :FIGHTING],
TEACHER: [:PSYCHIC, :NORMAL],
PRESCHOOLER_M: [:NORMAL, :BUG],
PRESCHOOLER_F: [:FAIRY, :NORMAL],
HAUNTEDGIRL_YOUNG: [:GHOST],
HAUNTEDGIRL: [:GHOST, :DARK],
CLOWN: [:PSYCHIC, :FAIRY],
NURSE: [:NORMAL, :FAIRY],
WORKER: [:STEEL, :GROUND],
COOLTRAINER_M2: [:FIGHTING, :STEEL],
COOLTRAINER_F2: [:PSYCHIC, :ICE],
FARMER: [:GRASS, :GROUND, :NORMAL],
PYROMANIAC: [:FIRE],
KIMONOGIRL: [:FAIRY, :PSYCHIC, :GHOST],
SAGE: [:PSYCHIC, :GHOST],
PLAYER: [:ICE, :FIGHTING],
POLICE: [:DARK, :FIGHTING],
SKIER_F: [:ICE],
DELIVERYMAN: [:NORMAL],
}
#Higher values: pickier
TRAINER_CLASS_PICKINESS = {
AROMALADY: 1.8,
BEAUTY: 2.2,
BIKER: 1.2,
BIRDKEEPER: 1.4,
BUGCATCHER: 1.1,
BURGLAR: 1.3,
CHANNELER: 1.7,
CUEBALL: 1.3,
ENGINEER: 2.0,
FISHERMAN: 1.4,
GAMBLER: 1.5,
GENTLEMAN: 2.3,
HIKER: 1.5,
JUGGLER: 1.8,
LADY: 2.4,
PAINTER: 2.0,
POKEMANIAC: 1.6,
POKEMONBREEDER: 1.9,
PROFESSOR: 2.5,
ROCKER: 1.4,
RUINMANIAC: 1.5,
SAILOR: 1.3,
SCIENTIST: 2.0,
SUPERNERD: 1.9,
TAMER: 1.5,
BLACKBELT: 1.7,
CRUSHGIRL: 1.6,
CAMPER: 1.2,
PICNICKER: 1.2,
COOLTRAINER_M: 2.4,
COOLTRAINER_F: 2.4,
YOUNGSTER: 0.9,
LASS: 1.0,
POKEMONRANGER_M: 2.0,
POKEMONRANGER_F: 2.0,
PSYCHIC_M: 2.0,
PSYCHIC_F: 2.0,
SWIMMER_M: 1.0,
SWIMMER_F: 1.0,
SWIMMER2_M: 1.0,
SWIMMER2_F: 1.0,
TUBER_M: 0.8,
TUBER_F: 0.8,
TUBER2_M: 0.8,
TUBER2_F: 0.8,
COOLCOUPLE: 2.1,
CRUSHKIN: 1.7,
SISANDBRO: 1.3,
TWINS: 1.0,
YOUNGCOUPLE: 1.6,
SOCIALITE: 2.3,
BUGCATCHER_F: 1.1,
ROUGHNECK: 1.4,
TEACHER: 2.0,
PRESCHOOLER_M: 0.6,
PRESCHOOLER_F: 0.6,
HAUNTEDGIRL_YOUNG: 1.3,
HAUNTEDGIRL: 1.7,
CLOWN: 1.5,
NURSE: 2.0,
WORKER: 1.6,
COOLTRAINER_M2: 2.4,
COOLTRAINER_F2: 2.4,
FARMER: 1.5,
PYROMANIAC: 1.6,
KIMONOGIRL: 2.2,
SAGE: 2.1,
PLAYER: 1.0,
POLICE: 1.8,
SKIER_F: 1.6,
DELIVERYMAN: 1.3
}
def evaluate_pokemon_worth(pkmn, compare_level: nil)
species_data = pkmn.species_data
return 0 unless species_data
level = pkmn.level
level_diff = compare_level ? (level - compare_level) : 0
level_score = level * 2 + [level_diff, 0].max * 1.5 # bonus if player's level is higher
base_stats_score = species_data.base_stats.values.sum / 10.0
rarity_score = (255 - species_data.catch_rate) / 5.0
iv_score = (pkmn.iv&.values&.sum || 0) / 4.0
shiny_score = pkmn.shiny? ? 50 : 0
fusion_bonus = pkmn.isFusion? ? 40 : 0
score = level_score +
base_stats_score +
rarity_score +
iv_score +
shiny_score +
fusion_bonus
echoln("#{pkmn.name} - Score : #{score}")
return score
end
def offerPokemonForTrade(player_pokemon, npc_party, trainer_class)
player_score = evaluate_pokemon_worth(player_pokemon)
pickiness = TRAINER_CLASS_PICKINESS[trainer_class] || 1.0
# Evaluate all NPC Pokémon scores
npc_scores = npc_party.map do |npc_pkmn|
[npc_pkmn, evaluate_pokemon_worth(npc_pkmn, compare_level: player_pokemon.level)]
end
best_npc_pokemon, best_score = npc_scores.max_by { |_, score| score }
return best_npc_pokemon if player_score > best_score
max_difference = [player_score, 100].min * pickiness
candidates = npc_scores.select do |npc_pkmn, npc_score|
(npc_score - player_score).abs <= max_difference
end
return nil if candidates.empty?
candidates.min_by do |_, npc_score|
(npc_score - player_score).abs
end.first
end
def doNPCTrainerTrade(trainer)
echoln trainer.getTimeSinceLastTrade
if trainer.isNextTradeReady?
pbMessage(_INTL("The trainer is not ready to trade yet. Wait a little bit before you make your offer."))
return trainer
end
return generateTrainerTradeOffer(trainer)
end
#prefered type depends on the trainer class
#
def generateTrainerTradeOffer(trainer)
bg_image_id=20
wanted_type = trainer.favorite_type
wanted_type = :NORMAL if !wanted_type
wanted_type_name = GameData::Type.get(wanted_type).real_name
trainerClassName = GameData::TrainerType.get(trainer.trainerType).real_name
pbMessage(_INTL("#{trainerClassName} #{trainer.trainerName} is looking for \\C[1]#{wanted_type_name}-type Pokémon\\C[0]. Which Pokémon do you want to trade?."),)
pbChoosePokemon(1,2,
proc {|pokemon|
pokemon.hasType?(wanted_type)
})
chosen_index = pbGet(1)
echoln pbGet(1)
if chosen_index && chosen_index >= 0
chosen_pokemon = $Trainer.party[chosen_index]
offered_pokemon = offerPokemonForTrade(chosen_pokemon,trainer.currentTeam,trainer.trainerType)
if !offered_pokemon
pbMessage(_INTL("#{trainerClassName} #{trainer.trainerName} does not want to trade..."))
return trainer
end
pif_sprite = BattleSpriteLoader.new.get_pif_sprite_from_species(offered_pokemon.species)
pif_sprite.dump_info()
message = _INTL("#{trainerClassName} #{trainer.trainerName} is offering #{offered_pokemon.name} (Level #{offered_pokemon.level}) for your #{chosen_pokemon.name}.")
showPokemonInPokeballWithMessage(pif_sprite, message)
if pbConfirmMessage(_INTL("Trade away #{chosen_pokemon.name} for #{trainerClassName} #{trainer.trainerName}'s #{offered_pokemon.name}?"))
pbStartTrade(chosen_index, offered_pokemon,offered_pokemon.name,trainer.trainerName,0)
updated_party = trainer.currentTeam
updated_party.delete(offered_pokemon)
updated_party << chosen_pokemon.clone
trainer.previous_trade_timestamp= Time.now
trainer.increase_friendship(20)
return trainer
end
end
return trainer
#todo
#
# NPC says "I'm looking for X or Y tyflpe Pokemon (prefered Pokemon can be determined when initializing from a pool of types that depends on the trainer class)
# Also possible to pass a list of specific Pokemon in trainers.txt that the trainer will ask for instead if it's defined
#
# you select one of your Pokemon and he gives you one for it
# prioritize recently caught pokemon
# prioritive weaker Pokemon
#
#Assign a score to each Pokemon in trainer's team. calculate the same score for trainer's pokemon - select which
# one is closer
#
# NPC says "I can offer A in exchange for your B.
# -Yes -> Trade, update trainer team to put the player's pokemon in there
# Cannot trade again with the same trainer for 5 minutes
# "You just traded with this trainer. Wait a bit before you make another offer
# -No
trainer.set_pending_action(false) if trainer
return trainer
end