<?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-08-03T22:13:42Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=JFTSE_Roadmap&amp;diff=333</id>
		<title>JFTSE Roadmap</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=JFTSE_Roadmap&amp;diff=333"/>
		<updated>2026-08-02T13:05:45Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This roadmap provides an overview of planned, ongoing and completed work across the wider JFTSE project. The work is divided into three areas.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Reverse Engineering&#039;&#039;&#039; covers research into the original Fantasy Tennis client, server behaviour, protocols and game mechanics.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Emulator Development&#039;&#039;&#039; covers the implementation of reverse-engineered systems, recreating the server behaviour expected by the game client, and maintaining and improving the JFTSE emulator itself.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Fantasy Tennis Reborn&#039;&#039;&#039; covers work specific to the hosted jftse.com/FTR service, including community features, events, administration, infrastructure and other player-facing improvements.&lt;br /&gt;
&lt;br /&gt;
= Influencing the Roadmap =&lt;br /&gt;
&lt;br /&gt;
Want to see a particular feature or improvement? Start a discussion with other players on the [https://discord.gg/YkxTGXwug3 JFTSE/FTR Discord].&lt;br /&gt;
&lt;br /&gt;
Ideas should be discussed openly with the community and, where useful, supported through polls or votes. Discord staff can then be included in the discussion to help evaluate the idea, clarify what is technically and community-wide possible and work towards a reasonable solution.&lt;br /&gt;
&lt;br /&gt;
JFTSE and Fantasy Tennis Reborn are shaped by their community, so feedback and participation remain an important part of deciding what should be worked on next.&lt;br /&gt;
&lt;br /&gt;
= Reverse Engineering =&lt;br /&gt;
&lt;br /&gt;
Reverse engineering is required to restore missing functionality, understand the Fantasy Tennis client and determine what the original server was expected to do.&lt;br /&gt;
&lt;br /&gt;
Understanding client and server packets is only part of this work. A considerable amount of trial and error is often required to understand the structures surrounding those packets and determine what effect each value or packet has on the game client.  &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Pets (Battlemons)&lt;br /&gt;
* Tournament Mode&lt;br /&gt;
* Club Match Mode&lt;br /&gt;
* Club Castle&lt;br /&gt;
* Emblems&lt;br /&gt;
* Quests&lt;br /&gt;
* Special items tied to client and server logic&lt;br /&gt;
* Cards&lt;br /&gt;
* Complete packet structures that are currently only partially understood&lt;br /&gt;
&lt;br /&gt;
= Emulator Development =&lt;br /&gt;
&lt;br /&gt;
This area covers everything the emulator needs to support so that the Fantasy Tennis client can use its original systems and communication as intended, as well as functionality extending beyond the original game.&lt;br /&gt;
&lt;br /&gt;
The priority is to restore core functionality as completely as possible before building custom systems on top of it. Development priorities are also influenced by the requirements of the hosted JFTSE version and which custom features are intended to become part of the emulator.&lt;br /&gt;
&lt;br /&gt;
== Completed ==&lt;br /&gt;
&lt;br /&gt;
* Singleplay&lt;br /&gt;
* Matchplay&lt;br /&gt;
** Basic&lt;br /&gt;
** Battle&lt;br /&gt;
** Guardian&lt;br /&gt;
* Recipe System&lt;br /&gt;
* Lottery System&lt;br /&gt;
* Enchanting&lt;br /&gt;
* Housing&lt;br /&gt;
** Decoration&lt;br /&gt;
** Fishing&lt;br /&gt;
** Gathering&lt;br /&gt;
** Chat Rooms&lt;br /&gt;
* Messenger&lt;br /&gt;
** Messages&lt;br /&gt;
** Gifts&lt;br /&gt;
** Parcels&lt;br /&gt;
** Proposals&lt;br /&gt;
* Club System (Guilds)&lt;br /&gt;
* Shop&lt;br /&gt;
* Inventory&lt;br /&gt;
* QS Items&lt;br /&gt;
* Special Items&lt;br /&gt;
** Recipes&lt;br /&gt;
** Ring of EXP&lt;br /&gt;
** Ring of Gold&lt;br /&gt;
** Ring of Wiseman&lt;br /&gt;
** Wing of Memory&lt;br /&gt;
** Couple Ring&lt;br /&gt;
** Trunk Small&lt;br /&gt;
** Trunk Medium&lt;br /&gt;
** Trunk Large&lt;br /&gt;
** Megaphone/Speaker&lt;br /&gt;
&lt;br /&gt;
== In Progress ==&lt;br /&gt;
&lt;br /&gt;
* Pets (Battlemons)&lt;br /&gt;
* GT redemption for players&lt;br /&gt;
&lt;br /&gt;
== Planned ==&lt;br /&gt;
&lt;br /&gt;
* Complete the Pets (Battlemons) system&lt;br /&gt;
* Progression Update&lt;br /&gt;
* Tournament Mode&lt;br /&gt;
* Club Match Mode&lt;br /&gt;
* Club Castle&lt;br /&gt;
* Remaining special items with specific functionality&lt;br /&gt;
* Quest System&lt;br /&gt;
* Emblem System&lt;br /&gt;
* Card System&lt;br /&gt;
* Track specific transactions within the emulator&lt;br /&gt;
** Shop purchases&lt;br /&gt;
** Gifts&lt;br /&gt;
** Parcels&lt;br /&gt;
* Improve the database importer&lt;br /&gt;
** Database and XML data should synchronise correctly&lt;br /&gt;
** It still needs to be decided which source is authoritative, as some data must also be represented in client-side files&lt;br /&gt;
* Improve the database exporter&lt;br /&gt;
** Not all data is currently exported, which can cause mismatches during larger updates&lt;br /&gt;
&lt;br /&gt;
=== Progression Update ===&lt;br /&gt;
&lt;br /&gt;
Fantasy Tennis uses a fixed progression system in which maps unlock at specific levels and PvE encounters are designed around that progression. A more flexible and modern solution is needed.&lt;br /&gt;
&lt;br /&gt;
A plan exists for redistributing the EXP required for each level. This may include temporarily rolling back player levels before introducing a new maximum level so that existing accounts can properly follow the updated progression.&lt;br /&gt;
&lt;br /&gt;
This is a large update that affects many parts of the game. Low-level, mid-game and end-game players should all be able to participate in suitable content without being limited to encounters, rewards and progression values designed only for a fixed map level.&lt;br /&gt;
&lt;br /&gt;
Encounters, rewards and progression should scale according to the levels of the players in the room while still respecting the identity and difficulty of each map.&lt;br /&gt;
&lt;br /&gt;
== Long-term ==&lt;br /&gt;
&lt;br /&gt;
Ongoing work includes:&lt;br /&gt;
&lt;br /&gt;
* Bug fixes&lt;br /&gt;
* Architectural improvements&lt;br /&gt;
&lt;br /&gt;
= Fantasy Tennis Reborn =&lt;br /&gt;
&lt;br /&gt;
This area covers work related specifically to the hosted jftse.com/FTR service.&lt;br /&gt;
&lt;br /&gt;
== Completed ==&lt;br /&gt;
&lt;br /&gt;
* [[Commands|Commands]]&lt;br /&gt;
* [[:Category:Scripting|Scripting System]]&lt;br /&gt;
* New Lottery System&lt;br /&gt;
** Hybrid system combining RNG, pity mechanics, currency and a shop-based fallback&lt;br /&gt;
** The system was selected by the community&lt;br /&gt;
* [[Club League Duel|Club League Duel]]&lt;br /&gt;
&lt;br /&gt;
== In Progress ==&lt;br /&gt;
&lt;br /&gt;
* Summer item balancing&lt;br /&gt;
* Lottery drop configuration&lt;br /&gt;
* Distribution of GT obtained from lottery items&lt;br /&gt;
&lt;br /&gt;
== Planned ==&lt;br /&gt;
&lt;br /&gt;
* Add Advanced Guardian mechanics to more boss maps by implementing encounter logic in JavaScript&lt;br /&gt;
* General item balancing&lt;br /&gt;
* Lottery weight balancing&lt;br /&gt;
* GT reward balancing for duplicate lottery items&lt;br /&gt;
* Events&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=JFTSE_Wiki&amp;diff=332</id>
		<title>JFTSE Wiki</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=JFTSE_Wiki&amp;diff=332"/>
		<updated>2026-08-02T12:49:48Z</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;
* [[JFTSE_Roadmap|JFTSE Roadmap]] - Current, planned and completed work across the JFTSE project.&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=JFTSE_Roadmap&amp;diff=331</id>
		<title>JFTSE Roadmap</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=JFTSE_Roadmap&amp;diff=331"/>
		<updated>2026-08-02T12:46:31Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;This roadmap provides an overview of planned, ongoing and completed work across the wider JFTSE project. The work is divided into three areas.  &amp;#039;&amp;#039;&amp;#039;Reverse Engineering&amp;#039;&amp;#039;&amp;#039; covers research into the original Fantasy Tennis client, server behaviour, protocols and game mechanics.  &amp;#039;&amp;#039;&amp;#039;Emulator Development&amp;#039;&amp;#039;&amp;#039; covers the implementation of reverse-engineered systems, recreating the server behaviour expected by the game client, and maintaining and improving the JFTSE emulator itsel...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This roadmap provides an overview of planned, ongoing and completed work across the wider JFTSE project. The work is divided into three areas.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Reverse Engineering&#039;&#039;&#039; covers research into the original Fantasy Tennis client, server behaviour, protocols and game mechanics.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Emulator Development&#039;&#039;&#039; covers the implementation of reverse-engineered systems, recreating the server behaviour expected by the game client, and maintaining and improving the JFTSE emulator itself.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Fantasy Tennis Reborn&#039;&#039;&#039; covers work specific to the hosted jftse.com/FTR service, including community features, events, administration, infrastructure and other player-facing improvements.&lt;br /&gt;
&lt;br /&gt;
= Reverse Engineering =&lt;br /&gt;
&lt;br /&gt;
Reverse engineering is required to restore missing functionality, understand the Fantasy Tennis client and determine what the original server was expected to do.&lt;br /&gt;
&lt;br /&gt;
Understanding client and server packets is only part of this work. A considerable amount of trial and error is often required to understand the structures surrounding those packets and determine what effect each value or packet has on the game client.  &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Pets (Battlemons)&lt;br /&gt;
* Tournament Mode&lt;br /&gt;
* Club Match Mode&lt;br /&gt;
* Club Castle&lt;br /&gt;
* Emblems&lt;br /&gt;
* Quests&lt;br /&gt;
* Special items tied to client and server logic&lt;br /&gt;
* Cards&lt;br /&gt;
* Complete packet structures that are currently only partially understood&lt;br /&gt;
&lt;br /&gt;
= Emulator Development =&lt;br /&gt;
&lt;br /&gt;
This area covers everything the emulator needs to support so that the Fantasy Tennis client can use its original systems and communication as intended, as well as functionality extending beyond the original game.&lt;br /&gt;
&lt;br /&gt;
The priority is to restore core functionality as completely as possible before building custom systems on top of it. Development priorities are also influenced by the requirements of the hosted JFTSE version and which custom features are intended to become part of the emulator.&lt;br /&gt;
&lt;br /&gt;
== Completed ==&lt;br /&gt;
&lt;br /&gt;
* Singleplay&lt;br /&gt;
* Matchplay&lt;br /&gt;
** Basic&lt;br /&gt;
** Battle&lt;br /&gt;
** Guardian&lt;br /&gt;
* Recipe System&lt;br /&gt;
* Lottery System&lt;br /&gt;
* Enchanting&lt;br /&gt;
* Housing&lt;br /&gt;
** Decoration&lt;br /&gt;
** Fishing&lt;br /&gt;
** Gathering&lt;br /&gt;
** Chat Rooms&lt;br /&gt;
* Messenger&lt;br /&gt;
** Messages&lt;br /&gt;
** Gifts&lt;br /&gt;
** Parcels&lt;br /&gt;
** Proposals&lt;br /&gt;
* Club System (Guilds)&lt;br /&gt;
* Shop&lt;br /&gt;
* Inventory&lt;br /&gt;
* QS Items&lt;br /&gt;
* Special Items&lt;br /&gt;
** Recipes&lt;br /&gt;
** Ring of EXP&lt;br /&gt;
** Ring of Gold&lt;br /&gt;
** Ring of Wiseman&lt;br /&gt;
** Wing of Memory&lt;br /&gt;
** Couple Ring&lt;br /&gt;
** Trunk Small&lt;br /&gt;
** Trunk Medium&lt;br /&gt;
** Trunk Large&lt;br /&gt;
** Megaphone/Speaker&lt;br /&gt;
&lt;br /&gt;
== In Progress ==&lt;br /&gt;
&lt;br /&gt;
* Pets (Battlemons)&lt;br /&gt;
* GT redemption for players&lt;br /&gt;
&lt;br /&gt;
== Planned ==&lt;br /&gt;
&lt;br /&gt;
* Complete the Pets (Battlemons) system&lt;br /&gt;
* Progression Update&lt;br /&gt;
* Tournament Mode&lt;br /&gt;
* Club Match Mode&lt;br /&gt;
* Club Castle&lt;br /&gt;
* Remaining special items with specific functionality&lt;br /&gt;
* Quest System&lt;br /&gt;
* Emblem System&lt;br /&gt;
* Card System&lt;br /&gt;
* Track specific transactions within the emulator&lt;br /&gt;
** Shop purchases&lt;br /&gt;
** Gifts&lt;br /&gt;
** Parcels&lt;br /&gt;
* Improve the database importer&lt;br /&gt;
** Database and XML data should synchronise correctly&lt;br /&gt;
** It still needs to be decided which source is authoritative, as some data must also be represented in client-side files&lt;br /&gt;
* Improve the database exporter&lt;br /&gt;
** Not all data is currently exported, which can cause mismatches during larger updates&lt;br /&gt;
&lt;br /&gt;
=== Progression Update ===&lt;br /&gt;
&lt;br /&gt;
Fantasy Tennis uses a fixed progression system in which maps unlock at specific levels and PvE encounters are designed around that progression. A more flexible and modern solution is needed.&lt;br /&gt;
&lt;br /&gt;
A plan exists for redistributing the EXP required for each level. This may include temporarily rolling back player levels before introducing a new maximum level so that existing accounts can properly follow the updated progression.&lt;br /&gt;
&lt;br /&gt;
This is a large update that affects many parts of the game. Low-level, mid-game and end-game players should all be able to participate in suitable content without being limited to encounters, rewards and progression values designed only for a fixed map level.&lt;br /&gt;
&lt;br /&gt;
Encounters, rewards and progression should scale according to the levels of the players in the room while still respecting the identity and difficulty of each map.&lt;br /&gt;
&lt;br /&gt;
== Long-term ==&lt;br /&gt;
&lt;br /&gt;
Ongoing work includes:&lt;br /&gt;
&lt;br /&gt;
* Bug fixes&lt;br /&gt;
* Architectural improvements&lt;br /&gt;
&lt;br /&gt;
= Fantasy Tennis Reborn =&lt;br /&gt;
&lt;br /&gt;
This area covers work related specifically to the hosted jftse.com/FTR service.&lt;br /&gt;
&lt;br /&gt;
== Completed ==&lt;br /&gt;
&lt;br /&gt;
* [[Commands|Commands]]&lt;br /&gt;
* [[:Category:Scripting|Scripting System]]&lt;br /&gt;
* New Lottery System&lt;br /&gt;
** Hybrid system combining RNG, pity mechanics, currency and a shop-based fallback&lt;br /&gt;
** The system was selected by the community&lt;br /&gt;
* [[Club League Duel|Club League Duel]]&lt;br /&gt;
&lt;br /&gt;
== In Progress ==&lt;br /&gt;
&lt;br /&gt;
* Summer item balancing&lt;br /&gt;
* Lottery drop configuration&lt;br /&gt;
* Distribution of GT obtained from lottery items&lt;br /&gt;
&lt;br /&gt;
== Planned ==&lt;br /&gt;
&lt;br /&gt;
* Add Advanced Guardian mechanics to more boss maps by implementing encounter logic in JavaScript&lt;br /&gt;
* General item balancing&lt;br /&gt;
* Lottery weight balancing&lt;br /&gt;
* GT reward balancing for duplicate lottery items&lt;br /&gt;
* Events&lt;/div&gt;</summary>
		<author><name>XxharCs</name></author>
	</entry>
	<entry>
		<id>https://wiki.jftse.com/index.php?title=FT_Messenger&amp;diff=330</id>
		<title>FT Messenger</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=FT_Messenger&amp;diff=330"/>
		<updated>2026-07-21T09:51:44Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Fantasy Tennis Messenger ==&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;FT Messenger&#039;&#039;&#039; is used to manage friends, messages and player-to-player deliveries. Like inventory management, keeping the Messenger organized is part of Fantasy Tennis&#039;s day-to-day gameplay.&lt;br /&gt;
&lt;br /&gt;
The Messenger is not an unlimited archive. Each category can contain up to &#039;&#039;&#039;128 entries&#039;&#039;&#039;, and very full lists may cause the Messenger to take longer to open.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Category&lt;br /&gt;
! Maximum entries&lt;br /&gt;
|-&lt;br /&gt;
| Friends&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Messages&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Gifts&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Parcels&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Proposals&lt;br /&gt;
| 128&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Proposals category will normally not reach this limit, as a player can only have one active sent or received proposal.&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=FT_Messenger&amp;diff=327</id>
		<title>FT Messenger</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=FT_Messenger&amp;diff=327"/>
		<updated>2026-07-20T20:24:10Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Fantasy Tennnis Messenger ==&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;FT Messenger&#039;&#039;&#039; is used to manage friends, messages and player-to-player deliveries. Like inventory management, keeping the Messenger organized is part of Fantasy Tennis&#039;s day-to-day gameplay.&lt;br /&gt;
&lt;br /&gt;
The Messenger is not an unlimited archive. Each category can contain up to &#039;&#039;&#039;128 entries&#039;&#039;&#039;, and very full lists may cause the Messenger to take longer to open.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Category&lt;br /&gt;
! Maximum entries&lt;br /&gt;
|-&lt;br /&gt;
| Friends&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Messages&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Gifts&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Parcels&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Proposals&lt;br /&gt;
| 128&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Proposals category will normally not reach this limit, as a player can only have one active sent or received proposal.&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=FT_Messenger&amp;diff=326</id>
		<title>FT Messenger</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=FT_Messenger&amp;diff=326"/>
		<updated>2026-07-20T20:23:49Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: XxharCs moved page Fantasy Messenger to FT Messenger without leaving a redirect&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Fantasy Messenger ==&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Fantasy Messenger&#039;&#039;&#039; is used to manage friends, messages and player-to-player deliveries. Like inventory management, keeping the Messenger organized is part of Fantasy Tennis&#039;s day-to-day gameplay.&lt;br /&gt;
&lt;br /&gt;
The Messenger is not an unlimited archive. Each category can contain up to &#039;&#039;&#039;128 entries&#039;&#039;&#039;, and very full lists may cause the Messenger to take longer to open.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Category&lt;br /&gt;
! Maximum entries&lt;br /&gt;
|-&lt;br /&gt;
| Friends&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Messages&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Gifts&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Parcels&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Proposals&lt;br /&gt;
| 128&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Proposals category will normally not reach this limit, as a player can only have one active sent or received proposal.&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=FT_Messenger&amp;diff=325</id>
		<title>FT Messenger</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=FT_Messenger&amp;diff=325"/>
		<updated>2026-07-20T20:22:56Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: Created page with &amp;quot;== Fantasy Messenger ==  The &amp;#039;&amp;#039;&amp;#039;Fantasy Messenger&amp;#039;&amp;#039;&amp;#039; is used to manage friends, messages and player-to-player deliveries. Like inventory management, keeping the Messenger organized is part of Fantasy Tennis&amp;#039;s day-to-day gameplay.  The Messenger is not an unlimited archive. Each category can contain up to &amp;#039;&amp;#039;&amp;#039;128 entries&amp;#039;&amp;#039;&amp;#039;, and very full lists may cause the Messenger to take longer to open.  {| class=&amp;quot;wikitable&amp;quot; |- ! Category ! Maximum entries |- | Friends | 128 |- | Mess...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Fantasy Messenger ==&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Fantasy Messenger&#039;&#039;&#039; is used to manage friends, messages and player-to-player deliveries. Like inventory management, keeping the Messenger organized is part of Fantasy Tennis&#039;s day-to-day gameplay.&lt;br /&gt;
&lt;br /&gt;
The Messenger is not an unlimited archive. Each category can contain up to &#039;&#039;&#039;128 entries&#039;&#039;&#039;, and very full lists may cause the Messenger to take longer to open.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Category&lt;br /&gt;
! Maximum entries&lt;br /&gt;
|-&lt;br /&gt;
| Friends&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Messages&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Gifts&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Parcels&lt;br /&gt;
| 128&lt;br /&gt;
|-&lt;br /&gt;
| Proposals&lt;br /&gt;
| 128&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Proposals category will normally not reach this limit, as a player can only have one active sent or received proposal.&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=Beginner%27s_Guide&amp;diff=324</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=324"/>
		<updated>2026-07-20T19:57:22Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Overview ==&lt;br /&gt;
&lt;br /&gt;
The Beginner&#039;s Guide helps new players understand what to do first in FT.&lt;br /&gt;
&lt;br /&gt;
Fantasy Tennis can feel confusing at the beginning because the game has several connected systems, such as the Tutorial, Single Play, Guardian Mode, Stats, Items, Skills, Enchantments, Housing, Commands and different match modes.&lt;br /&gt;
&lt;br /&gt;
This guide gives new players a clear starting path and links to the most important wiki pages.&lt;br /&gt;
&lt;br /&gt;
The recommended beginner path is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install the game → Complete the Tutorial → Try Single Play → Start Guardian Mode → Learn Stats, Items and Skills → Choose a beginner build → Improve your character → Try Basic and Battle&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Before You Start ==&lt;br /&gt;
&lt;br /&gt;
Before playing, make sure the game is installed correctly.&lt;br /&gt;
&lt;br /&gt;
If you are installing the game for the first time, see the &#039;&#039;&#039;[[Game Installation Guide|Game Installation Guide]]&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
If the game does not start, shows missing DLL errors, disconnects with an old version message, or has launcher problems, see the &#039;&#039;&#039;[[Troubleshooting|Troubleshooting]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
Useful starting pages:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Topic&lt;br /&gt;
! Page&lt;br /&gt;
! When to use it&lt;br /&gt;
|-&lt;br /&gt;
| Installation&lt;br /&gt;
| &#039;&#039;&#039;[[Game Installation Guide]]&#039;&#039;&#039;&lt;br /&gt;
| If you are installing the game for the first time&lt;br /&gt;
|-&lt;br /&gt;
| Common errors&lt;br /&gt;
| &#039;&#039;&#039;[[Troubleshooting]]&#039;&#039;&#039;&lt;br /&gt;
| If the game does not start or shows errors&lt;br /&gt;
|-&lt;br /&gt;
| Stats and builds&lt;br /&gt;
| &#039;&#039;&#039;[[Stats]]&#039;&#039;&#039;&lt;br /&gt;
| If you want to understand STR, STA, DEX and WIL&lt;br /&gt;
|-&lt;br /&gt;
| Items&lt;br /&gt;
| &#039;&#039;&#039;[[Items]]&#039;&#039;&#039;&lt;br /&gt;
| If you want to understand Quick Slot items, Buff Items, Count, Cooltime and Duration&lt;br /&gt;
|-&lt;br /&gt;
| Skills / Effects&lt;br /&gt;
| &#039;&#039;&#039;[[Skills]]&#039;&#039;&#039;&lt;br /&gt;
| If you want to understand what item effects do during gameplay&lt;br /&gt;
|-&lt;br /&gt;
| Enchant&lt;br /&gt;
| &#039;&#039;&#039;[[Enchantment]]&#039;&#039;&#039;&lt;br /&gt;
| If you want to improve equipment with Tablets, Elemental Stones or Jewels&lt;br /&gt;
|-&lt;br /&gt;
| Commands&lt;br /&gt;
| &#039;&#039;&#039;[[Commands]]&#039;&#039;&#039;&lt;br /&gt;
| If you want to learn useful ingame chat commands&lt;br /&gt;
|-&lt;br /&gt;
| Maps&lt;br /&gt;
| &#039;&#039;&#039;[[Maps]]&#039;&#039;&#039;&lt;br /&gt;
| If you want to learn about maps and possible rewards&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Recommended Beginner Path ==&lt;br /&gt;
&lt;br /&gt;
The simplest beginner path is:&lt;br /&gt;
&lt;br /&gt;
# Install and update the game.&lt;br /&gt;
# Complete the Tutorial.&lt;br /&gt;
# Try Single Play against NPCs.&lt;br /&gt;
# Start with Guardian Mode.&lt;br /&gt;
# Level up your character.&lt;br /&gt;
# Learn the basic stat, item and skill systems.&lt;br /&gt;
# Choose a simple beginner-friendly build.&lt;br /&gt;
# Improve your character step by step.&lt;br /&gt;
# Try Basic or Battle once you feel more comfortable.&lt;br /&gt;
&lt;br /&gt;
In short:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tutorial → Single Play → Guardian → STR / DEX beginner build → Stats / Items / Skills / Enchant → Basic / Battle&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Tutorial ==&lt;br /&gt;
&lt;br /&gt;
[[File:Tutorial_Menu_Icon.png|300px|right|Tutorial menu icon]]&lt;br /&gt;
&lt;br /&gt;
If you are new, start with the &#039;&#039;&#039;Tutorial&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The Tutorial is available directly from the main menu and teaches the basic controls and match flow.&lt;br /&gt;
&lt;br /&gt;
It helps you understand:&lt;br /&gt;
&lt;br /&gt;
* Basic controls&lt;br /&gt;
* Movement&lt;br /&gt;
* Timing&lt;br /&gt;
* Ball hitting&lt;br /&gt;
* Basic match flow&lt;br /&gt;
&lt;br /&gt;
Skipping the Tutorial can make the beginning harder, especially if you are not familiar with Fantasy Tennis movement and timing.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tip:&#039;&#039;&#039; Complete the Tutorial before trying Guardian, Single Play, Basic or Battle.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br style=&amp;quot;clear: both;&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Single Play ==&lt;br /&gt;
&lt;br /&gt;
[[File:Single_Play_Menu_Icon.png|300px|right|Single Play menu icon]]&lt;br /&gt;
&lt;br /&gt;
After completing the Tutorial, &#039;&#039;&#039;Single Play&#039;&#039;&#039; is a good place to practice against NPC opponents.&lt;br /&gt;
&lt;br /&gt;
Single Play lets new players learn the game without immediately playing against experienced players.&lt;br /&gt;
&lt;br /&gt;
It can help beginners practice:&lt;br /&gt;
&lt;br /&gt;
* Movement&lt;br /&gt;
* Ball timing&lt;br /&gt;
* Basic Mode mechanics&lt;br /&gt;
* Battle Mode mechanics&lt;br /&gt;
* Item usage&lt;br /&gt;
* Match flow&lt;br /&gt;
* Positioning&lt;br /&gt;
* How different game modes feel&lt;br /&gt;
&lt;br /&gt;
Single Play includes Basic and Battle-style stages where players can progress through different levels.&lt;br /&gt;
&lt;br /&gt;
It can also be useful for collecting early items and Gold.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tip:&#039;&#039;&#039; If Guardian Mode still feels confusing, try Single Play first to get more comfortable with the mechanics.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br style=&amp;quot;clear: both;&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Start with Guardian Mode ==&lt;br /&gt;
&lt;br /&gt;
After the Tutorial and some Single Play practice, Guardian Mode is usually the best place for new players to build their character.&lt;br /&gt;
&lt;br /&gt;
Guardian Mode helps new players:&lt;br /&gt;
&lt;br /&gt;
* Level up their character&lt;br /&gt;
* Earn Gold and rewards&lt;br /&gt;
* Learn basic gameplay&lt;br /&gt;
* Collect useful items&lt;br /&gt;
* Understand movement and timing&lt;br /&gt;
* Build a stronger foundation before entering PvP modes&lt;br /&gt;
&lt;br /&gt;
If your goal is to compete later in Basic or Battle, Guardian Mode is usually the best long-term starting point.&lt;br /&gt;
&lt;br /&gt;
== Why Single Play and Guardian Help Beginners ==&lt;br /&gt;
&lt;br /&gt;
Basic and Battle against other players can be difficult for new players if they enter too early.&lt;br /&gt;
&lt;br /&gt;
New players usually lack:&lt;br /&gt;
&lt;br /&gt;
* Levels&lt;br /&gt;
* Items&lt;br /&gt;
* Stats&lt;br /&gt;
* Game knowledge&lt;br /&gt;
* Movement experience&lt;br /&gt;
* Understanding of Quick Slot items and buffs&lt;br /&gt;
* Understanding of skill effects&lt;br /&gt;
* Understanding of Guardian mechanics and team roles&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Single Play&#039;&#039;&#039; helps beginners practice mechanics against NPCs.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Guardian Mode&#039;&#039;&#039; helps beginners level up, collect rewards and build a stronger character.&lt;br /&gt;
&lt;br /&gt;
Together, Single Play and Guardian Mode give new players a much better foundation before entering more competitive modes.&lt;br /&gt;
&lt;br /&gt;
== Tips for Guardian ==&lt;br /&gt;
&lt;br /&gt;
Guardian Mode is one of the best early progression modes for new players because it helps with leveling, Gold and useful rewards.&lt;br /&gt;
&lt;br /&gt;
To improve your progression, use your starting Gold carefully and focus on reward-boosting options when possible.&lt;br /&gt;
&lt;br /&gt;
=== Carnival Pro Coin Gacha ===&lt;br /&gt;
&lt;br /&gt;
[[File:Screenshot 2026-05-03 200053.png|180px|center|alt=Carnival Pro Coin Gacha|Carnival Pro Coin Gacha]]&lt;br /&gt;
&lt;br /&gt;
One useful early option is the &#039;&#039;&#039;Carnival Pro Coin Gacha&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Carnival Pro Coin Gacha&#039;&#039;&#039; can provide &#039;&#039;&#039;Gold Rings&#039;&#039;&#039; and &#039;&#039;&#039;EXP Rings&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
These rings can double your Guardian rewards:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Gold Ring&#039;&#039;&#039; – doubles Gold rewards&lt;br /&gt;
* &#039;&#039;&#039;EXP Ring&#039;&#039;&#039; – doubles EXP rewards&lt;br /&gt;
&lt;br /&gt;
Gold and EXP Rings are consumable items. Once equipped, one ring is used per round.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Useful command:&#039;&#039;&#039; If you want to open multiple gachas faster, you can use the &#039;&#039;&#039;-og&#039;&#039;&#039; command.&lt;br /&gt;
&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;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-og &amp;quot;Carnival Pro Coin&amp;quot; 10&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This opens the selected gacha without the animation.&lt;br /&gt;
&lt;br /&gt;
For more commands, see the &#039;&#039;&#039;[[Commands|Commands]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
=== Housing ===&lt;br /&gt;
&lt;br /&gt;
[[File:Housing.png|180px|center|alt=Housing|Housing]]&lt;br /&gt;
&lt;br /&gt;
After earning enough Gold, it can be useful to invest in a &#039;&#039;&#039;House&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Housing can provide additional Gold and EXP bonuses and can help with long-term progression.&lt;br /&gt;
&lt;br /&gt;
Housing is its own system based on houses, furniture, Housing Points and bonuses.&lt;br /&gt;
&lt;br /&gt;
For this Beginner&#039;s Guide, the important point is simple:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Housing can improve your long-term Gold and EXP gain.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Detailed house values and furniture information should be documented on a dedicated &#039;&#039;&#039;[[Housing|Housing]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
== Learn the Core Systems ==&lt;br /&gt;
&lt;br /&gt;
After you understand the basic gameplay, you should learn the core systems that affect progression.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! System&lt;br /&gt;
! Why it matters&lt;br /&gt;
! Wiki page&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Stats&#039;&#039;&#039;&lt;br /&gt;
| Explains STR, STA, DEX, WIL and Secondary Stats&lt;br /&gt;
| &#039;&#039;&#039;[[Stats]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Items&#039;&#039;&#039;&lt;br /&gt;
| Explains Quick Slot items, Buff Items, Count, Cooltime and Duration&lt;br /&gt;
| &#039;&#039;&#039;[[Items]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Skills&#039;&#039;&#039;&lt;br /&gt;
| Explains what gameplay effects do during a match&lt;br /&gt;
| &#039;&#039;&#039;[[Skills]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Enchant&#039;&#039;&#039;&lt;br /&gt;
| Explains Tablets, Elemental Stones, Jewels and equipment improvement&lt;br /&gt;
| &#039;&#039;&#039;[[Enchantment]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Commands&#039;&#039;&#039;&lt;br /&gt;
| Lists useful ingame chat commands&lt;br /&gt;
| &#039;&#039;&#039;[[Commands]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Maps&#039;&#039;&#039;&lt;br /&gt;
| Shows map information and where certain rewards may come from&lt;br /&gt;
| &#039;&#039;&#039;[[Maps]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Troubleshooting&#039;&#039;&#039;&lt;br /&gt;
| Helps fix common installation or launch issues&lt;br /&gt;
| &#039;&#039;&#039;[[Troubleshooting]]&#039;&#039;&#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Stats for Beginners ==&lt;br /&gt;
&lt;br /&gt;
Stats are numerical values and bonuses that affect gameplay.&lt;br /&gt;
&lt;br /&gt;
The four main attributes are:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[[Stats#STR|STR]]&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;[[Stats#STA|STA]]&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;[[Stats#DEX|DEX]]&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;[[Stats#WIL|WIL]]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As a beginner, you do not need to fully optimize your build immediately.&lt;br /&gt;
&lt;br /&gt;
It is more important to understand what stats are and how they affect gameplay.&lt;br /&gt;
&lt;br /&gt;
For more details, see the &#039;&#039;&#039;[[Stats|Stats]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
== Beginner Build Guidance ==&lt;br /&gt;
&lt;br /&gt;
New players do not need a perfect build immediately, but choosing a simple and useful stat direction can make early progression much easier.&lt;br /&gt;
&lt;br /&gt;
For beginners, a &#039;&#039;&#039;STR / DEX damage build&#039;&#039;&#039; is usually the easiest starting point.&lt;br /&gt;
&lt;br /&gt;
This type of build is simple to understand, works well in Guardian Mode and helps the team by dealing damage.&lt;br /&gt;
&lt;br /&gt;
A common damage-focused setup is:&lt;br /&gt;
&lt;br /&gt;
* Around &#039;&#039;&#039;60 [[Stats#DEX|DEX]]&#039;&#039;&#039;&lt;br /&gt;
* The remaining points into &#039;&#039;&#039;[[Stats#STR|STR]]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DEX&#039;&#039;&#039; helps with movement, responsiveness and projectile-related mechanics.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;STR&#039;&#039;&#039; supports offensive pressure and damage-related mechanics.&lt;br /&gt;
&lt;br /&gt;
This makes the build easier to play than more specialized setups.&lt;br /&gt;
&lt;br /&gt;
For detailed stat explanations, see the &#039;&#039;&#039;[[Stats|Stats]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
=== Damage Build ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Build&lt;br /&gt;
! Main stats&lt;br /&gt;
! Purpose&lt;br /&gt;
! Beginner friendly?&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;STR / DEX Damage Build&#039;&#039;&#039;&lt;br /&gt;
| Around 60 DEX, rest STR&lt;br /&gt;
| Damage, movement and offensive pressure&lt;br /&gt;
| Yes&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This build is recommended for many beginners because it gives clear value in Guardian Mode and is easier to use than more specialized support or ball-control builds.&lt;br /&gt;
&lt;br /&gt;
=== Why STR / DEX Is Beginner Friendly ===&lt;br /&gt;
&lt;br /&gt;
A STR / DEX damage build is usually easier for beginners because:&lt;br /&gt;
&lt;br /&gt;
* It gives clear offensive value.&lt;br /&gt;
* It helps in Guardian Mode.&lt;br /&gt;
* It is easier to understand than specialized setups.&lt;br /&gt;
* It supports movement and responsiveness through DEX.&lt;br /&gt;
* It supports damage and offensive pressure through STR.&lt;br /&gt;
* Most Guardian rooms need players who can deal damage.&lt;br /&gt;
&lt;br /&gt;
If you are unsure what to build first, starting as a damage dealer is usually the safest choice.&lt;br /&gt;
&lt;br /&gt;
== WIL / STA Builds ==&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;&#039;WIL / STA&#039;&#039;&#039; build can be useful, but it is usually more specialized.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Stats#WIL|WIL]]&#039;&#039;&#039; supports ball behavior, ball damage and curve-related gameplay.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Stats#STA|STA]]&#039;&#039;&#039; supports survivability, recovery and defensive gameplay.&lt;br /&gt;
&lt;br /&gt;
This type of build can be useful in certain team setups, but it is not always the easiest option for new players.&lt;br /&gt;
&lt;br /&gt;
In Guardian Mode, a room usually does not need every player to focus on WIL. Often, only one WIL-focused player can be enough, while the other players focus on dealing damage.&lt;br /&gt;
&lt;br /&gt;
Because of this, beginners usually have an easier time starting as damage dealers.&lt;br /&gt;
&lt;br /&gt;
== Recommended Beginner Build Choice ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Build type&lt;br /&gt;
! Recommended for beginners?&lt;br /&gt;
! Why&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;STR / DEX Damage Build&#039;&#039;&#039;&lt;br /&gt;
| Yes&lt;br /&gt;
| Easy to understand, strong for Guardian progression and useful in most rooms&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;WIL / STA Build&#039;&#039;&#039;&lt;br /&gt;
| Later&lt;br /&gt;
| More specialized and more dependent on team setup&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Pure experimental builds&#039;&#039;&#039;&lt;br /&gt;
| Later&lt;br /&gt;
| Better after understanding Stats, Items and Guardian mechanics&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tip:&#039;&#039;&#039; If you are new and unsure what to build, start with a simple STR / DEX damage setup first. It is easier to play, helps your team in Guardian Mode and gives you a strong foundation before trying more specialized builds.&lt;br /&gt;
&lt;br /&gt;
== Items and Skills for Beginners ==&lt;br /&gt;
&lt;br /&gt;
Items and Skills are closely connected in FT.&lt;br /&gt;
&lt;br /&gt;
Many gameplay effects are triggered through items.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Fireball&#039;&#039;&#039; is a Quick Slot item, but using it triggers a Fireball attack effect during gameplay.&lt;br /&gt;
&lt;br /&gt;
As a beginner, you should understand both sides:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Topic&lt;br /&gt;
! What it explains&lt;br /&gt;
! Page&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Items&#039;&#039;&#039;&lt;br /&gt;
| What you buy, equip, manage and consume&lt;br /&gt;
| &#039;&#039;&#039;[[Items]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Skills / Effects&#039;&#039;&#039;&lt;br /&gt;
| What happens during gameplay when an item or action is used&lt;br /&gt;
| &#039;&#039;&#039;[[Skills]]&#039;&#039;&#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Important beginner item concepts:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Quick Slot Items&#039;&#039;&#039; – usable items such as Fireball, Blizzard, Healing or Rebirth&lt;br /&gt;
* &#039;&#039;&#039;Buff Items&#039;&#039;&#039; – items such as Item Bash, Blessed Aim, Frenzy or Defensive Auras&lt;br /&gt;
* &#039;&#039;&#039;Cooltime&#039;&#039;&#039; – how long you must wait before using an item again&lt;br /&gt;
* &#039;&#039;&#039;Duration&#039;&#039;&#039; – how long an item effect stays active&lt;br /&gt;
* &#039;&#039;&#039;Synergistic Effects&#039;&#039;&#039; – temporary effects that support gameplay or other items&lt;br /&gt;
&lt;br /&gt;
== Beginner Item Tips ==&lt;br /&gt;
&lt;br /&gt;
As a beginner, focus on understanding what your items do before trying to optimize everything.&lt;br /&gt;
&lt;br /&gt;
Useful beginner concepts:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Concept&lt;br /&gt;
! Why it matters&lt;br /&gt;
! Page&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Quick Slot Items&#039;&#039;&#039;&lt;br /&gt;
| Used during gameplay for attack, defense, healing or support&lt;br /&gt;
| &#039;&#039;&#039;[[Items#Quick Slot Items|Items → Quick Slot Items]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Buff Items&#039;&#039;&#039;&lt;br /&gt;
| Can improve damage, projectiles, movement, defense or ball behavior&lt;br /&gt;
| &#039;&#039;&#039;[[Items#Buff Items and Synergistic Effects|Items → Buff Items]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Skill Effects&#039;&#039;&#039;&lt;br /&gt;
| Explain what happens when items are used during gameplay&lt;br /&gt;
| &#039;&#039;&#039;[[Skills]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Cooltime&#039;&#039;&#039;&lt;br /&gt;
| Tells you when an item can be used again&lt;br /&gt;
| &#039;&#039;&#039;[[Items#Quick Slot Tooltip Information|Items → Quick Slot Tooltip Information]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Duration&#039;&#039;&#039;&lt;br /&gt;
| Tells you how long an effect stays active&lt;br /&gt;
| &#039;&#039;&#039;[[Items#Quick Slot Tooltip Information|Items → Quick Slot Tooltip Information]]&#039;&#039;&#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A simple beginner-friendly idea is to use offensive Quick Slot items together with suitable Buff Items.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Item Bash&#039;&#039;&#039; can increase item damage.&lt;br /&gt;
* &#039;&#039;&#039;Blessed Aim&#039;&#039;&#039; can add an additional projectile.&lt;br /&gt;
* &#039;&#039;&#039;Defensive Auras&#039;&#039;&#039; can help with survivability.&lt;br /&gt;
&lt;br /&gt;
Do not worry about perfect timing immediately. First learn what each item does.&lt;br /&gt;
&lt;br /&gt;
== Useful Commands for Beginners ==&lt;br /&gt;
&lt;br /&gt;
FT has several ingame chat commands that can be useful for players.&lt;br /&gt;
&lt;br /&gt;
Commands must start with a &#039;&#039;&#039;-&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
For a full command list, see the &#039;&#039;&#039;[[Commands|Commands]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
Useful beginner commands include:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Command&lt;br /&gt;
! Use&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;-help&#039;&#039;&#039;&lt;br /&gt;
| Shows available commands and descriptions&lt;br /&gt;
| Good first command to learn&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;-og &amp;quot;Gacha Name&amp;quot; &amp;lt;amount&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
| Opens gachas without animation&lt;br /&gt;
| Useful when opening many gachas, for example Carnival Pro Coin Gacha&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;-random&#039;&#039;&#039;&lt;br /&gt;
| Enables random Guardians&lt;br /&gt;
| Useful for Guardian variation&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;-hard&#039;&#039;&#039;&lt;br /&gt;
| Enables Hard Mode&lt;br /&gt;
| Makes Guardian harder and should only be used when the room is ready&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;-pb&#039;&#039;&#039;&lt;br /&gt;
| Votes to reset the last point in Basic Mode&lt;br /&gt;
| All players must vote before a new point is made&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tip:&#039;&#039;&#039; New players should start with &#039;&#039;&#039;-help&#039;&#039;&#039; and only use commands like &#039;&#039;&#039;-hard&#039;&#039;&#039; when they understand what the command does.&lt;br /&gt;
&lt;br /&gt;
== Enchanting for Beginners ==&lt;br /&gt;
&lt;br /&gt;
Enchanting is used to improve equipment.&lt;br /&gt;
&lt;br /&gt;
There are two main enchant types:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Attribute Enchants&#039;&#039;&#039; – add STR, STA, DEX or WIL to equipment&lt;br /&gt;
* &#039;&#039;&#039;Element Enchants&#039;&#039;&#039; – add elemental attack or elemental defense effects&lt;br /&gt;
&lt;br /&gt;
Enchanting uses materials such as:&lt;br /&gt;
&lt;br /&gt;
* Tablets&lt;br /&gt;
* Elemental Stones&lt;br /&gt;
* Jewels&lt;br /&gt;
&lt;br /&gt;
New players do not need to focus heavily on perfect enchants immediately. Learn the system first and avoid wasting rare materials too early.&lt;br /&gt;
&lt;br /&gt;
For more details, see the &#039;&#039;&#039;[[Enchantment|Enchantment]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
== Beginner Enchant Tips ==&lt;br /&gt;
&lt;br /&gt;
Enchanting can be useful, but it can also consume important materials.&lt;br /&gt;
&lt;br /&gt;
For beginners:&lt;br /&gt;
&lt;br /&gt;
* Do not waste rare Jewels too early.&lt;br /&gt;
* Learn how enchantment levels work first.&lt;br /&gt;
* Use cheaper materials for early attempts when possible.&lt;br /&gt;
* Read the Enchantment page before pushing high enchantment levels.&lt;br /&gt;
* Focus on understanding your build before investing heavily.&lt;br /&gt;
&lt;br /&gt;
For detailed upgrade strategy and Jewel usage, see &#039;&#039;&#039;[[Enchantment#Recommended Jewel Usage|Recommended Jewel Usage]]&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Game Modes for Beginners ==&lt;br /&gt;
&lt;br /&gt;
FT has different game modes with different learning curves.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Mode&lt;br /&gt;
! Description&lt;br /&gt;
! Beginner note&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Tutorial&#039;&#039;&#039;&lt;br /&gt;
| Teaches the basic controls and match flow&lt;br /&gt;
| Always recommended for completely new players&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Single Play&#039;&#039;&#039;&lt;br /&gt;
| Lets players practice against NPCs&lt;br /&gt;
| Good for learning Basic and Battle-style mechanics&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Guardian&#039;&#039;&#039;&lt;br /&gt;
| Progression-focused mode against NPC enemies and bosses&lt;br /&gt;
| Recommended for leveling, Gold and early rewards&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Basic&#039;&#039;&#039;&lt;br /&gt;
| Tennis-focused mode&lt;br /&gt;
| Best after learning movement, timing and ball control&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Battle&#039;&#039;&#039;&lt;br /&gt;
| More chaotic mode with Quick Slot items and combat effects&lt;br /&gt;
| Easier after learning Items, Skills, Stats and buffs&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Basic and Battle are more enjoyable once you have some levels, items and general experience.&lt;br /&gt;
&lt;br /&gt;
== When to Try Basic or Battle ==&lt;br /&gt;
&lt;br /&gt;
You can try Basic or Battle whenever you want, but they may feel difficult at the beginning.&lt;br /&gt;
&lt;br /&gt;
Before focusing on Basic or Battle, it helps to understand:&lt;br /&gt;
&lt;br /&gt;
* Movement&lt;br /&gt;
* Timing&lt;br /&gt;
* Stats&lt;br /&gt;
* Quick Slot items&lt;br /&gt;
* Skill effects&lt;br /&gt;
* Buff Items&lt;br /&gt;
* Enchanting&lt;br /&gt;
* Useful commands&lt;br /&gt;
* Your character build&lt;br /&gt;
&lt;br /&gt;
A good rule for beginners:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Build your foundation in Tutorial, Single Play and Guardian first, then explore Basic and Battle.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Common Beginner Mistakes ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Mistake&lt;br /&gt;
! Better approach&lt;br /&gt;
|-&lt;br /&gt;
| Skipping the Tutorial&lt;br /&gt;
| Complete the Tutorial first to learn basic controls and timing&lt;br /&gt;
|-&lt;br /&gt;
| Ignoring Single Play&lt;br /&gt;
| Use Single Play to practice against NPCs and understand Basic / Battle mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Going into Basic or Battle too early&lt;br /&gt;
| Start with Guardian Mode to build your character first&lt;br /&gt;
|-&lt;br /&gt;
| Ignoring Guardian progression&lt;br /&gt;
| Use Guardian to level up, earn rewards and learn the game&lt;br /&gt;
|-&lt;br /&gt;
| Overthinking builds too early&lt;br /&gt;
| Start with a simple STR / DEX damage build first&lt;br /&gt;
|-&lt;br /&gt;
| Starting with a specialized build too early&lt;br /&gt;
| Try specialized builds such as WIL / STA later&lt;br /&gt;
|-&lt;br /&gt;
| Expecting every player to play WIL&lt;br /&gt;
| In Guardian Mode, damage dealers are usually still needed, and often only one WIL-focused player is enough&lt;br /&gt;
|-&lt;br /&gt;
| Using items without understanding their effect&lt;br /&gt;
| Read the Items and Skills pages to understand item usage and gameplay effects&lt;br /&gt;
|-&lt;br /&gt;
| Using commands without understanding them&lt;br /&gt;
| Start with -help and avoid using mode-changing commands carelessly&lt;br /&gt;
|-&lt;br /&gt;
| Wasting rare materials too soon&lt;br /&gt;
| Learn Enchanting before using rare Jewels or valuable materials&lt;br /&gt;
|-&lt;br /&gt;
| Confusing stats, items and skills&lt;br /&gt;
| Use the Stats, Items and Skills pages to understand the terminology&lt;br /&gt;
|-&lt;br /&gt;
| Not asking for help&lt;br /&gt;
| Ask the community when you are confused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Recommended Progression ==&lt;br /&gt;
&lt;br /&gt;
A simple beginner progression path:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Stage&lt;br /&gt;
! Focus&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;1. First Start&#039;&#039;&#039;&lt;br /&gt;
| Install and update the game&lt;br /&gt;
| Use the &#039;&#039;&#039;[[Game Installation Guide]]&#039;&#039;&#039; if needed&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;2. Tutorial&#039;&#039;&#039;&lt;br /&gt;
| Learn controls and basic gameplay&lt;br /&gt;
| Available directly from the main menu&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;3. Single Play&#039;&#039;&#039;&lt;br /&gt;
| Practice against NPCs&lt;br /&gt;
| Useful for learning Basic and Battle mechanics, collecting early items and Gold&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;4. Guardian Mode&#039;&#039;&#039;&lt;br /&gt;
| Level up and collect rewards&lt;br /&gt;
| Best early progression path for character growth&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;5. Learn Core Systems&#039;&#039;&#039;&lt;br /&gt;
| Stats, Items, Skills, Commands and Enchant&lt;br /&gt;
| Helps you understand builds and progression&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;6. Choose a Beginner Build&#039;&#039;&#039;&lt;br /&gt;
| STR / DEX damage build&lt;br /&gt;
| Around 60 DEX and the rest into STR is a simple beginner-friendly direction&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;7. Improve Character&#039;&#039;&#039;&lt;br /&gt;
| Better items, stats and equipment&lt;br /&gt;
| Do not rush rare upgrades too early&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;8. Try Basic / Battle&#039;&#039;&#039;&lt;br /&gt;
| PvP and more competitive modes&lt;br /&gt;
| Easier after building a foundation&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Beginner Checklist ==&lt;br /&gt;
&lt;br /&gt;
Use this checklist if you are not sure what to do next.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Done?&lt;br /&gt;
! Task&lt;br /&gt;
! Related page&lt;br /&gt;
|-&lt;br /&gt;
| ☐&lt;br /&gt;
| Install and update the game&lt;br /&gt;
| &#039;&#039;&#039;[[Game Installation Guide]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| ☐&lt;br /&gt;
| Fix launcher or missing DLL issues if needed&lt;br /&gt;
| &#039;&#039;&#039;[[Troubleshooting]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| ☐&lt;br /&gt;
| Complete the Tutorial&lt;br /&gt;
| This guide&lt;br /&gt;
|-&lt;br /&gt;
| ☐&lt;br /&gt;
| Try Single Play against NPCs&lt;br /&gt;
| This guide&lt;br /&gt;
|-&lt;br /&gt;
| ☐&lt;br /&gt;
| Start Guardian Mode&lt;br /&gt;
| This guide&lt;br /&gt;
|-&lt;br /&gt;
| ☐&lt;br /&gt;
| Learn what STR, STA, DEX and WIL do&lt;br /&gt;
| &#039;&#039;&#039;[[Stats]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| ☐&lt;br /&gt;
| Start with a STR / DEX damage build&lt;br /&gt;
| &#039;&#039;&#039;[[Stats#STR|STR]]&#039;&#039;&#039; / &#039;&#039;&#039;[[Stats#DEX|DEX]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| ☐&lt;br /&gt;
| Learn Quick Slot Items and Buff Items&lt;br /&gt;
| &#039;&#039;&#039;[[Items]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| ☐&lt;br /&gt;
| Learn what skill effects do during gameplay&lt;br /&gt;
| &#039;&#039;&#039;[[Skills]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| ☐&lt;br /&gt;
| Learn useful ingame commands&lt;br /&gt;
| &#039;&#039;&#039;[[Commands]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| ☐&lt;br /&gt;
| Learn Enchanting before using rare materials&lt;br /&gt;
| &#039;&#039;&#039;[[Enchantment]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| ☐&lt;br /&gt;
| Ask the community for help if confused&lt;br /&gt;
| Discord / in-game community&lt;br /&gt;
|}&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, stats, builds, commands and what to do first.&lt;br /&gt;
&lt;br /&gt;
Asking questions in Discord or in game can help you avoid common mistakes and understand the game faster.&lt;br /&gt;
&lt;br /&gt;
Most experienced players would rather answer beginner questions than see new players quit because the game feels overwhelming.&lt;br /&gt;
&lt;br /&gt;
== Final Tips ==&lt;br /&gt;
&lt;br /&gt;
If you are completely new, do not rush.&lt;br /&gt;
&lt;br /&gt;
Start with the Tutorial, practice in Single Play, focus on Guardian Mode, learn the core systems step by step, and ask the community for help when needed.&lt;br /&gt;
&lt;br /&gt;
A good early goal is not to build the perfect character immediately. It is to understand how the game works and avoid wasting important resources too early.&lt;br /&gt;
&lt;br /&gt;
For most beginners, a simple &#039;&#039;&#039;STR / DEX damage build&#039;&#039;&#039; is a good starting point because it is easier to play and useful in Guardian Mode.&lt;br /&gt;
&lt;br /&gt;
== Related Pages ==&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[[Game Installation Guide]]&#039;&#039;&#039; – explains how to install and start the game.&lt;br /&gt;
* &#039;&#039;&#039;[[Troubleshooting]]&#039;&#039;&#039; – explains common errors and fixes.&lt;br /&gt;
* &#039;&#039;&#039;[[Stats]]&#039;&#039;&#039; – explains attributes, secondary stats and formulas.&lt;br /&gt;
* &#039;&#039;&#039;[[Items]]&#039;&#039;&#039; – explains Quick Slot items, Buff Items and item systems.&lt;br /&gt;
* &#039;&#039;&#039;[[Skills]]&#039;&#039;&#039; – explains gameplay effects, usage, timing and synergies.&lt;br /&gt;
* &#039;&#039;&#039;[[Enchantment]]&#039;&#039;&#039; – explains Tablets, Elemental Stones, Jewels and the Enchant system.&lt;br /&gt;
* &#039;&#039;&#039;[[Commands]]&#039;&#039;&#039; – lists useful ingame chat commands.&lt;br /&gt;
* &#039;&#039;&#039;[[Maps]]&#039;&#039;&#039; – shows map information and Gacha availability.&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=Stats&amp;diff=323</id>
		<title>Stats</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Stats&amp;diff=323"/>
		<updated>2026-07-20T19:57:08Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: add Guides category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Attribute Points ==&lt;br /&gt;
&lt;br /&gt;
Attribute points are character-specific.&lt;br /&gt;
&lt;br /&gt;
Each character has its own level, Attribute points and stat distribution.&lt;br /&gt;
&lt;br /&gt;
Attribute points can be assigned to:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;STR&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;STA&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;DEX&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;WIL&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
New characters start with &#039;&#039;&#039;5 free Attribute points&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Characters gain additional Attribute points while leveling, but only up to &#039;&#039;&#039;Level 65&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This means a character can gain up to &#039;&#039;&#039;69 free Attribute points&#039;&#039;&#039; in total:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
5 starting points + 64 points from leveling = 69 total Attribute points&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After Level 65, characters can still continue leveling, but they no longer gain additional free Attribute points.&lt;br /&gt;
&lt;br /&gt;
These points are distributed separately for each character.&lt;br /&gt;
&lt;br /&gt;
== Attribute Overview ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Attribute&lt;br /&gt;
! Main role&lt;br /&gt;
! Good for&lt;br /&gt;
! Beginner note&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;STR&#039;&#039;&#039;&lt;br /&gt;
| Damage and offensive pressure&lt;br /&gt;
| Skill / item damage setups, attack pressure, ball speed pressure&lt;br /&gt;
| Good for simple damage-focused builds&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;STA&#039;&#039;&#039;&lt;br /&gt;
| Defense and survivability&lt;br /&gt;
| Taking less damage, surviving longer&lt;br /&gt;
| Useful, but usually not the first beginner focus&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;DEX&#039;&#039;&#039;&lt;br /&gt;
| Movement and projectile behavior&lt;br /&gt;
| Mobility, responsiveness, Meteor variants / Homing Soul shot count&lt;br /&gt;
| Very useful for beginners, especially around 60 DEX&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;WIL&#039;&#039;&#039;&lt;br /&gt;
| Ball damage and ball behavior&lt;br /&gt;
| Ball damage, curve, spin-focused setups&lt;br /&gt;
| Stronger for specialized ball-focused builds&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== STR ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;STR&#039;&#039;&#039; is mainly useful for offensive pressure and skill / item damage.&lt;br /&gt;
&lt;br /&gt;
It increases the damage bonus used by skill or item damage calculations.&lt;br /&gt;
&lt;br /&gt;
=== Formula ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DamageBonus = floor(STR × 0.35)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Examples ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! STR&lt;br /&gt;
! Damage bonus&lt;br /&gt;
|-&lt;br /&gt;
| 20&lt;br /&gt;
| 7&lt;br /&gt;
|-&lt;br /&gt;
| 40&lt;br /&gt;
| 14&lt;br /&gt;
|-&lt;br /&gt;
| 60&lt;br /&gt;
| 21&lt;br /&gt;
|-&lt;br /&gt;
| 100&lt;br /&gt;
| 35&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;jftse-calculator&amp;quot; data-calc=&amp;quot;str&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Beginner note:&#039;&#039;&#039; STR is simple and useful for damage-focused Guardian builds, especially when combined with DEX.&lt;br /&gt;
&lt;br /&gt;
== STA ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;STA&#039;&#039;&#039; is mainly useful for survivability.&lt;br /&gt;
&lt;br /&gt;
It reduces incoming damage and supports defensive builds.&lt;br /&gt;
&lt;br /&gt;
=== Formula ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DamageReduction = floor(STA × 0.30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Examples ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! STA&lt;br /&gt;
! Damage reduction&lt;br /&gt;
|-&lt;br /&gt;
| 20&lt;br /&gt;
| 6&lt;br /&gt;
|-&lt;br /&gt;
| 40&lt;br /&gt;
| 12&lt;br /&gt;
|-&lt;br /&gt;
| 60&lt;br /&gt;
| 18&lt;br /&gt;
|-&lt;br /&gt;
| 100&lt;br /&gt;
| 30&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;jftse-calculator&amp;quot; data-calc=&amp;quot;sta&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Beginner note:&#039;&#039;&#039; STA helps you survive, but most beginners usually get more early value from a simple STR / DEX damage setup.&lt;br /&gt;
&lt;br /&gt;
== DEX ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DEX&#039;&#039;&#039; is useful for movement, responsiveness and some projectile-based effects.&lt;br /&gt;
&lt;br /&gt;
It is one of the most beginner-friendly Attributes because it helps with general gameplay and certain damage setups.&lt;br /&gt;
&lt;br /&gt;
== Projectile Shot Count ==&lt;br /&gt;
&lt;br /&gt;
Some projectile-based effects can gain additional shots at specific DEX values.&lt;br /&gt;
&lt;br /&gt;
This applies to effects such as:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Meteor variants&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Homing Soul&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! DEX&lt;br /&gt;
! Additional shots&lt;br /&gt;
|-&lt;br /&gt;
| 0–29&lt;br /&gt;
| +0&lt;br /&gt;
|-&lt;br /&gt;
| 30–39&lt;br /&gt;
| +1&lt;br /&gt;
|-&lt;br /&gt;
| 40–49&lt;br /&gt;
| +2&lt;br /&gt;
|-&lt;br /&gt;
| 50–59&lt;br /&gt;
| +3&lt;br /&gt;
|-&lt;br /&gt;
| 60+&lt;br /&gt;
| +4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Beginner note:&#039;&#039;&#039; This is why around &#039;&#039;&#039;60 DEX&#039;&#039;&#039; is often recommended for simple damage-focused builds.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important:&#039;&#039;&#039; DEX does not increase the projectile count of every skill effect. It only applies to specific projectile-based effects.&lt;br /&gt;
&lt;br /&gt;
== WIL ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;WIL&#039;&#039;&#039; is mainly useful for ball damage and ball-focused gameplay.&lt;br /&gt;
&lt;br /&gt;
It affects ball damage and is especially relevant in ball-pressure or Guardian damage setups.&lt;br /&gt;
&lt;br /&gt;
=== Ball Damage Formula ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TotalBallDamage = max(20, 10 + floor(WIL × 0.52))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Equivalent form:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TotalBallDamage = max(20, 10 + floor(WIL × 13 / 25))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; 20 is the minimum value, not the base value.&lt;br /&gt;
&lt;br /&gt;
=== Examples ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! WIL&lt;br /&gt;
! Ball damage&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| 20&lt;br /&gt;
|-&lt;br /&gt;
| 30&lt;br /&gt;
| 25&lt;br /&gt;
|-&lt;br /&gt;
| 60&lt;br /&gt;
| 41&lt;br /&gt;
|-&lt;br /&gt;
| 100&lt;br /&gt;
| 62&lt;br /&gt;
|-&lt;br /&gt;
| 135&lt;br /&gt;
| 80&lt;br /&gt;
|-&lt;br /&gt;
| 160&lt;br /&gt;
| 93&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;jftse-calculator&amp;quot; data-calc=&amp;quot;wil&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Guardian WIL Damage ==&lt;br /&gt;
&lt;br /&gt;
In Guardian Mode, WIL can add extra ball damage based on the Guardian&#039;s maximum health.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Extra Guardian Ball Damage = 0.0001 × WIL × Guardian maximum health&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;jftse-calculator&amp;quot; data-calc=&amp;quot;guardian-wil&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Beginner note:&#039;&#039;&#039; WIL can be useful, but WIL-focused builds are usually more specialized than STR / DEX damage builds.&lt;br /&gt;
&lt;br /&gt;
== HP ==&lt;br /&gt;
&lt;br /&gt;
HP is a survivability value.&lt;br /&gt;
&lt;br /&gt;
Player HP increases with level.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
HP = 200 + 5 × (Level - 1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Level&lt;br /&gt;
! HP&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 200&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 245&lt;br /&gt;
|-&lt;br /&gt;
| 20&lt;br /&gt;
| 295&lt;br /&gt;
|-&lt;br /&gt;
| 30&lt;br /&gt;
| 345&lt;br /&gt;
|-&lt;br /&gt;
| 40&lt;br /&gt;
| 395&lt;br /&gt;
|-&lt;br /&gt;
| 50&lt;br /&gt;
| 445&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Additional HP can also come from equipment or other effects.&lt;br /&gt;
&lt;br /&gt;
== Secondary Stats ==&lt;br /&gt;
&lt;br /&gt;
Secondary Stats are bonuses that are not STR, STA, DEX or WIL.&lt;br /&gt;
&lt;br /&gt;
They can come from equipment, items, buffs or other systems.&lt;br /&gt;
&lt;br /&gt;
Common examples:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Secondary Stat&lt;br /&gt;
! What it affects&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Move Speed&#039;&#039;&#039;&lt;br /&gt;
| Character movement speed&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Chargeshot Speed&#039;&#039;&#039;&lt;br /&gt;
| Speed of charged shots&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Ball Speed&#039;&#039;&#039;&lt;br /&gt;
| How fast the ball travels&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Ball Damage&#039;&#039;&#039;&lt;br /&gt;
| Damage caused by the ball&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Ball Spin&#039;&#039;&#039;&lt;br /&gt;
| Spin / ball behavior&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;HP&#039;&#039;&#039;&lt;br /&gt;
| Survivability&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important:&#039;&#039;&#039; Secondary Stats can be related to Attributes, but they are not the same thing.&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;DEX&#039;&#039;&#039; is an Attribute.&lt;br /&gt;
* &#039;&#039;&#039;Chargeshot Speed&#039;&#039;&#039; is a Secondary Stat / item effect.&lt;br /&gt;
* &#039;&#039;&#039;Frenzy&#039;&#039;&#039; can increase Chargeshot Speed as an item effect.&lt;br /&gt;
* &#039;&#039;&#039;WIL&#039;&#039;&#039; affects ball damage, while &#039;&#039;&#039;Power Strike&#039;&#039;&#039; is an item effect that can increase ball damage.&lt;br /&gt;
* &#039;&#039;&#039;STR&#039;&#039;&#039; is related to damage calculations, while &#039;&#039;&#039;Item Bash&#039;&#039;&#039; is a Buff Item that increases item damage.&lt;br /&gt;
&lt;br /&gt;
== Beginner Build Notes ==&lt;br /&gt;
&lt;br /&gt;
New players do not need a perfect build immediately.&lt;br /&gt;
&lt;br /&gt;
A simple beginner-friendly direction is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;around 60 DEX → remaining points into STR&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This works well because:&lt;br /&gt;
&lt;br /&gt;
* DEX helps with movement and projectile behavior.&lt;br /&gt;
* 60 DEX is an important breakpoint for certain projectile effects.&lt;br /&gt;
* STR supports simple damage-focused gameplay.&lt;br /&gt;
* The build is easier to understand than specialized WIL / STA setups.&lt;br /&gt;
* Guardian rooms usually still need damage dealers.&lt;br /&gt;
&lt;br /&gt;
WIL / STA builds can be useful, but they are usually more specialized and more dependent on team setup.&lt;br /&gt;
&lt;br /&gt;
For more beginner progression advice, see the &#039;&#039;&#039;[[Beginner&#039;s Guide|Beginner&#039;s Guide]]&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Mode Relevance ==&lt;br /&gt;
&lt;br /&gt;
Stats can matter differently depending on the game mode.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Attribute&lt;br /&gt;
! Basic Mode&lt;br /&gt;
! Battle Mode&lt;br /&gt;
! Guardian Mode&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;STR&#039;&#039;&#039;&lt;br /&gt;
| Ball speed and offensive pressure&lt;br /&gt;
| Skill / item damage bonus and ball speed pressure&lt;br /&gt;
| Damage-focused progression and offensive pressure&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;STA&#039;&#039;&#039;&lt;br /&gt;
| Lower direct value compared to Battle / Guardian&lt;br /&gt;
| Damage reduction and survival&lt;br /&gt;
| Damage reduction and survival&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;DEX&#039;&#039;&#039;&lt;br /&gt;
| Movement, turning and responsiveness&lt;br /&gt;
| Movement, projectile behavior and charge-related gameplay&lt;br /&gt;
| Movement, projectile setups and charge-related gameplay&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;WIL&#039;&#039;&#039;&lt;br /&gt;
| Ball behavior, curve and ball pressure&lt;br /&gt;
| Ball damage, ball pressure and curve-based play&lt;br /&gt;
| Guardian ball damage and ball-focused setups&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Basic Mode ===&lt;br /&gt;
&lt;br /&gt;
In Basic Mode, stats mostly affect ball behavior, movement and timing.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;STR&#039;&#039;&#039; supports ball speed and offensive pressure.&lt;br /&gt;
* &#039;&#039;&#039;DEX&#039;&#039;&#039; supports movement, turning and responsiveness.&lt;br /&gt;
* &#039;&#039;&#039;WIL&#039;&#039;&#039; supports ball behavior, curve and ball pressure.&lt;br /&gt;
* &#039;&#039;&#039;STA&#039;&#039;&#039; has less direct value than in Battle or Guardian content, because Basic Mode is less focused on damage trading.&lt;br /&gt;
&lt;br /&gt;
=== Battle Mode ===&lt;br /&gt;
&lt;br /&gt;
Battle Mode uses more stat, item and skill interactions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;STR&#039;&#039;&#039; supports skill / item damage bonus and offensive pressure.&lt;br /&gt;
* &#039;&#039;&#039;STA&#039;&#039;&#039; supports damage reduction and survival.&lt;br /&gt;
* &#039;&#039;&#039;DEX&#039;&#039;&#039; supports movement, projectile setups and charge-related gameplay.&lt;br /&gt;
* &#039;&#039;&#039;WIL&#039;&#039;&#039; supports ball damage, ball pressure and curve-based play.&lt;br /&gt;
&lt;br /&gt;
Quick Slot items and Buff Items can affect similar gameplay areas.&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Item Bash&#039;&#039;&#039; supports item damage setups.&lt;br /&gt;
* &#039;&#039;&#039;Blessed Aim&#039;&#039;&#039; supports projectile setups.&lt;br /&gt;
* &#039;&#039;&#039;Defensive Auras&#039;&#039;&#039; supports survivability setups.&lt;br /&gt;
* &#039;&#039;&#039;Power Strike&#039;&#039;&#039; supports ball-damage setups.&lt;br /&gt;
&lt;br /&gt;
=== Guardian Mode ===&lt;br /&gt;
&lt;br /&gt;
Guardian Mode uses dedicated scenario and combat systems.&lt;br /&gt;
&lt;br /&gt;
Stats can still be very relevant:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;STR&#039;&#039;&#039; supports damage-focused progression and offensive pressure.&lt;br /&gt;
* &#039;&#039;&#039;STA&#039;&#039;&#039; supports survivability and damage reduction.&lt;br /&gt;
* &#039;&#039;&#039;DEX&#039;&#039;&#039; supports movement and projectile-based setups.&lt;br /&gt;
* &#039;&#039;&#039;WIL&#039;&#039;&#039; supports Guardian ball damage and ball-focused setups.&lt;br /&gt;
&lt;br /&gt;
A common beginner-friendly Guardian direction is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;around 60 DEX → remaining points into STR&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This works well because &#039;&#039;&#039;DEX&#039;&#039;&#039; helps with movement and projectile-based effects, while &#039;&#039;&#039;STR&#039;&#039;&#039; supports straightforward damage-focused gameplay.&lt;br /&gt;
&lt;br /&gt;
== Related Pages ==&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[[Items]]&#039;&#039;&#039; – explains Quick Slot items, Buff Items and item systems.&lt;br /&gt;
* &#039;&#039;&#039;[[Skills]]&#039;&#039;&#039; – explains gameplay effects, usage, timing and synergies.&lt;br /&gt;
* &#039;&#039;&#039;[[Enchantment]]&#039;&#039;&#039; – explains Tablets, Elemental Stones, Jewels and equipment improvement.&lt;br /&gt;
* &#039;&#039;&#039;[[Beginner&#039;s Guide]]&#039;&#039;&#039; – explains the recommended starting path for new players.&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=Items&amp;diff=322</id>
		<title>Items</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Items&amp;diff=322"/>
		<updated>2026-07-20T19:55:14Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: add Guides category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Overview ==&lt;br /&gt;
&lt;br /&gt;
Items are objects that players can buy, own, use, consume or manage in FT.&lt;br /&gt;
&lt;br /&gt;
This page focuses on the player-facing item systems, especially:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Special&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Quick Slot&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Tools&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Clublogo&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Balls&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This page does not try to be a complete shop database. Prices, temporary availability, event items and shop changes should be checked in game.&lt;br /&gt;
&lt;br /&gt;
Costume and equipment pieces such as clothing, rackets, shoes, gloves and accessories should be documented separately on a &#039;&#039;&#039;Costume / Equipment&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
Housing should be documented separately on a &#039;&#039;&#039;Housing&#039;&#039;&#039; page, because it is a progression system with houses, furniture, Housing Points and Gold / EXP bonuses.&lt;br /&gt;
&lt;br /&gt;
For stats such as STR, STA, DEX and WIL, see the &#039;&#039;&#039;[[Stats|Stats]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
For gameplay effects triggered by items, see the &#039;&#039;&#039;[[Skills|Skills]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
For equipment improvement, see the &#039;&#039;&#039;[[Enchantment|Enchantment]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
== Item Terminology ==&lt;br /&gt;
&lt;br /&gt;
FT uses items in several different ways. Some items are consumed, some are time-limited, some are equipped, and some trigger gameplay effects.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Term&lt;br /&gt;
! Meaning&lt;br /&gt;
! Example&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Item&#039;&#039;&#039;&lt;br /&gt;
| An object the player can own, buy, use, equip or consume&lt;br /&gt;
| Fireball, Quick Slot Changer, Ball items&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Product&#039;&#039;&#039;&lt;br /&gt;
| A shop entry that can contain one or more items&lt;br /&gt;
| Item bundles, Quick Slot sets&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Category&#039;&#039;&#039;&lt;br /&gt;
| The item group used by the shop or game data&lt;br /&gt;
| QUICK, SPECIAL, TOOL, BALL&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Quick Slot Item&#039;&#039;&#039;&lt;br /&gt;
| An item that can be equipped into a Quick Slot and activated during gameplay&lt;br /&gt;
| Fireball, Meteor, Healing, Item Bash&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Skill Effect&#039;&#039;&#039;&lt;br /&gt;
| The gameplay effect triggered when an item is used&lt;br /&gt;
| Fireball attack, Healing effect, Damage Increase buff&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Count / Charges&#039;&#039;&#039;&lt;br /&gt;
| Number of uses available for an item&lt;br /&gt;
| Using Fireball consumes one charge&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Cooltime&#039;&#039;&#039;&lt;br /&gt;
| Waiting time before an item or effect can be used again&lt;br /&gt;
| Battle Mode Cooltime, Guardian Mode Cooltime&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Duration&#039;&#039;&#039;&lt;br /&gt;
| How long an effect stays active&lt;br /&gt;
| Buff duration&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A clear way to describe many FT items is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The item is what you buy, equip and manage. The effect is what happens during gameplay when you use it.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Fireball&#039;&#039;&#039; is a Quick Slot item, but using it triggers a Fireball attack effect.&lt;br /&gt;
&lt;br /&gt;
== Item Categories ==&lt;br /&gt;
&lt;br /&gt;
The game data contains several item categories. For the wiki, these should be separated by player-facing purpose.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Category&lt;br /&gt;
! Meaning&lt;br /&gt;
! Recommended wiki usage&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;SPECIAL&#039;&#039;&#039;&lt;br /&gt;
| Special utility or convenience items&lt;br /&gt;
| This page&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;QUICK&#039;&#039;&#039;&lt;br /&gt;
| Quick Slot items&lt;br /&gt;
| This page and &#039;&#039;&#039;[[Skills]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;TOOL&#039;&#039;&#039;&lt;br /&gt;
| Utility tools&lt;br /&gt;
| This page&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;BALL&#039;&#039;&#039;&lt;br /&gt;
| Ball items&lt;br /&gt;
| This page&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;ENCHANT&#039;&#039;&#039;&lt;br /&gt;
| Enchantment materials&lt;br /&gt;
| &#039;&#039;&#039;[[Enchantment]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;PARTS&#039;&#039;&#039;&lt;br /&gt;
| Costume / equipment parts&lt;br /&gt;
| Costume / Equipment page&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;HOUSE&#039;&#039;&#039;&lt;br /&gt;
| House items&lt;br /&gt;
| Housing page&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;HOUSE_DECO&#039;&#039;&#039;&lt;br /&gt;
| Housing decoration items&lt;br /&gt;
| Housing page&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;MATERIAL&#039;&#039;&#039;&lt;br /&gt;
| Materials&lt;br /&gt;
| Material / Crafting page if needed&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;RECIPE&#039;&#039;&#039;&lt;br /&gt;
| Recipes&lt;br /&gt;
| Recipe / Crafting page if needed&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;SKILL&#039;&#039;&#039;&lt;br /&gt;
| Skill-related item category in the data&lt;br /&gt;
| &#039;&#039;&#039;[[Skills]]&#039;&#039;&#039; page if needed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important:&#039;&#039;&#039; This page should focus on item usage and item systems. It should not try to document every internal item category in detail.&lt;br /&gt;
&lt;br /&gt;
== Item Use Types ==&lt;br /&gt;
&lt;br /&gt;
Items can have different use types.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Use Type&lt;br /&gt;
! Meaning&lt;br /&gt;
! Example usage&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Count&#039;&#039;&#039;&lt;br /&gt;
| The item has a limited number of uses or charges&lt;br /&gt;
| Quick Slot items, Megaphone, Quick Slot Changer&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Time&#039;&#039;&#039;&lt;br /&gt;
| The item is active for a limited time period&lt;br /&gt;
| Time-limited tools or special items&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Durable&#039;&#039;&#039;&lt;br /&gt;
| The item is consumed through repeated use or has durability-style usage&lt;br /&gt;
| Reward rings, ball items&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Instant&#039;&#039;&#039;&lt;br /&gt;
| The item is used immediately&lt;br /&gt;
| Stat reset, inventory expansion, nickname change&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Shop Products and Availability ==&lt;br /&gt;
&lt;br /&gt;
A shop product can contain one or more items.&lt;br /&gt;
&lt;br /&gt;
Most simple products contain one item, but some products can contain several items as a bundle or set.&lt;br /&gt;
&lt;br /&gt;
Some products exist in the data but may be disabled, event-only, promotional or not normally available.&lt;br /&gt;
&lt;br /&gt;
Because of this, the wiki should avoid treating every data entry as a currently available shop item.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tip:&#039;&#039;&#039; Use the in-game shop as the source for current availability and prices.&lt;br /&gt;
&lt;br /&gt;
== Quick Slot Items ==&lt;br /&gt;
&lt;br /&gt;
Quick Slot items are usable items that can be equipped into Quick Slots and activated during gameplay.&lt;br /&gt;
&lt;br /&gt;
They are commonly used for:&lt;br /&gt;
&lt;br /&gt;
* Attacks&lt;br /&gt;
* Healing&lt;br /&gt;
* Defense&lt;br /&gt;
* Support&lt;br /&gt;
* Control&lt;br /&gt;
* Temporary buffs&lt;br /&gt;
&lt;br /&gt;
In the current data structure, Quick Slot equipment contains five slots:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Slot 1&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Slot 2&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Slot 3&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Slot 4&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Slot 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
When a Quick Slot item is used, its item count is reduced by 1.&lt;br /&gt;
&lt;br /&gt;
If the count reaches 0, the item is removed from the pocket / inventory and also removed from the equipped Quick Slot.&lt;br /&gt;
&lt;br /&gt;
This means Quick Slot items are usually limited-use items.&lt;br /&gt;
&lt;br /&gt;
== Quick Slot Items and Skill Effects ==&lt;br /&gt;
&lt;br /&gt;
Quick Slot items and skills are connected, but they are not exactly the same thing.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Side&lt;br /&gt;
! Meaning&lt;br /&gt;
! Example&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Item side&#039;&#039;&#039;&lt;br /&gt;
| What the player buys, equips, manages and consumes&lt;br /&gt;
| Fireball item with Count and Cooltime&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Gameplay effect side&#039;&#039;&#039;&lt;br /&gt;
| What happens during the match when used&lt;br /&gt;
| Fireball attack effect&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For item details, use this page.&lt;br /&gt;
&lt;br /&gt;
For gameplay usage, synergies and effect roles, see the &#039;&#039;&#039;[[Skills|Skills]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
== Quick Slot Tooltip Information ==&lt;br /&gt;
&lt;br /&gt;
Quick Slot item tooltips can show several important fields.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Tooltip field&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Count&#039;&#039;&#039;&lt;br /&gt;
| Number of uses or charges received&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Element&#039;&#039;&#039;&lt;br /&gt;
| Element of the effect, if any&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Battle Mode Cooltime&#039;&#039;&#039;&lt;br /&gt;
| Cooldown time in Battle Mode&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Guardian Mode Cooltime&#039;&#039;&#039;&lt;br /&gt;
| Cooldown time in Guardian Mode&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Duration&#039;&#039;&#039;&lt;br /&gt;
| How long a buff or effect stays active&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
| Short explanation of what the item does&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Synergistic&#039;&#039;&#039;&lt;br /&gt;
| Buff or support effect attached to the item&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The game uses the term &#039;&#039;&#039;Cooltime&#039;&#039;&#039; for cooldown time.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cooltime&#039;&#039;&#039; means how long you must wait before using the item again.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Duration&#039;&#039;&#039; means how long the item effect stays active.&lt;br /&gt;
&lt;br /&gt;
== Quick Slot Item Types ==&lt;br /&gt;
&lt;br /&gt;
Quick Slot items can be grouped by their main gameplay purpose.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Type&lt;br /&gt;
! Purpose&lt;br /&gt;
! Examples&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Attack&#039;&#039;&#039;&lt;br /&gt;
| Deals damage or applies offensive pressure&lt;br /&gt;
| Fireball, Meteor, Blizzard, Homing Soul, Inferno&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Projectile Pressure&#039;&#039;&#039;&lt;br /&gt;
| Uses projectile-based pressure or multiple shots&lt;br /&gt;
| Meteor, Homing Soul, Blizzard&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Area Control / Trap&#039;&#039;&#039;&lt;br /&gt;
| Controls space or punishes movement&lt;br /&gt;
| Magic Circle, Crab trap, Inferno&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Control&#039;&#039;&#039;&lt;br /&gt;
| Disrupts, disables or transforms opponents&lt;br /&gt;
| Flash Bomb, Polymorph, Minimum, Kiss of Medusa&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Defense&#039;&#039;&#039;&lt;br /&gt;
| Protects the player or reduces pressure&lt;br /&gt;
| Holy Shield, Area Shield&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Recovery&#039;&#039;&#039;&lt;br /&gt;
| Restores strength or heals players&lt;br /&gt;
| Potions, Healing&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Support&#039;&#039;&#039;&lt;br /&gt;
| Helps teammates or changes match state&lt;br /&gt;
| Rebirth, Sandglass&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Buff&#039;&#039;&#039;&lt;br /&gt;
| Temporarily improves damage, projectiles, movement, defense or ball behavior&lt;br /&gt;
| Item Bash, Blessed Aim, Defensive Auras, Frenzy&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Common Quick Slot Item List ==&lt;br /&gt;
&lt;br /&gt;
The following list contains common Quick Slot items. It is not meant to be a complete availability or shop-price list.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Item&lt;br /&gt;
! Type&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Holy Shield&#039;&#039;&#039;&lt;br /&gt;
| Defense&lt;br /&gt;
| Defensive protection item&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Rebirth&#039;&#039;&#039;&lt;br /&gt;
| Support&lt;br /&gt;
| Revives a teammate&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Medium Potion&#039;&#039;&#039;&lt;br /&gt;
| Recovery&lt;br /&gt;
| Restores strength&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Large Potion&#039;&#039;&#039;&lt;br /&gt;
| Recovery&lt;br /&gt;
| Restores strength&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Magic Potion&#039;&#039;&#039;&lt;br /&gt;
| Recovery&lt;br /&gt;
| Restores strength&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Meteor&#039;&#039;&#039;&lt;br /&gt;
| Attack / Projectile&lt;br /&gt;
| Strong projectile pressure&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Homing Soul&#039;&#039;&#039;&lt;br /&gt;
| Attack / Projectile&lt;br /&gt;
| Tracking pressure&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Fireball&#039;&#039;&#039;&lt;br /&gt;
| Attack&lt;br /&gt;
| Simple direct attack item&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Polymorph&#039;&#039;&#039;&lt;br /&gt;
| Control&lt;br /&gt;
| Transformation / disruption&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Minimum&#039;&#039;&#039;&lt;br /&gt;
| Control&lt;br /&gt;
| Disruption effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Flash Bomb&#039;&#039;&#039;&lt;br /&gt;
| Control&lt;br /&gt;
| Blind / disruption&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Crab trap&#039;&#039;&#039;&lt;br /&gt;
| Trap / Control&lt;br /&gt;
| Trap effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Blizzard&#039;&#039;&#039;&lt;br /&gt;
| Attack / Pressure&lt;br /&gt;
| Random pressure and disruption&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Magic Circle&#039;&#039;&#039;&lt;br /&gt;
| Area Control&lt;br /&gt;
| Creates magic circle effects on the court&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Kiss of Medusa&#039;&#039;&#039;&lt;br /&gt;
| Control&lt;br /&gt;
| Transformation / disable effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Healing&#039;&#039;&#039;&lt;br /&gt;
| Recovery&lt;br /&gt;
| Healing effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Area Shield&#039;&#039;&#039;&lt;br /&gt;
| Defense&lt;br /&gt;
| Defensive shield effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Inferno&#039;&#039;&#039;&lt;br /&gt;
| Attack / Area&lt;br /&gt;
| Area damage pressure&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Sandglass&#039;&#039;&#039;&lt;br /&gt;
| Utility&lt;br /&gt;
| Time-related utility&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Item Bash&#039;&#039;&#039;&lt;br /&gt;
| Buff&lt;br /&gt;
| Increases item damage&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Defensive Auras&#039;&#039;&#039;&lt;br /&gt;
| Buff&lt;br /&gt;
| Increases damage resistance&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Blessed Aim&#039;&#039;&#039;&lt;br /&gt;
| Buff&lt;br /&gt;
| Adds an additional projectile&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Power Strike&#039;&#039;&#039;&lt;br /&gt;
| Buff&lt;br /&gt;
| Increases ball damage&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Speed Ball&#039;&#039;&#039;&lt;br /&gt;
| Buff&lt;br /&gt;
| Increases ball speed&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Frenzy&#039;&#039;&#039;&lt;br /&gt;
| Buff&lt;br /&gt;
| Increases Chargeshot Speed&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Bust of speed&#039;&#039;&#039;&lt;br /&gt;
| Buff&lt;br /&gt;
| Increases movement speed&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Top Spin&#039;&#039;&#039;&lt;br /&gt;
| Buff&lt;br /&gt;
| Increases ball spin&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Special, Event or Disabled Quick Slot Products ==&lt;br /&gt;
&lt;br /&gt;
The game data may contain additional Quick Slot products that are disabled, promotional, event-only, special or bundle-related.&lt;br /&gt;
&lt;br /&gt;
These should not be treated as normal beginner items unless their current in-game availability is confirmed.&lt;br /&gt;
&lt;br /&gt;
Examples can include special versions, promo items, bundles or unused entries.&lt;br /&gt;
&lt;br /&gt;
== Quick Slot Expansion and Slot Management ==&lt;br /&gt;
&lt;br /&gt;
Some Special items interact with Quick Slots.&lt;br /&gt;
&lt;br /&gt;
=== Quick Slot Changer ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Quick Slot Changer&#039;&#039;&#039; is a Special item that can switch the order of Quick Slot items.&lt;br /&gt;
&lt;br /&gt;
This is useful for managing Quick Slot setups.&lt;br /&gt;
&lt;br /&gt;
== Buff Items and Synergistic Effects ==&lt;br /&gt;
&lt;br /&gt;
Some Quick Slot items are not classic attack, defense or recovery items.&lt;br /&gt;
&lt;br /&gt;
Instead, they are &#039;&#039;&#039;Buff Items&#039;&#039;&#039; with &#039;&#039;&#039;Synergistic Effects&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
These effects temporarily improve another gameplay mechanic.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Buff item&lt;br /&gt;
! Synergistic effect&lt;br /&gt;
! Gameplay meaning&lt;br /&gt;
! Related stat / mechanic&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Item Bash&#039;&#039;&#039;&lt;br /&gt;
| Damage Increase&lt;br /&gt;
| Increases item damage&lt;br /&gt;
| Item damage / &#039;&#039;&#039;[[Stats#STR|STR]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Defensive Auras&#039;&#039;&#039;&lt;br /&gt;
| Defense Increase&lt;br /&gt;
| Increases damage resistance&lt;br /&gt;
| Defense / &#039;&#039;&#039;[[Stats#STA|STA]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Blessed Aim&#039;&#039;&#039;&lt;br /&gt;
| Projectile +1&lt;br /&gt;
| Adds an additional projectile&lt;br /&gt;
| Projectiles / &#039;&#039;&#039;[[Stats#DEX|DEX]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Power Strike&#039;&#039;&#039;&lt;br /&gt;
| Ball Damage Increase&lt;br /&gt;
| Increases ball damage&lt;br /&gt;
| Ball damage / &#039;&#039;&#039;[[Stats#WIL|WIL]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Speed Ball&#039;&#039;&#039;&lt;br /&gt;
| Ball Speed Up&lt;br /&gt;
| Increases ball speed&lt;br /&gt;
| Ball speed / &#039;&#039;&#039;[[Stats#STR|STR]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Frenzy&#039;&#039;&#039;&lt;br /&gt;
| Chargeshot Speed Up&lt;br /&gt;
| Increases Chargeshot Speed&lt;br /&gt;
| &#039;&#039;&#039;[[Stats#Secondary Stats|Secondary Stat]]&#039;&#039;&#039; / item effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Bust of speed&#039;&#039;&#039;&lt;br /&gt;
| Movement Speed Up&lt;br /&gt;
| Increases movement speed&lt;br /&gt;
| Movement / &#039;&#039;&#039;[[Stats#DEX|DEX]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Top Spin&#039;&#039;&#039;&lt;br /&gt;
| Ball Spin Increase&lt;br /&gt;
| Increases ball spin&lt;br /&gt;
| Ball behavior / &#039;&#039;&#039;[[Stats#WIL|WIL]]&#039;&#039;&#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important:&#039;&#039;&#039; These are item effects. They can be related to stats, but they are not attributes themselves.&lt;br /&gt;
&lt;br /&gt;
== Buff Timing ==&lt;br /&gt;
&lt;br /&gt;
Buff Items usually work best when activated shortly before the action they are meant to support.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
# Use &#039;&#039;&#039;Item Bash&#039;&#039;&#039;.&lt;br /&gt;
# Then use an attack item such as Fireball, Meteor or Homing Soul.&lt;br /&gt;
# The attack benefits from the damage buff while the effect is active.&lt;br /&gt;
&lt;br /&gt;
Some buffs have a duration and a cooltime.&lt;br /&gt;
&lt;br /&gt;
If the duration is longer than the cooltime, players may be able to overlap multiple buff effects when using the correct slot setup.&lt;br /&gt;
&lt;br /&gt;
For deeper gameplay usage and synergy examples, see the &#039;&#039;&#039;[[Skills|Skills]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
== Special Items ==&lt;br /&gt;
&lt;br /&gt;
Special items provide utility, convenience or account/character-related effects.&lt;br /&gt;
&lt;br /&gt;
Common Special item types include:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Item type&lt;br /&gt;
! Purpose&lt;br /&gt;
! Examples&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Stat reset&#039;&#039;&#039;&lt;br /&gt;
| Resets assigned stats&lt;br /&gt;
| Wing of Memory&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Communication&#039;&#039;&#039;&lt;br /&gt;
| Sends server-wide or expanded messages&lt;br /&gt;
| Megaphone, Speaker&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Pocket expansion&#039;&#039;&#039;&lt;br /&gt;
| Expands pocket / inventory capacity&lt;br /&gt;
| Trunk items&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Quick Slot management&#039;&#039;&#039;&lt;br /&gt;
| Changes Quick Slot item order&lt;br /&gt;
| Quick Slot Changer&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Nickname change&#039;&#039;&#039;&lt;br /&gt;
| Changes character nickname&lt;br /&gt;
| Nickname Changer&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Social / proposal items&#039;&#039;&#039;&lt;br /&gt;
| Used for social systems&lt;br /&gt;
| Rose Card, Diamond Ring Card&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Tools ==&lt;br /&gt;
&lt;br /&gt;
Tools are utility items used for systems such as fishing or collecting.&lt;br /&gt;
&lt;br /&gt;
Examples include:&lt;br /&gt;
&lt;br /&gt;
* Fishing rods&lt;br /&gt;
* Baskets&lt;br /&gt;
&lt;br /&gt;
Tool details, prices and availability should be checked in game, especially if the shop changes.&lt;br /&gt;
&lt;br /&gt;
== Balls ==&lt;br /&gt;
&lt;br /&gt;
Balls are items related to the ball used in gameplay or cosmetics.&lt;br /&gt;
&lt;br /&gt;
Ball products may exist in different versions, and availability can depend on the current shop configuration.&lt;br /&gt;
&lt;br /&gt;
== House Items ==&lt;br /&gt;
&lt;br /&gt;
House items are part of the broader item system, but Housing should be documented on a dedicated &#039;&#039;&#039;Housing&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
Housing is not mainly about item usage like Quick Slot items, Tools or Balls. It is a progression system based on houses, furniture, Housing Points and Gold / EXP bonuses.&lt;br /&gt;
&lt;br /&gt;
For beginner progression tips, see the &#039;&#039;&#039;[[Beginner&#039;s Guide#Tips for Guardian|Tips for Guardian]]&#039;&#039;&#039; section.&lt;br /&gt;
&lt;br /&gt;
For detailed information about houses, furniture, Housing Points and bonuses, see the &#039;&#039;&#039;[[Housing|Housing]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
== Items and Stats ==&lt;br /&gt;
&lt;br /&gt;
Items can interact with stats in different ways.&lt;br /&gt;
&lt;br /&gt;
Some items trigger temporary effects that are related to a gameplay area.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Item effect&lt;br /&gt;
! Example item&lt;br /&gt;
! Related stat / mechanic&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Damage Increase&#039;&#039;&#039;&lt;br /&gt;
| Item Bash&lt;br /&gt;
| Item damage / &#039;&#039;&#039;[[Stats#STR|STR]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Defense Increase&#039;&#039;&#039;&lt;br /&gt;
| Defensive Auras&lt;br /&gt;
| Defense / &#039;&#039;&#039;[[Stats#STA|STA]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Projectile +1&#039;&#039;&#039;&lt;br /&gt;
| Blessed Aim&lt;br /&gt;
| Projectiles / &#039;&#039;&#039;[[Stats#DEX|DEX]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Movement Speed Up&#039;&#039;&#039;&lt;br /&gt;
| Bust of speed&lt;br /&gt;
| Movement / &#039;&#039;&#039;[[Stats#DEX|DEX]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Ball Damage Increase&#039;&#039;&#039;&lt;br /&gt;
| Power Strike&lt;br /&gt;
| Ball damage / &#039;&#039;&#039;[[Stats#WIL|WIL]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Ball Spin Increase&#039;&#039;&#039;&lt;br /&gt;
| Top Spin&lt;br /&gt;
| Ball behavior / &#039;&#039;&#039;[[Stats#WIL|WIL]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Ball Speed Up&#039;&#039;&#039;&lt;br /&gt;
| Speed Ball&lt;br /&gt;
| Ball speed / &#039;&#039;&#039;[[Stats#STR|STR]]&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Chargeshot Speed Up&#039;&#039;&#039;&lt;br /&gt;
| Frenzy&lt;br /&gt;
| &#039;&#039;&#039;[[Stats#Secondary Stats|Secondary Stat]]&#039;&#039;&#039; / item effect&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important:&#039;&#039;&#039; These item effects can be related to stats, but they should not be described as attributes themselves.&lt;br /&gt;
&lt;br /&gt;
== Items and Enchantment ==&lt;br /&gt;
&lt;br /&gt;
Enchantment materials are part of the item system, but should be documented mainly on the &#039;&#039;&#039;[[Enchantment|Enchantment]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
Examples include:&lt;br /&gt;
&lt;br /&gt;
* Tablets&lt;br /&gt;
* Elemental Stones&lt;br /&gt;
* Jewels&lt;br /&gt;
&lt;br /&gt;
For full enchantment logic, Jewel usage, Elemental Stones and enchant levels, see the &#039;&#039;&#039;[[Enchantment|Enchantment]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
== Items and Skills ==&lt;br /&gt;
&lt;br /&gt;
Items and Skills are closely connected in FT.&lt;br /&gt;
&lt;br /&gt;
Many gameplay skills or ability-like effects are triggered through items.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Example&lt;br /&gt;
! Item side&lt;br /&gt;
! Skill / effect side&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Fireball&#039;&#039;&#039;&lt;br /&gt;
| Quick Slot item with Count and Cooltime&lt;br /&gt;
| Fire attack effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Healing&#039;&#039;&#039;&lt;br /&gt;
| Quick Slot item with Count and Cooltime&lt;br /&gt;
| Recovery effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Item Bash&#039;&#039;&#039;&lt;br /&gt;
| Buff item&lt;br /&gt;
| Damage Increase effect&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &#039;&#039;&#039;Items&#039;&#039;&#039; page for item management.&lt;br /&gt;
&lt;br /&gt;
Use the &#039;&#039;&#039;[[Skills|Skills]]&#039;&#039;&#039; page for gameplay effect usage, timing, roles and synergies.&lt;br /&gt;
&lt;br /&gt;
== Common Mistakes ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Mistake&lt;br /&gt;
! Better explanation&lt;br /&gt;
|-&lt;br /&gt;
| Calling every Quick Slot product normally available&lt;br /&gt;
| Some products are disabled, promo-only, event-only or bundle-only&lt;br /&gt;
|-&lt;br /&gt;
| Treating Count and Cooltime as stat values&lt;br /&gt;
| They belong to item usage and item management&lt;br /&gt;
|-&lt;br /&gt;
| Confusing item and effect&lt;br /&gt;
| The item is managed in inventory; the effect happens in gameplay&lt;br /&gt;
|-&lt;br /&gt;
| Mixing Costume equipment into this page&lt;br /&gt;
| Costume / Equipment should have its own page&lt;br /&gt;
|-&lt;br /&gt;
| Calling Buff Items attributes&lt;br /&gt;
| Buff Items create temporary item effects; they are not STR, STA, DEX or WIL&lt;br /&gt;
|-&lt;br /&gt;
| Assuming disabled shop entries are available&lt;br /&gt;
| Availability should be confirmed in game&lt;br /&gt;
|-&lt;br /&gt;
| Explaining Housing in detail here&lt;br /&gt;
| Housing should have its own page&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Recommended Wiki Usage ==&lt;br /&gt;
&lt;br /&gt;
To keep the wiki consistent:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Topic&lt;br /&gt;
! Main page&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Item tab objects&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;Items&#039;&#039;&#039;&lt;br /&gt;
| Special, Quick Slot, Tools, Balls&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Quick Slot item list&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;Items&#039;&#039;&#039;&lt;br /&gt;
| Item names, Count, Cooltime and category&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Gameplay effects&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;[[Skills]]&#039;&#039;&#039;&lt;br /&gt;
| What the effect does during a match&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Stats and formulas&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;[[Stats]]&#039;&#039;&#039;&lt;br /&gt;
| STR, STA, DEX, WIL and Secondary Stats&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Costume and equipment&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;Costume / Equipment&#039;&#039;&#039;&lt;br /&gt;
| Clothing, rackets, shoes, gloves, accessories&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Enchant materials and upgrade logic&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;[[Enchantment]]&#039;&#039;&#039;&lt;br /&gt;
| Tablets, Elemental Stones, Jewels and levels&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Housing and decoration&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;Housing&#039;&#039;&#039;&lt;br /&gt;
| Houses, furniture and housing bonuses&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Related Pages ==&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[[Skills]]&#039;&#039;&#039; – explains gameplay effects, usage, timing and synergies.&lt;br /&gt;
* &#039;&#039;&#039;[[Stats]]&#039;&#039;&#039; – explains STR, STA, DEX, WIL, Secondary Stats and formulas.&lt;br /&gt;
* &#039;&#039;&#039;[[Enchantment]]&#039;&#039;&#039; – explains Tablets, Elemental Stones, Jewels and equipment improvement.&lt;br /&gt;
* &#039;&#039;&#039;[[Beginner&#039;s Guide]]&#039;&#039;&#039; – explains the recommended starting path for new players.&lt;br /&gt;
* &#039;&#039;&#039;[[Maps]]&#039;&#039;&#039; – shows map information and possible rewards.&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=Skills&amp;diff=321</id>
		<title>Skills</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Skills&amp;diff=321"/>
		<updated>2026-07-20T19:54:41Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: add Guides category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Overview ==&lt;br /&gt;
&lt;br /&gt;
Skills in Fantasy Tennis describe gameplay effects that are activated during a match.&lt;br /&gt;
&lt;br /&gt;
FT does not use a classic skill tree like many other games. Many skill-like effects are accessed through items, Quick Slot items, Buff items or special shot systems.&lt;br /&gt;
&lt;br /&gt;
The important difference is:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Item side&#039;&#039;&#039; – what the player buys, owns, equips, consumes or manages.&lt;br /&gt;
* &#039;&#039;&#039;Gameplay effect side&#039;&#039;&#039; – what happens during the match when the item or action is used.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Fireball&#039;&#039;&#039; can be managed as a Quick Slot item, but when used during a match it triggers a Fireball attack effect.&lt;br /&gt;
&lt;br /&gt;
This page focuses on the &#039;&#039;&#039;gameplay effect side&#039;&#039;&#039;: what these effects do, how they are used, which gameplay role they have, and which stats or buffs can support them.&lt;br /&gt;
&lt;br /&gt;
For item categories, Count, Cooltime, Duration, availability and Quick Slot management, see the &#039;&#039;&#039;[[Items|Items]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
For STR, STA, DEX, WIL and Secondary Stats, see the &#039;&#039;&#039;[[Stats|Stats]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
== Purpose of This Page ==&lt;br /&gt;
&lt;br /&gt;
This page is not meant to be a complete item database.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Items&#039;&#039;&#039; page explains what players buy, equip, manage and consume.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Skills&#039;&#039;&#039; page explains what the activated effects do during gameplay.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Page&lt;br /&gt;
! Main focus&lt;br /&gt;
! Examples&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;[[Items|Items]]&#039;&#039;&#039;&lt;br /&gt;
| Item category, Count, Cooltime, Duration, Quick Slot setup and item management&lt;br /&gt;
| Fireball as an item, Item Bash as a Buff Item&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Skills&#039;&#039;&#039;&lt;br /&gt;
| Gameplay effect, role, usage, synergy and counterplay&lt;br /&gt;
| Fireball as an attack effect, Item Bash as a damage buff effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;[[Stats|Stats]]&#039;&#039;&#039;&lt;br /&gt;
| Numerical values and formulas&lt;br /&gt;
| STR, STA, DEX, WIL, Secondary Stats&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Some Quick Slot products may be unavailable, disabled, event-only, promo-only or part of special bundles. Availability should be documented on the &#039;&#039;&#039;[[Items|Items]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
This page focuses on common and important gameplay effects.&lt;br /&gt;
&lt;br /&gt;
== How Skills Work in FT ==&lt;br /&gt;
&lt;br /&gt;
Many skill-like effects in FT follow this basic flow:&lt;br /&gt;
&lt;br /&gt;
# The player owns or buys an item.&lt;br /&gt;
# The item can be equipped or placed into a usable slot.&lt;br /&gt;
# The player activates it during gameplay.&lt;br /&gt;
# The item triggers a gameplay effect.&lt;br /&gt;
# The effect applies damage, healing, control, protection, support or a temporary buff.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Concept&lt;br /&gt;
! Meaning&lt;br /&gt;
! Example&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Item&#039;&#039;&#039;&lt;br /&gt;
| The object bought, owned, equipped or consumed by the player&lt;br /&gt;
| Fireball item&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Quick Slot&#039;&#039;&#039;&lt;br /&gt;
| A slot used to activate certain items during gameplay&lt;br /&gt;
| Action-key slot&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Count / Charges&#039;&#039;&#039;&lt;br /&gt;
| The number of uses available for an item&lt;br /&gt;
| Using Fireball consumes one charge&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Skill Effect&#039;&#039;&#039;&lt;br /&gt;
| The gameplay effect triggered during a match&lt;br /&gt;
| Fireball attack&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A clear way to describe this is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Fireball is a Quick Slot item that triggers a Fireball attack effect when used.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Skill Effect Properties ==&lt;br /&gt;
&lt;br /&gt;
Skill effects can have different gameplay properties.&lt;br /&gt;
&lt;br /&gt;
These properties explain how the effect behaves during a match.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Property&lt;br /&gt;
! Meaning&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Element&#039;&#039;&#039;&lt;br /&gt;
| The elemental type of the effect&lt;br /&gt;
| Fire, Wind, Earth, Water or None&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Shot Count&#039;&#039;&#039;&lt;br /&gt;
| Number of projectiles or effect instances&lt;br /&gt;
| Some effects can gain additional shots from DEX&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Damage&#039;&#039;&#039;&lt;br /&gt;
| Base damage or healing value&lt;br /&gt;
| Negative values in data usually represent damage effects&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Damage Rate&#039;&#039;&#039;&lt;br /&gt;
| Scaling or rate value used by the effect&lt;br /&gt;
| Depends on the effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Targeting&#039;&#039;&#039;&lt;br /&gt;
| Defines how the effect chooses or applies to targets&lt;br /&gt;
| Single target, area, self, ally or special behavior&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Radius&#039;&#039;&#039;&lt;br /&gt;
| Area range of the effect&lt;br /&gt;
| Important for range heal, range shield or area effects&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Cooldown / Cooltime&#039;&#039;&#039;&lt;br /&gt;
| Waiting time before the effect can be used again&lt;br /&gt;
| Item usage details are explained on the Items page&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Guardian Cooldown&#039;&#039;&#039;&lt;br /&gt;
| Cooldown used in Guardian Mode&lt;br /&gt;
| Can differ from normal Battle cooldown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; This page explains these properties from a gameplay perspective. For item Count, Cooltime, Duration and shop availability, see the &#039;&#039;&#039;[[Items|Items]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
== Skill Effect Types ==&lt;br /&gt;
&lt;br /&gt;
Skill effects can be grouped by their gameplay role.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Type&lt;br /&gt;
! Purpose&lt;br /&gt;
! Common examples&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Attack&#039;&#039;&#039;&lt;br /&gt;
| Deals damage or applies offensive pressure&lt;br /&gt;
| Fireball, Meteor, Blizzard, Homing Soul, Inferno&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Projectile Pressure&#039;&#039;&#039;&lt;br /&gt;
| Uses multiple shots or tracking pressure&lt;br /&gt;
| Meteor variants, Homing Soul, Blizzard&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Area Control / Trap&#039;&#039;&#039;&lt;br /&gt;
| Controls space, creates zones or punishes movement&lt;br /&gt;
| Magic Circle, Crap trap, Inferno&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Control&#039;&#039;&#039;&lt;br /&gt;
| Disrupts, disables or transforms opponents&lt;br /&gt;
| Flash Bomb, Kiss of Medusa, Polymorph, Minimum&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Defense&#039;&#039;&#039;&lt;br /&gt;
| Protects the player or reduces incoming pressure&lt;br /&gt;
| Area Shield, Holy Shield&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Recovery&#039;&#039;&#039;&lt;br /&gt;
| Restores strength or heals players&lt;br /&gt;
| Potion effects, Healing&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Support&#039;&#039;&#039;&lt;br /&gt;
| Helps teammates or changes the match state&lt;br /&gt;
| Rebirth, Sandglass&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Buff&#039;&#039;&#039;&lt;br /&gt;
| Temporarily improves damage, projectiles, movement, defense or ball behavior&lt;br /&gt;
| Item Bash, Blessed Aim, Defensive Auras, Frenzy&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The examples above are common gameplay examples, not a full list of every possible Quick Slot product.&lt;br /&gt;
&lt;br /&gt;
== Attack Effects ==&lt;br /&gt;
&lt;br /&gt;
Attack effects are used to damage opponents, force reactions or create offensive pressure.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Effect&lt;br /&gt;
! Element&lt;br /&gt;
! Gameplay role&lt;br /&gt;
! Works well with&lt;br /&gt;
! Beginner note&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Fireball&#039;&#039;&#039;&lt;br /&gt;
| Fire&lt;br /&gt;
| Direct attack pressure&lt;br /&gt;
| Item Bash, STR-related offensive setups&lt;br /&gt;
| Simple and easy to understand&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Meteor&#039;&#039;&#039;&lt;br /&gt;
| Fire&lt;br /&gt;
| Burst / projectile pressure&lt;br /&gt;
| Item Bash, Blessed Aim, DEX projectile setups&lt;br /&gt;
| Strong, but timing matters&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Small Meteor&#039;&#039;&#039;&lt;br /&gt;
| Fire&lt;br /&gt;
| Faster projectile pressure&lt;br /&gt;
| Item Bash, Blessed Aim, DEX projectile setups&lt;br /&gt;
| Useful for pressure-based setups&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Inferno&#039;&#039;&#039;&lt;br /&gt;
| Fire&lt;br /&gt;
| Area damage pressure&lt;br /&gt;
| Item Bash&lt;br /&gt;
| Useful when enemies stay grouped or near the effect area&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Blizzard&#039;&#039;&#039;&lt;br /&gt;
| Water&lt;br /&gt;
| Random pressure and disruption&lt;br /&gt;
| Item Bash&lt;br /&gt;
| Can be strong, but less predictable&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Homing Soul&#039;&#039;&#039;&lt;br /&gt;
| Wind&lt;br /&gt;
| Tracking pressure&lt;br /&gt;
| Item Bash, Blessed Aim, DEX projectile setups&lt;br /&gt;
| Useful because it pressures moving targets&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tip:&#039;&#039;&#039; Attack effects are usually easier for beginners than specialized control or utility effects because their purpose is clear: use them to deal damage or create pressure.&lt;br /&gt;
&lt;br /&gt;
== Projectile Effects and DEX ==&lt;br /&gt;
&lt;br /&gt;
Some projectile-based effects can benefit from &#039;&#039;&#039;[[Stats#DEX|DEX]]&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
According to the game logic, specific projectile effects can gain additional shots at certain DEX values.&lt;br /&gt;
&lt;br /&gt;
This applies to effects such as:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Meteor variants&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Homing Soul&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! DEX&lt;br /&gt;
! Additional shots&lt;br /&gt;
|-&lt;br /&gt;
| 0–29&lt;br /&gt;
| +0&lt;br /&gt;
|-&lt;br /&gt;
| 30–39&lt;br /&gt;
| +1&lt;br /&gt;
|-&lt;br /&gt;
| 40–49&lt;br /&gt;
| +2&lt;br /&gt;
|-&lt;br /&gt;
| 50–59&lt;br /&gt;
| +3&lt;br /&gt;
|-&lt;br /&gt;
| 60+&lt;br /&gt;
| +4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This is one reason why &#039;&#039;&#039;60 DEX&#039;&#039;&#039; can be an important breakpoint for damage or projectile-focused setups.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important:&#039;&#039;&#039; This does not mean that DEX increases the shot count of every skill effect. It applies to specific projectile effects.&lt;br /&gt;
&lt;br /&gt;
For more information about DEX and other attributes, see the &#039;&#039;&#039;[[Stats#DEX|DEX]]&#039;&#039;&#039; section on the Stats page.&lt;br /&gt;
&lt;br /&gt;
== Area Control and Trap Effects ==&lt;br /&gt;
&lt;br /&gt;
Area control and trap effects are used to control court space, punish movement or force opponents into bad positions.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Effect&lt;br /&gt;
! Element&lt;br /&gt;
! Gameplay role&lt;br /&gt;
! Best used for&lt;br /&gt;
! Beginner note&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Magic Circle&#039;&#039;&#039;&lt;br /&gt;
| Wind&lt;br /&gt;
| Area control&lt;br /&gt;
| Controlling court space and forcing movement&lt;br /&gt;
| More useful when you understand positioning&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Crap trap&#039;&#039;&#039;&lt;br /&gt;
| Earth&lt;br /&gt;
| Trap&lt;br /&gt;
| Punishing movement and controlling paths&lt;br /&gt;
| Requires prediction and placement&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Inferno&#039;&#039;&#039;&lt;br /&gt;
| Fire&lt;br /&gt;
| Area damage&lt;br /&gt;
| Punishing grouped enemies or area movement&lt;br /&gt;
| Easier when enemies stay near the effect area&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Area control effects are usually stronger when the player understands positioning, opponent movement and timing.&lt;br /&gt;
&lt;br /&gt;
== Control Effects ==&lt;br /&gt;
&lt;br /&gt;
Control effects are used to disrupt, disable or transform opponents.&lt;br /&gt;
&lt;br /&gt;
They are often strongest when followed up with pressure or damage.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Effect&lt;br /&gt;
! Element&lt;br /&gt;
! Gameplay role&lt;br /&gt;
! Best used for&lt;br /&gt;
! Beginner note&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Flash Bomb&#039;&#039;&#039;&lt;br /&gt;
| None&lt;br /&gt;
| Blind / disruption&lt;br /&gt;
| Interrupting opponents and creating confusion&lt;br /&gt;
| Timing is important&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Kiss of Medusa&#039;&#039;&#039;&lt;br /&gt;
| Earth&lt;br /&gt;
| Disable / transformation&lt;br /&gt;
| Stopping a target for follow-up pressure&lt;br /&gt;
| Strong but situational&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Polymorph&#039;&#039;&#039;&lt;br /&gt;
| None&lt;br /&gt;
| Transformation&lt;br /&gt;
| Disrupting an opponent&lt;br /&gt;
| Useful when timed during pressure&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Minimum&#039;&#039;&#039;&lt;br /&gt;
| None&lt;br /&gt;
| Disruption&lt;br /&gt;
| Weakening or disrupting opponents&lt;br /&gt;
| More situational&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tip:&#039;&#039;&#039; Control effects are often better in coordinated play than when used randomly.&lt;br /&gt;
&lt;br /&gt;
== Defense Effects ==&lt;br /&gt;
&lt;br /&gt;
Defense effects help players survive pressure, block dangerous effects or reduce incoming damage.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Effect&lt;br /&gt;
! Gameplay role&lt;br /&gt;
! Best used for&lt;br /&gt;
! Works well with&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Area Shield&#039;&#039;&#039;&lt;br /&gt;
| Protection&lt;br /&gt;
| Surviving incoming pressure&lt;br /&gt;
| Defensive Auras, STA-related setups&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Holy Shield&#039;&#039;&#039;&lt;br /&gt;
| Protection / item counter&lt;br /&gt;
| Reducing or blocking enemy item pressure&lt;br /&gt;
| Defensive Auras, support play&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Defense effects are useful when opponents rely heavily on attack or control effects.&lt;br /&gt;
&lt;br /&gt;
== Recovery and Support Effects ==&lt;br /&gt;
&lt;br /&gt;
Recovery and support effects help the player or team stay active during gameplay.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Effect&lt;br /&gt;
! Type&lt;br /&gt;
! Gameplay role&lt;br /&gt;
! Beginner note&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Potion effects&#039;&#039;&#039;&lt;br /&gt;
| Recovery&lt;br /&gt;
| Restore strength&lt;br /&gt;
| Easy to understand and useful&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Healing&#039;&#039;&#039;&lt;br /&gt;
| Recovery&lt;br /&gt;
| Heals the user or nearby players&lt;br /&gt;
| Useful in longer fights&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Range Heal&#039;&#039;&#039;&lt;br /&gt;
| Recovery / Team support&lt;br /&gt;
| Heals allies in an area&lt;br /&gt;
| Stronger in team play&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Rebirth&#039;&#039;&#039;&lt;br /&gt;
| Support&lt;br /&gt;
| Revives a teammate with limited strength&lt;br /&gt;
| Strong in team play&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Sandglass&#039;&#039;&#039;&lt;br /&gt;
| Utility&lt;br /&gt;
| Delays or affects the time limit&lt;br /&gt;
| Situational&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Support effects can be very valuable, but beginners should first understand when their team actually needs them.&lt;br /&gt;
&lt;br /&gt;
== Buff Effects ==&lt;br /&gt;
&lt;br /&gt;
Buff effects temporarily improve another gameplay mechanic.&lt;br /&gt;
&lt;br /&gt;
They usually do not directly attack an opponent. Instead, they support other effects, attacks or playstyles.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Buff item&lt;br /&gt;
! Buff effect&lt;br /&gt;
! Supports&lt;br /&gt;
! Related stat / mechanic&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Item Bash&#039;&#039;&#039;&lt;br /&gt;
| Damage Increase&lt;br /&gt;
| Attack effects such as Fireball, Meteor, Blizzard and Homing Soul&lt;br /&gt;
| STR-related offensive setups&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Blessed Aim&#039;&#039;&#039;&lt;br /&gt;
| Projectile +1&lt;br /&gt;
| Projectile-based effects&lt;br /&gt;
| DEX-related projectile behavior&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Defensive Auras&#039;&#039;&#039;&lt;br /&gt;
| Defense Increase&lt;br /&gt;
| Defensive and survival setups&lt;br /&gt;
| STA-related survivability&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Frenzy&#039;&#039;&#039;&lt;br /&gt;
| Chargeshot Speed Up&lt;br /&gt;
| Charge-based gameplay&lt;br /&gt;
| Secondary Stat / item effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Top Spin&#039;&#039;&#039;&lt;br /&gt;
| Ball Spin Increase&lt;br /&gt;
| Ball behavior setups&lt;br /&gt;
| WIL-related ball behavior&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Power Strike&#039;&#039;&#039;&lt;br /&gt;
| Ball Damage Increase&lt;br /&gt;
| Ball damage setups&lt;br /&gt;
| WIL-related ball damage&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Speed Ball&#039;&#039;&#039;&lt;br /&gt;
| Ball Speed Up&lt;br /&gt;
| Ball speed pressure&lt;br /&gt;
| STR-related ball speed&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Bust of speed&#039;&#039;&#039;&lt;br /&gt;
| Movement Speed Up&lt;br /&gt;
| Mobility and positioning&lt;br /&gt;
| DEX-related movement&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Buff effects are strongest when they are used shortly before the action they are meant to support.&lt;br /&gt;
&lt;br /&gt;
For Buff Slot Expansion, Cooltime and Duration, see the &#039;&#039;&#039;[[Items#Buff Items and Synergistic Effects|Buff Items]]&#039;&#039;&#039; section on the Items page.&lt;br /&gt;
&lt;br /&gt;
== Range Buff Effects ==&lt;br /&gt;
&lt;br /&gt;
Some buff effects can also exist as range or team-oriented versions.&lt;br /&gt;
&lt;br /&gt;
These effects can support nearby allies instead of only affecting the user.&lt;br /&gt;
&lt;br /&gt;
Examples include range versions of:&lt;br /&gt;
&lt;br /&gt;
* Damage Increase&lt;br /&gt;
* Defense Increase&lt;br /&gt;
* Projectile Speed Increase&lt;br /&gt;
* Ball Damage Increase&lt;br /&gt;
* Ball Speed / Strength Increase&lt;br /&gt;
* Chargeshot Speed Increase&lt;br /&gt;
* Movement Speed Increase&lt;br /&gt;
* Ball Spin Increase&lt;br /&gt;
&lt;br /&gt;
Range buffs are usually more valuable in team play than in solo situations.&lt;br /&gt;
&lt;br /&gt;
== Skill Synergies ==&lt;br /&gt;
&lt;br /&gt;
Some effects work better together than alone.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Goal&lt;br /&gt;
! Useful combination&lt;br /&gt;
! Why it works&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;High item damage&#039;&#039;&#039;&lt;br /&gt;
| Item Bash + Fireball / Meteor / Blizzard / Homing Soul&lt;br /&gt;
| Item Bash increases item damage before using attack effects&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Projectile pressure&#039;&#039;&#039;&lt;br /&gt;
| Blessed Aim + Meteor / Homing Soul&lt;br /&gt;
| Adds projectile pressure and can combine well with DEX setups&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Survivability&#039;&#039;&#039;&lt;br /&gt;
| Defensive Auras + Area Shield / Holy Shield / Healing&lt;br /&gt;
| Combines damage resistance with defensive or recovery effects&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Ball pressure&#039;&#039;&#039;&lt;br /&gt;
| Speed Ball / Top Spin / Power Strike&lt;br /&gt;
| Improves ball speed, spin or ball damage&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Control follow-up&#039;&#039;&#039;&lt;br /&gt;
| Kiss of Medusa / Polymorph + attack effects&lt;br /&gt;
| Control effects can create openings for damage&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Team sustain&#039;&#039;&#039;&lt;br /&gt;
| Range Heal + defensive effects&lt;br /&gt;
| Helps keep teammates active during longer fights&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tip:&#039;&#039;&#039; Buffs should usually be activated shortly before the attack, defense or support effect they are meant to improve.&lt;br /&gt;
&lt;br /&gt;
== Elements and Skill Effects ==&lt;br /&gt;
&lt;br /&gt;
Some skill effects have an element.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Element&lt;br /&gt;
! Example effects&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Fire&#039;&#039;&#039;&lt;br /&gt;
| Fireball, Meteor, Inferno&lt;br /&gt;
| Fire-based attack effects&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Wind&#039;&#039;&#039;&lt;br /&gt;
| Magic Circle, Homing Soul&lt;br /&gt;
| Wind-based attack or area-control effects&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Earth&#039;&#039;&#039;&lt;br /&gt;
| Crap trap, Kiss of Medusa&lt;br /&gt;
| Earth-based trap or control effects&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Water&#039;&#039;&#039;&lt;br /&gt;
| Blizzard&lt;br /&gt;
| Water-based attack effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;None&#039;&#039;&#039;&lt;br /&gt;
| Healing, shields, utility effects and many buff effects&lt;br /&gt;
| Effects without an elemental type&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Elements can interact with elemental attack, elemental defense and element reactions.&lt;br /&gt;
&lt;br /&gt;
For more information, see the &#039;&#039;&#039;[[Enchantment|Enchantment]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
== Racket Special Shots ==&lt;br /&gt;
&lt;br /&gt;
Not all skill-like effects come from Quick Slot items.&lt;br /&gt;
&lt;br /&gt;
Some gameplay effects are connected to racket offensive or defensive special shots.&lt;br /&gt;
&lt;br /&gt;
These should be documented separately from Quick Slot effects where needed.&lt;br /&gt;
&lt;br /&gt;
Examples of special shot categories include:&lt;br /&gt;
&lt;br /&gt;
* Offensive special shots&lt;br /&gt;
* Defensive special shots&lt;br /&gt;
* Guardian-related shots&lt;br /&gt;
* Special ball-hit actions&lt;br /&gt;
&lt;br /&gt;
A useful structure is:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Source&lt;br /&gt;
! What it means&lt;br /&gt;
! Example&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Quick Slot Item&#039;&#039;&#039;&lt;br /&gt;
| Item from the Item tab that triggers an effect&lt;br /&gt;
| Fireball, Meteor, Blizzard&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Buff Item&#039;&#039;&#039;&lt;br /&gt;
| Item that temporarily improves gameplay&lt;br /&gt;
| Item Bash, Blessed Aim, Frenzy&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Racket Special Shot&#039;&#039;&#039;&lt;br /&gt;
| Special shot connected to racket gameplay&lt;br /&gt;
| Offensive / Defensive special shots&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This section can be expanded later with a dedicated list of racket special shots once the exact in-game names and effects are confirmed.&lt;br /&gt;
&lt;br /&gt;
== Skills and Stats ==&lt;br /&gt;
&lt;br /&gt;
Stats can affect gameplay mechanics related to skill effects.&lt;br /&gt;
&lt;br /&gt;
The four main attributes are:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[[Stats#STR|STR]]&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;[[Stats#STA|STA]]&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;[[Stats#DEX|DEX]]&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;[[Stats#WIL|WIL]]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Stat&lt;br /&gt;
! Related gameplay area&lt;br /&gt;
! Example connection&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;[[Stats#STR|STR]]&#039;&#039;&#039;&lt;br /&gt;
| Offensive pressure and damage-related mechanics&lt;br /&gt;
| Item Bash supports item damage setups&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;[[Stats#STA|STA]]&#039;&#039;&#039;&lt;br /&gt;
| Survivability and damage reduction&lt;br /&gt;
| Defensive Auras supports defensive setups&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;[[Stats#DEX|DEX]]&#039;&#039;&#039;&lt;br /&gt;
| Movement, responsiveness and projectile behavior&lt;br /&gt;
| DEX can increase shot count for some projectile effects&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;[[Stats#WIL|WIL]]&#039;&#039;&#039;&lt;br /&gt;
| Ball behavior and ball damage&lt;br /&gt;
| Top Spin and Power Strike support ball-based setups&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For formulas, scaling and detailed stat explanations, see the &#039;&#039;&#039;[[Stats|Stats]]&#039;&#039;&#039; page.&lt;br /&gt;
&lt;br /&gt;
== Beginner Recommendations ==&lt;br /&gt;
&lt;br /&gt;
New players should start with effects that are simple and easy to understand.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Recommended for beginners&lt;br /&gt;
! Why&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Fireball&#039;&#039;&#039;&lt;br /&gt;
| Simple direct attack effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Meteor&#039;&#039;&#039;&lt;br /&gt;
| Strong offensive pressure&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Homing Soul&#039;&#039;&#039;&lt;br /&gt;
| Useful tracking pressure&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Healing&#039;&#039;&#039;&lt;br /&gt;
| Easy recovery option&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Area Shield&#039;&#039;&#039;&lt;br /&gt;
| Helps survive pressure&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Item Bash&#039;&#039;&#039;&lt;br /&gt;
| Simple damage buff for attack effects&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Blessed Aim&#039;&#039;&#039;&lt;br /&gt;
| Useful for projectile-based pressure&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Effects that require more timing, positioning or experience are better learned later:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Learn later&lt;br /&gt;
! Why&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Magic Circle&#039;&#039;&#039;&lt;br /&gt;
| Requires positioning and area-control understanding&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Crap trap&#039;&#039;&#039;&lt;br /&gt;
| Requires prediction and placement&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Kiss of Medusa&#039;&#039;&#039;&lt;br /&gt;
| Strong but timing-dependent&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Polymorph&#039;&#039;&#039;&lt;br /&gt;
| More situational and timing-based&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Sandglass&#039;&#039;&#039;&lt;br /&gt;
| Situational utility&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Common Mistakes ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Mistake&lt;br /&gt;
! Better approach&lt;br /&gt;
|-&lt;br /&gt;
| Using buffs too early&lt;br /&gt;
| Use buffs shortly before the effect they should support&lt;br /&gt;
|-&lt;br /&gt;
| Using control effects randomly&lt;br /&gt;
| Use them when you can follow up with pressure or damage&lt;br /&gt;
|-&lt;br /&gt;
| Ignoring defensive effects&lt;br /&gt;
| Defense and recovery can be important in longer fights&lt;br /&gt;
|-&lt;br /&gt;
| Only using attack effects&lt;br /&gt;
| Combine attack effects with buffs or support effects&lt;br /&gt;
|-&lt;br /&gt;
| Confusing item details with gameplay role&lt;br /&gt;
| Use the Items page for item details and this page for effect usage&lt;br /&gt;
|-&lt;br /&gt;
| Ignoring DEX breakpoints for projectile setups&lt;br /&gt;
| Some projectile effects gain additional shots at higher DEX values&lt;br /&gt;
|-&lt;br /&gt;
| Treating every Quick Slot product as a beginner item&lt;br /&gt;
| Some items can be event-only, disabled, promotional or situational&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Items, Stats and Skills ==&lt;br /&gt;
&lt;br /&gt;
The same name can appear in different contexts.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Example&lt;br /&gt;
! Item side&lt;br /&gt;
! Gameplay effect side&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Fireball&#039;&#039;&#039;&lt;br /&gt;
| Quick Slot item with Count and Cooltime&lt;br /&gt;
| Fire attack effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Healing&#039;&#039;&#039;&lt;br /&gt;
| Quick Slot item with Count and Cooltime&lt;br /&gt;
| Recovery effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Item Bash&#039;&#039;&#039;&lt;br /&gt;
| Buff item&lt;br /&gt;
| Damage Increase effect&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Blessed Aim&#039;&#039;&#039;&lt;br /&gt;
| Buff item&lt;br /&gt;
| Projectile +1 effect&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A clear way to describe this:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Fireball is a Quick Slot item that triggers a Fireball attack effect when used.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Recommended Wiki Usage ==&lt;br /&gt;
&lt;br /&gt;
To keep the wiki consistent:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Topic&lt;br /&gt;
! Main page&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Stats / Attributes&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;[[Stats]]&#039;&#039;&#039;&lt;br /&gt;
| STR, STA, DEX, WIL, Secondary Stats and formulas&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Quick Slot Items&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;[[Items#Quick Slot Items|Items]]&#039;&#039;&#039;&lt;br /&gt;
| Item category, Count, Cooltime, Duration and item management&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Quick Slot Item List&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;[[Items#Quick Slot Item List|Items]]&#039;&#039;&#039;&lt;br /&gt;
| More complete list of common and special Quick Slot products&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Buff Items&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;[[Items#Buff Items and Synergistic Effects|Items]]&#039;&#039;&#039;&lt;br /&gt;
| Synergistic effects, Buff Slot Expansion and timing&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Skill effects&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;[[Skills]]&#039;&#039;&#039;&lt;br /&gt;
| Gameplay effects triggered during a match&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Enchant&#039;&#039;&#039;&lt;br /&gt;
| &#039;&#039;&#039;[[Enchantment]]&#039;&#039;&#039;&lt;br /&gt;
| Equipment improvement system&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Related Pages ==&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[[Items]]&#039;&#039;&#039; – explains Quick Slot items, Buff Items, Count, Cooltime, Duration and item management.&lt;br /&gt;
* &#039;&#039;&#039;[[Stats]]&#039;&#039;&#039; – explains STR, STA, DEX, WIL, Secondary Stats and formulas.&lt;br /&gt;
* &#039;&#039;&#039;[[Enchantment]]&#039;&#039;&#039; – explains Tablets, Elemental Stones, Jewels and the Enchant system.&lt;br /&gt;
* &#039;&#039;&#039;[[Beginner&#039;s Guide]]&#039;&#039;&#039; – explains the recommended starting path for new players.&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=Maps&amp;diff=320</id>
		<title>Maps</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Maps&amp;diff=320"/>
		<updated>2026-07-20T19:54:22Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: add Guides category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Nest of Rubycrab ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
No Boss Battle | No Gacha | 1 Guardian&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Dokaro || 500&lt;br /&gt;
|-&lt;br /&gt;
| Penkaro || 550&lt;br /&gt;
|-&lt;br /&gt;
| Bekaro || 600&lt;br /&gt;
|-&lt;br /&gt;
| Hokaro || 650&lt;br /&gt;
|-&lt;br /&gt;
| Sikaro || 700&lt;br /&gt;
|-&lt;br /&gt;
| Ikaro || 750&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Emerald Beach ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
No Boss Battle | No Gacha | 1 Guardian&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doteko || 650&lt;br /&gt;
|-&lt;br /&gt;
| Penteko || 700&lt;br /&gt;
|-&lt;br /&gt;
| Beteko || 750&lt;br /&gt;
|-&lt;br /&gt;
| Hoteko || 800&lt;br /&gt;
|-&lt;br /&gt;
| Siteko || 850&lt;br /&gt;
|-&lt;br /&gt;
| Eteko || 900&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Twinkle Town ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
No Boss Battle | No Gacha | 2 Guardian&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;width:100%; border:0; cellpadding:0; cellspacing:0;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 1 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doteko || 650&lt;br /&gt;
|-&lt;br /&gt;
| Penteko || 700&lt;br /&gt;
|-&lt;br /&gt;
| Beteko || 750&lt;br /&gt;
|-&lt;br /&gt;
| Hoteko || 800&lt;br /&gt;
|-&lt;br /&gt;
| Siteko || 850&lt;br /&gt;
|-&lt;br /&gt;
| Eteko || 900&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 1 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 2 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Additional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Dokaro || 500&lt;br /&gt;
|-&lt;br /&gt;
| Penkaro || 550&lt;br /&gt;
|-&lt;br /&gt;
| Bekaro || 600&lt;br /&gt;
|-&lt;br /&gt;
| Hokaro || 650&lt;br /&gt;
|-&lt;br /&gt;
| Sikaro || 700&lt;br /&gt;
|-&lt;br /&gt;
| Ikaro || 750&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 2 END --&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== The Aeolos ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
No Boss Battle | No Gacha | 2 Guardian&lt;br /&gt;
{| style=&amp;quot;width:100%; border:0; cellpadding:0; cellspacing:0;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 1 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doga || 800&lt;br /&gt;
|-&lt;br /&gt;
| Penga || 850&lt;br /&gt;
|-&lt;br /&gt;
| Bega || 900&lt;br /&gt;
|-&lt;br /&gt;
| Hoga || 950&lt;br /&gt;
|-&lt;br /&gt;
| Siga || 1000&lt;br /&gt;
|-&lt;br /&gt;
| Ega || 1&#039;050&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 1 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 2 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Additional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doteko || 650&lt;br /&gt;
|-&lt;br /&gt;
| Penteko || 700&lt;br /&gt;
|-&lt;br /&gt;
| Beteko || 750&lt;br /&gt;
|-&lt;br /&gt;
| Hoteko || 800&lt;br /&gt;
|-&lt;br /&gt;
| Siteko || 850&lt;br /&gt;
|-&lt;br /&gt;
| Eteko || 900&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 2 END --&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Snow Moon (Event Map Only) ==&lt;br /&gt;
&#039;&#039;&#039;1st Stage:&#039;&#039;&#039; No Gacha | 2 Guardian&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| No.1 || 15&#039;000&lt;br /&gt;
|-&lt;br /&gt;
| No. 2 || 15&#039;000&lt;br /&gt;
|-&lt;br /&gt;
| No. 3 || 15&#039;000&lt;br /&gt;
|-&lt;br /&gt;
| No. 4 || 24&#039;000&lt;br /&gt;
|-&lt;br /&gt;
| No. 5 || 24&#039;000&lt;br /&gt;
|-&lt;br /&gt;
| No. 6 || 24&#039;000&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2nd Stage:&#039;&#039;&#039; Boss Battle: Hera + 2 Mini Boss&lt;br /&gt;
[[File:Hera.png|100px|left|alt=Hera|Hera]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Hera Base HP: 20&#039;000&lt;br /&gt;
&lt;br /&gt;
Requirement: Finish first Stage in under 8 Minutes.&lt;br /&gt;
&lt;br /&gt;
Gacha: Usually X-Mas Coin&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Aquara || 15&#039;000&lt;br /&gt;
|-&lt;br /&gt;
| Moora || 15&#039;000&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Life Woods ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
No Boss Battle | No Gacha | 2 Guardian&lt;br /&gt;
{| style=&amp;quot;width:100%; border:0; cellpadding:0; cellspacing:0;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 1 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Dogoliath || 1&#039;200&lt;br /&gt;
|-&lt;br /&gt;
| Pengoliath || 1&#039;350&lt;br /&gt;
|-&lt;br /&gt;
| Begoliath || 1&#039;500&lt;br /&gt;
|-&lt;br /&gt;
| Hogoliath || 1&#039;650&lt;br /&gt;
|-&lt;br /&gt;
| Sigoliath || 1&#039;800&lt;br /&gt;
|-&lt;br /&gt;
| Egoliath || 1&#039;950&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 1 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 2 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Optional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doga || 800&lt;br /&gt;
|-&lt;br /&gt;
| Penga || 850&lt;br /&gt;
|-&lt;br /&gt;
| Bega || 900&lt;br /&gt;
|-&lt;br /&gt;
| Hoga || 950&lt;br /&gt;
|-&lt;br /&gt;
| Siga || 1&#039;000&lt;br /&gt;
|-&lt;br /&gt;
| Ega || 1&#039;050&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 2 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 3 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Optional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doteko || 650&lt;br /&gt;
|-&lt;br /&gt;
| Penteko || 700&lt;br /&gt;
|-&lt;br /&gt;
| Beteko || 750&lt;br /&gt;
|-&lt;br /&gt;
| Hoteko || 800&lt;br /&gt;
|-&lt;br /&gt;
| Siteko || 850&lt;br /&gt;
|-&lt;br /&gt;
| Eteko || 900&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 3 END --&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Arena ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 17&#039;&#039;&#039;&lt;br /&gt;
&#039;&#039;&#039;1st Stage:&#039;&#039;&#039; No Gacha | 2 Guardian&lt;br /&gt;
{| style=&amp;quot;width:100%; border:0; cellpadding:0; cellspacing:0;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 1 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doblood || 2&#039;150&lt;br /&gt;
|-&lt;br /&gt;
| Penblood || 2&#039;300&lt;br /&gt;
|-&lt;br /&gt;
| Beblood || 2&#039;450&lt;br /&gt;
|-&lt;br /&gt;
| Hoblood || 2&#039;600&lt;br /&gt;
|-&lt;br /&gt;
| Siblood || 2&#039;750&lt;br /&gt;
|-&lt;br /&gt;
| Iblood || 2&#039;900&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 1 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 2 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Optional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Dogoliath || 1&#039;200&lt;br /&gt;
|-&lt;br /&gt;
| Pengoliath || 1&#039;350&lt;br /&gt;
|-&lt;br /&gt;
| Begoliath || 1&#039;500&lt;br /&gt;
|-&lt;br /&gt;
| Hogoliath || 1&#039;650&lt;br /&gt;
|-&lt;br /&gt;
| Sigoliath || 1&#039;800&lt;br /&gt;
|-&lt;br /&gt;
| Egoliath || 1&#039;950&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 2 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 3 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Optional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doga || 800&lt;br /&gt;
|-&lt;br /&gt;
| Penga || 850&lt;br /&gt;
|-&lt;br /&gt;
| Bega || 900&lt;br /&gt;
|-&lt;br /&gt;
| Hoga || 950&lt;br /&gt;
|-&lt;br /&gt;
| Siga || 1&#039;000&lt;br /&gt;
|-&lt;br /&gt;
| Ega || 1&#039;050&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 3 END --&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2nd Stage:&#039;&#039;&#039; Boss Battle: Hell Blood + 2 Mini Boss&lt;br /&gt;
[[File:Hellblood.png|100px|left|alt=Hellblood|Hellblood]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Hellblood Base HP: 5&#039;000&lt;br /&gt;
&lt;br /&gt;
Requirement: Finish first Stage in under 3 Minutes.&lt;br /&gt;
&lt;br /&gt;
Gacha: Dragon Set (Red / Orange / Blue / Purple)&lt;br /&gt;
&lt;br /&gt;
== Monslava ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 20&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1st Stage:&#039;&#039;&#039; No Gacha | 2 Guardian&lt;br /&gt;
{| style=&amp;quot;width:100%; border:0; cellpadding:0; cellspacing:0;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 1 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doblood || 2&#039;150&lt;br /&gt;
|-&lt;br /&gt;
| Penblood || 2&#039;300&lt;br /&gt;
|-&lt;br /&gt;
| Beblood || 2&#039;450&lt;br /&gt;
|-&lt;br /&gt;
| Hoblood || 2&#039;600&lt;br /&gt;
|-&lt;br /&gt;
| Siblood || 2&#039;750&lt;br /&gt;
|-&lt;br /&gt;
| Iblood || 2&#039;900&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 1 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 2 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Optional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Dogoliath || 1&#039;200&lt;br /&gt;
|-&lt;br /&gt;
| Pengoliath || 1&#039;350&lt;br /&gt;
|-&lt;br /&gt;
| Begoliath || 1&#039;500&lt;br /&gt;
|-&lt;br /&gt;
| Hogoliath || 1&#039;650&lt;br /&gt;
|-&lt;br /&gt;
| Sigoliath || 1&#039;800&lt;br /&gt;
|-&lt;br /&gt;
| Egoliath || 1&#039;950&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 2 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 3 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Optional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doga || 800&lt;br /&gt;
|-&lt;br /&gt;
| Penga || 850&lt;br /&gt;
|-&lt;br /&gt;
| Bega || 900&lt;br /&gt;
|-&lt;br /&gt;
| Hoga || 950&lt;br /&gt;
|-&lt;br /&gt;
| Siga || 1&#039;000&lt;br /&gt;
|-&lt;br /&gt;
| Ega || 1&#039;050&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 3 END --&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2nd Stage:&#039;&#039;&#039; Boss Battle: Hell Blood + 2 Mini Boss&lt;br /&gt;
[[File:Hellblood.png|100px|left|alt=Hellblood|Hellblood]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Hellblood Base HP: 14&#039;000&lt;br /&gt;
&lt;br /&gt;
Requirement: Finish first Stage in under 3 Minutes.&lt;br /&gt;
&lt;br /&gt;
Gacha: Titan Capsule / Monslava Capsule&lt;br /&gt;
&lt;br /&gt;
== Monslava Blue (Randomly Triggered on Monslava)==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 40&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1st Stage:&#039;&#039;&#039; No Gacha | 2 Guardian&lt;br /&gt;
{| style=&amp;quot;width:100%; border:0; cellpadding:0; cellspacing:0;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 1 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doblood || 2&#039;150&lt;br /&gt;
|-&lt;br /&gt;
| Penblood || 2&#039;300&lt;br /&gt;
|-&lt;br /&gt;
| Beblood || 2&#039;450&lt;br /&gt;
|-&lt;br /&gt;
| Hoblood || 2&#039;600&lt;br /&gt;
|-&lt;br /&gt;
| Siblood || 2&#039;750&lt;br /&gt;
|-&lt;br /&gt;
| Iblood || 2&#039;900&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 1 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 2 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Optional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Dogoliath || 1&#039;200&lt;br /&gt;
|-&lt;br /&gt;
| Pengoliath || 1&#039;350&lt;br /&gt;
|-&lt;br /&gt;
| Begoliath || 1&#039;500&lt;br /&gt;
|-&lt;br /&gt;
| Hogoliath || 1&#039;650&lt;br /&gt;
|-&lt;br /&gt;
| Sigoliath || 1&#039;800&lt;br /&gt;
|-&lt;br /&gt;
| Egoliath || 1&#039;950&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 2 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 3 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Optional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| DevilDokaro || 4&#039;150&lt;br /&gt;
|-&lt;br /&gt;
| DevilPenkaro || 4&#039;300&lt;br /&gt;
|-&lt;br /&gt;
| DevilBekaro || 4&#039;450&lt;br /&gt;
|-&lt;br /&gt;
| DevilHokaro || 4&#039;600&lt;br /&gt;
|-&lt;br /&gt;
| DevilSikaro || 4&#039;750&lt;br /&gt;
|-&lt;br /&gt;
| DevilIkaro || 4&#039;900&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- TABLE 3 END --&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2nd Stage:&#039;&#039;&#039; Boss Battle: Hell Blood + 2 Mini Boss&lt;br /&gt;
[[File:Hellblood.png|100px|left|alt=Hellblood|Hellblood]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Hellblood Base HP: 14&#039;000&lt;br /&gt;
&lt;br /&gt;
Requirement: Finish first Stage in under 3 Minutes.&lt;br /&gt;
&lt;br /&gt;
Gacha: Blue Capsule / Titan Capsule / Monslava Capsule&lt;br /&gt;
&lt;br /&gt;
== Devaberg ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 30&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1st Stage:&#039;&#039;&#039; No Gacha | 2 Guardian&lt;br /&gt;
{| style=&amp;quot;width:100%; border:0; cellpadding:0; cellspacing:0;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 1 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Windra || 2&#039;650&lt;br /&gt;
|-&lt;br /&gt;
| Earthra || 2&#039;800&lt;br /&gt;
|-&lt;br /&gt;
| Moora || 2&#039;950&lt;br /&gt;
|-&lt;br /&gt;
| Aquara || 3&#039;100&lt;br /&gt;
|-&lt;br /&gt;
| Fyra || 3&#039;250&lt;br /&gt;
|-&lt;br /&gt;
| Thundera || 3&#039;400&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 1 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 2 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Optional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doblood || 2&#039;150&lt;br /&gt;
|-&lt;br /&gt;
| Penblood || 2&#039;300&lt;br /&gt;
|-&lt;br /&gt;
| Beblood || 2&#039;450&lt;br /&gt;
|-&lt;br /&gt;
| Hoblood || 2&#039;600&lt;br /&gt;
|-&lt;br /&gt;
| Siblood || 2&#039;750&lt;br /&gt;
|-&lt;br /&gt;
| Iblood || 2&#039;900&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 2 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 3 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Optional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doga || 800&lt;br /&gt;
|-&lt;br /&gt;
| Penga || 850&lt;br /&gt;
|-&lt;br /&gt;
| Bega || 900&lt;br /&gt;
|-&lt;br /&gt;
| Hoga || 950&lt;br /&gt;
|-&lt;br /&gt;
| Siga || 1&#039;000&lt;br /&gt;
|-&lt;br /&gt;
| Ega || 1&#039;050&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 3 END --&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2nd Stage:&#039;&#039;&#039; Boss Battle: Hera + 2 Mini Boss&lt;br /&gt;
[[File:Hera.png|100px|left|alt=Hera|Hera]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Hera Base HP: 7&#039;000&lt;br /&gt;
&lt;br /&gt;
Requirement: Finish first Stage in under 3 Minutes.&lt;br /&gt;
&lt;br /&gt;
Gacha: Divinity Set (Red / Green / Blue / Purple)&lt;br /&gt;
&lt;br /&gt;
== Atlantis ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 40&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1st Stage:&#039;&#039;&#039; No Gacha | 3 Guardian&lt;br /&gt;
{| style=&amp;quot;width:100%; border:0; cellpadding:0; cellspacing:0;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 1 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Dolizard || 3&#039;650&lt;br /&gt;
|-&lt;br /&gt;
| Penlizard || 3&#039;800&lt;br /&gt;
|-&lt;br /&gt;
| Belizard || 3&#039;950&lt;br /&gt;
|-&lt;br /&gt;
| Holizard || 4&#039;100&lt;br /&gt;
|-&lt;br /&gt;
| Silizard || 4&#039;250&lt;br /&gt;
|-&lt;br /&gt;
| Elizard || 4&#039;400&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 1 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 2 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Additional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Dogoliath || 1&#039;200&lt;br /&gt;
|-&lt;br /&gt;
| Pengoliath || 1&#039;350&lt;br /&gt;
|-&lt;br /&gt;
| Begoliath || 1&#039;500&lt;br /&gt;
|-&lt;br /&gt;
| Hogoliath || 1&#039;650&lt;br /&gt;
|-&lt;br /&gt;
| Sigoliath || 1&#039;800&lt;br /&gt;
|-&lt;br /&gt;
| Egoliath || 1&#039;950&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 2 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 3 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Additional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doga || 800&lt;br /&gt;
|-&lt;br /&gt;
| Penga || 850&lt;br /&gt;
|-&lt;br /&gt;
| Bega || 900&lt;br /&gt;
|-&lt;br /&gt;
| Hoga || 950&lt;br /&gt;
|-&lt;br /&gt;
| Siga || 1&#039;000&lt;br /&gt;
|-&lt;br /&gt;
| Ega || 1&#039;050&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 3 END --&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2nd Stage:&#039;&#039;&#039; Boss Battle: Royal Lizard + 2 Mini Boss&lt;br /&gt;
[[File:Royal lizard.png|100px|left|alt=Royal lizard|Royal lizard]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Royal Lizard Base HP: 15&#039;000&lt;br /&gt;
&lt;br /&gt;
Requirement: Finish first Stage in under 4 Minutes.&lt;br /&gt;
&lt;br /&gt;
Gacha: Black Scale Set Box / Scale set (Blue) / Scale set (Grey)&lt;br /&gt;
&lt;br /&gt;
== Yaksa Arena ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1st Stage:&#039;&#039;&#039; No Gacha | 2 Guardian&lt;br /&gt;
{| style=&amp;quot;width:100%; border:0; cellpadding:0; cellspacing:0;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 1 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Dotossakan || 1&#039;200&lt;br /&gt;
|-&lt;br /&gt;
| Pantossakan || 1&#039;350&lt;br /&gt;
|-&lt;br /&gt;
| Betossakan || 1&#039;500&lt;br /&gt;
|-&lt;br /&gt;
| Hotossakan || 1&#039;650&lt;br /&gt;
|-&lt;br /&gt;
| Sitossakan || 1&#039;800&lt;br /&gt;
|-&lt;br /&gt;
| Etoossakan || 1&#039;950&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 1 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 2 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Optional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doblood || 2&#039;150&lt;br /&gt;
|-&lt;br /&gt;
| Penblood || 2&#039;300&lt;br /&gt;
|-&lt;br /&gt;
| Beblood || 2&#039;450&lt;br /&gt;
|-&lt;br /&gt;
| Hoblood || 2&#039;600&lt;br /&gt;
|-&lt;br /&gt;
| Siblood || 2&#039;750&lt;br /&gt;
|-&lt;br /&gt;
| Iblood || 2&#039;900&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 2 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 3 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Optional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| DevilDokaro || 4&#039;150&lt;br /&gt;
|-&lt;br /&gt;
| DevilPenkaro || 4&#039;300&lt;br /&gt;
|-&lt;br /&gt;
| DevilBekaro || 4&#039;450&lt;br /&gt;
|-&lt;br /&gt;
| DevilHokaro || 4&#039;600&lt;br /&gt;
|-&lt;br /&gt;
| DevilSikaro || 4&#039;750&lt;br /&gt;
|-&lt;br /&gt;
| DevilIkaro || 4&#039;900&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 3 END --&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&#039;&#039;&#039;2nd Stage:&#039;&#039;&#039; Boss Battle: Tossakan Boss + 2 Mini Boss&lt;br /&gt;
[[File:Tossakan boss.png|100px|left|alt=Tossakan boss|Tossakan boss]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Tossakan Boss Base HP: 11&#039;000&lt;br /&gt;
&lt;br /&gt;
Requirement: Finish first Stage in under 3 Minutes.&lt;br /&gt;
&lt;br /&gt;
Gacha: Tossakan Coin / Tossakan Boss Coin&lt;br /&gt;
&lt;br /&gt;
== Room of Shadows ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
No Boss Battle | No Gacha | 2 Guardian&lt;br /&gt;
{| style=&amp;quot;width:100%; border:0; cellpadding:0; cellspacing:0;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 1 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Doteko || 650&lt;br /&gt;
|-&lt;br /&gt;
| Penteko || 700&lt;br /&gt;
|-&lt;br /&gt;
| Beteko || 750&lt;br /&gt;
|-&lt;br /&gt;
| Hoteko || 800&lt;br /&gt;
|-&lt;br /&gt;
| Siteko || 850&lt;br /&gt;
|-&lt;br /&gt;
| Eteko || 900&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 1 END --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;width:33%; vertical-align:top;&amp;quot; |&lt;br /&gt;
&amp;lt;!-- TABLE 2 START --&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Additional Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Dokaro || 500&lt;br /&gt;
|-&lt;br /&gt;
| Penkaro || 550&lt;br /&gt;
|-&lt;br /&gt;
| Bekaro || 600&lt;br /&gt;
|-&lt;br /&gt;
| Hokaro || 650&lt;br /&gt;
|-&lt;br /&gt;
| Sikaro || 700&lt;br /&gt;
|-&lt;br /&gt;
| Ikaro || 750&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!-- TABLE 2 END --&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Machine City ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 50&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1st Stage:&#039;&#039;&#039; No Gacha | 2 Guardian&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| No.1 || 2&#039;650&lt;br /&gt;
|-&lt;br /&gt;
| No. 2 || 2&#039;800&lt;br /&gt;
|-&lt;br /&gt;
| No. 3 || 2&#039;950&lt;br /&gt;
|-&lt;br /&gt;
| No. 4 || 3&#039;100&lt;br /&gt;
|-&lt;br /&gt;
| No. 5 || 3&#039;250&lt;br /&gt;
|-&lt;br /&gt;
| No. 6 || 3&#039;400&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2nd Stage:&#039;&#039;&#039; Boss Battle: TB-255 + 2 Mini Boss&lt;br /&gt;
[[File:TB-255.png|100px|left|alt=TB-255|TB-255]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
TB-255 Base HP: 13&#039;000&lt;br /&gt;
&lt;br /&gt;
Requirement: Finish first Stage in under 3 Minutes.&lt;br /&gt;
&lt;br /&gt;
Gacha: Black Scale Set Box / Machine Coin&lt;br /&gt;
&lt;br /&gt;
== Dance Time ==&lt;br /&gt;
Available at &#039;&#039;&#039;Lvl 60&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1st Stage:&#039;&#039;&#039; No Gacha | 2 Guardian&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Mini Boss&lt;br /&gt;
|-&lt;br /&gt;
! Guardian !!  Base HP&lt;br /&gt;
|-&lt;br /&gt;
| Yeppeuni || 4&#039;000&lt;br /&gt;
|-&lt;br /&gt;
| Gwiyeomi || 4&#039;100&lt;br /&gt;
|-&lt;br /&gt;
| Ballari || 4&#039;200&lt;br /&gt;
|-&lt;br /&gt;
| Sangkeumi || 4&#039;300&lt;br /&gt;
|-&lt;br /&gt;
| Hwaljjagi || 4&#039;400&lt;br /&gt;
|-&lt;br /&gt;
| Saechimi || 4&#039;500&lt;br /&gt;
|}&lt;br /&gt;
&#039;&#039;&#039;2nd Stage:&#039;&#039;&#039; Boss Battle: Dance King + 2 Mini Boss&lt;br /&gt;
[[File:Dance King.png|100px|left|alt=Dance King|Dance King]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dance King Base HP: 15&#039;000&lt;br /&gt;
&lt;br /&gt;
Requirement: Finish first Stage in under 3 Minutes.&lt;br /&gt;
&lt;br /&gt;
Gacha: Black Scale Set Box / Dance Box&lt;br /&gt;
&lt;br /&gt;
== Mods ==&lt;br /&gt;
As the Room Master you can type these commands in the waiting room before you start the Match:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;-random&#039;&#039;&#039; randomizes Mini Bosses &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;-hard&#039;&#039;&#039; Boosts HP of Bosses and Mini Bosses significantly. Requirement for triggering Boss Match jumps from 3 Minutes to 5 Minutes. Can also trigger Boss Match on Maps without a Boss, but if the Map had no Gacha Reward previously, it stays without Gacha even in Hard mode.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;-random&#039;&#039;&#039; and &#039;&#039;&#039;-hard&#039;&#039;&#039; can be triggered simultaniously. To undo this Mods, simply type the commands again or change Maps.&lt;br /&gt;
&lt;br /&gt;
Here you can find a [[Commands|Complete list of all commands in game]]&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=Fantasy_Tennis_Solo_Beginner_Guide&amp;diff=319</id>
		<title>Fantasy Tennis Solo Beginner Guide</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Fantasy_Tennis_Solo_Beginner_Guide&amp;diff=319"/>
		<updated>2026-07-20T19:53:17Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: add Guides category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Fantasy Tennis Solo Beginner Guide =&lt;br /&gt;
Author: &#039;&#039;&#039;Ely&#039;&#039;&#039; (Character: &#039;&#039;&#039;Niki&#039;&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Welcome to my &#039;&#039;&#039;Fantasy Tennis Solo STR/DEX Beginner Guide&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This guide is aimed at new players who want to level efficiently while dealing as much DPS as possible from the beginning all the way to the endgame.&lt;br /&gt;
&lt;br /&gt;
Our goal is to maximize both our damage and our economy throughout the leveling process.&lt;br /&gt;
&lt;br /&gt;
=== Skill Points ===&lt;br /&gt;
&lt;br /&gt;
Invest &#039;&#039;&#039;all Skill Points into STR until Level 20&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important:&#039;&#039;&#039;&lt;br /&gt;
When you reach &#039;&#039;&#039;Level 20&#039;&#039;&#039;, check which equipment you plan to buy and adjust your stats accordingly.&lt;br /&gt;
&lt;br /&gt;
This guide covers progression up to &#039;&#039;&#039;Level 30&#039;&#039;&#039;. After that, the shop no longer provides additional stat upgrades, so make sure your final build reaches at least &#039;&#039;&#039;60 DEX&#039;&#039;&#039;. Every remaining Skill Point should be invested into STR.&lt;br /&gt;
&lt;br /&gt;
== Gold Management ==&lt;br /&gt;
&lt;br /&gt;
If you have more Gold than you currently need, there are several good ways to spend it.&lt;br /&gt;
&lt;br /&gt;
* Buy furniture for your house to increase your Gold and EXP income (up to 45%).&lt;br /&gt;
* Purchase Dragon Slayer.&lt;br /&gt;
* Buy Gachas from other players or from the shop.&lt;br /&gt;
&lt;br /&gt;
Managing your Gold correctly early on will make the entire leveling process much easier.&lt;br /&gt;
&lt;br /&gt;
== Guardian Battle Mechanics ==&lt;br /&gt;
&lt;br /&gt;
Understanding Guardian AI is extremely important.&lt;br /&gt;
&lt;br /&gt;
NPCs usually aim for the side where you are &#039;&#039;&#039;not&#039;&#039;&#039; standing.&lt;br /&gt;
&lt;br /&gt;
* Stand more to the right → the NPC is likely to hit left.&lt;br /&gt;
* Stand more to the left → the NPC is likely to hit right.&lt;br /&gt;
* Stay too close to the net → the NPC will often lob behind you.&lt;br /&gt;
&lt;br /&gt;
Learning this behavior allows you to position yourself before every shot and makes Guardian Battles much easier.&lt;br /&gt;
&lt;br /&gt;
== Crab Quick Slot Strategy ==&lt;br /&gt;
&lt;br /&gt;
On every Guardian map, try to throw your &#039;&#039;&#039;Crab&#039;&#039;&#039; as far as possible onto the enemy side.&lt;br /&gt;
&lt;br /&gt;
The goal is either:&lt;br /&gt;
&lt;br /&gt;
* Force the NPC to run into the Crab.&lt;br /&gt;
* Hit the NPC directly.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Life Woods&#039;&#039;&#039; is the first map that contains two enemies. Fortunately, you have plenty of time before reaching it to practice using Crabs efficiently.&lt;br /&gt;
&lt;br /&gt;
== Quick Slot Economy ==&lt;br /&gt;
&lt;br /&gt;
Gold is extremely limited at the beginning of the game, so managing your Quick Slots correctly is very important.&lt;br /&gt;
&lt;br /&gt;
Whenever possible, always buy a full stack.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
* 1 Stack Crab = 50 Crabs&lt;br /&gt;
* Cost: 1,000 Gold&lt;br /&gt;
&lt;br /&gt;
During leveling, continue using the stack until only one use remains.&lt;br /&gt;
&lt;br /&gt;
After that:&lt;br /&gt;
&lt;br /&gt;
# Sell the remaining Quick Slot.&lt;br /&gt;
# Buy a new full stack.&lt;br /&gt;
# Slot it again.&lt;br /&gt;
&lt;br /&gt;
Selling the nearly empty stack refunds roughly &#039;&#039;&#039;50%&#039;&#039;&#039; of the original purchase price.&lt;br /&gt;
&lt;br /&gt;
This trick works for almost every Quick Slot item and saves a large amount of Gold over time.&lt;br /&gt;
&lt;br /&gt;
== Starting Equipment ==&lt;br /&gt;
&lt;br /&gt;
You begin the game with &#039;&#039;&#039;10,000 Gold&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Spend almost everything on increasing your STR and DEX while also buying shoes that provide movement speed.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Item&lt;br /&gt;
! Cost&lt;br /&gt;
! Bonus&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| Rudolph Horn&lt;br /&gt;
| 3,960 Gold&lt;br /&gt;
| +3 STR / +3 DEX&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Mariachi Shoes&lt;br /&gt;
| 1,960 Gold&lt;br /&gt;
| +8 DEX&lt;br /&gt;
| +5 Movement Speed&lt;br /&gt;
|-&lt;br /&gt;
| Racing Gloves&lt;br /&gt;
| 2,360 Gold&lt;br /&gt;
| +3 DEX&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Crab Trap&lt;br /&gt;
| 1,000 Gold&lt;br /&gt;
|&lt;br /&gt;
| Main DPS Quick Slot&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Early Progression ==&lt;br /&gt;
&lt;br /&gt;
Farm &#039;&#039;&#039;Nest of Rubycrab&#039;&#039;&#039; until you unlock &#039;&#039;&#039;The Aelos&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
While leveling, your priority is purchasing the following shop items.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Priority&lt;br /&gt;
! Item&lt;br /&gt;
! Cost&lt;br /&gt;
! Stats&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Star Wing Racket&lt;br /&gt;
| 7,960 Gold&lt;br /&gt;
| +10 STR&lt;br /&gt;
| Optional&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Love Boy T-Shirt&lt;br /&gt;
| 5,836 Gold&lt;br /&gt;
| +12 DEX&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Rooters Pennant&lt;br /&gt;
| 11,960 Gold&lt;br /&gt;
| +15 STR&lt;br /&gt;
| Highly Recommended&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Soft Mohican&lt;br /&gt;
| 3,160 Gold&lt;br /&gt;
| +1 All Stats&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Racket Skillshots ==&lt;br /&gt;
&lt;br /&gt;
Rackets can have different Skillshots.&lt;br /&gt;
&lt;br /&gt;
There are two types:&lt;br /&gt;
&lt;br /&gt;
* Offensive Skillshot&lt;br /&gt;
* Defensive Skillshot&lt;br /&gt;
&lt;br /&gt;
Some rackets provide both.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Rooters Pennant&#039;&#039;&#039; is especially useful because it offers both offensive and defensive Skillshots.&lt;br /&gt;
&lt;br /&gt;
If you miss a ball while having a Skillshot charge available, you can often recover the point instead of immediately losing it.&lt;br /&gt;
&lt;br /&gt;
== Optional Equipment ==&lt;br /&gt;
&lt;br /&gt;
If available, use lower-body equipment that provides &#039;&#039;&#039;STA&#039;&#039;&#039; or &#039;&#039;&#039;WIL&#039;&#039;&#039; for additional Ball Damage.&lt;br /&gt;
&lt;br /&gt;
Otherwise, simply leave the slot empty until you obtain a better item.&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
== Switching from Crabs to Souls ==&lt;br /&gt;
&lt;br /&gt;
During leveling, your DEX will continuously increase. At some point, we will replace our Crab Quick Slot with Souls to increase our single-target damage.&lt;br /&gt;
&lt;br /&gt;
To make this change successfully, it is important to understand the difference between both Quick Slots.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Quick Slot&lt;br /&gt;
! Advantages&lt;br /&gt;
! Disadvantages&lt;br /&gt;
|-&lt;br /&gt;
| Crab&lt;br /&gt;
| Easy to use, attacks enemies in the backline, very fast cooldown&lt;br /&gt;
| Lower damage later in the game&lt;br /&gt;
|-&lt;br /&gt;
| Souls&lt;br /&gt;
| Higher single-target damage, scales with DEX&lt;br /&gt;
| Only attacks the front enemy&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Crabs are easier to use because they can hit enemies standing in the backline.&lt;br /&gt;
&lt;br /&gt;
Souls have higher single-target damage, but they only hit the enemy in front.&lt;br /&gt;
&lt;br /&gt;
Souls also scale with DEX:&lt;br /&gt;
&lt;br /&gt;
* Every 10 DEX provides +1 additional Soul projectile.&lt;br /&gt;
* The first additional projectile starts at 30 DEX.&lt;br /&gt;
&lt;br /&gt;
=== Soul Projectile Scaling ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! DEX&lt;br /&gt;
! Soul Projectiles&lt;br /&gt;
|-&lt;br /&gt;
| 30 DEX&lt;br /&gt;
| 2 Projectiles&lt;br /&gt;
|-&lt;br /&gt;
| 40 DEX&lt;br /&gt;
| 3 Projectiles&lt;br /&gt;
|-&lt;br /&gt;
| 50 DEX&lt;br /&gt;
| 4 Projectiles&lt;br /&gt;
|-&lt;br /&gt;
| 60 DEX&lt;br /&gt;
| 5 Projectiles&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Later in the game, Souls become much stronger than Crabs and are the main single-target DPS Quick Slot for endgame.&lt;br /&gt;
&lt;br /&gt;
You need a good STR/DEX balance before Souls outperform Crabs.&lt;br /&gt;
&lt;br /&gt;
== Ball Damage Knowledge ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important mechanic:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
When a Guardian serves the ball, the incoming damage if you miss the ball is lower than the Ball Damage of the NPC who hits the ball.&lt;br /&gt;
&lt;br /&gt;
Understanding this mechanic helps you decide when taking a risk is acceptable.&lt;br /&gt;
&lt;br /&gt;
== The Aelos Progression ==&lt;br /&gt;
&lt;br /&gt;
The Aelos will be your main farming map for a long time.&lt;br /&gt;
&lt;br /&gt;
The next map after The Aelos is &#039;&#039;&#039;Life Woods&#039;&#039;&#039;. This map contains two NPCs and requires better preparation.&lt;br /&gt;
&lt;br /&gt;
Before moving forward, focus on maximizing your STR and DEX using shop equipment.&lt;br /&gt;
&lt;br /&gt;
If you already have a Bag and unlocked your third Quick Slot, you can start farming Life Woods.&lt;br /&gt;
&lt;br /&gt;
Without a third Quick Slot, Life Woods is much harder because the Goliath can use Souls, which deal a large amount of damage.&lt;br /&gt;
&lt;br /&gt;
== Bag Upgrade ==&lt;br /&gt;
&lt;br /&gt;
If you have AP, you can purchase the Heart Bag Coin for:&lt;br /&gt;
&lt;br /&gt;
* +1 Expand Quick Slot&lt;br /&gt;
&lt;br /&gt;
Most bags are useful, except the red one and Guardian rings, because the additional Quick Slot is extremely valuable.&lt;br /&gt;
&lt;br /&gt;
If you do not have AP, buy the Gold Bag from the shop when reaching Level 25.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Item&lt;br /&gt;
! Cost&lt;br /&gt;
! Bonus&lt;br /&gt;
|-&lt;br /&gt;
| We Love Mod / Dad Bag&lt;br /&gt;
| 11,960 Gold&lt;br /&gt;
| +2 All Stats / +1 Expand Quick Slot&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Level 30 Equipment Upgrade ==&lt;br /&gt;
&lt;br /&gt;
At Level 30, we can replace some equipment to gain additional DEX.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Item&lt;br /&gt;
! Cost&lt;br /&gt;
! Stats&lt;br /&gt;
! Improvement&lt;br /&gt;
|-&lt;br /&gt;
| Flowerboys Shoes&lt;br /&gt;
| 1,240 Gold&lt;br /&gt;
| +11 DEX&lt;br /&gt;
| +3 DEX compared to old shoes&lt;br /&gt;
|-&lt;br /&gt;
| Bear Sole Racket&lt;br /&gt;
| 9,160 Gold&lt;br /&gt;
| +12 STR / +3 DEX&lt;br /&gt;
| Converts 3 STR into 3 DEX&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
After these upgrades, you have reached the maximum STR/DEX values currently available from the shop.&lt;br /&gt;
&lt;br /&gt;
Your target stats should be:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;60 DEX + remaining points into STR&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Depending on your racket, equipment choices, and personal preferences, you will usually end up around:&lt;br /&gt;
&lt;br /&gt;
* 50–60 DEX&lt;br /&gt;
* Remaining stats in STR&lt;br /&gt;
&lt;br /&gt;
== Life Woods and Beyond ==&lt;br /&gt;
&lt;br /&gt;
From Life Woods onward, the game changes.&lt;br /&gt;
&lt;br /&gt;
You will need three different Quick Slots to clear all maps after The Aelos.&lt;br /&gt;
&lt;br /&gt;
Understanding Quick Slot cooldowns is very important.&lt;br /&gt;
&lt;br /&gt;
Quick Slot 1 and Quick Slot 2 share the same cooldown.&lt;br /&gt;
&lt;br /&gt;
This means you can only use one of them at a time.&lt;br /&gt;
&lt;br /&gt;
Quick Slot 3 has its own independent cooldown.&lt;br /&gt;
&lt;br /&gt;
I recommend placing valuable defensive items in this slot.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Slot&lt;br /&gt;
! Purpose&lt;br /&gt;
! Recommended Item&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Damage&lt;br /&gt;
| Crab / Souls&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Healing&lt;br /&gt;
| Magic Potion&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Shield&lt;br /&gt;
| Holy Shield&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Holy Shield&#039;&#039;&#039; is especially useful because it reduces all incoming damage to 1.&lt;br /&gt;
&lt;br /&gt;
Use it whenever you are in danger, for example when an NPC uses Souls.&lt;br /&gt;
&lt;br /&gt;
== Advanced Guardian Mechanics ==&lt;br /&gt;
&lt;br /&gt;
With this setup, you can clear every map.&lt;br /&gt;
&lt;br /&gt;
However, later you should focus on &#039;&#039;&#039;Monslava&#039;&#039;&#039; because it provides a very good amount of Gold.&lt;br /&gt;
&lt;br /&gt;
Higher maps are usually better played in groups.&lt;br /&gt;
&lt;br /&gt;
Farm Life Woods until you are confident enough to focus your damage on the smaller NPC in higher maps.&lt;br /&gt;
&lt;br /&gt;
The smaller NPC is always the first priority because defeating it quickly makes the fight much easier.&lt;br /&gt;
&lt;br /&gt;
== Fighting Multiple NPCs ==&lt;br /&gt;
&lt;br /&gt;
Three mechanics become extremely important:&lt;br /&gt;
&lt;br /&gt;
=== 1. Avoid Over-Pushing ===&lt;br /&gt;
&lt;br /&gt;
Only use one DPS Quick Slot at a time.&lt;br /&gt;
&lt;br /&gt;
Souls, Fireballs, and similar items can push enemies backwards.&lt;br /&gt;
&lt;br /&gt;
Be careful not to push the smaller NPC into the backline, because then the bigger NPC with much more HP will become the front target.&lt;br /&gt;
&lt;br /&gt;
=== 2. Control NPC Movement ===&lt;br /&gt;
&lt;br /&gt;
Try to play the ball toward the bigger NPC.&lt;br /&gt;
&lt;br /&gt;
This prevents the smaller NPC from following the ball and moving into the backline.&lt;br /&gt;
&lt;br /&gt;
Use your DPS Quick Slots one after another.&lt;br /&gt;
&lt;br /&gt;
Slow and controlled damage is better than bursting too quickly.&lt;br /&gt;
&lt;br /&gt;
=== 3. Avoid Early Boss Battles ===&lt;br /&gt;
&lt;br /&gt;
Boss Battle starts when you clear a map too quickly.&lt;br /&gt;
&lt;br /&gt;
Avoid clearing maps under the timer limit.&lt;br /&gt;
&lt;br /&gt;
This mainly happens in Arena and higher maps.&lt;br /&gt;
&lt;br /&gt;
Boss timers can differ depending on the map and may be 4 minutes or more.&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
== Endgame Progression ==&lt;br /&gt;
&lt;br /&gt;
Once you can farm Life Woods without problems, you can move on to Arena and higher maps.&lt;br /&gt;
&lt;br /&gt;
At this point, you should already have enough STR and DEX to make Souls your main damage source.&lt;br /&gt;
&lt;br /&gt;
Remember to switch from Crabs to Souls.&lt;br /&gt;
&lt;br /&gt;
For solo farming, &#039;&#039;&#039;Monslava&#039;&#039;&#039; is the final map we recommend focusing on because it provides a very good amount of Gold.&lt;br /&gt;
&lt;br /&gt;
== Endgame STR/DEX Equipment ==&lt;br /&gt;
&lt;br /&gt;
There are two main ways to reach the highest DPS with a STR/DEX build.&lt;br /&gt;
&lt;br /&gt;
== Option 1: Shop Equipment ==&lt;br /&gt;
&lt;br /&gt;
Spend your Gold on the best STR/DEX equipment available in the shop.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Slot&lt;br /&gt;
! Item&lt;br /&gt;
! Cost&lt;br /&gt;
! Base Stats&lt;br /&gt;
! Enchanted Stats&lt;br /&gt;
|-&lt;br /&gt;
| Head&lt;br /&gt;
| Black Thief Cap&lt;br /&gt;
| 50,000 Gold&lt;br /&gt;
| +6 STR&lt;br /&gt;
| +9 STR&lt;br /&gt;
|-&lt;br /&gt;
| Upper&lt;br /&gt;
| Training Red Hood&lt;br /&gt;
| 100,000 Gold&lt;br /&gt;
| +12 STR&lt;br /&gt;
| +3 DEX&lt;br /&gt;
|-&lt;br /&gt;
| Lower&lt;br /&gt;
| Training Jeans&lt;br /&gt;
| 100,000 Gold&lt;br /&gt;
| +12 STR&lt;br /&gt;
| +3 DEX&lt;br /&gt;
|-&lt;br /&gt;
| Shoes&lt;br /&gt;
| Training Sneakers&lt;br /&gt;
| 50,000 Gold&lt;br /&gt;
| +5 STR / +7 DEX&lt;br /&gt;
| +7 STR / +10 DEX&lt;br /&gt;
|-&lt;br /&gt;
| Auxiliary&lt;br /&gt;
| Angry Face Mask&lt;br /&gt;
| 2,360 Gold&lt;br /&gt;
| +4 STR&lt;br /&gt;
| 30 Day Timer&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Option 2: Gacha Equipment ==&lt;br /&gt;
&lt;br /&gt;
Instead of buying shop equipment, you can invest your Gold into Gachas.&lt;br /&gt;
&lt;br /&gt;
Recommended Gachas:&lt;br /&gt;
&lt;br /&gt;
* Japanese School Coin&lt;br /&gt;
* Rome Soldier Coin&lt;br /&gt;
* Lucky Plate Gloves&lt;br /&gt;
* Bonus Option items&lt;br /&gt;
&lt;br /&gt;
You can also farm the &#039;&#039;&#039;Black Scale Set Boxes&#039;&#039;&#039; in Machine City to obtain endgame equipment.&lt;br /&gt;
&lt;br /&gt;
== Group Play STR/DEX Build ==&lt;br /&gt;
&lt;br /&gt;
For group content, we change our Quick Slots to maximize DPS.&lt;br /&gt;
&lt;br /&gt;
Recommended setup:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Slot&lt;br /&gt;
! Item&lt;br /&gt;
! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Meteorite (Mets)&lt;br /&gt;
| Random damage on opponent side&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Deluxe Healing&lt;br /&gt;
| Area healing for the group&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Souls&lt;br /&gt;
| Main single-target DPS&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Item Bash&lt;br /&gt;
| +20% Damage increase&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Blessed Aim&lt;br /&gt;
| +1 Soul Projectile&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Deluxe Healing and other healing Quick Slots can be obtained from the Gacha Viking Coin.&lt;br /&gt;
&lt;br /&gt;
For Blessed Aim, you need a Bag with:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;+1 Item Buff&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If you cannot use this bonus, use Item Bash instead.&lt;br /&gt;
&lt;br /&gt;
Without the additional Item Buff, only one buff can be active at the same time.&lt;br /&gt;
&lt;br /&gt;
== Final Item Hunting ==&lt;br /&gt;
&lt;br /&gt;
The following items are considered important upgrades for maximum DPS.&lt;br /&gt;
&lt;br /&gt;
=== Gold Gacha / Boss Farming ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Source&lt;br /&gt;
! Reward&lt;br /&gt;
! Item Slot&lt;br /&gt;
|-&lt;br /&gt;
| Arena Boss&lt;br /&gt;
| Boss Gacha&lt;br /&gt;
| Dragon Slayer Racket&lt;br /&gt;
|-&lt;br /&gt;
| Machine City Boss&lt;br /&gt;
| Boss Gacha&lt;br /&gt;
| Full Gear Replacement&lt;br /&gt;
|-&lt;br /&gt;
| Head G Coin&lt;br /&gt;
| Shop Gacha&lt;br /&gt;
| Devil Horn&lt;br /&gt;
|-&lt;br /&gt;
| Panda Coin&lt;br /&gt;
| Shop Gacha&lt;br /&gt;
| Panda Bag (Pink)&lt;br /&gt;
|-&lt;br /&gt;
| Monslava (Blue Capsule)&lt;br /&gt;
| Boss Gacha&lt;br /&gt;
| JFTSE Modulator (Red)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== AP Gacha Farming ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Source&lt;br /&gt;
! Reward&lt;br /&gt;
! Slot&lt;br /&gt;
|-&lt;br /&gt;
| Angel / Devil Coin&lt;br /&gt;
| Devil Horn&lt;br /&gt;
| Head&lt;br /&gt;
|-&lt;br /&gt;
| Angel / Devil Coin&lt;br /&gt;
| Devil Wing&lt;br /&gt;
| Bag&lt;br /&gt;
|-&lt;br /&gt;
| Rare Box II&lt;br /&gt;
| Angel Blade&lt;br /&gt;
| Racket&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Stat Reset and Skill Points ==&lt;br /&gt;
&lt;br /&gt;
When farming your final equipment, your STR and DEX values may change.&lt;br /&gt;
&lt;br /&gt;
You can either:&lt;br /&gt;
&lt;br /&gt;
* Play with less than 60 DEX.&lt;br /&gt;
* Mix equipment to maintain 60 DEX.&lt;br /&gt;
&lt;br /&gt;
The shop contains an item called &#039;&#039;&#039;Wing of Memory&#039;&#039;&#039; which resets all your stats.&lt;br /&gt;
&lt;br /&gt;
At Level 50, you can start saving Skill Points to avoid buying Wing of Memory.&lt;br /&gt;
&lt;br /&gt;
The maximum level where you receive +1 Skill Point is Level 65.&lt;br /&gt;
&lt;br /&gt;
After Level 65, only HP increases.&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
== Enchantment System ==&lt;br /&gt;
&lt;br /&gt;
All PvE Boss Battle Gachas can provide enchantment materials.&lt;br /&gt;
&lt;br /&gt;
These materials are:&lt;br /&gt;
&lt;br /&gt;
* Jewel&lt;br /&gt;
* Tablet&lt;br /&gt;
* Element&lt;br /&gt;
&lt;br /&gt;
=== Elements ===&lt;br /&gt;
&lt;br /&gt;
Elements increase either damage or defense.&lt;br /&gt;
&lt;br /&gt;
For this STR/DEX build, we focus only on increasing damage.&lt;br /&gt;
&lt;br /&gt;
Recommended usage:&lt;br /&gt;
&lt;br /&gt;
* Racket → Damage increase&lt;br /&gt;
* Armor → Defense increase&lt;br /&gt;
&lt;br /&gt;
=== Jewels and Tablets ===&lt;br /&gt;
&lt;br /&gt;
Onyx, Moonstone, and Jasper can be used to enchant equipment with STR, DEX, and other stats.&lt;br /&gt;
&lt;br /&gt;
These are lower-level jewels and have a lower success rate.&lt;br /&gt;
&lt;br /&gt;
Tablets always have a &#039;&#039;&#039;100% success rate&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Element Enchanting ===&lt;br /&gt;
&lt;br /&gt;
When upgrading Elements, stronger Jewels are required.&lt;br /&gt;
&lt;br /&gt;
Recommended Jewels:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Jewel&lt;br /&gt;
! Quality&lt;br /&gt;
|-&lt;br /&gt;
| Spinel&lt;br /&gt;
| Good&lt;br /&gt;
|-&lt;br /&gt;
| Peridot&lt;br /&gt;
| Very Good&lt;br /&gt;
|-&lt;br /&gt;
| Lolite&lt;br /&gt;
| Best choice for higher enchant levels&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For a STR/DEX build, use the &#039;&#039;&#039;Wind Element&#039;&#039;&#039; because it increases the damage of the Soul Quick Slot.&lt;br /&gt;
&lt;br /&gt;
At minimum, upgrade your:&lt;br /&gt;
&lt;br /&gt;
* Dragon Slayer Racket&lt;br /&gt;
* or a better racket&lt;br /&gt;
&lt;br /&gt;
with Wind Element.&lt;br /&gt;
&lt;br /&gt;
=== Enchantment Failure Rules ===&lt;br /&gt;
&lt;br /&gt;
The first 5 Element enchant levels cannot be downgraded.&lt;br /&gt;
&lt;br /&gt;
This means you can use cheaper materials safely.&lt;br /&gt;
&lt;br /&gt;
After +5:&lt;br /&gt;
&lt;br /&gt;
* A failed enchantment reduces the enchantment level by 1.&lt;br /&gt;
&lt;br /&gt;
Maximum Element level:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;+9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Important Endgame Tips ==&lt;br /&gt;
&lt;br /&gt;
When replacing old equipment with stronger items, always check your STR and DEX values again.&lt;br /&gt;
&lt;br /&gt;
Your final equipment may change your stat distribution.&lt;br /&gt;
&lt;br /&gt;
Make sure your build still fits your preferred playstyle.&lt;br /&gt;
&lt;br /&gt;
== Other Useful Mechanics ==&lt;br /&gt;
&lt;br /&gt;
=== Canceling Actions ===&lt;br /&gt;
&lt;br /&gt;
When using a Crystal, you can press:&lt;br /&gt;
&lt;br /&gt;
* S&lt;br /&gt;
* A&lt;br /&gt;
&lt;br /&gt;
to cancel your Item action and Strike.&lt;br /&gt;
&lt;br /&gt;
=== Ghost Mechanic ===&lt;br /&gt;
&lt;br /&gt;
The first player who dies becomes a Ghost.&lt;br /&gt;
&lt;br /&gt;
NPCs will ignore the Ghost player.&lt;br /&gt;
&lt;br /&gt;
This only applies to the ball interaction.&lt;br /&gt;
&lt;br /&gt;
=== Carnival Coin ===&lt;br /&gt;
&lt;br /&gt;
Carnival Coin has a high chance to provide:&lt;br /&gt;
&lt;br /&gt;
* EXP Rings&lt;br /&gt;
* Gold Rings&lt;br /&gt;
&lt;br /&gt;
=== Crystal Timing ===&lt;br /&gt;
&lt;br /&gt;
Using a Crystal right before being hit by certain Quick Slots can prevent you from being staggered.&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
* Souls&lt;br /&gt;
* Meteorites&lt;br /&gt;
&lt;br /&gt;
This does not work against pushing attacks such as:&lt;br /&gt;
&lt;br /&gt;
* Fireball&lt;br /&gt;
* Inferno&lt;br /&gt;
&lt;br /&gt;
=== Selling Unused Quick Slots ===&lt;br /&gt;
&lt;br /&gt;
During leveling, sell Quick Slots that you no longer need.&lt;br /&gt;
&lt;br /&gt;
This is a great way to generate additional Gold.&lt;br /&gt;
&lt;br /&gt;
Unused items have no value while sitting in your inventory, so converting them back into Gold helps your progression.&lt;br /&gt;
```&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=Fantasy_Tennis_Solo_Beginner_Guide&amp;diff=318</id>
		<title>Fantasy Tennis Solo Beginner Guide</title>
		<link rel="alternate" type="text/html" href="https://wiki.jftse.com/index.php?title=Fantasy_Tennis_Solo_Beginner_Guide&amp;diff=318"/>
		<updated>2026-07-20T19:51:52Z</updated>

		<summary type="html">&lt;p&gt;XxharCs: XxharCs moved page User:Marylin to Fantasy Tennis Solo Beginner Guide without leaving a redirect&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Fantasy Tennis Solo Beginner Guide =&lt;br /&gt;
Author: &#039;&#039;&#039;Ely&#039;&#039;&#039; (Character: &#039;&#039;&#039;Niki&#039;&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Welcome to my &#039;&#039;&#039;Fantasy Tennis Solo STR/DEX Beginner Guide&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This guide is aimed at new players who want to level efficiently while dealing as much DPS as possible from the beginning all the way to the endgame.&lt;br /&gt;
&lt;br /&gt;
Our goal is to maximize both our damage and our economy throughout the leveling process.&lt;br /&gt;
&lt;br /&gt;
=== Skill Points ===&lt;br /&gt;
&lt;br /&gt;
Invest &#039;&#039;&#039;all Skill Points into STR until Level 20&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important:&#039;&#039;&#039;&lt;br /&gt;
When you reach &#039;&#039;&#039;Level 20&#039;&#039;&#039;, check which equipment you plan to buy and adjust your stats accordingly.&lt;br /&gt;
&lt;br /&gt;
This guide covers progression up to &#039;&#039;&#039;Level 30&#039;&#039;&#039;. After that, the shop no longer provides additional stat upgrades, so make sure your final build reaches at least &#039;&#039;&#039;60 DEX&#039;&#039;&#039;. Every remaining Skill Point should be invested into STR.&lt;br /&gt;
&lt;br /&gt;
== Gold Management ==&lt;br /&gt;
&lt;br /&gt;
If you have more Gold than you currently need, there are several good ways to spend it.&lt;br /&gt;
&lt;br /&gt;
* Buy furniture for your house to increase your Gold and EXP income (up to 45%).&lt;br /&gt;
* Purchase Dragon Slayer.&lt;br /&gt;
* Buy Gachas from other players or from the shop.&lt;br /&gt;
&lt;br /&gt;
Managing your Gold correctly early on will make the entire leveling process much easier.&lt;br /&gt;
&lt;br /&gt;
== Guardian Battle Mechanics ==&lt;br /&gt;
&lt;br /&gt;
Understanding Guardian AI is extremely important.&lt;br /&gt;
&lt;br /&gt;
NPCs usually aim for the side where you are &#039;&#039;&#039;not&#039;&#039;&#039; standing.&lt;br /&gt;
&lt;br /&gt;
* Stand more to the right → the NPC is likely to hit left.&lt;br /&gt;
* Stand more to the left → the NPC is likely to hit right.&lt;br /&gt;
* Stay too close to the net → the NPC will often lob behind you.&lt;br /&gt;
&lt;br /&gt;
Learning this behavior allows you to position yourself before every shot and makes Guardian Battles much easier.&lt;br /&gt;
&lt;br /&gt;
== Crab Quick Slot Strategy ==&lt;br /&gt;
&lt;br /&gt;
On every Guardian map, try to throw your &#039;&#039;&#039;Crab&#039;&#039;&#039; as far as possible onto the enemy side.&lt;br /&gt;
&lt;br /&gt;
The goal is either:&lt;br /&gt;
&lt;br /&gt;
* Force the NPC to run into the Crab.&lt;br /&gt;
* Hit the NPC directly.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Life Woods&#039;&#039;&#039; is the first map that contains two enemies. Fortunately, you have plenty of time before reaching it to practice using Crabs efficiently.&lt;br /&gt;
&lt;br /&gt;
== Quick Slot Economy ==&lt;br /&gt;
&lt;br /&gt;
Gold is extremely limited at the beginning of the game, so managing your Quick Slots correctly is very important.&lt;br /&gt;
&lt;br /&gt;
Whenever possible, always buy a full stack.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
* 1 Stack Crab = 50 Crabs&lt;br /&gt;
* Cost: 1,000 Gold&lt;br /&gt;
&lt;br /&gt;
During leveling, continue using the stack until only one use remains.&lt;br /&gt;
&lt;br /&gt;
After that:&lt;br /&gt;
&lt;br /&gt;
# Sell the remaining Quick Slot.&lt;br /&gt;
# Buy a new full stack.&lt;br /&gt;
# Slot it again.&lt;br /&gt;
&lt;br /&gt;
Selling the nearly empty stack refunds roughly &#039;&#039;&#039;50%&#039;&#039;&#039; of the original purchase price.&lt;br /&gt;
&lt;br /&gt;
This trick works for almost every Quick Slot item and saves a large amount of Gold over time.&lt;br /&gt;
&lt;br /&gt;
== Starting Equipment ==&lt;br /&gt;
&lt;br /&gt;
You begin the game with &#039;&#039;&#039;10,000 Gold&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Spend almost everything on increasing your STR and DEX while also buying shoes that provide movement speed.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Item&lt;br /&gt;
! Cost&lt;br /&gt;
! Bonus&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| Rudolph Horn&lt;br /&gt;
| 3,960 Gold&lt;br /&gt;
| +3 STR / +3 DEX&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Mariachi Shoes&lt;br /&gt;
| 1,960 Gold&lt;br /&gt;
| +8 DEX&lt;br /&gt;
| +5 Movement Speed&lt;br /&gt;
|-&lt;br /&gt;
| Racing Gloves&lt;br /&gt;
| 2,360 Gold&lt;br /&gt;
| +3 DEX&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Crab Trap&lt;br /&gt;
| 1,000 Gold&lt;br /&gt;
|&lt;br /&gt;
| Main DPS Quick Slot&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Early Progression ==&lt;br /&gt;
&lt;br /&gt;
Farm &#039;&#039;&#039;Nest of Rubycrab&#039;&#039;&#039; until you unlock &#039;&#039;&#039;The Aelos&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
While leveling, your priority is purchasing the following shop items.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Priority&lt;br /&gt;
! Item&lt;br /&gt;
! Cost&lt;br /&gt;
! Stats&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Star Wing Racket&lt;br /&gt;
| 7,960 Gold&lt;br /&gt;
| +10 STR&lt;br /&gt;
| Optional&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Love Boy T-Shirt&lt;br /&gt;
| 5,836 Gold&lt;br /&gt;
| +12 DEX&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Rooters Pennant&lt;br /&gt;
| 11,960 Gold&lt;br /&gt;
| +15 STR&lt;br /&gt;
| Highly Recommended&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Soft Mohican&lt;br /&gt;
| 3,160 Gold&lt;br /&gt;
| +1 All Stats&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Racket Skillshots ==&lt;br /&gt;
&lt;br /&gt;
Rackets can have different Skillshots.&lt;br /&gt;
&lt;br /&gt;
There are two types:&lt;br /&gt;
&lt;br /&gt;
* Offensive Skillshot&lt;br /&gt;
* Defensive Skillshot&lt;br /&gt;
&lt;br /&gt;
Some rackets provide both.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Rooters Pennant&#039;&#039;&#039; is especially useful because it offers both offensive and defensive Skillshots.&lt;br /&gt;
&lt;br /&gt;
If you miss a ball while having a Skillshot charge available, you can often recover the point instead of immediately losing it.&lt;br /&gt;
&lt;br /&gt;
== Optional Equipment ==&lt;br /&gt;
&lt;br /&gt;
If available, use lower-body equipment that provides &#039;&#039;&#039;STA&#039;&#039;&#039; or &#039;&#039;&#039;WIL&#039;&#039;&#039; for additional Ball Damage.&lt;br /&gt;
&lt;br /&gt;
Otherwise, simply leave the slot empty until you obtain a better item.&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
== Switching from Crabs to Souls ==&lt;br /&gt;
&lt;br /&gt;
During leveling, your DEX will continuously increase. At some point, we will replace our Crab Quick Slot with Souls to increase our single-target damage.&lt;br /&gt;
&lt;br /&gt;
To make this change successfully, it is important to understand the difference between both Quick Slots.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Quick Slot&lt;br /&gt;
! Advantages&lt;br /&gt;
! Disadvantages&lt;br /&gt;
|-&lt;br /&gt;
| Crab&lt;br /&gt;
| Easy to use, attacks enemies in the backline, very fast cooldown&lt;br /&gt;
| Lower damage later in the game&lt;br /&gt;
|-&lt;br /&gt;
| Souls&lt;br /&gt;
| Higher single-target damage, scales with DEX&lt;br /&gt;
| Only attacks the front enemy&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Crabs are easier to use because they can hit enemies standing in the backline.&lt;br /&gt;
&lt;br /&gt;
Souls have higher single-target damage, but they only hit the enemy in front.&lt;br /&gt;
&lt;br /&gt;
Souls also scale with DEX:&lt;br /&gt;
&lt;br /&gt;
* Every 10 DEX provides +1 additional Soul projectile.&lt;br /&gt;
* The first additional projectile starts at 30 DEX.&lt;br /&gt;
&lt;br /&gt;
=== Soul Projectile Scaling ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! DEX&lt;br /&gt;
! Soul Projectiles&lt;br /&gt;
|-&lt;br /&gt;
| 30 DEX&lt;br /&gt;
| 2 Projectiles&lt;br /&gt;
|-&lt;br /&gt;
| 40 DEX&lt;br /&gt;
| 3 Projectiles&lt;br /&gt;
|-&lt;br /&gt;
| 50 DEX&lt;br /&gt;
| 4 Projectiles&lt;br /&gt;
|-&lt;br /&gt;
| 60 DEX&lt;br /&gt;
| 5 Projectiles&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Later in the game, Souls become much stronger than Crabs and are the main single-target DPS Quick Slot for endgame.&lt;br /&gt;
&lt;br /&gt;
You need a good STR/DEX balance before Souls outperform Crabs.&lt;br /&gt;
&lt;br /&gt;
== Ball Damage Knowledge ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important mechanic:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
When a Guardian serves the ball, the incoming damage if you miss the ball is lower than the Ball Damage of the NPC who hits the ball.&lt;br /&gt;
&lt;br /&gt;
Understanding this mechanic helps you decide when taking a risk is acceptable.&lt;br /&gt;
&lt;br /&gt;
== The Aelos Progression ==&lt;br /&gt;
&lt;br /&gt;
The Aelos will be your main farming map for a long time.&lt;br /&gt;
&lt;br /&gt;
The next map after The Aelos is &#039;&#039;&#039;Life Woods&#039;&#039;&#039;. This map contains two NPCs and requires better preparation.&lt;br /&gt;
&lt;br /&gt;
Before moving forward, focus on maximizing your STR and DEX using shop equipment.&lt;br /&gt;
&lt;br /&gt;
If you already have a Bag and unlocked your third Quick Slot, you can start farming Life Woods.&lt;br /&gt;
&lt;br /&gt;
Without a third Quick Slot, Life Woods is much harder because the Goliath can use Souls, which deal a large amount of damage.&lt;br /&gt;
&lt;br /&gt;
== Bag Upgrade ==&lt;br /&gt;
&lt;br /&gt;
If you have AP, you can purchase the Heart Bag Coin for:&lt;br /&gt;
&lt;br /&gt;
* +1 Expand Quick Slot&lt;br /&gt;
&lt;br /&gt;
Most bags are useful, except the red one and Guardian rings, because the additional Quick Slot is extremely valuable.&lt;br /&gt;
&lt;br /&gt;
If you do not have AP, buy the Gold Bag from the shop when reaching Level 25.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Item&lt;br /&gt;
! Cost&lt;br /&gt;
! Bonus&lt;br /&gt;
|-&lt;br /&gt;
| We Love Mod / Dad Bag&lt;br /&gt;
| 11,960 Gold&lt;br /&gt;
| +2 All Stats / +1 Expand Quick Slot&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Level 30 Equipment Upgrade ==&lt;br /&gt;
&lt;br /&gt;
At Level 30, we can replace some equipment to gain additional DEX.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Item&lt;br /&gt;
! Cost&lt;br /&gt;
! Stats&lt;br /&gt;
! Improvement&lt;br /&gt;
|-&lt;br /&gt;
| Flowerboys Shoes&lt;br /&gt;
| 1,240 Gold&lt;br /&gt;
| +11 DEX&lt;br /&gt;
| +3 DEX compared to old shoes&lt;br /&gt;
|-&lt;br /&gt;
| Bear Sole Racket&lt;br /&gt;
| 9,160 Gold&lt;br /&gt;
| +12 STR / +3 DEX&lt;br /&gt;
| Converts 3 STR into 3 DEX&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
After these upgrades, you have reached the maximum STR/DEX values currently available from the shop.&lt;br /&gt;
&lt;br /&gt;
Your target stats should be:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;60 DEX + remaining points into STR&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Depending on your racket, equipment choices, and personal preferences, you will usually end up around:&lt;br /&gt;
&lt;br /&gt;
* 50–60 DEX&lt;br /&gt;
* Remaining stats in STR&lt;br /&gt;
&lt;br /&gt;
== Life Woods and Beyond ==&lt;br /&gt;
&lt;br /&gt;
From Life Woods onward, the game changes.&lt;br /&gt;
&lt;br /&gt;
You will need three different Quick Slots to clear all maps after The Aelos.&lt;br /&gt;
&lt;br /&gt;
Understanding Quick Slot cooldowns is very important.&lt;br /&gt;
&lt;br /&gt;
Quick Slot 1 and Quick Slot 2 share the same cooldown.&lt;br /&gt;
&lt;br /&gt;
This means you can only use one of them at a time.&lt;br /&gt;
&lt;br /&gt;
Quick Slot 3 has its own independent cooldown.&lt;br /&gt;
&lt;br /&gt;
I recommend placing valuable defensive items in this slot.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Slot&lt;br /&gt;
! Purpose&lt;br /&gt;
! Recommended Item&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Damage&lt;br /&gt;
| Crab / Souls&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Healing&lt;br /&gt;
| Magic Potion&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Shield&lt;br /&gt;
| Holy Shield&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Holy Shield&#039;&#039;&#039; is especially useful because it reduces all incoming damage to 1.&lt;br /&gt;
&lt;br /&gt;
Use it whenever you are in danger, for example when an NPC uses Souls.&lt;br /&gt;
&lt;br /&gt;
== Advanced Guardian Mechanics ==&lt;br /&gt;
&lt;br /&gt;
With this setup, you can clear every map.&lt;br /&gt;
&lt;br /&gt;
However, later you should focus on &#039;&#039;&#039;Monslava&#039;&#039;&#039; because it provides a very good amount of Gold.&lt;br /&gt;
&lt;br /&gt;
Higher maps are usually better played in groups.&lt;br /&gt;
&lt;br /&gt;
Farm Life Woods until you are confident enough to focus your damage on the smaller NPC in higher maps.&lt;br /&gt;
&lt;br /&gt;
The smaller NPC is always the first priority because defeating it quickly makes the fight much easier.&lt;br /&gt;
&lt;br /&gt;
== Fighting Multiple NPCs ==&lt;br /&gt;
&lt;br /&gt;
Three mechanics become extremely important:&lt;br /&gt;
&lt;br /&gt;
=== 1. Avoid Over-Pushing ===&lt;br /&gt;
&lt;br /&gt;
Only use one DPS Quick Slot at a time.&lt;br /&gt;
&lt;br /&gt;
Souls, Fireballs, and similar items can push enemies backwards.&lt;br /&gt;
&lt;br /&gt;
Be careful not to push the smaller NPC into the backline, because then the bigger NPC with much more HP will become the front target.&lt;br /&gt;
&lt;br /&gt;
=== 2. Control NPC Movement ===&lt;br /&gt;
&lt;br /&gt;
Try to play the ball toward the bigger NPC.&lt;br /&gt;
&lt;br /&gt;
This prevents the smaller NPC from following the ball and moving into the backline.&lt;br /&gt;
&lt;br /&gt;
Use your DPS Quick Slots one after another.&lt;br /&gt;
&lt;br /&gt;
Slow and controlled damage is better than bursting too quickly.&lt;br /&gt;
&lt;br /&gt;
=== 3. Avoid Early Boss Battles ===&lt;br /&gt;
&lt;br /&gt;
Boss Battle starts when you clear a map too quickly.&lt;br /&gt;
&lt;br /&gt;
Avoid clearing maps under the timer limit.&lt;br /&gt;
&lt;br /&gt;
This mainly happens in Arena and higher maps.&lt;br /&gt;
&lt;br /&gt;
Boss timers can differ depending on the map and may be 4 minutes or more.&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
== Endgame Progression ==&lt;br /&gt;
&lt;br /&gt;
Once you can farm Life Woods without problems, you can move on to Arena and higher maps.&lt;br /&gt;
&lt;br /&gt;
At this point, you should already have enough STR and DEX to make Souls your main damage source.&lt;br /&gt;
&lt;br /&gt;
Remember to switch from Crabs to Souls.&lt;br /&gt;
&lt;br /&gt;
For solo farming, &#039;&#039;&#039;Monslava&#039;&#039;&#039; is the final map we recommend focusing on because it provides a very good amount of Gold.&lt;br /&gt;
&lt;br /&gt;
== Endgame STR/DEX Equipment ==&lt;br /&gt;
&lt;br /&gt;
There are two main ways to reach the highest DPS with a STR/DEX build.&lt;br /&gt;
&lt;br /&gt;
== Option 1: Shop Equipment ==&lt;br /&gt;
&lt;br /&gt;
Spend your Gold on the best STR/DEX equipment available in the shop.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Slot&lt;br /&gt;
! Item&lt;br /&gt;
! Cost&lt;br /&gt;
! Base Stats&lt;br /&gt;
! Enchanted Stats&lt;br /&gt;
|-&lt;br /&gt;
| Head&lt;br /&gt;
| Black Thief Cap&lt;br /&gt;
| 50,000 Gold&lt;br /&gt;
| +6 STR&lt;br /&gt;
| +9 STR&lt;br /&gt;
|-&lt;br /&gt;
| Upper&lt;br /&gt;
| Training Red Hood&lt;br /&gt;
| 100,000 Gold&lt;br /&gt;
| +12 STR&lt;br /&gt;
| +3 DEX&lt;br /&gt;
|-&lt;br /&gt;
| Lower&lt;br /&gt;
| Training Jeans&lt;br /&gt;
| 100,000 Gold&lt;br /&gt;
| +12 STR&lt;br /&gt;
| +3 DEX&lt;br /&gt;
|-&lt;br /&gt;
| Shoes&lt;br /&gt;
| Training Sneakers&lt;br /&gt;
| 50,000 Gold&lt;br /&gt;
| +5 STR / +7 DEX&lt;br /&gt;
| +7 STR / +10 DEX&lt;br /&gt;
|-&lt;br /&gt;
| Auxiliary&lt;br /&gt;
| Angry Face Mask&lt;br /&gt;
| 2,360 Gold&lt;br /&gt;
| +4 STR&lt;br /&gt;
| 30 Day Timer&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Option 2: Gacha Equipment ==&lt;br /&gt;
&lt;br /&gt;
Instead of buying shop equipment, you can invest your Gold into Gachas.&lt;br /&gt;
&lt;br /&gt;
Recommended Gachas:&lt;br /&gt;
&lt;br /&gt;
* Japanese School Coin&lt;br /&gt;
* Rome Soldier Coin&lt;br /&gt;
* Lucky Plate Gloves&lt;br /&gt;
* Bonus Option items&lt;br /&gt;
&lt;br /&gt;
You can also farm the &#039;&#039;&#039;Black Scale Set Boxes&#039;&#039;&#039; in Machine City to obtain endgame equipment.&lt;br /&gt;
&lt;br /&gt;
== Group Play STR/DEX Build ==&lt;br /&gt;
&lt;br /&gt;
For group content, we change our Quick Slots to maximize DPS.&lt;br /&gt;
&lt;br /&gt;
Recommended setup:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Slot&lt;br /&gt;
! Item&lt;br /&gt;
! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Meteorite (Mets)&lt;br /&gt;
| Random damage on opponent side&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Deluxe Healing&lt;br /&gt;
| Area healing for the group&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Souls&lt;br /&gt;
| Main single-target DPS&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Item Bash&lt;br /&gt;
| +20% Damage increase&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Blessed Aim&lt;br /&gt;
| +1 Soul Projectile&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Deluxe Healing and other healing Quick Slots can be obtained from the Gacha Viking Coin.&lt;br /&gt;
&lt;br /&gt;
For Blessed Aim, you need a Bag with:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;+1 Item Buff&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If you cannot use this bonus, use Item Bash instead.&lt;br /&gt;
&lt;br /&gt;
Without the additional Item Buff, only one buff can be active at the same time.&lt;br /&gt;
&lt;br /&gt;
== Final Item Hunting ==&lt;br /&gt;
&lt;br /&gt;
The following items are considered important upgrades for maximum DPS.&lt;br /&gt;
&lt;br /&gt;
=== Gold Gacha / Boss Farming ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Source&lt;br /&gt;
! Reward&lt;br /&gt;
! Item Slot&lt;br /&gt;
|-&lt;br /&gt;
| Arena Boss&lt;br /&gt;
| Boss Gacha&lt;br /&gt;
| Dragon Slayer Racket&lt;br /&gt;
|-&lt;br /&gt;
| Machine City Boss&lt;br /&gt;
| Boss Gacha&lt;br /&gt;
| Full Gear Replacement&lt;br /&gt;
|-&lt;br /&gt;
| Head G Coin&lt;br /&gt;
| Shop Gacha&lt;br /&gt;
| Devil Horn&lt;br /&gt;
|-&lt;br /&gt;
| Panda Coin&lt;br /&gt;
| Shop Gacha&lt;br /&gt;
| Panda Bag (Pink)&lt;br /&gt;
|-&lt;br /&gt;
| Monslava (Blue Capsule)&lt;br /&gt;
| Boss Gacha&lt;br /&gt;
| JFTSE Modulator (Red)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== AP Gacha Farming ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Source&lt;br /&gt;
! Reward&lt;br /&gt;
! Slot&lt;br /&gt;
|-&lt;br /&gt;
| Angel / Devil Coin&lt;br /&gt;
| Devil Horn&lt;br /&gt;
| Head&lt;br /&gt;
|-&lt;br /&gt;
| Angel / Devil Coin&lt;br /&gt;
| Devil Wing&lt;br /&gt;
| Bag&lt;br /&gt;
|-&lt;br /&gt;
| Rare Box II&lt;br /&gt;
| Angel Blade&lt;br /&gt;
| Racket&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Stat Reset and Skill Points ==&lt;br /&gt;
&lt;br /&gt;
When farming your final equipment, your STR and DEX values may change.&lt;br /&gt;
&lt;br /&gt;
You can either:&lt;br /&gt;
&lt;br /&gt;
* Play with less than 60 DEX.&lt;br /&gt;
* Mix equipment to maintain 60 DEX.&lt;br /&gt;
&lt;br /&gt;
The shop contains an item called &#039;&#039;&#039;Wing of Memory&#039;&#039;&#039; which resets all your stats.&lt;br /&gt;
&lt;br /&gt;
At Level 50, you can start saving Skill Points to avoid buying Wing of Memory.&lt;br /&gt;
&lt;br /&gt;
The maximum level where you receive +1 Skill Point is Level 65.&lt;br /&gt;
&lt;br /&gt;
After Level 65, only HP increases.&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
== Enchantment System ==&lt;br /&gt;
&lt;br /&gt;
All PvE Boss Battle Gachas can provide enchantment materials.&lt;br /&gt;
&lt;br /&gt;
These materials are:&lt;br /&gt;
&lt;br /&gt;
* Jewel&lt;br /&gt;
* Tablet&lt;br /&gt;
* Element&lt;br /&gt;
&lt;br /&gt;
=== Elements ===&lt;br /&gt;
&lt;br /&gt;
Elements increase either damage or defense.&lt;br /&gt;
&lt;br /&gt;
For this STR/DEX build, we focus only on increasing damage.&lt;br /&gt;
&lt;br /&gt;
Recommended usage:&lt;br /&gt;
&lt;br /&gt;
* Racket → Damage increase&lt;br /&gt;
* Armor → Defense increase&lt;br /&gt;
&lt;br /&gt;
=== Jewels and Tablets ===&lt;br /&gt;
&lt;br /&gt;
Onyx, Moonstone, and Jasper can be used to enchant equipment with STR, DEX, and other stats.&lt;br /&gt;
&lt;br /&gt;
These are lower-level jewels and have a lower success rate.&lt;br /&gt;
&lt;br /&gt;
Tablets always have a &#039;&#039;&#039;100% success rate&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Element Enchanting ===&lt;br /&gt;
&lt;br /&gt;
When upgrading Elements, stronger Jewels are required.&lt;br /&gt;
&lt;br /&gt;
Recommended Jewels:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Jewel&lt;br /&gt;
! Quality&lt;br /&gt;
|-&lt;br /&gt;
| Spinel&lt;br /&gt;
| Good&lt;br /&gt;
|-&lt;br /&gt;
| Peridot&lt;br /&gt;
| Very Good&lt;br /&gt;
|-&lt;br /&gt;
| Lolite&lt;br /&gt;
| Best choice for higher enchant levels&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For a STR/DEX build, use the &#039;&#039;&#039;Wind Element&#039;&#039;&#039; because it increases the damage of the Soul Quick Slot.&lt;br /&gt;
&lt;br /&gt;
At minimum, upgrade your:&lt;br /&gt;
&lt;br /&gt;
* Dragon Slayer Racket&lt;br /&gt;
* or a better racket&lt;br /&gt;
&lt;br /&gt;
with Wind Element.&lt;br /&gt;
&lt;br /&gt;
=== Enchantment Failure Rules ===&lt;br /&gt;
&lt;br /&gt;
The first 5 Element enchant levels cannot be downgraded.&lt;br /&gt;
&lt;br /&gt;
This means you can use cheaper materials safely.&lt;br /&gt;
&lt;br /&gt;
After +5:&lt;br /&gt;
&lt;br /&gt;
* A failed enchantment reduces the enchantment level by 1.&lt;br /&gt;
&lt;br /&gt;
Maximum Element level:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;+9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Important Endgame Tips ==&lt;br /&gt;
&lt;br /&gt;
When replacing old equipment with stronger items, always check your STR and DEX values again.&lt;br /&gt;
&lt;br /&gt;
Your final equipment may change your stat distribution.&lt;br /&gt;
&lt;br /&gt;
Make sure your build still fits your preferred playstyle.&lt;br /&gt;
&lt;br /&gt;
== Other Useful Mechanics ==&lt;br /&gt;
&lt;br /&gt;
=== Canceling Actions ===&lt;br /&gt;
&lt;br /&gt;
When using a Crystal, you can press:&lt;br /&gt;
&lt;br /&gt;
* S&lt;br /&gt;
* A&lt;br /&gt;
&lt;br /&gt;
to cancel your Item action and Strike.&lt;br /&gt;
&lt;br /&gt;
=== Ghost Mechanic ===&lt;br /&gt;
&lt;br /&gt;
The first player who dies becomes a Ghost.&lt;br /&gt;
&lt;br /&gt;
NPCs will ignore the Ghost player.&lt;br /&gt;
&lt;br /&gt;
This only applies to the ball interaction.&lt;br /&gt;
&lt;br /&gt;
=== Carnival Coin ===&lt;br /&gt;
&lt;br /&gt;
Carnival Coin has a high chance to provide:&lt;br /&gt;
&lt;br /&gt;
* EXP Rings&lt;br /&gt;
* Gold Rings&lt;br /&gt;
&lt;br /&gt;
=== Crystal Timing ===&lt;br /&gt;
&lt;br /&gt;
Using a Crystal right before being hit by certain Quick Slots can prevent you from being staggered.&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
* Souls&lt;br /&gt;
* Meteorites&lt;br /&gt;
&lt;br /&gt;
This does not work against pushing attacks such as:&lt;br /&gt;
&lt;br /&gt;
* Fireball&lt;br /&gt;
* Inferno&lt;br /&gt;
&lt;br /&gt;
=== Selling Unused Quick Slots ===&lt;br /&gt;
&lt;br /&gt;
During leveling, sell Quick Slots that you no longer need.&lt;br /&gt;
&lt;br /&gt;
This is a great way to generate additional Gold.&lt;br /&gt;
&lt;br /&gt;
Unused items have no value while sitting in your inventory, so converting them back into Gold helps your progression.&lt;br /&gt;
```&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=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>
</feed>