Suspend-aware modifications to the DadsMmoLab WoW 3.3.5a + AzerothCore + Playerbots stack so a Steam Deck can be put to sleep mid-session and resumed without breaking the world.
AzerothCore stores time-based events (creature respawns, mail expiry, daily/weekly resets, instance lockouts) as absolute Unix timestamps and compares them against GameTime::GetGameTime(). When the deck enters S3 suspend, the wall clock keeps advancing while the worldserver process is frozen. On resume the server wakes into a future where everything that should have ticked during sleep is past due — respawn storms, premature mail expiry, scripted-event pile-ups, and the WoW client's ~30s ping timeout has already killed the connection.
Two coordinated mechanisms, one C++ patch and one systemd hook:
-
Within-day suspend (typical: morning → evening pickup) — a patch to
GameTime.cppdetects suspends by sampling the divergence betweenCLOCK_BOOTTIME(advances during suspend) andCLOCK_MONOTONIC(does not), accumulates the suspend duration into aSuspendOffset, and subtracts it from every wall-clock value the server exposes. The world's perceived clock pauses during sleep; all existing comparisons againstGameTime::GetGameTime()automatically see frozen time. No call-site changes. -
Cross-reset suspend (overnight, multi-day, deck-in-a-drawer) — a systemd-sleep hook records the next daily-quest-reset deadline (plus a 15-minute grace) before suspend, and on resume cleanly tears down the Docker stack if we slept past it. The launcher script picks up the kill flag and exits, prompting the user to relaunch on next play. This bounds the regime the C++ patch handles to <24h and eliminates the risk of dozens of reset events firing in a single tick after weeks of abandonment.
A World::Update diff clamp at 1000 ms is added defensively to protect anything that consumes the raw tick diff (session timeouts, AI ticks) from a single huge update.
| Path | Purpose |
|---|---|
bootstrap.sh |
One-shot installer: clones source, applies patches, builds the worldserver, installs the launcher and systemd hook. Run once after the upstream DadsMmoLab install-wow.sh has set the deck up (see Install below). |
patches/0001-gametime-suspend-aware.patch |
C++ patch to src/server/game/Time/GameTime.cpp (suspend detection + offset) and src/server/game/World/World.cpp (diff clamp). Applies cleanly against mod-playerbots/azerothcore-wotlk branch Playerbot. |
systemd/10-wow-server |
Installed to /etc/systemd/system-sleep/10-wow-server. Runs pre/post around suspend; enforces the daily-reset shutdown cap. |
launcher/wow-playerbots-launcher.sh |
Installed to ~/wow-playerbots-launcher.sh. Replaces the upstream Dad's MMO Lab launcher with a version that watches for the sleep hook's flags, exits cleanly on long-suspend kill, and drives ydotool to dismiss the disconnect dialog and re-enter world on short-suspend resume. |
Starting point. This bootstrap assumes you've already run the upstream DadsMmoLab install-wow.sh on your deck once before, for any option. We rely on its side effects — Docker installed, git installed, the pacman keyring populated, SteamOS dev mode enabled, sudo password set — but not on its output. If you've never run it, run it once (the lightest option is fine, you don't have to play through it) and then come back here.
About any existing Playerbots install. This bootstrap does not upgrade or migrate a pre-existing ~/wow-server-playerbots/ from a previous DadsMmoLab Playerbots run. It clones and builds its own copy from scratch against the patched source. If that directory already exists, the bootstrap will detect it and prompt — answer yes to remove it and get a clean rebuild. There's no need to manually delete anything first.
git clone https://github.com/chadrach/Azerothcore-deck.git
cd Azerothcore-deck
bash bootstrap.shBuild time: 2–4 hours for the worldserver compile. Keep the deck plugged in.
After install, verify with these tests in increasing order of confidence:
-
Suspend detection log line. Tail
docker logs -f $(docker ps --format '{{.Names}}' | grep worldserver). Suspend the deck for 60 seconds viasystemctl suspend, then wake it. You should seeSuspend detected: paused for ~60 seconds. Cumulative offset now ~60 seconds.in the log. -
Same-day respawn freeze. In-game: kill a creature with a known 5-minute respawn (any city guard). Suspend the deck for 30 minutes. Resume. The creature should still be dead and respawn only after 5 in-game minutes elapse from when you killed it, not immediately on wake.
-
Auto-reconnect. With the WoW client in-world, suspend for 5 minutes. Resume. The launcher should see
/run/wow-resumed, check the socket, andydotool-press Enter twice. If this works reliably you're done; if not you'll need to manually click Reconnect. -
Daily-reset cap near miss. Start a session at 14:00. Suspend at 14:30. Resume at 17:00. The hook computed next-reset = 03:00 tomorrow; 17:00 is comfortably before it, so the server stays up and resume is seamless.
-
Daily-reset cap crossed. Start a session at 22:00. Suspend at 22:30. Resume at 08:00 the next morning. The hook computed next-reset = 03:00 that night; 08:00 is past it, so
docker compose downfires and/run/wow-killedis created. The launcher prints the long-suspend message, force-quits WoW, and exits. Manually relaunch — dailies are properly reset for the new day. -
Long abandonment. Suspend the deck for a few days. On resume, same path as test 5 — clean shutdown, no respawn storm, no event pile-up. Confirm with
docker psthat the stack is fully down before relaunching.
At install time, bootstrap.sh does a fresh --depth 1 clone of two upstream repos into ~/wow-server-playerbots/:
mod-playerbots/azerothcore-wotlkbranchPlayerbot— the worldserver source.mod-playerbots/mod-playerbotsbranchmaster— the Playerbots module, dropped intomodules/mod-playerbots/.
Our patch (patches/0001-gametime-suspend-aware.patch) is then applied with git apply against that fresh clone before the Docker build kicks off. We do not vendor the upstream source in this repo and we do not maintain forks — every install pulls upstream HEAD of the Playerbot branch fresh, so upstream bug fixes flow through automatically. If upstream ever drifts to where the patch no longer applies cleanly, the bootstrap fails loudly with a clear message; the troubleshooting section below covers that case.
- Patch fails to apply. The upstream
mod-playerbots/azerothcore-wotlkPlayerbot branch has drifted. Either rebase the patch on the current HEAD or pin the bootstrap to a known-good commit viagit clone ... && git checkout <sha>. - No "Suspend detected" log line after waking. The
clock_gettime(CLOCK_BOOTTIME)call returned an error, orgrowthwas below the 2-second noise threshold (e.g., the deck only slept for one second). For real suspends this should always fire. ydotoolreconnect does nothing. Theydotoold.servicemay not be running. Check withsystemctl status ydotooldandsudo systemctl enable --now ydotoold. In Gaming Mode under Wayland,xdotoolwill not work —ydotoolis required.- systemd hook didn't run. Check
journalctl -u systemd-suspend.service -b 0andjournalctl -t wow-sleep-hook -b 0. Verify the hook is executable and at the right path:ls -la /etc/systemd/system-sleep/10-wow-server.