Character & Skills Reference
Full design detail for the Character/Stats/Skills plugins (see the plan entry) — every skill node, both equipment systems, the UI notification spec, and the currency design. Extracted from a standalone reference document that isn't tracked anywhere else; this page is now the source of truth for it.
definitions.db at the time it was written, not re-verified since. Confirm hooks are still current before implementing.Progression model
A single character-level XP track (from combat, gathering, and quests) grants both stat points and skill points. Skills don't level independently through use — all growth comes from spending points earned at the character level.
Each node holds multiple ranks. A node needs rank ≥1 in the node before it to unlock, and its own rank can never exceed that prior node's current rank — forcing balanced investment up the chain rather than rushing straight to the strongest node.
There is no separate profession system or profession point pool. "Carpenter," "miner," etc. are just descriptive labels for whatever skill combination a player has actually invested in.
definitions.db. Speed and cost-reduction nodes without any supporting hook were cut rather than kept as aspirational placeholders. Animal taming/riding was cut entirely (saddle systems are the separate saddle & saddle bag system below) — the roster is smaller than the original ambition (Crafting skills lost half their nodes) since the API doesn't expose everything that was hoped for.Extraction
Mining
Break stone and pull ore from the world.
Reads the event's current value and boosts it by the rank's percentage before handing the event back.
Rolls a chance sized to the rank; on success, grants the bonus item straight to the player's inventory.
Reads the real per-depth ore-weight table (definitions.db) for the player's region, then rolls a bonus toward whichever ore is genuinely rarer at that depth — not a guessed curve.
Deducts a small stamina cost on every qualifying hit — a cost the plugin introduces itself — then reduces that deduction by the rank's percentage.
Felling
Cut down trees for lumber and fruit.
Reads the event's current value and boosts it by the rank's percentage before handing the event back.
Rolls a chance sized to the rank; on success, grants the bonus item straight to the player's inventory.
Checks the target's type ID against a maintained category list, then rolls a bonus-item chance if it matches.
pickupitem), not a maintained name list — new fruit trees added in future updates are caught automatically.Rolls a chance sized to the rank; on success, grants the bonus item straight to the player's inventory.
Deducts a small stamina cost on every qualifying hit — a cost the plugin introduces itself — then reduces that deduction by the rank's percentage.
Gathering
Forage wild plants, berries, and loose materials.
Reads the event's current value and boosts it by the rank's percentage before handing the event back.
Rolls a chance sized to the rank; on success, grants the bonus item straight to the player's inventory.
Checks the target's type ID against a maintained category list, then rolls a bonus-item chance if it matches.
plants.type='Plant' field — no maintenance needed as content is added.Production
Plant farming
Grow and harvest cultivated crops.
Rolls a chance sized to the rank; on success, grants the bonus item straight to the player's inventory.
Directly shortens the plant's next-growth-stage timer by the rank's percentage.
Rolls a chance sized to the rank; on success, grants the bonus item straight to the player's inventory.
Rolls a chance sized to the rank; on success, bumps the resulting item stack instead of consuming the input.
Animal husbandry
Breed, raise, and collect from livestock.
npcs_breeding shows identical pregnancy timing (1800s duration, 20000s cooldown) across every animal — the real variation is litter size (pig/shewolf up to 3, most animals locked to 1) and which animals have milk fields populated at all (only cow, in this dataset).Cow's real milkduration (8000s) and milkcooldown (600s) define the base collection window — this node grants a bonus item on top of the normal yield, scaled by rank.
For animals whose real maxchilds exceeds 1 (pig, shewolf, arcticshewolf), biases the litter roll toward that maximum rather than the minimum, scaled by rank.
Directly advances the animal's age value, scaled by rank, to speed up maturation.
Rolls a chance sized to the rank; on failure only, cancels the event to represent a missed attempt.
Fishing
Cast a line and reel in a catch.
spawnchance ranges from 5% (blobfish) to 100% (common fish like herring/perch/plaice) — tier alone doesn't encode rarity in this dataset, spawnchance does.Reads the event's current value and boosts it by the rank's percentage before handing the event back.
Rerolls toward a low-spawnchance entry (e.g. blobfish at 5%, tuna at 20%) instead of the default weighted pool, scaled by rank.
spawnchance values — the bonus is meaningfully rare, not an arbitrary reroll.Applies only to species with requiresbait=1 (salmon, tuna, koi, carp, shrimp, blobfish) — rolls a refund chance on those catches specifically, scaled by rank.
Compares against real minwaterdepth thresholds (e.g. salmon 40, tuna 50) — grants a bonus only once the player is actually deep enough for that species to spawn.
Combat
Hunting
Track and take down wild animals.
Reads the event's current value and boosts it by the rank's percentage before handing the event back.
Rolls a chance sized to the rank; on success, grants the bonus item straight to the player's inventory.
Rolls a chance sized to the rank; on success, grants the bonus item straight to the player's inventory.
Reduces the native stamina cost of sprinting while tracking, scaled by rank.
Combat
Fight hostile NPCs and other players.
items/weapondata: huntingknife 70 dmg / 800 durability, combatknife 70 dmg / 1000 durability, javelinspear 100 dmg / 180 durability.Boosts the event's damage value by the rank's percentage — e.g. rank 5 on a 70-damage huntingknife adds roughly 8.75 damage.
Nudges the fired shot's rotation toward the target to reduce spread, scaled by rank; damage bonus applies at the eventual hit event.
Reads the incoming damage value and reduces it by the rank's percentage before it's applied.
Crafting
Smithing
Smelt ore and forge tools and weapons.
Rolls a chance sized to the rank; on success, bumps the resulting item stack instead of consuming the input.
Applies a stat or durability modifier to the crafted item object, scaled by rank.
setModifier() isn't documented — needs an in-game test to confirm what it actually changes before relying on it.Cooking
Prepare ingredients into meals.
Applies a stat or durability modifier to the crafted item object, scaled by rank.
setModifier() semantics caveat — verify in-game before finalizing.Rolls a chance sized to the rank; on success, bumps the resulting item stack instead of consuming the input.
Construction
Build with wood or stone, and specialize in either.
basestrength varies by shape, not material: block/cylinder=100, ramp=60, plank=50, quartercylinder=30. Material comes from the live texture (blockids.type), not a static per-shape field.Boosts the placed element's real basestrength (which already varies 30–100 by shape) by the rank's percentage.
Checks the target's type ID against a maintained category list, then rolls a bonus-item chance if it matches.
Checks the target's type ID against a maintained category list, then rolls a bonus-item chance if it matches.
Tailoring
Craft clothing and armor.
Applies a stat or durability modifier to the crafted item object, scaled by rank.
setModifier() verification caveat.Applies a stat or durability modifier to the crafted item object, scaled by rank.
setDurability() is unambiguous — unlike setModifier(), there's no semantic uncertainty about what this changes.Type lookup table
Confirmed against the real definitions.db schema. Correction from an earlier pass: constructions.relateditem is a fixed placeholder on every row, not a real link — construction material actually comes from the live element's texture ID, cross-referenced against blockids.type.
- Home server calls
Definitions.getAllPlantDefinitions()andgetAllConstructionDefinitions()at startup. No other server ever runs this scan. - Each definition's real type/material/pickupitem fields are read directly — no name matching required.
- Home pushes the finished typeID → category table to the backend over the existing persistent socket.
- Backend caches it (write-through, same pattern as player data) and serves it to every server — including Home — on connect.
| Category | Real field check | Used by |
|---|---|---|
felling.lumber | plants.type = 'Tree' AND pickupitem IS NULL | Felling — Axe power, Timber yield, Sapling recovery |
felling.fruit | plants.type = 'Tree' AND pickupitem IS NOT NULL | Felling — Orchard bonus |
gathering.wild | plants.type = 'Plant' | Gathering — Keen eye |
farming.crop | plants.type = 'Crop' | Plant farming — Bumper crop, Crop quality, Land efficiency |
construction.wood | ConstructionElement.getTexture() → blockids.type = 'wood' | Construction — Woodworking |
construction.stone | ConstructionElement.getTexture() → blockids.type = 'stone' | Construction — Stonework |
Unmatched plant or construction types fall into no category and receive no bonus — worth logging a startup warning for anything that doesn't match, so new game content doesn't silently miss out on skill support.
Backpack system
A modular, stat-scalable storage container, synced across every server.
World.createStorage() creates a plugin-owned container tagged StorageType.API — distinct from vanilla Chest/Vehicle/Npc storages. Player.showStorage(storageID) / hideStorage() open and close it exactly like a native chest.
Storage.setSlotCount() is a plain integer, so a "Carrying capacity" stat — paid for with character-level stat points, not skill points — can drive it directly: base 9 slots + 1 per point invested, recalculated whenever the storage is (re)created.
On disconnect/transfer, serialize the storage's contents (Storage.getItems()) into the player data package as its own module, not folded into the core inventory blob — keeps it toggleable and extensible later. On the destination server, call World.createStorage() fresh and replay the item list back in via addItemToSlot().
This is the same force-sync-on-transfer pattern already used for inventory — a second, independent payload riding alongside it, not a new sync mechanism.
Saddle & saddle bag system
Custom-crafted saddles and saddle bags with hide-socketed stats, synced and persistent through trades.
setAttribute()/getAttribute() like Npc, Player, ConstructionElement, and Plant do — stat storage has to be encoded into Item.setValue() instead, and that field's native meaning (if any) needs a quick in-game confirmation before relying on it.Each socketed hide is stored as a small ID packed into Item.setValue(): 6 bits per slot supports up to 63 hide types. Saddles use 4 slots (24 bits total — health, speed, whistle distance, whistle cooldown); saddle bags use 2 slots (12 bits — both extra capacity). The actual bonus amounts are never stored on the item at all.
setValue() may already carry a native meaning (e.g. vendor sell price) that this would collide with — needs an in-game test before committing.The bonus each hide grants per slot lives in backend config, not on the item — synced to every server on connect, the same handshake pattern already used for currency, skill trees, and the type lookup table. Adjusting a hide's power is a config edit, not a code change.
| Hide (item, variant) | Health slot | Speed slot | Whistle distance | Whistle cooldown |
|---|---|---|---|---|
| penguinhide | +50 HP | +3% | +10m | −2s |
| foxpelt | +50 HP | +5% | +10m | −2s |
| sheeppelt | +50 HP | +5% | +10m | −2s |
| goatpelt | +50 HP | +5% | +10m | −2s |
| pighide | +50 HP | +4% | +10m | −2s |
| cowhide | +100 HP | +8% | +20m | −4s |
| deerpelt | +100 HP | +8% | +20m | −4s |
| boarhide | +150 HP | +7% | +25m | −5s |
| wolfpelt | +100 HP | +15% | +40m | −8s |
| deerredpelt | +150 HP | +9% | +30m | −6s |
| lionpelt | +150 HP | +9% | +30m | −6s |
| moosepelt | +150 HP | +9% | +30m | −6s |
| bearpelt | +150 HP | +7% | +25m | −5s |
| polarbearpelt | +150 HP | +7% | +25m | −5s |
| horsehide (v0–3) | +100 HP | +15% | +40m | −8s |
| zebrahide | +150 HP | +10% | +30m | −6s |
| rhinohide | +250 HP | +10% | +35m | −7s |
| elephanthide | +350 HP | +3% | +25m | −5s |
| horsehide (v4, unicorn) | +500 HP | +10% | +60m | −15s |
Saddle bags use the same hide roster but a different rationale: bonus scales with hide surface area/animal size rather than speed, since carrying capacity is about material, not agility. Same backend-config sync pattern as the saddle table above.
| Hide (item, variant) | Extra storage slots |
|---|---|
| penguinhide / foxpelt / sheeppelt / goatpelt / pighide | +1 |
| cowhide / deerpelt | +2 |
| boarhide / wolfpelt / deerredpelt / lionpelt / moosepelt / bearpelt / polarbearpelt / horsehide (v0–3) / zebrahide | +3 |
| rhinohide | +4 |
| elephanthide | +5 |
| horsehide (v4, unicorn) | +6 |
Unicorn is not a separate item — it's horsehide with itemtexture/variant=4, dropped by horse-variant NPCs 14 and 15 in npcs_variants (800 HP source, 1.0% and 0.1% spawn chance respectively — the rarest, toughest entry in the whole dataset). Item.getVariant() already distinguishes it natively from common horsehide.
Npc.setPosition() is confirmed directly — whistling reads the player's current position and calls setPosition() on the mounted Npc. The cooldown itself isn't an API concept: plain plugin-side timestamp tracking, compared against now plus whatever the socketed cooldown-reduction stat subtracts.
Since the socket configuration lives inside Item.setValue(), it rides along automatically with whatever serialization already happens for inventory/equipped items in the player data package — no new sync mechanism, same as the backpack.
Toast notification system
One shared, modular popup function every other system calls into. First concrete spec for the parked "UI system" feature idea.
One function — showToast(player, text, backgroundColor, borderColor, duration) — callable from any system. Skill rank-ups, currency changes, backpack-respec-blocked messages, and saddle stat notifications all call the same function instead of each building its own popup.
Toasts are their own top-level UIElement, added directly via player.addUIElement() — not nested under the Skills/Currency/Map page-switching root. This makes them always visible: they render regardless of which page is open, or even if the whole page UI is hidden entirely.
This is the one real architectural decision this system forces: two separate UI roots per player, not one — the page root and the always-visible toast root, each managed independently.
Capped at 4 visible toasts. A 5th incoming toast immediately dismisses the oldest (instant removal, no fade) to make room, then the new one appears. Dismiss is likewise instant on timeout — no animation.
Each toast stores its own creation timestamp. A shared tick (via UpdateEvent, checked periodically rather than every frame to avoid overhead) walks the active toast list per player and instantly removes anything past its duration.
Currency system
Backend-defined, multiple currencies, physical exchange markets in each city.
Backend config defines each currency's value type (int/double/float), icon, and is-tradable flag — synced to every server plugin on connect.
Base currency is Gold Coin; more currencies can be added later purely through config, no code changes required.
Physical location in each main city; leaning toward dynamic (supply/demand) exchange rates, mechanics still undecided.
Remaining build order
The original phased plan had 11 phases (0–10), sequenced by dependency. Phases 0, 1, 2, 5, and 6 covered backend skeleton, single-plugin socket connection, single-server player data sync, multi-server topology, and Home-only systems (weather/time/chat/routing) — all effectively satisfied already by the real framework rework, TransferManager's grid-transfer system, and WeatherChangeListener/TimeSyncManager, though built independently rather than by following this exact plan. What's left, in dependency order:
Pick Mining — the most-verified skill with the cleanest hooks. Build the ranked-node data model and the unlock-gate/rank-ceiling enforcement server-side. Wire PlayerHitTerrainEvent to Swing power and Ore yield. Skip Prospecting and Endurance initially — they carry extra dependencies (region data, a custom stamina system).
Done when: a player can spend a point, see Swing power's damage bonus actually apply in-game, and the rank persists through a disconnect/reconnect.
Balances just live inside the player data package, so this is mostly data-model work: add currency fields, a basic admin/debug command to grant currency, confirm it round-trips through sync like everything else in the package.
Done when: currency balance survives a disconnect/reconnect cycle.
Add the Definitions.getAllPlantDefinitions()/getAllConstructionDefinitions() scan on Home, build the typeID → category table, push it through the existing config-handshake pattern.
Done when: a spoke server logs the received category table on connect and it matches what Home actually scanned.
With the pattern proven on Mining, the rest go faster: Felling and Gathering reuse the lookup table immediately. Then Plant Farming, Hunting, Combat, Fishing, Husbandry, and finally the thinner Crafting skills (Smithing, Cooking, Construction, Tailoring).
Extraction and combat skills only need player/hit events already built for Mining; Construction specifically needs the lookup table's wood/stone split.
Define the in-world Area per city, wire PlayerEnterAreaEvent to the per-player discovery flag, and connect discovered-portal teleportation into the existing transfer flow — it's a trigger, not a new system. See the plan entry.
Exchange market rate mechanics, cache eviction tuning under real load (see TODO), TLS if moving beyond local testing, an admin re-scan command for the lookup table.