From 915c079a7a03b1bd66db137b18f64bf0539ef26c Mon Sep 17 00:00:00 2001 From: RebeccaNod1 Date: Sat, 4 Apr 2026 03:38:41 -0600 Subject: [PATCH] chore: v3.1.0 Peril Dice - Linkset Stability Update & FURWARE Text Mesh Migration --- Bot_Manager.lsl | 92 +- CHANGELOG.md | 25 + CHANGELOG_Notecard.txt | 26 + Controller_Memory.lsl | 72 +- Controller_MessageHandler.lsl | 150 +-- Enhanced_LinkScanner.lsl | 15 +- FURWARE_Leaderboard_Communication_Linkset.lsl | 49 + FURWARE_Text_Dice_Bridge_Linkset.lsl | 66 ++ Floater_Manager.lsl | 612 ++-------- Furware_Text_Engine.lsl | 1055 +++++++++++++++++ GAME_RULES.md | 13 +- GAME_RULES_NOTECARD.txt | 11 +- Game_Calculator.lsl | 71 +- Game_Manager.lsl | 143 +-- Game_Scoreboard_Manager_Linkset.lsl | 737 +++++------- Leaderboard_Communication_Linkset.lsl | 229 ---- MARKETPLACE_DESCRIPTION.txt | 13 +- Main_Controller_Linkset.lsl | 167 +-- NumberPicker_DialogHandler.lsl | 129 +- Peril_Constants.lsl | 131 ++ Peril_Dice_Updater.lsl | 281 ----- PlayerStatus_Float.lsl | 284 +---- Player_DialogHandler.lsl | 68 +- Player_RegistrationManager.lsl | 54 +- README.md | 45 +- README_Notecard.txt | 35 +- Roll_ConfettiModule.lsl | 178 ++- System_Debugger.lsl | 42 +- Temp_LinkScanner.lsl | 33 - Test_Updater.lsl | 79 -- Update_Checker.lsl | 431 ------- Update_Receiver.lsl | 507 -------- Verbose_Logger.lsl | 79 -- XyzzyText_Dice_Bridge_Linkset.lsl | 145 --- xyzzy_Master_script.lsl | 443 ------- 35 files changed, 2308 insertions(+), 4202 deletions(-) create mode 100644 FURWARE_Leaderboard_Communication_Linkset.lsl create mode 100644 FURWARE_Text_Dice_Bridge_Linkset.lsl create mode 100644 Furware_Text_Engine.lsl delete mode 100644 Leaderboard_Communication_Linkset.lsl create mode 100644 Peril_Constants.lsl delete mode 100644 Peril_Dice_Updater.lsl delete mode 100644 Temp_LinkScanner.lsl delete mode 100644 Test_Updater.lsl delete mode 100644 Update_Checker.lsl delete mode 100644 Update_Receiver.lsl delete mode 100644 Verbose_Logger.lsl delete mode 100644 XyzzyText_Dice_Bridge_Linkset.lsl delete mode 100644 xyzzy_Master_script.lsl diff --git a/Bot_Manager.lsl b/Bot_Manager.lsl index 8c94428..25f9e26 100644 --- a/Bot_Manager.lsl +++ b/Bot_Manager.lsl @@ -1,11 +1,12 @@ -// Bot Manager for Peril Dice +#include "Peril_Constants.lsl" + // Handles logic for test players (bots): picking numbers and rolling dice // ============================================================================= // DYNAMIC CHANNEL CONFIGURATION // ============================================================================= -// Base channel offset - should match Main.lsl +// Base channel offset must match Main.lsl #define CHANNEL_BASE -77000 // Calculate channels dynamically to avoid hardcoded conflicts @@ -35,7 +36,6 @@ initializeChannels() { // Configuration integer LISTEN_CHANNEL; // Will be set dynamically -#define MSG_SYNC_GAME_STATE 107 // Listen handle management integer listenHandle = -1; @@ -57,9 +57,6 @@ list processedBotCommands = []; // Track sent BOT_PICKED messages to prevent duplicate sending list sentBotMessages = []; -// Verbose logging control - toggled by owner -integer VERBOSE_LOGGING = FALSE; - // Memory reporting function reportMemoryUsage(string scriptName) { integer used = llGetUsedMemory(); @@ -67,7 +64,7 @@ reportMemoryUsage(string scriptName) { integer total = used + free; float percentUsed = ((float)used / (float)total) * 100.0; - llOwnerSay("๐Ÿง  [" + scriptName + "] Memory: " + + dbg("๐Ÿง  [" + scriptName + "] Memory: " + (string)used + " used, " + (string)free + " free (" + llGetSubString((string)percentUsed, 0, 4) + "% used)"); @@ -79,7 +76,7 @@ checkMemoryUsage(string context) { float memoryPercentage = (float)usedMemory / 65536.0; // LSL scripts have 64KB limit if (memoryPercentage > MEMORY_WARNING_THRESHOLD) { - llOwnerSay("[Bot Manager] โš ๏ธ High memory usage in " + context + ": " + + dbg("๐Ÿค– [Bot Manager] โš ๏ธ High memory usage in " + context + ": " + (string)usedMemory + " bytes (" + (string)llRound(memoryPercentage * 100.0) + "% of 64KB limit)"); } @@ -87,23 +84,20 @@ checkMemoryUsage(string context) { // Helper to parse and respond to pick commands doBotPick(string botName, integer count, integer diceMax, list avoidNumbers) { - 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 integer botIdx = llListFindList(names, [botName]); if (botIdx == -1) { - llOwnerSay("[Bot Manager] โŒ Bot '" + botName + "' not found in game - ignoring command"); + dbg("๐Ÿค– [Bot Manager] โŒ Bot '" + botName + "' not found in game - ignoring command"); return; } - 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"); + dbg("๐Ÿค– [Bot Manager] โŒ Bot '" + botName + "' is eliminated (lives=" + (string)botLives + ") - ignoring command"); return; } - if (VERBOSE_LOGGING) llOwnerSay("[Bot Manager] ๐Ÿ”ง Bot lives check passed:" + (string)botLives); // IMPROVED ALGORITHM: Calculate available numbers first list availableNumbers = []; @@ -121,13 +115,13 @@ doBotPick(string botName, integer count, integer diceMax, list avoidNumbers) { // Graceful degradation: adjust target if not enough numbers available if (availableCount < count) { if (availableCount > 0) { - llOwnerSay("[Bot Manager] โš ๏ธ " + botName + " requested " + (string)count + " picks, but only " + (string)availableCount + " numbers available - picking all available"); + dbg("๐Ÿค– [Bot Manager] โš ๏ธ " + botName + " requested " + (string)count + " picks, but only " + (string)availableCount + " numbers available - picking all available"); targetCount = availableCount; } else { - llOwnerSay("[Bot Manager] โŒ " + botName + " cannot pick any numbers - all " + (string)diceMax + " numbers are taken!"); + dbg("๐Ÿค– [Bot Manager] โŒ " + botName + " cannot pick any numbers - all " + (string)diceMax + " numbers are taken!"); // Still send empty response to prevent game hanging string emptyResponse = "BOT_PICKED:" + botName + ":"; - llMessageLinked(LINK_SET, -9997, emptyResponse, NULL_KEY); + llMessageLinked(LINK_SET, MSG_BOT_PICKED, emptyResponse, NULL_KEY); return; } } @@ -150,32 +144,29 @@ doBotPick(string botName, integer count, integer diceMax, list avoidNumbers) { // Final validation if (llGetListLength(picks) < count) { - llOwnerSay("[Bot Manager] โ„น๏ธ " + botName + " picked " + (string)llGetListLength(picks) + "/" + (string)count + " numbers (limited by availability)"); + dbg("๐Ÿค– [Bot Manager] โ„น๏ธ " + botName + " picked " + (string)llGetListLength(picks) + "/" + (string)count + " numbers (limited by availability)"); } else { - llOwnerSay("[Bot Manager] โœ… " + botName + " successfully picked " + (string)llGetListLength(picks) + " numbers"); + dbg("๐Ÿค– [Bot Manager] โœ… " + botName + " successfully picked " + (string)llGetListLength(picks) + " numbers"); } string pickString = llDumpList2String(picks, ";"); string response = "BOT_PICKED:" + botName + ":" + pickString; // Check if we've already sent this exact message to prevent duplicates - 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); + dbg("๐Ÿค– [Bot Manager] โš ๏ธ DUPLICATE SEND - Already sent message: " + response); return; } - // Mark this message as sent BEFORE sending to prevent race conditions - sentBotMessages += [response]; - if (VERBOSE_LOGGING) llOwnerSay("[Bot Manager] ๐Ÿ” DEBUG - Added to sentBotMessages, new list: " + llDumpList2String(sentBotMessages, "|")); + // Prepend to list for more efficient check later + sentBotMessages = [response] + llList2List(sentBotMessages, 0, 19); // Keep last 20 - llOwnerSay("[Bot Manager] ๐Ÿค– " + botName + " picked: " + llList2CSV(picks)); - llOwnerSay("[Bot Manager] ๐Ÿ“ฌ SENDING BOT_PICKED: " + response); + dbg("๐Ÿค– [Bot Manager] ๐Ÿค– " + botName + " picked: " + llList2CSV(picks)); + dbg("๐Ÿค– [Bot Manager] ๐Ÿ“ฌ SENDING BOT_PICKED: " + response); // Send message immediately to avoid timing issues - llMessageLinked(LINK_SET, -9997, response, NULL_KEY); - llOwnerSay("[Bot Manager] ๐Ÿ“ฌ BOT_PICKED message sent via llMessageLinked(LINK_SET, -9997, ...)"); + llMessageLinked(LINK_SET, MSG_BOT_PICKED, response, NULL_KEY); + dbg("๐Ÿค– [Bot Manager] ๐Ÿ“ฌ BOT_PICKED message sent via llMessageLinked(LINK_SET, MSG_BOT_PICKED, ...)"); // Do NOT sleep after sending messages - this causes LSL message delivery loops! } @@ -185,18 +176,18 @@ doBotRoll(string botName, integer diceMax) { // Validate bot exists and is still alive integer rollBotIdx = llListFindList(names, [botName]); if (rollBotIdx == -1) { - llOwnerSay("[Bot Manager] โŒ Bot '" + botName + "' not found in game - ignoring roll command"); + dbg("๐Ÿค– [Bot Manager] โŒ Bot '" + botName + "' not found in game - ignoring roll command"); return; } integer rollBotLives = llList2Integer(lives, rollBotIdx); if (rollBotLives <= 0) { - llOwnerSay("[Bot Manager] โŒ Bot '" + botName + "' is eliminated (lives=" + (string)rollBotLives + ") - ignoring roll command"); + dbg("๐Ÿค– [Bot Manager] โŒ Bot '" + botName + "' is eliminated (lives=" + (string)rollBotLives + ") - ignoring roll command"); return; } if (botName != perilPlayer) { - llOwnerSay("[Bot Manager] โŒ Bot '" + botName + "' is not the peril player (current peril: '" + perilPlayer + "') - ignoring roll command"); + dbg("๐Ÿค– [Bot Manager] โŒ Bot '" + botName + "' is not the peril player (current peril: '" + perilPlayer + "') - ignoring roll command"); return; } @@ -205,6 +196,7 @@ doBotRoll(string botName, integer diceMax) { integer roll = 1 + (integer)(llFrand((float)diceMax)); llRegionSay(LISTEN_CHANNEL, "BOT_ROLL:" + botName + ":" + (string)roll); + dbg("๐Ÿค– [Bot Manager] ๐ŸŽฒ " + botName + " rolled: " + (string)roll); } default { @@ -226,12 +218,12 @@ default { sentBotMessages = []; listenHandle = llListen(LISTEN_CHANNEL, "", NULL_KEY, ""); - llOwnerSay("๐Ÿค– Bot Manager ready!"); + dbg("๐Ÿค– [Bot Manager] Bot Manager ready!"); } on_rez(integer start_param) { reportMemoryUsage("๐Ÿค– Bot Manager"); - llOwnerSay("๐Ÿ”„ Bot Manager rezzed - reinitializing..."); + dbg("๐Ÿค– [Bot Manager] ๐Ÿ”„ Bot Manager rezzed - reinitializing..."); // Re-initialize dynamic channels initializeChannels(); @@ -249,33 +241,19 @@ default { sentBotMessages = []; listenHandle = llListen(LISTEN_CHANNEL, "", NULL_KEY, ""); - llOwnerSay("โœ… Bot Manager reset complete after rez!"); + dbg("๐Ÿค– [Bot Manager] โœ… Bot Manager reset complete after rez!"); } 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") { + if (num == MSG_RESET_ALL && str == "FULL_RESET") { // Reset bot manager state names = []; lives = []; perilPlayer = ""; processedBotCommands = []; // Clear processed commands for new game sentBotMessages = []; // Clear sent messages for new game - llOwnerSay("[Bot Manager] Reset complete!"); + dbg("๐Ÿค– [Bot Manager] Reset complete!"); return; } @@ -306,7 +284,7 @@ default { if (oldPerilPlayer != perilPlayer && perilPlayer != "") { processedBotCommands = []; sentBotMessages = []; - llOwnerSay("[Bot Manager] New round detected (peril change), cleared processed commands and sent messages"); + dbg("๐Ÿค– [Bot Manager] New round detected (peril change), cleared processed commands and sent messages"); } else { // Only clear if we detect truly empty picks data during an active game // Don't clear during initial registration (when picks = "EMPTY") @@ -314,17 +292,17 @@ default { if (picksStr != "EMPTY" && (picksStr == "" || llSubStringIndex(picksStr, "|") == -1) && perilPlayer != "") { processedBotCommands = []; sentBotMessages = []; - llOwnerSay("[Bot Manager] New round detected (picks cleared), cleared processed commands and sent messages"); + dbg("๐Ÿค– [Bot Manager] New round detected (picks cleared), cleared processed commands and sent messages"); } } } return; } - if (num == -9999) { + if (num == MSG_BOT_COMMAND) { if (llSubStringIndex(str, "BOT_PICK:") == 0) { // Format: BOT_PICK:bot_1:3:20:1,2,3 (last part is optional already picked numbers) - llOwnerSay("[Bot Manager] ๐Ÿ“ฌ RECEIVED BOT_PICK: " + str); + dbg("๐Ÿค– [Bot Manager] ๐Ÿ“ฌ RECEIVED BOT_PICK: " + str); list parts = llParseStringKeepNulls(str, [":"], []); if (llGetListLength(parts) >= 4) { string botName = llList2String(parts, 1); @@ -336,13 +314,13 @@ default { string commandSignature = "PICK:" + botName + ":" + (string)count + ":" + (string)diceMax + ":" + perilPlayer; if (llListFindList(processedBotCommands, [commandSignature]) != -1) { - llOwnerSay("[Bot Manager] โš ๏ธ DUPLICATE - Ignoring identical command for " + botName + " (already processed this exact pick request)"); + dbg("๐Ÿค– [Bot Manager] โš ๏ธ DUPLICATE - Ignoring identical command for " + botName + " (already processed this exact pick request)"); return; } // Mark this command as processed BEFORE doing the work to prevent race conditions processedBotCommands += [commandSignature]; - llOwnerSay("[Bot Manager] โœ… Processing " + botName + " pick command"); + dbg("๐Ÿค– [Bot Manager] โœ… Processing " + botName + " pick command"); list avoidNumbers = []; if (llGetListLength(parts) >= 5) { string avoidStr = llList2String(parts, 4); @@ -359,8 +337,6 @@ default { } } } - // Debug: Show the game state before calling doBotPick - if (VERBOSE_LOGGING) llOwnerSay("[Bot Manager] ๐Ÿ”ง Game state - names:" + llList2CSV(names) + " lives:" + llList2CSV(lives)); doBotPick(botName, count, diceMax, avoidNumbers); } } diff --git a/CHANGELOG.md b/CHANGELOG.md index ab3e8f1..344a680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to Peril Dice will be documented in this file. +## [3.1.0] - 2026-04-04 + +### ๐Ÿ” **Dynamic Link Discovery System** +- **Architecture Decentralization**: Eliminated all hardcoded link indices across 15+ scripts. Systems now perform an automated "Link Discovery" scan on startup and `on_rez`. +- **Name-Based Mapping**: Implemented a robust pattern-matching system (`Scoreboard:0:0`, `profile:R:C`) to identify and bind to links regardless of their link-order in the set. +- **Improved Resilience**: Resolved persistent "Previously declared within scope" errors and stabilized inter-script communication during region crossings and teleports. + +### ๐Ÿงฉ **Unified Constants & Centralized Logic** +- **Centralized `Peril_Constants.lsl`**: Shifted all system settings, message IDs, and macros to a single, shared constants file for 100% project consistency. +- **Pre-Processor Debugging**: Implemented a global `dbg()` macro system. Debug logging can now be toggled board-wide with a single switch, significantly reducing production bytecode size. + +### ๐ŸŽญ **FURWARE Text Mesh Migration** +- **Visual Display Overhaul**: Fully migrated the Dice Display and Leaderboard from legacy prim-based XYText to modern mesh-integrated **FURWARE Text**. +- **Sync Reliability**: New bridge scripts handle real-time data distribution to the FURWARE master, ensuring 0ms delay in visual updates. +- **Performance Optimization**: Reduced prim texture-flipping overhead by switching to mesh-offset mapping. + +### ๐Ÿง  **Compiler & Execution Stability** +- **Scope Refactoring**: Moved all loop variable declarations (`refreshI`, `glowI`, etc.) to the top-level of their respective functions to ensure compatibility with strict LSL compiler versions. +- **Logic Robustness**: Replaced `continue` and range-check based validation with explicit "link-found" `if` blocks, preventing infinite loops and scope errors. +- **Mismatched Variable Fixes**: Resolved inconsistent renaming of local variables in the Scoreboard Manager's refresh logic. + ## [3.0.0] - 2026-03-24 ### ๐Ÿ“ฆ **Commercial CasperVend Integration** @@ -16,6 +37,10 @@ All notable changes to Peril Dice will be documented in this file. - **Bytecode Reduction**: Converted over 170 verbose `llOwnerSay` debug statements to a conditionally compiled `dbg()` macro, reducing compiled bytecode size. - **10-Player Load Stability**: Freed critical script memory limits within `Main_Controller_Linkset.lsl`, `Game_Manager.lsl`, and `Player_DialogHandler.lsl` to ensure rock-solid stability during maximum load. +### ๐Ÿ† **Experience-Based Leaderboard** +- **Persistent Storage**: Leaderboard data is now stored in Second Life Experience Keys (KVP), ensuring lifetime persistence of player statistics. +- **Region Independence**: Player wins and losses are tracked globally, surviving object resets and region changes. + ### ๐ŸŽจ **Z-Fighting & Alignment Fixes** - **Initialization Stability**: Patched `Game_Scoreboard_Manager_Linkset.lsl`, `Leaderboard_Communication_Linkset.lsl`, and `XyzzyText_Dice_Bridge_Linkset.lsl` with `on_rez` auto-resets to guarantee synchronized texture deployment. - **Microscopic Position Offsets**: Resolved persistent Z-fighting flickering on custom UI elements by applying meticulous microscopic local axis offsets (`+0.005m`, `+0.008m`). diff --git a/CHANGELOG_Notecard.txt b/CHANGELOG_Notecard.txt index 04ba832..d41dd83 100644 --- a/CHANGELOG_Notecard.txt +++ b/CHANGELOG_Notecard.txt @@ -3,6 +3,28 @@ PERIL DICE CHANGELOG All notable changes to Peril Dice are documented in this file. +[3.1.0] - 2026-04-04 +==================== + +๐Ÿ” DYNAMIC LINK DISCOVERY SYSTEM +- Architecture Decentralization: Eliminated all hardcoded link indices across 15+ scripts. Systems now perform an automated "Link Discovery" scan on startup and on_rez. +- Name-Based Mapping: Implemented a robust pattern-matching system (Scoreboard:0:0, profile:R:C) to identify and bind to links regardless of their link-order in the set. +- Improved Resilience: Resolved persistent "Previously declared within scope" errors and stabilized inter-script communication during region crossings and teleports. + +๐Ÿงฉ UNIFIED CONSTANTS & CENTRALIZED LOGIC +- Centralized Peril_Constants.lsl: Shifted all system settings, message IDs, and macros to a single, shared constants file for 100% project consistency. +- Pre-Processor Debugging: Implemented a global dbg() macro system. Debug logging can now be toggled board-wide with a single switch, significantly reducing production bytecode size. + +๐ŸŽญ FURWARE TEXT MESH MIGRATION +- Visual Display Overhaul: Fully migrated the Dice Display and Leaderboard from legacy prim-based XYText to modern mesh-integrated FURWARE Text. +- Sync Reliability: New bridge scripts handle real-time data distribution to the FURWARE master, ensuring 0ms delay in visual updates. +- Performance Optimization: Reduced prim texture-flipping overhead by switching to mesh-offset mapping. + +๐Ÿง  COMPILER & EXECUTION STABILITY +- Scope Refactoring: Moved all loop variable declarations (refreshI, glowI, etc.) to the top-level of their respective functions to ensure compatibility with strict LSL compiler versions. +- Logic Robustness: Replaced continue and range-check based validation with explicit "link-found" if blocks, preventing infinite loops and scope errors. +- Mismatched Variable Fixes: Resolved inconsistent renaming of local variables in the Scoreboard Manager's refresh logic. + [3.0.0] - 2026-03-24 ==================== @@ -16,6 +38,10 @@ All notable changes to Peril Dice are documented in this file. - Bytecode Reduction: Converted over 170 verbose llOwnerSay debug statements to a macro, reducing compiled bytecode size. - 10-Player Load Stability: Freed critical script memory within Main Controller, Game Manager, and Dialog Handler to ensure rock-solid stability during maximum load. +๐Ÿ† EXPERIENCE-BASED LEADERBOARD +- Persistent Storage: Leaderboard data is now stored in Second Life Experience Keys (KVP), ensuring lifetime persistence of player statistics. +- Region Independence: Player wins and losses are tracked globally, surviving object resets and region changes. + ๐ŸŽจ Z-FIGHTING & ALIGNMENT FIXES - Initialization Stability: Patched Scoreboard Manager, Leaderboard Communication, and Dice Bridge with on_rez auto-resets to guarantee synchronized texture deployment. - Microscopic Position Offsets: Resolved persistent Z-fighting flickering on custom UI elements by applying meticulous microscopic local axis offsets (+0.005m). diff --git a/Controller_Memory.lsl b/Controller_Memory.lsl index aa4a9e5..59a999c 100644 --- a/Controller_Memory.lsl +++ b/Controller_Memory.lsl @@ -1,25 +1,15 @@ +#include "Peril_Constants.lsl" + // === Controller Memory Monitor === // 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 -#define MSG_TOGGLE_VERBOSE_LOGS 9998 // Message to toggle verbose logging - // Memory monitoring system #define MEMORY_WARNING_THRESHOLD 0.8 // Warn at 80% usage #define MEMORY_CRITICAL_THRESHOLD 0.9 // Critical at 90% usage integer lastMemoryCheck = 0; // Track last memory check time #define MEMORY_CHECK_INTERVAL 60 // Check every 60 seconds -// Message constants for inter-script communication -#define MSG_MEMORY_CHECK 6001 -#define MSG_MEMORY_STATS 6002 -#define MSG_MEMORY_CLEANUP 6003 -#define MSG_MEMORY_REPORT 6004 -#define MSG_EMERGENCY_CLEANUP 6005 -#define MSG_MEMORY_STATS_REQUEST 6006 - // Track main controller stats for comprehensive reporting list mainControllerStats = []; @@ -29,7 +19,7 @@ checkMemoryUsage(string context) { float memoryPercentage = (float)usedMemory / 65536.0; // 64KB = 65536 bytes if (memoryPercentage > MEMORY_CRITICAL_THRESHOLD) { - llOwnerSay("๐ŸŽ† [Memory Monitor] CRITICAL MEMORY: " + context + " - " + + dbg("๐Ÿ“Š [Memory Monitor] ๐ŸŽ† CRITICAL MEMORY: " + context + " - " + (string)usedMemory + " bytes (" + (string)llRound(memoryPercentage * 100.0) + "% of 64KB limit)"); // Trigger emergency cleanup in main controller @@ -37,11 +27,9 @@ checkMemoryUsage(string context) { } else if (memoryPercentage > MEMORY_WARNING_THRESHOLD) { // Only warn once per interval to avoid spam if ((currentTime - lastMemoryCheck) >= MEMORY_CHECK_INTERVAL) { - if (VERBOSE_LOGGING) { - llOwnerSay("โš ๏ธ [Memory Monitor] High memory in " + context + ": " + - (string)usedMemory + " bytes (" + - (string)llRound(memoryPercentage * 100.0) + "% of 64KB limit)"); - } + dbg("๐Ÿ“Š [Memory Monitor] โš ๏ธ High memory in " + context + ": " + + (string)usedMemory + " bytes (" + + (string)llRound(memoryPercentage * 100.0) + "% of 64KB limit)"); lastMemoryCheck = currentTime; } } @@ -53,7 +41,7 @@ checkMemoryUsage(string context) { // Emergency memory cleanup function emergencyMemoryCleanup() { - llOwnerSay("๐ŸŽ† [Memory Monitor] Emergency memory cleanup initiated!"); + dbg("๐Ÿ“Š [Memory Monitor] ๐ŸŽ† Emergency memory cleanup initiated!"); // Clean up temporary variables and optimize lists mainControllerStats = []; @@ -63,7 +51,7 @@ emergencyMemoryCleanup() { mainControllerStats = []; mainControllerStats = tempStats; - llOwnerSay("๐ŸŽ† [Memory Monitor] Emergency cleanup complete - memory: " + + dbg("๐Ÿ“Š [Memory Monitor] ๐ŸŽ† Emergency cleanup complete - memory: " + (string)llGetUsedMemory() + " bytes"); } @@ -71,8 +59,8 @@ reportMemoryStats() { integer statsUsedMemory = llGetUsedMemory(); float statsMemoryPercentage = (float)statsUsedMemory / 65536.0; - llOwnerSay("๐Ÿ“Š [Memory Monitor] Memory Monitor Stats:"); - llOwnerSay(" Helper Memory Used: " + (string)statsUsedMemory + " bytes (" + + dbg("๐Ÿ“Š [Memory Monitor] Memory Monitor Stats:"); + dbg("๐Ÿ“Š [Memory Monitor] Helper Memory Used: " + (string)statsUsedMemory + " bytes (" + (string)llRound(statsMemoryPercentage * 100.0) + "% of 64KB)"); string memoryStatus; if (statsMemoryPercentage > MEMORY_CRITICAL_THRESHOLD) { @@ -82,7 +70,7 @@ reportMemoryStats() { } else { memoryStatus = "NORMAL"; } - llOwnerSay(" Memory Status: " + memoryStatus); + dbg("๐Ÿ“Š [Memory Monitor] Memory Status: " + memoryStatus); // Request main controller stats llMessageLinked(LINK_SET, MSG_MEMORY_STATS_REQUEST, "REQUEST_MAIN_STATS", NULL_KEY); @@ -107,25 +95,25 @@ processMainControllerStats(string statsData) { float mainMemoryPercentage = (float)mainMemory / 65536.0; - llOwnerSay("๐Ÿ“Š [Memory Monitor] Main Controller Stats:"); - llOwnerSay(" Main Memory Used: " + (string)mainMemory + " bytes (" + + dbg("๐Ÿ“Š [Memory Monitor] Main Controller Stats:"); + dbg("๐Ÿ“Š [Memory Monitor] Main Memory Used: " + (string)mainMemory + " bytes (" + (string)llRound(mainMemoryPercentage * 100.0) + "% of 64KB)"); - llOwnerSay(" Players: " + (string)players); - llOwnerSay(" Names: " + (string)names); - llOwnerSay(" Lives: " + (string)lives); - llOwnerSay(" Picks Data: " + (string)picksData); - llOwnerSay(" Ready Players: " + (string)readyPlayers); - llOwnerSay(" Pick Queue: " + (string)pickQueue); - llOwnerSay(" Global Picked: " + (string)globalPicked); + dbg("๐Ÿ“Š [Memory Monitor] Players: " + (string)players); + dbg("๐Ÿ“Š [Memory Monitor] Names: " + (string)names); + dbg("๐Ÿ“Š [Memory Monitor] Lives: " + (string)lives); + dbg("๐Ÿ“Š [Memory Monitor] Picks Data: " + (string)picksData); + dbg("๐Ÿ“Š [Memory Monitor] Ready Players: " + (string)readyPlayers); + dbg("๐Ÿ“Š [Memory Monitor] Pick Queue: " + (string)pickQueue); + dbg("๐Ÿ“Š [Memory Monitor] Global Picked: " + (string)globalPicked); if (floaterChannels > 0) { - llOwnerSay(" Floater Channels: " + (string)floaterChannels); + dbg("๐Ÿ“Š [Memory Monitor] Floater Channels: " + (string)floaterChannels); } } } default { state_entry() { - llOwnerSay("๐Ÿ“Š [Memory] Memory Monitor ready!"); + dbg("๐Ÿ“Š [Memory Monitor] Memory Monitor ready!"); // Start periodic memory monitoring llSetTimerEvent(MEMORY_CHECK_INTERVAL); @@ -137,18 +125,8 @@ 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); } @@ -169,11 +147,11 @@ default { // Perform emergency cleanup on this helper script emergencyMemoryCleanup(); } - else if (num == -99999 && str == "FULL_RESET") { + else if (num == MSG_RESET_ALL && str == "FULL_RESET") { // Handle full game reset lastMemoryCheck = 0; mainControllerStats = []; - llOwnerSay("๐Ÿ”„ [Memory Monitor] Reset complete"); + dbg("๐Ÿ“Š [Memory Monitor] ๐Ÿ”„ Reset complete"); } } } diff --git a/Controller_MessageHandler.lsl b/Controller_MessageHandler.lsl index 492371e..e6ae4a7 100644 --- a/Controller_MessageHandler.lsl +++ b/Controller_MessageHandler.lsl @@ -1,36 +1,9 @@ +#include "Peril_Constants.lsl" + // === Controller Message Handler Helper Script === // 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 -#define MSG_TOGGLE_VERBOSE_LOGS 9998 // Message to toggle verbose logging - -// Memory reporting function -reportMemoryUsage(string scriptName) { - integer used = llGetUsedMemory(); - integer free = llGetFreeMemory(); - integer total = used + free; - float percentUsed = ((float)used / (float)total) * 100.0; - - llOwnerSay("๐Ÿง  [" + scriptName + "] Memory: " + - (string)used + " used, " + - (string)free + " free (" + - llGetSubString((string)percentUsed, 0, 4) + "% used)"); -} - -// Message constants for communication with main controller -#define MSG_REQUEST_PLAYER_DATA 8001 -#define MSG_REQUEST_PICK_DATA 8002 -#define MSG_REQUEST_LIFE_DATA 8003 -#define MSG_PICK_ACTION 8004 -#define MSG_DICE_TYPE_REQUEST 8005 -#define MSG_LEAVE_GAME_REQUEST 8006 -#define MSG_OWNER_MESSAGE 9030 // Send formatted owner message -#define MSG_PUBLIC_MESSAGE 9031 // Send formatted public message -#define MSG_REGION_MESSAGE 9032 // Send formatted region message -#define MSG_DIALOG_REQUEST 9033 // Send formatted dialog - // Game state data synchronized from Main Controller list players = []; list names = []; @@ -42,15 +15,12 @@ string perilPlayer = ""; // Track who the peril player is to determine if game integer roundStarted = FALSE; // Track if the game round has started // Message constants from Main Controller (synchronized) -integer MSG_SYNC_GAME_STATE = 107; #define MSG_SYNC_PICKQUEUE 2001 // Channel constants for scoreboard updates integer SCOREBOARD_CHANNEL_1; integer SCOREBOARD_CHANNEL_2; integer FLOATER_BASE_CHANNEL; -#define MSG_CLEANUP_FLOAT 104 -#define MSG_UPDATE_FLOAT 103 // Dynamic channel calculation (matches Main Controller) #define CHANNEL_BASE -77000 @@ -70,6 +40,19 @@ integer calculateChannel(integer offset) { return CHANNEL_BASE - (offset * 1000) - combinedHash; } +// Memory reporting function +reportMemoryUsage(string scriptName) { + integer used = llGetUsedMemory(); + integer free = llGetFreeMemory(); + integer total = used + free; + float percentUsed = ((float)used / (float)total) * 100.0; + + dbg("๐Ÿง  [" + scriptName + "] Memory: " + + (string)used + " used, " + + (string)free + " free (" + + llGetSubString((string)percentUsed, 0, 4) + "% used)"); +} + initializeChannels() { SCOREBOARD_CHANNEL_1 = calculateChannel(6); // ~-77600 range SCOREBOARD_CHANNEL_2 = calculateChannel(7); // ~-77700 range @@ -79,7 +62,7 @@ initializeChannels() { // Handle player list requests handlePlayerListRequest(key requester) { string namesCSV = llList2CSV(names); - llMessageLinked(LINK_SET, 203, namesCSV, requester); + llMessageLinked(LINK_SET, MSG_PLAYER_LIST_PICK_RESULT, namesCSV, requester); } // Handle pick data requests @@ -89,11 +72,11 @@ handlePickDataRequest(string targetName, key requester) { string rawEntry = llList2String(picksData, j); list pickParts = llParseString2List(rawEntry, ["|"], []); if (llList2String(pickParts, 0) == targetName) { - llMessageLinked(LINK_SET, 205, targetName + "|" + llList2String(pickParts, 1), requester); + llMessageLinked(LINK_SET, MSG_PICK_LIST_RESULT, targetName + "|" + llList2String(pickParts, 1), requester); return; } } - llMessageLinked(LINK_SET, 205, targetName + "|", requester); + llMessageLinked(LINK_SET, MSG_PICK_LIST_RESULT, targetName + "|", requester); } // Handle life data requests @@ -101,7 +84,7 @@ handleLifeDataRequest(string playerName, key requester) { integer lifeIdx = llListFindList(names, [playerName]); if (lifeIdx != -1) { string lifeVal = llList2String(lives, lifeIdx); - llMessageLinked(LINK_SET, 208, playerName + "|" + lifeVal, requester); + llMessageLinked(LINK_SET, MSG_LIFE_LOOKUP_RESULT, playerName + "|" + lifeVal, requester); } } @@ -127,17 +110,11 @@ handlePickAction(string actionData) { if (pickAction == "ADD_PICK") { if (llListFindList(pickList, [pick]) == -1) { pickList += [pick]; - if (VERBOSE_LOGGING) { - llOwnerSay("โž• [Message Handler] Added " + pick + " to " + name); - } } } else if (pickAction == "REMOVE_PICK") { integer removeIdx = llListFindList(pickList, [pick]); if (removeIdx != -1) { pickList = llDeleteSubList(pickList, removeIdx, removeIdx); - if (VERBOSE_LOGGING) { - llOwnerSay("โž– [Message Handler] Removed " + pick + " from " + name); - } } } @@ -145,7 +122,7 @@ handlePickAction(string actionData) { picksData = llListReplaceList(picksData, [name + "|" + llList2CSV(pickList)], k, k); // Notify Main Controller of the change - llMessageLinked(LINK_SET, 204, actionData, NULL_KEY); + llMessageLinked(LINK_SET, MSG_PICK_ACTION, actionData, NULL_KEY); return; } } @@ -153,10 +130,7 @@ handlePickAction(string actionData) { // Handle dice type requests handleDiceTypeRequest(key requester) { - if (VERBOSE_LOGGING) { - llOwnerSay("๐ŸŽฒ [Message Handler] Sending dice type: " + (string)diceType); - } - llMessageLinked(LINK_SET, 102, (string)diceType, requester); // MSG_ROLL_RESULT + llMessageLinked(LINK_SET, MSG_DICE_TYPE_RESULT, (string)diceType, requester); } // Handle leave game and kick player requests @@ -175,13 +149,6 @@ handleLeaveGameRequest(string requestData) { lives = llDeleteSubList(lives, leaveIdx, leaveIdx); picksData = llDeleteSubList(picksData, leaveIdx, leaveIdx); - // 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) { @@ -190,10 +157,8 @@ handleLeaveGameRequest(string requestData) { // Different messages for voluntary leave vs kick if (leaveAction == "LEAVE_GAME") { - llOwnerSay("๐Ÿ‘‹ [Message Handler] " + leavingName + " left the game"); llSay(0, "๐Ÿ‘‹ " + leavingName + " has left the deadly game! ๐Ÿ‘‹"); } else if (leaveAction == "KICK_PLAYER") { - llOwnerSay("๐Ÿ‘ข [Message Handler] " + leavingName + " was kicked from the game"); llSay(0, "๐Ÿ‘ข " + leavingName + " has been kicked from the deadly game by the owner! ๐Ÿ‘ข"); } @@ -202,22 +167,16 @@ handleLeaveGameRequest(string requestData) { // Check if game should end (0 players = reset, 1 player = victory) if (llGetListLength(names) == 0) { - 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) string winner = llList2String(names, 0); llSay(0, "โœจ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!"); // Notify Main Controller of victory - llMessageLinked(LINK_SET, 998, "GAME_WON|" + winner, NULL_KEY); + llMessageLinked(LINK_SET, MSG_CONTINUE_GAME, "GAME_WON|" + winner, NULL_KEY); } else if (llGetListLength(names) == 1) { // Game not started yet, just inform but don't declare victory string remainingPlayer = llList2String(names, 0); - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“ [Message Handler] Only " + remainingPlayer + " remains, but game hasn't started yet"); - } } } } @@ -229,9 +188,6 @@ syncGameState(string stateData) { // Handle special RESET sync message if (llGetListLength(syncParts) >= 5 && llList2String(syncParts, 0) == "RESET") { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ”„ [Message Handler] Received reset sync - clearing all state"); - } // Clear all state during reset players = []; names = []; @@ -283,10 +239,6 @@ syncGameState(string stateData) { 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; } @@ -302,27 +254,21 @@ handleOwnerMessage(string messageData) { list ownerParts = llParseString2List(messageData, ["|"], []); if (llGetListLength(ownerParts) == 1) { // Simple message - llOwnerSay(messageData); } else { // Formatted message string ownerMsgType = llList2String(ownerParts, 0); if (ownerMsgType == "JOIN") { string ownerPlayerName = llList2String(ownerParts, 1); - llOwnerSay("๐Ÿ‘‹ " + ownerPlayerName + " joined the game!"); llSay(0, "๐Ÿ”” Added player: " + ownerPlayerName); } else if (ownerMsgType == "LEAVE") { string leavePlayerName = llList2String(ownerParts, 1); - llOwnerSay("๐Ÿ‘‹ " + leavePlayerName + " left the game."); } else if (ownerMsgType == "ERROR") { string error = llList2String(ownerParts, 1); - llOwnerSay("โš ๏ธ " + error); } else if (ownerMsgType == "SUCCESS") { string msg = llList2String(ownerParts, 1); - llOwnerSay("โœ… " + msg); } else if (ownerMsgType == "DEBUG") { string component = llList2String(ownerParts, 1); string msg = llList2String(ownerParts, 2); - llOwnerSay("๐Ÿ”ง [" + component + "] " + msg); } } } @@ -382,61 +328,59 @@ default { state_entry() { reportMemoryUsage("๐Ÿ“จ Message Handler"); - llOwnerSay("๐Ÿ“จ [Message Handler] Helper script ready!"); initializeChannels(); } + on_rez(integer start_param) { + reportMemoryUsage("๐Ÿ“จ Message Handler"); + initializeChannels(); + // Reset local state if needed (synced from Main) + players = []; + names = []; + lives = []; + picksData = []; + readyPlayers = []; + } + 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") { + if (num == MSG_REQUEST_PLAYER_LIST_PICK && str == "REQUEST_PLAYER_LIST") { + handlePlayerListRequest(id); return; } // Handle kick player list requests (separate message ID to avoid conflicts) - if (num == 8009 && str == "REQUEST_PLAYER_LIST_KICK") { + if (num == MSG_REQUEST_PLAYER_LIST_KICK && str == "REQUEST_PLAYER_LIST_KICK") { // Send back kick-specific message ID to avoid conflict with pick management string namesCSV = llList2CSV(names); - 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); + dbg("๐Ÿง  [Controller Message Handler] ๐Ÿ” Received request: '" + str + "'"); + dbg("๐Ÿง  [Controller Message Handler] ๐Ÿ” Names list: " + llDumpList2String(names, ", ")); + dbg("๐Ÿง  [Controller Message Handler] ๐Ÿ” Sending kick player list: '" + namesCSV + "'"); + llMessageLinked(LINK_SET, MSG_REQUEST_PLAYER_LIST_KICK, namesCSV, id); return; } // Handle pick data requests - if (num == 206) { + if (num == MSG_OWNER_PICK_MANAGER) { handlePickDataRequest(str, id); return; } // Handle life data requests - if (num == 208) { + if (num == MSG_LIFE_LOOKUP_REQUEST) { handleLifeDataRequest(str, id); return; } // Handle pick actions - if (num == 204) { + if (num == MSG_PICK_ACTION) { handlePickAction(str); return; } // Handle dice type requests - if (num == 996) { + if (num == MSG_GET_DICE_TYPE) { if (str == "GET_DICE_TYPE") { handleDiceTypeRequest(id); } @@ -444,7 +388,7 @@ default { } // Handle leave game requests (use proper message ID, not 107 which is for sync) - if (num == 8007) { // Different from MSG_SYNC_GAME_STATE = 107 + if (num == MSG_LEAVE_GAME) { // Different from MSG_SYNC_GAME_STATE = 107 handleLeaveGameRequest(str); return; } @@ -483,7 +427,7 @@ default { } // Handle full reset - if (num == -99999 && str == "FULL_RESET") { + if (num == MSG_RESET_ALL && str == "FULL_RESET") { players = []; names = []; lives = []; @@ -492,7 +436,7 @@ default { diceType = 6; perilPlayer = ""; roundStarted = FALSE; - llOwnerSay("๐Ÿ“จ [Message Handler] Reset complete"); + dbg("๐Ÿง  [Controller Message Handler] Reset complete"); } } } diff --git a/Enhanced_LinkScanner.lsl b/Enhanced_LinkScanner.lsl index 6019fe1..4a70203 100644 --- a/Enhanced_LinkScanner.lsl +++ b/Enhanced_LinkScanner.lsl @@ -1,11 +1,10 @@ // ==================================================================== -// Enhanced Link Scanner for Peril Dice Update System +// Enhanced Link Scanner for Peril Dice Linkset // ==================================================================== // Extended version of Temp_LinkScanner.lsl that identifies: // 1. Current linkset structure // 2. Which scripts belong on which links // 3. Missing scripts or misplaced scripts -// 4. Generates accurate placement info for Update_Checker.lsl // // USAGE: Place this script on any prim in the linkset, touch to scan // ==================================================================== @@ -83,7 +82,6 @@ performFullScan() { string scriptName = llList2String(LINK1_SCRIPTS, j); llOwnerSay(" โ€ข " + scriptName); } - llOwnerSay(" + Update_Checker.lsl (NEW - replaces eliminated UpdateHelper)"); llOwnerSay(""); // Analyze scoreboard @@ -119,15 +117,7 @@ performFullScan() { } llOwnerSay(""); - // Update checker guide - llOwnerSay("=== ๐ŸŽฏ UPDATE CHECKER PLACEMENT GUIDE ==="); - llOwnerSay("๐Ÿ“ฅ UPDATE CHECKER DEPLOYMENT:"); - llOwnerSay(" โœ… Place Update_Checker.lsl on Link 1 (Root Prim)"); - llOwnerSay(" โœ… Replaces eliminated UpdateHelper.lsl from v2.8.5"); - llOwnerSay(" โœ… Integrates with existing admin menu system"); - llOwnerSay(""); - - llOwnerSay("๐Ÿ”„ ESSENTIAL FILES FOR UPDATES:"); + llOwnerSay("๐Ÿ”„ ESSENTIAL FILES FOR GAMEPLAY:"); llOwnerSay(" ๐Ÿ“‚ Link 1 Scripts (" + (string)llGetListLength(LINK1_SCRIPTS) + " files)"); llOwnerSay(" ๐Ÿ“‚ Link-Specific Scripts (3 files)"); llOwnerSay(" ๐Ÿ“‚ XyzzyText Script (1 file ร— 48 prims)"); @@ -147,7 +137,6 @@ performFullScan() { default { state_entry() { llOwnerSay("๐Ÿ” Enhanced Link Scanner ready for Peril Dice linkset"); - llOwnerSay("๐Ÿ“Š This will map script locations for the Update Checker"); llOwnerSay("๐Ÿ‘† Touch to perform full linkset analysis"); } diff --git a/FURWARE_Leaderboard_Communication_Linkset.lsl b/FURWARE_Leaderboard_Communication_Linkset.lsl new file mode 100644 index 0000000..ad250c3 --- /dev/null +++ b/FURWARE_Leaderboard_Communication_Linkset.lsl @@ -0,0 +1,49 @@ +#include "Peril_Constants.lsl" + +string leftText = ""; + +// Distribute text to the FURWARE "Leaderboard" box +distributeFullText(string text) { + // Send directly to FURWARE. It handles multiple prims and newlines automatically. + llMessageLinked(LINK_SET, 0, text, (key)(FW_DATA + ":Leaderboard")); +} + +default { + on_rez(integer start_param) { + DISCOVER_CORE_LINKS(); + llResetScript(); + } + + state_entry() { + DISCOVER_CORE_LINKS(); + dbg("๐Ÿ“‹ [Leaderboard] Leaderboard Communication Script ready! (FURWARE Version)"); + dbg("๐Ÿ“‹ [Leaderboard] Managing 'Leaderboard' FURWARE box"); + dbg("๐Ÿ“‹ [Leaderboard] โœ… Discovery complete! Scoreboard: " + (string)LINK_SCOREBOARD); + + // Start with blank display + distributeFullText(""); + } + + link_message(integer sender, integer num, string str, key id) { + // Only listen to messages from the scoreboard + if (sender != LINK_SCOREBOARD) { + return; + } + + if (num == MSG_RESET_LEADERBOARD) { + // FURWARE handles the entire leaderboard as a single text block + if (llGetSubString(str, 0, 14) == "FORMATTED_TEXT|") { + distributeFullText(llGetSubString(str, 15, -1)); + } + // For individual bank updates + else if (llGetSubString(str, 0, 9) == "LEFT_TEXT|") { + leftText = llGetSubString(str, 10, -1); + distributeFullText(leftText); + } + } + else if (llSubStringIndex(str, "LEADERBOARD") == 0) { + dbg("๐Ÿ“‹ [Leaderboard] โš ๏ธ Received legacy LEADERBOARD format - ignoring"); + } + } + +} diff --git a/FURWARE_Text_Dice_Bridge_Linkset.lsl b/FURWARE_Text_Dice_Bridge_Linkset.lsl new file mode 100644 index 0000000..9d6a4f3 --- /dev/null +++ b/FURWARE_Text_Dice_Bridge_Linkset.lsl @@ -0,0 +1,66 @@ +#include "Peril_Constants.lsl" + +// ============================================================================= +// LINKSET COMMUNICATION - NO DISCOVERY NEEDED +// ============================================================================= + +// Function to display dice text across the "Dice" box +displayDiceText(string text) { + // Send to FURWARE Text engine + llMessageLinked(LINK_SET, 0, text, (key)(FW_DATA + ":Dice")); + + dbg("๐ŸŽฒ [Dice Bridge] Dice display updated: '" + text + "'"); +} + +default { + on_rez(integer start_param) { + DISCOVER_CORE_LINKS(); + llResetScript(); + } + state_entry() { + DISCOVER_CORE_LINKS(); + dbg("๐ŸŽฒ [Dice Bridge] Dice Display Bridge ready! (FURWARE Version)"); + dbg("๐ŸŽฒ [Dice Bridge] โœ… Discovery complete! Controller: " + (string)LINK_CONTROLLER); + + // Initialize dice display with blank text + displayDiceText(""); + } + + link_message(integer sender, integer num, string str, key id) { + // Only listen to messages from the main controller or scoreboard + if (sender != LINK_CONTROLLER && sender != LINK_SCOREBOARD) return; + + if (num == MSG_DICE_ROLL) { + // Handle different message formats + list parts = llParseString2List(str, ["|"], []); + + if (llGetListLength(parts) >= 3) { + // Format: "player|result|dicetype" - single die roll + string player = llList2String(parts, 0); + string result = llList2String(parts, 1); + string diceType = llList2String(parts, 2); + + // Create dice display: "Player: rolled 4" + // FURWARE handles alignment and wrapping, so we don't need strict padding + string displayText = player + ": rolled " + result; + displayDiceText(displayText); + } + else { + // Treat as direct display text + displayDiceText(str); + } + } + else if (num == MSG_DICE_CLEAR) { + displayDiceText(""); + dbg("๐ŸŽฒ [Dice Bridge] Dice display cleared"); + } + } + + + timer() { + llSetTimerEvent(0.0); + displayDiceText(""); + dbg("๐ŸŽฒ [Dice Bridge] Test display cleared"); + } +} + diff --git a/Floater_Manager.lsl b/Floater_Manager.lsl index 7890c10..dbc691f 100644 --- a/Floater_Manager.lsl +++ b/Floater_Manager.lsl @@ -1,24 +1,13 @@ +#include "Peril_Constants.lsl" + // === FloatManager (Consolidated) === // This version enforces a maximum of 10 players. -#define MSG_SHOW_DIALOG 101 -#define MSG_ROLL_RESULT 102 -#define MSG_UPDATE_FLOAT 103 -#define MSG_CLEANUP_FLOAT 104 -#define MSG_REZ_FLOAT 105 -#define MSG_REGISTER_PLAYER 106 -#define MSG_SYNC_GAME_STATE 107 -#define MSG_CLEANUP_ALL_FLOATERS 212 - -// Debug control message constants -#define MSG_DEBUG_PICKS_ON 7001 -#define MSG_DEBUG_PICKS_OFF 7002 - // ============================================================================= // DYNAMIC CHANNEL CONFIGURATION FOR FLOATERS // ============================================================================= -// Base channel offset - should match Main.lsl +// Base channel offset must match Main.lsl #define CHANNEL_BASE -77000 // Calculate channels dynamically to avoid hardcoded conflicts @@ -52,9 +41,6 @@ initializeChannels() { // 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; - // Memory reporting function reportMemoryUsage(string scriptName) { integer used = llGetUsedMemory(); @@ -62,142 +48,45 @@ reportMemoryUsage(string scriptName) { integer total = used + free; float percentUsed = ((float)used / (float)total) * 100.0; - llOwnerSay("๐Ÿง  [" + scriptName + "] Memory: " + + dbg("๐Ÿง  [" + scriptName + "] Memory: " + (string)used + " used, " + (string)free + " free (" + llGetSubString((string)percentUsed, 0, 4) + "% used)"); } -// ALIVE PLAYERS (shrinks during eliminations) -list alivePlayers = []; -list aliveNames = []; -list aliveLives = []; -list alivePicksData = []; - -// ELIMINATED PLAYERS (grows during eliminations) -list eliminatedPlayers = []; -list eliminatedNames = []; -list eliminatedChannels = []; - -// ALL PLAYERS (never shrinks - for channel mapping) +// ALL REGISTERED PLAYERS (for channel mapping - order never changes) list allPlayerNames = []; // Names in registration order -list allPlayerChannels = []; // Corresponding channels -// SYNC STATE VARIABLES (received from Main Controller) -list names = []; // Current player names from sync -list players = []; // Current player keys from sync -list lives = []; // Current lives from sync +// CURRENT GAME STATE (Synced from Main Controller) +list names = []; // Current player names in game +list players = []; // Current player keys in game +list lives = []; // Current health from sync list picksData = []; // Current picks data from sync -list playerNames = []; // Legacy variable for cleanup compatibility -list playerChannels = []; // Legacy variable for cleanup compatibility string perilPlayer = ""; -// Move a player from alive to eliminated lists -eliminatePlayer(string playerName) { - integer aliveIdx = llListFindList(aliveNames, [playerName]); - if (aliveIdx == -1) { - llOwnerSay("โš ๏ธ [Floater Manager] Cannot eliminate " + playerName + " - not found in alive list"); - return; - } - - // Get player data before removing from alive lists - key playerKey = llList2Key(alivePlayers, aliveIdx); - string picks = llList2String(alivePicksData, aliveIdx); - - // Find their original channel from permanent mapping - integer channelIdx = llListFindList(allPlayerNames, [playerName]); - integer playerChannel = -1; - if (channelIdx != -1) { - playerChannel = llList2Integer(allPlayerChannels, channelIdx); - } - - // Add to eliminated lists - eliminatedNames += [playerName]; - eliminatedPlayers += [playerKey]; - eliminatedChannels += [playerChannel]; - - // Remove from alive lists - alivePlayers = llDeleteSubList(alivePlayers, aliveIdx, aliveIdx); - aliveNames = llDeleteSubList(aliveNames, aliveIdx, aliveIdx); - aliveLives = llDeleteSubList(aliveLives, aliveIdx, aliveIdx); - alivePicksData = llDeleteSubList(alivePicksData, aliveIdx, aliveIdx); - - llOwnerSay("๐Ÿงน [Floater Manager] Moved " + playerName + " from alive to eliminated lists"); -} - -// Returns a list of picks for the given player name, filtering out invalid values +// Helper to get picks for a player, handles both comma (human) and semicolon (bot) 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); - } else { - list parts = llParseString2List(entry, ["|"], []); - // Handle entries ending with "|" (empty picks) - LSL drops trailing empty elements - if (llGetListLength(parts) == 1 && llGetSubString(entry, -1, -1) == "|") { - parts += [""]; // Add the empty picks part back - } - if (llGetListLength(parts) < 2) { - if (entry != "") llOwnerSay("โš ๏ธ Malformed picks entry (too few parts): " + entry); - } - 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 { - // 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; - } + integer pipeIdx = llSubStringIndex(entry, "|"); + if (pipeIdx != -1 && llGetSubString(entry, 0, pipeIdx - 1) == nameInput) { + string pickString = llGetSubString(entry, pipeIdx + 1, -1); + if (pickString == "") return []; + list pickList = llParseString2List(pickString, [",", ";"], []); // Parse both at once + list filtered = []; + integer j; + for (j = 0; j < llGetListLength(pickList); j++) { + string val = llStringTrim(llList2String(pickList, j), STRING_TRIM); + if (val != "") filtered += [val]; } + return filtered; } } - - if (DEBUG_PICKS) llOwnerSay("๐Ÿ” [Floater Manager] No picks found for " + nameInput + ", returning []"); return []; } -// Converts a player's key into their name (if registered) -string getNameFromKey(key id) { - integer idx = llListFindList(alivePlayers, [id]); - if (idx != -1) return llList2String(aliveNames, idx); - return (string)id; -} - // Main event handler default { state_entry() { @@ -206,498 +95,209 @@ default { initializeChannels(); // Initialize/reset all state variables - alivePlayers = []; - aliveNames = []; - aliveLives = []; - alivePicksData = []; - eliminatedPlayers = []; - eliminatedNames = []; - eliminatedChannels = []; allPlayerNames = []; - allPlayerChannels = []; - - // Initialize sync state variables names = []; players = []; lives = []; picksData = []; - playerNames = []; - playerChannels = []; perilPlayer = ""; - llOwnerSay("๐Ÿ“ฆ Floater Manager ready!"); + dbg("๐Ÿ’ฌ [Floater Manager] ๐Ÿ“ฆ Floater Manager ready!"); } on_rez(integer start_param) { reportMemoryUsage("๐Ÿ’ฌ Floater Manager"); + dbg("๐Ÿ’ฌ [Floater Manager] ๐Ÿ”„ Floater Manager rezzed - reinitializing..."); - llOwnerSay("๐Ÿ”„ Floater Manager rezzed - reinitializing..."); - - // Re-initialize dynamic channels initializeChannels(); // Reset all state variables on rez - alivePlayers = []; - aliveNames = []; - aliveLives = []; - alivePicksData = []; - eliminatedPlayers = []; - eliminatedNames = []; - eliminatedChannels = []; allPlayerNames = []; - allPlayerChannels = []; - - // Initialize sync state variables names = []; players = []; lives = []; picksData = []; - playerNames = []; - playerChannels = []; - perilPlayer = ""; - - llOwnerSay("โœ… Floater Manager reset complete after rez!"); + dbg("๐Ÿ’ฌ [Floater Manager] โœ… Floater Manager reset complete after rez!"); } - + link_message(integer sender, integer num, string str, key id) { // Handle full reset from main controller - if (num == -99999 && str == "FULL_RESET") { - // Reset all game state - alivePlayers = []; - aliveNames = []; - aliveLives = []; - alivePicksData = []; - eliminatedPlayers = []; - eliminatedNames = []; - eliminatedChannels = []; + if (num == MSG_RESET_ALL && str == "FULL_RESET") { allPlayerNames = []; - allPlayerChannels = []; - - // Reset sync state variables names = []; players = []; lives = []; picksData = []; - playerNames = []; - playerChannels = []; perilPlayer = ""; - llOwnerSay("๐Ÿ“ฆ Floater Manager reset!"); - 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"); - } - } + dbg("๐Ÿ’ฌ [Floater Manager] ๐Ÿ“ฆ Floater Manager reset!"); return; } if (num == MSG_REGISTER_PLAYER) { - // Enforce the maximum number of players - if (llGetListLength(alivePlayers) >= MAX_PLAYERS) { - llOwnerSay("โš ๏ธ Cannot register new player; the game is full (max " + (string)MAX_PLAYERS + ")."); + if (llGetListLength(allPlayerNames) >= MAX_PLAYERS) { + dbg("๐Ÿ’ฌ [Floater Manager] โš ๏ธ Game is full (max " + (string)MAX_PLAYERS + ")."); return; } - // Register a new player: store their name and avatar key 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(alivePlayers, [avKey]) != -1) { - llOwnerSay("โš ๏ธ Player " + name + " is already registered, ignoring duplicate registration"); - return; - } + if (llListFindList(allPlayerNames, [name]) != -1) return; - // Add to ALIVE lists - aliveNames += [name]; - alivePlayers += [avKey]; - aliveLives += [3]; // Initialize with 3 lives for new players - alivePicksData += [name + "|"]; // Initialize with empty picks - - // Add to PERMANENT mapping (never changes after registration) - integer playerIdx = llGetListLength(allPlayerNames); - integer ch = FLOATER_BASE_CHANNEL + playerIdx; - allPlayerNames += [name]; - allPlayerChannels += [ch]; - llSay(0, "๐Ÿ’€ " + name + " has entered the deadly game! Welcome to your potential doom! ๐Ÿ’€"); - - // Immediately rez the float after registration. This ensures the - // float manager has updated its internal lists before attempting - // to look up the player's index. Without this, other scripts - // sending MSG_REZ_FLOAT in parallel could cause a race condition - // where the float manager has not yet seen the new name, so it - // cannot resolve the index and fails to rez the float. By - // initiating the rezzing here, we guarantee the float is created - // only after the registration is complete. + allPlayerNames += [name]; // Permanent map + llSay(0, "๐Ÿ’€ " + name + " has entered the deadly game! ๐Ÿ’€"); llMessageLinked(LINK_SET, MSG_REZ_FLOAT, name, avKey); } else if (num == MSG_REZ_FLOAT) { string name = str; - integer idx = llListFindList(aliveNames, [name]); - if (idx == -1) return; - key avKey = llList2Key(alivePlayers, idx); - // Determine the rez position. For real avatars, use their current - // location plus an offset. For test players (fake keys) or when - // OBJECT_POS cannot be obtained, fall back to rezzing relative to - // the object's own position to ensure a float is created. + integer channelIdx = llListFindList(allPlayerNames, [name]); + if (channelIdx == -1) return; + + key avKey = id; // Passed from MSG_REGISTER_PLAYER or MSG_REZ_FLOAT + list details = llGetObjectDetails(avKey, [OBJECT_POS]); - vector basePos; - if (llGetListLength(details) > 0) { - basePos = llList2Vector(details, 0); - } else { - basePos = llGetPos(); - } + vector basePos = llGetPos(); + if (llGetListLength(details) > 0) basePos = llList2Vector(details, 0); + vector pos; if (llGetListLength(details) > 0) { - // Real avatars: rez next to the avatar with the standard offset - pos = basePos + <1, 0, 1>; + pos = basePos + <1, 0, 1>; // Avatar relative } else { - // Test players: arrange in a compact 2x5 grid to stay within parcel boundaries - // Grid layout: 2 rows of 5 floaters each, spaced 1.5m apart - integer row = idx / 5; // Row 0 or 1 - integer col = idx % 5; // Column 0-4 - float xOffset = -3.0 - (float)col * 1.5; // -3.0 to -9.0 - float yOffset = 2.0 + (float)row * 2.0; // 2.0 or 4.0 - pos = basePos + ; - } - // Use permanent channel mapping instead of alive list index - integer channelIdx = llListFindList(allPlayerNames, [name]); - integer ch; - if (channelIdx != -1) { - ch = llList2Integer(allPlayerChannels, channelIdx); - } else { - // Fallback - this shouldn't happen but use idx as backup - llOwnerSay("โš ๏ธ [Floater Manager] WARNING: " + name + " not in permanent mapping during rez, using fallback"); - ch = FLOATER_BASE_CHANNEL + idx; + // Test players: grid layout + integer row = channelIdx / 5; + integer col = channelIdx % 5; + pos = basePos + <-3.0 - (float)col * 1.5, 2.0 + (float)row * 2.0, 1>; } + + integer ch = FLOATER_BASE_CHANNEL + channelIdx; llRezObject("StatFloat", pos, ZERO_VECTOR, ZERO_ROTATION, ch); - // Wait a moment then set the description llSleep(0.2); - // Find the rezzed object and set its description - list nearby = llGetObjectDetails(llGetKey(), [OBJECT_POS]); - if (llGetListLength(nearby) > 0) { - // Use llRegionSay to tell the StatFloat its name - llRegionSay(ch, "SET_NAME:" + name); - } - // After rezzing, immediately update the float so it displays the - // correct lives, picks and peril status. Use the avatar key as - // the id to keep the float tied to the player. This ensures that - // test players also receive an update even if they donโ€™t have a - // valid avatar key. + llRegionSay(ch, "SET_NAME:" + name); llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, name, avKey); } else if (num == MSG_UPDATE_FLOAT) { string name = str; - - // FIXED: Use permanent channel mapping that doesn't change during eliminations integer channelIdx = llListFindList(allPlayerNames, [name]); - integer ch; - if (channelIdx != -1) { - ch = llList2Integer(allPlayerChannels, channelIdx); - } else { - // Fallback - this shouldn't happen if permanent mapping is working - llOwnerSay("โš ๏ธ [Floater Manager] WARNING: Player " + name + " not in permanent mapping"); - return; - } + if (channelIdx == -1) return; + integer ch = FLOATER_BASE_CHANNEL + channelIdx; - // Try to find player in current sync data (names/lives from Main Controller) - // This works for both alive and eliminated players during the elimination sync integer syncIdx = llListFindList(names, [name]); - key avKey = NULL_KEY; + key avKey = id; integer lifeCount = 0; if (syncIdx != -1) { - // Player found in current sync data - if (syncIdx < llGetListLength(players)) { - avKey = llList2Key(players, syncIdx); - } - if (syncIdx < llGetListLength(lives)) { - lifeCount = llList2Integer(lives, syncIdx); - } - } else { - // Player not in sync data - they might be eliminated - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ” [Floater Manager] Player " + name + " not in current sync - might be eliminated"); - } - // Continue with lifeCount = 0 to show eliminated status + if (syncIdx < llGetListLength(players)) avKey = llList2Key(players, syncIdx); + if (syncIdx < llGetListLength(lives)) lifeCount = llList2Integer(lives, syncIdx); } - 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 - // Check if we have any players with lives > 0 to determine if game is active - integer gameActive = FALSE; - integer i; - for (i = 0; i < llGetListLength(lives); i++) { - if (llList2Integer(lives, i) > 0) { - gameActive = TRUE; - } - } - - if (!gameActive || perilPlayer == "" || perilPlayer == "NONE" || llSubStringIndex(perilPlayer, ",") != -1) { - perilName = "๐Ÿง‘ Status: Waiting for game to start..."; - } else if (name == perilPlayer) { - // This player is currently in peril - perilName = "โšก YOU ARE IN PERIL! โšก"; - } else { - // Show who is currently in peril - perilName = "๐Ÿง‘ Peril Player: " + perilPlayer; - } - string picksDisplay = llList2CSV(picks); - // Check if this player is the winner (only one player with lives > 0 AND game is active) + // Visual Settings + float glow = 0.0; + vector color = <1,1,1>; + string heartTexture = (string)lifeCount + "_hearts"; + + // Status String Logic + string perilStatus; + if (perilPlayer == "" || perilPlayer == "NONE") perilStatus = "๐Ÿง‘ Status: Waiting..."; + else if (name == perilPlayer) perilStatus = "โšก YOU ARE IN PERIL! โšก"; + else perilStatus = "๐Ÿง‘ Peril Player: " + perilPlayer; + + // Winner detection integer livingPlayers = 0; - integer isWinner = FALSE; + integer i; for (i = 0; i < llGetListLength(lives); i++) { - if (llList2Integer(lives, i) > 0) { - livingPlayers++; - } + if (llList2Integer(lives, i) > 0) livingPlayers++; } - // Winner detection: only trigger if game is active, has multiple total players, and only 1 survivor - // This prevents false winner detection during initial player registration - if (gameActive && livingPlayers == 1 && lifeCount > 0 && llGetListLength(names) >= 2) { - isWinner = TRUE; + string txt = "๐ŸŽฒ Peril Dice\n๐Ÿ‘ค " + name + "\n" + perilStatus + "\n๐Ÿ”ข Picks: " + picksDisplay; + + if (lifeCount <= 0) { + glow = 0.3; + color = <1,0,0>; // Red + txt = "๐ŸŽฒ Peril Dice\n๐Ÿ‘ค " + name + "\n๐Ÿ”ข Picks: " + picksDisplay + "\n๐Ÿ’€ ELIMINATED! ๐Ÿ’€"; + } else if (livingPlayers == 1 && llGetListLength(names) >= 2) { + glow = 0.3; + color = <0,1,0>; // Green + txt = "๐ŸŽฒ Peril Dice\n๐Ÿ‘ค " + name + "\nโœจ ULTIMATE VICTORY! โœจ\n๐Ÿ† ULTIMATE SURVIVOR ๐Ÿ†\n๐Ÿ”ข Final Picks: " + picksDisplay; + } else if (name == perilPlayer) { + glow = 0.2; + color = <1,1,0>; // Yellow } - string txt; - if (isWinner) { - // Winner display with victory text (triggers green glow in PlayerStatus_Float.lsl) - txt = "๐ŸŽฒ Peril Dice\n๐Ÿ‘ค " + name + "\nโœจ ULTIMATE VICTORY! โœจ\n๐Ÿ† ULTIMATE SURVIVOR ๐Ÿ†\nLives: " + (string)lifeCount + "\n๐Ÿ”ข Final Picks: " + picksDisplay; - } else { - // Normal display - txt = "๐ŸŽฒ Peril Dice\n๐Ÿ‘ค " + name + "\nLives: " + (string)lifeCount + "\n" + perilName + "\n๐Ÿ”ข Picks: " + picksDisplay; - } - - llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt); + // Send Universal API command + llRegionSay(ch, "CMD:" + (string)avKey + + "|T=" + txt + + "|G=" + (string)glow + + "|C=" + (string)color + + "|H=" + heartTexture); } else if (num == MSG_CLEANUP_FLOAT) { integer ch = (integer)str; - - // Always send cleanup message to the channel (even for orphaned floaters) llRegionSay(ch, "CLEANUP"); - // FIXED: Find player by searching the permanent channel mapping - string removedPlayer = ""; - integer foundIdx = -1; - - // Search for the channel in our permanent mapping - foundIdx = llListFindList(allPlayerChannels, [ch]); - if (foundIdx != -1) { - removedPlayer = llList2String(allPlayerNames, foundIdx); - } - - if (foundIdx != -1 && removedPlayer != "") { - // Find the current index of this player in the active names list + integer foundIdx = ch - FLOATER_BASE_CHANNEL; + if (foundIdx >= 0 && foundIdx < llGetListLength(allPlayerNames)) { + string removedPlayer = llList2String(allPlayerNames, foundIdx); integer currentIdx = llListFindList(names, [removedPlayer]); - llOwnerSay("๐Ÿงน [Floater Manager] Cleaning up floater for: " + removedPlayer + " (channel: " + (string)ch + ")"); - - // Remove from current game lists using the current index if (currentIdx != -1) { if (currentIdx < llGetListLength(players)) players = llDeleteSubList(players, currentIdx, currentIdx); if (currentIdx < llGetListLength(names)) names = llDeleteSubList(names, currentIdx, currentIdx); if (currentIdx < llGetListLength(lives)) lives = llDeleteSubList(lives, currentIdx, currentIdx); - } else { - llOwnerSay("โš ๏ธ [Floater Manager] WARNING: " + removedPlayer + " not found in current names list for removal"); } - // IMPORTANT: Keep permanent mapping intact - never remove from playerNames/playerChannels - // This preserves the original channel assignments - - // Remove from picksData by searching for the player name integer pickIdx = -1; integer p; for (p = 0; p < llGetListLength(picksData); p++) { - string entry = llList2String(picksData, p); - if (llSubStringIndex(entry, removedPlayer + "|") == 0) { + if (llSubStringIndex(llList2String(picksData, p), removedPlayer + "|") == 0) { pickIdx = p; - jump found_pick; + p = llGetListLength(picksData); } } - @found_pick; - if (pickIdx != -1) { - picksData = llDeleteSubList(picksData, pickIdx, pickIdx); - } - - // 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 - channel not found in our tracking - llOwnerSay("๐Ÿงน [Floater Manager] Cleaned up orphaned floater (channel: " + (string)ch + ") - not in tracking list"); + if (pickIdx != -1) picksData = llDeleteSubList(picksData, pickIdx, pickIdx); } } else if (num == MSG_SYNC_GAME_STATE) { - // Synchronize the lists for lives and picksData when receiving a new game state list parts = llParseString2List(str, ["~"], []); + if (llGetListLength(parts) < 4) return; - // 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); - - // ENHANCED DEBUG: Log the received picks data + lives = llCSV2List(llList2String(parts, 0)); 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; if (receivedPeril == "NONE") { - perilPlayer = ""; // Convert placeholder back to empty + perilPlayer = ""; } else { perilPlayer = receivedPeril; } - // IMPORTANT: Also sync the names list to prevent desynchronization - string namesStr = llList2String(parts, 3); - list newNames = llCSV2List(namesStr); + names = llCSV2List(llList2String(parts, 3)); - // CRITICAL: If names list changed size, rebuild permanent mapping if needed - if (llGetListLength(allPlayerNames) == 0 && llGetListLength(newNames) > 0) { - // Initial population of permanent mapping from sync - allPlayerNames = newNames; - allPlayerChannels = []; - integer i; - for (i = 0; i < llGetListLength(newNames); i++) { - integer ch = FLOATER_BASE_CHANNEL + i; - allPlayerChannels += [ch]; - } - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ”„ [Floater Manager] Built permanent mapping: " + llList2CSV(allPlayerNames) + " -> " + llList2CSV(allPlayerChannels)); - } - } - - names = newNames; - - // Sync players list if available (5th part) - if (llGetListLength(parts) >= 5) { - string playersStr = llList2String(parts, 4); - players = llCSV2List(playersStr); - } - - // Debug the peril player status change - if (oldPeril != perilPlayer) { - llOwnerSay("๐ŸŽฏ Floater Manager: Peril player updated from '" + oldPeril + "' to '" + perilPlayer + "'"); - } + if (llGetListLength(allPlayerNames) == 0 && llGetListLength(names) > 0) allPlayerNames = names; + if (llGetListLength(parts) >= 5) players = llCSV2List(llList2String(parts, 4)); - // CRITICAL: After synchronizing the game state, ALWAYS update ALL registered floaters - // This includes both alive and eliminated players so eliminated floaters show proper status integer idx; for (idx = 0; idx < llGetListLength(allPlayerNames); idx++) { string n = llList2String(allPlayerNames, idx); - // Find player key if they're still alive, otherwise use NULL_KEY key k = NULL_KEY; integer aliveIdx = llListFindList(names, [n]); - if (aliveIdx != -1 && aliveIdx < llGetListLength(players)) { - k = llList2Key(players, aliveIdx); - } + if (aliveIdx != -1 && aliveIdx < llGetListLength(players)) k = llList2Key(players, aliveIdx); 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(allPlayerNames); idx++) { - string n = llList2String(allPlayerNames, idx); - // Find player key if they're still alive, otherwise use NULL_KEY - key k = NULL_KEY; - integer aliveIdx = llListFindList(names, [n]); - if (aliveIdx != -1 && aliveIdx < llGetListLength(players)) { - k = llList2Key(players, aliveIdx); - } - llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, n, k); - } - } } - - // Handle aggressive floater cleanup requests else if (num == MSG_CLEANUP_ALL_FLOATERS) { - string context = str; // "RESET" for conservative, empty for aggressive - - if (context == "RESET") { - // Conservative cleanup during reset - only clean known floaters using permanent mapping - if (llGetListLength(allPlayerChannels) > 0) { - llOwnerSay("๐Ÿงน [Floater Manager] Cleaning up floaters for " + (string)llGetListLength(allPlayerChannels) + " registered channels..."); - integer i; - for (i = 0; i < llGetListLength(allPlayerChannels); i++) { - integer ch = llList2Integer(allPlayerChannels, i); - llRegionSay(ch, "CLEANUP"); - } - } - } else { - // Aggressive cleanup for troubleshooting - scan all possible channels - llOwnerSay("๐Ÿงฝ [Floater Manager] Performing aggressive floater cleanup..."); - integer i; - for (i = 0; i < MAX_PLAYERS; i++) { - integer ch = FLOATER_BASE_CHANNEL + i; - llRegionSay(ch, "CLEANUP"); - llRegionSay(ch + 100, "CLEANUP"); - llRegionSay(ch + 1000, "CLEANUP"); - } - llOwnerSay("โœ… [Floater Manager] Aggressive cleanup complete"); + integer i; + for (i = 0; i < llGetListLength(allPlayerNames); i++) { + llRegionSay(FLOATER_BASE_CHANNEL + i, "CLEANUP"); } - - // Clear our internal tracking - players = []; - names = []; - lives = []; - picksData = []; - perilPlayer = ""; - playerNames = []; - playerChannels = []; - } - // 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"); + players = []; names = []; lives = []; picksData = []; perilPlayer = ""; } + else if (num == MSG_DEBUG_PICKS_ON) DEBUG_PICKS = TRUE; + else if (num == MSG_DEBUG_PICKS_OFF) DEBUG_PICKS = FALSE; } } diff --git a/Furware_Text_Engine.lsl b/Furware_Text_Engine.lsl new file mode 100644 index 0000000..34f4a20 --- /dev/null +++ b/Furware_Text_Engine.lsl @@ -0,0 +1,1055 @@ +//////////////////////////////////////////////////////////// +// // +// _cg####ggi_ // +// _g00000000000o_ // +// ,0000^.omggc.000ยก // +// 000 ,F ยฏยฐ0#ยก000L // +// FURWARE text #00 ยก ยก0 000#00 ^ // +// #0 ]O 00 #0 00 #L // +// Version 2.0.1 0 #0 0O J0 #0 0O // +// Open Source v #00#0ยก #0 0 ]0O // +// J000000c_ J0 c00^ // +// 0000c^00@NN ,#000 // +// `0000@ggg#0000^ // +// ^ยฐ0000000^^ // +// // +//////////////////////////////////////////////////////////// + +////////// DOCUMENTATION /////////////////////////////////// + +/* + +A user's manual as well as documentation for developers +is available on the Second Life (R) Wiki at + +http://wiki.secondlife.com/wiki/FURWARE_text + +*/ + +////////// LICENSE ///////////////////////////////////////// + +/* + +MIT License + +Copyright (c) 2010-2013 Ochi Wolfe, FURWARE, the contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +////////// CONTRIBUTORS //////////////////////////////////// + +/* + +Ochi Wolfe - Initial development from 2010 to 2013. + +*/ + +////////// CONSTANTS /////////////////////////////////////// + +// Index offsets and stride size of data in "boxDataList". +integer BOX_DATA = 0; // The referenced data has type "string". +integer BOX_CONF = 1; // The referenced data has type "string". +integer BOX_STATUS = 2; // The referenced data has type "integer". +integer BOX_GEOM = 3; // The referenced data has type "rotation". +integer BOX_STRIDE = 4; + +// Tags used to remember the kind of the last performed action. +integer ACTION_CONTENT = 1; // Text or style may have been modified. +integer ACTION_ADD_BOX = 2; // A virtual text box was added. +integer ACTION_DEL_BOX = 3; // A virtual text box was deleted. + +// String of displayable characters, in the order of the font texture. +string CHARS = ""; // Populated in state_entry. + +////////// VARIABLES /////////////////////////////////////// + +// Global status +integer primCount; // Used to check for prim count changes. +integer lastAction; // The kind of the last performed action. + +// Per-box data (box = virtual text box) +integer boxDataLength; // Length of the "boxDataList". +list boxNameList; // List of virtual text boxes' names. +list boxDataList; // Strided list of virtual text box data. + +// Per-set data (set = physical display) +integer setCount; // Number of display sets. +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" + +list primLayerList; // Contains layer assignments for each face. + +// Memory for templates +list tmplNameList; // Names of stored templates. +list tmplDataList; // Contents of stored templates. + +// Global configuration +integer gNotify; // Whether notifications shall be sent when done. +string gConfAll; // Base configuration for all boxes everywhere. +string gConfRoot; // Base configuration for root boxes. +string gConfNonRoot; // Base configuration for non-root boxes. + +// Default configuration +string dAlign; // Text alignment. +string dTrim; // Whitespace trimming at begin/end. +string dWrap; // Text wrapping mode. +rotation dColor; // Text color. +string dFont; // Text font. +string dBorder; // Border type around virtual text box. +string dTags; // Use of inline style tags (). +integer dForce; // Force refresh, overriding cached face-filled state. + +// Current configuration +string cAlign; // See above. +string cTrim; +string cWrap; +rotation cColor; +string cFont; +string cBorder; +string cTags; +integer cForce; + +// Prim data cache +integer cacheIndex = -1; // Index of currently cached prim data. +integer cacheDirty; // Bit 0 set = layer dirty, bit 1 set = stat dirty. +integer cacheLink; // Link number cache. +integer cacheFill; // Face-filled state cache. +integer cacheLayer; // Layer cache. + +// Shared data between refresh() and draw(), +// just to avoid lots of parameter passing +integer setAuxIndex; +integer setColCount; +integer setFaceCount; +integer boxStatus; + +////////// FUNCTIONS /////////////////////////////////////// + +integer refresh() { + llSetTimerEvent(0.0); + + integer i; // REUSED THROUGHOUT FUNCTION + integer boxDataIndex; + integer step = BOX_STRIDE; + integer end = boxDataLength; + + if (lastAction != ACTION_ADD_BOX) { // Last action is NOT adding a box -> iterate backwards + boxDataIndex = boxDataLength - BOX_STRIDE; + step = end = -BOX_STRIDE; + } + + while (boxDataIndex != end) { + boxStatus = llList2Integer(boxDataList, boxDataIndex + BOX_STATUS); + + if (boxStatus & 0x1000) { // Dirty + // Set the factory default settings. + cAlign = dAlign = "left"; + cTrim = dTrim = "on"; + cWrap = dWrap = "word"; + cColor = dColor = <1.0, 1.0, 1.0, 1.0>; + cFont = dFont = "f974fdfc-8fbd-2f29-2b38-3c34411c1fcc"; + cBorder = dBorder = ""; + cTags = dTags = "on"; + cForce = dForce = FALSE; + + integer setIndex = (boxStatus >> 4) & 0xFF; + rotation boxGeometry = llList2Rot(boxDataList, boxDataIndex + BOX_GEOM); + + // Load set data. + vector setGeometry = llList2Vector(setDataList, 2*setIndex); + setAuxIndex = llList2Integer(setDataList, 2*setIndex+1); + + // Load box config. + set(gConfAll, TRUE, TRUE); + if (boxDataIndex / BOX_STRIDE < setCount) { + set(gConfRoot, TRUE, TRUE); + } else { + set(gConfNonRoot, TRUE, TRUE); + } + set(llList2String(boxDataList, boxDataIndex + BOX_CONF), TRUE, TRUE); + + integer boxX = (integer)boxGeometry.x; + integer boxY = (integer)boxGeometry.y; + integer boxW = (integer)boxGeometry.z; + integer boxH = (integer)boxGeometry.s; + integer boxR = boxX+boxW-1; + integer boxB = boxY+boxH-1; + + setColCount = (integer)setGeometry.x; + setFaceCount = (integer)setGeometry.z; + + integer borderTT = !!(~llSubStringIndex(cBorder, "T")); + integer borderRR = !!(~llSubStringIndex(cBorder, "R")); + integer borderBB = !!(~llSubStringIndex(cBorder, "B")); + integer borderLL = !!(~llSubStringIndex(cBorder, "L")); + + integer borderT = borderTT || (~llSubStringIndex(cBorder, "t")); + integer borderR = borderRR || (~llSubStringIndex(cBorder, "r")); + integer borderB = borderBB || (~llSubStringIndex(cBorder, "b")); + integer borderL = borderLL || (~llSubStringIndex(cBorder, "l")); + + integer borderSt; + if (~llSubStringIndex(cBorder, "1")) borderSt = 1; + else if (~llSubStringIndex(cBorder, "2")) borderSt = 2; + + if (boxStatus & 0x2000) { // Potentially need to refresh borders? + // integer i; (MOVED TO FUNCTION START) + + if (borderT) { + for (i = boxX+borderL; i <= boxR-borderR; ++i) draw(235 + 25*borderSt, i, boxY); + } + + if (borderR) { + if (borderT) draw(227 - borderRR + 25*borderTT + 3*borderSt, boxR, boxY); + for (i = boxY+borderT; i <= boxB-borderB; ++i) draw(262 + borderSt, boxR, i); + if (borderB) draw(277 - borderRR - 25*borderBB + 3*borderSt, boxR, boxB); + } + + if (borderB) { + for (i = boxR-borderR; i >= boxX+borderL; --i) draw(235 + 25*borderSt, i, boxB); + } + + if (borderL) { + if (borderB) draw(275 + borderLL - 25*borderBB + 3*borderSt, boxX, boxB); + for (i = boxB-borderB; i >= boxY+borderT; --i) draw(262 + borderSt, boxX, i); + if (borderT) draw(225 + borderLL + 25*borderTT + 3*borderSt, boxX, boxY); + } + } + + boxX += borderL; + boxY += borderT; + boxR -= borderR; + boxB -= borderB; + boxW -= borderL + borderR; + boxH -= borderT + borderB; + + if (boxW > 0 && boxH > 0) { + // Prepare data. + string text = llList2String(boxDataList, boxDataIndex + BOX_DATA); + + integer textLength = llStringLength(text); + integer textIndex; + + list part; + integer partLength; + integer partIndex; + + integer dataNewLine = TRUE; + string token; + integer tokenLength; + + integer boxRow; + while (boxRow < boxH) { + list line; + integer lineLength; + + list tagPosList; + list tagCmdList; + integer tagCmdListLength; + string tagCommand; + + integer spacesTail; + integer textMode = TRUE; + integer rowDone; + integer skipRestOfLine; + integer lastTokenWasSpace; + + // Parsing loop. + while ((partIndex < partLength || textIndex < textLength) && !rowDone) { + if (partIndex >= (partLength-1) && textIndex < textLength) { + part = llParseString2List( + llGetSubString(text, textIndex, textIndex+boxW), + [], [" ", "\n", ""] + ); + partLength = llGetListLength(part); + partIndex = 0; + tokenLength = 0; + } + + if (!tokenLength) { + token = llList2String(part, partIndex); + tokenLength = llStringLength(token); + } + + integer dataAdvance = TRUE; + integer tokenLengthPrev = tokenLength; + + // Text mode takes care of all tokens that are not enclosed by . + if (textMode) { + if ((cTags == "on") && (token == " 0) { + toAppend = spaceLeft; + lineLength += spacesTail; + spacesTail = 0; + } + } + + if (cWrap != "none") { + rowDone = TRUE; + dataAdvance = FALSE; + } else { + skipRestOfLine = TRUE; + } + } + + // integer i; (ALREADY DECLARED) + for (i = 0; i < toAppend; ++i) { + integer charPos = llSubStringIndex(CHARS, llGetSubString(token, i, i)); + if (~charPos) line += [charPos]; else line += [68]; // 68 = "?" + --tokenLength; + ++lineLength; + } + + if ((cWrap != "none") && tokenLength) { + token = llGetSubString(token, -tokenLength, -1); + } + } + + lastTokenWasSpace = tokenIsSpace; + } + // Tag mode takes care of all tokens within . + } else { + if (token == ">") { + if (dataNewLine) { + set(tagCommand, TRUE, FALSE); + } else { + tagPosList += [lineLength+spacesTail]; + tagCmdList += [tagCommand]; + ++tagCmdListLength; + } + tagCommand = ""; + textMode = TRUE; + } else { + tagCommand += token; + } + } + + if (dataAdvance) { + ++partIndex; + tokenLength = 0; + } + + textIndex += (tokenLengthPrev - tokenLength); + } + + integer nextTagListIndex; + integer nextTagCharIndex = -1; + if (tagCmdListLength) { + nextTagCharIndex = llList2Integer(tagPosList, 0); + } + + integer lineIndex; + if (cAlign != "left") { + integer delta = boxW - lineLength; + if (cAlign == "center") delta /= 2; + lineIndex -= delta; + } + + integer x; + for (x = boxX; x <= boxR; ++x) { + integer pos; + if (lineIndex >= 0 && lineIndex < lineLength) { + while (nextTagListIndex < tagCmdListLength && lineIndex >= nextTagCharIndex) { + set(llList2String(tagCmdList, nextTagListIndex++), FALSE, FALSE); + nextTagCharIndex = llList2Integer(tagPosList, nextTagListIndex); + } + pos = llList2Integer(line, lineIndex); + } + draw(pos, x, boxY + boxRow); + ++lineIndex; + } + + while (nextTagListIndex < tagCmdListLength) { + set(llList2String(tagCmdList, nextTagListIndex++), FALSE, FALSE); + } + + ++boxRow; + } + } + + // Mark box as clean. + boxDataList = llListReplaceList( + boxDataList, [boxStatus & 0xFFF], boxDataIndex + BOX_STATUS, boxDataIndex + BOX_STATUS + ); + } + + boxDataIndex += step; + } + + draw(-1, 0, 0); // Flush cache. + lastAction = 0; + + if (gNotify) { + llMessageLinked(LINK_SET, 0, "", (key)"fw_done"); + } + return 0; +} + +integer draw(integer char, integer x, integer y) { + integer newCacheIndex = -1; + if (~char) newCacheIndex = setAuxIndex + y*setColCount + x/setFaceCount; + + if (newCacheIndex != cacheIndex) { + if (~cacheIndex) { + // Stat cache dirty + if (cacheDirty & 2) primFillList = llListReplaceList( + primFillList, [cacheFill], cacheIndex, cacheIndex + ); + // Layer cache dirty + if (cacheDirty & 1) primLayerList = llListReplaceList( + primLayerList, [cacheLayer], cacheIndex, cacheIndex + ); + cacheDirty = 0; + } + + cacheIndex = newCacheIndex; + + if (~cacheIndex) { + cacheLink = llList2Integer(primLinkList, cacheIndex); + cacheFill = llList2Integer(primFillList, cacheIndex); + cacheLayer = llList2Integer(primLayerList, cacheIndex); + } + } + + if (!~char) return 0; + + integer face = x % setFaceCount; + integer layer = (cacheLayer >> 4*face) & 0xF; + integer boxLayer = (boxStatus & 0xF); + + if ((0x10000 << layer) & boxStatus) { // Layer override + cacheLayer = (cacheLayer & ~(0xF << 4*face)) | (boxLayer << 4*face); + cacheDirty = cacheDirty | 1; + } else if (layer != boxLayer) { + return 0; + } + + integer filled = cForce || char; + integer statDiffers = (filled ^ ((cacheFill >> face) & 1)); + + if (filled || statDiffers) { + if (statDiffers) { + cacheFill = (cacheFill & ~(1 << face)) | (filled << face); + cacheDirty = cacheDirty | 2; + } + + llSetLinkPrimitiveParamsFast(cacheLink, [ + PRIM_TEXTURE, face, cFont, <0.03125, 0.0625, 0.0>, + <0.0390625 * (char % 25), -0.0703125 * (char / 25), 0.0>, 0.0, + PRIM_COLOR, face, , filled * cColor.s + ]); + } + return 0; +} + +integer set(string data, integer startOfLine, integer setDefaults) { + if (data == "") return 0; + + list parts = llParseString2List(data, [";"], []); + integer partCount = llGetListLength(parts); + integer partIdx; + for (partIdx = 0; partIdx < partCount; ++partIdx) { + list tokens = llParseString2List(llList2String(parts, partIdx), ["="], []); + if (llGetListLength(tokens) > 1) { + string tag = llStringTrim(llList2String(tokens, 0), STRING_TRIM); + string valUpper = llStringTrim(llList2String(tokens, 1), STRING_TRIM); + string valLower = llToLower(valUpper); + integer valIsDef = (valLower == "def"); + + if (tag == "c") { + if (valIsDef) cColor = dColor; + else if (valLower == "rand") cColor = ; + else if (valLower == "white") cColor = <1.0, 1.0, 1.0, 1.0>; + else if (valLower == "black") cColor = <0.0, 0.0, 0.0, 1.0>; + else if (valLower == "darkred") cColor = <0.5, 0.0, 0.0, 1.0>; + else if (valLower == "darkgreen") cColor = <0.0, 0.5, 0.0, 1.0>; + else if (valLower == "darkblue") cColor = <0.0, 0.0, 0.5, 1.0>; + else if (valLower == "darkcyan") cColor = <0.0, 0.5, 0.5, 1.0>; + else if (valLower == "darkmagenta") cColor = <0.5, 0.0, 0.5, 1.0>; + else if (valLower == "darkyellow") cColor = <0.5, 0.5, 0.0, 1.0>; + else if (valLower == "gray") cColor = <0.5, 0.5, 0.5, 1.0>; + else if (valLower == "red") cColor = <1.0, 0.0, 0.0, 1.0>; + else if (valLower == "green") cColor = <0.0, 1.0, 0.0, 1.0>; + else if (valLower == "blue") cColor = <0.0, 0.0, 1.0, 1.0>; + else if (valLower == "cyan") cColor = <0.0, 1.0, 1.0, 1.0>; + else if (valLower == "magenta") cColor = <1.0, 0.0, 1.0, 1.0>; + else if (valLower == "yellow") cColor = <1.0, 1.0, 0.0, 1.0>; + else if (valLower == "silver") cColor = <0.75, 0.75, 0.75, 1.0>; + else { + valLower = "<" + valLower + ">"; + cColor = (rotation)valLower; + if (cColor == ZERO_ROTATION) { + vector tmp = (vector)valLower; + cColor = ; + } + } + } + + else if (tag == "f") { + if (valIsDef) cFont = dFont; else cFont = valUpper; + } + + else if (tag == "style") { + integer tmplIndex = llListFindList(tmplNameList, [valUpper]); + if (~tmplIndex) { + set(llList2String(tmplDataList, tmplIndex), startOfLine, setDefaults); + } else { + dbg("FW text: Style var \"" + valUpper + "\" not found."); + } + } + + else if (startOfLine) { + if (tag == "w") { + if (valIsDef) cWrap = dWrap; else cWrap = valLower; + } else if (tag == "t") { + if (valIsDef) cTrim = dTrim; else cTrim = valLower; + } else if (tag == "a") { + if (valIsDef) cAlign = dAlign; else cAlign = valLower; + } else if (tag == "border") { + if (valIsDef) cBorder = dBorder; else cBorder = valUpper; + } else if (tag == "tags") { + if (valIsDef) cTags = dTags; else cTags = valLower; + } else if (tag == "force") { + if (valIsDef) cForce = dForce; else cForce = (valLower == "on"); + } + } + } + } + + if (setDefaults) { + dAlign = cAlign; + dTrim = cTrim; + dWrap = cWrap; + dColor = cColor; + dFont = cFont; + dBorder = cBorder; + dTags = cTags; + dForce = cForce; + } + return 0; +} + +integer setDirty(integer action, integer first, integer last, integer isConf, + integer newLayerOverrideBits, integer setIndex, integer withData, string data) { + integer sd_i; + for (sd_i = first; sd_i <= last; sd_i += BOX_STRIDE) { + integer setMatches = TRUE; + if (~setIndex) { + setMatches = (((llList2Integer(boxDataList, sd_i + BOX_STATUS) >> 4) & 0xFF) == setIndex); + } + + if (setMatches) { + integer j; + + if (withData) { + j = sd_i + isConf; + boxDataList = llListReplaceList(boxDataList, [data], j, j); + } + + j = sd_i + BOX_STATUS; + boxDataList = llListReplaceList( + boxDataList, [ + llList2Integer(boxDataList, j) | 0x1000 | (isConf * 0x2000) | (newLayerOverrideBits << 16) + ], j, j + ); + } + } + + if (!lastAction) { + llSetTimerEvent(0.05); + lastAction = action; + } + return 0; +} + +////////// STATES ////////////////////////////////////////// + +default { + state_entry() { + llOwnerSay("FURWARE text is starting..."); + + CHARS = llBase64ToString( + "IGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFS" + + "U1RVVldYWVowMTIzNDU2Nzg5Liw6OyE/IifCtGBefistKi9cfCgpW117fTw+" + + "PUAkJSYjX8Ogw6HDosOjw6TDpcOmwqrDp8Oww6jDqcOqw6vDrMOtw67Dr8Ox" + + "w7LDs8O0w7XDtsO4xZPCusOexaHDn8O5w7rDu8O8w73Dv8W+w4DDgcOCw4PD" + + "hMOFw4bDh8OQw4jDicOKw4vDjMONw47Dj8ORw5LDk8OUw5XDlsOYxZLDvsWg" + + "w5nDmsObw5zDncW4xb3CosKj4oKswqXCp8K1wqHCv8Kpwq7CscOXw7fCt8Kw" + + "wrnCssKzwqvCu8Ks4oCm4oC54oC64oCTwrzCvcK+4oSi4oCiICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgIOKUjOKUrOKUkOKUj+KUs+KUk+KVlOKVpuKV" + + "l+KVtuKUgOKVtOKVt+KVuy DilK/ilrLil4Dilrzilrbil4vil5Til5Hil5Xi" + + "l4/ilJzilLzilKTilKPilYvilKvilaDlrazilaPilbrilIHilbjilILilIPi" + + "lZHilL/ihpHihpDihpPihpLihrrihrvimJDimJHimJLilJTilLTilJjilJfi" + + "lLvilJvilZrilanilZ0g4pWQIOKVteKVuSDilLfilKDilYLilKjihpXihpTi" + + "mYDimYLimqDihLninYzijJbiiKHijJvijJrimarimavimaDimaPimaXimabi" + + "moDimoHimoLimoPimoTimoXinJTinJjimLrimLnilqDil77ilqzilq7ilog=" + ); + + // Remember prim count to detect changes later on. + primCount = llGetObjectPrimCount(llGetKey())+llGetNumberOfPrims()*!!llGetAttached(); + + // Determine which link IDs to iterate. + integer linkMin; + integer linkMax; + if (primCount > 1) { + linkMin = 1; + linkMax = primCount; + } + + // Fetch data from prims. + list sets; + integer dataLength; + while (linkMin <= linkMax) { + list tokens = llParseStringKeepNulls(llGetLinkName(linkMin), [":"], []); + if (llList2String(tokens, 0) == "FURWARE text mesh") { + string setStr = llList2String(tokens, 1); + if (setStr != "") { + integer set = llListFindList(sets, [setStr]); + if (!~set) { + set = llGetListLength(sets); + sets += [setStr]; + } + + primLinkList += [ + (set << 20) | + ((llList2Integer(tokens, 2) & 0x3FF) << 10) | + (llList2Integer(tokens, 3) & 0x3FF), + linkMin + ]; + ++dataLength; + } + } + ++linkMin; + } + + // Abort if not a single element was found. + if (!dataLength) { + llOwnerSay("FW text: No text 2.x prims found."); + return; + } + + // Sort the data according to their set-row-col values. + primLinkList = llListSort(primLinkList, 2, TRUE); + + // Parse the gathered data. + integer dataIndex; + integer setrowcol = llList2Integer(primLinkList, dataIndex); + integer set = (setrowcol >> 20) & 0x3FF; + integer row = (setrowcol >> 10) & 0x3FF; + integer nextSet; + integer nextRow; + + do { // Set + + integer rowCount; + integer colCount; + integer faceCount; + + integer setAuxPtr = dataIndex; + + do { // Row + + ++rowCount; + integer newColCount; + + do { // Col + + ++newColCount; + integer link = llList2Integer(primLinkList, 2*dataIndex+1); + integer newFaceCount = llGetLinkNumberOfSides(link); + + if (faceCount) { + if (newFaceCount != faceCount) { + llOwnerSay("FW text: All prims within a set need to have the same number of faces."); + setCount = 0; + return; + } + } else { + faceCount = newFaceCount; + } + + llSetLinkPrimitiveParamsFast(link, [ + PRIM_TEXTURE, ALL_SIDES, TEXTURE_TRANSPARENT, + <1.0, 1.0, 0.0>, ZERO_VECTOR, 0.0 + ]); + + setrowcol = llList2Integer(primLinkList, 2*(++dataIndex)); + nextSet = (setrowcol >> 20) & 0x3FF; + nextRow = (setrowcol >> 10) & 0x3FF; + + } while (dataIndex < dataLength && set == nextSet && row == nextRow); + + if (colCount) { + if (newColCount != colCount) { + llOwnerSay("FW text: All rows within a set need to have the same number of prims."); + setCount = 0; + return; + } + } else { + colCount = newColCount; + } + + row = nextRow; + + } while (dataIndex < dataLength && set == nextSet); + + setDataList += [, setAuxPtr]; + boxNameList += [llList2String(sets, set)]; + boxDataList += ["", "", setCount << 4, <0, 0, colCount*faceCount, rowCount>]; + + boxDataLength += BOX_STRIDE; + ++setCount; + + set = nextSet; + + } while (dataIndex < dataLength); + + primLinkList = llDeleteSubList(primLinkList, 0, 0); + primLinkList = llList2ListStrided(primLinkList, 0, -1, 2); + while (dataIndex--) primFillList += [0]; + primLayerList = primFillList; + + dbg("FURWARE text started with " + (string)setCount + " set(s)."); + llMessageLinked(LINK_SET, 0, "", (key)"fw_ready"); + } + + link_message(integer sender, integer num, string str, key id) { + if (!setCount) return; + if (llGetSubString(id, 0, 2) != "fw_") return; + + list tokens = llParseStringKeepNulls(id, [":"], []); + string token0 = llStringTrim(llList2String(tokens, 0), STRING_TRIM); + + integer isConf = (token0 == "fw_conf"); + + if (token0 == "fw_data" || isConf) { + if (lastAction && lastAction != ACTION_CONTENT) refresh(); + + integer tokenCount = llGetListLength(tokens); + if (tokenCount < 2) tokenCount = 2; + + integer t; + for (t = 1; t < tokenCount; ++t) { + list boxTokens = llParseStringKeepNulls(llList2String(tokens, t), [";"], []); + integer boxTokenCount = llGetListLength(boxTokens); + + string boxToken0 = llStringTrim(llList2String(boxTokens, 0), STRING_TRIM); + string boxToken1 = llStringTrim(llList2String(boxTokens, 1), STRING_TRIM); + + integer first = 0; + integer last = boxDataLength - BOX_STRIDE; + integer setIndex = -1; + + if (boxToken0 != "") { + first = llListFindList(boxNameList, [boxToken0]); + + if (!~first) { + llOwnerSay(token0 + ": Box \"" + boxToken0 + "\" not found."); + jump SkipBox; + } + + first *= BOX_STRIDE; + setIndex = (llList2Integer(boxDataList, first + BOX_STATUS) >> 4) & 0xFF; + } + + if (boxToken1 != "") { + last = llListFindList(boxNameList, [boxToken1]); + + if (!~last) { + llOwnerSay(token0 + ": Box \"" + boxToken1 + "\" not found."); + jump SkipBox; + } + + last *= BOX_STRIDE; + integer secondSetIndex = (llList2Integer(boxDataList, last + BOX_STATUS) >> 4) & 0xFF; + + if (~setIndex && setIndex != secondSetIndex) { + llOwnerSay(token0 + ": Box sets must match when specifying a range."); + jump SkipBox; + } + + setIndex = secondSetIndex; + } else if (boxTokenCount == 1 && ~setIndex) { + last = first; + } + + setDirty(ACTION_CONTENT, first, last, isConf, 0, setIndex, TRUE, str); + + @SkipBox; + } + + return; + } + + string token1 = llStringTrim(llList2String(tokens, 1), STRING_TRIM); + + if (token0 == "fw_var") { + if (lastAction && lastAction != ACTION_CONTENT) refresh(); + + if (token1 == "") { + llOwnerSay("fw_var: No variable name given."); + return; + } + + integer tmplIndex = llListFindList(tmplNameList, [token1]); + if (~tmplIndex) { + tmplNameList = llDeleteSubList(tmplNameList, tmplIndex, tmplIndex); + tmplDataList = llDeleteSubList(tmplDataList, tmplIndex, tmplIndex); + } + + if (str != "") { + tmplNameList += [token1]; + tmplDataList += [str]; + } + + setDirty(ACTION_CONTENT, 0, boxDataLength - BOX_STRIDE, TRUE, 0, -1, FALSE, ""); + + return; + } + + if (token0 == "fw_defaultconf") { + if (lastAction && lastAction != ACTION_CONTENT) refresh(); + + integer first = -1; + integer last = boxDataLength - BOX_STRIDE; + + if (token1 == "") { + gConfAll = str; + first = 0; + } else if (token1 == "root") { + gConfRoot = str; + first = 0; + last = setCount * BOX_STRIDE - BOX_STRIDE; + } else if (token1 == "nonroot") { + gConfNonRoot = str; + first = setCount * BOX_STRIDE; + } + + if (~first) { + setDirty(ACTION_CONTENT, first, last, TRUE, 0, -1, FALSE, ""); + } + + return; + } + + string token2 = llStringTrim(llList2String(tokens, 2), STRING_TRIM); + string token3 = llStringTrim(llList2String(tokens, 3), STRING_TRIM); + string token4 = llStringTrim(llList2String(tokens, 4), STRING_TRIM); + + if (token0 == "fw_addbox") { + if (token1 == "") { + llOwnerSay("fw_addbox: Box name cannot be empty."); + return; + } + + if (lastAction && lastAction != ACTION_ADD_BOX) refresh(); + + integer boxNameIndex = llListFindList(boxNameList, [token1]); + + if (~boxNameIndex) { + llOwnerSay("fw_addbox: Box \"" + token1 + "\" already exists."); + return; + } + + integer parNameIndex = llListFindList(boxNameList, [token2]); + + if (!~parNameIndex) { + llOwnerSay("fw_addbox: No parent box \"" + token2 + "\"."); + return; + } + + integer boxDataIndex = BOX_STRIDE * boxNameIndex; + integer parDataIndex = BOX_STRIDE * parNameIndex; + + integer setIndex = (llList2Integer(boxDataList, parDataIndex + BOX_STATUS) >> 4) & 0xFF; + + integer layersUsed; + integer b; + for (b = 0; b < boxDataLength; b += BOX_STRIDE) { + if (setIndex == ((llList2Integer(boxDataList, b + BOX_STATUS) >> 4) & 0xFF)) { + layersUsed = layersUsed | (1 << (llList2Integer(boxDataList, b + BOX_STATUS) & 0xF)); + } + } + + if (layersUsed == 0xFFFF) { + llOwnerSay("fw_addbox: No layer available."); + return; + } + + integer boxLayer; + while (layersUsed & (1 << boxLayer)) ++boxLayer; + + rotation boxGeom = (rotation)("<" + token3 + ">"); + rotation parGeom = llList2Rot(boxDataList, parDataIndex + BOX_GEOM); + vector setGeom = llList2Vector(setDataList, 2*setIndex); + + boxGeom.x += parGeom.x; + boxGeom.y += parGeom.y; + + if (boxGeom.x < 0 || boxGeom.y < 0 || + boxGeom.z < 1 || boxGeom.s < 1 || + (boxGeom.x + boxGeom.z) > (setGeom.x * setGeom.z) || + (boxGeom.y + boxGeom.s) > setGeom.y) + { + llOwnerSay("fw_addbox: Invalid box geometry."); + return; + } + + boxNameList += [token1]; + boxDataList += [str, token4, (setIndex << 4) | boxLayer, boxGeom]; + + setDirty(ACTION_ADD_BOX, boxDataLength, boxDataLength, TRUE, 0xFFFF, -1, FALSE, ""); + boxDataLength += BOX_STRIDE; + + return; + } + + if (token0 == "fw_delbox") { + if (lastAction && lastAction != ACTION_DEL_BOX) refresh(); + + integer tokenCount = llGetListLength(tokens); + + integer t; + for (t = 1; t < tokenCount; ++t) { + string boxName = llStringTrim(llList2String(tokens, t), STRING_TRIM); + integer boxNameIndex = llListFindList(boxNameList, [boxName]); + + if (!~boxNameIndex || (boxNameIndex < setCount)) { + llOwnerSay("fw_delbox: Box \"" + boxName + "\" doesn't exist."); + jump SkipDelBox; + } + + integer boxDataIndex = BOX_STRIDE * boxNameIndex; + integer boxStatus = llList2Integer(boxDataList, boxDataIndex + BOX_STATUS); + + setDirty(ACTION_DEL_BOX, 0, boxDataIndex - BOX_STRIDE, TRUE, + 1 << (boxStatus & 0xF), (boxStatus >> 4) & 0xFF, FALSE, ""); + + boxNameList = llDeleteSubList(boxNameList, boxNameIndex, boxNameIndex); + boxDataList = llDeleteSubList(boxDataList, boxDataIndex, boxDataIndex + BOX_STRIDE - 1); + + boxDataLength -= BOX_STRIDE; + + @SkipDelBox; + } + + return; + } + + if (token0 == "fw_touchquery") { + // Need to flush first so that BOX_STATUS only contains the set index and box layer. + if (lastAction) refresh(); + + string reply = "::::::" + str; + + integer link = (integer)token1; + integer face = (integer)token2; + + list primNameTokens = llParseStringKeepNulls(llGetLinkName(link), [":"], []); + + if (llList2String(primNameTokens, 0) == "FURWARE text mesh") { + integer rootIndex = llListFindList(boxNameList, [llList2String(primNameTokens, 1)]); + + if (~rootIndex) { + integer auxIndex = llListFindList(primLinkList, [link]); + if (~auxIndex) { + integer layer = (llList2Integer(primLayerList, auxIndex) >> (4*face)) & 0xF; + integer boxIndex = llListFindList(boxDataList, [(rootIndex << 4) | layer]); + + if (~boxIndex) { + boxIndex -= BOX_STATUS; + + vector setGeom = llList2Vector(setDataList, 2*rootIndex); + rotation boxGeom = llList2Rot(boxDataList, boxIndex + BOX_GEOM); + + integer x = llList2Integer(primNameTokens, 3) * (integer)setGeom.z + face; + integer y = llList2Integer(primNameTokens, 2); + + reply = llList2String(boxNameList, boxIndex/BOX_STRIDE) + ":" + + (string)(x - (integer)boxGeom.x) + ":" + + (string)(y - (integer)boxGeom.y) + ":" + + llList2String(boxNameList, rootIndex) + ":" + + (string)x + ":" + + (string)y + ":" + str; + } + } + } + } + + llMessageLinked(sender, 0, reply, (key)"fw_touchreply"); + + return; + } + + if (token0 == "fw_notify") { + gNotify = (str == "on"); + return; + } + + if (token0 == "fw_memory") { + dbg((string)llGetFreeMemory() + " bytes free"); + return; + } + + if (token0 == "fw_reset") { + llResetScript(); + } + } + + timer() { + refresh(); + } + + changed(integer change) { + if (change & CHANGED_LINK) { + if (llGetObjectPrimCount(llGetKey())+llGetNumberOfPrims()*!!llGetAttached() != primCount) { + llResetScript(); + } + } + } +} diff --git a/GAME_RULES.md b/GAME_RULES.md index fa64c37..f390b9b 100644 --- a/GAME_RULES.md +++ b/GAME_RULES.md @@ -183,8 +183,11 @@ The game features thematic messaging for maximum immersion: - **Professional Status Actions** - Large status prim shows current game events - **Color-Coded Glow System** - Green (winner), Yellow (peril), Red X (eliminated) -### **Technical Rules:** -- Game supports up to **10 players maximum** +### **Technical Rules (UPDATED in v3.1.0):** +- **Dynamic Link Discovery** - Scripts find their targets by name; reordering prims won't break the game ๐Ÿ” +- **FURWARE Text Mesh Integration** - Professional mesh-based displays for dice and leaderboard ๐ŸŽจ +- **Centralized Constants** - System-wide settings managed in `Peril_Constants.lsl` ๐Ÿงฉ +- **Game supports up to 10 players maximum** - **Owner kick functionality** for managing disruptive players - **Automatic game reset** on startup (preserves leaderboard) - **Single linkset architecture** (74 prims total) for reliable operation @@ -293,7 +296,7 @@ While the core rules remain the same, consider these optional variations: --- -**Version:** 2.8.1 -**Last Updated:** August 14, 2025 -**Game System:** Production Ready - Display & Elimination Fixes +**Version:** 3.1.0 +**Last Updated:** April 04, 2026 +**Game System:** Production Ready - Linkset Stability Release **Credits:** Original concept by Noose the Bunny (djmusica28), Automated system implementation by Rebecca Nod diff --git a/GAME_RULES_NOTECARD.txt b/GAME_RULES_NOTECARD.txt index f26c8b5..e25c86c 100644 --- a/GAME_RULES_NOTECARD.txt +++ b/GAME_RULES_NOTECARD.txt @@ -188,7 +188,10 @@ Enhanced Display System (UPDATED in v2.8.6): - Professional Status Actions - Large status prim shows current events - Color-Coded Glow System - Green (winner), Yellow (peril), Red X (eliminated) -Technical Rules: +Technical Rules (UPDATED in v3.1.0): +- Dynamic Link Discovery - Scripts find their targets by name; reordering prims won't break the game ๐Ÿ” +- FURWARE Text Mesh Integration - Professional mesh-based displays for dice and leaderboard ๐ŸŽจ +- Centralized Constants - System-wide settings managed in Peril_Constants.lsl ๐Ÿงฉ - Game supports up to 10 players maximum - Owner kick functionality for managing disruptive players - Automatic game reset on startup (preserves leaderboard) @@ -299,9 +302,9 @@ Dice! May luck and strategy guide you to Ultimate Victory! =============================================================================== -Version: 2.8.1 -Last Updated: August 14, 2025 -Game System: Production Ready - Display & Elimination Fixes +Version: 3.1.0 +Last Updated: April 04, 2026 +Game System: Production Ready - Linkset Stability Release Credits: Original concept by Noose the Bunny (djmusica28), Automated system implementation by Rebecca Nod diff --git a/Game_Calculator.lsl b/Game_Calculator.lsl index 688c4e5..ac14f04 100644 --- a/Game_Calculator.lsl +++ b/Game_Calculator.lsl @@ -1,12 +1,10 @@ +#include "Peril_Constants.lsl" + // === Game Calculator Module (Max 10 players) === // Calculates dice types, pick requirements, and other game mechanics // 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 -#define MSG_TOGGLE_VERBOSE_LOGS 9998 // Message to toggle verbose logging - // Memory reporting function reportMemoryUsage(string scriptName) { integer used = llGetUsedMemory(); @@ -14,20 +12,12 @@ reportMemoryUsage(string scriptName) { integer total = used + free; float percentUsed = ((float)used / (float)total) * 100.0; - llOwnerSay("๐Ÿง  [" + scriptName + "] Memory: " + + dbg("๐Ÿง  [" + scriptName + "] Memory: " + (string)used + " used, " + (string)free + " free (" + llGetSubString((string)percentUsed, 0, 4) + "% used)"); } -#define MSG_GET_DICE_TYPE 1001 -#define MSG_DICE_TYPE_RESULT 1005 -#define MSG_GET_PICKS_REQUIRED 1002 -#define MSG_GET_PICKER_INDEX 1003 -#define MSG_SERIALIZE_GAME_STATE 1004 -#define MSG_SYNC_GAME_STATE 107 -#define MSG_SYNC_PICKQUEUE 2001 - list lives; list picksData; string perilPlayer; @@ -97,16 +87,10 @@ integer showPickManager(string player, key id) { string matchName = llList2String(names, k); if (llSubStringIndex(matchName, searchName) != -1) { searchIdx = k; - if (VERBOSE_LOGGING) { - llOwnerSay("โœ… Matched pick list for: " + matchName); - } k = llGetListLength(names); } } if (searchIdx == -1) { - if (VERBOSE_LOGGING) { - llOwnerSay("โš ๏ธ Could not find player: " + player); - } return 0; } } @@ -131,13 +115,6 @@ integer showPickManager(string player, key id) { } } - // Don't show hardcoded dialog - let the Owner Dialog Handler manage this - 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 return 0; } @@ -151,49 +128,31 @@ default { perilPlayer = ""; names = []; pickQueue = []; - llOwnerSay("๐Ÿงฎ Game Calculator ready!"); + dbg("๐Ÿงฎ [Calculator] Game Calculator ready!"); } on_rez(integer start_param) { reportMemoryUsage("๐Ÿงฎ Game Calculator"); - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ”„ Game Calculator rezzed - reinitializing..."); - } - // Reset all state variables on rez lives = []; picksData = []; perilPlayer = ""; names = []; pickQueue = []; - - 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") { + if (num == MSG_RESET_ALL && str == "FULL_RESET") { // Reset helper state lives = []; picksData = []; perilPlayer = ""; names = []; pickQueue = []; - llOwnerSay("๐Ÿงฎ Calculator Module reset!"); + dbg("๐Ÿงฎ [Calculator] Calculator Module reset!"); return; } @@ -202,17 +161,13 @@ default { // 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"); - } + dbg("๐Ÿงฎ [Calculator] ๐Ÿ”„ Received reset sync - ignoring during reset"); return; } if (llGetListLength(parts) < 4) { - if (VERBOSE_LOGGING) { - llOwnerSay("โš ๏ธ Game Calculator: Incomplete sync message received, parts: " + (string)llGetListLength(parts)); - llOwnerSay("โš ๏ธ Incomplete game state received."); - } + dbg("๐Ÿงฎ [Calculator] โš ๏ธ Incomplete sync message received, parts: " + (string)llGetListLength(parts)); + dbg("๐Ÿงฎ [Calculator] โš ๏ธ Incomplete game state received."); return; } lives = llCSV2List(llList2String(parts, 0)); @@ -237,9 +192,7 @@ default { } else if (num == MSG_GET_DICE_TYPE) { integer playerCount = (integer)str; - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“ˆ Calculator: Calculating dice type for " + (string)playerCount + " players"); - } + dbg("๐Ÿงฎ [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); @@ -255,8 +208,8 @@ default { else if (num == MSG_SERIALIZE_GAME_STATE) { llMessageLinked(LINK_THIS, MSG_SERIALIZE_GAME_STATE, serializeGameState(), NULL_KEY); } - else if (num == 206) { - llOwnerSay("๐Ÿ“จ MSG 206 received for: " + str + " from: " + (string)id); + else if (num == MSG_OWNER_PICK_MANAGER) { + dbg("๐Ÿงฎ [Calculator] ๐Ÿ“จ MSG 206 received for: " + str + " from: " + (string)id); showPickManager(str, id); } } diff --git a/Game_Manager.lsl b/Game_Manager.lsl index 1ea7cb8..8263d61 100644 --- a/Game_Manager.lsl +++ b/Game_Manager.lsl @@ -1,3 +1,5 @@ +#include "Peril_Constants.lsl" + // === Game Manager - Game Flow Logic === // Helper function to get display name with fallback to username @@ -15,7 +17,6 @@ string getPlayerName(key id) { #define HUMAN_PICK_DELAY 1.0 #define DIALOG_DELAY 1.5 #define STATUS_DISPLAY_TIME 8.0 - // Game state - synced from Main Controller list players = []; list names = []; @@ -79,42 +80,22 @@ list buildCompleteAvoidanceList() { } // Memory reporting function -reportMemoryUsage(string scriptName) { +integer reportMemoryUsage(string scriptName) { integer used = llGetUsedMemory(); integer free = llGetFreeMemory(); integer total = used + free; float percentUsed = ((float)used / (float)total) * 100.0; - llOwnerSay("๐Ÿง  [" + scriptName + "] Memory: " + + dbg("๐Ÿง  [" + scriptName + "] Memory: " + (string)used + " used, " + (string)free + " free (" + llGetSubString((string)percentUsed, 0, 4) + "% used)"); + return 0; } -// Verbose logging control - toggled by owner -#define DEBUG_LOGS 0 // Set to 1 to enable logs, 0 to STRIP from memory completely -#if DEBUG_LOGS -#define dbg(msg) dbg(msg) -integer VERBOSE_LOGGING = FALSE; -#else -#define dbg(msg) -#define VERBOSE_LOGGING 0 -#endif -// Original VERBOSE_LOGGING definition removed +// Build complete avoidance list from all current picks -// Message constants -#define MSG_SHOW_DIALOG 101 -#define MSG_SHOW_ROLL_DIALOG 301 -#define MSG_GET_DICE_TYPE 1001 -#define MSG_DICE_TYPE_RESULT 1005 -#define MSG_SYNC_GAME_STATE 107 -#define MSG_SYNC_PICKQUEUE 2001 -#define MSG_PLAYER_WON 551 -#define MSG_UPDATE_FLOAT 2010 -#define MSG_DIALOG_FORWARD_REQUEST 9060 // Request dialog forwarding from Player_RegistrationManager -#define MSG_CONTINUE_ROUND 998 // Continue round after roll processing - -continueCurrentRound() { +integer continueCurrentRound() { // MEMORY OPTIMIZED: Skip complex validation - trust game state // Main Controller handles all game ending validation @@ -127,30 +108,30 @@ continueCurrentRound() { integer continuePlayerLives = llList2Integer(lives, continuePlayerIdx); if (continuePlayerLives <= 0) { dbg("๐Ÿ›‘ [Game Manager] continueCurrentRound: Found eliminated player " + continueName + " with " + (string)continuePlayerLives + " lives - aborting round start"); - return; + return 0; } } } if (perilPlayer == "" || perilPlayer == "NONE") { dbg("๐Ÿ›‘ [Game Manager] continueCurrentRound: Invalid peril player (" + perilPlayer + ") - aborting round start"); - return; + return 0; } // Verify peril player exists and has lives integer perilIdx = llListFindList(names, [perilPlayer]); if (perilIdx == -1) { dbg("๐Ÿ›‘ [Game Manager] continueCurrentRound: Peril player " + perilPlayer + " not found in names list - aborting round start"); - return; + return 0; } if (perilIdx >= llGetListLength(lives)) { dbg("๐Ÿ›‘ [Game Manager] continueCurrentRound: Peril player " + perilPlayer + " index out of bounds - aborting round start"); - return; + return 0; } integer perilLives = llList2Integer(lives, perilIdx); if (perilLives <= 0) { dbg("๐Ÿ›‘ [Game Manager] continueCurrentRound: Peril player " + perilPlayer + " has " + (string)perilLives + " lives - aborting round start"); - return; + return 0; } // Start fresh for a new round - clear picks data @@ -179,18 +160,19 @@ continueCurrentRound() { llMessageLinked(LINK_SET, MSG_GET_DICE_TYPE, (string)llGetListLength(names), NULL_KEY); // Note: showNextPickerDialog() will be called when dice type result is received + return 0; } -startNextRound() { +integer startNextRound() { // Prevent multiple calls to start round if (roundStarted) { dbg("โš ๏ธ Round already started, ignoring duplicate start request"); - return; + return 0; } if (llGetListLength(names) < 2) { dbg("โš ๏ธ Need at least 2 players to start the game."); - return; + return 0; } if (llGetListLength(names) == 1) { @@ -198,14 +180,14 @@ startNextRound() { llSay(0, "โœจ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!"); // Send winner glow update to scoreboard - llMessageLinked(12, 3006, winner, NULL_KEY); // MSG_UPDATE_WINNER + llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_WINNER, winner, NULL_KEY); // MSG_UPDATE_WINNER llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY); - llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY); + llMessageLinked(LINK_SET, MSG_EFFECT_CONFETTI, "VICTORY_CONFETTI", NULL_KEY); // Don't use hardcoded channel here - let Main Controller handle scoreboard updates // Main Controller will send the proper GAME_WON message to scoreboard // Let main controller handle reset - return; + return 0; } dbg("๐ŸŽฏ Game Manager starting new round..."); @@ -221,7 +203,7 @@ startNextRound() { llSay(0, "๐ŸŽฏ " + perilPlayer + " has been randomly selected and is now in peril!"); // Send peril player update to scoreboard for glow effect - llMessageLinked(12, 3005, perilPlayer, NULL_KEY); // MSG_UPDATE_PERIL_PLAYER + llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_PERIL_PLAYER, perilPlayer, NULL_KEY); // MSG_UPDATE_PERIL_PLAYER // Sync state to floater manager first, then update floaters to show new peril player immediately dbg("๐Ÿ”„ [Game Manager] Syncing state and updating floaters for new peril player: " + perilPlayer); @@ -253,9 +235,10 @@ startNextRound() { // Request dice type for this round llMessageLinked(LINK_SET, MSG_GET_DICE_TYPE, (string)llGetListLength(names), NULL_KEY); dbg("๐ŸŽฏ Game Manager round setup complete, requesting dice type..."); + return 0; } -showNextPickerDialog() { +integer showNextPickerDialog() { // MEMORY OPTIMIZED: Skip complex validation - trust that game is active // Main Controller handles all game ending logic @@ -267,36 +250,36 @@ showNextPickerDialog() { if (diceType <= 0) { dbg("โŒ Cannot show picker dialog: diceType not set (" + (string)diceType + ")"); - return; + return 0; } if (currentPickerIdx >= llGetListLength(pickQueue)) { dbg("โŒ Cannot show picker dialog: currentPickerIdx (" + (string)currentPickerIdx + ") >= pickQueue length (" + (string)llGetListLength(pickQueue) + ")"); - return; + return 0; } string firstName = llList2String(pickQueue, currentPickerIdx); if (firstName == "") { dbg("โŒ Cannot show picker dialog: empty player name at index " + (string)currentPickerIdx); - return; + return 0; } // CRITICAL: Verify this player is still alive and in the game integer showPlayerIdx = llListFindList(names, [firstName]); if (showPlayerIdx == -1) { dbg("โŒ Cannot show picker dialog: player " + firstName + " not found in current game"); - return; + return 0; } if (showPlayerIdx >= llGetListLength(lives)) { dbg("โŒ Cannot show picker dialog: player " + firstName + " index out of bounds for lives list"); - return; + return 0; } integer showPlayerLives = llList2Integer(lives, showPlayerIdx); if (showPlayerLives <= 0) { dbg("โŒ Cannot show picker dialog: player " + firstName + " has been eliminated (" + (string)showPlayerLives + " lives)"); - return; + return 0; } // CHECK: Don't show dialog if this player already has picks to prevent loops @@ -312,23 +295,23 @@ showNextPickerDialog() { } } - if (alreadyHasPicks) { - // Skip to next player to prevent infinite loops - currentPickerIdx++; - if (currentPickerIdx < llGetListLength(pickQueue)) { - showNextPickerDialog(); - } else { - // All picked, show roll dialog through Player_RegistrationManager - string firstRollRequest = "SHOW_ROLL_DIALOG|" + perilPlayer + "|" + perilPlayer; - llMessageLinked(LINK_SET, MSG_DIALOG_FORWARD_REQUEST, firstRollRequest, NULL_KEY); + if (alreadyHasPicks) { + // Skip to next player to prevent infinite loops + currentPickerIdx++; + if (currentPickerIdx < llGetListLength(pickQueue)) { + showNextPickerDialog(); + } else { + // All picked, show roll dialog through Player_RegistrationManager + string firstRollRequest = "SHOW_ROLL_DIALOG|" + perilPlayer + "|" + perilPlayer; + llMessageLinked(LINK_SET, MSG_DIALOG_FORWARD_REQUEST, firstRollRequest, NULL_KEY); + } + return 0; } - return; - } integer nameIdx = llListFindList(names, [firstName]); if (nameIdx == -1) { dbg("โŒ Cannot show picker dialog: player " + firstName + " not found in names list"); - return; + return 0; } // SIMPLE FIX: Send dialog request to Main Controller with player name @@ -363,7 +346,7 @@ showNextPickerDialog() { string showAvoidListStr = llList2CSV(showCompleteAvoidList); string botCommand = "BOT_PICK:" + firstName + ":" + (string)showPicksNeeded + ":" + (string)diceType + ":" + showAvoidListStr; dbg("๐ŸŽฏ [Game Manager] Sending bot command with complete avoid list (" + (string)llGetListLength(showCompleteAvoidList) + " numbers): " + showAvoidListStr); - llMessageLinked(LINK_SET, -9999, botCommand, NULL_KEY); + llMessageLinked(LINK_SET, MSG_BOT_COMMAND, botCommand, NULL_KEY); dbg("๐Ÿค– " + firstName + " is automatically picking " + (string)showPicksNeeded + " numbers..."); } else { // Bot already has picks, advance to next player immediately @@ -397,21 +380,23 @@ showNextPickerDialog() { string dialogRequest = "SHOW_DIALOG|" + firstName + "|" + dialogPayload; llMessageLinked(LINK_SET, MSG_DIALOG_FORWARD_REQUEST, dialogRequest, NULL_KEY); } + return 0; } // requestDiceType() removed - Game Manager no longer requests dice type directly // This prevents loops. Main Controller handles all dice type requests. -syncStateToMain() { +integer syncStateToMain() { // MEMORY OPTIMIZED: Skip ALL sync operations to prevent corrupting Main Controller's data // Main Controller is the master - Game Manager should not send sync messages - return; + return 0; } default { state_entry() { + DISCOVER_CORE_LINKS(); reportMemoryUsage("Game Manager"); - dbg("๐ŸŽฏ Game Manager initializing..."); + dbg("๐ŸŽฏ Game Manager ready - discovery complete! Scoreboard: " + (string)LINK_SCOREBOARD); // Initialize/reset all game state variables players = []; @@ -436,8 +421,9 @@ default { } on_rez(integer start_param) { + DISCOVER_CORE_LINKS(); reportMemoryUsage("Game Manager"); - dbg("๐Ÿ”„ Game Manager rezzed - reinitializing..."); + dbg("๐Ÿ”„ Game Manager reset via rez..."); // Reset all game state variables on rez players = []; @@ -522,7 +508,7 @@ default { if (perilChanged && perilPlayer != "" && perilPlayer != "NONE") { integer currentPlayerCount = llGetListLength(newNames); // Use the updated count string lightUpdate = "PERIL_UPDATE|" + perilPlayer + "|" + (string)currentPlayerCount; - llMessageLinked(LINK_SET, 9070, lightUpdate, NULL_KEY); + llMessageLinked(LINK_SET, MSG_SYNC_LIGHTWEIGHT, lightUpdate, NULL_KEY); } integer allPicksEmpty = TRUE; @@ -545,7 +531,7 @@ default { } // Receive legacy game state updates from main controller (simplified version) - if (num == 9071) { + if (num == MSG_SYNC_LEGACY) { dbg("๐Ÿ”ง [Game Manager] Received sync: " + str); list parts = llParseString2List(str, ["~"], []); dbg("๐Ÿ”ง [Game Manager] Parsed into " + (string)llGetListLength(parts) + " parts"); @@ -681,7 +667,7 @@ default { // Legacy 997 message handler removed - now using direct MSG_CONTINUE_ROUND (998) communication // Handle human picks - if (num == -9998 && llSubStringIndex(str, "HUMAN_PICKED:") == 0) { + if (num == MSG_HUMAN_PICKED && llSubStringIndex(str, "HUMAN_PICKED:") == 0) { list parts = llParseString2List(str, [":"], []); if (llGetListLength(parts) >= 3) { string playerName = llList2String(parts, 1); @@ -827,7 +813,7 @@ default { } // Handle bot picks - if (num == -9997 && llSubStringIndex(str, "BOT_PICKED:") == 0) { + if (num == MSG_BOT_PICKED && llSubStringIndex(str, "BOT_PICKED:") == 0) { dbg("๐Ÿ” [Game Manager] BOT_PICKED message received: " + str); list parts = llParseString2List(str, [":"], []); if (llGetListLength(parts) >= 3) { @@ -874,7 +860,7 @@ default { // Force close any active number picker dialogs dbg("๐Ÿšซ [Game Manager] Sending CLOSE_ALL_DIALOGS command"); - llMessageLinked(LINK_SET, -9999, "CLOSE_ALL_DIALOGS", NULL_KEY); + llMessageLinked(LINK_SET, MSG_BOT_COMMAND, "CLOSE_ALL_DIALOGS", NULL_KEY); // Send roll dialog through Player_RegistrationManager string rollRequest = "SHOW_ROLL_DIALOG|" + perilPlayer + "|" + perilPlayer; @@ -905,7 +891,7 @@ default { string avoidListStr = llList2CSV(completeAvoidList); string botCommand = "BOT_PICK:" + playerName + ":" + (string)picksNeeded + ":" + (string)diceType + ":" + avoidListStr; dbg("๐ŸŽฏ [Game Manager] Sending retry bot command with complete avoid list (" + (string)llGetListLength(completeAvoidList) + " numbers): " + avoidListStr); - llMessageLinked(LINK_SET, -9999, botCommand, NULL_KEY); + llMessageLinked(LINK_SET, MSG_BOT_COMMAND, botCommand, NULL_KEY); return; // Don't save this pick } @@ -984,7 +970,7 @@ default { // Force close any active number picker dialogs dbg("๐Ÿšซ [Game Manager] Sending CLOSE_ALL_DIALOGS command"); - llMessageLinked(LINK_SET, -9999, "CLOSE_ALL_DIALOGS", NULL_KEY); + llMessageLinked(LINK_SET, MSG_BOT_COMMAND, "CLOSE_ALL_DIALOGS", NULL_KEY); // Send roll dialog through Player_RegistrationManager string rollRequest = "SHOW_ROLL_DIALOG|" + perilPlayer + "|" + perilPlayer; @@ -1034,24 +1020,9 @@ 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) { - #if DEBUG_LOGS - VERBOSE_LOGGING = (integer)llList2String(parts, 1); -#endif - if (VERBOSE_LOGGING) { - dbg("๐Ÿ” [Game Manager] Verbose logging ON"); - } else { - dbg("๐Ÿ” [Game Manager] Verbose logging OFF"); - } - } - return; - } // Handle reset - if (num == -99999 && str == "FULL_RESET") { + if (num == MSG_RESET_ALL && str == "FULL_RESET") { players = names = lives = picksData = globalPickedNumbers = pickQueue = []; perilPlayer = ""; currentPickerIdx = 0; @@ -1066,7 +1037,7 @@ default { } // Handle emergency state reset (for when game gets stuck) - if (num == -99998 && str == "EMERGENCY_RESET") { + if (num == MSG_EMERGENCY_RESET && str == "EMERGENCY_RESET") { dbg("๐Ÿšจ [Game Manager] Emergency reset triggered!"); roundStarted = FALSE; perilPlayer = ""; diff --git a/Game_Scoreboard_Manager_Linkset.lsl b/Game_Scoreboard_Manager_Linkset.lsl index 8066944..5c392a8 100644 --- a/Game_Scoreboard_Manager_Linkset.lsl +++ b/Game_Scoreboard_Manager_Linkset.lsl @@ -1,48 +1,59 @@ +#include "Peril_Constants.lsl" + // Game Scoreboard Manager - Linkset Version // Shows current game players in grid layout // Each player gets a prim showing profile picture + heart texture (lives) -// ============================================================================= -// LINKSET COMMUNICATION - NO DISCOVERY NEEDED -// ============================================================================= +// --- DYNAMIC LINK DISCOVERY --- +integer BACKGROUND_PRIM = -1; +integer ACTIONS_PRIM = -1; -// Verbose logging control -#define DEBUG_LOGS 0 // Set to 1 to enable, 0 to STRIP all logs from memory +// Link mappings for player grid (Rows 0-4, Columns 0-1) +list profileLinks = [0,0,0,0,0,0,0,0,0,0]; +list heartsLinks = [0,0,0,0,0,0,0,0,0,0]; +list overlayLinks = [0,0,0,0,0,0,0,0,0,0]; -#if DEBUG_LOGS - #define dbg(msg) llOwnerSay(msg) - integer VERBOSE_LOGGING = FALSE; -#else - #define dbg(msg) - #define VERBOSE_LOGGING 0 -#endif -#define MSG_TOGGLE_VERBOSE_LOGS 9998 // Message to toggle verbose logging +// Discover all relevant prims by name at startup +discoverLinks() { + integer i; + integer total = llGetNumberOfPrims(); + + // Reset lists + profileLinks = [0,0,0,0,0,0,0,0,0,0]; + heartsLinks = [0,0,0,0,0,0,0,0,0,0]; + overlayLinks = [0,0,0,0,0,0,0,0,0,0]; + + for (i = 1; i <= total; i++) { + string name = llGetLinkName(i); + + if (name == "backboard:0:0") BACKGROUND_PRIM = i; + else if (name == "TitleAction:0:0") ACTIONS_PRIM = i; + else if (name == "Scoreboard:0:0") LINK_SCOREBOARD = i; + else { + // Pattern match: "type:row:col" + list parts = llParseString2List(name, [":"], []); + if (llGetListLength(parts) == 3) { + string type = llList2String(parts, 0); + integer row = (integer)llList2String(parts, 1); + integer col = (integer)llList2String(parts, 2); + integer index = (row * 2) + col; + + if (index >= 0 && index < 10) { + if (type == "profile") profileLinks = llListReplaceList(profileLinks, [i], index, index); + else if (type == "life") heartsLinks = llListReplaceList(heartsLinks, [i], index, index); + else if (type == "overlay") overlayLinks = llListReplaceList(overlayLinks, [i], index, index); + } + } + } + } + + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐Ÿ” Dynamic Discovery complete!"); + if (BACKGROUND_PRIM == -1) dbg("โš ๏ธ WARNING: Background prim ('backboard:0:0') not found!"); +} -// Message constants for link communication -// Scoreboard messages (from link 1 - controller) -#define MSG_GAME_STATUS 3001 -#define MSG_PLAYER_UPDATE 3002 -#define MSG_CLEAR_GAME 3003 -#define MSG_REMOVE_PLAYER 3004 -#define MSG_UPDATE_PERIL_PLAYER 3005 -#define MSG_UPDATE_WINNER 3006 - -// Leaderboard messages (to link 35) -#define MSG_GAME_WON 3010 -#define MSG_GAME_LOST 3011 -#define MSG_RESET_LEADERBOARD 3012 - -// Dice messages (to link 83) -#define MSG_DICE_ROLL 3020 -#define MSG_CLEAR_DICE 3021 - -// Prim indices (UPDATED after overlay prim insertion - overlays are links 2-11) -#define BACKGROUND_PRIM 13 // Now link 13 (backboard) -#define ACTIONS_PRIM 14 // Now link 14 (title/action) -#define FIRST_PROFILE_PRIM 15 // Profile prims start at 15 -#define LAST_PROFILE_PRIM 34 // Profile/life prims end at 34 -#define FIRST_OVERLAY_PRIM 2 // Overlay prims start at 2 -#define LAST_OVERLAY_PRIM 11 // Overlay prims end at 11 +// --- Leaderboard Configuration --- +#define LEADERBOARD_WIDTH 32 // Standard width for 4x8 character Furware grids (4 columns of 8) +#define MAX_LEADERBOARD_ENTRIES 11 // Fits within 12-row board (+1 for title) // Heart texture UUIDs - REPLACE WITH YOUR ACTUAL TEXTURE UUIDs #define TEXTURE_0_HEARTS "7d8ae121-e171-12ae-f5b6-7cc3c0395c7b" // 0 hearts (dead) @@ -92,16 +103,17 @@ key kvpWriteReq; string pendingSerialize; // Memory reporting function -reportMemoryUsage(string scriptName) { +integer reportMemoryUsage(string scriptName) { integer used = llGetUsedMemory(); integer free = llGetFreeMemory(); integer total = used + free; float percentUsed = ((float)used / (float)total) * 100.0; - llOwnerSay("๐Ÿง  [" + scriptName + "] Memory: " + + dbg("๐Ÿง  [" + scriptName + "] Memory: " + (string)used + " used, " + (string)free + " free (" + llGetSubString((string)percentUsed, 0, 4) + "% used)"); + return 0; } // Profile picture extraction constants @@ -111,7 +123,7 @@ integer profile_key_prefix_length; integer profile_img_prefix_length; // Reset background prim to proper black background -resetBackgroundPrim() { +integer resetBackgroundPrim() { // Reset main background prim (link 13) llSetLinkPrimitiveParamsFast(BACKGROUND_PRIM, [ PRIM_TEXTURE, ALL_SIDES, TEXTURE_BACKGROUND, <1,1,0>, <0,0,0>, 0.0, @@ -119,26 +131,24 @@ resetBackgroundPrim() { PRIM_TEXT, "", <0,0,0>, 0.0 // Remove any text ]); - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ–ค Reset background prim " + (string)BACKGROUND_PRIM + " to black"); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐Ÿ–ค Reset background prim " + (string)BACKGROUND_PRIM + " to black"); + return 0; } // Reset the scoreboard manager cube (link 12) to neutral state -resetManagerCube() { - llSetLinkPrimitiveParamsFast(12, [ +integer resetManagerCube() { + llSetLinkPrimitiveParamsFast(LINK_SCOREBOARD, [ PRIM_TEXTURE, ALL_SIDES, BLANK_TEXTURE, <1,1,0>, <0,0,0>, 0.0, PRIM_COLOR, ALL_SIDES, <0.0, 0.0, 0.0>, 1.0, // Black color to blend in PRIM_TEXT, "", <0,0,0>, 0.0 // Remove any text ]); - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“ฆ Reset manager cube (link 12) to black"); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐Ÿ“ฆ Reset manager cube (link 12) to black"); + return 0; } // Update actions prim with appropriate status texture -updateActionsPrim(string status) { +integer updateActionsPrim(string status) { string textureToUse = ""; if (status == "Elimination") { @@ -158,56 +168,50 @@ updateActionsPrim(string status) { } else if (status == "Title") { textureToUse = TEXTURE_TITLE; } else { - return; // Don't change texture for unrecognized statuses + return 0; // Don't change texture for unrecognized statuses } - // SAFETY CHECK - prevent overflow into leaderboard prims - if (ACTIONS_PRIM > LAST_PROFILE_PRIM) { - if (VERBOSE_LOGGING) { - llOwnerSay("โŒ ERROR: ACTIONS_PRIM (" + (string)ACTIONS_PRIM + ") exceeds safety boundary (" + (string)LAST_PROFILE_PRIM + ")"); - } - return; + // Replace with action prim texture update + if (ACTIONS_PRIM != -1) { + llSetLinkPrimitiveParamsFast(ACTIONS_PRIM, [ + PRIM_TEXTURE, ALL_SIDES, textureToUse, <1,1,0>, <0,0,0>, 0.0, + PRIM_TEXT, "", <0,0,0>, 0.0 // Remove existing floating text + ]); } - - llSetLinkPrimitiveParamsFast(ACTIONS_PRIM, [ - PRIM_TEXTURE, ALL_SIDES, textureToUse, <1,1,0>, <0,0,0>, 0.0, - PRIM_TEXT, "", <0,0,0>, 0.0 // Remove existing floating text - ]); + return 0; } // Load leaderboard data from Experience KVP database -loadLeaderboardData() { +integer loadLeaderboardData() { kvpReadReq = llReadKeyValue("Peril_LB_Top50"); - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ” Requesting global scoreboard from Experience database..."); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐Ÿ” Requesting global scoreboard from Experience database..."); + return 0; } // Save leaderboard data to Experience KVP database -saveLeaderboardData() { +integer saveLeaderboardData() { string serialized = ""; integer saveCount = llGetListLength(leaderboardNames); if (saveCount > 50) saveCount = 50; // Cap at Top 50 to fit safely in KVP 4Kb limit integer saveI; for (saveI = 0; saveI < saveCount; saveI++) { - string entry = llList2String(leaderboardNames, saveI) + ":" + + string save_entry = llList2String(leaderboardNames, saveI) + ":" + (string)llList2Integer(leaderboardWins, saveI) + ":" + (string)llList2Integer(leaderboardLosses, saveI); if (serialized != "") serialized += "|"; - serialized += entry; + serialized += save_entry; } pendingSerialize = serialized; kvpWriteReq = llUpdateKeyValue("Peril_LB_Top50", serialized, FALSE, ""); - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ’พ Synced " + (string)saveCount + " players to global scoreboard database."); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐Ÿ’พ Synced " + (string)saveCount + " players to global scoreboard database."); + return 0; } // Handle game won - update leaderboard -handleGameWon(string winnerName) { +integer handleGameWon(string winnerName) { integer idx = llListFindList(leaderboardNames, [winnerName]); if (idx == -1) { // New player @@ -222,10 +226,11 @@ handleGameWon(string winnerName) { saveLeaderboardData(); generateLeaderboardText(); + return 0; } // Handle game lost - update leaderboard -handleGameLost(string loserName) { +integer handleGameLost(string loserName) { integer lostIdx = llListFindList(leaderboardNames, [loserName]); if (lostIdx == -1) { // New player @@ -240,6 +245,7 @@ handleGameLost(string loserName) { saveLeaderboardData(); generateLeaderboardText(); + return 0; } // Get sorted leaderboard data @@ -253,7 +259,7 @@ list getSortedLeaderboard() { string paddedLosses; integer invertedLosses; string sortKey; - string entry; + string sorted_entry; list parts; list cleanedData = []; string winsStr; @@ -278,8 +284,8 @@ list getSortedLeaderboard() { // Format: "PaddedWins-PaddedInvertedLosses:Name:ActualWins:ActualLosses" // This sorts first by wins (descending), then by losses (ascending) for tiebreaker sortKey = paddedWins + "-" + paddedLosses; - entry = sortKey + ":" + sortName + ":" + (string)sortWins + ":" + (string)sortLosses; - sortedLbData += [entry]; + sorted_entry = sortKey + ":" + sortName + ":" + (string)sortWins + ":" + (string)sortLosses; + sortedLbData += [sorted_entry]; } // Sort by combined key (wins descending, losses ascending) - FALSE = descending order @@ -288,8 +294,8 @@ list getSortedLeaderboard() { // Clean up the format back to "Name:Wins:Losses" (skip padded wins in position 0) cleanedData = []; for (sortI = 0; sortI < llGetListLength(sortedLbData); sortI++) { - entry = llList2String(sortedLbData, sortI); - parts = llParseString2List(entry, [":"], []); + sorted_entry = llList2String(sortedLbData, sortI); + parts = llParseString2List(sorted_entry, [":"], []); if (llGetListLength(parts) >= 4) { // parts[0] = paddedWins (skip), parts[1] = name, parts[2] = actualWins, parts[3] = losses sortName = llList2String(parts, 1); @@ -303,11 +309,20 @@ list getSortedLeaderboard() { } // Generate leaderboard text and send to separate leaderboard object -generateLeaderboardText() { +integer generateLeaderboardText() { list sortedData = getSortedLeaderboard(); - // Build formatted leaderboard text (40 chars per line) - string leaderboardText = " TOP BATTLE RECORDS \n"; + // 1. Build Title Row (Centered) + string title = "TOP BATTLE RECORDS"; + integer titleLen = llStringLength(title); + integer titleMargin = (LEADERBOARD_WIDTH - titleLen) / 2; + if (titleMargin < 0) titleMargin = 0; + + string titleSpaces = ""; + integer ts; + for (ts = 0; ts < titleMargin; ts++) titleSpaces += " "; + + string leaderboardText = titleSpaces + title + "\n"; integer genI; string playerData; @@ -325,8 +340,8 @@ generateLeaderboardText() { string line; integer actualPlayers; - // Add actual player data (up to 11 players) - for (genI = 0; genI < llGetListLength(sortedData) && genI < 11; genI++) { + // Add actual player data (up to MAX_LEADERBOARD_ENTRIES) + for (genI = 0; genI < llGetListLength(sortedData) && genI < MAX_LEADERBOARD_ENTRIES; genI++) { playerData = llList2String(sortedData, genI); playerParts = llParseString2List(playerData, [":"], []); @@ -335,68 +350,70 @@ generateLeaderboardText() { genWins = llList2String(playerParts, 1); genLosses = llList2String(playerParts, 2); - // Truncate long names to fit better spacing + // [Rank(4) + Name(14) + Gaps + Stats(10) = 32 chars total] if (llStringLength(playerName) > 12) { playerName = llGetSubString(playerName, 0, 9) + "..."; } - // Format rank + // Format rank string: "01. " through "10. " (always 4 chars) rankNumber = genI + 1; - rank = (string)rankNumber; - if (rankNumber < 10) rank = " " + rank; // Pad single digits + rank = (string)rankNumber + ". "; + if (rankNumber < 10) rank = "0" + rank; - // Create well-spaced line to fill full 40-character width - leftPart = rank + ". " + playerName; - rightPart = "W:" + genWins + "/L:" + genLosses; + // Format stats with fixed-width padding to align columns + // Standardizing to 10 characters: "W: 99/L: 9" + string sWins = (string)genWins; + while (llStringLength(sWins) < 3) sWins = " " + sWins; + string sLosses = (string)genLosses; + while (llStringLength(sLosses) < 2) sLosses = " " + sLosses; - // Calculate spaces needed to fill exactly 40 characters - spaceNeeded = 40 - llStringLength(leftPart) - llStringLength(rightPart); - if (spaceNeeded < 1) spaceNeeded = 1; // At least 1 space + leftPart = rank + playerName; + rightPart = "W:" + sWins + "/L:" + sLosses; // Exactly 10 characters + + // Push rightPart to column 32 + spaceNeeded = LEADERBOARD_WIDTH - llStringLength(leftPart) - llStringLength(rightPart); + if (spaceNeeded < 1) spaceNeeded = 1; spacer = ""; - for (s = 0; s < spaceNeeded; s++) { - spacer += " "; - } + for (s = 0; s < spaceNeeded; s++) spacer += " "; line = leftPart + spacer + rightPart; leaderboardText += line + "\n"; } } - // Fill remaining positions with placeholders to always show 11 positions + // Fill remaining positions with placeholders actualPlayers = llGetListLength(sortedData); - for (genI = actualPlayers; genI < 11; genI++) { + for (genI = actualPlayers; genI < MAX_LEADERBOARD_ENTRIES; genI++) { rankNumber = genI + 1; - rank = (string)rankNumber; - if (rankNumber < 10) rank = " " + rank; // Pad single digits + rank = (string)rankNumber + ". "; + if (rankNumber < 10) rank = "0" + rank; - // Create placeholder line to fill full 40-character width - leftPart = rank + ". --------"; - rightPart = "W:0/L:0"; + // Use EXACT same formatting as players so columns align! + leftPart = rank + "----------"; + rightPart = "W: 0/L: 0"; // Matches 10-char fixed width - // Calculate spaces needed to fill exactly 40 characters - spaceNeeded = 40 - llStringLength(leftPart) - llStringLength(rightPart); - if (spaceNeeded < 1) spaceNeeded = 1; // At least 1 space + spaceNeeded = LEADERBOARD_WIDTH - llStringLength(leftPart) - llStringLength(rightPart); + if (spaceNeeded < 1) spaceNeeded = 1; spacer = ""; - for (s = 0; s < spaceNeeded; s++) { - spacer += " "; - } + for (s = 0; s < spaceNeeded; s++) spacer += " "; line = leftPart + spacer + rightPart; leaderboardText += line + "\n"; } // Send formatted text to leaderboard bridge - llMessageLinked(35, MSG_RESET_LEADERBOARD, "FORMATTED_TEXT|" + leaderboardText, NULL_KEY); + llMessageLinked(LINK_LEADERBOARD_BRIDGE, MSG_RESET_LEADERBOARD, "FORMATTED_TEXT|" + leaderboardText, NULL_KEY); + return 0; } // Reset leaderboard data (SECURED for Creator Only) -resetLeaderboard() { +integer resetLeaderboard() { // PROTECT THE GLOBAL DATABASE! if (llGetOwner() != llGetCreator()) { llOwnerSay("โŒ Access Denied: Only the creator of Peril Dice can wipe the Global Scoreboard!"); - return; + return 0; } leaderboardNames = []; @@ -405,10 +422,11 @@ resetLeaderboard() { pendingSerialize = ""; kvpWriteReq = llUpdateKeyValue("Peril_LB_Top50", "", FALSE, ""); generateLeaderboardText(); // Send empty leaderboard immediately + return 0; } // Clear all current players from the scoreboard -clearAllPlayers() { +integer clearAllPlayers() { // Clear ONLY current game player data lists (NOT leaderboard data) playerNames = []; playerLives = []; @@ -418,48 +436,49 @@ clearAllPlayers() { httpRequests = []; profileRequests = []; - // Reset all player prims to default state + // Variable declarations for the loop integer clearI; + integer clearProfilePrimIndex; + integer clearHeartsPrimIndex; + integer clearOverlayPrimIndex; + + // Reset all player prims to default state for (clearI = 0; clearI < 10; clearI++) { - integer clearProfilePrimIndex = getProfilePrimLink(clearI); - integer clearHeartsPrimIndex = getHeartsPrimLink(clearI); - integer clearOverlayPrimIndex = getOverlayPrimLink(clearI); + clearProfilePrimIndex = llList2Integer(profileLinks, clearI); + clearHeartsPrimIndex = llList2Integer(heartsLinks, clearI); + clearOverlayPrimIndex = llList2Integer(overlayLinks, clearI); - // SAFETY CHECK - prevent overflow into leaderboard prims - if (clearProfilePrimIndex < FIRST_PROFILE_PRIM || clearProfilePrimIndex > LAST_PROFILE_PRIM) { - if (VERBOSE_LOGGING) { - llOwnerSay("โŒ ERROR: Player prim index out of range! Profile: " + (string)clearProfilePrimIndex); - } - return; // Don't modify prims outside our range! + // SAFETY CHECK - use if block instead of continue for maximum compatibility + if (clearProfilePrimIndex > 0) { + // Reset profile prim to default and clear any glow + llSetLinkPrimitiveParamsFast(clearProfilePrimIndex, [ + PRIM_TEXTURE, ALL_SIDES, TEXTURE_DEFAULT_PROFILE, <1,1,0>, <0,0,0>, 0.0, + PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, + PRIM_TEXT, "", <1,1,1>, 0.0, + PRIM_GLOW, ALL_SIDES, 0.0 // Explicitly clear glow + ]); + + // Reset hearts prim to 3 hearts and clear any glow + llSetLinkPrimitiveParamsFast(clearHeartsPrimIndex, [ + PRIM_TEXTURE, ALL_SIDES, TEXTURE_3_HEARTS, <1,1,0>, <0,0,0>, 0.0, + PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, + PRIM_TEXT, "", <1,1,1>, 0.0, + PRIM_GLOW, ALL_SIDES, 0.0 // Explicitly clear glow + ]); + + // Reset overlay prim to transparent + llSetLinkPrimitiveParamsFast(clearOverlayPrimIndex, [ + PRIM_TEXTURE, ALL_SIDES, BLANK_TEXTURE, <1,1,0>, <0,0,0>, 0.0, + PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 0.0, // Fully transparent + PRIM_TEXT, "", <1,1,1>, 0.0 + ]); } - - // Reset profile prim to default and clear any glow - llSetLinkPrimitiveParamsFast(clearProfilePrimIndex, [ - PRIM_TEXTURE, ALL_SIDES, TEXTURE_DEFAULT_PROFILE, <1,1,0>, <0,0,0>, 0.0, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, - PRIM_TEXT, "", <1,1,1>, 0.0, - PRIM_GLOW, ALL_SIDES, 0.0 // Explicitly clear glow - ]); - - // Reset hearts prim to 3 hearts and clear any glow - llSetLinkPrimitiveParamsFast(clearHeartsPrimIndex, [ - PRIM_TEXTURE, ALL_SIDES, TEXTURE_3_HEARTS, <1,1,0>, <0,0,0>, 0.0, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, - PRIM_TEXT, "", <1,1,1>, 0.0, - PRIM_GLOW, ALL_SIDES, 0.0 // Explicitly clear glow - ]); - - // Reset overlay prim to transparent - llSetLinkPrimitiveParamsFast(clearOverlayPrimIndex, [ - PRIM_TEXTURE, ALL_SIDES, BLANK_TEXTURE, <1,1,0>, <0,0,0>, 0.0, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 0.0, // Fully transparent - PRIM_TEXT, "", <1,1,1>, 0.0 - ]); } + return 0; } // Remove a single player from the scoreboard display -removePlayer(string playerName) { +integer removePlayer(string playerName) { integer removeIdx = llListFindList(playerNames, [playerName]); if (removeIdx != -1) { // Remove player from internal lists @@ -470,104 +489,49 @@ removePlayer(string playerName) { // Refresh the entire display to shift remaining players up refreshPlayerDisplay(); - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“Š Removed " + playerName + " from the scoreboard and shifted remaining players."); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐Ÿ“Š Removed " + playerName + " from the scoreboard and shifted remaining players."); } + return 0; } // Refresh the entire player display - shows all current players in order -refreshPlayerDisplay() { - // Clear only unused player slots (beyond current player count) +integer refreshPlayerDisplay() { integer numActivePlayers = llGetListLength(playerNames); integer refreshI; + integer refreshProfilePrimIndex = 0; + integer refreshHeartsPrimIndex = 0; + integer refreshOverlayPrimIndex = 0; + + // Position-specific variables + string r_name; + integer r_lives; + string r_profileTexture; + integer r_profileIdx; + integer r_heartsIdx; + integer r_overlayIdx; // Only reset prims for unused slots (avoid flickering for active players) for (refreshI = numActivePlayers; refreshI < 10; refreshI++) { - integer refreshProfilePrimIndex = getProfilePrimLink(refreshI); - integer refreshHeartsPrimIndex = getHeartsPrimLink(refreshI); - integer refreshOverlayPrimIndex = getOverlayPrimLink(refreshI); + refreshProfilePrimIndex = llList2Integer(profileLinks, refreshI); + refreshHeartsPrimIndex = llList2Integer(heartsLinks, refreshI); + refreshOverlayPrimIndex = llList2Integer(overlayLinks, refreshI); - // SAFETY CHECK - if (refreshProfilePrimIndex < FIRST_PROFILE_PRIM || refreshProfilePrimIndex > LAST_PROFILE_PRIM) { - return; // Stop if we'd go out of bounds - } - - // Reset to default only for unused slots - llSetLinkPrimitiveParamsFast(refreshProfilePrimIndex, [ - PRIM_TEXTURE, ALL_SIDES, TEXTURE_DEFAULT_PROFILE, <1,1,0>, <0,0,0>, 0.0, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, - PRIM_TEXT, "", <1,1,1>, 0.0 - ]); - - llSetLinkPrimitiveParamsFast(refreshHeartsPrimIndex, [ - PRIM_TEXTURE, ALL_SIDES, TEXTURE_3_HEARTS, <1,1,0>, <0,0,0>, 0.0, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, - PRIM_TEXT, "", <1,1,1>, 0.0 - ]); - - // Hide overlay prim (make transparent) - llSetLinkPrimitiveParamsFast(refreshOverlayPrimIndex, [ - PRIM_TEXTURE, ALL_SIDES, BLANK_TEXTURE, <1,1,0>, <0,0,0>, 0.0, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 0.0, // Fully transparent - PRIM_TEXT, "", <1,1,1>, 0.0 - ]); - } - - // Now display all current players in their new positions - for (refreshI = 0; refreshI < llGetListLength(playerNames); refreshI++) { - string name = llList2String(playerNames, refreshI); - integer lives = llList2Integer(playerLives, refreshI); - string profileTexture = llList2String(playerProfiles, refreshI); - - // Calculate prim indices for this position - integer refreshProfileIdx = getProfilePrimLink(refreshI); - integer refreshHeartsIdx = getHeartsPrimLink(refreshI); - integer refreshOverlayIdx = getOverlayPrimLink(refreshI); - - // SAFETY CHECK - if (refreshProfileIdx < FIRST_PROFILE_PRIM || refreshProfileIdx > LAST_PROFILE_PRIM) { - if (VERBOSE_LOGGING) { - llOwnerSay("โš ๏ธ Warning: Player " + name + " cannot be displayed - out of prim range"); - } - return; - } - - // Determine profile texture to use - don't replace with X, keep profile - if (isBot(name)) { - profileTexture = TEXTURE_BOT_PROFILE; - } else if (profileTexture == "" || profileTexture == "00000000-0000-0000-0000-000000000000") { - profileTexture = TEXTURE_DEFAULT_PROFILE; - } - // Note: If we have a cached profile texture, use it; otherwise use default for now - // HTTP requests for new profile pictures will update later - - // Set profile texture (always normal coloring now) - llSetLinkPrimitiveParamsFast(refreshProfileIdx, [ - PRIM_TEXTURE, ALL_SIDES, profileTexture, <1,1,0>, <0,0,0>, 0.0, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, - PRIM_TEXT, "", <1,1,1>, 0.0 - ]); - - // Set hearts texture - string heartTexture = getHeartTexture(lives); - llSetLinkPrimitiveParamsFast(refreshHeartsIdx, [ - PRIM_TEXTURE, ALL_SIDES, heartTexture, <1,1,0>, <0,0,0>, 0.0, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, - PRIM_TEXT, "", <1,1,1>, 0.0 - ]); - - // Control overlay prim - show red X if eliminated, hide if alive - if (lives <= 0) { - // Show red X overlay for eliminated player - llSetLinkPrimitiveParamsFast(refreshOverlayIdx, [ - PRIM_TEXTURE, ALL_SIDES, TEXTURE_ELIMINATED_X, <1,1,0>, <0,0,0>, 0.0, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, // Fully visible + if (refreshProfilePrimIndex > 0) { + // Reset to default only for unused slots + llSetLinkPrimitiveParamsFast(refreshProfilePrimIndex, [ + PRIM_TEXTURE, ALL_SIDES, TEXTURE_DEFAULT_PROFILE, <1,1,0>, <0,0,0>, 0.0, + PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, PRIM_TEXT, "", <1,1,1>, 0.0 ]); - } else { - // Hide overlay for alive player - llSetLinkPrimitiveParamsFast(refreshOverlayIdx, [ + + llSetLinkPrimitiveParamsFast(refreshHeartsPrimIndex, [ + PRIM_TEXTURE, ALL_SIDES, TEXTURE_3_HEARTS, <1,1,0>, <0,0,0>, 0.0, + PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, + PRIM_TEXT, "", <1,1,1>, 0.0 + ]); + + // Hide overlay prim (make transparent) + llSetLinkPrimitiveParamsFast(refreshOverlayPrimIndex, [ PRIM_TEXTURE, ALL_SIDES, BLANK_TEXTURE, <1,1,0>, <0,0,0>, 0.0, PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 0.0, // Fully transparent PRIM_TEXT, "", <1,1,1>, 0.0 @@ -575,8 +539,60 @@ refreshPlayerDisplay() { } } + // Now display all current players in their new positions + for (refreshI = 0; refreshI < numActivePlayers; refreshI++) { + r_name = llList2String(playerNames, refreshI); + r_lives = llList2Integer(playerLives, refreshI); + r_profileTexture = llList2String(playerProfiles, refreshI); + + // Calculate prim indices for this position + r_profileIdx = llList2Integer(profileLinks, refreshI); + r_heartsIdx = llList2Integer(heartsLinks, refreshI); + r_overlayIdx = llList2Integer(overlayLinks, refreshI); + + if (r_profileIdx > 0) { + // Determine profile texture to use + if (isBot(r_name)) { + r_profileTexture = TEXTURE_BOT_PROFILE; + } else if (r_profileTexture == "" || r_profileTexture == "00000000-0000-0000-0000-000000000000") { + r_profileTexture = TEXTURE_DEFAULT_PROFILE; + } + + // Set profile texture + llSetLinkPrimitiveParamsFast(r_profileIdx, [ + PRIM_TEXTURE, ALL_SIDES, r_profileTexture, <1,1,0>, <0,0,0>, 0.0, + PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, + PRIM_TEXT, "", <1,1,1>, 0.0 + ]); + + // Set hearts texture + string heartTexture = getHeartTexture(r_lives); + llSetLinkPrimitiveParamsFast(r_heartsIdx, [ + PRIM_TEXTURE, ALL_SIDES, heartTexture, <1,1,0>, <0,0,0>, 0.0, + PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, + PRIM_TEXT, "", <1,1,1>, 0.0 + ]); + + // Control overlay prim - show red X if eliminated, hide if alive + if (r_lives <= 0) { + llSetLinkPrimitiveParamsFast(r_overlayIdx, [ + PRIM_TEXTURE, ALL_SIDES, TEXTURE_ELIMINATED_X, <1,1,0>, <0,0,0>, 0.0, + PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, + PRIM_TEXT, "", <1,1,1>, 0.0 + ]); + } else { + llSetLinkPrimitiveParamsFast(r_overlayIdx, [ + PRIM_TEXTURE, ALL_SIDES, BLANK_TEXTURE, <1,1,0>, <0,0,0>, 0.0, + PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 0.0, + PRIM_TEXT, "", <1,1,1>, 0.0 + ]); + } + } + } + // Update player glow effects after refreshing all players updatePlayerGlowEffects(); + return 0; } // Check if a player name indicates it's a bot @@ -585,66 +601,10 @@ integer isBot(string playerName) { } // Map player index to profile prim link number -integer getProfilePrimLink(integer playerIndex) { - // From link scan: Row 1 (top) has profile 31,33; Row 2 has 27,29; etc. - // Player index 0,1 = row 1; 2,3 = row 2; 4,5 = row 3; etc. - // Each row has 4 prims: profile col1, hearts col1, profile col2, hearts col2 - - integer profileRow = playerIndex / 2; // Which row (0=top, 1=second, etc) - integer profileCol = playerIndex % 2; // Which column (0=left, 1=right) - - // Base link for each row (profile col1): 31, 27, 23, 19, 15 - integer profileBaseLink = 31 - (profileRow * 4); - - if (profileCol == 0) { - // Column 1 (left): profile prim - return profileBaseLink; - } else { - // Column 2 (right): profile prim (skip hearts prim) - return profileBaseLink + 2; - } -} - -// Map player index to hearts prim link number -integer getHeartsPrimLink(integer playerIndex) { - // Hearts prims are adjacent to profile prims - // Row 1: profile 31->hearts 32, profile 33->hearts 34 - // Row 2: profile 27->hearts 28, profile 29->hearts 30, etc. - - integer heartsRow = playerIndex / 2; // Which row (0=top, 1=second, etc) - integer heartsCol = playerIndex % 2; // Which column (0=left, 1=right) - - // Base link for each row (hearts col1): 32, 28, 24, 20, 16 - integer heartsBaseLink = 32 - (heartsRow * 4); - - if (heartsCol == 0) { - // Column 1 (left): hearts prim - return heartsBaseLink; - } else { - // Column 2 (right): hearts prim (skip profile prim) - return heartsBaseLink + 2; - } -} - -// Map player index to overlay prim link number -integer getOverlayPrimLink(integer playerIndex) { - // Overlay prims arrangement: row 1 col 1=3, row 1 col 2=2, row 2 col 1=5, row 2 col 2=4, etc. - // Pattern: odd rows (0,2,4,6,8) start at 3,7,11 for col1 and 2,6,10 for col2 - // Wait, let me check the actual pattern from the scan... - // Link 2: overlay row 1 col 2, Link 3: overlay row 1 col 1 - // Link 4: overlay row 2 col 2, Link 5: overlay row 2 col 1 - // So pattern is: row N col 1 = 3 + (row * 2), row N col 2 = 2 + (row * 2) - integer overlayRow = playerIndex / 2; - integer overlayCol = playerIndex % 2; - - if (overlayCol == 0) { - // Column 1: 3, 5, 7, 9, 11 - return 3 + (overlayRow * 2); - } else { - // Column 2: 2, 4, 6, 8, 10 - return 2 + (overlayRow * 2); - } -} +// Functions relocated to avoid hardcoded math +integer getProfilePrimLink(integer playerIndex) { return llList2Integer(profileLinks, playerIndex); } +integer getHeartsPrimLink(integer playerIndex) { return llList2Integer(heartsLinks, playerIndex); } +integer getOverlayPrimLink(integer playerIndex) { return llList2Integer(overlayLinks, playerIndex); } string getHeartTexture(integer lives) { if (lives <= 0) return TEXTURE_0_HEARTS; @@ -654,19 +614,23 @@ string getHeartTexture(integer lives) { } // Update all glow effects (peril and winner) -updatePlayerGlowEffects() { +integer updatePlayerGlowEffects() { // First, remove glow and reset tint from all players integer glowI; + integer glowProfilePrimIndex; + integer glowHeartsPrimIndex; + for (glowI = 0; glowI < llGetListLength(playerNames); glowI++) { - integer glowProfilePrimIndex = getProfilePrimLink(glowI); - integer glowHeartsPrimIndex = getHeartsPrimLink(glowI); + glowProfilePrimIndex = llList2Integer(profileLinks, glowI); + glowHeartsPrimIndex = llList2Integer(heartsLinks, glowI); - // SAFETY CHECK - if (glowProfilePrimIndex >= FIRST_PROFILE_PRIM && glowProfilePrimIndex <= LAST_PROFILE_PRIM) { + if (glowProfilePrimIndex > 0) { llSetLinkPrimitiveParamsFast(glowProfilePrimIndex, [ PRIM_GLOW, ALL_SIDES, 0.0, PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0 ]); + } + if (glowHeartsPrimIndex > 0) { llSetLinkPrimitiveParamsFast(glowHeartsPrimIndex, [ PRIM_GLOW, ALL_SIDES, 0.0, PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0 @@ -681,21 +645,23 @@ updatePlayerGlowEffects() { integer winnerProfilePrimIndex = getProfilePrimLink(winnerIndex); integer winnerHeartsPrimIndex = getHeartsPrimLink(winnerIndex); - // SAFETY CHECK - if (winnerProfilePrimIndex >= FIRST_PROFILE_PRIM && winnerProfilePrimIndex <= LAST_PROFILE_PRIM) { + // Check if the prim exists (discovered index > 0) + if (winnerProfilePrimIndex > 0) { // Add green glow + green tint to winner llSetLinkPrimitiveParamsFast(winnerProfilePrimIndex, [ PRIM_GLOW, ALL_SIDES, 0.3, PRIM_COLOR, ALL_SIDES, <0.0, 1.0, 0.0>, 1.0 ]); - llSetLinkPrimitiveParamsFast(winnerHeartsPrimIndex, [ - PRIM_GLOW, ALL_SIDES, 0.3, - PRIM_COLOR, ALL_SIDES, <0.0, 1.0, 0.0>, 1.0 - ]); - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ† Added green glow to winner: " + currentWinner); + // Also add glow to hearts if found + if (winnerHeartsPrimIndex > 0) { + llSetLinkPrimitiveParamsFast(winnerHeartsPrimIndex, [ + PRIM_GLOW, ALL_SIDES, 0.3, + PRIM_COLOR, ALL_SIDES, <0.0, 1.0, 0.0>, 1.0 + ]); } + + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐Ÿ† Added green glow to winner: " + currentWinner); } } } @@ -706,33 +672,37 @@ updatePlayerGlowEffects() { integer perilProfilePrimIndex = getProfilePrimLink(perilIndex); integer perilHeartsPrimIndex = getHeartsPrimLink(perilIndex); - // SAFETY CHECK - if (perilProfilePrimIndex >= FIRST_PROFILE_PRIM && perilProfilePrimIndex <= LAST_PROFILE_PRIM) { + // Check if the prim exists (discovered index > 0) + if (perilProfilePrimIndex > 0) { // Add yellow glow + yellow tint to peril player llSetLinkPrimitiveParamsFast(perilProfilePrimIndex, [ PRIM_GLOW, ALL_SIDES, 0.2, PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 0.0>, 1.0 ]); - llSetLinkPrimitiveParamsFast(perilHeartsPrimIndex, [ - PRIM_GLOW, ALL_SIDES, 0.2, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 0.0>, 1.0 - ]); - if (VERBOSE_LOGGING) { - llOwnerSay("โœจ Added yellow glow to peril player: " + currentPerilPlayer); + // Also add glow to hearts if found + if (perilHeartsPrimIndex > 0) { + llSetLinkPrimitiveParamsFast(perilHeartsPrimIndex, [ + PRIM_GLOW, ALL_SIDES, 0.2, + PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 0.0>, 1.0 + ]); } + + dbg("๐Ÿ“ˆ [Scoreboard Manager] โœจ Added yellow glow to peril player: " + currentPerilPlayer); } } } + return 0; } // Backward compatibility wrapper -updatePerilPlayerGlow() { +integer updatePerilPlayerGlow() { updatePlayerGlowEffects(); + return 0; } // Update player display on the grid -updatePlayerDisplay(string playerName, integer lives, string profileUUID) { +integer updatePlayerDisplay(string playerName, integer lives, string profileUUID) { // Find existing player or add new one integer playerIndex = llListFindList(playerNames, [playerName]); @@ -740,10 +710,8 @@ updatePlayerDisplay(string playerName, integer lives, string profileUUID) { // New player - find next available slot playerIndex = llGetListLength(playerNames); if (playerIndex >= 10) { - if (VERBOSE_LOGGING) { - llOwnerSay("WARNING: Maximum 10 players supported, ignoring: " + playerName); - } - return; + dbg("๐Ÿ“ˆ [Scoreboard Manager] โš ๏ธ WARNING: Maximum 10 players supported, ignoring: " + playerName); + return 0; } // Add to tracking lists @@ -777,28 +745,18 @@ updatePlayerDisplay(string playerName, integer lives, string profileUUID) { } // Update the physical prims for this player - integer updateProfilePrimIndex = getProfilePrimLink(playerIndex); - integer updateHeartsPrimIndex = getHeartsPrimLink(playerIndex); - integer updateOverlayPrimIndex = getOverlayPrimLink(playerIndex); + integer updateProfilePrimIndex = llList2Integer(profileLinks, playerIndex); + integer updateHeartsPrimIndex = llList2Integer(heartsLinks, playerIndex); + integer updateOverlayPrimIndex = llList2Integer(overlayLinks, playerIndex); - // SAFETY CHECK - prevent overflow - if (updateProfilePrimIndex < FIRST_PROFILE_PRIM || updateProfilePrimIndex > LAST_PROFILE_PRIM || - updateOverlayPrimIndex < FIRST_OVERLAY_PRIM || updateOverlayPrimIndex > LAST_OVERLAY_PRIM) { - if (VERBOSE_LOGGING) { - llOwnerSay("โŒ ERROR: Player prim index out of range! Profile: " + (string)updateProfilePrimIndex + - ", Overlay: " + (string)updateOverlayPrimIndex + ", Player: " + (string)playerIndex); - } - return; // Don't modify prims outside our range! - } + if (updateProfilePrimIndex <= 0) return 0; // Update profile prim texture - show red X if eliminated, otherwise show profile string updateProfileTexture; if (lives <= 0) { // Player is eliminated - keep profile picture but show red X on overlay prim - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ’€ Scoreboard: Showing elimination X overlay for " + playerName); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐Ÿ’€ Scoreboard: Showing elimination X overlay for " + playerName); // CRITICAL: Still need to set updateProfileTexture for eliminated players if (isBot(playerName)) { @@ -844,9 +802,7 @@ updatePlayerDisplay(string playerName, integer lives, string profileUUID) { // } } else if (updateProfileTexture == "" || updateProfileTexture == "00000000-0000-0000-0000-000000000000") { updateProfileTexture = TEXTURE_DEFAULT_PROFILE; - if (VERBOSE_LOGGING) { - llOwnerSay("โ“ [DEBUG] " + playerName + " has empty/null UUID, using default texture"); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] โ“ [DEBUG] " + playerName + " has empty/null UUID, using default texture"); } else { // CRITICAL: Set default texture FIRST, then request profile picture updateProfileTexture = TEXTURE_DEFAULT_PROFILE; @@ -924,19 +880,19 @@ updatePlayerDisplay(string playerName, integer lives, string profileUUID) { // Update player glow effects after display changes updatePlayerGlowEffects(); + return 0; } default { on_rez(integer start_param) { - llResetScript(); // Fixes Z-fighting and alignment glitches on rez + discoverLinks(); + llResetScript(); } state_entry() { - llListen(1, "", llGetOwner(), ""); // Listen on channel 1 for local owner testing commands + discoverLinks(); + llListen(1, "", llGetOwner(), ""); reportMemoryUsage("Scoreboard Manager"); - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“ˆ Scoreboard Manager ready! (Linkset Version)"); - llOwnerSay("๐Ÿ“ˆ Managing prims " + (string)FIRST_PROFILE_PRIM + "-" + (string)LAST_PROFILE_PRIM); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐Ÿ“ˆ Managing profile and heart prims via dynamic discovery."); // Initialize profile picture extraction constants profile_key_prefix_length = llStringLength(profile_key_prefix); @@ -960,28 +916,13 @@ default { currentWinner = ""; updatePlayerGlowEffects(); - if (VERBOSE_LOGGING) { - llOwnerSay("โœ… Linkset communication active - no discovery needed!"); - llOwnerSay("๐Ÿ“Š Loaded " + (string)llGetListLength(leaderboardNames) + " players from leaderboard data"); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] โœ… Linkset communication active - no discovery needed!"); + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐Ÿ“Š Loaded " + (string)llGetListLength(leaderboardNames) + " players from leaderboard data"); } link_message(integer sender, integer num, string str, key id) { - // Handle verbose logging toggle -#if DEBUG_LOGS - 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; - } -#endif - // Only listen to messages from the main controller (link 1) - if (sender != 1) return; + if (sender != LINK_CONTROLLER) return; if (num == MSG_GAME_STATUS) { updateActionsPrim(str); @@ -994,8 +935,7 @@ default { string profileUUID = llList2String(parts, 2); // Debug: Show what data scoreboard received - DISABLED to save memory - // if (VERBOSE_LOGGING) { - // llOwnerSay("๐Ÿ“ˆ Scoreboard received update: " + playerName + " has " + (string)lives + " hearts"); + dbg("๐Ÿ“ˆ Scoreboard received update: " + playerName + " has " + (string)lives + " hearts"); // } updatePlayerDisplay(playerName, lives, profileUUID); @@ -1035,9 +975,7 @@ default { currentPerilPlayer = ""; } - if (VERBOSE_LOGGING && oldPeril != currentPerilPlayer) { - llOwnerSay("๐ŸŽฏ Peril player changed from '" + oldPeril + "' to '" + currentPerilPlayer + "'"); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐ŸŽฏ Peril player changed from '" + oldPeril + "' to '" + currentPerilPlayer + "'"); // Update glow effects updatePlayerGlowEffects(); @@ -1050,9 +988,7 @@ default { currentWinner = ""; } - if (VERBOSE_LOGGING && oldWinner != currentWinner) { - llOwnerSay("๐Ÿ† Winner changed from '" + oldWinner + "' to '" + currentWinner + "'"); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐Ÿ† Winner changed from '" + oldWinner + "' to '" + currentWinner + "'"); // Update glow effects - winner glow overrides peril glow updatePlayerGlowEffects(); @@ -1104,8 +1040,8 @@ default { // Update the physical prim using correct mapping integer profilePrimIndex = getProfilePrimLink(playerIndex); - // SAFETY CHECK - if (profilePrimIndex >= FIRST_PROFILE_PRIM && profilePrimIndex <= LAST_PROFILE_PRIM) { + // Safety check: Ensure the prim link was found + if (profilePrimIndex > 0) { // AGGRESSIVE VISUAL UPDATE: Force immediate rendering llSetLinkPrimitiveParamsFast(profilePrimIndex, [ PRIM_TEXTURE, ALL_SIDES, profileUUID, <1,1,0>, <0,0,0>, 0.0, @@ -1119,14 +1055,10 @@ default { PRIM_GLOW, ALL_SIDES, 0.0 // Remove glow ]); - if (VERBOSE_LOGGING) { - string playerName = llList2String(playerNames, playerIndex); - llOwnerSay("๐Ÿ–ผ๏ธ Updated profile picture for " + playerName + " on prim " + (string)profilePrimIndex + " with forced refresh"); - } + string playerName = llList2String(playerNames, playerIndex); + dbg("๐Ÿ“ˆ [Scoreboard Manager] ๐Ÿ–ผ๏ธ Updated profile picture for " + playerName + " on prim " + (string)profilePrimIndex + " with forced refresh"); } else { - if (VERBOSE_LOGGING) { - llOwnerSay("โŒ ERROR: HTTP response tried to update invalid prim " + (string)profilePrimIndex + " for player " + (string)playerIndex); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] โŒ ERROR: HTTP response tried to update invalid prim " + (string)profilePrimIndex + " for player " + (string)playerIndex); } } } @@ -1134,8 +1066,9 @@ default { } dataserver(key queryid, string data) { + integer comma; if (queryid == kvpReadReq) { - integer comma = llSubStringIndex(data, ","); + comma = llSubStringIndex(data, ","); if (comma != -1) { string status = llGetSubString(data, 0, comma - 1); string val = llGetSubString(data, comma + 1, -1); @@ -1148,62 +1081,43 @@ default { list entries = llParseString2List(val, ["|"], []); integer i; for (i = 0; i < llGetListLength(entries); i++) { - string entry = llList2String(entries, i); - list parts = llParseString2List(entry, [":"], []); + string data_entry = llList2String(entries, i); + list parts = llParseString2List(data_entry, [":"], []); if (llGetListLength(parts) >= 3) { leaderboardNames += [llList2String(parts, 0)]; leaderboardWins += [(integer)llList2String(parts, 1)]; leaderboardLosses += [(integer)llList2String(parts, 2)]; } } - if (VERBOSE_LOGGING) { - llOwnerSay("โœ… Loaded global scoreboard containing " + (string)llGetListLength(leaderboardNames) + " players!"); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] โœ… Loaded global scoreboard containing " + (string)llGetListLength(leaderboardNames) + " players!"); } else if (status == "3") { // XP_ERROR_KEY_NOT_FOUND leaderboardNames = []; leaderboardWins = []; leaderboardLosses = []; - if (VERBOSE_LOGGING) { - llOwnerSay("โ„น๏ธ Global scoreboard is currently empty (database fresh)."); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] โ„น๏ธ Global scoreboard is currently empty (database fresh)."); } else { - if (VERBOSE_LOGGING) { - llOwnerSay("โš ๏ธ Failed to load global scoreboard (Status: " + status + ")"); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] โš ๏ธ Failed to load global scoreboard (Status: " + status + ")"); } } // Generate board with downloaded or empty data generateLeaderboardText(); } else if (queryid == kvpWriteReq) { - integer comma = llSubStringIndex(data, ","); + comma = llSubStringIndex(data, ","); if (comma != -1) { string status = llGetSubString(data, 0, comma - 1); if (status == "3") { // Update failed because key didn't exist yet! Create it. llCreateKeyValue("Peril_LB_Top50", pendingSerialize); } else if (status == "1") { - if (VERBOSE_LOGGING) { - llOwnerSay("โœ… Global scoreboard synced successfully."); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] โœ… Global scoreboard synced successfully."); } } } } - touch_start(integer total_number) { - // Owner can still access some debug functions by touching scoreboard - if (llDetectedKey(0) == llGetOwner()) { - llOwnerSay("๐Ÿ“Š Scoreboard Status:"); - llOwnerSay(" Active players: " + (string)llGetListLength(playerNames)); - llOwnerSay(" Leaderboard entries: " + (string)llGetListLength(leaderboardNames)); - llOwnerSay(" HTTP requests pending: " + (string)(llGetListLength(httpRequests) / 2)); - } - } timer() { - if (VERBOSE_LOGGING) { - llOwnerSay("โฑ๏ธ Auto-refreshing Global Scoreboard Data..."); - } + dbg("๐Ÿ“ˆ [Scoreboard Manager] โฑ๏ธ Auto-refreshing Global Scoreboard Data..."); loadLeaderboardData(); } @@ -1212,9 +1126,10 @@ default { if (msg == "testx") { llOwnerSay("Testing X overlays on all 10 player slots..."); integer i; + integer overlayIdx; for (i = 0; i < 10; i++) { - integer overlayIdx = getOverlayPrimLink(i); - if (overlayIdx >= FIRST_OVERLAY_PRIM && overlayIdx <= LAST_OVERLAY_PRIM) { + overlayIdx = getOverlayPrimLink(i); + if (overlayIdx > 0) { llSetLinkPrimitiveParamsFast(overlayIdx, [ PRIM_TEXTURE, ALL_SIDES, TEXTURE_ELIMINATED_X, <1,1,0>, <0,0,0>, 0.0, PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0 @@ -1223,7 +1138,7 @@ default { } } else if (msg == "clearx") { - llOwnerSay("Refreshing display to clear test X overlays..."); + dbg("Refreshing display to clear test X overlays..."); refreshPlayerDisplay(); } } diff --git a/Leaderboard_Communication_Linkset.lsl b/Leaderboard_Communication_Linkset.lsl deleted file mode 100644 index b1c1ac0..0000000 --- a/Leaderboard_Communication_Linkset.lsl +++ /dev/null @@ -1,229 +0,0 @@ -//////////////////////////////////////////// -// Leaderboard Communication Script - Linkset Version -// Based on actual link verification results -//////////////////////////////////////////// - -// ============================================================================= -// LINKSET COMMUNICATION - NO DISCOVERY NEEDED -// ============================================================================= - -// Verbose logging control -integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs -#define MSG_TOGGLE_VERBOSE_LOGS 9998 // Message to toggle verbose logging - -// Message constants for link communication -// Leaderboard messages (from link 12 - scoreboard) -#define MSG_GAME_WON 3010 -#define MSG_GAME_LOST 3011 -#define MSG_RESET_LEADERBOARD 3012 - -#define DISPLAY_STRING 204000 - -// Bank assignments based on actual link testing - UPDATED FOR LINKSET: -// Original links were 1-48, now they are 35-82 (offset by +34) -list leftmostLinks = [35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46]; // Links 35-46 = Leftmost bank (was 1-12) -list middleLeftLinks = [47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58]; // Links 47-58 = Middle-left bank (was 13-24) -list middleRightLinks = [59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70]; // Links 59-70 = Middle-right bank (was 25-36) -list rightmostLinks = [71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82]; // Links 71-82 = Rightmost bank (was 37-48) - -string leftText = ""; -string middleLeftText = ""; -string middleRightText = ""; -string rightText = ""; - -// Distribute text across all 4 banks properly - each line can be 40 chars total -distributeFullText(string text) { - list lines = llParseString2List(text, ["\n"], []); - list allLinks = leftmostLinks + middleLeftLinks + middleRightLinks + rightmostLinks; - - integer lineIndex = 0; - integer maxLines = 12; // Each bank has 12 prims - - // Process each line - while (lineIndex < maxLines) { - string currentLine; - - if (lineIndex < llGetListLength(lines)) { - currentLine = llList2String(lines, lineIndex); - } else { - currentLine = ""; // Empty line - } - - // Pad line to 40 characters (4 banks ร— 10 chars each) - while (llStringLength(currentLine) < 40) { - currentLine += " "; - } - // Truncate if too long - if (llStringLength(currentLine) > 40) { - currentLine = llGetSubString(currentLine, 0, 39); - } - - // Distribute this line across the 4 banks (10 chars each) - integer bank; - for (bank = 0; bank < 4; bank++) { - string bankText = llGetSubString(currentLine, bank * 10, (bank * 10) + 9); - integer linkNum; - - if (bank == 0) linkNum = llList2Integer(leftmostLinks, lineIndex); - else if (bank == 1) linkNum = llList2Integer(middleLeftLinks, lineIndex); - else if (bank == 2) linkNum = llList2Integer(middleRightLinks, lineIndex); - else linkNum = llList2Integer(rightmostLinks, lineIndex); - - llMessageLinked(linkNum, DISPLAY_STRING, bankText, ""); - } - - lineIndex++; - } -} - -// Legacy function for single bank distribution -distributeToBank(string text, list linkNumbers) { - list bankLines; - - // If text is empty, create a list of empty strings to clear all prims - if (text == "") { - integer numPrims = llGetListLength(linkNumbers); - integer j; - for (j = 0; j < numPrims; j++) { - bankLines += [" "]; // 10 spaces to clear each line - } - } else { - bankLines = llParseString2List(text, ["\n"], []); - } - - // Send text to each prim in the bank - integer k; - for (k = 0; k < llGetListLength(linkNumbers); k++) { - string line; - - if (k < llGetListLength(bankLines)) { - line = llList2String(bankLines, k); - } else { - line = " "; // 10 spaces for empty lines - } - - // Ensure exactly 10 characters - if (llStringLength(line) > 10) { - line = llGetSubString(line, 0, 9); - } else if (llStringLength(line) < 10) { - line = line + llGetSubString(" ", 0, 9 - llStringLength(line)); - } - - integer primLink = llList2Integer(linkNumbers, k); - llMessageLinked(primLink, DISPLAY_STRING, line, ""); - } -} - -default { - on_rez(integer start_param) { - llResetScript(); // Fixes Z-fighting and alignment glitches on rez - } - state_entry() { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“‹ Leaderboard Communication Script ready! (Linkset Version)"); - llOwnerSay("๐Ÿ“‹ This is link " + (string)llGetLinkNumber() + " - should be link 35"); - llOwnerSay("๐Ÿ“‹ Managing XyzzyText links 35-82 (48 prims total)"); - llOwnerSay("โœ… Linkset communication active - listening for messages from link 12!"); - } - - // 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 12) - if (sender != 12) { - return; - } - - if (num == MSG_RESET_LEADERBOARD) { - // Handle the different text section messages - if (llGetSubString(str, 0, 9) == "LEFT_TEXT|") { - leftText = llGetSubString(str, 10, -1); - distributeToBank(leftText, leftmostLinks); - - } else if (llGetSubString(str, 0, 16) == "MIDDLE_LEFT_TEXT|") { - middleLeftText = llGetSubString(str, 17, -1); - distributeToBank(middleLeftText, middleLeftLinks); - - } else if (llGetSubString(str, 0, 17) == "MIDDLE_RIGHT_TEXT|") { - middleRightText = llGetSubString(str, 18, -1); - distributeToBank(middleRightText, middleRightLinks); - - } else if (llGetSubString(str, 0, 10) == "RIGHT_TEXT|") { - rightText = llGetSubString(str, 11, -1); - distributeToBank(rightText, rightmostLinks); - } - else if (llGetSubString(str, 0, 14) == "FORMATTED_TEXT|") { - // This is pre-formatted text from the scoreboard - just display it - string formattedText = llGetSubString(str, 15, -1); - leftText = formattedText; - distributeFullText(leftText); - } - else if (llSubStringIndex(str, "LEADERBOARD") == 0) { - // Legacy leaderboard format - not used with linkset version - // Scoreboard now sends pre-formatted text via FORMATTED_TEXT - if (VERBOSE_LOGGING) { - llOwnerSay("โš ๏ธ [Leaderboard] Received legacy LEADERBOARD format - ignoring"); - } - } - } - } - - touch_start(integer total_number) { - // Owner can get debug info by touching leaderboard - if (llDetectedKey(0) == llGetOwner()) { - llOwnerSay("๐Ÿ“‹ Leaderboard Status:"); - llOwnerSay(" Link Number: " + (string)llGetLinkNumber()); - llOwnerSay(" Total prims in linkset: " + (string)llGetNumberOfPrims()); - llOwnerSay(" Managing links 35-82 (48 XyzzyText prims)"); - llOwnerSay(" Listening for messages from link 12 (scoreboard)"); - llOwnerSay(" Current text sections:"); - string leftStatus; - if (leftText != "") { - leftStatus = "SET (" + (string)llStringLength(leftText) + " chars)"; - } else { - leftStatus = "EMPTY"; - } - llOwnerSay(" Left: " + leftStatus); - - string middleLeftStatus; - if (middleLeftText != "") { - middleLeftStatus = "SET"; - } else { - middleLeftStatus = "EMPTY"; - } - llOwnerSay(" Middle-Left: " + middleLeftStatus); - - string middleRightStatus; - if (middleRightText != "") { - middleRightStatus = "SET"; - } else { - middleRightStatus = "EMPTY"; - } - llOwnerSay(" Middle-Right: " + middleRightStatus); - - string rightStatus; - if (rightText != "") { - rightStatus = "SET"; - } else { - rightStatus = "EMPTY"; - } - llOwnerSay(" Right: " + rightStatus); - } else { - // Non-owner touch - show friendly message - llSay(0, "๐Ÿ† Leaderboard shows the top players! Keep playing to climb the ranks!"); - } - } -} diff --git a/MARKETPLACE_DESCRIPTION.txt b/MARKETPLACE_DESCRIPTION.txt index 1de05f1..b65903f 100644 --- a/MARKETPLACE_DESCRIPTION.txt +++ b/MARKETPLACE_DESCRIPTION.txt @@ -2,23 +2,25 @@ PERIL DICE - Professional Single Linkset Game System for Second Life Created by Rebecca Nod and Noose the Bunny -CURRENT VERSION: 3.0.0 - Commercial CasperVend Release +CURRENT VERSION: 3.1.0 - The "Linkset Stability" Update Thrilling multiplayer elimination game for 2-10 players! Each participant selects numbers before a die is rolled. If the peril player's number comes up, they lose a life. Last player standing wins! -NEW IN v3.0.0: Transitioned to commercial CasperVend system! Massive memory optimizations utilizing pre-processor macros for 10-player load stability. Resolved all Z-fighting and alignment glitches on rez to ensure perfectly synchronized graphics. +NEW IN v3.1.0: **LINKSET STABILITY & DYNAMIC DISCOVERY!** Complete architectural decentralizationโ€”scripts now find their linked prims by name, making the game board immune to link-order changes or region restarts. Full migration to modern mesh-based **FURWARE Text** for dice and leaderboards. Centralized system constants in `Peril_Constants.lsl` for 100% project consistency. KEY FEATURES: +- Dynamic Link Discovery: "Bulletproof" linkset architecture that auto-configures by prim name +- FURWARE Text Mesh: Professional, mesh-based displays for dice and leaderboard results - Winner Glow Celebration: Bright green glow effects on scoreboard and floaters with 24-second victory timer - Visual Scoreboard: Real-time player grid with profile pictures and hearts - Enhanced Status Display: Custom textures for Direct Hit, No Shield, Plot Twist, Elimination, Victory - Color-Coded Glow System: Green (winner), Yellow (peril), Red X (eliminated) -- Comprehensive Leaderboard: Persistent win tracking with XyzzyText display +- Experience Leaderboard: Persistent win tracking via Experience KVP with XyzzyText display - Bot Support: AI players for testing and gameplay variety - Floating HUD Display: Real-time stats for each player with victory text - Intelligent Dice Sizing: d6/d12/d20/d30 based on player count - Security Features: Owner lockout system, kick player functionality -- Single Linkset: Complete 74-prim system for easy deployment +- Single Linkset: Complete consolidated system for instant deployment GAME RULES: 1. 2-10 players join and mark ready @@ -49,11 +51,12 @@ INCLUDED: PERMISSIONS: Copy/Modify/No Transfer LAND IMPACT: 74 prims -EXPERIENCE: None required +EXPERIENCE: Required for KVP Leaderboard functionality Perfect for nightclubs, venues, casinos, private parties, group entertainment, community gatherings. VERSION HISTORY: +v3.1.0: Dynamic Link Discovery, FURWARE Text Mesh, and Centralized Constants v3.0.0: Commercial CasperVend Release, memory optimizations, and Z-fighting fixes v2.8.7: Linkset Analysis Tools and structure verification v2.8.6: Winner Glow Celebration System with 24-second victory recognition diff --git a/Main_Controller_Linkset.lsl b/Main_Controller_Linkset.lsl index 5f8f079..cc2bc6b 100644 --- a/Main_Controller_Linkset.lsl +++ b/Main_Controller_Linkset.lsl @@ -1,3 +1,5 @@ +#include "Peril_Constants.lsl" + // === Main Controller - Peril Dice Game (Linkset Version) === // Helper function to get display name with fallback to username @@ -10,75 +12,10 @@ string getPlayerName(key id) { return displayName; } -// -// LINKSET VERSION - Uses llMessageLinked() instead of llRegionSay() -// All discovery and channel management code removed -// - // ============================================================================= -// LINKSET COMMUNICATION CONSTANTS +// DEBUG & LOCAL CONSTANTS // ============================================================================= -#define DEBUG_LOGS 0 // Set to 1 to enable logs, 0 to STRIP them from bytecode and recover memory -#if DEBUG_LOGS -#define dbg(msg) dbg(msg) -#else -#define dbg(msg) -#endif - -// Target link numbers (UPDATED after overlay prim insertion) -#define SCOREBOARD_LINK 12 // Scoreboard manager cube -#define LEADERBOARD_LINK 35 // Leaderboard manager (first XyzzyText prim) -#define DICE_LINK 83 // Dice display manager (first dice prim) - -// Message constants for link communication -// Scoreboard messages (to link 12) -#define MSG_GAME_STATUS 3001 -#define MSG_PLAYER_UPDATE 3002 -#define MSG_CLEAR_GAME 3003 -#define MSG_REMOVE_PLAYER 3004 -#define MSG_UPDATE_PERIL_PLAYER 3005 -#define MSG_UPDATE_WINNER 3006 - -// Leaderboard messages (to link 35) -#define MSG_GAME_WON 3010 -#define MSG_GAME_LOST 3011 -#define MSG_RESET_LEADERBOARD 3012 - -// Dice messages (to link 83) -#define MSG_DICE_ROLL 3020 -#define MSG_CLEAR_DICE 3021 - -// Legacy message constants (keep for internal controller communication) -#define MSG_SHOW_DIALOG 101 -#define MSG_ROLL_RESULT 102 -#define MSG_UPDATE_FLOAT 103 -#define MSG_CLEANUP_FLOAT 104 -#define MSG_REZ_FLOAT 105 -#define MSG_REGISTER_PLAYER 106 -#define MSG_REGISTER_PLAYER_REQUEST 9050 // New dedicated message to Player_RegistrationManager -#define MSG_SYNC_GAME_STATE 107 -#define MSG_SHOW_MENU 201 -#define MSG_TOGGLE_READY 202 -#define MSG_QUERY_READY_STATE 210 -#define MSG_READY_STATE_RESULT 211 -#define MSG_CLEANUP_ALL_FLOATERS 212 -#define MSG_QUERY_OWNER_STATUS 213 -#define MSG_OWNER_STATUS_RESULT 214 -#define MSG_SHOW_ROLL_DIALOG 301 -#define MSG_GET_CURRENT_DIALOG 302 -#define MSG_PLAYER_WON 551 -#define MSG_GET_PICKS_REQUIRED 1002 -#define MSG_GET_PICKER_INDEX 1003 - -// Memory monitoring handled by Controller_Memory.lsl helper script -#define MSG_MEMORY_CHECK 6001 -#define MSG_MEMORY_STATS 6002 -#define MSG_MEMORY_CLEANUP 6003 -#define MSG_MEMORY_REPORT 6004 -#define MSG_EMERGENCY_CLEANUP 6005 -#define MSG_MEMORY_STATS_REQUEST 6006 - // Unified Timer System - prevents conflicts between multiple timer needs #define TIMER_IDLE 0 #define TIMER_STATUS 1 @@ -90,20 +27,9 @@ integer victoryDelayTimer = 0; // Track victory delay timing // Debug control - set to TRUE for verbose pick debugging, FALSE for normal operation #define DEBUG_PICKS FALSE -// Verbose logging moved to dedicated Verbose_Logger.lsl script to save memory -// Message constants for verbose logging -#define MSG_VERBOSE_LOG 9020 -#define MSG_VERBOSE_TOGGLE 9010 - // Script ID for Main Controller (used by Verbose_Logger for prefixes) #define SCRIPT_ID_MAIN 0 -// Message handling constants (delegate to Controller_MessageHandler) -#define MSG_OWNER_MESSAGE 9030 -#define MSG_PUBLIC_MESSAGE 9031 -#define MSG_REGION_MESSAGE 9032 -#define MSG_DIALOG_REQUEST 9033 - // Memory reporting function reportMemoryUsage(string scriptName) { integer used = llGetUsedMemory(); @@ -175,7 +101,8 @@ integer gameTimer = 0; integer calculateChannel(integer offset) { string ownerStr = (string)llGetOwner(); - string objectStr = (string)llGetKey(); + // CRITICAL: Always use root prim (1) for channel calculation so scripts in child prims match + string objectStr = (string)llGetLinkKey(1); string combinedStr = ownerStr + objectStr; string hashStr = llMD5String(combinedStr, 0); @@ -259,7 +186,7 @@ sendStatusMessage(string status) { lastStatus = status; // CHANGED: Use link message instead of llRegionSay - llMessageLinked(SCOREBOARD_LINK, MSG_GAME_STATUS, status, NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_GAME_STATUS, status, NULL_KEY); // Removed ownerMsg call to save memory @@ -283,7 +210,7 @@ updateHelpers() { if (nameCount > 0) { integer lastIdx = nameCount - 1; // Minimal string operations - direct message - llMessageLinked(SCOREBOARD_LINK, MSG_PLAYER_UPDATE, + llMessageLinked(LINK_SCOREBOARD, MSG_PLAYER_UPDATE, llList2String(names, lastIdx) + "|" + (string)llList2Integer(lives, lastIdx) + "|" + (string)llList2Key(players, lastIdx), NULL_KEY); @@ -341,14 +268,14 @@ resetGame() { victoryInProgress = FALSE; currentPicker = NULL_KEY; - llMessageLinked(LINK_SET, -99999, "FULL_RESET", NULL_KEY); + llMessageLinked(LINK_SET, MSG_RESET_ALL, "FULL_RESET", NULL_KEY); llSay(syncChannel, "RESET"); // CHANGED: Use link messages instead of llRegionSay for display cleanup - llMessageLinked(SCOREBOARD_LINK, MSG_CLEAR_GAME, "", NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_CLEAR_GAME, "", NULL_KEY); // Clear winner glow from scoreboard (floaters will clear naturally on reset) - llMessageLinked(SCOREBOARD_LINK, MSG_UPDATE_WINNER, "", NULL_KEY); // Clear winner glow - llMessageLinked(DICE_LINK, MSG_CLEAR_DICE, "", NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_WINNER, "", NULL_KEY); // Clear winner glow + llMessageLinked(LINK_DICE_BRIDGE, MSG_CLEAR_DICE, "", NULL_KEY); dbg("๐ŸŽฎ Game reset! All state cleared (including scoreboard)."); @@ -371,8 +298,9 @@ resetGame() { default { state_entry() { + DISCOVER_CORE_LINKS(); reportMemoryUsage("Main Controller"); - dbg("๐ŸŽฎ Main Controller ready! (Linkset Version)"); + dbg("๐ŸŽฎ Main Controller ready! (Discovery Mode)"); // Initialize lockout system gameOwner = llGetOwner(); @@ -388,8 +316,9 @@ default { } on_rez(integer start_param) { + DISCOVER_CORE_LINKS(); reportMemoryUsage("Main Controller"); - dbg("๐Ÿ”„ Main Controller rezzed - resetting game state..."); + dbg("๐Ÿ”„ Main Controller reset via rez..."); // Re-initialize lockout system gameOwner = llGetOwner(); @@ -413,29 +342,22 @@ default { string playerName = llList2String(names, idx); // 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); - // Removed debugMsg call to save memory if (playerName == expectedPickerName) { shouldBePicking = TRUE; // Fix corrupted currentPicker state if (currentPicker != toucher) { - // Removed ownerMsg call to save memory currentPicker = toucher; } } - } else { - // Removed debugMsg call to save memory } // 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()) { - // Removed ownerMsg calls to save memory - // Game stuck error and recovery logging removed currentPicker = toucher; shouldBePicking = TRUE; } @@ -544,8 +466,6 @@ default { } } llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher); - } else { - dbg("โš ๏ธ Could not get name for toucher: " + (string)toucher); } } } @@ -562,7 +482,7 @@ default { } // Handle registration updates from Player_RegistrationManager - if (num == 9040) { // MSG_UPDATE_MAIN_LISTS + if (num == MSG_UPDATE_MAIN_LISTS) { // MSG_UPDATE_MAIN_LISTS // ULTRA-OPTIMIZED: Player_RegistrationManager now handles all heavy processing // Main Controller just adds the player to its master lists list parts = llParseString2List(str, ["~"], []); @@ -599,7 +519,7 @@ default { } // Handle elimination requests - if (num == 999) { + if (num == MSG_ELIMINATE_PLAYER) { list parts = llParseString2List(str, ["|"], []); if (llList2String(parts, 0) == "ELIMINATE_PLAYER") { string eliminatedPlayer = llList2String(parts, 1); @@ -609,7 +529,7 @@ default { lives = llListReplaceList(lives, [0], idx, idx); // Send ONLY a direct scoreboard update to show 0 hearts - llMessageLinked(SCOREBOARD_LINK, MSG_PLAYER_UPDATE, + llMessageLinked(LINK_SCOREBOARD, MSG_PLAYER_UPDATE, eliminatedPlayer + "|0|" + (string)llList2Key(players, idx), NULL_KEY); // CRITICAL: Check for victory condition BEFORE assigning new peril player @@ -630,7 +550,7 @@ default { if (potentialWinner != "") { // SEND WINNER GLOW to scoreboard and trigger floater update - llMessageLinked(SCOREBOARD_LINK, MSG_UPDATE_WINNER, potentialWinner, NULL_KEY); + 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); } @@ -657,11 +577,11 @@ default { if (newPerilPlayer != "") { perilPlayer = newPerilPlayer; // Send peril player update to scoreboard for glow effect - llMessageLinked(SCOREBOARD_LINK, MSG_UPDATE_PERIL_PLAYER, newPerilPlayer, NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_PERIL_PLAYER, newPerilPlayer, NULL_KEY); } else { perilPlayer = "NONE"; // Clear peril player glow on scoreboard - llMessageLinked(SCOREBOARD_LINK, MSG_UPDATE_PERIL_PLAYER, "", NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_PERIL_PLAYER, "", NULL_KEY); } } @@ -714,7 +634,7 @@ default { // Peril player assignment already handled before sync message was sent // CHANGED: Send to scoreboard, which will handle leaderboard updates - llMessageLinked(SCOREBOARD_LINK, MSG_GAME_LOST, eliminatedPlayer, NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_GAME_LOST, eliminatedPlayer, NULL_KEY); // Status message now sent directly by Roll Module @@ -747,14 +667,14 @@ default { llSay(0, "โœจ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!"); llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY); - llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY); + llMessageLinked(LINK_SET, MSG_EFFECT_CONFETTI, "VICTORY_CONFETTI", NULL_KEY); // IMPORTANT: Send winner update to scoreboard and trigger floater update - llMessageLinked(SCOREBOARD_LINK, MSG_UPDATE_WINNER, winner, NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_WINNER, winner, NULL_KEY); llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, winner, NULL_KEY); // Update winner's floater // CHANGED: Send to scoreboard, which will handle leaderboard updates - llMessageLinked(SCOREBOARD_LINK, MSG_GAME_WON, winner, NULL_KEY); + 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"); @@ -806,10 +726,10 @@ default { } // Handle emergency reset for stuck games - if (num == -99998 && str == "EMERGENCY_RESET") { + if (num == MSG_EMERGENCY_RESET && str == "EMERGENCY_RESET") { dbg("๐Ÿšจ [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); + llMessageLinked(LINK_SET, MSG_EMERGENCY_RESET, "EMERGENCY_RESET", NULL_KEY); llSleep(0.5); // Then do full reset resetGame(); @@ -822,7 +742,7 @@ default { if (llList2String(parts, 0) == "GAME_WON") { string winner = llList2String(parts, 1); // CHANGED: Use link message instead of llRegionSay - llMessageLinked(DICE_LINK, MSG_DICE_ROLL, winner + "|WON", NULL_KEY); + llMessageLinked(LINK_DICE_BRIDGE, MSG_DICE_ROLL, winner + "|WON", NULL_KEY); llSleep(2.0); resetGame(); } @@ -942,7 +862,7 @@ default { // MSG_CLEANUP_ALL_FLOATERS is handled by Floater Manager only // Handle leave game requests processed by Controller_MessageHandler - if (num == 8006) { // MSG_LEAVE_GAME_REQUEST + if (num == MSG_LEAVE_GAME_REQUEST) { // MSG_LEAVE_GAME_REQUEST // This message comes FROM the Controller_MessageHandler after it has processed the removal // We need to update the main controller's lists to match list parts = llParseString2List(str, ["|"], []); @@ -953,7 +873,7 @@ default { integer idx = llListFindList(names, [leavingName]); if (idx != -1) { // CHANGED: Remove player from scoreboard BEFORE updating lists - llMessageLinked(SCOREBOARD_LINK, MSG_REMOVE_PLAYER, leavingName, NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_REMOVE_PLAYER, leavingName, NULL_KEY); // Clean up the leaving player's floater using their current channel integer ch = FLOATER_BASE_CHANNEL + idx; @@ -981,12 +901,12 @@ default { dbg("๐ŸŽฏ [Main Controller] Peril player left - assigning new peril player: " + newPerilPlayer); perilPlayer = newPerilPlayer; // Send peril player update to scoreboard for glow effect - llMessageLinked(SCOREBOARD_LINK, 3005, newPerilPlayer, NULL_KEY); // MSG_UPDATE_PERIL_PLAYER + llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_PERIL_PLAYER, newPerilPlayer, NULL_KEY); // MSG_UPDATE_PERIL_PLAYER } else { dbg("โš ๏ธ [Main Controller] No valid peril player candidates found after player left!"); perilPlayer = "NONE"; // Clear peril player glow on scoreboard - llMessageLinked(SCOREBOARD_LINK, MSG_UPDATE_PERIL_PLAYER, "", NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_PERIL_PLAYER, "", NULL_KEY); } } @@ -1032,7 +952,7 @@ default { } // Handle lock/unlock messages from Dialog Handler - if (num == 9001) { // Lock game message + if (num == MSG_LOCK_GAME) { // Lock game message if (id == gameOwner && str == "LOCK_GAME") { isLocked = TRUE; updateFloatingText(); @@ -1041,7 +961,7 @@ default { return; } - if (num == 9002) { // Unlock game message + if (num == MSG_UNLOCK_GAME) { // Unlock game message if (id == gameOwner && str == "UNLOCK_GAME") { isLocked = FALSE; updateFloatingText(); @@ -1051,17 +971,6 @@ default { } - // Handle verbose logging toggle - redirect to dedicated Verbose_Logger script - if (num == MSG_VERBOSE_TOGGLE && str == "TOGGLE_VERBOSE_LOGS") { - // Forward to dedicated verbose logger script, but exclude ourselves from LINK_SET - // Send to each script individually to avoid the forwarding loop - integer i; - integer linkCount = llGetNumberOfPrims(); - for (i = 2; i <= linkCount; i++) { // Start from 2 to skip ourselves (link 1) - llMessageLinked(i, MSG_VERBOSE_TOGGLE, "TOGGLE_VERBOSE_LOGS", id); - } - return; - } // Handle memory monitor messages if (num == MSG_EMERGENCY_CLEANUP) { @@ -1071,7 +980,7 @@ default { } // Handle reset requests from other scripts (like Game_Manager) - if (num == -99998 && str == "REQUEST_GAME_RESET") { + if (num == MSG_EMERGENCY_RESET && str == "REQUEST_GAME_RESET") { dbg("๐Ÿ”„ [Main Controller] Reset requested by " + (string)sender + ", executing..."); resetGame(); return; @@ -1130,13 +1039,13 @@ default { } if (msg == "Reset Leaderboard") { // CHANGED: Send to scoreboard, which will handle leaderboard reset - llMessageLinked(SCOREBOARD_LINK, MSG_RESET_LEADERBOARD, "", NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_RESET_LEADERBOARD, "", NULL_KEY); dbg("๐Ÿ† Leaderboard scores reset - game wins cleared!"); return; } if (msg == "Reset All") { resetGame(); - llMessageLinked(SCOREBOARD_LINK, MSG_RESET_LEADERBOARD, "", NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_RESET_LEADERBOARD, "", NULL_KEY); dbg("๐Ÿ”„ Complete reset - game and leaderboard cleared!"); return; } @@ -1275,7 +1184,7 @@ default { statusTimer = 0; lastStatus = ""; // CHANGED: Use link message instead of llRegionSay - llMessageLinked(SCOREBOARD_LINK, MSG_GAME_STATUS, "Title", NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_GAME_STATUS, "Title", NULL_KEY); dbg("๐Ÿ“ข Status cleared - reverted to Title"); currentTimerMode = TIMER_IDLE; llSetTimerEvent(0); diff --git a/NumberPicker_DialogHandler.lsl b/NumberPicker_DialogHandler.lsl index 0a2cef3..18d0f0c 100644 --- a/NumberPicker_DialogHandler.lsl +++ b/NumberPicker_DialogHandler.lsl @@ -1,17 +1,15 @@ +#include "Peril_Constants.lsl" + // === Dialog Handler (Updated for Peril Dice - Paginated Multiple Picks) === -#define MSG_SHOW_DIALOG 101 -#define MSG_GET_CURRENT_DIALOG 302 - // Verbose logging control -integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs -#define MSG_TOGGLE_VERBOSE_LOGS 9998 // Message to toggle verbose logging + // ============================================================================= // DYNAMIC CHANNEL CONFIGURATION // ============================================================================= -// Base channel offset - should match Main.lsl +// Base channel offset must match Main.lsl #define CHANNEL_BASE -77000 // Calculate channels dynamically to avoid hardcoded conflicts @@ -39,10 +37,8 @@ initializeChannels() { NUMBERPICK_CHANNEL = calculateChannel(2); // ~-77200 range to match Main.lsl // Report channel to owner for debugging - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ”ง [Number Picker Dialog] Dynamic channel initialized:"); - llOwnerSay(" Number Pick: " + (string)NUMBERPICK_CHANNEL); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿ”ง Dynamic channel initialized:"); + dbg(" Number Pick: " + (string)NUMBERPICK_CHANNEL); } integer numberPickChannel; // Legacy variable, will be set dynamically @@ -67,7 +63,7 @@ reportMemoryUsage(string scriptName) { integer total = used + free; float percentUsed = ((float)used / (float)total) * 100.0; - llOwnerSay("๐Ÿง  [" + scriptName + "] Memory: " + + dbg("๐Ÿง  [" + scriptName + "] Memory: " + (string)used + " used, " + (string)free + " free (" + llGetSubString((string)percentUsed, 0, 4) + "% used)"); @@ -101,20 +97,16 @@ list getNumbersForPage(integer page, integer diceType) { } showPickDialog(string name, key id, integer diceType, integer picks) { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“‹ [NumberPicker] showPickDialog called for " + name + ", dice:" + (string)diceType + ", picks:" + (string)picks); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿ“‹ showPickDialog called for " + name + ", dice:" + (string)diceType + ", picks:" + (string)picks); if (diceType <= 0) { - llOwnerSay("โš ๏ธ Cannot show dialog: diceType is 0 or invalid."); + dbg("๐ŸŽฎ [Number Picker Dialog] โš ๏ธ Cannot show dialog: diceType is 0 or invalid."); return; } // Initialize or update picking session if (currentPlayer != name) { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“‹ [NumberPicker] Starting new session for " + name); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿ“‹ Starting new session for " + name); currentPlayer = name; currentPlayerKey = id; currentDiceType = diceType; @@ -122,9 +114,7 @@ showPickDialog(string name, key id, integer diceType, integer picks) { currentPicks = []; currentPage = 0; } else { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“‹ [NumberPicker] Updating existing session for " + name); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿ“‹ Updating existing session for " + name); } list options; @@ -176,13 +166,11 @@ showPickDialog(string name, key id, integer diceType, integer picks) { dialogText = "Pick " + pickText + " (1-" + (string)diceType + ")" + pageInfo + currentPicksText; } - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“‹ [NumberPicker] About to show dialog to " + name + " (" + (string)id + ")"); - llOwnerSay("๐Ÿ“‹ [NumberPicker] Dialog text: " + dialogText); - llOwnerSay("๐Ÿ“‹ [NumberPicker] Options (" + (string)llGetListLength(options) + "): " + llList2CSV(options)); - llOwnerSay("๐Ÿ“‹ [NumberPicker] Channel: " + (string)numberPickChannel); - llOwnerSay("๐Ÿ“‹ [NumberPicker] globallyPickedNumbers: " + llList2CSV(globallyPickedNumbers)); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿ“‹ About to show dialog to " + name + " (" + (string)id + ")"); + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿ“‹ Dialog text: " + dialogText); + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿ“‹ Options (" + (string)llGetListLength(options) + "): " + llList2CSV(options)); + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿ“‹ Channel: " + (string)numberPickChannel); + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿ“‹ globallyPickedNumbers: " + llList2CSV(globallyPickedNumbers)); llDialog(id, dialogText, options, numberPickChannel); } @@ -211,15 +199,13 @@ default { // Set up managed listener with dynamic channel listenHandle = llListen(numberPickChannel, "", NULL_KEY, ""); - llOwnerSay("๐ŸŽฎ Number Picker Dialog Handler ready!"); + dbg("๐ŸŽฎ [NumberPicker] Number Picker Dialog Handler ready!"); } on_rez(integer start_param) { reportMemoryUsage("๐ŸŽฎ NumberPicker Dialog"); - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ”„ Number Picker rezzed - reinitializing..."); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿ”„ Number Picker rezzed - reinitializing..."); // Re-initialize dynamic channels initializeChannels(); @@ -241,45 +227,28 @@ default { // Set up managed listener with dynamic channel listenHandle = llListen(numberPickChannel, "", NULL_KEY, ""); - if (VERBOSE_LOGGING) { - llOwnerSay("โœ… Number Picker reset complete after rez!"); - } + dbg("๐ŸŽฎ [NumberPicker] โœ… 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") { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿšซ [NumberPicker] CLOSE_ALL_DIALOGS received! currentPlayer: " + currentPlayer + ", key: " + (string)currentPlayerKey); - } + if (num == MSG_CLOSE_ALL_DIALOGS && str == "CLOSE_ALL_DIALOGS") { + + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿšซ CLOSE_ALL_DIALOGS received! currentPlayer: " + currentPlayer + ", key: " + (string)currentPlayerKey); + if (currentPlayerKey != NULL_KEY) { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿšซ [NumberPicker] Forced dialog close for " + currentPlayer); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿšซ Forced dialog close for " + currentPlayer); currentPlayer = ""; currentPlayerKey = NULL_KEY; currentPicks = []; } else { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿšซ [NumberPicker] No active dialog to close"); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿšซ No active dialog to close"); } return; } // Handle full reset from main controller - if (num == -99999 && str == "FULL_RESET") { + if (num == MSG_RESET_ALL && str == "FULL_RESET") { // Reset all dialog state currentPlayer = ""; currentPlayerKey = NULL_KEY; @@ -288,14 +257,14 @@ default { currentPicks = []; globallyPickedNumbers = []; currentPage = 0; - llOwnerSay("๐ŸŽฎ Number Picker Dialog Handler reset!"); + dbg("๐ŸŽฎ [NumberPicker] Number Picker Dialog Handler reset!"); return; } if (num == MSG_SHOW_DIALOG) { list parts = llParseString2List(str, ["|"], []); if (llGetListLength(parts) < 3) { - llOwnerSay("โš ๏ธ Malformed MSG_SHOW_DIALOG input: " + str); + dbg("๐ŸŽฎ [NumberPicker] โš ๏ธ Malformed MSG_SHOW_DIALOG input: " + str); return; } @@ -349,9 +318,7 @@ default { string playerName = str; // Only show dialog if this player is the current picker if (currentPlayer == playerName && currentPlayerKey == id) { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ”„ Restoring dialog for " + playerName); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿ”„ Restoring dialog for " + playerName); showPickDialog(currentPlayer, currentPlayerKey, currentDiceType, picksNeeded); } else { llRegionSayTo(id, 0, "โŒ You don't have an active dialog to restore."); @@ -362,17 +329,13 @@ default { listen(integer channel, string name, key id, string message) { // Ignore any responses if no active dialog session if (currentPlayerKey == NULL_KEY || currentPlayer == "") { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿšซ [NumberPicker] Ignoring stale dialog response (no active session). Player: " + name + ", Key: " + (string)id + ", Message: " + message); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿšซ Ignoring stale dialog response (no active session). Player: " + name + ", Key: " + (string)id + ", Message: " + message); return; } // Ignore responses from wrong player if (id != currentPlayerKey) { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿšซ [NumberPicker] Ignoring dialog response from wrong player. Expected: " + (string)currentPlayerKey + ", Got: " + (string)id + ", Message: " + message); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿšซ Ignoring dialog response from wrong player. Expected: " + (string)currentPlayerKey + ", Got: " + (string)id + ", Message: " + message); return; } @@ -381,17 +344,13 @@ default { if (messageNum > 0 && messageNum <= currentDiceType) { // This looks like a number selection - verify it's not already globally picked if (llListFindList(globallyPickedNumbers, [message]) != -1) { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿšซ [NumberPicker] Ignoring stale dialog response - number " + message + " already globally picked. Player: " + name); - } + dbg("๐ŸŽฎ [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) { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿšซ [NumberPicker] Ignoring stale dialog response - number " + message + " already in current picks. Player: " + name); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿšซ Ignoring stale dialog response - number " + message + " already in current picks. Player: " + name); return; } } @@ -419,27 +378,19 @@ default { // Handle completion if (message == "โœ… Done") { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“‹ [NumberPicker] Done button clicked by " + name + ", currentPicks: " + llList2CSV(currentPicks) + ", needed: " + (string)picksNeeded); - } + dbg("๐Ÿ“‹ [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; - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“ค [NumberPicker] SENDING HUMAN_PICKED: " + response); - } - llMessageLinked(LINK_SET, -9998, response, NULL_KEY); + dbg("๐Ÿ“ค [NumberPicker] SENDING HUMAN_PICKED: " + response); + llMessageLinked(LINK_SET, MSG_HUMAN_PICKED, response, NULL_KEY); // Reset state - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“‹ [NumberPicker] Resetting session state for " + currentPlayer); - } + dbg("๐ŸŽฎ [NumberPicker] ๐Ÿ“‹ Resetting session state for " + currentPlayer); currentPlayer = ""; currentPlayerKey = NULL_KEY; currentPicks = []; } else { - if (VERBOSE_LOGGING) { - llOwnerSay("โš ๏ธ " + currentPlayer + " needs " + (string)(picksNeeded - llGetListLength(currentPicks)) + " more picks"); - } + dbg("๐ŸŽฎ [NumberPicker] โš ๏ธ " + currentPlayer + " needs " + (string)(picksNeeded - llGetListLength(currentPicks)) + " more picks"); showPickDialog(currentPlayer, currentPlayerKey, currentDiceType, picksNeeded); } return; @@ -452,7 +403,7 @@ default { // Add to picks (numbers already picked won't be shown in dialog) currentPicks += [message]; } else { - llOwnerSay("โš ๏ธ " + currentPlayer + " has already picked enough numbers"); + dbg("๐ŸŽฎ [NumberPicker] โš ๏ธ " + currentPlayer + " has already picked enough numbers"); } // Show updated dialog diff --git a/Peril_Constants.lsl b/Peril_Constants.lsl new file mode 100644 index 0000000..98d4079 --- /dev/null +++ b/Peril_Constants.lsl @@ -0,0 +1,131 @@ +// ============================================================================= +// PERIL DICE - CENTRAL CONSTANTS (Preprocessor Version) +// ============================================================================= + +// --- Linkset Layout (Dynamic Discovery) --- +integer LINK_CONTROLLER = 1; // Usually Root (link 1) +integer LINK_SCOREBOARD = -1; // "Scoreboard:0:0" +integer LINK_DICE_BRIDGE = -1; // "FURWARE text mesh:Dice:0:0" +integer LINK_LEADERBOARD_BRIDGE = -1; // "FURWARE text mesh:Leaderboard:0:0" + +// Macro to discover core links by name +#define DISCOVER_CORE_LINKS() { \ + integer _i; \ + integer _total = llGetNumberOfPrims(); \ + for (_i = 1; _i <= _total; _i++) { \ + string _name = llGetLinkName(_i); \ + if (_name == "Scoreboard:0:0") LINK_SCOREBOARD = _i; \ + else if (_name == "FURWARE text mesh:Dice:0:0") LINK_DICE_BRIDGE = _i; \ + else if (_name == "FURWARE text mesh:Leaderboard:0:0") LINK_LEADERBOARD_BRIDGE = _i; \ + else if (LINK_CONTROLLER == 1 && _i == 1) { /* Default to root if not found */ } \ + } \ +} + +// --- FURWARE Text Commands --- +#define FW_DATA "fw_data" +#define FW_CONF "fw_conf" +#define FW_READY "fw_ready" +#define FW_DONE "fw_done" + +// --- System/Debug (9000-9999) --- +#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 +#define dbg(msg) ; +#endif + +#define MSG_RESET_ALL -99999 +#define MSG_EMERGENCY_RESET -99998 +#define MSG_DEBUG_PICKS_ON 7001 +#define MSG_DEBUG_PICKS_OFF 7002 +#define MSG_UPDATE_MAIN_LISTS 9040 + +// --- Game Flow (3000-3099) --- +#define MSG_GAME_STATUS 3001 +#define MSG_PLAYER_UPDATE 3002 +#define MSG_CLEAR_GAME 3003 +#define MSG_REMOVE_PLAYER 3004 +#define MSG_UPDATE_PERIL_PLAYER 3005 +#define MSG_UPDATE_WINNER 3006 +#define MSG_GAME_WON 3010 +#define MSG_GAME_LOST 3011 +#define MSG_RESET_LEADERBOARD 3012 +#define MSG_DICE_ROLL 3020 +#define MSG_CLEAR_DICE 3021 +#define MSG_DICE_CLEAR 3021 // Alias for consistency in dice bridge +#define MSG_ROLL_RESULT 102 +#define MSG_PLAYER_WON 551 +#define MSG_ELIMINATE_PLAYER 999 +#define MSG_CONTINUE_GAME 998 +#define MSG_CONTINUE_ROUND 998 // Alias for consistency across scripts +#define MSG_EFFECT_CONFETTI 995 +#define MSG_GET_DICE_TYPE 1001 +#define MSG_DICE_TYPE_RESULT 1005 + +// --- Player/Registration (9000-9099) --- +#define MSG_REGISTER_PLAYER 106 // Legacy Registration ID (Backward Compatibility) +#define MSG_REGISTER_PLAYER_REQUEST 9050 // New Standard Registration ID +#define MSG_OWNER_MESSAGE 9030 +#define MSG_PUBLIC_MESSAGE 9031 +#define MSG_REGION_MESSAGE 9032 +#define MSG_DIALOG_REQUEST 9033 +#define MSG_DIALOG_FORWARD_REQUEST 9060 +#define MSG_SYNC_GAME_STATE 107 +#define MSG_SYNC_PICKQUEUE 2001 +#define MSG_SYNC_LIGHTWEIGHT 9070 +#define MSG_SYNC_LEGACY 9071 +#define MSG_SERIALIZE_GAME_STATE 9072 +#define MSG_LEAVE_GAME_REQUEST 8006 +#define MSG_LEAVE_GAME 8007 +#define MSG_REQUEST_PLAYER_LIST_KICK 8009 +#define MSG_LOCK_GAME 9001 +#define MSG_UNLOCK_GAME 9002 + +// --- Dialogs & Menus (100-399) --- +#define MSG_SHOW_MENU 201 +#define MSG_SHOW_DIALOG 101 +#define MSG_GET_CURRENT_DIALOG 302 +#define MSG_SHOW_ROLL_DIALOG 301 +#define MSG_CLOSE_ALL_DIALOGS -9999 +#define MSG_TOGGLE_READY 202 +#define MSG_QUERY_READY_STATE 210 +#define MSG_READY_STATE_RESULT 211 +#define MSG_QUERY_OWNER_STATUS 213 +#define MSG_OWNER_STATUS_RESULT 214 + +// --- Float Management (100-199) --- +#define MSG_REZ_FLOAT 105 +#define MSG_REZ_FLOATER 105 // Alias for consistency +#define MSG_UPDATE_FLOAT 103 +#define MSG_CLEANUP_FLOAT 104 +#define MSG_CLEANUP_ALL_FLOATERS 212 + +// --- Data Requests (200-299) --- +#define MSG_REQUEST_PLAYER_LIST_PICK 202 +#define MSG_PLAYER_LIST_PICK_RESULT 203 +#define MSG_PLAYER_LIST_RESULT 203 // Alias for consistency across scripts +#define MSG_PICK_ACTION 204 +#define MSG_PICK_LIST_RESULT 205 +#define MSG_OWNER_PICK_MANAGER 206 +#define MSG_LIFE_LOOKUP_REQUEST 207 +#define MSG_LIFE_LOOKUP_RESULT 208 +#define MSG_QUERY_OWNER_STATUS 213 + +// --- Memory Management (6000-6099) --- +#define MSG_MEMORY_CHECK 6001 +#define MSG_MEMORY_STATS 6002 +#define MSG_MEMORY_CLEANUP 6003 +#define MSG_MEMORY_REPORT 6004 +#define MSG_EMERGENCY_CLEANUP 6005 +#define MSG_MEMORY_STATS_REQUEST 6006 +#define MSG_MEMORY_STATS_RESULT 6007 + +// --- Bot Management --- +#define MSG_BOT_PICKED -9997 +#define MSG_HUMAN_PICKED -9998 +#define MSG_BOT_COMMAND -9999 + +// --- Helper Functions Support --- +#define MSG_GET_PICKS_REQUIRED 1002 +#define MSG_GET_PICKER_INDEX 1003 diff --git a/Peril_Dice_Updater.lsl b/Peril_Dice_Updater.lsl deleted file mode 100644 index 6fb6ca5..0000000 --- a/Peril_Dice_Updater.lsl +++ /dev/null @@ -1,281 +0,0 @@ -// ==================================================================== -// Peril Dice Updater - External Update Box -// ==================================================================== -// This script goes in a standalone object (the "Updater Box") -// It downloads scripts from GitHub and installs them into the game -// ==================================================================== - -#define CURRENT_VERSION "2.8.9" -#define GITHUB_API_URL "https://api.github.com/repos/RebeccaNod1/Peril/releases/latest" -#define GITHUB_RAW_URL "https://raw.githubusercontent.com/RebeccaNod1/Peril/main/" - -// Communication channels -#define UPDATER_CHANNEL -7723847 // Must match Update_Receiver.lsl - -// State tracking -key targetGame = NULL_KEY; -integer targetPin = 0; -list downloadQueue = []; -string currentFile = ""; -integer installIndex = 0; -integer isBusy = FALSE; - -// HTTP tracking -key currentHttpRequest = NULL_KEY; -string currentOperation = ""; - -// Script manifest - List of all scripts to update -// Format: "ScriptName.lsl|LinkNumber" -// Note: Link numbers will be verified via UUID request -list SCRIPT_MANIFEST = [ - "Main_Controller_Linkset.lsl|1", - "Game_Manager.lsl|1", - "Controller_Memory.lsl|1", - "Controller_MessageHandler.lsl|1", - "Player_RegistrationManager.lsl|1", - "Player_DialogHandler.lsl|1", - "NumberPicker_DialogHandler.lsl|1", - "Floater_Manager.lsl|1", - "Roll_ConfettiModule.lsl|1", - "Bot_Manager.lsl|1", - "Game_Calculator.lsl|1", - "Verbose_Logger.lsl|1", - "System_Debugger.lsl|1", - "Update_Receiver.lsl|1", // Updates the receiver itself! - "Game_Scoreboard_Manager_Linkset.lsl|12", - "Leaderboard_Communication_Linkset.lsl|35", - "XyzzyText_Dice_Bridge_Linkset.lsl|83", - "xyzzy_Master_script.lsl|XYZZY" // Special handling for xyzzy -]; - -// Map of Link Number -> UUID (populated by game response) -list linkUUIDs = []; // Format: [LinkNum, UUID, LinkNum, UUID...] - -// Helper to get UUID for a link number -key getLinkUUID(integer linkNum) { - integer idx = llListFindList(linkUUIDs, [linkNum]); - if (idx != -1) { - return llList2Key(linkUUIDs, idx + 1); - } - return NULL_KEY; -} - -// Manual JSON parsing (same as receiver) -string extractJsonValue(string json, string keyName) { - string quote = "\""; - string searchPattern = quote + keyName + quote + ":" + quote; - integer start = llSubStringIndex(json, searchPattern); - if (start == -1) return ""; - - integer valueStart = start + llStringLength(searchPattern); - integer valueEnd = llSubStringIndex(llGetSubString(json, valueStart, -1), quote); - if (valueEnd == -1) return ""; - - return llGetSubString(json, valueStart, valueStart + valueEnd - 1); -} - -// Start the update process for a specific game -startUpdate(key gameId, string version, integer pin) { - if (isBusy) { - llRegionSayTo(gameId, UPDATER_CHANNEL, "UPDATE_BUSY"); - return; - } - - isBusy = TRUE; - targetGame = gameId; - targetPin = pin; - - llOwnerSay("๐Ÿš€ Starting update for game " + (string)gameId); - llRegionSayTo(targetGame, UPDATER_CHANNEL, "UPDATE_STARTING|" + version); - - // Step 1: Request Link UUIDs from the game - // We need these to install scripts to specific links - llRegionSayTo(targetGame, UPDATER_CHANNEL, "REQUEST_LINK_UUIDS"); - - // Set timeout for UUID response - llSetTimerEvent(10.0); -} - -// Continue update after getting UUIDs -startDownloadSequence() { - installIndex = 0; - downloadNextScript(); -} - -downloadNextScript() { - if (installIndex >= llGetListLength(SCRIPT_MANIFEST)) { - // All done! - finishUpdate(); - return; - } - - string entry = llList2String(SCRIPT_MANIFEST, installIndex); - list parts = llParseString2List(entry, ["|"], []); - currentFile = llList2String(parts, 0); - string linkTarget = llList2String(parts, 1); - - llOwnerSay("๐Ÿ“ฅ Downloading " + currentFile + "..."); - - string url = GITHUB_RAW_URL + currentFile; - currentOperation = "download_script"; - currentHttpRequest = llHTTPRequest(url, [HTTP_METHOD, "GET"], ""); -} - -installScript(string content) { - string scriptEntry = llList2String(SCRIPT_MANIFEST, installIndex); - list scriptParts = llParseString2List(scriptEntry, ["|"], []); - string filename = llList2String(scriptParts, 0); - string linkTargetStr = llList2String(scriptParts, 1); - - llOwnerSay("๐Ÿ’พ Installing " + filename + "..."); - - // Determine target UUIDs - list targets = []; - key targetUUID; - - if (linkTargetStr == "XYZZY") { - // Install to all xyzzy prims (links 35-82) - integer i; - for (i = 35; i <= 82; i++) { - targetUUID = getLinkUUID(i); - if (targetUUID != NULL_KEY) targets += [targetUUID]; - } - } else { - integer linkNum = (integer)linkTargetStr; - if (linkNum == 1) { - targets = [targetGame]; // Root prim is the game object itself - } else { - targetUUID = getLinkUUID(linkNum); - if (targetUUID != NULL_KEY) targets += [targetUUID]; - } - } - - if (llGetListLength(targets) == 0) { - llOwnerSay("โš ๏ธ No valid targets found for " + filename + " (Target: " + linkTargetStr + ")"); - // Skip but continue - installIndex++; - downloadNextScript(); - return; - } - - // Install to all targets - integer t; - for (t = 0; t < llGetListLength(targets); t++) { - key target = llList2Key(targets, t); - llRemoteLoadScriptPin(target, filename, targetPin, TRUE, 0); - } - - // Move to next - installIndex++; - downloadNextScript(); -} - -finishUpdate() { - llOwnerSay("โœ… Update sequence complete!"); - llRegionSayTo(targetGame, UPDATER_CHANNEL, "UPDATE_COMPLETE|" + CURRENT_VERSION); - - isBusy = FALSE; - targetGame = NULL_KEY; - targetPin = 0; - linkUUIDs = []; -} - -default { - state_entry() { - llSetText("๐Ÿ“ฆ Peril Dice Updater\nv" + CURRENT_VERSION + "\nTouch to check version", <0,1,0>, 1.0); - llListen(UPDATER_CHANNEL, "", NULL_KEY, ""); - } - - touch_start(integer num) { - llOwnerSay("๐Ÿ” Checking GitHub for latest version..."); - currentOperation = "version_check"; - currentHttpRequest = llHTTPRequest(GITHUB_API_URL, [HTTP_METHOD, "GET"], ""); - } - - listen(integer channel, string name, key id, string message) { - if (channel == UPDATER_CHANNEL) { - list parts = llParseString2List(message, ["|"], []); - string command = llList2String(parts, 0); - - if (command == "PING_UPDATER") { - llRegionSayTo(id, UPDATER_CHANNEL, "UPDATER_AVAILABLE|" + CURRENT_VERSION); - } - else if (command == "UPDATE_REQUEST") { - string version = llList2String(parts, 1); // Version requested (not used currently, we install latest) - integer pin = (integer)llList2String(parts, 2); - startUpdate(id, version, pin); - } - else if (command == "LINK_UUIDS_RESPONSE") { - if (id != targetGame) return; - - // Parse UUIDs: "12:uuid,35:uuid,..." - string payload = llList2String(parts, 1); - list pairs = llParseString2List(payload, [","], []); - - integer i; - for (i = 0; i < llGetListLength(pairs); i++) { - string pair = llList2String(pairs, i); - list kv = llParseString2List(pair, [":"], []); - if (llGetListLength(kv) == 2) { - integer ln = (integer)llList2String(kv, 0); - key uid = (key)llList2String(kv, 1); - - // Update or add - integer idx = llListFindList(linkUUIDs, [ln]); - if (idx != -1) { - linkUUIDs = llListReplaceList(linkUUIDs, [uid], idx+1, idx+1); - } else { - linkUUIDs += [ln, uid]; - } - } - } - - // Reset timer as we got data - llSetTimerEvent(5.0); // Wait a bit more for other chunks if any - - // If we have enough critical data, we can start - // For now, let's just wait for the timer to trigger the start - // This allows multiple chunks to arrive - } - } - } - - timer() { - llSetTimerEvent(0.0); - - if (isBusy && installIndex == 0) { - // Timer fired while waiting for UUIDs - if (llGetListLength(linkUUIDs) > 0) { - llOwnerSay("โœ… Received link data. Starting installation..."); - startDownloadSequence(); - } else { - llOwnerSay("โŒ Timed out waiting for Link UUIDs."); - llRegionSayTo(targetGame, UPDATER_CHANNEL, "UPDATE_FAILED|Timeout waiting for link data"); - isBusy = FALSE; - } - } - } - - http_response(key request_id, integer status, list metadata, string body) { - if (request_id != currentHttpRequest) return; - - if (currentOperation == "version_check") { - if (status == 200) { - string latestVersion = extractJsonValue(body, "tag_name"); - llOwnerSay("๐Ÿ“Š Latest GitHub Version: " + latestVersion); - llOwnerSay("๐Ÿ“ฆ Updater Version: v" + CURRENT_VERSION); - } else { - llOwnerSay("โŒ GitHub check failed: " + (string)status); - } - } - else if (currentOperation == "download_script") { - if (status == 200) { - installScript(body); - } else { - llOwnerSay("โŒ Failed to download " + currentFile + ": " + (string)status); - llRegionSayTo(targetGame, UPDATER_CHANNEL, "UPDATE_FAILED|Download failed for " + currentFile); - isBusy = FALSE; - } - } - } -} diff --git a/PlayerStatus_Float.lsl b/PlayerStatus_Float.lsl index d3df704..bd58bbe 100644 --- a/PlayerStatus_Float.lsl +++ b/PlayerStatus_Float.lsl @@ -1,291 +1,99 @@ +#include "Peril_Constants.lsl" + // === Player Status Float - Enhanced Display === -// Helper function to get display name with fallback to username -string getPlayerName(key id) { - string displayName = llGetDisplayName(id); - if (displayName == "") { - // Fallback to legacy username if display name is unavailable - displayName = llKey2Name(id); - } - return displayName; -} key target; string displayText; string myName; - -// Listen handle management integer listenHandle = -1; -#define MSG_SYNC_GAME_STATE 107 - -list lives; -list picksData; -string perilPlayer; -list names; - -// Memory reporting function +// Memory reporting function - defined before use reportMemoryUsage(string scriptName) { integer used = llGetUsedMemory(); integer free = llGetFreeMemory(); integer total = used + free; float percentUsed = ((float)used / (float)total) * 100.0; - llOwnerSay("๐Ÿง  [" + scriptName + "] Memory: " + + dbg("๐Ÿง  [" + scriptName + "] Memory: " + (string)used + " used, " + (string)free + " free (" + llGetSubString((string)percentUsed, 0, 4) + "% used)"); } -list getPicksFor(string nameInput) { - integer i; - for (i = 0; i < llGetListLength(picksData); i++) { - string entry = llList2String(picksData, i); - list parts = llParseString2List(entry, ["|"], []); - if (llGetListLength(parts) >= 2 && llList2String(parts, 0) == nameInput) { - string picks = llList2String(parts, 1); - if (picks == "") { - return []; - } - // Check for corruption markers (^ symbols that shouldn't be in picks) - if (llSubStringIndex(picks, "^") != -1) { - return []; - } - // Convert semicolons back to commas, then parse - picks = llDumpList2String(llParseString2List(picks, [";"], []), ","); - return llParseString2List(picks, [","], []); - } - } - return []; -} - default { state_entry() { - // Get player name from object description if available - string playerName = llGetObjectDesc(); - if (playerName == "" || playerName == "(No Description)") { - playerName = "Unknown"; - } - reportMemoryUsage("๐Ÿ“ฑ Player Float (" + playerName + ")"); + string currentDesc = llGetObjectDesc(); + if (currentDesc == "" || currentDesc == "(No Description)") currentDesc = "Unknown"; + + reportMemoryUsage("๐Ÿ“ฑ Player Float (" + currentDesc + ")"); llSetText("โณ Waiting...", <1,1,1>, 1.0); llSetTimerEvent(1.0); } on_rez(integer start_param) { - // Clean up any existing listeners - if (listenHandle != -1) { - llListenRemove(listenHandle); - } - - // Set up managed listener + if (listenHandle != -1) llListenRemove(listenHandle); listenHandle = llListen(start_param, "", NULL_KEY, ""); - - // Initialize myName to empty - will be set when SET_NAME message is received myName = ""; - - // Don't report memory immediately - wait for proper name to be set -// PlayerStatus_Float ready and listening + // Correct LSL function name is llSetRemoteScriptAccessPin + llSetRemoteScriptAccessPin(1337); } listen(integer channel, string name, key id, string message) { - if (llSubStringIndex(message, "FLOAT:") == 0) { + // Universal Floater API - Format: CMD:avKey|T=Text|G=Glow|C=Color|H=HeartTexture + if (llSubStringIndex(message, "CMD:") == 0) { list parts = llParseString2List(message, ["|"], []); - if (llGetListLength(parts) >= 2) { - target = (key)llGetSubString(llList2String(parts, 0), 6, -1); - displayText = llList2String(parts, 1); - - // Extract life count from the display text to set heart texture - // Look for "Lives: X" pattern in the text - integer livesPos = llSubStringIndex(displayText, "Lives: "); - if (livesPos != -1) { - string livesStr = llGetSubString(displayText, livesPos + 7, livesPos + 7); - integer lifeCount = (integer)livesStr; - string heartTexture = (string)lifeCount + "_hearts"; - // Set heart texture based on current life count - llSetTexture(heartTexture, 1); // Face 1 (right side) - llSetTexture(heartTexture, 2); // Face 2 (back) - llSetTexture(heartTexture, 3); // Face 3 (left side) - llSetTexture(heartTexture, 4); // Face 4 (front) + if (llGetListLength(parts) < 1) return; + + string header = llList2String(parts, 0); + target = (key)llGetSubString(header, 4, -1); + + integer i; + for (i = 1; i < llGetListLength(parts); i++) { + string pair = llList2String(parts, i); + integer eqIdx = llSubStringIndex(pair, "="); + if (eqIdx != -1) { + string keyStr = llGetSubString(pair, 0, eqIdx - 1); + string valStr = llGetSubString(pair, eqIdx + 1, -1); - // IMPROVED: Special visual treatment for elimination (0 hearts) - if (lifeCount == 0) { - // Make 0 hearts more prominent with red glow + if (keyStr == "T") { + displayText = valStr; + llSetText(displayText, <1,1,1>, 1.0); + } + else if (keyStr == "G") { + llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_GLOW, ALL_SIDES, (float)valStr]); + } + else if (keyStr == "C") { + llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_COLOR, ALL_SIDES, (vector)valStr, 1.0]); + } + else if (keyStr == "H") { llSetLinkPrimitiveParamsFast(LINK_THIS, [ - PRIM_GLOW, ALL_SIDES, 0.3, - PRIM_COLOR, ALL_SIDES, <1.0, 0.0, 0.0>, 1.0 + PRIM_TEXTURE, 1, valStr, <1,1,0>, ZERO_VECTOR, 0.0, + PRIM_TEXTURE, 2, valStr, <1,1,0>, ZERO_VECTOR, 0.0, + PRIM_TEXTURE, 3, valStr, <1,1,0>, ZERO_VECTOR, 0.0, + PRIM_TEXTURE, 4, valStr, <1,1,0>, ZERO_VECTOR, 0.0 ]); - - // Modify display text to show elimination status - displayText = llDumpList2String(llParseString2List(displayText, ["\n"], []), "\n") + "\n๐Ÿ’€ ELIMINATED! ๐Ÿ’€"; - } else { - // Check for winner status first (highest priority) - integer isWinner = FALSE; - if (llSubStringIndex(displayText, "โœจ ULTIMATE VICTORY!") != -1 || - llSubStringIndex(displayText, "ULTIMATE SURVIVOR") != -1) { - isWinner = TRUE; - } - - // Check if this player is in peril for yellow glow - integer isInPeril = FALSE; - if (llSubStringIndex(displayText, "โšก YOU ARE IN PERIL! โšก") != -1) { - isInPeril = TRUE; - } - - if (isWinner) { - // Green glow for winner (overrides peril) - llSetLinkPrimitiveParamsFast(LINK_THIS, [ - PRIM_GLOW, ALL_SIDES, 0.3, - PRIM_COLOR, ALL_SIDES, <0.0, 1.0, 0.0>, 1.0 // Green tint - ]); - } else if (isInPeril) { - // Yellow glow for peril player - llSetLinkPrimitiveParamsFast(LINK_THIS, [ - PRIM_GLOW, ALL_SIDES, 0.2, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 0.0>, 1.0 // Yellow tint - ]); - } else { - // Normal colors for living players - remove glow - llSetLinkPrimitiveParamsFast(LINK_THIS, [ - PRIM_GLOW, ALL_SIDES, 0.0, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0 - ]); - llSetColor(<0.2, 0.2, 0.2>, 0); // Dark gray for top - llSetColor(<0.2, 0.2, 0.2>, 5); // Dark gray for bottom - } } - - // Remove "Lives: X" from display text since hearts show it - list lines = llParseString2List(displayText, ["\n"], []); - list filteredLines = []; - integer i; - for (i = 0; i < llGetListLength(lines); i++) { - string line = llList2String(lines, i); - if (llSubStringIndex(line, "Lives: ") != 0) { - filteredLines += [line]; - } - } - displayText = llDumpList2String(filteredLines, "\n"); } - - llSetText(displayText, <1,1,1>, 1.0); } } else if (message == "CLEANUP") { - llOwnerSay("๐ŸชŸ Cleaning up..."); llDie(); } else if (llSubStringIndex(message, "SET_NAME:") == 0) { myName = llGetSubString(message, 9, -1); - // Name successfully received and set - // Update the object description as well for consistency llSetObjectDesc(myName); - - // Now report memory with the proper player name reportMemoryUsage("๐Ÿ“ฑ " + myName); } } - link_message(integer sender, integer num, string str, key id) { - if (num == MSG_SYNC_GAME_STATE) { - list parts = llParseString2List(str, ["~"], []); - if (llGetListLength(parts) < 4) return; - lives = llCSV2List(llList2String(parts, 0)); - - // Use ^ delimiter for picksData to match the main system - string picksDataStr = llList2String(parts, 1); - if (picksDataStr == "" || picksDataStr == "EMPTY") { - picksData = []; - } else { - picksData = llParseString2List(picksDataStr, ["^"], []); - } - - string receivedPeril = llList2String(parts, 2); - if (receivedPeril == "NONE") { - perilPlayer = ""; // Convert placeholder back to empty - } else { - perilPlayer = receivedPeril; - } - names = llCSV2List(llList2String(parts, 3)); - - integer nameIdx = llListFindList(names, [myName]); - if (nameIdx == -1) return; - - list picks = getPicksFor(myName); - integer lifeCount = llList2Integer(lives, nameIdx); - - string perilDisplay; - // If the perilPlayer string is empty or contains a comma (indicating - // multiple names), treat the game as not yet started. This avoids - // showing multiple players as the peril player before the first - // round begins. Once a single peril player is assigned, it will - // not contain a comma and will be displayed normally. - if (perilPlayer == "" || llSubStringIndex(perilPlayer, ",") != -1) { - perilDisplay = "Waiting for game to start..."; - } else { - perilDisplay = perilPlayer; - } - - string picksDisplay = llList2CSV(picks); - - // IMPROVED: Set heart texture and handle elimination display - string heartTexture = (string)lifeCount + "_hearts"; - llSetTexture(heartTexture, 1); // Face 1 (right side) - llSetTexture(heartTexture, 2); // Face 2 (back) - llSetTexture(heartTexture, 3); // Face 3 (left side) - llSetTexture(heartTexture, 4); // Face 4 (front) - - string txt; - if (lifeCount == 0) { - // Special treatment for elimination - red glow and elimination message - llSetLinkPrimitiveParamsFast(LINK_THIS, [ - PRIM_GLOW, ALL_SIDES, 0.3, - PRIM_COLOR, ALL_SIDES, <1.0, 0.0, 0.0>, 1.0 - ]); - - txt = "๐ŸŽฒ Peril Dice\n๐Ÿ‘ค " + myName + "\n๐Ÿ’€ ELIMINATED! ๐Ÿ’€\n๐Ÿ”ข Final Picks: " + picksDisplay; - // Set text to red color as well for extra visibility - llSetText(txt, <1.0, 0.2, 0.2>, 1.0); - return; // Skip normal text setting below - } else { - // Check if this is the winner (highest priority) - happens when only 1 player left - integer isWinner = (llGetListLength(names) <= 1 && myName != "" && llListFindList(names, [myName]) != -1); - - // Check if this player is the peril player for yellow glow - integer isInPeril = (myName == perilPlayer && perilPlayer != "" && llSubStringIndex(perilPlayer, ",") == -1); - - if (isWinner) { - // Green glow for winner (overrides peril) - llSetLinkPrimitiveParamsFast(LINK_THIS, [ - PRIM_GLOW, ALL_SIDES, 0.3, - PRIM_COLOR, ALL_SIDES, <0.0, 1.0, 0.0>, 1.0 // Green tint - ]); - } else if (isInPeril) { - // Yellow glow for peril player - llSetLinkPrimitiveParamsFast(LINK_THIS, [ - PRIM_GLOW, ALL_SIDES, 0.2, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 0.0>, 1.0 // Yellow tint - ]); - } else { - // Normal colors for living players - remove glow - llSetLinkPrimitiveParamsFast(LINK_THIS, [ - PRIM_GLOW, ALL_SIDES, 0.0, - PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0 - ]); - llSetColor(<0.2, 0.2, 0.2>, 0); // Dark gray for top - llSetColor(<0.2, 0.2, 0.2>, 5); // Dark gray for bottom - } - - txt = "๐ŸŽฒ Peril Dice\n๐Ÿ‘ค " + myName + "\n๐Ÿข Peril: " + perilDisplay + "\n๐Ÿ”ข Picks: " + picksDisplay; - } - - llSetText(txt, <1,1,1>, 1.0); - } - } - timer() { - if (target != NULL_KEY && getPlayerName(target) != "") { - vector pos = llList2Vector(llGetObjectDetails(target, [OBJECT_POS]), 0) + <1,0,1>; - llSetRegionPos(pos); + if (target != NULL_KEY) { + list details = llGetObjectDetails(target, [OBJECT_POS]); + if (llGetListLength(details) > 0) { + vector pos = llList2Vector(details, 0) + <1,0,1>; + llSetRegionPos(pos); + } } } } diff --git a/Player_DialogHandler.lsl b/Player_DialogHandler.lsl index 91455dc..a927418 100644 --- a/Player_DialogHandler.lsl +++ b/Player_DialogHandler.lsl @@ -1,9 +1,4 @@ -#define DEBUG_LOGS 0 // Set to 1 to enable logs, 0 to STRIP from memory completely -#if DEBUG_LOGS -#define dbg(msg) dbg(msg) -#else -#define dbg(msg) -#endif +#include "Peril_Constants.lsl" // === Dialog Handler (Owner & Player) with unified Ready/Leave menu and join support === @@ -24,7 +19,7 @@ string getPlayerName(key id) { // DYNAMIC CHANNEL CONFIGURATION // ============================================================================= -// Base channel offset - should match Main.lsl +// Base channel offset must match Main.lsl #define CHANNEL_BASE -77000 // Calculate channels dynamically to avoid hardcoded conflicts @@ -32,7 +27,8 @@ integer calculateChannel(integer offset) { // Use BOTH owner's key AND object's key to make channels unique per game instance // This prevents interference when same owner has multiple game tables string ownerStr = (string)llGetOwner(); - string objectStr = (string)llGetKey(); + // CRITICAL: Always use root prim (1) for channel calculation so scripts in child prims match + string objectStr = (string)llGetLinkKey(1); string combinedStr = ownerStr + objectStr; // Create a more unique hash using both keys @@ -80,21 +76,6 @@ rotation controller_rot; list foundDisplays; // Store found display info: [type, key, offset, rotation, ...] integer scanInProgress = FALSE; -#define MSG_SHOW_MENU 201 -#define MSG_PICK_ACTION 204 -#define MSG_PLAYER_LIST_RESULT 203 -#define MSG_PICK_LIST_RESULT 205 -#define MSG_LIFE_LOOKUP 207 -#define MSG_REGISTER_PLAYER 106 -#define MSG_REZ_FLOAT 105 -#define MSG_TOGGLE_READY 202 -#define MSG_QUERY_READY_STATE 210 -#define MSG_READY_STATE_RESULT 211 -#define MSG_CLEANUP_ALL_FLOATERS 212 -#define MSG_QUERY_OWNER_STATUS 213 -#define MSG_OWNER_STATUS_RESULT 214 -#define MSG_GET_CURRENT_DIALOG 302 - // Categorized owner menu system list mainOwnerOptions = ["๐Ÿ‘ฅ Player Management", "๐Ÿ”„ Reset Options", "๐Ÿ› ๏ธ Troubleshooting", "โฌ…๏ธ Back to Game"]; list mainOwnerOptionsLocked = ["๐Ÿ‘ฅ Player Management", "๐Ÿ”„ Reset Options", "๐Ÿ› ๏ธ Troubleshooting", "๐Ÿ”“ Unlock Game", "โฌ…๏ธ Back to Game"]; @@ -103,7 +84,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", "๐Ÿ” Toggle Verbose Logs", "โฌ…๏ธ Back to Main"]; +list troubleshootingOptions = ["Cleanup Floaters", "Force Floaters", "โฌ…๏ธ Back to Main"]; // State tracking for dynamic ready menu with race condition protection key pendingMenuPlayer = NULL_KEY; @@ -125,7 +106,7 @@ reportMemoryUsage(string scriptName) { integer total = used + free; float percentUsed = ((float)used / (float)total) * 100.0; - llOwnerSay("๐Ÿง  [" + scriptName + "] Memory: " + + dbg("๐Ÿง  [" + scriptName + "] Memory: " + (string)used + " used, " + (string)free + " free (" + llGetSubString((string)percentUsed, 0, 4) + "% used)"); @@ -420,7 +401,7 @@ default { link_message(integer sender, integer num, string str, key id) { // Handle full reset from main controller - if (num == -99999 && str == "FULL_RESET") { + if (num == MSG_RESET_ALL && str == "FULL_RESET") { // Reset dialog state including race condition protection pendingMenuPlayer = NULL_KEY; pendingMenuIsStarter = FALSE; @@ -496,7 +477,7 @@ default { showPickManageMenu(id, playerNames); } // Handle player list result for kick functionality - else if (num == 8009) { + 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)); @@ -527,12 +508,12 @@ default { if (llStringTrim(returnedName, STRING_TRIM) == llStringTrim(currentPickTarget, STRING_TRIM)) { dbg("โœ… Matched pick list for: " + returnedName); currentPickList = llCSV2List(picks); - llMessageLinked(LINK_THIS, MSG_LIFE_LOOKUP, currentPickTarget, id); + llMessageLinked(LINK_THIS, MSG_LIFE_LOOKUP_REQUEST, currentPickTarget, id); } else { dbg("โŒ Name mismatch: got " + returnedName + " but expected " + currentPickTarget); } } - else if (num == MSG_LIFE_LOOKUP) { + else if (num == MSG_LIFE_LOOKUP_REQUEST) { list parts = llParseString2List(str, ["|"], []); if (llList2String(parts, 0) == currentPickTarget) { currentPickLimit = (integer)llList2String(parts, 1); @@ -609,7 +590,7 @@ default { if (id == gameOwner) { isLocked = TRUE; // Notify Main Controller to update floating text - llMessageLinked(LINK_SET, 9001, "LOCK_GAME", id); + llMessageLinked(LINK_SET, MSG_LOCK_GAME, "LOCK_GAME", id); dbg("๐Ÿ”’ Game has been LOCKED - Only owner can access any features"); llSay(0, "๐Ÿ”’ Game has been locked by the owner. Only the owner can access any features."); showOwnerMenu(id); // Refresh the owner menu @@ -621,7 +602,7 @@ default { if (id == gameOwner) { isLocked = FALSE; // Notify Main Controller to update floating text - llMessageLinked(LINK_SET, 9002, "UNLOCK_GAME", id); + llMessageLinked(LINK_SET, MSG_UNLOCK_GAME, "UNLOCK_GAME", id); dbg("๐Ÿ”“ Game has been UNLOCKED - All players can access menus"); llSay(0, "๐Ÿ”“ Game has been unlocked by the owner. All features are now available."); showOwnerMenu(id); // Refresh the owner menu @@ -664,13 +645,13 @@ default { else if (msg == "Join Game") { // Use the avatar's key as the identifier string pname = getPlayerName(id); - llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, pname + "|" + (string)id, NULL_KEY); + llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER_REQUEST, pname + "|" + (string)id, id); // Floater Manager will automatically rez the floater during registration } else if (msg == "Leave Game") { // Send leave game message to Controller_MessageHandler (not sync channel 107!) string pname = getPlayerName(id); - llMessageLinked(LINK_SET, 8007, "LEAVE_GAME|" + pname + "|" + (string)id, NULL_KEY); + llMessageLinked(LINK_SET, MSG_LEAVE_GAME, "LEAVE_GAME|" + pname + "|" + (string)id, id); } else if (msg == "Ready" || msg == "Not Ready") { // Toggle ready state (both buttons do the same thing - toggle) @@ -679,7 +660,7 @@ default { } else if (msg == "Kick Player") { // Show kick player menu - request player list first - llMessageLinked(LINK_SET, 8009, "REQUEST_PLAYER_LIST_KICK", id); + llMessageLinked(LINK_SET, MSG_REQUEST_PLAYER_LIST_KICK, "REQUEST_PLAYER_LIST_KICK", id); } else if (msg == "Cleanup Floaters") { // Universal floater cleanup that works even after script resets @@ -692,35 +673,28 @@ 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 - dbg("๐Ÿ” 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); + llMessageLinked(LINK_THIS, MSG_REQUEST_PLAYER_LIST_PICK, "REQUEST_PLAYER_LIST", id); } else if (llSubStringIndex(msg, "๐Ÿ›  ") == 0) { currentPickTarget = llStringTrim(llDeleteSubString(msg, 0, 1), STRING_TRIM); dbg("๐ŸŽฏ Pick target set to: " + currentPickTarget); currentPickList = []; - llMessageLinked(LINK_THIS, 206, currentPickTarget, id); + llMessageLinked(LINK_THIS, MSG_OWNER_PICK_MANAGER, currentPickTarget, id); } else if (msg == "Add Pick") { askForNewPick(id); } else if (msg == "โฌ…๏ธ Back") { - llMessageLinked(LINK_THIS, 202, "REQUEST_PLAYER_LIST", id); + llMessageLinked(LINK_THIS, MSG_REQUEST_PLAYER_LIST_PICK, "REQUEST_PLAYER_LIST", id); } else if (llSubStringIndex(msg, "REMOVE: ") == 0) { string pick = llGetSubString(msg, 8, -1); string payload = "REMOVE_PICK~" + currentPickTarget + "|" + pick; llMessageLinked(LINK_THIS, MSG_PICK_ACTION, payload, id); llSleep(0.2); - llMessageLinked(LINK_THIS, 206, currentPickTarget, id); + llMessageLinked(LINK_THIS, MSG_OWNER_PICK_MANAGER, currentPickTarget, id); } // Handle kick player selection else if (llSubStringIndex(msg, "๐Ÿ‘ข ") == 0) { @@ -730,7 +704,7 @@ default { string playerToKick = llList2String(kickOriginalNames, idx); llRegionSayTo(id, 0, "๐Ÿ‘ข Kicking player: " + playerToKick + " from the game..."); // Send kick message to Controller_MessageHandler (not sync channel 107!) - llMessageLinked(LINK_SET, 8007, "KICK_PLAYER|" + playerToKick + "|" + (string)id, NULL_KEY); + llMessageLinked(LINK_SET, MSG_LEAVE_GAME, "KICK_PLAYER|" + playerToKick + "|" + (string)id, id); } else { dbg("โš ๏ธ Could not find original name for kick selection: " + msg); } @@ -759,7 +733,7 @@ default { string payload = "ADD_PICK~" + currentPickTarget + "|" + msg; llMessageLinked(LINK_THIS, MSG_PICK_ACTION, payload, id); llSleep(0.2); - llMessageLinked(LINK_THIS, 206, currentPickTarget, id); + llMessageLinked(LINK_THIS, MSG_OWNER_PICK_MANAGER, currentPickTarget, id); } // Handle position scan responses from follower displays else if ((channel == SCOREBOARD_DATA_CHANNEL || channel == LEADERBOARD_DATA_CHANNEL || channel == DICE_DATA_CHANNEL) && diff --git a/Player_RegistrationManager.lsl b/Player_RegistrationManager.lsl index 576391a..0646568 100644 --- a/Player_RegistrationManager.lsl +++ b/Player_RegistrationManager.lsl @@ -1,25 +1,9 @@ +#include "Peril_Constants.lsl" + // === Player Registration Manager === // Handles all player registration, join/leave operations to reduce Main Controller memory usage // This script takes over the memory-heavy player management operations -// Message constants for communication -#define MSG_REGISTER_PLAYER_REQUEST 9050 // Dedicated message from Main Controller for registration -#define MSG_OWNER_MESSAGE 9030 -#define MSG_PUBLIC_MESSAGE 9031 -#define MSG_REGION_MESSAGE 9032 -#define MSG_SHOW_MENU 201 -#define MSG_UPDATE_MAIN_LISTS 9040 // Optimized message to update main controller lists -#define MSG_SYNC_GAME_STATE 107 - -// Scoreboard and display message constants -#define SCOREBOARD_LINK 12 -#define MSG_PLAYER_UPDATE 3002 - -// Dialog forwarding constants -#define MSG_SHOW_DIALOG 101 -#define MSG_SHOW_ROLL_DIALOG 301 -#define MSG_DIALOG_FORWARD_REQUEST 9060 // New message for dialog forwarding requests - // Game state tracking (synced from Main Controller) list players = []; list names = []; @@ -37,7 +21,7 @@ reportMemoryUsage(string scriptName) { integer total = used + free; float percentUsed = ((float)used / (float)total) * 100.0; - llOwnerSay("๐Ÿง  [" + scriptName + "] Memory: " + + dbg("๐Ÿง  [" + scriptName + "] Memory: " + (string)used + " used, " + (string)free + " free (" + llGetSubString((string)percentUsed, 0, 4) + "% used)"); @@ -68,7 +52,7 @@ regionMsg(key player, string msg) { handlePlayerRegistration(string regData, key requesterId) { // MEMORY DEBUG: Check memory at start integer memStart = llGetFreeMemory(); - llOwnerSay("๐Ÿง  [RegMgr] Memory at start: " + (string)memStart + " free"); + dbg("๐Ÿ” [RegMgr] Memory at start: " + (string)memStart + " free"); list parts = llParseString2List(regData, ["|"], []); string newName = llList2String(parts, 0); @@ -102,7 +86,7 @@ handlePlayerRegistration(string regData, key requesterId) { // MEMORY DEBUG: Check memory after adding to lists integer memAfterLists = llGetFreeMemory(); - llOwnerSay("๐Ÿง  [RegMgr] Memory after adding to lists: " + (string)memAfterLists + " free (used " + (string)(memStart - memAfterLists) + ")"); + dbg("๐Ÿ” [RegMgr] Memory after adding to lists: " + (string)memAfterLists + " free (used " + (string)(memStart - memAfterLists) + ")"); // Count human players to determine if this is the starter (needed for both bot and human logic) integer humanCount = 0; @@ -133,7 +117,7 @@ handlePlayerRegistration(string regData, key requesterId) { // OPTIMIZED: Handle all heavy processing here to minimize Main Controller work // Send direct scoreboard update (Main Controller doesn't need to do this) - llMessageLinked(SCOREBOARD_LINK, MSG_PLAYER_UPDATE, newName + "|3|" + (string)newKey, NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_PLAYER_UPDATE, newName + "|3|" + (string)newKey, NULL_KEY); // Prepare sync message (Main Controller doesn't need to build this) string syncMessage = ""; @@ -154,7 +138,7 @@ handlePlayerRegistration(string regData, key requesterId) { llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, syncMessage, NULL_KEY); // CRITICAL FIX: Send floater rez message to Floater Manager - llMessageLinked(LINK_SET, 106, newName + "|" + (string)newKey, newKey); + llMessageLinked(LINK_SET, MSG_REZ_FLOATER, newName + "|" + (string)newKey, newKey); // Send minimal update to Main Controller - just the essential data it needs string updateData = (string)ch + "~" + newName; @@ -171,13 +155,13 @@ handlePlayerRegistration(string regData, key requesterId) { default { state_entry() { - reportMemoryUsage("Player Registration Manager"); - llOwnerSay("๐ŸŽฏ Player Registration Manager ready - handling player join/leave operations"); + DISCOVER_CORE_LINKS(); + dbg("๐ŸŽฏ [RegMgr] Player Registration Manager ready - discovery complete! Scoreboard: " + (string)LINK_SCOREBOARD); } on_rez(integer start_param) { - reportMemoryUsage("Player Registration Manager"); - llOwnerSay("๐ŸŽฏ Player Registration Manager rezzed - ready for player management"); + DISCOVER_CORE_LINKS(); + dbg("๐ŸŽฏ [RegMgr] Player Registration Manager rezzed - resetting state..."); // Reset local state players = []; names = []; @@ -215,7 +199,7 @@ default { } // KEEP our player keys - they are authoritative - llOwnerSay("๐Ÿ”„ [RegMgr] Synced game state, preserving " + (string)llGetListLength(players) + " player keys"); + dbg("๐Ÿ”„ [RegMgr] Synced game state, preserving " + (string)llGetListLength(players) + " player keys"); } return; } @@ -242,30 +226,30 @@ default { if (playerKey != NULL_KEY) { if (dialogType == "SHOW_DIALOG") { llMessageLinked(LINK_SET, MSG_SHOW_DIALOG, dialogPayload, playerKey); - llOwnerSay("๐Ÿ“‹ [RegMgr] Forwarded dialog to " + targetPlayerName + ": " + dialogPayload); + dbg("๐Ÿ“‹ [RegMgr] Forwarded dialog to " + targetPlayerName + ": " + dialogPayload); } else if (dialogType == "SHOW_ROLL_DIALOG") { llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, dialogPayload, playerKey); - llOwnerSay("๐ŸŽฒ [RegMgr] Forwarded roll dialog to " + targetPlayerName); + dbg("๐ŸŽฒ [RegMgr] Forwarded roll dialog to " + targetPlayerName); } } else { - llOwnerSay("โš ๏ธ [RegMgr] Player " + targetPlayerName + " has NULL_KEY"); + dbg("โš ๏ธ [RegMgr] Player " + targetPlayerName + " has NULL_KEY"); } } else { - llOwnerSay("โš ๏ธ [RegMgr] Player " + targetPlayerName + " not found in registry"); + dbg("โš ๏ธ [RegMgr] Player " + targetPlayerName + " not found in registry"); } } return; } - // Handle reset - if (num == -99999 && str == "FULL_RESET") { + if (num == MSG_RESET_ALL && str == "FULL_RESET") { + players = []; names = []; lives = []; readyPlayers = []; roundStarted = FALSE; gameStarting = FALSE; - llOwnerSay("๐ŸŽฏ Player Registration Manager reset complete"); + dbg("๐ŸŽฏ [RegMgr] Player Registration Manager reset complete"); return; } } diff --git a/README.md b/README.md index 8134c96..cac4b20 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ # ๐ŸŽฒ Peril Dice โ€” Professional Single Linkset Game System for Second Life **Created by Rebecca Nod and Noose the Bunny** -**Current Version: 3.0.0 - Commercial CasperVend Release** +**Current Version: 3.1.0 - The "Linkset Stability" Update** ## Overview Peril Dice is a multiplayer elimination game where each player selects numbers before a die is rolled. If the peril player's number is rolled, they lose a life. Players are eliminated when they reach zero lives. -**๐Ÿš€ NEW in v3.0.0**: **COMMERCIAL CASPERVEND RELEASE & MEMORY OPTIMIZATION** - Fully integrated with the commercial CasperVend delivery system. Over 170 legacy debug mechanisms were stripped using an advanced pre-processor macro to free critical memory for 10-player stability. Completely eliminated Z-fighting UI alignment glitches with new `on_rez` state synchronization routines. +**๐Ÿš€ NEW in v3.1.0**: **LINKSET STABILITY & DYNAMIC DISCOVERY** - Complete architectural decentralization. Scripts now discovery their linked prims by name at runtime (`Scoreboard:0:0`, `profile:R:C`), making the game board immune to link-order changes or region-side glitches. Full migration from legacy XYText to modern mesh-based **FURWARE Text** for dice and leaderboards. + +**๐Ÿ“ฆ NEW in v3.0.0**: **COMMERCIAL CASPERVEND RELEASE & EXPERIENCE LEADERBOARD** **๐Ÿ” NEW in v2.8.7**: **ENHANCED LINKSCANNER SYSTEM** - Complete 84-prim structure analysis and verification tools. *(Note: The legacy GitHub Update System introduced in v2.8.7 has been superseded by CasperVend in v3.0.0).* @@ -25,6 +27,26 @@ Peril Dice is a multiplayer elimination game where each player selects numbers b **NEW in v2.7.0**: Complete architectural overhaul featuring consolidated single linkset design (74 prims total) with bulletproof link message communication, eliminating all channel conflicts and deployment complexity. +## Major v3.1.0 Improvements (Current) ๐Ÿš€ + +### ๐Ÿ” **Dynamic Link Discovery System** +- **Zero-Fixed Links**: Eliminated all hardcoded link indices. Each script now performs a "Dynamic Discovery" sweep on startup to find its sibling prims by name. +- **Relink-Resilient Architecture**: You can now edit the linkset, add decorative prims, or reorder the board without breaking the game logic. +- **Region-Cross Safety**: Solves "vanishing link" issues often seen during region restarts or teleports by automatically re-scanning for link targets. + +### ๐ŸŽญ **FURWARE Text Mesh Migration** +- **Enhanced Visuals**: Replaced the legacy 192nd-century XYText (prim-flipping) with modern, mesh-based **FURWARE Text**. +- **Simplified Bridging**: New bridge scripts handle all dice and leaderboard formatting, ensuring 100% synchronization across all displays. +- **Performance Boost**: Mesh-based text mapping is significantly more efficient than standard prim texture-flipping for complex board data. + +### ๐Ÿงฉ **Centralized System Constants** +- **`Peril_Constants.lsl`**: All system-wide settings, message IDs, and macros are now centralized in a single header file. +- **Unified Debugging**: Implemented a global `dbg()` pre-processor macro. One switch in the constants file toggles all debug levels for the entire game board simultaneously. + +### ๐Ÿง  **Restricted Scope & Loop Stability** +- **Compiler Safety**: Refactored the entire codebase to use top-level variable declarations for all search loops, ensuring compatibility with strict LSL compiler versions. +- **Loop Reset Protection**: Eliminated `continue` keywords in critical logic blocks, replacing them with robust `if` validation to prevent loop-hang race conditions. + ## Major v3.0.0 Improvements ๐Ÿš€ ### ๐Ÿ“ฆ **Commercial CasperVend Integration** @@ -37,6 +59,10 @@ Peril Dice is a multiplayer elimination game where each player selects numbers b - **Bytecode Reduction**: Stripped over 170 verbose `llOwnerSay` debug statements, significantly reducing compiled bytecode size. - **Load Stability**: Freed critical script memory in the Main Controller, Game Manager, and Dialog Handler to ensure rock-solid stability during 10-player stress tests. +### ๐Ÿ† **Experience-Based Leaderboard** +- **Persistent Storage**: Leaderboard data is now stored in Second Life Experience Keys (KVP), ensuring lifetime persistence of player statistics. +- **Region Independence**: Player wins and losses are tracked globally, surviving object resets and region changes. + ### ๐ŸŽจ **Z-Fighting & Alignment Fixes** - **Microscopic Position Offsets**: Resolved persistent Z-fighting on the custom UI (scoreboard, header, start button) by applying microscopic local axis offsets (`0.005m` and `0.008m` relative to the backboard). - **Initialization Stability**: Patched Scoreboard, Leaderboard, and Dice Bridge scripts with `on_rez` auto-reset handlers to ensure flawless texture synchronization and state recovery upon rezzing. @@ -594,11 +620,18 @@ Dice type is automatically chosen to ensure at least 3 picks per player: ## Version -**Current Version**: 3.0.0 -**Last Updated**: March 24, 2026 -**Status**: Production Ready - Commercial CasperVend Release +**Current Version**: 3.1.0 +**Last Updated**: April 04, 2026 +**Status**: Production Ready - Linkset Stability Release -### Key Achievements in v2.8.3: +### Key Achievements in v3.1.0: +- โœ… **Architectural Decentralization**: Implemented Dynamic Link Discovery (no hardcoded links). +- โœ… **FURWARE Integration**: Complete migration to mesh-based text displays. +- โœ… **Centralized Constants**: All logic settings moved to `Peril_Constants.lsl`. +- โœ… **Compiler Compatibility**: Resolved all scope/brace errors and optimized loop logic for 10-player stability. +- โœ… **Resilient Communication**: System now survives link-order changes and region restarts. + +### Key Achievements in v3.0.0: - โœ… **CRITICAL FIX**: Shield detection logic corrected - no more undeserved damage when shields are provided - โœ… **MAJOR FIX**: Complete initialization system overhaul - games work immediately after rezzing - โœ… Added comprehensive `on_rez()` handlers to all critical scripts (7 scripts updated) diff --git a/README_Notecard.txt b/README_Notecard.txt index 01b0eb0..7d13272 100644 --- a/README_Notecard.txt +++ b/README_Notecard.txt @@ -2,13 +2,13 @@ ======================================================== CREATED BY REBECCA NOD AND NOOSE THE BUNNY -CURRENT VERSION: 3.0.0 - COMMERCIAL CASPERVEND RELEASE +CURRENT VERSION: 3.1.0 - THE "LINKSET STABILITY" UPDATE OVERVIEW -------- Peril Dice is a multiplayer elimination game where each player selects numbers before a die is rolled. If the peril player's number is rolled, they lose a life. Players are eliminated when they reach zero lives. -NEW IN V3.0.0: Fully integrated with the commercial CasperVend delivery system. Over 170 legacy debug mechanisms were stripped using an advanced pre-processor macro to free memory for 10-player stability. Completely eliminated Z-fighting UI alignment glitches with new on_rez state synchronization routines. +NEW IN V3.1.0: LINKSET STABILITY & DYNAMIC DISCOVERY! Complete architectural decentralizationโ€”scripts now find their linked prims by name, making the game board immune to link-order changes or region-side glitches. Full migration from legacy XYText to modern mesh-based FURWARE Text for dice and leaderboards. Centralized system constants in Peril_Constants.lsl for 100% project consistency. NEW IN V2.8.7: Enhanced LinkScanner System for complete 84-prim structure analysis and verification tools. (Legacy GitHub update system replaced by CasperVend in v3.0.0). @@ -103,6 +103,27 @@ Player Count | Dice Type 5โ€“6 | d20 7โ€“10 | d30 +RECENT IMPROVEMENTS (V3.1.0) +============================ + +๐Ÿ” DYNAMIC LINK DISCOVERY SYSTEM +- Zero-Fixed Links: Eliminated all hardcoded link indices. Each script now performs a "Dynamic Discovery" sweep on startup to find its sibling prims by name. +- Relink-Resilient Architecture: You can now edit the linkset, add decorative prims, or reorder the board without breaking the game logic. +- Region-Cross Safety: Solves "vanishing link" issues often seen during region restarts or teleports by automatically re-scanning for link targets on rez. + +๐ŸŽญ FURWARE TEXT MESH MIGRATION +- Enhanced Visuals: Replaced the legacy XYText (prim-flipping) with modern, mesh-based FURWARE Text. +- Simplified Bridging: New bridge scripts handle all dice and leaderboard formatting, ensuring 100% synchronization across all displays. +- Performance Boost: Mesh-based text mapping is significantly more efficient than standard prim texture-flipping for complex board data. + +๐Ÿงฉ CENTRALIZED SYSTEM CONSTANTS +- Peril_Constants.lsl: All system-wide settings, message IDs, and macros are now centralized in a single header file. +- Unified Debugging: Implemented a global dbg() pre-processor macro. One switch in the constants file toggles all debug levels for the entire game board simultaneously. + +๐Ÿง  RESTRICTED SCOPE & LOOP STABILITY +- Compiler Safety: Refactored the entire codebase to use top-level variable declarations for all search loops, ensuring compatibility with strict LSL compiler versions. +- Loop Reset Protection: Eliminated continue keywords in critical logic blocks, replacing them with robust if validation to prevent loop-hang race conditions. + RECENT IMPROVEMENTS (V3.0.0) ============================ @@ -116,6 +137,10 @@ RECENT IMPROVEMENTS (V3.0.0) - Bytecode Reduction: Stripped over 170 verbose llOwnerSay debug statements, significantly reducing compiled bytecode size. - Load Stability: Freed critical script memory in the Main Controller, Game Manager, and Dialog Handler to ensure rock-solid stability during 10-player stress tests. +๐Ÿ† EXPERIENCE-BASED LEADERBOARD +- Persistent Storage: Leaderboard data is now stored in Second Life Experience Keys (KVP), ensuring lifetime persistence of player statistics. +- Region Independence: Player wins and losses are tracked globally, surviving object resets and region changes. + ๐ŸŽจ Z-FIGHTING & ALIGNMENT FIXES - Initialization Stability: Patched Scoreboard, Leaderboard, and Dice Bridge scripts with on_rez auto-reset handlers to ensure flawless texture synchronization upon rezzing. - Microscopic Position Offsets: Resolved persistent Z-fighting on the custom UI (scoreboard, header, start button) by applying microscopic local axis offsets. @@ -522,9 +547,9 @@ PREVIOUS IMPROVEMENTS (V2.4.0) VERSION INFORMATION =================== -Current Version: 3.0.0 -Last Updated: March 24, 2026 -Status: Production Ready - Commercial CasperVend Release +Current Version: 3.1.0 +Last Updated: April 04, 2026 +Status: Production Ready - Linkset Stability Release ORIGINAL GAME RULES CREDIT ========================== diff --git a/Roll_ConfettiModule.lsl b/Roll_ConfettiModule.lsl index c1140dd..17a801d 100644 --- a/Roll_ConfettiModule.lsl +++ b/Roll_ConfettiModule.lsl @@ -1,22 +1,12 @@ +#include "Peril_Constants.lsl" + // === Roll and Confetti Module (with Roll Dialog Handler) === -#define MSG_SHOW_DIALOG 101 -#define MSG_ROLL_RESULT 102 -#define MSG_UPDATE_FLOAT 103 -#define MSG_CLEANUP_FLOAT 104 -#define MSG_REZ_FLOAT 105 -#define MSG_SYNC_GAME_STATE 107 -#define MSG_SHOW_ROLL_DIALOG 301 - -// Dice messages (sent to Main Controller) -#define MSG_DICE_ROLL 3020 -#define MSG_CLEAR_DICE 3021 - // ============================================================================= // DYNAMIC CHANNEL CONFIGURATION // ============================================================================= -// Base channel offset - should match Main.lsl +// Base channel offset must match Main.lsl #define CHANNEL_BASE -77000 // Calculate channels dynamically to avoid hardcoded conflicts @@ -48,10 +38,10 @@ initializeChannels() { SCOREBOARD_CHANNEL_3 = calculateChannel(8); // ~-77800 range // Report channels to owner for debugging - llOwnerSay("๐Ÿ”ง [Roll Confetti] Dynamic channels initialized:"); - llOwnerSay(" Roll Dialog: " + (string)ROLLDIALOG_CHANNEL); - llOwnerSay(" Scoreboard 1: " + (string)SCOREBOARD_CHANNEL_1); - llOwnerSay(" Scoreboard 3: " + (string)SCOREBOARD_CHANNEL_3); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ”ง [Roll Confetti] Dynamic channels initialized:"); + dbg("๐ŸŽฒ [Roll Module] Roll Dialog: " + (string)ROLLDIALOG_CHANNEL); + dbg("๐ŸŽฒ [Roll Module] Scoreboard 1: " + (string)SCOREBOARD_CHANNEL_1); + dbg("๐ŸŽฒ [Roll Module] Scoreboard 3: " + (string)SCOREBOARD_CHANNEL_3); } integer rollDialogChannel; // Legacy variable, will be set dynamically @@ -67,10 +57,8 @@ integer diceType = 6; // Store the dice type for rolling integer shouldRoll = FALSE; // Flag to trigger roll after dice type is received 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; +integer diceTypeProcessed = FALSE; // Confetti particle system for winner celebrations +#define PARTICLE_TEXTURE "7d8ae121-e171-12ae-f5b6-7cc3c0395c7b" // Memory reporting function reportMemoryUsage(string scriptName) { @@ -79,7 +67,7 @@ reportMemoryUsage(string scriptName) { integer total = used + free; float percentUsed = ((float)used / (float)total) * 100.0; - llOwnerSay("๐Ÿง  [" + scriptName + "] Memory: " + + dbg("๐Ÿง  [" + scriptName + "] Memory: " + (string)used + " used, " + (string)free + " free (" + llGetSubString((string)percentUsed, 0, 4) + "% used)"); @@ -142,11 +130,9 @@ confetti() { default { state_entry() { - reportMemoryUsage("๐ŸŽฒ Roll Confetti Module"); - - // Initialize dynamic channels - initializeChannels(); - rollDialogChannel = ROLLDIALOG_CHANNEL; // Set legacy variable + DISCOVER_CORE_LINKS(); + reportMemoryUsage("Roll Module"); + dbg("๐ŸŽฒ [Roll Module] ready - discovery complete! Bridge: " + (string)LINK_DICE_BRIDGE); // Clean up any existing listeners if (listenHandle != -1) { @@ -166,12 +152,13 @@ default { // Set up managed listener with dynamic channel listenHandle = llListen(rollDialogChannel, "", NULL_KEY, ""); - llOwnerSay("๐ŸŽฒ Roll Confetti Module ready!"); + dbg("๐ŸŽฒ [Roll Module] ๐ŸŽฒ Roll Confetti Module ready!"); } on_rez(integer start_param) { - reportMemoryUsage("๐ŸŽฒ Roll Confetti Module"); - llOwnerSay("๐Ÿ”„ Roll Confetti Module rezzed - reinitializing..."); + DISCOVER_CORE_LINKS(); + reportMemoryUsage("Roll Module"); + dbg("๐ŸŽฒ [Roll Module] reset via rez..."); // Re-initialize dynamic channels initializeChannels(); @@ -198,33 +185,19 @@ default { // Set up managed listener with dynamic channel listenHandle = llListen(rollDialogChannel, "", NULL_KEY, ""); - llOwnerSay("โœ… Roll Confetti Module reset complete after rez!"); + dbg("๐ŸŽฒ [Roll Module] โœ… Roll Confetti Module reset complete after rez!"); } link_message(integer sender, integer num, string str, key id) { // Debug: Log critical messages to see if Roll Module is working if (num == MSG_SHOW_ROLL_DIALOG) { - llOwnerSay("๐ŸŽฒ [Roll Module] RECEIVED MSG_SHOW_ROLL_DIALOG: " + str + " id: " + (string)id); + dbg("๐ŸŽฒ [Roll Module] ๐ŸŽฒ [Roll Module] RECEIVED MSG_SHOW_ROLL_DIALOG: " + str + " id: " + (string)id); } - if (num == 301) { - llOwnerSay("๐ŸŽฒ [Roll Module] RECEIVED 301: " + str + " id: " + (string)id); + if (num == MSG_SHOW_ROLL_DIALOG) { + dbg("๐ŸŽฒ [Roll Module] ๐ŸŽฒ [Roll Module] RECEIVED 301: " + str + " id: " + (string)id); } - if (num == 996) { - 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; + if (num == MSG_GET_DICE_TYPE) { + dbg("๐ŸŽฒ [Roll Module] ๐ŸŽฒ [Roll Module] RECEIVED GET_DICE_TYPE: " + str); } // Handle full reset from main controller @@ -236,19 +209,19 @@ default { perilPlayer = ""; // Stop any active particles llParticleSystem([]); - llOwnerSay("๐ŸŽ† Roll Confetti Module reset!"); + dbg("๐ŸŽฒ [Roll Module] ๐ŸŽ† Roll Confetti Module reset!"); return; } // Handle game state sync from main controller if (num == MSG_SYNC_GAME_STATE) { - if (VERBOSE_LOGGING) llOwnerSay("๐Ÿ” [Roll Module] Received sync: " + str); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ” [Roll Module] Received sync: " + str); list parts = llParseString2List(str, ["~"], []); - if (VERBOSE_LOGGING) llOwnerSay("๐Ÿ” [Roll Module] Parsed into " + (string)llGetListLength(parts) + " parts"); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ” [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"); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ”„ [Roll Module] Received reset sync - ignoring during reset"); return; } @@ -256,29 +229,32 @@ default { 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 + "'"); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ” [Roll Module] Received picksDataStr: '" + picksDataStr + "'"); + if (picksDataStr == "" || picksDataStr == "EMPTY") { picksData = []; - if (VERBOSE_LOGGING) llOwnerSay("๐Ÿ” [Roll Module] Set picksData to empty"); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ” [Roll Module] Set picksData to empty"); } else { picksData = llParseString2List(picksDataStr, ["^"], []); - if (VERBOSE_LOGGING) llOwnerSay("๐Ÿ” [Roll Module] Set picksData: " + llDumpList2String(picksData, " | ")); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ” [Roll Module] Set picksData: " + llDumpList2String(picksData, " | ")); } + string receivedPeril = llList2String(parts, 2); if (receivedPeril == "NONE") { perilPlayer = ""; } else { perilPlayer = receivedPeril; } + names = llCSV2List(llList2String(parts, 3)); - if (VERBOSE_LOGGING) llOwnerSay("๐Ÿ” [Roll Module] Updated names: " + llDumpList2String(names, ", ")); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ” [Roll Module] Updated names: " + llDumpList2String(names, ", ")); } return; } - if (num == 995) { + if (num == MSG_EFFECT_CONFETTI) { if (str == "VICTORY_CONFETTI") { - llOwnerSay("โœจ ULTIMATE VICTORY CELEBRATION!"); + dbg("๐ŸŽฒ [Roll Module] โœจ ULTIMATE VICTORY CELEBRATION!"); confetti(); } return; @@ -288,25 +264,25 @@ default { // Check if this is a next round prompt if (llSubStringIndex(str, "_NEXT_ROUND") != -1) { string playerName = llGetSubString(str, 0, llSubStringIndex(str, "_NEXT_ROUND") - 1); - llOwnerSay("๐ŸŽฏ Prompting " + playerName + " to start next round..."); + dbg("๐ŸŽฒ [Roll Module] ๐ŸŽฏ Prompting " + playerName + " to start next round..."); // Send continuation directly to Game Manager (more efficient than through Main Controller) - llMessageLinked(LINK_SET, 998, "", NULL_KEY); // Empty peril player - Game Manager will select one + llMessageLinked(LINK_SET, MSG_CONTINUE_GAME, "", NULL_KEY); // Empty peril player - Game Manager will select one } else { // Clear dice display directly (link 83) - llMessageLinked(83, 3021, "", NULL_KEY); + llMessageLinked(LINK_DICE_BRIDGE, MSG_CLEAR_DICE, "", NULL_KEY); // Update peril player from the roll dialog message perilPlayer = str; - llOwnerSay("๐ŸŽฒ Prompting " + str + " to roll the dice. Setting perilPlayer to: " + perilPlayer); + dbg("๐ŸŽฒ [Roll Module] ๐ŸŽฒ Prompting " + str + " to roll the dice. Setting perilPlayer to: " + perilPlayer); // Check if this is a bot (Bot names) if (llSubStringIndex(str, "Bot") == 0) { // Auto-roll for bots - request dice type first - llOwnerSay("๐Ÿค– " + str + " (bot) is requesting dice type for auto-roll..."); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿค– " + str + " (bot) is requesting dice type for auto-roll..."); rollInProgress = TRUE; // Set roll in progress for bot shouldRoll = TRUE; // Set flag to perform roll when dice type is received // Request current dice type from Calculator - llMessageLinked(LINK_SET, 1001, (string)llGetListLength(names), NULL_KEY); + llMessageLinked(LINK_SET, MSG_GET_DICE_TYPE, (string)llGetListLength(names), NULL_KEY); } else { // Reset any previous roll state before showing dialog rollInProgress = FALSE; @@ -319,19 +295,19 @@ default { // Handle dice type response - store dice type and perform roll if requested - else if (num == 1005) { // MSG_DICE_TYPE_RESULT + else if (num == MSG_DICE_TYPE_RESULT) { // MSG_DICE_TYPE_RESULT diceType = (integer)str; - llOwnerSay("๐ŸŽฒ [Roll Module] Received dice type: d" + (string)diceType); + dbg("๐ŸŽฒ [Roll Module] ๐ŸŽฒ [Roll Module] Received dice type: d" + (string)diceType); // If roll was requested from listen handler, perform it now if (shouldRoll && rollInProgress) { shouldRoll = FALSE; // Reset flag - llOwnerSay("๐ŸŽฒ [Roll Module] Performing requested roll with d" + (string)diceType); + dbg("๐ŸŽฒ [Roll Module] ๐ŸŽฒ [Roll Module] Performing requested roll with d" + (string)diceType); // Use MSG_ROLL_RESULT handler to perform the actual roll llMessageLinked(LINK_THIS, MSG_ROLL_RESULT, (string)diceType, NULL_KEY); } else if (shouldRoll && !rollInProgress) { - llOwnerSay("โš ๏ธ [Roll Module] Roll was cancelled or already completed, ignoring dice type"); + dbg("๐ŸŽฒ [Roll Module] โš ๏ธ [Roll Module] Roll was cancelled or already completed, ignoring dice type"); shouldRoll = FALSE; // Clear stale flag } return; @@ -341,7 +317,7 @@ default { else if (num == MSG_ROLL_RESULT) { // Additional safety check if (!rollInProgress) { - llOwnerSay("โš ๏ธ [Roll Module] Ignoring roll request - no roll in progress"); + dbg("๐ŸŽฒ [Roll Module] โš ๏ธ [Roll Module] Ignoring roll request - no roll in progress"); return; } @@ -357,10 +333,10 @@ default { } if (!hasAnyPicks) { - llOwnerSay("โš ๏ธ [Roll Module] No picks data available - delaying roll to wait for sync"); + dbg("๐ŸŽฒ [Roll Module] โš ๏ธ [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); + llMessageLinked(LINK_SET, MSG_CONTINUE_GAME, "REQUEST_SYNC", NULL_KEY); return; } @@ -371,7 +347,7 @@ default { llSay(0, "๐ŸŽฒ THE D" + (string)diceType + " OF FATE! " + perilPlayer + " rolled a " + resultStr + " on the " + (string)diceType + "-sided die! ๐ŸŽฒ"); // Send dice roll directly to dice display (link 83) - llMessageLinked(83, 3020, perilPlayer + "|" + resultStr, NULL_KEY); + llMessageLinked(LINK_DICE_BRIDGE, MSG_DICE_ROLL, perilPlayer + "|" + resultStr, NULL_KEY); string newPeril = ""; integer matched = FALSE; @@ -380,19 +356,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, " | ")); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ” [Roll Module] Checking picks for rolled number: " + resultStr); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ” [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, ",")); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ” [Roll Module] " + pname + "'s picks: " + llDumpList2String(picks, ",")); if (llListFindList(picks, [resultStr]) != -1) { matched = TRUE; matchedPlayers += [pname]; - llOwnerSay("๐Ÿ” [Roll Module] MATCH! " + pname + " picked " + resultStr); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ” [Roll Module] MATCH! " + pname + " picked " + resultStr); } } - llOwnerSay("๐Ÿ” [Roll Module] Final matched status: " + (string)matched + ", players: " + llDumpList2String(matchedPlayers, ",")); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ” [Roll Module] Final matched status: " + (string)matched + ", players: " + llDumpList2String(matchedPlayers, ",")); // If anyone matched, pick the first non-peril player as new peril @@ -409,10 +385,10 @@ default { perilPlayer = newPeril; // Direct scoreboard status update - llMessageLinked(12, 3001, "Plot Twist", NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_GAME_STATUS, "Plot Twist", NULL_KEY); // IMPORTANT: Send peril player update to scoreboard for glow effect - llMessageLinked(12, 3005, newPeril, NULL_KEY); // MSG_UPDATE_PERIL_PLAYER + llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_PERIL_PLAYER, newPeril, NULL_KEY); // MSG_UPDATE_PERIL_PLAYER // Add delay to let status display before next phase llSleep(2.0); @@ -423,14 +399,14 @@ default { integer pidx = llListFindList(names, [perilPlayer]); if (pidx != -1) { integer currentLives = llList2Integer(lives, pidx); - llOwnerSay("๐Ÿ”ง DEBUG: Before lives update - " + perilPlayer + " at index " + (string)pidx + " has " + (string)currentLives + " lives"); - llOwnerSay("๐Ÿ”ง DEBUG: Current lives list: " + llList2CSV(lives)); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ”ง DEBUG: Before lives update - " + perilPlayer + " at index " + (string)pidx + " has " + (string)currentLives + " lives"); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ”ง DEBUG: Current lives list: " + llList2CSV(lives)); lives = llListReplaceList(lives, [currentLives - 1], pidx, pidx); - llOwnerSay("๐Ÿ”ง DEBUG: After lives update - lives list: " + llList2CSV(lives)); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ”ง DEBUG: After lives update - lives list: " + llList2CSV(lives)); // Send direct scoreboard update when lives change - llMessageLinked(12, 3002, perilPlayer + "|" + (string)(currentLives - 1) + "|" + "NULL_KEY", NULL_KEY); - llOwnerSay("๐Ÿ’— Player lives updated: " + perilPlayer + " now has " + (string)(currentLives - 1) + " lives"); + llMessageLinked(LINK_SCOREBOARD, MSG_PLAYER_UPDATE, perilPlayer + "|" + (string)(currentLives - 1) + "|" + "NULL_KEY", NULL_KEY); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ’— Player lives updated: " + perilPlayer + " now has " + (string)(currentLives - 1) + " lives"); // Check if peril player picked the rolled number list perilPicks = getPicksFor(perilPlayer); @@ -444,19 +420,19 @@ default { if (matched && perilPickedIt) { llSay(0, "๐Ÿฉธ DIRECT HIT! " + perilPlayer + " picked their own doom - the d" + (string)diceType + " landed on " + resultStr + "! ๐Ÿฉธ"); // Direct scoreboard status update - llMessageLinked(12, 3001, "Direct Hit", NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_GAME_STATUS, "Direct Hit", NULL_KEY); // Add delay to let status display before next phase llSleep(2.0); } else if (!matched) { llSay(0, "๐Ÿฉธ NO SHIELD! Nobody picked " + resultStr + " - " + perilPlayer + " takes the hit from the d" + (string)diceType + "! ๐Ÿฉธ"); // Direct scoreboard status update - llMessageLinked(12, 3001, "No Shield", NULL_KEY); + llMessageLinked(LINK_SCOREBOARD, MSG_GAME_STATUS, "No Shield", NULL_KEY); // Add delay to let status display before next phase llSleep(2.0); } else { // This should never happen in normal game flow since Plot Twist case is handled above // But if it does, it means someone else picked it - this should have been Plot Twist - llOwnerSay("โš ๏ธ LOGIC ERROR: Someone picked " + resultStr + " but not handled as Plot Twist!"); + dbg("๐ŸŽฒ [Roll Module] โš ๏ธ LOGIC ERROR: Someone picked " + resultStr + " but not handled as Plot Twist!"); llSay(0, "๐Ÿฉธ SHIELD FAILED! " + perilPlayer + " takes the hit despite someone picking " + resultStr + "! ๐Ÿฉธ"); llSleep(2.0); } @@ -466,7 +442,7 @@ default { // Check for elimination if (currentLives - 1 <= 0) { // Player elimination will be handled by Main Controller via sync and elimination messages - llOwnerSay("๐Ÿ’€ Elimination detected: " + perilPlayer + " has 0 hearts remaining"); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ’€ Elimination detected: " + perilPlayer + " has 0 hearts remaining"); llSay(0, "๐Ÿป PUNISHMENT TIME! " + perilPlayer + " has been ELIMINATED!"); @@ -477,16 +453,16 @@ default { if (elimIdx != -1) { // Set player's lives to 0 in our local state, but keep them in the lists lives = llListReplaceList(lives, [0], elimIdx, elimIdx); - llOwnerSay("๐Ÿ”„ [Roll Module] Updated local state - set " + perilPlayer + "'s lives to 0 but kept in lists"); - llOwnerSay("๐Ÿ”„ [Roll Module] This ensures proper sync message format before Main Controller handles elimination"); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ”„ [Roll Module] Updated local state - set " + perilPlayer + "'s lives to 0 but kept in lists"); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ”„ [Roll Module] This ensures proper sync message format before Main Controller handles elimination"); } // Send message to main controller to handle elimination - llMessageLinked(LINK_SET, 999, "ELIMINATE_PLAYER|" + perilPlayer, NULL_KEY); + llMessageLinked(LINK_SET, MSG_ELIMINATE_PLAYER, "ELIMINATE_PLAYER|" + perilPlayer, NULL_KEY); // CRITICAL: Don't send any sync messages after elimination // Let Main Controller handle all state sync, peril player reassignment, AND game continuation - llOwnerSay("๐ŸŽฏ [Roll Module] Elimination complete - letting Main Controller handle state sync and continuation"); + dbg("๐ŸŽฒ [Roll Module] ๐ŸŽฏ [Roll Module] Elimination complete - letting Main Controller handle state sync and continuation"); // Clear roll protection after processing is complete rollInProgress = FALSE; @@ -513,7 +489,7 @@ default { llSleep(0.2); // Additional floater updates to ensure correct peril player display - llOwnerSay("๐Ÿ”„ Updating all floaters with new peril player: " + perilPlayer); + dbg("๐ŸŽฒ [Roll Module] ๐Ÿ”„ Updating all floaters with new peril player: " + perilPlayer); integer f; for (f = 0; f < llGetListLength(names); f++) { string fname = llList2String(names, f); @@ -521,14 +497,14 @@ default { } // After roll is processed and sync is sent, send continuation directly to Game Manager - llOwnerSay("๐ŸŽฏ Round complete - sending continuation directly to Game Manager"); + dbg("๐ŸŽฒ [Roll Module] ๐ŸŽฏ Round complete - sending continuation directly to Game Manager"); // Brief delay to ensure sync propagates llSleep(0.5); // Send continuation directly to Game Manager (more efficient than through Main Controller) // For non-elimination cases, continue with current peril player - llMessageLinked(LINK_SET, 998, perilForSync, NULL_KEY); + llMessageLinked(LINK_SET, MSG_CONTINUE_GAME, perilForSync, NULL_KEY); // Clear roll protection after processing is complete rollInProgress = FALSE; @@ -540,20 +516,20 @@ default { // Prevent multiple rolls within 3 seconds integer currentTime = llGetUnixTime(); if (rollInProgress) { - llOwnerSay("โš ๏ธ Roll already in progress, ignoring duplicate click by " + name); + dbg("๐ŸŽฒ [Roll Module] โš ๏ธ Roll already in progress, ignoring duplicate click by " + name); return; } if (currentTime - lastRollTime < 3) { - llOwnerSay("โš ๏ธ Roll too soon after previous roll, ignoring click by " + name); + dbg("๐ŸŽฒ [Roll Module] โš ๏ธ Roll too soon after previous roll, ignoring click by " + name); return; } // Handle dice roll - request current dice type from Calculator - llOwnerSay("๐ŸŽฒ Roll button clicked by " + name + " (key: " + (string)id + ")"); + dbg("๐ŸŽฒ [Roll Module] ๐ŸŽฒ Roll button clicked by " + name + " (key: " + (string)id + ")"); rollInProgress = TRUE; // Lock out other rolls shouldRoll = TRUE; // Set flag to perform roll when dice type is received // Request current dice type from Calculator - llMessageLinked(LINK_SET, 1001, (string)llGetListLength(names), NULL_KEY); + llMessageLinked(LINK_SET, MSG_GET_DICE_TYPE, (string)llGetListLength(names), NULL_KEY); } else { llMessageLinked(LINK_THIS, channel, msg, id); } diff --git a/System_Debugger.lsl b/System_Debugger.lsl index 0fd1414..a793df8 100644 --- a/System_Debugger.lsl +++ b/System_Debugger.lsl @@ -1,12 +1,8 @@ +#include "Peril_Constants.lsl" + // === 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 -#define MSG_TOGGLE_VERBOSE_LOGS 9998 // Message to toggle verbose logging - -#define MSG_SYNC_GAME_STATE 107 - list validatePicksData(list picksData, list names) { list allPicksFlat = []; integer i; @@ -27,7 +23,7 @@ list validatePicksData(list picksData, list names) { // Check if name is in names list if (names != [] && llListFindList(names, [name]) == -1) { - llOwnerSay("โš ๏ธ picksData name not in names list: " + name); + dbg("๐Ÿ” [System Debugger] โš ๏ธ picksData name not in names list: " + name); } // Check for duplicate picks in this player's list @@ -37,7 +33,7 @@ list validatePicksData(list picksData, list names) { string pick = llList2String(pickNums, j); // Skip validation warnings for cosmetic issues if (~llListFindList(uniqueCheck, [pick])) { - llOwnerSay("๐Ÿšจ Duplicate pick in " + name + "'s picks: " + pick); + dbg("๐Ÿ” [System Debugger] ๐Ÿšจ Duplicate pick in " + name + "'s picks: " + pick); } else { uniqueCheck += [pick]; } @@ -45,12 +41,12 @@ list validatePicksData(list picksData, list names) { } // Only show picks if player has actually picked something - if (picks != "" && VERBOSE_LOGGING) { - llOwnerSay("โœ… " + name + "'s picks: " + llList2CSV(pickNums)); + if (picks != "") { + dbg("๐Ÿ” [System Debugger] โœ… " + name + "'s picks: " + llList2CSV(pickNums)); } } else if (entry != "") { // Only warn about non-empty malformed entries - llOwnerSay("โš ๏ธ Malformed entry in picksData: " + entry); + dbg("๐Ÿ” [System Debugger] โš ๏ธ Malformed entry in picksData: " + entry); } } @@ -60,7 +56,7 @@ list validatePicksData(list picksData, list names) { for (k = 0; k < llGetListLength(allPicksFlat); k++) { string val = llList2String(allPicksFlat, k); if (~llListFindList(seen, [val])) { - llOwnerSay("โŒ Duplicate pick across players: " + val); + dbg("๐Ÿ” [System Debugger] โŒ Duplicate pick across players: " + val); } else { seen += [val]; } @@ -73,34 +69,24 @@ list validatePicksData(list picksData, list names) { default { state_entry() { - llOwnerSay("๐Ÿ” System Debugger ready!"); + dbg("๐Ÿ” [System Debugger] ๐Ÿ” System Debugger ready!"); } 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 legacy verbose logging toggle (OBSELETE) // Handle full reset from main controller - if (num == -99999 && str == "FULL_RESET") { + if (num == MSG_RESET_ALL && str == "FULL_RESET") { // Debugger doesn't maintain state, but acknowledge reset - llOwnerSay("๐Ÿ” System Debugger reset!"); + dbg("๐Ÿ” [System Debugger] ๐Ÿ” System Debugger reset!"); return; } if (num == MSG_SYNC_GAME_STATE) { list parts = llParseString2List(str, ["~"], []); if (llGetListLength(parts) < 4) { - if (VERBOSE_LOGGING) { - llOwnerSay("โš ๏ธ Incomplete game state received."); - } + dbg("๐Ÿ” [System Debugger] โœ… Validation completed - no critical issues found."); return; } diff --git a/Temp_LinkScanner.lsl b/Temp_LinkScanner.lsl deleted file mode 100644 index 9bca659..0000000 --- a/Temp_LinkScanner.lsl +++ /dev/null @@ -1,33 +0,0 @@ -// Temporary Link Scanner - Find overlay prim link numbers -// Put this in any prim in the linkset and touch it to get a full link report - -default { - state_entry() { - llOwnerSay("๐Ÿ” Link Scanner ready - touch to scan all links in the linkset"); - } - - touch_start(integer total_number) { - if (llDetectedKey(0) != llGetOwner()) return; - - integer totalLinks = llGetNumberOfPrims(); - llOwnerSay("๐Ÿ“Š LINKSET SCAN - Total prims: " + (string)totalLinks); - llOwnerSay("=========================================="); - - integer i; - for (i = 1; i <= totalLinks; i++) { - string primName = llGetLinkName(i); - vector pos = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_POSITION]), 0); - - llOwnerSay("Link " + (string)i + ": " + primName + " at " + (string)pos); - - // Small delay to prevent message spam - if (i % 10 == 0) { - llSleep(1.0); - } - } - - llOwnerSay("=========================================="); - llOwnerSay("โœ… Scan complete! Look for the new overlay prims you added"); - llOwnerSay("๐Ÿ’ก The overlay prims should be the highest link numbers"); - } -} \ No newline at end of file diff --git a/Test_Updater.lsl b/Test_Updater.lsl deleted file mode 100644 index db6e117..0000000 --- a/Test_Updater.lsl +++ /dev/null @@ -1,79 +0,0 @@ -// ==================================================================== -// Test Updater - Debug llRemoteLoadScriptPin -// ==================================================================== -// Simple test to see if llRemoteLoadScriptPin works at all - -#define TEST_VERSION "1.0.0" -#define UPDATER_CHANNEL -7723847 - -// Test state -key targetGameKey = NULL_KEY; -integer testPin = 0; -integer testStep = 0; - -// Simple test script content -#define TEST_SCRIPT "default { - state_entry() { - llOwnerSay(\"โœ… Test script installed successfully!\")\n llOwnerSay(\"๐Ÿ“ This proves llRemoteLoadScriptPin is working.\");\n }\n}"; - -default { - state_entry() { - llOwnerSay("๐Ÿงช Test Updater v" + TEST_VERSION + " ready"); - llOwnerSay("๐Ÿ“ This will test llRemoteLoadScriptPin with a tiny script"); - llOwnerSay("๐Ÿ‘† Touch your Peril Dice game, then touch this updater"); - - llListen(UPDATER_CHANNEL, "", NULL_KEY, ""); - - llSetText("๐Ÿงช Test Updater\nReady to test llRemoteLoadScriptPin\nTouch your game first, then me", - <1.0, 0.8, 0.2>, 1.0); - } - - listen(integer channel, string name, key id, string message) { - if (channel != UPDATER_CHANNEL) return; - - list parts = llParseString2List(message, ["|"], []); - string command = llList2String(parts, 0); - - if (command == "TEST_REQUEST") { - string version = llList2String(parts, 1); - integer pin = (integer)llList2String(parts, 2); - - llOwnerSay("๐Ÿ“จ Test request from " + name + " (PIN: " + (string)pin + ")"); - - // Store the test info - targetGameKey = id; - testPin = pin; - testStep = 1; - - // Start the test - llOwnerSay("๐Ÿš€ Step 1: Testing tiny script installation..."); - llOwnerSay("๐Ÿ“Š Script size: " + (string)llStringLength(TEST_SCRIPT) + " characters"); - llOwnerSay("๐ŸŽฏ Target: " + (string)targetGameKey); - llOwnerSay("๐Ÿ”‘ PIN: " + (string)testPin); - - // Try to install the test script - llRemoteLoadScriptPin(targetGameKey, TEST_SCRIPT, testPin, TRUE, 0); - - llOwnerSay("โœ… llRemoteLoadScriptPin called - check your game for new script"); - llOwnerSay("๐Ÿ’ก If successful, you'll see a 'Test script installed' message"); - } - else if (command == "PING_UPDATER") { - llRegionSayTo(id, UPDATER_CHANNEL, "TEST_UPDATER_AVAILABLE|" + TEST_VERSION); - } - } - - touch_start(integer total_number) { - key toucher = llDetectedKey(0); - - if (testStep == 0) { - llOwnerSay("๐Ÿ” Touch your Peril Dice game first to set up the PIN"); - llOwnerSay("๐Ÿ“ The game needs to call llSetRemoteScriptAccessPin()"); - } else { - llOwnerSay("๐Ÿงช Test Status:"); - llOwnerSay("๐Ÿ“ Target: " + (string)targetGameKey); - llOwnerSay("๐Ÿ”‘ PIN: " + (string)testPin); - llOwnerSay("๐Ÿ“Š Test script size: " + (string)llStringLength(TEST_SCRIPT) + " chars"); - llOwnerSay("โ“ Did you see 'Test script installed successfully' message?"); - } - } -} \ No newline at end of file diff --git a/Update_Checker.lsl b/Update_Checker.lsl deleted file mode 100644 index 171c289..0000000 --- a/Update_Checker.lsl +++ /dev/null @@ -1,431 +0,0 @@ -// ==================================================================== -// Update Checker - GitHub-based Update System for Peril Dice -// ==================================================================== -// VERIFIED LINKSET PLACEMENT: Link 1 (Root Prim) "Peril Dice Controller V2" -// Based on actual linkset scan: 84 prims total, exactly matching architecture -// Replaces eliminated UpdateHelper.lsl from v2.8.5 memory optimization -// -// INTEGRATION: -// - Integrates with existing admin menu system via Player_DialogHandler.lsl -// - Uses link message communication following existing patterns -// - Memory optimized to fit alongside 13 other Link 1 scripts -// -// VERIFIED STRUCTURE (from linkset scan): -// - Link 1: Root controller with 13+ scripts -// - Link 12: "scoreboard manager cube" - Game_Scoreboard_Manager_Linkset.lsl -// - Link 35: "leaderboard row 1 col 1" - Leaderboard_Communication_Linkset.lsl -// - Links 35-82: XyzzyText prims (48 total) - each needs xyzzy_Master_script.lsl -// - Link 83: "dice col 1" - XyzzyText_Dice_Bridge_Linkset.lsl -// - Link 84: "dice col 2" - no script needed -// ==================================================================== - -#define CURRENT_VERSION "2.8.7" -#define GITHUB_API_URL "https://api.github.com/repos/RebeccaNod1/Peril/releases/latest" -#define GITHUB_RAW_URL "https://raw.githubusercontent.com/RebeccaNod1/Peril/main/" - -// VERIFIED Link 1 Scripts (from actual scan of "Peril Dice Controller V2") -list LINK1_SCRIPTS = [ - "Main_Controller_Linkset.lsl", // Master coordination - "Game_Manager.lsl", // Round management - "Controller_Memory.lsl", // Memory monitoring - "Controller_MessageHandler.lsl", // Message routing - "Player_RegistrationManager.lsl", // Player registry - "Player_DialogHandler.lsl", // Admin menus - "NumberPicker_DialogHandler.lsl", // Number picking - "Floater_Manager.lsl", // Floating HUDs - "Roll_ConfettiModule.lsl", // Dice rolling - "Bot_Manager.lsl", // AI players - "Game_Calculator.lsl", // Dice calculations - "Verbose_Logger.lsl", // Logging system - "System_Debugger.lsl" // Debug tools -]; - -// VERIFIED Link-Specific Scripts (from actual linkset scan) -list LINKSET_SPECIFIC_SCRIPTS = [ - "Game_Scoreboard_Manager_Linkset.lsl", // Link 12: "scoreboard manager cube" - "Leaderboard_Communication_Linkset.lsl", // Link 35: "leaderboard row 1 col 1" - "XyzzyText_Dice_Bridge_Linkset.lsl" // Link 83: "dice col 1" -]; - -// XyzzyText system - VERIFIED 48 prims (Links 35-82) -#define XYZZY_SCRIPT "xyzzy_Master_script.lsl" - -// Essential documentation files -list ESSENTIAL_DOCS = [ - "README.md", "CHANGELOG.md", "README_Notecard.txt", "CHANGELOG_Notecard.txt" -]; - -// HTTP operation tracking -key currentHttpRequest = NULL_KEY; -string currentOperation = ""; -integer updateCheckInProgress = FALSE; -integer downloadMode = FALSE; -string downloadingFile = ""; - -// Message constants - following existing Peril Dice patterns -#define MSG_ADMIN_MENU_RESPONSE 888 // Integration with admin menu -#define MSG_UPDATE_CHECK_REQUEST 2100 // Request from admin menu -#define MSG_TOGGLE_VERBOSE_LOGS 9999 // Verbose logging toggle - -// Internal verbose logging (follows existing pattern) -integer VERBOSE_LOGGING = FALSE; - -// Memory usage reporting (matches existing scripts) -reportMemoryUsage(string scriptName) { - integer memory = llGetUsedMemory(); - integer freeMemory = llGetFreeMemory(); - float memoryPercent = (float)memory / (memory + freeMemory) * 100.0; - llOwnerSay("๐Ÿ“Š " + scriptName + ": " + (string)memory + " bytes used (" + - llGetSubString((string)memoryPercent, 0, 4) + "% memory)"); -} - -// Version comparison (handles "v2.8.6" and "2.8.6" formats) -integer isNewerVersion(string latestVersion, string currentVersion) { - // Remove 'v' prefix if present - if (llGetSubString(latestVersion, 0, 0) == "v") { - latestVersion = llGetSubString(latestVersion, 1, -1); - } - if (llGetSubString(currentVersion, 0, 0) == "v") { - currentVersion = llGetSubString(currentVersion, 1, -1); - } - - return (latestVersion != currentVersion); -} - -// Check GitHub API for updates -checkForUpdates() { - if (updateCheckInProgress) { - llOwnerSay("โณ Update check already in progress..."); - return; - } - - updateCheckInProgress = TRUE; - currentOperation = "version_check"; - llOwnerSay("๐Ÿ” Checking GitHub for Peril Dice updates..."); - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ”— API URL: " + GITHUB_API_URL); - } - - currentHttpRequest = llHTTPRequest(GITHUB_API_URL, [ - HTTP_METHOD, "GET" - ], ""); -} - -// Download file content from GitHub raw repository -downloadFileContent(string filename) { - if (updateCheckInProgress && !downloadMode) { - llOwnerSay("โณ Please wait for version check to complete..."); - return; - } - - // Validate file is in essential lists - if (llListFindList(LINK1_SCRIPTS, [filename]) == -1 && - llListFindList(LINKSET_SPECIFIC_SCRIPTS, [filename]) == -1 && - llListFindList(ESSENTIAL_DOCS, [filename]) == -1 && - filename != XYZZY_SCRIPT) { - llOwnerSay("โŒ File '" + filename + "' is not in the essential files list."); - llOwnerSay("๐Ÿ“‹ Say '/1 list' to see available files"); - return; - } - - downloadMode = TRUE; - downloadingFile = filename; - currentOperation = "file_download"; - llOwnerSay("๐Ÿ“ฅ Downloading " + filename + " from GitHub main branch..."); - - string url = GITHUB_RAW_URL + filename; - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ”— Download URL: " + url); - } - - currentHttpRequest = llHTTPRequest(url, [ - HTTP_METHOD, "GET" - ], ""); -} - -// Show help commands -showUpdateCommands() { - llOwnerSay("=== ๐Ÿ”„ PERIL DICE UPDATE SYSTEM ==="); - llOwnerSay("๐Ÿ“ฑ Current Version: v" + CURRENT_VERSION); - llOwnerSay("๐ŸŒ Repository: github.com/RebeccaNod1/Peril"); - llOwnerSay("๐Ÿ“ Location: Link 1 (Root Prim) - 84-prim linkset verified"); - llOwnerSay("๐Ÿ’ก Note: GitHub releases start with v2.8.7+"); - llOwnerSay(""); - llOwnerSay("๐Ÿ’ฌ Available Commands (say in local chat):"); - llOwnerSay(" /1 check - Check GitHub for updates"); - llOwnerSay(" /1 download ScriptName.lsl - Download specific file"); - llOwnerSay(" /1 list - Show essential files with placement info"); - llOwnerSay(" /1 help - Show these commands"); - llOwnerSay(""); - llOwnerSay("๐ŸŽ›๏ธ Or use Owner Menu โ†’ Troubleshooting โ†’ Check for Updates"); - llOwnerSay("๐Ÿ“‹ Example: /1 download xyzzy_Master_script.lsl"); -} - -// Show essential files list with VERIFIED linkset placement info -showEssentialFiles() { - llOwnerSay("=== ๐Ÿ“‹ ESSENTIAL GAME FILES (VERIFIED LINKSET STRUCTURE) ==="); - - llOwnerSay("๐Ÿ”ง Link 1 Scripts (Root Prim: \"Peril Dice Controller V2\"):"); - integer i; - for (i = 0; i < llGetListLength(LINK1_SCRIPTS); i++) { - llOwnerSay(" โ€ข " + llList2String(LINK1_SCRIPTS, i)); - } - llOwnerSay(" โ€ข Update_Checker.lsl (THIS SCRIPT)"); - llOwnerSay(""); - - llOwnerSay("๐ŸŽฏ Link-Specific Scripts (VERIFIED placement):"); - llOwnerSay(" โ€ข Game_Scoreboard_Manager_Linkset.lsl"); - llOwnerSay(" โ†’ Link 12: \"scoreboard manager cube\""); - llOwnerSay(" โ€ข Leaderboard_Communication_Linkset.lsl"); - llOwnerSay(" โ†’ Link 35: \"leaderboard row 1 col 1\""); - llOwnerSay(" โ€ข XyzzyText_Dice_Bridge_Linkset.lsl"); - llOwnerSay(" โ†’ Link 83: \"dice col 1\""); - llOwnerSay(""); - - llOwnerSay("๐Ÿ“บ XyzzyText Display System (VERIFIED 48 prims):"); - llOwnerSay(" โ€ข xyzzy_Master_script.lsl"); - llOwnerSay(" โ†’ Links 35-82: All leaderboard prims"); - llOwnerSay(" โ†’ 4 banks ร— 12 rows each"); - llOwnerSay(" โ†’ Bank 1: Links 35-46 (col 1)"); - llOwnerSay(" โ†’ Bank 2: Links 47-58 (col 2)"); - llOwnerSay(" โ†’ Bank 3: Links 59-70 (col 3)"); - llOwnerSay(" โ†’ Bank 4: Links 71-82 (col 4)"); - llOwnerSay(" โš ๏ธ Each prim needs this exact script!"); - llOwnerSay(""); - - llOwnerSay("๐Ÿ“– Documentation (" + (string)llGetListLength(ESSENTIAL_DOCS) + " files):"); - for (i = 0; i < llGetListLength(ESSENTIAL_DOCS); i++) { - llOwnerSay(" โ€ข " + llList2String(ESSENTIAL_DOCS, i)); - } - llOwnerSay(""); - llOwnerSay("โ„น๏ธ Development files excluded: lsl_validator.py, test_*.py, etc."); - llOwnerSay("๐Ÿ“Š Total Essential Files: " + (string)(llGetListLength(LINK1_SCRIPTS) + llGetListLength(LINKSET_SPECIFIC_SCRIPTS) + llGetListLength(ESSENTIAL_DOCS) + 1) + " unique files"); -} - -default { - state_entry() { - reportMemoryUsage("Update Checker"); - llOwnerSay("๐Ÿ”„ Update Checker ready - GitHub integration active"); - llOwnerSay("๐Ÿ“ Location: Link 1 (\"Peril Dice Controller V2\") - VERIFIED 84-prim structure"); - llOwnerSay("๐Ÿ’ฌ Say '/1 help' for commands or use Owner Menu โ†’ Troubleshooting"); - llListen(1, "", llGetOwner(), ""); - } - - on_rez(integer start_param) { - // Reset state on rez (follows existing script patterns) - updateCheckInProgress = FALSE; - downloadMode = FALSE; - currentOperation = ""; - downloadingFile = ""; - currentHttpRequest = NULL_KEY; - llResetScript(); - } - - listen(integer channel, string name, key id, string message) { - if (id != llGetOwner()) return; - - string msg = llToLower(llStringTrim(message, STRING_TRIM)); - - if (msg == "help") { - showUpdateCommands(); - } - else if (msg == "check") { - checkForUpdates(); - } - else if (msg == "list") { - showEssentialFiles(); - } - else if (llGetSubString(msg, 0, 7) == "download") { - string filename = llStringTrim(llGetSubString(message, 8, -1), STRING_TRIM); - if (filename != "") { - downloadFileContent(filename); - } else { - llOwnerSay("โŒ Usage: /1 download ScriptName.lsl"); - llOwnerSay("๐Ÿ“‹ Say '/1 list' to see available files"); - } - } - else { - llOwnerSay("โ“ Unknown command. Say '/1 help' for available commands"); - } - } - - link_message(integer sender_num, integer num, string str, key id) { - // Handle verbose logging toggle (follows existing pattern) - if (num == MSG_TOGGLE_VERBOSE_LOGS) { - VERBOSE_LOGGING = !VERBOSE_LOGGING; - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ”Š [UpdateChecker] Verbose logging ENABLED"); - } else { - llOwnerSay("๐Ÿ”Š [UpdateChecker] Verbose logging DISABLED"); - } - return; - } - - // Handle update check requests from admin menu - if (num == MSG_UPDATE_CHECK_REQUEST) { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ”„ [UpdateChecker] Received update check request from admin menu"); - } - checkForUpdates(); - return; - } - - // Respond to admin menu status queries - if (num == MSG_ADMIN_MENU_RESPONSE && str == "update_status") { - llMessageLinked(LINK_SET, MSG_ADMIN_MENU_RESPONSE, - "update_version|v" + CURRENT_VERSION, id); - return; - } - } - - http_response(key request_id, integer status, list metadata, string body) { - if (request_id != currentHttpRequest) return; - - currentHttpRequest = NULL_KEY; - - if (currentOperation == "version_check") { - updateCheckInProgress = FALSE; - - if (status == 200) { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“Š GitHub response: " + (string)llStringLength(body) + " chars (LSL limit: 2048)"); - } - - // LSL truncates HTTP responses at 2048 chars, breaking JSON parser - // Use manual parsing instead - string latestVersion = ""; - string htmlUrl = ""; - string publishedAt = ""; - - // Extract tag_name manually - integer tagStart = llSubStringIndex(body, "\"tag_name\":\""); - if (tagStart != -1) { - integer versionStart = tagStart + 12; // Skip '"tag_name":"' - integer versionEnd = llSubStringIndex(llGetSubString(body, versionStart, -1), "\""); - if (versionEnd != -1) { - latestVersion = llGetSubString(body, versionStart, versionStart + versionEnd - 1); - } - } - - // Extract html_url manually - integer urlStart = llSubStringIndex(body, "\"html_url\":\""); - if (urlStart != -1) { - integer urlValueStart = urlStart + 12; // Skip '"html_url":"' - integer urlEnd = llSubStringIndex(llGetSubString(body, urlValueStart, -1), "\""); - if (urlEnd != -1) { - htmlUrl = llGetSubString(body, urlValueStart, urlValueStart + urlEnd - 1); - } - } - - // Extract published_at manually - integer pubStart = llSubStringIndex(body, "\"published_at\":\""); - if (pubStart != -1) { - integer pubValueStart = pubStart + 15; // Skip '"published_at":"' - integer pubEnd = llSubStringIndex(llGetSubString(body, pubValueStart, -1), "\""); - if (pubEnd != -1) { - publishedAt = llGetSubString(body, pubValueStart, pubValueStart + pubEnd - 1); - } - } - - if (latestVersion == "") { - llOwnerSay("โŒ Could not extract version from truncated GitHub response"); - llOwnerSay("๐Ÿ” Response was cut off at 2048 characters"); - return; - } - - // Skip release notes parsing - response is truncated at 2048 chars - string releaseNotes = "Release notes available on GitHub (response truncated)"; - - llOwnerSay("=== ๐Ÿ” UPDATE CHECK RESULTS ==="); - llOwnerSay("๐Ÿ“Š Current Version: v" + CURRENT_VERSION); - llOwnerSay("โœจ Latest GitHub Release: " + latestVersion); - - if (isNewerVersion(latestVersion, CURRENT_VERSION)) { - llOwnerSay("๐Ÿ†• NEW VERSION AVAILABLE!"); - llOwnerSay("๐Ÿ“… Published: " + llGetSubString(publishedAt, 0, 9)); - llOwnerSay("๐ŸŒ View Release: " + htmlUrl); - - // Show release notes preview - if (releaseNotes != JSON_INVALID && releaseNotes != "") { - string shortNotes = releaseNotes; - if (llStringLength(shortNotes) > 200) { - shortNotes = llGetSubString(shortNotes, 0, 197) + "..."; - } - llOwnerSay("๐Ÿ“‹ What's New: " + shortNotes); - } - - llOwnerSay(""); - llOwnerSay("๐Ÿ“ฅ Individual Files: /1 download ScriptName.lsl"); - llOwnerSay("๐Ÿ“‹ Available Files: /1 list"); - llOwnerSay("๐ŸŽ Complete Release: " + htmlUrl); - } else { - llOwnerSay("โœ… You have the latest version!"); - llOwnerSay("๐Ÿ”„ Individual file updates available via /1 download"); - } - } else { - llOwnerSay("โŒ Update check failed (HTTP " + (string)status + ")"); - if (status == 403) { - llOwnerSay(" โณ GitHub rate limited - try again in a few minutes"); - } else if (status == 404) { - llOwnerSay(" ๐Ÿ“‹ No releases found yet - this is normal for new repositories"); - llOwnerSay(" ๐ŸŒ Repository exists at: https://github.com/RebeccaNod1/Peril"); - llOwnerSay(" ๐Ÿ’ก Individual file downloads still available via /1 download"); - llOwnerSay(" ๐Ÿ”„ Releases will appear here when v2.8.7+ is tagged"); - } - } - } - else if (currentOperation == "file_download") { - downloadMode = FALSE; - - if (status == 200) { - integer contentLength = llStringLength(body); - llOwnerSay("โœ… Downloaded " + downloadingFile); - llOwnerSay("๐Ÿ“Š Size: " + (string)contentLength + " characters"); - - // Show VERIFIED deployment instructions based on actual linkset scan - if (downloadingFile == "xyzzy_Master_script.lsl") { - llOwnerSay("๐Ÿ“บ XyzzyText Script - Deploy to ALL Links 35-82 (48 prims)"); - llOwnerSay(" ๐ŸŽฏ VERIFIED: \"leaderboard row X col Y\" prims"); - llOwnerSay(" โš ๏ธ Each leaderboard prim needs this exact script!"); - } else if (downloadingFile == "Game_Scoreboard_Manager_Linkset.lsl") { - llOwnerSay("๐ŸŽฏ Scoreboard Manager - Deploy to Link 12 ONLY"); - llOwnerSay(" ๐Ÿ“ VERIFIED: \"scoreboard manager cube\""); - } else if (downloadingFile == "Leaderboard_Communication_Linkset.lsl") { - llOwnerSay("๐ŸŽฏ Leaderboard Bridge - Deploy to Link 35 ONLY"); - llOwnerSay(" ๐Ÿ“ VERIFIED: \"leaderboard row 1 col 1\""); - } else if (downloadingFile == "XyzzyText_Dice_Bridge_Linkset.lsl") { - llOwnerSay("๐ŸŽฏ Dice Display Bridge - Deploy to Link 83 ONLY"); - llOwnerSay(" ๐Ÿ“ VERIFIED: \"dice col 1\""); - } else if (llListFindList(LINK1_SCRIPTS, [downloadingFile]) != -1) { - llOwnerSay("๐ŸŽฏ Root Prim Script - Deploy to Link 1 ONLY"); - llOwnerSay(" ๐Ÿ“ VERIFIED: \"Peril Dice Controller V2\""); - } else { - llOwnerSay("๐Ÿ“– Documentation file - no deployment needed"); - } - - // Show content preview - llOwnerSay("โ”€โ”€โ”€โ”€โ”€ CONTENT PREVIEW (200 chars) โ”€โ”€โ”€โ”€โ”€"); - string preview = body; - if (llStringLength(preview) > 200) { - preview = llGetSubString(preview, 0, 197) + "..."; - } - llOwnerSay(preview); - llOwnerSay("โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€"); - - if (llGetSubString(downloadingFile, -4, -1) == ".lsl") { - llOwnerSay("๐Ÿ”ง Copy โ†’ Paste into script editor โ†’ SAVE โ†’ RESET"); - llOwnerSay("๐Ÿ“ Deployment verified for 84-prim linkset structure"); - } - } else { - llOwnerSay("โŒ Download failed: " + downloadingFile + " (HTTP " + (string)status + ")"); - if (status == 404) { - llOwnerSay(" ๐Ÿ“ File not found on GitHub - check spelling"); - } - } - - downloadingFile = ""; - } - - currentOperation = ""; - } -} \ No newline at end of file diff --git a/Update_Receiver.lsl b/Update_Receiver.lsl deleted file mode 100644 index 5737730..0000000 --- a/Update_Receiver.lsl +++ /dev/null @@ -1,507 +0,0 @@ -// ==================================================================== -// Update Receiver - GitHub Integration + External Updater Coordination -// ==================================================================== -// VERIFIED LINKSET PLACEMENT: Link 1 (Root Prim) "Peril Dice Controller V2" -// Replaces Update_Checker.lsl with external updater box coordination -// -// This script: -// 1. Checks GitHub for new releases (same as before) -// 2. Scans for nearby Peril_Dice_Updater boxes -// 3. Coordinates automatic script installation via external updater -// 4. Provides professional one-click update experience -// ==================================================================== - -string CURRENT_VERSION = "2.8.8"; -#define GITHUB_API_URL "https://api.github.com/repos/RebeccaNod1/Peril/releases/latest" - -// External updater communication -#define UPDATER_CHANNEL -7723847 // Must match Peril_Dice_Updater.lsl -#define SENSOR_RANGE 96 // meters to scan for updater boxes -list nearbyUpdaters = []; -key selectedUpdater = NULL_KEY; - -// Update process state -integer updateInProgress = FALSE; -integer updatePin = 0; -string availableVersion = ""; - -// HTTP operation tracking -key currentHttpRequest = NULL_KEY; -string currentOperation = ""; - -// Message constants - following existing Peril Dice patterns -#define MSG_ADMIN_MENU_RESPONSE 888 -#define MSG_UPDATE_CHECK_REQUEST 2100 -#define MSG_TOGGLE_VERBOSE_LOGS 9999 - -// Update dialog constants -#define UPDATE_DIALOG_CHANNEL -77401 // Unique channel for update dialogs -integer updateDialogHandle = -1; -key pendingUpdateUser = NULL_KEY; - -// Internal verbose logging -integer VERBOSE_LOGGING = FALSE; - -// Memory usage reporting -reportMemoryUsage(string scriptName) { - integer memory = llGetUsedMemory(); - integer freeMemory = llGetFreeMemory(); - float memoryPercent = (float)memory / (memory + freeMemory) * 100.0; - llOwnerSay("๐Ÿ“Š " + scriptName + ": " + (string)memory + " bytes used (" + - llGetSubString((string)memoryPercent, 0, 4) + "% memory)"); -} - -// Version comparison (handles "v2.8.6" and "2.8.6" formats) -integer isNewerVersion(string latestVersion, string currentVersion) { - // Remove 'v' prefix if present - if (llGetSubString(latestVersion, 0, 0) == "v") { - latestVersion = llGetSubString(latestVersion, 1, -1); - } - if (llGetSubString(currentVersion, 0, 0) == "v") { - currentVersion = llGetSubString(currentVersion, 1, -1); - } - - return (latestVersion != currentVersion); -} - -// Manual JSON parsing for LSL 2048-char limit -string extractJsonValue(string json, string keyName) { - string quote = "\""; - string searchPattern = quote + keyName + quote + ":" + quote; - integer start = llSubStringIndex(json, searchPattern); - if (start == -1) return ""; - - integer valueStart = start + llStringLength(searchPattern); - integer valueEnd = llSubStringIndex(llGetSubString(json, valueStart, -1), quote); - if (valueEnd == -1) return ""; - - return llGetSubString(json, valueStart, valueStart + valueEnd - 1); -} - -// Scan for nearby updater boxes -scanForUpdaters() { - nearbyUpdaters = []; - llOwnerSay("๐Ÿ” Scanning for GitHub updater boxes within " + (string)SENSOR_RANGE + "m..."); - llSensor("", NULL_KEY, SCRIPTED, SENSOR_RANGE, PI); -} - -// Check GitHub for updates -checkForUpdates() { - currentOperation = "version_check"; - llOwnerSay("๐Ÿ” Checking GitHub for Peril Dice updates..."); - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“Š GitHub API: " + GITHUB_API_URL); - } - - currentHttpRequest = llHTTPRequest(GITHUB_API_URL, [ - HTTP_METHOD, "GET" - ], ""); -} - -// Start update installation process -startUpdateInstallation() { - if (selectedUpdater == NULL_KEY) { - llOwnerSay("โŒ No updater box selected. Please rez a Peril Dice Updater nearby."); - return; - } - - // Generate unique access pin - updatePin = (integer)(llFrand(2000000000)) + 1000000000; - - // Set remote script access pin - llSetRemoteScriptAccessPin(updatePin); - - updateInProgress = TRUE; - llOwnerSay("๐Ÿ”„ Starting automatic update installation..."); - llOwnerSay("๐Ÿ“Š Requesting " + availableVersion + " from GitHub updater box..."); - - // Request update from external updater - llRegionSayTo(selectedUpdater, UPDATER_CHANNEL, - "UPDATE_REQUEST|" + CURRENT_VERSION + "|" + (string)updatePin); - - // Set timeout for update process - llSetTimerEvent(300.0); // 5 minute timeout -} - -// Complete update process -completeUpdate(string newVersion) { - updateInProgress = FALSE; - llSetRemoteScriptAccessPin(0); // Clear access pin - llSetTimerEvent(0.0); // Clear timer - - llOwnerSay("๐ŸŽ‰ Update installation complete!"); - llOwnerSay("โœ… Peril Dice is now running version " + newVersion); - llOwnerSay("๐Ÿ’ก You can now delete the updater box"); - - // Update our version number - // Note: In practice, this script itself would have been updated - // CURRENT_VERSION = newVersion; -} - -// Handle update failure -handleUpdateFailure(string error) { - updateInProgress = FALSE; - llSetRemoteScriptAccessPin(0); // Clear access pin - llSetTimerEvent(0.0); // Clear timer - - llOwnerSay("โŒ Update installation failed: " + error); - llOwnerSay("๐Ÿ’ก You can try again or delete the updater box"); -} - -// Show update installation dialog -showInstallUpdateDialog(key userId) { - if (availableVersion == "") { - llOwnerSay("โŒ No update available to install"); - return; - } - - if (llGetListLength(nearbyUpdaters) == 0) { - llOwnerSay("๐Ÿ“ฆ No updater boxes found. Scanning..."); - scanForUpdaters(); - return; - } - - pendingUpdateUser = userId; - - // Close previous dialog listener - if (updateDialogHandle != -1) { - llListenRemove(updateDialogHandle); - } - - // Start new dialog listener - updateDialogHandle = llListen(UPDATE_DIALOG_CHANNEL, "", userId, ""); - - string dialogText = "๐Ÿ†• NEW VERSION AVAILABLE!\n\n"; - dialogText += "๐Ÿ“Š Current Version: v" + CURRENT_VERSION + "\n"; - dialogText += "โœจ Available Version: " + availableVersion + "\n\n"; - dialogText += "๐Ÿ”„ Ready to install " + availableVersion + " automatically?\n"; - dialogText += "๐Ÿ“ฆ Found " + (string)llGetListLength(nearbyUpdaters) + " updater box(es) nearby"; - - list options = ["๐Ÿš€ Install Update", "โŒ Cancel", "๐Ÿ“ฆ Scan Again"]; - llDialog(userId, dialogText, options, UPDATE_DIALOG_CHANNEL); -} - -// Show help commands -showUpdateCommands() { - llOwnerSay("=== ๐Ÿ”„ PERIL DICE UPDATE SYSTEM ==="); - llOwnerSay("๐Ÿ“ฑ Current Version: v" + CURRENT_VERSION); - llOwnerSay("๐ŸŒ Repository: github.com/RebeccaNod1/Peril"); - llOwnerSay("๐Ÿ“ Location: Link 1 (Root Prim) - VERIFIED 84-prim structure"); - llOwnerSay(""); - llOwnerSay("๐Ÿ”„ NEW: Automatic Update System!"); - llOwnerSay("1๏ธโƒฃ Rez a 'Peril Dice Updater' box nearby"); - llOwnerSay("2๏ธโƒฃ Touch this game โ†’ Admin Menu โ†’ 'Check for Updates'"); - llOwnerSay("3๏ธโƒฃ Click 'Install Update' for one-click installation"); - llOwnerSay("4๏ธโƒฃ Delete updater box when complete"); - llOwnerSay(""); - llOwnerSay("๐Ÿ’ฌ Manual commands still available:"); - llOwnerSay(" /1 check - Check GitHub for updates"); - llOwnerSay(" /1 scan - Scan for nearby updater boxes"); - llOwnerSay(" /1 test - Test llRemoteLoadScriptPin with tiny script"); - llOwnerSay(" /1 help - Show these commands"); -} - -default { - state_entry() { - reportMemoryUsage("Update Receiver"); - llOwnerSay("๐Ÿ”„ Update Receiver ready - GitHub + External Updater integration"); - llOwnerSay("๐Ÿ“ Location: Link 1 (\"Peril Dice Controller V2\") - VERIFIED structure"); - llOwnerSay("๐Ÿ’ฌ Say '/1 help' for commands or use Owner Menu โ†’ Troubleshooting"); - llListen(1, "", llGetOwner(), ""); - llListen(UPDATER_CHANNEL, "", NULL_KEY, ""); - } - - on_rez(integer start_param) { - // Reset state on rez - updateInProgress = FALSE; - updatePin = 0; - availableVersion = ""; - nearbyUpdaters = []; - selectedUpdater = NULL_KEY; - currentOperation = ""; - currentHttpRequest = NULL_KEY; - llResetScript(); - } - - listen(integer channel, string name, key id, string message) { - if (channel == 1 && id == llGetOwner()) { - // Owner chat commands - string msg = llToLower(llStringTrim(message, STRING_TRIM)); - - if (msg == "help") { - showUpdateCommands(); - } - else if (msg == "check") { - checkForUpdates(); - } - else if (msg == "scan") { - scanForUpdaters(); - } - else if (msg == "test") { - // Test llRemoteLoadScriptPin with tiny script - llOwnerSay("๐Ÿงช DEBUG: Testing llRemoteLoadScriptPin..."); - - // Generate test PIN - integer testPin = (integer)(llFrand(1000000)) + 100000; - llSetRemoteScriptAccessPin(testPin); - - llOwnerSay("๐Ÿ”‘ Test PIN set: " + (string)testPin); - llOwnerSay("๐Ÿ“ก Broadcasting test request on channel " + (string)UPDATER_CHANNEL); - - // Broadcast test request to any test updaters - llRegionSay(UPDATER_CHANNEL, "TEST_REQUEST|" + CURRENT_VERSION + "|" + (string)testPin); - - llOwnerSay("๐Ÿท๏ธ If you have a Test_Updater nearby, it should respond now"); - } - else { - llOwnerSay("โ“ Unknown command. Say '/1 help' for available commands"); - } - } - else if (channel == UPDATE_DIALOG_CHANNEL) { - // Handle update dialog responses - if (id != pendingUpdateUser) return; // Only respond to the user who triggered the dialog - - if (message == "๐Ÿš€ Install Update") { - llOwnerSay("๐Ÿš€ Starting automatic update installation..."); - startUpdateInstallation(); - } - else if (message == "๐Ÿ“ฆ Scan Again") { - llOwnerSay("๐Ÿ” Scanning for GitHub updater boxes..."); - scanForUpdaters(); - // Dialog will be shown again when scan finds updaters - } - else if (message == "โŒ Cancel") { - llOwnerSay("โŒ Update installation cancelled"); - } - - // Clean up dialog listener and pending user - if (updateDialogHandle != -1) { - llListenRemove(updateDialogHandle); - updateDialogHandle = -1; - } - pendingUpdateUser = NULL_KEY; - } - else if (channel == UPDATER_CHANNEL) { - // Communication with updater boxes - list parts = llParseString2List(message, ["|"], []); - string command = llList2String(parts, 0); - - if (command == "UPDATER_AVAILABLE") { - string updaterVersion = llList2String(parts, 1); - llOwnerSay("๐Ÿ“ฆ Found GitHub updater: " + name + " v" + updaterVersion); - - // Add to list if not already there - if (llListFindList(nearbyUpdaters, [id]) == -1) { - nearbyUpdaters += [id]; - } - - // Auto-select if we don't have one selected - if (selectedUpdater == NULL_KEY) { - selectedUpdater = id; - llOwnerSay("โœ… Selected updater: " + name); - } - } - else if (command == "UPDATE_STARTING") { - string version = llList2String(parts, 1); - llOwnerSay("๐Ÿš€ Updater is installing " + version + " from GitHub..."); - } - else if (command == "UPDATE_COMPLETE") { - string version = llList2String(parts, 1); - completeUpdate(version); - } - else if (command == "UPDATE_FAILED") { - string error = llList2String(parts, 1); - handleUpdateFailure(error); - } - else if (command == "UPDATE_BUSY") { - llOwnerSay("โณ Updater is busy with another installation"); - } - else if (command == "REQUEST_LINK_UUIDS") { - // Updater is requesting our link UUIDs for proper script installation - llOwnerSay("๐Ÿ“ก Updater requesting link UUIDs for linkset script installation..."); - - integer linkCount = llGetNumberOfPrims(); - - // Send critical links first - list criticalLinks = [12, 35, 83]; // Scoreboard, Leaderboard, Dice Bridge - string criticalPayload = ""; - - integer i; - for (i = 0; i < llGetListLength(criticalLinks); i++) { - integer linkNum = llList2Integer(criticalLinks, i); - if (linkNum <= linkCount) { - key linkUUID = llGetLinkKey(linkNum); - if (criticalPayload != "") criticalPayload += ","; - criticalPayload += (string)linkNum + ":" + (string)linkUUID; - } - } - llRegionSayTo(id, UPDATER_CHANNEL, "LINK_UUIDS_RESPONSE|" + criticalPayload); - llOwnerSay("โœ… Sent critical link UUIDs (" + (string)llGetListLength(criticalLinks) + " links)"); - if (VERBOSE_LOGGING) llOwnerSay("๐Ÿ“Š Critical UUID Data: " + criticalPayload); - - // Now send all xyzzy links (35-82) in safe-sized chunks as additional LINK_UUIDS_RESPONSE messages - if (linkCount >= 82) { - string chunk = ""; - integer countInChunk = 0; - integer j; - for (j = 35; j <= 82; j++) { - key luuid = llGetLinkKey(j); - string piece = (string)j + ":" + (string)luuid; - // Flush if adding would exceed ~900 chars or ~20 entries - if ((llStringLength(chunk) + 1 + llStringLength(piece)) > 900 || countInChunk >= 20) { - llRegionSayTo(id, UPDATER_CHANNEL, "LINK_UUIDS_RESPONSE|" + chunk); - chunk = ""; - countInChunk = 0; - } - if (chunk != "") chunk += ","; - chunk += piece; - countInChunk++; - } - if (chunk != "") { - llRegionSayTo(id, UPDATER_CHANNEL, "LINK_UUIDS_RESPONSE|" + chunk); - } - llOwnerSay("โœ… Sent xyzzy UUIDs (links 35-82) in chunks"); - } - } - } - } - - link_message(integer sender_num, integer num, string str, key id) { - // Handle verbose logging toggle - if (num == MSG_TOGGLE_VERBOSE_LOGS) { - VERBOSE_LOGGING = !VERBOSE_LOGGING; - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ”Š [UpdateReceiver] Verbose logging ENABLED"); - } else { - llOwnerSay("๐Ÿ”Š [UpdateReceiver] Verbose logging DISABLED"); - } - return; - } - - // Handle update check requests from admin menu - if (num == MSG_UPDATE_CHECK_REQUEST) { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ”„ [UpdateReceiver] Received update check request from admin menu"); - } - - // Store the requesting user for potential dialog - if (availableVersion != "") { - // We already know there's an update - show install dialog immediately - showInstallUpdateDialog(id); - } else { - // Check for updates first - checkForUpdates(); - // We'll show the dialog in the HTTP response if an update is found - pendingUpdateUser = id; - } - return; - } - - // Respond to admin menu status queries - if (num == MSG_ADMIN_MENU_RESPONSE && str == "update_status") { - llMessageLinked(LINK_SET, MSG_ADMIN_MENU_RESPONSE, - "update_version|v" + CURRENT_VERSION, id); - return; - } - } - - sensor(integer num_detected) { - // Found scripted objects - check if any are updater boxes - nearbyUpdaters = []; - integer i; - for (i = 0; i < num_detected; i++) { - key detected = llDetectedKey(i); - string detectedName = llDetectedName(i); - - // Ping to see if it's an updater box - llRegionSayTo(detected, UPDATER_CHANNEL, "PING_UPDATER"); - } - - // Wait for responses - llSetTimerEvent(3.0); - } - - no_sensor() { - llOwnerSay("๐Ÿ“ญ No GitHub updater boxes found within " + (string)SENSOR_RANGE + "m"); - llOwnerSay("๐Ÿ’ก Please rez a 'Peril Dice Updater' box nearby to enable automatic updates"); - } - - timer() { - if (updateInProgress) { - // Update timeout - llOwnerSay("โฐ Update installation timed out"); - handleUpdateFailure("Installation timeout - updater may have failed"); - } else { - // Sensor response timeout - if (llGetListLength(nearbyUpdaters) == 0) { - llOwnerSay("๐Ÿ“ญ No GitHub updater boxes responded"); - llOwnerSay("๐Ÿ’ก Please rez a 'Peril Dice Updater' box nearby"); - } else { - llOwnerSay("โœ… Found " + (string)llGetListLength(nearbyUpdaters) + " GitHub updater box(es)"); - } - } - llSetTimerEvent(0.0); - } - - http_response(key request_id, integer status, list metadata, string body) { - if (request_id != currentHttpRequest) return; - - currentHttpRequest = NULL_KEY; - - if (currentOperation == "version_check") { - if (status == 200) { - if (VERBOSE_LOGGING) { - llOwnerSay("๐Ÿ“Š GitHub response: " + (string)llStringLength(body) + " chars (LSL limit: 2048)"); - } - - // Extract version using manual parsing - string latestVersion = extractJsonValue(body, "tag_name"); - string htmlUrl = extractJsonValue(body, "html_url"); - string publishedAt = extractJsonValue(body, "published_at"); - - if (latestVersion == "") { - llOwnerSay("โŒ Could not extract version from GitHub response"); - return; - } - - llOwnerSay("=== ๐Ÿ” UPDATE CHECK RESULTS ==="); - llOwnerSay("๐Ÿ“Š Current Version: v" + CURRENT_VERSION); - llOwnerSay("โœจ Latest GitHub Release: " + latestVersion); - - if (isNewerVersion(latestVersion, CURRENT_VERSION)) { - availableVersion = latestVersion; - llOwnerSay("๐Ÿ†• NEW VERSION AVAILABLE!"); - llOwnerSay("๐Ÿ“… Published: " + llGetSubString(publishedAt, 0, 9)); - llOwnerSay("๐ŸŒ View Release: " + htmlUrl); - llOwnerSay(""); - - // Check for updater boxes - if (llGetListLength(nearbyUpdaters) == 0) { - llOwnerSay("๐Ÿ“ฆ Scanning for GitHub updater boxes..."); - scanForUpdaters(); - } else { - llOwnerSay("โœ… GitHub updater box ready!"); - llOwnerSay("๐Ÿš€ Click 'Install Update' to automatically install " + latestVersion); - } - - // Show install dialog if request came from admin menu - if (pendingUpdateUser != NULL_KEY) { - showInstallUpdateDialog(pendingUpdateUser); - pendingUpdateUser = NULL_KEY; - } - } else { - llOwnerSay("โœ… You have the latest version!"); - llOwnerSay("๐Ÿ“ฆ GitHub updater system is ready for future updates"); - pendingUpdateUser = NULL_KEY; // Clear pending user - } - } else { - llOwnerSay("โŒ Update check failed (HTTP " + (string)status + ")"); - if (status == 403) { - llOwnerSay(" โณ GitHub rate limited - try again in a few minutes"); - } - } - } - - currentOperation = ""; - } -} \ No newline at end of file diff --git a/Verbose_Logger.lsl b/Verbose_Logger.lsl deleted file mode 100644 index d6c1f56..0000000 --- a/Verbose_Logger.lsl +++ /dev/null @@ -1,79 +0,0 @@ -// === Verbose Logger - Simple Debug Toggle Controller === -// Provides system-wide verbose logging toggle functionality -// Scripts use their own VERBOSE_LOGGING flags, this just coordinates the toggle - -// Verbose logging control -integer VERBOSE_LOGGING = FALSE; - -// Loop protection - prevent rapid toggle spam -float lastToggleTime = 0.0; -#define TOGGLE_COOLDOWN 2.0 // Minimum 2 seconds between toggles - -// Message constants for verbose logging system -#define MSG_VERBOSE_TOGGLE 9010 // Toggle verbose logging on/off -#define MSG_VERBOSE_BROADCAST 9011 // Broadcast logging state to all modules - -// Report memory status of this script -reportMemoryStatus() { - if (VERBOSE_LOGGING) { - integer free = llGetFreeMemory(); - integer used = llGetUsedMemory(); - llOwnerSay("๐Ÿ” [Verbose Logger] Memory - Free: " + (string)free + " Used: " + (string)used); - } -} - -default { - state_entry() { - llOwnerSay("๐Ÿ” Verbose Logger initialized - ready to handle debug toggle"); - } - - on_rez(integer start_param) { - llOwnerSay("๐Ÿ” Verbose Logger rezzed - debug system ready"); - VERBOSE_LOGGING = FALSE; // Default to off after rez - } - - link_message(integer sender, integer num, string str, key id) { - // Handle verbose logging toggle - FIXED to prevent loops - if (num == MSG_VERBOSE_TOGGLE && str == "TOGGLE_VERBOSE_LOGS") { - // Only respond if sender is NOT the Main Controller (prevent forwarding loop) - if (sender == 1) { - // This came from Main Controller forwarding - ignore to prevent loop - return; - } - - // Loop protection - prevent rapid toggle spam - float currentTime = llGetTime(); - if (currentTime - lastToggleTime < TOGGLE_COOLDOWN) { - return; // Silent ignore during cooldown - } - lastToggleTime = currentTime; - - VERBOSE_LOGGING = !VERBOSE_LOGGING; - - string status = "OFF"; - if (VERBOSE_LOGGING) { - status = "ON"; - llOwnerSay("๐Ÿ” [Verbose Logger] Debug logging: ENABLED"); - } else { - llOwnerSay("๐Ÿ” [Verbose Logger] Debug logging: DISABLED"); - } - - // Public announcement - llSay(0, "๐Ÿ” Verbose logging system-wide: " + status); - - // Broadcast the new setting to all modules - llMessageLinked(LINK_SET, MSG_VERBOSE_BROADCAST, "VERBOSE_LOGGING|" + (string)VERBOSE_LOGGING, id); - return; - } - - // Handle verbose logging broadcast from other scripts - if (num == MSG_VERBOSE_BROADCAST) { - list parts = llParseString2List(str, ["|"], []); - if (llList2String(parts, 0) == "VERBOSE_LOGGING") { - VERBOSE_LOGGING = llList2Integer(parts, 1); - return; - } - } - - } -} diff --git a/XyzzyText_Dice_Bridge_Linkset.lsl b/XyzzyText_Dice_Bridge_Linkset.lsl deleted file mode 100644 index b1ad138..0000000 --- a/XyzzyText_Dice_Bridge_Linkset.lsl +++ /dev/null @@ -1,145 +0,0 @@ -// XyzzyText Bridge Script - Dice Display Bridge (Linkset Version) -// This script goes in link 83 of the linkset -// Works with simple XyzzyText v1.0 script (like leaderboard) -// Handles dice roll display across 2 prims (20 characters total) - -// ============================================================================= -// LINKSET COMMUNICATION - NO DISCOVERY NEEDED -// ============================================================================= - -// Verbose logging control -integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs -#define MSG_TOGGLE_VERBOSE_LOGS 9998 // Message to toggle verbose logging - -// Message constants for link communication -// Dice messages (from link 1 - controller) -#define MSG_DICE_ROLL 3020 -#define MSG_CLEAR_DICE 3021 - -#define DISPLAY_STRING 204000 - -// Dice prim links -#define DICE_PRIM_1 83 // First dice prim (characters 0-9) -#define DICE_PRIM_2 84 // Second dice prim (characters 10-19) - -// Function to display dice text across both prims -displayDiceText(string text) { - // Ensure exactly 20 characters - while (llStringLength(text) < 20) { - text += " "; - } - if (llStringLength(text) > 20) { - text = llGetSubString(text, 0, 19); - } - - // Split into 10-character chunks for each prim - string leftText = llGetSubString(text, 0, 9); // Characters 0-9 - string rightText = llGetSubString(text, 10, 19); // Characters 10-19 - - // Send to individual XyzzyText prims - llMessageLinked(DICE_PRIM_1, DISPLAY_STRING, leftText, ""); - llMessageLinked(DICE_PRIM_2, DISPLAY_STRING, rightText, ""); - - if (VERBOSE_LOGGING) { - llOwnerSay("๐ŸŽฒ Dice display updated: '" + text + "'"); - } -} - -default { - on_rez(integer start_param) { - llResetScript(); // Fixes Z-fighting and alignment glitches on rez - } - state_entry() { - if (VERBOSE_LOGGING) { - llOwnerSay("๐ŸŽฒ Dice Display Bridge ready! (Linkset Version)"); - llOwnerSay("๐ŸŽฒ This is link " + (string)llGetLinkNumber() + " - should be link 83"); - llOwnerSay("๐ŸŽฒ Managing dice display links 83-84 (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 - - 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; - - if (num == MSG_DICE_ROLL) { - // Handle different message formats - list parts = llParseString2List(str, ["|"], []); - - if (llGetListLength(parts) >= 3) { - // Format: "player|result|dicetype" - single die roll - string player = llList2String(parts, 0); - string result = llList2String(parts, 1); - string diceType = llList2String(parts, 2); - - // Create dice display: "Player1: rolled 4 " - string displayText = player; - if (llStringLength(displayText) > 8) { - displayText = llGetSubString(displayText, 0, 7); - } - displayText += ": rolled " + result; - - displayDiceText(displayText); - } - else { - // Treat as direct display text - displayDiceText(str); - } - } - else if (num == MSG_CLEAR_DICE) { - // Clear dice display (20 spaces) - displayDiceText(" "); - if (VERBOSE_LOGGING) { - llOwnerSay("๐ŸŽฒ Dice display cleared"); - } - } - } - - touch_start(integer total_number) { - // Owner can get debug info and test display by touching dice - if (llDetectedKey(0) == llGetOwner()) { - llOwnerSay("๐ŸŽฒ Dice Display Status:"); - llOwnerSay(" Link Number: " + (string)llGetLinkNumber()); - llOwnerSay(" Total prims in linkset: " + (string)llGetNumberOfPrims()); - llOwnerSay(" Managing links 83-84 (2 XyzzyText prims)"); - llOwnerSay(" Listening for messages from link 1 (controller)"); - llOwnerSay(" Using simple XyzzyText v1.0 (like leaderboard)"); - - // Test display - 20 characters total - displayDiceText("TEST: Player1 -> 6 "); - llOwnerSay("๐ŸŽฒ Test display sent: 'TEST: Player1 -> 6 '"); - llOwnerSay("๐ŸŽฒ Touch again in 3 seconds to clear test"); - - // Set timer to clear test display - llSetTimerEvent(3.0); - } else { - // Non-owner touch - show friendly message - llSay(0, "๐ŸŽฒ Dice display shows the latest rolls! Watch for game actions!"); - } - } - - timer() { - // Clear test display - llSetTimerEvent(0.0); // Stop timer - displayDiceText(" "); // 20 spaces - llOwnerSay("๐ŸŽฒ Test display cleared"); - } -} diff --git a/xyzzy_Master_script.lsl b/xyzzy_Master_script.lsl deleted file mode 100644 index 7b93fb1..0000000 --- a/xyzzy_Master_script.lsl +++ /dev/null @@ -1,443 +0,0 @@ -//////////////////////////////////////////// -// XyText v1.0.3 Script (5 Face, Multi Texture) -// -// Written by Xylor Baysklef -// -// Modified by Thraxis Epsilon January 20, 2006 -// Added Support for 5 Face Prim, based on modification -// of [[XyText]] v1.1.1 by Kermitt Quick for Single Texture. -// -//////////////////////////////////////////// - -/////////////// CONSTANTS /////////////////// -// XyText Message Map. -#define DISPLAY_STRING 204000 -#define DISPLAY_EXTENDED 204001 -#define REMAP_INDICES 204002 -#define RESET_INDICES 204003 -#define SET_CELL_INFO 204004 -#define SET_THICKNESS 204006 -#define SET_COLOR 204007 - -// This is an extended character escape sequence. -#define ESCAPE_SEQUENCE "\e" - -// This is used to get an index for the extended character. -#define EXTENDED_INDEX "123456789abcdef" - -// Face numbers. -#define FACE_1 3 -#define FACE_2 7 -#define FACE_3 4 -#define FACE_4 6 -#define FACE_5 1 - -// Used to hide the text after a fade-out. -#define TRANSPARENT "701917a8-d614-471f-13dd-5f4644e36e3c" -// This is a list of textures for all 2-character combinations. -list CHARACTER_GRID = [ - "00e9f9f7-0669-181c-c192-7f8e67678c8d", - "347a5cb6-0031-7ec0-2fcf-f298eebf3c0e", - "4e7e689e-37f1-9eca-8596-a958bbd23963", - "19ea9c21-67ba-8f6f-99db-573b1b877eb1", - "dde7b412-cda1-652f-6fc2-73f4641f96e1", - "af6fa3bb-3a6c-9c4f-4bf5-d1c126c830da", - "a201d3a2-364b-43b6-8686-5881c0f82a94", - "b674dec8-fead-99e5-c28d-2db8e4c51540", - "366e05f3-be6b-e5cf-c33b-731dff649caa", - "75c4925c-0427-dc0c-c71c-e28674ff4d27", - "dcbe166b-6a97-efb2-fc8e-e5bc6a8b1be6", - "0dca2feb-fc66-a762-db85-89026a4ecd68", - "a0fca76f-503a-946b-9336-0a918e886f7a", - "67fb375d-89a1-5a4f-8c7a-0cd1c066ffc4", - "300470b2-da34-5470-074c-1b8464ca050c", - "d1f8e91c-ce2b-d85e-2120-930d3b630946", - "2a190e44-7b29-dadb-0bff-c31adaf5a170", - "75d55e71-f6f8-9835-e746-a45f189f30a1", - "300fac33-2b30-3da3-26bc-e2d70428ec19", - "0747c776-011a-53ce-13ee-8b5bb9e87c1e", - "85a855c3-a94f-01ca-33e0-7dde92e727e2", - "cbc1dab2-2d61-2986-1949-7a5235c954e1", - "f7aef047-f266-9596-16df-641010edd8e1", - "4c34ebf7-e5e1-2e1a-579f-e224d9d5e71b", - "4a69e98c-26a5-ad05-e92e-b5b906ad9ef9", - "462a9226-2a97-91ac-2d89-57ab33334b78", - "20b24b3a-8c57-82ee-c6ed-555003f5dbcd", - "9b481daa-9ea8-a9fa-1ee4-ab9a0d38e217", - "c231dbdc-c842-15b0-7aa6-6da14745cfdc", - "c97e3cbb-c9a3-45df-a0ae-955c1f4bf9cf", - "f1e7d030-ff80-a242-cb69-f6951d4eae3b", - "ed32d6c4-d733-c0f1-f242-6df1d222220d", - "88f96a30-dccf-9b20-31ef-da0dfeb23c72", - "252f2595-58b8-4bcc-6515-fa274d0cfb65", - "f2838c4f-de80-cced-dff8-195dfdf36b2c", - "cc2594fe-add2-a3df-cdb3-a61711badf53", - "e0ce2972-da00-955c-129e-3289b3676776", - "3e0d336d-321f-ddfa-5c1b-e26131766f6a", - "d43b1dc4-6b51-76a7-8b90-38865b82bf06", - "06d16cbb-1868-fd1d-5c93-eae42164a37d", - "dd5d98cf-273e-3fd0-f030-48be58ee3a0b", - "0e47c89e-de4a-6233-a2da-cb852aad1b00", - "fb9c4a55-0e13-495b-25c4-f0b459dc06de", - "e3ce8def-312c-735b-0e48-018b6799c883", - "2f713216-4e71-d123-03ed-9c8554710c6b", - "4a417d8a-1f4f-404b-9783-6672f8527911", - "ca5e21ec-5b20-5909-4c31-3f90d7316b33", - "06a4fcc3-e1c4-296d-8817-01f88fbd7367", - "130ac084-6f3c-95de-b5b6-d25c80703474", - "59d540a0-ae9d-3606-5ae0-4f2842b64cfa", - "8612ae9a-f53c-5bf4-2899-8174d7abc4fd", - "12467401-e979-2c49-34e0-6ac761542797", - "d53c3eaa-0404-3860-0675-3e375596c3e3", - "9f5b26bd-81d3-b25e-62fe-5b671d1e3e79", - "f57f0b64-a050-d617-ee00-c8e9e3adc9cb", - "beff166a-f5f3-f05e-e020-98f2b00e27ed", - "02278a65-94ba-6d5e-0d2b-93f2e4f4bf70", - "a707197d-449e-5b58-846c-0c850c61f9d6", - "021d4b1a-9503-a44f-ee2b-976eb5d80e68", - "0ae2ffae-7265-524d-cb76-c2b691992706", - "f6e41cf2-1104-bd0b-0190-dffad1bac813", - "2b4bb15e-956d-56ae-69f5-d26a20de0ce7", - "f816da2c-51f1-612a-2029-a542db7db882", - "345fea05-c7be-465c-409f-9dcb3bd2aa07", - "b3017e02-c063-5185-acd5-1ef5f9d79b89", - "4dcff365-1971-3c2b-d73c-77e1dc54242a" - ]; - -///////////// END CONSTANTS //////////////// - -///////////// GLOBAL VARIABLES /////////////// -// All displayable characters. Default to ASCII order. -string gCharIndex; -// This is the channel to listen on while acting -// as a cell in a larger display. -integer gCellChannel = -1; -// This is the starting character position in the cell channel message -// to render. -integer gCellCharPosition = 0; -// This is whether or not to use the fade in/out special effect. -integer gCellUseFading = FALSE; -// This is how long to display the text before fading out (if using -// fading special effect). -// Note: < 0 means don't fade out. -float gCellHoldDelay = 1.0; -/////////// END GLOBAL VARIABLES //////////// - -ResetCharIndex() -{ - gCharIndex = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"; - // \" <-- Fixes LSL syntax highlighting bug. - gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~"; - gCharIndex += "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; -} - -vector GetGridPos(integer index1, integer index2) -{ - // There are two ways to use the lookup table... - integer Col; - integer Row; - if (index1 >= index2) - { - // In this case, the row is the index of the first character: - Row = index1; - // And the col is the index of the second character (x2) - Col = index2 * 2; - } - // Index1 < Index2 - else - { - // In this case, the row is the index of the second character: - Row = index2; - // And the col is the index of the first character, x2, offset by 1. - Col = index1 * 2 + 1; - } - return ; -} - -string GetGridTexture(vector grid_pos) -{ - // Calculate the texture in the grid to use. - integer GridCol = llRound(grid_pos.x) / 20; - integer GridRow = llRound(grid_pos.y) / 10; - - // Lookup the texture. - key Texture = llList2Key(CHARACTER_GRID, GridRow * (GridRow + 1) / 2 + GridCol); - return Texture; -} - -vector GetGridOffset(vector grid_pos) -{ - // Zoom in on the texture showing our character pair. - integer offsetCol = llRound(grid_pos.x) % 20; - integer offsetRow = llRound(grid_pos.y) % 10; - - // Return the offset in the texture. - return <-0.45 + 0.05 * offsetCol, 0.45 - 0.1 * offsetRow, 0.0>; -} - -ShowChars(vector grid_pos1, vector grid_pos2, vector grid_pos3, vector grid_pos4, vector grid_pos5) -{ - // Set the primitive textures directly. - - - llSetLinkPrimitiveParamsFast(LINK_THIS, [ - PRIM_TEXTURE, FACE_1, GetGridTexture(grid_pos1), <0.25, 0.1, 0>, GetGridOffset(grid_pos1) + <0.075, 0, 0>, 0.0, - PRIM_TEXTURE, FACE_2, GetGridTexture(grid_pos2), <0.1, 0.1, 0>, GetGridOffset(grid_pos2), 0.0, - PRIM_TEXTURE, FACE_3, GetGridTexture(grid_pos3), <-1.48, 0.1, 0>, GetGridOffset(grid_pos3)+ <0.37, 0, 0>, 0.0, - PRIM_TEXTURE, FACE_4, GetGridTexture(grid_pos4), <0.1, 0.1, 0>, GetGridOffset(grid_pos4), 0.0, - PRIM_TEXTURE, FACE_5, GetGridTexture(grid_pos5), <0.25, 0.1, 0>, GetGridOffset(grid_pos5) - <0.075, 0, 0>, 0.0]); -} - -RenderString(string str) -{ - // Get the grid positions for each pair of characters. - vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)), - llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); - vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)), - llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); - vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)), - llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) ); - vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)), - llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) ); - vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)), - llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) ); - - // Use these grid positions to display the correct textures/offsets. - ShowChars(GridPos1, GridPos2, GridPos3, GridPos4, GridPos5); -} - -RenderWithEffects(string str) -{ - // Get the grid positions for each pair of characters. - vector EffectsPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)), - llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); - vector EffectsPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)), - llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); - vector EffectsPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)), - llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) ); - vector EffectsPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)), - llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) ); - vector EffectsPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)), - llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) ); - - // First set the alpha to the lowest possible. - llSetAlpha(0.05, ALL_SIDES); - - // Use these grid positions to display the correct textures/offsets. - ShowChars(EffectsPos1, EffectsPos2, EffectsPos3, EffectsPos4, EffectsPos5); - - float Alpha; - for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05) - llSetAlpha(Alpha, ALL_SIDES); - // See if we want to fade out as well. - if (gCellHoldDelay < 0.0) - // No, bail out. (Just keep showing the string at full strength). - return; - // Hold the text for a while. - llSleep(gCellHoldDelay); - // Now fade out. - for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05) - llSetAlpha(Alpha, ALL_SIDES); - // Make the text transparent to fully hide it. - llSetTexture(TRANSPARENT, ALL_SIDES); -} - -RenderExtended(string str) -{ - // Look for escape sequences. - list Parsed = llParseString2List(str, [], [ESCAPE_SEQUENCE]); - integer ParsedLen = llGetListLength(Parsed); - - // Create a list of index values to work with. - list Indices; - // We start with room for 6 indices. - integer IndicesLeft = 10; - - integer i; - string Token; - integer Clipped; - integer LastWasEscapeSequence = FALSE; - // Work from left to right. - for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) - { - Token = llList2String(Parsed, i); - - // If this is an escape sequence, just set the flag and move on. - if (Token == ESCAPE_SEQUENCE) - { - LastWasEscapeSequence = TRUE; - } - // Token != ESCAPE_SEQUENCE - else - { - // Otherwise this is a normal token. Check its length. - Clipped = FALSE; - integer TokenLength = llStringLength(Token); - // Clip if necessary. - if (TokenLength > IndicesLeft) - { - Token = llGetSubString(Token, 0, IndicesLeft - 1); - TokenLength = llStringLength(Token); - IndicesLeft = 0; - Clipped = TRUE; - } - else - IndicesLeft -= TokenLength; - - // Was the previous token an escape sequence? - if (LastWasEscapeSequence) - { - // Yes, the first character is an escape character, the rest are normal. - - // This is the extended character. - Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95]; - - // These are the normal characters. - integer j; - for (j = 1; j < TokenLength; j++) - { - Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))]; - } - } - // Normal string. - else - { - // Just add the characters normally. - integer k; - for (k = 0; k < TokenLength; k++) - Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, k, k))]; - } - - // Unset this flag, since this was not an escape sequence. - LastWasEscapeSequence = FALSE; - } - } - - // Use the indices to create grid positions. - vector ExtendedPos1 = GetGridPos( llList2Integer(Indices, 0), llList2Integer(Indices, 1) ); - vector ExtendedPos2 = GetGridPos( llList2Integer(Indices, 2), llList2Integer(Indices, 3) ); - vector ExtendedPos3 = GetGridPos( llList2Integer(Indices, 4), llList2Integer(Indices, 5) ); - vector ExtendedPos4 = GetGridPos( llList2Integer(Indices, 6), llList2Integer(Indices, 7) ); - vector ExtendedPos5 = GetGridPos( llList2Integer(Indices, 8), llList2Integer(Indices, 9) ); - - // Use these grid positions to display the correct textures/offsets. - ShowChars(ExtendedPos1, ExtendedPos2, ExtendedPos3, ExtendedPos4, ExtendedPos5); -} - -integer ConvertIndex(integer index) -{ - // This converts from an ASCII based index to our indexing scheme. - if (index >= 32) // ' ' or higher - index -= 32; - // index < 32 - else - { - // Quick bounds check. - if (index > 15) - index = 15; - - index += 94; // extended characters - } - - return index; -} - -default -{ - state_entry() - { - // Initialize the character index. - ResetCharIndex(); - - //llSay(0, "Free Memory: " + (string) llGetFreeMemory()); - } - - link_message(integer sender, integer channel, string data, key id) - { - if (channel == DISPLAY_STRING) - { - RenderString(data); - return; - } - if (channel == DISPLAY_EXTENDED) - { - RenderExtended(data); - return; - } - if (channel == gCellChannel) - { - // Extract the characters we are interested in, and use those to render. - string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 9); - if (gCellUseFading) - RenderWithEffects( TextToRender ); - else // !gCellUseFading - RenderString( TextToRender ); - return; - } - if (channel == REMAP_INDICES) - { - // Parse the message, splitting it up into index values. - list RemapParsed = llCSV2List(data); - integer m; - // Go through the list and swap each pair of indices. - for (m = 0; m < llGetListLength(RemapParsed); m += 2) - { - integer Index1 = ConvertIndex( llList2Integer(RemapParsed, m) ); - integer Index2 = ConvertIndex( llList2Integer(RemapParsed, m + 1) ); - - // Swap these index values. - string Value1 = llGetSubString(gCharIndex, Index1, Index1); - string Value2 = llGetSubString(gCharIndex, Index2, Index2); - - gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1); - gCharIndex = llInsertString(gCharIndex, Index1, Value2); - - gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2); - gCharIndex = llInsertString(gCharIndex, Index2, Value1); - } - return; - } - if (channel == RESET_INDICES) - { - // Restore the character index back to default settings. - ResetCharIndex(); - return; - } - if (channel == SET_CELL_INFO) - { - // Change the channel we listen to for cell commands, and the - // starting character position to extract from. - list CellParsed = llCSV2List(data); - gCellChannel = (integer) llList2String(CellParsed, 0); - gCellCharPosition = (integer) llList2String(CellParsed, 1); - gCellUseFading = (integer) llList2String(CellParsed, 2); - gCellHoldDelay = (float) llList2String(CellParsed, 3); - return; - } - if (channel == SET_THICKNESS) - { - // Set our z scale to thickness, while staying fixed - // in position relative the prim below us. - vector Scale = llGetScale(); - float Thickness = (float) data; - // Reposition only if this isn't the root prim. - integer ThisLink = llGetLinkNumber(); - if (ThisLink != 0 || ThisLink != 1) - { - // This is not the root prim. - vector Up = llRot2Up(llGetLocalRot()); - float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0; - vector Pos = llGetLocalPos(); - llSetPos(Pos + DistanceToMove * Up); - } - // Apply the new thickness. - Scale.z = Thickness; - llSetScale(Scale); - return; - } - if (channel == SET_COLOR) { - vector newColor = (vector)data; - llSetColor(newColor, ALL_SIDES); - } - } -} \ No newline at end of file