diff --git a/PerilController.lsl b/PerilController.lsl index d5dd48d..29151ea 100644 --- a/PerilController.lsl +++ b/PerilController.lsl @@ -1,4 +1,4 @@ -// === Fully Unified Peril Dice Controller (Testing Enabled + Click-to-Join) === +// === Fully Unified Peril Dice Controller (Sequential Picks + Dynamic Dice Pool) === // Communication channels integer syncChannel = -77777; @@ -12,19 +12,27 @@ list picksData = []; string perilPlayer = ""; list globalPickedNumbers = []; list floatSpawnMap = []; // stores avatar key and rezzed object key as pairs +string pendingUpdateName = ""; // for delayed float updates + +// Sequential pick management +list pickQueue = []; +integer currentPickerIdx = -1; // Dice and UI settings integer diceType = 6; integer page = 0; integer maxPerPage = 12; +integer getDiceType(integer playerCount) { + return ((playerCount + 1) / 2) * 6; +} + 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++) { @@ -41,50 +49,38 @@ list getPicksFor(string nameInput) { return llParseString2List(llList2String(llParseString2List(data, ["|"], []), 1), [","], []); } - addPick(string nameInput, string number) { +integer addPick(string nameInput, string number) { integer idx = getPicksIndex(nameInput); integer playerIdx = llListFindList(names, [nameInput]); + integer perilIdx = llListFindList(names, [perilPlayer]); + list picks; integer lifeCount = llList2Integer(lives, playerIdx); integer limit = getPicksRequired(lifeCount); - list picks; + if (perilIdx != -1) { + limit = getPicksRequired(llList2Integer(lives, perilIdx)); + } if (idx == -1) { picks = [number]; picksData += [nameInput + "|" + number]; } else { picks = getPicksFor(nameInput); - if (llListFindList(picks, [number]) != -1 || llGetListLength(picks) >= limit) return; + if (llListFindList(picks, [number]) != -1 || llGetListLength(picks) >= limit) { + return 0; + } picks += [number]; picksData = llListReplaceList(picksData, [nameInput + "|" + llDumpList2String(picks, ",")], idx, idx); } globalPickedNumbers += [number]; + integer nameIdx = llListFindList(names, [nameInput]); + pendingUpdateName = nameInput; + llSetTimerEvent(0.3); + updateFloater(nameInput); + return 0; } -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]))); @@ -112,33 +108,56 @@ showPickDialog(key id, string name) { 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 - ]); +integer startNextPick() { + if (currentPickerIdx >= llGetListLength(pickQueue)) { + llOwnerSay("โœ… All players have finished picking."); + + // Prompt peril player to roll + integer pidx = llListFindList(names, [perilPlayer]); + if (pidx != -1) { + key pkey = llList2Key(players, pidx); + llDialog(pkey, "๐ŸŽฒ All picks are in! Ready to roll the dice?", ["Roll"], syncChannel); + } + return 0; + } + string currentName = llList2String(pickQueue, currentPickerIdx); + integer perilIdx = llListFindList(names, [perilPlayer]); + if (perilIdx == -1) return 0; + integer playerIdx = llListFindList(names, [currentName]); + if (playerIdx == -1) { + llOwnerSay("โš ๏ธ Could not find player key for " + currentName); + return 0; + } + key avKey = llList2Key(players, playerIdx); + showPickDialog(avKey, currentName); + return 0; } -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!"); +showStatus(string name) { + integer idx = llListFindList(names, [name]); + if (idx == -1) return; + key avKey = llList2Key(players, idx); + + // Check if already in floatSpawnMap + if (llListFindList(floatSpawnMap, [avKey]) != -1) return; + + integer lifeCount = llList2Integer(lives, idx); + list picks = getPicksFor(name); + string txt = "๐ŸŽฒ Peril Dice +๐Ÿ‘ค " + name + + " +โค๏ธ Lives: " + (string)lifeCount + + " +๐Ÿง Peril: " + perilPlayer + + " +๐Ÿ”ข Picks: " + llList2CSV(picks); + + vector pos = llList2Vector(llGetObjectDetails(avKey, [OBJECT_POS]), 0); + vector rezPos = pos + <1,0,1>; + integer channel = -777000 + idx; + llOwnerSay("๐Ÿ“ฆ Attempting to rez statFloat at: " + (string)rezPos); + llRezObject("StatFloat", rezPos, ZERO_VECTOR, ZERO_ROTATION, channel); + floatSpawnMap += [avKey, (string)channel]; } checkForWinner() { @@ -157,94 +176,205 @@ checkForWinner() { } } +updateFloater(string name) { + integer idx = llListFindList(names, [name]); + if (idx == -1) return; + key avKey = llList2Key(players, idx); + integer ch = -777000 + 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); +} + +resetGame() { + 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!"); +} + +integer rollDice() { + return 1 + (integer)llFrand(diceType); +} 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); +timer() { + if (pendingUpdateName != "") { + updateFloater(pendingUpdateName); + pendingUpdateName = ""; } +} - 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 +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; - } + llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt); + floatSpawnMap = llDeleteSubList(floatSpawnMap, i, i + 1); + return; } } } + +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"); +} + +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); +} + +listen(integer chan, string speakerName, key id, string msg) { + if (chan == syncChannel && msg == "Roll" && llKey2Name(id) != "") { + if (llList2String(names, llListFindList(players, [id])) != perilPlayer) return; + string newPeril = ""; + integer result = rollDice(); + string resultStr = (string)result; + llSay(0, "๐ŸŽฒ " + perilPlayer + " rolled a " + resultStr + "!"); + + integer matched = FALSE; + integer i; + for (i = 0; i < llGetListLength(names); i++) { + string pname = llList2String(names, i); + list picks = getPicksFor(pname); + if (llListFindList(picks, [resultStr]) != -1) { + matched = TRUE; + if (pname != perilPlayer) { + newPeril = pname; + return; + } + } + } + if (matched && newPeril != "") { + llSay(0, "๐ŸŽฏ " + newPeril + " matched the roll and becomes the new peril player!"); + perilPlayer = newPeril; + } else { + integer pidx = llListFindList(names, [perilPlayer]); + if (pidx != -1) { + integer currentLives = llList2Integer(lives, pidx); + lives = llListReplaceList(lives, [currentLives - 1], pidx, pidx); + llSay(0, "๐Ÿ’€ " + perilPlayer + " was hit and lost a life!"); + showStatus(perilPlayer); + } + } + + checkForWinner(); + } + + else if (chan == numberPickChannel) { + string currentName = llList2String(pickQueue, currentPickerIdx); + integer playerIdx = llListFindList(names, [currentName]); + if (playerIdx == -1) return; + key playerKey = llList2Key(players, playerIdx); + + if (id != playerKey) return; // Not their turn + + if (msg == "Next ยป") { + page++; + showPickDialog(id, currentName); + } else if (msg == "ยซ Prev") { + if (page > 0) page--; + showPickDialog(id, currentName); + } else { + addPick(currentName, msg); + list picks = getPicksFor(currentName); + integer perilIdx = llListFindList(names, [perilPlayer]); + if (perilIdx == -1) return; + integer required = getPicksRequired(llList2Integer(lives, perilIdx)); + if (llGetListLength(picks) >= required) { + currentPickerIdx++; + } + page = 0; + startNextPick(); + } + } + else 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; + diceType = getDiceType(llGetListLength(names)); + + pickQueue = names; + currentPickerIdx = 0; + + llSay(syncChannel, "PERIL:" + perilPlayer); + llOwnerSay("๐Ÿ”ฅ Peril player is: " + perilPlayer); + + integer j; + for (j = 0; j < llGetListLength(names); j++) { + showStatus(llList2String(names, j)); + } + + startNextPick(); + } + } +} // end of default state +// end of script diff --git a/StatFloat.lsl b/StatFloat.lsl index 5f4e71e..d368d22 100644 --- a/StatFloat.lsl +++ b/StatFloat.lsl @@ -2,13 +2,20 @@ key target; string displayText; default { + on_rez(integer start_param) { + llOwnerSay("๐Ÿงช on_rez triggered with start_param: " + (string)start_param); + llListen(start_param, "", NULL_KEY, ""); + llOwnerSay("๐Ÿ“ก Listening on channel (on_rez): " + (string)start_param); + } + state_entry() { llOwnerSay("๐Ÿงญ StatFloat active"); - llSetText("", <1,1,1>, 1.0); - llSetTimerEvent(1.0); // track every second + llSetText("Waiting...", <1,1,1>, 1.0); + llSetTimerEvent(1.0); } listen(integer channel, string name, key id, string message) { + llOwnerSay("๐Ÿ”‰ Heard on channel " + (string)channel + ": " + message); if (llSubStringIndex(message, "FLOAT:") == 0) { list parts = llParseString2List(message, ["|"], []); if (llGetListLength(parts) >= 2) { @@ -23,17 +30,10 @@ default { } } - 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."); } } }