SCS hooked component on PlayerState no longer saving correctly

seems like FreeSamples is broken now for dedicated server clients, not certain exactly when this happened, guessing 1.0? I'm saving data via a component added via SCS hooks on PlayerState. and I can confirm it's saving something, but it seems like after my player disconnects from the server, a new blank data component spawns for them and overwrites their old one before the save actually takes place, see logs in below message
No description
38 Replies
Robb
RobbOP•3w ago
this log is from right before the player leaves
Robb
RobbOP•3w ago
this log is from when the save loads:
Rex
Rex•3w ago
What is the owner of the rogue component?
Robb
RobbOP•3w ago
Robb
RobbOP•3w ago
my parent is messages display the owner which is a player state object with a different id#, so I wonder if a whole new player state is getting spawned? @Rex [they/them] if I recall correctly you set up Flashlight Settings also does saving on player state without this issue?
xXdrewbaccaXx
xXdrewbaccaXx•3w ago
grammar 👮
No description
Robb
RobbOP•3w ago
pushed some extra logging if you decide to clone for comparison, here is saving in singleplayer:
Robb
RobbOP•3w ago
Robb
RobbOP•3w ago
and the new player state creation doesn't happen during singleplayer world exit here is loading in singleplayer:
Robb
RobbOP•3w ago
xXdrewbaccaXx
xXdrewbaccaXx•3w ago
I don't know if this will be helpful but I looked at this in the first log [980]LogNet: - Result=ControlChannelClose, ErrorContext="ControlChannelClose", and google-fu'd it and came up with this QA post from Sept 10th with a nearly identical log and similar scenario. Apparently an issue when someone connected to the server via localhost disconnects? https://questions.satisfactorygame.com/post/66e0b26d772a987f4a8aa1d8
Robb
RobbOP•3w ago
hmmm, it's true that I am connecting on localhost, but I'm not sure it would make a difference here. I can test on my unraid server but it will be annoyingly slow sadly
xXdrewbaccaXx
xXdrewbaccaXx•3w ago
I couldn't remember for sure if that's how you tested but it tripped a bell when I saw it, and it was a QA post
Rex
Rex•3w ago
I don't think I've checked that because I only need to persist stuff while the player is connected
Robb
RobbOP•3w ago
can confirm it also happens on my unraid (not localhost) might be worth adding a per-player saved value to examplemod as a demo and to further test this... the spawning of new components after player leave is happening for examplemod as well, it's not just something in freesamples pushed to the examplemod-per-player-data branch @Archengius do you have any insights as to what could be causing this? if I recall correctly, SCS hooked component on player state was your suggested method for storing per-player saved data. small scale code to reproduce it is on examplemod-per-player-data branch, if the log messages and description above are not enough detail
Archengius
Archengius•3w ago
Have you hooked OverridePlayerState to copy your data from old player state to a new one?
Robb
RobbOP•3w ago
Nope, didn't know that was a step that had to happen, and discord search says your message here is the only mention of it. I can look into it this evening, but it sounds like that will have to be hooked in C++... Not ideal, that means we can't have it in examplemod until we have a hybrid example mod
Rex
Rex•3w ago
Hmmm, would it make sense to provide something (delegate?) in the FG player state for BPs to work with?
Archengius
Archengius•3w ago
Yes
Robb
RobbOP•2w ago
it looks like OverridePlayerState is on UE's GameMode and FG does not override it anywhere?
/** Override PC's PlayerState with the values in OldPlayerState as part of the inactive player handling */
ENGINE_API virtual void OverridePlayerState(APlayerController* PC, APlayerState* OldPlayerState);
/** Override PC's PlayerState with the values in OldPlayerState as part of the inactive player handling */
ENGINE_API virtual void OverridePlayerState(APlayerController* PC, APlayerState* OldPlayerState);
so the task here would be hook this function (on BP_GameMode, in c++ because the function is implemented in c++ and not blueprint as far as I can tell), and then when it's called, get the component on the passed old player state and copy its data to the component being used on the player state of the passed player controller?
Archengius
Archengius•2w ago
function might have a different name it's on AFGPlayerState might be called CopyPlayerState
Robb
RobbOP•2w ago
?
No description
Archengius
Archengius•2w ago
here we go yeah it's CopyProperties
Robb
RobbOP•2w ago
looks like bp has an event, not sure of Bind on BP Function can do events, I might be able to look into that this evening
No description
Robb
RobbOP•2w ago
ah nevermind, Bind on BPFunction doesn't get function arguments, still has to be c++
Robb
RobbOP•2w ago
eh, bp hooking it succeeds but the hook never seems to fire anyways
No description
No description
Robb
RobbOP•2w ago
it sounds like it might be OverrideWith/ReceiveOverrideWith? and the fact that the action function name is ReceiveCopyProperties might be why my bp hook wasn't firing...
No description
Robb
RobbOP•2w ago
[2025.02.07-17.32.53:181][ 0]LogFreeSamples: Verbose: [/FreeSamples/RootInstance_FreeSamples]: qweqweqjwe binding
[2025.02.07-17.32.53:189][ 0]LogFreeSamples: Verbose: [/FreeSamples/RootInstance_FreeSamples]: qweqweqjwe done binding 1
[2025.02.07-17.32.53:189][ 0]LogSatisfactoryModLoader: Error: Failed to bind on Blueprint Function 'ReceiveOverrideWith' of class '/Game/FactoryGame/Character/Player/BP_PlayerState.BP_PlayerState_C': Function is native
[2025.02.07-17.32.53:189][ 0]LogFreeSamples: Verbose: [/FreeSamples/RootInstance_FreeSamples]: qweqweqjwe done binding 2
[2025.02.07-17.32.53:181][ 0]LogFreeSamples: Verbose: [/FreeSamples/RootInstance_FreeSamples]: qweqweqjwe binding
[2025.02.07-17.32.53:189][ 0]LogFreeSamples: Verbose: [/FreeSamples/RootInstance_FreeSamples]: qweqweqjwe done binding 1
[2025.02.07-17.32.53:189][ 0]LogSatisfactoryModLoader: Error: Failed to bind on Blueprint Function 'ReceiveOverrideWith' of class '/Game/FactoryGame/Character/Player/BP_PlayerState.BP_PlayerState_C': Function is native
[2025.02.07-17.32.53:189][ 0]LogFreeSamples: Verbose: [/FreeSamples/RootInstance_FreeSamples]: qweqweqjwe done binding 2
[2025.02.07-17.35.19:386][202]LogExampleMod: Verbose: [/ExampleMod/Characters/Player/ExampleModPlayerDataComponent]: [BP_PlayerState_C_2147479917.ExampleModDataComponent with owner: BP_PlayerState_C_2147479917] Begin Play
[2025.02.07-17.35.19:386][202]LogExampleMod: Verbose: [/ExampleMod/Characters/Player/ExampleModPlayerDataComponent]: Has authority - this actor component is on the server side
[2025.02.07-17.35.19:386][202]LogFreeSamples: Verbose: [/FreeSamples/RootInstance_FreeSamples]: qweqweqjwe ReceiveCopyProperties BP_PlayerState_C_2147480303
[2025.02.07-17.35.19:386][202]LogFreeSamples: Verbose: [/FreeSamples/RootInstance_FreeSamples]: has component
[2025.02.07-17.35.19:386][202]LogExampleMod: Verbose: [/ExampleMod/Characters/Player/ExampleModPlayerDataComponent]: [BP_PlayerState_C_2147479917.ExampleModDataComponent with owner: BP_PlayerState_C_2147479917] Begin Play
[2025.02.07-17.35.19:386][202]LogExampleMod: Verbose: [/ExampleMod/Characters/Player/ExampleModPlayerDataComponent]: Has authority - this actor component is on the server side
[2025.02.07-17.35.19:386][202]LogFreeSamples: Verbose: [/FreeSamples/RootInstance_FreeSamples]: qweqweqjwe ReceiveCopyProperties BP_PlayerState_C_2147480303
[2025.02.07-17.35.19:386][202]LogFreeSamples: Verbose: [/FreeSamples/RootInstance_FreeSamples]: has component
maybe I'll need both
No description
Rex
Rex•2w ago
I imagine a long-term solution would be to provide a delegate or something in AFGPlayerState and call it from CopyProperties Looks like there's a pattern to call both C++ and BP implementations
void APlayerState::DispatchOverrideWith(APlayerState* PlayerState)
{
OverrideWith(PlayerState);
ReceiveOverrideWith(PlayerState);
}

