# ActiveNPCs Profile Stamp Patch **Targets:** Satyr Aeon's **ActiveNPCs** controller as published on OpenSimWorld ([opensimworld.com/library?view=4](https://opensimworld.com/library?view=4)) — specifically the file `Controller.lsl`. **Adds:** Auto-applied profile image and About text on every NPC load — including `LoadNPC`, `LoadAll`, `loadnpc` from notecards, and `RescanAvis` (NPCs already in the region at controller startup). **Why:** When ActiveNPCs spawns, respawns, or rescans an NPC, the new puppet body has a blank profile. Visitors clicking the NPC see no picture and no About text. This patch lets the existing `__npc_names` notecard hold an identity backup that gets stamped onto the NPC every time it comes online through the controller. **Author note:** This patch was developed alongside a private fork (JesterNPC) and contributed back for the public ActiveNPCs release. The two implementations are functionally equivalent. --- ## What Changes — At a Glance - `__npc_names` notecard format gains two **optional** fields per line: an image inventory name and an About text. - `Controller.lsl` gains two parallel lists, an updated `ReloadConfig()` parser, a new `StampProfile()` helper, and a single one-line call inside `doAddNpc()`. - **All existing notecard entries with just `firstname lastname` continue to work unchanged** — no migration required. - No changes to `Listener.lsl`, `Extension.lsl`, or any other file in the package. --- ## New `__npc_names` Notecard Format ``` firstname lastname imageNAME | About text goes here with spaces and punctuation ``` | Field | Required | Notes | |---|---|---| | `firstname` | yes | First name as before. | | `lastname` | no | Last name. Falls back to `LASTNAME` from `__config` if omitted. | | `imageNAME` | no | Inventory name of a texture sitting in the **controller prim's** root inventory. Leave blank to skip the image stamp. | | `\| About text` | no | Everything after the first pipe character becomes the profile About box text. May contain spaces and punctuation. | **Example notecard:** ``` Bob TheRobot bob_face.png | Itinerant scholar. Knows too much about wormholes. Alice TheNPC alice_profile.jpg | Runs the Velvet Lounge. Ask about the back room. Charlie TheNPC | No image, just a bio. Mysterious type. Dave TheNPC ``` The first three lines exercise different combinations of optional fields. The fourth line is fully legacy and parses identically to the unpatched controller. --- ## OSSL Permission Requirements `osNpcSetProfileImage` and `osNpcSetProfileAbout` are OSSL functions. Your simulator's `OpenSim.ini` (or the active `osslEnable.ini`) must allow them for the script's owner. On most builds these sit at **Threat Level Moderate** — confirm with your grid operator if you don't run the region yourself. If the permissions aren't granted the script will throw a runtime error on the stamp call. The `StampProfile()` helper below silently no-ops on missing-image or missing-about-text, but it cannot defend against an OSSL permission denial — that has to be fixed at the simulator config level. --- ## Code Changes — Four Edits to `Controller.lsl` All line numbers reference the published `Controller.lsl` from [opensimworld.com/library?viewcode=3&raw=1&sid=4](https://opensimworld.com/library?viewcode=3&raw=1&sid=4) as of the date of this patch. If your copy has been modified locally, find the matching landmark code in the descriptions below. --- ### Edit 1 — Add two parallel lists alongside `availableNames` and `lastNames` **Find this block** near the top of the script (around line 11): ```lsl list availableNames = []; list lastNames = []; ``` **Replace with:** ```lsl list availableNames = []; list lastNames = []; list npcImages = []; // texture inventory name per availableNames index list npcAbouts = []; // about text per availableNames index ``` --- ### Edit 2 — Replace the `ReloadConfig()` parser **Find** the existing `ReloadConfig()` function. The block to replace begins at the function declaration and ends just before `flyTargets = llParseString2List(...)`. The original is: ```lsl ReloadConfig() { availableNames = []; lastNames = []; list tk = llParseString2List(osGetNotecard("__npc_names"), ["\n"] , []); integer i=0; for (i=0; i < llGetListLength(tk); i++) { list t2 = llParseString2List(llList2String(tk,i), [" "] , []); string f = llList2String(t2, 0); string l = llList2String(t2, 1); if (l =="") l = LASTNAME; if (f != "") { availableNames += f; lastNames += l; } } llOwnerSay("npc_names: "+llList2CSV(availableNames)); ``` **Replace** *only* that portion (do **not** touch the `flyTargets`, `__config`, or `LoadPerms()` calls that follow) with: ```lsl ReloadConfig() { availableNames = []; lastNames = []; npcImages = []; npcAbouts = []; list tk = llParseString2List(osGetNotecard("__npc_names"), ["\n"] , []); integer i=0; for (i=0; i < llGetListLength(tk); i++) { string raw = llStringTrim(llList2String(tk,i), STRING_TRIM); if (raw == "") jump skipline; if (llGetSubString(raw, 0, 1) == "//") jump skipline; // ---- split off about text on the first pipe character ---- string nameBlock = raw; string aboutText = ""; integer pipeIdx = llSubStringIndex(raw, "|"); if (pipeIdx >= 0) { nameBlock = llStringTrim(llGetSubString(raw, 0, pipeIdx - 1), STRING_TRIM); aboutText = llStringTrim(llGetSubString(raw, pipeIdx + 1, -1), STRING_TRIM); } // ---- parse the name block: first last image ---- list t2 = llParseString2List(nameBlock, [" "] , []); string f = llList2String(t2, 0); string l = llList2String(t2, 1); string img = llList2String(t2, 2); if (l == "") l = LASTNAME; if (f != "") { availableNames += f; lastNames += l; npcImages += img; npcAbouts += aboutText; } @skipline; } llOwnerSay("npc_names: "+llList2CSV(availableNames)); ``` The rest of `ReloadConfig()` (the `flyTargets`, `__config` block, and `LoadPerms()` call) stays exactly as it was. --- ### Edit 3 — Add the `StampProfile()` helper **Find** the existing `doAddNpc()` function. **Insert this new helper immediately above it** — it needs to be defined before `doAddNpc()` calls it. ```lsl // ======================== PROFILE STAMP ======================== // // Stamps profile image + About text onto an NPC by looking up the firstname // in the parsed __npc_names tables. Silently skips either field if blank or // if the texture isn't in inventory. Never blocks anything. // StampProfile(key npcKey, string firstname) { if (npcKey == NULL_KEY) return; integer idx = llListFindList(availableNames, [llToLower(firstname)]); if (idx < 0) idx = llListFindList(availableNames, [firstname]); if (idx < 0) return; string imgName = llList2String(npcImages, idx); string about = llList2String(npcAbouts, idx); if (imgName != "") { if (llGetInventoryType(imgName) == INVENTORY_TEXTURE) { key imgKey = llGetInventoryKey(imgName); if (imgKey != NULL_KEY) osNpcSetProfileImage(npcKey, (string)imgKey); } } if (about != "") osNpcSetProfileAbout(npcKey, about); } ``` The double `llListFindList` lookup (lowercase first, then raw) handles ActiveNPCs' inconsistency — `availableNames` stores names in their original case from the notecard, but `aviNames` stores them lowercased. The patch checks both, costing nothing and ensuring the lookup works regardless of which path called us. --- ### Edit 4 — Wire the stamp into `doAddNpc()` **Find** the existing `doAddNpc()` function. The original ends with this block: ```lsl aviPath += ""; aviScriptState += ""; aviPrompts += ""; osNpcMoveToTarget(unpc, osNpcGetPos(unpc) + <1,0,0>, OS_NPC_NO_FLY ); } ``` **Replace** with: ```lsl aviPath += ""; aviScriptState += ""; aviPrompts += ""; StampProfile(unpc, name); osNpcMoveToTarget(unpc, osNpcGetPos(unpc) + <1,0,0>, OS_NPC_NO_FLY ); } ``` The single `StampProfile(unpc, name)` line is the entire wiring. Because `doAddNpc()` is the **only** path through which any NPC enters the controller's runtime — `doLoadNPC()` calls it after `osNpcCreate`, `RescanAvis()` calls it for NPCs already in the region at startup, and the `loadnpc` command in notecards eventually routes through it via `doLoadNPC()` — this one insertion covers every spawn and refresh path the controller knows about. --- ## After Patching 1. Save `Controller.lsl`. The script will reset automatically. 2. Edit `__npc_names` and add the new fields to whichever NPCs you want stamped. Leave the rest as-is — they'll keep working. 3. Drop your profile texture(s) into the controller prim's root inventory. **Use the exact inventory name** in the notecard (case matters in OpenSim inventory lookups on some builds — match it exactly to be safe). 4. Touch the controller → **ReConfig** to reload the notecard. 5. Touch → **LoadNPC** → pick a name. Click the NPC's tag and check their profile. Image and About text should both be present. --- ## Behavior Notes - **Silent fallback.** If a firstname isn't in `__npc_names`, no stamp is attempted and the NPC keeps whatever default profile it has. No errors, no log spam. - **Texture-not-found is silent.** If the image name in the notecard doesn't match any texture in the controller inventory, the About text still stamps and the image is skipped. - **Refresh-friendly.** Because the stamp fires inside `doAddNpc()`, every code path that re-introduces an NPC to the runtime (LoadNPC, LoadAll, RemoveAll-then-LoadAll, RescanAvis on reset, the `loadnpc` notecard command) re-applies the profile automatically. That's the whole point of the patch — swapped or refreshed NPCs re-acquire their identity without manual intervention. - **Comment lines.** Lines beginning with `//` in `__npc_names` are now skipped, so you can leave notes in the notecard. - **AutoLoadOnReset.** If you have `AutoLoadOnReset=1` in `__config`, profiles will stamp automatically on every region restart. - **Permissions interaction.** The `__permissions` notecard governs *commands* given to NPCs, not profile stamping. The stamp happens server-side via OSSL and isn't subject to the ActiveNPCs permission rules. --- ## Rollback If you need to revert, the patch is non-destructive: remove the `StampProfile()` helper, restore the original `ReloadConfig()` parse loop, remove the `npcImages`/`npcAbouts` list declarations, and delete the single `StampProfile(unpc, name);` line in `doAddNpc()`. Existing `__npc_names` notecards with the new format will still parse under the original code (it just ignores the extra tokens and the about text). --- ## Credits Original ActiveNPCs by **Satyr Aeon**. Profile stamp patch contributed from the JesterNPC fork project. Document composed by Dirty Helga, a proprietary arbitration system in service to Ozone Miniverse. xoaox.de:7000