feat(jobs): novo módulo ZMMOJobs (peer de ZMMOAttributes) + DA_Job_Novice
Sub-módulo cliente do JobsSystem do server. Pavimenta dados visuais/UI por job (DisplayName localizado, ícone, anim override, som de level up) sem acoplar ao ZMMOAttributes. ARQUITETURA — espelha o peer-module pattern do server (PR #9): Server: Game/MMO/Modules/JobsSystem (módulo núcleo, LoadOrder=50) Client: Source/ZMMOJobs/ (LoadingPhase=PreDefault, peer de ZMMOAttributes) ZMMOAttributes NÃO depende de ZMMOJobs — server envia classId no snapshot, cliente faz lookup local via Library/Subsystem. NOVOS ARQUIVOS C++ module (Source/ZMMOJobs/): - module.json + ZMMOJobs.Build.cs (deps: Core/Engine/AssetRegistry) - ZMMOJobsModule.h/.cpp — IMPLEMENT_MODULE - ZMMOJobDataAsset.h/.cpp — UPrimaryDataAsset (BlueprintType) campos: ClassId, TechnicalName, DisplayName, JobIcon (soft), DefaultAnimInstanceClass, JobChangeMontage (soft), LevelUpSoundCue (soft) - ZMMOJobsSubsystem.h/.cpp — UGameInstanceSubsystem scan AssetRegistry no Initialize + cache TMap<int32, DA*> por ClassId O(1) GetJobData(ClassId) - ZMMOJobsLibrary.h/.cpp — UBlueprintFunctionLibrary GetJobDisplayName/GetJobData/IsJobRegistered fallback "Classe N" se ClassId desconhecido Content: - Content/ZMMO/Data/Jobs/DA_Job_Novice.uasset (ClassId=0, TechnicalName="Novice", DisplayName="Aprendiz") MODIFICADOS - ZMMO.uproject: + Module "ZMMOJobs" (LoadingPhase PreDefault) - Source/ZMMO/ZMMO.Build.cs: + "ZMMOJobs" em PublicDependencyModuleNames - Source/ZMMO/Game/UI/FrontEnd/UICharCard_Base.cpp:51 — primeiro consumer do Library: "Classe %d" hardcoded → UZMMOJobsLibrary::GetJobDisplayName(this, ClassId) Card da seleção de personagem agora mostra "Aprendiz" via DA lookup. BLUEPRINT ACCESS Toda a API exposta pra BP via UFUNCTION(BlueprintPure): - UZMMOJobsLibrary::GetJobDisplayName(Self, ClassId) → FText - UZMMOJobsLibrary::GetJobData(Self, ClassId) → UZMMOJobDataAsset* - UZMMOJobsSubsystem::GetJobData(ClassId) - UZMMOJobDataAsset propriedades BlueprintReadOnly (UMG binding direto) LOOKUP AssetRegistry varre /Game/ por UZMMOJobDataAsset no boot do GameInstance. Chave = campo ClassId do asset (não nome do arquivo — renomear .uasset OK). Lookup O(1) via TMap. Custo de scan amortizado em 1 vez por sessão (~50ms pra 50 jobs futuro). VALIDAÇÃO Editor: [Log] DA class found: <class 'ZMMOJobDataAsset'> [Log] Subsystem class found: <class 'ZMMOJobsSubsystem'> [Log] Library class found: <class 'ZMMOJobsLibrary'> [Log] AssetRegistry encontrou 1 ZMMOJobDataAsset(s): - DA_Job_Novice @ /Game/ZMMO/Data/Jobs/DA_Job_Novice Próximo módulo cliente: 1 linha no .uproject + .Build.cs + ZMMOJobs.Build.cs não muda. Pattern estabelecido. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
5
Source/ZMMOJobs/Private/ZMMOJobDataAsset.cpp
Normal file
5
Source/ZMMOJobs/Private/ZMMOJobDataAsset.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "ZMMOJobDataAsset.h"
|
||||
|
||||
// Implementacao vazia — todas as props sao UPROPERTY EditDefaultsOnly e
|
||||
// vivem inline no .uasset. Override de GetPrimaryAssetId() esta no header
|
||||
// (constexpr-friendly).
|
||||
54
Source/ZMMOJobs/Private/ZMMOJobsLibrary.cpp
Normal file
54
Source/ZMMOJobs/Private/ZMMOJobsLibrary.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "ZMMOJobsLibrary.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/World.h"
|
||||
#include "ZMMOJobDataAsset.h"
|
||||
#include "ZMMOJobsSubsystem.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
/// Resolve Subsystem via WorldContextObject. Retorna nullptr em contextos
|
||||
/// invalidos (CDO em editor, world em destruicao).
|
||||
UZMMOJobsSubsystem* ResolveSubsystem(const UObject* WorldContextObject)
|
||||
{
|
||||
if (WorldContextObject == nullptr) { return nullptr; }
|
||||
const UWorld* World = WorldContextObject->GetWorld();
|
||||
if (World == nullptr) { return nullptr; }
|
||||
UGameInstance* GI = World->GetGameInstance();
|
||||
if (GI == nullptr) { return nullptr; }
|
||||
return GI->GetSubsystem<UZMMOJobsSubsystem>();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
FText UZMMOJobsLibrary::GetJobDisplayName(const UObject* WorldContextObject, int32 ClassId)
|
||||
{
|
||||
if (UZMMOJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
|
||||
{
|
||||
if (UZMMOJobDataAsset* Job = Sub->GetJobData(ClassId))
|
||||
{
|
||||
return Job->DisplayName.IsEmpty()
|
||||
? FText::FromString(Job->TechnicalName)
|
||||
: Job->DisplayName;
|
||||
}
|
||||
}
|
||||
// Fallback: nao crasha UI se ClassId desconhecido.
|
||||
return FText::FromString(FString::Printf(TEXT("Classe %d"), ClassId));
|
||||
}
|
||||
|
||||
UZMMOJobDataAsset* UZMMOJobsLibrary::GetJobData(const UObject* WorldContextObject, int32 ClassId)
|
||||
{
|
||||
if (UZMMOJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
|
||||
{
|
||||
return Sub->GetJobData(ClassId);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UZMMOJobsLibrary::IsJobRegistered(const UObject* WorldContextObject, int32 ClassId)
|
||||
{
|
||||
if (UZMMOJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
|
||||
{
|
||||
return Sub->GetJobData(ClassId) != nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
13
Source/ZMMOJobs/Private/ZMMOJobsModule.cpp
Normal file
13
Source/ZMMOJobs/Private/ZMMOJobsModule.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "ZMMOJobsModule.h"
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
void FZMMOJobsModule::StartupModule()
|
||||
{
|
||||
}
|
||||
|
||||
void FZMMOJobsModule::ShutdownModule()
|
||||
{
|
||||
}
|
||||
|
||||
IMPLEMENT_MODULE(FZMMOJobsModule, ZMMOJobs)
|
||||
67
Source/ZMMOJobs/Private/ZMMOJobsSubsystem.cpp
Normal file
67
Source/ZMMOJobs/Private/ZMMOJobsSubsystem.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "ZMMOJobsSubsystem.h"
|
||||
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "ZMMOJobDataAsset.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogZMMOJobs, Log, All);
|
||||
|
||||
void UZMMOJobsSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
LoadJobsFromAssetRegistry();
|
||||
UE_LOG(LogZMMOJobs, Log, TEXT("[ZMMOJobsSubsystem] Initialize: %d job(s) carregados"), CachedJobs.Num());
|
||||
}
|
||||
|
||||
void UZMMOJobsSubsystem::Deinitialize()
|
||||
{
|
||||
CachedJobs.Empty();
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
void UZMMOJobsSubsystem::LoadJobsFromAssetRegistry()
|
||||
{
|
||||
FAssetRegistryModule& AssetRegistryModule =
|
||||
FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
||||
IAssetRegistry& AR = AssetRegistryModule.Get();
|
||||
|
||||
// Editor pode chamar antes de scan inicial. Standalone/PIE scan ja' rodou.
|
||||
// SearchAllAssets(false) = nao-blocking; assume cache populado em runtime.
|
||||
#if WITH_EDITOR
|
||||
AR.SearchAllAssets(true); // bloqueia editor; ok em Initialize
|
||||
#endif
|
||||
|
||||
TArray<FAssetData> Assets;
|
||||
const FTopLevelAssetPath ClassPath(UZMMOJobDataAsset::StaticClass()->GetClassPathName());
|
||||
AR.GetAssetsByClass(ClassPath, Assets, /*bSearchSubClasses*/ true);
|
||||
|
||||
for (const FAssetData& AssetData : Assets)
|
||||
{
|
||||
UZMMOJobDataAsset* Job = Cast<UZMMOJobDataAsset>(AssetData.GetAsset());
|
||||
if (Job == nullptr) { continue; }
|
||||
|
||||
if (CachedJobs.Contains(Job->ClassId))
|
||||
{
|
||||
UE_LOG(LogZMMOJobs, Warning,
|
||||
TEXT("[ZMMOJobsSubsystem] Duplicate ClassId=%d (existing=%s, new=%s) — ignorando segundo"),
|
||||
Job->ClassId,
|
||||
*CachedJobs[Job->ClassId]->GetName(),
|
||||
*Job->GetName());
|
||||
continue;
|
||||
}
|
||||
|
||||
CachedJobs.Add(Job->ClassId, Job);
|
||||
UE_LOG(LogZMMOJobs, Verbose,
|
||||
TEXT("[ZMMOJobsSubsystem] Cached classId=%d name=%s display=%s"),
|
||||
Job->ClassId, *Job->TechnicalName, *Job->DisplayName.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
UZMMOJobDataAsset* UZMMOJobsSubsystem::GetJobData(int32 ClassId) const
|
||||
{
|
||||
if (const TObjectPtr<UZMMOJobDataAsset>* Found = CachedJobs.Find(ClassId))
|
||||
{
|
||||
return Found->Get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
Reference in New Issue
Block a user