refactor: prefixo ZMMO -> Zeus em classes/módulos C++ do cliente

- Renomeia ~50 classes UCLASS/USTRUCT/UENUM/UINTERFACE pra Zeus*
  (AZeusGameMode, AZeusCharacter, UZeusGameInstance, IZeusEntityInterface,
  UZeusWorldSubsystem, FZeusEntitySnapshot, etc.)
- Encurta AZMMOPlayerCharacter -> AZeusCharacter
- Renomeia módulos satélite ZMMOAttributes -> ZeusAttributes e
  ZMMOJobs -> ZeusJobs (pasta + .Build.cs + .uproject)
- Mantém módulo principal ZMMO (renomeia depois quando jogo ganhar nome)
- DefaultEngine.ini: ActiveClassRedirects ZMMOX -> ZeusX (preserva BPs/assets)
- DefaultGame.ini: sections atualizadas pra ZeusThemeSubsystem/ZeusPlayerState
- Category="ZMMO|..." -> "Zeus|..." em UPROPERTYs
- Preserva LogZMMO, ZMMO_API, /Script/ZMMO., /Game/ZMMO/, classes Target

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 17:24:38 -03:00
parent bd1dab25ca
commit 6a6a28a086
128 changed files with 1224 additions and 1210 deletions

View File

@@ -0,0 +1,67 @@
#include "ZeusJobChangeNetworkHandler.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "ZeusNetworkSubsystem.h"
DEFINE_LOG_CATEGORY_STATIC(LogZeusJobChange, Log, All);
bool UZeusJobChangeNetworkHandler::ShouldCreateSubsystem(UObject* Outer) const
{
// Mesmo criterio do AttributeNetworkHandler — so' em mundos de gameplay
// (PIE / standalone in-game). Editor preview / front-end menu nao precisa.
const UWorld* World = Cast<UWorld>(Outer);
if (World == nullptr) { return false; }
return World->WorldType == EWorldType::Game
|| World->WorldType == EWorldType::PIE;
}
void UZeusJobChangeNetworkHandler::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
if (UZeusNetworkSubsystem* Net = GetZeusNetSubsystem())
{
JobChangeResultHandle = Net->OnJobChangeResult.AddUObject(
this, &UZeusJobChangeNetworkHandler::HandleJobChangeResult);
UE_LOG(LogZeusJobChange, Log,
TEXT("[ZeusJobChangeNetworkHandler] Bind OnJobChangeResult OK"));
}
else
{
UE_LOG(LogZeusJobChange, Warning,
TEXT("[ZeusJobChangeNetworkHandler] ZeusNetworkSubsystem indisponivel — bind pulado"));
}
}
void UZeusJobChangeNetworkHandler::Deinitialize()
{
if (UZeusNetworkSubsystem* Net = GetZeusNetSubsystem())
{
if (JobChangeResultHandle.IsValid())
{
Net->OnJobChangeResult.Remove(JobChangeResultHandle);
JobChangeResultHandle.Reset();
}
}
OnJobChangeResult.Clear();
Super::Deinitialize();
}
void UZeusJobChangeNetworkHandler::HandleJobChangeResult(
bool bAccepted, int32 Reason, int32 NewClassId)
{
UE_LOG(LogZeusJobChange, Log,
TEXT("[ZeusJobChangeNetworkHandler] S_JOB_CHANGE_RESULT accepted=%d reason=%d newClassId=%d"),
bAccepted ? 1 : 0, Reason, NewClassId);
OnJobChangeResult.Broadcast(bAccepted, Reason, NewClassId);
}
UZeusNetworkSubsystem* UZeusJobChangeNetworkHandler::GetZeusNetSubsystem() const
{
const UWorld* World = GetWorld();
if (World == nullptr) { return nullptr; }
UGameInstance* GI = World->GetGameInstance();
if (GI == nullptr) { return nullptr; }
return GI->GetSubsystem<UZeusNetworkSubsystem>();
}

View File

@@ -0,0 +1,5 @@
#include "ZeusJobDataAsset.h"
// Implementacao vazia — todas as props sao UPROPERTY EditDefaultsOnly e
// vivem inline no .uasset. Override de GetPrimaryAssetId() esta no header
// (constexpr-friendly).

View File

