Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
SunkenCryptTimer
Client-only HUD countdown + ground rings for dungeons on servers running Venture Location Reset: 'resets in 2d 14h' near any entrance, workbench-style ring at VLR's check radius. No server changes needed.
CHANGELOG
CHANGELOG — valheimmer / SunkenCryptTimer
2026-10-06 — v1.1.8: match status-effect text size, sit below the effect row
- Field feedback on v1.1.7's right-side label: font too large, and the fixed 170 px top offset overlapped the minimap. The label now copies its font size from the status-effect template text (the small text on the Rested/Wet icons) and repositions itself every update just below the live bottom edge of the status-effect row, right edges aligned - so wrapped rows of effect icons push the timer down instead of it overlapping anything. HudOffsetY is now only a fallback offset for the rare case the row can't be located.
2026-10-06 — v1.1.7: right-side label, friendly ground-feature names, no rings on graves
- HUD label moved from top-center to the top-right corner (user preference; the top-center spot collides with event/notice text). Right-aligned, anchored 20 px from the right edge; word wrap on with the rect width clamped to the canvas, so long lines ("Odin's Tomb (-435,-142) - resets in 9d 3h") wrap to extra lines growing downward instead of running off the screen. HudOffsetY now means the offset below the top edge on the right side - raise it if it overlaps the minimap or right-side UI mods.
- Field log showed VLR stamps nearly every zone location on the Stormer server (Grave1, InfestedTree01, SwampHut1/2/5, SwampRuin1, StoneTower1, StoneTowerRuins09, Dolmen01, ShipSetting01, Runestone_, WoodHouse2/4/5, Crypt4, ...), so the timer label was firing for all of them with raw prefab names ("Grave1 (-435,-142) - resets in ...") - and kept cycling the whole way home through meadows, which is why the text "kept updating after leaving the swamp". Ground features now get friendly names (Grave, Infested Tree, Swamp Hut, Swamp Ruin, Stone Tower, Ruined Tower, Shipwreck, Dolmen, Runestone, Abandoned House, Goblin Camp); unknown Runestone_/StoneTowerRuins* variants collapse by prefix.
- Ring bug from the same log: ground features (Grave1, InfestedTree01, SwampHut2, SwampRuin1) each got a phantom 45.3 m ring - the same radius as crypts. ZNetScene.GetPrefab does not resolve their location prefabs, so the radius fallback borrowed a neighbouring crypt's dungeon generator (dense swamp = always one within the 120 m bound). Location prefabs now resolve through ZoneSystem's own location registry (GetPrefab kept as secondary), so ground features correctly resolve to "no ring"; the generator fallback only remains for genuinely unresolvable names ("Dungeon" placeholder, DG_*).
2026-10-06 — v1.1.6: fix duplicate HUD labels (v1.1.5 field regression)
- First field session with v1.1.5's clone-based label showed timer text twice (red top-center + brown right side, sometimes running off the right screen edge) with copies persisting long after leaving the dungeon. Session log: "HUD label ready" fired 34 times in one session (should be once per world), sources alternating between a HUD mod's 'Name' and 'TimeText' panels, each creation followed by "Can't remove CanvasRenderer because TextMeshProUGUI depends on it".
- Cause: the label was created by cloning some other panel's text as its SIBLING and stripping non-TMP components (including the CanvasRenderer TMP needs). Parenting into foreign panels means the label inherits their color (brown/red), anchors to the panel's rect instead of the screen (off-screen), and dies or gets orphaned when those panels rebuild/pool — orphaned copies are unreachable by Hide(), hence the persistent text.
- Fix: only the FONT is taken from a donor label (event-name font if loaded, else any actively rendering label). The label itself is built from scratch as a direct child of the HUD canvas root — true top-center-of-screen anchoring, no component stripping, no CanvasRenderer errors. Style (size + color) comes consistently from the event-name element instead of whatever panel donated the font. Stale copies from earlier HUD rebuilds are swept by name on recreation so only one label can ever exist.
2026-10-06 — v1.1.2: fix invisible HUD text + missing rings on dedicated servers
- First real-server test (Stormer dedicated server, Valheim 1.0.16) showed neither feature; session-log instrumentation added in-between builds pinpointed both causes.
- HUD text invisible: the font copied from the vanilla event-name label was a non-null reference to a not-yet-loaded font asset (Unity 6 lazy loading) — TMP renders nothing and logs "no Font Asset assigned". Fonts are now trusted only when their atlas texture is resident; otherwise the HUD is scanned for any actively rendering label's font, then TMP's default asset, retying each update until one loads (so late font assignment self-heals).
- Rings never created: dungeon names resolved to the generic "Dungeon"
placeholder because the LocationProxy ZDO's
s_locationstring reads empty on dedicated servers (worked on the local test server — why the smoke test passed). Names now come (via reflection) from the proxy's privatem_instance— the spawned location GameObject, whose name isSunkenCrypt4(Clone)— with the ZDO field and a zone-system position match as fallbacks. - Ring lifecycle now logs at Info level once per stage (
zone rings: template ready,ring radius: <prefab> <r>m,ring created: ...,tracked dungeon: '<name>' (via <source>)), so field failures are diagnosable from LogOutput.log without enabling DebugLogging. - Buildable on any machine:
dotnet build -p:GameDir=... -p:ProfileDir=...(first non-124 build; dotnet SDK 9 installed on Machine B for this).
2026-10-06 — GitHub publish + Thunderstore packaging
- Public repo created: https://github.com/altpersona/SunkenCryptTimer (MIT license, gh CLI authenticated by user over device flow, SSH remote via uploaded ed25519 key). Private/local files kept out via .gitignore: DEV_NOTES.md, TODO.md, deploy.sh, setup-local-server.py, .claude/, build artifacts (leak scan: server IP/local paths only ever lived in those).
- Release v1.1.0 published with assets
SunkenCryptTimer-v1.1.0.zip(Thunderstore format: manifest+README+icon+CHANGELOG at root, DLL under Plugins/) and the raw DLL. - README rewritten user-facing (requirements matrix, install, usage incl. VLR approach-trigger semantics, config table, build-from-source, version compatibility).
package.sh(builds the zip),tools/make_icon.py(256×256 icon: timer ring + arched crypt door), manifest description trimmed to 204/250 chars, website_url set to the GitHub repo. Thunderstore upload itself is the remaining user step (TODO.md).
2026-10-05 — v1.1.0: zone rings + deep VLR research
- Research session (all verified against VLR master source, the server-synced config,
and raw game asset dumps):
- Stormer server VLR config:
ResetTime=10in-game days (~5 h real; 1 day ≈ 30 min),OverrideResetTimes=false→ ALL dungeon types use 10, incl. Sunken Crypts (SunkenCrypt4; theCrypt2/3/4names are black-forest burial chambers). - Reset does NOT happen in the background: fires when a modded player approaches
≤100 m horizontal (VLR
RESET_RANGE; watcher gives up >200 m; ~5 s after zone load, 1 s polls, ONE SHOT per zone load). Reset executes on the approaching client (VLR adds its watcher only on non-dedicated instances, needs ZDO ownership). - Blocks ("player activity", horizontal-only checks): player pieces/tombstones/players
within exteriorRadius = 12 m (ground) or dungeon radius = 45.25 m
(hypot(64/2,64/2) of
DG_SunkenCrypt.m_zoneSize=64×64, at interior altitude y≥4000) of the crypt center. Skip defers, never cancels; timer keeps counting. - Terrain: raising land does NOT block resets, but VLR's
TerrainReset.ResetTerrainreverts all terrain mods within the 12 m ground radius on each reset.
- Stormer server VLR config:
- v1.1.0 changes (user decisions: 45 m ring only, instanced dungeons only, same
plugin, visible within 100 m):
- New
SunkenCryptTimer/src/ZoneRing.cs— ring manager (see DEV_NOTES architecture). src/Tracker.cs—FindNearestTrackedDungeon→CollectTrackedDungeons(collects all);NearbyDungeongainedPosition/Uid, nowinternal; HUD label logic extracted toUpdateHudLabel()unchanged.Plugin.cs—Rings/Enabled(true),Rings/ShowRadiusMeters(100) config;PluginVersion1.0.0 → 1.1.0. Same bump inSunkenCryptTimer.csproj(<Version>) andSunkenCryptTimer/manifest.json(version_number, description updated).README.md— zone rings section, config rows, layout line.- Verified: build 0 errors; deploy to Gale profile; local server smoke test clean
(
Loading [SunkenCryptTimer 1.1.0], 0 fatal). In-game visual check pending (user).
- New