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:
Binary file not shown.
Binary file not shown.
@@ -27,6 +27,7 @@ enum class EZMMOInGameUIState : uint8
|
||||
None, ///< Antes do player local spawnar; UI in-game inativa.
|
||||
Playing, ///< HUD principal visivel (HP/SP/level/etc).
|
||||
StatusWindow, ///< Janela de atributos + botões de alocacao (Fase 4).
|
||||
JobChangePanel, ///< Painel de promocao de classe (Jobs.1). Toggle tecla J.
|
||||
Inventory, ///< (futuro) Bag/equipamentos.
|
||||
SkillTree, ///< (futuro) Arvore de skills.
|
||||
EscapeMenu, ///< (futuro) Pausa, settings, logout.
|
||||
|
||||
@@ -104,6 +104,14 @@ void AZMMOPlayerController::SetupInputComponent()
|
||||
this, &AZMMOPlayerController::ToggleStatusWindow);
|
||||
Binding.bConsumeInput = true;
|
||||
Binding.bExecuteWhenPaused = true; // dispara mesmo com StatusWindow aberto (CommonUI menu mode)
|
||||
|
||||
// Hotkey J -> toggle JobChangePanel (Jobs.1).
|
||||
FInputKeyBinding& JobBinding = InputComponent->BindKey(
|
||||
FInputChord(EKeys::J, /*bShift*/ false, /*bCtrl*/ false, /*bAlt*/ false, /*bCmd*/ false),
|
||||
IE_Pressed,
|
||||
this, &AZMMOPlayerController::ToggleJobChangePanel);
|
||||
JobBinding.bConsumeInput = true;
|
||||
JobBinding.bExecuteWhenPaused = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +126,17 @@ void AZMMOPlayerController::ToggleStatusWindow()
|
||||
}
|
||||
}
|
||||
|
||||
void AZMMOPlayerController::ToggleJobChangePanel()
|
||||
{
|
||||
if (UGameInstance* GI = GetGameInstance())
|
||||
{
|
||||
if (UUIInGameFlowSubsystem* Flow = GI->GetSubsystem<UUIInGameFlowSubsystem>())
|
||||
{
|
||||
Flow->ToggleScreen(EZMMOInGameUIState::JobChangePanel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AZMMOPlayerController::ShouldUseTouchControls() const
|
||||
{
|
||||
return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls;
|
||||
|
||||
@@ -50,4 +50,7 @@ protected:
|
||||
|
||||
/** Hotkey Alt+A → toggle StatusWindow via UUIInGameFlowSubsystem. */
|
||||
void ToggleStatusWindow();
|
||||
|
||||
/** Hotkey J → toggle JobChangePanel (Jobs.1 — promocao de classe). */
|
||||
void ToggleJobChangePanel();
|
||||
};
|
||||
|
||||
67
Source/ZMMOJobs/Private/ZMMOJobChangeNetworkHandler.cpp
Normal file
67
Source/ZMMOJobs/Private/ZMMOJobChangeNetworkHandler.cpp
Normal 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>();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
54
Source/ZMMOJobs/Public/ZMMOJobChangeNetworkHandler.h
Normal file
54
Source/ZMMOJobs/Public/ZMMOJobChangeNetworkHandler.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/WorldSubsystem.h"
|
||||
#include "ZMMOJobChangeNetworkHandler.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(FZMMOOnJobChangeResultBP,
|
||||
bool, bAccepted, int32, Reason, int32, NewClassId);
|
||||
|
||||
/**
|
||||
* Bridge entre o `UZeusNetworkSubsystem` (do plugin ZeusNetwork) e a UI
|
||||
* Blueprint (WBP de promocao). Mesmo pattern do `UZMMOAttributeNetworkHandler`
|
||||
* — converte o delegate C++-only `FZeusOnJobChangeResult` num dynamic
|
||||
* multicast (`FZMMOOnJobChangeResultBP`) 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 ZMMOJOBS_API UZMMOJobChangeNetworkHandler : 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")
|
||||
FZMMOOnJobChangeResultBP OnJobChangeResult;
|
||||
|
||||
private:
|
||||
void HandleJobChangeResult(bool bAccepted, int32 Reason, int32 NewClassId);
|
||||
UZeusNetworkSubsystem* GetZeusNetSubsystem() const;
|
||||
|
||||
FDelegateHandle JobChangeResultHandle;
|
||||
};
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "ZMMOJobTypes.h"
|
||||
#include "ZMMOJobsLibrary.generated.h"
|
||||
|
||||
class UZMMOJobDataAsset;
|
||||
@@ -36,4 +37,26 @@ public:
|
||||
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<UZMMOJobDataAsset*> GetJobsByTier(
|
||||
const UObject* WorldContextObject, EZMMOJobTier 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<UZMMOJobDataAsset*> 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);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "ZMMOJobTypes.h"
|
||||
#include "ZMMOJobsSubsystem.generated.h"
|
||||
|
||||
class UZMMOJobDataAsset;
|
||||
@@ -41,6 +42,22 @@ public:
|
||||
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<UZMMOJobDataAsset*> GetJobsByTier(EZMMOJobTier 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<UZMMOJobDataAsset*> 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
|
||||
|
||||
@@ -13,7 +13,8 @@ public class ZMMOJobs : ModuleRules
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||
"AssetRegistry" // FAssetRegistryModule pra scan de DA_Job_*
|
||||
"AssetRegistry", // FAssetRegistryModule pra scan de DA_Job_*
|
||||
"ZeusNetwork" // UZeusNetworkSubsystem (SendJobChangeRequest)
|
||||
});
|
||||
|
||||
// Espelho do `Server/.../JobsSystem/module.json` (modulo nucleo MMO).
|
||||
|
||||
Reference in New Issue
Block a user