Files
inventory-system-plugin/Source/ItemDeveloper/Private/ItemDeveloperModule.cpp
Bonchellon 7f7e043a88 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>
2026-06-22 20:49:56 +03:00

159 lines
6.0 KiB
C++

// Copyright ExByte Studios. Item Interaction Ecosystem.
#include "ItemDeveloperModule.h"
#include "ItemEcosystemSettings.h"
#include "ItemDefinitionRow.h"
#include "Engine/DataTable.h"
#include "ToolMenus.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
DEFINE_LOG_CATEGORY_STATIC(LogItemDeveloper, Log, All);
#define LOCTEXT_NAMESPACE "ItemDeveloper"
void FItemDeveloperModule::StartupModule()
{
UToolMenus::RegisterStartupCallback(
FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FItemDeveloperModule::RegisterMenus));
}
void FItemDeveloperModule::ShutdownModule()
{
UToolMenus::UnRegisterStartupCallback(this);
UToolMenus::UnregisterOwner(this);
}
void FItemDeveloperModule::RegisterMenus()
{
FToolMenuOwnerScoped OwnerScoped(this);
UToolMenu* ToolsMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Tools");
if (!ToolsMenu)
{
return;
}
FToolMenuSection& Section = ToolsMenu->AddSection("ItemEcosystem", LOCTEXT("ItemEcosystemSection", "Item Ecosystem"));
Section.AddMenuEntry(
"ValidateItemData",
LOCTEXT("ValidateItemData", "Validate Item Data"),
LOCTEXT("ValidateItemDataTip", "Run data validation over DT_Items (section 28)."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateStatic(&FItemDeveloperModule::ValidateAllItems)));
Section.AddMenuEntry(
"ExportItemsCsv",
LOCTEXT("ExportItemsCsv", "Export DT_Items to CSV"),
LOCTEXT("ExportItemsCsvTip", "Write DT_Items to Saved/ItemEcosystem/DT_Items.csv (section 27)."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateStatic(&FItemDeveloperModule::ExportItemsToCsv)));
Section.AddMenuEntry(
"ReportMissingAssets",
LOCTEXT("ReportMissingAssets", "Report: Items Missing Icon/Mesh"),
LOCTEXT("ReportMissingAssetsTip", "List items with no Icon or WorldMesh (section 27)."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateStatic(&FItemDeveloperModule::ReportMissingAssets)));
}
void FItemDeveloperModule::ExportItemsToCsv()
{
const UItemEcosystemSettings* Settings = UItemEcosystemSettings::Get();
UDataTable* ItemsTable = Settings ? Settings->ItemsTable.LoadSynchronous() : nullptr;
if (!ItemsTable)
{
FNotificationInfo Info(LOCTEXT("NoItemsTable", "DT_Items is not assigned."));
Info.ExpireDuration = 5.f;
FSlateNotificationManager::Get().AddNotification(Info);
return;
}
const FString Csv = ItemsTable->GetTableAsCSV();
const FString Path = FPaths::ProjectSavedDir() / TEXT("ItemEcosystem") / TEXT("DT_Items.csv");
const bool bOk = FFileHelper::SaveStringToFile(Csv, *Path);
const FText Msg = bOk
? FText::Format(LOCTEXT("ExportOk", "Exported DT_Items to {0}"), FText::FromString(Path))
: LOCTEXT("ExportFail", "Failed to write CSV.");
FNotificationInfo Info(Msg);
Info.ExpireDuration = 6.f;
FSlateNotificationManager::Get().AddNotification(Info);
UE_LOG(LogItemDeveloper, Log, TEXT("CSV export: %s -> %s"), bOk ? TEXT("OK") : TEXT("FAIL"), *Path);
}
void FItemDeveloperModule::ReportMissingAssets()
{
const UItemEcosystemSettings* Settings = UItemEcosystemSettings::Get();
UDataTable* ItemsTable = Settings ? Settings->ItemsTable.LoadSynchronous() : nullptr;
if (!ItemsTable)
{
return;
}
int32 Missing = 0;
TArray<FItemDefinitionRow*> Rows;
ItemsTable->GetAllRows<FItemDefinitionRow>(TEXT("ItemDeveloper.ReportMissing"), Rows);
for (const FItemDefinitionRow* Row : Rows)
{
if (!Row) { continue; }
if (Row->Icon.IsNull()) { ++Missing; UE_LOG(LogItemDeveloper, Warning, TEXT("%s: no Icon"), *Row->ItemId.ToString()); }
if (Row->WorldMesh.IsNull()) { ++Missing; UE_LOG(LogItemDeveloper, Warning, TEXT("%s: no WorldMesh"), *Row->ItemId.ToString()); }
}
FNotificationInfo Info(FText::Format(LOCTEXT("MissingResult", "Asset report: {0} missing reference(s). See Output Log."), FText::AsNumber(Missing)));
Info.ExpireDuration = 6.f;
FSlateNotificationManager::Get().AddNotification(Info);
}
void FItemDeveloperModule::ValidateAllItems()
{
const UItemEcosystemSettings* Settings = UItemEcosystemSettings::Get();
UDataTable* ItemsTable = Settings ? Settings->ItemsTable.LoadSynchronous() : nullptr;
int32 Errors = 0;
int32 Warnings = 0;
auto Report = [&](const FName& Id, const FString& Msg, bool bError)
{
bError ? ++Errors : ++Warnings;
UE_LOG(LogItemDeveloper, Warning, TEXT("[%s] %s: %s"), bError ? TEXT("ERROR") : TEXT("WARN"), *Id.ToString(), *Msg);
};
if (!ItemsTable)
{
UE_LOG(LogItemDeveloper, Error, TEXT("DT_Items is not assigned in Project Settings -> Item Ecosystem."));
}
else
{
TArray<FItemDefinitionRow*> Rows;
ItemsTable->GetAllRows<FItemDefinitionRow>(TEXT("ItemDeveloper.Validate"), Rows);
for (const FItemDefinitionRow* Row : Rows)
{
if (!Row) { continue; }
const FName Id = Row->ItemId;
if (Id.IsNone()) { Report(Id, TEXT("ItemId is empty."), true); }
if (Row->MaxStack <= 0) { Report(Id, TEXT("MaxStack <= 0."), true); }
if (Row->GridSize.X <= 0 || Row->GridSize.Y <= 0) { Report(Id, TEXT("GridSize must be >= 1."), true); }
if (Row->bCanInstantPickup && !Row->bCanStoreInInventory) { Report(Id, TEXT("InstantPickup but not storable."), true); }
if (Row->bCanEquip && Row->EquippedActorClass.IsNull()) { Report(Id, TEXT("CanEquip but no EquippedActorClass."), true); }
if (Row->bCanWorldCarry && Row->WorldMesh.IsNull()) { Report(Id, TEXT("CanWorldCarry but no WorldMesh."), true); }
if (Row->bCanInstallIntoWorldSlot && Row->CompatibleWorldSlots.IsEmpty()) { Report(Id, TEXT("CanInstall but no CompatibleWorldSlots."), true); }
if (Row->bIsQuestCritical && Row->bCanDrop) { Report(Id, TEXT("QuestCritical item is droppable."), false); }
}
UE_LOG(LogItemDeveloper, Log, TEXT("Validated %d item rows."), Rows.Num());
}
const FText Summary = FText::Format(
LOCTEXT("ValidateResult", "Item validation: {0} error(s), {1} warning(s). See Output Log."),
FText::AsNumber(Errors), FText::AsNumber(Warnings));
FNotificationInfo Info(Summary);
Info.ExpireDuration = 6.f;
FSlateNotificationManager::Get().AddNotification(Info);
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FItemDeveloperModule, ItemDeveloper)