Peril_Dice/Owner and Player Dialog Handler.lsl
RebeccaNod1 ede3f6ec75 🎲 Peril Dice — Modular Game System for Second Life
Overview
Peril Dice is a multiplayer elimination game where each player selects numbers before a die is rolled. If the peril player’s number is rolled, they lose a life. Players are eliminated when they reach zero lives.

🔧 Features
✅ Player System
Max players: 10

Each player starts with 3 lives

Players can Join, Leave, or mark themselves Ready

The first player to join gets a Start Game button

The owner can join the game and also has a special Owner menu

🎲 Dice Scaling
Dice type is automatically chosen to ensure at least 3 picks per player

Dice types supported: d6, d12, d20, d30

Player Count	Dice Type
1–2	d6
3–4	d12
5–6	d20
7–10	d30

🧍 Test Players
Owner can add/remove test players (bots)

Test players rez stat floaters at offset positions to prevent overlap

Floater objects follow players and update in real time

🪟 HUD & Stat Floaters
Players receive a floating HUD showing:

Player name

Lives

Picks

Peril status or game state

📦 Modular Architecture
Separated scripts:

Main Controller: Handles core game logic

Dialog Handler: Manages pick dialogs

Dice Selector: Returns dice type based on player count

Stat Float Manager: Rezzes and updates floaters

Stat Float Object: Displays floating text per player

🕹️ Game Flow
Players touch the controller object to join the game

Once 2+ players have joined, the first player gets the option to Start Game

Dice type is selected automatically

Each player is prompted (in order) to select 3 numbers

After all picks are made:

The dice is rolled

The result is compared to each player’s picks

If the peril player’s number is rolled or no one picked it, the peril player loses a life

The system checks for eliminations and starts the next round

🧰 Admin Controls
Owner can:

Join or leave the game

Open a special Owner menu

Add/remove test bots

Reset the game

Debug current state (optional)
2025-08-02 00:18:45 -05:00

182 lines
No EOL
7.2 KiB
Text

