Fixed all 18 variable conflicts by renaming variables in different functions: - parts -> pickParts, actionParts, leaveParts, syncParts, ownerParts, publicParts, regionParts, dialogParts - i -> j, k, m in different loops - idx -> lifeIdx, removeIdx, leaveIdx - action -> pickAction, leaveAction - playerName -> syncPlayerName, ownerPlayerName, leavePlayerName - msgType -> ownerMsgType, publicMsgType, regionMsgType - playerKey -> regionPlayerKey, dialogPlayerKey - entry -> syncEntry Progress: 115 → 57 total errors (-58 total, -18 this fix) 9 files now completely error-free! 50% error reduction achieved!
498 lines
20 KiB
Text
498 lines
20 KiB
Text
// === Controller Message Handler Helper Script ===
|
||
// Handles non-critical message processing for the Main Controller
|
||
// Reduces Main Controller size and improves modularity
|
||
|
||
// Verbose logging control
|
||
integer VERBOSE_LOGGING = TRUE; // Global flag for verbose debug logs
|
||
integer MSG_TOGGLE_VERBOSE_LOGS = 9998; // Message to toggle verbose logging
|
||
|
||
// Memory reporting function
|
||
reportMemoryUsage(string scriptName) {
|
||
integer used = llGetUsedMemory();
|
||
integer free = llGetFreeMemory();
|
||
integer total = used + free;
|
||
float percentUsed = ((float)used / (float)total) * 100.0;
|
||
|
||
llOwnerSay("🧠 [" + scriptName + "] Memory: " +
|
||
(string)used + " used, " +
|
||
(string)free + " free (" +
|
||
llGetSubString((string)percentUsed, 0, 4) + "% used)");
|
||
}
|
||
|
||
// Message constants for communication with main controller
|
||
integer MSG_REQUEST_PLAYER_DATA = 8001;
|
||
integer MSG_REQUEST_PICK_DATA = 8002;
|
||
integer MSG_REQUEST_LIFE_DATA = 8003;
|
||
integer MSG_PICK_ACTION = 8004;
|
||
integer MSG_DICE_TYPE_REQUEST = 8005;
|
||
integer MSG_LEAVE_GAME_REQUEST = 8006;
|
||
integer MSG_OWNER_MESSAGE = 9030; // Send formatted owner message
|
||
integer MSG_PUBLIC_MESSAGE = 9031; // Send formatted public message
|
||
integer MSG_REGION_MESSAGE = 9032; // Send formatted region message
|
||
integer MSG_DIALOG_REQUEST = 9033; // Send formatted dialog
|
||
|
||
// Game state data synchronized from Main Controller
|
||
list players = [];
|
||
list names = [];
|
||
list lives = [];
|
||
list picksData = [];
|
||
list readyPlayers = [];
|
||
integer diceType = 6;
|
||
string perilPlayer = ""; // Track who the peril player is to determine if game is active
|
||
integer roundStarted = FALSE; // Track if the game round has started
|
||
|
||
// Message constants from Main Controller (synchronized)
|
||
integer MSG_SYNC_GAME_STATE = 107;
|
||
integer MSG_SYNC_PICKQUEUE = 2001;
|
||
|
||
// Channel constants for scoreboard updates
|
||
integer SCOREBOARD_CHANNEL_1;
|
||
integer SCOREBOARD_CHANNEL_2;
|
||
integer FLOATER_BASE_CHANNEL;
|
||
integer MSG_CLEANUP_FLOAT = 104;
|
||
integer MSG_UPDATE_FLOAT = 103;
|
||
|
||
// Dynamic channel calculation (matches Main Controller)
|
||
integer CHANNEL_BASE = -77000;
|
||
|
||
integer calculateChannel(integer offset) {
|
||
// Use BOTH owner's key AND object's key to make channels unique per game instance
|
||
string ownerStr = (string)llGetOwner();
|
||
string objectStr = (string)llGetKey();
|
||
string combinedStr = ownerStr + objectStr;
|
||
|
||
// Create a more unique hash using both keys
|
||
string hashStr = llMD5String(combinedStr, 0);
|
||
integer hash1 = llSubStringIndex("0123456789abcdef", llGetSubString(hashStr, 0, 0));
|
||
integer hash2 = llSubStringIndex("0123456789abcdef", llGetSubString(hashStr, 1, 1));
|
||
integer combinedHash = hash1 * 16 + hash2; // Creates 0-255 range
|
||
|
||
return CHANNEL_BASE - (offset * 1000) - combinedHash;
|
||
}
|
||
|
||
initializeChannels() {
|
||
SCOREBOARD_CHANNEL_1 = calculateChannel(6); // ~-77600 range
|
||
SCOREBOARD_CHANNEL_2 = calculateChannel(7); // ~-77700 range
|
||
FLOATER_BASE_CHANNEL = calculateChannel(9); // ~-77900 range
|
||
}
|
||
|
||
// Handle player list requests
|
||
handlePlayerListRequest(key requester) {
|
||
string namesCSV = llList2CSV(names);
|
||
llMessageLinked(LINK_SET, 203, namesCSV, requester);
|
||
}
|
||
|
||
// Handle pick data requests
|
||
handlePickDataRequest(string targetName, key requester) {
|
||
integer j;
|
||
for (j = 0; j < llGetListLength(picksData); j++) {
|
||
string rawEntry = llList2String(picksData, j);
|
||
list pickParts = llParseString2List(rawEntry, ["|"], []);
|
||
if (llList2String(pickParts, 0) == targetName) {
|
||
llMessageLinked(LINK_SET, 205, targetName + "|" + llList2String(pickParts, 1), requester);
|
||
return;
|
||
}
|
||
}
|
||
llMessageLinked(LINK_SET, 205, targetName + "|", requester);
|
||
}
|
||
|
||
// Handle life data requests
|
||
handleLifeDataRequest(string playerName, key requester) {
|
||
integer lifeIdx = llListFindList(names, [playerName]);
|
||
if (lifeIdx != -1) {
|
||
string lifeVal = llList2String(lives, lifeIdx);
|
||
llMessageLinked(LINK_SET, 208, playerName + "|" + lifeVal, requester);
|
||
}
|
||
}
|
||
|
||
// Handle pick actions (ADD_PICK/REMOVE_PICK)
|
||
handlePickAction(string actionData) {
|
||
list actionParts = llParseString2List(actionData, ["~"], []);
|
||
string pickAction = llList2String(actionParts, 0);
|
||
list args = llParseString2List(llList2String(actionParts, 1), ["|"], []);
|
||
string name = llList2String(args, 0);
|
||
string pick = llList2String(args, 1);
|
||
|
||
integer k;
|
||
for (k = 0; k < llGetListLength(picksData); k++) {
|
||
string entry = llList2String(picksData, k);
|
||
list pdParts = llParseString2List(entry, ["|"], []);
|
||
if (llList2String(pdParts, 0) == name) {
|
||
list pickList = [];
|
||
string rawPicks = llList2String(pdParts, 1);
|
||
if (rawPicks != "") {
|
||
pickList = llParseString2List(rawPicks, [","], []);
|
||
}
|
||
|
||
if (pickAction == "ADD_PICK") {
|
||
if (llListFindList(pickList, [pick]) == -1) {
|
||
pickList += [pick];
|
||
if (VERBOSE_LOGGING) {
|
||
llOwnerSay("➕ [Message Handler] Added " + pick + " to " + name);
|
||
}
|
||
}
|
||
} else if (pickAction == "REMOVE_PICK") {
|
||
integer removeIdx = llListFindList(pickList, [pick]);
|
||
if (removeIdx != -1) {
|
||
pickList = llDeleteSubList(pickList, removeIdx, removeIdx);
|
||
if (VERBOSE_LOGGING) {
|
||
llOwnerSay("➖ [Message Handler] Removed " + pick + " from " + name);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Update local picks data
|
||
picksData = llListReplaceList(picksData, [name + "|" + llList2CSV(pickList)], k, k);
|
||
|
||
// Notify Main Controller of the change
|
||
llMessageLinked(LINK_SET, 204, actionData, NULL_KEY);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Handle dice type requests
|
||
handleDiceTypeRequest(key requester) {
|
||
if (VERBOSE_LOGGING) {
|
||
llOwnerSay("🎲 [Message Handler] Sending dice type: " + (string)diceType);
|
||
}
|
||
llMessageLinked(LINK_SET, 102, (string)diceType, requester); // MSG_ROLL_RESULT
|
||
}
|
||
|
||
// Handle leave game and kick player requests
|
||
handleLeaveGameRequest(string requestData) {
|
||
list leaveParts = llParseString2List(requestData, ["|"], []);
|
||
string leaveAction = llList2String(leaveParts, 0);
|
||
if (leaveAction == "LEAVE_GAME" || leaveAction == "KICK_PLAYER") {
|
||
string leavingName = llList2String(leaveParts, 1);
|
||
key requestKey = (key)llList2String(leaveParts, 2); // This is the requester's key, not the player's key
|
||
integer leaveIdx = llListFindList(names, [leavingName]); // Find by name instead of key
|
||
|
||
if (leaveIdx != -1) {
|
||
// Update local lists FIRST to prevent race conditions
|
||
players = llDeleteSubList(players, leaveIdx, leaveIdx);
|
||
names = llDeleteSubList(names, leaveIdx, leaveIdx);
|
||
lives = llDeleteSubList(lives, leaveIdx, leaveIdx);
|
||
picksData = llDeleteSubList(picksData, leaveIdx, leaveIdx);
|
||
|
||
// THEN send floater cleanup using the previously calculated channel
|
||
// Note: We DON'T send MSG_CLEANUP_FLOAT here anymore to avoid double-cleanup
|
||
// The Main Controller will handle floater cleanup after list synchronization
|
||
if (VERBOSE_LOGGING) {
|
||
llOwnerSay("📝 [Message Handler] Updated local lists for " + leavingName + ", deferring floater cleanup to Main Controller");
|
||
}
|
||
|
||
// Remove from ready list if present
|
||
integer readyIdx = llListFindList(readyPlayers, [leavingName]);
|
||
if (readyIdx != -1) {
|
||
readyPlayers = llDeleteSubList(readyPlayers, readyIdx, readyIdx);
|
||
}
|
||
|
||
// Different messages for voluntary leave vs kick
|
||
if (leaveAction == "LEAVE_GAME") {
|
||
llOwnerSay("👋 [Message Handler] " + leavingName + " left the game");
|
||
llSay(0, "👋 " + leavingName + " has left the deadly game! 👋");
|
||
} else if (leaveAction == "KICK_PLAYER") {
|
||
llOwnerSay("👢 [Message Handler] " + leavingName + " was kicked from the game");
|
||
llSay(0, "👢 " + leavingName + " has been kicked from the deadly game by the owner! 👢");
|
||
}
|
||
|
||
// Notify Main Controller of the removal
|
||
llMessageLinked(LINK_SET, MSG_LEAVE_GAME_REQUEST, requestData, NULL_KEY);
|
||
|
||
// Check if game should end (0 players = reset, 1 player = victory)
|
||
if (llGetListLength(names) == 0) {
|
||
if (VERBOSE_LOGGING) {
|
||
llOwnerSay("🔄 [Message Handler] All players left - game will reset");
|
||
}
|
||
// Main Controller will handle the reset
|
||
} else if (llGetListLength(names) == 1 && roundStarted && perilPlayer != "" && perilPlayer != "NONE") {
|
||
// Only declare victory if the game has actually started (round active and peril player assigned)
|
||
string winner = llList2String(names, 0);
|
||
llSay(0, "✨ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!");
|
||
// Notify Main Controller of victory
|
||
llMessageLinked(LINK_SET, 998, "GAME_WON|" + winner, NULL_KEY);
|
||
} else if (llGetListLength(names) == 1) {
|
||
// Game not started yet, just inform but don't declare victory
|
||
string remainingPlayer = llList2String(names, 0);
|
||
if (VERBOSE_LOGGING) {
|
||
llOwnerSay("📝 [Message Handler] Only " + remainingPlayer + " remains, but game hasn't started yet");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Synchronize game state from Main Controller
|
||
syncGameState(string stateData) {
|
||
list syncParts = llParseString2List(stateData, ["~"], []);
|
||
|
||
// Handle special RESET sync message
|
||
if (llGetListLength(syncParts) >= 5 && llList2String(syncParts, 0) == "RESET") {
|
||
if (VERBOSE_LOGGING) {
|
||
llOwnerSay("🔄 [Message Handler] Received reset sync - clearing all state");
|
||
}
|
||
// Clear all state during reset
|
||
players = [];
|
||
names = [];
|
||
lives = [];
|
||
picksData = [];
|
||
readyPlayers = [];
|
||
perilPlayer = "";
|
||
roundStarted = FALSE;
|
||
return;
|
||
}
|
||
|
||
if (llGetListLength(syncParts) >= 4) {
|
||
lives = llCSV2List(llList2String(syncParts, 0));
|
||
|
||
// Decode picks data
|
||
string encodedPicksDataStr = llList2String(syncParts, 1);
|
||
picksData = [];
|
||
if (encodedPicksDataStr != "" && encodedPicksDataStr != "EMPTY") {
|
||
list encodedEntries = llParseString2List(encodedPicksDataStr, ["^"], []);
|
||
integer m;
|
||
for (m = 0; m < llGetListLength(encodedEntries); m++) {
|
||
string syncEntry = llList2String(encodedEntries, m);
|
||
list entryParts = llParseString2List(syncEntry, ["|"], []);
|
||
if (llGetListLength(entryParts) >= 2) {
|
||
string syncPlayerName = llList2String(entryParts, 0);
|
||
string picks = llList2String(entryParts, 1);
|
||
// Convert semicolons back to commas
|
||
picks = llDumpList2String(llParseString2List(picks, [";"], []), ",");
|
||
picksData += [syncPlayerName + "|" + picks];
|
||
} else {
|
||
picksData += [syncEntry];
|
||
}
|
||
}
|
||
}
|
||
|
||
// Track peril player and round state for victory detection
|
||
string receivedPeril = llList2String(syncParts, 2);
|
||
if (receivedPeril == "NONE" || receivedPeril == "") {
|
||
perilPlayer = "";
|
||
roundStarted = FALSE;
|
||
} else {
|
||
perilPlayer = receivedPeril;
|
||
roundStarted = TRUE; // Game is active if there's a peril player
|
||
}
|
||
|
||
names = llCSV2List(llList2String(syncParts, 3));
|
||
|
||
// SAFETY: Validate peril player exists in current names list AFTER updating names
|
||
if (perilPlayer != "" && perilPlayer != "NONE") {
|
||
integer perilIdx = llListFindList(names, [perilPlayer]);
|
||
if (perilIdx == -1) {
|
||
// Only warn if we actually have players - during reset this is expected
|
||
if (llGetListLength(names) > 0 && VERBOSE_LOGGING) {
|
||
llOwnerSay("⚠️ [Message Handler] WARNING: Peril player '" + perilPlayer + "' not found in names list - clearing peril status");
|
||
}
|
||
perilPlayer = "";
|
||
roundStarted = FALSE;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Memory-efficient message handling functions
|
||
|
||
// Handle owner messages
|
||
handleOwnerMessage(string messageData) {
|
||
// Format: "simple_message" or "type|param1|param2"
|
||
list ownerParts = llParseString2List(messageData, ["|"], []);
|
||
if (llGetListLength(ownerParts) == 1) {
|
||
// Simple message
|
||
llOwnerSay(messageData);
|
||
} else {
|
||
// Formatted message
|
||
string ownerMsgType = llList2String(ownerParts, 0);
|
||
if (ownerMsgType == "JOIN") {
|
||
string ownerPlayerName = llList2String(ownerParts, 1);
|
||
llOwnerSay("👋 " + ownerPlayerName + " joined the game!");
|
||
llSay(0, "🔔 Added player: " + ownerPlayerName);
|
||
} else if (ownerMsgType == "LEAVE") {
|
||
string leavePlayerName = llList2String(ownerParts, 1);
|
||
llOwnerSay("👋 " + leavePlayerName + " left the game.");
|
||
} else if (ownerMsgType == "ERROR") {
|
||
string error = llList2String(ownerParts, 1);
|
||
llOwnerSay("⚠️ " + error);
|
||
} else if (ownerMsgType == "SUCCESS") {
|
||
string msg = llList2String(ownerParts, 1);
|
||
llOwnerSay("✅ " + msg);
|
||
} else if (ownerMsgType == "DEBUG") {
|
||
string component = llList2String(ownerParts, 1);
|
||
string msg = llList2String(ownerParts, 2);
|
||
llOwnerSay("🔧 [" + component + "] " + msg);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Handle public messages
|
||
handlePublicMessage(string messageData) {
|
||
list publicParts = llParseString2List(messageData, ["|"], []);
|
||
if (llGetListLength(publicParts) == 1) {
|
||
llSay(0, messageData);
|
||
} else {
|
||
string publicMsgType = llList2String(publicParts, 0);
|
||
if (publicMsgType == "GAME") {
|
||
string stateVal = llList2String(publicParts, 1);
|
||
llSay(0, "🎮 " + stateVal);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Handle region messages to specific players
|
||
handleRegionMessage(string messageData) {
|
||
// Format: "player_key|message" or "player_key|type|param1|param2"
|
||
list regionParts = llParseString2List(messageData, ["|"], []);
|
||
if (llGetListLength(regionParts) >= 2) {
|
||
key regionPlayerKey = llList2Key(regionParts, 0);
|
||
string message = llList2String(regionParts, 1);
|
||
|
||
if (llGetListLength(regionParts) == 2) {
|
||
// Simple message - use llSay since we can't use llRegionSayTo on channel 0
|
||
llSay(0, "[To " + llKey2Name(regionPlayerKey) + "] " + message);
|
||
} else {
|
||
// Formatted message
|
||
string regionMsgType = llList2String(regionParts, 1);
|
||
if (regionMsgType == "WELCOME") {
|
||
string msg = llList2String(regionParts, 2);
|
||
llSay(0, "[To " + llKey2Name(regionPlayerKey) + "] 🔄 " + msg);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Handle dialog requests
|
||
handleDialogRequest(string dialogData) {
|
||
// Format: "player_key|dialog_text|option1,option2,option3|channel"
|
||
list dialogParts = llParseString2List(dialogData, ["|"], []);
|
||
if (llGetListLength(dialogParts) >= 4) {
|
||
key dialogPlayerKey = llList2Key(dialogParts, 0);
|
||
string dialogText = llList2String(dialogParts, 1);
|
||
string optionsStr = llList2String(dialogParts, 2);
|
||
integer channel = llList2Integer(dialogParts, 3);
|
||
|
||
list options = llParseString2List(optionsStr, [","], []);
|
||
llDialog(dialogPlayerKey, dialogText, options, channel);
|
||
}
|
||
}
|
||
|
||
default {
|
||
state_entry() {
|
||
reportMemoryUsage("📨 Message Handler");
|
||
|
||
llOwnerSay("📨 [Message Handler] Helper script ready!");
|
||
initializeChannels();
|
||
}
|
||
|
||
link_message(integer sender, integer num, string str, key id) {
|
||
// Handle verbose logging toggle
|
||
if (num == MSG_TOGGLE_VERBOSE_LOGS) {
|
||
VERBOSE_LOGGING = !VERBOSE_LOGGING;
|
||
if (VERBOSE_LOGGING) {
|
||
llOwnerSay("🔊 [Message Handler] Verbose logging ENABLED");
|
||
} else {
|
||
llOwnerSay("🔊 [Message Handler] Verbose logging DISABLED");
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Handle player list requests (for pick management only)
|
||
if (num == 202 && str == "REQUEST_PLAYER_LIST") {
|
||
handlePlayerListRequest(id);
|
||
return;
|
||
}
|
||
|
||
// Handle kick player list requests (separate message ID to avoid conflicts)
|
||
if (num == 8009 && str == "REQUEST_PLAYER_LIST_KICK") {
|
||
// Send back kick-specific message ID to avoid conflict with pick management
|
||
string namesCSV = llList2CSV(names);
|
||
if (VERBOSE_LOGGING) {
|
||
llOwnerSay("🔍 [Message Handler] Received request: '" + str + "'");
|
||
llOwnerSay("🔍 [Message Handler] Names list: " + llDumpList2String(names, ", "));
|
||
llOwnerSay("🔍 [Message Handler] Sending kick player list: '" + namesCSV + "'");
|
||
}
|
||
llMessageLinked(LINK_SET, 8009, namesCSV, id);
|
||
return;
|
||
}
|
||
|
||
// Handle pick data requests
|
||
if (num == 206) {
|
||
handlePickDataRequest(str, id);
|
||
return;
|
||
}
|
||
|
||
// Handle life data requests
|
||
if (num == 208) {
|
||
handleLifeDataRequest(str, id);
|
||
return;
|
||
}
|
||
|
||
// Handle pick actions
|
||
if (num == 204) {
|
||
handlePickAction(str);
|
||
return;
|
||
}
|
||
|
||
// Handle dice type requests
|
||
if (num == 996) {
|
||
if (str == "GET_DICE_TYPE") {
|
||
handleDiceTypeRequest(id);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Handle leave game requests (use proper message ID, not 107 which is for sync)
|
||
if (num == 8007) { // Different from MSG_SYNC_GAME_STATE = 107
|
||
handleLeaveGameRequest(str);
|
||
return;
|
||
}
|
||
|
||
// Sync game state from Main Controller
|
||
if (num == MSG_SYNC_GAME_STATE) {
|
||
syncGameState(str);
|
||
return;
|
||
}
|
||
|
||
// Sync player list for reference
|
||
if (num == MSG_SYNC_PICKQUEUE) {
|
||
// Update players list reference (if needed for future functionality)
|
||
return;
|
||
}
|
||
|
||
// Handle memory-heavy message operations
|
||
if (num == MSG_OWNER_MESSAGE) {
|
||
handleOwnerMessage(str);
|
||
return;
|
||
}
|
||
|
||
if (num == MSG_PUBLIC_MESSAGE) {
|
||
handlePublicMessage(str);
|
||
return;
|
||
}
|
||
|
||
if (num == MSG_REGION_MESSAGE) {
|
||
handleRegionMessage(str);
|
||
return;
|
||
}
|
||
|
||
if (num == MSG_DIALOG_REQUEST) {
|
||
handleDialogRequest(str);
|
||
return;
|
||
}
|
||
|
||
// Handle full reset
|
||
if (num == -99999 && str == "FULL_RESET") {
|
||
players = [];
|
||
names = [];
|
||
lives = [];
|
||
picksData = [];
|
||
readyPlayers = [];
|
||
diceType = 6;
|
||
perilPlayer = "";
|
||
roundStarted = FALSE;
|
||
llOwnerSay("📨 [Message Handler] Reset complete");
|
||
}
|
||
}
|
||
}
|