diff --git a/CHANGELOG_Notecard.txt b/CHANGELOG_Notecard.txt index 5c351ba..7f9c2a4 100644 --- a/CHANGELOG_Notecard.txt +++ b/CHANGELOG_Notecard.txt @@ -6,6 +6,20 @@ All notable changes to Peril Dice are documented in this file. [2.8.6] - 2025-09-16 ==================== +🏆 LEADERBOARD SORTING ENHANCEMENT +- Enhanced Tie-Breaking: Fixed leaderboard sorting to properly handle players with identical win counts + * Before: Players with same wins (e.g., 5 wins each) appeared in random/inconsistent order + * After: Players with same wins are sorted by losses ascending (fewer losses = better rank) + * Example: Player A (5W/2L) now ranks higher than Player B (5W/3L) with same wins but more losses + * Fair Ranking: Ensures consistent, logical player rankings that reflect overall performance +- Multi-Level Sorting: Implemented compound sorting key with wins primary, losses secondary + * Primary Sort: Wins descending (higher wins = better rank) + * Tiebreaker Sort: Losses ascending (lower losses = better rank among tied players) + * Technical: Uses padded numeric strings for reliable multi-level sorting in LSL +- LSL Compliance: Fixed variable declaration issues to ensure proper script compilation + * Resolved: Multiple variable redeclaration errors in sorting functions + * Enhanced: Proper LSL variable scoping throughout leaderboard management code + 🏆 WINNER GLOW CELEBRATION SYSTEM - Victory Visual Enhancement: Added comprehensive winner celebration with green glow effects * Scoreboard Glow: Winner's profile picture and hearts display bright green glow with green tint diff --git a/Floater_Manager.lsl b/Floater_Manager.lsl index 0eadd60..8af47df 100644 --- a/Floater_Manager.lsl +++ b/Floater_Manager.lsl @@ -360,10 +360,13 @@ default { // Real avatars: rez next to the avatar with the standard offset pos = basePos + <1, 0, 1>; } else { - // Test players: space floats apart and away from the scoreboard/leaderboard area. - // Move them further away (3-5 meters) and spread them out more. - // This prevents them from spawning directly on the leaderboard. - pos = basePos + <-4.0 - (float)idx * 1.0, 2.0 + (float)idx * 0.8, 1>; + // Test players: arrange in a compact 2x5 grid to stay within parcel boundaries + // Grid layout: 2 rows of 5 floaters each, spaced 1.5m apart + integer row = idx / 5; // Row 0 or 1 + integer col = idx % 5; // Column 0-4 + float xOffset = -3.0 - (float)col * 1.5; // -3.0 to -9.0 + float yOffset = 2.0 + (float)row * 2.0; // 2.0 or 4.0 + pos = basePos + ; } // Use permanent channel mapping instead of alive list index integer channelIdx = llListFindList(allPlayerNames, [name]); @@ -454,7 +457,7 @@ default { string picksDisplay = llList2CSV(picks); - // Check if this player is the winner (only one player with lives > 0) + // Check if this player is the winner (only one player with lives > 0 AND game is active) integer livingPlayers = 0; integer isWinner = FALSE; for (i = 0; i < llGetListLength(lives); i++) { @@ -463,8 +466,9 @@ default { } } - // If only one player has lives > 0 and this player has lives > 0, they're the winner - if (livingPlayers == 1 && lifeCount > 0) { + // 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; } diff --git a/Game_Manager.lsl b/Game_Manager.lsl index 6c17fbc..5a76d26 100644 --- a/Game_Manager.lsl +++ b/Game_Manager.lsl @@ -510,8 +510,10 @@ default { names = newNames; // Send lightweight update to Main Controller for floating text (only if substantial change) + // Use the NEW names list count (after elimination has been processed) if (perilChanged && perilPlayer != "" && perilPlayer != "NONE") { - string lightUpdate = "PERIL_UPDATE|" + perilPlayer + "|" + (string)llGetListLength(names); + integer currentPlayerCount = llGetListLength(newNames); // Use the updated count + string lightUpdate = "PERIL_UPDATE|" + perilPlayer + "|" + (string)currentPlayerCount; llMessageLinked(LINK_SET, 9070, lightUpdate, NULL_KEY); } diff --git a/Game_Scoreboard_Manager_Linkset.lsl b/Game_Scoreboard_Manager_Linkset.lsl index 7000a7a..94cd740 100644 --- a/Game_Scoreboard_Manager_Linkset.lsl +++ b/Game_Scoreboard_Manager_Linkset.lsl @@ -39,7 +39,7 @@ integer LAST_OVERLAY_PRIM = 11; // Overlay prims end at 11 // Heart texture UUIDs - REPLACE WITH YOUR ACTUAL TEXTURE UUIDs string TEXTURE_0_HEARTS = "7d8ae121-e171-12ae-f5b6-7cc3c0395c7b"; // 0 hearts (dead) string TEXTURE_1_HEARTS = "6605d25f-8e2d-2870-eb87-77c58cd47fa9"; // 1 heart -string TEXTURE_2_HEARTS = "7ba6cb1e-f384-25a5-8e88-a90bbd7cc041"; // 2 hearts +string TEXTURE_2_HEARTS = "7ba6cb1e-f384-25a5-8e88-a90bbd7cc041"; // 2 hearts string TEXTURE_3_HEARTS = "a5d16715-4648-6526-5582-e8068293f792"; // 3 hearts // Default textures - REPLACE WITH YOUR ACTUAL TEXTURE UUIDs @@ -240,36 +240,56 @@ handleGameLost(string loserName) { list getSortedLeaderboard() { list sortedData = []; integer i; + string name; + integer wins; + integer losses; + string paddedWins; + string paddedLosses; + integer invertedLosses; + string sortKey; + string entry; + list parts; + list cleanedData = []; + string winsStr; + string lossesStr; - // Create combined data for sorting with padded wins for proper numerical sorting + // Create combined data for sorting with padded wins and losses for proper multi-level sorting for (i = 0; i < llGetListLength(leaderboardNames); i++) { - string name = llList2String(leaderboardNames, i); - integer wins = llList2Integer(leaderboardWins, i); - integer losses = llList2Integer(leaderboardLosses, i); + name = llList2String(leaderboardNames, i); + wins = llList2Integer(leaderboardWins, i); + losses = llList2Integer(leaderboardLosses, i); - // Pad wins to 4 digits for proper string sorting (0000-9999) - string paddedWins = (string)(10000 + wins); // Add 10000 then remove first digit + // Pad wins to 4 digits (higher wins = better rank) + paddedWins = (string)(10000 + wins); paddedWins = llGetSubString(paddedWins, 1, -1); // Remove the "1" prefix - // Format: "PaddedWins:Name:ActualWins:Losses" for sorting - string entry = paddedWins + ":" + name + ":" + (string)wins + ":" + (string)losses; + // 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 - losses; + 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; + entry = sortKey + ":" + name + ":" + (string)wins + ":" + (string)losses; sortedData += [entry]; } - // Sort by padded wins (descending) - numerical order preserved in string sort - sortedData = llListSort(sortedData, 1, FALSE); // FALSE = descending order + // Sort by combined key (wins descending, losses ascending) - FALSE = descending order + sortedData = llListSort(sortedData, 1, FALSE); // Clean up the format back to "Name:Wins:Losses" (skip padded wins in position 0) - list cleanedData = []; + cleanedData = []; for (i = 0; i < llGetListLength(sortedData); i++) { - string entry = llList2String(sortedData, i); - list parts = llParseString2List(entry, [":"], []); + entry = llList2String(sortedData, i); + parts = llParseString2List(entry, [":"], []); if (llGetListLength(parts) >= 4) { // parts[0] = paddedWins (skip), parts[1] = name, parts[2] = actualWins, parts[3] = losses - string name = llList2String(parts, 1); - string wins = llList2String(parts, 2); - string losses = llList2String(parts, 3); - cleanedData += [name + ":" + wins + ":" + losses]; + name = llList2String(parts, 1); + winsStr = llList2String(parts, 2); + lossesStr = llList2String(parts, 3); + cleanedData += [name + ":" + winsStr + ":" + lossesStr]; } } @@ -284,15 +304,30 @@ generateLeaderboardText() { string leaderboardText = " TOP BATTLE RECORDS \n"; integer i; + string playerData; + list playerParts; + string playerName; + string wins; + string losses; + integer rankNumber; + string rank; + string leftPart; + string rightPart; + integer spaceNeeded; + string spacer; + integer s; + string line; + integer actualPlayers; + // Add actual player data (up to 11 players) for (i = 0; i < llGetListLength(sortedData) && i < 11; i++) { - string playerData = llList2String(sortedData, i); - list playerParts = llParseString2List(playerData, [":"], []); + playerData = llList2String(sortedData, i); + playerParts = llParseString2List(playerData, [":"], []); if (llGetListLength(playerParts) >= 3) { - string playerName = llList2String(playerParts, 0); - string wins = llList2String(playerParts, 1); - string losses = llList2String(playerParts, 2); + playerName = llList2String(playerParts, 0); + wins = llList2String(playerParts, 1); + losses = llList2String(playerParts, 2); // Truncate long names to fit better spacing if (llStringLength(playerName) > 12) { @@ -300,51 +335,49 @@ generateLeaderboardText() { } // Format rank - integer rankNumber = i + 1; - string rank = (string)rankNumber; + rankNumber = i + 1; + rank = (string)rankNumber; if (rankNumber < 10) rank = " " + rank; // Pad single digits // Create well-spaced line to fill full 40-character width - string leftPart = rank + ". " + playerName; - string rightPart = "W:" + wins + "/L:" + losses; + leftPart = rank + ". " + playerName; + rightPart = "W:" + wins + "/L:" + losses; // Calculate spaces needed to fill exactly 40 characters - integer spaceNeeded = 40 - llStringLength(leftPart) - llStringLength(rightPart); + spaceNeeded = 40 - llStringLength(leftPart) - llStringLength(rightPart); if (spaceNeeded < 1) spaceNeeded = 1; // At least 1 space - string spacer = ""; - integer s; + spacer = ""; for (s = 0; s < spaceNeeded; s++) { spacer += " "; } - string line = leftPart + spacer + rightPart; + line = leftPart + spacer + rightPart; leaderboardText += line + "\n"; } } // Fill remaining positions with placeholders to always show 11 positions - integer actualPlayers = llGetListLength(sortedData); + actualPlayers = llGetListLength(sortedData); for (i = actualPlayers; i < 11; i++) { - integer rankNumber = i + 1; - string rank = (string)rankNumber; + rankNumber = i + 1; + rank = (string)rankNumber; if (rankNumber < 10) rank = " " + rank; // Pad single digits // Create placeholder line to fill full 40-character width - string leftPart = rank + ". --------"; - string rightPart = "W:0/L:0"; + leftPart = rank + ". --------"; + rightPart = "W:0/L:0"; // Calculate spaces needed to fill exactly 40 characters - integer spaceNeeded = 40 - llStringLength(leftPart) - llStringLength(rightPart); + spaceNeeded = 40 - llStringLength(leftPart) - llStringLength(rightPart); if (spaceNeeded < 1) spaceNeeded = 1; // At least 1 space - string spacer = ""; - integer s; + spacer = ""; for (s = 0; s < spaceNeeded; s++) { spacer += " "; } - string line = leftPart + spacer + rightPart; + line = leftPart + spacer + rightPart; leaderboardText += line + "\n"; } diff --git a/README_Notecard.txt b/README_Notecard.txt index 43fcc2b..a28f6a2 100644 --- a/README_Notecard.txt +++ b/README_Notecard.txt @@ -33,7 +33,8 @@ KEY FEATURES 🎯 COMPREHENSIVE LEADERBOARD - Persistent Win Tracking: Player victories saved across sessions - XyzzyText Display: Professional 3-prim text system for leaderboard -- Automatic Sorting: Top players by win count +- Smart Multi-Level Sorting: Primary by wins (descending), secondary by losses (ascending) +- Fair Tie-Breaking: Players with same wins ranked by fewer losses for accurate competitive standings 🎮 CORE GAME FEATURES - 🎯 Dynamic Player Management: Players can join at runtime (owner and other avatars) @@ -101,6 +102,14 @@ Player Count | Dice Type RECENT IMPROVEMENTS (V2.8.6) ============================ +🏆 LEADERBOARD SORTING ENHANCEMENT +- Enhanced Tie-Breaking System: Fixed leaderboard rankings to properly handle players with identical win counts + * Smart Multi-Level Sorting: Primary by wins (descending), then by losses (ascending) for fair tiebreaker + * Example: Player A (5W/2L) now correctly ranks higher than Player B (5W/3L) + * Consistent Rankings: No more random ordering of tied players - losses now serve as logical tiebreaker +- Technical Implementation: Compound sorting key ensures reliable multi-level sorting in LSL environment +- LSL Compliance: Fixed variable declaration issues for proper script compilation + 🏆 WINNER GLOW CELEBRATION SYSTEM - Revolutionary Victory Recognition: Comprehensive winner celebration with bright green glow effects * Scoreboard Glory: Winner's profile picture and hearts glow bright green with green tint