diff --git a/Data/Scripts/001_Settings.rb b/Data/Scripts/001_Settings.rb index 4874caa46..ec6a454c5 100644 --- a/Data/Scripts/001_Settings.rb +++ b/Data/Scripts/001_Settings.rb @@ -5,6 +5,9 @@ #==============================================================================# module Settings + # The version of the game. It has to adhere to the MAJOR.MINOR.PATCH format. + GAME_VERSION = '1.0.0' + # The generation that the battle system follows. Used throughout the battle # scripts, and also by some other settings which are used in and out of battle # (you can of course change those settings to suit your game). diff --git a/Data/Scripts/001_Technical/010_Validation.rb b/Data/Scripts/001_Technical/010_Validation.rb index 0714d1368..e3c23ccca 100644 --- a/Data/Scripts/001_Technical/010_Validation.rb +++ b/Data/Scripts/001_Technical/010_Validation.rb @@ -2,13 +2,13 @@ module Kernel private - # Used to check whether values are of a given class or respond to a method. + # Used to check whether method arguments are of a given class or respond to a method. # @param value_pairs [Hash{Object => Class, Array, Symbol}] value pairs to validate # @example Validate a class or method # validate foo => Integer, baz => :to_s # raises an error if foo is not an Integer or if baz doesn't implement #to_s # @example Validate a class from an array # validate foo => [Sprite, Bitmap, Viewport] # raises an error if foo isn't a Sprite, Bitmap or Viewport - # @raise [ArgumentError] raised if validation fails + # @raise [ArgumentError] if validation fails def validate(value_pairs) unless value_pairs.is_a?(Hash) raise ArgumentError, "Non-hash argument #{value_pairs.inspect} passed into validate." diff --git a/Data/Scripts/007_Events and files/001_Save data/001_SaveData.rb b/Data/Scripts/007_Events and files/001_Save data/001_SaveData.rb new file mode 100644 index 000000000..393bd2cf3 --- /dev/null +++ b/Data/Scripts/007_Events and files/001_Save data/001_SaveData.rb @@ -0,0 +1,106 @@ +# The SaveData module is used to manipulate save data. It contains the {Value}s +# that make up the save data and {Conversion}s for resolving incompatibilities +# between Essentials and game versions. +# @see SaveData.register +# @see SaveData.register_conversion +module SaveData + # Contains the file path of the save file. + FILE_PATH = if File.directory?(System.data_directory) + System.data_directory + '/Game.rxdata' + else + './Game.rxdata' + end + + # Compiles the save data and saves a marshaled version of it into + # the given file. + # @param file_path [String] path of the file to save into + # @raise [InvalidValueError] if an invalid value is being saved + def self.save_to_file(file_path) + validate file_path => String + + save_data = self.compile + + File.open(file_path, 'wb') { |file| Marshal.dump(save_data, file) } + end + + # Fetches save data from the given file. + # @param file_path [String] path of the file to read from + # @return [Hash] save data in Hash format + # @raise (see .get_data_from_file) + def self.read_from_file(file_path) + validate file_path => String + + save_data = get_data_from_file(file_path) + + save_data = to_hash_format(save_data) if save_data.is_a?(Array) + + return save_data + end + + # @return [Boolean] whether the save file exists + def self.exists? + return File.file?(FILE_PATH) + end + + # 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') + 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 + # @return [Hash, Array] loaded save data + # @raise [IOError, SystemCallError] if file opening fails + def self.get_data_from_file(file_path) + validate file_path => String + save_data = nil + + File.open(file_path) do |file| + data = Marshal.load(file) + + if data.is_a?(Hash) + save_data = data + next + end + + save_data = [data] + + save_data << Marshal.load(file) until file.eof? + end + + return save_data + end + + # Converts the pre-v19 format data to the new format. + # @param old_format [Array] pre-v19 format save data + # @return [Hash] save data in new format + def self.to_hash_format(old_format) + validate old_format => Array + hash = {} + + @values.each do |value| + data = value.get_from_old_format(old_format) + hash[value.id] = data unless data.nil? + end + + return hash + end + + # Moves a save file from the old Saved Games folder to the new + # location specified by {FILE_PATH}. Does nothing if a save file + # already exists in {FILE_PATH}. + def self.move_old_windows_save + return if File.file?(FILE_PATH) + 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, FILE_PATH) + end +end diff --git a/Data/Scripts/007_Events and files/001_Save data/002_Values.rb b/Data/Scripts/007_Events and files/001_Save data/002_Values.rb new file mode 100644 index 000000000..39c816c65 --- /dev/null +++ b/Data/Scripts/007_Events and files/001_Save data/002_Values.rb @@ -0,0 +1,264 @@ +module SaveData + # Contains Value objects for each save element. + # Populated during runtime by SaveData.register calls. + # @type [Array] + @values = [] + + # An error raised if an invalid save value is being saved or loaded. + class InvalidValueError < RuntimeError; end + + # Represents a single value in save data. + # New values are added using {SaveData.register}. + class Value + # @return [Symbol] the value id + attr_reader :id + + # @param id [Symbol] value id + def initialize(id, &block) + validate id => Symbol, block => Proc + @id = id + @loaded = false + @load_in_bootup = false + instance_eval(&block) + raise "No save_value defined for save value #{id.inspect}" if @save_proc.nil? + raise "No load_value defined for save value #{id.inspect}" if @load_proc.nil? + end + + # Calls the value's save proc and returns its value. + # @return [Object] save proc value + # @raise [InvalidValueError] if an invalid value is being saved + def save + value = @save_proc.call + + validate_value(value) + + return value + end + + # Calls the value's load proc with the given argument passed into it. + # @param value [Object] load proc argument + # @raise [InvalidValueError] if an invalid value is being loaded + def load(value) + validate_value(value) + + @load_proc.call(value) + @loaded = true + end + + # @param value [Object] value to check + # @return [Boolean] whether the given value is valid + def valid?(value) + return true if @ensured_class.nil? + return value.is_a?(Object.const_get(@ensured_class)) + end + + # Calls the save value's load proc with the value fetched + # from the defined new game value proc. + # @raise (see #load) + def load_new_game_value + unless self.has_new_game_proc? + raise "Save value #{@id.inspect} has no new_game_value defined" + end + + self.load(@new_game_value_proc.call) + end + + # @return [Boolean] whether the value has a new game value proc defined + def has_new_game_proc? + return @new_game_value_proc.is_a?(Proc) + end + + # @return [Boolean] whether the value should be loaded during bootup + def load_in_bootup? + return @load_in_bootup + end + + # @return [Boolean] whether the value has been loaded + def loaded? + return @loaded + end + + # Uses the {#from_old_format} proc to select the correct data from + # +old_format+ and return it. + # Returns nil if the proc is undefined. + # @param old_format [Array] old format to load value from + # @return [Object] data from the old format + def get_from_old_format(old_format) + return nil if @old_format_get_proc.nil? + return @old_format_get_proc.call(old_format) + end + + private + + # @!group Configuration + + # Defines what is saved into save data. Requires a block. + # @see SaveData.register + def save_value(&block) + raise ArgumentError, 'No block given to save_value' unless block_given? + @save_proc = block + end + + # Defines how the loaded value is placed into a global variable. + # Requires a block with the loaded value as its parameter. + # @see SaveData.register + def load_value(&block) + raise ArgumentError, 'No block given to load_value' unless block_given? + @load_proc = block + end + + # If present, sets the value to be loaded during bootup. + # @see SaveData.register + def load_in_bootup + @load_in_bootup = true + end + + # If present, defines what the value is set to at the start of a new game. + # @see SaveData.register + def new_game_value(&block) + raise ArgumentError, 'No block given to new_game_value' unless block_given? + @new_game_value_proc = block + end + + # If present, ensures that the value is of the given class. + # @param class_name [Symbol] class to enforce + # @see SaveData.register + def ensure_class(class_name) + validate class_name => Symbol + @ensured_class = class_name + end + + # If present, defines how the value should be fetched from the pre-v19 + # save format. Requires a block with the old format array as its parameter. + # @see SaveData.register + def from_old_format(&block) + raise ArgumentError, 'No block given to from_old_format' unless block_given? + @old_format_get_proc = block + end + + # @!endgroup + + # Raises an {InvalidValueError} if the given value is invalid. + # @param value [Object] value to check + # @raise [InvalidValueError] if the value is invalid + def validate_value(value) + return if self.valid?(value) + + raise InvalidValueError, + "Save value #{@id.inspect} is not a #{@ensured_class} (#{value.class.name} given)" + end + end + + # Registers a {Value} to be saved into save data. + # Takes a block which defines the value's saving ({Value#save_value}) + # and loading ({Value#load_value}) procedures. + # + # It is also possible to provide a proc for fetching the value + # from the pre-v19 format ({Value#from_old_format}), define + # a value to be set upon starting a new game with {Value#new_game_value} + # and ensure that the saved and loaded value is of the correct + # class with {Value#ensure_class}. + # + # Values can be registered to be loaded on bootup with + # {Value#load_in_bootup}. If a new_game_value proc is defined, it + # will be called when the game is launched for the first time, + # or if the save data does not contain the value in question. + # + # @example Registering a new value + # SaveData.register(:foo) do + # ensure_class :Foo + # save_value { $foo } + # load_value { |value| $foo = value } + # new_game_value { Foo.new } + # from_old_format { |old_format| old_format[16] if old_format[16].is_a?(Foo) } + # end + # @example Registering a value to be loaded on bootup + # SaveData.register(:bar) do + # load_in_bootup + # save_value { $bar } + # load_value { |value| $bar = value } + # new_game_value { Bar.new } + # end + # @param id [Symbol] value id + # @yieldself [Value] + def self.register(id, &block) + validate id => Symbol + + unless block_given? + raise ArgumentError, 'No block given to SaveData.register' + end + + @values << Value.new(id, &block) + end + + # @param save_data [Hash] save data to validate + # @return [Boolean] whether the given save data is valid + def self.valid?(save_data) + validate save_data => Hash + return @values.all? { |value| value.valid?(save_data[value.id]) } + end + + # @return [Hash{Symbol => Object}] a hash representation of the save data + # @raise [InvalidValueError] if an invalid value is being saved + def self.compile + save_data = {} + @values.each { |value| save_data[value.id] = value.save } + return save_data + end + + # Loads the values from the given save data by + # calling each {Value} object's {Value#load_value} proc. + # Values that are already loaded are skipped. + # If a value does not exist in the save data and has + # a {Value#new_game_value} proc defined, that value + # is loaded instead. + # @param save_data [Hash] save data to load + # @raise [InvalidValueError] if an invalid value is being loaded + def self.load_all_values(save_data) + validate save_data => Hash + + load_values(save_data) { |value| !value.loaded? } + end + + # Loads each value from the given save data that has + # been set to be loaded during bootup. + # @param save_data [Hash] save data to load + # @raise [InvalidValueError] if an invalid value is being loaded + def self.load_bootup_values(save_data) + validate save_data => Hash + + load_values(save_data) { |value| !value.loaded? && value.load_in_bootup? } + end + + # Loads values from the given save data. + # An optional condition can be passed. + # @param save_data [Hash] save data to load from + # @param condition_block [Proc] optional condition + # @api private + def self.load_values(save_data, &condition_block) + @values.each do |value| + next if block_given? && !condition_block.call(value) + if save_data.has_key?(value.id) + value.load(save_data[value.id]) + elsif value.has_new_game_proc? + value.load_new_game_value + end + end + end + + # Loads each {Value}'s new game value, if one is defined. + def self.load_new_game_values + @values.each do |value| + value.load_new_game_value if value.has_new_game_proc? && !value.loaded? + end + end + + # Goes through each value with {Value#load_in_bootup} enabled and + # loads their new game value, if one is defined. + def self.initialize_bootup_values + @values.each do |value| + next unless value.load_in_bootup? + value.load_new_game_value if value.has_new_game_proc? && !value.loaded? + end + end +end diff --git a/Data/Scripts/007_Events and files/001_Save data/003_BuiltinValues.rb b/Data/Scripts/007_Events and files/001_Save data/003_BuiltinValues.rb new file mode 100644 index 000000000..cf58818ea --- /dev/null +++ b/Data/Scripts/007_Events and files/001_Save data/003_BuiltinValues.rb @@ -0,0 +1,129 @@ +# Contains the save values defined in Essentials by default. + +SaveData.register(:player) do + ensure_class :PlayerTrainer + save_value { $Trainer } + load_value { |value| $Trainer = value } + from_old_format { |old_format| old_format[0] } +end + +SaveData.register(:frame_count) do + ensure_class :Integer + save_value { Graphics.frame_count } + load_value { |value| Graphics.frame_count = value } + new_game_value { 0 } + from_old_format { |old_format| old_format[1] } +end + +SaveData.register(:game_system) do + load_in_bootup + ensure_class :Game_System + save_value { $game_system } + load_value { |value| $game_system = value } + new_game_value { Game_System.new } + from_old_format { |old_format| old_format[2] } +end + +SaveData.register(:pokemon_system) do + load_in_bootup + ensure_class :PokemonSystem + save_value { $PokemonSystem } + load_value { |value| $PokemonSystem = value } + new_game_value { PokemonSystem.new } + from_old_format { |old_format| old_format[3] } +end + +SaveData.register(:switches) do + ensure_class :Game_Switches + save_value { $game_switches } + load_value { |value| $game_switches = value } + new_game_value { Game_Switches.new } + from_old_format { |old_format| old_format[5] } +end + +SaveData.register(:variables) do + ensure_class :Game_Variables + save_value { $game_variables } + load_value { |value| $game_variables = value } + new_game_value { Game_Variables.new } + from_old_format { |old_format| old_format[6] } +end + +SaveData.register(:self_switches) do + ensure_class :Game_SelfSwitches + save_value { $game_self_switches } + load_value { |value| $game_self_switches = value } + new_game_value { Game_SelfSwitches.new } + from_old_format { |old_format| old_format[7] } +end + +SaveData.register(:game_screen) do + ensure_class :Game_Screen + save_value { $game_screen } + load_value { |value| $game_screen = value } + new_game_value { Game_Screen.new } + from_old_format { |old_format| old_format[8] } +end + +SaveData.register(:map_factory) do + ensure_class :PokemonMapFactory + save_value { $MapFactory } + load_value { |value| $MapFactory = value } + from_old_format { |old_format| old_format[9] } +end + +SaveData.register(:game_player) do + ensure_class :Game_Player + save_value { $game_player } + load_value { |value| $game_player = value } + new_game_value { Game_Player.new } + from_old_format { |old_format| old_format[10] } +end + +SaveData.register(:global_metadata) do + ensure_class :PokemonGlobalMetadata + save_value { $PokemonGlobal } + load_value { |value| $PokemonGlobal = value } + new_game_value { PokemonGlobalMetadata.new } + from_old_format { |old_format| old_format[11] } +end + +SaveData.register(:map_metadata) do + ensure_class :PokemonMapMetadata + save_value { $PokemonMap } + load_value { |value| $PokemonMap = value } + new_game_value { PokemonMapMetadata.new } + from_old_format { |old_format| old_format[12] } +end + +SaveData.register(:bag) do + ensure_class :PokemonBag + save_value { $PokemonBag } + load_value { |value| $PokemonBag = value } + from_old_format { |old_format| old_format[13] } +end + +SaveData.register(:storage_system) do + ensure_class :PokemonStorage + save_value { $PokemonStorage } + load_value { |value| $PokemonStorage = value } + new_game_value { PokemonStorage.new } + from_old_format { |old_format| old_format[14] } +end + +SaveData.register(:essentials_version) do + load_in_bootup + ensure_class :String + save_value { Essentials::VERSION } + load_value { |value| $SaveVersion = value } + new_game_value { Essentials::VERSION } + from_old_format { |old_format| old_format[15] } +end + +SaveData.register(:game_version) do + load_in_bootup + ensure_class :String + save_value { Settings::GAME_VERSION } + load_value { |value| $game_version = value } + new_game_value { Settings::GAME_VERSION } +end diff --git a/Data/Scripts/007_Events and files/001_Save data/004_Conversion.rb b/Data/Scripts/007_Events and files/001_Save data/004_Conversion.rb new file mode 100644 index 000000000..cf96df00b --- /dev/null +++ b/Data/Scripts/007_Events and files/001_Save data/004_Conversion.rb @@ -0,0 +1,218 @@ +module SaveData + # Contains Conversion objects for each defined conversion: + # { + # :essentials => { + # '19' => [, ...], + # '19.1' => [, ...], + # ... + # }, + # :game => { + # '1.1.0' => [, ...], + # '1.2.0' => [, ...], + # ... + # } + # } + # Populated during runtime by SaveData.register_conversion calls. + @conversions = { + essentials: {}, + game: {} + } + + # Represents a conversion made to save data. + # New conversions are added using {SaveData.register_conversion}. + class Conversion + # @return [Symbol] conversion ID + attr_reader :id + # @return [String] conversion title + attr_reader :title + # @return [Symbol] trigger type of the conversion (+:essentials+ or +:game+) + attr_reader :trigger_type + # @return [String] trigger version of the conversion + attr_reader :version + + # @param id [String] conversion ID + def initialize(id, &block) + @id = id + @value_procs = {} + @all_proc = nil + @title = "Running conversion #{@id}" + @trigger_type = nil + @version = nil + instance_eval(&block) + if @trigger_type.nil? || @version.nil? + raise "Conversion #{@id} is missing a condition" + end + end + + # Runs the conversion on the given save data. + # @param save_data [Hash] + def run(save_data) + @value_procs.each do |value_id, proc| + unless save_data.has_key?(value_id) + raise "Save data does not have value #{value_id.inspect}" + end + proc.call(save_data[value_id]) + end + @all_proc.call(save_data) if @all_proc.is_a?(Proc) + end + + # Returns whether the conversion should be run with the given version. + # @param version [String] version to check + # @return [Boolean] whether the conversion should be run + def should_run?(version) + return PluginManager.compare_versions(version, @version) < 0 + end + + private + + # @!group Configuration + + # Sets the conversion's title. + # @param new_title [String] conversion title + # @note Since conversions are run before loading the player's chosen language, + # conversion titles can not be localized. + # @see SaveData.register_conversion + def display_title(new_title) + validate new_title => String + @title = new_title + end + + # Sets the conversion to trigger for save files created below + # the given Essentials version. + # @param version [Numeric, String] + # @see SaveData.register_conversion + def essentials_version(version) + validate version => [Numeric, String] + + raise "Multiple conditions in conversion #{@id}" unless @version.nil? + + @trigger_type = :essentials + @version = version.to_s + end + + # Sets the conversion to trigger for save files created below + # the given game version. + # @param version [Numeric, String] + # @see SaveData.register_conversion + def game_version(version) + validate version => [Numeric, String] + + raise "Multiple conditions in conversion #{@id}" unless @version.nil? + + @trigger_type = :game + @version = version.to_s + end + + # Defines a conversion to the given save value. + # @param value_id [Symbol] save value ID + # @see SaveData.register_conversion + def to_value(value_id, &block) + validate value_id => Symbol + + raise ArgumentError, 'No block given to to_value' unless block_given? + + if @value_procs[value_id].is_a?(Proc) + raise "Multiple to_value definitions in conversion #{@id} for #{value_id}" + end + + @value_procs[value_id] = block + end + + # Defines a conversion to the entire save data. + # @see SaveData.register_conversion + def to_all(&block) + raise ArgumentError, 'No block given to to_all' unless block_given? + + if @all_proc.is_a?(Proc) + raise "Multiple to_all definitions in conversion #{@id}" + end + + @all_proc = block + end + + # @!endgroup + end + + # Registers a {Conversion} to occur for save data that meets the given criteria. + # Two types of criteria can be defined: {Conversion#essentials_version} and + # {Conversion#game_version}. The conversion is automatically run on save data + # that contains an older version number. + # + # A single value can be modified with {Conversion#to_value}. The entire save data + # is accessed with {Conversion#to_all}, and a conversion title can be specified + # with {Conversion#display_title}. + # @example Registering a new conversion + # SaveData.register_conversion(:my_conversion) do + # game_version '1.1.0' + # display_title 'Converting some stuff' + # to_value :player do |player| + # # code that modifies the :player value + # end + # to_all do |save_data| + # save_data[:new_value] = Foo.new + # end + # end + # @yieldself [Conversion] + def self.register_conversion(id, &block) + validate id => Symbol + + unless block_given? + raise ArgumentError, 'No block given to SaveData.register_conversion' + end + + conversion = Conversion.new(id, &block) + + @conversions[conversion.trigger_type][conversion.version] ||= [] + @conversions[conversion.trigger_type][conversion.version] << conversion + 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::FILE_PATH + '.bak', 'wb') { |f| Marshal.dump(save_data, f) } + + echoln "Running #{conversions_to_run.length} conversions..." + + conversions_to_run.each do |conversion| + echo "#{conversion.title}..." + conversion.run(save_data) + echoln ' done.' + end + + return true + end + + # @param save_data [Hash] save data to get conversions for + # @return [Array] all conversions that should be run on the data + def self.get_conversions(save_data) + conversions_to_run = [] + + versions = { + essentials: save_data[:essentials_version] || '18.1', + game: save_data[:game_version] || '0.0.0' + } + + [:essentials, :game].each do |trigger_type| + # Ensure the versions are sorted from lowest to highest + sorted_versions = @conversions[trigger_type].keys.sort do |v1, v2| + PluginManager.compare_versions(v1, v2) + end + + sorted_versions.each do |version| + @conversions[trigger_type][version].each do |conversion| + next unless conversion.should_run?(versions[trigger_type]) + conversions_to_run << conversion + end + end + end + + return conversions_to_run + end +end diff --git a/Data/Scripts/007_Events and files/001_Save data/005_BuiltinConversions.rb b/Data/Scripts/007_Events and files/001_Save data/005_BuiltinConversions.rb new file mode 100644 index 000000000..3be75f7d5 --- /dev/null +++ b/Data/Scripts/007_Events and files/001_Save data/005_BuiltinConversions.rb @@ -0,0 +1,48 @@ +# Contains conversions defined in Essentials by default. + +SaveData.register_conversion(:v19_define_versions) do + essentials_version 19 + display_title 'Defining versions in save data' + to_all do |save_data| + unless save_data.has_key?(:essentials_version) + save_data[:essentials_version] = Essentials::VERSION + end + unless save_data.has_key?(:game_version) + save_data[:game_version] = Settings::GAME_VERSION + end + end +end + +SaveData.register_conversion(:v19_convert_player) do + essentials_version 19 + display_title 'Converting player trainer' + to_all do |save_data| + next if save_data[:player].is_a?(PlayerTrainer) + # Conversion of the party is handled in PokeBattle_Trainer.copy + save_data[:player] = PokeBattle_Trainer.copy(save_data[:player]) + end +end + +SaveData.register_conversion(:v19_convert_storage) do + essentials_version 19 + display_title 'Converting Pokémon in storage' + to_value :storage_system do |storage| + storage.instance_eval do + for box in 0...self.maxBoxes + for i in 0...self.maxPokemon(box) + next unless self[box, i] + next if self[box, i].is_a?(Pokemon) + self[box, i] = PokeBattle_Pokemon.copy(self[box, i]) + end + end + end # storage.instance_eval + end # to_value +end + +SaveData.register_conversion(:v19_convert_global_metadata) do + essentials_version 19 + display_title 'Converting global metadata' + to_value :global_metadata do |global| + global.encounter_version ||= 0 + end +end diff --git a/Data/Scripts/014_Trainers/001b_Trainer_deprecated.rb b/Data/Scripts/014_Trainers/001b_Trainer_deprecated.rb index b3e8e1810..b33ad800d 100644 --- a/Data/Scripts/014_Trainers/001b_Trainer_deprecated.rb +++ b/Data/Scripts/014_Trainers/001b_Trainer_deprecated.rb @@ -51,7 +51,7 @@ class PokeBattle_Trainer ret.character_ID = trainer.metaID if trainer.metaID ret.outfit = trainer.outfit if trainer.outfit ret.language = trainer.language if trainer.language - trainer.party.each { |p| ret.party.push(Pokemon.copy(p)) } + trainer.party.each { |p| ret.party.push(PokeBattle_Pokemon.copy(p)) } ret.badges = trainer.badges.clone ret.money = trainer.money trainer.seen.each_with_index { |value, i| ret.set_seen(i) if value } diff --git a/Data/Scripts/016_Pokemon/010_Pokemon_Deprecated.rb b/Data/Scripts/016_Pokemon/010_Pokemon_Deprecated.rb index d5b383bf7..425292aed 100644 --- a/Data/Scripts/016_Pokemon/010_Pokemon_Deprecated.rb +++ b/Data/Scripts/016_Pokemon/010_Pokemon_Deprecated.rb @@ -20,13 +20,15 @@ class PokeBattle_Pokemon attr_reader :trainerID, :ot, :otgender, :language attr_reader :shadow, :heartgauge, :savedexp, :savedev, :hypermode, :shadowmoves - def initialise + def initialize(*args) raise "PokeBattle_Pokemon.new is deprecated. Use Pokemon.new instead." end def self.copy(pkmn) + return pkmn if pkmn.is_a?(Pokemon) owner = Pokemon::Owner.new(pkmn.trainerID, pkmn.ot, pkmn.otgender, pkmn.language) - ret = Pokemon.new(pkmn.species, pkmn.level, owner, false) + # Set level to 1 initially, as it will be recalculated later + ret = Pokemon.new(pkmn.species, 1, owner, false) ret.forced_form = pkmn.forcedForm if pkmn.forcedForm ret.time_form_set = pkmn.formTime ret.exp = pkmn.exp diff --git a/Data/Scripts/017_UI/013_PScreen_Load.rb b/Data/Scripts/017_UI/013_PScreen_Load.rb index 3035d77c8..a1121ab4d 100644 --- a/Data/Scripts/017_UI/013_PScreen_Load.rb +++ b/Data/Scripts/017_UI/013_PScreen_Load.rb @@ -98,20 +98,20 @@ end # #=============================================================================== class PokemonLoad_Scene - def pbStartScene(commands,showContinue,trainer,framecount,mapid) + def pbStartScene(commands, show_continue, trainer, frame_count, map_id) @commands = commands @sprites = {} - @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height) @viewport.z = 99998 addBackgroundOrColoredPlane(@sprites,"background","loadbg",Color.new(248,248,248),@viewport) y = 16*2 for i in 0...commands.length @sprites["panel#{i}"] = PokemonLoadPanel.new(i,commands[i], - (showContinue) ? (i==0) : false,trainer,framecount,mapid,@viewport) + (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 += (showContinue && i==0) ? 112*2 : 24*2 + y += (show_continue && i==0) ? 112*2 : 24*2 end @sprites["cmdwindow"] = Window_CommandPokemon.new([]) @sprites["cmdwindow"].viewport = @viewport @@ -212,41 +212,52 @@ end class PokemonLoadScreen def initialize(scene) @scene = scene + if SaveData.exists? + @save_data = load_save_file(SaveData::FILE_PATH) + else + @save_data = {} + end end - def pbTryLoadFile(savefile) - trainer = nil - framecount = nil - game_system = nil - pokemonSystem = nil - mapid = nil - File.open(savefile) { |f| - trainer = Marshal.load(f) - framecount = Marshal.load(f) - game_system = Marshal.load(f) - pokemonSystem = Marshal.load(f) - mapid = Marshal.load(f) - } - raise "Corrupted file" if !trainer.is_a?(PlayerTrainer) - raise "Corrupted file" if !framecount.is_a?(Numeric) - raise "Corrupted file" if !game_system.is_a?(Game_System) - raise "Corrupted file" if !pokemonSystem.is_a?(PokemonSystem) - raise "Corrupted file" if !mapid.is_a?(Numeric) - return [trainer,framecount,game_system,pokemonSystem,mapid] + # @param file_path [String] file to load save data from + # @return [Hash] save data + def load_save_file(file_path) + save_data = SaveData.read_from_file(file_path) + + unless SaveData.valid?(save_data) + 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 + return {} + end + end + + return save_data + end + + # Called if all save data is invalid. + # Prompts the player to delete the save files. + def prompt_save_deletion + pbMessage(_INTL('The save file is corrupt, or is incompatible with this game.')) + exit unless pbConfirmMessageSerious( + _INTL('Do you want to delete the save file and start anew?') + ) + self.delete_save_data + $game_system = Game_System.new + $PokemonSystem = PokemonSystem.new end def pbStartDeleteScreen - savefile = RTP.getSaveFileName("Game.rxdata") @scene.pbStartDeleteScene @scene.pbStartScene2 - if safeExists?(savefile) + if SaveData.exists? if pbConfirmMessageSerious(_INTL("Delete all saved data?")) pbMessage(_INTL("Once data has been deleted, there is no way to recover it.\1")) if pbConfirmMessageSerious(_INTL("Delete the saved data anyway?")) pbMessage(_INTL("Deleting all data. Don't turn off the power.\\wtnp[0]")) - begin; File.delete(savefile); rescue; end - begin; File.delete(savefile+".bak"); rescue; end - pbMessage(_INTL("The save file was deleted.")) + self.delete_save_data end end else @@ -256,226 +267,82 @@ class PokemonLoadScreen $scene = pbCallTitle end + def delete_save_data + begin + SaveData.delete_file + pbMessage(_INTL('The saved data was deleted.')) + rescue SystemCallError + pbMessage(_INTL('All saved data could not be deleted.')) + end + end + def pbStartLoadScreen - $PokemonTemp = PokemonTemp.new - $game_temp = Game_Temp.new - $game_system = Game_System.new - $PokemonSystem = PokemonSystem.new if !$PokemonSystem - savefile = RTP.getSaveFileName("Game.rxdata") - mapfile = sprintf("Data/Map%03d.rxdata", $data_system.start_map_id) - if $data_system.start_map_id == 0 || !pbRgssExists?(mapfile) - pbMessage(_INTL("No starting position was set in the map editor.\1")) - pbMessage(_INTL("The game cannot continue.")) - @scene.pbEndScene - $scene = nil - return - end commands = [] - cmdContinue = -1 - cmdNewGame = -1 - cmdOption = -1 - cmdLanguage = -1 - cmdMysteryGift = -1 - cmdDebug = -1 - cmdQuit = -1 - if safeExists?(savefile) - trainer = nil - framecount = 0 - mapid = 0 - haveBackup = false - showContinue = false - begin - trainer, framecount, $game_system, $PokemonSystem, mapid = pbTryLoadFile(savefile) - showContinue = true - rescue - if safeExists?(savefile+".bak") - begin - trainer, framecount, $game_system, $PokemonSystem, mapid = pbTryLoadFile(savefile+".bak") - haveBackup = true - showContinue = true - rescue - end - end - if haveBackup - pbMessage(_INTL("The save file is corrupt. The previous save file will be loaded.")) - else - pbMessage(_INTL("The save file is corrupt, or is incompatible with this game.")) - if !pbConfirmMessageSerious(_INTL("Do you want to delete the save file and start anew?")) - $scene = nil - return - end - begin; File.delete(savefile); rescue; end - begin; File.delete(savefile+".bak"); rescue; end - $game_system = Game_System.new - $PokemonSystem = PokemonSystem.new if !$PokemonSystem - pbMessage(_INTL("The save file was deleted.")) - end + 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? + if show_continue + commands[cmd_continue = commands.length] = _INTL('Continue') + if @save_data[:player].mystery_gift_unlocked + commands[cmd_mystery_gift = commands.length] = _INTL('Mystery Gift') end - if showContinue - if !haveBackup - begin; File.delete(savefile+".bak"); rescue; end - end - end - commands[cmdContinue = commands.length] = _INTL("Continue") if showContinue - commands[cmdNewGame = commands.length] = _INTL("New Game") - commands[cmdMysteryGift = commands.length] = _INTL("Mystery Gift") if trainer.mystery_gift_unlocked - else - commands[cmdNewGame = commands.length] = _INTL("New Game") end - commands[cmdOption = commands.length] = _INTL("Options") - commands[cmdLanguage = commands.length] = _INTL("Language") if Settings::LANGUAGES.length>=2 - commands[cmdDebug = commands.length] = _INTL("Debug") if $DEBUG - commands[cmdQuit = commands.length] = _INTL("Quit Game") - @scene.pbStartScene(commands,showContinue,trainer,framecount,mapid) - @scene.pbSetParty(trainer) if showContinue + commands[cmd_new_game = commands.length] = _INTL('New Game') + commands[cmd_options = commands.length] = _INTL('Options') + commands[cmd_language = commands.length] = _INTL('Language') if Settings::LANGUAGES.length >= 2 + commands[cmd_debug = commands.length] = _INTL('Debug') if $DEBUG + commands[cmd_quit = commands.length] = _INTL('Quit Game') + 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 @scene.pbStartScene2 - pbLoadBattleAnimations loop do command = @scene.pbChoose(commands) - if cmdContinue>=0 && command==cmdContinue - unless safeExists?(savefile) - pbPlayBuzzerSE - next - end - pbPlayDecisionSE + pbPlayDecisionSE if command != cmd_quit + case command + when cmd_continue @scene.pbEndScene - metadata = nil - File.open(savefile) { |f| - Marshal.load(f) # Trainer already loaded - $Trainer = trainer - Graphics.frame_count = Marshal.load(f) - $game_system = Marshal.load(f) - Marshal.load(f) # PokemonSystem already loaded - Marshal.load(f) # Current map id no longer needed - $game_switches = Marshal.load(f) - $game_variables = Marshal.load(f) - $game_self_switches = Marshal.load(f) - $game_screen = Marshal.load(f) - $MapFactory = Marshal.load(f) - $game_map = $MapFactory.map - $game_player = Marshal.load(f) - $PokemonGlobal = Marshal.load(f) - metadata = Marshal.load(f) - $PokemonBag = Marshal.load(f) - $PokemonStorage = Marshal.load(f) - $SaveVersion = Marshal.load(f) unless f.eof? - magicNumberMatches = false - if $data_system.respond_to?("magic_number") - magicNumberMatches = ($game_system.magic_number==$data_system.magic_number) - else - magicNumberMatches = ($game_system.magic_number==$data_system.version_id) - end - if !magicNumberMatches || $PokemonGlobal.safesave - if pbMapInterpreterRunning? - pbMapInterpreter.setup(nil,0) - end - begin - $MapFactory.setup($game_map.map_id) # calls setMapChanged - rescue Errno::ENOENT - if $DEBUG - pbMessage(_INTL("Map {1} was not found.",$game_map.map_id)) - map = pbWarpToMap - if map - $MapFactory.setup(map[0]) - $game_player.moveto(map[1],map[2]) - else - $game_map = nil - $scene = nil - return - end - else - $game_map = nil - $scene = nil - pbMessage(_INTL("The map was not found. The game cannot continue.")) - end - end - $game_player.center($game_player.x, $game_player.y) - else - $MapFactory.setMapChanged($game_map.map_id) - end - } - if !$game_map.events # Map wasn't set up - $game_map = nil - $scene = nil - pbMessage(_INTL("The map is corrupt. The game cannot continue.")) - return - end - $PokemonMap = metadata - $PokemonEncounters = PokemonEncounters.new - $PokemonEncounters.setup($game_map.map_id) - pbAutoplayOnSave - $game_map.update - $PokemonMap.updateMap - $scene = Scene_Map.new + Game.load(@save_data) return - elsif cmdNewGame>=0 && command==cmdNewGame - pbPlayDecisionSE + when cmd_new_game @scene.pbEndScene - if $game_map && $game_map.events - for event in $game_map.events.values - event.clear_starting - end - end - $game_temp.common_event_id = 0 if $game_temp - $scene = Scene_Map.new - Graphics.frame_count = 0 - $game_system = Game_System.new - $game_switches = Game_Switches.new - $game_variables = Game_Variables.new - $game_self_switches = Game_SelfSwitches.new - $game_screen = Game_Screen.new - $game_player = Game_Player.new - $PokemonMap = PokemonMapMetadata.new - $PokemonGlobal = PokemonGlobalMetadata.new - $PokemonStorage = PokemonStorage.new - $PokemonEncounters = PokemonEncounters.new - $PokemonTemp.begunNewGame = true - $MapFactory = PokemonMapFactory.new($data_system.start_map_id) # calls setMapChanged - $game_player.moveto($data_system.start_x, $data_system.start_y) - $game_player.refresh - $game_map.autoplay - $game_map.update + Game.start_new return - elsif cmdMysteryGift>=0 && command==cmdMysteryGift - pbPlayDecisionSE - pbFadeOutIn { - trainer = pbDownloadMysteryGift(trainer) - } - elsif cmdOption>=0 && command==cmdOption - pbPlayDecisionSE - pbFadeOutIn { + when cmd_mystery_gift + pbFadeOutIn do + @save_data[:player] = pbDownloadMysteryGift(@save_data[:player]) + end + when cmd_options + pbFadeOutIn do scene = PokemonOption_Scene.new screen = PokemonOptionScreen.new(scene) screen.pbStartScreen(true) - } - elsif cmdLanguage>=0 && command==cmdLanguage - pbPlayDecisionSE + end + when cmd_language @scene.pbEndScene $PokemonSystem.language = pbChooseLanguage - pbLoadMessages("Data/"+Settings::LANGUAGES[$PokemonSystem.language][1]) - savedata = [] - if safeExists?(savefile) - File.open(savefile,"rb") { |f| - 16.times { savedata.push(Marshal.load(f)) } - } - savedata[3]=$PokemonSystem - begin - File.open(RTP.getSaveFileName("Game.rxdata"),"wb") { |f| - 16.times { |i| Marshal.dump(savedata[i],f) } - } - rescue - end + pbLoadMessages('Data/' + Settings::LANGUAGES[$PokemonSystem.language][1]) + if show_continue + @save_data[:pokemon_system] = $PokemonSystem + File.open(SaveData::FILE_PATH, 'wb') { |file| Marshal.dump(@save_data, file) } end $scene = pbCallTitle return - elsif cmdDebug>=0 && command==cmdDebug - pbPlayDecisionSE + when cmd_debug pbFadeOutIn { pbDebugMenu(false) } - elsif cmdQuit>=0 && command==cmdQuit + when cmd_quit pbPlayCloseMenuSE @scene.pbEndScene $scene = nil return + else + pbPlayBuzzerSE end end end diff --git a/Data/Scripts/017_UI/014_PScreen_Save.rb b/Data/Scripts/017_UI/014_PScreen_Save.rb index bf5727ccc..1642efd1d 100644 --- a/Data/Scripts/017_UI/014_PScreen_Save.rb +++ b/Data/Scripts/017_UI/014_PScreen_Save.rb @@ -1,60 +1,29 @@ -#=============================================================================== -# -#=============================================================================== -def pbSave(safesave=false) - begin - File.open(RTP.getSaveFileName("Game.rxdata"),"wb") { |f| - Marshal.dump($Trainer,f) - Marshal.dump(Graphics.frame_count,f) - if $data_system.respond_to?("magic_number") - $game_system.magic_number = $data_system.magic_number - else - $game_system.magic_number = $data_system.version_id - end - $game_system.save_count+=1 - Marshal.dump($game_system,f) - Marshal.dump($PokemonSystem,f) - Marshal.dump($game_map.map_id,f) - Marshal.dump($game_switches,f) - Marshal.dump($game_variables,f) - Marshal.dump($game_self_switches,f) - Marshal.dump($game_screen,f) - Marshal.dump($MapFactory,f) - Marshal.dump($game_player,f) - $PokemonGlobal.safesave=safesave - Marshal.dump($PokemonGlobal,f) - Marshal.dump($PokemonMap,f) - Marshal.dump($PokemonBag,f) - Marshal.dump($PokemonStorage,f) - Marshal.dump(Essentials::VERSION, f) - } - Graphics.frame_reset - rescue - return false - end - return true +# @deprecated Use {Game.save} instead. pbSave is slated to be removed in v20. +def pbSave(safesave = false) + Deprecation.warn_method('pbSave', 'Game.save', 'v20') + Game.save(safe: safesave) end def pbEmergencySave - oldscene=$scene - $scene=nil + oldscene = $scene + $scene = nil pbMessage(_INTL("The script is taking too long. The game will restart.")) return if !$Trainer - if safeExists?(RTP.getSaveFileName("Game.rxdata")) - File.open(RTP.getSaveFileName("Game.rxdata"), 'rb') { |r| - File.open(RTP.getSaveFileName("Game.rxdata.bak"), 'wb') { |w| + if SaveData.exists? + File.open(SaveData::FILE_PATH, 'rb') do |r| + File.open(SaveData::FILE_PATH + '.bak', 'wb') do |w| while s = r.read(4096) w.write s end - } - } + end + end end - if pbSave + 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 + $scene = oldscene end #=============================================================================== @@ -117,33 +86,31 @@ class PokemonSaveScreen end def pbSaveScreen - ret=false + ret = false @scene.pbStartScreen - if pbConfirmMessage(_INTL("Would you like to save the game?")) - if safeExists?(RTP.getSaveFileName("Game.rxdata")) - if $PokemonTemp.begunNewGame - pbMessage(_INTL("WARNING!")) - pbMessage(_INTL("There is a different game file that is already saved.")) - pbMessage(_INTL("If you save now, the other file's adventure, including items and Pokémon, will be entirely lost.")) - if !pbConfirmMessageSerious( - _INTL("Are you sure you want to save now and overwrite the other save file?")) - pbSEPlay("GUI save choice") - @scene.pbEndScreen - return false - end + if pbConfirmMessage(_INTL('Would you like to save the game?')) + if SaveData.exists? && $PokemonTemp.begunNewGame + pbMessage(_INTL('WARNING!')) + pbMessage(_INTL('There is a different game file that is already saved.')) + pbMessage(_INTL("If you save now, the other file's adventure, including items and Pokémon, will be entirely lost.")) + if !pbConfirmMessageSerious( + _INTL('Are you sure you want to save now and overwrite the other save file?')) + pbSEPlay('GUI save choice') + @scene.pbEndScreen + return false end end - $PokemonTemp.begunNewGame=false - pbSEPlay("GUI save choice") - if pbSave - pbMessage(_INTL("\\se[]{1} saved the game.\\me[GUI save game]\\wtnp[30]",$Trainer.name)) - ret=true + $PokemonTemp.begunNewGame = false + pbSEPlay('GUI save choice') + if Game.save + pbMessage(_INTL("\\se[]{1} saved the game.\\me[GUI save game]\\wtnp[30]", $Trainer.name)) + ret = true else pbMessage(_INTL("\\se[]Save failed.\\wtnp[30]")) - ret=false + ret = false end else - pbSEPlay("GUI save choice") + pbSEPlay('GUI save choice') end @scene.pbEndScreen return ret diff --git a/Data/Scripts/019_Other battles/003_PBattle_OrgBattle.rb b/Data/Scripts/019_Other battles/003_PBattle_OrgBattle.rb index 580478fe5..8adf6dd5c 100644 --- a/Data/Scripts/019_Other battles/003_PBattle_OrgBattle.rb +++ b/Data/Scripts/019_Other battles/003_PBattle_OrgBattle.rb @@ -355,7 +355,7 @@ class BattleChallengeData @start=[$game_map.map_id,$game_player.x,$game_player.y] @oldParty=$Trainer.party $Trainer.party=@party if @party - pbSave(true) + Game.save(safe: true) end def pbCancel @@ -369,9 +369,7 @@ class BattleChallengeData save=(@decision!=0) reset $game_map.need_refresh=true - if save - pbSave(true) - end + Game.save(safe: true) if save end def pbGoOn @@ -410,7 +408,7 @@ class BattleChallengeData $game_map.map_id=@start[0] $game_player.moveto2(@start[1],@start[2]) $game_player.direction=8 # facing up - pbSave(true) + Game.save(safe: true) $game_map.map_id=oldmapid $game_player.moveto2(oldx,oldy) $game_player.direction=olddirection diff --git a/Data/Scripts/020_System and utilities/002_PSystem_System.rb b/Data/Scripts/020_System and utilities/002_PSystem_System.rb index d81679421..0ea15d38e 100644 --- a/Data/Scripts/020_System and utilities/002_PSystem_System.rb +++ b/Data/Scripts/020_System and utilities/002_PSystem_System.rb @@ -16,44 +16,6 @@ def pbChooseLanguage return pbShowCommands(nil,commands) end - -def pbSetUpSystem - begin - trainer = nil - framecount = 0 - game_system = nil - pokemonSystem = nil - havedata = false - File.open(RTP.getSaveFileName("Game.rxdata")) { |f| - trainer = Marshal.load(f) - framecount = Marshal.load(f) - game_system = Marshal.load(f) - pokemonSystem = Marshal.load(f) - } - raise "Corrupted file" if !trainer.is_a?(PlayerTrainer) - raise "Corrupted file" if !framecount.is_a?(Numeric) - raise "Corrupted file" if !game_system.is_a?(Game_System) - raise "Corrupted file" if !pokemonSystem.is_a?(PokemonSystem) - havedata = true - rescue - game_system = Game_System.new - pokemonSystem = PokemonSystem.new - end - if $INEDITOR - pbSetResizeFactor(1.0) - else - $game_system = game_system - $PokemonSystem = pokemonSystem - pbSetResizeFactor([$PokemonSystem.screensize, 4].min) - end - # Load constants - GameData.load_all - if Settings::LANGUAGES.length>=2 - pokemonSystem.language = pbChooseLanguage if !havedata - pbLoadMessages("Data/"+Settings::LANGUAGES[pokemonSystem.language][1]) - end -end - def pbScreenCapture t = pbGetTimeNow filestart = t.strftime("[%Y-%m-%d] %H_%M_%S") diff --git a/Data/Scripts/020_System and utilities/006_Game.rb b/Data/Scripts/020_System and utilities/006_Game.rb new file mode 100644 index 000000000..8e164b8ae --- /dev/null +++ b/Data/Scripts/020_System and utilities/006_Game.rb @@ -0,0 +1,136 @@ +# The Game module contains methods for saving and loading the game. +module Game + # Initializes various global variables and loads the game data. + def self.initialize + $PokemonTemp = PokemonTemp.new + $game_temp = Game_Temp.new + $game_system = Game_System.new + $data_animations = load_data('Data/Animations.rxdata') + $data_tilesets = load_data('Data/Tilesets.rxdata') + $data_common_events = load_data('Data/CommonEvents.rxdata') + $data_system = load_data('Data/System.rxdata') + pbLoadBattleAnimations + GameData.load_all + + map_file = format('Data/Map%03d.rxdata', $data_system.start_map_id) + + if $data_system.start_map_id == 0 || !pbRgssExists?(map_file) + raise _INTL('No starting position was set in the map editor.') + end + end + + # Loads values from the save file and runs any necessary + # conversions on it. + def self.set_up_system + SaveData.move_old_windows_save if System.platform[/Windows/] + + if SaveData.exists? + save_data = SaveData.read_from_file(SaveData::FILE_PATH) + else + save_data = {} + end + + if !save_data.empty? && SaveData.run_conversions(save_data) + File.open(SaveData::FILE_PATH, 'wb') { |f| Marshal.dump(save_data, f) } + end + + if save_data.empty? + SaveData.initialize_bootup_values + else + SaveData.load_bootup_values(save_data) + end + + pbSetResizeFactor([$PokemonSystem.screensize, 4].min) + if Settings::LANGUAGES.length >= 2 + $PokemonSystem.language = pbChooseLanguage if save_data.empty? + pbLoadMessages('Data/' + Settings::LANGUAGES[$PokemonSystem.language][1]) + 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(save_file = SaveData::FILE_PATH, safe: false) + validate save_file => String, safe => [TrueClass, FalseClass] + + $PokemonGlobal.safesave = safe + $game_system.save_count += 1 + $game_system.magic_number = $data_system.magic_number + begin + SaveData.save_to_file(save_file) + Graphics.frame_reset + rescue IOError, SystemCallError + $game_system.save_count -= 1 + return false + end + + return true + end + + # Loads the game from the given save data and starts the map scene. + # @param save_data [Hash] hash containing the save data + # @raise [SaveData::InvalidValueError] if an invalid value is being loaded + def self.load(save_data) + validate save_data => Hash + + SaveData.load_all_values(save_data) + + self.load_map + + pbAutoplayOnSave + $game_map.update + $PokemonMap.updateMap + $scene = Scene_Map.new + end + + # Loads and validates the map. Called when loading a saved game. + def self.load_map + $game_map = $MapFactory.map + magic_number_matches = ($game_system.magic_number == $data_system.magic_number) + if !magic_number_matches || $PokemonGlobal.safesave + if pbMapInterpreterRunning? + pbMapInterpreter.setup(nil, 0) + end + begin + $MapFactory.setup($game_map.map_id) + rescue Errno::ENOENT + if $DEBUG + pbMessage(_INTL('Map {1} was not found.', $game_map.map_id)) + map = pbWarpToMap + exit unless map + $MapFactory.setup(map[0]) + $game_player.moveto(map[1], map[2]) + else + raise _INTL('The map was not found. The game cannot continue.') + end + end + $game_player.center($game_player.x, $game_player.y) + else + $MapFactory.setMapChanged($game_map.map_id) + end + if $game_map.events.nil? + raise _INTL('The map is corrupt. The game cannot continue.') + end + $PokemonEncounters = PokemonEncounters.new + $PokemonEncounters.setup($game_map.map_id) + end + + # Called when starting a new game. Initializes global variables + # and transfers the player into the map scene. + def self.start_new + 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 + $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 + $game_map.autoplay + $game_map.update + end +end diff --git a/Data/Scripts/999_Main/999_Main.rb b/Data/Scripts/999_Main/999_Main.rb index edd9b5ef6..5d01980d0 100644 --- a/Data/Scripts/999_Main/999_Main.rb +++ b/Data/Scripts/999_Main/999_Main.rb @@ -28,12 +28,8 @@ end def mainFunctionDebug begin Compiler.main - pbSetUpSystem - $data_animations = load_data("Data/Animations.rxdata") - $data_tilesets = load_data("Data/Tilesets.rxdata") - $data_common_events = load_data("Data/CommonEvents.rxdata") - $data_system = load_data("Data/System.rxdata") - $game_system = Game_System.new + Game.initialize + Game.set_up_system Graphics.update Graphics.freeze $scene = pbCallTitle