Peril_Dice/Player_RegistrationManager.lsl

389 lines
No EOL
15 KiB
Text

#include "peril/Peril_Constants.lsl"
// === Player Registration Manager ===
// Handles all player registration, join/leave operations to reduce Main Controller memory usage
// This script takes over the memory-heavy player management operations
// Game state tracking (synced from Main Controller)
list players = [];
list names = [];
list lives = [];
list readyPlayers = [];
#define MAX_PLAYERS 10
// Dynamic channel system
#define CHANNEL_BASE -77000
integer FLOATER_BASE_CHANNEL;
integer roundStarted = FALSE;
integer gameStarting = FALSE;
// Experience Sentinel Tracking
key sentinelQueryID;
integer EX_READY = FALSE;
#define TIMER_XP_CHECK 1
integer currentTimerMode = 0;
integer resetInProgress = FALSE; // Lockout for reset syncs
integer calculateChannel(integer offset) {
string ownerStr = (string)llGetOwner();
string objectStr = (string)llGetLinkKey(1);
string combinedStr = ownerStr + objectStr;
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;
return CHANNEL_BASE - (offset * 1000) - combinedHash;
}
initializeChannels() {
FLOATER_BASE_CHANNEL = calculateChannel(9);
}
// Helper functions
string getPlayerName(key id) {
string displayName = llGetDisplayName(id);
if (displayName == "") {
displayName = llKey2Name(id);
}
return displayName;
}
ownerMsg(string msg) {
llMessageLinked(LINK_SET, MSG_OWNER_MESSAGE, msg, NULL_KEY);
}
publicMsg(string msg) {
llMessageLinked(LINK_SET, MSG_PUBLIC_MESSAGE, msg, NULL_KEY);
}
regionMsg(key player, string msg) {
llMessageLinked(LINK_SET, MSG_REGION_MESSAGE, (string)player + "|" + msg, NULL_KEY);
}
// MOVED: Experience Sentinel Logic
checkExperience() {
dbg("🔍 [RegMgr] Experience Sentinel: Starting functional diagnostics...");
if (!llAgentInExperience(llGetOwner())) {
llOwnerSay("🛡️ [Peril Dice] LAND NOTICE: Experience 'Final Girlz I.N.C.' is NOT enabled on this land.");
llOwnerSay("⚠️ [Peril Dice] Automatic HUD attachments will FAIL. To fix this:");
llOwnerSay("👉 1. Right-click the ground -> 'About Land'");
llOwnerSay("👉 2. Go to the 'Experiences' tab.");
llOwnerSay("👉 3. Click the 'Add' button.");
llOwnerSay("👉 4. Search for 'Final Girlz I.N.C.' and click 'OK'.");
sentinelQueryID = llReadKeyValue("PERIL_SENTINEL");
llSetTimerEvent(10.0);
currentTimerMode = TIMER_XP_CHECK;
} else {
llOwnerSay("✅ [Peril Dice] Experience 'Final Girlz I.N.C.' is ACTIVE. HUD Auto-attachment ready.");
EX_READY = TRUE;
sentinelQueryID = llReadKeyValue("PERIL_SENTINEL");
llSetTimerEvent(10.0);
currentTimerMode = TIMER_XP_CHECK;
}
}
// Handle player registration with all the complex logic moved here
handlePlayerRegistration(string regData, key requesterId) {
// MEMORY DEBUG: Check memory at start
integer memStart = llGetFreeMemory();
dbg("🔍 [RegMgr] Memory at start: " + (string)memStart + " free");
list parts = llParseString2List(regData, ["|"], []);
string newName = llList2String(parts, 0);
key newKey = (key)llList2String(parts, 1);
// Check game state restrictions
if ((roundStarted || gameStarting) && newKey != llGetOwner()) {
ownerMsg("ERROR|" + newName + " cannot join - game started");
regionMsg(newKey, "Game already started - wait for current game to end");
return;
}
// Check if already exists
integer existingIdx = llListFindList(players, [newKey]);
if (existingIdx != -1) {
ownerMsg("ERROR|" + newName + " already registered");
return;
}
// Check max players
if (llGetListLength(players) >= MAX_PLAYERS) {
ownerMsg("ERROR|Game full - max " + (string)MAX_PLAYERS + " players");
regionMsg(newKey, "Game is full - maximum " + (string)MAX_PLAYERS + " players allowed");
return;
}
// Add player to local lists
players += [newKey];
names += [newName];
lives += [3];
// MEMORY DEBUG: Check memory after adding to lists
integer memAfterLists = llGetFreeMemory();
dbg("🔍 [RegMgr] Memory after adding to lists: " + (string)memAfterLists + " free (used " + (string)(memStart - memAfterLists) + ")");
// Count human players to determine if this is the starter (needed for both bot and human logic)
integer humanCount = 0;
integer i;
for (i = 0; i < llGetListLength(names); i++) {
string playerName = llList2String(names, i);
if (llSubStringIndex(playerName, "Bot") != 0) {
humanCount++;
}
}
// Handle bot vs human logic
if (llSubStringIndex(newName, "Bot") == 0) {
readyPlayers += [newName];
publicMsg("GAME|" + newName + " (Bot) ready to play!");
} else {
if (humanCount == 1) {
publicMsg("👑 " + newName + " steps forward as the game starter! Touch to set your ready status.");
} else {
publicMsg("🎮 " + newName + " has joined the deadly game! Touch to set your ready status.");
}
}
// Calculate floater channel
integer newPlayerIdx = llGetListLength(names) - 1;
integer ch = FLOATER_BASE_CHANNEL + newPlayerIdx;
// OPTIMIZED: Handle all heavy processing here to minimize Main Controller work
// Send direct scoreboard update (Main Controller doesn't need to do this)
llMessageLinked(LINK_SCOREBOARD, MSG_PLAYER_UPDATE, newName + "|3|" + (string)newKey, NULL_KEY);
// Prepare sync message (Main Controller doesn't need to build this)
string syncMessage = "";
if (llGetListLength(names) == 1) {
syncMessage = "3~EMPTY~NONE~" + newName;
} else {
// Build lives string dynamically
string livesStr = "";
integer j;
for (j = 0; j < llGetListLength(names); j++) {
if (j > 0) livesStr += ",";
livesStr += "3"; // Everyone starts with 3 lives
}
syncMessage = livesStr + "~EMPTY~NONE~" + llList2CSV(names);
}
// CRITICAL FIX: Send floater rez message to Floater Manager first
llMessageLinked(LINK_SET, MSG_REZ_FLOATER, newName + "|" + (string)newKey, newKey);
// Then send the sync message to update everyone on the new state
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, syncMessage, NULL_KEY);
// Send minimal update to Main Controller - just the essential data it needs
string updateData = (string)ch + "~" + newName;
llMessageLinked(LINK_SET, MSG_UPDATE_MAIN_LISTS, updateData, newKey);
// Show appropriate menu to the new player
integer isStarter = (humanCount <= 1);
if (newKey == llGetOwner()) {
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, newKey);
} else {
// NEW: Also show menu for non-owners immediately after registration
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, newKey);
}
ownerMsg("JOIN|" + newName);
}
default {
state_entry() {
DISCOVER_CORE_LINKS();
initializeChannels();
checkExperience();
REPORT_MEMORY();
dbg("🎯 [RegMgr] Player Registration Manager ready - discovery complete! Scoreboard: " + (string)LINK_SCOREBOARD);
}
on_rez(integer start_param) {
DISCOVER_CORE_LINKS();
initializeChannels();
checkExperience();
REPORT_MEMORY();
dbg("🎯 [RegMgr] Player Registration Manager rezzed - resetting state...");
// Reset local state
players = [];
names = [];
lives = [];
readyPlayers = [];
roundStarted = FALSE;
gameStarting = FALSE;
}
link_message(integer sender, integer num, string str, key id) {
// Handle player registration requests from Main Controller
if (num == MSG_REGISTER_PLAYER_REQUEST) {
handlePlayerRegistration(str, id);
return;
}
// Handle game state sync from Main Controller
if (num == MSG_SYNC_GAME_STATE) {
if (resetInProgress) return; // IGNORE stale syncs during reset lockout
// PRESERVE PLAYER KEYS: Don't overwrite our authoritative player registry
list parts = llParseStringKeepNulls(str, ["~"], []);
if (llGetListLength(parts) >= 4) {
list newLives = llCSV2List(llList2String(parts, 0));
string picksPart = llList2String(parts, 1);
list newNames = llCSV2List(llList2String(parts, 3));
if (llGetListLength(newLives) == llGetListLength(players)) {
lives = newLives;
}
if (llGetListLength(newNames) == llGetListLength(players)) {
names = newNames;
}
dbg("🔄 [RegMgr] Synced game state, preserving " + (string)llGetListLength(players) + " player keys");
}
return;
}
// --- Status Queries ---
if (num == MSG_QUERY_READY_STATE) {
list parts = llParseString2List(str, ["|"], []);
if (llGetListLength(parts) >= 2) {
string name = llList2String(parts, 0);
string requestId = llList2String(parts, 1);
integer isReady = (llListFindList(readyPlayers, [name]) != -1);
integer isBot = (llSubStringIndex(name, "Bot") == 0);
llMessageLinked(LINK_SET, MSG_READY_STATE_RESULT,
name + "|" + (string)isReady + "|" + (string)isBot + "|" + requestId, id);
}
return;
}
if (num == MSG_QUERY_OWNER_STATUS) {
list parts = llParseString2List(str, ["|"], []);
if (llGetListLength(parts) >= 2) {
string name = llList2String(parts, 0);
string requestId = llList2String(parts, 1);
integer isRegistered = (llListFindList(players, [id]) != -1);
integer isPending = FALSE;
integer isStarter = FALSE;
if (isRegistered) {
integer starterIdx = -1;
integer i;
for (i = 0; i < llGetListLength(names) && starterIdx == -1; i++) {
if (llSubStringIndex(llList2String(names, i), "Bot") != 0) starterIdx = i;
}
if (starterIdx != -1 && llList2Key(players, starterIdx) == id) isStarter = TRUE;
}
dbg("📤 [RegMgr] Responding to OWNER STATUS (" + requestId + ") - Registered: " + (string)isRegistered);
llMessageLinked(LINK_SET, MSG_OWNER_STATUS_RESULT,
name + "|" + (string)isRegistered + "|" + (string)isPending + "|" + (string)isStarter + "|" + requestId, id);
}
return;
}
if (num == MSG_TOGGLE_READY) {
string name = str;
integer idx = llListFindList(readyPlayers, [name]);
if (idx == -1) {
readyPlayers += [name];
publicMsg("GAME|" + name + " is NOW READY!");
} else {
readyPlayers = llDeleteSubList(readyPlayers, idx, idx);
publicMsg("GAME|" + name + " is NO LONGER READY.");
}
// Broadcast the official state list to all scripts (Main Controller needs this)
llMessageLinked(LINK_SET, MSG_UPDATE_MAIN_LISTS, "READY_LIST|" + llList2CSV(readyPlayers), NULL_KEY);
return;
}
if (num == MSG_REMOVE_PLAYER) {
string leavingName = str;
integer idx = llListFindList(names, [leavingName]);
if (idx != -1) {
// Scrub from all synchronized lists
players = llDeleteSubList(players, idx, idx);
names = llDeleteSubList(names, idx, idx);
lives = llDeleteSubList(lives, idx, idx);
// ALSO scrub from ready list if they were there
integer rIdx = llListFindList(readyPlayers, [leavingName]);
if (rIdx != -1) readyPlayers = llDeleteSubList(readyPlayers, rIdx, rIdx);
dbg("🎯 [RegMgr] Deep Scrub Complete: " + leavingName + " removed from all registries.");
// Broadcast updated lists to keep Main Controller in sync
llMessageLinked(LINK_SET, MSG_UPDATE_MAIN_LISTS, "READY_LIST|" + llList2CSV(readyPlayers), NULL_KEY);
}
return;
}
// Handle dialog forwarding requests from Game Manager
if (num == MSG_DIALOG_FORWARD_REQUEST) {
list parts = llParseString2List(str, ["~"], []); // Fixed delimiter to match forwarder
if (llGetListLength(parts) >= 3) {
string dialogType = llList2String(parts, 0);
string targetPlayerName = llList2String(parts, 1);
string dialogPayload = "";
integer i;
for (i = 2; i < llGetListLength(parts); i++) {
if (i > 2) dialogPayload += "|";
dialogPayload += llList2String(parts, i);
}
integer playerIdx = llListFindList(names, [targetPlayerName]);
if (playerIdx != -1 && playerIdx < llGetListLength(players)) {
key playerKey = llList2Key(players, playerIdx);
if (playerKey != NULL_KEY) {
if (dialogType == "SHOW_DIALOG") {
llMessageLinked(LINK_SET, MSG_SHOW_DIALOG, dialogPayload, playerKey);
} else if (dialogType == "SHOW_ROLL_DIALOG") {
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, dialogPayload, playerKey);
}
}
}
}
return;
}
if (num == MSG_RESET_ALL && str == "FULL_RESET") {
resetInProgress = TRUE;
players = []; names = []; lives = []; readyPlayers = [];
roundStarted = FALSE; gameStarting = FALSE;
// Activate brief lockout timer to ignore stale syncs while Linkset settles
llSetTimerEvent(0.2);
dbg("🎯 [RegMgr] Player Registration Manager reset complete (Lockout Active)");
return;
}
}
timer() {
if (currentTimerMode == TIMER_XP_CHECK) {
// Diagnostic timer complete
llSetTimerEvent(0);
currentTimerMode = 0;
return;
}
resetInProgress = FALSE;
llSetTimerEvent(0);
dbg("🎯 [RegMgr] Reset lockout complete - sync active.");
}
dataserver(key queryid, string data) {
if (queryid == sentinelQueryID) {
dbg("📡 [RegMgr] Experience KVP Heartbeat: " + data);
}
}
}