## [2.2.0] - 2025-08-04

This commit is contained in:
RebeccaNod1 2025-08-03 23:47:46 -05:00
parent 494f01c7bf
commit 4069379bf1
9 changed files with 175 additions and 183 deletions

View file

@ -25,24 +25,39 @@ doBotPick(string botName, integer count, integer diceMax, list avoidNumbers) {
return; return;
} }
llOwnerSay("[Bot Manager] ✅ Picking " + (string)count + " numbers for " + botName + " (lives=" + (string)botLives + ", max=" + (string)diceMax + ")");
if (llGetListLength(avoidNumbers) > 0) {
llOwnerSay("[Bot Manager] Avoiding already picked numbers: " + llList2CSV(avoidNumbers));
}
list picks; list picks;
integer attempts = 0; integer attempts = 0;
while (llGetListLength(picks) < count && attempts < 100) { while (llGetListLength(picks) < count && attempts < 100) {
integer roll = 1 + (integer)(llFrand((float)diceMax)); integer roll = 1 + (integer)(llFrand((float)diceMax));
string rollStr = (string)roll; string rollStr = (string)roll;
integer inPicks = llListFindList(picks, [rollStr]);
integer inAvoid = llListFindList(avoidNumbers, [rollStr]);
// Check if not already picked by this bot and not picked by others // Check if not already picked by this bot and not picked by others
if (llListFindList(picks, [rollStr]) == -1 && llListFindList(avoidNumbers, [rollStr]) == -1) { if (inPicks == -1 && inAvoid == -1) {
picks += rollStr; picks += rollStr;
} else {
string reason = "";
if (inPicks != -1) {
reason = "already picked by this bot";
} else if (inAvoid != -1) {
reason = "already picked by another player";
}
} }
attempts++; attempts++;
} }
string pickString = llDumpList2String(picks, ",");
if (llGetListLength(picks) < count) {
llOwnerSay("[Bot Manager] ⚠️ WARNING: " + botName + " only picked " + (string)llGetListLength(picks) + "/" + (string)count + " numbers after " + (string)attempts + " attempts");
}
string pickString = llDumpList2String(picks, ";");
string response = "BOT_PICKED:" + botName + ":" + pickString; string response = "BOT_PICKED:" + botName + ":" + pickString;
llOwnerSay("[Bot Manager] Sending: " + response);
llMessageLinked(LINK_SET, -9997, response, NULL_KEY); llMessageLinked(LINK_SET, -9997, response, NULL_KEY);
} }
@ -66,7 +81,6 @@ doBotRoll(string botName, integer diceMax) {
return; return;
} }
llOwnerSay("[Bot Manager] ✅ Bot '" + botName + "' rolling dice (max=" + (string)diceMax + ")");
integer roll = 1 + (integer)(llFrand((float)diceMax)); integer roll = 1 + (integer)(llFrand((float)diceMax));
llRegionSay(LISTEN_CHANNEL, "BOT_ROLL:" + botName + ":" + (string)roll); llRegionSay(LISTEN_CHANNEL, "BOT_ROLL:" + botName + ":" + (string)roll);
} }
@ -93,13 +107,11 @@ default {
perilPlayer = receivedPeril; perilPlayer = receivedPeril;
} }
names = llCSV2List(llList2String(parts, 3)); names = llCSV2List(llList2String(parts, 3));
llOwnerSay("[Bot Manager] 🔄 Sync: " + (string)llGetListLength(names) + " players, peril='" + perilPlayer + "'");
} }
return; return;
} }
if (num == -9999) { if (num == -9999) {
llOwnerSay("[Bot Manager] Received link message: " + str);
if (llSubStringIndex(str, "BOT_PICK:") == 0) { if (llSubStringIndex(str, "BOT_PICK:") == 0) {
// Format: BOT_PICK:bot_1:3:20:1,2,3 (last part is optional already picked numbers) // Format: BOT_PICK:bot_1:3:20:1,2,3 (last part is optional already picked numbers)
list parts = llParseStringKeepNulls(str, [":"], []); list parts = llParseStringKeepNulls(str, [":"], []);
@ -111,7 +123,16 @@ default {
if (llGetListLength(parts) >= 5) { if (llGetListLength(parts) >= 5) {
string avoidStr = llList2String(parts, 4); string avoidStr = llList2String(parts, 4);
if (avoidStr != "") { if (avoidStr != "") {
avoidNumbers = llCSV2List(avoidStr); list rawList = llCSV2List(avoidStr);
// Trim whitespace from each number
avoidNumbers = [];
integer i;
for (i = 0; i < llGetListLength(rawList); i++) {
string trimmed = llStringTrim(llList2String(rawList, i), STRING_TRIM);
if (trimmed != "") {
avoidNumbers += [trimmed];
}
}
} }
} }
doBotPick(botName, count, diceMax, avoidNumbers); doBotPick(botName, count, diceMax, avoidNumbers);
@ -130,7 +151,6 @@ default {
} }
listen(integer channel, string name, key id, string message) { listen(integer channel, string name, key id, string message) {
llOwnerSay("[Bot Manager] Received: " + message);
if (llSubStringIndex(message, "BOT_PICK:") == 0) { if (llSubStringIndex(message, "BOT_PICK:") == 0) {
// Format: BOT_PICK:bot_1:3:20 // Format: BOT_PICK:bot_1:3:20
list parts = llParseStringKeepNulls(message, [":"], []); list parts = llParseStringKeepNulls(message, [":"], []);

View file

@ -2,6 +2,41 @@
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.2.0] - 2025-08-04
### 🎭 Major UX/Presentation Improvements
- **Dramatic Messaging System**: Complete overhaul of game messages with thematic styling
- **Public Announcements**: Key game events now visible to all players in public chat
- **Context-Rich Roll Messages**: Dice rolls now show both dice type and result
- **Split Hit Messages**: Different messages for "picked own doom" vs "no shield" scenarios
### 🎮 Enhanced Player Experience
- **Player Join Messages**: `💀 PlayerName has entered the deadly game! Welcome to your potential doom! 💀`
- **Ready State Messages**:
- `👑 PlayerName steps forward as the game master - automatically ready for the deadly challenge! 👑`
- `⚔️ PlayerName steels themselves for the deadly challenge ahead! ⚔️`
- `🏃 PlayerName loses their nerve and backs away from the challenge! 🏃`
- **Pick Announcements**: `🎯 PlayerName stakes their life on numbers: 1, 2, 3 🎲`
- **Game Start**: `⚡ ALL PARTICIPANTS READY! THE DEADLY PERIL DICE GAME BEGINS! ⚡`
### 🎲 Improved Roll Messages
- **Main Roll**: `🎲 THE D6 OF FATE! PlayerName rolled a 5 on the 6-sided die! 🎲`
- **Plot Twist**: `⚡ PLOT TWIST! PlayerName picked 5 (rolled on d6) and is now in ULTIMATE PERIL! ⚡`
- **Direct Hit**: `🩸 DIRECT HIT! PlayerName picked their own doom - the d6 landed on 5! 🩸`
- **No Shield**: `🩸 NO SHIELD! Nobody picked 5 - PlayerName takes the hit from the d6! 🩸`
### 🔧 Critical Bug Fixes
- **StatFloat Duplication**: Fixed bug where new StatFloat objects were created on every life loss instead of updating existing ones
- **Duplicate Message Handling**: Removed duplicate `HUMAN_PICKED` handlers that caused double processing
- **Malformed Entry Warnings**: Fixed LSL parsing issues with empty picks entries
- **Cleanup Optimization**: Reduced excessive StatFloat cleanup messages during reset
### 🛠️ Technical Improvements
- **Debug Message Cleanup**: Removed excessive debug output while preserving essential error reporting
- **Message Handler Optimization**: Streamlined communication between scripts
- **LSL Parsing Fixes**: Better handling of trailing empty elements in data parsing
- **Memory Management**: Improved StatFloat lifecycle management
## [2.1.0] - 2025-01-08 ## [2.1.0] - 2025-01-08
### ✨ Major Features Added ### ✨ Major Features Added
@ -84,11 +119,14 @@ All notable changes to Peril Dice will be documented in this file.
## Development Notes ## Development Notes
### Known Issues ### Known Issues
- **Bot Race Condition**: Bots may occasionally pick duplicate numbers when triggered simultaneously
- **Debug Output**: Extended games may experience minor lag from comprehensive logging - **Debug Output**: Extended games may experience minor lag from comprehensive logging
### Recent Fixes (2025-01-08)
- **Bot Race Condition**: Fixed critical bug where duplicate message handlers prevented proper `globalPickedNumbers` updates
- **Enhanced Bot Logging**: Added detailed pick tracking and duplicate detection in Bot Manager
- **Message Handler Separation**: Separated bot and human pick handling to prevent conflicts
### Future Enhancements ### Future Enhancements
- Serialized bot picking to eliminate race conditions
- Configurable debug levels - Configurable debug levels
- Additional game modes and variants - Additional game modes and variants
- Enhanced visual effects and animations - Enhanced visual effects and animations

View file

@ -8,10 +8,15 @@ list validatePicksData(list picksData, list names) {
string entry = llList2String(picksData, i); string entry = llList2String(picksData, i);
list parts = llParseString2List(entry, ["|"], []); list parts = llParseString2List(entry, ["|"], []);
// Handle entries ending with "|" (empty picks) - LSL drops trailing empty elements
if (llGetListLength(parts) == 1 && llGetSubString(entry, -1, -1) == "|") {
parts += [""]; // Add the empty picks part back
}
if (llGetListLength(parts) == 2) { if (llGetListLength(parts) == 2) {
string name = llList2String(parts, 0); string name = llList2String(parts, 0);
string picks = llList2String(parts, 1); string picks = llList2String(parts, 1);
list pickNums = llParseString2List(picks, [","], []); list pickNums = llParseString2List(picks, [";"], []);
// Check if name is in names list // Check if name is in names list
if (names != [] && llListFindList(names, [name]) == -1) { if (names != [] && llListFindList(names, [name]) == -1) {
@ -23,9 +28,7 @@ list validatePicksData(list picksData, list names) {
list uniqueCheck = []; list uniqueCheck = [];
for (j = 0; j < llGetListLength(pickNums); j++) { for (j = 0; j < llGetListLength(pickNums); j++) {
string pick = llList2String(pickNums, j); string pick = llList2String(pickNums, j);
if ((string)((float)pick) != pick) { // Skip validation warnings for cosmetic issues
llOwnerSay("⚠️ Invalid number in picks for " + name + ": '" + pick + "'");
}
if (~llListFindList(uniqueCheck, [pick])) { if (~llListFindList(uniqueCheck, [pick])) {
llOwnerSay("🚨 Duplicate pick in " + name + "'s picks: " + pick); llOwnerSay("🚨 Duplicate pick in " + name + "'s picks: " + pick);
} else { } else {
@ -75,7 +78,12 @@ default {
} }
list lives = llCSV2List(llList2String(parts, 0)); list lives = llCSV2List(llList2String(parts, 0));
list picksData = llCSV2List(llList2String(parts, 1)); // Use ^ delimiter for picksData to avoid comma conflicts
string picksDataStr = llList2String(parts, 1);
list picksData = [];
if (picksDataStr != "" && picksDataStr != "EMPTY") {
picksData = llParseString2List(picksDataStr, ["^"], []);
}
string perilPlayer = llList2String(parts, 2); string perilPlayer = llList2String(parts, 2);
list names = llCSV2List(llList2String(parts, 3)); list names = llCSV2List(llList2String(parts, 3));
@ -92,9 +100,7 @@ default {
} }
@skipCheck; @skipCheck;
if (hasPicksData) { // Only show checking message for actual issues, not routine validation
llOwnerSay("🔍 Checking picksData...");
}
validatePicksData(picksData, names); validatePicksData(picksData, names);
} }
} }

View file

@ -28,6 +28,10 @@ list getPicksFor(string nameInput) {
if (entry != "") llOwnerSay("⚠️ Malformed picks entry (missing pipe): " + entry); if (entry != "") llOwnerSay("⚠️ Malformed picks entry (missing pipe): " + entry);
} else { } else {
list parts = llParseString2List(entry, ["|"], []); list parts = llParseString2List(entry, ["|"], []);
// Handle entries ending with "|" (empty picks) - LSL drops trailing empty elements
if (llGetListLength(parts) == 1 && llGetSubString(entry, -1, -1) == "|") {
parts += [""]; // Add the empty picks part back
}
if (llGetListLength(parts) < 2) { if (llGetListLength(parts) < 2) {
if (entry != "") llOwnerSay("⚠️ Malformed picks entry (too few parts): " + entry); if (entry != "") llOwnerSay("⚠️ Malformed picks entry (too few parts): " + entry);
} }
@ -37,22 +41,14 @@ list getPicksFor(string nameInput) {
// Empty picks are normal during initialization, don't warn // Empty picks are normal during initialization, don't warn
return []; return [];
} else { } else {
// Check for corruption markers (^ symbols that shouldn't be in picks) // Convert semicolons back to commas for display
if (llSubStringIndex(pickString, "^") != -1) { list pickList = llParseString2List(pickString, [";"], []);
llOwnerSay("⚠️ Corrupted picks data for " + nameInput + " (contains ^): '" + pickString + "'");
return [];
}
// Convert semicolons back to commas, then parse
string convertedPicks = llDumpList2String(llParseString2List(pickString, [";"], []), ",");
list all = llParseString2List(convertedPicks, [","], []);
list filtered = []; list filtered = [];
integer j; integer j;
for (j = 0; j < llGetListLength(all); j++) { for (j = 0; j < llGetListLength(pickList); j++) {
string val = llStringTrim(llList2String(all, j), STRING_TRIM); string val = llStringTrim(llList2String(pickList, j), STRING_TRIM);
if (val != "" && (string)((integer)val) == val) { if (val != "" && (string)((integer)val) == val) {
filtered += [val]; filtered += [val];
} else {
llOwnerSay("⚠️ Invalid number in picks for " + nameInput + ": '" + val + "'");
} }
} }
return filtered; return filtered;
@ -89,7 +85,7 @@ default {
key avKey = llList2Key(info, 1); key avKey = llList2Key(info, 1);
names += [name]; names += [name];
players += [avKey]; players += [avKey];
llOwnerSay("📝 Registered: " + name); 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
// float manager has updated its internal lists before attempting // float manager has updated its internal lists before attempting
@ -129,7 +125,6 @@ default {
pos = basePos + <1 + (float)idx * 0.5, 0, 1>; pos = basePos + <1 + (float)idx * 0.5, 0, 1>;
} }
integer ch = -777000 + idx; integer ch = -777000 + idx;
llOwnerSay("📦 Rezzing float for " + name + " at " + (string)pos);
llSetObjectDesc(name); llSetObjectDesc(name);
llRezObject("StatFloat", pos, ZERO_VECTOR, ZERO_ROTATION, ch); llRezObject("StatFloat", pos, ZERO_VECTOR, ZERO_ROTATION, ch);
// After rezzing, immediately update the float so it displays the // After rezzing, immediately update the float so it displays the
@ -147,11 +142,8 @@ default {
integer ch = -777000 + idx; integer ch = -777000 + idx;
integer lifeCount = llList2Integer(lives, idx); integer lifeCount = llList2Integer(lives, idx);
llOwnerSay("🔍 Checking picksData: " + name);
list picks = getPicksFor(name); list picks = getPicksFor(name);
llOwnerSay("🛠️ Updating float for " + name);
llOwnerSay("🔎 Found picks: " + llList2CSV(picks));
string perilName; string perilName;
// If the perilPlayer string is empty or contains a comma (indicating // If the perilPlayer string is empty or contains a comma (indicating
@ -169,7 +161,6 @@ default {
string picksDisplay = llList2CSV(picks); string picksDisplay = llList2CSV(picks);
string txt = "🎲 Peril Dice\n👤 " + name + "\n❤️ Lives: " + (string)lifeCount + "\n" + perilName + "\n🔢 Picks: " + picksDisplay; string txt = "🎲 Peril Dice\n👤 " + name + "\n❤️ Lives: " + (string)lifeCount + "\n" + perilName + "\n🔢 Picks: " + picksDisplay;
llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt); llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt);
llOwnerSay("📤 Sent FLOAT message on channel " + (string)ch + ": " + txt);
} }
else if (num == MSG_CLEANUP_FLOAT) { else if (num == MSG_CLEANUP_FLOAT) {
integer ch = (integer)str; integer ch = (integer)str;
@ -177,7 +168,6 @@ default {
// 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");
llOwnerSay("🧹 Sent CLEANUP to channel " + (string)ch);
// Only clean up internal lists if this corresponds to a valid player // Only clean up internal lists if this corresponds to a valid player
if (idx >= 0 && idx < llGetListLength(players)) { if (idx >= 0 && idx < llGetListLength(players)) {
@ -185,23 +175,19 @@ default {
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); picksData = llDeleteSubList(picksData, idx, idx);
llOwnerSay("🗑️ Cleaned up internal data for player index " + (string)idx);
} }
} }
else if (num == MSG_SYNC_GAME_STATE) { else if (num == MSG_SYNC_GAME_STATE) {
// Synchronize the lists for lives and picksData when receiving a new game state // Synchronize the lists for lives and picksData when receiving a new game state
llOwnerSay("🔍 DEBUG: Floater Manager received sync: " + str);
list parts = llParseString2List(str, ["~"], []); list parts = llParseString2List(str, ["~"], []);
lives = llCSV2List(llList2String(parts, 0)); string livesStr = llList2String(parts, 0);
lives = llCSV2List(livesStr);
// Use ^ delimiter for picksData to avoid comma conflicts // Use ^ delimiter for picksData to avoid comma conflicts
string picksDataStr = llList2String(parts, 1); string picksDataStr = llList2String(parts, 1);
llOwnerSay("🔍 DEBUG: Raw picksDataStr: '" + picksDataStr + "'");
if (picksDataStr == "" || picksDataStr == "EMPTY") { if (picksDataStr == "" || picksDataStr == "EMPTY") {
picksData = []; picksData = [];
llOwnerSay("🔍 DEBUG: Parsed picksData: (empty)");
} else { } else {
picksData = llParseString2List(picksDataStr, ["^"], []); picksData = llParseString2List(picksDataStr, ["^"], []);
llOwnerSay("🔍 DEBUG: Parsed picksData: " + llDumpList2String(picksData, " | "));
} }
string receivedPeril = llList2String(parts, 2); string receivedPeril = llList2String(parts, 2);
string oldPeril = perilPlayer; string oldPeril = perilPlayer;
@ -211,10 +197,6 @@ default {
perilPlayer = receivedPeril; perilPlayer = receivedPeril;
} }
// Debug peril player changes
if (oldPeril != perilPlayer) {
llOwnerSay("🔄 Floater Manager: Peril player changed from '" + oldPeril + "' to '" + perilPlayer + "'");
}
// After synchronizing the game state, update all existing floats so // After synchronizing the game state, update all existing floats so
// they reflect the current peril status. Without this, floats // they reflect the current peril status. Without this, floats

133
Main.lsl
View file

@ -68,8 +68,6 @@ updateHelpers() {
if (roundStarted) { if (roundStarted) {
perilForSync = perilPlayer; perilForSync = perilPlayer;
} }
llOwnerSay("🔍 updateHelpers() - lives: " + llList2CSV(lives) + ", roundStarted: " + (string)roundStarted);
llOwnerSay("🔍 updateHelpers() - perilPlayer: '" + perilPlayer + "', perilForSync: '" + perilForSync + "'");
// Encode picksData with a safe delimiter that won't conflict with player names or picks // Encode picksData with a safe delimiter that won't conflict with player names or picks
// Replace commas in picks with semicolons to avoid CSV conflicts // Replace commas in picks with semicolons to avoid CSV conflicts
list encodedPicksData = []; list encodedPicksData = [];
@ -137,17 +135,19 @@ resetGame() {
integer i; integer i;
for (i = 0; i < llGetListLength(floaterChannels); i++) { for (i = 0; i < llGetListLength(floaterChannels); i++) {
integer ch = llList2Integer(floaterChannels, i); integer ch = llList2Integer(floaterChannels, i);
llOwnerSay("🔍 Cleaning up floater channel " + (string)ch + " during reset");
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY); llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
} }
// Also send cleanup for all possible floater channels in case of orphaned floaters // Only send cleanup for channels that might actually have floaters
// This handles any duplicate floaters that weren't properly tracked // Check up to the number of players that were registered
for (i = 0; i < MAX_PLAYERS; i++) { integer maxUsedIdx = llGetListLength(names);
for (i = 0; i < maxUsedIdx; i++) {
integer ch = -777000 + i; integer ch = -777000 + i;
llOwnerSay("🧹 Sending cleanup for possible orphaned floater channel " + (string)ch); // Only send cleanup if not already in tracked channels
if (llListFindList(floaterChannels, [ch]) == -1) {
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY); llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
} }
}
players = names = lives = picksData = globalPickedNumbers = readyPlayers = []; players = names = lives = picksData = globalPickedNumbers = readyPlayers = [];
floaterChannels = []; // Clear the tracked channels floaterChannels = []; // Clear the tracked channels
perilPlayer = ""; perilPlayer = "";
@ -250,23 +250,20 @@ startNextRound() {
} }
// If somehow called with exactly one player, treat them as the winner and reset. // If somehow called with exactly one player, treat them as the winner and reset.
if (llGetListLength(names) == 1) { if (llGetListLength(names) == 1) {
llSay(0, " " + llList2String(names, 0) + " is the last player standing and wins the game!"); llSay(0, "✨ ULTIMATE VICTORY! " + llList2String(names, 0) + " is the Ultimate Survivor!");
resetGame(); resetGame();
return; return;
} }
// Reset round flag for new round // DON'T reset round flag during continuation rounds - only reset for brand new games
roundStarted = FALSE; // This prevents peril player from being lost during round transitions
llOwnerSay("🔍 DEBUG: startNextRound() called with perilPlayer = '" + perilPlayer + "'");
// Select random initial peril player if none is set (only for very first round) // Select random initial peril player if none is set (only for very first round)
if (perilPlayer == "" || perilPlayer == "NONE") { if (perilPlayer == "" || perilPlayer == "NONE") {
integer randomIdx = (integer)llFrand(llGetListLength(names)); integer randomIdx = (integer)llFrand(llGetListLength(names));
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!");
llOwnerSay("🔍 DEBUG: Selected random peril player: " + perilPlayer);
} else { } else {
llOwnerSay("🔍 DEBUG: Continuing with existing peril player: " + perilPlayer);
} }
picksData = []; picksData = [];
globalPickedNumbers = []; globalPickedNumbers = [];
@ -303,14 +300,13 @@ showNextPickerDialog() {
if (perilIdx != -1) { if (perilIdx != -1) {
perilLives = llList2Integer(lives, perilIdx); perilLives = llList2Integer(lives, perilIdx);
} }
llOwnerSay("🔍 DEBUG BOT: Peril player '" + perilPlayer + "' lives=" + (string)perilLives + " (idx=" + (string)perilIdx + ")");
// Pick count = 4 - peril player's lives (3 lives=1 pick, 2 lives=2 picks, 1 life=3 picks) // Pick count = 4 - peril player's lives (3 lives=1 pick, 2 lives=2 picks, 1 life=3 picks)
integer picksNeeded = 4 - perilLives; integer picksNeeded = 4 - perilLives;
// Send command to Bot Manager to auto-pick numbers (include already picked numbers) // Send command to Bot Manager to auto-pick numbers (include already picked numbers)
// CRITICAL: Use the most current globalPickedNumbers to prevent race conditions
string alreadyPicked = llList2CSV(globalPickedNumbers); string alreadyPicked = llList2CSV(globalPickedNumbers);
string botCommand = "BOT_PICK:" + firstName + ":" + (string)picksNeeded + ":" + (string)diceType + ":" + alreadyPicked; string botCommand = "BOT_PICK:" + firstName + ":" + (string)picksNeeded + ":" + (string)diceType + ":" + alreadyPicked;
llOwnerSay("📡 Sending to Bot Manager: " + botCommand);
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 {
@ -320,15 +316,11 @@ showNextPickerDialog() {
if (perilIdx != -1) { if (perilIdx != -1) {
perilLives = llList2Integer(lives, perilIdx); perilLives = llList2Integer(lives, perilIdx);
} }
llOwnerSay("🔍 DEBUG: Peril player '" + perilPlayer + "' lives=" + (string)perilLives + " (idx=" + (string)perilIdx + ")");
llOwnerSay("🔍 DEBUG: All lives: " + llList2CSV(lives));
integer picksNeeded = 4 - perilLives; integer picksNeeded = 4 - perilLives;
llOwnerSay("🔍 DEBUG: Picks needed calculation: 4 - " + (string)perilLives + " = " + (string)picksNeeded);
// Include already picked numbers so player can't pick duplicates // Include already picked numbers so player can't pick duplicates
string alreadyPicked = llList2CSV(globalPickedNumbers); string alreadyPicked = llList2CSV(globalPickedNumbers);
string dialogPayload = firstName + "|" + (string)diceType + "|" + (string)picksNeeded + "|" + alreadyPicked; string dialogPayload = firstName + "|" + (string)diceType + "|" + (string)picksNeeded + "|" + alreadyPicked;
llOwnerSay("🎯 Sending dialog to " + firstName + " with diceType=" + (string)diceType + ", picksNeeded=" + (string)picksNeeded);
llMessageLinked(LINK_SET, MSG_SHOW_DIALOG, dialogPayload, currentPicker); llMessageLinked(LINK_SET, MSG_SHOW_DIALOG, dialogPayload, currentPicker);
timeoutTimer = llGetUnixTime(); timeoutTimer = llGetUnixTime();
lastWarning = 0; lastWarning = 0;
@ -374,18 +366,15 @@ default {
// Already registered; check if owner is the first human player // Already registered; check if owner is the first human player
string ownerName = llList2String(names, idx); string ownerName = llList2String(names, idx);
isStarter = TRUE; // Default to TRUE for owner isStarter = TRUE; // Default to TRUE for owner
llOwnerSay("🔍 Owner check: idx=" + (string)idx + ", ownerName=" + ownerName);
integer k; integer k;
for (k = 0; k < idx && isStarter; k++) { for (k = 0; k < idx && isStarter; k++) {
string existingName = llList2String(names, k); string existingName = llList2String(names, k);
llOwnerSay("🔍 Checking name[" + (string)k + "] = " + existingName + " (isBot=" + (string)(llSubStringIndex(existingName, "TestBot") == 0) + ")");
// If there's a human player before the owner, owner is not starter // If there's a human player before the owner, owner is not starter
if (llSubStringIndex(existingName, "TestBot") != 0) { if (llSubStringIndex(existingName, "TestBot") != 0) {
isStarter = FALSE; isStarter = FALSE;
llOwnerSay("❌ Found human player before owner, not starter"); llOwnerSay("❌ Found human player before owner, not starter");
} }
} }
llOwnerSay("🔍 Final isStarter = " + (string)isStarter);
} }
// Show the owner menu with the appropriate starter flag // Show the owner menu with the appropriate starter flag
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, toucher); llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, toucher);
@ -512,32 +501,40 @@ default {
// Prevent joining game in progress (except owner accessing menu) // Prevent joining game in progress (except owner accessing menu)
if (roundStarted && newKey != llGetOwner()) { if (roundStarted && newKey != llGetOwner()) {
llOwnerSay("⚠️ " + newName + " cannot join - game in progress"); llOwnerSay("🚫 " + newName + " cannot join - the killing game has begun!");
llRegionSayTo(newKey, 0, "⚠️ Cannot join game in progress. Wait for current game to finish."); llRegionSayTo(newKey, 0, "🚫 The killing game has already begun! Wait for the current game to end.");
return; return;
} }
// Do not register if already present // Do not register if already present
integer existingIdx = llListFindList(players, [newKey]); integer existingIdx = llListFindList(players, [newKey]);
llOwnerSay("🐛 DEBUG: Registration request for " + newName + " (key: " + (string)newKey + "), existingIdx: " + (string)existingIdx);
llOwnerSay("🐛 DEBUG: Current players: " + llList2CSV(players));
llOwnerSay("🐛 DEBUG: Current lives: " + llList2CSV(lives));
if (existingIdx == -1) { if (existingIdx == -1) {
// Add to local lists // Add to local lists
players += [newKey]; players += [newKey];
names += [newName]; names += [newName];
lives += [3]; lives += [3];
llOwnerSay("🐛 DEBUG: Added player " + newName + " with 3 lives. Lives now: " + llList2CSV(lives));
picksData += [newName + "|"]; picksData += [newName + "|"];
// Track the floater channel for this player // Track the floater channel for this player
integer newPlayerIdx = llGetListLength(names) - 1; integer newPlayerIdx = llGetListLength(names) - 1;
integer ch = -777000 + newPlayerIdx; integer ch = -777000 + newPlayerIdx;
floaterChannels += [ch]; floaterChannels += [ch];
llOwnerSay("🔍 Added floater channel " + (string)ch + " for " + newName);
// Auto-mark bots as ready, leave humans as not ready // Auto-mark bots as ready, leave humans as not ready
if (llSubStringIndex(newName, "TestBot") == 0) { if (llSubStringIndex(newName, "TestBot") == 0) {
readyPlayers += [newName]; readyPlayers += [newName];
llOwnerSay("🤖 Auto-marked bot " + newName + " as ready"); llSay(0, "🤖 " + newName + " boots up with deadly precision - ready to play! 🤖");
} else {
// Check if this is the first human player (starter - automatically ready)
integer humanCount = 0;
integer i;
for (i = 0; i < llGetListLength(names); i++) {
string playerName = llList2String(names, i);
if (llSubStringIndex(playerName, "TestBot") != 0) {
humanCount++;
}
}
if (humanCount == 1) { // This is the first human player
llSay(0, "👑 " + newName + " steps forward as the game master - automatically ready for the deadly challenge! 👑");
}
} }
// The float will be rezzed by the Floater Manager after it processes // The float will be rezzed by the Floater Manager after it processes
// this registration. Avoid sending MSG_REZ_FLOAT here to prevent // this registration. Avoid sending MSG_REZ_FLOAT here to prevent
@ -559,7 +556,6 @@ default {
if (idx != -1) { if (idx != -1) {
// Remove player's float using the tracked channel // Remove player's float using the tracked channel
integer ch = llList2Integer(floaterChannels, idx); integer ch = llList2Integer(floaterChannels, idx);
llOwnerSay("🔍 Cleaning up floater channel " + (string)ch + " for " + eliminatedPlayer);
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY); llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
// Remove from all lists // Remove from all lists
players = llDeleteSubList(players, idx, idx); players = llDeleteSubList(players, idx, idx);
@ -576,11 +572,11 @@ default {
// Check if game should end (1 or fewer players remaining) // Check if game should end (1 or fewer players remaining)
if (llGetListLength(names) <= 1) { if (llGetListLength(names) <= 1) {
if (llGetListLength(names) == 1) { if (llGetListLength(names) == 1) {
llSay(0, "🏆 " + llList2String(names, 0) + " wins the game!"); llSay(0, "✨ ULTIMATE VICTORY! " + llList2String(names, 0) + " is the Ultimate Survivor!");
// Trigger victory confetti // Trigger victory confetti
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY); llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
} else { } else {
llSay(0, "🏆 Game over - no players remaining!"); llSay(0, "💀 DESPAIR WINS! No Ultimate Survivors remain!");
} }
resetGame(); resetGame();
return; return;
@ -653,10 +649,8 @@ default {
// Check if current peril player is still in the game // Check if current peril player is still in the game
integer perilStillInGame = llListFindList(names, [perilPlayer]) != -1; integer perilStillInGame = llListFindList(names, [perilPlayer]) != -1;
if (perilStillInGame) { if (perilStillInGame) {
llOwnerSay("🔍 DEBUG: Ignoring sync attempt to change peril player from '" + perilPlayer + "' to 'NONE'");
return; // Don't process this sync update return; // Don't process this sync update
} else { } else {
llOwnerSay("🔍 DEBUG: Allowing peril player clear because current peril '" + perilPlayer + "' is no longer in game");
} }
} }
@ -664,7 +658,6 @@ default {
integer livesChanged = (newLivesStr != currentLivesStr); integer livesChanged = (newLivesStr != currentLivesStr);
if (livesChanged || perilChanged) { if (livesChanged || perilChanged) {
llOwnerSay("🔍 DEBUG: Sync update needed - livesChanged=" + (string)livesChanged + ", perilChanged=" + (string)perilChanged);
// Decode the pickData from the encoded format // Decode the pickData from the encoded format
string encodedPicksDataStr = llList2String(parts, 1); string encodedPicksDataStr = llList2String(parts, 1);
list newPicksData = []; list newPicksData = [];
@ -690,7 +683,6 @@ default {
// Debug peril player changes // Debug peril player changes
if (perilPlayer != newPerilPlayer) { if (perilPlayer != newPerilPlayer) {
llOwnerSay("🔍 DEBUG: Main Controller peril change: '" + perilPlayer + "' -> '" + newPerilPlayer + "'");
} }
// Update our game state with the new data // Update our game state with the new data
@ -726,7 +718,7 @@ default {
requestDiceType(); requestDiceType();
} else { } else {
// Show dialog for humans // Show dialog for humans
llDialog(playerKey, "🎯 You're in peril! Ready for the next round?", ["Start Next Round"], -77999); llDialog(playerKey, "💀 YOU ARE IN ULTIMATE PERIL! Are you ready to face the deadly challenge?", ["BEGIN KILLING GAME"], -77999);
} }
} }
} }
@ -750,7 +742,6 @@ default {
if (idx != -1) { if (idx != -1) {
// Remove player's float using the tracked channel // Remove player's float using the tracked channel
integer ch = llList2Integer(floaterChannels, idx); integer ch = llList2Integer(floaterChannels, idx);
llOwnerSay("🔍 Cleaning up floater channel " + (string)ch + " for " + leavingName);
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY); llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
// Remove from all lists including ready state // Remove from all lists including ready state
players = llDeleteSubList(players, idx, idx); players = llDeleteSubList(players, idx, idx);
@ -768,7 +759,7 @@ default {
llOwnerSay("👋 " + leavingName + " left the game"); llOwnerSay("👋 " + leavingName + " left the game");
// Check if game should end (less than 2 players) // Check if game should end (less than 2 players)
if (llGetListLength(names) == 1) { if (llGetListLength(names) == 1) {
llSay(0, " " + llList2String(names, 0) + " is the last player standing and wins the game!"); llSay(0, "✨ ULTIMATE VICTORY! " + llList2String(names, 0) + " survives the killing game!");
// Trigger victory confetti // Trigger victory confetti
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY); llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
resetGame(); resetGame();
@ -794,13 +785,12 @@ default {
if (readyIdx == -1) { if (readyIdx == -1) {
// Player is not ready, make them ready // Player is not ready, make them ready
readyPlayers += [playerName]; readyPlayers += [playerName];
llOwnerSay("✅ " + playerName + " is now ready"); llSay(0, "⚔️ " + playerName + " steels themselves for the deadly challenge ahead! ⚔️");
} else { } else {
// Player is ready, make them not ready // Player is ready, make them not ready
readyPlayers = llDeleteSubList(readyPlayers, readyIdx, readyIdx); readyPlayers = llDeleteSubList(readyPlayers, readyIdx, readyIdx);
llOwnerSay("❌ " + playerName + " is no longer ready"); llSay(0, "🏃 " + playerName + " loses their nerve and backs away from the challenge! 🏃");
} }
llOwnerSay("🔍 Ready players: " + llList2CSV(readyPlayers));
} }
return; return;
} }
@ -815,7 +805,6 @@ default {
} }
// Handle aggressive floater cleanup // Handle aggressive floater cleanup
if (num == MSG_CLEANUP_ALL_FLOATERS) { if (num == MSG_CLEANUP_ALL_FLOATERS) {
llOwnerSay("🧹 Performing aggressive floater cleanup for all possible channels...");
integer i; integer i;
for (i = 0; i < MAX_PLAYERS; i++) { for (i = 0; i < MAX_PLAYERS; i++) {
integer ch = -777000 + i; integer ch = -777000 + i;
@ -826,12 +815,10 @@ default {
} }
// Reset our tracked channels list // Reset our tracked channels list
floaterChannels = []; floaterChannels = [];
llOwnerSay("🧹 Aggressive cleanup completed!");
return; return;
} }
// Handle HUMAN_PICKED messages from dialog handler // Handle HUMAN_PICKED messages from dialog handler
if (num == -9998 && llSubStringIndex(str, "HUMAN_PICKED:") == 0) { if (num == -9998 && llSubStringIndex(str, "HUMAN_PICKED:") == 0) {
llOwnerSay("📨 Main Controller received: " + str);
list parts = llParseString2List(str, [":"], []); list parts = llParseString2List(str, [":"], []);
if (llGetListLength(parts) >= 3) { if (llGetListLength(parts) >= 3) {
string playerName = llList2String(parts, 1); string playerName = llList2String(parts, 1);
@ -865,7 +852,7 @@ default {
} }
} }
updateHelpers(); updateHelpers();
llOwnerSay("👤 " + playerName + " picked: " + picksStr); llSay(0, "🎯 " + playerName + " stakes their life on numbers: " + picksStr + " 🎲");
// Move to next picker // Move to next picker
currentPickerIdx++; currentPickerIdx++;
@ -874,13 +861,11 @@ default {
} else { } else {
llOwnerSay("✅ All players have picked their numbers!"); llOwnerSay("✅ All players have picked their numbers!");
// Show roll dialog to peril player // Show roll dialog to peril player
llOwnerSay("🔍 DEBUG: About to show roll dialog - perilPlayer: '" + perilPlayer + "'");
if (perilPlayer == "" || perilPlayer == "NONE") { if (perilPlayer == "" || perilPlayer == "NONE") {
llOwnerSay("❌ ERROR: Cannot show roll dialog - perilPlayer is invalid: '" + perilPlayer + "'"); llOwnerSay("❌ ERROR: Cannot show roll dialog - perilPlayer is invalid: '" + perilPlayer + "'");
return; return;
} }
key perilKey = llList2Key(players, llListFindList(names, [perilPlayer])); key perilKey = llList2Key(players, llListFindList(names, [perilPlayer]));
llOwnerSay("🔍 DEBUG: Sending roll dialog to '" + perilPlayer + "' (key: " + (string)perilKey + ")");
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, perilKey); llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, perilKey);
} }
} }
@ -889,12 +874,12 @@ default {
} }
// Handle BOT_PICKED messages from Bot Manager // Handle BOT_PICKED messages from Bot Manager
if (num == -9997 && llSubStringIndex(str, "BOT_PICKED:") == 0) { if (num == -9997 && llSubStringIndex(str, "BOT_PICKED:") == 0) {
llOwnerSay("📨 Main Controller received: " + str);
list parts = llParseString2List(str, [":"], []); list parts = llParseString2List(str, [":"], []);
if (llGetListLength(parts) >= 3) { if (llGetListLength(parts) >= 3) {
string playerName = llList2String(parts, 1); string playerName = llList2String(parts, 1);
string picksStr = llList2String(parts, 2); string picksStr = llList2String(parts, 2);
// Update the player's picks in picksData // Update the player's picks in picksData
integer idx = llListFindList(names, [playerName]); integer idx = llListFindList(names, [playerName]);
if (idx != -1) { if (idx != -1) {
@ -913,32 +898,33 @@ default {
} else { } else {
picksData = llListReplaceList(picksData, [newEntry], picksIdx, picksIdx); picksData = llListReplaceList(picksData, [newEntry], picksIdx, picksIdx);
} }
// Update global picked numbers list // Update global picked numbers list - bot picks use semicolon delimiters
list newPicks = llParseString2List(picksStr, [","], []); list newPicks = llParseString2List(picksStr, [";"], []);
integer j; integer j;
for (j = 0; j < llGetListLength(newPicks); j++) { for (j = 0; j < llGetListLength(newPicks); j++) {
string pick = llList2String(newPicks, j); string pick = llList2String(newPicks, j);
if (llListFindList(globalPickedNumbers, [pick]) == -1) { if (llListFindList(globalPickedNumbers, [pick]) == -1) {
globalPickedNumbers += [pick]; globalPickedNumbers += [pick];
} else {
llOwnerSay("⚠️ WARNING: " + playerName + " picked " + pick + " which was already in globalPickedNumbers!");
} }
} }
updateHelpers(); updateHelpers();
llOwnerSay("🤖 " + playerName + " picked: " + picksStr); llSay(0, "🎯 " + playerName + " (bot) stakes their digital life on numbers: " + picksStr + " 🎲");
// Move to next picker // Move to next picker
currentPickerIdx++; currentPickerIdx++;
if (currentPickerIdx < llGetListLength(pickQueue)) { if (currentPickerIdx < llGetListLength(pickQueue)) {
string nextPicker = llList2String(pickQueue, currentPickerIdx);
showNextPickerDialog(); showNextPickerDialog();
} else { } else {
llOwnerSay("✅ All players have picked their numbers!"); llOwnerSay("✅ All players have picked their numbers!");
// Show roll dialog to peril player // Show roll dialog to peril player
llOwnerSay("🔍 DEBUG: About to show roll dialog - perilPlayer: '" + perilPlayer + "'");
if (perilPlayer == "" || perilPlayer == "NONE") { if (perilPlayer == "" || perilPlayer == "NONE") {
llOwnerSay("❌ ERROR: Cannot show roll dialog - perilPlayer is invalid: '" + perilPlayer + "'"); llOwnerSay("❌ ERROR: Cannot show roll dialog - perilPlayer is invalid: '" + perilPlayer + "'");
return; return;
} }
key perilKey = llList2Key(players, llListFindList(names, [perilPlayer])); key perilKey = llList2Key(players, llListFindList(names, [perilPlayer]));
llOwnerSay("🔍 DEBUG: Sending roll dialog to '" + perilPlayer + "' (key: " + (string)perilKey + ")");
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, perilKey); llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, perilKey);
} }
} }
@ -949,41 +935,10 @@ default {
listen(integer channel, string name, key id, string msg) { listen(integer channel, string name, key id, string msg) {
// Handle bot and human responses
if (channel == -9999 && (llSubStringIndex(msg, "BOT_PICKED:") == 0 || llSubStringIndex(msg, "HUMAN_PICKED:") == 0)) {
list parts = llParseString2List(msg, [":"], []);
if (llGetListLength(parts) >= 3) {
string playerName = llList2String(parts, 1);
string picksStr = llList2String(parts, 2);
// Update the player's picks in picksData
integer idx = llListFindList(names, [playerName]);
if (idx != -1) {
picksData = llListReplaceList(picksData, [playerName + "|" + picksStr], idx, idx);
updateHelpers();
string icon = "🤖";
if (llSubStringIndex(msg, "HUMAN_PICKED:") == 0) icon = "👤";
llOwnerSay(icon + " " + playerName + " picked: " + picksStr);
// Move to next picker
currentPickerIdx++;
if (currentPickerIdx < llGetListLength(pickQueue)) {
showNextPickerDialog();
} else {
llOwnerSay("✅ All players have picked their numbers!");
// Show roll dialog to peril player
key perilKey = llList2Key(players, llListFindList(names, [perilPlayer]));
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, perilKey);
}
}
}
return;
}
if (channel == rollDialogChannel) { if (channel == rollDialogChannel) {
if (msg == "Start Next Round") { if (msg == "Start Next Round" || msg == "BEGIN KILLING GAME") {
llOwnerSay("🔍 DEBUG: Start Next Round clicked by " + llKey2Name(id) + ", but perilPlayer=" + perilPlayer); llSay(0, "⚡ THE KILLING GAME CONTINUES! " + perilPlayer + " begins the next deadly round!");
llSay(0, "🎯 " + perilPlayer + " is starting the next round!");
startNextRound(); startNextRound();
requestDiceType(); requestDiceType();
return; return;
@ -1078,7 +1033,7 @@ default {
} }
// All checks passed, start the game // All checks passed, start the game
llOwnerSay("✅ All players ready! Starting game..."); llSay(0, "⚡ ALL PARTICIPANTS READY! THE DEADLY PERIL DICE GAME BEGINS! ⚡");
startNextRound(); startNextRound();
requestDiceType(); requestDiceType();
} }

View file

@ -41,7 +41,6 @@ list getNumbersForPage(integer page, integer diceType) {
} }
showPickDialog(string name, key id, integer diceType, integer picks) { showPickDialog(string name, key id, integer diceType, integer picks) {
llOwnerSay("🎮 showPickDialog called: name=" + name + ", id=" + (string)id + ", diceType=" + (string)diceType + ", picks=" + (string)picks);
if (diceType <= 0) { if (diceType <= 0) {
llOwnerSay("⚠️ Cannot show dialog: diceType is 0 or invalid."); llOwnerSay("⚠️ Cannot show dialog: diceType is 0 or invalid.");
return; return;
@ -98,7 +97,6 @@ showPickDialog(string name, key id, integer diceType, integer picks) {
dialogText = "Pick " + pickText + " (1-" + (string)diceType + ")" + pageInfo + currentPicksText; dialogText = "Pick " + pickText + " (1-" + (string)diceType + ")" + pageInfo + currentPicksText;
} }
llOwnerSay("🗨️ Showing dialog to: " + name + " - needs " + pickText + pageInfo);
llDialog(id, dialogText, options, numberPickChannel); llDialog(id, dialogText, options, numberPickChannel);
} }
@ -109,9 +107,7 @@ default {
} }
link_message(integer sender, integer num, string str, key id) { link_message(integer sender, integer num, string str, key id) {
llOwnerSay("📥 Dialog Handler received: num=" + (string)num + ", str=" + str + ", id=" + (string)id);
if (num == MSG_SHOW_DIALOG) { if (num == MSG_SHOW_DIALOG) {
llOwnerSay("✅ Processing MSG_SHOW_DIALOG...");
list parts = llParseString2List(str, ["|"], []); list parts = llParseString2List(str, ["|"], []);
if (llGetListLength(parts) < 3) { if (llGetListLength(parts) < 3) {
llOwnerSay("⚠️ Malformed MSG_SHOW_DIALOG input: " + str); llOwnerSay("⚠️ Malformed MSG_SHOW_DIALOG input: " + str);
@ -134,16 +130,11 @@ default {
globallyPickedNumbers = []; globallyPickedNumbers = [];
} }
llOwnerSay("🎯 Calling showPickDialog for " + name + " with diceType=" + (string)diceType + ", picks=" + (string)picks);
if (llGetListLength(globallyPickedNumbers) > 0) {
llOwnerSay("📋 Available numbers: " + (string)(diceType - llGetListLength(globallyPickedNumbers)) + "/" + (string)diceType);
}
showPickDialog(name, id, diceType, picks); showPickDialog(name, id, diceType, picks);
} }
} }
listen(integer channel, string name, key id, string message) { listen(integer channel, string name, key id, string message) {
llOwnerSay("🔊 Listen triggered: " + message + " from " + name);
// Handle navigation // Handle navigation
if (message == "◀ Prev") { if (message == "◀ Prev") {
@ -166,9 +157,7 @@ default {
if (message == "✅ Done") { if (message == "✅ Done") {
if (llGetListLength(currentPicks) >= picksNeeded) { if (llGetListLength(currentPicks) >= picksNeeded) {
string picksStr = llList2CSV(currentPicks); string picksStr = llList2CSV(currentPicks);
llOwnerSay("✅ " + currentPlayer + " completed picks: " + picksStr);
string response = "HUMAN_PICKED:" + currentPlayer + ":" + picksStr; string response = "HUMAN_PICKED:" + currentPlayer + ":" + picksStr;
llOwnerSay("📡 Sending to Main Controller: " + response);
llMessageLinked(LINK_SET, -9998, response, NULL_KEY); llMessageLinked(LINK_SET, -9998, response, NULL_KEY);
// Reset state // Reset state
currentPlayer = ""; currentPlayer = "";
@ -187,7 +176,6 @@ default {
if (llGetListLength(currentPicks) < picksNeeded) { if (llGetListLength(currentPicks) < picksNeeded) {
// Add to picks (numbers already picked won't be shown in dialog) // Add to picks (numbers already picked won't be shown in dialog)
currentPicks += [message]; currentPicks += [message];
llOwnerSay("✅ " + currentPlayer + " picked: " + message + " (" + (string)llGetListLength(currentPicks) + "/" + (string)picksNeeded + ")");
} else { } else {
llOwnerSay("⚠️ " + currentPlayer + " has already picked enough numbers"); llOwnerSay("⚠️ " + currentPlayer + " has already picked enough numbers");
} }

View file

@ -11,6 +11,8 @@ Peril Dice is a multiplayer elimination game where each player selects numbers b
- **📱 Floating HUD Display**: Real-time stats for each player - **📱 Floating HUD Display**: Real-time stats for each player
- **🎮 Intelligent Dice Sizing**: Automatic dice size based on player count - **🎮 Intelligent Dice Sizing**: Automatic dice size based on player count
- **🔄 Ready State System**: Players must be ready before games start - **🔄 Ready State System**: Players must be ready before games start
- **🎭 Dramatic Messaging**: Immersive thematic announcements visible to all players
- **🎲 Context-Rich Rolls**: Detailed dice information with type and result
- **⚡ Performance Optimized**: Reduced lag with selective particle effects - **⚡ Performance Optimized**: Reduced lag with selective particle effects
- **🛡️ Robust Error Handling**: Comprehensive game state synchronization - **🛡️ Robust Error Handling**: Comprehensive game state synchronization
- **🚫 Game Protection**: Prevents joining games in progress - **🚫 Game Protection**: Prevents joining games in progress
@ -22,8 +24,9 @@ Peril Dice is a multiplayer elimination game where each player selects numbers b
3. **Picking Phase**: Each player picks 1-3 numbers (based on peril player's remaining lives) 3. **Picking Phase**: Each player picks 1-3 numbers (based on peril player's remaining lives)
4. **Rolling Phase**: Peril player rolls the dice 4. **Rolling Phase**: Peril player rolls the dice
5. **Resolution**: 5. **Resolution**:
- If rolled number matches another player's pick → that player becomes new peril player - If rolled number matches another player's pick → that player becomes new peril player (`⚡ PLOT TWIST!`)
- If no match or peril player hits themselves → peril player loses a life - If peril player picked the rolled number → they lose a life (`🩸 DIRECT HIT!`)
- If nobody picked the rolled number → peril player loses a life (`🩸 NO SHIELD!`)
6. **Elimination**: Players with 0 lives are eliminated 6. **Elimination**: Players with 0 lives are eliminated
7. **Victory**: Last player standing wins! 🏆 7. **Victory**: Last player standing wins! 🏆
@ -77,16 +80,18 @@ Dice type is automatically chosen to ensure at least 3 picks per player:
| 5–6 | d20 | | 5–6 | d20 |
| 7–10 | d30 | | 7–10 | d30 |
## Known Issues ## Recent Improvements (v2.2.0)
- **Minor Bot Collision**: Bots may occasionally pick duplicate numbers in rapid succession (race condition) - **🎭 Dramatic Messaging**: All game events now use thematic, immersive language
- **Performance**: Extended games with many bots may experience some lag due to debug output - **📢 Public Announcements**: Players can see all major game events in public chat
- **🎲 Enhanced Dice Messages**: Rolls show both dice type (d6, d8, etc.) and result
- **🔧 Bug Fixes**: Resolved StatFloat duplication and duplicate message handling issues
## Version ## Version
**Current Version**: 2.1.0 **Current Version**: 2.2.0
**Last Updated**: January 2025 **Last Updated**: August 2025
**Status**: Stable - Full game flow functional **Status**: Stable - Enhanced UX with dramatic messaging system
--- ---

View file

@ -27,22 +27,18 @@ list getPicksFor(string nameInput) {
// Check for corruption markers (^ symbols that shouldn't be in picks) // Check for corruption markers (^ symbols that shouldn't be in picks)
if (llSubStringIndex(picks, "^") != -1) { if (llSubStringIndex(picks, "^") != -1) {
llOwnerSay("🔍 DEBUG getPicksFor(" + nameInput + "): CORRUPTED data contains ^: '" + originalPicks + "'");
return []; return [];
} }
if (picks == "") { if (picks == "") {
llOwnerSay("🔍 DEBUG getPicksFor(" + nameInput + "): empty picks");
return []; return [];
} }
picks = llDumpList2String(llParseString2List(picks, [";"], []), ","); picks = llDumpList2String(llParseString2List(picks, [";"], []), ",");
list result = llParseString2List(picks, [","], []); list result = llParseString2List(picks, [","], []);
llOwnerSay("🔍 DEBUG getPicksFor(" + nameInput + "): raw='" + originalPicks + "' -> converted='" + picks + "' -> list=" + llList2CSV(result));
return result; return result;
} }
} }
llOwnerSay("🔍 DEBUG getPicksFor(" + nameInput + "): NOT FOUND in picksData=" + llDumpList2String(picksData, " | "));
return []; return [];
} }
@ -97,7 +93,7 @@ default {
if (num == 995) { if (num == 995) {
if (str == "VICTORY_CONFETTI") { if (str == "VICTORY_CONFETTI") {
llOwnerSay("🎉 Victory confetti!"); llOwnerSay("✨ ULTIMATE VICTORY CELEBRATION!");
confetti(); confetti();
} }
return; return;
@ -119,7 +115,7 @@ default {
llMessageLinked(LINK_SET, 996, "GET_DICE_TYPE", NULL_KEY); llMessageLinked(LINK_SET, 996, "GET_DICE_TYPE", NULL_KEY);
} else { } else {
// Show dialog for human players // Show dialog for human players
llDialog(id, "🎲 All picks are in! You're the peril player. Roll the dice?", ["Roll"], rollDialogChannel); llDialog(id, "🎲 THE MOMENT OF TRUTH! You're in ultimate peril. Will you face your fate?", ["ROLL THE DICE OF FATE"], rollDialogChannel);
} }
} }
} }
@ -128,28 +124,24 @@ default {
integer diceType = (integer)str; integer diceType = (integer)str;
integer result = rollDice(diceType); integer result = rollDice(diceType);
string resultStr = (string)result; string resultStr = (string)result;
llSay(0, "🎲 " + perilPlayer + " rolled a " + resultStr + "!"); llSay(0, "🎲 THE D" + (string)diceType + " OF FATE! " + perilPlayer + " rolled a " + resultStr + " on the " + (string)diceType + "-sided die! 🎲");
string newPeril = ""; string newPeril = "";
integer matched = FALSE; integer matched = FALSE;
list matchedPlayers = []; list matchedPlayers = [];
integer i; integer i;
llOwnerSay("🔍 DEBUG: Current peril player: " + perilPlayer + ", rolled: " + resultStr);
// Find all players who picked the rolled number // Find all players who picked the rolled number
for (i = 0; i < llGetListLength(names); i++) { for (i = 0; i < llGetListLength(names); i++) {
string pname = llList2String(names, i); string pname = llList2String(names, i);
list picks = getPicksFor(pname); list picks = getPicksFor(pname);
llOwnerSay("🔍 DEBUG: " + pname + " picks: " + llList2CSV(picks));
if (llListFindList(picks, [resultStr]) != -1) { if (llListFindList(picks, [resultStr]) != -1) {
matched = TRUE; matched = TRUE;
matchedPlayers += [pname]; matchedPlayers += [pname];
llOwnerSay("🔍 DEBUG: " + pname + " matched the roll!");
} }
} }
llOwnerSay("🔍 DEBUG: Matched players: " + llList2CSV(matchedPlayers));
// If anyone matched, pick the first non-peril player as new peril // If anyone matched, pick the first non-peril player as new peril
// If only the current peril player matched, they stay in peril (no change) // If only the current peril player matched, they stay in peril (no change)
@ -157,12 +149,11 @@ default {
string pname = llList2String(matchedPlayers, i); string pname = llList2String(matchedPlayers, i);
if (pname != perilPlayer) { if (pname != perilPlayer) {
newPeril = pname; newPeril = pname;
llOwnerSay("🔍 DEBUG: New peril player will be: " + newPeril);
} }
} }
if (matched && newPeril != "") { if (matched && newPeril != "") {
llSay(0, "🎯 " + newPeril + " matched the roll and becomes the new peril player!"); llSay(0, "⚡ PLOT TWIST! " + newPeril + " picked " + resultStr + " (rolled on d" + (string)diceType + ") and is now in ULTIMATE PERIL! ⚡");
perilPlayer = newPeril; perilPlayer = newPeril;
// Update floaters immediately to show correct peril player before sync // Update floaters immediately to show correct peril player before sync
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, newPeril, NULL_KEY); llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, newPeril, NULL_KEY);
@ -171,12 +162,21 @@ default {
if (pidx != -1) { if (pidx != -1) {
integer currentLives = llList2Integer(lives, pidx); integer currentLives = llList2Integer(lives, pidx);
lives = llListReplaceList(lives, [currentLives - 1], pidx, pidx); lives = llListReplaceList(lives, [currentLives - 1], pidx, pidx);
llSay(0, "💀 " + perilPlayer + " was hit and lost a life!");
llMessageLinked(LINK_SET, MSG_REZ_FLOAT, perilPlayer, NULL_KEY); // Check if peril player picked the rolled number
list perilPicks = getPicksFor(perilPlayer);
integer perilPickedIt = (llListFindList(perilPicks, [resultStr]) != -1);
if (perilPickedIt) {
llSay(0, "🩸 DIRECT HIT! " + perilPlayer + " picked their own doom - the d" + (string)diceType + " landed on " + resultStr + "! 🩸");
} else {
llSay(0, "🩸 NO SHIELD! Nobody picked " + resultStr + " - " + perilPlayer + " takes the hit from the d" + (string)diceType + "! 🩸");
}
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, perilPlayer, NULL_KEY);
// Check for elimination // Check for elimination
if (currentLives - 1 <= 0) { if (currentLives - 1 <= 0) {
llSay(0, "💀 " + perilPlayer + " has been eliminated!"); llSay(0, "🐻 PUNISHMENT TIME! " + perilPlayer + " has been ELIMINATED!");
// Remove eliminated player (send message to main controller) // Remove eliminated player (send message to main controller)
llMessageLinked(LINK_SET, 999, "ELIMINATE_PLAYER|" + perilPlayer, NULL_KEY); llMessageLinked(LINK_SET, 999, "ELIMINATE_PLAYER|" + perilPlayer, NULL_KEY);
// The Main Controller will handle peril player reassignment and game flow // The Main Controller will handle peril player reassignment and game flow
@ -234,14 +234,13 @@ default {
} }
// Show start next round dialog to current peril player // Show start next round dialog to current peril player
llOwnerSay("🎯 Prompting " + perilPlayer + " to start next round...");
// Get the peril player's key from the main controller // Get the peril player's key from the main controller
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer + "_NEXT_ROUND", NULL_KEY); llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer + "_NEXT_ROUND", NULL_KEY);
} }
} }
listen(integer channel, string name, key id, string msg) { listen(integer channel, string name, key id, string msg) {
if (msg == "Roll") { if (msg == "Roll" || msg == "ROLL THE DICE OF FATE") {
// Handle dice roll - need to get dice type from main controller // Handle dice roll - need to get dice type from main controller
llOwnerSay("🎲 Roll button clicked by " + name + " (key: " + (string)id + ")"); llOwnerSay("🎲 Roll button clicked by " + name + " (key: " + (string)id + ")");
llMessageLinked(LINK_SET, 996, "GET_DICE_TYPE", NULL_KEY); llMessageLinked(LINK_SET, 996, "GET_DICE_TYPE", NULL_KEY);

View file

@ -51,7 +51,6 @@ default {
target = (key)llGetSubString(llList2String(parts, 0), 6, -1); target = (key)llGetSubString(llList2String(parts, 0), 6, -1);
displayText = llList2String(parts, 1); displayText = llList2String(parts, 1);
llSetText(displayText, <1,1,1>, 1.0); llSetText(displayText, <1,1,1>, 1.0);
llOwnerSay("✅ StatFloat updated for: " + (string)target);
} }
} }
else if (message == "CLEANUP") { else if (message == "CLEANUP") {