## [2.4.0] - 2025-08-08

This commit is contained in:
RebeccaNod1 2025-08-07 19:54:36 -05:00
parent f8d711d3c1
commit 0e713136d2
33 changed files with 1736 additions and 2477 deletions

View file

@ -1,6 +1,48 @@
# Changelog
All notable changes to Peril Dice will be documented in this file.
## [2.4.0] - 2025-08-08
### 📍 Position Management System
- **Master-Follower Architecture**: Complete position synchronization system for all game components
- **Position Controller Script**: Main controller object manages movement of scoreboard, leaderboard, and displays
- **Follower Script Template**: Generic follower script that reads position settings from config notecard
- **Config-Based Positioning**: Position offsets and rotations defined in "config" notecard for easy customization
- **Automatic Movement Sync**: When controller moves, all linked objects maintain relative positions
- **Position Reset Tools**: Built-in recalibration system for repositioning knocked-out components
- **Multi-Object Coordination**: Seamless movement of entire game system as single unit
### 🧹 Production Code Cleanup
- **Debug Code Removal**: Complete removal of all debug logging statements and test messages
- Removed `llOwnerSay("DEBUG: ...)` calls throughout Main.lsl (lines 147, 161, 479, 517, 523, 527, 874, 1163)
- Eliminated development-time diagnostic messages and empty debug blocks
- Cleaner, more professional game experience without debug clutter
- **Syntax Error Fixes**:
- Fixed critical missing closing brace on line 835 in Main.lsl causing compilation failure
- Resolved LSL syntax errors that prevented script execution
- All scripts now compile cleanly without warnings or errors
### 🛠️ Development Quality Improvements
- **Production Readiness**: Scripts optimized for live deployment without development artifacts
- **Performance Enhancement**: Eliminated unnecessary debug output reducing execution overhead
- **Memory Optimization**: Reduced script memory footprint by removing debug string operations
- **Code Maintainability**: Cleaner codebase structure for future development
- **Professional Polish**: Game system ready for production deployment
### 🔧 Technical Improvements
- **LSL Compliance**: All scripts pass Second Life LSL compilation standards
- **Execution Efficiency**: Faster script performance without debug processing overhead
- **Position Synchronization**: Robust communication system for coordinated object movement
- **Config System**: Flexible notecard-based configuration for easy position adjustments
- **Error Resolution**: Fixed all syntax issues preventing proper game operation
### 📚 Documentation Updates
- **Position System Guide**: Comprehensive instructions for setting up position management
- **Reset Tools Documentation**: Step-by-step guide for recalibrating positions
- **Version Tracking**: Updated to semantic versioning 2.4.0
- **Change History**: Detailed tracking of all modifications and improvements
## [2.3.0] - 2025-08-05

190
CHANGELOG_Notecard.txt Normal file
View file

