mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-06 06:01:46 +00:00
270 lines
9.0 KiB
Ruby
270 lines
9.0 KiB
Ruby
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
|
|
|
|
# @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 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
|
|
|
|
# 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
|
|
|
|
# @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
|
|
|
|
# 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 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
|
|
|
|
# Marks value as unloaded.
|
|
def mark_as_unloaded
|
|
@loaded = false
|
|
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
|
|
|
|
# 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
|
|
|
|
# @!group Configuration
|
|
|
|
# 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
|
|
|
|
# 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
|
|
|
|
# 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
|
|
|
|
# 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, sets the value to be loaded during bootup.
|
|
# @see SaveData.register
|
|
def load_in_bootup
|
|
@load_in_bootup = true
|
|
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
|
|
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
|
|
|
|
# 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 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
|
|
|
|
# Marks all values that aren't loaded on bootup as unloaded.
|
|
def self.mark_values_as_unloaded
|
|
@values.each do |value|
|
|
value.mark_as_unloaded unless value.load_in_bootup?
|
|
end
|
|
end
|
|
|
|
# Loads each value from the given save data that has
|
|
# been set to be loaded during bootup. Done when a save file exists.
|
|
# @param save_data [Hash] save data to load
|
|
# @raise [InvalidValueError] if an invalid value is being loaded
|
|
def self.load_bootup_values(save_data)
|
|
validate save_data => Hash
|
|
load_values(save_data) { |value| !value.loaded? && value.load_in_bootup? }
|
|
end
|
|
|
|
# Goes through each value with {Value#load_in_bootup} enabled and loads their
|
|
# new game value, if one is defined. Done when no save file exists.
|
|
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
|
|
|
|
# Loads each {Value}'s new game value, if one is defined. Done when starting a
|
|
# new game.
|
|
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
|
|
|
|
# @return [Hash{Symbol => Object}] a hash representation of the save data
|
|
# @raise [InvalidValueError] if an invalid value is being saved
|
|
def self.compile_save_hash
|
|
save_data = {}
|
|
@values.each { |value| save_data[value.id] = value.save }
|
|
return save_data
|
|
end
|
|
end
|