✅ 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:
parent
f26c0d2853
commit
f895f9851c
6 changed files with 470 additions and 355 deletions
120
DialogHandler.lsl
Normal file
120
DialogHandler.lsl
Normal 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
43
FloaterManager.lsl
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
88
RollConfettiModule.lsl
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
77
StatFloatRezzer.lsl
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue