You are viewing a potentially older version of this package. View all versions.
Dafini-NorntasticSkillGrowth-0.9.2 icon

NorntasticSkillGrowth

Skyrim-style perk trees for every Valheim skill, plus weapon forms, craft disciplines, dual wield, ally auras, prestige and respec. Optional Jewelcrafting support.

Date uploaded 2 months ago
Version 0.9.2
Download link Dafini-NorntasticSkillGrowth-0.9.2.zip
Downloads 92
Dependency string Dafini-NorntasticSkillGrowth-0.9.2

This mod requires the following mods to function

denikson-BepInExPack_Valheim-5.4.2350 icon
denikson-BepInExPack_Valheim

BepInEx pack for Valheim. Preconfigured with the correct entry point for mods and preferred defaults for the community.

Preferred version: 5.4.2350
ValheimModding-Jotunn-2.30.0 icon
ValheimModding-Jotunn

Jötunn (/ˈjɔːtʊn/, 'giant'), the Valheim Library was created with the goal of making the lives of mod developers easier. It enables you to create mods for Valheim using an abstracted API so you can focus on the actual content creation.

Preferred version: 2.30.0

README

Norntastic Skill Growth

Skyrim-style perk trees for every Valheim skill, four overlapping sector trees, and three experimental skills. Built against Valheim 1.0.12, BepInEx 5.4.2350, Jötunn 2.30.0.

This drop is Stages 0 through 7 — everything but the balance pass.


What works right now

  • Loads against 1.0.12 and logs every registered skill, including ones other mods added
  • High-water point tracking with a retroactive grant on first load — an existing character walks in with every point their levels have already earned
  • Perk purchase with level gates, prerequisites and a point budget
  • Refunds with downstream cascade, priced in respec points (or free, per config)
  • Prestige at level 100
  • Auto-generated stub trees for foreign skills
  • Persistence in the character's own save, travelling with the .fch across worlds
  • All 31 trees, 360 nodes and 715 ranks loaded from an editable perks.json
  • Perk effects via one aggregate status effect, with live conditions
  • A full-screen perk panel — press P — with the node graph, the skill rail, per-rank detail, buy/refund and prestige
  • Armor, Unarmored and Tavern Brawler registered as real skills, so their trees earn points like any other
  • Armor and Unarmored level from taking hits — Armor off what your gear absorbed, Unarmored only when every armour slot is empty, on a deliberately front-loaded curve
  • An on-hit dispatcher and the first handler family: bleed across Axes and Swords
  • Tavern Brawler: swing or throw any inventory item as an improvised weapon, with damage derived from what the item actually is

Effect coverage — read this before judging the mod

69 of 384 nodes currently do something. 315 are authored but not implemented.

That is not a shortfall against a plan, it is what the trees turned out to be. When every node was mapped against the status-effect API, only the unconditional numeric perks fit — damage percentages, stamina costs, regeneration, armour, carry weight. Everything else is a mechanic: a bleed, a stack counter, an active ability, a yield change, a co-op aura. Each of those needs its own code.

The breakdown of what the 313 are waiting on:

Needs Share
Bespoke combat code (bleeds, counters, on-hit, AoE) ~45%
Yield / gathering patches (ore, wood, crops, fish) ~11%
Active abilities and event triggers ~11%
Crafting, building and durability patches ~5%
Chance-based procs ~5%
Mount patches ~3%
Conditions not yet modelled ~7%
Co-op auras ~2%

So the honest roadmap is not "one more stage". It is a long tail, and the mod is built for that: PerkHandler is a one-perk-at-a-time extension point, registered by node id, and Effects/Handlers/ is where they land. Nothing in Core or Effects changes as perks go live. A perk that is not implemented says so — in the panel, in nsg.tree, and in the message when you buy it. It never silently does nothing.

The Character.Damage dispatcher is the cheapest hook in the whole plan: HitData carries m_skill, so one patch makes every "when you land a hit, do X" perk implementable without another. Eighteen authored perks are that shape. Twelve are done, and Character.IsStaggering() being public means the "against staggered targets" family rides the same hook.

Tavern Brawler

