diff --git a/DialogHandler.lsl b/DialogHandler.lsl new file mode 100644 index 0000000..0820d9a --- /dev/null +++ b/DialogHandler.lsl @@ -0,0 +1,120 @@ +// === Dialog Handler Module === +integer MSG_SHOW_DIALOG = 101; +integer MSG_ROLL_RESULT = 102; +integer MSG_UPDATE_FLOAT = 103; +integer MSG_CLEANUP_FLOAT = 104; +integer MSG_SYNC_GAME_STATE = 107; + +integer numberPickChannel = -77888; +integer diceType = 6; +integer maxPerPage = 12; +integer page = 0; + +integer getPicksRequired(integer lifeCount) { + if (lifeCount == 3) return 1; + if (lifeCount == 2) return 2; + return 3; +} + +list globalPickedNumbers = []; +list names = []; +list lives = []; +list players = []; +list picksData = []; +string perilPlayer = ""; +list pickQueue = []; +integer currentPickerIdx = 0; + +list getPicksFor(string nameInput) { + integer i; + for (i = 0; i < llGetListLength(picksData); i++) { + string entry = llList2String(picksData, i); + if (llSubStringIndex(entry, nameInput + "|") == 0) { + list parts = llParseString2List(entry, ["|"], []); + return llParseString2List(llList2String(parts, 1), [","], []); + } + } + return []; +} + +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); +} + +default { + state_entry() { + llListen(numberPickChannel, "", NULL_KEY, ""); + } + + link_message(integer sender, integer num, string str, key id) { + if (num == MSG_SHOW_DIALOG) { + page = 0; + showPickDialog(id, str); + } + } + + listen(integer channel, string name, key id, string msg) { + if (channel == numberPickChannel) { + string currentName = llList2String(pickQueue, currentPickerIdx); + integer playerIdx = llListFindList(names, [currentName]); + if (playerIdx == -1) return; + key playerKey = llList2Key(players, playerIdx); + if (id != playerKey) return; + + if (msg == "Next ยป") { + page++; + showPickDialog(id, currentName); + } else if (msg == "ยซ Prev") { + if (page > 0) page--; + showPickDialog(id, currentName); + } else { + integer idx = llListFindList(names, [currentName]); + integer perilIdx = llListFindList(names, [perilPlayer]); + integer limit = getPicksRequired(llList2Integer(lives, perilIdx)); + list picks = getPicksFor(currentName); + + if (llListFindList(picks, [msg]) != -1 || llGetListLength(picks) >= limit) return; + + picks += [msg]; + picksData = llListReplaceList(picksData, [currentName + "|" + llDumpList2String(picks, ",")], idx, idx); + globalPickedNumbers += [msg]; + + string sync = llList2CSV(lives) + "~" + llDumpList2String(picksData, "~") + "~" + perilPlayer + "~" + llList2CSV(names); + llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, sync, NULL_KEY); + llSleep(0.2); + + if (llGetListLength(picks) >= limit) { + currentPickerIdx++; + } + page = 0; + if (currentPickerIdx < llGetListLength(pickQueue)) { + showPickDialog(llList2Key(players, llListFindList(names, [llList2String(pickQueue, currentPickerIdx)])), llList2String(pickQueue, currentPickerIdx)); + } + } + } +} + +} diff --git a/FloaterManager.lsl b/FloaterManager.lsl new file mode 100644 index 0000000..200e162 --- /dev/null +++ b/FloaterManager.lsl @@ -0,0 +1,43 @@ +// === Floater Manager Module === +integer MSG_SHOW_DIALOG = 101; +integer MSG_ROLL_RESULT = 102; +integer MSG_UPDATE_FLOAT = 103; +integer MSG_CLEANUP_FLOAT = 104; + +list players = []; +list names = []; +list lives = []; +list picksData = []; +string perilPlayer = ""; + +list getPicksFor(string nameInput) { + integer i; + for (i = 0; i < llGetListLength(picksData); i++) { + string entry = llList2String(picksData, i); + if (llSubStringIndex(entry, nameInput + "|") == 0) { + list parts = llParseString2List(entry, ["|"], []); + return llParseString2List(llList2String(parts, 1), [","], []); + } + } + return []; +} + +default { + link_message(integer sender, integer num, string str, key id) { + if (num == MSG_UPDATE_FLOAT) { + string name = str; + 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\n๐Ÿ‘ค " + name + "\nโค๏ธ Lives: " + (string)lifeCount + "\n๐Ÿง Peril: " + perilPlayer + "\n๐Ÿ”ข Picks: " + llList2CSV(picks); + llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt); + } + else if (num == MSG_CLEANUP_FLOAT) { + integer ch = (integer)str; + llRegionSay(ch, "CLEANUP"); + } + } +} diff --git a/PerilController.lsl b/PerilController.lsl index 6c36989..cb5c2ef 100644 --- a/PerilController.lsl +++ b/PerilController.lsl @@ -1,27 +1,26 @@ -// === Fully Unified Peril Dice Controller (Sequential Picks + Dynamic Dice Pool) === +// === Main Peril Dice Controller (Modular) === -// 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 -string pendingUpdateName = ""; // for delayed float updates +list picksData = []; -// Sequential pick management list pickQueue = []; -integer currentPickerIdx = -1; - -// Dice and UI settings +integer currentPickerIdx = 0; integer diceType = 6; -integer page = 0; -integer maxPerPage = 12; + +integer MSG_SHOW_DIALOG = 101; +integer MSG_ROLL_RESULT = 102; +integer MSG_UPDATE_FLOAT = 103; +integer MSG_CLEANUP_FLOAT = 104; +integer MSG_REZ_FLOAT = 105; +integer MSG_REGISTER_PLAYER = 106; +integer MSG_SYNC_GAME_STATE = 107; integer getDiceType(integer playerCount) { return ((playerCount + 1) / 2) * 6; @@ -33,364 +32,108 @@ integer getPicksRequired(integer lifeCount) { 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), [","], []); -} - -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); - 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 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; -} - - - -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); -} - -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; -} - -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]; -} - -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 - ]); -} - -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(); - } -} - -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); +string serializeGameState() { + return llList2CSV(lives) + "~" + llList2CSV(picksData) + "~" + perilPlayer + "~" + llList2CSV(names); } resetGame() { + string gameSync; integer i; for (i = 0; i < llGetListLength(names); i++) { integer ch = -777000 + i; - llRegionSay(ch, "CLEANUP"); + llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY); } players = names = lives = picksData = globalPickedNumbers = []; perilPlayer = ""; - page = 0; + pickQueue = []; + currentPickerIdx = 0; llSay(syncChannel, "RESET"); llOwnerSay("๐Ÿ”„ Game reset!"); -} - -integer rollDice() { - return 1 + (integer)llFrand(diceType); + // defer game state sync until picks are populated + llSleep(0.2); } default { - -timer() { - if (pendingUpdateName != "") { - updateFloater(pendingUpdateName); - pendingUpdateName = ""; + state_entry() { + llListen(0, "", NULL_KEY, ""); + llListen(syncChannel, "", NULL_KEY, ""); + llListen(numberPickChannel, "", NULL_KEY, ""); + llListen(-88888, "", NULL_KEY, ""); + llOwnerSay("๐ŸŽฒ Modular Peril Controller Ready"); } -} -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); + touch_start(integer total_number) { + string gameSync; + 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."); + llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, name + "|" + (string)toucher, NULL_KEY); + llMessageLinked(LINK_SET, MSG_REZ_FLOAT, name, toucher); + llSleep(0.2); + gameSync = serializeGameState(); + llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, gameSync, NULL_KEY); + llSleep(0.2); + llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, name, toucher); + } + + listen(integer chan, string speakerName, key id, string msg) { + string gameSync; + if (chan == -88888 && msg == "Yes") { + integer i = llListFindList(players, [id]); + if (i != -1) { + integer ch = -777000 + i; + llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY); + players = llDeleteSubList(players, i, i); + names = llDeleteSubList(names, i, i); + lives = llDeleteSubList(lives, i, i); + picksData = llDeleteSubList(picksData, i, i); + globalPickedNumbers = llDeleteSubList(globalPickedNumbers, i, i); + llOwnerSay("๐Ÿ‘‹ Player left the game: " + llKey2Name(id)); + gameSync = serializeGameState(); + llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, gameSync, NULL_KEY); + llSleep(0.2); + } + } + 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)); + perilPlayer = llList2String(names, r); + picksData = globalPickedNumbers = []; + diceType = getDiceType(llGetListLength(names)); + pickQueue = names; + currentPickerIdx = 0; + + llSay(syncChannel, "PERIL:" + perilPlayer); + llOwnerSay("๐Ÿ”ฅ Peril player is: " + perilPlayer); + gameSync = serializeGameState(); + llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, gameSync, NULL_KEY); + llSleep(0.2); + + integer j; + for (j = 0; j < llGetListLength(names); j++) { + llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, llList2String(names, j), llList2Key(players, j)); + } + + string firstName = llList2String(pickQueue, currentPickerIdx); + key firstKey = llList2Key(players, llListFindList(names, [firstName])); + llMessageLinked(LINK_SET, MSG_SHOW_DIALOG, firstName, firstKey); + gameSync = serializeGameState(); + llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, gameSync, NULL_KEY); + } } } - -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!"); - confetti(); - 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/RollConfettiModule.lsl b/RollConfettiModule.lsl new file mode 100644 index 0000000..324a0f8 --- /dev/null +++ b/RollConfettiModule.lsl @@ -0,0 +1,88 @@ +// === Roll and Confetti Module === +integer MSG_SHOW_DIALOG = 101; +integer MSG_ROLL_RESULT = 102; +integer MSG_UPDATE_FLOAT = 103; +integer MSG_CLEANUP_FLOAT = 104; +integer MSG_REZ_FLOAT = 105; +integer MSG_SYNC_GAME_STATE = 107; + +list names = []; +list lives = []; +list picksData = []; +string perilPlayer = ""; + +list getPicksFor(string nameInput) { + integer i; + for (i = 0; i < llGetListLength(picksData); i++) { + string entry = llList2String(picksData, i); + if (llSubStringIndex(entry, nameInput + "|") == 0) { + list parts = llParseString2List(entry, ["|"], []); + return llParseString2List(llList2String(parts, 1), [","], []); + } + } + return []; +} + +integer rollDice(integer diceType) { + return 1 + (integer)llFrand(diceType); +} + +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 + ]); +} + +default { + link_message(integer sender, integer num, string str, key id) { + if (num == MSG_ROLL_RESULT) { + integer diceType = (integer)str; + integer result = rollDice(diceType); + string resultStr = (string)result; + llSay(0, "๐ŸŽฒ " + perilPlayer + " rolled a " + resultStr + "!"); + + string newPeril = ""; + 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 == "") { + newPeril = pname; + } + } + } + + if (matched && newPeril != "") { + llSay(0, "๐ŸŽฏ " + newPeril + " matched the roll and becomes the new peril player!"); + perilPlayer = newPeril; + llMessageLinked(LINK_SET, MSG_REZ_FLOAT, newPeril, NULL_KEY); + } 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!"); + confetti(); + llMessageLinked(LINK_SET, MSG_REZ_FLOAT, perilPlayer, NULL_KEY); + } + } + + // Send updated game state to floats + string gameSync = llList2CSV(lives) + "~" + llList2CSV(picksData) + "~" + perilPlayer; + llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, gameSync, NULL_KEY); + llSleep(0.2); + } + } +} diff --git a/StatFloat.lsl b/StatFloat.lsl index d6e2044..84a1406 100644 --- a/StatFloat.lsl +++ b/StatFloat.lsl @@ -1,5 +1,28 @@ +// === StatFloat Enhanced === key target; string displayText; +string myName; + +integer MSG_SYNC_GAME_STATE = 107; + +list lives; +list picksData; +string perilPlayer; +list names; + +list getPicksFor(string nameInput) { + integer i; + for (i = 0; i < llGetListLength(picksData); i++) { + string entry = llList2String(picksData, i); + if (llSubStringIndex(entry, nameInput + "|") == 0) { + list parts = llParseString2List(entry, ["|"], []); + if (llGetListLength(parts) >= 2) { + return llParseString2List(llList2String(parts, 1), [","], []); + } + } + } + return []; +} default { state_entry() { @@ -9,7 +32,8 @@ default { on_rez(integer start_param) { llListen(start_param, "", NULL_KEY, ""); - llOwnerSay("๐Ÿ“ก Listening on channel " + (string)start_param); + myName = llGetObjectDesc(); + llOwnerSay("๐Ÿ“ฑ Listening on channel " + (string)start_param + " for " + myName); } listen(integer channel, string name, key id, string message) { @@ -23,11 +47,31 @@ default { } } else if (message == "CLEANUP") { - llOwnerSay("๐Ÿงน Cleaning up..."); + llOwnerSay("๐ŸชŸ Cleaning up..."); llDie(); } } + link_message(integer sender, integer num, string str, key id) { + if (num == MSG_SYNC_GAME_STATE) { + list parts = llParseString2List(str, ["~"], []); + if (llGetListLength(parts) < 4) return; + lives = llParseString2List(llList2String(parts, 0), [","], []); + picksData = llParseString2List(llList2String(parts, 1), ["~"], []); + perilPlayer = llList2String(parts, 2); + names = llParseString2List(llList2String(parts, 3), [","], []); + + integer nameIdx = llListFindList(names, [myName]); + if (nameIdx == -1) return; + + list picks = getPicksFor(myName); + integer lifeCount = llList2Integer(lives, nameIdx); + + string txt = "๐ŸŽฒ Peril Dice\n๐Ÿ‘ค " + myName + "\nโค๏ธ Lives: " + (string)lifeCount + "\n๐Ÿข Peril: " + perilPlayer + "\n๐Ÿ”ข Picks: " + llList2CSV(picks); + llSetText(txt, <1,1,1>, 1.0); + } + } + timer() { if (target != NULL_KEY && llKey2Name(target) != "") { vector pos = llList2Vector(llGetObjectDetails(target, [OBJECT_POS]), 0) + <1,0,1>; diff --git a/StatFloatRezzer.lsl b/StatFloatRezzer.lsl new file mode 100644 index 0000000..6679aea --- /dev/null +++ b/StatFloatRezzer.lsl @@ -0,0 +1,77 @@ +// === Float Rezzer Module === + +integer MSG_SHOW_DIALOG = 101; +integer MSG_ROLL_RESULT = 102; +integer MSG_UPDATE_FLOAT = 103; +integer MSG_CLEANUP_FLOAT = 104; +integer MSG_REZ_FLOAT = 105; +integer MSG_REGISTER_PLAYER = 106; +integer MSG_SYNC_GAME_STATE = 107; + +list players = []; +list names = []; +list lives = []; +list picksData = []; +string perilPlayer = ""; + +list getPicksFor(string nameInput) { + integer i; + for (i = 0; i < llGetListLength(picksData); i++) { + string entry = llList2String(picksData, i); + if (llSubStringIndex(entry, nameInput + "|") == 0) { + list parts = llParseString2List(entry, ["|"], []); + return llParseString2List(llList2String(parts, 1), [","], []); + } + } + return []; +} + +default { + link_message(integer sender, integer num, string str, key id) { + if (num == MSG_REGISTER_PLAYER) { + list info = llParseString2List(str, ["|"], []); + names += [llList2String(info, 0)]; + players += [llList2Key(info, 1)]; + llOwnerSay("๐Ÿ“ Registered player for float rezzer: " + llList2String(info, 0)); + } + else if (num == MSG_REZ_FLOAT) { + string name = str; + integer idx = llListFindList(names, [name]); + if (idx == -1) return; + key avKey = llList2Key(players, idx); + vector pos = llList2Vector(llGetObjectDetails(avKey, [OBJECT_POS]), 0) + <1,0,1>; + integer ch = -777000 + idx; + llOwnerSay("๐Ÿ“ฆ Rezzing StatFloat for " + name + " at " + (string)pos); + llSetObjectDesc(name); // Set the description to the name before rez + llRezObject("StatFloat", pos, ZERO_VECTOR, ZERO_ROTATION, ch); + } + else if (num == MSG_SYNC_GAME_STATE) { + list parts = llParseString2List(str, ["~"], []); + lives = llCSV2List(llList2String(parts, 0)); + picksData = llCSV2List(llList2String(parts, 1)); + perilPlayer = llList2String(parts, 2); + } + else if (num == MSG_UPDATE_FLOAT) { + string name = str; + 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\n๐Ÿ‘ค " + name + "\nโค๏ธ Lives: " + (string)lifeCount + "\n๐Ÿง Peril: " + perilPlayer + "\n๐Ÿ”ข Picks: " + llList2CSV(picks); + llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt); + } + else if (num == MSG_CLEANUP_FLOAT) { + integer ch = (integer)str; + integer idx = ch - (-777000); + if (idx >= 0 && idx < llGetListLength(players)) { + llRegionSay(ch, "CLEANUP"); + players = llDeleteSubList(players, idx, idx); + names = llDeleteSubList(names, idx, idx); + lives = llDeleteSubList(lives, idx, idx); + picksData = llDeleteSubList(picksData, idx, idx); + } + } + } +}