Save data rewrite & Game module (#91)

This commit is contained in:
Joni Savolainen
2021-02-28 23:26:16 +02:00
committed by GitHub
parent a112a21a87
commit 9c2b6e943b
15 changed files with 1038 additions and 342 deletions

View File

@@ -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).

View File

@@ -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<Class>, 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."

View File

@@ -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

View File

@@ -0,0 +1,264 @@
module SaveData
# Contains Value objects for each save element.
# Populated during runtime by SaveData.register calls.
# @type [Array<Value>]
@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

View File

@@ -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

View File

@@ -0,0 +1,218 @@
module SaveData
# Contains Conversion objects for each defined conversion:
# {
# :essentials => {
# '19' => [<Conversion>, ...],
# '19.1' => [<Conversion>, ...],
# ...
# },
# :game => {
# '1.1.0' => [<Conversion>, ...],
# '1.2.0' => [<Conversion>, ...],
# ...
# }
# }
# 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<Conversion>] 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

View File

@@ -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

View File

@@ -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 }

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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