feat(jobs): Jobs.1 client UI infra (handler + library + tecla J) + UI tweaks

Infraestrutura cliente da promocao de classe (Jobs.1). WBP_JobChangePanel
NAO esta neste commit — foi exploracao descartada (criar UI dinamica de
botoes via MCP BP graph esbarrou em limites do CreateWidget node). A
fundacao C++ fica pronta pra ser usada quando voltarmos pra UI.

INFRA C++ NOVA (Source/ZMMOJobs/)
* UZMMOJobChangeNetworkHandler (WorldSubsystem novo) — bind no
  OnJobChangeResult do plugin e re-broadcast via FZMMOOnJobChangeResultBP
  (dynamic multicast assignavel em BP). WBP futura escuta direto.
* UZMMOJobsSubsystem helpers: GetJobsByTier(EZMMOJobTier) +
  GetEligibleNextJobs(currentClassId) — filtra por ParentClassId. Pure.
* UZMMOJobsLibrary BPFL wrappers + SendJobChangeRequest convenience
  (delega pro UZeusNetworkSubsystem). Dep ZeusNetwork adicionada no
  Build.cs.

ENUM + INPUT
* EZMMOInGameUIState ganha JobChangePanel (entre StatusWindow e
  Inventory). DA_InGameScreenSet pendente de receber entry quando
  WBP for criada (tecla J vai falhar silenciosamente ate la).
* ZMMOPlayerController: tecla J -> ToggleJobChangePanel ->
  Flow->ToggleScreen(JobChangePanel). Espelha pattern do Alt+A do
  StatusWindow (InputComponent legacy BindKey + FInputChord).

UI TWEAKS
* WBP_StatusWindow + DT_UI_Styles: ajustes visuais no Editor durante a
  sessao (sem mudanca funcional documentavel — provavelmente fontes/
  spacing/cores).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 01:37:04 -03:00
parent f832c1a4df
commit 6391372f00
12 changed files with 250 additions and 1 deletions

View File

@@ -0,0 +1,67 @@
#include "ZMMOJobChangeNetworkHandler.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "ZeusNetworkSubsystem.h"
DEFINE_LOG_CATEGORY_STATIC(LogZMMOJobChange, Log, All);
bool UZMMOJobChangeNetworkHandler::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 UZMMOJobChangeNetworkHandler::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
if (UZeusNetworkSubsystem* Net = GetZeusNetSubsystem())
{
JobChangeResultHandle = Net->OnJobChangeResult.AddUObject(
this, &UZMMOJobChangeNetworkHandler::HandleJobChangeResult);
UE_LOG(LogZMMOJobChange, Log,
TEXT("[ZMMOJobChangeNetworkHandler] Bind OnJobChangeResult OK"));
}
else
{
UE_LOG(LogZMMOJobChange, Warning,
TEXT("[ZMMOJobChangeNetworkHandler] ZeusNetworkSubsystem indisponivel — bind pulado"));
}
}
void UZMMOJobChangeNetworkHandler::Deinitialize()
{
if (UZeusNetworkSubsystem* Net = GetZeusNetSubsystem())
{
if (JobChangeResultHandle.IsValid())
{
Net->OnJobChangeResult.Remove(JobChangeResultHandle);
JobChangeResultHandle.Reset();
}
}
OnJobChangeResult.Clear();
Super::Deinitialize();
}
void UZMMOJobChangeNetworkHandler::HandleJobChangeResult(
bool bAccepted, int32 Reason, int32 NewClassId)
{
UE_LOG(LogZMMOJobChange, Log,
TEXT("[ZMMOJobChangeNetworkHandler] S_JOB_CHANGE_RESULT accepted=%d reason=%d newClassId=%d"),
bAccepted ? 1 : 0, Reason, NewClassId);
OnJobChangeResult.Broadcast(bAccepted, Reason, NewClassId);
}
UZeusNetworkSubsystem* UZMMOJobChangeNetworkHandler::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

@@ -4,6 +4,7 @@
#include "Engine/World.h"
#include "ZMMOJobDataAsset.h"
#include "ZMMOJobsSubsystem.h"
#include "ZeusNetworkSubsystem.h"
namespace
{
@@ -52,3 +53,37 @@ bool UZMMOJobsLibrary::IsJobRegistered(const UObject* WorldContextObject, int32
}
return false;
}
TArray<UZMMOJobDataAsset*> UZMMOJobsLibrary::GetJobsByTier(
const UObject* WorldContextObject, EZMMOJobTier Tier)
{
if (UZMMOJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
{
return Sub->GetJobsByTier(Tier);
}
return {};
}
TArray<UZMMOJobDataAsset*> UZMMOJobsLibrary::GetEligibleNextJobs(
const UObject* WorldContextObject, int32 CurrentClassId)
{
if (UZMMOJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
{
return Sub->GetEligibleNextJobs(CurrentClassId);
}
return {};
}
void UZMMOJobsLibrary::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

@@ -65,3 +65,32 @@ UZMMOJobDataAsset* UZMMOJobsSubsystem::GetJobData(int32 ClassId) const
}
return nullptr;
}
TArray<UZMMOJobDataAsset*> UZMMOJobsSubsystem::GetJobsByTier(EZMMOJobTier Tier) const
{
TArray<UZMMOJobDataAsset*> Out;
Out.Reserve(CachedJobs.Num());
for (const TPair<int32, TObjectPtr<UZMMOJobDataAsset>>& Pair : CachedJobs)
{
if (Pair.Value && Pair.Value->Tier == Tier)
{
Out.Add(Pair.Value.Get());
}
}
return Out;
}
TArray<UZMMOJobDataAsset*> UZMMOJobsSubsystem::GetEligibleNextJobs(int32 CurrentClassId) const
{
TArray<UZMMOJobDataAsset*> Out;
Out.Reserve(4); // tipico: 2-6 destinos
for (const TPair<int32, TObjectPtr<UZMMOJobDataAsset>>& Pair : CachedJobs)
{
if (Pair.Value && Pair.Value->ParentClassId == CurrentClassId
&& Pair.Value->ClassId != CurrentClassId)
{
Out.Add(Pair.Value.Get());
}
}
return Out;
}