Valheim
You are viewing a potentially older version of this package. View Latest Version
Install with App

Details

Date Uploaded
3 weeks ago
Downloads
1.5K
Size
262KB
ADDatHost Valheim hosting
30% off

Valheim WebMap

A server-side mod that publishes a live web map of your world. Share http://your_ip:port and anyone can watch — clients need no mods. Dedicated server only.

A fork of h0tw1r3/valheim-webmap rebuilt for Valheim 1.0 (Deep North).

screenshot

Features

  • Explorable map in the browser — wheel zoom, pinch on mobile.
  • Shared fog of war: only what players have actually explored.
  • Live player list and positions, auto-follow, and in-game pings.
  • Structures — placed pieces drawn in the colour of their material, so bases read as bases. Keyed off the piece's creator, so terrain and world-generated ruins never appear. The sweep behind it runs only while someone is looking at the map.
  • Forest and logging — standing trees shade the terrain and felled ground stops being shaded, so clearings show through. Stumps are counted as the record of felling.
  • Boats and carts, in explored territory only.
  • Portals, graves and every placed piece as JSON, for a front-end of your own. The bundled page does not draw them yet.
  • World render at the resolution you choose — render_size 4096 halves the metres per pixel, and rendering no longer stalls the server.
  • Server announcements on every player's screen, for restart warnings and the like.
  • Chat, deaths and joins in the message log; optional Discord notifications.

Install

  1. With BepInEx working, put the WebMap directory in Valheim dedicated server/BepInEx/plugins/WebMap.
  2. Start the server once to write a default config.
  3. Stop the server before editing that config — BepInEx rewrites it on shutdown, so edits made while it runs are discarded.
  4. Open the configured port (default 8080) and visit http://your_ip:port.

After updating, hard-reload the page (shift+reload) to clear cached layers.

Using the map

The viewer at http://your_ip:port is four pages — the live map, a portal atlas drawn on a biome chart, a planning board for drawing and sharing routes, and per-player tallies — with a legend that switches each layer and a Layers card, as on Google Maps, holding presets of the legend and the choice of ground: the render, or the flat biome atlas. The sidebar folds away with the ☰ in the bar, on phones it is a drawer, and the choice holds across pages. Its source is WebMap/web: site.css is the style system every page draws from, map-core.js the rendering core, and each page holds only its own. Our own hosted copy at xn-valheim deploys the same files with a site-config.js that names our server.

Players only appear once they set visible to other players on the in-game map (m).

Chat commands

  • !pin — a dot where you stand. !pin some text labels it.
  • !pin <type> <text> — types are dot, fire, mine, house, cave.
  • !undoPin — remove your most recent pin.
  • !deletePin <text> — remove the pin matching that text. With no text, your most recent unnamed pin.

Not case sensitive. Past the configured limit a player's oldest pin is dropped. A deployment with a sign-in (see /pins below) lets members do the same from the map: tap walked ground and Pin here, or tap a pin to relabel, retype, move or delete it.

Server announcements

POST /announce, message as the body, X-Announce-Token header. The secret lives in announce.token beside the DLL — not in the BepInEx config, which is rewritten on shutdown and would discard it. No token file means the route is closed.

Configuration

Standard BepInEx config, plus:

  • render_size — pixels across the world render, default 2048. Same area as texture_size, only sharper: 4096 halves the metres per pixel for a one-time render of about a minute and a larger download. The overlays stay at texture_size, where extra resolution buys nothing. A map.png of the wrong size is rebuilt on start.
  • show_vehicles — report boats and carts at /vehicles. They are only ever reported in explored territory; off stops them being reported at all.

HTTP endpoints

