// © 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, int32> ActiveCounts; FGameplayTagContainer ActiveTags; };