Ready an inventory item with J, swing it with G, throw it with H. Damage comes from the item — weight is the base, anything with its own edge keeps half of it, tool tier stands in for an edge on things that have no damage profile at all. Skill is worth up to +60% by level 100, roughly a weapon skill's worth.

Only items with no durability can break. A tool, a weapon, a piece of armour — anything whose SharedData sets m_useDurability — wears down instead and is never destroyed. The one edge case handled explicitly: Valheim destroys a broken item when m_destroyBroken is set, so those are floored at 1 durability and can reach broken-but-present, never gone.

Materials, food and trophies have no durability to wear, so those are what shatter, one out of the stack at a time. Heavier things survive better and skill reduces the chance further, before Careful Grip touches it. A thrown breakable always leaves your hand; the roll decides whether it is lying where it landed or gone.

Six of its twelve perks are live: Anything Goes, Careful Grip, Bottle Service, Bar Fight, Ore Toss and Closing Time.

What still doesn't work

  • No Unwoven Thread item. nsg.rp stands in for it.
  • The brawler's readied item is cycled with a key rather than picked in the inventory screen. Patching InventoryGui for a right-click-to-ready gesture is nicer and a much bigger surface to get wrong.
  • Throws are hitscan, not arcing projectiles.
  • The panel has no pan or zoom; trees are laid out to fit.
  • No custom icons for the three new skills; Jötunn's default is used.

Building

Full walkthrough, written for r2modman: BUILDING.md.

The short version:

$env:VALHEIM_INSTALL = "C:\Program Files (x86)\Steam\steamapps\common\Valheim"
$env:BEPINEX_ROOT    = "$env:APPDATA\r2modmanPlus-local\Valheim\profiles\<YourProfile>"
.\pack.ps1

Then r2modman → Settings → Import local mod and pick the zip in dist\.

The two paths are different on purpose: under r2modman, BepInEx and Jötunn live inside the profile, not the game folder, and each mod gets its own Author-ModName directory — Jötunn ends up at plugins\ValheimModding-Jotunn\Jotunn.dll. The build probes that layout and the flat one, and tells you where it looked if both miss.

ServerSync is optional. Without lib/ServerSync.dll the mod builds and runs, it just doesn't push config from the server — fine for a first build and for single player. See BUILDING.md section 6 for adding it.

Tests

./tests/run.sh

Runs the point engine and the effect layer against the real perks.json with no game, no BepInEx and no Jötunn — tests/Stubs.cs stands in for every external type. 58 assertions covering retroactivity, the high-water ratchet, sector normalisation, purchase gating, the cascade refund, prestige arithmetic, the save round-trip, effect aggregation and conditional effects. Needs mono-devel, or swap mcs for csc.

The UI, the Harmony patches and the console are excluded — they need a live game. Everything that can be tested without one, is.

Linting against the real assembly

pip install dnfile
python3 tests/lint_api.py "<Valheim>/valheim_Data/Managed/assembly_valheim.dll"

The stub compile proves the code is internally consistent, but the stubs are hand-written, so a Valheim member that does not actually exist compiles happily and throws at runtime. This checks every Valheim member the mod names against the shipped assembly. It found four invented equipment accessors on its first run — GetChestItem() and friends do not exist; Humanoid exposes m_chestItem as a public field. Run it after every Valheim update.


Installing

Drop Norntastic.dll in BepInEx/plugins/. Everyone on the server needs it — the mod declares EveryoneMustHaveMod.

On first launch it writes BepInEx/config/Norntastic/perks.json. The file on disk wins from then on, so edit trees freely and nsg.reload to pick them up without restarting.


Console

nsg.status first. Cheat commands need devcommands.

Command What
nsg.status Points earned, spent and open across every tree
nsg.trees Every tree key, kind and size
nsg.tree <key> Every node in a tree with its state, including anything held
nsg.buy <perkId> Spend a point
nsg.refund <perkId> Refund one rank, cascading downstream
nsg.clear <treeKey> Refund a whole tree, free (cheat)
nsg.prestige <treeKey> Prestige at level 100
nsg.rp [n] Grant respec points (cheat)
nsg.sethw <skillKey> <level> Set a high-water mark (cheat) — the fast way to test
nsg.reload Re-read perks.json (cheat)
nsg.panel Open the perk panel

