Ezomic-Utangard icon

Utangard

Biomes nobody in your group has earned starve you: food burns away, buffs are refused, and the land leaves you sapped.

CHANGELOG

Changelog

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

[1.4.0] - 2026-09-30

Added

  • You can earn eating and healing back in a locked biome. Pidgey said it on Longhouse: a locked biome was a wall, not a challenge, and the inability to eat was the problem. Each character now has two bars in every locked biome. Fighting fills from kills of that biome's creatures, and at 50% you can eat there again. Discovery fills from the map you uncover there yourself, and once both bars are full your wounds heal at the normal rate. Meads, powers and Rested are still refused, food and buffs still burn faster and you still leave Sapped. The bars are yours, they only count in their own biome, and they never open it for the group.
  • Fighting only counts kills Utangard saw happen. It keeps its own count in your character, one for each world, so a kill in one world never counts in another, and a singleplayer world is another world too. Everybody starts at zero with this version, so kills from before it do not count. Helping with a kill counts. A tame or bred animal counts for nothing. So does a creature spawned with devcommands or wiped out with killall, and your own kill while you have devcommands on or are in god mode or ghost mode. A friend who helped somebody in god mode can still be credited when the god-mode player's machine did not have the creature. Each kind of creature is worth 1 to 5 points and the bar is full at 150. No single kind can put in more than half of it.
  • Discovery only counts fog you lifted yourself, not what a map table shares. It is full at 1 km² in every biome, and a piece of map counts once. The fog lifts in a wide circle around you, so walking a border or sailing a coast fills some of the biome on the other side as well, and nothing stops that filling the whole bar. At 1 km² it takes about ten kilometres of coastline to do it.
  • Where two locked biomes meet, an unlock needs both bars. Within 5 m of the second biome its rules reach you, so stepping a metre into a biome you have fought enough in does not let you eat on the edge of one you have not.
  • A new Foothold config section: FootholdEnabled, FightingFullPoints, EatAtFightingPercent, DiscoveryFullKm2, MaxFromOneKindPercent, and a Points_ line per biome saying what each creature is worth there. Eating and the limit per kind are percents of the full bar, so raising the bar keeps them at half. All of it is synced from the host.
  • Utangard now writes to your character file, which it never did before: one entry per world holding its foothold kill counts.
  • A refused meal now says how far your Fighting bar has got. The wording is Presentation.EatProgressLine.
  • utangard foothold in the console shows both bars for every biome, how many of each creature Utangard has counted for you, what each kind put in, where the cap stopped it, and what is unlocked. utangard creatures checks every name on the points lines against the game.
  • utangardtest kills <creature> <count> sets your count of one creature in this world, so a test can start a Fighting bar anywhere. It is a cheat command.
  • utangard deaths <creature> says whether a creature dies through its animation, how many your machine has seen die this session and whether it had them, how often it ran the boss credit for one, and whose machine has the nearest live one. It was written for a two-player test of who gets the credit for a kill, and it is not a cheat. utangard deaths on its own lists every creature that dies through its animation, and since <notowner> after a creature adds whether your machine ran a death of one it did not own since that count.
  • A boss will not come to an altar in a biome the group has not earned. Before, one player could carry an egg into the locked Mountains, kill Moder there, and start the Plains deadline for everybody while the rest were still on Bonemass. A refused offering uses nothing up. Each altar stands in the biome the boss before it opens, so it answers as soon as that biome opens, by kills or by the deadline. BlockBossSummons turns it off.
  • The Queen's door stays sealed the same way while the Mistlands are locked, since she is not summoned at an altar. The Sealbreaker is not used up when the door refuses, and a door that is already open stays open. BossDoorKeys lists which keys count.
  • utangard biomes, a console command listing each biome's creatures, how many of each Utangard counted for you in this world, and how much of it you have explored. Only creatures a points line pays for are counted, so deer and the rest of the prey always show 0.

Changed

  • The Utangard page in the compendium is now a panel. A row across the top has every biome, green when your group has earned it and red when it has not. It opens on the first locked one, and you can click any other, or use left and right on the d-pad. Under the row it says who that biome is waiting on and when it opens anyway. For a locked biome you also see your Fighting and Discovery bars, what each one unlocks, and the rules of the lock as your server has them set, so on Longhouse it says food burns 3x faster and wounds heal at a fifth. Presentation.ShowCompendiumPanel turns it back into the old text page, and the text page is also what you get if the panel fails to draw.
  • The tooltip on the in-biome icon lists only the rules that apply to you right now, so it says when you may eat or heal there.

Fixed

  • The blocked messages and everything under Presentation and Diagnostics are yours on a server now, as the README always said. They were not. Core hands every client the host's whole config apart from keybinds, so the host's wording and its Verbose flag were applied on every client and put back if you changed them. The two boss messages under Gate count as wording too. This needs Core 1.1.0 or later.
  • A boss kill is credited by one machine, the one that had the boss. In Valheim 1.0 a creature with a death animation dies on every machine showing it, and in 1.3.1 each of them could write the group's credit. Nobody got it twice, because a credit already written is not written again.

[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.