v2.8.6: Winner Glow Celebration System

- Winner Glow Effects: Bright green glow on both scoreboard and floaters for winners
- Extended Victory Display: 24-second celebration timer before game reset
- Enhanced Visual Feedback: Color-coded glow system (green/yellow/red)
- Victory Text Display: Winner floaters show 'ULTIMATE VICTORY!' and 'ULTIMATE SURVIVOR'
- Timer System Optimization: Dedicated victory timer mode, removed unused timeout system
- Memory Efficiency: Cleaned up unused components for better performance
- Synchronized Celebration: Both scoreboard and floater systems celebrate winner simultaneously
- Visual Priority: Winner glow overrides peril glow during victory celebration

Professional winner recognition system makes victory feel rewarding and celebrated!
This commit is contained in:
RebeccaNod1 2025-09-16 13:10:41 -05:00
parent dd03bd8eef
commit 03c5d81bb0
23 changed files with 1484 additions and 585 deletions

View file

@ -60,6 +60,19 @@ list sentBotMessages = [];
// Verbose logging control - toggled by owner // Verbose logging control - toggled by owner
integer VERBOSE_LOGGING = FALSE; integer VERBOSE_LOGGING = 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;
llOwnerSay("🧠 [" + scriptName + "] Memory: " +
(string)used + " used, " +
(string)free + " free (" +
llGetSubString((string)percentUsed, 0, 4) + "% used)");
}
// Helper function to check and report memory usage // Helper function to check and report memory usage
checkMemoryUsage(string context) { checkMemoryUsage(string context) {
integer usedMemory = llGetUsedMemory(); integer usedMemory = llGetUsedMemory();
@ -196,6 +209,8 @@ doBotRoll(string botName, integer diceMax) {
default { default {
state_entry() { state_entry() {
reportMemoryUsage("🤖 Bot Manager");
initializeChannels(); initializeChannels();
LISTEN_CHANNEL = BOT_COMMAND_CHANNEL; LISTEN_CHANNEL = BOT_COMMAND_CHANNEL;
@ -215,6 +230,7 @@ default {
} }
on_rez(integer start_param) { on_rez(integer start_param) {
reportMemoryUsage("🤖 Bot Manager");
llOwnerSay("🔄 Bot Manager rezzed - reinitializing..."); llOwnerSay("🔄 Bot Manager rezzed - reinitializing...");
// Re-initialize dynamic channels // Re-initialize dynamic channels

View file

@ -4,6 +4,56 @@
All notable changes to Peril Dice will be documented in this file. All notable changes to Peril Dice will be documented in this file.
## [2.8.6] - 2025-09-16
### 🏆 **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
- **Floater Glow**: Winner's floating display shows green glow with victory text "✨ ULTIMATE VICTORY! ✨" and "🏆 ULTIMATE SURVIVOR 🏆"
- **Visual Priority**: Winner glow overrides peril glow - green takes priority over yellow during victory
- **Clear Recognition**: Eliminated players show red X markers while winner gets celebration spotlight
- **Extended Victory Display**: 24-second timer delay before game reset allows players to appreciate the winner
- **Before**: Winner declared → Immediate reset → No time to see who won
- **After**: Winner declared → Green glow celebration → 24 seconds to admire → Then reset
- **User Experience**: Proper victory moment with visual feedback before returning to lobby
### 🎨 **Enhanced Visual Feedback System**
- **Improved Glow Colors**: Enhanced scoreboard glow system with proper color tinting
- **Winner Glow**: Bright green glow + green color tint for unmistakable victory recognition
- **Peril Glow**: Yellow glow + yellow color tint for clear peril player identification
- **Visual Clarity**: Replaced generic white glow with distinct colored glows for better gameplay feedback
- **Floater Victory Detection**: Smart winner detection in floating displays
- **Automatic Detection**: Floaters automatically detect when only one player remains alive
- **Victory Text**: Winner's floater displays celebration text instead of standard game info
- **Consistent Experience**: Both scoreboard and floater systems celebrate the winner simultaneously
### 🔧 **Timer System Optimization**
- **Victory Delay Timer**: Added dedicated timer mode for victory celebration delays
- **Memory Efficiency**: Removed unused timeout system to free up memory resources
- **Clean Architecture**: Streamlined timer system focuses on essential functionality
- **Reliable Operation**: Victory display timing works consistently without interference from other systems
- **Message Communication Enhancement**: Improved winner glow message handling
- **Scoreboard Integration**: Added proper MSG_UPDATE_WINNER message constant for clean communication
- **Floater Integration**: Enhanced floater update system to trigger victory displays
- **Synchronized Effects**: Both display systems receive winner updates simultaneously
### 🎮 **User Experience Improvements**
- **Victory Celebration**: Clear winner recognition with proper visual feedback
- **Extended Appreciation Time**: 24 seconds to see and congratulate the winner before reset
- **Visual Hierarchy**: Clean priority system - Winner (green) > Peril (yellow) > Eliminated (red X)
- **Consistent Display**: Winner celebration works on both scoreboard and personal floater displays
### 📊 **System Performance**
- **Memory Optimization**: Removed unused timeout system components saving memory
- **Efficient Messaging**: Clean message routing for winner glow effects
- **Timer Efficiency**: Dedicated victory timer prevents conflicts with other timing systems
### 🎯 **Impact Summary**
- **Before**: Winners were announced but no visual celebration - immediate reset gave no recognition
- **After**: Winners get bright green glow celebration on both scoreboard and floaters with 24 seconds of recognition
- **Player Satisfaction**: Proper victory moment makes winning feel rewarding and celebrated
- **Visual Polish**: Professional-grade visual feedback system enhances the competitive gaming experience
## [2.8.5] - 2025-09-15 ## [2.8.5] - 2025-09-15
### 🧠 **Memory Optimization & Dead Code Cleanup** ### 🧠 **Memory Optimization & Dead Code Cleanup**

View file

@ -3,6 +3,57 @@ PERIL DICE CHANGELOG
All notable changes to Peril Dice are documented in this file. All notable changes to Peril Dice are documented in this file.
[2.8.6] - 2025-09-16
====================
🏆 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
* Floater Glow: Winner's floating display shows green glow with victory text "✨ ULTIMATE VICTORY! ✨" and "🏆 ULTIMATE SURVIVOR 🏆"
* Visual Priority: Winner glow overrides peril glow - green takes priority over yellow during victory
* Clear Recognition: Eliminated players show red X markers while winner gets celebration spotlight
- Extended Victory Display: 24-second timer delay before game reset allows players to appreciate the winner
* Before: Winner declared → Immediate reset → No time to see who won
* After: Winner declared → Green glow celebration → 24 seconds to admire → Then reset
* User Experience: Proper victory moment with visual feedback before returning to lobby
🎨 ENHANCED VISUAL FEEDBACK SYSTEM
- Improved Glow Colors: Enhanced scoreboard glow system with proper color tinting
* Winner Glow: Bright green glow + green color tint for unmistakable victory recognition
* Peril Glow: Yellow glow + yellow color tint for clear peril player identification
* Visual Clarity: Replaced generic white glow with distinct colored glows for better gameplay feedback
- Floater Victory Detection: Smart winner detection in floating displays
* Automatic Detection: Floaters automatically detect when only one player remains alive
* Victory Text: Winner's floater displays celebration text instead of standard game info
* Consistent Experience: Both scoreboard and floater systems celebrate the winner simultaneously
🔧 TIMER SYSTEM OPTIMIZATION
- Victory Delay Timer: Added dedicated timer mode for victory celebration delays
* Memory Efficiency: Removed unused timeout system to free up memory resources
* Clean Architecture: Streamlined timer system focuses on essential functionality
* Reliable Operation: Victory display timing works consistently without interference from other systems
- Message Communication Enhancement: Improved winner glow message handling
* Scoreboard Integration: Added proper MSG_UPDATE_WINNER message constant for clean communication
* Floater Integration: Enhanced floater update system to trigger victory displays
* Synchronized Effects: Both display systems receive winner updates simultaneously
🎮 USER EXPERIENCE IMPROVEMENTS
- Victory Celebration: Clear winner recognition with proper visual feedback
- Extended Appreciation Time: 24 seconds to see and congratulate the winner before reset
- Visual Hierarchy: Clean priority system - Winner (green) > Peril (yellow) > Eliminated (red X)
- Consistent Display: Winner celebration works on both scoreboard and personal floater displays
📊 SYSTEM PERFORMANCE
- Memory Optimization: Removed unused timeout system components saving memory
- Efficient Messaging: Clean message routing for winner glow effects
- Timer Efficiency: Dedicated victory timer prevents conflicts with other timing systems
🎯 IMPACT SUMMARY
- Before: Winners were announced but no visual celebration - immediate reset gave no recognition
- After: Winners get bright green glow celebration on both scoreboard and floaters with 24 seconds of recognition
- Player Satisfaction: Proper victory moment makes winning feel rewarding and celebrated
- Visual Polish: Professional-grade visual feedback system enhances the competitive gaming experience
[2.8.5] - 2025-09-15 [2.8.5] - 2025-09-15
==================== ====================

View file

@ -6,6 +6,19 @@
integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs
integer MSG_TOGGLE_VERBOSE_LOGS = 9998; // Message to toggle verbose logging integer 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 // Message constants for communication with main controller
integer MSG_REQUEST_PLAYER_DATA = 8001; integer MSG_REQUEST_PLAYER_DATA = 8001;
integer MSG_REQUEST_PICK_DATA = 8002; integer MSG_REQUEST_PICK_DATA = 8002;
@ -367,6 +380,8 @@ handleDialogRequest(string dialogData) {
default { default {
state_entry() { state_entry() {
reportMemoryUsage("📨 Message Handler");
llOwnerSay("📨 [Message Handler] Helper script ready!"); llOwnerSay("📨 [Message Handler] Helper script ready!");
initializeChannels(); initializeChannels();
} }

View file

@ -1,106 +0,0 @@
# Direct Communication Architecture
## Overview
To reduce Main Controller memory pressure (from 92% usage), we've implemented direct system-to-system communication, bypassing the Main Controller for specific operations.
## Direct Communication Flows
### ✅ Implemented Direct Connections
#### 1. **Roll Module → Scoreboard** (Status & Life Updates)
```lsl
// Life updates during gameplay
llMessageLinked(12, 3002, perilPlayer + "|" + (string)lives + "|NULL_KEY", NULL_KEY);
// Game status updates
llMessageLinked(12, 3001, "Plot Twist", NULL_KEY); // When player picks rolled number
llMessageLinked(12, 3001, "Direct Hit", NULL_KEY); // When peril player picks their doom
llMessageLinked(12, 3001, "No Shield", NULL_KEY); // When nobody picks rolled number
```
**Benefit**: Real-time scoreboard updates without Main Controller memory overhead.
#### 2. **Roll Module → Dice Display** (Roll Results)
```lsl
// Display roll results directly
llMessageLinked(83, 3020, perilPlayer + "|" + rollResult, NULL_KEY);
// Clear dice display
llMessageLinked(83, 3021, "", NULL_KEY);
```
**Benefit**: Eliminates dice message routing through Main Controller.
#### 3. **Main Controller → Scoreboard → Leaderboard** (Win/Loss Tracking)
```lsl
// Elimination tracking (already optimized)
llMessageLinked(SCOREBOARD_LINK, MSG_GAME_LOST, eliminatedPlayer, NULL_KEY);
// Victory tracking (already optimized)
llMessageLinked(SCOREBOARD_LINK, MSG_GAME_WON, winner, NULL_KEY);
```
**Benefit**: Scoreboard handles leaderboard updates internally, reducing Main Controller load.
## Architecture Benefits
### Memory Savings
- **Main Controller**: Reduced from 92% to estimated ~85% memory usage
- **Eliminated routing logic**: No more message forwarding for status/dice/scoreboard updates
- **Reduced string operations**: Direct messages avoid CSV building/parsing in Main Controller
### Performance Improvements
- **Real-time updates**: Scoreboard updates immediately when lives change
- **Reduced latency**: No message forwarding delays
- **Better reliability**: Fewer message hops = fewer failure points
### System Resilience
- **Distributed processing**: Each system handles its own display updates
- **Reduced bottlenecks**: Main Controller no longer central routing point
- **Memory stability**: Less memory pressure on critical controller script
## Message Flow Comparison
### Before (Centralized Routing)
```
Roll Module → Main Controller → Scoreboard (Status)
Roll Module → Main Controller → Dice Display (Results)
↳ Main Controller ← High Memory Usage
```
### After (Direct Communication)
```
Roll Module → Scoreboard (Status & Lives)
Roll Module → Dice Display (Results)
↳ Main Controller ← Reduced Memory Usage
```
## Link Mapping Reference
- **Link 1**: Main Controller (Root)
- **Link 12**: Scoreboard Manager
- **Link 35**: Leaderboard Communication
- **Link 83**: Dice Display Bridge
## Message Constants
```lsl
// Scoreboard Messages
integer MSG_GAME_STATUS = 3001; // Status updates (Plot Twist, Direct Hit, etc.)
integer MSG_PLAYER_UPDATE = 3002; // Life updates during gameplay
integer MSG_GAME_WON = 3010; // Victory notifications
integer MSG_GAME_LOST = 3011; // Elimination notifications
// Dice Display Messages
integer MSG_DICE_ROLL = 3020; // Roll result display
integer MSG_CLEAR_DICE = 3021; // Clear display
```
## Testing Validation
- ✅ Roll Module sends status updates directly to scoreboard
- ✅ Roll Module sends life updates directly to scoreboard
- ✅ Roll Module sends dice results directly to dice display
- ✅ Floater cleanup with timing delays for elimination
- ✅ Main Controller routing logic removed for direct paths
## Future Optimization Opportunities
1. **Bot Manager → Game Manager**: Direct bot pick notifications
2. **Floater Manager → Individual Floaters**: Direct floater commands
3. **Dialog Systems**: Direct dialog routing without Main Controller
This architecture maintains the robust linkset communication while significantly reducing memory pressure on the Main Controller.

View file

@ -8,6 +8,7 @@ integer MSG_CLEANUP_FLOAT = 104;
integer MSG_REZ_FLOAT = 105; integer MSG_REZ_FLOAT = 105;
integer MSG_REGISTER_PLAYER = 106; integer MSG_REGISTER_PLAYER = 106;
integer MSG_SYNC_GAME_STATE = 107; integer MSG_SYNC_GAME_STATE = 107;
integer MSG_CLEANUP_ALL_FLOATERS = 212;
// Debug control message constants // Debug control message constants
integer MSG_DEBUG_PICKS_ON = 7001; integer MSG_DEBUG_PICKS_ON = 7001;
@ -54,12 +55,77 @@ integer DEBUG_PICKS = FALSE;
// Verbose logging control - affects general debug messages, toggled by owner // Verbose logging control - affects general debug messages, toggled by owner
integer VERBOSE_LOGGING = FALSE; integer VERBOSE_LOGGING = FALSE;
list players = []; // Memory reporting function
list names = []; reportMemoryUsage(string scriptName) {
list lives = []; integer used = llGetUsedMemory();
list picksData = []; 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)");
}
// 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)
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
list picksData = []; // Current picks data from sync
list playerNames = []; // Legacy variable for cleanup compatibility
list playerChannels = []; // Legacy variable for cleanup compatibility
string perilPlayer = ""; 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 // Returns a list of picks for the given player name, filtering out invalid values
list getPicksFor(string nameInput) { list getPicksFor(string nameInput) {
// ENHANCED DEBUG: Log the request and current data state // ENHANCED DEBUG: Log the request and current data state
@ -127,37 +193,67 @@ list getPicksFor(string nameInput) {
// Converts a player's key into their name (if registered) // Converts a player's key into their name (if registered)
string getNameFromKey(key id) { string getNameFromKey(key id) {
integer i = llListFindList(players, [id]); integer i = llListFindList(alivePlayers, [id]);
if (i != -1) return llList2String(names, i); if (i != -1) return llList2String(aliveNames, i);
return (string)id; return (string)id;
} }
// Main event handler // Main event handler
default { default {
state_entry() { state_entry() {
reportMemoryUsage("💬 Floater Manager");
initializeChannels(); initializeChannels();
// Initialize/reset all state variables // Initialize/reset all state variables
players = []; alivePlayers = [];
aliveNames = [];
aliveLives = [];
alivePicksData = [];
eliminatedPlayers = [];
eliminatedNames = [];
eliminatedChannels = [];
allPlayerNames = [];
allPlayerChannels = [];
// Initialize sync state variables
names = []; names = [];
players = [];
lives = []; lives = [];
picksData = []; picksData = [];
playerNames = [];
playerChannels = [];
perilPlayer = ""; perilPlayer = "";
llOwnerSay("📦 Floater Manager ready!"); llOwnerSay("📦 Floater Manager ready!");
} }
on_rez(integer start_param) { on_rez(integer start_param) {
reportMemoryUsage("💬 Floater Manager");
llOwnerSay("🔄 Floater Manager rezzed - reinitializing..."); llOwnerSay("🔄 Floater Manager rezzed - reinitializing...");
// Re-initialize dynamic channels // Re-initialize dynamic channels
initializeChannels(); initializeChannels();
// Reset all state variables on rez // Reset all state variables on rez
players = []; alivePlayers = [];
aliveNames = [];
aliveLives = [];
alivePicksData = [];
eliminatedPlayers = [];
eliminatedNames = [];
eliminatedChannels = [];
allPlayerNames = [];
allPlayerChannels = [];
// Initialize sync state variables
names = []; names = [];
players = [];
lives = []; lives = [];
picksData = []; picksData = [];
playerNames = [];
playerChannels = [];
perilPlayer = ""; perilPlayer = "";
llOwnerSay("✅ Floater Manager reset complete after rez!"); llOwnerSay("✅ Floater Manager reset complete after rez!");
@ -167,10 +263,23 @@ default {
// Handle full reset from main controller // Handle full reset from main controller
if (num == -99999 && str == "FULL_RESET") { if (num == -99999 && str == "FULL_RESET") {
// Reset all game state // Reset all game state
players = []; alivePlayers = [];
aliveNames = [];
aliveLives = [];
alivePicksData = [];
eliminatedPlayers = [];
eliminatedNames = [];
eliminatedChannels = [];
allPlayerNames = [];
allPlayerChannels = [];
// Reset sync state variables
names = []; names = [];
players = [];
lives = []; lives = [];
picksData = []; picksData = [];
playerNames = [];
playerChannels = [];
perilPlayer = ""; perilPlayer = "";
llOwnerSay("📦 Floater Manager reset!"); llOwnerSay("📦 Floater Manager reset!");
return; return;
@ -192,7 +301,7 @@ default {
if (num == MSG_REGISTER_PLAYER) { if (num == MSG_REGISTER_PLAYER) {
// Enforce the maximum number of players // Enforce the maximum number of players
if (llGetListLength(players) >= MAX_PLAYERS) { if (llGetListLength(alivePlayers) >= MAX_PLAYERS) {
llOwnerSay("⚠️ Cannot register new player; the game is full (max " + (string)MAX_PLAYERS + ")."); llOwnerSay("⚠️ Cannot register new player; the game is full (max " + (string)MAX_PLAYERS + ").");
return; return;
} }
@ -202,15 +311,22 @@ default {
key avKey = llList2Key(info, 1); key avKey = llList2Key(info, 1);
// Check if this player is already registered to prevent duplicate floaters // Check if this player is already registered to prevent duplicate floaters
if (llListFindList(players, [avKey]) != -1) { if (llListFindList(alivePlayers, [avKey]) != -1) {
llOwnerSay("⚠️ Player " + name + " is already registered, ignoring duplicate registration"); llOwnerSay("⚠️ Player " + name + " is already registered, ignoring duplicate registration");
return; return;
} }
names += [name]; // Add to ALIVE lists
players += [avKey]; aliveNames += [name];
lives += [3]; // Initialize with 3 lives for new players alivePlayers += [avKey];
picksData += [name + "|"]; // Initialize with empty picks 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! 💀"); llSay(0, "💀 " + name + " has entered the deadly game! Welcome to your potential doom! 💀");
// Immediately rez the float after registration. This ensures the // Immediately rez the float after registration. This ensures the
@ -225,9 +341,9 @@ default {
} }
else if (num == MSG_REZ_FLOAT) { else if (num == MSG_REZ_FLOAT) {
string name = str; string name = str;
integer idx = llListFindList(names, [name]); integer idx = llListFindList(aliveNames, [name]);
if (idx == -1) return; if (idx == -1) return;
key avKey = llList2Key(players, idx); key avKey = llList2Key(alivePlayers, idx);
// Determine the rez position. For real avatars, use their current // Determine the rez position. For real avatars, use their current
// location plus an offset. For test players (fake keys) or when // location plus an offset. For test players (fake keys) or when
// OBJECT_POS cannot be obtained, fall back to rezzing relative to // OBJECT_POS cannot be obtained, fall back to rezzing relative to
@ -249,7 +365,16 @@ default {
// This prevents them from spawning directly on the leaderboard. // This prevents them from spawning directly on the leaderboard.
pos = basePos + <-4.0 - (float)idx * 1.0, 2.0 + (float)idx * 0.8, 1>; pos = basePos + <-4.0 - (float)idx * 1.0, 2.0 + (float)idx * 0.8, 1>;
} }
integer ch = FLOATER_BASE_CHANNEL + idx; // 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;
}
llRezObject("StatFloat", pos, ZERO_VECTOR, ZERO_ROTATION, ch); llRezObject("StatFloat", pos, ZERO_VECTOR, ZERO_ROTATION, ch);
// Wait a moment then set the description // Wait a moment then set the description
llSleep(0.2); llSleep(0.2);
@ -268,14 +393,39 @@ default {
} }
else if (num == MSG_UPDATE_FLOAT) { else if (num == MSG_UPDATE_FLOAT) {
string name = str; string name = str;
integer idx = llListFindList(names, [name]);
if (idx == -1) { // FIXED: Use permanent channel mapping that doesn't change during eliminations
llOwnerSay("⚠️ [Floater Manager] MSG_UPDATE_FLOAT: Player '" + name + "' not found in names list"); 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; return;
} }
key avKey = llList2Key(players, idx);
integer ch = FLOATER_BASE_CHANNEL + idx; // Try to find player in current sync data (names/lives from Main Controller)
integer lifeCount = llList2Integer(lives, idx); // This works for both alive and eliminated players during the elimination sync
integer syncIdx = llListFindList(names, [name]);
key avKey = NULL_KEY;
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 (VERBOSE_LOGGING) llOwnerSay("🔄 [Floater Manager] Updating floater for " + name + " (lives: " + (string)lifeCount + ")"); if (VERBOSE_LOGGING) llOwnerSay("🔄 [Floater Manager] Updating floater for " + name + " (lives: " + (string)lifeCount + ")");
list picks = getPicksFor(name); list picks = getPicksFor(name);
@ -303,50 +453,95 @@ default {
} }
string picksDisplay = llList2CSV(picks); string picksDisplay = llList2CSV(picks);
string txt = "🎲 Peril Dice\n👤 " + name + "\nLives: " + (string)lifeCount + "\n" + perilName + "\n🔢 Picks: " + picksDisplay;
// Check if this player is the winner (only one player with lives > 0)
integer livingPlayers = 0;
integer isWinner = FALSE;
for (i = 0; i < llGetListLength(lives); i++) {
if (llList2Integer(lives, i) > 0) {
livingPlayers++;
}
}
// If only one player has lives > 0 and this player has lives > 0, they're the winner
if (livingPlayers == 1 && lifeCount > 0) {
isWinner = TRUE;
}
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); llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt);
} }
else if (num == MSG_CLEANUP_FLOAT) { else if (num == MSG_CLEANUP_FLOAT) {
integer ch = (integer)str; integer ch = (integer)str;
integer idx = ch - FLOATER_BASE_CHANNEL;
// Always send cleanup message to the channel (even for orphaned floaters) // Always send cleanup message to the channel (even for orphaned floaters)
llRegionSay(ch, "CLEANUP"); llRegionSay(ch, "CLEANUP");
// IMPROVED: Clean up internal lists more carefully to handle race conditions // FIXED: Find player by searching the permanent channel mapping
// Instead of relying on index calculation, find by channel and remove consistently string removedPlayer = "";
if (idx >= 0 && idx < llGetListLength(players)) { integer foundIdx = -1;
// Store player info before removal for debugging
string removedPlayer = ""; // Search for the channel in our permanent mapping
if (idx < llGetListLength(names)) { foundIdx = llListFindList(allPlayerChannels, [ch]);
removedPlayer = llList2String(names, idx); if (foundIdx != -1) {
removedPlayer = llList2String(allPlayerNames, foundIdx);
}
if (foundIdx != -1 && removedPlayer != "") {
// Find the current index of this player in the active names list
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");
} }
// Remove from all lists at the same index to maintain synchronization // IMPORTANT: Keep permanent mapping intact - never remove from playerNames/playerChannels
if (idx < llGetListLength(players)) players = llDeleteSubList(players, idx, idx); // This preserves the original channel assignments
if (idx < llGetListLength(names)) names = llDeleteSubList(names, idx, idx);
if (idx < llGetListLength(lives)) lives = llDeleteSubList(lives, idx, idx);
if (idx < llGetListLength(picksData)) picksData = llDeleteSubList(picksData, idx, idx);
if (removedPlayer != "") { // Remove from picksData by searching for the player name
llOwnerSay("🧹 [Floater Manager] Cleaned up floater for: " + removedPlayer + " (channel: " + (string)ch + ")"); integer pickIdx = -1;
integer p;
// CRITICAL: Update all remaining floaters to refresh peril status after player removal for (p = 0; p < llGetListLength(picksData); p++) {
// This prevents stale peril player information from persisting in floaters string entry = llList2String(picksData, p);
llSleep(0.2); // Brief delay to ensure cleanup completes if (llSubStringIndex(entry, removedPlayer + "|") == 0) {
integer i; pickIdx = p;
for (i = 0; i < llGetListLength(names); i++) { jump found_pick;
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);
} }
} }
@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 { } else {
// Orphaned floater cleanup // Orphaned floater cleanup - channel not found in our tracking
llOwnerSay("🧹 [Floater Manager] Cleaned up orphaned floater (channel: " + (string)ch + ")"); llOwnerSay("🧹 [Floater Manager] Cleaned up orphaned floater (channel: " + (string)ch + ") - not in tracking list");
} }
} }
else if (num == MSG_SYNC_GAME_STATE) { else if (num == MSG_SYNC_GAME_STATE) {
@ -393,7 +588,24 @@ default {
} }
// IMPORTANT: Also sync the names list to prevent desynchronization // IMPORTANT: Also sync the names list to prevent desynchronization
string namesStr = llList2String(parts, 3); string namesStr = llList2String(parts, 3);
names = llCSV2List(namesStr); list newNames = llCSV2List(namesStr);
// 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) // Sync players list if available (5th part)
if (llGetListLength(parts) >= 5) { if (llGetListLength(parts) >= 5) {
@ -406,18 +618,16 @@ default {
llOwnerSay("🎯 Floater Manager: Peril player updated from '" + oldPeril + "' to '" + perilPlayer + "'"); llOwnerSay("🎯 Floater Manager: Peril player updated from '" + oldPeril + "' to '" + perilPlayer + "'");
} }
// CRITICAL: After synchronizing the game state, ALWAYS update all existing floats // CRITICAL: After synchronizing the game state, ALWAYS update ALL registered floaters
// This ensures they reflect the current peril status and prevents desynchronization // This includes both alive and eliminated players so eliminated floaters show proper status
// Force update even if peril player didn't change - other state may have changed
integer idx; integer idx;
for (idx = 0; idx < llGetListLength(names); idx++) { for (idx = 0; idx < llGetListLength(allPlayerNames); idx++) {
string n = llList2String(names, idx); string n = llList2String(allPlayerNames, idx);
// Use the player's key if it exists; fallback to NULL_KEY for test bots // Find player key if they're still alive, otherwise use NULL_KEY
key k; key k = NULL_KEY;
if (idx < llGetListLength(players)) { integer aliveIdx = llListFindList(names, [n]);
k = llList2Key(players, idx); if (aliveIdx != -1 && aliveIdx < llGetListLength(players)) {
} else { k = llList2Key(players, aliveIdx);
k = NULL_KEY;
} }
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, n, k); llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, n, k);
} }
@ -427,19 +637,55 @@ default {
if (oldPeril != perilPlayer && perilPlayer != "" && perilPlayer != "NONE") { if (oldPeril != perilPlayer && perilPlayer != "" && perilPlayer != "NONE") {
llOwnerSay("🔄 [Floater Manager] Peril player changed - scheduling secondary update for stability"); llOwnerSay("🔄 [Floater Manager] Peril player changed - scheduling secondary update for stability");
llSleep(0.3); // Brief delay to let initial updates complete llSleep(0.3); // Brief delay to let initial updates complete
for (idx = 0; idx < llGetListLength(names); idx++) { for (idx = 0; idx < llGetListLength(allPlayerNames); idx++) {
string n = llList2String(names, idx); string n = llList2String(allPlayerNames, idx);
key k; // Find player key if they're still alive, otherwise use NULL_KEY
if (idx < llGetListLength(players)) { key k = NULL_KEY;
k = llList2Key(players, idx); integer aliveIdx = llListFindList(names, [n]);
} else { if (aliveIdx != -1 && aliveIdx < llGetListLength(players)) {
k = NULL_KEY; k = llList2Key(players, aliveIdx);
} }
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, n, k); 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");
}
// Clear our internal tracking
players = [];
names = [];
lives = [];
picksData = [];
perilPlayer = "";
playerNames = [];
playerChannels = [];
}
// Handle debug control messages // Handle debug control messages
else if (num == MSG_DEBUG_PICKS_ON) { else if (num == MSG_DEBUG_PICKS_ON) {
DEBUG_PICKS = TRUE; DEBUG_PICKS = TRUE;

View file

@ -77,6 +77,9 @@ Three possible outcomes based on the dice roll:
### **6️⃣ Victory** ### **6️⃣ Victory**
- **Last player standing wins!** 🏆 - **Last player standing wins!** 🏆
- **Winner Glow Celebration** (NEW in v2.8.6): Winner gets bright green glow on both scoreboard and floater 💚
- **Extended Victory Display**: 24-second celebration timer before game reset ⏱️
- **Victory Text**: Winner's floater displays "✨ ULTIMATE VICTORY! ✨" and "🏆 ULTIMATE SURVIVOR 🏆"
- Victory celebration with confetti effects! ✨ - Victory celebration with confetti effects! ✨
- Winner's name recorded on the leaderboard - Winner's name recorded on the leaderboard
@ -169,18 +172,23 @@ The game features thematic messaging for maximum immersion:
- **Kick Player** functionality allows owners to remove disruptive players - **Kick Player** functionality allows owners to remove disruptive players
- **Leave Game** system properly cleans up all player data - **Leave Game** system properly cleans up all player data
### **Enhanced Display System (NEW in v2.8.1):** ### **Enhanced Display System (UPDATED in v2.8.6):**
- **Winner Glow Celebration** - Winners get bright green glow on both scoreboard and floaters 💚
- **Visual Priority System** - Winner glow (green) overrides peril glow (yellow) during victory
- **24-Second Victory Timer** - Extended celebration before game reset for proper recognition
- **Real-Time Status Updates** - Peril status updates immediately on floating displays - **Real-Time Status Updates** - Peril status updates immediately on floating displays
- **Enhanced Elimination** - Players see 0 hearts ❤️ → 💔💔💔 before removal - **Enhanced Elimination** - Players see 0 hearts ❤️ → 💔💔💔 before removal with red X markers ❌
- **Visual Scoreboard** - Shows all players with profile pictures and heart counts - **Visual Scoreboard** - Shows all players with profile pictures and heart counts
- **Floating HUD Displays** - Personal status displays for each player - **Floating HUD Displays** - Personal status displays for each player
- **Professional Status Actions** - Large status prim shows current game events - **Professional Status Actions** - Large status prim shows current game events
- **Color-Coded Glow System** - Green (winner), Yellow (peril), Red X (eliminated)
### **Technical Rules:** ### **Technical Rules:**
- Game supports up to **10 players maximum** - Game supports up to **10 players maximum**
- **2-minute timeout** on dialog responses (improved for faster gameplay) - **Owner kick functionality** for managing disruptive players
- **Automatic game reset** on startup (preserves leaderboard) - **Automatic game reset** on startup (preserves leaderboard)
- **Single linkset architecture** (74 prims total) for reliable operation - **Single linkset architecture** (74 prims total) for reliable operation
- **Professional winner celebration** with 24-second recognition timer
--- ---

View file

@ -77,6 +77,11 @@ NO SHIELD!
6. Victory 6. Victory
- Last player standing wins! - Last player standing wins!
- Winner Glow Celebration (NEW in v2.8.6): Winner gets bright green glow
on both scoreboard and floater
- Extended Victory Display: 24-second celebration timer before game reset
- Victory Text: Winner's floater displays "ULTIMATE VICTORY!" and
"ULTIMATE SURVIVOR"
- Victory celebration with confetti effects! - Victory celebration with confetti effects!
- Winner's name recorded on the leaderboard - Winner's name recorded on the leaderboard
@ -169,18 +174,26 @@ Game Security (NEW in v2.8.0):
- Kick Player functionality allows owners to remove disruptive players - Kick Player functionality allows owners to remove disruptive players
- Leave Game system properly cleans up all player data - Leave Game system properly cleans up all player data
Enhanced Display System (NEW in v2.8.1): Enhanced Display System (UPDATED in v2.8.6):
- Winner Glow Celebration - Winners get bright green glow on both
scoreboard and floaters
- Visual Priority System - Winner glow (green) overrides peril glow (yellow)
during victory
- 24-Second Victory Timer - Extended celebration before game reset for
proper recognition
- Real-Time Status Updates - Peril status updates immediately on displays - Real-Time Status Updates - Peril status updates immediately on displays
- Enhanced Elimination - Players see 0 hearts before removal - Enhanced Elimination - Players see 0 hearts before removal with red X markers
- Visual Scoreboard - Shows all players with profile pictures and hearts - Visual Scoreboard - Shows all players with profile pictures and hearts
- Floating HUD Displays - Personal status displays for each player - Floating HUD Displays - Personal status displays for each player
- Professional Status Actions - Large status prim shows current events - Professional Status Actions - Large status prim shows current events
- Color-Coded Glow System - Green (winner), Yellow (peril), Red X (eliminated)
Technical Rules: Technical Rules:
- Game supports up to 10 players maximum - Game supports up to 10 players maximum
- 2-minute timeout on dialog responses (improved for faster gameplay) - Owner kick functionality for managing disruptive players
- Automatic game reset on startup (preserves leaderboard) - Automatic game reset on startup (preserves leaderboard)
- Single linkset architecture (74 prims total) for reliable operation - Single linkset architecture (74 prims total) for reliable operation
- Professional winner celebration with 24-second recognition timer
=============================================================================== ===============================================================================

View file

@ -7,6 +7,19 @@
integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs
integer MSG_TOGGLE_VERBOSE_LOGS = 9998; // Message to toggle verbose logging integer 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)");
}
integer MSG_GET_DICE_TYPE = 1001; integer MSG_GET_DICE_TYPE = 1001;
integer MSG_DICE_TYPE_RESULT = 1005; integer MSG_DICE_TYPE_RESULT = 1005;
integer MSG_GET_PICKS_REQUIRED = 1002; integer MSG_GET_PICKS_REQUIRED = 1002;
@ -130,6 +143,8 @@ integer showPickManager(string player, key id) {
default { default {
state_entry() { state_entry() {
reportMemoryUsage("🧮 Game Calculator");
// Initialize/reset all state variables // Initialize/reset all state variables
lives = []; lives = [];
picksData = []; picksData = [];
@ -140,6 +155,8 @@ default {
} }
on_rez(integer start_param) { on_rez(integer start_param) {
reportMemoryUsage("🧮 Game Calculator");
if (VERBOSE_LOGGING) { if (VERBOSE_LOGGING) {
llOwnerSay("🔄 Game Calculator rezzed - reinitializing..."); llOwnerSay("🔄 Game Calculator rezzed - reinitializing...");
} }

View file

@ -38,6 +38,46 @@ string lastHumanPickMessage = "";
string lastBotPickMessage = ""; string lastBotPickMessage = "";
integer lastProcessTime = 0; integer lastProcessTime = 0;
// Build complete avoidance list from all current picks
list buildCompleteAvoidanceList() {
list allPicks = [];
integer i;
// Add all picks from picksData (current round)
for (i = 0; i < llGetListLength(picksData); i++) {
string entry = llList2String(picksData, i);
list entryParts = llParseString2List(entry, ["|"], []);
if (llGetListLength(entryParts) >= 2) {
string picks = llList2String(entryParts, 1);
if (picks != "") {
// Split multiple picks (comma or semicolon separated)
list playerPicks = llParseString2List(picks, [","], []);
if (llGetListLength(playerPicks) == 1) {
// Also try semicolon separator
playerPicks = llParseString2List(picks, [";"], []);
}
integer j;
for (j = 0; j < llGetListLength(playerPicks); j++) {
string pick = llStringTrim(llList2String(playerPicks, j), STRING_TRIM);
if (pick != "" && llListFindList(allPicks, [pick]) == -1) {
allPicks += [pick];
}
}
}
}
}
// Also add any picks from globalPickedNumbers as backup
for (i = 0; i < llGetListLength(globalPickedNumbers); i++) {
string pick = llList2String(globalPickedNumbers, i);
if (pick != "" && llListFindList(allPicks, [pick]) == -1) {
allPicks += [pick];
}
}
return allPicks;
}
// Memory reporting function // Memory reporting function
reportMemoryUsage(string scriptName) { reportMemoryUsage(string scriptName) {
integer used = llGetUsedMemory(); integer used = llGetUsedMemory();
@ -64,6 +104,7 @@ integer MSG_SYNC_PICKQUEUE = 2001;
integer MSG_PLAYER_WON = 551; integer MSG_PLAYER_WON = 551;
integer MSG_UPDATE_FLOAT = 2010; integer MSG_UPDATE_FLOAT = 2010;
integer MSG_DIALOG_FORWARD_REQUEST = 9060; // Request dialog forwarding from Player_RegistrationManager integer MSG_DIALOG_FORWARD_REQUEST = 9060; // Request dialog forwarding from Player_RegistrationManager
integer MSG_CONTINUE_ROUND = 998; // Continue round after roll processing
continueCurrentRound() { continueCurrentRound() {
// MEMORY OPTIMIZED: Skip complex validation - trust game state // MEMORY OPTIMIZED: Skip complex validation - trust game state
@ -147,6 +188,10 @@ startNextRound() {
if (llGetListLength(names) == 1) { if (llGetListLength(names) == 1) {
string winner = llList2String(names, 0); string winner = llList2String(names, 0);
llSay(0, "✨ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!"); 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_SET, MSG_PLAYER_WON, winner, NULL_KEY); llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY);
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY); llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
// Don't use hardcoded channel here - let Main Controller handle scoreboard updates // Don't use hardcoded channel here - let Main Controller handle scoreboard updates
@ -167,6 +212,9 @@ startNextRound() {
perilPlayer = llList2String(names, randomIdx); perilPlayer = llList2String(names, randomIdx);
llSay(0, "🎯 " + perilPlayer + " has been randomly selected and is now in peril!"); 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
// Sync state to floater manager first, then update floaters to show new peril player immediately // Sync state to floater manager first, then update floaters to show new peril player immediately
llOwnerSay("🔄 [Game Manager] Syncing state and updating floaters for new peril player: " + perilPlayer); llOwnerSay("🔄 [Game Manager] Syncing state and updating floaters for new peril player: " + perilPlayer);
syncStateToMain(); // Sync the peril player to all modules first syncStateToMain(); // Sync the peril player to all modules first
@ -303,8 +351,10 @@ showNextPickerDialog() {
integer picksNeeded = 4 - perilLives; integer picksNeeded = 4 - perilLives;
// Send proper avoid list to bots so they don't pick human numbers // Send proper avoid list to bots so they don't pick human numbers
string globalPicksStr = llList2CSV(globalPickedNumbers); list completeAvoidList = buildCompleteAvoidanceList();
string botCommand = "BOT_PICK:" + firstName + ":" + (string)picksNeeded + ":" + (string)diceType + ":" + globalPicksStr; string avoidListStr = llList2CSV(completeAvoidList);
string botCommand = "BOT_PICK:" + firstName + ":" + (string)picksNeeded + ":" + (string)diceType + ":" + avoidListStr;
llOwnerSay("🎯 [Game Manager] Sending bot command with complete avoid list (" + (string)llGetListLength(completeAvoidList) + " numbers): " + avoidListStr);
llMessageLinked(LINK_SET, -9999, botCommand, NULL_KEY); llMessageLinked(LINK_SET, -9999, botCommand, NULL_KEY);
llOwnerSay("🤖 " + firstName + " is automatically picking " + (string)picksNeeded + " numbers..."); llOwnerSay("🤖 " + firstName + " is automatically picking " + (string)picksNeeded + " numbers...");
} else { } else {
@ -327,9 +377,10 @@ showNextPickerDialog() {
} }
integer picksNeeded = 4 - perilLives; integer picksNeeded = 4 - perilLives;
// Send dialog payload with current globally picked numbers // Send dialog payload with complete avoid list for humans too
string globalPicksStr = llList2CSV(globalPickedNumbers); list completeAvoidList = buildCompleteAvoidanceList();
string dialogPayload = firstName + "|" + (string)diceType + "|" + (string)picksNeeded + "|" + globalPicksStr; string avoidListStr = llList2CSV(completeAvoidList);
string dialogPayload = firstName + "|" + (string)diceType + "|" + (string)picksNeeded + "|" + avoidListStr;
llOwnerSay("🎯 Showing pick dialog for " + firstName); llOwnerSay("🎯 Showing pick dialog for " + firstName);
llSleep(0.5); // Brief delay to prevent spam llSleep(0.5); // Brief delay to prevent spam
@ -617,22 +668,7 @@ default {
return; return;
} }
// Handle start round requests // Legacy 997 message handler removed - now using direct MSG_CONTINUE_ROUND (998) communication
if (num == 997) {
if (str == "START_NEXT_ROUND") {
startNextRound();
}
else if (llSubStringIndex(str, "CONTINUE_ROUND|") == 0) {
string newPeril = llGetSubString(str, 15, -1);
perilPlayer = newPeril;
llOwnerSay("🎯 Game Manager received CONTINUE_ROUND for " + newPeril);
// Reset round state for new round
roundStarted = FALSE;
currentPickerIdx = 0;
continueCurrentRound();
}
return;
}
// Handle human picks // Handle human picks
if (num == -9998 && llSubStringIndex(str, "HUMAN_PICKED:") == 0) { if (num == -9998 && llSubStringIndex(str, "HUMAN_PICKED:") == 0) {
@ -661,16 +697,19 @@ default {
return; // Ignore duplicate human picks return; // Ignore duplicate human picks
} }
// Validate picks against global picked numbers // Validate picks against complete avoidance list (all current picks from all players)
list completeAvoidList = buildCompleteAvoidanceList();
list newPicks = llParseString2List(picksStr, [","], []); list newPicks = llParseString2List(picksStr, [","], []);
list validPicks = []; list validPicks = [];
list duplicatePicks = []; list duplicatePicks = [];
integer i; integer i;
llOwnerSay("🔍 [Game Manager] Validating " + playerName + "'s picks against complete avoid list (" + (string)llGetListLength(completeAvoidList) + " numbers): " + llList2CSV(completeAvoidList));
for (i = 0; i < llGetListLength(newPicks); i++) { for (i = 0; i < llGetListLength(newPicks); i++) {
string pick = llStringTrim(llList2String(newPicks, i), STRING_TRIM); string pick = llStringTrim(llList2String(newPicks, i), STRING_TRIM);
if (pick != "") { if (pick != "") {
if (llListFindList(globalPickedNumbers, [pick]) == -1) { if (llListFindList(completeAvoidList, [pick]) == -1) {
validPicks += [pick]; validPicks += [pick];
globalPickedNumbers += [pick]; globalPickedNumbers += [pick];
} else { } else {
@ -705,7 +744,9 @@ default {
} }
integer picksNeeded = 4 - perilLives; integer picksNeeded = 4 - perilLives;
string dialogPayload = playerName + "|" + (string)diceType + "|" + (string)picksNeeded + "|" + llList2CSV(globalPickedNumbers); // Use complete avoid list for re-shown dialog too
list updatedCompleteAvoidList = buildCompleteAvoidanceList();
string dialogPayload = playerName + "|" + (string)diceType + "|" + (string)picksNeeded + "|" + llList2CSV(updatedCompleteAvoidList);
string dialogRequest = "SHOW_DIALOG|" + playerName + "|" + dialogPayload; string dialogRequest = "SHOW_DIALOG|" + playerName + "|" + dialogPayload;
llMessageLinked(LINK_SET, MSG_DIALOG_FORWARD_REQUEST, dialogRequest, NULL_KEY); llMessageLinked(LINK_SET, MSG_DIALOG_FORWARD_REQUEST, dialogRequest, NULL_KEY);
} }
@ -849,8 +890,11 @@ default {
} }
integer picksNeeded = 4 - perilLives; integer picksNeeded = 4 - perilLives;
// Send new bot command with updated avoid list // Send new bot command with complete updated avoid list
string botCommand = "BOT_PICK:" + playerName + ":" + (string)picksNeeded + ":" + (string)diceType + ":" + llList2CSV(globalPickedNumbers); list completeAvoidList = buildCompleteAvoidanceList();
string avoidListStr = llList2CSV(completeAvoidList);
string botCommand = "BOT_PICK:" + playerName + ":" + (string)picksNeeded + ":" + (string)diceType + ":" + avoidListStr;
llOwnerSay("🎯 [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, -9999, botCommand, NULL_KEY);
return; // Don't save this pick return; // Don't save this pick
} }
@ -946,6 +990,39 @@ default {
return; return;
} }
// Handle continue round requests from Main Controller
if (num == MSG_CONTINUE_ROUND) {
llOwnerSay("🎯 [Game Manager] Received continue round request with peril player: '" + str + "'");
// Update peril player from the continue message if provided
if (str != "" && str != "NONE") {
perilPlayer = str;
llOwnerSay("🎯 [Game Manager] Updated peril player to: " + perilPlayer);
// Reset round state and continue with existing peril player
roundStarted = FALSE;
currentPickerIdx = 0;
diceTypeProcessed = FALSE;
roundContinueInProgress = FALSE;
// Continue current round with assigned peril player
continueCurrentRound();
} else {
// Empty peril player means this is initial game start - use startNextRound()
llOwnerSay("🎯 [Game Manager] Empty peril player - starting initial game round");
// Reset round state
roundStarted = FALSE;
currentPickerIdx = 0;
diceTypeProcessed = FALSE;
roundContinueInProgress = FALSE;
// Start initial round (will select random peril player)
startNextRound();
}
return;
}
// Handle verbose logging toggle from Main Controller // Handle verbose logging toggle from Main Controller
if (num == 9011 && llSubStringIndex(str, "VERBOSE_LOGGING|") == 0) { if (num == 9011 && llSubStringIndex(str, "VERBOSE_LOGGING|") == 0) {
list parts = llParseString2List(str, ["|"], []); list parts = llParseString2List(str, ["|"], []);

View file

@ -16,6 +16,8 @@ integer MSG_GAME_STATUS = 3001;
integer MSG_PLAYER_UPDATE = 3002; integer MSG_PLAYER_UPDATE = 3002;
integer MSG_CLEAR_GAME = 3003; integer MSG_CLEAR_GAME = 3003;
integer MSG_REMOVE_PLAYER = 3004; integer MSG_REMOVE_PLAYER = 3004;
integer MSG_UPDATE_PERIL_PLAYER = 3005;
integer MSG_UPDATE_WINNER = 3006;
// Leaderboard messages (to link 35) // Leaderboard messages (to link 35)
integer MSG_GAME_WON = 3010; integer MSG_GAME_WON = 3010;
@ -63,6 +65,10 @@ list playerNames = []; // Current game players for display
list playerLives = []; // Current game player lives list playerLives = []; // Current game player lives
list playerProfiles = []; // Maps to profile texture UUIDs list playerProfiles = []; // Maps to profile texture UUIDs
// 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) // Leaderboard data (persistent - stored in linkset data)
list leaderboardNames = []; // Loaded from linkset data at startup list leaderboardNames = []; // Loaded from linkset data at startup
list leaderboardWins = []; // Loaded from linkset data at startup list leaderboardWins = []; // Loaded from linkset data at startup
@ -382,18 +388,20 @@ clearAllPlayers() {
return; // Don't modify prims outside our range! return; // Don't modify prims outside our range!
} }
// Reset profile prim to default // Reset profile prim to default and clear any glow
llSetLinkPrimitiveParamsFast(profilePrimIndex, [ llSetLinkPrimitiveParamsFast(profilePrimIndex, [
PRIM_TEXTURE, ALL_SIDES, TEXTURE_DEFAULT_PROFILE, <1,1,0>, <0,0,0>, 0.0, PRIM_TEXTURE, ALL_SIDES, TEXTURE_DEFAULT_PROFILE, <1,1,0>, <0,0,0>, 0.0,
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0,
PRIM_TEXT, "", <1,1,1>, 0.0 PRIM_TEXT, "", <1,1,1>, 0.0,
PRIM_GLOW, ALL_SIDES, 0.0 // Explicitly clear glow
]); ]);
// Reset hearts prim to 3 hearts // Reset hearts prim to 3 hearts and clear any glow
llSetLinkPrimitiveParamsFast(heartsPrimIndex, [ llSetLinkPrimitiveParamsFast(heartsPrimIndex, [
PRIM_TEXTURE, ALL_SIDES, TEXTURE_3_HEARTS, <1,1,0>, <0,0,0>, 0.0, PRIM_TEXTURE, ALL_SIDES, TEXTURE_3_HEARTS, <1,1,0>, <0,0,0>, 0.0,
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0,
PRIM_TEXT, "", <1,1,1>, 0.0 PRIM_TEXT, "", <1,1,1>, 0.0,
PRIM_GLOW, ALL_SIDES, 0.0 // Explicitly clear glow
]); ]);
// Reset overlay prim to transparent // Reset overlay prim to transparent
@ -521,6 +529,9 @@ refreshPlayerDisplay() {
]); ]);
} }
} }
// Update player glow effects after refreshing all players
updatePlayerGlowEffects();
} }
// Check if a player name indicates it's a bot // Check if a player name indicates it's a bot
@ -597,6 +608,84 @@ string getHeartTexture(integer lives) {
else return TEXTURE_3_HEARTS; // 3 or more else return TEXTURE_3_HEARTS; // 3 or more
} }
// Update all glow effects (peril and winner)
updatePlayerGlowEffects() {
// First, remove glow and reset tint from all players
integer i;
for (i = 0; i < llGetListLength(playerNames); i++) {
integer profilePrimIndex = getProfilePrimLink(i);
integer heartsPrimIndex = getHeartsPrimLink(i);
// SAFETY CHECK
if (profilePrimIndex >= FIRST_PROFILE_PRIM && profilePrimIndex <= LAST_PROFILE_PRIM) {
llSetLinkPrimitiveParamsFast(profilePrimIndex, [
PRIM_GLOW, ALL_SIDES, 0.0,
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0
]);
llSetLinkPrimitiveParamsFast(heartsPrimIndex, [
PRIM_GLOW, ALL_SIDES, 0.0,
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0
]);
}
}
// Priority 1: Green glow for winner (overrides peril glow)
if (currentWinner != "" && currentWinner != "NONE") {
integer winnerIndex = llListFindList(playerNames, [currentWinner]);
if (winnerIndex != -1) {
integer profilePrimIndex = getProfilePrimLink(winnerIndex);
integer heartsPrimIndex = getHeartsPrimLink(winnerIndex);
// SAFETY CHECK
if (profilePrimIndex >= FIRST_PROFILE_PRIM && profilePrimIndex <= LAST_PROFILE_PRIM) {
// Add green glow + green tint to winner
llSetLinkPrimitiveParamsFast(profilePrimIndex, [
PRIM_GLOW, ALL_SIDES, 0.3,
PRIM_COLOR, ALL_SIDES, <0.0, 1.0, 0.0>, 1.0
]);
llSetLinkPrimitiveParamsFast(heartsPrimIndex, [
PRIM_GLOW, ALL_SIDES, 0.3,
PRIM_COLOR, ALL_SIDES, <0.0, 1.0, 0.0>, 1.0
]);
if (VERBOSE_LOGGING) {
llOwnerSay("🏆 Added green glow to winner: " + currentWinner);
}
}
}
}
// 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 profilePrimIndex = getProfilePrimLink(perilIndex);
integer heartsPrimIndex = getHeartsPrimLink(perilIndex);
// SAFETY CHECK
if (profilePrimIndex >= FIRST_PROFILE_PRIM && profilePrimIndex <= LAST_PROFILE_PRIM) {
// Add yellow glow + yellow tint to peril player
llSetLinkPrimitiveParamsFast(profilePrimIndex, [
PRIM_GLOW, ALL_SIDES, 0.2,
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 0.0>, 1.0
]);
llSetLinkPrimitiveParamsFast(heartsPrimIndex, [
PRIM_GLOW, ALL_SIDES, 0.2,
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 0.0>, 1.0
]);
if (VERBOSE_LOGGING) {
llOwnerSay("✨ Added yellow glow to peril player: " + currentPerilPlayer);
}
}
}
}
}
// Backward compatibility wrapper
updatePerilPlayerGlow() {
updatePlayerGlowEffects();
}
// Update player display on the grid // Update player display on the grid
updatePlayerDisplay(string playerName, integer lives, string profileUUID) { updatePlayerDisplay(string playerName, integer lives, string profileUUID) {
// Find existing player or add new one // Find existing player or add new one
@ -791,6 +880,9 @@ updatePlayerDisplay(string playerName, integer lives, string profileUUID) {
PRIM_TEXT, "", <1,1,1>, 0.0 PRIM_TEXT, "", <1,1,1>, 0.0
]); ]);
} }
// Update player glow effects after display changes
updatePlayerGlowEffects();
} }
default { default {
@ -817,6 +909,11 @@ default {
// Generate initial leaderboard // Generate initial leaderboard
generateLeaderboardText(); generateLeaderboardText();
// Ensure no glow effects remain from previous sessions
currentPerilPlayer = "";
currentWinner = "";
updatePlayerGlowEffects();
if (VERBOSE_LOGGING) { if (VERBOSE_LOGGING) {
llOwnerSay("✅ Linkset communication active - no discovery needed!"); llOwnerSay("✅ Linkset communication active - no discovery needed!");
llOwnerSay("📊 Loaded " + (string)llGetListLength(leaderboardNames) + " players from leaderboard data"); llOwnerSay("📊 Loaded " + (string)llGetListLength(leaderboardNames) + " players from leaderboard data");
@ -863,6 +960,11 @@ default {
clearAllPlayers(); // Clear all current players clearAllPlayers(); // Clear all current players
updateActionsPrim("Title"); // Reset to title updateActionsPrim("Title"); // Reset to title
// Clear peril player and winner tracking and remove any remaining glow
currentPerilPlayer = "";
currentWinner = "";
updatePlayerGlowEffects(); // This will remove glow from all players
generateLeaderboardText(); // Refresh leaderboard display generateLeaderboardText(); // Refresh leaderboard display
} }
else if (num == MSG_GAME_WON) { else if (num == MSG_GAME_WON) {
@ -877,6 +979,36 @@ default {
else if (num == MSG_REMOVE_PLAYER) { else if (num == MSG_REMOVE_PLAYER) {
removePlayer(str); removePlayer(str);
} }
else if (num == MSG_UPDATE_PERIL_PLAYER) {
// Update peril player and refresh glow effects
string oldPeril = currentPerilPlayer;
currentPerilPlayer = str;
if (currentPerilPlayer == "NONE") {
currentPerilPlayer = "";
}
if (VERBOSE_LOGGING && oldPeril != currentPerilPlayer) {
llOwnerSay("🎯 Peril player changed from '" + oldPeril + "' to '" + currentPerilPlayer + "'");
}
// Update glow effects
updatePlayerGlowEffects();
}
else if (num == MSG_UPDATE_WINNER) {
// Update winner and refresh glow effects
string oldWinner = currentWinner;
currentWinner = str;
if (currentWinner == "NONE") {
currentWinner = "";
}
if (VERBOSE_LOGGING && oldWinner != currentWinner) {
llOwnerSay("🏆 Winner changed from '" + oldWinner + "' to '" + currentWinner + "'");
}
// Update glow effects - winner glow overrides peril glow
updatePlayerGlowEffects();
}
} }
http_response(key request_id, integer status, list metadata, string body) { http_response(key request_id, integer status, list metadata, string body) {

View file

@ -1,69 +0,0 @@
# EMERGENCY FIX: Infinite Loop Resolution
## Problem
The delegation system created an infinite loop between Main_Controller_Linkset.lsl and Player_RegistrationManager.lsl:
1. Main Controller's `touch_start` sends `MSG_REGISTER_PLAYER`
2. Player_RegistrationManager receives `MSG_REGISTER_PLAYER` and processes it
3. BUT Main Controller's `link_message` handler ALSO receives `MSG_REGISTER_PLAYER`
4. Main Controller forwards it AGAIN to Player_RegistrationManager
5. INFINITE LOOP 💀
## Root Cause
Both scripts were using the same message constant `MSG_REGISTER_PLAYER = 106`, causing message collision.
## Solution Applied
### 1. New Dedicated Message Constant
```lsl
integer MSG_REGISTER_PLAYER_REQUEST = 9050; // Dedicated message to Player_RegistrationManager
```
### 2. Updated Message Flow
- **Main Controller `touch_start`**: Sends `MSG_REGISTER_PLAYER_REQUEST` (new)
- **Main Controller `link_message`**: Converts old `MSG_REGISTER_PLAYER` to `MSG_REGISTER_PLAYER_REQUEST` for backward compatibility
- **Player_RegistrationManager**: Only listens for `MSG_REGISTER_PLAYER_REQUEST`
### 3. Files Modified
#### Main_Controller_Linkset.lsl
- **Added**: `MSG_REGISTER_PLAYER_REQUEST = 9050`
- **Changed**: `touch_start` uses `MSG_REGISTER_PLAYER_REQUEST` instead of `MSG_REGISTER_PLAYER`
- **Changed**: `link_message` converts old messages to new format
- **Changed**: "Add Test Player" admin function uses new message
#### Player_RegistrationManager.lsl
- **Changed**: `MSG_REGISTER_PLAYER = 106` → `MSG_REGISTER_PLAYER_REQUEST = 9050`
- **Changed**: `link_message` handler listens for `MSG_REGISTER_PLAYER_REQUEST`
## Message Flow Diagram
### BEFORE (Infinite Loop)
```
Touch → Main Controller → MSG_REGISTER_PLAYER → Player_RegistrationManager
↓
link_message handler
↓
MSG_REGISTER_PLAYER → Player_RegistrationManager (LOOP!)
```
### AFTER (Fixed)
```
Touch → Main Controller → MSG_REGISTER_PLAYER_REQUEST → Player_RegistrationManager
↓
link_message handler (converts old messages only)
↓
MSG_REGISTER_PLAYER → MSG_REGISTER_PLAYER_REQUEST → Player_RegistrationManager
```
## Backward Compatibility
The Main Controller still accepts old `MSG_REGISTER_PLAYER` messages from other scripts and converts them to the new format, ensuring no functionality is lost.
## Testing
- ✅ Single player registration should work without loops
- ✅ Bot addition via admin menu should work
- ✅ Multiple rapid registrations should not cause loops
- ✅ Existing functionality preserved
## Status: EMERGENCY FIX APPLIED ⚡
The infinite loop has been eliminated while preserving all functionality and maintaining the memory optimization benefits.

View file

@ -2,18 +2,20 @@ PERIL DICE - Professional Single Linkset Game System for Second Life
Created by Rebecca Nod and Noose the Bunny Created by Rebecca Nod and Noose the Bunny
CURRENT VERSION: 2.8.2 - Scoreboard Spam & Display Fixes CURRENT VERSION: 2.8.6 - Winner Glow Celebration System
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! 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 v2.8.2: Fixed critical scoreboard spam bug, re-fixed peril status display on floating HUDs, enhanced elimination sequence to show 0 hearts before player removal. NEW IN v2.8.6: Revolutionary winner recognition with bright green glow effects on both scoreboard and floaters, plus 24-second victory celebration timer giving proper recognition to the Ultimate Survivor before game reset.
KEY FEATURES: KEY FEATURES:
- 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 - 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 - 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 - Comprehensive Leaderboard: Persistent win tracking with XyzzyText display
- Bot Support: AI players for testing and gameplay variety - Bot Support: AI players for testing and gameplay variety
- Floating HUD Display: Real-time stats for each player - Floating HUD Display: Real-time stats for each player with victory text
- Intelligent Dice Sizing: d6/d12/d20/d30 based on player count - Intelligent Dice Sizing: d6/d12/d20/d30 based on player count
- Security Features: Owner lockout system, kick player functionality - Security Features: Owner lockout system, kick player functionality
- Single Linkset: Complete 74-prim system for easy deployment - Single Linkset: Complete 74-prim system for easy deployment
@ -52,6 +54,10 @@ EXPERIENCE: None required
Perfect for nightclubs, venues, casinos, private parties, group entertainment, community gatherings. Perfect for nightclubs, venues, casinos, private parties, group entertainment, community gatherings.
VERSION HISTORY: VERSION HISTORY:
v2.8.6: Winner Glow Celebration System with 24-second victory recognition
v2.8.5: Memory optimization and bot profile picture fixes
v2.8.4: Disconnect recovery system and verbose logging
v2.8.3: Shield detection fixes and initialization overhaul
v2.8.2: Scoreboard spam fix, display improvements v2.8.2: Scoreboard spam fix, display improvements
v2.8.1: Peril status and elimination heart fixes v2.8.1: Peril status and elimination heart fixes
v2.8.0: Game lockout security, automatic reset v2.8.0: Game lockout security, automatic reset
@ -60,6 +66,6 @@ v2.7.0: Single linkset architecture revolution
ORIGINAL CONCEPT: Noose the Bunny (djmusica28) ORIGINAL CONCEPT: Noose the Bunny (djmusica28)
AUTOMATED SYSTEM: Enhanced for professional gameplay AUTOMATED SYSTEM: Enhanced for professional gameplay
Transform your space into an exciting gaming destination! Transform your space into an exciting gaming destination with professional winner celebration effects!
Keywords: dice game, elimination game, multiplayer, party game, club game, casino, entertainment, social game, LSL script, venue game Keywords: dice game, elimination game, multiplayer, party game, club game, casino, entertainment, social game, LSL script, venue game

View file

@ -1,110 +0,0 @@
# Memory Optimization Summary - Peril Dice Game v2.8.2
## Issue Description
The Main Controller script was experiencing stack-heap collisions during player registration, specifically when new players joined the game. This was causing script failures and preventing proper player registration.
## Root Cause Analysis
The player registration process in `Main_Controller_Linkset.lsl` was performing memory-intensive operations:
1. **Heavy string concatenations** for status messages and announcements
2. **Large list manipulations** during player addition
3. **Complex bot vs human logic processing**
4. **Immediate floater channel calculations**
5. **Multiple helper update calls** during registration
All of these operations happened simultaneously in the main controller's memory space, causing the stack and heap to collide.
## Solution Implemented
**Delegation Pattern**: Moved the memory-intensive player registration logic to a dedicated helper script.
### Changes Made
#### 1. Created New Script: `Player_RegistrationManager.lsl`
- **Purpose**: Handle all player registration operations
- **Memory Benefits**: Isolates heavy string operations from main controller
- **Responsibilities**:
- Validate game state restrictions (game started, max players)
- Handle bot vs human player logic
- Manage floater channel assignments
- Send appropriate announcements
- Update main controller with final results
#### 2. Modified `Main_Controller_Linkset.lsl`
- **Removed**: Entire player registration logic block (lines 617-682 in original)
- **Added**: Simple delegation call that forwards registration requests
- **Added**: Handler for registration updates from the dedicated manager
- **Memory Savings**: ~65 lines of complex string manipulation removed
### Technical Implementation
#### Communication Protocol
```lsl
// Main Controller forwards registration requests
if (num == MSG_REGISTER_PLAYER) {
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, str, id);
return;
}
// Registration Manager sends updates back
if (num == 9040) { // MSG_UPDATE_MAIN_LISTS
// Update main controller lists from registration manager
list parts = llParseString2List(str, ["~"], []);
if (llGetListLength(parts) >= 6) {
players = llCSV2List(llList2String(parts, 0));
names = llCSV2List(llList2String(parts, 1));
lives = llCSV2List(llList2String(parts, 2));
readyPlayers = llCSV2List(llList2String(parts, 3));
// ... process update ...
}
}
```
#### Data Flow
1. Player touches object → Main Controller
2. Main Controller forwards `MSG_REGISTER_PLAYER` → Registration Manager
3. Registration Manager processes all complex logic in its own memory space
4. Registration Manager sends `MSG_UPDATE_MAIN_LISTS` → Main Controller
5. Main Controller updates its lists with final results
6. Main Controller calls `updateHelpers()` to sync other scripts
## Expected Benefits
### Memory Usage
- **Main Controller**: Reduced peak memory usage during player join by ~30-40%
- **Registration Manager**: Handles heavy operations in isolated memory space
- **Overall System**: Better memory distribution across multiple scripts
### Performance
- **Faster Registration**: Less memory pressure = faster processing
- **Reduced Failures**: Eliminates stack-heap collision during joins
- **Better Stability**: Main controller maintains lower baseline memory usage
### Maintainability
- **Separation of Concerns**: Registration logic isolated from main game logic
- **Easier Debugging**: Registration issues can be traced to dedicated script
- **Modular Design**: Can optimize registration logic without affecting main controller
## Testing Recommendations
1. **Basic Registration**: Test single player joins
2. **Rapid Registration**: Test multiple players joining quickly
3. **Bot Registration**: Test bot addition via admin menu
4. **Memory Monitoring**: Use "Memory Stats" admin option to verify improvements
5. **Edge Cases**: Test max players, game started restrictions
## File Changes Summary
### Modified Files
- `Main_Controller_Linkset.lsl` - Delegated player registration, reduced memory usage
### New Files
- `Player_RegistrationManager.lsl` - Dedicated player registration handler
### Architecture Impact
- **Single Linkset Design**: Maintained - no external communication required
- **Link Message Protocol**: Extended with new message type (9040)
- **Script Responsibilities**: Better distributed across helper scripts
- **Memory Architecture**: More balanced memory usage across scripts
## Version Notes
This optimization maintains full backward compatibility and preserves all existing functionality while significantly reducing memory pressure in the main controller script.

View file

@ -30,6 +30,8 @@ integer MSG_GAME_STATUS = 3001;
integer MSG_PLAYER_UPDATE = 3002; integer MSG_PLAYER_UPDATE = 3002;
integer MSG_CLEAR_GAME = 3003; integer MSG_CLEAR_GAME = 3003;
integer MSG_REMOVE_PLAYER = 3004; integer MSG_REMOVE_PLAYER = 3004;
integer MSG_UPDATE_PERIL_PLAYER = 3005;
integer MSG_UPDATE_WINNER = 3006;
// Leaderboard messages (to link 35) // Leaderboard messages (to link 35)
integer MSG_GAME_WON = 3010; integer MSG_GAME_WON = 3010;
@ -73,10 +75,10 @@ integer MSG_MEMORY_STATS_REQUEST = 6006;
// Unified Timer System - prevents conflicts between multiple timer needs // Unified Timer System - prevents conflicts between multiple timer needs
integer TIMER_IDLE = 0; integer TIMER_IDLE = 0;
integer TIMER_STATUS = 1; integer TIMER_STATUS = 1;
integer TIMER_TIMEOUT = 2; integer TIMER_VICTORY_DELAY = 2; // Timer mode for victory glow display
integer TIMER_DISCOVERY = 3;
integer currentTimerMode = 0; // Track what the timer is currently doing integer currentTimerMode = 0; // Track what the timer is currently doing
float timerInterval = 1.0; // How often timer() is called for checks float timerInterval = 1.0; // How often timer() is called for checks
integer victoryDelayTimer = 0; // Track victory delay timing
// Debug control - set to TRUE for verbose pick debugging, FALSE for normal operation // Debug control - set to TRUE for verbose pick debugging, FALSE for normal operation
integer DEBUG_PICKS = FALSE; integer DEBUG_PICKS = FALSE;
@ -151,12 +153,7 @@ string TEXTURE_GAME_ACTIVE = "game_active"; // Optional: different texture duri
// Maximum number of players allowed (including test players) // Maximum number of players allowed (including test players)
integer MAX_PLAYERS = 10; integer MAX_PLAYERS = 10;
integer TIMEOUT_SECONDS = 120; // 2 minute timeout // Timeout system removed - using owner kick functionality instead
integer timeoutTimer;
integer warning30sec = 30;
integer warning1min = 60;
integer warning90sec = 90;
integer lastWarning = 0;
key currentPicker; key currentPicker;
@ -317,20 +314,14 @@ updateFloatingText() {
resetGame() { resetGame() {
cleanupListeners(); cleanupListeners();
// Only clean up channels that actually have active floaters // Let Floater Manager handle cleanup intelligently
integer i; // Only request cleanup if we actually have game data that might have floaters
for (i = 0; i < llGetListLength(floaterChannels); i++) { if (llGetListLength(names) > 0 || llGetListLength(floaterChannels) > 0) {
integer ch = llList2Integer(floaterChannels, i); llMessageLinked(LINK_SET, MSG_CLEANUP_ALL_FLOATERS, "RESET", NULL_KEY);
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY); llSleep(1.0);
} }
// Also send a few cleanup messages to common channel ranges in case of orphaned floaters llSleep(0.5);
for (i = 0; i < llGetListLength(names); i++) {
integer ch = FLOATER_BASE_CHANNEL + i;
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
}
llSleep(2.0);
players = names = lives = picksData = globalPickedNumbers = readyPlayers = []; players = names = lives = picksData = globalPickedNumbers = readyPlayers = [];
floaterChannels = []; floaterChannels = [];
@ -342,14 +333,14 @@ resetGame() {
gameStarting = FALSE; gameStarting = FALSE;
victoryInProgress = FALSE; victoryInProgress = FALSE;
currentPicker = NULL_KEY; currentPicker = NULL_KEY;
timeoutTimer = 0;
lastWarning = 0;
llMessageLinked(LINK_SET, -99999, "FULL_RESET", NULL_KEY); llMessageLinked(LINK_SET, -99999, "FULL_RESET", NULL_KEY);
llSay(syncChannel, "RESET"); llSay(syncChannel, "RESET");
// CHANGED: Use link messages instead of llRegionSay for display cleanup // CHANGED: Use link messages instead of llRegionSay for display cleanup
llMessageLinked(SCOREBOARD_LINK, MSG_CLEAR_GAME, "", NULL_KEY); llMessageLinked(SCOREBOARD_LINK, 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(DICE_LINK, MSG_CLEAR_DICE, "", NULL_KEY);
llOwnerSay("🎮 Game reset! All state cleared (including scoreboard)."); llOwnerSay("🎮 Game reset! All state cleared (including scoreboard).");
@ -484,6 +475,7 @@ default {
} }
if (toucher == llGetOwner()) { if (toucher == llGetOwner()) {
llOwnerSay("🔍 [DEBUG] Owner touched - idx: " + (string)idx + ", players list: " + llList2CSV(players));
if (idx == -1) { if (idx == -1) {
// Owner is not in game - show join/admin choice // Owner is not in game - show join/admin choice
if (llListFindList(pendingRegistrations, [toucher]) != -1) { if (llListFindList(pendingRegistrations, [toucher]) != -1) {
@ -491,10 +483,14 @@ default {
return; return;
} }
// Show owner choice menu: join game or access admin functions // Owner is not in game - determine menu type based on player count
list options = ["Join Game", "Owner Menu"]; if (llGetListLength(players) >= 2) {
string menuText = "👑 Owner Options:\n\n🎮 Join Game - Register as a player\n🔧 Owner Menu - Admin functions without joining"; // Enough players for game - show starter menu
llDialog(toucher, menuText, options, DIALOG_CHANNEL); llMessageLinked(LINK_SET, MSG_SHOW_MENU, "unregistered_owner_starter|1", toucher);
} else {
// Not enough players - show basic menu
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "unregistered_owner|0", toucher);
}
} else { } else {
// Owner is already in game - show player menu with appropriate starter status // Owner is already in game - show player menu with appropriate starter status
integer isStarter = TRUE; integer isStarter = TRUE;
@ -608,53 +604,112 @@ default {
// Send ONLY a direct scoreboard update to show 0 hearts // Send ONLY a direct scoreboard update to show 0 hearts
llMessageLinked(SCOREBOARD_LINK, MSG_PLAYER_UPDATE, llMessageLinked(SCOREBOARD_LINK, MSG_PLAYER_UPDATE,
eliminatedPlayer + "|0|" + (string)llList2Key(players, idx), NULL_KEY); eliminatedPlayer + "|0|" + (string)llList2Key(players, idx), NULL_KEY);
llSleep(1.5); // Give time for 0 hearts display
// Check if this will cause victory BEFORE removing player // CRITICAL: Check for victory condition BEFORE assigning new peril player
integer willCauseVictory = (llGetListLength(names) <= 2); // After removing one player integer willCauseVictory = (llGetListLength(names) <= 2); // After removing one player
string potentialWinner = "";
if (willCauseVictory) {
// Find the winner (the remaining non-eliminated player)
integer w;
for (w = 0; w < llGetListLength(names); w++) {
string candidateName = llList2String(names, w);
if (candidateName != eliminatedPlayer) {
potentialWinner = candidateName;
jump found_winner;
}
}
@found_winner;
if (potentialWinner != "") {
// SEND WINNER GLOW to scoreboard and trigger floater update
llMessageLinked(SCOREBOARD_LINK, MSG_UPDATE_WINNER, potentialWinner, NULL_KEY);
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, potentialWinner, NULL_KEY); // Update winner's floater
llOwnerSay("🏆 [Main Controller] Pre-victory winner glow sent for: " + potentialWinner);
}
}
// Only assign new peril player if NOT causing victory
if (!willCauseVictory && (perilPlayer == eliminatedPlayer || perilPlayer == "")) {
// Find first remaining alive player to be new peril player (before removing eliminated player)
string newPerilPlayer = "";
integer k;
for (k = 0; k < llGetListLength(names) && newPerilPlayer == ""; k++) {
string candidateName = llList2String(names, k);
if (candidateName != eliminatedPlayer) { // Skip the player being eliminated
integer candidateIdx = llListFindList(names, [candidateName]);
if (candidateIdx != -1 && candidateIdx < llGetListLength(lives)) {
integer candidateLives = llList2Integer(lives, candidateIdx);
if (candidateLives > 0) {
newPerilPlayer = candidateName;
}
}
}
}
if (newPerilPlayer != "") {
perilPlayer = newPerilPlayer;
// Send peril player update to scoreboard for glow effect
llMessageLinked(SCOREBOARD_LINK, 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);
}
}
// CRITICAL: Send sync message with 0 lives so floater shows elimination state
// Build sync message BEFORE any list modifications to prevent corruption
string livesStr = llList2CSV(lives);
string picksStr = llDumpList2String(picksData, "^");
if (picksStr == "") picksStr = "EMPTY"; // Prevent empty string
string perilStr = perilPlayer;
if (perilStr == "") perilStr = "NONE"; // Prevent empty string
string namesStr = llList2CSV(names);
string playersStr = llList2CSV(players);
string eliminationSync = livesStr + "~" + picksStr + "~" + perilStr + "~" + namesStr + "~" + playersStr;
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, eliminationSync, NULL_KEY);
// Direct floater update to ensure eliminated status is displayed
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, eliminatedPlayer, llList2Key(players, idx));
llSleep(2.0); // Give floater time to show eliminated status with red display
if (willCauseVictory) { if (willCauseVictory) {
// If this elimination will cause victory, give extra time for 0 hearts display // If this elimination will cause victory, give extra time for 0 hearts display
llSleep(1.5); // Additional time for victory scenarios llSleep(1.5); // Additional time for victory scenarios
} }
// THEN: Clean up and remove the player // CHANGED: Don't cleanup floaters during elimination - let them show eliminated status
integer ch = llList2Integer(floaterChannels, idx); // The floater will show red glow + "ELIMINATED!" and stay visible until game end
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY); // integer ch = llList2Integer(floaterChannels, idx);
llSleep(0.3); // Give floater time to cleanup // llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
// llSleep(0.3); // Give floater time to cleanup
// CRITICAL: Remove eliminated player's picks data by searching for their entry
// NOT by index, since picksData format is "PlayerName|picks" strings
integer pickIdx = -1;
integer p;
for (p = 0; p < llGetListLength(picksData); p++) {
string entry = llList2String(picksData, p);
if (llSubStringIndex(entry, eliminatedPlayer + "|") == 0) {
pickIdx = p;
jump found_pick_entry;
}
}
@found_pick_entry;
if (pickIdx != -1) {
picksData = llDeleteSubList(picksData, pickIdx, pickIdx);
}
// Now remove from indexed lists (players, names, lives, floaterChannels)
players = llDeleteSubList(players, idx, idx); players = llDeleteSubList(players, idx, idx);
names = llDeleteSubList(names, idx, idx); names = llDeleteSubList(names, idx, idx);
lives = llDeleteSubList(lives, idx, idx); lives = llDeleteSubList(lives, idx, idx);
picksData = llDeleteSubList(picksData, idx, idx);
floaterChannels = llDeleteSubList(floaterChannels, idx, idx); floaterChannels = llDeleteSubList(floaterChannels, idx, idx);
// CRITICAL: Update perilPlayer BEFORE any updateHelpers() calls // Peril player assignment already handled before sync message was sent
// This prevents sending stale sync messages with eliminated players
if (perilPlayer == eliminatedPlayer) {
// Find first remaining alive player to be new peril player
string newPerilPlayer = "";
integer k;
for (k = 0; k < llGetListLength(names) && newPerilPlayer == ""; k++) {
string candidateName = llList2String(names, k);
integer candidateIdx = llListFindList(names, [candidateName]);
if (candidateIdx != -1 && candidateIdx < llGetListLength(lives)) {
integer candidateLives = llList2Integer(lives, candidateIdx);
if (candidateLives > 0) {
newPerilPlayer = candidateName;
}
}
}
if (newPerilPlayer != "") {
llOwnerSay("🎯 [Main Controller] Peril player eliminated - assigning new peril player: " + newPerilPlayer);
perilPlayer = newPerilPlayer;
} else {
llOwnerSay("⚠️ [Main Controller] No valid peril player candidates found after elimination!");
perilPlayer = "NONE";
}
}
// CHANGED: Send to scoreboard, which will handle leaderboard updates // CHANGED: Send to scoreboard, which will handle leaderboard updates
llMessageLinked(SCOREBOARD_LINK, MSG_GAME_LOST, eliminatedPlayer, NULL_KEY); llMessageLinked(SCOREBOARD_LINK, MSG_GAME_LOST, eliminatedPlayer, NULL_KEY);
@ -666,6 +721,11 @@ default {
if (llGetListLength(names) == 1) { if (llGetListLength(names) == 1) {
string winner = llList2String(names, 0); string winner = llList2String(names, 0);
// ENHANCED: Give eliminated player's floater time to show eliminated status
// before declaring victory and cleaning up
llOwnerSay("⏳ [Main Controller] Final elimination detected - allowing display time before victory");
llSleep(2.0); // Give final eliminated player time to show red eliminated status
// CRITICAL: Set victory flag immediately to prevent sync processing // CRITICAL: Set victory flag immediately to prevent sync processing
victoryInProgress = TRUE; victoryInProgress = TRUE;
@ -687,10 +747,19 @@ default {
llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY); llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY);
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY); llMessageLinked(LINK_SET, 995, "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_SET, MSG_UPDATE_FLOAT, winner, NULL_KEY); // Update winner's floater
// CHANGED: Send to scoreboard, which will handle leaderboard updates // CHANGED: Send to scoreboard, which will handle leaderboard updates
llMessageLinked(SCOREBOARD_LINK, MSG_GAME_WON, winner, NULL_KEY); llMessageLinked(SCOREBOARD_LINK, MSG_GAME_WON, winner, NULL_KEY);
llSleep(STATUS_DISPLAY_TIME * 2.0); // Combined delay // Use timer instead of llSleep to avoid immediate reset
llOwnerSay("🏆 [Main Controller] Winner glow applied - starting victory delay timer");
victoryDelayTimer = llGetUnixTime();
currentTimerMode = TIMER_VICTORY_DELAY;
llSetTimerEvent(1.0); // Check every second
return; // Exit here - timer will handle reset
} else { } else {
// CRITICAL: Set victory flag and clear state for no survivors scenario too // CRITICAL: Set victory flag and clear state for no survivors scenario too
victoryInProgress = TRUE; victoryInProgress = TRUE;
@ -706,10 +775,25 @@ default {
currentPicker = NULL_KEY; currentPicker = NULL_KEY;
llSay(0, "💀 DESPAIR WINS! No Ultimate Survivors remain!"); llSay(0, "💀 DESPAIR WINS! No Ultimate Survivors remain!");
llSleep(STATUS_DISPLAY_TIME + 1.0);
// Use timer for no survivors scenario too
victoryDelayTimer = llGetUnixTime();
currentTimerMode = TIMER_VICTORY_DELAY;
llSetTimerEvent(1.0);
return;
} }
resetGame(); }
return;
// CRITICAL: After elimination, sync state and continue game with new peril player
if (perilPlayer != "" && perilPlayer != "NONE") {
// Sync the updated game state to all modules (5-part format)
string picksDataStr = "EMPTY"; // Empty picks for new round
string gameSync = llList2CSV(lives) + "~" + picksDataStr + "~" + perilPlayer + "~" + llList2CSV(names) + "~" + llList2CSV(players);
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, gameSync, NULL_KEY);
llSleep(0.5); // Give sync time to propagate
// Continue the game with the new peril player - delegate to Game Manager
llMessageLinked(LINK_SET, 998, perilPlayer, NULL_KEY);
} }
// MINIMAL: Only send direct scoreboard update for remaining players // MINIMAL: Only send direct scoreboard update for remaining players
@ -770,21 +854,7 @@ default {
return; return;
} }
// Handle start next round request - delegate to Game Manager // Legacy 997 message handler removed - now using direct communication to Game Manager
if (num == 997) {
if (sender == llGetLinkNumber()) {
return;
}
if (roundStarted && str == "START_NEXT_ROUND") {
llOwnerSay("⚠️ [Main Controller] Round already in progress, ignoring START_NEXT_ROUND");
return;
}
llOwnerSay("🔄 [Main Controller] Starting new round - Game Manager will handle dice type");
llMessageLinked(LINK_SET, num, str, id);
return;
}
// Handle ready state toggle // Handle ready state toggle
if (num == MSG_TOGGLE_READY) { if (num == MSG_TOGGLE_READY) {
@ -867,18 +937,7 @@ default {
return; return;
} }
// Handle aggressive floater cleanup // MSG_CLEANUP_ALL_FLOATERS is handled by Floater Manager only
if (num == MSG_CLEANUP_ALL_FLOATERS) {
integer i;
for (i = 0; i < MAX_PLAYERS; i++) {
integer ch = FLOATER_BASE_CHANNEL + i;
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)(ch + 100), NULL_KEY);
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)(ch + 1000), NULL_KEY);
}
floaterChannels = [];
return;
}
// Handle leave game requests processed by Controller_MessageHandler // Handle leave game requests processed by Controller_MessageHandler
if (num == 8006) { // MSG_LEAVE_GAME_REQUEST if (num == 8006) { // MSG_LEAVE_GAME_REQUEST
@ -919,9 +978,13 @@ default {
if (newPerilPlayer != "") { if (newPerilPlayer != "") {
llOwnerSay("🎯 [Main Controller] Peril player left - assigning new peril player: " + newPerilPlayer); llOwnerSay("🎯 [Main Controller] Peril player left - assigning new peril player: " + newPerilPlayer);
perilPlayer = newPerilPlayer; perilPlayer = newPerilPlayer;
// Send peril player update to scoreboard for glow effect
llMessageLinked(SCOREBOARD_LINK, 3005, newPerilPlayer, NULL_KEY); // MSG_UPDATE_PERIL_PLAYER
} else { } else {
llOwnerSay("⚠️ [Main Controller] No valid peril player candidates found after player left!"); llOwnerSay("⚠️ [Main Controller] No valid peril player candidates found after player left!");
perilPlayer = "NONE"; perilPlayer = "NONE";
// Clear peril player glow on scoreboard
llMessageLinked(SCOREBOARD_LINK, MSG_UPDATE_PERIL_PLAYER, "", NULL_KEY);
} }
} }
@ -1023,7 +1086,7 @@ default {
} }
llSay(0, "⚡ THE KILLING GAME CONTINUES! " + perilPlayer + " begins the next deadly round!"); llSay(0, "⚡ THE KILLING GAME CONTINUES! " + perilPlayer + " begins the next deadly round!");
llMessageLinked(LINK_SET, 997, "START_NEXT_ROUND", NULL_KEY); llMessageLinked(LINK_SET, 998, perilPlayer, NULL_KEY);
return; return;
} }
} }
@ -1087,21 +1150,23 @@ default {
} }
// Create shorter bot names to avoid dialog button length issues // Create shorter bot names to avoid dialog button length issues
integer botNum = llGetListLength(players) + 1; // Count existing bots to determine next bot number
integer existingBots = 0;
integer i;
for (i = 0; i < llGetListLength(names); i++) {
string playerName = llList2String(names, i);
if (llSubStringIndex(playerName, "Bot") == 0) {
existingBots++;
}
}
integer botNum = existingBots + 1;
string testName = "Bot" + (string)botNum; string testName = "Bot" + (string)botNum;
key fake = llGenerateKey(); key fake = llGenerateKey();
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER_REQUEST, testName + "|" + (string)fake, NULL_KEY); llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER_REQUEST, testName + "|" + (string)fake, NULL_KEY);
integer ownerIdx = llListFindList(players, [llGetOwner()]); // Don't show menu immediately - let the player touch the board again
integer ownerIsStarter = TRUE; // This avoids timing issues where the menu is shown before registration completes
integer n; llOwnerSay("✅ " + testName + " registration sent - touch the board again to refresh menu");
for (n = 0; n < ownerIdx && ownerIsStarter; n++) {
string existingName = llList2String(names, n);
if (llSubStringIndex(existingName, "Bot") != 0) {
ownerIsStarter = FALSE;
}
}
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)ownerIsStarter, llGetOwner());
return; return;
} }
if (msg == "Force Floaters") { if (msg == "Force Floaters") {
@ -1115,16 +1180,27 @@ default {
} }
llOwnerSay("✅ Floater creation requests sent for all players!"); llOwnerSay("✅ Floater creation requests sent for all players!");
// After forcing floaters, show appropriate menu based on owner registration status
integer ownerIdx = llListFindList(players, [llGetOwner()]); integer ownerIdx = llListFindList(players, [llGetOwner()]);
integer ownerIsStarter = TRUE; if (ownerIdx != -1) {
integer n; // Owner is registered - show registered owner menu
for (n = 0; n < ownerIdx && ownerIsStarter; n++) { integer ownerIsStarter = TRUE;
string existingName = llList2String(names, n); integer n;
if (llSubStringIndex(existingName, "Bot") != 0) { for (n = 0; n < ownerIdx && ownerIsStarter; n++) {
ownerIsStarter = FALSE; string existingName = llList2String(names, n);
if (llSubStringIndex(existingName, "Bot") != 0) {
ownerIsStarter = FALSE;
}
}
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)ownerIsStarter, llGetOwner());
} else {
// Owner is not registered - show unregistered owner menu
if (llGetListLength(players) >= 2) {
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "unregistered_owner_starter|1", llGetOwner());
} else {
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "unregistered_owner|0", llGetOwner());
} }
} }
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)ownerIsStarter, llGetOwner());
return; return;
} }
} }
@ -1176,7 +1252,8 @@ default {
llSetText("🎮 GAME IN PROGRESS\n" + (string)actualPlayerCount + " players", <1.0, 0.2, 0.2>, 1.0); llSetText("🎮 GAME IN PROGRESS\n" + (string)actualPlayerCount + " players", <1.0, 0.2, 0.2>, 1.0);
sendStatusMessage("Title"); sendStatusMessage("Title");
llMessageLinked(LINK_SET, 997, "START_NEXT_ROUND", NULL_KEY); // For game start, use empty peril player - Game Manager will select one
llMessageLinked(LINK_SET, 998, "", NULL_KEY);
} }
if (llListFindList(names, [msg]) != -1) { if (llListFindList(names, [msg]) != -1) {
@ -1197,26 +1274,15 @@ default {
llSetTimerEvent(0); llSetTimerEvent(0);
} }
} }
else if (currentTimerMode == TIMER_TIMEOUT) { else if (currentTimerMode == TIMER_VICTORY_DELAY) {
if (timeoutTimer > 0) { if (victoryDelayTimer > 0) {
integer elapsed = llGetUnixTime() - timeoutTimer; integer elapsed = llGetUnixTime() - victoryDelayTimer;
if (elapsed >= TIMEOUT_SECONDS) { if (elapsed >= (integer)(STATUS_DISPLAY_TIME * 3.0)) { // 24 seconds
llOwnerSay("⏰ Dialog timeout reached!"); llOwnerSay("🏆 [Main Controller] Victory display time complete - executing game reset");
timeoutTimer = 0; victoryDelayTimer = 0;
currentTimerMode = TIMER_IDLE; currentTimerMode = TIMER_IDLE;
llSetTimerEvent(0); llSetTimerEvent(0);
} else { resetGame(); // Now reset after the delay
integer remaining = TIMEOUT_SECONDS - elapsed;
if (remaining <= warning30sec && lastWarning < warning30sec) {
llOwnerSay("⚠️ 30 seconds remaining for dialog response");
lastWarning = warning30sec;
} else if (remaining <= warning1min && lastWarning < warning1min) {
llOwnerSay("⚠️ 1 minute remaining for dialog response");
lastWarning = warning1min;
} else if (remaining <= warning90sec && lastWarning < warning90sec) {
llOwnerSay("⚠️ 1.5 minutes remaining for dialog response");
lastWarning = warning90sec;
}
} }
} }
} }

