## [2.3.0] - 2025-08-05
This commit is contained in:
parent
0eceef2227
commit
f8d711d3c1
20 changed files with 3736 additions and 59 deletions
|
|
@ -5,6 +5,9 @@
|
||||||
integer LISTEN_CHANNEL = -9999; // Change if needed to match main controller
|
integer LISTEN_CHANNEL = -9999; // Change if needed to match main controller
|
||||||
integer MSG_SYNC_GAME_STATE = 107;
|
integer MSG_SYNC_GAME_STATE = 107;
|
||||||
|
|
||||||
|
// Bot timing configuration to prevent dialog system overload
|
||||||
|
float BOT_RESPONSE_DELAY = 1.5; // Delay before bot responds to commands
|
||||||
|
|
||||||
// Track game state to validate bot commands
|
// Track game state to validate bot commands
|
||||||
list names = [];
|
list names = [];
|
||||||
list lives = [];
|
list lives = [];
|
||||||
|
|
@ -56,6 +59,9 @@ doBotPick(string botName, integer count, integer diceMax, list avoidNumbers) {
|
||||||
llOwnerSay("[Bot Manager] ⚠️ WARNING: " + botName + " only picked " + (string)llGetListLength(picks) + "/" + (string)count + " numbers after " + (string)attempts + " attempts");
|
llOwnerSay("[Bot Manager] ⚠️ WARNING: " + botName + " only picked " + (string)llGetListLength(picks) + "/" + (string)count + " numbers after " + (string)attempts + " attempts");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add delay before responding to prevent dialog system overload
|
||||||
|
llSleep(BOT_RESPONSE_DELAY);
|
||||||
|
|
||||||
string pickString = llDumpList2String(picks, ";");
|
string pickString = llDumpList2String(picks, ";");
|
||||||
string response = "BOT_PICKED:" + botName + ":" + pickString;
|
string response = "BOT_PICKED:" + botName + ":" + pickString;
|
||||||
llMessageLinked(LINK_SET, -9997, response, NULL_KEY);
|
llMessageLinked(LINK_SET, -9997, response, NULL_KEY);
|
||||||
|
|
@ -81,6 +87,9 @@ doBotRoll(string botName, integer diceMax) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add delay before rolling to prevent dialog system overload
|
||||||
|
llSleep(BOT_RESPONSE_DELAY);
|
||||||
|
|
||||||
integer roll = 1 + (integer)(llFrand((float)diceMax));
|
integer roll = 1 + (integer)(llFrand((float)diceMax));
|
||||||
llRegionSay(LISTEN_CHANNEL, "BOT_ROLL:" + botName + ":" + (string)roll);
|
llRegionSay(LISTEN_CHANNEL, "BOT_ROLL:" + botName + ":" + (string)roll);
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +103,16 @@ default {
|
||||||
}
|
}
|
||||||
|
|
||||||
link_message(integer sender, integer num, string str, key id) {
|
link_message(integer sender, integer num, string str, key id) {
|
||||||
|
// Handle full reset from main controller
|
||||||
|
if (num == -99999 && str == "FULL_RESET") {
|
||||||
|
// Reset bot manager state
|
||||||
|
names = [];
|
||||||
|
lives = [];
|
||||||
|
perilPlayer = "";
|
||||||
|
llOwnerSay("[Bot Manager] Reset complete!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle game state sync to track player eliminations
|
// Handle game state sync to track player eliminations
|
||||||
if (num == MSG_SYNC_GAME_STATE) {
|
if (num == MSG_SYNC_GAME_STATE) {
|
||||||
list parts = llParseString2List(str, ["~"], []);
|
list parts = llParseString2List(str, ["~"], []);
|
||||||
|
|
|
||||||
52
CHANGELOG.md
52
CHANGELOG.md
|
|
@ -1,6 +1,58 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to Peril Dice will be documented in this file.
|
All notable changes to Peril Dice will be documented in this file.
|
||||||
|
## [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
|
## [2.2.0] - 2025-08-04
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,13 @@ default {
|
||||||
}
|
}
|
||||||
|
|
||||||
link_message(integer sender, integer num, string str, key id) {
|
link_message(integer sender, integer num, string str, key id) {
|
||||||
|
// Handle full reset from main controller
|
||||||
|
if (num == -99999 && str == "FULL_RESET") {
|
||||||
|
// Debugger doesn't maintain state, but acknowledge reset
|
||||||
|
llOwnerSay("🔍 Debugger reset!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (num == MSG_SYNC_GAME_STATE) {
|
if (num == MSG_SYNC_GAME_STATE) {
|
||||||
list parts = llParseString2List(str, ["~"], []);
|
list parts = llParseString2List(str, ["~"], []);
|
||||||
if (llGetListLength(parts) < 4) {
|
if (llGetListLength(parts) < 4) {
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,18 @@ default {
|
||||||
}
|
}
|
||||||
|
|
||||||
link_message(integer sender, integer num, string str, key id) {
|
link_message(integer sender, integer num, string str, key id) {
|
||||||
|
// Handle full reset from main controller
|
||||||
|
if (num == -99999 && str == "FULL_RESET") {
|
||||||
|
// Reset all game state
|
||||||
|
players = [];
|
||||||
|
names = [];
|
||||||
|
lives = [];
|
||||||
|
picksData = [];
|
||||||
|
perilPlayer = "";
|
||||||
|
llOwnerSay("📦 Floater Manager reset!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (num == MSG_REGISTER_PLAYER) {
|
if (num == MSG_REGISTER_PLAYER) {
|
||||||
// Enforce the maximum number of players
|
// Enforce the maximum number of players
|
||||||
if (llGetListLength(players) >= MAX_PLAYERS) {
|
if (llGetListLength(players) >= MAX_PLAYERS) {
|
||||||
|
|
@ -85,6 +97,8 @@ default {
|
||||||
key avKey = llList2Key(info, 1);
|
key avKey = llList2Key(info, 1);
|
||||||
names += [name];
|
names += [name];
|
||||||
players += [avKey];
|
players += [avKey];
|
||||||
|
lives += [3]; // Initialize with 3 lives for new players
|
||||||
|
picksData += [name + "|"]; // Initialize with empty picks
|
||||||
llSay(0, "💀 " + name + " has entered the deadly game! Welcome to your potential doom! 💀");
|
llSay(0, "💀 " + name + " has entered the deadly game! Welcome to your potential doom! 💀");
|
||||||
|
|
||||||
// Immediately rez the float after registration. This ensures the
|
// Immediately rez the float after registration. This ensures the
|
||||||
|
|
@ -118,15 +132,21 @@ default {
|
||||||
// Real avatars: rez next to the avatar with the standard offset
|
// Real avatars: rez next to the avatar with the standard offset
|
||||||
pos = basePos + <1, 0, 1>;
|
pos = basePos + <1, 0, 1>;
|
||||||
} else {
|
} else {
|
||||||
// Test players: space floats apart along the X axis so they don't overlap.
|
// Test players: space floats apart and away from the scoreboard/leaderboard area.
|
||||||
// Each test player gets an additional 0.5m offset based on their index.
|
// Move them further away (3-5 meters) and spread them out more.
|
||||||
// This uses the current prim's position as the base since there's no
|
// This prevents them from spawning directly on the leaderboard.
|
||||||
// avatar position available.
|
pos = basePos + <-4.0 - (float)idx * 1.0, 2.0 + (float)idx * 0.8, 1>;
|
||||||
pos = basePos + <1 + (float)idx * 0.5, 0, 1>;
|
|
||||||
}
|
}
|
||||||
integer ch = -777000 + idx;
|
integer ch = -777000 + idx;
|
||||||
llSetObjectDesc(name);
|
|
||||||
llRezObject("StatFloat", pos, ZERO_VECTOR, ZERO_ROTATION, ch);
|
llRezObject("StatFloat", pos, ZERO_VECTOR, ZERO_ROTATION, ch);
|
||||||
|
// Wait a moment then set the description
|
||||||
|
llSleep(0.2);
|
||||||
|
// Find the rezzed object and set its description
|
||||||
|
list nearby = llGetObjectDetails(llGetKey(), [OBJECT_POS]);
|
||||||
|
if (llGetListLength(nearby) > 0) {
|
||||||
|
// Use llRegionSay to tell the StatFloat its name
|
||||||
|
llRegionSay(ch, "SET_NAME:" + name);
|
||||||
|
}
|
||||||
// After rezzing, immediately update the float so it displays the
|
// After rezzing, immediately update the float so it displays the
|
||||||
// correct lives, picks and peril status. Use the avatar key as
|
// correct lives, picks and peril status. Use the avatar key as
|
||||||
// the id to keep the float tied to the player. This ensures that
|
// the id to keep the float tied to the player. This ensures that
|
||||||
|
|
@ -159,7 +179,7 @@ default {
|
||||||
}
|
}
|
||||||
|
|
||||||
string picksDisplay = llList2CSV(picks);
|
string picksDisplay = llList2CSV(picks);
|
||||||
string txt = "🎲 Peril Dice\n👤 " + name + "\n❤️ Lives: " + (string)lifeCount + "\n" + perilName + "\n🔢 Picks: " + picksDisplay;
|
string txt = "🎲 Peril Dice\n👤 " + name + "\nLives: " + (string)lifeCount + "\n" + perilName + "\n🔢 Picks: " + picksDisplay;
|
||||||
llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt);
|
llRegionSay(ch, "FLOAT:" + (string)avKey + "|" + txt);
|
||||||
}
|
}
|
||||||
else if (num == MSG_CLEANUP_FLOAT) {
|
else if (num == MSG_CLEANUP_FLOAT) {
|
||||||
|
|
|
||||||
727
Game_Scoreboard_Manager.lsl
Normal file
727
Game_Scoreboard_Manager.lsl
Normal file
|
|
@ -0,0 +1,727 @@
|
||||||
|
// Game Scoreboard Manager - Shows current game players in grid layout
|
||||||
|
// Each player gets a prim showing profile picture + heart texture (lives)
|
||||||
|
|
||||||
|
// Heart texture UUIDs - REPLACE WITH YOUR ACTUAL TEXTURE UUIDs
|
||||||
|
integer LEADERBOARD_CHANNEL = -12346;
|
||||||
|
list playerWins = []; // Store wins data for persistent leaderboard
|
||||||
|
string TEXTURE_0_HEARTS = "7d8ae121-e171-12ae-f5b6-7cc3c0395c7b"; // 0 hearts (dead)
|
||||||
|
string TEXTURE_1_HEARTS = "6605d25f-8e2d-2870-eb87-77c58cd47fa9"; // 1 heart
|
||||||
|
string TEXTURE_2_HEARTS = "7ba6cb1e-f384-25a5-8e88-a90bbd7cc041"; // 2 hearts
|
||||||
|
string TEXTURE_3_HEARTS = "a5d16715-4648-6526-5582-e8068293f792"; // 3 hearts
|
||||||
|
|
||||||
|
// Default textures - REPLACE WITH YOUR ACTUAL TEXTURE UUIDs
|
||||||
|
string TEXTURE_DEFAULT_PROFILE = "1ce89375-6c3c-3845-26b1-1dc666bc9169"; // Default avatar
|
||||||
|
string TEXTURE_BOT_PROFILE = "62f31722-04c1-8c29-c236-398543f2a6ae"; // Bot avatar picture
|
||||||
|
string BLANK_TEXTURE = "5748decc-f629-461c-9a36-a35a221fe21f"; // White texture UUID
|
||||||
|
string TEXTURE_BACKGROUND = "5748decc-f629-461c-9a36-a35a221fe21f"; // Background texture (blank + black color)
|
||||||
|
string TEXTURE_ACTIONS = "6aac8dce-1b83-f931-abe3-286f4f2faa29"; // Punishment texture
|
||||||
|
string TEXTURE_LEADERBOARD = "5748decc-f629-461c-9a36-a35a221fe21f"; // Leaderboard texture
|
||||||
|
|
||||||
|
// Status textures - REPLACE WITH YOUR ACTUAL TEXTURE UUIDs
|
||||||
|
string TEXTURE_PERIL = "c5676fec-0c85-5567-3dd8-f939234e21d9"; // Elimination texture
|
||||||
|
string TEXTURE_PERIL_SELECTED = "a53ff601-3c8a-e312-9e0e-f6fa76f6773a"; // Peril Selected texture
|
||||||
|
string TEXTURE_VICTORY = "ec5bf10e-4970-fb63-e7bf-751e1dc27a8d"; // Victory texture
|
||||||
|
string TEXTURE_PUNISHMENT = "acfabed0-84ad-bfd1-cdfc-2ada0aeeaa2f"; // Punishment texture (fallback)
|
||||||
|
string TEXTURE_DIRECT_HIT = "ecd2dba2-3969-6c39-ad59-319747307f55"; // Direct Hit texture
|
||||||
|
string TEXTURE_NO_SHIELD = "2440174f-e385-44e2-8016-ac34934f11f5"; // No Shield texture
|
||||||
|
string TEXTURE_PLOT_TWIST = "ec533379-4f7f-8183-e877-e68af703dcce"; // Plot Twist texture
|
||||||
|
string TEXTURE_TITLE = "624bb7a7-e856-965c-bae8-94d75226c1bc"; // Title texture
|
||||||
|
|
||||||
|
// Player data - used for BOTH current game display AND leaderboard
|
||||||
|
list playerNames = []; // Current game players for display
|
||||||
|
list playerLives = []; // Current game player lives
|
||||||
|
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
|
||||||
|
|
||||||
|
// HTTP tracking
|
||||||
|
list profileRequests = []; // Track which profile requests belong to which player
|
||||||
|
list httpRequests = []; // Track HTTP requests for profile pictures
|
||||||
|
|
||||||
|
// Profile picture extraction constants
|
||||||
|
string profile_key_prefix = "<meta name=\"imageid\" content=\"";
|
||||||
|
string profile_img_prefix = "<img alt=\"profile image\" src=\"http://secondlife.com/app/image/";
|
||||||
|
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_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
|
||||||
|
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_DEPTH = 0.01;
|
||||||
|
float ACTIONS_WIDTH = 4;
|
||||||
|
float ACTIONS_HEIGHT = 0.4;
|
||||||
|
float ACTIONS_DEPTH = 0.02;
|
||||||
|
float LEADERBOARD_WIDTH = 0.8;
|
||||||
|
float LEADERBOARD_HEIGHT = 1.2;
|
||||||
|
float LEADERBOARD_DEPTH = 0.02;
|
||||||
|
|
||||||
|
// Prim indices (after root prim = 1)
|
||||||
|
integer BACKGROUND_PRIM = 2;
|
||||||
|
integer ACTIONS_PRIM = 3;
|
||||||
|
integer FIRST_PLAYER_PRIM = 4; // Player prims start from index 4 (no leaderboard prim)
|
||||||
|
|
||||||
|
// Communication
|
||||||
|
integer SCOREBOARD_CHANNEL = -12345;
|
||||||
|
|
||||||
|
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
|
||||||
|
if (totalPrims < 23) {
|
||||||
|
llOwnerSay("Need 23 prims total: 1 root + 2 UI + 20 player prims (2 per player)");
|
||||||
|
llOwnerSay("Current prims: " + (string)totalPrims + ", needed: 23");
|
||||||
|
llOwnerSay("Note: Leaderboard is separate object now");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup UI prims first
|
||||||
|
setupUIPrims();
|
||||||
|
|
||||||
|
llOwnerSay("DEBUG: Starting to position " + (string)totalPrims + " prims...");
|
||||||
|
|
||||||
|
// Position prims for each player (2 prims per player)
|
||||||
|
integer i;
|
||||||
|
for (i = 0; i < 10; i++) {
|
||||||
|
integer row = i / GRID_COLS;
|
||||||
|
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,
|
||||||
|
0.0,
|
||||||
|
row * SPACING_Y - 1.0 * SPACING_Y + -1.5>;
|
||||||
|
|
||||||
|
// Profile prim (left side)
|
||||||
|
vector profilePos = basePos + <-PRIM_OFFSET, 0.0, 0.0>;
|
||||||
|
integer profilePrimIndex = FIRST_PLAYER_PRIM + (i * 2); // Prims 4, 6, 8, 10...
|
||||||
|
|
||||||
|
// Hearts prim (right side)
|
||||||
|
vector heartsPos = basePos + <PRIM_OFFSET, 0.0, 0.0>;
|
||||||
|
integer heartsPrimIndex = FIRST_PLAYER_PRIM + (i * 2) + 1; // Prims 5, 7, 9, 11...
|
||||||
|
|
||||||
|
// 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
|
||||||
|
PRIM_SIZE, <PRIM_HEIGHT, PRIM_WIDTH, PRIM_DEPTH>, // Swap width/height for landscape
|
||||||
|
PRIM_TEXTURE, ALL_SIDES, TEXTURE_DEFAULT_PROFILE, <1,1,0>, <0,0,0>, 0.0, // All faces
|
||||||
|
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
|
||||||
|
if (heartsPrimIndex <= totalPrims) {
|
||||||
|
llSetLinkPrimitiveParamsFast(heartsPrimIndex, [
|
||||||
|
PRIM_POSITION, heartsPos,
|
||||||
|
PRIM_ROTATION, <-0.7, 0.0, 0.0, 0.7>, // Back to upright portrait rotation
|
||||||
|
PRIM_SIZE, <PRIM_HEIGHT, PRIM_WIDTH, PRIM_DEPTH>, // Swap width/height for landscape
|
||||||
|
PRIM_TEXTURE, ALL_SIDES, TEXTURE_3_HEARTS, <1,1,0>, <0,0,0>, 0.0, // All faces
|
||||||
|
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, // Fully opaque white
|
||||||
|
PRIM_TEXT, "", <1,1,1>, 0.0
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
llSetLinkPrimitiveParamsFast(BACKGROUND_PRIM, [
|
||||||
|
PRIM_POSITION, bgPosition, // Behind the board and higher up
|
||||||
|
PRIM_ROTATION, <0.0, 0.0, -0.04, 1.0>, // No rotation
|
||||||
|
PRIM_SIZE, <BACKGROUND_WIDTH, BACKGROUND_DEPTH, BACKGROUND_HEIGHT>,
|
||||||
|
PRIM_TEXTURE, ALL_SIDES, TEXTURE_BACKGROUND, <1,1,0>, <0,0,0>, 0.0,
|
||||||
|
PRIM_COLOR, ALL_SIDES, <0.0, 0.0, 0.0>, 1.0, // Black background color
|
||||||
|
PRIM_TEXT, "", <1,1,1>, 0.0
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Actions/status prim - rectangle above the player grid
|
||||||
|
llSetLinkPrimitiveParamsFast(ACTIONS_PRIM, [
|
||||||
|
PRIM_POSITION, <0.0, 0.01, -0.3>, // Even higher above the grid
|
||||||
|
PRIM_ROTATION, <0.0, 0.0, -0.04, 1.0>, // No rotation
|
||||||
|
PRIM_SIZE, <ACTIONS_WIDTH, ACTIONS_DEPTH, ACTIONS_HEIGHT>,
|
||||||
|
PRIM_TEXTURE, ALL_SIDES, TEXTURE_TITLE, <1,1,0>, <0,0,0>, 0.0, // Start with title texture
|
||||||
|
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0, // White for texture visibility
|
||||||
|
PRIM_TEXT, "", <0,0,0>, 0.0 // No floating text
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Note: Leaderboard is now a separate object
|
||||||
|
|
||||||
|
llOwnerSay("DEBUG: UI prims setup complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update actions prim with appropriate status texture
|
||||||
|
updateActionsPrim(string status) {
|
||||||
|
string textureToUse = "";
|
||||||
|
|
||||||
|
if (status == "Elimination") {
|
||||||
|
textureToUse = TEXTURE_PERIL;
|
||||||
|
} else if (status == "Victory") {
|
||||||
|
textureToUse = TEXTURE_VICTORY;
|
||||||
|
} else if (status == "Punishment") {
|
||||||
|
textureToUse = TEXTURE_PUNISHMENT;
|
||||||
|
} else if (status == "Direct Hit") {
|
||||||
|
textureToUse = TEXTURE_DIRECT_HIT;
|
||||||
|
} else if (status == "No Shield") {
|
||||||
|
textureToUse = TEXTURE_NO_SHIELD;
|
||||||
|
} else if (status == "Peril Selected") {
|
||||||
|
textureToUse = TEXTURE_PERIL_SELECTED;
|
||||||
|
} else if (status == "Plot Twist") {
|
||||||
|
textureToUse = TEXTURE_PLOT_TWIST;
|
||||||
|
} else if (status == "Title") {
|
||||||
|
textureToUse = TEXTURE_TITLE;
|
||||||
|
} else {
|
||||||
|
llOwnerSay("DEBUG: Unrecognized status '" + status + "' - keeping current texture");
|
||||||
|
return; // Don't change texture for unrecognized statuses
|
||||||
|
}
|
||||||
|
|
||||||
|
llSetLinkPrimitiveParamsFast(ACTIONS_PRIM, [
|
||||||
|
PRIM_TEXTURE, ALL_SIDES, textureToUse, <1,1,0>, <0,0,0>, 0.0,
|
||||||
|
PRIM_TEXT, "", <0,0,0>, 0.0 // Remove existing floating text
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XyzzyText Communication Constants
|
||||||
|
integer DISPLAY_STRING = 204000;
|
||||||
|
integer DISPLAY_EXTENDED = 204001;
|
||||||
|
|
||||||
|
// Generate leaderboard text for triple XyzzyText display (15 characters per line split across 3 prims)
|
||||||
|
generateLeaderboardText() {
|
||||||
|
list sortedData = getSortedLeaderboard();
|
||||||
|
integer maxDisplay = llGetListLength(sortedData);
|
||||||
|
if (maxDisplay > 11) maxDisplay = 11; // Show top 11 entries to fit in display
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
integer i;
|
||||||
|
for (i = 0; i < maxDisplay; i++) {
|
||||||
|
list playerData = llParseString2List(llList2String(sortedData, i), [":"], []);
|
||||||
|
string playerName = llList2String(playerData, 0);
|
||||||
|
string winsText = llList2String(playerData, 1);
|
||||||
|
|
||||||
|
// Create 15-character line: "PlayerNameHere Wins" (player name + padding + wins)
|
||||||
|
// Truncate name to max 12 characters to leave room for wins
|
||||||
|
if (llStringLength(playerName) > 12) {
|
||||||
|
playerName = llGetSubString(playerName, 0, 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the complete line (15 chars total)
|
||||||
|
string fullLine = playerName;
|
||||||
|
|
||||||
|
// Add padding spaces
|
||||||
|
integer padding = 13 - llStringLength(playerName); // Leave 2 chars for wins
|
||||||
|
integer j;
|
||||||
|
for (j = 0; j < padding; j++) {
|
||||||
|
fullLine += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add wins (max 2 digits)
|
||||||
|
if (llStringLength(winsText) > 2) {
|
||||||
|
winsText = llGetSubString(winsText, 0, 1); // Truncate to 2 digits
|
||||||
|
}
|
||||||
|
fullLine += winsText;
|
||||||
|
|
||||||
|
// Ensure exactly 15 characters
|
||||||
|
if (llStringLength(fullLine) > 15) {
|
||||||
|
fullLine = llGetSubString(fullLine, 0, 14);
|
||||||
|
} else if (llStringLength(fullLine) < 15) {
|
||||||
|
// Pad with spaces if needed
|
||||||
|
while (llStringLength(fullLine) < 15) {
|
||||||
|
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
|
||||||
|
|
||||||
|
leftText += "\n" + leftPart;
|
||||||
|
middleText += "\n" + middlePart;
|
||||||
|
rightText += "\n" + rightPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no data, show empty message
|
||||||
|
if (maxDisplay == 0) {
|
||||||
|
leftText = "NO ";
|
||||||
|
middleText = "GAMES";
|
||||||
|
rightText = " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, "RIGHT_TEXT|" + rightText);
|
||||||
|
|
||||||
|
// Also create combined floating text as backup display method
|
||||||
|
string combinedText = "WINS:\n";
|
||||||
|
for (i = 0; i < maxDisplay; i++) {
|
||||||
|
list playerData = llParseString2List(llList2String(sortedData, i), [":"], []);
|
||||||
|
string playerName = llList2String(playerData, 0);
|
||||||
|
string winsText = llList2String(playerData, 1);
|
||||||
|
|
||||||
|
if (llStringLength(playerName) > 12) {
|
||||||
|
playerName = llGetSubString(playerName, 0, 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
combinedText += playerName + ": " + winsText + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxDisplay == 0) {
|
||||||
|
combinedText = "No games played yet";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Leaderboard is now in separate object - no prim to update here
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update leaderboard prim with current standings and update persistent data
|
||||||
|
updateLeaderboard(list topPlayers) {
|
||||||
|
generateLeaderboardText();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle game won message
|
||||||
|
handleGameWon(string winnerName) {
|
||||||
|
integer playerIndex = llListFindList(leaderboardNames, [winnerName]);
|
||||||
|
|
||||||
|
if (playerIndex == -1) {
|
||||||
|
leaderboardNames += [winnerName];
|
||||||
|
leaderboardWins += [1];
|
||||||
|
} else {
|
||||||
|
integer currentWins = llList2Integer(leaderboardWins, playerIndex);
|
||||||
|
leaderboardWins = llListReplaceList(leaderboardWins, [currentWins + 1], playerIndex, playerIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveLeaderboardData();
|
||||||
|
updateLeaderboard(leaderboardNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort leaderboard by wins
|
||||||
|
list getSortedLeaderboard() {
|
||||||
|
list combined = [];
|
||||||
|
integer i;
|
||||||
|
|
||||||
|
for (i = 0; i < llGetListLength(leaderboardNames); i++) {
|
||||||
|
string playerName = llList2String(leaderboardNames, i);
|
||||||
|
integer wins = llList2Integer(leaderboardWins, i);
|
||||||
|
combined += [playerName + ":" + (string)wins];
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save leaderboard data
|
||||||
|
saveLeaderboardData() {
|
||||||
|
integer i;
|
||||||
|
llLinksetDataWrite("lb_count", (string)llGetListLength(leaderboardNames));
|
||||||
|
for (i = 0; i < llGetListLength(leaderboardNames); i++) {
|
||||||
|
string playerName = llList2String(leaderboardNames, i);
|
||||||
|
integer wins = llList2Integer(leaderboardWins, i);
|
||||||
|
llLinksetDataWrite("lb_player_" + (string)i, playerName);
|
||||||
|
llLinksetDataWrite("lb_wins_" + (string)i, (string)wins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load leaderboard data
|
||||||
|
loadLeaderboardData() {
|
||||||
|
string countStr = llLinksetDataRead("lb_count");
|
||||||
|
if (countStr == "") return;
|
||||||
|
|
||||||
|
integer count = (integer)countStr;
|
||||||
|
leaderboardNames = [];
|
||||||
|
leaderboardWins = [];
|
||||||
|
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 != "") {
|
||||||
|
leaderboardNames += [playerName];
|
||||||
|
leaderboardWins += [(integer)winsStr];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset leaderboard data
|
||||||
|
resetLeaderboard() {
|
||||||
|
leaderboardNames = [];
|
||||||
|
leaderboardWins = [];
|
||||||
|
integer i;
|
||||||
|
for (i = 0; i < 100; i++) {
|
||||||
|
llLinksetDataDelete("lb_player_" + (string)i);
|
||||||
|
llLinksetDataDelete("lb_wins_" + (string)i);
|
||||||
|
}
|
||||||
|
llLinksetDataDelete("lb_count");
|
||||||
|
updateLeaderboard([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = [];
|
||||||
|
playerLives = [];
|
||||||
|
playerProfiles = [];
|
||||||
|
|
||||||
|
// NOTE: leaderboardNames and leaderboardWins are NOT cleared - they persist
|
||||||
|
|
||||||
|
// Clear any pending HTTP requests
|
||||||
|
httpRequests = [];
|
||||||
|
profileRequests = [];
|
||||||
|
|
||||||
|
// Reset all player prims to default state
|
||||||
|
integer i;
|
||||||
|
for (i = 0; i < 10; i++) {
|
||||||
|
integer profilePrimIndex = FIRST_PLAYER_PRIM + (i * 2);
|
||||||
|
integer heartsPrimIndex = FIRST_PLAYER_PRIM + (i * 2) + 1;
|
||||||
|
|
||||||
|
// Reset profile prim to default
|
||||||
|
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,
|
||||||
|
PRIM_TEXT, "", <1,1,1>, 0.0
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Reset hearts prim to 3 hearts
|
||||||
|
llSetLinkPrimitiveParamsFast(heartsPrimIndex, [
|
||||||
|
PRIM_TEXTURE, ALL_SIDES, TEXTURE_3_HEARTS, <1,1,0>, <0,0,0>, 0.0,
|
||||||
|
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0,
|
||||||
|
PRIM_TEXT, "", <1,1,1>, 0.0
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a player name indicates it's a bot
|
||||||
|
integer isBot(string playerName) {
|
||||||
|
return (llSubStringIndex(playerName, "TestBot") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
string getHeartTexture(integer lives) {
|
||||||
|
if (lives <= 0) return TEXTURE_0_HEARTS;
|
||||||
|
else if (lives == 1) return TEXTURE_1_HEARTS;
|
||||||
|
else if (lives == 2) return TEXTURE_2_HEARTS;
|
||||||
|
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
|
||||||
|
integer playerIndex = llListFindList(playerNames, [playerName]);
|
||||||
|
|
||||||
|
if (playerIndex == -1) {
|
||||||
|
// New player - find next available slot
|
||||||
|
playerIndex = llGetListLength(playerNames);
|
||||||
|
if (playerIndex >= 10) {
|
||||||
|
llOwnerSay("WARNING: Maximum 10 players supported, ignoring: " + playerName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to tracking lists
|
||||||
|
playerNames += [playerName];
|
||||||
|
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
|
||||||
|
integer profilePrimIndex = FIRST_PLAYER_PRIM + (playerIndex * 2);
|
||||||
|
integer heartsPrimIndex = FIRST_PLAYER_PRIM + (playerIndex * 2) + 1;
|
||||||
|
|
||||||
|
// Update profile prim texture
|
||||||
|
string profileTexture = profileUUID;
|
||||||
|
|
||||||
|
// Check if we already have a cached profile texture for this player
|
||||||
|
if (playerIndex < llGetListLength(playerProfiles)) {
|
||||||
|
string cachedTexture = llList2String(playerProfiles, playerIndex);
|
||||||
|
if (cachedTexture != "" && cachedTexture != 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no cached texture or using original UUID, determine what to use
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
llSetLinkPrimitiveParamsFast(profilePrimIndex, [
|
||||||
|
PRIM_TEXTURE, ALL_SIDES, profileTexture, <1,1,0>, <0,0,0>, 0.0,
|
||||||
|
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0,
|
||||||
|
PRIM_TEXT, "", <1,1,1>, 0.0 // Remove player name text
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update hearts prim texture
|
||||||
|
string heartTexture = getHeartTexture(lives);
|
||||||
|
llSetLinkPrimitiveParamsFast(heartsPrimIndex, [
|
||||||
|
PRIM_TEXTURE, ALL_SIDES, heartTexture, <1,1,0>, <0,0,0>, 0.0,
|
||||||
|
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0,
|
||||||
|
PRIM_TEXT, "", <1,0,0>, 0.0 // Remove lives text
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
default {
|
||||||
|
state_entry() {
|
||||||
|
// Clear any old floating text on root prim
|
||||||
|
llSetText("", <1,1,1>, 0.0);
|
||||||
|
|
||||||
|
// Initialize profile picture extraction constants
|
||||||
|
profile_key_prefix_length = llStringLength(profile_key_prefix);
|
||||||
|
profile_img_prefix_length = llStringLength(profile_img_prefix);
|
||||||
|
|
||||||
|
llListen(SCOREBOARD_CHANNEL, "", "", "");
|
||||||
|
llListen(LEADERBOARD_CHANNEL, "", "", "");
|
||||||
|
loadLeaderboardData();
|
||||||
|
setupPlayerGrid();
|
||||||
|
|
||||||
|
// Generate initial leaderboard texture
|
||||||
|
llSetTimerEvent(1.0); // Short delay to ensure prims are set up
|
||||||
|
|
||||||
|
llOwnerSay("Game Scoreboard Manager ready - Grid layout initialized");
|
||||||
|
llOwnerSay("Touch me to re-run setup.");
|
||||||
|
}
|
||||||
|
|
||||||
|
timer() {
|
||||||
|
llSetTimerEvent(0.0); // Stop timer
|
||||||
|
generateLeaderboardText(); // Generate initial leaderboard
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(integer channel, string senderName, key id, string message) {
|
||||||
|
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) {
|
||||||
|
string playerName = llList2String(parts, 1);
|
||||||
|
integer lives = (integer)llList2String(parts, 2);
|
||||||
|
string profileUUID = llList2String(parts, 3);
|
||||||
|
|
||||||
|
updatePlayerDisplay(playerName, lives, profileUUID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message == "CLEAR_GAME") {
|
||||||
|
// Reset to default state
|
||||||
|
clearAllPlayers(); // Clear all current players
|
||||||
|
updateActionsPrim("Title"); // Reset to title instead of "Game Cleared"
|
||||||
|
generateLeaderboardText(); // Refresh leaderboard display
|
||||||
|
}
|
||||||
|
else if (llSubStringIndex(message, "GAME_START|") == 0) {
|
||||||
|
updateActionsPrim("Title");
|
||||||
|
}
|
||||||
|
else if (llSubStringIndex(message, "GAME_STATUS|") == 0) {
|
||||||
|
// Format: GAME_STATUS|StatusText
|
||||||
|
list parts = llParseString2List(message, ["|"], []);
|
||||||
|
if (llGetListLength(parts) >= 2) {
|
||||||
|
string status = llList2String(parts, 1);
|
||||||
|
updateActionsPrim(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (llSubStringIndex(message, "LEADERBOARD|") == 0) {
|
||||||
|
// Format: LEADERBOARD|Player1:Score1|Player2:Score2|...
|
||||||
|
list parts = llParseString2List(message, ["|"], []);
|
||||||
|
list playerData = llDeleteSubList(parts, 0, 0); // Remove "LEADERBOARD"
|
||||||
|
updateLeaderboard(playerData);
|
||||||
|
} else if (llSubStringIndex(message, "GAME_WON|") == 0) {
|
||||||
|
list parts = llParseString2List(message, ["|"], []);
|
||||||
|
if (llGetListLength(parts) >= 2) {
|
||||||
|
string winnerName = llList2String(parts, 1);
|
||||||
|
handleGameWon(winnerName);
|
||||||
|
}
|
||||||
|
} else if (llSubStringIndex(message, "RESET_LEADERBOARD") == 0) {
|
||||||
|
resetLeaderboard();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
touch_start(integer total_number) {
|
||||||
|
// Owner can touch to manually re-run setup
|
||||||
|
if (llDetectedKey(0) == llGetOwner()) {
|
||||||
|
llOwnerSay("Owner touched - re-running setup...");
|
||||||
|
setupPlayerGrid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataserver(key query_id, string data) {
|
||||||
|
// Handle profile picture data responses
|
||||||
|
integer requestIndex = llListFindList(profileRequests, [query_id]);
|
||||||
|
if (requestIndex != -1) {
|
||||||
|
// Found the request - get the corresponding player index and data type
|
||||||
|
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);
|
||||||
|
|
||||||
|
llSetLinkPrimitiveParamsFast(profilePrimIndex, [
|
||||||
|
PRIM_TEXTURE, ALL_SIDES, data, <1,1,0>, <0,0,0>, 0.0,
|
||||||
|
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)
|
||||||
|
profileRequests = llDeleteSubList(profileRequests, requestIndex, requestIndex + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response(key request_id, integer status, list metadata, string body) {
|
||||||
|
// Handle HTTP response for profile picture requests
|
||||||
|
integer requestIndex = llListFindList(httpRequests, [request_id]);
|
||||||
|
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
|
||||||
|
integer s1 = llSubStringIndex(body, profile_key_prefix);
|
||||||
|
integer s1l = profile_key_prefix_length;
|
||||||
|
|
||||||
|
if (s1 == -1) {
|
||||||
|
// 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, [
|
||||||
|
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 {
|
||||||
|
// Extract the UUID
|
||||||
|
s1 += s1l;
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Update the profile texture
|
||||||
|
integer profilePrimIndex = FIRST_PLAYER_PRIM + (playerIndex * 2);
|
||||||
|
llSetLinkPrimitiveParamsFast(profilePrimIndex, [
|
||||||
|
PRIM_TEXTURE, ALL_SIDES, (string)profileUUID, <1,1,0>, <0,0,0>, 0.0,
|
||||||
|
PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 1.0
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
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
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the completed HTTP request (2 elements: requestID, playerIndex)
|
||||||
|
httpRequests = llDeleteSubList(httpRequests, requestIndex, requestIndex + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Helper.lsl
12
Helper.lsl
|
|
@ -122,6 +122,18 @@ default {
|
||||||
}
|
}
|
||||||
|
|
||||||
link_message(integer sender, integer num, string str, key id) {
|
link_message(integer sender, integer num, string str, key id) {
|
||||||
|
// Handle full reset from main controller
|
||||||
|
if (num == -99999 && str == "FULL_RESET") {
|
||||||
|
// Reset helper state
|
||||||
|
lives = [];
|
||||||
|
picksData = [];
|
||||||
|
perilPlayer = "";
|
||||||
|
names = [];
|
||||||
|
pickQueue = [];
|
||||||
|
llOwnerSay("🧘 Helper Module reset!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (num == MSG_SYNC_GAME_STATE) {
|
if (num == MSG_SYNC_GAME_STATE) {
|
||||||
list parts = llParseString2List(str, ["~"], []);
|
list parts = llParseString2List(str, ["~"], []);
|
||||||
lives = llCSV2List(llList2String(parts, 0));
|
lives = llCSV2List(llList2String(parts, 0));
|
||||||
|
|
|
||||||
56
Leaderboard_Bridge.lsl
Normal file
56
Leaderboard_Bridge.lsl
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
253
Leaderboard_Manager.lsl
Normal file
253
Leaderboard_Manager.lsl
Normal file
|
|
@ -0,0 +1,253 @@
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
331
Main.lsl
331
Main.lsl
|
|
@ -13,6 +13,17 @@ integer numberPickChannel = -77888;
|
||||||
integer rollDialogChannel = -77999;
|
integer rollDialogChannel = -77999;
|
||||||
integer DIALOG_CHANNEL = -88888;
|
integer DIALOG_CHANNEL = -88888;
|
||||||
|
|
||||||
|
// Game timing settings to prevent dialog system overload
|
||||||
|
float BOT_PICK_DELAY = 2.0; // Delay after bot picks before next action
|
||||||
|
float HUMAN_PICK_DELAY = 1.0; // Delay after human picks before next action
|
||||||
|
float DIALOG_DELAY = 1.5; // Delay before showing dialogs
|
||||||
|
integer gameTimer = 0; // Timer for game flow delays
|
||||||
|
|
||||||
|
// Status message timing
|
||||||
|
float STATUS_DISPLAY_TIME = 8.0; // How long to show status messages on scoreboard
|
||||||
|
integer statusTimer = 0; // Track when status messages were sent
|
||||||
|
string lastStatus = ""; // Track last status sent
|
||||||
|
|
||||||
list players = [];
|
list players = [];
|
||||||
list names = [];
|
list names = [];
|
||||||
list lives = [];
|
list lives = [];
|
||||||
|
|
@ -39,6 +50,14 @@ integer MSG_QUERY_READY_STATE = 210;
|
||||||
integer MSG_READY_STATE_RESULT = 211;
|
integer MSG_READY_STATE_RESULT = 211;
|
||||||
integer MSG_CLEANUP_ALL_FLOATERS = 212;
|
integer MSG_CLEANUP_ALL_FLOATERS = 212;
|
||||||
integer MSG_SHOW_ROLL_DIALOG = 301;
|
integer MSG_SHOW_ROLL_DIALOG = 301;
|
||||||
|
integer MSG_GET_CURRENT_DIALOG = 302;
|
||||||
|
integer MSG_PLAYER_WON = 551;
|
||||||
|
integer MSG_RESET_LEADERBOARD = 553; // Custom message to reset leaderboard
|
||||||
|
|
||||||
|
// Display configuration
|
||||||
|
integer CONTROLLER_FACE = 1; // Face to display start image on (front face)
|
||||||
|
string TEXTURE_START = "title_start"; // Start image texture
|
||||||
|
string TEXTURE_GAME_ACTIVE = "game_active"; // Optional: different texture during game
|
||||||
|
|
||||||
integer MSG_GET_DICE_TYPE = 1001;
|
integer MSG_GET_DICE_TYPE = 1001;
|
||||||
integer MSG_GET_PICKS_REQUIRED = 1002;
|
integer MSG_GET_PICKS_REQUIRED = 1002;
|
||||||
|
|
@ -61,6 +80,17 @@ integer lastWarning = 0;
|
||||||
|
|
||||||
key currentPicker;
|
key currentPicker;
|
||||||
|
|
||||||
|
// Send status message to scoreboard with timing
|
||||||
|
sendStatusMessage(string status) {
|
||||||
|
statusTimer = llGetUnixTime();
|
||||||
|
lastStatus = status;
|
||||||
|
llRegionSay(-12345, "GAME_STATUS|" + status);
|
||||||
|
llOwnerSay("📢 Status: " + status + " (showing for " + (string)STATUS_DISPLAY_TIME + "s)");
|
||||||
|
|
||||||
|
// Start timer to automatically clear status after display time
|
||||||
|
llSetTimerEvent(STATUS_DISPLAY_TIME + 1.0); // Add 1 second buffer
|
||||||
|
}
|
||||||
|
|
||||||
// Forward game state to helpers when it changes
|
// Forward game state to helpers when it changes
|
||||||
updateHelpers() {
|
updateHelpers() {
|
||||||
// Don't send peril player name if game hasn't started yet
|
// Don't send peril player name if game hasn't started yet
|
||||||
|
|
@ -111,6 +141,26 @@ updateHelpers() {
|
||||||
"~" + perilForSync + "~" + llList2CSV(names);
|
"~" + perilForSync + "~" + llList2CSV(names);
|
||||||
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, serialized, NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_SYNC_GAME_STATE, serialized, NULL_KEY);
|
||||||
llMessageLinked(LINK_SET, MSG_SYNC_PICKQUEUE, llList2CSV(pickQueue), 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;
|
||||||
|
for (j = 0; j < llGetListLength(names); j++) {
|
||||||
|
string playerName = llList2String(names, j);
|
||||||
|
integer playerLives = llList2Integer(lives, j);
|
||||||
|
key playerKey = llList2Key(players, j);
|
||||||
|
|
||||||
|
// Use the actual player key as the profile UUID
|
||||||
|
string profileUUID = (string)playerKey;
|
||||||
|
|
||||||
|
// Send PLAYER_UPDATE message to scoreboard
|
||||||
|
string updateMsg = "PLAYER_UPDATE|" + playerName + "|" + (string)playerLives + "|" + profileUUID;
|
||||||
|
llOwnerSay("DEBUG: Sending to scoreboard: " + updateMsg);
|
||||||
|
llRegionSay(-12345, updateMsg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request helper-calculated values
|
// Request helper-calculated values
|
||||||
|
|
@ -148,15 +198,36 @@ resetGame() {
|
||||||
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_CLEANUP_FLOAT, (string)ch, NULL_KEY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset all game state variables
|
||||||
players = names = lives = picksData = globalPickedNumbers = readyPlayers = [];
|
players = names = lives = picksData = globalPickedNumbers = readyPlayers = [];
|
||||||
floaterChannels = []; // Clear the tracked channels
|
floaterChannels = []; // Clear the tracked channels
|
||||||
perilPlayer = "";
|
perilPlayer = "";
|
||||||
pickQueue = [];
|
pickQueue = [];
|
||||||
currentPickerIdx = 0;
|
currentPickerIdx = 0;
|
||||||
roundStarted = FALSE; // Reset round flag
|
roundStarted = FALSE; // Reset round flag
|
||||||
|
currentPicker = NULL_KEY; // Reset current picker
|
||||||
|
timeoutTimer = 0; // Reset timeout timer
|
||||||
|
lastWarning = 0; // Reset warning timer
|
||||||
|
diceType = 6; // Reset dice type to default
|
||||||
|
|
||||||
|
// Send reset message to all other scripts to clear their state
|
||||||
|
llMessageLinked(LINK_SET, -99999, "FULL_RESET", NULL_KEY);
|
||||||
llSay(syncChannel, "RESET");
|
llSay(syncChannel, "RESET");
|
||||||
llOwnerSay(" Game reset!");
|
|
||||||
llSleep(0.2);
|
// Send CLEAR_GAME message to scoreboard to reset display
|
||||||
|
llRegionSay(-12345, "CLEAR_GAME");
|
||||||
|
llOwnerSay("🎮 Game reset! All state cleared (including scoreboard).");
|
||||||
|
|
||||||
|
// Clear status tracking
|
||||||
|
statusTimer = 0;
|
||||||
|
lastStatus = "";
|
||||||
|
|
||||||
|
// Reset controller display to start image
|
||||||
|
llSetTexture(TEXTURE_START, CONTROLLER_FACE);
|
||||||
|
llSetText("🎮 PERIL DICE GAME\nTouch to play!", <1.0, 1.0, 0.0>, 1.0);
|
||||||
|
|
||||||
|
llSleep(0.5); // Give other scripts time to process reset
|
||||||
llSetTimerEvent(0);
|
llSetTimerEvent(0);
|
||||||
updateHelpers();
|
updateHelpers();
|
||||||
}
|
}
|
||||||
|
|
@ -249,9 +320,17 @@ startNextRound() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If somehow called with exactly one player, treat them as the winner and reset.
|
// If somehow called with exactly one player, treat them as the winner and reset.
|
||||||
if (llGetListLength(names) == 1) {
|
if (llGetListLength(names) == 1) {
|
||||||
llSay(0, "✨ ULTIMATE VICTORY! " + llList2String(names, 0) + " is the Ultimate Survivor!");
|
string winner = llList2String(names, 0);
|
||||||
resetGame();
|
llSay(0, "✨ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!");
|
||||||
|
// Trigger victory confetti and record win
|
||||||
|
llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY);
|
||||||
|
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
|
||||||
|
llRegionSay(-12345, "GAME_WON|" + winner); // Send winner info to scoreboard
|
||||||
|
sendStatusMessage("Victory"); // Show victory status
|
||||||
|
// Wait for victory status to display before reset
|
||||||
|
llSleep(STATUS_DISPLAY_TIME + 1.0);
|
||||||
|
resetGame();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// DON'T reset round flag during continuation rounds - only reset for brand new games
|
// DON'T reset round flag during continuation rounds - only reset for brand new games
|
||||||
|
|
@ -263,7 +342,12 @@ startNextRound() {
|
||||||
integer randomIdx = (integer)llFrand(llGetListLength(names));
|
integer randomIdx = (integer)llFrand(llGetListLength(names));
|
||||||
perilPlayer = llList2String(names, randomIdx);
|
perilPlayer = llList2String(names, randomIdx);
|
||||||
llSay(0, "🎯 " + perilPlayer + " has been randomly selected and is now in peril!");
|
llSay(0, "🎯 " + perilPlayer + " has been randomly selected and is now in peril!");
|
||||||
|
sendStatusMessage("Peril Selected"); // Show peril selected status when peril player is chosen
|
||||||
} else {
|
} else {
|
||||||
|
// Continue showing peril selected status for existing peril player
|
||||||
|
sendStatusMessage("Peril Selected");
|
||||||
|
// Add delay to allow this status to display before next phase
|
||||||
|
llSleep(STATUS_DISPLAY_TIME * 0.6); // Wait 60% of status display time (~5 seconds)
|
||||||
}
|
}
|
||||||
picksData = [];
|
picksData = [];
|
||||||
globalPickedNumbers = [];
|
globalPickedNumbers = [];
|
||||||
|
|
@ -292,6 +376,9 @@ showNextPickerDialog() {
|
||||||
string firstName = llList2String(pickQueue, currentPickerIdx);
|
string firstName = llList2String(pickQueue, currentPickerIdx);
|
||||||
currentPicker = llList2Key(players, llListFindList(names, [firstName]));
|
currentPicker = llList2Key(players, llListFindList(names, [firstName]));
|
||||||
|
|
||||||
|
// Add delay before showing dialog to prevent system overload
|
||||||
|
llSleep(DIALOG_DELAY);
|
||||||
|
|
||||||
// Check if this is a bot (TestBot names)
|
// Check if this is a bot (TestBot names)
|
||||||
if (llSubStringIndex(firstName, "TestBot") == 0) {
|
if (llSubStringIndex(firstName, "TestBot") == 0) {
|
||||||
// Get number of picks needed based on peril player's lives
|
// Get number of picks needed based on peril player's lives
|
||||||
|
|
@ -335,6 +422,11 @@ default {
|
||||||
llOwnerSay("🎮 Main Controller ready!");
|
llOwnerSay("🎮 Main Controller ready!");
|
||||||
llOwnerSay("🎮 Main Controller key: " + (string)llGetKey());
|
llOwnerSay("🎮 Main Controller key: " + (string)llGetKey());
|
||||||
llOwnerSay("🎮 Main Controller position: " + (string)llGetPos());
|
llOwnerSay("🎮 Main Controller position: " + (string)llGetPos());
|
||||||
|
|
||||||
|
// Set the start image on the controller prim
|
||||||
|
llSetTexture(TEXTURE_START, CONTROLLER_FACE);
|
||||||
|
llSetText("🎮 PERIL DICE GAME\nTouch to play!", <1.0, 1.0, 0.0>, 1.0);
|
||||||
|
|
||||||
llListen(DIALOG_CHANNEL, "", NULL_KEY, "");
|
llListen(DIALOG_CHANNEL, "", NULL_KEY, "");
|
||||||
llListen(-9999, "", NULL_KEY, ""); // Listen for bot responses
|
llListen(-9999, "", NULL_KEY, ""); // Listen for bot responses
|
||||||
llListen(rollDialogChannel, "", NULL_KEY, ""); // Listen for roll dialog responses
|
llListen(rollDialogChannel, "", NULL_KEY, ""); // Listen for roll dialog responses
|
||||||
|
|
@ -344,6 +436,56 @@ default {
|
||||||
llOwnerSay("Touched by: " + (string)llDetectedKey(0));
|
llOwnerSay("Touched by: " + (string)llDetectedKey(0));
|
||||||
key toucher = llDetectedKey(0);
|
key toucher = llDetectedKey(0);
|
||||||
integer idx = llListFindList(players, [toucher]);
|
integer idx = llListFindList(players, [toucher]);
|
||||||
|
|
||||||
|
// Check if player has a current dialog they can recover
|
||||||
|
if (idx != -1 && toucher != llGetOwner() && roundStarted) {
|
||||||
|
string playerName = llList2String(names, idx);
|
||||||
|
// Check if this player is the current picker waiting for a dialog
|
||||||
|
if (currentPicker == toucher) {
|
||||||
|
llRegionSayTo(toucher, 0, "🔄 Restoring your number picking dialog...");
|
||||||
|
llMessageLinked(LINK_SET, MSG_GET_CURRENT_DIALOG, playerName, toucher);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check if this player is the peril player who might need a roll dialog
|
||||||
|
else if (playerName == perilPlayer && currentPickerIdx >= llGetListLength(pickQueue)) {
|
||||||
|
llRegionSayTo(toucher, 0, "🔄 Restoring your roll dialog...");
|
||||||
|
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, toucher);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for owner during gameplay
|
||||||
|
if (toucher == llGetOwner() && roundStarted) {
|
||||||
|
string ownerName = llKey2Name(toucher);
|
||||||
|
list options = [];
|
||||||
|
string menuText = "Game in progress. What would you like to do?";
|
||||||
|
|
||||||
|
// Check if owner has active dialogs they can recover
|
||||||
|
integer hasActiveDialog = FALSE;
|
||||||
|
if (currentPicker == toucher) {
|
||||||
|
options += ["🔄 Recover Pick Dialog"];
|
||||||
|
hasActiveDialog = TRUE;
|
||||||
|
}
|
||||||
|
if (ownerName == perilPlayer && currentPickerIdx >= llGetListLength(pickQueue)) {
|
||||||
|
options += ["🔄 Recover Roll Dialog"];
|
||||||
|
hasActiveDialog = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always offer admin menu
|
||||||
|
options += ["🔧 Admin Menu"];
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show choice dialog
|
||||||
|
llDialog(toucher, menuText, options, DIALOG_CHANNEL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (toucher == llGetOwner()) {
|
if (toucher == llGetOwner()) {
|
||||||
// Determine if this will be the first registered player (starter) before registration
|
// Determine if this will be the first registered player (starter) before registration
|
||||||
integer isStarter = FALSE;
|
integer isStarter = FALSE;
|
||||||
|
|
@ -364,7 +506,7 @@ default {
|
||||||
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, ownerName + "|" + (string)toucher, NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, ownerName + "|" + (string)toucher, NULL_KEY);
|
||||||
} else {
|
} else {
|
||||||
// Already registered; check if owner is the first human player
|
// Already registered; check if owner is the first human player
|
||||||
string ownerName = llList2String(names, idx);
|
// Owner is starter if they are at index 0 OR if all players before them are bots
|
||||||
isStarter = TRUE; // Default to TRUE for owner
|
isStarter = TRUE; // Default to TRUE for owner
|
||||||
integer k;
|
integer k;
|
||||||
for (k = 0; k < idx && isStarter; k++) {
|
for (k = 0; k < idx && isStarter; k++) {
|
||||||
|
|
@ -372,11 +514,17 @@ default {
|
||||||
// If there's a human player before the owner, owner is not starter
|
// If there's a human player before the owner, owner is not starter
|
||||||
if (llSubStringIndex(existingName, "TestBot") != 0) {
|
if (llSubStringIndex(existingName, "TestBot") != 0) {
|
||||||
isStarter = FALSE;
|
isStarter = FALSE;
|
||||||
llOwnerSay("❌ Found human player before owner, not starter");
|
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
|
// 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);
|
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|" + (string)isStarter, toucher);
|
||||||
} else if (idx != -1) {
|
} else if (idx != -1) {
|
||||||
// For non-owner players, check if they're the first human player
|
// For non-owner players, check if they're the first human player
|
||||||
|
|
@ -390,6 +538,27 @@ default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher);
|
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher);
|
||||||
|
} else {
|
||||||
|
// Unregistered non-owner player - register them and show menu
|
||||||
|
string playerName = llKey2Name(toucher);
|
||||||
|
if (playerName != "") {
|
||||||
|
// Send a registration request; Main.lsl will handle adding them and rezzing a float
|
||||||
|
llMessageLinked(LINK_SET, MSG_REGISTER_PLAYER, playerName + "|" + (string)toucher, NULL_KEY);
|
||||||
|
// Determine if this will be the first human player (starter)
|
||||||
|
integer isStarter = TRUE; // Default to TRUE
|
||||||
|
integer n;
|
||||||
|
for (n = 0; n < llGetListLength(names) && isStarter; n++) {
|
||||||
|
string existingName = llList2String(names, n);
|
||||||
|
// If there's already a human player (not a bot), this player is not starter
|
||||||
|
if (llSubStringIndex(existingName, "TestBot") != 0) {
|
||||||
|
isStarter = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Show the player menu with the appropriate starter flag
|
||||||
|
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "player|" + (string)isStarter, toucher);
|
||||||
|
} else {
|
||||||
|
llOwnerSay("⚠️ Could not get name for toucher: " + (string)toucher);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -523,7 +692,7 @@ default {
|
||||||
readyPlayers += [newName];
|
readyPlayers += [newName];
|
||||||
llSay(0, "🤖 " + newName + " boots up with deadly precision - ready to play! 🤖");
|
llSay(0, "🤖 " + newName + " boots up with deadly precision - ready to play! 🤖");
|
||||||
} else {
|
} else {
|
||||||
// Check if this is the first human player (starter - automatically ready)
|
// Check if this is the first human player (starter - automatically ready only if owner)
|
||||||
integer humanCount = 0;
|
integer humanCount = 0;
|
||||||
integer i;
|
integer i;
|
||||||
for (i = 0; i < llGetListLength(names); i++) {
|
for (i = 0; i < llGetListLength(names); i++) {
|
||||||
|
|
@ -533,7 +702,14 @@ default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (humanCount == 1) { // This is the first human player
|
if (humanCount == 1) { // This is the first human player
|
||||||
llSay(0, "👑 " + newName + " steps forward as the game master - automatically ready for the deadly challenge! 👑");
|
if (newKey == llGetOwner()) {
|
||||||
|
// Owner is auto-ready as starter
|
||||||
|
readyPlayers += [newName];
|
||||||
|
llSay(0, "👑 " + newName + " steps forward as the game master - automatically ready for the deadly challenge! 👑");
|
||||||
|
} else {
|
||||||
|
// Non-owner starter can toggle ready state
|
||||||
|
llSay(0, "👑 " + newName + " steps forward as the game master! Touch to set your ready status.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The float will be rezzed by the Floater Manager after it processes
|
// The float will be rezzed by the Floater Manager after it processes
|
||||||
|
|
@ -566,17 +742,30 @@ default {
|
||||||
|
|
||||||
llOwnerSay("🗑️ Eliminated " + eliminatedPlayer + ". Remaining players: " + (string)llGetListLength(names));
|
llOwnerSay("🗑️ Eliminated " + eliminatedPlayer + ". Remaining players: " + (string)llGetListLength(names));
|
||||||
|
|
||||||
|
// Show elimination status when someone is actually eliminated
|
||||||
|
sendStatusMessage("Elimination");
|
||||||
|
|
||||||
// Update all scripts with the new player lists after elimination
|
// Update all scripts with the new player lists after elimination
|
||||||
updateHelpers();
|
updateHelpers();
|
||||||
|
|
||||||
// Check if game should end (1 or fewer players remaining)
|
// Check if game should end (1 or fewer players remaining)
|
||||||
if (llGetListLength(names) <= 1) {
|
if (llGetListLength(names) <= 1) {
|
||||||
if (llGetListLength(names) == 1) {
|
if (llGetListLength(names) == 1) {
|
||||||
llSay(0, "✨ ULTIMATE VICTORY! " + llList2String(names, 0) + " is the Ultimate Survivor!");
|
string winner = llList2String(names, 0);
|
||||||
// Trigger victory confetti
|
llSay(0, "✨ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!");
|
||||||
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
|
// Trigger victory confetti and record win
|
||||||
|
llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY);
|
||||||
|
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
|
||||||
|
llRegionSay(-12345, "GAME_WON|" + winner); // Send winner info to scoreboard
|
||||||
|
// Wait for elimination status to display before showing victory
|
||||||
|
llSleep(STATUS_DISPLAY_TIME * 0.8); // Wait 80% of status display time (~6.4 seconds)
|
||||||
|
sendStatusMessage("Victory"); // Show victory status
|
||||||
|
// Wait for victory status to display before reset
|
||||||
|
llSleep(STATUS_DISPLAY_TIME + 1.0);
|
||||||
} else {
|
} else {
|
||||||
llSay(0, "💀 DESPAIR WINS! No Ultimate Survivors remain!");
|
llSay(0, "💀 DESPAIR WINS! No Ultimate Survivors remain!");
|
||||||
|
// Wait for elimination status to display before reset (elimination already sent above)
|
||||||
|
llSleep(STATUS_DISPLAY_TIME + 1.0);
|
||||||
}
|
}
|
||||||
resetGame();
|
resetGame();
|
||||||
return;
|
return;
|
||||||
|
|
@ -625,9 +814,10 @@ default {
|
||||||
if (num == 998) {
|
if (num == 998) {
|
||||||
list parts = llParseString2List(str, ["|"], []);
|
list parts = llParseString2List(str, ["|"], []);
|
||||||
if (llList2String(parts, 0) == "GAME_WON") {
|
if (llList2String(parts, 0) == "GAME_WON") {
|
||||||
string winner = llList2String(parts, 1);
|
string winner = llList2String(parts, 1);
|
||||||
llSleep(2.0); // Let celebration message display
|
llRegionSay(-12346, "GAME_WON|" + winner); // Send winner info to scoreboard
|
||||||
resetGame();
|
llSleep(2.0); // Let celebration message display
|
||||||
|
resetGame();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -758,13 +948,17 @@ default {
|
||||||
updateHelpers();
|
updateHelpers();
|
||||||
llOwnerSay("👋 " + leavingName + " left the game");
|
llOwnerSay("👋 " + leavingName + " left the game");
|
||||||
// Check if game should end (less than 2 players)
|
// Check if game should end (less than 2 players)
|
||||||
if (llGetListLength(names) == 1) {
|
if (llGetListLength(names) == 1) {
|
||||||
llSay(0, "✨ ULTIMATE VICTORY! " + llList2String(names, 0) + " survives the killing game!");
|
string winner = llList2String(names, 0);
|
||||||
// Trigger victory confetti
|
llSay(0, "✨ ULTIMATE VICTORY! " + winner + " is the Ultimate Survivor!");
|
||||||
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
|
// Trigger victory confetti and record win
|
||||||
resetGame();
|
llMessageLinked(LINK_SET, MSG_PLAYER_WON, winner, NULL_KEY);
|
||||||
} else if (llGetListLength(names) == 0) {
|
llMessageLinked(LINK_SET, 995, "VICTORY_CONFETTI", NULL_KEY);
|
||||||
resetGame();
|
llRegionSay(-12345, "GAME_WON|" + winner); // Send winner info to scoreboard
|
||||||
|
sendStatusMessage("Victory"); // Show victory status
|
||||||
|
// Wait for victory status to display before reset
|
||||||
|
llSleep(STATUS_DISPLAY_TIME + 1.0);
|
||||||
|
resetGame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -854,6 +1048,9 @@ default {
|
||||||
updateHelpers();
|
updateHelpers();
|
||||||
llSay(0, "🎯 " + playerName + " stakes their life on numbers: " + picksStr + " 🎲");
|
llSay(0, "🎯 " + playerName + " stakes their life on numbers: " + picksStr + " 🎲");
|
||||||
|
|
||||||
|
// Add delay before moving to next picker to prevent dialog system overload
|
||||||
|
llSleep(HUMAN_PICK_DELAY);
|
||||||
|
|
||||||
// Move to next picker
|
// Move to next picker
|
||||||
currentPickerIdx++;
|
currentPickerIdx++;
|
||||||
if (currentPickerIdx < llGetListLength(pickQueue)) {
|
if (currentPickerIdx < llGetListLength(pickQueue)) {
|
||||||
|
|
@ -912,6 +1109,9 @@ default {
|
||||||
updateHelpers();
|
updateHelpers();
|
||||||
llSay(0, "🎯 " + playerName + " (bot) stakes their digital life on numbers: " + picksStr + " 🎲");
|
llSay(0, "🎯 " + playerName + " (bot) stakes their digital life on numbers: " + picksStr + " 🎲");
|
||||||
|
|
||||||
|
// Add delay after bot picks to prevent dialog system overload
|
||||||
|
llSleep(BOT_PICK_DELAY);
|
||||||
|
|
||||||
// Move to next picker
|
// Move to next picker
|
||||||
currentPickerIdx++;
|
currentPickerIdx++;
|
||||||
if (currentPickerIdx < llGetListLength(pickQueue)) {
|
if (currentPickerIdx < llGetListLength(pickQueue)) {
|
||||||
|
|
@ -946,23 +1146,45 @@ default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channel == DIALOG_CHANNEL) {
|
if (channel == DIALOG_CHANNEL) {
|
||||||
|
// Handle owner choice dialog responses during gameplay
|
||||||
|
if (id == llGetOwner() && roundStarted) {
|
||||||
|
if (msg == "🔄 Recover Pick Dialog") {
|
||||||
|
string ownerName = llKey2Name(id);
|
||||||
|
llRegionSayTo(id, 0, "🔄 Restoring your number picking dialog...");
|
||||||
|
llMessageLinked(LINK_SET, MSG_GET_CURRENT_DIALOG, ownerName, id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (msg == "🔄 Recover Roll Dialog") {
|
||||||
|
llRegionSayTo(id, 0, "🔄 Restoring your roll dialog...");
|
||||||
|
llMessageLinked(LINK_SET, MSG_SHOW_ROLL_DIALOG, perilPlayer, id);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Owner-specific commands: only owner messages in the dialog should be processed here.
|
// Owner-specific commands: only owner messages in the dialog should be processed here.
|
||||||
if (id == llGetOwner()) {
|
if (id == llGetOwner()) {
|
||||||
if (msg == "Reset Game") {
|
if (msg == "Reset Game") {
|
||||||
resetGame();
|
resetGame(); // Only reset current game, keep leaderboard
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (msg == "Reset Leaderboard") {
|
||||||
|
llRegionSay(-12345, "RESET_LEADERBOARD"); // Reset only leaderboard scores
|
||||||
|
llOwnerSay("🏆 Leaderboard scores reset - game wins cleared!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (msg == "Reset All") {
|
||||||
|
resetGame(); // Reset current game
|
||||||
|
llRegionSay(-12345, "RESET_LEADERBOARD"); // Reset leaderboard scores
|
||||||
|
llOwnerSay("🔄 Complete reset - game and leaderboard cleared!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Note: do not handle "Start Game" here; allow the generic start logic below to apply,
|
// Note: do not handle "Start Game" here; allow the generic start logic below to apply,
|
||||||
// so that minimum player checks are enforced and non-owner starters can initiate the game.
|
// so that minimum player checks are enforced and non-owner starters can initiate the game.
|
||||||
if (msg == "Dump Players") {
|
|
||||||
llOwnerSay(" Players: " + llList2CSV(names));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (msg == "Manage Picks") {
|
|
||||||
llOwnerSay(" Fetching list of players for pick management...");
|
|
||||||
llMessageLinked(LINK_SET, 202, "REQUEST_PLAYER_LIST", llGetOwner());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (msg == "Add Test Player") {
|
if (msg == "Add Test Player") {
|
||||||
// Ensure we do not exceed the maximum allowed players
|
// Ensure we do not exceed the maximum allowed players
|
||||||
if (llGetListLength(players) >= MAX_PLAYERS) {
|
if (llGetListLength(players) >= MAX_PLAYERS) {
|
||||||
|
|
@ -1034,6 +1256,11 @@ default {
|
||||||
|
|
||||||
// All checks passed, start the game
|
// All checks passed, start the game
|
||||||
llSay(0, "⚡ ALL PARTICIPANTS READY! THE DEADLY PERIL DICE GAME BEGINS! ⚡");
|
llSay(0, "⚡ ALL PARTICIPANTS READY! THE DEADLY PERIL DICE GAME BEGINS! ⚡");
|
||||||
|
|
||||||
|
// Change controller display to show game is active
|
||||||
|
llSetText("🎮 GAME IN PROGRESS\nRound " + (string)(llGetListLength(names)) + " players", <1.0, 0.2, 0.2>, 1.0);
|
||||||
|
|
||||||
|
sendStatusMessage("Title"); // Show title status at game start
|
||||||
startNextRound();
|
startNextRound();
|
||||||
requestDiceType();
|
requestDiceType();
|
||||||
}
|
}
|
||||||
|
|
@ -1043,4 +1270,38 @@ default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
timer() {
|
||||||
|
// Check if we need to clear status message
|
||||||
|
if (statusTimer > 0 && (llGetUnixTime() - statusTimer) >= STATUS_DISPLAY_TIME) {
|
||||||
|
// Clear the status and revert to title
|
||||||
|
statusTimer = 0;
|
||||||
|
lastStatus = "";
|
||||||
|
llRegionSay(-12345, "GAME_STATUS|Title");
|
||||||
|
llOwnerSay("📢 Status cleared - reverted to Title");
|
||||||
|
llSetTimerEvent(0); // Stop timer if no other timing needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle dialog timeout warnings (existing code)
|
||||||
|
if (timeoutTimer > 0) {
|
||||||
|
integer elapsed = llGetUnixTime() - timeoutTimer;
|
||||||
|
if (elapsed >= TIMEOUT_SECONDS) {
|
||||||
|
llOwnerSay("⏰ Dialog timeout reached!");
|
||||||
|
llSetTimerEvent(0);
|
||||||
|
timeoutTimer = 0;
|
||||||
|
} else {
|
||||||
|
integer remaining = TIMEOUT_SECONDS - elapsed;
|
||||||
|
if (remaining <= warning2min && lastWarning < warning2min) {
|
||||||
|
llOwnerSay("⚠️ " + (string)(remaining / 60) + " minutes remaining for dialog response");
|
||||||
|
lastWarning = warning2min;
|
||||||
|
} else if (remaining <= warning5min && lastWarning < warning5min) {
|
||||||
|
llOwnerSay("⚠️ " + (string)(remaining / 60) + " minutes remaining for dialog response");
|
||||||
|
lastWarning = warning5min;
|
||||||
|
} else if (remaining <= warning9min && lastWarning < warning9min) {
|
||||||
|
llOwnerSay("⚠️ " + (string)(remaining / 60) + " minutes remaining for dialog response");
|
||||||
|
lastWarning = warning9min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// === Dialog Handler (Updated for Peril Dice - Paginated Multiple Picks) ===
|
// === Dialog Handler (Updated for Peril Dice - Paginated Multiple Picks) ===
|
||||||
|
|
||||||
integer MSG_SHOW_DIALOG = 101;
|
integer MSG_SHOW_DIALOG = 101;
|
||||||
|
integer MSG_GET_CURRENT_DIALOG = 302;
|
||||||
integer numberPickChannel = -77888;
|
integer numberPickChannel = -77888;
|
||||||
|
|
||||||
// Pagination and picking state
|
// Pagination and picking state
|
||||||
|
|
@ -66,11 +67,19 @@ showPickDialog(string name, key id, integer diceType, integer picks) {
|
||||||
// Show number selection with pagination
|
// Show number selection with pagination
|
||||||
options = getNumbersForPage(currentPage, diceType);
|
options = getNumbersForPage(currentPage, diceType);
|
||||||
|
|
||||||
|
// If current page has no numbers, go back to page 0
|
||||||
|
if (llGetListLength(options) == 0 && currentPage > 0) {
|
||||||
|
currentPage = 0;
|
||||||
|
options = getNumbersForPage(currentPage, diceType);
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate total pages based on available numbers, not all numbers
|
// Calculate total pages based on available numbers, not all numbers
|
||||||
integer availableCount = diceType - llGetListLength(globallyPickedNumbers) - llGetListLength(currentPicks);
|
integer availableCount = diceType - llGetListLength(globallyPickedNumbers) - llGetListLength(currentPicks);
|
||||||
totalPages = (availableCount + NUMBERS_PER_PAGE - 1) / NUMBERS_PER_PAGE;
|
totalPages = (availableCount + NUMBERS_PER_PAGE - 1) / NUMBERS_PER_PAGE;
|
||||||
if (totalPages < 1) totalPages = 1;
|
if (totalPages < 1) totalPages = 1;
|
||||||
if (totalPages > 1) {
|
|
||||||
|
// Only show navigation if there are multiple pages AND current page has numbers
|
||||||
|
if (totalPages > 1 && llGetListLength(options) > 0) {
|
||||||
if (currentPage > 0) options += ["◀ Prev"];
|
if (currentPage > 0) options += ["◀ Prev"];
|
||||||
if (currentPage < totalPages - 1) options += ["Next ▶"];
|
if (currentPage < totalPages - 1) options += ["Next ▶"];
|
||||||
}
|
}
|
||||||
|
|
@ -107,8 +116,22 @@ default {
|
||||||
}
|
}
|
||||||
|
|
||||||
link_message(integer sender, integer num, string str, key id) {
|
link_message(integer sender, integer num, string str, key id) {
|
||||||
|
// Handle full reset from main controller
|
||||||
|
if (num == -99999 && str == "FULL_RESET") {
|
||||||
|
// Reset all dialog state
|
||||||
|
currentPlayer = "";
|
||||||
|
currentPlayerKey = NULL_KEY;
|
||||||
|
currentDiceType = 0;
|
||||||
|
picksNeeded = 1;
|
||||||
|
currentPicks = [];
|
||||||
|
globallyPickedNumbers = [];
|
||||||
|
currentPage = 0;
|
||||||
|
llOwnerSay("🎮 Number Picker Dialog Handler reset!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (num == MSG_SHOW_DIALOG) {
|
if (num == MSG_SHOW_DIALOG) {
|
||||||
list parts = llParseString2List(str, ["|"], []);
|
list parts = llParseString2List(str, ["|"], []);
|
||||||
if (llGetListLength(parts) < 3) {
|
if (llGetListLength(parts) < 3) {
|
||||||
llOwnerSay("⚠️ Malformed MSG_SHOW_DIALOG input: " + str);
|
llOwnerSay("⚠️ Malformed MSG_SHOW_DIALOG input: " + str);
|
||||||
return;
|
return;
|
||||||
|
|
@ -132,6 +155,18 @@ default {
|
||||||
|
|
||||||
showPickDialog(name, id, diceType, picks);
|
showPickDialog(name, id, diceType, picks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle dialog recovery requests
|
||||||
|
else if (num == MSG_GET_CURRENT_DIALOG) {
|
||||||
|
string playerName = str;
|
||||||
|
// Only show dialog if this player is the current picker
|
||||||
|
if (currentPlayer == playerName && currentPlayerKey == id) {
|
||||||
|
llOwnerSay("🔄 Restoring dialog for " + playerName);
|
||||||
|
showPickDialog(currentPlayer, currentPlayerKey, currentDiceType, picksNeeded);
|
||||||
|
} else {
|
||||||
|
llRegionSayTo(id, 0, "❌ You don't have an active dialog to restore.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
listen(integer channel, string name, key id, string message) {
|
listen(integer channel, string name, key id, string message) {
|
||||||
|
|
@ -145,7 +180,11 @@ default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (message == "Next ▶") {
|
if (message == "Next ▶") {
|
||||||
integer totalPages = (currentDiceType + NUMBERS_PER_PAGE - 1) / NUMBERS_PER_PAGE;
|
// Calculate total pages based on AVAILABLE numbers, not all dice numbers
|
||||||
|
integer availableCount = currentDiceType - llGetListLength(globallyPickedNumbers) - llGetListLength(currentPicks);
|
||||||
|
integer totalPages = (availableCount + NUMBERS_PER_PAGE - 1) / NUMBERS_PER_PAGE;
|
||||||
|
if (totalPages < 1) totalPages = 1;
|
||||||
|
|
||||||
if (currentPage < totalPages - 1) {
|
if (currentPage < totalPages - 1) {
|
||||||
currentPage++;
|
currentPage++;
|
||||||
showPickDialog(currentPlayer, currentPlayerKey, currentDiceType, picksNeeded);
|
showPickDialog(currentPlayer, currentPlayerKey, currentDiceType, picksNeeded);
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,42 @@ integer MSG_TOGGLE_READY = 202;
|
||||||
integer MSG_QUERY_READY_STATE = 210;
|
integer MSG_QUERY_READY_STATE = 210;
|
||||||
integer MSG_READY_STATE_RESULT = 211;
|
integer MSG_READY_STATE_RESULT = 211;
|
||||||
integer MSG_CLEANUP_ALL_FLOATERS = 212;
|
integer MSG_CLEANUP_ALL_FLOATERS = 212;
|
||||||
|
integer MSG_GET_CURRENT_DIALOG = 302;
|
||||||
|
|
||||||
// Owner-specific options shown when pressing the "Owner" button
|
// Categorized owner menu system
|
||||||
list ownerOptions = ["Join Game", "Leave Game", "Add Test Player", "Manage Picks", "Start Game", "Reset Game", "Dump Players", "Cleanup Floaters"];
|
list mainOwnerOptions = ["🎮 Game Participation", "👥 Player Management", "🔄 Reset Options", "🛠️ Troubleshooting", "⬅️ Back to Game"];
|
||||||
|
|
||||||
|
// Sub-menu options for each category
|
||||||
|
list gameParticipationOptions = ["Join Game", "Leave Game", "⬅️ Back to Main"];
|
||||||
|
list playerManagementOptions = ["Add Test Player", "⬅️ Back to Main"];
|
||||||
|
list resetOptions = ["Reset Game", "Reset Leaderboard", "Reset All", "⬅️ Back to Main"];
|
||||||
|
list troubleshootingOptions = ["Cleanup Floaters", "⬅️ Back to Main"];
|
||||||
|
|
||||||
// State tracking for dynamic ready menu
|
// State tracking for dynamic ready menu
|
||||||
key pendingMenuPlayer = NULL_KEY;
|
key pendingMenuPlayer = NULL_KEY;
|
||||||
integer pendingMenuIsStarter = FALSE;
|
integer pendingMenuIsStarter = FALSE;
|
||||||
integer pendingMenuIsOwner = FALSE;
|
integer pendingMenuIsOwner = FALSE;
|
||||||
|
|
||||||
// Display the debug/owner menu
|
// Display the main categorized owner menu
|
||||||
showOwnerMenu(key id) {
|
showOwnerMenu(key id) {
|
||||||
llDialog(id, "🔧 Owner Menu", ownerOptions, DIALOG_CHANNEL);
|
llDialog(id, "🔧 Owner Menu - Select Category", mainOwnerOptions, DIALOG_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display category sub-menus
|
||||||
|
showGameParticipationMenu(key id) {
|
||||||
|
llDialog(id, "🎮 Game Participation", gameParticipationOptions, DIALOG_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
showPlayerManagementMenu(key id) {
|
||||||
|
llDialog(id, "👥 Player Management", playerManagementOptions, DIALOG_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
showResetOptionsMenu(key id) {
|
||||||
|
llDialog(id, "🔄 Reset Options", resetOptions, DIALOG_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
showTroubleshootingMenu(key id) {
|
||||||
|
llDialog(id, "🛠️ Troubleshooting", troubleshootingOptions, DIALOG_CHANNEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display the combined Ready/Leave menu for both players and owners
|
// Display the combined Ready/Leave menu for both players and owners
|
||||||
|
|
@ -114,6 +138,19 @@ default {
|
||||||
}
|
}
|
||||||
|
|
||||||
link_message(integer sender, integer num, string str, key id) {
|
link_message(integer sender, integer num, string str, key id) {
|
||||||
|
// Handle full reset from main controller
|
||||||
|
if (num == -99999 && str == "FULL_RESET") {
|
||||||
|
// Reset dialog state
|
||||||
|
pendingMenuPlayer = NULL_KEY;
|
||||||
|
pendingMenuIsStarter = FALSE;
|
||||||
|
pendingMenuIsOwner = FALSE;
|
||||||
|
currentPickList = [];
|
||||||
|
currentPickTarget = "";
|
||||||
|
currentPickLimit = 3;
|
||||||
|
llOwnerSay("🎭 Owner and Player Dialog Handler reset!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (num == MSG_SHOW_MENU) {
|
if (num == MSG_SHOW_MENU) {
|
||||||
llOwnerSay("📨 Received MSG_SHOW_MENU with: " + str);
|
llOwnerSay("📨 Received MSG_SHOW_MENU with: " + str);
|
||||||
list args = llParseString2List(str, ["|"], []);
|
list args = llParseString2List(str, ["|"], []);
|
||||||
|
|
@ -124,8 +161,11 @@ default {
|
||||||
string targetType = llList2String(args, 0);
|
string targetType = llList2String(args, 0);
|
||||||
integer isStarter = (integer)llList2String(args, 1);
|
integer isStarter = (integer)llList2String(args, 1);
|
||||||
if (targetType == "owner") {
|
if (targetType == "owner") {
|
||||||
|
// Both starter and non-starter owners get ready/leave menu with Owner button
|
||||||
|
llOwnerSay("DEBUG: Owner menu request - isStarter=" + (string)isStarter + ", calling showReadyLeaveMenu");
|
||||||
showReadyLeaveMenu(id, isStarter, TRUE);
|
showReadyLeaveMenu(id, isStarter, TRUE);
|
||||||
} else if (targetType == "player") {
|
} else if (targetType == "player") {
|
||||||
|
llOwnerSay("DEBUG: Player menu request - isStarter=" + (string)isStarter + ", calling showReadyLeaveMenu");
|
||||||
showReadyLeaveMenu(id, isStarter, FALSE);
|
showReadyLeaveMenu(id, isStarter, FALSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,11 +177,17 @@ default {
|
||||||
integer isReady = (integer)llList2String(parts, 1);
|
integer isReady = (integer)llList2String(parts, 1);
|
||||||
integer isBot = (integer)llList2String(parts, 2);
|
integer isBot = (integer)llList2String(parts, 2);
|
||||||
|
|
||||||
|
llOwnerSay("DEBUG: Ready state result for " + playerName + ": ready=" + (string)isReady + ", bot=" + (string)isBot);
|
||||||
|
llOwnerSay("DEBUG: Pending menu - starter=" + (string)pendingMenuIsStarter + ", owner=" + (string)pendingMenuIsOwner);
|
||||||
|
|
||||||
// Show menu with the current ready state
|
// Show menu with the current ready state
|
||||||
if (pendingMenuPlayer == id) {
|
if (pendingMenuPlayer == id) {
|
||||||
|
llOwnerSay("DEBUG: Calling showReadyLeaveMenuWithState");
|
||||||
showReadyLeaveMenuWithState(pendingMenuPlayer, pendingMenuIsStarter, pendingMenuIsOwner, isReady, isBot);
|
showReadyLeaveMenuWithState(pendingMenuPlayer, pendingMenuIsStarter, pendingMenuIsOwner, isReady, isBot);
|
||||||
// Reset pending state
|
// Reset pending state
|
||||||
pendingMenuPlayer = NULL_KEY;
|
pendingMenuPlayer = NULL_KEY;
|
||||||
|
} else {
|
||||||
|
llOwnerSay("DEBUG: Pending menu player mismatch: expected " + (string)pendingMenuPlayer + ", got " + (string)id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -177,6 +223,27 @@ default {
|
||||||
if (msg == "Owner") {
|
if (msg == "Owner") {
|
||||||
showOwnerMenu(id);
|
showOwnerMenu(id);
|
||||||
}
|
}
|
||||||
|
// Handle category menu navigation
|
||||||
|
else if (msg == "🎮 Game Participation") {
|
||||||
|
showGameParticipationMenu(id);
|
||||||
|
}
|
||||||
|
else if (msg == "👥 Player Management") {
|
||||||
|
showPlayerManagementMenu(id);
|
||||||
|
}
|
||||||
|
else if (msg == "🔄 Reset Options") {
|
||||||
|
showResetOptionsMenu(id);
|
||||||
|
}
|
||||||
|
else if (msg == "🛠️ Troubleshooting") {
|
||||||
|
showTroubleshootingMenu(id);
|
||||||
|
}
|
||||||
|
else if (msg == "⬅️ Back to Main") {
|
||||||
|
showOwnerMenu(id);
|
||||||
|
}
|
||||||
|
else if (msg == "⬅️ Back to Game") {
|
||||||
|
// Return to the owner's ready/leave menu
|
||||||
|
// Need to determine if owner is starter
|
||||||
|
llMessageLinked(LINK_SET, MSG_SHOW_MENU, "owner|1", id); // Assume starter for now, will be corrected by main controller
|
||||||
|
}
|
||||||
// Owner joins the game: register and rez a float for them
|
// Owner joins the game: register and rez a float for them
|
||||||
else if (msg == "Join Game") {
|
else if (msg == "Join Game") {
|
||||||
// Use the avatar's key as the identifier
|
// Use the avatar's key as the identifier
|
||||||
|
|
|
||||||
49
README.md
49
README.md
|
|
@ -6,6 +6,23 @@ Peril Dice is a multiplayer elimination game where each player selects numbers b
|
||||||
|
|
||||||
## Key Features
|
## 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)
|
- **🎯 Dynamic Player Management**: Players can join at runtime (owner and other avatars)
|
||||||
- **🤖 Bot Support**: Add AI bots for testing and gameplay variety
|
- **🤖 Bot Support**: Add AI bots for testing and gameplay variety
|
||||||
- **📱 Floating HUD Display**: Real-time stats for each player
|
- **📱 Floating HUD Display**: Real-time stats for each player
|
||||||
|
|
@ -69,18 +86,34 @@ Dice type is automatically chosen to ensure at least 3 picks per player:
|
||||||
| 5–6 | d20 |
|
| 5–6 | d20 |
|
||||||
| 7–10 | d30 |
|
| 7–10 | d30 |
|
||||||
|
|
||||||
## Recent Improvements (v2.2.0)
|
## Recent Improvements (v2.3.0)
|
||||||
|
|
||||||
- **🎭 Dramatic Messaging**: All game events now use thematic, immersive language
|
### 🏆 Visual Scoreboard Revolution
|
||||||
- **📢 Public Announcements**: Players can see all major game events in public chat
|
- **Real-Time Visual Display**: Complete visual overhaul with player grid showing profile pictures and hearts
|
||||||
- **🎲 Enhanced Dice Messages**: Rolls show both dice type (d6, d8, etc.) and result
|
- **Instant Heart Updates**: Hearts change immediately when lives are lost, before any dialogs appear
|
||||||
- **🔧 Bug Fixes**: Resolved StatFloat duplication and duplicate message handling issues
|
- **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
|
||||||
|
- **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
|
||||||
|
- **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
|
||||||
|
|
||||||
|
### 🔧 Critical Fixes
|
||||||
|
- **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
|
||||||
|
|
||||||
## Version
|
## Version
|
||||||
|
|
||||||
**Current Version**: 2.2.0
|
**Current Version**: 2.3.0
|
||||||
**Last Updated**: August 2025
|
**Last Updated**: August 5, 2025
|
||||||
**Status**: Stable - Enhanced UX with dramatic messaging system
|
**Status**: Stable - Complete visual scoreboard system with enhanced status display
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,19 @@ default {
|
||||||
}
|
}
|
||||||
|
|
||||||
link_message(integer sender, integer num, string str, key id) {
|
link_message(integer sender, integer num, string str, key id) {
|
||||||
|
// Handle full reset from main controller
|
||||||
|
if (num == -99999 && str == "FULL_RESET") {
|
||||||
|
// Reset roll confetti module state
|
||||||
|
names = [];
|
||||||
|
lives = [];
|
||||||
|
picksData = [];
|
||||||
|
perilPlayer = "";
|
||||||
|
// Stop any active particles
|
||||||
|
llParticleSystem([]);
|
||||||
|
llOwnerSay("🎆 Roll Confetti Module reset!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle game state sync from main controller
|
// Handle game state sync from main controller
|
||||||
if (num == MSG_SYNC_GAME_STATE) {
|
if (num == MSG_SYNC_GAME_STATE) {
|
||||||
list parts = llParseString2List(str, ["~"], []);
|
list parts = llParseString2List(str, ["~"], []);
|
||||||
|
|
@ -155,6 +168,12 @@ default {
|
||||||
if (matched && newPeril != "") {
|
if (matched && newPeril != "") {
|
||||||
llSay(0, "⚡ PLOT TWIST! " + newPeril + " picked " + resultStr + " (rolled on d" + (string)diceType + ") and is now in ULTIMATE PERIL! ⚡");
|
llSay(0, "⚡ PLOT TWIST! " + newPeril + " picked " + resultStr + " (rolled on d" + (string)diceType + ") and is now in ULTIMATE PERIL! ⚡");
|
||||||
perilPlayer = newPeril;
|
perilPlayer = newPeril;
|
||||||
|
|
||||||
|
// Send Plot Twist status to scoreboard
|
||||||
|
llRegionSay(-12345, "GAME_STATUS|Plot Twist");
|
||||||
|
// Add delay to let status display before next phase
|
||||||
|
llSleep(2.0);
|
||||||
|
|
||||||
// Update floaters immediately to show correct peril player before sync
|
// Update floaters immediately to show correct peril player before sync
|
||||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, newPeril, NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, newPeril, NULL_KEY);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -163,19 +182,42 @@ default {
|
||||||
integer currentLives = llList2Integer(lives, pidx);
|
integer currentLives = llList2Integer(lives, pidx);
|
||||||
lives = llListReplaceList(lives, [currentLives - 1], pidx, pidx);
|
lives = llListReplaceList(lives, [currentLives - 1], pidx, pidx);
|
||||||
|
|
||||||
|
// Immediately send updated heart count to scoreboard
|
||||||
|
integer newLives = currentLives - 1;
|
||||||
|
key perilKey = NULL_KEY; // We don't have player keys in this module, use NULL_KEY
|
||||||
|
if (pidx < llGetListLength(names)) {
|
||||||
|
string updateMsg = "PLAYER_UPDATE|" + perilPlayer + "|" + (string)newLives + "|" + (string)perilKey;
|
||||||
|
llRegionSay(-12345, updateMsg);
|
||||||
|
llOwnerSay("💗 Immediate scoreboard update: " + perilPlayer + " now has " + (string)newLives + " lives");
|
||||||
|
}
|
||||||
|
|
||||||
// Check if peril player picked the rolled number
|
// Check if peril player picked the rolled number
|
||||||
list perilPicks = getPicksFor(perilPlayer);
|
list perilPicks = getPicksFor(perilPlayer);
|
||||||
integer perilPickedIt = (llListFindList(perilPicks, [resultStr]) != -1);
|
integer perilPickedIt = (llListFindList(perilPicks, [resultStr]) != -1);
|
||||||
|
|
||||||
if (perilPickedIt) {
|
if (perilPickedIt) {
|
||||||
llSay(0, "🩸 DIRECT HIT! " + perilPlayer + " picked their own doom - the d" + (string)diceType + " landed on " + resultStr + "! 🩸");
|
llSay(0, "🩸 DIRECT HIT! " + perilPlayer + " picked their own doom - the d" + (string)diceType + " landed on " + resultStr + "! 🩸");
|
||||||
|
// Send Direct Hit status to scoreboard
|
||||||
|
llRegionSay(-12345, "GAME_STATUS|Direct Hit");
|
||||||
|
// Add delay to let status display before next phase
|
||||||
|
llSleep(2.0);
|
||||||
} else {
|
} else {
|
||||||
llSay(0, "🩸 NO SHIELD! Nobody picked " + resultStr + " - " + perilPlayer + " takes the hit from the d" + (string)diceType + "! 🩸");
|
llSay(0, "🩸 NO SHIELD! Nobody picked " + resultStr + " - " + perilPlayer + " takes the hit from the d" + (string)diceType + "! 🩸");
|
||||||
|
// Send No Shield status to scoreboard
|
||||||
|
llRegionSay(-12345, "GAME_STATUS|No Shield");
|
||||||
|
// Add delay to let status display before next phase
|
||||||
|
llSleep(2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, perilPlayer, NULL_KEY);
|
llMessageLinked(LINK_SET, MSG_UPDATE_FLOAT, perilPlayer, NULL_KEY);
|
||||||
|
|
||||||
// Check for elimination
|
// Check for elimination
|
||||||
if (currentLives - 1 <= 0) {
|
if (currentLives - 1 <= 0) {
|
||||||
|
// Show 0 hearts on scoreboard before elimination message
|
||||||
|
string eliminationUpdateMsg = "PLAYER_UPDATE|" + perilPlayer + "|0|" + (string)NULL_KEY;
|
||||||
|
llRegionSay(-12345, eliminationUpdateMsg);
|
||||||
|
llOwnerSay("💀 Elimination update: " + perilPlayer + " now shows 0 hearts");
|
||||||
|
|
||||||
llSay(0, "🐻 PUNISHMENT TIME! " + perilPlayer + " has been ELIMINATED!");
|
llSay(0, "🐻 PUNISHMENT TIME! " + perilPlayer + " has been ELIMINATED!");
|
||||||
// Remove eliminated player (send message to main controller)
|
// Remove eliminated player (send message to main controller)
|
||||||
llMessageLinked(LINK_SET, 999, "ELIMINATE_PLAYER|" + perilPlayer, NULL_KEY);
|
llMessageLinked(LINK_SET, 999, "ELIMINATE_PLAYER|" + perilPlayer, NULL_KEY);
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,13 @@ default {
|
||||||
|
|
||||||
on_rez(integer start_param) {
|
on_rez(integer start_param) {
|
||||||
llListen(start_param, "", NULL_KEY, "");
|
llListen(start_param, "", NULL_KEY, "");
|
||||||
|
// Wait a moment for the description to be set
|
||||||
|
llSleep(0.1);
|
||||||
myName = llGetObjectDesc();
|
myName = llGetObjectDesc();
|
||||||
llOwnerSay("📱 Listening on channel " + (string)start_param + " for " + myName);
|
if (myName == "" || myName == "(No Description)") {
|
||||||
|
myName = "UnknownPlayer" + (string)start_param;
|
||||||
|
}
|
||||||
|
// StatFloat ready and listening
|
||||||
}
|
}
|
||||||
|
|
||||||
listen(integer channel, string name, key id, string message) {
|
listen(integer channel, string name, key id, string message) {
|
||||||
|
|
@ -50,6 +55,40 @@ default {
|
||||||
if (llGetListLength(parts) >= 2) {
|
if (llGetListLength(parts) >= 2) {
|
||||||
target = (key)llGetSubString(llList2String(parts, 0), 6, -1);
|
target = (key)llGetSubString(llList2String(parts, 0), 6, -1);
|
||||||
displayText = llList2String(parts, 1);
|
displayText = llList2String(parts, 1);
|
||||||
|
|
||||||
|
// Extract life count from the display text to set heart texture
|
||||||
|
// Look for "Lives: X" pattern in the text
|
||||||
|
integer livesPos = llSubStringIndex(displayText, "Lives: ");
|
||||||
|
if (livesPos != -1) {
|
||||||
|
string livesStr = llGetSubString(displayText, livesPos + 7, livesPos + 7);
|
||||||
|
integer lifeCount = (integer)livesStr;
|
||||||
|
string heartTexture = (string)lifeCount + "_hearts";
|
||||||
|
// Set heart texture based on current life count
|
||||||
|
llSetTexture(heartTexture, 1); // Face 1 (right side)
|
||||||
|
llSetTexture(heartTexture, 2); // Face 2 (back)
|
||||||
|
llSetTexture(heartTexture, 3); // Face 3 (left side)
|
||||||
|
llSetTexture(heartTexture, 4); // Face 4 (front)
|
||||||
|
// Set white background for all faces to eliminate gaps without tinting
|
||||||
|
llSetColor(<1.0, 1.0, 1.0>, 1); // White background for right side
|
||||||
|
llSetColor(<1.0, 1.0, 1.0>, 2); // White background for back
|
||||||
|
llSetColor(<1.0, 1.0, 1.0>, 3); // White background for left side
|
||||||
|
llSetColor(<1.0, 1.0, 1.0>, 4); // White background for front
|
||||||
|
llSetColor(<0.2, 0.2, 0.2>, 0); // Dark gray for top
|
||||||
|
llSetColor(<0.2, 0.2, 0.2>, 5); // Dark gray for bottom
|
||||||
|
|
||||||
|
// Remove "Lives: X" from display text since hearts show it
|
||||||
|
list lines = llParseString2List(displayText, ["\n"], []);
|
||||||
|
list filteredLines = [];
|
||||||
|
integer i;
|
||||||
|
for (i = 0; i < llGetListLength(lines); i++) {
|
||||||
|
string line = llList2String(lines, i);
|
||||||
|
if (llSubStringIndex(line, "Lives: ") != 0) {
|
||||||
|
filteredLines += [line];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
displayText = llDumpList2String(filteredLines, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
llSetText(displayText, <1,1,1>, 1.0);
|
llSetText(displayText, <1,1,1>, 1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,6 +96,12 @@ default {
|
||||||
llOwnerSay("🪟 Cleaning up...");
|
llOwnerSay("🪟 Cleaning up...");
|
||||||
llDie();
|
llDie();
|
||||||
}
|
}
|
||||||
|
else if (llSubStringIndex(message, "SET_NAME:") == 0) {
|
||||||
|
myName = llGetSubString(message, 9, -1);
|
||||||
|
// Name successfully received and set
|
||||||
|
// Update the object description as well for consistency
|
||||||
|
llSetObjectDesc(myName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
link_message(integer sender, integer num, string str, key id) {
|
link_message(integer sender, integer num, string str, key id) {
|
||||||
|
|
@ -101,7 +146,10 @@ default {
|
||||||
|
|
||||||
string picksDisplay = llList2CSV(picks);
|
string picksDisplay = llList2CSV(picks);
|
||||||
|
|
||||||
string txt = "🎲 Peril Dice\n👤 " + myName + "\n❤️ Lives: " + (string)lifeCount + "\n Peril: " + perilDisplay + "\n🔢 Picks: " + picksDisplay;
|
// Note: Heart texture is set via listen() path to avoid conflicts
|
||||||
|
|
||||||
|
// Update text display (lives now shown by heart textures)
|
||||||
|
string txt = "🎲 Peril Dice\n👤 " + myName + "\n Peril: " + perilDisplay + "\n🔢 Picks: " + picksDisplay;
|
||||||
llSetText(txt, <1,1,1>, 1.0);
|
llSetText(txt, <1,1,1>, 1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
88
XyzzyText_Debug.lsl
Normal file
88
XyzzyText_Debug.lsl
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
images/title.png
Normal file
BIN
images/title.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 190 KiB |
611
xytext_master.lsl
Normal file
611
xytext_master.lsl
Normal file
|
|
@ -0,0 +1,611 @@
|
||||||
|
////////////////////////////////////////////
|
||||||
|
// 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 == []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
521
xytext_master_clean.lsl
Normal file
521
xytext_master_clean.lsl
Normal file
|
|
@ -0,0 +1,521 @@
|
||||||
|
////////////////////////////////////////////
|
||||||
|
// 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, "", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
540
xytext_master_with_listener.lsl
Normal file
540
xytext_master_with_listener.lsl
Normal file
|
|
@ -0,0 +1,540 @@
|
||||||
|
////////////////////////////////////////////
|
||||||
|
// 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
Normal file
281
xytext_slave.lsl
Normal file
|
|
@ -0,0 +1,281 @@
|
||||||
|
////////////////////////////////////////////
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in a new issue