fix: replace ternary operators with conditional statements for LSL compatibility
- Fixed LSL syntax errors caused by unsupported ternary operator (condition ? value1 : value2) - Replaced all ternary operators with proper if/else statements in verbose logging toggles - Ensures 'ENABLED'/'DISABLED' messages display correctly without compilation errors - Updated files: Bot_Manager.lsl, Controller_Memory.lsl, Controller_MessageHandler.lsl, Floater_Manager.lsl, Game_Calculator.lsl, Game_Manager.lsl, Game_Scoreboard_Manager_Linkset.lsl, Leaderboard_Communication_Linkset.lsl, Main_Controller_Linkset.lsl, NumberPicker_DialogHandler.lsl, Player_DialogHandler.lsl, Roll_ConfettiModule.lsl, System_Debugger.lsl, XyzzyText_Dice_Bridge_Linkset.lsl - Updated WARP.md with latest v2.8.4 release information and fixes documentation
This commit is contained in:
parent
c2341d0a93
commit
8f95e792e2
15 changed files with 999 additions and 152 deletions
|
|
@ -57,6 +57,9 @@ list processedBotCommands = [];
|
|||
// Track sent BOT_PICKED messages to prevent duplicate sending
|
||||
list sentBotMessages = [];
|
||||
|
||||
// Verbose logging control - toggled by owner
|
||||
integer VERBOSE_LOGGING = FALSE;
|
||||
|
||||
// Helper function to check and report memory usage
|
||||
checkMemoryUsage(string context) {
|
||||
integer usedMemory = llGetUsedMemory();
|
||||
|
|
@ -71,7 +74,7 @@ checkMemoryUsage(string context) {
|
|||
|
||||
// Helper to parse and respond to pick commands
|
||||
doBotPick(string botName, integer count, integer diceMax, list avoidNumbers) {
|
||||
llOwnerSay("[Bot Manager] 🔧 doBotPick ENTRY - botName:" + botName + " count:" + (string)count + " diceMax:" + (string)diceMax);
|
||||
if (VERBOSE_LOGGING) llOwnerSay("[Bot Manager] 🔧 doBotPick ENTRY - botName:" + botName + " count:" + (string)count + " diceMax:" + (string)diceMax);
|
||||
checkMemoryUsage("doBotPick start");
|
||||
|
||||
// Validate bot exists and is still alive
|
||||
|
|
@ -80,14 +83,14 @@ doBotPick(string botName, integer count, integer diceMax, list avoidNumbers) {
|
|||
llOwnerSay("[Bot Manager] ❌ Bot '" + botName + "' not found in game - ignoring command");
|
||||
return;
|
||||
}
|
||||
llOwnerSay("[Bot Manager] 🔧 Bot found at index:" + (string)botIdx);
|
||||
if (VERBOSE_LOGGING) llOwnerSay("[Bot Manager] 🔧 Bot found at index:" + (string)botIdx);
|
||||
|
||||
integer botLives = llList2Integer(lives, botIdx);
|
||||
if (botLives <= 0) {
|
||||
llOwnerSay("[Bot Manager] ❌ Bot '" + botName + "' is eliminated (lives=" + (string)botLives + ") - ignoring command");
|
||||
return;
|
||||
}
|
||||
llOwnerSay("[Bot Manager] 🔧 Bot lives check passed:" + (string)botLives);
|
||||
if (VERBOSE_LOGGING) llOwnerSay("[Bot Manager] 🔧 Bot lives check passed:" + (string)botLives);
|
||||
|
||||
// IMPROVED ALGORITHM: Calculate available numbers first
|
||||
list availableNumbers = [];
|
||||
|
|
@ -143,8 +146,8 @@ doBotPick(string botName, integer count, integer diceMax, list avoidNumbers) {
|
|||
string response = "BOT_PICKED:" + botName + ":" + pickString;
|
||||
|
||||
// Check if we've already sent this exact message to prevent duplicates
|
||||
llOwnerSay("[Bot Manager] 🔍 DEBUG - Checking if message already sent: " + response);
|
||||
llOwnerSay("[Bot Manager] 🔍 DEBUG - sentBotMessages list: " + llDumpList2String(sentBotMessages, "|"));
|
||||
if (VERBOSE_LOGGING) llOwnerSay("[Bot Manager] 🔍 DEBUG - Checking if message already sent: " + response);
|
||||
if (VERBOSE_LOGGING) llOwnerSay("[Bot Manager] 🔍 DEBUG - sentBotMessages list: " + llDumpList2String(sentBotMessages, "|"));
|
||||
if (llListFindList(sentBotMessages, [response]) != -1) {
|
||||
llOwnerSay("[Bot Manager] ⚠️ DUPLICATE SEND - Already sent message: " + response);
|
||||
return;
|
||||
|
|
@ -152,7 +155,7 @@ doBotPick(string botName, integer count, integer diceMax, list avoidNumbers) {
|
|||
|
||||
// Mark this message as sent BEFORE sending to prevent race conditions
|
||||
sentBotMessages += [response];
|
||||
llOwnerSay("[Bot Manager] 🔍 DEBUG - Added to sentBotMessages, new list: " + llDumpList2String(sentBotMessages, "|"));
|
||||
if (VERBOSE_LOGGING) llOwnerSay("[Bot Manager] 🔍 DEBUG - Added to sentBotMessages, new list: " + llDumpList2String(sentBotMessages, "|"));
|
||||
|
||||
llOwnerSay("[Bot Manager] 🤖 " + botName + " picked: " + llList2CSV(picks));
|
||||
llOwnerSay("[Bot Manager] 📬 SENDING BOT_PICKED: " + response);
|
||||
|
|
@ -234,6 +237,20 @@ default {
|
|||
}
|
||||
|
||||
link_message(integer sender, integer num, string str, key id) {
|
||||
// Handle verbose logging toggle from Main Controller
|
||||
if (num == 9011 && llSubStringIndex(str, "VERBOSE_LOGGING|") == 0) {
|
||||
list parts = llParseString2List(str, ["|"], []);
|
||||
if (llGetListLength(parts) >= 2) {
|
||||
VERBOSE_LOGGING = (integer)llList2String(parts, 1);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔍 [Bot Manager] Verbose logging ON");
|
||||
} else {
|
||||
llOwnerSay("🔍 [Bot Manager] Verbose logging OFF");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle full reset from main controller
|
||||
if (num == -99999 && str == "FULL_RESET") {
|
||||
// Reset bot manager state
|
||||
|
|
@ -261,10 +278,11 @@ default {
|
|||
}
|
||||
names = llCSV2List(llList2String(parts, 3));
|
||||
|
||||
// Clear processed commands when peril player changes (new round)
|
||||
// Clear processed commands and sent messages when peril player changes (new round)
|
||||
if (oldPerilPlayer != perilPlayer && perilPlayer != "") {
|
||||
processedBotCommands = [];
|
||||
llOwnerSay("[Bot Manager] New round detected, cleared processed commands");
|
||||
sentBotMessages = [];
|
||||
llOwnerSay("[Bot Manager] New round detected, cleared processed commands and sent messages");
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
|
@ -309,7 +327,7 @@ default {
|
|||
}
|
||||
}
|
||||
// Debug: Show the game state before calling doBotPick
|
||||
llOwnerSay("[Bot Manager] 🔧 Game state - names:" + llList2CSV(names) + " lives:" + llList2CSV(lives));
|
||||
if (VERBOSE_LOGGING) llOwnerSay("[Bot Manager] 🔧 Game state - names:" + llList2CSV(names) + " lives:" + llList2CSV(lives));
|
||||
doBotPick(botName, count, diceMax, avoidNumbers);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
// Handles memory monitoring and reporting for the Main Controller
|
||||
// Communicates with other scripts via link messages
|
||||
|
||||
// Verbose logging control
|
||||
integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs
|
||||
integer MSG_TOGGLE_VERBOSE_LOGS = 9998; // Message to toggle verbose logging
|
||||
|
||||
// Memory monitoring system
|
||||
float MEMORY_WARNING_THRESHOLD = 0.8; // Warn at 80% usage
|
||||
float MEMORY_CRITICAL_THRESHOLD = 0.9; // Critical at 90% usage
|
||||
|
|
@ -33,9 +37,11 @@ checkMemoryUsage(string context) {
|
|||
} else if (memoryPercentage > MEMORY_WARNING_THRESHOLD) {
|
||||
// Only warn once per interval to avoid spam
|
||||
if ((currentTime - lastMemoryCheck) >= MEMORY_CHECK_INTERVAL) {
|
||||
llOwnerSay("⚠️ [Memory Monitor] High memory in " + context + ": " +
|
||||
(string)usedMemory + " bytes (" +
|
||||
(string)llRound(memoryPercentage * 100.0) + "% of 64KB limit)");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("⚠️ [Memory Monitor] High memory in " + context + ": " +
|
||||
(string)usedMemory + " bytes (" +
|
||||
(string)llRound(memoryPercentage * 100.0) + "% of 64KB limit)");
|
||||
}
|
||||
lastMemoryCheck = currentTime;
|
||||
}
|
||||
}
|
||||
|
|
@ -131,6 +137,17 @@ default {
|
|||
}
|
||||
|
||||
link_message(integer sender, integer num, string str, key id) {
|
||||
// Handle verbose logging toggle
|
||||
if (num == MSG_TOGGLE_VERBOSE_LOGS) {
|
||||
VERBOSE_LOGGING = !VERBOSE_LOGGING;
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔊 [Memory Monitor] Verbose logging ENABLED");
|
||||
} else {
|
||||
llOwnerSay("🔊 [Memory Monitor] Verbose logging DISABLED");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (num == MSG_MEMORY_CHECK) {
|
||||
// Manual memory check request
|
||||
checkMemoryUsage(str);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
// Handles non-critical message processing for the Main Controller
|
||||
// Reduces Main Controller size and improves modularity
|
||||
|
||||
// Verbose logging control
|
||||
integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs
|
||||
integer MSG_TOGGLE_VERBOSE_LOGS = 9998; // Message to toggle verbose logging
|
||||
|
||||
// Message constants for communication with main controller
|
||||
integer MSG_REQUEST_PLAYER_DATA = 8001;
|
||||
integer MSG_REQUEST_PICK_DATA = 8002;
|
||||
|
|
@ -106,13 +110,17 @@ handlePickAction(string actionData) {
|
|||
if (action == "ADD_PICK") {
|
||||
if (llListFindList(pickList, [pick]) == -1) {
|
||||
pickList += [pick];
|
||||
llOwnerSay("➕ [Message Handler] Added " + pick + " to " + name);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("➕ [Message Handler] Added " + pick + " to " + name);
|
||||
}
|
||||
}
|
||||
} else if (action == "REMOVE_PICK") {
|
||||
integer idx = llListFindList(pickList, [pick]);
|
||||
if (idx != -1) {
|
||||
pickList = llDeleteSubList(pickList, idx, idx);
|
||||
llOwnerSay("➖ [Message Handler] Removed " + pick + " from " + name);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("➖ [Message Handler] Removed " + pick + " from " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +136,9 @@ handlePickAction(string actionData) {
|
|||
|
||||
// Handle dice type requests
|
||||
handleDiceTypeRequest(key requester) {
|
||||
llOwnerSay("🎲 [Message Handler] Sending dice type: " + (string)diceType);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🎲 [Message Handler] Sending dice type: " + (string)diceType);
|
||||
}
|
||||
llMessageLinked(LINK_SET, 102, (string)diceType, requester); // MSG_ROLL_RESULT
|
||||
}
|
||||
|
||||
|
|
@ -142,16 +152,19 @@ handleLeaveGameRequest(string requestData) {
|
|||
integer idx = llListFindList(names, [leavingName]); // Find by name instead of key
|
||||
|
||||
if (idx != -1) {
|
||||
// Remove player's float using the tracked channel
|
||||
integer ch = FLOATER_BASE_CHANNEL + idx;
|
||||
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
|
||||
|
||||
// Update local lists
|
||||
// Update local lists FIRST to prevent race conditions
|
||||
players = llDeleteSubList(players, idx, idx);
|
||||
names = llDeleteSubList(names, idx, idx);
|
||||
lives = llDeleteSubList(lives, idx, idx);
|
||||
picksData = llDeleteSubList(picksData, idx, idx);
|
||||
|
||||
// THEN send floater cleanup using the previously calculated channel
|
||||
// Note: We DON'T send MSG_CLEANUP_FLOAT here anymore to avoid double-cleanup
|
||||
// The Main Controller will handle floater cleanup after list synchronization
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📝 [Message Handler] Updated local lists for " + leavingName + ", deferring floater cleanup to Main Controller");
|
||||
}
|
||||
|
||||
// Remove from ready list if present
|
||||
integer readyIdx = llListFindList(readyPlayers, [leavingName]);
|
||||
if (readyIdx != -1) {
|
||||
|
|
@ -172,7 +185,9 @@ handleLeaveGameRequest(string requestData) {
|
|||
|
||||
// Check if game should end (0 players = reset, 1 player = victory)
|
||||
if (llGetListLength(names) == 0) {
|
||||
llOwnerSay("🔄 [Message Handler] All players left - game will reset");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔄 [Message Handler] All players left - game will reset");
|
||||
}
|
||||
// Main Controller will handle the reset
|
||||
} else if (llGetListLength(names) == 1 && roundStarted && perilPlayer != "" && perilPlayer != "NONE") {
|
||||
// Only declare victory if the game has actually started (round active and peril player assigned)
|
||||
|
|
@ -183,7 +198,9 @@ handleLeaveGameRequest(string requestData) {
|
|||
} else if (llGetListLength(names) == 1) {
|
||||
// Game not started yet, just inform but don't declare victory
|
||||
string remainingPlayer = llList2String(names, 0);
|
||||
llOwnerSay("📝 [Message Handler] Only " + remainingPlayer + " remains, but game hasn't started yet");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📝 [Message Handler] Only " + remainingPlayer + " remains, but game hasn't started yet");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -192,6 +209,23 @@ handleLeaveGameRequest(string requestData) {
|
|||
// Synchronize game state from Main Controller
|
||||
syncGameState(string stateData) {
|
||||
list parts = llParseString2List(stateData, ["~"], []);
|
||||
|
||||
// Handle special RESET sync message
|
||||
if (llGetListLength(parts) >= 5 && llList2String(parts, 0) == "RESET") {
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔄 [Message Handler] Received reset sync - clearing all state");
|
||||
}
|
||||
// Clear all state during reset
|
||||
players = [];
|
||||
names = [];
|
||||
lives = [];
|
||||
picksData = [];
|
||||
readyPlayers = [];
|
||||
perilPlayer = "";
|
||||
roundStarted = FALSE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (llGetListLength(parts) >= 4) {
|
||||
lives = llCSV2List(llList2String(parts, 0));
|
||||
|
||||
|
|
@ -227,6 +261,19 @@ syncGameState(string stateData) {
|
|||
}
|
||||
|
||||
names = llCSV2List(llList2String(parts, 3));
|
||||
|
||||
// SAFETY: Validate peril player exists in current names list AFTER updating names
|
||||
if (perilPlayer != "" && perilPlayer != "NONE") {
|
||||
integer perilIdx = llListFindList(names, [perilPlayer]);
|
||||
if (perilIdx == -1) {
|
||||
// Only warn if we actually have players - during reset this is expected
|
||||
if (llGetListLength(names) > 0 && VERBOSE_LOGGING) {
|
||||
llOwnerSay("⚠️ [Message Handler] WARNING: Peril player '" + perilPlayer + "' not found in names list - clearing peril status");
|
||||
}
|
||||
perilPlayer = "";
|
||||
roundStarted = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -237,6 +284,17 @@ default {
|
|||
}
|
||||
|
||||
link_message(integer sender, integer num, string str, key id) {
|
||||
// Handle verbose logging toggle
|
||||
if (num == MSG_TOGGLE_VERBOSE_LOGS) {
|
||||
VERBOSE_LOGGING = !VERBOSE_LOGGING;
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔊 [Message Handler] Verbose logging ENABLED");
|
||||
} else {
|
||||
llOwnerSay("🔊 [Message Handler] Verbose logging DISABLED");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle player list requests (for pick management only)
|
||||
if (num == 202 && str == "REQUEST_PLAYER_LIST") {
|
||||
handlePlayerListRequest(id);
|
||||
|
|
@ -247,9 +305,11 @@ default {
|
|||
if (num == 8009 && str == "REQUEST_PLAYER_LIST_KICK") {
|
||||
// Send back kick-specific message ID to avoid conflict with pick management
|
||||
string namesCSV = llList2CSV(names);
|
||||
llOwnerSay("🔍 [Message Handler] Received request: '" + str + "'");
|
||||
llOwnerSay("🔍 [Message Handler] Names list: " + llDumpList2String(names, ", "));
|
||||
llOwnerSay("🔍 [Message Handler] Sending kick player list: '" + namesCSV + "'");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔍 [Message Handler] Received request: '" + str + "'");
|
||||
llOwnerSay("🔍 [Message Handler] Names list: " + llDumpList2String(names, ", "));
|
||||
llOwnerSay("🔍 [Message Handler] Sending kick player list: '" + namesCSV + "'");
|
||||
}
|
||||
llMessageLinked(LINK_SET, 8009, namesCSV, id);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ integer MSG_REZ_FLOAT = 105;
|
|||
integer MSG_REGISTER_PLAYER = 106;
|
||||
integer MSG_SYNC_GAME_STATE = 107;
|
||||
|
||||
// Debug control message constants
|
||||
integer MSG_DEBUG_PICKS_ON = 7001;
|
||||
integer MSG_DEBUG_PICKS_OFF = 7002;
|
||||
|
||||
// =============================================================================
|
||||
// DYNAMIC CHANNEL CONFIGURATION FOR FLOATERS
|
||||
// =============================================================================
|
||||
|
|
@ -44,6 +48,12 @@ initializeChannels() {
|
|||
// Maximum number of players allowed in the game
|
||||
integer MAX_PLAYERS = 10;
|
||||
|
||||
// Debug control - set to TRUE for verbose pick debugging, FALSE for normal operation
|
||||
integer DEBUG_PICKS = FALSE;
|
||||
|
||||
// Verbose logging control - affects general debug messages, toggled by owner
|
||||
integer VERBOSE_LOGGING = FALSE;
|
||||
|
||||
list players = [];
|
||||
list names = [];
|
||||
list lives = [];
|
||||
|
|
@ -52,9 +62,13 @@ string perilPlayer = "";
|
|||
|
||||
// Returns a list of picks for the given player name, filtering out invalid values
|
||||
list getPicksFor(string nameInput) {
|
||||
// ENHANCED DEBUG: Log the request and current data state
|
||||
if (DEBUG_PICKS) llOwnerSay("🔍 [Floater Manager] getPicksFor(" + nameInput + "), picksData entries: " + (string)llGetListLength(picksData));
|
||||
|
||||
integer i;
|
||||
for (i = 0; i < llGetListLength(picksData); i++) {
|
||||
string entry = llList2String(picksData, i);
|
||||
if (DEBUG_PICKS) llOwnerSay("🔍 [Floater Manager] Examining entry [" + (string)i + "]: " + entry);
|
||||
|
||||
if (llSubStringIndex(entry, "|") == -1) {
|
||||
if (entry != "") llOwnerSay("⚠️ Malformed picks entry (missing pipe): " + entry);
|
||||
|
|
@ -69,25 +83,45 @@ list getPicksFor(string nameInput) {
|
|||
}
|
||||
else if (llList2String(parts, 0) == nameInput) {
|
||||
string pickString = llList2String(parts, 1);
|
||||
if (DEBUG_PICKS) llOwnerSay("🔍 [Floater Manager] Found match for " + nameInput + ", pickString: '" + pickString + "'");
|
||||
|
||||
if (pickString == "") {
|
||||
// Empty picks are normal during initialization, don't warn
|
||||
if (DEBUG_PICKS) llOwnerSay("🔍 [Floater Manager] Empty picks for " + nameInput + ", returning []");
|
||||
return [];
|
||||
} else {
|
||||
// Convert semicolons back to commas for display
|
||||
list pickList = llParseString2List(pickString, [";"], []);
|
||||
// IMPROVED PARSING: Handle both comma AND semicolon delimited picks
|
||||
// First, try parsing as comma-separated (most common format from Game Manager)
|
||||
list pickList = llParseString2List(pickString, [","], []);
|
||||
|
||||
// If that gives us only one item, try semicolon-separated
|
||||
if (llGetListLength(pickList) == 1 && llSubStringIndex(pickString, ";") != -1) {
|
||||
pickList = llParseString2List(pickString, [";"], []);
|
||||
if (DEBUG_PICKS) llOwnerSay("🔍 [Floater Manager] Using semicolon parsing for: " + pickString);
|
||||
} else {
|
||||
if (DEBUG_PICKS) llOwnerSay("🔍 [Floater Manager] Using comma parsing for: " + pickString + ", got " + (string)llGetListLength(pickList) + " picks");
|
||||
}
|
||||
|
||||
list filtered = [];
|
||||
integer j;
|
||||
for (j = 0; j < llGetListLength(pickList); j++) {
|
||||
string val = llStringTrim(llList2String(pickList, j), STRING_TRIM);
|
||||
if (val != "" && (string)((integer)val) == val) {
|
||||
filtered += [val];
|
||||
if (DEBUG_PICKS) llOwnerSay("🔍 [Floater Manager] Added valid pick: " + val);
|
||||
} else if (val != "") {
|
||||
if (DEBUG_PICKS) llOwnerSay("⚠️ [Floater Manager] Filtered out invalid pick: '" + val + "'");
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG_PICKS) llOwnerSay("🔍 [Floater Manager] Final filtered picks for " + nameInput + ": " + llList2CSV(filtered));
|
||||
return filtered;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG_PICKS) llOwnerSay("🔍 [Floater Manager] No picks found for " + nameInput + ", returning []");
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +176,20 @@ default {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle verbose logging toggle from Main Controller
|
||||
if (num == 9011 && llSubStringIndex(str, "VERBOSE_LOGGING|") == 0) {
|
||||
list parts = llParseString2List(str, ["|"], []);
|
||||
if (llGetListLength(parts) >= 2) {
|
||||
VERBOSE_LOGGING = (integer)llList2String(parts, 1);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔍 [Floater Manager] Verbose logging ON");
|
||||
} else {
|
||||
llOwnerSay("🔍 [Floater Manager] Verbose logging OFF");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (num == MSG_REGISTER_PLAYER) {
|
||||
// Enforce the maximum number of players
|
||||
if (llGetListLength(players) >= MAX_PLAYERS) {
|
||||
|
|
@ -221,13 +269,17 @@ default {
|
|||
else if (num == MSG_UPDATE_FLOAT) {
|
||||
string name = str;
|
||||
integer idx = llListFindList(names, [name]);
|
||||
if (idx == -1) return;
|
||||
if (idx == -1) {
|
||||
llOwnerSay("⚠️ [Floater Manager] MSG_UPDATE_FLOAT: Player '" + name + "' not found in names list");
|
||||
return;
|
||||
}
|
||||
key avKey = llList2Key(players, idx);
|
||||
integer ch = FLOATER_BASE_CHANNEL + idx;
|
||||
integer lifeCount = llList2Integer(lives, idx);
|
||||
|
||||
if (VERBOSE_LOGGING) llOwnerSay("🔄 [Floater Manager] Updating floater for " + name + " (lives: " + (string)lifeCount + ")");
|
||||
list picks = getPicksFor(name);
|
||||
|
||||
if (VERBOSE_LOGGING) llOwnerSay("🔄 [Floater Manager] Retrieved picks for " + name + ": " + llList2CSV(picks));
|
||||
|
||||
string perilName;
|
||||
// Show proper peril status based on current game state
|
||||
|
|
@ -261,29 +313,76 @@ default {
|
|||
// Always send cleanup message to the channel (even for orphaned floaters)
|
||||
llRegionSay(ch, "CLEANUP");
|
||||
|
||||
// Only clean up internal lists if this corresponds to a valid player
|
||||
// IMPROVED: Clean up internal lists more carefully to handle race conditions
|
||||
// Instead of relying on index calculation, find by channel and remove consistently
|
||||
if (idx >= 0 && idx < llGetListLength(players)) {
|
||||
players = llDeleteSubList(players, idx, idx);
|
||||
names = llDeleteSubList(names, idx, idx);
|
||||
lives = llDeleteSubList(lives, idx, idx);
|
||||
picksData = llDeleteSubList(picksData, idx, idx);
|
||||
// Store player info before removal for debugging
|
||||
string removedPlayer = "";
|
||||
if (idx < llGetListLength(names)) {
|
||||
removedPlayer = llList2String(names, idx);
|
||||
}
|
||||
|
||||
// Remove from all lists at the same index to maintain synchronization
|
||||
if (idx < llGetListLength(players)) players = llDeleteSubList(players, idx, idx);
|
||||
if (idx < llGetListLength(names)) names = llDeleteSubList(names, idx, idx);
|
||||
if (idx < llGetListLength(lives)) lives = llDeleteSubList(lives, idx, idx);
|
||||
if (idx < llGetListLength(picksData)) picksData = llDeleteSubList(picksData, idx, idx);
|
||||
|
||||
if (removedPlayer != "") {
|
||||
llOwnerSay("🧹 [Floater Manager] Cleaned up floater for: " + removedPlayer + " (channel: " + (string)ch + ")");
|
||||
|
||||
// CRITICAL: Update all remaining floaters to refresh peril status after player removal
|
||||
// This prevents stale peril player information from persisting in floaters
|
||||
llSleep(0.2); // Brief delay to ensure cleanup completes
|
||||
integer i;
|
||||
for (i = 0; i < llGetListLength(names); i++) {
|
||||
string remainingPlayerName = llList2String(names, i);
|
||||
key remainingPlayerKey = NULL_KEY;
|
||||
if (i < llGetListLength(players)) {
|
||||
remainingPlayerKey = llList2Key(players, i);
|
||||
}
|
||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, remainingPlayerName, remainingPlayerKey);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Orphaned floater cleanup
|
||||
llOwnerSay("🧹 [Floater Manager] Cleaned up orphaned floater (channel: " + (string)ch + ")");
|
||||
}
|
||||
}
|
||||
else if (num == MSG_SYNC_GAME_STATE) {
|
||||
// Synchronize the lists for lives and picksData when receiving a new game state
|
||||
list parts = llParseString2List(str, ["~"], []);
|
||||
|
||||
// Handle special RESET sync message
|
||||
if (llGetListLength(parts) >= 5 && llList2String(parts, 0) == "RESET") {
|
||||
llOwnerSay("🔄 [Floater Manager] Received reset sync - ignoring during reset");
|
||||
return;
|
||||
}
|
||||
|
||||
if (llGetListLength(parts) < 4) {
|
||||
llOwnerSay("⚠️ Floater Manager: Incomplete sync message received, parts: " + (string)llGetListLength(parts));
|
||||
return;
|
||||
}
|
||||
string livesStr = llList2String(parts, 0);
|
||||
lives = llCSV2List(livesStr);
|
||||
// Use ^ delimiter for picksData to avoid comma conflicts
|
||||
|
||||
// ENHANCED DEBUG: Log the received picks data
|
||||
string picksDataStr = llList2String(parts, 1);
|
||||
if (VERBOSE_LOGGING) llOwnerSay("📥 [Floater Manager] Received picks data: '" + picksDataStr + "'");
|
||||
|
||||
// Use ^ delimiter for picksData to avoid comma conflicts
|
||||
if (picksDataStr == "" || picksDataStr == "EMPTY") {
|
||||
picksData = [];
|
||||
if (VERBOSE_LOGGING) llOwnerSay("📥 [Floater Manager] Picks data empty, cleared picksData list");
|
||||
} else {
|
||||
picksData = llParseString2List(picksDataStr, ["^"], []);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📥 [Floater Manager] Parsed " + (string)llGetListLength(picksData) + " picks data entries:");
|
||||
integer debugIdx;
|
||||
for (debugIdx = 0; debugIdx < llGetListLength(picksData); debugIdx++) {
|
||||
llOwnerSay(" [" + (string)debugIdx + "]: " + llList2String(picksData, debugIdx));
|
||||
}
|
||||
}
|
||||
}
|
||||
string receivedPeril = llList2String(parts, 2);
|
||||
string oldPeril = perilPlayer;
|
||||
|
|
@ -307,10 +406,9 @@ default {
|
|||
llOwnerSay("🎯 Floater Manager: Peril player updated from '" + oldPeril + "' to '" + perilPlayer + "'");
|
||||
}
|
||||
|
||||
// After synchronizing the game state, update all existing floats so
|
||||
// they reflect the current peril status. Without this, floats
|
||||
// created in previous rounds may continue to display an old
|
||||
// peril name until another manual update occurs.
|
||||
// CRITICAL: After synchronizing the game state, ALWAYS update all existing floats
|
||||
// This ensures they reflect the current peril status and prevents desynchronization
|
||||
// Force update even if peril player didn't change - other state may have changed
|
||||
integer idx;
|
||||
for (idx = 0; idx < llGetListLength(names); idx++) {
|
||||
string n = llList2String(names, idx);
|
||||
|
|
@ -323,6 +421,33 @@ default {
|
|||
}
|
||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, n, k);
|
||||
}
|
||||
|
||||
// Additional safety: If there was a peril player change, add a brief delay and update again
|
||||
// This handles edge cases where floater updates might race with other state changes
|
||||
if (oldPeril != perilPlayer && perilPlayer != "" && perilPlayer != "NONE") {
|
||||
llOwnerSay("🔄 [Floater Manager] Peril player changed - scheduling secondary update for stability");
|
||||
llSleep(0.3); // Brief delay to let initial updates complete
|
||||
for (idx = 0; idx < llGetListLength(names); idx++) {
|
||||
string n = llList2String(names, idx);
|
||||
key k;
|
||||
if (idx < llGetListLength(players)) {
|
||||
k = llList2Key(players, idx);
|
||||
} else {
|
||||
k = NULL_KEY;
|
||||
}
|
||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, n, k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle debug control messages
|
||||
else if (num == MSG_DEBUG_PICKS_ON) {
|
||||
DEBUG_PICKS = TRUE;
|
||||
llOwnerSay("🔍 [Floater Manager] Pick debugging ENABLED");
|
||||
}
|
||||
else if (num == MSG_DEBUG_PICKS_OFF) {
|
||||
DEBUG_PICKS = FALSE;
|
||||
llOwnerSay("🔇 [Floater Manager] Pick debugging DISABLED");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
// This version chooses a dice size based on the number of players,
|
||||
// ensuring at least three numbers are available per player.
|
||||
|
||||
// Verbose logging control
|
||||
integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs
|
||||
integer MSG_TOGGLE_VERBOSE_LOGS = 9998; // Message to toggle verbose logging
|
||||
|
||||
integer MSG_GET_DICE_TYPE = 1001;
|
||||
integer MSG_DICE_TYPE_RESULT = 1005;
|
||||
integer MSG_GET_PICKS_REQUIRED = 1002;
|
||||
|
|
@ -80,12 +84,16 @@ integer showPickManager(string player, key id) {
|
|||
string testName = llList2String(names, i);
|
||||
if (llSubStringIndex(testName, searchName) != -1) {
|
||||
idx = i;
|
||||
llOwnerSay("✅ Matched pick list for: " + testName);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("✅ Matched pick list for: " + testName);
|
||||
}
|
||||
i = llGetListLength(names);
|
||||
}
|
||||
}
|
||||
if (idx == -1) {
|
||||
llOwnerSay("⚠️ Could not find player: " + player);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("⚠️ Could not find player: " + player);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -111,7 +119,9 @@ integer showPickManager(string player, key id) {
|
|||
}
|
||||
|
||||
// Don't show hardcoded dialog - let the Owner Dialog Handler manage this
|
||||
llOwnerSay("🧮 Calculator delegating pick management to dialog handler for: " + (string)id);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🧮 Calculator delegating pick management to dialog handler for: " + (string)id);
|
||||
}
|
||||
// Return without showing dialog - this should be handled by the proper dialog handler
|
||||
|
||||
// Return dummy value since LSL does not have a void return type
|
||||
|
|
@ -130,7 +140,9 @@ default {
|
|||
}
|
||||
|
||||
on_rez(integer start_param) {
|
||||
llOwnerSay("🔄 Game Calculator rezzed - reinitializing...");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔄 Game Calculator rezzed - reinitializing...");
|
||||
}
|
||||
|
||||
// Reset all state variables on rez
|
||||
lives = [];
|
||||
|
|
@ -139,10 +151,23 @@ default {
|
|||
names = [];
|
||||
pickQueue = [];
|
||||
|
||||
llOwnerSay("✅ Game Calculator reset complete after rez!");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("✅ Game Calculator reset complete after rez!");
|
||||
}
|
||||
}
|
||||
|
||||
link_message(integer sender, integer num, string str, key id) {
|
||||
// Handle verbose logging toggle
|
||||
if (num == MSG_TOGGLE_VERBOSE_LOGS) {
|
||||
VERBOSE_LOGGING = !VERBOSE_LOGGING;
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔊 [Game Calculator] Verbose logging ENABLED");
|
||||
} else {
|
||||
llOwnerSay("🔊 [Game Calculator] Verbose logging DISABLED");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle full reset from main controller
|
||||
if (num == -99999 && str == "FULL_RESET") {
|
||||
// Reset helper state
|
||||
|
|
@ -157,8 +182,20 @@ default {
|
|||
|
||||
if (num == MSG_SYNC_GAME_STATE) {
|
||||
list parts = llParseString2List(str, ["~"], []);
|
||||
|
||||
// Handle special RESET sync message
|
||||
if (llGetListLength(parts) >= 5 && llList2String(parts, 0) == "RESET") {
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔄 [Game Calculator] Received reset sync - ignoring during reset");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (llGetListLength(parts) < 4) {
|
||||
llOwnerSay("⚠️ Game Calculator: Incomplete sync message received, parts: " + (string)llGetListLength(parts));
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("⚠️ Game Calculator: Incomplete sync message received, parts: " + (string)llGetListLength(parts));
|
||||
llOwnerSay("⚠️ Incomplete game state received.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
lives = llCSV2List(llList2String(parts, 0));
|
||||
|
|
@ -183,7 +220,9 @@ default {
|
|||
}
|
||||
else if (num == MSG_GET_DICE_TYPE) {
|
||||
integer playerCount = (integer)str;
|
||||
llOwnerSay("📈 Calculator: Calculating dice type for " + (string)playerCount + " players");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📈 Calculator: Calculating dice type for " + (string)playerCount + " players");
|
||||
}
|
||||
integer result = getDiceType(playerCount);
|
||||
// Send result only to the requester (sender), not broadcast to everyone
|
||||
llMessageLinked(sender, MSG_DICE_TYPE_RESULT, (string)result, NULL_KEY);
|
||||
|
|
|
|||
226
Game_Manager.lsl
226
Game_Manager.lsl
|
|
@ -31,12 +31,16 @@ integer roundStarted = FALSE;
|
|||
integer diceTypeProcessed = FALSE; // Track if we've already processed dice type for this round
|
||||
integer ignorePicksSync = FALSE; // Temporarily ignore picks data sync during round initialization
|
||||
integer roundContinueInProgress = FALSE; // Prevent multiple rapid calls to continueCurrentRound
|
||||
integer lastSyncProcessTime = 0; // Track when we last processed a sync to prevent rapid duplicates
|
||||
|
||||
// Duplicate request prevention
|
||||
string lastHumanPickMessage = "";
|
||||
string lastBotPickMessage = "";
|
||||
integer lastProcessTime = 0;
|
||||
|
||||
// Verbose logging control - toggled by owner
|
||||
integer VERBOSE_LOGGING = FALSE;
|
||||
|
||||
// Message constants
|
||||
integer MSG_SHOW_DIALOG = 101;
|
||||
integer MSG_SHOW_ROLL_DIALOG = 301;
|
||||
|
|
@ -48,12 +52,53 @@ integer MSG_PLAYER_WON = 551;
|
|||
integer MSG_UPDATE_FLOAT = 2010;
|
||||
|
||||
continueCurrentRound() {
|
||||
// CRITICAL PROTECTION: Don't start rounds if game is ending or players are eliminated
|
||||
if (llGetListLength(names) <= 1) {
|
||||
llOwnerSay("🛑 [Game Manager] continueCurrentRound: Game ending (" + (string)llGetListLength(names) + " players) - aborting round start");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for eliminated players (0 lives) - don't continue if any exist
|
||||
integer i;
|
||||
for (i = 0; i < llGetListLength(names); i++) {
|
||||
string playerName = llList2String(names, i);
|
||||
integer playerIdx = llListFindList(names, [playerName]);
|
||||
if (playerIdx != -1 && playerIdx < llGetListLength(lives)) {
|
||||
integer playerLives = llList2Integer(lives, playerIdx);
|
||||
if (playerLives <= 0) {
|
||||
llOwnerSay("🛑 [Game Manager] continueCurrentRound: Found eliminated player " + playerName + " with " + (string)playerLives + " lives - aborting round start");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (perilPlayer == "" || perilPlayer == "NONE") {
|
||||
llOwnerSay("🛑 [Game Manager] continueCurrentRound: Invalid peril player (" + perilPlayer + ") - aborting round start");
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify peril player exists and has lives
|
||||
integer perilIdx = llListFindList(names, [perilPlayer]);
|
||||
if (perilIdx == -1) {
|
||||
llOwnerSay("🛑 [Game Manager] continueCurrentRound: Peril player " + perilPlayer + " not found in names list - aborting round start");
|
||||
return;
|
||||
}
|
||||
if (perilIdx >= llGetListLength(lives)) {
|
||||
llOwnerSay("🛑 [Game Manager] continueCurrentRound: Peril player " + perilPlayer + " index out of bounds - aborting round start");
|
||||
return;
|
||||
}
|
||||
integer perilLives = llList2Integer(lives, perilIdx);
|
||||
if (perilLives <= 0) {
|
||||
llOwnerSay("🛑 [Game Manager] continueCurrentRound: Peril player " + perilPlayer + " has " + (string)perilLives + " lives - aborting round start");
|
||||
return;
|
||||
}
|
||||
|
||||
// ALWAYS start fresh for a new round - clear all picks and global picked numbers
|
||||
// After a roll, we need everyone to pick new numbers
|
||||
picksData = [];
|
||||
globalPickedNumbers = [];
|
||||
|
||||
llOwnerSay("🔄 [Game Manager] continueCurrentRound: Cleared picks data, starting fresh");
|
||||
llOwnerSay("🔄 [Game Manager] continueCurrentRound: All validation passed, starting fresh round with " + (string)llGetListLength(names) + " players");
|
||||
|
||||
// Set round as started for the new round
|
||||
roundStarted = TRUE;
|
||||
|
|
@ -61,7 +106,6 @@ continueCurrentRound() {
|
|||
|
||||
// Create new pick queue with peril player first
|
||||
pickQueue = [perilPlayer];
|
||||
integer i;
|
||||
for (i = 0; i < llGetListLength(names); i++) {
|
||||
string playerName = llList2String(names, i);
|
||||
if (playerName != perilPlayer) {
|
||||
|
|
@ -146,6 +190,12 @@ startNextRound() {
|
|||
}
|
||||
|
||||
showNextPickerDialog() {
|
||||
// CRITICAL PROTECTION: Don't show dialogs if game is ending or players eliminated
|
||||
if (llGetListLength(names) <= 1) {
|
||||
llOwnerSay("🛑 [Game Manager] showNextPickerDialog: Game ending (" + (string)llGetListLength(names) + " players) - aborting dialog");
|
||||
return;
|
||||
}
|
||||
|
||||
if (diceType <= 0) {
|
||||
llOwnerSay("❌ Cannot show picker dialog: diceType not set (" + (string)diceType + ")");
|
||||
return;
|
||||
|
|
@ -162,6 +212,24 @@ showNextPickerDialog() {
|
|||
return;
|
||||
}
|
||||
|
||||
// CRITICAL: Verify this player is still alive and in the game
|
||||
integer playerIdx = llListFindList(names, [firstName]);
|
||||
if (playerIdx == -1) {
|
||||
llOwnerSay("❌ Cannot show picker dialog: player " + firstName + " not found in current game");
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerIdx >= llGetListLength(lives)) {
|
||||
llOwnerSay("❌ Cannot show picker dialog: player " + firstName + " index out of bounds for lives list");
|
||||
return;
|
||||
}
|
||||
|
||||
integer playerLives = llList2Integer(lives, playerIdx);
|
||||
if (playerLives <= 0) {
|
||||
llOwnerSay("❌ Cannot show picker dialog: player " + firstName + " has been eliminated (" + (string)playerLives + " lives)");
|
||||
return;
|
||||
}
|
||||
|
||||
// CHECK: Don't show dialog if this player already has picks to prevent loops
|
||||
integer alreadyHasPicks = FALSE;
|
||||
integer i;
|
||||
|
|
@ -371,10 +439,17 @@ default {
|
|||
}
|
||||
}
|
||||
|
||||
// Handle special RESET sync message
|
||||
if (llGetListLength(parts) >= 5 && llList2String(parts, 0) == "RESET") {
|
||||
llOwnerSay("🔄 [Game Manager] Received reset sync - ignoring during reset");
|
||||
return;
|
||||
}
|
||||
|
||||
if (llGetListLength(parts) < 4) {
|
||||
llOwnerSay("⚠️ [Game Manager] Incomplete sync message received, parts: " + (string)llGetListLength(parts) + " - IGNORING");
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
{
|
||||
list newLives = llCSV2List(llList2String(parts, 0));
|
||||
|
||||
string picksDataStr = llList2String(parts, 1);
|
||||
|
|
@ -463,36 +538,91 @@ default {
|
|||
if (llGetListLength(newNames) == 1) {
|
||||
string winner = llList2String(newNames, 0);
|
||||
llOwnerSay("🏆 [Game Manager] Victory detected: " + winner + " wins!");
|
||||
// Reset round state to prevent loops
|
||||
// IMMEDIATE FULL STATE RESET to prevent any further round processing
|
||||
roundStarted = FALSE;
|
||||
perilPlayer = "";
|
||||
currentPickerIdx = 0;
|
||||
pickQueue = [];
|
||||
diceTypeProcessed = FALSE;
|
||||
roundContinueInProgress = FALSE;
|
||||
llOwnerSay("🔒 [Game Manager] Game state locked - no further round processing until reset");
|
||||
// Let Main Controller handle the victory celebration and reset
|
||||
return;
|
||||
} else {
|
||||
llOwnerSay("💀 [Game Manager] No survivors remain!");
|
||||
// Reset round state to prevent loops
|
||||
// IMMEDIATE FULL STATE RESET to prevent any further round processing
|
||||
roundStarted = FALSE;
|
||||
perilPlayer = "";
|
||||
currentPickerIdx = 0;
|
||||
pickQueue = [];
|
||||
diceTypeProcessed = FALSE;
|
||||
roundContinueInProgress = FALSE;
|
||||
llOwnerSay("🔒 [Game Manager] Game state locked - no further round processing until reset");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// STOP processing if game is over or peril player is invalid
|
||||
if (!roundStarted || perilPlayer == "" || perilPlayer == "NONE" || llGetListLength(newNames) <= 1) {
|
||||
// Game is ending or already ended, don't continue rounds
|
||||
// ADDITIONAL PROTECTION: Check for eliminated players in the current game
|
||||
// Don't continue rounds if any player has 0 lives (elimination in progress)
|
||||
integer hasEliminatedPlayers = FALSE;
|
||||
integer e;
|
||||
for (e = 0; e < llGetListLength(newLives) && !hasEliminatedPlayers; e++) {
|
||||
integer playerLives = llList2Integer(newLives, e);
|
||||
if (playerLives <= 0) {
|
||||
hasEliminatedPlayers = TRUE;
|
||||
string eliminatedPlayerName = llList2String(newNames, e);
|
||||
llOwnerSay("⚠️ [Game Manager] ELIMINATION DETECTED: " + eliminatedPlayerName + " has 0 lives - stopping round progression");
|
||||
}
|
||||
}
|
||||
|
||||
if (hasEliminatedPlayers) {
|
||||
llOwnerSay("🛑 [Game Manager] Elimination in progress - waiting for Main Controller to handle victory/elimination sequence");
|
||||
// IMMEDIATE STATE RESET to prevent further processing during elimination
|
||||
roundStarted = FALSE;
|
||||
perilPlayer = "";
|
||||
currentPickerIdx = 0;
|
||||
pickQueue = [];
|
||||
diceTypeProcessed = FALSE;
|
||||
roundContinueInProgress = FALSE;
|
||||
llOwnerSay("🔒 [Game Manager] Round state reset due to elimination - awaiting Main Controller reset");
|
||||
return; // Don't continue rounds during elimination
|
||||
}
|
||||
|
||||
// Only process game continuation if we have a valid active game
|
||||
// During initial player joining (roundStarted=FALSE, single player), just skip processing quietly
|
||||
if (!roundStarted) {
|
||||
// Game hasn't started yet (normal during player joining) - no warning needed
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for actual game ending conditions only during active games
|
||||
if (perilPlayer == "" || perilPlayer == "NONE") {
|
||||
llOwnerSay("⚠️ [Game Manager] Game ending - invalid peril player (" + perilPlayer + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for game ending when we had multiple players but now have 1 or less
|
||||
if (llGetListLength(newNames) <= 1 && llGetListLength(pickQueue) > 1) {
|
||||
llOwnerSay("⚠️ [Game Manager] Game ending - only " + (string)llGetListLength(newNames) + " player(s) remaining");
|
||||
return;
|
||||
}
|
||||
|
||||
// Only show debug and continue if game is actually active
|
||||
llOwnerSay("🔍 [Game Manager] DEBUG - allPicksEmpty: " + (string)allPicksEmpty + ", perilPlayer: " + perilPlayer + ", roundStarted: " + (string)roundStarted);
|
||||
if (VERBOSE_LOGGING) llOwnerSay("🔍 [Game Manager] DEBUG - allPicksEmpty: " + (string)allPicksEmpty + ", perilPlayer: " + perilPlayer + ", roundStarted: " + (string)roundStarted);
|
||||
|
||||
// If picks are empty, round started, and we have a valid peril player, continue to next round
|
||||
// This works for ALL outcomes: Direct Hit, No Shield, AND Plot Twist
|
||||
// PROTECTION: Prevent rapid consecutive calls to continueCurrentRound
|
||||
// ADDITIONAL PROTECTION: Verify peril player still exists in the game
|
||||
// CRITICAL FIX: Prevent double processing during Plot Twist scenarios
|
||||
// NEW PROTECTION: Prevent duplicate processing within short time windows
|
||||
integer currentTime = llGetUnixTime();
|
||||
integer perilPlayerExists = (perilPlayer != "" && perilPlayer != "NONE" && llListFindList(newNames, [perilPlayer]) != -1);
|
||||
if (allPicksEmpty && roundStarted && perilPlayerExists && llGetListLength(newNames) > 1 && !roundContinueInProgress) {
|
||||
integer timingOk = ((currentTime - lastSyncProcessTime) > 1);
|
||||
integer gameActive = (roundStarted && llGetListLength(newNames) > 1);
|
||||
integer canContinue = (allPicksEmpty && perilPlayerExists && !roundContinueInProgress);
|
||||
|
||||
if (gameActive && canContinue && timingOk) {
|
||||
// ADDITIONAL CHECK: Don't start new rounds if we just received a peril player update
|
||||
// This prevents double round starts during Plot Twist scenarios
|
||||
if (receivedPerilPlayer != "NONE" && receivedPerilPlayer != "" && receivedPerilPlayer != originalPerilPlayer) {
|
||||
|
|
@ -503,6 +633,7 @@ default {
|
|||
|
||||
llOwnerSay("🎯 [Game Manager] Round complete - starting next round with peril player: " + perilPlayer);
|
||||
roundContinueInProgress = TRUE; // Set flag to prevent rapid calls
|
||||
lastSyncProcessTime = currentTime; // Record processing time
|
||||
llSleep(2.0); // Brief pause for dramatic effect
|
||||
continueCurrentRound(); // Use continueCurrentRound instead of startNextRound
|
||||
roundContinueInProgress = FALSE; // Clear flag after completion
|
||||
|
|
@ -517,7 +648,9 @@ default {
|
|||
// Receive pick queue updates
|
||||
if (num == MSG_SYNC_PICKQUEUE) {
|
||||
list newPickQueue = llCSV2List(str);
|
||||
llOwnerSay("🔍 [Game Manager] Received pickQueue sync from sender " + (string)sender + ": '" + str + "' (" + (string)llGetListLength(newPickQueue) + " items)");
|
||||
string senderStr = (string)sender;
|
||||
string queueLenStr = (string)llGetListLength(newPickQueue);
|
||||
llOwnerSay("🔍 [Game Manager] Received pickQueue sync from sender " + senderStr + ": '" + str + "' (" + queueLenStr + " items)");
|
||||
|
||||
// STRONGEST PROTECTION: Never accept empty pickQueues during any active round
|
||||
if (roundStarted && llGetListLength(pickQueue) > 0 && (str == "" || llGetListLength(newPickQueue) == 0)) {
|
||||
|
|
@ -692,7 +825,23 @@ default {
|
|||
llOwnerSay("🟢 " + playerName + " picks saved: " + picksStr);
|
||||
|
||||
llSay(0, "🎯 " + playerName + " stakes their life on numbers: " + picksStr + " 🎲");
|
||||
// Don't sync to Main here - it creates loops
|
||||
|
||||
// IMMEDIATE FLOATER UPDATE: Update this player's floater right after their pick is saved
|
||||
// First sync the picks data so Floater Manager has the latest picks, then update the floater
|
||||
llOwnerSay("🔄 [Game Manager] Syncing picks and updating floater immediately for " + playerName + " with new picks");
|
||||
|
||||
// Send just the updated picks data to Floater Manager (lightweight sync)
|
||||
string picksDataStr = llDumpList2String(picksData, "^");
|
||||
string lightSync = llList2CSV(lives) + "~" + picksDataStr + "~" + perilPlayer + "~" + llList2CSV(names);
|
||||
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, lightSync, NULL_KEY);
|
||||
|
||||
// Brief delay to ensure sync reaches Floater Manager before update request
|
||||
llSleep(0.1);
|
||||
|
||||
// Now update the floater
|
||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, playerName, llList2Key(players, idx));
|
||||
|
||||
// Don't do full sync to Main here - it creates loops, but targeted floater update is safe
|
||||
llSleep(HUMAN_PICK_DELAY);
|
||||
|
||||
// Move to next picker
|
||||
|
|
@ -708,7 +857,7 @@ default {
|
|||
// CRITICAL: Sync the updated picks data to all modules before roll phase
|
||||
llOwnerSay("🔄 [Game Manager] Syncing final picks data before roll phase...");
|
||||
syncStateToMain();
|
||||
llSleep(0.2); // Brief delay to ensure sync propagates
|
||||
llSleep(1.0); // Longer delay to ensure sync propagates to all modules
|
||||
|
||||
integer perilIdx = llListFindList(names, [perilPlayer]);
|
||||
if (perilIdx != -1) {
|
||||
|
|
@ -768,7 +917,7 @@ default {
|
|||
// CRITICAL: Sync the updated picks data to all modules before roll phase
|
||||
llOwnerSay("🔄 [Game Manager] Syncing final picks data before roll phase...");
|
||||
syncStateToMain();
|
||||
llSleep(0.2); // Brief delay to ensure sync propagates
|
||||
llSleep(1.0); // Longer delay to ensure sync propagates to all modules
|
||||
|
||||
// Force close any active number picker dialogs
|
||||
llOwnerSay("🚫 [Game Manager] Sending CLOSE_ALL_DIALOGS command");
|
||||
|
|
@ -815,7 +964,23 @@ default {
|
|||
}
|
||||
|
||||
llSay(0, "🎯 " + playerName + " (bot) stakes their digital life on numbers: " + picksStr + " 🎲");
|
||||
// Don't sync to Main here - it creates loops
|
||||
|
||||
// IMMEDIATE FLOATER UPDATE: Update this bot's floater right after their pick is saved
|
||||
// First sync the picks data so Floater Manager has the latest picks, then update the floater
|
||||
llOwnerSay("🔄 [Game Manager] Syncing picks and updating floater immediately for " + playerName + " (bot) with new picks");
|
||||
|
||||
// Send just the updated picks data to Floater Manager (lightweight sync)
|
||||
string picksDataStr = llDumpList2String(picksData, "^");
|
||||
string lightSync = llList2CSV(lives) + "~" + picksDataStr + "~" + perilPlayer + "~" + llList2CSV(names);
|
||||
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, lightSync, NULL_KEY);
|
||||
|
||||
// Brief delay to ensure sync reaches Floater Manager before update request
|
||||
llSleep(0.1);
|
||||
|
||||
// Now update the floater
|
||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, playerName, NULL_KEY);
|
||||
|
||||
// Don't do full sync to Main here - it creates loops, but targeted floater update is safe
|
||||
llSleep(BOT_PICK_DELAY);
|
||||
|
||||
// Check if all players now have picks (more reliable than picker index)
|
||||
|
|
@ -870,6 +1035,20 @@ default {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle verbose logging toggle from Main Controller
|
||||
if (num == 9011 && llSubStringIndex(str, "VERBOSE_LOGGING|") == 0) {
|
||||
list parts = llParseString2List(str, ["|"], []);
|
||||
if (llGetListLength(parts) >= 2) {
|
||||
VERBOSE_LOGGING = (integer)llList2String(parts, 1);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔍 [Game Manager] Verbose logging ON");
|
||||
} else {
|
||||
llOwnerSay("🔍 [Game Manager] Verbose logging OFF");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle reset
|
||||
if (num == -99999 && str == "FULL_RESET") {
|
||||
players = names = lives = picksData = globalPickedNumbers = pickQueue = [];
|
||||
|
|
@ -880,7 +1059,22 @@ default {
|
|||
roundStarted = FALSE;
|
||||
diceTypeProcessed = FALSE; // Reset for new game
|
||||
roundContinueInProgress = FALSE; // Reset protection flag
|
||||
llOwnerSay("🎯 Game Manager reset!");
|
||||
lastSyncProcessTime = 0; // Reset sync timing
|
||||
llOwnerSay("🎯 Game Manager reset - ready for new game!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle emergency state reset (for when game gets stuck)
|
||||
if (num == -99998 && str == "EMERGENCY_RESET") {
|
||||
llOwnerSay("🚨 [Game Manager] Emergency reset triggered!");
|
||||
roundStarted = FALSE;
|
||||
perilPlayer = "";
|
||||
currentPickerIdx = 0;
|
||||
pickQueue = [];
|
||||
diceTypeProcessed = FALSE;
|
||||
roundContinueInProgress = FALSE;
|
||||
lastSyncProcessTime = 0; // Reset sync timing
|
||||
llOwnerSay("🔒 [Game Manager] Emergency state reset complete");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@
|
|||
// LINKSET COMMUNICATION - NO DISCOVERY NEEDED
|
||||
// =============================================================================
|
||||
|
||||
// Verbose logging control
|
||||
integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs
|
||||
integer MSG_TOGGLE_VERBOSE_LOGS = 9998; // Message to toggle verbose logging
|
||||
|
||||
// Message constants for link communication
|
||||
// Scoreboard messages (from link 1 - controller)
|
||||
integer MSG_GAME_STATUS = 3001;
|
||||
|
|
@ -50,6 +54,7 @@ string TEXTURE_DIRECT_HIT = "ecd2dba2-3969-6c39-ad59-319747307f55"; // Direct Hi
|
|||
string TEXTURE_NO_SHIELD = "2440174f-e385-44e2-8016-ac34934f11f5"; // No Shield texture
|
||||
string TEXTURE_PLOT_TWIST = "ec533379-4f7f-8183-e877-e68af703dcce"; // Plot Twist texture
|
||||
string TEXTURE_TITLE = "624bb7a7-e856-965c-bae8-94d75226c1bc"; // Title texture
|
||||
string TEXTURE_ELIMINATED_X = "90524092-03b0-1b3c-bcea-3ea5118c6dba"; // Red X overlay for eliminated players
|
||||
|
||||
// Player data - used for BOTH current game display AND leaderboard
|
||||
list playerNames = []; // Current game players for display
|
||||
|
|
@ -97,7 +102,9 @@ updateActionsPrim(string status) {
|
|||
|
||||
// SAFETY CHECK - prevent overflow into leaderboard prims
|
||||
if (ACTIONS_PRIM > LAST_PLAYER_PRIM) {
|
||||
llOwnerSay("❌ ERROR: ACTIONS_PRIM (" + (string)ACTIONS_PRIM + ") exceeds safety boundary (" + (string)LAST_PLAYER_PRIM + ")");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("❌ ERROR: ACTIONS_PRIM (" + (string)ACTIONS_PRIM + ") exceeds safety boundary (" + (string)LAST_PLAYER_PRIM + ")");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -326,8 +333,10 @@ clearAllPlayers() {
|
|||
|
||||
// SAFETY CHECK - prevent overflow into leaderboard prims
|
||||
if (profilePrimIndex > LAST_PLAYER_PRIM || heartsPrimIndex > LAST_PLAYER_PRIM) {
|
||||
llOwnerSay("❌ ERROR: Player prim index overflow! Attempted: " + (string)profilePrimIndex +
|
||||
" or " + (string)heartsPrimIndex + ", max allowed: " + (string)LAST_PLAYER_PRIM);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("❌ ERROR: Player prim index overflow! Attempted: " + (string)profilePrimIndex +
|
||||
" or " + (string)heartsPrimIndex + ", max allowed: " + (string)LAST_PLAYER_PRIM);
|
||||
}
|
||||
return; // Don't modify prims outside our range!
|
||||
}
|
||||
|
||||
|
|
@ -359,7 +368,9 @@ removePlayer(string playerName) {
|
|||
// Refresh the entire display to shift remaining players up
|
||||
refreshPlayerDisplay();
|
||||
|
||||
llOwnerSay("📊 Removed " + playerName + " from the scoreboard and shifted remaining players.");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📊 Removed " + playerName + " from the scoreboard and shifted remaining players.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -402,12 +413,17 @@ refreshPlayerDisplay() {
|
|||
|
||||
// SAFETY CHECK
|
||||
if (profilePrimIndex > LAST_PLAYER_PRIM || heartsPrimIndex > LAST_PLAYER_PRIM) {
|
||||
llOwnerSay("⚠️ Warning: Player " + name + " cannot be displayed - out of prim range");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("⚠️ Warning: Player " + name + " cannot be displayed - out of prim range");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine profile texture to use
|
||||
if (isBot(name)) {
|
||||
// Determine profile texture to use - check for elimination first
|
||||
if (lives <= 0) {
|
||||
// Player is eliminated - show red X overlay
|
||||
profileTexture = TEXTURE_ELIMINATED_X;
|
||||
} else if (isBot(name)) {
|
||||
profileTexture = TEXTURE_BOT_PROFILE;
|
||||
} else if (profileTexture == "" || profileTexture == "00000000-0000-0000-0000-000000000000") {
|
||||
profileTexture = TEXTURE_DEFAULT_PROFILE;
|
||||
|
|
@ -453,7 +469,9 @@ updatePlayerDisplay(string playerName, integer lives, string profileUUID) {
|
|||
// New player - find next available slot
|
||||
playerIndex = llGetListLength(playerNames);
|
||||
if (playerIndex >= 10) {
|
||||
llOwnerSay("WARNING: Maximum 10 players supported, ignoring: " + playerName);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("WARNING: Maximum 10 players supported, ignoring: " + playerName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -474,37 +492,51 @@ updatePlayerDisplay(string playerName, integer lives, string profileUUID) {
|
|||
|
||||
// SAFETY CHECK - prevent overflow into leaderboard prims
|
||||
if (profilePrimIndex > LAST_PLAYER_PRIM || heartsPrimIndex > LAST_PLAYER_PRIM) {
|
||||
llOwnerSay("❌ ERROR: Player prim index overflow! Attempted: " + (string)profilePrimIndex +
|
||||
" or " + (string)heartsPrimIndex + ", max allowed: " + (string)LAST_PLAYER_PRIM);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("❌ ERROR: Player prim index overflow! Attempted: " + (string)profilePrimIndex +
|
||||
" or " + (string)heartsPrimIndex + ", max allowed: " + (string)LAST_PLAYER_PRIM);
|
||||
}
|
||||
return; // Don't modify prims outside our range!
|
||||
}
|
||||
|
||||
// Update profile prim texture
|
||||
string profileTexture = profileUUID;
|
||||
// Update profile prim texture - show red X if eliminated, otherwise show profile
|
||||
string profileTexture;
|
||||
|
||||
// Check if we already have a cached profile texture for this player
|
||||
if (playerIndex < llGetListLength(playerProfiles)) {
|
||||
string cachedTexture = llList2String(playerProfiles, playerIndex);
|
||||
if (cachedTexture != "" && cachedTexture != profileUUID &&
|
||||
cachedTexture != TEXTURE_DEFAULT_PROFILE && cachedTexture != TEXTURE_BOT_PROFILE) {
|
||||
// We have a previously fetched profile picture, use it
|
||||
profileTexture = cachedTexture;
|
||||
if (lives <= 0) {
|
||||
// Player is eliminated - show red X (replaces profile picture)
|
||||
profileTexture = TEXTURE_ELIMINATED_X;
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("💀 Scoreboard: Showing elimination X for " + playerName);
|
||||
}
|
||||
}
|
||||
|
||||
// If no cached texture or using original UUID, determine what to use
|
||||
if (profileTexture == profileUUID) {
|
||||
if (isBot(playerName)) {
|
||||
profileTexture = TEXTURE_BOT_PROFILE;
|
||||
} else if (profileTexture == "" || profileTexture == "00000000-0000-0000-0000-000000000000") {
|
||||
profileTexture = TEXTURE_DEFAULT_PROFILE;
|
||||
} else {
|
||||
// Request profile picture via HTTP
|
||||
string URL_RESIDENT = "https://world.secondlife.com/resident/";
|
||||
key httpRequestID = llHTTPRequest(URL_RESIDENT + profileUUID, [HTTP_METHOD, "GET"], "");
|
||||
|
||||
// Store the HTTP request mapping: requestID -> playerIndex
|
||||
httpRequests += [httpRequestID, playerIndex];
|
||||
} else {
|
||||
// Player is alive - determine profile texture to use
|
||||
profileTexture = profileUUID;
|
||||
|
||||
// Check if we already have a cached profile texture for this player
|
||||
if (playerIndex < llGetListLength(playerProfiles)) {
|
||||
string cachedTexture = llList2String(playerProfiles, playerIndex);
|
||||
if (cachedTexture != "" && cachedTexture != profileUUID &&
|
||||
cachedTexture != TEXTURE_DEFAULT_PROFILE && cachedTexture != TEXTURE_BOT_PROFILE &&
|
||||
cachedTexture != TEXTURE_ELIMINATED_X) {
|
||||
// We have a previously fetched profile picture, use it
|
||||
profileTexture = cachedTexture;
|
||||
}
|
||||
}
|
||||
|
||||
// If no cached texture or using original UUID, determine what to use
|
||||
if (profileTexture == profileUUID) {
|
||||
if (isBot(playerName)) {
|
||||
profileTexture = TEXTURE_BOT_PROFILE;
|
||||
} else if (profileTexture == "" || profileTexture == "00000000-0000-0000-0000-000000000000") {
|
||||
profileTexture = TEXTURE_DEFAULT_PROFILE;
|
||||
} else {
|
||||
// Request profile picture via HTTP
|
||||
string URL_RESIDENT = "https://world.secondlife.com/resident/";
|
||||
key httpRequestID = llHTTPRequest(URL_RESIDENT + profileUUID, [HTTP_METHOD, "GET"], "");
|
||||
|
||||
// Store the HTTP request mapping: requestID -> playerIndex
|
||||
httpRequests += [httpRequestID, playerIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -525,8 +557,10 @@ updatePlayerDisplay(string playerName, integer lives, string profileUUID) {
|
|||
|
||||
default {
|
||||
state_entry() {
|
||||
llOwnerSay("📊 Scoreboard Manager ready! (Linkset Version)");
|
||||
llOwnerSay("📊 Managing prims " + (string)FIRST_PLAYER_PRIM + "-" + (string)LAST_PLAYER_PRIM);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📊 Scoreboard Manager ready! (Linkset Version)");
|
||||
llOwnerSay("📊 Managing prims " + (string)FIRST_PLAYER_PRIM + "-" + (string)LAST_PLAYER_PRIM);
|
||||
}
|
||||
|
||||
// Initialize profile picture extraction constants
|
||||
profile_key_prefix_length = llStringLength(profile_key_prefix);
|
||||
|
|
@ -538,11 +572,24 @@ default {
|
|||
// Generate initial leaderboard
|
||||
generateLeaderboardText();
|
||||
|
||||
llOwnerSay("✅ Linkset communication active - no discovery needed!");
|
||||
llOwnerSay("📊 Loaded " + (string)llGetListLength(leaderboardNames) + " players from leaderboard data");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("✅ Linkset communication active - no discovery needed!");
|
||||
llOwnerSay("📊 Loaded " + (string)llGetListLength(leaderboardNames) + " players from leaderboard data");
|
||||
}
|
||||
}
|
||||
|
||||
link_message(integer sender, integer num, string str, key id) {
|
||||
// Handle verbose logging toggle
|
||||
if (num == MSG_TOGGLE_VERBOSE_LOGS) {
|
||||
VERBOSE_LOGGING = !VERBOSE_LOGGING;
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔊 [Scoreboard] Verbose logging ENABLED");
|
||||
} else {
|
||||
llOwnerSay("🔊 [Scoreboard] Verbose logging DISABLED");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Only listen to messages from the main controller (link 1)
|
||||
if (sender != 1) return;
|
||||
|
||||
|
|
@ -557,7 +604,9 @@ default {
|
|||
string profileUUID = llList2String(parts, 2);
|
||||
|
||||
// Debug: Show what data scoreboard received
|
||||
llOwnerSay("📊 Scoreboard received update: " + playerName + " has " + (string)lives + " hearts");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📊 Scoreboard received update: " + playerName + " has " + (string)lives + " hearts");
|
||||
}
|
||||
|
||||
updatePlayerDisplay(playerName, lives, profileUUID);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
// LINKSET COMMUNICATION - NO DISCOVERY NEEDED
|
||||
// =============================================================================
|
||||
|
||||
// Verbose logging control
|
||||
integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs
|
||||
integer MSG_TOGGLE_VERBOSE_LOGS = 9998; // Message to toggle verbose logging
|
||||
|
||||
// Message constants for link communication
|
||||
// Leaderboard messages (from link 2 - scoreboard)
|
||||
integer MSG_GAME_WON = 3010;
|
||||
|
|
@ -112,16 +116,29 @@ distributeToBank(string text, list linkNumbers) {
|
|||
|
||||
default {
|
||||
state_entry() {
|
||||
llOwnerSay("📋 Leaderboard Communication Script ready! (Linkset Version)");
|
||||
llOwnerSay("📋 This is link " + (string)llGetLinkNumber() + " - should be link 25");
|
||||
llOwnerSay("📋 Managing XyzzyText links 25-72 (48 prims total)");
|
||||
llOwnerSay("✅ Linkset communication active - listening for messages from link 2!");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📋 Leaderboard Communication Script ready! (Linkset Version)");
|
||||
llOwnerSay("📋 This is link " + (string)llGetLinkNumber() + " - should be link 25");
|
||||
llOwnerSay("📋 Managing XyzzyText links 25-72 (48 prims total)");
|
||||
llOwnerSay("✅ Linkset communication active - listening for messages from link 2!");
|
||||
}
|
||||
|
||||
// Start with blank display - real data will come from scoreboard script
|
||||
distributeFullText("");
|
||||
}
|
||||
|
||||
link_message(integer sender, integer num, string str, key id) {
|
||||
// Handle verbose logging toggle
|
||||
if (num == MSG_TOGGLE_VERBOSE_LOGS) {
|
||||
VERBOSE_LOGGING = !VERBOSE_LOGGING;
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔊 [Leaderboard] Verbose logging ENABLED");
|
||||
} else {
|
||||
llOwnerSay("🔊 [Leaderboard] Verbose logging DISABLED");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Only listen to messages from the scoreboard (link 2)
|
||||
if (sender != 2) {
|
||||
return;
|
||||
|
|
@ -154,7 +171,9 @@ default {
|
|||
else if (llSubStringIndex(str, "LEADERBOARD") == 0) {
|
||||
// Legacy leaderboard format - not used with linkset version
|
||||
// Scoreboard now sends pre-formatted text via FORMATTED_TEXT
|
||||
llOwnerSay("⚠️ [Leaderboard] Received legacy LEADERBOARD format - ignoring");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("⚠️ [Leaderboard] Received legacy LEADERBOARD format - ignoring");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,12 @@ integer TIMER_DISCOVERY = 3;
|
|||
integer currentTimerMode = 0; // Track what the timer is currently doing
|
||||
float timerInterval = 1.0; // How often timer() is called for checks
|
||||
|
||||
// Debug control - set to TRUE for verbose pick debugging, FALSE for normal operation
|
||||
integer DEBUG_PICKS = FALSE;
|
||||
|
||||
// Verbose logging system - affects all modules when toggled
|
||||
integer VERBOSE_LOGGING = FALSE;
|
||||
|
||||
// Status message timing
|
||||
float STATUS_DISPLAY_TIME = 8.0; // How long to show status messages on scoreboard
|
||||
integer statusTimer = 0; // Track when status messages were sent
|
||||
|
|
@ -92,6 +98,9 @@ list picksData = [];
|
|||
list readyPlayers = [];
|
||||
list floaterChannels = []; // Track actual floater channels for cleanup
|
||||
|
||||
// Victory state tracking
|
||||
integer victoryInProgress = FALSE;
|
||||
|
||||
// Lockout system variables
|
||||
integer isLocked = FALSE;
|
||||
key gameOwner;
|
||||
|
|
@ -260,6 +269,12 @@ sendStatusMessage(string status) {
|
|||
|
||||
// Forward game state to helpers when it changes
|
||||
updateHelpers() {
|
||||
// Skip sending sync messages during victory sequence to prevent stale data broadcasts
|
||||
if (victoryInProgress) {
|
||||
llOwnerSay("🏆 [Main Controller] Skipping updateHelpers during victory sequence");
|
||||
return;
|
||||
}
|
||||
|
||||
checkMemoryUsage("updateHelpers_start");
|
||||
|
||||
string perilForSync = "NONE";
|
||||
|
|
@ -272,11 +287,40 @@ updateHelpers() {
|
|||
if (dataCount > 0) {
|
||||
picksDataStr = llDumpList2String(picksData, "^");
|
||||
checkMemoryUsage("updateHelpers_after_picks_processing");
|
||||
|
||||
// DEBUG: Log what we're sending
|
||||
llOwnerSay("📤 [Main Controller] Sending picks data (" + (string)dataCount + " entries):");
|
||||
integer debugIdx;
|
||||
for (debugIdx = 0; debugIdx < llGetListLength(picksData); debugIdx++) {
|
||||
llOwnerSay(" [" + (string)debugIdx + "]: " + llList2String(picksData, debugIdx));
|
||||
}
|
||||
llOwnerSay("📤 [Main Controller] Encoded picks data: '" + picksDataStr + "'");
|
||||
} else {
|
||||
llOwnerSay("📤 [Main Controller] No picks data to send (empty)");
|
||||
}
|
||||
|
||||
// Send core game state to internal scripts (include players list for floater management)
|
||||
string syncMessage = llList2CSV(lives) + "~" + picksDataStr + "~" + perilForSync + "~" + llList2CSV(names) + "~" + llList2CSV(players);
|
||||
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, syncMessage, NULL_KEY);
|
||||
// CRITICAL: Always ensure we have at least 4 parts in sync message to prevent incomplete sync warnings
|
||||
string livesStr = llList2CSV(lives);
|
||||
string namesStr = llList2CSV(names);
|
||||
string playersStr = llList2CSV(players);
|
||||
|
||||
// During reset/empty states, ensure we have proper empty values instead of completely empty strings
|
||||
if (livesStr == "") livesStr = "EMPTY_LIVES";
|
||||
if (namesStr == "") namesStr = "EMPTY_NAMES";
|
||||
if (playersStr == "") playersStr = "EMPTY_PLAYERS";
|
||||
|
||||
string syncMessage = livesStr + "~" + picksDataStr + "~" + perilForSync + "~" + namesStr + "~" + playersStr;
|
||||
llOwnerSay("📤 [Main Controller] Full sync message: " + syncMessage);
|
||||
|
||||
// Only send sync if we have actual game data OR it's a proper reset sync
|
||||
if (llGetListLength(names) > 0 || picksDataStr != "EMPTY" || perilForSync != "NONE") {
|
||||
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, syncMessage, NULL_KEY);
|
||||
} else {
|
||||
// This is a true empty state (reset), send a special reset sync instead
|
||||
llOwnerSay("📤 [Main Controller] Sending reset sync instead of empty sync");
|
||||
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, "RESET~RESET~RESET~RESET~RESET", NULL_KEY);
|
||||
}
|
||||
|
||||
// Send scoreboard updates using link messages
|
||||
integer i;
|
||||
|
|
@ -342,6 +386,7 @@ resetGame() {
|
|||
currentPickerIdx = 0;
|
||||
roundStarted = FALSE;
|
||||
gameStarting = FALSE;
|
||||
victoryInProgress = FALSE;
|
||||
currentPicker = NULL_KEY;
|
||||
timeoutTimer = 0;
|
||||
lastWarning = 0;
|
||||
|
|
@ -405,16 +450,52 @@ default {
|
|||
key toucher = llDetectedKey(0);
|
||||
integer idx = llListFindList(players, [toucher]);
|
||||
|
||||
// Check if player has a current dialog they can recover
|
||||
// Enhanced disconnect/reconnect handling - check if this player should be picking
|
||||
if (idx != -1 && toucher != llGetOwner() && roundStarted) {
|
||||
string playerName = llList2String(names, idx);
|
||||
if (currentPicker == toucher) {
|
||||
llRegionSayTo(toucher, 0, "🔄 Restoring your number picking dialog...");
|
||||
|
||||
// ENHANCED: Check if this player is supposed to be the current picker based on game state
|
||||
// This handles cases where currentPicker got corrupted due to disconnect
|
||||
integer shouldBePicking = FALSE;
|
||||
if (currentPickerIdx < llGetListLength(pickQueue)) {
|
||||
string expectedPickerName = llList2String(pickQueue, currentPickerIdx);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔍 [Touch Debug] Player: " + playerName + ", Expected picker: " + expectedPickerName + ", PickerIdx: " + (string)currentPickerIdx);
|
||||
llOwnerSay("🔍 [Touch Debug] CurrentPicker: " + (string)currentPicker + ", Toucher: " + (string)toucher);
|
||||
}
|
||||
if (playerName == expectedPickerName) {
|
||||
shouldBePicking = TRUE;
|
||||
// Fix corrupted currentPicker state
|
||||
if (currentPicker != toucher) {
|
||||
llOwnerSay("🔄 [Disconnect Recovery] Fixing currentPicker for returning player: " + playerName);
|
||||
llOwnerSay("🔄 [Disconnect Recovery] Old currentPicker: " + (string)currentPicker + " -> New: " + (string)toucher);
|
||||
currentPicker = toucher;
|
||||
}
|
||||
}
|
||||
} else if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔍 [Touch Debug] Pick phase complete - currentPickerIdx (" + (string)currentPickerIdx + ") >= pickQueue length (" + (string)llGetListLength(pickQueue) + ")");
|
||||
}
|
||||
|
||||
// FALLBACK: If game is stuck and no one is set as current picker, but we're in pick phase,
|
||||
// let any registered player attempt to resume (with owner confirmation)
|
||||
if (!shouldBePicking && currentPicker == NULL_KEY && currentPickerIdx < llGetListLength(pickQueue)) {
|
||||
if (toucher == llGetOwner()) {
|
||||
llOwnerSay("⚠️ [Recovery] Game appears stuck - no current picker set!");
|
||||
llOwnerSay("⚠️ [Recovery] Expected picker: " + llList2String(pickQueue, currentPickerIdx));
|
||||
llOwnerSay("⚠️ [Recovery] You (owner) touched - fixing by assigning you as current picker");
|
||||
currentPicker = toucher;
|
||||
shouldBePicking = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for dialog recovery (original logic OR enhanced detection)
|
||||
if (currentPicker == toucher || shouldBePicking) {
|
||||
llRegionSayTo(toucher, 0, "🔄 Welcome back! Restoring your number picking dialog...");
|
||||
llMessageLinked(LINK_SET, MSG_GET_CURRENT_DIALOG, playerName, toucher);
|
||||
return;
|
||||
}
|
||||
else if (playerName == perilPlayer && currentPickerIdx >= llGetListLength(pickQueue)) {
|
||||
llRegionSayTo(toucher, 0, "🔄 Restoring your roll dialog...");
|
||||
llRegionSayTo(toucher, 0, "🔄 Welcome back! Restoring your roll dialog...");
|
||||
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, toucher);
|
||||
return;
|
||||
}
|
||||
|
|
@ -614,14 +695,8 @@ default {
|
|||
floaterChannels = llDeleteSubList(floaterChannels, idx, idx);
|
||||
|
||||
|
||||
// CHANGED: Send to scoreboard, which will handle leaderboard updates
|
||||
llMessageLinked(SCOREBOARD_LINK, MSG_GAME_LOST, eliminatedPlayer, NULL_KEY);
|
||||
|
||||
sendStatusMessage("Elimination");
|
||||
updateHelpers();
|
||||
|
||||
// CRITICAL: Update perilPlayer if the eliminated player was the peril player
|
||||
// This prevents sending stale sync messages in updateHelpers()
|
||||
// CRITICAL: Update perilPlayer BEFORE any updateHelpers() calls
|
||||
// This prevents sending stale sync messages with eliminated players
|
||||
if (perilPlayer == eliminatedPlayer) {
|
||||
// Find first remaining alive player to be new peril player
|
||||
string newPerilPlayer = "";
|
||||
|
|
@ -646,10 +721,32 @@ default {
|
|||
}
|
||||
}
|
||||
|
||||
// Check for victory condition AFTER the 0 hearts display AND removal
|
||||
// CHANGED: Send to scoreboard, which will handle leaderboard updates
|
||||
llMessageLinked(SCOREBOARD_LINK, MSG_GAME_LOST, eliminatedPlayer, NULL_KEY);
|
||||
|
||||
sendStatusMessage("Elimination");
|
||||
|
||||
// Check for victory condition BEFORE calling updateHelpers()
|
||||
if (llGetListLength(names) <= 1) {
|
||||
if (llGetListLength(names) == 1) {
|
||||
string winner = llList2String(names, 0);
|
||||
|
||||
// CRITICAL: Set victory flag immediately to prevent sync processing
|
||||
victoryInProgress = TRUE;
|
||||
|
||||
// CRITICAL: Clear all game state immediately before any sync messages
|
||||
// This prevents broadcasting stale winner data to other scripts
|
||||
players = [];
|
||||
names = [];
|
||||
lives = [];
|
||||
picksData = [];
|
||||
perilPlayer = "";
|
||||
pickQueue = [];
|
||||
currentPickerIdx = 0;
|
||||
roundStarted = FALSE;
|
||||
gameStarting = FALSE;
|
||||
currentPicker = NULL_KEY;
|
||||
|
||||
llSay(0, "✨ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!");
|
||||
|
||||
llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY);
|
||||
|
|
@ -662,6 +759,19 @@ default {
|
|||
sendStatusMessage("Victory");
|
||||
llSleep(STATUS_DISPLAY_TIME + 1.0);
|
||||
} else {
|
||||
// CRITICAL: Set victory flag and clear state for no survivors scenario too
|
||||
victoryInProgress = TRUE;
|
||||
players = [];
|
||||
names = [];
|
||||
lives = [];
|
||||
picksData = [];
|
||||
perilPlayer = "";
|
||||
pickQueue = [];
|
||||
currentPickerIdx = 0;
|
||||
roundStarted = FALSE;
|
||||
gameStarting = FALSE;
|
||||
currentPicker = NULL_KEY;
|
||||
|
||||
llSay(0, "💀 DESPAIR WINS! No Ultimate Survivors remain!");
|
||||
llSleep(STATUS_DISPLAY_TIME + 1.0);
|
||||
}
|
||||
|
|
@ -669,12 +779,27 @@ default {
|
|||
return;
|
||||
}
|
||||
|
||||
// Only call updateHelpers() if victory was NOT detected
|
||||
updateHelpers();
|
||||
|
||||
// Final updateHelpers() call after all elimination processing is complete
|
||||
updateHelpers();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle emergency reset for stuck games
|
||||
if (num == -99998 && str == "EMERGENCY_RESET") {
|
||||
llOwnerSay("🚨 [Main Controller] Emergency reset triggered - sending emergency reset to all scripts");
|
||||
// Signal all scripts to emergency reset
|
||||
llMessageLinked(LINK_SET, -99998, "EMERGENCY_RESET", NULL_KEY);
|
||||
llSleep(0.5);
|
||||
// Then do full reset
|
||||
resetGame();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle game won
|
||||
if (num == 998) {
|
||||
list parts = llParseString2List(str, ["|"], []);
|
||||
|
|
@ -690,6 +815,12 @@ default {
|
|||
|
||||
// Handle incoming sync updates from Roll Confetti Module
|
||||
if (num == MSG_SYNC_GAME_STATE) {
|
||||
// Skip processing sync messages during victory sequence
|
||||
if (victoryInProgress) {
|
||||
llOwnerSay("🏆 [Main Controller] Ignoring sync message during victory sequence");
|
||||
return;
|
||||
}
|
||||
|
||||
list parts = llParseString2List(str, ["~"], []);
|
||||
if (llGetListLength(parts) >= 4) {
|
||||
list newLives = llCSV2List(llList2String(parts, 0));
|
||||
|
|
@ -905,6 +1036,33 @@ default {
|
|||
integer ch = FLOATER_BASE_CHANNEL + idx;
|
||||
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
|
||||
|
||||
// CRITICAL: If the leaving player was the peril player, update before updateHelpers()
|
||||
if (perilPlayer == leavingName) {
|
||||
// Find first remaining alive player to be new peril player
|
||||
string newPerilPlayer = "";
|
||||
integer k;
|
||||
for (k = 0; k < llGetListLength(names) && newPerilPlayer == ""; k++) {
|
||||
string candidateName = llList2String(names, k);
|
||||
if (candidateName != leavingName) { // Skip the player being removed
|
||||
integer candidateIdx = llListFindList(names, [candidateName]);
|
||||
if (candidateIdx != -1 && candidateIdx < llGetListLength(lives)) {
|
||||
integer candidateLives = llList2Integer(lives, candidateIdx);
|
||||
if (candidateLives > 0) {
|
||||
newPerilPlayer = candidateName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newPerilPlayer != "") {
|
||||
llOwnerSay("🎯 [Main Controller] Peril player left - assigning new peril player: " + newPerilPlayer);
|
||||
perilPlayer = newPerilPlayer;
|
||||
} else {
|
||||
llOwnerSay("⚠️ [Main Controller] No valid peril player candidates found after player left!");
|
||||
perilPlayer = "NONE";
|
||||
}
|
||||
}
|
||||
|
||||
// Update main controller's lists to match the message handler
|
||||
players = llDeleteSubList(players, idx, idx);
|
||||
names = llDeleteSubList(names, idx, idx);
|
||||
|
|
@ -965,6 +1123,22 @@ default {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle verbose logging toggle
|
||||
if (num == 9010 && str == "TOGGLE_VERBOSE_LOGS") {
|
||||
VERBOSE_LOGGING = !VERBOSE_LOGGING;
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔍 [Main Controller] Verbose logging turned ON");
|
||||
llSay(0, "🔍 Verbose logging system-wide: ON");
|
||||
} else {
|
||||
llOwnerSay("🔍 [Main Controller] Verbose logging turned OFF");
|
||||
llSay(0, "🔍 Verbose logging system-wide: OFF");
|
||||
}
|
||||
|
||||
// Broadcast the setting to all modules
|
||||
llMessageLinked(LINK_SET, 9011, "VERBOSE_LOGGING|" + (string)VERBOSE_LOGGING, id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle memory monitor messages
|
||||
if (num == MSG_EMERGENCY_CLEANUP) {
|
||||
emergencyMemoryCleanup();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
integer MSG_SHOW_DIALOG = 101;
|
||||
integer MSG_GET_CURRENT_DIALOG = 302;
|
||||
|
||||
// Verbose logging control
|
||||
integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs
|
||||
integer MSG_TOGGLE_VERBOSE_LOGS = 9998; // Message to toggle verbose logging
|
||||
|
||||
// =============================================================================
|
||||
// DYNAMIC CHANNEL CONFIGURATION
|
||||
// =============================================================================
|
||||
|
|
@ -35,8 +39,10 @@ initializeChannels() {
|
|||
NUMBERPICK_CHANNEL = calculateChannel(2); // ~-77200 range to match Main.lsl
|
||||
|
||||
// Report channel to owner for debugging
|
||||
llOwnerSay("🔧 [Number Picker Dialog] Dynamic channel initialized:");
|
||||
llOwnerSay(" Number Pick: " + (string)NUMBERPICK_CHANNEL);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔧 [Number Picker Dialog] Dynamic channel initialized:");
|
||||
llOwnerSay(" Number Pick: " + (string)NUMBERPICK_CHANNEL);
|
||||
}
|
||||
}
|
||||
|
||||
integer numberPickChannel; // Legacy variable, will be set dynamically
|
||||
|
|
@ -82,7 +88,9 @@ list getNumbersForPage(integer page, integer diceType) {
|
|||
}
|
||||
|
||||
showPickDialog(string name, key id, integer diceType, integer picks) {
|
||||
llOwnerSay("📋 [NumberPicker] showPickDialog called for " + name + ", dice:" + (string)diceType + ", picks:" + (string)picks);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📋 [NumberPicker] showPickDialog called for " + name + ", dice:" + (string)diceType + ", picks:" + (string)picks);
|
||||
}
|
||||
|
||||
if (diceType <= 0) {
|
||||
llOwnerSay("⚠️ Cannot show dialog: diceType is 0 or invalid.");
|
||||
|
|
@ -91,7 +99,9 @@ showPickDialog(string name, key id, integer diceType, integer picks) {
|
|||
|
||||
// Initialize or update picking session
|
||||
if (currentPlayer != name) {
|
||||
llOwnerSay("📋 [NumberPicker] Starting new session for " + name);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📋 [NumberPicker] Starting new session for " + name);
|
||||
}
|
||||
currentPlayer = name;
|
||||
currentPlayerKey = id;
|
||||
currentDiceType = diceType;
|
||||
|
|
@ -99,7 +109,9 @@ showPickDialog(string name, key id, integer diceType, integer picks) {
|
|||
currentPicks = [];
|
||||
currentPage = 0;
|
||||
} else {
|
||||
llOwnerSay("📋 [NumberPicker] Updating existing session for " + name);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📋 [NumberPicker] Updating existing session for " + name);
|
||||
}
|
||||
}
|
||||
|
||||
list options;
|
||||
|
|
@ -180,7 +192,9 @@ default {
|
|||
}
|
||||
|
||||
on_rez(integer start_param) {
|
||||
llOwnerSay("🔄 Number Picker rezzed - reinitializing...");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔄 Number Picker rezzed - reinitializing...");
|
||||
}
|
||||
|
||||
// Re-initialize dynamic channels
|
||||
initializeChannels();
|
||||
|
|
@ -202,20 +216,39 @@ default {
|
|||
|
||||
// Set up managed listener with dynamic channel
|
||||
listenHandle = llListen(numberPickChannel, "", NULL_KEY, "");
|
||||
llOwnerSay("✅ Number Picker reset complete after rez!");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("✅ Number Picker reset complete after rez!");
|
||||
}
|
||||
}
|
||||
|
||||
link_message(integer sender, integer num, string str, key id) {
|
||||
// Handle verbose logging toggle
|
||||
if (num == MSG_TOGGLE_VERBOSE_LOGS) {
|
||||
VERBOSE_LOGGING = !VERBOSE_LOGGING;
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔊 [NumberPicker] Verbose logging ENABLED");
|
||||
} else {
|
||||
llOwnerSay("🔊 [NumberPicker] Verbose logging DISABLED");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle dialog close command
|
||||
if (num == -9999 && str == "CLOSE_ALL_DIALOGS") {
|
||||
llOwnerSay("🚫 [NumberPicker] CLOSE_ALL_DIALOGS received! currentPlayer: " + currentPlayer + ", key: " + (string)currentPlayerKey);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🚫 [NumberPicker] CLOSE_ALL_DIALOGS received! currentPlayer: " + currentPlayer + ", key: " + (string)currentPlayerKey);
|
||||
}
|
||||
if (currentPlayerKey != NULL_KEY) {
|
||||
llOwnerSay("🚫 [NumberPicker] Forced dialog close for " + currentPlayer);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🚫 [NumberPicker] Forced dialog close for " + currentPlayer);
|
||||
}
|
||||
currentPlayer = "";
|
||||
currentPlayerKey = NULL_KEY;
|
||||
currentPicks = [];
|
||||
} else {
|
||||
llOwnerSay("🚫 [NumberPicker] No active dialog to close");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🚫 [NumberPicker] No active dialog to close");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -265,7 +298,9 @@ default {
|
|||
string playerName = str;
|
||||
// Only show dialog if this player is the current picker
|
||||
if (currentPlayer == playerName && currentPlayerKey == id) {
|
||||
llOwnerSay("🔄 Restoring dialog for " + playerName);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔄 Restoring dialog for " + playerName);
|
||||
}
|
||||
showPickDialog(currentPlayer, currentPlayerKey, currentDiceType, picksNeeded);
|
||||
} else {
|
||||
llRegionSayTo(id, 0, "❌ You don't have an active dialog to restore.");
|
||||
|
|
@ -276,13 +311,17 @@ default {
|
|||
listen(integer channel, string name, key id, string message) {
|
||||
// Ignore any responses if no active dialog session
|
||||
if (currentPlayerKey == NULL_KEY || currentPlayer == "") {
|
||||
llOwnerSay("🚫 [NumberPicker] Ignoring stale dialog response (no active session). Player: " + name + ", Key: " + (string)id + ", Message: " + message);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🚫 [NumberPicker] Ignoring stale dialog response (no active session). Player: " + name + ", Key: " + (string)id + ", Message: " + message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore responses from wrong player
|
||||
if (id != currentPlayerKey) {
|
||||
llOwnerSay("🚫 [NumberPicker] Ignoring dialog response from wrong player. Expected: " + (string)currentPlayerKey + ", Got: " + (string)id + ", Message: " + message);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🚫 [NumberPicker] Ignoring dialog response from wrong player. Expected: " + (string)currentPlayerKey + ", Got: " + (string)id + ", Message: " + message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -291,13 +330,17 @@ default {
|
|||
if (messageNum > 0 && messageNum <= currentDiceType) {
|
||||
// This looks like a number selection - verify it's not already globally picked
|
||||
if (llListFindList(globallyPickedNumbers, [message]) != -1) {
|
||||
llOwnerSay("🚫 [NumberPicker] Ignoring stale dialog response - number " + message + " already globally picked. Player: " + name);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🚫 [NumberPicker] Ignoring stale dialog response - number " + message + " already globally picked. Player: " + name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Also verify it's not already in current picks
|
||||
if (llListFindList(currentPicks, [message]) != -1) {
|
||||
llOwnerSay("🚫 [NumberPicker] Ignoring stale dialog response - number " + message + " already in current picks. Player: " + name);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🚫 [NumberPicker] Ignoring stale dialog response - number " + message + " already in current picks. Player: " + name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -325,19 +368,27 @@ default {
|
|||
|
||||
// Handle completion
|
||||
if (message == "✅ Done") {
|
||||
llOwnerSay("📋 [NumberPicker] Done button clicked by " + name + ", currentPicks: " + llList2CSV(currentPicks) + ", needed: " + (string)picksNeeded);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📋 [NumberPicker] Done button clicked by " + name + ", currentPicks: " + llList2CSV(currentPicks) + ", needed: " + (string)picksNeeded);
|
||||
}
|
||||
if (llGetListLength(currentPicks) >= picksNeeded) {
|
||||
string picksStr = llList2CSV(currentPicks);
|
||||
string response = "HUMAN_PICKED:" + currentPlayer + ":" + picksStr;
|
||||
llOwnerSay("📤 [NumberPicker] SENDING HUMAN_PICKED: " + response);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📤 [NumberPicker] SENDING HUMAN_PICKED: " + response);
|
||||
}
|
||||
llMessageLinked(LINK_SET, -9998, response, NULL_KEY);
|
||||
// Reset state
|
||||
llOwnerSay("📋 [NumberPicker] Resetting session state for " + currentPlayer);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("📋 [NumberPicker] Resetting session state for " + currentPlayer);
|
||||
}
|
||||
currentPlayer = "";
|
||||
currentPlayerKey = NULL_KEY;
|
||||
currentPicks = [];
|
||||
} else {
|
||||
llOwnerSay("⚠️ " + currentPlayer + " needs " + (string)(picksNeeded - llGetListLength(currentPicks)) + " more picks");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("⚠️ " + currentPlayer + " needs " + (string)(picksNeeded - llGetListLength(currentPicks)) + " more picks");
|
||||
}
|
||||
showPickDialog(currentPlayer, currentPlayerKey, currentDiceType, picksNeeded);
|
||||
}
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ list mainOwnerOptionsUnlocked = ["👥 Player Management", "🔄 Reset Options",
|
|||
// Sub-menu options for each category
|
||||
list playerManagementOptions = ["Add Test Player", "Kick Player", "⬅️ Back to Main"];
|
||||
list resetOptions = ["Reset Game", "Reset Leaderboard", "Reset All", "⬅️ Back to Main"];
|
||||
list troubleshootingOptions = ["Cleanup Floaters", "Force Floaters", "⬅️ Back to Main"];
|
||||
list troubleshootingOptions = ["Cleanup Floaters", "Force Floaters", "🔍 Toggle Verbose Logs", "⬅️ Back to Main"];
|
||||
|
||||
// State tracking for dynamic ready menu with race condition protection
|
||||
key pendingMenuPlayer = NULL_KEY;
|
||||
|
|
@ -640,6 +640,13 @@ default {
|
|||
// This will be handled by Main Controller's dialog listener
|
||||
// No need to forward via link_message - the Main Controller is already listening on DIALOG_CHANNEL
|
||||
}
|
||||
else if (msg == "🔍 Toggle Verbose Logs") {
|
||||
// Toggle verbose logging system-wide
|
||||
llOwnerSay("🔍 Toggling verbose logging system-wide...");
|
||||
llMessageLinked(LINK_SET, 9010, "TOGGLE_VERBOSE_LOGS", id);
|
||||
// Return to troubleshooting menu after toggling
|
||||
showTroubleshootingMenu(id);
|
||||
}
|
||||
else if (msg == "Manage Picks") {
|
||||
llMessageLinked(LINK_THIS, 202, "REQUEST_PLAYER_LIST", id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ integer rollInProgress = FALSE; // Prevent multiple simultaneous rolls
|
|||
integer lastRollTime = 0; // Track when last roll happened
|
||||
integer diceTypeProcessed = FALSE; // Prevent duplicate dice type processing
|
||||
|
||||
// Verbose logging control - toggled by owner
|
||||
integer VERBOSE_LOGGING = FALSE;
|
||||
|
||||
list getPicksFor(string nameInput) {
|
||||
integer i;
|
||||
for (i = 0; i < llGetListLength(picksData); i++) {
|
||||
|
|
@ -185,6 +188,20 @@ default {
|
|||
llOwnerSay("🎲 [Roll Module] RECEIVED GET_DICE_TYPE: " + str);
|
||||
}
|
||||
|
||||
// Handle verbose logging toggle from Main Controller
|
||||
if (num == 9011 && llSubStringIndex(str, "VERBOSE_LOGGING|") == 0) {
|
||||
list parts = llParseString2List(str, ["|"], []);
|
||||
if (llGetListLength(parts) >= 2) {
|
||||
VERBOSE_LOGGING = (integer)llList2String(parts, 1);
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔍 [Roll Module] Verbose logging ON");
|
||||
} else {
|
||||
llOwnerSay("🔍 [Roll Module] Verbose logging OFF");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle full reset from main controller
|
||||
if (num == -99999 && str == "FULL_RESET") {
|
||||
// Reset roll confetti module state
|
||||
|
|
@ -200,15 +217,27 @@ default {
|
|||
|
||||
// Handle game state sync from main controller
|
||||
if (num == MSG_SYNC_GAME_STATE) {
|
||||
if (VERBOSE_LOGGING) llOwnerSay("🔍 [Roll Module] Received sync: " + str);
|
||||
list parts = llParseString2List(str, ["~"], []);
|
||||
if (VERBOSE_LOGGING) llOwnerSay("🔍 [Roll Module] Parsed into " + (string)llGetListLength(parts) + " parts");
|
||||
|
||||
// Handle special RESET sync message
|
||||
if (llGetListLength(parts) >= 5 && llList2String(parts, 0) == "RESET") {
|
||||
llOwnerSay("🔄 [Roll Module] Received reset sync - ignoring during reset");
|
||||
return;
|
||||
}
|
||||
|
||||
if (llGetListLength(parts) >= 4) {
|
||||
lives = llCSV2List(llList2String(parts, 0));
|
||||
// Use ^ delimiter for picksData to avoid comma conflicts
|
||||
string picksDataStr = llList2String(parts, 1);
|
||||
if (VERBOSE_LOGGING) llOwnerSay("🔍 [Roll Module] Received picksDataStr: '" + picksDataStr + "'");
|
||||
if (picksDataStr == "" || picksDataStr == "EMPTY") {
|
||||
picksData = [];
|
||||
if (VERBOSE_LOGGING) llOwnerSay("🔍 [Roll Module] Set picksData to empty");
|
||||
} else {
|
||||
picksData = llParseString2List(picksDataStr, ["^"], []);
|
||||
if (VERBOSE_LOGGING) llOwnerSay("🔍 [Roll Module] Set picksData: " + llDumpList2String(picksData, " | "));
|
||||
}
|
||||
string receivedPeril = llList2String(parts, 2);
|
||||
if (receivedPeril == "NONE") {
|
||||
|
|
@ -217,6 +246,7 @@ default {
|
|||
perilPlayer = receivedPeril;
|
||||
}
|
||||
names = llCSV2List(llList2String(parts, 3));
|
||||
if (VERBOSE_LOGGING) llOwnerSay("🔍 [Roll Module] Updated names: " + llDumpList2String(names, ", "));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -290,6 +320,25 @@ default {
|
|||
return;
|
||||
}
|
||||
|
||||
// CRITICAL: Verify we have picks data for at least some players before processing roll
|
||||
integer hasAnyPicks = FALSE;
|
||||
integer validationIdx;
|
||||
for (validationIdx = 0; validationIdx < llGetListLength(picksData) && !hasAnyPicks; validationIdx++) {
|
||||
string entry = llList2String(picksData, validationIdx);
|
||||
list entryParts = llParseString2List(entry, ["|"], []);
|
||||
if (llGetListLength(entryParts) >= 2 && llList2String(entryParts, 1) != "") {
|
||||
hasAnyPicks = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAnyPicks) {
|
||||
llOwnerSay("⚠️ [Roll Module] No picks data available - delaying roll to wait for sync");
|
||||
rollInProgress = FALSE; // Reset roll state
|
||||
// Request a re-sync of game state
|
||||
llMessageLinked(LINK_SET, 998, "REQUEST_SYNC", NULL_KEY);
|
||||
return;
|
||||
}
|
||||
|
||||
integer diceType = (integer)str;
|
||||
integer result = rollDice(diceType);
|
||||
string resultStr = (string)result;
|
||||
|
|
@ -306,14 +355,19 @@ default {
|
|||
|
||||
|
||||
// Find all players who picked the rolled number
|
||||
llOwnerSay("🔍 [Roll Module] Checking picks for rolled number: " + resultStr);
|
||||
llOwnerSay("🔍 [Roll Module] Current picksData: " + llDumpList2String(picksData, " | "));
|
||||
for (i = 0; i < llGetListLength(names); i++) {
|
||||
string pname = llList2String(names, i);
|
||||
list picks = getPicksFor(pname);
|
||||
llOwnerSay("🔍 [Roll Module] " + pname + "'s picks: " + llDumpList2String(picks, ","));
|
||||
if (llListFindList(picks, [resultStr]) != -1) {
|
||||
matched = TRUE;
|
||||
matchedPlayers += [pname];
|
||||
llOwnerSay("🔍 [Roll Module] MATCH! " + pname + " picked " + resultStr);
|
||||
}
|
||||
}
|
||||
llOwnerSay("🔍 [Roll Module] Final matched status: " + (string)matched + ", players: " + llDumpList2String(matchedPlayers, ","));
|
||||
|
||||
|
||||
// If anyone matched, pick the first non-peril player as new peril
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
// === System Debugger - Game State Validation ===
|
||||
// Validates game state consistency and reports issues
|
||||
|
||||
// Verbose logging control
|
||||
integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs
|
||||
integer MSG_TOGGLE_VERBOSE_LOGS = 9998; // Message to toggle verbose logging
|
||||
|
||||
integer MSG_SYNC_GAME_STATE = 107;
|
||||
|
||||
list validatePicksData(list picksData, list names) {
|
||||
|
|
@ -41,7 +45,7 @@ list validatePicksData(list picksData, list names) {
|
|||
}
|
||||
|
||||
// Only show picks if player has actually picked something
|
||||
if (picks != "") {
|
||||
if (picks != "" && VERBOSE_LOGGING) {
|
||||
llOwnerSay("✅ " + name + "'s picks: " + llList2CSV(pickNums));
|
||||
}
|
||||
} else if (entry != "") {
|
||||
|
|
@ -73,6 +77,17 @@ default {
|
|||
}
|
||||
|
||||
link_message(integer sender, integer num, string str, key id) {
|
||||
// Handle verbose logging toggle
|
||||
if (num == MSG_TOGGLE_VERBOSE_LOGS) {
|
||||
VERBOSE_LOGGING = !VERBOSE_LOGGING;
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔊 [System Debugger] Verbose logging ENABLED");
|
||||
} else {
|
||||
llOwnerSay("🔊 [System Debugger] Verbose logging DISABLED");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle full reset from main controller
|
||||
if (num == -99999 && str == "FULL_RESET") {
|
||||
// Debugger doesn't maintain state, but acknowledge reset
|
||||
|
|
@ -83,7 +98,9 @@ default {
|
|||
if (num == MSG_SYNC_GAME_STATE) {
|
||||
list parts = llParseString2List(str, ["~"], []);
|
||||
if (llGetListLength(parts) < 4) {
|
||||
llOwnerSay("⚠️ Incomplete game state received.");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("⚠️ Incomplete game state received.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
2
WARP.md
2
WARP.md
|
|
@ -6,7 +6,7 @@ This file provides guidance to WARP (warp.dev) when working with code in this re
|
|||
|
||||
**Peril Dice** is a multiplayer elimination game for Second Life, built using LSL (Linden Scripting Language). It's a professional-grade linkset system where players select numbers before dice rolls, with the peril player losing lives when their number comes up. The project features a single 74-prim linkset architecture with sophisticated inter-script communication.
|
||||
|
||||
**Current Version**: 2.8.2 - Scoreboard Spam & Display Fixes
|
||||
**Current Version**: 2.8.4 - Disconnect Recovery & System-Wide Debug Control
|
||||
**Technology**: LSL (Linden Scripting Language) for Second Life virtual world
|
||||
**Architecture**: Single linkset with 16 modular LSL scripts communicating via `llMessageLinked()`
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
// LINKSET COMMUNICATION - NO DISCOVERY NEEDED
|
||||
// =============================================================================
|
||||
|
||||
// Verbose logging control
|
||||
integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs
|
||||
integer MSG_TOGGLE_VERBOSE_LOGS = 9998; // Message to toggle verbose logging
|
||||
|
||||
// Message constants for link communication
|
||||
// Dice messages (from link 1 - controller)
|
||||
integer MSG_DICE_ROLL = 3020;
|
||||
|
|
@ -36,23 +40,40 @@ displayDiceText(string text) {
|
|||
llMessageLinked(DICE_PRIM_1, DISPLAY_STRING, leftText, "");
|
||||
llMessageLinked(DICE_PRIM_2, DISPLAY_STRING, rightText, "");
|
||||
|
||||
llOwnerSay("🎲 Dice display updated: '" + text + "'");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🎲 Dice display updated: '" + text + "'");
|
||||
}
|
||||
}
|
||||
|
||||
default {
|
||||
state_entry() {
|
||||
llOwnerSay("🎲 Dice Display Bridge ready! (Linkset Version)");
|
||||
llOwnerSay("🎲 This is link " + (string)llGetLinkNumber() + " - should be link 73");
|
||||
llOwnerSay("🎲 Managing dice display links 73-74 (2 prims total)");
|
||||
llOwnerSay("✅ Linkset communication active - listening for messages from link 1!");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🎲 Dice Display Bridge ready! (Linkset Version)");
|
||||
llOwnerSay("🎲 This is link " + (string)llGetLinkNumber() + " - should be link 73");
|
||||
llOwnerSay("🎲 Managing dice display links 73-74 (2 prims total)");
|
||||
llOwnerSay("✅ Linkset communication active - listening for messages from link 1!");
|
||||
}
|
||||
|
||||
// Initialize dice display with blank text (10 spaces each prim)
|
||||
displayDiceText(" "); // 20 spaces
|
||||
|
||||
llOwnerSay("Dice display initialized - using simple XyzzyText v1.0 (like leaderboard)");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("Dice display initialized - using simple XyzzyText v1.0 (like leaderboard)");
|
||||
}
|
||||
}
|
||||
|
||||
link_message(integer sender, integer num, string str, key id) {
|
||||
// Handle verbose logging toggle
|
||||
if (num == MSG_TOGGLE_VERBOSE_LOGS) {
|
||||
VERBOSE_LOGGING = !VERBOSE_LOGGING;
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🔊 [Dice Bridge] Verbose logging ENABLED");
|
||||
} else {
|
||||
llOwnerSay("🔊 [Dice Bridge] Verbose logging DISABLED");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Only listen to messages from the main controller (link 1)
|
||||
if (sender != 1) return;
|
||||
|
||||
|
|
@ -83,7 +104,9 @@ default {
|
|||
else if (num == MSG_CLEAR_DICE) {
|
||||
// Clear dice display (20 spaces)
|
||||
displayDiceText(" ");
|
||||
llOwnerSay("🎲 Dice display cleared");
|
||||
if (VERBOSE_LOGGING) {
|
||||
llOwnerSay("🎲 Dice display cleared");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue