Peril_Dice/Owner and Player Dialog Handler.lsl
RebeccaNod1 494f01c7bf # Changelog
All notable changes to Peril Dice will be documented in this file.

## [2.1.0] - 2025-01-08

### ✨ Major Features Added
- **Ready State System**: Players must mark themselves ready before games can start
- **Game Protection**: Prevents players from joining games in progress
- **Performance Optimization**: Reduced lag by limiting confetti to wins only
- **Enhanced Bot Intelligence**: Improved bot behavior and duplicate number avoidance

### 🔧 Core Fixes
- **Complete Game Flow**: Fixed end-to-end gameplay from start to finish
- **Elimination Logic**: Proper handling of player elimination and game continuation
- **Win Condition**: Fixed victory detection and game ending
- **Data Synchronization**: Resolved corruption in pick data between scripts
- **Peril Player Assignment**: Fixed race conditions in peril player transitions

### 🎮 Gameplay Improvements
- **Dynamic Picking**: Number of picks now properly based on peril player's lives
- **Duplicate Prevention**: Enhanced system to prevent duplicate number selection
- **Dialog Pagination**: Improved number selection interface for d30 dice
- **Floater Updates**: Real-time HUD updates throughout gameplay
- **Bot Continuation**: Bots now properly continue games when humans are eliminated

### 🛠️ Technical Enhancements
- **Modular Communication**: Improved inter-script messaging system
- **Error Handling**: Comprehensive validation and error recovery
- **Memory Management**: Better floater cleanup and channel tracking
- **Data Encoding**: Fixed CSV conflicts with semicolon encoding system
- **State Management**: Robust game state synchronization across modules

### 🐛 Bug Fixes
- Fixed missing pick dialogs after game events
- Resolved duplicate message issues between scripts
- Fixed lives reset problems between rounds
- Corrected floater display inconsistencies
- Fixed bot confusion during player elimination
- Resolved pick data corruption issues
- Fixed peril player variable sync problems

### ⚡ Performance
- Removed confetti from life loss (keeping only victory confetti)
- Optimized debug output to reduce lag
- Improved floater management efficiency
- Enhanced bot processing speed

## [2.0.0] - 2024-12-XX

### 🎉 Initial Modular Release
- **Modular Architecture**: Separated functionality into specialized scripts
- **Bot System**: Added AI players for testing and gameplay variety
- **Floating HUD**: Real-time player statistics display
- **Dynamic Dice**: Automatic dice sizing based on player count
- **Player Management**: Runtime player joining and leaving
- **Owner Controls**: Administrative functions for game management

### 🎲 Core Game Features
- **Pick System**: Number selection with validation
- **Rolling Mechanism**: Automated dice rolling with visual effects
- **Life Management**: 3-life system with elimination
- **Peril Tracking**: Dynamic peril player assignment
- **Victory Conditions**: Last player standing wins

### 🏗️ Architecture
- **Main Controller**: Central game logic and state management
- **Dialog Handlers**: User interface and input processing
- **Roll Module**: Dice mechanics and particle effects
- **Bot Manager**: AI player behavior and automation
- **Floater Manager**: HUD display coordination
- **Game Helpers**: Utility functions and calculations

## [1.0.0] - 2024-XX-XX

### 🌟 Original Release
- Basic Peril Dice game implementation
- Manual gameplay mechanics
- Simple player tracking
- Basic dice rolling functionality

---

## Development Notes

### Known Issues
- **Bot Race Condition**: Bots may occasionally pick duplicate numbers when triggered simultaneously
- **Debug Output**: Extended games may experience minor lag from comprehensive logging

### Future Enhancements
- Serialized bot picking to eliminate race conditions
- Configurable debug levels
- Additional game modes and variants
- Enhanced visual effects and animations

### Credits
- **Original Game Design**: Noose the Bunny (djmusica28) - Second Life
- **Automation & Development**: Enhanced for automated gameplay
- **Architecture**: Modular LSL system for scalability and maintainability
2025-08-03 04:38:55 -05:00

249 lines
No EOL
10 KiB
Text

// === 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;
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;
integer MSG_TOGGLE_READY = 202;
integer MSG_QUERY_READY_STATE = 210;
integer MSG_READY_STATE_RESULT = 211;
integer MSG_CLEANUP_ALL_FLOATERS = 212;
// 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", "Cleanup Floaters"];
// State tracking for dynamic ready menu
key pendingMenuPlayer = NULL_KEY;
integer pendingMenuIsStarter = FALSE;
integer pendingMenuIsOwner = FALSE;
// Display the debug/owner menu
showOwnerMenu(key id) {
llDialog(id, "🔧 Owner Menu", ownerOptions, DIALOG_CHANNEL);
}
// Display the combined Ready/Leave menu for both players and owners
// If the caller is the starter, the first button is "Start"; otherwise shows dynamic ready state.
// Owners receive an extra "Owner" button to access the advanced owner menu.
showReadyLeaveMenu(key id, integer isStarter, integer isOwner) {
// Store pending menu state and query ready status
pendingMenuPlayer = id;
pendingMenuIsStarter = isStarter;
pendingMenuIsOwner = isOwner;
string playerName = llKey2Name(id);
llMessageLinked(LINK_SET, MSG_QUERY_READY_STATE, playerName, id);
}
// Internal function to show the menu once we have ready state
showReadyLeaveMenuWithState(key id, integer isStarter, integer isOwner, integer isReady, integer isBot) {
// Bots don't get dialogs - they're managed by the owner
if (isBot) {
return;
}
list options;
string menuText = "Select an option:";
if (isStarter) {
options = ["Start Game", "Leave Game"];
menuText = "You are the game starter. You can start the game when all other players are ready.";
} else {
// Dynamic ready button based on current state
if (isReady) {
options = ["Not Ready", "Leave Game"];
menuText = "You are READY to play. Click 'Not Ready' to change your status.";
} else {
options = ["Ready", "Leave Game"];
menuText = "You are NOT READY to play. Click 'Ready' when you want to participate.";
}
}
if (isOwner) {
options += ["Owner"];
}
// Display the menu with contextual information
llDialog(id, menuText, options, DIALOG_CHANNEL);
}
// Manage picks UI helpers remain unchanged
list currentPickList;
string currentPickTarget;
integer currentPickLimit = 3;
showPickManageMenu(key id, list playerNames) {
list options = [];
integer i;
for (i = 0; i < llGetListLength(playerNames); i++) {
string pname = llList2String(playerNames, i);
options += ["🛠 " + pname];
}
options += ["⬅️ Back"];
llDialog(id, "📋 Manage Picks for: ", options, DIALOG_CHANNEL);
}
showPickListMenu(key id) {
list options = [];
integer i;
for (i = 0; i < llGetListLength(currentPickList); i++) {
string pick = llList2String(currentPickList, i);
if (llStringLength(pick) > 0) {
options += ["REMOVE: " + pick];
}
}
options += ["Add Pick", "⬅️ Back"];
llDialog(id, "🛠 Picks for: " + currentPickTarget, options, DIALOG_CHANNEL);
}
askForNewPick(key id) {
llTextBox(id, "🔢 Enter a number to add for " + currentPickTarget + ":", DIALOG_CHANNEL);
}
default {
state_entry() {
llOwnerSay("🎭 Owner and Player Dialog Handler ready!");
llListen(DIALOG_CHANNEL, "", NULL_KEY, "");
}
link_message(integer sender, integer num, string str, key id) {
if (num == MSG_SHOW_MENU) {
llOwnerSay("📨 Received MSG_SHOW_MENU with: " + str);
list args = llParseString2List(str, ["|"], []);
if (llGetListLength(args) < 2) {
llOwnerSay("⚠️ Invalid message format: " + str);
return;
}
string targetType = llList2String(args, 0);
integer isStarter = (integer)llList2String(args, 1);
if (targetType == "owner") {
showReadyLeaveMenu(id, isStarter, TRUE);
} else if (targetType == "player") {
showReadyLeaveMenu(id, isStarter, FALSE);
}
}
else if (num == MSG_READY_STATE_RESULT) {
// Parse ready state response and show appropriate menu
list parts = llParseString2List(str, ["|"], []);
if (llGetListLength(parts) >= 3) {
string playerName = llList2String(parts, 0);
integer isReady = (integer)llList2String(parts, 1);
integer isBot = (integer)llList2String(parts, 2);
// Show menu with the current ready state
if (pendingMenuPlayer == id) {
showReadyLeaveMenuWithState(pendingMenuPlayer, pendingMenuIsStarter, pendingMenuIsOwner, isReady, isBot);
// Reset pending state
pendingMenuPlayer = NULL_KEY;
}
}
return;
}
else if (num == MSG_PLAYER_LIST_RESULT) {
list playerNames = llParseString2List(str, [","], []);
llOwnerSay("📋 Fetching list of players for pick management...");
showPickManageMenu(id, playerNames);
}
else if (num == MSG_PICK_LIST_RESULT) {
list parts = llParseString2List(str, ["|"], []);
string returnedName = llList2String(parts, 0);
string picks = llList2String(parts, 1);
if (llStringTrim(returnedName, STRING_TRIM) == llStringTrim(currentPickTarget, STRING_TRIM)) {
llOwnerSay("✅ Matched pick list for: " + returnedName);
currentPickList = llCSV2List(picks);
llMessageLinked(LINK_THIS, MSG_LIFE_LOOKUP, currentPickTarget, id);
} else {
llOwnerSay("❌ Name mismatch: got " + returnedName + " but expected " + currentPickTarget);
}
}
else if (num == MSG_LIFE_LOOKUP) {
list parts = llParseString2List(str, ["|"], []);
if (llList2String(parts, 0) == currentPickTarget) {
currentPickLimit = (integer)llList2String(parts, 1);
showPickListMenu(id);
}
}
}
listen(integer channel, string name, key id, string msg) {
// 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);
// Floater Manager will automatically rez the floater during registration
}
else if (msg == "Leave Game") {
// Send leave game message to main controller
string pname = llKey2Name(id);
llMessageLinked(LINK_SET, 107, "LEAVE_GAME|" + pname + "|" + (string)id, NULL_KEY);
}
else if (msg == "Ready" || msg == "Not Ready") {
// Toggle ready state (both buttons do the same thing - toggle)
string pname = llKey2Name(id);
llMessageLinked(LINK_SET, MSG_TOGGLE_READY, pname, NULL_KEY);
}
else if (msg == "Cleanup Floaters") {
// Send aggressive floater cleanup command
llOwnerSay("🧹 Sending aggressive floater cleanup command...");
llMessageLinked(LINK_SET, MSG_CLEANUP_ALL_FLOATERS, "", NULL_KEY);
}
else if (msg == "Manage Picks") {
llMessageLinked(LINK_THIS, 202, "REQUEST_PLAYER_LIST", id);
}
else if (llSubStringIndex(msg, "🛠 ") == 0) {
currentPickTarget = llStringTrim(llDeleteSubString(msg, 0, 1), STRING_TRIM);
llOwnerSay("🎯 Pick target set to: " + currentPickTarget);
currentPickList = [];
llMessageLinked(LINK_THIS, 206, currentPickTarget, id);
}
else if (msg == "Add Pick") {
askForNewPick(id);
}
else if (msg == "⬅️ Back") {
llMessageLinked(LINK_THIS, 202, "REQUEST_PLAYER_LIST", id);
}
else if (llSubStringIndex(msg, "REMOVE: ") == 0) {
string pick = llGetSubString(msg, 8, -1);
string payload = "REMOVE_PICK~" + currentPickTarget + "|" + pick;
llMessageLinked(LINK_THIS, MSG_PICK_ACTION, payload, id);
llSleep(0.2);
llMessageLinked(LINK_THIS, 206, currentPickTarget, id);
}
// Forward numeric picks to the game logic
else if ((integer)msg > 0) {
if (llGetListLength(currentPickList) >= currentPickLimit) {
llOwnerSay("⚠️ Reached pick limit of " + (string)currentPickLimit + " for " + currentPickTarget);
showPickListMenu(id);
return;
}
if (llListFindList(currentPickList, [msg]) != -1) {
llOwnerSay("⚠️ Pick already exists: " + msg);
showPickListMenu(id);
return;
}
integer i;
for (i = 0; i < llGetListLength(currentPickList); i++) {
if (llList2String(currentPickList, i) == msg) {
llOwnerSay("⚠️ Duplicate pick detected.");
return;
}
}
string payload = "ADD_PICK~" + currentPickTarget + "|" + msg;
llMessageLinked(LINK_THIS, MSG_PICK_ACTION, payload, id);
llSleep(0.2);
llMessageLinked(LINK_THIS, 206, currentPickTarget, id);
}
}
}