void APlayerState::DispatchCopyProperties(APlayerState* PlayerState)
{
CopyProperties(PlayerState);
ReceiveCopyProperties(PlayerState);
}
void APlayerState::DispatchOverrideWith(APlayerState* PlayerState)
{
OverrideWith(PlayerState);
ReceiveOverrideWith(PlayerState);
}

void APlayerState::DispatchCopyProperties(APlayerState* PlayerState)
{
CopyProperties(PlayerState);
ReceiveCopyProperties(PlayerState);
}
Okay, cursed idea time: get player state components by class/interface, and for each of them call their CopyProperties / OverrideWith functions or something
Robb
RobbOP•2w ago
I would like to get this working before the long term solution comes out in 1.1 assuming it doesn't drive me insane although it looks like it may drive me insane not sure how to hook a protected function in engine code (APlayerState) since access transformers can't affect engine stuff I can hook AFGPlayerState::CopyProperties since it's in fg code, but not OverrideWith, which sounds like the one I actually care about here's what I have thus far (fails to build because can't subscribe APlayerState)
#include "FreeSamples.h"
#include "Data/FreeSamplePlayerDataComponent.h"
#include "Patching/NativeHookManager.h"
#include "FGPlayerState.h"
//#include "Engine/Classes/GameFramework/PlayerState.h"

#define LOCTEXT_NAMESPACE "FFreeSamplesModule"

DEFINE_LOG_CATEGORY(LogFreeSamplesCpp);

void OnCopyProperties(CallScope<void(*)(AFGPlayerState*, APlayerState*)>& scope, AFGPlayerState* self, APlayerState* playerState) {
UE_LOG(LogFreeSamplesCpp, Warning, TEXT("OnCopyProperties hook"));
scope(self, playerState);
}

void OnOverrideWith(CallScope<void(*)(APlayerState*, APlayerState*)>& scope, APlayerState* self, APlayerState* playerState) {
UE_LOG(LogFreeSamplesCpp, Warning, TEXT("OnOverrideWith hook"));
scope(self, playerState);
}

void ApplyPlayerStateHooks() {
AFGPlayerState* SampleObject1 = GetMutableDefault<AFGPlayerState>();
SUBSCRIBE_METHOD_VIRTUAL(AFGPlayerState::CopyProperties, SampleObject1, &OnCopyProperties);

APlayerState* SampleObject2 = GetMutableDefault<APlayerState>();
SUBSCRIBE_METHOD_VIRTUAL(APlayerState::OverrideWith, SampleObject2, &OnOverrideWith);
}

void FFreeSamplesModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
#if !WITH_EDITOR
ApplyPlayerStateHook();
#endif
}

void FFreeSamplesModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FFreeSamplesModule, FreeSamples)
#include "FreeSamples.h"
#include "Data/FreeSamplePlayerDataComponent.h"
#include "Patching/NativeHookManager.h"
#include "FGPlayerState.h"
//#include "Engine/Classes/GameFramework/PlayerState.h"

#define LOCTEXT_NAMESPACE "FFreeSamplesModule"

DEFINE_LOG_CATEGORY(LogFreeSamplesCpp);

void OnCopyProperties(CallScope<void(*)(AFGPlayerState*, APlayerState*)>& scope, AFGPlayerState* self, APlayerState* playerState) {
UE_LOG(LogFreeSamplesCpp, Warning, TEXT("OnCopyProperties hook"));
scope(self, playerState);
}

void OnOverrideWith(CallScope<void(*)(APlayerState*, APlayerState*)>& scope, APlayerState* self, APlayerState* playerState) {
UE_LOG(LogFreeSamplesCpp, Warning, TEXT("OnOverrideWith hook"));
scope(self, playerState);
}

void ApplyPlayerStateHooks() {
AFGPlayerState* SampleObject1 = GetMutableDefault<AFGPlayerState>();
SUBSCRIBE_METHOD_VIRTUAL(AFGPlayerState::CopyProperties, SampleObject1, &OnCopyProperties);

APlayerState* SampleObject2 = GetMutableDefault<APlayerState>();
SUBSCRIBE_METHOD_VIRTUAL(APlayerState::OverrideWith, SampleObject2, &OnOverrideWith);
}

void FFreeSamplesModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
#if !WITH_EDITOR
ApplyPlayerStateHook();
#endif
}

void FFreeSamplesModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FFreeSamplesModule, FreeSamples)
Rex
Rex•2w ago
Consider hooking these instead:
/** calls OverrideWith and triggers OnOverrideWith for BP extension */
ENGINE_API void DispatchOverrideWith(APlayerState* PlayerState);

ENGINE_API void DispatchCopyProperties(APlayerState* PlayerState);
/** calls OverrideWith and triggers OnOverrideWith for BP extension */
ENGINE_API void DispatchOverrideWith(APlayerState* PlayerState);

ENGINE_API void DispatchCopyProperties(APlayerState* PlayerState);
Robb
RobbOP•2w ago
I am blind, those ones are public, yup
Rex
Rex•2w ago
I thought you were Robb
Robb
RobbOP•2w ago
incorrect
No description
Robb
RobbOP•2w ago
yup this seems to be the way. both functions are called, Override on join and Copy on leave before save, and my hooks seem to be working traveling this weekend though, so further testing will have to wait
Robb
RobbOP•2w ago
Robb
RobbOP•2w ago
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "FreeSamplePlayerDataComponent.generated.h"

/**
* C++ parent class so the hook has a class to look up via. All implementation in blueprint.
*/
UCLASS(Blueprintable, BlueprintType, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class FREESAMPLES_API UFreeSamplePlayerDataComponent : public UActorComponent
{
GENERATED_BODY()

public:
UFUNCTION(BlueprintImplementableEvent)
void CopyProperties(APlayerState* playerState);

UFUNCTION(BlueprintImplementableEvent)
void OverrideWith(APlayerState* playerState);
};
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "FreeSamplePlayerDataComponent.generated.h"

/**
* C++ parent class so the hook has a class to look up via. All implementation in blueprint.
*/
UCLASS(Blueprintable, BlueprintType, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class FREESAMPLES_API UFreeSamplePlayerDataComponent : public UActorComponent
{
GENERATED_BODY()

public:
UFUNCTION(BlueprintImplementableEvent)
void CopyProperties(APlayerState* playerState);

UFUNCTION(BlueprintImplementableEvent)
void OverrideWith(APlayerState* playerState);
};
what I currently have
Archengius
Archengius•2w ago
The issue has been addressed for 1.1

Did you find this page helpful?