Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
SetUpSkills
Makes the hard-coded player skill numbers configurable: bow draw time, crossbow reload, damage curve, stamina costs, stealth, jump, XP rate and more. Server can push its config to clients.
| Last updated | 2 weeks ago |
| Total downloads | 282 |
| Total rating | 1 |
| Categories | Tweaks Tools Server-side Client-side |
| Dependency string | Neocor-SetUpSkills-0.1.0 |
| Dependants | 0 other packages depend on this package |
README
Set Up Skills
Puts every hard-coded player-skill number in Valheim into a config file. Built against Valheim 1.0.x and BepInEx 5.4.23.
The example that motivated the mod: a bow takes 2.50 s to draw at Bows 0 and 0.50 s at Bows 100, and nothing in the game lets you change that. With this mod:
[Bows]
DrawTimeMultAtSkill0 = 1.2 ## 2.5 * 1.2 = 3.00 s at skill 0
DrawTimeMultAtSkill100 = 0.4 ## 2.5 * 0.4 = 1.00 s at skill 100
Config file: BepInEx/config/neocor.SetUpSkills.cfg. Edits take effect on the next reload —
the file is watched, so on a server you can retune without restarting it.
📖 WIKI.md is the full configuration guide: every setting explained in plain
language, the vanilla numbers in tables, ready-made presets, and troubleshooting. Every
setting also carries its own explanation as a comment inside the generated .cfg.
Install
The mod must be on every client. Valheim computes all skill effects on the machine that
owns the player: Character.GetSkillFactor() returns 0 for anything that is not the local
Player, and the methods that consume it (Player.UpdateAttackBowDraw, Player.CheckRun,
ItemDrop.ItemData.GetDrawStaminaDrain, Attack.FireProjectileBurst, ...) only ever run
there. Damage is rolled on the attacker's client and only the result is sent over the wire.
A server-only install would change nothing.
| Where | What |
|---|---|
| Client | Drop SetUpSkills.dll into Valheim/BepInEx/plugins/. |
| Dedicated server | Install the BepInEx pack for Valheim, drop the same DLL into its BepInEx/plugins/, and start the server with start_server_bepinex.bat — the stock start_headless_server.bat does not load Doorstop. |
Installing it on the server as well is optional but recommended: the server then hands its config to every client that has the mod, so everyone plays on the same balance. See Server sync. Players without the mod can still join; they simply keep vanilla values.
How the settings are layered
Layer B — endpoint knobs (the usual way in)
Each of these replaces one vanilla constant. The defaults are the vanilla values, so an untouched config file is bit-for-bit vanilla.
| Section | Setting | Vanilla | What it controls |
|---|---|---|---|
Bows |
DrawTimeMultAtSkill0 / AtSkill100 |
1.0 / 0.2 | Draw time as a multiple of the weapon's m_drawDurationMin (2.5 s for every bow). |
Bows |
DrawTimeSecondsAtSkill0 / AtSkill100 |
0 / 0 | Absolute draw time in seconds, ignoring the weapon's base. Both must be > 0 to take over from the multipliers. |
Crossbows |
ReloadTimeMultAtSkill0 / AtSkill100 |
1.0 / 0.5 | Reload time as a multiple of m_reloadTime. |
Combat |
DrawStaminaReductionAtSkill100 |
0.33 | Stamina drain removed while drawing, at skill 100. |
Combat |
DrawEitrReductionAtSkill100 |
0.33 | Same for eitr. |
Combat |
AttackStaminaReductionAtSkill100 |
0.33 | Stamina cost of an attack removed at skill 100. |
Combat |
AttackEitrReductionAtSkill100 |
0.33 | Eitr cost of an attack. |
Combat |
AttackHealthReductionAtSkill100 |
0.33 | Health cost of an attack (blood magic). |
Damage |
MinFactorAtSkill0 / MaxFactorAtSkill100 |
0.4 / 1.0 | Damage multiplier before the random spread. |
Damage |
RandomRange |
0.15 | Half-width of the damage roll. 0 gives flat damage. |
Blocking |
BlockPowerBonusAtSkill100 |
0.5 | Extra block power as a fraction of the base. |
Run |
StaminaMultAtSkill0 / AtSkill100 |
1.0 / 0.5 | Run stamina drain. |
Run |
SpeedBonusAtSkill100 |
0.25 | Extra run speed (+25 %). |
Sneak |
StaminaMultAtSkill0 / AtSkill100 |
1.0 / 0.25 | Sneak stamina drain. |
Sneak |
StaminaExponent |
0.5 | Shape of the sneak stamina curve. Vanilla is a square root; 1.0 makes it linear. |
Sneak |
StealthBaseAtSkill100 / StealthLightWeightAtSkill100 |
0.2 / 0.4 | Visibility in darkness and the weight of ambient light, at skill 100. Lower is stealthier. |
Dodge |
StaminaMultAtSkill0 / AtSkill100 |
1.0 / 0.5 | Dodge stamina cost. |
Jump |
ForceBonusAtSkill100 |
0.4 | Extra jump force (+40 %), up and forward. |
Building |
StaminaReductionAtSkill100 |
0.5 | Build stamina cost removed at skill 100. |
Building |
PlaceDurabilityReductionMultiplier |
1.0 | Scales the tool's own m_placementDurabilityMax. |
Layer A — per-skill curve ([Curve])
Three values per skill, for all 24 skills:
[Curve]
Bows.Level0Factor = 0 ## what the skill is worth at level 0
Bows.Level100Factor = 1 ## ...and at level 100
Bows.Exponent = 1 ## 1 = linear, >1 = back-loaded, <1 = front-loaded
This remaps the skill factor itself, so it reaches everything that skill touches — including the effects driven by values stored in the prefabs, which Layer B cannot reach: swim stamina, fishing stamina, farming harvest radius, crafting speed and bonus-yield chance, placement durability, and riding.
Layer A and Layer B compose. Layer A decides how fast the benefit accrues with level;
Layer B decides where it ends up. Setting Bows.Level100Factor = 0.75 with default Layer B
gives a draw time of Lerp(2.5, 0.5, 0.75) = 1.0 s at skill 100 — the same result as the
example at the top, but it also scales bow damage by the same token. Use Layer B when you
want one effect, Layer A when you want the whole skill.
Layer C — experience ([Experience])
| Setting | Vanilla | Effect |
|---|---|---|
<Skill>.GainMultiplier |
1.0 | XP gain per skill. Stacks with the world modifier. |
CurveExponent / CurveScale |
1.5 / 0.5 | The XP wall: Pow(floor(level + 1), 1.5) * 0.5 + 0.5. Lower exponent means faster levelling. |
DeathLossFactor |
-1 | Fraction of every skill lost on death. -1 keeps the game's own value (0.05); 0 loses nothing. |
The level cap stays at 100. Raising it would mean rewriting Skills.Skill.Raise and the
level / 100 divisor, which touches save data, so it is deliberately out of scope.
Server sync
With [ServerSync] ServerIsAuthority = true (the default, only read on a server), the server
sends its whole config to each client as it connects, over a routed RPC named
SetUpSkills_Config. The client applies it in memory only — its own .cfg on disk is
never written to, and the local values come back the moment it disconnects. Editing the
config file on a running server reloads it and re-pushes to everyone already connected.
A client never sends config anywhere, and a server never accepts config from a client.
How it is patched
| Target | Kind | Why |
|---|---|---|
Skills.GetSkillFactor |
postfix | Layer A curve. |
Humanoid.GetAttackDrawPercentage |
prefix | Needs a different base, not just a different endpoint. |
ItemDrop.ItemData.GetWeaponLoadingTime |
prefix | Same. |
ItemDrop.ItemData.GetDrawStaminaDrain / GetDrawEitrDrain |
prefix | Two-line methods. |
Attack.GetAttackEitr(Character, ItemData) |
prefix | Two-line method. |
Skills.GetRandomSkillFactor / GetRandomSkillRange |
prefix | Roll and its tooltip, kept in step. |
ItemDrop.ItemData.GetBlockPower(int, float) |
prefix | Two-line method. |
Player.GetPlaceDurability |
prefix | Scales a prefab field, not a constant. |
Skills.RaiseSkill, Skills.Skill.GetNextLevelRequirement, Skills.OnDeath |
prefix | Experience. |
Attack.GetAttackStamina, Attack.GetAttackHealth, Character.Jump, Player.CheckRun, Player.GetRunSpeedFactor, Player.OnSneaking, Player.UpdateStealth, Player.GetDodgeStaminaUse, Player.GetBuildStamina |
transpiler | The skill constant is unique inside the method, so swapping just that literal leaves every other piece of vanilla logic running. Much more robust across game updates than a rewrite. |
Each patch is installed on its own. If a game update renames a method or moves a constant,
that one effect falls back to vanilla and a warning appears in the log — the rest keeps
working. Set [General] IsDebug = true to see the computed values and which transpilers hit.
[General] Enabled = false returns every knob to its vanilla default without unloading
anything, which is also what happens to transpiled code (it cannot be un-patched at runtime,
so it looks up the vanilla constant instead).
Building
With the .NET SDK:
dotnet build SetUpSkills\SetUpSkills.csproj -c Release
Override the game path with -p:ValheimPath="C:\Path\To\Valheim" if it is not on G:.
Without an SDK, build.ps1 finds a C# compiler on its own and package.ps1 produces the
Thunderstore zip:
powershell -ExecutionPolicy Bypass -File SetUpSkills\build.ps1
powershell -ExecutionPolicy Bypass -File SetUpSkills\package.ps1
That fallback path can land on C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe,
which only understands C# 5. The source currently stays inside that subset — no string
interpolation, no nameof, no ?., no expression-bodied members — so both build routes
work. Drop the restriction only if you also stop using build.ps1.