You are viewing a potentially older version of this package. View all versions.
StandardVibeware-InstancedTimer-1.2.4 icon

InstancedTimer

Client-only HUD countdown + ground rings for dungeons and other VLR-tracked locations: 'resets in 2d 14h' near any entrance, workbench-style ring at VLR's check radius. No server changes needed.

Date uploaded 2 weeks ago
Version 1.2.4
Download link StandardVibeware-InstancedTimer-1.2.4.zip
Downloads 65
Dependency string StandardVibeware-InstancedTimer-1.2.4

This mod requires the following mods to function

denikson-BepInExPack_Valheim-5.4.2351 icon
denikson-BepInExPack_Valheim

BepInEx pack for Valheim. Preconfigured with the correct entry point for mods and preferred defaults for the community.

Preferred version: 5.4.2351

README

InstancedTimer

A client-only Valheim mod for players on servers that run Venture Location Reset (VLR).

It answers two questions at a glance:

  • "When does this dungeon reset?" — a HUD countdown ("Sunken Crypt — resets in 2d 14h") whenever you are near a dungeon entrance: sunken crypts, burial chambers, troll caves, frost caves, goblin camps, dvergr mines, Hildir's dungeons, Ashlands fortresses, and more.
  • "How close is too close?" — a workbench-style ground ring around each nearby instanced dungeon at the radius VLR's player-activity check uses for it (45 m for sunken crypts), so you can see the footprint where player-made structures or a tombstone would block the dungeon from ever resetting.

No server changes, no Harmony patches, no world-data writes: the mod only reads the VV_LastReset stamps VLR leaves on each dungeon's world object — those sync to every connected client automatically.

Requirements

Component Needed? Notes
Valheim on your client Built/tested against Valheim 1.0.16 (Unity 6 build). Older versions untested.
BepInEx on your client Tested with denikson-BepInExPack_Valheim 5.4.2351 (BepInEx 5.4.22+).
Venture Location Reset on the server The mod displays VLR's reset data; without VLR on the server there is simply nothing to show. Any recent 1.x.
VLR on your client optional If installed (it usually is on modded servers), the countdown uses the server's live reset intervals, synced through Jotunn. If not, set FallbackResetDays to match your server.
Jotunn not required This mod does not reference Jotunn or any other package.

The mod does not need to be installed on the server (installing it there is harmless but does nothing — servers have no HUD). Other players don't see your rings or your timer unless they also run the mod.

Installation

With a mod manager (r2modman / Gale / Thunderstore Mod Manager): search for "InstancedTimer" and install. Done.

Manual: copy Plugins/InstancedTimer.dll from the package into your BepInEx profile's BepInEx/plugins/ folder.

Usage

Timer

Walk near any location VLR manages (default: within 80 m of the entrance) - dungeons primarily, but on servers where VLR stamps everything (its default), runestones, graves, shipwrecks and ruins get counted down too. A single line appears in the top-right corner of the HUD, right below the status-effect row (Rested/Wet), in the same small text size:

  • Sunken Crypt - resets in 2d 14h — the dungeon still holds its loot.
  • Sunken Crypt - reset ready (regenerates on approach) — the timer has elapsed. VLR only regenerates a dungeon when a player walks within 100 m of it after the timer expires, so it will pop fresh the moment someone (maybe you) shows up.

Timers keep running while the server is empty (the world clock advances 24/7 while the server process runs), and the countdown is computed from live world time — log back in after a week and the label already reflects it.

Zone rings

Each nearby instanced dungeon (crypt, burial chamber, troll cave, ...) gets a ground ring cloned from the vanilla workbench build-circle: thin rotating dashes hugging the terrain. Its radius is the distance VLR checks for player activity inside that dungeon type — 45 m for sunken crypts, computed per type from the game's own dungeon data (hypot(zoneSize.x/2, zoneSize.z/2), matching VLR's GetDungeonRadius).

Rules of thumb the ring makes visible:

  • Build or die inside the ring's area (or within the small entrance zone around the door) and that dungeon stops resetting until the pieces/tombstones are removed.
  • A base on the ground outside the ring is safe and never blocks resets.
  • Terrain shaping (raising/leveling) never blocks a reset, but VLR reverts terrain within ~12 m of the entrance each time the dungeon resets.
  • Rings show while you're within 100 m (VLR's own trigger distance), hide while you are inside the dungeon, and are capped at the 4 nearest features. Ground locations (meadow farms, villages, fuling camps) intentionally get no ring.

Configuration (BepInEx/config/lan124.InstancedTimer.cfg)

All settings are client-side; changes apply immediately (no restart).

