Initial commit: Item Interaction Ecosystem plugin (UE5.7)
Server-authoritative, data-driven item/inventory/interaction/carry/equipment/ world-slot/placement/crafting framework. 8 modules, compiles against UE 5.7. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
25
Source/ItemDebug/ItemDebug.Build.cs
Normal file
25
Source/ItemDebug/ItemDebug.Build.cs
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class ItemDebug : ModuleRules
|
||||
{
|
||||
public ItemDebug(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
IWYUSupport = IWYUSupport.Full;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"GameplayTags",
|
||||
"ItemCore",
|
||||
"ItemDatabase",
|
||||
"ItemInventory",
|
||||
"ItemInteraction",
|
||||
"ItemWorld"
|
||||
});
|
||||
}
|
||||
}
|
||||
252
Source/ItemDebug/Private/ItemDebugModule.cpp
Normal file
252
Source/ItemDebug/Private/ItemDebugModule.cpp
Normal file
@ -0,0 +1,252 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "ItemDebugLog.h"
|
||||
#include "ItemDatabaseSubsystem.h"
|
||||
#include "ItemDefinitionRow.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "ItemFeatureLibrary.h"
|
||||
#include "ItemCraftingLibrary.h"
|
||||
#include "PickupItemActor.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/Engine.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogItemDebug);
|
||||
|
||||
// Implemented as the Item Debugger console toolkit (section 29).
|
||||
namespace ItemDebugCmd
|
||||
{
|
||||
static APawn* GetPlayerPawn(UWorld* World)
|
||||
{
|
||||
APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||||
return PC ? PC->GetPawn() : nullptr;
|
||||
}
|
||||
|
||||
static UInventoryComponent* GetPlayerInventory(UWorld* World)
|
||||
{
|
||||
APawn* Pawn = GetPlayerPawn(World);
|
||||
return Pawn ? Pawn->FindComponentByClass<UInventoryComponent>() : nullptr;
|
||||
}
|
||||
|
||||
static void Screen(const FString& Msg, const FColor Color = FColor::Green)
|
||||
{
|
||||
UE_LOG(LogItemDebug, Log, TEXT("%s"), *Msg);
|
||||
if (GEngine)
|
||||
{
|
||||
GEngine->AddOnScreenDebugMessage(-1, 4.f, Color, Msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void Give(const TArray<FString>& Args, UWorld* World)
|
||||
{
|
||||
if (Args.Num() < 1) { Screen(TEXT("Usage: ItemEco.Give <ItemId> [Qty]"), FColor::Yellow); return; }
|
||||
UInventoryComponent* Inv = GetPlayerInventory(World);
|
||||
UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(World);
|
||||
if (!Inv || !DB) { Screen(TEXT("No player inventory / database."), FColor::Red); return; }
|
||||
|
||||
const FName Id(*Args[0]);
|
||||
const int32 Qty = Args.Num() > 1 ? FMath::Max(1, FCString::Atoi(*Args[1])) : 1;
|
||||
if (!DB->HasItem(Id)) { Screen(FString::Printf(TEXT("Unknown item '%s'"), *Id.ToString()), FColor::Red); return; }
|
||||
|
||||
FItemInstanceData Item = DB->MakeItemInstance(Id, Qty);
|
||||
Inv->AddItem(Item);
|
||||
Screen(FString::Printf(TEXT("[Inventory] gave %s x%d"), *Id.ToString(), Qty));
|
||||
}
|
||||
|
||||
static void Spawn(const TArray<FString>& Args, UWorld* World)
|
||||
{
|
||||
if (Args.Num() < 1) { Screen(TEXT("Usage: ItemEco.Spawn <ItemId> [Qty]"), FColor::Yellow); return; }
|
||||
APawn* Pawn = GetPlayerPawn(World);
|
||||
if (!Pawn) { Screen(TEXT("No player pawn."), FColor::Red); return; }
|
||||
|
||||
const FName Id(*Args[0]);
|
||||
const int32 Qty = Args.Num() > 1 ? FMath::Max(1, FCString::Atoi(*Args[1])) : 1;
|
||||
const FTransform Xf(Pawn->GetActorRotation(), Pawn->GetActorLocation() + Pawn->GetActorForwardVector() * 200.f + FVector(0, 0, 50.f));
|
||||
if (APickupItemActor::SpawnPickup(World, Id, Qty, Xf))
|
||||
{
|
||||
Screen(FString::Printf(TEXT("Spawned pickup %s x%d"), *Id.ToString(), Qty));
|
||||
}
|
||||
else
|
||||
{
|
||||
Screen(FString::Printf(TEXT("Failed to spawn '%s'"), *Id.ToString()), FColor::Red);
|
||||
}
|
||||
}
|
||||
|
||||
static void Clear(const TArray<FString>&, UWorld* World)
|
||||
{
|
||||
UInventoryComponent* Inv = GetPlayerInventory(World);
|
||||
if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
|
||||
TArray<FItemInstanceData> Items;
|
||||
Inv->GetAllItems(Items);
|
||||
for (const FItemInstanceData& It : Items) { Inv->RemoveItem(It.InstanceId); }
|
||||
Screen(FString::Printf(TEXT("Cleared %d items."), Items.Num()));
|
||||
}
|
||||
|
||||
static void Dump(const TArray<FString>&, UWorld* World)
|
||||
{
|
||||
UInventoryComponent* Inv = GetPlayerInventory(World);
|
||||
if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
|
||||
TArray<FItemInstanceData> Items;
|
||||
Inv->GetAllItems(Items);
|
||||
Screen(FString::Printf(TEXT("--- Inventory (%d entries) ---"), Items.Num()), FColor::Cyan);
|
||||
for (const FItemInstanceData& It : Items)
|
||||
{
|
||||
UE_LOG(LogItemDebug, Log, TEXT(" %s x%d [container %s slot %d]"),
|
||||
*It.ItemId.ToString(), It.Quantity, *It.CurrentContainerId.ToString(), It.SlotIndex);
|
||||
}
|
||||
}
|
||||
|
||||
static void Validate(const TArray<FString>&, UWorld* World)
|
||||
{
|
||||
UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(World);
|
||||
if (!DB) { Screen(TEXT("No database."), FColor::Red); return; }
|
||||
TArray<FItemValidationIssue> Issues;
|
||||
DB->ValidateData(Issues);
|
||||
Screen(FString::Printf(TEXT("Validation: %d issue(s)."), Issues.Num()), Issues.Num() ? FColor::Yellow : FColor::Green);
|
||||
for (const FItemValidationIssue& Issue : Issues)
|
||||
{
|
||||
UE_LOG(LogItemDebug, Warning, TEXT(" [%s] %s: %s"),
|
||||
Issue.bIsError ? TEXT("ERROR") : TEXT("WARN"), *Issue.ItemId.ToString(), *Issue.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// Mutates the first inventory item via the feature library.
|
||||
static void MutateFirst(UWorld* World, TFunctionRef<void(FItemInstanceData&)> Mutator, const TCHAR* What)
|
||||
{
|
||||
UInventoryComponent* Inv = GetPlayerInventory(World);
|
||||
if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
|
||||
TArray<FItemInstanceData> Items;
|
||||
Inv->GetAllItems(Items);
|
||||
if (Items.Num() == 0) { Screen(TEXT("Inventory empty."), FColor::Yellow); return; }
|
||||
FItemInstanceData Item = Items[0];
|
||||
Mutator(Item);
|
||||
Inv->UpdateItem(Item);
|
||||
Screen(FString::Printf(TEXT("%s %s"), What, *Item.ItemId.ToString()));
|
||||
}
|
||||
|
||||
static void Rot(const TArray<FString>&, UWorld* World)
|
||||
{
|
||||
UInventoryComponent* Inv = GetPlayerInventory(World);
|
||||
if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
|
||||
TArray<FItemInstanceData> Items;
|
||||
Inv->GetAllItems(Items);
|
||||
if (Items.Num() == 0) { Screen(TEXT("Inventory empty."), FColor::Yellow); return; }
|
||||
FItemInstanceData Item = Items[0];
|
||||
FName TransformId;
|
||||
UItemFeatureLibrary::ForceRot(World, Item, TransformId);
|
||||
if (!TransformId.IsNone())
|
||||
{
|
||||
UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(World);
|
||||
FItemInstanceData NewItem = DB ? DB->MakeItemInstance(TransformId, Item.Quantity) : FItemInstanceData(TransformId, Item.Quantity);
|
||||
Inv->RemoveItem(Item.InstanceId);
|
||||
Inv->AddItem(NewItem);
|
||||
Screen(FString::Printf(TEXT("Rotted -> %s"), *TransformId.ToString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
Inv->UpdateItem(Item);
|
||||
Screen(FString::Printf(TEXT("Rotted %s"), *Item.ItemId.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
static void Break(const TArray<FString>&, UWorld* World)
|
||||
{
|
||||
MutateFirst(World, [](FItemInstanceData& It) { UItemFeatureLibrary::BreakItem(It); }, TEXT("Broke"));
|
||||
}
|
||||
|
||||
static void DrainBattery(const TArray<FString>& Args, UWorld* World)
|
||||
{
|
||||
const float Amt = Args.Num() > 0 ? FCString::Atof(*Args[0]) : 0.25f;
|
||||
MutateFirst(World, [Amt](FItemInstanceData& It) { UItemFeatureLibrary::DrainBattery(It, Amt, 1.f); }, TEXT("Drained battery of"));
|
||||
}
|
||||
|
||||
static void AddFuel(const TArray<FString>& Args, UWorld* World)
|
||||
{
|
||||
const float Amt = Args.Num() > 0 ? FCString::Atof(*Args[0]) : 5.f;
|
||||
MutateFirst(World, [Amt](FItemInstanceData& It) { UItemFeatureLibrary::AddFuel(It, Amt, 9999.f); }, TEXT("Added fuel to"));
|
||||
}
|
||||
|
||||
static void UnlockAll(const TArray<FString>&, UWorld* World)
|
||||
{
|
||||
UInventoryComponent* Inv = GetPlayerInventory(World);
|
||||
if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
|
||||
TArray<FItemInstanceData> Items;
|
||||
Inv->GetAllItems(Items);
|
||||
int32 Count = 0;
|
||||
for (const FItemInstanceData& It : Items) { if (Inv->ClearItemLock(It.InstanceId)) { ++Count; } }
|
||||
Screen(FString::Printf(TEXT("Unlocked %d items."), Count));
|
||||
}
|
||||
|
||||
static void RollLoot(const TArray<FString>& Args, UWorld* World)
|
||||
{
|
||||
if (Args.Num() < 1) { Screen(TEXT("Usage: ItemEco.RollLoot <TableId> [Rolls]"), FColor::Yellow); return; }
|
||||
UInventoryComponent* Inv = GetPlayerInventory(World);
|
||||
if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
|
||||
const int32 Rolls = Args.Num() > 1 ? FMath::Max(1, FCString::Atoi(*Args[1])) : 5;
|
||||
UItemCraftingLibrary::RollLootIntoInventory(World, Inv, FName(*Args[0]), Rolls);
|
||||
Screen(FString::Printf(TEXT("Rolled loot '%s' x%d"), *Args[0], Rolls));
|
||||
}
|
||||
|
||||
static void Craft(const TArray<FString>& Args, UWorld* World)
|
||||
{
|
||||
if (Args.Num() < 1) { Screen(TEXT("Usage: ItemEco.Craft <RecipeId>"), FColor::Yellow); return; }
|
||||
UInventoryComponent* Inv = GetPlayerInventory(World);
|
||||
if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
|
||||
const bool bOk = UItemCraftingLibrary::Craft(World, Inv, FName(*Args[0]), FGameplayTag());
|
||||
Screen(FString::Printf(TEXT("Craft '%s': %s"), *Args[0], bOk ? TEXT("OK") : TEXT("FAILED")), bOk ? FColor::Green : FColor::Red);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Console command registration ---
|
||||
|
||||
static FAutoConsoleCommandWithWorldAndArgs GCmdGive(
|
||||
TEXT("ItemEco.Give"), TEXT("Give <ItemId> [Qty] to the local player inventory."),
|
||||
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Give));
|
||||
|
||||
static FAutoConsoleCommandWithWorldAndArgs GCmdSpawn(
|
||||
TEXT("ItemEco.Spawn"), TEXT("Spawn <ItemId> [Qty] as a world pickup in front of the player."),
|
||||
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Spawn));
|
||||
|
||||
static FAutoConsoleCommandWithWorldAndArgs GCmdClear(
|
||||
TEXT("ItemEco.Clear"), TEXT("Clear the local player inventory."),
|
||||
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Clear));
|
||||
|
||||
static FAutoConsoleCommandWithWorldAndArgs GCmdDump(
|
||||
TEXT("ItemEco.Dump"), TEXT("Dump the local player inventory to the log."),
|
||||
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Dump));
|
||||
|
||||
static FAutoConsoleCommandWithWorldAndArgs GCmdValidate(
|
||||
TEXT("ItemEco.Validate"), TEXT("Run data validation on the item database."),
|
||||
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Validate));
|
||||
|
||||
static FAutoConsoleCommandWithWorldAndArgs GCmdRot(
|
||||
TEXT("ItemEco.Rot"), TEXT("Force-rot the first inventory item."),
|
||||
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Rot));
|
||||
|
||||
static FAutoConsoleCommandWithWorldAndArgs GCmdBreak(
|
||||
TEXT("ItemEco.Break"), TEXT("Break the first inventory item."),
|
||||
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Break));
|
||||
|
||||
static FAutoConsoleCommandWithWorldAndArgs GCmdDrain(
|
||||
TEXT("ItemEco.DrainBattery"), TEXT("DrainBattery [Amount] from the first item."),
|
||||
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::DrainBattery));
|
||||
|
||||
static FAutoConsoleCommandWithWorldAndArgs GCmdAddFuel(
|
||||
TEXT("ItemEco.AddFuel"), TEXT("AddFuel [Amount] to the first item."),
|
||||
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::AddFuel));
|
||||
|
||||
static FAutoConsoleCommandWithWorldAndArgs GCmdUnlock(
|
||||
TEXT("ItemEco.UnlockAll"), TEXT("Clear all item locks in the player inventory."),
|
||||
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::UnlockAll));
|
||||
|
||||
static FAutoConsoleCommandWithWorldAndArgs GCmdRollLoot(
|
||||
TEXT("ItemEco.RollLoot"), TEXT("RollLoot <TableId> [Rolls] into the player inventory."),
|
||||
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::RollLoot));
|
||||
|
||||
static FAutoConsoleCommandWithWorldAndArgs GCmdCraft(
|
||||
TEXT("ItemEco.Craft"), TEXT("Craft <RecipeId> using the player inventory."),
|
||||
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Craft));
|
||||
|
||||
IMPLEMENT_MODULE(FDefaultModuleImpl, ItemDebug)
|
||||
83
Source/ItemDebug/Private/ItemDebugSubsystem.cpp
Normal file
83
Source/ItemDebug/Private/ItemDebugSubsystem.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#include "ItemDebugSubsystem.h"
|
||||
#include "InteractionTraceComponent.h"
|
||||
#include "InteractionResolver.h"
|
||||
#include "InteractionTypes.h"
|
||||
#include "HeldItemComponent.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Engine/Engine.h"
|
||||
|
||||
static TAutoConsoleVariable<int32> CVarItemDebugHUD(
|
||||
TEXT("ItemEco.DebugHUD"), 0,
|
||||
TEXT("Draw the Item Ecosystem on-screen debugger (held item / target / interactions)."),
|
||||
ECVF_Cheat);
|
||||
|
||||
void UItemDebugSubsystem::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
if (CVarItemDebugHUD.GetValueOnGameThread() <= 0 || !GEngine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||||
APawn* Pawn = PC ? PC->GetPawn() : nullptr;
|
||||
if (!Pawn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int32 Key = 9000;
|
||||
auto Line = [&Key](const FString& Text, const FColor Color = FColor::White)
|
||||
{
|
||||
GEngine->AddOnScreenDebugMessage(Key++, 0.f, Color, Text);
|
||||
};
|
||||
|
||||
Line(TEXT("== Item Ecosystem Debug =="), FColor::Cyan);
|
||||
|
||||
if (const UHeldItemComponent* Held = Pawn->FindComponentByClass<UHeldItemComponent>())
|
||||
{
|
||||
FItemInstanceData HeldItem;
|
||||
if (Held->GetHeldItem(HeldItem))
|
||||
{
|
||||
Line(FString::Printf(TEXT("Held: %s"), *HeldItem.ItemId.ToString()), FColor::Yellow);
|
||||
}
|
||||
else
|
||||
{
|
||||
Line(TEXT("Held: -"));
|
||||
}
|
||||
}
|
||||
|
||||
AActor* Target = nullptr;
|
||||
if (const UInteractionTraceComponent* Trace = Pawn->FindComponentByClass<UInteractionTraceComponent>())
|
||||
{
|
||||
Target = Trace->CurrentTarget;
|
||||
}
|
||||
Line(FString::Printf(TEXT("Target: %s"), *GetNameSafe(Target)), FColor::Green);
|
||||
|
||||
if (Target)
|
||||
{
|
||||
TArray<FInteractionOption> Options;
|
||||
UInteractionResolver::GetAvailableInteractions(Pawn, Target, Options);
|
||||
for (const FInteractionOption& Opt : Options)
|
||||
{
|
||||
Line(FString::Printf(TEXT(" [%d] %s%s"),
|
||||
Opt.Priority, *Opt.DisplayText.ToString(),
|
||||
Opt.bIsEnabled ? TEXT("") : TEXT(" (disabled)")),
|
||||
Opt.bIsEnabled ? FColor::White : FColor::Silver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TStatId UItemDebugSubsystem::GetStatId() const
|
||||
{
|
||||
RETURN_QUICK_DECLARE_CYCLE_STAT(UItemDebugSubsystem, STATGROUP_Tickables);
|
||||
}
|
||||
|
||||
bool UItemDebugSubsystem::DoesSupportWorldType(const EWorldType::Type WorldType) const
|
||||
{
|
||||
return WorldType == EWorldType::Game || WorldType == EWorldType::PIE;
|
||||
}
|
||||
8
Source/ItemDebug/Public/ItemDebugLog.h
Normal file
8
Source/ItemDebug/Public/ItemDebugLog.h
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
|
||||
ITEMDEBUG_API DECLARE_LOG_CATEGORY_EXTERN(LogItemDebug, Log, All);
|
||||
24
Source/ItemDebug/Public/ItemDebugSubsystem.h
Normal file
24
Source/ItemDebug/Public/ItemDebugSubsystem.h
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/WorldSubsystem.h"
|
||||
#include "ItemDebugSubsystem.generated.h"
|
||||
|
||||
/**
|
||||
* On-screen runtime debugger (section 29). Toggle with the console variable
|
||||
* `ItemEco.DebugHUD 1`. Draws the local player's held item, current interaction
|
||||
* target and the available interactions each frame. Read-only / client-local.
|
||||
*/
|
||||
UCLASS()
|
||||
class ITEMDEBUG_API UItemDebugSubsystem : public UTickableWorldSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
//~ FTickableGameObject
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
virtual TStatId GetStatId() const override;
|
||||
virtual bool DoesSupportWorldType(const EWorldType::Type WorldType) const override;
|
||||
};
|
||||
Reference in New Issue
Block a user