Tavern Brawler binds G / H / J by default — swing, throw, cycle.

The quickest way to see the whole thing work:

nsg.sethw axes 100
nsg.tree axes
nsg.buy ax1
nsg.buy ax1
nsg.prestige axes
nsg.status

How the economy works

Three things are stored per character, and points are not one of them:

highWater : skillKey -> highest level ever reached   (monotonic)
allocated : perkId   -> ranks bought
prestige  : skillKey -> times prestiged

Everything else derives:

earned(skill)  = floor(highWater / interval) + prestige
earned(sector) = floor(sum(member highWater) / (5 x memberCount)) + sum(member prestige)

Why it has to work this way. Valheim 1.0 added a total skill cap: Skills.RaiseSkill calls RebalanceSkills, which — when the total is over the cap — walks the whole skill dictionary and proportionally scales every other skill down. Levelling Axes actively drains Swords. Add the DeathSkillsReset world modifier and death zeroes everything.

If points tracked the live level, players on a capped world would watch perks wink out while doing nothing wrong. They don't, because the high-water mark never moves down, and because a perk's level requirement is checked when you buy it and never again. A perk below its requirement shows as held and keeps applying.

That also makes retroactivity free: seeding highWater from current levels on first load is the retroactive grant. No migration step, no already-granted flag to get wrong.

Prestige resets the live skill but not the high-water mark, so it costs you the vanilla skill's own effectiveness and nothing else.


Known unknowns

  • Skills.m_useSkillCap and m_totalSkillCap are Unity inspector values on the Player prefab, not global keys, so the cap's default state can't be read from the assembly. Open the skills panel in game: SkillsDialog has an m_totalSkillText, so a running total means the cap is live.
  • ValheimPlus patches Skills.RaiseSkill for its own gain multipliers and separately touches stamina, carry weight, build cost and durability. Harmony composes the patches; the numbers multiply. Turn its skill/stamina/building modules off for the balance pass.

Built with AI assistance.

CHANGELOG

Changelog

0.14.2 — the Deep Fryer Basin, and the reason the fryer never made anything

Fryer planned its output for several versions and created nothing. nsg.fryer listed forty dishes that did not exist - a design preview presented as an inventory, which is the same wrong as a checklist saying "not seen yet" about a subsystem that has run.

It was not a broken builder. There was no builder. Nothing registered it with the forge, so nothing ever called it, and from in game that is indistinguishable from a feature that is merely slow to appear. The plan was always right; what was missing was the two dozen lines that turn a FryResult into a prefab.

Fried food is derived now, like the pies and the jerky, because what a modlist can fry is not knowable in advance. nsg.fryer reports built against planned rather than one number that means either.

The basin is a seventh station, not a seventh system

"Upgraded variants frying more at a time" is what the tiers already say: scale the batch, hold the speed. So the Deep Fryer Basin is an AutoStation like the other six - Container split by row, offline accrual, owner check, experience to whoever built it - and Deep Fry Tactician has something to level off at last.

It runs faster than the rest at five minutes a cycle, and it is the one station where that is right: frying is the quick trade in a mod full of slow ones.

Its chains are worked out at build time, because "ANY food" is a promise a written list cannot keep. Hydroponics needed the same thing and was a named special case inside Stations.cs - the fryer needing it too is exactly how a second special case gets added beside the first instead of replacing the reason it existed, so it is a DerivedChains hook on the definition now and both use it.

It is the third and last gate under StationPolicy's budget of three. "Any food becomes the next kind" is what the basin DOES, and no vanilla building does it - which is the whole test for whether a gate is earned.

The check that would have caught it years earlier

check_content_armed fails the build on any class that registers a content builder while Plugin never calls its Arm(). Same shape as a ConsoleCommand nobody registers, which this file has checked for the same reason since 0.9.4 - content that compiles, tests clean, ships, and does nothing.

Verified by removing the new Fryer.Arm() line, which is precisely the state the fryer shipped in for months.

844 assertions, 174 files, 563 API references clean.