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
162 lines
6 KiB
Text
162 lines
6 KiB
Text
// === 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.
|
||
|
||
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_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;
|
||
|
||
// List of supported dice sizes; ensure the last entry can accommodate 10 players × 3 picks = 30
|
||
list STANDARD_DICE = [6, 12, 20, 30];
|
||
|
||
// 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);
|
||
}
|
||
|
||
// 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);
|
||
// Pick count = 4 - peril player's lives (3 lives=1 pick, 2 lives=2 picks, 1 life=3 picks)
|
||
return 4 - lifeCount;
|
||
}
|
||
|
||
// Find the index of a player in the pickQueue by name
|
||
integer getPickerIndex(string name) {
|
||
return llListFindList(pickQueue, [name]);
|
||
}
|
||
|
||
// Serialize the current game state for syncing with other scripts
|
||
string serializeGameState() {
|
||
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 = [];
|
||
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];
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
default {
|
||
state_entry() {
|
||
llOwnerSay("🧮 Helper Module ready!");
|
||
}
|
||
|
||
link_message(integer sender, integer num, string str, key id) {
|
||
if (num == MSG_SYNC_GAME_STATE) {
|
||
list parts = llParseString2List(str, ["~"], []);
|
||
lives = llCSV2List(llList2String(parts, 0));
|
||
picksData = llCSV2List(llList2String(parts, 1));
|
||
perilPlayer = llList2String(parts, 2);
|
||
names = llCSV2List(llList2String(parts, 3));
|
||
}
|
||
else if (num == MSG_SYNC_PICKQUEUE) {
|
||
pickQueue = llCSV2List(str);
|
||
}
|
||
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);
|
||
}
|
||
}
|
||
}
|