@@ -0,0 +1,89 @@
#include "ZeusJobsLibrary.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "ZeusJobDataAsset.h"
#include "ZeusJobsSubsystem.h"
#include "ZeusNetworkSubsystem.h"
namespace
{
/// Resolve Subsystem via WorldContextObject. Retorna nullptr em contextos
/// invalidos (CDO em editor, world em destruicao).
UZeusJobsSubsystem* 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<UZeusJobsSubsystem>();
}
} // namespace
FText UZeusJobsLibrary::GetJobDisplayName(const UObject* WorldContextObject, int32 ClassId)
{
if (UZeusJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
{
if (UZeusJobDataAsset* 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));
}
UZeusJobDataAsset* UZeusJobsLibrary::GetJobData(const UObject* WorldContextObject, int32 ClassId)
{
if (UZeusJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
{
return Sub->GetJobData(ClassId);
}
return nullptr;
}
bool UZeusJobsLibrary::IsJobRegistered(const UObject* WorldContextObject, int32 ClassId)
{
if (UZeusJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
{
return Sub->GetJobData(ClassId) != nullptr;
}
return false;
}
TArray<UZeusJobDataAsset*> UZeusJobsLibrary::GetJobsByTier(
const UObject* WorldContextObject, EZeusJobTier Tier)
{
if (UZeusJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
{
return Sub->GetJobsByTier(Tier);
}
return {};
}
TArray<UZeusJobDataAsset*> UZeusJobsLibrary::GetEligibleNextJobs(
const UObject* WorldContextObject, int32 CurrentClassId)
{
if (UZeusJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
{
return Sub->GetEligibleNextJobs(CurrentClassId);
}
return {};
}
void UZeusJobsLibrary::SendJobChangeRequest(
const UObject* WorldContextObject, int32 TargetClassId)
{
if (WorldContextObject == nullptr) { return; }
const UWorld* World = WorldContextObject->GetWorld();
if (World == nullptr) { return; }
UGameInstance* GI = World->GetGameInstance();
if (GI == nullptr) { return; }
if (UZeusNetworkSubsystem* Net = GI->GetSubsystem<UZeusNetworkSubsystem>())
{
Net->SendJobChangeRequest(TargetClassId);
}
}

View File

@@ -0,0 +1,13 @@
#include "ZeusJobsModule.h"
#include "Modules/ModuleManager.h"
void FZeusJobsModule::StartupModule()
{
}
void FZeusJobsModule::ShutdownModule()
{
}
IMPLEMENT_MODULE(FZeusJobsModule, ZeusJobs)

View File

@@ -0,0 +1,96 @@
#include "ZeusJobsSubsystem.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Modules/ModuleManager.h"
#include "ZeusJobDataAsset.h"
DEFINE_LOG_CATEGORY_STATIC(LogZeusJobs, Log, All);
void UZeusJobsSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
LoadJobsFromAssetRegistry();
UE_LOG(LogZeusJobs, Log, TEXT("[ZeusJobsSubsystem] Initialize: %d job(s) carregados"), CachedJobs.Num());
}
void UZeusJobsSubsystem::Deinitialize()
{
CachedJobs.Empty();
Super::Deinitialize();
}
void UZeusJobsSubsystem::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(UZeusJobDataAsset::StaticClass()->GetClassPathName());
AR.GetAssetsByClass(ClassPath, Assets, /*bSearchSubClasses*/ true);
for (const FAssetData& AssetData : Assets)
{
UZeusJobDataAsset* Job = Cast<UZeusJobDataAsset>(AssetData.GetAsset());
if (Job == nullptr) { continue; }
if (CachedJobs.Contains(Job->ClassId))
{
UE_LOG(LogZeusJobs, Warning,
TEXT("[ZeusJobsSubsystem] 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(LogZeusJobs, Verbose,
TEXT("[ZeusJobsSubsystem] Cached classId=%d name=%s display=%s"),
Job->ClassId, *Job->TechnicalName, *Job->DisplayName.ToString());
}
}
UZeusJobDataAsset* UZeusJobsSubsystem::GetJobData(int32 ClassId) const
{
if (const TObjectPtr<UZeusJobDataAsset>* Found = CachedJobs.Find(ClassId))
{
return Found->Get();
}
return nullptr;
}
TArray<UZeusJobDataAsset*> UZeusJobsSubsystem::GetJobsByTier(EZeusJobTier Tier) const
{
TArray<UZeusJobDataAsset*> Out;
Out.Reserve(CachedJobs.Num());
for (const TPair<int32, TObjectPtr<UZeusJobDataAsset>>& Pair : CachedJobs)
{
if (Pair.Value && Pair.Value->Tier == Tier)
{
Out.Add(Pair.Value.Get());
}
}
return Out;
}
TArray<UZeusJobDataAsset*> UZeusJobsSubsystem::GetEligibleNextJobs(int32 CurrentClassId) const
{
TArray<UZeusJobDataAsset*> Out;
Out.Reserve(4); // tipico: 2-6 destinos
for (const TPair<int32, TObjectPtr<UZeusJobDataAsset>>& Pair : CachedJobs)
{
if (Pair.Value && Pair.Value->ParentClassId == CurrentClassId
&& Pair.Value->ClassId != CurrentClassId)
{
Out.Add(Pair.Value.Get());
}
}
return Out;
}

View File

@@ -0,0 +1,54 @@
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/WorldSubsystem.h"
#include "ZeusJobChangeNetworkHandler.generated.h"
class UZeusNetworkSubsystem;
/**
* Multicast dinamico assignable em Blueprint. WBP de promocao binda no
* OnJobChangeResult pra reagir ao S_JOB_CHANGE_RESULT (1520) do server.
*
* Parametros:
* - bAccepted : true = promocao aplicada (S_ATTRIBUTE_SNAPSHOT_FULL
* chega logo apos com state novo).
* - Reason : EJobChangeRejectReason (None/NoSession/UnknownJob/
* NotParent/WrongTier/JobLevelTooLow/AlreadyAtTarget/
* JobsDbUnavailable). Significativo apenas se !bAccepted.
* - NewClassId : significativo apenas se bAccepted=true (redundante com
* snapshot que vem junto, mas util pra triggers/toasts).
*/
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FZeusOnJobChangeResultBP,
bool, bAccepted, int32, Reason, int32, NewClassId);
/**
* Bridge entre o `UZeusNetworkSubsystem` (do plugin ZeusNetwork) e a UI
* Blueprint (WBP de promocao). Mesmo pattern do `UZeusAttributeNetworkHandler`
* — converte o delegate C++-only `FZeusOnJobChangeResult` num dynamic
* multicast (`FZeusOnJobChangeResultBP`) assignavel em BP.
*
* Por que UWorldSubsystem? Player so' faz promocao quando ja' esta no
* WorldServer (com mundo carregado). No front-end / lobby, esse subsystem
* nao existe — economiza memoria.
*/
UCLASS()
class ZEUSJOBS_API UZeusJobChangeNetworkHandler : public UWorldSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
/// Assignavel em BP: WBP_JobChangePanel.OnEvent_OnJobChangeResult.
UPROPERTY(BlueprintAssignable, Category = "Zeus|Jobs")
FZeusOnJobChangeResultBP OnJobChangeResult;
private:
void HandleJobChangeResult(bool bAccepted, int32 Reason, int32 NewClassId);
UZeusNetworkSubsystem* GetZeusNetSubsystem() const;
FDelegateHandle JobChangeResultHandle;
};

View File

@@ -0,0 +1,93 @@
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "ZeusJobTypes.h"
#include "ZeusJobDataAsset.generated.h"
class UTexture2D;
class USoundBase;
class UAnimInstance;
class UAnimMontage;
/**
* Dados visuais/UI de UM job (classe). 1 .uasset por job em
* `Content/ZMMO/Data/Jobs/DA_Job_<Nome>.uasset`.
*
* ClassId eh a CHAVE — bate com `classId` do snapshot do server (rathena
* pc.hpp `job_class`, ex: Novice=0, Swordsman=1, Mage=2, ...).
*
* Soft pointers (TSoftObjectPtr) sao usados pra assets pesados (textures,
* sounds) — lazy-loaded quando precisar (level up, hover icon). DataAsset
* em si fica em memoria depois do scan do Subsystem.
*
* Async pointers (TSubclassOf) sao sync — anim BP precisa estar carregada
* quando o player spawna.
*/
UCLASS(BlueprintType)
class ZEUSJOBS_API UZeusJobDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
/// Chave estavel (bate com `Config/Jobs/<Name>.json:Id` no server e com
/// `FZeusAttributesSnapshot::ClassId` recebido do server).
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
int32 ClassId = 0;
/// Nome tecnico estavel (en-US). Ex: "Novice", "Swordsman". Aparece em
/// logs e telemetria — NUNCA exibido pro player.
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
FString TechnicalName;
/// Nome localizado exibido na UI (pt-BR no projeto base). Ex: "Novato".
/// Pode usar FText Localization tables pra multi-idioma futuro.
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
FText DisplayName;
/// Tier hierarquico — espelha `JobDef::tier` do server. UI usa pra agrupar
/// jobs (ex: lista de Vocacoes possiveis quando promovendo Novato).
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
EZeusJobTier Tier = EZeusJobTier::Beginner;
/// ClassId do parent na arvore de promocao. 0 = sem parent (Novato eh root).
/// Espelha `JobDef::parentJobId` do server. UI usa pra filtrar destinos
/// possiveis ao mostrar painel de promocao.
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
int32 ParentClassId = 0;
/// Lista de armas que o job pode equipar. Espelha `JobDef::allowedWeaponsMask`
/// do server (mas como TArray pra editor visual em vez de bitmask cru).
/// Inventory UI usa pra hint "nao pode equipar" + filtro de catalogo.
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
TArray<EZeusWeaponType> AllowedWeapons;
/// Icone do job — usado em StatusWindow, CharacterCard, JobChangeUI.
/// Soft ptr: nao carrega ate alguem chamar LoadSynchronous() ou async load.
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job|Assets")
TSoftObjectPtr<UTexture2D> JobIcon;
/// AnimInstance class override (opcional). Quando nao-null, AnimBP
/// padrao do PlayerCharacter eh substituido por esta classe no spawn
/// (jobs com move set diferente — Monge bate, Assassin furtivo, etc.).
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job|Anim")
TSubclassOf<UAnimInstance> DefaultAnimInstanceClass;
/// Montage de "job change" (animacao de transformacao). Tocada quando
/// player promove de Novice → 1a classe. V1 nao implementado.
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job|Anim")
TSoftObjectPtr<UAnimMontage> JobChangeMontage;
/// Som de level up override. Quando null, HUD usa som generico do projeto.
/// Soft ptr: lazy load no primeiro level up.
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job|Audio")
TSoftObjectPtr<USoundBase> LevelUpSoundCue;
/// PrimaryAssetType pro AssetManager (precisa pra GetPrimaryAssetIdList).
/// Hardcoded "ZeusJob" — registry classifica todos os DAs deste tipo.
virtual FPrimaryAssetId GetPrimaryAssetId() const override
{
return FPrimaryAssetId(FPrimaryAssetType(TEXT("ZeusJob")),
*FString::Printf(TEXT("Job_%d_%s"), ClassId, *TechnicalName));
}
};

View File

@@ -0,0 +1,57 @@
#pragma once
#include "CoreMinimal.h"
#include "ZeusJobTypes.generated.h"
/**
* Tier hierarquico de um job. Espelha `EJobTier` do server em
* `Server/.../JobsSystem/Public/JobTypes.hpp`. Valores TEM que bater
* byte-a-byte com a enum do server porque o snapshot pode carregar
* tier como uint8 no futuro.
*
* Renascer (RO pre-renewal): char no Tier 2 (Specialization) que cumpre
* condicao volta pra Tier 3 (RebornBeginner) com bonus permanente,
* abrindo path paralelo RebornVocation → Mastery (coroa final).
*/
UENUM(BlueprintType)
enum class EZeusJobTier : uint8
{
Beginner = 0 UMETA(DisplayName = "Iniciante"),
Vocation = 1 UMETA(DisplayName = "Vocacao"),
Specialization = 2 UMETA(DisplayName = "Especializacao"),
RebornBeginner = 3 UMETA(DisplayName = "Iniciante Renascido"),
RebornVocation = 4 UMETA(DisplayName = "Vocacao Renascida"),
Mastery = 5 UMETA(DisplayName = "Mestria"),
};
/**
* Tipos de arma. Espelha `EWeaponType` do server. Usado em `AllowedWeapons`
* do DA pra UI (filtrar items equipaveis) + validacao server-side futura
* quando InventorySystem entrar.
*
* Ordem TEM que bater com server porque o bitmask `allowedWeaponsMask` no
* JobDef usa bit N = EWeaponType(N).
*/
UENUM(BlueprintType)
enum class EZeusWeaponType : uint8
{
Fist = 0 UMETA(DisplayName = "Punho"),
Dagger = 1 UMETA(DisplayName = "Adaga"),
Sword1H = 2 UMETA(DisplayName = "Espada 1M"),
Sword2H = 3 UMETA(DisplayName = "Espada 2M"),
Spear1H = 4 UMETA(DisplayName = "Lanca 1M"),
Spear2H = 5 UMETA(DisplayName = "Lanca 2M"),
Axe1H = 6 UMETA(DisplayName = "Machado 1M"),
Axe2H = 7 UMETA(DisplayName = "Machado 2M"),
Mace1H = 8 UMETA(DisplayName = "Maca 1M"),
Mace2H = 9 UMETA(DisplayName = "Maca 2M"),
Staff1H = 10 UMETA(DisplayName = "Cajado 1M"),
Staff2H = 11 UMETA(DisplayName = "Cajado 2M"),
Bow = 12 UMETA(DisplayName = "Arco"),
Knuckle = 13 UMETA(DisplayName = "Soqueira"),
Musical = 14 UMETA(DisplayName = "Instrumento Musical"),
Whip = 15 UMETA(DisplayName = "Chicote"),
Book = 16 UMETA(DisplayName = "Livro"),
Katar = 17 UMETA(DisplayName = "Katar"),
Gun = 18 UMETA(DisplayName = "Arma de Fogo"),
};

View File

@@ -0,0 +1,62 @@
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "ZeusJobTypes.h"
#include "ZeusJobsLibrary.generated.h"
class UZeusJobDataAsset;
/**
* Helpers Blueprint pra UI/HUD acessar dados de job sem precisar pegar o
* Subsystem manualmente. Todos usam WorldContextObject pra resolver
* GameInstance → Subsystem internamente.
*
* Padrao de uso em UMG:
* - Text binding: `JobsLibrary::GetJobDisplayName(Self, AttrComp.Current.ClassId)`
* - Image binding: `JobsLibrary::GetJobIcon(Self, ClassId).LoadSynchronous()`
*/
UCLASS()
class ZEUSJOBS_API UZeusJobsLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/// Nome localizado do job. Fallback "Classe N" se ClassId nao tem DA
/// correspondente (job nao registrado no Content/ZMMO/Data/Jobs/).
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
meta = (WorldContext = "WorldContextObject"))
static FText GetJobDisplayName(const UObject* WorldContextObject, int32 ClassId);
/// DataAsset completo (pode ser nullptr — caller checa).
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
meta = (WorldContext = "WorldContextObject"))
static UZeusJobDataAsset* GetJobData(const UObject* WorldContextObject, int32 ClassId);
/// Helper de UI: retorna true se ClassId tem DA registrado.
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
meta = (WorldContext = "WorldContextObject"))
static bool IsJobRegistered(const UObject* WorldContextObject, int32 ClassId);
/// Lista jobs num tier (UI dropdown). Vazio se subsystem indisponivel.
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
meta = (WorldContext = "WorldContextObject"))
static TArray<UZeusJobDataAsset*> GetJobsByTier(
const UObject* WorldContextObject, EZeusJobTier Tier);
/// Destinos de promocao a partir do char atual. WBP de promocao consome
/// direto. Vazio se nao houver filhos (ex: char ja' esta em Tier 3
/// Mastery — Jobs.4 introduzira terminal de progressao).
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
meta = (WorldContext = "WorldContextObject"))
static TArray<UZeusJobDataAsset*> GetEligibleNextJobs(
const UObject* WorldContextObject, int32 CurrentClassId);
/// Helper de send via ZeusNetworkSubsystem. Conveniencia pra WBP nao
/// precisar pegar o subsystem manualmente. Idempotente: rejeitado
/// silenciosamente se nao conectado.
UFUNCTION(BlueprintCallable, Category = "Zeus|Jobs",
meta = (WorldContext = "WorldContextObject"))
static void SendJobChangeRequest(
const UObject* WorldContextObject, int32 TargetClassId);
};

