✅ New Features Added

Sequential Player Picking Logic

Players now pick in order starting with the peril player.

Number of picks is determined by the peril player’s current lives.

Picks are tracked globally to prevent duplicates.

Dynamic Dice Type Adjustment

Dice size (diceType) now adjusts based on player count:

1–2 players = 6

3–4 players = 12

5–6 players = 18, etc.

Prompt for Dice Roll

After all players have made their picks, the peril player is prompted to roll via a dialog.

Dice Roll Resolution Logic

If another player matches the roll, they become the new peril player.

If no one matches or the peril player matches, the peril player loses a life.

🔄 Fixes & Improvements
Fixed syntax and scoping errors (resultStr, newPeril, and timer logic).

Prevented duplicate rezzing of StatFloat objects.

Restored and ensured 🔢 Picks: are shown on the floating stat displays.

Updated floaters dynamically after each pick using llSetTimerEvent.
This commit is contained in:
RebeccaNod1 2025-07-20 00:04:48 -05:00 committed by GitHub
parent af80271691
commit 8e69688bff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 271 additions and 141 deletions

View file

@ -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 // Communication channels
integer syncChannel = -77777; integer syncChannel = -77777;
@ -12,19 +12,27 @@ list picksData = [];
string perilPlayer = ""; string perilPlayer = "";
list globalPickedNumbers = []; list globalPickedNumbers = [];
list floatSpawnMap = []; // stores avatar key and rezzed object key as pairs 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 // Dice and UI settings
integer diceType = 6; integer diceType = 6;
integer page = 0; integer page = 0;
integer maxPerPage = 12; integer maxPerPage = 12;
integer getDiceType(integer playerCount) {
return ((playerCount + 1) / 2) * 6;
}
integer getPicksRequired(integer lifeCount) { integer getPicksRequired(integer lifeCount) {
if (lifeCount == 3) return 1; if (lifeCount == 3) return 1;
if (lifeCount == 2) return 2; if (lifeCount == 2) return 2;
return 3; return 3;
} }
integer getPicksIndex(string nameInput) { integer getPicksIndex(string nameInput) {
integer i; integer i;
for (i = 0; i < llGetListLength(picksData); i++) { for (i = 0; i < llGetListLength(picksData); i++) {
@ -41,50 +49,38 @@ list getPicksFor(string nameInput) {
return llParseString2List(llList2String(llParseString2List(data, ["|"], []), 1), [","], []); return llParseString2List(llList2String(llParseString2List(data, ["|"], []), 1), [","], []);
} }
addPick(string nameInput, string number) { integer addPick(string nameInput, string number) {
integer idx = getPicksIndex(nameInput); integer idx = getPicksIndex(nameInput);
integer playerIdx = llListFindList(names, [nameInput]); integer playerIdx = llListFindList(names, [nameInput]);
integer perilIdx = llListFindList(names, [perilPlayer]);
list picks;
integer lifeCount = llList2Integer(lives, playerIdx); integer lifeCount = llList2Integer(lives, playerIdx);
integer limit = getPicksRequired(lifeCount); integer limit = getPicksRequired(lifeCount);
list picks; if (perilIdx != -1) {
limit = getPicksRequired(llList2Integer(lives, perilIdx));
}
if (idx == -1) { if (idx == -1) {
picks = [number]; picks = [number];
picksData += [nameInput + "|" + number]; picksData += [nameInput + "|" + number];
} else { } else {
picks = getPicksFor(nameInput); picks = getPicksFor(nameInput);
if (llListFindList(picks, [number]) != -1 || llGetListLength(picks) >= limit) return; if (llListFindList(picks, [number]) != -1 || llGetListLength(picks) >= limit) {
return 0;
}
picks += [number]; picks += [number];
picksData = llListReplaceList(picksData, [nameInput + "|" + llDumpList2String(picks, ",")], idx, idx); picksData = llListReplaceList(picksData, [nameInput + "|" + llDumpList2String(picks, ",")], idx, idx);
} }
globalPickedNumbers += [number]; 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) { showPickDialog(key id, string name) {
list picks = getPicksFor(name); list picks = getPicksFor(name);
integer picksRequired = getPicksRequired(llList2Integer(lives, llListFindList(names, [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); llDialog(id, "Pick " + (string)picksRequired + " unique number(s):", buttons, numberPickChannel);
} }
confetti() { integer startNextPick() {
llParticleSystem([ if (currentPickerIdx >= llGetListLength(pickQueue)) {
PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE, llOwnerSay("✅ All players have finished picking.");
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, // Prompt peril player to roll
PSYS_PART_START_SCALE, <0.2,0.2,0>, PSYS_PART_END_SCALE, <0.5,0.5,0>, integer pidx = llListFindList(names, [perilPlayer]);
PSYS_PART_MAX_AGE, 2.0, PSYS_SRC_MAX_AGE, 2.0, if (pidx != -1) {
PSYS_SRC_ACCEL, <0,0,-0.4>, key pkey = llList2Key(players, pidx);
PSYS_SRC_BURST_RATE, 0.01, PSYS_SRC_BURST_PART_COUNT, 50, llDialog(pkey, "🎲 All picks are in! Ready to roll the dice?", ["Roll"], syncChannel);
PSYS_SRC_BURST_RADIUS, 0.2, }
PSYS_SRC_BURST_SPEED_MIN, 1.0, PSYS_SRC_BURST_SPEED_MAX, 2.0, return 0;
PSYS_PART_FLAGS, PSYS_PART_INTERP_COLOR_MASK | PSYS_PART_INTERP_SCALE_MASK | PSYS_PART_EMISSIVE_MASK }
]); 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() { showStatus(string name) {
// Tell all active statFloat objects to clean up integer idx = llListFindList(names, [name]);
integer i; if (idx == -1) return;
for (i = 0; i < llGetListLength(names); i++) { key avKey = llList2Key(players, idx);
integer ch = -777000 + i;
llRegionSay(ch, "CLEANUP"); // Check if already in floatSpawnMap
} if (llListFindList(floatSpawnMap, [avKey]) != -1) return;
players = names = lives = picksData = globalPickedNumbers = [];
perilPlayer = ""; integer lifeCount = llList2Integer(lives, idx);
page = 0; list picks = getPicksFor(name);
llSay(syncChannel, "RESET"); string txt = "🎲 Peril Dice
llOwnerSay("🔄 Game reset!"); 👤 " + 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() { 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 { 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]; timer() {
names += [name]; if (pendingUpdateName != "") {
lives += [3]; updateFloater(pendingUpdateName);
llOwnerSay("✅ " + name + " has joined the game with 3 lives."); pendingUpdateName = "";
showStatus(name);
} }
}
state_entry() { object_rez(key id) {
llVolumeDetect(TRUE); integer i;
llSetStatus(STATUS_PHYSICS, FALSE); for (i = 0; i < llGetListLength(floatSpawnMap); i += 2) {
llSetClickAction(CLICK_ACTION_TOUCH); key avKey = llList2Key(floatSpawnMap, i);
llListen(-88888, "", NULL_KEY, ""); integer ch = (integer)llList2String(floatSpawnMap, i + 1);
llListen(0, "", NULL_KEY, ""); integer idx = llListFindList(players, [avKey]);
llListen(syncChannel, "", NULL_KEY, ""); if (idx != -1 && ch == -777000 + idx) {
llListen(numberPickChannel, "", NULL_KEY, ""); string name = llList2String(names, idx);
llOwnerSay("🎲 Unified Peril Controller Ready"); integer lifeCount = llList2Integer(lives, idx);
} list picks = getPicksFor(name);
string txt = "🎲 Peril Dice
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 + 👤 " + name +
" "
❤️ Lives: " + (string)lifeCount + ❤️ Lives: " + (string)lifeCount +
" "
🧍 Peril: " + perilPlayer + 🧍 Peril: " + perilPlayer +
" "
🔢 Picks: " + llList2CSV(picks); 🔢 Picks: " + llList2CSV(picks);
llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt); llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt);
floatSpawnMap = llDeleteSubList(floatSpawnMap, i, i + 1); floatSpawnMap = llDeleteSubList(floatSpawnMap, i, i + 1);
return; 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

View file

@ -2,13 +2,20 @@ key target;
string displayText; string displayText;
default { 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() { state_entry() {
llOwnerSay("🧭 StatFloat active"); llOwnerSay("🧭 StatFloat active");
llSetText("", <1,1,1>, 1.0); llSetText("Waiting...", <1,1,1>, 1.0);
llSetTimerEvent(1.0); // track every second llSetTimerEvent(1.0);
} }
listen(integer channel, string name, key id, string message) { listen(integer channel, string name, key id, string message) {
llOwnerSay("🔉 Heard on channel " + (string)channel + ": " + message);
if (llSubStringIndex(message, "FLOAT:") == 0) { if (llSubStringIndex(message, "FLOAT:") == 0) {
list parts = llParseString2List(message, ["|"], []); list parts = llParseString2List(message, ["|"], []);
if (llGetListLength(parts) >= 2) { 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() { timer() {
if (target != NULL_KEY && llKey2Name(target) != "") { if (target != NULL_KEY && llKey2Name(target) != "") {
vector pos = llList2Vector(llGetObjectDetails(target, [OBJECT_POS]), 0) + <1,0,1>; vector pos = llList2Vector(llGetObjectDetails(target, [OBJECT_POS]), 0) + <1,0,1>;
llSetRegionPos(pos); llSetRegionPos(pos);
} else {
llOwnerSay("⚠️ Target key invalid or avatar not found.");
} }
} }
} }