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>
159 lines
6.0 KiB
C++
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)
|