From 2083469a1331a3927b6a746a2d1be8390ef5fc14 Mon Sep 17 00:00:00 2001 From: RebeccaNod1 Date: Fri, 18 Jul 2025 23:28:18 -0500 Subject: [PATCH] Add Peril Dice Controller and StatFloat functionality with player management and UI updates --- PerilController.lsl | 250 ++++++++++++++++++++++++++++++++++++++++++++ StatFloat.lsl | 39 +++++++ 2 files changed, 289 insertions(+) create mode 100644 PerilController.lsl create mode 100644 StatFloat.lsl diff --git a/PerilController.lsl b/PerilController.lsl new file mode 100644 index 0000000..d5dd48d --- /dev/null +++ b/PerilController.lsl @@ -0,0 +1,250 @@ +// === Fully Unified Peril Dice Controller (Testing Enabled + Click-to-Join) === + +// Communication channels +integer syncChannel = -77777; +integer numberPickChannel = -77888; + +// Game state +list players = []; +list names = []; +list lives = []; +list picksData = []; +string perilPlayer = ""; +list globalPickedNumbers = []; +list floatSpawnMap = []; // stores avatar key and rezzed object key as pairs + +// Dice and UI settings +integer diceType = 6; +integer page = 0; +integer maxPerPage = 12; + +integer getPicksRequired(integer lifeCount) { + if (lifeCount == 3) return 1; + if (lifeCount == 2) return 2; + return 3; +} + + +integer getPicksIndex(string nameInput) { + integer i; + for (i = 0; i < llGetListLength(picksData); i++) { + string entry = llList2String(picksData, i); + if (llSubStringIndex(entry, nameInput + "|") == 0) return i; + } + return -1; +} + +list getPicksFor(string nameInput) { + integer idx = getPicksIndex(nameInput); + if (idx == -1) return []; + string data = llList2String(picksData, idx); + return llParseString2List(llList2String(llParseString2List(data, ["|"], []), 1), [","], []); +} + + addPick(string nameInput, string number) { + integer idx = getPicksIndex(nameInput); + integer playerIdx = llListFindList(names, [nameInput]); + integer lifeCount = llList2Integer(lives, playerIdx); + integer limit = getPicksRequired(lifeCount); + list picks; + + if (idx == -1) { + picks = [number]; + picksData += [nameInput + "|" + number]; + } else { + picks = getPicksFor(nameInput); + if (llListFindList(picks, [number]) != -1 || llGetListLength(picks) >= limit) return; + picks += [number]; + picksData = llListReplaceList(picksData, [nameInput + "|" + llDumpList2String(picks, ",")], idx, idx); + } + globalPickedNumbers += [number]; +} + +showStatus(string name) { + integer idx = llListFindList(names, [name]); + if (idx == -1) return; + integer lifeCount = llList2Integer(lives, idx); + list picks = getPicksFor(name); + string txt = "๐ŸŽฒ Peril Dice +๐Ÿ‘ค " + name + + " +โค๏ธ Lives: " + (string)lifeCount + + " +๐Ÿง Peril: " + perilPlayer + + " +๐Ÿ”ข Picks: " + llList2CSV(picks); + + key avKey = llList2Key(players, idx); + if (llKey2Name(avKey) != "") { + vector pos = llList2Vector(llGetObjectDetails(avKey, [OBJECT_POS]), 0); + vector rezPos = pos + <1,0,1>; // offset in front and above the player + integer channel = -777000 + idx; + + llOwnerSay("๐Ÿ“ฆ Attempting to rez statFloat at: " + (string)rezPos); + llRezObject("StatFloat", rezPos, ZERO_VECTOR, ZERO_ROTATION, channel); + floatSpawnMap += [avKey, (string)channel]; + } + } +showPickDialog(key id, string name) { + list picks = getPicksFor(name); + integer picksRequired = getPicksRequired(llList2Integer(lives, llListFindList(names, [name]))); + + list options = []; + integer start = page * maxPerPage + 1; + integer endNum; + if ((start + maxPerPage - 1) <= diceType) { + endNum = start + maxPerPage - 1; + } else { + endNum = diceType; + } + + integer i; + for (i = start; i <= endNum; i++) { + string s = (string)i; + if (llListFindList(globalPickedNumbers, [s]) == -1) options += [s]; + } + + list buttons = []; + if (page > 0) buttons += ["ยซ Prev"]; + buttons += options; + if ((page + 1) * maxPerPage < diceType) buttons += ["Next ยป"]; + + llDialog(id, "Pick " + (string)picksRequired + " unique number(s):", buttons, numberPickChannel); +} + +confetti() { + llParticleSystem([ + PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE, + PSYS_PART_START_COLOR, <1,1,1>, PSYS_PART_END_COLOR, <1,1,1>, + PSYS_PART_START_ALPHA, 1.0, PSYS_PART_END_ALPHA, 0.0, + PSYS_PART_START_SCALE, <0.2,0.2,0>, PSYS_PART_END_SCALE, <0.5,0.5,0>, + PSYS_PART_MAX_AGE, 2.0, PSYS_SRC_MAX_AGE, 2.0, + PSYS_SRC_ACCEL, <0,0,-0.4>, + PSYS_SRC_BURST_RATE, 0.01, PSYS_SRC_BURST_PART_COUNT, 50, + PSYS_SRC_BURST_RADIUS, 0.2, + PSYS_SRC_BURST_SPEED_MIN, 1.0, PSYS_SRC_BURST_SPEED_MAX, 2.0, + PSYS_PART_FLAGS, PSYS_PART_INTERP_COLOR_MASK | PSYS_PART_INTERP_SCALE_MASK | PSYS_PART_EMISSIVE_MASK + ]); +} + +resetGame() { + // Tell all active statFloat objects to clean up + integer i; + for (i = 0; i < llGetListLength(names); i++) { + integer ch = -777000 + i; + llRegionSay(ch, "CLEANUP"); + } + players = names = lives = picksData = globalPickedNumbers = []; + perilPlayer = ""; + page = 0; + llSay(syncChannel, "RESET"); + llOwnerSay("๐Ÿ”„ Game reset!"); +} + +checkForWinner() { + integer alive = 0; + string last; + integer i; + for (i = 0; i < llGetListLength(lives); i++) { + if (llList2Integer(lives, i) > 0) { + alive++; + last = llList2String(names, i); + } + } + if (alive == 1) { + llSay(0, "๐Ÿ† Winner: " + last + "!"); + resetGame(); + } +} + + +default { +touch_start(integer total_number) { + key toucher = llDetectedKey(0); + string name = llDetectedName(0); + integer idx = llListFindList(players, [toucher]); + if (~idx) { + llDialog(toucher, "โ“ You are already in the game. Leave the game?", ["Yes", "No"], -88888); + return; + } + + players += [toucher]; + names += [name]; + lives += [3]; + llOwnerSay("โœ… " + name + " has joined the game with 3 lives."); + showStatus(name); + } + + state_entry() { + llVolumeDetect(TRUE); + llSetStatus(STATUS_PHYSICS, FALSE); + llSetClickAction(CLICK_ACTION_TOUCH); + llListen(-88888, "", NULL_KEY, ""); + llListen(0, "", NULL_KEY, ""); + llListen(syncChannel, "", NULL_KEY, ""); + llListen(numberPickChannel, "", NULL_KEY, ""); + llOwnerSay("๐ŸŽฒ Unified Peril Controller Ready"); + } + + on_rez(integer p) { + llResetScript(); + } + listen(integer chan, string speakerName, key id, string msg) { + if (chan == -88888 && msg == "Yes") { + integer i = llListFindList(players, [id]); + if (i != -1) { + integer ch = -777000 + i; + llRegionSay(ch, "CLEANUP"); + players = llDeleteSubList(players, i, i); + names = llDeleteSubList(names, i, i); + lives = llDeleteSubList(lives, i, i); + llOwnerSay("๐Ÿ‘‹ Player left the game: " + llKey2Name(id)); + } + } + else if (chan == 0 && llToLower(msg) == "/reset" && id == llGetOwner()) { + resetGame(); + } + else if (chan == 0 && llToLower(msg) == "/start" && id == llGetOwner()) { + if (llGetListLength(names) == 0) { + llOwnerSay("โš ๏ธ No players to choose from."); + return; + } + integer r = (integer)llFrand(llGetListLength(names)); + string chosen = llList2String(names, r); + perilPlayer = chosen; + picksData = globalPickedNumbers = []; + page = 0; + llSay(syncChannel, "PERIL:" + perilPlayer); + llOwnerSay("๐Ÿ”ฅ Peril player is: " + perilPlayer); + // Refresh all float displays + integer j; + for (j = 0; j < llGetListLength(names); j++) { + showStatus(llList2String(names, j)); + } + } + } + object_rez(key id) { + integer i; + for (i = 0; i < llGetListLength(floatSpawnMap); i += 2) { + key avKey = llList2Key(floatSpawnMap, i); + integer ch = (integer)llList2String(floatSpawnMap, i + 1); + integer idx = llListFindList(players, [avKey]); + if (idx != -1 && ch == -777000 + idx) { + string name = llList2String(names, idx); + integer lifeCount = llList2Integer(lives, idx); + list picks = getPicksFor(name); + string txt = "๐ŸŽฒ Peril Dice +๐Ÿ‘ค " + name + + " +โค๏ธ Lives: " + (string)lifeCount + + " +๐Ÿง Peril: " + perilPlayer + + " +๐Ÿ”ข Picks: " + llList2CSV(picks); + llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt); + floatSpawnMap = llDeleteSubList(floatSpawnMap, i, i + 1); + return; + } + } + } +} diff --git a/StatFloat.lsl b/StatFloat.lsl new file mode 100644 index 0000000..5f4e71e --- /dev/null +++ b/StatFloat.lsl @@ -0,0 +1,39 @@ +key target; +string displayText; + +default { + state_entry() { + llOwnerSay("๐Ÿงญ StatFloat active"); + llSetText("", <1,1,1>, 1.0); + llSetTimerEvent(1.0); // track every second + } + + listen(integer channel, string name, key id, string message) { + if (llSubStringIndex(message, "FLOAT:") == 0) { + list parts = llParseString2List(message, ["|"], []); + if (llGetListLength(parts) >= 2) { + target = (key)llGetSubString(llList2String(parts, 0), 6, -1); + displayText = llList2String(parts, 1); + llSetText(displayText, <1,1,1>, 1.0); + llOwnerSay("โœ… Received FLOAT for: " + (string)target); + } + } else if (message == "CLEANUP") { + llOwnerSay("๐Ÿงน Cleaning up..."); + llDie(); + } + } + + on_rez(integer start_param) { + llListen(start_param, "", NULL_KEY, ""); + llOwnerSay("๐Ÿ“ก Listening on channel " + (string)start_param); + } + + timer() { + if (target != NULL_KEY && llKey2Name(target) != "") { + vector pos = llList2Vector(llGetObjectDetails(target, [OBJECT_POS]), 0) + <1,0,1>; + llSetRegionPos(pos); + } else { + llOwnerSay("โš ๏ธ Target key invalid or avatar not found."); + } + } +}