View File

@@ -0,0 +1,23 @@
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleInterface.h"
/**
* Sub-modulo cliente do JobsSystem.
*
* Espelho de `Server/ZeusServerEngine/Game/MMO/Modules/JobsSystem/`. Roda
* como modulo Runtime em LoadingPhase PreDefault para que o
* `UZeusJobsSubsystem` esteja registrado antes do ZMMO core spawnar a HUD.
*
* Responsabilidades:
* - Dados visuais por job: DisplayName localizado, JobIcon, anim override
* - Lookup classId → UZeusJobDataAsset via Subsystem
* - Library Blueprint pra HUD/UI consumir
*/
class FZeusJobsModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View File

@@ -0,0 +1,69 @@
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "ZeusJobTypes.h"
#include "ZeusJobsSubsystem.generated.h"
class UZeusJobDataAsset;
/**
* Cache central de UZeusJobDataAsset por ClassId. Vive como GameInstance
* Subsystem — startup automatico quando GameInstance inicializa (PIE,
* standalone, packaged). Sobrevive a map transitions.
*
* Initialize:
* 1. Carrega via AssetRegistry: scan de UZeusJobDataAsset em todo Content
* 2. Cacheia em TMap<int32, UZeusJobDataAsset*> por ClassId
* 3. Log: quantos jobs encontrou
*
* Uso:
* - C++: GameInstance->GetSubsystem<UZeusJobsSubsystem>()->GetJobData(0)
* - BP: UZeusJobsLibrary::GetJobData(WorldContext, 0)
*
* Fase 7+ (Mod support): SCANEAR PrimaryAssetType="ZeusJob" do AssetManager
* permite mods adicionarem DAs proprios sem mexer no source.
*/
UCLASS()
class ZEUSJOBS_API UZeusJobsSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
/// Lookup por classId. Retorna nullptr se ClassId nao tem DA correspondente
/// (caller usa Library com fallback FText).
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs")
UZeusJobDataAsset* GetJobData(int32 ClassId) const;
/// Total de jobs cacheados (diagnostico).
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs")
int32 GetJobCount() const { return CachedJobs.Num(); }
/// Lista jobs num tier especifico (UI agrupa por tier — ex: dropdown
/// "Vocacoes possiveis"). Ordem do retorno e' a do AssetRegistry scan
/// — UI pode reordenar por ClassId/DisplayName se precisar.
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs")
TArray<UZeusJobDataAsset*> GetJobsByTier(EZeusJobTier Tier) const;
/// Destinos de promocao validos a partir do `CurrentClassId`. Filtra
/// pelos jobs que tem `ParentClassId == CurrentClassId`. WBP de
/// promocao consome direto pra montar botoes "Tornar-se X".
///
/// NOTA: condicao de elegibilidade real (jobLevel >= cap, quest, item)
/// e' do server — UI apenas LISTA opcoes possiveis pelo schema. Server
/// reponde rejeicao via S_JOB_CHANGE_RESULT (Jobs::EJobChangeRejectReason).
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs")
TArray<UZeusJobDataAsset*> GetEligibleNextJobs(int32 CurrentClassId) const;
private:
/// Scan AssetRegistry + popula CachedJobs. Sincronizo (V1) — chamado UMA
/// vez no Initialize. Pra 50 jobs eh aceitavel; se crescer pra 200+ pode
/// virar async no futuro.
void LoadJobsFromAssetRegistry();
UPROPERTY()
TMap<int32, TObjectPtr<UZeusJobDataAsset>> CachedJobs;
};

