<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.jftse.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=XxharCs</id>
	<title>JFTSE Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.jftse.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=XxharCs"/>
	<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php/Special:Contributions/XxharCs"/>
	<updated>2026-06-19T17:13:12Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Custom_Client_UI_with_StageManager_Input&amp;diff=308</id>
		<title>Custom Client UI with StageManager Input</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Custom_Client_UI_with_StageManager_Input&amp;diff=308"/>
		<updated>2026-05-20T08:51:29Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Overview ==&lt;br /&gt;
&lt;br /&gt;
This page shows a small proof of concept for loading a separate custom XML GUI through the Fantasy Tennis client GUI system.&lt;br /&gt;
&lt;br /&gt;
The goal is simple:&lt;br /&gt;
&lt;br /&gt;
* construct a small custom GUI owner;&lt;br /&gt;
* attach it to the currently active &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt;;&lt;br /&gt;
* load a custom &amp;lt;code&amp;gt;Gui_Test.xml&amp;lt;/code&amp;gt;;&lt;br /&gt;
* receive XML callback events;&lt;br /&gt;
* forward input through the normal &amp;lt;code&amp;gt;StageManager&amp;lt;/code&amp;gt; input path.&lt;br /&gt;
&lt;br /&gt;
This does not replace an existing popup. The custom GUI is attached as an additional child owner of the current stage owner.&lt;br /&gt;
&lt;br /&gt;
The example intentionally avoids custom control type registration. It only uses normal XML controls such as &amp;lt;code&amp;gt;Static&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Button&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;EditBox&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== StageManager Input Hook ==&lt;br /&gt;
&lt;br /&gt;
The Fantasy Tennis client already routes keyboard and mouse input through the &amp;lt;code&amp;gt;StageManager&amp;lt;/code&amp;gt;. A manually attached custom GUI owner can render and receive callbacks, but it still needs to receive input messages.&lt;br /&gt;
&lt;br /&gt;
The hook below gives the custom GUI a chance to consume input before the original &amp;lt;code&amp;gt;StageManager::HandleInput&amp;lt;/code&amp;gt; continues.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
using StageManagerHandleInputFn = bool(FTSDK_FASTCALL*)(&lt;br /&gt;
    FTSDK::StageManager* self,&lt;br /&gt;
    int edx0,&lt;br /&gt;
    uint32_t msg,&lt;br /&gt;
    int32_t wParam,&lt;br /&gt;
    int32_t lParam&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
static StageManagerHandleInputFn o_StageManagerHandleInput = nullptr;&lt;br /&gt;
&lt;br /&gt;
bool FTSDK_FASTCALL hk_StageManagerHandleInput(&lt;br /&gt;
    FTSDK::StageManager* self,&lt;br /&gt;
    int edx0,&lt;br /&gt;
    uint32_t msg,&lt;br /&gt;
    int32_t wParam,&lt;br /&gt;
    int32_t lParam&lt;br /&gt;
) {&lt;br /&gt;
    FUNCTION_GUARD;&lt;br /&gt;
&lt;br /&gt;
    if (CustomGuiTest::HandleInput(msg, wParam, lParam))&lt;br /&gt;
        return true;&lt;br /&gt;
&lt;br /&gt;
    return o_StageManagerHandleInput(self, edx0, msg, wParam, lParam);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Install the hook on the current &amp;lt;code&amp;gt;StageManager&amp;lt;/code&amp;gt; instance. In the currently analyzed client build, &amp;lt;code&amp;gt;HandleInput&amp;lt;/code&amp;gt; is vtable index &amp;lt;code&amp;gt;38&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
static std::unique_ptr&amp;lt;ShadowVTManager&amp;gt; g_stageManagerHook;&lt;br /&gt;
&lt;br /&gt;
static void* GetObjectVTable(const void* object) {&lt;br /&gt;
    return object ? *reinterpret_cast&amp;lt;void* const*&amp;gt;(object) : nullptr;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void InstallStageManagerInputHook() {&lt;br /&gt;
    auto* manager = FTSDK::GetStageManager();&lt;br /&gt;
    if (!manager)&lt;br /&gt;
        return;&lt;br /&gt;
&lt;br /&gt;
    g_stageManagerHook = std::make_unique&amp;lt;ShadowVTManager&amp;gt;();&lt;br /&gt;
    g_stageManagerHook-&amp;gt;Setup(manager);&lt;br /&gt;
    g_stageManagerHook-&amp;gt;Hook(38, hk_StageManagerHandleInput);&lt;br /&gt;
&lt;br /&gt;
    o_StageManagerHandleInput =&lt;br /&gt;
        g_stageManagerHook-&amp;gt;GetOriginal&amp;lt;StageManagerHandleInputFn&amp;gt;(38);&lt;br /&gt;
&lt;br /&gt;
    std::printf(&lt;br /&gt;
        &amp;quot;[StageManagerHook] hooked manager=%p vt=%p index=38 original=%p hook=%p\n&amp;quot;,&lt;br /&gt;
        manager,&lt;br /&gt;
        GetObjectVTable(manager),&lt;br /&gt;
        o_StageManagerHandleInput,&lt;br /&gt;
        hk_StageManagerHandleInput&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Custom GUI Test Code ==&lt;br /&gt;
&lt;br /&gt;
The test creates one custom &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt; owner and attaches it to the current stage.&lt;br /&gt;
&lt;br /&gt;
It uses &amp;lt;code&amp;gt;GuiBind&amp;lt;/code&amp;gt; to bind XML controls to command ids. The callback handles button clicks and edit box changes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
namespace CustomGuiTest {&lt;br /&gt;
&lt;br /&gt;
    enum CommandId : int32_t {&lt;br /&gt;
        Cmd_TestButton   = 1000,&lt;br /&gt;
        Cmd_CloseButton  = 1001,&lt;br /&gt;
        Cmd_WindowStatic = 1002,&lt;br /&gt;
        Cmd_TitleStatic  = 1003,&lt;br /&gt;
        Cmd_InfoStatic   = 1004,&lt;br /&gt;
        Cmd_InputEditBox = 1005,&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    struct CustomGuiContext {&lt;br /&gt;
        FTSDK::GameDialogStage* owner = nullptr;&lt;br /&gt;
        FTSDK::GameDialogStage* parentOwner = nullptr;&lt;br /&gt;
        bool loaded = false;&lt;br /&gt;
        bool opened = false;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static CustomGuiContext g_ctx{};&lt;br /&gt;
&lt;br /&gt;
    // The XML/dialog/control objects are allocated by the client.&lt;br /&gt;
    // This buffer only stores the custom owner object itself.&lt;br /&gt;
    alignas(16) static std::uint8_t g_customOwnerStorage[0x800]{};&lt;br /&gt;
&lt;br /&gt;
    static constexpr FTSDK::Structs::GuiBind g_customBinds[] = {&lt;br /&gt;
        { Cmd_TestButton,   &amp;quot;btTest&amp;quot;,         FTSDK::GuiControlType::Button },&lt;br /&gt;
        { Cmd_CloseButton,  &amp;quot;btClose&amp;quot;,        FTSDK::GuiControlType::Button },&lt;br /&gt;
        { Cmd_WindowStatic, &amp;quot;stCustomWindow&amp;quot;, FTSDK::GuiControlType::Static },&lt;br /&gt;
        { Cmd_TitleStatic,  &amp;quot;stCustomTop&amp;quot;,    FTSDK::GuiControlType::Static },&lt;br /&gt;
        { Cmd_InfoStatic,   &amp;quot;stCustomInfo&amp;quot;,   FTSDK::GuiControlType::Static },&lt;br /&gt;
        { Cmd_InputEditBox, &amp;quot;ebCustomInput&amp;quot;,  FTSDK::GuiControlType::EditBox },&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static constexpr int32_t CustomBindCount =&lt;br /&gt;
        static_cast&amp;lt;int32_t&amp;gt;(sizeof(g_customBinds) / sizeof(g_customBinds[0]));&lt;br /&gt;
&lt;br /&gt;
    void CloseCustomGui();&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::GameDialogStage* CurrentParentOwner() {&lt;br /&gt;
        auto* manager = FTSDK::GetStageManager();&lt;br /&gt;
        if (!manager)&lt;br /&gt;
            return nullptr;&lt;br /&gt;
&lt;br /&gt;
        return manager-&amp;gt;CurrentStage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::AduGuiControl* FindControl(&lt;br /&gt;
        FTSDK::GameDialogStage* owner,&lt;br /&gt;
        int32_t commandId,&lt;br /&gt;
        const char* fallbackName = nullptr&lt;br /&gt;
    ) {&lt;br /&gt;
        if (!owner)&lt;br /&gt;
            return nullptr;&lt;br /&gt;
&lt;br /&gt;
        if (auto* bound = owner-&amp;gt;FindBoundControl(commandId))&lt;br /&gt;
            return bound;&lt;br /&gt;
&lt;br /&gt;
        auto* dialog = owner-&amp;gt;XmlDialog();&lt;br /&gt;
        if (!dialog)&lt;br /&gt;
            return nullptr;&lt;br /&gt;
&lt;br /&gt;
        if (auto* byCommand = dialog-&amp;gt;FindControlByCommandId(commandId))&lt;br /&gt;
            return byCommand;&lt;br /&gt;
&lt;br /&gt;
        if (fallbackName)&lt;br /&gt;
            return dialog-&amp;gt;FindControlByName(fallbackName);&lt;br /&gt;
&lt;br /&gt;
        return nullptr;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::AduGuiEditBox* FindInputEditBox(FTSDK::GameDialogStage* owner) {&lt;br /&gt;
        auto* control = FindControl(owner, Cmd_InputEditBox, &amp;quot;ebCustomInput&amp;quot;);&lt;br /&gt;
        if (!control || control-&amp;gt;ControlTypeId() != FTSDK::GuiControlType::EditBox)&lt;br /&gt;
            return nullptr;&lt;br /&gt;
&lt;br /&gt;
        return reinterpret_cast&amp;lt;FTSDK::AduGuiEditBox*&amp;gt;(control);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::AduGuiStatic* FindStatic(&lt;br /&gt;
        FTSDK::GameDialogStage* owner,&lt;br /&gt;
        int32_t commandId,&lt;br /&gt;
        const char* fallbackName&lt;br /&gt;
    ) {&lt;br /&gt;
        auto* control = FindControl(owner, commandId, fallbackName);&lt;br /&gt;
        if (!control || control-&amp;gt;ControlTypeId() != FTSDK::GuiControlType::Static)&lt;br /&gt;
            return nullptr;&lt;br /&gt;
&lt;br /&gt;
        return reinterpret_cast&amp;lt;FTSDK::AduGuiStatic*&amp;gt;(control);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    static void SetInfoText(const wchar_t* text) {&lt;br /&gt;
        auto* stInfo = FindStatic(g_ctx.owner, Cmd_InfoStatic, &amp;quot;stCustomInfo&amp;quot;);&lt;br /&gt;
        if (!stInfo)&lt;br /&gt;
            return;&lt;br /&gt;
&lt;br /&gt;
        FTSDK::SetStaticText(stInfo, text ? text : L&amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    void FTSDK_STDCALL CustomGuiCallback(&lt;br /&gt;
        FTSDK::GuiEventType eventType,&lt;br /&gt;
        int commandId,&lt;br /&gt;
        int param,&lt;br /&gt;
        void* userData&lt;br /&gt;
    ) {&lt;br /&gt;
        auto* ctx = reinterpret_cast&amp;lt;CustomGuiContext*&amp;gt;(userData);&lt;br /&gt;
        auto* owner = ctx ? ctx-&amp;gt;owner : nullptr;&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] callback event=%d commandId=%d param=%p ctx=%p owner=%p\n&amp;quot;,&lt;br /&gt;
            static_cast&amp;lt;int32_t&amp;gt;(eventType),&lt;br /&gt;
            commandId,&lt;br /&gt;
            reinterpret_cast&amp;lt;void*&amp;gt;(param),&lt;br /&gt;
            userData,&lt;br /&gt;
            owner&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (!ctx || !owner)&lt;br /&gt;
            return;&lt;br /&gt;
&lt;br /&gt;
        switch (commandId) {&lt;br /&gt;
        case Cmd_TestButton:&lt;br /&gt;
            if (eventType == FTSDK::GuiEventType::ButtonClick) {&lt;br /&gt;
                auto* input = FindInputEditBox(owner);&lt;br /&gt;
                if (!input) {&lt;br /&gt;
                    std::printf(&amp;quot;[CustomGui] input editbox not found\n&amp;quot;);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                const wchar_t* text = input-&amp;gt;TextW();&lt;br /&gt;
&lt;br /&gt;
                std::printf(&amp;quot;[CustomGui] btTest clicked, input=\&amp;quot;%ls\&amp;quot;\n&amp;quot;, text);&lt;br /&gt;
                SetInfoText(text &amp;amp;&amp;amp; text[0] ? text : L&amp;quot;Input was empty&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
            return;&lt;br /&gt;
&lt;br /&gt;
        case Cmd_CloseButton:&lt;br /&gt;
            if (eventType == FTSDK::GuiEventType::ButtonClick) {&lt;br /&gt;
                std::printf(&amp;quot;[CustomGui] btClose clicked\n&amp;quot;);&lt;br /&gt;
                CloseCustomGui();&lt;br /&gt;
            }&lt;br /&gt;
            return;&lt;br /&gt;
&lt;br /&gt;
        case Cmd_InputEditBox:&lt;br /&gt;
            if (eventType == FTSDK::GuiEventType::EditBoxChange) {&lt;br /&gt;
                auto* input = reinterpret_cast&amp;lt;FTSDK::AduGuiEditBox*&amp;gt;(param);&lt;br /&gt;
                if (input) {&lt;br /&gt;
                    std::printf(&amp;quot;[CustomGui] input changed: \&amp;quot;%ls\&amp;quot;\n&amp;quot;, input-&amp;gt;TextW());&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            return;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    static bool IsReady() {&lt;br /&gt;
        return g_ctx.owner &amp;amp;&amp;amp; g_ctx.loaded &amp;amp;&amp;amp; g_ctx.owner-&amp;gt;XmlDialog();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    static bool IsAttachedTo(FTSDK::GameDialogStage* parentOwner) {&lt;br /&gt;
        return g_ctx.owner&lt;br /&gt;
            &amp;amp;&amp;amp; parentOwner&lt;br /&gt;
            &amp;amp;&amp;amp; g_ctx.parentOwner == parentOwner&lt;br /&gt;
            &amp;amp;&amp;amp; g_ctx.owner-&amp;gt;parent == parentOwner;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::GameDialogStage* CreateCustomGuiOwner(&lt;br /&gt;
        FTSDK::GameDialogStage* parentOwner&lt;br /&gt;
    ) {&lt;br /&gt;
        if (!parentOwner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] create failed: parentOwner=null\n&amp;quot;);&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        std::memset(g_customOwnerStorage, 0, sizeof(g_customOwnerStorage));&lt;br /&gt;
&lt;br /&gt;
        auto* owner = FTSDK::ConstructGameDialogStage(&lt;br /&gt;
            reinterpret_cast&amp;lt;FTSDK::GameDialogStage*&amp;gt;(g_customOwnerStorage)&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (!owner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] ConstructGameDialogStage failed\n&amp;quot;);&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        g_ctx.owner = owner;&lt;br /&gt;
        g_ctx.parentOwner = parentOwner;&lt;br /&gt;
        g_ctx.loaded = false;&lt;br /&gt;
        g_ctx.opened = false;&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] constructed owner=%p vt=%p parentOwner=%p\n&amp;quot;,&lt;br /&gt;
            owner,&lt;br /&gt;
            GetObjectVTable(owner),&lt;br /&gt;
            parentOwner&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        owner-&amp;gt;InitDialogWithParentOwner(parentOwner);&lt;br /&gt;
&lt;br /&gt;
        const bool loaded = owner-&amp;gt;LoadXml(&lt;br /&gt;
            &amp;quot;Gui_Test.xml&amp;quot;,&lt;br /&gt;
            g_customBinds,&lt;br /&gt;
            CustomBindCount,&lt;br /&gt;
            nullptr,&lt;br /&gt;
            false&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] LoadXml returned=%d owner=%p xml=%p\n&amp;quot;,&lt;br /&gt;
            loaded ? 1 : 0,&lt;br /&gt;
            owner,&lt;br /&gt;
            owner-&amp;gt;XmlDialog()&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (!loaded || !owner-&amp;gt;XmlDialog())&lt;br /&gt;
            return owner;&lt;br /&gt;
&lt;br /&gt;
        owner-&amp;gt;XmlDialog()-&amp;gt;SetCallback(CustomGuiCallback, &amp;amp;g_ctx);&lt;br /&gt;
&lt;br /&gt;
        owner-&amp;gt;DeactivateProcessing();&lt;br /&gt;
        owner-&amp;gt;ClearOpenFlag();&lt;br /&gt;
        owner-&amp;gt;parentChainEnabled = 0;&lt;br /&gt;
&lt;br /&gt;
        owner-&amp;gt;CenterRectInParent();&lt;br /&gt;
&lt;br /&gt;
        g_ctx.loaded = true;&lt;br /&gt;
&lt;br /&gt;
        return owner;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::GameDialogStage* GetOrCreateCustomGuiOwner() {&lt;br /&gt;
        auto* parentOwner = CurrentParentOwner();&lt;br /&gt;
&lt;br /&gt;
        if (!parentOwner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] no current parent owner\n&amp;quot;);&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (g_ctx.owner) {&lt;br /&gt;
            if (!IsAttachedTo(parentOwner)) {&lt;br /&gt;
                std::printf(&lt;br /&gt;
                    &amp;quot;[CustomGui] parent owner changed. oldParent=%p newParent=%p\n&amp;quot;,&lt;br /&gt;
                    g_ctx.parentOwner,&lt;br /&gt;
                    parentOwner&lt;br /&gt;
                );&lt;br /&gt;
                return nullptr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return g_ctx.owner;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return CreateCustomGuiOwner(parentOwner);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    void OpenOrShowCustomGui() {&lt;br /&gt;
        auto* parentOwner = CurrentParentOwner();&lt;br /&gt;
&lt;br /&gt;
        std::printf(&amp;quot;========== OpenOrShowCustomGui ==========\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        if (!parentOwner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] parent owner missing\n&amp;quot;);&lt;br /&gt;
            std::printf(&amp;quot;=========================================\n&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        auto* owner = GetOrCreateCustomGuiOwner();&lt;br /&gt;
        if (!owner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] owner unavailable\n&amp;quot;);&lt;br /&gt;
            std::printf(&amp;quot;=========================================\n&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!IsReady()) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] owner exists but XML is not ready\n&amp;quot;);&lt;br /&gt;
            std::printf(&amp;quot;=========================================\n&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        owner-&amp;gt;SetOpenFlag();&lt;br /&gt;
        owner-&amp;gt;ActivateProcessing();&lt;br /&gt;
        owner-&amp;gt;parentChainEnabled = 1;&lt;br /&gt;
&lt;br /&gt;
        parentOwner-&amp;gt;BringChildOwnerToFront(owner);&lt;br /&gt;
&lt;br /&gt;
        g_ctx.opened = true;&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] opened parent=%p owner=%p open=%d process=%d reachable=%d xml=%p\n&amp;quot;,&lt;br /&gt;
            parentOwner,&lt;br /&gt;
            owner,&lt;br /&gt;
            owner-&amp;gt;openFlag,&lt;br /&gt;
            owner-&amp;gt;processEnabled,&lt;br /&gt;
            owner-&amp;gt;IsReachableThroughParentChain(),&lt;br /&gt;
            owner-&amp;gt;XmlDialog()&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        std::printf(&amp;quot;=========================================\n&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    void CloseCustomGui() {&lt;br /&gt;
        auto* owner = g_ctx.owner;&lt;br /&gt;
        auto* parentOwner = g_ctx.parentOwner;&lt;br /&gt;
&lt;br /&gt;
        if (!owner)&lt;br /&gt;
            return;&lt;br /&gt;
&lt;br /&gt;
        if (parentOwner)&lt;br /&gt;
            parentOwner-&amp;gt;ClearPriorityInputChildOwnerIf(owner);&lt;br /&gt;
&lt;br /&gt;
        owner-&amp;gt;ClearOpenFlag();&lt;br /&gt;
        owner-&amp;gt;DeactivateProcessing();&lt;br /&gt;
        owner-&amp;gt;parentChainEnabled = 0;&lt;br /&gt;
&lt;br /&gt;
        g_ctx.opened = false;&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] closed parent=%p owner=%p open=%d process=%d reachable=%d\n&amp;quot;,&lt;br /&gt;
            parentOwner,&lt;br /&gt;
            owner,&lt;br /&gt;
            owner-&amp;gt;openFlag,&lt;br /&gt;
            owner-&amp;gt;processEnabled,&lt;br /&gt;
            owner-&amp;gt;IsReachableThroughParentChain()&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bool HandleInput(uint32_t msg, int32_t wParam, int32_t lParam) {&lt;br /&gt;
        auto* owner = g_ctx.owner;&lt;br /&gt;
&lt;br /&gt;
        if (!g_ctx.opened || !owner || !owner-&amp;gt;XmlDialog())&lt;br /&gt;
            return false;&lt;br /&gt;
&lt;br /&gt;
        if (!owner-&amp;gt;processEnabled || !owner-&amp;gt;openFlag)&lt;br /&gt;
            return false;&lt;br /&gt;
&lt;br /&gt;
        if (!owner-&amp;gt;IsReachableThroughParentChain())&lt;br /&gt;
            return false;&lt;br /&gt;
&lt;br /&gt;
        return owner-&amp;gt;HandleInput(msg, wParam, lParam);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bool IsOpen() {&lt;br /&gt;
        return g_ctx.opened;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
} // namespace CustomGuiTest&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== XML Dialog Sample ==&lt;br /&gt;
&lt;br /&gt;
The XML file must be available through the same GUI resource path that the client normally uses for XML dialogs.&lt;br /&gt;
&lt;br /&gt;
Save this as &amp;lt;code&amp;gt;Gui_Test.xml&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;Dialog x=&amp;quot;0&amp;quot; y=&amp;quot;0&amp;quot; w=&amp;quot;1024&amp;quot; h=&amp;quot;768&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Image Name=&amp;quot;DEFAULT&amp;quot; du=&amp;quot;0&amp;quot; dv=&amp;quot;0&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;DEFAULT&amp;quot;&lt;br /&gt;
             Type=&amp;quot;Static&amp;quot;&lt;br /&gt;
             Enable=&amp;quot;Yes&amp;quot;&lt;br /&gt;
             Font=&amp;quot;&amp;quot;&lt;br /&gt;
             x=&amp;quot;0&amp;quot;&lt;br /&gt;
             y=&amp;quot;0&amp;quot;&lt;br /&gt;
             w=&amp;quot;0&amp;quot;&lt;br /&gt;
             h=&amp;quot;0&amp;quot;&lt;br /&gt;
             dx=&amp;quot;0&amp;quot;&lt;br /&gt;
             dy=&amp;quot;0&amp;quot;&lt;br /&gt;
             Text=&amp;quot;&amp;quot;&lt;br /&gt;
             TextID=&amp;quot;&amp;quot;&lt;br /&gt;
             TextFx=&amp;quot;&amp;quot;&lt;br /&gt;
             TextAlignX=&amp;quot;CENTER&amp;quot;&lt;br /&gt;
             TextAlignY=&amp;quot;CENTER&amp;quot;&lt;br /&gt;
             TextMargin=&amp;quot;&amp;quot;&lt;br /&gt;
             Debug=&amp;quot;No&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomWindow&amp;quot;&lt;br /&gt;
             Type=&amp;quot;Static&amp;quot;&lt;br /&gt;
             x=&amp;quot;18&amp;quot;&lt;br /&gt;
             y=&amp;quot;42&amp;quot;&lt;br /&gt;
             w=&amp;quot;330&amp;quot;&lt;br /&gt;
             h=&amp;quot;190&amp;quot;&lt;br /&gt;
             Text=&amp;quot;JFTSE Custom GUI&amp;quot;&lt;br /&gt;
             TextID=&amp;quot;&amp;quot;&lt;br /&gt;
             TextFx=&amp;quot;Shadow&amp;quot;&lt;br /&gt;
             TextAlignX=&amp;quot;CENTER&amp;quot;&lt;br /&gt;
             TextAlignY=&amp;quot;TOP&amp;quot;&lt;br /&gt;
             TextMargin=&amp;quot;0,8,0,0&amp;quot;&lt;br /&gt;
             Debug=&amp;quot;No&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;State Image=&amp;quot;cmnWindow&amp;quot;/&amp;gt;&lt;br /&gt;
    &amp;lt;/Control&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomLineTop&amp;quot;&lt;br /&gt;
             Type=&amp;quot;Static&amp;quot;&lt;br /&gt;
             x=&amp;quot;42&amp;quot;&lt;br /&gt;
             y=&amp;quot;80&amp;quot;&lt;br /&gt;
             w=&amp;quot;282&amp;quot;&lt;br /&gt;
             h=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;State Image=&amp;quot;cmnWindowLine&amp;quot;/&amp;gt;&lt;br /&gt;
    &amp;lt;/Control&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomTop&amp;quot;&lt;br /&gt;
             Type=&amp;quot;Static&amp;quot;&lt;br /&gt;
             x=&amp;quot;40&amp;quot;&lt;br /&gt;
             y=&amp;quot;96&amp;quot;&lt;br /&gt;
             w=&amp;quot;286&amp;quot;&lt;br /&gt;
             h=&amp;quot;24&amp;quot;&lt;br /&gt;
             Text=&amp;quot;Enter text and press Test&amp;quot;&lt;br /&gt;
             TextID=&amp;quot;&amp;quot;&lt;br /&gt;
             TextAlignX=&amp;quot;CENTER&amp;quot;&lt;br /&gt;
             TextAlignY=&amp;quot;CENTER&amp;quot;&lt;br /&gt;
             Debug=&amp;quot;No&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;State TextColor=&amp;quot;255,0,166,165&amp;quot;/&amp;gt;&lt;br /&gt;
    &amp;lt;/Control&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomInfo&amp;quot;&lt;br /&gt;
             Type=&amp;quot;Static&amp;quot;&lt;br /&gt;
             x=&amp;quot;40&amp;quot;&lt;br /&gt;
             y=&amp;quot;124&amp;quot;&lt;br /&gt;
             w=&amp;quot;286&amp;quot;&lt;br /&gt;
             h=&amp;quot;22&amp;quot;&lt;br /&gt;
             Text=&amp;quot;Gui_Test.xml&amp;quot;&lt;br /&gt;
             TextID=&amp;quot;&amp;quot;&lt;br /&gt;
             TextAlignX=&amp;quot;CENTER&amp;quot;&lt;br /&gt;
             TextAlignY=&amp;quot;CENTER&amp;quot;&lt;br /&gt;
             Debug=&amp;quot;No&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;State TextColor=&amp;quot;255,30,30,30&amp;quot;/&amp;gt;&lt;br /&gt;
    &amp;lt;/Control&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;ebCustomInput&amp;quot;&lt;br /&gt;
             Type=&amp;quot;EditBox&amp;quot;&lt;br /&gt;
             x=&amp;quot;58&amp;quot;&lt;br /&gt;
             y=&amp;quot;154&amp;quot;&lt;br /&gt;
             w=&amp;quot;250&amp;quot;&lt;br /&gt;
             h=&amp;quot;20&amp;quot;&lt;br /&gt;
             Text=&amp;quot;&amp;quot;&lt;br /&gt;
             TextID=&amp;quot;&amp;quot;&lt;br /&gt;
             Debug=&amp;quot;No&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;State Image=&amp;quot;cmnBlank2&amp;quot;/&amp;gt;&lt;br /&gt;
    &amp;lt;/Control&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomLineBottom&amp;quot;&lt;br /&gt;
             Type=&amp;quot;Static&amp;quot;&lt;br /&gt;
             x=&amp;quot;42&amp;quot;&lt;br /&gt;
             y=&amp;quot;184&amp;quot;&lt;br /&gt;
             w=&amp;quot;282&amp;quot;&lt;br /&gt;
             h=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;State Image=&amp;quot;cmnWindowLine&amp;quot;/&amp;gt;&lt;br /&gt;
    &amp;lt;/Control&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;DEFAULT&amp;quot; Type=&amp;quot;Button&amp;quot; TextFx=&amp;quot;Shadow&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;State Name=&amp;quot;All&amp;quot; Image=&amp;quot;cmnWindowBtn&amp;quot;/&amp;gt;&lt;br /&gt;
        &amp;lt;State Name=&amp;quot;Over&amp;quot; Image=&amp;quot;cmnShopBuyBtn&amp;quot;/&amp;gt;&lt;br /&gt;
        &amp;lt;State Name=&amp;quot;Pressed&amp;quot; Color=&amp;quot;255,155,155,155&amp;quot;/&amp;gt;&lt;br /&gt;
    &amp;lt;/Control&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;btTest&amp;quot;&lt;br /&gt;
             Type=&amp;quot;Button&amp;quot;&lt;br /&gt;
             x=&amp;quot;58&amp;quot;&lt;br /&gt;
             y=&amp;quot;194&amp;quot;&lt;br /&gt;
             w=&amp;quot;120&amp;quot;&lt;br /&gt;
             h=&amp;quot;22&amp;quot;&lt;br /&gt;
             Text=&amp;quot;Test&amp;quot;&lt;br /&gt;
             TextID=&amp;quot;&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;btClose&amp;quot;&lt;br /&gt;
             Type=&amp;quot;Button&amp;quot;&lt;br /&gt;
             x=&amp;quot;188&amp;quot;&lt;br /&gt;
             y=&amp;quot;194&amp;quot;&lt;br /&gt;
             w=&amp;quot;120&amp;quot;&lt;br /&gt;
             h=&amp;quot;22&amp;quot;&lt;br /&gt;
             Text=&amp;quot;Close&amp;quot;&lt;br /&gt;
             TextID=&amp;quot;&amp;quot;/&amp;gt;&lt;br /&gt;
&amp;lt;/Dialog&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Opening the Test UI ==&lt;br /&gt;
&lt;br /&gt;
Call &amp;lt;code&amp;gt;CustomGuiTest::OpenOrShowCustomGui()&amp;lt;/code&amp;gt; from your own test trigger, for example a debug hotkey, injected menu command or temporary packet/debug handler.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
if (pressedDebugKey) {&lt;br /&gt;
    CustomGuiTest::OpenOrShowCustomGui();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The input hook must already be installed, otherwise the window may render but not receive mouse or keyboard input.&lt;br /&gt;
&lt;br /&gt;
== Notes ==&lt;br /&gt;
&lt;br /&gt;
This is proof of concept code for the currently analyzed client build.&lt;br /&gt;
&lt;br /&gt;
The important parts are:&lt;br /&gt;
&lt;br /&gt;
* the custom owner is constructed with the client constructor;&lt;br /&gt;
* it is attached with &amp;lt;code&amp;gt;InitDialogWithParentOwner&amp;lt;/code&amp;gt;;&lt;br /&gt;
* XML is loaded through &amp;lt;code&amp;gt;StageBase::LoadXml&amp;lt;/code&amp;gt;;&lt;br /&gt;
* callbacks are installed through &amp;lt;code&amp;gt;AduGuiDialog::SetCallback&amp;lt;/code&amp;gt;;&lt;br /&gt;
* the owner is opened with &amp;lt;code&amp;gt;SetOpenFlag&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ActivateProcessing&amp;lt;/code&amp;gt;;&lt;br /&gt;
* input is forwarded through the hooked &amp;lt;code&amp;gt;StageManager::HandleInput&amp;lt;/code&amp;gt; path.&lt;br /&gt;
&lt;br /&gt;
Do not add a new global stage for this. The client stage list is fixed. For custom UI, attach to an existing reachable owner instead.&lt;br /&gt;
&lt;br /&gt;
This example intentionally avoids custom XML control type registration. For &amp;lt;code&amp;gt;Type=&amp;quot;CustomName&amp;quot;&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;GuiCtrl_*.xml&amp;lt;/code&amp;gt; controls, additional custom control factory and embedded dialog behavior has to be handled separately.&lt;br /&gt;
&lt;br /&gt;
[[Category:Client]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=FT_SDK&amp;diff=307</id>
		<title>FT SDK</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=FT_SDK&amp;diff=307"/>
		<updated>2026-05-20T08:43:01Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Overview ==&lt;br /&gt;
&lt;br /&gt;
This page documents the current reverse engineering baseline for the Fantasy Tennis client-side SDK used by JFTSE tooling, injected debugging code and client UI experiments.&lt;br /&gt;
&lt;br /&gt;
The SDK maps the known client GUI/stage layer into C++ wrappers around the original client structures. The current focus is the UI system around &amp;lt;code&amp;gt;StageManager&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ScreenRoot&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ScreenInputRoot&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;AduGuiDialog&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;AduGuiControl&amp;lt;/code&amp;gt; and all known built-in Adu GUI controls.&lt;br /&gt;
&lt;br /&gt;
The practical goal of this SDK is to make custom client UI possible without adding unsupported client stages. The Fantasy Tennis client uses a fixed stage model, so custom UI should attach to an existing stage owner, popup owner or embedded/custom GUI owner instead of creating an additional global stage.&lt;br /&gt;
&lt;br /&gt;
The SDK is intentionally conservative. Names describe current reverse engineering understanding, not original source names. Offsets, virtual slots and function addresses are tied to the currently analyzed Fantasy Tennis client build.&lt;br /&gt;
&lt;br /&gt;
== Current Focus ==&lt;br /&gt;
&lt;br /&gt;
The currently documented SDK covers:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;StageManager&amp;lt;/code&amp;gt; state tracking, current-stage access and stage switching helpers.&lt;br /&gt;
* &amp;lt;code&amp;gt;ScreenRoot&amp;lt;/code&amp;gt; ownership, parent chains, open state, processing state and input routing.&lt;br /&gt;
* &amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt; XML dialog ownership, GUI callback routing, priority child-owner behavior and event dispatch.&lt;br /&gt;
* &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt; built-in popup slots, popup creation helpers, selected/active popup state and game-dialog lifecycle hooks.&lt;br /&gt;
* &amp;lt;code&amp;gt;AduGuiDialog&amp;lt;/code&amp;gt; runtime layout, object array access, command-ID lookup, name lookup and callback/user-data storage.&lt;br /&gt;
* &amp;lt;code&amp;gt;AduGuiControl&amp;lt;/code&amp;gt; common runtime fields such as name, command id, type id, group id, subgroup id, rectangle, state sets, text and context menu data.&lt;br /&gt;
* Built-in Adu GUI controls:&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiStatic&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiButton&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiCheckBox&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiRadioButton&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiComboBox&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiSlider&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiGauge&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiEditBox&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiIMEEditBox&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiListBox&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiScrollBar&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiContextMenu&amp;lt;/code&amp;gt;&lt;br /&gt;
* Generic custom GUI controls through &amp;lt;code&amp;gt;AduGuiCustomControlBase&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Known concrete custom controls such as &amp;lt;code&amp;gt;FTRankingInfo&amp;lt;/code&amp;gt;.&lt;br /&gt;
* XML binding through &amp;lt;code&amp;gt;GuiBind&amp;lt;/code&amp;gt; arrays, command ids and control type ids.&lt;br /&gt;
* Runtime text assignment for static controls through &amp;lt;code&amp;gt;FTSDK::SetStaticText&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Custom type name registration through &amp;lt;code&amp;gt;GuiCustomControlRegistry&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Custom UI Direction ==&lt;br /&gt;
&lt;br /&gt;
The preferred custom UI direction is now:&lt;br /&gt;
&lt;br /&gt;
# Create or reuse a &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt; owner attached to an existing game stage or popup owner.&lt;br /&gt;
# Load a normal XML dialog with &amp;lt;code&amp;gt;StageBase::LoadXml&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Register additional XML control type names with &amp;lt;code&amp;gt;GuiCustomControlRegistry::RegisterTypeName&amp;lt;/code&amp;gt;.&lt;br /&gt;
# For simple embedded GUI controls, map a custom XML type name to &amp;lt;code&amp;gt;GuiControlType::Custom&amp;lt;/code&amp;gt; / type id &amp;lt;code&amp;gt;12&amp;lt;/code&amp;gt;.&lt;br /&gt;
# For each generic custom control instance, call &amp;lt;code&amp;gt;AduGuiCustomControlBase::LoadEmbeddedGuiXml&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Cache the inner controls from the embedded &amp;lt;code&amp;gt;AduGuiDialog&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Update inner controls directly, for example with &amp;lt;code&amp;gt;FTSDK::SetStaticText&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This mirrors the real client pattern used by controls such as &amp;lt;code&amp;gt;FTRankingInfo&amp;lt;/code&amp;gt;: the outer custom control is created by the dialog loader, then its concrete load function loads a separate &amp;lt;code&amp;gt;GuiCtrl_*.xml&amp;lt;/code&amp;gt;, binds/caches the inner controls and refreshes them from runtime data.&lt;br /&gt;
&lt;br /&gt;
Important: registering a name such as &amp;lt;code&amp;gt;MyCustomType&amp;lt;/code&amp;gt; to type id &amp;lt;code&amp;gt;12&amp;lt;/code&amp;gt; makes &amp;lt;code&amp;gt;Type=&amp;quot;MyCustomType&amp;quot;&amp;lt;/code&amp;gt; resolve to the generic custom control base. This does not create a new factory-backed custom type. Using type ids above the built-in range such as &amp;lt;code&amp;gt;100&amp;lt;/code&amp;gt;, requires extending or hooking the custom control factory. Without that, the default factory only creates the known built-in custom control ids.&lt;br /&gt;
&lt;br /&gt;
== Current Custom UI Caveats ==&lt;br /&gt;
&lt;br /&gt;
The custom UI path is partly proven but not fully finished.&lt;br /&gt;
&lt;br /&gt;
Known working pieces:&lt;br /&gt;
&lt;br /&gt;
* The client can resolve a custom XML type name to generic custom type &amp;lt;code&amp;gt;12&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;StageBase::LoadXml&amp;lt;/code&amp;gt; can create generic custom control instances from XML.&lt;br /&gt;
* The created generic custom controls appear in &amp;lt;code&amp;gt;AduGuiDialog::objectArray&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;AduGuiCustomControlBase::LoadEmbeddedGuiXml&amp;lt;/code&amp;gt; can load a nested &amp;lt;code&amp;gt;GuiCtrl_*.xml&amp;lt;/code&amp;gt;.&lt;br /&gt;
* The embedded dialog can be inspected and inner controls can be cached.&lt;br /&gt;
* Static text can be written through &amp;lt;code&amp;gt;FTSDK::SetStaticText&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Known caveats:&lt;br /&gt;
&lt;br /&gt;
* In manually constructed/attached owners, &amp;lt;code&amp;gt;StageBase::controlBindMap&amp;lt;/code&amp;gt; may not contain every control even when the dialog loaded correctly. For robust lookup, fall back to &amp;lt;code&amp;gt;AduGuiDialog::FindControlByCommandId&amp;lt;/code&amp;gt; and then &amp;lt;code&amp;gt;AduGuiDialog::FindControlByName&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Setting active/visible state on the outer custom row is not always enough if the embedded child dialog/control tree is not being processed or rendered by the same path as concrete client controls.&lt;br /&gt;
* The generic custom control rendering/update path still needs more reversing. The next useful target is the base custom control implementation used by real &amp;lt;code&amp;gt;GuiCtrl_*&amp;lt;/code&amp;gt; controls and the concrete GUI popup/stage base classes.&lt;br /&gt;
* Closing or hiding existing popups may happen by priority-child/parent-chain changes rather than by clean deactivation. Do not rely only on &amp;lt;code&amp;gt;OnDeactivate&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Stability Notes ==&lt;br /&gt;
&lt;br /&gt;
All offsets and function addresses are version specific. They are verified against the currently analyzed Fantasy Tennis client build through disassembly, runtime dumps, vtable summaries and real call sites.&lt;br /&gt;
&lt;br /&gt;
The SDK should be treated as a reverse engineering aid, not a stable API. Fields marked &amp;lt;code&amp;gt;unknown&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;field*&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;maybe&amp;lt;/code&amp;gt; or described conservatively are intentionally not finalized.&lt;br /&gt;
&lt;br /&gt;
Do not add another global stage. The client uses a fixed 24 stage model. Attach custom UI to an existing stage, popup owner, modal owner or embedded GUI owner.&lt;br /&gt;
&lt;br /&gt;
When testing custom UI, prefer defensive lookup and logging:&lt;br /&gt;
&lt;br /&gt;
* Check &amp;lt;code&amp;gt;owner-&amp;gt;XmlDialog()&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Dump &amp;lt;code&amp;gt;AduGuiDialog::objectArray&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Prefer &amp;lt;code&amp;gt;FindControlByCommandId&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Fall back to &amp;lt;code&amp;gt;FindControlByName&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Verify &amp;lt;code&amp;gt;ControlTypeId()&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Check &amp;lt;code&amp;gt;active&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;visible&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openFlag&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;processEnabled&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;parentChainEnabled&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Verify whether the target owner is reachable through the parent chain.&lt;br /&gt;
&lt;br /&gt;
== Current SDK Header ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
#pragma once&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;cstdint&amp;gt;&lt;br /&gt;
#include &amp;lt;cstddef&amp;gt;&lt;br /&gt;
#include &amp;lt;cstdio&amp;gt;&lt;br /&gt;
#include &amp;lt;cstring&amp;gt;&lt;br /&gt;
#include &amp;lt;cwchar&amp;gt;&lt;br /&gt;
#include &amp;lt;string&amp;gt;&lt;br /&gt;
#include &amp;lt;type_traits&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define FTSDK_THISCALL __thiscall&lt;br /&gt;
#define FTSDK_CDECL    __cdecl&lt;br /&gt;
#define FTSDK_STDCALL  __stdcall&lt;br /&gt;
#define FTSDK_FASTCALL __fastcall&lt;br /&gt;
&lt;br /&gt;
namespace FTSDK {&lt;br /&gt;
&lt;br /&gt;
    namespace Structs {&lt;br /&gt;
        struct FTStringA {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
&lt;br /&gt;
            union {&lt;br /&gt;
                char inlineBuffer[16];&lt;br /&gt;
                char* heapPtr;&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            uint32_t length;&lt;br /&gt;
            uint32_t capacity;&lt;br /&gt;
&lt;br /&gt;
            bool IsInline() const {&lt;br /&gt;
                return capacity &amp;lt; 16;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool IsValid() const {&lt;br /&gt;
                if (length &amp;gt; capacity || length &amp;gt; 4096)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (capacity &amp;gt; 0xFFFFFFFEu)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (!IsInline() &amp;amp;&amp;amp; !heapPtr)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                return true;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const char* Data() const {&lt;br /&gt;
                if (!IsValid())&lt;br /&gt;
                    return &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
                return IsInline() ? inlineBuffer : heapPtr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            int32_t Length() const {&lt;br /&gt;
                return IsValid() ? static_cast&amp;lt;int32_t&amp;gt;(length) : 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool Equals(const char* text) const {&lt;br /&gt;
                if (!text || !IsValid())&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                const size_t textLen = std::strlen(text);&lt;br /&gt;
                return textLen == length &amp;amp;&amp;amp; std::memcmp(Data(), text, length) == 0;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct FTStringW {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
&lt;br /&gt;
            union {&lt;br /&gt;
                wchar_t inlineBuffer[8];&lt;br /&gt;
                wchar_t* heapPtr;&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            uint32_t length;&lt;br /&gt;
            uint32_t capacity;&lt;br /&gt;
&lt;br /&gt;
            bool IsInline() const {&lt;br /&gt;
                return capacity &amp;lt; 8;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool IsValid() const {&lt;br /&gt;
                if (length &amp;gt; capacity || length &amp;gt; 4096)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (capacity &amp;gt; 0x7FFFFFFEu)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (!IsInline() &amp;amp;&amp;amp; !heapPtr)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                return true;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const wchar_t* Data() const {&lt;br /&gt;
                if (!IsValid())&lt;br /&gt;
                    return L&amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
                return IsInline() ? inlineBuffer : heapPtr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            int32_t Length() const {&lt;br /&gt;
                return IsValid() ? static_cast&amp;lt;int32_t&amp;gt;(length) : 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool Equals(const wchar_t* text) const {&lt;br /&gt;
                if (!text || !IsValid())&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                const size_t textLen = std::wcslen(text);&lt;br /&gt;
                return textLen == length &amp;amp;&amp;amp; std::memcmp(Data(), text, length * sizeof(wchar_t)) == 0;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        struct FTVector {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
            T* first;&lt;br /&gt;
            T* last;&lt;br /&gt;
            T* end;&lt;br /&gt;
&lt;br /&gt;
            int32_t Size() const {&lt;br /&gt;
                if (!first || !last || last &amp;lt; first)&lt;br /&gt;
                    return 0;&lt;br /&gt;
&lt;br /&gt;
                return static_cast&amp;lt;int32_t&amp;gt;(last - first);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            int32_t Capacity() const {&lt;br /&gt;
                if (!first || !end || end &amp;lt; first)&lt;br /&gt;
                    return 0;&lt;br /&gt;
&lt;br /&gt;
                return static_cast&amp;lt;int32_t&amp;gt;(end - first);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool IsEmpty() const {&lt;br /&gt;
                return Size() == 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            T&amp;amp; operator[](size_t index) {&lt;br /&gt;
                return first[index];&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const T&amp;amp; operator[](size_t index) const {&lt;br /&gt;
                return first[index];&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            T* Begin() {&lt;br /&gt;
                return first;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            T* End() {&lt;br /&gt;
                return last;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const T* Begin() const {&lt;br /&gt;
                return first;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const T* End() const {&lt;br /&gt;
                return last;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        struct FTList {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
            T* next;&lt;br /&gt;
            uint32_t count;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Key, typename Value&amp;gt;&lt;br /&gt;
        struct FTTreeNode {&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* left;&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* parent;&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* right;&lt;br /&gt;
&lt;br /&gt;
            Key key;&lt;br /&gt;
            Value value;&lt;br /&gt;
&lt;br /&gt;
            uint8_t rbColor;&lt;br /&gt;
            uint8_t isNil;&lt;br /&gt;
            uint8_t pad16[2];&lt;br /&gt;
&lt;br /&gt;
            bool IsNil() const {&lt;br /&gt;
                return isNil != 0;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Key, typename Value&amp;gt;&lt;br /&gt;
        struct FTTreeMap {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* head;&lt;br /&gt;
            uint32_t count;&lt;br /&gt;
&lt;br /&gt;
            bool IsEmpty() const {&lt;br /&gt;
                return count == 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            uint32_t Size() const {&lt;br /&gt;
                return count;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* Head() const {&lt;br /&gt;
                return head;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* Root() const {&lt;br /&gt;
                if (!head || head-&amp;gt;IsNil())&lt;br /&gt;
                    return nullptr;&lt;br /&gt;
&lt;br /&gt;
                auto* root = head-&amp;gt;parent;&lt;br /&gt;
                return root &amp;amp;&amp;amp; !root-&amp;gt;IsNil() ? root : nullptr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* FindNode(const Key&amp;amp; key) const {&lt;br /&gt;
                auto* node = Root();&lt;br /&gt;
&lt;br /&gt;
                while (node) {&lt;br /&gt;
                    if (node-&amp;gt;IsNil())&lt;br /&gt;
                        return nullptr;&lt;br /&gt;
&lt;br /&gt;
                    if (key &amp;lt; node-&amp;gt;key) {&lt;br /&gt;
                        node = node-&amp;gt;left;&lt;br /&gt;
                    }&lt;br /&gt;
                    else if (node-&amp;gt;key &amp;lt; key) {&lt;br /&gt;
                        node = node-&amp;gt;right;&lt;br /&gt;
                    }&lt;br /&gt;
                    else {&lt;br /&gt;
                        return node;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                return nullptr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            Value Find(const Key&amp;amp; key) const {&lt;br /&gt;
                auto* node = FindNode(key);&lt;br /&gt;
                return node ? node-&amp;gt;value : Value{};&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool Contains(const Key&amp;amp; key) const {&lt;br /&gt;
                return FindNode(key) != nullptr;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct GuiBind {&lt;br /&gt;
            int32_t commandId;&lt;br /&gt;
            const char* controlName;&lt;br /&gt;
            int32_t controlTypeId;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct Point {&lt;br /&gt;
            int32_t x;&lt;br /&gt;
            int32_t y;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct Rect {&lt;br /&gt;
            int32_t left;&lt;br /&gt;
            int32_t top;&lt;br /&gt;
            int32_t right;&lt;br /&gt;
            int32_t bottom;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct GuiRectStorage {&lt;br /&gt;
            void* vtable;&lt;br /&gt;
            int32_t left;&lt;br /&gt;
            int32_t top;&lt;br /&gt;
            int32_t right;&lt;br /&gt;
            int32_t bottom;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct StageOwnedResource {&lt;br /&gt;
            int32_t activeOrTransitionFlag;&lt;br /&gt;
            int32_t enabled;&lt;br /&gt;
            float baseOrDuration;&lt;br /&gt;
            float progressOrAlpha;&lt;br /&gt;
            float colorR_orParam10;&lt;br /&gt;
            float colorG_orParam14;&lt;br /&gt;
            float colorB_orParam18;&lt;br /&gt;
            void* resourceHandle;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct StageOverlayVertex {&lt;br /&gt;
            float x;&lt;br /&gt;
            float y;&lt;br /&gt;
            float z;&lt;br /&gt;
            float rhw;&lt;br /&gt;
            uint32_t color;&lt;br /&gt;
            float u;&lt;br /&gt;
            float v;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct StageOverlayQuad {&lt;br /&gt;
            StageOverlayVertex vertices[4];&lt;br /&gt;
&lt;br /&gt;
            int32_t unknown70;&lt;br /&gt;
            int32_t unknown74;&lt;br /&gt;
            int32_t widthInt;&lt;br /&gt;
            int32_t heightInt;&lt;br /&gt;
&lt;br /&gt;
            float x;&lt;br /&gt;
            float y;&lt;br /&gt;
            float width;&lt;br /&gt;
            float height;&lt;br /&gt;
&lt;br /&gt;
            float scaleX;&lt;br /&gt;
            float alpha;&lt;br /&gt;
            float rotationOrAngle;&lt;br /&gt;
&lt;br /&gt;
            uint8_t unknown9C;&lt;br /&gt;
            uint8_t unknown9D;&lt;br /&gt;
            uint8_t visibleOrEnabled;&lt;br /&gt;
            uint8_t pad9F;&lt;br /&gt;
&lt;br /&gt;
            uint8_t unknownA0[0x0C];&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        static_assert(sizeof(Point) == 0x08);&lt;br /&gt;
        static_assert(sizeof(Rect) == 0x10);&lt;br /&gt;
        static_assert(sizeof(GuiRectStorage) == 0x14);&lt;br /&gt;
        static_assert(sizeof(StageOwnedResource) == 0x20);&lt;br /&gt;
        static_assert(sizeof(FTStringA) == 0x1C);&lt;br /&gt;
        static_assert(sizeof(FTStringW) == 0x1C);&lt;br /&gt;
        static_assert(sizeof(GuiBind) == 0x0C);&lt;br /&gt;
        static_assert(sizeof(FTVector&amp;lt;void*&amp;gt;) == 0x10);&lt;br /&gt;
        static_assert(sizeof(StageOverlayQuad) == 0xAC);&lt;br /&gt;
        static_assert(sizeof(StageOverlayVertex) == 0x1C);&lt;br /&gt;
        static_assert(sizeof(FTList&amp;lt;void*&amp;gt;) == 0x0C);&lt;br /&gt;
        static_assert(sizeof(FTTreeNode&amp;lt;int32_t, void*&amp;gt;) == 0x18);&lt;br /&gt;
        static_assert(sizeof(FTTreeMap&amp;lt;int32_t, void*&amp;gt;) == 0x0C);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    enum GuiEventType : int32_t {&lt;br /&gt;
        ButtonClick = 0,&lt;br /&gt;
        ButtonOver = 1,&lt;br /&gt;
&lt;br /&gt;
        ComboBoxOpen = 2,&lt;br /&gt;
        ComboBoxClose = 3,&lt;br /&gt;
        ComboBoxSelectChange = 4,&lt;br /&gt;
&lt;br /&gt;
        RadioButtonChange = 5,&lt;br /&gt;
        CheckBoxChange = 6,&lt;br /&gt;
&lt;br /&gt;
        SliderValueChange = 7,&lt;br /&gt;
&lt;br /&gt;
        EditBoxEnter = 8,&lt;br /&gt;
        EditBoxChange = 9,&lt;br /&gt;
        EditBoxTAB = 10,&lt;br /&gt;
        EditBoxESC = 11,&lt;br /&gt;
        EditBoxCharLimit = 12,&lt;br /&gt;
        EditBoxKeyUp = 13,&lt;br /&gt;
        EditBoxKeyDown = 14,&lt;br /&gt;
&lt;br /&gt;
        ListBoxDBClick = 15,&lt;br /&gt;
        ListBoxSelect = 16,&lt;br /&gt;
        ListBoxSelectEnd = 17,&lt;br /&gt;
&lt;br /&gt;
        ContextMenuClick = 18,&lt;br /&gt;
        ScrollBarPosChange = 19&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum GuiControlType : int32_t {&lt;br /&gt;
        Static = 0,&lt;br /&gt;
        Button = 1,&lt;br /&gt;
        CheckBox = 2,&lt;br /&gt;
        RadioButton = 3,&lt;br /&gt;
        ComboBox = 4,&lt;br /&gt;
        Slider = 5,&lt;br /&gt;
        Gauge = 6,&lt;br /&gt;
        EditBox = 7,&lt;br /&gt;
        IMEEditBox = 8,&lt;br /&gt;
        ListBox = 9,&lt;br /&gt;
        ScrollBar = 10,&lt;br /&gt;
        ContextMenu = 11,&lt;br /&gt;
		Custom = 12,&lt;br /&gt;
&lt;br /&gt;
        FTRankingInfoType = 68,&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum GuiVisualStateName : int32_t {&lt;br /&gt;
        StateAll = 0,&lt;br /&gt;
        StateNormal = 1,&lt;br /&gt;
        StateOver = 2,&lt;br /&gt;
        StateFocus = 3,&lt;br /&gt;
        StatePressed = 4,&lt;br /&gt;
        StateDisabled = 5,&lt;br /&gt;
        StateHidden = 6&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum GuiBlendMode : int32_t {&lt;br /&gt;
        BlendModulate = 0,&lt;br /&gt;
        BlendAdd = 1&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum GuiTextFx : int32_t {&lt;br /&gt;
        TextFxNone = 0,&lt;br /&gt;
        TextFxShadow = 1,&lt;br /&gt;
        TextFxOutline = 2&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum MessageBoxMode : int32_t {&lt;br /&gt;
        MessageBox_OK = 1,&lt;br /&gt;
        MessageBox_TimedOK = 2,&lt;br /&gt;
        MessageBox_YesNo = 3,&lt;br /&gt;
        MessageBox_TimedYesNo = 4&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiStateSet;&lt;br /&gt;
    class AduGuiDialog;&lt;br /&gt;
    class AduGuiControl;&lt;br /&gt;
    class AduGuiStatic;&lt;br /&gt;
    class AduGuiButton;&lt;br /&gt;
    class AduGuiRadioButton;&lt;br /&gt;
    class AduGuiCheckBox;&lt;br /&gt;
    class AduGuiComboBox;&lt;br /&gt;
    class AduGuiEditBox;&lt;br /&gt;
    class AduGuiIMEEditBox;&lt;br /&gt;
    class AduGuiListBox;&lt;br /&gt;
    class AduGuiScrollBar;&lt;br /&gt;
    class AduGuiSlider;&lt;br /&gt;
    class AduGuiGauge;&lt;br /&gt;
    class AduGuiContextMenu;&lt;br /&gt;
&lt;br /&gt;
    using GuiCallbackFn = void(FTSDK_STDCALL*)(GuiEventType eventType, int commandId, int param, void* userData);&lt;br /&gt;
    using CreateCustomGuiControlFn = AduGuiControl * (FTSDK_CDECL*)(AduGuiDialog* dialog, int32_t customTypeId);&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiVisualStateInfo {&lt;br /&gt;
        int32_t kind;&lt;br /&gt;
        int32_t field04;&lt;br /&gt;
        int32_t field08;&lt;br /&gt;
        int32_t field0C;&lt;br /&gt;
        float timing;&lt;br /&gt;
        int32_t field14;&lt;br /&gt;
        int32_t field18;&lt;br /&gt;
        int32_t field1C;&lt;br /&gt;
        int32_t alternateStateIndex;&lt;br /&gt;
        int32_t field24;&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiVisualStateInfo) == 0x28);&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiParsedStateDef {&lt;br /&gt;
        int32_t image[6]; // +0x00&lt;br /&gt;
        int32_t color[6]; // +0x18&lt;br /&gt;
        int32_t textColor[6]; // +0x30&lt;br /&gt;
        int32_t textFxColor[6]; // +0x48&lt;br /&gt;
        int32_t blend[6]; // +0x60&lt;br /&gt;
        int32_t fx[6]; // +0x78&lt;br /&gt;
&lt;br /&gt;
        uint8_t scale[6]; // +0x90&lt;br /&gt;
        uint8_t tiling[6]; // +0x96&lt;br /&gt;
&lt;br /&gt;
        int32_t offsetX[6]; // +0x9C&lt;br /&gt;
        int32_t offsetY[6]; // +0xB4&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiParsedStateDef) == 0xCC);&lt;br /&gt;
&lt;br /&gt;
    // Dialog-side parsed XML/control definition.&lt;br /&gt;
    // This is not AduGuiControl runtime storage.&lt;br /&gt;
    //&lt;br /&gt;
    // Confirmed:&lt;br /&gt;
    // - sub_624F90 initializes this structure.&lt;br /&gt;
    // - sub_614F60 fills it from one &amp;lt;Control&amp;gt;.&lt;br /&gt;
    // - AduGuiControl::LoadFromXml consumes it.&lt;br /&gt;
    // - parsedStates uses 0xCC-byte parser-side state records.&lt;br /&gt;
    // - eventSoundIds are indexed by GuiEventType and initialized to invalid/NaN-like values.&lt;br /&gt;
    struct AduGuiParsedControlDef {&lt;br /&gt;
        Structs::FTVector&amp;lt;AduGuiParsedStateDef&amp;gt; parsedStates; // +0x0000&lt;br /&gt;
&lt;br /&gt;
        int32_t eventSoundIds[20]; // +0x0010, indexed by GuiEventType&lt;br /&gt;
&lt;br /&gt;
        uint8_t stateAndImageParseStorage[0x804]; // +0x0060..+0x0863&lt;br /&gt;
&lt;br /&gt;
        wchar_t resolvedText[256]; // +0x0864, assigned to AduGuiControl::text&lt;br /&gt;
&lt;br /&gt;
        uint8_t tooltipAndMenuParseStorage[0x600]; // +0x0A64..+0x1063&lt;br /&gt;
&lt;br /&gt;
        uint8_t enable;       // +0x1064, XML Enable, default 1&lt;br /&gt;
        uint8_t enabledState; // +0x1065, XML EnabledState, default 1&lt;br /&gt;
        uint8_t pad1066[2];&lt;br /&gt;
&lt;br /&gt;
        int32_t x;      // +0x1068, XML x&lt;br /&gt;
        int32_t y;      // +0x106C, XML y&lt;br /&gt;
        int32_t width;  // +0x1070, XML w&lt;br /&gt;
        int32_t height; // +0x1074, XML h&lt;br /&gt;
&lt;br /&gt;
        int32_t alignX; // XML AlignX&lt;br /&gt;
        int32_t alignY; // XML AlignY&lt;br /&gt;
&lt;br /&gt;
        float scaleX;  // XML ScaleX, default 1.0&lt;br /&gt;
        float scaleY;  // XML ScaleY, default 1.0&lt;br /&gt;
        float rotate;  // XML Rotate&lt;br /&gt;
        float centerX; // XML CenterX&lt;br /&gt;
        float centerY; // XML CenterY&lt;br /&gt;
&lt;br /&gt;
        int32_t flipX; // XML FlipX&lt;br /&gt;
&lt;br /&gt;
        int32_t textAlignX; // XML TextAlignX&lt;br /&gt;
        int32_t textAlignY; // XML TextAlignY&lt;br /&gt;
        int32_t textFx;     // XML TextFx&lt;br /&gt;
&lt;br /&gt;
        int32_t textMarginLeft;   // XML TextMargin&lt;br /&gt;
        int32_t textMarginTop;&lt;br /&gt;
        int32_t textMarginRight;&lt;br /&gt;
        int32_t textMarginBottom;&lt;br /&gt;
&lt;br /&gt;
        int32_t wordWrap;   // XML WordWrap&lt;br /&gt;
        int32_t groupId;    // XML GroupID&lt;br /&gt;
        int32_t subGroupId; // XML SubGroupID&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknown10C0[0x24];&lt;br /&gt;
&lt;br /&gt;
        int32_t tabStop;      // +0x10E4, XML TabStop&lt;br /&gt;
        int32_t initialStage; // XML Stage&lt;br /&gt;
        int32_t bindStage;    // XML BindStage&lt;br /&gt;
        int32_t hotKey;       // XML HotKey&lt;br /&gt;
&lt;br /&gt;
        char contextMenuName[256]; // +0x10F4, XML Menu&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiParsedControlDef) == 0x11F4);&lt;br /&gt;
&lt;br /&gt;
    namespace Constants {&lt;br /&gt;
        constexpr int32_t StageOwnerCount = 24;&lt;br /&gt;
        constexpr int32_t KnownBuiltinPopupSlotCount = 10;&lt;br /&gt;
        constexpr int32_t GuiEventTypeCount = 20;&lt;br /&gt;
        constexpr int32_t GuiVisualStateNameCount = 7;&lt;br /&gt;
        constexpr int32_t ParsedGuiStateSize = 0xCC;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    class AduBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual AduBase* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t unk04;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool Unk04Enabled() const {&lt;br /&gt;
            return unk04 != 0;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGameObj : public AduBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual AduGameObj* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetDirtyValue(int32_t value) = 0;&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL ResetElapsedTimeRecursive() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetActive(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetUpdateBlocked(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetVisible(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL UpdateRecursive(float dt) = 0;&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL ProcessVisibleRecursive() = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t dirtyValue; // +0x08&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* firstChild;&lt;br /&gt;
        AduGameObj* nextSibling;&lt;br /&gt;
&lt;br /&gt;
        uint8_t dirtyFlag;&lt;br /&gt;
        uint8_t active;&lt;br /&gt;
        uint8_t updateBlocked;&lt;br /&gt;
        uint8_t visible;&lt;br /&gt;
&lt;br /&gt;
        float elapsedTime;&lt;br /&gt;
        float deltaTime;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsDirty() const { return dirtyFlag != 0; }&lt;br /&gt;
        bool IsActive() const { return active != 0; }&lt;br /&gt;
        bool IsUpdateBlocked() const { return updateBlocked != 0; }&lt;br /&gt;
        bool IsVisible() const { return visible != 0; }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* FirstChild() const { return firstChild; }&lt;br /&gt;
        AduGameObj* NextSibling() const { return nextSibling; }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Callback&amp;gt;&lt;br /&gt;
        void ForEachChild(Callback&amp;amp;&amp;amp; callback) {&lt;br /&gt;
            for (AduGameObj* child = firstChild; child; child = child-&amp;gt;nextSibling)&lt;br /&gt;
                callback(child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Callback&amp;gt;&lt;br /&gt;
        void ForEachChild(Callback&amp;amp;&amp;amp; callback) const {&lt;br /&gt;
            for (AduGameObj* child = firstChild; child; child = child-&amp;gt;nextSibling)&lt;br /&gt;
                callback(child);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiControl : public AduGameObj {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual bool FTSDK_THISCALL RecalculateRect() = 0; // vt+0x20, rect = {x,y,x+w,y+h}&lt;br /&gt;
        virtual bool FTSDK_THISCALL LoadFromXml(const char* name, void* xmlParser, AduGuiParsedControlDef* parsedControlDef) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL PostLoadResolve() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual AduGuiStatic* FTSDK_THISCALL AsStatic() = 0; // vt+0x2C&lt;br /&gt;
        virtual AduGuiButton* FTSDK_THISCALL AsButton() = 0;&lt;br /&gt;
        virtual AduGuiRadioButton* FTSDK_THISCALL AsRadioButton() = 0;&lt;br /&gt;
        virtual AduGuiCheckBox* FTSDK_THISCALL AsCheckBox() = 0;&lt;br /&gt;
        virtual AduGuiComboBox* FTSDK_THISCALL AsComboBox() = 0;&lt;br /&gt;
        virtual AduGuiEditBox* FTSDK_THISCALL AsEditBox() = 0;&lt;br /&gt;
        virtual AduGuiIMEEditBox* FTSDK_THISCALL AsIMEEditBox() = 0;&lt;br /&gt;
        virtual AduGuiListBox* FTSDK_THISCALL AsListBox() = 0;&lt;br /&gt;
        virtual AduGuiScrollBar* FTSDK_THISCALL AsScrollBar() = 0;&lt;br /&gt;
        virtual AduGuiSlider* FTSDK_THISCALL AsSlider() = 0;&lt;br /&gt;
        virtual AduGuiGauge* FTSDK_THISCALL AsGauge() = 0;&lt;br /&gt;
        virtual AduGuiContextMenu* FTSDK_THISCALL AsContextMenu() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual bool FTSDK_THISCALL DefaultFalse5C() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL IsOwnerOrRelatedObject(void* candidate) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL DefaultTrue64() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ResetInteractionState() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleKeyboardCharInput(uint32_t msg, int32_t wParam, int32_t lParam) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleKeyboardNavigation(uint32_t msg, int32_t wParam, int32_t lParam) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleMouseInput(uint32_t msg, int32_t wParam, int32_t lParam, int32_t a4, int32_t a5) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL DefaultFalse78() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL SetPressedFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ClearPressedFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL SetOverFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ClearOverFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL OnHotKeyMatched() = 0;&lt;br /&gt;
        virtual Structs::Rect* FTSDK_THISCALL GetRectPtr() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL ContainsPoint(Structs::Point pt) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetEnabledState(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL IsEnabledState() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL DefaultFalseA0() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleHotKey(int32_t hotKeyValue) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ApplyStateFieldE7E0(int32_t stateSetIndex, int32_t visualStateIndex, int32_t parsedStateIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ApplyStateFieldE770(int32_t stateSetIndex, int32_t visualStateIndex) = 0;&lt;br /&gt;
        virtual AduGuiStateSet* FTSDK_THISCALL GetStateSet(int32_t stateSetIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL EnsureStateSetCopy(uint32_t stateSetIndex, const AduGuiStateSet* source) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL UpdateCurrentVisualState() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetStateFlagE8A0(int32_t visualStateIndex, bool value, int32_t stateSetIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetStateRangeD850(int32_t visualStateIndex, float minValue, float maxValue, int32_t stateSetIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ClearStateRuntimeFlag(int32_t visualStateIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ClearVisualStateInfo(int32_t visualStateIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ConfigureVisualStateInfo(int32_t visualStateIndex, bool enabled, float timing, int32_t alternateStateIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetEventSoundId(int32_t soundId, int32_t eventIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL GetEventSoundId(int32_t eventIndex) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiStateSet** stateSets; // +0x20&lt;br /&gt;
        int32_t stateCount;&lt;br /&gt;
        int32_t stateCapacity;&lt;br /&gt;
&lt;br /&gt;
        Structs::FTStringA name; // XML Name&lt;br /&gt;
&lt;br /&gt;
        int32_t commandId;     // +0x48, bind command id, ctor -1&lt;br /&gt;
        int32_t groupId;       // XML GroupID&lt;br /&gt;
        int32_t controlTypeId; // XML Type, GuiControlType&lt;br /&gt;
        int32_t hotKey;        // XML HotKey&lt;br /&gt;
        int32_t subGroupId;    // XML SubGroupID, likely&lt;br /&gt;
&lt;br /&gt;
        uint8_t enabledStateFlag; // XML EnabledState via SetEnabledState&lt;br /&gt;
        uint8_t interactionActiveFlag;&lt;br /&gt;
        uint8_t focusFlag;&lt;br /&gt;
        uint8_t pad5F;&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect rect; // calculated from x/y/width/height&lt;br /&gt;
&lt;br /&gt;
        int32_t currentVisualState; // ctor 5&lt;br /&gt;
        int32_t selectedStateIndex; // XML Stage / selected visual stage&lt;br /&gt;
&lt;br /&gt;
        uint8_t bindStage; // XML BindStage&lt;br /&gt;
        uint8_t debugFlag; // XML Debug&lt;br /&gt;
        uint8_t overFlag;&lt;br /&gt;
        uint8_t pressedFlag;&lt;br /&gt;
        uint8_t field7C;&lt;br /&gt;
        uint8_t pad7D[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t x;&lt;br /&gt;
        int32_t y;&lt;br /&gt;
        int32_t width;&lt;br /&gt;
        int32_t height;&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* ownerDialog;&lt;br /&gt;
        void* relatedObject;&lt;br /&gt;
        int32_t controlIndex;&lt;br /&gt;
        int32_t dx;&lt;br /&gt;
        int32_t dy;&lt;br /&gt;
&lt;br /&gt;
        AduGuiVisualStateInfo visualStateInfos[7];&lt;br /&gt;
&lt;br /&gt;
        Structs::FTStringW text;&lt;br /&gt;
        AduGuiControl* resolvedContextMenuControl; // resolved from XML Menu, type ContextMenu&lt;br /&gt;
        Structs::FTStringA contextMenuName;        // XML Menu&lt;br /&gt;
&lt;br /&gt;
        uint8_t field1F8; // ctor 1&lt;br /&gt;
        uint8_t pad1F9[3];&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiStateSet** StateSets() const { return stateSets; }&lt;br /&gt;
        int32_t StateCount() const { return stateCount; }&lt;br /&gt;
        int32_t StateCapacity() const { return stateCapacity; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::FTStringA&amp;amp; Name() const { return name; }&lt;br /&gt;
        int32_t CommandId() const { return commandId; }&lt;br /&gt;
        int32_t GroupId() const { return groupId; }&lt;br /&gt;
        int32_t ControlTypeId() const { return controlTypeId; }&lt;br /&gt;
        int32_t HotKey() const { return hotKey; }&lt;br /&gt;
        int32_t SubGroupId() const { return subGroupId; }&lt;br /&gt;
&lt;br /&gt;
        bool IsType(GuiControlType type) const { return controlTypeId == static_cast&amp;lt;int32_t&amp;gt;(type); }&lt;br /&gt;
        bool IsEnabledStateFlag() const { return enabledStateFlag != 0; }&lt;br /&gt;
        bool IsInteractionActiveFlag() const { return interactionActiveFlag != 0; }&lt;br /&gt;
        bool IsFocusFlag() const { return focusFlag != 0; }&lt;br /&gt;
        bool IsBindStage() const { return bindStage != 0; }&lt;br /&gt;
        bool IsDebugFlag() const { return debugFlag != 0; }&lt;br /&gt;
        bool IsOverFlag() const { return overFlag != 0; }&lt;br /&gt;
        bool IsPressedFlag() const { return pressedFlag != 0; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; Rect() const { return rect; }&lt;br /&gt;
        Structs::Rect&amp;amp; Rect() { return rect; }&lt;br /&gt;
&lt;br /&gt;
        int32_t CurrentVisualState() const { return currentVisualState; }&lt;br /&gt;
        int32_t SelectedStateIndex() const { return selectedStateIndex; }&lt;br /&gt;
&lt;br /&gt;
        int32_t X() const { return x; }&lt;br /&gt;
        int32_t Y() const { return y; }&lt;br /&gt;
        int32_t Width() const { return width; }&lt;br /&gt;
        int32_t Height() const { return height; }&lt;br /&gt;
        int32_t Right() const { return x + width; }&lt;br /&gt;
        int32_t Bottom() const { return y + height; }&lt;br /&gt;
&lt;br /&gt;
        int32_t Dx() const { return dx; }&lt;br /&gt;
        int32_t Dy() const { return dy; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* OwnerDialog() const { return ownerDialog; }&lt;br /&gt;
        void* RelatedObject() const { return relatedObject; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::FTStringW&amp;amp; Text() const { return text; }&lt;br /&gt;
        const Structs::FTStringA&amp;amp; ContextMenuName() const { return contextMenuName; }&lt;br /&gt;
        AduGuiControl* ResolvedContextMenuControl() const { return resolvedContextMenuControl; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiStatic : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual int FTSDK_THISCALL RenderVisuals(void* renderCtx, int flags, const Structs::Rect* rect) = 0; // vt+0xD8&lt;br /&gt;
        virtual bool FTSDK_THISCALL RenderText(int visualIndex) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL RenderState(int stateIndex) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        Structs::FTStringW sourceTextOrTextId;&lt;br /&gt;
        Structs::FTStringW resolvedDisplayText;&lt;br /&gt;
&lt;br /&gt;
        uint8_t textDirtyFlag;&lt;br /&gt;
        uint8_t resolveTextFlag;&lt;br /&gt;
        uint8_t pad236[2];&lt;br /&gt;
&lt;br /&gt;
        int32_t field238;&lt;br /&gt;
        int32_t textFx;&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect textMargin;&lt;br /&gt;
&lt;br /&gt;
        int32_t textScrollOffsetX;&lt;br /&gt;
        uint32_t clippedTextWidth;&lt;br /&gt;
        uint8_t clippedTextRenderFlag;&lt;br /&gt;
        uint8_t pad259[3];&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        const Structs::FTStringW&amp;amp; SourceTextOrTextId() const { return sourceTextOrTextId; }&lt;br /&gt;
        const Structs::FTStringW&amp;amp; ResolvedDisplayText() const { return resolvedDisplayText; }&lt;br /&gt;
        const Structs::Rect&amp;amp; TextMargin() const { return textMargin; }&lt;br /&gt;
        bool IsTextDirty() const { return textDirtyFlag != 0; }&lt;br /&gt;
        bool UsesTextResolve() const { return resolveTextFlag != 0; }&lt;br /&gt;
        bool UsesClippedTextRender() const { return clippedTextRenderFlag != 0; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiButton : public AduGuiStatic {&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t buttonClickSoundId;&lt;br /&gt;
        int32_t buttonOverSoundId;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t ButtonClickSoundId() const { return buttonClickSoundId; }&lt;br /&gt;
        int32_t ButtonOverSoundId() const { return buttonOverSoundId; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiEditTextBuffer {&lt;br /&gt;
        void (FTSDK_CDECL* callback)(int owner); // +0x00&lt;br /&gt;
        void* owner; // +0x04&lt;br /&gt;
        void* scriptStringAnalysis; // +0x08&lt;br /&gt;
        wchar_t* text; // +0x0C&lt;br /&gt;
        uint32_t capacity; // +0x10&lt;br /&gt;
        uint32_t field14; // +0x14&lt;br /&gt;
&lt;br /&gt;
        uint8_t dirtyFlag; // +0x18&lt;br /&gt;
        uint8_t pad19[3];&lt;br /&gt;
&lt;br /&gt;
        uint32_t field1C; // +0x1C&lt;br /&gt;
        uint32_t field20; // +0x20&lt;br /&gt;
        uint32_t field24; // +0x24&lt;br /&gt;
        uint32_t field28; // +0x28&lt;br /&gt;
&lt;br /&gt;
        uint8_t hasScriptStringAnalysis; // +0x2C&lt;br /&gt;
        uint8_t pad2D[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t maxLength; // +0x30&lt;br /&gt;
&lt;br /&gt;
        const wchar_t* Text() const { return text ? text : L&amp;quot;&amp;quot;; }&lt;br /&gt;
        uint32_t Capacity() const { return capacity; }&lt;br /&gt;
        int32_t MaxLength() const { return maxLength; }&lt;br /&gt;
        std::size_t Length() const { return std::wcslen(Text()); }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiEditTextBuffer) == 0x34);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiEditBox : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual int FTSDK_THISCALL SetEditTextColor(int32_t color) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        uint32_t lineCacheUnknown; // +0x1FC&lt;br /&gt;
        void** lineCacheFirst; // +0x200&lt;br /&gt;
        void** lineCacheLast; // +0x204&lt;br /&gt;
        void** lineCacheEnd; // +0x208&lt;br /&gt;
&lt;br /&gt;
        AduGuiEditTextBuffer editText; // +0x20C&lt;br /&gt;
&lt;br /&gt;
        int32_t outerInset; // +0x240&lt;br /&gt;
        int32_t innerInset; // +0x244&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect textRect; // +0x248&lt;br /&gt;
        Structs::Rect visualRect; // +0x258&lt;br /&gt;
&lt;br /&gt;
        double caretBlinkInterval; // +0x268&lt;br /&gt;
        double lastCaretBlinkTime; // +0x270&lt;br /&gt;
&lt;br /&gt;
        uint8_t caretVisibleFlag; // +0x278&lt;br /&gt;
        uint8_t pad279[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t caretIndex; // +0x27C&lt;br /&gt;
&lt;br /&gt;
        uint8_t insertMode; // +0x280&lt;br /&gt;
        uint8_t pad281[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t selectionAnchorIndex; // +0x284&lt;br /&gt;
        int32_t editTextColor; // +0x288&lt;br /&gt;
        int32_t field28C;&lt;br /&gt;
        int32_t selectionColorOrField290;&lt;br /&gt;
        int32_t caretColorOrField294;&lt;br /&gt;
&lt;br /&gt;
        uint8_t field298;&lt;br /&gt;
        uint8_t numericOnlyFlag;&lt;br /&gt;
        uint8_t pad29A[2];&lt;br /&gt;
&lt;br /&gt;
        int32_t field29C;&lt;br /&gt;
&lt;br /&gt;
        uint8_t mouseSelectingFlag; // +0x2A0&lt;br /&gt;
        uint8_t preventOverflowFlag; // +0x2A1&lt;br /&gt;
        uint8_t multilineFlag; // +0x2A2&lt;br /&gt;
        uint8_t field2A3;&lt;br /&gt;
&lt;br /&gt;
        int32_t lineHeight; // +0x2A4, XML Option/LineHeight&lt;br /&gt;
        int32_t field2A8;&lt;br /&gt;
        int32_t field2AC;&lt;br /&gt;
&lt;br /&gt;
        uint8_t field2B0;&lt;br /&gt;
        uint8_t pad2B1[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t field2B4;&lt;br /&gt;
        int32_t field2B8;&lt;br /&gt;
&lt;br /&gt;
        int32_t editEventSoundIds[7]; // +0x2BC, EditBoxEnter..EditBoxKeyDown&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        const wchar_t* TextW() const { return editText.Text(); }&lt;br /&gt;
        std::size_t TextLength() const { return editText.Length(); }&lt;br /&gt;
        uint32_t TextCapacity() const { return editText.Capacity(); }&lt;br /&gt;
&lt;br /&gt;
        bool IsInsertMode() const { return insertMode != 0; }&lt;br /&gt;
        bool IsNumericOnly() const { return numericOnlyFlag != 0; }&lt;br /&gt;
        bool IsMultiline() const { return multilineFlag != 0; }&lt;br /&gt;
&lt;br /&gt;
        int32_t EditEventSoundId(GuiEventType eventType) const {&lt;br /&gt;
            const int32_t index = static_cast&amp;lt;int32_t&amp;gt;(eventType) - static_cast&amp;lt;int32_t&amp;gt;(EditBoxEnter);&lt;br /&gt;
            if (index &amp;lt; 0 || index &amp;gt;= 7)&lt;br /&gt;
                return -1;&lt;br /&gt;
&lt;br /&gt;
            return editEventSoundIds[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; TextRect() const { return textRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; VisualRect() const { return visualRect; }&lt;br /&gt;
&lt;br /&gt;
        int32_t OuterInset() const { return outerInset; }&lt;br /&gt;
        int32_t InnerInset() const { return innerInset; }&lt;br /&gt;
&lt;br /&gt;
        int32_t CaretIndex() const { return caretIndex; }&lt;br /&gt;
        int32_t SelectionAnchorIndex() const { return selectionAnchorIndex; }&lt;br /&gt;
        bool HasSelection() const { return caretIndex != selectionAnchorIndex; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiIMEEditBox : public AduGuiEditBox {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleImeComposition(int a2, int a3, uint16_t compositionFlags) = 0; // vt+0xD8&lt;br /&gt;
        virtual bool FTSDK_THISCALL ResetImeComposition(int a1, int a2) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleImeCandidateList(int notifyType, int param) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL RenderImeCandidateOrReadingWindow(int a2, int a3, bool readingWindow) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL RenderImeCompositionString(int a2, int a3) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL RenderImeStatusIndicator(int a2, int a3) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ApplyImeTextStyle(int styleValue) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t imeCandidatePageBackgroundColor; // +0x2D8&lt;br /&gt;
        int32_t imeCandidateTextColor; // +0x2DC&lt;br /&gt;
        int32_t imeCandidateSelectedTextColor; // +0x2E0&lt;br /&gt;
        int32_t imeCandidateSelectedBackColor; // +0x2E4&lt;br /&gt;
        int32_t imeCandidateBorderColor; // +0x2E8&lt;br /&gt;
        int32_t imeCompositionTextColor; // +0x2EC&lt;br /&gt;
        int32_t imeCompositionBackColor; // +0x2F0&lt;br /&gt;
        int32_t imeCompositionBorderColor; // +0x2F4&lt;br /&gt;
        int32_t imeReadingTextColor; // +0x2F8&lt;br /&gt;
        int32_t imeReadingBackColor; // +0x2FC&lt;br /&gt;
        int32_t imeReadingBorderColor; // +0x300&lt;br /&gt;
        int32_t imeField304;&lt;br /&gt;
        int32_t imeField308;&lt;br /&gt;
        int32_t imeField30C;&lt;br /&gt;
        int32_t imeField310;&lt;br /&gt;
        int32_t imeField314;&lt;br /&gt;
        int32_t imeField318;&lt;br /&gt;
        int32_t imeField31C;&lt;br /&gt;
        int32_t imeField320;&lt;br /&gt;
        int32_t imeField324;&lt;br /&gt;
&lt;br /&gt;
        uint8_t imeActiveFlag; // +0x328&lt;br /&gt;
        uint8_t pad329[3];&lt;br /&gt;
        uint8_t pad32C[4];&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiCheckBox : public AduGuiButton {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL SetChecked(bool checked, bool dispatchEvent) = 0; // vt+0xE4&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        uint8_t hasSeparateCheckImage; // +0x264&lt;br /&gt;
        uint8_t checkedFlag; // +0x265&lt;br /&gt;
        uint8_t pad266[2];&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect checkImageRect; // +0x268&lt;br /&gt;
        Structs::Rect checkLabelHitRect; // +0x278&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;int32_t&amp;gt; linkedCheckCommandIds; // +0x288&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsChecked() const { return checkedFlag != 0; }&lt;br /&gt;
        bool HasSeparateCheckImage() const { return hasSeparateCheckImage != 0; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; CheckImageRect() const { return checkImageRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; CheckLabelHitRect() const { return checkLabelHitRect; }&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;int32_t&amp;gt;&amp;amp; LinkedCheckCommandIds() { return linkedCheckCommandIds; }&lt;br /&gt;
        const Structs::FTVector&amp;lt;int32_t&amp;gt;&amp;amp; LinkedCheckCommandIds() const { return linkedCheckCommandIds; }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiCheckBox) == 0x298);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiRadioButton : public AduGuiCheckBox {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL SetRadioChecked(bool checked, bool clearGroup, bool dispatchEvent) = 0; // vt+0xE8&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        uint8_t allowRightMouseToggleFlag; // +0x298&lt;br /&gt;
        uint8_t pad299[3];&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool AllowsRightMouseToggle() const { return allowRightMouseToggleFlag != 0; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiSlider : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t value; // +0x1FC, XML Value, ctor 50&lt;br /&gt;
        int32_t minValue; // +0x200, XML Range min, ctor 0&lt;br /&gt;
        int32_t maxValue; // +0x204, XML Range max, ctor 100&lt;br /&gt;
&lt;br /&gt;
        int32_t dragStartX; // +0x208&lt;br /&gt;
        int32_t dragOffsetX; // +0x20C&lt;br /&gt;
&lt;br /&gt;
        int32_t thumbOffsetX; // +0x210, calculated from value/range&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect thumbRect; // +0x214&lt;br /&gt;
&lt;br /&gt;
        int32_t sliderValueChangeSoundId; // +0x224, GuiEventType::SliderValueChange&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t Value() const { return value; }&lt;br /&gt;
        int32_t MinValue() const { return minValue; }&lt;br /&gt;
        int32_t MaxValue() const { return maxValue; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; ThumbRect() const { return thumbRect; }&lt;br /&gt;
&lt;br /&gt;
        int32_t SliderValueChangeSoundId() const { return sliderValueChangeSoundId; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiGauge : public AduGuiStatic {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL GetRange() = 0; // vt+0xE4&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL GetValue() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetRange(int32_t range) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetValue(int32_t value) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t range; // +0x25C, XML Range, ctor 100&lt;br /&gt;
        int32_t value; // +0x260, XML Value, ctor 50&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t Range() const { return range; }&lt;br /&gt;
        int32_t Value() const { return value; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiScrollBar : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL ContainsScrollHitRect(Structs::Point pt) = 0; // vt+0xD8&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        uint8_t scrollEnabledFlag; // +0x1FC&lt;br /&gt;
        uint8_t thumbDraggingFlag; // +0x1FD&lt;br /&gt;
        uint8_t pad1FE[2];&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect upButtonRect; // +0x200&lt;br /&gt;
        Structs::Rect downButtonRect; // +0x210&lt;br /&gt;
        Structs::Rect trackRect; // +0x220&lt;br /&gt;
        Structs::Rect thumbRect; // +0x230&lt;br /&gt;
&lt;br /&gt;
        int32_t scrollValue; // +0x240&lt;br /&gt;
        int32_t pageSize; // +0x244&lt;br /&gt;
        int32_t minValue; // +0x248&lt;br /&gt;
        int32_t maxValue; // +0x24C&lt;br /&gt;
&lt;br /&gt;
        Structs::Point lastMousePoint; // +0x250&lt;br /&gt;
&lt;br /&gt;
        int32_t repeatMode; // +0x258, 0 none, 1 up initial, 2 down initial, 3 up repeat, 4 down repeat&lt;br /&gt;
        double lastRepeatTime; // +0x260&lt;br /&gt;
&lt;br /&gt;
        uint8_t suppressScrollChangeEvent; // +0x268&lt;br /&gt;
        uint8_t pad269[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t checkWidth; // +0x26C, XML Option/CheckWidth&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect scrollHitRect; // +0x270&lt;br /&gt;
&lt;br /&gt;
        uint8_t field280; // +0x280&lt;br /&gt;
        uint8_t normalDirectionFlag; // +0x281&lt;br /&gt;
        uint8_t pad282[2];&lt;br /&gt;
&lt;br /&gt;
        int32_t scrollChangeSoundId; // +0x284, GuiEventType::ScrollBarChange&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsScrollEnabled() const { return scrollEnabledFlag != 0; }&lt;br /&gt;
        bool IsThumbDragging() const { return thumbDraggingFlag != 0; }&lt;br /&gt;
&lt;br /&gt;
        int32_t ScrollValue() const { return scrollValue; }&lt;br /&gt;
        int32_t PageSize() const { return pageSize; }&lt;br /&gt;
        int32_t MinValue() const { return minValue; }&lt;br /&gt;
        int32_t MaxValue() const { return maxValue; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; UpButtonRect() const { return upButtonRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; DownButtonRect() const { return downButtonRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; TrackRect() const { return trackRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; ThumbRect() const { return thumbRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; ScrollHitRect() const { return scrollHitRect; }&lt;br /&gt;
&lt;br /&gt;
        int32_t ScrollChangeSoundId() const { return scrollChangeSoundId; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiListBoxItem {&lt;br /&gt;
        int32_t textColor; // +0x000&lt;br /&gt;
        wchar_t text[256]; // +0x004&lt;br /&gt;
        int32_t itemData; // +0x204&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknown208[0x10];&lt;br /&gt;
&lt;br /&gt;
        uint8_t selectedFlag; // +0x218&lt;br /&gt;
        uint8_t pad219[3];&lt;br /&gt;
&lt;br /&gt;
        const wchar_t* Text() const { return text; }&lt;br /&gt;
        bool IsSelected() const { return selectedFlag != 0; }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiListBoxItem) == 0x21C);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiListBox : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        Structs::Rect textClipRect; // +0x1FC&lt;br /&gt;
        Structs::Rect itemVisualRect; // +0x20C&lt;br /&gt;
&lt;br /&gt;
        AduGuiScrollBar* scrollBar; // +0x21C&lt;br /&gt;
&lt;br /&gt;
        int32_t rightReservedWidth; // +0x220, ctor 16&lt;br /&gt;
        int32_t outerPadding; // +0x224, ctor 6&lt;br /&gt;
        int32_t textHorizontalPadding; // +0x228, ctor 5&lt;br /&gt;
        int32_t fontHeight; // +0x22C&lt;br /&gt;
&lt;br /&gt;
        int32_t selectionFlags; // +0x230, bit 0 enables multi/range selection behavior&lt;br /&gt;
        int32_t selectedIndex; // +0x234, ctor -1&lt;br /&gt;
        int32_t selectionAnchorIndex; // +0x238&lt;br /&gt;
&lt;br /&gt;
        uint8_t mouseSelectingFlag; // +0x23C&lt;br /&gt;
        uint8_t autoScrollToBottomFlag; // +0x23D&lt;br /&gt;
        uint8_t listEnabledFlag; // +0x23E&lt;br /&gt;
        uint8_t pad23F;&lt;br /&gt;
&lt;br /&gt;
        int32_t rowHeight; // +0x240, fontHeight + rowSpacing&lt;br /&gt;
        int32_t rowSpacing; // +0x244, ctor 2&lt;br /&gt;
        int32_t field248; // +0x248, ctor 1&lt;br /&gt;
&lt;br /&gt;
        AduGuiListBoxItem** items; // +0x24C&lt;br /&gt;
        int32_t itemCount; // +0x250&lt;br /&gt;
        int32_t itemCapacity; // +0x254&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiScrollBar* ScrollBar() const { return scrollBar; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; TextClipRect() const { return textClipRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; ItemVisualRect() const { return itemVisualRect; }&lt;br /&gt;
&lt;br /&gt;
        int32_t SelectedIndex() const { return selectedIndex; }&lt;br /&gt;
        int32_t SelectionAnchorIndex() const { return selectionAnchorIndex; }&lt;br /&gt;
&lt;br /&gt;
        bool IsMultiSelectEnabled() const { return (selectionFlags &amp;amp; 1) != 0; }&lt;br /&gt;
        bool IsMouseSelecting() const { return mouseSelectingFlag != 0; }&lt;br /&gt;
        bool IsAutoScrollToBottomEnabled() const { return autoScrollToBottomFlag != 0; }&lt;br /&gt;
        bool IsListEnabled() const { return listEnabledFlag != 0; }&lt;br /&gt;
&lt;br /&gt;
        int32_t ItemCount() const { return itemCount; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiListBoxItem* ItemAt(int32_t index) const {&lt;br /&gt;
            if (!items || index &amp;lt; 0 || index &amp;gt;= itemCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return items[index];&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiComboBoxItem {&lt;br /&gt;
        wchar_t text[256]; // +0x000&lt;br /&gt;
        int32_t itemData; // +0x200&lt;br /&gt;
        Structs::Rect itemRect; // +0x204&lt;br /&gt;
        uint8_t visibleFlag; // +0x214&lt;br /&gt;
        uint8_t pad215[3];&lt;br /&gt;
&lt;br /&gt;
        const wchar_t* Text() const { return text; }&lt;br /&gt;
        bool IsVisible() const { return visibleFlag != 0; }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiComboBoxItem) == 0x218);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiComboBox : public AduGuiButton {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual int FTSDK_THISCALL ApplyComboBoxStateStyle(int styleValue) = 0; // vt+0xE4&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t selectedIndex; // +0x264&lt;br /&gt;
        int32_t hoverOrPreviewIndex; // +0x268&lt;br /&gt;
&lt;br /&gt;
        int32_t dropHeight; // +0x26C, XML DropHeight&lt;br /&gt;
        AduGuiScrollBar* scrollBar; // +0x270&lt;br /&gt;
&lt;br /&gt;
        int32_t dropButtonWidth; // +0x274, ctor 16&lt;br /&gt;
&lt;br /&gt;
        uint8_t dropdownOpenFlag; // +0x278&lt;br /&gt;
        uint8_t showDropButtonFlag; // +0x279, XML ShowDropButton&lt;br /&gt;
        uint8_t pad27A[2];&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect selectedTextRect; // +0x27C&lt;br /&gt;
        Structs::Rect dropButtonRect; // +0x28C&lt;br /&gt;
        Structs::Rect dropdownOuterRect; // +0x29C&lt;br /&gt;
        Structs::Rect dropdownInnerRect; // +0x2AC&lt;br /&gt;
&lt;br /&gt;
        int32_t rowHeight; // +0x2BC&lt;br /&gt;
        int32_t rowSpacing; // +0x2C0, ctor 2&lt;br /&gt;
&lt;br /&gt;
        int32_t comboBoxCloseSoundId; // +0x2C4, GuiEventType::ComboBoxClose&lt;br /&gt;
&lt;br /&gt;
        AduGuiComboBoxItem** items; // +0x2C8&lt;br /&gt;
        int32_t itemCount; // +0x2CC&lt;br /&gt;
        int32_t itemCapacity; // +0x2D0&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t SelectedIndex() const { return selectedIndex; }&lt;br /&gt;
        int32_t HoverOrPreviewIndex() const { return hoverOrPreviewIndex; }&lt;br /&gt;
&lt;br /&gt;
        bool IsDropdownOpen() const { return dropdownOpenFlag != 0; }&lt;br /&gt;
        bool ShowsDropButton() const { return showDropButtonFlag != 0; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiScrollBar* ScrollBar() const { return scrollBar; }&lt;br /&gt;
&lt;br /&gt;
        int32_t ItemCount() const { return itemCount; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiComboBoxItem* ItemAt(int32_t index) const {&lt;br /&gt;
            if (!items || index &amp;lt; 0 || index &amp;gt;= itemCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return items[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t ComboBoxOpenSoundId() const { return buttonClickSoundId; }&lt;br /&gt;
        int32_t ComboBoxSelectChangeSoundId() const { return buttonOverSoundId; }&lt;br /&gt;
        int32_t ComboBoxCloseSoundId() const { return comboBoxCloseSoundId; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiContextMenuItem {&lt;br /&gt;
        int32_t itemData; // +0x00&lt;br /&gt;
        Structs::FTStringW text; // +0x04&lt;br /&gt;
&lt;br /&gt;
        const wchar_t* Text() const { return text.Data(); }&lt;br /&gt;
        int32_t ItemData() const { return itemData; }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiContextMenuItem) == 0x20);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiContextMenu : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t field1FC; // +0x1FC, ctor 0&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect itemAreaRect; // +0x200&lt;br /&gt;
&lt;br /&gt;
        int32_t itemPaddingX; // +0x210, ctor 5&lt;br /&gt;
        int32_t itemPaddingY; // +0x214, ctor 3&lt;br /&gt;
        int32_t maxTextWidth; // +0x218&lt;br /&gt;
        int32_t fontHeight; // +0x21C&lt;br /&gt;
&lt;br /&gt;
        int32_t menuContentWidth; // +0x220&lt;br /&gt;
        int32_t itemHeight; // +0x224&lt;br /&gt;
&lt;br /&gt;
        int32_t hoverItemIndex; // +0x228, ctor -1&lt;br /&gt;
        int32_t selectedItemIndex; // +0x22C, ctor -1&lt;br /&gt;
&lt;br /&gt;
        int32_t contextMenuClickSoundId; // +0x230, GuiEventType::ContextMenuClick&lt;br /&gt;
&lt;br /&gt;
        AduGuiContextMenuItem** items; // +0x234&lt;br /&gt;
        int32_t itemCount; // +0x238&lt;br /&gt;
        int32_t itemCapacity; // +0x23C&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t HoverItemIndex() const { return hoverItemIndex; }&lt;br /&gt;
        int32_t SelectedItemIndex() const { return selectedItemIndex; }&lt;br /&gt;
&lt;br /&gt;
        int32_t ItemCount() const { return itemCount; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiContextMenuItem* ItemAt(int32_t index) const {&lt;br /&gt;
            if (!items || index &amp;lt; 0 || index &amp;gt;= itemCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return items[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t ContextMenuClickSoundId() const { return contextMenuClickSoundId; }&lt;br /&gt;
    };&lt;br /&gt;
    &lt;br /&gt;
    static_assert(sizeof(AduBase) == 0x08);&lt;br /&gt;
    static_assert(sizeof(AduGameObj) == 0x20);&lt;br /&gt;
    static_assert(sizeof(AduGuiControl) == 0x1FC);&lt;br /&gt;
    static_assert(sizeof(AduGuiStatic) == 0x25C);&lt;br /&gt;
    static_assert(sizeof(AduGuiButton) == 0x264);&lt;br /&gt;
    static_assert(sizeof(AduGuiCheckBox) == 0x298);&lt;br /&gt;
    static_assert(sizeof(AduGuiRadioButton) == 0x29C);&lt;br /&gt;
    static_assert(sizeof(AduGuiComboBox) == 0x2D4);&lt;br /&gt;
    static_assert(sizeof(AduGuiSlider) == 0x228);&lt;br /&gt;
    static_assert(sizeof(AduGuiGauge) == 0x264);&lt;br /&gt;
    static_assert(sizeof(AduGuiEditBox) == 0x2D8);&lt;br /&gt;
    static_assert(sizeof(AduGuiIMEEditBox) == 0x330);&lt;br /&gt;
    static_assert(sizeof(AduGuiListBox) == 0x258);&lt;br /&gt;
    static_assert(sizeof(AduGuiScrollBar) == 0x288);&lt;br /&gt;
    static_assert(sizeof(AduGuiContextMenu) == 0x240);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiDialog : public AduGameObj {&lt;br /&gt;
    public:&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t dialogIdOrField20; // +0x20, ctor 0xFFFF&lt;br /&gt;
        int32_t field24;&lt;br /&gt;
&lt;br /&gt;
        double lastPeriodicUpdateTime; // +0x28&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* tooltipOrDelayedTextControl; // +0x30&lt;br /&gt;
&lt;br /&gt;
        uint8_t field34;&lt;br /&gt;
        uint8_t field35;&lt;br /&gt;
        uint8_t pad36[2];&lt;br /&gt;
&lt;br /&gt;
        int32_t field38;&lt;br /&gt;
        int32_t field3C;&lt;br /&gt;
        int32_t dialogX;      // +0x40, XML Dialog x&lt;br /&gt;
        int32_t dialogY;      // +0x44, XML Dialog y&lt;br /&gt;
        int32_t dialogWidth;  // +0x48, XML Dialog w&lt;br /&gt;
        int32_t dialogHeight; // +0x4C, XML Dialog h&lt;br /&gt;
&lt;br /&gt;
        float scaleX; // +0x50, ctor 1.0&lt;br /&gt;
        float scaleY; // +0x54, ctor 1.0&lt;br /&gt;
&lt;br /&gt;
        int32_t field58;&lt;br /&gt;
        int32_t field5C;&lt;br /&gt;
        int32_t field60;&lt;br /&gt;
        int32_t field64;&lt;br /&gt;
&lt;br /&gt;
        void* renderContext; // +0x68&lt;br /&gt;
&lt;br /&gt;
        GuiCallbackFn callback; // +0x6C&lt;br /&gt;
        void* callbackUserData; // +0x70&lt;br /&gt;
&lt;br /&gt;
        void** imageResources; // +0x74&lt;br /&gt;
        int32_t imageResourceCount; // +0x78&lt;br /&gt;
        int32_t imageResourceCapacity; // +0x7C&lt;br /&gt;
&lt;br /&gt;
        Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt; controlMap; // +0x80&lt;br /&gt;
&lt;br /&gt;
        AduGameObj** objectArray; // +0x8C&lt;br /&gt;
        int32_t objectCount; // +0x90&lt;br /&gt;
        int32_t objectCapacity; // +0x94&lt;br /&gt;
&lt;br /&gt;
        void** runtimeEntryArray; // +0x98&lt;br /&gt;
        int32_t runtimeEntryCount; // +0x9C&lt;br /&gt;
        int32_t runtimeEntryCapacity; // +0xA0&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* selfA4; // +0xA4&lt;br /&gt;
        AduGuiDialog* selfA8; // +0xA8&lt;br /&gt;
&lt;br /&gt;
        uint8_t rotateFocusFlag; // +0xAC, XML RotateFocus, ctor 1&lt;br /&gt;
        uint8_t padAD[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t delayedTextX; // +0xB0&lt;br /&gt;
        int32_t delayedTextY; // +0xB4&lt;br /&gt;
&lt;br /&gt;
        uint8_t delayedTextEnabled; // +0xB8&lt;br /&gt;
        uint8_t padB9[3];&lt;br /&gt;
&lt;br /&gt;
        float delayedTextTimer; // +0xBC&lt;br /&gt;
&lt;br /&gt;
        uint8_t dispatchEventsFlag; // +0xC0&lt;br /&gt;
        uint8_t keepCaptureAfterEventFlag; // +0xC1&lt;br /&gt;
        uint8_t fieldC2; // +0xC2, ctor 1&lt;br /&gt;
        uint8_t padC3;&lt;br /&gt;
&lt;br /&gt;
        float fieldC4;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        void SetCallback(GuiCallbackFn callback, void* userData) {&lt;br /&gt;
			this-&amp;gt;callback = callback;&lt;br /&gt;
			this-&amp;gt;callbackUserData = userData;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* ObjectAt(int32_t index) const {&lt;br /&gt;
            auto* arr = objectArray;&lt;br /&gt;
&lt;br /&gt;
			if (!arr || index &amp;lt; 0 || index &amp;gt;= objectCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return arr[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* ControlAt(int32_t index) const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;AduGuiControl*&amp;gt;(ObjectAt(index));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByCommandId(int32_t commandId) const {&lt;br /&gt;
            const int32_t count = objectCount;&lt;br /&gt;
&lt;br /&gt;
            if (count &amp;lt; 0 || count &amp;gt; 4096)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; count; ++i) {&lt;br /&gt;
                auto* control = ControlAt(i);&lt;br /&gt;
                if (!control)&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;CommandId() == commandId)&lt;br /&gt;
                    return control;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByName(const char* name) const {&lt;br /&gt;
            if (!name)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            const int32_t count = objectCount;&lt;br /&gt;
&lt;br /&gt;
            if (count &amp;lt; 0 || count &amp;gt; 4096)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; count; ++i) {&lt;br /&gt;
                auto* control = ControlAt(i);&lt;br /&gt;
                if (!control)&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;Name().Equals(name))&lt;br /&gt;
                    return control;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByNameAndType(const char* name, GuiControlType type) const {&lt;br /&gt;
            if (!name)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            const int32_t count = objectCount;&lt;br /&gt;
&lt;br /&gt;
            if (count &amp;lt; 0 || count &amp;gt; 4096)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; count; ++i) {&lt;br /&gt;
                auto* control = ControlAt(i);&lt;br /&gt;
                if (!control)&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;ControlTypeId() != static_cast&amp;lt;int32_t&amp;gt;(type))&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;Name().Equals(name))&lt;br /&gt;
                    return control;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
	static_assert(sizeof(AduGuiDialog) == 0xC8);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiCustomControlBase : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual bool FTSDK_THISCALL LoadEmbeddedGuiXml(const char* xmlName, const Structs::GuiBind* binds, int32_t bindCount, GuiCallbackFn callback) = 0; // vt+0xD8&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedCommand(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedEditEnter(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedEditChange(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedEditTab(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedEditEsc(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedEditKeyUp(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedEditKeyDown(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedContextMenuClick(int32_t commandId, int32_t selectedValue, AduGuiControl* contextMenuControl, int32_t selectedData) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL DispatchEmbeddedGuiEvent(GuiEventType eventType, int32_t commandId, int32_t param, AduGuiControl* control) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiDialog* embeddedDialog; // +0x1FC&lt;br /&gt;
        Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt; embeddedBindMap; // +0x200&lt;br /&gt;
        int32_t embeddedField20C;&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiCustomControlBase) == 0x210);&lt;br /&gt;
&lt;br /&gt;
    class FTRankingInfo : public AduGuiCustomControlBase {&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiButton* btClothInfoInner; // +0x210, XML btClothInfo&lt;br /&gt;
&lt;br /&gt;
        AduGuiStatic* stRankingInner; // +0x214&lt;br /&gt;
        AduGuiStatic* stEmblemMarkInner; // +0x218&lt;br /&gt;
        AduGuiStatic* stUserNameInner; // +0x21C&lt;br /&gt;
        AduGuiStatic* stUserLevelInner; // +0x220&lt;br /&gt;
        AduGuiStatic* stUserExpInner; // +0x224&lt;br /&gt;
        AduGuiStatic* stWinPercentInner; // +0x228&lt;br /&gt;
        AduGuiStatic* stRankingPointInner; // +0x22C&lt;br /&gt;
&lt;br /&gt;
        void* rankingRecord; // +0x230, points to 52-byte ranking row/cache record&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool HasRankingRecord() const {&lt;br /&gt;
            return rankingRecord != nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t RankingPlayerId() const {&lt;br /&gt;
            return rankingRecord&lt;br /&gt;
                ? *reinterpret_cast&amp;lt;const int32_t*&amp;gt;(&lt;br /&gt;
                    reinterpret_cast&amp;lt;const uint8_t*&amp;gt;(rankingRecord) + 0x04&lt;br /&gt;
                    )&lt;br /&gt;
                : 0;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(FTRankingInfo) == 0x234);&lt;br /&gt;
&lt;br /&gt;
    class StageManager;&lt;br /&gt;
    class AduEngine;&lt;br /&gt;
&lt;br /&gt;
    class ScreenRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved00() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved04() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved08() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved0C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved10() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved14() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved18() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved1C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved20() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved24() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved28() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual int FTSDK_THISCALL OnFocusEnter() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnFocusLeave() = 0;&lt;br /&gt;
        virtual void* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL AttachRaw(ScreenRoot* parentOrManager, int32_t rectVtableOrTemp, int32_t left, int32_t top, int32_t right, int32_t bottom, int32_t id) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL Shutdown() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL Update() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OffsetRect(int32_t dx, int32_t dy) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL CenterRectInParent() = 0;&lt;br /&gt;
        virtual ScreenRoot* FTSDK_THISCALL ActivateProcessing() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL DeactivateProcessing() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual int FTSDK_THISCALL OnStageCommand(int32_t commandId) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved58() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved5C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved60() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved64() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved68() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved6C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved70() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved74() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved78() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved7C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved80() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_THISCALL SetOpenFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ClearOpenFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_STDCALL RectCleanupThunk(int32_t a1, uint8_t rectObj, int32_t a3, int32_t a4) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL HandleSystemEvent(int32_t eventType, void* data, int32_t param) = 0;&lt;br /&gt;
        virtual void FTSDK_STDCALL RectCleanupThunk2(uint8_t rectObj, int32_t a2, int32_t a3) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleInput(uint32_t msg, int32_t wParam, int32_t lParam) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t id;&lt;br /&gt;
        float field08;&lt;br /&gt;
        float field0C;&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknown10[0x80];&lt;br /&gt;
&lt;br /&gt;
        Structs::GuiRectStorage rect;&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;ScreenRoot*&amp;gt; children;&lt;br /&gt;
        ScreenRoot* parent;&lt;br /&gt;
&lt;br /&gt;
        int32_t parentChainEnabled;&lt;br /&gt;
        int32_t processEnabled;&lt;br /&gt;
        int32_t openFlag;&lt;br /&gt;
        int32_t rectOffsetEnabled;&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknownC8[0x70];&lt;br /&gt;
        char resourceName[0x80];&lt;br /&gt;
        void* lazyResource;&lt;br /&gt;
        uint8_t lazyResourceAllowed;&lt;br /&gt;
        uint8_t pad1BD[3];&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsParentChainEnabled() const { return parentChainEnabled != 0; }&lt;br /&gt;
        bool IsProcessingEnabled() const { return processEnabled != 0; }&lt;br /&gt;
        bool IsOpen() const { return openFlag != 0; }&lt;br /&gt;
        bool IsRectOffsetEnabled() const { return rectOffsetEnabled != 0; }&lt;br /&gt;
        bool HasParent() const { return parent != nullptr; }&lt;br /&gt;
        bool HasLazyResource() const { return lazyResource != nullptr; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::GuiRectStorage&amp;amp; RectStorage() const { return rect; }&lt;br /&gt;
        Structs::GuiRectStorage&amp;amp; RectStorage() { return rect; }&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;ScreenRoot*&amp;gt;&amp;amp; Children() { return children; }&lt;br /&gt;
        const Structs::FTVector&amp;lt;ScreenRoot*&amp;gt;&amp;amp; Children() const { return children; }&lt;br /&gt;
&lt;br /&gt;
        int32_t Left() const { return rect.left; }&lt;br /&gt;
        int32_t Top() const { return rect.top; }&lt;br /&gt;
        int32_t Right() const { return rect.right; }&lt;br /&gt;
        int32_t Bottom() const { return rect.bottom; }&lt;br /&gt;
        int32_t Width() const { return rect.right - rect.left; }&lt;br /&gt;
        int32_t Height() const { return rect.bottom - rect.top; }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(ScreenRoot) == 0x1C0);&lt;br /&gt;
&lt;br /&gt;
    class ScreenInputRoot : public ScreenRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        void* inputHwnd;&lt;br /&gt;
        int32_t startupParamOrMode;&lt;br /&gt;
        AduEngine* engine;&lt;br /&gt;
&lt;br /&gt;
        ScreenRoot* previousFocusOwner;&lt;br /&gt;
        ScreenRoot* currentFocusOwner;&lt;br /&gt;
        ScreenRoot* previousHoverOwner;&lt;br /&gt;
        ScreenRoot* currentHoverOwner;&lt;br /&gt;
&lt;br /&gt;
        int32_t previousCursorX;&lt;br /&gt;
        int32_t previousCursorY;&lt;br /&gt;
&lt;br /&gt;
        int32_t cursorX;&lt;br /&gt;
        int32_t cursorY;&lt;br /&gt;
&lt;br /&gt;
        uint8_t inputState[0x26C];&lt;br /&gt;
&lt;br /&gt;
        ScreenRoot* hoverOrCapturedOwner;&lt;br /&gt;
&lt;br /&gt;
        char resourceBasePath[0x104];&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool HasCurrentFocusOwner() const { return currentFocusOwner != nullptr; }&lt;br /&gt;
        bool HasCurrentHoverOwner() const { return currentHoverOwner != nullptr; }&lt;br /&gt;
&lt;br /&gt;
        int32_t CursorDeltaX() const { return cursorX - previousCursorX; }&lt;br /&gt;
        int32_t CursorDeltaY() const { return cursorY - previousCursorY; }&lt;br /&gt;
&lt;br /&gt;
        using SetFocusOwnerFn = ScreenRoot * (FTSDK_THISCALL*)(ScreenInputRoot* self, ScreenRoot* owner);&lt;br /&gt;
        ScreenRoot* SetFocusOwner(ScreenRoot* owner) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;SetFocusOwnerFn&amp;gt;(0x005AAF70)(this, owner);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using DispatchInputMessageFn = bool(FTSDK_FASTCALL*)(ScreenInputRoot* self, int edx0, uint32_t msg, int32_t wParam, int32_t lParam);&lt;br /&gt;
        bool DispatchInputMessage(uint32_t msg, int32_t wParam, int32_t lParam) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;DispatchInputMessageFn&amp;gt;(0x005AB1F0)(this, 0, msg, wParam, lParam);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using UpdateInputRootFn = int(FTSDK_THISCALL*)(ScreenInputRoot* self);&lt;br /&gt;
        int UpdateInputRoot() {&lt;br /&gt;
            return reinterpret_cast&amp;lt;UpdateInputRootFn&amp;gt;(0x005AB180)(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using InitInputRootFn = char* (FTSDK_THISCALL*)(ScreenInputRoot* self, void* hwnd, AduEngine* aduEngine, int32_t startupParamOrMode,&lt;br /&gt;
            const char* resourceBasePath, int cursorResourceA, int cursorResourceB);&lt;br /&gt;
        char* InitInputRoot(void* hwndValue, AduEngine* aduEngineValue, int32_t startupParam, const char* basePath, int cursorResourceA, int cursorResourceB) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;InitInputRootFn&amp;gt;(0x005AAEA0)(this, hwndValue, aduEngineValue, startupParam, basePath, cursorResourceA, cursorResourceB);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using SetCursorModeFn = uint32_t(FTSDK_THISCALL*)(ScreenInputRoot* self, int32_t mode);&lt;br /&gt;
        uint32_t SetCursorMode(int32_t mode) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;SetCursorModeFn&amp;gt;(0x005AAD30)(this, mode);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(ScreenInputRoot) == 0x560);&lt;br /&gt;
&lt;br /&gt;
    class StageBase : public ScreenRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL AttachOwnerWithDefaultRect(ScreenRoot* parent, int32_t id) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL InitDialogWithParentOwner(StageBase* parent) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL LoadXml(const char* xmlName, const Structs::GuiBind* binds, int32_t bindCount, GuiCallbackFn callback, bool reload) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnActivate() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnDeactivate() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnUpdate() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL ProcessChildOwners(float dt) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL HandleStagePacket(void* packet) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL HandleGuiEvent(GuiEventType eventType, int commandId, int param) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnCommandAction(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxEnter(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxChange(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxTab(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxEsc(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxKeyUp(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxKeyDown(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnContextMenuClick(int32_t commandId, int selectedValue, AduGuiControl* contextMenuControl, int selectedData) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        StageBase* priorityInputChildOwner;&lt;br /&gt;
        uint8_t inputConsumedFlag;&lt;br /&gt;
        uint8_t padOrUnknown1C5[3];&lt;br /&gt;
        AduGuiDialog* xmlDialog;&lt;br /&gt;
&lt;br /&gt;
        Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt; controlBindMap;&lt;br /&gt;
        Structs::FTVector&amp;lt;StageBase*&amp;gt; processingChildOwners;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool InputConsumedFlag() const { return inputConsumedFlag != 0; }&lt;br /&gt;
        AduGuiDialog* XmlDialog() const { return xmlDialog; }&lt;br /&gt;
        bool HasXmlDialog() const { return xmlDialog != nullptr; }&lt;br /&gt;
        StageBase* PriorityInputChildOwner() const { return priorityInputChildOwner; }&lt;br /&gt;
&lt;br /&gt;
        Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt;&amp;amp; ControlBindMap() { return controlBindMap; }&lt;br /&gt;
        const Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt;&amp;amp; ControlBindMap() const { return controlBindMap; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindBoundControl(int32_t commandId) const { return controlBindMap.Find(commandId); }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindBoundControl(int32_t commandId, GuiControlType expectedType) const {&lt;br /&gt;
            auto* control = FindBoundControl(commandId);&lt;br /&gt;
&lt;br /&gt;
            if (!control)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            if (control-&amp;gt;ControlTypeId() != static_cast&amp;lt;int32_t&amp;gt;(expectedType))&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return control;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool HasBoundControl(int32_t commandId) const { return controlBindMap.Contains(commandId); }&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;StageBase*&amp;gt;&amp;amp; ProcessingChildOwners() { return processingChildOwners; }&lt;br /&gt;
        const Structs::FTVector&amp;lt;StageBase*&amp;gt;&amp;amp; ProcessingChildOwners() const { return processingChildOwners; }&lt;br /&gt;
&lt;br /&gt;
        void SetPriorityInputChildOwner(StageBase* child) { priorityInputChildOwner = child; }&lt;br /&gt;
&lt;br /&gt;
        void ClearPriorityInputChildOwnerIf(StageBase* child) {&lt;br /&gt;
            if (PriorityInputChildOwner() == child)&lt;br /&gt;
                SetPriorityInputChildOwner(nullptr);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using AddChildOwnerFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);&lt;br /&gt;
        void AddChildOwner(StageBase* child) {&lt;br /&gt;
            reinterpret_cast&amp;lt;AddChildOwnerFn&amp;gt;(0x005AA720)(this, child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using BringChildOwnerToFrontFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);&lt;br /&gt;
        void BringChildOwnerToFront(StageBase* child) {&lt;br /&gt;
            reinterpret_cast&amp;lt;BringChildOwnerToFrontFn&amp;gt;(0x005AA780)(this, child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using IsOwnerReachableFn = int(FTSDK_FASTCALL*)(StageBase* self);&lt;br /&gt;
        bool IsReachableThroughParentChain() const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;IsOwnerReachableFn&amp;gt;(0x005A99E0)(const_cast&amp;lt;StageBase*&amp;gt;(this)) != 0;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(StageBase) == 0x1E8);&lt;br /&gt;
&lt;br /&gt;
    class MessageBoxDialogOwner : public StageBase {&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t messageBoxMode;&lt;br /&gt;
&lt;br /&gt;
        int32_t okYesEventType;&lt;br /&gt;
        int32_t okYesEventData;&lt;br /&gt;
        int32_t okYesEventParam;&lt;br /&gt;
&lt;br /&gt;
        int32_t noEventType;&lt;br /&gt;
        int32_t noEventData;&lt;br /&gt;
        int32_t noEventParam;&lt;br /&gt;
&lt;br /&gt;
        void* stMessageInnerObject;&lt;br /&gt;
        AduGuiControl* btOKControl;&lt;br /&gt;
        AduGuiControl* btYesControl;&lt;br /&gt;
        AduGuiControl* btNoControl;&lt;br /&gt;
        void* stTimeLeftInnerObject;&lt;br /&gt;
&lt;br /&gt;
        float delayTimer;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsTimedMode() const {&lt;br /&gt;
            return messageBoxMode == MessageBox_TimedOK || messageBoxMode == MessageBox_TimedYesNo;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsYesNoMode() const {&lt;br /&gt;
            return messageBoxMode == MessageBox_YesNo || messageBoxMode == MessageBox_TimedYesNo;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsOkOnlyMode() const {&lt;br /&gt;
            return messageBoxMode == MessageBox_OK || messageBoxMode == MessageBox_TimedOK;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsShowing() const {&lt;br /&gt;
            return processEnabled != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using ShowMessageBoxOwnerFn = MessageBoxDialogOwner * (FTSDK_THISCALL*)(MessageBoxDialogOwner* self, const wchar_t* message, int32_t mode, const int32_t* okYesEventArgs, const int32_t* noEventArgs);&lt;br /&gt;
        MessageBoxDialogOwner* Show(const wchar_t* message, MessageBoxMode mode, const int32_t* okYesEventArgs = nullptr, const int32_t* noEventArgs = nullptr) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;ShowMessageBoxOwnerFn&amp;gt;(0x0056DAE0)(&lt;br /&gt;
                this,&lt;br /&gt;
                message,&lt;br /&gt;
                static_cast&amp;lt;int32_t&amp;gt;(mode),&lt;br /&gt;
                okYesEventArgs,&lt;br /&gt;
                noEventArgs&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        MessageBoxDialogOwner* ShowOK(const wchar_t* message) {&lt;br /&gt;
            return Show(message, MessageBox_OK);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        MessageBoxDialogOwner* ShowTimedOK(const wchar_t* message) {&lt;br /&gt;
            return Show(message, MessageBox_TimedOK);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        MessageBoxDialogOwner* ShowYesNo(&lt;br /&gt;
            const wchar_t* message,&lt;br /&gt;
            int32_t okYesEventType = 0,&lt;br /&gt;
            int32_t okYesEventData = 0,&lt;br /&gt;
            int32_t okYesEventParam = 0,&lt;br /&gt;
            int32_t noEventType = 0,&lt;br /&gt;
            int32_t noEventData = 0,&lt;br /&gt;
            int32_t noEventParam = 0&lt;br /&gt;
        ) {&lt;br /&gt;
            const int32_t okYesArgs[3] = {&lt;br /&gt;
                okYesEventType,&lt;br /&gt;
                okYesEventData,&lt;br /&gt;
                okYesEventParam&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            const int32_t noArgs[3] = {&lt;br /&gt;
                noEventType,&lt;br /&gt;
                noEventData,&lt;br /&gt;
                noEventParam&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            return Show(message, MessageBox_YesNo, okYesArgs, noArgs);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        MessageBoxDialogOwner* ShowTimedYesNo(&lt;br /&gt;
            const wchar_t* message,&lt;br /&gt;
            int32_t okYesEventType = 0,&lt;br /&gt;
            int32_t okYesEventData = 0,&lt;br /&gt;
            int32_t okYesEventParam = 0,&lt;br /&gt;
            int32_t noEventType = 0,&lt;br /&gt;
            int32_t noEventData = 0,&lt;br /&gt;
            int32_t noEventParam = 0&lt;br /&gt;
        ) {&lt;br /&gt;
            const int32_t okYesArgs[3] = {&lt;br /&gt;
                okYesEventType,&lt;br /&gt;
                okYesEventData,&lt;br /&gt;
                okYesEventParam&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            const int32_t noArgs[3] = {&lt;br /&gt;
                noEventType,&lt;br /&gt;
                noEventData,&lt;br /&gt;
                noEventParam&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            return Show(message, MessageBox_TimedYesNo, okYesArgs, noArgs);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(MessageBoxDialogOwner) == 0x21C);&lt;br /&gt;
&lt;br /&gt;
    class GameDialogStage : public StageBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL AttachOwnerAndCreateUserNotice(ScreenRoot* parent, int32_t id) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL InitStageXml() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetSelectedOrActivePopupIndex(int32_t index) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL CanLeaveStageOrPopup(int32_t reason) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ReservedF0() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnBackOrDefaultAction() = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        StageBase* builtinPopupOwners[10];&lt;br /&gt;
        uint8_t xmlLoadedFlag;&lt;br /&gt;
        uint8_t pad211[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t selectedOrActivePopupIndex;&lt;br /&gt;
        void* trayPopupOwner;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsXmlLoaded() const {&lt;br /&gt;
            return xmlLoadedFlag != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        StageBase* BuiltinPopupOwnerAt(int32_t index) const {&lt;br /&gt;
            if (index &amp;lt; 0 || index &amp;gt;= 10)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return builtinPopupOwners[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using GetOrCreateBuiltinPopupOwnerFn = StageBase * (FTSDK_THISCALL*)(StageBase* self, uint32_t popupIndex);&lt;br /&gt;
        StageBase* GetOrCreateBuiltinPopupOwner(uint32_t popupIndex) {&lt;br /&gt;
            if (popupIndex &amp;gt;= Constants::KnownBuiltinPopupSlotCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return reinterpret_cast&amp;lt;GetOrCreateBuiltinPopupOwnerFn&amp;gt;(0x00498890)(this, popupIndex);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class StageManager : public ScreenInputRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        void* stageHwnd;&lt;br /&gt;
&lt;br /&gt;
        int32_t previousStageId;&lt;br /&gt;
        int32_t pendingStageId;&lt;br /&gt;
        int32_t currentStageId;&lt;br /&gt;
        int32_t transitionStageScratchId;&lt;br /&gt;
&lt;br /&gt;
        GameDialogStage* currentStage;&lt;br /&gt;
        uint8_t messageBoxDialog[0x21C];&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;GameDialogStage*&amp;gt; stages;&lt;br /&gt;
&lt;br /&gt;
        void* currentOverlayRaw;&lt;br /&gt;
        Structs::StageOverlayQuad overlayQuad;&lt;br /&gt;
&lt;br /&gt;
        float overlayDrawTimer;&lt;br /&gt;
&lt;br /&gt;
        Structs::StageOwnedResource stageTransitionResource;&lt;br /&gt;
&lt;br /&gt;
        int32_t frameUpdating;&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknown87C[0x10];&lt;br /&gt;
&lt;br /&gt;
        uint8_t delayedReconnectFlag;&lt;br /&gt;
        uint8_t pad88D[3];&lt;br /&gt;
&lt;br /&gt;
        float periodicActionTimer;&lt;br /&gt;
&lt;br /&gt;
        uint8_t hourlyPacketReadyFlag;&lt;br /&gt;
        uint8_t pad895[3];&lt;br /&gt;
&lt;br /&gt;
        float hourlyPacketTimer;&lt;br /&gt;
        int32_t unknown89C;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        MessageBoxDialogOwner* MessageBoxDialog() {&lt;br /&gt;
            return reinterpret_cast&amp;lt;MessageBoxDialogOwner*&amp;gt;(messageBoxDialog);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const MessageBoxDialogOwner* MessageBoxDialog() const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;const MessageBoxDialogOwner*&amp;gt;(messageBoxDialog);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        GameDialogStage* CurrentStage() const {&lt;br /&gt;
            if (currentStage)&lt;br /&gt;
                return currentStage;&lt;br /&gt;
&lt;br /&gt;
            if (currentStageId &amp;lt; 0 || currentStageId &amp;gt;= stages.Size())&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return stages[currentStageId];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsValidStageIndex(uint32_t index) const {&lt;br /&gt;
            return index &amp;lt; Constants::StageOwnerCount;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsFrameUpdating() const {&lt;br /&gt;
            return frameUpdating != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool HasOverlay() const {&lt;br /&gt;
            return currentOverlayRaw != nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsHourlyPacketReady() const {&lt;br /&gt;
            return hourlyPacketReadyFlag != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using RequestStateFn = void(FTSDK_THISCALL*)(StageManager* self, uint32_t state);&lt;br /&gt;
        void RequestState(uint32_t state) {&lt;br /&gt;
            if (!IsValidStageIndex(state))&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            reinterpret_cast&amp;lt;RequestStateFn&amp;gt;(0x004AE9A0)(this, state);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using SwitchStateNowFn = void(FTSDK_THISCALL*)(StageManager* self, uint32_t state);&lt;br /&gt;
        void SwitchStateNow(uint32_t state) {&lt;br /&gt;
            if (!IsValidStageIndex(state))&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            reinterpret_cast&amp;lt;SwitchStateNowFn&amp;gt;(0x004AD7D0)(this, state);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using InitStagesFn = bool(FTSDK_THISCALL*)(StageManager* self, void* hwnd, int startupParam);&lt;br /&gt;
        bool InitStages(void* hwnd, int startupParam) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;InitStagesFn&amp;gt;(0x004B0590)(this, hwnd, startupParam);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using ShowMessageBoxFn = void(FTSDK_THISCALL*)(StageManager* self, const wchar_t* message, int32_t mode, int32_t okYesEventType, int32_t okYesEventData, int32_t okYesEventParam, uint8_t playSound);&lt;br /&gt;
        void ShowMessageBox(&lt;br /&gt;
            const wchar_t* message,&lt;br /&gt;
            MessageBoxMode mode = MessageBox_OK,&lt;br /&gt;
            int32_t okYesEventType = 0,&lt;br /&gt;
            int32_t okYesEventData = 0,&lt;br /&gt;
            int32_t okYesEventParam = 0,&lt;br /&gt;
            bool playSound = true&lt;br /&gt;
        ) {&lt;br /&gt;
            reinterpret_cast&amp;lt;ShowMessageBoxFn&amp;gt;(0x004AE9C0)(&lt;br /&gt;
                this,&lt;br /&gt;
                message,&lt;br /&gt;
                static_cast&amp;lt;int32_t&amp;gt;(mode),&lt;br /&gt;
                okYesEventType,&lt;br /&gt;
                okYesEventData,&lt;br /&gt;
                okYesEventParam,&lt;br /&gt;
                playSound ? 1 : 0&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ShowOKMessage(const wchar_t* message, bool playSound = true) {&lt;br /&gt;
            ShowMessageBox(message, MessageBox_OK, 0, 0, 0, playSound);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ShowTimedOKMessage(const wchar_t* message, bool playSound = true) {&lt;br /&gt;
            ShowMessageBox(message, MessageBox_TimedOK, 0, 0, 0, playSound);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ShowYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) {&lt;br /&gt;
            ShowMessageBox(message, MessageBox_YesNo, okYesEventType, okYesEventData, okYesEventParam, playSound);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ShowTimedYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) {&lt;br /&gt;
            ShowMessageBox(message, MessageBox_TimedYesNo, okYesEventType, okYesEventData, okYesEventParam, playSound);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(StageManager) == 0x8A0);&lt;br /&gt;
&lt;br /&gt;
    using GetStageManagerFn = StageManager * (FTSDK_CDECL*)();&lt;br /&gt;
    inline StageManager* GetStageManager() {&lt;br /&gt;
        return reinterpret_cast&amp;lt;GetStageManagerFn&amp;gt;(0x004B0520)();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    using ConstructGuiOwnerBaseFn = StageBase * (FTSDK_THISCALL*)(StageBase* self);&lt;br /&gt;
    inline StageBase* ConstructStageBase(StageBase* memory) {&lt;br /&gt;
        return reinterpret_cast&amp;lt;ConstructGuiOwnerBaseFn&amp;gt;(0x005AEC40)(memory);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    using ConstructGameDialogStageFn = GameDialogStage * (FTSDK_THISCALL*)(GameDialogStage* self);&lt;br /&gt;
    inline GameDialogStage* ConstructGameDialogStage(GameDialogStage* memory) {&lt;br /&gt;
        return reinterpret_cast&amp;lt;ConstructGameDialogStageFn&amp;gt;(0x004987C0)(memory);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    using SetStaticTextFn = int(FTSDK_THISCALL*)(AduGuiStatic* control, const wchar_t* text);&lt;br /&gt;
    inline int SetStaticText(AduGuiStatic* control, const wchar_t* text) {&lt;br /&gt;
        if (!control)&lt;br /&gt;
			return 0;&lt;br /&gt;
        &lt;br /&gt;
		return reinterpret_cast&amp;lt;SetStaticTextFn&amp;gt;(0x006185E0)(control, text ? text : L&amp;quot;&amp;quot;);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
    namespace GuiCustomControlRegistry {&lt;br /&gt;
        constexpr uintptr_t RegisterFactoryAddr = 0x006113F0;&lt;br /&gt;
        constexpr uintptr_t RegisterTypeNameAddr = 0x00617E20;&lt;br /&gt;
        constexpr uintptr_t DefaultFactoryAddr = 0x005AC950;&lt;br /&gt;
        constexpr uintptr_t RegisterBuiltinCustomTypesAddr = 0x005AE050;&lt;br /&gt;
&lt;br /&gt;
        using RegisterFactoryFn = CreateCustomGuiControlFn(FTSDK_CDECL*)(CreateCustomGuiControlFn factory);&lt;br /&gt;
        using RegisterTypeNameFn = void (FTSDK_CDECL*)(const char* typeName, int32_t customTypeId);&lt;br /&gt;
        using RegisterBuiltinCustomTypesFn = int (FTSDK_CDECL*)();&lt;br /&gt;
&lt;br /&gt;
        inline CreateCustomGuiControlFn RegisterFactory(CreateCustomGuiControlFn factory) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;RegisterFactoryFn&amp;gt;(RegisterFactoryAddr)(factory);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        inline void RegisterTypeName(const char* typeName, int32_t customTypeId) {&lt;br /&gt;
            reinterpret_cast&amp;lt;RegisterTypeNameFn&amp;gt;(RegisterTypeNameAddr)(typeName, customTypeId);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        inline int RegisterBuiltinCustomTypes() {&lt;br /&gt;
            return reinterpret_cast&amp;lt;RegisterBuiltinCustomTypesFn&amp;gt;(RegisterBuiltinCustomTypesAddr)();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        inline AduGuiControl* CreateByDefaultFactory(AduGuiDialog* dialog, int32_t customTypeId) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;CreateCustomGuiControlFn&amp;gt;(DefaultFactoryAddr)(dialog, customTypeId);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Example Usage ==&lt;br /&gt;
* [[Custom Client UI with StageManager Input|Custom client UI with StageManager input forwarding]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Client]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=FT_SDK&amp;diff=306</id>
		<title>FT SDK</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=FT_SDK&amp;diff=306"/>
		<updated>2026-05-20T08:34:54Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Overview ==&lt;br /&gt;
&lt;br /&gt;
This page documents the current reverse engineering baseline for the Fantasy Tennis client-side SDK used by JFTSE tooling, injected debugging code and client UI experiments.&lt;br /&gt;
&lt;br /&gt;
The SDK maps the known client GUI/stage layer into C++ wrappers around the original client structures. The current focus is the UI system around &amp;lt;code&amp;gt;StageManager&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ScreenRoot&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ScreenInputRoot&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;AduGuiDialog&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;AduGuiControl&amp;lt;/code&amp;gt; and all known built-in Adu GUI controls.&lt;br /&gt;
&lt;br /&gt;
The practical goal of this SDK is to make custom client UI possible without adding unsupported client stages. The Fantasy Tennis client uses a fixed stage model, so custom UI should attach to an existing stage owner, popup owner or embedded/custom GUI owner instead of creating an additional global stage.&lt;br /&gt;
&lt;br /&gt;
The SDK is intentionally conservative. Names describe current reverse engineering understanding, not original source names. Offsets, virtual slots and function addresses are tied to the currently analyzed Fantasy Tennis client build.&lt;br /&gt;
&lt;br /&gt;
== Current Focus ==&lt;br /&gt;
&lt;br /&gt;
The currently documented SDK covers:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;StageManager&amp;lt;/code&amp;gt; state tracking, current-stage access and stage switching helpers.&lt;br /&gt;
* &amp;lt;code&amp;gt;ScreenRoot&amp;lt;/code&amp;gt; ownership, parent chains, open state, processing state and input routing.&lt;br /&gt;
* &amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt; XML dialog ownership, GUI callback routing, priority child-owner behavior and event dispatch.&lt;br /&gt;
* &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt; built-in popup slots, popup creation helpers, selected/active popup state and game-dialog lifecycle hooks.&lt;br /&gt;
* &amp;lt;code&amp;gt;AduGuiDialog&amp;lt;/code&amp;gt; runtime layout, object array access, command-ID lookup, name lookup and callback/user-data storage.&lt;br /&gt;
* &amp;lt;code&amp;gt;AduGuiControl&amp;lt;/code&amp;gt; common runtime fields such as name, command id, type id, group id, subgroup id, rectangle, state sets, text and context menu data.&lt;br /&gt;
* Built-in Adu GUI controls:&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiStatic&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiButton&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiCheckBox&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiRadioButton&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiComboBox&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiSlider&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiGauge&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiEditBox&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiIMEEditBox&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiListBox&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiScrollBar&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;AduGuiContextMenu&amp;lt;/code&amp;gt;&lt;br /&gt;
* Generic custom GUI controls through &amp;lt;code&amp;gt;AduGuiCustomControlBase&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Known concrete custom controls such as &amp;lt;code&amp;gt;FTRankingInfo&amp;lt;/code&amp;gt;.&lt;br /&gt;
* XML binding through &amp;lt;code&amp;gt;GuiBind&amp;lt;/code&amp;gt; arrays, command ids and control type ids.&lt;br /&gt;
* Runtime text assignment for static controls through &amp;lt;code&amp;gt;FTSDK::SetStaticText&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Custom type name registration through &amp;lt;code&amp;gt;GuiCustomControlRegistry&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Custom UI Direction ==&lt;br /&gt;
&lt;br /&gt;
The preferred custom UI direction is now:&lt;br /&gt;
&lt;br /&gt;
# Create or reuse a &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt; owner attached to an existing game stage or popup owner.&lt;br /&gt;
# Load a normal XML dialog with &amp;lt;code&amp;gt;StageBase::LoadXml&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Register additional XML control type names with &amp;lt;code&amp;gt;GuiCustomControlRegistry::RegisterTypeName&amp;lt;/code&amp;gt;.&lt;br /&gt;
# For simple embedded GUI controls, map a custom XML type name to &amp;lt;code&amp;gt;GuiControlType::Custom&amp;lt;/code&amp;gt; / type id &amp;lt;code&amp;gt;12&amp;lt;/code&amp;gt;.&lt;br /&gt;
# For each generic custom control instance, call &amp;lt;code&amp;gt;AduGuiCustomControlBase::LoadEmbeddedGuiXml&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Cache the inner controls from the embedded &amp;lt;code&amp;gt;AduGuiDialog&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Update inner controls directly, for example with &amp;lt;code&amp;gt;FTSDK::SetStaticText&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This mirrors the real client pattern used by controls such as &amp;lt;code&amp;gt;FTRankingInfo&amp;lt;/code&amp;gt;: the outer custom control is created by the dialog loader, then its concrete load function loads a separate &amp;lt;code&amp;gt;GuiCtrl_*.xml&amp;lt;/code&amp;gt;, binds/caches the inner controls and refreshes them from runtime data.&lt;br /&gt;
&lt;br /&gt;
Important: registering a name such as &amp;lt;code&amp;gt;MyCustomType&amp;lt;/code&amp;gt; to type id &amp;lt;code&amp;gt;12&amp;lt;/code&amp;gt; makes &amp;lt;code&amp;gt;Type=&amp;quot;MyCustomType&amp;quot;&amp;lt;/code&amp;gt; resolve to the generic custom control base. This does not create a new factory-backed custom type. Using type ids above the built-in range such as &amp;lt;code&amp;gt;100&amp;lt;/code&amp;gt;, requires extending or hooking the custom control factory. Without that, the default factory only creates the known built-in custom control ids.&lt;br /&gt;
&lt;br /&gt;
== Current Custom UI Caveats ==&lt;br /&gt;
&lt;br /&gt;
The custom UI path is partly proven but not fully finished.&lt;br /&gt;
&lt;br /&gt;
Known working pieces:&lt;br /&gt;
&lt;br /&gt;
* The client can resolve a custom XML type name to generic custom type &amp;lt;code&amp;gt;12&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;StageBase::LoadXml&amp;lt;/code&amp;gt; can create generic custom control instances from XML.&lt;br /&gt;
* The created generic custom controls appear in &amp;lt;code&amp;gt;AduGuiDialog::objectArray&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;AduGuiCustomControlBase::LoadEmbeddedGuiXml&amp;lt;/code&amp;gt; can load a nested &amp;lt;code&amp;gt;GuiCtrl_*.xml&amp;lt;/code&amp;gt;.&lt;br /&gt;
* The embedded dialog can be inspected and inner controls can be cached.&lt;br /&gt;
* Static text can be written through &amp;lt;code&amp;gt;FTSDK::SetStaticText&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Known caveats:&lt;br /&gt;
&lt;br /&gt;
* In manually constructed/attached owners, &amp;lt;code&amp;gt;StageBase::controlBindMap&amp;lt;/code&amp;gt; may not contain every control even when the dialog loaded correctly. For robust lookup, fall back to &amp;lt;code&amp;gt;AduGuiDialog::FindControlByCommandId&amp;lt;/code&amp;gt; and then &amp;lt;code&amp;gt;AduGuiDialog::FindControlByName&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Setting active/visible state on the outer custom row is not always enough if the embedded child dialog/control tree is not being processed or rendered by the same path as concrete client controls.&lt;br /&gt;
* The generic custom control rendering/update path still needs more reversing. The next useful target is the base custom control implementation used by real &amp;lt;code&amp;gt;GuiCtrl_*&amp;lt;/code&amp;gt; controls and the concrete GUI popup/stage base classes.&lt;br /&gt;
* Closing or hiding existing popups may happen by priority-child/parent-chain changes rather than by clean deactivation. Do not rely only on &amp;lt;code&amp;gt;OnDeactivate&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Stability Notes ==&lt;br /&gt;
&lt;br /&gt;
All offsets and function addresses are version specific. They are verified against the currently analyzed Fantasy Tennis client build through disassembly, runtime dumps, vtable summaries and real call sites.&lt;br /&gt;
&lt;br /&gt;
The SDK should be treated as a reverse engineering aid, not a stable API. Fields marked &amp;lt;code&amp;gt;unknown&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;field*&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;maybe&amp;lt;/code&amp;gt; or described conservatively are intentionally not finalized.&lt;br /&gt;
&lt;br /&gt;
Do not add another global stage. The client uses a fixed 24 stage model. Attach custom UI to an existing stage, popup owner, modal owner or embedded GUI owner.&lt;br /&gt;
&lt;br /&gt;
When testing custom UI, prefer defensive lookup and logging:&lt;br /&gt;
&lt;br /&gt;
* Check &amp;lt;code&amp;gt;owner-&amp;gt;XmlDialog()&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Dump &amp;lt;code&amp;gt;AduGuiDialog::objectArray&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Prefer &amp;lt;code&amp;gt;FindControlByCommandId&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Fall back to &amp;lt;code&amp;gt;FindControlByName&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Verify &amp;lt;code&amp;gt;ControlTypeId()`.&lt;br /&gt;
* Check &amp;lt;code&amp;gt;active&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;visible&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openFlag&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;processEnabled&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;parentChainEnabled&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Verify whether the target owner is reachable through the parent chain.&lt;br /&gt;
&lt;br /&gt;
== Current SDK Header ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
#pragma once&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;cstdint&amp;gt;&lt;br /&gt;
#include &amp;lt;cstddef&amp;gt;&lt;br /&gt;
#include &amp;lt;cstdio&amp;gt;&lt;br /&gt;
#include &amp;lt;cstring&amp;gt;&lt;br /&gt;
#include &amp;lt;cwchar&amp;gt;&lt;br /&gt;
#include &amp;lt;string&amp;gt;&lt;br /&gt;
#include &amp;lt;type_traits&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define FTSDK_THISCALL __thiscall&lt;br /&gt;
#define FTSDK_CDECL    __cdecl&lt;br /&gt;
#define FTSDK_STDCALL  __stdcall&lt;br /&gt;
#define FTSDK_FASTCALL __fastcall&lt;br /&gt;
&lt;br /&gt;
namespace FTSDK {&lt;br /&gt;
&lt;br /&gt;
    namespace Structs {&lt;br /&gt;
        struct FTStringA {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
&lt;br /&gt;
            union {&lt;br /&gt;
                char inlineBuffer[16];&lt;br /&gt;
                char* heapPtr;&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            uint32_t length;&lt;br /&gt;
            uint32_t capacity;&lt;br /&gt;
&lt;br /&gt;
            bool IsInline() const {&lt;br /&gt;
                return capacity &amp;lt; 16;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool IsValid() const {&lt;br /&gt;
                if (length &amp;gt; capacity || length &amp;gt; 4096)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (capacity &amp;gt; 0xFFFFFFFEu)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (!IsInline() &amp;amp;&amp;amp; !heapPtr)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                return true;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const char* Data() const {&lt;br /&gt;
                if (!IsValid())&lt;br /&gt;
                    return &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
                return IsInline() ? inlineBuffer : heapPtr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            int32_t Length() const {&lt;br /&gt;
                return IsValid() ? static_cast&amp;lt;int32_t&amp;gt;(length) : 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool Equals(const char* text) const {&lt;br /&gt;
                if (!text || !IsValid())&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                const size_t textLen = std::strlen(text);&lt;br /&gt;
                return textLen == length &amp;amp;&amp;amp; std::memcmp(Data(), text, length) == 0;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct FTStringW {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
&lt;br /&gt;
            union {&lt;br /&gt;
                wchar_t inlineBuffer[8];&lt;br /&gt;
                wchar_t* heapPtr;&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            uint32_t length;&lt;br /&gt;
            uint32_t capacity;&lt;br /&gt;
&lt;br /&gt;
            bool IsInline() const {&lt;br /&gt;
                return capacity &amp;lt; 8;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool IsValid() const {&lt;br /&gt;
                if (length &amp;gt; capacity || length &amp;gt; 4096)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (capacity &amp;gt; 0x7FFFFFFEu)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (!IsInline() &amp;amp;&amp;amp; !heapPtr)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                return true;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const wchar_t* Data() const {&lt;br /&gt;
                if (!IsValid())&lt;br /&gt;
                    return L&amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
                return IsInline() ? inlineBuffer : heapPtr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            int32_t Length() const {&lt;br /&gt;
                return IsValid() ? static_cast&amp;lt;int32_t&amp;gt;(length) : 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool Equals(const wchar_t* text) const {&lt;br /&gt;
                if (!text || !IsValid())&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                const size_t textLen = std::wcslen(text);&lt;br /&gt;
                return textLen == length &amp;amp;&amp;amp; std::memcmp(Data(), text, length * sizeof(wchar_t)) == 0;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        struct FTVector {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
            T* first;&lt;br /&gt;
            T* last;&lt;br /&gt;
            T* end;&lt;br /&gt;
&lt;br /&gt;
            int32_t Size() const {&lt;br /&gt;
                if (!first || !last || last &amp;lt; first)&lt;br /&gt;
                    return 0;&lt;br /&gt;
&lt;br /&gt;
                return static_cast&amp;lt;int32_t&amp;gt;(last - first);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            int32_t Capacity() const {&lt;br /&gt;
                if (!first || !end || end &amp;lt; first)&lt;br /&gt;
                    return 0;&lt;br /&gt;
&lt;br /&gt;
                return static_cast&amp;lt;int32_t&amp;gt;(end - first);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool IsEmpty() const {&lt;br /&gt;
                return Size() == 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            T&amp;amp; operator[](size_t index) {&lt;br /&gt;
                return first[index];&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const T&amp;amp; operator[](size_t index) const {&lt;br /&gt;
                return first[index];&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            T* Begin() {&lt;br /&gt;
                return first;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            T* End() {&lt;br /&gt;
                return last;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const T* Begin() const {&lt;br /&gt;
                return first;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const T* End() const {&lt;br /&gt;
                return last;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        struct FTList {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
            T* next;&lt;br /&gt;
            uint32_t count;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Key, typename Value&amp;gt;&lt;br /&gt;
        struct FTTreeNode {&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* left;&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* parent;&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* right;&lt;br /&gt;
&lt;br /&gt;
            Key key;&lt;br /&gt;
            Value value;&lt;br /&gt;
&lt;br /&gt;
            uint8_t rbColor;&lt;br /&gt;
            uint8_t isNil;&lt;br /&gt;
            uint8_t pad16[2];&lt;br /&gt;
&lt;br /&gt;
            bool IsNil() const {&lt;br /&gt;
                return isNil != 0;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Key, typename Value&amp;gt;&lt;br /&gt;
        struct FTTreeMap {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* head;&lt;br /&gt;
            uint32_t count;&lt;br /&gt;
&lt;br /&gt;
            bool IsEmpty() const {&lt;br /&gt;
                return count == 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            uint32_t Size() const {&lt;br /&gt;
                return count;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* Head() const {&lt;br /&gt;
                return head;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* Root() const {&lt;br /&gt;
                if (!head || head-&amp;gt;IsNil())&lt;br /&gt;
                    return nullptr;&lt;br /&gt;
&lt;br /&gt;
                auto* root = head-&amp;gt;parent;&lt;br /&gt;
                return root &amp;amp;&amp;amp; !root-&amp;gt;IsNil() ? root : nullptr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* FindNode(const Key&amp;amp; key) const {&lt;br /&gt;
                auto* node = Root();&lt;br /&gt;
&lt;br /&gt;
                while (node) {&lt;br /&gt;
                    if (node-&amp;gt;IsNil())&lt;br /&gt;
                        return nullptr;&lt;br /&gt;
&lt;br /&gt;
                    if (key &amp;lt; node-&amp;gt;key) {&lt;br /&gt;
                        node = node-&amp;gt;left;&lt;br /&gt;
                    }&lt;br /&gt;
                    else if (node-&amp;gt;key &amp;lt; key) {&lt;br /&gt;
                        node = node-&amp;gt;right;&lt;br /&gt;
                    }&lt;br /&gt;
                    else {&lt;br /&gt;
                        return node;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                return nullptr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            Value Find(const Key&amp;amp; key) const {&lt;br /&gt;
                auto* node = FindNode(key);&lt;br /&gt;
                return node ? node-&amp;gt;value : Value{};&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool Contains(const Key&amp;amp; key) const {&lt;br /&gt;
                return FindNode(key) != nullptr;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct GuiBind {&lt;br /&gt;
            int32_t commandId;&lt;br /&gt;
            const char* controlName;&lt;br /&gt;
            int32_t controlTypeId;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct Point {&lt;br /&gt;
            int32_t x;&lt;br /&gt;
            int32_t y;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct Rect {&lt;br /&gt;
            int32_t left;&lt;br /&gt;
            int32_t top;&lt;br /&gt;
            int32_t right;&lt;br /&gt;
            int32_t bottom;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct GuiRectStorage {&lt;br /&gt;
            void* vtable;&lt;br /&gt;
            int32_t left;&lt;br /&gt;
            int32_t top;&lt;br /&gt;
            int32_t right;&lt;br /&gt;
            int32_t bottom;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct StageOwnedResource {&lt;br /&gt;
            int32_t activeOrTransitionFlag;&lt;br /&gt;
            int32_t enabled;&lt;br /&gt;
            float baseOrDuration;&lt;br /&gt;
            float progressOrAlpha;&lt;br /&gt;
            float colorR_orParam10;&lt;br /&gt;
            float colorG_orParam14;&lt;br /&gt;
            float colorB_orParam18;&lt;br /&gt;
            void* resourceHandle;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct StageOverlayVertex {&lt;br /&gt;
            float x;&lt;br /&gt;
            float y;&lt;br /&gt;
            float z;&lt;br /&gt;
            float rhw;&lt;br /&gt;
            uint32_t color;&lt;br /&gt;
            float u;&lt;br /&gt;
            float v;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct StageOverlayQuad {&lt;br /&gt;
            StageOverlayVertex vertices[4];&lt;br /&gt;
&lt;br /&gt;
            int32_t unknown70;&lt;br /&gt;
            int32_t unknown74;&lt;br /&gt;
            int32_t widthInt;&lt;br /&gt;
            int32_t heightInt;&lt;br /&gt;
&lt;br /&gt;
            float x;&lt;br /&gt;
            float y;&lt;br /&gt;
            float width;&lt;br /&gt;
            float height;&lt;br /&gt;
&lt;br /&gt;
            float scaleX;&lt;br /&gt;
            float alpha;&lt;br /&gt;
            float rotationOrAngle;&lt;br /&gt;
&lt;br /&gt;
            uint8_t unknown9C;&lt;br /&gt;
            uint8_t unknown9D;&lt;br /&gt;
            uint8_t visibleOrEnabled;&lt;br /&gt;
            uint8_t pad9F;&lt;br /&gt;
&lt;br /&gt;
            uint8_t unknownA0[0x0C];&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        static_assert(sizeof(Point) == 0x08);&lt;br /&gt;
        static_assert(sizeof(Rect) == 0x10);&lt;br /&gt;
        static_assert(sizeof(GuiRectStorage) == 0x14);&lt;br /&gt;
        static_assert(sizeof(StageOwnedResource) == 0x20);&lt;br /&gt;
        static_assert(sizeof(FTStringA) == 0x1C);&lt;br /&gt;
        static_assert(sizeof(FTStringW) == 0x1C);&lt;br /&gt;
        static_assert(sizeof(GuiBind) == 0x0C);&lt;br /&gt;
        static_assert(sizeof(FTVector&amp;lt;void*&amp;gt;) == 0x10);&lt;br /&gt;
        static_assert(sizeof(StageOverlayQuad) == 0xAC);&lt;br /&gt;
        static_assert(sizeof(StageOverlayVertex) == 0x1C);&lt;br /&gt;
        static_assert(sizeof(FTList&amp;lt;void*&amp;gt;) == 0x0C);&lt;br /&gt;
        static_assert(sizeof(FTTreeNode&amp;lt;int32_t, void*&amp;gt;) == 0x18);&lt;br /&gt;
        static_assert(sizeof(FTTreeMap&amp;lt;int32_t, void*&amp;gt;) == 0x0C);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    enum GuiEventType : int32_t {&lt;br /&gt;
        ButtonClick = 0,&lt;br /&gt;
        ButtonOver = 1,&lt;br /&gt;
&lt;br /&gt;
        ComboBoxOpen = 2,&lt;br /&gt;
        ComboBoxClose = 3,&lt;br /&gt;
        ComboBoxSelectChange = 4,&lt;br /&gt;
&lt;br /&gt;
        RadioButtonChange = 5,&lt;br /&gt;
        CheckBoxChange = 6,&lt;br /&gt;
&lt;br /&gt;
        SliderValueChange = 7,&lt;br /&gt;
&lt;br /&gt;
        EditBoxEnter = 8,&lt;br /&gt;
        EditBoxChange = 9,&lt;br /&gt;
        EditBoxTAB = 10,&lt;br /&gt;
        EditBoxESC = 11,&lt;br /&gt;
        EditBoxCharLimit = 12,&lt;br /&gt;
        EditBoxKeyUp = 13,&lt;br /&gt;
        EditBoxKeyDown = 14,&lt;br /&gt;
&lt;br /&gt;
        ListBoxDBClick = 15,&lt;br /&gt;
        ListBoxSelect = 16,&lt;br /&gt;
        ListBoxSelectEnd = 17,&lt;br /&gt;
&lt;br /&gt;
        ContextMenuClick = 18,&lt;br /&gt;
        ScrollBarPosChange = 19&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum GuiControlType : int32_t {&lt;br /&gt;
        Static = 0,&lt;br /&gt;
        Button = 1,&lt;br /&gt;
        CheckBox = 2,&lt;br /&gt;
        RadioButton = 3,&lt;br /&gt;
        ComboBox = 4,&lt;br /&gt;
        Slider = 5,&lt;br /&gt;
        Gauge = 6,&lt;br /&gt;
        EditBox = 7,&lt;br /&gt;
        IMEEditBox = 8,&lt;br /&gt;
        ListBox = 9,&lt;br /&gt;
        ScrollBar = 10,&lt;br /&gt;
        ContextMenu = 11,&lt;br /&gt;
		Custom = 12,&lt;br /&gt;
&lt;br /&gt;
        FTRankingInfoType = 68,&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum GuiVisualStateName : int32_t {&lt;br /&gt;
        StateAll = 0,&lt;br /&gt;
        StateNormal = 1,&lt;br /&gt;
        StateOver = 2,&lt;br /&gt;
        StateFocus = 3,&lt;br /&gt;
        StatePressed = 4,&lt;br /&gt;
        StateDisabled = 5,&lt;br /&gt;
        StateHidden = 6&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum GuiBlendMode : int32_t {&lt;br /&gt;
        BlendModulate = 0,&lt;br /&gt;
        BlendAdd = 1&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum GuiTextFx : int32_t {&lt;br /&gt;
        TextFxNone = 0,&lt;br /&gt;
        TextFxShadow = 1,&lt;br /&gt;
        TextFxOutline = 2&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum MessageBoxMode : int32_t {&lt;br /&gt;
        MessageBox_OK = 1,&lt;br /&gt;
        MessageBox_TimedOK = 2,&lt;br /&gt;
        MessageBox_YesNo = 3,&lt;br /&gt;
        MessageBox_TimedYesNo = 4&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiStateSet;&lt;br /&gt;
    class AduGuiDialog;&lt;br /&gt;
    class AduGuiControl;&lt;br /&gt;
    class AduGuiStatic;&lt;br /&gt;
    class AduGuiButton;&lt;br /&gt;
    class AduGuiRadioButton;&lt;br /&gt;
    class AduGuiCheckBox;&lt;br /&gt;
    class AduGuiComboBox;&lt;br /&gt;
    class AduGuiEditBox;&lt;br /&gt;
    class AduGuiIMEEditBox;&lt;br /&gt;
    class AduGuiListBox;&lt;br /&gt;
    class AduGuiScrollBar;&lt;br /&gt;
    class AduGuiSlider;&lt;br /&gt;
    class AduGuiGauge;&lt;br /&gt;
    class AduGuiContextMenu;&lt;br /&gt;
&lt;br /&gt;
    using GuiCallbackFn = void(FTSDK_STDCALL*)(GuiEventType eventType, int commandId, int param, void* userData);&lt;br /&gt;
    using CreateCustomGuiControlFn = AduGuiControl * (FTSDK_CDECL*)(AduGuiDialog* dialog, int32_t customTypeId);&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiVisualStateInfo {&lt;br /&gt;
        int32_t kind;&lt;br /&gt;
        int32_t field04;&lt;br /&gt;
        int32_t field08;&lt;br /&gt;
        int32_t field0C;&lt;br /&gt;
        float timing;&lt;br /&gt;
        int32_t field14;&lt;br /&gt;
        int32_t field18;&lt;br /&gt;
        int32_t field1C;&lt;br /&gt;
        int32_t alternateStateIndex;&lt;br /&gt;
        int32_t field24;&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiVisualStateInfo) == 0x28);&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiParsedStateDef {&lt;br /&gt;
        int32_t image[6]; // +0x00&lt;br /&gt;
        int32_t color[6]; // +0x18&lt;br /&gt;
        int32_t textColor[6]; // +0x30&lt;br /&gt;
        int32_t textFxColor[6]; // +0x48&lt;br /&gt;
        int32_t blend[6]; // +0x60&lt;br /&gt;
        int32_t fx[6]; // +0x78&lt;br /&gt;
&lt;br /&gt;
        uint8_t scale[6]; // +0x90&lt;br /&gt;
        uint8_t tiling[6]; // +0x96&lt;br /&gt;
&lt;br /&gt;
        int32_t offsetX[6]; // +0x9C&lt;br /&gt;
        int32_t offsetY[6]; // +0xB4&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiParsedStateDef) == 0xCC);&lt;br /&gt;
&lt;br /&gt;
    // Dialog-side parsed XML/control definition.&lt;br /&gt;
    // This is not AduGuiControl runtime storage.&lt;br /&gt;
    //&lt;br /&gt;
    // Confirmed:&lt;br /&gt;
    // - sub_624F90 initializes this structure.&lt;br /&gt;
    // - sub_614F60 fills it from one &amp;lt;Control&amp;gt;.&lt;br /&gt;
    // - AduGuiControl::LoadFromXml consumes it.&lt;br /&gt;
    // - parsedStates uses 0xCC-byte parser-side state records.&lt;br /&gt;
    // - eventSoundIds are indexed by GuiEventType and initialized to invalid/NaN-like values.&lt;br /&gt;
    struct AduGuiParsedControlDef {&lt;br /&gt;
        Structs::FTVector&amp;lt;AduGuiParsedStateDef&amp;gt; parsedStates; // +0x0000&lt;br /&gt;
&lt;br /&gt;
        int32_t eventSoundIds[20]; // +0x0010, indexed by GuiEventType&lt;br /&gt;
&lt;br /&gt;
        uint8_t stateAndImageParseStorage[0x804]; // +0x0060..+0x0863&lt;br /&gt;
&lt;br /&gt;
        wchar_t resolvedText[256]; // +0x0864, assigned to AduGuiControl::text&lt;br /&gt;
&lt;br /&gt;
        uint8_t tooltipAndMenuParseStorage[0x600]; // +0x0A64..+0x1063&lt;br /&gt;
&lt;br /&gt;
        uint8_t enable;       // +0x1064, XML Enable, default 1&lt;br /&gt;
        uint8_t enabledState; // +0x1065, XML EnabledState, default 1&lt;br /&gt;
        uint8_t pad1066[2];&lt;br /&gt;
&lt;br /&gt;
        int32_t x;      // +0x1068, XML x&lt;br /&gt;
        int32_t y;      // +0x106C, XML y&lt;br /&gt;
        int32_t width;  // +0x1070, XML w&lt;br /&gt;
        int32_t height; // +0x1074, XML h&lt;br /&gt;
&lt;br /&gt;
        int32_t alignX; // XML AlignX&lt;br /&gt;
        int32_t alignY; // XML AlignY&lt;br /&gt;
&lt;br /&gt;
        float scaleX;  // XML ScaleX, default 1.0&lt;br /&gt;
        float scaleY;  // XML ScaleY, default 1.0&lt;br /&gt;
        float rotate;  // XML Rotate&lt;br /&gt;
        float centerX; // XML CenterX&lt;br /&gt;
        float centerY; // XML CenterY&lt;br /&gt;
&lt;br /&gt;
        int32_t flipX; // XML FlipX&lt;br /&gt;
&lt;br /&gt;
        int32_t textAlignX; // XML TextAlignX&lt;br /&gt;
        int32_t textAlignY; // XML TextAlignY&lt;br /&gt;
        int32_t textFx;     // XML TextFx&lt;br /&gt;
&lt;br /&gt;
        int32_t textMarginLeft;   // XML TextMargin&lt;br /&gt;
        int32_t textMarginTop;&lt;br /&gt;
        int32_t textMarginRight;&lt;br /&gt;
        int32_t textMarginBottom;&lt;br /&gt;
&lt;br /&gt;
        int32_t wordWrap;   // XML WordWrap&lt;br /&gt;
        int32_t groupId;    // XML GroupID&lt;br /&gt;
        int32_t subGroupId; // XML SubGroupID&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknown10C0[0x24];&lt;br /&gt;
&lt;br /&gt;
        int32_t tabStop;      // +0x10E4, XML TabStop&lt;br /&gt;
        int32_t initialStage; // XML Stage&lt;br /&gt;
        int32_t bindStage;    // XML BindStage&lt;br /&gt;
        int32_t hotKey;       // XML HotKey&lt;br /&gt;
&lt;br /&gt;
        char contextMenuName[256]; // +0x10F4, XML Menu&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiParsedControlDef) == 0x11F4);&lt;br /&gt;
&lt;br /&gt;
    namespace Constants {&lt;br /&gt;
        constexpr int32_t StageOwnerCount = 24;&lt;br /&gt;
        constexpr int32_t KnownBuiltinPopupSlotCount = 10;&lt;br /&gt;
        constexpr int32_t GuiEventTypeCount = 20;&lt;br /&gt;
        constexpr int32_t GuiVisualStateNameCount = 7;&lt;br /&gt;
        constexpr int32_t ParsedGuiStateSize = 0xCC;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    class AduBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual AduBase* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t unk04;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool Unk04Enabled() const {&lt;br /&gt;
            return unk04 != 0;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGameObj : public AduBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual AduGameObj* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetDirtyValue(int32_t value) = 0;&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL ResetElapsedTimeRecursive() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetActive(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetUpdateBlocked(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetVisible(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL UpdateRecursive(float dt) = 0;&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL ProcessVisibleRecursive() = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t dirtyValue; // +0x08&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* firstChild;&lt;br /&gt;
        AduGameObj* nextSibling;&lt;br /&gt;
&lt;br /&gt;
        uint8_t dirtyFlag;&lt;br /&gt;
        uint8_t active;&lt;br /&gt;
        uint8_t updateBlocked;&lt;br /&gt;
        uint8_t visible;&lt;br /&gt;
&lt;br /&gt;
        float elapsedTime;&lt;br /&gt;
        float deltaTime;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsDirty() const { return dirtyFlag != 0; }&lt;br /&gt;
        bool IsActive() const { return active != 0; }&lt;br /&gt;
        bool IsUpdateBlocked() const { return updateBlocked != 0; }&lt;br /&gt;
        bool IsVisible() const { return visible != 0; }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* FirstChild() const { return firstChild; }&lt;br /&gt;
        AduGameObj* NextSibling() const { return nextSibling; }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Callback&amp;gt;&lt;br /&gt;
        void ForEachChild(Callback&amp;amp;&amp;amp; callback) {&lt;br /&gt;
            for (AduGameObj* child = firstChild; child; child = child-&amp;gt;nextSibling)&lt;br /&gt;
                callback(child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Callback&amp;gt;&lt;br /&gt;
        void ForEachChild(Callback&amp;amp;&amp;amp; callback) const {&lt;br /&gt;
            for (AduGameObj* child = firstChild; child; child = child-&amp;gt;nextSibling)&lt;br /&gt;
                callback(child);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiControl : public AduGameObj {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual bool FTSDK_THISCALL RecalculateRect() = 0; // vt+0x20, rect = {x,y,x+w,y+h}&lt;br /&gt;
        virtual bool FTSDK_THISCALL LoadFromXml(const char* name, void* xmlParser, AduGuiParsedControlDef* parsedControlDef) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL PostLoadResolve() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual AduGuiStatic* FTSDK_THISCALL AsStatic() = 0; // vt+0x2C&lt;br /&gt;
        virtual AduGuiButton* FTSDK_THISCALL AsButton() = 0;&lt;br /&gt;
        virtual AduGuiRadioButton* FTSDK_THISCALL AsRadioButton() = 0;&lt;br /&gt;
        virtual AduGuiCheckBox* FTSDK_THISCALL AsCheckBox() = 0;&lt;br /&gt;
        virtual AduGuiComboBox* FTSDK_THISCALL AsComboBox() = 0;&lt;br /&gt;
        virtual AduGuiEditBox* FTSDK_THISCALL AsEditBox() = 0;&lt;br /&gt;
        virtual AduGuiIMEEditBox* FTSDK_THISCALL AsIMEEditBox() = 0;&lt;br /&gt;
        virtual AduGuiListBox* FTSDK_THISCALL AsListBox() = 0;&lt;br /&gt;
        virtual AduGuiScrollBar* FTSDK_THISCALL AsScrollBar() = 0;&lt;br /&gt;
        virtual AduGuiSlider* FTSDK_THISCALL AsSlider() = 0;&lt;br /&gt;
        virtual AduGuiGauge* FTSDK_THISCALL AsGauge() = 0;&lt;br /&gt;
        virtual AduGuiContextMenu* FTSDK_THISCALL AsContextMenu() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual bool FTSDK_THISCALL DefaultFalse5C() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL IsOwnerOrRelatedObject(void* candidate) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL DefaultTrue64() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ResetInteractionState() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleKeyboardCharInput(uint32_t msg, int32_t wParam, int32_t lParam) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleKeyboardNavigation(uint32_t msg, int32_t wParam, int32_t lParam) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleMouseInput(uint32_t msg, int32_t wParam, int32_t lParam, int32_t a4, int32_t a5) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL DefaultFalse78() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL SetPressedFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ClearPressedFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL SetOverFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ClearOverFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL OnHotKeyMatched() = 0;&lt;br /&gt;
        virtual Structs::Rect* FTSDK_THISCALL GetRectPtr() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL ContainsPoint(Structs::Point pt) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetEnabledState(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL IsEnabledState() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL DefaultFalseA0() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleHotKey(int32_t hotKeyValue) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ApplyStateFieldE7E0(int32_t stateSetIndex, int32_t visualStateIndex, int32_t parsedStateIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ApplyStateFieldE770(int32_t stateSetIndex, int32_t visualStateIndex) = 0;&lt;br /&gt;
        virtual AduGuiStateSet* FTSDK_THISCALL GetStateSet(int32_t stateSetIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL EnsureStateSetCopy(uint32_t stateSetIndex, const AduGuiStateSet* source) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL UpdateCurrentVisualState() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetStateFlagE8A0(int32_t visualStateIndex, bool value, int32_t stateSetIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetStateRangeD850(int32_t visualStateIndex, float minValue, float maxValue, int32_t stateSetIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ClearStateRuntimeFlag(int32_t visualStateIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ClearVisualStateInfo(int32_t visualStateIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ConfigureVisualStateInfo(int32_t visualStateIndex, bool enabled, float timing, int32_t alternateStateIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetEventSoundId(int32_t soundId, int32_t eventIndex) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL GetEventSoundId(int32_t eventIndex) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiStateSet** stateSets; // +0x20&lt;br /&gt;
        int32_t stateCount;&lt;br /&gt;
        int32_t stateCapacity;&lt;br /&gt;
&lt;br /&gt;
        Structs::FTStringA name; // XML Name&lt;br /&gt;
&lt;br /&gt;
        int32_t commandId;     // +0x48, bind command id, ctor -1&lt;br /&gt;
        int32_t groupId;       // XML GroupID&lt;br /&gt;
        int32_t controlTypeId; // XML Type, GuiControlType&lt;br /&gt;
        int32_t hotKey;        // XML HotKey&lt;br /&gt;
        int32_t subGroupId;    // XML SubGroupID, likely&lt;br /&gt;
&lt;br /&gt;
        uint8_t enabledStateFlag; // XML EnabledState via SetEnabledState&lt;br /&gt;
        uint8_t interactionActiveFlag;&lt;br /&gt;
        uint8_t focusFlag;&lt;br /&gt;
        uint8_t pad5F;&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect rect; // calculated from x/y/width/height&lt;br /&gt;
&lt;br /&gt;
        int32_t currentVisualState; // ctor 5&lt;br /&gt;
        int32_t selectedStateIndex; // XML Stage / selected visual stage&lt;br /&gt;
&lt;br /&gt;
        uint8_t bindStage; // XML BindStage&lt;br /&gt;
        uint8_t debugFlag; // XML Debug&lt;br /&gt;
        uint8_t overFlag;&lt;br /&gt;
        uint8_t pressedFlag;&lt;br /&gt;
        uint8_t field7C;&lt;br /&gt;
        uint8_t pad7D[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t x;&lt;br /&gt;
        int32_t y;&lt;br /&gt;
        int32_t width;&lt;br /&gt;
        int32_t height;&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* ownerDialog;&lt;br /&gt;
        void* relatedObject;&lt;br /&gt;
        int32_t controlIndex;&lt;br /&gt;
        int32_t dx;&lt;br /&gt;
        int32_t dy;&lt;br /&gt;
&lt;br /&gt;
        AduGuiVisualStateInfo visualStateInfos[7];&lt;br /&gt;
&lt;br /&gt;
        Structs::FTStringW text;&lt;br /&gt;
        AduGuiControl* resolvedContextMenuControl; // resolved from XML Menu, type ContextMenu&lt;br /&gt;
        Structs::FTStringA contextMenuName;        // XML Menu&lt;br /&gt;
&lt;br /&gt;
        uint8_t field1F8; // ctor 1&lt;br /&gt;
        uint8_t pad1F9[3];&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiStateSet** StateSets() const { return stateSets; }&lt;br /&gt;
        int32_t StateCount() const { return stateCount; }&lt;br /&gt;
        int32_t StateCapacity() const { return stateCapacity; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::FTStringA&amp;amp; Name() const { return name; }&lt;br /&gt;
        int32_t CommandId() const { return commandId; }&lt;br /&gt;
        int32_t GroupId() const { return groupId; }&lt;br /&gt;
        int32_t ControlTypeId() const { return controlTypeId; }&lt;br /&gt;
        int32_t HotKey() const { return hotKey; }&lt;br /&gt;
        int32_t SubGroupId() const { return subGroupId; }&lt;br /&gt;
&lt;br /&gt;
        bool IsType(GuiControlType type) const { return controlTypeId == static_cast&amp;lt;int32_t&amp;gt;(type); }&lt;br /&gt;
        bool IsEnabledStateFlag() const { return enabledStateFlag != 0; }&lt;br /&gt;
        bool IsInteractionActiveFlag() const { return interactionActiveFlag != 0; }&lt;br /&gt;
        bool IsFocusFlag() const { return focusFlag != 0; }&lt;br /&gt;
        bool IsBindStage() const { return bindStage != 0; }&lt;br /&gt;
        bool IsDebugFlag() const { return debugFlag != 0; }&lt;br /&gt;
        bool IsOverFlag() const { return overFlag != 0; }&lt;br /&gt;
        bool IsPressedFlag() const { return pressedFlag != 0; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; Rect() const { return rect; }&lt;br /&gt;
        Structs::Rect&amp;amp; Rect() { return rect; }&lt;br /&gt;
&lt;br /&gt;
        int32_t CurrentVisualState() const { return currentVisualState; }&lt;br /&gt;
        int32_t SelectedStateIndex() const { return selectedStateIndex; }&lt;br /&gt;
&lt;br /&gt;
        int32_t X() const { return x; }&lt;br /&gt;
        int32_t Y() const { return y; }&lt;br /&gt;
        int32_t Width() const { return width; }&lt;br /&gt;
        int32_t Height() const { return height; }&lt;br /&gt;
        int32_t Right() const { return x + width; }&lt;br /&gt;
        int32_t Bottom() const { return y + height; }&lt;br /&gt;
&lt;br /&gt;
        int32_t Dx() const { return dx; }&lt;br /&gt;
        int32_t Dy() const { return dy; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* OwnerDialog() const { return ownerDialog; }&lt;br /&gt;
        void* RelatedObject() const { return relatedObject; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::FTStringW&amp;amp; Text() const { return text; }&lt;br /&gt;
        const Structs::FTStringA&amp;amp; ContextMenuName() const { return contextMenuName; }&lt;br /&gt;
        AduGuiControl* ResolvedContextMenuControl() const { return resolvedContextMenuControl; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiStatic : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual int FTSDK_THISCALL RenderVisuals(void* renderCtx, int flags, const Structs::Rect* rect) = 0; // vt+0xD8&lt;br /&gt;
        virtual bool FTSDK_THISCALL RenderText(int visualIndex) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL RenderState(int stateIndex) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        Structs::FTStringW sourceTextOrTextId;&lt;br /&gt;
        Structs::FTStringW resolvedDisplayText;&lt;br /&gt;
&lt;br /&gt;
        uint8_t textDirtyFlag;&lt;br /&gt;
        uint8_t resolveTextFlag;&lt;br /&gt;
        uint8_t pad236[2];&lt;br /&gt;
&lt;br /&gt;
        int32_t field238;&lt;br /&gt;
        int32_t textFx;&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect textMargin;&lt;br /&gt;
&lt;br /&gt;
        int32_t textScrollOffsetX;&lt;br /&gt;
        uint32_t clippedTextWidth;&lt;br /&gt;
        uint8_t clippedTextRenderFlag;&lt;br /&gt;
        uint8_t pad259[3];&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        const Structs::FTStringW&amp;amp; SourceTextOrTextId() const { return sourceTextOrTextId; }&lt;br /&gt;
        const Structs::FTStringW&amp;amp; ResolvedDisplayText() const { return resolvedDisplayText; }&lt;br /&gt;
        const Structs::Rect&amp;amp; TextMargin() const { return textMargin; }&lt;br /&gt;
        bool IsTextDirty() const { return textDirtyFlag != 0; }&lt;br /&gt;
        bool UsesTextResolve() const { return resolveTextFlag != 0; }&lt;br /&gt;
        bool UsesClippedTextRender() const { return clippedTextRenderFlag != 0; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiButton : public AduGuiStatic {&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t buttonClickSoundId;&lt;br /&gt;
        int32_t buttonOverSoundId;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t ButtonClickSoundId() const { return buttonClickSoundId; }&lt;br /&gt;
        int32_t ButtonOverSoundId() const { return buttonOverSoundId; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiEditTextBuffer {&lt;br /&gt;
        void (FTSDK_CDECL* callback)(int owner); // +0x00&lt;br /&gt;
        void* owner; // +0x04&lt;br /&gt;
        void* scriptStringAnalysis; // +0x08&lt;br /&gt;
        wchar_t* text; // +0x0C&lt;br /&gt;
        uint32_t capacity; // +0x10&lt;br /&gt;
        uint32_t field14; // +0x14&lt;br /&gt;
&lt;br /&gt;
        uint8_t dirtyFlag; // +0x18&lt;br /&gt;
        uint8_t pad19[3];&lt;br /&gt;
&lt;br /&gt;
        uint32_t field1C; // +0x1C&lt;br /&gt;
        uint32_t field20; // +0x20&lt;br /&gt;
        uint32_t field24; // +0x24&lt;br /&gt;
        uint32_t field28; // +0x28&lt;br /&gt;
&lt;br /&gt;
        uint8_t hasScriptStringAnalysis; // +0x2C&lt;br /&gt;
        uint8_t pad2D[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t maxLength; // +0x30&lt;br /&gt;
&lt;br /&gt;
        const wchar_t* Text() const { return text ? text : L&amp;quot;&amp;quot;; }&lt;br /&gt;
        uint32_t Capacity() const { return capacity; }&lt;br /&gt;
        int32_t MaxLength() const { return maxLength; }&lt;br /&gt;
        std::size_t Length() const { return std::wcslen(Text()); }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiEditTextBuffer) == 0x34);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiEditBox : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual int FTSDK_THISCALL SetEditTextColor(int32_t color) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        uint32_t lineCacheUnknown; // +0x1FC&lt;br /&gt;
        void** lineCacheFirst; // +0x200&lt;br /&gt;
        void** lineCacheLast; // +0x204&lt;br /&gt;
        void** lineCacheEnd; // +0x208&lt;br /&gt;
&lt;br /&gt;
        AduGuiEditTextBuffer editText; // +0x20C&lt;br /&gt;
&lt;br /&gt;
        int32_t outerInset; // +0x240&lt;br /&gt;
        int32_t innerInset; // +0x244&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect textRect; // +0x248&lt;br /&gt;
        Structs::Rect visualRect; // +0x258&lt;br /&gt;
&lt;br /&gt;
        double caretBlinkInterval; // +0x268&lt;br /&gt;
        double lastCaretBlinkTime; // +0x270&lt;br /&gt;
&lt;br /&gt;
        uint8_t caretVisibleFlag; // +0x278&lt;br /&gt;
        uint8_t pad279[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t caretIndex; // +0x27C&lt;br /&gt;
&lt;br /&gt;
        uint8_t insertMode; // +0x280&lt;br /&gt;
        uint8_t pad281[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t selectionAnchorIndex; // +0x284&lt;br /&gt;
        int32_t editTextColor; // +0x288&lt;br /&gt;
        int32_t field28C;&lt;br /&gt;
        int32_t selectionColorOrField290;&lt;br /&gt;
        int32_t caretColorOrField294;&lt;br /&gt;
&lt;br /&gt;
        uint8_t field298;&lt;br /&gt;
        uint8_t numericOnlyFlag;&lt;br /&gt;
        uint8_t pad29A[2];&lt;br /&gt;
&lt;br /&gt;
        int32_t field29C;&lt;br /&gt;
&lt;br /&gt;
        uint8_t mouseSelectingFlag; // +0x2A0&lt;br /&gt;
        uint8_t preventOverflowFlag; // +0x2A1&lt;br /&gt;
        uint8_t multilineFlag; // +0x2A2&lt;br /&gt;
        uint8_t field2A3;&lt;br /&gt;
&lt;br /&gt;
        int32_t lineHeight; // +0x2A4, XML Option/LineHeight&lt;br /&gt;
        int32_t field2A8;&lt;br /&gt;
        int32_t field2AC;&lt;br /&gt;
&lt;br /&gt;
        uint8_t field2B0;&lt;br /&gt;
        uint8_t pad2B1[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t field2B4;&lt;br /&gt;
        int32_t field2B8;&lt;br /&gt;
&lt;br /&gt;
        int32_t editEventSoundIds[7]; // +0x2BC, EditBoxEnter..EditBoxKeyDown&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        const wchar_t* TextW() const { return editText.Text(); }&lt;br /&gt;
        std::size_t TextLength() const { return editText.Length(); }&lt;br /&gt;
        uint32_t TextCapacity() const { return editText.Capacity(); }&lt;br /&gt;
&lt;br /&gt;
        bool IsInsertMode() const { return insertMode != 0; }&lt;br /&gt;
        bool IsNumericOnly() const { return numericOnlyFlag != 0; }&lt;br /&gt;
        bool IsMultiline() const { return multilineFlag != 0; }&lt;br /&gt;
&lt;br /&gt;
        int32_t EditEventSoundId(GuiEventType eventType) const {&lt;br /&gt;
            const int32_t index = static_cast&amp;lt;int32_t&amp;gt;(eventType) - static_cast&amp;lt;int32_t&amp;gt;(EditBoxEnter);&lt;br /&gt;
            if (index &amp;lt; 0 || index &amp;gt;= 7)&lt;br /&gt;
                return -1;&lt;br /&gt;
&lt;br /&gt;
            return editEventSoundIds[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; TextRect() const { return textRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; VisualRect() const { return visualRect; }&lt;br /&gt;
&lt;br /&gt;
        int32_t OuterInset() const { return outerInset; }&lt;br /&gt;
        int32_t InnerInset() const { return innerInset; }&lt;br /&gt;
&lt;br /&gt;
        int32_t CaretIndex() const { return caretIndex; }&lt;br /&gt;
        int32_t SelectionAnchorIndex() const { return selectionAnchorIndex; }&lt;br /&gt;
        bool HasSelection() const { return caretIndex != selectionAnchorIndex; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiIMEEditBox : public AduGuiEditBox {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleImeComposition(int a2, int a3, uint16_t compositionFlags) = 0; // vt+0xD8&lt;br /&gt;
        virtual bool FTSDK_THISCALL ResetImeComposition(int a1, int a2) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleImeCandidateList(int notifyType, int param) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL RenderImeCandidateOrReadingWindow(int a2, int a3, bool readingWindow) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL RenderImeCompositionString(int a2, int a3) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL RenderImeStatusIndicator(int a2, int a3) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL ApplyImeTextStyle(int styleValue) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t imeCandidatePageBackgroundColor; // +0x2D8&lt;br /&gt;
        int32_t imeCandidateTextColor; // +0x2DC&lt;br /&gt;
        int32_t imeCandidateSelectedTextColor; // +0x2E0&lt;br /&gt;
        int32_t imeCandidateSelectedBackColor; // +0x2E4&lt;br /&gt;
        int32_t imeCandidateBorderColor; // +0x2E8&lt;br /&gt;
        int32_t imeCompositionTextColor; // +0x2EC&lt;br /&gt;
        int32_t imeCompositionBackColor; // +0x2F0&lt;br /&gt;
        int32_t imeCompositionBorderColor; // +0x2F4&lt;br /&gt;
        int32_t imeReadingTextColor; // +0x2F8&lt;br /&gt;
        int32_t imeReadingBackColor; // +0x2FC&lt;br /&gt;
        int32_t imeReadingBorderColor; // +0x300&lt;br /&gt;
        int32_t imeField304;&lt;br /&gt;
        int32_t imeField308;&lt;br /&gt;
        int32_t imeField30C;&lt;br /&gt;
        int32_t imeField310;&lt;br /&gt;
        int32_t imeField314;&lt;br /&gt;
        int32_t imeField318;&lt;br /&gt;
        int32_t imeField31C;&lt;br /&gt;
        int32_t imeField320;&lt;br /&gt;
        int32_t imeField324;&lt;br /&gt;
&lt;br /&gt;
        uint8_t imeActiveFlag; // +0x328&lt;br /&gt;
        uint8_t pad329[3];&lt;br /&gt;
        uint8_t pad32C[4];&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiCheckBox : public AduGuiButton {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL SetChecked(bool checked, bool dispatchEvent) = 0; // vt+0xE4&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        uint8_t hasSeparateCheckImage; // +0x264&lt;br /&gt;
        uint8_t checkedFlag; // +0x265&lt;br /&gt;
        uint8_t pad266[2];&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect checkImageRect; // +0x268&lt;br /&gt;
        Structs::Rect checkLabelHitRect; // +0x278&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;int32_t&amp;gt; linkedCheckCommandIds; // +0x288&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsChecked() const { return checkedFlag != 0; }&lt;br /&gt;
        bool HasSeparateCheckImage() const { return hasSeparateCheckImage != 0; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; CheckImageRect() const { return checkImageRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; CheckLabelHitRect() const { return checkLabelHitRect; }&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;int32_t&amp;gt;&amp;amp; LinkedCheckCommandIds() { return linkedCheckCommandIds; }&lt;br /&gt;
        const Structs::FTVector&amp;lt;int32_t&amp;gt;&amp;amp; LinkedCheckCommandIds() const { return linkedCheckCommandIds; }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiCheckBox) == 0x298);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiRadioButton : public AduGuiCheckBox {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL SetRadioChecked(bool checked, bool clearGroup, bool dispatchEvent) = 0; // vt+0xE8&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        uint8_t allowRightMouseToggleFlag; // +0x298&lt;br /&gt;
        uint8_t pad299[3];&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool AllowsRightMouseToggle() const { return allowRightMouseToggleFlag != 0; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiSlider : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t value; // +0x1FC, XML Value, ctor 50&lt;br /&gt;
        int32_t minValue; // +0x200, XML Range min, ctor 0&lt;br /&gt;
        int32_t maxValue; // +0x204, XML Range max, ctor 100&lt;br /&gt;
&lt;br /&gt;
        int32_t dragStartX; // +0x208&lt;br /&gt;
        int32_t dragOffsetX; // +0x20C&lt;br /&gt;
&lt;br /&gt;
        int32_t thumbOffsetX; // +0x210, calculated from value/range&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect thumbRect; // +0x214&lt;br /&gt;
&lt;br /&gt;
        int32_t sliderValueChangeSoundId; // +0x224, GuiEventType::SliderValueChange&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t Value() const { return value; }&lt;br /&gt;
        int32_t MinValue() const { return minValue; }&lt;br /&gt;
        int32_t MaxValue() const { return maxValue; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; ThumbRect() const { return thumbRect; }&lt;br /&gt;
&lt;br /&gt;
        int32_t SliderValueChangeSoundId() const { return sliderValueChangeSoundId; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiGauge : public AduGuiStatic {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL GetRange() = 0; // vt+0xE4&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL GetValue() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetRange(int32_t range) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetValue(int32_t value) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t range; // +0x25C, XML Range, ctor 100&lt;br /&gt;
        int32_t value; // +0x260, XML Value, ctor 50&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t Range() const { return range; }&lt;br /&gt;
        int32_t Value() const { return value; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiScrollBar : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL ContainsScrollHitRect(Structs::Point pt) = 0; // vt+0xD8&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        uint8_t scrollEnabledFlag; // +0x1FC&lt;br /&gt;
        uint8_t thumbDraggingFlag; // +0x1FD&lt;br /&gt;
        uint8_t pad1FE[2];&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect upButtonRect; // +0x200&lt;br /&gt;
        Structs::Rect downButtonRect; // +0x210&lt;br /&gt;
        Structs::Rect trackRect; // +0x220&lt;br /&gt;
        Structs::Rect thumbRect; // +0x230&lt;br /&gt;
&lt;br /&gt;
        int32_t scrollValue; // +0x240&lt;br /&gt;
        int32_t pageSize; // +0x244&lt;br /&gt;
        int32_t minValue; // +0x248&lt;br /&gt;
        int32_t maxValue; // +0x24C&lt;br /&gt;
&lt;br /&gt;
        Structs::Point lastMousePoint; // +0x250&lt;br /&gt;
&lt;br /&gt;
        int32_t repeatMode; // +0x258, 0 none, 1 up initial, 2 down initial, 3 up repeat, 4 down repeat&lt;br /&gt;
        double lastRepeatTime; // +0x260&lt;br /&gt;
&lt;br /&gt;
        uint8_t suppressScrollChangeEvent; // +0x268&lt;br /&gt;
        uint8_t pad269[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t checkWidth; // +0x26C, XML Option/CheckWidth&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect scrollHitRect; // +0x270&lt;br /&gt;
&lt;br /&gt;
        uint8_t field280; // +0x280&lt;br /&gt;
        uint8_t normalDirectionFlag; // +0x281&lt;br /&gt;
        uint8_t pad282[2];&lt;br /&gt;
&lt;br /&gt;
        int32_t scrollChangeSoundId; // +0x284, GuiEventType::ScrollBarChange&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsScrollEnabled() const { return scrollEnabledFlag != 0; }&lt;br /&gt;
        bool IsThumbDragging() const { return thumbDraggingFlag != 0; }&lt;br /&gt;
&lt;br /&gt;
        int32_t ScrollValue() const { return scrollValue; }&lt;br /&gt;
        int32_t PageSize() const { return pageSize; }&lt;br /&gt;
        int32_t MinValue() const { return minValue; }&lt;br /&gt;
        int32_t MaxValue() const { return maxValue; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; UpButtonRect() const { return upButtonRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; DownButtonRect() const { return downButtonRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; TrackRect() const { return trackRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; ThumbRect() const { return thumbRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; ScrollHitRect() const { return scrollHitRect; }&lt;br /&gt;
&lt;br /&gt;
        int32_t ScrollChangeSoundId() const { return scrollChangeSoundId; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiListBoxItem {&lt;br /&gt;
        int32_t textColor; // +0x000&lt;br /&gt;
        wchar_t text[256]; // +0x004&lt;br /&gt;
        int32_t itemData; // +0x204&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknown208[0x10];&lt;br /&gt;
&lt;br /&gt;
        uint8_t selectedFlag; // +0x218&lt;br /&gt;
        uint8_t pad219[3];&lt;br /&gt;
&lt;br /&gt;
        const wchar_t* Text() const { return text; }&lt;br /&gt;
        bool IsSelected() const { return selectedFlag != 0; }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiListBoxItem) == 0x21C);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiListBox : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        Structs::Rect textClipRect; // +0x1FC&lt;br /&gt;
        Structs::Rect itemVisualRect; // +0x20C&lt;br /&gt;
&lt;br /&gt;
        AduGuiScrollBar* scrollBar; // +0x21C&lt;br /&gt;
&lt;br /&gt;
        int32_t rightReservedWidth; // +0x220, ctor 16&lt;br /&gt;
        int32_t outerPadding; // +0x224, ctor 6&lt;br /&gt;
        int32_t textHorizontalPadding; // +0x228, ctor 5&lt;br /&gt;
        int32_t fontHeight; // +0x22C&lt;br /&gt;
&lt;br /&gt;
        int32_t selectionFlags; // +0x230, bit 0 enables multi/range selection behavior&lt;br /&gt;
        int32_t selectedIndex; // +0x234, ctor -1&lt;br /&gt;
        int32_t selectionAnchorIndex; // +0x238&lt;br /&gt;
&lt;br /&gt;
        uint8_t mouseSelectingFlag; // +0x23C&lt;br /&gt;
        uint8_t autoScrollToBottomFlag; // +0x23D&lt;br /&gt;
        uint8_t listEnabledFlag; // +0x23E&lt;br /&gt;
        uint8_t pad23F;&lt;br /&gt;
&lt;br /&gt;
        int32_t rowHeight; // +0x240, fontHeight + rowSpacing&lt;br /&gt;
        int32_t rowSpacing; // +0x244, ctor 2&lt;br /&gt;
        int32_t field248; // +0x248, ctor 1&lt;br /&gt;
&lt;br /&gt;
        AduGuiListBoxItem** items; // +0x24C&lt;br /&gt;
        int32_t itemCount; // +0x250&lt;br /&gt;
        int32_t itemCapacity; // +0x254&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiScrollBar* ScrollBar() const { return scrollBar; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; TextClipRect() const { return textClipRect; }&lt;br /&gt;
        const Structs::Rect&amp;amp; ItemVisualRect() const { return itemVisualRect; }&lt;br /&gt;
&lt;br /&gt;
        int32_t SelectedIndex() const { return selectedIndex; }&lt;br /&gt;
        int32_t SelectionAnchorIndex() const { return selectionAnchorIndex; }&lt;br /&gt;
&lt;br /&gt;
        bool IsMultiSelectEnabled() const { return (selectionFlags &amp;amp; 1) != 0; }&lt;br /&gt;
        bool IsMouseSelecting() const { return mouseSelectingFlag != 0; }&lt;br /&gt;
        bool IsAutoScrollToBottomEnabled() const { return autoScrollToBottomFlag != 0; }&lt;br /&gt;
        bool IsListEnabled() const { return listEnabledFlag != 0; }&lt;br /&gt;
&lt;br /&gt;
        int32_t ItemCount() const { return itemCount; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiListBoxItem* ItemAt(int32_t index) const {&lt;br /&gt;
            if (!items || index &amp;lt; 0 || index &amp;gt;= itemCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return items[index];&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiComboBoxItem {&lt;br /&gt;
        wchar_t text[256]; // +0x000&lt;br /&gt;
        int32_t itemData; // +0x200&lt;br /&gt;
        Structs::Rect itemRect; // +0x204&lt;br /&gt;
        uint8_t visibleFlag; // +0x214&lt;br /&gt;
        uint8_t pad215[3];&lt;br /&gt;
&lt;br /&gt;
        const wchar_t* Text() const { return text; }&lt;br /&gt;
        bool IsVisible() const { return visibleFlag != 0; }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiComboBoxItem) == 0x218);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiComboBox : public AduGuiButton {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual int FTSDK_THISCALL ApplyComboBoxStateStyle(int styleValue) = 0; // vt+0xE4&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t selectedIndex; // +0x264&lt;br /&gt;
        int32_t hoverOrPreviewIndex; // +0x268&lt;br /&gt;
&lt;br /&gt;
        int32_t dropHeight; // +0x26C, XML DropHeight&lt;br /&gt;
        AduGuiScrollBar* scrollBar; // +0x270&lt;br /&gt;
&lt;br /&gt;
        int32_t dropButtonWidth; // +0x274, ctor 16&lt;br /&gt;
&lt;br /&gt;
        uint8_t dropdownOpenFlag; // +0x278&lt;br /&gt;
        uint8_t showDropButtonFlag; // +0x279, XML ShowDropButton&lt;br /&gt;
        uint8_t pad27A[2];&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect selectedTextRect; // +0x27C&lt;br /&gt;
        Structs::Rect dropButtonRect; // +0x28C&lt;br /&gt;
        Structs::Rect dropdownOuterRect; // +0x29C&lt;br /&gt;
        Structs::Rect dropdownInnerRect; // +0x2AC&lt;br /&gt;
&lt;br /&gt;
        int32_t rowHeight; // +0x2BC&lt;br /&gt;
        int32_t rowSpacing; // +0x2C0, ctor 2&lt;br /&gt;
&lt;br /&gt;
        int32_t comboBoxCloseSoundId; // +0x2C4, GuiEventType::ComboBoxClose&lt;br /&gt;
&lt;br /&gt;
        AduGuiComboBoxItem** items; // +0x2C8&lt;br /&gt;
        int32_t itemCount; // +0x2CC&lt;br /&gt;
        int32_t itemCapacity; // +0x2D0&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t SelectedIndex() const { return selectedIndex; }&lt;br /&gt;
        int32_t HoverOrPreviewIndex() const { return hoverOrPreviewIndex; }&lt;br /&gt;
&lt;br /&gt;
        bool IsDropdownOpen() const { return dropdownOpenFlag != 0; }&lt;br /&gt;
        bool ShowsDropButton() const { return showDropButtonFlag != 0; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiScrollBar* ScrollBar() const { return scrollBar; }&lt;br /&gt;
&lt;br /&gt;
        int32_t ItemCount() const { return itemCount; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiComboBoxItem* ItemAt(int32_t index) const {&lt;br /&gt;
            if (!items || index &amp;lt; 0 || index &amp;gt;= itemCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return items[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t ComboBoxOpenSoundId() const { return buttonClickSoundId; }&lt;br /&gt;
        int32_t ComboBoxSelectChangeSoundId() const { return buttonOverSoundId; }&lt;br /&gt;
        int32_t ComboBoxCloseSoundId() const { return comboBoxCloseSoundId; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiContextMenuItem {&lt;br /&gt;
        int32_t itemData; // +0x00&lt;br /&gt;
        Structs::FTStringW text; // +0x04&lt;br /&gt;
&lt;br /&gt;
        const wchar_t* Text() const { return text.Data(); }&lt;br /&gt;
        int32_t ItemData() const { return itemData; }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiContextMenuItem) == 0x20);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiContextMenu : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t field1FC; // +0x1FC, ctor 0&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect itemAreaRect; // +0x200&lt;br /&gt;
&lt;br /&gt;
        int32_t itemPaddingX; // +0x210, ctor 5&lt;br /&gt;
        int32_t itemPaddingY; // +0x214, ctor 3&lt;br /&gt;
        int32_t maxTextWidth; // +0x218&lt;br /&gt;
        int32_t fontHeight; // +0x21C&lt;br /&gt;
&lt;br /&gt;
        int32_t menuContentWidth; // +0x220&lt;br /&gt;
        int32_t itemHeight; // +0x224&lt;br /&gt;
&lt;br /&gt;
        int32_t hoverItemIndex; // +0x228, ctor -1&lt;br /&gt;
        int32_t selectedItemIndex; // +0x22C, ctor -1&lt;br /&gt;
&lt;br /&gt;
        int32_t contextMenuClickSoundId; // +0x230, GuiEventType::ContextMenuClick&lt;br /&gt;
&lt;br /&gt;
        AduGuiContextMenuItem** items; // +0x234&lt;br /&gt;
        int32_t itemCount; // +0x238&lt;br /&gt;
        int32_t itemCapacity; // +0x23C&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t HoverItemIndex() const { return hoverItemIndex; }&lt;br /&gt;
        int32_t SelectedItemIndex() const { return selectedItemIndex; }&lt;br /&gt;
&lt;br /&gt;
        int32_t ItemCount() const { return itemCount; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiContextMenuItem* ItemAt(int32_t index) const {&lt;br /&gt;
            if (!items || index &amp;lt; 0 || index &amp;gt;= itemCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return items[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t ContextMenuClickSoundId() const { return contextMenuClickSoundId; }&lt;br /&gt;
    };&lt;br /&gt;
    &lt;br /&gt;
    static_assert(sizeof(AduBase) == 0x08);&lt;br /&gt;
    static_assert(sizeof(AduGameObj) == 0x20);&lt;br /&gt;
    static_assert(sizeof(AduGuiControl) == 0x1FC);&lt;br /&gt;
    static_assert(sizeof(AduGuiStatic) == 0x25C);&lt;br /&gt;
    static_assert(sizeof(AduGuiButton) == 0x264);&lt;br /&gt;
    static_assert(sizeof(AduGuiCheckBox) == 0x298);&lt;br /&gt;
    static_assert(sizeof(AduGuiRadioButton) == 0x29C);&lt;br /&gt;
    static_assert(sizeof(AduGuiComboBox) == 0x2D4);&lt;br /&gt;
    static_assert(sizeof(AduGuiSlider) == 0x228);&lt;br /&gt;
    static_assert(sizeof(AduGuiGauge) == 0x264);&lt;br /&gt;
    static_assert(sizeof(AduGuiEditBox) == 0x2D8);&lt;br /&gt;
    static_assert(sizeof(AduGuiIMEEditBox) == 0x330);&lt;br /&gt;
    static_assert(sizeof(AduGuiListBox) == 0x258);&lt;br /&gt;
    static_assert(sizeof(AduGuiScrollBar) == 0x288);&lt;br /&gt;
    static_assert(sizeof(AduGuiContextMenu) == 0x240);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiDialog : public AduGameObj {&lt;br /&gt;
    public:&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t dialogIdOrField20; // +0x20, ctor 0xFFFF&lt;br /&gt;
        int32_t field24;&lt;br /&gt;
&lt;br /&gt;
        double lastPeriodicUpdateTime; // +0x28&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* tooltipOrDelayedTextControl; // +0x30&lt;br /&gt;
&lt;br /&gt;
        uint8_t field34;&lt;br /&gt;
        uint8_t field35;&lt;br /&gt;
        uint8_t pad36[2];&lt;br /&gt;
&lt;br /&gt;
        int32_t field38;&lt;br /&gt;
        int32_t field3C;&lt;br /&gt;
        int32_t dialogX;      // +0x40, XML Dialog x&lt;br /&gt;
        int32_t dialogY;      // +0x44, XML Dialog y&lt;br /&gt;
        int32_t dialogWidth;  // +0x48, XML Dialog w&lt;br /&gt;
        int32_t dialogHeight; // +0x4C, XML Dialog h&lt;br /&gt;
&lt;br /&gt;
        float scaleX; // +0x50, ctor 1.0&lt;br /&gt;
        float scaleY; // +0x54, ctor 1.0&lt;br /&gt;
&lt;br /&gt;
        int32_t field58;&lt;br /&gt;
        int32_t field5C;&lt;br /&gt;
        int32_t field60;&lt;br /&gt;
        int32_t field64;&lt;br /&gt;
&lt;br /&gt;
        void* renderContext; // +0x68&lt;br /&gt;
&lt;br /&gt;
        GuiCallbackFn callback; // +0x6C&lt;br /&gt;
        void* callbackUserData; // +0x70&lt;br /&gt;
&lt;br /&gt;
        void** imageResources; // +0x74&lt;br /&gt;
        int32_t imageResourceCount; // +0x78&lt;br /&gt;
        int32_t imageResourceCapacity; // +0x7C&lt;br /&gt;
&lt;br /&gt;
        Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt; controlMap; // +0x80&lt;br /&gt;
&lt;br /&gt;
        AduGameObj** objectArray; // +0x8C&lt;br /&gt;
        int32_t objectCount; // +0x90&lt;br /&gt;
        int32_t objectCapacity; // +0x94&lt;br /&gt;
&lt;br /&gt;
        void** runtimeEntryArray; // +0x98&lt;br /&gt;
        int32_t runtimeEntryCount; // +0x9C&lt;br /&gt;
        int32_t runtimeEntryCapacity; // +0xA0&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* selfA4; // +0xA4&lt;br /&gt;
        AduGuiDialog* selfA8; // +0xA8&lt;br /&gt;
&lt;br /&gt;
        uint8_t rotateFocusFlag; // +0xAC, XML RotateFocus, ctor 1&lt;br /&gt;
        uint8_t padAD[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t delayedTextX; // +0xB0&lt;br /&gt;
        int32_t delayedTextY; // +0xB4&lt;br /&gt;
&lt;br /&gt;
        uint8_t delayedTextEnabled; // +0xB8&lt;br /&gt;
        uint8_t padB9[3];&lt;br /&gt;
&lt;br /&gt;
        float delayedTextTimer; // +0xBC&lt;br /&gt;
&lt;br /&gt;
        uint8_t dispatchEventsFlag; // +0xC0&lt;br /&gt;
        uint8_t keepCaptureAfterEventFlag; // +0xC1&lt;br /&gt;
        uint8_t fieldC2; // +0xC2, ctor 1&lt;br /&gt;
        uint8_t padC3;&lt;br /&gt;
&lt;br /&gt;
        float fieldC4;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        void SetCallback(GuiCallbackFn callback, void* userData) {&lt;br /&gt;
			this-&amp;gt;callback = callback;&lt;br /&gt;
			this-&amp;gt;callbackUserData = userData;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* ObjectAt(int32_t index) const {&lt;br /&gt;
            auto* arr = objectArray;&lt;br /&gt;
&lt;br /&gt;
			if (!arr || index &amp;lt; 0 || index &amp;gt;= objectCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return arr[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* ControlAt(int32_t index) const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;AduGuiControl*&amp;gt;(ObjectAt(index));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByCommandId(int32_t commandId) const {&lt;br /&gt;
            const int32_t count = objectCount;&lt;br /&gt;
&lt;br /&gt;
            if (count &amp;lt; 0 || count &amp;gt; 4096)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; count; ++i) {&lt;br /&gt;
                auto* control = ControlAt(i);&lt;br /&gt;
                if (!control)&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;CommandId() == commandId)&lt;br /&gt;
                    return control;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByName(const char* name) const {&lt;br /&gt;
            if (!name)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            const int32_t count = objectCount;&lt;br /&gt;
&lt;br /&gt;
            if (count &amp;lt; 0 || count &amp;gt; 4096)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; count; ++i) {&lt;br /&gt;
                auto* control = ControlAt(i);&lt;br /&gt;
                if (!control)&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;Name().Equals(name))&lt;br /&gt;
                    return control;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByNameAndType(const char* name, GuiControlType type) const {&lt;br /&gt;
            if (!name)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            const int32_t count = objectCount;&lt;br /&gt;
&lt;br /&gt;
            if (count &amp;lt; 0 || count &amp;gt; 4096)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; count; ++i) {&lt;br /&gt;
                auto* control = ControlAt(i);&lt;br /&gt;
                if (!control)&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;ControlTypeId() != static_cast&amp;lt;int32_t&amp;gt;(type))&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;Name().Equals(name))&lt;br /&gt;
                    return control;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
	static_assert(sizeof(AduGuiDialog) == 0xC8);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiCustomControlBase : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual bool FTSDK_THISCALL LoadEmbeddedGuiXml(const char* xmlName, const Structs::GuiBind* binds, int32_t bindCount, GuiCallbackFn callback) = 0; // vt+0xD8&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedCommand(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedEditEnter(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedEditChange(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedEditTab(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedEditEsc(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedEditKeyUp(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedEditKeyDown(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEmbeddedContextMenuClick(int32_t commandId, int32_t selectedValue, AduGuiControl* contextMenuControl, int32_t selectedData) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL DispatchEmbeddedGuiEvent(GuiEventType eventType, int32_t commandId, int32_t param, AduGuiControl* control) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiDialog* embeddedDialog; // +0x1FC&lt;br /&gt;
        Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt; embeddedBindMap; // +0x200&lt;br /&gt;
        int32_t embeddedField20C;&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiCustomControlBase) == 0x210);&lt;br /&gt;
&lt;br /&gt;
    class FTRankingInfo : public AduGuiCustomControlBase {&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiButton* btClothInfoInner; // +0x210, XML btClothInfo&lt;br /&gt;
&lt;br /&gt;
        AduGuiStatic* stRankingInner; // +0x214&lt;br /&gt;
        AduGuiStatic* stEmblemMarkInner; // +0x218&lt;br /&gt;
        AduGuiStatic* stUserNameInner; // +0x21C&lt;br /&gt;
        AduGuiStatic* stUserLevelInner; // +0x220&lt;br /&gt;
        AduGuiStatic* stUserExpInner; // +0x224&lt;br /&gt;
        AduGuiStatic* stWinPercentInner; // +0x228&lt;br /&gt;
        AduGuiStatic* stRankingPointInner; // +0x22C&lt;br /&gt;
&lt;br /&gt;
        void* rankingRecord; // +0x230, points to 52-byte ranking row/cache record&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool HasRankingRecord() const {&lt;br /&gt;
            return rankingRecord != nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t RankingPlayerId() const {&lt;br /&gt;
            return rankingRecord&lt;br /&gt;
                ? *reinterpret_cast&amp;lt;const int32_t*&amp;gt;(&lt;br /&gt;
                    reinterpret_cast&amp;lt;const uint8_t*&amp;gt;(rankingRecord) + 0x04&lt;br /&gt;
                    )&lt;br /&gt;
                : 0;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(FTRankingInfo) == 0x234);&lt;br /&gt;
&lt;br /&gt;
    class StageManager;&lt;br /&gt;
    class AduEngine;&lt;br /&gt;
&lt;br /&gt;
    class ScreenRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved00() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved04() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved08() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved0C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved10() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved14() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved18() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved1C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved20() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved24() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved28() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual int FTSDK_THISCALL OnFocusEnter() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnFocusLeave() = 0;&lt;br /&gt;
        virtual void* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL AttachRaw(ScreenRoot* parentOrManager, int32_t rectVtableOrTemp, int32_t left, int32_t top, int32_t right, int32_t bottom, int32_t id) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL Shutdown() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL Update() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OffsetRect(int32_t dx, int32_t dy) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL CenterRectInParent() = 0;&lt;br /&gt;
        virtual ScreenRoot* FTSDK_THISCALL ActivateProcessing() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL DeactivateProcessing() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual int FTSDK_THISCALL OnStageCommand(int32_t commandId) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved58() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved5C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved60() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved64() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved68() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved6C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved70() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved74() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved78() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved7C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved80() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_THISCALL SetOpenFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ClearOpenFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_STDCALL RectCleanupThunk(int32_t a1, uint8_t rectObj, int32_t a3, int32_t a4) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL HandleSystemEvent(int32_t eventType, void* data, int32_t param) = 0;&lt;br /&gt;
        virtual void FTSDK_STDCALL RectCleanupThunk2(uint8_t rectObj, int32_t a2, int32_t a3) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleInput(uint32_t msg, int32_t wParam, int32_t lParam) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t id;&lt;br /&gt;
        float field08;&lt;br /&gt;
        float field0C;&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknown10[0x80];&lt;br /&gt;
&lt;br /&gt;
        Structs::GuiRectStorage rect;&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;ScreenRoot*&amp;gt; children;&lt;br /&gt;
        ScreenRoot* parent;&lt;br /&gt;
&lt;br /&gt;
        int32_t parentChainEnabled;&lt;br /&gt;
        int32_t processEnabled;&lt;br /&gt;
        int32_t openFlag;&lt;br /&gt;
        int32_t rectOffsetEnabled;&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknownC8[0x70];&lt;br /&gt;
        char resourceName[0x80];&lt;br /&gt;
        void* lazyResource;&lt;br /&gt;
        uint8_t lazyResourceAllowed;&lt;br /&gt;
        uint8_t pad1BD[3];&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsParentChainEnabled() const { return parentChainEnabled != 0; }&lt;br /&gt;
        bool IsProcessingEnabled() const { return processEnabled != 0; }&lt;br /&gt;
        bool IsOpen() const { return openFlag != 0; }&lt;br /&gt;
        bool IsRectOffsetEnabled() const { return rectOffsetEnabled != 0; }&lt;br /&gt;
        bool HasParent() const { return parent != nullptr; }&lt;br /&gt;
        bool HasLazyResource() const { return lazyResource != nullptr; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::GuiRectStorage&amp;amp; RectStorage() const { return rect; }&lt;br /&gt;
        Structs::GuiRectStorage&amp;amp; RectStorage() { return rect; }&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;ScreenRoot*&amp;gt;&amp;amp; Children() { return children; }&lt;br /&gt;
        const Structs::FTVector&amp;lt;ScreenRoot*&amp;gt;&amp;amp; Children() const { return children; }&lt;br /&gt;
&lt;br /&gt;
        int32_t Left() const { return rect.left; }&lt;br /&gt;
        int32_t Top() const { return rect.top; }&lt;br /&gt;
        int32_t Right() const { return rect.right; }&lt;br /&gt;
        int32_t Bottom() const { return rect.bottom; }&lt;br /&gt;
        int32_t Width() const { return rect.right - rect.left; }&lt;br /&gt;
        int32_t Height() const { return rect.bottom - rect.top; }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(ScreenRoot) == 0x1C0);&lt;br /&gt;
&lt;br /&gt;
    class ScreenInputRoot : public ScreenRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        void* inputHwnd;&lt;br /&gt;
        int32_t startupParamOrMode;&lt;br /&gt;
        AduEngine* engine;&lt;br /&gt;
&lt;br /&gt;
        ScreenRoot* previousFocusOwner;&lt;br /&gt;
        ScreenRoot* currentFocusOwner;&lt;br /&gt;
        ScreenRoot* previousHoverOwner;&lt;br /&gt;
        ScreenRoot* currentHoverOwner;&lt;br /&gt;
&lt;br /&gt;
        int32_t previousCursorX;&lt;br /&gt;
        int32_t previousCursorY;&lt;br /&gt;
&lt;br /&gt;
        int32_t cursorX;&lt;br /&gt;
        int32_t cursorY;&lt;br /&gt;
&lt;br /&gt;
        uint8_t inputState[0x26C];&lt;br /&gt;
&lt;br /&gt;
        ScreenRoot* hoverOrCapturedOwner;&lt;br /&gt;
&lt;br /&gt;
        char resourceBasePath[0x104];&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool HasCurrentFocusOwner() const { return currentFocusOwner != nullptr; }&lt;br /&gt;
        bool HasCurrentHoverOwner() const { return currentHoverOwner != nullptr; }&lt;br /&gt;
&lt;br /&gt;
        int32_t CursorDeltaX() const { return cursorX - previousCursorX; }&lt;br /&gt;
        int32_t CursorDeltaY() const { return cursorY - previousCursorY; }&lt;br /&gt;
&lt;br /&gt;
        using SetFocusOwnerFn = ScreenRoot * (FTSDK_THISCALL*)(ScreenInputRoot* self, ScreenRoot* owner);&lt;br /&gt;
        ScreenRoot* SetFocusOwner(ScreenRoot* owner) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;SetFocusOwnerFn&amp;gt;(0x005AAF70)(this, owner);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using DispatchInputMessageFn = bool(FTSDK_FASTCALL*)(ScreenInputRoot* self, int edx0, uint32_t msg, int32_t wParam, int32_t lParam);&lt;br /&gt;
        bool DispatchInputMessage(uint32_t msg, int32_t wParam, int32_t lParam) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;DispatchInputMessageFn&amp;gt;(0x005AB1F0)(this, 0, msg, wParam, lParam);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using UpdateInputRootFn = int(FTSDK_THISCALL*)(ScreenInputRoot* self);&lt;br /&gt;
        int UpdateInputRoot() {&lt;br /&gt;
            return reinterpret_cast&amp;lt;UpdateInputRootFn&amp;gt;(0x005AB180)(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using InitInputRootFn = char* (FTSDK_THISCALL*)(ScreenInputRoot* self, void* hwnd, AduEngine* aduEngine, int32_t startupParamOrMode,&lt;br /&gt;
            const char* resourceBasePath, int cursorResourceA, int cursorResourceB);&lt;br /&gt;
        char* InitInputRoot(void* hwndValue, AduEngine* aduEngineValue, int32_t startupParam, const char* basePath, int cursorResourceA, int cursorResourceB) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;InitInputRootFn&amp;gt;(0x005AAEA0)(this, hwndValue, aduEngineValue, startupParam, basePath, cursorResourceA, cursorResourceB);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using SetCursorModeFn = uint32_t(FTSDK_THISCALL*)(ScreenInputRoot* self, int32_t mode);&lt;br /&gt;
        uint32_t SetCursorMode(int32_t mode) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;SetCursorModeFn&amp;gt;(0x005AAD30)(this, mode);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(ScreenInputRoot) == 0x560);&lt;br /&gt;
&lt;br /&gt;
    class StageBase : public ScreenRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL AttachOwnerWithDefaultRect(ScreenRoot* parent, int32_t id) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL InitDialogWithParentOwner(StageBase* parent) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL LoadXml(const char* xmlName, const Structs::GuiBind* binds, int32_t bindCount, GuiCallbackFn callback, bool reload) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnActivate() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnDeactivate() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnUpdate() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL ProcessChildOwners(float dt) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL HandleStagePacket(void* packet) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL HandleGuiEvent(GuiEventType eventType, int commandId, int param) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnCommandAction(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxEnter(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxChange(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxTab(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxEsc(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxKeyUp(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxKeyDown(int32_t commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnContextMenuClick(int32_t commandId, int selectedValue, AduGuiControl* contextMenuControl, int selectedData) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        StageBase* priorityInputChildOwner;&lt;br /&gt;
        uint8_t inputConsumedFlag;&lt;br /&gt;
        uint8_t padOrUnknown1C5[3];&lt;br /&gt;
        AduGuiDialog* xmlDialog;&lt;br /&gt;
&lt;br /&gt;
        Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt; controlBindMap;&lt;br /&gt;
        Structs::FTVector&amp;lt;StageBase*&amp;gt; processingChildOwners;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool InputConsumedFlag() const { return inputConsumedFlag != 0; }&lt;br /&gt;
        AduGuiDialog* XmlDialog() const { return xmlDialog; }&lt;br /&gt;
        bool HasXmlDialog() const { return xmlDialog != nullptr; }&lt;br /&gt;
        StageBase* PriorityInputChildOwner() const { return priorityInputChildOwner; }&lt;br /&gt;
&lt;br /&gt;
        Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt;&amp;amp; ControlBindMap() { return controlBindMap; }&lt;br /&gt;
        const Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt;&amp;amp; ControlBindMap() const { return controlBindMap; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindBoundControl(int32_t commandId) const { return controlBindMap.Find(commandId); }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindBoundControl(int32_t commandId, GuiControlType expectedType) const {&lt;br /&gt;
            auto* control = FindBoundControl(commandId);&lt;br /&gt;
&lt;br /&gt;
            if (!control)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            if (control-&amp;gt;ControlTypeId() != static_cast&amp;lt;int32_t&amp;gt;(expectedType))&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return control;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool HasBoundControl(int32_t commandId) const { return controlBindMap.Contains(commandId); }&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;StageBase*&amp;gt;&amp;amp; ProcessingChildOwners() { return processingChildOwners; }&lt;br /&gt;
        const Structs::FTVector&amp;lt;StageBase*&amp;gt;&amp;amp; ProcessingChildOwners() const { return processingChildOwners; }&lt;br /&gt;
&lt;br /&gt;
        void SetPriorityInputChildOwner(StageBase* child) { priorityInputChildOwner = child; }&lt;br /&gt;
&lt;br /&gt;
        void ClearPriorityInputChildOwnerIf(StageBase* child) {&lt;br /&gt;
            if (PriorityInputChildOwner() == child)&lt;br /&gt;
                SetPriorityInputChildOwner(nullptr);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using AddChildOwnerFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);&lt;br /&gt;
        void AddChildOwner(StageBase* child) {&lt;br /&gt;
            reinterpret_cast&amp;lt;AddChildOwnerFn&amp;gt;(0x005AA720)(this, child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using BringChildOwnerToFrontFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);&lt;br /&gt;
        void BringChildOwnerToFront(StageBase* child) {&lt;br /&gt;
            reinterpret_cast&amp;lt;BringChildOwnerToFrontFn&amp;gt;(0x005AA780)(this, child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using IsOwnerReachableFn = int(FTSDK_FASTCALL*)(StageBase* self);&lt;br /&gt;
        bool IsReachableThroughParentChain() const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;IsOwnerReachableFn&amp;gt;(0x005A99E0)(const_cast&amp;lt;StageBase*&amp;gt;(this)) != 0;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(StageBase) == 0x1E8);&lt;br /&gt;
&lt;br /&gt;
    class MessageBoxDialogOwner : public StageBase {&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t messageBoxMode;&lt;br /&gt;
&lt;br /&gt;
        int32_t okYesEventType;&lt;br /&gt;
        int32_t okYesEventData;&lt;br /&gt;
        int32_t okYesEventParam;&lt;br /&gt;
&lt;br /&gt;
        int32_t noEventType;&lt;br /&gt;
        int32_t noEventData;&lt;br /&gt;
        int32_t noEventParam;&lt;br /&gt;
&lt;br /&gt;
        void* stMessageInnerObject;&lt;br /&gt;
        AduGuiControl* btOKControl;&lt;br /&gt;
        AduGuiControl* btYesControl;&lt;br /&gt;
        AduGuiControl* btNoControl;&lt;br /&gt;
        void* stTimeLeftInnerObject;&lt;br /&gt;
&lt;br /&gt;
        float delayTimer;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsTimedMode() const {&lt;br /&gt;
            return messageBoxMode == MessageBox_TimedOK || messageBoxMode == MessageBox_TimedYesNo;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsYesNoMode() const {&lt;br /&gt;
            return messageBoxMode == MessageBox_YesNo || messageBoxMode == MessageBox_TimedYesNo;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsOkOnlyMode() const {&lt;br /&gt;
            return messageBoxMode == MessageBox_OK || messageBoxMode == MessageBox_TimedOK;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsShowing() const {&lt;br /&gt;
            return processEnabled != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using ShowMessageBoxOwnerFn = MessageBoxDialogOwner * (FTSDK_THISCALL*)(MessageBoxDialogOwner* self, const wchar_t* message, int32_t mode, const int32_t* okYesEventArgs, const int32_t* noEventArgs);&lt;br /&gt;
        MessageBoxDialogOwner* Show(const wchar_t* message, MessageBoxMode mode, const int32_t* okYesEventArgs = nullptr, const int32_t* noEventArgs = nullptr) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;ShowMessageBoxOwnerFn&amp;gt;(0x0056DAE0)(&lt;br /&gt;
                this,&lt;br /&gt;
                message,&lt;br /&gt;
                static_cast&amp;lt;int32_t&amp;gt;(mode),&lt;br /&gt;
                okYesEventArgs,&lt;br /&gt;
                noEventArgs&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        MessageBoxDialogOwner* ShowOK(const wchar_t* message) {&lt;br /&gt;
            return Show(message, MessageBox_OK);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        MessageBoxDialogOwner* ShowTimedOK(const wchar_t* message) {&lt;br /&gt;
            return Show(message, MessageBox_TimedOK);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        MessageBoxDialogOwner* ShowYesNo(&lt;br /&gt;
            const wchar_t* message,&lt;br /&gt;
            int32_t okYesEventType = 0,&lt;br /&gt;
            int32_t okYesEventData = 0,&lt;br /&gt;
            int32_t okYesEventParam = 0,&lt;br /&gt;
            int32_t noEventType = 0,&lt;br /&gt;
            int32_t noEventData = 0,&lt;br /&gt;
            int32_t noEventParam = 0&lt;br /&gt;
        ) {&lt;br /&gt;
            const int32_t okYesArgs[3] = {&lt;br /&gt;
                okYesEventType,&lt;br /&gt;
                okYesEventData,&lt;br /&gt;
                okYesEventParam&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            const int32_t noArgs[3] = {&lt;br /&gt;
                noEventType,&lt;br /&gt;
                noEventData,&lt;br /&gt;
                noEventParam&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            return Show(message, MessageBox_YesNo, okYesArgs, noArgs);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        MessageBoxDialogOwner* ShowTimedYesNo(&lt;br /&gt;
            const wchar_t* message,&lt;br /&gt;
            int32_t okYesEventType = 0,&lt;br /&gt;
            int32_t okYesEventData = 0,&lt;br /&gt;
            int32_t okYesEventParam = 0,&lt;br /&gt;
            int32_t noEventType = 0,&lt;br /&gt;
            int32_t noEventData = 0,&lt;br /&gt;
            int32_t noEventParam = 0&lt;br /&gt;
        ) {&lt;br /&gt;
            const int32_t okYesArgs[3] = {&lt;br /&gt;
                okYesEventType,&lt;br /&gt;
                okYesEventData,&lt;br /&gt;
                okYesEventParam&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            const int32_t noArgs[3] = {&lt;br /&gt;
                noEventType,&lt;br /&gt;
                noEventData,&lt;br /&gt;
                noEventParam&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            return Show(message, MessageBox_TimedYesNo, okYesArgs, noArgs);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(MessageBoxDialogOwner) == 0x21C);&lt;br /&gt;
&lt;br /&gt;
    class GameDialogStage : public StageBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL AttachOwnerAndCreateUserNotice(ScreenRoot* parent, int32_t id) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL InitStageXml() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetSelectedOrActivePopupIndex(int32_t index) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL CanLeaveStageOrPopup(int32_t reason) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ReservedF0() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnBackOrDefaultAction() = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        StageBase* builtinPopupOwners[10];&lt;br /&gt;
        uint8_t xmlLoadedFlag;&lt;br /&gt;
        uint8_t pad211[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t selectedOrActivePopupIndex;&lt;br /&gt;
        void* trayPopupOwner;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsXmlLoaded() const {&lt;br /&gt;
            return xmlLoadedFlag != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        StageBase* BuiltinPopupOwnerAt(int32_t index) const {&lt;br /&gt;
            if (index &amp;lt; 0 || index &amp;gt;= 10)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return builtinPopupOwners[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using GetOrCreateBuiltinPopupOwnerFn = StageBase * (FTSDK_THISCALL*)(StageBase* self, uint32_t popupIndex);&lt;br /&gt;
        StageBase* GetOrCreateBuiltinPopupOwner(uint32_t popupIndex) {&lt;br /&gt;
            if (popupIndex &amp;gt;= Constants::KnownBuiltinPopupSlotCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return reinterpret_cast&amp;lt;GetOrCreateBuiltinPopupOwnerFn&amp;gt;(0x00498890)(this, popupIndex);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class StageManager : public ScreenInputRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        void* stageHwnd;&lt;br /&gt;
&lt;br /&gt;
        int32_t previousStageId;&lt;br /&gt;
        int32_t pendingStageId;&lt;br /&gt;
        int32_t currentStageId;&lt;br /&gt;
        int32_t transitionStageScratchId;&lt;br /&gt;
&lt;br /&gt;
        GameDialogStage* currentStage;&lt;br /&gt;
        uint8_t messageBoxDialog[0x21C];&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;GameDialogStage*&amp;gt; stages;&lt;br /&gt;
&lt;br /&gt;
        void* currentOverlayRaw;&lt;br /&gt;
        Structs::StageOverlayQuad overlayQuad;&lt;br /&gt;
&lt;br /&gt;
        float overlayDrawTimer;&lt;br /&gt;
&lt;br /&gt;
        Structs::StageOwnedResource stageTransitionResource;&lt;br /&gt;
&lt;br /&gt;
        int32_t frameUpdating;&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknown87C[0x10];&lt;br /&gt;
&lt;br /&gt;
        uint8_t delayedReconnectFlag;&lt;br /&gt;
        uint8_t pad88D[3];&lt;br /&gt;
&lt;br /&gt;
        float periodicActionTimer;&lt;br /&gt;
&lt;br /&gt;
        uint8_t hourlyPacketReadyFlag;&lt;br /&gt;
        uint8_t pad895[3];&lt;br /&gt;
&lt;br /&gt;
        float hourlyPacketTimer;&lt;br /&gt;
        int32_t unknown89C;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        MessageBoxDialogOwner* MessageBoxDialog() {&lt;br /&gt;
            return reinterpret_cast&amp;lt;MessageBoxDialogOwner*&amp;gt;(messageBoxDialog);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const MessageBoxDialogOwner* MessageBoxDialog() const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;const MessageBoxDialogOwner*&amp;gt;(messageBoxDialog);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        GameDialogStage* CurrentStage() const {&lt;br /&gt;
            if (currentStage)&lt;br /&gt;
                return currentStage;&lt;br /&gt;
&lt;br /&gt;
            if (currentStageId &amp;lt; 0 || currentStageId &amp;gt;= stages.Size())&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return stages[currentStageId];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsValidStageIndex(uint32_t index) const {&lt;br /&gt;
            return index &amp;lt; Constants::StageOwnerCount;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsFrameUpdating() const {&lt;br /&gt;
            return frameUpdating != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool HasOverlay() const {&lt;br /&gt;
            return currentOverlayRaw != nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsHourlyPacketReady() const {&lt;br /&gt;
            return hourlyPacketReadyFlag != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using RequestStateFn = void(FTSDK_THISCALL*)(StageManager* self, uint32_t state);&lt;br /&gt;
        void RequestState(uint32_t state) {&lt;br /&gt;
            if (!IsValidStageIndex(state))&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            reinterpret_cast&amp;lt;RequestStateFn&amp;gt;(0x004AE9A0)(this, state);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using SwitchStateNowFn = void(FTSDK_THISCALL*)(StageManager* self, uint32_t state);&lt;br /&gt;
        void SwitchStateNow(uint32_t state) {&lt;br /&gt;
            if (!IsValidStageIndex(state))&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            reinterpret_cast&amp;lt;SwitchStateNowFn&amp;gt;(0x004AD7D0)(this, state);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using InitStagesFn = bool(FTSDK_THISCALL*)(StageManager* self, void* hwnd, int startupParam);&lt;br /&gt;
        bool InitStages(void* hwnd, int startupParam) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;InitStagesFn&amp;gt;(0x004B0590)(this, hwnd, startupParam);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using ShowMessageBoxFn = void(FTSDK_THISCALL*)(StageManager* self, const wchar_t* message, int32_t mode, int32_t okYesEventType, int32_t okYesEventData, int32_t okYesEventParam, uint8_t playSound);&lt;br /&gt;
        void ShowMessageBox(&lt;br /&gt;
            const wchar_t* message,&lt;br /&gt;
            MessageBoxMode mode = MessageBox_OK,&lt;br /&gt;
            int32_t okYesEventType = 0,&lt;br /&gt;
            int32_t okYesEventData = 0,&lt;br /&gt;
            int32_t okYesEventParam = 0,&lt;br /&gt;
            bool playSound = true&lt;br /&gt;
        ) {&lt;br /&gt;
            reinterpret_cast&amp;lt;ShowMessageBoxFn&amp;gt;(0x004AE9C0)(&lt;br /&gt;
                this,&lt;br /&gt;
                message,&lt;br /&gt;
                static_cast&amp;lt;int32_t&amp;gt;(mode),&lt;br /&gt;
                okYesEventType,&lt;br /&gt;
                okYesEventData,&lt;br /&gt;
                okYesEventParam,&lt;br /&gt;
                playSound ? 1 : 0&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ShowOKMessage(const wchar_t* message, bool playSound = true) {&lt;br /&gt;
            ShowMessageBox(message, MessageBox_OK, 0, 0, 0, playSound);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ShowTimedOKMessage(const wchar_t* message, bool playSound = true) {&lt;br /&gt;
            ShowMessageBox(message, MessageBox_TimedOK, 0, 0, 0, playSound);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ShowYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) {&lt;br /&gt;
            ShowMessageBox(message, MessageBox_YesNo, okYesEventType, okYesEventData, okYesEventParam, playSound);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ShowTimedYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) {&lt;br /&gt;
            ShowMessageBox(message, MessageBox_TimedYesNo, okYesEventType, okYesEventData, okYesEventParam, playSound);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(StageManager) == 0x8A0);&lt;br /&gt;
&lt;br /&gt;
    using GetStageManagerFn = StageManager * (FTSDK_CDECL*)();&lt;br /&gt;
    inline StageManager* GetStageManager() {&lt;br /&gt;
        return reinterpret_cast&amp;lt;GetStageManagerFn&amp;gt;(0x004B0520)();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    using ConstructGuiOwnerBaseFn = StageBase * (FTSDK_THISCALL*)(StageBase* self);&lt;br /&gt;
    inline StageBase* ConstructStageBase(StageBase* memory) {&lt;br /&gt;
        return reinterpret_cast&amp;lt;ConstructGuiOwnerBaseFn&amp;gt;(0x005AEC40)(memory);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    using ConstructGameDialogStageFn = GameDialogStage * (FTSDK_THISCALL*)(GameDialogStage* self);&lt;br /&gt;
    inline GameDialogStage* ConstructGameDialogStage(GameDialogStage* memory) {&lt;br /&gt;
        return reinterpret_cast&amp;lt;ConstructGameDialogStageFn&amp;gt;(0x004987C0)(memory);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    using SetStaticTextFn = int(FTSDK_THISCALL*)(AduGuiStatic* control, const wchar_t* text);&lt;br /&gt;
    inline int SetStaticText(AduGuiStatic* control, const wchar_t* text) {&lt;br /&gt;
        if (!control)&lt;br /&gt;
			return 0;&lt;br /&gt;
        &lt;br /&gt;
		return reinterpret_cast&amp;lt;SetStaticTextFn&amp;gt;(0x006185E0)(control, text ? text : L&amp;quot;&amp;quot;);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
    namespace GuiCustomControlRegistry {&lt;br /&gt;
        constexpr uintptr_t RegisterFactoryAddr = 0x006113F0;&lt;br /&gt;
        constexpr uintptr_t RegisterTypeNameAddr = 0x00617E20;&lt;br /&gt;
        constexpr uintptr_t DefaultFactoryAddr = 0x005AC950;&lt;br /&gt;
        constexpr uintptr_t RegisterBuiltinCustomTypesAddr = 0x005AE050;&lt;br /&gt;
&lt;br /&gt;
        using RegisterFactoryFn = CreateCustomGuiControlFn(FTSDK_CDECL*)(CreateCustomGuiControlFn factory);&lt;br /&gt;
        using RegisterTypeNameFn = void (FTSDK_CDECL*)(const char* typeName, int32_t customTypeId);&lt;br /&gt;
        using RegisterBuiltinCustomTypesFn = int (FTSDK_CDECL*)();&lt;br /&gt;
&lt;br /&gt;
        inline CreateCustomGuiControlFn RegisterFactory(CreateCustomGuiControlFn factory) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;RegisterFactoryFn&amp;gt;(RegisterFactoryAddr)(factory);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        inline void RegisterTypeName(const char* typeName, int32_t customTypeId) {&lt;br /&gt;
            reinterpret_cast&amp;lt;RegisterTypeNameFn&amp;gt;(RegisterTypeNameAddr)(typeName, customTypeId);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        inline int RegisterBuiltinCustomTypes() {&lt;br /&gt;
            return reinterpret_cast&amp;lt;RegisterBuiltinCustomTypesFn&amp;gt;(RegisterBuiltinCustomTypesAddr)();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        inline AduGuiControl* CreateByDefaultFactory(AduGuiDialog* dialog, int32_t customTypeId) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;CreateCustomGuiControlFn&amp;gt;(DefaultFactoryAddr)(dialog, customTypeId);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Example Usage ==&lt;br /&gt;
* [[Custom Client UI with StageManager Input|Custom client UI with StageManager input forwarding]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Client]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=FT_SDK&amp;diff=305</id>
		<title>FT SDK</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=FT_SDK&amp;diff=305"/>
		<updated>2026-05-18T00:19:57Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Overview ==&lt;br /&gt;
This page documents the current reverse engineering progress for the Fantasy Tennis client-side SDK used by JFTSE tooling. The SDK maps known client structures, virtual functions, GUI objects, stage management, XML dialog loading, popup ownership and input routing into C++ wrappers that can be used from injected debugging or extension code.&lt;br /&gt;
&lt;br /&gt;
The current focus is the client GUI layer around &amp;lt;code&amp;gt;StageManager&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ScreenRoot&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt; and Adu GUI controls. Confirmed areas include the fixed 24-stage model, current-stage lookup, parent-chain/process/open-state behavior, built-in popup slot creation, XML dialog loading, command-ID binding, callback dispatch and Adu object state handling.&lt;br /&gt;
&lt;br /&gt;
This page is the current reverse engineering handoff for the SDK. Names, offsets and layouts are updated only after they are verified through disassembly, runtime dumps and real client call sites.&lt;br /&gt;
&lt;br /&gt;
== Scope ==&lt;br /&gt;
* &amp;lt;code&amp;gt;StageManager&amp;lt;/code&amp;gt; state tracking, stage switching and current-stage access.&lt;br /&gt;
* Fixed 24-stage owner list and safe current-stage attachment.&lt;br /&gt;
* &amp;lt;code&amp;gt;ScreenRoot&amp;lt;/code&amp;gt; parent/child ownership, input routing, open state and processing state.&lt;br /&gt;
* &amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt; XML dialog ownership, child-owner lists, priority input child handling and GUI event dispatch.&lt;br /&gt;
* &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt; built-in popup slots, popup factory behavior, selected/active popup index and tray-popup related state.&lt;br /&gt;
* Game-dialog lifecycle hooks, including XML initialization, activation, update, back/default action and popup close paths.&lt;br /&gt;
* Adu GUI object hierarchy, including active/update/visible flags, timers, child traversal and recursive processing.&lt;br /&gt;
* Adu dialog/control layouts, including object arrays, command IDs, control names, control types, state sets and basic runtime flags.&lt;br /&gt;
* XML GUI binding through &amp;lt;code&amp;gt;GuiBind&amp;lt;/code&amp;gt; arrays, command IDs and control type IDs.&lt;br /&gt;
* Default GUI callback routing through &amp;lt;code&amp;gt;AduGuiDialog&amp;lt;/code&amp;gt; callback/user-data fields.&lt;br /&gt;
* Confirmed event dispatch for buttons, radio buttons, edit boxes, context menus and related GUI events.&lt;br /&gt;
* Confirmed edit-box text access through the wide-character text pointer at the current &amp;lt;code&amp;gt;AduGuiEditBox&amp;lt;/code&amp;gt; layout.&lt;br /&gt;
* Group-style control activation behavior used by ranking and popup XML, especially active/visible state synchronization.&lt;br /&gt;
&lt;br /&gt;
== Stability Notes ==&lt;br /&gt;
Offsets and function addresses are version specific to the currently analyzed Fantasy Tennis client build. Names describe current understanding, not original source names. Any field marked unknown, maybe or conservative should be treated as subject to change until confirmed by call sites, runtime tests or additional disassembly.&lt;br /&gt;
&lt;br /&gt;
Important caveat: custom GUI work should not add another stage. The client uses a fixed set of 24 stage owners. Custom UI should attach to the current stage owner, an existing popup path or the embedded modal/input owner where appropriate.&lt;br /&gt;
&lt;br /&gt;
== Current SDK Header ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
#pragma once&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;cstdint&amp;gt;&lt;br /&gt;
#include &amp;lt;cstddef&amp;gt;&lt;br /&gt;
#include &amp;lt;cstdio&amp;gt;&lt;br /&gt;
#include &amp;lt;cstring&amp;gt;&lt;br /&gt;
#include &amp;lt;cwchar&amp;gt;&lt;br /&gt;
#include &amp;lt;string&amp;gt;&lt;br /&gt;
#include &amp;lt;type_traits&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define FTSDK_THISCALL __thiscall&lt;br /&gt;
#define FTSDK_CDECL    __cdecl&lt;br /&gt;
#define FTSDK_STDCALL  __stdcall&lt;br /&gt;
#define FTSDK_FASTCALL __fastcall&lt;br /&gt;
&lt;br /&gt;
namespace FTSDK {&lt;br /&gt;
&lt;br /&gt;
    namespace Structs {&lt;br /&gt;
        struct FTStringA {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
&lt;br /&gt;
            union {&lt;br /&gt;
                char inlineBuffer[16];&lt;br /&gt;
                char* heapPtr;&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            uint32_t length;&lt;br /&gt;
            uint32_t capacity;&lt;br /&gt;
&lt;br /&gt;
            bool IsInline() const {&lt;br /&gt;
                return capacity &amp;lt; 16;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool IsValid() const {&lt;br /&gt;
                if (length &amp;gt; capacity || length &amp;gt; 4096)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (capacity &amp;gt; 0xFFFFFFFEu)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (!IsInline() &amp;amp;&amp;amp; !heapPtr)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                return true;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const char* Data() const {&lt;br /&gt;
                if (!IsValid())&lt;br /&gt;
                    return &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
                return IsInline() ? inlineBuffer : heapPtr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            int32_t Length() const {&lt;br /&gt;
                return IsValid() ? static_cast&amp;lt;int32_t&amp;gt;(length) : 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool Equals(const char* text) const {&lt;br /&gt;
                if (!text || !IsValid())&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                const size_t textLen = std::strlen(text);&lt;br /&gt;
                return textLen == length &amp;amp;&amp;amp; std::memcmp(Data(), text, length) == 0;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct FTStringW {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
&lt;br /&gt;
            union {&lt;br /&gt;
                wchar_t inlineBuffer[8];&lt;br /&gt;
                wchar_t* heapPtr;&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            uint32_t length;&lt;br /&gt;
            uint32_t capacity;&lt;br /&gt;
&lt;br /&gt;
            bool IsInline() const {&lt;br /&gt;
                return capacity &amp;lt; 8;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool IsValid() const {&lt;br /&gt;
                if (length &amp;gt; capacity || length &amp;gt; 4096)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (capacity &amp;gt; 0x7FFFFFFEu)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (!IsInline() &amp;amp;&amp;amp; !heapPtr)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                return true;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const wchar_t* Data() const {&lt;br /&gt;
                if (!IsValid())&lt;br /&gt;
                    return L&amp;quot;&amp;quot;;&lt;br /&gt;
                return IsInline() ? inlineBuffer : heapPtr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            int32_t Length() const {&lt;br /&gt;
                return IsValid() ? static_cast&amp;lt;int32_t&amp;gt;(length) : 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool Equals(const wchar_t* text) const {&lt;br /&gt;
                if (!text || !IsValid())&lt;br /&gt;
                    return false;&lt;br /&gt;
                const size_t textLen = std::wcslen(text);&lt;br /&gt;
                return textLen == length &amp;amp;&amp;amp; std::memcmp(Data(), text, length * sizeof(wchar_t)) == 0;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        struct FTVector {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
            T* first;&lt;br /&gt;
            T* last;&lt;br /&gt;
            T* end;&lt;br /&gt;
&lt;br /&gt;
            int32_t Size() const {&lt;br /&gt;
                if (!first || !last || last &amp;lt; first)&lt;br /&gt;
                    return 0;&lt;br /&gt;
&lt;br /&gt;
                return static_cast&amp;lt;int32_t&amp;gt;(last - first);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            int32_t Capacity() const {&lt;br /&gt;
                if (!first || !end || end &amp;lt; first)&lt;br /&gt;
                    return 0;&lt;br /&gt;
&lt;br /&gt;
                return static_cast&amp;lt;int32_t&amp;gt;(end - first);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool IsEmpty() const {&lt;br /&gt;
                return Size() == 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            T&amp;amp; operator[](size_t index) {&lt;br /&gt;
                return first[index];&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const T&amp;amp; operator[](size_t index) const {&lt;br /&gt;
                return first[index];&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            T* Begin() {&lt;br /&gt;
                return first;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            T* End() {&lt;br /&gt;
                return last;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const T* Begin() const {&lt;br /&gt;
                return first;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const T* End() const {&lt;br /&gt;
                return last;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        struct FTList {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
            T* next;&lt;br /&gt;
            uint32_t count;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Key, typename Value&amp;gt;&lt;br /&gt;
        struct FTTreeNode {&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* left;&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* parent;&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* right;&lt;br /&gt;
&lt;br /&gt;
            Key key;&lt;br /&gt;
            Value value;&lt;br /&gt;
&lt;br /&gt;
            uint8_t rbColor;&lt;br /&gt;
            uint8_t isNil;&lt;br /&gt;
            uint8_t pad16[2];&lt;br /&gt;
&lt;br /&gt;
            bool IsNil() const { return isNil != 0; }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Key, typename Value&amp;gt;&lt;br /&gt;
        struct FTTreeMap {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* head;&lt;br /&gt;
            uint32_t count;&lt;br /&gt;
&lt;br /&gt;
            bool IsEmpty() const { return count == 0; }&lt;br /&gt;
            uint32_t Size() const { return count; }&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* Head() const { return head; }&lt;br /&gt;
&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* Root() const {&lt;br /&gt;
                if (!head || head-&amp;gt;IsNil())&lt;br /&gt;
                    return nullptr;&lt;br /&gt;
&lt;br /&gt;
                auto* root = head-&amp;gt;parent;&lt;br /&gt;
                return root &amp;amp;&amp;amp; !root-&amp;gt;IsNil() ? root : nullptr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            FTTreeNode&amp;lt;Key, Value&amp;gt;* FindNode(const Key&amp;amp; key) const {&lt;br /&gt;
                auto* node = Root();&lt;br /&gt;
&lt;br /&gt;
                while (node) {&lt;br /&gt;
                    if (node-&amp;gt;IsNil())&lt;br /&gt;
                        return nullptr;&lt;br /&gt;
&lt;br /&gt;
                    if (key &amp;lt; node-&amp;gt;key) {&lt;br /&gt;
                        node = node-&amp;gt;left;&lt;br /&gt;
                    }&lt;br /&gt;
                    else if (node-&amp;gt;key &amp;lt; key) {&lt;br /&gt;
                        node = node-&amp;gt;right;&lt;br /&gt;
                    }&lt;br /&gt;
                    else {&lt;br /&gt;
                        return node;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                return nullptr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            Value Find(const Key&amp;amp; key) const {&lt;br /&gt;
                auto* node = FindNode(key);&lt;br /&gt;
                return node ? node-&amp;gt;value : Value{};&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool Contains(const Key&amp;amp; key) const { return FindNode(key) != nullptr; }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct GuiBind {&lt;br /&gt;
            int32_t commandId;&lt;br /&gt;
            const char* controlName;&lt;br /&gt;
            int32_t controlTypeId;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct Rect {&lt;br /&gt;
            int32_t left;&lt;br /&gt;
            int32_t top;&lt;br /&gt;
            int32_t right;&lt;br /&gt;
            int32_t bottom;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct GuiRectStorage {&lt;br /&gt;
            void* vtable;&lt;br /&gt;
            int32_t left;&lt;br /&gt;
            int32_t top;&lt;br /&gt;
            int32_t right;&lt;br /&gt;
            int32_t bottom;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        struct StageOwnedResource {&lt;br /&gt;
            int32_t activeOrTransitionFlag;&lt;br /&gt;
            int32_t enabled;&lt;br /&gt;
            float baseOrDuration;&lt;br /&gt;
            float progressOrAlpha;&lt;br /&gt;
            float colorR_orParam10;&lt;br /&gt;
            float colorG_orParam14;&lt;br /&gt;
            float colorB_orParam18;&lt;br /&gt;
            void* resourceHandle;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct StageOverlayVertex {&lt;br /&gt;
            float x;&lt;br /&gt;
            float y;&lt;br /&gt;
            float z;&lt;br /&gt;
            float rhw;&lt;br /&gt;
            uint32_t color;&lt;br /&gt;
            float u;&lt;br /&gt;
            float v;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct StageOverlayQuad {&lt;br /&gt;
            StageOverlayVertex vertices[4];&lt;br /&gt;
&lt;br /&gt;
            int32_t unknown70;&lt;br /&gt;
            int32_t unknown74;&lt;br /&gt;
            int32_t widthInt;&lt;br /&gt;
            int32_t heightInt;&lt;br /&gt;
&lt;br /&gt;
            float x;&lt;br /&gt;
            float y;&lt;br /&gt;
            float width;&lt;br /&gt;
            float height;&lt;br /&gt;
&lt;br /&gt;
            float scaleX;&lt;br /&gt;
            float alpha;&lt;br /&gt;
            float rotationOrAngle;&lt;br /&gt;
&lt;br /&gt;
            uint8_t unknown9C;&lt;br /&gt;
            uint8_t unknown9D;&lt;br /&gt;
            uint8_t visibleOrEnabled;&lt;br /&gt;
            uint8_t pad9F;&lt;br /&gt;
&lt;br /&gt;
            uint8_t unknownA0[0x0C];&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        static_assert(sizeof(Rect) == 0x10);&lt;br /&gt;
        static_assert(sizeof(GuiRectStorage) == 0x14);&lt;br /&gt;
        static_assert(sizeof(StageOwnedResource) == 0x20);&lt;br /&gt;
        static_assert(sizeof(FTStringA) == 0x1C);&lt;br /&gt;
        static_assert(sizeof(FTStringW) == 0x1C);&lt;br /&gt;
        static_assert(sizeof(GuiBind) == 0x0C);&lt;br /&gt;
        static_assert(sizeof(FTVector&amp;lt;void*&amp;gt;) == 0x10);&lt;br /&gt;
        static_assert(sizeof(StageOverlayQuad) == 0xAC);&lt;br /&gt;
        static_assert(sizeof(StageOverlayVertex) == 0x1C);&lt;br /&gt;
        static_assert(sizeof(FTList&amp;lt;void*&amp;gt;) == 0x0C);&lt;br /&gt;
        static_assert(sizeof(FTTreeNode&amp;lt;int32_t, void*&amp;gt;) == 0x18);&lt;br /&gt;
        static_assert(sizeof(FTTreeMap&amp;lt;int32_t, void*&amp;gt;) == 0x0C);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    enum GuiEventType : int32_t {&lt;br /&gt;
        ButtonClick = 0,&lt;br /&gt;
        ButtonOver = 1,&lt;br /&gt;
&lt;br /&gt;
        ComboBoxOpen = 2,&lt;br /&gt;
        ComboBoxClose = 3,&lt;br /&gt;
        ComboBoxSelectChange = 4,&lt;br /&gt;
&lt;br /&gt;
        RadioButtonChange = 5,&lt;br /&gt;
        CheckBoxChange = 6,&lt;br /&gt;
&lt;br /&gt;
        SliderValueChange = 7,&lt;br /&gt;
&lt;br /&gt;
        EditBoxEnter = 8,&lt;br /&gt;
        EditBoxChange = 9,&lt;br /&gt;
        EditBoxTAB = 10,&lt;br /&gt;
        EditBoxESC = 11,&lt;br /&gt;
        EditBoxCharLimit = 12,&lt;br /&gt;
        EditBoxKeyUp = 13,&lt;br /&gt;
        EditBoxKeyDown = 14,&lt;br /&gt;
&lt;br /&gt;
        ListBoxDBClick = 15,&lt;br /&gt;
        ListBoxSelect = 16,&lt;br /&gt;
        ListBoxSelectEnd = 17,&lt;br /&gt;
&lt;br /&gt;
        ContextMenuClick = 18,&lt;br /&gt;
        ScrollBarPosChange = 19&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum GuiControlType : int32_t {&lt;br /&gt;
        Static = 0,&lt;br /&gt;
        Button = 1,&lt;br /&gt;
        CheckBox = 2,&lt;br /&gt;
        RadioButton = 3,&lt;br /&gt;
        ComboBox = 4,&lt;br /&gt;
        Slider = 5,&lt;br /&gt;
        Gauge = 6,&lt;br /&gt;
        EditBox = 7,&lt;br /&gt;
        IMEEditBox = 8,&lt;br /&gt;
        ListBox = 9,&lt;br /&gt;
        ScrollBar = 10,&lt;br /&gt;
        ContextMenu = 11&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum MessageBoxMode : int32_t {&lt;br /&gt;
        MessageBox_OK = 1,&lt;br /&gt;
        MessageBox_TimedOK = 2,&lt;br /&gt;
        MessageBox_YesNo = 3,&lt;br /&gt;
        MessageBox_TimedYesNo = 4&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    using GuiCallbackFn = void(FTSDK_STDCALL*)(&lt;br /&gt;
        GuiEventType eventType,&lt;br /&gt;
        int commandId,&lt;br /&gt;
        int param,&lt;br /&gt;
        void* userData&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiStateSet;&lt;br /&gt;
    class AduGuiDialog;&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiVisualStateInfo {&lt;br /&gt;
        int32_t kind;&lt;br /&gt;
        int32_t field04;&lt;br /&gt;
        int32_t field08;&lt;br /&gt;
        int32_t field0C;&lt;br /&gt;
        float timing;&lt;br /&gt;
        int32_t field14;&lt;br /&gt;
        int32_t field18;&lt;br /&gt;
        int32_t field1C;&lt;br /&gt;
        int32_t alternateStateIndex;&lt;br /&gt;
        int32_t field24;&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiVisualStateInfo) == 0x28);&lt;br /&gt;
&lt;br /&gt;
    // Dialog side parsed XML/control definition. This is not AduGuiControl storage.&lt;br /&gt;
    // It is initialized by sub_624F90, populated by sub_614F60, then passed into&lt;br /&gt;
    // AduGuiControl::LoadFromXml / concrete LoadFromXml overrides.&lt;br /&gt;
    struct AduGuiParsedControlDef {&lt;br /&gt;
        uint32_t unknown00;&lt;br /&gt;
        uint32_t stateBegin; // +0x04, parsed state array begin/pointer-like&lt;br /&gt;
        uint32_t stateEnd; // +0x08, parsed state array end/pointer-like&lt;br /&gt;
        uint32_t unknown0C; // +0x0C&lt;br /&gt;
        int32_t eventField[20]; // +0x10, initialized to -1; indexed by GuiEventType&lt;br /&gt;
        uint8_t unknown60[0x804]; // +0x60...+0x863&lt;br /&gt;
        wchar_t resolvedText[256]; // +0x864, assigned to AduGuiControl::text&lt;br /&gt;
        uint8_t unknownA64[0x600]; // +0xA64...+0x1063&lt;br /&gt;
        uint8_t enable; // +0x1064, XML Enable, default 1&lt;br /&gt;
        uint8_t enabledState; // +0x1065, XML EnabledState, default 1&lt;br /&gt;
        uint8_t pad1066[2]; // +0x1066&lt;br /&gt;
        int32_t x;&lt;br /&gt;
        int32_t y;&lt;br /&gt;
        int32_t width;&lt;br /&gt;
        int32_t height;&lt;br /&gt;
        int32_t alignX;&lt;br /&gt;
        int32_t alignY;&lt;br /&gt;
        float scaleX;&lt;br /&gt;
        float scaleY;&lt;br /&gt;
        float rotate;&lt;br /&gt;
        float centerX;&lt;br /&gt;
        float centerY;&lt;br /&gt;
        int32_t flipX;&lt;br /&gt;
        int32_t textAlignX;&lt;br /&gt;
        int32_t textAlignY;&lt;br /&gt;
        int32_t textFx;&lt;br /&gt;
        int32_t textMarginLeft;&lt;br /&gt;
        int32_t textMarginTop;&lt;br /&gt;
        int32_t textMarginRight;&lt;br /&gt;
        int32_t textMarginBottom;&lt;br /&gt;
        int32_t wordWrap;&lt;br /&gt;
        int32_t groupId;&lt;br /&gt;
        int32_t subGroupId;&lt;br /&gt;
        uint8_t unknown10C0[0x24];&lt;br /&gt;
        int32_t tabStop; // +0x10E4&lt;br /&gt;
        int32_t initialStage;&lt;br /&gt;
        int32_t bindStage;&lt;br /&gt;
        int32_t hotKey;&lt;br /&gt;
        char contextMenuName[256]; // +0x10F4&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    namespace Constants {&lt;br /&gt;
        // Confirmed by StageManager InitStages / SwitchStateNow bounds.&lt;br /&gt;
        // Do not add a custom 25th stage; attach custom UI to an existing owner/modal&lt;br /&gt;
        constexpr int32_t StageOwnerCount = 24;&lt;br /&gt;
        // These are built-in popup owner slots at FTGameDialogOwner + 0x1E8&lt;br /&gt;
        // Not proof that every stage uses all 10 slots&lt;br /&gt;
        constexpr int32_t KnownBuiltinPopupSlotCount = 10;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    class AduBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual AduBase* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t unk04;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool Unk04Enabled() const {&lt;br /&gt;
            return unk04 != 0;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduBase) == 0x08);&lt;br /&gt;
&lt;br /&gt;
    class AduGameObj : public AduBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetDirtyValue(int32_t value) = 0;&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL ResetTimerRecursive() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetActive(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetUpdateBlocked(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetVisible(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL UpdateRecursive(float dt) = 0;&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL ProcessVisibleRecursive() = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t dirtyValue; // +0x08&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* firstChild;&lt;br /&gt;
        AduGameObj* nextSibling;&lt;br /&gt;
&lt;br /&gt;
        uint8_t dirtyFlag;&lt;br /&gt;
        uint8_t active;&lt;br /&gt;
        uint8_t updateBlocked;&lt;br /&gt;
        uint8_t visible;&lt;br /&gt;
&lt;br /&gt;
        float elapsedTime;&lt;br /&gt;
        float deltaTime; // +0x1C&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsDirty() const { return dirtyFlag != 0; }&lt;br /&gt;
        bool IsActive() const { return active != 0; }&lt;br /&gt;
        bool IsUpdateBlocked() const { return updateBlocked != 0; }&lt;br /&gt;
        bool IsVisible() const { return visible != 0; }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* FirstChild() const { return firstChild; }&lt;br /&gt;
        AduGameObj* NextSibling() const { return nextSibling; }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Callback&amp;gt;&lt;br /&gt;
        void ForEachChild(Callback&amp;amp;&amp;amp; callback) {&lt;br /&gt;
            for (AduGameObj* child = firstChild; child; child = child-&amp;gt;nextSibling)&lt;br /&gt;
                callback(child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Callback&amp;gt;&lt;br /&gt;
        void ForEachChild(Callback&amp;amp;&amp;amp; callback) const {&lt;br /&gt;
            for (AduGameObj* child = firstChild; child; child = child-&amp;gt;nextSibling)&lt;br /&gt;
                callback(child);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGameObj) == 0x20);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiControl : public AduGameObj {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual bool FTSDK_THISCALL RecalculateRect() = 0; // vt+0x20&lt;br /&gt;
        virtual bool FTSDK_THISCALL LoadFromXml(const char* name, void* xmlParser, AduGuiParsedControlDef* parsedControlDef) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL PostLoadResolve() = 0;&lt;br /&gt;
        virtual AduGameObj* FTSDK_THISCALL GetInnerObject() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_THISCALL ControlVfunc30() = 0; // vt+0x30&lt;br /&gt;
        virtual void FTSDK_THISCALL ControlVfunc34() = 0; // vt+0x34&lt;br /&gt;
        virtual void FTSDK_THISCALL ControlVfunc38() = 0; // vt+0x38&lt;br /&gt;
        virtual void FTSDK_THISCALL ControlVfunc3C() = 0; // vt+0x3C&lt;br /&gt;
        virtual void FTSDK_THISCALL ControlVfunc40() = 0; // vt+0x40&lt;br /&gt;
        virtual void FTSDK_THISCALL ControlVfunc44() = 0; // vt+0x44&lt;br /&gt;
        virtual void FTSDK_THISCALL ControlVfunc48() = 0; // vt+0x48&lt;br /&gt;
        virtual void FTSDK_THISCALL ControlVfunc4C() = 0; // vt+0x4C&lt;br /&gt;
        virtual void FTSDK_THISCALL ControlVfunc50() = 0; // vt+0x50&lt;br /&gt;
        virtual void FTSDK_THISCALL ControlVfunc54() = 0; // vt+0x54&lt;br /&gt;
        virtual void FTSDK_THISCALL ControlVfunc58() = 0; // vt+0x58&lt;br /&gt;
&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc5C() = 0; // vt+0x5C&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc60() = 0; // vt+0x60&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc64() = 0; // vt+0x64&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc68() = 0; // vt+0x68&lt;br /&gt;
        virtual bool FTSDK_THISCALL ControlHandleInputA(uint32_t msg, int32_t wParam, int32_t lParam) = 0; // vt+0x6C&lt;br /&gt;
        virtual bool FTSDK_THISCALL ControlHandleInputB(uint32_t msg, int32_t wParam, int32_t lParam) = 0; // vt+0x70&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc74() = 0; // vt+0x74&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc78() = 0; // vt+0x78&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc7C() = 0; // vt+0x7C&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc80() = 0; // vt+0x80&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc84() = 0; // vt+0x84&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc88() = 0; // vt+0x88&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc8C() = 0; // vt+0x8C&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc90() = 0; // vt+0x90&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc94() = 0; // vt+0x94&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfunc98() = 0; // vt+0x98&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetEnabledState(bool value) = 0; // vt+0x9C&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfuncA0() = 0; // vt+0xA0&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfuncA4() = 0; // vt+0xA4&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfuncA8() = 0; // vt+0xA8&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfuncAC() = 0; // vt+0xAC&lt;br /&gt;
        virtual AduGuiStateSet* FTSDK_THISCALL GetStateSet(int32_t stateIndex) = 0; // vt+0xB0&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfuncB4() = 0; // vt+0xB4&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfuncB8() = 0; // vt+0xB8&lt;br /&gt;
        virtual bool FTSDK_THISCALL CanAddStateSets() = 0; // vt+0xBC&lt;br /&gt;
        virtual void FTSDK_THISCALL SetStateTilingOrFlag(int32_t stateIndex, float a3, float a4, int32_t stageIndex) = 0; // vt+0xC0&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfuncC4() = 0; // vt+0xC4&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfuncC8() = 0; // vt+0xC8&lt;br /&gt;
        virtual void FTSDK_THISCALL SetStateTimingOrFx(int32_t stateIndex, int32_t enabled, float timing, int32_t alternateStateIndex) = 0; // vt+0xCC&lt;br /&gt;
        virtual void FTSDK_THISCALL SetEventField(int32_t value, int32_t eventIndex) = 0; // vt+0xD0&lt;br /&gt;
        virtual int FTSDK_THISCALL ControlVfuncD4() = 0; // vt+0xD4&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiStateSet** stateSets;&lt;br /&gt;
        int32_t stateCount;&lt;br /&gt;
        int32_t stateCapacity;&lt;br /&gt;
&lt;br /&gt;
        Structs::FTStringA name;&lt;br /&gt;
&lt;br /&gt;
        int32_t commandId;&lt;br /&gt;
        int32_t groupId;&lt;br /&gt;
        int32_t controlTypeId;&lt;br /&gt;
        int32_t hotKey;&lt;br /&gt;
        int32_t field58;&lt;br /&gt;
&lt;br /&gt;
        uint8_t enabledFlag;&lt;br /&gt;
&lt;br /&gt;
        uint8_t field5D;&lt;br /&gt;
        uint8_t pad5E[2];&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect rect;&lt;br /&gt;
&lt;br /&gt;
        int32_t currentVisualState;&lt;br /&gt;
        int32_t selectedStateIndex;&lt;br /&gt;
&lt;br /&gt;
        uint8_t bindStage;&lt;br /&gt;
        uint8_t debugFlag;&lt;br /&gt;
        uint8_t overFlag;&lt;br /&gt;
        uint8_t pressedFlag;&lt;br /&gt;
        uint8_t field7C;&lt;br /&gt;
        uint8_t pad7D[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t x;&lt;br /&gt;
        int32_t y;&lt;br /&gt;
        int32_t width;&lt;br /&gt;
        int32_t height;&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* ownerDialog;&lt;br /&gt;
        int32_t field94;&lt;br /&gt;
        int32_t controlIndex;&lt;br /&gt;
        int32_t dx;&lt;br /&gt;
        int32_t dy;&lt;br /&gt;
&lt;br /&gt;
        AduGuiVisualStateInfo visualStateInfos[7];&lt;br /&gt;
&lt;br /&gt;
        Structs::FTStringW text;&lt;br /&gt;
        AduGuiControl* resolvedContextMenuControl;&lt;br /&gt;
        Structs::FTStringA contextMenuName;&lt;br /&gt;
&lt;br /&gt;
        uint8_t field1F8;&lt;br /&gt;
        uint8_t pad1F9[3]; // +0x1F9&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiStateSet** StateSets() const { return stateSets; }&lt;br /&gt;
        int32_t StateCount() const { return stateCount; }&lt;br /&gt;
        int32_t StateCapacity() const { return stateCapacity; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::FTStringA&amp;amp; Name() const { return name; }&lt;br /&gt;
        int32_t CommandId() const { return commandId; }&lt;br /&gt;
        int32_t GroupId() const { return groupId; }&lt;br /&gt;
        int32_t ControlTypeId() const { return controlTypeId; }&lt;br /&gt;
        int32_t HotKey() const { return hotKey; }&lt;br /&gt;
&lt;br /&gt;
        bool IsType(GuiControlType type) const { return controlTypeId == static_cast&amp;lt;int32_t&amp;gt;(type); }&lt;br /&gt;
        bool IsEnabledFlag() const { return enabledFlag != 0; }&lt;br /&gt;
        bool IsBindStage() const { return bindStage != 0; }&lt;br /&gt;
        bool IsDebugFlag() const { return debugFlag != 0; }&lt;br /&gt;
        bool IsOverFlag() const { return overFlag != 0; }&lt;br /&gt;
        bool IsPressedFlag() const { return pressedFlag != 0; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; Rect() const { return rect; }&lt;br /&gt;
        Structs::Rect&amp;amp; Rect() { return rect; }&lt;br /&gt;
&lt;br /&gt;
        int32_t CurrentVisualState() const { return currentVisualState; }&lt;br /&gt;
        int32_t SelectedStateIndex() const { return selectedStateIndex; }&lt;br /&gt;
&lt;br /&gt;
        int32_t X() const { return x; }&lt;br /&gt;
        int32_t Y() const { return y; }&lt;br /&gt;
        int32_t Width() const { return width; }&lt;br /&gt;
        int32_t Height() const { return height; }&lt;br /&gt;
        int32_t Right() const { return x + width; }&lt;br /&gt;
        int32_t Bottom() const { return y + height; }&lt;br /&gt;
&lt;br /&gt;
        int32_t Dx() const { return dx; }&lt;br /&gt;
        int32_t Dy() const { return dy; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* OwnerDialog() const { return ownerDialog; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::FTStringW&amp;amp; Text() const { return text; }&lt;br /&gt;
        const Structs::FTStringA&amp;amp; ContextMenuName() const { return contextMenuName; }&lt;br /&gt;
        AduGuiControl* ResolvedContextMenuControl() const { return resolvedContextMenuControl; }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        T* InnerObjectAs() { return reinterpret_cast&amp;lt;T*&amp;gt;(GetInnerObject()); }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        const T* InnerObjectAs() const { return reinterpret_cast&amp;lt;const T*&amp;gt;(const_cast&amp;lt;AduGuiControl*&amp;gt;(this)-&amp;gt;GetInnerObject()); }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static_assert(sizeof(AduGuiControl) == 0x1FC);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiStatic : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual int FTSDK_THISCALL RenderVisuals(void* renderCtx, int flags, const Structs::Rect* rect) = 0; // vt+0xD8&lt;br /&gt;
        virtual bool FTSDK_THISCALL RenderText(int visualIndex) = 0; // vt+0xDC&lt;br /&gt;
        virtual void FTSDK_THISCALL RenderState(int stateIndex) = 0; // vt+0xE0&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        Structs::FTStringW sourceTextOrTextId;&lt;br /&gt;
        Structs::FTStringW resolvedDisplayText;&lt;br /&gt;
&lt;br /&gt;
        uint8_t textDirtyFlag;&lt;br /&gt;
        uint8_t resolveTextFlag;&lt;br /&gt;
        uint8_t pad236[2];&lt;br /&gt;
&lt;br /&gt;
        int32_t field238;&lt;br /&gt;
        int32_t textFx;&lt;br /&gt;
&lt;br /&gt;
        Structs::Rect textMargin;&lt;br /&gt;
&lt;br /&gt;
        int32_t textScrollOffsetX;&lt;br /&gt;
        uint32_t field254;&lt;br /&gt;
        uint8_t clippedTextRenderFlag;&lt;br /&gt;
        uint8_t pad259[3]; // +0x259&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        const Structs::FTStringW&amp;amp; SourceTextOrTextId() const { return sourceTextOrTextId; }&lt;br /&gt;
        const Structs::FTStringW&amp;amp; ResolvedDisplayText() const { return resolvedDisplayText; }&lt;br /&gt;
        const Structs::Rect&amp;amp; TextMargin() const { return textMargin; }&lt;br /&gt;
        bool IsTextDirty() const { return textDirtyFlag != 0; }&lt;br /&gt;
        bool UsesTextResolve() const { return resolveTextFlag != 0; }&lt;br /&gt;
        bool UsesClippedTextRender() const { return clippedTextRenderFlag != 0; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static_assert(sizeof(AduGuiStatic) == 0x25C);&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiEditTextBuffer {&lt;br /&gt;
        void (FTSDK_CDECL* callback)(int owner); // +0x00, ctor sets sub_610260&lt;br /&gt;
        void* owner;&lt;br /&gt;
        wchar_t* secondaryBuffer; // +0x08, used only when hasSecondaryBuffer is set&lt;br /&gt;
        wchar_t* text; // +0x0C, absolute AduGuiEditBox +0x218&lt;br /&gt;
        uint32_t capacity; // +0x10, absolute AduGuiEditBox +0x21C&lt;br /&gt;
        uint32_t field14;&lt;br /&gt;
        uint8_t field18;&lt;br /&gt;
        uint8_t pad19[3];&lt;br /&gt;
        uint32_t field1C;&lt;br /&gt;
        uint32_t field20;&lt;br /&gt;
        uint32_t field24;&lt;br /&gt;
        uint32_t field28;&lt;br /&gt;
        uint8_t hasSecondaryBuffer;&lt;br /&gt;
        uint8_t pad2D[3];&lt;br /&gt;
        int32_t field30;&lt;br /&gt;
        int32_t field34;&lt;br /&gt;
        int32_t field38;&lt;br /&gt;
&lt;br /&gt;
        const wchar_t* Text() const { return text ? text : L&amp;quot;&amp;quot;; }&lt;br /&gt;
        uint32_t Capacity() const { return capacity; }&lt;br /&gt;
        std::size_t Length() const { return std::wcslen(Text()); }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(AduGuiEditTextBuffer) == 0x3C);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiEditBox : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual int FTSDK_THISCALL SetEditBoxColorOrField(int32_t value) = 0; // vt+0xD8&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        uint32_t lineCacheUnknown; // +0x1FC&lt;br /&gt;
        void** lineCacheFirst;&lt;br /&gt;
        void** lineCacheLast;&lt;br /&gt;
        void** lineCacheEnd;&lt;br /&gt;
&lt;br /&gt;
        AduGuiEditTextBuffer editText; // +0x20C&lt;br /&gt;
&lt;br /&gt;
        uint8_t field248[0x20]; // +0x248&lt;br /&gt;
&lt;br /&gt;
        double caretBlinkInterval; // +0x268, GetCaretBlinkTime() * 0.001&lt;br /&gt;
        double lastCaretBlinkTime; // +0x270, performance-counter seconds&lt;br /&gt;
&lt;br /&gt;
        uint8_t field278;&lt;br /&gt;
        uint8_t pad279[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t field27C;&lt;br /&gt;
&lt;br /&gt;
        uint8_t insertMode;&lt;br /&gt;
        uint8_t pad281[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t field284;&lt;br /&gt;
        int32_t colorOrField288;&lt;br /&gt;
        int32_t field28C;&lt;br /&gt;
        int32_t colorOrField290;&lt;br /&gt;
        int32_t colorOrField294;&lt;br /&gt;
&lt;br /&gt;
        uint8_t field298;&lt;br /&gt;
        uint8_t numericOnlyFlag;&lt;br /&gt;
        uint8_t pad29A[2];&lt;br /&gt;
&lt;br /&gt;
        int32_t field29C;&lt;br /&gt;
&lt;br /&gt;
        uint8_t field2A0;&lt;br /&gt;
        uint8_t field2A1;&lt;br /&gt;
        uint8_t multilineFlag;&lt;br /&gt;
        uint8_t field2A3;&lt;br /&gt;
&lt;br /&gt;
        int32_t field2A4;&lt;br /&gt;
        int32_t field2A8;&lt;br /&gt;
        int32_t field2AC;&lt;br /&gt;
&lt;br /&gt;
        uint8_t field2B0;&lt;br /&gt;
        uint8_t pad2B1[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t field2B4;&lt;br /&gt;
        int32_t field2B8;&lt;br /&gt;
&lt;br /&gt;
        int32_t editEventFields[7]; // +0x2BC, GuiEventType 8..14&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        const wchar_t* TextW() const { return editText.Text(); }&lt;br /&gt;
        std::size_t TextLength() const { return editText.Length(); }&lt;br /&gt;
        uint32_t TextCapacity() const { return editText.Capacity(); }&lt;br /&gt;
&lt;br /&gt;
        bool IsInsertMode() const { return insertMode != 0; }&lt;br /&gt;
        bool IsNumericOnly() const { return numericOnlyFlag != 0; }&lt;br /&gt;
        bool IsMultiline() const { return multilineFlag != 0; }&lt;br /&gt;
&lt;br /&gt;
        int32_t EditEventField(GuiEventType eventType) const {&lt;br /&gt;
            const int32_t index = static_cast&amp;lt;int32_t&amp;gt;(eventType) - static_cast&amp;lt;int32_t&amp;gt;(EditBoxEnter);&lt;br /&gt;
            if (index &amp;lt; 0 || index &amp;gt;= 7)&lt;br /&gt;
                return -1;&lt;br /&gt;
            return editEventFields[index];&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static_assert(sizeof(AduGuiEditBox) == 0x2D8);&lt;br /&gt;
&lt;br /&gt;
    class AduGuiDialog : public AduGameObj {&lt;br /&gt;
    protected:&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        T ReadField(size_t offset) const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;const T*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;const uint8_t*&amp;gt;(this) + offset&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        void WriteField(size_t offset, T value) {&lt;br /&gt;
            *reinterpret_cast&amp;lt;T*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uint8_t*&amp;gt;(this) + offset&lt;br /&gt;
                ) = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        AduGameObj** ObjectArray() const {&lt;br /&gt;
            return ReadField&amp;lt;AduGameObj**&amp;gt;(0x8C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t ObjectCount() const {&lt;br /&gt;
            return ReadField&amp;lt;int32_t&amp;gt;(0x90);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        GuiCallbackFn Callback() const {&lt;br /&gt;
            return ReadField&amp;lt;GuiCallbackFn&amp;gt;(0x6C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void* CallbackUserData() const {&lt;br /&gt;
            return ReadField&amp;lt;void*&amp;gt;(0x70);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void SetCallback(GuiCallbackFn callback, void* userData) {&lt;br /&gt;
            WriteField&amp;lt;GuiCallbackFn&amp;gt;(0x6C, callback);&lt;br /&gt;
            WriteField&amp;lt;void*&amp;gt;(0x70, userData);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* ObjectAt(int32_t index) const {&lt;br /&gt;
            auto* arr = ObjectArray();&lt;br /&gt;
&lt;br /&gt;
            if (!arr || index &amp;lt; 0 || index &amp;gt;= ObjectCount())&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return arr[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* ControlAt(int32_t index) const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;AduGuiControl*&amp;gt;(ObjectAt(index));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByCommandId(int32_t commandId) const {&lt;br /&gt;
            const int32_t count = ObjectCount();&lt;br /&gt;
&lt;br /&gt;
            if (count &amp;lt; 0 || count &amp;gt; 4096)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; count; ++i) {&lt;br /&gt;
                auto* control = ControlAt(i);&lt;br /&gt;
                if (!control)&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;CommandId() == commandId)&lt;br /&gt;
                    return control;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByName(const char* name) const {&lt;br /&gt;
            if (!name)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            const int32_t count = ObjectCount();&lt;br /&gt;
&lt;br /&gt;
            if (count &amp;lt; 0 || count &amp;gt; 4096)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; count; ++i) {&lt;br /&gt;
                auto* control = ControlAt(i);&lt;br /&gt;
                if (!control)&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;Name().Equals(name))&lt;br /&gt;
                    return control;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class StageManager;&lt;br /&gt;
    class AduEngine;&lt;br /&gt;
&lt;br /&gt;
    class ScreenRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved00() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved04() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved08() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved0C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved10() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved14() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved18() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved1C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved20() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved24() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved28() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual int FTSDK_THISCALL OnFocusEnter() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnFocusLeave() = 0;&lt;br /&gt;
        virtual void* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL AttachRaw(ScreenRoot* parentOrManager, int32_t rectVtableOrTemp, int32_t left, int32_t top, int32_t right, int32_t bottom, int32_t id) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL Shutdown() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL Update() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OffsetRect(int32_t dx, int32_t dy) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL CenterRectInParent() = 0;&lt;br /&gt;
        virtual ScreenRoot* FTSDK_THISCALL ActivateProcessing() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL DeactivateProcessing() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_THISCALL OnStageCommand(int commandId) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved58() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved5C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved60() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved64() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved68() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved6C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved70() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved74() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved78() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved7C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved80() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_THISCALL SetOpenFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ClearOpenFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_STDCALL RectCleanupThunk(int32_t a1, uint8_t rectObj, int32_t a3, int32_t a4) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL HandleSystemEvent(int32_t eventType, void* data, int32_t param) = 0;&lt;br /&gt;
        virtual void FTSDK_STDCALL RectCleanupThunk2(uint8_t rectObj, int32_t a2, int32_t a3) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleInput(uint32_t msg, int32_t wParam, int32_t lParam) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t id;&lt;br /&gt;
        float field08;&lt;br /&gt;
        float field0C;&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknown10[0x80];&lt;br /&gt;
&lt;br /&gt;
        Structs::GuiRectStorage rect;&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;ScreenRoot*&amp;gt; children;&lt;br /&gt;
        ScreenRoot* parent;&lt;br /&gt;
&lt;br /&gt;
        int32_t parentChainEnabled;&lt;br /&gt;
        int32_t processEnabled;&lt;br /&gt;
        int32_t openFlag;&lt;br /&gt;
        int32_t rectOffsetEnabled;&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknownC8[0x70];&lt;br /&gt;
        char resourceName[0x80];&lt;br /&gt;
        void* lazyResource;&lt;br /&gt;
        uint8_t lazyResourceAllowed;&lt;br /&gt;
        uint8_t pad1BD[3];&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsParentChainEnabled() const { return parentChainEnabled != 0; }&lt;br /&gt;
        bool IsProcessingEnabled() const { return processEnabled != 0; }&lt;br /&gt;
        bool IsOpen() const { return openFlag != 0; }&lt;br /&gt;
        bool IsRectOffsetEnabled() const { return rectOffsetEnabled != 0; }&lt;br /&gt;
        bool HasParent() const { return parent != nullptr; }&lt;br /&gt;
        bool HasLazyResource() const { return lazyResource != nullptr; }&lt;br /&gt;
&lt;br /&gt;
        const Structs::GuiRectStorage&amp;amp; RectStorage() const { return rect; }&lt;br /&gt;
        Structs::GuiRectStorage&amp;amp; RectStorage() { return rect; }&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;ScreenRoot*&amp;gt;&amp;amp; Children() { return children; }&lt;br /&gt;
        const Structs::FTVector&amp;lt;ScreenRoot*&amp;gt;&amp;amp; Children() const { return children; }&lt;br /&gt;
&lt;br /&gt;
        int32_t Left() const { return rect.left; }&lt;br /&gt;
        int32_t Top() const { return rect.top; }&lt;br /&gt;
        int32_t Right() const { return rect.right; }&lt;br /&gt;
        int32_t Bottom() const { return rect.bottom; }&lt;br /&gt;
        int32_t Width() const { return rect.right - rect.left; }&lt;br /&gt;
        int32_t Height() const { return rect.bottom - rect.top; }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(ScreenRoot) == 0x1C0);&lt;br /&gt;
&lt;br /&gt;
    class ScreenInputRoot : public ScreenRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        void* inputHwnd;&lt;br /&gt;
        int32_t startupParamOrMode;&lt;br /&gt;
        AduEngine* engine;&lt;br /&gt;
&lt;br /&gt;
        ScreenRoot* previousFocusOwner;&lt;br /&gt;
        ScreenRoot* currentFocusOwner;&lt;br /&gt;
        ScreenRoot* previousHoverOwner;&lt;br /&gt;
        ScreenRoot* currentHoverOwner;&lt;br /&gt;
&lt;br /&gt;
        int32_t previousCursorX;&lt;br /&gt;
        int32_t previousCursorY;&lt;br /&gt;
&lt;br /&gt;
        int32_t cursorX;&lt;br /&gt;
        int32_t cursorY; // +0x1E8&lt;br /&gt;
&lt;br /&gt;
        uint8_t inputState[0x26C];&lt;br /&gt;
&lt;br /&gt;
        ScreenRoot* hoverOrCapturedOwner;&lt;br /&gt;
&lt;br /&gt;
        char resourceBasePath[0x104]; // +0x45C&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool HasCurrentFocusOwner() const { return currentFocusOwner != nullptr; }&lt;br /&gt;
&lt;br /&gt;
        bool HasCurrentHoverOwner() const { return currentHoverOwner != nullptr; }&lt;br /&gt;
&lt;br /&gt;
        int32_t CursorDeltaX() const { return cursorX - previousCursorX; }&lt;br /&gt;
&lt;br /&gt;
        int32_t CursorDeltaY() const { return cursorY - previousCursorY; }&lt;br /&gt;
&lt;br /&gt;
        using SetFocusOwnerFn = ScreenRoot * (FTSDK_THISCALL*)(ScreenInputRoot* self, ScreenRoot* owner);&lt;br /&gt;
        ScreenRoot* SetFocusOwner(ScreenRoot* owner) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;SetFocusOwnerFn&amp;gt;(0x005AAF70)(this, owner);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using DispatchInputMessageFn = bool(FTSDK_FASTCALL*)(ScreenInputRoot* self, int edx0, uint32_t msg, int32_t wParam, int32_t lParam);&lt;br /&gt;
        bool DispatchInputMessage(uint32_t msg, int32_t wParam, int32_t lParam) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;DispatchInputMessageFn&amp;gt;(0x005AB1F0)(this, 0, msg, wParam, lParam);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using UpdateInputRootFn = int(FTSDK_THISCALL*)(ScreenInputRoot* self);&lt;br /&gt;
        int UpdateInputRoot() {&lt;br /&gt;
            return reinterpret_cast&amp;lt;UpdateInputRootFn&amp;gt;(0x005AB180)(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using InitInputRootFn = char* (FTSDK_THISCALL*)(ScreenInputRoot* self, void* hwnd, AduEngine* aduEngine, int32_t startupParamOrMode,&lt;br /&gt;
            const char* resourceBasePath, int cursorResourceA, int cursorResourceB);&lt;br /&gt;
        char* InitInputRoot(void* hwndValue, AduEngine* aduEngineValue, int32_t startupParam, const char* basePath, int cursorResourceA, int cursorResourceB) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;InitInputRootFn&amp;gt;(0x005AAEA0)(this, hwndValue, aduEngineValue, startupParam, basePath, cursorResourceA, cursorResourceB);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using SetCursorModeFn = uint32_t(FTSDK_THISCALL*)(ScreenInputRoot* self, int32_t mode);&lt;br /&gt;
        uint32_t SetCursorMode(int32_t mode) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;SetCursorModeFn&amp;gt;(0x005AAD30)(this, mode);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(ScreenInputRoot) == 0x560);&lt;br /&gt;
&lt;br /&gt;
    class StageBase : public ScreenRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL AttachOwnerWithDefaultRect(ScreenRoot* parent, int32_t id) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL InitDialogWithParentOwner(StageBase* parent) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL LoadXml(const char* xmlName, const Structs::GuiBind* binds, int32_t bindCount, GuiCallbackFn callback, bool reload) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnActivate() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnDeactivate() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnUpdate() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL ProcessChildOwners(float dt) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL HandleStagePacket(void* packet) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL HandleGuiEvent(GuiEventType eventType, int commandId, int param) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnCommandAction(int commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxEnter(int commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxChange(int commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxTab(int commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxEsc(int commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxKeyUp(int commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnEditBoxKeyDown(int commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnContextMenuClick(int commandId, int selectedValue, AduGuiControl* contextMenuControl, int selectedData) = 0;&lt;br /&gt;
    public:&lt;br /&gt;
        StageBase* priorityInputChildOwner;&lt;br /&gt;
        uint8_t inputConsumedFlag;&lt;br /&gt;
        uint8_t padOrUnknown1C5[3];&lt;br /&gt;
        AduGuiDialog* xmlDialog;&lt;br /&gt;
&lt;br /&gt;
        Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt; controlBindMap;&lt;br /&gt;
        Structs::FTVector&amp;lt;StageBase*&amp;gt; processingChildOwners;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool InputConsumedFlag() const { return inputConsumedFlag != 0; }&lt;br /&gt;
        AduGuiDialog* XmlDialog() const { return xmlDialog; }&lt;br /&gt;
        bool HasXmlDialog() const { return xmlDialog != nullptr; }&lt;br /&gt;
        StageBase* PriorityInputChildOwner() const { return priorityInputChildOwner; }&lt;br /&gt;
&lt;br /&gt;
        Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt;&amp;amp; ControlBindMap() { return controlBindMap; }&lt;br /&gt;
        const Structs::FTTreeMap&amp;lt;int32_t, AduGuiControl*&amp;gt;&amp;amp; ControlBindMap() const { return controlBindMap; }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindBoundControl(int32_t commandId) const { return controlBindMap.Find(commandId); }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindBoundControl(int32_t commandId, GuiControlType expectedType) const {&lt;br /&gt;
            auto* control = FindBoundControl(commandId);&lt;br /&gt;
&lt;br /&gt;
            if (!control)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            if (control-&amp;gt;ControlTypeId() != static_cast&amp;lt;int32_t&amp;gt;(expectedType))&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return control;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool HasBoundControl(int32_t commandId) const { return controlBindMap.Contains(commandId); }&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;StageBase*&amp;gt;&amp;amp; ProcessingChildOwners() { return processingChildOwners; }&lt;br /&gt;
        const Structs::FTVector&amp;lt;StageBase*&amp;gt;&amp;amp; ProcessingChildOwners() const { return processingChildOwners; }&lt;br /&gt;
&lt;br /&gt;
        void SetPriorityInputChildOwner(StageBase* child) { priorityInputChildOwner = child; }&lt;br /&gt;
        void ClearPriorityInputChildOwnerIf(StageBase* child) { if (PriorityInputChildOwner() == child) SetPriorityInputChildOwner(nullptr); }&lt;br /&gt;
&lt;br /&gt;
        using AddChildOwnerFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);&lt;br /&gt;
        void AddChildOwner(StageBase* child) { reinterpret_cast&amp;lt;AddChildOwnerFn&amp;gt;(0x005AA720)(this, child); }&lt;br /&gt;
&lt;br /&gt;
        using BringChildOwnerToFrontFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);&lt;br /&gt;
        void BringChildOwnerToFront(StageBase* child) { reinterpret_cast&amp;lt;BringChildOwnerToFrontFn&amp;gt;(0x005AA780)(this, child); }&lt;br /&gt;
&lt;br /&gt;
        using IsOwnerReachableFn = int(FTSDK_FASTCALL*)(StageBase* self);&lt;br /&gt;
        bool IsReachableThroughParentChain() const { return reinterpret_cast&amp;lt;IsOwnerReachableFn&amp;gt;(0x005A99E0)(const_cast&amp;lt;StageBase*&amp;gt;(this)) != 0; }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* GetControlInnerControl(int32_t commandId) {&lt;br /&gt;
            auto* control = FindBoundControl(commandId);&lt;br /&gt;
            return control ? control-&amp;gt;GetInnerObject() : nullptr;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    static_assert(sizeof(StageBase) == 0x1E8);&lt;br /&gt;
&lt;br /&gt;
    class MessageBoxDialogOwner : public StageBase {&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t messageBoxMode;&lt;br /&gt;
        // 1 = OK&lt;br /&gt;
        // 2 = timed OK&lt;br /&gt;
        // 3 = Yes/No&lt;br /&gt;
        // 4 = timed Yes/No&lt;br /&gt;
&lt;br /&gt;
        int32_t okYesEventType;&lt;br /&gt;
        int32_t okYesEventData;&lt;br /&gt;
        int32_t okYesEventParam;&lt;br /&gt;
&lt;br /&gt;
        int32_t noEventType;&lt;br /&gt;
        int32_t noEventData;&lt;br /&gt;
        int32_t noEventParam;&lt;br /&gt;
&lt;br /&gt;
        void* stMessageInnerObject;&lt;br /&gt;
        AduGuiControl* btOKControl;&lt;br /&gt;
        AduGuiControl* btYesControl;&lt;br /&gt;
        AduGuiControl* btNoControl;&lt;br /&gt;
        void* stTimeLeftInnerObject;&lt;br /&gt;
&lt;br /&gt;
        float delayTimer;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsTimedMode() const {&lt;br /&gt;
            return messageBoxMode == MessageBox_TimedOK || messageBoxMode == MessageBox_TimedYesNo;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsYesNoMode() const {&lt;br /&gt;
            return messageBoxMode == MessageBox_YesNo || messageBoxMode == MessageBox_TimedYesNo;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsOkOnlyMode() const {&lt;br /&gt;
            return messageBoxMode == MessageBox_OK || messageBoxMode == MessageBox_TimedOK;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsShowing() const {&lt;br /&gt;
            return processEnabled != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using ShowMessageBoxOwnerFn = MessageBoxDialogOwner * (FTSDK_THISCALL*)(MessageBoxDialogOwner* self, const wchar_t* message, int32_t mode, const int32_t* okYesEventArgs, const int32_t* noEventArgs);&lt;br /&gt;
        MessageBoxDialogOwner* Show(const wchar_t* message, MessageBoxMode mode, const int32_t* okYesEventArgs = nullptr, const int32_t* noEventArgs = nullptr) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;ShowMessageBoxOwnerFn&amp;gt;(0x0056DAE0)(&lt;br /&gt;
                this,&lt;br /&gt;
                message,&lt;br /&gt;
                static_cast&amp;lt;int32_t&amp;gt;(mode),&lt;br /&gt;
                okYesEventArgs,&lt;br /&gt;
                noEventArgs&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        MessageBoxDialogOwner* ShowOK(const wchar_t* message) {&lt;br /&gt;
            return Show(message, MessageBox_OK);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        MessageBoxDialogOwner* ShowTimedOK(const wchar_t* message) {&lt;br /&gt;
            return Show(message, MessageBox_TimedOK);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        MessageBoxDialogOwner* ShowYesNo(&lt;br /&gt;
            const wchar_t* message,&lt;br /&gt;
            int32_t okYesEventType = 0,&lt;br /&gt;
            int32_t okYesEventData = 0,&lt;br /&gt;
            int32_t okYesEventParam = 0,&lt;br /&gt;
            int32_t noEventType = 0,&lt;br /&gt;
            int32_t noEventData = 0,&lt;br /&gt;
            int32_t noEventParam = 0&lt;br /&gt;
        ) {&lt;br /&gt;
            const int32_t okYesArgs[3] = {&lt;br /&gt;
                okYesEventType,&lt;br /&gt;
                okYesEventData,&lt;br /&gt;
                okYesEventParam&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            const int32_t noArgs[3] = {&lt;br /&gt;
                noEventType,&lt;br /&gt;
                noEventData,&lt;br /&gt;
                noEventParam&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            return Show(message, MessageBox_YesNo, okYesArgs, noArgs);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        MessageBoxDialogOwner* ShowTimedYesNo(&lt;br /&gt;
            const wchar_t* message,&lt;br /&gt;
            int32_t okYesEventType = 0,&lt;br /&gt;
            int32_t okYesEventData = 0,&lt;br /&gt;
            int32_t okYesEventParam = 0,&lt;br /&gt;
            int32_t noEventType = 0,&lt;br /&gt;
            int32_t noEventData = 0,&lt;br /&gt;
            int32_t noEventParam = 0&lt;br /&gt;
        ) {&lt;br /&gt;
            const int32_t okYesArgs[3] = {&lt;br /&gt;
                okYesEventType,&lt;br /&gt;
                okYesEventData,&lt;br /&gt;
                okYesEventParam&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            const int32_t noArgs[3] = {&lt;br /&gt;
                noEventType,&lt;br /&gt;
                noEventData,&lt;br /&gt;
                noEventParam&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            return Show(message, MessageBox_TimedYesNo, okYesArgs, noArgs);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static_assert(sizeof(MessageBoxDialogOwner) == 0x21C);&lt;br /&gt;
&lt;br /&gt;
    class GameDialogStage : public StageBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL AttachOwnerAndCreateUserNotice(ScreenRoot* parent, int32_t id) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL InitStageXml() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetSelectedOrActivePopupIndex(int32_t index) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL CanLeaveStageOrPopup(int32_t reason) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ReservedF0() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnBackOrDefaultAction() = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        StageBase* builtinPopupOwners[10];&lt;br /&gt;
        uint8_t xmlLoadedFlag;&lt;br /&gt;
        uint8_t pad211[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t selectedOrActivePopupIndex;&lt;br /&gt;
        void* trayPopupOwner;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsXmlLoaded() const {&lt;br /&gt;
            return xmlLoadedFlag != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        StageBase* BuiltinPopupOwnerAt(int32_t index) const {&lt;br /&gt;
            if (index &amp;lt; 0 || index &amp;gt;= 10)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return builtinPopupOwners[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using GetOrCreateBuiltinPopupOwnerFn = StageBase * (FTSDK_THISCALL*)(StageBase* self, uint32_t popupIndex);&lt;br /&gt;
        StageBase* GetOrCreateBuiltinPopupOwner(uint32_t popupIndex) {&lt;br /&gt;
            if (popupIndex &amp;gt;= Constants::KnownBuiltinPopupSlotCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return reinterpret_cast&amp;lt;GetOrCreateBuiltinPopupOwnerFn&amp;gt;(0x00498890)(this, popupIndex);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class StageManager : public ScreenInputRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        void* stageHwnd;&lt;br /&gt;
&lt;br /&gt;
        int32_t previousStageId;&lt;br /&gt;
        int32_t pendingStageId;&lt;br /&gt;
        int32_t currentStageId;&lt;br /&gt;
        int32_t transitionStageScratchId;&lt;br /&gt;
&lt;br /&gt;
        GameDialogStage* currentStage;&lt;br /&gt;
        uint8_t messageBoxDialog[0x21C];&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;GameDialogStage*&amp;gt; stages;&lt;br /&gt;
&lt;br /&gt;
        void* currentOverlayRaw;&lt;br /&gt;
        Structs::StageOverlayQuad overlayQuad;&lt;br /&gt;
&lt;br /&gt;
        float overlayDrawTimer;&lt;br /&gt;
&lt;br /&gt;
        Structs::StageOwnedResource stageTransitionResource;&lt;br /&gt;
&lt;br /&gt;
        int32_t frameUpdating;&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknown87C[0x10];&lt;br /&gt;
&lt;br /&gt;
        uint8_t delayedReconnectFlag;&lt;br /&gt;
        uint8_t pad88D[3];&lt;br /&gt;
&lt;br /&gt;
        float periodicActionTimer;&lt;br /&gt;
&lt;br /&gt;
        uint8_t hourlyPacketReadyFlag;&lt;br /&gt;
        uint8_t pad895[3];&lt;br /&gt;
&lt;br /&gt;
        float hourlyPacketTimer;&lt;br /&gt;
        int32_t unknown89C; // +0x89C&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        MessageBoxDialogOwner* MessageBoxDialog() {&lt;br /&gt;
            return reinterpret_cast&amp;lt;MessageBoxDialogOwner*&amp;gt;(messageBoxDialog);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const MessageBoxDialogOwner* MessageBoxDialog() const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;const MessageBoxDialogOwner*&amp;gt;(messageBoxDialog);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        GameDialogStage* CurrentStage() const {&lt;br /&gt;
            if (currentStage)&lt;br /&gt;
                return currentStage;&lt;br /&gt;
&lt;br /&gt;
            if (currentStageId &amp;lt; 0 || currentStageId &amp;gt;= stages.Size())&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return stages[currentStageId];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsValidStageIndex(uint32_t index) const {&lt;br /&gt;
            return index &amp;lt; Constants::StageOwnerCount;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsFrameUpdating() const {&lt;br /&gt;
            return frameUpdating != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool HasOverlay() const {&lt;br /&gt;
            return currentOverlayRaw != nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsHourlyPacketReady() const {&lt;br /&gt;
            return hourlyPacketReadyFlag != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using RequestStateFn = void(FTSDK_THISCALL*)(StageManager* self, uint32_t state);&lt;br /&gt;
        void RequestState(uint32_t state) {&lt;br /&gt;
            if (!IsValidStageIndex(state))&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            reinterpret_cast&amp;lt;RequestStateFn&amp;gt;(0x004AE9A0)(this, state);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using SwitchStateNowFn = void(FTSDK_THISCALL*)(StageManager* self, uint32_t state);&lt;br /&gt;
        void SwitchStateNow(uint32_t state) {&lt;br /&gt;
            if (!IsValidStageIndex(state))&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            reinterpret_cast&amp;lt;SwitchStateNowFn&amp;gt;(0x004AD7D0)(this, state);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using InitStagesFn = bool(FTSDK_THISCALL*)(StageManager* self, void* hwnd, int startupParam);&lt;br /&gt;
        bool InitStages(void* hwnd, int startupParam) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;InitStagesFn&amp;gt;(0x004B0590)(this, hwnd, startupParam);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using ShowMessageBoxFn = void(FTSDK_THISCALL*)(StageManager* self, const wchar_t* message, int32_t mode, int32_t okYesEventType, int32_t okYesEventData, int32_t okYesEventParam, uint8_t playSound);&lt;br /&gt;
        void ShowMessageBox(const wchar_t* message, MessageBoxMode mode = MessageBox_OK, int32_t okYesEventType = 0, int32_t okYesEventData = 0,&lt;br /&gt;
            int32_t okYesEventParam = 0, bool playSound = true) {&lt;br /&gt;
            reinterpret_cast&amp;lt;ShowMessageBoxFn&amp;gt;(0x004AE9C0)(&lt;br /&gt;
                this,&lt;br /&gt;
                message,&lt;br /&gt;
                static_cast&amp;lt;int32_t&amp;gt;(mode),&lt;br /&gt;
                okYesEventType,&lt;br /&gt;
                okYesEventData,&lt;br /&gt;
                okYesEventParam,&lt;br /&gt;
                playSound ? 1 : 0&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ShowOKMessage(const wchar_t* message, bool playSound = true) {&lt;br /&gt;
            ShowMessageBox(message, MessageBox_OK, 0, 0, 0, playSound);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ShowTimedOKMessage(const wchar_t* message, bool playSound = true) {&lt;br /&gt;
            ShowMessageBox(message, MessageBox_TimedOK, 0, 0, 0, playSound);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ShowYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) {&lt;br /&gt;
            ShowMessageBox(&lt;br /&gt;
                message,&lt;br /&gt;
                MessageBox_YesNo,&lt;br /&gt;
                okYesEventType,&lt;br /&gt;
                okYesEventData,&lt;br /&gt;
                okYesEventParam,&lt;br /&gt;
                playSound&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ShowTimedYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) {&lt;br /&gt;
            ShowMessageBox(&lt;br /&gt;
                message,&lt;br /&gt;
                MessageBox_TimedYesNo,&lt;br /&gt;
                okYesEventType,&lt;br /&gt;
                okYesEventData,&lt;br /&gt;
                okYesEventParam,&lt;br /&gt;
                playSound&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static_assert(sizeof(StageManager) == 0x8A0);&lt;br /&gt;
&lt;br /&gt;
    using GetStageManagerFn = StageManager * (FTSDK_CDECL*)();&lt;br /&gt;
    inline StageManager* GetStageManager() {&lt;br /&gt;
        return reinterpret_cast&amp;lt;GetStageManagerFn&amp;gt;(0x004B0520)();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    using ConstructGuiOwnerBaseFn = StageBase * (FTSDK_THISCALL*)(StageBase* self);&lt;br /&gt;
    inline StageBase* ConstructStageBase(StageBase* memory) {&lt;br /&gt;
        return reinterpret_cast&amp;lt;ConstructGuiOwnerBaseFn&amp;gt;(0x005AEC40)(memory);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    using ConstructGameDialogStageFn = GameDialogStage * (FTSDK_THISCALL*)(GameDialogStage* self);&lt;br /&gt;
    inline GameDialogStage* ConstructGameDialogStage(GameDialogStage* memory) {&lt;br /&gt;
        return reinterpret_cast&amp;lt;ConstructGameDialogStageFn&amp;gt;(0x004987C0)(memory);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Example Usage ==&lt;br /&gt;
* [[Custom Client UI with StageManager Input|Custom client UI with StageManager input forwarding]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Client]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Custom_Client_UI_with_StageManager_Input&amp;diff=304</id>
		<title>Custom Client UI with StageManager Input</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Custom_Client_UI_with_StageManager_Input&amp;diff=304"/>
		<updated>2026-05-13T14:16:27Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Overview ==&lt;br /&gt;
This page shows a working custom UI proof of concept for the Fantasy Tennis client SDK. The goal is to create a separate XML based GUI owner, attach it to the current stage owner and route input to it without replacing or hijacking an existing built-in popup.&lt;br /&gt;
&lt;br /&gt;
The important part is that the custom owner can be loaded and displayed like a normal client GUI object, but input still has to reach it through the client input path. For that reason the StageManager input handler is hooked and the custom owner is given a chance to process input before normal stage input continues.&lt;br /&gt;
&lt;br /&gt;
This test confirms that a custom GUI can:&lt;br /&gt;
* create its own &amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt; owner;&lt;br /&gt;
* attach to the current &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt; or another &amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt; parent;&lt;br /&gt;
* load a custom XML dialog;&lt;br /&gt;
* bind buttons and edit boxes through &amp;lt;code&amp;gt;GuiBind&amp;lt;/code&amp;gt;;&lt;br /&gt;
* receive XML callback events;&lt;br /&gt;
* read text from an EditBox;&lt;br /&gt;
* forward mouse and keyboard input through the StageManager input path.&lt;br /&gt;
&lt;br /&gt;
== StageManager Input Hook ==&lt;br /&gt;
The custom GUI needs to receive the same input messages as normal client GUI owners. The client already routes window messages through the StageManager and then into the active GUI owner tree, so the test hooks &amp;lt;code&amp;gt;StageManager::HandleInput&amp;lt;/code&amp;gt; and forwards input to the custom owner before falling back to the original client handler.&lt;br /&gt;
&lt;br /&gt;
A custom GUI owner attached manually to the current stage can render and receive callbacks, but keyboard and mouse input must still be forwarded through the same owner input path.&lt;br /&gt;
&lt;br /&gt;
The hook below replaces the StageManager HandleInput vtable entry. It keeps the original behavior intact, but gives the custom UI owner a chance to handle input first when it is open and reachable. If the custom UI consumes the input, the hook returns true. Otherwise the original StageManager input handler is called.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
// Function type for StageManager::HandleInput.&lt;br /&gt;
// The hook uses __fastcall because thiscall is represented as ECX=this,&lt;br /&gt;
// with the dummy EDX parameter kept in the hook signature.&lt;br /&gt;
using StageManagerHandleInputFn = bool(__fastcall*)(&lt;br /&gt;
    FTSDK::StageManager* self,&lt;br /&gt;
    void* edx,&lt;br /&gt;
    uint32_t msg,&lt;br /&gt;
    int32_t wParam,&lt;br /&gt;
    int32_t lParam&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
StageManagerHandleInputFn o_StageManagerHandleInput = nullptr;&lt;br /&gt;
&lt;br /&gt;
bool __fastcall hk_StageManagerHandleInput(&lt;br /&gt;
    FTSDK::StageManager* self,&lt;br /&gt;
    void* edx,&lt;br /&gt;
    uint32_t msg,&lt;br /&gt;
    int32_t wParam,&lt;br /&gt;
    int32_t lParam&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
static void* GetObjectVTable(const void* object) {&lt;br /&gt;
    return object ? *reinterpret_cast&amp;lt;void* const*&amp;gt;(object) : nullptr;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
g_StageManager = std::make_unique&amp;lt;ShadowVTManager&amp;gt;();&lt;br /&gt;
&lt;br /&gt;
// Get the global StageManager instance.&lt;br /&gt;
auto* manager = FTSDK::GetStageManager();&lt;br /&gt;
if (manager) {&lt;br /&gt;
    // Hook StageManager::HandleInput.&lt;br /&gt;
    // In the currently analyzed client build this is vtable index 38.&lt;br /&gt;
    g_StageManager-&amp;gt;Setup(manager);&lt;br /&gt;
    g_StageManager-&amp;gt;Hook(38, hk_StageManagerHandleInput);&lt;br /&gt;
    o_StageManagerHandleInput =&lt;br /&gt;
        g_StageManager-&amp;gt;GetOriginal&amp;lt;StageManagerHandleInputFn&amp;gt;(38);&lt;br /&gt;
&lt;br /&gt;
    std::printf(&lt;br /&gt;
        &amp;quot;[StageManagerHook] hooked manager=%p vt=%p index=38 original=%p hook=%p\n&amp;quot;,&lt;br /&gt;
        manager,&lt;br /&gt;
        GetObjectVTable(manager),&lt;br /&gt;
        o_StageManagerHandleInput,&lt;br /&gt;
        hk_StageManagerHandleInput&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
bool __fastcall hk_StageManagerHandleInput(&lt;br /&gt;
    FTSDK::StageManager* self,&lt;br /&gt;
    void* edx,&lt;br /&gt;
    uint32_t msg,&lt;br /&gt;
    int32_t wParam,&lt;br /&gt;
    int32_t lParam&lt;br /&gt;
) {&lt;br /&gt;
    FUNCTION_GUARD;&lt;br /&gt;
&lt;br /&gt;
    // Give the custom owner a chance first.&lt;br /&gt;
    // If it consumes the input, stop here.&lt;br /&gt;
    if (CustomGuiTest::HandleInput(msg, wParam, lParam)) {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Normal game/stage input continues.&lt;br /&gt;
    return o_StageManagerHandleInput(self, edx, msg, wParam, lParam);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Custom GUI Definition ==&lt;br /&gt;
The custom UI uses its own &amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt; instance instead of reusing one of the built-in popup slots. The owner is backed by local storage, constructed with the original client constructor and later attached to the current stage owner.&lt;br /&gt;
&lt;br /&gt;
The XML controls are connected through &amp;lt;code&amp;gt;GuiBind&amp;lt;/code&amp;gt; entries. Buttons use normal &amp;lt;code&amp;gt;ButtonClick&amp;lt;/code&amp;gt; events, while the EditBox sends &amp;lt;code&amp;gt;EditBoxChange&amp;lt;/code&amp;gt; events while typing. The callback receives the command id, event type and a control pointer in &amp;lt;code&amp;gt;param&amp;lt;/code&amp;gt; for the EditBox event.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
namespace CustomGuiTest {&lt;br /&gt;
&lt;br /&gt;
    struct CustomGuiContext {&lt;br /&gt;
        FTSDK::StageBase* owner = nullptr;&lt;br /&gt;
        FTSDK::StageBase* parentOwner = nullptr;&lt;br /&gt;
        bool loaded = false;&lt;br /&gt;
        bool opened = false;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static CustomGuiContext g_ctx{};&lt;br /&gt;
&lt;br /&gt;
    // Keep this larger than the known base owner size.&lt;br /&gt;
    // The XML/dialog/control objects are allocated by the client, not inside this buffer.&lt;br /&gt;
    alignas(16) static std::uint8_t g_customOwnerStorage[0x800]{};&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::Structs::GuiBind g_customBinds[] = {&lt;br /&gt;
        { 1000, &amp;quot;btTest&amp;quot;,         FTSDK::GuiControlType::Button },&lt;br /&gt;
        { 1001, &amp;quot;btClose&amp;quot;,        FTSDK::GuiControlType::Button },&lt;br /&gt;
        { 1002, &amp;quot;stCustomWindow&amp;quot;, FTSDK::GuiControlType::Static },&lt;br /&gt;
        { 1003, &amp;quot;stCustomTop&amp;quot;,    FTSDK::GuiControlType::Static },&lt;br /&gt;
        { 1004, &amp;quot;stCustomInfo&amp;quot;,   FTSDK::GuiControlType::Static },&lt;br /&gt;
        { 1005, &amp;quot;ebCustomInput&amp;quot;,  FTSDK::GuiControlType::EditBox },&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static constexpr int32_t CustomBindCount =&lt;br /&gt;
        static_cast&amp;lt;int32_t&amp;gt;(sizeof(g_customBinds) / sizeof(g_customBinds[0]));&lt;br /&gt;
&lt;br /&gt;
    void CloseCustomGui();&lt;br /&gt;
    bool IsOpen();&lt;br /&gt;
&lt;br /&gt;
    void FTSDK_STDCALL CustomGuiCallback(&lt;br /&gt;
        FTSDK::GuiEventType eventType,&lt;br /&gt;
        int commandId,&lt;br /&gt;
        int param,&lt;br /&gt;
        void* userData&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
} // namespace CustomGuiTest&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Loading and Opening ==&lt;br /&gt;
Opening the custom UI follows the same basic order as the built-in GUI owners:&lt;br /&gt;
&lt;br /&gt;
* construct the owner;&lt;br /&gt;
* attach it to the current stage owner;&lt;br /&gt;
* load the XML file;&lt;br /&gt;
* set the XML callback user data;&lt;br /&gt;
* run the base post-load state cleanup;&lt;br /&gt;
* center the owner;&lt;br /&gt;
* open and activate it;&lt;br /&gt;
* bring it to the front.&lt;br /&gt;
&lt;br /&gt;
The owner is only reused while the current stage owner stays the same. If the stage changes, the test code refuses to reuse the old owner because the parent owner relationship would no longer be valid.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
namespace CustomGuiTest {&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::StageBase* CurrentParentOwner() {&lt;br /&gt;
        auto* manager = FTSDK::GetStageManager();&lt;br /&gt;
        auto* currentStage = manager ? manager-&amp;gt;CurrentStage() : nullptr;&lt;br /&gt;
&lt;br /&gt;
        return reinterpret_cast&amp;lt;FTSDK::StageBase*&amp;gt;(currentStage);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::StageBase* CreateCustomGuiOwner(&lt;br /&gt;
        FTSDK::StageBase* parentOwner&lt;br /&gt;
    ) {&lt;br /&gt;
        if (!parentOwner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] Create failed: parentOwner=null\n&amp;quot;);&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        std::memset(g_customOwnerStorage, 0, sizeof(g_customOwnerStorage));&lt;br /&gt;
&lt;br /&gt;
        auto* owner = FTSDK::ConstructStageBase(&lt;br /&gt;
            reinterpret_cast&amp;lt;FTSDK::StageBase*&amp;gt;(g_customOwnerStorage)&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (!owner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] ConstructStageBase returned null\n&amp;quot;);&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        g_ctx.owner = owner;&lt;br /&gt;
        g_ctx.parentOwner = parentOwner;&lt;br /&gt;
        g_ctx.loaded = false;&lt;br /&gt;
        g_ctx.opened = false;&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] constructed owner=%p vt=%p parentOwner=%p\n&amp;quot;,&lt;br /&gt;
            owner,&lt;br /&gt;
            GetObjectVTable(owner),&lt;br /&gt;
            parentOwner&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        // This attaches the custom owner to the current stage owner:&lt;br /&gt;
        //   parent = StageBase/ScreenRoot parent&lt;br /&gt;
        //   parent-&amp;gt;children += owner&lt;br /&gt;
        owner-&amp;gt;InitDialogWithParentOwner(parentOwner);&lt;br /&gt;
&lt;br /&gt;
        const bool loaded = owner-&amp;gt;LoadXml(&lt;br /&gt;
            &amp;quot;Gui_Test.xml&amp;quot;,&lt;br /&gt;
            g_customBinds,&lt;br /&gt;
            CustomBindCount,&lt;br /&gt;
            CustomGuiCallback,&lt;br /&gt;
            false&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] LoadXml returned=%d owner=%p xml=%p\n&amp;quot;,&lt;br /&gt;
            loaded ? 1 : 0,&lt;br /&gt;
            owner,&lt;br /&gt;
            owner-&amp;gt;XmlDialog()&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (!loaded || !owner-&amp;gt;XmlDialog()) {&lt;br /&gt;
            return owner;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Important: force callback userData to our context.&lt;br /&gt;
        // LoadXml installs the callback, but the dialog callback userData should point to our context.&lt;br /&gt;
        owner-&amp;gt;XmlDialog()-&amp;gt;SetCallback(CustomGuiCallback, &amp;amp;g_ctx);&lt;br /&gt;
&lt;br /&gt;
        // Match the built-in owner pattern after XML load.&lt;br /&gt;
        // For the base owner implementation this clears the active/processing flag.&lt;br /&gt;
        owner-&amp;gt;OnDeactivate();&lt;br /&gt;
&lt;br /&gt;
        // Optional. Use only if the XML/root rect supports it.&lt;br /&gt;
        owner-&amp;gt;CenterRectInParent();&lt;br /&gt;
&lt;br /&gt;
        g_ctx.loaded = true;&lt;br /&gt;
&lt;br /&gt;
        return owner;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::StageBase* GetOrCreateCustomGuiOwner() {&lt;br /&gt;
        auto* manager = FTSDK::GetStageManager();&lt;br /&gt;
        auto* parentOwner = CurrentParentOwner();&lt;br /&gt;
&lt;br /&gt;
        if (!manager || !parentOwner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] no manager/current stage owner\n&amp;quot;);&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (g_ctx.owner) {&lt;br /&gt;
            // InitDialogWithParentOwner only attaches cleanly for the original parent.&lt;br /&gt;
            // Do not reuse this test owner after a state switch yet.&lt;br /&gt;
            if (g_ctx.parentOwner != parentOwner) {&lt;br /&gt;
                std::printf(&lt;br /&gt;
                    &amp;quot;[CustomGui] current stage owner changed. oldParent=%p newParent=%p. &amp;quot;&lt;br /&gt;
                    &amp;quot;Not reusing custom owner in this test build.\n&amp;quot;,&lt;br /&gt;
                    g_ctx.parentOwner,&lt;br /&gt;
                    parentOwner&lt;br /&gt;
                );&lt;br /&gt;
                return nullptr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return g_ctx.owner;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return CreateCustomGuiOwner(parentOwner);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    void OpenOrShowCustomGui() {&lt;br /&gt;
        auto* manager = FTSDK::GetStageManager();&lt;br /&gt;
        auto* parentOwner = CurrentParentOwner();&lt;br /&gt;
&lt;br /&gt;
        std::printf(&amp;quot;========== OpenOrShowCustomGui ==========\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        if (!manager || !parentOwner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] manager/current stage owner missing\n&amp;quot;);&lt;br /&gt;
            std::printf(&amp;quot;=========================================\n&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] manager=%p currentStageId=%d parentOwner=%p parentXml=%p\n&amp;quot;,&lt;br /&gt;
            manager,&lt;br /&gt;
            manager-&amp;gt;currentStageId,&lt;br /&gt;
            parentOwner,&lt;br /&gt;
            parentOwner-&amp;gt;XmlDialog()&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        auto* owner = GetOrCreateCustomGuiOwner();&lt;br /&gt;
&lt;br /&gt;
        if (!owner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] owner unavailable\n&amp;quot;);&lt;br /&gt;
            std::printf(&amp;quot;=========================================\n&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!g_ctx.loaded || !owner-&amp;gt;XmlDialog()) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] owner exists but GUI is not loaded\n&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        owner-&amp;gt;SetOpenFlag();&lt;br /&gt;
        owner-&amp;gt;ActivateProcessing();&lt;br /&gt;
        owner-&amp;gt;parentChainEnabled = 1;&lt;br /&gt;
&lt;br /&gt;
        // Extra explicit fronting is harmless and useful while testing.&lt;br /&gt;
        if (g_ctx.parentOwner) {&lt;br /&gt;
            g_ctx.parentOwner-&amp;gt;BringChildOwnerToFront(owner);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        g_ctx.opened = true;&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] opened parent=%p owner=%p open=%d process=%d reachable=%d xml=%p\n&amp;quot;,&lt;br /&gt;
            g_ctx.parentOwner,&lt;br /&gt;
            owner,&lt;br /&gt;
            owner-&amp;gt;openFlag,&lt;br /&gt;
            owner-&amp;gt;processEnabled,&lt;br /&gt;
            owner-&amp;gt;IsReachableThroughParentChain(),&lt;br /&gt;
            owner-&amp;gt;XmlDialog()&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    void CloseCustomGui() {&lt;br /&gt;
        auto* owner = g_ctx.owner;&lt;br /&gt;
&lt;br /&gt;
        if (!owner)&lt;br /&gt;
            return;&lt;br /&gt;
&lt;br /&gt;
        owner-&amp;gt;ClearOpenFlag();&lt;br /&gt;
        owner-&amp;gt;DeactivateProcessing();&lt;br /&gt;
&lt;br /&gt;
        g_ctx.opened = false;&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] closed owner=%p open=%d process=%d reachable=%d\n&amp;quot;,&lt;br /&gt;
            owner,&lt;br /&gt;
            owner-&amp;gt;openFlag,&lt;br /&gt;
            owner-&amp;gt;processEnabled,&lt;br /&gt;
            owner-&amp;gt;IsReachableThroughParentChain()&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bool IsOpen() {&lt;br /&gt;
        return g_ctx.opened;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bool HandleInput(uint32_t msg, int32_t wParam, int32_t lParam) {&lt;br /&gt;
        auto* owner = g_ctx.owner;&lt;br /&gt;
&lt;br /&gt;
        if (!g_ctx.opened || !owner || !owner-&amp;gt;XmlDialog())&lt;br /&gt;
            return false;&lt;br /&gt;
&lt;br /&gt;
        if (!owner-&amp;gt;processEnabled || !owner-&amp;gt;openFlag)&lt;br /&gt;
            return false;&lt;br /&gt;
&lt;br /&gt;
        if (!owner-&amp;gt;IsReachableThroughParentChain())&lt;br /&gt;
            return false;&lt;br /&gt;
&lt;br /&gt;
        return owner-&amp;gt;HandleInput(msg, wParam, lParam);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
} // namespace CustomGuiTest&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Callback Behavior ==&lt;br /&gt;
The XML callback is used for button clicks and EditBox events. The Test button reads the current text from the custom EditBox and forwards it to the test logic. The Close button closes the custom owner and resets the opened state.&lt;br /&gt;
&lt;br /&gt;
For &amp;lt;code&amp;gt;EditBoxChange&amp;lt;/code&amp;gt; events, &amp;lt;code&amp;gt;param&amp;lt;/code&amp;gt; points to the EditBox control that changed. The current test SDK exposes the wide text pointer at the confirmed EditBox offset, which allows the callback to print or forward the typed text.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
void FTSDK_STDCALL CustomGuiCallback(&lt;br /&gt;
    FTSDK::GuiEventType eventType,&lt;br /&gt;
    int commandId,&lt;br /&gt;
    int param,&lt;br /&gt;
    void* userData&lt;br /&gt;
) {&lt;br /&gt;
    auto* ctx = reinterpret_cast&amp;lt;CustomGuiContext*&amp;gt;(userData);&lt;br /&gt;
    auto* owner = ctx ? ctx-&amp;gt;owner : nullptr;&lt;br /&gt;
&lt;br /&gt;
    std::printf(&lt;br /&gt;
        &amp;quot;[CustomGui] callback eventType=%d commandId=%d param=%p ctx=%p owner=%p\n&amp;quot;,&lt;br /&gt;
        static_cast&amp;lt;int&amp;gt;(eventType),&lt;br /&gt;
        commandId,&lt;br /&gt;
        reinterpret_cast&amp;lt;void*&amp;gt;(param),&lt;br /&gt;
        userData,&lt;br /&gt;
        owner&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    if (!ctx || !owner)&lt;br /&gt;
        return;&lt;br /&gt;
&lt;br /&gt;
    switch (commandId) {&lt;br /&gt;
    case 1000:&lt;br /&gt;
        if (eventType == FTSDK::GuiEventType::ButtonClick) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] btTest clicked\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
            auto* inputControl = owner-&amp;gt;GetControlByCommandId(&lt;br /&gt;
                1005,&lt;br /&gt;
                FTSDK::GuiControlType::EditBox&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            if (!inputControl) {&lt;br /&gt;
                std::printf(&amp;quot;[CustomGui] EditBox 1005 not found as EditBox, trying without type filter\n&amp;quot;);&lt;br /&gt;
                inputControl = owner-&amp;gt;GetControlByCommandId(1005, -1);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (!inputControl) {&lt;br /&gt;
                std::printf(&amp;quot;[CustomGui] input control commandId=1005 not found\n&amp;quot;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            auto* input = reinterpret_cast&amp;lt;FTSDK::AduGuiEditBox*&amp;gt;(inputControl);&lt;br /&gt;
&lt;br /&gt;
            std::printf(&lt;br /&gt;
                &amp;quot;[CustomGui] input=%p vt=%p name=\&amp;quot;%s\&amp;quot; cmd=%d type=%d\n&amp;quot;,&lt;br /&gt;
                input,&lt;br /&gt;
                input-&amp;gt;VTable(),&lt;br /&gt;
                input-&amp;gt;Name().Data(),&lt;br /&gt;
                input-&amp;gt;CommandId(),&lt;br /&gt;
                input-&amp;gt;ControlTypeId()&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            input-&amp;gt;DumpText();&lt;br /&gt;
&lt;br /&gt;
            const wchar_t* text = input-&amp;gt;TextW();&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] forwarding text: \&amp;quot;%ls\&amp;quot;\n&amp;quot;, text);&lt;br /&gt;
&lt;br /&gt;
            // Example: send text to your own logic here.&lt;br /&gt;
            // SendCustomCommand(text);&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
&lt;br /&gt;
    case 1001:&lt;br /&gt;
        if (eventType == FTSDK::GuiEventType::ButtonClick) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] btClose clicked -&amp;gt; closing custom GUI\n&amp;quot;);&lt;br /&gt;
            CloseCustomGui();&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
&lt;br /&gt;
    case 1005:&lt;br /&gt;
        // Do not dump raw memory here.&lt;br /&gt;
        // This can fire while the editbox is changing internal state.&lt;br /&gt;
        if (eventType == FTSDK::GuiEventType::EditBoxChange) {&lt;br /&gt;
            auto* input = reinterpret_cast&amp;lt;FTSDK::AduGuiEditBox*&amp;gt;(param);&lt;br /&gt;
&lt;br /&gt;
            if (input) {&lt;br /&gt;
                std::printf(&lt;br /&gt;
                    &amp;quot;[CustomGui] input changed input=%p textPtr=%p lenMaybe=%d text=\&amp;quot;%ls\&amp;quot;\n&amp;quot;,&lt;br /&gt;
                    input,&lt;br /&gt;
                    input-&amp;gt;TextW(),&lt;br /&gt;
                    input-&amp;gt;TextField21C(),&lt;br /&gt;
                    input-&amp;gt;TextW()&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
&lt;br /&gt;
    default:&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] unhandled commandId=%d eventType=%d\n&amp;quot;,&lt;br /&gt;
            commandId,&lt;br /&gt;
            static_cast&amp;lt;int&amp;gt;(eventType)&lt;br /&gt;
        );&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Do not call owner-&amp;gt;HandleGuiEvent(...) here.&lt;br /&gt;
    // This callback is usually reached from dispatch already and forwarding&lt;br /&gt;
    // back into the same owner can recurse.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== XML Dialog Sample ==&lt;br /&gt;
The dialog below is a minimal XML GUI used by the test. It defines a small custom window with two buttons and one EditBox. The names must match the &amp;lt;code&amp;gt;GuiBind&amp;lt;/code&amp;gt; table in the C++ code, otherwise the controls will not be assigned the expected command ids.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;Dialog x=&amp;quot;0&amp;quot; y=&amp;quot;0&amp;quot; w=&amp;quot;1024&amp;quot; h=&amp;quot;768&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;Image Name=&amp;quot;DEFAULT&amp;quot; du=&amp;quot;0&amp;quot; dv=&amp;quot;0&amp;quot;/&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;DEFAULT&amp;quot; Type=&amp;quot;Static&amp;quot; Enable=&amp;quot;Yes&amp;quot; Font=&amp;quot;&amp;quot; x=&amp;quot;0&amp;quot; y=&amp;quot;0&amp;quot; w=&amp;quot;0&amp;quot; h=&amp;quot;0&amp;quot; dx=&amp;quot;0&amp;quot; dy=&amp;quot;0&amp;quot; Text=&amp;quot;&amp;quot; TextID=&amp;quot;&amp;quot; TextFx=&amp;quot;&amp;quot; TextAlignX=&amp;quot;CENTER&amp;quot; TextAlignY=&amp;quot;CENTER&amp;quot; TextMargin=&amp;quot;&amp;quot; Debug=&amp;quot;No&amp;quot;/&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomWindow&amp;quot; Type=&amp;quot;Static&amp;quot; x=&amp;quot;18&amp;quot; y=&amp;quot;42&amp;quot; w=&amp;quot;330&amp;quot; h=&amp;quot;190&amp;quot; Text=&amp;quot;JFTSE Custom GUI&amp;quot; TextID=&amp;quot;&amp;quot; TextFx=&amp;quot;Shadow&amp;quot; TextAlignX=&amp;quot;CENTER&amp;quot; TextAlignY=&amp;quot;TOP&amp;quot; TextMargin=&amp;quot;0,8,0,0&amp;quot; Debug=&amp;quot;No&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State Image=&amp;quot;cmnWindow&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomLineTop&amp;quot; Type=&amp;quot;Static&amp;quot; x=&amp;quot;42&amp;quot; y=&amp;quot;80&amp;quot; w=&amp;quot;282&amp;quot; h=&amp;quot;1&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State Image=&amp;quot;cmnWindowLine&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomTop&amp;quot; Type=&amp;quot;Static&amp;quot; x=&amp;quot;40&amp;quot; y=&amp;quot;96&amp;quot; w=&amp;quot;286&amp;quot; h=&amp;quot;24&amp;quot; Text=&amp;quot;Enter text and press Test&amp;quot; TextID=&amp;quot;&amp;quot; TextAlignX=&amp;quot;CENTER&amp;quot; TextAlignY=&amp;quot;CENTER&amp;quot; Debug=&amp;quot;No&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State TextColor=&amp;quot;255,0,166,165&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomInfo&amp;quot; Type=&amp;quot;Static&amp;quot; x=&amp;quot;40&amp;quot; y=&amp;quot;124&amp;quot; w=&amp;quot;286&amp;quot; h=&amp;quot;22&amp;quot; Text=&amp;quot;Gui_Test.xml&amp;quot; TextID=&amp;quot;&amp;quot; TextAlignX=&amp;quot;CENTER&amp;quot; TextAlignY=&amp;quot;CENTER&amp;quot; Debug=&amp;quot;No&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State TextColor=&amp;quot;255,30,30,30&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;ebCustomInput&amp;quot; Type=&amp;quot;EditBox&amp;quot; x=&amp;quot;58&amp;quot; y=&amp;quot;154&amp;quot; w=&amp;quot;250&amp;quot; h=&amp;quot;20&amp;quot; Text=&amp;quot;&amp;quot; TextID=&amp;quot;&amp;quot; Debug=&amp;quot;No&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State Image=&amp;quot;cmnBlank2&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomLineBottom&amp;quot; Type=&amp;quot;Static&amp;quot; x=&amp;quot;42&amp;quot; y=&amp;quot;184&amp;quot; w=&amp;quot;282&amp;quot; h=&amp;quot;1&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State Image=&amp;quot;cmnWindowLine&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;DEFAULT&amp;quot; Type=&amp;quot;Button&amp;quot; TextFx=&amp;quot;Shadow&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State Name=&amp;quot;All&amp;quot; Image=&amp;quot;cmnWindowBtn&amp;quot;/&amp;gt;  &lt;br /&gt;
        &amp;lt;State Name=&amp;quot;Over&amp;quot; Image=&amp;quot;cmnShopBuyBtn&amp;quot;/&amp;gt;  &lt;br /&gt;
        &amp;lt;State Name=&amp;quot;Pressed&amp;quot; Color=&amp;quot;255,155,155,155&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;btTest&amp;quot; Type=&amp;quot;Button&amp;quot; x=&amp;quot;58&amp;quot; y=&amp;quot;194&amp;quot; w=&amp;quot;120&amp;quot; h=&amp;quot;22&amp;quot; Text=&amp;quot;Test&amp;quot; TextID=&amp;quot;&amp;quot;/&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;btClose&amp;quot; Type=&amp;quot;Button&amp;quot; x=&amp;quot;188&amp;quot; y=&amp;quot;194&amp;quot; w=&amp;quot;120&amp;quot; h=&amp;quot;22&amp;quot; Text=&amp;quot;Close&amp;quot; TextID=&amp;quot;&amp;quot;/&amp;gt; &lt;br /&gt;
&amp;lt;/Dialog&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Notes ==&lt;br /&gt;
This is test code for documenting the client GUI path, not final UI framework code. The hardcoded function addresses, vtable indices, object sizes and offsets are specific to the currently analyzed client build.&lt;br /&gt;
&lt;br /&gt;
The most important result is that custom UI does not need to overwrite a built-in popup owner. A separate owner can be attached to the current stage owner, loaded from XML and made interactive by forwarding input through the StageManager path.&lt;br /&gt;
&lt;br /&gt;
[[Category:Client]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=FT_SDK&amp;diff=303</id>
		<title>FT SDK</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=FT_SDK&amp;diff=303"/>
		<updated>2026-05-13T14:11:10Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Overview ==&lt;br /&gt;
This page documents the current reverse engineering progress for the Fantasy Tennis client-side SDK used by JFTSE tooling. The SDK maps known client structures, virtual functions, GUI objects, stage management, XML dialog loading, popup ownership and input routing into C++ wrappers that can be used from injected debugging or extension code.&lt;br /&gt;
&lt;br /&gt;
The current focus is the client GUI layer around &amp;lt;code&amp;gt;StageManager&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ScreenRoot&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt; and Adu GUI controls. Confirmed areas include the fixed 24-stage model, current-stage lookup, parent-chain/process/open-state behavior, built-in popup slot creation, XML dialog loading, command-ID binding, callback dispatch and Adu object state handling.&lt;br /&gt;
&lt;br /&gt;
This page is the current reverse engineering handoff for the SDK. Names, offsets and layouts are updated only after they are verified through disassembly, runtime dumps and real client call sites.&lt;br /&gt;
&lt;br /&gt;
== Scope ==&lt;br /&gt;
* &amp;lt;code&amp;gt;StageManager&amp;lt;/code&amp;gt; state tracking, stage switching and current-stage access.&lt;br /&gt;
* Fixed 24-stage owner list and safe current-stage attachment.&lt;br /&gt;
* &amp;lt;code&amp;gt;ScreenRoot&amp;lt;/code&amp;gt; parent/child ownership, input routing, open state and processing state.&lt;br /&gt;
* &amp;lt;code&amp;gt;StageBase&amp;lt;/code&amp;gt; XML dialog ownership, child-owner lists, priority input child handling and GUI event dispatch.&lt;br /&gt;
* &amp;lt;code&amp;gt;GameDialogStage&amp;lt;/code&amp;gt; built-in popup slots, popup factory behavior, selected/active popup index and tray-popup related state.&lt;br /&gt;
* Game-dialog lifecycle hooks, including XML initialization, activation, update, back/default action and popup close paths.&lt;br /&gt;
* Adu GUI object hierarchy, including active/update/visible flags, timers, child traversal and recursive processing.&lt;br /&gt;
* Adu dialog/control layouts, including object arrays, command IDs, control names, control types, state sets and basic runtime flags.&lt;br /&gt;
* XML GUI binding through &amp;lt;code&amp;gt;GuiBind&amp;lt;/code&amp;gt; arrays, command IDs and control type IDs.&lt;br /&gt;
* Default GUI callback routing through &amp;lt;code&amp;gt;AduGuiDialog&amp;lt;/code&amp;gt; callback/user-data fields.&lt;br /&gt;
* Confirmed event dispatch for buttons, radio buttons, edit boxes, context menus and related GUI events.&lt;br /&gt;
* Confirmed edit-box text access through the wide-character text pointer at the current &amp;lt;code&amp;gt;AduGuiEditBox&amp;lt;/code&amp;gt; layout.&lt;br /&gt;
* Group-style control activation behavior used by ranking and popup XML, especially active/visible state synchronization.&lt;br /&gt;
&lt;br /&gt;
== Stability Notes ==&lt;br /&gt;
Offsets and function addresses are version specific to the currently analyzed Fantasy Tennis client build. Names describe current understanding, not original source names. Any field marked unknown, maybe or conservative should be treated as subject to change until confirmed by call sites, runtime tests or additional disassembly.&lt;br /&gt;
&lt;br /&gt;
Important caveat: custom GUI work should not add another stage. The client uses a fixed set of 24 stage owners. Custom UI should attach to the current stage owner, an existing popup path or the embedded modal/input owner where appropriate.&lt;br /&gt;
&lt;br /&gt;
== Current SDK Header ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
#pragma once&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;cstdint&amp;gt;&lt;br /&gt;
#include &amp;lt;cstddef&amp;gt;&lt;br /&gt;
#include &amp;lt;cstdio&amp;gt;&lt;br /&gt;
#include &amp;lt;cstring&amp;gt;&lt;br /&gt;
#include &amp;lt;cwchar&amp;gt;&lt;br /&gt;
#include &amp;lt;string&amp;gt;&lt;br /&gt;
#include &amp;lt;type_traits&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define FTSDK_THISCALL __thiscall&lt;br /&gt;
#define FTSDK_CDECL    __cdecl&lt;br /&gt;
#define FTSDK_STDCALL  __stdcall&lt;br /&gt;
#define FTSDK_FASTCALL __fastcall&lt;br /&gt;
&lt;br /&gt;
namespace FTSDK {&lt;br /&gt;
&lt;br /&gt;
    namespace Structs {&lt;br /&gt;
        struct FTStringA {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
&lt;br /&gt;
            union {&lt;br /&gt;
                char inlineBuffer[16];&lt;br /&gt;
                char* heapPtr;&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            uint32_t length;&lt;br /&gt;
            uint32_t capacity;&lt;br /&gt;
&lt;br /&gt;
            bool IsInline() const {&lt;br /&gt;
                return capacity &amp;lt; 16;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool IsValid() const {&lt;br /&gt;
				if (length &amp;gt; capacity || length &amp;gt; 4096)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (capacity &amp;gt; 0xFFFFFFFEu)&lt;br /&gt;
					return false;&lt;br /&gt;
&lt;br /&gt;
                if (!IsInline() &amp;amp;&amp;amp; !heapPtr)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                return true;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const char* Data() const {&lt;br /&gt;
                if (!IsValid())&lt;br /&gt;
                    return &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
                return IsInline() ? inlineBuffer : heapPtr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            int32_t Length() const {&lt;br /&gt;
                return IsValid() ? static_cast&amp;lt;int32_t&amp;gt;(length) : 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool Equals(const char* text) const {&lt;br /&gt;
                if (!text || !IsValid())&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                const size_t textLen = std::strlen(text);&lt;br /&gt;
                return textLen == length &amp;amp;&amp;amp; std::memcmp(Data(), text, length) == 0;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct FTStringW {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
&lt;br /&gt;
            union {&lt;br /&gt;
                wchar_t inlineBuffer[8];&lt;br /&gt;
                wchar_t* heapPtr;&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
			uint32_t length;&lt;br /&gt;
            uint32_t capacity;&lt;br /&gt;
&lt;br /&gt;
            bool IsInline() const {&lt;br /&gt;
                return capacity &amp;lt; 8;&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
            bool IsValid() const {&lt;br /&gt;
				if (length &amp;gt; capacity || length &amp;gt; 4096)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (capacity &amp;gt; 0x7FFFFFFEu)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                if (!IsInline() &amp;amp;&amp;amp; !heapPtr)&lt;br /&gt;
                    return false;&lt;br /&gt;
&lt;br /&gt;
                return true;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const wchar_t* Data() const {&lt;br /&gt;
                if (!IsValid())&lt;br /&gt;
                    return L&amp;quot;&amp;quot;;&lt;br /&gt;
                return IsInline() ? inlineBuffer : heapPtr;&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
            int32_t Length() const {&lt;br /&gt;
                return IsValid() ? static_cast&amp;lt;int32_t&amp;gt;(length) : 0;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool Equals(const wchar_t* text) const {&lt;br /&gt;
                if (!text || !IsValid())&lt;br /&gt;
                    return false;&lt;br /&gt;
                const size_t textLen = std::wcslen(text);&lt;br /&gt;
                return textLen == length &amp;amp;&amp;amp; std::memcmp(Data(), text, length * sizeof(wchar_t)) == 0;&lt;br /&gt;
			}&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        struct FTVector {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
			T* first;&lt;br /&gt;
			T* last;&lt;br /&gt;
            T* end;&lt;br /&gt;
&lt;br /&gt;
            int32_t Size() const {&lt;br /&gt;
                if (!first || !last || last &amp;lt; first)&lt;br /&gt;
                    return 0;&lt;br /&gt;
&lt;br /&gt;
                return static_cast&amp;lt;int32_t&amp;gt;(last - first);&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
            int32_t Capacity() const {&lt;br /&gt;
                if (!first || !end || end &amp;lt; first)&lt;br /&gt;
                    return 0;&lt;br /&gt;
&lt;br /&gt;
                return static_cast&amp;lt;int32_t&amp;gt;(end - first);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            bool IsEmpty() const {&lt;br /&gt;
				return Size() == 0;&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
            T&amp;amp; operator[](size_t index) {&lt;br /&gt;
                return first[index];&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const T&amp;amp; operator[](size_t index) const {&lt;br /&gt;
                return first[index];&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            T* Begin() {&lt;br /&gt;
                return first;&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
            T* End() {&lt;br /&gt;
                return last;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const T* Begin() const {&lt;br /&gt;
                return first;&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
            const T* End() const {&lt;br /&gt;
                return last;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        struct FTList {&lt;br /&gt;
            uint32_t unknown_00;&lt;br /&gt;
			T* next;&lt;br /&gt;
			uint32_t count;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct GuiBind {&lt;br /&gt;
            int32_t commandId;&lt;br /&gt;
            const char* controlName;&lt;br /&gt;
            int32_t controlTypeId;&lt;br /&gt;
        };&lt;br /&gt;
        &lt;br /&gt;
        struct Rect {&lt;br /&gt;
            int32_t left;&lt;br /&gt;
            int32_t top;&lt;br /&gt;
            int32_t right;&lt;br /&gt;
            int32_t bottom;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct GuiRectStorage {&lt;br /&gt;
            void* vtable;&lt;br /&gt;
            int32_t left;&lt;br /&gt;
            int32_t top;&lt;br /&gt;
            int32_t right;&lt;br /&gt;
            int32_t bottom;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        struct StageOwnedResource {&lt;br /&gt;
            uint8_t unknown00[0x1C];&lt;br /&gt;
            void* object;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct StageOverlayVertex {&lt;br /&gt;
            float x;&lt;br /&gt;
            float y;&lt;br /&gt;
            float unknown08;&lt;br /&gt;
            float rhwOrOne;&lt;br /&gt;
            uint32_t color;&lt;br /&gt;
            float unknown14;&lt;br /&gt;
            float unknown18;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        struct StageOverlayQuad {&lt;br /&gt;
            StageOverlayVertex vertices[4];&lt;br /&gt;
&lt;br /&gt;
            int32_t unknown70;&lt;br /&gt;
            int32_t unknown74;&lt;br /&gt;
            int32_t widthInt;&lt;br /&gt;
            int32_t heightInt;&lt;br /&gt;
&lt;br /&gt;
            float x;&lt;br /&gt;
            float y;&lt;br /&gt;
            float width;&lt;br /&gt;
            float height;&lt;br /&gt;
&lt;br /&gt;
            float scaleX;&lt;br /&gt;
            float alpha;&lt;br /&gt;
            float rotationOrAngle;&lt;br /&gt;
&lt;br /&gt;
            uint8_t unknown9C;&lt;br /&gt;
            uint8_t unknown9D;&lt;br /&gt;
            uint8_t visibleOrEnabled;&lt;br /&gt;
            uint8_t pad9F;&lt;br /&gt;
&lt;br /&gt;
            uint8_t unknownA0[0x0C];&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        static_assert(sizeof(Rect) == 0x10);&lt;br /&gt;
        static_assert(sizeof(GuiRectStorage) == 0x14);&lt;br /&gt;
        static_assert(sizeof(StageOwnedResource) == 0x20);&lt;br /&gt;
        static_assert(sizeof(FTStringA) == 0x1C);&lt;br /&gt;
		static_assert(sizeof(FTStringW) == 0x1C);&lt;br /&gt;
		static_assert(sizeof(GuiBind) == 0x0C);&lt;br /&gt;
		static_assert(sizeof(FTVector&amp;lt;void*&amp;gt;) == 0x10);&lt;br /&gt;
        static_assert(sizeof(StageOverlayQuad) == 0xAC);&lt;br /&gt;
        static_assert(sizeof(StageOverlayVertex) == 0x1C);&lt;br /&gt;
        static_assert(sizeof(FTList&amp;lt;void*&amp;gt;) == 0x0C);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    enum GuiEventType : int32_t {&lt;br /&gt;
        ButtonClick = 0,&lt;br /&gt;
        ButtonOver = 1,&lt;br /&gt;
&lt;br /&gt;
        ComboBoxOpen = 2,&lt;br /&gt;
        ComboBoxClose = 3,&lt;br /&gt;
        ComboBoxSelectChange = 4,&lt;br /&gt;
&lt;br /&gt;
        RadioButtonChange = 5,&lt;br /&gt;
        CheckBoxChange = 6,&lt;br /&gt;
&lt;br /&gt;
        SliderValueChange = 7,&lt;br /&gt;
&lt;br /&gt;
        EditBoxEnter = 8,&lt;br /&gt;
        EditBoxChange = 9,&lt;br /&gt;
        EditBoxTAB = 10,&lt;br /&gt;
        EditBoxESC = 11,&lt;br /&gt;
        EditBoxCharLimit = 12,&lt;br /&gt;
        EditBoxKeyUp = 13,&lt;br /&gt;
        EditBoxKeyDown = 14,&lt;br /&gt;
&lt;br /&gt;
        ListBoxDBClick = 15,&lt;br /&gt;
        ListBoxSelect = 16,&lt;br /&gt;
        ListBoxSelectEnd = 17,&lt;br /&gt;
&lt;br /&gt;
        ContextMenuClick = 18,&lt;br /&gt;
        ScrollBarPosChange = 19&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum GuiControlType : int32_t {&lt;br /&gt;
        Static = 0,&lt;br /&gt;
        Button = 1,&lt;br /&gt;
        CheckBox = 2,&lt;br /&gt;
        RadioButton = 3,&lt;br /&gt;
        ComboBox = 4,&lt;br /&gt;
        Slider = 5,&lt;br /&gt;
        Gauge = 6,&lt;br /&gt;
        EditBox = 7,&lt;br /&gt;
        IMEEditBox = 8,&lt;br /&gt;
        ListBox = 9,&lt;br /&gt;
        ScrollBar = 10,&lt;br /&gt;
        ContextMenu = 11&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    using GuiCallbackFn = void(FTSDK_STDCALL*)(&lt;br /&gt;
        GuiEventType eventType,&lt;br /&gt;
        int commandId,&lt;br /&gt;
        int param,&lt;br /&gt;
        void* userData&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiStateSet;&lt;br /&gt;
&lt;br /&gt;
    namespace Constants {&lt;br /&gt;
        // Confirmed by StageManager InitStages / SwitchStateNow bounds.&lt;br /&gt;
        // Do not add a custom 25th stage; attach custom UI to an existing owner/modal&lt;br /&gt;
        constexpr int32_t StageOwnerCount = 24;&lt;br /&gt;
        // These are built-in popup owner slots at FTGameDialogOwner + 0x1E8&lt;br /&gt;
        // Not proof that every stage uses all 10 slots&lt;br /&gt;
        constexpr int32_t KnownBuiltinPopupSlotCount = 10;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    class AduBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual AduBase* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGameObj : public AduBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual AduGameObj* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetField08AndMark(int32_t value) = 0;&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL ResetTimerRecursive() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetActive(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetUpdateBlocked(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetVisible(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL UpdateRecursive(float dt) = 0;&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL ProcessVisibleRecursive() = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        void* VTable() const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;void* const*&amp;gt;(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Field04() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x04);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Field08() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x08);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* FirstChild() const {&lt;br /&gt;
            return Read&amp;lt;AduGameObj*&amp;gt;(0x0C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* NextSibling() const {&lt;br /&gt;
            return Read&amp;lt;AduGameObj*&amp;gt;(0x10);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsMarked() const {&lt;br /&gt;
            return ReadBool(0x14);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsActive() const {&lt;br /&gt;
            return ReadBool(0x15);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsUpdateBlocked() const {&lt;br /&gt;
            return ReadBool(0x16);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsVisible() const {&lt;br /&gt;
            return ReadBool(0x17);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        float ElapsedOrTime() const {&lt;br /&gt;
            return Read&amp;lt;float&amp;gt;(0x18);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        float LastDelta() const {&lt;br /&gt;
            return Read&amp;lt;float&amp;gt;(0x1C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Callback&amp;gt;&lt;br /&gt;
        void ForEachChild(Callback&amp;amp;&amp;amp; callback) {&lt;br /&gt;
            for (AduGameObj* child = FirstChild(); child; child = child-&amp;gt;NextSibling())&lt;br /&gt;
                callback(child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Callback&amp;gt;&lt;br /&gt;
        void ForEachChild(Callback&amp;amp;&amp;amp; callback) const {&lt;br /&gt;
            for (AduGameObj* child = FirstChild(); child; child = child-&amp;gt;NextSibling())&lt;br /&gt;
                callback(child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    protected:&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        T Read(uintptr_t offset) const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;const T*&amp;gt;(reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + offset);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        void Write(uintptr_t offset, T value) {&lt;br /&gt;
            *reinterpret_cast&amp;lt;T*&amp;gt;(reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + offset) = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool ReadBool(uintptr_t offset) const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(offset) != 0;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiControl : public AduGameObj {&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiStateSet** StateSets() const {&lt;br /&gt;
            return Read&amp;lt;AduGuiStateSet**&amp;gt;(0x20);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t StateCount() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x24);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t StateCapacityMaybe() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x28);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const Structs::FTStringA&amp;amp; Name() const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;const Structs::FTStringA*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0x2C&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t CommandId() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x48);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        uint32_t Field4C() const {&lt;br /&gt;
            return Read&amp;lt;uint32_t&amp;gt;(0x4C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t ControlTypeId() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x50);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsControlEnabledFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x5C) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsHoverOrState3Flag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x5D) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const Structs::Rect&amp;amp; Rect() const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;const Structs::Rect*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0x60&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t CurrentVisualState() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x70);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t DefaultStateIndex() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x74);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsDebugFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x79) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsOverFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x7A) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsPressedFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x7B) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t X() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x80);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Y() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x84);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Width() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x88);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Height() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x8C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        float ControlTimer() const {&lt;br /&gt;
            return Read&amp;lt;float&amp;gt;(0x18);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiEditBox : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        const wchar_t* TextW() const {&lt;br /&gt;
            const wchar_t* text = Read&amp;lt;const wchar_t*&amp;gt;(0x218);&lt;br /&gt;
            return text ? text : L&amp;quot;&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        std::size_t TextLength() const {&lt;br /&gt;
            return std::wcslen(TextW());&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        uint32_t TextField21C() const {&lt;br /&gt;
            return Read&amp;lt;uint32_t&amp;gt;(0x21C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void DumpText() const {&lt;br /&gt;
            std::printf(&lt;br /&gt;
                &amp;quot;[EditBox] this=%p textPtr=%p strlen=%u field21C=%u text=\&amp;quot;%ls\&amp;quot;\n&amp;quot;,&lt;br /&gt;
                this,&lt;br /&gt;
                TextW(),&lt;br /&gt;
                static_cast&amp;lt;unsigned&amp;gt;(TextLength()),&lt;br /&gt;
                TextField21C(),&lt;br /&gt;
                TextW()&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiDialog : public AduGameObj {&lt;br /&gt;
    public:&lt;br /&gt;
        AduGameObj** ObjectArray() const {&lt;br /&gt;
            return Read&amp;lt;AduGameObj**&amp;gt;(0x8C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t ObjectCount() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x90);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* ObjectAt(int32_t index) const {&lt;br /&gt;
            auto arr = ObjectArray();&lt;br /&gt;
&lt;br /&gt;
            if (!arr || index &amp;lt; 0 || index &amp;gt;= ObjectCount())&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return arr[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* ControlAt(int32_t index) const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;AduGuiControl*&amp;gt;(ObjectAt(index));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByCommandId(int32_t commandId) const {&lt;br /&gt;
            const int32_t count = ObjectCount();&lt;br /&gt;
&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; count; ++i) {&lt;br /&gt;
                auto* control = ControlAt(i);&lt;br /&gt;
                if (!control)&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;CommandId() == commandId)&lt;br /&gt;
                    return control;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByName(const char* name) const {&lt;br /&gt;
            if (!name)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            const int32_t count = ObjectCount();&lt;br /&gt;
&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; count; ++i) {&lt;br /&gt;
                auto* control = ControlAt(i);&lt;br /&gt;
                if (!control)&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;Name().Equals(name))&lt;br /&gt;
                    return control;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void SetCallback(GuiCallbackFn callback, void* userData) {&lt;br /&gt;
            Write&amp;lt;GuiCallbackFn&amp;gt;(27 * 4, callback);&lt;br /&gt;
            Write&amp;lt;void*&amp;gt;(28 * 4, userData);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        GuiCallbackFn Callback() const {&lt;br /&gt;
            return Read&amp;lt;GuiCallbackFn&amp;gt;(27 * 4);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void* CallbackUserData() const {&lt;br /&gt;
            return Read&amp;lt;void*&amp;gt;(28 * 4);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class StageManager;&lt;br /&gt;
&lt;br /&gt;
    class ScreenRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved00() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved04() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved08() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved0C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved10() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved14() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved18() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved1C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved20() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved24() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved28() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual int FTSDK_THISCALL OnFocusEnter() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnFocusLeave() = 0;&lt;br /&gt;
        virtual void* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL AttachRaw(ScreenRoot* parentOrManager, int32_t rectVtableOrTemp, int32_t left, int32_t top, int32_t right, int32_t bottom, int32_t id) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL Shutdown() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL Update() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OffsetRect(int32_t dx, int32_t dy) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL CenterRectInParent() = 0;&lt;br /&gt;
        virtual ScreenRoot* FTSDK_THISCALL ActivateProcessing() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL DeactivateProcessing() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_THISCALL OnStageCommand(int commandId) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved58() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved5C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved60() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved64() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved68() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved6C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved70() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved74() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved78() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved7C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved80() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_THISCALL SetOpenFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ClearOpenFlag() = 0;&lt;br /&gt;
        virtual void FTSDK_STDCALL RectCleanupThunk(int32_t a1, uint8_t rectObj, int32_t a3, int32_t a4) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL HandleSystemEvent(int32_t eventType, void* data, int32_t param) = 0;&lt;br /&gt;
        virtual void FTSDK_STDCALL RectCleanupThunk2(uint8_t rectObj, int32_t a2, int32_t a3) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleInput(uint32_t msg, int32_t wParam, int32_t lParam) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        int32_t id;&lt;br /&gt;
        float field08;&lt;br /&gt;
        float field0C;&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknown10[0x80];&lt;br /&gt;
&lt;br /&gt;
        Structs::GuiRectStorage rect;&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;ScreenRoot*&amp;gt; children;&lt;br /&gt;
        ScreenRoot* parent;&lt;br /&gt;
&lt;br /&gt;
        int32_t parentChainEnabled;&lt;br /&gt;
        int32_t processEnabled;&lt;br /&gt;
        int32_t openFlag;&lt;br /&gt;
        int32_t rectOffsetEnabled;&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknownC8[0x70];&lt;br /&gt;
        char resourceName[0x80];&lt;br /&gt;
        void* lazyResource;&lt;br /&gt;
        uint8_t lazyResourceAllowed;&lt;br /&gt;
        uint8_t pad1BD[3];&lt;br /&gt;
    };&lt;br /&gt;
	static_assert(sizeof(ScreenRoot) == 0x1C0);&lt;br /&gt;
&lt;br /&gt;
    class ScreenInputRoot : public ScreenRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        uint8_t unknown1C0[0x0C];&lt;br /&gt;
        &lt;br /&gt;
        ScreenRoot* rootFocusOwner;&lt;br /&gt;
        ScreenRoot* inputFocusOwner;&lt;br /&gt;
        ScreenRoot* previousFocusOwner;&lt;br /&gt;
        ScreenRoot* activeFocusOwner;&lt;br /&gt;
        &lt;br /&gt;
        uint8_t unknown1DC[0x08];&lt;br /&gt;
&lt;br /&gt;
        int32_t cursorX;&lt;br /&gt;
		int32_t cursorY;&lt;br /&gt;
&lt;br /&gt;
        uint8_t inputState[0x26C];&lt;br /&gt;
&lt;br /&gt;
        ScreenRoot* hoverOrCapturedOwner;&lt;br /&gt;
&lt;br /&gt;
        char resourceBasePath[0x104];&lt;br /&gt;
    };&lt;br /&gt;
	static_assert(sizeof(ScreenInputRoot) == 0x560);&lt;br /&gt;
&lt;br /&gt;
    class StageBase : public ScreenRoot {&lt;br /&gt;
    public:&lt;br /&gt;
		virtual void FTSDK_THISCALL AttachOwnerWithDefaultRect(ScreenRoot* parent, int32_t id) = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL InitDialogWithParentOwner(StageBase* parent) = 0;&lt;br /&gt;
		virtual bool FTSDK_THISCALL LoadXml(const char* xmlName, const Structs::GuiBind* binds, int32_t bindCount, GuiCallbackFn callback, bool reload) = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL OnActivate() = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL OnDeactivate() = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL OnUpdate() = 0;&lt;br /&gt;
		virtual bool FTSDK_THISCALL ProcessChildOwners(float dt) = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL HandleStagePacket(void* packet) = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL HandleGuiEvent(GuiEventType eventType, int commandId, int param) = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL OnCommandAction(int commandId) = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL OnEditBoxEnter(int commandId) = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL OnEditBoxChange(int commandId) = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL OnEditBoxTab(int commandId) = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL OnEditBoxEsc(int commandId) = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL OnEditBoxKeyUp(int commandId) = 0;&lt;br /&gt;
		virtual int FTSDK_THISCALL OnEditBoxKeyDown(int commandId) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnContextMenuClick(int commandId, int selectedValue, AduGuiControl* contextMenuControl, int selectedData) = 0;&lt;br /&gt;
    public:&lt;br /&gt;
        StageBase* priorityInputChildOwner;&lt;br /&gt;
        uint8_t inputConsumedFlag;&lt;br /&gt;
        uint8_t padOrUnknown1C5[3];&lt;br /&gt;
        AduGuiDialog* xmlDialog;&lt;br /&gt;
&lt;br /&gt;
		Structs::FTList&amp;lt;void&amp;gt; internalOwnerList;&lt;br /&gt;
        Structs::FTVector&amp;lt;StageBase*&amp;gt; processingChildOwners;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool InputConsumedFlag() const {&lt;br /&gt;
            return inputConsumedFlag != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* XmlDialog() const {&lt;br /&gt;
            return xmlDialog;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool HasXmlDialog() const {&lt;br /&gt;
            return xmlDialog != nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        StageBase* PriorityInputChildOwner() const {&lt;br /&gt;
            return priorityInputChildOwner;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;StageBase*&amp;gt;&amp;amp; ProcessingChildOwners() {&lt;br /&gt;
            return processingChildOwners;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const Structs::FTVector&amp;lt;StageBase*&amp;gt;&amp;amp; ProcessingChildOwners() const {&lt;br /&gt;
            return processingChildOwners;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void SetPriorityInputChildOwner(StageBase* child) {&lt;br /&gt;
            priorityInputChildOwner = child;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
        void ClearPriorityInputChildOwnerIf(StageBase* child) {&lt;br /&gt;
            if (PriorityInputChildOwner() == child)&lt;br /&gt;
                SetPriorityInputChildOwner(nullptr);&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
        using AddChildOwnerFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);&lt;br /&gt;
        void AddChildOwner(StageBase* child) {&lt;br /&gt;
            reinterpret_cast&amp;lt;AddChildOwnerFn&amp;gt;(0x005AA720)(this, child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using BringChildOwnerToFrontFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);&lt;br /&gt;
        void BringChildOwnerToFront(StageBase* child) {&lt;br /&gt;
            reinterpret_cast&amp;lt;BringChildOwnerToFrontFn&amp;gt;(0x005AA780)(this, child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using IsOwnerReachableFn = int(FTSDK_FASTCALL*)(StageBase* self);&lt;br /&gt;
        bool IsReachableThroughParentChain() const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;IsOwnerReachableFn&amp;gt;(0x005A99E0)(const_cast&amp;lt;StageBase*&amp;gt;(this)) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using GetControlByCommandIdFn = AduGuiControl* (FTSDK_THISCALL*)(StageBase* self, int32_t commandId, int32_t expectedType);&lt;br /&gt;
        AduGuiControl* GetControlByCommandId(int32_t commandId, int32_t expectedType = -1) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;GetControlByCommandIdFn&amp;gt;(0x005AE7D0)(this, commandId, expectedType);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using GetControlInnerObjectFn = void* (FTSDK_THISCALL*)(StageBase* self, int32_t commandId);&lt;br /&gt;
        void* GetControlInnerObject(int32_t commandId) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;GetControlInnerObjectFn&amp;gt;(0x005AE840)(this, commandId);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class GameDialogStage : public StageBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL AttachOwnerAndCreateUserNotice(ScreenRoot* parent, int32_t id) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL InitStageXml() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL SetSelectedOrActivePopupIndex(int32_t index) = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL CanLeaveStageOrPopup(int32_t reason) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ReservedF0() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL OnBackOrDefaultAction() = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        StageBase* builtinPopupOwners[10];&lt;br /&gt;
        uint8_t xmlLoadedFlag;&lt;br /&gt;
        uint8_t pad211[3];&lt;br /&gt;
&lt;br /&gt;
        int32_t selectedOrActivePopupIndex;&lt;br /&gt;
        void* trayPopupOwner;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsXmlLoaded() const {&lt;br /&gt;
            return xmlLoadedFlag != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        StageBase* BuiltinPopupOwnerAt(int32_t index) const {&lt;br /&gt;
            if (index &amp;lt; 0 || index &amp;gt;= 10)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return builtinPopupOwners[index];&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        using GetOrCreateBuiltinPopupOwnerFn = StageBase * (FTSDK_THISCALL*)(StageBase* self, uint32_t popupIndex);&lt;br /&gt;
        StageBase* GetOrCreateBuiltinPopupOwner(uint32_t popupIndex) {&lt;br /&gt;
            if (popupIndex &amp;gt;= Constants::KnownBuiltinPopupSlotCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return reinterpret_cast&amp;lt;GetOrCreateBuiltinPopupOwnerFn&amp;gt;(0x00498890)(this, popupIndex);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class StageManager : public ScreenInputRoot {&lt;br /&gt;
    public:&lt;br /&gt;
        void* hwnd;&lt;br /&gt;
        int32_t previousStageId;&lt;br /&gt;
        int32_t pendingStageId;&lt;br /&gt;
        int32_t currentStageId;&lt;br /&gt;
        int32_t transitionStageScratchId;&lt;br /&gt;
&lt;br /&gt;
        GameDialogStage* currentStage;&lt;br /&gt;
        uint8_t embeddedDialogStageStorage[0x21C];&lt;br /&gt;
&lt;br /&gt;
        Structs::FTVector&amp;lt;GameDialogStage*&amp;gt; stages;&lt;br /&gt;
&lt;br /&gt;
        void* currentOverlayRaw;&lt;br /&gt;
        Structs::StageOverlayQuad overlayQuad;&lt;br /&gt;
&lt;br /&gt;
        float overlayTimer;&lt;br /&gt;
&lt;br /&gt;
        Structs::StageOwnedResource ownedEffectResource;&lt;br /&gt;
&lt;br /&gt;
        int32_t frameUpdating;&lt;br /&gt;
&lt;br /&gt;
        uint8_t unknown87C[0x10];&lt;br /&gt;
&lt;br /&gt;
        uint8_t delayedReconnectFlag;&lt;br /&gt;
&lt;br /&gt;
        uint8_t pad88D[3];&lt;br /&gt;
&lt;br /&gt;
        float periodicTimer;&lt;br /&gt;
        uint8_t hourlyPacketReadyFlag;&lt;br /&gt;
&lt;br /&gt;
        uint8_t pad895[3];&lt;br /&gt;
&lt;br /&gt;
        float hourlyPacketTimer;&lt;br /&gt;
        int32_t unknown89C;&lt;br /&gt;
&lt;br /&gt;
        GameDialogStage* EmbeddedDialogStage() {&lt;br /&gt;
            return reinterpret_cast&amp;lt;GameDialogStage*&amp;gt;(embeddedDialogStageStorage);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const GameDialogStage* EmbeddedDialogStage() const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;const GameDialogStage*&amp;gt;(embeddedDialogStageStorage);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        GameDialogStage* CurrentStage() const {&lt;br /&gt;
            if (currentStage)&lt;br /&gt;
                return currentStage;&lt;br /&gt;
&lt;br /&gt;
			if (currentStageId &amp;lt; 0 || currentStageId &amp;gt;= stages.Size())&lt;br /&gt;
				return nullptr;&lt;br /&gt;
&lt;br /&gt;
			return stages[currentStageId];&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        bool IsValidStageIndex(uint32_t index) const {&lt;br /&gt;
            return index &amp;lt; Constants::StageOwnerCount;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using RequestStateFn = void(FTSDK_THISCALL*)(&lt;br /&gt;
            StageManager* self,&lt;br /&gt;
            uint32_t state&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        void RequestState(uint32_t state) {&lt;br /&gt;
            if (!IsValidStageIndex(state))&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            reinterpret_cast&amp;lt;RequestStateFn&amp;gt;(0x004AE9A0)(this, state);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using SwitchStateNowFn = void(FTSDK_THISCALL*)(&lt;br /&gt;
            StageManager* self,&lt;br /&gt;
            uint32_t state&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        void SwitchStateNow(uint32_t state) {&lt;br /&gt;
            if (!IsValidStageIndex(state))&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            reinterpret_cast&amp;lt;SwitchStateNowFn&amp;gt;(0x004AD7D0)(this, state);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using InitStagesFn = bool(FTSDK_THISCALL*)(&lt;br /&gt;
            StageManager* self,&lt;br /&gt;
            void* hwnd,&lt;br /&gt;
            int startupParam&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        bool InitStages(void* hwnd, int startupParam) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;InitStagesFn&amp;gt;(0x004B0590)(this, hwnd, startupParam);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    using GetStageManagerFn = StageManager* (FTSDK_CDECL*)();&lt;br /&gt;
    inline StageManager* GetStageManager() {&lt;br /&gt;
        return reinterpret_cast&amp;lt;GetStageManagerFn&amp;gt;(0x004B0520)();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    using ConstructGuiOwnerBaseFn = StageBase* (FTSDK_THISCALL*)(StageBase* self);&lt;br /&gt;
    inline StageBase* ConstructStageBase(StageBase* memory) {&lt;br /&gt;
        return reinterpret_cast&amp;lt;ConstructGuiOwnerBaseFn&amp;gt;(0x005AEC40)(memory);&lt;br /&gt;
    }&lt;br /&gt;
     &lt;br /&gt;
    using ConstructGameDialogStageFn = GameDialogStage* (FTSDK_THISCALL*)(GameDialogStage* self);&lt;br /&gt;
    inline GameDialogStage* ConstructGameDialogStage(GameDialogStage* memory) {&lt;br /&gt;
        return reinterpret_cast&amp;lt;ConstructGameDialogStageFn&amp;gt;(0x004987C0)(memory);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Example Usage ==&lt;br /&gt;
* [[Custom Client UI with StageManager Input|Custom client UI with StageManager input forwarding]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Client]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Custom_Client_UI_with_StageManager_Input&amp;diff=292</id>
		<title>Custom Client UI with StageManager Input</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Custom_Client_UI_with_StageManager_Input&amp;diff=292"/>
		<updated>2026-05-11T16:46:09Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;== Overview == This page shows a working custom UI proof of concept for the Fantasy Tennis client SDK. The goal is to create a separate XML based GUI owner, attach it to the current stage owner and route input to it without replacing or hijacking an existing built-in popup.  The important part is that the custom owner can be loaded and displayed like a normal client GUI object, but input still has to reach it through the client input path. For that reason the StageManage...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Overview ==&lt;br /&gt;
This page shows a working custom UI proof of concept for the Fantasy Tennis client SDK. The goal is to create a separate XML based GUI owner, attach it to the current stage owner and route input to it without replacing or hijacking an existing built-in popup.&lt;br /&gt;
&lt;br /&gt;
The important part is that the custom owner can be loaded and displayed like a normal client GUI object, but input still has to reach it through the client input path. For that reason the StageManager input handler is hooked and the custom owner is given a chance to process input before normal stage input continues.&lt;br /&gt;
&lt;br /&gt;
This test confirms that a custom GUI can:&lt;br /&gt;
* create its own FTGameDialogOwner;&lt;br /&gt;
* attach to the current stage owner;&lt;br /&gt;
* load a custom XML dialog;&lt;br /&gt;
* bind buttons and edit boxes through GuiBind;&lt;br /&gt;
* receive XML callback events;&lt;br /&gt;
* read text from an EditBox;&lt;br /&gt;
* forward mouse and keyboard input through the StageManager input path.&lt;br /&gt;
&lt;br /&gt;
== StageManager Input Hook ==&lt;br /&gt;
The custom GUI needs to receive the same input messages as normal client GUI owners. The client already routes window messages through the StageManager and then into the active GUI owner tree, so the test hooks StageManager::HandleInput and forwards input to the custom owner before falling back to the original client handler.&lt;br /&gt;
&lt;br /&gt;
A custom GUI owner attached manually to the current stage can render and receive callbacks, but keyboard and mouse input must still be forwarded through the same owner input path.&lt;br /&gt;
&lt;br /&gt;
The hook below replaces the StageManager HandleInput vtable entry. It keeps the original behavior intact, but gives the custom UI owner a chance to handle input first when it is open and reachable. If the custom UI consumes the input, the hook returns true. Otherwise the original StageManager input handler is called.&lt;br /&gt;
&lt;br /&gt;
Internal modal dialogs are allowed to win first, because they are part of the original client state and should not be bypassed by the custom test UI.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
// Function type for StageManager::HandleInput.&lt;br /&gt;
// The hook uses __fastcall because thiscall is represented as ECX=this,&lt;br /&gt;
// with the dummy EDX parameter kept in the hook signature.&lt;br /&gt;
using StageManagerHandleInputFn = bool(__fastcall*)(&lt;br /&gt;
    FTSDK::StageManager* self,&lt;br /&gt;
    void* edx,&lt;br /&gt;
    uint32_t msg,&lt;br /&gt;
    int32_t wParam,&lt;br /&gt;
    int32_t lParam&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
StageManagerHandleInputFn o_StageManagerHandleInput = nullptr;&lt;br /&gt;
&lt;br /&gt;
bool __fastcall hk_StageManagerHandleInput(&lt;br /&gt;
    FTSDK::StageManager* self,&lt;br /&gt;
    void* edx,&lt;br /&gt;
    uint32_t msg,&lt;br /&gt;
    int32_t wParam,&lt;br /&gt;
    int32_t lParam&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
g_StageManager = std::make_unique&amp;lt;ShadowVTManager&amp;gt;();&lt;br /&gt;
&lt;br /&gt;
// Get the global StageManager instance.&lt;br /&gt;
auto* manager = FTSDK::GetStageManager();&lt;br /&gt;
if (manager) {&lt;br /&gt;
    // Hook StageManager::HandleInput.&lt;br /&gt;
    // In the currently analyzed client build this is vtable index 38.&lt;br /&gt;
    g_StageManager-&amp;gt;Setup(manager);&lt;br /&gt;
    g_StageManager-&amp;gt;Hook(38, hk_StageManagerHandleInput);&lt;br /&gt;
    o_StageManagerHandleInput =&lt;br /&gt;
        g_StageManager-&amp;gt;GetOriginal&amp;lt;StageManagerHandleInputFn&amp;gt;(38);&lt;br /&gt;
&lt;br /&gt;
    std::printf(&lt;br /&gt;
        &amp;quot;[StageManagerHook] hooked manager=%p vt=%p index=38 original=%p hook=%p\n&amp;quot;,&lt;br /&gt;
        manager,&lt;br /&gt;
        manager-&amp;gt;VTable(),&lt;br /&gt;
        o_StageManagerHandleInput,&lt;br /&gt;
        hk_StageManagerHandleInput&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
bool __fastcall hk_StageManagerHandleInput(&lt;br /&gt;
    FTSDK::StageManager* self,&lt;br /&gt;
    void* edx,&lt;br /&gt;
    uint32_t msg,&lt;br /&gt;
    int32_t wParam,&lt;br /&gt;
    int32_t lParam&lt;br /&gt;
) {&lt;br /&gt;
    FUNCTION_GUARD;&lt;br /&gt;
&lt;br /&gt;
    // Let real internal modal state win.&lt;br /&gt;
    if (self &amp;amp;&amp;amp; self-&amp;gt;HasModalDialog()) {&lt;br /&gt;
        return o_StageManagerHandleInput(self, edx, msg, wParam, lParam);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Give the custom owner a chance first.&lt;br /&gt;
    // If it consumes the input, stop here.&lt;br /&gt;
    if (CustomGuiTest::HandleInput(msg, wParam, lParam)) {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Normal game/stage input continues.&lt;br /&gt;
    return o_StageManagerHandleInput(self, edx, msg, wParam, lParam);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Custom GUI Definition ==&lt;br /&gt;
The custom UI uses its own FTGameDialogOwner instance instead of reusing one of the built-in popup slots. The owner is backed by local storage, constructed with the original client constructor and later attached to the current stage owner.&lt;br /&gt;
&lt;br /&gt;
The XML controls are connected through GuiBind entries. Buttons use normal ButtonClick events, while the EditBox sends EditBoxChange events while typing. The callback receives the command id, event type and a control pointer in param for the EditBox event.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
namespace CustomGuiTest {&lt;br /&gt;
&lt;br /&gt;
    struct CustomGuiContext {&lt;br /&gt;
        FTSDK::FTGameDialogOwner* owner = nullptr;&lt;br /&gt;
        FTSDK::FTGameDialogOwner* parentStageOwner = nullptr;&lt;br /&gt;
        bool loaded = false;&lt;br /&gt;
        bool opened = false;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static CustomGuiContext g_ctx{};&lt;br /&gt;
&lt;br /&gt;
    // Keep this larger than the known base/game-dialog owner size.&lt;br /&gt;
    // The XML/dialog/control objects are allocated by the client, not inside this buffer.&lt;br /&gt;
    alignas(16) static std::uint8_t g_customOwnerStorage[0x800]{};&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::GuiBind g_customBinds[] = {&lt;br /&gt;
        { 1000, &amp;quot;btTest&amp;quot;,         FTSDK::GuiControlType::Button },&lt;br /&gt;
        { 1001, &amp;quot;btClose&amp;quot;,        FTSDK::GuiControlType::Button },&lt;br /&gt;
        { 1002, &amp;quot;stCustomWindow&amp;quot;, FTSDK::GuiControlType::Static },&lt;br /&gt;
        { 1003, &amp;quot;stCustomTop&amp;quot;,    FTSDK::GuiControlType::Static },&lt;br /&gt;
        { 1004, &amp;quot;stCustomInfo&amp;quot;,   FTSDK::GuiControlType::Static },&lt;br /&gt;
        { 1005, &amp;quot;ebCustomInput&amp;quot;,  FTSDK::GuiControlType::EditBox },&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static constexpr int32_t CustomBindCount =&lt;br /&gt;
        static_cast&amp;lt;int32_t&amp;gt;(sizeof(g_customBinds) / sizeof(g_customBinds[0]));&lt;br /&gt;
&lt;br /&gt;
    void CloseCustomGui();&lt;br /&gt;
    bool IsOpen();&lt;br /&gt;
&lt;br /&gt;
    void FTSDK_STDCALL CustomGuiCallback(&lt;br /&gt;
        FTSDK::GuiEventType eventType,&lt;br /&gt;
        int commandId,&lt;br /&gt;
        int param,&lt;br /&gt;
        void* userData&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
} // namespace CustomGuiTest&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Loading and Opening ==&lt;br /&gt;
Opening the custom UI follows the same basic order as the built-in GUI owners:&lt;br /&gt;
&lt;br /&gt;
* construct the owner;&lt;br /&gt;
* attach it to the current stage owner;&lt;br /&gt;
* load the XML file;&lt;br /&gt;
* set the XML callback user data;&lt;br /&gt;
* run the post-load owner setup;&lt;br /&gt;
* center the owner;&lt;br /&gt;
* open and activate it;&lt;br /&gt;
* bring it to the front.&lt;br /&gt;
&lt;br /&gt;
The owner is only reused while the current stage owner stays the same. If the stage changes, the test code refuses to reuse the old owner because the parent owner relationship would no longer be valid.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
namespace CustomGuiTest {&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::FTGameDialogOwner* CreateCustomGuiOwner(&lt;br /&gt;
        FTSDK::FTGameDialogOwner* parentStageOwner&lt;br /&gt;
    ) {&lt;br /&gt;
        if (!parentStageOwner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] Create failed: parentStageOwner=null\n&amp;quot;);&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        std::memset(g_customOwnerStorage, 0, sizeof(g_customOwnerStorage));&lt;br /&gt;
&lt;br /&gt;
        auto* owner = FTSDK::ConstructGameDialogOwner(&lt;br /&gt;
            reinterpret_cast&amp;lt;FTSDK::FTGameDialogOwner*&amp;gt;(g_customOwnerStorage)&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (!owner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] ConstructGameDialogOwner returned null\n&amp;quot;);&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        g_ctx.owner = owner;&lt;br /&gt;
        g_ctx.parentStageOwner = parentStageOwner;&lt;br /&gt;
        g_ctx.loaded = false;&lt;br /&gt;
        g_ctx.opened = false;&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] constructed owner=%p vt=%p parentStageOwner=%p\n&amp;quot;,&lt;br /&gt;
            owner,&lt;br /&gt;
            owner-&amp;gt;VTable(),&lt;br /&gt;
            parentStageOwner&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        // This attaches the custom owner to the current stage owner:&lt;br /&gt;
        //   parent = +0xB4&lt;br /&gt;
        //   parent-&amp;gt;ActiveChildOwners += owner&lt;br /&gt;
        owner-&amp;gt;InitWithParentOwner(parentStageOwner);&lt;br /&gt;
&lt;br /&gt;
        const bool loaded = owner-&amp;gt;LoadGui(&lt;br /&gt;
            &amp;quot;Gui_Test.xml&amp;quot;,&lt;br /&gt;
            g_customBinds,&lt;br /&gt;
            CustomBindCount,&lt;br /&gt;
            CustomGuiCallback,&lt;br /&gt;
            false&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] LoadGui returned=%d owner=%p xml=%p\n&amp;quot;,&lt;br /&gt;
            loaded ? 1 : 0,&lt;br /&gt;
            owner,&lt;br /&gt;
            owner-&amp;gt;XmlDialog()&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (!loaded || !owner-&amp;gt;XmlDialog()) {&lt;br /&gt;
            return owner;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Important: force callback userData to our context.&lt;br /&gt;
        // LoadGui only receives callback, not explicit userData in this signature.&lt;br /&gt;
        owner-&amp;gt;XmlDialog()-&amp;gt;SetCallback(CustomGuiCallback, &amp;amp;g_ctx);&lt;br /&gt;
&lt;br /&gt;
        // Match the built-in popup pattern:&lt;br /&gt;
        // ranking init ends with vt+0x50. For base owners this clears +0xBC.&lt;br /&gt;
        owner-&amp;gt;OnPostGuiLoad();&lt;br /&gt;
&lt;br /&gt;
        // Optional. Use only if the XML/root rect supports it.&lt;br /&gt;
        owner-&amp;gt;SetOwnerRectCenteredInParent();&lt;br /&gt;
&lt;br /&gt;
        g_ctx.loaded = true;&lt;br /&gt;
&lt;br /&gt;
        return owner;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    static FTSDK::FTGameDialogOwner* GetOrCreateCustomGuiOwner() {&lt;br /&gt;
        auto* manager = FTSDK::GetStageManager();&lt;br /&gt;
        auto* parentStageOwner = manager ? manager-&amp;gt;CurrentStageOwner() : nullptr;&lt;br /&gt;
&lt;br /&gt;
        if (!manager || !parentStageOwner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] no manager/current stage owner\n&amp;quot;);&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (g_ctx.owner) {&lt;br /&gt;
            // InitWithParentOwner only attaches if parent is currently null.&lt;br /&gt;
            // Do not reuse this test owner after a state switch yet.&lt;br /&gt;
            if (g_ctx.parentStageOwner != parentStageOwner) {&lt;br /&gt;
                std::printf(&lt;br /&gt;
                    &amp;quot;[CustomGui] current stage owner changed. oldParent=%p newParent=%p. &amp;quot;&lt;br /&gt;
                    &amp;quot;Not reusing custom owner in this test build.\n&amp;quot;,&lt;br /&gt;
                    g_ctx.parentStageOwner,&lt;br /&gt;
                    parentStageOwner&lt;br /&gt;
                );&lt;br /&gt;
                return nullptr;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return g_ctx.owner;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return CreateCustomGuiOwner(parentStageOwner);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    void OpenOrShowCustomGui() {&lt;br /&gt;
        auto* manager = FTSDK::GetStageManager();&lt;br /&gt;
        auto* parentStageOwner = manager ? manager-&amp;gt;CurrentStageOwner() : nullptr;&lt;br /&gt;
&lt;br /&gt;
        std::printf(&amp;quot;========== OpenOrShowCustomGui ==========\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        if (!manager || !parentStageOwner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] manager/current stage owner missing\n&amp;quot;);&lt;br /&gt;
            std::printf(&amp;quot;=========================================\n&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] manager=%p state=%d parentStageOwner=%p parentXml=%p\n&amp;quot;,&lt;br /&gt;
            manager,&lt;br /&gt;
            manager-&amp;gt;CurrentState(),&lt;br /&gt;
            parentStageOwner,&lt;br /&gt;
            parentStageOwner-&amp;gt;XmlDialog()&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        auto* owner = GetOrCreateCustomGuiOwner();&lt;br /&gt;
&lt;br /&gt;
        if (!owner) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] owner unavailable\n&amp;quot;);&lt;br /&gt;
            std::printf(&amp;quot;=========================================\n&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!g_ctx.loaded || !owner-&amp;gt;XmlDialog()) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] owner exists but GUI is not loaded\n&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        owner-&amp;gt;OpenOwner();&lt;br /&gt;
&lt;br /&gt;
        // This sets +0xBC=1 and brings it to the front through the parent chain.&lt;br /&gt;
        owner-&amp;gt;ActivateOwnerProcessing();&lt;br /&gt;
        owner-&amp;gt;SetParentChainEnabled(true);&lt;br /&gt;
&lt;br /&gt;
        // Extra explicit fronting is harmless and useful while testing.&lt;br /&gt;
        if (owner-&amp;gt;ParentOwner()) {&lt;br /&gt;
            owner-&amp;gt;ParentOwner()-&amp;gt;BringChildOwnerToFront(owner);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        g_ctx.opened = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    void CloseCustomGui() {&lt;br /&gt;
        auto* owner = g_ctx.owner;&lt;br /&gt;
&lt;br /&gt;
        if (!owner)&lt;br /&gt;
            return;&lt;br /&gt;
&lt;br /&gt;
        owner-&amp;gt;CloseOwner();&lt;br /&gt;
        owner-&amp;gt;OnPostGuiLoad();&lt;br /&gt;
&lt;br /&gt;
        g_ctx.opened = false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bool IsOpen() {&lt;br /&gt;
        return g_ctx.opened;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bool HandleInput(uint32_t msg, int32_t wParam, int32_t lParam) {&lt;br /&gt;
        auto* owner = g_ctx.owner;&lt;br /&gt;
&lt;br /&gt;
        if (!g_ctx.opened || !owner || !owner-&amp;gt;XmlDialog())&lt;br /&gt;
            return false;&lt;br /&gt;
&lt;br /&gt;
        if (!owner-&amp;gt;IsProcessEnabled() || !owner-&amp;gt;IsOwnerModalOrOpenFlag())&lt;br /&gt;
            return false;&lt;br /&gt;
&lt;br /&gt;
        if (!owner-&amp;gt;IsReachableThroughParentChain())&lt;br /&gt;
            return false;&lt;br /&gt;
&lt;br /&gt;
        return owner-&amp;gt;HandleInput(msg, wParam, lParam);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
} // namespace CustomGuiTest&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Callback Behavior ==&lt;br /&gt;
The XML callback is used for button clicks and EditBox events. The Test button reads the current text from the custom EditBox and forwards it to the test logic. The Close button closes the custom owner and resets the opened state.&lt;br /&gt;
&lt;br /&gt;
For EditBoxChange events, param points to the EditBox control that changed. The current test SDK exposes the wide text pointer at the confirmed EditBox offset, which allows the callback to print or forward the typed text.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
void FTSDK_STDCALL CustomGuiCallback(&lt;br /&gt;
    FTSDK::GuiEventType eventType,&lt;br /&gt;
    int commandId,&lt;br /&gt;
    int param,&lt;br /&gt;
    void* userData&lt;br /&gt;
) {&lt;br /&gt;
    auto* ctx = reinterpret_cast&amp;lt;CustomGuiContext*&amp;gt;(userData);&lt;br /&gt;
    auto* owner = ctx ? ctx-&amp;gt;owner : nullptr;&lt;br /&gt;
&lt;br /&gt;
    std::printf(&lt;br /&gt;
        &amp;quot;[CustomGui] callback eventType=%d commandId=%d param=%p ctx=%p owner=%p\n&amp;quot;,&lt;br /&gt;
        static_cast&amp;lt;int&amp;gt;(eventType),&lt;br /&gt;
        commandId,&lt;br /&gt;
        reinterpret_cast&amp;lt;void*&amp;gt;(param),&lt;br /&gt;
        userData,&lt;br /&gt;
        owner&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    if (!ctx || !owner)&lt;br /&gt;
        return;&lt;br /&gt;
&lt;br /&gt;
    switch (commandId) {&lt;br /&gt;
    case 1000:&lt;br /&gt;
        if (eventType == FTSDK::GuiEventType::ButtonClick) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] btTest clicked\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
            auto* inputControl = owner-&amp;gt;GetControlByCommandId(&lt;br /&gt;
                1005,&lt;br /&gt;
                FTSDK::GuiControlType::EditBox&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            if (!inputControl) {&lt;br /&gt;
                std::printf(&amp;quot;[CustomGui] EditBox 1005 not found as EditBox, trying without type filter\n&amp;quot;);&lt;br /&gt;
                inputControl = owner-&amp;gt;GetControlByCommandId(1005, -1);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (!inputControl) {&lt;br /&gt;
                std::printf(&amp;quot;[CustomGui] input control commandId=1005 not found\n&amp;quot;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            auto* input = reinterpret_cast&amp;lt;FTSDK::AduGuiEditBox*&amp;gt;(inputControl);&lt;br /&gt;
&lt;br /&gt;
            std::printf(&lt;br /&gt;
                &amp;quot;[CustomGui] input=%p vt=%p name=\&amp;quot;%s\&amp;quot; cmd=%d type=%d\n&amp;quot;,&lt;br /&gt;
                input,&lt;br /&gt;
                input-&amp;gt;VTable(),&lt;br /&gt;
                input-&amp;gt;Name().Data(),&lt;br /&gt;
                input-&amp;gt;CommandId(),&lt;br /&gt;
                input-&amp;gt;ControlTypeId()&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            input-&amp;gt;DumpText();&lt;br /&gt;
&lt;br /&gt;
            const wchar_t* text = input-&amp;gt;TextW();&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] forwarding text: \&amp;quot;%ls\&amp;quot;\n&amp;quot;, text);&lt;br /&gt;
&lt;br /&gt;
            // Example: send text to your own logic here.&lt;br /&gt;
            // SendCustomCommand(text);&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
&lt;br /&gt;
    case 1001:&lt;br /&gt;
        if (eventType == FTSDK::GuiEventType::ButtonClick) {&lt;br /&gt;
            std::printf(&amp;quot;[CustomGui] btClose clicked -&amp;gt; closing custom GUI\n&amp;quot;);&lt;br /&gt;
            CloseCustomGui();&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
&lt;br /&gt;
    case 1005:&lt;br /&gt;
        // Do not dump raw memory here.&lt;br /&gt;
        // This can fire while the editbox is changing internal state.&lt;br /&gt;
        if (eventType == FTSDK::GuiEventType::EditBoxChange) {&lt;br /&gt;
            auto* input = reinterpret_cast&amp;lt;FTSDK::AduGuiEditBox*&amp;gt;(param);&lt;br /&gt;
&lt;br /&gt;
            if (input) {&lt;br /&gt;
                std::printf(&lt;br /&gt;
                    &amp;quot;[CustomGui] input changed input=%p textPtr=%p lenMaybe=%d text=\&amp;quot;%ls\&amp;quot;\n&amp;quot;,&lt;br /&gt;
                    input,&lt;br /&gt;
                    input-&amp;gt;TextW(),&lt;br /&gt;
                    input-&amp;gt;TextField21C(),&lt;br /&gt;
                    input-&amp;gt;TextW()&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
&lt;br /&gt;
    default:&lt;br /&gt;
        std::printf(&lt;br /&gt;
            &amp;quot;[CustomGui] unhandled commandId=%d eventType=%d\n&amp;quot;,&lt;br /&gt;
            commandId,&lt;br /&gt;
            static_cast&amp;lt;int&amp;gt;(eventType)&lt;br /&gt;
        );&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Do not call owner-&amp;gt;DispatchGuiEvent(...) here.&lt;br /&gt;
    // This callback is usually reached from dispatch already and forwarding&lt;br /&gt;
    // back into the same owner can recurse.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== XML Dialog Sample ==&lt;br /&gt;
The dialog below is a minimal XML GUI used by the test. It defines a small custom window with two buttons and one EditBox. The names must match the GuiBind table in the C++ code, otherwise the controls will not be assigned the expected command ids.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;Dialog x=&amp;quot;0&amp;quot; y=&amp;quot;0&amp;quot; w=&amp;quot;1024&amp;quot; h=&amp;quot;768&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;Image Name=&amp;quot;DEFAULT&amp;quot; du=&amp;quot;0&amp;quot; dv=&amp;quot;0&amp;quot;/&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;DEFAULT&amp;quot; Type=&amp;quot;Static&amp;quot; Enable=&amp;quot;Yes&amp;quot; Font=&amp;quot;&amp;quot; x=&amp;quot;0&amp;quot; y=&amp;quot;0&amp;quot; w=&amp;quot;0&amp;quot; h=&amp;quot;0&amp;quot; dx=&amp;quot;0&amp;quot; dy=&amp;quot;0&amp;quot; Text=&amp;quot;&amp;quot; TextID=&amp;quot;&amp;quot; TextFx=&amp;quot;&amp;quot; TextAlignX=&amp;quot;CENTER&amp;quot; TextAlignY=&amp;quot;CENTER&amp;quot; TextMargin=&amp;quot;&amp;quot; Debug=&amp;quot;No&amp;quot;/&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomWindow&amp;quot; Type=&amp;quot;Static&amp;quot; x=&amp;quot;18&amp;quot; y=&amp;quot;42&amp;quot; w=&amp;quot;330&amp;quot; h=&amp;quot;190&amp;quot; Text=&amp;quot;JFTSE Custom GUI&amp;quot; TextID=&amp;quot;&amp;quot; TextFx=&amp;quot;Shadow&amp;quot; TextAlignX=&amp;quot;CENTER&amp;quot; TextAlignY=&amp;quot;TOP&amp;quot; TextMargin=&amp;quot;0,8,0,0&amp;quot; Debug=&amp;quot;No&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State Image=&amp;quot;cmnWindow&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomLineTop&amp;quot; Type=&amp;quot;Static&amp;quot; x=&amp;quot;42&amp;quot; y=&amp;quot;80&amp;quot; w=&amp;quot;282&amp;quot; h=&amp;quot;1&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State Image=&amp;quot;cmnWindowLine&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomTop&amp;quot; Type=&amp;quot;Static&amp;quot; x=&amp;quot;40&amp;quot; y=&amp;quot;96&amp;quot; w=&amp;quot;286&amp;quot; h=&amp;quot;24&amp;quot; Text=&amp;quot;Enter text and press Test&amp;quot; TextID=&amp;quot;&amp;quot; TextAlignX=&amp;quot;CENTER&amp;quot; TextAlignY=&amp;quot;CENTER&amp;quot; Debug=&amp;quot;No&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State TextColor=&amp;quot;255,0,166,165&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomInfo&amp;quot; Type=&amp;quot;Static&amp;quot; x=&amp;quot;40&amp;quot; y=&amp;quot;124&amp;quot; w=&amp;quot;286&amp;quot; h=&amp;quot;22&amp;quot; Text=&amp;quot;Gui_Test.xml&amp;quot; TextID=&amp;quot;&amp;quot; TextAlignX=&amp;quot;CENTER&amp;quot; TextAlignY=&amp;quot;CENTER&amp;quot; Debug=&amp;quot;No&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State TextColor=&amp;quot;255,30,30,30&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;ebCustomInput&amp;quot; Type=&amp;quot;EditBox&amp;quot; x=&amp;quot;58&amp;quot; y=&amp;quot;154&amp;quot; w=&amp;quot;250&amp;quot; h=&amp;quot;20&amp;quot; Text=&amp;quot;&amp;quot; TextID=&amp;quot;&amp;quot; Debug=&amp;quot;No&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State Image=&amp;quot;cmnBlank2&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;stCustomLineBottom&amp;quot; Type=&amp;quot;Static&amp;quot; x=&amp;quot;42&amp;quot; y=&amp;quot;184&amp;quot; w=&amp;quot;282&amp;quot; h=&amp;quot;1&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State Image=&amp;quot;cmnWindowLine&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;DEFAULT&amp;quot; Type=&amp;quot;Button&amp;quot; TextFx=&amp;quot;Shadow&amp;quot;&amp;gt; &lt;br /&gt;
        &amp;lt;State Name=&amp;quot;All&amp;quot; Image=&amp;quot;cmnWindowBtn&amp;quot;/&amp;gt;  &lt;br /&gt;
        &amp;lt;State Name=&amp;quot;Over&amp;quot; Image=&amp;quot;cmnShopBuyBtn&amp;quot;/&amp;gt;  &lt;br /&gt;
        &amp;lt;State Name=&amp;quot;Pressed&amp;quot; Color=&amp;quot;255,155,155,155&amp;quot;/&amp;gt; &lt;br /&gt;
    &amp;lt;/Control&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;btTest&amp;quot; Type=&amp;quot;Button&amp;quot; x=&amp;quot;58&amp;quot; y=&amp;quot;194&amp;quot; w=&amp;quot;120&amp;quot; h=&amp;quot;22&amp;quot; Text=&amp;quot;Test&amp;quot; TextID=&amp;quot;&amp;quot;/&amp;gt;  &lt;br /&gt;
    &amp;lt;Control Name=&amp;quot;btClose&amp;quot; Type=&amp;quot;Button&amp;quot; x=&amp;quot;188&amp;quot; y=&amp;quot;194&amp;quot; w=&amp;quot;120&amp;quot; h=&amp;quot;22&amp;quot; Text=&amp;quot;Close&amp;quot; TextID=&amp;quot;&amp;quot;/&amp;gt; &lt;br /&gt;
&amp;lt;/Dialog&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Notes ==&lt;br /&gt;
This is test code for documenting the client GUI path, not final UI framework code. The hardcoded function addresses, vtable indices, object sizes and offsets are specific to the currently analyzed client build.&lt;br /&gt;
&lt;br /&gt;
The most important result is that custom UI does not need to overwrite a built-in popup owner. A separate owner can be attached to the current stage owner, loaded from XML and made interactive by forwarding input through the StageManager path.&lt;br /&gt;
&lt;br /&gt;
[[Category:Client]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=FT_SDK&amp;diff=291</id>
		<title>FT SDK</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=FT_SDK&amp;diff=291"/>
		<updated>2026-05-11T16:25:41Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;== Overview == This page documents the current reverse engineering progress for the Fantasy Tennis client-side SDK used by JFTSE tooling. The SDK maps known client structures, virtual functions, GUI objects, stage management, XML dialog loading, popup ownership and input routing into C++ wrappers that can be used from injected debugging or extension code.  The current focus is the client GUI and StageManager layer. Confirmed areas include the fixed 24-stage owner model,...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Overview ==&lt;br /&gt;
This page documents the current reverse engineering progress for the Fantasy Tennis client-side SDK used by JFTSE tooling. The SDK maps known client structures, virtual functions, GUI objects, stage management, XML dialog loading, popup ownership and input routing into C++ wrappers that can be used from injected debugging or extension code.&lt;br /&gt;
&lt;br /&gt;
The current focus is the client GUI and StageManager layer. Confirmed areas include the fixed 24-stage owner model, current-stage GUI attachment, embedded modal/input owner behavior, built-in popup factory behavior, owner lifecycle functions, ADU GUI object and control layouts, XML control binding, callback dispatch, StageManager-based input forwarding and EditBox text extraction.&lt;br /&gt;
&lt;br /&gt;
This page is the current reverse engineering handoff for the SDK. Names, offsets and layouts are updated as behavior is verified through disassembly, runtime tests and real client call sites.&lt;br /&gt;
&lt;br /&gt;
== Scope ==&lt;br /&gt;
* StageManager state, input routing and owner lifecycle.&lt;br /&gt;
* Fixed stage owner list and current-stage owner access.&lt;br /&gt;
* Safe custom GUI attachment to the current stage owner.&lt;br /&gt;
* Built-in popup owner factory and popup slot behavior.&lt;br /&gt;
* ADU GUI object, dialog and control layouts.&lt;br /&gt;
* XML GUI binding through command IDs and control types.&lt;br /&gt;
* GUI callback dispatch, including callback user data and event parameters.&lt;br /&gt;
* Confirmed edit-box text access through the wide-character text buffer.&lt;br /&gt;
* Utility wrappers for dumping owners, dialogs, controls and popup state.&lt;br /&gt;
&lt;br /&gt;
== Stability Notes ==&lt;br /&gt;
Offsets and function addresses are version specific to the currently analyzed Fantasy Tennis client build. Names describe current understanding, not original source names. Any field marked unknown, maybe or conservative should be treated as subject to change until confirmed by call sites, runtime tests or additional disassembly.&lt;br /&gt;
&lt;br /&gt;
Important caveat: custom GUI work should not add another stage. The client uses a fixed set of 24 stage owners. Custom UI should attach to the current stage owner, an existing popup path or the embedded modal/input owner where appropriate.&lt;br /&gt;
&lt;br /&gt;
== Current SDK Header ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
#pragma once&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;cstdint&amp;gt;&lt;br /&gt;
#include &amp;lt;cstddef&amp;gt;&lt;br /&gt;
#include &amp;lt;cstdio&amp;gt;&lt;br /&gt;
#include &amp;lt;cstring&amp;gt;&lt;br /&gt;
#include &amp;lt;type_traits&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define FTSDK_THISCALL __thiscall&lt;br /&gt;
#define FTSDK_CDECL    __cdecl&lt;br /&gt;
#define FTSDK_STDCALL  __stdcall&lt;br /&gt;
#define FTSDK_FASTCALL __fastcall&lt;br /&gt;
&lt;br /&gt;
namespace FTSDK {&lt;br /&gt;
&lt;br /&gt;
    struct GuiBind {&lt;br /&gt;
        int32_t commandId;&lt;br /&gt;
        const char* controlName;&lt;br /&gt;
        int32_t controlTypeId;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum GuiEventType : int32_t {&lt;br /&gt;
        ButtonClick = 0,&lt;br /&gt;
        ButtonOver = 1,&lt;br /&gt;
&lt;br /&gt;
        ComboBoxOpen = 2,&lt;br /&gt;
        ComboBoxClose = 3,&lt;br /&gt;
        ComboBoxSelectChange = 4,&lt;br /&gt;
&lt;br /&gt;
        RadioButtonChange = 5,&lt;br /&gt;
        CheckBoxChange = 6,&lt;br /&gt;
&lt;br /&gt;
        SliderValueChange = 7,&lt;br /&gt;
&lt;br /&gt;
        EditBoxEnter = 8,&lt;br /&gt;
        EditBoxChange = 9,&lt;br /&gt;
        EditBoxTAB = 10,&lt;br /&gt;
        EditBoxESC = 11,&lt;br /&gt;
        EditBoxCharLimit = 12,&lt;br /&gt;
        EditBoxKeyUp = 13,&lt;br /&gt;
        EditBoxKeyDown = 14,&lt;br /&gt;
&lt;br /&gt;
        ListBoxDBClick = 15,&lt;br /&gt;
        ListBoxSelect = 16,&lt;br /&gt;
        ListBoxSelectEnd = 17,&lt;br /&gt;
&lt;br /&gt;
        ContextMenuClick = 18,&lt;br /&gt;
        ScrollBarPosChange = 19&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    enum GuiControlType : int32_t {&lt;br /&gt;
        Static = 0,&lt;br /&gt;
        Button = 1,&lt;br /&gt;
        CheckBox = 2,&lt;br /&gt;
        RadioButton = 3,&lt;br /&gt;
        ComboBox = 4,&lt;br /&gt;
        Slider = 5,&lt;br /&gt;
        Gauge = 6,&lt;br /&gt;
        EditBox = 7,&lt;br /&gt;
        IMEEditBox = 8,&lt;br /&gt;
        ListBox = 9,&lt;br /&gt;
        ScrollBar = 10,&lt;br /&gt;
        ContextMenu = 11&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    using GuiCallbackFn = void(FTSDK_STDCALL*)(&lt;br /&gt;
        GuiEventType eventType,&lt;br /&gt;
        int commandId,&lt;br /&gt;
        int param,&lt;br /&gt;
        void* userData&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    struct RectI {&lt;br /&gt;
        int32_t left;&lt;br /&gt;
        int32_t top;&lt;br /&gt;
        int32_t right;&lt;br /&gt;
        int32_t bottom;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    struct FTString {&lt;br /&gt;
        uint32_t unknown_00;&lt;br /&gt;
&lt;br /&gt;
        union {&lt;br /&gt;
            char inlineBuffer[16];&lt;br /&gt;
            char* heapPtr;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        uint32_t length;&lt;br /&gt;
        uint32_t capacity;&lt;br /&gt;
&lt;br /&gt;
        bool IsInline() const {&lt;br /&gt;
            return capacity &amp;lt;= 15;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsValid() const {&lt;br /&gt;
            if (length &amp;gt; capacity || length &amp;gt;= 4096)&lt;br /&gt;
                return false;&lt;br /&gt;
&lt;br /&gt;
            if (!IsInline() &amp;amp;&amp;amp; !heapPtr)&lt;br /&gt;
                return false;&lt;br /&gt;
&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const char* Data() const {&lt;br /&gt;
            if (!IsValid())&lt;br /&gt;
                return &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
            return IsInline() ? inlineBuffer : heapPtr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Length() const {&lt;br /&gt;
            return IsValid() ? static_cast&amp;lt;int32_t&amp;gt;(length) : 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool Equals(const char* text) const {&lt;br /&gt;
            if (!text || !IsValid())&lt;br /&gt;
                return false;&lt;br /&gt;
&lt;br /&gt;
            const size_t textLen = std::strlen(text);&lt;br /&gt;
            return textLen == length &amp;amp;&amp;amp; std::memcmp(Data(), text, length) == 0;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static_assert(sizeof(GuiBind) == 0x0C, &amp;quot;GuiBind must be 0x0C&amp;quot;);&lt;br /&gt;
    static_assert(sizeof(RectI) == 0x10, &amp;quot;RectI must be 0x10&amp;quot;);&lt;br /&gt;
    static_assert(sizeof(FTString) == 0x1C, &amp;quot;FTString must be 0x1C&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    struct AduGuiStateSet;&lt;br /&gt;
&lt;br /&gt;
    template &amp;lt;typename T&amp;gt;&lt;br /&gt;
    struct FTContainerVector {&lt;br /&gt;
        void* containerBase;&lt;br /&gt;
        T* first;&lt;br /&gt;
        T* last;&lt;br /&gt;
        T* capacityEnd;&lt;br /&gt;
&lt;br /&gt;
        int32_t size() const {&lt;br /&gt;
            if (!first || !last || last &amp;lt; first)&lt;br /&gt;
                return 0;&lt;br /&gt;
&lt;br /&gt;
            return static_cast&amp;lt;int32_t&amp;gt;(last - first);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t capacity() const {&lt;br /&gt;
            if (!first || !capacityEnd || capacityEnd &amp;lt; first)&lt;br /&gt;
                return 0;&lt;br /&gt;
&lt;br /&gt;
            return static_cast&amp;lt;int32_t&amp;gt;(capacityEnd - first);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool empty() const {&lt;br /&gt;
            return size() == 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool validIndex(std::size_t index) const {&lt;br /&gt;
            return index &amp;lt; static_cast&amp;lt;std::size_t&amp;gt;(size());&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        T&amp;amp; operator[](std::size_t index) {&lt;br /&gt;
            return first[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const T&amp;amp; operator[](std::size_t index) const {&lt;br /&gt;
            return first[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        T* begin() {&lt;br /&gt;
            return first;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        T* end() {&lt;br /&gt;
            return last;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const T* begin() const {&lt;br /&gt;
            return first;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const T* end() const {&lt;br /&gt;
            return last;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static_assert(sizeof(FTContainerVector&amp;lt;void*&amp;gt;) == 0x10, &amp;quot;FTContainerVector must be 0x10&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    template &amp;lt;typename T&amp;gt;&lt;br /&gt;
    struct FTVector3 {&lt;br /&gt;
        T* first;&lt;br /&gt;
        T* last;&lt;br /&gt;
        T* capacityEnd;&lt;br /&gt;
&lt;br /&gt;
        int32_t size() const {&lt;br /&gt;
            if (!first || !last || last &amp;lt; first)&lt;br /&gt;
                return 0;&lt;br /&gt;
&lt;br /&gt;
            return static_cast&amp;lt;int32_t&amp;gt;(last - first);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t capacity() const {&lt;br /&gt;
            if (!first || !capacityEnd || capacityEnd &amp;lt; first)&lt;br /&gt;
                return 0;&lt;br /&gt;
&lt;br /&gt;
            return static_cast&amp;lt;int32_t&amp;gt;(capacityEnd - first);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool empty() const {&lt;br /&gt;
            return size() == 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool validIndex(std::size_t index) const {&lt;br /&gt;
            return index &amp;lt; static_cast&amp;lt;std::size_t&amp;gt;(size());&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        T&amp;amp; operator[](std::size_t index) {&lt;br /&gt;
            return first[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const T&amp;amp; operator[](std::size_t index) const {&lt;br /&gt;
            return first[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        T* begin() {&lt;br /&gt;
            return first;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        T* end() {&lt;br /&gt;
            return last;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const T* begin() const {&lt;br /&gt;
            return first;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const T* end() const {&lt;br /&gt;
            return last;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    static_assert(sizeof(FTVector3&amp;lt;void*&amp;gt;) == 0x0C, &amp;quot;FTVector3 must be 0x0C&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    template &amp;lt;typename T&amp;gt;&lt;br /&gt;
    struct FTProcessingOwnerVector {&lt;br /&gt;
        void* unknown; // +0x00 / owner +0x1D8&lt;br /&gt;
        T* first;      // +0x04 / owner +0x1DC&lt;br /&gt;
        T* last;       // +0x08 / owner +0x1E0&lt;br /&gt;
&lt;br /&gt;
        int32_t size() const {&lt;br /&gt;
            if (!first || !last || last &amp;lt; first)&lt;br /&gt;
                return 0;&lt;br /&gt;
            return static_cast&amp;lt;int32_t&amp;gt;(last - first);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool validIndex(std::size_t index) const {&lt;br /&gt;
            return index &amp;lt; static_cast&amp;lt;std::size_t&amp;gt;(size());&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        T&amp;amp; operator[](std::size_t index) {&lt;br /&gt;
            return first[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const T&amp;amp; operator[](std::size_t index) const {&lt;br /&gt;
            return first[index];&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    namespace Constants {&lt;br /&gt;
        // Confirmed by StageManager InitStages / SwitchStateNow bounds.&lt;br /&gt;
        // Do not add a custom 25th stage; attach custom UI to an existing owner/modal.&lt;br /&gt;
        constexpr int32_t StageOwnerCount = 24;&lt;br /&gt;
        // These are built-in popup owner slots at FTGameDialogOwner + 0x1E8.&lt;br /&gt;
        // Not proof that every stage uses all 10 slots.&lt;br /&gt;
        constexpr int32_t KnownBuiltinPopupSlotCount = 10;&lt;br /&gt;
        constexpr uintptr_t BuiltinPopupOwnerSlotsOffset = 0x1E8;&lt;br /&gt;
        constexpr int32_t BuiltinPopupOwnerSlotBaseIndex = 122;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    class AduBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual AduBase* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGameObj : public AduBase {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual AduGameObj* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetField08AndMark(int32_t value) = 0;&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL ResetElapsedRecursive() = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetActive(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetUpdateBlocked(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL SetVisible(bool value) = 0;&lt;br /&gt;
        virtual bool FTSDK_THISCALL UpdateRecursive(float dt) = 0;&lt;br /&gt;
        virtual int32_t FTSDK_THISCALL ProcessVisibleRecursive() = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        void* VTable() const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;void* const*&amp;gt;(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Field04() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x04);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Field08() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x08);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* FirstChild() const {&lt;br /&gt;
            return Read&amp;lt;AduGameObj*&amp;gt;(0x0C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* NextSibling() const {&lt;br /&gt;
            return Read&amp;lt;AduGameObj*&amp;gt;(0x10);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsMarked() const {&lt;br /&gt;
            return ReadBool(0x14);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsActive() const {&lt;br /&gt;
            return ReadBool(0x15);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsUpdateBlocked() const {&lt;br /&gt;
            return ReadBool(0x16);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsVisible() const {&lt;br /&gt;
            return ReadBool(0x17);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        float ElapsedOrTime() const {&lt;br /&gt;
            return Read&amp;lt;float&amp;gt;(0x18);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        float LastDelta() const {&lt;br /&gt;
            return Read&amp;lt;float&amp;gt;(0x1C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Callback&amp;gt;&lt;br /&gt;
        void ForEachChild(Callback&amp;amp;&amp;amp; callback) {&lt;br /&gt;
            for (AduGameObj* child = FirstChild(); child; child = child-&amp;gt;NextSibling())&lt;br /&gt;
                callback(child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename Callback&amp;gt;&lt;br /&gt;
        void ForEachChild(Callback&amp;amp;&amp;amp; callback) const {&lt;br /&gt;
            for (AduGameObj* child = FirstChild(); child; child = child-&amp;gt;NextSibling())&lt;br /&gt;
                callback(child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    protected:&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        T Read(uintptr_t offset) const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;const T*&amp;gt;(reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + offset);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        void Write(uintptr_t offset, T value) {&lt;br /&gt;
            *reinterpret_cast&amp;lt;T*&amp;gt;(reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + offset) = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool ReadBool(uintptr_t offset) const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(offset) != 0;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiControl : public AduGameObj {&lt;br /&gt;
    public:&lt;br /&gt;
        AduGuiStateSet** StateSets() const {&lt;br /&gt;
            return Read&amp;lt;AduGuiStateSet**&amp;gt;(0x20);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t StateCount() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x24);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t StateCapacityMaybe() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x28);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const FTString&amp;amp; Name() const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;const FTString*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0x2C&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t CommandId() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x48);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        uint32_t Field4C() const {&lt;br /&gt;
            return Read&amp;lt;uint32_t&amp;gt;(0x4C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t ControlTypeId() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x50);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsControlEnabledFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x5C) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsHoverOrState3Flag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x5D) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const RectI&amp;amp; Rect() const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;const RectI*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0x60&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t CurrentVisualState() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x70);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t DefaultStateIndex() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x74);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsDebugFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x79) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsOverFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x7A) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsPressedFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x7B) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t X() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x80);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Y() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x84);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Width() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x88);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Height() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x8C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        float ControlTimer() const {&lt;br /&gt;
            return Read&amp;lt;float&amp;gt;(0x18);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiEditBox : public AduGuiControl {&lt;br /&gt;
    public:&lt;br /&gt;
        const wchar_t* TextW() const {&lt;br /&gt;
            const wchar_t* text = Read&amp;lt;const wchar_t*&amp;gt;(0x218);&lt;br /&gt;
            return text ? text : L&amp;quot;&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        std::size_t TextLength() const {&lt;br /&gt;
            return std::wcslen(TextW());&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        uint32_t TextField21C() const {&lt;br /&gt;
            return Read&amp;lt;uint32_t&amp;gt;(0x21C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void DumpText() const {&lt;br /&gt;
            std::printf(&lt;br /&gt;
                &amp;quot;[EditBox] this=%p textPtr=%p strlen=%u field21C=%u text=\&amp;quot;%ls\&amp;quot;\n&amp;quot;,&lt;br /&gt;
                this,&lt;br /&gt;
                TextW(),&lt;br /&gt;
                static_cast&amp;lt;unsigned&amp;gt;(TextLength()),&lt;br /&gt;
                TextField21C(),&lt;br /&gt;
                TextW()&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class AduGuiDialog : public AduGameObj {&lt;br /&gt;
    public:&lt;br /&gt;
        AduGameObj** ObjectArray() const {&lt;br /&gt;
            return Read&amp;lt;AduGameObj**&amp;gt;(0x8C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t ObjectCount() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x90);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGameObj* ObjectAt(int32_t index) const {&lt;br /&gt;
            auto arr = ObjectArray();&lt;br /&gt;
&lt;br /&gt;
            if (!arr || index &amp;lt; 0 || index &amp;gt;= ObjectCount())&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return arr[index];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* ControlAt(int32_t index) const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;AduGuiControl*&amp;gt;(ObjectAt(index));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByCommandId(int32_t commandId) const {&lt;br /&gt;
            const int32_t count = ObjectCount();&lt;br /&gt;
&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; count; ++i) {&lt;br /&gt;
                auto* control = ControlAt(i);&lt;br /&gt;
                if (!control)&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;CommandId() == commandId)&lt;br /&gt;
                    return control;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByName(const char* name) const {&lt;br /&gt;
            if (!name)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            const int32_t count = ObjectCount();&lt;br /&gt;
&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; count; ++i) {&lt;br /&gt;
                auto* control = ControlAt(i);&lt;br /&gt;
                if (!control)&lt;br /&gt;
                    continue;&lt;br /&gt;
&lt;br /&gt;
                if (control-&amp;gt;Name().Equals(name))&lt;br /&gt;
                    return control;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void SetCallback(GuiCallbackFn callback, void* userData) {&lt;br /&gt;
            Write&amp;lt;GuiCallbackFn&amp;gt;(27 * 4, callback);&lt;br /&gt;
            Write&amp;lt;void*&amp;gt;(28 * 4, userData);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        GuiCallbackFn Callback() const {&lt;br /&gt;
            return Read&amp;lt;GuiCallbackFn&amp;gt;(27 * 4);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void* CallbackUserData() const {&lt;br /&gt;
            return Read&amp;lt;void*&amp;gt;(28 * 4);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class StageManager;&lt;br /&gt;
&lt;br /&gt;
    class FTGuiOwnerBase {&lt;br /&gt;
    public:&lt;br /&gt;
        void* VTable() const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;void* const*&amp;gt;(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t StateIndex() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x04);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        uintptr_t Address() const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const char* OwnerName() const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;const char*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0x10&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        RectI OwnerRect() const {&lt;br /&gt;
            RectI r{};&lt;br /&gt;
            r.left = Left();&lt;br /&gt;
            r.top = Top();&lt;br /&gt;
            r.right = Right();&lt;br /&gt;
            r.bottom = Bottom();&lt;br /&gt;
            return r;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Width() const {&lt;br /&gt;
            return Right() - Left();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Height() const {&lt;br /&gt;
            return Bottom() - Top();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Left() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x94);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Top() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x98);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Right() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x9C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t Bottom() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0xA0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        FTContainerVector&amp;lt;FTGuiOwnerBase*&amp;gt;&amp;amp; ActiveChildOwners() {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;FTContainerVector&amp;lt;FTGuiOwnerBase*&amp;gt;*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0xA4&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const FTContainerVector&amp;lt;FTGuiOwnerBase*&amp;gt;&amp;amp; ActiveChildOwners() const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;const FTContainerVector&amp;lt;FTGuiOwnerBase*&amp;gt;*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0xA4&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        FTGuiOwnerBase* UnknownB0_ActiveChildCapacityEndAlias() const {&lt;br /&gt;
            return Read&amp;lt;FTGuiOwnerBase*&amp;gt;(0xB0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        FTGuiOwnerBase* ParentOwner() const {&lt;br /&gt;
            return Read&amp;lt;FTGuiOwnerBase*&amp;gt;(0xB4);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void* OwnerManager() const {&lt;br /&gt;
            return ParentOwner();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsParentChainEnabled() const {&lt;br /&gt;
            return Read&amp;lt;uint32_t&amp;gt;(0xB8) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsProcessEnabled() const {&lt;br /&gt;
            return Read&amp;lt;uint32_t&amp;gt;(0xBC) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsRegisteredForProcessing() const {&lt;br /&gt;
            return IsProcessEnabled();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsOwnerActiveFlag() const {&lt;br /&gt;
            return IsRegisteredForProcessing();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsOwnerModalOrOpenFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint32_t&amp;gt;(0xC0) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsRectOffsetEnabled() const {&lt;br /&gt;
            return Read&amp;lt;uint32_t&amp;gt;(0xC4) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool InputConsumedFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x1C4) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* XmlDialog() const {&lt;br /&gt;
            return Read&amp;lt;AduGuiDialog*&amp;gt;(0x1C8);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool HasXmlDialog() const {&lt;br /&gt;
            return XmlDialog() != nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        FTProcessingOwnerVector&amp;lt;FTGuiOwnerBase*&amp;gt;&amp;amp; ProcessingChildOwners() {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;FTProcessingOwnerVector&amp;lt;FTGuiOwnerBase*&amp;gt;*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0x1D8&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const FTProcessingOwnerVector&amp;lt;FTGuiOwnerBase*&amp;gt;&amp;amp; ProcessingChildOwners() const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;const FTProcessingOwnerVector&amp;lt;FTGuiOwnerBase*&amp;gt;*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0x1D8&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        FTGuiOwnerBase* Unknown1D0_SelfOnEmbeddedModalInit() const {&lt;br /&gt;
            return Read&amp;lt;FTGuiOwnerBase*&amp;gt;(0x1D0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByCommandId(int32_t commandId) const {&lt;br /&gt;
            auto* xml = XmlDialog();&lt;br /&gt;
            return xml ? xml-&amp;gt;FindControlByCommandId(commandId) : nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* FindControlByName(const char* name) const {&lt;br /&gt;
            auto* xml = XmlDialog();&lt;br /&gt;
            return xml ? xml-&amp;gt;FindControlByName(name) : nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void SetOwnerRect(int32_t left, int32_t top, int32_t right, int32_t bottom) {&lt;br /&gt;
            Write&amp;lt;int32_t&amp;gt;(0x94, left);&lt;br /&gt;
            Write&amp;lt;int32_t&amp;gt;(0x98, top);&lt;br /&gt;
            Write&amp;lt;int32_t&amp;gt;(0x9C, right);&lt;br /&gt;
            Write&amp;lt;int32_t&amp;gt;(0xA0, bottom);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using DestroyOwnerFn = void* (FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self,&lt;br /&gt;
            uint32_t flags&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        void* DestroyOwner(uint32_t flags) {&lt;br /&gt;
            auto vt = *reinterpret_cast&amp;lt;void***&amp;gt;(this);&lt;br /&gt;
            auto fn = reinterpret_cast&amp;lt;DestroyOwnerFn&amp;gt;(vt[0x34 / 4]);&lt;br /&gt;
            return fn(this, flags);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using AddChildOwnerFn = int(FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self,&lt;br /&gt;
            FTGuiOwnerBase* child&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        void AddChildOwner(FTGuiOwnerBase* child) {&lt;br /&gt;
            reinterpret_cast&amp;lt;AddChildOwnerFn&amp;gt;(0x005AA720)(this, child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using BringChildOwnerToFrontFn = int(FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self,&lt;br /&gt;
            FTGuiOwnerBase* child&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        void BringChildOwnerToFront(FTGuiOwnerBase* child) {&lt;br /&gt;
            reinterpret_cast&amp;lt;BringChildOwnerToFrontFn&amp;gt;(0x005AA780)(this, child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using IsOwnerReachableFn = int(FTSDK_FASTCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        bool IsReachableThroughParentChain() const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;IsOwnerReachableFn&amp;gt;(0x005A99E0)(&lt;br /&gt;
                const_cast&amp;lt;FTGuiOwnerBase*&amp;gt;(this)&lt;br /&gt;
                ) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using ActivateOwnerProcessingFn = FTGuiOwnerBase * (FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        // vtable + 0x4C.&lt;br /&gt;
        FTGuiOwnerBase* ActivateOwnerProcessing() {&lt;br /&gt;
            auto vt = *reinterpret_cast&amp;lt;void***&amp;gt;(this);&lt;br /&gt;
            auto fn = reinterpret_cast&amp;lt;ActivateOwnerProcessingFn&amp;gt;(vt[0x4C / 4]);&lt;br /&gt;
            return fn(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using OnPostGuiLoadFn = void(FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        void OnPostGuiLoad() {&lt;br /&gt;
            auto vt = *reinterpret_cast&amp;lt;void***&amp;gt;(this);&lt;br /&gt;
            auto fn = reinterpret_cast&amp;lt;OnPostGuiLoadFn&amp;gt;(vt[0x50 / 4]);&lt;br /&gt;
            fn(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using SetOpenFlagFn = void(FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        void OnOwnerOpened() {&lt;br /&gt;
            auto vt = *reinterpret_cast&amp;lt;void***&amp;gt;(this);&lt;br /&gt;
            auto fn = reinterpret_cast&amp;lt;SetOpenFlagFn&amp;gt;(vt[0x84 / 4]);&lt;br /&gt;
            fn(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void OnOwnerClosed() {&lt;br /&gt;
            auto vt = *reinterpret_cast&amp;lt;void***&amp;gt;(this);&lt;br /&gt;
            auto fn = reinterpret_cast&amp;lt;SetOpenFlagFn&amp;gt;(vt[0x88 / 4]);&lt;br /&gt;
            fn(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using OpenOwnerFn = int(FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        int OpenOwner() {&lt;br /&gt;
            auto vt = *reinterpret_cast&amp;lt;void***&amp;gt;(this);&lt;br /&gt;
            auto fn = reinterpret_cast&amp;lt;OpenOwnerFn&amp;gt;(vt[0x2C / 4]);&lt;br /&gt;
            return fn(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int CloseOwner() {&lt;br /&gt;
            auto vt = *reinterpret_cast&amp;lt;void***&amp;gt;(this);&lt;br /&gt;
            auto fn = reinterpret_cast&amp;lt;OpenOwnerFn&amp;gt;(vt[0x30 / 4]);&lt;br /&gt;
            return fn(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using LoadGuiFn = bool(FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self,&lt;br /&gt;
            const char* xmlName,&lt;br /&gt;
            const GuiBind* binds,&lt;br /&gt;
            int32_t bindCount,&lt;br /&gt;
            GuiCallbackFn callback,&lt;br /&gt;
            bool reload&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        bool LoadGui(&lt;br /&gt;
            const char* xmlName,&lt;br /&gt;
            const GuiBind* binds,&lt;br /&gt;
            int32_t bindCount,&lt;br /&gt;
            GuiCallbackFn callback = nullptr,&lt;br /&gt;
            bool reload = false&lt;br /&gt;
        ) {&lt;br /&gt;
            auto vt = *reinterpret_cast&amp;lt;void***&amp;gt;(this);&lt;br /&gt;
            auto fn = reinterpret_cast&amp;lt;LoadGuiFn&amp;gt;(vt[0xA4 / 4]);&lt;br /&gt;
            return fn(this, xmlName, binds, bindCount, callback, reload);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using InitWithParentOwnerFn = void(FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self,&lt;br /&gt;
            FTGuiOwnerBase* parentOwner&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        // Base implementation vfunc_1757376:&lt;br /&gt;
        //   - attaches this owner to parent with a zero rect&lt;br /&gt;
        //   - sets ParentOwner at +0xB4&lt;br /&gt;
        //   - adds this to parent-&amp;gt;ActiveChildOwners&lt;br /&gt;
        //&lt;br /&gt;
        // Popup subclasses override this&lt;br /&gt;
        void InitWithParentOwner(FTGuiOwnerBase* parentOwner) {&lt;br /&gt;
            auto vt = *reinterpret_cast&amp;lt;void***&amp;gt;(this);&lt;br /&gt;
            auto fn = reinterpret_cast&amp;lt;InitWithParentOwnerFn&amp;gt;(vt[0xA0 / 4]);&lt;br /&gt;
            fn(this, parentOwner);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using DispatchGuiEventFn = int(FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self,&lt;br /&gt;
            GuiEventType eventType,&lt;br /&gt;
            int commandId,&lt;br /&gt;
            int param&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        int DispatchGuiEvent(GuiEventType eventType, int commandId, int param) {&lt;br /&gt;
            auto vt = *reinterpret_cast&amp;lt;void***&amp;gt;(this);&lt;br /&gt;
            auto fn = reinterpret_cast&amp;lt;DispatchGuiEventFn&amp;gt;(vt[0xBC / 4]);&lt;br /&gt;
            return fn(this, eventType, commandId, param);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using SetOwnerRectCenteredInParentFn = void(FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        void SetOwnerRectCenteredInParent() {&lt;br /&gt;
            auto vt = *reinterpret_cast&amp;lt;void***&amp;gt;(this);&lt;br /&gt;
            auto fn = reinterpret_cast&amp;lt;SetOwnerRectCenteredInParentFn&amp;gt;(vt[0x48 / 4]);&lt;br /&gt;
            fn(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using GetControlByCommandIdFn = AduGuiControl * (FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self,&lt;br /&gt;
            int32_t commandId,&lt;br /&gt;
            int32_t expectedType&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        AduGuiControl* GetControlByCommandId(int32_t commandId, int32_t expectedType = -1) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;GetControlByCommandIdFn&amp;gt;(0x005AE7D0)(&lt;br /&gt;
                this,&lt;br /&gt;
                commandId,&lt;br /&gt;
                expectedType&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using GetControlInnerObjectFn = void* (FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self,&lt;br /&gt;
            int32_t commandId&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        void* GetControlInnerObject(int32_t commandId) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;GetControlInnerObjectFn&amp;gt;(0x005AE840)(&lt;br /&gt;
                this,&lt;br /&gt;
                commandId&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using HandleInputFn = bool(FTSDK_THISCALL*)(&lt;br /&gt;
            FTGuiOwnerBase* self,&lt;br /&gt;
            uint32_t msg,&lt;br /&gt;
            int32_t wParam,&lt;br /&gt;
            int32_t lParam&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        // vtable + 0x98.&lt;br /&gt;
        //&lt;br /&gt;
        // Base/game-dialog behavior vfunc_1758720:&lt;br /&gt;
        //   - delegates to focused/input owner if present&lt;br /&gt;
        //   - handles IME/edit path if present&lt;br /&gt;
        //   - walks ProcessingChildOwners at +0x1D8&lt;br /&gt;
        //   - calls child-&amp;gt;HandleInput(...) when child +0xBC is enabled&lt;br /&gt;
        bool HandleInput(uint32_t msg, int32_t wParam, int32_t lParam) {&lt;br /&gt;
            auto vt = *reinterpret_cast&amp;lt;void***&amp;gt;(this);&lt;br /&gt;
            auto fn = reinterpret_cast&amp;lt;HandleInputFn&amp;gt;(vt[0x98 / 4]);&lt;br /&gt;
            return fn(this, msg, wParam, lParam);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // +0x1C0 / this[112].&lt;br /&gt;
        //&lt;br /&gt;
        // Used by vt+0x98 HandleInput and vt+0xB4 update traversal before&lt;br /&gt;
        // falling back to the +0x1D8 processing child vector.&lt;br /&gt;
        //&lt;br /&gt;
        // If set and reachable/process-enabled, this owner receives input first.&lt;br /&gt;
        FTGuiOwnerBase* PriorityInputChildOwner() const {&lt;br /&gt;
            return Read&amp;lt;FTGuiOwnerBase*&amp;gt;(0x1C0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void SetPriorityInputChildOwner(FTGuiOwnerBase* child) {&lt;br /&gt;
            Write&amp;lt;FTGuiOwnerBase*&amp;gt;(0x1C0, child);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void ClearPriorityInputChildOwnerIf(FTGuiOwnerBase* child) {&lt;br /&gt;
            if (PriorityInputChildOwner() == child)&lt;br /&gt;
                SetPriorityInputChildOwner(nullptr);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void SetParentChainEnabled(bool value) {&lt;br /&gt;
            Write&amp;lt;uint32_t&amp;gt;(0xB8, value ? 1u : 0u);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        uint8_t ReadU8(uintptr_t offset) const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(offset);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        uint32_t ReadU32(uintptr_t offset) const {&lt;br /&gt;
            return Read&amp;lt;uint32_t&amp;gt;(offset);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void* ReadPtr(uintptr_t offset) const {&lt;br /&gt;
            return Read&amp;lt;void*&amp;gt;(offset);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    protected:&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        T Read(uintptr_t offset) const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;const T*&amp;gt;(reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + offset);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        void Write(uintptr_t offset, T value) {&lt;br /&gt;
            *reinterpret_cast&amp;lt;T*&amp;gt;(reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + offset) = value;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class FTGameDialogOwner : public FTGuiOwnerBase {&lt;br /&gt;
    public:&lt;br /&gt;
        bool IsXmlLoadedGameFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x210) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsXmlLoaded() const {&lt;br /&gt;
            return IsXmlLoadedGameFlag();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Valid only for stage/current dialog owners that own the built-in popup table.&lt;br /&gt;
        // Do not call this on arbitrary popup owner instances.&lt;br /&gt;
        //&lt;br /&gt;
        // sub_498890 uses parentOwner[122 + index] as the built-in popup slot table.&lt;br /&gt;
        // Ranking popup constructor also writes to [122], proving that offset 0x1E8&lt;br /&gt;
        // is subclass-specific on popup objects and not universally a popup table.&lt;br /&gt;
        FTGameDialogOwner* StageBuiltinPopupOwnerAt(int32_t popupIndex) const {&lt;br /&gt;
            if (popupIndex &amp;lt; 0 || popupIndex &amp;gt;= Constants::KnownBuiltinPopupSlotCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return Read&amp;lt;FTGameDialogOwner*&amp;gt;(&lt;br /&gt;
                Constants::BuiltinPopupOwnerSlotsOffset + popupIndex * 4&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Compatibility alias. Use only on stage/current dialog owners.&lt;br /&gt;
        FTGameDialogOwner* BuiltinPopupOwnerAt(int32_t popupIndex) const {&lt;br /&gt;
            return StageBuiltinPopupOwnerAt(popupIndex);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Compatibility alias. Use only on stage/current dialog owners.&lt;br /&gt;
        FTGameDialogOwner* PopupOwnerAt(int32_t popupIndex) const {&lt;br /&gt;
            return StageBuiltinPopupOwnerAt(popupIndex);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* StageBuiltinPopupXmlDialogAt(int32_t popupIndex) const {&lt;br /&gt;
            auto* popup = StageBuiltinPopupOwnerAt(popupIndex);&lt;br /&gt;
            return popup ? popup-&amp;gt;XmlDialog() : nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* BuiltinPopupXmlDialogAt(int32_t popupIndex) const {&lt;br /&gt;
            return StageBuiltinPopupXmlDialogAt(popupIndex);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* PopupXmlDialogAt(int32_t popupIndex) const {&lt;br /&gt;
            return StageBuiltinPopupXmlDialogAt(popupIndex);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool HasStageBuiltinPopupOwner(int32_t popupIndex) const {&lt;br /&gt;
            return StageBuiltinPopupOwnerAt(popupIndex) != nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool HasAnyStageBuiltinPopupOwner() const {&lt;br /&gt;
            for (int32_t i = 0; i &amp;lt; Constants::KnownBuiltinPopupSlotCount; ++i) {&lt;br /&gt;
                if (StageBuiltinPopupOwnerAt(i))&lt;br /&gt;
                    return true;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using GetOrCreateBuiltinPopupOwnerFn = FTGameDialogOwner * (FTSDK_THISCALL*)(&lt;br /&gt;
            FTGameDialogOwner* self,&lt;br /&gt;
            uint32_t popupIndex&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        // sub_498890 / 0x00498890.&lt;br /&gt;
        //&lt;br /&gt;
        // Valid for stage/current dialog owners.&lt;br /&gt;
        //&lt;br /&gt;
        // Creates one of the known built-in popup owner types if the slot is empty,&lt;br /&gt;
        // stores it at parentOwner[122 + popupIndex], then calls the popup owner&#039;s&lt;br /&gt;
        // vt+0xA0 init/attach method with this owner as parent.&lt;br /&gt;
        FTGameDialogOwner* GetOrCreateBuiltinPopupOwner(uint32_t popupIndex) {&lt;br /&gt;
            if (popupIndex &amp;gt;= Constants::KnownBuiltinPopupSlotCount)&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return reinterpret_cast&amp;lt;GetOrCreateBuiltinPopupOwnerFn&amp;gt;(0x00498890)(&lt;br /&gt;
                this,&lt;br /&gt;
                popupIndex&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    class StageManager {&lt;br /&gt;
    public:&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved00() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved04() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved08() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved0C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved10() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved14() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved18() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved1C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved20() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved24() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved28() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual int FTSDK_THISCALL OpenOwner() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL CloseOwner() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void* FTSDK_THISCALL Destroy(uint32_t flags) = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_THISCALL AttachOwnerRaw(&lt;br /&gt;
            void* parentOrManager,&lt;br /&gt;
            int32_t rectVtableOrTemp,&lt;br /&gt;
            int32_t left,&lt;br /&gt;
            int32_t top,&lt;br /&gt;
            int32_t right,&lt;br /&gt;
            int32_t bottom,&lt;br /&gt;
            int32_t stateIndex&lt;br /&gt;
        ) = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual int FTSDK_THISCALL ShutdownStages() = 0;&lt;br /&gt;
        virtual int FTSDK_THISCALL UpdateStageManager() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual int FTSDK_THISCALL OffsetOwnerRect(int32_t dx, int32_t dy) = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL CenterOwnerRectInParent() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual StageManager* FTSDK_THISCALL RegisterProcessEnabled() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL ResetProcessEnabled() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved54() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved58() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved5C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved60() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved64() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved68() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved6C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved70() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved74() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved78() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved7C() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL Reserved80() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_THISCALL OnOwnerOpened() = 0;&lt;br /&gt;
        virtual void FTSDK_THISCALL OnOwnerClosed() = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_STDCALL RectCleanupThunk(&lt;br /&gt;
            int32_t a1,&lt;br /&gt;
            uint8_t rectObj,&lt;br /&gt;
            int32_t a3,&lt;br /&gt;
            int32_t a4&lt;br /&gt;
        ) = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_THISCALL HandleSystemEvent(&lt;br /&gt;
            int32_t eventType,&lt;br /&gt;
            void* data,&lt;br /&gt;
            int32_t param&lt;br /&gt;
        ) = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual void FTSDK_STDCALL RectCleanupThunk2(&lt;br /&gt;
            uint8_t rectObj,&lt;br /&gt;
            int32_t a2,&lt;br /&gt;
            int32_t a3&lt;br /&gt;
        ) = 0;&lt;br /&gt;
&lt;br /&gt;
        virtual bool FTSDK_THISCALL HandleInput(&lt;br /&gt;
            uint32_t msg,&lt;br /&gt;
            int32_t wParam,&lt;br /&gt;
            int32_t lParam&lt;br /&gt;
        ) = 0;&lt;br /&gt;
&lt;br /&gt;
    public:&lt;br /&gt;
        void* VTable() const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;void* const*&amp;gt;(this);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void* Hwnd() const {&lt;br /&gt;
            return Read&amp;lt;void*&amp;gt;(0x560);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t PreviousState() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x564);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t PendingState() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x568);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t CurrentState() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x56C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        int32_t TransitionStateScratch() const {&lt;br /&gt;
            return Read&amp;lt;int32_t&amp;gt;(0x570);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        FTGameDialogOwner* CurrentStageOwner() const {&lt;br /&gt;
            return Read&amp;lt;FTGameDialogOwner*&amp;gt;(0x574);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        FTGameDialogOwner* CurrentDialogOwner() const {&lt;br /&gt;
            return CurrentStageOwner();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        AduGuiDialog* CurrentDialog() const {&lt;br /&gt;
            auto* owner = CurrentStageOwner();&lt;br /&gt;
            return owner ? owner-&amp;gt;XmlDialog() : nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool HasModalDialog() const {&lt;br /&gt;
            return Read&amp;lt;uint32_t&amp;gt;(0x634) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        FTGuiOwnerBase* ModalDialogOwner() {&lt;br /&gt;
            return reinterpret_cast&amp;lt;FTGuiOwnerBase*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0x578&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const FTGuiOwnerBase* ModalDialogOwner() const {&lt;br /&gt;
            return reinterpret_cast&amp;lt;const FTGuiOwnerBase*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0x578&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        FTContainerVector&amp;lt;FTGameDialogOwner*&amp;gt;&amp;amp; StageOwners() {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;FTContainerVector&amp;lt;FTGameDialogOwner*&amp;gt;*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0x794&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const FTContainerVector&amp;lt;FTGameDialogOwner*&amp;gt;&amp;amp; StageOwners() const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;const FTContainerVector&amp;lt;FTGameDialogOwner*&amp;gt;*&amp;gt;(&lt;br /&gt;
                reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + 0x794&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        FTContainerVector&amp;lt;FTGameDialogOwner*&amp;gt;&amp;amp; DialogOwners() {&lt;br /&gt;
            return StageOwners();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const FTContainerVector&amp;lt;FTGameDialogOwner*&amp;gt;&amp;amp; DialogOwners() const {&lt;br /&gt;
            return StageOwners();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        FTGameDialogOwner* StageOwnerAt(int32_t index) const {&lt;br /&gt;
            const auto&amp;amp; owners = StageOwners();&lt;br /&gt;
&lt;br /&gt;
            if (index &amp;lt; 0 || !owners.validIndex(static_cast&amp;lt;std::size_t&amp;gt;(index)))&lt;br /&gt;
                return nullptr;&lt;br /&gt;
&lt;br /&gt;
            return owners[static_cast&amp;lt;std::size_t&amp;gt;(index)];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        FTGameDialogOwner* DialogOwnerAt(int32_t index) const {&lt;br /&gt;
            return StageOwnerAt(index);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        void* CurrentOverlayRaw() const {&lt;br /&gt;
            return Read&amp;lt;void*&amp;gt;(0x7A4);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        float OverlayTimer() const {&lt;br /&gt;
            return Read&amp;lt;float&amp;gt;(0x854);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsFrameUpdating() const {&lt;br /&gt;
            return Read&amp;lt;uint32_t&amp;gt;(0x878) != 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        uint8_t DelayedReconnectFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x88C);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        float PeriodicTimer() const {&lt;br /&gt;
            return Read&amp;lt;float&amp;gt;(0x890);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        uint8_t HourlyPacketReadyFlag() const {&lt;br /&gt;
            return Read&amp;lt;uint8_t&amp;gt;(0x894);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        float HourlyPacketTimer() const {&lt;br /&gt;
            return Read&amp;lt;float&amp;gt;(0x898);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool IsValidStageIndex(uint32_t state) const {&lt;br /&gt;
            return state &amp;lt; Constants::StageOwnerCount;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        bool HasCurrentStageOwner() const {&lt;br /&gt;
            return CurrentStageOwner() != nullptr;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using RequestStateFn = void(FTSDK_THISCALL*)(&lt;br /&gt;
            StageManager* self,&lt;br /&gt;
            uint32_t state&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        void RequestState(uint32_t state) {&lt;br /&gt;
            if (!IsValidStageIndex(state))&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            reinterpret_cast&amp;lt;RequestStateFn&amp;gt;(0x004AE9A0)(this, state);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using SwitchStateNowFn = void(FTSDK_THISCALL*)(&lt;br /&gt;
            StageManager* self,&lt;br /&gt;
            uint32_t state&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        void SwitchStateNow(uint32_t state) {&lt;br /&gt;
            if (!IsValidStageIndex(state))&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            reinterpret_cast&amp;lt;SwitchStateNowFn&amp;gt;(0x004AD7D0)(this, state);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        using InitStagesFn = bool(FTSDK_THISCALL*)(&lt;br /&gt;
            StageManager* self,&lt;br /&gt;
            void* hwnd,&lt;br /&gt;
            int startupParam&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        bool InitStages(void* hwnd, int startupParam) {&lt;br /&gt;
            return reinterpret_cast&amp;lt;InitStagesFn&amp;gt;(0x004B0590)(this, hwnd, startupParam);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    private:&lt;br /&gt;
        template &amp;lt;typename T&amp;gt;&lt;br /&gt;
        T Read(uintptr_t offset) const {&lt;br /&gt;
            return *reinterpret_cast&amp;lt;const T*&amp;gt;(reinterpret_cast&amp;lt;uintptr_t&amp;gt;(this) + offset);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    using GetStageManagerFn = StageManager * (FTSDK_CDECL*)();&lt;br /&gt;
&lt;br /&gt;
    inline StageManager* GetStageManager() {&lt;br /&gt;
        return reinterpret_cast&amp;lt;GetStageManagerFn&amp;gt;(0x004B0520)();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    using ConstructGuiOwnerBaseFn = FTGuiOwnerBase * (FTSDK_THISCALL*)(&lt;br /&gt;
        FTGuiOwnerBase* self&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    inline FTGuiOwnerBase* ConstructGuiOwnerBase(FTGuiOwnerBase* memory) {&lt;br /&gt;
        return reinterpret_cast&amp;lt;ConstructGuiOwnerBaseFn&amp;gt;(0x005AEC40)(memory);&lt;br /&gt;
    }&lt;br /&gt;
     &lt;br /&gt;
    using ConstructGameDialogOwnerFn = FTGameDialogOwner * (FTSDK_THISCALL*)(&lt;br /&gt;
        FTGameDialogOwner* self&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    inline FTGameDialogOwner* ConstructGameDialogOwner(FTGameDialogOwner* memory) {&lt;br /&gt;
        return reinterpret_cast&amp;lt;ConstructGameDialogOwnerFn&amp;gt;(0x004987C0)(memory);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Example Usage ==&lt;br /&gt;
* [[Custom Client UI with StageManager Input|Custom client UI with StageManager input forwarding]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Client]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=286</id>
		<title>List of GameEventType Events</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=286"/>
		<updated>2026-05-06T19:25:46Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: added new events and edited events that are currently called&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== List of Events (GameEventType) ==&lt;br /&gt;
&lt;br /&gt;
This page lists the currently available events you can hook into using the &amp;lt;code&amp;gt;geb.on(&amp;quot;EVENT_NAME&amp;quot;, ...)&amp;lt;/code&amp;gt; function from JavaScript.&lt;br /&gt;
&lt;br /&gt;
All events come from the &amp;lt;code&amp;gt;GameEventType&amp;lt;/code&amp;gt; enum in either the &#039;&#039;&#039;game server&#039;&#039;&#039; or &#039;&#039;&#039;chat server&#039;&#039;&#039; codebase.  &lt;br /&gt;
These are used in event scripts placed inside &amp;lt;code&amp;gt;scripts/event/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Game Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Parameters !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || &amp;lt;code&amp;gt;long diff&amp;lt;/code&amp;gt; || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt; || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || || Player logs out&lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_INIT_HP || ❌ || ||  &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_DAMAGE || ❌ || ||  &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_POINT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_FINISH || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| EMBLEM_INIT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ENCHANT_ON_ANNOUNCE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_ENCHANT || ✅ || &amp;lt;code&amp;gt;FTClient, PlayerPocket itemPocket, int costs&amp;lt;/code&amp;gt; || Called after a successfull enchant. It gives you the enchanted inventory item and the cost (gold) of the enchanting itself&lt;br /&gt;
|-&lt;br /&gt;
| GACHA_OPENED || ✅ || &amp;lt;code&amp;gt;FTClient, int productIndex, List&amp;lt;GachaOpenResult&amp;gt;&amp;lt;/code&amp;gt; || Called after gacha opening. You get the opening gacha productItemIndex and the result of the opening&lt;br /&gt;
|-&lt;br /&gt;
| RECIPE_COMBINED || ✅ || &amp;lt;code&amp;gt;FTClient, Recipe&amp;lt;/code&amp;gt; || Called after a recipe was combined&lt;br /&gt;
|-&lt;br /&gt;
| INVENTORY_ITEM_TIME_EXPIRED || ❌ || || Time-based item expired&lt;br /&gt;
|-&lt;br /&gt;
| ON_PLAYER_ANNOUNCE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| PLAYER_QUICK_SLOT_USE || ✅ || &amp;lt;code&amp;gt;FTClient, int slotIndex&amp;lt;/code&amp;gt; || Called in matchplay when quick slot is used &lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_JOINED || ❌ || || Player entered the lobby&lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_LEFT || ❌ || || Player left the lobby&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LOBBY_JOINED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_CREATED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_JOINED || ❌ || || Player joined a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LEFT || ❌ || || Player left a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_POSITION_CHANGED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_MAP_CHANGED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| MP_RELAY_CONNECTED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_TRIGGERED || ✅ || &amp;lt;code&amp;gt;FTClient, Room, RoomPlayer&amp;lt;/code&amp;gt; || After skipping the match intro, this is called before Player stats are sent to the client&lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_END || ✅ || &amp;lt;code&amp;gt;MatchplayGame, Room&amp;lt;/code&amp;gt; || Skipping match intro finished and is called right before guardian serve in battle or guardian mode, or serve in basic mode&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_START || ❌ || || Match begins&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_END || ✅ || &amp;lt;code&amp;gt;MatchplayBasicGame / MatchplayBattleGame / MatchplayGuardianGame, Room, ConcurrentLinkedDeque&amp;amp;lt;FTClient&amp;amp;gt;&amp;lt;/code&amp;gt; || Match ends. Game object can be one of these three&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_USE_SKILL || ✅ || &amp;lt;code&amp;gt;FTClient, MatchplayGame, RoomPlayer, Skill, SkillUse, C2SMatchplayUsesSkill&amp;lt;/code&amp;gt; || A skill was used&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_PICKING_UP_CRYSTAL || ✅ || &amp;lt;code&amp;gt;FTClient, SkillCrystal, int randomSkillIndex&amp;lt;/code&amp;gt; || Player crystal pickup&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_HITS_TARGET || ✅ || &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
(1) FTClient, MatchplayGame, Skill&lt;br /&gt;
&lt;br /&gt;
(2) FTConnection, MatchplayGame, short newHealth, C2SMatchplaySkillHitsTarget&lt;br /&gt;
&lt;br /&gt;
(3) FTConnection, MatchplayGame, short newHealth, Skill, C2SMatchplaySkillHitsTarget&lt;br /&gt;
&amp;lt;/code&amp;gt; &lt;br /&gt;
|| Supports multiple overloads. A target is hit during battle or guardian mode. See [[Event_MP_PLAYER_HITS_TARGET|Event MP_PLAYER_HITS_TARGET]]&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_SWAP_QUICK_SLOT_ITEMS || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| MP_BALL_HIT || ✅ || &amp;lt;code&amp;gt;MatchBallSyncMessage&amp;lt;/code&amp;gt; || A ball hit got registered (only MP, no solo-play!)&lt;br /&gt;
|-&lt;br /&gt;
| SHOP_MAINTENANCE_REQUESTED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD_PREPARE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_ITEM_BOUGHT || ✅ || &amp;lt;code&amp;gt;FTClient, List&amp;lt;PlayerPocket&amp;gt;, int costsGold, int costsAp&amp;lt;/code&amp;gt; || Called after an item is bought. Provides the result of the shopping and its costs&lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_INIT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_FINISH || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_DEV_PACKET || ❌ || || Developer-only test packet&lt;br /&gt;
|-&lt;br /&gt;
| SESSION_TIME_UPDATE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_HEARTBEAT || ❌ || || &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Chat Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Parameters !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || &amp;lt;code&amp;gt;long diff&amp;lt;/code&amp;gt; || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt; || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || || Player logs out&lt;br /&gt;
|-&lt;br /&gt;
| SHOP_ITEM_BOUGHT || ✅ || &amp;lt;code&amp;gt;FTClient, List&amp;lt;PlayerPocket&amp;gt;, int costsGold, int costsAp&amp;lt;/code&amp;gt; || Called after an item is bought. Provides the result of the shopping and its costs&lt;br /&gt;
|-&lt;br /&gt;
| RECIPE_COMBINED || ✅ || &amp;lt;code&amp;gt;FTClient, Recipe&amp;lt;/code&amp;gt; || Called after a recipe was combined&lt;br /&gt;
|-&lt;br /&gt;
| ON_ENCHANT || ✅ || &amp;lt;code&amp;gt;FTClient, PlayerPocket itemPocket, int costs&amp;lt;/code&amp;gt; || Called after a successfull enchant. It gives you the enchanted inventory item and the cost (gold) of the enchanting itself&lt;br /&gt;
|-&lt;br /&gt;
| GACHA_OPENED || ✅ || &amp;lt;code&amp;gt;FTClient, int productIndex, List&amp;lt;GachaOpenResult&amp;gt;&amp;lt;/code&amp;gt; || Called after gacha opening. You get the opening gacha productItemIndex and the result of the opening&lt;br /&gt;
|-&lt;br /&gt;
| TREE_SHAKE_SUCCESS || ✅ || &amp;lt;code&amp;gt;FTClient, ItemMaterial reward&amp;lt;/code&amp;gt; || Called when the tree shaking mini game ends successfully&lt;br /&gt;
|-&lt;br /&gt;
| FISHING_SUCCESS || ✅ || &amp;lt;code&amp;gt;FTClient, Product reward&amp;lt;/code&amp;gt; || Called when the fishing mini game ends successfully&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Legend ==&lt;br /&gt;
;✅ : Event is currently called (triggered by the server)&lt;br /&gt;
;❌ : Event exists but is not currently triggered&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
In the &amp;quot;Parameters &amp;quot; column, Java &#039;&#039;&#039;types&#039;&#039;&#039; are shown (e.g. &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;MatchplayGame&amp;lt;/code&amp;gt;), not variable names.&amp;lt;br&amp;gt;&lt;br /&gt;
This is to help you identify the object classes you can interact with inside your script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;Don&#039;t see your needed event called yet?&#039;&#039;  &lt;br /&gt;
→ You can still write scripts using them and ask for the event to be wired on Discord.  &lt;br /&gt;
Include the event name and what parameters your handler expects.&lt;br /&gt;
&lt;br /&gt;
== Event Lists ==&lt;br /&gt;
* [[#Game Server Events|Game Server]]&lt;br /&gt;
* [[#Chat Server Events|Chat Server]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Event]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Game_Modes&amp;diff=160</id>
		<title>Game Modes</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Game_Modes&amp;diff=160"/>
		<updated>2026-05-04T14:48:53Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: added tournament mode&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Game Modes =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
Fantasy Tennis featured several different game modes, each offering a different style of play.&lt;br /&gt;
&lt;br /&gt;
The main game modes were Basic Mode, Battle Mode, and Guardian Mode. In addition to these core modes, the game also included Battlemon support, chat-oriented modes such as House Mode, a Quest Mode, and later Club Match in some versions.&lt;br /&gt;
&lt;br /&gt;
Depending on the region and version, the available features could differ slightly, but the overall mode structure remained centered around competitive tennis, combat-based matches, cooperative play, and social side content.&lt;br /&gt;
&lt;br /&gt;
== Single Play Mode ==&lt;br /&gt;
&lt;br /&gt;
== Basic Mode ==&lt;br /&gt;
&lt;br /&gt;
Basic Mode was the standard tennis mode in Fantasy Tennis.&lt;br /&gt;
&lt;br /&gt;
It followed normal tennis rules and could be played in singles or doubles. This mode represented the core competitive gameplay of Fantasy Tennis and was the closest to a traditional tennis match.&lt;br /&gt;
&lt;br /&gt;
Basic Mode focused on movement, positioning, timing, and rally control. For many players, it was the main mode for learning the fundamentals of the game.&lt;br /&gt;
&lt;br /&gt;
== Battle Mode ==&lt;br /&gt;
&lt;br /&gt;
Battle Mode was a more action-oriented variation of the game.&lt;br /&gt;
&lt;br /&gt;
Instead of only playing for points, each side had HP. Missing the ball or taking damage from attacks reduced HP, and the match ended when one side ran out of health. Battle Mode also included items and special attacks, making it much more chaotic than Basic Mode.&lt;br /&gt;
&lt;br /&gt;
This mode gave Fantasy Tennis a more arcade-like identity and was one of the features that made it stand out from a normal tennis game.&lt;br /&gt;
&lt;br /&gt;
== Guardian Mode ==&lt;br /&gt;
&lt;br /&gt;
Guardian Mode was the cooperative PvE mode.&lt;br /&gt;
&lt;br /&gt;
Players could team up and face one or more Guardians, which acted as boss-style enemies. After successful runs, players had a chance to obtain special materials or items, which added a progression element to the mode.&lt;br /&gt;
&lt;br /&gt;
Guardian Mode offered a different pace from the competitive modes and became an important part of the game’s wider progression and replay value.&lt;br /&gt;
&lt;br /&gt;
== Battlemon Mode ==&lt;br /&gt;
&lt;br /&gt;
Battlemons were pet companions that could be used alongside the main match modes.&lt;br /&gt;
&lt;br /&gt;
Historical descriptions indicate that both Basic Mode and Battle Mode could also be played with Battlemons. In practice, this added another layer to the match structure and gave players more variety in how they approached games.&lt;br /&gt;
&lt;br /&gt;
Battlemons were one of the more distinctive features of Fantasy Tennis and helped separate it from more conventional sports games.&lt;br /&gt;
&lt;br /&gt;
== House Mode and Chat ==&lt;br /&gt;
&lt;br /&gt;
Fantasy Tennis also included social spaces outside of normal matches.&lt;br /&gt;
&lt;br /&gt;
Players could communicate in chat areas located on the courts or in House Mode. Houses could be customized with furniture, wallpaper, carpets, and other decorations. Some versions also allowed players to find additional items through activities such as fishing or interacting with objects in the garden.&lt;br /&gt;
&lt;br /&gt;
These features made Fantasy Tennis feel more like a light online world rather than only a menu-based sports game.&lt;br /&gt;
&lt;br /&gt;
== Quest Mode ==&lt;br /&gt;
&lt;br /&gt;
Quest Mode was another part of the game’s side content.&lt;br /&gt;
&lt;br /&gt;
In this mode, players could complete tasks or objectives outside of the standard competitive match structure. While it was not as central as Basic, Battle, or Guardian, it added more variety to the overall game.&lt;br /&gt;
&lt;br /&gt;
== Club Match ==&lt;br /&gt;
&lt;br /&gt;
Club Match was added later in the European version of Fantasy Tennis.&lt;br /&gt;
&lt;br /&gt;
This system allowed clubs to compete against each other in ranked matches. The format included multiple match types such as Basic 1vs1, Basic 2vs2, Battle 1vs1, Battle 2vs2, and Battlemon. Club Match gave organized groups a competitive objective beyond normal matchmaking.&lt;br /&gt;
&lt;br /&gt;
== Tournament Mode ==&lt;br /&gt;
&lt;br /&gt;
This system allowed players to compete against each other by registering to available tournaments. Torunament Mode used a 64 Players bracket system.&lt;br /&gt;
&lt;br /&gt;
== Regional Notes ==&lt;br /&gt;
&lt;br /&gt;
The game first appeared in Korea under the name Fanta Tennis and later had versions in other Asian regions, including Thailand, where it was operated by Ini3. In Europe, the game was released as Fantasy Tennis.&lt;br /&gt;
&lt;br /&gt;
Although region-specific versions could differ in presentation and updates, the identity of the game remained based on a mix of standard tennis, combat-based modes, cooperative play, and social features.&lt;br /&gt;
&lt;br /&gt;
== Summary ==&lt;br /&gt;
&lt;br /&gt;
The game mode structure was one of the defining features of Fantasy Tennis.&lt;br /&gt;
&lt;br /&gt;
Instead of relying on only one type of match, the game offered multiple ways to play:&lt;br /&gt;
* Basic Mode for standard tennis&lt;br /&gt;
* Battle Mode for HP-based action matches&lt;br /&gt;
* Guardian Mode for cooperative PvE&lt;br /&gt;
* Battlemon for pet-supported matches&lt;br /&gt;
* House Mode and chat spaces for social interaction&lt;br /&gt;
* Quest Mode for side content&lt;br /&gt;
* Club Match for organized club competition&lt;br /&gt;
* Tournament Mode for organized player and club competition&lt;br /&gt;
&lt;br /&gt;
This variety helped Fantasy Tennis stand out from more traditional online sports games.&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Beginner%27s_Guide&amp;diff=154</id>
		<title>Beginner&#039;s Guide</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Beginner%27s_Guide&amp;diff=154"/>
		<updated>2026-05-04T14:25:39Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Beginner Guide =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
Fantasy Tennis is not a game where most new players should jump straight into competitive modes.&lt;br /&gt;
&lt;br /&gt;
For beginners, the most effective path is usually to start with the tutorial, then spend time in Guardian Mode to level up, get items, and build a stronger character. After that, it becomes much easier to move into Basic and Battle.&lt;br /&gt;
&lt;br /&gt;
== Tutorial ==&lt;br /&gt;
&lt;br /&gt;
If you are new, start with the tutorial.&lt;br /&gt;
&lt;br /&gt;
The tutorial helps you understand the controls, movement, timing, and the general flow of the game. Skipping it usually only makes the beginning harder.&lt;br /&gt;
&lt;br /&gt;
== Start with Guardian Mode ==&lt;br /&gt;
&lt;br /&gt;
After the tutorial, Guardian Mode is usually the best place for new players to begin.&lt;br /&gt;
&lt;br /&gt;
Guardian is one of the most important modes for early progression. It allows you to level up your character, gain rewards, and collect items that help you become stronger.&lt;br /&gt;
&lt;br /&gt;
If your goal is to compete later in Basic or Battle, Guardian is where that journey usually starts.&lt;br /&gt;
&lt;br /&gt;
== Why Guardian Comes First ==&lt;br /&gt;
&lt;br /&gt;
For many beginners, Basic and Battle can be much harder if they enter too early without levels, items, or experience.&lt;br /&gt;
&lt;br /&gt;
Guardian gives you time to improve your character, get more comfortable with the game, and build a better foundation before moving into more competitive modes.&lt;br /&gt;
&lt;br /&gt;
That is why it usually makes the most sense to focus on Guardian first instead of rushing straight into PvP.&lt;br /&gt;
&lt;br /&gt;
== Tips for Guardian ==&lt;br /&gt;
[[File:Screenshot 2026-05-03 200053.png|thumb|150px|alt=Carnival Pro Coin Gacha|Carnival Pro Coin Gacha]]&lt;br /&gt;
To maximize the rewards from playing Guardian, buy the &amp;quot;Carnival Pro Coin&amp;quot; Gacha with the initial Gold given to new Characters. The droprate of the Gold and EXP rings are greatest in the &amp;quot;Carnival Pro Coin&amp;quot; Gacha. The Gold and EXP rings doubles the rewards (Gold x2 / EXP x2). It is a consumable item, once equipped it will use 1 ring per round.&lt;br /&gt;
[[File:Housing.png|thumb|150px|alt=Housing|Housing]]&lt;br /&gt;
Once you have reached a certain amount of Gold, it is recommended to invest in a House. Houses will provide additional gold and EXP boosts. A Level 1 House gives 30% Bonus to Gold and and EXP and a Level 4 House up to 45%. Before you can level up your house, you have to reach a certain level of Housing points, those and the maximum House bonus can be aquired by buying furniture and filling your House on the Main Screen.&lt;br /&gt;
&lt;br /&gt;
== Basic and Battle ==&lt;br /&gt;
&lt;br /&gt;
Once you have made progress in Guardian, you will be in a much better position to move into Basic and Battle.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Basic&#039;&#039;&#039; is the core competitive tennis experience.&lt;br /&gt;
* &#039;&#039;&#039;Battle&#039;&#039;&#039; is more action-heavy and chaotic.&lt;br /&gt;
* &#039;&#039;&#039;Guardian&#039;&#039;&#039; is the main early progression path.&lt;br /&gt;
&lt;br /&gt;
Basic and Battle make much more sense once you already have some levels, items, and general experience with the game.&lt;br /&gt;
&lt;br /&gt;
== Community and Help ==&lt;br /&gt;
&lt;br /&gt;
Do not be afraid to ask other players for help.&lt;br /&gt;
&lt;br /&gt;
Fantasy Tennis can feel confusing at the beginning, especially when it comes to progression, items, builds, and knowing what to do first. One of the best things you can do as a beginner is connect with other players.&lt;br /&gt;
&lt;br /&gt;
Ask questions in Discord, in game, or anywhere the community is active. Most experienced players would rather answer a simple question than see a new player quit because the game feels overwhelming.&lt;br /&gt;
&lt;br /&gt;
Getting help from other players can make the early game much easier and help you understand the game faster.&lt;br /&gt;
&lt;br /&gt;
== Recommended Progression ==&lt;br /&gt;
&lt;br /&gt;
The simplest beginner path is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tutorial → Guardian → Basic/Battle&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
First learn the game, then build your character in Guardian, and then move into the other modes once you have a stronger foundation.&lt;br /&gt;
&lt;br /&gt;
== Common Beginner Mistakes ==&lt;br /&gt;
&lt;br /&gt;
* Skipping the tutorial&lt;br /&gt;
* Going into Basic or Battle too early&lt;br /&gt;
* Ignoring Guardian progression&lt;br /&gt;
* Overthinking builds too soon&lt;br /&gt;
* Playing too aggressively without understanding the game first&lt;br /&gt;
* Not asking other players for help when confused&lt;br /&gt;
&lt;br /&gt;
== Final Tips ==&lt;br /&gt;
&lt;br /&gt;
If you are completely new to Fantasy Tennis, do the tutorial first and then focus mainly on Guardian Mode.&lt;br /&gt;
&lt;br /&gt;
Use Guardian to level up, collect items, and strengthen your character. Once you have built a better foundation, moving into Basic and Battle will feel much more natural and rewarding.&lt;br /&gt;
&lt;br /&gt;
Also, try to connect with other players. Asking for help in Discord or in game can save you a lot of confusion and help you improve much faster.&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Troubleshooting&amp;diff=146</id>
		<title>Troubleshooting</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Troubleshooting&amp;diff=146"/>
		<updated>2026-05-04T10:01:29Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Troubleshooting &amp;amp; Fixes =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
This page covers common problems and fixes for Fantasy Tennis.&lt;br /&gt;
&lt;br /&gt;
If the game does not start correctly, shows login errors, disconnects with version-related messages, or lag delay, check the sections below.&lt;br /&gt;
&lt;br /&gt;
== Common Issues ==&lt;br /&gt;
&lt;br /&gt;
=== Old Version Error (4002, -62) ===&lt;br /&gt;
&lt;br /&gt;
If you receive the following message:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;You have been disconnected because you tried to connect with an old version.  &lt;br /&gt;
Application will now shut down.  &lt;br /&gt;
(code = 4002, -62)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
as of now, there are 4 common causes for this problem:&lt;br /&gt;
&lt;br /&gt;
* C++ 2015 (x86) is not installed&lt;br /&gt;
* &#039;&#039;&#039;d3dx9_43.dll&#039;&#039;&#039; is missing&lt;br /&gt;
* Windows Firewall, Defender, or antivirus deleted &#039;&#039;&#039;JFTSE.dll&#039;&#039;&#039;&lt;br /&gt;
* The game files are out of date&lt;br /&gt;
&lt;br /&gt;
==== Fix 1: C++ 2015 (x86) is not installed ====&lt;br /&gt;
&lt;br /&gt;
Install the Visual C++ Redistributable (x86):&lt;br /&gt;
&lt;br /&gt;
[https://aka.ms/vs/17/release/vc_redist.x86.exe Download C++ 2015 x86 Redistributable]&lt;br /&gt;
&lt;br /&gt;
==== Fix 2: d3dx9_43.dll is missing ====&lt;br /&gt;
&lt;br /&gt;
If you receive an error saying that &#039;&#039;&#039;d3dx9_43.dll&#039;&#039;&#039; is missing, install the DirectX runtime:&lt;br /&gt;
&lt;br /&gt;
[https://www.microsoft.com/en-us/download/details.aspx?id=35 Download DirectX End-User Runtime]&lt;br /&gt;
&lt;br /&gt;
==== Fix 3: JFTSE.dll was deleted ====&lt;br /&gt;
&lt;br /&gt;
Windows Defender, Firewall, or third-party antivirus software may block, quarantine, or delete &#039;&#039;&#039;JFTSE.dll&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;What to do:&#039;&#039;&#039;&lt;br /&gt;
* Check your antivirus quarantine&lt;br /&gt;
* Restore &#039;&#039;&#039;JFTSE.dll&#039;&#039;&#039; if it was removed&lt;br /&gt;
* Add the full game folder to your antivirus or Defender exclusions&lt;br /&gt;
* Make sure the anti-cheat files are not being blocked&lt;br /&gt;
&lt;br /&gt;
==== Fix 4: Files are out of date ====&lt;br /&gt;
&lt;br /&gt;
Always launch the game with:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;FT_Launcher.exe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This will check for updates and download missing or outdated files.&lt;br /&gt;
&lt;br /&gt;
=== User Already Logged In (4002, -2) ===&lt;br /&gt;
&lt;br /&gt;
If you receive the following message:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;This user id is currently logged on.  &lt;br /&gt;
Please try again later.  &lt;br /&gt;
(code = 4002, -2)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
then your account is still marked as logged in on the server.&lt;br /&gt;
&lt;br /&gt;
This can happen if the game did not close correctly, the connection was interrupted, or the previous session did not reset properly.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;What to do:&#039;&#039;&#039;&lt;br /&gt;
* Wait a few minutes and try again&lt;br /&gt;
* Make sure the game is fully closed&lt;br /&gt;
* Restart the launcher and try again&lt;br /&gt;
* If the problem continues, contact the moderators through &#039;&#039;&#039;#support-ticket&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== This User ID has expired. (4002, -3) ===&lt;br /&gt;
&lt;br /&gt;
If you see this message, your account has not been confirmed yet and access to the game is denied.&lt;br /&gt;
&lt;br /&gt;
For newly registered accounts, you must confirm your account using the registration confirmation email sent by &#039;&#039;&#039;JFTSE Security&#039;&#039;&#039;. This email may appear in your &#039;&#039;&#039;Spam&#039;&#039;&#039; folder.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;What to do:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Check the email address you used during registration. Look in your inbox and &#039;&#039;&#039;Spam&#039;&#039;&#039; folder for an email from &#039;&#039;&#039;JFTSE Security&#039;&#039;&#039;, then click the confirmation link inside.&lt;br /&gt;
&lt;br /&gt;
* If you cannot find the confirmation email or the link has expired, request a new registration confirmation email from the website.&lt;br /&gt;
&lt;br /&gt;
* If you used the wrong email address during registration or still cannot receive the email, contact the moderators through &#039;&#039;&#039;#support-ticket&#039;&#039;&#039; and click &#039;&#039;&#039;Create Ticket&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Recommended Fix Order ==&lt;br /&gt;
&lt;br /&gt;
If you are not sure what is causing the issue, try the following in order:&lt;br /&gt;
&lt;br /&gt;
# Install the C++ 2015 (x86) Redistributable&lt;br /&gt;
# Install the DirectX runtime&lt;br /&gt;
# Check whether &#039;&#039;&#039;JFTSE.dll&#039;&#039;&#039; was deleted or quarantined&lt;br /&gt;
# Run &#039;&#039;&#039;FT_Launcher.exe&#039;&#039;&#039;&lt;br /&gt;
# Restart the PC and try again&lt;br /&gt;
# If the account is still marked as logged in, contact &#039;&#039;&#039;@ModMail&#039;&#039;&#039; on Discord&lt;br /&gt;
&lt;br /&gt;
== Mac Users ==&lt;br /&gt;
&lt;br /&gt;
Fantasy Tennis is a Windows game.&lt;br /&gt;
&lt;br /&gt;
Mac users may be able to run the game through &#039;&#039;&#039;Parallels Desktop&#039;&#039;&#039; by installing Windows in a virtual machine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Possible setup:&#039;&#039;&#039;&lt;br /&gt;
* Install &#039;&#039;&#039;Parallels Desktop&#039;&#039;&#039;&lt;br /&gt;
* Install &#039;&#039;&#039;Windows&#039;&#039;&#039;&lt;br /&gt;
* Install Fantasy Tennis inside Windows&lt;br /&gt;
* Run &#039;&#039;&#039;FT_Launcher.exe&#039;&#039;&#039;&lt;br /&gt;
* Start the game from the Windows environment&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039;&lt;br /&gt;
Compatibility may vary depending on the setup, especially with anti-cheat related components.&lt;br /&gt;
&lt;br /&gt;
== Reduce Lag, Delay via Vpn Gamer for South American Players ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Notice&#039;&#039;&#039;: This is &#039;&#039;&#039;information of public interest&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Since the JFTSE Fantasy Tennis server is located in &#039;&#039;&#039;Europe&#039;&#039;&#039;, the only way for South Americans to have a direct connection is through a gaming VPN; this is a fact. Although, depending on the region and routes, they may not suffer as much, they can still have an even better experience when trying to play in Europe.&lt;br /&gt;
&lt;br /&gt;
You can try using ExitLag&#039;s paid VPN, which costs $28 dollars for 365 days, and that&#039;s what Is recommended because, in addition to making the connection more stable, it also reduces lag, delay maximum as possible. Or you can use Cloudflare&#039;s with warp free VPN, which offers a significant reduction, although there&#039;s more variation.&lt;br /&gt;
&lt;br /&gt;
First, here are the links to &#039;&#039;&#039;free days to test ExitLag:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[https://www.exitlag.com/pt ExitLag em Português]&lt;br /&gt;
&lt;br /&gt;
[https://www.exitlag.com/es ExitLag en Español]&lt;br /&gt;
&lt;br /&gt;
How to use it? Always open the ExitLag program, search for Fantasy Tennis in the program&#039;s search bar, and select the Automatic Europe route. After applying the routes, then minimize the program and open FT_Launcher to play and enjoy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now cloudflare with warp free vpn:&lt;br /&gt;
&lt;br /&gt;
[https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/cloudflare-one-client/download/ Download the CloudFlare with warp in english]&lt;br /&gt;
&lt;br /&gt;
How to use it? Always open the CloudFlare program, click the gear icon, select the option 1.1.1.1 with warp, and activate it using the large button, then minimize the program and open FT_Launcher to play and enjoy.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039;&lt;br /&gt;
[https://www.exitlag.com/en ExitLag English Website]&lt;br /&gt;
ExitLag program works on almost all WORLD including America, Asia, Oceania and Africa. Recommended for windows 10, 11, but take attention windows 7 some players related wont working. Only by testing will you see if it actually improves things for you in practice, as many factors influence the final result of how efficient ExitLag will be for you.&lt;br /&gt;
&lt;br /&gt;
== Notes ==&lt;br /&gt;
&lt;br /&gt;
* Do not launch outdated executables directly if &#039;&#039;&#039;FT_Launcher.exe&#039;&#039;&#039; is available&lt;br /&gt;
* If the launcher does not repair the client, reinstall the game files and update again&lt;br /&gt;
* If security software keeps deleting files, create a permanent exclusion for the full game folder&lt;br /&gt;
* If login problems continue, use &#039;&#039;&#039;@ModMail&#039;&#039;&#039; on Discord&lt;br /&gt;
* &#039;&#039;&#039;Discord support: &#039;&#039;&#039;[https://discord.com/invite/YkxTGXwug3 Official Discord Server]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=JFTSE_Wiki&amp;diff=145</id>
		<title>JFTSE Wiki</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=JFTSE_Wiki&amp;diff=145"/>
		<updated>2026-05-04T09:49:21Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= JFTSE Wiki =&lt;br /&gt;
&lt;br /&gt;
Welcome to the &#039;&#039;&#039;JFTSE Wiki&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:red; font-size: 16px;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;b&amp;gt;Please [https://wiki.jftse.com/index.php?title=Special:UserLogin Log In] with your JFTSE account to edit and contribute.&amp;lt;/b&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This Wiki is a community driven knowledge base for everything related to &#039;&#039;&#039;JFTSE and Fantasy Tennis&#039;&#039;&#039;. Here you will find guides, tutorials, tips, tricks, and information about gameplay mechanics, server configurations and much more.&lt;br /&gt;
&lt;br /&gt;
This Wiki should be considered a place where community members can create new informative webpages that cover specific topics, &lt;br /&gt;
and where fellow community members can contribute to and improve existing pages.&lt;br /&gt;
&lt;br /&gt;
We strongly believe in the idea that this Wiki should be a free and open method of overlaying information to everyone. Therefore, our stance is that everyone should be able to contribute to this Wiki, as long as the information is accurate and relevant. As a guest, you are free to access and read all the information the Wiki has to offer. As a community member, you are free to contribute to the Wiki by creating new pages, editing existing pages, and discussing the content of the Wiki with other community members as long as you conform to the [[JFTSE Wiki:Community Rules|Community Rules]].&lt;br /&gt;
&lt;br /&gt;
= 📚 Usage And Information =&lt;br /&gt;
=== Information ===&lt;br /&gt;
* [[About_JFTSE|About JFTSE]]&lt;br /&gt;
* [[About_Fantasy_Tennis|About Fantasy Tennis]]&lt;br /&gt;
* [[JFTSE Wiki:Community Rules|Community Rules]]&lt;br /&gt;
&lt;br /&gt;
=== Contributing ===&lt;br /&gt;
&amp;lt;strong&amp;gt;Only registered JFTSE users can edit pages.&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
To contribute, [https://wiki.jftse.com/index.php?title=Special:UserLogin Log In] with your JFTSE account and help expand the knowledge base!&amp;lt;br&amp;gt;&lt;br /&gt;
Please read and follow the [[JFTSE Wiki:Wiki Guidelines|Wiki Guidelines]] before contributing.&lt;br /&gt;
&lt;br /&gt;
= 🛠 Getting Started =&lt;br /&gt;
* [[Game Lore]] - Dive into the world of Fantasy Tennis.&lt;br /&gt;
* [[Game Installation Guide]] - Step-by-step guide on installing the game.&lt;br /&gt;
* [[Beginner&#039;s Guide]] - Basics of gameplay, controls and UI.&lt;br /&gt;
* [[Gameplay Mechanics]] - Strategies, combos and in-depth gameplay mechanics.&lt;br /&gt;
* [[Troubleshooting|Troubleshooting &amp;amp; Fixes]] - Solutions for common issues.&lt;br /&gt;
&lt;br /&gt;
= 📖 Game Information =&lt;br /&gt;
* [[Characters]] - Information about the characters in Fantasy Tennis.&lt;br /&gt;
* [[Items]] - List of items and their effects.&lt;br /&gt;
* [[Skills]] - List of skills and their effects.&lt;br /&gt;
* [[Maps]] - Learn about the different maps in Fantasy Tennis.&lt;br /&gt;
* [[Game Modes]] - Information about the different game modes.&lt;br /&gt;
* [[Guides &amp;amp; Tutorials]] - Learn tips and tricks from experienced players.&lt;br /&gt;
&lt;br /&gt;
= 📜 Server Information =&lt;br /&gt;
* [[Database Schema &amp;amp; Cheatsheet]] - Database Schema used for Fantasy Tennis &amp;amp; Cheatsheet.&lt;br /&gt;
* [[Server Configuration]] - Information about server configuration.&lt;br /&gt;
* [[Packet Schema (.packet) Format]] - Defines the schema language used to declare and generate network packets.&lt;br /&gt;
&lt;br /&gt;
= 🔗 Useful Links =&lt;br /&gt;
* [https://jftse.com/ JFTSE Website]&lt;br /&gt;
* [https://discord.gg/YkxTGXwug3 Discord]&lt;br /&gt;
* [[Special:RecentChanges|Recent Changes]]&lt;br /&gt;
* [[Special:AllPages|All Pages]]&lt;br /&gt;
* [[Special:Random|Random Page]]&lt;br /&gt;
* [[Special:Categories|Categories]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Game_Installation_Guide&amp;diff=101</id>
		<title>Game Installation Guide</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Game_Installation_Guide&amp;diff=101"/>
		<updated>2026-03-16T15:29:31Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Undo revision 96 by RiggedFish (talk)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== &amp;lt;big&amp;gt;&#039;&#039;&#039;FantaTennis – Steam Deck Compatibility &amp;amp; Setup Guide&#039;&#039;&#039;&amp;lt;/big&amp;gt; ==&lt;br /&gt;
&lt;br /&gt;
This entry documents the current working method for running FantaTennis on the Steam Deck. &lt;br /&gt;
&lt;br /&gt;
Due to the game’s launcher, patcher, and anti‑cheat system, it cannot be launched directly through Steam, regardless of Proton version. &lt;br /&gt;
&lt;br /&gt;
The procedure below outlines the reliable way to install, patch, and run the game.&lt;br /&gt;
&lt;br /&gt;
== &amp;lt;big&amp;gt;&#039;&#039;&#039;Overview&#039;&#039;&#039;&amp;lt;/big&amp;gt; ==&lt;br /&gt;
&lt;br /&gt;
FantaTennis does not natively support SteamOS or Proton. &lt;br /&gt;
&lt;br /&gt;
The game’s patcher and anti‑cheat fail to initialize when launched through Steam, making Desktop Mode and a Wine frontend (&#039;&#039;Q4Wine&#039;&#039;) mandatory. &lt;br /&gt;
&lt;br /&gt;
Once the initial setup is complete, your settings and input configuration persist, but the game must still be launched through Desktop Mode.&lt;br /&gt;
&lt;br /&gt;
== &amp;lt;big&amp;gt;&#039;&#039;&#039;Prerequisites&#039;&#039;&#039;&amp;lt;/big&amp;gt; ==&lt;br /&gt;
&lt;br /&gt;
* Steam Deck running SteamOS (&#039;&#039;current version&#039;&#039;)&lt;br /&gt;
* &lt;br /&gt;
* Desktop Mode access&lt;br /&gt;
* &lt;br /&gt;
* Q4Wine installed&lt;br /&gt;
* &lt;br /&gt;
* FantaTennis JTFSE game files downloaded from the official JTFSE website&lt;br /&gt;
* &lt;br /&gt;
&lt;br /&gt;
== &#039;&#039;&#039;&amp;lt;big&amp;gt;Installation &amp;amp; First‑Time Setup&amp;lt;/big&amp;gt;&#039;&#039;&#039; ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;1. Switch to Desktop Mode&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Hold the Power Button → select Switch to Desktop.&lt;br /&gt;
* &lt;br /&gt;
* FantaTennis will not launch through Steam, regardless of Proton version.&lt;br /&gt;
* &lt;br /&gt;
* The patcher and anti‑cheat also fail under Steam’s runtime.&lt;br /&gt;
* &lt;br /&gt;
* You must launch the game from Desktop Mode every time.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;2. Download the Game&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Download the FantaTennis JTFSE installer from the official JTFSE website.&lt;br /&gt;
* &lt;br /&gt;
* Extract or place the game folder wherever you prefer on your Steam Deck’s storage.&lt;br /&gt;
* &lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;3. Install Q4Wine&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
* &lt;br /&gt;
* Open Discover in Desktop Mode.&lt;br /&gt;
* &lt;br /&gt;
* Search for Q4Wine and install it.&lt;br /&gt;
* &lt;br /&gt;
* Q4Wine will serve as the launcher for both the patcher and the game executable.&lt;br /&gt;
* &lt;br /&gt;
&lt;br /&gt;
== &amp;lt;big&amp;gt;&#039;&#039;&#039;Running the Patcher (&amp;lt;small&amp;gt;&#039;&#039;Mandatory First Step&#039;&#039;&amp;lt;/small&amp;gt;)&#039;&#039;&#039;&amp;lt;/big&amp;gt; ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;4. Launch FT_Launcher.exe via Q4Wine&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Open Q4Wine.&lt;br /&gt;
* Add or navigate to the FantaTennis folder.&lt;br /&gt;
* &lt;br /&gt;
* Right‑click FT_Launcher.exe → Run.&lt;br /&gt;
* &lt;br /&gt;
* Open Advanced Options and set:&lt;br /&gt;
 Priority = 0  &lt;br /&gt;
 (This is required for the launcher to behave correctly.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;5. Allow the Patcher to Run&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* The launcher will begin downloading and applying patches.&lt;br /&gt;
* &lt;br /&gt;
* Let it complete without interruption.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;6. Do NOT Start the Game After Patching&#039;&#039;&#039;&amp;lt;/big&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* When the patcher finishes, do not click any BAT files or the Start button.&lt;br /&gt;
* &lt;br /&gt;
* Starting the game at this stage will cause anti‑cheat issues.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;7. Close the Patcher&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Once the folder size increases and all patches are applied, close the launcher.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;8. Close Q4Wine Completely&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Ensure all Q4Wine sockets/tickets are cleared.&lt;br /&gt;
* &lt;br /&gt;
* Optionally reboot your Steam Deck to guarantee a clean state.&lt;br /&gt;
&lt;br /&gt;
== &amp;lt;big&amp;gt;&#039;&#039;&#039;First Actual Game Launch (&amp;lt;small&amp;gt;&#039;&#039;Required Once&#039;&#039;&amp;lt;/small&amp;gt;)&#039;&#039;&#039;&amp;lt;/big&amp;gt; ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;9. Launch FantaTennis.exe via Q4Wine&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Open Q4Wine again and run FantaTennis.exe directly.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;10. Set Virtual Desktop Size&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* In the Q4Wine run window, set:&lt;br /&gt;
* &lt;br /&gt;
  Virtual Desktop Size = 1920×1080&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;11. Set Priority&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Under Advanced Options, set:&lt;br /&gt;
&lt;br /&gt;
  Priority = 0&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;12. Confirm Settings&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Click OK to launch.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;13. Wait for Anti‑Cheat Initialization&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* The anti‑cheat will patch and install required components.&lt;br /&gt;
* &lt;br /&gt;
* This may take a moment; do not interrupt it.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;14. Game Launches&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* You can now log in.&lt;br /&gt;
* &lt;br /&gt;
* Adjust your Settings immediately:&lt;br /&gt;
* &lt;br /&gt;
* Configure controls for Steam Deck input instead of keyboard.&lt;br /&gt;
* &lt;br /&gt;
* These settings will persist for future sessions.&lt;br /&gt;
&lt;br /&gt;
== &amp;lt;big&amp;gt;&#039;&#039;&#039;Regular Use After Initial Setup&#039;&#039;&#039;&amp;lt;/big&amp;gt; ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;15. Using the Launcher Normally&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* After the first successful launch, you may return to using FT_Launcher.exe.&lt;br /&gt;
* &lt;br /&gt;
* This ensures the game is patched before each session.&lt;br /&gt;
* &lt;br /&gt;
* Once patched, you can launch the game normally through the launcher.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;16. Anti‑Cheat Stuck?&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* If the game hangs on the anti‑cheat screen after launching from the launcher:&lt;br /&gt;
* &lt;br /&gt;
* Restart your Steam Deck.&lt;br /&gt;
* The anti‑cheat can become stuck and cannot unload itself.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;17. Recommended Reliable Startup Method&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;For a guaranteed clean launch every time:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Run FT_Launcher.exe first.&lt;br /&gt;
* &lt;br /&gt;
* Let it check and apply patches.&lt;br /&gt;
* &lt;br /&gt;
* Close the launcher completely.&lt;br /&gt;
* &lt;br /&gt;
* Launch FantaTennis.exe directly via Q4Wine (&#039;&#039;same method as the first launch&#039;&#039;).&lt;br /&gt;
* &lt;br /&gt;
&#039;&#039;&#039;This avoids anti‑cheat lockups and ensures consistent startup success.&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Packet_Structure&amp;diff=100</id>
		<title>Packet Structure</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Packet_Structure&amp;diff=100"/>
		<updated>2026-03-16T15:24:10Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Packet Structure =&lt;br /&gt;
&lt;br /&gt;
All communication between the client and server uses a binary packet format.&lt;br /&gt;
&lt;br /&gt;
Each packet consists of a fixed 8 byte header (metadata) followed by a variable length payload.&lt;br /&gt;
&lt;br /&gt;
== Packet Layout ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Offset   Size   Field&lt;br /&gt;
0x00     2      serialnum&lt;br /&gt;
0x02     2      checksum&lt;br /&gt;
0x04     2      packet id&lt;br /&gt;
0x06     2      data length&lt;br /&gt;
0x08     n      data (payload)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Header Fields ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Size&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| serialnum&lt;br /&gt;
| 2 bytes&lt;br /&gt;
| Serial value used by the protocol for packet validation and sequencing.&lt;br /&gt;
|-&lt;br /&gt;
| checksum&lt;br /&gt;
| 2 bytes&lt;br /&gt;
| Checksum used by the protocol to verify packet integrity.&lt;br /&gt;
|-&lt;br /&gt;
| packet id&lt;br /&gt;
| 2 bytes&lt;br /&gt;
| Identifier that determines the packet type and how the payload should be interpreted.&lt;br /&gt;
|-&lt;br /&gt;
| data length&lt;br /&gt;
| 2 bytes&lt;br /&gt;
| Length of the payload (data) in bytes.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Payload ==&lt;br /&gt;
&lt;br /&gt;
The payload immediately follows the header and contains packet specific data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
data length = number of bytes following the header&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The structure of the payload depends entirely on the packetId.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The maximum packet size is limited to &#039;&#039;&#039;16384 bytes&#039;&#039;&#039;.  &lt;br /&gt;
Since the packet header has a fixed size of &#039;&#039;&#039;8 bytes&#039;&#039;&#039;, the remaining &#039;&#039;&#039;16376 bytes&#039;&#039;&#039; are available for payload data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Maximum packet size : 16384 bytes&lt;br /&gt;
Header size         : 8 bytes&lt;br /&gt;
Maximum payload     : 16376 bytes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Example Packet ==&lt;br /&gt;
&lt;br /&gt;
The following packet was captured from the server while processing a point update during a match.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2026-03-16 14:57:47,231 [epollEventLoopGroup-3-2] PacketLogger [decode] DEBUG RECV [12 bytes]&lt;br /&gt;
CMSGPoint { &amp;quot;id&amp;quot;: &amp;quot;0x183F&amp;quot;, &amp;quot;len&amp;quot;: 4, &amp;quot;data&amp;quot;: { &amp;quot;pointsTeam&amp;quot;: 1, &amp;quot;unk0&amp;quot;: 0, &amp;quot;ballState&amp;quot;: 4, &amp;quot;playerPosition&amp;quot;: 10 } }&lt;br /&gt;
&lt;br /&gt;
--- Hex Dump ---&lt;br /&gt;
B8 79 BF 07 3F 18 04 00    01 00 04 0A&lt;br /&gt;
===&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The total packet size is 12 bytes:&lt;br /&gt;
&lt;br /&gt;
* 8 bytes header&lt;br /&gt;
* 4 bytes payload&lt;br /&gt;
&lt;br /&gt;
=== Header ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
B8 79 -&amp;gt; serialnum&lt;br /&gt;
BF 07 -&amp;gt; checksum&lt;br /&gt;
3F 18 -&amp;gt; packet id (0x183F)&lt;br /&gt;
04 00 -&amp;gt; data length (4 bytes)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Payload ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
01 00 04 0A&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Which the packet parser interprets as:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Value&lt;br /&gt;
! Hex&lt;br /&gt;
|-&lt;br /&gt;
| pointsTeam&lt;br /&gt;
| 1&lt;br /&gt;
| 01&lt;br /&gt;
|-&lt;br /&gt;
| unk0&lt;br /&gt;
| 0&lt;br /&gt;
| 00&lt;br /&gt;
|-&lt;br /&gt;
| ballState&lt;br /&gt;
| 4&lt;br /&gt;
| 04&lt;br /&gt;
|-&lt;br /&gt;
| playerPosition&lt;br /&gt;
| 10&lt;br /&gt;
| 0A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Packet Interpretation ===&lt;br /&gt;
&lt;br /&gt;
From the packet metadata:&lt;br /&gt;
&lt;br /&gt;
* packet id = &#039;&#039;&#039;0x183F&#039;&#039;&#039;&lt;br /&gt;
* payload length = &#039;&#039;&#039;4 bytes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The server maps this packetId to the packet type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CMSGPoint&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The payload is then decoded according to the structure defined for that packet type.&lt;br /&gt;
&lt;br /&gt;
== Byte Order ==&lt;br /&gt;
&lt;br /&gt;
All packet fields are encoded using little-endian byte order.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
17 00 -&amp;gt; 0x0017 -&amp;gt; 23&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Packet Interface Contract ==&lt;br /&gt;
&lt;br /&gt;
All packets in the server implementation follow the IPacket interface.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
package com.jftse.server.core.protocol;&lt;br /&gt;
&lt;br /&gt;
public interface IPacket {&lt;br /&gt;
&lt;br /&gt;
    byte[] toBytes();&lt;br /&gt;
&lt;br /&gt;
    char getDataLength();&lt;br /&gt;
&lt;br /&gt;
    char getPacketId();&lt;br /&gt;
&lt;br /&gt;
    char getCheckSerial();&lt;br /&gt;
&lt;br /&gt;
    char getCheckSum();&lt;br /&gt;
&lt;br /&gt;
    String toString();&lt;br /&gt;
&lt;br /&gt;
    // must be implemented by the concrete class for field deserialization&lt;br /&gt;
    static &amp;lt;T extends IPacket&amp;gt; T fromBytes(byte[] packet) {&lt;br /&gt;
        throw new UnsupportedOperationException(&amp;quot;fromBytes method not implemented&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This interface defines the minimal contract required for packet serialization and metadata access.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;fromBytes(payload)&amp;lt;/code&amp;gt; is called by the Packet Deserializer, a registered packet class must implement it.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
&lt;br /&gt;
Example implementation for CMSG_AuthLogin (auto-generated).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public static CMSGAuthLogin fromBytes(byte[] rawData) {&lt;br /&gt;
    CMSGAuthLogin msg = new CMSGAuthLogin();&lt;br /&gt;
    ByteBuffer buffer = ByteBuffer.wrap(rawData).order(ByteOrder.nativeOrder());&lt;br /&gt;
&lt;br /&gt;
    msg.metaData.checkSerial = buffer.getChar(0);&lt;br /&gt;
    msg.metaData.checkSum = buffer.getChar(2);&lt;br /&gt;
    msg.metaData.packetId = buffer.getChar(4);&lt;br /&gt;
    msg.metaData.dataLen = buffer.getChar(6);&lt;br /&gt;
&lt;br /&gt;
    msg.data = new byte[msg.metaData.dataLen];&lt;br /&gt;
    BitKit.blockCopy(rawData, 8, msg.data, 0, msg.metaData.dataLen);&lt;br /&gt;
&lt;br /&gt;
    msg.username = msg.readString();&lt;br /&gt;
    msg.token = msg.readFixedString(16);&lt;br /&gt;
    msg.timestamp = msg.readLong();&lt;br /&gt;
    return msg;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Packet Schema ==&lt;br /&gt;
&lt;br /&gt;
Packet structures themselves are defined using the &#039;&#039;&#039;[[Packet Schema (.packet) Format]]&#039;&#039;&#039;.  &lt;br /&gt;
These schema files describe the payload layout for each packet id and are used to generate the corresponding packet classes automatically.&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;br /&gt;
[[Category:Protocol]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Packet_Structure&amp;diff=99</id>
		<title>Packet Structure</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Packet_Structure&amp;diff=99"/>
		<updated>2026-03-16T15:23:20Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Packet Structure =&lt;br /&gt;
&lt;br /&gt;
All communication between the client and server uses a binary packet format.&lt;br /&gt;
&lt;br /&gt;
Each packet consists of a fixed 8 byte header (metadata) followed by a variable length payload.&lt;br /&gt;
&lt;br /&gt;
== Packet Layout ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Offset   Size   Field&lt;br /&gt;
0x00     2      serialnum&lt;br /&gt;
0x02     2      checksum&lt;br /&gt;
0x04     2      packet id&lt;br /&gt;
0x06     2      data length&lt;br /&gt;
0x08     n      data (payload)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Header Fields ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Size&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| serialnum&lt;br /&gt;
| 2 bytes&lt;br /&gt;
| Serial value used by the protocol for packet validation and sequencing.&lt;br /&gt;
|-&lt;br /&gt;
| checksum&lt;br /&gt;
| 2 bytes&lt;br /&gt;
| Checksum used by the protocol to verify packet integrity.&lt;br /&gt;
|-&lt;br /&gt;
| packet id&lt;br /&gt;
| 2 bytes&lt;br /&gt;
| Identifier that determines the packet type and how the payload should be interpreted.&lt;br /&gt;
|-&lt;br /&gt;
| data length&lt;br /&gt;
| 2 bytes&lt;br /&gt;
| Length of the payload (data) in bytes.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Payload ==&lt;br /&gt;
&lt;br /&gt;
The payload immediately follows the header and contains packet specific data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dataLength = number of bytes following the header&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The structure of the payload depends entirely on the packetId.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The maximum packet size is limited to &#039;&#039;&#039;16384 bytes&#039;&#039;&#039;.  &lt;br /&gt;
Since the packet header has a fixed size of &#039;&#039;&#039;8 bytes&#039;&#039;&#039;, the remaining &#039;&#039;&#039;16376 bytes&#039;&#039;&#039; are available for payload data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Maximum packet size : 16384 bytes&lt;br /&gt;
Header size         : 8 bytes&lt;br /&gt;
Maximum payload     : 16376 bytes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Example Packet ==&lt;br /&gt;
&lt;br /&gt;
The following packet was captured from the server while processing a point update during a match.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2026-03-16 14:57:47,231 [epollEventLoopGroup-3-2] PacketLogger [decode] DEBUG RECV [12 bytes]&lt;br /&gt;
CMSGPoint { &amp;quot;id&amp;quot;: &amp;quot;0x183F&amp;quot;, &amp;quot;len&amp;quot;: 4, &amp;quot;data&amp;quot;: { &amp;quot;pointsTeam&amp;quot;: 1, &amp;quot;unk0&amp;quot;: 0, &amp;quot;ballState&amp;quot;: 4, &amp;quot;playerPosition&amp;quot;: 10 } }&lt;br /&gt;
&lt;br /&gt;
--- Hex Dump ---&lt;br /&gt;
B8 79 BF 07 3F 18 04 00    01 00 04 0A&lt;br /&gt;
===&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The total packet size is 12 bytes:&lt;br /&gt;
&lt;br /&gt;
* 8 bytes header&lt;br /&gt;
* 4 bytes payload&lt;br /&gt;
&lt;br /&gt;
=== Header ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
B8 79 -&amp;gt; serialnum&lt;br /&gt;
BF 07 -&amp;gt; checksum&lt;br /&gt;
3F 18 -&amp;gt; packet id (0x183F)&lt;br /&gt;
04 00 -&amp;gt; data length (4 bytes)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Payload ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
01 00 04 0A&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Which the packet parser interprets as:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Value&lt;br /&gt;
! Hex&lt;br /&gt;
|-&lt;br /&gt;
| pointsTeam&lt;br /&gt;
| 1&lt;br /&gt;
| 01&lt;br /&gt;
|-&lt;br /&gt;
| unk0&lt;br /&gt;
| 0&lt;br /&gt;
| 00&lt;br /&gt;
|-&lt;br /&gt;
| ballState&lt;br /&gt;
| 4&lt;br /&gt;
| 04&lt;br /&gt;
|-&lt;br /&gt;
| playerPosition&lt;br /&gt;
| 10&lt;br /&gt;
| 0A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Packet Interpretation ===&lt;br /&gt;
&lt;br /&gt;
From the packet metadata:&lt;br /&gt;
&lt;br /&gt;
* packet id = &#039;&#039;&#039;0x183F&#039;&#039;&#039;&lt;br /&gt;
* payload length = &#039;&#039;&#039;4 bytes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The server maps this packetId to the packet type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CMSGPoint&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The payload is then decoded according to the structure defined for that packet type.&lt;br /&gt;
&lt;br /&gt;
== Byte Order ==&lt;br /&gt;
&lt;br /&gt;
All packet fields are encoded using little-endian byte order.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
17 00 -&amp;gt; 0x0017 -&amp;gt; 23&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Packet Interface Contract ==&lt;br /&gt;
&lt;br /&gt;
All packets in the server implementation follow the IPacket interface.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
package com.jftse.server.core.protocol;&lt;br /&gt;
&lt;br /&gt;
public interface IPacket {&lt;br /&gt;
&lt;br /&gt;
    byte[] toBytes();&lt;br /&gt;
&lt;br /&gt;
    char getDataLength();&lt;br /&gt;
&lt;br /&gt;
    char getPacketId();&lt;br /&gt;
&lt;br /&gt;
    char getCheckSerial();&lt;br /&gt;
&lt;br /&gt;
    char getCheckSum();&lt;br /&gt;
&lt;br /&gt;
    String toString();&lt;br /&gt;
&lt;br /&gt;
    // must be implemented by the concrete class for field deserialization&lt;br /&gt;
    static &amp;lt;T extends IPacket&amp;gt; T fromBytes(byte[] packet) {&lt;br /&gt;
        throw new UnsupportedOperationException(&amp;quot;fromBytes method not implemented&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This interface defines the minimal contract required for packet serialization and metadata access.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;fromBytes(payload)&amp;lt;/code&amp;gt; is called by the Packet Deserializer, a registered packet class must implement it.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
&lt;br /&gt;
Example implementation for CMSG_AuthLogin (auto-generated).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public static CMSGAuthLogin fromBytes(byte[] rawData) {&lt;br /&gt;
    CMSGAuthLogin msg = new CMSGAuthLogin();&lt;br /&gt;
    ByteBuffer buffer = ByteBuffer.wrap(rawData).order(ByteOrder.nativeOrder());&lt;br /&gt;
&lt;br /&gt;
    msg.metaData.checkSerial = buffer.getChar(0);&lt;br /&gt;
    msg.metaData.checkSum = buffer.getChar(2);&lt;br /&gt;
    msg.metaData.packetId = buffer.getChar(4);&lt;br /&gt;
    msg.metaData.dataLen = buffer.getChar(6);&lt;br /&gt;
&lt;br /&gt;
    msg.data = new byte[msg.metaData.dataLen];&lt;br /&gt;
    BitKit.blockCopy(rawData, 8, msg.data, 0, msg.metaData.dataLen);&lt;br /&gt;
&lt;br /&gt;
    msg.username = msg.readString();&lt;br /&gt;
    msg.token = msg.readFixedString(16);&lt;br /&gt;
    msg.timestamp = msg.readLong();&lt;br /&gt;
    return msg;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Packet Schema ==&lt;br /&gt;
&lt;br /&gt;
Packet structures themselves are defined using the &#039;&#039;&#039;[[Packet Schema (.packet) Format]]&#039;&#039;&#039;.  &lt;br /&gt;
These schema files describe the payload layout for each packet id and are used to generate the corresponding packet classes automatically.&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;br /&gt;
[[Category:Protocol]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Category:Protocol&amp;diff=98</id>
		<title>Category:Protocol</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Category:Protocol&amp;diff=98"/>
		<updated>2026-03-16T15:07:51Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created blank page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Packet_Structure&amp;diff=97</id>
		<title>Packet Structure</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Packet_Structure&amp;diff=97"/>
		<updated>2026-03-16T15:07:28Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;= Packet Structure =  All communication between the client and server uses a binary packet format.  Each packet consists of a fixed 8 byte header (metadata) followed by a variable length payload.  == Packet Layout ==  &amp;lt;pre&amp;gt; Offset   Size   Field 0x00     2      checkSerial 0x02     2      checkSum 0x04     2      packetId 0x06     2      dataLength 0x08     n      data (payload) &amp;lt;/pre&amp;gt;  == Header Fields ==  {| class=&amp;quot;wikitable&amp;quot; ! Field ! Size ! Description |- | checkSeri...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Packet Structure =&lt;br /&gt;
&lt;br /&gt;
All communication between the client and server uses a binary packet format.&lt;br /&gt;
&lt;br /&gt;
Each packet consists of a fixed 8 byte header (metadata) followed by a variable length payload.&lt;br /&gt;
&lt;br /&gt;
== Packet Layout ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Offset   Size   Field&lt;br /&gt;
0x00     2      checkSerial&lt;br /&gt;
0x02     2      checkSum&lt;br /&gt;
0x04     2      packetId&lt;br /&gt;
0x06     2      dataLength&lt;br /&gt;
0x08     n      data (payload)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Header Fields ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Size&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| checkSerial&lt;br /&gt;
| 2 bytes&lt;br /&gt;
| Serial value used by the protocol for packet validation and sequencing.&lt;br /&gt;
|-&lt;br /&gt;
| checkSum&lt;br /&gt;
| 2 bytes&lt;br /&gt;
| Checksum used by the protocol to verify packet integrity.&lt;br /&gt;
|-&lt;br /&gt;
| packetId&lt;br /&gt;
| 2 bytes&lt;br /&gt;
| Identifier that determines the packet type and how the payload should be interpreted.&lt;br /&gt;
|-&lt;br /&gt;
| dataLength&lt;br /&gt;
| 2 bytes&lt;br /&gt;
| Length of the payload (data) in bytes.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Payload ==&lt;br /&gt;
&lt;br /&gt;
The payload immediately follows the header and contains packet specific data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dataLength = number of bytes following the header&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The structure of the payload depends entirely on the packetId.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The maximum packet size is limited to &#039;&#039;&#039;16384 bytes&#039;&#039;&#039;.  &lt;br /&gt;
Since the packet header has a fixed size of &#039;&#039;&#039;8 bytes&#039;&#039;&#039;, the remaining &#039;&#039;&#039;16376 bytes&#039;&#039;&#039; are available for payload data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Maximum packet size : 16384 bytes&lt;br /&gt;
Header size         : 8 bytes&lt;br /&gt;
Maximum payload     : 16376 bytes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Example Packet ==&lt;br /&gt;
&lt;br /&gt;
The following packet was captured from the server while processing a point update during a match.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2026-03-16 14:57:47,231 [epollEventLoopGroup-3-2] PacketLogger [decode] DEBUG RECV [12 bytes]&lt;br /&gt;
CMSGPoint { &amp;quot;id&amp;quot;: &amp;quot;0x183F&amp;quot;, &amp;quot;len&amp;quot;: 4, &amp;quot;data&amp;quot;: { &amp;quot;pointsTeam&amp;quot;: 1, &amp;quot;unk0&amp;quot;: 0, &amp;quot;ballState&amp;quot;: 4, &amp;quot;playerPosition&amp;quot;: 10 } }&lt;br /&gt;
&lt;br /&gt;
--- Hex Dump ---&lt;br /&gt;
B8 79 BF 07 3F 18 04 00    01 00 04 0A&lt;br /&gt;
===&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The total packet size is 12 bytes:&lt;br /&gt;
&lt;br /&gt;
* 8 bytes header&lt;br /&gt;
* 4 bytes payload&lt;br /&gt;
&lt;br /&gt;
=== Header ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
B8 79 -&amp;gt; checkSerial&lt;br /&gt;
BF 07 -&amp;gt; checkSum&lt;br /&gt;
3F 18 -&amp;gt; packetId (0x183F)&lt;br /&gt;
04 00 -&amp;gt; dataLength (4 bytes)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Payload ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
01 00 04 0A&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Which the packet parser interprets as:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Value&lt;br /&gt;
! Hex&lt;br /&gt;
|-&lt;br /&gt;
| pointsTeam&lt;br /&gt;
| 1&lt;br /&gt;
| 01&lt;br /&gt;
|-&lt;br /&gt;
| unk0&lt;br /&gt;
| 0&lt;br /&gt;
| 00&lt;br /&gt;
|-&lt;br /&gt;
| ballState&lt;br /&gt;
| 4&lt;br /&gt;
| 04&lt;br /&gt;
|-&lt;br /&gt;
| playerPosition&lt;br /&gt;
| 10&lt;br /&gt;
| 0A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Packet Interpretation ===&lt;br /&gt;
&lt;br /&gt;
From the packet metadata:&lt;br /&gt;
&lt;br /&gt;
* packetId = &#039;&#039;&#039;0x183F&#039;&#039;&#039;&lt;br /&gt;
* payload length = &#039;&#039;&#039;4 bytes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The server maps this packetId to the packet type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CMSGPoint&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The payload is then decoded according to the structure defined for that packet type.&lt;br /&gt;
&lt;br /&gt;
== Byte Order ==&lt;br /&gt;
&lt;br /&gt;
All packet fields are encoded using little-endian byte order.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
17 00 -&amp;gt; 0x0017 -&amp;gt; 23&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Packet Interface Contract ==&lt;br /&gt;
&lt;br /&gt;
All packets in the server implementation follow the IPacket interface.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
package com.jftse.server.core.protocol;&lt;br /&gt;
&lt;br /&gt;
public interface IPacket {&lt;br /&gt;
&lt;br /&gt;
    byte[] toBytes();&lt;br /&gt;
&lt;br /&gt;
    char getDataLength();&lt;br /&gt;
&lt;br /&gt;
    char getPacketId();&lt;br /&gt;
&lt;br /&gt;
    char getCheckSerial();&lt;br /&gt;
&lt;br /&gt;
    char getCheckSum();&lt;br /&gt;
&lt;br /&gt;
    String toString();&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This interface defines the minimal contract required for packet serialization and metadata access.&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;br /&gt;
[[Category:Protocol]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Packet_Schema_(.packet)_Format&amp;diff=93</id>
		<title>Packet Schema (.packet) Format</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Packet_Schema_(.packet)_Format&amp;diff=93"/>
		<updated>2025-12-18T15:41:14Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Packet Schema (.packet) Format =&lt;br /&gt;
This page explains how to define Fantasy Tennis packets using the schema based &amp;lt;code&amp;gt;.packet&amp;lt;/code&amp;gt; format consumed by &amp;lt;code&amp;gt;FTPacketGen&amp;lt;/code&amp;gt;. The generator turns these schema files into Java classes (packets and nested structures), including parsing for client packets and auto writing for server packets.&lt;br /&gt;
&lt;br /&gt;
== Where .packet files live ==&lt;br /&gt;
&amp;lt;code&amp;gt;FTPacketGen&amp;lt;/code&amp;gt; scans a directory tree for files ending in &amp;lt;code&amp;gt;.packet&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;server-core/src/main/packets/&amp;lt;/code&amp;gt;). The folder structure becomes the Java package:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
com.jftse.server.core.shared.packets.&amp;lt;relative folders...&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
server-core/src/main/packets/&lt;br /&gt;
  auth/&lt;br /&gt;
    CMSG_Login.packet&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Generates classes in:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
com.jftse.server.core.shared.packets.auth&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Message blocks ==&lt;br /&gt;
A schema file can contain one or multiple &amp;lt;code&amp;gt;message&amp;lt;/code&amp;gt; blocks.&lt;br /&gt;
&lt;br /&gt;
=== Packet message (has packet id) ===&lt;br /&gt;
Use this to define an actual network packet (generates a class that implements &amp;lt;code&amp;gt;IPacket&amp;lt;/code&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message CMSG_Login (0xFA1) {&lt;br /&gt;
    string username = 1;&lt;br /&gt;
    string password = 2 [encoding = utf8];&lt;br /&gt;
    int32 version = 3;&lt;br /&gt;
    byte unk0 = 4;&lt;br /&gt;
    string hwid = 5 [encoding = utf8];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* The &amp;lt;code&amp;gt;(0x....)&amp;lt;/code&amp;gt; makes it a packet class and sets &amp;lt;code&amp;gt;PACKET_ID&amp;lt;/code&amp;gt;.&lt;br /&gt;
* If the message name starts with &amp;lt;code&amp;gt;CMSG&amp;lt;/code&amp;gt; (case-insensitive), it is treated as a client packet (read/parse from bytes).&lt;br /&gt;
* Otherwise it is treated as a server packet (write/build to bytes).&lt;br /&gt;
&lt;br /&gt;
=== Struct / nested message (no packet id) ===&lt;br /&gt;
Use this to define a reusable structure you can reference as a field type in other messages:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message Account {&lt;br /&gt;
    int32 id = 1;&lt;br /&gt;
    int32 id2 = 2;&lt;br /&gt;
    byte tutorialCount = 3;&lt;br /&gt;
    int32 lastPlayedPlayerId = 4;&lt;br /&gt;
    boolean gameMaster = 5;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then use it inside a packet:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message SMSG_PlayerList (0x1005) {&lt;br /&gt;
    Account account = 1;&lt;br /&gt;
    repeated Player players = 2;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Field syntax ==&lt;br /&gt;
Each field line follows this shape:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[repeated] &amp;lt;type&amp;gt; &amp;lt;name&amp;gt; = &amp;lt;number&amp;gt; [&amp;lt;options&amp;gt;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
int32 gold = 1;&lt;br /&gt;
repeated int32 itemIds = 2;&lt;br /&gt;
string nickname = 3 [len = 16];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Field numbering rules (IMPORTANT) ==&lt;br /&gt;
Field numbers are validated strictly:&lt;br /&gt;
&lt;br /&gt;
* Must start at &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
* Must be sequential with no gaps (&amp;lt;code&amp;gt;1,2,3,...&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Must not contain duplicates&lt;br /&gt;
&lt;br /&gt;
If you skip a number (or duplicate one), generation fails.&lt;br /&gt;
&lt;br /&gt;
== Reserved field names ==&lt;br /&gt;
Do &#039;&#039;&#039;not&#039;&#039;&#039; use these field names:&lt;br /&gt;
* &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;metaData&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
They are reserved internally by the generated packet classes.&lt;br /&gt;
&lt;br /&gt;
== Supported base types ==&lt;br /&gt;
These are recognized and mapped by the generator. Sizes refer to how values are written to / read from the packet payload.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Schema type !! Java type !! Payload representation&lt;br /&gt;
|-&lt;br /&gt;
| int / int32 / uint32 || int || 4 bytes (little-endian)&lt;br /&gt;
|-&lt;br /&gt;
| long / int64 / uint64 || long || 8 bytes (little-endian)&lt;br /&gt;
|-&lt;br /&gt;
| short / int16 / uint16 || short || 2 bytes (little-endian)&lt;br /&gt;
|-&lt;br /&gt;
| char || char || 2 bytes (UTF-16 / Java &amp;lt;code&amp;gt;char&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| byte / int8 / uint8 || byte || 1 byte&lt;br /&gt;
|-&lt;br /&gt;
| bool / boolean || boolean || 1 byte (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| float || float || 4 bytes (IEEE-754, little-endian)&lt;br /&gt;
|-&lt;br /&gt;
| double || double || 8 bytes (IEEE-754, little-endian)&lt;br /&gt;
|-&lt;br /&gt;
| string || String || Variable length, null-terminated (UTF-16LE by default; UTF-8 if specified)&lt;br /&gt;
|-&lt;br /&gt;
| date || java.util.Date || 8 bytes (Windows FILETIME)&lt;br /&gt;
|-&lt;br /&gt;
| bytes || byte[] || Raw byte sequence (length defined by schema)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Anything else is treated as a custom/nested message type (must be defined as &amp;lt;code&amp;gt;message &amp;amp;lt;TypeName&amp;amp;gt; { ... }&amp;lt;/code&amp;gt; somewhere in the scanned schema set).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Notes:&#039;&#039;&lt;br /&gt;
* All numeric values are written using little-endian byte order.&lt;br /&gt;
* &amp;lt;code&amp;gt;char&amp;lt;/code&amp;gt; corresponds to Java &amp;lt;code&amp;gt;char&amp;lt;/code&amp;gt; and is always 2 bytes wide.&lt;br /&gt;
* String encoding can be overridden using &amp;lt;code&amp;gt;[encoding=utf8]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[len=...]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== repeated fields ==&lt;br /&gt;
Use &amp;lt;code&amp;gt;repeated&amp;lt;/code&amp;gt; for lists/arrays (except &amp;lt;code&amp;gt;bytes&amp;lt;/code&amp;gt;, which is already a raw byte array).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
repeated int32 values = 1;&lt;br /&gt;
repeated Player players = 2;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== How repeated is encoded ===&lt;br /&gt;
By default, repeated fields are encoded as:&lt;br /&gt;
# a count prefix (&#039;&#039;&#039;1 byte&#039;&#039;&#039; by default), then&lt;br /&gt;
# that many elements&lt;br /&gt;
&lt;br /&gt;
On the read side, the count prefix is always read as a single byte unless you force fixed length via &amp;lt;code&amp;gt;[len=...]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
On the write side, you can control the numeric type used for the count prefix via &amp;lt;code&amp;gt;[type=...]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Options ==&lt;br /&gt;
Options go in square brackets:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
string name = 1 [len = 16];&lt;br /&gt;
repeated Player players = 2 [type = short];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Options are parsed as:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
key = value&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
separated by commas.&lt;br /&gt;
&lt;br /&gt;
=== len (strings, bytes, repeated) ===&lt;br /&gt;
&amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt; changes how a field is read/written.&lt;br /&gt;
&lt;br /&gt;
==== string + len ====&lt;br /&gt;
Reads a fixed-length string (UTF-8) of exactly &amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt; bytes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
string nickname = 1 [len = 16];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== bytes + len ====&lt;br /&gt;
Reads exactly &amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt; bytes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
bytes payload = 1 [len = 64];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If &amp;lt;code&amp;gt;bytes&amp;lt;/code&amp;gt; has no &amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt;, it reads “the rest of the packet payload”.&lt;br /&gt;
&lt;br /&gt;
==== repeated + len ====&lt;br /&gt;
Reads a fixed number of elements (no count prefix is consumed on read):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
repeated int32 fixedSet = 1 [len = 10];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Dynamic length references (len = msg.&amp;amp;lt;field&amp;amp;gt;) ===&lt;br /&gt;
In addition to constant values, the &amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt; option may reference a previously read field using the generated packet instance via &amp;lt;code&amp;gt;msg.&amp;amp;lt;fieldName&amp;amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This allows defining dynamic-length fields whose size depends on earlier values in the packet payload.&lt;br /&gt;
&lt;br /&gt;
==== Rules ====&lt;br /&gt;
* The referenced field &#039;&#039;&#039;must appear earlier&#039;&#039;&#039; in the schema.&lt;br /&gt;
* The referenced field must already be fully read when the dynamic field is processed.&lt;br /&gt;
* This is most commonly used with &amp;lt;code&amp;gt;repeated&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
* The reference uses the generated packet instance (&amp;lt;code&amp;gt;msg&amp;lt;/code&amp;gt;), not raw schema names.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message CMSG_GuildCreate (0x200B) {&lt;br /&gt;
    string name = 1;&lt;br /&gt;
    string introduction = 2;&lt;br /&gt;
    bool isPublic = 3;&lt;br /&gt;
    byte levelRestriction = 4;&lt;br /&gt;
    byte allowedCharacterTypeCount = 5;&lt;br /&gt;
    repeated byte allowedCharacterType = 6 [len = msg.allowedCharacterTypeCount];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this example:&lt;br /&gt;
* &amp;lt;code&amp;gt;allowedCharacterTypeCount&amp;lt;/code&amp;gt; is read first&lt;br /&gt;
* &amp;lt;code&amp;gt;allowedCharacterType&amp;lt;/code&amp;gt; reads exactly that many elements&lt;br /&gt;
* No count prefix is read for the repeated field&lt;br /&gt;
&lt;br /&gt;
==== Notes ====&lt;br /&gt;
* Dynamic references are evaluated at runtime during packet parsing.&lt;br /&gt;
* If the referenced value is invalid or out of bounds, parsing behavior is undefined.&lt;br /&gt;
* Only simple field references are supported (no expressions or calculations).&lt;br /&gt;
&lt;br /&gt;
=== type (size/count prefix control) ===&lt;br /&gt;
&amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; controls the integer type used when writing sizes/counts (and can also force a cast for some primitives).&lt;br /&gt;
&lt;br /&gt;
==== repeated + type ====&lt;br /&gt;
Controls the numeric type used to write the list size:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
repeated Player players = 1 [type = short];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This writes &amp;lt;code&amp;gt;(short) players.size()&amp;lt;/code&amp;gt; before the elements.&lt;br /&gt;
&lt;br /&gt;
==== primitive field + type ====&lt;br /&gt;
If you set &amp;lt;code&amp;gt;type=...&amp;lt;/code&amp;gt; on a supported primitive field, the generator will cast when writing. This is useful when the schema expresses a value as &amp;lt;code&amp;gt;int32&amp;lt;/code&amp;gt; but the protocol stores it smaller:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
int32 flags = 1 [type = byte];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== encoding=utf8 (strings) ===&lt;br /&gt;
For writing strings:&lt;br /&gt;
* Default writing uses UTF-16LE + &amp;lt;code&amp;gt;0x0000&amp;lt;/code&amp;gt; terminator.&lt;br /&gt;
* With &amp;lt;code&amp;gt;[encoding = utf8]&amp;lt;/code&amp;gt; it writes UTF-8 + &amp;lt;code&amp;gt;0x00&amp;lt;/code&amp;gt; terminator.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
string password = 1 [encoding = utf8];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Reading strings:&lt;br /&gt;
* Variable-length &amp;lt;code&amp;gt;string&amp;lt;/code&amp;gt; is auto-detected by the generated reader (UTF-16LE vs ASCII/UTF-8 style).&lt;br /&gt;
* Fixed-length strings (&amp;lt;code&amp;gt;[len = ...]&amp;lt;/code&amp;gt;) are read as UTF-8 bytes.&lt;br /&gt;
&lt;br /&gt;
== Client vs Server packet behavior ==&lt;br /&gt;
The generator treats packets differently depending on the message name.&lt;br /&gt;
&lt;br /&gt;
=== Client packets (CMSG*) ===&lt;br /&gt;
If the message name starts with &amp;lt;code&amp;gt;CMSG&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;cmsg&amp;lt;/code&amp;gt;:&lt;br /&gt;
* The generated class registers itself with &amp;lt;code&amp;gt;PacketRegistry&amp;lt;/code&amp;gt; in a static block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fromBytes(...)&amp;lt;/code&amp;gt; parses all defined fields in schema order.&lt;br /&gt;
* A generic &amp;lt;code&amp;gt;read(Class&amp;amp;lt;T&amp;amp;gt;)&amp;lt;/code&amp;gt; API exists plus type-specific read helpers.&lt;br /&gt;
&lt;br /&gt;
These are also included in the generated &amp;lt;code&amp;gt;PacketAutoRegister&amp;lt;/code&amp;gt; list.&lt;br /&gt;
&lt;br /&gt;
=== Server packets (everything else) ===&lt;br /&gt;
For non-&amp;lt;code&amp;gt;CMSG&amp;lt;/code&amp;gt; packet messages:&lt;br /&gt;
* The builder’s &amp;lt;code&amp;gt;build()&amp;lt;/code&amp;gt; writes fields into the internal byte buffer automatically (in schema order).&lt;br /&gt;
* &amp;lt;code&amp;gt;toBytes()&amp;lt;/code&amp;gt; returns the full header + payload.&lt;br /&gt;
&lt;br /&gt;
== Nested/composite messages ==&lt;br /&gt;
Any &amp;lt;code&amp;gt;message&amp;lt;/code&amp;gt; without a packet id becomes a plain Java class with fields, getters/setters, and a builder.&lt;br /&gt;
&lt;br /&gt;
When you reference that message name as a field type inside a packet, the generator writes/reads its subfields in order.&lt;br /&gt;
&lt;br /&gt;
== Full example: Player list packet ==&lt;br /&gt;
This shows nested messages and a repeated composite type.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message Account {&lt;br /&gt;
    int32 id = 1;&lt;br /&gt;
    int32 id2 = 2;&lt;br /&gt;
    byte tutorialCount = 3;&lt;br /&gt;
    int32 lastPlayedPlayerId = 4;&lt;br /&gt;
    boolean gameMaster = 5;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
message ClothEquipment {&lt;br /&gt;
    int32 hair = 1;&lt;br /&gt;
    int32 face = 2;&lt;br /&gt;
    int32 dress = 3;&lt;br /&gt;
    int32 pants = 4;&lt;br /&gt;
    int32 socks = 5;&lt;br /&gt;
    int32 shoes = 6;&lt;br /&gt;
    int32 gloves = 7;&lt;br /&gt;
    int32 racket = 8;&lt;br /&gt;
    int32 glasses = 9;&lt;br /&gt;
    int32 bag = 10;&lt;br /&gt;
    int32 hat = 11;&lt;br /&gt;
    int32 dye = 12;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
message Player {&lt;br /&gt;
    int32 id = 1;&lt;br /&gt;
    string name = 2;&lt;br /&gt;
    byte level = 3;&lt;br /&gt;
    boolean created = 4;&lt;br /&gt;
    boolean canDelete = 5;&lt;br /&gt;
    int32 gold = 6;&lt;br /&gt;
    byte playerType = 7;&lt;br /&gt;
    byte str = 8;&lt;br /&gt;
    byte sta = 9;&lt;br /&gt;
    byte dex = 10;&lt;br /&gt;
    byte wil = 11;&lt;br /&gt;
    byte statPoints = 12;&lt;br /&gt;
    boolean oldRenameAllowed = 13;&lt;br /&gt;
    boolean renameAllowed = 14;&lt;br /&gt;
    ClothEquipment clothEquipment = 15;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
message SMSG_PlayerList (0x1005) {&lt;br /&gt;
    Account account = 1;&lt;br /&gt;
    repeated Player players = 2;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Practical tips / gotchas ==&lt;br /&gt;
* Keep schema field order exactly matching the on wire protocol order. The generator reads/writes strictly in schema order.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;[type = short]&amp;lt;/code&amp;gt; (or another type) on &amp;lt;code&amp;gt;repeated&amp;lt;/code&amp;gt; if the protocol’s list length prefix is not 1 byte.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;bytes&amp;lt;/code&amp;gt; for raw data blocks; don’t try &amp;lt;code&amp;gt;repeated byte&amp;lt;/code&amp;gt; unless you specifically want a length-prefixed list of bytes.&lt;br /&gt;
* Avoid &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;metaData&amp;lt;/code&amp;gt; as field names.&lt;br /&gt;
* Don’t skip field numbers, generation will fail.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Protocol]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=JFTSE_Wiki&amp;diff=92</id>
		<title>JFTSE Wiki</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=JFTSE_Wiki&amp;diff=92"/>
		<updated>2025-12-18T15:27:23Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= JFTSE Wiki =&lt;br /&gt;
&lt;br /&gt;
Welcome to the &#039;&#039;&#039;JFTSE Wiki&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:red; font-size: 16px;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;b&amp;gt;Please [https://wiki.jftse.com/index.php?title=Special:UserLogin Log In] with your JFTSE account to edit and contribute.&amp;lt;/b&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This Wiki is a community driven knowledge base for everything related to &#039;&#039;&#039;JFTSE and Fantasy Tennis&#039;&#039;&#039;. Here you will find guides, tutorials, tips, tricks, and information about gameplay mechanics, server configurations and much more.&lt;br /&gt;
&lt;br /&gt;
This Wiki should be considered a place where community members can create new informative webpages that cover specific topics, &lt;br /&gt;
and where fellow community members can contribute to and improve existing pages.&lt;br /&gt;
&lt;br /&gt;
We strongly believe in the idea that this Wiki should be a free and open method of overlaying information to everyone. Therefore, our stance is that everyone should be able to contribute to this Wiki, as long as the information is accurate and relevant. As a guest, you are free to access and read all the information the Wiki has to offer. As a community member, you are free to contribute to the Wiki by creating new pages, editing existing pages, and discussing the content of the Wiki with other community members as long as you conform to the [[JFTSE Wiki:Community Rules|Community Rules]].&lt;br /&gt;
&lt;br /&gt;
= 📚 Usage And Information =&lt;br /&gt;
=== Information ===&lt;br /&gt;
* [[About_JFTSE|About JFTSE]]&lt;br /&gt;
* [[About_Fantasy_Tennis|About Fantasy Tennis]]&lt;br /&gt;
* [[JFTSE Wiki:Community Rules|Community Rules]]&lt;br /&gt;
&lt;br /&gt;
=== Contributing ===&lt;br /&gt;
&amp;lt;strong&amp;gt;Only registered JFTSE users can edit pages.&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
To contribute, [https://wiki.jftse.com/index.php?title=Special:UserLogin Log In] with your JFTSE account and help expand the knowledge base!&amp;lt;br&amp;gt;&lt;br /&gt;
Please read and follow the [[JFTSE Wiki:Wiki Guidelines|Wiki Guidelines]] before contributing.&lt;br /&gt;
&lt;br /&gt;
= 🛠 Getting Started =&lt;br /&gt;
* [[Game Lore]] - Dive into the world of Fantasy Tennis.&lt;br /&gt;
* [[Game Installation Guide]] - Step-by-step guide on installing the game.&lt;br /&gt;
* [[Beginner&#039;s Guide]] - Basics of gameplay, controls and UI.&lt;br /&gt;
* [[Gameplay Mechanics]] - Strategies, combos and in-depth gameplay mechanics.&lt;br /&gt;
* [[Troubleshooting|Troubleshooting &amp;amp; Fixes]] - Solutions for common issues.&lt;br /&gt;
&lt;br /&gt;
= 📖 Game Information =&lt;br /&gt;
* [[Characters]] - Information about the characters in Fantasy Tennis.&lt;br /&gt;
* [[Items]] - List of items and their effects.&lt;br /&gt;
* [[Skills]] - List of skills and their effects.&lt;br /&gt;
* [[Maps]] - Learn about the different maps in Fantasy Tennis.&lt;br /&gt;
* [[Game Modes]] - Information about the different game modes.&lt;br /&gt;
* [[Guides &amp;amp; Tutorials]] - Learn tips and tricks from experienced players.&lt;br /&gt;
&lt;br /&gt;
= 📜 Server Information =&lt;br /&gt;
* [[Database Schema &amp;amp; Cheatsheet]] - Database Schema used for Fantasy Tennis &amp;amp; Cheatsheet.&lt;br /&gt;
* [[Server Configuration]] - Information about server configuration.&lt;br /&gt;
* [[Packet Schema (.packet) Format]] - Defines the schema language used to declare and generate network packets.&lt;br /&gt;
&lt;br /&gt;
= 🔗 Useful Links =&lt;br /&gt;
* [https://jftse.com/ JFTSE Website]&lt;br /&gt;
* [https://discord.gg/MwamzbMTMy Discord]&lt;br /&gt;
* [[Special:RecentChanges|Recent Changes]]&lt;br /&gt;
* [[Special:AllPages|All Pages]]&lt;br /&gt;
* [[Special:Random|Random Page]]&lt;br /&gt;
* [[Special:Categories|Categories]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Packet_Schema_(.packet)_Format&amp;diff=91</id>
		<title>Packet Schema (.packet) Format</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Packet_Schema_(.packet)_Format&amp;diff=91"/>
		<updated>2025-12-18T15:24:14Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Packet Schema (.packet) Format =&lt;br /&gt;
This page explains how to define Fantasy Tennis packets using the schema based &amp;lt;code&amp;gt;.packet&amp;lt;/code&amp;gt; format consumed by &amp;lt;code&amp;gt;FTPacketGen&amp;lt;/code&amp;gt;. The generator turns these schema files into Java classes (packets and nested structures), including parsing for client packets and auto writing for server packets.&lt;br /&gt;
&lt;br /&gt;
== Where .packet files live ==&lt;br /&gt;
&amp;lt;code&amp;gt;FTPacketGen&amp;lt;/code&amp;gt; scans a directory tree for files ending in &amp;lt;code&amp;gt;.packet&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;server-core/src/main/packets/&amp;lt;/code&amp;gt;). The folder structure becomes the Java package:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
com.jftse.server.core.shared.packets.&amp;lt;relative folders...&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
server-core/src/main/packets/&lt;br /&gt;
  auth/&lt;br /&gt;
    CMSG_Login.packet&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Generates classes in:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
com.jftse.server.core.shared.packets.auth&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Message blocks ==&lt;br /&gt;
A schema file can contain one or multiple &amp;lt;code&amp;gt;message&amp;lt;/code&amp;gt; blocks.&lt;br /&gt;
&lt;br /&gt;
=== Packet message (has packet id) ===&lt;br /&gt;
Use this to define an actual network packet (generates a class that implements &amp;lt;code&amp;gt;IPacket&amp;lt;/code&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message CMSG_Login (0xFA1) {&lt;br /&gt;
    string username = 1;&lt;br /&gt;
    string password = 2 [encoding = utf8];&lt;br /&gt;
    int32 version = 3;&lt;br /&gt;
    byte unk0 = 4;&lt;br /&gt;
    string hwid = 5 [encoding = utf8];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* The &amp;lt;code&amp;gt;(0x....)&amp;lt;/code&amp;gt; makes it a packet class and sets &amp;lt;code&amp;gt;PACKET_ID&amp;lt;/code&amp;gt;.&lt;br /&gt;
* If the message name starts with &amp;lt;code&amp;gt;CMSG&amp;lt;/code&amp;gt; (case-insensitive), it is treated as a client packet (read/parse from bytes).&lt;br /&gt;
* Otherwise it is treated as a server packet (write/build to bytes).&lt;br /&gt;
&lt;br /&gt;
=== Struct / nested message (no packet id) ===&lt;br /&gt;
Use this to define a reusable structure you can reference as a field type in other messages:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message Account {&lt;br /&gt;
    int32 id = 1;&lt;br /&gt;
    int32 id2 = 2;&lt;br /&gt;
    byte tutorialCount = 3;&lt;br /&gt;
    int32 lastPlayedPlayerId = 4;&lt;br /&gt;
    boolean gameMaster = 5;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then use it inside a packet:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message SMSG_PlayerList (0x1005) {&lt;br /&gt;
    Account account = 1;&lt;br /&gt;
    repeated Player players = 2;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Field syntax ==&lt;br /&gt;
Each field line follows this shape:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[repeated] &amp;lt;type&amp;gt; &amp;lt;name&amp;gt; = &amp;lt;number&amp;gt; [&amp;lt;options&amp;gt;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
int32 gold = 1;&lt;br /&gt;
repeated int32 itemIds = 2;&lt;br /&gt;
string nickname = 3 [len=16];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Field numbering rules (IMPORTANT) ==&lt;br /&gt;
Field numbers are validated strictly:&lt;br /&gt;
&lt;br /&gt;
* Must start at &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
* Must be sequential with no gaps (&amp;lt;code&amp;gt;1,2,3,...&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Must not contain duplicates&lt;br /&gt;
&lt;br /&gt;
If you skip a number (or duplicate one), generation fails.&lt;br /&gt;
&lt;br /&gt;
== Reserved field names ==&lt;br /&gt;
Do &#039;&#039;&#039;not&#039;&#039;&#039; use these field names:&lt;br /&gt;
* &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;metaData&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
They are reserved internally by the generated packet classes.&lt;br /&gt;
&lt;br /&gt;
== Supported base types ==&lt;br /&gt;
These are recognized and mapped by the generator. Sizes refer to how values are written to / read from the packet payload.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Schema type !! Java type !! Payload representation&lt;br /&gt;
|-&lt;br /&gt;
| int / int32 / uint32 || int || 4 bytes (little-endian)&lt;br /&gt;
|-&lt;br /&gt;
| long / int64 / uint64 || long || 8 bytes (little-endian)&lt;br /&gt;
|-&lt;br /&gt;
| short / int16 / uint16 || short || 2 bytes (little-endian)&lt;br /&gt;
|-&lt;br /&gt;
| char || char || 2 bytes (UTF-16 / Java &amp;lt;code&amp;gt;char&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| byte / int8 / uint8 || byte || 1 byte&lt;br /&gt;
|-&lt;br /&gt;
| bool / boolean || boolean || 1 byte (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| float || float || 4 bytes (IEEE-754, little-endian)&lt;br /&gt;
|-&lt;br /&gt;
| double || double || 8 bytes (IEEE-754, little-endian)&lt;br /&gt;
|-&lt;br /&gt;
| string || String || Variable length, null-terminated (UTF-16LE by default; UTF-8 if specified)&lt;br /&gt;
|-&lt;br /&gt;
| date || java.util.Date || 8 bytes (Windows FILETIME)&lt;br /&gt;
|-&lt;br /&gt;
| bytes || byte[] || Raw byte sequence (length defined by schema)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Anything else is treated as a custom/nested message type (must be defined as &amp;lt;code&amp;gt;message &amp;amp;lt;TypeName&amp;amp;gt; { ... }&amp;lt;/code&amp;gt; somewhere in the scanned schema set).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Notes:&#039;&#039;&lt;br /&gt;
* All numeric values are written using little-endian byte order.&lt;br /&gt;
* &amp;lt;code&amp;gt;char&amp;lt;/code&amp;gt; corresponds to Java &amp;lt;code&amp;gt;char&amp;lt;/code&amp;gt; and is always 2 bytes wide.&lt;br /&gt;
* String encoding can be overridden using &amp;lt;code&amp;gt;[encoding=utf8]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[len=...]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== repeated fields ==&lt;br /&gt;
Use &amp;lt;code&amp;gt;repeated&amp;lt;/code&amp;gt; for lists/arrays (except &amp;lt;code&amp;gt;bytes&amp;lt;/code&amp;gt;, which is already a raw byte array).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
repeated int32 values = 1;&lt;br /&gt;
repeated Player players = 2;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== How repeated is encoded ===&lt;br /&gt;
By default, repeated fields are encoded as:&lt;br /&gt;
# a count prefix (&#039;&#039;&#039;1 byte&#039;&#039;&#039; by default), then&lt;br /&gt;
# that many elements&lt;br /&gt;
&lt;br /&gt;
On the read side, the count prefix is always read as a single byte unless you force fixed length via &amp;lt;code&amp;gt;[len=...]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
On the write side, you can control the numeric type used for the count prefix via &amp;lt;code&amp;gt;[type=...]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Options ==&lt;br /&gt;
Options go in square brackets:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
string name = 1 [len=16];&lt;br /&gt;
repeated Player players = 2 [type=short];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Options are parsed as:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
key = value&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
separated by commas.&lt;br /&gt;
&lt;br /&gt;
=== len (strings, bytes, repeated) ===&lt;br /&gt;
&amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt; changes how a field is read/written.&lt;br /&gt;
&lt;br /&gt;
==== string + len ====&lt;br /&gt;
Reads a fixed-length string (UTF-8) of exactly &amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt; bytes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
string nickname = 1 [len=16];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== bytes + len ====&lt;br /&gt;
Reads exactly &amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt; bytes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
bytes payload = 1 [len=64];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If &amp;lt;code&amp;gt;bytes&amp;lt;/code&amp;gt; has no &amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt;, it reads “the rest of the packet payload”.&lt;br /&gt;
&lt;br /&gt;
==== repeated + len ====&lt;br /&gt;
Reads a fixed number of elements (no count prefix is consumed on read):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
repeated int32 fixedSet = 1 [len=10];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== type (size/count prefix control) ===&lt;br /&gt;
&amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; controls the integer type used when writing sizes/counts (and can also force a cast for some primitives).&lt;br /&gt;
&lt;br /&gt;
==== repeated + type ====&lt;br /&gt;
Controls the numeric type used to write the list size:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
repeated Player players = 1 [type=short];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This writes &amp;lt;code&amp;gt;(short) players.size()&amp;lt;/code&amp;gt; before the elements.&lt;br /&gt;
&lt;br /&gt;
==== primitive field + type ====&lt;br /&gt;
If you set &amp;lt;code&amp;gt;type=...&amp;lt;/code&amp;gt; on a supported primitive field, the generator will cast when writing. This is useful when the schema expresses a value as &amp;lt;code&amp;gt;int32&amp;lt;/code&amp;gt; but the protocol stores it smaller:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
int32 flags = 1 [type=byte];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== encoding=utf8 (strings) ===&lt;br /&gt;
For writing strings:&lt;br /&gt;
* Default writing uses UTF-16LE + &amp;lt;code&amp;gt;0x0000&amp;lt;/code&amp;gt; terminator.&lt;br /&gt;
* With &amp;lt;code&amp;gt;[encoding=utf8]&amp;lt;/code&amp;gt; it writes UTF-8 + &amp;lt;code&amp;gt;0x00&amp;lt;/code&amp;gt; terminator.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
string password = 1 [encoding = utf8];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Reading strings:&lt;br /&gt;
* Variable-length &amp;lt;code&amp;gt;string&amp;lt;/code&amp;gt; is auto-detected by the generated reader (UTF-16LE vs ASCII/UTF-8 style).&lt;br /&gt;
* Fixed-length strings (&amp;lt;code&amp;gt;[len=...]&amp;lt;/code&amp;gt;) are read as UTF-8 bytes.&lt;br /&gt;
&lt;br /&gt;
== Client vs Server packet behavior ==&lt;br /&gt;
The generator treats packets differently depending on the message name.&lt;br /&gt;
&lt;br /&gt;
=== Client packets (CMSG*) ===&lt;br /&gt;
If the message name starts with &amp;lt;code&amp;gt;CMSG&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;cmsg&amp;lt;/code&amp;gt;:&lt;br /&gt;
* The generated class registers itself with &amp;lt;code&amp;gt;PacketRegistry&amp;lt;/code&amp;gt; in a static block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fromBytes(...)&amp;lt;/code&amp;gt; parses all defined fields in schema order.&lt;br /&gt;
* A generic &amp;lt;code&amp;gt;read(Class&amp;amp;lt;T&amp;amp;gt;)&amp;lt;/code&amp;gt; API exists plus type-specific read helpers.&lt;br /&gt;
&lt;br /&gt;
These are also included in the generated &amp;lt;code&amp;gt;PacketAutoRegister&amp;lt;/code&amp;gt; list.&lt;br /&gt;
&lt;br /&gt;
=== Server packets (everything else) ===&lt;br /&gt;
For non-&amp;lt;code&amp;gt;CMSG&amp;lt;/code&amp;gt; packet messages:&lt;br /&gt;
* The builder’s &amp;lt;code&amp;gt;build()&amp;lt;/code&amp;gt; writes fields into the internal byte buffer automatically (in schema order).&lt;br /&gt;
* &amp;lt;code&amp;gt;toBytes()&amp;lt;/code&amp;gt; returns the full header + payload.&lt;br /&gt;
&lt;br /&gt;
== Nested/composite messages ==&lt;br /&gt;
Any &amp;lt;code&amp;gt;message&amp;lt;/code&amp;gt; without a packet id becomes a plain Java class with fields, getters/setters, and a builder.&lt;br /&gt;
&lt;br /&gt;
When you reference that message name as a field type inside a packet, the generator writes/reads its subfields in order.&lt;br /&gt;
&lt;br /&gt;
== Full example: Player list packet ==&lt;br /&gt;
This shows nested messages and a repeated composite type.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message Account {&lt;br /&gt;
    int32 id = 1;&lt;br /&gt;
    int32 id2 = 2;&lt;br /&gt;
    byte tutorialCount = 3;&lt;br /&gt;
    int32 lastPlayedPlayerId = 4;&lt;br /&gt;
    boolean gameMaster = 5;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
message ClothEquipment {&lt;br /&gt;
    int32 hair = 1;&lt;br /&gt;
    int32 face = 2;&lt;br /&gt;
    int32 dress = 3;&lt;br /&gt;
    int32 pants = 4;&lt;br /&gt;
    int32 socks = 5;&lt;br /&gt;
    int32 shoes = 6;&lt;br /&gt;
    int32 gloves = 7;&lt;br /&gt;
    int32 racket = 8;&lt;br /&gt;
    int32 glasses = 9;&lt;br /&gt;
    int32 bag = 10;&lt;br /&gt;
    int32 hat = 11;&lt;br /&gt;
    int32 dye = 12;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
message Player {&lt;br /&gt;
    int32 id = 1;&lt;br /&gt;
    string name = 2;&lt;br /&gt;
    byte level = 3;&lt;br /&gt;
    boolean created = 4;&lt;br /&gt;
    boolean canDelete = 5;&lt;br /&gt;
    int32 gold = 6;&lt;br /&gt;
    byte playerType = 7;&lt;br /&gt;
    byte str = 8;&lt;br /&gt;
    byte sta = 9;&lt;br /&gt;
    byte dex = 10;&lt;br /&gt;
    byte wil = 11;&lt;br /&gt;
    byte statPoints = 12;&lt;br /&gt;
    boolean oldRenameAllowed = 13;&lt;br /&gt;
    boolean renameAllowed = 14;&lt;br /&gt;
    ClothEquipment clothEquipment = 15;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
message SMSG_PlayerList (0x1005) {&lt;br /&gt;
    Account account = 1;&lt;br /&gt;
    repeated Player players = 2;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Practical tips / gotchas ==&lt;br /&gt;
* Keep schema field order exactly matching the on wire protocol order. The generator reads/writes strictly in schema order.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;[type=short]&amp;lt;/code&amp;gt; (or another type) on &amp;lt;code&amp;gt;repeated&amp;lt;/code&amp;gt; if the protocol’s list length prefix is not 1 byte.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;bytes&amp;lt;/code&amp;gt; for raw data blocks; don’t try &amp;lt;code&amp;gt;repeated byte&amp;lt;/code&amp;gt; unless you specifically want a length-prefixed list of bytes.&lt;br /&gt;
* Avoid &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;metaData&amp;lt;/code&amp;gt; as field names.&lt;br /&gt;
* Don’t skip field numbers, generation will fail.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Protocol]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Packet_Schema_(.packet)_Format&amp;diff=90</id>
		<title>Packet Schema (.packet) Format</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Packet_Schema_(.packet)_Format&amp;diff=90"/>
		<updated>2025-12-18T15:10:16Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;= Packet Schema (.packet) Format = This page explains how to define Fantasy Tennis packets using the schema based &amp;lt;code&amp;gt;.packet&amp;lt;/code&amp;gt; format consumed by &amp;lt;code&amp;gt;FTPacketGen&amp;lt;/code&amp;gt;. The generator turns these schema files into Java classes (packets and nested structures), including parsing for client packets and auto writing for server packets.  == Where .packet files live == &amp;lt;code&amp;gt;FTPacketGen&amp;lt;/code&amp;gt; scans a directory tree for files ending in &amp;lt;code&amp;gt;.packet&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;ser...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Packet Schema (.packet) Format =&lt;br /&gt;
This page explains how to define Fantasy Tennis packets using the schema based &amp;lt;code&amp;gt;.packet&amp;lt;/code&amp;gt; format consumed by &amp;lt;code&amp;gt;FTPacketGen&amp;lt;/code&amp;gt;. The generator turns these schema files into Java classes (packets and nested structures), including parsing for client packets and auto writing for server packets.&lt;br /&gt;
&lt;br /&gt;
== Where .packet files live ==&lt;br /&gt;
&amp;lt;code&amp;gt;FTPacketGen&amp;lt;/code&amp;gt; scans a directory tree for files ending in &amp;lt;code&amp;gt;.packet&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;server-core/src/main/packets/&amp;lt;/code&amp;gt;). The folder structure becomes the Java package:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
com.jftse.server.core.shared.packets.&amp;lt;relative folders...&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
server-core/src/main/packets/&lt;br /&gt;
  auth/&lt;br /&gt;
    CMSG_Login.packet&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Generates classes in:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
com.jftse.server.core.shared.packets.auth&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Message blocks ==&lt;br /&gt;
A schema file can contain one or multiple &amp;lt;code&amp;gt;message&amp;lt;/code&amp;gt; blocks.&lt;br /&gt;
&lt;br /&gt;
=== Packet message (has packet id) ===&lt;br /&gt;
Use this to define an actual network packet (generates a class that implements &amp;lt;code&amp;gt;IPacket&amp;lt;/code&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message CMSG_Login (0xFA1) {&lt;br /&gt;
    string username = 1;&lt;br /&gt;
    string password = 2 [encoding = utf8];&lt;br /&gt;
    int32 version = 3;&lt;br /&gt;
    byte unk0 = 4;&lt;br /&gt;
    string hwid = 5 [encoding = utf8];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* The &amp;lt;code&amp;gt;(0x....)&amp;lt;/code&amp;gt; makes it a packet class and sets &amp;lt;code&amp;gt;PACKET_ID&amp;lt;/code&amp;gt;.&lt;br /&gt;
* If the message name starts with &amp;lt;code&amp;gt;CMSG&amp;lt;/code&amp;gt; (case-insensitive), it is treated as a client packet (read/parse from bytes).&lt;br /&gt;
* Otherwise it is treated as a server packet (write/build to bytes).&lt;br /&gt;
&lt;br /&gt;
=== Struct / nested message (no packet id) ===&lt;br /&gt;
Use this to define a reusable structure you can reference as a field type in other messages:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message Account {&lt;br /&gt;
    int32 id = 1;&lt;br /&gt;
    int32 id2 = 2;&lt;br /&gt;
    byte tutorialCount = 3;&lt;br /&gt;
    int32 lastPlayedPlayerId = 4;&lt;br /&gt;
    boolean gameMaster = 5;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then use it inside a packet:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message SMSG_PlayerList (0x1005) {&lt;br /&gt;
    Account account = 1;&lt;br /&gt;
    repeated Player players = 2;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Field syntax ==&lt;br /&gt;
Each field line follows this shape:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[repeated] &amp;lt;type&amp;gt; &amp;lt;name&amp;gt; = &amp;lt;number&amp;gt; [&amp;lt;options&amp;gt;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
int32 gold = 1;&lt;br /&gt;
repeated int32 itemIds = 2;&lt;br /&gt;
string nickname = 3 [len=16];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Field numbering rules (IMPORTANT) ==&lt;br /&gt;
Field numbers are validated strictly:&lt;br /&gt;
&lt;br /&gt;
* Must start at &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
* Must be sequential with no gaps (&amp;lt;code&amp;gt;1,2,3,...&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Must not contain duplicates&lt;br /&gt;
&lt;br /&gt;
If you skip a number (or duplicate one), generation fails.&lt;br /&gt;
&lt;br /&gt;
== Reserved field names ==&lt;br /&gt;
Do &#039;&#039;&#039;not&#039;&#039;&#039; use these field names:&lt;br /&gt;
* &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;metaData&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
They are reserved internally by the generated packet classes.&lt;br /&gt;
&lt;br /&gt;
== Supported base types ==&lt;br /&gt;
These are recognized and mapped by the generator:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Schema type !! Java type&lt;br /&gt;
|-&lt;br /&gt;
| int / int32 / uint32 || int&lt;br /&gt;
|-&lt;br /&gt;
| long / int64 / uint64 || long&lt;br /&gt;
|-&lt;br /&gt;
| short / int16 / uint16 || short&lt;br /&gt;
|-&lt;br /&gt;
| char || char&lt;br /&gt;
|-&lt;br /&gt;
| byte / int8 / uint8 || byte&lt;br /&gt;
|-&lt;br /&gt;
| bool / boolean || boolean&lt;br /&gt;
|-&lt;br /&gt;
| float || float&lt;br /&gt;
|-&lt;br /&gt;
| double || double&lt;br /&gt;
|-&lt;br /&gt;
| string || String&lt;br /&gt;
|-&lt;br /&gt;
| date || java.util.Date&lt;br /&gt;
|-&lt;br /&gt;
| bytes || byte[]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Anything else is treated as a custom/nested message type (must be defined as &amp;lt;code&amp;gt;message &amp;amp;lt;TypeName&amp;amp;gt; { ... }&amp;lt;/code&amp;gt; somewhere in the scanned schema set).&lt;br /&gt;
&lt;br /&gt;
== repeated fields ==&lt;br /&gt;
Use &amp;lt;code&amp;gt;repeated&amp;lt;/code&amp;gt; for lists/arrays (except &amp;lt;code&amp;gt;bytes&amp;lt;/code&amp;gt;, which is already a raw byte array).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
repeated int32 values = 1;&lt;br /&gt;
repeated Player players = 2;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== How repeated is encoded ===&lt;br /&gt;
By default, repeated fields are encoded as:&lt;br /&gt;
# a count prefix (&#039;&#039;&#039;1 byte&#039;&#039;&#039; by default), then&lt;br /&gt;
# that many elements&lt;br /&gt;
&lt;br /&gt;
On the read side, the count prefix is always read as a single byte unless you force fixed length via &amp;lt;code&amp;gt;[len=...]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
On the write side, you can control the numeric type used for the count prefix via &amp;lt;code&amp;gt;[type=...]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Options ==&lt;br /&gt;
Options go in square brackets:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
string name = 1 [len=16];&lt;br /&gt;
repeated Player players = 2 [type=short];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Options are parsed as:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
key = value&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
separated by commas.&lt;br /&gt;
&lt;br /&gt;
=== len (strings, bytes, repeated) ===&lt;br /&gt;
&amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt; changes how a field is read/written.&lt;br /&gt;
&lt;br /&gt;
==== string + len ====&lt;br /&gt;
Reads a fixed-length string (UTF-8) of exactly &amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt; bytes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
string nickname = 1 [len=16];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== bytes + len ====&lt;br /&gt;
Reads exactly &amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt; bytes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
bytes payload = 1 [len=64];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If &amp;lt;code&amp;gt;bytes&amp;lt;/code&amp;gt; has no &amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt;, it reads “the rest of the packet payload”.&lt;br /&gt;
&lt;br /&gt;
==== repeated + len ====&lt;br /&gt;
Reads a fixed number of elements (no count prefix is consumed on read):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
repeated int32 fixedSet = 1 [len=10];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== type (size/count prefix control) ===&lt;br /&gt;
&amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; controls the integer type used when writing sizes/counts (and can also force a cast for some primitives).&lt;br /&gt;
&lt;br /&gt;
==== repeated + type ====&lt;br /&gt;
Controls the numeric type used to write the list size:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
repeated Player players = 1 [type=short];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This writes &amp;lt;code&amp;gt;(short) players.size()&amp;lt;/code&amp;gt; before the elements.&lt;br /&gt;
&lt;br /&gt;
==== primitive field + type ====&lt;br /&gt;
If you set &amp;lt;code&amp;gt;type=...&amp;lt;/code&amp;gt; on a supported primitive field, the generator will cast when writing. This is useful when the schema expresses a value as &amp;lt;code&amp;gt;int32&amp;lt;/code&amp;gt; but the protocol stores it smaller:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
int32 flags = 1 [type=byte];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== encoding=utf8 (strings) ===&lt;br /&gt;
For writing strings:&lt;br /&gt;
* Default writing uses UTF-16LE + &amp;lt;code&amp;gt;0x0000&amp;lt;/code&amp;gt; terminator.&lt;br /&gt;
* With &amp;lt;code&amp;gt;[encoding=utf8]&amp;lt;/code&amp;gt; it writes UTF-8 + &amp;lt;code&amp;gt;0x00&amp;lt;/code&amp;gt; terminator.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
string password = 1 [encoding = utf8];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Reading strings:&lt;br /&gt;
* Variable-length &amp;lt;code&amp;gt;string&amp;lt;/code&amp;gt; is auto-detected by the generated reader (UTF-16LE vs ASCII/UTF-8 style).&lt;br /&gt;
* Fixed-length strings (&amp;lt;code&amp;gt;[len=...]&amp;lt;/code&amp;gt;) are read as UTF-8 bytes.&lt;br /&gt;
&lt;br /&gt;
== Client vs Server packet behavior ==&lt;br /&gt;
The generator treats packets differently depending on the message name.&lt;br /&gt;
&lt;br /&gt;
=== Client packets (CMSG*) ===&lt;br /&gt;
If the message name starts with &amp;lt;code&amp;gt;CMSG&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;cmsg&amp;lt;/code&amp;gt;:&lt;br /&gt;
* The generated class registers itself with &amp;lt;code&amp;gt;PacketRegistry&amp;lt;/code&amp;gt; in a static block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fromBytes(...)&amp;lt;/code&amp;gt; parses all defined fields in schema order.&lt;br /&gt;
* A generic &amp;lt;code&amp;gt;read(Class&amp;amp;lt;T&amp;amp;gt;)&amp;lt;/code&amp;gt; API exists plus type-specific read helpers.&lt;br /&gt;
&lt;br /&gt;
These are also included in the generated &amp;lt;code&amp;gt;PacketAutoRegister&amp;lt;/code&amp;gt; list.&lt;br /&gt;
&lt;br /&gt;
=== Server packets (everything else) ===&lt;br /&gt;
For non-&amp;lt;code&amp;gt;CMSG&amp;lt;/code&amp;gt; packet messages:&lt;br /&gt;
* The builder’s &amp;lt;code&amp;gt;build()&amp;lt;/code&amp;gt; writes fields into the internal byte buffer automatically (in schema order).&lt;br /&gt;
* &amp;lt;code&amp;gt;toBytes()&amp;lt;/code&amp;gt; returns the full header + payload.&lt;br /&gt;
&lt;br /&gt;
== Nested/composite messages ==&lt;br /&gt;
Any &amp;lt;code&amp;gt;message&amp;lt;/code&amp;gt; without a packet id becomes a plain Java class with fields, getters/setters, and a builder.&lt;br /&gt;
&lt;br /&gt;
When you reference that message name as a field type inside a packet, the generator writes/reads its subfields in order.&lt;br /&gt;
&lt;br /&gt;
== Full example: Player list packet ==&lt;br /&gt;
This shows nested messages and a repeated composite type.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
message Account {&lt;br /&gt;
    int32 id = 1;&lt;br /&gt;
    int32 id2 = 2;&lt;br /&gt;
    byte tutorialCount = 3;&lt;br /&gt;
    int32 lastPlayedPlayerId = 4;&lt;br /&gt;
    boolean gameMaster = 5;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
message ClothEquipment {&lt;br /&gt;
    int32 hair = 1;&lt;br /&gt;
    int32 face = 2;&lt;br /&gt;
    int32 dress = 3;&lt;br /&gt;
    int32 pants = 4;&lt;br /&gt;
    int32 socks = 5;&lt;br /&gt;
    int32 shoes = 6;&lt;br /&gt;
    int32 gloves = 7;&lt;br /&gt;
    int32 racket = 8;&lt;br /&gt;
    int32 glasses = 9;&lt;br /&gt;
    int32 bag = 10;&lt;br /&gt;
    int32 hat = 11;&lt;br /&gt;
    int32 dye = 12;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
message Player {&lt;br /&gt;
    int32 id = 1;&lt;br /&gt;
    string name = 2;&lt;br /&gt;
    byte level = 3;&lt;br /&gt;
    boolean created = 4;&lt;br /&gt;
    boolean canDelete = 5;&lt;br /&gt;
    int32 gold = 6;&lt;br /&gt;
    byte playerType = 7;&lt;br /&gt;
    byte str = 8;&lt;br /&gt;
    byte sta = 9;&lt;br /&gt;
    byte dex = 10;&lt;br /&gt;
    byte wil = 11;&lt;br /&gt;
    byte statPoints = 12;&lt;br /&gt;
    boolean oldRenameAllowed = 13;&lt;br /&gt;
    boolean renameAllowed = 14;&lt;br /&gt;
    ClothEquipment clothEquipment = 15;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
message SMSG_PlayerList (0x1005) {&lt;br /&gt;
    Account account = 1;&lt;br /&gt;
    repeated Player players = 2;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Practical tips / gotchas ==&lt;br /&gt;
* Keep schema field order exactly matching the on wire protocol order. The generator reads/writes strictly in schema order.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;[type=short]&amp;lt;/code&amp;gt; (or another type) on &amp;lt;code&amp;gt;repeated&amp;lt;/code&amp;gt; if the protocol’s list length prefix is not 1 byte.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;bytes&amp;lt;/code&amp;gt; for raw data blocks; don’t try &amp;lt;code&amp;gt;repeated byte&amp;lt;/code&amp;gt; unless you specifically want a length-prefixed list of bytes.&lt;br /&gt;
* Avoid &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;metaData&amp;lt;/code&amp;gt; as field names.&lt;br /&gt;
* Don’t skip field numbers, generation will fail.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Protocol]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Database_Schema_%26_Cheatsheet&amp;diff=89</id>
		<title>Database Schema &amp; Cheatsheet</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Database_Schema_%26_Cheatsheet&amp;diff=89"/>
		<updated>2025-08-06T13:18:53Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Database Schema &amp;amp; Cheatsheet =&lt;br /&gt;
This page provides an overview of the database structure used in Fantasy Tennis, showing the key tables, their columns, and relationships. The schema diagram below visually represents how different entities connect within the system.&lt;br /&gt;
&lt;br /&gt;
== Schema ==&lt;br /&gt;
[[File:Db-entities.svg|200px|thumb|center|alt=Database Schema|Database Schema]]&lt;br /&gt;
&lt;br /&gt;
== Tables of Interest ==&lt;br /&gt;
Tables might worth looking into as they are important on setting up your own Fantasy Tennis Server Configuration inside the Database.&lt;br /&gt;
&lt;br /&gt;
=== Config ===&lt;br /&gt;
The `config` table is used to store various server wide settings that control gameplay mechanics, security settings, logging behavior, and feature toggles. Each configuration entry consists of:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;name&#039;&#039;&#039; – The unique identifier of the config setting.&lt;br /&gt;
* &#039;&#039;&#039;description&#039;&#039;&#039; – A short explanation of what the setting controls (may be null).&lt;br /&gt;
* &#039;&#039;&#039;value&#039;&#039;&#039; – The actual configured value.&lt;br /&gt;
* &#039;&#039;&#039;type&#039;&#039;&#039; – The data type of the value (e.g., boolean, int, double, string).&lt;br /&gt;
&lt;br /&gt;
Administrators can modify these values to tweak server behavior, enable or disable features, and adjust game mechanics dynamically.&lt;br /&gt;
&lt;br /&gt;
Below is a list of currently supported configuration values along with their default settings.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Name !! Type !! Default Value !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;anticheat.enabled&#039;&#039;&#039; || boolean || false || Enables or disables the built-in anticheat (JFTSE exclusive).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;anticheat.port&#039;&#039;&#039; || int || 1337 || Port used for the anticheat system (JFTSE exclusive).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;dev.packets.handle&#039;&#039;&#039; || boolean || false || Handles developer/debug packets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;packets.id.translate.enabled&#039;&#039;&#039; || boolean || true || Enables translation of packet IDs.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;logging.packets.all.enabled&#039;&#039;&#039; || boolean || true || Enables logging of all network packets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;network.encryption.enabled&#039;&#039;&#039; || boolean || false || Enables network encryption for secure communication.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;password.encryption.enabled&#039;&#039;&#039; || boolean || false || Encrypts player passwords upon login.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;player.level.max&#039;&#039;&#039; || int || 60 || Maximum player level (JFTSE exclusive, levels above 65 require client modifications).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;command.room.mode.change.player.level&#039;&#039;&#039; || int || 60 || Minimum required level to change room mode via command.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.global.gold&#039;&#039;&#039; || int || 5 || Global gold bonus multiplier (Single Player).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.global.exp&#039;&#039;&#039; || int || 5 || Global experience bonus multiplier (Single Player).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.wongame.exp&#039;&#039;&#039; || double || 0.2 || Experience bonus for winning a game (Basic &amp;amp; Battle).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.wongame.gold&#039;&#039;&#039; || double || 0.2 || Gold bonus for winning a game (Basic &amp;amp; Battle).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;logging.packets.console-output.enabled&#039;&#039;&#039; || boolean || true || Enables logging of packets in console output.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;matchplay.guardian.hard.won.ranking-point.multiplier&#039;&#039;&#039; || double || 1.0 || Multiplier applied to ranking points in Guardian Hard mode.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.map.allow.snowmoon&#039;&#039;&#039; || boolean || false || Allows Snow Moon map in Guardian mode.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.spawn.max_per_room&#039;&#039;&#039; || int || 10 || Maximum number of fish allowed per room.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.spawn.count&#039;&#039;&#039; || int || 4 || Number of fish to spawn initially per spawn location.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.inactivity.timeout&#039;&#039;&#039; || int || 10 || Timeout for inactive fish before despawning (in minutes).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.spawn.chance&#039;&#039;&#039; || int || 20 || Chance for fish to spawn at a location (in percent).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.spawn.min_time&#039;&#039;&#039; || double || 6.0 || Minimum time between fish spawns (in seconds).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.spawn.max_time&#039;&#039;&#039; || double || 8.0 || Maximum time between fish spawns (in seconds).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.normal1&#039;&#039;&#039; || double || 2.0 || Standard swimming speed for fish (variant 1).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.normal2&#039;&#039;&#039; || double || 3.0 || Standard swimming speed for fish (variant 2).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.attack&#039;&#039;&#039; || double || 4.0 || Speed used by fish when attacking bait.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.scared&#039;&#039;&#039; || double || 10.0 || Speed used by fish when frightened.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.disengage&#039;&#039;&#039; || double || 10.0 || Speed used by fish when disengaging from bait.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.turning.normal&#039;&#039;&#039; || double || 3.0 || Normal turning speed of fish (in degrees).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.turning.disengage&#039;&#039;&#039; || double || 6.0 || Turning speed during disengagement (in degrees).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.bite.success.chance&#039;&#039;&#039; || int || 40 || Chance for a successful bite once bait is reached (in percent).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.bite.attempt.interval&#039;&#039;&#039; || double || 2.0 || Interval between bite attempts (in seconds).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.radius.attack&#039;&#039;&#039; || double || 4.0 || Attack radius relative to fish length.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.radius.bait.too_close&#039;&#039;&#039; || double || 6.0 || Distance considered too close to bait to attack (relative to length).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.radius.frightened&#039;&#039;&#039; || double || 10.0 || Radius for detecting frightening stimuli (relative to length).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.radius.bait.detection&#039;&#039;&#039; || double || 10.0 || Detection radius for bait (relative to fish length).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.radius.random_position&#039;&#039;&#039; || double || 10.0 || Maximum distance from spawn point for roaming (5.0 = 1 square).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.movement.chance1&#039;&#039;&#039; || int || 40 || Chance for idle fish to start swimming movement (pattern 1).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.movement.chance2&#039;&#039;&#039; || int || 25 || Chance for idle fish to start swimming movement (pattern 2).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Notes:&#039;&#039;&#039;&lt;br /&gt;
* Config values marked as &#039;&#039;&#039;JFTSE exclusive&#039;&#039;&#039; are unique to this server implementation and may not be available in other Fantasy Tennis servers.&lt;br /&gt;
* Those values represent the development chosen defaults and does not represent values set within the [https://jftse.com JFTSE] hosted server.&lt;br /&gt;
&lt;br /&gt;
=== M_Scenarios ===&lt;br /&gt;
Each game mode has multiple scenarios. A scenario marked as &amp;quot;default&amp;quot; is always selected first. If multiple scenarios are active (`status = 1`), the default scenario is prioritized. If multiple active scenarios are marked as default, the first one retrieved from the database is chosen.&lt;br /&gt;
&lt;br /&gt;
Scenario types:&lt;br /&gt;
* &#039;&#039;&#039;Guardian Normal (non Boss Battle)&#039;&#039;&#039; → `GUARDIAN`&lt;br /&gt;
* &#039;&#039;&#039;Boss Guardian&#039;&#039;&#039; → `BOSS_BATTLE`&lt;br /&gt;
* &#039;&#039;&#039;Boss Guardian v2 (new mechanics defined through scripting)&#039;&#039;&#039; → `BOSS_BATTLE_V2`&lt;br /&gt;
&lt;br /&gt;
=== S_Maps ===&lt;br /&gt;
Defines all available maps and references client map id&#039;s.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! bossPlayTime !! breathTime !! description !! isBossStage !! map !! name !! playTime !! triggerBossTime !! useBreathTime&lt;br /&gt;
|-&lt;br /&gt;
| 1  || [NULL]  || 100  || [NULL]  || 0  || 0  || Rubycrab  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 2  || [NULL]  || 100  || [NULL]  || 0  || 1  || Emerald Beach  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 3  || 10  || 100  || [NULL]  || 0  || 2  || Twinkle Town  || 15  || 10  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 4  || [NULL]  || 100  || [NULL]  || 0  || 3  || The Aeolos  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 5  || [NULL]  || 100  || [NULL]  || 0  || 5  || Life Wood  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 6  || 5  || 100  || [NULL]  || 1  || 4  || Snow Moon  || [NULL]  || 8  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 7  || 5  || 100  || [NULL]  || 1  || 6  || Arena  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 8  || 5  || 100  || [NULL]  || 1  || 7  || Monslava  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 9  || 5  || 100  || [NULL]  || 1  || 8  || Monslava Blue  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 10  || 5  || 100  || [NULL]  || 1  || 9  || Devaberg  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 11  || 5  || 100  || [NULL]  || 1  || 10  || Atlantis  || 8  || 4  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 12  || 5  || 100  || [NULL]  || 1  || 11  || Temple  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 13  || [NULL]  || 100  || [NULL]  || 0  || 12  || Room of Shadow  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 14  || 5  || 100  || [NULL]  || 1  || 13  || Machine City  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 15  || 5  || 100  || [NULL]  || 1  || 14  || Dance Time  || [NULL]  || 3  || 0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Map_2_Scenarios ===&lt;br /&gt;
Establishes the link between maps (`S_Maps`) and scenarios (`M_Scenarios`). Without defining mappings in this table, the scenario system will not function correctly.&lt;br /&gt;
&lt;br /&gt;
This table uses a many-to-many relationship, ensuring that:&lt;br /&gt;
* Each scenario can be assigned to multiple maps.&lt;br /&gt;
* Each map can be associated with multiple scenarios.&lt;br /&gt;
&lt;br /&gt;
The relationship is defined as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Column !! Description&lt;br /&gt;
|-&lt;br /&gt;
| `scenario_id` || References `M_Scenarios.id`, linking the scenario.&lt;br /&gt;
|-&lt;br /&gt;
| `map_id` || References `S_Maps.id`, linking the map.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The mapping structure allows flexibility, ensuring that game modes can be dynamically adjusted based on the assigned scenarios.&lt;br /&gt;
&lt;br /&gt;
=== Guardian_2_Maps ===&lt;br /&gt;
Connects a guardian (or boss guardian) to a map with a specific scenario. Each scenario has a unique set of guardians available for matches.&lt;br /&gt;
&lt;br /&gt;
=== Skill_2_Guardians ===&lt;br /&gt;
Links skills to guardians. The connection is made through `Guardian_2_Maps`, associating skills with a guardian in a specific scenario and map. If no `guardian_2_maps` entry is set, it defaults to `btItem`, which is predefined for guardian types. `btItem` is shared among similar guardian types (e.g., Dokaro and related &amp;quot;ro&amp;quot; types).&lt;br /&gt;
&lt;br /&gt;
=== S_Guardian_Multipliers ===&lt;br /&gt;
Defines multipliers for Guardian Mode, applicable for gold or experience.&lt;br /&gt;
&lt;br /&gt;
These multipliers can be linked in the `S_Relationships` table.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! description !! multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 1  || default exp multiplier  || 1.0&lt;br /&gt;
|-&lt;br /&gt;
| 2  || default gold multiplier  || 1.0&lt;br /&gt;
|-&lt;br /&gt;
| 3  || exp x3  || 3.0&lt;br /&gt;
|-&lt;br /&gt;
| 4  || exp x4  || 4.0&lt;br /&gt;
|-&lt;br /&gt;
| 5  || exp x5  || 5.0&lt;br /&gt;
|-&lt;br /&gt;
| 6  || exp x6  || 6.0&lt;br /&gt;
|-&lt;br /&gt;
| 7  || exp x7  || 7.0&lt;br /&gt;
|-&lt;br /&gt;
| 8  || gold x7  || 7.0&lt;br /&gt;
|-&lt;br /&gt;
| 9  || gold x5  || 5.0&lt;br /&gt;
|-&lt;br /&gt;
| 10  || gold x3  || 3.5&lt;br /&gt;
|-&lt;br /&gt;
| 11  || exp x3  || 3.5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== S_Relationships ===&lt;br /&gt;
Defines connections between various entities within the game, enabling precise control over item drops, multipliers, and other gameplay mechanics. Relationships are established using two key fields:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;`id_f`&#039;&#039;&#039; (`id_from`): Represents the originating entity of the relationship, such as a Product or Multiplier.&lt;br /&gt;
* &#039;&#039;&#039;`id_t`&#039;&#039;&#039; (`id_to`): Represents the target entity of the relationship, such as a Guardian, Boss Guardian, or Map.&lt;br /&gt;
&lt;br /&gt;
When linking products specifically to guardians, the &#039;&#039;&#039;`productIndex`&#039;&#039;&#039; from the product definition is used for &#039;&#039;&#039;`id_f`&#039;&#039;&#039;, and the &#039;&#039;&#039;`guardian_2_maps.id`&#039;&#039;&#039; (not the direct Guardian or Boss Guardian ID) is used for &#039;&#039;&#039;`id_t`&#039;&#039;&#039;. Each relationship is further described by its type and role, defining how it influences gameplay.&lt;br /&gt;
&lt;br /&gt;
Relationships can be individually toggled on or off via the &#039;&#039;&#039;`status`&#039;&#039;&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== Advanced Item Drop Control (Role ID 1) ====&lt;br /&gt;
Additionally, enhanced item drop control is available specifically for the &#039;&#039;&#039;Item Drop&#039;&#039;&#039; role (role ID &#039;&#039;&#039;1&#039;&#039;&#039;):&lt;br /&gt;
&lt;br /&gt;
* Default item weight is &#039;&#039;&#039;20.0&#039;&#039;&#039; when no specific weight is provided.&lt;br /&gt;
* A fixed weight of &#039;&#039;&#039;15.0&#039;&#039;&#039; is used to determine scenarios where no item is awarded (displayed as an &amp;quot;X&amp;quot;).&lt;br /&gt;
* Drop exclusivity can be defined specifically for:&lt;br /&gt;
** &#039;&#039;&#039;`forHardMode`&#039;&#039;&#039;: Drops exclusive to [[Guardian Hard Mode]].&lt;br /&gt;
** &#039;&#039;&#039;`forRandomMode`&#039;&#039;&#039;: Drops exclusive to [[Guardian Random Mode]].&lt;br /&gt;
* The &#039;&#039;&#039;`levelReq`&#039;&#039;&#039; field, currently unused, is reserved for future use to restrict drops based on player levels in a planned progression system.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Quantity Determination for Item Drops:&#039;&#039;&#039;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Condition !! Quantity Result&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; is set, &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; not set || Exact quantity from &#039;&#039;&#039;`qty`&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; not set, &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; set || Random quantity between &#039;&#039;&#039;`qtyMin`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMax`&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| All quantity fields &#039;&#039;&#039;NULL&#039;&#039;&#039; || Defaults based on item type:&lt;br /&gt;
* &#039;&#039;&#039;Material&#039;&#039;&#039;: Min 1 / Max 3&lt;br /&gt;
* &#039;&#039;&#039;Quick&#039;&#039;&#039;: Min 5 / Max 100&lt;br /&gt;
* &#039;&#039;&#039;Parts &amp;amp; Others&#039;&#039;&#039;: Min 1 / Max 1&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; both set || Random quantity between &#039;&#039;&#039;`qtyMin`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMax`&#039;&#039;&#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== S_Relationship_Types ===&lt;br /&gt;
Defines supported relationship types, describing what entities can be linked together:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Product to Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Product to Boss Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 3  || Product to Map&lt;br /&gt;
|-&lt;br /&gt;
| 4  || Multiplier to Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 5  || Multiplier to Boss Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 6  || Multiplier to Map&lt;br /&gt;
|-&lt;br /&gt;
| 7  || Product to Map (Guardian Mode)&lt;br /&gt;
|-&lt;br /&gt;
| 8  || Product to Sell Price&lt;br /&gt;
|-&lt;br /&gt;
| 9  || Fishing: Product to Group&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== S_Relationship_Roles ===&lt;br /&gt;
Defines the supported roles that relationships can take, describing their in game functionality:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Item Drop&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Exp Multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 3  || Gold Multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 4  || Item Sell Price&lt;br /&gt;
|-&lt;br /&gt;
| 5  || Fishing Item Drop&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== K_Status ===&lt;br /&gt;
Defines the different status types used in game.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Active&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Inactive&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Notes &amp;amp; Additional Information ===&lt;br /&gt;
* The database schema is subject to updates as new mechanics and features are introduced.&lt;br /&gt;
* Some tables may have additional columns not explicitly documented if they are not required for core functionality.&lt;br /&gt;
* Unused fields, such as `levelReq` in `S_Relationships`, are placeholders for future game progression updates.&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Commands&amp;diff=88</id>
		<title>Commands</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Commands&amp;diff=88"/>
		<updated>2025-08-06T09:40:36Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Ingame Commands =&lt;br /&gt;
&lt;br /&gt;
This page provides a list of available ingame commands for &#039;&#039;&#039;Fantasy Tennis&#039;&#039;&#039; within &#039;&#039;&#039;JFTSE&#039;&#039;&#039;. Commands must be prefixed with a `-` to execute. Some commands are &#039;&#039;&#039;restricted to Game Masters (GMs)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== General Commands ==&lt;br /&gt;
These commands can be used by all players.&lt;br /&gt;
&lt;br /&gt;
=== 🎲 Random Mode ===&lt;br /&gt;
[[Guardian Random Mode|Random guardians]] will spawn instead of the default ones (boss remains the same).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-random&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🔥 Hard Mode ===&lt;br /&gt;
Enables &#039;&#039;&#039;[[Guardian Hard Mode|Hard Mode]]&#039;&#039;&#039;, increasing enemy stats and spawning additional guardians.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-hard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🎁 Gacha Opening ===&lt;br /&gt;
Opens gachas without animation for faster processing.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-og &amp;quot;Gacha Name&amp;quot; &amp;lt;amount&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-og &amp;quot;Western Coin&amp;quot; 50&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
➡ &#039;&#039;&#039;&amp;lt;span style=&amp;quot;color:red;&amp;quot;&amp;gt;Note:&amp;lt;/span&amp;gt;&#039;&#039;&#039; Some gachas contain &amp;quot;ll&amp;quot; (e.g., `&amp;quot;Rare Box ll&amp;quot;`), requiring a lowercase `L` in the command.&lt;br /&gt;
&lt;br /&gt;
=== 🏆 Arcade Mode ===&lt;br /&gt;
Enters &#039;&#039;&#039;Arcade Mode&#039;&#039;&#039;. WIP.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-arcade&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🔄 Pointback ===&lt;br /&gt;
Allows players to vote for resetting the last point in &#039;&#039;&#039;[[Basic Mode]]&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-pb&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
➡ &#039;&#039;&#039;Note:&#039;&#039;&#039; All players must vote using `-pb` before a new point is made.&lt;br /&gt;
&lt;br /&gt;
== GM Commands ==&lt;br /&gt;
These commands are &#039;&#039;&#039;restricted to Game Masters (GMs)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== 🚫 Player Management ===&lt;br /&gt;
Banning, unbanning, and kicking players.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-ban &amp;lt;player&amp;gt;&lt;br /&gt;
-unban &amp;lt;player&amp;gt;&lt;br /&gt;
-serverKick &amp;lt;player&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 📝 Server Notice ===&lt;br /&gt;
Sets and broadcasts a &#039;&#039;&#039;server wide notice&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-sN &amp;lt;message&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🔄 Reset &amp;amp; Reload ===&lt;br /&gt;
Resets or reloads server data.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-rsLogin &amp;lt;player&amp;gt;    # Resets login status&lt;br /&gt;
-reloadScripts       # Reloads all scripts&lt;br /&gt;
-event               # Event configuration&lt;br /&gt;
-reloadFish          # Reloads fish settings&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🎁 Item &amp;amp; Gift Commands ===&lt;br /&gt;
Used to grant items, experience, or gold to players.&lt;br /&gt;
&lt;br /&gt;
Give items to &#039;&#039;&#039;another player&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-give &amp;lt;player&amp;gt; &amp;lt;item&amp;gt; &amp;lt;amount&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Give items to &#039;&#039;&#039;yourself&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-get &amp;lt;item&amp;gt; &amp;lt;amount&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Send a &#039;&#039;&#039;gift&#039;&#039;&#039; to another player:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-gift &amp;lt;player&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Help Command ==&lt;br /&gt;
Displays all commands and their description.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-help&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;Notes:&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
* Commands &#039;&#039;&#039;must always start with `-`&#039;&#039;&#039;.&lt;br /&gt;
* Some commands may have additional restrictions (e.g., requiring level 60).&lt;br /&gt;
* GM commands are &#039;&#039;&#039;restricted&#039;&#039;&#039; to authorized users.&lt;br /&gt;
&lt;br /&gt;
For further details, refer to the ingame chat.&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Database_Schema_%26_Cheatsheet&amp;diff=87</id>
		<title>Database Schema &amp; Cheatsheet</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Database_Schema_%26_Cheatsheet&amp;diff=87"/>
		<updated>2025-08-06T09:35:04Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Database Schema &amp;amp; Cheatsheet =&lt;br /&gt;
This page provides an overview of the database structure used in Fantasy Tennis, showing the key tables, their columns, and relationships. The schema diagram below visually represents how different entities connect within the system.&lt;br /&gt;
&lt;br /&gt;
== Schema ==&lt;br /&gt;
[[File:Db-entities.svg|200px|thumb|center|alt=Database Schema|Database Schema]]&lt;br /&gt;
&lt;br /&gt;
== Tables of Interest ==&lt;br /&gt;
Tables might worth looking into as they are important on setting up your own Fantasy Tennis Server Configuration inside the Database.&lt;br /&gt;
&lt;br /&gt;
=== Config ===&lt;br /&gt;
The `config` table is used to store various server wide settings that control gameplay mechanics, security settings, logging behavior, and feature toggles. Each configuration entry consists of:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;name&#039;&#039;&#039; – The unique identifier of the config setting.&lt;br /&gt;
* &#039;&#039;&#039;description&#039;&#039;&#039; – A short explanation of what the setting controls (may be null).&lt;br /&gt;
* &#039;&#039;&#039;value&#039;&#039;&#039; – The actual configured value.&lt;br /&gt;
* &#039;&#039;&#039;type&#039;&#039;&#039; – The data type of the value (e.g., boolean, int, double, string).&lt;br /&gt;
&lt;br /&gt;
Administrators can modify these values to tweak server behavior, enable or disable features, and adjust game mechanics dynamically.&lt;br /&gt;
&lt;br /&gt;
Below is a list of currently supported configuration values along with their default settings.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Name !! Type !! Default Value !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;anticheat.enabled&#039;&#039;&#039; || boolean || false || Enables or disables the built-in anticheat (JFTSE exclusive).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;anticheat.port&#039;&#039;&#039; || int || 1337 || Port used for the anticheat system (JFTSE exclusive).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;dev.packets.handle&#039;&#039;&#039; || boolean || false || Handles developer/debug packets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;packets.id.translate.enabled&#039;&#039;&#039; || boolean || true || Enables translation of packet IDs.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;logging.packets.all.enabled&#039;&#039;&#039; || boolean || true || Enables logging of all network packets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;network.encryption.enabled&#039;&#039;&#039; || boolean || false || Enables network encryption for secure communication.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;password.encryption.enabled&#039;&#039;&#039; || boolean || false || Encrypts player passwords upon login.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;player.level.max&#039;&#039;&#039; || int || 60 || Maximum player level (JFTSE exclusive, levels above 65 require client modifications).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;command.room.mode.change.player.level&#039;&#039;&#039; || int || 60 || Minimum required level to change room mode via command.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.global.gold&#039;&#039;&#039; || int || 5 || Global gold bonus multiplier (Single Player).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.global.exp&#039;&#039;&#039; || int || 5 || Global experience bonus multiplier (Single Player).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.wongame.exp&#039;&#039;&#039; || double || 0.2 || Experience bonus for winning a game (Basic &amp;amp; Battle).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.wongame.gold&#039;&#039;&#039; || double || 0.2 || Gold bonus for winning a game (Basic &amp;amp; Battle).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;logging.packets.console-output.enabled&#039;&#039;&#039; || boolean || true || Enables logging of packets in console output.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;matchplay.guardian.hard.won.ranking-point.multiplier&#039;&#039;&#039; || double || 1.0 || Multiplier applied to ranking points in Guardian Hard mode.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.map.allow.snowmoon&#039;&#039;&#039; || boolean || false || Allows Snow Moon map in Guardian mode.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.spawn.max_per_room&#039;&#039;&#039; || int || 10 || Maximum number of fish allowed per room.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.spawn.count&#039;&#039;&#039; || int || 4 || Number of fish to spawn initially per spawn location.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.inactivity.timeout&#039;&#039;&#039; || int || 10 || Timeout for inactive fish before despawning (in minutes).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.spawn.chance&#039;&#039;&#039; || int || 20 || Chance for fish to spawn at a location (in percent).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.spawn.min_time&#039;&#039;&#039; || double || 6.0 || Minimum time between fish spawns (in seconds).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.spawn.max_time&#039;&#039;&#039; || double || 8.0 || Maximum time between fish spawns (in seconds).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.normal1&#039;&#039;&#039; || double || 2.0 || Standard swimming speed for fish (variant 1).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.normal2&#039;&#039;&#039; || double || 3.0 || Standard swimming speed for fish (variant 2).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.attack&#039;&#039;&#039; || double || 4.0 || Speed used by fish when attacking bait.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.scared&#039;&#039;&#039; || double || 10.0 || Speed used by fish when frightened.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.disengage&#039;&#039;&#039; || double || 10.0 || Speed used by fish when disengaging from bait.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.turning.normal&#039;&#039;&#039; || double || 3.0 || Normal turning speed of fish (in degrees).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.speed.turning.disengage&#039;&#039;&#039; || double || 6.0 || Turning speed during disengagement (in degrees).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.bite.success.chance&#039;&#039;&#039; || int || 40 || Chance for a successful bite once bait is reached (in percent).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.bite.attempt.interval&#039;&#039;&#039; || double || 2.0 || Interval between bite attempts (in seconds).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.radius.attack&#039;&#039;&#039; || double || 4.0 || Attack radius relative to fish length.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.radius.bait.too_close&#039;&#039;&#039; || double || 6.0 || Distance considered too close to bait to attack (relative to length).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.radius.frightened&#039;&#039;&#039; || double || 10.0 || Radius for detecting frightening stimuli (relative to length).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.radius.bait.detection&#039;&#039;&#039; || double || 10.0 || Detection radius for bait (relative to fish length).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.radius.random_position&#039;&#039;&#039; || double || 10.0 || Maximum distance from spawn point for roaming (5.0 = 1 square).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.movement.chance1&#039;&#039;&#039; || int || 40 || Chance for idle fish to start swimming movement (pattern 1).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;fish.movement.chance2&#039;&#039;&#039; || int || 25 || Chance for idle fish to start swimming movement (pattern 2).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Notes:&#039;&#039;&#039;&lt;br /&gt;
* Config values marked as &#039;&#039;&#039;JFTSE exclusive&#039;&#039;&#039; are unique to this server implementation and may not be available in other Fantasy Tennis servers.&lt;br /&gt;
* Those values represent the development chosen defaults and does not represent values set within the [https://jftse.com JFTSE] hosted server.&lt;br /&gt;
&lt;br /&gt;
=== M_Scenarios ===&lt;br /&gt;
Each game mode has multiple scenarios. A scenario marked as &amp;quot;default&amp;quot; is always selected first. If multiple scenarios are active (`status = 1`), the default scenario is prioritized. If multiple active scenarios are marked as default, the first one retrieved from the database is chosen.&lt;br /&gt;
&lt;br /&gt;
Scenario types:&lt;br /&gt;
* &#039;&#039;&#039;Guardian Normal (non Boss Battle)&#039;&#039;&#039; → `GUARDIAN`&lt;br /&gt;
* &#039;&#039;&#039;Boss Guardian&#039;&#039;&#039; → `BOSS_BATTLE`&lt;br /&gt;
* &#039;&#039;&#039;Boss Guardian v2 (new mechanics defined through scripting)&#039;&#039;&#039; → `BOSS_BATTLE_V2`&lt;br /&gt;
&lt;br /&gt;
=== S_Maps ===&lt;br /&gt;
Defines all available maps and references client map id&#039;s.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! bossPlayTime !! breathTime !! description !! isBossStage !! map !! name !! playTime !! triggerBossTime !! useBreathTime&lt;br /&gt;
|-&lt;br /&gt;
| 1  || [NULL]  || 100  || [NULL]  || 0  || 0  || Rubycrab  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 2  || [NULL]  || 100  || [NULL]  || 0  || 1  || Emerald Beach  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 3  || 10  || 100  || [NULL]  || 0  || 2  || Twinkle Town  || 15  || 10  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 4  || [NULL]  || 100  || [NULL]  || 0  || 3  || The Aeolos  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 5  || [NULL]  || 100  || [NULL]  || 0  || 5  || Life Wood  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 6  || 5  || 100  || [NULL]  || 1  || 4  || Snow Moon  || [NULL]  || 8  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 7  || 5  || 100  || [NULL]  || 1  || 6  || Arena  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 8  || 5  || 100  || [NULL]  || 1  || 7  || Monslava  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 9  || 5  || 100  || [NULL]  || 1  || 8  || Monslava Blue  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 10  || 5  || 100  || [NULL]  || 1  || 9  || Devaberg  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 11  || 5  || 100  || [NULL]  || 1  || 10  || Atlantis  || 8  || 4  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 12  || 5  || 100  || [NULL]  || 1  || 11  || Temple  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 13  || [NULL]  || 100  || [NULL]  || 0  || 12  || Room of Shadow  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 14  || 5  || 100  || [NULL]  || 1  || 13  || Machine City  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 15  || 5  || 100  || [NULL]  || 1  || 14  || Dance Time  || [NULL]  || 3  || 0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Map_2_Scenarios ===&lt;br /&gt;
Establishes the link between maps (`S_Maps`) and scenarios (`M_Scenarios`). Without defining mappings in this table, the scenario system will not function correctly.&lt;br /&gt;
&lt;br /&gt;
This table uses a many-to-many relationship, ensuring that:&lt;br /&gt;
* Each scenario can be assigned to multiple maps.&lt;br /&gt;
* Each map can be associated with multiple scenarios.&lt;br /&gt;
&lt;br /&gt;
The relationship is defined as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Column !! Description&lt;br /&gt;
|-&lt;br /&gt;
| `scenario_id` || References `M_Scenarios.id`, linking the scenario.&lt;br /&gt;
|-&lt;br /&gt;
| `map_id` || References `S_Maps.id`, linking the map.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The mapping structure allows flexibility, ensuring that game modes can be dynamically adjusted based on the assigned scenarios.&lt;br /&gt;
&lt;br /&gt;
=== Guardian_2_Maps ===&lt;br /&gt;
Connects a guardian (or boss guardian) to a map with a specific scenario. Each scenario has a unique set of guardians available for matches.&lt;br /&gt;
&lt;br /&gt;
=== Skill_2_Guardians ===&lt;br /&gt;
Links skills to guardians. The connection is made through `Guardian_2_Maps`, associating skills with a guardian in a specific scenario and map. If no `guardian_2_maps` entry is set, it defaults to `btItem`, which is predefined for guardian types. `btItem` is shared among similar guardian types (e.g., Dokaro and related &amp;quot;ro&amp;quot; types).&lt;br /&gt;
&lt;br /&gt;
=== S_Guardian_Multipliers ===&lt;br /&gt;
Defines multipliers for Guardian Mode, applicable for gold or experience.&lt;br /&gt;
&lt;br /&gt;
These multipliers can be linked in the `S_Relationships` table.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! description !! multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 1  || default exp multiplier  || 1.0&lt;br /&gt;
|-&lt;br /&gt;
| 2  || default gold multiplier  || 1.0&lt;br /&gt;
|-&lt;br /&gt;
| 3  || exp x3  || 3.0&lt;br /&gt;
|-&lt;br /&gt;
| 4  || exp x4  || 4.0&lt;br /&gt;
|-&lt;br /&gt;
| 5  || exp x5  || 5.0&lt;br /&gt;
|-&lt;br /&gt;
| 6  || exp x6  || 6.0&lt;br /&gt;
|-&lt;br /&gt;
| 7  || exp x7  || 7.0&lt;br /&gt;
|-&lt;br /&gt;
| 8  || gold x7  || 7.0&lt;br /&gt;
|-&lt;br /&gt;
| 9  || gold x5  || 5.0&lt;br /&gt;
|-&lt;br /&gt;
| 10  || gold x3  || 3.5&lt;br /&gt;
|-&lt;br /&gt;
| 11  || exp x3  || 3.5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== S_Relationships ===&lt;br /&gt;
Defines connections between various entities within the game, enabling precise control over item drops, multipliers, and other gameplay mechanics. Relationships are established using two key fields:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;`id_f`&#039;&#039;&#039; (`id_from`): Represents the originating entity of the relationship, such as a Product or Multiplier.&lt;br /&gt;
* &#039;&#039;&#039;`id_t`&#039;&#039;&#039; (`id_to`): Represents the target entity of the relationship, such as a Guardian, Boss Guardian, or Map.&lt;br /&gt;
&lt;br /&gt;
When linking products specifically to guardians, the &#039;&#039;&#039;`productIndex`&#039;&#039;&#039; from the product definition is used for &#039;&#039;&#039;`id_f`&#039;&#039;&#039;, and the &#039;&#039;&#039;`guardian_2_maps.id`&#039;&#039;&#039; (not the direct Guardian or Boss Guardian ID) is used for &#039;&#039;&#039;`id_t`&#039;&#039;&#039;. Each relationship is further described by its type and role, defining how it influences gameplay.&lt;br /&gt;
&lt;br /&gt;
Relationships can be individually toggled on or off via the &#039;&#039;&#039;`status`&#039;&#039;&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== Advanced Item Drop Control (Role ID 1) ====&lt;br /&gt;
Additionally, enhanced item drop control is available specifically for the &#039;&#039;&#039;Item Drop&#039;&#039;&#039; role (role ID &#039;&#039;&#039;1&#039;&#039;&#039;):&lt;br /&gt;
&lt;br /&gt;
* Default item weight is &#039;&#039;&#039;20.0&#039;&#039;&#039; when no specific weight is provided.&lt;br /&gt;
* A fixed weight of &#039;&#039;&#039;15.0&#039;&#039;&#039; is used to determine scenarios where no item is awarded (displayed as an &amp;quot;X&amp;quot;).&lt;br /&gt;
* Drop exclusivity can be defined specifically for:&lt;br /&gt;
** &#039;&#039;&#039;`forHardMode`&#039;&#039;&#039;: Drops exclusive to [[Guardian Hard Mode]].&lt;br /&gt;
** &#039;&#039;&#039;`forRandomMode`&#039;&#039;&#039;: Drops exclusive to [[Guardian Random Mode]].&lt;br /&gt;
* The &#039;&#039;&#039;`levelReq`&#039;&#039;&#039; field, currently unused, is reserved for future use to restrict drops based on player levels in a planned progression system.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Quantity Determination for Item Drops:&#039;&#039;&#039;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Condition !! Quantity Result&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; is set, &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; not set || Exact quantity from &#039;&#039;&#039;`qty`&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; not set, &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; set || Random quantity between &#039;&#039;&#039;`qtyMin`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMax`&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| All quantity fields &#039;&#039;&#039;NULL&#039;&#039;&#039; || Defaults based on item type:&lt;br /&gt;
* &#039;&#039;&#039;Material&#039;&#039;&#039;: Min 1 / Max 3&lt;br /&gt;
* &#039;&#039;&#039;Quick&#039;&#039;&#039;: Min 5 / Max 100&lt;br /&gt;
* &#039;&#039;&#039;Parts &amp;amp; Others&#039;&#039;&#039;: Min 1 / Max 1&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; both set || Random quantity between &#039;&#039;&#039;`qtyMin`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMax`&#039;&#039;&#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== S_Relationship_Types ===&lt;br /&gt;
Defines supported relationship types, describing what entities can be linked together:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Product to Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Product to Boss Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 3  || Product to Map&lt;br /&gt;
|-&lt;br /&gt;
| 4  || Multiplier to Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 5  || Multiplier to Boss Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 6  || Multiplier to Map&lt;br /&gt;
|-&lt;br /&gt;
| 7  || Product to Map (Guardian Mode)&lt;br /&gt;
|-&lt;br /&gt;
| 8  || Product to Sell Price&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== S_Relationship_Roles ===&lt;br /&gt;
Defines the supported roles that relationships can take, describing their in game functionality:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Item Drop&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Exp Multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 3  || Gold Multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 4  || Item Sell Price&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== K_Status ===&lt;br /&gt;
Defines the different status types used in game.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Active&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Inactive&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Notes &amp;amp; Additional Information ===&lt;br /&gt;
* The database schema is subject to updates as new mechanics and features are introduced.&lt;br /&gt;
* Some tables may have additional columns not explicitly documented if they are not required for core functionality.&lt;br /&gt;
* Unused fields, such as `levelReq` in `S_Relationships`, are placeholders for future game progression updates.&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=86</id>
		<title>List of GameEventType Events</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=86"/>
		<updated>2025-07-08T11:53:57Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== List of Events (GameEventType) ==&lt;br /&gt;
&lt;br /&gt;
This page lists the currently available events you can hook into using the &amp;lt;code&amp;gt;geb.on(&amp;quot;EVENT_NAME&amp;quot;, ...)&amp;lt;/code&amp;gt; function from JavaScript.&lt;br /&gt;
&lt;br /&gt;
All events come from the &amp;lt;code&amp;gt;GameEventType&amp;lt;/code&amp;gt; enum in either the &#039;&#039;&#039;game server&#039;&#039;&#039; or &#039;&#039;&#039;chat server&#039;&#039;&#039; codebase.  &lt;br /&gt;
These are used in event scripts placed inside &amp;lt;code&amp;gt;scripts/event/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Game Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Parameters !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || &amp;lt;code&amp;gt;long diff&amp;lt;/code&amp;gt; || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt; || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || || Player logs out&lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_INIT_HP || ❌ || ||  &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_DAMAGE || ❌ || ||  &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_POINT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_FINISH || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| EMBLEM_INIT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ENCHANT_ON_ANNOUNCE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_ENCHANT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| GACHA_OPENED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| RECIPE_COMBINED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| INVENTORY_ITEM_TIME_EXPIRED || ❌ || || Time-based item expired&lt;br /&gt;
|-&lt;br /&gt;
| ON_PLAYER_ANNOUNCE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| PLAYER_QUICK_SLOT_USE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_JOINED || ❌ || || Player entered the lobby&lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_LEFT || ❌ || || Player left the lobby&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LOBBY_JOINED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_CREATED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_JOINED || ❌ || || Player joined a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LEFT || ❌ || || Player left a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_POSITION_CHANGED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_MAP_CHANGED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| MP_RELAY_CONNECTED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_TRIGGERED || ✅ || &amp;lt;code&amp;gt;FTClient, Room, RoomPlayer&amp;lt;/code&amp;gt; || After skipping the match intro, this is called before Player stats are sent to the client&lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_END || ✅ || &amp;lt;code&amp;gt;MatchplayGame, Room&amp;lt;/code&amp;gt; || Skipping match intro finished and is called right before guardian serve in battle or guardian mode, or serve in basic mode&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_START || ❌ || || Match begins&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_END || ✅ || &amp;lt;code&amp;gt;MatchplayBasicGame / MatchplayBattleGame / MatchplayGuardianGame, Room, ConcurrentLinkedDeque&amp;amp;lt;FTClient&amp;amp;gt;&amp;lt;/code&amp;gt; || Match ends. Game object can be one of these three&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_USE_SKILL || ✅ || &amp;lt;code&amp;gt;FTClient, MatchplayGame, RoomPlayer, Skill, SkillUse, C2SMatchplayUsesSkill&amp;lt;/code&amp;gt; || A skill was used&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_PICKING_UP_CRYSTAL || ✅ || &amp;lt;code&amp;gt;FTClient, SkillCrystal, int randomSkillIndex&amp;lt;/code&amp;gt; || Player crystal pickup&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_HITS_TARGET || ✅ || &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
(1) FTClient, MatchplayGame, Skill&lt;br /&gt;
&lt;br /&gt;
(2) FTConnection, MatchplayGame, short newHealth, C2SMatchplaySkillHitsTarget&lt;br /&gt;
&lt;br /&gt;
(3) FTConnection, MatchplayGame, short newHealth, Skill, C2SMatchplaySkillHitsTarget&lt;br /&gt;
&amp;lt;/code&amp;gt; &lt;br /&gt;
|| Supports multiple overloads. A target is hit during battle or guardian mode. See [[Event_MP_PLAYER_HITS_TARGET|Event MP_PLAYER_HITS_TARGET]]&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_SWAP_QUICK_SLOT_ITEMS || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_MAINTENANCE_REQUESTED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD_PREPARE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_ITEM_BOUGHT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_INIT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_FINISH || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_DEV_PACKET || ❌ || || Developer-only test packet&lt;br /&gt;
|-&lt;br /&gt;
| SESSION_TIME_UPDATE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_HEARTBEAT || ❌ || || &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Chat Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Parameters !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || &amp;lt;code&amp;gt;long diff&amp;lt;/code&amp;gt; || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt; || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || || Player logs out&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Legend ==&lt;br /&gt;
;✅ : Event is currently called (triggered by the server)&lt;br /&gt;
;❌ : Event exists but is not currently triggered&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
In the &amp;quot;Parameters &amp;quot; column, Java &#039;&#039;&#039;types&#039;&#039;&#039; are shown (e.g. &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;MatchplayGame&amp;lt;/code&amp;gt;), not variable names.&amp;lt;br&amp;gt;&lt;br /&gt;
This is to help you identify the object classes you can interact with inside your script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;Don&#039;t see your needed event called yet?&#039;&#039;  &lt;br /&gt;
→ You can still write scripts using them and ask for the event to be wired on Discord.  &lt;br /&gt;
Include the event name and what parameters your handler expects.&lt;br /&gt;
&lt;br /&gt;
== Event Lists ==&lt;br /&gt;
* [[#Game Server Events|Game Server]]&lt;br /&gt;
* [[#Chat Server Events|Chat Server]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Event]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=85</id>
		<title>List of GameEventType Events</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=85"/>
		<updated>2025-07-08T11:53:34Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== List of Events (GameEventType) ==&lt;br /&gt;
&lt;br /&gt;
This page lists the currently available events you can hook into using the &amp;lt;code&amp;gt;geb.on(&amp;quot;EVENT_NAME&amp;quot;, ...)&amp;lt;/code&amp;gt; function from JavaScript.&lt;br /&gt;
&lt;br /&gt;
All events come from the &amp;lt;code&amp;gt;GameEventType&amp;lt;/code&amp;gt; enum in either the &#039;&#039;&#039;game server&#039;&#039;&#039; or &#039;&#039;&#039;chat server&#039;&#039;&#039; codebase.  &lt;br /&gt;
These are used in event scripts placed inside &amp;lt;code&amp;gt;scripts/event/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Game Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Parameters !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || &amp;lt;code&amp;gt;long diff&amp;lt;/code&amp;gt; || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt; || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || || Player logs out&lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_INIT_HP || ❌ || ||  &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_DAMAGE || ❌ || ||  &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_POINT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_FINISH || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| EMBLEM_INIT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ENCHANT_ON_ANNOUNCE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_ENCHANT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| GACHA_OPENED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| RECIPE_COMBINED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| INVENTORY_ITEM_TIME_EXPIRED || ❌ || || Time-based item expired&lt;br /&gt;
|-&lt;br /&gt;
| ON_PLAYER_ANNOUNCE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| PLAYER_QUICK_SLOT_USE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_JOINED || ❌ || || Player entered the lobby&lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_LEFT || ❌ || || Player left the lobby&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LOBBY_JOINED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_CREATED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_JOINED || ❌ || || Player joined a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LEFT || ❌ || || Player left a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_POSITION_CHANGED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_MAP_CHANGED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| MP_RELAY_CONNECTED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_TRIGGERED || ✅ || &amp;lt;code&amp;gt;FTClient, Room, RoomPlayer&amp;lt;/code&amp;gt; || After skipping the match intro, this is called before Player stats are sent to the client&lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_END || ✅ || &amp;lt;code&amp;gt;FTClient, Room&amp;lt;/code&amp;gt; || Skipping match intro finished and is called right before guardian serve in battle or guardian mode, or serve in basic mode&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_START || ❌ || || Match begins&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_END || ✅ || &amp;lt;code&amp;gt;MatchplayBasicGame / MatchplayBattleGame / MatchplayGuardianGame, Room, ConcurrentLinkedDeque&amp;amp;lt;FTClient&amp;amp;gt;&amp;lt;/code&amp;gt; || Match ends. Game object can be one of these three&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_USE_SKILL || ✅ || &amp;lt;code&amp;gt;FTClient, MatchplayGame, RoomPlayer, Skill, SkillUse, C2SMatchplayUsesSkill&amp;lt;/code&amp;gt; || A skill was used&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_PICKING_UP_CRYSTAL || ✅ || &amp;lt;code&amp;gt;FTClient, SkillCrystal, int randomSkillIndex&amp;lt;/code&amp;gt; || Player crystal pickup&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_HITS_TARGET || ✅ || &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
(1) FTClient, MatchplayGame, Skill&lt;br /&gt;
&lt;br /&gt;
(2) FTConnection, MatchplayGame, short newHealth, C2SMatchplaySkillHitsTarget&lt;br /&gt;
&lt;br /&gt;
(3) FTConnection, MatchplayGame, short newHealth, Skill, C2SMatchplaySkillHitsTarget&lt;br /&gt;
&amp;lt;/code&amp;gt; &lt;br /&gt;
|| Supports multiple overloads. A target is hit during battle or guardian mode. See [[Event_MP_PLAYER_HITS_TARGET|Event MP_PLAYER_HITS_TARGET]]&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_SWAP_QUICK_SLOT_ITEMS || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_MAINTENANCE_REQUESTED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD_PREPARE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_ITEM_BOUGHT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_INIT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_FINISH || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_DEV_PACKET || ❌ || || Developer-only test packet&lt;br /&gt;
|-&lt;br /&gt;
| SESSION_TIME_UPDATE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_HEARTBEAT || ❌ || || &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Chat Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Parameters !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || &amp;lt;code&amp;gt;long diff&amp;lt;/code&amp;gt; || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt; || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || || Player logs out&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Legend ==&lt;br /&gt;
;✅ : Event is currently called (triggered by the server)&lt;br /&gt;
;❌ : Event exists but is not currently triggered&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
In the &amp;quot;Parameters &amp;quot; column, Java &#039;&#039;&#039;types&#039;&#039;&#039; are shown (e.g. &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;MatchplayGame&amp;lt;/code&amp;gt;), not variable names.&amp;lt;br&amp;gt;&lt;br /&gt;
This is to help you identify the object classes you can interact with inside your script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;Don&#039;t see your needed event called yet?&#039;&#039;  &lt;br /&gt;
→ You can still write scripts using them and ask for the event to be wired on Discord.  &lt;br /&gt;
Include the event name and what parameters your handler expects.&lt;br /&gt;
&lt;br /&gt;
== Event Lists ==&lt;br /&gt;
* [[#Game Server Events|Game Server]]&lt;br /&gt;
* [[#Chat Server Events|Chat Server]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Event]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Creating_custom_events&amp;diff=84</id>
		<title>Creating custom events</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Creating_custom_events&amp;diff=84"/>
		<updated>2025-07-08T11:10:21Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Scripted Event System =&lt;br /&gt;
&lt;br /&gt;
This page documents how to implement custom in-game event logic using the internal JavaScript scripting system.&lt;br /&gt;
&lt;br /&gt;
All scripts are located in:&lt;br /&gt;
&amp;lt;pre&amp;gt;game-server/src/main/resources/scripts/event&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Files are named as:&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;amp;lt;ID&amp;amp;gt;_&amp;amp;lt;name&amp;amp;gt;.js&amp;lt;/pre&amp;gt;&lt;br /&gt;
Example:&lt;br /&gt;
* &amp;lt;code&amp;gt;1_exampleEvent.js&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;2_exampleEvent2.js&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;3_weeklyLogin.js&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Registering to an Event ==&lt;br /&gt;
&lt;br /&gt;
Scripts are registered to server events via:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;ON_LOGIN&amp;quot;, function(client) {&lt;br /&gt;
    // Runs on player login&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Another example for match end:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;MP_MATCH_END&amp;quot;, function(game, room, clients) {&lt;br /&gt;
    // Runs when match ends&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The string refers to a constant from the &amp;lt;code&amp;gt;GameEventType&amp;lt;/code&amp;gt; enum:&lt;br /&gt;
&amp;lt;pre&amp;gt;com.jftse.emulator.server.core.life.event.GameEventType&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Script Environment ==&lt;br /&gt;
&lt;br /&gt;
The following bindings are available in every script:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gameManager&lt;br /&gt;
serviceManager&lt;br /&gt;
threadManager&lt;br /&gt;
eventHandler&lt;br /&gt;
state&lt;br /&gt;
geb&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These expose access to game services, event queues, persistent state, and event registration.&lt;br /&gt;
&lt;br /&gt;
== Using Persistent State ==&lt;br /&gt;
&lt;br /&gt;
To save player-specific or global data across restarts:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let accountId = client.getAccountId();&lt;br /&gt;
let loginData = JSON.parse(state.get(&amp;quot;loginTracker&amp;quot;, accountId) || &amp;quot;{}&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
loginData.lastLogin = new Date().toISOString();&lt;br /&gt;
&lt;br /&gt;
state.set(&amp;quot;loginTracker&amp;quot;, accountId, JSON.stringify(loginData));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This allows tracking login streaks, cooldowns, event progress, and more.&lt;br /&gt;
&lt;br /&gt;
== Java Class Access ==&lt;br /&gt;
&lt;br /&gt;
You can use &amp;lt;code&amp;gt;Java.type(...)&amp;lt;/code&amp;gt; to access and work with server-side Java classes directly from your script.  &lt;br /&gt;
This allows you to integrate deeply with server logic, access player data, send packets, schedule tasks, and more.&lt;br /&gt;
&lt;br /&gt;
=== Example: Give gold and send a chat message ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let PlayerScriptableImpl = Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
let helper = new PlayerScriptableImpl(client);&lt;br /&gt;
helper.giveGold(500);&lt;br /&gt;
helper.sendChat(&amp;quot;System&amp;quot;, &amp;quot;You received 500 gold!&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Example: Send a room chat packet manually ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let S2CChatRoomAnswerPacket = Java.type(&amp;quot;com.jftse.emulator.server.core.packets.chat.S2CChatRoomAnswerPacket&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
let packet = new S2CChatRoomAnswerPacket(2, &amp;quot;Server&amp;quot;, &amp;quot;Welcome!&amp;quot;);&lt;br /&gt;
client.getConnection().sendTCP(packet);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Example: Access services via ServiceManager ===&lt;br /&gt;
You can access any backend service via the pre-bound &amp;lt;code&amp;gt;serviceManager&amp;lt;/code&amp;gt; instance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let playerService = serviceManager.getPlayerService();&lt;br /&gt;
let player = playerService.findById(playerId);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Example: Schedule delayed task with EventHandler ===&lt;br /&gt;
Use &amp;lt;code&amp;gt;eventHandler.createRunnableEvent&amp;lt;/code&amp;gt; to run code after a delay (milliseconds):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let PlayerScriptableImpl = Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;);&lt;br /&gt;
let helper = new PlayerScriptableImpl(client);&lt;br /&gt;
&lt;br /&gt;
let event = eventHandler.createRunnableEvent(function () {&lt;br /&gt;
    helper.sendChat(&amp;quot;System&amp;quot;, &amp;quot;This message appears after 3 seconds!&amp;quot;);&lt;br /&gt;
}, 3000);&lt;br /&gt;
&lt;br /&gt;
eventHandler.offer(event);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tips ===&lt;br /&gt;
* Only use classes that exist in the current server (game-server or chat-server).&lt;br /&gt;
* Prefer using available bindings like &amp;lt;code&amp;gt;client&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;state&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;gameManager&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;serviceManager&amp;lt;/code&amp;gt; when possible.&lt;br /&gt;
* Combine everything — services, packets, player APIs — to build your own gameplay features.&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
These examples demonstrate how to respond to server events using the `geb.on(...)` method in JavaScript. Each file is named using the format `id_name.js` (e.g. `1_exampleEvent.js`) and is auto-loaded from the event script folder if placed there.&lt;br /&gt;
&lt;br /&gt;
=== 1_exampleEvent.js ===&lt;br /&gt;
Send a login greeting to a player using a helper class:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;ON_LOGIN&amp;quot;, function(client) {&lt;br /&gt;
    let PlayerScriptableImpl = Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;);&lt;br /&gt;
    let helper = new PlayerScriptableImpl(client);&lt;br /&gt;
    helper.sendChat(&amp;quot;System&amp;quot;, &amp;quot;Welcome back, player!&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 2_exampleEvent2.js ===&lt;br /&gt;
Rewards gold and a message to every player in a finished multiplayer match:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;MP_MATCH_END&amp;quot;, function(game, room, clients) {&lt;br /&gt;
    clients.forEach(function(client) {&lt;br /&gt;
        let PlayerScriptableImpl = Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;);&lt;br /&gt;
        let helper = new PlayerScriptableImpl(client);&lt;br /&gt;
        helper.giveGold(200);&lt;br /&gt;
        helper.sendChat(&amp;quot;Match&amp;quot;, &amp;quot;You earned 200 gold for finishing!&amp;quot;);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 3_weeklyLogin.js ===&lt;br /&gt;
Tracks login streaks using `state` storage (per account):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;ON_LOGIN&amp;quot;, function (client) {&lt;br /&gt;
    const accountId = client.getAccountId();&lt;br /&gt;
    const playerId = client.getActivePlayerId();&lt;br /&gt;
    const today = new Date().toISOString().split(&#039;T&#039;)[0];&lt;br /&gt;
&lt;br /&gt;
    if (!accountId || !playerId) return;&lt;br /&gt;
&lt;br /&gt;
    let loginState;&lt;br /&gt;
    try {&lt;br /&gt;
        loginState = JSON.parse(state.get(&amp;quot;weeklyLogin&amp;quot;, accountId) || &amp;quot;{}&amp;quot;);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        loginState = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    loginState.playerId = loginState.playerId || playerId;&lt;br /&gt;
    loginState.lastLoginDate = loginState.lastLoginDate || null;&lt;br /&gt;
    loginState.loginCount = loginState.loginCount || 0;&lt;br /&gt;
&lt;br /&gt;
    if (loginState.lastLoginDate !== today) {&lt;br /&gt;
        loginState.lastLoginDate = today;&lt;br /&gt;
        loginState.loginCount += 1;&lt;br /&gt;
&lt;br /&gt;
        state.set(&amp;quot;weeklyLogin&amp;quot;, accountId, JSON.stringify(loginState));&lt;br /&gt;
&lt;br /&gt;
        let PlayerScriptableImpl = Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;);&lt;br /&gt;
        let helper = new PlayerScriptableImpl(client);&lt;br /&gt;
        helper.sendChat(&amp;quot;System&amp;quot;, `You&#039;ve logged in ${loginState.loginCount} time(s) this week!`);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4_exampleDelayedEvent.js ===&lt;br /&gt;
Sends a delayed chat message 3 seconds after login:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;ON_LOGIN&amp;quot;, function(client) {&lt;br /&gt;
    let PlayerScriptableImpl = Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    let event = eventHandler.createRunnableEvent(function () {&lt;br /&gt;
        let helper = new PlayerScriptableImpl(client);&lt;br /&gt;
        helper.sendChat(&amp;quot;System&amp;quot;, &amp;quot;This message was delayed by 3 seconds.&amp;quot;);&lt;br /&gt;
    }, 3000);&lt;br /&gt;
&lt;br /&gt;
    eventHandler.offer(event);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Event Types and Registration ==&lt;br /&gt;
&lt;br /&gt;
To register for events, you call:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;EVENT_NAME&amp;quot;, function(...) { ... });&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Valid event names come from the enum:&lt;br /&gt;
&amp;lt;pre&amp;gt;com.jftse.emulator.server.core.life.event.GameEventType&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Examples include:&lt;br /&gt;
* &amp;lt;code&amp;gt;ON_LOGIN&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;LOBBY_JOINED&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;MP_MATCH_END&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Not all enum values are triggered yet. If an event you need doesn’t fire or seems unsupported:&lt;br /&gt;
* Ask in Discord (`#event-creators-club`)&lt;br /&gt;
* Provide the name you need (e.g. &amp;lt;code&amp;gt;MP_PLAYER_HITS_TARGET&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Describe what data you&#039;d want passed (e.g. &amp;lt;code&amp;gt;client&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;game&amp;lt;/code&amp;gt;, etc.)&lt;br /&gt;
&lt;br /&gt;
The dev team can wire up new events if they make sense.&lt;br /&gt;
&lt;br /&gt;
=== Manually Triggering Events from Script ===&lt;br /&gt;
&lt;br /&gt;
You can also call any other registered event manually inside your script using:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.call(&amp;quot;EVENT_NAME&amp;quot;, ...args);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is useful for composing logic across multiple scripts, chaining behaviors, or invoking shared logic.  &lt;br /&gt;
The called event must have been previously registered using &amp;lt;code&amp;gt;geb.on(...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;ON_LOGIN&amp;quot;, function(client) {&lt;br /&gt;
    geb.call(&amp;quot;GREET_PLAYER&amp;quot;, client);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
geb.on(&amp;quot;GREET_PLAYER&amp;quot;, function(client) {&lt;br /&gt;
    let helper = new (Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;))(client);&lt;br /&gt;
    helper.sendChat(&amp;quot;System&amp;quot;, &amp;quot;Welcome!&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips ==&lt;br /&gt;
&lt;br /&gt;
* Use &amp;lt;code&amp;gt;state.get(...)&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;state.set(...)&amp;lt;/code&amp;gt; to persist data per player, account, or globally&lt;br /&gt;
* Validate input: check &amp;lt;code&amp;gt;client&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;playerId&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;room&amp;lt;/code&amp;gt;, etc. before acting&lt;br /&gt;
* Use &amp;lt;code&amp;gt;eventHandler.createRunnableEvent(...)&amp;lt;/code&amp;gt; to delay code execution&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let event = eventHandler.createRunnableEvent(function () {&lt;br /&gt;
    // delayed logic&lt;br /&gt;
}, 3000);&lt;br /&gt;
eventHandler.offer(event);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
* You can access Java classes via &amp;lt;code&amp;gt;Java.type(&amp;quot;...&amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
* Stick to one purpose per script — modularity helps with debugging&lt;br /&gt;
* Explore existing scripts and backend source to learn event arguments&lt;br /&gt;
&lt;br /&gt;
== Need Help? ==&lt;br /&gt;
&lt;br /&gt;
Join us in &#039;&#039;&#039;#event-creators-club&#039;&#039;&#039; on Discord.  &lt;br /&gt;
You can share your scripts, get help, request missing events, or propose new scripting features.&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Scripting]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Event_MP_PLAYER_HITS_TARGET&amp;diff=83</id>
		<title>Event MP PLAYER HITS TARGET</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Event_MP_PLAYER_HITS_TARGET&amp;diff=83"/>
		<updated>2025-07-08T11:08:08Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;MP_PLAYER_HITS_TARGET&#039;&#039;&#039; is one of these events that are called from multiple places with multiple argument count.&lt;br /&gt;
&lt;br /&gt;
Therefore accessing the parameters passed to that event require proper handling to avoid errors.&lt;br /&gt;
&lt;br /&gt;
Currently it is called 3 times in sum:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, ftClient, game, skill); // ftClient might be changed to connection in future&lt;br /&gt;
GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, connection, game, newHealth, skillHitsTarget);&lt;br /&gt;
GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, connection, game, newHealth, skill, skillHitsTarget);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You need to register and handle it the following way:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;MP_PLAYER_HITS_TARGET&amp;quot;, function () {&lt;br /&gt;
    switch (arguments.length) {&lt;br /&gt;
        case 3:&lt;br /&gt;
            handleUniqueSkill(arguments[0], arguments[1], arguments[2]);&lt;br /&gt;
            break;&lt;br /&gt;
        case 4:&lt;br /&gt;
            handleBallLossDamage(arguments[0], arguments[1], arguments[2], arguments[3]);&lt;br /&gt;
            break;&lt;br /&gt;
        case 5:&lt;br /&gt;
            handleSkillDamage(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);&lt;br /&gt;
        default:&lt;br /&gt;
            logUnexpectedArgs(arguments);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;arguments&amp;lt;/code&amp;gt; is a built-in object in JavaScript functions, especially in non-arrow (&amp;lt;code&amp;gt;function (...) {}&amp;lt;/code&amp;gt;) style functions. It is implicitly available inside any function declared with the classic &amp;lt;code&amp;gt;function (...) {}&amp;lt;/code&amp;gt; syntax.&lt;br /&gt;
&lt;br /&gt;
It is:&lt;br /&gt;
* an array-like object&lt;br /&gt;
* holds all the values passed to the function&lt;br /&gt;
* always available in classic functions (not in arrow &amp;lt;code&amp;gt;functions ()=&amp;gt;{}&amp;lt;/code&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
[[Category:Event]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=82</id>
		<title>List of GameEventType Events</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=82"/>
		<updated>2025-07-08T11:06:37Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== List of Events (GameEventType) ==&lt;br /&gt;
&lt;br /&gt;
This page lists the currently available events you can hook into using the &amp;lt;code&amp;gt;geb.on(&amp;quot;EVENT_NAME&amp;quot;, ...)&amp;lt;/code&amp;gt; function from JavaScript.&lt;br /&gt;
&lt;br /&gt;
All events come from the &amp;lt;code&amp;gt;GameEventType&amp;lt;/code&amp;gt; enum in either the &#039;&#039;&#039;game server&#039;&#039;&#039; or &#039;&#039;&#039;chat server&#039;&#039;&#039; codebase.  &lt;br /&gt;
These are used in event scripts placed inside &amp;lt;code&amp;gt;scripts/event/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Game Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Parameters !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || &amp;lt;code&amp;gt;long diff&amp;lt;/code&amp;gt; || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt; || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || || Player logs out&lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_INIT_HP || ❌ || ||  &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_DAMAGE || ❌ || ||  &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_POINT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_FINISH || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| EMBLEM_INIT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ENCHANT_ON_ANNOUNCE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_ENCHANT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| GACHA_OPENED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| RECIPE_COMBINED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| INVENTORY_ITEM_TIME_EXPIRED || ❌ || || Time-based item expired&lt;br /&gt;
|-&lt;br /&gt;
| ON_PLAYER_ANNOUNCE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| PLAYER_QUICK_SLOT_USE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_JOINED || ❌ || || Player entered the lobby&lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_LEFT || ❌ || || Player left the lobby&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LOBBY_JOINED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_CREATED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_JOINED || ❌ || || Player joined a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LEFT || ❌ || || Player left a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_POSITION_CHANGED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_MAP_CHANGED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| MP_RELAY_CONNECTED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_TRIGGERED || ✅ || &amp;lt;code&amp;gt;FTClient, Room, RoomPlayer&amp;lt;/code&amp;gt; || After skipping the match intro, this is called before Player stats are sent to the client&lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_END || ✅ || || Skipping match intro finished and is called right before guardian serve in battle or guardian mode, or serve in basic mode&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_START || ❌ || || Match begins&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_END || ✅ || &amp;lt;code&amp;gt;MatchplayBasicGame / MatchplayBattleGame / MatchplayGuardianGame, Room, ConcurrentLinkedDeque&amp;amp;lt;FTClient&amp;amp;gt;&amp;lt;/code&amp;gt; || Match ends. Game object can be one of these three&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_USE_SKILL || ✅ || &amp;lt;code&amp;gt;FTClient, MatchplayGame, RoomPlayer, Skill, SkillUse, C2SMatchplayUsesSkill&amp;lt;/code&amp;gt; || A skill was used&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_PICKING_UP_CRYSTAL || ✅ || &amp;lt;code&amp;gt;FTClient, SkillCrystal, int randomSkillIndex&amp;lt;/code&amp;gt; || Player crystal pickup&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_HITS_TARGET || ✅ || &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
(1) FTClient, MatchplayGame, Skill&lt;br /&gt;
&lt;br /&gt;
(2) FTConnection, MatchplayGame, short newHealth, C2SMatchplaySkillHitsTarget&lt;br /&gt;
&lt;br /&gt;
(3) FTConnection, MatchplayGame, short newHealth, Skill, C2SMatchplaySkillHitsTarget&lt;br /&gt;
&amp;lt;/code&amp;gt; &lt;br /&gt;
|| Supports multiple overloads. A target is hit during battle or guardian mode. See [[Event_MP_PLAYER_HITS_TARGET|Event MP_PLAYER_HITS_TARGET]]&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_SWAP_QUICK_SLOT_ITEMS || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_MAINTENANCE_REQUESTED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD_PREPARE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_ITEM_BOUGHT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_INIT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_FINISH || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_DEV_PACKET || ❌ || || Developer-only test packet&lt;br /&gt;
|-&lt;br /&gt;
| SESSION_TIME_UPDATE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_HEARTBEAT || ❌ || || &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Chat Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Parameters !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || &amp;lt;code&amp;gt;long diff&amp;lt;/code&amp;gt; || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt; || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || || Player logs out&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Legend ==&lt;br /&gt;
;✅ : Event is currently called (triggered by the server)&lt;br /&gt;
;❌ : Event exists but is not currently triggered&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
In the &amp;quot;Parameters &amp;quot; column, Java &#039;&#039;&#039;types&#039;&#039;&#039; are shown (e.g. &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;MatchplayGame&amp;lt;/code&amp;gt;), not variable names.&amp;lt;br&amp;gt;&lt;br /&gt;
This is to help you identify the object classes you can interact with inside your script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;Don&#039;t see your needed event called yet?&#039;&#039;  &lt;br /&gt;
→ You can still write scripts using them and ask for the event to be wired on Discord.  &lt;br /&gt;
Include the event name and what parameters your handler expects.&lt;br /&gt;
&lt;br /&gt;
== Event Lists ==&lt;br /&gt;
* [[#Game Server Events|Game Server]]&lt;br /&gt;
* [[#Chat Server Events|Chat Server]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Event]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=81</id>
		<title>List of GameEventType Events</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=81"/>
		<updated>2025-07-08T11:03:24Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== List of Events (GameEventType) ==&lt;br /&gt;
&lt;br /&gt;
This page lists the currently available events you can hook into using the &amp;lt;code&amp;gt;geb.on(&amp;quot;EVENT_NAME&amp;quot;, ...)&amp;lt;/code&amp;gt; function from JavaScript.&lt;br /&gt;
&lt;br /&gt;
All events come from the &amp;lt;code&amp;gt;GameEventType&amp;lt;/code&amp;gt; enum in either the &#039;&#039;&#039;game server&#039;&#039;&#039; or &#039;&#039;&#039;chat server&#039;&#039;&#039; codebase.  &lt;br /&gt;
These are used in event scripts placed inside &amp;lt;code&amp;gt;scripts/event/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Game Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Parameters !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || &amp;lt;code&amp;gt;long diff&amp;lt;/code&amp;gt; || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt; || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || || Player logs out&lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_INIT_HP || ❌ || ||  &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_DAMAGE || ❌ || ||  &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_POINT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_FINISH || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| EMBLEM_INIT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ENCHANT_ON_ANNOUNCE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_ENCHANT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| GACHA_OPENED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| RECIPE_COMBINED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| INVENTORY_ITEM_TIME_EXPIRED || ❌ || || Time-based item expired&lt;br /&gt;
|-&lt;br /&gt;
| ON_PLAYER_ANNOUNCE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| PLAYER_QUICK_SLOT_USE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_JOINED || ❌ || || Player entered the lobby&lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_LEFT || ❌ || || Player left the lobby&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LOBBY_JOINED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_CREATED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_JOINED || ❌ || || Player joined a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LEFT || ❌ || || Player left a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_POSITION_CHANGED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_MAP_CHANGED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| MP_RELAY_CONNECTED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_TRIGGERED || ✅ || &amp;lt;code&amp;gt;FTClient, Room, RoomPlayer&amp;lt;/code&amp;gt; || After skipping the match intro, this is called before Player stats are sent to the client&lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_END || ✅ || || Skipping match intro finished and is called right before guardian serve in battle or guardian mode, or serve in basic mode&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_START || ❌ || || Match begins&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_END || ✅ || &amp;lt;code&amp;gt;MatchplayBasicGame / MatchplayBattleGame / MatchplayGuardianGame, Room, ConcurrentLinkedDeque&amp;amp;lt;FTClient&amp;amp;gt;&amp;lt;/code&amp;gt; || Match ends. Game object can be one of these three&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_USE_SKILL || ✅ || &amp;lt;code&amp;gt;FTClient, MatchplayGame, RoomPlayer, Skill, SkillUse, C2SMatchplayUsesSkill&amp;lt;/code&amp;gt; || A skill was used&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_PICKING_UP_CRYSTAL || ✅ || &amp;lt;code&amp;gt;FTClient, SkillCrystal, int randomSkillIndex&amp;lt;/code&amp;gt; || Player crystal pickup&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_HITS_TARGET || ✅ || &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
(1) FTClient, MatchplayGame, Skill&lt;br /&gt;
&lt;br /&gt;
(2) FTConnection, MatchplayGame, short newHealth, C2SMatchplaySkillHitsTarget&lt;br /&gt;
&lt;br /&gt;
(3) FTConnection, MatchplayGame, short newHealth, Skill, C2SMatchplaySkillHitsTarget&lt;br /&gt;
&amp;lt;/code&amp;gt; &lt;br /&gt;
|| Supports multiple overloads. A target is hit during battle or guardian mode&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_SWAP_QUICK_SLOT_ITEMS || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_MAINTENANCE_REQUESTED || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD_PREPARE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_ITEM_BOUGHT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_INIT || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_FINISH || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_DEV_PACKET || ❌ || || Developer-only test packet&lt;br /&gt;
|-&lt;br /&gt;
| SESSION_TIME_UPDATE || ❌ || || &lt;br /&gt;
|-&lt;br /&gt;
| ON_HEARTBEAT || ❌ || || &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Chat Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Parameters !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || &amp;lt;code&amp;gt;long diff&amp;lt;/code&amp;gt; || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt; || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || || Player logs out&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Legend ==&lt;br /&gt;
;✅ : Event is currently called (triggered by the server)&lt;br /&gt;
;❌ : Event exists but is not currently triggered&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
In the &amp;quot;Parameters &amp;quot; column, Java &#039;&#039;&#039;types&#039;&#039;&#039; are shown (e.g. &amp;lt;code&amp;gt;FTClient&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;MatchplayGame&amp;lt;/code&amp;gt;), not variable names.&amp;lt;br&amp;gt;&lt;br /&gt;
This is to help you identify the object classes you can interact with inside your script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;Don&#039;t see your needed event called yet?&#039;&#039;  &lt;br /&gt;
→ You can still write scripts using them and ask for the event to be wired on Discord.  &lt;br /&gt;
Include the event name and what parameters your handler expects.&lt;br /&gt;
&lt;br /&gt;
== Event Lists ==&lt;br /&gt;
* [[#Game Server Events|Game Server]]&lt;br /&gt;
* [[#Chat Server Events|Chat Server]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Event]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=80</id>
		<title>List of GameEventType Events</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=80"/>
		<updated>2025-07-08T10:34:30Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== List of Events (GameEventType) ==&lt;br /&gt;
&lt;br /&gt;
This page lists the currently available events you can hook into using the &amp;lt;code&amp;gt;geb.on(&amp;quot;EVENT_NAME&amp;quot;, ...)&amp;lt;/code&amp;gt; function from JavaScript.&lt;br /&gt;
&lt;br /&gt;
All events come from the &amp;lt;code&amp;gt;GameEventType&amp;lt;/code&amp;gt; enum in either the &#039;&#039;&#039;game server&#039;&#039;&#039; or &#039;&#039;&#039;chat server&#039;&#039;&#039; codebase.  &lt;br /&gt;
These are used in event scripts placed inside &amp;lt;code&amp;gt;scripts/event/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Game Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || Player logs out&lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_INIT_HP || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_DAMAGE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_POINT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_FINISH || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| EMBLEM_INIT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ENCHANT_ON_ANNOUNCE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_ENCHANT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| GACHA_OPENED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| RECIPE_COMBINED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| INVENTORY_ITEM_TIME_EXPIRED || ❌ || Time-based item expired&lt;br /&gt;
|-&lt;br /&gt;
| ON_PLAYER_ANNOUNCE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| PLAYER_QUICK_SLOT_USE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_JOINED || ❌ || Player entered the lobby&lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_LEFT || ❌ || Player left the lobby&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LOBBY_JOINED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_CREATED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_JOINED || ❌ || Player joined a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LEFT || ❌ || Player left a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_POSITION_CHANGED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_MAP_CHANGED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| MP_RELAY_CONNECTED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_TRIGGERED || ✅ || After skipping the match intro, this is called before Player stats are sent to the client&lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_END || ✅ || Skipping match intro finished and is called right before guardian serve in battle or guardian mode, or serve in basic mode&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_START || ❌ || Match begins&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_END || ✅ || Match ends&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_USE_SKILL || ✅ || A skill was used&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_PICKING_UP_CRYSTAL || ✅ || Player crystal pickup&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_HITS_TARGET || ✅ || A target is hit during battle or guardian mode&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_SWAP_QUICK_SLOT_ITEMS || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_MAINTENANCE_REQUESTED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD_PREPARE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_ITEM_BOUGHT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_INIT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_FINISH || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_DEV_PACKET || ❌ || Developer-only test packet&lt;br /&gt;
|-&lt;br /&gt;
| SESSION_TIME_UPDATE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_HEARTBEAT || ❌ || &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Chat Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || Player logs out&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Legend ==&lt;br /&gt;
;✅ : Event is currently called (triggered by the server)&lt;br /&gt;
;❌ : Event exists but is not currently triggered&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Don&#039;t see your needed event called yet?&#039;&#039;  &lt;br /&gt;
→ You can still write scripts using them and ask for the event to be wired on Discord.  &lt;br /&gt;
Include the event name and what parameters your handler expects.&lt;br /&gt;
&lt;br /&gt;
== Event Lists ==&lt;br /&gt;
* [[#Game Server Events|Game Server]]&lt;br /&gt;
* [[#Chat Server Events|Chat Server]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Event]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=79</id>
		<title>List of GameEventType Events</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=79"/>
		<updated>2025-07-08T10:31:24Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== List of Events (GameEventType) ==&lt;br /&gt;
&lt;br /&gt;
This page lists the currently available events you can hook into using the &amp;lt;code&amp;gt;geb.on(&amp;quot;EVENT_NAME&amp;quot;, ...)&amp;lt;/code&amp;gt; function from JavaScript.&lt;br /&gt;
&lt;br /&gt;
All events come from the &amp;lt;code&amp;gt;GameEventType&amp;lt;/code&amp;gt; enum in either the &#039;&#039;&#039;game server&#039;&#039;&#039; or &#039;&#039;&#039;chat server&#039;&#039;&#039; codebase.  &lt;br /&gt;
These are used in event scripts placed inside &amp;lt;code&amp;gt;scripts/event/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Game Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || Player logs out&lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_INIT_HP || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_DAMAGE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_POINT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_FINISH || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| EMBLEM_INIT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ENCHANT_ON_ANNOUNCE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_ENCHANT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| GACHA_OPENED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| RECIPE_COMBINED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| INVENTORY_ITEM_TIME_EXPIRED || ❌ || Time-based item expired&lt;br /&gt;
|-&lt;br /&gt;
| ON_PLAYER_ANNOUNCE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| PLAYER_QUICK_SLOT_USE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_JOINED || ❌ || Player entered the lobby&lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_LEFT || ❌ || Player left the lobby&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LOBBY_JOINED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_CREATED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_JOINED || ❌ || Player joined a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LEFT || ❌ || Player left a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_POSITION_CHANGED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_MAP_CHANGED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| MP_RELAY_CONNECTED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_TRIGGERED || ✅ || After skipping the match intro, this is called before Player stats are sent to the client&lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_END || ✅ || Skipping match intro finished and is called right before guardian serve in battle or guardian mode, or serve in basic mode&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_START || ❌ || Match begins&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_END || ✅ || Match ends&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_USE_SKILL || ✅ || A skill was used&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_PICKING_UP_CRYSTAL || ✅ || Player crystal pickup&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_HITS_TARGET || ✅ || A target is hit during battle or guardian mode&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_SWAP_QUICK_SLOT_ITEMS || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_MAINTENANCE_REQUESTED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD_PREPARE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_ITEM_BOUGHT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_INIT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_FINISH || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_DEV_PACKET || ❌ || Developer-only test packet&lt;br /&gt;
|-&lt;br /&gt;
| SESSION_TIME_UPDATE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_HEARTBEAT || ❌ || &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Chat Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ❌ || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || Player logs out&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Legend ==&lt;br /&gt;
;✅ : Event is currently called (triggered by the server)&lt;br /&gt;
;❌ : Event exists but is not currently triggered&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Don&#039;t see your needed event called yet?&#039;&#039;  &lt;br /&gt;
→ You can still write scripts using them and ask for the event to be wired on Discord.  &lt;br /&gt;
Include the event name and what parameters your handler expects.&lt;br /&gt;
&lt;br /&gt;
== Event Lists ==&lt;br /&gt;
* [[#Game Server Events|Game Server]]&lt;br /&gt;
* [[#Chat Server Events|Chat Server]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Event]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Category:Event&amp;diff=78</id>
		<title>Category:Event</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Category:Event&amp;diff=78"/>
		<updated>2025-07-08T10:26:38Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;Category:Scripting&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Scripting]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Category:Scripting&amp;diff=77</id>
		<title>Category:Scripting</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Category:Scripting&amp;diff=77"/>
		<updated>2025-07-08T10:26:22Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;Category:Server&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Server]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=76</id>
		<title>List of GameEventType Events</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=List_of_GameEventType_Events&amp;diff=76"/>
		<updated>2025-07-08T10:25:51Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;== List of Events (GameEventType) ==  This page lists the currently available events you can hook into using the &amp;lt;code&amp;gt;geb.on(&amp;quot;EVENT_NAME&amp;quot;, ...)&amp;lt;/code&amp;gt; function from JavaScript.  All events come from the &amp;lt;code&amp;gt;GameEventType&amp;lt;/code&amp;gt; enum in either the &amp;#039;&amp;#039;&amp;#039;game server&amp;#039;&amp;#039;&amp;#039; or &amp;#039;&amp;#039;&amp;#039;chat server&amp;#039;&amp;#039;&amp;#039; codebase.   These are used in event scripts placed inside &amp;lt;code&amp;gt;scripts/event/&amp;lt;/code&amp;gt;.  == Game Server Events ==  {| class=&amp;quot;wikitable sortable&amp;quot; ! Event Name !! Called? !! Description |-...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== List of Events (GameEventType) ==&lt;br /&gt;
&lt;br /&gt;
This page lists the currently available events you can hook into using the &amp;lt;code&amp;gt;geb.on(&amp;quot;EVENT_NAME&amp;quot;, ...)&amp;lt;/code&amp;gt; function from JavaScript.&lt;br /&gt;
&lt;br /&gt;
All events come from the &amp;lt;code&amp;gt;GameEventType&amp;lt;/code&amp;gt; enum in either the &#039;&#039;&#039;game server&#039;&#039;&#039; or &#039;&#039;&#039;chat server&#039;&#039;&#039; codebase.  &lt;br /&gt;
These are used in event scripts placed inside &amp;lt;code&amp;gt;scripts/event/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Game Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ✅ || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || Player logs out&lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_INIT_HP || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_DAMAGE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_POINT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| CHALLENGE_FINISH || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| EMBLEM_INIT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ENCHANT_ON_ANNOUNCE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_ENCHANT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| GACHA_OPENED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| RECIPE_COMBINED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| INVENTORY_ITEM_TIME_EXPIRED || ❌ || Time-based item expired&lt;br /&gt;
|-&lt;br /&gt;
| ON_PLAYER_ANNOUNCE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| PLAYER_QUICK_SLOT_USE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_JOINED || ❌ || Player entered the lobby&lt;br /&gt;
|-&lt;br /&gt;
| LOBBY_LEFT || ❌ || Player left the lobby&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LOBBY_JOINED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_CREATED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_JOINED || ❌ || Player joined a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_LEFT || ❌ || Player left a room&lt;br /&gt;
|-&lt;br /&gt;
| ROOM_POSITION_CHANGED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ROOM_MAP_CHANGED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| MP_RELAY_CONNECTED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_TRIGGERED || ✅ || After skipping the match intro, this is called before Player stats are sent to the client&lt;br /&gt;
|-&lt;br /&gt;
| MP_GAME_ANIM_SKIP_END || ✅ || Skipping match intro finished and is called right before guardian serve in battle or guardian mode, or serve in basic mode&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_START || ❌ || Match begins&lt;br /&gt;
|-&lt;br /&gt;
| MP_MATCH_END || ✅ || Match ends&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_USE_SKILL || ✅ || A skill was used&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_PICKING_UP_CRYSTAL || ✅ || Player crystal pickup&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_HITS_TARGET || ✅ || A target is hit during battle or guardian mode&lt;br /&gt;
|-&lt;br /&gt;
| MP_PLAYER_SWAP_QUICK_SLOT_ITEMS || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_MAINTENANCE_REQUESTED || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_SHOP_LOAD_PREPARE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| SHOP_ITEM_BOUGHT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_INIT || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| TUTORIAL_FINISH || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_DEV_PACKET || ❌ || Developer-only test packet&lt;br /&gt;
|-&lt;br /&gt;
| SESSION_TIME_UPDATE || ❌ || &lt;br /&gt;
|-&lt;br /&gt;
| ON_HEARTBEAT || ❌ || &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Chat Server Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Event Name !! Called? !! Description&lt;br /&gt;
|-&lt;br /&gt;
| ON_TICK || ✅ || Called on every server tick&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGIN || ❌ || Player logs in&lt;br /&gt;
|-&lt;br /&gt;
| ON_LOGOUT || ❌ || Player logs out&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Legend ===&lt;br /&gt;
;✅ : Event is currently called (triggered by the server)&lt;br /&gt;
;❌ : Event exists but is not currently triggered&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Don&#039;t see your needed event called yet?&#039;&#039;  &lt;br /&gt;
→ You can still write scripts using them and ask for the event to be wired on Discord.  &lt;br /&gt;
Include the event name and what parameters your handler expects.&lt;br /&gt;
&lt;br /&gt;
== Event Lists ==&lt;br /&gt;
* [[#Game Server Events|Game Server]]&lt;br /&gt;
* [[#Chat Server Events|Chat Server]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Event]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Event_MP_PLAYER_HITS_TARGET&amp;diff=75</id>
		<title>Event MP PLAYER HITS TARGET</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Event_MP_PLAYER_HITS_TARGET&amp;diff=75"/>
		<updated>2025-07-08T09:45:37Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;MP_PLAYER_HITS_TARGET&#039;&#039;&#039; is one of these events that are called from multiple places with multiple argument count.&lt;br /&gt;
&lt;br /&gt;
Therefore accessing the parameters passed to that event require proper handling to avoid errors.&lt;br /&gt;
&lt;br /&gt;
Currently it is called 3 times in sum:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, ftClient, game, skill); // ftClient might be changed to connection in future&lt;br /&gt;
GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, connection, game, newHealth, skillHitsTarget);&lt;br /&gt;
GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, connection, game, newHealth, skill, skillHitsTarget);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You need to register and handle it the following way:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;MP_PLAYER_HITS_TARGET&amp;quot;, function () {&lt;br /&gt;
    switch (arguments.length) {&lt;br /&gt;
        case 3:&lt;br /&gt;
            handleUniqueSkill(arguments[0], arguments[1], arguments[2]);&lt;br /&gt;
            break;&lt;br /&gt;
        case 4:&lt;br /&gt;
            handleBallLossDamage(arguments[0], arguments[1], arguments[2], arguments[3]);&lt;br /&gt;
            break;&lt;br /&gt;
        case 5:&lt;br /&gt;
            handleSkillDamage(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);&lt;br /&gt;
        default:&lt;br /&gt;
            logUnexpectedArgs(arguments);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;arguments&amp;lt;/code&amp;gt; is a built-in object in JavaScript functions, especially in non-arrow (&amp;lt;code&amp;gt;function (...) {}&amp;lt;/code&amp;gt;) style functions. It is implicitly available inside any function declared with the classic &amp;lt;code&amp;gt;function (...) {}&amp;lt;/code&amp;gt; syntax.&lt;br /&gt;
&lt;br /&gt;
It is:&lt;br /&gt;
* an array-like object&lt;br /&gt;
* holds all the values passed to the function&lt;br /&gt;
* always available in classic functions (not in arrow &amp;lt;code&amp;gt;functions ()=&amp;gt;{}&amp;lt;/code&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;br /&gt;
[[Category:Scripting]]&lt;br /&gt;
[[Category:Event]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Event_MP_PLAYER_HITS_TARGET&amp;diff=74</id>
		<title>Event MP PLAYER HITS TARGET</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Event_MP_PLAYER_HITS_TARGET&amp;diff=74"/>
		<updated>2025-07-08T09:45:07Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;MP_PLAYER_HITS_TARGET&#039;&#039;&#039; is one of these events that are called from multiple places with multiple argument count.&lt;br /&gt;
&lt;br /&gt;
Therefore accessing the parameters passed to that event require proper handling to avoid errors.&lt;br /&gt;
&lt;br /&gt;
Currently it is called 3 times in sum:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, ftClient, game, skill); // ftClient might be changed to connection in future&lt;br /&gt;
GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, connection, game, newHealth, skillHitsTarget);&lt;br /&gt;
GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, connection, game, newHealth, skill, skillHitsTarget);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You need to register and handle it the following way:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;MP_PLAYER_HITS_TARGET&amp;quot;, function () {&lt;br /&gt;
    switch (arguments.length) {&lt;br /&gt;
        case 3:&lt;br /&gt;
            handleUniqueSkill(arguments[0], arguments[1], arguments[2]);&lt;br /&gt;
            break;&lt;br /&gt;
        case 4:&lt;br /&gt;
            handleBallLossDamage(arguments[0], arguments[1], arguments[2], arguments[3]);&lt;br /&gt;
            break;&lt;br /&gt;
        case 5:&lt;br /&gt;
            handleSkillDamage(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);&lt;br /&gt;
        default:&lt;br /&gt;
            logUnexpectedArgs(arguments);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;arguments&amp;lt;/code&amp;gt; is a built-in object in JavaScript functions, especially in non-arrow (&amp;lt;code&amp;gt;function (...) {}&amp;lt;/code&amp;gt;) style functions. It is implicitly available inside any function declared with the classic &amp;lt;code&amp;gt;function (...) {}&amp;lt;/code&amp;gt; syntax.&lt;br /&gt;
&lt;br /&gt;
It is:&lt;br /&gt;
* an array-like object&lt;br /&gt;
* holds all the values passed to the function&lt;br /&gt;
* always available in classic functions (not in arrow &amp;lt;code&amp;gt;functions ()=&amp;gt;{}&amp;lt;/code&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;br /&gt;
[[Category:Scripting]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Event_MP_PLAYER_HITS_TARGET&amp;diff=73</id>
		<title>Event MP PLAYER HITS TARGET</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Event_MP_PLAYER_HITS_TARGET&amp;diff=73"/>
		<updated>2025-07-08T09:44:43Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;= Event MP_PLAYER_HITS_TARGET =  &amp;#039;&amp;#039;&amp;#039;MP_PLAYER_HITS_TARGET&amp;#039;&amp;#039;&amp;#039; is one of these events that are called from multiple places with multiple argument count.  Therefore accessing the parameters passed to that event require proper handling to avoid errors.  Currently it is called 3 times in sum: &amp;lt;pre&amp;gt; GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, ftClient, game, skill); // ftClient might be changed to connection in future GameEventBus.call(GameEventType.MP_PLAYER_HITS_T...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Event MP_PLAYER_HITS_TARGET =&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;MP_PLAYER_HITS_TARGET&#039;&#039;&#039; is one of these events that are called from multiple places with multiple argument count.&lt;br /&gt;
&lt;br /&gt;
Therefore accessing the parameters passed to that event require proper handling to avoid errors.&lt;br /&gt;
&lt;br /&gt;
Currently it is called 3 times in sum:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, ftClient, game, skill); // ftClient might be changed to connection in future&lt;br /&gt;
GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, connection, game, newHealth, skillHitsTarget);&lt;br /&gt;
GameEventBus.call(GameEventType.MP_PLAYER_HITS_TARGET, connection, game, newHealth, skill, skillHitsTarget);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You need to register and handle it the following way:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;MP_PLAYER_HITS_TARGET&amp;quot;, function () {&lt;br /&gt;
    switch (arguments.length) {&lt;br /&gt;
        case 3:&lt;br /&gt;
            handleUniqueSkill(arguments[0], arguments[1], arguments[2]);&lt;br /&gt;
            break;&lt;br /&gt;
        case 4:&lt;br /&gt;
            handleBallLossDamage(arguments[0], arguments[1], arguments[2], arguments[3]);&lt;br /&gt;
            break;&lt;br /&gt;
        case 5:&lt;br /&gt;
            handleSkillDamage(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);&lt;br /&gt;
        default:&lt;br /&gt;
            logUnexpectedArgs(arguments);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;arguments&amp;lt;/code&amp;gt; is a built-in object in JavaScript functions, especially in non-arrow (&amp;lt;code&amp;gt;function (...) {}&amp;lt;/code&amp;gt;) style functions. It is implicitly available inside any function declared with the classic &amp;lt;code&amp;gt;function (...) {}&amp;lt;/code&amp;gt; syntax.&lt;br /&gt;
&lt;br /&gt;
It is:&lt;br /&gt;
* an array-like object&lt;br /&gt;
* holds all the values passed to the function&lt;br /&gt;
* always available in classic functions (not in arrow &amp;lt;code&amp;gt;functions ()=&amp;gt;{}&amp;lt;/code&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;br /&gt;
[[Category:Scripting]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Creating_custom_events&amp;diff=72</id>
		<title>Creating custom events</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Creating_custom_events&amp;diff=72"/>
		<updated>2025-07-07T11:32:41Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;= Scripted Event System =  This page documents how to implement custom in-game event logic using the internal JavaScript scripting system.  All scripts are located in: &amp;lt;pre&amp;gt;game-server/src/main/resources/scripts/event&amp;lt;/pre&amp;gt;  Files are named as: &amp;lt;pre&amp;gt;&amp;amp;lt;ID&amp;amp;gt;_&amp;amp;lt;name&amp;amp;gt;.js&amp;lt;/pre&amp;gt; Example: * &amp;lt;code&amp;gt;1_exampleEvent.js&amp;lt;/code&amp;gt; * &amp;lt;code&amp;gt;2_exampleEvent2.js&amp;lt;/code&amp;gt; * &amp;lt;code&amp;gt;3_weeklyLogin.js&amp;lt;/code&amp;gt;  == Registering to an Event ==  Scripts are registered to server events via: &amp;lt;pre&amp;gt; g...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Scripted Event System =&lt;br /&gt;
&lt;br /&gt;
This page documents how to implement custom in-game event logic using the internal JavaScript scripting system.&lt;br /&gt;
&lt;br /&gt;
All scripts are located in:&lt;br /&gt;
&amp;lt;pre&amp;gt;game-server/src/main/resources/scripts/event&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Files are named as:&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;amp;lt;ID&amp;amp;gt;_&amp;amp;lt;name&amp;amp;gt;.js&amp;lt;/pre&amp;gt;&lt;br /&gt;
Example:&lt;br /&gt;
* &amp;lt;code&amp;gt;1_exampleEvent.js&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;2_exampleEvent2.js&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;3_weeklyLogin.js&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Registering to an Event ==&lt;br /&gt;
&lt;br /&gt;
Scripts are registered to server events via:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;ON_LOGIN&amp;quot;, function(client) {&lt;br /&gt;
    // Runs on player login&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Another example for match end:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;MP_MATCH_END&amp;quot;, function(game, room, clients) {&lt;br /&gt;
    // Runs when match ends&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The string refers to a constant from the &amp;lt;code&amp;gt;GameEventType&amp;lt;/code&amp;gt; enum:&lt;br /&gt;
&amp;lt;pre&amp;gt;com.jftse.emulator.server.core.life.event.GameEventType&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Script Environment ==&lt;br /&gt;
&lt;br /&gt;
The following bindings are available in every script:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gameManager&lt;br /&gt;
serviceManager&lt;br /&gt;
threadManager&lt;br /&gt;
eventHandler&lt;br /&gt;
state&lt;br /&gt;
geb&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These expose access to game services, event queues, persistent state, and event registration.&lt;br /&gt;
&lt;br /&gt;
== Using Persistent State ==&lt;br /&gt;
&lt;br /&gt;
To save player-specific or global data across restarts:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let accountId = client.getAccountId();&lt;br /&gt;
let loginData = JSON.parse(state.get(&amp;quot;loginTracker&amp;quot;, accountId) || &amp;quot;{}&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
loginData.lastLogin = new Date().toISOString();&lt;br /&gt;
&lt;br /&gt;
state.set(&amp;quot;loginTracker&amp;quot;, accountId, JSON.stringify(loginData));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This allows tracking login streaks, cooldowns, event progress, and more.&lt;br /&gt;
&lt;br /&gt;
== Java Class Access ==&lt;br /&gt;
&lt;br /&gt;
You can use &amp;lt;code&amp;gt;Java.type(...)&amp;lt;/code&amp;gt; to access and work with server-side Java classes directly from your script.  &lt;br /&gt;
This allows you to integrate deeply with server logic, access player data, send packets, schedule tasks, and more.&lt;br /&gt;
&lt;br /&gt;
=== Example: Give gold and send a chat message ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let PlayerScriptableImpl = Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
let helper = new PlayerScriptableImpl(client);&lt;br /&gt;
helper.giveGold(500);&lt;br /&gt;
helper.sendChat(&amp;quot;System&amp;quot;, &amp;quot;You received 500 gold!&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Example: Send a room chat packet manually ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let S2CChatRoomAnswerPacket = Java.type(&amp;quot;com.jftse.emulator.server.core.packets.chat.S2CChatRoomAnswerPacket&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
let packet = new S2CChatRoomAnswerPacket(2, &amp;quot;Server&amp;quot;, &amp;quot;Welcome!&amp;quot;);&lt;br /&gt;
client.getConnection().sendTCP(packet);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Example: Access services via ServiceManager ===&lt;br /&gt;
You can access any backend service via the pre-bound &amp;lt;code&amp;gt;serviceManager&amp;lt;/code&amp;gt; instance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let playerService = serviceManager.getPlayerService();&lt;br /&gt;
let player = playerService.findById(playerId);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Example: Schedule delayed task with EventHandler ===&lt;br /&gt;
Use &amp;lt;code&amp;gt;eventHandler.createRunnableEvent&amp;lt;/code&amp;gt; to run code after a delay (milliseconds):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let PlayerScriptableImpl = Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;);&lt;br /&gt;
let helper = new PlayerScriptableImpl(client);&lt;br /&gt;
&lt;br /&gt;
let event = eventHandler.createRunnableEvent(function () {&lt;br /&gt;
    helper.sendChat(&amp;quot;System&amp;quot;, &amp;quot;This message appears after 3 seconds!&amp;quot;);&lt;br /&gt;
}, 3000);&lt;br /&gt;
&lt;br /&gt;
eventHandler.offer(event);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tips ===&lt;br /&gt;
* Only use classes that exist in the current server (game-server or chat-server).&lt;br /&gt;
* Prefer using available bindings like &amp;lt;code&amp;gt;client&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;state&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;gameManager&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;serviceManager&amp;lt;/code&amp;gt; when possible.&lt;br /&gt;
* Combine everything — services, packets, player APIs — to build your own gameplay features.&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
These examples demonstrate how to respond to server events using the `geb.on(...)` method in JavaScript. Each file is named using the format `id_name.js` (e.g. `1_exampleEvent.js`) and is auto-loaded from the event script folder if placed there.&lt;br /&gt;
&lt;br /&gt;
=== 1_exampleEvent.js ===&lt;br /&gt;
Send a login greeting to a player using a helper class:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;ON_LOGIN&amp;quot;, function(client) {&lt;br /&gt;
    let PlayerScriptableImpl = Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;);&lt;br /&gt;
    let helper = new PlayerScriptableImpl(client);&lt;br /&gt;
    helper.sendChat(&amp;quot;System&amp;quot;, &amp;quot;Welcome back, player!&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 2_exampleEvent2.js ===&lt;br /&gt;
Rewards gold and a message to every player in a finished multiplayer match:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;MP_MATCH_END&amp;quot;, function(game, room, clients) {&lt;br /&gt;
    clients.forEach(function(client) {&lt;br /&gt;
        let PlayerScriptableImpl = Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;);&lt;br /&gt;
        let helper = new PlayerScriptableImpl(client);&lt;br /&gt;
        helper.giveGold(200);&lt;br /&gt;
        helper.sendChat(&amp;quot;Match&amp;quot;, &amp;quot;You earned 200 gold for finishing!&amp;quot;);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 3_weeklyLogin.js ===&lt;br /&gt;
Tracks login streaks using `state` storage (per account):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;ON_LOGIN&amp;quot;, function (client) {&lt;br /&gt;
    const accountId = client.getAccountId();&lt;br /&gt;
    const playerId = client.getActivePlayerId();&lt;br /&gt;
    const today = new Date().toISOString().split(&#039;T&#039;)[0];&lt;br /&gt;
&lt;br /&gt;
    if (!accountId || !playerId) return;&lt;br /&gt;
&lt;br /&gt;
    let loginState;&lt;br /&gt;
    try {&lt;br /&gt;
        loginState = JSON.parse(state.get(&amp;quot;weeklyLogin&amp;quot;, accountId) || &amp;quot;{}&amp;quot;);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        loginState = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    loginState.playerId = loginState.playerId || playerId;&lt;br /&gt;
    loginState.lastLoginDate = loginState.lastLoginDate || null;&lt;br /&gt;
    loginState.loginCount = loginState.loginCount || 0;&lt;br /&gt;
&lt;br /&gt;
    if (loginState.lastLoginDate !== today) {&lt;br /&gt;
        loginState.lastLoginDate = today;&lt;br /&gt;
        loginState.loginCount += 1;&lt;br /&gt;
&lt;br /&gt;
        state.set(&amp;quot;weeklyLogin&amp;quot;, accountId, JSON.stringify(loginState));&lt;br /&gt;
&lt;br /&gt;
        let PlayerScriptableImpl = Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;);&lt;br /&gt;
        let helper = new PlayerScriptableImpl(client);&lt;br /&gt;
        helper.sendChat(&amp;quot;System&amp;quot;, `You&#039;ve logged in ${loginState.loginCount} time(s) this week!`);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4_exampleDelayedEvent.js ===&lt;br /&gt;
Sends a delayed chat message 3 seconds after login:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;ON_LOGIN&amp;quot;, function(client) {&lt;br /&gt;
    let PlayerScriptableImpl = Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    let event = eventHandler.createRunnableEvent(function () {&lt;br /&gt;
        let helper = new PlayerScriptableImpl(client);&lt;br /&gt;
        helper.sendChat(&amp;quot;System&amp;quot;, &amp;quot;This message was delayed by 3 seconds.&amp;quot;);&lt;br /&gt;
    }, 3000);&lt;br /&gt;
&lt;br /&gt;
    eventHandler.offer(event);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Event Types and Registration ==&lt;br /&gt;
&lt;br /&gt;
To register for events, you call:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;EVENT_NAME&amp;quot;, function(...) { ... });&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Valid event names come from the enum:&lt;br /&gt;
&amp;lt;pre&amp;gt;com.jftse.emulator.server.core.life.event.GameEventType&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Examples include:&lt;br /&gt;
* &amp;lt;code&amp;gt;ON_LOGIN&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;LOBBY_JOINED&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;MP_MATCH_END&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Not all enum values are triggered yet. If an event you need doesn’t fire or seems unsupported:&lt;br /&gt;
* Ask in Discord (`#event-creators-club`)&lt;br /&gt;
* Provide the name you need (e.g. &amp;lt;code&amp;gt;MP_PLAYER_HITS_TARGET&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Describe what data you&#039;d want passed (e.g. &amp;lt;code&amp;gt;client&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;game&amp;lt;/code&amp;gt;, etc.)&lt;br /&gt;
&lt;br /&gt;
The dev team can wire up new events if they make sense.&lt;br /&gt;
&lt;br /&gt;
=== Manually Triggering Events from Script ===&lt;br /&gt;
&lt;br /&gt;
You can also call any other registered event manually inside your script using:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.call(&amp;quot;EVENT_NAME&amp;quot;, ...args);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is useful for composing logic across multiple scripts, chaining behaviors, or invoking shared logic.  &lt;br /&gt;
The called event must have been previously registered using &amp;lt;code&amp;gt;geb.on(...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
geb.on(&amp;quot;ON_LOGIN&amp;quot;, function(client) {&lt;br /&gt;
    geb.call(&amp;quot;GREET_PLAYER&amp;quot;, client);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
geb.on(&amp;quot;GREET_PLAYER&amp;quot;, function(client) {&lt;br /&gt;
    let helper = new (Java.type(&amp;quot;com.jftse.emulator.server.core.interaction.PlayerScriptableImpl&amp;quot;))(client);&lt;br /&gt;
    helper.sendChat(&amp;quot;System&amp;quot;, &amp;quot;Welcome!&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips ==&lt;br /&gt;
&lt;br /&gt;
* Use &amp;lt;code&amp;gt;state.get(...)&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;state.set(...)&amp;lt;/code&amp;gt; to persist data per player, account, or globally&lt;br /&gt;
* Validate input: check &amp;lt;code&amp;gt;client&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;playerId&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;room&amp;lt;/code&amp;gt;, etc. before acting&lt;br /&gt;
* Use &amp;lt;code&amp;gt;eventHandler.createRunnableEvent(...)&amp;lt;/code&amp;gt; to delay code execution&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let event = eventHandler.createRunnableEvent(function () {&lt;br /&gt;
    // delayed logic&lt;br /&gt;
}, 3000);&lt;br /&gt;
eventHandler.offer(event);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
* You can access Java classes via &amp;lt;code&amp;gt;Java.type(&amp;quot;...&amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
* Stick to one purpose per script — modularity helps with debugging&lt;br /&gt;
* Explore existing scripts and backend source to learn event arguments&lt;br /&gt;
&lt;br /&gt;
== Need Help? ==&lt;br /&gt;
&lt;br /&gt;
Join us in &#039;&#039;&#039;#event-creators-club&#039;&#039;&#039; on Discord.  &lt;br /&gt;
You can share your scripts, get help, request missing events, or propose new scripting features.&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Server]]&lt;br /&gt;
[[Category:Scripting]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Fixing_SQL_Import_Script_Errors&amp;diff=71</id>
		<title>Fixing SQL Import Script Errors</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Fixing_SQL_Import_Script_Errors&amp;diff=71"/>
		<updated>2025-02-26T11:41:34Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;= Fixing SQL Import Script Errors =  == Overview ==  When running the `import_sql.sh` script to import SQL files into the database, you might encounter errors related to missing `Guardian` and `BossGuardian` entries.    This happens because certain event maps reference Guardian entries that do not exist in the default SQL files provided by [https://github.com/sstokic-tgm/JFTSE/tree/master/scripts/sql JFTSE]. These missing definitions are a result of JFTSE-specific config...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Fixing SQL Import Script Errors =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
When running the `import_sql.sh` script to import SQL files into the database, you might encounter errors related to missing `Guardian` and `BossGuardian` entries.  &lt;br /&gt;
&lt;br /&gt;
This happens because certain event maps reference Guardian entries that do not exist in the default SQL files provided by [https://github.com/sstokic-tgm/JFTSE/tree/master/scripts/sql JFTSE]. These missing definitions are a result of JFTSE-specific configurations that differ from the original Fantasy Tennis setup.&lt;br /&gt;
&lt;br /&gt;
== How to Fix ==&lt;br /&gt;
&lt;br /&gt;
There are two ways to resolve this issue:&lt;br /&gt;
&lt;br /&gt;
=== 1. Modifying SQL Files Before Import ===&lt;br /&gt;
&lt;br /&gt;
If the import fails due to missing Guardian entries, you can manually edit the affected SQL files. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Steps:&#039;&#039;&#039;&lt;br /&gt;
# Open the SQL file that failed during the import.&lt;br /&gt;
# Locate and remove any `INSERT` statements that reference missing Guardian or BossGuardian entries.&lt;br /&gt;
# Save the file and delete any partial entries from the database that were created before the failure.&lt;br /&gt;
# Re-run the `import_sql.sh` script to import the cleaned SQL files.&lt;br /&gt;
&lt;br /&gt;
This approach prevents invalid data from being inserted and ensures the script runs smoothly.&lt;br /&gt;
&lt;br /&gt;
=== 2. Adding Missing Entries Manually ===&lt;br /&gt;
&lt;br /&gt;
If you prefer to retain all Guardian entries referenced in the event maps, you can manually add the missing records to the database. Below are the required `INSERT` statements for Guardian and BossGuardian entries:&lt;br /&gt;
If you choose this method, make sure to restart the SQL import after executing these statements.&lt;br /&gt;
&lt;br /&gt;
==== Missing Guardian and BossGuardian entries ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sql&amp;quot;&amp;gt;&lt;br /&gt;
-- Additional Guardian entries for event maps&lt;br /&gt;
INSERT INTO fantasytennis.Guardian (id, addDex, addSta, addStr, addWill, baseDex, baseSta, baseStr, baseWill, btItemID, hpBase, hpPer, `level`, name, rewardExp, rewardGold, rewardRankingPoint, guardIndex, earth, elementGrade, fire, water, wind) VALUES(73, 1, 8, 1, 1, 165, 45, 110, 120, 26, 15000, 55, 50, &#039;No. 1&#039;, 200, 200, 6, 61, 0, 9, 0, 1, 0);&lt;br /&gt;
INSERT INTO fantasytennis.Guardian (id, addDex, addSta, addStr, addWill, baseDex, baseSta, baseStr, baseWill, btItemID, hpBase, hpPer, `level`, name, rewardExp, rewardGold, rewardRankingPoint, guardIndex, earth, elementGrade, fire, water, wind) VALUES(74, 1, 8, 1, 1, 165, 45, 110, 120, 26, 15000, 55, 50, &#039;No. 2&#039;, 225, 225, 6, 62, 0, 9, 0, 1, 0);&lt;br /&gt;
INSERT INTO fantasytennis.Guardian (id, addDex, addSta, addStr, addWill, baseDex, baseSta, baseStr, baseWill, btItemID, hpBase, hpPer, `level`, name, rewardExp, rewardGold, rewardRankingPoint, guardIndex, earth, elementGrade, fire, water, wind) VALUES(75, 1, 8, 1, 1, 165, 45, 110, 120, 26, 15000, 55, 50, &#039;No. 3&#039;, 250, 250, 6, 63, 0, 9, 0, 1, 0);&lt;br /&gt;
INSERT INTO fantasytennis.Guardian (id, addDex, addSta, addStr, addWill, baseDex, baseSta, baseStr, baseWill, btItemID, hpBase, hpPer, `level`, name, rewardExp, rewardGold, rewardRankingPoint, guardIndex, earth, elementGrade, fire, water, wind) VALUES(76, 1, 8, 1, 1, 200, 75, 150, 105, 26, 24000, 500, 50, &#039;No. 4&#039;, 475, 600, 20, 64, 0, 9, 0, 1, 0);&lt;br /&gt;
INSERT INTO fantasytennis.Guardian (id, addDex, addSta, addStr, addWill, baseDex, baseSta, baseStr, baseWill, btItemID, hpBase, hpPer, `level`, name, rewardExp, rewardGold, rewardRankingPoint, guardIndex, earth, elementGrade, fire, water, wind) VALUES(77, 1, 8, 1, 1, 200, 75, 150, 100, 26, 24000, 500, 50, &#039;No. 5&#039;, 500, 600, 20, 65, 0, 9, 0, 1, 0);&lt;br /&gt;
INSERT INTO fantasytennis.Guardian (id, addDex, addSta, addStr, addWill, baseDex, baseSta, baseStr, baseWill, btItemID, hpBase, hpPer, `level`, name, rewardExp, rewardGold, rewardRankingPoint, guardIndex, earth, elementGrade, fire, water, wind) VALUES(78, 1, 8, 1, 1, 200, 75, 150, 100, 26, 24000, 500, 50, &#039;No. 6&#039;, 525, 600, 20, 66, 1, 9, 0, 1, 0);&lt;br /&gt;
INSERT INTO fantasytennis.Guardian (id, addDex, addSta, addStr, addWill, baseDex, baseSta, baseStr, baseWill, btItemID, hpBase, hpPer, `level`, name, rewardExp, rewardGold, rewardRankingPoint, guardIndex, earth, elementGrade, fire, water, wind) VALUES(79, 1, 8, 1, 1, 165, 45, 110, 120, 10, 15000, 55, 46, &#039;Aquara&#039;, 275, 275, 6, 34, 0, 9, 0, 0, 1);&lt;br /&gt;
INSERT INTO fantasytennis.Guardian (id, addDex, addSta, addStr, addWill, baseDex, baseSta, baseStr, baseWill, btItemID, hpBase, hpPer, `level`, name, rewardExp, rewardGold, rewardRankingPoint, guardIndex, earth, elementGrade, fire, water, wind) VALUES(80, 1, 8, 1, 1, 165, 45, 110, 120, 9, 15000, 55, 44, &#039;Moora&#039;, 325, 360, 6, 33, 0, 9, 0, 0, 1);&lt;br /&gt;
&lt;br /&gt;
-- Additional BossGuardian entries for event maps&lt;br /&gt;
INSERT INTO fantasytennis.BossGuardian (id, addDex, addSta, addStr, addWill, baseDex, baseSta, baseStr, baseWill, btItemID, hpBase, hpPer, `level`, name, rewardExp, rewardGold, rewardRankingPoint, guardIndex, earth, elementGrade, fire, water, wind) VALUES(8, 1, 15, 1, 1, 165, 60, 150, 120, 23, 20000, 200, 60, &#039;Hera&#039;, 500, 375, 34, 3, 0, 9, 1, 1, 1);&lt;br /&gt;
INSERT INTO fantasytennis.BossGuardian (id, addDex, addSta, addStr, addWill, baseDex, baseSta, baseStr, baseWill, btItemID, hpBase, hpPer, `level`, name, rewardExp, rewardGold, rewardRankingPoint, guardIndex, earth, elementGrade, fire, water, wind) VALUES(9, 1, 15, 1, 1, 220, 80, 160, 120, 27, 24000, 500, 60, &#039;TB-255&#039;, 2000, 1500, 60, 6, 1, 9, 1, 0, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Fix existing Guardian and BossGuardian entries ====&lt;br /&gt;
&lt;br /&gt;
Some existing Guardian and BossGuardian records might be missing important attributes like `rewardExp`, `rewardGold`, or `guardIndex`. These entries can be corrected with the following `UPDATE` statements:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sql&amp;quot;&amp;gt;&lt;br /&gt;
-- Fix Guardian entries for rest since they miss rewardExp,rewardGold and guardIndex&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=4, addStr=1, addWill=1, baseDex=10, baseSta=1, baseStr=10, baseWill=30, btItemID=1, hpBase=500, hpPer=1, `level`=10, name=&#039;Dokaro&#039;, rewardExp=11, rewardGold=16, rewardRankingPoint=0, guardIndex=1, earth=0, elementGrade=2, fire=0, water=0, wind=0 WHERE id=1;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=4, addStr=1, addWill=1, baseDex=12, baseSta=1, baseStr=13, baseWill=32, btItemID=1, hpBase=550, hpPer=1, `level`=15, name=&#039;Penkaro&#039;, rewardExp=11, rewardGold=16, rewardRankingPoint=0, guardIndex=2, earth=0, elementGrade=2, fire=0, water=0, wind=0 WHERE id=2;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=4, addStr=1, addWill=1, baseDex=14, baseSta=1, baseStr=16, baseWill=34, btItemID=1, hpBase=600, hpPer=1, `level`=20, name=&#039;Bekaro&#039;, rewardExp=12, rewardGold=17, rewardRankingPoint=0, guardIndex=3, earth=0, elementGrade=2, fire=0, water=0, wind=0 WHERE id=3;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=4, addStr=1, addWill=1, baseDex=16, baseSta=1, baseStr=19, baseWill=36, btItemID=1, hpBase=650, hpPer=1, `level`=25, name=&#039;Hokaro&#039;, rewardExp=12, rewardGold=17, rewardRankingPoint=0, guardIndex=4, earth=0, elementGrade=2, fire=0, water=0, wind=0 WHERE id=4;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=4, addStr=1, addWill=1, baseDex=18, baseSta=1, baseStr=22, baseWill=38, btItemID=1, hpBase=700, hpPer=1, `level`=30, name=&#039;Sikaro&#039;, rewardExp=12, rewardGold=18, rewardRankingPoint=0, guardIndex=5, earth=0, elementGrade=2, fire=0, water=0, wind=0 WHERE id=5;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=4, addStr=1, addWill=1, baseDex=20, baseSta=1, baseStr=25, baseWill=40, btItemID=1, hpBase=750, hpPer=1, `level`=40, name=&#039;Ikaro&#039;, rewardExp=13, rewardGold=18, rewardRankingPoint=0, guardIndex=6, earth=1, elementGrade=2, fire=1, water=0, wind=0 WHERE id=6;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=5, addStr=1, addWill=1, baseDex=10, baseSta=1, baseStr=14, baseWill=35, btItemID=2, hpBase=650, hpPer=6, `level`=20, name=&#039;Doteko&#039;, rewardExp=14, rewardGold=19, rewardRankingPoint=0, guardIndex=7, earth=0, elementGrade=3, fire=0, water=0, wind=0 WHERE id=7;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=5, addStr=1, addWill=1, baseDex=12, baseSta=1, baseStr=17, baseWill=37, btItemID=2, hpBase=700, hpPer=6, `level`=25, name=&#039;Penteko&#039;, rewardExp=16, rewardGold=19, rewardRankingPoint=0, guardIndex=8, earth=0, elementGrade=3, fire=0, water=0, wind=0 WHERE id=8;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=5, addStr=1, addWill=1, baseDex=14, baseSta=1, baseStr=20, baseWill=39, btItemID=2, hpBase=750, hpPer=6, `level`=30, name=&#039;Beteko&#039;, rewardExp=16, rewardGold=21, rewardRankingPoint=0, guardIndex=9, earth=0, elementGrade=3, fire=0, water=0, wind=0 WHERE id=9;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=5, addStr=1, addWill=1, baseDex=16, baseSta=1, baseStr=23, baseWill=41, btItemID=2, hpBase=800, hpPer=6, `level`=35, name=&#039;Hoteko&#039;, rewardExp=16, rewardGold=21, rewardRankingPoint=0, guardIndex=10, earth=0, elementGrade=3, fire=0, water=0, wind=0 WHERE id=10;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=6, addStr=1, addWill=1, baseDex=18, baseSta=1, baseStr=26, baseWill=43, btItemID=2, hpBase=850, hpPer=6, `level`=40, name=&#039;Siteko&#039;, rewardExp=18, rewardGold=22, rewardRankingPoint=0, guardIndex=11, earth=0, elementGrade=3, fire=0, water=0, wind=0 WHERE id=11;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=6, addStr=1, addWill=1, baseDex=20, baseSta=1, baseStr=29, baseWill=45, btItemID=2, hpBase=900, hpPer=6, `level`=45, name=&#039;Eteko&#039;, rewardExp=19, rewardGold=22, rewardRankingPoint=0, guardIndex=12, earth=0, elementGrade=3, fire=1, water=0, wind=1 WHERE id=12;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=6, addStr=1, addWill=1, baseDex=15, baseSta=1, baseStr=25, baseWill=40, btItemID=3, hpBase=800, hpPer=7, `level`=25, name=&#039;Doga&#039;, rewardExp=27, rewardGold=37, rewardRankingPoint=2, guardIndex=13, earth=0, elementGrade=4, fire=0, water=1, wind=0 WHERE id=13;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=6, addStr=1, addWill=1, baseDex=17, baseSta=1, baseStr=30, baseWill=42, btItemID=3, hpBase=850, hpPer=7, `level`=30, name=&#039;Penga&#039;, rewardExp=27, rewardGold=39, rewardRankingPoint=2, guardIndex=14, earth=0, elementGrade=4, fire=0, water=1, wind=0 WHERE id=14;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=6, addStr=1, addWill=1, baseDex=19, baseSta=1, baseStr=35, baseWill=44, btItemID=3, hpBase=900, hpPer=7, `level`=35, name=&#039;Bega&#039;, rewardExp=28, rewardGold=39, rewardRankingPoint=2, guardIndex=15, earth=0, elementGrade=4, fire=0, water=1, wind=0 WHERE id=15;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=6, addStr=1, addWill=1, baseDex=21, baseSta=1, baseStr=40, baseWill=46, btItemID=3, hpBase=950, hpPer=7, `level`=40, name=&#039;Hoga&#039;, rewardExp=29, rewardGold=43, rewardRankingPoint=2, guardIndex=16, earth=0, elementGrade=4, fire=0, water=1, wind=0 WHERE id=16;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=6, addStr=1, addWill=1, baseDex=23, baseSta=1, baseStr=45, baseWill=48, btItemID=3, hpBase=1000, hpPer=7, `level`=45, name=&#039;Siga&#039;, rewardExp=30, rewardGold=43, rewardRankingPoint=2, guardIndex=17, earth=0, elementGrade=4, fire=0, water=1, wind=0 WHERE id=17;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=6, addStr=1, addWill=1, baseDex=25, baseSta=1, baseStr=50, baseWill=50, btItemID=3, hpBase=1050, hpPer=7, `level`=50, name=&#039;Ega&#039;, rewardExp=31, rewardGold=45, rewardRankingPoint=2, guardIndex=18, earth=1, elementGrade=4, fire=0, water=1, wind=0 WHERE id=18;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=7, addStr=1, addWill=1, baseDex=50, baseSta=5, baseStr=30, baseWill=45, btItemID=4, hpBase=1200, hpPer=30, `level`=40, name=&#039;Dogoliath&#039;, rewardExp=55, rewardGold=70, rewardRankingPoint=4, guardIndex=19, earth=1, elementGrade=5, fire=0, water=0, wind=0 WHERE id=19;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=7, addStr=1, addWill=1, baseDex=55, baseSta=5, baseStr=35, baseWill=48, btItemID=4, hpBase=1350, hpPer=30, `level`=42, name=&#039;Pengoliath&#039;, rewardExp=60, rewardGold=75, rewardRankingPoint=4, guardIndex=20, earth=1, elementGrade=5, fire=0, water=0, wind=0 WHERE id=20;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=7, addStr=1, addWill=1, baseDex=60, baseSta=5, baseStr=40, baseWill=51, btItemID=4, hpBase=1500, hpPer=30, `level`=44, name=&#039;Begoliath&#039;, rewardExp=65, rewardGold=80, rewardRankingPoint=4, guardIndex=21, earth=1, elementGrade=5, fire=0, water=0, wind=0 WHERE id=21;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=7, addStr=1, addWill=1, baseDex=65, baseSta=5, baseStr=45, baseWill=54, btItemID=4, hpBase=1650, hpPer=30, `level`=46, name=&#039;Hogoliath&#039;, rewardExp=70, rewardGold=85, rewardRankingPoint=4, guardIndex=22, earth=1, elementGrade=5, fire=0, water=0, wind=0 WHERE id=22;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=7, addStr=1, addWill=1, baseDex=70, baseSta=5, baseStr=50, baseWill=57, btItemID=19, hpBase=1800, hpPer=30, `level`=48, name=&#039;Sigoliath&#039;, rewardExp=75, rewardGold=90, rewardRankingPoint=4, guardIndex=23, earth=1, elementGrade=5, fire=0, water=0, wind=0 WHERE id=23;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=7, addStr=1, addWill=1, baseDex=75, baseSta=5, baseStr=55, baseWill=60, btItemID=19, hpBase=1950, hpPer=30, `level`=50, name=&#039;Egoliath&#039;, rewardExp=85, rewardGold=92, rewardRankingPoint=4, guardIndex=24, earth=1, elementGrade=5, fire=0, water=0, wind=1 WHERE id=24;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=55, baseSta=10, baseStr=30, baseWill=45, btItemID=5, hpBase=2150, hpPer=50, `level`=40, name=&#039;Doblood&#039;, rewardExp=60, rewardGold=70, rewardRankingPoint=5, guardIndex=25, earth=0, elementGrade=6, fire=1, water=0, wind=0 WHERE id=25;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=60, baseSta=10, baseStr=35, baseWill=48, btItemID=5, hpBase=2300, hpPer=50, `level`=42, name=&#039;Penblood&#039;, rewardExp=65, rewardGold=75, rewardRankingPoint=5, guardIndex=26, earth=0, elementGrade=6, fire=1, water=0, wind=0 WHERE id=26;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=65, baseSta=10, baseStr=40, baseWill=51, btItemID=5, hpBase=2450, hpPer=50, `level`=44, name=&#039;Beblood&#039;, rewardExp=70, rewardGold=80, rewardRankingPoint=5, guardIndex=27, earth=0, elementGrade=6, fire=1, water=0, wind=0 WHERE id=27;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=70, baseSta=10, baseStr=45, baseWill=54, btItemID=5, hpBase=2600, hpPer=50, `level`=46, name=&#039;Hoblood&#039;, rewardExp=75, rewardGold=85, rewardRankingPoint=5, guardIndex=28, earth=0, elementGrade=6, fire=1, water=0, wind=0 WHERE id=28;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=75, baseSta=10, baseStr=50, baseWill=57, btItemID=5, hpBase=2750, hpPer=50, `level`=48, name=&#039;Siblood&#039;, rewardExp=80, rewardGold=90, rewardRankingPoint=5, guardIndex=29, earth=0, elementGrade=6, fire=1, water=0, wind=0 WHERE id=29;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=80, baseSta=10, baseStr=55, baseWill=60, btItemID=5, hpBase=2900, hpPer=50, `level`=50, name=&#039;Iblood&#039;, rewardExp=85, rewardGold=95, rewardRankingPoint=5, guardIndex=30, earth=0, elementGrade=6, fire=1, water=0, wind=1 WHERE id=30;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=55, baseSta=10, baseStr=30, baseWill=45, btItemID=7, hpBase=2650, hpPer=55, `level`=40, name=&#039;Windra&#039;, rewardExp=60, rewardGold=75, rewardRankingPoint=6, guardIndex=31, earth=0, elementGrade=7, fire=0, water=0, wind=1 WHERE id=31;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=60, baseSta=10, baseStr=35, baseWill=48, btItemID=8, hpBase=2800, hpPer=55, `level`=42, name=&#039;Earthra&#039;, rewardExp=65, rewardGold=80, rewardRankingPoint=6, guardIndex=32, earth=0, elementGrade=7, fire=0, water=0, wind=1 WHERE id=32;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=65, baseSta=10, baseStr=40, baseWill=51, btItemID=9, hpBase=2950, hpPer=55, `level`=44, name=&#039;Moora&#039;, rewardExp=70, rewardGold=85, rewardRankingPoint=6, guardIndex=33, earth=0, elementGrade=7, fire=0, water=0, wind=1 WHERE id=33;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=70, baseSta=10, baseStr=45, baseWill=54, btItemID=10, hpBase=3100, hpPer=55, `level`=46, name=&#039;Aquara&#039;, rewardExp=75, rewardGold=90, rewardRankingPoint=6, guardIndex=34, earth=0, elementGrade=7, fire=0, water=0, wind=1 WHERE id=34;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=75, baseSta=10, baseStr=50, baseWill=57, btItemID=11, hpBase=3250, hpPer=55, `level`=48, name=&#039;Fyra&#039;, rewardExp=80, rewardGold=95, rewardRankingPoint=6, guardIndex=35, earth=0, elementGrade=7, fire=0, water=0, wind=1 WHERE id=35;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=80, baseSta=10, baseStr=55, baseWill=60, btItemID=12, hpBase=3400, hpPer=55, `level`=50, name=&#039;Thundera&#039;, rewardExp=85, rewardGold=100, rewardRankingPoint=6, guardIndex=36, earth=0, elementGrade=7, fire=1, water=0, wind=1 WHERE id=36;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=10, addStr=1, addWill=1, baseDex=55, baseSta=10, baseStr=30, baseWill=45, btItemID=5, hpBase=4150, hpPer=60, `level`=45, name=&#039;DevilDokaro&#039;, rewardExp=80, rewardGold=85, rewardRankingPoint=3, guardIndex=37, earth=0, elementGrade=8, fire=1, water=0, wind=1 WHERE id=37;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=10, addStr=1, addWill=1, baseDex=60, baseSta=10, baseStr=35, baseWill=48, btItemID=5, hpBase=4300, hpPer=60, `level`=46, name=&#039;DevilPenkaro&#039;, rewardExp=85, rewardGold=90, rewardRankingPoint=3, guardIndex=38, earth=0, elementGrade=8, fire=1, water=0, wind=1 WHERE id=38;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=10, addStr=1, addWill=1, baseDex=65, baseSta=10, baseStr=40, baseWill=51, btItemID=5, hpBase=4450, hpPer=60, `level`=47, name=&#039;DevilBekaro&#039;, rewardExp=90, rewardGold=95, rewardRankingPoint=3, guardIndex=39, earth=0, elementGrade=8, fire=1, water=0, wind=1 WHERE id=39;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=10, addStr=1, addWill=1, baseDex=70, baseSta=10, baseStr=45, baseWill=54, btItemID=5, hpBase=4600, hpPer=60, `level`=48, name=&#039;DevilHokaro&#039;, rewardExp=95, rewardGold=100, rewardRankingPoint=3, guardIndex=40, earth=0, elementGrade=8, fire=1, water=0, wind=1 WHERE id=40;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=10, addStr=1, addWill=1, baseDex=75, baseSta=10, baseStr=50, baseWill=57, btItemID=5, hpBase=4750, hpPer=60, `level`=49, name=&#039;DevilSikaro&#039;, rewardExp=100, rewardGold=105, rewardRankingPoint=3, guardIndex=41, earth=0, elementGrade=8, fire=1, water=0, wind=1 WHERE id=41;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=10, addStr=1, addWill=1, baseDex=80, baseSta=10, baseStr=55, baseWill=60, btItemID=5, hpBase=4900, hpPer=60, `level`=50, name=&#039;DevilIkaro&#039;, rewardExp=105, rewardGold=110, rewardRankingPoint=3, guardIndex=42, earth=0, elementGrade=8, fire=1, water=0, wind=1 WHERE id=42;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=55, baseSta=10, baseStr=30, baseWill=45, btItemID=13, hpBase=2650, hpPer=55, `level`=50, name=&#039;Dolizard&#039;, rewardExp=65, rewardGold=90, rewardRankingPoint=8, guardIndex=43, earth=0, elementGrade=9, fire=0, water=1, wind=0 WHERE id=43;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=60, baseSta=10, baseStr=35, baseWill=48, btItemID=14, hpBase=2800, hpPer=55, `level`=50, name=&#039;Penlizard&#039;, rewardExp=65, rewardGold=95, rewardRankingPoint=8, guardIndex=44, earth=0, elementGrade=9, fire=0, water=1, wind=0 WHERE id=44;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=65, baseSta=10, baseStr=40, baseWill=51, btItemID=15, hpBase=2950, hpPer=55, `level`=50, name=&#039;Belizard&#039;, rewardExp=70, rewardGold=100, rewardRankingPoint=8, guardIndex=45, earth=0, elementGrade=9, fire=0, water=1, wind=0 WHERE id=45;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=70, baseSta=10, baseStr=45, baseWill=54, btItemID=16, hpBase=3100, hpPer=55, `level`=50, name=&#039;Holizard&#039;, rewardExp=70, rewardGold=105, rewardRankingPoint=8, guardIndex=46, earth=0, elementGrade=9, fire=0, water=1, wind=0 WHERE id=46;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=75, baseSta=10, baseStr=50, baseWill=57, btItemID=17, hpBase=3250, hpPer=55, `level`=50, name=&#039;Silizard&#039;, rewardExp=80, rewardGold=110, rewardRankingPoint=8, guardIndex=47, earth=0, elementGrade=9, fire=0, water=1, wind=0 WHERE id=47;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=80, baseSta=10, baseStr=55, baseWill=60, btItemID=18, hpBase=3400, hpPer=55, `level`=50, name=&#039;Elizard&#039;, rewardExp=90, rewardGold=120, rewardRankingPoint=8, guardIndex=48, earth=1, elementGrade=9, fire=0, water=1, wind=0 WHERE id=48;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=7, addStr=1, addWill=1, baseDex=50, baseSta=5, baseStr=30, baseWill=45, btItemID=21, hpBase=1200, hpPer=30, `level`=40, name=&#039;Dotossakan&#039;, rewardExp=55, rewardGold=65, rewardRankingPoint=4, guardIndex=49, earth=1, elementGrade=5, fire=0, water=0, wind=0 WHERE id=49;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=7, addStr=1, addWill=1, baseDex=55, baseSta=5, baseStr=35, baseWill=48, btItemID=21, hpBase=1350, hpPer=30, `level`=42, name=&#039;Pantossakan&#039;, rewardExp=60, rewardGold=65, rewardRankingPoint=4, guardIndex=50, earth=1, elementGrade=5, fire=0, water=0, wind=0 WHERE id=50;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=7, addStr=1, addWill=1, baseDex=60, baseSta=5, baseStr=40, baseWill=51, btItemID=21, hpBase=1500, hpPer=30, `level`=44, name=&#039;Betossakan&#039;, rewardExp=65, rewardGold=75, rewardRankingPoint=4, guardIndex=51, earth=1, elementGrade=5, fire=0, water=0, wind=0 WHERE id=51;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=7, addStr=1, addWill=1, baseDex=65, baseSta=5, baseStr=45, baseWill=54, btItemID=21, hpBase=1650, hpPer=30, `level`=46, name=&#039;Hotossakan&#039;, rewardExp=70, rewardGold=75, rewardRankingPoint=4, guardIndex=52, earth=1, elementGrade=5, fire=0, water=0, wind=0 WHERE id=52;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=7, addStr=1, addWill=1, baseDex=70, baseSta=5, baseStr=50, baseWill=57, btItemID=22, hpBase=1800, hpPer=30, `level`=48, name=&#039;Sitossakan&#039;, rewardExp=75, rewardGold=75, rewardRankingPoint=4, guardIndex=53, earth=1, elementGrade=5, fire=0, water=0, wind=0 WHERE id=53;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=7, addStr=1, addWill=1, baseDex=75, baseSta=5, baseStr=55, baseWill=60, btItemID=22, hpBase=1950, hpPer=30, `level`=50, name=&#039;Etoossakan&#039;, rewardExp=80, rewardGold=90, rewardRankingPoint=4, guardIndex=54, earth=1, elementGrade=5, fire=0, water=0, wind=1 WHERE id=54;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=4, addStr=1, addWill=1, baseDex=14, baseSta=2, baseStr=16, baseWill=34, btItemID=1, hpBase=600, hpPer=1, `level`=20, name=&#039;Kuromaro&#039;, rewardExp=10, rewardGold=10, rewardRankingPoint=1, guardIndex=55, earth=1, elementGrade=2, fire=0, water=0, wind=0 WHERE id=55;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=4, addStr=1, addWill=1, baseDex=16, baseSta=2, baseStr=19, baseWill=36, btItemID=1, hpBase=650, hpPer=1, `level`=25, name=&#039;Akamaro&#039;, rewardExp=12, rewardGold=10, rewardRankingPoint=1, guardIndex=56, earth=0, elementGrade=2, fire=1, water=0, wind=0 WHERE id=56;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=4, addStr=1, addWill=1, baseDex=18, baseSta=2, baseStr=22, baseWill=38, btItemID=1, hpBase=700, hpPer=1, `level`=30, name=&#039;Aomaro&#039;, rewardExp=15, rewardGold=12, rewardRankingPoint=1, guardIndex=57, earth=0, elementGrade=2, fire=0, water=1, wind=0 WHERE id=57;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=4, addStr=1, addWill=1, baseDex=20, baseSta=2, baseStr=25, baseWill=40, btItemID=1, hpBase=750, hpPer=1, `level`=35, name=&#039;Kokechamaro&#039;, rewardExp=18, rewardGold=15, rewardRankingPoint=1, guardIndex=58, earth=0, elementGrade=2, fire=0, water=0, wind=1 WHERE id=58;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=4, addStr=1, addWill=1, baseDex=22, baseSta=2, baseStr=28, baseWill=42, btItemID=1, hpBase=800, hpPer=1, `level`=40, name=&#039;Chachamaro&#039;, rewardExp=20, rewardGold=18, rewardRankingPoint=1, guardIndex=59, earth=1, elementGrade=2, fire=1, water=0, wind=0 WHERE id=59;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=4, addStr=1, addWill=1, baseDex=24, baseSta=2, baseStr=31, baseWill=44, btItemID=1, hpBase=850, hpPer=1, `level`=50, name=&#039;Siromaro&#039;, rewardExp=35, rewardGold=20, rewardRankingPoint=1, guardIndex=60, earth=0, elementGrade=2, fire=0, water=1, wind=1 WHERE id=60;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=55, baseSta=10, baseStr=30, baseWill=45, btItemID=26, hpBase=2650, hpPer=55, `level`=50, name=&#039;No. 1&#039;, rewardExp=45, rewardGold=45, rewardRankingPoint=6, guardIndex=61, earth=0, elementGrade=9, fire=0, water=1, wind=0 WHERE id=61;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=60, baseSta=10, baseStr=35, baseWill=48, btItemID=26, hpBase=2800, hpPer=55, `level`=50, name=&#039;No. 2&#039;, rewardExp=55, rewardGold=50, rewardRankingPoint=6, guardIndex=62, earth=0, elementGrade=9, fire=0, water=1, wind=0 WHERE id=62;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=65, baseSta=10, baseStr=40, baseWill=51, btItemID=26, hpBase=2950, hpPer=55, `level`=50, name=&#039;No. 3&#039;, rewardExp=60, rewardGold=55, rewardRankingPoint=6, guardIndex=63, earth=0, elementGrade=9, fire=0, water=1, wind=0 WHERE id=63;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=70, baseSta=10, baseStr=45, baseWill=54, btItemID=26, hpBase=3100, hpPer=55, `level`=50, name=&#039;No. 4&#039;, rewardExp=65, rewardGold=60, rewardRankingPoint=6, guardIndex=64, earth=0, elementGrade=9, fire=0, water=1, wind=0 WHERE id=64;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=75, baseSta=10, baseStr=50, baseWill=57, btItemID=26, hpBase=3250, hpPer=55, `level`=50, name=&#039;No. 5&#039;, rewardExp=70, rewardGold=70, rewardRankingPoint=6, guardIndex=65, earth=0, elementGrade=9, fire=0, water=1, wind=0 WHERE id=65;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=8, addStr=1, addWill=1, baseDex=80, baseSta=10, baseStr=55, baseWill=60, btItemID=26, hpBase=3400, hpPer=55, `level`=50, name=&#039;No. 6&#039;, rewardExp=75, rewardGold=75, rewardRankingPoint=6, guardIndex=66, earth=1, elementGrade=9, fire=0, water=1, wind=0 WHERE id=66;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=11, addStr=1, addWill=1, baseDex=110, baseSta=25, baseStr=70, baseWill=88, btItemID=28, hpBase=4000, hpPer=850, `level`=50, name=&#039;Yeppeuni&#039;, rewardExp=150, rewardGold=120, rewardRankingPoint=7, guardIndex=67, earth=1, elementGrade=9, fire=0, water=1, wind=0 WHERE id=67;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=11, addStr=1, addWill=1, baseDex=115, baseSta=25, baseStr=72, baseWill=91, btItemID=28, hpBase=4100, hpPer=850, `level`=52, name=&#039;Gwiyeomi&#039;, rewardExp=155, rewardGold=125, rewardRankingPoint=7, guardIndex=68, earth=1, elementGrade=9, fire=0, water=1, wind=0 WHERE id=68;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=11, addStr=1, addWill=1, baseDex=120, baseSta=25, baseStr=74, baseWill=94, btItemID=28, hpBase=4200, hpPer=850, `level`=54, name=&#039;Ballari&#039;, rewardExp=160, rewardGold=130, rewardRankingPoint=7, guardIndex=69, earth=1, elementGrade=9, fire=0, water=1, wind=0 WHERE id=69;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=11, addStr=1, addWill=1, baseDex=125, baseSta=25, baseStr=76, baseWill=97, btItemID=28, hpBase=4300, hpPer=850, `level`=56, name=&#039;Sangkeumi&#039;, rewardExp=165, rewardGold=135, rewardRankingPoint=7, guardIndex=70, earth=1, elementGrade=9, fire=0, water=1, wind=0 WHERE id=70;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=11, addStr=1, addWill=1, baseDex=130, baseSta=25, baseStr=78, baseWill=100, btItemID=28, hpBase=4400, hpPer=850, `level`=58, name=&#039;Hwaljjagi&#039;, rewardExp=170, rewardGold=140, rewardRankingPoint=7, guardIndex=71, earth=1, elementGrade=9, fire=0, water=1, wind=0 WHERE id=71;&lt;br /&gt;
UPDATE fantasytennis.Guardian SET addDex=1, addSta=11, addStr=1, addWill=1, baseDex=135, baseSta=25, baseStr=80, baseWill=103, btItemID=28, hpBase=4500, hpPer=850, `level`=60, name=&#039;Saechimi&#039;, rewardExp=175, rewardGold=145, rewardRankingPoint=7, guardIndex=72, earth=1, elementGrade=9, fire=0, water=1, wind=0 WHERE id=72;&lt;br /&gt;
&lt;br /&gt;
-- -- Fix BossGuardian entries for rest since they miss rewardExp,rewardGold and guardIndex&lt;br /&gt;
UPDATE fantasytennis.BossGuardian SET addDex=1, addSta=15, addStr=1, addWill=1, baseDex=100, baseSta=45, baseStr=40, baseWill=90, btItemID=20, hpBase=5000, hpPer=150, `level`=60, name=&#039;Hell Blood&#039;, rewardExp=90, rewardGold=165, rewardRankingPoint=14, guardIndex=1, earth=1, elementGrade=9, fire=1, water=0, wind=1 WHERE id=1;&lt;br /&gt;
UPDATE fantasytennis.BossGuardian SET addDex=1, addSta=15, addStr=1, addWill=1, baseDex=100, baseSta=45, baseStr=40, baseWill=90, btItemID=20, hpBase=10000, hpPer=150, `level`=60, name=&#039;Hell Blood&#039;, rewardExp=480, rewardGold=450, rewardRankingPoint=44, guardIndex=2, earth=1, elementGrade=9, fire=1, water=0, wind=1 WHERE id=2;&lt;br /&gt;
UPDATE fantasytennis.BossGuardian SET addDex=1, addSta=15, addStr=1, addWill=1, baseDex=120, baseSta=35, baseStr=85, baseWill=95, btItemID=23, hpBase=7000, hpPer=200, `level`=60, name=&#039;Hera&#039;, rewardExp=200, rewardGold=180, rewardRankingPoint=14, guardIndex=3, earth=0, elementGrade=9, fire=1, water=1, wind=1 WHERE id=3;&lt;br /&gt;
UPDATE fantasytennis.BossGuardian SET addDex=1, addSta=15, addStr=1, addWill=1, baseDex=130, baseSta=25, baseStr=90, baseWill=100, btItemID=24, hpBase=9000, hpPer=250, `level`=60, name=&#039;Royal Lizard&#039;, rewardExp=220, rewardGold=270, rewardRankingPoint=14, guardIndex=4, earth=1, elementGrade=9, fire=0, water=1, wind=1 WHERE id=4;&lt;br /&gt;
UPDATE fantasytennis.BossGuardian SET addDex=1, addSta=15, addStr=1, addWill=1, baseDex=140, baseSta=15, baseStr=95, baseWill=105, btItemID=25, hpBase=11000, hpPer=300, `level`=60, name=&#039;TossakanBoss&#039;, rewardExp=240, rewardGold=285, rewardRankingPoint=10, guardIndex=5, earth=1, elementGrade=9, fire=1, water=1, wind=0 WHERE id=5;&lt;br /&gt;
UPDATE fantasytennis.BossGuardian SET addDex=1, addSta=15, addStr=1, addWill=1, baseDex=140, baseSta=10, baseStr=95, baseWill=105, btItemID=27, hpBase=13000, hpPer=300, `level`=60, name=&#039;TB-255&#039;, rewardExp=240, rewardGold=300, rewardRankingPoint=18, guardIndex=6, earth=1, elementGrade=9, fire=1, water=0, wind=1 WHERE id=6;&lt;br /&gt;
UPDATE fantasytennis.BossGuardian SET addDex=1, addSta=15, addStr=1, addWill=1, baseDex=160, baseSta=30, baseStr=105, baseWill=115, btItemID=29, hpBase=15000, hpPer=400, `level`=60, name=&#039;Dance King&#039;, rewardExp=280, rewardGold=300, rewardRankingPoint=10, guardIndex=7, earth=1, elementGrade=9, fire=1, water=1, wind=0 WHERE id=7;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Cleaning Up the Database ===&lt;br /&gt;
&lt;br /&gt;
Before re-running the script, it&#039;s important to delete any invalid or partially inserted data left behind by previous failed attempts. You can do this with the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sql&amp;quot;&amp;gt;&lt;br /&gt;
DELETE FROM Config;&lt;br /&gt;
DELETE FROM K_Status;&lt;br /&gt;
DELETE FROM S_Maps;&lt;br /&gt;
DELETE FROM M_Scenarios;&lt;br /&gt;
DELETE FROM Map_2_Scenarios;&lt;br /&gt;
DELETE FROM Guardian_2_Maps;&lt;br /&gt;
DELETE FROM Skill_2_Guardians;&lt;br /&gt;
DELETE FROM S_Guardian_Multiplier;&lt;br /&gt;
DELETE FROM S_Relationship_Roles;&lt;br /&gt;
DELETE FROM S_Relationship_Types;&lt;br /&gt;
DELETE FROM S_Relationships;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running the Import Script ===&lt;br /&gt;
&lt;br /&gt;
Once you’ve applied the necessary fixes, you can proceed with re-importing the SQL files:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd &amp;lt;path-to-local-JFTSE-repo&amp;gt;/scripts&lt;br /&gt;
./import_sql.sh&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Related Issues ==&lt;br /&gt;
* [https://github.com/sstokic-tgm/JFTSE/issues/180 GitHub Issue #180]&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;br /&gt;
[[Category:SQL]]&lt;br /&gt;
[[Category:Fixing]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Game_Lore&amp;diff=70</id>
		<title>Game Lore</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Game_Lore&amp;diff=70"/>
		<updated>2025-02-24T21:05:51Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;== Game Lore == The world of &amp;#039;&amp;#039;Fantasy Tennis&amp;#039;&amp;#039; is set in the mystical realm of &amp;#039;&amp;#039;&amp;#039;Fantasy Land&amp;#039;&amp;#039;&amp;#039;, where warriors, adventurers, and magical beings compete in battles that blend athletic skill with powerful magic. This land is rich with legends, and its many races and factions form alliances, rivalries, and struggles for survival.  == The Origins of Fantasy Land == Fantasy Land was shaped by divine forces, a realm born from the delicate balance between light and darkness...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Game Lore ==&lt;br /&gt;
The world of &#039;&#039;Fantasy Tennis&#039;&#039; is set in the mystical realm of &#039;&#039;&#039;Fantasy Land&#039;&#039;&#039;, where warriors, adventurers, and magical beings compete in battles that blend athletic skill with powerful magic. This land is rich with legends, and its many races and factions form alliances, rivalries, and struggles for survival.&lt;br /&gt;
&lt;br /&gt;
== The Origins of Fantasy Land ==&lt;br /&gt;
Fantasy Land was shaped by divine forces, a realm born from the delicate balance between light and darkness. Celestial beings once watched over this harmony, but over time, conflicts arose—some seeking power, others striving to preserve the balance.&lt;br /&gt;
&lt;br /&gt;
== The Great Conflict ==&lt;br /&gt;
A great war once raged between the guardians of Fantasy Land and rebellious forces that sought to seize control of its magic. Heroes emerged to defend the realm, wielding both weapons and enchanted abilities. These battles were no ordinary duels; they took place on the mystical courts of Fantasy Land, where champions clashed in matches that could determine the fate of entire realms.&lt;br /&gt;
&lt;br /&gt;
== The Ark Crystal ==&lt;br /&gt;
At the heart of Fantasy Land lies the &#039;&#039;&#039;Ark Crystal&#039;&#039;&#039;, a powerful source of magical energy that maintains the world&#039;s balance. It ensures that no single faction or force gains absolute dominance. However, in recent times, its energy has begun to fade, throwing the land into uncertainty. Strange events ripple across the realm, and both scholars and champions search for a way to restore its strength before it&#039;s too late.&lt;br /&gt;
&lt;br /&gt;
== The Goddess Hera and Her Wrath ==&lt;br /&gt;
Among the divine beings watching over Fantasy Land, the goddess &#039;&#039;&#039;Hera&#039;&#039;&#039; holds immense power. Once a guardian of order, she became disillusioned with mortals&#039; reckless use of magic. Her frustration turned into wrath, and over time, her influence became a force of both challenge and destruction.&lt;br /&gt;
&lt;br /&gt;
It is said that during the winter months, under the eerie glow of the &#039;&#039;&#039;Snow Moon&#039;&#039;&#039;, Hera’s magic summons formidable foes to test the champions of Fantasy Land. Many see these battles as divine trials, a way for Hera to judge the strength of those who walk her land. Others believe they serve as warnings of greater calamities to come.&lt;br /&gt;
&lt;br /&gt;
== Town Square and Its Role ==&lt;br /&gt;
At the heart of Fantasy Land lies &#039;&#039;&#039;Town Square&#039;&#039;&#039;, a gathering place for adventurers and champions. It is a sanctuary where players share knowledge, prepare for battles, and stay informed about the changing tides of the world. Notable figures, such as the wise &#039;&#039;&#039;Frostlin&#039;&#039;&#039; and a peculiar pelican scholar, offer guidance to those who seek it.&lt;br /&gt;
&lt;br /&gt;
== The Frozen Lakes Mystery ==&lt;br /&gt;
During a recent event, the lakes of Fantasy Land, once teeming with life, mysteriously froze over. Some believed this was caused by the Ark Crystal’s instability, while others suspected Hera’s growing influence. Though the ice has since melted, the phenomenon left many questions unanswered. A curious penguin scholar had taken up the task of investigating this strange occurrence, but whether the lakes will freeze again remains unknown.&lt;br /&gt;
&lt;br /&gt;
== The Pirate Merchant ==&lt;br /&gt;
A wandering pirate, often seen near a cart filled with rare goods, has been spotted in Town Square. His true intentions remain a mystery, but his knowledge of distant lands and his selection of unusual wares have drawn the attention of many travelers. Some suspect he holds secrets about the fading power of the Ark Crystal, though whether he will share them is another matter.&lt;br /&gt;
&lt;br /&gt;
== Conclusion ==&lt;br /&gt;
Fantasy Land is ever changing, shaped by its inhabitants, celestial forces, and the ongoing struggle for balance. As the Ark Crystal weakens and ancient powers awaken, champions from across the world must rise to the challenge. Whether through strength, strategy, or sheer determination, they will decide the fate of this mystical realm.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;The lore of &#039;&#039;Fantasy Tennis&#039;&#039; is shaped by both official sources and community driven storytelling. While some elements are drawn from the original game&#039;s world, others have evolved through the creativity and interpretations of players over time. As Fantasy Land continues to grow, its legends, myths, and mysteries remain ever changing, shaped by those who explore its depths.&#039;&#039;&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Commands&amp;diff=69</id>
		<title>Commands</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Commands&amp;diff=69"/>
		<updated>2025-02-24T20:32:36Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Ingame Commands =&lt;br /&gt;
&lt;br /&gt;
This page provides a list of available ingame commands for &#039;&#039;&#039;Fantasy Tennis&#039;&#039;&#039; within &#039;&#039;&#039;JFTSE&#039;&#039;&#039;. Commands must be prefixed with a `-` to execute. Some commands are &#039;&#039;&#039;restricted to Game Masters (GMs)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== General Commands ==&lt;br /&gt;
These commands can be used by all players.&lt;br /&gt;
&lt;br /&gt;
=== 🎲 Random Mode ===&lt;br /&gt;
[[Guardian Random Mode|Random guardians]] will spawn instead of the default ones (boss remains the same).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-random&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🔥 Hard Mode ===&lt;br /&gt;
Enables &#039;&#039;&#039;[[Guardian Hard Mode|Hard Mode]]&#039;&#039;&#039;, increasing enemy stats and spawning additional guardians.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-hard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🎁 Gacha Opening ===&lt;br /&gt;
Opens gachas without animation for faster processing.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-og &amp;quot;Gacha Name&amp;quot; &amp;lt;amount&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-og &amp;quot;Western Coin&amp;quot; 50&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
➡ &#039;&#039;&#039;&amp;lt;span style=&amp;quot;color:red;&amp;quot;&amp;gt;Note:&amp;lt;/span&amp;gt;&#039;&#039;&#039; Some gachas contain &amp;quot;ll&amp;quot; (e.g., `&amp;quot;Rare Box ll&amp;quot;`), requiring a lowercase `L` in the command.&lt;br /&gt;
&lt;br /&gt;
=== 🏆 Arcade Mode ===&lt;br /&gt;
Enters &#039;&#039;&#039;Arcade Mode&#039;&#039;&#039;. WIP.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-arcade&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🔄 Pointback ===&lt;br /&gt;
Allows players to vote for resetting the last point in &#039;&#039;&#039;[[Basic Mode]]&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-pb&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
➡ &#039;&#039;&#039;Note:&#039;&#039;&#039; All players must vote using `-pb` before a new point is made.&lt;br /&gt;
&lt;br /&gt;
== GM Commands ==&lt;br /&gt;
These commands are &#039;&#039;&#039;restricted to Game Masters (GMs)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== 🚫 Player Management ===&lt;br /&gt;
Banning, unbanning, and kicking players.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-ban &amp;lt;player&amp;gt;&lt;br /&gt;
-unban &amp;lt;player&amp;gt;&lt;br /&gt;
-serverKick &amp;lt;player&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 📝 Server Notice ===&lt;br /&gt;
Sets and broadcasts a &#039;&#039;&#039;server wide notice&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-sN &amp;lt;message&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🔄 Reset &amp;amp; Reload ===&lt;br /&gt;
Resets or reloads server data.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-rsLogin &amp;lt;player&amp;gt;    # Resets login status&lt;br /&gt;
-reloadScripts       # Reloads all scripts&lt;br /&gt;
-event              # Event configuration&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🎁 Item &amp;amp; Gift Commands ===&lt;br /&gt;
Used to grant items, experience, or gold to players.&lt;br /&gt;
&lt;br /&gt;
Give items to &#039;&#039;&#039;another player&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-give &amp;lt;player&amp;gt; &amp;lt;item&amp;gt; &amp;lt;amount&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Give items to &#039;&#039;&#039;yourself&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-get &amp;lt;item&amp;gt; &amp;lt;amount&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Send a &#039;&#039;&#039;gift&#039;&#039;&#039; to another player:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-gift &amp;lt;player&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Help Command ==&lt;br /&gt;
Displays all commands and their description.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-help&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;Notes:&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
* Commands &#039;&#039;&#039;must always start with `-`&#039;&#039;&#039;.&lt;br /&gt;
* Some commands may have additional restrictions (e.g., requiring level 60).&lt;br /&gt;
* GM commands are &#039;&#039;&#039;restricted&#039;&#039;&#039; to authorized users.&lt;br /&gt;
&lt;br /&gt;
For further details, refer to the ingame chat.&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Database_Schema_%26_Cheatsheet&amp;diff=68</id>
		<title>Database Schema &amp; Cheatsheet</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Database_Schema_%26_Cheatsheet&amp;diff=68"/>
		<updated>2025-02-24T20:30:36Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Database Schema &amp;amp; Cheatsheet =&lt;br /&gt;
This page provides an overview of the database structure used in Fantasy Tennis, showing the key tables, their columns, and relationships. The schema diagram below visually represents how different entities connect within the system.&lt;br /&gt;
&lt;br /&gt;
== Schema ==&lt;br /&gt;
[[File:Db-entities.svg|200px|thumb|center|alt=Database Schema|Database Schema]]&lt;br /&gt;
&lt;br /&gt;
== Tables of Interest ==&lt;br /&gt;
Tables might worth looking into as they are important on setting up your own Fantasy Tennis Server Configuration inside the Database.&lt;br /&gt;
&lt;br /&gt;
=== Config ===&lt;br /&gt;
The `config` table is used to store various server wide settings that control gameplay mechanics, security settings, logging behavior, and feature toggles. Each configuration entry consists of:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;name&#039;&#039;&#039; – The unique identifier of the config setting.&lt;br /&gt;
* &#039;&#039;&#039;description&#039;&#039;&#039; – A short explanation of what the setting controls (may be null).&lt;br /&gt;
* &#039;&#039;&#039;value&#039;&#039;&#039; – The actual configured value.&lt;br /&gt;
* &#039;&#039;&#039;type&#039;&#039;&#039; – The data type of the value (e.g., boolean, int, double, string).&lt;br /&gt;
&lt;br /&gt;
Administrators can modify these values to tweak server behavior, enable or disable features, and adjust game mechanics dynamically.&lt;br /&gt;
&lt;br /&gt;
Below is a list of currently supported configuration values along with their default settings.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Name !! Type !! Default Value !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;anticheat.enabled&#039;&#039;&#039; || boolean || false || Enables or disables the built-in anticheat (JFTSE exclusive).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;anticheat.port&#039;&#039;&#039; || int || 1337 || Port used for the anticheat system (JFTSE exclusive).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;dev.packets.handle&#039;&#039;&#039; || boolean || false || Handles developer/debug packets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;packets.id.translate.enabled&#039;&#039;&#039; || boolean || true || Enables translation of packet IDs.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;logging.packets.all.enabled&#039;&#039;&#039; || boolean || true || Enables logging of all network packets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;network.encryption.enabled&#039;&#039;&#039; || boolean || false || Enables network encryption for secure communication.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;password.encryption.enabled&#039;&#039;&#039; || boolean || false || Encrypts player passwords upon login.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;player.level.max&#039;&#039;&#039; || int || 60 || Maximum player level (JFTSE exclusive, levels above 65 require client modifications).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;command.room.mode.change.player.level&#039;&#039;&#039; || int || 60 || Minimum required level to change room mode via command.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.global.gold&#039;&#039;&#039; || int || 5 || Global gold bonus multiplier (Single Player).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.global.exp&#039;&#039;&#039; || int || 5 || Global experience bonus multiplier (Single Player).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.wongame.exp&#039;&#039;&#039; || double || 0.2 || Experience bonus for winning a game (Basic &amp;amp; Battle).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.wongame.gold&#039;&#039;&#039; || double || 0.2 || Gold bonus for winning a game (Basic &amp;amp; Battle).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;logging.packets.console-output.enabled&#039;&#039;&#039; || boolean || true || Enables logging of packets in console output.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;matchplay.guardian.hard.won.ranking-point.multiplier&#039;&#039;&#039; || double || 1.0 || Multiplier applied to ranking points in Guardian Hard mode.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.map.allow.snowmoon&#039;&#039;&#039; || boolean || false || Allows Snow Moon map in Guardian mode.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Notes:&#039;&#039;&#039;&lt;br /&gt;
* Config values marked as &#039;&#039;&#039;JFTSE exclusive&#039;&#039;&#039; are unique to this server implementation and may not be available in other Fantasy Tennis servers.&lt;br /&gt;
* Those values represent the development chosen defaults and does not represent values set within the [https://jftse.com JFTSE] hosted server.&lt;br /&gt;
&lt;br /&gt;
=== M_Scenarios ===&lt;br /&gt;
Each game mode has multiple scenarios. A scenario marked as &amp;quot;default&amp;quot; is always selected first. If multiple scenarios are active (`status = 1`), the default scenario is prioritized. If multiple active scenarios are marked as default, the first one retrieved from the database is chosen.&lt;br /&gt;
&lt;br /&gt;
Scenario types:&lt;br /&gt;
* &#039;&#039;&#039;Guardian Normal (non Boss Battle)&#039;&#039;&#039; → `GUARDIAN`&lt;br /&gt;
* &#039;&#039;&#039;Boss Guardian&#039;&#039;&#039; → `BOSS_BATTLE`&lt;br /&gt;
* &#039;&#039;&#039;Boss Guardian v2 (new mechanics defined through scripting)&#039;&#039;&#039; → `BOSS_BATTLE_V2`&lt;br /&gt;
&lt;br /&gt;
=== S_Maps ===&lt;br /&gt;
Defines all available maps and references client map id&#039;s.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! bossPlayTime !! breathTime !! description !! isBossStage !! map !! name !! playTime !! triggerBossTime !! useBreathTime&lt;br /&gt;
|-&lt;br /&gt;
| 1  || [NULL]  || 100  || [NULL]  || 0  || 0  || Rubycrab  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 2  || [NULL]  || 100  || [NULL]  || 0  || 1  || Emerald Beach  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 3  || 10  || 100  || [NULL]  || 0  || 2  || Twinkle Town  || 15  || 10  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 4  || [NULL]  || 100  || [NULL]  || 0  || 3  || The Aeolos  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 5  || [NULL]  || 100  || [NULL]  || 0  || 5  || Life Wood  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 6  || 5  || 100  || [NULL]  || 1  || 4  || Snow Moon  || [NULL]  || 8  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 7  || 5  || 100  || [NULL]  || 1  || 6  || Arena  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 8  || 5  || 100  || [NULL]  || 1  || 7  || Monslava  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 9  || 5  || 100  || [NULL]  || 1  || 8  || Monslava Blue  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 10  || 5  || 100  || [NULL]  || 1  || 9  || Devaberg  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 11  || 5  || 100  || [NULL]  || 1  || 10  || Atlantis  || 8  || 4  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 12  || 5  || 100  || [NULL]  || 1  || 11  || Temple  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 13  || [NULL]  || 100  || [NULL]  || 0  || 12  || Room of Shadow  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 14  || 5  || 100  || [NULL]  || 1  || 13  || Machine City  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 15  || 5  || 100  || [NULL]  || 1  || 14  || Dance Time  || [NULL]  || 3  || 0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Map_2_Scenarios ===&lt;br /&gt;
Establishes the link between maps (`S_Maps`) and scenarios (`M_Scenarios`). Without defining mappings in this table, the scenario system will not function correctly.&lt;br /&gt;
&lt;br /&gt;
This table uses a many-to-many relationship, ensuring that:&lt;br /&gt;
* Each scenario can be assigned to multiple maps.&lt;br /&gt;
* Each map can be associated with multiple scenarios.&lt;br /&gt;
&lt;br /&gt;
The relationship is defined as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Column !! Description&lt;br /&gt;
|-&lt;br /&gt;
| `scenario_id` || References `M_Scenarios.id`, linking the scenario.&lt;br /&gt;
|-&lt;br /&gt;
| `map_id` || References `S_Maps.id`, linking the map.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The mapping structure allows flexibility, ensuring that game modes can be dynamically adjusted based on the assigned scenarios.&lt;br /&gt;
&lt;br /&gt;
=== Guardian_2_Maps ===&lt;br /&gt;
Connects a guardian (or boss guardian) to a map with a specific scenario. Each scenario has a unique set of guardians available for matches.&lt;br /&gt;
&lt;br /&gt;
=== Skill_2_Guardians ===&lt;br /&gt;
Links skills to guardians. The connection is made through `Guardian_2_Maps`, associating skills with a guardian in a specific scenario and map. If no `guardian_2_maps` entry is set, it defaults to `btItem`, which is predefined for guardian types. `btItem` is shared among similar guardian types (e.g., Dokaro and related &amp;quot;ro&amp;quot; types).&lt;br /&gt;
&lt;br /&gt;
=== S_Guardian_Multipliers ===&lt;br /&gt;
Defines multipliers for Guardian Mode, applicable for gold or experience.&lt;br /&gt;
&lt;br /&gt;
These multipliers can be linked in the `S_Relationships` table.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! description !! multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 1  || default exp multiplier  || 1.0&lt;br /&gt;
|-&lt;br /&gt;
| 2  || default gold multiplier  || 1.0&lt;br /&gt;
|-&lt;br /&gt;
| 3  || exp x3  || 3.0&lt;br /&gt;
|-&lt;br /&gt;
| 4  || exp x4  || 4.0&lt;br /&gt;
|-&lt;br /&gt;
| 5  || exp x5  || 5.0&lt;br /&gt;
|-&lt;br /&gt;
| 6  || exp x6  || 6.0&lt;br /&gt;
|-&lt;br /&gt;
| 7  || exp x7  || 7.0&lt;br /&gt;
|-&lt;br /&gt;
| 8  || gold x7  || 7.0&lt;br /&gt;
|-&lt;br /&gt;
| 9  || gold x5  || 5.0&lt;br /&gt;
|-&lt;br /&gt;
| 10  || gold x3  || 3.5&lt;br /&gt;
|-&lt;br /&gt;
| 11  || exp x3  || 3.5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== S_Relationships ===&lt;br /&gt;
Defines connections between various entities within the game, enabling precise control over item drops, multipliers, and other gameplay mechanics. Relationships are established using two key fields:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;`id_f`&#039;&#039;&#039; (`id_from`): Represents the originating entity of the relationship, such as a Product or Multiplier.&lt;br /&gt;
* &#039;&#039;&#039;`id_t`&#039;&#039;&#039; (`id_to`): Represents the target entity of the relationship, such as a Guardian, Boss Guardian, or Map.&lt;br /&gt;
&lt;br /&gt;
When linking products specifically to guardians, the &#039;&#039;&#039;`productIndex`&#039;&#039;&#039; from the product definition is used for &#039;&#039;&#039;`id_f`&#039;&#039;&#039;, and the &#039;&#039;&#039;`guardian_2_maps.id`&#039;&#039;&#039; (not the direct Guardian or Boss Guardian ID) is used for &#039;&#039;&#039;`id_t`&#039;&#039;&#039;. Each relationship is further described by its type and role, defining how it influences gameplay.&lt;br /&gt;
&lt;br /&gt;
Relationships can be individually toggled on or off via the &#039;&#039;&#039;`status`&#039;&#039;&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== Advanced Item Drop Control (Role ID 1) ====&lt;br /&gt;
Additionally, enhanced item drop control is available specifically for the &#039;&#039;&#039;Item Drop&#039;&#039;&#039; role (role ID &#039;&#039;&#039;1&#039;&#039;&#039;):&lt;br /&gt;
&lt;br /&gt;
* Default item weight is &#039;&#039;&#039;20.0&#039;&#039;&#039; when no specific weight is provided.&lt;br /&gt;
* A fixed weight of &#039;&#039;&#039;15.0&#039;&#039;&#039; is used to determine scenarios where no item is awarded (displayed as an &amp;quot;X&amp;quot;).&lt;br /&gt;
* Drop exclusivity can be defined specifically for:&lt;br /&gt;
** &#039;&#039;&#039;`forHardMode`&#039;&#039;&#039;: Drops exclusive to [[Guardian Hard Mode]].&lt;br /&gt;
** &#039;&#039;&#039;`forRandomMode`&#039;&#039;&#039;: Drops exclusive to [[Guardian Random Mode]].&lt;br /&gt;
* The &#039;&#039;&#039;`levelReq`&#039;&#039;&#039; field, currently unused, is reserved for future use to restrict drops based on player levels in a planned progression system.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Quantity Determination for Item Drops:&#039;&#039;&#039;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Condition !! Quantity Result&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; is set, &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; not set || Exact quantity from &#039;&#039;&#039;`qty`&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; not set, &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; set || Random quantity between &#039;&#039;&#039;`qtyMin`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMax`&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| All quantity fields &#039;&#039;&#039;NULL&#039;&#039;&#039; || Defaults based on item type:&lt;br /&gt;
* &#039;&#039;&#039;Material&#039;&#039;&#039;: Min 1 / Max 3&lt;br /&gt;
* &#039;&#039;&#039;Quick&#039;&#039;&#039;: Min 5 / Max 100&lt;br /&gt;
* &#039;&#039;&#039;Parts &amp;amp; Others&#039;&#039;&#039;: Min 1 / Max 1&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; both set || Random quantity between &#039;&#039;&#039;`qtyMin`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMax`&#039;&#039;&#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== S_Relationship_Types ===&lt;br /&gt;
Defines supported relationship types, describing what entities can be linked together:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Product to Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Product to Boss Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 3  || Product to Map&lt;br /&gt;
|-&lt;br /&gt;
| 4  || Multiplier to Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 5  || Multiplier to Boss Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 6  || Multiplier to Map&lt;br /&gt;
|-&lt;br /&gt;
| 7  || Product to Map (Guardian Mode)&lt;br /&gt;
|-&lt;br /&gt;
| 8  || Product to Sell Price&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== S_Relationship_Roles ===&lt;br /&gt;
Defines the supported roles that relationships can take, describing their in game functionality:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Item Drop&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Exp Multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 3  || Gold Multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 4  || Item Sell Price&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== K_Status ===&lt;br /&gt;
Defines the different status types used in game.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Active&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Inactive&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Notes &amp;amp; Additional Information ===&lt;br /&gt;
* The database schema is subject to updates as new mechanics and features are introduced.&lt;br /&gt;
* Some tables may have additional columns not explicitly documented if they are not required for core functionality.&lt;br /&gt;
* Unused fields, such as `levelReq` in `S_Relationships`, are placeholders for future game progression updates.&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Database_Schema_%26_Cheatsheet&amp;diff=67</id>
		<title>Database Schema &amp; Cheatsheet</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Database_Schema_%26_Cheatsheet&amp;diff=67"/>
		<updated>2025-02-24T20:03:33Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Database Schema &amp;amp; Cheatsheet =&lt;br /&gt;
This page provides an overview of the database structure used in Fantasy Tennis, showing the key tables, their columns, and relationships. The schema diagram below visually represents how different entities connect within the system.&lt;br /&gt;
&lt;br /&gt;
== Schema ==&lt;br /&gt;
[[File:Db-entities.svg|200px|thumb|center|alt=Database Schema|Database Schema]]&lt;br /&gt;
&lt;br /&gt;
== Tables of Interest ==&lt;br /&gt;
Tables might worth looking into as they are important on setting up your own Fantasy Tennis Server Configuration inside the Database.&lt;br /&gt;
&lt;br /&gt;
=== Config ===&lt;br /&gt;
The `config` table is used to store various server wide settings that control gameplay mechanics, security settings, logging behavior, and feature toggles. Each configuration entry consists of:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;name&#039;&#039;&#039; – The unique identifier of the config setting.&lt;br /&gt;
* &#039;&#039;&#039;description&#039;&#039;&#039; – A short explanation of what the setting controls (may be null).&lt;br /&gt;
* &#039;&#039;&#039;value&#039;&#039;&#039; – The actual configured value.&lt;br /&gt;
* &#039;&#039;&#039;type&#039;&#039;&#039; – The data type of the value (e.g., boolean, int, double, string).&lt;br /&gt;
&lt;br /&gt;
Administrators can modify these values to tweak server behavior, enable or disable features, and adjust game mechanics dynamically.&lt;br /&gt;
&lt;br /&gt;
Below is a list of currently supported configuration values along with their default settings.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Name !! Type !! Default Value !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;anticheat.enabled&#039;&#039;&#039; || boolean || false || Enables or disables the built-in anticheat (JFTSE exclusive).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;anticheat.port&#039;&#039;&#039; || int || 1337 || Port used for the anticheat system (JFTSE exclusive).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;dev.packets.handle&#039;&#039;&#039; || boolean || false || Handles developer/debug packets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;packets.id.translate.enabled&#039;&#039;&#039; || boolean || true || Enables translation of packet IDs.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;logging.packets.all.enabled&#039;&#039;&#039; || boolean || true || Enables logging of all network packets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;network.encryption.enabled&#039;&#039;&#039; || boolean || false || Enables network encryption for secure communication.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;password.encryption.enabled&#039;&#039;&#039; || boolean || false || Encrypts player passwords upon login.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;player.level.max&#039;&#039;&#039; || int || 60 || Maximum player level (JFTSE exclusive, levels above 65 require client modifications).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;command.room.mode.change.player.level&#039;&#039;&#039; || int || 60 || Minimum required level to change room mode via command.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.global.gold&#039;&#039;&#039; || int || 5 || Global gold bonus multiplier (Single Player).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.global.exp&#039;&#039;&#039; || int || 5 || Global experience bonus multiplier (Single Player).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.wongame.exp&#039;&#039;&#039; || double || 0.2 || Experience bonus for winning a game (Basic &amp;amp; Battle).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.wongame.gold&#039;&#039;&#039; || double || 0.2 || Gold bonus for winning a game (Basic &amp;amp; Battle).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;logging.packets.console-output.enabled&#039;&#039;&#039; || boolean || true || Enables logging of packets in console output.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;matchplay.guardian.hard.won.ranking-point.multiplier&#039;&#039;&#039; || double || 1.0 || Multiplier applied to ranking points in Guardian Hard mode.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.map.allow.snowmoon&#039;&#039;&#039; || boolean || false || Allows Snow Moon map in Guardian mode.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Notes:&#039;&#039;&#039;&lt;br /&gt;
* Config values marked as &#039;&#039;&#039;JFTSE exclusive&#039;&#039;&#039; are unique to this server implementation and may not be available in other Fantasy Tennis servers.&lt;br /&gt;
* Those values represent the development chosen defaults and does not represent values set within the [https://jftse.com JFTSE] hosted server.&lt;br /&gt;
&lt;br /&gt;
=== M_Scenarios ===&lt;br /&gt;
Each game mode has multiple scenarios. A scenario marked as &amp;quot;default&amp;quot; is always selected first. If multiple scenarios are active (`status = 1`), the default scenario is prioritized. If multiple active scenarios are marked as default, the first one retrieved from the database is chosen.&lt;br /&gt;
&lt;br /&gt;
Scenario types:&lt;br /&gt;
* &#039;&#039;&#039;Guardian Normal (non Boss Battle)&#039;&#039;&#039; → `GUARDIAN`&lt;br /&gt;
* &#039;&#039;&#039;Boss Guardian&#039;&#039;&#039; → `BOSS_BATTLE`&lt;br /&gt;
* &#039;&#039;&#039;Boss Guardian v2 (new mechanics defined through scripting)&#039;&#039;&#039; → `BOSS_BATTLE_V2`&lt;br /&gt;
&lt;br /&gt;
=== S_Maps ===&lt;br /&gt;
Defines all available maps and references client map id&#039;s.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! bossPlayTime !! breathTime !! description !! isBossStage !! map !! name !! playTime !! triggerBossTime !! useBreathTime&lt;br /&gt;
|-&lt;br /&gt;
| 1  || [NULL]  || 100  || [NULL]  || 0  || 0  || Rubycrab  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 2  || [NULL]  || 100  || [NULL]  || 0  || 1  || Emerald Beach  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 3  || 10  || 100  || [NULL]  || 0  || 2  || Twinkle Town  || 15  || 10  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 4  || [NULL]  || 100  || [NULL]  || 0  || 3  || The Aeolos  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 5  || [NULL]  || 100  || [NULL]  || 0  || 5  || Life Wood  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 6  || 5  || 100  || [NULL]  || 1  || 4  || Snow Moon  || [NULL]  || 8  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 7  || 5  || 100  || [NULL]  || 1  || 6  || Arena  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 8  || 5  || 100  || [NULL]  || 1  || 7  || Monslava  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 9  || 5  || 100  || [NULL]  || 1  || 8  || Monslava Blue  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 10  || 5  || 100  || [NULL]  || 1  || 9  || Devaberg  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 11  || 5  || 100  || [NULL]  || 1  || 10  || Atlantis  || 8  || 4  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 12  || 5  || 100  || [NULL]  || 1  || 11  || Temple  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 13  || [NULL]  || 100  || [NULL]  || 0  || 12  || Room of Shadow  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 14  || 5  || 100  || [NULL]  || 1  || 13  || Machine City  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 15  || 5  || 100  || [NULL]  || 1  || 14  || Dance Time  || [NULL]  || 3  || 0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Map_2_Scenarios ===&lt;br /&gt;
Establishes the link between maps (`S_Maps`) and scenarios (`M_Scenarios`). Without defining mappings in this table, the scenario system will not function correctly.&lt;br /&gt;
&lt;br /&gt;
This table uses a many-to-many relationship, ensuring that:&lt;br /&gt;
* Each scenario can be assigned to multiple maps.&lt;br /&gt;
* Each map can be associated with multiple scenarios.&lt;br /&gt;
&lt;br /&gt;
The relationship is defined as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Column !! Description&lt;br /&gt;
|-&lt;br /&gt;
| `scenario_id` || References `M_Scenarios.id`, linking the scenario.&lt;br /&gt;
|-&lt;br /&gt;
| `map_id` || References `S_Maps.id`, linking the map.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The mapping structure allows flexibility, ensuring that game modes can be dynamically adjusted based on the assigned scenarios.&lt;br /&gt;
&lt;br /&gt;
=== Guardian_2_Maps ===&lt;br /&gt;
Connects a guardian (or boss guardian) to a map with a specific scenario. Each scenario has a unique set of guardians available for matches.&lt;br /&gt;
&lt;br /&gt;
=== Skill_2_Guardians ===&lt;br /&gt;
Links skills to guardians. The connection is made through `Guardian_2_Maps`, associating skills with a guardian in a specific scenario and map. If no `guardian_2_maps` entry is set, it defaults to `btItem`, which is predefined for guardian types. `btItem` is shared among similar guardian types (e.g., Dokaro and related &amp;quot;ro&amp;quot; types).&lt;br /&gt;
&lt;br /&gt;
=== S_Guardian_Multipliers ===&lt;br /&gt;
Defines multipliers for Guardian Mode, applicable for gold or experience.&lt;br /&gt;
&lt;br /&gt;
These multipliers can be linked in the `S_Relationships` table.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! description !! multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 1  || default exp multiplier  || 1.0&lt;br /&gt;
|-&lt;br /&gt;
| 2  || default gold multiplier  || 1.0&lt;br /&gt;
|-&lt;br /&gt;
| 3  || exp x3  || 3.0&lt;br /&gt;
|-&lt;br /&gt;
| 4  || exp x4  || 4.0&lt;br /&gt;
|-&lt;br /&gt;
| 5  || exp x5  || 5.0&lt;br /&gt;
|-&lt;br /&gt;
| 6  || exp x6  || 6.0&lt;br /&gt;
|-&lt;br /&gt;
| 7  || exp x7  || 7.0&lt;br /&gt;
|-&lt;br /&gt;
| 8  || gold x7  || 7.0&lt;br /&gt;
|-&lt;br /&gt;
| 9  || gold x5  || 5.0&lt;br /&gt;
|-&lt;br /&gt;
| 10  || gold x3  || 3.5&lt;br /&gt;
|-&lt;br /&gt;
| 11  || exp x3  || 3.5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== S_Relationships ===&lt;br /&gt;
Defines connections between various entities within the game, enabling precise control over item drops, multipliers, and other gameplay mechanics. Relationships are established using two key fields:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;`id_f`&#039;&#039;&#039; (`id_from`): Represents the originating entity of the relationship, such as a Product or Multiplier.&lt;br /&gt;
* &#039;&#039;&#039;`id_t`&#039;&#039;&#039; (`id_to`): Represents the target entity of the relationship, such as a Guardian, Boss Guardian, or Map.&lt;br /&gt;
&lt;br /&gt;
When linking products specifically to guardians, the &#039;&#039;&#039;`productIndex`&#039;&#039;&#039; from the product definition is used for &#039;&#039;&#039;`id_f`&#039;&#039;&#039;, and the &#039;&#039;&#039;`guardian_2_maps.id`&#039;&#039;&#039; (not the direct Guardian or Boss Guardian ID) is used for &#039;&#039;&#039;`id_t`&#039;&#039;&#039;. Each relationship is further described by its type and role, defining how it influences gameplay.&lt;br /&gt;
&lt;br /&gt;
Relationships can be individually toggled on or off via the &#039;&#039;&#039;`status`&#039;&#039;&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== Advanced Item Drop Control (Role ID 1) ====&lt;br /&gt;
Additionally, enhanced item drop control is available specifically for the &#039;&#039;&#039;Item Drop&#039;&#039;&#039; role (role ID &#039;&#039;&#039;1&#039;&#039;&#039;):&lt;br /&gt;
&lt;br /&gt;
* Default item weight is &#039;&#039;&#039;20.0&#039;&#039;&#039; when no specific weight is provided.&lt;br /&gt;
* A fixed weight of &#039;&#039;&#039;15.0&#039;&#039;&#039; is used to determine scenarios where no item is awarded (displayed as an &amp;quot;X&amp;quot;).&lt;br /&gt;
* Drop exclusivity can be defined specifically for:&lt;br /&gt;
** &#039;&#039;&#039;`forHardMode`&#039;&#039;&#039;: Drops exclusive to Guardian Hard Mode.&lt;br /&gt;
** &#039;&#039;&#039;`forRandomMode`&#039;&#039;&#039;: Drops exclusive to Guardian Random Mode.&lt;br /&gt;
* The &#039;&#039;&#039;`levelReq`&#039;&#039;&#039; field, currently unused, is reserved for future use to restrict drops based on player levels in a planned progression system.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Quantity Determination for Item Drops:&#039;&#039;&#039;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Condition !! Quantity Result&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; is set, &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; not set || Exact quantity from &#039;&#039;&#039;`qty`&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; not set, &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; set || Random quantity between &#039;&#039;&#039;`qtyMin`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMax`&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| All quantity fields &#039;&#039;&#039;NULL&#039;&#039;&#039; || Defaults based on item type:&lt;br /&gt;
* &#039;&#039;&#039;Material&#039;&#039;&#039;: Min 1 / Max 3&lt;br /&gt;
* &#039;&#039;&#039;Quick&#039;&#039;&#039;: Min 5 / Max 100&lt;br /&gt;
* &#039;&#039;&#039;Parts &amp;amp; Others&#039;&#039;&#039;: Min 1 / Max 1&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; both set || Random quantity between &#039;&#039;&#039;`qtyMin`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMax`&#039;&#039;&#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== S_Relationship_Types ===&lt;br /&gt;
Defines supported relationship types, describing what entities can be linked together:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Product to Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Product to Boss Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 3  || Product to Map&lt;br /&gt;
|-&lt;br /&gt;
| 4  || Multiplier to Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 5  || Multiplier to Boss Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 6  || Multiplier to Map&lt;br /&gt;
|-&lt;br /&gt;
| 7  || Product to Map (Guardian Mode)&lt;br /&gt;
|-&lt;br /&gt;
| 8  || Product to Sell Price&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== S_Relationship_Roles ===&lt;br /&gt;
Defines the supported roles that relationships can take, describing their in game functionality:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Item Drop&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Exp Multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 3  || Gold Multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 4  || Item Sell Price&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== K_Status ===&lt;br /&gt;
Defines the different status types used in game.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Active&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Inactive&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Notes &amp;amp; Additional Information ===&lt;br /&gt;
* The database schema is subject to updates as new mechanics and features are introduced.&lt;br /&gt;
* Some tables may have additional columns not explicitly documented if they are not required for core functionality.&lt;br /&gt;
* Unused fields, such as `levelReq` in `S_Relationships`, are placeholders for future game progression updates.&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Database_Schema_%26_Cheatsheet&amp;diff=66</id>
		<title>Database Schema &amp; Cheatsheet</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Database_Schema_%26_Cheatsheet&amp;diff=66"/>
		<updated>2025-02-24T19:57:41Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Database Schema &amp;amp; Cheatsheet =&lt;br /&gt;
This page provides an overview of the database structure used in Fantasy Tennis, showing the key tables, their columns, and relationships. The schema diagram below visually represents how different entities connect within the system.&lt;br /&gt;
&lt;br /&gt;
== Schema ==&lt;br /&gt;
[[File:Db-entities.svg|200px|thumb|center|alt=Database Schema|Database Schema]]&lt;br /&gt;
&lt;br /&gt;
== Tables of Interest ==&lt;br /&gt;
Tables might worth looking into as they are important on setting up your own Fantasy Tennis Server Configuration inside the Database.&lt;br /&gt;
&lt;br /&gt;
=== Config ===&lt;br /&gt;
The `config` table is used to store various server wide settings that control gameplay mechanics, security settings, logging behavior, and feature toggles. Each configuration entry consists of:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;name&#039;&#039;&#039; – The unique identifier of the config setting.&lt;br /&gt;
* &#039;&#039;&#039;description&#039;&#039;&#039; – A short explanation of what the setting controls (may be null).&lt;br /&gt;
* &#039;&#039;&#039;value&#039;&#039;&#039; – The actual configured value.&lt;br /&gt;
* &#039;&#039;&#039;type&#039;&#039;&#039; – The data type of the value (e.g., boolean, int, double, string).&lt;br /&gt;
&lt;br /&gt;
Administrators can modify these values to tweak server behavior, enable or disable features, and adjust game mechanics dynamically.&lt;br /&gt;
&lt;br /&gt;
Below is a list of currently supported configuration values along with their default settings.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Name !! Type !! Default Value !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;anticheat.enabled&#039;&#039;&#039; || boolean || false || Enables or disables the built-in anticheat (JFTSE exclusive).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;anticheat.port&#039;&#039;&#039; || int || 1337 || Port used for the anticheat system (JFTSE exclusive).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;dev.packets.handle&#039;&#039;&#039; || boolean || false || Handles developer/debug packets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;packets.id.translate.enabled&#039;&#039;&#039; || boolean || true || Enables translation of packet IDs.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;logging.packets.all.enabled&#039;&#039;&#039; || boolean || true || Enables logging of all network packets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;network.encryption.enabled&#039;&#039;&#039; || boolean || false || Enables network encryption for secure communication.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;password.encryption.enabled&#039;&#039;&#039; || boolean || false || Encrypts player passwords upon login.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;player.level.max&#039;&#039;&#039; || int || 60 || Maximum player level (JFTSE exclusive, levels above 65 require client modifications).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;command.room.mode.change.player.level&#039;&#039;&#039; || int || 60 || Minimum required level to change room mode via command.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.global.gold&#039;&#039;&#039; || int || 5 || Global gold bonus multiplier (Single Player).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.global.exp&#039;&#039;&#039; || int || 5 || Global experience bonus multiplier (Single Player).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.wongame.exp&#039;&#039;&#039; || double || 0.2 || Experience bonus for winning a game (Basic &amp;amp; Battle).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.wongame.gold&#039;&#039;&#039; || double || 0.2 || Gold bonus for winning a game (Basic &amp;amp; Battle).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;logging.packets.console-output.enabled&#039;&#039;&#039; || boolean || true || Enables logging of packets in console output.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;matchplay.guardian.hard.won.ranking-point.multiplier&#039;&#039;&#039; || double || 1.0 || Multiplier applied to ranking points in Guardian Hard mode.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.map.allow.snowmoon&#039;&#039;&#039; || boolean || false || Allows Snow Moon map in Guardian mode.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Notes:&#039;&#039;&#039;&lt;br /&gt;
* Config values marked as &#039;&#039;&#039;JFTSE exclusive&#039;&#039;&#039; are unique to this server implementation and may not be available in other Fantasy Tennis servers.&lt;br /&gt;
* Those values represent the development chosen defaults and does not represent values set within the [https://jftse.com JFTSE] hosted server.&lt;br /&gt;
&lt;br /&gt;
=== M_Scenarios ===&lt;br /&gt;
Each game mode has multiple scenarios. A scenario marked as &amp;quot;default&amp;quot; is always selected first. If multiple scenarios are active (`status = 1`), the default scenario is prioritized. If multiple active scenarios are marked as default, the first one retrieved from the database is chosen.&lt;br /&gt;
&lt;br /&gt;
Scenario types:&lt;br /&gt;
* &#039;&#039;&#039;Guardian Normal (non Boss Battle)&#039;&#039;&#039; → `GUARDIAN`&lt;br /&gt;
* &#039;&#039;&#039;Boss Guardian&#039;&#039;&#039; → `BOSS_BATTLE`&lt;br /&gt;
* &#039;&#039;&#039;Boss Guardian v2 (new mechanics defined through scripting)&#039;&#039;&#039; → `BOSS_BATTLE_V2`&lt;br /&gt;
&lt;br /&gt;
=== S_Maps ===&lt;br /&gt;
Defines all available maps and references client map id&#039;s.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! bossPlayTime !! breathTime !! description !! isBossStage !! map !! name !! playTime !! triggerBossTime !! useBreathTime&lt;br /&gt;
|-&lt;br /&gt;
| 1  || [NULL]  || 100  || [NULL]  || 0  || 0  || Rubycrab  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 2  || [NULL]  || 100  || [NULL]  || 0  || 1  || Emerald Beach  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 3  || 10  || 100  || [NULL]  || 0  || 2  || Twinkle Town  || 15  || 10  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 4  || [NULL]  || 100  || [NULL]  || 0  || 3  || The Aeolos  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 5  || [NULL]  || 100  || [NULL]  || 0  || 5  || Life Wood  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 6  || 5  || 100  || [NULL]  || 1  || 4  || Snow Moon  || [NULL]  || 8  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 7  || 5  || 100  || [NULL]  || 1  || 6  || Arena  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 8  || 5  || 100  || [NULL]  || 1  || 7  || Monslava  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 9  || 5  || 100  || [NULL]  || 1  || 8  || Monslava Blue  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 10  || 5  || 100  || [NULL]  || 1  || 9  || Devaberg  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 11  || 5  || 100  || [NULL]  || 1  || 10  || Atlantis  || 8  || 4  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 12  || 5  || 100  || [NULL]  || 1  || 11  || Temple  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 13  || [NULL]  || 100  || [NULL]  || 0  || 12  || Room of Shadow  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 14  || 5  || 100  || [NULL]  || 1  || 13  || Machine City  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 15  || 5  || 100  || [NULL]  || 1  || 14  || Dance Time  || [NULL]  || 3  || 0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Guardian_2_Maps ===&lt;br /&gt;
Connects a guardian (or boss guardian) to a map with a specific scenario. Each scenario has a unique set of guardians available for matches.&lt;br /&gt;
&lt;br /&gt;
=== Skill_2_Guardians ===&lt;br /&gt;
Links skills to guardians. The connection is made through `Guardian_2_Maps`, associating skills with a guardian in a specific scenario and map. If no `guardian_2_maps` entry is set, it defaults to `btItem`, which is predefined for guardian types. `btItem` is shared among similar guardian types (e.g., Dokaro and related &amp;quot;ro&amp;quot; types).&lt;br /&gt;
&lt;br /&gt;
=== S_Guardian_Multipliers ===&lt;br /&gt;
Defines multipliers for Guardian Mode, applicable for gold or experience.&lt;br /&gt;
&lt;br /&gt;
These multipliers can be linked in the `S_Relationships` table.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! description !! multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 1  || default exp multiplier  || 1.0&lt;br /&gt;
|-&lt;br /&gt;
| 2  || default gold multiplier  || 1.0&lt;br /&gt;
|-&lt;br /&gt;
| 3  || exp x3  || 3.0&lt;br /&gt;
|-&lt;br /&gt;
| 4  || exp x4  || 4.0&lt;br /&gt;
|-&lt;br /&gt;
| 5  || exp x5  || 5.0&lt;br /&gt;
|-&lt;br /&gt;
| 6  || exp x6  || 6.0&lt;br /&gt;
|-&lt;br /&gt;
| 7  || exp x7  || 7.0&lt;br /&gt;
|-&lt;br /&gt;
| 8  || gold x7  || 7.0&lt;br /&gt;
|-&lt;br /&gt;
| 9  || gold x5  || 5.0&lt;br /&gt;
|-&lt;br /&gt;
| 10  || gold x3  || 3.5&lt;br /&gt;
|-&lt;br /&gt;
| 11  || exp x3  || 3.5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== S_Relationships ===&lt;br /&gt;
Defines connections between various entities within the game, enabling precise control over item drops, multipliers, and other gameplay mechanics. Relationships are established using two key fields:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;`id_f`&#039;&#039;&#039; (`id_from`): Represents the originating entity of the relationship, such as a Product or Multiplier.&lt;br /&gt;
* &#039;&#039;&#039;`id_t`&#039;&#039;&#039; (`id_to`): Represents the target entity of the relationship, such as a Guardian, Boss Guardian, or Map.&lt;br /&gt;
&lt;br /&gt;
When linking products specifically to guardians, the &#039;&#039;&#039;`productIndex`&#039;&#039;&#039; from the product definition is used for &#039;&#039;&#039;`id_f`&#039;&#039;&#039;, and the &#039;&#039;&#039;`guardian_2_maps.id`&#039;&#039;&#039; (not the direct Guardian or Boss Guardian ID) is used for &#039;&#039;&#039;`id_t`&#039;&#039;&#039;. Each relationship is further described by its type and role, defining how it influences gameplay.&lt;br /&gt;
&lt;br /&gt;
Relationships can be individually toggled on or off via the &#039;&#039;&#039;`status`&#039;&#039;&#039; field.&lt;br /&gt;
&lt;br /&gt;
==== Advanced Item Drop Control (Role ID 1) ====&lt;br /&gt;
Additionally, enhanced item drop control is available specifically for the &#039;&#039;&#039;Item Drop&#039;&#039;&#039; role (role ID &#039;&#039;&#039;1&#039;&#039;&#039;):&lt;br /&gt;
&lt;br /&gt;
* Default item weight is &#039;&#039;&#039;20.0&#039;&#039;&#039; when no specific weight is provided.&lt;br /&gt;
* A fixed weight of &#039;&#039;&#039;15.0&#039;&#039;&#039; is used to determine scenarios where no item is awarded (displayed as an &amp;quot;X&amp;quot;).&lt;br /&gt;
* Drop exclusivity can be defined specifically for:&lt;br /&gt;
** &#039;&#039;&#039;`forHardMode`&#039;&#039;&#039;: Drops exclusive to Guardian Hard Mode.&lt;br /&gt;
** &#039;&#039;&#039;`forRandomMode`&#039;&#039;&#039;: Drops exclusive to Guardian Random Mode.&lt;br /&gt;
* The &#039;&#039;&#039;`levelReq`&#039;&#039;&#039; field, currently unused, is reserved for future use to restrict drops based on player levels in a planned progression system.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Quantity Determination for Item Drops:&#039;&#039;&#039;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Condition !! Quantity Result&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; is set, &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; not set || Exact quantity from &#039;&#039;&#039;`qty`&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; not set, &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; set || Random quantity between &#039;&#039;&#039;`qtyMin`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMax`&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| All quantity fields &#039;&#039;&#039;NULL&#039;&#039;&#039; || Defaults based on item type:&lt;br /&gt;
* &#039;&#039;&#039;Material&#039;&#039;&#039;: Min 1 / Max 3&lt;br /&gt;
* &#039;&#039;&#039;Quick&#039;&#039;&#039;: Min 5 / Max 100&lt;br /&gt;
* &#039;&#039;&#039;Parts &amp;amp; Others&#039;&#039;&#039;: Min 1 / Max 1&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;`qty`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMin`/`qtyMax`&#039;&#039;&#039; both set || Random quantity between &#039;&#039;&#039;`qtyMin`&#039;&#039;&#039; and &#039;&#039;&#039;`qtyMax`&#039;&#039;&#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== S_Relationship_Types ===&lt;br /&gt;
Defines supported relationship types, describing what entities can be linked together:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Product to Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Product to Boss Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 3  || Product to Map&lt;br /&gt;
|-&lt;br /&gt;
| 4  || Multiplier to Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 5  || Multiplier to Boss Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 6  || Multiplier to Map&lt;br /&gt;
|-&lt;br /&gt;
| 7  || Product to Map (Guardian Mode)&lt;br /&gt;
|-&lt;br /&gt;
| 8  || Product to Sell Price&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== S_Relationship_Roles ===&lt;br /&gt;
Defines the supported roles that relationships can take, describing their in game functionality:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Item Drop&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Exp Multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 3  || Gold Multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 4  || Item Sell Price&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== K_Status ===&lt;br /&gt;
Defines the different status types used in game.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Active&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Inactive&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Notes &amp;amp; Additional Information ===&lt;br /&gt;
* The database schema is subject to updates as new mechanics and features are introduced.&lt;br /&gt;
* Some tables may have additional columns not explicitly documented if they are not required for core functionality.&lt;br /&gt;
* Unused fields, such as `levelReq` in `S_Relationships`, are placeholders for future game progression updates.&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Database_Schema_%26_Cheatsheet&amp;diff=65</id>
		<title>Database Schema &amp; Cheatsheet</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Database_Schema_%26_Cheatsheet&amp;diff=65"/>
		<updated>2025-02-24T19:45:07Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Database Schema &amp;amp; Cheatsheet =&lt;br /&gt;
This page provides an overview of the database structure used in Fantasy Tennis, showing the key tables, their columns, and relationships. The schema diagram below visually represents how different entities connect within the system.&lt;br /&gt;
&lt;br /&gt;
== Schema ==&lt;br /&gt;
[[File:Db-entities.svg|200px|thumb|center|alt=Database Schema|Database Schema]]&lt;br /&gt;
&lt;br /&gt;
== Tables of Interest ==&lt;br /&gt;
Tables might worth looking into as they are important on setting up your own Fantasy Tennis Server Configuration inside the Database.&lt;br /&gt;
&lt;br /&gt;
=== Config ===&lt;br /&gt;
The `config` table is used to store various server wide settings that control gameplay mechanics, security settings, logging behavior, and feature toggles. Each configuration entry consists of:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;name&#039;&#039;&#039; – The unique identifier of the config setting.&lt;br /&gt;
* &#039;&#039;&#039;description&#039;&#039;&#039; – A short explanation of what the setting controls (may be null).&lt;br /&gt;
* &#039;&#039;&#039;value&#039;&#039;&#039; – The actual configured value.&lt;br /&gt;
* &#039;&#039;&#039;type&#039;&#039;&#039; – The data type of the value (e.g., boolean, int, double, string).&lt;br /&gt;
&lt;br /&gt;
Administrators can modify these values to tweak server behavior, enable or disable features, and adjust game mechanics dynamically.&lt;br /&gt;
&lt;br /&gt;
Below is a list of currently supported configuration values along with their default settings.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Name !! Type !! Default Value !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;anticheat.enabled&#039;&#039;&#039; || boolean || false || Enables or disables the built-in anticheat (JFTSE exclusive).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;anticheat.port&#039;&#039;&#039; || int || 1337 || Port used for the anticheat system (JFTSE exclusive).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;dev.packets.handle&#039;&#039;&#039; || boolean || false || Handles developer/debug packets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;packets.id.translate.enabled&#039;&#039;&#039; || boolean || true || Enables translation of packet IDs.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;logging.packets.all.enabled&#039;&#039;&#039; || boolean || true || Enables logging of all network packets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;network.encryption.enabled&#039;&#039;&#039; || boolean || false || Enables network encryption for secure communication.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;password.encryption.enabled&#039;&#039;&#039; || boolean || false || Encrypts player passwords upon login.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;player.level.max&#039;&#039;&#039; || int || 60 || Maximum player level (JFTSE exclusive, levels above 65 require client modifications).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;command.room.mode.change.player.level&#039;&#039;&#039; || int || 60 || Minimum required level to change room mode via command.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.global.gold&#039;&#039;&#039; || int || 5 || Global gold bonus multiplier (Single Player).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.global.exp&#039;&#039;&#039; || int || 5 || Global experience bonus multiplier (Single Player).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.wongame.exp&#039;&#039;&#039; || double || 0.2 || Experience bonus for winning a game (Basic &amp;amp; Battle).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.bonus.wongame.gold&#039;&#039;&#039; || double || 0.2 || Gold bonus for winning a game (Basic &amp;amp; Battle).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;logging.packets.console-output.enabled&#039;&#039;&#039; || boolean || true || Enables logging of packets in console output.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;matchplay.guardian.hard.won.ranking-point.multiplier&#039;&#039;&#039; || double || 1.0 || Multiplier applied to ranking points in Guardian Hard mode.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;game.map.allow.snowmoon&#039;&#039;&#039; || boolean || false || Allows Snow Moon map in Guardian mode.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Notes:&#039;&#039;&#039;&lt;br /&gt;
* Config values marked as &#039;&#039;&#039;JFTSE exclusive&#039;&#039;&#039; are unique to this server implementation and may not be available in other Fantasy Tennis servers.&lt;br /&gt;
* Those values represent the development chosen defaults and does not represent values set within the [https://jftse.com JFTSE] hosted server.&lt;br /&gt;
&lt;br /&gt;
=== M_Scenarios ===&lt;br /&gt;
Each game mode has multiple scenarios. A scenario marked as &amp;quot;default&amp;quot; is always selected first. If multiple scenarios are active (`status = 1`), the default scenario is prioritized. If multiple active scenarios are marked as default, the first one retrieved from the database is chosen.&lt;br /&gt;
&lt;br /&gt;
Scenario types:&lt;br /&gt;
* &#039;&#039;&#039;Guardian Normal (non Boss Battle)&#039;&#039;&#039; → `GUARDIAN`&lt;br /&gt;
* &#039;&#039;&#039;Boss Guardian&#039;&#039;&#039; → `BOSS_BATTLE`&lt;br /&gt;
* &#039;&#039;&#039;Boss Guardian v2 (new mechanics defined through scripting)&#039;&#039;&#039; → `BOSS_BATTLE_V2`&lt;br /&gt;
&lt;br /&gt;
=== S_Maps ===&lt;br /&gt;
Defines all available maps and references client map id&#039;s.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! bossPlayTime !! breathTime !! description !! isBossStage !! map !! name !! playTime !! triggerBossTime !! useBreathTime&lt;br /&gt;
|-&lt;br /&gt;
| 1  || [NULL]  || 100  || [NULL]  || 0  || 0  || Rubycrab  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 2  || [NULL]  || 100  || [NULL]  || 0  || 1  || Emerald Beach  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 3  || 10  || 100  || [NULL]  || 0  || 2  || Twinkle Town  || 15  || 10  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 4  || [NULL]  || 100  || [NULL]  || 0  || 3  || The Aeolos  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 5  || [NULL]  || 100  || [NULL]  || 0  || 5  || Life Wood  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 6  || 5  || 100  || [NULL]  || 1  || 4  || Snow Moon  || [NULL]  || 8  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 7  || 5  || 100  || [NULL]  || 1  || 6  || Arena  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 8  || 5  || 100  || [NULL]  || 1  || 7  || Monslava  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 9  || 5  || 100  || [NULL]  || 1  || 8  || Monslava Blue  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 10  || 5  || 100  || [NULL]  || 1  || 9  || Devaberg  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 11  || 5  || 100  || [NULL]  || 1  || 10  || Atlantis  || 8  || 4  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 12  || 5  || 100  || [NULL]  || 1  || 11  || Temple  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 13  || [NULL]  || 100  || [NULL]  || 0  || 12  || Room of Shadow  || [NULL]  || [NULL]  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 14  || 5  || 100  || [NULL]  || 1  || 13  || Machine City  || [NULL]  || 3  || 0&lt;br /&gt;
|-&lt;br /&gt;
| 15  || 5  || 100  || [NULL]  || 1  || 14  || Dance Time  || [NULL]  || 3  || 0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Guardian_2_Maps ===&lt;br /&gt;
Connects a guardian (or boss guardian) to a map with a specific scenario. Each scenario has a unique set of guardians available for matches.&lt;br /&gt;
&lt;br /&gt;
=== Skill_2_Guardians ===&lt;br /&gt;
Links skills to guardians. The connection is made through `Guardian_2_Maps`, associating skills with a guardian in a specific scenario and map. If no `guardian_2_maps` entry is set, it defaults to `btItem`, which is predefined for guardian types. `btItem` is shared among similar guardian types (e.g., Dokaro and related &amp;quot;ro&amp;quot; types).&lt;br /&gt;
&lt;br /&gt;
=== S_Guardian_Multipliers ===&lt;br /&gt;
Defines multipliers for Guardian Mode, applicable for gold or experience.&lt;br /&gt;
&lt;br /&gt;
These multipliers can be linked in the `S_Relationships` table.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! description !! multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 1  || default exp multiplier  || 1.0&lt;br /&gt;
|-&lt;br /&gt;
| 2  || default gold multiplier  || 1.0&lt;br /&gt;
|-&lt;br /&gt;
| 3  || exp x3  || 3.0&lt;br /&gt;
|-&lt;br /&gt;
| 4  || exp x4  || 4.0&lt;br /&gt;
|-&lt;br /&gt;
| 5  || exp x5  || 5.0&lt;br /&gt;
|-&lt;br /&gt;
| 6  || exp x6  || 6.0&lt;br /&gt;
|-&lt;br /&gt;
| 7  || exp x7  || 7.0&lt;br /&gt;
|-&lt;br /&gt;
| 8  || gold x7  || 7.0&lt;br /&gt;
|-&lt;br /&gt;
| 9  || gold x5  || 5.0&lt;br /&gt;
|-&lt;br /&gt;
| 10  || gold x3  || 3.5&lt;br /&gt;
|-&lt;br /&gt;
| 11  || exp x3  || 3.5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== S_Relationships ===&lt;br /&gt;
Defines additional connections between entities.&lt;br /&gt;
* Uses:&lt;br /&gt;
** `id_f` (`id_from`) and `id_t` (`id_to`) to establish relationships.&lt;br /&gt;
** When linking products to guardians, `productIndex` is used for `id_f`, and `guardian_2_maps.id` for `id_t`.&lt;br /&gt;
** The `type` and `role` fields control drop behavior.&lt;br /&gt;
** Relationships can be toggled for in-game use using the `status` field.&lt;br /&gt;
** The `id_f` (`id_from`) corresponds to the first entity in the relationship (e.g., Product, Multiplier).&lt;br /&gt;
** The `id_t` (`id_to`) corresponds to the target entity (e.g., Guardian, Boss Guardian, Map).&lt;br /&gt;
** For Guardian/Boss Guardian relationships, `guardian_2_maps.id` is used instead of direct Guardian IDs.&lt;br /&gt;
** Each relationship type and role determines how the relationship functions in game.&lt;br /&gt;
&lt;br /&gt;
=== S_Relationship_Types ===&lt;br /&gt;
Defines different types of relationships that can exist in the game currently supported.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Product to Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Product to Boss Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 3  || Product to Map&lt;br /&gt;
|-&lt;br /&gt;
| 4  || Multiplier to Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 5  || Multiplier to Boss Guardian&lt;br /&gt;
|-&lt;br /&gt;
| 6  || Multiplier to Map&lt;br /&gt;
|-&lt;br /&gt;
| 7  || Product to Map (Guardian Mode)&lt;br /&gt;
|-&lt;br /&gt;
| 8  || Product to Sell Price&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== S_Relationship_Roles ===&lt;br /&gt;
Defines the role of each relationship in gameplay currently supported.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Item Drop&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Exp Multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 3  || Gold Multiplier&lt;br /&gt;
|-&lt;br /&gt;
| 4  || Item Sell Price&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== K_Status ===&lt;br /&gt;
Defines the different status types used in game.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! id !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1  || Active&lt;br /&gt;
|-&lt;br /&gt;
| 2  || Inactive&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Server]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Commands&amp;diff=64</id>
		<title>Commands</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Commands&amp;diff=64"/>
		<updated>2025-02-24T18:31:01Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;= Ingame Commands =  This page provides a list of available ingame commands for &amp;#039;&amp;#039;&amp;#039;Fantasy Tennis&amp;#039;&amp;#039;&amp;#039; within &amp;#039;&amp;#039;&amp;#039;JFTSE&amp;#039;&amp;#039;&amp;#039;. Commands must be prefixed with a `-` to execute. Some commands are &amp;#039;&amp;#039;&amp;#039;restricted to Game Masters (GMs)&amp;#039;&amp;#039;&amp;#039;.  == General Commands == These commands can be used by all players.  === 🎲 Random Mode === Random guardians will spawn instead of the default ones (boss remains the same). &amp;lt;pre&amp;gt; -random &amp;lt;/pre&amp;gt;  === 🔥 Hard Mode === Enables &amp;#039;&amp;#039;&amp;#039;Hard Mode&amp;#039;&amp;#039;&amp;#039;, inc...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Ingame Commands =&lt;br /&gt;
&lt;br /&gt;
This page provides a list of available ingame commands for &#039;&#039;&#039;Fantasy Tennis&#039;&#039;&#039; within &#039;&#039;&#039;JFTSE&#039;&#039;&#039;. Commands must be prefixed with a `-` to execute. Some commands are &#039;&#039;&#039;restricted to Game Masters (GMs)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== General Commands ==&lt;br /&gt;
These commands can be used by all players.&lt;br /&gt;
&lt;br /&gt;
=== 🎲 Random Mode ===&lt;br /&gt;
Random guardians will spawn instead of the default ones (boss remains the same).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-random&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🔥 Hard Mode ===&lt;br /&gt;
Enables &#039;&#039;&#039;Hard Mode&#039;&#039;&#039;, increasing enemy stats and spawning additional guardians.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-hard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🎁 Gacha Opening ===&lt;br /&gt;
Opens gachas without animation for faster processing.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-og &amp;quot;Gacha Name&amp;quot; &amp;lt;amount&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-og &amp;quot;Western Coin&amp;quot; 50&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
➡ &#039;&#039;&#039;&amp;lt;span style=&amp;quot;color:red;&amp;quot;&amp;gt;Note:&amp;lt;/span&amp;gt;&#039;&#039;&#039; Some gachas contain &amp;quot;ll&amp;quot; (e.g., `&amp;quot;Rare Box ll&amp;quot;`), requiring a lowercase `L` in the command.&lt;br /&gt;
&lt;br /&gt;
=== 🏆 Arcade Mode ===&lt;br /&gt;
Enters &#039;&#039;&#039;Arcade Mode&#039;&#039;&#039;. WIP.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-arcade&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🔄 Pointback ===&lt;br /&gt;
Allows players to vote for resetting the last point in &#039;&#039;&#039;[[Basic Mode]]&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-pb&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
➡ &#039;&#039;&#039;Note:&#039;&#039;&#039; All players must vote using `-pb` before a new point is made.&lt;br /&gt;
&lt;br /&gt;
== GM Commands ==&lt;br /&gt;
These commands are &#039;&#039;&#039;restricted to Game Masters (GMs)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== 🚫 Player Management ===&lt;br /&gt;
Banning, unbanning, and kicking players.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-ban &amp;lt;player&amp;gt;&lt;br /&gt;
-unban &amp;lt;player&amp;gt;&lt;br /&gt;
-serverKick &amp;lt;player&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 📝 Server Notice ===&lt;br /&gt;
Sets and broadcasts a &#039;&#039;&#039;server wide notice&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-sN &amp;lt;message&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🔄 Reset &amp;amp; Reload ===&lt;br /&gt;
Resets or reloads server data.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-rsLogin &amp;lt;player&amp;gt;    # Resets login status&lt;br /&gt;
-reloadScripts       # Reloads all scripts&lt;br /&gt;
-event              # Event configuration&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🎁 Item &amp;amp; Gift Commands ===&lt;br /&gt;
Used to grant items, experience, or gold to players.&lt;br /&gt;
&lt;br /&gt;
Give items to &#039;&#039;&#039;another player&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-give &amp;lt;player&amp;gt; &amp;lt;item&amp;gt; &amp;lt;amount&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Give items to &#039;&#039;&#039;yourself&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-get &amp;lt;item&amp;gt; &amp;lt;amount&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Send a &#039;&#039;&#039;gift&#039;&#039;&#039; to another player:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-gift &amp;lt;player&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Help Command ==&lt;br /&gt;
Displays all commands and their description.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-help&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&amp;lt;big&amp;gt;&#039;&#039;&#039;Notes:&#039;&#039;&#039;&amp;lt;/big&amp;gt;&lt;br /&gt;
* Commands &#039;&#039;&#039;must always start with `-`&#039;&#039;&#039;.&lt;br /&gt;
* Some commands may have additional restrictions (e.g., requiring level 60).&lt;br /&gt;
* GM commands are &#039;&#039;&#039;restricted&#039;&#039;&#039; to authorized users.&lt;br /&gt;
&lt;br /&gt;
For further details, refer to the ingame chat.&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Guides_%26_Tutorials&amp;diff=63</id>
		<title>Guides &amp; Tutorials</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Guides_%26_Tutorials&amp;diff=63"/>
		<updated>2025-02-24T18:03:37Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Undo revision&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 📖 Guides &amp;amp; Tutorials ==&lt;br /&gt;
Here you can find various guides and tutorials.&lt;br /&gt;
&lt;br /&gt;
=== 📜 Available Guides ===&lt;br /&gt;
&#039;&#039;This list is automatically generated. To add a guide, create a new page and categorize it under Guides.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;dpl&amp;gt;&lt;br /&gt;
category=Guides&lt;br /&gt;
ordermethod=title&lt;br /&gt;
order=ascending&lt;br /&gt;
namespace=Main&lt;br /&gt;
mode=unordered&lt;br /&gt;
format=list&lt;br /&gt;
linkstoresult=true&lt;br /&gt;
noresultsheader=❌ No guides found. Be the first to add one!&lt;br /&gt;
&amp;lt;/dpl&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 🛠 Contributing ==&lt;br /&gt;
* &#039;&#039;&#039;Want to add a guide?&#039;&#039;&#039; Create a new page and add &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;[[Category:Guides]]&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; at the bottom.&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=Guides_%26_Tutorials&amp;diff=62</id>
		<title>Guides &amp; Tutorials</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Guides_%26_Tutorials&amp;diff=62"/>
		<updated>2025-02-24T18:02:27Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Here you can find various guides and tutorials.&lt;br /&gt;
&lt;br /&gt;
=== 📜 Available Guides ===&lt;br /&gt;
&#039;&#039;This list is automatically generated. To add a guide, create a new page and categorize it under Guides.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;dpl&amp;gt;&lt;br /&gt;
category=Guides&lt;br /&gt;
ordermethod=title&lt;br /&gt;
order=ascending&lt;br /&gt;
namespace=Main&lt;br /&gt;
mode=unordered&lt;br /&gt;
format=list&lt;br /&gt;
linkstoresult=true&lt;br /&gt;
noresultsheader=❌ No guides found. Be the first to add one!&lt;br /&gt;
&amp;lt;/dpl&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 🛠 Contributing ==&lt;br /&gt;
* &#039;&#039;&#039;Want to add a guide?&#039;&#039;&#039; Create a new page and add &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;[[Category:Guides]]&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; at the bottom.&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
</feed>