## [2.4.0] - 2025-08-08
42
CHANGELOG.md
|
|
@ -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
|
|
@ -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
|
||||
101
Corrected_Leaderboard_Listener.lsl
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
109
Follower_Script_Template.lsl
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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++) {
|
||||
for (i = 0; i < 11; i++) {
|
||||
string fullLine;
|
||||
|
||||
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);
|
||||
|
||||
// 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: "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 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)
|
||||
// Format wins and losses with 2-digit limit
|
||||
if (llStringLength(winsText) > 2) {
|
||||
winsText = llGetSubString(winsText, 0, 1); // Truncate to 2 digits
|
||||
winsText = llGetSubString(winsText, 0, 1);
|
||||
}
|
||||
if (llStringLength(lossesText) > 2) {
|
||||
lossesText = llGetSubString(lossesText, 0, 1);
|
||||
}
|
||||
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) {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Leaderboard_Communication_Clean.lsl
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
13
Main.lsl
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
47
Position_Controller_Timer.lsl
Normal 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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Position_Reset_Instructions.txt
Normal 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
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
29
README.md
|
|
@ -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
|
|
@ -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!
|
||||
|
|
@ -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 = [];
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
BIN
images/Bot player profile pic.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
images/Direct Hit.png
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
images/Elimination.png
Normal file
|
After Width: | Height: | Size: 3 MiB |
BIN
images/No Shiled.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
images/Peril Selected.png
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
images/PlotTwist.png
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
images/Punishment Time.png
Normal file
|
After Width: | Height: | Size: 3 MiB |
|
Before Width: | Height: | Size: 174 KiB |
BIN
images/Victory.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
|
|
@ -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 == []);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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, "", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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, "", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
281
xytext_slave.lsl
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||