View File

@@ -0,0 +1,27 @@
using UnrealBuildTool;
public class ZeusJobs : ModuleRules
{
public ZeusJobs(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine"
});
PrivateDependencyModuleNames.AddRange(new string[] {
"AssetRegistry", // FAssetRegistryModule pra scan de DA_Job_*
"ZeusNetwork" // UZeusNetworkSubsystem (SendJobChangeRequest)
});
// Espelho do `Server/.../JobsSystem/module.json` (modulo nucleo MMO).
// Cliente roda em LoadingPhase PreDefault — Subsystem inicializa
// ANTES de o ZMMO core criar a player character / HUD.
//
// Sem dependencia em ZeusAttributes: server envia classId no snapshot,
// cliente faz lookup local. Mantem o peer-module pattern.
}
}

View File

@@ -0,0 +1,18 @@
{
"name": "ZeusJobs",
"gameType": "MMO",
"version": "0.1.0",
"side": "client",
"dependencies": {
"engine": ["Core", "CoreUObject", "Engine", "AssetRegistry"],
"modules": []
},
"publicHeaders": [
"ZeusJobsModule.h",
"ZeusJobDataAsset.h",
"ZeusJobsSubsystem.h",
"ZeusJobsLibrary.h"
],
"loadingPhase": "PreDefault",
"description": "Sub-modulo cliente do GameType MMO — dados visuais/UI por job (DisplayName localizado, ícone, anim override, som de level up). Espelha JobsSystem do server mas focado em apresentacao. NAO depende de ZeusAttributes — server envia classId no snapshot, cliente faz lookup local. Pattern peer-module estabelecido no PR #9."
}