Peril_Dice/Main.lsl

1294 lines
60 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// === Main Peril Dice Controller (Refactored with Game Helpers Integration, with dynamic player join) ===
//
// This version of the main game controller includes support for players (including
// the owner) joining the game at runtime via a MSG_REGISTER_PLAYER message. When
// a new player registers, they are added to the internal lists (players, names,
// lives and picksData), a floating display is rezzed for them, and helpers are
// updated. This mirrors the behaviour used when adding a test player, but now
// applies to any avatar joining the game via the dialog handler.
integer syncChannel = -77777;
integer numberPickChannel = -77888;
integer rollDialogChannel = -77999;
integer DIALOG_CHANNEL = -88888;
// Game timing settings to prevent dialog system overload
float BOT_PICK_DELAY = 2.0; // Delay after bot picks before next action
float HUMAN_PICK_DELAY = 1.0; // Delay after human picks before next action
float DIALOG_DELAY = 1.5; // Delay before showing dialogs
integer gameTimer = 0; // Timer for game flow delays
// Status message timing
float STATUS_DISPLAY_TIME = 8.0; // How long to show status messages on scoreboard
integer statusTimer = 0; // Track when status messages were sent
string lastStatus = ""; // Track last status sent
list players = [];
list names = [];
list lives = [];
string perilPlayer = "";
list globalPickedNumbers = [];
list picksData = [];
list readyPlayers = [];
list floaterChannels = []; // Track actual floater channels for cleanup
list pickQueue = [];
integer currentPickerIdx = 0;
integer diceType = 6;
integer MSG_SHOW_DIALOG = 101;
integer MSG_ROLL_RESULT = 102;
integer MSG_UPDATE_FLOAT = 103;
integer MSG_CLEANUP_FLOAT = 104;
integer MSG_REZ_FLOAT = 105;
integer MSG_REGISTER_PLAYER = 106;
integer MSG_SYNC_GAME_STATE = 107;
integer MSG_SHOW_MENU = 201;
integer MSG_TOGGLE_READY = 202;
integer MSG_QUERY_READY_STATE = 210;
integer MSG_READY_STATE_RESULT = 211;
integer MSG_CLEANUP_ALL_FLOATERS = 212;
integer MSG_SHOW_ROLL_DIALOG = 301;
integer MSG_GET_CURRENT_DIALOG = 302;
integer MSG_PLAYER_WON = 551;
integer MSG_RESET_LEADERBOARD = 553; // Custom message to reset leaderboard
// Display configuration
integer CONTROLLER_FACE = 1; // Face to display start image on (front face)
string TEXTURE_START = "title_start"; // Start image texture
string TEXTURE_GAME_ACTIVE = "game_active"; // Optional: different texture during game
integer MSG_GET_DICE_TYPE = 1001;
integer MSG_GET_PICKS_REQUIRED = 1002;
integer MSG_GET_PICKER_INDEX = 1003;
integer MSG_DICE_TYPE_RESULT = 1005;
integer MSG_SERIALIZE_GAME_STATE = 1004;
integer MSG_SYNC_PICKQUEUE = 2001;
// Maximum number of players allowed (including test players). This should
// mirror the value used in Floater Manager to avoid inconsistencies.
integer MAX_PLAYERS = 10;
integer TIMEOUT_SECONDS = 600;
integer timeoutTimer;
integer warning2min = 120;
integer warning5min = 300;
integer warning9min = 540;
integer lastWarning = 0;
key currentPicker;
// Send status message to scoreboard with timing
sendStatusMessage(string status) {
statusTimer = llGetUnixTime();
lastStatus = status;
llRegionSay(-12345, "GAME_STATUS|" + status);
llOwnerSay("📢 Status: " + status + " (showing for " + (string)STATUS_DISPLAY_TIME + "s)");
// Start timer to automatically clear status after display time
llSetTimerEvent(STATUS_DISPLAY_TIME + 1.0); // Add 1 second buffer
}
// Forward game state to helpers when it changes
updateHelpers() {
// Don't send peril player name if game hasn't started yet
string perilForSync = "NONE"; // Use placeholder instead of empty string
if (roundStarted) {
perilForSync = perilPlayer;
}
// 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 = [];
integer i;
for (i = 0; i < llGetListLength(picksData); i++) {
string entry = llList2String(picksData, i);
// Replace commas with semicolons in the picks portion only
list parts = llParseString2List(entry, ["|"], []);
if (llGetListLength(parts) >= 2) {
string playerName = llList2String(parts, 0);
string picks = llList2String(parts, 1);
// Only process picks if they're not empty
if (picks != "") {
// Parse picks, trim whitespace, then join with semicolons
list pickList = llParseString2List(picks, [","], []);
list cleanPickList = [];
integer j;
for (j = 0; j < llGetListLength(pickList); j++) {
string pick = llStringTrim(llList2String(pickList, j), STRING_TRIM);
if (pick != "") {
cleanPickList += [pick];
}
}
picks = llDumpList2String(cleanPickList, ";");
}
encodedPicksData += [playerName + "|" + picks];
} else if (llSubStringIndex(entry, "|") != -1) {
// Handle entries with empty picks (e.g., "PlayerName|")
encodedPicksData += [entry];
} else {
// Skip malformed entries without pipe separator
llOwnerSay("⚠️ Skipping malformed picksData entry: " + entry);
}
}
string picksDataStr = llDumpList2String(encodedPicksData, "^");
// Ensure we always send valid format even with empty picks
if (picksDataStr == "") {
picksDataStr = "EMPTY"; // Use a clear placeholder for empty picks data
}
string serialized = llList2CSV(lives) + "~" + picksDataStr +
"~" + perilForSync + "~" + llList2CSV(names);
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, serialized, NULL_KEY);
llMessageLinked(LINK_SET, MSG_SYNC_PICKQUEUE, llList2CSV(pickQueue), NULL_KEY);
// Send player updates to scoreboard
integer j;
for (j = 0; j < llGetListLength(names); j++) {
string playerName = llList2String(names, j);
integer playerLives = llList2Integer(lives, j);
key playerKey = llList2Key(players, j);
// Use the actual player key as the profile UUID
string profileUUID = (string)playerKey;
// Send PLAYER_UPDATE message to scoreboard
string updateMsg = "PLAYER_UPDATE|" + playerName + "|" + (string)playerLives + "|" + profileUUID;
llRegionSay(-12345, updateMsg);
}
}
// Request helper-calculated values
requestDiceType() {
llMessageLinked(LINK_SET, MSG_GET_DICE_TYPE, (string)llGetListLength(names), NULL_KEY);
}
requestPicksRequired(integer idx) {
llMessageLinked(LINK_SET, MSG_GET_PICKS_REQUIRED, llList2String(names, idx), NULL_KEY);
}
requestPickerIndex(string name) {
llMessageLinked(LINK_SET, MSG_GET_PICKER_INDEX, name, NULL_KEY);
}
string generateSerializedState() {
return llList2CSV(lives) + "~" + llList2CSV(picksData) + "~" + perilPlayer + "~" + llList2CSV(names);
}
resetGame() {
// Clean up all tracked floater channels
integer i;
for (i = 0; i < llGetListLength(floaterChannels); i++) {
integer ch = llList2Integer(floaterChannels, i);
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
}
// 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;
// Only send cleanup if not already in tracked channels
if (llListFindList(floaterChannels, [ch]) == -1) {
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
}
}
// Reset all game state variables
players = names = lives = picksData = globalPickedNumbers = readyPlayers = [];
floaterChannels = []; // Clear the tracked channels
perilPlayer = "";
pickQueue = [];
currentPickerIdx = 0;
roundStarted = FALSE; // Reset round flag
currentPicker = NULL_KEY; // Reset current picker
timeoutTimer = 0; // Reset timeout timer
lastWarning = 0; // Reset warning timer
diceType = 6; // Reset dice type to default
// Send reset message to all other scripts to clear their state
llMessageLinked(LINK_SET, -99999, "FULL_RESET", NULL_KEY);
llSay(syncChannel, "RESET");
// Send CLEAR_GAME message to scoreboard to reset display
llRegionSay(-12345, "CLEAR_GAME");
llOwnerSay("🎮 Game reset! All state cleared (including scoreboard).");
// Clear status tracking
statusTimer = 0;
lastStatus = "";
// Reset controller display to start image
llSetTexture(TEXTURE_START, CONTROLLER_FACE);
llSetText("🎮 PERIL DICE GAME\nTouch to play!", <1.0, 1.0, 0.0>, 1.0);
llSleep(0.5); // Give other scripts time to process reset
llSetTimerEvent(0);
updateHelpers();
}
continueCurrentRound() {
// Continue the current round after elimination with existing picks intact
// This is different from startNextRound() which resets picks for a new round
llOwnerSay("🔄 Continuing current round with new peril player: " + perilPlayer);
// Remove eliminated player's pick data but keep others
integer i;
list newPicksData = [];
for (i = 0; i < llGetListLength(picksData); i++) {
string entry = llList2String(picksData, i);
list parts = llParseString2List(entry, ["|"], []);
if (llGetListLength(parts) >= 1) {
string entryPlayerName = llList2String(parts, 0);
// Only keep picks from players still in the game
if (llListFindList(names, [entryPlayerName]) != -1) {
newPicksData += [entry];
}
}
}
picksData = newPicksData;
// Update global picked numbers to match remaining players' picks
globalPickedNumbers = [];
for (i = 0; i < llGetListLength(picksData); i++) {
string entry = llList2String(picksData, i);
list parts = llParseString2List(entry, ["|"], []);
if (llGetListLength(parts) >= 2) {
string picks = llList2String(parts, 1);
if (picks != "") {
// Handle semicolon encoding for picks
string decodedPicks = llDumpList2String(llParseString2List(picks, [";"], []), ",");
list playerPicks = llParseString2List(decodedPicks, [","], []);
globalPickedNumbers += playerPicks;
}
}
}
// Check if all remaining players have picked
integer allPicked = TRUE;
for (i = 0; i < llGetListLength(names); i++) {
string playerName = llList2String(names, i);
// Check if this player has picks
integer hasPicksEntry = FALSE;
integer j;
for (j = 0; j < llGetListLength(picksData); j++) {
string entry = llList2String(picksData, j);
list parts = llParseString2List(entry, ["|"], []);
if (llGetListLength(parts) >= 1 && llList2String(parts, 0) == playerName) {
string picks = "";
if (llGetListLength(parts) >= 2) {
picks = llList2String(parts, 1);
}
if (picks != "") {
hasPicksEntry = TRUE;
}
}
}
if (!hasPicksEntry) {
allPicked = FALSE;
}
}
if (allPicked) {
// All players have picked, prompt peril player to roll
llOwnerSay("✅ All remaining players have picks, prompting " + perilPlayer + " to roll");
integer perilIdx = llListFindList(names, [perilPlayer]);
if (perilIdx != -1) {
key perilKey = llList2Key(players, perilIdx);
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, perilKey);
}
} else {
// Some players still need to pick, continue with pick phase
llOwnerSay("⏳ Some players still need to pick, continuing pick phase");
updateHelpers();
// Check if peril player needs to pick
showNextPickerDialog();
}
}
startNextRound() {
// Do not start a round if there are fewer than 2 players. This guard prevents
// accidental starts when only one player is present (e.g. after a reset or
// before any other participants join).
if (llGetListLength(names) < 2) {
llOwnerSay("⚠️ Need at least 2 players to start the game.");
return;
}
// If somehow called with exactly one player, treat them as the winner and reset.
if (llGetListLength(names) == 1) {
string winner = llList2String(names, 0);
llSay(0, "✨ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!");
// Trigger victory confetti and record win
llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY);
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
llRegionSay(-12345, "GAME_WON|" + winner); // Send winner info to scoreboard
sendStatusMessage("Victory"); // Show victory status
// Wait for victory status to display before reset
llSleep(STATUS_DISPLAY_TIME + 1.0);
resetGame();
return;
}
// DON'T reset round flag during continuation rounds - only reset for brand new games
// This prevents peril player from being lost during round transitions
// 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!");
sendStatusMessage("Peril Selected"); // Show peril selected status when peril player is chosen
} else {
// Continue showing peril selected status for existing peril player
sendStatusMessage("Peril Selected");
// Add delay to allow this status to display before next phase
llSleep(STATUS_DISPLAY_TIME * 0.6); // Wait 60% of status display time (~5 seconds)
}
picksData = [];
globalPickedNumbers = [];
// Create pick queue with peril player first, then all others
pickQueue = [perilPlayer];
integer i;
for (i = 0; i < llGetListLength(names); i++) {
string playerName = llList2String(names, i);
if (playerName != perilPlayer) {
pickQueue += [playerName];
}
}
currentPickerIdx = 0;
// Don't pre-populate picksData with empty entries - let them be added as picks are made
// This prevents encoding corruption with empty "|" entries
updateHelpers();
// requestDiceType(); // Removed to avoid recursion loop
}
showNextPickerDialog() {
if (diceType <= 0) {
llOwnerSay("❌ Cannot show picker dialog: diceType not set (" + (string)diceType + ")");
return;
}
string firstName = llList2String(pickQueue, currentPickerIdx);
currentPicker = llList2Key(players, llListFindList(names, [firstName]));
// Add delay before showing dialog to prevent system overload
llSleep(DIALOG_DELAY);
// Check if this is a bot (TestBot names)
if (llSubStringIndex(firstName, "TestBot") == 0) {
// Get number of picks needed based on peril player's lives
integer perilIdx = llListFindList(names, [perilPlayer]);
integer perilLives = 3; // default
if (perilIdx != -1) {
perilLives = llList2Integer(lives, 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;
llMessageLinked(LINK_SET, -9999, botCommand, NULL_KEY);
llOwnerSay("🤖 " + firstName + " is automatically picking " + (string)picksNeeded + " numbers...");
} else {
// Show dialog for human players - calculate picks needed
integer perilIdx = llListFindList(names, [perilPlayer]);
integer perilLives = 3; // default
if (perilIdx != -1) {
perilLives = llList2Integer(lives, perilIdx);
}
integer picksNeeded = 4 - perilLives;
// Include already picked numbers so player can't pick duplicates
string alreadyPicked = llList2CSV(globalPickedNumbers);
string dialogPayload = firstName + "|" + (string)diceType + "|" + (string)picksNeeded + "|" + alreadyPicked;
llMessageLinked(LINK_SET, MSG_SHOW_DIALOG, dialogPayload, currentPicker);
timeoutTimer = llGetUnixTime();
lastWarning = 0;
llSetTimerEvent(60.0);
}
}
integer roundStarted = FALSE;
default {
state_entry() {
llOwnerSay("🎮 Main Controller ready!");
llOwnerSay("🎮 Main Controller key: " + (string)llGetKey());
llOwnerSay("🎮 Main Controller position: " + (string)llGetPos());
// Set the start image on the controller prim
llSetTexture(TEXTURE_START, CONTROLLER_FACE);
llSetText("🎮 PERIL DICE GAME\nTouch to play!", <1.0, 1.0, 0.0>, 1.0);
llListen(DIALOG_CHANNEL, "", NULL_KEY, "");
llListen(-9999, "", NULL_KEY, ""); // Listen for bot responses
llListen(rollDialogChannel, "", NULL_KEY, ""); // Listen for roll dialog responses
}
touch_start(integer total_number) {
llOwnerSay("Touched by: " + (string)llDetectedKey(0));
key toucher = llDetectedKey(0);
integer idx = llListFindList(players, [toucher]);
// Check if player has a current dialog they can recover
if (idx != -1 && toucher != llGetOwner() && roundStarted) {
string playerName = llList2String(names, idx);
// Check if this player is the current picker waiting for a dialog
if (currentPicker == toucher) {
llRegionSayTo(toucher, 0, "🔄 Restoring your number picking dialog...");
llMessageLinked(LINK_SET, MSG_GET_CURRENT_DIALOG, playerName, toucher);
return;
}
// Check if this player is the peril player who might need a roll dialog
else if (playerName == perilPlayer && currentPickerIdx >= llGetListLength(pickQueue)) {
llRegionSayTo(toucher, 0, "🔄 Restoring your roll dialog...");
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, toucher);
return;
}
}
// Special handling for owner during gameplay
if (toucher == llGetOwner() && roundStarted) {
string ownerName = llKey2Name(toucher);
list options = [];
string menuText = "Game in progress. What would you like to do?";
// Check if owner has active dialogs they can recover
integer hasActiveDialog = FALSE;
if (currentPicker == toucher) {
options += ["🔄 Recover Pick Dialog"];
hasActiveDialog = TRUE;
}
if (ownerName == perilPlayer && currentPickerIdx >= llGetListLength(pickQueue)) {
options += ["🔄 Recover Roll Dialog"];
hasActiveDialog = TRUE;
}
// Always offer admin menu
options += ["🔧 Admin Menu"];
// If no active dialogs, just show admin menu directly
if (!hasActiveDialog) {
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|0", toucher);
return;
}
// Show choice dialog
llDialog(toucher, menuText, options, DIALOG_CHANNEL);
return;
}
if (toucher == llGetOwner()) {
// Determine if this will be the first registered player (starter) before registration
integer isStarter = FALSE;
if (idx == -1) {
// No index found; the owner is not yet registered.
// Owner becomes starter if there are no existing human players (excluding bots)
isStarter = TRUE; // Default to TRUE for owner
integer j;
for (j = 0; j < llGetListLength(names) && isStarter; j++) {
string existingName = llList2String(names, j);
// If there's already a human player (not a bot), owner is not starter
if (llSubStringIndex(existingName, "TestBot") != 0) {
isStarter = FALSE;
}
}
string ownerName = llKey2Name(toucher);
// Send a registration request; Main.lsl will handle adding them and rezzing a float
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, ownerName + "|" + (string)toucher, NULL_KEY);
} else {
// Already registered; check if owner is the first human player
// Owner is starter if they are at index 0 OR if all players before them are bots
isStarter = TRUE; // Default to TRUE for owner
integer k;
for (k = 0; k < idx && isStarter; k++) {
string existingName = llList2String(names, k);
// If there's a human player before the owner, owner is not starter
if (llSubStringIndex(existingName, "TestBot") != 0) {
isStarter = FALSE;
}
}
// Special case: if owner is at index 0, they are definitely the starter
if (idx == 0) {
isStarter = TRUE;
}
}
// Show the owner menu with the appropriate starter flag
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, toucher);
} else if (idx != -1) {
// For non-owner players, check if they're the first human player
integer isStarter = TRUE; // Default to TRUE
integer m;
for (m = 0; m < idx && isStarter; m++) {
string existingName = llList2String(names, m);
// If there's a human player before this player, they're not starter
if (llSubStringIndex(existingName, "TestBot") != 0) {
isStarter = FALSE;
}
}
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher);
} else {
// Unregistered non-owner player - register them and show menu
string playerName = llKey2Name(toucher);
if (playerName != "") {
// Send a registration request; Main.lsl will handle adding them and rezzing a float
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, playerName + "|" + (string)toucher, NULL_KEY);
// Determine if this will be the first human player (starter)
integer isStarter = TRUE; // Default to TRUE
integer n;
for (n = 0; n < llGetListLength(names) && isStarter; n++) {
string existingName = llList2String(names, n);
// If there's already a human player (not a bot), this player is not starter
if (llSubStringIndex(existingName, "TestBot") != 0) {
isStarter = FALSE;
}
}
// Show the player menu with the appropriate starter flag
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher);
} else {
llOwnerSay("⚠️ Could not get name for toucher: " + (string)toucher);
}
}
}
link_message(integer sender, integer num, string str, key id) {
// Player list and pick list handling remain unchanged
if (num == 202 && str == "REQUEST_PLAYER_LIST") {
string namesCSV = llList2CSV(names);
llMessageLinked(LINK_SET, 203, namesCSV, id);
return;
}
if (num == 206) {
string targetName = str;
integer i;
for (i = 0; i < llGetListLength(picksData); i++) {
string rawEntry = llList2String(picksData, i);
list parts = llParseString2List(rawEntry, ["|"], []);
if (llList2String(parts, 0) == targetName) {
llMessageLinked(LINK_SET, 205, targetName + "|" + llList2String(parts, 1), id);
return;
}
}
llMessageLinked(LINK_SET, 205, targetName + "|", id);
return;
}
if (num == 208) {
string playerName = str;
integer idx = llListFindList(names, [playerName]);
if (idx != -1) {
string lifeVal = llList2String(lives, idx);
llMessageLinked(LINK_SET, 208, playerName + "|" + lifeVal, id);
}
return;
}
// Handle dice type result from helper
if (num == MSG_DICE_TYPE_RESULT) {
if (!roundStarted) {
roundStarted = TRUE;
diceType = (integer)str;
llOwnerSay(" Dice type set to: " + str);
// Update helpers now that game has started - this will sync peril player
updateHelpers();
showNextPickerDialog();
} else {
// Game already started, but dice type changed (e.g., after elimination)
diceType = (integer)str;
llOwnerSay(" Dice type set to: " + str);
updateHelpers();
showNextPickerDialog();
}
return;
}
if (num == MSG_GET_PICKS_REQUIRED) {
integer picksRequired = (integer)str;
llOwnerSay(" Picks required: " + str);
return;
}
if (num == MSG_GET_PICKER_INDEX) {
integer pickIndex = (integer)str;
llOwnerSay(" Picker index: " + str);
return;
}
if (num == MSG_SERIALIZE_GAME_STATE) {
string serialized = str;
llOwnerSay(" Serialized game state: " + serialized);
return;
}
// Handle pick actions
if (num == 204) {
list parts = llParseString2List(str, ["~"], []);
string action = llList2String(parts, 0);
list args = llParseString2List(llList2String(parts, 1), ["|"], []);
string name = llList2String(args, 0);
string pick = llList2String(args, 1);
integer i;
for (i = 0; i < llGetListLength(picksData); i++) {
string entry = llList2String(picksData, i);
list pdParts = llParseString2List(entry, ["|"], []);
if (llList2String(pdParts, 0) == name) {
list pickList = [];
string rawPicks = llList2String(pdParts, 1);
if (rawPicks != "") {
pickList = llParseString2List(rawPicks, [","], []);
}
if (action == "ADD_PICK") {
if (llListFindList(pickList, [pick]) == -1) {
pickList += [pick];
llOwnerSay("➕ Added " + pick + " to " + name);
}
} else if (action == "REMOVE_PICK") {
integer idx = llListFindList(pickList, [pick]);
if (idx != -1) {
pickList = llDeleteSubList(pickList, idx, idx);
llOwnerSay("➖ Removed " + pick + " from " + name);
}
}
picksData = llListReplaceList(picksData, [name + "|" + llList2CSV(pickList)], i, i);
updateHelpers();
return;
}
}
return;
}
// New: handle dynamic registration of players (owner or players) via MSG_REGISTER_PLAYER
if (num == MSG_REGISTER_PLAYER) {
// str is in the format "Name|<key>"
list parts = llParseString2List(str, ["|"], []);
string newName = llList2String(parts, 0);
key newKey = (key)llList2String(parts, 1);
// Prevent joining game in progress (except owner accessing menu)
if (roundStarted && newKey != llGetOwner()) {
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]);
if (existingIdx == -1) {
// Add to local lists
players += [newKey];
names += [newName];
lives += [3];
picksData += [newName + "|"];
// Track the floater channel for this player
integer newPlayerIdx = llGetListLength(names) - 1;
integer ch = -777000 + newPlayerIdx;
floaterChannels += [ch];
// Auto-mark bots as ready, leave humans as not ready
if (llSubStringIndex(newName, "TestBot") == 0) {
readyPlayers += [newName];
llSay(0, "🤖 " + newName + " boots up with deadly precision - ready to play! 🤖");
} else {
// Check if this is the first human player (starter - automatically ready only if owner)
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
if (newKey == llGetOwner()) {
// Owner is auto-ready as starter
readyPlayers += [newName];
llSay(0, "👑 " + newName + " steps forward as the game master - automatically ready for the deadly challenge! 👑");
} else {
// Non-owner starter can toggle ready state
llSay(0, "👑 " + newName + " steps forward as the game master! Touch to set your ready status.");
}
}
}
// The float will be rezzed by the Floater Manager after it processes
// this registration. Avoid sending MSG_REZ_FLOAT here to prevent
// race conditions; instead, request the float from Floater Manager
// after it has updated its own lists. Immediately propagate
// helper updates now.
updateHelpers();
// Notify owner of registration
llOwnerSay("🔔 Added player: " + newName);
}
return;
}
// Handle elimination requests
if (num == 999) {
list parts = llParseString2List(str, ["|"], []);
if (llList2String(parts, 0) == "ELIMINATE_PLAYER") {
string eliminatedPlayer = llList2String(parts, 1);
integer idx = llListFindList(names, [eliminatedPlayer]);
if (idx != -1) {
// Remove player's float using the tracked channel
integer ch = llList2Integer(floaterChannels, idx);
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
// Remove from all lists
players = llDeleteSubList(players, idx, idx);
names = llDeleteSubList(names, idx, idx);
lives = llDeleteSubList(lives, idx, idx);
picksData = llDeleteSubList(picksData, idx, idx);
floaterChannels = llDeleteSubList(floaterChannels, idx, idx);
llOwnerSay("🗑️ Eliminated " + eliminatedPlayer + ". Remaining players: " + (string)llGetListLength(names));
// Show elimination status when someone is actually eliminated
sendStatusMessage("Elimination");
// Update all scripts with the new player lists after elimination
updateHelpers();
// Check if game should end (1 or fewer players remaining)
if (llGetListLength(names) <= 1) {
if (llGetListLength(names) == 1) {
string winner = llList2String(names, 0);
llSay(0, "✨ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!");
// Trigger victory confetti and record win
llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY);
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
llRegionSay(-12345, "GAME_WON|" + winner); // Send winner info to scoreboard
// Wait for elimination status to display before showing victory
llSleep(STATUS_DISPLAY_TIME * 0.8); // Wait 80% of status display time (~6.4 seconds)
sendStatusMessage("Victory"); // Show victory status
// Wait for victory status to display before reset
llSleep(STATUS_DISPLAY_TIME + 1.0);
} else {
llSay(0, "💀 DESPAIR WINS! No Ultimate Survivors remain!");
// Wait for elimination status to display before reset (elimination already sent above)
llSleep(STATUS_DISPLAY_TIME + 1.0);
}
resetGame();
return;
}
// If the eliminated player was the peril player, assign a new one and continue game
if (eliminatedPlayer == perilPlayer) {
// Find first remaining alive player to be new peril player
perilPlayer = "";
integer k;
for (k = 0; k < llGetListLength(names) && perilPlayer == ""; k++) {
string candidateName = llList2String(names, k);
if (llList2Integer(lives, k) > 0) {
perilPlayer = candidateName;
llOwnerSay("🔄 After elimination, new peril player: " + perilPlayer);
}
}
// Continue the game with the new peril player (don't reset roundStarted)
if (perilPlayer != "") {
llOwnerSay("🎯 Continuing game with " + perilPlayer + " as new peril player");
// Reset picks and queue for new round, but keep roundStarted = TRUE
picksData = [];
globalPickedNumbers = [];
pickQueue = [perilPlayer];
integer i;
for (i = 0; i < llGetListLength(names); i++) {
string playerName = llList2String(names, i);
if (playerName != perilPlayer) {
pickQueue += [playerName];
}
}
currentPickerIdx = 0;
updateHelpers();
requestDiceType();
}
} else {
// Continue with existing peril player
updateHelpers();
}
}
}
return;
}
// Handle game won
if (num == 998) {
list parts = llParseString2List(str, ["|"], []);
if (llList2String(parts, 0) == "GAME_WON") {
string winner = llList2String(parts, 1);
llRegionSay(-12346, "GAME_WON|" + winner); // Send winner info to scoreboard
llSleep(2.0); // Let celebration message display
resetGame();
}
return;
}
// Handle incoming sync updates from Roll Confetti Module (only after rolls)
if (num == MSG_SYNC_GAME_STATE) {
// Only accept sync if it contains different data than what we have
list parts = llParseString2List(str, ["~"], []);
if (llGetListLength(parts) >= 4) {
list newLives = llCSV2List(llList2String(parts, 0));
string currentLivesStr = llList2CSV(lives);
string newLivesStr = llList2CSV(newLives);
// Check if we need to update (lives changed OR peril player changed)
string newPerilPlayerCheck = llList2String(parts, 2);
// Don't accept "NONE" as a valid peril player if we already have a real one,
// UNLESS the eliminated player is trying to be set as peril player
if (newPerilPlayerCheck == "NONE" && perilPlayer != "" && perilPlayer != "NONE") {
// Check if current peril player is still in the game
integer perilStillInGame = llListFindList(names, [perilPlayer]) != -1;
if (perilStillInGame) {
return; // Don't process this sync update
}
}
integer perilChanged = (perilPlayer != newPerilPlayerCheck);
integer livesChanged = (newLivesStr != currentLivesStr);
if (livesChanged || perilChanged) {
// Decode the pickData from the encoded format
string encodedPicksDataStr = llList2String(parts, 1);
list newPicksData = [];
if (encodedPicksDataStr != "") {
list encodedEntries = llParseString2List(encodedPicksDataStr, ["^"], []);
integer i;
for (i = 0; i < llGetListLength(encodedEntries); i++) {
string entry = llList2String(encodedEntries, i);
list entryParts = llParseString2List(entry, ["|"], []);
if (llGetListLength(entryParts) >= 2) {
string playerName = llList2String(entryParts, 0);
string picks = llList2String(entryParts, 1);
// Convert semicolons back to commas
picks = llDumpList2String(llParseString2List(picks, [";"], []), ",");
newPicksData += [playerName + "|" + picks];
} else {
newPicksData += [entry];
}
}
}
string newPerilPlayer = llList2String(parts, 2);
list newNames = llCSV2List(llList2String(parts, 3));
// Update our game state with the new data
lives = newLives;
picksData = newPicksData;
perilPlayer = newPerilPlayer;
names = newNames;
// Don't call updateHelpers() here to avoid loop - other modules already have the data
}
}
return;
}
// Handle start next round request
if (num == 997) {
if (str == "START_NEXT_ROUND") {
startNextRound();
requestDiceType();
}
else if (llSubStringIndex(str, "START_NEXT_ROUND_DIALOG|") == 0) {
string playerName = llGetSubString(str, 24, -1); // Remove "START_NEXT_ROUND_DIALOG|" prefix
integer idx = llListFindList(names, [playerName]);
if (idx != -1) {
key playerKey = llList2Key(players, idx);
llOwnerSay("🎯 Showing next round dialog to " + playerName);
// Check if it's a bot
if (llSubStringIndex(playerName, "TestBot") == 0) {
// Auto-start for bots with delay to allow sync to complete
llOwnerSay("🤖 " + playerName + " (bot) auto-starting next round...");
llSleep(0.5); // Give sync time to complete
startNextRound();
requestDiceType();
} else {
// Show dialog for humans
llDialog(playerKey, "💀 YOU ARE IN ULTIMATE PERIL! Are you ready to face the deadly challenge?", ["BEGIN KILLING GAME"], -77999);
}
}
}
return;
}
// Handle dice type request from roll module
if (num == 996) {
if (str == "GET_DICE_TYPE") {
llOwnerSay("🎲 Main Controller sending dice type: " + (string)diceType);
llMessageLinked(LINK_SET, MSG_ROLL_RESULT, (string)diceType, NULL_KEY);
}
return;
}
// Handle leave game requests
if (num == 107) {
list parts = llParseString2List(str, ["|"], []);
if (llList2String(parts, 0) == "LEAVE_GAME") {
string leavingName = llList2String(parts, 1);
key leavingKey = (key)llList2String(parts, 2);
integer idx = llListFindList(players, [leavingKey]);
if (idx != -1) {
// Remove player's float using the tracked channel
integer ch = llList2Integer(floaterChannels, idx);
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
// Remove from all lists including ready state
players = llDeleteSubList(players, idx, idx);
names = llDeleteSubList(names, idx, idx);
lives = llDeleteSubList(lives, idx, idx);
picksData = llDeleteSubList(picksData, idx, idx);
floaterChannels = llDeleteSubList(floaterChannels, idx, idx);
// Remove from ready list if present
integer readyIdx = llListFindList(readyPlayers, [leavingName]);
if (readyIdx != -1) {
readyPlayers = llDeleteSubList(readyPlayers, readyIdx, readyIdx);
}
// Update helpers
updateHelpers();
llOwnerSay("👋 " + leavingName + " left the game");
// Check if game should end (less than 2 players)
if (llGetListLength(names) == 1) {
string winner = llList2String(names, 0);
llSay(0, "✨ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!");
// Trigger victory confetti and record win
llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY);
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
llRegionSay(-12345, "GAME_WON|" + winner); // Send winner info to scoreboard
sendStatusMessage("Victory"); // Show victory status
// Wait for victory status to display before reset
llSleep(STATUS_DISPLAY_TIME + 1.0);
resetGame();
}
}
}
return;
}
// Handle ready state toggle
if (num == MSG_TOGGLE_READY) {
string playerName = str;
integer idx = llListFindList(names, [playerName]);
if (idx != -1) {
// Don't allow bots to toggle ready state (they're always ready)
if (llSubStringIndex(playerName, "TestBot") == 0) {
llOwnerSay("🤖 Bots are always ready and cannot change state");
return;
}
integer readyIdx = llListFindList(readyPlayers, [playerName]);
if (readyIdx == -1) {
// Player is not ready, make them ready
readyPlayers += [playerName];
llSay(0, "⚔️ " + playerName + " steels themselves for the deadly challenge ahead! ⚔️");
} else {
// Player is ready, make them not ready
readyPlayers = llDeleteSubList(readyPlayers, readyIdx, readyIdx);
llSay(0, "🏃 " + playerName + " loses their nerve and backs away from the challenge! 🏃");
}
}
return;
}
// Handle ready state queries
if (num == MSG_QUERY_READY_STATE) {
string playerName = str;
integer isReady = llListFindList(readyPlayers, [playerName]) != -1;
integer isBot = llSubStringIndex(playerName, "TestBot") == 0;
string result = playerName + "|" + (string)isReady + "|" + (string)isBot;
llMessageLinked(LINK_SET, MSG_READY_STATE_RESULT, result, id);
return;
}
// Handle aggressive floater cleanup
if (num == MSG_CLEANUP_ALL_FLOATERS) {
integer i;
for (i = 0; i < MAX_PLAYERS; i++) {
integer ch = -777000 + i;
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
// Also try some potential duplicate channels
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)(ch + 100), NULL_KEY);
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)(ch + 1000), NULL_KEY);
}
// Reset our tracked channels list
floaterChannels = [];
return;
}
// Handle HUMAN_PICKED messages from dialog handler
if (num == -9998 && llSubStringIndex(str, "HUMAN_PICKED:") == 0) {
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) {
// Find existing picks entry or add new one
integer picksIdx = -1;
integer k;
for (k = 0; k < llGetListLength(picksData); k++) {
string entry = llList2String(picksData, k);
if (llSubStringIndex(entry, playerName + "|") == 0) {
picksIdx = k;
}
}
string newEntry = playerName + "|" + picksStr;
if (picksIdx == -1) {
picksData += [newEntry];
} else {
picksData = llListReplaceList(picksData, [newEntry], picksIdx, picksIdx);
}
// Update global picked numbers list
list newPicks = llParseString2List(picksStr, [","], []);
integer i;
for (i = 0; i < llGetListLength(newPicks); i++) {
string pick = llList2String(newPicks, i);
if (llListFindList(globalPickedNumbers, [pick]) == -1) {
globalPickedNumbers += [pick];
}
}
updateHelpers();
llSay(0, "🎯 " + playerName + " stakes their life on numbers: " + picksStr + " 🎲");
// Add delay before moving to next picker to prevent dialog system overload
llSleep(HUMAN_PICK_DELAY);
// Move to next picker
currentPickerIdx++;
if (currentPickerIdx < llGetListLength(pickQueue)) {
showNextPickerDialog();
} else {
llOwnerSay("✅ All players have picked their numbers!");
// Show roll dialog to peril player
if (perilPlayer == "" || perilPlayer == "NONE") {
llOwnerSay("❌ ERROR: Cannot show roll dialog - perilPlayer is invalid: '" + perilPlayer + "'");
return;
}
key perilKey = llList2Key(players, llListFindList(names, [perilPlayer]));
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, perilKey);
}
}
}
return;
}
// Handle BOT_PICKED messages from Bot Manager
if (num == -9997 && llSubStringIndex(str, "BOT_PICKED:") == 0) {
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) {
// Find existing picks entry or add new one
integer picksIdx = -1;
integer k;
for (k = 0; k < llGetListLength(picksData); k++) {
string entry = llList2String(picksData, k);
if (llSubStringIndex(entry, playerName + "|") == 0) {
picksIdx = k;
}
}
string newEntry = playerName + "|" + picksStr;
if (picksIdx == -1) {
picksData += [newEntry];
} else {
picksData = llListReplaceList(picksData, [newEntry], picksIdx, picksIdx);
}
// 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();
llSay(0, "🎯 " + playerName + " (bot) stakes their digital life on numbers: " + picksStr + " 🎲");
// Add delay after bot picks to prevent dialog system overload
llSleep(BOT_PICK_DELAY);
// 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
if (perilPlayer == "" || perilPlayer == "NONE") {
llOwnerSay("❌ ERROR: Cannot show roll dialog - perilPlayer is invalid: '" + perilPlayer + "'");
return;
}
key perilKey = llList2Key(players, llListFindList(names, [perilPlayer]));
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, perilKey);
}
}
}
return;
}
}
listen(integer channel, string name, key id, string msg) {
if (channel == rollDialogChannel) {
if (msg == "Start Next Round" || msg == "BEGIN KILLING GAME") {
llSay(0, "⚡ THE KILLING GAME CONTINUES! " + perilPlayer + " begins the next deadly round!");
startNextRound();
requestDiceType();
return;
}
}
if (channel == DIALOG_CHANNEL) {
// Handle owner choice dialog responses during gameplay
if (id == llGetOwner() && roundStarted) {
if (msg == "🔄 Recover Pick Dialog") {
string ownerName = llKey2Name(id);
llRegionSayTo(id, 0, "🔄 Restoring your number picking dialog...");
llMessageLinked(LINK_SET, MSG_GET_CURRENT_DIALOG, ownerName, id);
return;
}
else if (msg == "🔄 Recover Roll Dialog") {
llRegionSayTo(id, 0, "🔄 Restoring your roll dialog...");
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, id);
return;
}
else if (msg == "🔧 Admin Menu") {
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|0", id);
return;
}
}
// Owner-specific commands: only owner messages in the dialog should be processed here.
if (id == llGetOwner()) {
if (msg == "Reset Game") {
resetGame(); // Only reset current game, keep leaderboard
return;
}
if (msg == "Reset Leaderboard") {
llRegionSay(-12345, "RESET_LEADERBOARD"); // Reset only leaderboard scores
llOwnerSay("🏆 Leaderboard scores reset - game wins cleared!");
return;
}
if (msg == "Reset All") {
resetGame(); // Reset current game
llRegionSay(-12345, "RESET_LEADERBOARD"); // Reset leaderboard scores
llOwnerSay("🔄 Complete reset - game and leaderboard cleared!");
return;
}
// Note: do not handle "Start Game" here; allow the generic start logic below to apply,
// so that minimum player checks are enforced and non-owner starters can initiate the game.
if (msg == "Add Test Player") {
// Ensure we do not exceed the maximum allowed players
if (llGetListLength(players) >= MAX_PLAYERS) {
llOwnerSay("⚠️ Cannot add test player; the game is full (max " + (string)MAX_PLAYERS + ").");
return;
}
// Generate a unique name and key for the test player
string testName = "TestBot" + (string)llGetUnixTime();
key fake = llGenerateKey();
// Delegate registration to the MSG_REGISTER_PLAYER handler; it will add to lists and rez a float
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, testName + "|" + (string)fake, NULL_KEY);
// Show the owner menu again - owner should still be starter after adding bots
integer ownerIdx = llListFindList(players, [llGetOwner()]);
integer ownerIsStarter = TRUE; // Default to TRUE for owner
integer n;
for (n = 0; n < ownerIdx && ownerIsStarter; n++) {
string existingName = llList2String(names, n);
// If there's a human player before the owner, owner is not starter
if (llSubStringIndex(existingName, "TestBot") != 0) {
ownerIsStarter = FALSE;
}
}
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)ownerIsStarter, llGetOwner());
return;
}
}
// Allow the first registered player (index 0) or the owner to start the game.
// When the starter clicks "Start Game", begin the round.
if (msg == "Start Game") {
// Enforce a minimum of 2 players before starting
if (llGetListLength(players) < 2) {
llOwnerSay("⚠️ Need at least 2 players to start the game.");
return;
}
// Check if all non-starter players are ready
string starterName = "";
if (id == llGetOwner()) {
starterName = llKey2Name(llGetOwner());
} else if (llGetListLength(players) > 0) {
key firstPlayer = llList2Key(players, 0);
if (id == firstPlayer) {
starterName = llKey2Name(firstPlayer);
}
}
if (starterName == "") {
llOwnerSay("⚠️ Only the game starter can start the game.");
return;
}
// Check if all non-starter human players are ready
list notReadyPlayers = [];
integer i;
for (i = 0; i < llGetListLength(names); i++) {
string playerName = llList2String(names, i);
// Skip the starter and bots (bots are auto-ready)
if (playerName != starterName && llSubStringIndex(playerName, "TestBot") != 0) {
if (llListFindList(readyPlayers, [playerName]) == -1) {
notReadyPlayers += [playerName];
}
}
}
if (llGetListLength(notReadyPlayers) > 0) {
llOwnerSay("⚠️ Cannot start game. These players are not ready: " + llList2CSV(notReadyPlayers));
return;
}
// All checks passed, start the game
llSay(0, "⚡ ALL PARTICIPANTS READY! THE DEADLY PERIL DICE GAME BEGINS! ⚡");
// Change controller display to show game is active
llSetText("🎮 GAME IN PROGRESS\nRound " + (string)(llGetListLength(names)) + " players", <1.0, 0.2, 0.2>, 1.0);
sendStatusMessage("Title"); // Show title status at game start
startNextRound();
requestDiceType();
}
// If the message text matches a player name, request their pick list
if (llListFindList(names, [msg]) != -1) {
llMessageLinked(LINK_SET, 206, msg, id);
}
}
}
timer() {
// Check if we need to clear status message
if (statusTimer > 0 && (llGetUnixTime() - statusTimer) >= STATUS_DISPLAY_TIME) {
// Clear the status and revert to title
statusTimer = 0;
lastStatus = "";
llRegionSay(-12345, "GAME_STATUS|Title");
llOwnerSay("📢 Status cleared - reverted to Title");
llSetTimerEvent(0); // Stop timer if no other timing needed
}
// Handle dialog timeout warnings (existing code)
if (timeoutTimer > 0) {
integer elapsed = llGetUnixTime() - timeoutTimer;
if (elapsed >= TIMEOUT_SECONDS) {
llOwnerSay("⏰ Dialog timeout reached!");
llSetTimerEvent(0);
timeoutTimer = 0;
} else {
integer remaining = TIMEOUT_SECONDS - elapsed;
if (remaining <= warning2min && lastWarning < warning2min) {
llOwnerSay("⚠️ " + (string)(remaining / 60) + " minutes remaining for dialog response");
lastWarning = warning2min;
} else if (remaining <= warning5min && lastWarning < warning5min) {
llOwnerSay("⚠️ " + (string)(remaining / 60) + " minutes remaining for dialog response");
lastWarning = warning5min;
} else if (remaining <= warning9min && lastWarning < warning9min) {
llOwnerSay("⚠️ " + (string)(remaining / 60) + " minutes remaining for dialog response");
lastWarning = warning9min;
}
}
}
}
}