282 lines
9.2 KiB
C++
282 lines
9.2 KiB
C++
#pragma once
|
|
|
|
#include <optional>
|
|
#include <string>
|
|
#include <type_traits>
|
|
|
|
#include <DynamicOutput/DynamicOutput.hpp>
|
|
#include <Unreal/Hooks.hpp>
|
|
#include <Unreal/UObject.hpp>
|
|
|
|
#include <Reflection/UnrealApiCall.hpp>
|
|
|
|
#include <Api/CanvasPanel.hpp>
|
|
#include <Api/CanvasPanelSlot.hpp>
|
|
#include <Api/MainMenuPalWidget.hpp>
|
|
#include <Api/Mod/PalStatus_PalNumber.hpp>
|
|
#include <Api/Pal/PalIndividual.hpp>
|
|
#include <Api/Pal/PalTypes.hpp>
|
|
#include <Api/Pal/PalUtility.hpp>
|
|
#include <Helpers/PalHelpers.hpp>
|
|
#include <Static/EventHookRegistry.hpp>
|
|
#include <Static/LogGate.hpp>
|
|
#include <Static/WidgetRegistry.hpp>
|
|
|
|
#include "IUIFeature.hpp"
|
|
|
|
namespace PalUIExtension::Features
|
|
{
|
|
using namespace RC;
|
|
using namespace RC::Unreal;
|
|
|
|
struct Tag_PalStatusNumberWidget {};
|
|
class PalStatusPalNumber final : public IUIFeature
|
|
{
|
|
public:
|
|
|
|
bool m_enabled = true;
|
|
|
|
static constexpr const TCHAR* kInstanceName = STR("PalUI_PalStatus_PalNumber");
|
|
|
|
// nudge the rest of the canvas components down this much to make room for pal number
|
|
static constexpr double kLabelOffsetY = -25;
|
|
static constexpr double kLabelOffsetXFraction = 2.4;
|
|
|
|
auto Name() const -> const TCHAR* override { return STR("PalStatusNumberLabel"); }
|
|
|
|
auto InstallHooks() -> void override
|
|
{
|
|
Static::EventHookRegistry::Register(
|
|
STR("PalStatusPalNumber::BindFromHandle"),
|
|
Api::MainMenuPalWidget::BindFromHandlePath(),
|
|
&PalStatusPalNumber::OnBindFromHandle,
|
|
nullptr,
|
|
this);
|
|
}
|
|
|
|
auto OnWorldTeardown() -> void override { ForgetAll(); }
|
|
|
|
auto SetEnabled(bool bEnabled) -> void override { m_enabled = bEnabled; }
|
|
auto GetEnabled() -> bool override { return m_enabled; }
|
|
|
|
private:
|
|
|
|
UObject* m_owner{}; // the status panel this memo describes
|
|
UObject* m_label{}; // the injected widget under that panel
|
|
PalKey m_key{};
|
|
bool m_has_key{};
|
|
int32 m_index{};
|
|
bool m_has_index{};
|
|
bool m_visible{ true }; // last visibility WE set
|
|
bool m_layout_done{}; // canvas nudge applied for m_owner
|
|
|
|
// ---- hooks ----------------------------------------
|
|
|
|
static void OnBindFromHandle(UnrealScriptFunctionCallableContext& Ctx, void* CustomData)
|
|
{
|
|
auto* Self = static_cast<PalStatusPalNumber*>(CustomData);
|
|
if (!Self || !Ctx.Context) return;
|
|
|
|
auto& Frame = Ctx.GetParams<Api::MainMenuPalWidget::BindFromHandleFrame>();
|
|
Self->HandleBind(Ctx.Context, Frame.targetHandle);
|
|
}
|
|
|
|
auto HandleBind(UObject* Owner, UObject* Handle) -> void
|
|
{
|
|
if (Owner != m_owner) AdoptOwner(Owner);
|
|
|
|
const auto Key = ReadKey(Handle);
|
|
if (!Key.has_value())
|
|
{
|
|
HideLabel();
|
|
m_has_key = false;
|
|
return;
|
|
}
|
|
|
|
if (m_has_key && SamePalIndividual(m_key, *Key)) return; // the early out that matters
|
|
|
|
UObject* Label = ResolveLabel(Owner);
|
|
if (!Label) return;
|
|
|
|
Api::Mod::PalStatus_PalNumberWidget W{ Label };
|
|
if (!W.IsValid()) return;
|
|
|
|
m_key = *Key;
|
|
m_has_key = true;
|
|
|
|
const auto Species = W.SetPalBinding(BuildBindingParam(m_key));
|
|
if (!Species.has_value())
|
|
{
|
|
ShowLabel(W);
|
|
return;
|
|
}
|
|
|
|
const auto Index = Api::Pal::PalUtility::Instance().ZukanIndexFor(*Species, Owner);
|
|
|
|
ShowLabel(W);
|
|
ApplyIndex(W, Index);
|
|
}
|
|
|
|
auto ApplyIndex(Api::Mod::PalStatus_PalNumberWidget& W, std::optional<int32> Index) -> void
|
|
{
|
|
const int32 Resolved = Index.value_or(kUnknownIndex);
|
|
|
|
if (m_has_index && m_index == Resolved) return;
|
|
|
|
m_index = Resolved;
|
|
m_has_index = true;
|
|
|
|
const FString Text = (Resolved == kUnknownIndex) ? FString(STR("?")) : FString::Printf(STR("%d"), Resolved);
|
|
|
|
if (!W.SetPalNumberText(Text))
|
|
{
|
|
m_has_index = false;
|
|
SendLog(LogLevel::Error, STR("SetPalNumberText failed on {}\n"), W.Get() ? W.Get()->GetFullName() : STR("<null>"));
|
|
}
|
|
}
|
|
|
|
// ---- widget resolution ---------------------------------------
|
|
|
|
auto ResolveLabel(UObject* Owner) -> UObject*
|
|
{
|
|
if (m_label && IsOurLabel(m_label, Owner)) return m_label;
|
|
|
|
const auto Key = RegistryKey(Owner);
|
|
|
|
if (UObject* Cached = Static::WidgetRegistry::GetObject(Key);
|
|
Cached && IsOurLabel(Cached, Owner))
|
|
{
|
|
m_label = Cached;
|
|
return m_label;
|
|
}
|
|
|
|
bool bWasNew = false;
|
|
UObject* Found = WidgetApi().FindOrConstruct(Owner, kInstanceName, bWasNew);
|
|
if (!Found)
|
|
{
|
|
SendLog(LogLevel::Error, STR("could not find or construct '{}' under {}\n"), kInstanceName, Owner->GetFullName());
|
|
return nullptr;
|
|
}
|
|
|
|
if (bWasNew && !AttachLabel(Owner, Found))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
m_label = Found;
|
|
m_visible = true; // freshly attached widgets are visible
|
|
Static::WidgetRegistry::Register(Key, Found);
|
|
return m_label;
|
|
}
|
|
|
|
auto AttachLabel(UObject* Owner, UObject* Label) -> bool
|
|
{
|
|
Api::MainMenuPalWidget Panel{ Owner };
|
|
if (!Panel.IsValid())
|
|
{
|
|
Output::send<LogLevel::Error>(
|
|
STR("[PalNumberLabel] {} is not a MainMenuPalWidget\n"), Owner->GetFullName());
|
|
return false;
|
|
}
|
|
Api::CanvasPanel Canvas = Panel.CanvasPalName();
|
|
if (!Canvas.IsValid())
|
|
{
|
|
Canvas = Panel.Child<Api::CanvasPanel>(STR("Canvas_PalName"));
|
|
}
|
|
if (!Canvas.IsValid()) return false;
|
|
|
|
|
|
Api::PanelSlot Added = Canvas.AddChild(Label);
|
|
if (!Added.IsValid()) return false;
|
|
|
|
Api::CanvasPanelSlot LabelSlot{ Added.Get() };
|
|
if (LabelSlot.IsValid())
|
|
{
|
|
const Api::FVector2D_ Size = LabelSlot.GetSize().value_or(Api::FVector2D_{ 0, 0 });
|
|
LabelSlot.SetPosition(Api::FVector2D_{ Size.X * kLabelOffsetXFraction, kLabelOffsetY });
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ---- visibility ----------------------------------------------
|
|
|
|
auto ShowLabel(Api::Mod::PalStatus_PalNumberWidget& W) -> void
|
|
{
|
|
if (m_visible) return;
|
|
if (W.SetVisibility(Api::ESlateVisibility_::SelfHitTestInvisible)) m_visible = true;
|
|
}
|
|
|
|
auto HideLabel() -> void
|
|
{
|
|
if (!m_visible || !m_label) return;
|
|
|
|
Api::Widget W{ m_label };
|
|
if (!W.IsValid()) return;
|
|
if (W.SetVisibility(Api::ESlateVisibility_::Collapsed)) m_visible = false;
|
|
}
|
|
|
|
// ---- identity -------------------------------------------------
|
|
|
|
static auto ReadKey(UObject* Handle) -> std::optional<PalKey>
|
|
{
|
|
if (!Handle) return std::nullopt;
|
|
|
|
Api::Pal::LooseObject H{ Handle, STR("PalIndividualCharacterHandle") };
|
|
auto Id = H.CallForReturn<Api::Pal::FPalInstanceID>(STR("GetIndividualID"));
|
|
if (!Id.has_value()) return std::nullopt;
|
|
|
|
return PalKey{ Id->PlayerUID, Id->InstanceID };
|
|
}
|
|
|
|
static auto BuildBindingParam(const PalKey& Key) -> Api::Pal::FPalInstanceID
|
|
{
|
|
return Api::Pal::FPalInstanceID{ Key.PlayerUID, Key.InstanceID, FString{} };
|
|
}
|
|
|
|
auto AdoptOwner(UObject* Owner) -> void
|
|
{
|
|
m_owner = Owner;
|
|
m_label = nullptr;
|
|
m_has_key = false;
|
|
m_has_index = false;
|
|
m_visible = true;
|
|
}
|
|
|
|
auto ForgetAll() -> void
|
|
{
|
|
m_owner = nullptr;
|
|
m_label = nullptr;
|
|
m_has_key = false;
|
|
m_has_index = false;
|
|
m_visible = true;
|
|
|
|
}
|
|
|
|
static auto IsOurLabel(UObject* Candidate, UObject* Owner) -> bool
|
|
{
|
|
if (!Candidate || !Owner) return false;
|
|
if (Candidate->GetOuterPrivate() != Owner) return false;
|
|
return Api::ObjectIsA<Api::Mod::PalStatus_PalNumberWidget>(Candidate);
|
|
}
|
|
|
|
static auto RegistryKey(UObject* Owner) -> Static::WidgetRegistry::KeyType
|
|
{
|
|
static_assert(std::is_same_v<TCHAR, wchar_t>,
|
|
"RegistryKey builds its suffix with std::to_wstring");
|
|
|
|
return Static::WidgetRegistry::KeyType{ STR("PalStatusNumberLabel:") } +
|
|
std::to_wstring(reinterpret_cast<uintptr_t>(Owner));
|
|
}
|
|
|
|
static auto WidgetApi() -> Reflection::UnrealApiCall<Tag_PalStatusNumberWidget>&
|
|
{
|
|
static Reflection::UnrealApiCall<Tag_PalStatusNumberWidget> Api{
|
|
Api::Mod::PalStatus_PalNumberWidget::ClassPath(),
|
|
Api::Mod::PalStatus_PalNumberWidget::DebugTag() };
|
|
return Api;
|
|
}
|
|
|
|
static constexpr int32 kUnknownIndex = -1;
|
|
};
|
|
}
|