Actually, yesIn general? No.
"This script is NOT foolproof," <-- I.e. does not work in general.This script attempts to fix many issues that were caused by loading an old save when new scripts were installed. This script is NOT foolproof, but it will prevent a large number of errors from occurring.
I'm under the impression that you're just copying over values that exist in a new object, but do not exist in the save file. If it already exists in the save file, then there is no need to copy it over.When I first released the script, it worked seemingly perfectly. New variables started popping up in the old files. It was great for what it was designed for, a general fix. However, issues soon started to arise. Turns out that the original script destroys any equips the player has, probably resets their skills and inventory. To be honest, I still haven't fixed these issues, they STILL happen. Its clear to me that the original method I was using will jot do the job we need.
It creates a new object and copies values from the old one to the new one. It has some issues, especially with things like followers. They're stored, essentially, in an array in the game player class. When we copy the old followers over, they're still trying to follow the old Game_Player object that exists in that array, breaking them. They'll only move onto the spot that the player was on when the game was saved since we no longer control that player, but it is still referenced in that array.I'm under the impression that you're just copying over values that exist in a new object, but do not exist in the save file. If it already exists in the save file, then there is no need to copy it over.
Unless that's the purpose of the script: re-initializing the object rather than just initializing missing variables.
That's something new to me.It creates a new object and copies values from the old one to the new one. It has some issues, especially with things like followers. They're stored, essentially, in an array in the game player class. When we copy the old followers over, they're still trying to follow the old Game_Player object that exists in that array, breaking them. They'll only move onto the spot that the player was on when the game was saved since we no longer control that player, but it is still referenced in that array.
[MOD]I had this lying around (paste above Main and below all other custom scripts):
#==============================================================================# ** MakerSystems#------------------------------------------------------------------------------# #==============================================================================module MakerSystems #============================================================================ # ** SaveFileDoctor #---------------------------------------------------------------------------- # #============================================================================ module SaveFileDoctor #---------------------------------------------------------------------- # * Heal. [NEW] #---------------------------------------------------------------------- def self.heal(contents) # Will keep track of repaired objects to avoid a stack overflow. @ms_save_file_doctor_ids = [] # Goes through each object in contents hash. contents.each_value do |patient| # Updated version of this Object. current = patient.class.new # Fixes this (loaded) Object with the help of its updated version. fix_object(patient, current) # Starts the healing process for each instance variable. child_health(object_data(patient), object_data(current)) end # No need to keep the objects ids in memory. @ms_save_file_doctor_ids = nil end #---------------------------------------------------------------------- # * Enumerable Child Processing. [NEW] #---------------------------------------------------------------------- def self.enum_child(patient, current) # Iterates loaded version and updated version as a sequence. patient.zip(current).each do |p_value, c_value| # If Enumerable, there might be instanceable Objects inside it. enum_child(p_value.to_a, c_value.to_a) if Enumerable === p_value # Ignore if it can't be instanced. next unless p_value.class.respond_to?new) # Stop if this Object was already healed to avoid a stack overflow. return if @ms_save_file_doctor_ids.include?(p_value.__id__) # Saves current Object id. @ms_save_file_doctor_ids << p_value.__id__ # Fixes this Object with the help of its updated version. fix_object(p_value, c_value) # Healing process for each instance variable of this Object. child_health(object_data(p_value), object_data(c_value)) end end #---------------------------------------------------------------------- # * Object Data. [NEW] #---------------------------------------------------------------------- def self.object_data(object) # Returns a hash with each instance variable name and value. Hash[ object.instance_variables.map do |name| [name, object.instance_variable_get(name)] end ] end #---------------------------------------------------------------------- # * Fix Object. [NEW] #---------------------------------------------------------------------- def self.fix_object(patient, current) # Adds new data to loaded Object's data target. data = object_data(current).merge(object_data(patient)) # Uses the correct data target to set each instance variable. data.each { |name, value| patient.instance_variable_set(name, value) } end #---------------------------------------------------------------------- # * Child Health. [NEW] #---------------------------------------------------------------------- def self.child_health(patient, current) # Goes through each instance variable. patient.each do |name, value| # If Enumerable, there might be instanceable Objects inside it. enum_child(value.to_a, current[name].to_a) if Enumerable === value # Ignore if it can't be instanced. next unless value.class.respond_to?
new) # Stop if this Object was already healed to avoid a stack overflow. return if @ms_save_file_doctor_ids.include?(value.__id__) # Saves current Object id. @ms_save_file_doctor_ids << value.__id__ # Fixes this Object with the help of its updated version. fix_object(value, current[name]) # Healing process for each instance variable of this Object. child_health(object_data(value), object_data(current[name])) end end end end#==============================================================================# ** DataManager#------------------------------------------------------------------------------# This module manages the database and game objects. Almost all of the # global variables used by the game are initialized by this module.#==============================================================================module DataManager class << self #------------------------------------------------------------------------ # * Alias Extract Save Contents. [NEW] #------------------------------------------------------------------------ alias_method
ms_save_file_doctor_original_extract_save_contents, :extract_save_contents) #------------------------------------------------------------------------ # * Extract Save Contents. [MOD] #------------------------------------------------------------------------ def extract_save_contents(contents) # Healthcare. Object repairing is handled at a local level. MakerSystems::SaveFileDoctor.heal(contents) # Proceeds with the original method, using repaired contents. ms_save_file_doctor_original_extract_save_contents(contents) end end endIt should fix the vast majority of errors caused by adding a script and attempting to load a savefile created before such addition.
Save File Doctor has no known bugs.
Will make a proper topic later.
[/MOD]
The problem usually stems from a lot of things, but in general it's the assumption that it is safe for required data to be initialized at certain points in time (eg: on start up)Isn't the problem usually that people save a serialized Hash directly, instead of making a wrapper / dedicated object for it instead? For example, even though the list of creatures in my game is stored in a Hash, I use a Creature object to access the data for each creature. I then access the creatures Hash only by using the methods on the Creature object, and thus if I change things later in a patch (like the data structure of the creatures Hash), I can build in backwards support into the Creature object's access methods.
This is exactly why my script currently does not work. It didn't go into hashes and arrays and such to rebuild everything, it rebuilt the $game_blarg objects that were stored in the save file hash. If we look at what Galenmereth is saying,However, solutions such as the copying data over from old data to new data would cover all cases. The question then becomes whether the copy is done correctly (and completely).
If some objects have not been updated properly, then the copy likely wasn't done correctly.
Got a hash? You need to examine every key-value pair.
Got an array? You need to examine every element.
Custom objects? You'll have to figure out how to examine them in the specified way. Those tend to be composed of a bunch of standard objects, so a little recursion does the trick. Most of the time. Try to copy a Color or a Tone or a Table. Instance variables are not going to help you. Solution? Please don't save them if you don't have to, or save them in such a way that you don't rely on other scripts knowing how to load them.
It is possible to write a script to cover standard objects and objects provided in the library, but if someone comes up with some perverse object that simply doesn't follow any of the rules...well, need to add a new case for that. Hopefully the copy script is written flexibly enough for that.
my script would completely break if there were mandatory changes to the Creature class. Sure, the hash would be brought over with all the creature objects, but the creature objects were not remade, therefore they don't have the new data they need. A proper fix needs to recursively check, which Save File Doctor seems to do properly. Granted I didn't do in-depth testing, it did fix the lack of a required instance variable, and it did fix followers properly.Isn't the problem usually that people save a serialized Hash directly, instead of making a wrapper / dedicated object for it instead? For example, even though the list of creatures in my game is stored in a Hash, I use a Creature object to access the data for each creature. I then access the creatures Hash only by using the methods on the Creature object, and thus if I change things later in a patch (like the data structure of the creatures Hash), I can build in backwards support into the Creature object's access methods.
Data from a specific save version MAY become an issue, but the only reason I can think of is that patching methods have changed between different versions.All you need is actually just checking whether a variable exists or not in your code, and have fallbacks for nil cases. How you implement that is the question. But it's usually bad to let a nil value break your code; there should always be fallbacks. If you have this all throughout your classes and modules, you can usually alleviate most of these issues. But like Zeriab mentioned, there might be a time where you do need to update old data from a specific save version, so keeping track of that will certainly make your job a lot easier.