Function bodies 468 total
GetDefinitionsByTag method · cpp · L85-L104 (20 LOC)Source/VaultCore/Private/Data/ItemDefinitionRegistry.cpp
TArray<UItemDefinition*> UItemDefinitionRegistry::GetDefinitionsByTag(FGameplayTag FilterTag) const
{
if (!FilterTag.IsValid())
{
return GetAllDefinitions();
}
FReadScopeLock ReadLock(RegistryLock);
TArray<UItemDefinition*> Result;
for (const auto& Pair : DefinitionMap)
{
if (IsValid(Pair.Value) && Pair.Value->ItemTags.HasTag(FilterTag))
{
Result.Add(Pair.Value.Get());
}
}
return Result;
}GetDefinitionCount method · cpp · L105-L110 (6 LOC)Source/VaultCore/Private/Data/ItemDefinitionRegistry.cpp
int32 UItemDefinitionRegistry::GetDefinitionCount() const
{
FReadScopeLock ReadLock(RegistryLock);
return DefinitionMap.Num();
}ValidateRegistry method · cpp · L111-L163 (53 LOC)Source/VaultCore/Private/Data/ItemDefinitionRegistry.cpp
TArray<FString> UItemDefinitionRegistry::ValidateRegistry() const
{
FReadScopeLock ReadLock(RegistryLock);
// Collect all definitions into an array for ParallelFor
TArray<TPair<FGameplayTag, const UItemDefinition*>> AllDefs;
AllDefs.Reserve(DefinitionMap.Num());
for (const auto& Pair : DefinitionMap)
{
AllDefs.Emplace(Pair.Key, Pair.Value.Get());
}
// Thread-safe collection of warnings
FCriticalSection WarningLock;
TArray<FString> Warnings;
// ParallelFor for batch validation of large catalogs
ParallelFor(AllDefs.Num(), [&](int32 Index)
{
const FGameplayTag& Tag = AllDefs[Index].Key;
const UItemDefinition* Def = AllDefs[Index].Value;
if (!IsValid(Def))
{
FScopeLock Lock(&WarningLock);
Warnings.Add(FString::Printf(TEXT("Tag '%s': null definition"), *Tag.ToString()));
return;
}
// Check for missing Visuals fragment
const UItemFragment_Visuals* Visuals = Def->FindFragmentByClass<UItemFragment_Visuals>();
if (!Visuals)
{
FScopeLock Lock(&WRegisterDefinition method · cpp · L166-L201 (36 LOC)Source/VaultCore/Private/Data/ItemDefinitionRegistry.cpp
bool UItemDefinitionRegistry::RegisterDefinition(FGameplayTag Tag, UItemDefinition* Definition)
{
if (!Tag.IsValid())
{
UE_LOG(LogVaultRegistry, Error, TEXT("RegisterDefinition: invalid tag"));
return false;
}
if (!IsValid(Definition))
{
UE_LOG(LogVaultRegistry, Error, TEXT("RegisterDefinition: null definition for tag '%s'"),
*Tag.ToString());
return false;
}
FWriteScopeLock WriteLock(RegistryLock);
if (bRegistryFinalized)
{
UE_LOG(LogVaultRegistry, Error,
TEXT("RegisterDefinition: registry is finalized, rejecting '%s'"), *Tag.ToString());
return false;
}
if (DefinitionMap.Contains(Tag))
{
UE_LOG(LogVaultRegistry, Error,
TEXT("RegisterDefinition: duplicate tag '%s' — collision rejected"), *Tag.ToString());
return false;
}
DefinitionMap.Add(Tag, Definition);
UE_LOG(LogVaultRegistry, Verbose, TEXT("Registered '%s'"), *Tag.ToString());
return true;
}PopulateFromDataTable method · cpp · L202-L235 (34 LOC)Source/VaultCore/Private/Data/ItemDefinitionRegistry.cpp
void UItemDefinitionRegistry::PopulateFromDataTable(const UDataTable* DataTable)
{
if (!DataTable)
{
return;
}
const FString ContextString(TEXT("ItemDefinitionRegistry::PopulateFromDataTable"));
TArray<FItemDefinitionRow*> AllRows;
DataTable->GetAllRows<FItemDefinitionRow>(ContextString, AllRows);
UE_LOG(LogVaultRegistry, Display, TEXT("Populating from DataTable '%s' (%d rows)"),
*DataTable->GetName(), AllRows.Num());
int32 SuccessCount = 0;
for (const FItemDefinitionRow* Row : AllRows)
{
if (!Row || !Row->ItemTag.IsValid())
{
UE_LOG(LogVaultRegistry, Warning, TEXT("Skipping row with invalid ItemTag"));
continue;
}
UItemDefinition* Def = CreateDefinitionFromRow(*Row);
if (Def && RegisterDefinition(Row->ItemTag, Def))
{
++SuccessCount;
}
}
UE_LOG(LogVaultRegistry, Display, TEXT("DataTable: registered %d/%d definitions"),
SuccessCount, AllRows.Num());
}PopulateFromAssetManager method · cpp · L236-L282 (47 LOC)Source/VaultCore/Private/Data/ItemDefinitionRegistry.cpp
void UItemDefinitionRegistry::PopulateFromAssetManager()
{
UAssetManager& AssetManager = UAssetManager::Get();
TArray<FPrimaryAssetId> AssetIds;
AssetManager.GetPrimaryAssetIdList(FPrimaryAssetType(TEXT("ItemDefinition")), AssetIds);
if (AssetIds.Num() == 0)
{
UE_LOG(LogVaultRegistry, Display, TEXT("Asset Manager: no ItemDefinition assets found"));
return;
}
UE_LOG(LogVaultRegistry, Display, TEXT("Asset Manager: found %d ItemDefinition assets"), AssetIds.Num());
int32 SuccessCount = 0;
for (const FPrimaryAssetId& AssetId : AssetIds)
{
FSoftObjectPath AssetPath = AssetManager.GetPrimaryAssetPath(AssetId);
UItemDefinition* Def = Cast<UItemDefinition>(AssetPath.TryLoad());
if (!IsValid(Def))
{
UE_LOG(LogVaultRegistry, Warning, TEXT("Failed to load ItemDefinition asset: %s"),
*AssetId.ToString());
continue;
}
if (Def->ItemTags.Num() == 0)
{
UE_LOG(LogVaultRegistry, Warning, TEXT("ItemDefinition '%s' has no ItemTags, skipping"),
*Def->GetNFinalizeRegistry method · cpp · L283-L299 (17 LOC)Source/VaultCore/Private/Data/ItemDefinitionRegistry.cpp
void UItemDefinitionRegistry::FinalizeRegistry()
{
{
FWriteScopeLock WriteLock(RegistryLock);
bRegistryFinalized = true;
IntegrityHash = ComputeIntegrityHash();
}
bRegistryReady = true;
UE_LOG(LogVaultRegistry, Display,
TEXT("Registry finalized: %d definitions, integrity hash 0x%08X"),
DefinitionMap.Num(), IntegrityHash);
OnRegistryReady.Broadcast();
}Repobility — the code-quality scanner for AI-generated software · https://repobility.com
CreateDefinitionFromRow method · cpp · L302-L368 (67 LOC)Source/VaultCore/Private/Data/ItemDefinitionRegistry.cpp
UItemDefinition* UItemDefinitionRegistry::CreateDefinitionFromRow(const FItemDefinitionRow& Row)
{
if (!Row.ItemTag.IsValid())
{
return nullptr;
}
// Create the definition with a stable name derived from the tag
// The outer is 'this' (the registry subsystem) so it stays alive for the session
FName DefName(*FString::Printf(TEXT("ItemDef_%s"), *Row.ItemTag.ToString().Replace(TEXT("."), TEXT("_"))));
UItemDefinition* Def = NewObject<UItemDefinition>(this, DefName);
// Build ItemTags from primary + extras
Def->ItemTags.AddTag(Row.ItemTag);
Def->ItemTags.AppendTags(Row.ExtraTags);
// Always create Visuals fragment — DisplayName is essential for UI
UItemFragment_Visuals* Visuals = NewObject<UItemFragment_Visuals>(Def);
Visuals->DisplayName = Row.DisplayName;
Visuals->Description = Row.Description;
Visuals->InventoryIcon = Row.InventoryIcon;
Visuals->WorldDropActorClass = Row.WorldDropActorClass;
Def->Fragments.Add(Visuals);
// Conditional: Stackable (only if MaxStacComputeIntegrityHash method · cpp · L369-L390 (22 LOC)Source/VaultCore/Private/Data/ItemDefinitionRegistry.cpp
int32 UItemDefinitionRegistry::ComputeIntegrityHash() const
{
// Collect and sort all tag strings for deterministic hashing
TArray<FString> TagStrings;
TagStrings.Reserve(DefinitionMap.Num());
for (const auto& Pair : DefinitionMap)
{
TagStrings.Add(Pair.Key.ToString());
}
TagStrings.Sort();
// Build a single concatenated string and hash it
FString Combined;
for (const FString& TagStr : TagStrings)
{
Combined += TagStr;
Combined += TEXT("|");
}
return static_cast<int32>(FCrc::StrCrc32(*Combined));
}GetPreloadDependencies method · cpp · L4-L9 (6 LOC)Source/VaultCore/Private/Data/ItemFragment.cpp
void UItemFragment::GetPreloadDependencies(TArray<UObject*>& OutDeps)
{
Super::GetPreloadDependencies(OutDeps);
// Subclasses override to register TSoftObjectPtr assets with the Asset Manager
}GetFragmentDescription method · cpp · L12-L15 (4 LOC)Source/VaultCore/Private/Data/ItemFragment.cpp
FText UItemFragment::GetFragmentDescription() const
{
return FText::FromString(GetClass()->GetName());
}ShouldCreateSubsystem method · cpp · L9-L18 (10 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
bool UInventoryDebugSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
// Never create in shipping builds — debug-only subsystem
#if UE_BUILD_SHIPPING
return false;
#else
return true;
#endif
}Initialize method · cpp · L19-L58 (40 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
RecentEvents.Reserve(MaxEventCount);
SessionStartTime = FPlatformTime::Seconds();
// Register console command for toggling the HUD overlay.
// During editor map transitions, the new world's subsystems initialize BEFORE
// the old world's subsystems deinitialize. Unregister the stale command first
// to prevent the console manager "already exists" warning and ensure the
// delegate binds to THIS subsystem's weak lambda (not the dying one).
IConsoleObject* Existing = IConsoleManager::Get().FindConsoleObject(TEXT("Vault.Debug.Toggle"), false);
if (Existing)
{
IConsoleManager::Get().UnregisterConsoleObject(Existing, false);
}
ToggleCommand = MakeUnique<FAutoConsoleCommand>(
TEXT("Vault.Debug.Toggle"),
TEXT("Toggle the Vault Debug HUD overlay"),
FConsoleCommandDelegate::CreateWeakLambda(this, [this]()
{
bHUDVisible = !bHUDVisible;
OnDebugHUDToggledCreateWeakLambda method · cpp · L40-L45 (6 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
FConsoleCommandDelegate::CreateWeakLambda(this, [this]()
{
bHUDVisible = !bHUDVisible;
OnDebugHUDToggled.Broadcast(bHUDVisible);
UE_LOG(LogVaultDX, Display, TEXT("Debug HUD %s"), bHUDVisible ? TEXT("shown") : TEXT("hidden"));
})Deinitialize method · cpp · L59-L80 (22 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::Deinitialize()
{
UnbindFromTrackedComponent();
// Skip unsubscribing from AuditLogSubsystem during world teardown.
// Subsystem deinit order is nondeterministic — AuditLogSubsystem may
// already be destroyed. AddUObject bindings auto-invalidate when this
// UObject is destroyed, so manual removal is unnecessary.
// Release console command ownership without unregistering.
// During editor map transitions the new subsystem's Initialize() already
// unregistered the stale command and created a fresh one, so Reset() here
// would accidentally unregister the NEW subsystem's command.
// Intentional leak (~16 bytes per map transition, debug-only subsystem).
FAutoConsoleCommand* Leaked = ToggleCommand.Release();
(void)Leaked;
UE_LOG(LogVaultDX, Display, TEXT("InventoryDebugSubsystem shut down."));
Super::Deinitialize();
}Repobility — same analyzer, your code, free for public repos · /scan/
RecordEvent method · cpp · L83-L99 (17 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::RecordEvent(const FAuditEvent& Event)
{
// No-op while frozen — preserve the frozen state for inspection
if (bFrozen)
{
return;
}
RecentEvents.Add(Event);
// Evict oldest when exceeding capacity
if (RecentEvents.Num() > MaxEventCount)
{
RecentEvents.RemoveAt(0);
}
}GetRecentEvents method · cpp · L100-L104 (5 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
const TArray<FAuditEvent>& UInventoryDebugSubsystem::GetRecentEvents() const
{
return bFrozen ? FrozenEvents : RecentEvents;
}CacheSnapshot method · cpp · L107-L116 (10 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::CacheSnapshot(const FInventorySnapshot& Snapshot)
{
if (bFrozen)
{
return;
}
CachedSnapshot = Snapshot;
}GetCachedSnapshot method · cpp · L117-L121 (5 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
const FInventorySnapshot& UInventoryDebugSubsystem::GetCachedSnapshot() const
{
return bFrozen ? FrozenSnapshot : CachedSnapshot;
}RefreshSnapshot method · cpp · L122-L134 (13 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::RefreshSnapshot()
{
if (bFrozen)
{
return;
}
if (TrackedComponent.IsValid())
{
CachedSnapshot = TrackedComponent->CreateSnapshot();
}
}GetHealthMetrics method · cpp · L137-L170 (34 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
FDebugHealthMetrics UInventoryDebugSubsystem::GetHealthMetrics() const
{
FDebugHealthMetrics Metrics;
// Query storage metrics from the game instance subsystem
if (const UWorld* World = GetWorld())
{
if (const UGameInstance* GI = World->GetGameInstance())
{
if (const UVaultStorageManager* StorageMgr = GI->GetSubsystem<UVaultStorageManager>())
{
Metrics.StorageLatencyP95 = StorageMgr->GetLatencyP95();
Metrics.StorageOperationCount = StorageMgr->GetOperationCount();
Metrics.StorageFailureRate = StorageMgr->GetFailureRate();
}
}
// Query audit event count
if (const UAuditLogSubsystem* AuditSub = World->GetSubsystem<UAuditLogSubsystem>())
{
Metrics.TotalAuditEvents = AuditSub->GetTotalEventsDispatched();
}
}
// Calculate events per second from session lifetime
const double ElapsedSeconds = FPlatformTime::Seconds() - SessionStartTime;
if (ElapsedSeconds > 0.0)
{
Metrics.EventsPerSecond = static_cast<float>(Metrics.TotalAuditEvents / ElapsSetFreezeFrame method · cpp · L173-L198 (26 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::SetFreezeFrame(bool bFreeze)
{
if (bFreeze == bFrozen)
{
return;
}
if (bFreeze)
{
// Capture current state into frozen copies
FrozenEvents = RecentEvents;
FrozenSnapshot = CachedSnapshot;
UE_LOG(LogVaultDX, Display, TEXT("Freeze-frame ON: captured %d events, hash=%d"),
FrozenEvents.Num(), FrozenSnapshot.InventoryHash);
}
else
{
// Discard frozen copies
FrozenEvents.Reset();
FrozenSnapshot = FInventorySnapshot();
UE_LOG(LogVaultDX, Display, TEXT("Freeze-frame OFF: resumed live updates."));
}
bFrozen = bFreeze;
}TrackInventory method · cpp · L201-L226 (26 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::TrackInventory(UInventoryManagerComponent* InComponent)
{
// Unbind from any previously tracked component
UnbindFromTrackedComponent();
if (!IsValid(InComponent))
{
TrackedComponent.Reset();
return;
}
TrackedComponent = InComponent;
// Bind to inventory delegates for automatic snapshot refresh
InComponent->OnInventoryItemAdded.AddDynamic(this, &UInventoryDebugSubsystem::OnItemAdded);
InComponent->OnInventoryItemRemoved.AddDynamic(this, &UInventoryDebugSubsystem::OnItemRemoved);
InComponent->OnInventoryItemChanged.AddDynamic(this, &UInventoryDebugSubsystem::OnItemChanged);
InComponent->OnCurrencyChanged.AddDynamic(this, &UInventoryDebugSubsystem::OnCurrencyChanged);
// Capture initial snapshot
RefreshSnapshot();
UE_LOG(LogVaultDX, Display, TEXT("Now tracking inventory for ProfileID=%s"),
*InComponent->ProfileID);
}Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
UnbindFromTrackedComponent method · cpp · L227-L239 (13 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::UnbindFromTrackedComponent()
{
if (TrackedComponent.IsValid())
{
TrackedComponent->OnInventoryItemAdded.RemoveDynamic(this, &UInventoryDebugSubsystem::OnItemAdded);
TrackedComponent->OnInventoryItemRemoved.RemoveDynamic(this, &UInventoryDebugSubsystem::OnItemRemoved);
TrackedComponent->OnInventoryItemChanged.RemoveDynamic(this, &UInventoryDebugSubsystem::OnItemChanged);
TrackedComponent->OnCurrencyChanged.RemoveDynamic(this, &UInventoryDebugSubsystem::OnCurrencyChanged);
}
TrackedComponent.Reset();
}OnItemAdded method · cpp · L242-L246 (5 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::OnItemAdded(const FInventoryEntry& Entry)
{
RefreshSnapshot();
}OnItemRemoved method · cpp · L247-L251 (5 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::OnItemRemoved(const FGuid& InstanceID)
{
RefreshSnapshot();
}OnItemChanged method · cpp · L252-L256 (5 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::OnItemChanged(const FInventoryEntry& Entry)
{
RefreshSnapshot();
}OnCurrencyChanged method · cpp · L257-L261 (5 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::OnCurrencyChanged(FGameplayTag CurrencyType, int32 NewBalance)
{
RefreshSnapshot();
}SetReconciliationReport method · cpp · L262-L268 (7 LOC)Source/VaultCore/Private/DX/InventoryDebugSubsystem.cpp
void UInventoryDebugSubsystem::SetReconciliationReport(const TArray<FReconciliationDiscrepancy>& Report)
{
LastReconciliationReport = Report;
UE_LOG(LogVault, Log, TEXT("DebugSubsystem: Reconciliation report updated with %d discrepancies"),
Report.Num());
}AsyncFetchConfig method · cpp · L8-L131 (124 LOC)Source/VaultCore/Private/Economy/LocalEconomyConfigProvider.cpp
void ULocalEconomyConfigProvider::AsyncFetchConfig(FOnEconomyConfigLoaded OnComplete)
{
CachedConfig = FVaultEconomyConfig();
CachedConfig.ConfigVersion = 1;
const UVaultSettings* Settings = UVaultSettings::Get();
int32 LootEntryCount = 0;
int32 RecipeCount = 0;
int32 ParamCount = 0;
// ── Load Loot Tables from DataTable ─────────────
// Each row is one loot entry; rows sharing the same LootTableTag form a single table.
if (Settings && !Settings->LootTablesDataTable.IsNull())
{
UDataTable* LootDT = Settings->LootTablesDataTable.LoadSynchronous();
if (IsValid(LootDT))
{
TArray<FLootTableRow*> Rows;
LootDT->GetAllRows<FLootTableRow>(TEXT("LocalEconomyConfigProvider"), Rows);
for (const FLootTableRow* Row : Rows)
{
if (!Row || !Row->LootTableTag.IsValid()) continue;
FLootEntry Entry;
Entry.ItemDef = Row->ItemDef;
Entry.MinQuantity = Row->MinQuantity;
Entry.MaxQuantity = Row->MaxQuantity;
Entry.Weight = Row->Weight;
// Group enGetCachedConfig method · cpp · L132-L136 (5 LOC)Source/VaultCore/Private/Economy/LocalEconomyConfigProvider.cpp
const FVaultEconomyConfig& ULocalEconomyConfigProvider::GetCachedConfig() const
{
return CachedConfig;
}Repobility · code-quality intelligence platform · https://repobility.com
GetConfigVersion method · cpp · L137-L141 (5 LOC)Source/VaultCore/Private/Economy/LocalEconomyConfigProvider.cpp
int32 ULocalEconomyConfigProvider::GetConfigVersion() const
{
return CachedConfig.ConfigVersion;
}ShouldCreateSubsystem method · cpp · L12-L18 (7 LOC)Source/VaultCore/Private/Lifecycle/ItemExpirationSubsystem.cpp
bool UItemExpirationSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
const UWorld* World = Cast<UWorld>(Outer);
if (!World) return false;
return World->GetNetMode() != NM_Client;
}Initialize method · cpp · L19-L38 (20 LOC)Source/VaultCore/Private/Lifecycle/ItemExpirationSubsystem.cpp
void UItemExpirationSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
const UVaultSettings* Settings = UVaultSettings::Get();
const float Interval = Settings ? Settings->ExpirationSweepIntervalSeconds : 60.0f;
if (GetWorld())
{
GetWorld()->GetTimerManager().SetTimer(
SweepTimerHandle,
this,
&UItemExpirationSubsystem::RunExpirationSweep,
Interval,
true);
}
UE_LOG(LogVault, Display, TEXT("ItemExpirationSubsystem initialized. Sweep interval: %.0fs"), Interval);
}Deinitialize method · cpp · L39-L47 (9 LOC)Source/VaultCore/Private/Lifecycle/ItemExpirationSubsystem.cpp
void UItemExpirationSubsystem::Deinitialize()
{
if (GetWorld())
{
GetWorld()->GetTimerManager().ClearTimer(SweepTimerHandle);
}
Super::Deinitialize();
}RunExpirationSweep method · cpp · L48-L105 (58 LOC)Source/VaultCore/Private/Lifecycle/ItemExpirationSubsystem.cpp
void UItemExpirationSubsystem::RunExpirationSweep()
{
const UVaultSettings* Settings = UVaultSettings::Get();
if (!Settings) return;
const FDateTime Now = FDateTime::UtcNow();
const FTimespan WarningThreshold = FTimespan::FromMinutes(Settings->NearExpiryWarningMinutes);
int32 ExpiredCount = 0;
int32 WarningCount = 0;
// Iterate all inventory components in the world
for (TObjectIterator<UInventoryManagerComponent> It; It; ++It)
{
UInventoryManagerComponent* Comp = *It;
if (!IsValid(Comp) || !Comp->GetOwner() || Comp->GetOwnerRole() != ROLE_Authority)
{
continue;
}
// Collect expired items (iterate backwards since we may remove)
TArray<FGuid> ToRemove;
for (const FInventoryEntry& Entry : Comp->InventoryArray.Entries)
{
if (!Entry.HasExpiry()) continue;
if (Entry.IsExpired())
{
ToRemove.Add(Entry.InstanceID);
}
else if ((Entry.ExpiresAtUTC - Now) < WarningThreshold)
{
// Near-expiry warning — fire once per item
if (!WarnedInsGetPrimaryAssetId method · cpp · L4-L8 (5 LOC)Source/VaultCore/Private/Lifecycle/ItemTransformationRule.cpp
FPrimaryAssetId UItemTransformationRule::GetPrimaryAssetId() const
{
return FPrimaryAssetId(TEXT("TransformationRule"), GetFName());
}ShouldCreateSubsystem method · cpp · L18-L23 (6 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
bool UMailSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
const UWorld* World = Cast<UWorld>(Outer);
return World && World->GetNetMode() != NM_Client;
}Initialize method · cpp · L24-L35 (12 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
void UMailSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
if (GetWorld())
{
GetWorld()->GetTimerManager().SetTimer(
ExpirationTimerHandle, this, &UMailSubsystem::CleanupExpiredMail, 300.0f, true);
}
AsyncLoadMailStore();
UE_LOG(LogVault, Display, TEXT("MailSubsystem initialized."));
}Repobility — the code-quality scanner for AI-generated software · https://repobility.com
SendMail method · cpp · L36-L63 (28 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
FGameplayTag UMailSubsystem::SendMail(const FMailMessage& Message)
{
FMailMessage Msg = Message;
Msg.MessageID = FGuid::NewGuid();
Msg.SentAtUTC = FDateTime::UtcNow();
if (Msg.ExpiresAtUTC.GetTicks() == 0)
{
Msg.ExpiresAtUTC = FDateTime::UtcNow() + FTimespan::FromDays(30);
}
if (Msg.IdempotencyKey.IsEmpty())
{
Msg.IdempotencyKey = Msg.MessageID.ToString();
}
// Check idempotency
if (UsedIdempotencyKeys.Contains(Msg.IdempotencyKey))
{
return VaultTags::Validation_Error_AlreadyClaimed;
}
MailStore.Add(Msg);
AsyncSaveMailStore();
UE_LOG(LogVault, Log, TEXT("Mail sent: From=%s To=%s Subject=%s"),
*Msg.SenderID, *Msg.RecipientID, *Msg.Subject.ToString());
return VaultTags::Validation_Success;
}GetMailbox method · cpp · L64-L76 (13 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
TArray<FMailMessage> UMailSubsystem::GetMailbox(const FString& RecipientID) const
{
TArray<FMailMessage> Result;
for (const FMailMessage& Msg : MailStore)
{
if (Msg.RecipientID == RecipientID && !Msg.bClaimed)
{
Result.Add(Msg);
}
}
return Result;
}ClaimAttachments method · cpp · L77-L116 (40 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
FGameplayTag UMailSubsystem::ClaimAttachments(const FGuid& MessageID, APlayerController* Recipient)
{
FMailMessage* Msg = MailStore.FindByPredicate([&MessageID](const FMailMessage& M)
{
return M.MessageID == MessageID;
});
if (!Msg) return VaultTags::Validation_Error_ItemNotFound;
if (Msg->bClaimed) return VaultTags::Validation_Error_AlreadyClaimed;
UInventoryManagerComponent* Comp = Recipient->GetPawn()
? Recipient->GetPawn()->FindComponentByClass<UInventoryManagerComponent>()
: nullptr;
if (!Comp) return VaultTags::Validation_Error_NotOwner;
// Transfer item attachments
for (const FMailAttachment& Att : Msg->Attachments)
{
if (IsValid(Att.ItemDef.Get()))
{
Comp->Authority_AddItem(Att.ItemDef, Att.Quantity, Att.InitialTags);
}
}
// Transfer currency attachments
for (const auto& Pair : Msg->CurrencyAttachments)
{
Comp->Authority_AddCurrency(Pair.Key, Pair.Value);
}
Msg->bClaimed = true;
UsedIdempotencyKeys.Add(Msg->IdempotencyKey);
AsyncSaveMailDeleteMessage method · cpp · L117-L129 (13 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
void UMailSubsystem::DeleteMessage(const FGuid& MessageID)
{
const int32 Removed = MailStore.RemoveAll([&MessageID](const FMailMessage& M)
{
return M.MessageID == MessageID && M.bClaimed;
});
if (Removed > 0)
{
AsyncSaveMailStore();
}
}GrantReward method · cpp · L130-L152 (23 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
FGameplayTag UMailSubsystem::GrantReward(
const FString& RecipientID, const FString& RewardKey,
const TArray<FMailAttachment>& Items, const TMap<FGameplayTag, int32>& Currency,
const FString& SenderID)
{
// Idempotency check
if (UsedIdempotencyKeys.Contains(RewardKey))
{
UE_LOG(LogVault, Log, TEXT("GrantReward: RewardKey=%s already claimed, skipping."), *RewardKey);
return VaultTags::Validation_Error_AlreadyClaimed;
}
FMailMessage Msg;
Msg.SenderID = SenderID;
Msg.RecipientID = RecipientID;
Msg.Subject = FText::FromString(TEXT("Reward Delivery"));
Msg.Attachments = Items;
Msg.CurrencyAttachments = Currency;
Msg.IdempotencyKey = RewardKey;
return SendMail(Msg);
}CleanupExpiredMail method · cpp · L153-L167 (15 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
void UMailSubsystem::CleanupExpiredMail()
{
const FDateTime Now = FDateTime::UtcNow();
const int32 Removed = MailStore.RemoveAll([&Now](const FMailMessage& M)
{
return M.ExpiresAtUTC.GetTicks() > 0 && Now > M.ExpiresAtUTC && !M.bClaimed;
});
if (Removed > 0)
{
UE_LOG(LogVault, Log, TEXT("Cleaned up %d expired unclaimed mail messages."), Removed);
AsyncSaveMailStore();
}
}AsyncSaveMailStore method · cpp · L170-L220 (51 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
void UMailSubsystem::AsyncSaveMailStore()
{
UWorld* World_Local = GetWorld();
UGameInstance* GI = World_Local ? World_Local->GetGameInstance() : nullptr;
UVaultStorageManager* SM = GI ? GI->GetSubsystem<UVaultStorageManager>() : nullptr;
if (SM == nullptr) return;
TScriptInterface<IInventoryStorageProvider> Provider = SM->GetActiveProvider();
if (!Provider.GetObject()) return;
// Serialize mail store to JSON
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
TArray<TSharedPtr<FJsonValue>> MessagesArray;
for (const FMailMessage& Msg : MailStore)
{
TSharedRef<FJsonObject> MsgObj = MakeShared<FJsonObject>();
MsgObj->SetStringField(TEXT("MessageID"), Msg.MessageID.ToString());
MsgObj->SetStringField(TEXT("SenderID"), Msg.SenderID);
MsgObj->SetStringField(TEXT("RecipientID"), Msg.RecipientID);
MsgObj->SetStringField(TEXT("Subject"), Msg.Subject.ToString());
MsgObj->SetStringField(TEXT("Body"), Msg.Body.ToString());
MsgObj->SetStringField(TEXT("SentAtUTC")AsyncLoadMailStore method · cpp · L221-L240 (20 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
void UMailSubsystem::AsyncLoadMailStore()
{
UWorld* World_Local = GetWorld();
UGameInstance* GI = World_Local ? World_Local->GetGameInstance() : nullptr;
UVaultStorageManager* SM = GI ? GI->GetSubsystem<UVaultStorageManager>() : nullptr;
if (SM == nullptr) return;
TScriptInterface<IInventoryStorageProvider> Provider = SM->GetActiveProvider();
if (!Provider.GetObject()) return;
// Dynamic delegates cannot BindLambda — use an unbound delegate.
// The load callback will fire on the game thread. For now, the mail store
// starts empty each session and is populated by incoming SendMail/GrantReward calls.
// Full deserialization from storage is deferred to a UFUNCTION callback approach.
FOnStorageLoadComplete OnComplete;
Provider->AsyncLoad(TEXT("System_Mail"), OnComplete);
UE_LOG(LogVault, Log, TEXT("MailSubsystem: AsyncLoadMailStore dispatched for System_Mail"));
}Repobility — same analyzer, your code, free for public repos · /scan/
BulkGrantRewards method · cpp · L243-L262 (20 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
TArray<FGameplayTag> UMailSubsystem::BulkGrantRewards(
const TArray<FString>& RecipientIDs,
const FString& RewardKey,
const TArray<FMailAttachment>& Items,
const TMap<FGameplayTag, int32>& Currency,
const FString& SenderID)
{
TArray<FGameplayTag> Results;
Results.Reserve(RecipientIDs.Num());
for (int32 i = 0; i < RecipientIDs.Num(); ++i)
{
// Each grant gets a unique idempotency key: BaseKey_Index
const FString UniqueKey = FString::Printf(TEXT("%s_%d"), *RewardKey, i);
Results.Add(GrantReward(RecipientIDs[i], UniqueKey, Items, Currency, SenderID));
}
return Results;
}ProcessWebhookPayload method · cpp · L263-L309 (47 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
FGameplayTag UMailSubsystem::ProcessWebhookPayload(const FString& JSONPayload)
{
TSharedPtr<FJsonObject> JsonObj;
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JSONPayload);
if (!FJsonSerializer::Deserialize(Reader, JsonObj) || !JsonObj.IsValid())
{
UE_LOG(LogVault, Warning, TEXT("MailSubsystem: Malformed webhook JSON payload"));
return FGameplayTag(VaultTags::Validation_Error_MalformedPayload);
}
const FString Recipient = JsonObj->GetStringField(TEXT("recipient"));
const FString RewardKey = JsonObj->GetStringField(TEXT("reward_key"));
const FString Sender = JsonObj->HasField(TEXT("sender"))
? JsonObj->GetStringField(TEXT("sender"))
: TEXT("System_Webhook");
if (Recipient.IsEmpty() || RewardKey.IsEmpty())
{
UE_LOG(LogVault, Warning, TEXT("MailSubsystem: Webhook missing recipient or reward_key"));
return FGameplayTag(VaultTags::Validation_Error_MalformedPayload);
}
// Parse items array (simplified — real implementation would resolve IGetUnclaimedMailCount method · cpp · L310-L322 (13 LOC)Source/VaultCore/Private/Mail/MailSubsystem.cpp
int32 UMailSubsystem::GetUnclaimedMailCount(const FString& RecipientID) const
{
int32 Count = 0;
for (const FMailMessage& Msg : MailStore)
{
if (Msg.RecipientID == RecipientID && !Msg.bClaimed)
{
++Count;
}
}
return Count;
}