Skip to content

Project Interfaces

Modular Building System provides the following project extension interfaces:

Interface Purpose Recommended Implementation Location
IBuildingOwnerProvider Returns a stable owner ID for the initiator of a building operation. PlayerState
IBuildingWorldSupportProvider Determines whether a custom Actor can act as permanent world support. The Actor that provides support

Building Owner Provider

IBuildingOwnerProvider integrates the plugin's building ownership system with the project's player, faction, or NPC identification system.

The plugin usually obtains the PlayerState from the Controller that initiated the building operation, then reads an FBuildingOwnerId through this interface.

GetBuildingOwnerId

UFUNCTION(
    BlueprintNativeEvent,
    BlueprintCallable,
    Category = "Building|Owner"
)
FBuildingOwnerId GetBuildingOwnerId();
Type Name Data Type Description
Return OwnerId FBuildingOwnerId The stable building owner identifier associated with the current object.

The returned value should remain stable across save operations and network synchronization.

It is recommended to implement this interface in the project's PlayerState.

Implementation Location Recommendation
PlayerState Recommended. Suitable for storing stable player identity and server replication.
PlayerController Not recommended as the primary ownership source. It may not remain stable when the Controller changes.
Pawn Not recommended. A Pawn may die, respawn, or be replaced.
Other objects Can be used for faction, NPC, or system-level owners.

Blueprint Implementation

In the project's PlayerState Blueprint:

Class Settings
→ Implemented Interfaces
→ Add Building Owner Provider
→ Implement Get Building Owner ID

Return the corresponding FBuildingOwnerId for the current player.

C++ Implementation

// Copyright 2026 Zhiying Li. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerState.h"
#include "Interfaces/BuildingOwnerProvider.h"
#include "MyPlayerState.generated.h"

UCLASS()
class AMyPlayerState
    : public APlayerState
    , public IBuildingOwnerProvider
{
    GENERATED_BODY()

public:
    virtual FBuildingOwnerId
    GetBuildingOwnerId_Implementation() override;
};
// Copyright 2026 Zhiying Li. All Rights Reserved.

#include "MyPlayerState.h"

FBuildingOwnerId
AMyPlayerState::GetBuildingOwnerId_Implementation()
{
    return FBuildingOwnerId::Player(
        GetUniqueId().IsValid()
            ? GetUniqueId()->ToString()
            : FString::FromInt(GetPlayerId())
    );
}

The project should generate a stable ID according to its own save and online identity systems.

Calling from C++

The interface function is an Unreal Engine Interface Event and must be called through Execute_.

FBuildingOwnerId OwnerId =
    IBuildingOwnerProvider::Execute_GetBuildingOwnerId(
        PlayerState
    );

Warning

Do not call the interface event function directly:

Provider->GetBuildingOwnerId();

Always call it through:

IBuildingOwnerProvider::Execute_GetBuildingOwnerId(Object);

Owner Types

FBuildingOwnerId can represent different types of owners.

Type Description
None No valid owner.
Player A player owner.
Faction A faction or team owner.
NPC An NPC owner.
System A game system or world owner.

For more information about ownership data, see Building Entities.

Example

FString ATopiaPlayerState::GetStablePlayerSaveId() const
{
#if WITH_EDITOR
    // PIE Test
    if (!EditorStableSaveId.IsEmpty())
    {
        return EditorStableSaveId;
    }

    return FString::Printf(TEXT("PIE_Player_%d"), GetPlayerId());
#else
    const FUniqueNetIdRepl PlayerUniqueId = GetUniqueId();

    if (PlayerUniqueId.IsValid())
    {
        return PlayerUniqueId->ToString();
    }

    return TEXT("UnknownPlayer");
#endif
}

FBuildingOwnerId ATopiaPlayerState::
GetBuildingOwnerId_Implementation()
{
    FBuildingOwnerId OwnerInfo;
    OwnerInfo.Id = GetStablePlayerSaveId();
    OwnerInfo.Type = EBuildingOwnerType::Player;
    return OwnerInfo;
}

Building World Support Provider

IBuildingWorldSupportProvider allows a project to determine whether a specific Actor can act as permanent world support for a building structure.

This interface is optional. Standard terrain or static objects can also be configured as world support through collision channels and Tags.

CanProvideBuildingWorldSupport

