Saving per-player data on a dedicated server

Does anybody have experience saving per-player data on a dedicated server? I have a SCS hook to attach my custom save data component to AFGPlayerState on construction and it all works perfectly for local gameplay. When I join my dedicated server, none of my player-specific data is restored. I have tried taking a local save file (that I have verified works locally) and loaded it with my dedicated server, but the data does not get restored. I have tried setting up the game in a known state ON the dedicated server and waiting for autosave/leaving the game to force a save (I verified it does), but on rejoin the data does not get restored, nor on server restart and rejoin. From parsing the save file, I can see that my custom component is referenced in the save file but NONE of my custom data is in the dedicated server save file (my custom properties are just stored as arrays of uint8s - nothing fancy). Through logging, I can see that the IFGSaveInterface functions I have overridden are invoked by the dedicated server. IFGSaveInterface::PreSaveGame_Implementation IS CALLED when I expect it to be and I can see that is has the correct data in memory, but that data doesn't get saved in the save file. I notice IFGSaveInterface::PostLoadGame_Implementation is called on server startup long before a player has even joined and acts like the player data is empty, which makes sense given that the save file data is empty, but I don't know what it thinks it's doing since there would/should(?) be no AFGPlayerState with no players connected, I assume?
Solution:
Ok, I can confirm that the issue was fundamentally about network serialization. Once I expanded the enum, everything "just worked" the way I expected and I no longer need a SCS hook for custom per-player save data.
Jump to solution
8 Replies
Archengius
Archengius6d ago
Player states are persistent and are kept indefinitely even if the player is not online There is a function that copies data from one player state to another that you should hook to also carry over your custom data This might be the reason why it gets lost
Epp
EppOP6d ago
This it? In FGPlayerState.h
virtual void CopyProperties( APlayerState* playerState ) override;
virtual void CopyProperties( APlayerState* playerState ) override;
Robb
Robb6d ago
using a hooked component on player state should™️ be the way, yeah. did it work in host and play with 2 clients? afaik it was working with Free Samples in the U8 but I haven't updated that to 1.0 yet to test with
Archengius
Archengius5d ago
Yes
Epp
EppOP5d ago
I haven't fully confirmed but I'm pretty sure this is actually an obscure PEBKAC issue. When CopyProperties didn't work, I added tracing to see what was actually being sent and received by the server and they didn't match. I did some engine investigation: https://discord.com/channels/555424930502541343/862002356626128907/1317632645395386379 Because I'm sending enum values outside of the known range of the enum, the network serialization truncates the values. And my saving code isn't saving anything because it doesn't recognize those values as things it needs to save. I think I have at least two options: 1) Hook engine code to tell it to use a full byte for this specific enum type 2) Expand the Enum with my custom values like what Crash Site Beacons does. (1) sounds like a can of worms I really don't want to open so I will probably go with (2). I've been resistant to it for fear of conflicting with mods like Crash Site Beacons but, now that I know a lot more about how UE serializes things (enum names to save files and minimal bits over the network), I think the only way it could break is if the client and server ever initialize two mods that expand the SAME enum in DIFFERENT orders. It looks like load order should be deterministic for a given mod set so even that shouldn't happen. I might STILL need to CopyProperties and will follow up here either way when I figure that out.
Archengius
Archengius5d ago
if you want to use a new enum value, actually just add it to the UENUM you can definitely do that from the code in runtime you should not be using untracked enum values but what UENUM are you specifically being limited by? representation type? because i really do not think you should be hacking around it i would rather use RT_Other and just wait for vanilla to add support to that because representation type is a problem for extensibility and i really want us to get rid of it because there is no reason to have it
Epp
EppOP5d ago
Yes, it's ERepresentationType and I've already worked (almost) ALL the way around it. 🙂 I don't think RT_Other solves my problem because I'm not actually adding new things; I'm just altering the behavior of the existing resources in the map, mapmenu, and compass. On the map screen, in particular, often all I have to go on is the representation type, so it was easiest to extend that rather than try to track/pipe objects into brand new places. I'm glad to hear you want to get rid of it, though. I assume you would just let each UFGActorRepresentation directly return things like its map category instead of inferring it from the representation type? That would be amazing and would have made my mod much easier. Also, I was using untracked enums to try the simplest approach - gotta see how it breaks to know how to fix it. I hoped it would just pass values through like in C# and raw C++ and it mostly did until serialization. For sure, I was caught off-guard by the saving and the network serialization behavior but I feel like I understand the rationale for it now and that feels good.
Solution
Epp
Epp5d ago
Ok, I can confirm that the issue was fundamentally about network serialization. Once I expanded the enum, everything "just worked" the way I expected and I no longer need a SCS hook for custom per-player save data.
Want results from more Discord servers?
Add your server