Slotted Items (Gem Socketing) System — Design Notes
Status: draft plan — not yet implemented. To be refined / implemented later. This is the socket/loot vehicle referenced by the opt-in curse in Endgame Difficulty.
Context
A native socket layer built on the module's own Forge / persistence infrastructure.
The module currently has no gem-socketing / "slotted items" mechanic. The goal is a system where equipment carries a number of sockets, and players insert gems that permanently grant item properties. It must:
- Roll out to players with pre-existing items (retroactive socket grants).
- Use gem rarity tiers — common gems from standard/common mobs, rare gems from bosses.
- Support retroactive updates — bump a version stamp and existing items re-reconcile to the latest socket schema on next login.
- Not send players to jail. Socketed properties must be recognized as legitimate by
the existing Forge contraband / "pit prison" illegal-item checks
(
forge_scan_step.nss→ForgeItemLegalityinforge_inc.nss).
Rather than import a foreign Neverwinter Vault socket mod (which would clash with CEP, the local
persistence conventions, and the Forge legality system), this builds a native socket layer on the
existing infrastructure: the Forge's item-property apply/inspect helpers, the chunked login-scan
retroactive pattern, item-local .bic stamps, the generated-include pattern
(gen-forge-legal.py), and the bestiary universal OnDeath wrapper.
Design decisions (confirmed with user)
The shape of the system as agreed.
- Socket UI: standalone usable gem — activate the gem, target the equipment.
- Schema: per-item socket count from a generated lookup
(
resref → count+ value/base-item tier fallback), compiled tosocket_def_inc.nssby abin/python script (mirrorsbin/gen-forge-legal.py→forge_legal_inc.nss). - Retroactive rollout: a login reconciliation pass brings each player-owned item up to its defined socket count (adds missing sockets only, never removes), version-stamped.
- Gem rarity by CR (drop curve across all creatures): gems come in rarity tiers
(e.g. common → uncommon → rare → epic). Common/standard mobs drop common gems; higher
CR drops rarer gems; bosses drop rare/epic gems. One scripted drop hook on the universal
creature-death path keys the rarity band off
GetChallengeRating, with a weighted random "upgrade" so the same creature can occasionally roll one band higher. - Gem effects: each gem resref = a fixed item property; socketing is permanent (gem consumed, no extraction).
Data model — item-local vars (persist in the .bic, survive trade)
Stored on each socketable item, mirroring how FORGE_CLEAN/FORGE_CEIL already ride in the .bic (forge_inc.nss:40-48).
| Var | Type | Meaning |
|---|---|---|
SOCKET_MAX | int | sockets this item has (set by reconciliation from the schema) |
SOCKET_FILLED | int | how many are filled |
SOCKET_n_GEM (n=0..MAX-1) | string | gem resref in slot n (empty = open) |
SOCKET_VER | int | schema version stamp; bump SOCKET_VER_CUR to force re-reconcile |
Recording the gem resref per slot is what makes the legality reconstruction (below) possible.
New files
Includes, generated schema, action/scan scripts, drop hook, gem blueprints, generator.
gem_inc.nss— shared include. Constants (SOCKET_VER_CUR, rarity bands, drop chances,GEM_*keys), socket var helpers,GemBuildProperty(object oGem)(returns the gem'sitempropertyfrom its blueprint locals — modeled onGetNewProperty()initemprocs.nss, but reading locals off the gem object, not the PC),GemReconcileItem(oItem),GemSocketSummary(oItem)(for description/feedback), gem rarity pools, andGemBeginScan(oPC)(queues inventory likeForgeBeginScaninforge_inc.nss). Noteforge_inc.nss's warning: do not#include "itemprocs"from inside an include consumed alongside it — include both side-by-side in the leaf scripts instead.socket_def_inc.nss— GENERATED, do not hand-edit. Exposesint GemSocketMax(string sResref, int nBaseItem, int nValue)returning the schema socket count (explicit resref override, else value/base-item tier fallback).gem_activate.nss— the socket action (invoked fromdmfi_activate, see below).gem_scan_step.nss— one chunked reconciliation step, a near-clone offorge_scan_step.nss(ownGEM_SCAN_*locals so it never collides withFORGE_SCAN_*).gem_drop.nss— CR→rarity drop on every creature death (common mobs → common gems, bosses → rare/epic), with a weighted random upgrade. Gem pools are keyed by rarity band.- Gem
.uti.jsonblueprints, grouped by rarity band (common/uncommon/rare/epic) and effect — e.g.gem_common_fire,gem_rare_str, …. Each is a non-equippable gem with a "Cast Spell: Unique Power – Self Only" item property (so activation firesOnActivateItem) and blueprint local vars describing its effect (GEM_PROP,GEM_P1/2/3,GEM_RARITY) read byGemBuildProperty. Stronger properties live on rarer bands. Add a palette entry (itempalcus.itp.json). The existing1kgem/10kgem/100kgemare vendor-trash — do not reuse them. bin/gen-socket-defs.py+ asocket_defs.jsonconfig (resref overrides + tier rules). Scansunpacked/*.uti.jsonfor base item / gold value and emitssocket_def_inc.nss. Mirrorsbin/gen-forge-legal.py.forge_legal_inc.nssappears committed, so commitsocket_def_inc.nsstoo.
Files to modify
Activation dispatch, login scan wiring, and the critical Forge-legality integration.
dmfi_activate.nss— the liveMod_OnActvtItemhandler. Add an early branch: if the activated item is a gem (tag prefixgem_or aGEM_RARITYlocal),ExecuteScript("gem_activate")andreturn. (It already supports x2 tag-based dispatch at line 45 viaGetUserDefinedItemEventScriptName; an explicit branch is simpler and matches the existingDyeKit/bestiarybookbranches.)hgll_cliententer.nss— addDelayCommand(7.0, GemBeginScan(oPC));next to the existingDelayCommand(6.0, ForgeBeginScan(oPC));(line 107). This is the retroactive rollout + update trigger: a bumpedSOCKET_VER_CURre-reconciles all items at next login.
forge_inc.nss — ForgeItemLegality (CRITICAL, the jail concern).
Before it declares an item illegal (fingerprint not whitelisted / over value or prop caps), it must account
for socketed gems:
- Reconstruct the expected property set = blueprint/whitelist baseline plus
the properties contributed by each recorded
SOCKET_n_GEMresref (viaGemBuildProperty). If the item's actual permanent properties equal baseline + socket contributions, it is legal, even though the raw fingerprint isn't inforge_legal_inc.nss. - Raise the effective value / prop-count ceiling by the socket contributions (or stamp
FORGE_CEILon socket, asmodifyitem.nssalready does for Appraise-extended items), so legitimate socketing doesn't tripFORGE_LEGAL_MAX_PROPS(6) /FORGE_LEGAL_MAX_VALUE(750000). Anti-exploit: a gem only applies when it lands in a real schema socket (GemSocketMax > SOCKET_FILLED) and its property is bounded by gem rarity, so this can't be used to launder arbitrary forge value.
gem_activate.nss must also DeleteLocalInt(oItem, "FORGE_CLEAN") after socketing
(as modifyitem.nss does) so the item is re-validated under the new rules next scan.
- Creature death hook for gem drops. Add
ExecuteScript("gem_drop", OBJECT_SELF)to the universal creature-death path. Preferred hook:bst_ondeath.nss(the bestiary death handler thatbst_installalready wraps onto every spawned creature) — uniform coverage of all current and future creatures without editing 900+ blueprints. Fallback for any unwrapped creatures:nw_c2_default9.nss(default creature OnDeath, right afterCTG_GenerateNPCTreasure).
Behavior detail
Socketing, reconciliation, and the CR→rarity drop roll.
Socketing (gem_activate.nss): oGem = GetItemActivated(),
oPC = GetItemActivator(), oTarget = GetItemActivatedTarget(). Validate target is an
item the PC owns/equips and is socketable (GemReconcileItem then SOCKET_MAX > 0).
If SOCKET_FILLED < SOCKET_MAX: AddItemProperty(DURATION_TYPE_PERMANENT, GemBuildProperty(oGem), oTarget)
(stock add, as the forge uses — not IPSafeAddItemProperty), set SOCKET_<filled>_GEM = GetResRef(oGem),
increment SOCKET_FILLED, raise FORGE_CEIL, clear FORGE_CLEAN,
DestroyObject(oGem) (consumed), update description/feedback. Else: "This item has no open sockets."
Reconciliation (gem_scan_step.nss): per queued item,
nWant = GemSocketMax(resref, baseitem, value); if SOCKET_VER stale and
nWant > SOCKET_MAX, set SOCKET_MAX = nWant (preserve SOCKET_FILLED),
stamp SOCKET_VER = SOCKET_VER_CUR. Never removes sockets or properties. Reschedules itself via
DelayCommand(0.1, ...) exactly like forge_scan_step.
Drops by rarity (gem_drop.nss): cr = GetChallengeRating(OBJECT_SELF);
map CR to a rarity band (e.g. CR<5 → common, 5–14 → uncommon, 15–29 → rare, 30+ → epic;
bands tunable, can align with the bestiary's BST_SF_CR = 60.0 "hard creature" threshold for an epic
cutoff). Roll d100() <= dropChance(band) (common mobs have a lower per-kill chance, bosses higher).
On success, apply a weighted upgrade roll (e.g. ~80% base band / ~15% +1 band / ~5% +2 band) so the same creature
can occasionally yield a rarer gem, pick a random gem resref from that band's pool, and
CreateItemOnObject(sGem, <corpse>). Drop on the corpse so normal looting/party rules apply
(consistent with how blueprint loot drops today).
Verification
Build, socket, persist, jail-safety, drops, wiki.
- Build: run
bin/gen-socket-defs.py; repack via therepack-homers-lotrwrapper (not barenwn-manager repack); restart the server; confirmsocket_def_inc.nss,gem_*.nsscompile clean (the repack dialog-integrity/compile gate will surface errors). - Socket: give yourself a gem + a socketable weapon, activate gem → target weapon → property applied, gem consumed, socket filled, message shown. Activate again with no open socket → refusal message.
- Persistence + retro update: log out/in → socket vars persist. Bump
SOCKET_VER_CUR, relog → existing item gains newly-defined sockets (none removed). - Jail safety (the key check): put a socketed item in inventory and trigger the login
contraband sweep (
ForgeBeginScan) → player is not jailed; an item with a forged illegal property still is. Verify against the pit-prison flow. - Rarity drops: kill common mobs → common gems at low rate; kill high-CR bosses → rare/epic gems; confirm occasional one-band-up upgrades.
- Wiki: run
bin/refresh-homers-lotr-wikiso gem items/sources appear initem_index.json.
Critical files referenced
The existing infrastructure this system extends.
unpacked/forge_inc.nss—ForgeItemLegality,ForgeBeginScan, caps,FORGE_CLEAN/FORGE_CEIL(extend here)unpacked/forge_scan_step.nss— template forgem_scan_step.nssunpacked/forge_legal_inc.nss+bin/gen-forge-legal.py— generated-whitelist pattern to mirrorunpacked/itemprocs.nss(GetNewProperty,CustomAddProperty) — property-builder templateunpacked/modifyitem.nss—FORGE_CLEANclear +FORGE_CEILstamp precedentunpacked/dmfi_activate.nss—Mod_OnActvtItemhandler (add gem branch)unpacked/hgll_cliententer.nss(~line 107) — login scan wiring (addGemBeginScan)unpacked/bst_ondeath.nss/bst_install.nss/nw_c2_default9.nss— creature-death hook for dropsunpacked/module.ifo.json— event-handler resref map (no edit needed; handlers already route)