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:
parent
dd03bd8eef
commit
03c5d81bb0
23 changed files with 1484 additions and 585 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
50
CHANGELOG.md
50
CHANGELOG.md
|
|
@ -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**
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
===============================================================================
|
===============================================================================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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...");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
129
Game_Manager.lsl
129
Game_Manager.lsl
|
|
@ -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, ["|"], []);
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 = [];
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
39
README.md
39
README.md
|
|
@ -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**
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
============================
|
============================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
211
lsl_validator.py
211
lsl_validator.py
|
|
@ -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"""
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue