Peril_Dice/Floater Manager.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

237 lines
No EOL
11 KiB
Text
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// === FloatManager (Consolidated) ===
// This version enforces a maximum of 10 players.
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;
// Maximum number of players allowed in the game
integer MAX_PLAYERS = 10;
list players = [];
list names = [];
list lives = [];
list picksData = [];
string perilPlayer = "";
// Returns a list of picks for the given player name, filtering out invalid values
list getPicksFor(string nameInput) {
integer i;
for (i = 0; i < llGetListLength(picksData); i++) {
string entry = llList2String(picksData, i);
if (llSubStringIndex(entry, "|") == -1) {
if (entry != "") llOwnerSay("⚠️ Malformed picks entry (missing pipe): " + entry);
} else {
list parts = llParseString2List(entry, ["|"], []);
if (llGetListLength(parts) < 2) {
if (entry != "") llOwnerSay("⚠️ Malformed picks entry (too few parts): " + entry);
}
else if (llList2String(parts, 0) == nameInput) {
string pickString = llList2String(parts, 1);
if (pickString == "") {
// Empty picks are normal during initialization, don't warn
return [];
} else {
// Check for corruption markers (^ symbols that shouldn't be in picks)
if (llSubStringIndex(pickString, "^") != -1) {
llOwnerSay("⚠️ Corrupted picks data for " + nameInput + " (contains ^): '" + pickString + "'");
return [];
}
// Convert semicolons back to commas, then parse
string convertedPicks = llDumpList2String(llParseString2List(pickString, [";"], []), ",");
list all = llParseString2List(convertedPicks, [","], []);
list filtered = [];
integer j;
for (j = 0; j < llGetListLength(all); j++) {
string val = llStringTrim(llList2String(all, j), STRING_TRIM);
if (val != "" && (string)((integer)val) == val) {
filtered += [val];
} else {
llOwnerSay("⚠️ Invalid number in picks for " + nameInput + ": '" + val + "'");
}
}
return filtered;
}
}
}
}
return [];
}
// Converts a player's key into their name (if registered)
string getNameFromKey(key id) {
integer i = llListFindList(players, [id]);
if (i != -1) return llList2String(names, i);
return (string)id;
}
// Main event handler
default {
state_entry() {
llOwnerSay("📦 Floater Manager ready!");
}
link_message(integer sender, integer num, string str, key id) {
if (num == MSG_REGISTER_PLAYER) {
// Enforce the maximum number of players
if (llGetListLength(players) >= MAX_PLAYERS) {
llOwnerSay("⚠️ Cannot register new player; the game is full (max " + (string)MAX_PLAYERS + ").");
return;
}
// Register a new player: store their name and avatar key
list info = llParseString2List(str, ["|"], []);
string name = llList2String(info, 0);
key avKey = llList2Key(info, 1);
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);
// 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;
integer idx = llListFindList(names, [name]);
if (idx == -1) return;
key avKey = llList2Key(players, idx);
integer ch = -777000 + idx;
integer lifeCount = llList2Integer(lives, idx);
llOwnerSay("🔍 Checking picksData: " + name);
list picks = getPicksFor(name);
llOwnerSay("🛠️ Updating float for " + name);
llOwnerSay("🔎 Found picks: " + llList2CSV(picks));
string perilName;
// 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 {
// perilPlayer is already a name string, not a key
perilName = "🧍 Peril: " + perilPlayer;
}
string picksDisplay = llList2CSV(picks);
string txt = "🎲 Peril Dice\n👤 " + name + "\n❤️ Lives: " + (string)lifeCount + "\n" + perilName + "\n🔢 Picks: " + picksDisplay;
llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt);
llOwnerSay("📤 Sent FLOAT message on channel " + (string)ch + ": " + txt);
}
else if (num == MSG_CLEANUP_FLOAT) {
integer ch = (integer)str;
integer idx = ch - (-777000);
// Always send cleanup message to the channel (even for orphaned floaters)
llRegionSay(ch, "CLEANUP");
llOwnerSay("🧹 Sent CLEANUP to channel " + (string)ch);
// Only clean up internal lists if this corresponds to a valid player
if (idx >= 0 && idx < llGetListLength(players)) {
players = llDeleteSubList(players, idx, idx);
names = llDeleteSubList(names, idx, idx);
lives = llDeleteSubList(lives, idx, idx);
picksData = llDeleteSubList(picksData, idx, idx);
llOwnerSay("🗑️ Cleaned up internal data for player index " + (string)idx);
}
}
else if (num == MSG_SYNC_GAME_STATE) {
// Synchronize the lists for lives and picksData when receiving a new game state
llOwnerSay("🔍 DEBUG: Floater Manager received sync: " + str);
list parts = llParseString2List(str, ["~"], []);
lives = llCSV2List(llList2String(parts, 0));
// Use ^ delimiter for picksData to avoid comma conflicts
string picksDataStr = llList2String(parts, 1);
llOwnerSay("🔍 DEBUG: Raw picksDataStr: '" + picksDataStr + "'");
if (picksDataStr == "" || picksDataStr == "EMPTY") {
picksData = [];
llOwnerSay("🔍 DEBUG: Parsed picksData: (empty)");
} else {
picksData = llParseString2List(picksDataStr, ["^"], []);
llOwnerSay("🔍 DEBUG: Parsed picksData: " + llDumpList2String(picksData, " | "));
}
string receivedPeril = llList2String(parts, 2);
string oldPeril = perilPlayer;
if (receivedPeril == "NONE") {
perilPlayer = ""; // Convert placeholder back to empty
} else {
perilPlayer = receivedPeril;
}
// Debug peril player changes
if (oldPeril != perilPlayer) {
llOwnerSay("🔄 Floater Manager: Peril player changed from '" + oldPeril + "' to '" + perilPlayer + "'");
}
// 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);
}
}
}
}