From 3f2fa978189ee63d6264eab391727dd06ba37e57 Mon Sep 17 00:00:00 2001 From: RebeccaNod1 Date: Wed, 8 Apr 2026 23:23:51 -0600 Subject: [PATCH] updated all scripts to new directory and #includes, and on sl the files now are #include "peril/filename.lsl" while fs preprocessor is now including ~/lsl-projects/ --- Bot_Manager.lsl | 2 +- Controller_Memory.lsl | 2 +- Controller_MessageHandler.lsl | 2 +- Enhanced_LinkScanner.lsl | 2 +- FURWARE_Leaderboard_Communication_Linkset.lsl | 2 +- FURWARE_Status_Bridge_Linkset.lsl | 2 +- FURWARE_Text_Dice_Bridge_Linkset.lsl | 2 +- Floater_Manager.lsl | 2 +- Furware_Text_Engine.lsl | 2 +- Game_Calculator.lsl | 2 +- Game_Leaderboard_Manager.lsl | 94 ++-- Game_Manager.lsl | 97 +++- Game_Scoreboard_Manager_Linkset.lsl | 2 +- Main_Controller_Linkset.lsl | 473 +++--------------- NumberPicker_DialogHandler.lsl | 2 +- Peril_Constants.lsl | 5 +- PlayerStatus_Float.lsl | 2 +- Player_DialogHandler.lsl | 118 +++-- Player_RegistrationManager.lsl | 50 +- Roll_ConfettiModule.lsl | 2 +- System_Debugger.lsl | 2 +- 21 files changed, 331 insertions(+), 536 deletions(-) diff --git a/Bot_Manager.lsl b/Bot_Manager.lsl index f9be2e0..508ec3f 100644 --- a/Bot_Manager.lsl +++ b/Bot_Manager.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // Handles logic for test players (bots): picking numbers and rolling dice diff --git a/Controller_Memory.lsl b/Controller_Memory.lsl index ea42874..e079286 100644 --- a/Controller_Memory.lsl +++ b/Controller_Memory.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // === Controller Memory Monitor === // Handles memory monitoring and reporting for the Main Controller diff --git a/Controller_MessageHandler.lsl b/Controller_MessageHandler.lsl index 3278f12..6851f5d 100644 --- a/Controller_MessageHandler.lsl +++ b/Controller_MessageHandler.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // === Controller Message Handler Helper Script === // Handles non-critical message processing for the Main Controller diff --git a/Enhanced_LinkScanner.lsl b/Enhanced_LinkScanner.lsl index 8130f06..2b5a2fb 100644 --- a/Enhanced_LinkScanner.lsl +++ b/Enhanced_LinkScanner.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // ==================================================================== // Enhanced Link Scanner for Peril Dice Linkset diff --git a/FURWARE_Leaderboard_Communication_Linkset.lsl b/FURWARE_Leaderboard_Communication_Linkset.lsl index 838efef..be6e5ce 100644 --- a/FURWARE_Leaderboard_Communication_Linkset.lsl +++ b/FURWARE_Leaderboard_Communication_Linkset.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" string leftText = ""; string firstName = ""; diff --git a/FURWARE_Status_Bridge_Linkset.lsl b/FURWARE_Status_Bridge_Linkset.lsl index 2f53fac..be22038 100644 --- a/FURWARE_Status_Bridge_Linkset.lsl +++ b/FURWARE_Status_Bridge_Linkset.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // ============================================================================= // FURWARE STATUS BRIDGE - Dynamic Game Info Display diff --git a/FURWARE_Text_Dice_Bridge_Linkset.lsl b/FURWARE_Text_Dice_Bridge_Linkset.lsl index 25fb76e..8aa91ef 100644 --- a/FURWARE_Text_Dice_Bridge_Linkset.lsl +++ b/FURWARE_Text_Dice_Bridge_Linkset.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // ============================================================================= // LINKSET COMMUNICATION - NO DISCOVERY NEEDED diff --git a/Floater_Manager.lsl b/Floater_Manager.lsl index 5e549c2..5e04bf7 100644 --- a/Floater_Manager.lsl +++ b/Floater_Manager.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // === FloatManager (Consolidated) === // This version enforces a maximum of 10 players. diff --git a/Furware_Text_Engine.lsl b/Furware_Text_Engine.lsl index 726fd8a..73ad935 100644 --- a/Furware_Text_Engine.lsl +++ b/Furware_Text_Engine.lsl @@ -97,7 +97,7 @@ list setDataList; // Strided list of display set data. // Per-prim data list primLinkList; // List of the prims' link indices list primFillList; // Tells us which faces are showing non-blanks. -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" list primLayerList; // Contains layer assignments for each face. diff --git a/Game_Calculator.lsl b/Game_Calculator.lsl index 7917658..15aef02 100644 --- a/Game_Calculator.lsl +++ b/Game_Calculator.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // === Game Calculator Module (Max 10 players) === // Calculates dice types, pick requirements, and other game mechanics diff --git a/Game_Leaderboard_Manager.lsl b/Game_Leaderboard_Manager.lsl index f9fa3c5..e90ff13 100644 --- a/Game_Leaderboard_Manager.lsl +++ b/Game_Leaderboard_Manager.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" debugMsg(string msg) { if (DEBUG_LOGS) llMessageLinked(LINK_SET, MSG_DEBUG_TEXT, msg, NULL_KEY); @@ -95,7 +95,7 @@ generateShardedData() { } loadLBPage(integer pageNum) { - if ((isBusy || isSaving || isLocking) && pageNum == 1) { + if ((isBusy || isSaving) && pageNum == 1) { dbg("ā³ [Leaderboard] Sync requested while Busy/Saving. Ignoring duplicate reset."); return; } @@ -113,8 +113,8 @@ loadLBPage(integer pageNum) { if (kvpReadReq == NULL_KEY) { dbg("āš ļø [Leaderboard] KVP Request FAILED (Throttle/NULL_KEY). Retrying " + keyName + " in 2s..."); - isBusy = FALSE; // Unlock for retry - llSetTimerEvent(2.0); // Use timer to trigger the retry + // KEEP isBusy = TRUE to prevent collisions during retry + llSetTimerEvent(2.0); return; } @@ -147,14 +147,14 @@ updatePlayerRecord(string pName, string pResult) { if (pResult == "WIN") leaderboardData += [1, 0, pName]; else leaderboardData += [0, 1, pName]; } else { - integer _base = _idx - 2; - integer _v; + integer _updBase = _idx - 2; + integer _updVal; if (pResult == "WIN") { - _v = llList2Integer(leaderboardData, _base); - leaderboardData = llListReplaceList(leaderboardData, [_v + 1], _base, _base); + _updVal = llList2Integer(leaderboardData, _updBase); + leaderboardData = llListReplaceList(leaderboardData, [_updVal + 1], _updBase, _updBase); } else { - _v = llList2Integer(leaderboardData, _base + 1); - leaderboardData = llListReplaceList(leaderboardData, [_v + 1], _base+1, _base+1); + _updVal = llList2Integer(leaderboardData, _updBase + 1); + leaderboardData = llListReplaceList(leaderboardData, [_updVal + 1], _updBase + 1, _updBase + 1); } } @@ -234,16 +234,16 @@ default { string _rawEntry = llList2String(_ent, _ei); if (llSubStringIndex(_rawEntry, ",") != -1) { // NEW FORMAT: Wins,Losses,Name - list _it = llParseString2List(_rawEntry, [","], []); - if (llGetListLength(_it) >= 3) { - leaderboardData += [(integer)llList2String(_it, 0), (integer)llList2String(_it, 1), llList2String(_it, 2)]; + list _itNew = llParseString2List(_rawEntry, [","], []); + if (llGetListLength(_itNew) >= 3) { + leaderboardData += [(integer)llList2String(_itNew, 0), (integer)llList2String(_itNew, 1), llList2String(_itNew, 2)]; } } else { // OLD LEGACY FORMAT: Name:Wins:Losses - list _it = llParseString2List(_rawEntry, [":"], []); - if (llGetListLength(_it) >= 3) { + list _itOld = llParseString2List(_rawEntry, [":"], []); + if (llGetListLength(_itOld) >= 3) { // Translate to new stride structure in memory - leaderboardData += [(integer)llList2String(_it, 1), (integer)llList2String(_it, 2), llList2String(_it, 0)]; + leaderboardData += [(integer)llList2String(_itOld, 1), (integer)llList2String(_itOld, 2), llList2String(_itOld, 0)]; } } } @@ -253,40 +253,54 @@ default { retryCount = 0; // Reset retries for next page loadLBPage(currentLoadPage + 1); } else { - if (isBusy) { - isBusy = FALSE; - llSetTimerEvent(0); - dbg("šŸ† [Leaderboard] SYNC COMPLETE - " + (string)(llGetListLength(leaderboardData)/3) + " players."); - llMessageLinked(LINK_SET, MSG_LB_LOAD_COMPLETE, "", NULL_KEY); - llMessageLinked(LINK_SET, MSG_LB_REQUEST_DISPLAY, "0", NULL_KEY); // Show page 1 - } + // SYNC COMPLETE (Natural end) + llSetTimerEvent(0); + dbg("šŸ† [Leaderboard] SYNC COMPLETE - " + (string)(llGetListLength(leaderboardData)/3) + " players."); + llMessageLinked(LINK_SET, MSG_LB_LOAD_COMPLETE, "", NULL_KEY); + llMessageLinked(LINK_SET, MSG_LB_REQUEST_DISPLAY, "0", NULL_KEY); // Show page 1 - // IF WE WERE SYNCING FOR AN UPDATE, APPLY IT NOW - if (isSaving && llGetListLength(pendingRecords) > 0) { - isSaving = FALSE; + // Check if any records were queued while we were busy or saving + if (llGetListLength(pendingRecords) > 0) { + dbg("šŸ”„ [Leaderboard] Processing queued record for [" + llList2String(pendingRecords, 0) + "]..."); string nextName = llList2String(pendingRecords, 0); string nextRes = llList2String(pendingRecords, 1); pendingRecords = llDeleteSubList(pendingRecords, 0, 1); + isBusy = FALSE; + isSaving = FALSE; updatePlayerRecord(nextName, nextRes); + } else { + isBusy = FALSE; + isSaving = FALSE; } } } else if (_st == "0") { if (_vl == "1") { // XP_ERROR_THROTTLED dbg("āš ļø [Leaderboard] Read throttled. Retrying Shard " + (string)currentLoadPage + " in 2s..."); llSleep(2.0); - isBusy = FALSE; + // RETAIN isBusy/isSaving status during retry loadLBPage(currentLoadPage); } else if (_vl == "14") { // XP_ERROR_KEY_NOT_FOUND (Value is empty) - isBusy = FALSE; - llSetTimerEvent(0); - dbg("šŸ† [Leaderboard] Shard " + (string)currentLoadPage + " is empty (New)."); - if (currentLoadPage < 10) { loadLBPage(currentLoadPage + 1); - } else if (isBusy) { + } else { + // SYNC COMPLETE (Empty trailing shards) + llSetTimerEvent(0); dbg("šŸ† [Leaderboard] SYNC COMPLETE - " + (string)(llGetListLength(leaderboardData)/3) + " players total."); llMessageLinked(LINK_SET, MSG_LB_LOAD_COMPLETE, "", NULL_KEY); llMessageLinked(LINK_SET, MSG_LB_REQUEST_DISPLAY, "0", NULL_KEY); + + if (llGetListLength(pendingRecords) > 0) { + dbg("šŸ”„ [Leaderboard] Processing queued record after empty shards..."); + string nextName = llList2String(pendingRecords, 0); + string nextRes = llList2String(pendingRecords, 1); + pendingRecords = llDeleteSubList(pendingRecords, 0, 1); + isBusy = FALSE; + isSaving = FALSE; + updatePlayerRecord(nextName, nextRes); + } else { + isBusy = FALSE; + isSaving = FALSE; + } } } else { dbg("āŒ [Leaderboard] Read Error (Code " + _vl + ") on Shard " + (string)currentLoadPage); @@ -359,18 +373,18 @@ default { if (llList2String(_p, 0) == (string)llGetKey()) { dbg("šŸ” [Leaderboard] Lock already held by this board (Self-Sync). Proceeding..."); - myLockVal = _curVal; // CRITICAL: Sync timestamp to allow future release + myLockVal = _curVal; isLocking = FALSE; - isSaving = TRUE; // MARK THAT WE ARE SYNCING FOR A SAVE - leaderboardData = []; // PURGE STALE MEMORY + // Set isBusy before loading to ensure the guard is handled correctly + isBusy = TRUE; + leaderboardData = []; loadLBPage(1); - return; // Done + return; } if (_curVal == "" || _curVal == "OFF" || _isStale) { dbg("šŸ” [Leaderboard] Lock available (or stale). Claiming..."); myLockVal = (string)llGetKey() + ":" + (string)llGetUnixTime(); - dbg("šŸ”“ [Leaderboard] CLAIMING WITH: [" + myLockVal + "] (Len: " + (string)llStringLength(myLockVal) + ")"); isClaiming = TRUE; kvpClaimReq = llUpdateKeyValue(LB_LOCK_KEY, myLockVal, TRUE, _curVal); } else { @@ -380,9 +394,7 @@ default { } else if (_st == "0" && _vl == "14") { // KEY_NOT_FOUND (Lock key doesn't exist) dbg("šŸ” [Leaderboard] Lock doesn't exist. Creating initial lock..."); myLockVal = (string)llGetKey() + ":" + (string)llGetUnixTime(); - dbg("šŸ”“ [Leaderboard] CLAIMING WITH: [" + myLockVal + "] (Len: " + (string)llStringLength(myLockVal) + ")"); isClaiming = TRUE; - // INITIAL CREATION: Use kvpClaimReq to match Stage 2 handler kvpClaimReq = llUpdateKeyValue(LB_LOCK_KEY, myLockVal, FALSE, ""); } else if (_st == "0" && _vl == "1") { // Throttled dbg("āš ļø [Leaderboard] Lock check throttled. Retrying..."); @@ -407,7 +419,7 @@ default { return; } - if (isBusy || (kvpReadReq == NULL_KEY && currentLoadPage > 0)) { + if (isBusy) { // DO NOT check isSaving here, as isBusy should be the authoritative lock for READS if (retryCount < 3) { retryCount++; dbg("ā³ [Leaderboard] Retrying Shard " + (string)currentLoadPage + " (" + (string)retryCount + "/3)..."); @@ -498,7 +510,7 @@ default { } } else if (num == MSG_RESET_ALL) { - if (str == "FULL_RESET" || str == "HARD_RESET") llResetScript(); + if (str == "HARD_RESET" || str == "ADMIN_RESET") llResetScript(); } } } diff --git a/Game_Manager.lsl b/Game_Manager.lsl index a2381cc..73455ac 100644 --- a/Game_Manager.lsl +++ b/Game_Manager.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // === Game Manager - Game Flow Logic === @@ -254,9 +254,9 @@ integer startNextRound() { currentPickerIdx = 0; dbg("šŸŽÆ Debug - Created pickQueue: " + llList2CSV(pickQueue) + ", currentPickerIdx: " + (string)currentPickerIdx); - integer currentPlayerCount = llGetListLength(names); - if (currentPlayerCount == lastPlayerCount && diceType > 0) { - dbg("šŸŽÆ [Game Manager] Player count unchanged (" + (string)currentPlayerCount + "), reusing stored dice type: d" + (string)diceType); + integer totalPlayers = llGetListLength(names); + if (totalPlayers == lastPlayerCount && diceType > 0) { + dbg("šŸŽÆ [Game Manager] Player count unchanged (" + (string)totalPlayers + "), reusing stored dice type: d" + (string)diceType); // NEW: Add a brief buffer to let the previous outcome status stay on screen llSleep(DELAY_MEDIUM_SYNC); @@ -265,9 +265,9 @@ integer startNextRound() { llMessageLinked(LINK_SET, MSG_STATUS_TEXT, "ROUND STARTING!\nPeril: " + perilPlayer, NULL_KEY); showNextPickerDialog(); } else { - lastPlayerCount = currentPlayerCount; + lastPlayerCount = totalPlayers; // Request dice type for this round - llMessageLinked(LINK_SET, MSG_GET_DICE_TYPE, (string)currentPlayerCount, NULL_KEY); + llMessageLinked(LINK_SET, MSG_GET_DICE_TYPE, (string)totalPlayers, NULL_KEY); // NEW: Update status bar for round start llMessageLinked(LINK_SET, MSG_STATUS_TEXT, "ROUND STARTING!\nPeril: " + perilPlayer, NULL_KEY); } @@ -1097,5 +1097,90 @@ default { dbg("šŸ”’ [Game Manager] Emergency state reset complete"); return; } + + // Handle elimination requests (Offloaded from Main Controller) + if (num == MSG_ELIMINATE_PLAYER) { + list parts = llParseString2List(str, ["|"], []); + if (llList2String(parts, 0) == "ELIMINATE_PLAYER") { + string eliminatedPlayer = llList2String(parts, 1); + integer idx = llListFindList(names, [eliminatedPlayer]); + if (idx != -1) { + dbg("šŸŽÆ [Game Manager] Processing elimination for: " + eliminatedPlayer); + + // NEW: Only announce elimination if it's not the end of the game (winner) + if (llGetListLength(names) > 1) { + llMessageLinked(LINK_SET, MSG_STATUS_TEXT, "PLAYER STRUCK!\n" + eliminatedPlayer + " has been eliminated!", NULL_KEY); + } + + // FIRST: Set the player's lives to 0 (locally and visually) + lives = llListReplaceList(lives, [0], idx, idx); + + // Update scoreboard visually for 0 hearts + llMessageLinked(LINK_SCOREBOARD, MSG_PLAYER_UPDATE, + eliminatedPlayer + "|0|" + (string)llList2Key(players, idx), NULL_KEY); + + // RECORD LOSS: Send eliminated player to World Ranking database + llMessageLinked(LINK_SET, MSG_LB_RECORD_PLAYER, eliminatedPlayer + "|LOSS", NULL_KEY); + + // CRITICAL: Check for victory condition BEFORE assigning new peril player + integer willCauseVictory = (llGetListLength(names) <= 2); + string potentialWinner = ""; + + if (willCauseVictory) { + integer w; + for (w = 0; w < llGetListLength(names); w++) { + string candidateName = llList2String(names, w); + if (candidateName != eliminatedPlayer) { + potentialWinner = candidateName; + jump found_winner; + } + } + @found_winner; + + if (potentialWinner != "") { + // Synchronize visually + llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_WINNER, potentialWinner, NULL_KEY); + llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, potentialWinner, NULL_KEY); + } + } + + // Visual elimination effect + llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, eliminatedPlayer, NULL_KEY); + llSleep(DELAY_ELIMINATION_NOTICE); + + if (willCauseVictory) { + llSleep(DELAY_VICTORY_NOTICE); + } + + // Inform Main Controller to perform deep cleanup (list deletion) + llMessageLinked(LINK_SET, MSG_PROCESS_ELIMINATION, eliminatedPlayer, NULL_KEY); + + // Handle Ultimate Victory Sequence + if (willCauseVictory && potentialWinner != "") { + // Clear local state + roundStarted = FALSE; + + llSay(0, "✨ ULTIMATE VICTORY! " + potentialWinner + " is the Ultimate Survivor!"); + + llMessageLinked(LINK_SET, MSG_STATUS_TEXT, "=== ULTIMATE VICTORY ===\n" + potentialWinner + " wins!", NULL_KEY); + llMessageLinked(LINK_SET, MSG_PLAYER_WON, potentialWinner, NULL_KEY); + llMessageLinked(LINK_SET, MSG_EFFECT_CONFETTI, "VICTORY_CONFETTI", NULL_KEY); + + // RECORD WIN: Send ultimate survivor to World Ranking database + llMessageLinked(LINK_SET, MSG_LB_RECORD_PLAYER, potentialWinner + "|WIN", NULL_KEY); + + // Inform scoreboard of victory visuals + llMessageLinked(LINK_SCOREBOARD, MSG_GAME_STATUS, "Victory", NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_GAME_WON, potentialWinner, NULL_KEY); + + // CRITICAL: Signal Main Controller to perform final reset after victory delay + // Main Controller expects 998 | "GAME_WON|name" + llMessageLinked(LINK_SET, MSG_CONTINUE_GAME, "GAME_WON|" + potentialWinner, NULL_KEY); + } + } + } + return; + } } } + diff --git a/Game_Scoreboard_Manager_Linkset.lsl b/Game_Scoreboard_Manager_Linkset.lsl index 10f0405..153845f 100644 --- a/Game_Scoreboard_Manager_Linkset.lsl +++ b/Game_Scoreboard_Manager_Linkset.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // ============================================================================= // PERIL SCOREBOARD - GRID DISPLAY MANAGER (v3.2.7 Final) diff --git a/Main_Controller_Linkset.lsl b/Main_Controller_Linkset.lsl index 89033ac..379d751 100644 --- a/Main_Controller_Linkset.lsl +++ b/Main_Controller_Linkset.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // === Main Controller - Peril Dice Game (Linkset Version) === @@ -184,32 +184,7 @@ checkMemoryUsage(string context) { // NOTE FOR FUTURE AI/DEVS: KVP (World Rankings) ONLY requires the script to be // compiled with the Experience. Land-Scope whitelisting (llAgentInExperience) // is ONLY required for HUD auto-attachments and Animations. -checkExperience() { - dbg("šŸ” [Peril Dice] Experience Sentinel: Starting functional heartbeat..."); - - // PROBE 1: Land-Scope Check (Crucial for HUD auto-attachment) - if (!llAgentInExperience(llGetOwner())) { - llOwnerSay("šŸ›”ļø [Peril Dice] LAND NOTICE: Experience 'Final Girlz I.N.C.' is NOT enabled on this land."); - llOwnerSay("āš ļø [Peril Dice] Automatic HUD attachments will FAIL. To fix this:"); - llOwnerSay("šŸ‘‰ 1. Right-click the ground -> 'About Land'"); - llOwnerSay("šŸ‘‰ 2. Go to the 'Experiences' tab."); - llOwnerSay("šŸ‘‰ 3. Click the 'Add' button."); - llOwnerSay("šŸ‘‰ 4. Search for 'Final Girlz I.N.C.' and click 'OK'."); - - // Silent probe for KVP (which works regardless) - sentinelQueryID = llReadKeyValue("PERIL_SENTINEL"); - llSetTimerEvent(10.0); - currentTimerMode = TIMER_XP_CHECK; - } else { - llOwnerSay("āœ… [Peril Dice] Experience 'Final Girlz I.N.C.' is ACTIVE. HUD Auto-attachment ready."); - EX_READY = TRUE; - - // Full KVP heartbeat - sentinelQueryID = llReadKeyValue("PERIL_SENTINEL"); - llSetTimerEvent(10.0); - currentTimerMode = TIMER_XP_CHECK; - } -} +// MOVED: checkExperience() - delegated to Player_RegistrationManager.lsl // Send status message to scoreboard using link messages sendStatusMessage(string status) { @@ -333,8 +308,8 @@ resetGame() { llMessageLinked(LINK_SET, MSG_CLEAR_DICE, "", NULL_KEY); // Give other scripts time to finish their reset before starting sync - // INCREASED to 2.0s for high-speed Mono startup with Debug Logs OFF - llSleep(2.0); + // INCREASED to 5.0s for high-speed Mono startup with Debug Logs OFF + llSleep(5.0); // NEW: Trigger Leaderboard to reload rankings from KVP on reset llMessageLinked(LINK_SET, MSG_RESET_LEADERBOARD, "START_SYNC", NULL_KEY); @@ -374,9 +349,7 @@ default { // Reset the game on startup to ensure clean state resetGame(); - // Experience Sentinel check - Moved after reset to protect timer - llSleep(2.0); - checkExperience(); + // MOVED: checkExperience() - already called by Registration Management dbg("āœ… Main Controller initialization complete!"); } @@ -396,8 +369,7 @@ default { // Reset the game to ensure clean state when rezzed resetGame(); - // Experience Sentinel check - Moved after reset to protect timer - checkExperience(); + // MOVED: checkExperience() dbg("āœ… Main Controller reset complete after rez!"); } @@ -405,152 +377,10 @@ default { touch_start(integer total_number) { integer clickedLink = llDetectedLinkNumber(0); string clickedName = llGetLinkName(clickedLink); - - // Ensure core links are discovered to prevent misidentification - if (LINK_LEADERBOARD_BRIDGE == -1) { DISCOVER_CORE_LINKS(); } - - // IGNORE TOUCHES ON UI BRIDGES ONLY - // Always allow touch on Root (link 1) or any prim containing 'Scoreboard' or NOT containing 'FURWARE' - // ENHANCED: Also filter out click on Leaderboard elements which was causing redundant diagnostic logs - if (clickedLink != 1 && (llSubStringIndex(clickedName, "FURWARE") != -1 || llSubStringIndex(clickedName, "LB_") != -1)) { - dbg("šŸ›‘ [Touch Filter] Bypassing menu for Bridge element: " + clickedName); - return; - } - key toucher = llDetectedKey(0); - integer idx = llListFindList(players, [toucher]); - - // Enhanced disconnect/reconnect handling - check if this player should be picking - if (idx != -1 && toucher != llGetOwner() && roundStarted) { - string playerName = llList2String(names, idx); - - // ENHANCED: Check if this player is supposed to be the current picker based on game state - integer shouldBePicking = FALSE; - if (currentPickerIdx < llGetListLength(pickQueue)) { - string expectedPickerName = llList2String(pickQueue, currentPickerIdx); - if (playerName == expectedPickerName) { - shouldBePicking = TRUE; - // Fix corrupted currentPicker state - if (currentPicker != toucher) { - currentPicker = toucher; - } - } - } - - // 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()) { - currentPicker = toucher; - shouldBePicking = TRUE; - } - } - - // Check for dialog recovery (original logic OR enhanced detection) - if (currentPicker == toucher || shouldBePicking) { - llSay(0, "šŸ”„ " + playerName + " - Welcome back! Restoring your number picking dialog..."); - llMessageLinked(LINK_SET, MSG_GET_CURRENT_DIALOG, playerName, toucher); - return; - } - else if (playerName == perilPlayer && currentPickerIdx >= llGetListLength(pickQueue)) { - llSay(0, "šŸ”„ " + playerName + " - Welcome back! Restoring your roll dialog..."); - llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, toucher); - return; - } - } - // Special handling for owner during gameplay - if (toucher == llGetOwner() && roundStarted) { - string ownerName = getPlayerName(toucher); - list options = []; - string menuText = "Game in progress. What would you like to do?"; - - integer hasActiveDialog = FALSE; - if (currentPicker == toucher) { - options += ["šŸ”„ Recover Pick Dialog"]; - hasActiveDialog = TRUE; - } - if (ownerName == perilPlayer && currentPickerIdx >= llGetListLength(pickQueue)) { - options += ["šŸ”„ Recover Roll Dialog"]; - hasActiveDialog = TRUE; - } - - options += ["šŸ”§ Admin Menu"]; - - if (!hasActiveDialog) { - llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|0", toucher); - return; - } - - llDialog(toucher, menuText, options, DIALOG_CHANNEL); - return; - } - - if (toucher == llGetOwner()) { - dbg("šŸ” [DEBUG] Owner touched - idx: " + (string)idx + ", players list: " + llList2CSV(players)); - if (idx == -1) { - // Owner is not in game - show join/admin choice - if (llListFindList(pendingRegistrations, [toucher]) != -1) { - dbg("ā³ Registration already in progress for " + getPlayerName(toucher)); - return; - } - - // Owner is not in game - determine menu type based on player count - if (llGetListLength(players) >= 2) { - // Enough players for game - show starter menu - llMessageLinked(LINK_SET, MSG_SHOW_MENU, "unregistered_owner_starter|1", toucher); - } else { - // Not enough players - show basic menu - llMessageLinked(LINK_SET, MSG_SHOW_MENU, "unregistered_owner|0", toucher); - } - } else { - // Owner is already in game - show player menu with appropriate starter status - integer isStarter = TRUE; - integer k; - for (k = 0; k < idx && isStarter; k++) { - string existingName = llList2String(names, k); - if (llSubStringIndex(existingName, "Bot") != 0) { - isStarter = FALSE; - } - } - if (idx == 0) { - isStarter = TRUE; - } - llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, toucher); - } - } else if (idx != -1) { - // Non-owner player who is already registered - integer isStarter = TRUE; - integer m; - for (m = 0; m < idx && isStarter; m++) { - string existingName = llList2String(names, m); - if (llSubStringIndex(existingName, "Bot") != 0) { - isStarter = FALSE; - } - } - llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher); - } else { - string playerName = getPlayerName(toucher); - if (playerName != "") { - if (llListFindList(pendingRegistrations, [toucher]) != -1) { - dbg("ā³ Registration already in progress for " + playerName); - return; - } - - pendingRegistrations += [toucher]; - llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER_REQUEST, playerName + "|" + (string)toucher, NULL_KEY); - - integer isStarter = TRUE; - integer n; - for (n = 0; n < llGetListLength(names) && isStarter; n++) { - string existingName = llList2String(names, n); - if (llSubStringIndex(existingName, "Bot") != 0) { - isStarter = FALSE; - } - } - llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher); - } - } + // DELIVER: Forward touch event to Dialog Handler to save bytecode in Main + llMessageLinked(LINK_SET, MSG_TOUCH_EVENT, (string)clickedLink + "|" + clickedName, toucher); } link_message(integer sender, integer num, string str, key id) { @@ -618,224 +448,35 @@ default { } // Handle elimination requests - if (num == MSG_ELIMINATE_PLAYER) { - list parts = llParseString2List(str, ["|"], []); - if (llList2String(parts, 0) == "ELIMINATE_PLAYER") { - string eliminatedPlayer = llList2String(parts, 1); - integer idx = llListFindList(names, [eliminatedPlayer]); - if (idx != -1) { - // NEW: Only announce elimination if it's not the end of the game (winner) - if (llGetListLength(names) > 1) { - sendStatusMessage("PLAYER STRUCK!\n" + eliminatedPlayer + " has been eliminated!"); + // MOVED: MSG_ELIMINATE_PLAYER logic - delegated to Game_Manager.lsl + // Process ONLY the final list cleanup here after Game Manager finishes visuals + if (num == MSG_PROCESS_ELIMINATION) { + string eliminatedPlayer = str; + integer idx = llListFindList(names, [eliminatedPlayer]); + if (idx != -1) { + // Remove from indexed lists + players = llDeleteSubList(players, idx, idx); + names = llDeleteSubList(names, idx, idx); + lives = llDeleteSubList(lives, idx, idx); + floaterChannels = llDeleteSubList(floaterChannels, idx, idx); + + // Remove from picksData + integer pickIdx = -1; + integer p; + for (p = 0; p < llGetListLength(picksData); p++) { + string entry = llList2String(picksData, p); + if (llSubStringIndex(entry, eliminatedPlayer + "|") == 0) { + pickIdx = p; + jump found_pick_entry; } - - // FIRST: Set the player's lives to 0 and show 0 hearts - lives = llListReplaceList(lives, [0], idx, idx); - - // Send ONLY a direct scoreboard update to show 0 hearts - llMessageLinked(LINK_SCOREBOARD, MSG_PLAYER_UPDATE, - eliminatedPlayer + "|0|" + (string)llList2Key(players, idx), NULL_KEY); - - // RECORD LOSS: Send eliminated player to World Ranking database - llMessageLinked(LINK_SET, MSG_LB_RECORD_PLAYER, eliminatedPlayer + "|LOSS", NULL_KEY); - - // CRITICAL: Check for victory condition BEFORE assigning new peril player - integer willCauseVictory = (llGetListLength(names) <= 2); // After removing one player - string potentialWinner = ""; - - if (willCauseVictory) { - // Find the winner (the remaining non-eliminated player) - integer w; - for (w = 0; w < llGetListLength(names); w++) { - string candidateName = llList2String(names, w); - if (candidateName != eliminatedPlayer) { - potentialWinner = candidateName; - jump found_winner; - } - } - @found_winner; - - if (potentialWinner != "") { - // NEW: Broadcast sync BEFORE redraw to ensure Floater Manager has fresh heart count - syncGameState(); - - // SEND WINNER GLOW to scoreboard and trigger floater update - llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_WINNER, potentialWinner, NULL_KEY); - llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, potentialWinner, NULL_KEY); // Update winner's floater - dbg("šŸ† [Main Controller] Pre-victory winner glow sent for: " + potentialWinner); - } - } - - // Only assign new peril player if NOT causing victory - if (!willCauseVictory && (perilPlayer == eliminatedPlayer || perilPlayer == "")) { - // Find first remaining alive player to be new peril player (before removing eliminated player) - string newPerilPlayer = ""; - integer k; - for (k = 0; k < llGetListLength(names) && newPerilPlayer == ""; k++) { - string candidateName = llList2String(names, k); - if (candidateName != eliminatedPlayer) { // Skip the player being eliminated - integer candidateIdx = llListFindList(names, [candidateName]); - if (candidateIdx != -1 && candidateIdx < llGetListLength(lives)) { - integer candidateLives = llList2Integer(lives, candidateIdx); - if (candidateLives > 0) { - newPerilPlayer = candidateName; - } - } - } - } - - if (newPerilPlayer != "") { - perilPlayer = newPerilPlayer; - // Send peril player update to scoreboard for glow effect - llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_PERIL_PLAYER, newPerilPlayer, NULL_KEY); - } else { - perilPlayer = "NONE"; - // Clear peril player glow on scoreboard - llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_PERIL_PLAYER, "", NULL_KEY); - } - } - - // UNIFIED SYNC: Replaces inline sync calls to prevent language rift - syncGameState(); - - // Direct floater update to ensure eliminated status is displayed - llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, eliminatedPlayer, llList2Key(players, idx)); - llSleep(DELAY_ELIMINATION_NOTICE); // Give floater time to show eliminated status with red display - - if (willCauseVictory) { - // If this elimination will cause victory, give extra time for 0 hearts display - llSleep(DELAY_VICTORY_NOTICE); // Additional time for victory scenarios - } - - // CHANGED: Don't cleanup floaters during elimination - let them show eliminated status - // The floater will show red glow + "ELIMINATED!" and stay visible until game end - // integer ch = llList2Integer(floaterChannels, idx); - // llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY); - // llSleep(0.3); // Give floater time to cleanup - - // CRITICAL: Remove eliminated player's picks data by searching for their entry - // NOT by index, since picksData format is "PlayerName|picks" strings - integer pickIdx = -1; - integer p; - for (p = 0; p < llGetListLength(picksData); p++) { - string entry = llList2String(picksData, p); - if (llSubStringIndex(entry, eliminatedPlayer + "|") == 0) { - pickIdx = p; - jump found_pick_entry; - } - } - @found_pick_entry; - if (pickIdx != -1) { - picksData = llDeleteSubList(picksData, pickIdx, pickIdx); - } - - // Now remove from indexed lists (players, names, lives, floaterChannels) - players = llDeleteSubList(players, idx, idx); - names = llDeleteSubList(names, idx, idx); - lives = llDeleteSubList(lives, idx, idx); - floaterChannels = llDeleteSubList(floaterChannels, idx, idx); - - // NEW: FINAL SYNC - Tell everyone the list has actually SHRUNK now - // This prevents "Zombie Respawns" and "Ghost Heals" - syncGameState(); - - // Peril player assignment already handled before sync message was sent - - // CHANGED: Send to scoreboard, which will handle leaderboard updates - llMessageLinked(LINK_SCOREBOARD, MSG_GAME_LOST, eliminatedPlayer, NULL_KEY); - - // Status message now sent directly by Roll Module - - // Check for victory condition BEFORE calling updateHelpers() - if (llGetListLength(names) <= 1) { - if (llGetListLength(names) == 1) { - string winner = llList2String(names, 0); - - // ENHANCED: Give eliminated player's floater time to show eliminated status - // before declaring victory and cleaning up - dbg("ā³ [Main Controller] Final elimination detected - allowing display time before victory"); - llSleep(DELAY_ELIMINATION_NOTICE); // Give final eliminated player time to show red eliminated status - - // 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!"); - - // NEW: Send victory status to board and display winner name! - sendStatusMessage("Victory"); - llMessageLinked(LINK_SET, MSG_STATUS_TEXT, "=== ULTIMATE VICTORY ===\n" + winner + " wins!", NULL_KEY); - - llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY); - llMessageLinked(LINK_SET, MSG_EFFECT_CONFETTI, "VICTORY_CONFETTI", NULL_KEY); - - // IMPORTANT: Send winner update to scoreboard and trigger floater update - llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_WINNER, winner, NULL_KEY); - llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, winner, NULL_KEY); // Update winner's floater - - // RECORD WIN: Send ultimate survivor to World Ranking database - llMessageLinked(LINK_SET, MSG_LB_RECORD_PLAYER, winner + "|WIN", NULL_KEY); - - // CHANGED: Send to scoreboard, which will handle visual updates (no more duplicate records) - llMessageLinked(LINK_SCOREBOARD, MSG_GAME_WON, winner, NULL_KEY); - - // Use timer instead of llSleep to avoid immediate reset - dbg("šŸ† [Main Controller] Winner glow applied - starting victory delay timer"); - victoryDelayTimer = llGetUnixTime(); - currentTimerMode = TIMER_VICTORY_DELAY; - llSetTimerEvent(1.0); // Check every second - return; // Exit here - timer will handle reset - } 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!"); - - // Use timer for no survivors scenario too - victoryDelayTimer = llGetUnixTime(); - currentTimerMode = TIMER_VICTORY_DELAY; - llSetTimerEvent(1.0); - return; - } - } - - // CRITICAL: After elimination, sync state and continue game with new peril player - if (perilPlayer != "" && perilPlayer != "NONE") { - // Sync the updated game state to all modules (4-part format) - // Optimized to avoid large temporary string variables - llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, - llList2CSV(lives) + "~EMPTY~" + perilPlayer + "~" + llList2CSV(names), NULL_KEY); - llSleep(DELAY_SYNC_PROPAGATION); // Give sync time to propagate - - // Continue the game with the new peril player - delegate to Game Manager - llMessageLinked(LINK_SET, 998, perilPlayer, NULL_KEY); - } - - // MINIMAL: Only send direct scoreboard update for remaining players - // Skip heavy updateHelpers() calls to prevent memory crashes } + @found_pick_entry; + if (pickIdx != -1) { + picksData = llDeleteSubList(picksData, pickIdx, pickIdx); + } + + syncGameState(); + dbg("šŸŽÆ [Main Controller] Deep Cleanup Complete: " + eliminatedPlayer + " purged from state."); } return; } @@ -929,37 +570,35 @@ default { // Handle ready state queries if (num == MSG_QUERY_READY_STATE) { - list queryParts = llParseString2List(str, ["|"], []); - string playerName; - integer requestID = 0; + list qParts = llParseString2List(str, ["|"], []); + string qPlayerName; + integer qRequestID = 0; - if (llGetListLength(queryParts) >= 2) { - playerName = llList2String(queryParts, 0); - requestID = (integer)llList2String(queryParts, 1); + if (llGetListLength(qParts) >= 2) { + qPlayerName = llList2String(qParts, 0); + qRequestID = (integer)llList2String(qParts, 1); } else { - playerName = str; + qPlayerName = str; } - integer isReady = llListFindList(readyPlayers, [playerName]) != -1; - integer isBot = llSubStringIndex(playerName, "Bot") == 0; - string result = playerName + "|" + (string)isReady + "|" + (string)isBot + "|" + (string)requestID; - llMessageLinked(LINK_SET, MSG_READY_STATE_RESULT, result, id); + integer isReady = llListFindList(readyPlayers, [qPlayerName]) != -1; + integer isBot = llSubStringIndex(qPlayerName, "Bot") == 0; + string resultStr = qPlayerName + "|" + (string)isReady + "|" + (string)isBot + "|" + (string)qRequestID; + llMessageLinked(LINK_SET, MSG_READY_STATE_RESULT, resultStr, id); return; } // Handle owner status queries if (num == MSG_QUERY_OWNER_STATUS) { - list queryParts = llParseString2List(str, ["|"], []); - string ownerName; - integer requestID = 0; + list osParts = llParseString2List(str, ["|"], []); + string osName; + integer osRequestID = 0; - // Owner status check (Now fully automated via on_rez / reset) - - if (llGetListLength(queryParts) >= 2) { - ownerName = llList2String(queryParts, 0); - requestID = (integer)llList2String(queryParts, 1); + if (llGetListLength(osParts) >= 2) { + osName = llList2String(osParts, 0); + osRequestID = (integer)llList2String(osParts, 1); } else { - ownerName = str; + osName = str; } // Check if owner is registered @@ -983,7 +622,7 @@ default { } } - string result = ownerName + "|" + (string)isRegistered + "|" + (string)isPending + "|" + (string)isStarter + "|" + (string)requestID; + string result = osName + "|" + (string)isRegistered + "|" + (string)isPending + "|" + (string)isStarter + "|" + (string)osRequestID; llMessageLinked(LINK_SET, MSG_OWNER_STATUS_RESULT, result, id); return; } @@ -1313,8 +952,8 @@ default { llSay(0, "⚔ ALL PARTICIPANTS READY! THE DEADLY PERIL DICE GAME BEGINS! ⚔"); - integer actualPlayerCount = llGetListLength(names); - llSetText("šŸŽ® GAME IN PROGRESS\n" + (string)actualPlayerCount + " players", <1.0, 0.2, 0.2>, 1.0); + integer currentLiveCount = llGetListLength(names); + llSetText("šŸŽ® GAME IN PROGRESS\n" + (string)currentLiveCount + " players", <1.0, 0.2, 0.2>, 1.0); // FIXED: Use proper announcement instead of internal texture name "Title" sendStatusMessage("GAME STARTING!\nAll players are ready!"); diff --git a/NumberPicker_DialogHandler.lsl b/NumberPicker_DialogHandler.lsl index f053bf1..bd2c80e 100644 --- a/NumberPicker_DialogHandler.lsl +++ b/NumberPicker_DialogHandler.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // === Dialog Handler (Updated for Peril Dice - Paginated Multiple Picks) === diff --git a/Peril_Constants.lsl b/Peril_Constants.lsl index 16ef32d..8d027ac 100644 --- a/Peril_Constants.lsl +++ b/Peril_Constants.lsl @@ -36,7 +36,7 @@ key EXPERIENCE_ID = "06926390-26e6-11f1-a452-0242ac110003"; // Final Girlz I.N.C #define FW_DONE "fw_done" // --- System/Debug (9000-9999) --- -#define DEBUG_LOGS 0 // GLOBAL MASTER SWITCH - Set to 1 for dev feedback, 0 to STRIP from memory +#define DEBUG_LOGS 1 // GLOBAL MASTER SWITCH - Set to 1 for dev feedback, 0 to STRIP from memory #if DEBUG_LOGS #define dbg(msg) llOwnerSay(msg) #else @@ -108,6 +108,9 @@ key EXPERIENCE_ID = "06926390-26e6-11f1-a452-0242ac110003"; // Final Girlz I.N.C #define MSG_REQUEST_PLAYER_LIST_KICK 8009 #define MSG_LOCK_GAME 9001 #define MSG_UNLOCK_GAME 9002 +#define MSG_TOUCH_EVENT 215 +#define MSG_PROCESS_ELIMINATION 3015 + // --- Dialogs & Menus (100-399) --- #define MSG_SHOW_MENU 201 diff --git a/PlayerStatus_Float.lsl b/PlayerStatus_Float.lsl index c75b48a..dd05444 100644 --- a/PlayerStatus_Float.lsl +++ b/PlayerStatus_Float.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // === Player Status Float - Enhanced Display === diff --git a/Player_DialogHandler.lsl b/Player_DialogHandler.lsl index 54c2af5..27750d4 100644 --- a/Player_DialogHandler.lsl +++ b/Player_DialogHandler.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // === Dialog Handler (Owner & Player) with unified Ready/Leave menu and join support === @@ -88,6 +88,11 @@ list troubleshootingOptions = ["Cleanup Floaters", "Force Floaters", "ā¬…ļø Bac // State tracking for dynamic ready menu with race condition protection key pendingMenuPlayer = NULL_KEY; +integer roundStarted = FALSE; +list players = []; +list names = []; +list readyPlayers = []; +list pendingRegistrations = []; // Local track of registration attempts integer pendingMenuIsStarter = FALSE; integer pendingMenuIsOwner = FALSE; integer pendingMenuRequestID = 0; // Unique ID for each menu request @@ -404,6 +409,63 @@ default { return; } + // --- TOP LEVEL EVENT HANDLERS --- + if (num == MSG_TOUCH_EVENT) { + list parts = llParseString2List(str, ["|"], []); + integer clickedLink = (integer)llList2String(parts, 0); + string clickedName = llList2String(parts, 1); + key toucher = id; + if (clickedLink != 1 && (llSubStringIndex(clickedName, "FURWARE") != -1 || llSubStringIndex(clickedName, "LB_") != -1)) return; + integer idx = llListFindList(players, [toucher]); + if (roundStarted) { + if (toucher == llGetOwner()) { llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|0", toucher); return; } + if (idx != -1) { llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|0", toucher); return; } + } + if (toucher == llGetOwner()) { + if (idx == -1) { + if (llListFindList(pendingRegistrations, [toucher]) != -1) return; + if (llGetListLength(players) >= 2) llMessageLinked(LINK_SET, MSG_SHOW_MENU, "unregistered_owner_starter|1", toucher); + else llMessageLinked(LINK_SET, MSG_SHOW_MENU, "unregistered_owner|0", toucher); + } else { + integer isStarter = (idx == 0); + llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, toucher); + } + } else if (idx != -1) { + integer isStarter = (idx == 0); + llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher); + } else { + string playerName = llGetDisplayName(toucher); + if (playerName == "") playerName = llKey2Name(toucher); + if (llListFindList(pendingRegistrations, [toucher]) == -1) { + pendingRegistrations += [toucher]; + if (llGetListLength(pendingRegistrations) > 10) pendingRegistrations = llDeleteSubList(pendingRegistrations, 0, 0); + llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER_REQUEST, playerName + "|" + (string)toucher, NULL_KEY); + } + llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|0", toucher); + } + return; + } + if (num == MSG_SYNC_GAME_STATE) { + list parts = llParseStringKeepNulls(str, ["~"], []); + if (llGetListLength(parts) >= 4) names = llCSV2List(llList2String(parts, 3)); + return; + } + + if (num == MSG_UPDATE_MAIN_LISTS) { + if (llSubStringIndex(str, "READY_LIST|") == 0) readyPlayers = llCSV2List(llGetSubString(str, 11, -1)); + else { + list parts = llParseString2List(str, ["~"], []); + if (llGetListLength(parts) >= 2 && llListFindList(players, [id]) == -1) players += [id]; + } + return; + } + + if (num == MSG_RESET_ALL) { + players = []; names = []; readyPlayers = []; pendingRegistrations = []; + roundStarted = FALSE; + return; + } + if (num == MSG_SHOW_MENU) { dbg("šŸ•µļø [Dialog Handler] Received MSG_SHOW_MENU (" + str + ") from script link/sender: " + (string)sender + ", ID: " + (string)id); list args = llParseString2List(str, ["|"], []); @@ -414,44 +476,33 @@ default { string targetType = llList2String(args, 0); integer isStarter = (integer)llList2String(args, 1); 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") { - // Show admin category menu directly showOwnerMenu(id); } else if (targetType == "unregistered_owner") { - // Unregistered owner - basic menu (join, owner menu) showUnregisteredOwnerMenu(id, FALSE); } else if (targetType == "unregistered_owner_starter") { - // Unregistered owner with enough players to start game showUnregisteredOwnerMenu(id, TRUE); } } else if (num == MSG_READY_STATE_RESULT) { - // Parse ready state response and show appropriate menu list parts = llParseString2List(str, ["|"], []); if (llGetListLength(parts) >= 4) { string playerName = llList2String(parts, 0); integer isReady = (integer)llList2String(parts, 1); integer isBot = (integer)llList2String(parts, 2); integer responseRequestID = (integer)llList2String(parts, 3); - - // Validate that this response matches our pending request if (pendingMenuPlayer == id && responseRequestID == pendingMenuRequestID) { - // Check for timeout if ((llGetTime() - pendingMenuTimestamp) > MENU_REQUEST_TIMEOUT) { dbg("ā° Menu request timed out, ignoring response"); pendingMenuPlayer = NULL_KEY; return; } - showReadyLeaveMenuWithState(pendingMenuPlayer, pendingMenuIsStarter, pendingMenuIsOwner, isReady, isBot); - // Reset pending state ONLY after successfully showing the dialog pendingMenuPlayer = NULL_KEY; pendingMenuRequestID = 0; } @@ -465,29 +516,16 @@ default { dbg("šŸ“‹ Fetching list of players for pick management..."); showPickManageMenu(id, playerNames); } - // Handle player list result for kick functionality else if (num == MSG_REQUEST_PLAYER_LIST_KICK) { - dbg("šŸ” [Dialog Handler] Received kick player list string: '" + str + "'"); list rawNames = llParseString2List(str, [","], []); - dbg("šŸ” [Dialog Handler] Raw parsed into " + (string)llGetListLength(rawNames) + " names: " + llList2CSV(rawNames)); - - // Filter out any command strings that might have gotten mixed in list playerNames = []; integer i; for (i = 0; i < llGetListLength(rawNames); i++) { string name = llStringTrim(llList2String(rawNames, i), STRING_TRIM); - // Skip empty names and command strings - if (name != "" && - llSubStringIndex(name, "REQUEST_") != 0 && - llSubStringIndex(name, "COMMAND_") != 0 && - llSubStringIndex(name, "MSG_") != 0) { + if (name != "" && llSubStringIndex(name, "REQUEST_") != 0 && llSubStringIndex(name, "COMMAND_") != 0 && llSubStringIndex(name, "MSG_") != 0) { playerNames += [name]; - } else { - dbg("āš ļø [Dialog Handler] Filtered out invalid name: '" + name + "'"); } } - - dbg("šŸ” [Dialog Handler] Filtered to " + (string)llGetListLength(playerNames) + " valid names: " + llList2CSV(playerNames)); showKickPlayerMenu(id, playerNames); } else if (num == MSG_PICK_LIST_RESULT) { @@ -495,11 +533,8 @@ default { string returnedName = llList2String(parts, 0); string picks = llList2String(parts, 1); if (llStringTrim(returnedName, STRING_TRIM) == llStringTrim(currentPickTarget, STRING_TRIM)) { - dbg("āœ… Matched pick list for: " + returnedName); currentPickList = llCSV2List(picks); llMessageLinked(LINK_THIS, MSG_LIFE_LOOKUP_REQUEST, currentPickTarget, id); - } else { - dbg("āŒ Name mismatch: got " + returnedName + " but expected " + currentPickTarget); } } else if (num == MSG_LIFE_LOOKUP_REQUEST) { @@ -510,7 +545,6 @@ default { } } 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); @@ -518,45 +552,23 @@ default { 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) { - dbg("ā° 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 - dbg("šŸ” [Debug] Owner is registered, isStarter=" + (string)isStarter + ", showing ready/leave menu"); - dbg("šŸ” [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); - dbg("šŸ” [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 + showUnregisteredOwnerMenu(id, (llGetListLength(players) >= 2)); pendingMenuPlayer = NULL_KEY; pendingMenuRequestID = 0; } - } else { - dbg("āš ļø Owner status response mismatch - ignoring"); } - } else { - dbg("āš ļø Invalid owner status result format - expected 5 parts, got " + (string)llGetListLength(parts)); } return; } diff --git a/Player_RegistrationManager.lsl b/Player_RegistrationManager.lsl index ea89699..25ec85f 100644 --- a/Player_RegistrationManager.lsl +++ b/Player_RegistrationManager.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // === Player Registration Manager === // Handles all player registration, join/leave operations to reduce Main Controller memory usage @@ -17,6 +17,12 @@ integer FLOATER_BASE_CHANNEL; integer roundStarted = FALSE; integer gameStarting = FALSE; +// Experience Sentinel Tracking +key sentinelQueryID; +integer EX_READY = FALSE; +#define TIMER_XP_CHECK 1 +integer currentTimerMode = 0; + integer resetInProgress = FALSE; // Lockout for reset syncs integer calculateChannel(integer offset) { @@ -56,6 +62,31 @@ regionMsg(key player, string msg) { llMessageLinked(LINK_SET, MSG_REGION_MESSAGE, (string)player + "|" + msg, NULL_KEY); } +// MOVED: Experience Sentinel Logic +checkExperience() { + dbg("šŸ” [RegMgr] Experience Sentinel: Starting functional diagnostics..."); + + if (!llAgentInExperience(llGetOwner())) { + llOwnerSay("šŸ›”ļø [Peril Dice] LAND NOTICE: Experience 'Final Girlz I.N.C.' is NOT enabled on this land."); + llOwnerSay("āš ļø [Peril Dice] Automatic HUD attachments will FAIL. To fix this:"); + llOwnerSay("šŸ‘‰ 1. Right-click the ground -> 'About Land'"); + llOwnerSay("šŸ‘‰ 2. Go to the 'Experiences' tab."); + llOwnerSay("šŸ‘‰ 3. Click the 'Add' button."); + llOwnerSay("šŸ‘‰ 4. Search for 'Final Girlz I.N.C.' and click 'OK'."); + + sentinelQueryID = llReadKeyValue("PERIL_SENTINEL"); + llSetTimerEvent(10.0); + currentTimerMode = TIMER_XP_CHECK; + } else { + llOwnerSay("āœ… [Peril Dice] Experience 'Final Girlz I.N.C.' is ACTIVE. HUD Auto-attachment ready."); + EX_READY = TRUE; + + sentinelQueryID = llReadKeyValue("PERIL_SENTINEL"); + llSetTimerEvent(10.0); + currentTimerMode = TIMER_XP_CHECK; + } +} + // Handle player registration with all the complex logic moved here handlePlayerRegistration(string regData, key requesterId) { // MEMORY DEBUG: Check memory at start @@ -153,12 +184,11 @@ handlePlayerRegistration(string regData, key requesterId) { llMessageLinked(LINK_SET, MSG_UPDATE_MAIN_LISTS, updateData, newKey); // Show appropriate menu to the new player + integer isStarter = (humanCount <= 1); if (newKey == llGetOwner()) { - integer isStarter = (humanCount <= 1); llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, newKey); } else { // NEW: Also show menu for non-owners immediately after registration - integer isStarter = (humanCount <= 1); llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, newKey); } @@ -169,6 +199,7 @@ default { state_entry() { DISCOVER_CORE_LINKS(); initializeChannels(); + checkExperience(); REPORT_MEMORY(); dbg("šŸŽÆ [RegMgr] Player Registration Manager ready - discovery complete! Scoreboard: " + (string)LINK_SCOREBOARD); } @@ -176,6 +207,7 @@ default { on_rez(integer start_param) { DISCOVER_CORE_LINKS(); initializeChannels(); + checkExperience(); REPORT_MEMORY(); dbg("šŸŽÆ [RegMgr] Player Registration Manager rezzed - resetting state..."); // Reset local state @@ -338,8 +370,20 @@ default { } timer() { + if (currentTimerMode == TIMER_XP_CHECK) { + // Diagnostic timer complete + llSetTimerEvent(0); + currentTimerMode = 0; + return; + } resetInProgress = FALSE; llSetTimerEvent(0); dbg("šŸŽÆ [RegMgr] Reset lockout complete - sync active."); } + + dataserver(key queryid, string data) { + if (queryid == sentinelQueryID) { + dbg("šŸ“” [RegMgr] Experience KVP Heartbeat: " + data); + } + } } \ No newline at end of file diff --git a/Roll_ConfettiModule.lsl b/Roll_ConfettiModule.lsl index 927aa3c..d32a081 100644 --- a/Roll_ConfettiModule.lsl +++ b/Roll_ConfettiModule.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // === Roll and Confetti Module (with Roll Dialog Handler) === diff --git a/System_Debugger.lsl b/System_Debugger.lsl index 0e44f09..667d253 100644 --- a/System_Debugger.lsl +++ b/System_Debugger.lsl @@ -1,4 +1,4 @@ -#include "Peril_Constants.lsl" +#include "peril/Peril_Constants.lsl" // === System Debugger - Game State Validation === // Validates game state consistency and reports issues