✅ Today's Completed Tasks Summary

🎯 Refactored the Game Architecture
We modularized the original Peril Dice controller into separate cohesive components:

Main Peril Dice Controller

Handles core logic: player join/leave, peril selection, game sync, and communication routing.

Broadcasts updates via MSG_SYNC_GAME_STATE after major state changes.

Applies llSleep(0.2) to improve message delivery reliability.

Dialog Handler Module

Manages the number-picking dialog.

Tracks player picks and updates picksData accordingly.

Sends state sync after every valid pick.

Fixed pick serialization bugs and ensured updates trigger correctly.

Float Rezzer Module

Registers player data and rezzes a StatFloat object beside each participant.

Applies llSetObjectDesc(name); before rezzing for easier identification.

On update, broadcasts llRegionSay messages including current lives, peril, and picks.

StatFloat (HUD Script)

Listens on assigned channel to receive floating text updates.

Parses synced game state from link_message to extract lives, picks, and peril data.

Dynamically follows the avatar.

Enhanced parsing reliability and fixed index mapping for correct player display.

Roll and Confetti Module

Performs dice rolls and checks for matches.

Transfers peril role on match or removes a life otherwise.

Triggers confetti on a miss and updates floats for all players.

🛠 Debugging and Enhancements
Added llOwnerSay debug statements across all modules.

Resolved multiple name not defined within scope (nndws) issues.

Verified and cleaned up serialized game data formats across modules.

Ensured consistent use of player indexing and life count mapping.
This commit is contained in:
RebeccaNod1 2025-07-21 03:22:32 -05:00 committed by GitHub
parent f26c0d2853
commit f895f9851c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 470 additions and 355 deletions

120
DialogHandler.lsl Normal file
View file

@ -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));
}
}
}
}
}

43
FloaterManager.lsl Normal file
View file

@ -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");
}
}
}

View file

@ -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

88
RollConfettiModule.lsl Normal file
View file

@ -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);
}
}
}

View file

@ -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>;

77
StatFloatRezzer.lsl Normal file
View file

@ -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);
}
}
}
}