update 6.7

This commit is contained in:
chardub
2025-09-28 15:53:01 -04:00
parent ef5e023ae0
commit 318ff90d8d
696 changed files with 111759 additions and 198230 deletions

View File

@@ -3,6 +3,7 @@
class PokemonEncounters
WEATHER_ENCOUNTER_BASE_CHANCE = 8 #/100 (for weather intensity of 0)
alias pokemonEssentials_PokemonEncounter_choose_wild_pokemon choose_wild_pokemon
ANIMATION_WEATHER_ENCOUNTER = 3
def choose_wild_pokemon(enc_type, *args)
return pokemonEssentials_PokemonEncounter_choose_wild_pokemon(enc_type, *args) if !$game_weather
current_weather_type = $game_weather.get_map_weather_type($game_map.map_id)

View File

@@ -3,10 +3,9 @@
Events.onMapChange+= proc { |_old_map_id|
next if !$game_weather || !$game_weather.current_weather || !$game_weather.last_update_time
next if !$game_map
echoln pbGetTimeNow.to_i
update_overworld_weather($game_map.map_id)
next if $game_weather.last_update_time.to_i + GameWeather::TIME_BETWEEN_WEATHER_UPDATES > pbGetTimeNow.to_i
echoln "- Updating the weather -"
new_map_id = $game_map.map_id
mapMetadata = GameData::MapMetadata.try_get(new_map_id)
next if mapMetadata.nil?

View File

@@ -1,5 +1,5 @@
class BetterRegionMap
DEBUG_WEATHER = true
DEBUG_WEATHER = $DEBUG
def update_weather_icon(location)
return
return nil if !location

View File

@@ -21,7 +21,8 @@ class GameWeather
attr_accessor :current_weather
attr_accessor :last_update_time
TIME_BETWEEN_WEATHER_UPDATES = 12000 # 180 seconds, only actually changes once the player changes map
#TIME_BETWEEN_WEATHER_UPDATES in in-game seconds (1 irl second = 60 in-game seconds)
TIME_BETWEEN_WEATHER_UPDATES = 3600 #1 in-game hour (1 irl minute) .
CHANCE_OF_NEW_WEATHER = 2 # /100 spontaneous new weather popping up somewhere
CHANCE_OF_RAIN = 40 #/100
@@ -122,6 +123,7 @@ class GameWeather
def try_propagate_weather_to_neighbors(map_id,propagating_map_weather_type,propagating_map_weather_intensity)
propagating_map_neighbors = @neighbors_maps[map_id]
return unless propagating_map_neighbors
return if propagating_map_weather_type == :None
return unless can_weather_spread(propagating_map_weather_type)
propagating_map_weather_type, propagating_map_weather_intensity = normalize_legendary_weather(propagating_map_weather_type, propagating_map_weather_intensity)
@@ -146,10 +148,10 @@ class GameWeather
def try_move_weather_to_neighbors(map_id,map_weather_type,weather_intensity)
map_neighbors = @neighbors_maps[map_id]
return unless map_neighbors
return if map_weather_type == :None || weather_intensity <= 1
return unless can_weather_spread(map_weather_type)
map_weather_type, weather_intensity = normalize_legendary_weather(map_weather_type, weather_intensity)
map_neighbors.each do |neighbor_id|
neighbor_weather_type = get_map_weather_type(neighbor_id)
neighbor_weather_intensity = get_map_weather_intensity(neighbor_id)

View File

@@ -1,11 +1,17 @@
# available channels
# :RANDOM
# :NEWS
# :WEATHER
#
def showTVText(channel = :NEWS)
TV_CHANNELS = [:NEWS, :WEATHER]
def showTVText(channel = :RANDOM)
channel = TV_CHANNELS.sample if channel == :RANDOM
case channel
when :NEWS
pbMessage(getTVNewsCaption())
when :WEATHER
pbMessage(_INTL("It's the weather channel! Let's see how things are looking out today."))
pbWeatherMapMap()
end
end
@@ -25,3 +31,41 @@ def hoennSelectStarter
pbAddPokemonSilent(selected_starter)
return selected_starter
end
def secretBaseQuest_pickedNearbySpot()
return false if !$Trainer.secretBase
expected_map = 65
expected_positions = [
[30,43],[31,43],[32,42],[33,42],[34,42],[35,42],[36,40],[37,40],#trees
[41,40] #cliff
]
picked_base_map = $Trainer.secretBase.outside_map_id
picked_position = $Trainer.secretBase.outside_entrance_position
echoln picked_base_map
echoln picked_position
echoln picked_base_map == expected_map && expected_positions.include?(picked_position)
return picked_base_map == expected_map && expected_positions.include?(picked_position)
end
#To scroll a picture on screen in a seamless, continuous loop (used in the truck scene in the intro)
# Provide 2 pictures (so that the loop isn't choppy)
# Speed in pixels per frame
def scroll_picture_loop(pic_a_nb, pic_b_nb, width, speed)
pic_a = $game_screen.pictures[pic_a_nb]
pic_b = $game_screen.pictures[pic_b_nb]
# move both
pic_a.x -= speed
pic_b.x -= speed
# wrap-around: always place offscreen one after the other
if pic_a.x <= -width
pic_a.x = pic_b.x + width
elsif pic_b.x <= -width
pic_b.x = pic_a.x + width
end
end

View File

@@ -8,20 +8,19 @@ class Player < Trainer
alias pokemonEssentials_player_initialize initialize
def initialize(*args)
pokemonEssentials_player_initialize(*args)
@rival_appearance = init_rival_appearance
end
def init_rival_appearance
if isPlayerMale
return TrainerAppearance.new(5,
@rival_appearance= TrainerAppearance.new(5,
HAT_MAY,
CLOTHES_MAY,
getFullHairId(HAIR_MAY,3) ,
0, 0, 0)
else
return TrainerAppearance.new(5,
@rival_appearance= TrainerAppearance.new(5,
HAT_BRENDAN,
CLOTHES_BRENDAN,
getFullHairId(HAIR_BRENDAN,3),
@@ -69,7 +68,7 @@ class Sprite_Character
end
end
def get_rival_starter
def get_hoenn_rival_starter
case get_rival_starter_type()
when :GRASS
return obtainStarter(0)
@@ -225,11 +224,11 @@ def initializeRivalBattledTrainer
trainer_type = :RIVAL1
trainer_name = isPlayerMale ? "May" : "Brendan"
trainer_appearance = $Trainer.rival_appearance
rivalBattledTrainer = BattledTrainer.new(trainer_type,trainer_name,0)
rivalBattledTrainer = BattledTrainer.new(trainer_type,trainer_name,0,BATTLED_TRAINER_RIVAL_KEY)
rivalBattledTrainer.set_custom_appearance(trainer_appearance)
echoln rivalBattledTrainer.currentTeam
team = []
team<<Pokemon.new(get_rival_starter,5)
team<<Pokemon.new(get_hoenn_rival_starter,5)
rivalBattledTrainer.currentTeam =team
return rivalBattledTrainer
end

View File

@@ -0,0 +1,92 @@
BATTLED_TRAINER_WALLY_KEY = "wally"
SWITCH_WALLY_CATCHING_POKEMON = 2022
SWITCH_WALLY_GAVE_POKEMON = 2023
SWITCH_WALLY_GAVE_POKEMON_DIALOGUE = 2024
COMMON_EVENT_WALLY_FOLLOWING_DIALOGUE = 199
def wally_initialize()
trainer_type = :RIVAL2
trainer_name = "Wally"
battledTrainer = BattledTrainer.new(trainer_type,trainer_name,0,BATTLED_TRAINER_WALLY_KEY)
battledTrainer.currentTeam =[]#team
$PokemonGlobal.battledTrainers={} if !$PokemonGlobal.battledTrainers
$PokemonGlobal.battledTrainers[BATTLED_TRAINER_WALLY_KEY] = battledTrainer
return battledTrainer
end
def wally_add_pokemon(pokemon_species,level)
trainer = $PokemonGlobal.battledTrainers[BATTLED_TRAINER_WALLY_KEY]
pokemon = Pokemon.new(pokemon_species,level)
trainer.currentTeam.push(pokemon)
updateRebattledTrainerWithKey(BATTLED_TRAINER_WALLY_KEY,trainer)
end
def wally_remove_pokemon(pokemon_species)
trainer = $PokemonGlobal.battledTrainers[BATTLED_TRAINER_WALLY_KEY]
echoln trainer.currentTeam
trainer.currentTeam.each { |pokemon|
if pokemon.species == pokemon_species
trainer.currentTeam.delete(pokemon)
updateRebattledTrainerWithKey(BATTLED_TRAINER_WALLY_KEY, trainer)
return
end
}
end
def wally_fuse_pokemon(with_fusion_screen=true)
head_pokemon_index=0
body_pokemon_index=1
trainer = $PokemonGlobal.battledTrainers[BATTLED_TRAINER_WALLY_KEY]
body_pokemon = trainer.currentTeam[body_pokemon_index]
head_pokemon = trainer.currentTeam[head_pokemon_index]
return if head_pokemon.isFusion? || body_pokemon.isFusion?
npcTrainerFusionScreenPokemon(head_pokemon.clone,body_pokemon.clone) if with_fusion_screen
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)
updateRebattledTrainerWithKey(BATTLED_TRAINER_WALLY_KEY,trainer)
end
def npcTrainerFusionScreenPokemon(headPokemon,bodyPokemon)
fusionScene = PokemonFusionScene.new
newSpecies = getFusedPokemonIdFromSymbols(bodyPokemon.species,headPokemon.species)
newDexNumber = getDexNumberForSpecies(newSpecies)
if fusionScene.pbStartScreen(bodyPokemon, headPokemon, newDexNumber, :DNASPLICERS)
fusionScene.pbFusionScreen(false, false, false,false)
fusionScene.pbEndScreen
end
end
def npcTrainerFusionScreen(headSpecies,bodySpecies)
fusionScene = PokemonFusionScene.new
newid = getFusedPokemonIdFromSymbols(bodySpecies,headSpecies)
fusionScene.pbStartScreen(Pokemon.new(bodySpecies,100), Pokemon.new(headSpecies,100), newid, :DNASPLICERS)
end
def getWallyTrainer()
return $PokemonGlobal.battledTrainers[BATTLED_TRAINER_WALLY_KEY]
end
def wally_follow(eventId)
trainer = $PokemonGlobal.battledTrainers[BATTLED_TRAINER_WALLY_KEY]
partnerWithTrainer(eventId, $game_map.map_id, trainer,BATTLED_TRAINER_WALLY_KEY, COMMON_EVENT_WALLY_FOLLOWING_DIALOGUE)
end
def wally_unfollow()
unpartnerWithTrainer()
end

View File

@@ -0,0 +1,85 @@
# frozen_string_literal: true
class SecretBase
attr_reader :outside_map_id #Id of the map where the secret base is
attr_reader :inside_map_id #Id of the secret base's map itself
attr_reader :location_name #Name of the Route where the secret base is in plain text
attr_reader :outside_entrance_position #Fly coordinates
attr_reader :inside_entrance_position #Where the player gets warped
attr_reader :biome_type #:CAVE, :TREE,
attr_reader :base_layout_type
attr_accessor :base_name
attr_accessor :base_message
attr_accessor :layout
attr_accessor :is_visitor
def initialize(biome:,outside_map_id:,outside_entrance_position:, inside_map_id:, base_layout_type:, is_visitor:, layout: nil, visitor_message:nil)
@biome_type = biome
@outside_map_id = outside_map_id
@inside_map_id = inside_map_id
@outside_entrance_position = outside_entrance_position
@base_layout_type = base_layout_type.to_sym
@inside_entrance_position = SecretBasesData::SECRET_BASE_ENTRANCES[@base_layout_type][:position]
@base_name=initializeBaseName
@base_message=visitor_message
initialize_base_message unless @base_message #Message that people see when visiting the secret base
@is_visitor=is_visitor
@layout = layout
initializeLayout unless @layout
end
def initializeBaseName
return _INTL("{1}'s secret base",$Trainer.name)
end
def initialize_base_message
return _INTL("Welcome to my secret base!")
end
def initializeLayout
@layout = SecretBaseLayout.new(@base_layout_type,!@is_visitor)
entrance_x = @inside_entrance_position[0]
entrance_y = @inside_entrance_position[1]
@layout.add_item(:PC,[entrance_x,entrance_y-3])
end
def load_furniture
@layout.items.each do |item_instance|
next unless item_instance.is_a?(SecretBaseItemInstance)
next unless SecretBasesData::SECRET_BASE_ITEMS[item_instance.itemId]
item_instance.direction = DIRECTION_DOWN
template = item_instance.itemTemplate
echoln template
item_instance.create_events
# event = $PokemonTemp.createTempEvent(TEMPLATE_EVENT_SECRET_BASE_FURNITURE, $game_map.map_id, item_instance.position, DIRECTION_DOWN)
# event.character_name = "player/SecretBases/#{template.graphics}"
# event.through = template.pass_through
# event.under_player = template.under_player
# event.direction = item_instance.direction
if item_instance.itemTemplate.id == :MANNEQUIN && @is_visitor
setEventAppearance(item_instance.main_event.id, @trainer_appearance) if @trainer_appearance
end
# item_instance.setMainEventId(event.id)
item_instance.refresh_events
#event.refresh
end
end
end

View File

@@ -0,0 +1,172 @@
# frozen_string_literal: true
class SecretBaseItemInstance
attr_reader :itemId
attr_reader :instanceId
attr_accessor :position
attr_accessor :direction
attr_accessor :itemTemplate
attr_accessor :main_event_id
attr_reader :disposed
attr_reader :main_event
RANDOM_ID_LENGTH = 6
def initialize(itemId, position = [0, 0], direction = DIRECTION_DOWN)
@itemId = itemId
@instanceId = generate_new_instance_id()
@direction = direction
@position = position
@main_event = nil # Objects that are wider or taller than 1 tile work by duplicating the object, but making only the middle instance visible. This contains the id of the middle instance
end
def create_events()
template = get_item_template
@volume_events = []
main_event = $PokemonTemp.createTempEvent(TEMPLATE_EVENT_SECRET_BASE_FURNITURE, $game_map.map_id, @position, @direction)
main_event.character_name = "player/SecretBases/#{template.graphics}"
main_event.through = template.pass_through
main_event.under_player = template.under_player
main_event.trigger = template.trigger
@main_event = main_event
occupied_positions = calculate_occupied_volume_positions(@position)
occupied_positions.each do |position|
event = $PokemonTemp.createTempEvent(TEMPLATE_EVENT_SECRET_BASE_FURNITURE, $game_map.map_id, position)
event.character_name = "player/SecretBases/empty" # Game will consider it passable if we don't put anything here
event.through = template.pass_through
event.trigger = template.trigger
@volume_events << event
end
end
# Use this - can't save it into the object because of proc
def get_item_template
return SecretBasesData::SECRET_BASE_ITEMS[@itemId]
end
# fake accessor
def itemTemplate
return get_item_template
end
def get_occupied_positions()
occupied_positions = []
occupied_positions << [@main_event.x, @main_event.y]
@volume_events.each do |event|
occupied_positions << [event.x, event.y]
end
return occupied_positions
end
def interactable?(position = [0,0])
return get_interactable_positions.include?(position)
end
def get_interactable_positions()
all_positions = get_occupied_positions
uninteractable_positions = get_item_template.uninteractable_positions
main_x, main_y = @main_event.x, @main_event.y
uninteractable_absolute_positions = uninteractable_positions.map do |dx, dy|
[main_x + dx, main_y + dy]
end
interactable_positions = all_positions.reject do |pos|
uninteractable_absolute_positions.include?(pos)
end
return interactable_positions
end
def calculate_occupied_volume_positions(main_event_position)
template = get_item_template
item_x, item_y = main_event_position
item_height = template.height || 1
item_width = template.width || 1
direction = @direction
# Flip width/height if rotated sideways
if direction == DIRECTION_LEFT || direction == DIRECTION_RIGHT
item_width, item_height = item_height, item_width
end
half_width = (item_width - 1) / 2
half_height = (item_height - 1) / 2
occupied_positions = []
x_range = (item_x - half_width..item_x + half_width)
y_range = (item_y - half_height..item_y + half_height)
x_range.each do |x|
y_range.each do |y|
is_main_event = (x == item_x && y == item_y)
next if is_main_event
occupied_positions << [x, y]
end
end
return occupied_positions
end
def refresh_events
@main_event.refresh
@volume_events.each do |event|
event.refresh
end
end
def name
return itemTemplate&.real_name
end
def getGraphics()
return itemTemplate.graphics
end
def height
return itemTemplate.height
end
def width
return itemTemplate.width
end
def generate_new_instance_id()
randomId = rand(36 ** RANDOM_ID_LENGTH).to_s(36)
return "#{@itemId}_#{randomId}"
end
def getMainEvent()
return @main_event
end
def disposed?
return @disposed
end
def dispose
@itemId = nil
@position = nil
@direction = nil
@main_event_id = nil
@itemTemplate = nil
@disposed = true
@main_event.erase
@main_event = nil
@volume_events.each do |event|
event.erase
end
@volume_events = nil
end
def connect_to_other_instance(instance_id)
@volume_events << instance_id
end
def set_main_instance(instance_id)
@main_event = instance_id
end
end

View File

@@ -0,0 +1,52 @@
# frozen_string_literal: true
# wrapper for secret base items
class SecretBaseItem
attr_reader :id # Symbol. Used for manipulating in other classes (trainer.unlockedBaseItems is an array of these, etc.)
attr_reader :graphics # File path to the item's graphics
attr_reader :real_name # Name displayed in-game
attr_reader :price
# Event attributes
attr_reader :pass_through # for carpets, etc.
attr_reader :under_player # for carpets, etc.
attr_reader :height
attr_reader :width
#todo: instead of this, have a 2d array that represents the layout visually and shows which tiles are interactable and which aren't
# ex:
# [
# [[x],[x],[x]],
# [[i],[i],[i]
# ]
# -> 2 rows, only interactable from the bottom
# Secret base object attributes
attr_reader :deletable
attr_reader :behavior # Lambda function that's defined when initializing the items. Some secret bases can have special effects when you interact with them (ex: a berry pot to grow berries, a bed, etc.)
# -> This is the function that will be called when the player interacts with the item in the base.
# Should just display flavor text for most basic items.
attr_reader :uninteractable_positions #Positions at which the behavior won't trigger (relative to the center) ex: [[-1,0],[1,0]] in a 3x1 object will trigger in the center, but not on the sides
attr_reader :trigger #Can define a different event trigger (Action Button by default)
def initialize(id:, graphics:, real_name:, price:, deletable: true, pass_through: false, under_player: false, behavior: nil, height:1, width:1, uninteractable_positions:[], trigger:TRIGGER_ACTION_BUTTON)
@id = id
@graphics = graphics
@real_name = real_name
@price = price
@deletable = deletable
@pass_through = pass_through
@under_player = under_player
#Parts of the item that the player shouldn't be able to pass through
@height = height
@width = width
# Default behavior just shows text if none provided
@behavior = behavior
@uninteractable_positions = uninteractable_positions
@trigger = trigger
end
end

View File

@@ -0,0 +1,81 @@
class SecretBaseLayout
attr_accessor :items #SecretBaseItemInstance
attr_accessor :tileset #todo Reuse the same layouts map for all bases and change the tileset depending on the type
attr_accessor :biome_type #:TREES, :CLIFF, :CLIFF_BEACH, :BUSH, etc. -> Determines which tiles are used in the base
attr_accessor :is_player_base
def initialize(layout_biome,is_player_base=false)
@biome_type = layout_biome
@items = []
@is_player_base = is_player_base
end
def add_item(itemId, position = [0, 0])
new_item = SecretBaseItemInstance.new(itemId, position)
@items << new_item
return new_item.instanceId
end
def get_item_at_position(position = [0,0])
@items.each do |item|
return item if item.get_occupied_positions.include?(position)
end
return nil
end
def get_item_by_id(instanceId)
@items.each do |item|
return item if item.instanceId == instanceId
end
return nil
end
def remove_item_by_instance(instanceId)
@items.each do |item|
if item.instanceId == instanceId
@items.delete(item)
end
end
return nil
end
def remove_item_at_position(position = [0, 0])
@items.each do |item|
if item.position == position
@items.delete(item)
end
end
end
# returns a list of ids of the items that are currently in the base's layout
def list_items_instances()
list = []
@items.each do |item|
list << item.instanceId
end
end
def get_all_occupied_positions()
occupied_positions = []
@items.each do |item|
occupied_positions << get_occupied_positions_for_item(item)
end
return occupied_positions
end
def check_position_available_for_item(itemInstance,position)
#placed_item_positions = get_all_occupied_positions
item_occupied_positions = itemInstance.calculate_occupied_volume_positions(position)
item_occupied_positions.each do |position|
x, y = position
#return false if placed_item_positions.include?(position)
return false if !$game_map.passableStrict?(x, y, DIRECTION_ALL)
end
return true
end
end

View File

@@ -0,0 +1,15 @@
class SecretBaseTrainer
attr_accessor :name
attr_accessor :nb_badges
attr_accessor :game_mode
attr_accessor :appearance #TrainerAppearance
attr_accessor :team #Array of Pokemon
def initialize(name, nb_badges, game_mode, appearance, team)
@name = name
@nb_badges = nb_badges
@game_mode = game_mode
@appearance = appearance
@team = team
end
end

View File

@@ -0,0 +1,52 @@
class VisitorSecretBase < SecretBase
attr_reader :trainer_name, :trainer_badges, :trainer_game_mode
attr_reader :trainer_appearance, :trainer_team
def initialize(biome:, outside_map_id:, outside_entrance_position:, inside_map_id:, layout:, base_layout_type:, base_message:, trainer_data:)
super(biome: biome,
outside_map_id: outside_map_id,
outside_entrance_position: outside_entrance_position,
inside_map_id: inside_map_id,
layout: layout,
base_layout_type: base_layout_type,
visitor_message: base_message,
is_visitor: true,)
@trainer_name = trainer_data.name
@trainer_badges = trainer_data.nb_badges || 0
@trainer_game_mode = trainer_data.game_mode || 0
@trainer_appearance = trainer_data.appearance
@trainer_team = trainer_data.team
end
def dump_info
echoln "=== Visitor Secret Base ==="
echoln "Biome: #{@biome_type}"
echoln "Outside Map ID: #{@outside_map_id}"
echoln "Inside Map ID: #{@inside_map_id}"
echoln "Outside Entrance Position: #{@outside_entrance_position.inspect}"
echoln "Inside Entrance Position: #{@inside_entrance_position.inspect}"
echoln "Layout Type: #{@base_layout_type}"
echoln "Base Name: #{@base_name}"
echoln "Base Message: #{@base_message}"
echoln "Visitor?: #{@is_visitor}"
echoln "--- Trainer Info ---"
echoln "Name: #{@trainer_name}"
echoln "Badges: #{@trainer_badges}"
echoln "Game Mode: #{@trainer_game_mode}"
echoln "Appearance: #{@trainer_appearance.inspect}"
echoln "--- Trainer Team ---"
if @trainer_team && !@trainer_team.empty?
@trainer_team.each_with_index do |pokemon, i|
echoln " #{i + 1}. #{pokemon.name} (#{pokemon.species}, Lv#{pokemon.level})"
end
else
echoln " (No Pokémon)"
end
echoln "============================="
end
end

View File

@@ -0,0 +1,332 @@
SWITCH_SECRET_BASE_PLACED_FIRST_DECORATION = 2047
class Trainer
attr_accessor :secretBase
attr_accessor :owned_decorations
end
class PokemonTemp
attr_accessor :enteredSecretBaseController
end
class SecretBaseController
attr_accessor :secretBase
def initialize(secretBase)
@secretBase = secretBase
end
def callBehaviorPosition(item_position)
item = @secretBase.layout.get_item_at_position(item_position)
if item && item.itemTemplate.behavior && item.interactable?(item_position)
item.itemTemplate.behavior.call(item)
end
end
def furnitureInteract(item_position = [], menuStartIndex=0)
cmd_labels = {
use: _INTL("Use"),
move: _INTL("Move"),
rotate: _INTL("Rotate"),
delete: _INTL("Put away"),
cancel: _INTL("Cancel"),
decorate: _INTL("Decorate!"),
storage: _INTL("Pokémon Storage"),
item_storage: _INTL("Item Storage")
}
item = @secretBase.layout.get_item_at_position(item_position)
return unless item
options = []
if item.itemId == :PC
pbMessage(_INTL("\\se[PC open]{1} booted up the PC.", $Trainer.name))
options << :decorate unless @secretBase.is_visitor
options << :storage
options << :item_storage
else
options << :use if item.itemTemplate.behavior && item.interactable?(item_position)
end
options << :move unless @secretBase.is_visitor
options << :rotate unless @secretBase.is_visitor || item.itemId == :PC
options << :delete if item.itemTemplate.deletable && !@secretBase.is_visitor
options << :cancel
actionable = options - [:cancel]
if actionable.length == 1
return executeFurnitureCommand(item, actionable.first,-1)
end
choice = optionsMenu(options.map { |cmd| cmd_labels[cmd] },-1,menuStartIndex)
executeFurnitureCommand(item, options[choice],choice, item_position)
end
def executeFurnitureCommand(item, command, commandIndex, position = nil)
case command
when :use
item.itemTemplate.behavior.call(item)
when :move
moveSecretBaseItem(item.instanceId, item.position)
when :rotate
rotateSecretBaseItem(item.getMainEvent)
furnitureInteract(position,commandIndex)
when :delete
if pbConfirmMessage(_INTL("Put away the #{item.name}?"))
pbSEPlay("GUI storage put down", 80, 100)
resetFurniture(item.instanceId)
else
furnitureInteract(position,commandIndex)
end
when :decorate
decorateSecretBase
when :storage
pbFadeOutIn {
scene = PokemonStorageScene.new
screen = PokemonStorageScreen.new(scene, $PokemonStorage)
screen.pbStartScreen(0)
}
when :item_storage
pbPCItemStorage
when :cancel
return
end
end
def reloadItems()
$PokemonTemp.pbClearTempEvents
SecretBaseLoader.new.loadSecretBaseFurniture(@secretBase)
end
def isMovingFurniture?
return $game_temp.moving_furniture
end
def decorateSecretBase
cmd_addItem = _INTL("Add a decoration")
cmd_moveItem = _INTL("Move a decoration")
cmd_cancel = _INTL("Back")
commands = []
commands << cmd_addItem
commands << cmd_moveItem
commands << cmd_cancel
choice = optionsMenu(commands)
case commands[choice]
when cmd_addItem
item_id = selectAnySecretBaseItem
addSecretBaseItem(item_id)
when cmd_moveItem
item_instance = selectPlacedSecretBaseItemInstance
moveSecretBaseItem(item_instance.instanceId, item_instance.position)
when cmd_cancel
return
end
end
def addSecretBaseItem(item_id)
return if @secretBase.is_a?(VisitorSecretBase)
echoln "ADDING ITEM #{item_id}"
if item_id
new_item_instance = $Trainer.secretBase.layout.add_item(item_id, [$game_player.x, $game_player.y])
SecretBaseLoader.new.loadSecretBaseFurniture(@secretBase)
$game_temp.original_direction = $game_player.direction
$game_player.direction = DIRECTION_DOWN
moveSecretBaseItem(new_item_instance, nil)
end
end
def rotateSecretBaseItem(event)
pbSEPlay("GUI party switch", 80, 100)
direction_fix = event.direction_fix
event.direction_fix = false
event.turn_left_90
event.direction_fix = direction_fix
end
def moveSecretBaseItem(itemInstanceId, oldPosition = nil)
return if @secretBase.is_a?(VisitorSecretBase)
itemInstance = @secretBase.layout.get_item_by_id(itemInstanceId)
event = itemInstance.getMainEvent
$game_player.setPlayerGraphicsOverride("SecretBases/#{itemInstance.getGraphics}")
$game_player.direction_fix = true
$game_player.under_player = event.under_player
$game_player.through = event.through # todo: Make it impossible to go past the walls
$game_temp.moving_furniture = itemInstanceId
$game_temp.moving_furniture_oldPlayerPosition = [$game_player.x, $game_player.y]
$game_temp.moving_furniture_oldItemPosition = oldPosition
event.opacity = 50 if event
event.through = true if event
$game_player.x, $game_player.y = itemInstance.position
$game_system.menu_disabled = true
$game_map.refresh
end
def cancelMovingFurniture()
$game_system.menu_disabled = false
$game_player.removeGraphicsOverride()
$game_temp.moving_furniture = nil
end
def placeFurnitureMenu(menu_position = 0)
if !$Trainer.secretBase || !$game_temp.moving_furniture
cancelMovingFurniture()
end
cmd_place = _INTL("Place here")
cmd_rotate = _INTL("Rotate")
cmd_reset = _INTL("Reset")
cmd_cancel = _INTL("Cancel")
options = []
options << cmd_place
options << cmd_rotate
options << cmd_reset
options << cmd_cancel
choice = optionsMenu(options, -1, menu_position)
case options[choice]
when cmd_place
placeFurnitureAtCurrentPosition($game_temp.moving_furniture, $game_player.direction)
when cmd_rotate
rotateFurniture
placeFurnitureMenu(choice)
when cmd_reset
resetFurniture($game_temp.moving_furniture)
when cmd_cancel
end
end
def placeFurnitureAtCurrentPosition(furnitureInstanceId, direction)
$game_switches[SWITCH_SECRET_BASE_PLACED_FIRST_DECORATION] = true
itemInstance = @secretBase.layout.get_item_by_id(furnitureInstanceId)
currentPosition = [$game_player.x, $game_player.y]
itemInstance.position = currentPosition
itemInstance.direction = direction
if @secretBase.layout.check_position_available_for_item(itemInstance,currentPosition)
main_event = itemInstance.getMainEvent
main_event.direction = $game_player.direction
$PokemonTemp.pbClearTempEvents
SecretBaseLoader.new.loadSecretBaseFurniture(@secretBase)
# Roload after items update
itemInstance = $Trainer.secretBase.layout.get_item_by_id(furnitureInstanceId)
event = itemInstance.getMainEvent
event.direction = $game_player.direction
resetPlayerPosition
else
pbMessage(_INTL("There's no room here!"))
end
end
def resetFurniture(furnitureInstanceId)
adding_new_item = $game_temp.moving_furniture_oldItemPosition == nil
itemInstance = $Trainer.secretBase.layout.get_item_by_id(furnitureInstanceId)
$Trainer.secretBase.layout.remove_item_by_instance(itemInstance.instanceId) if adding_new_item
reloadItems
resetPlayerPosition
itemInstance.dispose if adding_new_item
end
def resetPlayerPosition
return unless $game_temp.moving_furniture
$game_player.removeGraphicsOverride
pbFadeOutIn {
$game_player.direction_fix = false
if $game_temp.original_direction
$game_player.direction = $game_temp.original_direction
end
$game_player.through = false
$game_player.under_player = false
$game_temp.player_new_map_id = $game_map.map_id
$game_temp.player_new_x = $game_temp.moving_furniture_oldPlayerPosition[0]
$game_temp.player_new_y = $game_temp.moving_furniture_oldPlayerPosition[1]
$scene.transfer_player(true)
$game_map.autoplay
$game_map.refresh
}
$game_temp.moving_furniture_oldPlayerPosition = nil
$game_temp.moving_furniture_oldItemPosition = nil
$game_temp.moving_furniture = nil
$game_system.menu_disabled = false
end
def rotateFurniture()
$game_player.direction_fix = false
$game_player.turn_right_90
$game_player.direction_fix = true
end
end
def getEnteredSecretBase
controller = $PokemonTemp.enteredSecretBaseController
return controller.secretBase if controller
end
def getSecretBaseController
return $PokemonTemp.enteredSecretBaseController
end
def secretBaseItem(event_id)
return if $game_temp.moving_furniture
begin
event = $game_map.events[event_id]
pos=[event.x,event.y]
controller=getSecretBaseController
controller.callBehaviorPosition(pos)
end
end
def secretBaseItemMenu
return unless Input.trigger?(Input::C)
event = $game_player.pbFacingEvent
return unless event
event_position = [event.x, event.y]
controller = getSecretBaseController
controller.furnitureInteract(event_position)
end
def selectPlacedSecretBaseItemInstance()
options = []
$Trainer.secretBase.layout.items.each do |item_instance|
item_id = item_instance.itemId
item_name = SecretBasesData::SECRET_BASE_ITEMS[item_id].real_name
options << item_name
end
options << _INTL("Cancel")
chosen = optionsMenu(options)
$Trainer.secretBase.layout.items.each do |item_instance|
item_id = item_instance.itemId
item_name = SecretBasesData::SECRET_BASE_ITEMS[item_id].real_name
return item_instance if item_name == options[chosen]
end
return nil
end
def selectAnySecretBaseItem()
options = []
$Trainer.owned_decorations = [] if $Trainer.owned_decorations.nil?
$Trainer.owned_decorations.each do |item_id|
item_name = SecretBasesData::SECRET_BASE_ITEMS[item_id].real_name
options << item_name
end
options << _INTL("Cancel")
chosen = optionsMenu(options)
$Trainer.owned_decorations.each do |item_id|
item_name = SecretBasesData::SECRET_BASE_ITEMS[item_id].real_name
return item_id if item_name == options[chosen]
end
return nil
end

View File

@@ -0,0 +1,252 @@
def getSecretBaseBiome(terrainTag)
return :TREE if terrainTag.secretBase_tree
return :CAVE if terrainTag.secretBase_cave
return :BUSH if terrainTag.secretBase_bush
# todo: other types
return nil
end
def pickSecretBaseLayout(baseType)
mapId = MAP_SECRET_BASES
# Distance is how far away the same coordinates will share the same seed
case baseType
when :TREE
distance = 2
else
distance = 4
end
# Snap to 2x2 blocks
block_x = $game_player.x / distance
block_y = $game_player.y / distance
# Universal deterministic seed
seed_str = "#{baseType}-#{mapId}-#{block_x}-#{block_y}"
seed = Zlib.crc32(seed_str)
rng = Random.new(seed)
layoutType = weighted_sample(SecretBasesData::SECRET_BASE_ENTRANCES, rng)
return layoutType
end
def weighted_sample(entries, rng)
total = entries.values.sum { |v| v[:rareness] }
pick = rng.rand * total
entries.each do |key, v|
return key if (pick -= v[:rareness]) <= 0
end
# Fallback: return the last key
return entries.keys.last
end
def pbSecretBase(biome_type, base_layout_type)
base_map_id = MAP_SECRET_BASES
player_map_id = $game_map.map_id
player_position = [$game_player.x, $game_player.y]
if secretBaseExistsAtPosition(player_map_id, player_position)
enterSecretBase
else
# Todo: Determine the secret base's map ids and coordinates from a seed using the current map and the base type instead of passing it manually.
createSecretBaseHere(biome_type, base_map_id, base_layout_type)
end
end
def secretBaseExistsAtPosition(map_id, position)
return false unless $Trainer.secretBase
current_outdoor_id = $Trainer.secretBase.outside_map_id
current_outdoor_coordinates = $Trainer.secretBase.outside_entrance_position
return current_outdoor_id == map_id && current_outdoor_coordinates == position
end
def createSecretBaseHere(biomeType, secretBaseMap = 0, baseLayoutType = :TYPE_1)
if pbConfirmMessage(_INTL("Do you want to create a new secret base here?"))
if $Trainer.secretBase
unless pbConfirmMessage(_INTL("This will overwrite your current secret base. Do you still wish to continue?"))
return
end
end
current_map_id = $game_map.map_id
current_position = [$game_player.x, $game_player.y]
$Trainer.secretBase = initialize_player_secret_base(biomeType, current_map_id, current_position, secretBaseMap, baseLayoutType)
setupAllSecretBaseEntrances
end
end
def initialize_player_secret_base(biome_type, outside_map_id, outside_position, base_map_id, layout_shape)
return SecretBase.new(
biome: biome_type,
outside_map_id: outside_map_id,
outside_entrance_position: outside_position,
inside_map_id: base_map_id,
base_layout_type: layout_shape,
is_visitor: false
)
end
#For when called from Scene_Map
def placeFurnitureMenu
controller = getSecretBaseController
controller.placeFurnitureMenu
end
def rotate_held_furniture_right
return unless $game_temp.moving_furniture
pbSEPlay("GUI party switch", 80, 100)
directionFix = $game_player.direction_fix
$game_player.direction_fix = false
$game_player.turn_right_90
$game_player.direction_fix=directionFix
end
def rotate__held_furniture_left
return unless $game_temp.moving_furniture
pbSEPlay("GUI party switch", 80, 100)
directionFix = $game_player.direction_fix
$game_player.direction_fix = false
$game_player.turn_left_90
$game_player.direction_fix=directionFix
end
def exitSecretBase()
controller = getSecretBaseController
return if controller&.isMovingFurniture?
pbStartOver if !$Trainer.secretBase || !$Trainer.secretBase.outside_map_id || !$Trainer.secretBase.outside_entrance_position
# Should never happen, but just in case
enteredSecretBase = getEnteredSecretBase
if enteredSecretBase && enteredSecretBase.is_a?(SecretBase)
outdoor_id = enteredSecretBase.outside_map_id
outdoor_coordinates = enteredSecretBase.outside_entrance_position
else
#Fallback on player's base
outdoor_id = $Trainer.secretBase.outside_map_id
outdoor_coordinates = $Trainer.secretBase.outside_entrance_position
end
$PokemonTemp.pbClearTempEvents
pbFadeOutIn {
$game_temp.player_new_map_id = outdoor_id
$game_temp.player_new_x = outdoor_coordinates[0]
$game_temp.player_new_y = outdoor_coordinates[1]
$scene.transfer_player(true)
$game_map.autoplay
$game_map.refresh
}
$PokemonTemp.pbClearTempEvents
$PokemonTemp.enteredSecretBaseController=nil
setupAllSecretBaseEntrances
end
def enterSecretBase()
event = $game_map.events[@event_id]
return if event.nil?
if event.variable && event.variable.is_a?(SecretBase)
secretBase = event.variable
else
secretBase= $Trainer.secretBase
end
controller = SecretBaseController.new(secretBase)
$PokemonTemp.enteredSecretBaseController = controller
return unless secretBase.is_a?(SecretBase)
$PokemonTemp.pbClearTempEvents
pbFadeOutIn {
$game_temp.player_new_map_id = MAP_SECRET_BASES
$game_temp.player_new_x = secretBase.inside_entrance_position[0]
$game_temp.player_new_y = secretBase.inside_entrance_position[1]
$scene.transfer_player(true)
$game_map.autoplay
SecretBaseLoader.new.loadSecretBaseFurniture(secretBase)
$game_map.refresh
}
end
def obtain_all_decorations
$Trainer.owned_decorations = [] unless $Trainer.owned_decorations
SecretBasesData::SECRET_BASE_ITEMS.keys.each do |item_id|
obtain_decoration_silent(item_id)
end
end
def obtain_decoration(item_id)
$Trainer.owned_decorations = [] unless $Trainer.owned_decorations
if SecretBasesData::SECRET_BASE_ITEMS[item_id]
obtainDecorationMessage(item_id)
$Trainer.owned_decorations << item_id
end
end
def obtain_decoration_silent(item_id)
$Trainer.owned_decorations = [] unless $Trainer.owned_decorations
if SecretBasesData::SECRET_BASE_ITEMS[item_id]
$Trainer.owned_decorations << item_id
end
end
def give_starting_decorations
furniture = [
:PLANT,:RED_CHAIR
]
obtain_decoration_silent(:PC)
furniture.each do |item|
obtain_decoration(item)
end
end
def obtainDecorationMessage(item_id)
decoration = SecretBasesData::SECRET_BASE_ITEMS[item_id]
pictureViewport = showDecorationPicture(item_id)
musical_effect = "Key item get"
pbMessage(_INTL("\\me[{1}]You obtained a \\c[1]{2}\\c[0]!", musical_effect, decoration.real_name))
pictureViewport.dispose if pictureViewport
end
def showDecorationPicture(item_id)
begin
decoration = SecretBasesData::SECRET_BASE_ITEMS[item_id]
path = "Graphics/Characters/player/secretBases/#{decoration.graphics}"
viewport = Viewport.new(Graphics.width / 4, 0, Graphics.width / 2, Graphics.height)
bg_sprite = Sprite.new(viewport)
decoration_sprite = Sprite.new(viewport)
echoln path
echoln pbResolveBitmap(path)
if pbResolveBitmap(path)
sheet = Bitmap.new(path)
# Character sheets are 4x4
frame_width = sheet.width / 4
frame_height = sheet.height / 4
# First frame = top-left corner (row 0, col 0)
rect = Rect.new(0, 0, frame_width, frame_height)
# Copy that frame into its own bitmap
cropped = Bitmap.new(frame_width, frame_height)
cropped.blt(0, 0, sheet, rect)
decoration_sprite.bitmap = cropped
end
bg_bitmap = AnimatedBitmap.new("Graphics/Pictures/Outfits/obtain_bg")
bg_sprite.bitmap = bg_bitmap.bitmap
decoration_sprite.x = 92
decoration_sprite.y = 50
decoration_sprite.zoom_x = 2
decoration_sprite.zoom_y = 2
bg_sprite.x = 0
viewport.z = 99999
return viewport
rescue
end
end

View File

@@ -0,0 +1,38 @@
TEMPLATE_EVENT_SECRET_BASE_ENTRANCE_TREE = 4
TEMPLATE_EVENT_SECRET_BASE_ENTRANCE_CAVE = 5
TEMPLATE_EVENT_SECRET_BASE_ENTRANCE_BUSH = 6
module SecretBasesData
# rareness: The higher, the more common
SECRET_BASE_ENTRANCES = {
:TYPE_SMALL_1 => { position: [8, 12], rareness: 1 },
:TYPE_SMALL_2 => { position: [35, 12], rareness: 1 },
:TYPE_SMALL_3 => { position: [55, 12], rareness: 1 },
:TYPE_SMALL_4 => { position: [75, 12], rareness: 1 },
:TYPE_SMALL_5 => { position: [101, 12], rareness: 1 },
:TYPE_SMALL_6 => { position: [124, 12], rareness: 1 },
:TYPE_SMALL_7 => { position: [12, 122], rareness: 1 },
:TYPE_SMALL_8 => { position: [36, 121], rareness: 1 },
:TYPE_SMALL_9 => { position: [65, 121], rareness: 1 },
:TYPE_SMALL_10 => { position: [94, 123], rareness: 1 },
:TYPE_SMALL_11 => { position: [121, 124], rareness: 1 },
:TYPE_WIDE_1 => { position: [11, 34], rareness: 0.3 },
:TYPE_WIDE_2 => { position: [43, 34], rareness: 0.2 },
:TYPE_WIDE_3 => { position: [72, 34], rareness: 0.2 },
:TYPE_WIDE_4 => { position: [106, 34], rareness: 0.2 },
:TYPE_TALL_1 => { position: [7, 71], rareness: 0.3 },
:TYPE_TALL_2 => { position: [31, 71], rareness: 0.2 },
:TYPE_TALL_3 => { position: [53, 71], rareness: 0.2 },
:TYPE_TALL_4 => { position: [85, 71], rareness: 0.2 },
:TYPE_TALL_5 => { position: [109, 71], rareness: 0.2 },
:TYPE_SPECIAL_1 => { position: [11, 98], rareness: 0.05 },
:TYPE_SPECIAL_2 => { position: [40, 97], rareness: 0.05 },
:TYPE_SPECIAL_3 => { position: [68, 98], rareness: 0.05 },
:TYPE_SPECIAL_4 => { position: [92, 99], rareness: 0.05 },
}
end

View File

@@ -0,0 +1,225 @@
# frozen_string_literal: true
module SecretBasesData
SECRET_BASE_ITEMS = {}
def SecretBasesData::register_base_item(id, **kwargs)
SECRET_BASE_ITEMS[id] = SecretBaseItem.new(id: id, **kwargs)
end
register_base_item(
:PC,
graphics: "Furniture/pc.png",
real_name: "PC",
deletable: false,
price: 0,
behavior: ->(event = nil) {
#Behavior for PC is handled in SecretBasesController
#useSecretBasePC
}
)
register_base_item(
:MANNEQUIN,
graphics: "Furniture/mannequin.png",
real_name: _INTL("Mannequin"),
price: 500,
behavior: ->(event = nil) {
useSecretBaseMannequin
}
)
register_base_item(
:PLANT,
graphics: "Furniture/plant.png",
real_name: _INTL("Decorative Plant"),
price: 500
)
register_base_item(
:RED_CHAIR,
graphics: "Furniture/red_chair.png",
real_name: _INTL("Red Chair"),
price: 350,
trigger: TRIGGER_PLAYER_TOUCH,
behavior: ->(itemInstance = nil) {
sit_on_chair(itemInstance)
}
)
register_base_item(
:FANCY_CARPET,
graphics: "Carpets/fancy_carpet.png",
real_name: _INTL("Fancy Carpet"),
price: 5000,
pass_through: true,
under_player: true
)
register_base_item(
:FANCY_CARPET_CONNECT,
graphics: "Carpets/fancy_carpet_connect.png",
real_name: _INTL("Fancy Carpet (Connection)"),
price: 100,
pass_through: true,
under_player: true
)
register_base_item(
:BOULDER,
graphics: "Furniture/boulder.png",
real_name: _INTL("Boulder"),
price: 600,
under_player: false,
behavior: ->(itemInstance = nil) {
pbStrength
if $PokemonMap.strengthUsed
pushEvent(itemInstance)
end
}
)
#Skitty set
register_base_item(
:SKITTY_CHAIR_3x3,
graphics: "skittySet/deco_3x3chair_skitty.png",
real_name: _INTL("Skitty Armchair"),
price: 1000,
height: 1,
width: 3,
trigger: TRIGGER_PLAYER_TOUCH,
behavior: ->(itemInstance = nil) {
sit_on_chair(itemInstance)
},
uninteractable_positions: [[-1,0],[1,0]]
)
register_base_item(
:SKITTY_CHAIR_3x3,
graphics: "skittySet/deco_3x3chair_skitty.png",
real_name: _INTL("Skitty Armchair"),
price: 1000,
height: 1,
width: 3,
trigger: TRIGGER_PLAYER_TOUCH,
behavior: ->(itemInstance = nil) {
sit_on_chair(itemInstance)
},
uninteractable_positions: [[-1,0],[1,0]]
)
register_base_item(
:SKITTY_COUCH_3x4,
graphics: "skittySet/deco_3x4chair_skitty.png",
real_name: _INTL("Skitty Couch"),
price: 2000,
height: 1,
width: 4,
trigger: TRIGGER_PLAYER_TOUCH,
behavior: ->(itemInstance = nil) {
sit_on_chair(itemInstance)
},
uninteractable_positions: [[-2,0],[2,0]]
)
register_base_item(
:SKITTY_COUCH_3x5,
graphics: "skittySet/deco_3x5couch_skitty.png",
real_name: _INTL("Wide Skitty Couch"),
price: 2000,
height: 1,
width: 5,
trigger: TRIGGER_PLAYER_TOUCH,
behavior: ->(itemInstance = nil) {
sit_on_chair(itemInstance)
},
uninteractable_positions: [[-2,0],[2,0]]
)
register_base_item(
:SKITTY_RUG_3x3,
graphics: "skittySet/deco_3x3rug_skitty.png",
real_name: _INTL("Large Skitty Rug"),
price: 3000,
pass_through: true,
under_player: true
)
#Rock set
register_base_item(
:ROCK_CHAIR_1x1,
graphics: "rockSet/deco_1x1chair_rock.png",
real_name: _INTL("Rocky Stool"),
price: 350,
trigger: TRIGGER_PLAYER_TOUCH,
behavior: ->(itemInstance = nil) {
sit_on_chair(itemInstance)
}
)
register_base_item(
:ROCK,
graphics: "rockSet/deco_1x1deco_rock.png",
real_name: _INTL("Rock"),
price: 50
)
register_base_item(
:ROCK_STATUE,
graphics: "rockSet/deco_1x1statue_rock.png",
real_name: _INTL("Rocky Statue"),
price: 50
)
register_base_item(
:ROCK_WALL,
graphics: "rockSet/deco_1x2wall_rock.png",
real_name: _INTL("Rocky Wall"),
price: 50
)
register_base_item(
:ROCK_TABLE_2x3,
graphics: "rockSet/deco_2x3table_rock.png",
real_name: _INTL("Large Rocky Table"),
width:3,
height:2,
price: 5000
)
register_base_item(
:ROCK_CHAIR_3x3,
graphics: "rockSet/deco_3x3chair_rock.png",
real_name: _INTL("Rocky Armchair"),
price: 1000,
height: 1,
width: 3,
trigger: TRIGGER_PLAYER_TOUCH,
behavior: ->(itemInstance = nil) {
sit_on_chair(itemInstance)
},
uninteractable_positions: [[-1,0],[1,0]]
)
register_base_item(
:ROCK_RUG_1x1,
graphics: "rockSet/deco_1x1rug_rock.png",
real_name: _INTL("Small Rocky Rug"),
price: 500,
pass_through: true,
under_player: true
)
register_base_item(
:ROCK_RUG_3x3,
graphics: "rockSet/deco_3x3rug_rock.png",
real_name: _INTL("Large Rocky Rug"),
price: 2000,
pass_through: true,
under_player: true
)
end

View File

@@ -0,0 +1,19 @@
class Game_Map
SECRET_BASE_TILESET_OVERRIDES = {
:BUSH => 100,
:CAVE => 101,
:TREE => 102,
}
alias __tileset_swap_updateTileset updateTileset
def updateTileset
if @map_id == MAP_SECRET_BASES && $Trainer.secretBase
override = SECRET_BASE_TILESET_OVERRIDES[$Trainer.secretBase.biome_type]
@map.tileset_id = override if override
end
__tileset_swap_updateTileset
end
end

View File

@@ -0,0 +1,200 @@
# For more complex item behaviors - to keep things organized
def useSecretBaseMannequin
# Todo: This is the item that players can use to "place themselves" in the base.
# When a base has a mannequin, the base will be shared online and
# the mannequin will appear as the player in other people's games.
secretBase = getEnteredSecretBase
if secretBase && secretBase.is_visitor
interact_other_player(secretBase)
else
secret_base_mannequin_menu(secretBase)
end
return
end
def secret_base_mannequin_menu(secretBase)
friend_bases = SecretBaseLoader.new.list_friend_bases
cmd_share = _INTL("Share your secret base")
cmd_import = _INTL("Import a friend's secret base")
cmd_removeFriend = _INTL("Remove a friend's secret base")
cmd_setTeam = _INTL("Set your base's Team")
cmd_trainerID = _INTL("[DEBUG] Copy your Trainer ID")
cmd_export = _INTL("[DEBUG] Export base to clipboard")
cmd_cancel = _INTL("Cancel")
commands = [cmd_share, cmd_setTeam, cmd_import]
commands << cmd_removeFriend if !friend_bases.empty?
commands << cmd_trainerID if $DEBUG
commands << cmd_export if $DEBUG
commands << cmd_cancel
pbMessage(_INTL("What would you like to do?"))
choice = optionsMenu(commands)
case commands[choice]
when cmd_share
pbMessage(_INTL("Once you share you base, it may randomly appear in other player's games."))
pbMessage(_INTL("The other players will be able to see your character's name, your base's layout, your custom message, and battle your team."))
continue = pbConfirmMessage(_INTL("You can only share your secret base once per day. Would you like to continue and publish your current secret base? (Your game will save automatically afterwards)"))
if continue
begin
exporter = SecretBaseExporter.new
json = exporter.export_secret_base(secretBase)
publisher = SecretBasePublisher.new
publisher.register unless $Trainer.secretBase_uuid
publisher.upload_base(json)
pbSEPlay('GUI save choice')
pbMessage(_INTL("Your secret base was shared successfully!"))
rescue Exception => e
echoln e
pbMessage(_INTL("There was a problem uploading your Secret Base. The operation was cancelled."))
end
end
when cmd_import
friend_code = input_friend_code
if friend_code
fetcher = SecretBaseFetcher.new
begin
fetcher.import_friend_base(friend_code)
loader = SecretBaseLoader.new
loader.load_visitor_bases
pbMessage(_INTL("Your friend's base was imported!"))
rescue
pbMessage(_INTL("There was a problem, your friend's secret base was not imported."))
end
end
when cmd_setTeam
when cmd_trainerID
Input.clipboard = $Trainer.id.to_s
pbMessage(_INTL("Your Trainer ID was copied to the clipboard!"))
when cmd_export
exporter = SecretBaseExporter.new
json = exporter.export_secret_base($Trainer.secretBase)
Input.clipboard = json
end
end
def check_copied_own_trainerId(clipboard_text)
if clipboard_text == $Trainer.id.to_s
pbMessage(_INTL("The trainer ID you copied is your own! You need to have your friend copy theirs and send it to you."))
return true
end
return false
end
def input_friend_code()
example = showPicture("Graphics/Pictures/Trainer Card/trainerID_example",0,0,0)
cmd_refresh = _INTL("Refresh")
cmd_confirm = _INTL("Confirm")
cmd_manual = _INTL("Enter manually")
cmd_cancel = _INTL("Cancel")
loop do
commands = []
clipboard_text = Input.clipboard || ""
clipboard_text = clipboard_text.slice(0, 10)
if numeric_string?(clipboard_text) && clipboard_text.length == 10 && !check_copied_own_trainerId(clipboard_text)
message = _INTL("Is this your friend's Trainer ID? \\C[1]#{clipboard_text}\\C[0]")
commands << cmd_refresh
commands << cmd_confirm
commands << cmd_manual
commands << cmd_cancel
else
message = _INTL("Copy your friend's ID and choose 'Refresh'. The game will detect it from your clipboard.")
commands << cmd_refresh
commands << cmd_manual
commands << cmd_cancel
end
choice = pbMessage(message, commands,commands.length)
case commands[choice]
when cmd_refresh
next
when cmd_confirm
example.dispose
return clipboard_text
when cmd_manual
friend_trainer_id = pbEnterText("Friend's Trainer ID", 10, 10, clipboard_text)
unless numeric_string?(friend_trainer_id)
pbMessage(_INTL("The Trainer ID you entered is not valid. Trainer IDs are composed of 10 numbers."))
end
Input.clipboard = friend_trainer_id
else
example.dispose
return nil
end
end
end
def interact_other_player(secretBase)
event = pbMapInterpreter.get_character(0)
event.direction_fix = false
event.turn_toward_player
message = secretBase.base_message
pbCallBub(3)
pbMessage(_INTL("Hey, I'm \\C[1]{1}\\C[0], welcome to my secret base!",secretBase.trainer_name))
if message
pbCallBub(3)
pbMessage(message)
end
end
def pushEvent(itemInstance)
event = itemInstance.getMainEvent
old_x = event.x
old_y = event.y
return if !event.can_move_in_direction?($game_player.direction, false)
case $game_player.direction
when 2 then event.move_down
when 4 then event.move_left
when 6 then event.move_right
when 8 then event.move_up
end
if old_x != event.x || old_y != event.y
$game_player.lock
loop do
Graphics.update
Input.update
pbUpdateSceneMap
break if !event.moving?
end
itemInstance.position = [event.x, event.y]
$PokemonTemp.pbClearTempEvents
$PokemonTemp.enteredSecretBaseController.reloadItems
$game_player.unlock
end
end
def sit_on_chair(itemInstance)
event=itemInstance.getMainEvent
pbSEPlay("jump", 80, 100)
$game_player.always_on_top=true
$game_player.through =true
$game_player.jump_forward
case event.direction
when DIRECTION_LEFT; $game_player.direction = DIRECTION_RIGHT
when DIRECTION_RIGHT; $game_player.direction = DIRECTION_LEFT
when DIRECTION_UP; $game_player.direction = DIRECTION_UP
when DIRECTION_DOWN; $game_player.direction = DIRECTION_DOWN
end
$game_player.through =false
loop do
Graphics.update
Input.update
pbUpdateSceneMap
if Input.trigger?(Input::UP) || Input.trigger?(Input::DOWN) || Input.trigger?(Input::LEFT) || Input.trigger?(Input::RIGHT)
$game_player.jump_forward
$game_player.always_on_top=false
break
end
end
end
# PC behavior set directly in SecretBaseController

View File

@@ -0,0 +1,73 @@
# Loads other player secret bases from a local file and places them into the world
# todo: only load max 5
class SecretBaseLoader
def initialize
@importer = SecretBaseImporter.new
end
def load_visitor_bases
visitor_bases = @importer.load_bases(SecretBaseImporter::VISITOR_BASES_FILE)
friend_bases = @importer.load_bases(SecretBaseImporter::FRIEND_BASES_FILE)
all_bases = visitor_bases + friend_bases
$game_temp.visitor_secret_bases = all_bases
end
def loadSecretBaseFurniture(secretBase)
return unless $scene.is_a?(Scene_Map)
secretBase.load_furniture
end
def list_friend_bases
return @importer.load_bases(SecretBaseImporter::FRIEND_BASES_FILE)
end
def list_visitor_bases
return @importer.load_bases(SecretBaseImporter::FRIEND_BASES_FILE)
end
end
class Game_Temp
attr_accessor :visitor_secret_bases
end
def setupAllSecretBaseEntrances
$PokemonTemp.pbClearTempEvents
if $Trainer && $Trainer.secretBase && $game_map.map_id == $Trainer.secretBase.outside_map_id
setupSecretBaseEntranceEvent($Trainer.secretBase)
end
if $game_temp.visitor_secret_bases && !$game_temp.visitor_secret_bases.empty?
$game_temp.visitor_secret_bases.each do |base|
if $game_map.map_id == base.outside_map_id
setupSecretBaseEntranceEvent(base)
end
end
end
end
# Called on map load
def setupSecretBaseEntranceEvent(secretBase)
warpPosition = secretBase.outside_entrance_position
entrancePosition = [warpPosition[0], warpPosition[1] - 1]
case secretBase.biome_type
when :TREE
template_event_id = TEMPLATE_EVENT_SECRET_BASE_ENTRANCE_TREE
when :CAVE
template_event_id = TEMPLATE_EVENT_SECRET_BASE_ENTRANCE_CAVE
when :BUSH
template_event_id = TEMPLATE_EVENT_SECRET_BASE_ENTRANCE_BUSH
else
template_event_id = TEMPLATE_EVENT_SECRET_BASE_ENTRANCE_CAVE
end
event = $PokemonTemp.createTempEvent(template_event_id, $game_map.map_id, entrancePosition)
event.setVariable(secretBase)
event.refresh
end
Events.onMapSceneChange += proc { |_sender, e|
next unless $PokemonTemp.tempEvents.empty?
setupAllSecretBaseEntrances
}

View File

@@ -0,0 +1,50 @@
#todo: limit of 10 at once
#todo: append new friends at the end of the list instead of overwriting everything
#todo: if the friend's id is already in there, update (overwrite) it
#
class SecretBaseFetcher
SECRETBASE_DOWNLOAD_URL = "https://secretbase-download.pkmninfinitefusion.workers.dev"
def import_friend_base(friend_player_id)
base_json = fetch_base(friend_player_id)
if base_json
save_friend_base(base_json)
else
pbMessage(_INTL("The game couldn't find your friend's base. Make sure that they published it and that you wrote their trainer ID correctly."))
raise "Secret Base does not exist"
end
end
# Fetch a secret base by playerID
def fetch_base(player_id)
url = "#{SECRETBASE_DOWNLOAD_URL}/get-base?playerID=#{player_id}"
begin
response = HTTPLite.get(url)
if response[:status] == 200
echoln "[SecretBase] Downloaded base for #{player_id}"
base_json = JSON.parse(response[:body])
return base_json
else
echoln "[SecretBase] Failed with status #{response[:status]} for #{player_id}"
return nil
end
rescue MKXPError => e
echoln "[SecretBase] MKXPError: #{e.message}"
return nil
rescue Exception => e
echoln "[SecretBase] Error: #{e.message}"
return nil
end
end
def save_friend_base(new_base)
exporter = SecretBaseExporter.new
exporter.write_base_json_to_file(new_base,SecretBaseImporter::FRIEND_BASES_FILE,true)
end
end

View File

@@ -0,0 +1,54 @@
class SecretBasePublisher
def initialize()
@player_id = $Trainer.id
end
def register
if $Trainer.secretBase_uuid
echoln "Already registered!"
else
begin
payload = { playerID: @player_id }
url = "#{Settings::SECRETBASE_UPLOAD_URL}/register"
response = pbPostToString(url,payload)
echoln response
json = JSON.parse(response) rescue {}
secret_uuid = json[:secretUUID]
echoln json
$Trainer.secretBase_uuid = secret_uuid
echoln $Trainer.secretBase_uuid
Game.save
rescue Exception => e
echoln e
end
end
return $Trainer.secretBase_uuid
end
#Trainer needs to be registered before this is called
def upload_base(base_json)
secret_uuid = $Trainer.secretBase_uuid
echoln secret_uuid
unless $Trainer.secretBase_uuid
echoln "Trainer not registered!"
pbMessage(_INTL("The base could not be uploaded"))
end
payload = {
playerID: @player_id,
secretUUID: secret_uuid,
baseJSON: base_json
}
url = "#{Settings::SECRETBASE_UPLOAD_URL}/upload-base"
response = pbPostToString(url,payload)
echoln response
json = JSON.parse(response) rescue {}
json["success"] == true
end
private
end

View File

@@ -0,0 +1,133 @@
class SecretBaseExporter
# Export a secret base as JSON
def export_secret_base(secretBase)
base_data = {
base: {
biome: secretBase.biome_type || "",
entrance_map: secretBase.outside_map_id || 0,
outside_entrance_position: secretBase.outside_entrance_position || [0, 0],
layout_type: secretBase.base_layout_type || "",
base_message: secretBase.base_message || "",
layout: {
items: list_base_items(secretBase)
}
},
trainer: {
name: $Trainer.name || "",
id: $Trainer.id,
nb_badges: $Trainer.badge_count || 0,
game_mode: $Trainer.game_mode || 0,
appearance: sanitize_string(export_current_outfit_to_json),
team: export_team_as_array
}
}
JSON.generate(sanitize_string(base_data))
end
# Export all items in the base layout
def list_base_items(secretBase)
return [] unless secretBase&.layout&.items
secretBase.layout.items.map do |item|
{
id: item.itemId || "",
position: item.position || [0, 0],
direction: item.direction || DIRECTION_DOWN,
}
end
end
def write_base_json_to_file(new_base_json, file_path, append = true)
ensure_folder_exists(File.dirname(file_path))
# Parse new_base JSON string into a Ruby object
begin
new_base = JSON.parse(new_base_json)
rescue Exception => e
echoln "[SecretBase] Failed to parse new base JSON: #{e.message}"
return
end
bases = []
if File.exist?(file_path)
begin
file_content = File.read(file_path).strip
if !file_content.empty?
# parse existing content into array
bases = JSON.parse(file_content)
bases = [] unless bases.is_a?(Array)
end
rescue Exception => e
echoln "[SecretBase] Error reading existing file: #{e.message}"
bases = []
end
end
# Append or replace
if append
bases << new_base
else
bases = [new_base]
end
# Write back
File.open(file_path, "w") do |file|
file.write(JSON.generate(bases))
end
echoln "[SecretBase] Saved base to #{file_path}"
end
# Export the trainer's Pokémon party
def export_team_as_array
$Trainer.party.compact.map { |p| export_fused_pokemon_hash(p) }
end
# Export a single Pokémon as a hash
def export_fused_pokemon_hash(pokemon)
{
species: pokemon.species.to_s,
name: pokemon.name || "",
item: pokemon.item ? pokemon.item.id.to_s : "",
ability: pokemon.ability ? pokemon.ability.id.to_s : "",
level: pokemon.level || 1,
evs: {
hp: pokemon.ev[:HP] || 0,
atk: pokemon.ev[:ATTACK] || 0,
def: pokemon.ev[:DEFENSE] || 0,
spa: pokemon.ev[:SPECIAL_ATTACK] || 0,
spd: pokemon.ev[:SPECIAL_DEFENSE] || 0,
spe: pokemon.ev[:SPEED] || 0
},
ivs: {
hp: pokemon.iv[:HP] || 0,
atk: pokemon.iv[:ATTACK] || 0,
def: pokemon.iv[:DEFENSE] || 0,
spa: pokemon.iv[:SPECIAL_ATTACK] || 0,
spd: pokemon.iv[:SPECIAL_DEFENSE] || 0,
spe: pokemon.iv[:SPEED] || 0
},
nature: GameData::Nature.get(pokemon.nature).id.to_s,
moves: pokemon.moves.compact.map { |m| GameData::Move.get(m.id).id.to_s }
}
end
private
# Recursively replace nils with empty strings (or zero if numeric leaf)
def sanitize_string(obj)
case obj
when Hash
obj.transform_values { |v| sanitize_string(v) }
when Array
obj.map { |v| sanitize_string(v) }
when NilClass
""
else
obj
end
end
end

View File

@@ -0,0 +1,147 @@
class SecretBaseImporter
FRIEND_BASES_FILE = "Data/bases/friend_bases.json"
VISITOR_BASES_FILE = "Data/bases/visitor_bases.json"
def read_secret_base_json(path)
return [] unless File.exist?(path)
file_contents = File.read(path)
begin
# Parse with symbolized keys directly
raw = JSON.parse(file_contents)
return deep_clean(raw) # cleaned object, keys stay symbols
rescue Exception => e
echoln caller
echoln("SecretBaseImporter: Failed to parse JSON: #{e}")
return []
end
end
# Load all bases from the JSON
def load_bases(path)
all_bases_data = read_secret_base_json(path)
return [] unless all_bases_data.is_a?(Array)
# Only keep entries with a trainer
visitor_bases = []
all_bases_data.map do |entry|
begin
base_data = entry[:base]
trainer_data = entry[:trainer]
biome = base_data[:biome].to_sym
base = VisitorSecretBase.new(
biome: biome,
outside_map_id: base_data[:entrance_map],
outside_entrance_position: base_data[:outside_entrance_position],
inside_map_id: base_data[:inside_map_id],
layout: import_layout_from_json(base_data[:layout],biome),
base_layout_type: base_data[:layout_type],
trainer_data: import_trainer_from_json(trainer_data),
base_message: base_data[:base_message],
)
echoln base.layout
visitor_bases << base
#base.dump_info
rescue Exception => e
echoln "COULD NOT LOAD BASE: #{e}"
end
end
return visitor_bases
end
def import_layout_from_json(layout_json, biome)
layout = SecretBaseLayout.new(
biome,
false
)
items = []
(layout_json[:items] || []).each do |item_data|
id = item_data[:id].to_sym
position = item_data[:position]
direction = item_data[:direction]
item_instance = SecretBaseItemInstance.new(id,position,direction)
echoln item_instance.direction
items << item_instance
end
echoln items
layout.items = items
return layout
end
def import_trainer_from_json(trainer_json)
app = trainer_json[:appearance]
trainer_appearance = TrainerAppearance.new(
app[:skin_color], app[:hat], app[:clothes], app[:hair],
app[:hair_color], app[:clothes_color], app[:hat_color],
app[:hat2], app[:hat2_color]
)
team = trainer_json[:team].map do |poke_json|
pokemon = Pokemon.new(poke_json[:species], poke_json[:level])
pokemon.name = poke_json[:name]
pokemon.item = poke_json[:item]
pokemon.ability = poke_json[:ability]
pokemon.nature = poke_json[:nature]
pokemon.moves = poke_json[:moves]
if poke_json[:ivs]
poke_json[:ivs].each do |stat, value|
case stat.to_s.downcase
when "hp" then pokemon.iv[:HP] = value
when "atk" then pokemon.iv[:ATTACK] = value
when "def" then pokemon.iv[:DEFENSE] = value
when "spe" then pokemon.iv[:SPEED] = value
when "spa" then pokemon.iv[:SPECIAL_ATTACK] = value
when "spd" then pokemon.iv[:SPECIAL_DEFENSE] = value
end
end
end
if poke_json[:evs]
poke_json[:evs].each do |stat, value|
case stat.to_s.downcase
when "hp" then pokemon.ev[:HP] = value
when "atk" then pokemon.ev[:ATTACK] = value
when "def" then pokemon.ev[:DEFENSE] = value
when "spe" then pokemon.ev[:SPEED] = value
when "spa" then pokemon.ev[:SPECIAL_ATTACK] = value
when "spd" then pokemon.ev[:SPECIAL_DEFENSE] = value
end
end
end
pokemon.calc_stats
pokemon
end
SecretBaseTrainer.new(
trainer_json[:name],
trainer_json[:nb_badges],
trainer_json[:game_mode],
trainer_appearance,
team
)
end
private
# Recursively converts "" → nil, but keeps keys as symbols
def deep_clean(obj)
case obj
when Hash
obj.transform_values { |v| deep_clean(v) }
when Array
obj.map { |v| deep_clean(v) }
when ""
nil
else
obj
end
end
end

View File

@@ -4,6 +4,7 @@ class BattledTrainer
attr_accessor :trainerType
attr_accessor :trainerName
attr_accessor :trainerKey
attr_accessor :currentTeam #list of Pokemon. The game selects in this list for trade offers. They can increase levels & involve as you rebattle them.
@@ -19,8 +20,6 @@ class BattledTrainer
#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
@@ -47,7 +46,8 @@ class BattledTrainer
attr_accessor :friendship #increases the more you interact with them, unlocks more interact options
attr_accessor :friendship_level
def initialize(trainerType,trainerName,trainerVersion)
def initialize(trainerType,trainerName,trainerVersion,trainerKey)
@trainerKey = trainerKey
@trainerType = trainerType
@trainerName = trainerName
@currentTeam = loadOriginalTrainerTeam(trainerVersion)
@@ -83,7 +83,7 @@ class BattledTrainer
@friendship_level += 1
trainerClassName = GameData::TrainerType.get(@trainerType).real_name
pbMessage(_INTL("\\C[3]Friendship increased with #{trainerClassName} #{@trainerName}!"))
pbMessage(_INTL("\\C[3]Friendship increased with {1} {2}!",trainerClassName,@trainerName))
case @friendship_level
when 1
pbMessage(_INTL("You can now trade with each other!"))

View File

@@ -30,7 +30,7 @@ end
def registerBattledTrainer(event_id, mapId, trainerType, trainerName, trainerVersion=0)
key = [event_id,mapId]
$PokemonGlobal.battledTrainers = {} unless $PokemonGlobal.battledTrainers
trainer = BattledTrainer.new(trainerType, trainerName, trainerVersion)
trainer = BattledTrainer.new(trainerType, trainerName, trainerVersion,key)
$PokemonGlobal.battledTrainers[key] = trainer
return trainer
end
@@ -58,13 +58,24 @@ end
def updateRebattledTrainer(event_id,map_id,updated_trainer)
key = [event_id,map_id]
updateRebattledTrainerWithKey(key,updated_trainer)
end
def updateRebattledTrainerWithKey(key,updated_trainer)
$PokemonGlobal.battledTrainers = {} if !$PokemonGlobal.battledTrainers
$PokemonGlobal.battledTrainers[key] = updated_trainer
end
def getRebattledTrainer(event_id,map_id)
key = [event_id,map_id]
def getRebattledTrainerKey(event_id, map_id)
return [event_id,map_id]
end
def getRebattledTrainerFromKey(key)
$PokemonGlobal.battledTrainers = {} if !$PokemonGlobal.battledTrainers
return $PokemonGlobal.battledTrainers[key]
end
def getRebattledTrainer(event_id,map_id)
key = getRebattledTrainerKey(event_id, map_id)
return getRebattledTrainerFromKey(key)
end

View File

@@ -59,16 +59,18 @@ def generateTrainerRematch(trainer)
trainer_data = GameData::Trainer.try_get(trainer.trainerType, trainer.trainerName, 0)
loseDialog = trainer_data&.loseText_rematch ? trainer_data.loseText_rematch : "..."
player_won = false
if customTrainerBattle(trainer.trainerName,trainer.trainerType, trainer.currentTeam,nil,loseDialog)
updated_trainer = makeRebattledTrainerTeamGainExp(trainer,true)
updated_trainer = healRebattledTrainerPokemon(updated_trainer)
player_won=true
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
return updated_trainer, player_won
end
def showPrerematchDialog()
@@ -116,6 +118,7 @@ def showPrerematchDialog()
split_messages = message_text.split("<br>")
split_messages.each do |msg|
pbCallBub(2,event.id)
pbCallBub(3) if isPartneredWithTrainer(trainer)
pbMessage(msg)
end
end

View File

@@ -32,7 +32,7 @@ def doPostBattleAction(actionType)
return if !trainer
case actionType
when :BATTLE
trainer = doNPCTrainerRematch(trainer)
trainer,player_won = doNPCTrainerRematch(trainer)
when :TRADE
trainer = doNPCTrainerTrade(trainer)
when :PARTNER
@@ -46,7 +46,7 @@ def setTrainerFriendship(trainer)
params = ChooseNumberParams.new
params.setRange(0,100)
params.setDefaultValue($game_map.map_id)
number = pbMessageChooseNumber("Frienship (0-100)?",params)
number = pbMessageChooseNumber(_INTL("Frienship (0-100)?"),params)
trainer.friendship = number
trainer.increase_friendship(0)
return trainer
@@ -57,15 +57,15 @@ end
#
#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!"
rematchCommand = _INTL("Rematch")
tradeCommand = _INTL("Trade Offer")
partnerCommand = _INTL("Partner up")
cancelCommand = _INTL("See ya!")
updateTeamDebugCommand = "(Debug) Simulate random event"
resetTrainerDebugCommand = "(Debug) Reset trainer"
setFriendshipDebugCommand = "(Debug) Set Friendship"
printTrainerTeamDebugCommand = "(Debug) Print team"
updateTeamDebugCommand = _INTL("(Debug) Simulate random event")
resetTrainerDebugCommand = _INTL("(Debug) Reset trainer")
setFriendshipDebugCommand = _INTL("(Debug) Set Friendship")
printTrainerTeamDebugCommand = _INTL("(Debug) Print team")
event = pbMapInterpreter.get_character(0)
@@ -116,3 +116,22 @@ def postBattleActionsMenu()
end
end
#leave event_type empty for random
def forceRandomRematchEventOnTrainer(event_type=nil)
event = pbMapInterpreter.get_character(0)
map_id = $game_map.map_id if map_id.nil?
trainer = getRebattledTrainer(event.id,map_id)
while !trainer.has_pending_action
trainer = applyTrainerRandomEvents(trainer,event_type)
end
updateRebattledTrainer(event.id,map_id,trainer)
end
def forceTrainerFriendshipOnTrainer(friendship=0)
event = pbMapInterpreter.get_character(0)
map_id = $game_map.map_id if map_id.nil?
trainer = getRebattledTrainer(event.id,map_id)
trainer.friendship = friendship
trainer.increase_friendship(0)
updateRebattledTrainer(event.id,map_id,trainer)
end

View File

@@ -1,7 +1,13 @@
COMMON_EVENT_TRAINER_REMATCH_PARTNER = 200
def partnerWithTrainer(eventId, mapID, trainer)
Kernel.pbAddDependency2(eventId,trainer.trainerName,COMMON_EVENT_TRAINER_REMATCH_PARTNER)
SWITCH_PARTNERED_WITH_NPC_TRAINER = 2049
class Trainer
attr_accessor :npcPartner
end
def partnerWithTrainer(eventId, mapID, trainer,trainer_key=nil ,common_event=nil)
common_event = COMMON_EVENT_TRAINER_REMATCH_PARTNER if !common_event
Kernel.pbAddDependency2(eventId,trainer.trainerName,common_event)
pbCancelVehicles
originalTrainer = pbLoadTrainer(trainer.trainerType, trainer.trainerName, 0)
Events.onTrainerPartyLoad.trigger(nil, originalTrainer)
@@ -9,5 +15,51 @@ def partnerWithTrainer(eventId, mapID, trainer)
i.owner = Pokemon::Owner.new_from_trainer(originalTrainer)
i.calc_stats
end
trainer_key = getRebattledTrainerKey(eventId,mapID) if !trainer_key
$PokemonGlobal.partner = [trainer.trainerType, trainer.trainerName, 0, trainer.currentTeam]
$Trainer.npcPartner = trainer_key
end
def unpartnerWithTrainer()
pbRemoveDependencies
$game_switches[SWITCH_PARTNERED_WITH_NPC_TRAINER]=false
$Trainer.npcPartner=nil
end
def promptGiveToPartner(caughtPokemon)
return false if !$Trainer.npcPartner
return false if $Trainer.npcPartner == BATTLED_TRAINER_WALLY_KEY && $game_switches[SWITCH_WALLY_GAVE_POKEMON]
if $Trainer.npcPartner == BATTLED_TRAINER_WALLY_KEY && caughtPokemon.isFusion?
pbMessage(_INTL("I... I don't think I can handle a fused Pokémon. Can we try to catch a different one?"))
return
end
partnerTrainer = getRebattledTrainerFromKey($Trainer.npcPartner)
return false if $Trainer.npcPartner == BATTLED_TRAINER_WALLY_KEY && partnerTrainer.currentTeam.length > 0
return false if !partnerTrainer
command = pbMessage(_INTL("Would you like to give the newly caught {1} to {2}?",caughtPokemon.name,partnerTrainer.trainerName),
[_INTL("Keep"),_INTL("Give to {1}",partnerTrainer.trainerName)], 2)
case command
when 0 # Keep
return
else
# Give
pbMessage(_INTL("You gave the {1} to {2}!",caughtPokemon.name,partnerTrainer.trainerName))
if partnerTrainer.currentTeam.length == 6
partnerTrainer.currentTeam[-1] = caughtPokemon
else
partnerTrainer.currentTeam << caughtPokemon
end
partnerTrainer.increase_friendship(10)
updateRebattledTrainerWithKey($Trainer.npcPartner,partnerTrainer)
if $Trainer.npcPartner == BATTLED_TRAINER_WALLY_KEY
$game_switches[SWITCH_WALLY_GAVE_POKEMON_DIALOGUE]=true
end
end
end
def isPartneredWithTrainer(trainer)
return $Trainer.npcPartner == trainer.trainerKey
end
def isPartneredWithAnyTrainer()
return $Trainer.npcPartner != nil
end

View File

@@ -12,7 +12,7 @@ def printNPCTrainerCurrentTeam(trainer)
echoln "Trainer's current team is: #{team_string}"
end
def applyTrainerRandomEvents(trainer)
def applyTrainerRandomEvents(trainer,event_type=nil)
if trainer.has_pending_action
echoln "Trainer has pending action"
end
@@ -28,20 +28,21 @@ def applyTrainerRandomEvents(trainer)
[:CATCH, 3],
[:FUSE, 6],
[:REVERSE, 1],
[:UNFUSE, 20]
[:UNFUSE, 2]
]
# Create a flat array of events based on weight
event_pool = weighted_events.flat_map { |event, weight| [event] * weight }
selected_event = event_pool.sample
selected_event = event_type if event_type
if selected_event
echoln "Trying to do random event: #{selected_event}"
end
return trainer if selected_event.nil?
original_team = trainer.currentTeam.clone
case selected_event
when :CATCH
@@ -53,7 +54,12 @@ def applyTrainerRandomEvents(trainer)
when :REVERSE
trainer = reverse_random_team_pokemon(trainer)
end
trainer.set_pending_action(true)
new_team = trainer.currentTeam
echoln original_team
echoln new_team
team_changed = original_team != new_team
trainer.set_pending_action(team_changed)
printNPCTrainerCurrentTeam(trainer)
return trainer
end

View File

@@ -217,7 +217,7 @@ def generateTrainerTradeOffer(trainer)
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?."),)
pbMessage(_INTL("{1} {2} is looking for {3}-type Pokémon. Which Pokémon do you want to trade?.", trainerClassName, trainer.trainerName, wanted_type_name))
pbChoosePokemon(1,2,
proc {|pokemon|
pokemon.hasType?(wanted_type)
@@ -229,17 +229,17 @@ def generateTrainerTradeOffer(trainer)
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..."))
pbMessage(_INTL("{1} {2} does not want to trade...", trainerClassName, trainer.trainerName))
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}.")
message = _INTL("{1} {2} is offering {3} (Level {4}) for your {5}.", trainerClassName, trainer.trainerName, offered_pokemon.name, offered_pokemon.level, chosen_pokemon.name)
showPokemonInPokeballWithMessage(pif_sprite, message)
if pbConfirmMessage(_INTL("Trade away #{chosen_pokemon.name} for #{trainerClassName} #{trainer.trainerName}'s #{offered_pokemon.name}?"))
if pbConfirmMessage(_INTL("Trade away {1} for {2} {3}'s {4}?", chosen_pokemon.name, trainerClassName, trainer.trainerName, offered_pokemon.name))
pbStartTrade(chosen_index, offered_pokemon,offered_pokemon.name,trainer.trainerName,0)
updated_party = trainer.currentTeam
updated_party.delete(offered_pokemon)

View File

@@ -0,0 +1,73 @@
class PokemonStorage
end
class StorageTransferBox < PokemonBox
TRANSFER_BOX_NAME = _INTL("Transfer Box")
def initialize()
super(TRANSFER_BOX_NAME,PokemonBox::BOX_SIZE)
@pokemon = []
@background = 16
for i in 0...PokemonBox::BOX_SIZE
@pokemon[i] = nil
end
loadTransferBoxPokemon
end
def loadTransferBoxPokemon
path = transferBoxSavePath
if File.exist?(path)
File.open(path, "rb") do |f|
@pokemon = Marshal.load(f)
end
end
rescue => e
echoln "Failed to load transfer box: #{e}"
@pokemon = Array.new(PokemonBox::BOX_SIZE, nil)
end
def []=(i,value)
@pokemon[i] = value
saveTransferBox()
Game.save()
end
def saveTransferBox
path = transferBoxSavePath
dir = File.dirname(path)
Dir.mkdir(dir) unless Dir.exist?(dir)
File.open(path, "wb") do |f|
Marshal.dump(@pokemon, f)
end
echoln "Transfer box saved to #{path}"
$game_temp.must_save_now=true
rescue => e
echoln "Failed to save transfer box: #{e}"
end
private
def transferBoxSavePath
save_dir = System.data_directory # e.g., %appdata%/infinitefusion
parent_dir = File.expand_path("..", save_dir)
File.join(parent_dir, "infinitefusion_common", "transfer_pokemon_storage")
end
end
#Never add more than 1, it would just be a copy
def addPokemonStorageTransferBox()
$PokemonStorage.boxes << StorageTransferBox.new
end
def verifyTransferBoxAutosave()
if !$game_temp.transfer_box_autosave
confirmed = pbConfirmMessage(_INTL("Moving Pokémon in and out of the transfer box will save the game automatically. Is this okay?"))
$game_temp.transfer_box_autosave=true if confirmed
return confirmed
end
return true
end