Path Returns
/map, /map.jpg the world render; the JPEG is about a seventh the size
/fog explored mask (PNG)
/chart the world as a chart: each pixel its biome's flat colour, water one blue, no relief (PNG, once per world)
/structures, /structures/stats structures overlay; counts by prefab and the last sweep's cost
/forest, /forest/stats forest overlay, tree and stump counts with density percentiles
/trails where players have walked: a count per map pixel, drawn as a faint blue band; 503 until the first sweep after someone walks (PNG)
/features the world's geography with its names: landmasses, ranges (with peaks), lakes, bays, rivers (with their course), biome regions; ?v= from rev.features
/names POST {"id","name"} names a place (empty name: back to the world's own) or, with an id of hub@x,z, a portal hub standing there; needs X-Announce-Token, credits X-User
/pins (POST) {"op":"add","x","z","type","text"}, {"op":"edit","id"} with any of x, z, type, text, or {"op":"delete","id"}: places, changes or takes up a pin, only ever on walked ground; answers {"ok","id","pins"}, 400 with {"error"}. Needs X-Announce-Token; X-User owns a new pin and is logged for every write
/at ?x=&z= in world metres: the biome and height at a walked spot and the places it lies in, with how much of each has been walked and what stands on it; 404 for unwalked ground
/pieces every placed piece as [prefab, x, z, yaw] against a table of prefab footprint and colour; a torch, fire pit or hearth carries a fifth field, 1 while it has fuel (JSON, about 60 KB for a world)
/portals portals with their tag and the portal each is linked to, as the game has connected them (JSON)
/graves tombstones still holding gear: owner, position, seconds since the death (JSON)
/vehicles boats and carts, position and type (JSON)
/stats/players per-player tallies: joins, deaths, chat, distance, portal hops, pins, standing pieces/portals/ships, graves (JSON)
/players, /pins, /messages live state (JSON)
/state all of the small JSON blocks in one document -- players, messages, pins, vehicles, portals, graves, traders, the last 500 deaths with where they happened -- plus a content revision per layer (rev.fog, rev.forest, rev.structures, rev.pieces, rev.chart, rev.trails) so a viewer fetches a layer only when its picture changed; pass the revision as ?v=
/announce POST, see above

The structure sweep walks every ZDO on the game thread, a few thousand per frame; everything after the walk runs on a pool thread. It runs only while someone is reading the map: a request to any layer or to the sweep-fed JSON arms it for two minutes, sweeps start at least a minute apart, and an idle server does none at all. /config, /players, /map, /pins and /messages do not arm it, so a monitor probing those keeps the game idle. /structures/stats reports the last sweep — ZDOs walked, game-thread milliseconds, frames, wall time, gen-2 collections — so the cost can be read rather than guessed.

Notes for developers

Chat. Valheim 1.0 addresses chat to each recipient rather than broadcasting it, so a hook on HandleRoutedRPC sees only pings. It still passes through the server, though: RPC_RoutedRPC calls RouteRPC to forward it, and that is where this mod observes chat. A shout arrives once per recipient and is de-duplicated on sender, method and payload.

Upstream instead registered a fake server-side player so clients would address the server. On 1.0 that stops anyone joining at all — the server stays healthy and registered but logs zero connection attempts — so it is gone here. Don't put it back.

The sweep yields only between sector lists. Yielding inside a List<ZDO> lets the game's removals shift the index under the walk and skip ZDOs; a whole sector between yields is the smallest safe step.

Announcements use MessageHud's ShowMessage rather than chat: Chat gates every message on a RelationsManager permission check against the sender's platform user id, which a server does not have, so chat sent from a server is dropped in silence.

The style system

site.css is the whole look: the tokens on :root (surfaces darkest to lightest, ink, accents, the map's own hues for portals, traders and graves, the halos over the world, radii, shadows, fonts, the bar and sidebar widths), the base, and the components every page shares -- .title .eyebrow .sub .note .section .foot, .card .well .glass .stat .dot, .btn .chip .field .btnrow, .stage .mapctl .legend, the nav and the sidebar shell. A page's own <style> holds only what that page alone draws, and names no colour of its own: every colour is a token, so a restyle is an edit to :root and the pages follow. The two exceptions are data, not chrome: the forest swatch's greens and the biome inks the map sets names in (NAME_STYLE in map-core.js).

Tests

dotnet test WebMap.Tests     # the mod's logic: builds the mod, then loads WebMap.dll on .NET 10
node --test                  # map-core.js's pure functions, from the repo root

The first needs libs/ set up as for a build. TESTING.md says what each layer covers, and what nothing covers yet. CI runs both on every push and pull request.

Checking the viewer against a live server

tools/sameorigin.py WebMap/web http://your_ip:port 8766 serves the viewer the way the mod does -- the pages from disk, every other path forwarded to the server -- and, with a headless Chrome started with --remote-debugging-port=9334, node tools/check.mjs http://127.0.0.1:8766 9334 [/tmp/shots] checks every page over it: no exceptions, markers and names drawn, a tapped name opening its place card, the names flyout, hubs and dial lines, a card per player, the plan loading. One line per check, a non-zero exit on any failure, and a screenshot of each page when given a directory. A viewer change is done when that is clean. tools/bench.mjs http://127.0.0.1:8766 9334 pans and zooms the map over the same Chrome and prints ms per frame: 16.7 is the screen's own rate, anything above it is lag.

Local test server

./testserver.sh runs the same image the hosts do (indifferentbroccoli/valheim-server-docker) under podman.

./testserver.sh up       # first run downloads the game, ~10 min
./testserver.sh deploy   # build, copy in, restart
./testserver.sh status   # container + endpoints
./testserver.sh logs

Join with Join by IP → 127.0.0.1:2456, password testpass123.

Two things worth knowing: steamcmd fails app_update with Missing configuration, having downloaded nothing, in roughly one run in four — it is transient, so just run it again. And on an SELinux host the bind mounts need :z or the container silently sees nothing.

Licence and credit

MIT where applicable.

Thunderstore development is made possible with ads. Please consider making an exception to your adblock.