🎲 Peril Dice — Modular Game System for Second Life
Overview Peril Dice is a multiplayer elimination game where each player selects numbers before a die is rolled. If the peril player’s number is rolled, they lose a life. Players are eliminated when they reach zero lives. 🔧 Features ✅ Player System Max players: 10 Each player starts with 3 lives Players can Join, Leave, or mark themselves Ready The first player to join gets a Start Game button The owner can join the game and also has a special Owner menu 🎲 Dice Scaling Dice type is automatically chosen to ensure at least 3 picks per player Dice types supported: d6, d12, d20, d30 Player Count Dice Type 1–2 d6 3–4 d12 5–6 d20 7–10 d30 🧍 Test Players Owner can add/remove test players (bots) Test players rez stat floaters at offset positions to prevent overlap Floater objects follow players and update in real time 🪟 HUD & Stat Floaters Players receive a floating HUD showing: Player name Lives Picks Peril status or game state 📦 Modular Architecture Separated scripts: Main Controller: Handles core game logic Dialog Handler: Manages pick dialogs Dice Selector: Returns dice type based on player count Stat Float Manager: Rezzes and updates floaters Stat Float Object: Displays floating text per player 🕹️ Game Flow Players touch the controller object to join the game Once 2+ players have joined, the first player gets the option to Start Game Dice type is selected automatically Each player is prompted (in order) to select 3 numbers After all picks are made: The dice is rolled The result is compared to each player’s picks If the peril player’s number is rolled or no one picked it, the peril player loses a life The system checks for eliminations and starts the next round 🧰 Admin Controls Owner can: Join or leave the game Open a special Owner menu Add/remove test bots Reset the game Debug current state (optional)
This commit is contained in:
parent
e7b853600a
commit
ede3f6ec75
6 changed files with 335 additions and 33 deletions
78
Debugger.lsl
78
Debugger.lsl
|
|
@ -57,6 +57,84 @@ list validatePicksData(list picksData, list names) {
|
|||
|
||||
|
||||
|
||||
default {
|
||||
link_message(integer sender, integer num, string str, key id) {
|
||||
if (num == MSG_SYNC_GAME_STATE) {
|
||||
list parts = llParseString2List(str, ["~"], []);
|
||||
if (llGetListLength(parts) < 4) {
|
||||
llOwnerSay("⚠️ Incomplete game state received.");
|
||||
return;
|
||||
}
|
||||
|
||||
list lives = llCSV2List(llList2String(parts, 0));
|
||||
list picksData = llCSV2List(llList2String(parts, 1));
|
||||
string perilPlayer = llList2String(parts, 2);
|
||||
list names = llCSV2List(llList2String(parts, 3));
|
||||
|
||||
llOwnerSay("🔍 Checking picksData...");
|
||||
validatePicksData(picksData, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
integer MSG_SYNC_GAME_STATE = 107;
|
||||
|
||||
list validatePicksData(list picksData, list names) {
|
||||
list allPicksFlat = [];
|
||||
integer i;
|
||||
|
||||
for (i = 0; i < llGetListLength(picksData); i++) {
|
||||
string entry = llList2String(picksData, i);
|
||||
list parts = llParseString2List(entry, ["|"], []);
|
||||
|
||||
if (llGetListLength(parts) == 2) {
|
||||
string name = llList2String(parts, 0);
|
||||
string picks = llList2String(parts, 1);
|
||||
list pickNums = llParseString2List(picks, [","], []);
|
||||
|
||||
// Check if name is in names list
|
||||
if (names != [] && llListFindList(names, [name]) == -1) {
|
||||
llOwnerSay("⚠️ picksData name not in names list: " + name);
|
||||
}
|
||||
|
||||
// Check for duplicate picks in this player's list
|
||||
integer j;
|
||||
list uniqueCheck = [];
|
||||
for (j = 0; j < llGetListLength(pickNums); j++) {
|
||||
string pick = llList2String(pickNums, j);
|
||||
if ((string)((float)pick) != pick) {
|
||||
llOwnerSay("⚠️ Invalid number in picks for " + name + ": '" + pick + "'");
|
||||
}
|
||||
if (~llListFindList(uniqueCheck, [pick])) {
|
||||
llOwnerSay("🚨 Duplicate pick in " + name + "'s picks: " + pick);
|
||||
} else {
|
||||
uniqueCheck += [pick];
|
||||
}
|
||||
allPicksFlat += [pick];
|
||||
}
|
||||
|
||||
llOwnerSay("✅ " + name + "'s picks: " + llList2CSV(pickNums));
|
||||
} else {
|
||||
llOwnerSay("⚠️ Malformed entry in picksData: " + entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for duplicate picks across all players
|
||||
list seen = [];
|
||||
integer k;
|
||||
for (k = 0; k < llGetListLength(allPicksFlat); k++) {
|
||||
string val = llList2String(allPicksFlat, k);
|
||||
if (~llListFindList(seen, [val])) {
|
||||
llOwnerSay("❌ Duplicate pick across players: " + val);
|
||||
} else {
|
||||
seen += [val];
|
||||
}
|
||||
}
|
||||
|
||||
return picksData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
default {
|
||||
link_message(integer sender, integer num, string str, key id) {
|
||||
if (num == MSG_SYNC_GAME_STATE) {
|
||||
|
|
|
|||
|
|
@ -78,17 +78,54 @@ default {
|
|||
names += [name];
|
||||
players += [avKey];
|
||||
llOwnerSay("📝 Registered: " + name);
|
||||
|
||||
// Immediately rez the float after registration. This ensures the
|
||||
// float manager has updated its internal lists before attempting
|
||||
// to look up the player's index. Without this, other scripts
|
||||
// sending MSG_REZ_FLOAT in parallel could cause a race condition
|
||||
// where the float manager has not yet seen the new name, so it
|
||||
// cannot resolve the index and fails to rez the float. By
|
||||
// initiating the rezzing here, we guarantee the float is created
|
||||
// only after the registration is complete.
|
||||
llMessageLinked(LINK_SET, MSG_REZ_FLOAT, name, avKey);
|
||||
}
|
||||
else if (num == MSG_REZ_FLOAT) {
|
||||
string name = str;
|
||||
integer idx = llListFindList(names, [name]);
|
||||
if (idx == -1) return;
|
||||
key avKey = llList2Key(players, idx);
|
||||
vector pos = llList2Vector(llGetObjectDetails(avKey, [OBJECT_POS]), 0) + <1,0,1>;
|
||||
// Determine the rez position. For real avatars, use their current
|
||||
// location plus an offset. For test players (fake keys) or when
|
||||
// OBJECT_POS cannot be obtained, fall back to rezzing relative to
|
||||
// the object's own position to ensure a float is created.
|
||||
list details = llGetObjectDetails(avKey, [OBJECT_POS]);
|
||||
vector basePos;
|
||||
if (llGetListLength(details) > 0) {
|
||||
basePos = llList2Vector(details, 0);
|
||||
} else {
|
||||
basePos = llGetPos();
|
||||
}
|
||||
vector pos;
|
||||
if (llGetListLength(details) > 0) {
|
||||
// Real avatars: rez next to the avatar with the standard offset
|
||||
pos = basePos + <1, 0, 1>;
|
||||
} else {
|
||||
// Test players: space floats apart along the X axis so they don't overlap.
|
||||
// Each test player gets an additional 0.5m offset based on their index.
|
||||
// This uses the current prim's position as the base since there's no
|
||||
// avatar position available.
|
||||
pos = basePos + <1 + (float)idx * 0.5, 0, 1>;
|
||||
}
|
||||
integer ch = -777000 + idx;
|
||||
llOwnerSay("📦 Rezzing float for " + name + " at " + (string)pos);
|
||||
llSetObjectDesc(name);
|
||||
llRezObject("StatFloat", pos, ZERO_VECTOR, ZERO_ROTATION, ch);
|
||||
// After rezzing, immediately update the float so it displays the
|
||||
// correct lives, picks and peril status. Use the avatar key as
|
||||
// the id to keep the float tied to the player. This ensures that
|
||||
// test players also receive an update even if they don’t have a
|
||||
// valid avatar key.
|
||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, name, avKey);
|
||||
}
|
||||
else if (num == MSG_UPDATE_FLOAT) {
|
||||
string name = str;
|
||||
|
|
@ -105,7 +142,12 @@ default {
|
|||
llOwnerSay("🔎 Found picks: " + llList2CSV(picks));
|
||||
|
||||
string perilName;
|
||||
if (perilPlayer == "") {
|
||||
// If the perilPlayer string is empty or contains a comma (indicating
|
||||
// multiple names), treat the game as not yet started. This avoids
|
||||
// showing multiple players as the peril player before the first
|
||||
// round begins. Once a single peril player is assigned, it will
|
||||
// not contain a comma and will be displayed normally.
|
||||
if (perilPlayer == "" || llSubStringIndex(perilPlayer, ",") != -1) {
|
||||
perilName = "🧍 Status: Waiting for game to start...";
|
||||
} else {
|
||||
perilName = "🧍 Peril: " + getNameFromKey(perilPlayer);
|
||||
|
|
@ -143,6 +185,23 @@ default {
|
|||
}
|
||||
}
|
||||
perilPlayer = llList2String(parts, 2);
|
||||
|
||||
// After synchronizing the game state, update all existing floats so
|
||||
// they reflect the current peril status. Without this, floats
|
||||
// created in previous rounds may continue to display an old
|
||||
// peril name until another manual update occurs.
|
||||
integer idx;
|
||||
for (idx = 0; idx < llGetListLength(names); idx++) {
|
||||
string n = llList2String(names, idx);
|
||||
// Use the player's key if it exists; fallback to NULL_KEY for test bots
|
||||
key k;
|
||||
if (idx < llGetListLength(players)) {
|
||||
k = llList2Key(players, idx);
|
||||
} else {
|
||||
k = NULL_KEY;
|
||||
}
|
||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, n, k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -133,7 +133,12 @@ default {
|
|||
integer playerCount = (integer)str;
|
||||
llOwnerSay("📊 playerCount received: " + (string)playerCount);
|
||||
integer result = getDiceType(playerCount);
|
||||
llMessageLinked(LINK_THIS, MSG_DICE_TYPE_RESULT, (string)result, NULL_KEY);
|
||||
// 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);
|
||||
|
|
@ -151,4 +156,4 @@ default {
|
|||
showPickManager(str, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Main.lsl
96
Main.lsl
|
|
@ -42,6 +42,10 @@ integer MSG_DICE_TYPE_RESULT = 1005;
|
|||
integer MSG_SERIALIZE_GAME_STATE = 1004;
|
||||
integer MSG_SYNC_PICKQUEUE = 2001;
|
||||
|
||||
// Maximum number of players allowed (including test players). This should
|
||||
// mirror the value used in Floater Manager to avoid inconsistencies.
|
||||
integer MAX_PLAYERS = 10;
|
||||
|
||||
integer TIMEOUT_SECONDS = 600;
|
||||
integer timeoutTimer;
|
||||
|
||||
|
|
@ -78,8 +82,11 @@ string generateSerializedState() {
|
|||
}
|
||||
|
||||
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 = 0; i < llGetListLength(names); i++) {
|
||||
for (i = llGetListLength(names) - 1; i >= 0; i--) {
|
||||
integer ch = -777000 + i;
|
||||
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
|
||||
}
|
||||
|
|
@ -95,6 +102,14 @@ resetGame() {
|
|||
}
|
||||
|
||||
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();
|
||||
|
|
@ -134,9 +149,23 @@ default {
|
|||
key toucher = llDetectedKey(0);
|
||||
integer idx = llListFindList(players, [toucher]);
|
||||
if (toucher == llGetOwner()) {
|
||||
// Owner touches the prim: show menu for owner if already registered; else start as not starter
|
||||
// Pass isStarter=0 because owner is never automatically starter until players join
|
||||
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|0", toucher);
|
||||
// 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);
|
||||
|
|
@ -248,9 +277,11 @@ default {
|
|||
names += [newName];
|
||||
lives += [3];
|
||||
picksData += [newName + "|"];
|
||||
// Rez a float for the new player
|
||||
llMessageLinked(LINK_SET, MSG_REZ_FLOAT, newName, newKey);
|
||||
llSleep(0.2);
|
||||
// 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);
|
||||
|
|
@ -267,11 +298,8 @@ default {
|
|||
resetGame();
|
||||
return;
|
||||
}
|
||||
if (msg == "Start Game") {
|
||||
startNextRound();
|
||||
requestDiceType();
|
||||
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;
|
||||
|
|
@ -282,21 +310,45 @@ default {
|
|||
return;
|
||||
}
|
||||
if (msg == "Add Test Player") {
|
||||
string name = "TestBot" + (string)llGetUnixTime();
|
||||
// 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();
|
||||
// Add the test player to lists and register them with floater manager
|
||||
players += [fake];
|
||||
names += [name];
|
||||
lives += [3];
|
||||
picksData += [name + "|"];
|
||||
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, name + "|" + (string)fake, NULL_KEY);
|
||||
llMessageLinked(LINK_SET, MSG_REZ_FLOAT, name, fake);
|
||||
llSleep(0.2);
|
||||
updateHelpers();
|
||||
// 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);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// === Dialog Handler (Owner & Player) with unified Ready/Leave menu ===
|
||||
// === Dialog Handler (Owner & Player) with unified Ready/Leave menu and join support ===
|
||||
// Owners and players share the same Ready/Leave dialog.
|
||||
// Owners also get an "Owner" button to access advanced options.
|
||||
// Owners can join the game and receive a floating display.
|
||||
|
||||
integer DIALOG_CHANNEL = -88888;
|
||||
integer MSG_SHOW_MENU = 201;
|
||||
|
|
@ -8,6 +9,8 @@ integer MSG_PICK_ACTION = 204;
|
|||
integer MSG_PLAYER_LIST_RESULT = 203;
|
||||
integer MSG_PICK_LIST_RESULT = 205;
|
||||
integer MSG_LIFE_LOOKUP = 207;
|
||||
integer MSG_REGISTER_PLAYER = 106;
|
||||
integer MSG_REZ_FLOAT = 105;
|
||||
|
||||
// Owner-specific options shown when pressing the "Owner" button
|
||||
list ownerOptions = ["Join Game", "Leave Game", "Add Test Player", "Manage Picks", "Start Game", "Reset Game", "Dump Players"];
|
||||
|
|
@ -22,8 +25,11 @@ showOwnerMenu(key id) {
|
|||
// Owners receive an extra "Owner" button to access the advanced owner menu.
|
||||
showReadyLeaveMenu(key id, integer isStarter, integer isOwner) {
|
||||
list options;
|
||||
// If this user is flagged as the starter, they should see the "Start Game"
|
||||
// button so they can initiate the round (regardless of ownership). Otherwise
|
||||
// they see the regular "Ready" button.
|
||||
if (isStarter) {
|
||||
options = ["Start", "Leave Game"];
|
||||
options = ["Start Game", "Leave Game"];
|
||||
} else {
|
||||
options = ["Ready", "Leave Game"];
|
||||
}
|
||||
|
|
@ -82,8 +88,6 @@ default {
|
|||
}
|
||||
string targetType = llList2String(args, 0);
|
||||
integer isStarter = (integer)llList2String(args, 1);
|
||||
// We treat owners and players the same for the Ready/Leave menu.
|
||||
// If the targetType is "owner" we pass isOwner=TRUE and display the extra Owner button.
|
||||
if (targetType == "owner") {
|
||||
showReadyLeaveMenu(id, isStarter, TRUE);
|
||||
} else if (targetType == "player") {
|
||||
|
|
@ -117,10 +121,17 @@ default {
|
|||
}
|
||||
|
||||
listen(integer channel, string name, key id, string msg) {
|
||||
// Handle menus: if the user selects "Owner" on the combined menu, display the owner options.
|
||||
// Handle "Owner" button to show owner options
|
||||
if (msg == "Owner") {
|
||||
showOwnerMenu(id);
|
||||
}
|
||||
// Owner joins the game: register and rez a float for them
|
||||
else if (msg == "Join Game") {
|
||||
// Use the avatar's key as the identifier
|
||||
string pname = llKey2Name(id);
|
||||
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, pname + "|" + (string)id, NULL_KEY);
|
||||
llMessageLinked(LINK_SET, MSG_REZ_FLOAT, pname, id);
|
||||
}
|
||||
else if (msg == "Manage Picks") {
|
||||
llMessageLinked(LINK_THIS, 202, "REQUEST_PLAYER_LIST", id);
|
||||
}
|
||||
|
|
@ -168,4 +179,4 @@ default {
|
|||
llMessageLinked(LINK_THIS, 206, currentPickTarget, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
99
README.md
99
README.md
|
|
@ -1,3 +1,100 @@
|
|||
🎲 Peril Dice — Modular Game System for Second Life
|
||||
Overview
|
||||
Peril Dice is a multiplayer elimination game where each player selects numbers before a die is rolled. If the peril player’s number is rolled, they lose a life. Players are eliminated when they reach zero lives.
|
||||
|
||||
🔧 Features
|
||||
✅ Player System
|
||||
Max players: 10
|
||||
|
||||
Each player starts with 3 lives
|
||||
|
||||
Players can Join, Leave, or mark themselves Ready
|
||||
|
||||
The first player to join gets a Start Game button
|
||||
|
||||
The owner can join the game and also has a special Owner menu
|
||||
|
||||
🎲 Dice Scaling
|
||||
Dice type is automatically chosen to ensure at least 3 picks per player
|
||||
|
||||
Dice types supported: d6, d12, d20, d30
|
||||
|
||||
Player Count Dice Type
|
||||
1–2 d6
|
||||
3–4 d12
|
||||
5–6 d20
|
||||
7–10 d30
|
||||
|
||||
🧍 Test Players
|
||||
Owner can add/remove test players (bots)
|
||||
|
||||
Test players rez stat floaters at offset positions to prevent overlap
|
||||
|
||||
Floater objects follow players and update in real time
|
||||
|
||||
🪟 HUD & Stat Floaters
|
||||
Players receive a floating HUD showing:
|
||||
|
||||
Player name
|
||||
|
||||
Lives
|
||||
|
||||
Picks
|
||||
|
||||
Peril status or game state
|
||||
|
||||
📦 Modular Architecture
|
||||
Separated scripts:
|
||||
|
||||
Main Controller: Handles core game logic
|
||||
|
||||
Dialog Handler: Manages pick dialogs
|
||||
|
||||
Dice Selector: Returns dice type based on player count
|
||||
|
||||
Stat Float Manager: Rezzes and updates floaters
|
||||
|
||||
Stat Float Object: Displays floating text per player
|
||||
|
||||
🕹️ Game Flow
|
||||
Players touch the controller object to join the game
|
||||
|
||||
Once 2+ players have joined, the first player gets the option to Start Game
|
||||
|
||||
Dice type is selected automatically
|
||||
|
||||
Each player is prompted (in order) to select 3 numbers
|
||||
|
||||
After all picks are made:
|
||||
|
||||
The dice is rolled
|
||||
|
||||
The result is compared to each player’s picks
|
||||
|
||||
If the peril player’s number is rolled or no one picked it, the peril player loses a life
|
||||
|
||||
The system checks for eliminations and starts the next round
|
||||
|
||||
🧰 Admin Controls
|
||||
Owner can:
|
||||
|
||||
Join or leave the game
|
||||
|
||||
Open a special Owner menu
|
||||
|
||||
Add/remove test bots
|
||||
|
||||
Reset the game
|
||||
|
||||
Debug current state (optional)
|
||||
|
||||
|
||||
|
||||
Below what was started, ill have this up just a reminder
|
||||
|
||||
|
||||
|
||||
|
||||
# Peril Dice
|
||||
Notice these rules were made by Noose the Bunny (djmusica28) secondlife:///app/agent/5935e159-63ba-415a-b20b-4813b50367d0/about, im baseing off this but im also automating it
|
||||
|
||||
|
|
@ -11,7 +108,7 @@ Each player wears a Peril Dice Tracker HUD
|
|||
|
||||
Everyone starts with 3 lives
|
||||
|
||||
Choose a dice type: d6, d12, or d20
|
||||
Choose a dice type: d6, d12, or d21
|
||||
|
||||
One player clicks "Set Yourself In Peril" on the HUD to start
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue