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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
457
Helper.lsl
457
Helper.lsl
|
|
@ -1,159 +1,358 @@
|
|||
// === Game Helper Functions Module (Max 10 players) ===
|
||||
// This version chooses a dice size based on the number of players,
|
||||
// ensuring at least three numbers are available per player.
|
||||
// === Main Peril Dice Controller (Refactored with Game Helpers Integration, with dynamic player join) ===
|
||||
|
||||
//
|
||||
// This version of the main game controller includes support for players (including
|
||||
// the owner) joining the game at runtime via a MSG_REGISTER_PLAYER message. When
|
||||
// a new player registers, they are added to the internal lists (players, names,
|
||||
// lives and picksData), a floating display is rezzed for them, and helpers are
|
||||
// updated. This mirrors the behaviour used when adding a test player, but now
|
||||
// applies to any avatar joining the game via the dialog handler.
|
||||
|
||||
integer syncChannel = -77777;
|
||||
integer numberPickChannel = -77888;
|
||||
integer rollDialogChannel = -77999;
|
||||
integer DIALOG_CHANNEL = -88888;
|
||||
|
||||
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_DICE_TYPE_RESULT = 1005;
|
||||
integer MSG_GET_PICKS_REQUIRED = 1002;
|
||||
integer MSG_GET_PICKER_INDEX = 1003;
|
||||
integer MSG_DICE_TYPE_RESULT = 1005;
|
||||
integer MSG_SERIALIZE_GAME_STATE = 1004;
|
||||
integer MSG_SYNC_GAME_STATE = 107;
|
||||
integer MSG_SYNC_PICKQUEUE = 2001;
|
||||
|
||||
list lives;
|
||||
list picksData;
|
||||
string perilPlayer;
|
||||
list names;
|
||||
list pickQueue;
|
||||
// Maximum number of players allowed (including test players). This should
|
||||
// mirror the value used in Floater Manager to avoid inconsistencies.
|
||||
integer MAX_PLAYERS = 10;
|
||||
|
||||
// List of supported dice sizes; ensure the last entry can accommodate 10 players × 3 picks = 30
|
||||
list STANDARD_DICE = [6, 12, 20, 30];
|
||||
integer TIMEOUT_SECONDS = 600;
|
||||
integer timeoutTimer;
|
||||
|
||||
// Determine which die to use based on player count.
|
||||
// Each player should have at least 3 numbers available to choose from.
|
||||
integer getDiceType(integer playerCount) {
|
||||
integer minimumSides = playerCount * 3;
|
||||
integer i;
|
||||
for (i = 0; i < llGetListLength(STANDARD_DICE); i++) {
|
||||
integer sides = llList2Integer(STANDARD_DICE, i);
|
||||
if (sides >= minimumSides) {
|
||||
return sides;
|
||||
}
|
||||
}
|
||||
// If no predefined die has enough sides, return the largest supported die (d30)
|
||||
return llList2Integer(STANDARD_DICE, llGetListLength(STANDARD_DICE) - 1);
|
||||
integer warning2min = 120;
|
||||
integer warning5min = 300;
|
||||
integer warning9min = 540;
|
||||
integer lastWarning = 0;
|
||||
|
||||
key currentPicker;
|
||||
|
||||
// Forward game state to helpers when it changes
|
||||
updateHelpers() {
|
||||
string serialized = llList2CSV(lives) + "~" + llList2CSV(picksData) +
|
||||
"~" + perilPlayer + "~" + llList2CSV(names);
|
||||
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, serialized, NULL_KEY);
|
||||
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
|
||||
integer getPicksRequiredFromName(string name) {
|
||||
integer idx = llListFindList(names, [name]);
|
||||
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;
|
||||
// Request helper-calculated values
|
||||
requestDiceType() {
|
||||
llMessageLinked(LINK_SET, MSG_GET_DICE_TYPE, (string)llGetListLength(names), NULL_KEY);
|
||||
}
|
||||
|
||||
// Find the index of a player in the pickQueue by name
|
||||
integer getPickerIndex(string name) {
|
||||
return llListFindList(pickQueue, [name]);
|
||||
requestPicksRequired(integer idx) {
|
||||
llMessageLinked(LINK_SET, MSG_GET_PICKS_REQUIRED, llList2String(names, idx), NULL_KEY);
|
||||
}
|
||||
|
||||
// Serialize the current game state for syncing with other scripts
|
||||
string serializeGameState() {
|
||||
requestPickerIndex(string name) {
|
||||
llMessageLinked(LINK_SET, MSG_GET_PICKER_INDEX, name, NULL_KEY);
|
||||
}
|
||||
|
||||
string generateSerializedState() {
|
||||
return llList2CSV(lives) + "~" + llList2CSV(picksData) + "~" + perilPlayer + "~" + llList2CSV(names);
|
||||
}
|
||||
|
||||
// Show a pick management dialog for the owner.
|
||||
// LSL does not support a void return type, so this returns an integer which is ignored.
|
||||
integer showPickManager(string player, key id) {
|
||||
string searchName = player;
|
||||
if (llSubStringIndex(searchName, "~") != -1) {
|
||||
list parts = llParseString2List(searchName, ["~"], []);
|
||||
searchName = llList2String(parts, 1);
|
||||
}
|
||||
|
||||
integer idx = llListFindList(names, [searchName]);
|
||||
if (idx == -1) {
|
||||
integer i;
|
||||
for (i = 0; i < llGetListLength(names); i++) {
|
||||
string testName = llList2String(names, i);
|
||||
if (llSubStringIndex(testName, searchName) != -1) {
|
||||
idx = i;
|
||||
llOwnerSay("✅ Matched pick list for: " + testName);
|
||||
i = llGetListLength(names);
|
||||
}
|
||||
}
|
||||
if (idx == -1) {
|
||||
llOwnerSay("⚠️ Could not find player: " + player);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
integer maxPicks = getPicksRequiredFromName(llList2String(names, idx));
|
||||
|
||||
string currentData = llList2String(picksData, idx);
|
||||
list parts = llParseString2List(currentData, ["|"], []);
|
||||
list currentPicks = [];
|
||||
if (llGetListLength(parts) > 1) {
|
||||
currentPicks = llParseString2List(llList2String(parts, 1), [","], []);
|
||||
}
|
||||
|
||||
list buttons = [];
|
||||
resetGame() {
|
||||
// Remove floats in reverse order so indices remain correct as entries are
|
||||
// deleted from the Floater Manager's lists. Otherwise, deleting index 0
|
||||
// first shifts subsequent indices and some floats are left behind.
|
||||
integer i;
|
||||
for (i = 1; i <= 6; i++) {
|
||||
string numStr = (string)i;
|
||||
if (llListFindList(currentPicks, [numStr]) != -1) {
|
||||
buttons += ["❌ " + numStr];
|
||||
} else if (llGetListLength(currentPicks) < maxPicks) {
|
||||
buttons += ["➕ " + numStr];
|
||||
}
|
||||
for (i = llGetListLength(names) - 1; i >= 0; i--) {
|
||||
integer ch = -777000 + i;
|
||||
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
|
||||
}
|
||||
|
||||
llOwnerSay("🛠 Showing pick dialog to: " + (string)id);
|
||||
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;
|
||||
players = names = lives = picksData = globalPickedNumbers = [];
|
||||
perilPlayer = "";
|
||||
pickQueue = [];
|
||||
currentPickerIdx = 0;
|
||||
llSay(syncChannel, "RESET");
|
||||
llOwnerSay(" Game reset!");
|
||||
llSleep(0.2);
|
||||
llSetTimerEvent(0);
|
||||
updateHelpers();
|
||||
}
|
||||
|
||||
startNextRound() {
|
||||
// Do not start a round if there are fewer than 2 players. This guard prevents
|
||||
// accidental starts when only one player is present (e.g. after a reset or
|
||||
// before any other participants join).
|
||||
if (llGetListLength(names) < 2) {
|
||||
llOwnerSay("⚠️ Need at least 2 players to start the game.");
|
||||
return;
|
||||
}
|
||||
// If somehow called with exactly one player, treat them as the winner and reset.
|
||||
if (llGetListLength(names) == 1) {
|
||||
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
|
||||
}
|
||||
|
||||
showNextPickerDialog() {
|
||||
string firstName = llList2String(pickQueue, currentPickerIdx);
|
||||
currentPicker = llList2Key(players, llListFindList(names, [firstName]));
|
||||
string dialogPayload = firstName + "|" + (string)diceType;
|
||||
llMessageLinked(LINK_SET, MSG_SHOW_DIALOG, dialogPayload, currentPicker);
|
||||
timeoutTimer = llGetUnixTime();
|
||||
lastWarning = 0;
|
||||
llSetTimerEvent(60.0);
|
||||
}
|
||||
|
||||
integer roundStarted = FALSE;
|
||||
|
||||
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) {
|
||||
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, ["~"], []);
|
||||
lives = llCSV2List(llList2String(parts, 0));
|
||||
picksData = llCSV2List(llList2String(parts, 1));
|
||||
perilPlayer = llList2String(parts, 2);
|
||||
names = llCSV2List(llList2String(parts, 3));
|
||||
string action = llList2String(parts, 0);
|
||||
list args = llParseString2List(llList2String(parts, 1), ["|"], []);
|
||||
string name = llList2String(args, 0);
|
||||
string pick = llList2String(args, 1);
|
||||
integer i;
|
||||
for (i = 0; i < llGetListLength(picksData); i++) {
|
||||
string entry = llList2String(picksData, i);
|
||||
list pdParts = llParseString2List(entry, ["|"], []);
|
||||
if (llList2String(pdParts, 0) == name) {
|
||||
list pickList = [];
|
||||
string rawPicks = llList2String(pdParts, 1);
|
||||
if (rawPicks != "") {
|
||||
pickList = llParseString2List(rawPicks, [","], []);
|
||||
}
|
||||
if (action == "ADD_PICK") {
|
||||
if (llListFindList(pickList, [pick]) == -1) {
|
||||
pickList += [pick];
|
||||
llOwnerSay("➕ Added " + pick + " to " + name);
|
||||
}
|
||||
} else if (action == "REMOVE_PICK") {
|
||||
integer idx = llListFindList(pickList, [pick]);
|
||||
if (idx != -1) {
|
||||
pickList = llDeleteSubList(pickList, idx, idx);
|
||||
llOwnerSay("➖ Removed " + pick + " from " + name);
|
||||
}
|
||||
}
|
||||
picksData = llListReplaceList(picksData, [name + "|" + llList2CSV(pickList)], i, i);
|
||||
updateHelpers();
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (num == MSG_SYNC_PICKQUEUE) {
|
||||
pickQueue = llCSV2List(str);
|
||||
// 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;
|
||||
}
|
||||
else if (num == MSG_GET_DICE_TYPE) {
|
||||
integer playerCount = (integer)str;
|
||||
llOwnerSay("📊 playerCount received: " + (string)playerCount);
|
||||
integer result = getDiceType(playerCount);
|
||||
// Broadcast the dice type result across the entire linkset so that
|
||||
// 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) {
|
||||
integer result = getPickerIndex(str);
|
||||
llMessageLinked(LINK_THIS, MSG_GET_PICKER_INDEX, (string)result, NULL_KEY);
|
||||
}
|
||||
else if (num == MSG_SERIALIZE_GAME_STATE) {
|
||||
llMessageLinked(LINK_THIS, MSG_SERIALIZE_GAME_STATE, serializeGameState(), NULL_KEY);
|
||||
}
|
||||
else if (num == 206) {
|
||||
llOwnerSay("📨 MSG 206 received for: " + str + " from: " + (string)id);
|
||||
showPickManager(str, id);
|
||||
}
|
||||
|
||||
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