Section Key Default Meaning
General Enabled true Master switch
General DebugLogging false Verbose scanner logging
HUD ScanRadiusMeters 80 Show the timer within this distance of an entrance
HUD UpdateIntervalSeconds 1 Rescan rate
HUD HudOffsetY 170 Fallback label offset from the top of the screen (px) - normally the label positions itself under the status-effect row
HUD TextScale 1.25 Timer text size relative to the status-effect labels (1 = same size); applies live, no restart
HUD InstanceNaming NameAndZone How the label names the location: Type, NameAndZone ("Odin's Tomb (-70,-21)"), Name, or Zone
Timers FallbackResetDays 10 Assumed interval when VLR's config can't be read — set to your server's actual interval
Rings Enabled true Draw ground rings around nearby instanced dungeons
Rings ShowRadiusMeters 100 Show a ring while within this distance of the entrance

How it works

VLR stamps every dungeon it manages with VV_LastReset (the in-game day it last reset) on the dungeon's LocationProxy world object (ZDO). ZDOs sync to every connected client, so this mod:

  1. scans nearby LocationProxy objects for the stamp (throttled, default 1 s),
  2. resolves the effective reset interval from VLR's own live config on the client (which Jotunn keeps synced with the server's settings) or falls back to FallbackResetDays,
  3. displays lastReset + interval − now, mirroring VLR's exact day-math, and
  4. (rings) clones the vanilla CircleProjector marker used by the workbench and sizes it per dungeon type from the game's location prefab data, confirmed against the live dungeon generator.

The mod never writes world data, owns no networked objects, and patches nothing.

Building from source

Requires the .NET SDK (6+) and a local Valheim install + BepInEx profile:

./build.sh -p:GameDir=/path/to/Valheim -p:ProfileDir=/path/to/BepInExProfileRoot

GameDir is the folder containing valheim_Data/; ProfileDir is the folder containing BepInEx/core. Output: InstancedTimer/bin/Release/InstancedTimer.dll. ./package.sh additionally builds the Thunderstore zip (requires zip). For the full release/upload workflow see PUBLISHING.md.

Version compatibility

Version Status
Valheim 1.0.16 (Unity 6000.0.75f1) tested, daily use
Valheim older / newer patch levels untested; the mod only uses stable APIs plus two reflective reads (EnvMan.m_totalSeconds, Hud.m_instance) — likely fine, not guaranteed
BepInEx pack (denikson) 5.4.2351 tested
Venture Location Reset 1.1.0 / 1.1.1 tested; interval mapping mirrors VLR's GetResetTime (re-check when VLR adds location types)
Jotunn any / absent not referenced

Known limitation: the per-type reset-interval table in src/ResetTimer.cs only takes effect when VLR's per-type override mode is on; otherwise VLR's global ResetTime applies to everything (as on most servers).

License

MIT

CHANGELOG

CHANGELOG — valheimmer / InstancedTimer (SunkenCryptTimer through v1.1.8)

