new bot manager
This commit is contained in:
parent
7e849d57c7
commit
d96b92a989
2 changed files with 381 additions and 129 deletions
53
Bot Manager
Normal file
53
Bot Manager
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Bot Manager for Peril Dice
|
||||||
|
// Handles logic for test players (bots): picking numbers and rolling dice
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
integer LISTEN_CHANNEL = -9999; // Change if needed to match main controller
|
||||||
|
|
||||||
|
// Helper to parse and respond to pick commands
|
||||||
|
doBotPick(string botName, integer count, integer diceMax) {
|
||||||
|
list picks;
|
||||||
|
while (llGetListLength(picks) < count) {
|
||||||
|
integer roll = 1 + (integer)(llFrand((float)diceMax));
|
||||||
|
if (llListFindList(picks, [roll]) == -1) {
|
||||||
|
picks += roll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string pickString = llDumpList2String(picks, ",");
|
||||||
|
llRegionSay(LISTEN_CHANNEL, "BOT_PICKED:" + botName + ":" + pickString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to respond to peril roll commands
|
||||||
|
doBotRoll(string botName, integer diceMax) {
|
||||||
|
integer roll = 1 + (integer)(llFrand((float)diceMax));
|
||||||
|
llRegionSay(LISTEN_CHANNEL, "BOT_ROLL:" + botName + ":" + (string)roll);
|
||||||
|
}
|
||||||
|
|
||||||
|
default {
|
||||||
|
state_entry() {
|
||||||
|
llListen(LISTEN_CHANNEL, "", NULL_KEY, "");
|
||||||
|
llOwnerSay("[Bot Manager] Ready and listening on channel " + (string)LISTEN_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(integer channel, string name, key id, string message) {
|
||||||
|
if (llSubStringIndex(message, "BOT_PICK:") == 0) {
|
||||||
|
// Format: BOT_PICK:bot_1:3:20
|
||||||
|
list parts = llParseStringKeepNulls(message, [":"], []);
|
||||||
|
if (llGetListLength(parts) == 4) {
|
||||||
|
string botName = llList2String(parts, 1);
|
||||||
|
integer count = (integer)llList2String(parts, 2);
|
||||||
|
integer diceMax = (integer)llList2String(parts, 3);
|
||||||
|
doBotPick(botName, count, diceMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (llSubStringIndex(message, "BOT_PERIL_ROLL:") == 0) {
|
||||||
|
// Format: BOT_PERIL_ROLL:bot_1:20
|
||||||
|
list parts = llParseStringKeepNulls(message, [":"], []);
|
||||||
|
if (llGetListLength(parts) == 3) {
|
||||||
|
string botName = llList2String(parts, 1);
|
||||||
|
integer diceMax = (integer)llList2String(parts, 2);
|
||||||
|
doBotRoll(botName, diceMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
439
Helper.lsl
439
Helper.lsl
|
|
@ -1,159 +1,358 @@
|
||||||
// === Game Helper Functions Module (Max 10 players) ===
|
// === Main Peril Dice Controller (Refactored with Game Helpers Integration, with dynamic player join) ===
|
||||||
// This version chooses a dice size based on the number of players,
|
|
||||||
// ensuring at least three numbers are available per player.
|
//
|
||||||
|
// This version of the main game controller includes support for players (including
|
||||||
|
// the owner) joining the game at runtime via a MSG_REGISTER_PLAYER message. When
|
||||||
|
// a new player registers, they are added to the internal lists (players, names,
|
||||||
|
// lives and picksData), a floating display is rezzed for them, and helpers are
|
||||||
|
// updated. This mirrors the behaviour used when adding a test player, but now
|
||||||
|
// applies to any avatar joining the game via the dialog handler.
|
||||||
|
|
||||||
|
integer syncChannel = -77777;
|
||||||
|
integer numberPickChannel = -77888;
|
||||||
|
integer rollDialogChannel = -77999;
|
||||||
|
integer DIALOG_CHANNEL = -88888;
|
||||||
|
|
||||||
|
list players = [];
|
||||||
|
list names = [];
|
||||||
|
list lives = [];
|
||||||
|
string perilPlayer = "";
|
||||||
|
list globalPickedNumbers = [];
|
||||||
|
list picksData = [];
|
||||||
|
list readyPlayers = [];
|
||||||
|
|
||||||
|
list pickQueue = [];
|
||||||
|
integer currentPickerIdx = 0;
|
||||||
|
integer diceType = 6;
|
||||||
|
|
||||||
|
integer MSG_SHOW_DIALOG = 101;
|
||||||
|
integer MSG_ROLL_RESULT = 102;
|
||||||
|
integer MSG_UPDATE_FLOAT = 103;
|
||||||
|
integer MSG_CLEANUP_FLOAT = 104;
|
||||||
|
integer MSG_REZ_FLOAT = 105;
|
||||||
|
integer MSG_REGISTER_PLAYER = 106;
|
||||||
|
integer MSG_SYNC_GAME_STATE = 107;
|
||||||
|
integer MSG_SHOW_MENU = 201;
|
||||||
|
integer MSG_SHOW_ROLL_DIALOG = 301;
|
||||||
|
|
||||||
integer MSG_GET_DICE_TYPE = 1001;
|
integer MSG_GET_DICE_TYPE = 1001;
|
||||||
integer MSG_DICE_TYPE_RESULT = 1005;
|
|
||||||
integer MSG_GET_PICKS_REQUIRED = 1002;
|
integer MSG_GET_PICKS_REQUIRED = 1002;
|
||||||
integer MSG_GET_PICKER_INDEX = 1003;
|
integer MSG_GET_PICKER_INDEX = 1003;
|
||||||
|
integer MSG_DICE_TYPE_RESULT = 1005;
|
||||||
integer MSG_SERIALIZE_GAME_STATE = 1004;
|
integer MSG_SERIALIZE_GAME_STATE = 1004;
|
||||||
integer MSG_SYNC_GAME_STATE = 107;
|
|
||||||
integer MSG_SYNC_PICKQUEUE = 2001;
|
integer MSG_SYNC_PICKQUEUE = 2001;
|
||||||
|
|
||||||
list lives;
|
// Maximum number of players allowed (including test players). This should
|
||||||
list picksData;
|
// mirror the value used in Floater Manager to avoid inconsistencies.
|
||||||
string perilPlayer;
|
integer MAX_PLAYERS = 10;
|
||||||
list names;
|
|
||||||
list pickQueue;
|
|
||||||
|
|
||||||
// List of supported dice sizes; ensure the last entry can accommodate 10 players × 3 picks = 30
|
integer TIMEOUT_SECONDS = 600;
|
||||||
list STANDARD_DICE = [6, 12, 20, 30];
|
integer timeoutTimer;
|
||||||
|
|
||||||
// Determine which die to use based on player count.
|
integer warning2min = 120;
|
||||||
// Each player should have at least 3 numbers available to choose from.
|
integer warning5min = 300;
|
||||||
integer getDiceType(integer playerCount) {
|
integer warning9min = 540;
|
||||||
integer minimumSides = playerCount * 3;
|
integer lastWarning = 0;
|
||||||
integer i;
|
|
||||||
for (i = 0; i < llGetListLength(STANDARD_DICE); i++) {
|
key currentPicker;
|
||||||
integer sides = llList2Integer(STANDARD_DICE, i);
|
|
||||||
if (sides >= minimumSides) {
|
// Forward game state to helpers when it changes
|
||||||
return sides;
|
updateHelpers() {
|
||||||
}
|
string serialized = llList2CSV(lives) + "~" + llList2CSV(picksData) +
|
||||||
}
|
"~" + perilPlayer + "~" + llList2CSV(names);
|
||||||
// If no predefined die has enough sides, return the largest supported die (d30)
|
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, serialized, NULL_KEY);
|
||||||
return llList2Integer(STANDARD_DICE, llGetListLength(STANDARD_DICE) - 1);
|
llMessageLinked(LINK_SET, MSG_SYNC_PICKQUEUE, llList2CSV(pickQueue), NULL_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine how many picks a player should make based on the peril player's lives
|
// Request helper-calculated values
|
||||||
integer getPicksRequiredFromName(string name) {
|
requestDiceType() {
|
||||||
integer idx = llListFindList(names, [name]);
|
llMessageLinked(LINK_SET, MSG_GET_DICE_TYPE, (string)llGetListLength(names), NULL_KEY);
|
||||||
if (idx == -1) {
|
|
||||||
integer i;
|
|
||||||
for (i = 0; i < llGetListLength(names); i++) {
|
|
||||||
string testName = llList2String(names, i);
|
|
||||||
if (llSubStringIndex(testName, name) != -1) {
|
|
||||||
idx = i;
|
|
||||||
i = llGetListLength(names);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (idx == -1) return 0;
|
|
||||||
integer lifeCount = (integer)llList2String(lives, idx);
|
|
||||||
if (lifeCount >= 3) return 1;
|
|
||||||
if (lifeCount == 2) return 2;
|
|
||||||
return 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the index of a player in the pickQueue by name
|
requestPicksRequired(integer idx) {
|
||||||
integer getPickerIndex(string name) {
|
llMessageLinked(LINK_SET, MSG_GET_PICKS_REQUIRED, llList2String(names, idx), NULL_KEY);
|
||||||
return llListFindList(pickQueue, [name]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize the current game state for syncing with other scripts
|
requestPickerIndex(string name) {
|
||||||
string serializeGameState() {
|
llMessageLinked(LINK_SET, MSG_GET_PICKER_INDEX, name, NULL_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
string generateSerializedState() {
|
||||||
return llList2CSV(lives) + "~" + llList2CSV(picksData) + "~" + perilPlayer + "~" + llList2CSV(names);
|
return llList2CSV(lives) + "~" + llList2CSV(picksData) + "~" + perilPlayer + "~" + llList2CSV(names);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show a pick management dialog for the owner.
|
resetGame() {
|
||||||
// LSL does not support a void return type, so this returns an integer which is ignored.
|
// Remove floats in reverse order so indices remain correct as entries are
|
||||||
integer showPickManager(string player, key id) {
|
// deleted from the Floater Manager's lists. Otherwise, deleting index 0
|
||||||
string searchName = player;
|
// first shifts subsequent indices and some floats are left behind.
|
||||||
if (llSubStringIndex(searchName, "~") != -1) {
|
|
||||||
list parts = llParseString2List(searchName, ["~"], []);
|
|
||||||
searchName = llList2String(parts, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
integer idx = llListFindList(names, [searchName]);
|
|
||||||
if (idx == -1) {
|
|
||||||
integer i;
|
integer i;
|
||||||
for (i = 0; i < llGetListLength(names); i++) {
|
for (i = llGetListLength(names) - 1; i >= 0; i--) {
|
||||||
string testName = llList2String(names, i);
|
integer ch = -777000 + i;
|
||||||
if (llSubStringIndex(testName, searchName) != -1) {
|
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
|
||||||
idx = i;
|
|
||||||
llOwnerSay("✅ Matched pick list for: " + testName);
|
|
||||||
i = llGetListLength(names);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (idx == -1) {
|
|
||||||
llOwnerSay("⚠️ Could not find player: " + player);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
players = names = lives = picksData = globalPickedNumbers = [];
|
||||||
|
perilPlayer = "";
|
||||||
|
pickQueue = [];
|
||||||
|
currentPickerIdx = 0;
|
||||||
|
llSay(syncChannel, "RESET");
|
||||||
|
llOwnerSay(" Game reset!");
|
||||||
|
llSleep(0.2);
|
||||||
|
llSetTimerEvent(0);
|
||||||
|
updateHelpers();
|
||||||
}
|
}
|
||||||
|
|
||||||
integer maxPicks = getPicksRequiredFromName(llList2String(names, idx));
|
startNextRound() {
|
||||||
|
// Do not start a round if there are fewer than 2 players. This guard prevents
|
||||||
string currentData = llList2String(picksData, idx);
|
// accidental starts when only one player is present (e.g. after a reset or
|
||||||
list parts = llParseString2List(currentData, ["|"], []);
|
// before any other participants join).
|
||||||
list currentPicks = [];
|
if (llGetListLength(names) < 2) {
|
||||||
if (llGetListLength(parts) > 1) {
|
llOwnerSay("⚠️ Need at least 2 players to start the game.");
|
||||||
currentPicks = llParseString2List(llList2String(parts, 1), [","], []);
|
return;
|
||||||
|
}
|
||||||
|
// If somehow called with exactly one player, treat them as the winner and reset.
|
||||||
|
if (llGetListLength(names) == 1) {
|
||||||
|
llSay(0, " " + llList2String(names, 0) + " is the last player standing and wins the game!");
|
||||||
|
resetGame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
picksData = [];
|
||||||
|
globalPickedNumbers = [];
|
||||||
|
pickQueue = names;
|
||||||
|
currentPickerIdx = 0;
|
||||||
|
integer j;
|
||||||
|
for (j = 0; j < llGetListLength(names); j++) {
|
||||||
|
picksData += [llList2String(names, j) + "|"];
|
||||||
|
}
|
||||||
|
updateHelpers();
|
||||||
|
// requestDiceType(); // Removed to avoid recursion loop
|
||||||
}
|
}
|
||||||
|
|
||||||
list buttons = [];
|
showNextPickerDialog() {
|
||||||
integer i;
|
string firstName = llList2String(pickQueue, currentPickerIdx);
|
||||||
for (i = 1; i <= 6; i++) {
|
currentPicker = llList2Key(players, llListFindList(names, [firstName]));
|
||||||
string numStr = (string)i;
|
string dialogPayload = firstName + "|" + (string)diceType;
|
||||||
if (llListFindList(currentPicks, [numStr]) != -1) {
|
llMessageLinked(LINK_SET, MSG_SHOW_DIALOG, dialogPayload, currentPicker);
|
||||||
buttons += ["❌ " + numStr];
|
timeoutTimer = llGetUnixTime();
|
||||||
} else if (llGetListLength(currentPicks) < maxPicks) {
|
lastWarning = 0;
|
||||||
buttons += ["➕ " + numStr];
|
llSetTimerEvent(60.0);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
llOwnerSay("🛠 Showing pick dialog to: " + (string)id);
|
integer roundStarted = FALSE;
|
||||||
llDialog(id, "🛠 Managing picks for: " + searchName + "\nCurrent: " + llList2CSV(currentPicks), buttons, -88888);
|
|
||||||
|
|
||||||
// Return dummy value since LSL does not have a void return type
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
default {
|
default {
|
||||||
|
state_entry() {
|
||||||
|
llListen(DIALOG_CHANNEL, "", NULL_KEY, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
touch_start(integer total_number) {
|
||||||
|
llOwnerSay("Touched by: " + (string)llDetectedKey(0));
|
||||||
|
key toucher = llDetectedKey(0);
|
||||||
|
integer idx = llListFindList(players, [toucher]);
|
||||||
|
if (toucher == llGetOwner()) {
|
||||||
|
// Determine if this will be the first registered player (starter) before registration
|
||||||
|
integer isStarter = FALSE;
|
||||||
|
if (idx == -1) {
|
||||||
|
// No index found; the owner is not yet registered.
|
||||||
|
// If there are no existing players, they will become the starter.
|
||||||
|
if (llGetListLength(players) == 0) {
|
||||||
|
isStarter = TRUE;
|
||||||
|
}
|
||||||
|
string ownerName = llKey2Name(toucher);
|
||||||
|
// Send a registration request; Main.lsl will handle adding them and rezzing a float
|
||||||
|
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, ownerName + "|" + (string)toucher, NULL_KEY);
|
||||||
|
} else {
|
||||||
|
// Already registered; starter if at index 0
|
||||||
|
isStarter = (idx == 0);
|
||||||
|
}
|
||||||
|
// Show the owner menu with the appropriate starter flag
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
link_message(integer sender, integer num, string str, key id) {
|
link_message(integer sender, integer num, string str, key id) {
|
||||||
if (num == MSG_SYNC_GAME_STATE) {
|
// Player list and pick list handling remain unchanged
|
||||||
|
if (num == 202 && str == "REQUEST_PLAYER_LIST") {
|
||||||
|
string namesCSV = llList2CSV(names);
|
||||||
|
llMessageLinked(LINK_SET, 203, namesCSV, id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (num == 206) {
|
||||||
|
string targetName = str;
|
||||||
|
integer i;
|
||||||
|
for (i = 0; i < llGetListLength(picksData); i++) {
|
||||||
|
string rawEntry = llList2String(picksData, i);
|
||||||
|
list parts = llParseString2List(rawEntry, ["|"], []);
|
||||||
|
if (llList2String(parts, 0) == targetName) {
|
||||||
|
llMessageLinked(LINK_SET, 205, targetName + "|" + llList2String(parts, 1), id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
llMessageLinked(LINK_SET, 205, targetName + "|", id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (num == 208) {
|
||||||
|
string playerName = str;
|
||||||
|
integer idx = llListFindList(names, [playerName]);
|
||||||
|
if (idx != -1) {
|
||||||
|
string lifeVal = llList2String(lives, idx);
|
||||||
|
llMessageLinked(LINK_SET, 208, playerName + "|" + lifeVal, id);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Handle dice type result from helper
|
||||||
|
if (num == MSG_DICE_TYPE_RESULT) {
|
||||||
|
if (!roundStarted) {
|
||||||
|
roundStarted = TRUE;
|
||||||
|
diceType = (integer)str;
|
||||||
|
llOwnerSay(" Dice type set to: " + str);
|
||||||
|
showNextPickerDialog();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (num == MSG_GET_PICKS_REQUIRED) {
|
||||||
|
integer picksRequired = (integer)str;
|
||||||
|
llOwnerSay(" Picks required: " + str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (num == MSG_GET_PICKER_INDEX) {
|
||||||
|
integer pickIndex = (integer)str;
|
||||||
|
llOwnerSay(" Picker index: " + str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (num == MSG_SERIALIZE_GAME_STATE) {
|
||||||
|
string serialized = str;
|
||||||
|
llOwnerSay(" Serialized game state: " + serialized);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Handle pick actions
|
||||||
|
if (num == 204) {
|
||||||
list parts = llParseString2List(str, ["~"], []);
|
list parts = llParseString2List(str, ["~"], []);
|
||||||
lives = llCSV2List(llList2String(parts, 0));
|
string action = llList2String(parts, 0);
|
||||||
picksData = llCSV2List(llList2String(parts, 1));
|
list args = llParseString2List(llList2String(parts, 1), ["|"], []);
|
||||||
perilPlayer = llList2String(parts, 2);
|
string name = llList2String(args, 0);
|
||||||
names = llCSV2List(llList2String(parts, 3));
|
string pick = llList2String(args, 1);
|
||||||
|
integer i;
|
||||||
|
for (i = 0; i < llGetListLength(picksData); i++) {
|
||||||
|
string entry = llList2String(picksData, i);
|
||||||
|
list pdParts = llParseString2List(entry, ["|"], []);
|
||||||
|
if (llList2String(pdParts, 0) == name) {
|
||||||
|
list pickList = [];
|
||||||
|
string rawPicks = llList2String(pdParts, 1);
|
||||||
|
if (rawPicks != "") {
|
||||||
|
pickList = llParseString2List(rawPicks, [","], []);
|
||||||
}
|
}
|
||||||
else if (num == MSG_SYNC_PICKQUEUE) {
|
if (action == "ADD_PICK") {
|
||||||
pickQueue = llCSV2List(str);
|
if (llListFindList(pickList, [pick]) == -1) {
|
||||||
|
pickList += [pick];
|
||||||
|
llOwnerSay("➕ Added " + pick + " to " + name);
|
||||||
}
|
}
|
||||||
else if (num == MSG_GET_DICE_TYPE) {
|
} else if (action == "REMOVE_PICK") {
|
||||||
integer playerCount = (integer)str;
|
integer idx = llListFindList(pickList, [pick]);
|
||||||
llOwnerSay("📊 playerCount received: " + (string)playerCount);
|
if (idx != -1) {
|
||||||
integer result = getDiceType(playerCount);
|
pickList = llDeleteSubList(pickList, idx, idx);
|
||||||
// Broadcast the dice type result across the entire linkset so that
|
llOwnerSay("➖ Removed " + pick + " from " + name);
|
||||||
// the main game controller can receive it. Using LINK_THIS
|
|
||||||
// restricted the message to the helper's prim, preventing the
|
|
||||||
// controller from seeing MSG_DICE_TYPE_RESULT and causing the
|
|
||||||
// game not to start.
|
|
||||||
llMessageLinked(LINK_SET, MSG_DICE_TYPE_RESULT, (string)result, NULL_KEY);
|
|
||||||
}
|
}
|
||||||
else if (num == MSG_GET_PICKS_REQUIRED) {
|
|
||||||
integer result = getPicksRequiredFromName(str);
|
|
||||||
llMessageLinked(LINK_THIS, MSG_GET_PICKS_REQUIRED, (string)result, NULL_KEY);
|
|
||||||
}
|
}
|
||||||
else if (num == MSG_GET_PICKER_INDEX) {
|
picksData = llListReplaceList(picksData, [name + "|" + llList2CSV(pickList)], i, i);
|
||||||
integer result = getPickerIndex(str);
|
updateHelpers();
|
||||||
llMessageLinked(LINK_THIS, MSG_GET_PICKER_INDEX, (string)result, NULL_KEY);
|
return;
|
||||||
}
|
}
|
||||||
else if (num == MSG_SERIALIZE_GAME_STATE) {
|
|
||||||
llMessageLinked(LINK_THIS, MSG_SERIALIZE_GAME_STATE, serializeGameState(), NULL_KEY);
|
|
||||||
}
|
}
|
||||||
else if (num == 206) {
|
return;
|
||||||
llOwnerSay("📨 MSG 206 received for: " + str + " from: " + (string)id);
|
}
|
||||||
showPickManager(str, id);
|
// New: handle dynamic registration of players (owner or players) via MSG_REGISTER_PLAYER
|
||||||
|
if (num == MSG_REGISTER_PLAYER) {
|
||||||
|
// str is in the format "Name|<key>"
|
||||||
|
list parts = llParseString2List(str, ["|"], []);
|
||||||
|
string newName = llList2String(parts, 0);
|
||||||
|
key newKey = (key)llList2String(parts, 1);
|
||||||
|
// Do not register if already present
|
||||||
|
integer existingIdx = llListFindList(players, [newKey]);
|
||||||
|
if (existingIdx == -1) {
|
||||||
|
// Add to local lists
|
||||||
|
players += [newKey];
|
||||||
|
names += [newName];
|
||||||
|
lives += [3];
|
||||||
|
picksData += [newName + "|"];
|
||||||
|
// The float will be rezzed by the Floater Manager after it processes
|
||||||
|
// this registration. Avoid sending MSG_REZ_FLOAT here to prevent
|
||||||
|
// race conditions; instead, request the float from Floater Manager
|
||||||
|
// after it has updated its own lists. Immediately propagate
|
||||||
|
// helper updates now.
|
||||||
|
updateHelpers();
|
||||||
|
// Notify owner of registration
|
||||||
|
llOwnerSay("🔔 Added player: " + newName);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(integer channel, string name, key id, string msg) {
|
||||||
|
if (channel == DIALOG_CHANNEL) {
|
||||||
|
// Owner-specific commands: only owner messages in the dialog should be processed here.
|
||||||
|
if (id == llGetOwner()) {
|
||||||
|
if (msg == "Reset Game") {
|
||||||
|
resetGame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Note: do not handle "Start Game" here; allow the generic start logic below to apply,
|
||||||
|
// so that minimum player checks are enforced and non-owner starters can initiate the game.
|
||||||
|
if (msg == "Dump Players") {
|
||||||
|
llOwnerSay(" Players: " + llList2CSV(names));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (msg == "Manage Picks") {
|
||||||
|
llOwnerSay(" Fetching list of players for pick management...");
|
||||||
|
llMessageLinked(LINK_SET, 202, "REQUEST_PLAYER_LIST", llGetOwner());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (msg == "Add Test Player") {
|
||||||
|
// Ensure we do not exceed the maximum allowed players
|
||||||
|
if (llGetListLength(players) >= MAX_PLAYERS) {
|
||||||
|
llOwnerSay("⚠️ Cannot add test player; the game is full (max " + (string)MAX_PLAYERS + ").");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Generate a unique name and key for the test player
|
||||||
|
string testName = "TestBot" + (string)llGetUnixTime();
|
||||||
|
key fake = llGenerateKey();
|
||||||
|
// Delegate registration to the MSG_REGISTER_PLAYER handler; it will add to lists and rez a float
|
||||||
|
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, testName + "|" + (string)fake, NULL_KEY);
|
||||||
|
// Show the owner menu again (not flagged as starter)
|
||||||
|
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|0", llGetOwner());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Allow the first registered player (index 0) or the owner to start the game.
|
||||||
|
// When the starter clicks "Start Game", begin the round.
|
||||||
|
if (msg == "Start Game") {
|
||||||
|
// Enforce a minimum of 2 players before starting
|
||||||
|
if (llGetListLength(players) < 2) {
|
||||||
|
llOwnerSay("⚠️ Need at least 2 players to start the game.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check if the sender is the owner
|
||||||
|
if (id == llGetOwner()) {
|
||||||
|
startNextRound();
|
||||||
|
requestDiceType();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Otherwise, check if the sender is the very first player registered
|
||||||
|
if (llGetListLength(players) > 0) {
|
||||||
|
key firstPlayer = llList2Key(players, 0);
|
||||||
|
if (id == firstPlayer) {
|
||||||
|
startNextRound();
|
||||||
|
requestDiceType();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the message text matches a player name, request their pick list
|
||||||
|
if (llListFindList(names, [msg]) != -1) {
|
||||||
|
llMessageLinked(LINK_SET, 206, msg, id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue