Peril_Dice/Helper.lsl
2025-08-02 10:00:14 -05:00

358 lines
No EOL
14 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// === Main Peril Dice Controller (Refactored with Game Helpers Integration, with dynamic player join) ===
//
// This version of the main game controller includes support for players (including
// the owner) joining the game at runtime via a MSG_REGISTER_PLAYER message. When
// a new player registers, they are added to the internal lists (players, names,
// lives and picksData), a floating display is rezzed for them, and helpers are
// updated. This mirrors the behaviour used when adding a test player, but now
// applies to any avatar joining the game via the dialog handler.
integer syncChannel = -77777;
integer numberPickChannel = -77888;
integer rollDialogChannel = -77999;
integer DIALOG_CHANNEL = -88888;
list players = [];
list names = [];
list lives = [];
string perilPlayer = "";
list globalPickedNumbers = [];
list picksData = [];
list readyPlayers = [];
list pickQueue = [];
integer currentPickerIdx = 0;
integer diceType = 6;
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 MSG_SHOW_MENU = 201;
integer MSG_SHOW_ROLL_DIALOG = 301;
integer MSG_GET_DICE_TYPE = 1001;
integer MSG_GET_PICKS_REQUIRED = 1002;
integer MSG_GET_PICKER_INDEX = 1003;
integer MSG_DICE_TYPE_RESULT = 1005;
integer MSG_SERIALIZE_GAME_STATE = 1004;
integer MSG_SYNC_PICKQUEUE = 2001;
// Maximum number of players allowed (including test players). This should
// mirror the value used in Floater Manager to avoid inconsistencies.
integer MAX_PLAYERS = 10;
integer TIMEOUT_SECONDS = 600;
integer timeoutTimer;
integer warning2min = 120;
integer warning5min = 300;
integer warning9min = 540;
integer lastWarning = 0;
key currentPicker;
// Forward game state to helpers when it changes
updateHelpers() {
string serialized = llList2CSV(lives) + "~" + llList2CSV(picksData) +
"~" + perilPlayer + "~" + llList2CSV(names);
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, serialized, NULL_KEY);
llMessageLinked(LINK_SET, MSG_SYNC_PICKQUEUE, llList2CSV(pickQueue), NULL_KEY);
}
// Request helper-calculated values
requestDiceType() {
llMessageLinked(LINK_SET, MSG_GET_DICE_TYPE, (string)llGetListLength(names), NULL_KEY);
}
requestPicksRequired(integer idx) {
llMessageLinked(LINK_SET, MSG_GET_PICKS_REQUIRED, llList2String(names, idx), NULL_KEY);
}
requestPickerIndex(string name) {
llMessageLinked(LINK_SET, MSG_GET_PICKER_INDEX, name, NULL_KEY);
}
string generateSerializedState() {
return llList2CSV(lives) + "~" + llList2CSV(picksData) + "~" + perilPlayer + "~" + llList2CSV(names);
}
resetGame() {
// Remove floats in reverse order so indices remain correct as entries are
// deleted from the Floater Manager's lists. Otherwise, deleting index 0
// first shifts subsequent indices and some floats are left behind.
integer i;
for (i = llGetListLength(names) - 1; i >= 0; i--) {
integer ch = -777000 + i;
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
}
players = names = lives = picksData = globalPickedNumbers = [];
perilPlayer = "";
pickQueue = [];
currentPickerIdx = 0;
llSay(syncChannel, "RESET");
llOwnerSay(" Game reset!");
llSleep(0.2);
llSetTimerEvent(0);
updateHelpers();
}
startNextRound() {
// Do not start a round if there are fewer than 2 players. This guard prevents
// accidental starts when only one player is present (e.g. after a reset or
// before any other participants join).
if (llGetListLength(names) < 2) {
llOwnerSay("⚠️ Need at least 2 players to start the game.");
return;
}
// If somehow called with exactly one player, treat them as the winner and reset.
if (llGetListLength(names) == 1) {
llSay(0, " " + llList2String(names, 0) + " is the last player standing and wins the game!");
resetGame();
return;
}
picksData = [];
globalPickedNumbers = [];
pickQueue = names;
currentPickerIdx = 0;
integer j;
for (j = 0; j < llGetListLength(names); j++) {
picksData += [llList2String(names, j) + "|"];
}
updateHelpers();
// requestDiceType(); // Removed to avoid recursion loop
}
showNextPickerDialog() {
string firstName = llList2String(pickQueue, currentPickerIdx);
currentPicker = llList2Key(players, llListFindList(names, [firstName]));
string dialogPayload = firstName + "|" + (string)diceType;
llMessageLinked(LINK_SET, MSG_SHOW_DIALOG, dialogPayload, currentPicker);
timeoutTimer = llGetUnixTime();
lastWarning = 0;
llSetTimerEvent(60.0);
}
integer roundStarted = FALSE;
default {
state_entry() {
llListen(DIALOG_CHANNEL, "", NULL_KEY, "");
}
touch_start(integer total_number) {
llOwnerSay("Touched by: " + (string)llDetectedKey(0));
key toucher = llDetectedKey(0);
integer idx = llListFindList(players, [toucher]);
if (toucher == llGetOwner()) {
// Determine if this will be the first registered player (starter) before registration
integer isStarter = FALSE;
if (idx == -1) {
// No index found; the owner is not yet registered.
// If there are no existing players, they will become the starter.
if (llGetListLength(players) == 0) {
isStarter = TRUE;
}
string ownerName = llKey2Name(toucher);
// Send a registration request; Main.lsl will handle adding them and rezzing a float
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, ownerName + "|" + (string)toucher, NULL_KEY);
} else {
// Already registered; starter if at index 0
isStarter = (idx == 0);
}
// Show the owner menu with the appropriate starter flag
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, toucher);
} else if (idx != -1) {
integer isStarter = (idx == 0);
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher);
}
}
link_message(integer sender, integer num, string str, key id) {
// Player list and pick list handling remain unchanged
if (num == 202 && str == "REQUEST_PLAYER_LIST") {
string namesCSV = llList2CSV(names);
llMessageLinked(LINK_SET, 203, namesCSV, id);
return;
}
if (num == 206) {
string targetName = str;
integer i;
for (i = 0; i < llGetListLength(picksData); i++) {
string rawEntry = llList2String(picksData, i);
list parts = llParseString2List(rawEntry, ["|"], []);
if (llList2String(parts, 0) == targetName) {
llMessageLinked(LINK_SET, 205, targetName + "|" + llList2String(parts, 1), id);
return;
}
}
llMessageLinked(LINK_SET, 205, targetName + "|", id);
return;
}
if (num == 208) {
string playerName = str;
integer idx = llListFindList(names, [playerName]);
if (idx != -1) {
string lifeVal = llList2String(lives, idx);
llMessageLinked(LINK_SET, 208, playerName + "|" + lifeVal, id);
}
return;
}
// Handle dice type result from helper
if (num == MSG_DICE_TYPE_RESULT) {
if (!roundStarted) {
roundStarted = TRUE;
diceType = (integer)str;
llOwnerSay(" Dice type set to: " + str);
showNextPickerDialog();
}
return;
}
if (num == MSG_GET_PICKS_REQUIRED) {
integer picksRequired = (integer)str;
llOwnerSay(" Picks required: " + str);
return;
}
if (num == MSG_GET_PICKER_INDEX) {
integer pickIndex = (integer)str;
llOwnerSay(" Picker index: " + str);
return;
}
if (num == MSG_SERIALIZE_GAME_STATE) {
string serialized = str;
llOwnerSay(" Serialized game state: " + serialized);
return;
}
// Handle pick actions
if (num == 204) {
list parts = llParseString2List(str, ["~"], []);
string action = llList2String(parts, 0);
list args = llParseString2List(llList2String(parts, 1), ["|"], []);
string name = llList2String(args, 0);
string pick = llList2String(args, 1);
integer i;
for (i = 0; i < llGetListLength(picksData); i++) {
string entry = llList2String(picksData, i);
list pdParts = llParseString2List(entry, ["|"], []);
if (llList2String(pdParts, 0) == name) {
list pickList = [];
string rawPicks = llList2String(pdParts, 1);
if (rawPicks != "") {
pickList = llParseString2List(rawPicks, [","], []);
}
if (action == "ADD_PICK") {
if (llListFindList(pickList, [pick]) == -1) {
pickList += [pick];
llOwnerSay("➕ Added " + pick + " to " + name);
}
} else if (action == "REMOVE_PICK") {
integer idx = llListFindList(pickList, [pick]);
if (idx != -1) {
pickList = llDeleteSubList(pickList, idx, idx);
llOwnerSay("➖ Removed " + pick + " from " + name);
}
}
picksData = llListReplaceList(picksData, [name + "|" + llList2CSV(pickList)], i, i);
updateHelpers();
return;
}
}
return;
}
// New: handle dynamic registration of players (owner or players) via MSG_REGISTER_PLAYER
if (num == MSG_REGISTER_PLAYER) {
// str is in the format "Name|<key>"
list parts = llParseString2List(str, ["|"], []);
string newName = llList2String(parts, 0);
key newKey = (key)llList2String(parts, 1);
// Do not register if already present
integer existingIdx = llListFindList(players, [newKey]);
if (existingIdx == -1) {
// Add to local lists
players += [newKey];
names += [newName];
lives += [3];
picksData += [newName + "|"];
// The float will be rezzed by the Floater Manager after it processes
// this registration. Avoid sending MSG_REZ_FLOAT here to prevent
// race conditions; instead, request the float from Floater Manager
// after it has updated its own lists. Immediately propagate
// helper updates now.
updateHelpers();
// Notify owner of registration
llOwnerSay("🔔 Added player: " + newName);
}
return;
}
}
listen(integer channel, string name, key id, string msg) {
if (channel == DIALOG_CHANNEL) {
// Owner-specific commands: only owner messages in the dialog should be processed here.
if (id == llGetOwner()) {
if (msg == "Reset Game") {
resetGame();
return;
}
// Note: do not handle "Start Game" here; allow the generic start logic below to apply,
// so that minimum player checks are enforced and non-owner starters can initiate the game.
if (msg == "Dump Players") {
llOwnerSay(" Players: " + llList2CSV(names));
return;
}
if (msg == "Manage Picks") {
llOwnerSay(" Fetching list of players for pick management...");
llMessageLinked(LINK_SET, 202, "REQUEST_PLAYER_LIST", llGetOwner());
return;
}
if (msg == "Add Test Player") {
// Ensure we do not exceed the maximum allowed players
if (llGetListLength(players) >= MAX_PLAYERS) {
llOwnerSay("⚠️ Cannot add test player; the game is full (max " + (string)MAX_PLAYERS + ").");
return;
}
// Generate a unique name and key for the test player
string testName = "TestBot" + (string)llGetUnixTime();
key fake = llGenerateKey();
// Delegate registration to the MSG_REGISTER_PLAYER handler; it will add to lists and rez a float
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, testName + "|" + (string)fake, NULL_KEY);
// Show the owner menu again (not flagged as starter)
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|0", llGetOwner());
return;
}
}
// Allow the first registered player (index 0) or the owner to start the game.
// When the starter clicks "Start Game", begin the round.
if (msg == "Start Game") {
// Enforce a minimum of 2 players before starting
if (llGetListLength(players) < 2) {
llOwnerSay("⚠️ Need at least 2 players to start the game.");
return;
}
// Check if the sender is the owner
if (id == llGetOwner()) {
startNextRound();
requestDiceType();
return;
}
// Otherwise, check if the sender is the very first player registered
if (llGetListLength(players) > 0) {
key firstPlayer = llList2Key(players, 0);
if (id == firstPlayer) {
startNextRound();
requestDiceType();
return;
}
}
}
// If the message text matches a player name, request their pick list
if (llListFindList(names, [msg]) != -1) {
llMessageLinked(LINK_SET, 206, msg, id);
}
}
}
}