diff --git a/Bot_Manager.lsl b/Bot_Manager.lsl index df78c2d..d19e365 100644 --- a/Bot_Manager.lsl +++ b/Bot_Manager.lsl @@ -31,10 +31,6 @@ integer BOT_COMMAND_CHANNEL; // Channel initialization function initializeChannels() { BOT_COMMAND_CHANNEL = calculateChannel(5); // ~-77500 range to match Main.lsl - - // Report channel to owner for debugging - llOwnerSay("šŸ”§ [Bot Manager] Dynamic channel initialized:"); - llOwnerSay(" Bot Command: " + (string)BOT_COMMAND_CHANNEL); } // Configuration @@ -197,20 +193,15 @@ doBotRoll(string botName, integer diceMax) { default { state_entry() { - // Initialize dynamic channels initializeChannels(); LISTEN_CHANNEL = BOT_COMMAND_CHANNEL; - // Clean up any existing listeners if (listenHandle != -1) { llListenRemove(listenHandle); } - // Set up managed listener with dynamic channel listenHandle = llListen(LISTEN_CHANNEL, "", NULL_KEY, ""); - llOwnerSay("[Bot Manager] Ready and listening on channel " + (string)LISTEN_CHANNEL); - llOwnerSay("[Bot Manager] My key: " + (string)llGetKey()); - llOwnerSay("[Bot Manager] My position: " + (string)llGetPos()); + llOwnerSay("šŸ¤– Bot Manager ready!"); } link_message(integer sender, integer num, string str, key id) { diff --git a/Floater_Manager.lsl b/Floater_Manager.lsl index 09bdb79..1a2b2fb 100644 --- a/Floater_Manager.lsl +++ b/Floater_Manager.lsl @@ -39,13 +39,6 @@ integer FLOATER_BASE_CHANNEL; // Channel initialization function initializeChannels() { FLOATER_BASE_CHANNEL = calculateChannel(9); // ~-86000 range base for floaters - - // Report channel to owner for debugging - llOwnerSay("šŸ”§ [Floater Manager] Dynamic channels initialized:"); - llOwnerSay(" Floater Base: " + (string)FLOATER_BASE_CHANNEL); - llOwnerSay(" Player 0 channel: " + (string)(FLOATER_BASE_CHANNEL + 0)); - llOwnerSay(" Player 1 channel: " + (string)(FLOATER_BASE_CHANNEL + 1)); - llOwnerSay(" Player 9 channel: " + (string)(FLOATER_BASE_CHANNEL + 9)); } // Maximum number of players allowed in the game @@ -108,12 +101,8 @@ string getNameFromKey(key id) { // Main event handler default { state_entry() { - // Initialize dynamic channels initializeChannels(); - - // Verify channel consistency with Main Controller llOwnerSay("šŸ“¦ Floater Manager ready!"); - llOwnerSay("šŸ“¦ Using FLOATER_BASE_CHANNEL: " + (string)FLOATER_BASE_CHANNEL); } link_message(integer sender, integer num, string str, key id) { @@ -139,6 +128,13 @@ default { list info = llParseString2List(str, ["|"], []); string name = llList2String(info, 0); key avKey = llList2Key(info, 1); + + // Check if this player is already registered to prevent duplicate floaters + if (llListFindList(players, [avKey]) != -1) { + llOwnerSay("āš ļø Player " + name + " is already registered, ignoring duplicate registration"); + return; + } + names += [name]; players += [avKey]; lives += [3]; // Initialize with 3 lives for new players @@ -267,6 +263,12 @@ default { // IMPORTANT: Also sync the names list to prevent desynchronization string namesStr = llList2String(parts, 3); names = llCSV2List(namesStr); + + // Sync players list if available (5th part) + if (llGetListLength(parts) >= 5) { + string playersStr = llList2String(parts, 4); + players = llCSV2List(playersStr); + } // After synchronizing the game state, update all existing floats so // they reflect the current peril status. Without this, floats diff --git a/Game_Manager.lsl b/Game_Manager.lsl index faf5cbb..f3bc73c 100644 --- a/Game_Manager.lsl +++ b/Game_Manager.lsl @@ -281,10 +281,6 @@ syncStateToMain() { default { state_entry() { llOwnerSay("šŸŽÆ Game Manager ready!"); - - // Reset game on startup to ensure clean state when core logic is updated - llOwnerSay("šŸ”„ [Game Manager] Requesting game reset on startup..."); - llMessageLinked(LINK_SET, -99998, "REQUEST_GAME_RESET", NULL_KEY); } link_message(integer sender, integer num, string str, key id) { diff --git a/Main_Controller_Linkset.lsl b/Main_Controller_Linkset.lsl index a1a7dba..958097c 100644 --- a/Main_Controller_Linkset.lsl +++ b/Main_Controller_Linkset.lsl @@ -53,6 +53,8 @@ integer MSG_TOGGLE_READY = 202; integer MSG_QUERY_READY_STATE = 210; integer MSG_READY_STATE_RESULT = 211; integer MSG_CLEANUP_ALL_FLOATERS = 212; +integer MSG_QUERY_OWNER_STATUS = 213; +integer MSG_OWNER_STATUS_RESULT = 214; integer MSG_SHOW_ROLL_DIALOG = 301; integer MSG_GET_CURRENT_DIALOG = 302; integer MSG_PLAYER_WON = 551; @@ -176,10 +178,6 @@ initializeChannels() { rollDialogChannel = ROLLDIALOG_CHANNEL; DIALOG_CHANNEL = MAIN_DIALOG_CHANNEL; - llOwnerSay("šŸ”§ [Main Controller] Channels initialized for external communication:"); - llOwnerSay(" Dialog: " + (string)MAIN_DIALOG_CHANNEL); - llOwnerSay(" Roll: " + (string)ROLLDIALOG_CHANNEL); - llOwnerSay(" Floater Base: " + (string)FLOATER_BASE_CHANNEL); } // Clean up listen handles @@ -274,7 +272,7 @@ updateHelpers() { checkMemoryUsage("updateHelpers_after_picks_processing"); } - // Send core game state to internal scripts + // 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); @@ -319,8 +317,15 @@ updateFloatingText() { resetGame() { cleanupListeners(); + // Only clean up channels that actually have active floaters integer i; - for (i = 0; i < MAX_PLAYERS; i++) { + for (i = 0; i < llGetListLength(floaterChannels); i++) { + integer ch = llList2Integer(floaterChannels, i); + llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY); + } + + // Also send a few cleanup messages to common channel ranges in case of orphaned floaters + for (i = 0; i < llGetListLength(names); i++) { integer ch = FLOATER_BASE_CHANNEL + i; llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY); } @@ -364,8 +369,6 @@ resetGame() { default { state_entry() { llOwnerSay("šŸŽ® Main Controller ready! (Linkset Version)"); - llOwnerSay("šŸŽ® Main Controller key: " + (string)llGetKey()); - llOwnerSay("šŸŽ® Main Controller position: " + (string)llGetPos()); // Initialize lockout system gameOwner = llGetOwner(); @@ -375,10 +378,8 @@ default { initializeChannels(); // Reset the game on startup to ensure clean state - llOwnerSay("šŸ”„ Resetting game state on startup..."); resetGame(); - llOwnerSay("šŸ“” Linkset communication active - no discovery needed!"); llOwnerSay("āœ… Main Controller initialization complete!"); } @@ -690,6 +691,9 @@ default { perilPlayer = newPerilPlayer; names = newNames; + // Update scoreboard and other helpers with the new game state + updateHelpers(); + integer allPicksEmpty = TRUE; integer i; for (i = 0; i < llGetListLength(newPicksData) && allPicksEmpty; i++) { @@ -766,6 +770,45 @@ default { return; } + // Handle owner status queries + if (num == MSG_QUERY_OWNER_STATUS) { + list queryParts = llParseString2List(str, ["|"], []); + string ownerName; + integer requestID = 0; + + if (llGetListLength(queryParts) >= 2) { + ownerName = llList2String(queryParts, 0); + requestID = (integer)llList2String(queryParts, 1); + } else { + ownerName = str; + } + + // Check if owner is registered + integer ownerIdx = llListFindList(players, [id]); + integer isRegistered = (ownerIdx != -1); + integer isPending = (llListFindList(pendingRegistrations, [id]) != -1); + integer isStarter = FALSE; + + if (isRegistered) { + // Calculate starter status if registered + isStarter = TRUE; + integer k; + for (k = 0; k < ownerIdx && isStarter; k++) { + string existingName = llList2String(names, k); + if (llSubStringIndex(existingName, "Bot") != 0) { + isStarter = FALSE; + } + } + if (ownerIdx == 0) { + isStarter = TRUE; + } + } + + string result = ownerName + "|" + (string)isRegistered + "|" + (string)isPending + "|" + (string)isStarter + "|" + (string)requestID; + llMessageLinked(LINK_SET, MSG_OWNER_STATUS_RESULT, result, id); + return; + } + // Handle aggressive floater cleanup if (num == MSG_CLEANUP_ALL_FLOATERS) { integer i; @@ -906,19 +949,9 @@ default { } } - // Handle owner choice menu + // Handle owner choice menu - "Join Game" is handled by Player_DialogHandler if (id == llGetOwner()) { - if (msg == "Join Game") { - // Owner wants to join as a player - string ownerName = getPlayerName(id); - pendingRegistrations += [id]; - llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, ownerName + "|" + (string)id, NULL_KEY); - - // After registration, they'll get the appropriate player menu - // The registration handler will call updateHelpers() and show menu - return; - } - else if (msg == "Owner Menu") { + if (msg == "Owner Menu") { // Owner wants admin functions without joining - use admin targetType llMessageLinked(LINK_SET, MSG_SHOW_MENU, "admin|0", id); return; diff --git a/Player_DialogHandler.lsl b/Player_DialogHandler.lsl index 071786c..072583c 100644 --- a/Player_DialogHandler.lsl +++ b/Player_DialogHandler.lsl @@ -84,6 +84,8 @@ integer MSG_TOGGLE_READY = 202; integer MSG_QUERY_READY_STATE = 210; integer MSG_READY_STATE_RESULT = 211; integer MSG_CLEANUP_ALL_FLOATERS = 212; +integer MSG_QUERY_OWNER_STATUS = 213; +integer MSG_OWNER_STATUS_RESULT = 214; integer MSG_GET_CURRENT_DIALOG = 302; // Categorized owner menu system @@ -155,9 +157,15 @@ showReadyLeaveMenu(key id, integer isStarter, integer isOwner) { } // Generate unique request ID and store pending menu state + // New requests always override previous ones for the same player currentRequestID++; if (currentRequestID > 1000000) currentRequestID = 1; // Prevent overflow + // If there's a pending request for a different player, warn about it but proceed + if (pendingMenuPlayer != NULL_KEY && pendingMenuPlayer != id) { + llOwnerSay("āš ļø [Debug] Overriding pending request for " + (string)pendingMenuPlayer + " with new request for " + (string)id); + } + pendingMenuPlayer = id; pendingMenuIsStarter = isStarter; pendingMenuIsOwner = isOwner; @@ -167,7 +175,7 @@ showReadyLeaveMenu(key id, integer isStarter, integer isOwner) { string playerName = getPlayerName(id); // Include request ID in the query to track responses string queryData = playerName + "|" + (string)currentRequestID; - llMessageLinked(LINK_SET, MSG_QUERY_READY_STATE, queryData, id); + llMessageLinked(LINK_SET, MSG_QUERY_READY_STATE, queryData, id); } // Internal function to show the menu once we have ready state @@ -337,6 +345,9 @@ default { if (targetType == "owner") { // Both starter and non-starter owners get ready/leave menu with Owner button showReadyLeaveMenu(id, isStarter, TRUE); + } else if (targetType == "registered_owner") { + // Registered owner gets ready/leave menu with Owner button + showReadyLeaveMenu(id, isStarter, TRUE); } else if (targetType == "player") { showReadyLeaveMenu(id, isStarter, FALSE); } else if (targetType == "admin") { @@ -363,7 +374,7 @@ default { } showReadyLeaveMenuWithState(pendingMenuPlayer, pendingMenuIsStarter, pendingMenuIsOwner, isReady, isBot); - // Reset pending state + // Reset pending state ONLY after successfully showing the dialog pendingMenuPlayer = NULL_KEY; pendingMenuRequestID = 0; } @@ -421,6 +432,57 @@ default { showPickListMenu(id); } } + else if (num == MSG_OWNER_STATUS_RESULT) { + // Parse owner status response and show appropriate menu + list parts = llParseString2List(str, ["|"], []); + if (llGetListLength(parts) >= 5) { + string ownerName = llList2String(parts, 0); + integer isRegistered = (integer)llList2String(parts, 1); + integer isPending = (integer)llList2String(parts, 2); + integer isStarter = (integer)llList2String(parts, 3); + integer responseRequestID = (integer)llList2String(parts, 4); + + // Validate that this response matches our pending request + if (pendingMenuPlayer == id && responseRequestID == pendingMenuRequestID) { + // Check for timeout + if ((llGetTime() - pendingMenuTimestamp) > MENU_REQUEST_TIMEOUT) { + llOwnerSay("ā° Owner status query timed out, ignoring response"); + pendingMenuPlayer = NULL_KEY; + return; + } + + // Decide which menu to show based on registration status + if (isRegistered) { + // Owner is registered - show ready/leave menu with Owner button + llOwnerSay("šŸ” [Debug] Owner is registered, isStarter=" + (string)isStarter + ", showing ready/leave menu"); + llOwnerSay("šŸ” [Debug] About to call showReadyLeaveMenu with id=" + (string)id + ", isStarter=" + (string)isStarter + ", isOwner=TRUE"); + + // DON'T reset pending state here - showReadyLeaveMenu needs it! + // The ready state handler will reset it when the dialog is shown + showReadyLeaveMenu(id, isStarter, TRUE); + llOwnerSay("šŸ” [Debug] showReadyLeaveMenu call completed"); + } else if (isPending) { + llRegionSayTo(id, 0, "ā³ Registration already in progress for " + ownerName); + // Reset pending state for non-registered responses + pendingMenuPlayer = NULL_KEY; + pendingMenuRequestID = 0; + } else { + // Owner is not registered - show join/admin choice menu + list options = ["Join Game", "Owner Menu"]; + string menuText = "šŸ‘‘ Owner Options:\n\nšŸŽ® Join Game - Register as a player\nšŸ”§ Owner Menu - Admin functions without joining"; + llDialog(id, menuText, options, DIALOG_CHANNEL); + // Reset pending state for non-registered responses + pendingMenuPlayer = NULL_KEY; + pendingMenuRequestID = 0; + } + } else { + llOwnerSay("āš ļø Owner status response mismatch - ignoring"); + } + } else { + llOwnerSay("āš ļø Invalid owner status result format - expected 5 parts, got " + (string)llGetListLength(parts)); + } + return; + } } listen(integer channel, string name, key id, string msg) { @@ -474,9 +536,22 @@ default { showOwnerMenu(id); } else if (msg == "ā¬…ļø Back to Game") { - // Return to the owner's ready/leave menu - // Need to determine if owner is starter - llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|1", id); // Assume starter for now, will be corrected by main controller + // Intelligently determine what menu to show based on owner's registration status + if (id == gameOwner) { + // Query the Main Controller about the owner's registration status + string ownerName = getPlayerName(id); + // Generate unique request ID for tracking + currentRequestID++; + if (currentRequestID > 1000000) currentRequestID = 1; + + // Store pending owner query state + pendingMenuPlayer = id; + pendingMenuRequestID = currentRequestID; + pendingMenuTimestamp = llGetTime(); + + string queryData = ownerName + "|" + (string)currentRequestID; + llMessageLinked(LINK_SET, MSG_QUERY_OWNER_STATUS, queryData, id); + } } // Owner joins the game: register and rez a float for them else if (msg == "Join Game") { diff --git a/Roll_ConfettiModule.lsl b/Roll_ConfettiModule.lsl index a6b11ce..1a686ab 100644 --- a/Roll_ConfettiModule.lsl +++ b/Roll_ConfettiModule.lsl @@ -287,8 +287,7 @@ default { llSay(0, "⚔ PLOT TWIST! " + newPeril + " picked " + resultStr + " (rolled on d" + (string)diceType + ") and is now in ULTIMATE PERIL! ⚔"); perilPlayer = newPeril; - // Send Plot Twist status to scoreboard - llRegionSay(SCOREBOARD_CHANNEL_1, "GAME_STATUS|Plot Twist"); + // Plot Twist status will be sent by Main Controller via link messages // Add delay to let status display before next phase llSleep(2.0); @@ -300,14 +299,8 @@ default { integer currentLives = llList2Integer(lives, pidx); lives = llListReplaceList(lives, [currentLives - 1], pidx, pidx); - // Immediately send updated heart count to scoreboard - integer newLives = currentLives - 1; - key perilKey = NULL_KEY; // We don't have player keys in this module, use NULL_KEY - if (pidx < llGetListLength(names)) { - string updateMsg = "PLAYER_UPDATE|" + perilPlayer + "|" + (string)newLives + "|" + (string)perilKey; - llRegionSay(SCOREBOARD_CHANNEL_1, updateMsg); - llOwnerSay("šŸ’— Immediate scoreboard update: " + perilPlayer + " now has " + (string)newLives + " lives"); - } + // Player update will be sent by Main Controller via sync message and updateHelpers() + llOwnerSay("šŸ’— Player lives updated: " + perilPlayer + " now has " + (string)(currentLives - 1) + " lives"); // Check if peril player picked the rolled number list perilPicks = getPicksFor(perilPlayer); @@ -315,14 +308,12 @@ default { if (perilPickedIt) { llSay(0, "🩸 DIRECT HIT! " + perilPlayer + " picked their own doom - the d" + (string)diceType + " landed on " + resultStr + "! 🩸"); - // Send Direct Hit status to scoreboard - llRegionSay(SCOREBOARD_CHANNEL_1, "GAME_STATUS|Direct Hit"); + // Direct Hit status will be sent by Main Controller via link messages // Add delay to let status display before next phase llSleep(2.0); } else { llSay(0, "🩸 NO SHIELD! Nobody picked " + resultStr + " - " + perilPlayer + " takes the hit from the d" + (string)diceType + "! 🩸"); - // Send No Shield status to scoreboard - llRegionSay(SCOREBOARD_CHANNEL_1, "GAME_STATUS|No Shield"); + // No Shield status will be sent by Main Controller via link messages // Add delay to let status display before next phase llSleep(2.0); } @@ -331,10 +322,8 @@ default { // Check for elimination if (currentLives - 1 <= 0) { - // Show 0 hearts on scoreboard before elimination message - string eliminationUpdateMsg = "PLAYER_UPDATE|" + perilPlayer + "|0|" + (string)NULL_KEY; - llRegionSay(SCOREBOARD_CHANNEL_1, eliminationUpdateMsg); - llOwnerSay("šŸ’€ Elimination update: " + perilPlayer + " now shows 0 hearts"); + // Player elimination will be handled by Main Controller via sync and elimination messages + llOwnerSay("šŸ’€ Elimination detected: " + perilPlayer + " has 0 hearts remaining"); llSay(0, "🐻 PUNISHMENT TIME! " + perilPlayer + " has been ELIMINATED!"); // Remove eliminated player (send message to main controller)