## [2.2.0] - 2025-08-04
This commit is contained in:
parent
494f01c7bf
commit
4069379bf1
9 changed files with 175 additions and 183 deletions
|
|
@ -25,24 +25,39 @@ doBotPick(string botName, integer count, integer diceMax, list avoidNumbers) {
|
|||
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;
|
||||
integer attempts = 0;
|
||||
|
||||
while (llGetListLength(picks) < count && attempts < 100) {
|
||||
integer roll = 1 + (integer)(llFrand((float)diceMax));
|
||||
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
|
||||
if (llListFindList(picks, [rollStr]) == -1 && llListFindList(avoidNumbers, [rollStr]) == -1) {
|
||||
if (inPicks == -1 && inAvoid == -1) {
|
||||
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++;
|
||||
}
|
||||
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;
|
||||
llOwnerSay("[Bot Manager] Sending: " + response);
|
||||
llMessageLinked(LINK_SET, -9997, response, NULL_KEY);
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +81,6 @@ doBotRoll(string botName, integer diceMax) {
|
|||
return;
|
||||
}
|
||||
|
||||
llOwnerSay("[Bot Manager] ✅ Bot '" + botName + "' rolling dice (max=" + (string)diceMax + ")");
|
||||
integer roll = 1 + (integer)(llFrand((float)diceMax));
|
||||
llRegionSay(LISTEN_CHANNEL, "BOT_ROLL:" + botName + ":" + (string)roll);
|
||||
}
|
||||
|
|
@ -93,14 +107,12 @@ default {
|
|||
perilPlayer = receivedPeril;
|
||||
}
|
||||
names = llCSV2List(llList2String(parts, 3));
|
||||
llOwnerSay("[Bot Manager] 🔄 Sync: " + (string)llGetListLength(names) + " players, peril='" + perilPlayer + "'");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
list parts = llParseStringKeepNulls(str, [":"], []);
|
||||
if (llGetListLength(parts) >= 4) {
|
||||
|
|
@ -111,7 +123,16 @@ default {
|
|||
if (llGetListLength(parts) >= 5) {
|
||||
string avoidStr = llList2String(parts, 4);
|
||||
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);
|
||||
|
|
@ -130,7 +151,6 @@ default {
|
|||
}
|
||||
|
||||
listen(integer channel, string name, key id, string message) {
|
||||
llOwnerSay("[Bot Manager] Received: " + message);
|
||||
if (llSubStringIndex(message, "BOT_PICK:") == 0) {
|
||||
// Format: BOT_PICK:bot_1:3:20
|
||||
list parts = llParseStringKeepNulls(message, [":"], []);
|
||||
|
|
|
|||
44
CHANGELOG.md
44
CHANGELOG.md
|
|
@ -2,6 +2,41 @@
|
|||
|
||||
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
|
||||
|
||||
### ✨ Major Features Added
|
||||
|
|
@ -84,11 +119,14 @@ All notable changes to Peril Dice will be documented in this file.
|
|||
## Development Notes
|
||||
|
||||
### 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
|
||||
|
||||
### Future Enhancements
|
||||
- Serialized bot picking to eliminate race conditions
|
||||
### 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
|
||||
- Configurable debug levels
|
||||
- Additional game modes and variants
|
||||
- Enhanced visual effects and animations
|
||||
|
|
|
|||
22
Debugger.lsl
22
Debugger.lsl
|
|
@ -7,11 +7,16 @@ list validatePicksData(list picksData, list names) {
|
|||
for (i = 0; i < llGetListLength(picksData); i++) {
|
||||
string entry = llList2String(picksData, i);
|
||||
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) {
|
||||
string name = llList2String(parts, 0);
|
||||
string picks = llList2String(parts, 1);
|
||||
list pickNums = llParseString2List(picks, [","], []);
|
||||
list pickNums = llParseString2List(picks, [";"], []);
|
||||
|
||||
// Check if name is in names list
|
||||
if (names != [] && llListFindList(names, [name]) == -1) {
|
||||
|
|
@ -23,9 +28,7 @@ list validatePicksData(list picksData, list names) {
|
|||
list uniqueCheck = [];
|
||||
for (j = 0; j < llGetListLength(pickNums); j++) {
|
||||
string pick = llList2String(pickNums, j);
|
||||
if ((string)((float)pick) != pick) {
|
||||
llOwnerSay("⚠️ Invalid number in picks for " + name + ": '" + pick + "'");
|
||||
}
|
||||
// Skip validation warnings for cosmetic issues
|
||||
if (~llListFindList(uniqueCheck, [pick])) {
|
||||
llOwnerSay("🚨 Duplicate pick in " + name + "'s picks: " + pick);
|
||||
} else {
|
||||
|
|
@ -75,7 +78,12 @@ default {
|
|||
}
|
||||
|
||||
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);
|
||||
list names = llCSV2List(llList2String(parts, 3));
|
||||
|
||||
|
|
@ -92,9 +100,7 @@ default {
|
|||
}
|
||||
@skipCheck;
|
||||
|
||||
if (hasPicksData) {
|
||||
llOwnerSay("🔍 Checking picksData...");
|
||||
}
|
||||
// Only show checking message for actual issues, not routine validation
|
||||
validatePicksData(picksData, names);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ list getPicksFor(string nameInput) {
|
|||
if (entry != "") llOwnerSay("⚠️ Malformed picks entry (missing pipe): " + entry);
|
||||
} else {
|
||||
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 (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
|
||||
return [];
|
||||
} else {
|
||||
// Check for corruption markers (^ symbols that shouldn't be in picks)
|
||||
if (llSubStringIndex(pickString, "^") != -1) {
|
||||
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, [","], []);
|
||||
// Convert semicolons back to commas for display
|
||||
list pickList = llParseString2List(pickString, [";"], []);
|
||||
list filtered = [];
|
||||
integer j;
|
||||
for (j = 0; j < llGetListLength(all); j++) {
|
||||
string val = llStringTrim(llList2String(all, j), STRING_TRIM);
|
||||
for (j = 0; j < llGetListLength(pickList); j++) {
|
||||
string val = llStringTrim(llList2String(pickList, j), STRING_TRIM);
|
||||
if (val != "" && (string)((integer)val) == val) {
|
||||
filtered += [val];
|
||||
} else {
|
||||
llOwnerSay("⚠️ Invalid number in picks for " + nameInput + ": '" + val + "'");
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
|
|
@ -89,7 +85,7 @@ default {
|
|||
key avKey = llList2Key(info, 1);
|
||||
names += [name];
|
||||
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
|
||||
// float manager has updated its internal lists before attempting
|
||||
|
|
@ -129,7 +125,6 @@ default {
|
|||
pos = basePos + <1 + (float)idx * 0.5, 0, 1>;
|
||||
}
|
||||
integer ch = -777000 + idx;
|
||||
llOwnerSay("📦 Rezzing float for " + name + " at " + (string)pos);
|
||||
llSetObjectDesc(name);
|
||||
llRezObject("StatFloat", pos, ZERO_VECTOR, ZERO_ROTATION, ch);
|
||||
// After rezzing, immediately update the float so it displays the
|
||||
|
|
@ -147,11 +142,8 @@ default {
|
|||
integer ch = -777000 + idx;
|
||||
integer lifeCount = llList2Integer(lives, idx);
|
||||
|
||||
llOwnerSay("🔍 Checking picksData: " + name);
|
||||
list picks = getPicksFor(name);
|
||||
|
||||
llOwnerSay("🛠️ Updating float for " + name);
|
||||
llOwnerSay("🔎 Found picks: " + llList2CSV(picks));
|
||||
|
||||
string perilName;
|
||||
// If the perilPlayer string is empty or contains a comma (indicating
|
||||
|
|
@ -169,7 +161,6 @@ default {
|
|||
string picksDisplay = llList2CSV(picks);
|
||||
string txt = "🎲 Peril Dice\n👤 " + name + "\n❤️ Lives: " + (string)lifeCount + "\n" + perilName + "\n🔢 Picks: " + picksDisplay;
|
||||
llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt);
|
||||
llOwnerSay("📤 Sent FLOAT message on channel " + (string)ch + ": " + txt);
|
||||
}
|
||||
else if (num == MSG_CLEANUP_FLOAT) {
|
||||
integer ch = (integer)str;
|
||||
|
|
@ -177,7 +168,6 @@ default {
|
|||
|
||||
// Always send cleanup message to the channel (even for orphaned floaters)
|
||||
llRegionSay(ch, "CLEANUP");
|
||||
llOwnerSay("🧹 Sent CLEANUP to channel " + (string)ch);
|
||||
|
||||
// Only clean up internal lists if this corresponds to a valid player
|
||||
if (idx >= 0 && idx < llGetListLength(players)) {
|
||||
|
|
@ -185,23 +175,19 @@ default {
|
|||
names = llDeleteSubList(names, idx, idx);
|
||||
lives = llDeleteSubList(lives, idx, idx);
|
||||
picksData = llDeleteSubList(picksData, idx, idx);
|
||||
llOwnerSay("🗑️ Cleaned up internal data for player index " + (string)idx);
|
||||
}
|
||||
}
|
||||
else if (num == MSG_SYNC_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, ["~"], []);
|
||||
lives = llCSV2List(llList2String(parts, 0));
|
||||
string livesStr = llList2String(parts, 0);
|
||||
lives = llCSV2List(livesStr);
|
||||
// Use ^ delimiter for picksData to avoid comma conflicts
|
||||
string picksDataStr = llList2String(parts, 1);
|
||||
llOwnerSay("🔍 DEBUG: Raw picksDataStr: '" + picksDataStr + "'");
|
||||
if (picksDataStr == "" || picksDataStr == "EMPTY") {
|
||||
picksData = [];
|
||||
llOwnerSay("🔍 DEBUG: Parsed picksData: (empty)");
|
||||
} else {
|
||||
picksData = llParseString2List(picksDataStr, ["^"], []);
|
||||
llOwnerSay("🔍 DEBUG: Parsed picksData: " + llDumpList2String(picksData, " | "));
|
||||
}
|
||||
string receivedPeril = llList2String(parts, 2);
|
||||
string oldPeril = perilPlayer;
|
||||
|
|
@ -211,10 +197,6 @@ default {
|
|||
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
|
||||
// they reflect the current peril status. Without this, floats
|
||||
|
|
|
|||
135
Main.lsl
135
Main.lsl
|
|
@ -68,8 +68,6 @@ updateHelpers() {
|
|||
if (roundStarted) {
|
||||
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
|
||||
// Replace commas in picks with semicolons to avoid CSV conflicts
|
||||
list encodedPicksData = [];
|
||||
|
|
@ -137,16 +135,18 @@ resetGame() {
|
|||
integer i;
|
||||
for (i = 0; i < llGetListLength(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);
|
||||
}
|
||||
|
||||
// Also send cleanup for all possible floater channels in case of orphaned floaters
|
||||
// This handles any duplicate floaters that weren't properly tracked
|
||||
for (i = 0; i < MAX_PLAYERS; i++) {
|
||||
// Only send cleanup for channels that might actually have floaters
|
||||
// Check up to the number of players that were registered
|
||||
integer maxUsedIdx = llGetListLength(names);
|
||||
for (i = 0; i < maxUsedIdx; i++) {
|
||||
integer ch = -777000 + i;
|
||||
llOwnerSay("🧹 Sending cleanup for possible orphaned floater channel " + (string)ch);
|
||||
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
|
||||
// Only send cleanup if not already in tracked channels
|
||||
if (llListFindList(floaterChannels, [ch]) == -1) {
|
||||
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
|
||||
}
|
||||
}
|
||||
players = names = lives = picksData = globalPickedNumbers = readyPlayers = [];
|
||||
floaterChannels = []; // Clear the tracked channels
|
||||
|
|
@ -250,23 +250,20 @@ startNextRound() {
|
|||
}
|
||||
// If somehow called with exactly one player, treat them as the winner and reset.
|
||||
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();
|
||||
return;
|
||||
}
|
||||
// Reset round flag for new round
|
||||
roundStarted = FALSE;
|
||||
// DON'T reset round flag during continuation rounds - only reset for brand new games
|
||||
// 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)
|
||||
if (perilPlayer == "" || perilPlayer == "NONE") {
|
||||
integer randomIdx = (integer)llFrand(llGetListLength(names));
|
||||
perilPlayer = llList2String(names, randomIdx);
|
||||
llSay(0, "🎯 " + perilPlayer + " has been randomly selected and is now in peril!");
|
||||
llOwnerSay("🔍 DEBUG: Selected random peril player: " + perilPlayer);
|
||||
} else {
|
||||
llOwnerSay("🔍 DEBUG: Continuing with existing peril player: " + perilPlayer);
|
||||
}
|
||||
picksData = [];
|
||||
globalPickedNumbers = [];
|
||||
|
|
@ -303,14 +300,13 @@ showNextPickerDialog() {
|
|||
if (perilIdx != -1) {
|
||||
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)
|
||||
integer picksNeeded = 4 - perilLives;
|
||||
|
||||
// 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 botCommand = "BOT_PICK:" + firstName + ":" + (string)picksNeeded + ":" + (string)diceType + ":" + alreadyPicked;
|
||||
llOwnerSay("📡 Sending to Bot Manager: " + botCommand);
|
||||
llMessageLinked(LINK_SET, -9999, botCommand, NULL_KEY);
|
||||
llOwnerSay("🤖 " + firstName + " is automatically picking " + (string)picksNeeded + " numbers...");
|
||||
} else {
|
||||
|
|
@ -320,15 +316,11 @@ showNextPickerDialog() {
|
|||
if (perilIdx != -1) {
|
||||
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;
|
||||
llOwnerSay("🔍 DEBUG: Picks needed calculation: 4 - " + (string)perilLives + " = " + (string)picksNeeded);
|
||||
|
||||
// Include already picked numbers so player can't pick duplicates
|
||||
string alreadyPicked = llList2CSV(globalPickedNumbers);
|
||||
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);
|
||||
timeoutTimer = llGetUnixTime();
|
||||
lastWarning = 0;
|
||||
|
|
@ -374,18 +366,15 @@ default {
|
|||
// Already registered; check if owner is the first human player
|
||||
string ownerName = llList2String(names, idx);
|
||||
isStarter = TRUE; // Default to TRUE for owner
|
||||
llOwnerSay("🔍 Owner check: idx=" + (string)idx + ", ownerName=" + ownerName);
|
||||
integer k;
|
||||
for (k = 0; k < idx && isStarter; 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 (llSubStringIndex(existingName, "TestBot") != 0) {
|
||||
isStarter = FALSE;
|
||||
llOwnerSay("❌ Found human player before owner, not starter");
|
||||
}
|
||||
}
|
||||
llOwnerSay("🔍 Final isStarter = " + (string)isStarter);
|
||||
}
|
||||
// Show the owner menu with the appropriate starter flag
|
||||
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, toucher);
|
||||
|
|
@ -512,32 +501,40 @@ default {
|
|||
|
||||
// Prevent joining game in progress (except owner accessing menu)
|
||||
if (roundStarted && newKey != llGetOwner()) {
|
||||
llOwnerSay("⚠️ " + newName + " cannot join - game in progress");
|
||||
llRegionSayTo(newKey, 0, "⚠️ Cannot join game in progress. Wait for current game to finish.");
|
||||
llOwnerSay("🚫 " + newName + " cannot join - the killing game has begun!");
|
||||
llRegionSayTo(newKey, 0, "🚫 The killing game has already begun! Wait for the current game to end.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not register if already present
|
||||
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) {
|
||||
// Add to local lists
|
||||
players += [newKey];
|
||||
names += [newName];
|
||||
lives += [3];
|
||||
llOwnerSay("🐛 DEBUG: Added player " + newName + " with 3 lives. Lives now: " + llList2CSV(lives));
|
||||
picksData += [newName + "|"];
|
||||
// Track the floater channel for this player
|
||||
integer newPlayerIdx = llGetListLength(names) - 1;
|
||||
integer ch = -777000 + newPlayerIdx;
|
||||
floaterChannels += [ch];
|
||||
llOwnerSay("🔍 Added floater channel " + (string)ch + " for " + newName);
|
||||
// Auto-mark bots as ready, leave humans as not ready
|
||||
if (llSubStringIndex(newName, "TestBot") == 0) {
|
||||
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
|
||||
// this registration. Avoid sending MSG_REZ_FLOAT here to prevent
|
||||
|
|
@ -559,7 +556,6 @@ default {
|
|||
if (idx != -1) {
|
||||
// Remove player's float using the tracked channel
|
||||
integer ch = llList2Integer(floaterChannels, idx);
|
||||
llOwnerSay("🔍 Cleaning up floater channel " + (string)ch + " for " + eliminatedPlayer);
|
||||
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
|
||||
// Remove from all lists
|
||||
players = llDeleteSubList(players, idx, idx);
|
||||
|
|
@ -576,11 +572,11 @@ default {
|
|||
// Check if game should end (1 or fewer players remaining)
|
||||
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
|
||||
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
|
||||
} else {
|
||||
llSay(0, "🏆 Game over - no players remaining!");
|
||||
llSay(0, "💀 DESPAIR WINS! No Ultimate Survivors remain!");
|
||||
}
|
||||
resetGame();
|
||||
return;
|
||||
|
|
@ -653,10 +649,8 @@ default {
|
|||
// Check if current peril player is still in the game
|
||||
integer perilStillInGame = llListFindList(names, [perilPlayer]) != -1;
|
||||
if (perilStillInGame) {
|
||||
llOwnerSay("🔍 DEBUG: Ignoring sync attempt to change peril player from '" + perilPlayer + "' to 'NONE'");
|
||||
return; // Don't process this sync update
|
||||
} 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);
|
||||
|
||||
if (livesChanged || perilChanged) {
|
||||
llOwnerSay("🔍 DEBUG: Sync update needed - livesChanged=" + (string)livesChanged + ", perilChanged=" + (string)perilChanged);
|
||||
// Decode the pickData from the encoded format
|
||||
string encodedPicksDataStr = llList2String(parts, 1);
|
||||
list newPicksData = [];
|
||||
|
|
@ -690,7 +683,6 @@ default {
|
|||
|
||||
// Debug peril player changes
|
||||
if (perilPlayer != newPerilPlayer) {
|
||||
llOwnerSay("🔍 DEBUG: Main Controller peril change: '" + perilPlayer + "' -> '" + newPerilPlayer + "'");
|
||||
}
|
||||
|
||||
// Update our game state with the new data
|
||||
|
|
@ -726,7 +718,7 @@ default {
|
|||
requestDiceType();
|
||||
} else {
|
||||
// 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) {
|
||||
// Remove player's float using the tracked channel
|
||||
integer ch = llList2Integer(floaterChannels, idx);
|
||||
llOwnerSay("🔍 Cleaning up floater channel " + (string)ch + " for " + leavingName);
|
||||
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
|
||||
// Remove from all lists including ready state
|
||||
players = llDeleteSubList(players, idx, idx);
|
||||
|
|
@ -768,7 +759,7 @@ default {
|
|||
llOwnerSay("👋 " + leavingName + " left the game");
|
||||
// Check if game should end (less than 2 players)
|
||||
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
|
||||
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
|
||||
resetGame();
|
||||
|
|
@ -794,13 +785,12 @@ default {
|
|||
if (readyIdx == -1) {
|
||||
// Player is not ready, make them ready
|
||||
readyPlayers += [playerName];
|
||||
llOwnerSay("✅ " + playerName + " is now ready");
|
||||
llSay(0, "⚔️ " + playerName + " steels themselves for the deadly challenge ahead! ⚔️");
|
||||
} else {
|
||||
// Player is ready, make them not ready
|
||||
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;
|
||||
}
|
||||
|
|
@ -815,7 +805,6 @@ default {
|
|||
}
|
||||
// Handle aggressive floater cleanup
|
||||
if (num == MSG_CLEANUP_ALL_FLOATERS) {
|
||||
llOwnerSay("🧹 Performing aggressive floater cleanup for all possible channels...");
|
||||
integer i;
|
||||
for (i = 0; i < MAX_PLAYERS; i++) {
|
||||
integer ch = -777000 + i;
|
||||
|
|
@ -826,12 +815,10 @@ default {
|
|||
}
|
||||
// Reset our tracked channels list
|
||||
floaterChannels = [];
|
||||
llOwnerSay("🧹 Aggressive cleanup completed!");
|
||||
return;
|
||||
}
|
||||
// Handle HUMAN_PICKED messages from dialog handler
|
||||
if (num == -9998 && llSubStringIndex(str, "HUMAN_PICKED:") == 0) {
|
||||
llOwnerSay("📨 Main Controller received: " + str);
|
||||
list parts = llParseString2List(str, [":"], []);
|
||||
if (llGetListLength(parts) >= 3) {
|
||||
string playerName = llList2String(parts, 1);
|
||||
|
|
@ -865,7 +852,7 @@ default {
|
|||
}
|
||||
}
|
||||
updateHelpers();
|
||||
llOwnerSay("👤 " + playerName + " picked: " + picksStr);
|
||||
llSay(0, "🎯 " + playerName + " stakes their life on numbers: " + picksStr + " 🎲");
|
||||
|
||||
// Move to next picker
|
||||
currentPickerIdx++;
|
||||
|
|
@ -874,13 +861,11 @@ default {
|
|||
} else {
|
||||
llOwnerSay("✅ All players have picked their numbers!");
|
||||
// Show roll dialog to peril player
|
||||
llOwnerSay("🔍 DEBUG: About to show roll dialog - perilPlayer: '" + perilPlayer + "'");
|
||||
if (perilPlayer == "" || perilPlayer == "NONE") {
|
||||
llOwnerSay("❌ ERROR: Cannot show roll dialog - perilPlayer is invalid: '" + perilPlayer + "'");
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -889,12 +874,12 @@ default {
|
|||
}
|
||||
// Handle BOT_PICKED messages from Bot Manager
|
||||
if (num == -9997 && llSubStringIndex(str, "BOT_PICKED:") == 0) {
|
||||
llOwnerSay("📨 Main Controller received: " + str);
|
||||
list parts = llParseString2List(str, [":"], []);
|
||||
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) {
|
||||
|
|
@ -913,32 +898,33 @@ default {
|
|||
} else {
|
||||
picksData = llListReplaceList(picksData, [newEntry], picksIdx, picksIdx);
|
||||
}
|
||||
// Update global picked numbers list
|
||||
list newPicks = llParseString2List(picksStr, [","], []);
|
||||
// Update global picked numbers list - bot picks use semicolon delimiters
|
||||
list newPicks = llParseString2List(picksStr, [";"], []);
|
||||
integer j;
|
||||
for (j = 0; j < llGetListLength(newPicks); j++) {
|
||||
string pick = llList2String(newPicks, j);
|
||||
if (llListFindList(globalPickedNumbers, [pick]) == -1) {
|
||||
globalPickedNumbers += [pick];
|
||||
} else {
|
||||
llOwnerSay("⚠️ WARNING: " + playerName + " picked " + pick + " which was already in globalPickedNumbers!");
|
||||
}
|
||||
}
|
||||
updateHelpers();
|
||||
llOwnerSay("🤖 " + playerName + " picked: " + picksStr);
|
||||
llSay(0, "🎯 " + playerName + " (bot) stakes their digital life on numbers: " + picksStr + " 🎲");
|
||||
|
||||
// Move to next picker
|
||||
currentPickerIdx++;
|
||||
if (currentPickerIdx < llGetListLength(pickQueue)) {
|
||||
string nextPicker = llList2String(pickQueue, currentPickerIdx);
|
||||
showNextPickerDialog();
|
||||
} else {
|
||||
llOwnerSay("✅ All players have picked their numbers!");
|
||||
// Show roll dialog to peril player
|
||||
llOwnerSay("🔍 DEBUG: About to show roll dialog - perilPlayer: '" + perilPlayer + "'");
|
||||
if (perilPlayer == "" || perilPlayer == "NONE") {
|
||||
llOwnerSay("❌ ERROR: Cannot show roll dialog - perilPlayer is invalid: '" + perilPlayer + "'");
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -949,41 +935,10 @@ default {
|
|||
|
||||
|
||||
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 (msg == "Start Next Round") {
|
||||
llOwnerSay("🔍 DEBUG: Start Next Round clicked by " + llKey2Name(id) + ", but perilPlayer=" + perilPlayer);
|
||||
llSay(0, "🎯 " + perilPlayer + " is starting the next round!");
|
||||
if (msg == "Start Next Round" || msg == "BEGIN KILLING GAME") {
|
||||
llSay(0, "⚡ THE KILLING GAME CONTINUES! " + perilPlayer + " begins the next deadly round!");
|
||||
startNextRound();
|
||||
requestDiceType();
|
||||
return;
|
||||
|
|
@ -1078,7 +1033,7 @@ default {
|
|||
}
|
||||
|
||||
// All checks passed, start the game
|
||||
llOwnerSay("✅ All players ready! Starting game...");
|
||||
llSay(0, "⚡ ALL PARTICIPANTS READY! THE DEADLY PERIL DICE GAME BEGINS! ⚡");
|
||||
startNextRound();
|
||||
requestDiceType();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ list getNumbersForPage(integer page, integer diceType) {
|
|||
}
|
||||
|
||||
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) {
|
||||
llOwnerSay("⚠️ Cannot show dialog: diceType is 0 or invalid.");
|
||||
return;
|
||||
|
|
@ -98,7 +97,6 @@ showPickDialog(string name, key id, integer diceType, integer picks) {
|
|||
dialogText = "Pick " + pickText + " (1-" + (string)diceType + ")" + pageInfo + currentPicksText;
|
||||
}
|
||||
|
||||
llOwnerSay("🗨️ Showing dialog to: " + name + " - needs " + pickText + pageInfo);
|
||||
llDialog(id, dialogText, options, numberPickChannel);
|
||||
}
|
||||
|
||||
|
|
@ -109,10 +107,8 @@ default {
|
|||
}
|
||||
|
||||
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) {
|
||||
llOwnerSay("✅ Processing MSG_SHOW_DIALOG...");
|
||||
list parts = llParseString2List(str, ["|"], []);
|
||||
list parts = llParseString2List(str, ["|"], []);
|
||||
if (llGetListLength(parts) < 3) {
|
||||
llOwnerSay("⚠️ Malformed MSG_SHOW_DIALOG input: " + str);
|
||||
return;
|
||||
|
|
@ -134,16 +130,11 @@ default {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
listen(integer channel, string name, key id, string message) {
|
||||
llOwnerSay("🔊 Listen triggered: " + message + " from " + name);
|
||||
|
||||
// Handle navigation
|
||||
if (message == "◀ Prev") {
|
||||
|
|
@ -166,9 +157,7 @@ default {
|
|||
if (message == "✅ Done") {
|
||||
if (llGetListLength(currentPicks) >= picksNeeded) {
|
||||
string picksStr = llList2CSV(currentPicks);
|
||||
llOwnerSay("✅ " + currentPlayer + " completed picks: " + picksStr);
|
||||
string response = "HUMAN_PICKED:" + currentPlayer + ":" + picksStr;
|
||||
llOwnerSay("📡 Sending to Main Controller: " + response);
|
||||
llMessageLinked(LINK_SET, -9998, response, NULL_KEY);
|
||||
// Reset state
|
||||
currentPlayer = "";
|
||||
|
|
@ -187,7 +176,6 @@ default {
|
|||
if (llGetListLength(currentPicks) < picksNeeded) {
|
||||
// Add to picks (numbers already picked won't be shown in dialog)
|
||||
currentPicks += [message];
|
||||
llOwnerSay("✅ " + currentPlayer + " picked: " + message + " (" + (string)llGetListLength(currentPicks) + "/" + (string)picksNeeded + ")");
|
||||
} else {
|
||||
llOwnerSay("⚠️ " + currentPlayer + " has already picked enough numbers");
|
||||
}
|
||||
|
|
|
|||
21
README.md
21
README.md
|
|
@ -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
|
||||
- **🎮 Intelligent Dice Sizing**: Automatic dice size based on player count
|
||||
- **🔄 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
|
||||
- **🛡️ Robust Error Handling**: Comprehensive game state synchronization
|
||||
- **🚫 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)
|
||||
4. **Rolling Phase**: Peril player rolls the dice
|
||||
5. **Resolution**:
|
||||
- If rolled number matches another player's pick → that player becomes new peril player
|
||||
- If no match or peril player hits themselves → peril player loses a life
|
||||
- If rolled number matches another player's pick → that player becomes new peril player (`⚡ PLOT TWIST!`)
|
||||
- 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
|
||||
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 |
|
||||
| 7–10 | d30 |
|
||||
|
||||
## Known Issues
|
||||
## Recent Improvements (v2.2.0)
|
||||
|
||||
- **Minor Bot Collision**: Bots may occasionally pick duplicate numbers in rapid succession (race condition)
|
||||
- **Performance**: Extended games with many bots may experience some lag due to debug output
|
||||
- **🎭 Dramatic Messaging**: All game events now use thematic, immersive language
|
||||
- **📢 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
|
||||
|
||||
**Current Version**: 2.1.0
|
||||
**Last Updated**: January 2025
|
||||
**Status**: Stable - Full game flow functional
|
||||
**Current Version**: 2.2.0
|
||||
**Last Updated**: August 2025
|
||||
**Status**: Stable - Enhanced UX with dramatic messaging system
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -27,22 +27,18 @@ list getPicksFor(string nameInput) {
|
|||
|
||||
// Check for corruption markers (^ symbols that shouldn't be in picks)
|
||||
if (llSubStringIndex(picks, "^") != -1) {
|
||||
llOwnerSay("🔍 DEBUG getPicksFor(" + nameInput + "): CORRUPTED data contains ^: '" + originalPicks + "'");
|
||||
return [];
|
||||
}
|
||||
|
||||
if (picks == "") {
|
||||
llOwnerSay("🔍 DEBUG getPicksFor(" + nameInput + "): empty picks");
|
||||
return [];
|
||||
}
|
||||
|
||||
picks = llDumpList2String(llParseString2List(picks, [";"], []), ",");
|
||||
list result = llParseString2List(picks, [","], []);
|
||||
llOwnerSay("🔍 DEBUG getPicksFor(" + nameInput + "): raw='" + originalPicks + "' -> converted='" + picks + "' -> list=" + llList2CSV(result));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
llOwnerSay("🔍 DEBUG getPicksFor(" + nameInput + "): NOT FOUND in picksData=" + llDumpList2String(picksData, " | "));
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -97,7 +93,7 @@ default {
|
|||
|
||||
if (num == 995) {
|
||||
if (str == "VICTORY_CONFETTI") {
|
||||
llOwnerSay("🎉 Victory confetti!");
|
||||
llOwnerSay("✨ ULTIMATE VICTORY CELEBRATION!");
|
||||
confetti();
|
||||
}
|
||||
return;
|
||||
|
|
@ -119,7 +115,7 @@ default {
|
|||
llMessageLinked(LINK_SET, 996, "GET_DICE_TYPE", NULL_KEY);
|
||||
} else {
|
||||
// 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 result = rollDice(diceType);
|
||||
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 = "";
|
||||
integer matched = FALSE;
|
||||
list matchedPlayers = [];
|
||||
integer i;
|
||||
|
||||
llOwnerSay("🔍 DEBUG: Current peril player: " + perilPlayer + ", rolled: " + resultStr);
|
||||
|
||||
// Find all players who picked the rolled number
|
||||
for (i = 0; i < llGetListLength(names); i++) {
|
||||
string pname = llList2String(names, i);
|
||||
list picks = getPicksFor(pname);
|
||||
llOwnerSay("🔍 DEBUG: " + pname + " picks: " + llList2CSV(picks));
|
||||
if (llListFindList(picks, [resultStr]) != -1) {
|
||||
matched = TRUE;
|
||||
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 only the current peril player matched, they stay in peril (no change)
|
||||
|
|
@ -157,12 +149,11 @@ default {
|
|||
string pname = llList2String(matchedPlayers, i);
|
||||
if (pname != perilPlayer) {
|
||||
newPeril = pname;
|
||||
llOwnerSay("🔍 DEBUG: New peril player will be: " + 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;
|
||||
// Update floaters immediately to show correct peril player before sync
|
||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, newPeril, NULL_KEY);
|
||||
|
|
@ -171,12 +162,21 @@ default {
|
|||
if (pidx != -1) {
|
||||
integer currentLives = llList2Integer(lives, 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
|
||||
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)
|
||||
llMessageLinked(LINK_SET, 999, "ELIMINATE_PLAYER|" + perilPlayer, NULL_KEY);
|
||||
// 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
|
||||
llOwnerSay("🎯 Prompting " + perilPlayer + " to start next round...");
|
||||
// Get the peril player's key from the main controller
|
||||
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer + "_NEXT_ROUND", NULL_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
llOwnerSay("🎲 Roll button clicked by " + name + " (key: " + (string)id + ")");
|
||||
llMessageLinked(LINK_SET, 996, "GET_DICE_TYPE", NULL_KEY);
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ default {
|
|||
target = (key)llGetSubString(llList2String(parts, 0), 6, -1);
|
||||
displayText = llList2String(parts, 1);
|
||||
llSetText(displayText, <1,1,1>, 1.0);
|
||||
llOwnerSay("✅ StatFloat updated for: " + (string)target);
|
||||
}
|
||||
}
|
||||
else if (message == "CLEANUP") {
|
||||
|
|
|
|||
Loading…
Reference in a new issue