@ -0,0 +1,190 @@
PERIL DICE CHANGELOG
===================
All notable changes to Peril Dice are documented in this file.
[2.4.0] - 2025-08-08
====================
📍 POSITION MANAGEMENT SYSTEM
- Master-Follower Architecture: Complete position synchronization system for all game components
- Position Controller Script: Main controller object manages movement of scoreboard, leaderboard, and displays
- Follower Script Template: Generic follower script that reads position settings from config notecard
- Config-Based Positioning: Position offsets and rotations defined in "config" notecard for easy customization
- Automatic Movement Sync: When controller moves, all linked objects maintain relative positions
- Position Reset Tools: Built-in recalibration system for repositioning knocked-out components
- Multi-Object Coordination: Seamless movement of entire game system as single unit
🧹 PRODUCTION CODE CLEANUP
- Debug Code Removal: Complete removal of all debug logging statements and test messages
* Removed llOwnerSay("DEBUG: ...) calls throughout Main.lsl (lines 147, 161, 479, 517, 523, 527, 874, 1163)
* Eliminated development-time diagnostic messages and empty debug blocks
* Cleaner, more professional game experience without debug clutter
- Syntax Error Fixes:
* Fixed critical missing closing brace on line 835 in Main.lsl causing compilation failure
* Resolved LSL syntax errors that prevented script execution
* All scripts now compile cleanly without warnings or errors
🛠️ DEVELOPMENT QUALITY IMPROVEMENTS
- Production Readiness: Scripts optimized for live deployment without development artifacts
- Performance Enhancement: Eliminated unnecessary debug output reducing execution overhead
- Memory Optimization: Reduced script memory footprint by removing debug string operations
- Code Maintainability: Cleaner codebase structure for future development
- Professional Polish: Game system ready for production deployment
🔧 TECHNICAL IMPROVEMENTS
- LSL Compliance: All scripts pass Second Life LSL compilation standards
- Execution Efficiency: Faster script performance without debug processing overhead
- Position Synchronization: Robust communication system for coordinated object movement
- Config System: Flexible notecard-based configuration for easy position adjustments
- Error Resolution: Fixed all syntax issues preventing proper game operation
📚 DOCUMENTATION UPDATES
- Position System Guide: Comprehensive instructions for setting up position management
- Reset Tools Documentation: Step-by-step guide for recalibrating positions
- Version Tracking: Updated to semantic versioning 2.4.0
- Change History: Detailed tracking of all modifications and improvements
[2.3.0] - 2025-08-05
====================
🏆 VISUAL SCOREBOARD SYSTEM
- Dynamic Player Grid: Real-time visual scoreboard displaying all players with profile pictures and heart count
- Profile Picture Integration: Automatic fetching of Second Life avatar profile pictures via HTTP requests
- Heart Texture Display: Visual life representation using custom heart textures (3, 2, 1, 0 hearts)
- Bot Profile Support: Special profile texture for TestBot players
- Immediate Updates: Hearts update instantly when lives change, before any dialogs appear
🎭 ENHANCED STATUS DISPLAY SYSTEM
- Visual Status Actions: Large action prim displaying current game status with custom textures
- Specific Status Messages:
* Direct Hit: When peril player picks their own rolled number
* No Shield: When nobody picks the rolled number
* Plot Twist: When peril switches to a new player
* Elimination: When a player is eliminated
* Victory: When someone wins the game
* Peril Selected: When a new peril player is chosen
* Title: Default/idle state
⚡ PERFECT STATUS TIMING
- Protected Display Time: Each status shows for 8 seconds with automatic clearing
- Strategic Delays: 2-second delays after status messages prevent overwriting
- Sequential Flow: Elimination → 6.4s delay → Victory → 9s delay → Reset
- Immediate Visual Feedback: Hearts and status update instantly, delays protect display time
🎯 COMPREHENSIVE LEADERBOARD
- Persistent Win Tracking: Player victories saved across game sessions
- XyzzyText Integration: Three-prim text display system for leaderboard
- Automatic Sorting: Top players by win count with formatted display
- Separate Reset Options: Game reset vs. leaderboard reset vs. complete reset
🎮 ENHANCED USER INTERFACE
- Categorized Owner Menus: Organized into Game Control, Player Management, Reset Options, etc.
- Dialog Recovery: Players can recover lost dialogs by touching the controller
- Admin Menu Access: Owner can access admin functions during gameplay
- Improved Menu Flow: Streamlined navigation with clear categorization
🔧 TECHNICAL IMPROVEMENTS
- Multi-Object Architecture: Scoreboard and leaderboard as separate linked objects
- HTTP Profile Fetching: Robust system for retrieving avatar profile pictures
- Texture Caching: Profile pictures cached to avoid repeated HTTP requests
- Region-Based Communication: Cross-object messaging using llRegionSay
- Elimination Sequence: Visual 0-hearts display before player removal
🐛 CRITICAL FIXES
- Heart Update Timing: Fixed hearts not updating until after next-turn dialog
- Victory Overwriting Elimination: Added proper delay between elimination and victory status
- Status Message Conflicts: All status messages now have protective delays
- Profile Picture Fallbacks: Proper handling of failed HTTP requests
- Scoreboard Reset: Fixed scoreboard not clearing when game resets
[2.2.0] - 2025-08-04
====================
🎭 MAJOR UX/PRESENTATION IMPROVEMENTS
- Dramatic Messaging System: Complete overhaul of game messages with thematic styling
- Public Announcements: Key game events now visible to all players in public chat
- Context-Rich Roll Messages: Dice rolls now show both dice type and result
- Split Hit Messages: Different messages for "picked own doom" vs "no shield" scenarios
🎮 ENHANCED PLAYER EXPERIENCE
- Player Join Messages: "💀 PlayerName has entered the deadly game! Welcome to your potential doom! 💀"
- Ready State Messages:
* "👑 PlayerName steps forward as the game master - automatically ready for the deadly challenge! 👑"
* "⚔️ PlayerName steels themselves for the deadly challenge ahead! ⚔️"
* "🏃 PlayerName loses their nerve and backs away from the challenge! 🏃"
- Pick Announcements: "🎯 PlayerName stakes their life on numbers: 1, 2, 3 🎲"
- Game Start: "⚡ ALL PARTICIPANTS READY! THE DEADLY PERIL DICE GAME BEGINS! ⚡"
🎲 IMPROVED ROLL MESSAGES
- Main Roll: "🎲 THE D6 OF FATE! PlayerName rolled a 5 on the 6-sided die! 🎲"
- Plot Twist: "⚡ PLOT TWIST! PlayerName picked 5 (rolled on d6) and is now in ULTIMATE PERIL! ⚡"
- Direct Hit: "🩸 DIRECT HIT! PlayerName picked their own doom - the d6 landed on 5! 🩸"
- No Shield: "🩸 NO SHIELD! Nobody picked 5 - PlayerName takes the hit from the d6! 🩸"
🔧 CRITICAL BUG FIXES
- StatFloat Duplication: Fixed bug where new StatFloat objects were created on every life loss instead of updating existing ones
- Duplicate Message Handling: Removed duplicate HUMAN_PICKED handlers that caused double processing
- Malformed Entry Warnings: Fixed LSL parsing issues with empty picks entries
- Cleanup Optimization: Reduced excessive StatFloat cleanup messages during reset
🛠️ TECHNICAL IMPROVEMENTS
- Debug Message Cleanup: Removed excessive debug output while preserving essential error reporting
- Message Handler Optimization: Streamlined communication between scripts
- LSL Parsing Fixes: Better handling of trailing empty elements in data parsing
- Memory Management: Improved StatFloat lifecycle management
[2.1.0] - 2025-08-03
====================
✨ 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
---
DEVELOPMENT NOTES
================
CREDITS
- Original Game Design: Noose the Bunny (djmusica28) - Second Life
- Automation & Development: Enhanced for automated gameplay
- Architecture: Modular LSL system for scalability and maintainability

View file

@ -0,0 +1,101 @@
// Corrected Leaderboard Listener - uses link messages for XyzzyText v2.1
// Place one of these in each leaderboard object alongside XyzzyText v2.1
// Configure MY_BANK for each object: 0, 1, 2, or 3
integer MY_BANK = 0; // Change this for each bank: 0, 1, 2, or 3
integer LEADERBOARD_CHANNEL = -12346; // Match the scoreboard manager channel
integer DISPLAY_STRING = 204000; // XyzzyText constant
// Map bank numbers to message prefixes that scoreboard actually sends
list MESSAGE_PREFIXES = ["LEFT_TEXT", "MIDDLE_LEFT_TEXT", "MIDDLE_RIGHT_TEXT", "RIGHT_TEXT"];
default
{
state_entry()
{
llListen(LEADERBOARD_CHANNEL, "", "", "");
string prefix = llList2String(MESSAGE_PREFIXES, MY_BANK);
llOwnerSay("Corrected Leaderboard Listener Bank " + (string)MY_BANK + " ready");
llOwnerSay("Listening on channel " + (string)LEADERBOARD_CHANNEL);
llOwnerSay("Looking for messages starting with: " + prefix);
llOwnerSay("Will send link messages to XyzzyText on channel " + (string)DISPLAY_STRING);
// Test XyzzyText with simple text first
llSetTimerEvent(3.0);
}
timer()
{
llSetTimerEvent(0.0); // Stop timer
llOwnerSay("=== TESTING XyzzyText with simple text ===");
// Send simple test: "HELLO " (10 chars) to first prim only
llMessageLinked(LINK_SET, DISPLAY_STRING, "HELLO ", (key)((string)0));
llOwnerSay("Sent test text: 'HELLO ' to XyzzyText bank 0");
}
listen(integer channel, string name, key id, string message)
{
if (channel != LEADERBOARD_CHANNEL) return;
// Get the expected message prefix for this bank
string expectedPrefix = llList2String(MESSAGE_PREFIXES, MY_BANK);
// Check if this message is for our bank
if (llSubStringIndex(message, expectedPrefix + "|") == 0)
{
// Extract the text part (everything after the prefix and |)
string text = llGetSubString(message, llStringLength(expectedPrefix) + 1, -1);
llOwnerSay("=== BANK " + (string)MY_BANK + " RECEIVED ===");
llOwnerSay("Message: " + message);
llOwnerSay("Raw text: '" + text + "'");
llOwnerSay("Text length: " + (string)llStringLength(text));
// XyzzyText v2.1 expects one continuous string without newlines
// Split the multi-line text and concatenate into one string
list lines = llParseString2List(text, ["\n"], []);
string continuousText = "";
llOwnerSay("Processing " + (string)llGetListLength(lines) + " lines for XyzzyText");
integer i;
for (i = 0; i < llGetListLength(lines); i++) {
string line = llList2String(lines, i);
// Ensure each line is exactly 10 characters (pad or truncate)
if (llStringLength(line) > 10) {
line = llGetSubString(line, 0, 9); // Truncate to 10 chars
} else if (llStringLength(line) < 10) {
// Pad with spaces to make exactly 10 characters
while (llStringLength(line) < 10) {
line += " ";
}
}
// Add this 10-character line to the continuous string
continuousText += line;
llOwnerSay("Line " + (string)i + ": '" + line + "'");
}
llOwnerSay("Sending continuous text (" + (string)llStringLength(continuousText) + " chars) to XyzzyText");
// Send the continuous string to XyzzyText with bank 0
llMessageLinked(LINK_SET, DISPLAY_STRING, continuousText, (key)((string)0));
llOwnerSay("Sent continuous text to XyzzyText - bank: 0");
}
else if (message == "CLEAR_LEADERBOARD")
{
// Clear display for this bank - send blank lines
llOwnerSay("=== BANK " + (string)MY_BANK + " CLEARING ===");
string clearText = " ";
integer i;
for (i = 1; i < 12; i++) {
clearText += " "; // 12 lines of 10 spaces each = 120 chars total
}
llMessageLinked(LINK_SET, DISPLAY_STRING, clearText, (key)((string)0));
llOwnerSay("Sent clear message to XyzzyText");
}
}
}

View file

@ -0,0 +1,109 @@
// Peril Game Display Follower Script - Config Version
// This script reads position settings from a "config" notecard
// Change DISPLAY_TYPE below to match your display object
string DISPLAY_TYPE = "scoreboard"; // Change to: "scoreboard", "leaderboard", or "dice"
// These will be loaded from config notecard
vector MY_OFFSET;
rotation MY_ROTATION;
integer MY_CHANNEL;
integer MY_DATA_CHANNEL;
// Controller tracking
key master_key;
vector last_master_pos;
rotation last_master_rot;
// Config reading
integer config_line = 0;
key config_query;
default {
state_entry() {
llOwnerSay("Starting " + DISPLAY_TYPE + " follower - reading config...");
// Start reading config notecard
if (llGetInventoryType("config") == INVENTORY_NOTECARD) {
config_query = llGetNotecardLine("config", 0);
config_line = 0;
} else {
llOwnerSay("ERROR: No 'config' notecard found!");
llOwnerSay("Please add a config notecard to this object.");
return;
}
}
dataserver(key query_id, string data) {
if (query_id == config_query) {
if (data == EOF) {
// Finished reading config
llOwnerSay(DISPLAY_TYPE + " config loaded. Listening for controller...");
llListen(MY_CHANNEL, "", "", ""); // Position sync channel
llListen(MY_DATA_CHANNEL, "", "", ""); // Data/scan channel
llRegionSay(MY_CHANNEL, "FOLLOWER_READY|" + DISPLAY_TYPE);
} else if (data != "") {
// Parse config line
data = llStringTrim(data, STRING_TRIM);
if (llGetSubString(data, 0, 0) != "#") { // Skip comments
integer eq_pos = llSubStringIndex(data, "=");
if (eq_pos != -1) {
string config_key = llStringTrim(llGetSubString(data, 0, eq_pos - 1), STRING_TRIM);
string value = llStringTrim(llGetSubString(data, eq_pos + 1, -1), STRING_TRIM);
// Load values for this display type
if (config_key == DISPLAY_TYPE + "_offset") {
MY_OFFSET = (vector)value;
} else if (config_key == DISPLAY_TYPE + "_rotation") {
MY_ROTATION = (rotation)value;
} else if (config_key == DISPLAY_TYPE + "_channel") {
MY_CHANNEL = (integer)value;
} else if (config_key == DISPLAY_TYPE + "_data_channel") {
MY_DATA_CHANNEL = (integer)value;
}
}
}
// Read next line
config_line++;
config_query = llGetNotecardLine("config", config_line);
} else {
// Read next line even if this one is empty
config_line++;
config_query = llGetNotecardLine("config", config_line);
}
}
}
listen(integer channel, string name, key id, string msg) {
if (channel == MY_CHANNEL) {
if (llSubStringIndex(msg, "CONTROLLER_MOVE|") == 0) {
list parts = llParseString2List(msg, ["|"], []);
master_key = (key)llList2String(parts, 1);
vector master_pos = (vector)llList2String(parts, 2);
rotation master_rot = (rotation)llList2String(parts, 3);
// Calculate new position
vector new_pos = master_pos + (MY_OFFSET * master_rot);
rotation new_rot = MY_ROTATION * master_rot;
llSetPos(new_pos);
llSetRot(new_rot);
last_master_pos = master_pos;
last_master_rot = master_rot;
}
} else if (channel == MY_DATA_CHANNEL) {
if (llSubStringIndex(msg, "POSITION_SCAN|") == 0) {
// Respond to position scan for reset tool
llRegionSay(MY_DATA_CHANNEL, "POSITION_RESPONSE|" + DISPLAY_TYPE + "|" +
(string)llGetPos() + "|" + (string)llGetRot());
}
}
}
on_rez(integer start_param) {
llResetScript();
}
}

View file

@ -35,6 +35,7 @@ list playerProfiles = []; // Maps to profile texture UUIDs
// Leaderboard data (persistent - stored in linkset data)
list leaderboardNames = []; // Loaded from linkset data at startup
list leaderboardWins = []; // Loaded from linkset data at startup
list leaderboardLosses = []; // Loaded from linkset data at startup
// HTTP tracking
list profileRequests = []; // Track which profile requests belong to which player
@ -47,17 +48,17 @@ integer profile_key_prefix_length;
integer profile_img_prefix_length;
// Grid layout settings - dual prim approach
float PRIM_WIDTH = 0.2; // Smaller width for individual prims
float PRIM_HEIGHT = 0.3;
float PRIM_WIDTH = .4; // Smaller width for individual prims
float PRIM_HEIGHT = .4;
float PRIM_DEPTH = 0;
float SPACING_X = 0.7; // Space between player pairs
float SPACING_Y = 0.25;
float PRIM_OFFSET = 0.16; // Distance between profile and heart prims
float SPACING_X = 1; // Space between player pairs
float SPACING_Y = .5;
float PRIM_OFFSET = 0.2; // Distance between profile and heart prims
integer GRID_COLS = 2; // 2 columns, 5 rows for 10 players
// Additional prims settings
float BACKGROUND_WIDTH = 4.0;
float BACKGROUND_HEIGHT = 3;
float BACKGROUND_WIDTH = 6;
float BACKGROUND_HEIGHT = 4;
float BACKGROUND_DEPTH = 0.01;
float ACTIONS_WIDTH = 4;
float ACTIONS_HEIGHT = 0.4;
@ -78,7 +79,6 @@ setupPlayerGrid() {
// Create/position prims: 3 UI prims + 10 player slots (20 prims) = 23 prims total
integer totalPrims = llGetNumberOfPrims();
llOwnerSay("DEBUG: Total prims detected: " + (string)totalPrims);
// Need 23 prims total: 1 root + 2 UI (background, actions) + 20 player prims (2 per player)
// Note: Leaderboard is now a separate object
@ -92,7 +92,6 @@ setupPlayerGrid() {
// Setup UI prims first
setupUIPrims();
llOwnerSay("DEBUG: Starting to position " + (string)totalPrims + " prims...");
// Position prims for each player (2 prims per player)
integer i;
@ -101,9 +100,9 @@ setupPlayerGrid() {
integer col = i % GRID_COLS;
// Calculate base position for this player slot (relative to root prim)
vector basePos = <col * SPACING_X - (GRID_COLS-1) * SPACING_X * 1.8,
vector basePos = <col * SPACING_X - (GRID_COLS-1) * SPACING_X * 2.2,
0.0,
row * SPACING_Y - 1.0 * SPACING_Y + -1.5>;
row * SPACING_Y - 2.5 * SPACING_Y + -1.5>;
// Profile prim (left side)
vector profilePos = basePos + <-PRIM_OFFSET, 0.0, 0.0>;
@ -115,7 +114,6 @@ setupPlayerGrid() {
// Set profile prim properties
if (profilePrimIndex <= totalPrims) {
llOwnerSay("DEBUG: Setting profile prim " + (string)profilePrimIndex + " for player " + (string)i);
llSetLinkPrimitiveParamsFast(profilePrimIndex, [
PRIM_POSITION, profilePos,
PRIM_ROTATION, <-0.707107, 0.0, 0.0, 0.707107>, // Back to upright portrait rotation
@ -124,8 +122,6 @@ setupPlayerGrid() {
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, // Fully opaque white
PRIM_TEXT, "", <1,1,1>, 0.0
]);
} else {
llOwnerSay("DEBUG: Skipping profile prim " + (string)profilePrimIndex + " (exceeds total prims)");
}
// Set hearts prim properties
@ -144,8 +140,7 @@ setupPlayerGrid() {
setupUIPrims() {
// Background prim - centered behind the player grid
vector bgPosition = <0.0, 0.04, -1.6>;
llOwnerSay("DEBUG: Setting background prim " + (string)BACKGROUND_PRIM + " to position " + (string)bgPosition);
vector bgPosition = <0.0, 0.05, -2>;
llSetLinkPrimitiveParamsFast(BACKGROUND_PRIM, [
PRIM_POSITION, bgPosition, // Behind the board and higher up
PRIM_ROTATION, <0.0, 0.0, -0.04, 1.0>, // No rotation
@ -167,7 +162,6 @@ setupUIPrims() {
// Note: Leaderboard is now a separate object
llOwnerSay("DEBUG: UI prims setup complete");
}
// Update actions prim with appropriate status texture
@ -191,7 +185,6 @@ updateActionsPrim(string status) {
} else if (status == "Title") {
textureToUse = TEXTURE_TITLE;
} else {
llOwnerSay("DEBUG: Unrecognized status '" + status + "' - keeping current texture");
return; // Don't change texture for unrecognized statuses
}
@ -204,86 +197,122 @@ updateActionsPrim(string status) {
// XyzzyText Communication Constants
integer DISPLAY_STRING = 204000;
integer DISPLAY_EXTENDED = 204001;
integer DICE_CHANNEL = -12347; // Channel for dice roll display
// Generate leaderboard text for triple XyzzyText display (15 characters per line split across 3 prims)
// Generate leaderboard text for quad XyzzyText display (40 characters per line split across 4 prims)
generateLeaderboardText() {
list sortedData = getSortedLeaderboard();
integer maxDisplay = llGetListLength(sortedData);
if (maxDisplay > 11) maxDisplay = 11; // Show top 11 entries to fit in display
integer dataCount = llGetListLength(sortedData);
// Triple prim approach - split lines between left, middle, and right prims
string leftText = "WINS:";
string middleText = " "; // 5 spaces to align with left prim
string rightText = " "; // 5 spaces to align with left prim
// Quad prim approach - split lines between left, middle-left, middle-right, and right prims
// Header: "LEADERBOARD" (11 chars) + spaces to make 40 total, then split into 10-char chunks
string headerLine = "LEADERBOARD "; // Exactly 40 chars: LEADERBOARD (11) + 29 spaces
// Split header into 4 parts of 10 characters each
string leftText = llGetSubString(headerLine, 0, 9); // Characters 0-9: "LEADERBOAR" (first 10 chars)
string middleLeftText = llGetSubString(headerLine, 10, 19); // Characters 10-19: " " (10 spaces)
string middleRightText = llGetSubString(headerLine, 20, 29); // Characters 20-29: " " (10 spaces)
string rightText = llGetSubString(headerLine, 30, 39); // Characters 30-39: " " (10 spaces)
// Show positions 1-11, filling in player data where available
integer i;
for (i = 0; i < maxDisplay; i++) {
list playerData = llParseString2List(llList2String(sortedData, i), [":"], []);
string playerName = llList2String(playerData, 0);
string winsText = llList2String(playerData, 1);
for (i = 0; i < 11; i++) {
string fullLine;
// Create 15-character line: "PlayerNameHere Wins" (player name + padding + wins)
// Truncate name to max 12 characters to leave room for wins
if (llStringLength(playerName) > 12) {
playerName = llGetSubString(playerName, 0, 11);
}
// Format the complete line (15 chars total)
string fullLine = playerName;
// Add padding spaces
integer padding = 13 - llStringLength(playerName); // Leave 2 chars for wins
integer j;
for (j = 0; j < padding; j++) {
fullLine += " ";
}
// Add wins (max 2 digits)
if (llStringLength(winsText) > 2) {
winsText = llGetSubString(winsText, 0, 1); // Truncate to 2 digits
}
fullLine += winsText;
// Ensure exactly 15 characters
if (llStringLength(fullLine) > 15) {
fullLine = llGetSubString(fullLine, 0, 14);
} else if (llStringLength(fullLine) < 15) {
// Pad with spaces if needed
while (llStringLength(fullLine) < 15) {
if (i < dataCount) {
// We have data for this position
list playerData = llParseString2List(llList2String(sortedData, i), [":"], []);
string playerName = llList2String(playerData, 0);
string winsText = llList2String(playerData, 1);
string lossesText = llList2String(playerData, 2);
// Format: "1. PlayerName W:xx L:yy"
// With 40 chars: position(3) + name(27) + stats(10) = 40 total
string position;
if (i < 9) {
position = (string)(i + 1) + ". "; // "1. ", "2. ", etc.
} else {
position = (string)(i + 1) + "."; // "10.", "11." (no extra space)
}
// Format wins and losses with 2-digit limit
if (llStringLength(winsText) > 2) {
winsText = llGetSubString(winsText, 0, 1);
}
if (llStringLength(lossesText) > 2) {
lossesText = llGetSubString(lossesText, 0, 1);
}
string statsText = "W:" + winsText + " L:" + lossesText; // e.g., "W:12 L:3"
// Calculate max name length (40 - position - stats - padding)
integer maxNameLength = 40 - llStringLength(position) - llStringLength(statsText) - 1; // -1 for space
if (llStringLength(playerName) > maxNameLength) {
playerName = llGetSubString(playerName, 0, maxNameLength - 1);
}
fullLine = position + playerName;
// Add padding spaces before stats
integer padding = 40 - llStringLength(fullLine) - llStringLength(statsText);
integer j;
for (j = 0; j < padding && padding > 0; j++) {
fullLine += " ";
}
fullLine += statsText;
} else {
// No data for this position, show empty slot
string position;
if (i < 9) {
position = (string)(i + 1) + ". "; // "1. ", "2. ", etc.
} else {
position = (string)(i + 1) + "."; // "10.", "11." (no extra space)
}
fullLine = position;
// Pad to 40 characters
while (llStringLength(fullLine) < 40) {
fullLine += " ";
}
}
// Split the 15-character line: 5 chars each to left, middle, right prims
string leftPart = llGetSubString(fullLine, 0, 4); // Characters 0-4
string middlePart = llGetSubString(fullLine, 5, 9); // Characters 5-9
string rightPart = llGetSubString(fullLine, 10, 14); // Characters 10-14
// Ensure exactly 40 characters
if (llStringLength(fullLine) > 40) {
fullLine = llGetSubString(fullLine, 0, 39);
} else if (llStringLength(fullLine) < 40) {
while (llStringLength(fullLine) < 40) {
fullLine += " ";
}
}
// Split the 40-character line: 10 chars each to left, middle-left, middle-right, right prims
string leftPart = llGetSubString(fullLine, 0, 9); // Characters 0-9
string middleLeftPart = llGetSubString(fullLine, 10, 19); // Characters 10-19
string middleRightPart = llGetSubString(fullLine, 20, 29); // Characters 20-29
string rightPart = llGetSubString(fullLine, 30, 39); // Characters 30-39
leftText += "\n" + leftPart;
middleText += "\n" + middlePart;
middleLeftText += "\n" + middleLeftPart;
middleRightText += "\n" + middleRightPart;
rightText += "\n" + rightPart;
}
// If no data, show empty message
if (maxDisplay == 0) {
leftText = "NO ";
middleText = "GAMES";
rightText = " ";
}
// We always show exactly 12 lines (header + 11 positions)
integer totalLines = 12;
integer currentLines = 12; // Header + 11 positions
// Send to separate leaderboard object via chat
llOwnerSay("DEBUG: Sending to LEFT XyzzyText: " + leftText);
llOwnerSay("DEBUG: Sending to MIDDLE XyzzyText: " + middleText);
llOwnerSay("DEBUG: Sending to RIGHT XyzzyText: " + rightText);
// Send to leaderboard bridge in separate object
llRegionSay(LEADERBOARD_CHANNEL, "LEFT_TEXT|" + leftText);
llRegionSay(LEADERBOARD_CHANNEL, "MIDDLE_TEXT|" + middleText);
llRegionSay(LEADERBOARD_CHANNEL, "MIDDLE_LEFT_TEXT|" + middleLeftText);
llRegionSay(LEADERBOARD_CHANNEL, "MIDDLE_RIGHT_TEXT|" + middleRightText);
llRegionSay(LEADERBOARD_CHANNEL, "RIGHT_TEXT|" + rightText);
// Also create combined floating text as backup display method
string combinedText = "WINS:\n";
for (i = 0; i < maxDisplay; i++) {
string combinedText = "Leaderboard:\n";
for (i = 0; i < dataCount; i++) {
list playerData = llParseString2List(llList2String(sortedData, i), [":"], []);
string playerName = llList2String(playerData, 0);
string winsText = llList2String(playerData, 1);
@ -292,11 +321,11 @@ generateLeaderboardText() {
playerName = llGetSubString(playerName, 0, 11);
}
combinedText += playerName + ": " + winsText + "\n";
combinedText += (string)(i + 1) + ". " + playerName + ": " + winsText + "\n";
}
if (maxDisplay == 0) {
combinedText = "No games played yet";
if (dataCount == 0) {
combinedText = "Leaderboard: No games played yet";
}
// Note: Leaderboard is now in separate object - no prim to update here
@ -307,6 +336,48 @@ updateLeaderboard(list topPlayers) {
generateLeaderboardText();
}
// Generate dice roll display text for dual XyzzyText display (20 characters total: 10 chars per prim)
generateDiceRollText(string playerName, integer diceValue, string rollType) {
string fullLine;
if (rollType == "CLEAR") {
// Clear the dice display
fullLine = " "; // 20 spaces
} else {
// Format: "Bob rolled 6" or "TestBot1 rolled 12" etc.
string diceStr = (string)diceValue;
string displayName = playerName;
// Calculate available space: 20 total - " rolled " (8) - dice value (1-2 chars) = 11-10 chars for name
integer maxNameLength = 20 - 8 - llStringLength(diceStr); // -8 for " rolled "
if (llStringLength(displayName) > maxNameLength) {
displayName = llGetSubString(displayName, 0, maxNameLength - 1);
}
fullLine = displayName + " rolled " + diceStr;
// Pad to exactly 20 characters
while (llStringLength(fullLine) < 20) {
fullLine += " ";
}
}
// Ensure exactly 20 characters
if (llStringLength(fullLine) > 20) {
fullLine = llGetSubString(fullLine, 0, 19);
}
// Split into 2 parts: 10 chars each for left and right prims
string leftPart = llGetSubString(fullLine, 0, 9); // Characters 0-9
string rightPart = llGetSubString(fullLine, 10, 19); // Characters 10-19
// Send to dice display XyzzyText prims
llRegionSay(DICE_CHANNEL, "DICE_LEFT|" + leftPart);
llRegionSay(DICE_CHANNEL, "DICE_RIGHT|" + rightPart);
}
// Handle game won message
handleGameWon(string winnerName) {
integer playerIndex = llListFindList(leaderboardNames, [winnerName]);
@ -314,6 +385,7 @@ handleGameWon(string winnerName) {
if (playerIndex == -1) {
leaderboardNames += [winnerName];
leaderboardWins += [1];
leaderboardLosses += [0]; // No losses for new winner
} else {
integer currentWins = llList2Integer(leaderboardWins, playerIndex);
leaderboardWins = llListReplaceList(leaderboardWins, [currentWins + 1], playerIndex, playerIndex);
@ -323,6 +395,30 @@ handleGameWon(string winnerName) {
updateLeaderboard(leaderboardNames);
}
// Handle game lost message
handleGameLost(string loserName) {
integer playerIndex = llListFindList(leaderboardNames, [loserName]);
if (playerIndex == -1) {
leaderboardNames += [loserName];
leaderboardWins += [0]; // No wins for new loser
leaderboardLosses += [1];
} else {
integer currentLosses = 0;
if (playerIndex < llGetListLength(leaderboardLosses)) {
currentLosses = llList2Integer(leaderboardLosses, playerIndex);
}
// Extend losses list if needed
while (llGetListLength(leaderboardLosses) <= playerIndex) {
leaderboardLosses += [0];
}
leaderboardLosses = llListReplaceList(leaderboardLosses, [currentLosses + 1], playerIndex, playerIndex);
}
saveLeaderboardData();
updateLeaderboard(leaderboardNames);
}
// Sort leaderboard by wins
list getSortedLeaderboard() {
list combined = [];
@ -331,7 +427,11 @@ list getSortedLeaderboard() {
for (i = 0; i < llGetListLength(leaderboardNames); i++) {
string playerName = llList2String(leaderboardNames, i);
integer wins = llList2Integer(leaderboardWins, i);
combined += [playerName + ":" + (string)wins];
integer losses = 0;
if (i < llGetListLength(leaderboardLosses)) {
losses = llList2Integer(leaderboardLosses, i);
}
combined += [playerName + ":" + (string)wins + ":" + (string)losses];
}
integer n = llGetListLength(combined);
@ -366,8 +466,10 @@ saveLeaderboardData() {
for (i = 0; i < llGetListLength(leaderboardNames); i++) {
string playerName = llList2String(leaderboardNames, i);
integer wins = llList2Integer(leaderboardWins, i);
integer losses = llList2Integer(leaderboardLosses, i);
llLinksetDataWrite("lb_player_" + (string)i, playerName);
llLinksetDataWrite("lb_wins_" + (string)i, (string)wins);
llLinksetDataWrite("lb_losses_" + (string)i, (string)losses);
}
}
@ -379,13 +481,16 @@ loadLeaderboardData() {
integer count = (integer)countStr;
leaderboardNames = [];
leaderboardWins = [];
leaderboardLosses = [];
integer i;
for (i = 0; i < count; i++) {
string playerName = llLinksetDataRead("lb_player_" + (string)i);
string winsStr = llLinksetDataRead("lb_wins_" + (string)i);
string lossesStr = llLinksetDataRead("lb_losses_" + (string)i);
if (playerName != "" && winsStr != "") {
leaderboardNames += [playerName];
leaderboardWins += [(integer)winsStr];
leaderboardLosses += [(integer)lossesStr]; // Default to 0 if empty
}
}
}
@ -394,10 +499,12 @@ loadLeaderboardData() {
resetLeaderboard() {
leaderboardNames = [];
leaderboardWins = [];
leaderboardLosses = [];
integer i;
for (i = 0; i < 100; i++) {
llLinksetDataDelete("lb_player_" + (string)i);
llLinksetDataDelete("lb_wins_" + (string)i);
llLinksetDataDelete("lb_losses_" + (string)i);
}
llLinksetDataDelete("lb_count");
updateLeaderboard([]);
@ -405,7 +512,6 @@ resetLeaderboard() {
// Clear all current players from the scoreboard
clearAllPlayers() {
llOwnerSay("DEBUG: Clearing all players from scoreboard");
// Clear ONLY current game player data lists (NOT leaderboard data)
playerNames = [];
@ -452,6 +558,7 @@ string getHeartTexture(integer lives) {
else return TEXTURE_3_HEARTS; // 3 or more
}
// Update player display on the grid
updatePlayerDisplay(string playerName, integer lives, string profileUUID) {
// Find existing player or add new one
@ -470,14 +577,12 @@ updatePlayerDisplay(string playerName, integer lives, string profileUUID) {
playerLives += [lives];
playerProfiles += [profileUUID];
llOwnerSay("DEBUG: Added new player " + playerName + " at slot " + (string)playerIndex);
} else {
// Update existing player
playerLives = llListReplaceList(playerLives, [lives], playerIndex, playerIndex);
// Don't overwrite cached profile texture with original UUID on updates
// playerProfiles = llListReplaceList(playerProfiles, [profileUUID], playerIndex, playerIndex);
llOwnerSay("DEBUG: Updated player " + playerName + " at slot " + (string)playerIndex + " with " + (string)lives + " lives");
}
// Update the physical prims for this player
@ -494,7 +599,6 @@ updatePlayerDisplay(string playerName, integer lives, string profileUUID) {
cachedTexture != TEXTURE_DEFAULT_PROFILE && cachedTexture != TEXTURE_BOT_PROFILE) {
// We have a previously fetched profile picture, use it
profileTexture = cachedTexture;
llOwnerSay("DEBUG: Using cached profile texture for " + playerName);
}
}
@ -502,21 +606,15 @@ updatePlayerDisplay(string playerName, integer lives, string profileUUID) {
if (profileTexture == profileUUID) {
if (isBot(playerName)) {
profileTexture = TEXTURE_BOT_PROFILE;
llOwnerSay("DEBUG: Using bot profile texture for " + playerName);
} else if (profileTexture == "" || profileTexture == "00000000-0000-0000-0000-000000000000") {
profileTexture = TEXTURE_DEFAULT_PROFILE;
llOwnerSay("DEBUG: Using default profile texture for " + playerName);
} else {
// Request profile picture via HTTP (the correct method!)
llOwnerSay("DEBUG: Requesting profile picture via HTTP for avatar UUID: " + profileUUID);
string URL_RESIDENT = "https://world.secondlife.com/resident/";
key httpRequestID = llHTTPRequest(URL_RESIDENT + profileUUID, [HTTP_METHOD, "GET"], "");
// Store the HTTP request mapping: requestID -> playerIndex
httpRequests += [httpRequestID, playerIndex];
// For now, use the avatar UUID directly as texture
llOwnerSay("DEBUG: Setting temporary profile texture: " + profileTexture);
}
}
@ -546,14 +644,15 @@ default {
llListen(SCOREBOARD_CHANNEL, "", "", "");
llListen(LEADERBOARD_CHANNEL, "", "", "");
llListen(DICE_CHANNEL, "", "", "");
llListen(-999, "", "", ""); // Dialog menu channel
loadLeaderboardData();
setupPlayerGrid();
// Generate initial leaderboard texture
llSetTimerEvent(1.0); // Short delay to ensure prims are set up
llSetTimerEvent(1.0); // Short delay for leaderboard generation
llOwnerSay("Game Scoreboard Manager ready - Grid layout initialized");
llOwnerSay("Touch me to re-run setup.");
llOwnerSay("Game Scoreboard Manager ready - Touch for menu");
llOwnerSay("Use dialog menu to setup prims if needed.");
}
timer() {
@ -562,11 +661,35 @@ default {
}
listen(integer channel, string senderName, key id, string message) {
if (channel == -999) {
// Handle dialog menu responses
if (id == llGetOwner()) {
if (message == "Setup Prims") {
llOwnerSay("Running prim setup...");
setupPlayerGrid();
} else if (message == "Clear Game") {
llOwnerSay("Clearing game...");
clearAllPlayers();
updateActionsPrim("Title");
llRegionSay(LEADERBOARD_CHANNEL, "CLEAR_LEADERBOARD");
generateLeaderboardText();
} else if (message == "Reset Board") {
llOwnerSay("Resetting leaderboard...");
resetLeaderboard();
} else if (message == "Gen Leaderboard") {
llOwnerSay("Generating leaderboard...");
generateLeaderboardText();
} else if (message == "Cancel") {
llOwnerSay("Menu cancelled.");
}
}
return;
}
if (channel == SCOREBOARD_CHANNEL) {
if (llSubStringIndex(message, "PLAYER_UPDATE|") == 0) {
// Handle player updates here if needed
// Format: PLAYER_UPDATE|PlayerName|Lives|ProfileUUID
llOwnerSay("DEBUG: Received player update: " + message);
list parts = llParseString2List(message, ["|"], []);
if (llGetListLength(parts) >= 4) {
@ -581,6 +704,10 @@ default {
// Reset to default state
clearAllPlayers(); // Clear all current players
updateActionsPrim("Title"); // Reset to title instead of "Game Cleared"
// Clear the leaderboard display completely
llRegionSay(LEADERBOARD_CHANNEL, "CLEAR_LEADERBOARD");
generateLeaderboardText(); // Refresh leaderboard display
}
else if (llSubStringIndex(message, "GAME_START|") == 0) {
@ -605,6 +732,24 @@ default {
string winnerName = llList2String(parts, 1);
handleGameWon(winnerName);
}
} else if (llSubStringIndex(message, "GAME_LOST|") == 0) {
list parts = llParseString2List(message, ["|"], []);
if (llGetListLength(parts) >= 2) {
string loserName = llList2String(parts, 1);
handleGameLost(loserName);
}
} else if (llSubStringIndex(message, "DICE_ROLL|") == 0) {
// Handle dice roll display
// Format: DICE_ROLL|PlayerName|DiceValue
list parts = llParseString2List(message, ["|"], []);
if (llGetListLength(parts) >= 3) {
string playerName = llList2String(parts, 1);
integer diceValue = (integer)llList2String(parts, 2);
generateDiceRollText(playerName, diceValue, "SHOW");
}
} else if (llSubStringIndex(message, "CLEAR_DICE") == 0) {
// Clear dice roll display
generateDiceRollText("", 0, "CLEAR");
} else if (llSubStringIndex(message, "RESET_LEADERBOARD") == 0) {
resetLeaderboard();
}
@ -612,10 +757,17 @@ default {
}
touch_start(integer total_number) {
// Owner can touch to manually re-run setup
// Owner can access dialog menu
if (llDetectedKey(0) == llGetOwner()) {
llOwnerSay("Owner touched - re-running setup...");
setupPlayerGrid();
llDialog(llDetectedKey(0), "Scoreboard Manager Options:", [
"Setup Prims",
"Clear Game",
"Reset Board",
"Gen Leaderboard",
"Cancel"
], -999);
} else {
llSay(0, "Only the owner can access this menu.");
}
}
@ -627,11 +779,9 @@ default {
integer playerIndex = llList2Integer(profileRequests, requestIndex + 1);
integer dataType = llList2Integer(profileRequests, requestIndex + 2);
llOwnerSay("DEBUG: Data type " + (string)dataType + " returned: '" + data + "' for player at index " + (string)playerIndex);
// Check if this looks like a valid texture UUID (36 characters, proper format)
if (llStringLength(data) == 36 && llSubStringIndex(data, "-") != -1) {
llOwnerSay("*** FOUND IT! Data type " + (string)dataType + " returned valid UUID: " + data + " ***");
// Update the profile texture for this player
integer profilePrimIndex = FIRST_PLAYER_PRIM + (playerIndex * 2);
@ -641,10 +791,8 @@ default {
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0
]);
llOwnerSay("Profile picture updated successfully for " + llList2String(playerNames, playerIndex));
} else {
// Not a valid UUID, continue testing other data types
llOwnerSay("Data type " + (string)dataType + " did not return valid UUID");
}
// Clean up the completed request (3 elements: requestID, playerIndex, dataType)
@ -658,7 +806,6 @@ default {
if (requestIndex != -1) {
integer playerIndex = llList2Integer(httpRequests, requestIndex + 1);
llOwnerSay("DEBUG: HTTP response received for player at index " + (string)playerIndex + ", status: " + (string)status);
if (status == 200) {
// Try to extract profile picture UUID from HTML
@ -669,14 +816,11 @@ default {
// Second try with img tag
s1 = llSubStringIndex(body, profile_img_prefix);
s1l = profile_img_prefix_length;
llOwnerSay("DEBUG: Trying img tag method for profile picture extraction");
} else {
llOwnerSay("DEBUG: Found meta tag with profile image");
}
if (s1 == -1) {
// Still no match - use default texture
llOwnerSay("DEBUG: No profile picture found in HTML for player " + llList2String(playerNames, playerIndex));
integer profilePrimIndex = FIRST_PLAYER_PRIM + (playerIndex * 2);
llSetLinkPrimitiveParamsFast(profilePrimIndex, [
@ -689,14 +833,12 @@ default {
key profileUUID = llGetSubString(body, s1, s1 + 35);
if (profileUUID == NULL_KEY) {
llOwnerSay("DEBUG: Extracted NULL_KEY, using default texture");
integer profilePrimIndex = FIRST_PLAYER_PRIM + (playerIndex * 2);
llSetLinkPrimitiveParamsFast(profilePrimIndex, [
PRIM_TEXTURE, ALL_SIDES, TEXTURE_DEFAULT_PROFILE, <1,1,0>, <0,0,0>, 0.0,
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0
]);
} else {
llOwnerSay("*** SUCCESS! Found profile picture UUID: " + (string)profileUUID + " for player " + llList2String(playerNames, playerIndex) + " ***");
// Cache the profile picture UUID
playerProfiles = llListReplaceList(playerProfiles, [(string)profileUUID], playerIndex, playerIndex);
@ -710,7 +852,6 @@ default {
}
}
} else {
llOwnerSay("DEBUG: HTTP request failed with status " + (string)status + " for player " + llList2String(playerNames, playerIndex));
// Use default texture on HTTP failure
integer profilePrimIndex = FIRST_PLAYER_PRIM + (playerIndex * 2);

View file

@ -1,56 +0,0 @@
// Leaderboard Bridge - Receives text from Game Scoreboard Manager and sends to XyzzyText
// This script goes in the root prim of your leaderboard object (alongside XyzzyText master)
// Communication channels
integer LEADERBOARD_CHANNEL = -12346; // Listen for messages from scoreboard
// XyzzyText Communication Constants
integer DISPLAY_STRING = 204000;
integer DISPLAY_EXTENDED = 204001;
default {
state_entry() {
llListen(LEADERBOARD_CHANNEL, "", "", "");
llOwnerSay("Leaderboard Bridge ready - listening on channel " + (string)LEADERBOARD_CHANNEL);
// Send initial blank display to all banks
llMessageLinked(LINK_SET, DISPLAY_STRING, " ", "0"); // Bank 0 (left)
llMessageLinked(LINK_SET, DISPLAY_STRING, " ", "1"); // Bank 1 (middle)
llMessageLinked(LINK_SET, DISPLAY_STRING, " ", "2"); // Bank 2 (right)
}
listen(integer channel, string name, key id, string message) {
if (channel == LEADERBOARD_CHANNEL) {
llOwnerSay("DEBUG: Received leaderboard message: " + message);
if (llSubStringIndex(message, "LEFT_TEXT|") == 0) {
// Extract left column text and send to bank 0
string leftText = llGetSubString(message, 10, -1); // Remove "LEFT_TEXT|" prefix
llOwnerSay("DEBUG: Sending to LEFT bank (0): " + leftText);
llMessageLinked(LINK_SET, DISPLAY_STRING, leftText, "0");
} else if (llSubStringIndex(message, "MIDDLE_TEXT|") == 0) {
// Extract middle column text and send to bank 1
string middleText = llGetSubString(message, 12, -1); // Remove "MIDDLE_TEXT|" prefix
llOwnerSay("DEBUG: Sending to MIDDLE bank (1): " + middleText);
llMessageLinked(LINK_SET, DISPLAY_STRING, middleText, "1");
} else if (llSubStringIndex(message, "RIGHT_TEXT|") == 0) {
// Extract right column text and send to bank 2
string rightText = llGetSubString(message, 11, -1); // Remove "RIGHT_TEXT|" prefix
llOwnerSay("DEBUG: Sending to RIGHT bank (2): " + rightText);
llMessageLinked(LINK_SET, DISPLAY_STRING, rightText, "2");
} else if (message == "CLEAR_LEADERBOARD") {
// Clear all displays
llOwnerSay("DEBUG: Clearing all leaderboard displays");
llMessageLinked(LINK_SET, DISPLAY_STRING, " ", "0");
llMessageLinked(LINK_SET, DISPLAY_STRING, " ", "1");
llMessageLinked(LINK_SET, DISPLAY_STRING, " ", "2");
} else {
llOwnerSay("DEBUG: Unrecognized leaderboard message: " + message);
}
}
}
}

View file

@ -0,0 +1,62 @@
////////////////////////////////////////////
// Leaderboard Communication Script - Clean Version
// Based on actual link verification results
////////////////////////////////////////////
integer LEADERBOARD_CHANNEL = -12346;
integer DISPLAY_STRING = 204000;
// Bank assignments based on actual link testing:
list leftmostLinks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; // Links 1-12 = Leftmost bank
list middleLeftLinks = [13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]; // Links 13-24 = Middle-left bank
list middleRightLinks = [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]; // Links 25-36 = Middle-right bank
list rightmostLinks = [37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48]; // Links 37-48 = Rightmost bank
string leftText = "";
string middleLeftText = "";
string middleRightText = "";
string rightText = "";
distributeToBank(string text, list linkNumbers) {
list lines = llParseString2List(text, ["\n"], []);
integer i;
for (i = 0; i < llGetListLength(lines) && i < llGetListLength(linkNumbers); i++) {
string line = llList2String(lines, i);
// Ensure exactly 10 characters
if (llStringLength(line) > 10) {
line = llGetSubString(line, 0, 9);
} else if (llStringLength(line) < 10) {
line = line + llGetSubString(" ", 0, 9 - llStringLength(line));
}
integer linkNum = llList2Integer(linkNumbers, i);
llMessageLinked(linkNum, DISPLAY_STRING, line, "");
}
}
default {
state_entry() {
llOwnerSay("Leaderboard Communication Script ready");
llListen(LEADERBOARD_CHANNEL, "", "", "");
}
listen(integer channel, string name, key id, string message) {
if (channel == LEADERBOARD_CHANNEL) {
if (llGetSubString(message, 0, 9) == "LEFT_TEXT|") {
leftText = llGetSubString(message, 10, -1);
distributeToBank(leftText, leftmostLinks);
} else if (llGetSubString(message, 0, 16) == "MIDDLE_LEFT_TEXT|") {
middleLeftText = llGetSubString(message, 17, -1);
distributeToBank(middleLeftText, middleLeftLinks);
} else if (llGetSubString(message, 0, 17) == "MIDDLE_RIGHT_TEXT|") {
middleRightText = llGetSubString(message, 18, -1);
distributeToBank(middleRightText, middleRightLinks);
} else if (llGetSubString(message, 0, 10) == "RIGHT_TEXT|") {
rightText = llGetSubString(message, 11, -1);
distributeToBank(rightText, rightmostLinks);
}
}
}
}

View file

@ -1,253 +0,0 @@
// Leaderboard Manager - Shows top 10 players ranked by total wins
// Displays as text on a prim face with persistent win tracking
// Communication
integer LEADERBOARD_CHANNEL = -12346;
integer MAIN_CHANNEL = -12340;
// Data storage
list playerNames = [];
list playerWins = [];
// Display settings
integer DISPLAY_FACE = 0; // Which face to show leaderboard text on
vector TEXT_COLOR = <1.0, 1.0, 0.0>; // Yellow text
float TEXT_ALPHA = 1.0;
default {
state_entry() {
// Listen for game results and commands
llListen(LEADERBOARD_CHANNEL, "", "", "");
llListen(MAIN_CHANNEL, "", "", "");
// Load persistent data
loadLeaderboardData();
// Display current leaderboard
updateLeaderboardDisplay();
llOwnerSay("Leaderboard Manager ready - Win tracking active");
}
listen(integer channel, string name, key id, string message) {
if (channel == LEADERBOARD_CHANNEL) {
if (llSubStringIndex(message, "GAME_WON|") == 0) {
handleGameWon(message);
}
else if (message == "RESET_LEADERBOARD") {
resetLeaderboard();
}
else if (message == "SHOW_LEADERBOARD") {
updateLeaderboardDisplay();
}
}
else if (channel == MAIN_CHANNEL) {
// Listen for game end messages from main controller
if (llSubStringIndex(message, "GAME_ENDED|") == 0) {
handleGameEnded(message);
}
}
}
touch_start(integer total_number) {
// Allow touching to refresh leaderboard display
updateLeaderboardDisplay();
llOwnerSay("Leaderboard refreshed");
}
}
handleGameWon(string message) {
// Format: "GAME_WON|PlayerName"
list parts = llParseString2List(message, ["|"], []);
if (llGetListLength(parts) < 2) return;
string winnerName = llList2String(parts, 1);
addWinToPlayer(winnerName);
llOwnerSay("Win recorded for: " + winnerName);
}
handleGameEnded(string message) {
// Format: "GAME_ENDED|WinnerName|PlayerList"
list parts = llParseString2List(message, ["|"], []);
if (llGetListLength(parts) >= 2) {
string winnerName = llList2String(parts, 1);
if (winnerName != "NONE" && winnerName != "") {
addWinToPlayer(winnerName);
llOwnerSay("Game ended - Win recorded for: " + winnerName);
}
}
}
addWinToPlayer(string playerName) {
// Check if player already exists in leaderboard
integer playerIndex = llListFindList(playerNames, [playerName]);
if (playerIndex == -1) {
// New player - add to lists
playerNames += [playerName];
playerWins += [1];
} else {
// Existing player - increment wins
integer currentWins = llList2Integer(playerWins, playerIndex);
playerWins = llListReplaceList(playerWins, [currentWins + 1], playerIndex, playerIndex);
}
// Save data and update display
saveLeaderboardData();
updateLeaderboardDisplay();
}
updateLeaderboardDisplay() {
// Create sorted list of top 10 players
list sortedData = getSortedLeaderboard();
// Build display text
string displayText = "═══ LEADERBOARD ═══\n";
integer i;
integer maxDisplay = llGetListLength(sortedData);
if (maxDisplay > 10) maxDisplay = 10; // Show only top 10
if (maxDisplay == 0) {
displayText += "No games played yet\n";
} else {
for (i = 0; i < maxDisplay; i++) {
list playerData = llParseString2List(llList2String(sortedData, i), [":"], []);
string name = llList2String(playerData, 0);
string wins = llList2String(playerData, 1);
displayText += (string)(i + 1) + ". " + name + " - " + wins + " wins\n";
}
}
displayText += "═══════════════════";
// Set text on prim face
llSetText("", <0,0,0>, 0.0); // Clear floating text
llSetLinkPrimitiveParamsFast(LINK_THIS, [
PRIM_TEXT, displayText, TEXT_COLOR, TEXT_ALPHA
]);
// Also set as texture text if supported
// Note: This would require creating text textures dynamically
// For now, we use floating text above the prim
}
list getSortedLeaderboard() {
// Create combined list of "PlayerName:Wins" for sorting
list combined = [];
integer i;
for (i = 0; i < llGetListLength(playerNames); i++) {
string playerName = llList2String(playerNames, i);
integer wins = llList2Integer(playerWins, i);
combined += [playerName + ":" + (string)wins];
}
// Sort by wins (descending) - bubble sort
integer n = llGetListLength(combined);
integer swapped = TRUE;
while (swapped) {
swapped = FALSE;
for (i = 0; i < n - 1; i++) {
list data1 = llParseString2List(llList2String(combined, i), [":"], []);
list data2 = llParseString2List(llList2String(combined, i + 1), [":"], []);
integer wins1 = (integer)llList2String(data1, 1);
integer wins2 = (integer)llList2String(data2, 1);
if (wins1 < wins2) {
// Swap positions
string temp = llList2String(combined, i);
combined = llListReplaceList(combined, [llList2String(combined, i + 1)], i, i);
combined = llListReplaceList(combined, [temp], i + 1, i + 1);
swapped = TRUE;
}
}
n--;
}
return combined;
}
saveLeaderboardData() {
// Save to linkset data for persistence
integer i;
// Save player count
llLinksetDataWrite("lb_count", (string)llGetListLength(playerNames));
// Save each player's data
for (i = 0; i < llGetListLength(playerNames); i++) {
string playerName = llList2String(playerNames, i);
integer wins = llList2Integer(playerWins, i);
llLinksetDataWrite("lb_player_" + (string)i, playerName);
llLinksetDataWrite("lb_wins_" + (string)i, (string)wins);
}
}
loadLeaderboardData() {
// Load from linkset data
string countStr = llLinksetDataRead("lb_count");
if (countStr == "") return; // No saved data
integer count = (integer)countStr;
playerNames = [];
playerWins = [];
integer i;
for (i = 0; i < count; i++) {
string playerName = llLinksetDataRead("lb_player_" + (string)i);
string winsStr = llLinksetDataRead("lb_wins_" + (string)i);
if (playerName != "" && winsStr != "") {
playerNames += [playerName];
playerWins += [(integer)winsStr];
}
}
llOwnerSay("Loaded leaderboard data: " + (string)llGetListLength(playerNames) + " players");
}
resetLeaderboard() {
// Clear all data
playerNames = [];
playerWins = [];
// Clear persistent storage
integer i;
for (i = 0; i < 100; i++) { // Clear up to 100 possible entries
llLinksetDataDelete("lb_player_" + (string)i);
llLinksetDataDelete("lb_wins_" + (string)i);
}
llLinksetDataDelete("lb_count");
// Update display
updateLeaderboardDisplay();
llOwnerSay("Leaderboard reset - all win data cleared");
}
// Public function to get current standings (for other scripts)
string getLeaderboardString() {
list sortedData = getSortedLeaderboard();
string result = "";
integer i;
integer maxDisplay = llGetListLength(sortedData);
if (maxDisplay > 10) maxDisplay = 10;
for (i = 0; i < maxDisplay; i++) {
list playerData = llParseString2List(llList2String(sortedData, i), [":"], []);
string name = llList2String(playerData, 0);
string wins = llList2String(playerData, 1);
if (i > 0) result += ", ";
result += name + "(" + wins + ")";
}
return result;
}

View file

@ -142,9 +142,6 @@ updateHelpers() {
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, serialized, NULL_KEY);
llMessageLinked(LINK_SET, MSG_SYNC_PICKQUEUE, llList2CSV(pickQueue), NULL_KEY);
// Send a test message first to verify scoreboard communication
llRegionSay(-12345, "GAME_STATUS|Testing communication...");
llOwnerSay("DEBUG: Sent test message to scoreboard");
// Send player updates to scoreboard
integer j;
@ -158,7 +155,6 @@ updateHelpers() {
// Send PLAYER_UPDATE message to scoreboard
string updateMsg = "PLAYER_UPDATE|" + playerName + "|" + (string)playerLives + "|" + profileUUID;
llOwnerSay("DEBUG: Sending to scoreboard: " + updateMsg);
llRegionSay(-12345, updateMsg);
}
}
@ -476,7 +472,6 @@ if (toucher == llGetOwner() && roundStarted) {
// If no active dialogs, just show admin menu directly
if (!hasActiveDialog) {
llOwnerSay("DEBUG: Sending MSG_SHOW_MENU for owner during gameplay");
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|0", toucher);
return;
}
@ -514,17 +509,14 @@ if (toucher == llGetOwner() && roundStarted) {
// If there's a human player before the owner, owner is not starter
if (llSubStringIndex(existingName, "TestBot") != 0) {
isStarter = FALSE;
llOwnerSay("DEBUG: Found human player before owner at index " + (string)k + ": " + existingName);
}
}
// Special case: if owner is at index 0, they are definitely the starter
if (idx == 0) {
isStarter = TRUE;
llOwnerSay("DEBUG: Owner is at index 0, definitely starter");
}
}
// Show the owner menu with the appropriate starter flag
llOwnerSay("DEBUG: Sending MSG_SHOW_MENU for owner, isStarter=" + (string)isStarter + ", idx=" + (string)idx);
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, toucher);
} else if (idx != -1) {
// For non-owner players, check if they're the first human player
@ -840,7 +832,6 @@ resetGame();
integer perilStillInGame = llListFindList(names, [perilPlayer]) != -1;
if (perilStillInGame) {
return; // Don't process this sync update
} else {
}
}
@ -871,9 +862,6 @@ resetGame();
string newPerilPlayer = llList2String(parts, 2);
list newNames = llCSV2List(llList2String(parts, 3));
// Debug peril player changes
if (perilPlayer != newPerilPlayer) {
}
// Update our game state with the new data
lives = newLives;
@ -1160,7 +1148,6 @@ resetGame();
return;
}
else if (msg == "🔧 Admin Menu") {
llOwnerSay("DEBUG: Sending MSG_SHOW_MENU for owner admin during gameplay");
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|0", id);
return;
}

View file

@ -0,0 +1,47 @@
// Position Controller - Master Object
// Add this to your controller object
// When it moves, all follower objects will maintain relative positions
integer SYNC_CHANNEL = -54321;
vector lastPos;
rotation lastRot;
default {
state_entry() {
llOwnerSay("Position Controller active - other objects will follow when this moves");
lastPos = llGetPos();
lastRot = llGetRot();
llSetTimerEvent(0.5); // Check position every 0.5 seconds
}
moving_start() {
// Optional: notify when starting to move
llOwnerSay("Moving game system...");
}
moving_end() {
// When movement stops, broadcast new position
vector pos = llGetPos();
rotation rot = llGetRot();
llRegionSay(SYNC_CHANNEL, "CONTROLLER_MOVE|" + (string)llGetKey() + "|" + (string)pos + "|" + (string)rot);
llOwnerSay("Game system repositioned");
}
timer() {
vector currentPos = llGetPos();
rotation currentRot = llGetRot();
// Check if position or rotation has changed significantly
if (llVecDist(currentPos, lastPos) > 0.01 ||
llAngleBetween(currentRot, lastRot) > 0.01) {
lastPos = currentPos;
lastRot = currentRot;
llRegionSay(SYNC_CHANNEL, "CONTROLLER_MOVE|" + (string)llGetKey() + "|" + (string)currentPos + "|" + (string)currentRot);
}
}
on_rez(integer start_param) {
llResetScript();
}
}

88
Position_Follower_Dev.lsl Normal file
View file

@ -0,0 +1,88 @@
// Position Follower - Development Version
// This calculates the offset automatically and reports it
// Use this to find your perfect spacing, then hardcode the result
key MASTER_KEY;
vector MY_OFFSET;
integer SYNC_CHANNEL = -54321;
integer SETUP_DONE = FALSE;
default {
state_entry() {
llListen(SYNC_CHANNEL, "", "", "");
llOwnerSay("Looking for Controller object...");
// Find the master object (controller)
llSensor("", "", ACTIVE, 96.0, PI); // Scan for all objects
}
sensor(integer num_detected) {
if (!SETUP_DONE) {
integer i;
for (i = 0; i < num_detected; i++) {
string name = llDetectedName(i);
// Look for object with "Controller" or "controller" in the name
if (llSubStringIndex(name, "Controller") >= 0 ||
llSubStringIndex(name, "controller") >= 0 ||
llSubStringIndex(name, "CONTROLLER") >= 0) {
MASTER_KEY = llDetectedKey(i);
// Calculate my offset from master's current position
vector master_pos = llDetectedPos(i);
rotation master_rot = llDetectedRot(i);
vector my_pos = llGetPos();
// Calculate offset in master's local coordinate system
MY_OFFSET = (my_pos - master_pos) / master_rot;
// Get my initial rotation
rotation my_rot = llGetRot();
SETUP_DONE = TRUE;
// IMPORTANT: This is what you need to hardcode in the final version
llOwnerSay("=== OFFSET CALCULATED ===");
llOwnerSay("MY_OFFSET = " + (string)MY_OFFSET + ";");
llOwnerSay("Copy this line into your final script!");
llOwnerSay("========================");
llOwnerSay("=== ROTATION INFO ===");
llOwnerSay("Controller rotation: " + (string)master_rot);
llOwnerSay("My initial rotation: " + (string)my_rot);
llOwnerSay("=====================");
return;
}
}
if (!SETUP_DONE) {
llOwnerSay("Controller object not found! Make sure it has 'controller' in the name.");
llSetTimerEvent(5.0); // Try again in 5 seconds
}
}
}
timer() {
llSetTimerEvent(0.0);
llSensor("", "", ACTIVE, 96.0, PI);
}
no_sensor() {
llOwnerSay("No objects detected nearby. Make sure Controller is within 96m.");
llSetTimerEvent(5.0);
}
listen(integer channel, string name, key id, string message) {
if (SETUP_DONE && id == MASTER_KEY && llSubStringIndex(message, "CONTROLLER_MOVED|") == 0) {
list parts = llParseString2List(message, ["|"], []);
vector master_pos = (vector)llList2String(parts, 1);
rotation master_rot = (rotation)llList2String(parts, 2);
vector new_pos = master_pos + (MY_OFFSET * master_rot);
llSetPos(new_pos);
llSetRot(master_rot);
llOwnerSay("Following controller to: " + (string)new_pos);
}
}
}

View file

@ -0,0 +1,54 @@
PERIL GAME POSITION RESET KIT
=============================
If your leaderboard, scoreboard, or dice display gets knocked out of position,
use this simple tool to recalibrate them.
WHAT'S INCLUDED:
- Position_Reset_Tool.lsl (temporary calibration script)
- config (updated configuration notecard)
HOW TO RECALIBRATE POSITIONS:
============================
1. POSITION YOUR DISPLAYS
- Move each display object to where you want it positioned
- Make sure they're all within 20 meters of the controller
2. DROP RESET TOOL IN CONTROLLER
- Put Position_Reset_Tool.lsl into your controller object
3. SCAN POSITIONS
- Click the controller object
- It will scan and give you new config values like:
scoreboard_offset=<1.234, 5.678, 0.000>
leaderboard_offset=<-2.345, 0.000, 1.500>
dice_offset=<3.456, 0.000, 0.000>
4. UPDATE CONFIG NOTECARDS
- Edit the "config" notecard in each display object
- Replace the old offset values with the new ones
- Save the notecard
5. CLEAN UP
- Remove Position_Reset_Tool.lsl from your controller
- Your displays will automatically move to the new positions!
EXAMPLE:
=======
If the reset tool says:
scoreboard_offset=<0.500, 2.800, 0.100>
Then in your scoreboard's config notecard, change:
scoreboard_offset=<0.000, 3.000, 0.000>
to:
scoreboard_offset=<0.500, 2.800, 0.100>
TROUBLESHOOTING:
===============
- Make sure displays are within 20 meters during scanning
- The config notecard must be named exactly "config" (lowercase)
- After editing config, the objects should move automatically
- If they don't move, try resetting the display objects
That's it! No script editing required - just update the config values!

55
Position_Reset_Tool.lsl Normal file
View file

@ -0,0 +1,55 @@
// Position Reset Tool for Peril Game Display Objects
// Drop this script in your CONTROLLER object and click it
// This will generate config values for your notecard
key controller_key;
vector controller_pos;
rotation controller_rot;
default {
state_entry() {
controller_key = llGetKey();
llOwnerSay("Position Reset Tool ready - Click to scan for display objects");
llListen(-12345, "", "", ""); // Scoreboard responses
llListen(-12346, "", "", ""); // Leaderboard responses
llListen(-12347, "", "", ""); // Dice responses
}
touch_start(integer total_number) {
if (llDetectedKey(0) == llGetOwner()) {
controller_pos = llGetPos();
controller_rot = llGetRot();
llOwnerSay("Scanning for display objects within 20 meters...");
// Send scan messages on all data channels to find display objects
llRegionSay(-12345, "POSITION_SCAN|" + (string)controller_key); // Scoreboard
llRegionSay(-12346, "POSITION_SCAN|" + (string)controller_key); // Leaderboard
llRegionSay(-12347, "POSITION_SCAN|" + (string)controller_key); // Dice
llSetTimerEvent(3.0); // Wait 3 seconds for responses
}
}
listen(integer channel, string name, key id, string msg) {
if ((channel == -12345 || channel == -12346 || channel == -12347) &&
llSubStringIndex(msg, "POSITION_RESPONSE|") == 0) {
list parts = llParseString2List(msg, ["|"], []);
string object_type = llList2String(parts, 1);
vector object_pos = (vector)llList2String(parts, 2);
rotation object_rot = (rotation)llList2String(parts, 3);
// Calculate offset and relative rotation
vector offset = (object_pos - controller_pos) / controller_rot;
rotation rel_rot = object_rot / controller_rot;
llOwnerSay("\n=== " + llToUpper(object_type) + " CONFIG ===");
llOwnerSay(object_type + "_offset=" + (string)offset);
llOwnerSay(object_type + "_rotation=" + (string)rel_rot);
}
}
timer() {
llSetTimerEvent(0.0);
llOwnerSay("\nScan complete. Copy values to your 'config' notecard.");
}
}

View file

@ -33,6 +33,7 @@ Peril Dice is a multiplayer elimination game where each player selects numbers b
- **⚡ Performance Optimized**: Reduced lag with selective particle effects
- **🛡️ Robust Error Handling**: Comprehensive game state synchronization
- **🚫 Game Protection**: Prevents joining games in progress
- **📍 Position Management System**: Coordinated movement of all game components
## Game Rules
@ -86,20 +87,33 @@ Dice type is automatically chosen to ensure at least 3 picks per player:
| 5–6 | d20 |
| 7–10 | d30 |
## Recent Improvements (v2.3.0)
## Recent Improvements (v2.4.0)
### 🏆 Visual Scoreboard Revolution
### 📍 Position Management System
- **Master-Follower Architecture**: Controller object manages position of all game components
- **Automatic Position Sync**: Scoreboard, leaderboard, and displays move with main controller
- **Config-Based Setup**: Position offsets and rotations defined in notecard configuration
- **Position Reset Tools**: Easy recalibration system for repositioning game components
- **Coordinated Movement**: All objects maintain relative positions when game is moved
### 🧹 Production Code Cleanup
- **Debug Code Removal**: Complete removal of all debug logging and test messages for production deployment
- **Syntax Error Fixes**: Resolved critical syntax errors including missing braces in conditional blocks
- **Code Optimization**: Cleaner, more maintainable codebase without development artifacts
- **Performance Enhancement**: Reduced script memory usage and execution overhead
### 🏆 Visual Scoreboard Revolution (v2.3.0)
- **Real-Time Visual Display**: Complete visual overhaul with player grid showing profile pictures and hearts
- **Instant Heart Updates**: Hearts change immediately when lives are lost, before any dialogs appear
- **Profile Picture Fetching**: Automatic HTTP requests to get actual Second Life avatar pictures
- **0-Hearts Display**: Shows elimination sequence visually before player removal
### 🎭 Enhanced Status System
### 🎭 Enhanced Status System (v2.3.0)
- **Specific Status Types**: Separate textures for Direct Hit, No Shield, Plot Twist, Elimination, Victory
- **Perfect Timing**: 8-second display with 2-second protective delays prevent status overwriting
- **Visual Impact**: Large action prim displays current game status with custom textures
### 🎯 Comprehensive Leaderboard
### 🎯 Comprehensive Leaderboard (v2.3.0)
- **Persistent Win Tracking**: Player victories saved across game sessions using linkset data
- **Professional Display**: XyzzyText 3-prim system for clean leaderboard presentation
- **Flexible Reset Options**: Separate commands for game reset, leaderboard reset, or complete reset
@ -108,12 +122,13 @@ Dice type is automatically chosen to ensure at least 3 picks per player:
- **Heart Update Timing**: Fixed hearts not updating until after next-turn dialog
- **Status Conflicts**: All status messages now have protective delays to prevent overwriting
- **Victory/Elimination Flow**: Proper 6.4-second delay between elimination and victory status
- **Syntax Errors**: Fixed missing braces and other LSL compilation issues
## Version
**Current Version**: 2.3.0
**Last Updated**: August 5, 2025
**Status**: Stable - Complete visual scoreboard system with enhanced status display
**Current Version**: 2.4.0
**Last Updated**: August 8, 2025
**Status**: Production Ready - Debug-free codebase with comprehensive visual systems
---

126
README_Notecard.txt Normal file
View file

@ -0,0 +1,126 @@
🎲 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.
KEY FEATURES
============
🏆 VISUAL SCOREBOARD SYSTEM
- Real-Time Player Grid: Visual scoreboard showing all players with profile pictures and hearts
- Profile Picture Integration: Automatic avatar profile fetching from Second Life
- Heart Texture Display: Visual life representation (3, 2, 1, 0 hearts)
- Instant Updates: Hearts change immediately when lives are lost
🎭 ENHANCED STATUS DISPLAY
- Visual Status Actions: Large status prim with custom textures for each game event
- Specific Status Types: Direct Hit, No Shield, Plot Twist, Elimination, Victory, etc.
- Perfect Timing: 8-second display time with protective delays to prevent overwriting
🎯 COMPREHENSIVE LEADERBOARD
- Persistent Win Tracking: Player victories saved across sessions
- XyzzyText Display: Professional 3-prim text system for leaderboard
- Automatic Sorting: Top players by win count
🎮 CORE GAME FEATURES
- 🎯 Dynamic Player Management: Players can join at runtime (owner and other avatars)
- 🤖 Bot Support: Add AI bots for testing and gameplay variety
- 📱 Floating HUD Display: Real-time stats for each player
- 🎮 Intelligent Dice Sizing: Automatic dice size based on player count
- 🔄 Ready State System: Players must be ready before games start
- 🎭 Dramatic Messaging: Immersive thematic announcements visible to all players
- 🎲 Context-Rich Rolls: Detailed dice information with type and result
- ⚡ Performance Optimized: Reduced lag with selective particle effects
- 🛡️ Robust Error Handling: Comprehensive game state synchronization
- 🚫 Game Protection: Prevents joining games in progress
- 📍 Position Management System: Coordinated movement of all game components
GAME RULES
==========
1. Setup: 2-10 players join the game
2. Ready Phase: All players except starter must mark themselves ready
3. Picking Phase: Each player picks 1-3 numbers (based on peril player's remaining lives)
4. Rolling Phase: Peril player rolls the dice
5. Resolution:
- If rolled number matches another player's pick → that player becomes new peril player (⚡ PLOT TWIST!)
- If peril player picked the rolled number → they lose a life (🩸 DIRECT HIT!)
- If nobody picked the rolled number → peril player loses a life (🩸 NO SHIELD!)
6. Elimination: Players with 0 lives are eliminated
7. Victory: Last player standing wins! 🏆
HOW TO PLAY
===========
FOR OWNER:
- Touch object → Access owner menu
- Add Bot → Add AI players for testing
- Start Game → Begin when all players are ready
- Reset Game → Reset to initial state
- Manage Picks → View/modify player selections
FOR PLAYERS:
- Touch object → Register and join game
- Ready/Not Ready → Toggle your ready state
- Pick Numbers → Select your numbers when prompted
- Roll Dice → Roll when you're the peril player
ARCHITECTURE
============
The system uses a modular LSL architecture with inter-script communication:
- Main Controller: Core game logic and state management
- Dialog Handlers: User interface and input processing
- Roll Module: Dice rolling and confetti effects
- Bot Manager: AI player behavior
- Floater Manager: Floating HUD display management
- Game Helpers: Utility functions and dice type calculation
DICE SCALING
============
Dice type is automatically chosen to ensure at least 3 picks per player:
Player Count | Dice Type
-------------|----------
1–2 | d6
3–4 | d12
5–6 | d20
7–10 | d30
RECENT IMPROVEMENTS (V2.4.0)
============================
📍 POSITION MANAGEMENT SYSTEM
- Master-Follower Architecture: Controller object manages position of all game components
- Automatic Position Sync: Scoreboard, leaderboard, and displays move with main controller
- Config-Based Setup: Position offsets and rotations defined in notecard configuration
- Position Reset Tools: Easy recalibration system for repositioning game components
- Coordinated Movement: All objects maintain relative positions when game is moved
🧹 PRODUCTION CODE CLEANUP
- Debug Code Removal: Complete removal of all debug logging and test messages for production deployment
- Syntax Error Fixes: Resolved critical syntax errors including missing braces in conditional blocks
- Code Optimization: Cleaner, more maintainable codebase without development artifacts
- Performance Enhancement: Reduced script memory usage and execution overhead
VERSION INFORMATION
===================
Current Version: 2.4.0
Last Updated: August 8, 2025
Status: Production Ready - Debug-free codebase with comprehensive visual systems
ORIGINAL GAME RULES CREDIT
==========================
Game rules were created by Noose the Bunny (djmusica28) in Second Life. This automated version builds upon their original manual gameplay concept.
ORIGINAL MANUAL RULES SUMMARY:
- Each player starts with 3 lives
- Players pick numbers based on peril player's remaining lives:
- 3 Lives → Pick 1 number
- 2 Lives → Pick 2 numbers
- 1 Life → Pick 3 numbers
- Roll dice to determine outcome
- Last player with lives wins
---
Peril Dice provides hours of entertainment for Second Life communities with its blend of strategy, luck, and social interaction!

View file

@ -120,6 +120,9 @@ default {
// Send message to main controller to get the player's key and show dialog
llMessageLinked(LINK_SET, 997, "START_NEXT_ROUND_DIALOG|" + playerName, NULL_KEY);
} else {
// Clear dice display before showing new roll dialog
llRegionSay(-12345, "CLEAR_DICE");
llOwnerSay("🎲 Prompting " + str + " to roll the dice.");
// Check if this is a bot (TestBot names)
if (llSubStringIndex(str, "TestBot") == 0) {
@ -139,6 +142,9 @@ default {
string resultStr = (string)result;
llSay(0, "🎲 THE D" + (string)diceType + " OF FATE! " + perilPlayer + " rolled a " + resultStr + " on the " + (string)diceType + "-sided die! 🎲");
// Send dice roll to scoreboard for display on dice screen
llRegionSay(-12345, "DICE_ROLL|" + perilPlayer + "|" + resultStr);
string newPeril = "";
integer matched = FALSE;
list matchedPlayers = [];

View file

@ -1,88 +0,0 @@
// XyzzyText Debug Helper
// This script helps debug issues with XyzzyText display
// Constants for XyzzyText communication
integer DISPLAY_STRING = 204000;
integer DISPLAY_EXTENDED = 204001;
integer RESCAN_LINKSET = 204008;
// Communication channel from scoreboard
integer LEADERBOARD_CHANNEL = -12346;
default {
state_entry() {
// Listen for messages from scoreboard
llListen(LEADERBOARD_CHANNEL, "", "", "");
llOwnerSay("XyzzyText Debug Helper started");
// Check linkset info
integer numPrims = llGetNumberOfPrims();
llOwnerSay("DEBUG: Linkset has " + (string)numPrims + " prims");
// List all prim names to verify XyzzyText setup
integer i;
for (i = 1; i <= numPrims; i++) {
string primName = llGetLinkName(i);
llOwnerSay("DEBUG: Prim " + (string)i + " name: '" + primName + "'");
}
// Trigger a rescan of the linkset for XyzzyText
llOwnerSay("DEBUG: Triggering XyzzyText rescan");
llMessageLinked(LINK_SET, RESCAN_LINKSET, "", "");
// Send test text to each bank
llSleep(1.0); // Wait for rescan to complete
llOwnerSay("DEBUG: Sending test text to banks");
llMessageLinked(LINK_SET, DISPLAY_STRING, "TEST0", "0"); // Bank 0 (left)
llMessageLinked(LINK_SET, DISPLAY_STRING, "TEST1", "1"); // Bank 1 (middle)
llMessageLinked(LINK_SET, DISPLAY_STRING, "TEST2", "2"); // Bank 2 (right)
}
listen(integer channel, string name, key id, string message) {
if (channel == LEADERBOARD_CHANNEL) {
llOwnerSay("DEBUG: Received message on channel " + (string)channel + ": " + message);
if (llSubStringIndex(message, "LEFT_TEXT|") == 0) {
string leftText = llGetSubString(message, 10, -1);
llOwnerSay("DEBUG: Got LEFT text: " + leftText);
llMessageLinked(LINK_SET, DISPLAY_STRING, leftText, "0");
}
else if (llSubStringIndex(message, "MIDDLE_TEXT|") == 0) {
string middleText = llGetSubString(message, 12, -1);
llOwnerSay("DEBUG: Got MIDDLE text: " + middleText);
llMessageLinked(LINK_SET, DISPLAY_STRING, middleText, "1");
}
else if (llSubStringIndex(message, "RIGHT_TEXT|") == 0) {
string rightText = llGetSubString(message, 11, -1);
llOwnerSay("DEBUG: Got RIGHT text: " + rightText);
llMessageLinked(LINK_SET, DISPLAY_STRING, rightText, "2");
}
}
}
touch_start(integer total_number) {
// When touched, refresh debug info and test displays
llOwnerSay("--- XyzzyText Debug Refresh ---");
// Check linkset info
integer numPrims = llGetNumberOfPrims();
llOwnerSay("DEBUG: Linkset has " + (string)numPrims + " prims");
// List all prim names
integer i;
for (i = 1; i <= numPrims; i++) {
string primName = llGetLinkName(i);
llOwnerSay("DEBUG: Prim " + (string)i + " name: '" + primName + "'");
}
// Trigger rescan and test displays
llOwnerSay("DEBUG: Triggering XyzzyText rescan");
llMessageLinked(LINK_SET, RESCAN_LINKSET, "", "");
llSleep(1.0);
llOwnerSay("DEBUG: Sending test text to banks");
llMessageLinked(LINK_SET, DISPLAY_STRING, "TEST0", "0");
llMessageLinked(LINK_SET, DISPLAY_STRING, "TEST1", "1");
llMessageLinked(LINK_SET, DISPLAY_STRING, "TEST2", "2");
}
}

118
XyzzyText_Dice_Bridge.lsl Normal file
View file

@ -0,0 +1,118 @@
// XyzzyText Bridge Script - Dice Display Bridge
// This script goes in the ROOT prim of the linked dice display XyzzyText object
// Works with official XyzzyText v2.1 (10-char) script
// Handles dice roll display across 1 bank (20 characters total)
integer DICE_CHANNEL = -12347; // Channel for dice roll display
integer DISPLAY_STRING = 204000;
integer DISPLAY_EXTENDED = 204001;
// Bank configuration for XyzzyText v2.1
// Single bank with 2 prims: xyzzytext-0-0 (chars 1-10), xyzzytext-0-1 (chars 11-20)
integer DICE_BANK = 0; // Single bank for dice display (20 characters total)
// Storage for combining left and right parts
string currentLeftText = "";
string currentRightText = "";
// Function to combine and display the dice text
displayDiceText() {
string fullText = currentLeftText + currentRightText;
// Ensure exactly 20 characters
while (llStringLength(fullText) < 20) {
fullText += " ";
}
if (llStringLength(fullText) > 20) {
fullText = llGetSubString(fullText, 0, 19);
}
// Send to XyzzyText bank
llMessageLinked(LINK_THIS, DISPLAY_STRING, fullText, (key)((string)DICE_BANK));
// Clear the stored parts for next message
currentLeftText = "";
currentRightText = "";
}
default {
state_entry() {
// Listen for dice display messages
llListen(DICE_CHANNEL, "", "", "");
// Initialize variables
currentLeftText = "";
currentRightText = "";
// Initialize XyzzyText bank with blank text (20 spaces)
llMessageLinked(LINK_THIS, DISPLAY_STRING, " ", (key)((string)DICE_BANK));
llOwnerSay("Dice Display Bridge ready - listening on channel " + (string)DICE_CHANNEL);
llOwnerSay("Using dice bank: " + (string)DICE_BANK + " (20 characters across 2 prims)");
llOwnerSay("Make sure prims are named: xyzzytext-0-0, xyzzytext-0-1");
}
listen(integer channel, string senderName, key id, string message) {
if (channel == DICE_CHANNEL) {
if (llSubStringIndex(message, "DICE_LEFT|") == 0) {
// Store the left part (characters 0-9)
currentLeftText = llGetSubString(message, 10, -1); // Remove "DICE_LEFT|" prefix
// Pad to exactly 10 characters
while (llStringLength(currentLeftText) < 10) {
currentLeftText += " ";
}
if (llStringLength(currentLeftText) > 10) {
currentLeftText = llGetSubString(currentLeftText, 0, 9);
}
// If we have both parts, display them
if (currentRightText != "") {
displayDiceText();
}
}
else if (llSubStringIndex(message, "DICE_RIGHT|") == 0) {
// Store the right part (characters 10-19)
currentRightText = llGetSubString(message, 11, -1); // Remove "DICE_RIGHT|" prefix
// Pad to exactly 10 characters
while (llStringLength(currentRightText) < 10) {
currentRightText += " ";
}
if (llStringLength(currentRightText) > 10) {
currentRightText = llGetSubString(currentRightText, 0, 9);
}
// If we have both parts, display them
if (currentLeftText != "") {
displayDiceText();
}
}
}
}
touch_start(integer total_number) {
// Touch for debugging - show current state and test display
if (llDetectedKey(0) == llGetOwner()) {
llOwnerSay("Dice Display Bridge - listening on channel " + (string)DICE_CHANNEL);
llOwnerSay("Total prims in linkset: " + (string)llGetNumberOfPrims());
llOwnerSay("Using dice bank: " + (string)DICE_BANK);
// Test display - 20 characters total
llMessageLinked(LINK_THIS, DISPLAY_STRING, "TEST: Player1 -> 6 ", (key)((string)DICE_BANK));
llOwnerSay("Test display sent. Touch again to clear.");
llSetTimerEvent(3.0); // Clear test after 3 seconds
}
}
timer() {
llSetTimerEvent(0.0); // Stop timer
// Clear test display (20 spaces)
llMessageLinked(LINK_THIS, DISPLAY_STRING, " ", (key)((string)DICE_BANK));
llOwnerSay("Test display cleared.");
}
}

25
config Normal file
View file

@ -0,0 +1,25 @@
# Peril Game Display Configuration
# Edit the values below to adjust your display positions
# Use the Position Reset Tool to generate new values if needed
# Scoreboard display position and rotation
scoreboard_offset=<0.000, 3.000, 0.000>
scoreboard_rotation=<0.000, 0.000, 0.000, 1.000>
# Leaderboard display position and rotation
leaderboard_offset=<-3.000, 0.000, 1.500>
leaderboard_rotation=<0.000, 0.000, 0.000, 1.000>
# Dice display position and rotation
dice_offset=<3.000, 0.000, 0.000>
dice_rotation=<0.000, 0.000, 0.000, 1.000>
# Channel settings (usually don't need to change these)
scoreboard_channel=-54321
leaderboard_channel=-54321
dice_channel=-54321
# Game data channels (separate from position sync)
scoreboard_data_channel=-12345
leaderboard_data_channel=-12346
dice_data_channel=-12347

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
images/Direct Hit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

BIN
images/Elimination.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 MiB

BIN
images/No Shiled.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

BIN
images/Peril Selected.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

BIN
images/PlotTwist.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
images/Punishment Time.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

BIN
images/Victory.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

View file

@ -1,611 +0,0 @@
////////////////////////////////////////////
// XyzzyText v2.1 Script (Set Line Color) by Huney Jewell
// XyzzyText v2.0 Script (5 Face, Single Texture)
//
//
// Edited trivially by Joel Cloquet on 1/2011 to use the (relatively)
// new llSetLinkPrimitiveParamsFast function, thereby removing the need in
// some cases to use the slave script.
//
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Enable prim-label functionality
// Enabled Banking
//
// Modified by Kermitt Quirk 19/01/2006
// To add support for 5 face prim instead of 3
//
// Core XyText Originally Written by Xylor Baysklef
//
//
////////////////////////////////////////////
/////////////// CONSTANTS ///////////////////
// XyText Message Map.
integer DISPLAY_STRING = 204000;
integer DISPLAY_EXTENDED = 204001;
integer REMAP_INDICES = 204002;
integer RESET_INDICES = 204003;
integer SET_FADE_OPTIONS = 204004;
integer SET_FONT_TEXTURE = 204005;
integer SET_LINE_COLOR = 204006;
integer SET_COLOR = 204007;
integer RESCAN_LINKSET = 204008;
//internal API
integer REGISTER_SLAVE = 205000;
integer SLAVE_RECOGNIZED = 205001;
integer SLAVE_DISPLAY = 205003;
integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET = 205005;
// This is an extended character escape sequence.
string ESCAPE_SEQUENCE = "\e";
// This is used to get an index for the extended character.
string EXTENDED_INDEX = "12345";
// Face numbers.
integer FACE_1 = 3;
integer FACE_2 = 7;
integer FACE_3 = 4;
integer FACE_4 = 6;
integer FACE_5 = 1;
// Used to hide the text after a fade-out.
key TRANSPARENT = "701917a8-d614-471f-13dd-5f4644e36e3c";
key null_key = NULL_KEY;
///////////// END CONSTANTS ////////////////
///////////// GLOBAL VARIABLES ///////////////
// This is the key of the font we are displaying.
key gFontTexture = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
// All displayable characters. Default to ASCII order.
string gCharIndex;
// This is whether or not to use the fade in/out special effect.
integer gCellUseFading = FALSE;
// This is how long to display the text before fading out (if using
// fading special effect).
// Note: < 0 means don't fade out.
float gCellHoldDelay = 1.0;
integer gSlaveRegistered;
list gSlaveNames;
integer BANK_STRIDE=3; //offset, length, highest_dirty
list gBankingData;
/////////// END GLOBAL VARIABLES ////////////
ResetCharIndex() {
gCharIndex = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
gCharIndex += "\n\n\n\n\n";
}
vector GetGridOffset(integer index) {
// Calculate the offset needed to display this character.
integer Row = index / 10;
integer Col = index % 10;
// Return the offset in the texture.
return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
}
ShowChars(integer link,vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
// Set the primitive textures directly.
// <-0.256, 0, 0>
// <0, 0, 0>
// <0.130, 0, 0>
// <0, 0, 0>
// <-0.74, 0, 0>
llSetLinkPrimitiveParamsFast( link,[
PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
]);
}
RenderString(integer link, string str) {
// Get the grid positions for each pair of characters.
vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
// Use these grid positions to display the correct textures/offsets.
ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
}
RenderWithEffects(integer link, string str) {
// Get the grid positions for each pair of characters.
vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
// First set the alpha to the lowest possible.
llSetLinkAlpha(link,0.05, ALL_SIDES);
// Use these grid positions to display the correct textures/offsets.
ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5); // Now turn up the alpha until it is at full strength.
float Alpha = 0.10;
for (; Alpha <= 1.0; Alpha += 0.05)
llSetLinkAlpha(link,Alpha, ALL_SIDES);
// See if we want to fade out as well.
if (gCellHoldDelay < 0.0)
// No, bail out. (Just keep showing the string at full strength).
return;
// Hold the text for a while.
llSleep(gCellHoldDelay);
// Now fade out.
for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
llSetLinkAlpha(link,Alpha, ALL_SIDES);
// Make the text transparent to fully hide it.
llSetLinkTexture(link,TRANSPARENT, ALL_SIDES);
}
integer RenderExtended(integer link, string str, integer render) {
// Look for escape sequences.
integer length = 0;
list Parsed = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);
integer ParsedLen = llGetListLength(Parsed);
// Create a list of index values to work with.
list Indices;
// We start with room for 5 indices.
integer IndicesLeft = 5;
string Token;
integer Clipped;
integer LastWasEscapeSequence = FALSE;
// Work from left to right.
integer i = 0;
for (; i < ParsedLen && IndicesLeft > 0; ++i) {
Token = llList2String(Parsed, i);
// If this is an escape sequence, just set the flag and move on.
if (Token == ESCAPE_SEQUENCE) {
LastWasEscapeSequence = TRUE;
}
else { // Token != ESCAPE_SEQUENCE
// Otherwise this is a normal token. Check its length.
Clipped = FALSE;
integer TokenLength = llStringLength(Token);
// Clip if necessary.
if (TokenLength > IndicesLeft) {
TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));
IndicesLeft = 0;
Clipped = TRUE;
}
else
IndicesLeft -= TokenLength;
// Was the previous token an escape sequence?
if (LastWasEscapeSequence) {
// Yes, the first character is an escape character, the rest are normal.
length += 2 + TokenLength;
if(render)
{
// This is the extended character.
Indices += (llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95);
// These are the normal characters.
integer j = 1;
for (; j < TokenLength; ++j)
{
Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));
}
}
}
else { // Normal string.
// Just add the characters normally.
length += TokenLength;
if(render)
{
integer j = 0;
for (; j < TokenLength; ++j)
{
Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));
}
}
}
// Unset this flag, since this was not an escape sequence.
LastWasEscapeSequence = FALSE;
}
}
// Use the indices to create grid positions.
if(render)
{
vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0) );
vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
// Use these grid positions to display the correct textures/offsets.
ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
}
return length;
}
integer ConvertIndex(integer index) {
// This converts from an ASCII based index to our indexing scheme.
if (index >= 32) // ' ' or higher
index -= 32;
else { // index < 32
// Quick bounds check.
if (index > 15)
index = 15;
index += 94; // extended characters
}
return index;
}
PassToRender(integer render,string message, integer bank)
{
// float time;
integer extendedlen = 0;
integer link;
integer i = 0;
integer msgLen = llStringLength(message);
string TextToRender;
integer num_slaves=llGetListLength(gSlaveNames);
string slave_name; //avoids unnecessary casts, keeping it as a string
//get the bank offset and length
integer bank_offset=llList2Integer(gBankingData, (bank * BANK_STRIDE));
integer bank_length=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
integer bank_highest_dirty=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 2);
integer x = 0;
for (;x < msgLen;x = x + 5)
{
if (i >= bank_length) //we don't want to run off the end of the bank
{
//set the dirty to max, and bail out, we're done
gBankingData=llListReplaceList(gBankingData, (list)bank_length, (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
return;
}
link = unpack(gXyTextPrims,(i + bank_offset));
TextToRender = llGetSubString(message, x, x + 15);
if(gSlaveRegistered && (link % (num_slaves + 1)))
{
slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
if (render == 1)
llMessageLinked(LINK_THIS, SLAVE_DISPLAY, TextToRender, (key)((string)link + "," + slave_name));
if (render == 2)
{
//time = llGetAndResetTime();
if(llSubStringIndex(TextToRender,"\e") > x)
extendedlen = 5;
else
extendedlen = RenderExtended(link,TextToRender,0);
if (extendedlen > 5)
{
x += extendedlen - 5;
}
llMessageLinked(LINK_THIS, SLAVE_DISPLAY_EXTENDED, TextToRender, (key)((string)link + "," + slave_name));
// llOwnerSay((string)llGetAndResetTime());
}
//sorry, no fade effect with slave
}
else
{
if (render == 1)
RenderString(link,TextToRender);
if (render == 2)
{
extendedlen = RenderExtended(link,TextToRender,1);
if (extendedlen > 5)
{
x += extendedlen - 5;
}
}
if (render == 3)
RenderWithEffects(link,TextToRender);
}
++i;
}
if (bank_highest_dirty==0)
bank_highest_dirty=bank_length;
integer current_highest_dirty=i;
while (i < bank_highest_dirty)
{
link = unpack(gXyTextPrims,(i + bank_offset));
if(gSlaveRegistered && (link % (num_slaves+1) != 0))
{
slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
llMessageLinked(LINK_THIS, SLAVE_DISPLAY, " ", (key)((string)link + "," + slave_name));
//sorry, no fade effect with slave
}
else
{
RenderString(link," ");
}
++i;
}
gBankingData=llListReplaceList(gBankingData, (list)current_highest_dirty, (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
}
// Bitwise Voodoo by Gigs Taggart and optimized by Strife Onizuka
list gXyTextPrims;
integer get_number_of_prims()
{//ignores avatars.
integer a = llGetNumberOfPrims();
//Mono tweak
vector size = llGetAgentSize(llGetLinkKey(a));
while(size.z > 0)
{
--a;
size = llGetAgentSize(llGetLinkKey(a));
}
return a;
}
//functions to pack 8-bit shorts into ints
list pack_and_insert(list in_list, integer pos, integer value)
{
// //figure out the bitpack position
// integer pack = pos & 3; //4 bytes per int
// pos=pos >> 2;
// integer shifted = value << (pack << 3);
// integer old_value = llList2Integer(in_list, pos);
// shifted = old_value | shifted;
// in_list = llListReplaceList(in_list, (list)shifted, pos, pos);
// return in_list;
//Safe optimized version
integer index = pos >> 2;
return llListReplaceList(in_list, (list)(llList2Integer(in_list, index) | (value << ((pos & 3) << 3))), index, index);
}
integer unpack(list in_list, integer pos)
{
return (llList2Integer(in_list, pos >> 2) >> ((pos & 3) << 3)) & 0x000000FF;//unsigned
// return (llList2Integer(in_list, pos >> 2) << (((~pos) & 3) << 3)) >> 24;//signed
}
change_color(vector color)
{
integer num_prims=llGetListLength(gXyTextPrims) << 2;
integer i = 0;
for (; i<=num_prims; ++i)
{
integer link = unpack(gXyTextPrims,i);
if (!link)
return;
llSetLinkPrimitiveParamsFast( link,[
PRIM_COLOR, FACE_1, color, 1.0,
PRIM_COLOR, FACE_2, color, 1.0,
PRIM_COLOR, FACE_3, color, 1.0,
PRIM_COLOR, FACE_4, color, 1.0,
PRIM_COLOR, FACE_5, color, 1.0
]);
}
}
change_line_color(integer bank, vector color)
{
//get the bank offset and length
integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE));
integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
for (; i < bank_end; ++i)
{
integer link = unpack(gXyTextPrims,i);
if (!link)
return;
llSetLinkPrimitiveParamsFast( link,[
PRIM_COLOR, FACE_1, color, 1.0,
PRIM_COLOR, FACE_2, color, 1.0,
PRIM_COLOR, FACE_3, color, 1.0,
PRIM_COLOR, FACE_4, color, 1.0,
PRIM_COLOR, FACE_5, color, 1.0
]);
}
}
init()
{
integer num_prims=get_number_of_prims();
string link_name;
integer bank=0;
integer prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list.
list temp_bank = [];
integer temp_bank_stride=2;
//FIXME: font texture might should be per-bank
llMessageLinked(LINK_THIS, SET_FONT_TEXTURE, "", gFontTexture);
// moving this before the prim scan so that the slaves properly configure themseves before
// any requests to display
llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key);
gXyTextPrims=[];
integer x=0;
for (;x<64;++x)
{
gXyTextPrims = (gXyTextPrims = []) + gXyTextPrims + 0; //we need to pad out the list to make it easier to add things in any order later
}
gBankingData = [];
@loop;
{
//loop over all prims, looking for ones in the current bank
for(x=0;x<=num_prims;++x)
{
link_name=llGetLinkName(x);
list tmp = llParseString2List(link_name, (list)"-", []);
if(llList2String(tmp,0) == "xyzzytext")
{
if (llList2Integer(tmp,1) == bank)
{
temp_bank += llList2Integer(tmp,2) + (list)x;
}
}
}
if (temp_bank != [])
{
//sort the current bank
temp_bank = llListSort(temp_bank, temp_bank_stride, TRUE);
integer temp_len = llGetListLength(temp_bank);
//store metadata
gBankingData += [prims_pointer, temp_len/temp_bank_stride, 0];
//repack the bank into the prim list
for (x = 0; x < temp_len; x += temp_bank_stride)
{
gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, x + 1));
++prims_pointer;
}
++bank;
temp_bank=[];
jump loop;
}
}
//llOwnerSay((string)llGetFreeMemory());
}
default {
state_entry() {
// Initialize the character index.
ResetCharIndex();
init();
}
on_rez(integer num)
{
llResetScript();
}
link_message(integer sender, integer channel, string data, key id) {
if (id==null_key)
id="0";
if (channel == DISPLAY_STRING) {
PassToRender(1,data, (integer)((string)id));
}
else if (channel == DISPLAY_EXTENDED) {
PassToRender(2,data, (integer)((string)id));
}
else if (channel == REMAP_INDICES) {
// Parse the message, splitting it up into index values.
list Parsed = llCSV2List(data);
integer i = 0;
// Go through the list and swap each pair of indices.
for (; i < llGetListLength(Parsed); i += 2) {
integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
// Swap these index values.
string Value1 = llGetSubString(gCharIndex, Index1, Index1);
string Value2 = llGetSubString(gCharIndex, Index2, Index2);
gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
gCharIndex = llInsertString(gCharIndex, Index1, Value2);
gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
gCharIndex = llInsertString(gCharIndex, Index2, Value1);
}
}
else if (channel == RESCAN_LINKSET)
{
init();
}
else if (channel == RESET_INDICES) {
// Restore the character index back to default settings.
ResetCharIndex();
}
else if (channel == SET_FADE_OPTIONS) {
// Change the channel we listen to for cell commands, the
// starting character position to extract from, and
// special effect attributes.
list Parsed = llCSV2List(data);
gCellUseFading = (integer) llList2String(Parsed, 0);
gCellHoldDelay = (float) llList2String(Parsed, 1);
}
else if (channel == SET_FONT_TEXTURE) {
// Use the new texture instead of the current one.
gFontTexture = id;
}
else if (channel == SET_COLOR) {
change_color((vector)data);
}
else if (channel == SET_LINE_COLOR) {
change_line_color((integer)((string)id), (vector)data);
}
else if (channel == REGISTER_SLAVE)
{
if(!~llListFindList(gSlaveNames, (list)data))
{//isn't registered yet
gSlaveNames += data;
gSlaveRegistered=TRUE;
//llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave(s) Recognized: " + data);
}
// else
// {//it already exists
// llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave, Existing Slave Recognized: " + data);
// }
llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , null_key);
}
}
changed(integer change)
{
if(change&CHANGED_INVENTORY)
{
if(gSlaveRegistered)
{
//by using negative indexes they don't need to be adjusted when an entry is deleted.
integer x = ~llGetListLength(gSlaveNames);
while(++x)
{
if (!~llGetInventoryType(llList2String(gSlaveNames, x)))
{
//llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x));
gSlaveNames = llDeleteSubList(gSlaveNames, x, x);
}
}
gSlaveRegistered = !(gSlaveNames == []);
}
}
}
}

View file

@ -1,521 +0,0 @@
////////////////////////////////////////////
// XyzzyText v2.1 Script (Set Line Color) by Huney Jewell
// Modified to listen directly to scoreboard channel
//
////////////////////////////////////////////
/////////////// CONSTANTS ///////////////////
// XyText Message Map.
integer DISPLAY_STRING = 204000;
integer DISPLAY_EXTENDED = 204001;
integer REMAP_INDICES = 204002;
integer RESET_INDICES = 204003;
integer SET_FADE_OPTIONS = 204004;
integer SET_FONT_TEXTURE = 204005;
integer SET_LINE_COLOR = 204006;
integer SET_COLOR = 204007;
integer RESCAN_LINKSET = 204008;
//internal API
integer REGISTER_SLAVE = 205000;
integer SLAVE_RECOGNIZED = 205001;
integer SLAVE_DISPLAY = 205003;
integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET = 205005;
// Communication channel from scoreboard
integer LEADERBOARD_CHANNEL = -12346;
// This is an extended character escape sequence.
string ESCAPE_SEQUENCE = "\\e";
// This is used to get an index for the extended character.
string EXTENDED_INDEX = "12345";
// Face numbers.
integer FACE_1 = 3;
integer FACE_2 = 7;
integer FACE_3 = 4;
integer FACE_4 = 6;
integer FACE_5 = 1;
// Used to hide the text after a fade-out.
key TRANSPARENT = "701917a8-d614-471f-13dd-5f4644e36e3c";
key null_key = NULL_KEY;
///////////// END CONSTANTS ////////////////
///////////// GLOBAL VARIABLES ///////////////
// This is the key of the font we are displaying.
key gFontTexture = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
// All displayable characters. Default to ASCII order.
string gCharIndex;
// This is whether or not to use the fade in/out special effect.
integer gCellUseFading = FALSE;
// This is how long to display the text before fading out (if using
// fading special effect).
// Note: < 0 means don't fade out.
float gCellHoldDelay = 1.0;
integer gSlaveRegistered;
list gSlaveNames;
integer BANK_STRIDE=3; //offset, length, highest_dirty
list gBankingData;
list gXyTextPrims; //rather than make it generic, we just reuse this from the original script
//////////////////////////////////////////////////////////////
// *** IMPORTANT: SET THIS FOR EACH XYZZYTEXT OBJECT ***
//
// Bank 0 = LEFT display (set MY_BANK = 0;)
// Bank 1 = MIDDLE display (set MY_BANK = 1;)
// Bank 2 = RIGHT display (set MY_BANK = 2;)
//
//////////////////////////////////////////////////////////////
integer MY_BANK = 1; // ← CHANGE THIS NUMBER FOR EACH OBJECT!
/////////// END GLOBAL VARIABLES ////////////
ResetCharIndex() {
gCharIndex = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
gCharIndex += "\\n\\n\\n\\n\\n";
}
vector GetGridOffset(integer index) {
// Calculate the offset needed to display this character.
integer Row = index / 10;
integer Col = index % 10;
// Return the offset in the texture.
return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
}
ShowChars(integer link,vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
// Set the primitive textures directly.
llSetLinkPrimitiveParamsFast( link,[
PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
]);
}
RenderString(integer link, string str) {
// Get the grid positions for each pair of characters.
vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
// Use these grid positions to display the correct textures/offsets.
ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
}
RenderWithEffects(integer link, string str) {
// Get the grid positions for each pair of characters.
vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
// First set the alpha to the lowest possible.
llSetLinkAlpha(link,0.05, ALL_SIDES);
// Use these grid positions to display the correct textures/offsets.
ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5); // Now turn up the alpha until it is at full strength.
float Alpha = 0.10;
for (; Alpha <= 1.0; Alpha += 0.05)
llSetLinkAlpha(link,Alpha, ALL_SIDES);
// See if we want to fade out as well.
if (gCellHoldDelay < 0.0)
// No, bail out. (Just keep showing the string at full strength).
return;
// Hold the text for a while.
llSleep(gCellHoldDelay);
// Now fade out.
for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
llSetLinkAlpha(link,Alpha, ALL_SIDES);
// Make the text transparent to fully hide it.
llSetLinkTexture(link,TRANSPARENT, ALL_SIDES);
}
integer RenderExtended(integer link, string str, integer render) {
// Look for escape sequences.
integer length = 0;
list Parsed = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);
integer ParsedLen = llGetListLength(Parsed);
// Create a list of index values to work with.
list Indices;
// We start with room for 5 indices.
integer IndicesLeft = 5;
string Token;
integer Clipped;
integer LastWasEscapeSequence = FALSE;
// Work from left to right.
integer i = 0;
for (; i < ParsedLen && IndicesLeft > 0; ++i) {
Token = llList2String(Parsed, i);
// If this is an escape sequence, just set the flag and move on.
if (Token == ESCAPE_SEQUENCE) {
LastWasEscapeSequence = TRUE;
}
else { // Token != ESCAPE_SEQUENCE
// Otherwise this is a normal token. Check its length.
Clipped = FALSE;
integer TokenLength = llStringLength(Token);
// Clip if necessary.
if (TokenLength > IndicesLeft) {
TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));
IndicesLeft = 0;
Clipped = TRUE;
}
else
IndicesLeft -= TokenLength;
// Was the previous token an escape sequence?
if (LastWasEscapeSequence) {
// Yes, the first character is an escape character, the rest are normal.
length += 2 + TokenLength;
if(render)
{
// This is the extended character.
Indices += (llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95);
// These are the normal characters.
integer j = 1;
for(; j < TokenLength; ++j) {
Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));
}
}
LastWasEscapeSequence = FALSE;
}
else {
// Nope, just normal characters.
length += TokenLength;
if(render)
{
// Just add the characters normally.
integer j = 0;
for(; j < TokenLength; ++j) {
Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));
}
}
}
// Bail out if this was a clipped token.
if (Clipped)
i = ParsedLen;
}
}
if(render) {
// Make sure we have 5 indices.
integer IndicesCount = llGetListLength(Indices);
for (; IndicesCount < 5; ++IndicesCount) {
Indices += (llSubStringIndex(gCharIndex, " "));
}
// Use the indices to create grid positions.
vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0) );
vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
// Use these grid positions to display the correct textures/offsets.
ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
}
return length;
}
integer ConvertIndex(integer index) {
// This function is used to convert from an ASCII based index to our
// internal representation.
if (index >= 32)
// ASCII character, subtract 32.
return index - 32;
else if (index == 10)
// Newline character.
return 94;
else
// Everything else is a space.
return 0;
}
PassToRender(integer render_type, string message, integer bank) {
if(bank != MY_BANK) {
// This message is not for our bank, ignore it
return;
}
list lines = llParseString2List(message, ["\n"], []);
integer line_count = llGetListLength(lines);
//get the bank offset and length
integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE));
integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
for(; i < bank_end && (i - llList2Integer(gBankingData, (bank * BANK_STRIDE))) < line_count; ++i) {
integer link = unpack(gXyTextPrims, i);
if(link) {
string line = llList2String(lines, i - llList2Integer(gBankingData, (bank * BANK_STRIDE)));
if(render_type == 1) {
RenderString(link, line);
} else if(render_type == 2) {
RenderExtended(link, line, TRUE);
}
}
}
}
integer get_number_of_prims()
{//ignores avatars.
integer a = llGetNumberOfPrims();
//Mono tweak
vector size = llGetAgentSize(llGetLinkKey(a));
while(size.z > 0)
{
--a;
size = llGetAgentSize(llGetLinkKey(a));
}
return a;
}
//functions to pack 8-bit shorts into ints
list pack_and_insert(list in_list, integer pos, integer value)
{
//Safe optimized version
integer index = pos >> 2;
return llListReplaceList(in_list, (list)(llList2Integer(in_list, index) | (value << ((pos & 3) << 3))), index, index);
}
integer unpack(list in_list, integer pos)
{
return (llList2Integer(in_list, pos >> 2) >> ((pos & 3) << 3)) & 0x000000FF;//unsigned
}
change_color(vector color)
{
integer num_prims=llGetListLength(gXyTextPrims) << 2;
integer i = 0;
for (; i<=num_prims; ++i)
{
integer link = unpack(gXyTextPrims,i);
if (!link)
return;
llSetLinkPrimitiveParamsFast( link,[
PRIM_COLOR, FACE_1, color, 1.0,
PRIM_COLOR, FACE_2, color, 1.0,
PRIM_COLOR, FACE_3, color, 1.0,
PRIM_COLOR, FACE_4, color, 1.0,
PRIM_COLOR, FACE_5, color, 1.0
]);
}
}
change_line_color(integer bank, vector color)
{
//get the bank offset and length
integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE));
integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
for (; i < bank_end; ++i)
{
integer link = unpack(gXyTextPrims,i);
if (!link)
return;
llSetLinkPrimitiveParamsFast( link,[
PRIM_COLOR, FACE_1, color, 1.0,
PRIM_COLOR, FACE_2, color, 1.0,
PRIM_COLOR, FACE_3, color, 1.0,
PRIM_COLOR, FACE_4, color, 1.0,
PRIM_COLOR, FACE_5, color, 1.0
]);
}
}
init()
{
integer num_prims=get_number_of_prims();
string link_name;
integer prims_pointer=0;
list temp_bank = [];
integer temp_bank_stride=2;
//FIXME: font texture might should be per-bank
llMessageLinked(LINK_THIS, SET_FONT_TEXTURE, "", gFontTexture);
// moving this before the prim scan so that the slaves properly configure themseves before
// any requests to display
llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key);
gXyTextPrims=[];
integer x=0;
for (;x<64;++x)
{
gXyTextPrims = (gXyTextPrims = []) + gXyTextPrims + 0; //we need to pad out the list to make it easier to add things in any order later
}
gBankingData = [];
// Only look for prims that match MY_BANK
for(x=0;x<=num_prims;++x)
{
link_name=llGetLinkName(x);
list tmp = llParseString2List(link_name, (list)"-", []);
if(llList2String(tmp,0) == "xyzzytext")
{
integer prim_bank = llList2Integer(tmp,1);
integer prim_position = llList2Integer(tmp,2);
if (prim_bank == MY_BANK)
{
temp_bank += prim_position + (list)x;
}
}
}
if (temp_bank != [])
{
//sort the current bank by position
temp_bank = llListSort(temp_bank, temp_bank_stride, TRUE);
integer temp_len = llGetListLength(temp_bank);
//store metadata for MY_BANK at position MY_BANK in the banking data
while(llGetListLength(gBankingData) <= (MY_BANK * BANK_STRIDE + 2)) {
gBankingData += [0, 0, 0]; // pad the list
}
gBankingData = llListReplaceList(gBankingData, [prims_pointer, temp_len/temp_bank_stride, 0],
MY_BANK * BANK_STRIDE, MY_BANK * BANK_STRIDE + 2);
//repack the bank into the prim list
for (x = 0; x < temp_len; x += temp_bank_stride)
{
integer link_num = llList2Integer(temp_bank, x + 1);
gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, link_num);
++prims_pointer;
}
}
llOwnerSay("XyzzyText Bank " + (string)MY_BANK + " ready with " + (string)prims_pointer + " prims");
}
default {
state_entry() {
// Initialize the character index.
ResetCharIndex();
// Listen for messages from scoreboard
llListen(LEADERBOARD_CHANNEL, "", "", "");
init();
}
on_rez(integer num)
{
llResetScript();
}
listen(integer channel, string name, key id, string message) {
if (channel == LEADERBOARD_CHANNEL) {
if (MY_BANK == 0 && llSubStringIndex(message, "LEFT_TEXT|") == 0) {
// This is bank 0 (left), process LEFT_TEXT messages
string leftText = llGetSubString(message, 10, -1);
PassToRender(1, leftText, 0);
} else if (MY_BANK == 1 && llSubStringIndex(message, "MIDDLE_TEXT|") == 0) {
// This is bank 1 (middle), process MIDDLE_TEXT messages
string middleText = llGetSubString(message, 12, -1);
PassToRender(1, middleText, 1);
} else if (MY_BANK == 2 && llSubStringIndex(message, "RIGHT_TEXT|") == 0) {
// This is bank 2 (right), process RIGHT_TEXT messages
string rightText = llGetSubString(message, 11, -1);
PassToRender(1, rightText, 2);
} else if (message == "CLEAR_LEADERBOARD") {
// Clear display for this bank
PassToRender(1, " ", MY_BANK);
}
}
}
link_message(integer sender, integer channel, string data, key id) {
if (id==null_key)
id="0";
if (channel == DISPLAY_STRING) {
PassToRender(1,data, (integer)((string)id));
}
else if (channel == DISPLAY_EXTENDED) {
PassToRender(2,data, (integer)((string)id));
}
else if (channel == REMAP_INDICES) {
// Parse the message, splitting it up into index values.
list Parsed = llCSV2List(data);
integer i = 0;
// Go through the list and swap each pair of indices.
for (; i < llGetListLength(Parsed); i += 2) {
integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
// Swap these index values.
string Value1 = llGetSubString(gCharIndex, Index1, Index1);
string Value2 = llGetSubString(gCharIndex, Index2, Index2);
gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
gCharIndex = llInsertString(gCharIndex, Index1, Value2);
gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
gCharIndex = llInsertString(gCharIndex, Index2, Value1);
}
}
else if (channel == RESCAN_LINKSET)
{
init();
}
else if (channel == RESET_INDICES) {
// Restore the character index back to default settings.
ResetCharIndex();
}
else if (channel == SET_FADE_OPTIONS) {
list Parsed = llCSV2List(data);
gCellUseFading = llList2Integer(Parsed, 0);
gCellHoldDelay = llList2Float(Parsed, 1);
}
else if (channel == SET_FONT_TEXTURE) {
gFontTexture = id;
}
else if (channel == SET_LINE_COLOR) {
change_line_color((integer)data, (vector)((string)id));
}
else if (channel == SET_COLOR) {
change_color((vector)data);
}
else if (channel == REGISTER_SLAVE) {
gSlaveNames += data;
llMessageLinked(sender, SLAVE_RECOGNIZED, "", "");
}
}
}

View file

@ -1,540 +0,0 @@
////////////////////////////////////////////
// XyzzyText v2.1 Script (Set Line Color) by Huney Jewell
// Modified to listen directly to scoreboard channel
//
////////////////////////////////////////////
/////////////// CONSTANTS ///////////////////
// XyText Message Map.
integer DISPLAY_STRING = 204000;
integer DISPLAY_EXTENDED = 204001;
integer REMAP_INDICES = 204002;
integer RESET_INDICES = 204003;
integer SET_FADE_OPTIONS = 204004;
integer SET_FONT_TEXTURE = 204005;
integer SET_LINE_COLOR = 204006;
integer SET_COLOR = 204007;
integer RESCAN_LINKSET = 204008;
//internal API
integer REGISTER_SLAVE = 205000;
integer SLAVE_RECOGNIZED = 205001;
integer SLAVE_DISPLAY = 205003;
integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET = 205005;
// Communication channel from scoreboard
integer LEADERBOARD_CHANNEL = -12346;
// This is an extended character escape sequence.
string ESCAPE_SEQUENCE = "\\e";
// This is used to get an index for the extended character.
string EXTENDED_INDEX = "12345";
// Face numbers.
integer FACE_1 = 3;
integer FACE_2 = 7;
integer FACE_3 = 4;
integer FACE_4 = 6;
integer FACE_5 = 1;
// Used to hide the text after a fade-out.
key TRANSPARENT = "701917a8-d614-471f-13dd-5f4644e36e3c";
key null_key = NULL_KEY;
///////////// END CONSTANTS ////////////////
///////////// GLOBAL VARIABLES ///////////////
// This is the key of the font we are displaying.
key gFontTexture = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
// All displayable characters. Default to ASCII order.
string gCharIndex;
// This is whether or not to use the fade in/out special effect.
integer gCellUseFading = FALSE;
// This is how long to display the text before fading out (if using
// fading special effect).
// Note: < 0 means don't fade out.
float gCellHoldDelay = 1.0;
integer gSlaveRegistered;
list gSlaveNames;
integer BANK_STRIDE=3; //offset, length, highest_dirty
list gBankingData;
list gXyTextPrims; //rather than make it generic, we just reuse this from the original script
// Bank configuration - SET THIS FOR EACH OBJECT
integer MY_BANK = 1; // Change this: 0 for left, 1 for middle, 2 for right
/////////// END GLOBAL VARIABLES ////////////
ResetCharIndex() {
gCharIndex = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
gCharIndex += "\\n\\n\\n\\n\\n";
}
vector GetGridOffset(integer index) {
// Calculate the offset needed to display this character.
integer Row = index / 10;
integer Col = index % 10;
// Return the offset in the texture.
return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
}
ShowChars(integer link,vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
// Set the primitive textures directly.
// <-0.256, 0, 0>
// <0, 0, 0>
// <0.130, 0, 0>
// <0, 0, 0>
// <-0.74, 0, 0>
llSetLinkPrimitiveParamsFast( link,[
PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
]);
}
RenderString(integer link, string str) {
// Get the grid positions for each pair of characters.
vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
// Use these grid positions to display the correct textures/offsets.
ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
}
RenderWithEffects(integer link, string str) {
// Get the grid positions for each pair of characters.
vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
// First set the alpha to the lowest possible.
llSetLinkAlpha(link,0.05, ALL_SIDES);
// Use these grid positions to display the correct textures/offsets.
ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5); // Now turn up the alpha until it is at full strength.
float Alpha = 0.10;
for (; Alpha <= 1.0; Alpha += 0.05)
llSetLinkAlpha(link,Alpha, ALL_SIDES);
// See if we want to fade out as well.
if (gCellHoldDelay < 0.0)
// No, bail out. (Just keep showing the string at full strength).
return;
// Hold the text for a while.
llSleep(gCellHoldDelay);
// Now fade out.
for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
llSetLinkAlpha(link,Alpha, ALL_SIDES);
// Make the text transparent to fully hide it.
llSetLinkTexture(link,TRANSPARENT, ALL_SIDES);
}
integer RenderExtended(integer link, string str, integer render) {
// Look for escape sequences.
integer length = 0;
list Parsed = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);
integer ParsedLen = llGetListLength(Parsed);
// Create a list of index values to work with.
list Indices;
// We start with room for 5 indices.
integer IndicesLeft = 5;
string Token;
integer Clipped;
integer LastWasEscapeSequence = FALSE;
// Work from left to right.
integer i = 0;
for (; i < ParsedLen && IndicesLeft > 0; ++i) {
Token = llList2String(Parsed, i);
// If this is an escape sequence, just set the flag and move on.
if (Token == ESCAPE_SEQUENCE) {
LastWasEscapeSequence = TRUE;
}
else { // Token != ESCAPE_SEQUENCE
// Otherwise this is a normal token. Check its length.
Clipped = FALSE;
integer TokenLength = llStringLength(Token);
// Clip if necessary.
if (TokenLength > IndicesLeft) {
TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));
IndicesLeft = 0;
Clipped = TRUE;
}
else
IndicesLeft -= TokenLength;
// Was the previous token an escape sequence?
if (LastWasEscapeSequence) {
// Yes, the first character is an escape character, the rest are normal.
length += 2 + TokenLength;
if(render)
{
// This is the extended character.
Indices += (llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95);
// These are the normal characters.
integer j = 1;
for(; j < TokenLength; ++j) {
Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));
}
}
LastWasEscapeSequence = FALSE;
}
else {
// Nope, just normal characters.
length += TokenLength;
if(render)
{
// Just add the characters normally.
integer j = 0;
for(; j < TokenLength; ++j) {
Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));
}
}
}
// Bail out if this was a clipped token.
if (Clipped)
i = ParsedLen;
}
}
if(render) {
// Make sure we have 5 indices.
integer IndicesCount = llGetListLength(Indices);
for (; IndicesCount < 5; ++IndicesCount) {
Indices += (llSubStringIndex(gCharIndex, " "));
}
// Use the indices to create grid positions.
vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0) );
vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
// Use these grid positions to display the correct textures/offsets.
ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
}
return length;
}
integer ConvertIndex(integer index) {
// This function is used to convert from an ASCII based index to our
// internal representation.
if (index >= 32)
// ASCII character, subtract 32.
return index - 32;
else if (index == 10)
// Newline character.
return 94;
else
// Everything else is a space.
return 0;
}
PassToRender(integer render_type, string message, integer bank) {
if(bank != MY_BANK) {
// This message is not for our bank, ignore it
return;
}
llOwnerSay("DEBUG: Bank " + (string)MY_BANK + " received message: " + message);
list lines = llParseString2List(message, ["\n"], []);
integer line_count = llGetListLength(lines);
//get the bank offset and length
integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE));
integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
for(; i < bank_end && (i - llList2Integer(gBankingData, (bank * BANK_STRIDE))) < line_count; ++i) {
integer link = unpack(gXyTextPrims, i);
if(link) {
string line = llList2String(lines, i - llList2Integer(gBankingData, (bank * BANK_STRIDE)));
if(render_type == 1) {
RenderString(link, line);
} else if(render_type == 2) {
RenderExtended(link, line, TRUE);
}
}
}
}
integer get_number_of_prims()
{//ignores avatars.
integer a = llGetNumberOfPrims();
//Mono tweak
vector size = llGetAgentSize(llGetLinkKey(a));
while(size.z > 0)
{
--a;
size = llGetAgentSize(llGetLinkKey(a));
}
return a;
}
//functions to pack 8-bit shorts into ints
list pack_and_insert(list in_list, integer pos, integer value)
{
//Safe optimized version
integer index = pos >> 2;
return llListReplaceList(in_list, (list)(llList2Integer(in_list, index) | (value << ((pos & 3) << 3))), index, index);
}
integer unpack(list in_list, integer pos)
{
return (llList2Integer(in_list, pos >> 2) >> ((pos & 3) << 3)) & 0x000000FF;//unsigned
}
change_color(vector color)
{
integer num_prims=llGetListLength(gXyTextPrims) << 2;
integer i = 0;
for (; i<=num_prims; ++i)
{
integer link = unpack(gXyTextPrims,i);
if (!link)
return;
llSetLinkPrimitiveParamsFast( link,[
PRIM_COLOR, FACE_1, color, 1.0,
PRIM_COLOR, FACE_2, color, 1.0,
PRIM_COLOR, FACE_3, color, 1.0,
PRIM_COLOR, FACE_4, color, 1.0,
PRIM_COLOR, FACE_5, color, 1.0
]);
}
}
change_line_color(integer bank, vector color)
{
//get the bank offset and length
integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE));
integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
for (; i < bank_end; ++i)
{
integer link = unpack(gXyTextPrims,i);
if (!link)
return;
llSetLinkPrimitiveParamsFast( link,[
PRIM_COLOR, FACE_1, color, 1.0,
PRIM_COLOR, FACE_2, color, 1.0,
PRIM_COLOR, FACE_3, color, 1.0,
PRIM_COLOR, FACE_4, color, 1.0,
PRIM_COLOR, FACE_5, color, 1.0
]);
}
}
init()
{
integer num_prims=get_number_of_prims();
string link_name;
integer prims_pointer=0;
list temp_bank = [];
integer temp_bank_stride=2;
//FIXME: font texture might should be per-bank
llMessageLinked(LINK_THIS, SET_FONT_TEXTURE, "", gFontTexture);
// moving this before the prim scan so that the slaves properly configure themseves before
// any requests to display
llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key);
gXyTextPrims=[];
integer x=0;
for (;x<64;++x)
{
gXyTextPrims = (gXyTextPrims = []) + gXyTextPrims + 0; //we need to pad out the list to make it easier to add things in any order later
}
gBankingData = [];
// Only look for prims that match MY_BANK
llOwnerSay("DEBUG: Scanning " + (string)num_prims + " prims for bank " + (string)MY_BANK);
for(x=0;x<=num_prims;++x)
{
link_name=llGetLinkName(x);
llOwnerSay("DEBUG: Prim " + (string)x + " name: '" + link_name + "'");
list tmp = llParseString2List(link_name, (list)"-", []);
if(llList2String(tmp,0) == "xyzzytext")
{
integer prim_bank = llList2Integer(tmp,1);
integer prim_position = llList2Integer(tmp,2);
llOwnerSay("DEBUG: Found xyzzytext prim - bank: " + (string)prim_bank + ", position: " + (string)prim_position);
if (prim_bank == MY_BANK)
{
llOwnerSay("DEBUG: Adding prim " + (string)x + " to bank " + (string)MY_BANK);
temp_bank += prim_position + (list)x;
}
}
}
if (temp_bank != [])
{
llOwnerSay("DEBUG: Found " + (string)(llGetListLength(temp_bank)/temp_bank_stride) + " prims for bank " + (string)MY_BANK);
//sort the current bank by position
temp_bank = llListSort(temp_bank, temp_bank_stride, TRUE);
integer temp_len = llGetListLength(temp_bank);
//store metadata for MY_BANK at position MY_BANK in the banking data
while(llGetListLength(gBankingData) <= (MY_BANK * BANK_STRIDE + 2)) {
gBankingData += [0, 0, 0]; // pad the list
}
gBankingData = llListReplaceList(gBankingData, [prims_pointer, temp_len/temp_bank_stride, 0],
MY_BANK * BANK_STRIDE, MY_BANK * BANK_STRIDE + 2);
//repack the bank into the prim list
for (x = 0; x < temp_len; x += temp_bank_stride)
{
integer link_num = llList2Integer(temp_bank, x + 1);
llOwnerSay("DEBUG: Packing prim " + (string)link_num + " at position " + (string)prims_pointer);
gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, link_num);
++prims_pointer;
}
} else {
llOwnerSay("DEBUG: No prims found for bank " + (string)MY_BANK);
}
llOwnerSay("XyzzyText initialized for bank " + (string)MY_BANK + " with " + (string)prims_pointer + " prims");
}
default {
state_entry() {
// Initialize the character index.
ResetCharIndex();
// Listen for messages from scoreboard
llListen(LEADERBOARD_CHANNEL, "", "", "");
llOwnerSay("XyzzyText Bank " + (string)MY_BANK + " ready - listening on channel " + (string)LEADERBOARD_CHANNEL);
init();
}
on_rez(integer num)
{
llResetScript();
}
listen(integer channel, string name, key id, string message) {
if (channel == LEADERBOARD_CHANNEL) {
llOwnerSay("DEBUG: Bank " + (string)MY_BANK + " heard: " + message);
if (MY_BANK == 0 && llSubStringIndex(message, "LEFT_TEXT|") == 0) {
// This is bank 0 (left), process LEFT_TEXT messages
string leftText = llGetSubString(message, 10, -1);
llOwnerSay("DEBUG: Bank 0 processing LEFT text: " + leftText);
PassToRender(1, leftText, 0);
} else if (MY_BANK == 1 && llSubStringIndex(message, "MIDDLE_TEXT|") == 0) {
// This is bank 1 (middle), process MIDDLE_TEXT messages
string middleText = llGetSubString(message, 12, -1);
llOwnerSay("DEBUG: Bank 1 processing MIDDLE text: " + middleText);
PassToRender(1, middleText, 1);
} else if (MY_BANK == 2 && llSubStringIndex(message, "RIGHT_TEXT|") == 0) {
// This is bank 2 (right), process RIGHT_TEXT messages
string rightText = llGetSubString(message, 11, -1);
llOwnerSay("DEBUG: Bank 2 processing RIGHT text: " + rightText);
PassToRender(1, rightText, 2);
} else if (message == "CLEAR_LEADERBOARD") {
// Clear display for this bank
llOwnerSay("DEBUG: Bank " + (string)MY_BANK + " clearing display");
PassToRender(1, " ", MY_BANK);
}
}
}
link_message(integer sender, integer channel, string data, key id) {
if (id==null_key)
id="0";
if (channel == DISPLAY_STRING) {
PassToRender(1,data, (integer)((string)id));
}
else if (channel == DISPLAY_EXTENDED) {
PassToRender(2,data, (integer)((string)id));
}
else if (channel == REMAP_INDICES) {
// Parse the message, splitting it up into index values.
list Parsed = llCSV2List(data);
integer i = 0;
// Go through the list and swap each pair of indices.
for (; i < llGetListLength(Parsed); i += 2) {
integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
// Swap these index values.
string Value1 = llGetSubString(gCharIndex, Index1, Index1);
string Value2 = llGetSubString(gCharIndex, Index2, Index2);
gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
gCharIndex = llInsertString(gCharIndex, Index1, Value2);
gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
gCharIndex = llInsertString(gCharIndex, Index2, Value1);
}
}
else if (channel == RESCAN_LINKSET)
{
init();
}
else if (channel == RESET_INDICES) {
// Restore the character index back to default settings.
ResetCharIndex();
}
else if (channel == SET_FADE_OPTIONS) {
list Parsed = llCSV2List(data);
gCellUseFading = llList2Integer(Parsed, 0);
gCellHoldDelay = llList2Float(Parsed, 1);
}
else if (channel == SET_FONT_TEXTURE) {
gFontTexture = id;
}
else if (channel == SET_LINE_COLOR) {
change_line_color((integer)data, (vector)((string)id));
}
else if (channel == SET_COLOR) {
change_color((vector)data);
}
else if (channel == REGISTER_SLAVE) {
gSlaveNames += data;
llMessageLinked(sender, SLAVE_RECOGNIZED, "", "");
}
}
}

