The stock scripts stop the game with SIGKILL (mbkill.sh runs kill -9) and
'./reboot' only calls mbrestart.sh, so anything still parked in a character's
deferred database job is thrown away: experience is written five minutes after
it is earned and stats/skills thirty seconds after they change
(engine.jobs.DatabaseUpdateJob). PlayerCharacter.updateDatabase() is an empty
stub, so there was no write-out path at all.
Added engine.gameManager.GracefulShutdown:
flushAllPlayers() - writes skills/powers, stat modifiers and experience
for every online character
announce(String) - server wide flash message
registerShutdownHook() - flush on an orderly SIGTERM (SIGKILL cannot be
intercepted by design)
gracefulStop(int) - countdown, flush, then stop the login and world
servers
Added the './shutdown [seconds]' dev command (engine.devcmd.cmds.ShutdownCmd,
default 60s, ADMIN only like every other dev command), registered together with
the shutdown hook in WorldServer.main and LoginServer.main.
Verified on a MagicBox container: ant builds clean, the boot log shows
"shutdown hook registered for WorldServer"/"LoginServer", and kill -TERM on the
world server logs "WorldServer is terminating, flushing player data" ->
"flush finished, saved=0 failed=0" -> clean exit.