UFUNCTION(
    BlueprintNativeEvent,
    BlueprintCallable,
    Category = "Building|World Support"
)
bool CanProvideBuildingWorldSupport(
    UPrimitiveComponent* HitComponent,
    const FHitResult& HitResult
) const;
Type Name Data Type Description
Param HitComponent UPrimitiveComponent* The component hit by the current support query.
Param HitResult const FHitResult& The complete hit information from the current collision query.
Return bCanProvideSupport bool Whether the current hit can act as permanent world support.

The interface implementation can determine the result using:

  • The hit component
  • HISM Instance Index
  • Hit location
  • Surface normal
  • Current Actor state
  • Project-specific permissions or gameplay rules

Evaluation Order

World support detection is performed in the following order:

Check World Support Object Channel
→ Check World Support Excluded Tag
→ Call Building World Support Provider
→ Check the optional World Support Tag
Check Description
Object Channel The hit component must use the configured World Support Object Channel.
Excluded Tag An Actor or component with the exclusion Tag can never provide support.
Interface If the Actor implements the interface, the interface determines the result.
Support Tag If the Actor does not implement the interface and strict mode is enabled, it must have the support Tag.

Warning

World Support Excluded Tag has higher priority than the interface.

Even if the interface returns true, an Actor or component with the exclusion Tag cannot provide support.

Blueprint Implementation

In the Actor Blueprint that requires custom support rules:

Class Settings
→ Implemented Interfaces
→ Add Building World Support Provider
→ Implement Can Provide Building World Support

For example, different results can be returned depending on the hit component:

Hit Object Return Value
Main ground component true
Decorative component false
Damaged platform false
A specified load-bearing HISM Instance true

C++ Implementation

// Copyright 2026 Zhiying Li. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Interfaces/BuildingWorldSupportProvider.h"
#include "MyWorldSupportActor.generated.h"

UCLASS()
class AMyWorldSupportActor
    : public AActor
    , public IBuildingWorldSupportProvider
{
    GENERATED_BODY()

public:
    virtual bool
    CanProvideBuildingWorldSupport_Implementation(
        UPrimitiveComponent* HitComponent,
        const FHitResult& HitResult
    ) const override;
};
// Copyright 2026 Zhiying Li. All Rights Reserved.

#include "MyWorldSupportActor.h"

bool AMyWorldSupportActor::
CanProvideBuildingWorldSupport_Implementation(
    UPrimitiveComponent* HitComponent,
    const FHitResult& HitResult
) const
{
    return IsValid(HitComponent) &&
        HitComponent->ComponentHasTag(
            TEXT("CanSupportBuilding")
        );
}

HISM Instances

When an HISM Component is hit, the Instance Index can be obtained from the hit result:

const int32 InstanceIndex = HitResult.Item;
Value Description
INDEX_NONE No valid Instance was hit.
0 or greater The index of the hit HISM Instance.

The interface can return different support results for different Instances.

Calling from C++

const bool bCanProvideSupport =
    IBuildingWorldSupportProvider::
    Execute_CanProvideBuildingWorldSupport(
        HitActor,
        HitComponent,
        HitResult
    );

Before calling the interface, check whether the Actor implements it:

if (
    IsValid(HitActor) &&
    HitActor->Implements<UBuildingWorldSupportProvider>()
)
{
    const bool bCanProvideSupport =
        IBuildingWorldSupportProvider::
        Execute_CanProvideBuildingWorldSupport(
            HitActor,
            HitComponent,
            HitResult
        );
}

Choosing Between Interfaces and Tags

Requirement Recommended Method
All objects of the same type can provide support Configure the collision channel.
Explicitly allow a specific Actor or component to provide support Use World Support Tag.
Explicitly prevent a specific Actor or component from providing support Use World Support Excluded Tag.
Evaluate support dynamically by component, Instance, or runtime state Implement IBuildingWorldSupportProvider.

When dynamic evaluation is not required, prefer collision channels and Tags instead of adding an interface implementation to simple objects.

Troubleshooting

Issue Description
The interface is not called Check whether the hit component uses the correct World Support Object Channel.
The interface returns true, but support is still rejected Check whether the Actor or component has the exclusion Tag.
The interface event cannot be found in Blueprint Check whether the interface has been added in Class Settings.
Calling the interface from C++ causes an error Use the corresponding Execute_ function.
HISM support evaluation is incorrect Check whether HitResult.Item contains a valid Instance Index.
PlayerState returns an invalid Owner ID Check whether the owner type and stable ID are configured correctly.

For structural support configuration, see Structural Support and Collapse.