Add Peril Dice Controller and StatFloat functionality with player management and UI updates

This commit is contained in:
RebeccaNod1 2025-07-18 23:28:18 -05:00
commit 2083469a13
2 changed files with 289 additions and 0 deletions

250
PerilController.lsl Normal file
View file

@ -0,0 +1,250 @@
// === Fully Unified Peril Dice Controller (Testing Enabled + Click-to-Join) ===
// 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
// Dice and UI settings
integer diceType = 6;
integer page = 0;
integer maxPerPage = 12;
integer getPicksRequired(integer lifeCount) {
if (lifeCount == 3) return 1;
if (lifeCount == 2) return 2;
return 3;
}
integer getPicksIndex(string nameInput) {
integer i;
for (i = 0; i < llGetListLength(picksData); i++) {
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), [","], []);
}
addPick(string nameInput, string number) {
integer idx = getPicksIndex(nameInput);
integer playerIdx = llListFindList(names, [nameInput]);
integer lifeCount = llList2Integer(lives, playerIdx);
integer limit = getPicksRequired(lifeCount);
list picks;
if (idx == -1) {
picks = [number];
picksData += [nameInput + "|" + number];
} else {
picks = getPicksFor(nameInput);
if (llListFindList(picks, [number]) != -1 || llGetListLength(picks) >= limit) return;
picks += [number];
picksData = llListReplaceList(picksData, [nameInput + "|" + llDumpList2String(picks, ",")], idx, idx);
}
globalPickedNumbers += [number];
}
showStatus(string name) {
integer idx = llListFindList(names, [name]);
if (idx == -1) return;
integer lifeCount = llList2Integer(lives, idx);
list picks = getPicksFor(name);
string txt = "🎲 Peril Dice
👤 " + name +
"
❤️ Lives: " + (string)lifeCount +
"
🧍 Peril: " + perilPlayer +
"
🔢 Picks: " + llList2CSV(picks);
key avKey = llList2Key(players, idx);
if (llKey2Name(avKey) != "") {
vector pos = llList2Vector(llGetObjectDetails(avKey, [OBJECT_POS]), 0);
vector rezPos = pos + <1,0,1>; // offset in front and above the player
integer channel = -777000 + idx;
llOwnerSay("📦 Attempting to rez statFloat at: " + (string)rezPos);
llRezObject("StatFloat", rezPos, ZERO_VECTOR, ZERO_ROTATION, channel);
floatSpawnMap += [avKey, (string)channel];
}
}
showPickDialog(key id, string name) {
list picks = getPicksFor(name);
integer picksRequired = getPicksRequired(llList2Integer(lives, llListFindList(names, [name])));
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);
}
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
]);
}
resetGame() {
// Tell all active statFloat objects to clean up
integer i;
for (i = 0; i < llGetListLength(names); i++) {
integer ch = -777000 + i;
llRegionSay(ch, "CLEANUP");
}
players = names = lives = picksData = globalPickedNumbers = [];
perilPlayer = "";
page = 0;
llSay(syncChannel, "RESET");
llOwnerSay("🔄 Game reset!");
}
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();
}
}
default {
touch_start(integer total_number) {
key toucher = llDetectedKey(0);
string name = llDetectedName(0);
integer idx = llListFindList(players, [toucher]);
if (~idx) {
llDialog(toucher, "❓ You are already in the game. Leave the game?", ["Yes", "No"], -88888);
return;
}
players += [toucher];
names += [name];
lives += [3];
llOwnerSay("✅ " + name + " has joined the game with 3 lives.");
showStatus(name);
}
state_entry() {
llVolumeDetect(TRUE);
llSetStatus(STATUS_PHYSICS, FALSE);
llSetClickAction(CLICK_ACTION_TOUCH);
llListen(-88888, "", NULL_KEY, "");
llListen(0, "", NULL_KEY, "");
llListen(syncChannel, "", NULL_KEY, "");
llListen(numberPickChannel, "", NULL_KEY, "");
llOwnerSay("🎲 Unified Peril Controller Ready");
}
on_rez(integer p) {
llResetScript();
}
listen(integer chan, string speakerName, key id, string msg) {
if (chan == -88888 && msg == "Yes") {
integer i = llListFindList(players, [id]);
if (i != -1) {
integer ch = -777000 + i;
llRegionSay(ch, "CLEANUP");
players = llDeleteSubList(players, i, i);
names = llDeleteSubList(names, i, i);
lives = llDeleteSubList(lives, i, i);
llOwnerSay("👋 Player left the game: " + llKey2Name(id));
}
}
else if (chan == 0 && llToLower(msg) == "/reset" && id == llGetOwner()) {
resetGame();
}
else if (chan == 0 && llToLower(msg) == "/start" && id == llGetOwner()) {
if (llGetListLength(names) == 0) {
llOwnerSay("⚠️ No players to choose from.");
return;
}
integer r = (integer)llFrand(llGetListLength(names));
string chosen = llList2String(names, r);
perilPlayer = chosen;
picksData = globalPickedNumbers = [];
page = 0;
llSay(syncChannel, "PERIL:" + perilPlayer);
llOwnerSay("🔥 Peril player is: " + perilPlayer);
// Refresh all float displays
integer j;
for (j = 0; j < llGetListLength(names); j++) {
showStatus(llList2String(names, j));
}
}
}
object_rez(key id) {
integer i;
for (i = 0; i < llGetListLength(floatSpawnMap); i += 2) {
key avKey = llList2Key(floatSpawnMap, i);
integer ch = (integer)llList2String(floatSpawnMap, i + 1);
integer idx = llListFindList(players, [avKey]);
if (idx != -1 && ch == -777000 + idx) {
string name = llList2String(names, idx);
integer lifeCount = llList2Integer(lives, idx);
list picks = getPicksFor(name);
string txt = "🎲 Peril Dice
👤 " + name +
"
❤️ Lives: " + (string)lifeCount +
"
🧍 Peril: " + perilPlayer +
"
🔢 Picks: " + llList2CSV(picks);
llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt);
floatSpawnMap = llDeleteSubList(floatSpawnMap, i, i + 1);
return;
}
}
}
}

39
StatFloat.lsl Normal file
View file

@ -0,0 +1,39 @@
key target;
string displayText;
default {
state_entry() {
llOwnerSay("🧭 StatFloat active");
llSetText("", <1,1,1>, 1.0);
llSetTimerEvent(1.0); // track every second
}
listen(integer channel, string name, key id, string message) {
if (llSubStringIndex(message, "FLOAT:") == 0) {
list parts = llParseString2List(message, ["|"], []);
if (llGetListLength(parts) >= 2) {
target = (key)llGetSubString(llList2String(parts, 0), 6, -1);
displayText = llList2String(parts, 1);
llSetText(displayText, <1,1,1>, 1.0);
llOwnerSay("✅ Received FLOAT for: " + (string)target);
}
} else if (message == "CLEANUP") {
llOwnerSay("🧹 Cleaning up...");
llDie();
}
}
on_rez(integer start_param) {
llListen(start_param, "", NULL_KEY, "");
llOwnerSay("📡 Listening on channel " + (string)start_param);
}
timer() {
if (target != NULL_KEY && llKey2Name(target) != "") {
vector pos = llList2Vector(llGetObjectDetails(target, [OBJECT_POS]), 0) + <1,0,1>;
llSetRegionPos(pos);
} else {
llOwnerSay("⚠️ Target key invalid or avatar not found.");
}
}
}