Valheim
Install with App

Details

Latest version
1.3.1
Last Updated
First Uploaded
Downloads
597
Likes
3
Size
122KB
Dependants
ADDatHost Valheim hosting
€1

Changelog

Notable changes to Utangard. Format follows Keep a Changelog, and the mod uses semantic versioning.

[1.3.1] - 2026-09-12

Changed

  • Rewritten README. Same mod, clearer documentation: what it does and how to install it come first, then configuration, multiplayer behaviour, compatibility and troubleshooting. Every config table was checked against the plugin's own Config.Bind calls, so the settings, sections and defaults listed are the ones actually bound. No code changed in this release.

[1.3.0] - 2026-09-09

Rebuilt for Valheim 1.0. This version does not run on pre-1.0 Valheim, and the previous one does not run on 1.0.

Fixed

  • The mod works again. Valheim 1.0 added a trailing parameter to SEMan.AddStatusEffect, so the patch no longer named a method - and an unresolved target throws out of PatchAll, which took all twelve patches in the class with it. Utangard did nothing at all while still registering on Core's gate and still refusing mismatched clients on behalf of a mod that was not running. It was nearly invisible: the log carried one warning and no error, because the exception went to Player.log rather than BepInEx's own log. The tell was the absence of the ready. line.

[1.2.1] - 2026-08-25 (second half)

Everything below shipped in 1.2.1 alongside the latch fix above. It sat under Unreleased while the code was already in the release, which is the kind of drift that makes a changelog worth less than no changelog - the README carried it, so the package page was right and only this file was wrong.

Only the people at the frontier hold a gate shut

A character now counts towards a boss's gate only once it has the boss before it in the table.

The case that forced it: a group has cleared Eikthyr, somebody kills The Elder, and the Swamp stays shut - held by a character who has killed nothing at all. That is a person two steps behind the frontier deciding when the people at it may move, and there was nothing they or anyone else could usefully do about it except wait out the catch-up deadline. The gate exists to make fetching your friend worth doing, not to stop a group at the boss its newest member has not reached.

They still count for the boss they are actually next in line for, so the gate that holds a group together is the one nearest the person who is behind - which is the one where helping them is a single evening rather than a campaign. And every biome the group has already earned stays open to them, because the latch is a fact about the group and not about who is standing in it today.

Two decisions inside that are worth naming. It tests the member's own credit rather than whether the previous gate is open: a gate that is open is open for everyone the moment it latches, so testing that would exclude nobody and the rule would do nothing. And it is the immediate predecessor rather than the whole chain, because somebody carrying The Elder without Eikthyr is at the Swamp's frontier by any honest reading.

It lands in Counts, the one seam the gate asks through, so the verdict, what gets latched and the names in "still owed by" all follow it together rather than two of the three.

Gate.RequirePreviousBoss, default on, host-synced like every other rule.

[1.2.1] - 2026-08-25

A gate could latch open off a half-loaded world. It did, on the live server: the Swamp opened permanently while seven of the nine characters on the roster had never met the Elder.

The bug

ZoneSystem.RPC_GlobalKeys clears every global key and re-adds them one at a time, and it runs on every client every time anybody sets any key, because SetGlobalKey ends in SendGlobalKeys(Everybody). For the length of that loop the dictionary this mod reads its roster and its credits out of is incomplete.

Vanilla never notices - the refill is synchronous, no frame boundary falls inside it. A Harmony postfix on GlobalKeyAdd does notice, and Yoke has one, hooked there deliberately so it catches the bulk list a server sends on connect. So every key in that list made Yoke ask this mod whether the group had cleared a boss, once per key, while the answer was built from whatever fraction had arrived.

With a partial roster the counted members can be exactly the ones who hold the key - the two who had just killed the Elder, whose credits were already in - and LatchIfGroupCleared then finds a group that has cleared it. The !anyCounted guard only ever caught a completely empty roster; a partial one walked straight through it. The open key is permanent by design, RPC_SetGlobalKey has no permission check, and so one client's half-loaded view became everyone's, for good.

The two-second roster cache is what let one frame of that outlive itself.

Fixed

  • The latch refuses to run while the world's keys are settling. A prefix and postfix on RPC_GlobalKeys hold a flag across the rebuild; while it is up, nothing latches. This is the irreversible half of the mod, so it is the half that must decline to answer early rather than answer wrongly.
  • The roster is never cached from a half-filled key list, and is invalidated on every key that arrives rather than only on a publish. A cache can no longer outlive the world state it was built from.

Nothing here changes a rule, a number or a saved value. A gate already latched open stays open - that is what "never regresses" means, and unpicking it after the fact would be a worse promise than the one that was broken.

[1.2.0] - 2026-08-19

The border is a band, and wounds do not close

Two rules, both configurable, both on by default.

Gate.BorderMargin, 5 metres. The gate now reaches five metres past the edge of a gated biome. On a line, every penalty in the mod is escapable by taking three steps out of the Swamp, eating, and stepping back in - the drain, the refusal and the grudge all end at a boundary you can see and stand behind. That makes it a rule about where you may chew rather than where you may live, and it is worst exactly where it matters most, at the edge of a fight you are already in. A band has to be genuinely cleared. Set it to 0 to put the gate back on the border.

It samples eight compass points at the margin, so it costs eight biome lookups. Those are cached against the player's position and re-taken every quarter of a metre walked; what is cached is which biomes are within reach and never the verdict on them, so a biome that opens while somebody stands at its border opens for them where they stand.

Food.HealthRegenMultiplier, 0. Health regeneration in a gated biome, as a fraction of normal. It sits in the Food section because food is the only passive healing Valheim has - Player.UpdateFood adds up every meal's m_foodRegen every ten seconds and heals you by it - so this multiplies exactly the healing the food you are not allowed to eat would have given. The land that will not feed you does not mend you either.

It rides StatusEffect.ModifyHealthRegen on the marker effect rather than a patch, because that is the seam vanilla already offers and it composes with every other multiplier instead of overriding them. Which meant the marker had to stop being skipped when ShowStatusEffects was off: it was pure signage then and is carrying a rule now, and turning off the HUD would otherwise have quietly turned off the healing block.

Both are host-synced with Core, like every other setting that decides a rule.

Also: the deadline in the entry message is now read from the biome that is actually withering you rather than the one underfoot. With a margin those part company, and a countdown for the wrong boss is worse than no countdown.

You can see the gate, and you are told when it opens

A Utangard page in the compendium, beside Logs and Active Effects: every biome, whether it is open, who still owes it, and how long until the deadline opens it anyway. Until now that report existed only as log lines on spawn, which is the wrong medium for the person who most needs it - somebody mid-raid wondering why their food vanished is not going to read LogOutput.log.

It is a postfix on TextsDialog.UpdateTextsList, so it is vanilla's list with vanilla's skin, font, scrolling, gamepad handling and close behaviour, none of which this mod then owns. The alternative was an IMGUI window: four patches (both TakeInput overloads, PlayerController.InInventoryEtc, GameCamera.UpdateMouseCapture) and a keybind, to arrive at something that looks like a different game.

The log and the page are one function now. They were about to be two copies of "is this biome open, and if not who owes it", and the interesting part is not the wording but the three-way distinction between open-because-latched, open-because-everyone-has-it and shut-with-an-empty- roster. Two copies of that stay right for about a week.

Presentation.AnnounceOpenings. A message when a biome opens, wherever you are. The mod's whole argument is that fetching the friend who is behind is worth doing, and the payoff for doing it used to land silently - you found out by walking to the Mountain and not being refused. It watches the answer rather than the kill, so a catch-up deadline expiring and a roster member ageing out announce themselves too, and it needs no network code at all: global keys are already broadcast to every client.

The gate keys are checked against the game, not assumed

defeated_queen and defeated_fader are set from prefab data rather than named in the GlobalKeys enum, so they were the two shipped defaults that could not be verified from the game's code - and a wrong key fails closed, which looks exactly like a working gate.

Character.m_defeatSetGlobalKey is a public string on every creature prefab and OnDeath hands it straight to SetGlobalKey, so walking ZNetScene's prefab list gives the complete list of keys any death in this world can set, another mod's creatures included. On spawn Utangard now warns about any gate row naming a key nothing here sets, and prints the ones that exist - which is the answer to the question the warning provokes. Diagnostics.LogDefeatKeys prints the whole map.

