#===============================================================================
# * Box Ranch System - Optimized Version
#===============================================================================
# Configuration is loaded automatically from config.rb
# Constants
module BoxRanchConstants
RANCH_EVENT_ID_START = 1000 # Starting ID for ranch events
DEFAULT_SPRITE = "Graphics/Characters/Followers/000"
end
# Helper function to get the correct event name based on configuration
def get_ranch_event_name(species, in_water = false)
if BoxRanchConfig::USE_REFLECTION_NAMES
prefix = in_water ? BoxRanchConfig::WATER_EVENT_PREFIX_REFLECTION : BoxRanchConfig::LAND_EVENT_PREFIX_REFLECTION
else
prefix = in_water ? BoxRanchConfig::WATER_EVENT_PREFIX : BoxRanchConfig::LAND_EVENT_PREFIX
end
return "#{prefix}_#{species}"
end
# Helper function to load the correct Pokémon graphic
def box_ranch_sprite_filename(species, form = 0, gender = 0, shiny = false, in_water = false, levitates = false)
folder_extra = if in_water
shiny ? "Swimming Shiny" : "Swimming"
elsif levitates
shiny ? "Levitates Shiny" : "Levitates"
else
shiny ? "Followers shiny" : "Followers"
end
# Try to get the graphic file
fname = nil
begin
fname = GameData::Species.check_graphic_file("Graphics/Characters/", species, form, gender, shiny, false, folder_extra)
rescue
# Ignore and use fallback
end
# Fallback chain
if nil_or_empty?(fname)
species_name = species.to_s
fname = "Graphics/Characters/#{folder_extra}/#{species_name}"
if !pbResolveBitmap(fname)
# Try standard Followers folder
fname = "Graphics/Characters/Followers/#{species_name}"
fname = BoxRanchConstants::DEFAULT_SPRITE if !pbResolveBitmap(fname)
end
end
return fname
end
# Helper function to check if a Pokémon is a Water type
def is_water_pokemon?(pokemon)
return false if !pokemon
return pokemon.types.include?(:WATER)
end
# Helper function to check if a Pokémon can levitate
def is_levitating_pokemon?(pokemon)
return false if !pokemon
levitating_species = [
:GASTLY, :HAUNTER, :GENGAR, :KOFFING, :WEEZING, :PORYGON,
:MISDREAVUS, :UNOWN, :NATU, :XATU, :ESPEON, :MURKROW, :WOBBUFFET,
:GIRAFARIG, :PINECO, :DUNSPARCE, :GLIGAR, :LUGIA, :CELEBI,
:DUSTOX, :SHEDINJA, :NINJASK, :WHISMUR, :LOUDRED, :EXPLOUD,
:VOLBEAT, :ILLUMISE, :FLYGON, :BALTOY, :CLAYDOL, :LUNATONE, :SOLROCK,
:CASTFORM, :SHUPPET, :BANETTE, :DUSKULL, :CHIMECHO, :GLALIE, :DEOXYS,
:BRONZOR, :BRONZONG, :DRIFLOON, :DRIFBLIM, :CHINGLING,
:SPIRITOMB, :CARNIVINE, :ROTOM, :UXIE, :MESPRIT, :AZELF,
:GIRATINA, :CRESSELIA, :DARKRAI,
:YAMASK, :SIGILYPH, :SOLOSIS, :DUOSION, :REUNICLUS, :VANILLITE,
:VANILLISH, :VANILLUXE, :EMOLGA, :TYNAMO, :EELEKTRIK, :EELEKTROSS,
:CRYOGONAL, :HYDREIGON, :VOLCARONA,
:VIKAVOLT, :CUTIEFLY, :RIBOMBEE, :COMFEY, :DHELMISE, :LUNALA,
:NIHILEGO, :CELESTEELA, :KARTANA, :XURKITREE, :PHEROMOSA
]
levitating_abilities = [:LEVITATE, :AIRLOCK, :MAGNETRISE, :TELEPATHY]
return levitating_species.include?(pokemon.species) ||
levitating_abilities.include?(pokemon.ability.id)
end
# Helper function to check if a tile is water
def is_water_tile?(x, y)
return false if !$game_map
terrain_tag = $game_map.terrain_tag(x, y)
return [5, 6, 7].include?(terrain_tag)
end
# Helper function to find water tiles on the map
def find_water_tiles
return [] if !$game_map
water_tiles = []
$game_map.width.times do |x|
$game_map.height.times do |y|
water_tiles.push([x, y]) if is_water_tile?(x, y)
end
end
return water_tiles
end
# Helper function to find land tiles on the map
def find_land_tiles
return [] if !$game_map
land_tiles = []
$game_map.width.times do |x|
$game_map.height.times do |y|
land_tiles.push([x, y]) if !is_water_tile?(x, y) && $game_map.passable?(x, y, 0)
end
end
return land_tiles
end
# Helper function to play the Pokémon's cry
def play_pokemon_cry(pokemon, volume = 90)
return if !pokemon
if pokemon.is_a?(Pokemon)
GameData::Species.play_cry_from_pokemon(pokemon, volume) if !pokemon.egg?
else
GameData::Species.play_cry_from_species(pokemon, 0, volume)
end
end
class BoxRanch
attr_reader :pokemon_events
attr_accessor :pending_swap_data # For deferred swap completion
def initialize
@pokemon_events = {}
@pokemon_locations = {}
@map_id = BoxRanchConfig::MAP_ID
@water_tiles = []
@land_tiles = []
@is_setting_up = false
@pending_swap_data = nil
end
def setup
return if @is_setting_up || !$game_map
return if $game_map.map_id != @map_id
return unless $scene.is_a?(Scene_Map) || $scene.is_a?(Scene_DebugIntro)
@is_setting_up = true
echoln("BoxRanch: Starting setup on map #{@map_id}")
begin
setup_ranch_pokemon
rescue => e
echoln("BoxRanch Error during setup: #{e.message}")
echoln(e.backtrace.join("\n"))
ensure
@is_setting_up = false
end
end
def update
# Check if we have a pending swap to complete
complete_pending_swap if @pending_swap_data
end
private
def setup_ranch_pokemon
block_autosave(true)
clear_ranch_pokemon
scan_map_tiles
load_pokemon_from_boxes
create_all_events
refresh_following_pokemon
echoln("BoxRanch: Setup complete! Total events: #{@pokemon_events.size}")
block_autosave(false)
end
def block_autosave(state)
return unless $game_temp && $game_temp.respond_to?(:no_autosave=)
$game_temp.no_autosave = state
end
def scan_map_tiles
@water_tiles = find_water_tiles
@land_tiles = find_land_tiles
echoln("BoxRanch: Found #{@water_tiles.length} water tiles and #{@land_tiles.length} land tiles")
end
def load_pokemon_from_boxes
@pokemon_locations.clear
pokemon_list = []
$PokemonStorage.maxBoxes.times do |i|
$PokemonStorage.maxPokemon(i).times do |j|
pkmn = $PokemonStorage[i, j]
next unless pkmn
pokemon_list.push(pkmn)
@pokemon_locations[pkmn] = [i, j]
end
end
echoln("BoxRanch: Found #{pokemon_list.length} Pokemon in boxes")
# Create test Pokemon if needed
pokemon_list = create_test_pokemon if pokemon_list.empty? && BoxRanchConfig::CREATE_TEST_POKEMON
categorize_pokemon(pokemon_list)
end
def create_test_pokemon
test_list = []
test_list.push(Pokemon.new(BoxRanchConfig::TEST_LAND_SPECIES, BoxRanchConfig::TEST_LEVEL))
if !@water_tiles.empty?
test_list.push(Pokemon.new(BoxRanchConfig::TEST_WATER_SPECIES, BoxRanchConfig::TEST_LEVEL))
end
echoln("BoxRanch: Created #{test_list.length} test Pokemon")
return test_list
end
def categorize_pokemon(pokemon_list)
@water_pokemon = []
@land_pokemon = []
pokemon_list.each do |pkmn|
if is_water_pokemon?(pkmn) && !@water_tiles.empty?
@water_pokemon.push(pkmn)
else
@land_pokemon.push(pkmn)
end
end
echoln("BoxRanch: #{@land_pokemon.length} land, #{@water_pokemon.length} water Pokemon")
end
def create_all_events
max_land = [@land_pokemon.size, BoxRanchConfig::MAX_LAND_POKEMON].min
@land_pokemon[0...max_land].each_with_index { |pkmn, i| create_pokemon_event(pkmn, i, false) }
max_water = [@water_pokemon.size, BoxRanchConfig::MAX_WATER_POKEMON].min
@water_pokemon[0...max_water].each_with_index { |pkmn, i| create_pokemon_event(pkmn, i, true) }
end
def create_pokemon_event(pkmn, index, in_water)
x, y = get_spawn_position(index, in_water)
event = build_event(pkmn, x, y, in_water)
game_event = spawn_event(event, x, y)
@pokemon_events[event.id] = pkmn
echoln("BoxRanch: Created event #{event.id} for #{pkmn.name} at (#{x}, #{y})")
end
def get_spawn_position(index, in_water)
tiles = in_water ? @water_tiles : @land_tiles
if !tiles.empty?
pos = tiles.sample
tiles.delete(pos)
return pos
end
# Fallback to grid positioning
return calculate_grid_position(index)
end
def calculate_grid_position(index)
area = { x_start: 30, y_start: 30, width: 15, height: 15, columns: 3, rows: 4 }
column = index % area[:columns]
row = (index / area[:columns]) % area[:rows]
cell_width = area[:width] / area[:columns]
cell_height = area[:height] / area[:rows]
x = area[:x_start] + (column * cell_width) + rand(cell_width / 2)
y = area[:y_start] + (row * cell_height) + rand(cell_height / 2)
return [x, y]
end
def build_event(pkmn, x, y, in_water)
event = RPG::Event.new(x, y)
event.id = generate_event_id
event.name = get_ranch_event_name(pkmn.species, in_water)
setup_event_graphic(event, pkmn, in_water)
setup_event_movement(event, pkmn, in_water)
setup_event_commands(event, pkmn, in_water)
return event
end
def generate_event_id
existing_ids = $game_map.events.keys
return existing_ids.empty? ? BoxRanchConstants::RANCH_EVENT_ID_START : [existing_ids.max + 1, BoxRanchConstants::RANCH_EVENT_ID_START].max
end
def setup_event_graphic(event, pkmn, in_water)
levitates = is_levitating_pokemon?(pkmn)
sprite_path = box_ranch_sprite_filename(pkmn.species, pkmn.form || 0, pkmn.gender, pkmn.shiny?, in_water, levitates)
event.pages[0].graphic.character_name = sprite_path.gsub("Graphics/Characters/", "")
event.pages[0].graphic.character_hue = 0
event.pages[0].graphic.direction = 2
end
def setup_event_movement(event, pkmn, in_water)
event.pages[0].through = false
event.pages[0].always_on_top = false
event.pages[0].step_anime = true
event.pages[0].trigger = 0
event.pages[0].move_type = 1
# Nature-based movement
nature_value = pkmn.nature.id.to_s.hash.abs
if in_water
speed_range = BoxRanchConfig::WATER_SPEED_MAX - BoxRanchConfig::WATER_SPEED_MIN + 1
event.pages[0].move_speed = BoxRanchConfig::WATER_SPEED_MIN + (nature_value % speed_range)
else
speed_range = BoxRanchConfig::LAND_SPEED_MAX - BoxRanchConfig::LAND_SPEED_MIN + 1
event.pages[0].move_speed = BoxRanchConfig::LAND_SPEED_MIN + (nature_value % speed_range)
end
freq_range = BoxRanchConfig::FREQUENCY_MAX - BoxRanchConfig::FREQUENCY_MIN + 1
event.pages[0].move_frequency = BoxRanchConfig::FREQUENCY_MIN + (nature_value % freq_range)
event.pages[0].move_route = RPG::MoveRoute.new
event.pages[0].move_route.repeat = true
event.pages[0].move_route.skippable = false
event.pages[0].move_route.list = []
end
def setup_event_commands(event, pkmn, in_water)
event.pages[0].list = []
Compiler::push_script(event.pages[0].list, "play_pokemon_cry(:#{pkmn.species}, 100)")
Compiler::push_script(event.pages[0].list, "pbMessage(\"#{pkmn.name} looks at you friendly!\")")
if pkmn.shiny?
Compiler::push_script(event.pages[0].list, "pbMessage(\"#{pkmn.name} shines conspicuously in the sunlight.\")")
end
nature_text = get_nature_text(pkmn.nature)
Compiler::push_script(event.pages[0].list, "pbMessage(\"#{nature_text}\")")
if in_water
Compiler::push_script(event.pages[0].list, "pbMessage(\"It swims happily in the water!\")")
elsif is_levitating_pokemon?(pkmn)
Compiler::push_script(event.pages[0].list, "pbMessage(\"It floats elegantly in the air!\")")
end
Compiler::push_script(event.pages[0].list, "pbMessage(\"Level: #{pkmn.level}\\nAbility: #{pkmn.ability.name}\")")
Compiler::push_script(event.pages[0].list, "show_pokemon_interaction_menu(:#{pkmn.species}, #{pkmn.level}, #{event.id})")
Compiler::push_end(event.pages[0].list)
end
def spawn_event(event, x, y)
game_event = Game_Event.new($game_map.map_id, event)
game_event.moveto(x, y)
game_event.refresh
$game_map.events[event.id] = game_event
return game_event
end
def get_nature_text(nature)
nature_texts = {
:JOLLY => "It dances around cheerfully.",
:NAIVE => "It is very playful.",
:HASTY => "It can't stand still and is constantly running around.",
:CALM => "It rests peacefully.",
:CAREFUL => "It attentively observes its surroundings.",
:QUIET => "It enjoys the tranquility of the ranch.",
:BRAVE => "It bravely shows itself off.",
:ADAMANT => "It trains its muscles.",
:NAUGHTY => "It seems to be up to something."
}
return nature_texts[nature.id] || "It feels very comfortable on the ranch."
end
public
def clear_ranch_pokemon
echoln("BoxRanch: Clearing #{@pokemon_events.size} ranch events")
# Remove tracked events
@pokemon_events.keys.each do |event_id|
$game_map.events.delete(event_id) if $game_map.events[event_id]
end
@pokemon_events.clear
@pokemon_locations.clear
refresh_following_pokemon
end
def remove_pokemon_event(event_id)
block_autosave(true)
echoln("BoxRanch: Removing event #{event_id}")
# Remove the event from the map
$game_map.events.delete(event_id)
@pokemon_events.delete(event_id)
# SOLUTION ULTIME: Map transfer pour force un refresh complet
force_map_reload
block_autosave(false)
end
def force_map_reload
echoln("BoxRanch: Forcing complete map reload to eliminate ghost sprites")
# Sauvegarder la position actuelle
current_map_id = $game_map.map_id
current_x = $game_player.x
current_y = $game_player.y
current_direction = $game_player.direction
echoln("BoxRanch: Player position: Map #{current_map_id}, (#{current_x}, #{current_y}), Dir #{current_direction}")
# Sauvegarder l'état du Following Pokemon
follower_toggled = $PokemonGlobal.follower_toggled if $PokemonGlobal
# Préparer le transfert vers la même position
$game_temp.player_transferring = true
$game_temp.player_new_map_id = current_map_id
$game_temp.player_new_x = current_x
$game_temp.player_new_y = current_y
$game_temp.player_new_direction = current_direction
# Effectuer le transfert immédiatement
if $scene.is_a?(Scene_Map)
$scene.transfer_player
# Petit délai pour que le transfert soit complété
Graphics.update
Input.update
pbUpdateSceneMap
end
# Restaurer l'état du Following Pokemon
if $PokemonGlobal && follower_toggled != nil
$PokemonGlobal.follower_toggled = follower_toggled
end
# Rafraîchir le Following Pokemon
if defined?(FollowingPkmn)
FollowingPkmn.refresh(false)
end
echoln("BoxRanch: Map reload complete")
pbWait(0.05)
end
def refresh_following_pokemon
return unless defined?(FollowingPkmn)
FollowingPkmn.refresh(false)
end
# Public method for creating event at specific position (used by swap)
def create_pokemon_event_at(pkmn, x, y, in_water)
event = build_event(pkmn, x, y, in_water)
game_event = spawn_event(event, x, y)
@pokemon_events[event.id] = pkmn
echoln("BoxRanch: Created event #{event.id} for #{pkmn.name} at (#{x}, #{y})")
end
def get_pokemon_location(pokemon)
@pokemon_locations[pokemon]
end
def set_pokemon_location(pokemon, box_index, slot_index)
@pokemon_locations[pokemon] = [box_index, slot_index]
end
def remove_pokemon_location(pokemon)
@pokemon_locations.delete(pokemon)
end
# Store swap data to be completed after event finishes
def prepare_swap(party_pokemon, x, y, in_water)
@pending_swap_data = {
pokemon: party_pokemon,
x: x,
y: y,
in_water: in_water
}
echoln("BoxRanch: Swap prepared for #{party_pokemon.name}, will complete after event")
end
def complete_pending_swap
return unless @pending_swap_data
return if $game_system.map_interpreter.running? # Wait until event is done
echoln("BoxRanch: Completing pending swap")
data = @pending_swap_data
@pending_swap_data = nil
# Create the new event
create_pokemon_event_at(data[:pokemon], data[:x], data[:y], data[:in_water])
# Force a complete refresh
force_map_reload
end
end
#===============================================================================
# Interaction Functions
#===============================================================================
def show_pokemon_interaction_menu(species, level, event_id = nil)
return unless event_id && $box_ranch && $box_ranch.pokemon_events[event_id]
commands = [_INTL("Pet"), _INTL("Feed"), _INTL("Play")]
if $player && $player.party
party_size = $player.party.length
if party_size < Settings::MAX_PARTY_SIZE
commands.push(_INTL("Take to team"))
commands.push(_INTL("Swap with team")) if party_size > 0
else
commands.push(_INTL("Swap with team"))
end
end
commands.push(_INTL("Back"))
choice = pbMessage(_INTL("What would you like to do?"), commands, commands.length)
case choice
when 0 # Pet
pbMessage(_INTL("You gently pet the Pokémon. It seems to enjoy that!"))
play_pokemon_cry(species, 70)
when 1 # Feed
pbMessage(_INTL("You give the Pokémon some food. It eats happily!"))
when 2 # Play
pbMessage(_INTL("You play with the Pokémon for a while. It has fun!"))
play_pokemon_cry(species, 100)
else
action = commands[choice]
take_to_party(event_id) if action == _INTL("Take to team")
swap_with_party_pokemon(event_id) if action == _INTL("Swap with team")
end
end
def take_to_party(event_id)
return unless $box_ranch && $box_ranch.pokemon_events[event_id] && $player
ranch_pokemon = $box_ranch.pokemon_events[event_id]
if $player.party.length >= Settings::MAX_PARTY_SIZE
pbMessage(_INTL("Your team is full. You can't take more Pokémon."))
return
end
msg = _INTL("Do you want to take {1} (Lv. {2}) to your team?", ranch_pokemon.name, ranch_pokemon.level)
return unless pbConfirmMessage(msg)
# Add to party
$player.party.push(ranch_pokemon)
# Remove from box if it came from there
ranch_box_location = $box_ranch.get_pokemon_location(ranch_pokemon)
if ranch_box_location
box_index, slot_index = ranch_box_location
$PokemonStorage[box_index, slot_index] = nil
$box_ranch.remove_pokemon_location(ranch_pokemon)
end
# Remove event (uses force_map_reload internally - this works fine)
$box_ranch.remove_pokemon_event(event_id)
pbMessage(_INTL("{1} joined your team!", ranch_pokemon.name))
end
def swap_with_party_pokemon(event_id)
return unless $box_ranch && $box_ranch.pokemon_events[event_id] && $player
return if !$player.party || $player.party.empty?
ranch_pokemon = $box_ranch.pokemon_events[event_id]
pbMessage(_INTL("Choose a Pokémon from your team to swap with."))
pbChoosePokemon(1, 2)
chosen_index = $game_variables[1]
if chosen_index < 0 || chosen_index >= $player.party.length
pbMessage(_INTL("Swap cancelled."))
return
end
party_pokemon = $player.party[chosen_index]
msg = _INTL("Do you want to swap {1} (Lv. {2}) with {3} (Lv. {4})?",
ranch_pokemon.name, ranch_pokemon.level, party_pokemon.name, party_pokemon.level)
return unless pbConfirmMessage(msg)
# Save event position
event = $game_map.events[event_id]
x, y = event.x, event.y
in_water = event.name.include?("InWater")
# Get box location
ranch_box_location = $box_ranch.get_pokemon_location(ranch_pokemon)
# Perform swap in data
$player.party[chosen_index] = ranch_pokemon
if ranch_box_location
box_index, slot_index = ranch_box_location
$PokemonStorage[box_index, slot_index] = party_pokemon
$box_ranch.set_pokemon_location(party_pokemon, box_index, slot_index)
$box_ranch.remove_pokemon_location(ranch_pokemon)
end
# Remove the old event (without map reload)
$game_map.events.delete(event_id)
$box_ranch.pokemon_events.delete(event_id)
# Show success message FIRST
pbMessage(_INTL("Swap successful! {1} is now in your team, and {2} is on the Ranch.",
ranch_pokemon.name, party_pokemon.name))
# AFTER the message, prepare the swap to be completed
$box_ranch.prepare_swap(party_pokemon, x, y, in_water)
end
#===============================================================================
# Event Handlers
#===============================================================================
# Initialize BoxRanch singleton
EventHandlers.add(:on_game_start, :init_box_ranch, proc {
$box_ranch = BoxRanch.new
})
EventHandlers.add(:on_game_load, :reload_box_ranch, proc {
$box_ranch = BoxRanch.new
if $game_map && $game_map.map_id == BoxRanchConfig::MAP_ID
$game_map.need_refresh = true
pbWait(0.05)
$box_ranch.setup
end
})
EventHandlers.add(:on_map_change, :setup_box_ranch, proc { |_old_map_id|
$box_ranch ||= BoxRanch.new
$box_ranch.setup
})
EventHandlers.add(:on_box_change, :sync_box_ranch, proc { |_old_box|
if $box_ranch && $game_map && $game_map.map_id == BoxRanchConfig::MAP_ID
$box_ranch.setup
end
})
#===============================================================================
# Game_Map integration
#===============================================================================
class Game_Map
alias box_ranch_setup setup
def setup(map_id)
box_ranch_setup(map_id)
$box_ranch ||= BoxRanch.new
$box_ranch.setup if map_id == BoxRanchConfig::MAP_ID
end
end
#===============================================================================
# Scene_Map integration
#===============================================================================
class Scene_Map
alias box_ranch_update update
def update
box_ranch_update
$box_ranch.update if $box_ranch
end
end