Files
Naked-Desire/Source/NakedDesire/Locations/LocationSubsystem.h
T
2026-06-03 15:17:02 +03:00

58 lines
2.1 KiB
C++

// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Subsystems/WorldSubsystem.h"
#include "LocationSubsystem.generated.h"
class ULocationData;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnLocationChangedSignature, ULocationData*, Location);
/**
* Single source of truth for which tagged locations the player currently occupies (GDD §10.4).
* Fed by ALocationTrigger overlaps; queried + subscribed by everything else (session boundary,
* commission location constraints, future shop entry). World-scoped and transient — nothing to save.
*
* Locations are identified by their ULocationData FGameplayTag, so they nest: while inside
* Location.City.Beach the player also matches Location.City. The player can be in several at once.
*/
UCLASS()
class NAKEDDESIRE_API ULocationSubsystem : public UWorldSubsystem
{
GENERATED_BODY()
public:
// Reported by ALocationTrigger as the player enters / leaves a tagged volume.
void EnterLocation(ULocationData* Location);
void ExitLocation(ULocationData* Location);
// True while the player occupies a location whose tag matches (or nests under) Query.
UFUNCTION(BlueprintPure, Category = "Location")
bool IsPlayerInLocation(FGameplayTag Query) const;
UFUNCTION(BlueprintPure, Category = "Location")
FGameplayTagContainer GetPlayerLocationTags() const { return ActiveTags; }
// Most-specific current location (deepest tag) — for HUD / prompts. Null when outdoors / untagged.
UFUNCTION(BlueprintPure, Category = "Location")
ULocationData* GetCurrentLocation() const;
UPROPERTY(BlueprintAssignable, Category = "Location")
FOnLocationChangedSignature OnLocationEntered;
UPROPERTY(BlueprintAssignable, Category = "Location")
FOnLocationChangedSignature OnLocationExited;
private:
void RebuildActiveTags();
// Ref-counted: a location can be several overlapping trigger volumes, so we only fire enter on
// 0->1 and exit on 1->0 — crossing between two boxes of the same place doesn't churn events.
UPROPERTY()
TMap<TObjectPtr<ULocationData>, int32> ActiveCounts;
FGameplayTagContainer ActiveTags;
};