mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-06 06:01:46 +00:00
6.4 update (minus sprites)
This commit is contained in:
898
Data/Scripts/052_AddOns/MultiSaves.rb
Normal file
898
Data/Scripts/052_AddOns/MultiSaves.rb
Normal file
@@ -0,0 +1,898 @@
|
||||
# Auto Multi Save by http404error
|
||||
# For Pokemon Essentials v19.1
|
||||
|
||||
# Description:
|
||||
# Adds multiple save slots and the abliity to auto-save.
|
||||
# Included is code to autosave every 30 overworld steps. Feel free to edit or delete it (it's right at the top).
|
||||
# On the Load screen you can use the left and right buttons while "Continue" is selected to cycle through files.
|
||||
# When saving, you can quickly save to the same slot you loaded from, or pick another slot.
|
||||
# Battle Challenges are NOT supported.
|
||||
|
||||
# Customization:
|
||||
# I recommend altering your pause menu to quit to the title screen or load screen instead of exiting entirely.
|
||||
# -> For instance, just change the menu text to "Quit to Title" and change `$scene = nil` to `$scene = pbCallTitle`.
|
||||
# Call Game.auto_save whenever you want.
|
||||
# -> Autosaving during an event script will correctly resume event execution when you load the game.
|
||||
# -> I haven't investigated if it might be possible to autosave on closing the window with the X or Alt-F4 yet.
|
||||
# You can rename the slots to your liking, or change how many there are.
|
||||
# In some cases, you might want to remove the option to save to a different slot than the one you loaded from.
|
||||
|
||||
# Notes:
|
||||
# On the first Load, the old Game.rxdata will be copied to the first slot in MANUAL_SLOTS. It won't have a known save time though.
|
||||
# The interface to `Game.save` has been changed.
|
||||
# Due to the slots, alters the save backup system in the case of save corruption/crashes - backups will be named Backup000.rxdata and so on.
|
||||
# Heavily modifies the SaveData module and Save and Load screens. This may cause incompatibility with some other plugins or custom game code.
|
||||
# Not everything here has been tested extensively, only what applies to normal usage of my game. Please let me know if you run into any problems.
|
||||
|
||||
# Future development ideas:
|
||||
# There isn't currently support for unlimited slots but it wouldn't be too hard.
|
||||
# Letting the user name their slots seems cool.
|
||||
# It would be nice if there was a sliding animation for switching files on that load screen. :)
|
||||
# It would be nice if the file select arrows used nicer animated graphics, kind of like the Bag.
|
||||
# Maybe auto-save slots should act like a queue instead of cycling around.
|
||||
|
||||
# Autosave every 30 steps
|
||||
# Events.onStepTaken += proc {
|
||||
# $Trainer.autosave_steps = 0 if !$Trainer.autosave_steps
|
||||
# $Trainer.autosave_steps += 1
|
||||
# if $Trainer.autosave_steps >= 30
|
||||
# echo("Autosaving...")
|
||||
# $Trainer.autosave_steps = 0
|
||||
# Game.auto_save
|
||||
# echoln("done.")
|
||||
# end
|
||||
# }
|
||||
|
||||
def onLoadExistingGame()
|
||||
migrateOldSavesToCharacterCustomization()
|
||||
clear_all_images()
|
||||
|
||||
end
|
||||
|
||||
def onStartingNewGame() end
|
||||
|
||||
def migrateOldSavesToCharacterCustomization()
|
||||
if !$Trainer.unlocked_clothes
|
||||
$Trainer.unlocked_clothes = [DEFAULT_OUTFIT_MALE,
|
||||
DEFAULT_OUTFIT_FEMALE,
|
||||
STARTING_OUTFIT]
|
||||
end
|
||||
if !$Trainer.unlocked_hats
|
||||
$Trainer.unlocked_hats = [DEFAULT_OUTFIT_MALE, DEFAULT_OUTFIT_FEMALE]
|
||||
end
|
||||
if !$Trainer.unlocked_hairstyles
|
||||
$Trainer.unlocked_hairstyles = [DEFAULT_OUTFIT_MALE, DEFAULT_OUTFIT_FEMALE]
|
||||
end
|
||||
|
||||
if !$Trainer.clothes || !$Trainer.hair #|| !$Trainer.hat
|
||||
setupStartingOutfit()
|
||||
end
|
||||
end
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
module SaveData
|
||||
# You can rename these slots or change the amount of them
|
||||
# They change the actual save file names though, so it would take some extra work to use the translation system on them.
|
||||
AUTO_SLOTS = [
|
||||
'Auto 1',
|
||||
'Auto 2'
|
||||
]
|
||||
MANUAL_SLOTS = [
|
||||
'File A',
|
||||
'File B',
|
||||
'File C',
|
||||
'File D',
|
||||
'File E',
|
||||
'File F',
|
||||
'File G',
|
||||
'File H'
|
||||
]
|
||||
|
||||
# For compatibility with games saved without this plugin
|
||||
OLD_SAVE_SLOT = 'Game'
|
||||
|
||||
SAVE_DIR = if File.directory?(System.data_directory)
|
||||
System.data_directory
|
||||
else
|
||||
'.'
|
||||
end
|
||||
|
||||
def self.each_slot
|
||||
(AUTO_SLOTS + MANUAL_SLOTS).each { |f| yield f }
|
||||
end
|
||||
|
||||
def self.get_full_path(file)
|
||||
return "#{SAVE_DIR}/#{file}.rxdata"
|
||||
end
|
||||
|
||||
def self.get_backup_file_path
|
||||
backup_file = "Backup000"
|
||||
while File.file?(self.get_full_path(backup_file))
|
||||
backup_file.next!
|
||||
end
|
||||
return self.get_full_path(backup_file)
|
||||
end
|
||||
|
||||
# Given a list of save file names and a file name in it, return the next file after it which exists
|
||||
# If no other file exists, will just return the same file again
|
||||
def self.get_next_slot(file_list, file)
|
||||
old_index = file_list.find_index(file)
|
||||
ordered_list = file_list.rotate(old_index + 1)
|
||||
ordered_list.each do |f|
|
||||
return f if File.file?(self.get_full_path(f))
|
||||
end
|
||||
# should never reach here since the original file should always exist
|
||||
return file
|
||||
end
|
||||
|
||||
# See self.get_next_slot
|
||||
def self.get_prev_slot(file_list, file)
|
||||
return self.get_next_slot(file_list.reverse, file)
|
||||
end
|
||||
|
||||
# Returns nil if there are no saves
|
||||
# Returns the first save if there's a tie for newest
|
||||
# Old saves from previous version don't store their saved time, so are treated as very old
|
||||
def self.get_newest_save_slot
|
||||
newest_time = Time.at(0) # the Epoch
|
||||
newest_slot = nil
|
||||
self.each_slot do |file_slot|
|
||||
full_path = self.get_full_path(file_slot)
|
||||
next if !File.file?(full_path)
|
||||
begin
|
||||
temp_save_data = self.read_from_file(full_path)
|
||||
rescue
|
||||
next
|
||||
end
|
||||
|
||||
save_time = temp_save_data[:player].last_time_saved || Time.at(1)
|
||||
if save_time > newest_time
|
||||
newest_time = save_time
|
||||
newest_slot = file_slot
|
||||
end
|
||||
end
|
||||
# Port old save
|
||||
if newest_slot.nil? && File.file?(self.get_full_path(OLD_SAVE_SLOT))
|
||||
file_copy(self.get_full_path(OLD_SAVE_SLOT), self.get_full_path(MANUAL_SLOTS[0]))
|
||||
return MANUAL_SLOTS[0]
|
||||
end
|
||||
return newest_slot
|
||||
end
|
||||
|
||||
# @return [Boolean] whether any save file exists
|
||||
def self.exists?
|
||||
self.each_slot do |slot|
|
||||
full_path = SaveData.get_full_path(slot)
|
||||
return true if File.file?(full_path)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
# This is used in a hidden function (ctrl+down+cancel on title screen) or if the save file is corrupt
|
||||
# Pass nil to delete everything, or a file path to just delete that one
|
||||
# @raise [Error::ENOENT]
|
||||
def self.delete_file(file_path = nil)
|
||||
if file_path
|
||||
File.delete(file_path) if File.file?(file_path)
|
||||
else
|
||||
self.each_slot do |slot|
|
||||
full_path = self.get_full_path(slot)
|
||||
File.delete(full_path) if File.file?(full_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Moves a save file from the old Saved Games folder to the new
|
||||
# location specified by {MANUAL_SLOTS[0]}. Does nothing if a save file
|
||||
# already exists in {MANUAL_SLOTS[0]}.
|
||||
def self.move_old_windows_save
|
||||
return if self.exists?
|
||||
game_title = System.game_title.gsub(/[^\w ]/, '_')
|
||||
home = ENV['HOME'] || ENV['HOMEPATH']
|
||||
return if home.nil?
|
||||
old_location = File.join(home, 'Saved Games', game_title)
|
||||
return unless File.directory?(old_location)
|
||||
old_file = File.join(old_location, 'Game.rxdata')
|
||||
return unless File.file?(old_file)
|
||||
File.move(old_file, MANUAL_SLOTS[0])
|
||||
end
|
||||
|
||||
# Runs all possible conversions on the given save data.
|
||||
# Saves a backup before running conversions.
|
||||
# @param save_data [Hash] save data to run conversions on
|
||||
# @return [Boolean] whether conversions were run
|
||||
def self.run_conversions(save_data)
|
||||
validate save_data => Hash
|
||||
conversions_to_run = self.get_conversions(save_data)
|
||||
return false if conversions_to_run.none?
|
||||
File.open(SaveData.get_backup_file_path, 'wb') { |f| Marshal.dump(save_data, f) }
|
||||
echoln "Backed up save to #{SaveData.get_backup_file_path}"
|
||||
echoln "Running #{conversions_to_run.length} conversions..."
|
||||
conversions_to_run.each do |conversion|
|
||||
echo "#{conversion.title}..."
|
||||
conversion.run(save_data)
|
||||
echoln ' done.'
|
||||
end
|
||||
echoln '' if conversions_to_run.length > 0
|
||||
save_data[:essentials_version] = Essentials::VERSION
|
||||
save_data[:game_version] = Settings::GAME_VERSION
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
class PokemonLoad_Scene
|
||||
def pbChoose(commands, continue_idx)
|
||||
@sprites["cmdwindow"].commands = commands
|
||||
loop do
|
||||
Graphics.update
|
||||
Input.update
|
||||
pbUpdate
|
||||
if Input.trigger?(Input::USE)
|
||||
return @sprites["cmdwindow"].index
|
||||
elsif @sprites["cmdwindow"].index == continue_idx
|
||||
@sprites["leftarrow"].visible = true
|
||||
@sprites["rightarrow"].visible = true
|
||||
if Input.trigger?(Input::LEFT)
|
||||
return -3
|
||||
elsif Input.trigger?(Input::RIGHT)
|
||||
return -2
|
||||
end
|
||||
else
|
||||
@sprites["leftarrow"].visible = false
|
||||
@sprites["rightarrow"].visible = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def pbStartScene(commands, show_continue, trainer, frame_count, map_id)
|
||||
@commands = commands
|
||||
@sprites = {}
|
||||
@viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
|
||||
@viewport.z = 99998
|
||||
addBackgroundOrColoredPlane(@sprites, "background", "loadbg", Color.new(248, 248, 248), @viewport)
|
||||
|
||||
@sprites["leftarrow"] = AnimatedSprite.new("Graphics/Pictures/leftarrow", 8, 40, 28, 2, @viewport)
|
||||
@sprites["leftarrow"].x = 10
|
||||
@sprites["leftarrow"].y = 140
|
||||
@sprites["leftarrow"].play
|
||||
|
||||
#@sprites["leftarrow"].visible=true
|
||||
|
||||
@sprites["rightarrow"] = AnimatedSprite.new("Graphics/Pictures/rightarrow", 8, 40, 28, 2, @viewport)
|
||||
@sprites["rightarrow"].x = 460
|
||||
@sprites["rightarrow"].y = 140
|
||||
@sprites["rightarrow"].play
|
||||
#@sprites["rightarrow"].visible=true
|
||||
|
||||
y = 16 * 2
|
||||
for i in 0...commands.length
|
||||
@sprites["panel#{i}"] = PokemonLoadPanel.new(i, commands[i],
|
||||
(show_continue) ? (i == 0) : false, trainer, frame_count, map_id, @viewport)
|
||||
@sprites["panel#{i}"].x = 24 * 2
|
||||
@sprites["panel#{i}"].y = y
|
||||
@sprites["panel#{i}"].pbRefresh
|
||||
y += (show_continue && i == 0) ? 112 * 2 : 24 * 2
|
||||
end
|
||||
@sprites["cmdwindow"] = Window_CommandPokemon.new([])
|
||||
@sprites["cmdwindow"].viewport = @viewport
|
||||
@sprites["cmdwindow"].visible = false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
class PokemonLoadScreen
|
||||
def initialize(scene)
|
||||
@scene = scene
|
||||
@selected_file = SaveData.get_newest_save_slot
|
||||
end
|
||||
|
||||
# @param file_path [String] file to load save data from
|
||||
# @return [Hash] save data
|
||||
def load_save_file(file_path)
|
||||
begin
|
||||
save_data = SaveData.read_from_file(file_path)
|
||||
rescue
|
||||
save_data = try_load_backup(file_path)
|
||||
end
|
||||
unless SaveData.valid?(save_data)
|
||||
save_data = try_load_backup(file_path)
|
||||
end
|
||||
return save_data
|
||||
end
|
||||
|
||||
def try_load_backup(file_path)
|
||||
if File.file?(file_path + ".bak")
|
||||
pbMessage(_INTL("The save file is corrupt. A backup will be loaded."))
|
||||
save_data = load_save_file(file_path + ".bak")
|
||||
else
|
||||
self.prompt_save_deletion(file_path)
|
||||
return {}
|
||||
end
|
||||
return save_data
|
||||
end
|
||||
|
||||
# Called if save file is invalid.
|
||||
# Prompts the player to delete the save files.
|
||||
def prompt_save_deletion(file_path)
|
||||
pbMessage(_INTL("A save file is corrupt, or is incompatible with this game."))
|
||||
self.delete_save_data(file_path) if pbConfirmMessageSerious(
|
||||
_INTL("Do you want to delete that save file? The game will exit afterwards either way.")
|
||||
)
|
||||
exit
|
||||
end
|
||||
|
||||
# nil deletes all, otherwise just the given file
|
||||
def delete_save_data(file_path = nil)
|
||||
begin
|
||||
SaveData.delete_file(file_path)
|
||||
pbMessage(_INTL("The save data was deleted."))
|
||||
rescue SystemCallError
|
||||
pbMessage(_INTL("The save data could not be deleted."))
|
||||
end
|
||||
end
|
||||
|
||||
def checkEnableSpritesDownload
|
||||
if $PokemonSystem.download_sprites && $PokemonSystem.download_sprites != 0
|
||||
customSprites = getCustomSpeciesList
|
||||
if !customSprites
|
||||
promptEnableSpritesDownload
|
||||
else
|
||||
if customSprites.length < 1000
|
||||
promptEnableSpritesDownload
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#So that the options menu is set on the correct difficulty on older saves
|
||||
def ensureCorrectDifficulty()
|
||||
$Trainer.selected_difficulty = 1 #normal
|
||||
$Trainer.selected_difficulty = 0 if $game_switches[SWITCH_GAME_DIFFICULTY_EASY]
|
||||
$Trainer.selected_difficulty = 2 if $game_switches[SWITCH_GAME_DIFFICULTY_HARD]
|
||||
$Trainer.lowest_difficulty = $Trainer.selected_difficulty if !$Trainer.lowest_difficulty
|
||||
end
|
||||
|
||||
def setGameMode()
|
||||
$Trainer.game_mode = 0 #classic
|
||||
$Trainer.game_mode = 2 if $game_switches[SWITCH_MODERN_MODE]
|
||||
$Trainer.game_mode = 3 if $game_switches[SWITCH_EXPERT_MODE]
|
||||
$Trainer.game_mode = 4 if $game_switches[SWITCH_SINGLE_POKEMON_MODE]
|
||||
$Trainer.game_mode = 1 if $game_switches[SWITCH_RANDOMIZED_AT_LEAST_ONCE]
|
||||
$Trainer.game_mode = 5 if $game_switches[ENABLED_DEBUG_MODE_AT_LEAST_ONCE]
|
||||
end
|
||||
|
||||
def promptEnableSpritesDownload
|
||||
message = "Some sprites appear to be missing from your game. \nWould you like the game to download sprites automatically while playing? (this requires an internet connection)"
|
||||
if pbConfirmMessage(message)
|
||||
$PokemonSystem.download_sprites = 0
|
||||
end
|
||||
end
|
||||
|
||||
def check_for_spritepack_update()
|
||||
$updated_spritesheets = [] if !$updated_spritesheets
|
||||
echoln new_spritepack_was_released()
|
||||
if new_spritepack_was_released()
|
||||
$updated_spritesheets = []
|
||||
end
|
||||
end
|
||||
|
||||
def preload_party(trainer)
|
||||
spriteLoader = BattleSpriteLoader.new
|
||||
for pokemon in trainer.party
|
||||
spriteLoader.preload_sprite_from_pokemon(pokemon)
|
||||
end
|
||||
end
|
||||
|
||||
#unused - too slow, & multithreading not possible
|
||||
# def preload_party_and_boxes(storage, trainer)
|
||||
# echoln "Loading boxes and party into cache in the background"
|
||||
# start_time = Time.now
|
||||
# spriterLoader = BattleSpriteLoader.new
|
||||
# for box in storage.boxes
|
||||
# for pokemon in box.pokemon
|
||||
# if pokemon != nil
|
||||
# if !pokemon.egg?
|
||||
# spriterLoader.preload_sprite_from_pokemon(pokemon)
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# for pokemon in trainer.party
|
||||
# spriterLoader.preload_sprite_from_pokemon(pokemon)
|
||||
# end
|
||||
# end_time = Time.now
|
||||
# echoln "Finished in #{end_time - start_time} seconds"
|
||||
# end
|
||||
|
||||
def pbStartLoadScreen
|
||||
updateHttpSettingsFile
|
||||
updateCreditsFile
|
||||
updateCustomDexFile
|
||||
newer_version = find_newer_available_version
|
||||
if newer_version
|
||||
pbMessage(_INTL("Version {1} is now available! Please use the game's installer to download the newest version. Check the Discord for more information.", newer_version))
|
||||
end
|
||||
|
||||
if Settings::STARTUP_MESSAGES != ""
|
||||
pbMessage(_INTL(Settings::STARTUP_MESSAGES))
|
||||
end
|
||||
if ($game_temp.unimportedSprites && $game_temp.unimportedSprites.size > 0)
|
||||
handleReplaceExistingSprites()
|
||||
end
|
||||
if ($game_temp.nb_imported_sprites && $game_temp.nb_imported_sprites > 0)
|
||||
pbMessage(_INTL("{1} new custom sprites were imported into the game", $game_temp.nb_imported_sprites.to_s))
|
||||
end
|
||||
checkEnableSpritesDownload
|
||||
$game_temp.nb_imported_sprites = nil
|
||||
copyKeybindings()
|
||||
save_file_list = SaveData::AUTO_SLOTS + SaveData::MANUAL_SLOTS
|
||||
first_time = true
|
||||
loop do
|
||||
# Outer loop is used for switching save files
|
||||
if @selected_file
|
||||
@save_data = load_save_file(SaveData.get_full_path(@selected_file))
|
||||
else
|
||||
@save_data = {}
|
||||
end
|
||||
commands = []
|
||||
cmd_continue = -1
|
||||
cmd_new_game = -1
|
||||
cmd_options = -1
|
||||
cmd_language = -1
|
||||
cmd_mystery_gift = -1
|
||||
cmd_debug = -1
|
||||
cmd_quit = -1
|
||||
show_continue = !@save_data.empty?
|
||||
new_game_plus = show_continue && (@save_data[:player].new_game_plus_unlocked || $DEBUG)
|
||||
|
||||
if show_continue
|
||||
commands[cmd_continue = commands.length] = "#{@selected_file}"
|
||||
if @save_data[:player].mystery_gift_unlocked
|
||||
commands[cmd_mystery_gift = commands.length] = _INTL('Mystery Gift') # Honestly I have no idea how to make Mystery Gift work well with this.
|
||||
end
|
||||
end
|
||||
|
||||
commands[cmd_new_game = commands.length] = _INTL('New Game')
|
||||
if new_game_plus
|
||||
commands[cmd_new_game_plus = commands.length] = _INTL('New Game +')
|
||||
end
|
||||
commands[cmd_options = commands.length] = _INTL('Options')
|
||||
commands[cmd_language = commands.length] = _INTL('Language') if Settings::LANGUAGES.length >= 2
|
||||
commands[cmd_discord = commands.length] = _INTL('Discord')
|
||||
commands[cmd_wiki = commands.length] = _INTL('Wiki')
|
||||
commands[cmd_debug = commands.length] = _INTL('Debug') if $DEBUG
|
||||
commands[cmd_quit = commands.length] = _INTL('Quit Game')
|
||||
cmd_left = -3
|
||||
cmd_right = -2
|
||||
|
||||
map_id = show_continue ? @save_data[:map_factory].map.map_id : 0
|
||||
@scene.pbStartScene(commands, show_continue, @save_data[:player],
|
||||
@save_data[:frame_count] || 0, map_id)
|
||||
@scene.pbSetParty(@save_data[:player]) if show_continue
|
||||
if first_time
|
||||
@scene.pbStartScene2
|
||||
first_time = false
|
||||
else
|
||||
@scene.pbUpdate
|
||||
end
|
||||
|
||||
loop do
|
||||
# Inner loop is used for going to other menus and back and stuff (vanilla)
|
||||
command = @scene.pbChoose(commands, cmd_continue)
|
||||
pbPlayDecisionSE if command != cmd_quit
|
||||
|
||||
case command
|
||||
when cmd_continue
|
||||
@scene.pbEndScene
|
||||
Game.load(@save_data)
|
||||
$game_switches[SWITCH_V5_1] = true
|
||||
ensureCorrectDifficulty()
|
||||
setGameMode()
|
||||
initialize_alt_sprite_substitutions()
|
||||
$PokemonGlobal.autogen_sprites_cache = {}
|
||||
check_for_spritepack_update()
|
||||
preload_party(@save_data[:player])
|
||||
return
|
||||
when cmd_new_game
|
||||
@scene.pbEndScene
|
||||
Game.start_new
|
||||
initialize_alt_sprite_substitutions()
|
||||
return
|
||||
when cmd_new_game_plus
|
||||
@scene.pbEndScene
|
||||
Game.start_new(@save_data[:bag], @save_data[:storage_system], @save_data[:player])
|
||||
initialize_alt_sprite_substitutions()
|
||||
@save_data[:player].new_game_plus_unlocked = true
|
||||
return
|
||||
when cmd_discord
|
||||
openUrlInBrowser(Settings::DISCORD_URL)
|
||||
when cmd_wiki
|
||||
openUrlInBrowser(Settings::WIKI_URL)
|
||||
when cmd_mystery_gift
|
||||
pbFadeOutIn { pbDownloadMysteryGift(@save_data[:player]) }
|
||||
when cmd_options
|
||||
pbFadeOutIn do
|
||||
scene = PokemonGameOption_Scene.new
|
||||
screen = PokemonOptionScreen.new(scene)
|
||||
screen.pbStartScreen(true)
|
||||
end
|
||||
when cmd_language
|
||||
@scene.pbEndScene
|
||||
$PokemonSystem.language = pbChooseLanguage
|
||||
pbLoadMessages('Data/' + Settings::LANGUAGES[$PokemonSystem.language][1])
|
||||
if show_continue
|
||||
@save_data[:pokemon_system] = $PokemonSystem
|
||||
File.open(SaveData.get_full_path(@selected_file), 'wb') { |file| Marshal.dump(@save_data, file) }
|
||||
end
|
||||
$scene = pbCallTitle
|
||||
return
|
||||
when cmd_debug
|
||||
pbFadeOutIn { pbDebugMenu(false) }
|
||||
when cmd_quit
|
||||
pbPlayCloseMenuSE
|
||||
@scene.pbEndScene
|
||||
$scene = nil
|
||||
return
|
||||
when cmd_left
|
||||
@scene.pbCloseScene
|
||||
@selected_file = SaveData.get_prev_slot(save_file_list, @selected_file)
|
||||
break # to outer loop
|
||||
when cmd_right
|
||||
@scene.pbCloseScene
|
||||
@selected_file = SaveData.get_next_slot(save_file_list, @selected_file)
|
||||
break # to outer loop
|
||||
else
|
||||
pbPlayBuzzerSE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
class PokemonSave_Scene
|
||||
def pbUpdateSlotInfo(slottext)
|
||||
pbDisposeSprite(@sprites, "slotinfo")
|
||||
@sprites["slotinfo"] = Window_AdvancedTextPokemon.new(slottext)
|
||||
@sprites["slotinfo"].viewport = @viewport
|
||||
@sprites["slotinfo"].x = 0
|
||||
@sprites["slotinfo"].y = 160
|
||||
@sprites["slotinfo"].width = 228 if @sprites["slotinfo"].width < 228
|
||||
@sprites["slotinfo"].visible = true
|
||||
end
|
||||
end
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
class PokemonSaveScreen
|
||||
def doSave(slot)
|
||||
if Game.save(slot)
|
||||
pbMessage(_INTL("\\se[]{1} saved the game.\\me[GUI save game]\\wtnp[30]", $Trainer.name))
|
||||
return true
|
||||
else
|
||||
pbMessage(_INTL("\\se[]Save failed.\\wtnp[30]"))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Return true if pause menu should close after this is done (if the game was saved successfully)
|
||||
def pbSaveScreen
|
||||
ret = false
|
||||
@scene.pbStartScreen
|
||||
if !$Trainer.save_slot
|
||||
# New Game - must select slot
|
||||
ret = slotSelect
|
||||
else
|
||||
choices = [
|
||||
_INTL("Save to #{$Trainer.save_slot}"),
|
||||
_INTL("Save to another slot"),
|
||||
_INTL("Don't save")
|
||||
]
|
||||
opt = pbMessage(_INTL('Would you like to save the game?'), choices, 3)
|
||||
if opt == 0
|
||||
pbSEPlay('GUI save choice')
|
||||
ret = doSave($Trainer.save_slot)
|
||||
elsif opt == 1
|
||||
pbPlayDecisionSE
|
||||
ret = slotSelect
|
||||
else
|
||||
pbPlayCancelSE
|
||||
end
|
||||
end
|
||||
@scene.pbEndScreen
|
||||
return ret
|
||||
end
|
||||
|
||||
# Call this to open the slot select screen
|
||||
# Returns true if the game was saved, otherwise false
|
||||
def slotSelect
|
||||
ret = false
|
||||
choices = SaveData::MANUAL_SLOTS
|
||||
choice_info = SaveData::MANUAL_SLOTS.map { |s| getSaveInfoBoxContents(s) }
|
||||
index = slotSelectCommands(choices, choice_info)
|
||||
if index >= 0
|
||||
slot = SaveData::MANUAL_SLOTS[index]
|
||||
# Confirm if slot not empty
|
||||
if !File.file?(SaveData.get_full_path(slot)) ||
|
||||
pbConfirmMessageSerious(_INTL("Are you sure you want to overwrite the save in #{slot}?")) # If the slot names were changed this grammar might need adjustment.
|
||||
pbSEPlay('GUI save choice')
|
||||
ret = doSave(slot)
|
||||
end
|
||||
end
|
||||
pbPlayCloseMenuSE if !ret
|
||||
return ret
|
||||
end
|
||||
|
||||
# Handles the UI for the save slot select screen. Returns the index of the chosen slot, or -1.
|
||||
# Based on pbShowCommands
|
||||
def slotSelectCommands(choices, choice_info, defaultCmd = 0)
|
||||
msgwindow = Window_AdvancedTextPokemon.new(_INTL("Which slot to save in?"))
|
||||
msgwindow.z = 99999
|
||||
msgwindow.visible = true
|
||||
msgwindow.letterbyletter = true
|
||||
msgwindow.back_opacity = MessageConfig::WINDOW_OPACITY
|
||||
pbBottomLeftLines(msgwindow, 2)
|
||||
$game_temp.message_window_showing = true if $game_temp
|
||||
msgwindow.setSkin(MessageConfig.pbGetSpeechFrame)
|
||||
|
||||
cmdwindow = Window_CommandPokemonEx.new(choices)
|
||||
cmdwindow.z = 99999
|
||||
cmdwindow.visible = true
|
||||
cmdwindow.resizeToFit(cmdwindow.commands)
|
||||
pbPositionNearMsgWindow(cmdwindow, msgwindow, :right)
|
||||
cmdwindow.index = defaultCmd
|
||||
command = 0
|
||||
loop do
|
||||
@scene.pbUpdateSlotInfo(choice_info[cmdwindow.index])
|
||||
Graphics.update
|
||||
Input.update
|
||||
cmdwindow.update
|
||||
msgwindow.update if msgwindow
|
||||
if Input.trigger?(Input::BACK)
|
||||
command = -1
|
||||
break
|
||||
end
|
||||
if Input.trigger?(Input::USE)
|
||||
command = cmdwindow.index
|
||||
break
|
||||
end
|
||||
pbUpdateSceneMap
|
||||
end
|
||||
ret = command
|
||||
cmdwindow.dispose
|
||||
msgwindow.dispose
|
||||
$game_temp.message_window_showing = false if $game_temp
|
||||
Input.update
|
||||
return ret
|
||||
end
|
||||
|
||||
# Show the player some data about their currently selected save slot for quick identification
|
||||
# This doesn't use player gender for coloring, unlike the default save window
|
||||
def getSaveInfoBoxContents(slot)
|
||||
full_path = SaveData.get_full_path(slot)
|
||||
if !File.file?(full_path)
|
||||
return _INTL("<ac><c3=3050C8,D0D0C8>(empty)</c3></ac>")
|
||||
end
|
||||
temp_save_data = SaveData.read_from_file(full_path)
|
||||
|
||||
# Last save time
|
||||
time = temp_save_data[:player].last_time_saved
|
||||
if time
|
||||
date_str = time.strftime("%x")
|
||||
time_str = time.strftime(_INTL("%I:%M%p"))
|
||||
datetime_str = "#{date_str}<r>#{time_str}<br>"
|
||||
else
|
||||
datetime_str = _INTL("<ac>(old save)</ac>")
|
||||
end
|
||||
|
||||
# Map name
|
||||
map_str = pbGetMapNameFromId(temp_save_data[:map_factory].map.map_id)
|
||||
|
||||
# Elapsed time
|
||||
totalsec = (temp_save_data[:frame_count] || 0) / Graphics.frame_rate
|
||||
hour = totalsec / 60 / 60
|
||||
min = totalsec / 60 % 60
|
||||
if hour > 0
|
||||
elapsed_str = _INTL("Time<r>{1}h {2}m<br>", hour, min)
|
||||
else
|
||||
elapsed_str = _INTL("Time<r>{1}m<br>", min)
|
||||
end
|
||||
|
||||
return "<c3=3050C8,D0D0C8>#{datetime_str}</c3>" + # blue
|
||||
"<ac><c3=209808,90F090>#{map_str}</c3></ac>" + # green
|
||||
"#{elapsed_str}"
|
||||
end
|
||||
end
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
module Game
|
||||
# Fix New Game bug (if you saved during an event script)
|
||||
# This fix is from Essentials v20.1 Hotfixes 1.0.5
|
||||
def self.start_new(ngp_bag = nil, ngp_storage = nil, ngp_trainer = nil)
|
||||
if $game_map && $game_map.events
|
||||
$game_map.events.each_value { |event| event.clear_starting }
|
||||
end
|
||||
$game_temp.common_event_id = 0 if $game_temp
|
||||
$PokemonTemp.begunNewGame = true
|
||||
pbMapInterpreter&.clear
|
||||
pbMapInterpreter&.setup(nil, 0, 0)
|
||||
$scene = Scene_Map.new
|
||||
SaveData.load_new_game_values
|
||||
$MapFactory = PokemonMapFactory.new($data_system.start_map_id)
|
||||
$game_player.moveto($data_system.start_x, $data_system.start_y)
|
||||
$game_player.refresh
|
||||
$PokemonEncounters = PokemonEncounters.new
|
||||
$PokemonEncounters.setup($game_map.map_id)
|
||||
$game_map.autoplay
|
||||
$game_map.update
|
||||
#
|
||||
# if ngp_bag != nil
|
||||
# $PokemonBag = ngp_clean_item_data(ngp_bag)
|
||||
# end
|
||||
if ngp_storage != nil
|
||||
$PokemonStorage = ngp_clean_pc_data(ngp_storage, ngp_trainer.party)
|
||||
end
|
||||
|
||||
if ngp_trainer
|
||||
$Trainer.unlocked_hats = ngp_trainer.unlocked_hats
|
||||
$Trainer.unlocked_clothes = ngp_trainer.unlocked_clothes
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Loads bootup data from save file (if it exists) or creates bootup data (if
|
||||
# it doesn't).
|
||||
def self.set_up_system
|
||||
SaveData.move_old_windows_save if System.platform[/Windows/]
|
||||
save_slot = SaveData.get_newest_save_slot
|
||||
if save_slot
|
||||
save_data = SaveData.read_from_file(SaveData.get_full_path(save_slot))
|
||||
else
|
||||
save_data = {}
|
||||
end
|
||||
if save_data.empty?
|
||||
SaveData.initialize_bootup_values
|
||||
else
|
||||
SaveData.load_bootup_values(save_data)
|
||||
end
|
||||
# Set resize factor
|
||||
pbSetResizeFactor([$PokemonSystem.screensize, 4].min)
|
||||
# Set language (and choose language if there is no save file)
|
||||
if Settings::LANGUAGES.length >= 2
|
||||
$PokemonSystem.language = pbChooseLanguage if save_data.empty?
|
||||
pbLoadMessages('Data/' + Settings::LANGUAGES[$PokemonSystem.language][1])
|
||||
end
|
||||
end
|
||||
|
||||
def self.backup_savefile(save_path, slot)
|
||||
backup_dir = File.join(File.dirname(save_path), "backups")
|
||||
Dir.mkdir(backup_dir) if !Dir.exist?(backup_dir)
|
||||
|
||||
backup_slot_dir = File.join(File.dirname(save_path), "backups/#{slot}")
|
||||
Dir.mkdir(backup_slot_dir) if !Dir.exist?(backup_slot_dir)
|
||||
|
||||
# Manage rolling backups
|
||||
if File.exist?(save_path)
|
||||
# Generate a timestamped backup name
|
||||
timestamp = Time.now.strftime("%Y%m%d%H%M%S")
|
||||
backup_file = File.join(backup_slot_dir, "#{slot}_#{timestamp}.rxdata")
|
||||
|
||||
# Copy the save file manually
|
||||
File.open(save_path, 'rb') do |source|
|
||||
File.open(backup_file, 'wb') do |dest|
|
||||
dest.write(source.read)
|
||||
end
|
||||
end
|
||||
|
||||
# Clean up old backups
|
||||
backups = Dir.get(backup_slot_dir, "*.rxdata")
|
||||
# Keep only the latest N backups
|
||||
if backups.length > Settings::SAVEFILE_NB_BACKUPS
|
||||
excess_backups = backups[0...(backups.length - Settings::SAVEFILE_NB_BACKUPS)]
|
||||
echoln excess_backups
|
||||
excess_backups.each { |old_backup| File.delete(old_backup) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Saves the game. Returns whether the operation was successful.
|
||||
# @param save_file [String] the save file path
|
||||
# @param safe [Boolean] whether $PokemonGlobal.safesave should be set to true
|
||||
# @return [Boolean] whether the operation was successful
|
||||
# @raise [SaveData::InvalidValueError] if an invalid value is being saved
|
||||
def self.save(slot = nil, auto = false, safe: false)
|
||||
slot = $Trainer.save_slot if slot.nil?
|
||||
return false if slot.nil?
|
||||
|
||||
file_path = SaveData.get_full_path(slot)
|
||||
self.backup_savefile(file_path, slot)
|
||||
|
||||
$PokemonGlobal.safesave = safe
|
||||
$game_system.save_count += 1
|
||||
$game_system.magic_number = $data_system.magic_number
|
||||
$Trainer.save_slot = slot unless auto
|
||||
$Trainer.last_time_saved = Time.now
|
||||
begin
|
||||
SaveData.save_to_file(file_path)
|
||||
Graphics.frame_reset
|
||||
rescue IOError, SystemCallError
|
||||
$game_system.save_count -= 1
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
# Overwrites the first empty autosave slot, otherwise the oldest existing autosave
|
||||
def self.auto_save
|
||||
oldest_time = nil
|
||||
oldest_slot = nil
|
||||
SaveData::AUTO_SLOTS.each do |slot|
|
||||
full_path = SaveData.get_full_path(slot)
|
||||
if !File.file?(full_path)
|
||||
oldest_slot = slot
|
||||
break
|
||||
end
|
||||
temp_save_data = SaveData.read_from_file(full_path)
|
||||
save_time = temp_save_data[:player].last_time_saved || Time.at(1)
|
||||
if oldest_time.nil? || save_time < oldest_time
|
||||
oldest_time = save_time
|
||||
oldest_slot = slot
|
||||
end
|
||||
end
|
||||
self.save(oldest_slot, true)
|
||||
end
|
||||
end
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
|
||||
# Lol who needs the FileUtils gem?
|
||||
# This is the implementation from the original pbEmergencySave.
|
||||
def file_copy(src, dst)
|
||||
File.open(src, 'rb') do |r|
|
||||
File.open(dst, 'wb') do |w|
|
||||
while s = r.read(4096)
|
||||
w.write s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# When I needed extra data fields in the save file I put them in Player because it seemed easier than figuring out
|
||||
# how to make a save file conversion, and I prefer to maintain backwards compatibility.
|
||||
class Player
|
||||
attr_accessor :last_time_saved
|
||||
attr_accessor :save_slot
|
||||
attr_accessor :autosave_steps
|
||||
end
|
||||
|
||||
def pbEmergencySave
|
||||
oldscene = $scene
|
||||
$scene = nil
|
||||
pbMessage(_INTL("The script is taking too long. The game will restart."))
|
||||
return if !$Trainer
|
||||
return if !$Trainer.save_slot
|
||||
current_file = SaveData.get_full_path($Trainer.save_slot)
|
||||
backup_file = SaveData.get_backup_file_path
|
||||
file_copy(current_file, backup_file)
|
||||
if Game.save
|
||||
pbMessage(_INTL("\\se[]The game was saved.\\me[GUI save game] The previous save file has been backed up.\\wtnp[30]"))
|
||||
else
|
||||
pbMessage(_INTL("\\se[]Save failed.\\wtnp[30]"))
|
||||
end
|
||||
$scene = oldscene
|
||||
end
|
||||
Reference in New Issue
Block a user