chore: v3.1.0 Peril Dice - Linkset Stability Update & FURWARE Text Mesh Migration
This commit is contained in:
parent
8931c88c35
commit
915c079a7a
35 changed files with 2308 additions and 4202 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
CHANGELOG.md
25
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`).
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
|||
49
FURWARE_Leaderboard_Communication_Linkset.lsl
Normal file
49
FURWARE_Leaderboard_Communication_Linkset.lsl
Normal file
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
66
FURWARE_Text_Dice_Bridge_Linkset.lsl
Normal file
66
FURWARE_Text_Dice_Bridge_Linkset.lsl
Normal file
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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 + <xOffset, yOffset, 1>;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1055
Furware_Text_Engine.lsl
Normal file
1055
Furware_Text_Engine.lsl
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
143
Game_Manager.lsl
143
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 = "";
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
131
Peril_Constants.lsl
Normal file
131
Peril_Constants.lsl
Normal file
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) &&
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
45
README.md
45
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
==========================
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = "";
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = "";
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <Col, Row, 0>;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue