[SOLVED (sort of) ] Landscape Sub-Block Actor Ownership

We have a game with a large map composed of one Landscape with about 200 sub-blocks (tiles).

In the sub-blocks the content added when the sub-block was the ‘active block’ in the editor.
When the block loads the Actors attached to it load just fine, and unload the same.

But some of our actors are ‘loot spawn points’ and create other actors and put them into the world.
Then we want to save actor position changes (e.g. the player carried a garbage can from where it spawned to over there) to a file with one file per sub-block. Then when the block is loaded again, tell the loot spawn points to not generate the loot and instead we load the loot item (possibly repositioned) in the world block.

This requires several bits of information.
Where and how do we get this information…

  • What actors are sub-objects of a landascape sub-block?
  • In the game, add a newly spawned actor to the sub-block so it gets destroyed when the block is removed from memory.

Currently our ‘add actor by string name’ adds to the overall world.
( See posting at Our solution to spawning blueprints from c++ - C++ - Epic Developer Community Forums answer #10 )

Any pointers where the function calls for this are. (C++ or Blueprint is fine.)

— More —

Ok, The WorldTile and UWorldTileDetails appear to have some of the information. But they are editor-only classes.
At runtime where is information that an actor was loaded by a given world tile?

— More —

Stuck on this one.
What is the recommended way to load actors in a tile block, play the game a bunch and then flush the changes to disk, quit the game, restart the game, and have the actors in the correct place when the tile loads?

I realize this is a rather extensive question. Obviously there is the Save/Load system in Unreal for game state, and we already save player last known position and their inventory just fine (every 15 seconds). And it re-loads fine.

But things like tin cans on the ground that get picked up, carried and reopped, or windows that get broken, or AI NPCs that move around. How are thos supposed to be save nad re-loaded in a world tile?

This appears so obvious as a feature that I must just be missing something simple?

I could just save the entire world state of object changes but that is not going to be elegant nor efficient for ‘large worlds’ such as a map 20 km on a side with 100 tiles.

— More —

I tested looking at the Actor’s (Tomato Soup Can) GetLevel result. It appears to be the top loade dmap, not one of the sub-tiles.

– Progress! —

Ok turns out the GetLevel does return the sub-tile name.
Like

Debug Soup: Level /Game/Maps/PepperValley/PepperValleyHeightmap/UEDPIE_0_PepperValleyHeightmap_x04_y02.PepperValleyHeightmap_x04_y02:PersistentLevel

I may be on the right track…

[SOLVED below]

Solved.

I ended up writing some C++ code to solve this.

It is a series of accessor methods for an Actor and its Level it belongs to.
These work by parsing the string name of the level which is a sub-tile of the overall world.
Like

“/Game/Maps/PepperValley/PepperValleyHeightmap/UEDPIE_0_PepperValleyHeightmap_x04_y02.PepperValleyHeightmap_x04_y02:PersistentLevel”

/Game/Maps/PepperValley/PepperValleyHeightmap/UEDPIE_0_PepperValleyHeightmap_x04_y02 is the context.

PepperValleyHeightmap_x04_y02 is the UWorld.

PersistentLevel is the ULevel that owns the AActor.

Warning: These work by string parsing so can be fooled if you have similar level or actor names containing _x or _y followed by numbers.

/** Walk up the world instances tree appending the path. Uses | as the seperator.
* NOT the blueprint asset path. 
* Returns like "House|TomatoSoup"
*/
FString UtdlBlueprintHelpers::GetActorPath(AActor * actor)
{
	if (actor == NULL)
	{
		return FString(TEXT("NULL"));
	}
	FString ret;
	bool hasAppended = false;

	AActor * o = actor;
	while (o != NULL)
	{
		if (hasAppended)
		{
			ret = o->GetName() + FString("|") + ret;
		}
		else
		{
			ret = o->GetName();
		}
		hasAppended = true;
		o = o->GetAttachParentActor();
	}

	return ret;
}

FString UtdlBlueprintHelpers::DebugTestFunction(AActor * actor)
{
	ULevel * u = actor->GetLevel();
	AActor * b = actor->GetOwner();
	if (u == NULL)
	{
		return FString(TEXT("No Actor Level"));
	}
	return u->GetFullName();
}

ULevel * UtdlBlueprintHelpers::GetActorLevel(AActor * actor)
{
	return actor->GetLevel();
}

int32 UtdlBlueprintHelpers::GetActorLevelTileX(AActor * actor)
{
	if (actor == NULL)
	{
		return 0;
	}
	ULevel * lev = actor->GetLevel();
	if (lev == NULL)
	{
		return 0;
	}

	UObject * world = lev->GetOuter();
	if (world == NULL)
	{
		return 0;
	}

	int32 x = 0;
	FString nm = world->GetName();
	int32 i = nm.Find(FString("_x"));
	if (i > 0)
	{
		char * s = TCHAR_TO_ANSI(*(nm.Mid(i + 2, 2)));
		int n = sscanf_s(s, "%d", &x);
		if (n != 1)
		{
			return 0;
		}
		return x;
	}
	return 0;
}

int32 UtdlBlueprintHelpers::GetActorLevelTileY(AActor * actor)
{
	if (actor == NULL)
	{
		return 0;
	}
	ULevel * lev = actor->GetLevel();
	if (lev == NULL)
	{
		return 0;
	}

	UObject * world = lev->GetOuter();
	if (world == NULL)
	{
		return 0;
	}

	int32 y = 0;
	FString nm = world->GetName();
	int32 i = nm.Find(FString("_y"));
	if (i > 0)
	{
		char * s = TCHAR_TO_ANSI(*(nm.Mid(i + 2, 2)));
		int n = sscanf_s(s, "%d", &y);
		if (n != 1)
		{
			return 0;
		}
		return y;
	}
	return 0;
}

TArray<AActor *> UtdlBlueprintHelpers::GetTileActorListByClass(UClass * filterClass, AActor * actor)
{
	TArray<AActor *> ret;

	if (actor == NULL)
	{
		return ret;
	}
	ULevel * v = actor->GetLevel();
	if (IsValidSubLevelTile(v) == false)
	{
		return ret;
	}

	for (int i = 0; i < v->Actors.Num(); i++)
	{
		if (v->Actors[i]->IsA(filterClass))
		{
			ret.Add(v->Actors[i]);
		}
	}

	return ret;
}

bool UtdlBlueprintHelpers::IsValidSubLevelTile(ULevel * level)
{
	if (level == NULL)
	{
		return false;
	}

	UObject * world = level->GetOuter();
	if (world == NULL)
	{
		return false;
	}

	int32 x = 0;
	int32 y = 0;
	FString nm = world->GetName();

	int32 i = nm.Find(FString("_y"));
	if (i > 0)
	{
		char * s = TCHAR_TO_ANSI(*(nm.Mid(i + 2, 2)));
		int n = sscanf_s(s, "%d", &y);
		if (n != 1)
		{
			return false;
		}

		i = nm.Find(FString("_x"));
		if (i > 0)
		{
			s = TCHAR_TO_ANSI(*(nm.Mid(i + 2, 2)));
			int n = sscanf_s(s, "%d", &x);
			if (n != 1)
			{
				return false;
			}
		}
		return true;
	}

	return false;
}

bool UtdlBlueprintHelpers::IsActorInValidSubLevelTile(AActor * actor)
{
	if (actor == NULL)
	{
		return false;
	}

	return IsValidSubLevelTile(actor->GetLevel());
}

Much later correction.

The AActor’s World is the sub-tile or top level world tile.

So

UWorld * UtdlBlueprintHelpers::GetActorWorldSubTile(AActor * actor)
{
if (actor == NULL)
{
return NULL;
}
return actor->GetWorld();
}

is usefull code.