PoeTweaks
Server-synced XP, recovery, boss health, night spawn controls, death audio, cartography atlas, inventory audit and Sanctuary Stone for the poe playthrough.PoeTweaks
Custom tweaks for the poe Valheim playthrough: separate combat/noncombat XP rates, combat boosts when trained skill progress is below the enemy's native biome tier, plus peak-based skill retention and recovery. Combat and noncombat rates start at 2×. Dummy XP uses a further 0.2×; each enemy star adds 1× to the star multiplier (zero/one/two stars use 1×/2×/3×). There are no flat biome multipliers.
Install the same mod version on the server and every client. Requires BepInExPack_Valheim 5.4.2351, Jotunn 2.30.2 and ZenMap 1.11.0; mod managers install these dependencies automatically. Jotunn enforces PoeTweaks' mod/version requirements and synchronizes all gameplay settings. Clients do not need to edit or export those settings in r2modman. This mod replaces FortifySkillsReborn and SkillCatchUp for this playthrough; remove those mods from the profile to avoid applying both implementations.
The generated server configuration is BepInEx/config/valde.poe.tweaks.cfg. In this repository, maintain server settings through overrides/bepinex/valde.poe.tweaks.cfg; the importer copies overrides onto the generated mod pack. Run the next manual ./setup.py import to apply those overrides and restart the server. Building this package does not install or publish it.
Experience settings
[Experience] contains five server-synced settings:
| Setting | Default | Meaning |
|---|---|---|
Enabled |
true | Enable category rates and biome/dummy/star adjustments; recovery remains separate |
CombatXpMultiplier |
2 | Base XP rate for combat weapons and blocking |
NonCombatXpMultiplier |
2 | Base XP rate for every other skill |
DummyMultiplier |
0.2 | Further combat XP rate for dummies, without biome/star bonuses |
BonusPerStar |
1 | Add 1× to the star multiplier per enemy star |
The repository sets native global skill gain to 100%. Category rates apply
once to each native XP award, including new skills with no remembered peak
and skill XP awarded by summons to their owner. Combat/noncombat use the same
classification as retention below. Noncombat receives no target/biome bonus.
Defaults give ordinary noncombat XP 2× and dummy combat XP 2 × 0.2 = 0.4×
before recovery and vanilla buffs. Disabling Enabled returns category and
target adjustments to 1×, while retaining the separate recovery feature.
Land biomes use eight ascending tiers. Their expected skill levels are evenly spaced from 12.5 to 100:
| Native biome | Tier | Expected skill |
|---|---|---|
| Meadows | 1 | 12.5 |
| BlackForest | 2 | 25 |
| Swamp | 3 | 37.5 |
| Mountain | 4 | 50 |
| Plains | 5 | 62.5 |
| Mistlands | 6 | 75 |
| AshLands | 7 | 87.5 |
| DeepNorth | 8 | 100 |
For a classified land enemy, combat XP uses:
expectedBiome = 8 × trainedSkillProgress / 100
biomeBonus = max(1, 1 + nativeBiomeTier - expectedBiome)
targetMultiplier = biomeBonus × (1 + stars × BonusPerStar)
Trained skill progress includes the XP bar and excludes equipment/status skill
bonuses. For Plains enemies, skill 0 gets 6×, skill 50 gets 2×, and skill 62.5+
gets 1× before other modifiers. Being half a biome behind adds 0.5×.
Classification uses the enemy prefab, so a swamp enemy retains its swamp tier
outside the swamp. Unknown or modded enemies, players and tamed targets receive
no biome/star bonus. Ocean enemies receive no biome bonus but retain the
independent star setting. Training dummies and archery targets use
DummyMultiplier without a biome or star bonus. The former [Experience biomes]
flat-rate settings have been removed.
Combat weapons (melee, ranged, and magic), summoned combat skill XP, and blocking use these biome/dummy rates in addition to their category multiplier. Noncombat skills receive their category multiplier and the recovery bonus described below. Multi-target attacks use the highest combined biome/star/dummy target multiplier, calculated using the actual awarded skill's trained progress, while retaining the game's XP event count. Area-spell XP is deferred until the hit batch completes; a failed batch does not award its deferred XP. Trigger-based area hits retain their immediate XP event.
Skill retention and recovery
[Skill recovery] has five server-synced settings:
| Setting | Default | Meaning |
|---|---|---|
CombatRetentionPercent |
35 | Percentage of each combat skill's personal peak restored on death |
NonCombatRetentionPercent |
35 | Percentage restored for every other skill |
StartMultiplier |
10 | Recovery XP multiplier at or below that skill's retained floor |
KneeMultiplier |
3 | Recovery XP multiplier at the boundary below |
KneePercent |
90 | Boundary as a percentage of the personal peak |
Combat means Swords, Knives, Clubs, Polearms, Spears, Blocking, Axes, Bows, ElementalMagic, BloodMagic, Unarmed and Crossbows. Every other skill, including WoodCutting and modded skills outside that list, uses noncombat retention. The recovery curve applies to all skills, with the same three curve settings and the appropriate category's retained floor.
For the default settings, recovery scales linearly from 10× at 35% of peak to 3× at 90% of peak, then linearly to 1× at the peak. At or above the peak, XP uses 1× recovery. Below the floor, recovery stays at 10×. For a peak of 60, the three anchors are level 21 at 10×, level 54 at 3× and level 60 at 1×. If retention reaches/exceeds the knee, or the knee is set to 100%, the middle anchor collapses: recovery tapers directly from StartMultiplier at the floor to 1× at the peak. Retention of 100% restores the peak on death.
Each skill's peak is its highest trained level including partial XP progress,
without equipment or status-effect bonuses. New records start from the
character's current skills; historical levels lost before installing this mod
cannot be reconstructed. Peaks never decrease, including after repeated deaths.
The mod stores them in the character's native custom data under
valde.poe.tweaks.skill-peaks.v1, so they survive save/load and server restarts.
Back up character saves as well as the server world. Missing modded skills keep
their saved peak and are restored only when their skill definition is available.
Every local player death resets known skills to the configured percentage of their peak and clears their XP accumulators. This includes deaths during the native no-skill-drain period. Native item deletion and world flags stay intact, including Hardcore's item rules. There is no separately earned fortified level.
Recovery multiplies the category and biome/dummy/star XP rates. Native global XP and other existing XP modifiers still apply. Ordinary native XP grants at most one level per award and discards overflow; large stacked multipliers can waste XP on individual awards. The shield-depletion awards below preserve overflow.
Blood magic shields
Hostile creature damage to a blood-magic shield awards XP to the player who originally cast it. Each 100% of maximum shield HP depleted grants 40 base XP; losing 25% grants 10 base XP. Partial depletion counts immediately. The breaking hit only earns XP for the shield HP remaining, even if the hit deals more damage. Environmental damage, player attacks and tamed creatures grant no shield XP. Shields protecting nonplayers, including summons and tamed creatures, use a further 0.2× XP multiplier: 100% depletion grants 8 base XP instead of a player shield's 40, before the other modifiers. The vanilla shield-break XP award is replaced; summon attack XP stays unchanged.
Awards use the attacking creature's native biome and stars, the original caster's trained Blood Magic progress and recovery, and the existing combat, global and native/status XP modifiers. Large shield awards preserve XP across level-ups; ordinary XP events keep their native overflow behavior. Depletion across all shields belonging to the caster contributes XP, including depletion after top-ups. When one award crosses levels, biome and recovery bonuses are recalculated at each level boundary, so an old recovery boost does not carry above the peak.
Recasting restores 10% of the existing maximum shield HP at skill 0, rising linearly to 30% at skill 100 using the recasting player's native Blood Magic skill level. Skill 50 restores 20%. Recasting preserves existing maximum capacity, caps remaining HP at full, refreshes duration and grants no XP itself. Another player can replenish a shield, but its original caster retains all subsequent XP. The shield buff icon displays remaining HP percentage in place of duration text.
[Blood magic shield] contains four server-synced settings:
| Setting | Default | Meaning |
|---|---|---|
XpPerFullShield |
40 | Base XP per 100% shield HP depleted by hostile creatures |
NonPlayerXpMultiplier |
0.2 | Further shield XP rate when the protected character is any nonplayer; player shields use 1× |
ReplenishPercentAtZero |
10 | Percentage points restored by recasting at skill 0 |
ReplenishPercentAtHundred |
30 | Percentage points restored by recasting at skill 100 |
The mod retains vanilla shield lifetime and simulation ownership behavior. Summon shields can disappear when their simulation owner changes; no extra shield state is saved or replicated. XP goes to the original caster's connected, living character; disconnected/dead casters receive no deferred XP. Shield expiry or removal grants no extra award.
Death audio
The bundled seven-second Ahhhhhh Sound Effect by FreeSoundSamples
plays once when the local player dies. No runtime download is required.
Two settings under [Death audio] synchronize from the server:
| Setting | Default | Meaning |
|---|---|---|
Audience |
NearbyPlayers |
DyingPlayerOnly plays only for the dying player; NearbyPlayers also plays for clients within the radius |
Radius |
100 |
Nearby listener radius in metres; the dying player always hears the clip |
Playback uses each client's SFX/master volume and has no distance attenuation. Dedicated servers do not load or play the clip.
Personal cartography atlas
In a nomap world, reading a cartography table copies its currently charted
region into your personal atlas and opens the atlas. Press the map action
(M by default, or its controller/rebound equivalent) to open or close it.
The atlas uses the native map's pan and zoom controls. It shows terrain only:
no pins, player arrow, ship marker or player-centred view.
Reading a mature table centres the view on that copied region; reopening with
the map action remembers the last viewed position during that world session.
Copied regions are snapshots of ZenMap's table position and current reveal radius. Overlapping copies form one visible area. Revisit a table to copy its expanded region; ageing elsewhere and ordinary travel reveal nothing in the atlas. Each character must read each table independently. Copies are stored in the character's custom data, separately for each world, and survive death and save/load. Back up character saves along with the server world.
The server-synced [Atlas] setting MinimumTableAgeDays defaults to 1.
A newly built table must age for one full in-game day before it can reveal
or copy a region. During that warmup, table interactions do not centre the
atlas on the table. Set the value to 0 to disable the warmup. Afterward,
ZenMap's existing age/radius curve applies unchanged.
This integration targets ZenMap 1.11.0's internal map hooks. Keep that version pinned until compatibility with another release is verified.
Inventory audit
PoeTweaks compares the inventory loaded at join with the server's last record for that world, platform account and character. Differences are written to the server log, including every item gained or lost and its quality level. Stack splits and slot moves are ignored. Identity uses the item prefab; durability, equipment state and custom item metadata are not compared.
Example server warning:
Inventory changed while away: character "Player" (platform "7656...", character ID 123); previous snapshot clean logout at ...; +40 "Iron" (quality 1), -1 "Bow" (quality 2), +1 "Bow" (quality 3)
The first observed join establishes a baseline. Later joins compare before normal gameplay, without treating deaths or respawns as new sessions. No inventory changes, kicks or join refusals are performed by this feature.
On normal logout or application quit, the mod sends the inventory from the
successful character save before native teardown. It briefly pumps the Steam
connection until the server acknowledges durable storage, or the timeout
expires. A crash, forced kill, network loss, server kick, failed character save
or failed acknowledgement can leave only a periodic checkpoint. Differences
against those checkpoints are explicitly marked potentially stale.
Character rollbacks can also create differences. Unchanged inventory does not
prove the character never visited another world; reports are client supplied.
All three [Inventory audit] settings synchronize from the server:
| Setting | Default | Meaning |
|---|---|---|
Enabled |
true | Enable inventory audit reports |
SnapshotIntervalSeconds |
5 | Periodic checkpoint interval, minimum 5 seconds |
LogoutTimeoutSeconds |
2 | Maximum wait for final server acknowledgement, 0–10 seconds |
Server records live under <savedir>/PoeTweaks/inventory-audit/, outside
BepInEx and the imported pack. With this repository's Compose setup, that is
config/PoeTweaks/inventory-audit/ on the host. Back up this directory alongside
world saves; pack imports and docker compose down preserve it. Each record is
an atomically replaced JSON snapshot; filenames hash world/account/character
identity. Unreadable records are logged and preserved rather than silently
overwritten. This final-snapshot path targets the playthrough's Steam server;
crossplay transports have not been validated.
Sanctuary Stone
The hammer's Misc category contains a separate Sanctuary Stone. Build it near a workbench with 50 Stone, 20 Fine Wood, 10 Surtling Cores, 10 Greydwarf Eyes and 100 Iron Nails. It uses the included two-metre rune-stone model; vanilla wards keep their existing behavior.
New stones start with zero protection and grow linearly to a 50-metre target
radius over one in-game day. Use the stone (E by default, including the
controller/rebound Use action) to enter a new target radius. Any nearby player
may adjust it. The target is capped by the server, initially 200 metres.
Hover text shows the current and target radii. Zero turns protection off.
Enlarging starts a new one-day expansion from the current radius. Reductions clamp the current radius immediately; reducing a target that is still above the current radius never grants an instant expansion. Submitting the same target preserves the original growth clock. Radius and growth epoch live in the piece's ZDO, so relogs, ownership changes and saved-world restarts preserve progress. Rebuilding creates a fresh stone and starts from zero. Growth uses world time, including the game's normal time skips.
The expanding visual bubble reuses the vendor's effect. Protection uses the same native monster-flee behavior, including its bounding-box approximation and avoidance margin. It does not create a physical barrier, block projectiles, or make occupants invulnerable. Tamed creatures retain the native exemption. Only loaded stones can affect local monster simulation; stones use the game's distant-object loading mode for the default large radius.
Since 0.4.1, the current spherical radius also provides native workbench-style spawn suppression. Ordinary creature spawns that respect player bases are blocked, including animals; nests, boss summons and other spawners that bypass base checks can still spawn. This also protects dropped items from normal despawning under the game's base rules and counts as one player-base area. Existing stones gain this behavior at their saved radius. Zero radius, disabling or removing a stone clears both protections.
Two [Sanctuary Stone] settings synchronize from the server:
| Setting | Default | Meaning |
|---|---|---|
InitialRadius |
50 | Target radius for new stones; existing targets are retained |
MaximumRadius |
200 | Cap on selectable and effective radius, in metres |
Both settings accept 0–10,000 metres. The default 200-metre cap is the intended playthrough setting; raising a cap does not extend the game's object-loading distance. Lowering the cap clamps existing protection while preserving saved targets. Raising it again can restore that previously grown coverage.
The original model is Low Poly Fantasy Rune Stone by Danciu
Cristian-Alexa,
licensed CC BY 4.0. The package includes attribution and adaptation details in
sanctuary/CREDITS.txt; the unchanged source GLB is retained in
assets/runestone/. The runtime OBJ, packed textures and build-menu icon can
be regenerated from the repository root:
nix develop path:. --command blender --background --factory-startup --python tools/prepare_sanctuary_assets.py
Asset regeneration is separate from an ordinary .NET build. The generated
files are retained under package/sanctuary/, so building the DLL/ZIP does not
require launching Blender or Unity Editor.
Boss health
[Bosses] BossHealthMultiplier defaults to 2. Since 0.4.4 it applies only
to newly spawned bosses from Mistlands onward: the Queen, Fader and the
final biome's boss phases. Eikthyr through Yagluth use normal health. The server
synchronizes this setting to clients; no client config changes are needed.
Native star, world-level and multiplayer scaling still apply.
Each eligible boss saves its multiplier in its world ZDO. Full-health and wounded bosses retain that multiplier and their health across reloads and ownership changes; reloading does not apply the multiplier again. Eligible bosses already present before the original HP feature keep normal health. Later config edits affect future eligible spawns, including when returning the setting to 1 for normal health. The setting accepts multipliers from 0.01 to 100.
Previously boosted earlier bosses lose their saved multiplier when loaded by their simulation owner or after ownership transfers. Current and maximum HP are rescaled together, preserving remaining health percentage: 400/1000 becomes 200/500 when removing a 2x multiplier. Repeated reads and reloads do not rescale again. No boss-progress keys are changed.
Boss-unlocked night invaders
[Spawns] DisableBossNightInvaders = true stops the listed boss-unlocked night
waves from spawning outside their home biomes. This setting synchronizes from
the server to every client, including whichever client owns a zone's simulation.
| Creatures | Allowed home biomes |
|---|---|
| Greydwarfs, Brutes and Shamans | Black Forest |
| Skeletons | Black Forest and Swamp |
| Draugr | Swamp |
| Fulings | Plains |
| Seekers, Seeker Broods and Ticks | Mistlands |
| Charred Warriors and Marksmen | Ashlands |
The filter only applies to night-only world-spawn entries requiring a boss's defeat key. Biome restrictions intersect the original spawn mask, never add new spawn locations. The installed game's extra Seeker Brood and Tick entries are already Mistlands-only, so they remain. Skeleton night waves retain their Black Forest and Swamp locations.
Ordinary spawns, raids, fixed spawners such as draugr villages, boss progression,
Odin visits and separate later-biome variants keep their existing behavior.
Existing creatures are not deleted; creatures can still walk across biome
borders. Setting the option to false restores the original rules on subsequent
spawn checks. Entry positions and original spawn data are preserved, keeping
native spawn timer identities intact.
Build
Requires the existing server/game assemblies under data/ (including the game's Newtonsoft.Json) and Jotunn under pack/. Testing and isolated runtime checks also use the installed ZenMap 1.11.0 and Zen.ModLib. Game and dependency libraries are not included in the package.
From the repository root:
nix develop path:.
dotnet restore mods/PoeTweaks/PoeTweaks.csproj --locked-mode
dotnet build mods/PoeTweaks/PoeTweaks.csproj -c Release --no-restore
dotnet build mods/PoeTweaks/PoeTweaks.csproj -c Release --no-restore -t:Package
The package is written to mods/PoeTweaks/dist/PoeTweaks-0.5.1.zip. It contains the plugin DLL, bundled death.wav, Sanctuary model/textures/attribution, manifest, README, and icon; it does not contain client configuration or game libraries.
Tests
From the repository root:
dotnet restore mods/PoeTweaks/tests/PoeTweaks.Tests.csproj --locked-mode
dotnet build mods/PoeTweaks/tests/PoeTweaks.Tests.csproj -c Release --no-restore
mono mods/PoeTweaks/tests/bin/Release/net462/PoeTweaks.Tests.exe --noresult --workers=0
Tests exercise XP calculations and nested target scopes, retention and recovery curve boundaries, monotonic peak tracking and native JSON persistence. They validate compiled game references and pass real Jotunn synchronization packets through independent server/client config instances. Initial packets and subsequent updates override client values without changing local-only settings, including under different number cultures. Tests do not run Valheim gameplay or a live client connection. Death-audio tests cover audience/radius boundaries, the local owned death transition, and compiled native audio/network references. Atlas tests cover JSON persistence, monotonic circle copies and warmup boundaries using the configured in-game day length. The isolated native startup check validates compatibility with ZenMap and invokes the actual patched table-read and map-input helpers. Headless checks do not verify the rendered client map. Inventory tests cover clean/stale comparisons, full signed item diffs, durable record replacement, account/world/character isolation and failed storage. Real native RPC/package roundtrips verify serialization and acknowledgement after storage; these do not replace a graphical client's actual Steam logout test. Sanctuary tests exercise saved growth trajectories, retargeting/cap boundaries, numeric input under different cultures and synchronized configuration. Native runtime checks load the model with Jotunn and verify area reindexing against the installed game's actual vendor-avoidance and player-base physics queries. Isolated checks exercise native spawn rejection/bypass and dropped-item despawn protection; terrain/blocking checks and flee movement are stubbed. Native boss checks verify full/wounded binary ZDO save-loads, existing bosses, config changes affecting new spawns, and native level/world/multiplayer scaling.
Disk edits require restarting/reloading the mod; no file watcher is installed.
Editing an override requires the next ./setup.py import, not just a restart,
because the importer copies overrides into pack/.