View file

@ -1,281 +0,0 @@
////////////////////////////////////////////
// XyText v2.0 SLAVE Script (5 Face, Single Texture)
//
// Modified by Thraxis Epsilon and Gigs Taggart 5/2007
// Rewrite to allow one-script-per-object operation
//
// Modified by Kermitt Quirk 19/01/2006
// To add support for 5 face prim instead of 3
//
// Originally Written by Xylor Baysklef
//
//
////////////////////////////////////////////
integer REMAP_INDICES = 204002;
integer RESET_INDICES = 204003;
//internal API
integer REGISTER_SLAVE = 205000;
integer SLAVE_RECOGNIZED = 205001;
integer SLAVE_DISPLAY = 205003;
integer SET_FONT_TEXTURE = 204005;
integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET = 205005;
// This is an extended character escape sequence.
string ESCAPE_SEQUENCE = "\e";
// This is used to get an index for the extended character.
string EXTENDED_INDEX = "12345";
// Face numbers.
integer FACE_1 = 3;
integer FACE_2 = 7;
integer FACE_3 = 4;
integer FACE_4 = 6;
integer FACE_5 = 1;
///////////// GLOBAL VARIABLES ///////////////
// This is the key of the font we are displaying.
key gFontTexture = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
// All displayable characters. Default to ASCII order.
string gCharIndex;
integer gActive; //if we are recognized, this is true
/////////// END GLOBAL VARIABLES ////////////
ResetCharIndex() {
gCharIndex = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
gCharIndex += "\n\n\n\n\n";
}
vector GetGridOffset(integer index) {
// Calculate the offset needed to display this character.
integer Row = index / 10;
integer Col = index % 10;
// Return the offset in the texture.
return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
}
ShowChars(integer link,vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
// Set the primitive textures directly.
// <-0.256, 0, 0>
// <0, 0, 0>
// <0.130, 0, 0>
// <0, 0, 0>
// <-0.74, 0, 0>
llSetLinkPrimitiveParamsFast( link,[
PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
]);
}
RenderString(integer link, string str) {
// Get the grid positions for each pair of characters.
vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
// Use these grid positions to display the correct textures/offsets.
ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
}
RenderExtended(integer link, string str) {
// Look for escape sequences.
list Parsed = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
integer ParsedLen = llGetListLength(Parsed);
// Create a list of index values to work with.
list Indices;
// We start with room for 5 indices.
integer IndicesLeft = 5;
integer i = 0;
string Token;
integer Clipped;
integer LastWasEscapeSequence = FALSE;
// Work from left to right.
for (; i < ParsedLen && IndicesLeft > 0; ++i) {
Token = llList2String(Parsed, i);
// If this is an escape sequence, just set the flag and move on.
if (Token == ESCAPE_SEQUENCE) {
LastWasEscapeSequence = TRUE;
}
else { // Token != ESCAPE_SEQUENCE
// Otherwise this is a normal token. Check its length.
Clipped = FALSE;
integer TokenLength = llStringLength(Token);
// Clip if necessary.
if (TokenLength > IndicesLeft) {
Token = llGetSubString(Token, 0, IndicesLeft - 1);
TokenLength = llStringLength(Token);
IndicesLeft = 0;
Clipped = TRUE;
}
else
IndicesLeft -= TokenLength;
// Was the previous token an escape sequence?
if (LastWasEscapeSequence) {
// Yes, the first character is an escape character, the rest are normal.
// This is the extended character.
Indices += (llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95);
// These are the normal characters.
integer j = 1;
for (; j < TokenLength; ++j)
Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));
}
else { // Normal string.
// Just add the characters normally.
integer j;
for (j = 0; j < TokenLength; ++j)
Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));
}
// Unset this flag, since this was not an escape sequence.
LastWasEscapeSequence = FALSE;
}
}
// Use the indices to create grid positions.
vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0) );
vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
// Use these grid positions to display the correct textures/offsets.
ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
}
integer ConvertIndex(integer index) {
// This converts from an ASCII based index to our indexing scheme.
if (index >= 32) // ' ' or higher
index -= 32;
else { // index < 32
// Quick bounds check.
if (index > 15)
index = 15;
index += 94; // extended characters
}
return index;
}
default
{
state_entry()
{
// Initialize the character index.
ResetCharIndex();
llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY);
}
on_rez(integer num)
{
llResetScript();
}
link_message(integer sender, integer channel, string data, key id)
{
if (channel == SLAVE_RECOGNIZED)
{
if (data == llGetScriptName())
{
gActive=TRUE;
}
return;
}
if (channel == SLAVE_DISPLAY)
{
if (!gActive)
return;
list params=llCSV2List((string)id);
if (llList2String(params, 1) != llGetScriptName())
return;
RenderString(llList2Integer(params, 0),data);
return;
}
if (channel == SLAVE_DISPLAY_EXTENDED)
{
if (!gActive)
return;
list params=llCSV2List((string)id);
if (llList2String(params, 1) != llGetScriptName())
return;
RenderExtended(llList2Integer(params, 0),data);
}
if (channel == SET_FONT_TEXTURE)
{
gFontTexture = id;
return;
}
if (channel == REMAP_INDICES) {
// Parse the message, splitting it up into index values.
list Parsed = llCSV2List(data);
integer i;
// Go through the list and swap each pair of indices.
for (i = 0; i < llGetListLength(Parsed); i += 2) {
integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
// Swap these index values.
string Value1 = llGetSubString(gCharIndex, Index1, Index1);
string Value2 = llGetSubString(gCharIndex, Index2, Index2);
gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
gCharIndex = llInsertString(gCharIndex, Index1, Value2);
gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
gCharIndex = llInsertString(gCharIndex, Index2, Value1);
}
return;
}
if (channel == RESET_INDICES) {
// Restore the character index back to default settings.
ResetCharIndex();
return;
}
if (channel == SLAVE_RESET)
{
ResetCharIndex();
gActive=FALSE;
llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY);
}
}
}