2026-10-13 — v1.3.2: tier name leads, fun name appends

  • Field feedback on v1.3.1: with real names replacing fun names, same-type neighbors read identically ("Sunken Crypts (-71,-21)" vs "(-70,-22)") - the fun name had been carrying the instance identity all along (field data: "Eirik's"/"Loki's" were both SunkenCrypt4; "Thor's Crypt" was a Grave1 - a different species entirely). Name modes now read "tier · instance": "Sunken Crypts · Loki's Barrow (-71,-21)". Type and Zone modes show the tier alone.
  • Coverage hardening from the same field log: prefix rules for WoodHouse* (8-13 seen beyond the mapped 1-7), StoneHouse*, and Ruin* - previously raw prefab names as tier. Every species now resolves a tier via game token -> friendly map -> prefix rule -> raw name; the once-per-prefab log line surfaces anything unmapped.
  • New [General] IgnoreLocations (default "StartTemple,Eikthyrnir,WoodHouse6" - VLR's documented hard-ignored set): locations VLR stamps but never resets now get no label and no ring (the spawn temple and boss altars were showing countdowns over nothing). Comma-separated, case-insensitive, applies live; each skip is logged once per site.

2026-10-13 — v1.3.1: field feedback from the swamp — old-world names + honest reset text

  • Field verdict on v1.3.0: every location approached in meadows/black forest/swamp reported an EMPTY discover label - the game reserves m_discoverLabel for newer content (Mistlands/Ashlands). Old-world dungeons are now mapped to the game's own localization tokens instead ($location_sunkencrypt = "Sunken Crypts", $location_forestcrypt = "Burial Chamber", $location_mountaincave = "Frost Cave", Dvergr towns/boss, morgen holes), resolved through the loaded language. Eirik's Crypt is dead; the sunken crypt reads "Sunken Crypts" in any language.
  • Field bug report: "said reset ready, it was not". VLR's own code/README explains - resets fire on approach only when unobstructed: player-built pieces/tombstones near the location ("player activity") block the regen (our camp in the swamp), and the check is one-shot per zone load. HUD text now says "regenerates on approach if unobstructed" and the README documents the blocker.
  • Gap fix: DG_ForestCrypt (generator-name fallback) missing from PrettyNames.

2026-10-13 — v1.3.0: in-game names win over generated ones

  • Locations the game itself names now display that name in every InstanceNaming mode: the Location prefab's m_discoverLabel ("$location_sunkencrypt") run through the loaded language ("Sunken Crypt") - verified against the game's own localization (location_forestcrypt = "Burial Chamber", location_mountaincave = "Frost Cave", Dvergr towns, morgen holes, ...). Generated "Odin's Tomb"-style names remain only for instances the game leaves unnamed (graves, huts, trees).
  • Resolution is logged once per prefab ("in-game name: 'SunkenCrypt4' -> 'Sunken Crypt' (label '$location_sunkencrypt')") so coverage is visible in LogOutput.log.
  • Build repairs after the 2026-10-04 Steam move: default GameDir now uses the stable ~/.steam/steam symlink (debian-installation path died), and build.sh prefers dotnet from PATH (~/.dotnet/dotnet moved to a system install). New reference: assembly_guiutils.dll (where Localization lives in this build).

2026-10-06 — v1.2.4: TextScale config knob (live, no restart)

  • Field verdict on v1.2.3: matching the labels exactly reads "small-ish". Instead of another guessed constant, the size is now user-tunable: new [HUD] TextScale (default 1.25, range 0.5-3) multiplies the adopted status-label size; changes via the F1 config manager apply on the next HUD update without a restart.

2026-10-06 — v1.2.3: timer text now matches the status labels' on-screen size

  • Field test (screenshot measured): v1.2.2 rendered 1.5x the size of the Wood/Resting/Shelter labels — the point bonus itself was the whole error. StatusTextSizeBonus removed; the label now adopts the rendered size of a live status-effect entry (its first active TMP - the same label vanilla writes the effect name into), carrying fontSize through both sides' world scale so the on-screen size matches exactly whatever scale the status hierarchy carries.

2026-10-06 — v1.2.2: timer text +2 more points

  • v1.2.1's +2 over the status-text size was still too small (user field test); StatusTextSizeBonus now +4.

2026-10-06 — v1.2.1: timer text a couple points larger

  • Field feedback on v1.2.0: the status-effect text size (adopted in v1.1.8) is too small to read comfortably. The label now adds +2 points to the status-text size (StatusTextSizeBonus in HudLabel.cs — tweak there and rebuild if it still needs tuning).

2026-10-06 — release automation: Thunderstore publishing scripted, push-on-every-test-phase

  • publish_thunderstore.sh: publishes the packaged zip via the Thunderstore API in one call (legacy submission/upload/ endpoint: multipart file + metadata → published; team StandardVibeware, community valheim, category client-side). Token at ~/.config/thunderstore/api_token (tss_ service account, Bearer auth). Hard-won HTTP facts, handled in-script: Swagger's "Basic" scheme is wrong for tss_ tokens; the URL's trailing slash is mandatory (Django append-slash redirect behind Cloudflare = 502); live endpoint returns 200 (docs say 201). Package deprecation is blocked for service accounts — browser-only step.
  • Standing procedure (user instruction): every build deployed for user testing automatically gets commit → push → GitHub release → Thunderstore publish. Full flow documented in PUBLISHING.md; also stored in Claude project memory.
  • v1.1.7 was the first Thunderstore listing (published manually via the same API); v1.1.8 published during script debugging (manual curl — the script had the trailing-slash bug); v1.2.0 was the script's first clean end-to-end publish.

2026-10-06 — v1.2.0: renamed SunkenCryptTimer → InstancedTimer

  • The mod outgrew its name: since v1.1.7 it labels every VLR-tracked location (runestones, graves, shipwrecks, ruins, huts), not just crypts. New identity everywhere: plugin name, GUID (lan124.SunkenCryptTimer → lan124.InstancedTimer), namespace, assembly + DLL name, project dir, scripts, GitHub repo (renamed; old URLs redirect), and Thunderstore package — where a name change means a new package (StandardVibeware-InstancedTimer); the old StandardVibeware-SunkenCryptTimer listing is deprecated in favor of it.
  • Consequence of the GUID change: the config file is now lan124.InstancedTimer.cfg and starts from defaults (defaults match the previous recommended settings). Remove the old plugins/SunkenCryptTimer/ folder when upgrading manually — both DLLs together would double-draw the HUD.
  • No behavior changes; v1.2.0 is v1.1.8 under the new name.

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_location string 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 private m_instance — the spawned location GameObject, whose name is SunkenCrypt4(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=10 in-game days (~5 h real; 1 day ≈ 30 min), OverrideResetTimes=false → ALL dungeon types use 10, incl. Sunken Crypts (SunkenCrypt4; the Crypt2/3/4 names 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.ResetTerrain reverts all terrain mods within the 12 m ground radius on each reset.
  • 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); NearbyDungeon gained Position/Uid, now internal; HUD label logic extracted to UpdateHudLabel() unchanged.
    • Plugin.cs — Rings/Enabled (true), Rings/ShowRadiusMeters (100) config; PluginVersion 1.0.0 → 1.1.0. Same bump in SunkenCryptTimer.csproj (<Version>) and SunkenCryptTimer/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).