View file

@ -60,6 +60,19 @@ list globallyPickedNumbers = []; // Numbers already picked by other players
integer currentPage = 0; integer currentPage = 0;
integer NUMBERS_PER_PAGE = 9; // Leave room for navigation buttons integer 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;
llOwnerSay("🧠 [" + scriptName + "] Memory: " +
(string)used + " used, " +
(string)free + " free (" +
llGetSubString((string)percentUsed, 0, 4) + "% used)");
}
list getNumbersForPage(integer page, integer diceType) { list getNumbersForPage(integer page, integer diceType) {
// Build list of available numbers (not picked by others OR by current player) // Build list of available numbers (not picked by others OR by current player)
list availableNumbers = []; list availableNumbers = [];
@ -176,6 +189,8 @@ showPickDialog(string name, key id, integer diceType, integer picks) {
default { default {
state_entry() { state_entry() {
reportMemoryUsage("🎮 NumberPicker Dialog");
// Initialize dynamic channels // Initialize dynamic channels
initializeChannels(); initializeChannels();
numberPickChannel = NUMBERPICK_CHANNEL; // Set legacy variable numberPickChannel = NUMBERPICK_CHANNEL; // Set legacy variable
@ -200,6 +215,8 @@ default {
} }
on_rez(integer start_param) { on_rez(integer start_param) {
reportMemoryUsage("🎮 NumberPicker Dialog");
if (VERBOSE_LOGGING) { if (VERBOSE_LOGGING) {
llOwnerSay("🔄 Number Picker rezzed - reinitializing..."); llOwnerSay("🔄 Number Picker rezzed - reinitializing...");
} }
@ -290,13 +307,32 @@ default {
if (llGetListLength(parts) >= 4) { if (llGetListLength(parts) >= 4) {
string globalPicksStr = llList2String(parts, 3); string globalPicksStr = llList2String(parts, 3);
if (globalPicksStr != "") { if (globalPicksStr != "") {
// Handle both CSV (comma) and semicolon-separated formats // IMPROVED: Handle mixed formats properly
if (llSubStringIndex(globalPicksStr, ";") != -1) { globallyPickedNumbers = [];
// Semicolon-separated format from bots
globallyPickedNumbers = llParseString2List(globalPicksStr, [";"], []); // First split by comma to handle CSV format
} else { list csvParts = llParseString2List(globalPicksStr, [","], []);
// CSV format from humans integer i;
globallyPickedNumbers = llCSV2List(globalPicksStr); for (i = 0; i < llGetListLength(csvParts); i++) {
string part = llStringTrim(llList2String(csvParts, i), STRING_TRIM);
// Check if this part contains semicolons (bot format)
if (llSubStringIndex(part, ";") != -1) {
// Split this part by semicolon and add each number
list semiParts = llParseString2List(part, [";"], []);
integer j;
for (j = 0; j < llGetListLength(semiParts); j++) {
string num = llStringTrim(llList2String(semiParts, j), STRING_TRIM);
if (num != "" && llListFindList(globallyPickedNumbers, [num]) == -1) {
globallyPickedNumbers += [num];
}
}
} else {
// Single number, add it if not already present
if (part != "" && llListFindList(globallyPickedNumbers, [part]) == -1) {
globallyPickedNumbers += [part];
}
}
} }
} else { } else {
globallyPickedNumbers = []; globallyPickedNumbers = [];

View file

@ -23,6 +23,19 @@ list picksData;
string perilPlayer; string perilPlayer;
list names; list names;
// 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)");
}
list getPicksFor(string nameInput) { list getPicksFor(string nameInput) {
integer i; integer i;
for (i = 0; i < llGetListLength(picksData); i++) { for (i = 0; i < llGetListLength(picksData); i++) {
@ -47,6 +60,13 @@ list getPicksFor(string nameInput) {
default { default {
state_entry() { state_entry() {
// Get player name from object description if available
string playerName = llGetObjectDesc();
if (playerName == "" || playerName == "(No Description)") {
playerName = "Unknown";
}
reportMemoryUsage("📱 Player Float (" + playerName + ")");
llSetText("⏳ Waiting...", <1,1,1>, 1.0); llSetText("⏳ Waiting...", <1,1,1>, 1.0);
llSetTimerEvent(1.0); llSetTimerEvent(1.0);
} }
@ -59,12 +79,11 @@ default {
// Set up managed listener // Set up managed listener
listenHandle = llListen(start_param, "", NULL_KEY, ""); listenHandle = llListen(start_param, "", NULL_KEY, "");
// Wait a moment for the description to be set
llSleep(0.1); // Initialize myName to empty - will be set when SET_NAME message is received
myName = llGetObjectDesc(); myName = "";
if (myName == "" || myName == "(No Description)") {
myName = "UnknownPlayer" + (string)start_param; // Don't report memory immediately - wait for proper name to be set
}
// PlayerStatus_Float ready and listening // PlayerStatus_Float ready and listening
} }
@ -87,13 +106,53 @@ default {
llSetTexture(heartTexture, 2); // Face 2 (back) llSetTexture(heartTexture, 2); // Face 2 (back)
llSetTexture(heartTexture, 3); // Face 3 (left side) llSetTexture(heartTexture, 3); // Face 3 (left side)
llSetTexture(heartTexture, 4); // Face 4 (front) llSetTexture(heartTexture, 4); // Face 4 (front)
// Set white background for all faces to eliminate gaps without tinting
llSetColor(<1.0, 1.0, 1.0>, 1); // White background for right side // IMPROVED: Special visual treatment for elimination (0 hearts)
llSetColor(<1.0, 1.0, 1.0>, 2); // White background for back if (lifeCount == 0) {
llSetColor(<1.0, 1.0, 1.0>, 3); // White background for left side // Make 0 hearts more prominent with red glow
llSetColor(<1.0, 1.0, 1.0>, 4); // White background for front llSetLinkPrimitiveParamsFast(LINK_THIS, [
llSetColor(<0.2, 0.2, 0.2>, 0); // Dark gray for top PRIM_GLOW, ALL_SIDES, 0.3,
llSetColor(<0.2, 0.2, 0.2>, 5); // Dark gray for bottom PRIM_COLOR, ALL_SIDES, <1.0, 0.0, 0.0>, 1.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 // Remove "Lives: X" from display text since hearts show it
list lines = llParseString2List(displayText, ["\n"], []); list lines = llParseString2List(displayText, ["\n"], []);
@ -120,6 +179,9 @@ default {
// Name successfully received and set // Name successfully received and set
// Update the object description as well for consistency // Update the object description as well for consistency
llSetObjectDesc(myName); llSetObjectDesc(myName);
// Now report memory with the proper player name
reportMemoryUsage("📱 " + myName);
} }
} }
@ -165,10 +227,57 @@ default {
string picksDisplay = llList2CSV(picks); string picksDisplay = llList2CSV(picks);
// Note: Heart texture is set via listen() path to avoid conflicts // 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;
}
// Update text display (lives now shown by heart textures)
string txt = "🎲 Peril Dice\n👤 " + myName + "\n🢍 Peril: " + perilDisplay + "\n🔢 Picks: " + picksDisplay;
llSetText(txt, <1,1,1>, 1.0); llSetText(txt, <1,1,1>, 1.0);
} }
} }

View file

@ -111,6 +111,19 @@ float MENU_REQUEST_TIMEOUT = 5.0; // 5 second timeout for menu requests
integer isLocked = FALSE; integer isLocked = FALSE;
key gameOwner; 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;
llOwnerSay("🧠 [" + scriptName + "] Memory: " +
(string)used + " used, " +
(string)free + " free (" +
llGetSubString((string)percentUsed, 0, 4) + "% used)");
}
// Display the main categorized owner menu with lock/unlock controls // Display the main categorized owner menu with lock/unlock controls
showOwnerMenu(key id) { showOwnerMenu(key id) {
// Only show full menu to owner // Only show full menu to owner
@ -288,8 +301,32 @@ showKickPlayerMenu(key id, list playerNames) {
llDialog(id, "👢 Select player to kick from game:", options, DIALOG_CHANNEL); llDialog(id, "👢 Select player to kick from game:", options, DIALOG_CHANNEL);
} }
// Show menu for unregistered owner
showUnregisteredOwnerMenu(key id, integer canStartGame) {
list options = [];
string menuText = "👑 Owner Options:\n\n";
// Always allow owner to join
options += ["Join Game"];
menuText += "🎮 Join Game - Register as a player\n";
// Allow starting game if there are enough players (even if all bots)
if (canStartGame) {
options += ["Start Game"];
menuText += "⚡ Start Game - Begin with current players\n";
}
// Always allow owner menu access
options += ["Owner Menu"];
menuText += "🔧 Owner Menu - Admin functions";
llDialog(id, menuText, options, DIALOG_CHANNEL);
}
default { default {
state_entry() { state_entry() {
reportMemoryUsage("🎭 Player Dialog");
// Initialize dynamic channels // Initialize dynamic channels
initializeChannels(); initializeChannels();
DIALOG_CHANNEL = MAIN_DIALOG_CHANNEL; // Set legacy variable DIALOG_CHANNEL = MAIN_DIALOG_CHANNEL; // Set legacy variable
@ -331,6 +368,8 @@ default {
} }
on_rez(integer start_param) { on_rez(integer start_param) {
reportMemoryUsage("🎭 Player Dialog");
llOwnerSay("🔄 Player Dialog Handler rezzed - reinitializing..."); llOwnerSay("🔄 Player Dialog Handler rezzed - reinitializing...");
// Re-initialize dynamic channels // Re-initialize dynamic channels
@ -408,6 +447,12 @@ default {
} else if (targetType == "admin") { } else if (targetType == "admin") {
// Show admin category menu directly // Show admin category menu directly
showOwnerMenu(id); showOwnerMenu(id);
} else if (targetType == "unregistered_owner") {
// Unregistered owner - basic menu (join, owner menu)
showUnregisteredOwnerMenu(id, FALSE);
} else if (targetType == "unregistered_owner_starter") {
// Unregistered owner with enough players to start game
showUnregisteredOwnerMenu(id, TRUE);
} }
} }
else if (num == MSG_READY_STATE_RESULT) { else if (num == MSG_READY_STATE_RESULT) {

View file

@ -153,6 +153,9 @@ handlePlayerRegistration(string regData, key requesterId) {
// Send pre-built sync message // Send pre-built sync message
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, syncMessage, NULL_KEY); 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);
// Send minimal update to Main Controller - just the essential data it needs // Send minimal update to Main Controller - just the essential data it needs
string updateData = (string)ch + "~" + newName; string updateData = (string)ch + "~" + newName;
llMessageLinked(LINK_SET, MSG_UPDATE_MAIN_LISTS, updateData, newKey); llMessageLinked(LINK_SET, MSG_UPDATE_MAIN_LISTS, updateData, newKey);

View file

@ -1,12 +1,14 @@
# 🎲 Peril Dice — Professional Single Linkset Game System for Second Life # 🎲 Peril Dice — Professional Single Linkset Game System for Second Life
**Created by Rebecca Nod and Noose the Bunny** **Created by Rebecca Nod and Noose the Bunny**
**Current Version: 2.8.5 - Memory Optimization & Architecture Cleanup** **Current Version: 2.8.6 - Winner Glow Celebration System**
## Overview ## 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. 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 v2.8.6**: **WINNER GLOW CELEBRATION SYSTEM** - Revolutionary winner recognition with bright green glow effects on both scoreboard and floaters, plus 24-second victory celebration timer giving proper recognition to the Ultimate Survivor before game reset.
**🚨 NEW in v2.8.5**: **MEMORY OPTIMIZATION & ARCHITECTURE CLEANUP** - Major system cleanup eliminating dead code, fixing bot profile picture flickering, and optimizing memory usage across all scripts with measurable performance improvements. **🚨 NEW in v2.8.5**: **MEMORY OPTIMIZATION & ARCHITECTURE CLEANUP** - Major system cleanup eliminating dead code, fixing bot profile picture flickering, and optimizing memory usage across all scripts with measurable performance improvements.
**NEW in v2.8.4**: **DISCONNECT RECOVERY & DEBUG SYSTEM** - Revolutionary disconnect/reconnect recovery system eliminates need to kick players who disconnect during their turn, plus comprehensive system-wide verbose logging toggle. **NEW in v2.8.4**: **DISCONNECT RECOVERY & DEBUG SYSTEM** - Revolutionary disconnect/reconnect recovery system eliminates need to kick players who disconnect during their turn, plus comprehensive system-wide verbose logging toggle.
@ -19,6 +21,41 @@ 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. **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 v2.8.6 Improvements 🏆
### 🏆 **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
- **Floater Celebration**: Winner's floating display shows green glow with victory text "✨ ULTIMATE VICTORY! ✨"
- **Visual Priority System**: Winner glow (green) overrides peril glow (yellow) during victory celebration
- **Professional Polish**: Eliminated players show red X while winner gets the spotlight
- **Extended Victory Moment**: 24-second celebration timer before game reset
- **Before**: Winner announced → Instant reset → No recognition
- **After**: Winner announced → Green glow celebration → 24 seconds of glory → Reset
- **Player Experience**: Proper victory moment makes winning feel rewarding and celebrated
### 🎨 **Enhanced Visual Feedback System**
- **Improved Glow Colors**: Professional-grade color tinting system
- **Winner Recognition**: Bright green glow + green tint for unmistakable victory celebration
- **Peril Identification**: Yellow glow + yellow tint for clear peril player status
- **Visual Clarity**: Replaced generic white glow with distinct colored feedback
- **Smart Winner Detection**: Floaters automatically detect victory conditions and display celebration text
- **Synchronized Effects**: Both scoreboard and floater systems celebrate winner simultaneously
### 🔧 **System Optimization**
- **Victory Timer System**: Dedicated timer mode for celebration delays without system interference
- **Memory Efficiency**: Removed unused timeout components to free up memory resources
- **Enhanced Communication**: Improved message routing for winner glow effects
- **Clean Architecture**: Streamlined systems focus on essential functionality
### 🎯 **Before vs After v2.8.6**
|| **Before** | **After** |
||------------|----------|
|| ❌ Winner announced but no visual celebration | ✅ Bright green glow celebration on scoreboard and floaters |
|| ❌ Immediate reset after victory - no recognition | ✅ 24-second victory celebration timer for proper recognition |
|| ❌ Generic white glow effects | ✅ Professional colored glow system (green/yellow/red) |
|| ❌ Winners felt unrewarded | ✅ Ultimate Survivors get proper celebration and recognition |
## Major v2.8.5 Improvements 🚨 ## Major v2.8.5 Improvements 🚨
### 🧠 **Memory Optimization & Dead Code Cleanup** ### 🧠 **Memory Optimization & Dead Code Cleanup**

View file

@ -2,12 +2,14 @@
======================================================== ========================================================
CREATED BY REBECCA NOD AND NOOSE THE BUNNY CREATED BY REBECCA NOD AND NOOSE THE BUNNY
CURRENT VERSION: 2.8.5 - MEMORY OPTIMIZATION & ARCHITECTURE CLEANUP CURRENT VERSION: 2.8.6 - WINNER GLOW CELEBRATION SYSTEM
OVERVIEW 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. 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 V2.8.6: Revolutionary winner recognition with bright green glow effects on both scoreboard and floaters, plus 24-second victory celebration timer giving proper recognition to the Ultimate Survivor before game reset.
NEW IN V2.8.5: Major system cleanup eliminating dead code, fixing bot profile picture flickering, and optimizing memory usage across all scripts with measurable performance improvements. NEW IN V2.8.5: Major system cleanup eliminating dead code, fixing bot profile picture flickering, and optimizing memory usage across all scripts with measurable performance improvements.
NEW IN V2.8.4: Revolutionary disconnect/reconnect recovery system eliminates need to kick players who disconnect during their turn, plus comprehensive system-wide verbose logging toggle for production vs development modes. NEW IN V2.8.4: Revolutionary disconnect/reconnect recovery system eliminates need to kick players who disconnect during their turn, plus comprehensive system-wide verbose logging toggle for production vs development modes.
@ -96,6 +98,40 @@ Player Count | Dice Type
5–6 | d20 5–6 | d20
7–10 | d30 7–10 | d30
RECENT IMPROVEMENTS (V2.8.6)
============================
🏆 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
* Floater Celebration: Winner's floating display shows green glow with victory text "ULTIMATE VICTORY!"
* Visual Priority System: Winner glow (green) overrides peril glow (yellow) during victory celebration
* Professional Polish: Eliminated players show red X while winner gets the spotlight
- Extended Victory Moment: 24-second celebration timer before game reset
* Before: Winner announced → Instant reset → No recognition
* After: Winner announced → Green glow celebration → 24 seconds of glory → Reset
* Player Experience: Proper victory moment makes winning feel rewarding and celebrated
🎨 ENHANCED VISUAL FEEDBACK SYSTEM
- Improved Glow Colors: Professional-grade color tinting system
* Winner Recognition: Bright green glow + green tint for unmistakable victory celebration
* Peril Identification: Yellow glow + yellow tint for clear peril player status
* Visual Clarity: Replaced generic white glow with distinct colored feedback
- Smart Winner Detection: Floaters automatically detect victory conditions and display celebration text
- Synchronized Effects: Both scoreboard and floater systems celebrate winner simultaneously
🔧 SYSTEM OPTIMIZATION
- Victory Timer System: Dedicated timer mode for celebration delays without system interference
- Memory Efficiency: Removed unused timeout components to free up memory resources
- Enhanced Communication: Improved message routing for winner glow effects
- Clean Architecture: Streamlined systems focus on essential functionality
🎯 BEFORE VS AFTER V2.8.6
- Before: Winner announced but no visual celebration | After: Bright green glow celebration on scoreboard and floaters
- Before: Immediate reset after victory - no recognition | After: 24-second victory celebration timer for proper recognition
- Before: Generic white glow effects | After: Professional colored glow system (green/yellow/red)
- Before: Winners felt unrewarded | After: Ultimate Survivors get proper celebration and recognition
RECENT IMPROVEMENTS (V2.8.5) RECENT IMPROVEMENTS (V2.8.5)
============================ ============================

View file

@ -72,6 +72,19 @@ integer diceTypeProcessed = FALSE; // Prevent duplicate dice type processing
// Verbose logging control - toggled by owner // Verbose logging control - toggled by owner
integer VERBOSE_LOGGING = FALSE; integer VERBOSE_LOGGING = 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;
llOwnerSay("🧠 [" + scriptName + "] Memory: " +
(string)used + " used, " +
(string)free + " free (" +
llGetSubString((string)percentUsed, 0, 4) + "% used)");
}
list getPicksFor(string nameInput) { list getPicksFor(string nameInput) {
integer i; integer i;
for (i = 0; i < llGetListLength(picksData); i++) { for (i = 0; i < llGetListLength(picksData); i++) {
@ -129,6 +142,8 @@ confetti() {
default { default {
state_entry() { state_entry() {
reportMemoryUsage("🎲 Roll Confetti Module");
// Initialize dynamic channels // Initialize dynamic channels
initializeChannels(); initializeChannels();
rollDialogChannel = ROLLDIALOG_CHANNEL; // Set legacy variable rollDialogChannel = ROLLDIALOG_CHANNEL; // Set legacy variable
@ -155,6 +170,7 @@ default {
} }
on_rez(integer start_param) { on_rez(integer start_param) {
reportMemoryUsage("🎲 Roll Confetti Module");
llOwnerSay("🔄 Roll Confetti Module rezzed - reinitializing..."); llOwnerSay("🔄 Roll Confetti Module rezzed - reinitializing...");
// Re-initialize dynamic channels // Re-initialize dynamic channels
@ -273,8 +289,8 @@ default {
if (llSubStringIndex(str, "_NEXT_ROUND") != -1) { if (llSubStringIndex(str, "_NEXT_ROUND") != -1) {
string playerName = llGetSubString(str, 0, llSubStringIndex(str, "_NEXT_ROUND") - 1); string playerName = llGetSubString(str, 0, llSubStringIndex(str, "_NEXT_ROUND") - 1);
llOwnerSay("🎯 Prompting " + playerName + " to start next round..."); llOwnerSay("🎯 Prompting " + playerName + " to start next round...");
// Send message to main controller to get the player's key and show dialog // Send continuation directly to Game Manager (more efficient than through Main Controller)
llMessageLinked(LINK_SET, 997, "START_NEXT_ROUND_DIALOG|" + playerName, NULL_KEY); llMessageLinked(LINK_SET, 998, "", NULL_KEY); // Empty peril player - Game Manager will select one
} else { } else {
// Clear dice display directly (link 83) // Clear dice display directly (link 83)
llMessageLinked(83, 3021, "", NULL_KEY); llMessageLinked(83, 3021, "", NULL_KEY);
@ -394,6 +410,10 @@ default {
// Direct scoreboard status update // Direct scoreboard status update
llMessageLinked(12, 3001, "Plot Twist", NULL_KEY); llMessageLinked(12, 3001, "Plot Twist", NULL_KEY);
// IMPORTANT: Send peril player update to scoreboard for glow effect
llMessageLinked(12, 3005, newPeril, NULL_KEY); // MSG_UPDATE_PERIL_PLAYER
// Add delay to let status display before next phase // Add delay to let status display before next phase
llSleep(2.0); llSleep(2.0);
@ -450,36 +470,23 @@ default {
llSay(0, "🐻 PUNISHMENT TIME! " + perilPlayer + " has been ELIMINATED!"); llSay(0, "🐻 PUNISHMENT TIME! " + perilPlayer + " has been ELIMINATED!");
// IMPORTANT: Update our local state to reflect the elimination // CRITICAL FIX: DO NOT update our local state to remove the eliminated player
// Remove the eliminated player from our names and lives lists to prevent stale data // This would corrupt the sync message. Instead, just mark the player as having 0 lives
// but keep them in the lists. Main Controller will handle the actual removal.
integer elimIdx = llListFindList(names, [perilPlayer]); integer elimIdx = llListFindList(names, [perilPlayer]);
if (elimIdx != -1) { if (elimIdx != -1) {
names = llDeleteSubList(names, elimIdx, elimIdx); // Set player's lives to 0 in our local state, but keep them in the lists
lives = llDeleteSubList(lives, elimIdx, elimIdx); lives = llListReplaceList(lives, [0], elimIdx, elimIdx);
// Also clean up their picks data llOwnerSay("🔄 [Roll Module] Updated local state - set " + perilPlayer + "'s lives to 0 but kept in lists");
integer pickIdx = -1; llOwnerSay("🔄 [Roll Module] This ensures proper sync message format before Main Controller handles elimination");
integer p;
// LSL doesn't support break, so use a flag instead
integer foundEntry = FALSE;
for (p = 0; p < llGetListLength(picksData) && !foundEntry; p++) {
string entry = llList2String(picksData, p);
if (llSubStringIndex(entry, perilPlayer + "|") == 0) {
pickIdx = p;
foundEntry = TRUE;
}
}
if (pickIdx != -1) {
picksData = llDeleteSubList(picksData, pickIdx, pickIdx);
}
llOwnerSay("🔄 [Roll Module] Updated local state - removed " + perilPlayer + " from names/lives/picks");
} }
// Send message to main controller to handle elimination // Send message to main controller to handle elimination
llMessageLinked(LINK_SET, 999, "ELIMINATE_PLAYER|" + perilPlayer, NULL_KEY); llMessageLinked(LINK_SET, 999, "ELIMINATE_PLAYER|" + perilPlayer, NULL_KEY);
// CRITICAL: Don't send any sync messages after elimination // CRITICAL: Don't send any sync messages after elimination
// Let Main Controller handle all state sync and peril player reassignment // Let Main Controller handle all state sync, peril player reassignment, AND game continuation
llOwnerSay("🎯 [Roll Module] Elimination complete - letting Main Controller handle state sync"); llOwnerSay("🎯 [Roll Module] Elimination complete - letting Main Controller handle state sync and continuation");
// Clear roll protection after processing is complete // Clear roll protection after processing is complete
rollInProgress = FALSE; rollInProgress = FALSE;
@ -490,14 +497,14 @@ default {
// Note: Win condition checking is handled by Main Controller after elimination // Note: Win condition checking is handled by Main Controller after elimination
// IMPORTANT: After processing a roll, clear picks data for the next round // IMPORTANT: After processing a roll, preserve the actual picks data that was used for validation
// The round is complete, so we should send empty picks to trigger next round logic // These picks will be used by Game Manager for building complete avoidance lists
list encodedPicksData = []; list encodedPicksData = [];
integer k; integer k;
for (k = 0; k < llGetListLength(names); k++) { for (k = 0; k < llGetListLength(picksData); k++) {
string playerName = llList2String(names, k); string entry = llList2String(picksData, k);
// Send empty picks for all players since round is complete // Preserve the actual picks data that was used for validation
encodedPicksData += [playerName + "|"]; encodedPicksData += [entry];
} }
// Validate perilPlayer is not empty before syncing // Validate perilPlayer is not empty before syncing
string perilForSync = perilPlayer; string perilForSync = perilPlayer;
@ -521,12 +528,15 @@ default {
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, fname, NULL_KEY); llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, fname, NULL_KEY);
} }
// After roll is processed and sync is sent, start next round automatically // After roll is processed and sync is sent, send continuation directly to Game Manager
llOwnerSay("🎯 Round complete, starting next round with peril player: " + perilPlayer); llOwnerSay("🎯 Round complete - sending continuation directly to Game Manager");
// Brief delay to ensure sync propagates, then start next round // Brief delay to ensure sync propagates
llSleep(1.0); llSleep(0.5);
llMessageLinked(LINK_SET, 997, "CONTINUE_ROUND|" + perilPlayer, NULL_KEY);
// 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);
// Clear roll protection after processing is complete // Clear roll protection after processing is complete
rollInProgress = FALSE; rollInProgress = FALSE;

View file

@ -63,6 +63,22 @@ class LSLValidator:
'PI', 'PI_BY_TWO', 'TWO_PI', 'DEG_TO_RAD', 'RAD_TO_DEG', 'PI', 'PI_BY_TWO', 'TWO_PI', 'DEG_TO_RAD', 'RAD_TO_DEG',
'LINK_ROOT', 'LINK_SET', 'LINK_ALL_OTHERS', 'LINK_ALL_CHILDREN', 'LINK_THIS', 'LINK_ROOT', 'LINK_SET', 'LINK_ALL_OTHERS', 'LINK_ALL_CHILDREN', 'LINK_THIS',
} }
# Keywords that are definitely forbidden in LSL (conservative list)
self.forbidden_keywords = {
'break', 'continue', 'switch', 'case', 'goto',
'class', 'struct', 'enum', 'union', 'namespace'
}
# LSL data types
self.lsl_types = {
'integer', 'float', 'string', 'key', 'vector', 'rotation', 'list'
}
# Scope tracking for variables
self.global_vars = set() # Global variables
self.local_vars = [] # Stack of local scopes
self.current_function = None
def validate_file(self, filepath): def validate_file(self, filepath):
"""Validate a single LSL file""" """Validate a single LSL file"""
@ -77,6 +93,7 @@ class LSLValidator:
self._check_syntax(lines, filepath) self._check_syntax(lines, filepath)
self._check_style(lines, filepath) self._check_style(lines, filepath)
self._check_scope(lines, filepath)
self._check_performance(content, filepath) self._check_performance(content, filepath)
self._check_memory_usage(content, filepath) self._check_memory_usage(content, filepath)
@ -135,9 +152,203 @@ class LSLValidator:
and '}' not in line and not line.startswith('state') and not line.startswith('default')): and '}' not in line and not line.startswith('state') and not line.startswith('default')):
self.warnings.append(f"Line {line_num}: Possible missing semicolon") self.warnings.append(f"Line {line_num}: Possible missing semicolon")
# Check for forbidden LSL keywords
self._check_forbidden_keywords(line, line_num)
# Final brace balance check # Final brace balance check
if brace_count != 0: if brace_count != 0:
self.errors.append(f"Unmatched braces: {brace_count} excess opening braces" if brace_count > 0 else f"{-brace_count} missing opening braces") self.errors.append(f"Unmatched braces: {brace_count} excess opening braces" if brace_count > 0 else f"{-brace_count} missing opening braces")
def _check_forbidden_keywords(self, line, line_num):
"""Check for keywords that are forbidden in LSL"""
# Skip comments
if '//' in line:
line = line[:line.index('//')]
# Remove strings to avoid false positives
# Simple approach: remove content between quotes
import re
line_no_strings = re.sub(r'"[^"]*"', '""', line)
# Check each forbidden keyword
for keyword in self.forbidden_keywords:
# Use word boundaries to avoid matching substrings
pattern = r'\b' + re.escape(keyword) + r'\b'
if re.search(pattern, line_no_strings):
self.errors.append(f"Line {line_num}: '{keyword}' is not supported in LSL")
def _check_scope(self, lines, filepath):
"""Check for variable scope issues"""
# Reset scope tracking
self.global_vars = set()
self.local_vars = []
self.current_function = None
in_function = False
brace_depth = 0
for line_num, line in enumerate(lines, 1):
original_line = line
line = line.strip()
# Skip empty lines and comments
if not line or line.startswith('//') or line.startswith('/*'):
continue
# Remove comments from line
if '//' in line:
line = line[:line.index('//')]
line = line.strip()
# Track brace depth for scope management
open_braces = line.count('{')
close_braces = line.count('}')
# Check for function definitions
if not in_function and ('(' in line and ')' in line and '{' in line):
# This might be a function definition
if any(event in line for event in ['state_entry', 'touch_start', 'listen', 'timer', 'on_rez', 'link_message', 'http_response']):
in_function = True
self.local_vars.append(set()) # New local scope
# Handle scope changes
if open_braces > 0:
for _ in range(open_braces):
brace_depth += 1
if in_function and brace_depth > 1: # Nested scope within function
self.local_vars.append(set())
# Check for variable declarations
self._check_variable_declaration(line, line_num, in_function)
# Check for variable usage
self._check_variable_usage(line, line_num)
# Handle closing braces
if close_braces > 0:
for _ in range(close_braces):
brace_depth -= 1
if in_function and len(self.local_vars) > 1:
self.local_vars.pop() # Exit nested scope
elif brace_depth == 0 and in_function:
# Exiting function
in_function = False
self.local_vars.clear()
def _check_variable_declaration(self, line, line_num, in_function):
"""Check for variable declarations"""
import re
# Remove string literals to avoid false matches
line_no_strings = re.sub(r'"[^"]*"', '""', line)
# Pattern to match LSL variable declarations: type varname;
for lsl_type in self.lsl_types:
# Handle special case of for loop declarations: for(integer i = 0; ...)
for_loop_pattern = rf'for\s*\(\s*{lsl_type}\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*='
for_matches = re.findall(for_loop_pattern, line_no_strings)
for var_name in for_matches:
if var_name.startswith('ll') or var_name in self.lsl_functions:
continue
# For loop variables are always local and shouldn't conflict with globals
if len(self.local_vars) > 0:
self.local_vars[-1].add(var_name)
continue
# Regular variable declarations: type identifier; or type identifier =
# Skip for loop variable declarations by checking the line content
if 'for(' not in line_no_strings.replace(' ', '') and 'for (' not in line_no_strings:
pattern = rf'\b{lsl_type}\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*[;=]'
matches = re.findall(pattern, line_no_strings)
else:
matches = []
for var_name in matches:
# Skip LSL built-in functions that might match the pattern
if var_name.startswith('ll') or var_name in self.lsl_functions:
continue
# Skip common parameter names that aren't real declarations
if var_name in {'total_number', 'sender', 'num', 'str', 'id', 'start_param',
'channel', 'name', 'msg', 'message', 'request_id', 'status', 'metadata', 'body'}:
continue
if in_function:
# Check if already declared in current local scope
if len(self.local_vars) > 0 and var_name in self.local_vars[-1]:
self.errors.append(f"Line {line_num}: Variable '{var_name}' already declared in this scope")
else:
# Add to current local scope
if len(self.local_vars) > 0:
self.local_vars[-1].add(var_name)
else:
# Global variable - only flag if it's a real redeclaration
if var_name in self.global_vars:
# Only error if this looks like a real declaration line
if ';' in line or '=' in line:
self.errors.append(f"Line {line_num}: Global variable '{var_name}' already declared")
else:
self.global_vars.add(var_name)
def _check_variable_usage(self, line, line_num):
"""Check for usage of undefined variables (conservative approach)"""
import re
# Skip variable declarations and function definitions
if any(lsl_type in line for lsl_type in self.lsl_types):
return
# Skip comments and string literals
if line.strip().startswith('//') or line.strip().startswith('/*') or '"' in line:
return
# Skip lines that are clearly not variable usage
if (line.startswith('state') or line.startswith('#') or line.strip() == 'default' or
'llOwnerSay' in line or 'llSay' in line or 'llMessageLinked' in line or
('{' in line and '}' in line and line.count('{') == line.count('}'))):
return
# Only check very specific patterns where we're confident it's a variable usage
# Pattern 1: variable assignment: varname = something
assignment_vars = re.findall(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=', line)
# Pattern 2: variable in arithmetic: something + varname or varname + something
arithmetic_vars = re.findall(r'(?:^|[+\-*/=\s])([a-zA-Z_][a-zA-Z0-9_]*)(?:\s*[+\-*/]|\s*;|$)', line)
# Combine patterns
potential_vars = set(assignment_vars + arithmetic_vars)
for var_name in potential_vars:
# Skip LSL functions, constants, keywords, and common false positives
if (var_name in self.lsl_functions or var_name in self.lsl_constants or
var_name in self.lsl_types or var_name.startswith('ll') or
var_name in {'if', 'else', 'for', 'while', 'do', 'return', 'jump', 'state', 'default',
'total_number', 'sender', 'num', 'str', 'id', 'start_param', 'channel',
'name', 'msg', 'message', 'request_id', 'status', 'metadata', 'body',
# Common English words that show up in strings/comments
'the', 'and', 'or', 'not', 'has', 'been', 'with', 'from', 'this', 'that',
'will', 'can', 'may', 'all', 'any', 'some', 'new', 'old', 'get', 'set'} or
var_name.isdigit() or len(var_name) <= 2):
continue
# Check if variable is defined
is_defined = var_name in self.global_vars
# Check local scopes (from innermost to outermost)
if not is_defined:
for scope in reversed(self.local_vars):
if var_name in scope:
is_defined = True
break
# Only warn for very likely variable usage patterns
if not is_defined:
# Must be lowercase and used in assignment or arithmetic context
if (var_name.islower() and
(var_name in assignment_vars or
(var_name in arithmetic_vars and any(op in line for op in ['+', '-', '*', '/'])))):
self.warnings.append(f"Line {line_num}: Variable '{var_name}' may not be defined")
def _check_style(self, lines, filepath): def _check_style(self, lines, filepath):
"""Check for style and best practice issues""" """Check for style and best practice issues"""