Add Peril Dice Controller and StatFloat functionality with player management and UI updates
This commit is contained in:
commit
2083469a13
2 changed files with 289 additions and 0 deletions
250
PerilController.lsl
Normal file
250
PerilController.lsl
Normal 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
39
StatFloat.lsl
Normal 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue