The Three Patterns You'll See Everywhere
Almost every feature you write or read lives in one of these three shapes.
Handlers
Pluggable behavior keyed off a tag in data files. IItemHandler, IChatHandler, IEffectHandler, IAdminCommandHandler and many more. Add a new tag, write one class, register it once, and the rest of the engine discovers it.
Managers
Long-lived subsystem singletons. CastleManager, OlympiadManager, RaidBossSpawnManager, DailyResetManager... about 50 of them. getInstance().load() on boot, ThreadPool timers for tick work.
Scripts
Quests, events and per-NPC AI live as Java sources under dist/data/scripts/ and are compiled at server startup by the JDK's in-process compiler. Restart the server to reload script changes.
Pattern 01 - Handlers
Handlers are the answer to "the data file says do this thing; what code does it?". Each handler family is a registry from a string tag to a Java class that implements an interface.
handler/
IItemHandler.java ◄─ interface
ItemHandler.java ◄─ registry (Map<String, IItemHandler>)
itemhandlers/
ScrollOfEnchant.java ◄─ concrete impls
BeastSoulShot.java
ExtractableItems.java
... (dozens per category)
Same shape repeats for:
ChatHandler AdminCommandHandler BypassHandler
ActionClickHandler EffectHandler ConditionHandler
TargetTypeHandler PunishmentHandler VoicedCommandHandler
UserCommandHandler DailyMissionHandler ...
Adding a new handler is three steps:
- Create a class that implements the right
I*Handlerinterface (e.g.IItemHandler). - Register it in the matching registry, usually in its constructor or via the registry's
registerHandler()call site. - Reference it by name from an XML data file (e.g.
handler="ScrollOfEnchant"on an item).
Pattern 02 - Managers
Managers are the long-lived singletons that own subsystem state. When something must outlive a single packet handler and stay consistent across all the players touching it - siege ownership, the running Olympiad tournament, raid boss respawn timers - it lives in a manager.
managers/ (~50 classes) AirShipManager DailyResetManager OlympiadManager AntiFeedManager DatabaseIdManager PetitionManager BoatManager DatabaseSpawnManager PrecautionaryRestart CaptchaManager DuelManager PunishmentManager CastleManager FakePlayerChatManager QuestManager CastleManorManager FortManager RaidBossSpawnManager ClanEntryManager FortSiegeManager SellBuffsManager ClanHallAuctionMgr GlobalVariablesManager SiegeManager CoupleManager GraciaSeedsManager WalkingManager CursedWeaponsManager InstanceManager ... CustomMailManager MentorManager
Shape (every manager looks like this):
getInstance() ─ singleton
load() ─ pulls XML + SQL once on boot
schedule(...) ─ ThreadPool timers for periodic work
public API ─ called by handlers, scripts, packets,
and other managers
Lifecycle: created during boot, survives until the shutdown hook fires. State that has to be authoritative across packets/players lives here, not in handlers.
Pattern 03 - Scripts
Quests, events, and per-NPC AI are not compiled into the main jar. They are Java source files under dist/data/scripts/ that the server compiles in-process at boot using the JDK's javax.tools.JavaCompiler API. This is why server owners can ship a new quest as a .java file and not a recompile.
dist/data/scripts/
ai/ ─ monster AI overrides
custom/ ─ server-owner additions
events/ ─ seasonal / world events
handlers/ ─ admin & gameplay extensions
instances/ ─ instanced dungeons
quests/ ─ every quest, per chronicle's questline
vehicles/ ─ boats / airships behavior
village_master/ ─ class change NPCs
Path at boot:
dist/data/scripts/*.java
│
▼
ScriptEngine.compile() ─ JDK in-process compiler
│
▼
isolated ClassLoader ─ one per script (hot reload)
│
▼
QuestManager / handlers see new instances
That is why server owners restart when a quest changes: the scripts are read fresh, compiled, and re-registered.