It checks and never corrects. A row pointed at another mod's key, or at a key a location sets, is a supported thing to want.

It has now been run, and both names are right. The scan on a live world reported defeated_eikthyr, defeated_gdking, defeated_bonemass, defeated_dragon, defeated_goblinking, defeated_queen, defeated_fader, and also defeated_hive and defeated_serpent for the two creatures that set a key without gating anything here.

Played

All of it, on a live world: the border margin refusing a player standing three metres outside a gated biome, the healing block, the compendium page, the announcement firing on the transition, and the healing block again with ShowStatusEffects = false - where the gate still refused food and held healing at zero with both icons hidden, and regeneration returned on leaving. A presentation toggle does not switch off a rule.

[1.1.0] - 2026-08-17

An API for other mods to ask what the group has earned

UtangardApi.GroupHasKey(bossKey) answers the one question this mod knows and nothing else does: whether the group has earned a boss, rather than whether the world has merely seen it die. Those two answers part company the moment somebody is offline for a kill.

It exists because Hoard scales stack sizes by world progression. Reading the raw defeated_ key there would hand out Plains-era stacks for a biome still fenced off here, which is two mods disagreeing out loud about the same word in a way that reads as a bug in whichever one the player happens to be looking at.

A facade rather than making Progression public: the roster, the latch and the deadline are nobody else's business. Read-only by construction, so a consumer cannot open a biome by asking about it.

The README is half the length

The source-code archaeology moved to DESIGN.md - why Character.OnDeath credits one player rather than all of them, what the global keys are called and why, and the handful of things that were nearly bugs. None of it is needed to play, and it was sitting between a new reader and the part that says what the mod does.

Nothing about the gameplay changed in this release.

[1.0.0] - 2026-08-16

First release. Played, not merely built.

Core is optional

Utangard installs and runs on its own. Core is a soft dependency: present, it is used exactly as before; absent, the mod is fully functional without it.

Nothing about the gameplay needed Core. The drain, the refusal and Sapped are local patches, and the group gate travels over vanilla global keys, which every client replicates already. Singleplayer is unaffected in every respect.

What Core buys is enforcement, and that is the whole of what standalone gives up. Core is what refuses a client that does not have Utangard; without it, a player who skips the mod is not gated at all and walks into the Ashlands on day one while everyone else waits on the roster. The gate becomes an agreement between players rather than a rule of the server.

That is a real trade and it belongs to whoever runs the server, so the mod logs it rather than refusing to run, and it says so loudly, once, on spawn, when it finds the group gate enabled in a multiplayer session with no Core. That combination is the one that looks like it is working and is not, and failing silently there is the worst of the options.

Mechanically: [BepInDependency] is SoftDependency, every Suite call sits behind a Chainloader.PluginInfos check inside a [MethodImpl(MethodImplOptions.NoInlining)] method, and the project reference to Core is compile-time only. The no-inlining is load-bearing rather than decorative. The JIT resolves the assemblies a method needs when it first compiles that method, so a Suite call sitting directly in Awake would drag Ezomic.Core in before the check could prevent it, and the missing-assembly exception would land during plugin load.

Core is not listed in manifest.json, so installing Utangard does not install Core with it. Confirmed in game: Utangard loads alone, logs that it is running standalone, and the whole gate works without Core present.

The group gate, finished

0.2.0 shipped the idea; this is the version where it holds up.

  • Credit is earned at the kill, by everyone present. The owning client credits every player within CreditRadius (100 m) of the corpse. It had to be done that way: Character.OnDeath looks like it runs on every client that had the boss loaded, since it pushes vanilla's key above an IsOwner early-return. But that guard is unreachable, because CheckDeath is its only caller and sits inside if (zDO.IsOwner()). Crediting "the local player" would have credited exactly one member of a group that killed a boss together, and the gate would then have jammed shut while looking like it worked.
  • Credit is per world. A character that cleared a solo world no longer arrives pre-credited. BackfillFromCharacter still allows the migration case, and only for a boss this world has already seen die.
  • Progress never regresses. Once the group clears a boss the biome latches open, so a newcomer gates only what has not been cleared rather than revoking what has.
  • A catch-up deadline, defaulting to a ladder of one day for Eikthyr and one more per boss after. Without it a single person who stops logging in holds a biome shut for everyone until RosterDays finally drops them. A biome the deadline opens latches too.
  • Per-boss roster windows via RosterDaysPerBoss, for when one boss deserves a shorter leash than another.
  • The blocker line names other people, never you, and shows how long is left.

Fixed

  • A refused meal or potion is no longer destroyed. Player.ConsumeItem removes the item regardless of what EatFood returns, so the refusal had to move to CanConsumeItem, the gate that path actually respects.
  • Refusing a guardian power no longer burns its cooldown; StartGuardianPower sets the cooldown before applying the effect.
  • Rested can no longer be topped up past the drain. SEMan refreshes a running effect through Internal_AddStatusEffect without ever reaching the public overload.
  • Puke is no longer treated as a buff. An item applies it on consume, so the potion rule swept up a debuff, which would have made a gated biome the one place bad food cannot hurt you.

Played, not merely built

On a local world and on a real dedicated server: refused meals and potions keep their items, both HUD icons render, food and buff timers burn at 5×, Sapped accumulates and follows you out and cripples stamina regeneration, a guardian power is refused without burning its cooldown, gates open and close at borders, credit is granted at the kill and survives a reload, the latch fires, a two-character roster names both debtors, and the catch-up deadline opens a biome for a group that had not all earned it. No exceptions in a long session.

Known limits

  • Attendee credit has never run with more than one player. Solo, you own the boss and credit yourself either way, and two characters taken in turns only credits whoever is logged in. The loop is the same for one player or five; what is unproven is whether other players' objects are instantiated on the owner's client at fight range.
  • defeated_queen and defeated_fader are taken from prefab data rather than the game's GlobalKeys enum. A wrong key fails closed, which is indistinguishable from a working gate. LogGlobalKeys prints what your world actually has.

[0.2.0] - 2026-08-15

Written and building. Never run in game.

The line this sits on

A biome you have not earned will not feed you. It never stops you walking in.

Valheim gates its biomes with damage, which is a soft gate: out-geared, out-run or out-healed, which is why the Plains stops being frightening ten minutes after it starts. The usual mod answer is a hard boss gate that refuses to let you across the border, which fixes the pacing by deleting the thing worth having: the walk into somewhere you should not be.

Utangard sits between them. You can go anywhere, immediately, and nothing stops you at the edge. The land just will not sustain you while you are there.

The three parts

Three parts rather than one number, because they do different jobs:

  • The drain. Food and buffs burn down five times faster in an unearned biome. This sets the clock, and it is the part you feel while things are going well.
  • The refusal. You cannot eat or drink anything at all while you are there. Without it the drain is simply beaten by a bigger pack, and the mod becomes an inventory tax rather than a time limit.
  • Sapped. Seventy-five percent less stamina regeneration, one second per second spent in the biome up to thirty, and it keeps ticking after you leave. Without it the optimal play is to sprint in, grab and sprint out at no cost, and a penalty you can dodge by being quick is a penalty for slow players only.

The gate is on the group, not the world

By default a biome opens when every member of the roster has personally killed the boss, not when the boss has died in this world. Kill Moder yourself and the Plains stays shut until the friend who was offline that night has killed it too.

This rides on Character.OnDeath pushing m_defeatSetGlobalKey into Player.m_addUniqueKeyQueue, which is how the game records a boss kill against a character rather than a world.

Known limits

  • Never played. None of the three parts has been felt in a session, and the numbers are therefore first guesses rather than tuned values.
  • The per-character kill record is only refreshed when a player loads in, so a boss killed during the current session is not visible until then unless the kill itself is hooked.
DatHost — Valheim server hosting, first month for one euro
DatHost — Valheim server hosting, first month for one euro
DatHost — Valheim server hosting, first month for one euro
DatHost — Valheim server hosting, first month for one euro
DatHost — Valheim server hosting, first month for one euro