[PLUGIN] Savior

On base c++ class you can experiment with MakeSGUID(Resolved Name to Guid) node if you don’t want to assign values manually.

In that scenery it’s not good to use the Create Once node.

Understanding SGUID

I am currently having problems with LoadGameWorld()
When I try to load from a save slot that does not exist I expect the function to call On Fail but that never happens. I just get an error thrown in my output log:
“LogStreaming: Warning: Failed to read file ‘FILE_LOCATION’ error.”
Is this intented and if so is there a way I can change the c++ code to call On Fail?

In demo project you will see that the UI tries to load slot meta data to check if it is valid slot, because that is much cheaper than testing the fat slot data:

https://brunoxavierleite.github.io/Savior2API.github.io/Savior3/nodes/LoadSlotMetaData.html

Okay I got it working with that.
I still think it is strange that the Load Game World (Async) node does not call On Fail when the save file does not exist on disk.
Thanks for the help :slight_smile:

I took a look and in fact Slot’s sync node exit without calling OnFinishDataLOAD.Broadcast();
Without broadcasting that delegate before exiting, the Async node will never know that the loading process was aborted.

I will fix that for v3.4.

Okay so I have a new problem which Is probably me misunderstanding things but here it is.
I have a TMap<ENUM, TSubclassof> in my pawn which I add classes to that are all derived from a base class.
So in my Pawn BP the TMap looks as follow:
TYPE_1 - DERIVED_CLASS_1
TYPE_2 - DERIVED_CLASS_2
etc.

Now during runtime I have an UI with buttons that has all the TYPES on them and when I click one of them I do Pawn->SpawnNewClass(TYPE)
In the SpawnNewClass function I get the actual class from the TMap with the TYPE as key.
Now the first time I spawn TYPE_1 or TYPE_2 they get a nice SGUID and then when I spawn the same TYPE again it gets a new SGUID but every single one after that has the same SGUID as the 2nd one.
So when I save the game world it has multiple actors with the same SGUID.
Now I tried to fix this by changing the SGUID after spawn by call MakeActorGUID again. But that doesn’t seem to work they still all have the same SGUID.
Can you tell me how to fix this? (I hope my explanation was clear enough)

Can you show a graph screenshot / code snippet?

I think this is enough hope it helps.

SPawn.h

UPROPERTY(EditDefaultsOnly)
	TMap<EType, TSubclassOf<ASActorBase>> BPClasses;

UPROPERTY(VisibleAnywhere, SaveGame)
	TArray<ASActorBase*> SActors;

UPROPERTY(VisibleAnywhere, SaveGame)
	TArray<FGuid> SActorsGuids;

SPawn.cpp

TSubclassOf<ASActorBase> ASPawn::GetActorClassByType(EType TType) const
{
  for (const TPair<EType, TSubclassOf<ASActorBase>>& Pair : SActors)
  {
    if (Pair.Key == TType)
    {
      return Pair.Value;
    }
  }

  return NULL;
}

void ASPawn::StartActorBuild(EType TType)
{
  UE_LOG(LogTemp, Warning, TEXT("SPawn::SpawnActor"));

  UWorld* WorldPtr = GetWorld();
  TSubclassOf<ASActorBase> TClass = GetActorClassByType(TType);
  FVector Location(0.f, 0.f, 0.f);
  FRotator Rotation(0.f, 0.f, 0.f);

  FActorSpawnParameters SpawnParams;
  SpawnParams.Owner = this;
  SpawnParams.Instigator = NULL;

  ASActorBase* TempActor = WorldPtr->SpawnActor<ASActorBase>(TClass, Location, Rotation, SpawnParams);
  TempActor->MakeGUID();
  TempActor->SetActorID(SActors.Num());
  TempActor->SetPawnOwner(this);

  FVector TempActorMeshOrigin;
  FVector TempActorMeshBounds;
  float TempActorMeshRadius;

  TempActor->GetActorBounds(false, TempActorMeshOrigin, TempActorMeshBounds);
  float YPos = SActors[(SActors.Num() - 1)]->GetActorLocation().Y - (PreviousActorExtentsY + TempActorMeshBounds.Y + SPACE_BETWEEN_ACTORS);
  TempActor->SetActorLocation(FVector(0.f, YPos, 0.f));
  TempActor->GetMesh()->SetVisibility(true);

  SActors.Add(TempActor);
  SActorsGuids.Add(TempActor->SGUID);
  PreviousActorExtentsY = TempActorMeshBounds.Y;

  USGameInstance* GI = Cast<USGameInstance>(GetGameInstance());
  if (GI)
  {
    GI->SaveWorld();
  }
}

SActorBase.h

void MakeGUID();

UPROPERTY(SaveGame)
FGuid SGUID;

UPROPERTY(VisibleAnywhere, SaveGame)
ASPawn* SPawn;

SActorBase.cpp

void ASActorBase::MakeGUID()
{
	SGUID = USavior3::MakeActorGUID(this, EGuidGeneratorMode::ResolvedNameToGUID);
}

void ASActorBase::OnFinishRespawn_Implementation(const FSaviorRecord& SaveData)
{
	UE_LOG(LogTemp, Warning, TEXT("ASActorBase: OnFinishRespawn()"));

	ESaviorResult Result;
	USavior3::LoadActorData(this, SaveData, Result);

  if (SPawn == NULL)
  {
    UE_LOG(LogTemp, Warning, TEXT("ASActorBase: OnLoaded() - SPawn NULL"));
    SPawn = Cast<ASPawn>(UGameplayStatics::GetPlayerPawn(GetWorld(), 0));
  }
  SPawn->AddActorOnLoaded(this);

	GetMesh()->SetVisibility(true);
}

void ASActorBase::OnLoaded_Implementation(const FSlotMeta& MetaData)
{
	UE_LOG(LogTemp, Warning, TEXT("ASActorBase: OnLoaded() - SaveLocation: %s - Name: %s"), *MetaData.SaveLocation, *GetName());

	if (SPawn == NULL)
	{
		UE_LOG(LogTemp, Warning, TEXT("ASActorBase: OnLoaded() - SPawn NULL"));
		SPawn = Cast<ASPawn>(UGameplayStatics::GetPlayerPawn(GetWorld(), 0));
	}
	SPawn->AddActorOnLoaded(this);

	GetMesh()->SetVisibility(true);
}

I will run some tests later today and see what the debugger shows from this. Thanks for the snippet.

@MrMightyErik this problem looks like a git regression to me.

I’ve downloaded the plugin from Marketplace and this function from Reflector class isn’t there at all.
Can you confirm that this function does not exist in your copy of the plugin?

FString Reflector::SanitizeObjectPath(const FString &InPath);

If you don’t want to wait for a fix, you can apply this to your source code:
In USavior3::MakeActorGUID, first line is:

const FString Path = Reflector::MakeActorID( Instance ).ToString();

We shouldn’t use that anymore since ages ago, the correct code there are these lines:

UWorld* Outermost = Instance->GetTypedOuter<UWorld>();
FString Path = Instance->GetFullName( Outermost );
Path = Reflector::SanitizeObjectPath( Path ); 

And the SanitizeObjectPath() function is this:

	const TCHAR* BadCH = INVALID_OBJECTNAME_CHARACTERS;

	FString Sanitize = InPath;

	while (*BadCH) {
		Sanitize.ReplaceCharInline(*BadCH,TCHAR('_'),ESearchCase::CaseSensitive); ++BadCH;
	} return Sanitize;


That should fix the problem you have been experiencing with the MakeActorGUID function.

Yeah you are correct that function is missing.
I applied the fix and it works now.
Thanks a lot.
What is the best way to update if you release a new version? Since I have the plugin in my project folder now and uninstalled it from engine.
Install it to engine again and copy it over to project? If that is the case how would I know if there is an update?

I have an Unreal Engine built from Epic’s github repo and that is what I use to develop.
I always install plugins to project folders using this engine build.

Then I have another engine installed from Epic’s Launcher where I create sandbox projects (Megascans) and check for Marketplace updates.

Eventually I use the migrate asset functionality to move things to my main project. I never test or download anything directly into my project folders.

Hey Bruno, I want to be able to set the SGUID Generator Mode from within a blueprint derived from a C++ class. The issue is that I would have to defer calling MakeActorGuid until PostInitProperties in order to do that instead of the C++ constructor. Do you anticipate any problems with me doing something like this?

Both execute before BeginPlay() is called.

The plug-in only respawn anything after BeginPlay, so I think it’s fine to use it in Constructor, PostLoad, PostInitProperties.

Epic just released v3.4 to Marketplace.

Hi, I am working with blueprints on a shopkeeper game and I’m having problems using savior. In the game, the player uses shelves on which he can display his merchandise. Each shelf inventory is represented with an array. Currently I need to save each shelf inventory, the player’s gold amount, and the player’s global inventory (a map). I’ve tried using the save/load game world node for this purpose but it doesn’t seems to work. What do you think should be the approach for this goal?

@anonymous_user_a80065651 I have no way to answer that question without knowing how these shelves are structured, what they are (actor, component, structs, etc), and how they are spawned by your game.

Hi, thank you for the reply :slight_smile:
The shelves are actors that can be spawned during runtime after the player buys one from the ingame shop. They are spawned from a different blueprint class (the shop bp). After they are spawned and located, the player is able to display items on them.
In the shelf blueprint class there are two variables that need to be saved: an array of integers that represent the item IDs of items that are currently on display and an integer that determines the max number of items the shelf can contain. Also, I need to save the location of each shelf. In the future There will be many more things to save about each shelf (like color, level etc.) so I’m trying to find an efficient to do this.
Thank you

Just give each shelf an unique SGUID property.

When you want a shelf to load it’s data, use:

New Slot Instance >> Read Slot from File >> Load Actor (shelf)

Would you be opposed to exposing that Reset function to blueprints? My slots are still somehow persisting records undesirably.