443
xyzzy_Master_script.lsl Normal file
View file

@ -0,0 +1,443 @@
////////////////////////////////////////////
// XyText v1.0.3 Script (5 Face, Multi Texture)
//
// Written by Xylor Baysklef
//
// Modified by Thraxis Epsilon January 20, 2006
// Added Support for 5 Face Prim, based on modification
// of [[XyText]] v1.1.1 by Kermitt Quick for Single Texture.
//
////////////////////////////////////////////
/////////////// CONSTANTS ///////////////////
// XyText Message Map.
integer DISPLAY_STRING = 204000;
integer DISPLAY_EXTENDED = 204001;
integer REMAP_INDICES = 204002;
integer RESET_INDICES = 204003;
integer SET_CELL_INFO = 204004;
integer SET_THICKNESS = 204006;
integer SET_COLOR = 204007;
// This is an extended character escape sequence.
string ESCAPE_SEQUENCE = "\\e";
// This is used to get an index for the extended character.
string EXTENDED_INDEX = "123456789abcdef";
// Face numbers.
integer FACE_1 = 3;
integer FACE_2 = 7;
integer FACE_3 = 4;
integer FACE_4 = 6;
integer FACE_5 = 1;
// Used to hide the text after a fade-out.
key TRANSPARENT = "701917a8-d614-471f-13dd-5f4644e36e3c";
// This is a list of textures for all 2-character combinations.
list CHARACTER_GRID = [
"00e9f9f7-0669-181c-c192-7f8e67678c8d",
"347a5cb6-0031-7ec0-2fcf-f298eebf3c0e",
"4e7e689e-37f1-9eca-8596-a958bbd23963",
"19ea9c21-67ba-8f6f-99db-573b1b877eb1",
"dde7b412-cda1-652f-6fc2-73f4641f96e1",
"af6fa3bb-3a6c-9c4f-4bf5-d1c126c830da",
"a201d3a2-364b-43b6-8686-5881c0f82a94",
"b674dec8-fead-99e5-c28d-2db8e4c51540",
"366e05f3-be6b-e5cf-c33b-731dff649caa",
"75c4925c-0427-dc0c-c71c-e28674ff4d27",
"dcbe166b-6a97-efb2-fc8e-e5bc6a8b1be6",
"0dca2feb-fc66-a762-db85-89026a4ecd68",
"a0fca76f-503a-946b-9336-0a918e886f7a",
"67fb375d-89a1-5a4f-8c7a-0cd1c066ffc4",
"300470b2-da34-5470-074c-1b8464ca050c",
"d1f8e91c-ce2b-d85e-2120-930d3b630946",
"2a190e44-7b29-dadb-0bff-c31adaf5a170",
"75d55e71-f6f8-9835-e746-a45f189f30a1",
"300fac33-2b30-3da3-26bc-e2d70428ec19",
"0747c776-011a-53ce-13ee-8b5bb9e87c1e",
"85a855c3-a94f-01ca-33e0-7dde92e727e2",
"cbc1dab2-2d61-2986-1949-7a5235c954e1",
"f7aef047-f266-9596-16df-641010edd8e1",
"4c34ebf7-e5e1-2e1a-579f-e224d9d5e71b",
"4a69e98c-26a5-ad05-e92e-b5b906ad9ef9",
"462a9226-2a97-91ac-2d89-57ab33334b78",
"20b24b3a-8c57-82ee-c6ed-555003f5dbcd",
"9b481daa-9ea8-a9fa-1ee4-ab9a0d38e217",
"c231dbdc-c842-15b0-7aa6-6da14745cfdc",
"c97e3cbb-c9a3-45df-a0ae-955c1f4bf9cf",
"f1e7d030-ff80-a242-cb69-f6951d4eae3b",
"ed32d6c4-d733-c0f1-f242-6df1d222220d",
"88f96a30-dccf-9b20-31ef-da0dfeb23c72",
"252f2595-58b8-4bcc-6515-fa274d0cfb65",
"f2838c4f-de80-cced-dff8-195dfdf36b2c",
"cc2594fe-add2-a3df-cdb3-a61711badf53",
"e0ce2972-da00-955c-129e-3289b3676776",
"3e0d336d-321f-ddfa-5c1b-e26131766f6a",
"d43b1dc4-6b51-76a7-8b90-38865b82bf06",
"06d16cbb-1868-fd1d-5c93-eae42164a37d",
"dd5d98cf-273e-3fd0-f030-48be58ee3a0b",
"0e47c89e-de4a-6233-a2da-cb852aad1b00",
"fb9c4a55-0e13-495b-25c4-f0b459dc06de",
"e3ce8def-312c-735b-0e48-018b6799c883",
"2f713216-4e71-d123-03ed-9c8554710c6b",
"4a417d8a-1f4f-404b-9783-6672f8527911",
"ca5e21ec-5b20-5909-4c31-3f90d7316b33",
"06a4fcc3-e1c4-296d-8817-01f88fbd7367",
"130ac084-6f3c-95de-b5b6-d25c80703474",
"59d540a0-ae9d-3606-5ae0-4f2842b64cfa",
"8612ae9a-f53c-5bf4-2899-8174d7abc4fd",
"12467401-e979-2c49-34e0-6ac761542797",
"d53c3eaa-0404-3860-0675-3e375596c3e3",
"9f5b26bd-81d3-b25e-62fe-5b671d1e3e79",
"f57f0b64-a050-d617-ee00-c8e9e3adc9cb",
"beff166a-f5f3-f05e-e020-98f2b00e27ed",
"02278a65-94ba-6d5e-0d2b-93f2e4f4bf70",
"a707197d-449e-5b58-846c-0c850c61f9d6",
"021d4b1a-9503-a44f-ee2b-976eb5d80e68",
"0ae2ffae-7265-524d-cb76-c2b691992706",
"f6e41cf2-1104-bd0b-0190-dffad1bac813",
"2b4bb15e-956d-56ae-69f5-d26a20de0ce7",
"f816da2c-51f1-612a-2029-a542db7db882",
"345fea05-c7be-465c-409f-9dcb3bd2aa07",
"b3017e02-c063-5185-acd5-1ef5f9d79b89",
"4dcff365-1971-3c2b-d73c-77e1dc54242a"
];
///////////// END CONSTANTS ////////////////
///////////// GLOBAL VARIABLES ///////////////
// All displayable characters. Default to ASCII order.
string gCharIndex;
// This is the channel to listen on while acting
// as a cell in a larger display.
integer gCellChannel = -1;
// This is the starting character position in the cell channel message
// to render.
integer gCellCharPosition = 0;
// This is whether or not to use the fade in/out special effect.
integer gCellUseFading = FALSE;
// This is how long to display the text before fading out (if using
// fading special effect).
// Note: < 0 means don't fade out.
float gCellHoldDelay = 1.0;
/////////// END GLOBAL VARIABLES ////////////
ResetCharIndex()
{
gCharIndex = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
// \" <-- Fixes LSL syntax highlighting bug.
gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
gCharIndex += "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
}
vector GetGridPos(integer index1, integer index2)
{
// There are two ways to use the lookup table...
integer Col;
integer Row;
if (index1 >= index2)
{
// In this case, the row is the index of the first character:
Row = index1;
// And the col is the index of the second character (x2)
Col = index2 * 2;
}
// Index1 < Index2
else
{
// In this case, the row is the index of the second character:
Row = index2;
// And the col is the index of the first character, x2, offset by 1.
Col = index1 * 2 + 1;
}
return <Col, Row, 0>;
}
string GetGridTexture(vector grid_pos)
{
// Calculate the texture in the grid to use.
integer GridCol = llRound(grid_pos.x) / 20;
integer GridRow = llRound(grid_pos.y) / 10;
// Lookup the texture.
key Texture = llList2Key(CHARACTER_GRID, GridRow * (GridRow + 1) / 2 + GridCol);
return Texture;
}
vector GetGridOffset(vector grid_pos)
{
// Zoom in on the texture showing our character pair.
integer Col = llRound(grid_pos.x) % 20;
integer Row = llRound(grid_pos.y) % 10;
// Return the offset in the texture.
return <-0.45 + 0.05 * Col, 0.45 - 0.1 * Row, 0.0>;
}
ShowChars(vector grid_pos1, vector grid_pos2, vector grid_pos3, vector grid_pos4, vector grid_pos5)
{
// Set the primitive textures directly.
llSetLinkPrimitiveParamsFast(LINK_THIS, [
PRIM_TEXTURE, FACE_1, GetGridTexture(grid_pos1), <0.25, 0.1, 0>, GetGridOffset(grid_pos1) + <0.075, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_2, GetGridTexture(grid_pos2), <0.1, 0.1, 0>, GetGridOffset(grid_pos2), 0.0,
PRIM_TEXTURE, FACE_3, GetGridTexture(grid_pos3), <-1.48, 0.1, 0>, GetGridOffset(grid_pos3)+ <0.37, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_4, GetGridTexture(grid_pos4), <0.1, 0.1, 0>, GetGridOffset(grid_pos4), 0.0,
PRIM_TEXTURE, FACE_5, GetGridTexture(grid_pos5), <0.25, 0.1, 0>, GetGridOffset(grid_pos5) - <0.075, 0, 0>, 0.0]);
}
RenderString(string str)
{
// Get the grid positions for each pair of characters.
vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)),
llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)),
llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)),
llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) );
vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)),
llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) );
vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)),
llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );
// Use these grid positions to display the correct textures/offsets.
ShowChars(GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
}
RenderWithEffects(string str)
{
// Get the grid positions for each pair of characters.
vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)),
llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)),
llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)),
llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) );
vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)),
llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) );
vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)),
llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );
// First set the alpha to the lowest possible.
llSetAlpha(0.05, ALL_SIDES);
// Use these grid positions to display the correct textures/offsets.
ShowChars(GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
float Alpha;
for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
llSetAlpha(Alpha, ALL_SIDES);
// See if we want to fade out as well.
if (gCellHoldDelay < 0.0)
// No, bail out. (Just keep showing the string at full strength).
return;
// Hold the text for a while.
llSleep(gCellHoldDelay);
// Now fade out.
for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
llSetAlpha(Alpha, ALL_SIDES);
// Make the text transparent to fully hide it.
llSetTexture(TRANSPARENT, ALL_SIDES);
}
RenderExtended(string str)
{
// Look for escape sequences.
list Parsed = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
integer ParsedLen = llGetListLength(Parsed);
// Create a list of index values to work with.
list Indices;
// We start with room for 6 indices.
integer IndicesLeft = 10;
integer i;
string Token;
integer Clipped;
integer LastWasEscapeSequence = FALSE;
// Work from left to right.
for (i = 0; i < ParsedLen && IndicesLeft > 0; i++)
{
Token = llList2String(Parsed, i);
// If this is an escape sequence, just set the flag and move on.
if (Token == ESCAPE_SEQUENCE)
{
LastWasEscapeSequence = TRUE;
}
// Token != ESCAPE_SEQUENCE
else
{
// Otherwise this is a normal token. Check its length.
Clipped = FALSE;
integer TokenLength = llStringLength(Token);
// Clip if necessary.
if (TokenLength > IndicesLeft)
{
Token = llGetSubString(Token, 0, IndicesLeft - 1);
TokenLength = llStringLength(Token);
IndicesLeft = 0;
Clipped = TRUE;
}
else
IndicesLeft -= TokenLength;
// Was the previous token an escape sequence?
if (LastWasEscapeSequence)
{
// Yes, the first character is an escape character, the rest are normal.
// This is the extended character.
Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
// These are the normal characters.
integer j;
for (j = 1; j < TokenLength; j++)
{
Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
}
}
// Normal string.
else
{
// Just add the characters normally.
integer j;
for (j = 0; j < TokenLength; j++)
Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
}
// Unset this flag, since this was not an escape sequence.
LastWasEscapeSequence = FALSE;
}
}
// Use the indices to create grid positions.
vector GridPos1 = GetGridPos( llList2Integer(Indices, 0), llList2Integer(Indices, 1) );
vector GridPos2 = GetGridPos( llList2Integer(Indices, 2), llList2Integer(Indices, 3) );
vector GridPos3 = GetGridPos( llList2Integer(Indices, 4), llList2Integer(Indices, 5) );
vector GridPos4 = GetGridPos( llList2Integer(Indices, 6), llList2Integer(Indices, 7) );
vector GridPos5 = GetGridPos( llList2Integer(Indices, 8), llList2Integer(Indices, 9) );
// Use these grid positions to display the correct textures/offsets.
ShowChars(GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
}
integer ConvertIndex(integer index)
{
// This converts from an ASCII based index to our indexing scheme.
if (index >= 32) // ' ' or higher
index -= 32;
// index < 32
else
{
// Quick bounds check.
if (index > 15)
index = 15;
index += 94; // extended characters
}
return index;
}
default
{
state_entry()
{
// Initialize the character index.
ResetCharIndex();
//llSay(0, "Free Memory: " + (string) llGetFreeMemory());
}
link_message(integer sender, integer channel, string data, key id)
{
if (channel == DISPLAY_STRING)
{
RenderString(data);
return;
}
if (channel == DISPLAY_EXTENDED)
{
RenderExtended(data);
return;
}
if (channel == gCellChannel)
{
// Extract the characters we are interested in, and use those to render.
string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 9);
if (gCellUseFading)
RenderWithEffects( TextToRender );
else // !gCellUseFading
RenderString( TextToRender );
return;
}
if (channel == REMAP_INDICES)
{
// Parse the message, splitting it up into index values.
list Parsed = llCSV2List(data);
integer i;
// Go through the list and swap each pair of indices.
for (i = 0; i < llGetListLength(Parsed); i += 2)
{
integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
// Swap these index values.
string Value1 = llGetSubString(gCharIndex, Index1, Index1);
string Value2 = llGetSubString(gCharIndex, Index2, Index2);
gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
gCharIndex = llInsertString(gCharIndex, Index1, Value2);
gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
gCharIndex = llInsertString(gCharIndex, Index2, Value1);
}
return;
}
if (channel == RESET_INDICES)
{
// Restore the character index back to default settings.
ResetCharIndex();
return;
}
if (channel == SET_CELL_INFO)
{
// Change the channel we listen to for cell commands, and the
// starting character position to extract from.
list Parsed = llCSV2List(data);
gCellChannel = (integer) llList2String(Parsed, 0);
gCellCharPosition = (integer) llList2String(Parsed, 1);
gCellUseFading = (integer) llList2String(Parsed, 2);
gCellHoldDelay = (float) llList2String(Parsed, 3);
return;
}
if (channel == SET_THICKNESS)
{
// Set our z scale to thickness, while staying fixed
// in position relative the prim below us.
vector Scale = llGetScale();
float Thickness = (float) data;
// Reposition only if this isn't the root prim.
integer ThisLink = llGetLinkNumber();
if (ThisLink != 0 || ThisLink != 1)
{
// This is not the root prim.
vector Up = llRot2Up(llGetLocalRot());
float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
vector Pos = llGetLocalPos();
llSetPos(Pos + DistanceToMove * Up);
}
// Apply the new thickness.
Scale.z = Thickness;
llSetScale(Scale);
return;
}
if (channel == SET_COLOR) {
vector newColor = (vector)data;
llSetColor(newColor, ALL_SIDES);
}
}
}