← back to drstreit__Unreal-01-VaultInventory

Function bodies 468 total

All specs Real LLM only Function bodies
DetectOrphanItems method · cpp · L172-L202 (31 LOC)
Source/VaultCore/Private/Audit/AnomalyDetectionSubsystem.cpp
TArray<FAnomalyReport> UAnomalyDetectionSubsystem::DetectOrphanItems(
	const TArray<FInventoryEntry>& SnapshotEntries,
	const TArray<FAuditEvent>& LedgerEvents,
	const FString& OwnerID) const
{
	TArray<FAnomalyReport> Reports;

	TArray<FReconciliationDiscrepancy> Discrepancies =
		UEventSourcingHelpers::ReconcileSnapshotVsLedger(
			SnapshotEntries, LedgerEvents, OwnerID);

	for (const FReconciliationDiscrepancy& Disc : Discrepancies)
	{
		// Positive delta = phantom items (in snapshot but not accounted for in ledger)
		if (Disc.Delta > 0)
		{
			FAnomalyReport Report;
			Report.AnomalyType = VaultTags::Anomaly_OrphanItem;
			Report.Severity = 3; // High — potential duplication exploit
			Report.AffectedOwnerID = OwnerID;
			Report.Evidence = FString::Printf(
				TEXT("Item %s: snapshot has %d but ledger accounts for %d (delta: +%d)"),
				*Disc.ItemDefTag, Disc.SnapshotQuantity, Disc.LedgerQuantity, Disc.Delta);
			Report.DetectedAtUTC = FDateTime::UtcNow();
			Reports.Add(Report);
	
DetectWealthVelocity method · cpp · L203-L246 (44 LOC)
Source/VaultCore/Private/Audit/AnomalyDetectionSubsystem.cpp
TArray<FAnomalyReport> UAnomalyDetectionSubsystem::DetectWealthVelocity(
	const TArray<FAuditEvent>& Events) const
{
	TArray<FAnomalyReport> Reports;

	const UVaultSettings* Settings = UVaultSettings::Get();
	const int32 ThresholdPerMinute = Settings ? Settings->WealthVelocityThresholdPerMinute : 1000;

	// Track currency gains per player in the last 60 seconds
	const FString RecentCutoff = (FDateTime::UtcNow() - FTimespan::FromSeconds(60.0)).ToIso8601();
	TMap<FString, int32> PlayerGains;

	for (const FAuditEvent& Event : Events)
	{
		if (Event.TimestampUTC < RecentCutoff) { continue; }

		// Only track currency-related positive deltas
		// Currency events use DefID like "Currency.Soft.Gold"
		if (Event.QtyDelta > 0 && Event.DefID.Contains(TEXT("Currency")))
		{
			int32& Gains = PlayerGains.FindOrAdd(Event.SourceActorID);
			Gains += Event.QtyDelta;
		}
	}

	for (const auto& Pair : PlayerGains)
	{
		if (Pair.Value > ThresholdPerMinute)
		{
			FAnomalyReport Report;
			Report.Anomaly
Create method · cpp · L18-L37 (20 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
FAuditEvent FAuditEvent::Create(
	FGameplayTag ActionTag,
	const FString& SourceActorID,
	const FString& InstanceUUID,
	const FString& DefID,
	int32 QtyDelta,
	const FString& Context)
{
	FAuditEvent Event;
	Event.TimestampUTC = FDateTime::UtcNow().ToIso8601();
	Event.TransactionID = FGuid::NewGuid().ToString();
	Event.ActionTag = ActionTag;
	Event.SourceActorID = SourceActorID;
	Event.InstanceUUID = InstanceUUID;
	Event.DefID = DefID;
	Event.QtyDelta = QtyDelta;
	Event.Context = Context;
	return Event;
}
ShouldCreateSubsystem method · cpp · L40-L47 (8 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
bool UAuditLogSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
	// Server-only: do not create on clients
	const UWorld* World = Cast<UWorld>(Outer);
	if (!World) return false;
	return World->GetNetMode() != NM_Client;
}
Initialize method · cpp · L48-L65 (18 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
void UAuditLogSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	// Periodic flush every 5 seconds
	if (GetWorld())
	{
		GetWorld()->GetTimerManager().SetTimer(
			FlushTimerHandle,
			this,
			&UAuditLogSubsystem::FlushLogBuffer,
			5.0f,
			true);
	}

	UE_LOG(LogVaultAudit, Display, TEXT("AuditLogSubsystem initialized (server-only)."));
}
Deinitialize method · cpp · L66-L81 (16 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
void UAuditLogSubsystem::Deinitialize()
{
	// Final flush on shutdown
	FlushLogBuffer();

	if (GetWorld())
	{
		GetWorld()->GetTimerManager().ClearTimer(FlushTimerHandle);
	}

	UE_LOG(LogVaultAudit, Display, TEXT("AuditLogSubsystem shut down. Total events: %d"),
		TotalEventsDispatched);

	Super::Deinitialize();
}
DispatchAuditEvent method · cpp · L82-L98 (17 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
void UAuditLogSubsystem::DispatchAuditEvent(const FAuditEvent& Event)
{
	EventBuffer.Add(Event);
	SessionEvents.Add(Event);
	TotalEventsDispatched++;
	OnAuditEventDispatched.Broadcast(Event);

	UE_LOG(LogVaultAudit, Verbose,
		TEXT("Audit: Action=%s, Source=%s, InstanceID=%s, Def=%s, Qty=%+d, Context=%s"),
		*Event.ActionTag.ToString(),
		*Event.SourceActorID,
		*Event.InstanceUUID,
		*Event.DefID,
		Event.QtyDelta,
		*Event.Context);
}
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
FlushLogBuffer method · cpp · L99-L128 (30 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
void UAuditLogSubsystem::FlushLogBuffer()
{
	if (EventBuffer.Num() == 0 && RetryBuffer.Num() == 0) return;

	// Take ownership of the buffer — swap to avoid blocking new events
	TArray<FAuditEvent> EventsToFlush;
	Swap(EventsToFlush, EventBuffer);

	// Prepend any events retained from previous failed dispatch
	if (RetryBuffer.Num() > 0)
	{
		EventsToFlush.Insert(RetryBuffer, 0);
		RetryBuffer.Reset();
	}

	const UVaultSettings* Settings = UVaultSettings::Get();
	const bool bIsCloudMode = Settings &&
		(Settings->StorageProvider == EVaultStorageProvider::Supabase ||
		 Settings->StorageProvider == EVaultStorageProvider::AWS);

	if (bIsCloudMode)
	{
		FlushToCloud(EventsToFlush);
	}
	else
	{
		FlushToLocalFile(EventsToFlush);
	}
}
GetAuditTrailForItem method · cpp · L129-L141 (13 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
TArray<FAuditEvent> UAuditLogSubsystem::GetAuditTrailForItem(const FString& InstanceUUID) const
{
	TArray<FAuditEvent> Result;
	for (const FAuditEvent& Event : SessionEvents)
	{
		if (Event.InstanceUUID == InstanceUUID)
		{
			Result.Add(Event);
		}
	}
	return Result;
}
FlushToLocalFile method · cpp · L142-L183 (42 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
void UAuditLogSubsystem::FlushToLocalFile(const TArray<FAuditEvent>& Events)
{
	// Write to daily rolling log file on a background thread
	// File: <ProjectSavedDir>/Vault/VaultAudit_YYYYMMDD.log
	const FString DateStr = FDateTime::UtcNow().ToString(TEXT("%Y%m%d"));
	const FString LogDir = FPaths::ProjectSavedDir() / TEXT("Vault");
	const FString AuditLogPath = LogDir / FString::Printf(TEXT("VaultAudit_%s.log"), *DateStr);

	// Build the log content as a string
	FString LogContent;
	LogContent.Reserve(Events.Num() * 256);

	for (const FAuditEvent& Event : Events)
	{
		LogContent += FString::Printf(
			TEXT("[%s] TXN=%s CorrID=%s Action=%s Source=%s Target=%s InstanceID=%s Def=%s Qty=%+d Context=%s\n"),
			*Event.TimestampUTC,
			*Event.TransactionID,
			*Event.CorrelationID,
			*Event.ActionTag.ToString(),
			*Event.SourceActorID,
			*Event.TargetActorID,
			*Event.InstanceUUID,
			*Event.DefID,
			Event.QtyDelta,
			*Event.Context);
	}

	// Async file write — never block the game thr
FlushToCloud method · cpp · L184-L200 (17 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
void UAuditLogSubsystem::FlushToCloud(const TArray<FAuditEvent>& Events)
{
	// Cloud dispatch is handled by VaultCoreServer module via delegate (SEC-1).
	// If the delegate is bound, the server module handles Supabase/Kinesis dispatch.
	// If unbound (client build or server module not loaded), fall back to local file.
	if (CloudDispatchDelegate.IsBound())
	{
		CloudDispatchDelegate.Execute(Events);
	}
	else
	{
		UE_LOG(LogVaultAudit, Verbose,
			TEXT("Cloud dispatch delegate not bound — falling back to local file."));
		FlushToLocalFile(Events);
	}
}
EventToJson method · cpp · L201-L216 (16 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
TSharedRef<FJsonObject> UAuditLogSubsystem::EventToJson(const FAuditEvent& Event) const
{
	TSharedRef<FJsonObject> Obj = MakeShared<FJsonObject>();
	Obj->SetStringField(TEXT("timestamp_utc"), Event.TimestampUTC);
	Obj->SetStringField(TEXT("transaction_id"), Event.TransactionID);
	Obj->SetStringField(TEXT("action_tag"), Event.ActionTag.ToString());
	Obj->SetStringField(TEXT("source_actor_id"), Event.SourceActorID);
	Obj->SetStringField(TEXT("target_actor_id"), Event.TargetActorID);
	Obj->SetStringField(TEXT("instance_uuid"), Event.InstanceUUID);
	Obj->SetStringField(TEXT("def_id"), Event.DefID);
	Obj->SetNumberField(TEXT("qty_delta"), Event.QtyDelta);
	Obj->SetStringField(TEXT("context"), Event.Context);
	Obj->SetStringField(TEXT("correlation_id"), Event.CorrelationID);
	return Obj;
}
DispatchToSupabase method · cpp · L221-L228 (8 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
void UAuditLogSubsystem::DispatchToSupabase(const TArray<FAuditEvent>& Events)
{
	// Redirected to CloudDispatchDelegate — see FlushToCloud()
	UE_LOG(LogVaultAudit, Warning,
		TEXT("DispatchToSupabase called directly — use CloudDispatchDelegate instead."));
	FlushToLocalFile(Events);
}
DispatchToKinesis method · cpp · L229-L236 (8 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
void UAuditLogSubsystem::DispatchToKinesis(const TArray<FAuditEvent>& Events)
{
	// Redirected to CloudDispatchDelegate — see FlushToCloud()
	UE_LOG(LogVaultAudit, Warning,
		TEXT("DispatchToKinesis called directly — use CloudDispatchDelegate instead."));
	FlushToLocalFile(Events);
}
QueryAuditEvents method · cpp · L239-L273 (35 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
TArray<FAuditEvent> UAuditLogSubsystem::QueryAuditEvents(
	const FString& ActorID,
	FGameplayTag ActionTag,
	const FString& CorrelationID,
	int32 MaxResults) const
{
	TArray<FAuditEvent> Results;
	const bool bFilterActor = !ActorID.IsEmpty();
	const bool bFilterAction = ActionTag.IsValid();
	const bool bFilterCorrelation = !CorrelationID.IsEmpty();

	// Search session events in reverse order (most recent first)
	for (int32 i = SessionEvents.Num() - 1; i >= 0 && Results.Num() < MaxResults; --i)
	{
		const FAuditEvent& Event = SessionEvents[i];

		if (bFilterActor && Event.SourceActorID != ActorID && Event.TargetActorID != ActorID)
		{
			continue;
		}
		if (bFilterAction && Event.ActionTag != ActionTag)
		{
			continue;
		}
		if (bFilterCorrelation && Event.CorrelationID != CorrelationID)
		{
			continue;
		}

		Results.Add(Event);
	}

	return Results;
}
Repobility · code-quality intelligence · https://repobility.com
QueryLedgerHistory method · cpp · L274-L310 (37 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
TArray<FAuditEvent> UAuditLogSubsystem::QueryLedgerHistory(
	const FString& OwnerID,
	const FString& StartTime,
	const FString& EndTime,
	int32 MaxResults) const
{
	TArray<FAuditEvent> Results;
	const bool bFilterOwner = !OwnerID.IsEmpty();
	const bool bFilterStart = !StartTime.IsEmpty();
	const bool bFilterEnd = !EndTime.IsEmpty();

	for (int32 i = SessionEvents.Num() - 1; i >= 0 && Results.Num() < MaxResults; --i)
	{
		const FAuditEvent& Event = SessionEvents[i];

		// Filter by owner (SourceActorID)
		if (bFilterOwner && Event.SourceActorID != OwnerID)
		{
			continue;
		}

		// Filter by time range (lexicographic ISO-8601 comparison)
		if (bFilterStart && Event.TimestampUTC < StartTime)
		{
			continue;
		}
		if (bFilterEnd && Event.TimestampUTC > EndTime)
		{
			continue;
		}

		Results.Add(Event);
	}

	return Results;
}
ExportAuditEvents method · cpp · L313-L331 (19 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
TArray<FAuditEvent> UAuditLogSubsystem::ExportAuditEvents(const FString& OwnerID) const
{
	TArray<FAuditEvent> Results;
	Results.Reserve(SessionEvents.Num());

	for (const FAuditEvent& Event : SessionEvents)
	{
		if (OwnerID.IsEmpty() || Event.SourceActorID == OwnerID || Event.TargetActorID == OwnerID)
		{
			Results.Add(Event);
		}
	}

	UE_LOG(LogVaultAudit, Display, TEXT("ExportAuditEvents: %d events for OwnerID=%s"),
		Results.Num(), *OwnerID);

	return Results;
}
ExportProfile method · cpp · L332-L361 (30 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
FString UAuditLogSubsystem::ExportProfile(const FString& ProfileID) const
{
	// Build a JSON document containing all data for this profile
	TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
	Root->SetStringField(TEXT("profile_id"), ProfileID);
	Root->SetStringField(TEXT("export_timestamp"), FDateTime::UtcNow().ToIso8601());

	// Export audit trail
	TArray<TSharedPtr<FJsonValue>> EventArray;
	for (const FAuditEvent& Event : SessionEvents)
	{
		if (Event.SourceActorID == ProfileID || Event.TargetActorID == ProfileID)
		{
			EventArray.Add(MakeShared<FJsonValueObject>(EventToJson(Event)));
		}
	}
	Root->SetArrayField(TEXT("audit_trail"), EventArray);
	Root->SetNumberField(TEXT("total_audit_events"), EventArray.Num());

	// Serialize to JSON string
	FString OutputString;
	TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
	FJsonSerializer::Serialize(Root, Writer);

	UE_LOG(LogVaultAudit, Display, TEXT("ExportProfile: ProfileID=%s, %d audit events"),
		
DeleteProfile method · cpp · L362-L406 (45 LOC)
Source/VaultCore/Private/Audit/AuditLogSubsystem.cpp
bool UAuditLogSubsystem::DeleteProfile(const FString& ProfileID)
{
	if (ProfileID.IsEmpty())
	{
		UE_LOG(LogVaultAudit, Warning, TEXT("DeleteProfile: Empty ProfileID — aborted."));
		return false;
	}

	// Remove session events for this profile
	int32 RemovedCount = 0;
	for (int32 i = SessionEvents.Num() - 1; i >= 0; --i)
	{
		if (SessionEvents[i].SourceActorID == ProfileID || SessionEvents[i].TargetActorID == ProfileID)
		{
			SessionEvents.RemoveAt(i);
			++RemovedCount;
		}
	}

	// Also remove from event buffer
	for (int32 i = EventBuffer.Num() - 1; i >= 0; --i)
	{
		if (EventBuffer[i].SourceActorID == ProfileID || EventBuffer[i].TargetActorID == ProfileID)
		{
			EventBuffer.RemoveAt(i);
		}
	}

	// Dispatch a deletion audit event (GDPR compliance trail)
	FAuditEvent DeletionEvent = FAuditEvent::Create(
		VaultTags::Reason_AdminGrant,
		TEXT("System_GDPR"),
		TEXT(""),
		TEXT(""),
		0,
		FString::Printf(TEXT("Profile data deleted: ProfileID=%s, %d events removed"),
			*ProfileID, R
ReconstructStateFromLedger method · cpp · L6-L40 (35 LOC)
Source/VaultCore/Private/Audit/EventSourcingHelpers.cpp
TMap<FString, int32> UEventSourcingHelpers::ReconstructStateFromLedger(
	const TArray<FAuditEvent>& LedgerEvents,
	const FString& OwnerID,
	const FString& UpToTimestamp)
{
	TMap<FString, int32> ItemCounts;

	for (const FAuditEvent& Event : LedgerEvents)
	{
		// Filter by owner
		if (!OwnerID.IsEmpty() && Event.SourceActorID != OwnerID)
		{
			continue;
		}

		// Filter by timestamp (lexicographic comparison works for ISO-8601)
		if (!UpToTimestamp.IsEmpty() && Event.TimestampUTC > UpToTimestamp)
		{
			continue;
		}

		// Skip events without a valid item definition
		if (Event.DefID.IsEmpty())
		{
			continue;
		}

		// Accumulate quantity deltas per item type
		int32& Count = ItemCounts.FindOrAdd(Event.DefID);
		Count += Event.QtyDelta;
	}

	return ItemCounts;
}
ReconcileSnapshotVsLedger method · cpp · L41-L89 (49 LOC)
Source/VaultCore/Private/Audit/EventSourcingHelpers.cpp
TArray<FReconciliationDiscrepancy> UEventSourcingHelpers::ReconcileSnapshotVsLedger(
	const TArray<FInventoryEntry>& SnapshotEntries,
	const TArray<FAuditEvent>& LedgerEvents,
	const FString& OwnerID)
{
	// Reconstruct state from ledger (no timestamp filter = all events)
	TMap<FString, int32> LedgerState = ReconstructStateFromLedger(
		LedgerEvents, OwnerID, TEXT(""));

	// Build snapshot state: aggregate by item def tag
	TMap<FString, int32> SnapshotState;
	for (const FInventoryEntry& Entry : SnapshotEntries)
	{
		if (IsValid(Entry.ItemDef.Get()))
		{
			// Use the item def's name as the tag
			FString DefTag = Entry.ItemDef.Get()->GetName();
			int32& Count = SnapshotState.FindOrAdd(DefTag);
			Count += Entry.StackCount;
		}
	}

	// Compare: find discrepancies
	TArray<FReconciliationDiscrepancy> Discrepancies;

	// Collect all unique item def tags from both sources
	TSet<FString> AllTags;
	for (const auto& Pair : SnapshotState) { AllTags.Add(Pair.Key); }
	for (const auto& Pair : Led
ComputeGlobalItemCounts method · cpp · L90-L96 (7 LOC)
Source/VaultCore/Private/Audit/EventSourcingHelpers.cpp
TMap<FString, int32> UEventSourcingHelpers::ComputeGlobalItemCounts(
	const TArray<FAuditEvent>& LedgerEvents)
{
	// Aggregate across all owners (empty OwnerID = no filter)
	return ReconstructStateFromLedger(LedgerEvents, TEXT(""), TEXT(""));
}
GetLedgerPartitionKey method · cpp · L97-L107 (11 LOC)
Source/VaultCore/Private/Audit/EventSourcingHelpers.cpp
FString UEventSourcingHelpers::GetLedgerPartitionKey(const FAuditEvent& Event)
{
	// Monthly partitioning: extract YYYY-MM from ISO-8601 timestamp
	// Event.TimestampUTC format: "2026-04-10T12:00:00Z"
	if (Event.TimestampUTC.Len() >= 7)
	{
		return Event.TimestampUTC.Left(7); // "2026-04"
	}
	return TEXT("unknown");
}
All rows scored by the Repobility analyzer (https://repobility.com)
ShouldCreateSubsystem method · cpp · L9-L15 (7 LOC)
Source/VaultCore/Private/Audit/GlobalReconciliationSubsystem.cpp
bool UGlobalReconciliationSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
	// Global reconciliation is server-side only.
	// UGameInstanceSubsystem — always create, but only start timer if enabled.
	return true;
}
Initialize method · cpp · L16-L22 (7 LOC)
Source/VaultCore/Private/Audit/GlobalReconciliationSubsystem.cpp
void UGlobalReconciliationSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	UE_LOG(LogVaultRecon, Log, TEXT("GlobalReconciliationSubsystem initialized"));
}
Deinitialize method · cpp · L23-L27 (5 LOC)
Source/VaultCore/Private/Audit/GlobalReconciliationSubsystem.cpp
void UGlobalReconciliationSubsystem::Deinitialize()
{
	Super::Deinitialize();
}
ComputeExpectedItemCounts method · cpp · L28-L33 (6 LOC)
Source/VaultCore/Private/Audit/GlobalReconciliationSubsystem.cpp
TMap<FString, int32> UGlobalReconciliationSubsystem::ComputeExpectedItemCounts(
	const TArray<FAuditEvent>& LedgerEvents) const
{
	return UEventSourcingHelpers::ComputeGlobalItemCounts(LedgerEvents);
}
ComputeActualItemCounts method · cpp · L34-L49 (16 LOC)
Source/VaultCore/Private/Audit/GlobalReconciliationSubsystem.cpp
TMap<FString, int32> UGlobalReconciliationSubsystem::ComputeActualItemCounts(
	const TArray<FInventoryEntry>& SnapshotEntries) const
{
	TMap<FString, int32> Counts;
	for (const FInventoryEntry& Entry : SnapshotEntries)
	{
		if (IsValid(Entry.ItemDef.Get()))
		{
			FString DefTag = Entry.ItemDef.Get()->GetName();
			int32& Count = Counts.FindOrAdd(DefTag);
			Count += Entry.StackCount;
		}
	}
	return Counts;
}
RunReconciliation method · cpp · L50-L98 (49 LOC)
Source/VaultCore/Private/Audit/GlobalReconciliationSubsystem.cpp
FReconciliationReport UGlobalReconciliationSubsystem::RunReconciliation(
	const TArray<FAuditEvent>& LedgerEvents,
	const TArray<FInventoryEntry>& SnapshotEntries)
{
	FReconciliationReport Report;
	Report.TimestampUTC = FDateTime::UtcNow();

	TMap<FString, int32> Expected = ComputeExpectedItemCounts(LedgerEvents);
	TMap<FString, int32> Actual = ComputeActualItemCounts(SnapshotEntries);

	// Collect all item types
	TSet<FString> AllTypes;
	for (const auto& Pair : Expected) { AllTypes.Add(Pair.Key); }
	for (const auto& Pair : Actual) { AllTypes.Add(Pair.Key); }

	Report.TotalItemTypes = AllTypes.Num();

	for (const FString& Type : AllTypes)
	{
		const int32 ExpectedQty = Expected.Contains(Type) ? Expected[Type] : 0;
		const int32 ActualQty = Actual.Contains(Type) ? Actual[Type] : 0;

		Report.TotalItemCount += ActualQty;

		if (ExpectedQty != ActualQty)
		{
			FReconciliationDiscrepancy Disc;
			Disc.ItemDefTag = Type;
			Disc.LedgerQuantity = ExpectedQty;
			Disc.SnapshotQuantity = Act
Super function · cpp · L25-L29 (5 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
	: Super(ObjectInitializer)
{
	SetIsReplicatedByDefault(true);
	bWantsInitializeComponent = true;
}
BeginPlay method · cpp · L30-L56 (27 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
void UInventoryManagerComponent::BeginPlay()
{
	Super::BeginPlay();

	// Set the back-pointer so FFastArray callbacks can fire our delegates
	InventoryArray.OwningComponent = this;

	// Default access policy: owner-only if none assigned in editor
	if (!AccessPolicy.GetObject() && GetOwnerRole() == ROLE_Authority)
	{
		UDefaultOwnerAccessPolicy* DefaultPolicy = NewObject<UDefaultOwnerAccessPolicy>(this);
		AccessPolicy.SetObject(DefaultPolicy);
		AccessPolicy.SetInterface(Cast<IInventoryAccessPolicy>(DefaultPolicy));
	}

	// Cache the validation subsystem — avoids repeated GetSubsystem<>() lookups in every RPC handler
	if (UWorld* World_BP = GetWorld())
	{
		CachedValidation = World_BP->GetSubsystem<UInventoryValidationSubsystem>();
	}

	UE_LOG(LogVault, Display, TEXT("InventoryManagerComponent initialized on %s (Authority=%s, Mode=%d)"),
		*GetOwner()->GetName(),
		GetOwnerRole() == ROLE_Authority ? TEXT("true") : TEXT("false"),
		static_cast<int32>(PersistenceMode));
}
About: code-quality intelligence by Repobility · https://repobility.com
GetLifetimeReplicatedProps method · cpp · L57-L65 (9 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
void UInventoryManagerComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	// COND_OwnerOnly: players cannot sniff other players' inventory data
	DOREPLIFETIME_CONDITION(UInventoryManagerComponent, InventoryArray, COND_OwnerOnly);
	// Wallet is not replicated (TMap not supported). Currency state synced via OnCurrencyChanged delegate.
}
Authority_AddItem method · cpp · L68-L184 (117 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
FGuid UInventoryManagerComponent::Authority_AddItem(
	UItemDefinition* ItemDef,
	int32 Quantity,
	FGameplayTagContainer InitialTags)
{
	if (!ensureMsgf(GetOwnerRole() == ROLE_Authority,
		TEXT("Authority_AddItem called on non-authority")))
	{
		return FGuid();
	}

	if (!IsValid(ItemDef) || Quantity <= 0)
	{
		UE_LOG(LogVault, Warning, TEXT("Authority_AddItem: Invalid ItemDef or Quantity=%d"), Quantity);
		return FGuid();
	}

	// Check if we can stack onto an existing entry
	const UItemFragment_Stackable* StackFrag = ItemDef->FindFragmentByClass<UItemFragment_Stackable>();
	const int32 MaxStack = StackFrag ? StackFrag->MaxStackSize : 1;
	int32 RemainingQty = Quantity;

	// Try to merge into existing stacks first
	if (MaxStack > 1)
	{
		for (FInventoryEntry& Entry : InventoryArray.Entries)
		{
			if (RemainingQty <= 0) break;
			if (Entry.ItemDef != ItemDef) continue;
			if (Entry.StackCount >= MaxStack) continue;

			const int32 Space = MaxStack - Entry.StackCount;
			const int32 ToAdd
Authority_RemoveItem method · cpp · L185-L240 (56 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
bool UInventoryManagerComponent::Authority_RemoveItem(
	FGuid InstanceID,
	int32 Quantity,
	FGameplayTag ReasonContext)
{
	if (!ensureMsgf(GetOwnerRole() == ROLE_Authority,
		TEXT("Authority_RemoveItem called on non-authority")))
	{
		return false;
	}

	const int32 Index = InventoryArray.FindIndexByInstanceID(InstanceID);
	if (Index == INDEX_NONE)
	{
		UE_LOG(LogVault, Warning, TEXT("Authority_RemoveItem: InstanceID=%s not found"),
			*InstanceID.ToString());
		return false;
	}

	FInventoryEntry& Entry = InventoryArray.Entries[Index];

	// Clamp to actual stack count — never trust external quantity
	const int32 SafeQty = FMath::Min(Quantity, Entry.StackCount);

	// Audit: log item removal before mutation
	if (UAuditLogSubsystem* AuditLog = GetWorld() ? GetWorld()->GetSubsystem<UAuditLogSubsystem>() : nullptr)
	{
		FAuditEvent Event = FAuditEvent::Create(
			ReasonContext, ProfileID,
			InstanceID.ToString(),
			IsValid(Entry.ItemDef.Get()) ? Entry.ItemDef->GetName() : TEXT("Unknown"),
Authority_AddCurrency method · cpp · L241-L265 (25 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
void UInventoryManagerComponent::Authority_AddCurrency(FGameplayTag CurrencyType, int32 Amount)
{
	if (!ensureMsgf(GetOwnerRole() == ROLE_Authority,
		TEXT("Authority_AddCurrency called on non-authority")))
	{
		return;
	}

	Wallet.AddCurrency(CurrencyType, Amount);
	OnCurrencyChanged.Broadcast(CurrencyType, Wallet.GetBalance(CurrencyType));
	TriggerAsyncSave();

	if (UAuditLogSubsystem* AuditLog = GetWorld() ? GetWorld()->GetSubsystem<UAuditLogSubsystem>() : nullptr)
	{
		FAuditEvent Event = FAuditEvent::Create(
			VaultTags::Reason_Spawn, ProfileID,
			TEXT(""), CurrencyType.ToString(), Amount,
			FString::Printf(TEXT("AddCurrency %s on %s"), *CurrencyType.ToString(), *GetOwner()->GetName()));
		AuditLog->DispatchAuditEvent(Event);
	}

	UE_LOG(LogVault, Log, TEXT("Authority_AddCurrency: %s += %d (New Balance: %d)"),
		*CurrencyType.ToString(), Amount, Wallet.GetBalance(CurrencyType));
}
Authority_DeductCurrency method · cpp · L266-L298 (33 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
bool UInventoryManagerComponent::Authority_DeductCurrency(FGameplayTag CurrencyType, int32 Amount)
{
	if (!ensureMsgf(GetOwnerRole() == ROLE_Authority,
		TEXT("Authority_DeductCurrency called on non-authority")))
	{
		return false;
	}

	if (!Wallet.DeductCurrency(CurrencyType, Amount))
	{
		UE_LOG(LogVault, Warning, TEXT("Authority_DeductCurrency: Insufficient %s (Have=%d, Need=%d)"),
			*CurrencyType.ToString(), Wallet.GetBalance(CurrencyType), Amount);
		return false;
	}

	OnCurrencyChanged.Broadcast(CurrencyType, Wallet.GetBalance(CurrencyType));
	TriggerAsyncSave();

	if (UAuditLogSubsystem* AuditLog = GetWorld() ? GetWorld()->GetSubsystem<UAuditLogSubsystem>() : nullptr)
	{
		FAuditEvent Event = FAuditEvent::Create(
			VaultTags::Reason_Consume, ProfileID,
			TEXT(""), CurrencyType.ToString(), -Amount,
			FString::Printf(TEXT("DeductCurrency %s on %s"), *CurrencyType.ToString(), *GetOwner()->GetName()));
		AuditLog->DispatchAuditEvent(Event);
	}

	UE_LOG(LogVault, Log, TEXT("Auth
Authority_BulkRemoveItems method · cpp · L301-L398 (98 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
TArray<FGameplayTag> UInventoryManagerComponent::Authority_BulkRemoveItems(
	const TArray<FGuid>& InstanceIDs,
	FGameplayTag ReasonContext)
{
	TArray<FGameplayTag> Results;
	Results.SetNum(InstanceIDs.Num());

	if (!ensureMsgf(GetOwnerRole() == ROLE_Authority,
		TEXT("Authority_BulkRemoveItems called on non-authority")))
	{
		for (FGameplayTag& Tag : Results)
		{
			Tag = VaultTags::Validation_Error_ServerNotReady;
		}
		return Results;
	}

	// Phase 1: Identify valid items and record expected removal count
	struct FPendingRemoval
	{
		FGuid InstanceID;
		int32 StackCount;
	};
	TArray<FPendingRemoval> Pending;
	Pending.Reserve(InstanceIDs.Num());
	int32 ExpectedTotalRemoved = 0;

	for (int32 i = 0; i < InstanceIDs.Num(); ++i)
	{
		const FInventoryEntry* Entry = InventoryArray.FindByInstanceID(InstanceIDs[i]);
		if (!Entry)
		{
			Results[i] = VaultTags::Validation_Error_ItemNotFound;
			UE_LOG(LogVault, Warning, TEXT("Authority_BulkRemoveItems: InstanceID=%s not found"),
				*Instance
Server_RequestMoveItem_Validate method · cpp · L403-L407 (5 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
bool UInventoryManagerComponent::Server_RequestMoveItem_Validate(int32 SourceIndex, int32 TargetIndex)
{
	return ValidateRPCPreconditions();
}
Server_RequestMoveItem_Implementation method · cpp · L408-L435 (28 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
void UInventoryManagerComponent::Server_RequestMoveItem_Implementation(int32 SourceIndex, int32 TargetIndex)
{
	// Route through validation subsystem — ensures index bounds, ownership, locking, expiration
	UInventoryValidationSubsystem* Validation = CachedValidation.Get();
	if (!Validation)
	{
		UE_LOG(LogVaultValidation, Warning, TEXT("MoveItem: Validation subsystem unavailable"));
		return;
	}

	// Derive the requesting player from the pawn that owns this component
	APawn* OwnerPawn = Cast<APawn>(GetOwner());
	APlayerController* PC = OwnerPawn ? Cast<APlayerController>(OwnerPawn->GetController()) : nullptr;

	// Both source and target are within the same inventory for a move/swap
	const FGameplayTag Result = Validation->ValidateMoveIntent(PC, this, this, SourceIndex, TargetIndex);
	if (Result != VaultTags::Validation_Success)
	{
		UE_LOG(LogVaultValidation, Warning, TEXT("MoveItem: Validation failed — %s (Source=%d, Target=%d)"),
			*Result.ToString(), SourceIndex, TargetIndex);
		r
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
Server_RequestDropItem_Validate method · cpp · L436-L440 (5 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
bool UInventoryManagerComponent::Server_RequestDropItem_Validate(FGuid InstanceID, int32 DropQuantity)
{
	return ValidateRPCPreconditions() && InstanceID.IsValid() && DropQuantity > 0;
}
Server_RequestDropItem_Implementation method · cpp · L441-L500 (60 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
void UInventoryManagerComponent::Server_RequestDropItem_Implementation(FGuid InstanceID, int32 DropQuantity)
{
	// Validate existence, ownership, binding, locking, and expiration before allowing drop
	UInventoryValidationSubsystem* Validation = CachedValidation.Get();
	if (!Validation)
	{
		UE_LOG(LogVaultValidation, Warning, TEXT("DropItem: Validation subsystem unavailable"));
		return;
	}

	APawn* OwnerPawn = Cast<APawn>(GetOwner());
	APlayerController* PC = OwnerPawn ? Cast<APlayerController>(OwnerPawn->GetController()) : nullptr;

	const FGameplayTag Result = Validation->ValidateItemOperation(PC, this, InstanceID);
	if (Result != VaultTags::Validation_Success)
	{
		UE_LOG(LogVaultValidation, Warning, TEXT("DropItem: Validation failed — %s (InstanceID=%s)"),
			*Result.ToString(), *InstanceID.ToString());
		return;
	}

	// Capture item data before removal (Authority_RemoveItem may invalidate the entry)
	const FInventoryEntry* DropEntry = FindEntryByID(InstanceID);
	UItemDefinition*
Server_RequestConsumeItem_Validate method · cpp · L501-L505 (5 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
bool UInventoryManagerComponent::Server_RequestConsumeItem_Validate(int32 ItemIndex)
{
	return ValidateRPCPreconditions();
}
Server_RequestConsumeItem_Implementation method · cpp · L506-L552 (47 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
void UInventoryManagerComponent::Server_RequestConsumeItem_Implementation(int32 ItemIndex)
{
	// Validate index bounds, ownership, player state, locking, and expiration
	UInventoryValidationSubsystem* Validation = CachedValidation.Get();
	if (!Validation)
	{
		UE_LOG(LogVaultValidation, Warning, TEXT("ConsumeItem: Validation subsystem unavailable"));
		return;
	}

	APawn* OwnerPawn = Cast<APawn>(GetOwner());
	APlayerController* PC = OwnerPawn ? Cast<APlayerController>(OwnerPawn->GetController()) : nullptr;

	// Check index bounds first since ConsumeItem uses an index, not an InstanceID
	FGameplayTag Result = Validation->CheckIndexBounds(this, ItemIndex);
	if (Result != VaultTags::Validation_Success)
	{
		UE_LOG(LogVaultValidation, Warning, TEXT("ConsumeItem: Validation failed — %s (Index=%d)"),
			*Result.ToString(), ItemIndex);
		return;
	}

	// Now validate the specific item via InstanceID for full ownership/binding/expiration checks
	const FGuid& InstanceID = InventoryArray.Entries
Server_RequestEquipItem_Validate method · cpp · L553-L557 (5 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
bool UInventoryManagerComponent::Server_RequestEquipItem_Validate(FGuid InstanceID)
{
	return ValidateRPCPreconditions() && InstanceID.IsValid();
}
Server_RequestEquipItem_Implementation method · cpp · L558-L602 (45 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
void UInventoryManagerComponent::Server_RequestEquipItem_Implementation(FGuid InstanceID)
{
	// Validate existence, ownership, player state, locking, and expiration before equipping
	UInventoryValidationSubsystem* Validation = CachedValidation.Get();
	if (!Validation)
	{
		UE_LOG(LogVaultValidation, Warning, TEXT("EquipItem: Validation subsystem unavailable"));
		return;
	}

	APawn* OwnerPawn = Cast<APawn>(GetOwner());
	APlayerController* PC = OwnerPawn ? Cast<APlayerController>(OwnerPawn->GetController()) : nullptr;

	const FGameplayTag Result = Validation->ValidateItemOperation(PC, this, InstanceID);
	if (Result != VaultTags::Validation_Success)
	{
		UE_LOG(LogVaultValidation, Warning, TEXT("EquipItem: Validation failed — %s (InstanceID=%s)"),
			*Result.ToString(), *InstanceID.ToString());
		return;
	}

	FInventoryEntry* Entry = InventoryArray.FindByInstanceID(InstanceID);
	if (!Entry) return;

	// Trigger Bind-on-Equip — once bound, item cannot be traded/dropped/mailed
	if (Entry-
Server_RequestUnequipItem_Validate method · cpp · L603-L607 (5 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
bool UInventoryManagerComponent::Server_RequestUnequipItem_Validate(FGuid InstanceID)
{
	return ValidateRPCPreconditions() && InstanceID.IsValid();
}
Server_RequestUnequipItem_Implementation method · cpp · L608-L636 (29 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
void UInventoryManagerComponent::Server_RequestUnequipItem_Implementation(FGuid InstanceID)
{
	// Validate existence, ownership, player state, and locking before unequipping
	UInventoryValidationSubsystem* Validation = CachedValidation.Get();
	if (!Validation)
	{
		UE_LOG(LogVaultValidation, Warning, TEXT("UnequipItem: Validation subsystem unavailable"));
		return;
	}

	APawn* OwnerPawn = Cast<APawn>(GetOwner());
	APlayerController* PC = OwnerPawn ? Cast<APlayerController>(OwnerPawn->GetController()) : nullptr;

	const FGameplayTag Result = Validation->ValidateItemOperation(PC, this, InstanceID);
	if (Result != VaultTags::Validation_Success)
	{
		UE_LOG(LogVaultValidation, Warning, TEXT("UnequipItem: Validation failed — %s (InstanceID=%s)"),
			*Result.ToString(), *InstanceID.ToString());
		return;
	}

	FInventoryEntry* Entry = InventoryArray.FindByInstanceID(InstanceID);
	if (!Entry) return;

	Entry->DynamicStateTags.RemoveTag(VaultTags::State_Equipped);
	InventoryArray.MarkItemDirty
Repobility · code-quality intelligence · https://repobility.com
Server_RequestPickupItem_Validate method · cpp · L639-L643 (5 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
bool UInventoryManagerComponent::Server_RequestPickupItem_Validate(AActor* WorldItem)
{
	return ValidateRPCPreconditions() && IsValid(WorldItem);
}
Server_RequestPickupItem_Implementation method · cpp · L644-L688 (45 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
void UInventoryManagerComponent::Server_RequestPickupItem_Implementation(AActor* WorldItem)
{
	AVaultWorldItemActor* VaultItem = Cast<AVaultWorldItemActor>(WorldItem);
	if (!VaultItem || !IsValid(VaultItem->ItemDef.Get()))
	{
		UE_LOG(LogVault, Warning, TEXT("PickupItem: Invalid world item or missing ItemDef"));
		return;
	}

	UInventoryValidationSubsystem* Validation = CachedValidation.Get();
	if (Validation)
	{
		// Distance check: player must be close enough
		FGameplayTag DistResult = Validation->CheckDistance(GetOwner(), VaultItem);
		if (DistResult != VaultTags::Validation_Success)
		{
			UE_LOG(LogVaultValidation, Warning, TEXT("PickupItem: Too far from world item"));
			return;
		}

		// Capacity check: inventory must have room
		FGameplayTag CapResult = Validation->CheckSlotCapacity(this);
		if (CapResult != VaultTags::Validation_Success)
		{
			UE_LOG(LogVaultValidation, Warning, TEXT("PickupItem: Inventory full"));
			return;
		}

		// Access policy check: actor must have d
Server_RequestOpenContainer_Validate method · cpp · L691-L695 (5 LOC)
Source/VaultCore/Private/Component/InventoryManagerComponent.cpp
bool UInventoryManagerComponent::Server_RequestOpenContainer_Validate(AActor* Container)
{
	return ValidateRPCPreconditions() && IsValid(Container);
}
‹ prevpage 2 / 10next ›