Things That Trip People Up
The mistakes that look reasonable on day one and cost a weekend on day two.
✗ "I'll fix it in the shared lib" ─► there is no shared lib.
Each chronicle is its own
codebase. Patches must be
ported folder by folder.
✗ "Scripts should be in the jar" ─► quests/AI live as source
under dist/data/scripts/
and are compiled at boot.
✗ "XML is just configuration" ─► XML files in dist/data/ are
authoritative game content.
Java loaders are parsers,
not the source of truth.
✗ "Login and Game share a process" ─► two JVMs, two daemons.
They talk over a TCP socket
via LoginServerThread.
✗ "Just hot-reload the handler" ─► most handlers are loaded
once at boot. Scripts can
be reloaded; core handlers
usually cannot.
✗ Editing under bin/ ─► bin/ is Eclipse build output.
Edit java/ instead.
Walking through each pitfall
"I'll fix it once in a shared lib"
There is no shared lib. Each chronicle directory is its own codebase, its own java/, its own dist/, its own jar. A fix in L2J_Mobius_GD_3.0_Lindvior/ does not propagate. Port it folder by folder; review the diff each time because the surrounding code drifts between chronicles.
"Scripts should be in the jar"
Quests, AI overrides and event scripts live as Java source files under dist/data/scripts/ and are compiled at server boot by the JDK's JavaCompiler. If you put a quest class in gameserver/ instead, it will be in the jar but it will not be registered with QuestManager.
"XML is just configuration"
XML in dist/data/ is the authoritative source of game content, items, skills, NPCs, drops, spawns, zones. The Java loaders in gameserver/data/xml/ are parsers, not the source of truth. If an item behaves wrong, the bug is almost always in the XML; only if the XML is correct should you blame the loader.
"Login and Game share a process"
They do not. Two separate JVMs, two separate working directories, two separate log files. They talk over a private TCP socket via LoginServerThread. If you grep the GameServer for "account auth" you will not find it, that code lives in the LoginServer.
"Just hot-reload the handler"
Most handlers are registered once during boot. The runtime registries are not designed for dynamic re-registration. Scripts can be reloaded (the JDK compiler reruns and a fresh classloader replaces the old one), and a handful of admin //reload commands cover specific subsystems, but core handlers in handler/ are not among them.
Editing files under bin/
bin/ is Eclipse's compile output. Your changes there will be overwritten the next time the project builds. Edit java/ instead and let the build copy the result into bin/ and then into dist/libs/.
Less obvious traps
Wrong chronicle's client
If you build L2J_Mobius_GD_3.0_Lindvior/ and connect with a 1.0 client, the encryption handshake completes but the very first in-world packet looks malformed and the client disconnects. Each chronicle expects a specific protocol version.
Forgetting the bridge port
If the GameServer cannot reach the LoginServer's bridge socket, it boots normally but every login attempt fails with "Session expired". The fix is on the LoginServer side (firewall, bind address, registered server entry), not in the GameServer config.
Cached singletons after XML edits
Edited an item's stats? The change does not take until restart. The XML is parsed once at boot into ItemData singletons. Reload commands exist for some data files; check the admin command list before assuming a restart is mandatory.
Mismatched schema
Adding a column to dist/db_installer/*.sql is half the work. The matching data/sql/*Table.java has to learn to read and write the new column too. If you only add the column, it stays NULL forever.