The plug-in cannot determine which instance should be restored.
You have to, besides using the “Procedural” interface, implement a guid property called “SGUID” on actor class and give each instance an unique value to the SGUID property at spawn time.
The level 3 on demo project uses actors spawned at runtime as example.
If guid values are correct then it’s a matter that, when array is restored, actors with corresponding SGUID didn’t finished spawning yet so the array do not restore references to them.
If the case is this, it’s possible to call “Load Actor” again from the slot instance once everything finished spawning.
Ah gotcha, calling LoadActor on the character after LoadGameWorld seemed to have worked. Certainly there’s a way to write the deserialization logic to wait until dependent object references are spawned before setting the property values right? Unless of course the re-spawning of procedural actors and the deserializing of properties are happening on separate threads. But could be nice to have a bool property on the async load nodes called bWaitForDependants or something. Just feels bad to have to explicitly reload specific actors that have properties that depend on procedural actors spawning.
To avoid this data race, some people uncheck “Save Game” flag from the array of actors, then they use the Actor itself to add self to the array (getting ref to array from somewhere), from the Procedural Interface’s “On Finish Respawn” event; so the array is repopulated without extra costs and without unwanted delays:
So I’m on to my next problem. I’ve done what you’ve said above, but what’s the best practice now for knowing which player to restore the references too? I was assuming I could just check the Instigator or Owner on the procedural actor (after OnFinishRespawn and LoadActor), but both are null. Are the Owner and Instigator not restored and if not how should I go about handling this? I could probably just set another SaveGame property on the procedural actor containing the owning player’s UniqueNetId, but figured I’d ask before going down that route. Thanks for all your help btw, really cool plugin!
FUniqueNetIdRepl structs don’t seem to serialize/deserialize properly (always returns INVALID on load)
FDateTime structs don’t seem to serialize/deserialize properly
I was also wondering how I should go about loading a player who joins after a load has been performed in a P2P co-op game where they can technically join at any point but still have saved state to restore? My initial thoughts were to just call LoadActor on the PlayerController, PlayerState, and Character in their respective BeginPlays. Would that work or is there a different/more preferable approach?
Okay so it looks like it should work just fine with multiplayer since you use the UniqueId to make the object/actor/component ID’s. I think in most of your functions that explicitly save the player’s data (PlayerController, HUD, PlayerState, and Character) you could check if the local user has authority and if so loop through all the PlayerControllers, otherwise do what your doing now and just serialize/deserialize the local player. Do you think you could make the Savior3 object constructor public so I can subclass it as well?
Better yet, if you provided a SavePlayer/LoadPlayer node that you could pass in a PlayerController (important since you hard code only saving the local player) and it would serialize/deserialize its’ respective HUD, PlayerState and Character. That should be simple enough and provide your users with multiplayer support.
Added “Save/Load Player State” nodes;
They do the same “Save/Load Game Mode” node does, except it doesn’t save the Game Mode blueprint neither write to file.
You can append to existing slot data +/- like this:
Beautiful! That should really help enable multiplayer support for your plugin. Thanks for adding that!
So I have another thought experiment for you. So I’m trying to build a P2P co-op game where the host is the only one who can save the state of the world and all the players. The issue I’m running into now is that I want to allow the host to play a saved game even when some of the players who were in the original save aren’t present. However with the way things currently work, when the host saves over that slot, all the other players saved state who are not currently in the game gets clobbered. Can you think of anyway to preserve this player specific data when they aren’t currently present during a slot overwrite?
You could save slots on Client side. And let Clients load it back from their own file. Only save world from server, but on Client side call “Save Game Mode”.
If that’s not wanted then you want a database which is not the case for a save file; in a database the records are individual rows, not a binary blob and it doesn’t matter who is writing data, other players records will still be there untouched.
Or you can do like above screenshot, always “Read Slot from File” before saving anything. Old data will always remain.
So the first one won’t work because all the properties of the actors I’m saving are replicated and thus cannot be set by the client (hence why I need all the saved state on the server).
The second one would work, however I’m still quite interested in fully utilizing your plugin to handle saved state.
The third works after some testing, however I’m concerned about how the system handles destroyed actors (via your Destroy bool property) with this method. Correct me if I’m wrong, but actors that are marked for destroy through your system will never have their Savior record removed. So for a game that has lots of destructables that at one point have a Savior record, they will forever exist on the slot if I always ReadSlotFromFile prior to saving which unnecessarily bloats the save file size.
I think what you’re after is using a dedicated slot file for “Save World” which keeps track of actors spawned at runtime, but then using separate slots for “Player Profiles”.
If I remember correctly, a long time ago, Savior 1 had nodes to “Save Player to Slot” that would create a separate slot for target player ID and save only that player data to that slot created after player’s name.
For whatever reason nobody was actually using that (separate files was an annoyance), so I didn’t feel like carrying it over to new plugin version.
Hmm yea having additional slots per player does sound annoying. In a perfect world, I can somehow choose which records to preserve and which to overwrite when saving. Right now I can either overwrite entirely and lose any data for players who aren’t currently in the game, or continue to append data to the same slot and keep unnecessary records for deleted actors forever.
This simplest thing I can think of is to create a AddSlotData node. That way I could extract the records I want to preserve via the GetSlotData node, perform a full overwrite of the slot, then manually add back the records I want to keep. It’s crude, but it would allow me to do what I need it to do.
A cleaner approach might be you adding a “preserve” tag and a node called FilterPreservedData that would essentially purge any records from a slot that don’t contain the “preserve” tag. That way I could ReadSlotFromFile -> FilterPreservedData -> SaveGameWorld. This only works if you save the tags in the records (not sure haven’t used them yet), otherwise you would need to use a SaveGame bool property called “Preserve” instead like you do with the “Destroyed” property.
Also let me know if I’m annoying you with these feature requests. I’m in the process of building a game with a small team and am willing to work with you to get your plugin to support multiplayer on some capacity. If not, I’ll likely have to roll my own solution.
I cannot change signature of record struct anymore, that would kill existing projects save files.
“Add Record” seems reasonable to do, it’s what “Save Actor” node does, except in this case you need it for actors that don’t exist so you want to copy directly from old records.
Cool that should work, you will also need to remove the BlueprintInternalUseOnly meta specifier on the FSaviorRecord struct if you want to allow people to use them in blueprints.
If you want to selectively copy records from one slot to another, that won’t be of any use because none of the fields in a record are editable by blueprints (nothing will be visible if you split the struct pin);
They are meant to be nothing more than in / out type passed by value.
I need instead to add helper nodes such as “Find Record for GUID” and etc.