diff --git a/Data/Scripts/002_Save data/001_SaveData.rb b/Data/Scripts/002_Save data/001_SaveData.rb index 0ddc1eed0..92f188414 100644 --- a/Data/Scripts/002_Save data/001_SaveData.rb +++ b/Data/Scripts/002_Save data/001_SaveData.rb @@ -6,6 +6,8 @@ # @see SaveData.register_conversion #=============================================================================== module SaveData + DIRECTORY = (File.directory?(System.data_directory)) ? System.data_directory : "./" + FILENAME_REGEX = /Game(\d*)\.rxdata$/ # Contains the file path of the save file. FILE_PATH = if File.directory?(System.data_directory) System.data_directory + "/Game.rxdata" @@ -18,6 +20,19 @@ module SaveData return File.file?(FILE_PATH) end + # @return[Array] array of filenames in the save folder that are save files + def self.all_save_files + files = Dir.get(DIRECTORY, "*", false) + ret = [] + files.each do |file| + next if !file[FILENAME_REGEX] + ret.push([$~[1].to_i, file]) + end + ret.sort! { |a, b| a[0] <=> b[0] } + ret.map! { |val| val[1] } + return ret + end + # Fetches the save data from the given file. # Returns an Array in the case of a pre-v19 save file. # @param file_path [String] path of the file to load from @@ -64,9 +79,9 @@ module SaveData # Deletes the save file (and a possible .bak backup file if one exists) # @raise [Error::ENOENT] - def self.delete_file - File.delete(FILE_PATH) - File.delete(FILE_PATH + ".bak") if File.file?(FILE_PATH + ".bak") + def self.delete_file(filename) + File.delete(DIRECTORY + filename) + File.delete(DIRECTORY + filename + ".bak") if File.file?(DIRECTORY + filename + ".bak") end # Converts the pre-v19 format data to the new format. diff --git a/Data/Scripts/002_Save data/002_SaveData_Value.rb b/Data/Scripts/002_Save data/002_SaveData_Value.rb index 86a3af3e2..31c6ae20b 100644 --- a/Data/Scripts/002_Save data/002_SaveData_Value.rb +++ b/Data/Scripts/002_Save data/002_SaveData_Value.rb @@ -259,9 +259,9 @@ module SaveData # been set to be loaded during bootup. Done when a save file exists. # @param save_data [Hash] save data to load # @raise [InvalidValueError] if an invalid value is being loaded - def self.load_bootup_values(save_data) + def self.load_bootup_values(save_data, reload = false) validate save_data => Hash - load_values(save_data) { |value| !value.loaded? && value.load_in_bootup? } + load_values(save_data) { |value| (reload || !value.loaded?) && value.load_in_bootup? } end # Goes through each value with {Value#load_in_bootup} enabled and loads their diff --git a/Data/Scripts/002_Save data/004_Game_SaveValues.rb b/Data/Scripts/002_Save data/004_Game_SaveValues.rb index 5c21c2bfa..9d3eb56f4 100644 --- a/Data/Scripts/002_Save data/004_Game_SaveValues.rb +++ b/Data/Scripts/002_Save data/004_Game_SaveValues.rb @@ -10,16 +10,17 @@ SaveData.register(:player) do end SaveData.register(:game_system) do - load_in_bootup + # TODO: Am I sure this doesn't need to be loaded in bootup? +# load_in_bootup ensure_class :Game_System save_value { $game_system } load_value { |value| $game_system = value } new_game_value { Game_System.new } - reset_on_new_game +# reset_on_new_game end SaveData.register(:pokemon_system) do - load_in_bootup + load_in_bootup # Because this contains values for the Options screen ensure_class :PokemonSystem save_value { $PokemonSystem } load_value { |value| $PokemonSystem = value } @@ -96,7 +97,6 @@ SaveData.register(:storage_system) do end SaveData.register(:essentials_version) do - load_in_bootup ensure_class :String save_value { Essentials::VERSION } load_value { |value| $save_engine_version = value } @@ -104,7 +104,6 @@ SaveData.register(:essentials_version) do end SaveData.register(:game_version) do - load_in_bootup ensure_class :String save_value { Settings::GAME_VERSION } load_value { |value| $save_game_version = value } @@ -112,10 +111,8 @@ SaveData.register(:game_version) do end SaveData.register(:stats) do - load_in_bootup ensure_class :GameStats save_value { $stats } load_value { |value| $stats = value } new_game_value { GameStats.new } - reset_on_new_game end diff --git a/Data/Scripts/003_Game processing/001_StartGame.rb b/Data/Scripts/003_Game processing/001_StartGame.rb index 69bb29425..ae33b817f 100644 --- a/Data/Scripts/003_Game processing/001_StartGame.rb +++ b/Data/Scripts/003_Game processing/001_StartGame.rb @@ -23,16 +23,12 @@ module Game # Loads bootup data from save file (if it exists) or creates bootup data (if # it doesn't). def set_up_system - save_data = (SaveData.exists?) ? SaveData.read_from_file(SaveData::FILE_PATH) : {} - if save_data.empty? - SaveData.initialize_bootup_values - else - SaveData.load_bootup_values(save_data) - end - # Set resize factor + SaveData.initialize_bootup_values pbSetResizeFactor([$PokemonSystem.screensize, 4].min) # Set language (and choose language if there is no save file) if !Settings::LANGUAGES.empty? + # TODO: Change this to check for any save file. + save_data = (SaveData.exists?) ? SaveData.read_from_file(SaveData::FILE_PATH) : {} $PokemonSystem.language = pbChooseLanguage if save_data.empty? && Settings::LANGUAGES.length >= 2 MessageTypes.load_message_files(Settings::LANGUAGES[$PokemonSystem.language][1]) end diff --git a/Data/Scripts/004_Game classes/012_Game_Stats.rb b/Data/Scripts/004_Game classes/012_Game_Stats.rb index 64dab8cb4..396ae3e18 100644 --- a/Data/Scripts/004_Game classes/012_Game_Stats.rb +++ b/Data/Scripts/004_Game classes/012_Game_Stats.rb @@ -61,6 +61,7 @@ class GameStats attr_writer :play_time # In seconds; the reader also updates the value attr_accessor :play_sessions attr_accessor :time_last_saved # In seconds + attr_reader :real_time_saved def initialize # Travel @@ -149,6 +150,7 @@ class GameStats @play_time = 0 @play_sessions = 0 @time_last_saved = 0 + @real_time_saved = 0 end def distance_moved @@ -189,6 +191,7 @@ class GameStats def set_time_last_saved @time_last_saved = play_time + @real_time_saved = Time.now.to_i end def time_since_last_save diff --git a/Data/Scripts/007_Objects and windows/002_MessageConfig.rb b/Data/Scripts/007_Objects and windows/002_MessageConfig.rb index d80d9e709..7a840fb39 100644 --- a/Data/Scripts/007_Objects and windows/002_MessageConfig.rb +++ b/Data/Scripts/007_Objects and windows/002_MessageConfig.rb @@ -243,17 +243,15 @@ end def pbRepositionMessageWindow(msgwindow, linecount = 2) msgwindow.height = (32 * linecount) + msgwindow.borderY msgwindow.y = (Graphics.height) - (msgwindow.height) - if $game_system - case $game_system.message_position - when 0 # up - msgwindow.y = 0 - when 1 # middle - msgwindow.y = (Graphics.height / 2) - (msgwindow.height / 2) - when 2 - msgwindow.y = (Graphics.height) - (msgwindow.height) - end - msgwindow.opacity = 0 if $game_system.message_frame != 0 + case $game_system&.message_position || 2 + when 0 # top + msgwindow.y = 0 + when 1 # middle + msgwindow.y = (Graphics.height - msgwindow.height) / 2 + when 2 # bottom + msgwindow.y = Graphics.height - msgwindow.height end + msgwindow.opacity = 0 if ($game_system&.message_frame || 0) != 0 end # internal function diff --git a/Data/Scripts/007_Objects and windows/011_Messages.rb b/Data/Scripts/007_Objects and windows/011_Messages.rb index 82702d437..60bff8edb 100644 --- a/Data/Scripts/007_Objects and windows/011_Messages.rb +++ b/Data/Scripts/007_Objects and windows/011_Messages.rb @@ -254,9 +254,10 @@ end def pbGetMapNameFromId(id) name = GameData::MapMetadata.try_get(id)&.name - if nil_or_empty?(name) - name = pbGetBasicMapNameFromId(id) - name.gsub!(/\\PN/, $player.name) if $player + name = pbGetBasicMapNameFromId(id) if nil_or_empty?(name) + name = name.gsub(/\\PN/, $player.name) if $player + if $game_variables + name = name.gsub(/\\v\[(\d+)\]/) { |num| $game_variables[$~[1].to_i].to_s } end return name end diff --git a/Data/Scripts/010_Data/002_PBS data/005_Move.rb b/Data/Scripts/010_Data/002_PBS data/005_Move.rb index 9b6f2e257..241501a23 100644 --- a/Data/Scripts/010_Data/002_PBS data/005_Move.rb +++ b/Data/Scripts/010_Data/002_PBS data/005_Move.rb @@ -102,6 +102,7 @@ module GameData return false end + # TODO: Make the below depend on a Setting rather than quoting it out. def display_type(pkmn, move = nil) =begin case @function_code @@ -187,6 +188,7 @@ module GameData return @type end + # TODO: Make the below depend on a Setting rather than quoting it out. def display_damage(pkmn, move = nil) =begin case @function_code diff --git a/Data/Scripts/011_Battle/003_Move/001_Battle_Move.rb b/Data/Scripts/011_Battle/003_Move/001_Battle_Move.rb index 094a190d7..33df386fe 100644 --- a/Data/Scripts/011_Battle/003_Move/001_Battle_Move.rb +++ b/Data/Scripts/011_Battle/003_Move/001_Battle_Move.rb @@ -159,6 +159,7 @@ class Battle::Move if battler.isSpecies?(:MORPEKO) || battler.effects[PBEffects::TransformSpecies] == :MORPEKO return pbBaseType(battler) end + # TODO: Make the below depend on a Setting rather than quoting it out. =begin when "TypeDependsOnUserPlate", "TypeDependsOnUserMemory", "TypeDependsOnUserDrive", "TypeAndPowerDependOnUserBerry", @@ -170,6 +171,7 @@ class Battle::Move return @realMove.display_type(battler.pokemon) end + # TODO: Make the below depend on a Setting rather than quoting it out. def display_damage(battler) =begin case @function_code @@ -185,6 +187,7 @@ class Battle::Move return @realMove.display_damage(battler.pokemon) end + # TODO: Make the below depend on a Setting rather than quoting it out. def display_category(battler) =begin case @function_code diff --git a/Data/Scripts/016_UI/001_Non-interactive UI/001_UI_SplashesAndTitleScreen.rb b/Data/Scripts/016_UI/001_Non-interactive UI/001_UI_SplashesAndTitleScreen.rb index 03ac69b74..3dbbd0099 100644 --- a/Data/Scripts/016_UI/001_Non-interactive UI/001_UI_SplashesAndTitleScreen.rb +++ b/Data/Scripts/016_UI/001_Non-interactive UI/001_UI_SplashesAndTitleScreen.rb @@ -90,16 +90,7 @@ class IntroEventScene < EventScene def close_title_screen(scene, *args) fade_out_title_screen(scene) - sscene = PokemonLoad_Scene.new - sscreen = PokemonLoadScreen.new(sscene) - sscreen.pbStartLoadScreen - end - - def close_title_screen_delete(scene, *args) - fade_out_title_screen(scene) - sscene = PokemonLoad_Scene.new - sscreen = PokemonLoadScreen.new(sscene) - sscreen.pbStartDeleteScreen + UI::Load.new.main end def title_screen_update(scene, args) @@ -108,11 +99,6 @@ class IntroEventScene < EventScene @pic2.moveOpacity(TICKS_PER_ENTER_FLASH * 2 / 10, TICKS_PER_ENTER_FLASH * 4 / 10, 0) @pic2.moveOpacity(TICKS_PER_ENTER_FLASH * 6 / 10, TICKS_PER_ENTER_FLASH * 4 / 10, 255) end - if Input.press?(Input::DOWN) && - Input.press?(Input::BACK) && - Input.press?(Input::CTRL) - close_title_screen_delete(scene, args) - end end end diff --git a/Data/Scripts/016_UI/009_UI_RegionMap.rb b/Data/Scripts/016_UI/009_UI_RegionMap.rb index 3749127d0..45c984c15 100644 --- a/Data/Scripts/016_UI/009_UI_RegionMap.rb +++ b/Data/Scripts/016_UI/009_UI_RegionMap.rb @@ -186,6 +186,8 @@ class PokemonRegionMap_Scene next if point[0] != x || point[1] != y return "" if point[7] && (@wallmap || point[7] <= 0 || !$game_switches[point[7]]) name = pbGetMessageFromHash(MessageTypes::REGION_LOCATION_NAMES, point[2]) + name = name.gsub(/\\PN/, $player.name) + name = name.gsub(/\\v\[(\d+)\]/) { |num| $game_variables[$~[1].to_i].to_s } return (@editor) ? point[2] : name end return "" @@ -336,7 +338,7 @@ class PokemonRegionMapScreen end def pbStartScreen - @scene.pbStartScene($DEBUG) + @scene.pbStartScene # ($DEBUG) ret = @scene.pbMapScene @scene.pbEndScene return ret diff --git a/Data/Scripts/016_UI/015_UI_Options.rb b/Data/Scripts/016_UI/015_UI_Options.rb index 3594e5db9..82d572f9a 100644 --- a/Data/Scripts/016_UI/015_UI_Options.rb +++ b/Data/Scripts/016_UI/015_UI_Options.rb @@ -392,7 +392,7 @@ MenuHandlers.add(:options_menu, :bgm_volume, { "set_proc" => proc { |value, scene| next if $PokemonSystem.bgmvolume == value $PokemonSystem.bgmvolume = value - next if scene.in_load_screen || $game_system.playing_bgm.nil? + next if scene.in_load_screen || !$game_system || $game_system.playing_bgm.nil? playingBGM = $game_system.getPlayingBGM $game_system.bgm_pause $game_system.bgm_resume(playingBGM) @@ -409,7 +409,7 @@ MenuHandlers.add(:options_menu, :se_volume, { "set_proc" => proc { |value, _scene| next if $PokemonSystem.sevolume == value $PokemonSystem.sevolume = value - if $game_system.playing_bgs + if $game_system && $game_system.playing_bgs $game_system.playing_bgs.volume = value playingBGS = $game_system.getPlayingBGS $game_system.bgs_pause diff --git a/Data/Scripts/016b_UI redesign/000_UI_base.rb b/Data/Scripts/016b_UI redesign/000_UI_base.rb index e53458357..c5ba52c74 100644 --- a/Data/Scripts/016b_UI redesign/000_UI_base.rb +++ b/Data/Scripts/016b_UI redesign/000_UI_base.rb @@ -65,7 +65,7 @@ module UI end def gendered_filename(base_filename) - return filename_with_appendix(base_filename, "_f") if $player.female? + return filename_with_appendix(base_filename, "_f") if $player&.female? return base_filename end @@ -306,6 +306,8 @@ module UI # The visuals class. #============================================================================= class BaseVisuals + attr_reader :sprites + BACKGROUND_FILENAME = "bg" include SpriteContainerMixin @@ -800,6 +802,7 @@ module UI end def main + return if @disposed start_screen loop do on_start_main_loop diff --git a/Data/Scripts/016b_UI redesign/005_UI_Party.rb b/Data/Scripts/016b_UI redesign/005_UI_Party.rb index 49074c291..40059c953 100644 --- a/Data/Scripts/016b_UI redesign/005_UI_Party.rb +++ b/Data/Scripts/016b_UI redesign/005_UI_Party.rb @@ -327,7 +327,6 @@ end # #=============================================================================== class UI::PartyVisuals < UI::BaseVisuals - attr_reader :sprites attr_reader :index attr_reader :sub_mode diff --git a/Data/Scripts/016b_UI redesign/007_UI_Bag.rb b/Data/Scripts/016b_UI redesign/007_UI_Bag.rb index 920b64e72..59d4356d1 100644 --- a/Data/Scripts/016b_UI redesign/007_UI_Bag.rb +++ b/Data/Scripts/016b_UI redesign/007_UI_Bag.rb @@ -164,7 +164,6 @@ end # #=============================================================================== class UI::BagVisuals < UI::BaseVisuals - attr_reader :sprites attr_reader :pocket GRAPHICS_FOLDER = "Bag/" # Subfolder in Graphics/UI diff --git a/Data/Scripts/016b_UI redesign/013_UI_Load.rb b/Data/Scripts/016b_UI redesign/013_UI_Load.rb new file mode 100644 index 000000000..64cf60846 --- /dev/null +++ b/Data/Scripts/016b_UI redesign/013_UI_Load.rb @@ -0,0 +1,717 @@ +#=============================================================================== +# +#=============================================================================== +class UI::LoadPanel < UI::SpriteContainer + GRAPHICS_FOLDER = "Load/" + TEXT_COLOR_THEMES = { # These color themes are added to @sprites[:overlay] + :default => [Color.new(88, 88, 80), Color.new(168, 184, 184)] # Base and shadow colour + } + PANEL_WIDTH = 392 + PANEL_HEIGHT = 56 + + def initialize(label, viewport) + @label = label + @selected = nil + super(viewport) + end + + def initialize_sprites + initialize_panel_background + initialize_overlay + end + + def initialize_panel_background + @sprites[:background] = ChangelingSprite.new(0, 0, @viewport) + panel_srcs.each_pair do |key, values| + @sprites[:background].add_bitmap(key, values) + end + @sprites[:background].change_bitmap(:default) + record_values(:background) + end + + def initialize_overlay + add_overlay(:overlay, @sprites[:background].src_rect.width, @sprites[:background].src_rect.height) + @sprites[:overlay].z = 10 + record_values(:overlay) + end + + #----------------------------------------------------------------------------- + + def width + return self.class::PANEL_WIDTH + end + + def height + return self.class::PANEL_HEIGHT + end + + def panel_srcs + return { + :default => [graphics_folder + "panels", 0, UI::LoadContinuePanel::PANEL_HEIGHT * 2, + self.class::PANEL_WIDTH, self.class::PANEL_HEIGHT], + :selected => [graphics_folder + "panels", 0, (UI::LoadContinuePanel::PANEL_HEIGHT * 2) + self.class::PANEL_HEIGHT, + self.class::PANEL_WIDTH, self.class::PANEL_HEIGHT] + } + end + + def selected=(value) + return if @selected == value + @selected = value + @sprites[:background].change_bitmap((@selected) ? :selected : :default) + refresh + end + + #----------------------------------------------------------------------------- + + def refresh + super + draw_text(@label, 18, 18) + end +end + +#=============================================================================== +# +#=============================================================================== +class UI::LoadContinuePanel < UI::LoadPanel + attr_reader :sprites + + GRAPHICS_FOLDER = "Load/" + TEXT_COLOR_THEMES = { # These color themes are added to @sprites[:overlay] + :default => [Color.new(88, 88, 80), Color.new(168, 184, 184)], # Base and shadow colour + :male => [Color.new(0, 112, 248), Color.new(120, 184, 232)], + :female => [Color.new(232, 32, 16), Color.new(248, 168, 184)] + } + PANEL_WIDTH = 392 + PANEL_HEIGHT = 248 + + def initialize(label, save_data, slot_index, total_slots, viewport) + @save_data = save_data + @slot_index = slot_index + @total_slots = total_slots + super(label, viewport) + refresh + end + + def initialize_sprites + super + initialize_player_sprite + initialize_pokemon_icons + initialize_arrow_sprites + end + + def initialize_player_sprite + meta = GameData::PlayerMetadata.get(@save_data[:player].character_ID) + filename = pbGetPlayerCharset(meta.walk_charset, @save_data[:player], true) + @sprites[:player] = TrainerWalkingCharSprite.new(filename, @viewport) + if !@sprites[:player].bitmap + raise _INTL("Player character {1}'s walking charset was not found (filename: \"{2}\").", + @save_data[:player].character_ID, filename) + end + @sprites[:player].x = 48 - (@sprites[:player].bitmap.width / 8) + @sprites[:player].y = 72 - (@sprites[:player].bitmap.height / 8) + @sprites[:player].z = 1 + record_values(:player) + end + + def initialize_pokemon_icons + Settings::MAX_PARTY_SIZE.times do |i| + @sprites["pokemon_#{i}"] = PokemonIconSprite.new(@save_data[:player].party[i], @viewport) + @sprites["pokemon_#{i}"].x, @sprites["pokemon_#{i}"].y = pokemon_coords(i) + @sprites["pokemon_#{i}"].z = 1 + @sprites["pokemon_#{i}"].setOffset + record_values("pokemon_#{i}") + end + end + + def initialize_arrow_sprites + @sprites[:left_arrow] = AnimatedSprite.new(UI_FOLDER + "left_arrow", 8, 40, 28, 2, @viewport) + @sprites[:left_arrow].x = -16 + @sprites[:left_arrow].y = (height / 2) - 14 + @sprites[:left_arrow].z = 20 + @sprites[:left_arrow].play + record_values(:left_arrow) + @sprites[:right_arrow] = AnimatedSprite.new(UI_FOLDER + "right_arrow", 8, 40, 28, 2, @viewport) + @sprites[:right_arrow].x = width - 24 + @sprites[:right_arrow].y = (height / 2) - 14 + @sprites[:right_arrow].z = 20 + @sprites[:right_arrow].play + record_values(:right_arrow) + end + + #----------------------------------------------------------------------------- + + def panel_srcs + return { + :default => [graphics_folder + "panels", 0, 0, + self.class::PANEL_WIDTH, self.class::PANEL_HEIGHT], + :selected => [graphics_folder + "panels", 0, self.class::PANEL_HEIGHT, + self.class::PANEL_WIDTH, self.class::PANEL_HEIGHT] + } + end + + def pokemon_coords(index) + return 276 + (66 * (index % 2)), + 74 + (50 * (index / 2)) + end + + def visible=(value) + super + @sprites[:left_arrow].visible = (@selected && @total_slots >= 2) + @sprites[:right_arrow].visible = (@selected && @total_slots >= 2) + end + + def selected=(value) + @sprites[:left_arrow].visible = (value && @total_slots >= 2) + @sprites[:right_arrow].visible = (value && @total_slots >= 2) + super + end + + def set_data(save_data, slot_index, total_slots) + @save_data = save_data + @slot_index = slot_index + @total_slots = total_slots + @sprites[:left_arrow].visible = (@selected && total_slots >= 2) + @sprites[:right_arrow].visible = (@selected && total_slots >= 2) + set_player_sprite + refresh + end + + def set_player_sprite + meta = GameData::PlayerMetadata.get(@save_data[:player].character_ID) + filename = pbGetPlayerCharset(meta.walk_charset, @save_data[:player], true) + @sprites[:player].charset = filename + if !@sprites[:player].bitmap + raise _INTL("Player character {1}'s walking charset was not found (filename: \"{2}\").", + @save_data[:player].character_ID, filename) + end + end + + #----------------------------------------------------------------------------- + + def refresh + super + refresh_pokemon + draw_slot_number + draw_save_file_text + end + + def refresh_pokemon + Settings::MAX_PARTY_SIZE.times do |i| + @sprites["pokemon_#{i}"].pokemon = @save_data[:player].party[i] + end + end + + def draw_slot_number + return if @total_slots <= 1 + draw_text(sprintf("%d/%d", @slot_index + 1, @total_slots), PANEL_WIDTH - 18, 18, align: :right) + end + + def draw_save_file_text + gender_theme = :default + if @save_data[:player].male? + gender_theme = :male + elsif @save_data[:player].female? + gender_theme = :female + end + # Player's name + draw_text(@save_data[:player].name, 78, 66, theme: gender_theme) + # Location + map_id = @save_data[:map_factory].map.map_id + map_name = pbGetMapNameFromId(map_id) + map_name = map_name.gsub(/\\PN/, @save_data[:player].name) + map_name = map_name.gsub(/\\v\[(\d+)\]/) { |num| @save_data[:variables][$~[1].to_i].to_s } + draw_text(map_name, 18, 114, theme: gender_theme) + # Gym Badges + draw_text(_INTL("Badges:"), 18, 146) + draw_text(@save_data[:player].badge_count.to_s, 156, 146, theme: gender_theme) + # Pokédex owned count + draw_text(_INTL("Pokédex:"), 18, 178) + draw_text(@save_data[:player].pokedex.seen_count.to_s, 156, 178, theme: gender_theme) + # Time played + draw_text(_INTL("Time played:"), 18, 210) + play_time = @save_data[:stats]&.play_time.to_i || 0 + hour = (play_time / 60) / 60 + min = (play_time / 60) % 60 + play_time_text = (hour > 0) ? _INTL("{1}h {2}m", hour, min) : _INTL("{1}m", min) + draw_text(play_time_text, 156, 210, theme: gender_theme) + save_time = @save_data[:stats]&.real_time_saved + if save_time + save_time = Time.at(save_time) + if System.user_language[3..4] == "US" # If the user is in the United States + save_text = save_time.strftime("%-m/&-d/%Y") + else + save_text = save_time.strftime("%-d/%-m/%Y") + end + draw_text(save_text, PANEL_WIDTH - 18, 210, align: :right, theme: gender_theme) + end + end + + def refresh_existing_pokemon + Settings::MAX_PARTY_SIZE.times do |i| + @sprites["pokemon_#{i}"].pokemon = @save_data[:player].party[i] + end + end +end + +#=============================================================================== +# +#=============================================================================== +class UI::LoadVisuals < UI::BaseVisuals + attr_reader :slot_index + + GRAPHICS_FOLDER = "Load/" # Subfolder in Graphics/UI + PANEL_SPACING_EDGE = 4 + PANEL_SPACING = PANEL_SPACING_EDGE * 2 + + # save_data here is an array of all save files' data. It has been compacted. + # commands is {:continue => _INTL("Continue"), :new_game => _INTL("New Game")}, etc. + def initialize(commands, save_data, default_slot_index = 0) + @save_data = save_data + @commands = commands + @index = @commands.keys.first # A symbol from @commands + @slot_index = default_slot_index # Which save slot is selected + super() + end + + def initialize_sprites + initialize_continue_panels + initialize_other_panels + end + + def initialize_continue_panels + return if @save_data.nil? || @commands.keys.first != :continue + # Continue panel in middle + this_slot_index = @slot_index + @sprites[:continue] = create_continue_panel(this_slot_index) + # Continue panel to left + if @save_data.length >= 2 + previous_slot_index = this_slot_index - 1 + @sprites[:continue_previous] = create_continue_panel(this_slot_index - 1) + @sprites[:continue_previous].x = @sprites[:continue].x - @sprites[:continue].width - PANEL_SPACING + # Continue panel to right + next_slot_index = this_slot_index + 1 + @sprites[:continue_next] = create_continue_panel(this_slot_index + 1) + @sprites[:continue_next].x = @sprites[:continue].x + @sprites[:continue].width + PANEL_SPACING + end + end + + def initialize_other_panels + @commands.each_pair do |key, text| + next if key == :continue + @sprites[key] = UI::LoadPanel.new(text, @viewport) + @sprites[key].x = (Graphics.width - @sprites[key].width) / 2 + end + @sprites[:mystery_gift]&.visible = @save_data[@slot_index] && @save_data[@slot_index][1][:player].mystery_gift_unlocked + end + + #----------------------------------------------------------------------------- + + def create_continue_panel(slot_index) + slot_index += @save_data.length if slot_index < 0 + slot_index -= @save_data.length if slot_index >= @save_data.length + ret = UI::LoadContinuePanel.new(@commands[:continue], + @save_data[slot_index][1], slot_index, @save_data.length, @viewport) + ret.x = (Graphics.width - ret.width) / 2 + return ret + end + + def set_index(new_index) + @index = new_index + refresh_on_index_changed(@index) + end + + def go_to_next_option(play_se = true) + return if @commands.length == 1 + old_index = @commands.keys.index(@index) + new_index = old_index + loop do + new_index = (new_index + 1) % @commands.length + break if @sprites[@commands.keys[new_index]] && @sprites[@commands.keys[new_index]].visible + break if new_index == old_index + end + return if new_index == old_index + pbPlayCursorSE if play_se + set_index(@commands.keys[new_index]) + end + + def go_to_previous_option(play_se = true) + return if @commands.length == 1 + old_index = @commands.keys.index(@index) + new_index = old_index + loop do + new_index -= 1 + new_index += @commands.length if new_index < 0 + break if @sprites[@commands.keys[new_index]] && @sprites[@commands.keys[new_index]].visible + break if new_index == old_index + end + return if new_index == old_index + pbPlayCursorSE if play_se + set_index(@commands.keys[new_index]) + end + + #----------------------------------------------------------------------------- + + def set_slot_index(new_index, forced = false) + while new_index < 0 + new_index += @save_data.length + end + while new_index >= @save_data.length + new_index -= @save_data.length + end + return if !forced && @slot_index == new_index + # Set the new index + @slot_index = new_index + # Show the newly selected slot's information in the Continue panel + @sprites[:continue].set_data(@save_data[@slot_index][1], @slot_index, @save_data.length) + # Show the newly adjacent slots' information in the adjacent Continue panels + prev_index = @slot_index - 1 + prev_index += @save_data.length if prev_index < 0 + @sprites[:continue_previous]&.set_data(@save_data[prev_index][1], prev_index, @save_data.length) + next_index = (@slot_index + 1) % @save_data.length + @sprites[:continue_next]&.set_data(@save_data[next_index][1], next_index, @save_data.length) + # Determine whether the Mystery Gift option is visible + @sprites[:mystery_gift].visible = @save_data[@slot_index][1][:player].mystery_gift_unlocked + refresh_panel_positions + SaveData.load_bootup_values(@save_data[@slot_index][1], true) + pbPlayCursorSE if !forced + end + + def go_to_next_save_slot + set_slot_index(@slot_index + 1) + end + + def go_to_previous_save_slot + set_slot_index(@slot_index - 1) + end + + #----------------------------------------------------------------------------- + + def refresh + super + @commands.keys.each { |key| @sprites[key]&.refresh } + refresh_panel_positions + refresh_selected_panel + end + + def full_refresh + refresh + @sprites.each_pair { |key, sprite| sprite.refresh if sprite.respond_to?(:refresh) } + end + + def refresh_panel_positions + @panel_y_offset ||= 0 + sprite_y = PANEL_SPACING_EDGE + # Determine the relative positions of all option panels + sprite_pos = {} + @commands.keys.each do |key| + next if !@sprites[key] || !@sprites[key].visible # If Mystery Gift option isn't available + sprite_pos[key] = sprite_y + sprite_y += @sprites[key].height + PANEL_SPACING + end + # Determine an offset that ensures the selected option panel is on-screen + screen_y = sprite_pos[@index] - @panel_y_offset + if screen_y < PANEL_SPACING_EDGE + @panel_y_offset = sprite_pos[@index] - PANEL_SPACING_EDGE + elsif screen_y + @sprites[@index].height > Graphics.height - PANEL_SPACING_EDGE + @panel_y_offset = sprite_pos[@index] + @sprites[@index].height + PANEL_SPACING_EDGE - Graphics.height + end + # Apply the calculated positions to all option panels + sprite_pos.each_pair do |key, value| + @sprites[key].y = value - @panel_y_offset + end + @sprites[:continue_previous]&.y = @sprites[:continue].y + @sprites[:continue_next]&.y = @sprites[:continue].y + end + + def refresh_selected_panel + @commands.keys.each do |key| + @sprites[key]&.selected = (key == @index) + end + @sprites[:continue_previous]&.selected = false + @sprites[:continue_next]&.selected = false + end + + def refresh_on_index_changed(old_index) + refresh_selected_panel + refresh_panel_positions + end + + def refresh_after_save_file_deleted + @slot_index = [@slot_index, @save_data.length - 1].min + if @save_data.empty? + [:continue, :continue_previous, :continue_next].each do |key| + @sprites[key].dispose if @sprites[key] && !@sprites[key].disposed? + @sprites[key] = nil + end + @sprites[:mystery_gift].visible = false + go_to_next_option(false) + else + if @save_data.length == 1 + [:continue_previous, :continue_next].each do |key| + @sprites[key].dispose if @sprites[key] && !@sprites[key].disposed? + @sprites[key] = nil + end + end + set_slot_index(@slot_index, true) + end + end + + #----------------------------------------------------------------------------- + + def update_input + # Check for movement to a new option + if Input.repeat?(Input::UP) + go_to_previous_option + elsif Input.repeat?(Input::DOWN) + go_to_next_option + end + # Check for movement to a different save slot + if @index == :continue && @save_data.length > 1 + if Input.repeat?(Input::LEFT) + go_to_previous_save_slot + elsif Input.repeat?(Input::RIGHT) + go_to_next_save_slot + end + end + # Check for interaction + if Input.trigger?(Input::USE) + if @index == :continue && Input.press?(Input::ACTION) && Input.press?(Input::BACK) + pbPlayDecisionSE + return :delete_save + end + return update_interaction(Input::USE) + end + return nil + end + + def update_interaction(input) + case input + when Input::USE + pbPlayDecisionSE if @index != :quit_game + return @index # This is a key from @commands + end + return nil + end +end + +#=============================================================================== +# +#=============================================================================== +class UI::Load < UI::BaseScreen + attr_reader :save_data + + SCREEN_ID = :load_screen + + def initialize + load_save_data + if $DEBUG && !FileTest.exist?("Game.rgssad") && Settings::SKIP_CONTINUE_SCREEN + @disposed = true + perform_action((@save_data.empty?) ? :new_game : :continue) + return + end + set_commands + super + end + + def initialize_visuals + @visuals = UI::LoadVisuals.new(@commands, @save_data, @default_slot_index) + end + + #----------------------------------------------------------------------------- + + def slot_index + return @visuals&.slot_index || @default_slot_index + end + + def set_commands + @commands = {} + MenuHandlers.each_available(:load_screen, self) do |option, _hash, name| + @commands[option] = name + end + end + + #----------------------------------------------------------------------------- + + def load_save_data + @save_data = [] + @default_slot_index = 0 + last_edited_time = nil + files = SaveData.all_save_files + files.each do |file| + # Load the save file + this_save_data = SaveData.read_from_file(SaveData::DIRECTORY + file) + if !SaveData.valid?(this_save_data) + if File.file?(SaveData::DIRECTORY + file + ".bak") + show_message(_INTL("The save file is corrupt. A backup will be loaded.")) + this_save_data = load_save_file(SaveData::FILE_PATH + ".bak") + else + prompt_corrupted_save_deletion(file) + end + end + @save_data.push([file, this_save_data]) + # Find the most recently edited save file; default to selecting that one + save_time = this_save_data[:stats].real_time_saved || 0 + if !last_edited_time || save_time > last_edited_time + last_edited_time = save_time + @default_slot_index = @save_data.length - 1 + end + end + SaveData.load_bootup_values(@save_data[@default_slot_index][1], true) if !@save_data.empty? + end + + def prompt_corrupted_save_deletion(filename) + show_message(_INTL("The save file is corrupt, or is incompatible with this game.") + "\1") + pbPlayDecisionSE + exit if !show_confirm_serious_message(_INTL("Do you want to delete the save file and start anew?")) + delete_save_data(filename) + $PokemonSystem = PokemonSystem.new + end + + def prompt_save_deletion(filename) + if show_confirm_serious_message(_INTL("Delete this save file?")) + show_message(_INTL("Once a save file has been deleted, there is no way to recover it.") + "\1") + pbPlayDecisionSE + if show_confirm_serious_message(_INTL("Delete the save file anyway?")) + delete_save_data(filename) { + @save_data.delete_if { |save| save[0] == filename } + @visuals.refresh_after_save_file_deleted + } + end + end + end + + def delete_save_data(filename) + begin + SaveData.delete_file(filename) + yield if block_given? + show_message(_INTL("The save file was deleted.")) + rescue SystemCallError + show_message(_INTL("The save file could not be deleted.")) + end + end + + #----------------------------------------------------------------------------- + + def full_refresh + @visuals.full_refresh + end +end + +#=============================================================================== +# Actions that can be triggered in the load screen. +#=============================================================================== +UIActionHandlers.add(UI::Load::SCREEN_ID, :continue, { + :effect => proc { |screen| + screen.end_screen + Game.load(screen.save_data[screen.slot_index][1]) + } +}) + +UIActionHandlers.add(UI::Load::SCREEN_ID, :mystery_gift, { + :effect => proc { |screen| + pbFadeOutInWithUpdate(screen.sprites) do + pbDownloadMysteryGift(screen.save_data[screen.slot_index][1][:player]) + end + } +}) + +UIActionHandlers.add(UI::Load::SCREEN_ID, :new_game, { + :effect => proc { |screen| + screen.end_screen + Game.start_new + } +}) + +UIActionHandlers.add(UI::Load::SCREEN_ID, :options, { + :effect => proc { |screen| + pbFadeOutInWithUpdate(screen.sprites) do + options_scene = PokemonOption_Scene.new + options_screen = PokemonOptionScreen.new(options_scene) + options_screen.pbStartScreen(true) + screen.full_refresh + end + } +}) + +UIActionHandlers.add(UI::Load::SCREEN_ID, :language, { + :effect => proc { |screen| + screen.end_screen + $PokemonSystem.language = pbChooseLanguage + MessageTypes.load_message_files(Settings::LANGUAGES[$PokemonSystem.language][1]) + if screen.save_data[screen.slot_index] + screen.save_data[screen.slot_index][1][:pokemon_system] = $PokemonSystem + File.open(SaveData::DIRECTORY + screen.save_data[screen.slot_index][0], "wb") do |file| + Marshal.dump(screen.save_data[screen.slot_index][1], file) + end + end + $scene = pbCallTitle + } +}) + +UIActionHandlers.add(UI::Load::SCREEN_ID, :debug, { + :effect => proc { |screen| + pbFadeOutInWithUpdate(screen.sprites) do + pbDebugMenu(false) + end + } +}) + +UIActionHandlers.add(UI::Load::SCREEN_ID, :quit_game, { + :effect => proc { |screen| + pbPlayCloseMenuSE + screen.end_screen + $scene = nil + } +}) + +UIActionHandlers.add(UI::Load::SCREEN_ID, :delete_save, { + :effect => proc { |screen| + screen.prompt_save_deletion(screen.save_data[screen.slot_index][0]) + } +}) + +#=============================================================================== +# Menu options that exist in the load screen. +#=============================================================================== +MenuHandlers.add(:load_screen, :continue, { + "name" => _INTL("Continue"), + "order" => 10, + "condition" => proc { |screen| next screen.save_data && !screen.save_data.empty? } +}) + +# NOTE: Mystery Gift is always added as an option here, even if no save files +# have unlocked it. Whether it is shown depends on the selected save file, +# and its visibility is toggled elsewhere because of that. +MenuHandlers.add(:load_screen, :mystery_gift, { + "name" => _INTL("Mystery Gift"), + "order" => 20, + "condition" => proc { |screen| next screen.save_data && !screen.save_data.empty? } +}) + +MenuHandlers.add(:load_screen, :new_game, { + "name" => _INTL("New Game"), + "order" => 30 +}) + +MenuHandlers.add(:load_screen, :options, { + "name" => _INTL("Options"), + "order" => 40 +}) + +# TODO: Put language in the options screen? +MenuHandlers.add(:load_screen, :language, { + "name" => _INTL("Language"), + "order" => 50, + "condition" => proc { |screen| next Settings::LANGUAGES.length >= 2 } +}) + +MenuHandlers.add(:load_screen, :debug, { + "name" => _INTL("Debug"), + "order" => 60, + "condition" => proc { |screen| next $DEBUG } +}) + +MenuHandlers.add(:load_screen, :quit_game, { + "name" => _INTL("Quit Game"), + "order" => 9999 +}) diff --git a/Data/Scripts/016b_UI redesign/017_UI_PokemonStorage.rb b/Data/Scripts/016b_UI redesign/017_UI_PokemonStorage.rb index e862f932c..8ec53d4d6 100644 --- a/Data/Scripts/016b_UI redesign/017_UI_PokemonStorage.rb +++ b/Data/Scripts/016b_UI redesign/017_UI_PokemonStorage.rb @@ -701,7 +701,6 @@ class UI::PokemonStorageVisuals < UI::BaseVisuals # 0+ = box number attr_reader :box attr_reader :sub_mode - attr_reader :sprites GRAPHICS_FOLDER = "Storage/" # Subfolder in Graphics/UI TEXT_COLOR_THEMES = { # These color themes are added to @sprites[:overlay] @@ -1352,9 +1351,9 @@ class UI::PokemonStorageVisuals < UI::BaseVisuals return update_interaction(Input::BACK) elsif Input.trigger?(Input::ACTION) return update_interaction(Input::ACTION) - elsif Input.trigger?(Input::JUMPUP) + elsif Input.repeat?(Input::JUMPUP) return update_interaction(Input::JUMPUP) - elsif Input.trigger?(Input::JUMPDOWN) + elsif Input.repeat?(Input::JUMPDOWN) return update_interaction(Input::JUMPDOWN) end return nil diff --git a/Data/Scripts/016b_UI redesign/020_UI_PokeMart.rb b/Data/Scripts/016b_UI redesign/020_UI_PokeMart.rb index becc81885..7dc8bf481 100644 --- a/Data/Scripts/016b_UI redesign/020_UI_PokeMart.rb +++ b/Data/Scripts/016b_UI redesign/020_UI_PokeMart.rb @@ -125,7 +125,6 @@ end # #=============================================================================== class UI::MartVisuals < UI::BaseVisuals - attr_reader :sprites attr_reader :pocket GRAPHICS_FOLDER = "Mart/" # Subfolder in Graphics/UI diff --git a/Data/Scripts/016_UI/013_UI_Load.rb b/Data/Scripts/016c_UI_old/013_UI_old_Load.rb similarity index 99% rename from Data/Scripts/016_UI/013_UI_Load.rb rename to Data/Scripts/016c_UI_old/013_UI_old_Load.rb index 9d15a685d..0b510c284 100644 --- a/Data/Scripts/016_UI/013_UI_Load.rb +++ b/Data/Scripts/016c_UI_old/013_UI_old_Load.rb @@ -1,3 +1,4 @@ +=begin #=============================================================================== # #=============================================================================== @@ -356,3 +357,4 @@ class PokemonLoadScreen end end end +=end diff --git a/Data/Scripts/016c_UI_old/017_UI_old_PokemonStorage.rb b/Data/Scripts/016c_UI_old/017_UI_old_PokemonStorage.rb index a133bab7a..fdfac9efe 100644 --- a/Data/Scripts/016c_UI_old/017_UI_old_PokemonStorage.rb +++ b/Data/Scripts/016c_UI_old/017_UI_old_PokemonStorage.rb @@ -1,3 +1,4 @@ +=begin #=============================================================================== # Pokémon icons. #=============================================================================== @@ -2029,3 +2030,4 @@ class PokemonStorageScreen return retval end end +=end diff --git a/Data/Scripts/999_Main/999_Main.rb b/Data/Scripts/999_Main/999_Main.rb index f0007662d..3b1b0da9b 100644 --- a/Data/Scripts/999_Main/999_Main.rb +++ b/Data/Scripts/999_Main/999_Main.rb @@ -4,9 +4,7 @@ class Scene_DebugIntro def main Graphics.transition(0) - sscene = PokemonLoad_Scene.new - sscreen = PokemonLoadScreen.new(sscene) - sscreen.pbStartLoadScreen + UI::Load.new.main Graphics.freeze end end