// === Dialog Handler (Owner & Player) with unified Ready/Leave menu and join support ===
// Owners and players share the same Ready/Leave dialog.
// Owners also get an "Owner" button to access advanced options.
// Owners can join the game and receive a floating display.
integer DIALOG_CHANNEL = -88888;
integer MSG_SHOW_MENU = 201;
integer MSG_PICK_ACTION = 204;
integer MSG_PLAYER_LIST_RESULT = 203;
integer MSG_PICK_LIST_RESULT = 205;
integer MSG_LIFE_LOOKUP = 207;
integer MSG_REGISTER_PLAYER = 106;
integer MSG_REZ_FLOAT = 105;
// Owner-specific options shown when pressing the "Owner" button
list ownerOptions = ["Join Game", "Leave Game", "Add Test Player", "Manage Picks", "Start Game", "Reset Game", "Dump Players"];
// Display the debug/owner menu
showOwnerMenu(key id) {
llDialog(id, "🔧 Owner Menu", ownerOptions, DIALOG_CHANNEL);
}
// Display the combined Ready/Leave menu for both players and owners
// If the caller is the starter, the first button is "Start"; otherwise "Ready".
// Owners receive an extra "Owner" button to access the advanced owner menu.
showReadyLeaveMenu(key id, integer isStarter, integer isOwner) {
list options;
// If this user is flagged as the starter, they should see the "Start Game"
// button so they can initiate the round (regardless of ownership). Otherwise
// they see the regular "Ready" button.
if (isStarter) {
options = ["Start Game", "Leave Game"];
} else {
options = ["Ready", "Leave Game"];
}
if (isOwner) {
options += ["Owner"];
}
// Display the combined menu
llDialog(id, "Select an option:", options, DIALOG_CHANNEL);
}
// Manage picks UI helpers remain unchanged
list currentPickList;
string currentPickTarget;
integer currentPickLimit = 3;
showPickManageMenu(key id, list playerNames) {
list options = [];
integer i;
for (i = 0; i < llGetListLength(playerNames); i++) {
string pname = llList2String(playerNames, i);
options += ["🛠 " + pname];
}
options += ["⬅️ Back"];
llDialog(id, "📋 Manage Picks for: ", options, DIALOG_CHANNEL);
}
showPickListMenu(key id) {
list options = [];
integer i;
for (i = 0; i < llGetListLength(currentPickList); i++) {
string pick = llList2String(currentPickList, i);
if (llStringLength(pick) > 0) {
options += ["REMOVE: " + pick];
}
}
options += ["Add Pick", "⬅️ Back"];
llDialog(id, "🛠 Picks for: " + currentPickTarget, options, DIALOG_CHANNEL);
}
askForNewPick(key id) {
llTextBox(id, "🔢 Enter a number to add for " + currentPickTarget + ":", DIALOG_CHANNEL);
}
default {
state_entry() {
llListen(DIALOG_CHANNEL, "", NULL_KEY, "");
}
link_message(integer sender, integer num, string str, key id) {
if (num == MSG_SHOW_MENU) {
llOwnerSay("📨 Received MSG_SHOW_MENU with: " + str);
list args = llParseString2List(str, ["|"], []);
if (llGetListLength(args) < 2) {
llOwnerSay("⚠️ Invalid message format: " + str);
return;
}
string targetType = llList2String(args, 0);
integer isStarter = (integer)llList2String(args, 1);
if (targetType == "owner") {
showReadyLeaveMenu(id, isStarter, TRUE);
} else if (targetType == "player") {
showReadyLeaveMenu(id, isStarter, FALSE);
}
}
else if (num == MSG_PLAYER_LIST_RESULT) {
list playerNames = llParseString2List(str, [","], []);
llOwnerSay("📋 Fetching list of players for pick management...");
showPickManageMenu(id, playerNames);
}
else if (num == MSG_PICK_LIST_RESULT) {
list parts = llParseString2List(str, ["|"], []);
string returnedName = llList2String(parts, 0);
string picks = llList2String(parts, 1);
if (llStringTrim(returnedName, STRING_TRIM) == llStringTrim(currentPickTarget, STRING_TRIM)) {
llOwnerSay("✅ Matched pick list for: " + returnedName);
currentPickList = llCSV2List(picks);
llMessageLinked(LINK_THIS, MSG_LIFE_LOOKUP, currentPickTarget, id);
} else {
llOwnerSay("❌ Name mismatch: got " + returnedName + " but expected " + currentPickTarget);
}
}
else if (num == MSG_LIFE_LOOKUP) {
list parts = llParseString2List(str, ["|"], []);
if (llList2String(parts, 0) == currentPickTarget) {
currentPickLimit = (integer)llList2String(parts, 1);
showPickListMenu(id);
}
}
}
listen(integer channel, string name, key id, string msg) {
// Handle "Owner" button to show owner options
if (msg == "Owner") {
showOwnerMenu(id);
}
// Owner joins the game: register and rez a float for them
else if (msg == "Join Game") {
// Use the avatar's key as the identifier
string pname = llKey2Name(id);
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, pname + "|" + (string)id, NULL_KEY);
llMessageLinked(LINK_SET, MSG_REZ_FLOAT, pname, id);
}
else if (msg == "Manage Picks") {
llMessageLinked(LINK_THIS, 202, "REQUEST_PLAYER_LIST", id);
}
else if (llSubStringIndex(msg, "🛠 ") == 0) {
currentPickTarget = llStringTrim(llDeleteSubString(msg, 0, 1), STRING_TRIM);
llOwnerSay("🎯 Pick target set to: " + currentPickTarget);
currentPickList = [];
llMessageLinked(LINK_THIS, 206, currentPickTarget, id);
}
else if (msg == "Add Pick") {
askForNewPick(id);
}
else if (msg == "⬅️ Back") {
llMessageLinked(LINK_THIS, 202, "REQUEST_PLAYER_LIST", id);
}
else if (llSubStringIndex(msg, "REMOVE: ") == 0) {
string pick = llGetSubString(msg, 8, -1);
string payload = "REMOVE_PICK~" + currentPickTarget + "|" + pick;
llMessageLinked(LINK_THIS, MSG_PICK_ACTION, payload, id);
llSleep(0.2);
llMessageLinked(LINK_THIS, 206, currentPickTarget, id);
}
// Forward numeric picks to the game logic
else if ((integer)msg > 0) {
if (llGetListLength(currentPickList) >= currentPickLimit) {
llOwnerSay("⚠️ Reached pick limit of " + (string)currentPickLimit + " for " + currentPickTarget);
showPickListMenu(id);
return;
}
if (llListFindList(currentPickList, [msg]) != -1) {
llOwnerSay("⚠️ Pick already exists: " + msg);
showPickListMenu(id);
return;
}
integer i;
for (i = 0; i < llGetListLength(currentPickList); i++) {
if (llList2String(currentPickList, i) == msg) {
llOwnerSay("⚠️ Duplicate pick detected.");
return;
}
}
string payload = "ADD_PICK~" + currentPickTarget + "|" + msg;
llMessageLinked(LINK_THIS, MSG_PICK_ACTION, payload, id);
llSleep(0.2);
llMessageLinked(LINK_THIS, 206, currentPickTarget, id);
}
}
}