Shadows Of Midgard
A stealth and perception engine for Valheim. Creatures see, hear, remember and doubt - vision cones, line of sight, noise, light, weather and camouflage feed a Stealth Brain that tracks each player separately. Server-synced, dedicated-server ready.π Shadows of Midgard
"You think the dark hides you. The dark hides nothing β it only makes them work for it. Step off the gravel. Wait for dark. Let the rain do the rest, and the draugr will walk past close enough to touch."
Creatures see, hear, remember and doubt β and each one judges every player on their own.
A creature has a vision cone with a real blind spot behind it, a real line of sight against the same geometry the game uses, and ears that hear you by what you wear and how fast you move. It remembers where it last saw you, goes and looks, gives up, calls for help, and runs when it is losing. It can be hunting your friend and never notice you, crouched two metres away, in the same moment.
It does not fight the game's AI. It answers the questions vanilla already asks β can I see this, can I hear this β and lets vanilla decide what to do about the answer. If anything ever goes wrong, the floor is always plain vanilla.
A clear night finally hides you. The light that falls on you is now measured the way the game's own stealth check measures it β its strength and its colour β so the moon is no longer as bright as the noon sun.
| When | It |
|---|---|
| A clear moonlit night | adds well under half the light of noon; clear days are unchanged |
| Rain, snow, the Mistlands after dark | darker again |
| Light kit, standing still in the open | spotted from roughly half as far away on a clear night as at noon |
| Heavy armour | still easy to see at any hour β armour adds more to being seen than daylight does |
π₯ Torches, honestly β only the sun and the moon count; a torch in your hand is one more item you carry Β· β© Made your nights stealthier by hand? Try the default shade and weather settings again first.
| If⦠| Then⦠| |
|---|---|---|
| π€ | you play on a server | install it on the server and every client. A client without it cannot connect β on purpose: a client that works out perception differently is playing a different game. |
| π | you run a server | leave Lock Configuration on. Off, any connected client can broadcast its own gameplay settings to everyone. |
| π½ | you add an armour mod later | set RebuildArmorData = true and launch once, so the new gear is scored. |
| βͺ | you are upgrading | nothing to do: the config migrates itself and backs up to .cfg.bak first, and the armour catalogue keeps every entry you marked "Protected": true. |
| π§ͺ | you suspect this mod of something | EnableStealthOverride = false makes the game byte-for-byte vanilla. It is the first thing to try. |
The dark is measured, not looked up β and so is everything you hide in.
| π Sun and moon | your visibility starts from the light actually falling on you: full daylight most, a clear night well under half, a crypt nothing |
| πͺ¨ Shade | a wall, a rock face or a roof between you and the sun or moon takes more off |
| π§οΈ Weather | rain and snow cut how far a creature can see you; fog cuts it harder |
| πΏ Bushes and tall grass | sampled from the real plants at your feet, not from the biome on the map |
| π§ Crouch | helps everywhere, and far more with something to crouch in |
| π¦ Camouflage | ghillie, forest, swamp, wolf, lox and mist-themed gear hides you where it matches the biome |
| π Movement | adds to being seen by your actual speed β walking is not sprinting |
π Read the saga β why a bush in the rain at midnight is a different world
None of this is a biome lookup or a time-of-day switch. It is the game's own sun and moon, exactly as bright as the hour and the weather make them. Torches, fires and forges are not counted.
The cover terms stack. Crouched, in grass, inside a bush is a genuinely different proposition from crouched on bare rock β and a bush is the strongest single piece of cover in the game.
Heavy gear adds to how visible you are, scaled by how heavy the item is, so plate costs far more than leather. Camouflage gear adds concealment of its own on top of matching the biome.
Plate is loud. Leather is quiet. Foul weather is the sneak-thief's friend twice over.
| π Moving | makes noise, scaled by how fast you go |
| π‘οΈ Armour | adds noise by weight β and heavy metal adds to being seen as well |
| π§ Crouching | removes most of it |
| π§οΈ Rain, snow and mist | mask a good part of what is left |
| π Hearing | all round, through walls β a creature that cannot possibly see you can still hear you blunder past in iron |
| π The armour file | every wearable item is scored on first launch by weight and material, modded armour included, into a JSON file you can edit by hand |
π Read the saga β how noise adds up, and how the armour gets its score
Noise. Movement and armour make it; crouching and weather each remove a fraction of it. They multiply, so crouching in the rain makes you quiet, never perfectly silent β unless you set crouching itself to remove everything.
Armour is a trade. Every item you can wear is classified by name into a weight and a material. Heavy metal is loud and visible, leather quieter, cloth barely registers. Gear named for ghillie, forest, swamp, wolf, lox or mist grants camouflage when it matches the biome you stand in. Monster attacks β which the game counts as equipable items β are left out: only things a player can pick up are scored.
When it gets one wrong. The classifier reads names, and names mislead. Correct the entry and mark it "Protected": true, and your answer survives every rebuild and every update (see The Toolbox).
A real cone, a real blind spot, a real line of sight.
| ποΈ Vision cone | 120Β° by default, with a blind spot behind β for a creature that has not noticed you yet |
| πͺ Line of sight | cast against the same layer mask vanilla uses: a shut door works, a rock works |
| π It keeps working | an alerted creature re-checks about twice a second, so going indoors and shutting the door really loses it |
| π«οΈ Mist | only creatures with mist vision see through it |
| βοΈ Mid-fight | once alerted to you it sees all round, as vanilla creatures do; one fighting you sees you up close whatever the light |
π Read the saga β the blind spot is for sneaking, not for fighting
Stepping past a creature's shoulder in a fight does not make you vanish: once it is alerted to you, the cone is gone. And a creature that is actually fighting you sees you within a few metres of its body whatever the light, cover or camouflage, so fights in the dark do not go quiet β darkness hides you from the hunt, not from the fight.
Walls, shut doors and mist still block it at any range, and it never sees you where a vanilla creature could not. A creature fighting one player still has its blind spot for anyone else sneaking up behind it, and one that is merely searching can still be hidden from.
The re-check is a rate, never a skip: an alerted creature always loses you when you shut a door; the setting only decides how quickly it notices.
Unaware β Suspicious β Alerted β Engaged.
| π§ Gain and decay apart | a single glimpse does not commit a mob; ducking behind one rock does not end a chase |
| π Search | it walks to where it last saw you and searches at a fraction of its own chase pace, then gives up on a timer |
| π― Calls for help | alerted creatures call nearby allies, who come without being handed a target they have no business knowing |
| π Flees when losing | a badly hurt creature turns and runs β at a fraction of its run speed, so it can still be chased down |
| π Bosses never flee | nor do Blighted World Heart's sub-bosses |
π Read the saga β what the rungs drive, and how a creature runs
The alert state is the game's own. The aggro roar, the alert animation, the give-up timer, taming, Sneak XP, the enemy icon on every client and the backstab bonus all follow it. Backstab lands only on a creature that has not been alerted β as it should.
Searching goes through vanilla's own pathfinder, so a creature does not beeline into walls. Fleeing goes through vanilla's flee behaviour: the creature picks a reachable spot away from water and lava, turns in place, and runs β never gliding backwards while staring at you.
Memory survives a handover. A creature's detection state goes with it when it passes from one player's game to another's, so it does not forget who it was chasing when you cross a zone boundary.
Walk up on a deer instead of watching the whole meadow scatter.
| π¦ Deer, hares, chickens, boar piglets, wolf cubs, lox calves | the same light, noise, cover, camouflage and line of sight as everything else |
| π Flee or not | that decision stays vanilla's β an animal has no target to be handed and no ally to call |
| π Settles at once | break cover properly and it calms on the spot, instead of bolting out vanilla's four-second timer |
π Read the saga β when an animal is allowed to calm down
Vanilla's calm-down is a timer, not a re-check, so an alarmed animal keeps fleeing for four full seconds after it has lost you. Shadows of Midgard drops the alarm the same moment β but only when it is sure: the measurement is fresh, sight and hearing are both negative, the detection ramp is at zero, and the animal was never escalated past Suspicious. An animal you hit is not an animal that has lost you, and is left alone.
This can only ever make an animal less aware than vanilla would. DriveAnimals switches the whole animal half off.
Built for a full server, not a single hero.
| π₯ A track per player | each creature keeps one per nearby player, admitted by distance β so ten players do not give every creature ten tracks |
| π Death does not launder | tracks follow your stable player identity: respawning neither wipes what a creature knows nor gets you out of a chase |
| π₯οΈ Dedicated servers | the whole engine runs headless |
| π Stealth stats | the server writes each player's sneak time and distance, times seen and heard, alerts caused, engagements and undetected kills, once a minute |
π Read the saga β the stealth stats file
The file is config/ShadowsOfMidgard/barrkbot_stealth.json, in a shape a Discord bot β or anything else that reads JSON β can use. The counters cover the current server session and reset when it restarts.
It costs nothing on the wire you were not already paying: each player's counters ride on their own replicated data, with no new messages. TelemetryExport turns the counting and the file off together; a file already written is left behind, so delete it by hand if you opt out for good.
Two widgets inside Valheim's own HUD that tell you how you are doing.
| ποΈ The eye | its colour is how aware the world is of you; its size is how visible you are right now, after cover and camouflage |
| π¨ Its colours | pale ice blue: unnoticed Β· dark red and pulsing: something has you |
| πΆ The bar | how much noise you are making |
| β Drag them | grab either while a menu has freed the cursor β inventory, chest, map, pause β and the spot is remembered |
| π±οΈ Never in the way | they never block or swallow a mouse click, in menus or in play |
π Read the saga β how they live in the HUD
They scale with Valheim's HUD, hide with it in cutscenes and when you hide the interface, and go with it on world exit. They anchor to the bottom-left corner. AllowDrag off locks them where they are; the four position settings are always editable by hand. EnableStealthUI off hides both β the simulation carries on regardless.
A frozen public surface β and anything that wants to win by priority, wins.
| π« Exempt a creature | set the ZDO key SoMStealthExempt to 1; vanilla AI runs untouched, and every patch checks it |
| π Read what it knows | SoMInterop, no reflection; the SoM_Ack key proves Shadows of Midgard actually reached that creature |
| ποΈ Per-creature directives | ZDO keys for a sense-range multiplier, force alert, ignore line of sight, or a named AI profile |
| π§© Patching alongside | Harmony id wubarrk.shadowsofmidgard; sense patches are prefix-only, run at Priority.Low and honour __runOriginal |
π Read the saga β the details for your own mod
These names, signatures and meanings do not change between releases. The full guide is MIGRATION-for-mod-authors.md, in this package.
nview.GetZDO().Set("SoMStealthExempt".GetStableHashCode(), 1); // owner-side
bool exempt = ShadowsOfMidgard.StealthExemption.IsExempt(character);
SoM_Ack lets you tell "Shadows of Midgard says unaware" apart from "Shadows of Midgard never reached this creature". AwarenessSystem.GetData(Character) keeps its original signature and never returns null. The Harmony id has never changed, so existing HarmonyBefore / HarmonyAfter attributes still resolve.
No forced priorities, no double-asserting postfixes, no reflection into vanilla. It does not patch BaseAI.IsAlerted, MonsterAI.UpdateTarget, Character.Damage or Character.OnDamaged.
First ten minutes: install on the server and every client β launch once (the armour catalogue is written, first launch only) β wait for dark β crouch β go and look at a greydwarf, and watch the eye.
| File | |
|---|---|
BepInEx/config/wubarrk.shadowsofmidgard.cfg |
every setting |
BepInEx/config/SoM_ArmorData.json |
the armour catalogue β yours to correct |
config/ShadowsOfMidgard/barrkbot_stealth.json |
the server's stealth stats |
π₯· Actually sneak past something
Roughly in order of how much each one moves the needle:
- Get off the metal. Heavy gear adds more to being seen than daylight does, and plate is loud too; leather and cloth barely register.
- Crouch. It cuts noise by 60% and adds concealment on top.
- Wait for dark, and keep out of the sun or moon. A clear night adds well under half the light of noon, and shade takes more off.
- Use the weather. Rain, snow and fog cut sight; rain and snow mask sound.
- Break line of sight, then keep it broken. A wall works even against something already hunting you β but it re-checks about twice a second, so a doorway you dart straight back across buys nothing.
- Watch the angle. Behind a creature that has not noticed you is genuinely behind it. Once it is alerted to you, it sees all round.
π½ Fix a mis-scored piece of armour
The classifier matches whole words, so a modded chestpiece called Werewolfmail will not match wolf. Open BepInEx/config/SoM_ArmorData.json, find the entry, fix Weight / Material / Camo, and add "Protected": true. That answer is now permanent: it survives a rebuild, a migration and every future update.
To re-derive everything else after installing an armour mod, set RebuildArmorData = true and launch once. It backs the file up, rebuilds, keeps every protected entry, and switches itself back off.
π₯οΈ Set up a dedicated server
Put the DLL in the server's BepInEx/plugins/ as well as every client's. Launch the server once to make its config, then edit the server's copy β every π setting below is pushed from there to all clients and locked to admins. There is no local player on a dedicated server, and nothing in the engine looks for one.
π Read the log when something is wrong
A healthy launch is two lines: [SoM/VanillaBind] β¦ vanilla capabilities bound, every gate open and [SoM/Patches] β¦ applied, none failed. If a game update moves something out from under the mod, those lines change and the full table prints by itself β you never have to switch anything on to see a failure. ShowPatchSaga = true prints the whole table on a healthy launch too.
Two failure states are designed, not crashes:
- READ-ONLY β something the perception layer depends on did not bind, so the mod installed nothing and the game runs vanilla. The table names the exact member that changed.
- DEGRADED β something failed after startup. The mod stops holding opinions about creatures and hands perception back to vanilla rather than guessing.
| Single player | The server | Every player |
|---|---|---|
| works on its own | runs the whole engine headless, and pushes its settings to every client, locked to admins | needs it installed β a client without it cannot connect |
β οΈ The settings are the server's. Everything that changes what a creature perceives is synced, so every peer agrees on it. Only the HUD and the debug switches are yours.
| Mod | |
|---|---|
| Other AI mods | It yields on priority and never double-asserts. If something conflicts, ShowPatchSaga shows exactly what it installed. |
| Armour mods | Classified automatically. Set RebuildArmorData once after installing one. |
| Creature mods | Anything built on MonsterAI or AnimalAI is picked up automatically, spawned or already in the world. Any other AI gets the perception model and nothing touches its behaviour. |
| Mists of Avalor Β· Dvergr Allies | Need no changes. Both can read Shadows of Midgard through SoMInterop. |
| Blighted World Heart | Its sub-bosses are never ordered to flee. |
All safe to leave alone. Configuration Manager in game β synced values push live, no restart β or BepInEx/config/wubarrk.shadowsofmidgard.cfg. The file migrates itself: when a setting's meaning changes, only that one is reset, the file is backed up to .cfg.bak first, and keys that no longer exist are tidied away.
π Server-synced β set on the server, the same for everyone
| Section | Setting | What it does |
|---|---|---|
| 0 - General | Lock Configuration |
leave it on β only admins change synced settings |
| 1 - Systems | EnableStealthOverride |
the master switch; off is byte-for-byte vanilla |
| 1 - Systems | EnableVisibilitySystem Β· EnableNoiseSystem Β· EnableHidingSystem Β· EnableCamoSystem |
each part of the model on its own switch |
| 1 - Systems | DriveAnimals |
the stealth model for deer, hares and the rest |
| 1 - Systems | TelemetryExport |
the stealth stats file |
| 2 - StealthBrain | VisionThreshold Β· HearingThreshold |
how visible or loud you must be to be seen or heard (0.25 Β· 0.15) |
| 2 - StealthBrain | SuspicionGain Β· AlertGain Β· DetectionDecay |
how fast detection rises when heard, rises when seen, and fades (2.5 Β· 3.0 Β· 0.5 per second) |
| 2 - StealthBrain | SearchDuration Β· GiveUpTime |
seconds searching your last spot, and before giving up entirely (4 Β· 8) |
| 2 - StealthBrain | SearchSpeedFactor Β· FleeSpeedFactor |
search and retreat pace, as a fraction of the creature's own speed (0.5 Β· 0.6) |
| 2 - StealthBrain | AlertedLosRecheckInterval |
how often an alerted creature re-checks line of sight (0.5 s) |
| 3 - Visibility | LightPenalty Β· ShadowBonus Β· GrassBonus |
sun or moon, shade, crouching in grass (0.35 Β· 0.25 Β· 0.20) |
| 3 - Visibility | MovementPenalty Β· ArmorVisibility |
moving and heavy gear (0.8 Β· 0.6) |
| 3 - Visibility | WeatherVisReduction Β· FogVisReduction |
rain or snow, and fog (0.20 Β· 0.30) |
| 4 - Noise | MovementNoise Β· ArmorNoise |
noise from moving and from heavy armour (0.7 Β· 0.6) |
| 4 - Noise | CrouchNoiseReduction Β· WeatherNoiseReduction |
fraction removed by crouching and by weather (0.6 Β· 0.4) |
| 5 - Hiding | BushBonus Β· GrassBonus Β· CrouchBonus |
inside a bush, crouched in tall grass, crouched anywhere (0.4 Β· 0.25 Β· 0.15) |
| 6 - Camo | BiomeCamo Β· ArmorCamo |
gear matching the biome, camouflaging armour (0.15 Β· 0.10) |
| 7 - Awareness | SuspiciousThreshold Β· AlertedThreshold Β· EngagedThreshold |
the ladder β keep them in ascending order (0.15 Β· 0.40 Β· 0.75) |
| 8 - Detection Ranges | MaxVisualRange Β· MaxHearingRange |
the furthest a creature can see or hear you (40 m Β· 25 m) |
| 8 - Detection Ranges | VisionConeHalfAngle |
half the cone of a creature not yet alerted to you; 60 is a 120Β° cone, 180 removes the blind spot |
| 8 - Detection Ranges | CloseQuartersRange |
how close a creature fighting you sees you whatever the light (4 m; 0 is off) |
| 8 - Detection Ranges | MaxTrackedPlayersPerCreature |
how many players one creature remembers at once β the main cost lever on a busy server (10) |
π₯οΈ Local to your game β yours alone
| Section | Settings |
|---|---|
| UI | EnableStealthUI Β· EyePosX Β· EyePosY Β· NoisePosX Β· NoisePosY Β· AllowDrag |
| 9 - Debug | EnableVisionDebug Β· EnableAIDebug Β· ShowPatchSaga Β· DebugLogInterval Β· RebuildArmorData Β· LogPerformance |
The debug switches are all off by default and loud when on. RebuildArmorData is one-shot and switches itself back off. Failures are always reported in full, whatever ShowPatchSaga says.
Needs: BepInExPack Valheim 5.4.2360 β and nothing else. No Jotunn, no JSON library, no shared framework: one DLL.
Install with a mod manager, or unzip into BepInEx/plugins β on the server and on every client.
π Also by me: TortalPortal, a complete portal overhaul, and Mists of Avalor.
Made by Wubarrk β ravenirongames.com. Bug reports and abuse welcome on Discord. License: MIT β use, modify and redistribute freely, including commercially; keep the copyright notice. Full text in LICENSE.md.
