Patterns

The Three Patterns You'll See Everywhere

Almost every feature you write or read lives in one of these three shapes.

Pattern 01

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.

Pattern 02

Managers

Long-lived subsystem singletons. CastleManager, OlympiadManager, RaidBossSpawnManager, DailyResetManager... about 50 of them. getInstance().load() on boot, ThreadPool timers for tick work.

Pattern 03

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:

  1. Create a class that implements the right I*Handler interface (e.g. IItemHandler).
  2. Register it in the matching registry, usually in its constructor or via the registry's registerHandler() call site.
  3. 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.