added memmory toggle in peril_constants.lsl, added new memory macro script in constants, added macro to all files, optimised scoreboard manager, bug fixes in mulitible scripts

This commit is contained in:
RebeccaNod1 2026-04-04 18:16:01 -06:00
parent 915c079a7a
commit a225244241
18 changed files with 386 additions and 548 deletions

View file

@ -57,18 +57,6 @@ list processedBotCommands = [];
// Track sent BOT_PICKED messages to prevent duplicate sending
list sentBotMessages = [];
// 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)");
}
// Helper function to check and report memory usage
checkMemoryUsage(string context) {
@ -201,7 +189,7 @@ doBotRoll(string botName, integer diceMax) {
default {
state_entry() {
reportMemoryUsage("🤖 Bot Manager");
REPORT_MEMORY();
initializeChannels();
LISTEN_CHANNEL = BOT_COMMAND_CHANNEL;
@ -222,7 +210,7 @@ default {
}
on_rez(integer start_param) {
reportMemoryUsage("🤖 Bot Manager");
REPORT_MEMORY();
dbg("🤖 [Bot Manager] 🔄 Bot Manager rezzed - reinitializing...");
// Re-initialize dynamic channels

View file

@ -113,12 +113,17 @@ processMainControllerStats(string statsData) {
default {
state_entry() {
REPORT_MEMORY();
dbg("📊 [Memory Monitor] Memory Monitor ready!");
// Start periodic memory monitoring
llSetTimerEvent(MEMORY_CHECK_INTERVAL);
}
on_rez(integer start_param) {
REPORT_MEMORY();
}
timer() {
// Periodic memory check
checkMemoryUsage("periodic_check");

View file

@ -40,18 +40,6 @@ 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
@ -326,13 +314,13 @@ handleDialogRequest(string dialogData) {
default {
state_entry() {
reportMemoryUsage("📨 Message Handler");
REPORT_MEMORY();
initializeChannels();
}
on_rez(integer start_param) {
reportMemoryUsage("📨 Message Handler");
REPORT_MEMORY();
initializeChannels();
// Reset local state if needed (synced from Main)
players = [];

View file

@ -16,6 +16,7 @@ default {
state_entry() {
DISCOVER_CORE_LINKS();
REPORT_MEMORY();
dbg("📋 [Leaderboard] Leaderboard Communication Script ready! (FURWARE Version)");
dbg("📋 [Leaderboard] Managing 'Leaderboard' FURWARE box");
dbg("📋 [Leaderboard] ✅ Discovery complete! Scoreboard: " + (string)LINK_SCOREBOARD);

View file

@ -19,6 +19,7 @@ default {
}
state_entry() {
DISCOVER_CORE_LINKS();
REPORT_MEMORY();
dbg("🎲 [Dice Bridge] Dice Display Bridge ready! (FURWARE Version)");
dbg("🎲 [Dice Bridge] ✅ Discovery complete! Controller: " + (string)LINK_CONTROLLER);

View file

@ -41,18 +41,6 @@ initializeChannels() {
// Debug control - set to TRUE for verbose pick debugging, FALSE for normal operation
integer DEBUG_PICKS = FALSE;
// 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)");
}
// ALL REGISTERED PLAYERS (for channel mapping - order never changes)
list allPlayerNames = []; // Names in registration order
@ -90,7 +78,7 @@ list getPicksFor(string nameInput) {
// Main event handler
default {
state_entry() {
reportMemoryUsage("💬 Floater Manager");
REPORT_MEMORY();
initializeChannels();
@ -106,7 +94,7 @@ default {
}
on_rez(integer start_param) {
reportMemoryUsage("💬 Floater Manager");
REPORT_MEMORY();
dbg("💬 [Floater Manager] 🔄 Floater Manager rezzed - reinitializing...");
initializeChannels();
@ -133,47 +121,56 @@ default {
return;
}
if (num == MSG_REGISTER_PLAYER) {
if (llGetListLength(allPlayerNames) >= MAX_PLAYERS) {
dbg("💬 [Floater Manager] ⚠️ Game is full (max " + (string)MAX_PLAYERS + ").");
return;
}
list info = llParseString2List(str, ["|"], []);
string name = llList2String(info, 0);
key avKey = llList2Key(info, 1);
if (llListFindList(allPlayerNames, [name]) != -1) return;
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) {
// MSG_REGISTER_PLAYER (106) is now handled by Player_RegistrationManager.lsl
// which then sends MSG_REZ_FLOAT (105) here. Legacy handler removed to prevent double-rezzing.
else if (num == MSG_REZ_FLOAT) { // 105
string name = str;
integer channelIdx = llListFindList(allPlayerNames, [name]);
if (channelIdx == -1) return;
key avKey = id;
key avKey = id; // Passed from MSG_REGISTER_PLAYER or MSG_REZ_FLOAT
list details = llGetObjectDetails(avKey, [OBJECT_POS]);
vector basePos = llGetPos();
if (llGetListLength(details) > 0) basePos = llList2Vector(details, 0);
vector pos;
if (llGetListLength(details) > 0) {
pos = basePos + <1, 0, 1>; // Avatar relative
} else {
// 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>;
// Handle name|key format if sent
integer pipeIdx = llSubStringIndex(str, "|");
if (pipeIdx != -1) {
name = llGetSubString(str, 0, pipeIdx - 1);
if (avKey == NULL_KEY) avKey = (key)llGetSubString(str, pipeIdx + 1, -1);
}
integer ch = FLOATER_BASE_CHANNEL + channelIdx;
llRezObject("StatFloat", pos, ZERO_VECTOR, ZERO_ROTATION, ch);
llSleep(0.2);
llRegionSay(ch, "SET_NAME:" + name);
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, name, avKey);
// Ensure player is in our permanent map
integer channelIdx = llListFindList(allPlayerNames, [name]);
if (channelIdx == -1) {
if (llGetListLength(allPlayerNames) >= MAX_PLAYERS) return;
allPlayerNames += [name];
channelIdx = llGetListLength(allPlayerNames) - 1;
// --- AUTHENTIC REZ BLOCK ---
// Only rez if we had to add them to the list (first time seeing them)
list details = llGetObjectDetails(avKey, [OBJECT_POS]);
vector basePos = llGetPos();
if (llGetListLength(details) > 0) basePos = llList2Vector(details, 0);
vector pos;
if (llGetListLength(details) > 0) {
pos = basePos + <1, 0, 1>; // Avatar relative
} else {
// 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);
llSleep(0.2);
llRegionSay(ch, "SET_NAME:" + name);
// Initial update to populate text/life display
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, name, avKey);
} else {
// Already in list - just trigger an update in case it was a re-registration
dbg("🛡️ [Rez Guard] " + name + " is already rezzed. Skipping redundant creation.");
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, name, avKey);
}
}
else if (num == MSG_UPDATE_FLOAT) {
string name = str;
@ -240,6 +237,12 @@ default {
integer foundIdx = ch - FLOATER_BASE_CHANNEL;
if (foundIdx >= 0 && foundIdx < llGetListLength(allPlayerNames)) {
string removedPlayer = llList2String(allPlayerNames, foundIdx);
// --- CRITICAL FIX: Clear from Rez Guard registry ---
// We don't remove from allPlayerNames (to maintain channel indices)
// BUT we replace it with an empty string so the slot is 'available' again
allPlayerNames = llListReplaceList(allPlayerNames, [""], foundIdx, foundIdx);
integer currentIdx = llListFindList(names, [removedPlayer]);
if (currentIdx != -1) {
if (currentIdx < llGetListLength(players)) players = llDeleteSubList(players, currentIdx, currentIdx);
@ -258,6 +261,19 @@ default {
if (pickIdx != -1) picksData = llDeleteSubList(picksData, pickIdx, pickIdx);
}
}
else if (num == MSG_LEAVE_GAME) { // 8007
// Format: LEAVE_GAME|NAME|KEY
list parts = llParseString2List(str, ["|"], []);
string leaveName = llList2String(parts, 1);
integer chanIdx = llListFindList(allPlayerNames, [leaveName]);
if (chanIdx != -1) {
// Remove their floater and clear the slot for re-entry
dbg("🚮 [Floater Manager] Player " + leaveName + " left - cleaning up floater.");
llRegionSay(FLOATER_BASE_CHANNEL + chanIdx, "CLEANUP");
allPlayerNames = llListReplaceList(allPlayerNames, [""], chanIdx, chanIdx);
}
}
else if (num == MSG_SYNC_GAME_STATE) {
list parts = llParseString2List(str, ["~"], []);
if (llGetListLength(parts) < 4) return;
@ -278,7 +294,16 @@ default {
}
names = llCSV2List(llList2String(parts, 3));
if (llGetListLength(allPlayerNames) == 0 && llGetListLength(names) > 0) allPlayerNames = names;
// Robustly update permanent map from sync names
integer nIdx;
for (nIdx = 0; nIdx < llGetListLength(names); nIdx++) {
string nName = llList2String(names, nIdx);
if (llListFindList(allPlayerNames, [nName]) == -1) {
if (llGetListLength(allPlayerNames) < MAX_PLAYERS) {
allPlayerNames += [nName];
}
}
}
if (llGetListLength(parts) >= 5) players = llCSV2List(llList2String(parts, 4));
integer idx;
@ -291,11 +316,20 @@ default {
}
}
else if (num == MSG_CLEANUP_ALL_FLOATERS) {
dbg("🧹 [Floater Manager] Universal Cleanup Triggered - Clearing all " + (string)MAX_PLAYERS + " possible channels.");
integer i;
for (i = 0; i < llGetListLength(allPlayerNames); i++) {
for (i = 0; i < MAX_PLAYERS; i++) {
// Ensure we hit EVERY possible channel, even if they aren't in our current memory
llRegionSay(FLOATER_BASE_CHANNEL + i, "CLEANUP");
}
players = []; names = []; lives = []; picksData = []; perilPlayer = "";
// Clear ALL registries to allow fresh re-registration via Rez Guard
allPlayerNames = [];
players = [];
names = [];
lives = [];
picksData = [];
perilPlayer = "";
dbg("✅ [Floater Manager] All player registries wiped.");
}
else if (num == MSG_DEBUG_PICKS_ON) DEBUG_PICKS = TRUE;
else if (num == MSG_DEBUG_PICKS_OFF) DEBUG_PICKS = FALSE;

View file

@ -610,7 +610,12 @@ integer setDirty(integer action, integer first, integer last, integer isConf,
////////// STATES //////////////////////////////////////////
default {
on_rez(integer start_param) {
REPORT_MEMORY();
}
state_entry() {
REPORT_MEMORY();
llOwnerSay("FURWARE text is starting...");
CHARS = llBase64ToString(

View file

@ -5,18 +5,6 @@
// This version chooses a dice size based on the number of players,
// ensuring at least three numbers are available per player.
// 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)");
}
list lives;
list picksData;
@ -120,7 +108,7 @@ integer showPickManager(string player, key id) {
default {
state_entry() {
reportMemoryUsage("🧮 Game Calculator");
REPORT_MEMORY();
// Initialize/reset all state variables
lives = [];
@ -132,7 +120,7 @@ default {
}
on_rez(integer start_param) {
reportMemoryUsage("🧮 Game Calculator");
REPORT_MEMORY();
// Reset all state variables on rez
lives = [];

View file

@ -395,7 +395,7 @@ integer syncStateToMain() {
default {
state_entry() {
DISCOVER_CORE_LINKS();
reportMemoryUsage("Game Manager");
REPORT_MEMORY();
dbg("🎯 Game Manager ready - discovery complete! Scoreboard: " + (string)LINK_SCOREBOARD);
// Initialize/reset all game state variables
@ -422,7 +422,7 @@ default {
on_rez(integer start_param) {
DISCOVER_CORE_LINKS();
reportMemoryUsage("Game Manager");
REPORT_MEMORY();
dbg("🔄 Game Manager reset via rez...");
// Reset all game state variables on rez

View file

@ -8,20 +8,22 @@
integer BACKGROUND_PRIM = -1;
integer ACTIONS_PRIM = -1;
// Link mappings for player grid (Rows 0-4, Columns 0-1)
list profileLinks = [0,0,0,0,0,0,0,0,0,0];
list heartsLinks = [0,0,0,0,0,0,0,0,0,0];
list overlayLinks = [0,0,0,0,0,0,0,0,0,0];
// Grid link mappings for player grid (Stride 3: Profile, Hearts, Overlay)
list gridLinks = [
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0,
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0
];
// Discover all relevant prims by name at startup
discoverLinks() {
integer i;
integer total = llGetNumberOfPrims();
// Reset lists
profileLinks = [0,0,0,0,0,0,0,0,0,0];
heartsLinks = [0,0,0,0,0,0,0,0,0,0];
overlayLinks = [0,0,0,0,0,0,0,0,0,0];
// Reset list (30 elements for 10 slots * 3 prims)
gridLinks = [
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0,
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0
];
for (i = 1; i <= total; i++) {
string name = llGetLinkName(i);
@ -39,16 +41,17 @@ discoverLinks() {
integer index = (row * 2) + col;
if (index >= 0 && index < 10) {
if (type == "profile") profileLinks = llListReplaceList(profileLinks, [i], index, index);
else if (type == "life") heartsLinks = llListReplaceList(heartsLinks, [i], index, index);
else if (type == "overlay") overlayLinks = llListReplaceList(overlayLinks, [i], index, index);
integer gridBase = index * 3;
if (type == "profile") gridLinks = llListReplaceList(gridLinks, [i], gridBase, gridBase);
else if (type == "life") gridLinks = llListReplaceList(gridLinks, [i], gridBase + 1, gridBase + 1);
else if (type == "overlay") gridLinks = llListReplaceList(gridLinks, [i], gridBase + 2, gridBase + 2);
}
}
}
}
dbg("📈 [Scoreboard Manager] 🔍 Dynamic Discovery complete!");
if (BACKGROUND_PRIM == -1) dbg("⚠️ WARNING: Background prim ('backboard:0:0') not found!");
dbg("Discovery done");
if (BACKGROUND_PRIM == -1) dbg("No backboard");
}
// --- Leaderboard Configuration ---
@ -80,18 +83,14 @@ discoverLinks() {
#define TEXTURE_ELIMINATED_X "90524092-03b0-1b3c-bcea-3ea5118c6dba" // Red X overlay for eliminated players
// Player data - used for BOTH current game display AND leaderboard
list playerNames = []; // Current game players for display
list playerLives = []; // Current game player lives
list playerProfiles = []; // Maps to profile texture UUIDs
list activePlayers = []; // Current game state (Stride 3: Name, Lives, UUID)
// Peril player tracking
string currentPerilPlayer = ""; // Track current peril player for glow effects
string currentWinner = ""; // Track current winner for green glow effects
// Leaderboard data (persistent - stored in linkset data)
list leaderboardNames = []; // Loaded from linkset data at startup
list leaderboardWins = []; // Loaded from linkset data at startup
list leaderboardLosses = []; // Loaded from linkset data at startup
// Leaderboard data (persistent - stored in linkset data) - Stride of 3 [Name, Wins, Losses]
list leaderboardData = [];
// HTTP tracking
list profileRequests = []; // Track which profile requests belong to which player
@ -102,25 +101,13 @@ key kvpReadReq;
key kvpWriteReq;
string pendingSerialize;
// Memory reporting function
integer 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)");
return 0;
}
// Profile picture extraction constants
string profile_key_prefix = "<meta name=\"imageid\" content=\"";
string profile_img_prefix = "<img alt=\"profile image\" src=\"http://secondlife.com/app/image/";
integer profile_key_prefix_length;
integer profile_img_prefix_length;
// Profile picture extraction constants
#define PROFILE_KEY_PREFIX "<meta name=\"imageid\" content=\""
#define PROFILE_IMG_PREFIX "<img alt=\"profile image\" src=\"http://secondlife.com/app/image/"
#define PROFILE_KEY_PREFIX_LENGTH 30
#define PROFILE_IMG_PREFIX_LENGTH 59
// Reset background prim to proper black background
integer resetBackgroundPrim() {
@ -131,7 +118,7 @@ integer resetBackgroundPrim() {
PRIM_TEXT, "", <0,0,0>, 0.0 // Remove any text
]);
dbg("📈 [Scoreboard Manager] 🖤 Reset background prim " + (string)BACKGROUND_PRIM + " to black");
dbg("Backboard Reset");
return 0;
}
@ -143,7 +130,7 @@ integer resetManagerCube() {
PRIM_TEXT, "", <0,0,0>, 0.0 // Remove any text
]);
dbg("📈 [Scoreboard Manager] 📦 Reset manager cube (link 12) to black");
dbg("Manager Reset");
return 0;
}
@ -184,21 +171,26 @@ integer updateActionsPrim(string status) {
// Load leaderboard data from Experience KVP database
integer loadLeaderboardData() {
kvpReadReq = llReadKeyValue("Peril_LB_Top50");
dbg("📈 [Scoreboard Manager] 🔍 Requesting global scoreboard from Experience database...");
dbg("LB Requesting");
return 0;
}
// Save leaderboard data to Experience KVP database
integer saveLeaderboardData() {
string serialized = "";
integer saveCount = llGetListLength(leaderboardNames);
if (saveCount > 50) saveCount = 50; // Cap at Top 50 to fit safely in KVP 4Kb limit
integer totalEntries = llGetListLength(leaderboardData) / 3;
integer saveCount = totalEntries;
integer saveI = 0;
integer base = 0;
string save_entry = "";
if (saveCount > 50) saveCount = 50; // Cap at Top 50
integer saveI;
for (saveI = 0; saveI < saveCount; saveI++) {
string save_entry = llList2String(leaderboardNames, saveI) + ":" +
(string)llList2Integer(leaderboardWins, saveI) + ":" +
(string)llList2Integer(leaderboardLosses, saveI);
base = saveI * 3;
save_entry = llList2String(leaderboardData, base) + ":" +
(string)llList2Integer(leaderboardData, base + 1) + ":" +
(string)llList2Integer(leaderboardData, base + 2);
if (serialized != "") serialized += "|";
serialized += save_entry;
}
@ -206,22 +198,20 @@ integer saveLeaderboardData() {
pendingSerialize = serialized;
kvpWriteReq = llUpdateKeyValue("Peril_LB_Top50", serialized, FALSE, "");
dbg("📈 [Scoreboard Manager] 💾 Synced " + (string)saveCount + " players to global scoreboard database.");
dbg("LB Saving");
return 0;
}
// Handle game won - update leaderboard
integer handleGameWon(string winnerName) {
integer idx = llListFindList(leaderboardNames, [winnerName]);
integer idx = llListFindList(leaderboardData, [winnerName]);
if (idx == -1) {
// New player
leaderboardNames += [winnerName];
leaderboardWins += [1];
leaderboardLosses += [0];
leaderboardData += [winnerName, 1, 0];
} else {
// Existing player
integer currentWins = llList2Integer(leaderboardWins, idx);
leaderboardWins = llListReplaceList(leaderboardWins, [currentWins + 1], idx, idx);
integer currentWins = llList2Integer(leaderboardData, idx + 1);
leaderboardData = llListReplaceList(leaderboardData, [currentWins + 1], idx + 1, idx + 1);
}
saveLeaderboardData();
@ -231,16 +221,14 @@ integer handleGameWon(string winnerName) {
// Handle game lost - update leaderboard
integer handleGameLost(string loserName) {
integer lostIdx = llListFindList(leaderboardNames, [loserName]);
integer lostIdx = llListFindList(leaderboardData, [loserName]);
if (lostIdx == -1) {
// New player
leaderboardNames += [loserName];
leaderboardWins += [0];
leaderboardLosses += [1];
leaderboardData += [loserName, 0, 1];
} else {
// Existing player
integer currentLosses = llList2Integer(leaderboardLosses, lostIdx);
leaderboardLosses = llListReplaceList(leaderboardLosses, [currentLosses + 1], lostIdx, lostIdx);
integer currentLosses = llList2Integer(leaderboardData, lostIdx + 2);
leaderboardData = llListReplaceList(leaderboardData, [currentLosses + 1], lostIdx + 2, lostIdx + 2);
}
saveLeaderboardData();
@ -248,159 +236,112 @@ integer handleGameLost(string loserName) {
return 0;
}
// Get sorted leaderboard data
list getSortedLeaderboard() {
list sortedLbData = [];
integer sortI;
string sortName;
integer sortWins;
integer sortLosses;
string paddedWins;
string paddedLosses;
integer invertedLosses;
string sortKey;
string sorted_entry;
list parts;
list cleanedData = [];
string winsStr;
string lossesStr;
list sortList = [];
integer i = 0;
integer totalEntries = llGetListLength(leaderboardData) / 3;
integer base = 0;
integer wins = 0;
integer losses = 0;
integer sortKey = 0;
list sortedData = [];
integer origIdx = 0;
// Create combined data for sorting with padded wins and losses for proper multi-level sorting
for (sortI = 0; sortI < llGetListLength(leaderboardNames); sortI++) {
sortName = llList2String(leaderboardNames, sortI);
sortWins = llList2Integer(leaderboardWins, sortI);
sortLosses = llList2Integer(leaderboardLosses, sortI);
for (i = 0; i < totalEntries; i++) {
base = i * 3;
wins = llList2Integer(leaderboardData, base + 1);
losses = llList2Integer(leaderboardData, base + 2);
// Pad wins to 4 digits (higher wins = better rank)
paddedWins = (string)(10000 + sortWins);
paddedWins = llGetSubString(paddedWins, 1, -1); // Remove the "1" prefix
if (wins > 65535) wins = 65535;
if (losses > 65535) losses = 65535;
// Pad losses to 4 digits, but INVERTED for tiebreaker (lower losses = better rank)
// Use 9999 - losses so lower losses get higher sort value
invertedLosses = 9999 - sortLosses;
paddedLosses = (string)(10000 + invertedLosses);
paddedLosses = llGetSubString(paddedLosses, 1, -1); // Remove the "1" prefix
// Format: "PaddedWins-PaddedInvertedLosses:Name:ActualWins:ActualLosses"
// This sorts first by wins (descending), then by losses (ascending) for tiebreaker
sortKey = paddedWins + "-" + paddedLosses;
sorted_entry = sortKey + ":" + sortName + ":" + (string)sortWins + ":" + (string)sortLosses;
sortedLbData += [sorted_entry];
sortKey = (wins << 16) | (65535 - losses);
sortList += [sortKey, i];
}
// Sort by combined key (wins descending, losses ascending) - FALSE = descending order
sortedLbData = llListSort(sortedLbData, 1, FALSE);
// 2. Sort by key (stride of 2) in descending order (highest score first)
sortList = llListSort(sortList, 2, FALSE);
// Clean up the format back to "Name:Wins:Losses" (skip padded wins in position 0)
cleanedData = [];
for (sortI = 0; sortI < llGetListLength(sortedLbData); sortI++) {
sorted_entry = llList2String(sortedLbData, sortI);
parts = llParseString2List(sorted_entry, [":"], []);
if (llGetListLength(parts) >= 4) {
// parts[0] = paddedWins (skip), parts[1] = name, parts[2] = actualWins, parts[3] = losses
sortName = llList2String(parts, 1);
winsStr = llList2String(parts, 2);
lossesStr = llList2String(parts, 3);
cleanedData += [sortName + ":" + winsStr + ":" + lossesStr];
}
// 3. Rebuild the leaderboard data in sorted order (Stided list: Name, Wins, Losses)
for (i = 0; i < llGetListLength(sortList); i += 2) {
origIdx = llList2Integer(sortList, i + 1);
base = origIdx * 3;
sortedData += [
llList2String(leaderboardData, base),
llList2Integer(leaderboardData, base + 1),
llList2Integer(leaderboardData, base + 2)
];
}
return cleanedData;
return sortedData;
}
// Generate leaderboard text and send to separate leaderboard object
integer generateLeaderboardText() {
list sortedData = getSortedLeaderboard();
string SPACES = " ";
string title = "TOP BATTLE RECORDS";
integer titleMargin = 0;
string leaderboardText = "";
integer genI = 0;
string playerName = "";
integer genWins = 0;
integer genLosses = 0;
string rank = "";
string leftPart = "";
string rightPart = "";
integer spaceNeeded = 0;
integer numSorted = llGetListLength(sortedData) / 3;
integer base = 0;
string sWins = "";
string sLosses = "";
// 1. Build Title Row (Centered)
string title = "TOP BATTLE RECORDS";
integer titleLen = llStringLength(title);
integer titleMargin = (LEADERBOARD_WIDTH - titleLen) / 2;
titleMargin = (LEADERBOARD_WIDTH - llStringLength(title)) / 2;
if (titleMargin < 0) titleMargin = 0;
string titleSpaces = "";
integer ts;
for (ts = 0; ts < titleMargin; ts++) titleSpaces += " ";
string leaderboardText = titleSpaces + title + "\n";
integer genI;
string playerData;
list playerParts;
string playerName;
string genWins;
string genLosses;
integer rankNumber;
string rank;
string leftPart;
string rightPart;
integer spaceNeeded;
string spacer;
integer s;
string line;
integer actualPlayers;
leaderboardText = llGetSubString(SPACES, 0, titleMargin - 1) + title + "\n";
// Add actual player data (up to MAX_LEADERBOARD_ENTRIES)
for (genI = 0; genI < llGetListLength(sortedData) && genI < MAX_LEADERBOARD_ENTRIES; genI++) {
playerData = llList2String(sortedData, genI);
playerParts = llParseString2List(playerData, [":"], []);
for (genI = 0; genI < numSorted && genI < MAX_LEADERBOARD_ENTRIES; genI++) {
base = genI * 3;
playerName = llList2String(sortedData, base);
genWins = llList2Integer(sortedData, base + 1);
genLosses = llList2Integer(sortedData, base + 2);
if (llGetListLength(playerParts) >= 3) {
playerName = llList2String(playerParts, 0);
genWins = llList2String(playerParts, 1);
genLosses = llList2String(playerParts, 2);
// [Rank(4) + Name(14) + Gaps + Stats(10) = 32 chars total]
if (llStringLength(playerName) > 12) {
playerName = llGetSubString(playerName, 0, 9) + "...";
}
// Format rank string: "01. " through "10. " (always 4 chars)
rankNumber = genI + 1;
rank = (string)rankNumber + ". ";
if (rankNumber < 10) rank = "0" + rank;
// Format stats with fixed-width padding to align columns
// Standardizing to 10 characters: "W: 99/L: 9"
string sWins = (string)genWins;
while (llStringLength(sWins) < 3) sWins = " " + sWins;
string sLosses = (string)genLosses;
while (llStringLength(sLosses) < 2) sLosses = " " + sLosses;
leftPart = rank + playerName;
rightPart = "W:" + sWins + "/L:" + sLosses; // Exactly 10 characters
// Push rightPart to column 32
spaceNeeded = LEADERBOARD_WIDTH - llStringLength(leftPart) - llStringLength(rightPart);
if (spaceNeeded < 1) spaceNeeded = 1;
spacer = "";
for (s = 0; s < spaceNeeded; s++) spacer += " ";
line = leftPart + spacer + rightPart;
leaderboardText += line + "\n";
if (llStringLength(playerName) > 12) {
playerName = llGetSubString(playerName, 0, 9) + "...";
}
// Format rank: "01. " through "10. "
rank = (string)(genI + 1) + ". ";
if (genI < 9) rank = "0" + rank;
// Format stats 10-char width: "W: 99/L: 9"
sWins = (string)genWins;
if (llStringLength(sWins) < 3) sWins = llGetSubString(" ", 0, 2 - llStringLength(sWins)) + sWins;
sLosses = (string)genLosses;
if (llStringLength(sLosses) < 2) sLosses = llGetSubString(" ", 0, 1 - llStringLength(sLosses)) + sLosses;
leftPart = rank + playerName;
rightPart = "W:" + sWins + "/L:" + sLosses;
spaceNeeded = LEADERBOARD_WIDTH - llStringLength(leftPart) - 10;
if (spaceNeeded < 1) spaceNeeded = 1;
leaderboardText += leftPart + llGetSubString(SPACES, 0, spaceNeeded - 1) + rightPart + "\n";
}
// Fill remaining positions with placeholders
actualPlayers = llGetListLength(sortedData);
for (genI = actualPlayers; genI < MAX_LEADERBOARD_ENTRIES; genI++) {
rankNumber = genI + 1;
rank = (string)rankNumber + ". ";
if (rankNumber < 10) rank = "0" + rank;
for (genI = numSorted; genI < MAX_LEADERBOARD_ENTRIES; genI++) {
rank = (string)(genI + 1) + ". ";
if (genI < 9) rank = "0" + rank;
// Use EXACT same formatting as players so columns align!
leftPart = rank + "----------";
rightPart = "W: 0/L: 0"; // Matches 10-char fixed width
rightPart = "W: 0/L: 0";
spaceNeeded = LEADERBOARD_WIDTH - llStringLength(leftPart) - 10;
spaceNeeded = LEADERBOARD_WIDTH - llStringLength(leftPart) - llStringLength(rightPart);
if (spaceNeeded < 1) spaceNeeded = 1;
spacer = "";
for (s = 0; s < spaceNeeded; s++) spacer += " ";
line = leftPart + spacer + rightPart;
leaderboardText += line + "\n";
leaderboardText += leftPart + llGetSubString(SPACES, 0, spaceNeeded - 1) + rightPart + "\n";
}
// Send formatted text to leaderboard bridge
@ -416,9 +357,7 @@ integer resetLeaderboard() {
return 0;
}
leaderboardNames = [];
leaderboardWins = [];
leaderboardLosses = [];
leaderboardData = [];
pendingSerialize = "";
kvpWriteReq = llUpdateKeyValue("Peril_LB_Top50", "", FALSE, "");
generateLeaderboardText(); // Send empty leaderboard immediately
@ -427,10 +366,7 @@ integer resetLeaderboard() {
// Clear all current players from the scoreboard
integer clearAllPlayers() {
// Clear ONLY current game player data lists (NOT leaderboard data)
playerNames = [];
playerLives = [];
playerProfiles = [];
activePlayers = [];
// Clear any pending HTTP requests
httpRequests = [];
@ -444,9 +380,9 @@ integer clearAllPlayers() {
// Reset all player prims to default state
for (clearI = 0; clearI < 10; clearI++) {
clearProfilePrimIndex = llList2Integer(profileLinks, clearI);
clearHeartsPrimIndex = llList2Integer(heartsLinks, clearI);
clearOverlayPrimIndex = llList2Integer(overlayLinks, clearI);
clearProfilePrimIndex = getProfilePrimLink(clearI);
clearHeartsPrimIndex = getHeartsPrimLink(clearI);
clearOverlayPrimIndex = getOverlayPrimLink(clearI);
// SAFETY CHECK - use if block instead of continue for maximum compatibility
if (clearProfilePrimIndex > 0) {
@ -479,24 +415,17 @@ integer clearAllPlayers() {
// Remove a single player from the scoreboard display
integer removePlayer(string playerName) {
integer removeIdx = llListFindList(playerNames, [playerName]);
if (removeIdx != -1) {
// Remove player from internal lists
playerNames = llDeleteSubList(playerNames, removeIdx, removeIdx);
playerLives = llDeleteSubList(playerLives, removeIdx, removeIdx);
playerProfiles = llDeleteSubList(playerProfiles, removeIdx, removeIdx);
// Refresh the entire display to shift remaining players up
integer idx = llListFindList(activePlayers, [playerName]);
if (idx != -1) {
activePlayers = llDeleteSubList(activePlayers, idx, idx + 2);
refreshPlayerDisplay();
dbg("📈 [Scoreboard Manager] 📊 Removed " + playerName + " from the scoreboard and shifted remaining players.");
dbg("📈 Player removed: " + playerName);
}
return 0;
}
// Refresh the entire player display - shows all current players in order
integer refreshPlayerDisplay() {
integer numActivePlayers = llGetListLength(playerNames);
integer numActivePlayers = llGetListLength(activePlayers) / 3;
integer refreshI;
integer refreshProfilePrimIndex = 0;
integer refreshHeartsPrimIndex = 0;
@ -512,9 +441,9 @@ integer refreshPlayerDisplay() {
// Only reset prims for unused slots (avoid flickering for active players)
for (refreshI = numActivePlayers; refreshI < 10; refreshI++) {
refreshProfilePrimIndex = llList2Integer(profileLinks, refreshI);
refreshHeartsPrimIndex = llList2Integer(heartsLinks, refreshI);
refreshOverlayPrimIndex = llList2Integer(overlayLinks, refreshI);
refreshProfilePrimIndex = getProfilePrimLink(refreshI);
refreshHeartsPrimIndex = getHeartsPrimLink(refreshI);
refreshOverlayPrimIndex = getOverlayPrimLink(refreshI);
if (refreshProfilePrimIndex > 0) {
// Reset to default only for unused slots
@ -541,14 +470,15 @@ integer refreshPlayerDisplay() {
// Now display all current players in their new positions
for (refreshI = 0; refreshI < numActivePlayers; refreshI++) {
r_name = llList2String(playerNames, refreshI);
r_lives = llList2Integer(playerLives, refreshI);
r_profileTexture = llList2String(playerProfiles, refreshI);
integer base = refreshI * 3;
r_name = llList2String(activePlayers, base);
r_lives = llList2Integer(activePlayers, base + 1);
r_profileTexture = llList2String(activePlayers, base + 2);
// Calculate prim indices for this position
r_profileIdx = llList2Integer(profileLinks, refreshI);
r_heartsIdx = llList2Integer(heartsLinks, refreshI);
r_overlayIdx = llList2Integer(overlayLinks, refreshI);
r_profileIdx = getProfilePrimLink(refreshI);
r_heartsIdx = getHeartsPrimLink(refreshI);
r_overlayIdx = getOverlayPrimLink(refreshI);
if (r_profileIdx > 0) {
// Determine profile texture to use
@ -602,9 +532,9 @@ integer isBot(string playerName) {
// Map player index to profile prim link number
// Functions relocated to avoid hardcoded math
integer getProfilePrimLink(integer playerIndex) { return llList2Integer(profileLinks, playerIndex); }
integer getHeartsPrimLink(integer playerIndex) { return llList2Integer(heartsLinks, playerIndex); }
integer getOverlayPrimLink(integer playerIndex) { return llList2Integer(overlayLinks, playerIndex); }
integer getProfilePrimLink(integer playerIndex) { return llList2Integer(gridLinks, playerIndex * 3); }
integer getHeartsPrimLink(integer playerIndex) { return llList2Integer(gridLinks, (playerIndex * 3) + 1); }
integer getOverlayPrimLink(integer playerIndex) { return llList2Integer(gridLinks, (playerIndex * 3) + 2); }
string getHeartTexture(integer lives) {
if (lives <= 0) return TEXTURE_0_HEARTS;
@ -613,84 +543,37 @@ string getHeartTexture(integer lives) {
else return TEXTURE_3_HEARTS; // 3 or more
}
// Helper to set glow and color on a prim
setGlow(integer link, float glow, vector color) {
if (link > 0) {
llSetLinkPrimitiveParamsFast(link, [PRIM_GLOW, ALL_SIDES, glow, PRIM_COLOR, ALL_SIDES, color, 1.0]);
}
}
// Update all glow effects (peril and winner)
integer updatePlayerGlowEffects() {
// First, remove glow and reset tint from all players
integer numPlayers = llGetListLength(activePlayers) / 3;
integer glowI;
integer glowProfilePrimIndex;
integer glowHeartsPrimIndex;
for (glowI = 0; glowI < llGetListLength(playerNames); glowI++) {
glowProfilePrimIndex = llList2Integer(profileLinks, glowI);
glowHeartsPrimIndex = llList2Integer(heartsLinks, glowI);
if (glowProfilePrimIndex > 0) {
llSetLinkPrimitiveParamsFast(glowProfilePrimIndex, [
PRIM_GLOW, ALL_SIDES, 0.0,
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0
]);
}
if (glowHeartsPrimIndex > 0) {
llSetLinkPrimitiveParamsFast(glowHeartsPrimIndex, [
PRIM_GLOW, ALL_SIDES, 0.0,
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0
]);
}
for (glowI = 0; glowI < numPlayers; glowI++) {
setGlow(getProfilePrimLink(glowI), 0.0, <1,1,1>);
setGlow(getHeartsPrimLink(glowI), 0.0, <1,1,1>);
}
// Priority 1: Green glow for winner (overrides peril glow)
integer idx = -1;
vector col = <1,1,1>;
float glow = 0.0;
if (currentWinner != "" && currentWinner != "NONE") {
integer winnerIndex = llListFindList(playerNames, [currentWinner]);
if (winnerIndex != -1) {
integer winnerProfilePrimIndex = getProfilePrimLink(winnerIndex);
integer winnerHeartsPrimIndex = getHeartsPrimLink(winnerIndex);
// Check if the prim exists (discovered index > 0)
if (winnerProfilePrimIndex > 0) {
// Add green glow + green tint to winner
llSetLinkPrimitiveParamsFast(winnerProfilePrimIndex, [
PRIM_GLOW, ALL_SIDES, 0.3,
PRIM_COLOR, ALL_SIDES, <0.0, 1.0, 0.0>, 1.0
]);
// Also add glow to hearts if found
if (winnerHeartsPrimIndex > 0) {
llSetLinkPrimitiveParamsFast(winnerHeartsPrimIndex, [
PRIM_GLOW, ALL_SIDES, 0.3,
PRIM_COLOR, ALL_SIDES, <0.0, 1.0, 0.0>, 1.0
]);
}
dbg("📈 [Scoreboard Manager] 🏆 Added green glow to winner: " + currentWinner);
}
}
idx = llListFindList(activePlayers, [currentWinner]);
if (idx != -1) { idx = idx / 3; col = <0,1,0>; glow = 0.3; }
} else if (currentPerilPlayer != "" && currentPerilPlayer != "NONE") {
idx = llListFindList(activePlayers, [currentPerilPlayer]);
if (idx != -1) { idx = idx / 3; col = <1,1,0>; glow = 0.2; }
}
// Priority 2: Yellow glow for peril player (only if not the winner)
else if (currentPerilPlayer != "" && currentPerilPlayer != "NONE") {
integer perilIndex = llListFindList(playerNames, [currentPerilPlayer]);
if (perilIndex != -1) {
integer perilProfilePrimIndex = getProfilePrimLink(perilIndex);
integer perilHeartsPrimIndex = getHeartsPrimLink(perilIndex);
// Check if the prim exists (discovered index > 0)
if (perilProfilePrimIndex > 0) {
// Add yellow glow + yellow tint to peril player
llSetLinkPrimitiveParamsFast(perilProfilePrimIndex, [
PRIM_GLOW, ALL_SIDES, 0.2,
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 0.0>, 1.0
]);
// Also add glow to hearts if found
if (perilHeartsPrimIndex > 0) {
llSetLinkPrimitiveParamsFast(perilHeartsPrimIndex, [
PRIM_GLOW, ALL_SIDES, 0.2,
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 0.0>, 1.0
]);
}
dbg("📈 [Scoreboard Manager] ✨ Added yellow glow to peril player: " + currentPerilPlayer);
}
}
if (idx != -1) {
setGlow(getProfilePrimLink(idx), glow, col);
setGlow(getHeartsPrimLink(idx), glow, col);
}
return 0;
}
@ -703,26 +586,19 @@ integer updatePerilPlayerGlow() {
// Update player display on the grid
integer updatePlayerDisplay(string playerName, integer lives, string profileUUID) {
// Find existing player or add new one
integer playerIndex = llListFindList(playerNames, [playerName]);
integer playerIndex = llListFindList(activePlayers, [playerName]);
if (playerIndex != -1) playerIndex = playerIndex / 3;
if (playerIndex == -1) {
// New player - find next available slot
playerIndex = llGetListLength(playerNames);
playerIndex = llGetListLength(activePlayers) / 3;
if (playerIndex >= 10) {
dbg("📈 [Scoreboard Manager] ⚠️ WARNING: Maximum 10 players supported, ignoring: " + playerName);
dbg("📈 MAX players reached: " + playerName);
return 0;
}
// Add to tracking lists
playerNames += [playerName];
playerLives += [lives];
// For bots, always store the bot texture UUID to prevent HTTP requests
if (isBot(playerName)) {
playerProfiles += [TEXTURE_BOT_PROFILE];
} else {
playerProfiles += [profileUUID];
}
string activeUUID = profileUUID;
if (isBot(playerName)) activeUUID = TEXTURE_BOT_PROFILE;
activePlayers += [playerName, lives, activeUUID];
// DEBUG: Show prim mapping - DISABLED to save memory
// if (VERBOSE_LOGGING) {
@ -739,15 +615,13 @@ integer updatePlayerDisplay(string playerName, integer lives, string profileUUID
// }
} else {
// Update existing player
playerLives = llListReplaceList(playerLives, [lives], playerIndex, playerIndex);
// Don't overwrite cached profile texture with original UUID on updates
activePlayers = llListReplaceList(activePlayers, [lives], (playerIndex * 3) + 1, (playerIndex * 3) + 1);
}
// Update the physical prims for this player
integer updateProfilePrimIndex = llList2Integer(profileLinks, playerIndex);
integer updateHeartsPrimIndex = llList2Integer(heartsLinks, playerIndex);
integer updateOverlayPrimIndex = llList2Integer(overlayLinks, playerIndex);
integer updateProfilePrimIndex = getProfilePrimLink(playerIndex);
integer updateHeartsPrimIndex = getHeartsPrimLink(playerIndex);
integer updateOverlayPrimIndex = getOverlayPrimLink(playerIndex);
if (updateProfilePrimIndex <= 0) return 0;
@ -761,32 +635,26 @@ integer updatePlayerDisplay(string playerName, integer lives, string profileUUID
// CRITICAL: Still need to set updateProfileTexture for eliminated players
if (isBot(playerName)) {
updateProfileTexture = TEXTURE_BOT_PROFILE;
} else if (playerIndex < llGetListLength(playerProfiles)) {
string updateCachedTexture = llList2String(playerProfiles, playerIndex);
} else {
string updateCachedTexture = llList2String(activePlayers, (playerIndex * 3) + 2);
if (updateCachedTexture != "" && updateCachedTexture != profileUUID &&
updateCachedTexture != TEXTURE_DEFAULT_PROFILE && updateCachedTexture != TEXTURE_BOT_PROFILE &&
updateCachedTexture != TEXTURE_ELIMINATED_X) {
// We have a previously fetched profile picture, use it
updateProfileTexture = updateCachedTexture;
} else {
updateProfileTexture = TEXTURE_DEFAULT_PROFILE;
}
} else {
updateProfileTexture = TEXTURE_DEFAULT_PROFILE;
}
} else {
// Player is alive - determine profile texture to use
updateProfileTexture = profileUUID;
// Check if we already have a cached profile texture for this player
if (playerIndex < llGetListLength(playerProfiles)) {
string liveCachedTexture = llList2String(playerProfiles, playerIndex);
if (liveCachedTexture != "" && liveCachedTexture != profileUUID &&
liveCachedTexture != TEXTURE_DEFAULT_PROFILE && liveCachedTexture != TEXTURE_BOT_PROFILE &&
liveCachedTexture != TEXTURE_ELIMINATED_X) {
// We have a previously fetched profile picture, use it
updateProfileTexture = liveCachedTexture;
}
string liveCachedTexture = llList2String(activePlayers, (playerIndex * 3) + 2);
if (liveCachedTexture != "" && liveCachedTexture != profileUUID &&
liveCachedTexture != TEXTURE_DEFAULT_PROFILE && liveCachedTexture != TEXTURE_BOT_PROFILE &&
liveCachedTexture != TEXTURE_ELIMINATED_X) {
updateProfileTexture = liveCachedTexture;
}
// If no cached texture or using original UUID, determine what to use
@ -802,7 +670,7 @@ integer updatePlayerDisplay(string playerName, integer lives, string profileUUID
// }
} else if (updateProfileTexture == "" || updateProfileTexture == "00000000-0000-0000-0000-000000000000") {
updateProfileTexture = TEXTURE_DEFAULT_PROFILE;
dbg("📈 [Scoreboard Manager] ❓ [DEBUG] " + playerName + " has empty/null UUID, using default texture");
dbg("Default texture");
} else {
// CRITICAL: Set default texture FIRST, then request profile picture
updateProfileTexture = TEXTURE_DEFAULT_PROFILE;
@ -817,10 +685,6 @@ integer updatePlayerDisplay(string playerName, integer lives, string profileUUID
// Store the HTTP request mapping: requestID -> playerIndex
httpRequests += [httpRequestID, playerIndex];
// if (VERBOSE_LOGGING) {
// llOwnerSay("🔄 [Scoreboard] Requesting profile pic for " + playerName + ", showing default for now");
// }
}
}
}
@ -891,12 +755,8 @@ default {
state_entry() {
discoverLinks();
llListen(1, "", llGetOwner(), "");
reportMemoryUsage("Scoreboard Manager");
dbg("📈 [Scoreboard Manager] 📈 Managing profile and heart prims via dynamic discovery.");
// Initialize profile picture extraction constants
profile_key_prefix_length = llStringLength(profile_key_prefix);
profile_img_prefix_length = llStringLength(profile_img_prefix);
REPORT_MEMORY();
dbg("Ready");
// Reset background prim to proper state
resetBackgroundPrim();
@ -916,8 +776,7 @@ default {
currentWinner = "";
updatePlayerGlowEffects();
dbg("📈 [Scoreboard Manager] ✅ Linkset communication active - no discovery needed!");
dbg("📈 [Scoreboard Manager] 📊 Loaded " + (string)llGetListLength(leaderboardNames) + " players from leaderboard data");
dbg("Init complete");
}
link_message(integer sender, integer num, string str, key id) {
@ -935,7 +794,7 @@ default {
string profileUUID = llList2String(parts, 2);
// Debug: Show what data scoreboard received - DISABLED to save memory
dbg("📈 Scoreboard received update: " + playerName + " has " + (string)lives + " hearts");
dbg("Update: " + playerName);
// }
updatePlayerDisplay(playerName, lives, profileUUID);
@ -975,7 +834,7 @@ default {
currentPerilPlayer = "";
}
dbg("📈 [Scoreboard Manager] 🎯 Peril player changed from '" + oldPeril + "' to '" + currentPerilPlayer + "'");
dbg("Peril: " + currentPerilPlayer);
// Update glow effects
updatePlayerGlowEffects();
@ -988,7 +847,7 @@ default {
currentWinner = "";
}
dbg("📈 [Scoreboard Manager] 🏆 Winner changed from '" + oldWinner + "' to '" + currentWinner + "'");
dbg("Winner: " + currentWinner);
// Update glow effects - winner glow overrides peril glow
updatePlayerGlowEffects();
@ -1004,14 +863,14 @@ default {
// Remove this request from tracking
httpRequests = llDeleteSubList(httpRequests, requestIndex, requestIndex + 1);
if (status == 200 && playerIndex < llGetListLength(playerNames)) {
if (status == 200 && playerIndex < llGetListLength(activePlayers) / 3) {
// Parse the HTML to extract profile image UUID
string profileUUID = "";
// Try the meta tag method first
integer metaStart = llSubStringIndex(body, profile_key_prefix);
integer metaStart = llSubStringIndex(body, PROFILE_KEY_PREFIX);
if (metaStart != -1) {
metaStart += profile_key_prefix_length;
metaStart += PROFILE_KEY_PREFIX_LENGTH;
string remainingBody = llGetSubString(body, metaStart, -1);
integer metaEnd = llSubStringIndex(remainingBody, "\"");
if (metaEnd != -1) {
@ -1021,9 +880,9 @@ default {
// If meta tag failed, try img src method
if (profileUUID == "") {
integer imgStart = llSubStringIndex(body, profile_img_prefix);
integer imgStart = llSubStringIndex(body, PROFILE_IMG_PREFIX);
if (imgStart != -1) {
imgStart += profile_img_prefix_length;
imgStart += PROFILE_IMG_PREFIX_LENGTH;
string remainingBody = llGetSubString(body, imgStart, -1);
integer imgEnd = llSubStringIndex(remainingBody, "/");
if (imgEnd != -1) {
@ -1034,8 +893,8 @@ default {
// If we got a valid UUID, update the profile prim and cache it
if (profileUUID != "" && profileUUID != "00000000-0000-0000-0000-000000000000") {
// Update the cached profile texture
playerProfiles = llListReplaceList(playerProfiles, [profileUUID], playerIndex, playerIndex);
// Update the cached profile texture in stride
activePlayers = llListReplaceList(activePlayers, [profileUUID], (playerIndex * 3) + 2, (playerIndex * 3) + 2);
// Update the physical prim using correct mapping
integer profilePrimIndex = getProfilePrimLink(playerIndex);
@ -1055,10 +914,10 @@ default {
PRIM_GLOW, ALL_SIDES, 0.0 // Remove glow
]);
string playerName = llList2String(playerNames, playerIndex);
dbg("📈 [Scoreboard Manager] 🖼️ Updated profile picture for " + playerName + " on prim " + (string)profilePrimIndex + " with forced refresh");
string playerName = llList2String(activePlayers, playerIndex * 3);
dbg("Profile updated: " + playerName);
} else {
dbg("📈 [Scoreboard Manager] ❌ ERROR: HTTP response tried to update invalid prim " + (string)profilePrimIndex + " for player " + (string)playerIndex);
dbg("Invalid prim: " + (string)profilePrimIndex);
}
}
}
@ -1074,9 +933,7 @@ default {
string val = llGetSubString(data, comma + 1, -1);
if (status == "1") {
leaderboardNames = [];
leaderboardWins = [];
leaderboardLosses = [];
leaderboardData = [];
list entries = llParseString2List(val, ["|"], []);
integer i;
@ -1084,19 +941,19 @@ default {
string data_entry = llList2String(entries, i);
list parts = llParseString2List(data_entry, [":"], []);
if (llGetListLength(parts) >= 3) {
leaderboardNames += [llList2String(parts, 0)];
leaderboardWins += [(integer)llList2String(parts, 1)];
leaderboardLosses += [(integer)llList2String(parts, 2)];
leaderboardData += [
llList2String(parts, 0),
(integer)llList2String(parts, 1),
(integer)llList2String(parts, 2)
];
}
}
dbg("📈 [Scoreboard Manager] ✅ Loaded global scoreboard containing " + (string)llGetListLength(leaderboardNames) + " players!");
} else if (status == "3") { // XP_ERROR_KEY_NOT_FOUND
leaderboardNames = [];
leaderboardWins = [];
leaderboardLosses = [];
dbg("📈 [Scoreboard Manager] ℹ️ Global scoreboard is currently empty (database fresh).");
dbg("LB Loaded");
} else if (status == "3") {
leaderboardData = [];
dbg("LB Empty");
} else {
dbg("📈 [Scoreboard Manager] ⚠️ Failed to load global scoreboard (Status: " + status + ")");
dbg("LB Error: " + status);
}
}
// Generate board with downloaded or empty data
@ -1108,8 +965,8 @@ default {
if (status == "3") {
// Update failed because key didn't exist yet! Create it.
llCreateKeyValue("Peril_LB_Top50", pendingSerialize);
} else if (status == "1") {
dbg("📈 [Scoreboard Manager] ✅ Global scoreboard synced successfully.");
dbg("LB Saved");
pendingSerialize = "";
}
}
}
@ -1117,7 +974,7 @@ default {
timer() {
dbg("📈 [Scoreboard Manager] ⏱️ Auto-refreshing Global Scoreboard Data...");
dbg("Timer");
loadLeaderboardData();
}
@ -1138,7 +995,6 @@ default {
}
}
else if (msg == "clearx") {
dbg("Refreshing display to clear test X overlays...");
refreshPlayerDisplay();
}
}

View file

@ -299,7 +299,7 @@ resetGame() {
default {
state_entry() {
DISCOVER_CORE_LINKS();
reportMemoryUsage("Main Controller");
REPORT_MEMORY();
dbg("🎮 Main Controller ready! (Discovery Mode)");
// Initialize lockout system
@ -317,7 +317,7 @@ default {
on_rez(integer start_param) {
DISCOVER_CORE_LINKS();
reportMemoryUsage("Main Controller");
REPORT_MEMORY();
dbg("🔄 Main Controller reset via rez...");
// Re-initialize lockout system
@ -473,14 +473,6 @@ default {
link_message(integer sender, integer num, string str, key id) {
// Dialog forwarding now handled by Player_RegistrationManager
// Handle player registration from other scripts (NOT delegated, this is for other internal communication)
if (num == MSG_REGISTER_PLAYER) {
// This is for other scripts that might still use the old MSG_REGISTER_PLAYER message
// Convert it to the new message for the Player_RegistrationManager
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER_REQUEST, str, id);
return;
}
// Handle registration updates from Player_RegistrationManager
if (num == MSG_UPDATE_MAIN_LISTS) { // MSG_UPDATE_MAIN_LISTS
// ULTRA-OPTIMIZED: Player_RegistrationManager now handles all heavy processing

View file

@ -56,18 +56,6 @@ list globallyPickedNumbers = []; // Numbers already picked by other players
integer currentPage = 0;
#define NUMBERS_PER_PAGE 9 // Leave room for navigation buttons
// 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)");
}
list getNumbersForPage(integer page, integer diceType) {
// Build list of available numbers (not picked by others OR by current player)
@ -177,7 +165,7 @@ showPickDialog(string name, key id, integer diceType, integer picks) {
default {
state_entry() {
reportMemoryUsage("🎮 NumberPicker Dialog");
REPORT_MEMORY();
// Initialize dynamic channels
initializeChannels();
@ -203,7 +191,7 @@ default {
}
on_rez(integer start_param) {
reportMemoryUsage("🎮 NumberPicker Dialog");
REPORT_MEMORY();
dbg("🎮 [NumberPicker] 🔄 Number Picker rezzed - reinitializing...");

View file

@ -28,7 +28,7 @@ integer LINK_LEADERBOARD_BRIDGE = -1; // "FURWARE text mesh:Leaderboard:0:0"
#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
#define DEBUG_LOGS 0 // GLOBAL MASTER SWITCH - Set to 1 for dev feedback, 0 to STRIP from memory
#if DEBUG_LOGS
#define dbg(msg) llOwnerSay(msg)
#else
@ -113,6 +113,17 @@ integer LINK_LEADERBOARD_BRIDGE = -1; // "FURWARE text mesh:Leaderboard:0:0"
#define MSG_QUERY_OWNER_STATUS 213
// --- Memory Management (6000-6099) ---
#define SHOW_MEMORY 1 // Toggle this to 1 to see memory usage on rez/reset
#define REPORT_MEMORY() { \
if (SHOW_MEMORY) { \
integer used = llGetUsedMemory(); \
integer free = llGetFreeMemory(); \
float percent = ((float)used / (float)(used + free)) * 100.0; \
llOwnerSay("🧠 [" + llGetScriptName() + "] Memory: " + (string)used + " used, " + (string)free + " free (" + llGetSubString((string)percent, 0, 4) + "% used)."); \
} \
}
#define MSG_MEMORY_CHECK 6001
#define MSG_MEMORY_STATS 6002
#define MSG_MEMORY_CLEANUP 6003

View file

@ -7,31 +7,18 @@ string displayText;
string myName;
integer listenHandle = -1;
// 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;
dbg("🧠 [" + scriptName + "] Memory: " +
(string)used + " used, " +
(string)free + " free (" +
llGetSubString((string)percentUsed, 0, 4) + "% used)");
}
default {
state_entry() {
REPORT_MEMORY();
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) {
REPORT_MEMORY();
if (listenHandle != -1) llListenRemove(listenHandle);
listenHandle = llListen(start_param, "", NULL_KEY, "");
myName = "";
@ -83,7 +70,6 @@ default {
else if (llSubStringIndex(message, "SET_NAME:") == 0) {
myName = llGetSubString(message, 9, -1);
llSetObjectDesc(myName);
reportMemoryUsage("📱 " + myName);
}
}

View file

@ -99,18 +99,6 @@ float pendingMenuTimestamp = 0.0; // Timeout for pending requests
integer isLocked = FALSE;
key gameOwner;
// 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)");
}
// Display the main categorized owner menu with lock/unlock controls
showOwnerMenu(key id) {
@ -313,7 +301,7 @@ showUnregisteredOwnerMenu(key id, integer canStartGame) {
default {
state_entry() {
reportMemoryUsage("🎭 Player Dialog");
REPORT_MEMORY();
// Initialize dynamic channels
initializeChannels();
@ -356,7 +344,7 @@ default {
}
on_rez(integer start_param) {
reportMemoryUsage("🎭 Player Dialog");
REPORT_MEMORY();
dbg("🔄 Player Dialog Handler rezzed - reinitializing...");

View file

@ -10,23 +10,29 @@ list names = [];
list lives = [];
list readyPlayers = [];
#define MAX_PLAYERS 10
#define FLOATER_BASE_CHANNEL -86000
// Dynamic channel system
#define CHANNEL_BASE -77000
integer FLOATER_BASE_CHANNEL;
integer roundStarted = FALSE;
integer gameStarting = FALSE;
// 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)");
integer calculateChannel(integer offset) {
string ownerStr = (string)llGetOwner();
string objectStr = (string)llGetLinkKey(1);
string combinedStr = ownerStr + objectStr;
string hashStr = llMD5String(combinedStr, 0);
integer hash1 = llSubStringIndex("0123456789abcdef", llGetSubString(hashStr, 0, 0));
integer hash2 = llSubStringIndex("0123456789abcdef", llGetSubString(hashStr, 1, 1));
integer combinedHash = hash1 * 16 + hash2;
return CHANNEL_BASE - (offset * 1000) - combinedHash;
}
initializeChannels() {
FLOATER_BASE_CHANNEL = calculateChannel(9);
}
// Helper functions
string getPlayerName(key id) {
string displayName = llGetDisplayName(id);
@ -134,12 +140,12 @@ handlePlayerRegistration(string regData, key requesterId) {
syncMessage = livesStr + "~EMPTY~NONE~" + llList2CSV(names);
}
// Send pre-built sync message
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, syncMessage, NULL_KEY);
// CRITICAL FIX: Send floater rez message to Floater Manager
// CRITICAL FIX: Send floater rez message to Floater Manager first
llMessageLinked(LINK_SET, MSG_REZ_FLOATER, newName + "|" + (string)newKey, newKey);
// Then send the sync message to update everyone on the new state
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, syncMessage, NULL_KEY);
// Send minimal update to Main Controller - just the essential data it needs
string updateData = (string)ch + "~" + newName;
llMessageLinked(LINK_SET, MSG_UPDATE_MAIN_LISTS, updateData, newKey);
@ -156,11 +162,15 @@ handlePlayerRegistration(string regData, key requesterId) {
default {
state_entry() {
DISCOVER_CORE_LINKS();
initializeChannels();
REPORT_MEMORY();
dbg("🎯 [RegMgr] Player Registration Manager ready - discovery complete! Scoreboard: " + (string)LINK_SCOREBOARD);
}
on_rez(integer start_param) {
DISCOVER_CORE_LINKS();
initializeChannels();
REPORT_MEMORY();
dbg("🎯 [RegMgr] Player Registration Manager rezzed - resetting state...");
// Reset local state
players = [];

View file

@ -60,18 +60,6 @@ integer lastRollTime = 0; // Track when last roll happened
integer diceTypeProcessed = FALSE; // Confetti particle system for winner celebrations
#define PARTICLE_TEXTURE "7d8ae121-e171-12ae-f5b6-7cc3c0395c7b"
// 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)");
}
list getPicksFor(string nameInput) {
integer i;
@ -131,9 +119,13 @@ confetti() {
default {
state_entry() {
DISCOVER_CORE_LINKS();
reportMemoryUsage("Roll Module");
REPORT_MEMORY();
dbg("🎲 [Roll Module] ready - discovery complete! Bridge: " + (string)LINK_DICE_BRIDGE);
// Re-initialize dynamic channels
initializeChannels();
rollDialogChannel = ROLLDIALOG_CHANNEL;
// Clean up any existing listeners
if (listenHandle != -1) {
llListenRemove(listenHandle);
@ -157,7 +149,7 @@ default {
on_rez(integer start_param) {
DISCOVER_CORE_LINKS();
reportMemoryUsage("Roll Module");
REPORT_MEMORY();
dbg("🎲 [Roll Module] reset via rez...");
// Re-initialize dynamic channels

View file

@ -69,9 +69,14 @@ list validatePicksData(list picksData, list names) {
default {
state_entry() {
REPORT_MEMORY();
dbg("🔍 [System Debugger] 🔍 System Debugger ready!");
}
on_rez(integer start_param) {
REPORT_MEMORY();
}
link_message(integer sender, integer num, string str, key id) {
// Handle legacy verbose logging toggle (OBSELETE)