updated all scripts to new directory and #includes, and on sl the files now are #include "peril/filename.lsl" while fs preprocessor is now including ~/lsl-projects/
This commit is contained in:
parent
1e70b70b72
commit
3f2fa97818
21 changed files with 331 additions and 536 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// Handles logic for test players (bots): picking numbers and rolling dice
|
// Handles logic for test players (bots): picking numbers and rolling dice
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// === Controller Memory Monitor ===
|
// === Controller Memory Monitor ===
|
||||||
// Handles memory monitoring and reporting for the Main Controller
|
// Handles memory monitoring and reporting for the Main Controller
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// === Controller Message Handler Helper Script ===
|
// === Controller Message Handler Helper Script ===
|
||||||
// Handles non-critical message processing for the Main Controller
|
// Handles non-critical message processing for the Main Controller
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
// Enhanced Link Scanner for Peril Dice Linkset
|
// Enhanced Link Scanner for Peril Dice Linkset
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
string leftText = "";
|
string leftText = "";
|
||||||
string firstName = "";
|
string firstName = "";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// FURWARE STATUS BRIDGE - Dynamic Game Info Display
|
// FURWARE STATUS BRIDGE - Dynamic Game Info Display
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// LINKSET COMMUNICATION - NO DISCOVERY NEEDED
|
// LINKSET COMMUNICATION - NO DISCOVERY NEEDED
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// === FloatManager (Consolidated) ===
|
// === FloatManager (Consolidated) ===
|
||||||
// This version enforces a maximum of 10 players.
|
// This version enforces a maximum of 10 players.
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ list setDataList; // Strided list of display set data.
|
||||||
// Per-prim data
|
// Per-prim data
|
||||||
list primLinkList; // List of the prims' link indices
|
list primLinkList; // List of the prims' link indices
|
||||||
list primFillList; // Tells us which faces are showing non-blanks.
|
list primFillList; // Tells us which faces are showing non-blanks.
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
list primLayerList; // Contains layer assignments for each face.
|
list primLayerList; // Contains layer assignments for each face.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// === Game Calculator Module (Max 10 players) ===
|
// === Game Calculator Module (Max 10 players) ===
|
||||||
// Calculates dice types, pick requirements, and other game mechanics
|
// Calculates dice types, pick requirements, and other game mechanics
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
debugMsg(string msg) {
|
debugMsg(string msg) {
|
||||||
if (DEBUG_LOGS) llMessageLinked(LINK_SET, MSG_DEBUG_TEXT, msg, NULL_KEY);
|
if (DEBUG_LOGS) llMessageLinked(LINK_SET, MSG_DEBUG_TEXT, msg, NULL_KEY);
|
||||||
|
|
@ -95,7 +95,7 @@ generateShardedData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadLBPage(integer pageNum) {
|
loadLBPage(integer pageNum) {
|
||||||
if ((isBusy || isSaving || isLocking) && pageNum == 1) {
|
if ((isBusy || isSaving) && pageNum == 1) {
|
||||||
dbg("⏳ [Leaderboard] Sync requested while Busy/Saving. Ignoring duplicate reset.");
|
dbg("⏳ [Leaderboard] Sync requested while Busy/Saving. Ignoring duplicate reset.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -113,8 +113,8 @@ loadLBPage(integer pageNum) {
|
||||||
|
|
||||||
if (kvpReadReq == NULL_KEY) {
|
if (kvpReadReq == NULL_KEY) {
|
||||||
dbg("⚠️ [Leaderboard] KVP Request FAILED (Throttle/NULL_KEY). Retrying " + keyName + " in 2s...");
|
dbg("⚠️ [Leaderboard] KVP Request FAILED (Throttle/NULL_KEY). Retrying " + keyName + " in 2s...");
|
||||||
isBusy = FALSE; // Unlock for retry
|
// KEEP isBusy = TRUE to prevent collisions during retry
|
||||||
llSetTimerEvent(2.0); // Use timer to trigger the retry
|
llSetTimerEvent(2.0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,14 +147,14 @@ updatePlayerRecord(string pName, string pResult) {
|
||||||
if (pResult == "WIN") leaderboardData += [1, 0, pName];
|
if (pResult == "WIN") leaderboardData += [1, 0, pName];
|
||||||
else leaderboardData += [0, 1, pName];
|
else leaderboardData += [0, 1, pName];
|
||||||
} else {
|
} else {
|
||||||
integer _base = _idx - 2;
|
integer _updBase = _idx - 2;
|
||||||
integer _v;
|
integer _updVal;
|
||||||
if (pResult == "WIN") {
|
if (pResult == "WIN") {
|
||||||
_v = llList2Integer(leaderboardData, _base);
|
_updVal = llList2Integer(leaderboardData, _updBase);
|
||||||
leaderboardData = llListReplaceList(leaderboardData, [_v + 1], _base, _base);
|
leaderboardData = llListReplaceList(leaderboardData, [_updVal + 1], _updBase, _updBase);
|
||||||
} else {
|
} else {
|
||||||
_v = llList2Integer(leaderboardData, _base + 1);
|
_updVal = llList2Integer(leaderboardData, _updBase + 1);
|
||||||
leaderboardData = llListReplaceList(leaderboardData, [_v + 1], _base+1, _base+1);
|
leaderboardData = llListReplaceList(leaderboardData, [_updVal + 1], _updBase + 1, _updBase + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,16 +234,16 @@ default {
|
||||||
string _rawEntry = llList2String(_ent, _ei);
|
string _rawEntry = llList2String(_ent, _ei);
|
||||||
if (llSubStringIndex(_rawEntry, ",") != -1) {
|
if (llSubStringIndex(_rawEntry, ",") != -1) {
|
||||||
// NEW FORMAT: Wins,Losses,Name
|
// NEW FORMAT: Wins,Losses,Name
|
||||||
list _it = llParseString2List(_rawEntry, [","], []);
|
list _itNew = llParseString2List(_rawEntry, [","], []);
|
||||||
if (llGetListLength(_it) >= 3) {
|
if (llGetListLength(_itNew) >= 3) {
|
||||||
leaderboardData += [(integer)llList2String(_it, 0), (integer)llList2String(_it, 1), llList2String(_it, 2)];
|
leaderboardData += [(integer)llList2String(_itNew, 0), (integer)llList2String(_itNew, 1), llList2String(_itNew, 2)];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// OLD LEGACY FORMAT: Name:Wins:Losses
|
// OLD LEGACY FORMAT: Name:Wins:Losses
|
||||||
list _it = llParseString2List(_rawEntry, [":"], []);
|
list _itOld = llParseString2List(_rawEntry, [":"], []);
|
||||||
if (llGetListLength(_it) >= 3) {
|
if (llGetListLength(_itOld) >= 3) {
|
||||||
// Translate to new stride structure in memory
|
// Translate to new stride structure in memory
|
||||||
leaderboardData += [(integer)llList2String(_it, 1), (integer)llList2String(_it, 2), llList2String(_it, 0)];
|
leaderboardData += [(integer)llList2String(_itOld, 1), (integer)llList2String(_itOld, 2), llList2String(_itOld, 0)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -253,40 +253,54 @@ default {
|
||||||
retryCount = 0; // Reset retries for next page
|
retryCount = 0; // Reset retries for next page
|
||||||
loadLBPage(currentLoadPage + 1);
|
loadLBPage(currentLoadPage + 1);
|
||||||
} else {
|
} else {
|
||||||
if (isBusy) {
|
// SYNC COMPLETE (Natural end)
|
||||||
isBusy = FALSE;
|
llSetTimerEvent(0);
|
||||||
llSetTimerEvent(0);
|
dbg("🏆 [Leaderboard] SYNC COMPLETE - " + (string)(llGetListLength(leaderboardData)/3) + " players.");
|
||||||
dbg("🏆 [Leaderboard] SYNC COMPLETE - " + (string)(llGetListLength(leaderboardData)/3) + " players.");
|
llMessageLinked(LINK_SET, MSG_LB_LOAD_COMPLETE, "", NULL_KEY);
|
||||||
llMessageLinked(LINK_SET, MSG_LB_LOAD_COMPLETE, "", NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_LB_REQUEST_DISPLAY, "0", NULL_KEY); // Show page 1
|
||||||
llMessageLinked(LINK_SET, MSG_LB_REQUEST_DISPLAY, "0", NULL_KEY); // Show page 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// IF WE WERE SYNCING FOR AN UPDATE, APPLY IT NOW
|
// Check if any records were queued while we were busy or saving
|
||||||
if (isSaving && llGetListLength(pendingRecords) > 0) {
|
if (llGetListLength(pendingRecords) > 0) {
|
||||||
isSaving = FALSE;
|
dbg("🔄 [Leaderboard] Processing queued record for [" + llList2String(pendingRecords, 0) + "]...");
|
||||||
string nextName = llList2String(pendingRecords, 0);
|
string nextName = llList2String(pendingRecords, 0);
|
||||||
string nextRes = llList2String(pendingRecords, 1);
|
string nextRes = llList2String(pendingRecords, 1);
|
||||||
pendingRecords = llDeleteSubList(pendingRecords, 0, 1);
|
pendingRecords = llDeleteSubList(pendingRecords, 0, 1);
|
||||||
|
isBusy = FALSE;
|
||||||
|
isSaving = FALSE;
|
||||||
updatePlayerRecord(nextName, nextRes);
|
updatePlayerRecord(nextName, nextRes);
|
||||||
|
} else {
|
||||||
|
isBusy = FALSE;
|
||||||
|
isSaving = FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (_st == "0") {
|
} else if (_st == "0") {
|
||||||
if (_vl == "1") { // XP_ERROR_THROTTLED
|
if (_vl == "1") { // XP_ERROR_THROTTLED
|
||||||
dbg("⚠️ [Leaderboard] Read throttled. Retrying Shard " + (string)currentLoadPage + " in 2s...");
|
dbg("⚠️ [Leaderboard] Read throttled. Retrying Shard " + (string)currentLoadPage + " in 2s...");
|
||||||
llSleep(2.0);
|
llSleep(2.0);
|
||||||
isBusy = FALSE;
|
// RETAIN isBusy/isSaving status during retry
|
||||||
loadLBPage(currentLoadPage);
|
loadLBPage(currentLoadPage);
|
||||||
} else if (_vl == "14") { // XP_ERROR_KEY_NOT_FOUND (Value is empty)
|
} else if (_vl == "14") { // XP_ERROR_KEY_NOT_FOUND (Value is empty)
|
||||||
isBusy = FALSE;
|
|
||||||
llSetTimerEvent(0);
|
|
||||||
dbg("🏆 [Leaderboard] Shard " + (string)currentLoadPage + " is empty (New).");
|
|
||||||
|
|
||||||
if (currentLoadPage < 10) {
|
if (currentLoadPage < 10) {
|
||||||
loadLBPage(currentLoadPage + 1);
|
loadLBPage(currentLoadPage + 1);
|
||||||
} else if (isBusy) {
|
} else {
|
||||||
|
// SYNC COMPLETE (Empty trailing shards)
|
||||||
|
llSetTimerEvent(0);
|
||||||
dbg("🏆 [Leaderboard] SYNC COMPLETE - " + (string)(llGetListLength(leaderboardData)/3) + " players total.");
|
dbg("🏆 [Leaderboard] SYNC COMPLETE - " + (string)(llGetListLength(leaderboardData)/3) + " players total.");
|
||||||
llMessageLinked(LINK_SET, MSG_LB_LOAD_COMPLETE, "", NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_LB_LOAD_COMPLETE, "", NULL_KEY);
|
||||||
llMessageLinked(LINK_SET, MSG_LB_REQUEST_DISPLAY, "0", NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_LB_REQUEST_DISPLAY, "0", NULL_KEY);
|
||||||
|
|
||||||
|
if (llGetListLength(pendingRecords) > 0) {
|
||||||
|
dbg("🔄 [Leaderboard] Processing queued record after empty shards...");
|
||||||
|
string nextName = llList2String(pendingRecords, 0);
|
||||||
|
string nextRes = llList2String(pendingRecords, 1);
|
||||||
|
pendingRecords = llDeleteSubList(pendingRecords, 0, 1);
|
||||||
|
isBusy = FALSE;
|
||||||
|
isSaving = FALSE;
|
||||||
|
updatePlayerRecord(nextName, nextRes);
|
||||||
|
} else {
|
||||||
|
isBusy = FALSE;
|
||||||
|
isSaving = FALSE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dbg("❌ [Leaderboard] Read Error (Code " + _vl + ") on Shard " + (string)currentLoadPage);
|
dbg("❌ [Leaderboard] Read Error (Code " + _vl + ") on Shard " + (string)currentLoadPage);
|
||||||
|
|
@ -359,18 +373,18 @@ default {
|
||||||
|
|
||||||
if (llList2String(_p, 0) == (string)llGetKey()) {
|
if (llList2String(_p, 0) == (string)llGetKey()) {
|
||||||
dbg("🔐 [Leaderboard] Lock already held by this board (Self-Sync). Proceeding...");
|
dbg("🔐 [Leaderboard] Lock already held by this board (Self-Sync). Proceeding...");
|
||||||
myLockVal = _curVal; // CRITICAL: Sync timestamp to allow future release
|
myLockVal = _curVal;
|
||||||
isLocking = FALSE;
|
isLocking = FALSE;
|
||||||
isSaving = TRUE; // MARK THAT WE ARE SYNCING FOR A SAVE
|
// Set isBusy before loading to ensure the guard is handled correctly
|
||||||
leaderboardData = []; // PURGE STALE MEMORY
|
isBusy = TRUE;
|
||||||
|
leaderboardData = [];
|
||||||
loadLBPage(1);
|
loadLBPage(1);
|
||||||
return; // Done
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_curVal == "" || _curVal == "OFF" || _isStale) {
|
if (_curVal == "" || _curVal == "OFF" || _isStale) {
|
||||||
dbg("🔐 [Leaderboard] Lock available (or stale). Claiming...");
|
dbg("🔐 [Leaderboard] Lock available (or stale). Claiming...");
|
||||||
myLockVal = (string)llGetKey() + ":" + (string)llGetUnixTime();
|
myLockVal = (string)llGetKey() + ":" + (string)llGetUnixTime();
|
||||||
dbg("🔓 [Leaderboard] CLAIMING WITH: [" + myLockVal + "] (Len: " + (string)llStringLength(myLockVal) + ")");
|
|
||||||
isClaiming = TRUE;
|
isClaiming = TRUE;
|
||||||
kvpClaimReq = llUpdateKeyValue(LB_LOCK_KEY, myLockVal, TRUE, _curVal);
|
kvpClaimReq = llUpdateKeyValue(LB_LOCK_KEY, myLockVal, TRUE, _curVal);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -380,9 +394,7 @@ default {
|
||||||
} else if (_st == "0" && _vl == "14") { // KEY_NOT_FOUND (Lock key doesn't exist)
|
} else if (_st == "0" && _vl == "14") { // KEY_NOT_FOUND (Lock key doesn't exist)
|
||||||
dbg("🔐 [Leaderboard] Lock doesn't exist. Creating initial lock...");
|
dbg("🔐 [Leaderboard] Lock doesn't exist. Creating initial lock...");
|
||||||
myLockVal = (string)llGetKey() + ":" + (string)llGetUnixTime();
|
myLockVal = (string)llGetKey() + ":" + (string)llGetUnixTime();
|
||||||
dbg("🔓 [Leaderboard] CLAIMING WITH: [" + myLockVal + "] (Len: " + (string)llStringLength(myLockVal) + ")");
|
|
||||||
isClaiming = TRUE;
|
isClaiming = TRUE;
|
||||||
// INITIAL CREATION: Use kvpClaimReq to match Stage 2 handler
|
|
||||||
kvpClaimReq = llUpdateKeyValue(LB_LOCK_KEY, myLockVal, FALSE, "");
|
kvpClaimReq = llUpdateKeyValue(LB_LOCK_KEY, myLockVal, FALSE, "");
|
||||||
} else if (_st == "0" && _vl == "1") { // Throttled
|
} else if (_st == "0" && _vl == "1") { // Throttled
|
||||||
dbg("⚠️ [Leaderboard] Lock check throttled. Retrying...");
|
dbg("⚠️ [Leaderboard] Lock check throttled. Retrying...");
|
||||||
|
|
@ -407,7 +419,7 @@ default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isBusy || (kvpReadReq == NULL_KEY && currentLoadPage > 0)) {
|
if (isBusy) { // DO NOT check isSaving here, as isBusy should be the authoritative lock for READS
|
||||||
if (retryCount < 3) {
|
if (retryCount < 3) {
|
||||||
retryCount++;
|
retryCount++;
|
||||||
dbg("⏳ [Leaderboard] Retrying Shard " + (string)currentLoadPage + " (" + (string)retryCount + "/3)...");
|
dbg("⏳ [Leaderboard] Retrying Shard " + (string)currentLoadPage + " (" + (string)retryCount + "/3)...");
|
||||||
|
|
@ -498,7 +510,7 @@ default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (num == MSG_RESET_ALL) {
|
else if (num == MSG_RESET_ALL) {
|
||||||
if (str == "FULL_RESET" || str == "HARD_RESET") llResetScript();
|
if (str == "HARD_RESET" || str == "ADMIN_RESET") llResetScript();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// === Game Manager - Game Flow Logic ===
|
// === Game Manager - Game Flow Logic ===
|
||||||
|
|
||||||
|
|
@ -254,9 +254,9 @@ integer startNextRound() {
|
||||||
currentPickerIdx = 0;
|
currentPickerIdx = 0;
|
||||||
dbg("🎯 Debug - Created pickQueue: " + llList2CSV(pickQueue) + ", currentPickerIdx: " + (string)currentPickerIdx);
|
dbg("🎯 Debug - Created pickQueue: " + llList2CSV(pickQueue) + ", currentPickerIdx: " + (string)currentPickerIdx);
|
||||||
|
|
||||||
integer currentPlayerCount = llGetListLength(names);
|
integer totalPlayers = llGetListLength(names);
|
||||||
if (currentPlayerCount == lastPlayerCount && diceType > 0) {
|
if (totalPlayers == lastPlayerCount && diceType > 0) {
|
||||||
dbg("🎯 [Game Manager] Player count unchanged (" + (string)currentPlayerCount + "), reusing stored dice type: d" + (string)diceType);
|
dbg("🎯 [Game Manager] Player count unchanged (" + (string)totalPlayers + "), reusing stored dice type: d" + (string)diceType);
|
||||||
|
|
||||||
// NEW: Add a brief buffer to let the previous outcome status stay on screen
|
// NEW: Add a brief buffer to let the previous outcome status stay on screen
|
||||||
llSleep(DELAY_MEDIUM_SYNC);
|
llSleep(DELAY_MEDIUM_SYNC);
|
||||||
|
|
@ -265,9 +265,9 @@ integer startNextRound() {
|
||||||
llMessageLinked(LINK_SET, MSG_STATUS_TEXT, "ROUND STARTING!\nPeril: <!c=red>" + perilPlayer, NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_STATUS_TEXT, "ROUND STARTING!\nPeril: <!c=red>" + perilPlayer, NULL_KEY);
|
||||||
showNextPickerDialog();
|
showNextPickerDialog();
|
||||||
} else {
|
} else {
|
||||||
lastPlayerCount = currentPlayerCount;
|
lastPlayerCount = totalPlayers;
|
||||||
// Request dice type for this round
|
// Request dice type for this round
|
||||||
llMessageLinked(LINK_SET, MSG_GET_DICE_TYPE, (string)currentPlayerCount, NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_GET_DICE_TYPE, (string)totalPlayers, NULL_KEY);
|
||||||
// NEW: Update status bar for round start
|
// NEW: Update status bar for round start
|
||||||
llMessageLinked(LINK_SET, MSG_STATUS_TEXT, "ROUND STARTING!\nPeril: <!c=red>" + perilPlayer, NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_STATUS_TEXT, "ROUND STARTING!\nPeril: <!c=red>" + perilPlayer, NULL_KEY);
|
||||||
}
|
}
|
||||||
|
|
@ -1097,5 +1097,90 @@ default {
|
||||||
dbg("🔒 [Game Manager] Emergency state reset complete");
|
dbg("🔒 [Game Manager] Emergency state reset complete");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle elimination requests (Offloaded from Main Controller)
|
||||||
|
if (num == MSG_ELIMINATE_PLAYER) {
|
||||||
|
list parts = llParseString2List(str, ["|"], []);
|
||||||
|
if (llList2String(parts, 0) == "ELIMINATE_PLAYER") {
|
||||||
|
string eliminatedPlayer = llList2String(parts, 1);
|
||||||
|
integer idx = llListFindList(names, [eliminatedPlayer]);
|
||||||
|
if (idx != -1) {
|
||||||
|
dbg("🎯 [Game Manager] Processing elimination for: " + eliminatedPlayer);
|
||||||
|
|
||||||
|
// NEW: Only announce elimination if it's not the end of the game (winner)
|
||||||
|
if (llGetListLength(names) > 1) {
|
||||||
|
llMessageLinked(LINK_SET, MSG_STATUS_TEXT, "PLAYER STRUCK!\n" + eliminatedPlayer + " has been eliminated!", NULL_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIRST: Set the player's lives to 0 (locally and visually)
|
||||||
|
lives = llListReplaceList(lives, [0], idx, idx);
|
||||||
|
|
||||||
|
// Update scoreboard visually for 0 hearts
|
||||||
|
llMessageLinked(LINK_SCOREBOARD, MSG_PLAYER_UPDATE,
|
||||||
|
eliminatedPlayer + "|0|" + (string)llList2Key(players, idx), NULL_KEY);
|
||||||
|
|
||||||
|
// RECORD LOSS: Send eliminated player to World Ranking database
|
||||||
|
llMessageLinked(LINK_SET, MSG_LB_RECORD_PLAYER, eliminatedPlayer + "|LOSS", NULL_KEY);
|
||||||
|
|
||||||
|
// CRITICAL: Check for victory condition BEFORE assigning new peril player
|
||||||
|
integer willCauseVictory = (llGetListLength(names) <= 2);
|
||||||
|
string potentialWinner = "";
|
||||||
|
|
||||||
|
if (willCauseVictory) {
|
||||||
|
integer w;
|
||||||
|
for (w = 0; w < llGetListLength(names); w++) {
|
||||||
|
string candidateName = llList2String(names, w);
|
||||||
|
if (candidateName != eliminatedPlayer) {
|
||||||
|
potentialWinner = candidateName;
|
||||||
|
jump found_winner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@found_winner;
|
||||||
|
|
||||||
|
if (potentialWinner != "") {
|
||||||
|
// Synchronize visually
|
||||||
|
llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_WINNER, potentialWinner, NULL_KEY);
|
||||||
|
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, potentialWinner, NULL_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visual elimination effect
|
||||||
|
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, eliminatedPlayer, NULL_KEY);
|
||||||
|
llSleep(DELAY_ELIMINATION_NOTICE);
|
||||||
|
|
||||||
|
if (willCauseVictory) {
|
||||||
|
llSleep(DELAY_VICTORY_NOTICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inform Main Controller to perform deep cleanup (list deletion)
|
||||||
|
llMessageLinked(LINK_SET, MSG_PROCESS_ELIMINATION, eliminatedPlayer, NULL_KEY);
|
||||||
|
|
||||||
|
// Handle Ultimate Victory Sequence
|
||||||
|
if (willCauseVictory && potentialWinner != "") {
|
||||||
|
// Clear local state
|
||||||
|
roundStarted = FALSE;
|
||||||
|
|
||||||
|
llSay(0, "✨ ULTIMATE VICTORY! " + potentialWinner + " is the Ultimate Survivor!");
|
||||||
|
|
||||||
|
llMessageLinked(LINK_SET, MSG_STATUS_TEXT, "=== ULTIMATE VICTORY ===\n<!c=white>" + potentialWinner + " wins!", NULL_KEY);
|
||||||
|
llMessageLinked(LINK_SET, MSG_PLAYER_WON, potentialWinner, NULL_KEY);
|
||||||
|
llMessageLinked(LINK_SET, MSG_EFFECT_CONFETTI, "VICTORY_CONFETTI", NULL_KEY);
|
||||||
|
|
||||||
|
// RECORD WIN: Send ultimate survivor to World Ranking database
|
||||||
|
llMessageLinked(LINK_SET, MSG_LB_RECORD_PLAYER, potentialWinner + "|WIN", NULL_KEY);
|
||||||
|
|
||||||
|
// Inform scoreboard of victory visuals
|
||||||
|
llMessageLinked(LINK_SCOREBOARD, MSG_GAME_STATUS, "Victory", NULL_KEY);
|
||||||
|
llMessageLinked(LINK_SCOREBOARD, MSG_GAME_WON, potentialWinner, NULL_KEY);
|
||||||
|
|
||||||
|
// CRITICAL: Signal Main Controller to perform final reset after victory delay
|
||||||
|
// Main Controller expects 998 | "GAME_WON|name"
|
||||||
|
llMessageLinked(LINK_SET, MSG_CONTINUE_GAME, "GAME_WON|" + potentialWinner, NULL_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// PERIL SCOREBOARD - GRID DISPLAY MANAGER (v3.2.7 Final)
|
// PERIL SCOREBOARD - GRID DISPLAY MANAGER (v3.2.7 Final)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// === Main Controller - Peril Dice Game (Linkset Version) ===
|
// === Main Controller - Peril Dice Game (Linkset Version) ===
|
||||||
|
|
||||||
|
|
@ -184,32 +184,7 @@ checkMemoryUsage(string context) {
|
||||||
// NOTE FOR FUTURE AI/DEVS: KVP (World Rankings) ONLY requires the script to be
|
// NOTE FOR FUTURE AI/DEVS: KVP (World Rankings) ONLY requires the script to be
|
||||||
// compiled with the Experience. Land-Scope whitelisting (llAgentInExperience)
|
// compiled with the Experience. Land-Scope whitelisting (llAgentInExperience)
|
||||||
// is ONLY required for HUD auto-attachments and Animations.
|
// is ONLY required for HUD auto-attachments and Animations.
|
||||||
checkExperience() {
|
// MOVED: checkExperience() - delegated to Player_RegistrationManager.lsl
|
||||||
dbg("🔍 [Peril Dice] Experience Sentinel: Starting functional heartbeat...");
|
|
||||||
|
|
||||||
// PROBE 1: Land-Scope Check (Crucial for HUD auto-attachment)
|
|
||||||
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'.");
|
|
||||||
|
|
||||||
// Silent probe for KVP (which works regardless)
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Full KVP heartbeat
|
|
||||||
sentinelQueryID = llReadKeyValue("PERIL_SENTINEL");
|
|
||||||
llSetTimerEvent(10.0);
|
|
||||||
currentTimerMode = TIMER_XP_CHECK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send status message to scoreboard using link messages
|
// Send status message to scoreboard using link messages
|
||||||
sendStatusMessage(string status) {
|
sendStatusMessage(string status) {
|
||||||
|
|
@ -333,8 +308,8 @@ resetGame() {
|
||||||
llMessageLinked(LINK_SET, MSG_CLEAR_DICE, "", NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_CLEAR_DICE, "", NULL_KEY);
|
||||||
|
|
||||||
// Give other scripts time to finish their reset before starting sync
|
// Give other scripts time to finish their reset before starting sync
|
||||||
// INCREASED to 2.0s for high-speed Mono startup with Debug Logs OFF
|
// INCREASED to 5.0s for high-speed Mono startup with Debug Logs OFF
|
||||||
llSleep(2.0);
|
llSleep(5.0);
|
||||||
|
|
||||||
// NEW: Trigger Leaderboard to reload rankings from KVP on reset
|
// NEW: Trigger Leaderboard to reload rankings from KVP on reset
|
||||||
llMessageLinked(LINK_SET, MSG_RESET_LEADERBOARD, "START_SYNC", NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_RESET_LEADERBOARD, "START_SYNC", NULL_KEY);
|
||||||
|
|
@ -374,9 +349,7 @@ default {
|
||||||
// Reset the game on startup to ensure clean state
|
// Reset the game on startup to ensure clean state
|
||||||
resetGame();
|
resetGame();
|
||||||
|
|
||||||
// Experience Sentinel check - Moved after reset to protect timer
|
// MOVED: checkExperience() - already called by Registration Management
|
||||||
llSleep(2.0);
|
|
||||||
checkExperience();
|
|
||||||
|
|
||||||
dbg("✅ Main Controller initialization complete!");
|
dbg("✅ Main Controller initialization complete!");
|
||||||
}
|
}
|
||||||
|
|
@ -396,8 +369,7 @@ default {
|
||||||
// Reset the game to ensure clean state when rezzed
|
// Reset the game to ensure clean state when rezzed
|
||||||
resetGame();
|
resetGame();
|
||||||
|
|
||||||
// Experience Sentinel check - Moved after reset to protect timer
|
// MOVED: checkExperience()
|
||||||
checkExperience();
|
|
||||||
|
|
||||||
dbg("✅ Main Controller reset complete after rez!");
|
dbg("✅ Main Controller reset complete after rez!");
|
||||||
}
|
}
|
||||||
|
|
@ -405,152 +377,10 @@ default {
|
||||||
touch_start(integer total_number) {
|
touch_start(integer total_number) {
|
||||||
integer clickedLink = llDetectedLinkNumber(0);
|
integer clickedLink = llDetectedLinkNumber(0);
|
||||||
string clickedName = llGetLinkName(clickedLink);
|
string clickedName = llGetLinkName(clickedLink);
|
||||||
|
|
||||||
// Ensure core links are discovered to prevent misidentification
|
|
||||||
if (LINK_LEADERBOARD_BRIDGE == -1) { DISCOVER_CORE_LINKS(); }
|
|
||||||
|
|
||||||
// IGNORE TOUCHES ON UI BRIDGES ONLY
|
|
||||||
// Always allow touch on Root (link 1) or any prim containing 'Scoreboard' or NOT containing 'FURWARE'
|
|
||||||
// ENHANCED: Also filter out click on Leaderboard elements which was causing redundant diagnostic logs
|
|
||||||
if (clickedLink != 1 && (llSubStringIndex(clickedName, "FURWARE") != -1 || llSubStringIndex(clickedName, "LB_") != -1)) {
|
|
||||||
dbg("🛑 [Touch Filter] Bypassing menu for Bridge element: " + clickedName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
key toucher = llDetectedKey(0);
|
key toucher = llDetectedKey(0);
|
||||||
integer idx = llListFindList(players, [toucher]);
|
|
||||||
|
|
||||||
// Enhanced disconnect/reconnect handling - check if this player should be picking
|
|
||||||
if (idx != -1 && toucher != llGetOwner() && roundStarted) {
|
|
||||||
string playerName = llList2String(names, idx);
|
|
||||||
|
|
||||||
// ENHANCED: Check if this player is supposed to be the current picker based on game state
|
|
||||||
integer shouldBePicking = FALSE;
|
|
||||||
if (currentPickerIdx < llGetListLength(pickQueue)) {
|
|
||||||
string expectedPickerName = llList2String(pickQueue, currentPickerIdx);
|
|
||||||
if (playerName == expectedPickerName) {
|
|
||||||
shouldBePicking = TRUE;
|
|
||||||
// Fix corrupted currentPicker state
|
|
||||||
if (currentPicker != toucher) {
|
|
||||||
currentPicker = toucher;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FALLBACK: If game is stuck and no one is set as current picker, but we're in pick phase,
|
|
||||||
// let any registered player attempt to resume (with owner confirmation)
|
|
||||||
if (!shouldBePicking && currentPicker == NULL_KEY && currentPickerIdx < llGetListLength(pickQueue)) {
|
|
||||||
if (toucher == llGetOwner()) {
|
|
||||||
currentPicker = toucher;
|
|
||||||
shouldBePicking = TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for dialog recovery (original logic OR enhanced detection)
|
|
||||||
if (currentPicker == toucher || shouldBePicking) {
|
|
||||||
llSay(0, "🔄 " + playerName + " - Welcome back! Restoring your number picking dialog...");
|
|
||||||
llMessageLinked(LINK_SET, MSG_GET_CURRENT_DIALOG, playerName, toucher);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (playerName == perilPlayer && currentPickerIdx >= llGetListLength(pickQueue)) {
|
|
||||||
llSay(0, "🔄 " + playerName + " - Welcome back! Restoring your roll dialog...");
|
|
||||||
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, toucher);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special handling for owner during gameplay
|
// DELIVER: Forward touch event to Dialog Handler to save bytecode in Main
|
||||||
if (toucher == llGetOwner() && roundStarted) {
|
llMessageLinked(LINK_SET, MSG_TOUCH_EVENT, (string)clickedLink + "|" + clickedName, toucher);
|
||||||
string ownerName = getPlayerName(toucher);
|
|
||||||
list options = [];
|
|
||||||
string menuText = "Game in progress. What would you like to do?";
|
|
||||||
|
|
||||||
integer hasActiveDialog = FALSE;
|
|
||||||
if (currentPicker == toucher) {
|
|
||||||
options += ["🔄 Recover Pick Dialog"];
|
|
||||||
hasActiveDialog = TRUE;
|
|
||||||
}
|
|
||||||
if (ownerName == perilPlayer && currentPickerIdx >= llGetListLength(pickQueue)) {
|
|
||||||
options += ["🔄 Recover Roll Dialog"];
|
|
||||||
hasActiveDialog = TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
options += ["🔧 Admin Menu"];
|
|
||||||
|
|
||||||
if (!hasActiveDialog) {
|
|
||||||
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|0", toucher);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
llDialog(toucher, menuText, options, DIALOG_CHANNEL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toucher == llGetOwner()) {
|
|
||||||
dbg("🔍 [DEBUG] Owner touched - idx: " + (string)idx + ", players list: " + llList2CSV(players));
|
|
||||||
if (idx == -1) {
|
|
||||||
// Owner is not in game - show join/admin choice
|
|
||||||
if (llListFindList(pendingRegistrations, [toucher]) != -1) {
|
|
||||||
dbg("⏳ Registration already in progress for " + getPlayerName(toucher));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Owner is not in game - determine menu type based on player count
|
|
||||||
if (llGetListLength(players) >= 2) {
|
|
||||||
// Enough players for game - show starter menu
|
|
||||||
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "unregistered_owner_starter|1", toucher);
|
|
||||||
} else {
|
|
||||||
// Not enough players - show basic menu
|
|
||||||
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "unregistered_owner|0", toucher);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Owner is already in game - show player menu with appropriate starter status
|
|
||||||
integer isStarter = TRUE;
|
|
||||||
integer k;
|
|
||||||
for (k = 0; k < idx && isStarter; k++) {
|
|
||||||
string existingName = llList2String(names, k);
|
|
||||||
if (llSubStringIndex(existingName, "Bot") != 0) {
|
|
||||||
isStarter = FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (idx == 0) {
|
|
||||||
isStarter = TRUE;
|
|
||||||
}
|
|
||||||
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, toucher);
|
|
||||||
}
|
|
||||||
} else if (idx != -1) {
|
|
||||||
// Non-owner player who is already registered
|
|
||||||
integer isStarter = TRUE;
|
|
||||||
integer m;
|
|
||||||
for (m = 0; m < idx && isStarter; m++) {
|
|
||||||
string existingName = llList2String(names, m);
|
|
||||||
if (llSubStringIndex(existingName, "Bot") != 0) {
|
|
||||||
isStarter = FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher);
|
|
||||||
} else {
|
|
||||||
string playerName = getPlayerName(toucher);
|
|
||||||
if (playerName != "") {
|
|
||||||
if (llListFindList(pendingRegistrations, [toucher]) != -1) {
|
|
||||||
dbg("⏳ Registration already in progress for " + playerName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingRegistrations += [toucher];
|
|
||||||
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER_REQUEST, playerName + "|" + (string)toucher, NULL_KEY);
|
|
||||||
|
|
||||||
integer isStarter = TRUE;
|
|
||||||
integer n;
|
|
||||||
for (n = 0; n < llGetListLength(names) && isStarter; n++) {
|
|
||||||
string existingName = llList2String(names, n);
|
|
||||||
if (llSubStringIndex(existingName, "Bot") != 0) {
|
|
||||||
isStarter = FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
link_message(integer sender, integer num, string str, key id) {
|
link_message(integer sender, integer num, string str, key id) {
|
||||||
|
|
@ -618,224 +448,35 @@ default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle elimination requests
|
// Handle elimination requests
|
||||||
if (num == MSG_ELIMINATE_PLAYER) {
|
// MOVED: MSG_ELIMINATE_PLAYER logic - delegated to Game_Manager.lsl
|
||||||
list parts = llParseString2List(str, ["|"], []);
|
// Process ONLY the final list cleanup here after Game Manager finishes visuals
|
||||||
if (llList2String(parts, 0) == "ELIMINATE_PLAYER") {
|
if (num == MSG_PROCESS_ELIMINATION) {
|
||||||
string eliminatedPlayer = llList2String(parts, 1);
|
string eliminatedPlayer = str;
|
||||||
integer idx = llListFindList(names, [eliminatedPlayer]);
|
integer idx = llListFindList(names, [eliminatedPlayer]);
|
||||||
if (idx != -1) {
|
if (idx != -1) {
|
||||||
// NEW: Only announce elimination if it's not the end of the game (winner)
|
// Remove from indexed lists
|
||||||
if (llGetListLength(names) > 1) {
|
players = llDeleteSubList(players, idx, idx);
|
||||||
sendStatusMessage("PLAYER STRUCK!\n" + eliminatedPlayer + " has been eliminated!");
|
names = llDeleteSubList(names, idx, idx);
|
||||||
|
lives = llDeleteSubList(lives, idx, idx);
|
||||||
|
floaterChannels = llDeleteSubList(floaterChannels, idx, idx);
|
||||||
|
|
||||||
|
// Remove from picksData
|
||||||
|
integer pickIdx = -1;
|
||||||
|
integer p;
|
||||||
|
for (p = 0; p < llGetListLength(picksData); p++) {
|
||||||
|
string entry = llList2String(picksData, p);
|
||||||
|
if (llSubStringIndex(entry, eliminatedPlayer + "|") == 0) {
|
||||||
|
pickIdx = p;
|
||||||
|
jump found_pick_entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIRST: Set the player's lives to 0 and show 0 hearts
|
|
||||||
lives = llListReplaceList(lives, [0], idx, idx);
|
|
||||||
|
|
||||||
// Send ONLY a direct scoreboard update to show 0 hearts
|
|
||||||
llMessageLinked(LINK_SCOREBOARD, MSG_PLAYER_UPDATE,
|
|
||||||
eliminatedPlayer + "|0|" + (string)llList2Key(players, idx), NULL_KEY);
|
|
||||||
|
|
||||||
// RECORD LOSS: Send eliminated player to World Ranking database
|
|
||||||
llMessageLinked(LINK_SET, MSG_LB_RECORD_PLAYER, eliminatedPlayer + "|LOSS", NULL_KEY);
|
|
||||||
|
|
||||||
// CRITICAL: Check for victory condition BEFORE assigning new peril player
|
|
||||||
integer willCauseVictory = (llGetListLength(names) <= 2); // After removing one player
|
|
||||||
string potentialWinner = "";
|
|
||||||
|
|
||||||
if (willCauseVictory) {
|
|
||||||
// Find the winner (the remaining non-eliminated player)
|
|
||||||
integer w;
|
|
||||||
for (w = 0; w < llGetListLength(names); w++) {
|
|
||||||
string candidateName = llList2String(names, w);
|
|
||||||
if (candidateName != eliminatedPlayer) {
|
|
||||||
potentialWinner = candidateName;
|
|
||||||
jump found_winner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@found_winner;
|
|
||||||
|
|
||||||
if (potentialWinner != "") {
|
|
||||||
// NEW: Broadcast sync BEFORE redraw to ensure Floater Manager has fresh heart count
|
|
||||||
syncGameState();
|
|
||||||
|
|
||||||
// SEND WINNER GLOW to scoreboard and trigger floater update
|
|
||||||
llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_WINNER, potentialWinner, NULL_KEY);
|
|
||||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, potentialWinner, NULL_KEY); // Update winner's floater
|
|
||||||
dbg("🏆 [Main Controller] Pre-victory winner glow sent for: " + potentialWinner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only assign new peril player if NOT causing victory
|
|
||||||
if (!willCauseVictory && (perilPlayer == eliminatedPlayer || perilPlayer == "")) {
|
|
||||||
// Find first remaining alive player to be new peril player (before removing eliminated player)
|
|
||||||
string newPerilPlayer = "";
|
|
||||||
integer k;
|
|
||||||
for (k = 0; k < llGetListLength(names) && newPerilPlayer == ""; k++) {
|
|
||||||
string candidateName = llList2String(names, k);
|
|
||||||
if (candidateName != eliminatedPlayer) { // Skip the player being eliminated
|
|
||||||
integer candidateIdx = llListFindList(names, [candidateName]);
|
|
||||||
if (candidateIdx != -1 && candidateIdx < llGetListLength(lives)) {
|
|
||||||
integer candidateLives = llList2Integer(lives, candidateIdx);
|
|
||||||
if (candidateLives > 0) {
|
|
||||||
newPerilPlayer = candidateName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newPerilPlayer != "") {
|
|
||||||
perilPlayer = newPerilPlayer;
|
|
||||||
// Send peril player update to scoreboard for glow effect
|
|
||||||
llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_PERIL_PLAYER, newPerilPlayer, NULL_KEY);
|
|
||||||
} else {
|
|
||||||
perilPlayer = "NONE";
|
|
||||||
// Clear peril player glow on scoreboard
|
|
||||||
llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_PERIL_PLAYER, "", NULL_KEY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UNIFIED SYNC: Replaces inline sync calls to prevent language rift
|
|
||||||
syncGameState();
|
|
||||||
|
|
||||||
// Direct floater update to ensure eliminated status is displayed
|
|
||||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, eliminatedPlayer, llList2Key(players, idx));
|
|
||||||
llSleep(DELAY_ELIMINATION_NOTICE); // Give floater time to show eliminated status with red display
|
|
||||||
|
|
||||||
if (willCauseVictory) {
|
|
||||||
// If this elimination will cause victory, give extra time for 0 hearts display
|
|
||||||
llSleep(DELAY_VICTORY_NOTICE); // Additional time for victory scenarios
|
|
||||||
}
|
|
||||||
|
|
||||||
// CHANGED: Don't cleanup floaters during elimination - let them show eliminated status
|
|
||||||
// The floater will show red glow + "ELIMINATED!" and stay visible until game end
|
|
||||||
// integer ch = llList2Integer(floaterChannels, idx);
|
|
||||||
// llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
|
|
||||||
// llSleep(0.3); // Give floater time to cleanup
|
|
||||||
|
|
||||||
// CRITICAL: Remove eliminated player's picks data by searching for their entry
|
|
||||||
// NOT by index, since picksData format is "PlayerName|picks" strings
|
|
||||||
integer pickIdx = -1;
|
|
||||||
integer p;
|
|
||||||
for (p = 0; p < llGetListLength(picksData); p++) {
|
|
||||||
string entry = llList2String(picksData, p);
|
|
||||||
if (llSubStringIndex(entry, eliminatedPlayer + "|") == 0) {
|
|
||||||
pickIdx = p;
|
|
||||||
jump found_pick_entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@found_pick_entry;
|
|
||||||
if (pickIdx != -1) {
|
|
||||||
picksData = llDeleteSubList(picksData, pickIdx, pickIdx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now remove from indexed lists (players, names, lives, floaterChannels)
|
|
||||||
players = llDeleteSubList(players, idx, idx);
|
|
||||||
names = llDeleteSubList(names, idx, idx);
|
|
||||||
lives = llDeleteSubList(lives, idx, idx);
|
|
||||||
floaterChannels = llDeleteSubList(floaterChannels, idx, idx);
|
|
||||||
|
|
||||||
// NEW: FINAL SYNC - Tell everyone the list has actually SHRUNK now
|
|
||||||
// This prevents "Zombie Respawns" and "Ghost Heals"
|
|
||||||
syncGameState();
|
|
||||||
|
|
||||||
// Peril player assignment already handled before sync message was sent
|
|
||||||
|
|
||||||
// CHANGED: Send to scoreboard, which will handle leaderboard updates
|
|
||||||
llMessageLinked(LINK_SCOREBOARD, MSG_GAME_LOST, eliminatedPlayer, NULL_KEY);
|
|
||||||
|
|
||||||
// Status message now sent directly by Roll Module
|
|
||||||
|
|
||||||
// Check for victory condition BEFORE calling updateHelpers()
|
|
||||||
if (llGetListLength(names) <= 1) {
|
|
||||||
if (llGetListLength(names) == 1) {
|
|
||||||
string winner = llList2String(names, 0);
|
|
||||||
|
|
||||||
// ENHANCED: Give eliminated player's floater time to show eliminated status
|
|
||||||
// before declaring victory and cleaning up
|
|
||||||
dbg("⏳ [Main Controller] Final elimination detected - allowing display time before victory");
|
|
||||||
llSleep(DELAY_ELIMINATION_NOTICE); // Give final eliminated player time to show red eliminated status
|
|
||||||
|
|
||||||
// CRITICAL: Set victory flag immediately to prevent sync processing
|
|
||||||
victoryInProgress = TRUE;
|
|
||||||
|
|
||||||
// CRITICAL: Clear all game state immediately before any sync messages
|
|
||||||
// This prevents broadcasting stale winner data to other scripts
|
|
||||||
players = [];
|
|
||||||
names = [];
|
|
||||||
lives = [];
|
|
||||||
picksData = [];
|
|
||||||
perilPlayer = "";
|
|
||||||
pickQueue = [];
|
|
||||||
currentPickerIdx = 0;
|
|
||||||
roundStarted = FALSE;
|
|
||||||
gameStarting = FALSE;
|
|
||||||
currentPicker = NULL_KEY;
|
|
||||||
|
|
||||||
llSay(0, "✨ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!");
|
|
||||||
|
|
||||||
// NEW: Send victory status to board and display winner name!
|
|
||||||
sendStatusMessage("Victory");
|
|
||||||
llMessageLinked(LINK_SET, MSG_STATUS_TEXT, "=== ULTIMATE VICTORY ===\n<!c=white>" + winner + " wins!", NULL_KEY);
|
|
||||||
|
|
||||||
llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY);
|
|
||||||
llMessageLinked(LINK_SET, MSG_EFFECT_CONFETTI, "VICTORY_CONFETTI", NULL_KEY);
|
|
||||||
|
|
||||||
// IMPORTANT: Send winner update to scoreboard and trigger floater update
|
|
||||||
llMessageLinked(LINK_SCOREBOARD, MSG_UPDATE_WINNER, winner, NULL_KEY);
|
|
||||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, winner, NULL_KEY); // Update winner's floater
|
|
||||||
|
|
||||||
// RECORD WIN: Send ultimate survivor to World Ranking database
|
|
||||||
llMessageLinked(LINK_SET, MSG_LB_RECORD_PLAYER, winner + "|WIN", NULL_KEY);
|
|
||||||
|
|
||||||
// CHANGED: Send to scoreboard, which will handle visual updates (no more duplicate records)
|
|
||||||
llMessageLinked(LINK_SCOREBOARD, MSG_GAME_WON, winner, NULL_KEY);
|
|
||||||
|
|
||||||
// Use timer instead of llSleep to avoid immediate reset
|
|
||||||
dbg("🏆 [Main Controller] Winner glow applied - starting victory delay timer");
|
|
||||||
victoryDelayTimer = llGetUnixTime();
|
|
||||||
currentTimerMode = TIMER_VICTORY_DELAY;
|
|
||||||
llSetTimerEvent(1.0); // Check every second
|
|
||||||
return; // Exit here - timer will handle reset
|
|
||||||
} else {
|
|
||||||
// CRITICAL: Set victory flag and clear state for no survivors scenario too
|
|
||||||
victoryInProgress = TRUE;
|
|
||||||
players = [];
|
|
||||||
names = [];
|
|
||||||
lives = [];
|
|
||||||
picksData = [];
|
|
||||||
perilPlayer = "";
|
|
||||||
pickQueue = [];
|
|
||||||
currentPickerIdx = 0;
|
|
||||||
roundStarted = FALSE;
|
|
||||||
gameStarting = FALSE;
|
|
||||||
currentPicker = NULL_KEY;
|
|
||||||
|
|
||||||
llSay(0, "💀 DESPAIR WINS! No Ultimate Survivors remain!");
|
|
||||||
|
|
||||||
// Use timer for no survivors scenario too
|
|
||||||
victoryDelayTimer = llGetUnixTime();
|
|
||||||
currentTimerMode = TIMER_VICTORY_DELAY;
|
|
||||||
llSetTimerEvent(1.0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CRITICAL: After elimination, sync state and continue game with new peril player
|
|
||||||
if (perilPlayer != "" && perilPlayer != "NONE") {
|
|
||||||
// Sync the updated game state to all modules (4-part format)
|
|
||||||
// Optimized to avoid large temporary string variables
|
|
||||||
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE,
|
|
||||||
llList2CSV(lives) + "~EMPTY~" + perilPlayer + "~" + llList2CSV(names), NULL_KEY);
|
|
||||||
llSleep(DELAY_SYNC_PROPAGATION); // Give sync time to propagate
|
|
||||||
|
|
||||||
// Continue the game with the new peril player - delegate to Game Manager
|
|
||||||
llMessageLinked(LINK_SET, 998, perilPlayer, NULL_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// MINIMAL: Only send direct scoreboard update for remaining players
|
|
||||||
// Skip heavy updateHelpers() calls to prevent memory crashes
|
|
||||||
}
|
}
|
||||||
|
@found_pick_entry;
|
||||||
|
if (pickIdx != -1) {
|
||||||
|
picksData = llDeleteSubList(picksData, pickIdx, pickIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncGameState();
|
||||||
|
dbg("🎯 [Main Controller] Deep Cleanup Complete: " + eliminatedPlayer + " purged from state.");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -929,37 +570,35 @@ default {
|
||||||
|
|
||||||
// Handle ready state queries
|
// Handle ready state queries
|
||||||
if (num == MSG_QUERY_READY_STATE) {
|
if (num == MSG_QUERY_READY_STATE) {
|
||||||
list queryParts = llParseString2List(str, ["|"], []);
|
list qParts = llParseString2List(str, ["|"], []);
|
||||||
string playerName;
|
string qPlayerName;
|
||||||
integer requestID = 0;
|
integer qRequestID = 0;
|
||||||
|
|
||||||
if (llGetListLength(queryParts) >= 2) {
|
if (llGetListLength(qParts) >= 2) {
|
||||||
playerName = llList2String(queryParts, 0);
|
qPlayerName = llList2String(qParts, 0);
|
||||||
requestID = (integer)llList2String(queryParts, 1);
|
qRequestID = (integer)llList2String(qParts, 1);
|
||||||
} else {
|
} else {
|
||||||
playerName = str;
|
qPlayerName = str;
|
||||||
}
|
}
|
||||||
|
|
||||||
integer isReady = llListFindList(readyPlayers, [playerName]) != -1;
|
integer isReady = llListFindList(readyPlayers, [qPlayerName]) != -1;
|
||||||
integer isBot = llSubStringIndex(playerName, "Bot") == 0;
|
integer isBot = llSubStringIndex(qPlayerName, "Bot") == 0;
|
||||||
string result = playerName + "|" + (string)isReady + "|" + (string)isBot + "|" + (string)requestID;
|
string resultStr = qPlayerName + "|" + (string)isReady + "|" + (string)isBot + "|" + (string)qRequestID;
|
||||||
llMessageLinked(LINK_SET, MSG_READY_STATE_RESULT, result, id);
|
llMessageLinked(LINK_SET, MSG_READY_STATE_RESULT, resultStr, id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle owner status queries
|
// Handle owner status queries
|
||||||
if (num == MSG_QUERY_OWNER_STATUS) {
|
if (num == MSG_QUERY_OWNER_STATUS) {
|
||||||
list queryParts = llParseString2List(str, ["|"], []);
|
list osParts = llParseString2List(str, ["|"], []);
|
||||||
string ownerName;
|
string osName;
|
||||||
integer requestID = 0;
|
integer osRequestID = 0;
|
||||||
|
|
||||||
// Owner status check (Now fully automated via on_rez / reset)
|
if (llGetListLength(osParts) >= 2) {
|
||||||
|
osName = llList2String(osParts, 0);
|
||||||
if (llGetListLength(queryParts) >= 2) {
|
osRequestID = (integer)llList2String(osParts, 1);
|
||||||
ownerName = llList2String(queryParts, 0);
|
|
||||||
requestID = (integer)llList2String(queryParts, 1);
|
|
||||||
} else {
|
} else {
|
||||||
ownerName = str;
|
osName = str;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if owner is registered
|
// Check if owner is registered
|
||||||
|
|
@ -983,7 +622,7 @@ default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string result = ownerName + "|" + (string)isRegistered + "|" + (string)isPending + "|" + (string)isStarter + "|" + (string)requestID;
|
string result = osName + "|" + (string)isRegistered + "|" + (string)isPending + "|" + (string)isStarter + "|" + (string)osRequestID;
|
||||||
llMessageLinked(LINK_SET, MSG_OWNER_STATUS_RESULT, result, id);
|
llMessageLinked(LINK_SET, MSG_OWNER_STATUS_RESULT, result, id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1313,8 +952,8 @@ default {
|
||||||
|
|
||||||
llSay(0, "⚡ ALL PARTICIPANTS READY! THE DEADLY PERIL DICE GAME BEGINS! ⚡");
|
llSay(0, "⚡ ALL PARTICIPANTS READY! THE DEADLY PERIL DICE GAME BEGINS! ⚡");
|
||||||
|
|
||||||
integer actualPlayerCount = llGetListLength(names);
|
integer currentLiveCount = llGetListLength(names);
|
||||||
llSetText("🎮 GAME IN PROGRESS\n" + (string)actualPlayerCount + " players", <1.0, 0.2, 0.2>, 1.0);
|
llSetText("🎮 GAME IN PROGRESS\n" + (string)currentLiveCount + " players", <1.0, 0.2, 0.2>, 1.0);
|
||||||
|
|
||||||
// FIXED: Use proper announcement instead of internal texture name "Title"
|
// FIXED: Use proper announcement instead of internal texture name "Title"
|
||||||
sendStatusMessage("GAME STARTING!\nAll players are ready!");
|
sendStatusMessage("GAME STARTING!\nAll players are ready!");
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// === Dialog Handler (Updated for Peril Dice - Paginated Multiple Picks) ===
|
// === Dialog Handler (Updated for Peril Dice - Paginated Multiple Picks) ===
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ key EXPERIENCE_ID = "06926390-26e6-11f1-a452-0242ac110003"; // Final Girlz I.N.C
|
||||||
#define FW_DONE "fw_done"
|
#define FW_DONE "fw_done"
|
||||||
|
|
||||||
// --- System/Debug (9000-9999) ---
|
// --- System/Debug (9000-9999) ---
|
||||||
#define DEBUG_LOGS 0 // GLOBAL MASTER SWITCH - Set to 1 for dev feedback, 0 to STRIP from memory
|
#define DEBUG_LOGS 1 // GLOBAL MASTER SWITCH - Set to 1 for dev feedback, 0 to STRIP from memory
|
||||||
#if DEBUG_LOGS
|
#if DEBUG_LOGS
|
||||||
#define dbg(msg) llOwnerSay(msg)
|
#define dbg(msg) llOwnerSay(msg)
|
||||||
#else
|
#else
|
||||||
|
|
@ -108,6 +108,9 @@ key EXPERIENCE_ID = "06926390-26e6-11f1-a452-0242ac110003"; // Final Girlz I.N.C
|
||||||
#define MSG_REQUEST_PLAYER_LIST_KICK 8009
|
#define MSG_REQUEST_PLAYER_LIST_KICK 8009
|
||||||
#define MSG_LOCK_GAME 9001
|
#define MSG_LOCK_GAME 9001
|
||||||
#define MSG_UNLOCK_GAME 9002
|
#define MSG_UNLOCK_GAME 9002
|
||||||
|
#define MSG_TOUCH_EVENT 215
|
||||||
|
#define MSG_PROCESS_ELIMINATION 3015
|
||||||
|
|
||||||
|
|
||||||
// --- Dialogs & Menus (100-399) ---
|
// --- Dialogs & Menus (100-399) ---
|
||||||
#define MSG_SHOW_MENU 201
|
#define MSG_SHOW_MENU 201
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// === Player Status Float - Enhanced Display ===
|
// === Player Status Float - Enhanced Display ===
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// === Dialog Handler (Owner & Player) with unified Ready/Leave menu and join support ===
|
// === Dialog Handler (Owner & Player) with unified Ready/Leave menu and join support ===
|
||||||
|
|
||||||
|
|
@ -88,6 +88,11 @@ list troubleshootingOptions = ["Cleanup Floaters", "Force Floaters", "⬅️ Bac
|
||||||
|
|
||||||
// State tracking for dynamic ready menu with race condition protection
|
// State tracking for dynamic ready menu with race condition protection
|
||||||
key pendingMenuPlayer = NULL_KEY;
|
key pendingMenuPlayer = NULL_KEY;
|
||||||
|
integer roundStarted = FALSE;
|
||||||
|
list players = [];
|
||||||
|
list names = [];
|
||||||
|
list readyPlayers = [];
|
||||||
|
list pendingRegistrations = []; // Local track of registration attempts
|
||||||
integer pendingMenuIsStarter = FALSE;
|
integer pendingMenuIsStarter = FALSE;
|
||||||
integer pendingMenuIsOwner = FALSE;
|
integer pendingMenuIsOwner = FALSE;
|
||||||
integer pendingMenuRequestID = 0; // Unique ID for each menu request
|
integer pendingMenuRequestID = 0; // Unique ID for each menu request
|
||||||
|
|
@ -404,6 +409,63 @@ default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- TOP LEVEL EVENT HANDLERS ---
|
||||||
|
if (num == MSG_TOUCH_EVENT) {
|
||||||
|
list parts = llParseString2List(str, ["|"], []);
|
||||||
|
integer clickedLink = (integer)llList2String(parts, 0);
|
||||||
|
string clickedName = llList2String(parts, 1);
|
||||||
|
key toucher = id;
|
||||||
|
if (clickedLink != 1 && (llSubStringIndex(clickedName, "FURWARE") != -1 || llSubStringIndex(clickedName, "LB_") != -1)) return;
|
||||||
|
integer idx = llListFindList(players, [toucher]);
|
||||||
|
if (roundStarted) {
|
||||||
|
if (toucher == llGetOwner()) { llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|0", toucher); return; }
|
||||||
|
if (idx != -1) { llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|0", toucher); return; }
|
||||||
|
}
|
||||||
|
if (toucher == llGetOwner()) {
|
||||||
|
if (idx == -1) {
|
||||||
|
if (llListFindList(pendingRegistrations, [toucher]) != -1) return;
|
||||||
|
if (llGetListLength(players) >= 2) llMessageLinked(LINK_SET, MSG_SHOW_MENU, "unregistered_owner_starter|1", toucher);
|
||||||
|
else llMessageLinked(LINK_SET, MSG_SHOW_MENU, "unregistered_owner|0", toucher);
|
||||||
|
} else {
|
||||||
|
integer isStarter = (idx == 0);
|
||||||
|
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, toucher);
|
||||||
|
}
|
||||||
|
} else if (idx != -1) {
|
||||||
|
integer isStarter = (idx == 0);
|
||||||
|
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher);
|
||||||
|
} else {
|
||||||
|
string playerName = llGetDisplayName(toucher);
|
||||||
|
if (playerName == "") playerName = llKey2Name(toucher);
|
||||||
|
if (llListFindList(pendingRegistrations, [toucher]) == -1) {
|
||||||
|
pendingRegistrations += [toucher];
|
||||||
|
if (llGetListLength(pendingRegistrations) > 10) pendingRegistrations = llDeleteSubList(pendingRegistrations, 0, 0);
|
||||||
|
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER_REQUEST, playerName + "|" + (string)toucher, NULL_KEY);
|
||||||
|
}
|
||||||
|
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|0", toucher);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (num == MSG_SYNC_GAME_STATE) {
|
||||||
|
list parts = llParseStringKeepNulls(str, ["~"], []);
|
||||||
|
if (llGetListLength(parts) >= 4) names = llCSV2List(llList2String(parts, 3));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num == MSG_UPDATE_MAIN_LISTS) {
|
||||||
|
if (llSubStringIndex(str, "READY_LIST|") == 0) readyPlayers = llCSV2List(llGetSubString(str, 11, -1));
|
||||||
|
else {
|
||||||
|
list parts = llParseString2List(str, ["~"], []);
|
||||||
|
if (llGetListLength(parts) >= 2 && llListFindList(players, [id]) == -1) players += [id];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num == MSG_RESET_ALL) {
|
||||||
|
players = []; names = []; readyPlayers = []; pendingRegistrations = [];
|
||||||
|
roundStarted = FALSE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (num == MSG_SHOW_MENU) {
|
if (num == MSG_SHOW_MENU) {
|
||||||
dbg("🕵️ [Dialog Handler] Received MSG_SHOW_MENU (" + str + ") from script link/sender: " + (string)sender + ", ID: " + (string)id);
|
dbg("🕵️ [Dialog Handler] Received MSG_SHOW_MENU (" + str + ") from script link/sender: " + (string)sender + ", ID: " + (string)id);
|
||||||
list args = llParseString2List(str, ["|"], []);
|
list args = llParseString2List(str, ["|"], []);
|
||||||
|
|
@ -414,44 +476,33 @@ default {
|
||||||
string targetType = llList2String(args, 0);
|
string targetType = llList2String(args, 0);
|
||||||
integer isStarter = (integer)llList2String(args, 1);
|
integer isStarter = (integer)llList2String(args, 1);
|
||||||
if (targetType == "owner") {
|
if (targetType == "owner") {
|
||||||
// Both starter and non-starter owners get ready/leave menu with Owner button
|
|
||||||
showReadyLeaveMenu(id, isStarter, TRUE);
|
showReadyLeaveMenu(id, isStarter, TRUE);
|
||||||
} else if (targetType == "registered_owner") {
|
} else if (targetType == "registered_owner") {
|
||||||
// Registered owner gets ready/leave menu with Owner button
|
|
||||||
showReadyLeaveMenu(id, isStarter, TRUE);
|
showReadyLeaveMenu(id, isStarter, TRUE);
|
||||||
} else if (targetType == "player") {
|
} else if (targetType == "player") {
|
||||||
showReadyLeaveMenu(id, isStarter, FALSE);
|
showReadyLeaveMenu(id, isStarter, FALSE);
|
||||||
} else if (targetType == "admin") {
|
} else if (targetType == "admin") {
|
||||||
// Show admin category menu directly
|
|
||||||
showOwnerMenu(id);
|
showOwnerMenu(id);
|
||||||
} else if (targetType == "unregistered_owner") {
|
} else if (targetType == "unregistered_owner") {
|
||||||
// Unregistered owner - basic menu (join, owner menu)
|
|
||||||
showUnregisteredOwnerMenu(id, FALSE);
|
showUnregisteredOwnerMenu(id, FALSE);
|
||||||
} else if (targetType == "unregistered_owner_starter") {
|
} else if (targetType == "unregistered_owner_starter") {
|
||||||
// Unregistered owner with enough players to start game
|
|
||||||
showUnregisteredOwnerMenu(id, TRUE);
|
showUnregisteredOwnerMenu(id, TRUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (num == MSG_READY_STATE_RESULT) {
|
else if (num == MSG_READY_STATE_RESULT) {
|
||||||
// Parse ready state response and show appropriate menu
|
|
||||||
list parts = llParseString2List(str, ["|"], []);
|
list parts = llParseString2List(str, ["|"], []);
|
||||||
if (llGetListLength(parts) >= 4) {
|
if (llGetListLength(parts) >= 4) {
|
||||||
string playerName = llList2String(parts, 0);
|
string playerName = llList2String(parts, 0);
|
||||||
integer isReady = (integer)llList2String(parts, 1);
|
integer isReady = (integer)llList2String(parts, 1);
|
||||||
integer isBot = (integer)llList2String(parts, 2);
|
integer isBot = (integer)llList2String(parts, 2);
|
||||||
integer responseRequestID = (integer)llList2String(parts, 3);
|
integer responseRequestID = (integer)llList2String(parts, 3);
|
||||||
|
|
||||||
// Validate that this response matches our pending request
|
|
||||||
if (pendingMenuPlayer == id && responseRequestID == pendingMenuRequestID) {
|
if (pendingMenuPlayer == id && responseRequestID == pendingMenuRequestID) {
|
||||||
// Check for timeout
|
|
||||||
if ((llGetTime() - pendingMenuTimestamp) > MENU_REQUEST_TIMEOUT) {
|
if ((llGetTime() - pendingMenuTimestamp) > MENU_REQUEST_TIMEOUT) {
|
||||||
dbg("⏰ Menu request timed out, ignoring response");
|
dbg("⏰ Menu request timed out, ignoring response");
|
||||||
pendingMenuPlayer = NULL_KEY;
|
pendingMenuPlayer = NULL_KEY;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
showReadyLeaveMenuWithState(pendingMenuPlayer, pendingMenuIsStarter, pendingMenuIsOwner, isReady, isBot);
|
showReadyLeaveMenuWithState(pendingMenuPlayer, pendingMenuIsStarter, pendingMenuIsOwner, isReady, isBot);
|
||||||
// Reset pending state ONLY after successfully showing the dialog
|
|
||||||
pendingMenuPlayer = NULL_KEY;
|
pendingMenuPlayer = NULL_KEY;
|
||||||
pendingMenuRequestID = 0;
|
pendingMenuRequestID = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -465,29 +516,16 @@ default {
|
||||||
dbg("📋 Fetching list of players for pick management...");
|
dbg("📋 Fetching list of players for pick management...");
|
||||||
showPickManageMenu(id, playerNames);
|
showPickManageMenu(id, playerNames);
|
||||||
}
|
}
|
||||||
// Handle player list result for kick functionality
|
|
||||||
else if (num == MSG_REQUEST_PLAYER_LIST_KICK) {
|
else if (num == MSG_REQUEST_PLAYER_LIST_KICK) {
|
||||||
dbg("🔍 [Dialog Handler] Received kick player list string: '" + str + "'");
|
|
||||||
list rawNames = llParseString2List(str, [","], []);
|
list rawNames = llParseString2List(str, [","], []);
|
||||||
dbg("🔍 [Dialog Handler] Raw parsed into " + (string)llGetListLength(rawNames) + " names: " + llList2CSV(rawNames));
|
|
||||||
|
|
||||||
// Filter out any command strings that might have gotten mixed in
|
|
||||||
list playerNames = [];
|
list playerNames = [];
|
||||||
integer i;
|
integer i;
|
||||||
for (i = 0; i < llGetListLength(rawNames); i++) {
|
for (i = 0; i < llGetListLength(rawNames); i++) {
|
||||||
string name = llStringTrim(llList2String(rawNames, i), STRING_TRIM);
|
string name = llStringTrim(llList2String(rawNames, i), STRING_TRIM);
|
||||||
// Skip empty names and command strings
|
if (name != "" && llSubStringIndex(name, "REQUEST_") != 0 && llSubStringIndex(name, "COMMAND_") != 0 && llSubStringIndex(name, "MSG_") != 0) {
|
||||||
if (name != "" &&
|
|
||||||
llSubStringIndex(name, "REQUEST_") != 0 &&
|
|
||||||
llSubStringIndex(name, "COMMAND_") != 0 &&
|
|
||||||
llSubStringIndex(name, "MSG_") != 0) {
|
|
||||||
playerNames += [name];
|
playerNames += [name];
|
||||||
} else {
|
|
||||||
dbg("⚠️ [Dialog Handler] Filtered out invalid name: '" + name + "'");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg("🔍 [Dialog Handler] Filtered to " + (string)llGetListLength(playerNames) + " valid names: " + llList2CSV(playerNames));
|
|
||||||
showKickPlayerMenu(id, playerNames);
|
showKickPlayerMenu(id, playerNames);
|
||||||
}
|
}
|
||||||
else if (num == MSG_PICK_LIST_RESULT) {
|
else if (num == MSG_PICK_LIST_RESULT) {
|
||||||
|
|
@ -495,11 +533,8 @@ default {
|
||||||
string returnedName = llList2String(parts, 0);
|
string returnedName = llList2String(parts, 0);
|
||||||
string picks = llList2String(parts, 1);
|
string picks = llList2String(parts, 1);
|
||||||
if (llStringTrim(returnedName, STRING_TRIM) == llStringTrim(currentPickTarget, STRING_TRIM)) {
|
if (llStringTrim(returnedName, STRING_TRIM) == llStringTrim(currentPickTarget, STRING_TRIM)) {
|
||||||
dbg("✅ Matched pick list for: " + returnedName);
|
|
||||||
currentPickList = llCSV2List(picks);
|
currentPickList = llCSV2List(picks);
|
||||||
llMessageLinked(LINK_THIS, MSG_LIFE_LOOKUP_REQUEST, currentPickTarget, id);
|
llMessageLinked(LINK_THIS, MSG_LIFE_LOOKUP_REQUEST, currentPickTarget, id);
|
||||||
} else {
|
|
||||||
dbg("❌ Name mismatch: got " + returnedName + " but expected " + currentPickTarget);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (num == MSG_LIFE_LOOKUP_REQUEST) {
|
else if (num == MSG_LIFE_LOOKUP_REQUEST) {
|
||||||
|
|
@ -510,7 +545,6 @@ default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (num == MSG_OWNER_STATUS_RESULT) {
|
else if (num == MSG_OWNER_STATUS_RESULT) {
|
||||||
// Parse owner status response and show appropriate menu
|
|
||||||
list parts = llParseString2List(str, ["|"], []);
|
list parts = llParseString2List(str, ["|"], []);
|
||||||
if (llGetListLength(parts) >= 5) {
|
if (llGetListLength(parts) >= 5) {
|
||||||
string ownerName = llList2String(parts, 0);
|
string ownerName = llList2String(parts, 0);
|
||||||
|
|
@ -518,45 +552,23 @@ default {
|
||||||
integer isPending = (integer)llList2String(parts, 2);
|
integer isPending = (integer)llList2String(parts, 2);
|
||||||
integer isStarter = (integer)llList2String(parts, 3);
|
integer isStarter = (integer)llList2String(parts, 3);
|
||||||
integer responseRequestID = (integer)llList2String(parts, 4);
|
integer responseRequestID = (integer)llList2String(parts, 4);
|
||||||
|
|
||||||
// Validate that this response matches our pending request
|
|
||||||
if (pendingMenuPlayer == id && responseRequestID == pendingMenuRequestID) {
|
if (pendingMenuPlayer == id && responseRequestID == pendingMenuRequestID) {
|
||||||
// Check for timeout
|
|
||||||
if ((llGetTime() - pendingMenuTimestamp) > MENU_REQUEST_TIMEOUT) {
|
if ((llGetTime() - pendingMenuTimestamp) > MENU_REQUEST_TIMEOUT) {
|
||||||
dbg("⏰ Owner status query timed out, ignoring response");
|
|
||||||
pendingMenuPlayer = NULL_KEY;
|
pendingMenuPlayer = NULL_KEY;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decide which menu to show based on registration status
|
|
||||||
if (isRegistered) {
|
if (isRegistered) {
|
||||||
// Owner is registered - show ready/leave menu with Owner button
|
|
||||||
dbg("🔍 [Debug] Owner is registered, isStarter=" + (string)isStarter + ", showing ready/leave menu");
|
|
||||||
dbg("🔍 [Debug] About to call showReadyLeaveMenu with id=" + (string)id + ", isStarter=" + (string)isStarter + ", isOwner=TRUE");
|
|
||||||
|
|
||||||
// DON'T reset pending state here - showReadyLeaveMenu needs it!
|
|
||||||
// The ready state handler will reset it when the dialog is shown
|
|
||||||
showReadyLeaveMenu(id, isStarter, TRUE);
|
showReadyLeaveMenu(id, isStarter, TRUE);
|
||||||
dbg("🔍 [Debug] showReadyLeaveMenu call completed");
|
|
||||||
} else if (isPending) {
|
} else if (isPending) {
|
||||||
llRegionSayTo(id, 0, "⏳ Registration already in progress for " + ownerName);
|
llRegionSayTo(id, 0, "⏳ Registration already in progress for " + ownerName);
|
||||||
// Reset pending state for non-registered responses
|
|
||||||
pendingMenuPlayer = NULL_KEY;
|
pendingMenuPlayer = NULL_KEY;
|
||||||
pendingMenuRequestID = 0;
|
pendingMenuRequestID = 0;
|
||||||
} else {
|
} else {
|
||||||
// Owner is not registered - show join/admin choice menu
|
showUnregisteredOwnerMenu(id, (llGetListLength(players) >= 2));
|
||||||
list options = ["Join Game", "Owner Menu"];
|
|
||||||
string menuText = "👑 Owner Options:\n\n🎮 Join Game - Register as a player\n🔧 Owner Menu - Admin functions without joining";
|
|
||||||
llDialog(id, menuText, options, DIALOG_CHANNEL);
|
|
||||||
// Reset pending state for non-registered responses
|
|
||||||
pendingMenuPlayer = NULL_KEY;
|
pendingMenuPlayer = NULL_KEY;
|
||||||
pendingMenuRequestID = 0;
|
pendingMenuRequestID = 0;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
dbg("⚠️ Owner status response mismatch - ignoring");
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
dbg("⚠️ Invalid owner status result format - expected 5 parts, got " + (string)llGetListLength(parts));
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// === Player Registration Manager ===
|
// === Player Registration Manager ===
|
||||||
// Handles all player registration, join/leave operations to reduce Main Controller memory usage
|
// Handles all player registration, join/leave operations to reduce Main Controller memory usage
|
||||||
|
|
@ -17,6 +17,12 @@ integer FLOATER_BASE_CHANNEL;
|
||||||
integer roundStarted = FALSE;
|
integer roundStarted = FALSE;
|
||||||
integer gameStarting = 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 resetInProgress = FALSE; // Lockout for reset syncs
|
||||||
|
|
||||||
integer calculateChannel(integer offset) {
|
integer calculateChannel(integer offset) {
|
||||||
|
|
@ -56,6 +62,31 @@ regionMsg(key player, string msg) {
|
||||||
llMessageLinked(LINK_SET, MSG_REGION_MESSAGE, (string)player + "|" + msg, NULL_KEY);
|
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
|
// Handle player registration with all the complex logic moved here
|
||||||
handlePlayerRegistration(string regData, key requesterId) {
|
handlePlayerRegistration(string regData, key requesterId) {
|
||||||
// MEMORY DEBUG: Check memory at start
|
// MEMORY DEBUG: Check memory at start
|
||||||
|
|
@ -153,12 +184,11 @@ handlePlayerRegistration(string regData, key requesterId) {
|
||||||
llMessageLinked(LINK_SET, MSG_UPDATE_MAIN_LISTS, updateData, newKey);
|
llMessageLinked(LINK_SET, MSG_UPDATE_MAIN_LISTS, updateData, newKey);
|
||||||
|
|
||||||
// Show appropriate menu to the new player
|
// Show appropriate menu to the new player
|
||||||
|
integer isStarter = (humanCount <= 1);
|
||||||
if (newKey == llGetOwner()) {
|
if (newKey == llGetOwner()) {
|
||||||
integer isStarter = (humanCount <= 1);
|
|
||||||
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, newKey);
|
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, newKey);
|
||||||
} else {
|
} else {
|
||||||
// NEW: Also show menu for non-owners immediately after registration
|
// NEW: Also show menu for non-owners immediately after registration
|
||||||
integer isStarter = (humanCount <= 1);
|
|
||||||
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, newKey);
|
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, newKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,6 +199,7 @@ default {
|
||||||
state_entry() {
|
state_entry() {
|
||||||
DISCOVER_CORE_LINKS();
|
DISCOVER_CORE_LINKS();
|
||||||
initializeChannels();
|
initializeChannels();
|
||||||
|
checkExperience();
|
||||||
REPORT_MEMORY();
|
REPORT_MEMORY();
|
||||||
dbg("🎯 [RegMgr] Player Registration Manager ready - discovery complete! Scoreboard: " + (string)LINK_SCOREBOARD);
|
dbg("🎯 [RegMgr] Player Registration Manager ready - discovery complete! Scoreboard: " + (string)LINK_SCOREBOARD);
|
||||||
}
|
}
|
||||||
|
|
@ -176,6 +207,7 @@ default {
|
||||||
on_rez(integer start_param) {
|
on_rez(integer start_param) {
|
||||||
DISCOVER_CORE_LINKS();
|
DISCOVER_CORE_LINKS();
|
||||||
initializeChannels();
|
initializeChannels();
|
||||||
|
checkExperience();
|
||||||
REPORT_MEMORY();
|
REPORT_MEMORY();
|
||||||
dbg("🎯 [RegMgr] Player Registration Manager rezzed - resetting state...");
|
dbg("🎯 [RegMgr] Player Registration Manager rezzed - resetting state...");
|
||||||
// Reset local state
|
// Reset local state
|
||||||
|
|
@ -338,8 +370,20 @@ default {
|
||||||
}
|
}
|
||||||
|
|
||||||
timer() {
|
timer() {
|
||||||
|
if (currentTimerMode == TIMER_XP_CHECK) {
|
||||||
|
// Diagnostic timer complete
|
||||||
|
llSetTimerEvent(0);
|
||||||
|
currentTimerMode = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
resetInProgress = FALSE;
|
resetInProgress = FALSE;
|
||||||
llSetTimerEvent(0);
|
llSetTimerEvent(0);
|
||||||
dbg("🎯 [RegMgr] Reset lockout complete - sync active.");
|
dbg("🎯 [RegMgr] Reset lockout complete - sync active.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dataserver(key queryid, string data) {
|
||||||
|
if (queryid == sentinelQueryID) {
|
||||||
|
dbg("📡 [RegMgr] Experience KVP Heartbeat: " + data);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// === Roll and Confetti Module (with Roll Dialog Handler) ===
|
// === Roll and Confetti Module (with Roll Dialog Handler) ===
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "Peril_Constants.lsl"
|
#include "peril/Peril_Constants.lsl"
|
||||||
|
|
||||||
// === System Debugger - Game State Validation ===
|
// === System Debugger - Game State Validation ===
|
||||||
// Validates game state consistency and reports issues
|
// Validates game state consistency and reports issues
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue