412 lines
19 KiB
Text
412 lines
19 KiB
Text
// Peril Game Display Follower Script - Config Version
|
|
// This script reads position settings from a "config" notecard
|
|
// Change DISPLAY_TYPE below to match your display object
|
|
|
|
string DISPLAY_TYPE = "scoreboard"; // Change to: "scoreboard", "leaderboard", or "dice"
|
|
|
|
// =============================================================================
|
|
// CONTROLLER DISCOVERY SYSTEM
|
|
// =============================================================================
|
|
|
|
// Fixed discovery channel for finding controller
|
|
integer DISCOVERY_CHANNEL = -77000;
|
|
|
|
// Base channel offset - should match Main.lsl
|
|
integer CHANNEL_BASE = -77000;
|
|
|
|
// Controller discovery state
|
|
key CONTROLLER_KEY = NULL_KEY;
|
|
integer DISCOVERY_ATTEMPTS = 0;
|
|
integer MAX_DISCOVERY_ATTEMPTS = 10;
|
|
integer AUTO_CONFIG_ATTEMPTS = 0;
|
|
integer MAX_AUTO_CONFIG_ATTEMPTS = 5;
|
|
integer INITIALIZED = FALSE; // Flag to prevent multiple initializations
|
|
|
|
// Calculate channels dynamically using controller key for consistency
|
|
integer calculateChannelWithController(integer offset, key controllerKey) {
|
|
// Use owner's key AND CONTROLLER's object key to ensure all objects use same channels
|
|
// This should match the calculateChannel method in Controller_Discovery.lsl
|
|
string ownerStr = (string)llGetOwner();
|
|
string controllerObjStr = (string)controllerKey; // This is the controller object key
|
|
string combinedStr = ownerStr + controllerObjStr;
|
|
|
|
// Create a more unique hash using both keys
|
|
string hashStr = llMD5String(combinedStr, 0);
|
|
integer hash1 = llSubStringIndex("0123456789abcdef", llGetSubString(hashStr, 0, 0));
|
|
integer hash2 = llSubStringIndex("0123456789abcdef", llGetSubString(hashStr, 1, 1));
|
|
integer combinedHash = hash1 * 16 + hash2; // Creates 0-255 range
|
|
|
|
return CHANNEL_BASE - (offset * 1000) - combinedHash;
|
|
}
|
|
|
|
// Legacy function for backward compatibility (uses own key)
|
|
integer calculateChannel(integer offset) {
|
|
string ownerStr = (string)llGetOwner();
|
|
string objectStr = (string)llGetKey();
|
|
string combinedStr = ownerStr + objectStr;
|
|
|
|
string hashStr = llMD5String(combinedStr, 0);
|
|
integer hash1 = llSubStringIndex("0123456789abcdef", llGetSubString(hashStr, 0, 0));
|
|
integer hash2 = llSubStringIndex("0123456789abcdef", llGetSubString(hashStr, 1, 1));
|
|
integer combinedHash = hash1 * 16 + hash2;
|
|
|
|
return CHANNEL_BASE - (offset * 1000) - combinedHash;
|
|
}
|
|
|
|
// Channel mapping for different display types (with controller discovery)
|
|
integer getDataChannelForType(string displayType) {
|
|
if (CONTROLLER_KEY != NULL_KEY) {
|
|
// Use controller-based channels for consistency
|
|
if (displayType == "scoreboard") return calculateChannelWithController(6, CONTROLLER_KEY); // SCOREBOARD_CHANNEL_1
|
|
else if (displayType == "leaderboard") return calculateChannelWithController(7, CONTROLLER_KEY); // SCOREBOARD_CHANNEL_2
|
|
else if (displayType == "dice") return calculateChannelWithController(8, CONTROLLER_KEY); // SCOREBOARD_CHANNEL_3
|
|
else return calculateChannelWithController(1, CONTROLLER_KEY); // Default to sync channel
|
|
} else {
|
|
// Fallback to legacy calculation during discovery
|
|
if (displayType == "scoreboard") return calculateChannel(6); // SCOREBOARD_CHANNEL_1
|
|
else if (displayType == "leaderboard") return calculateChannel(7); // SCOREBOARD_CHANNEL_2
|
|
else if (displayType == "dice") return calculateChannel(8); // SCOREBOARD_CHANNEL_3
|
|
else return calculateChannel(1); // Default to sync channel
|
|
}
|
|
}
|
|
|
|
// Controller discovery function
|
|
startControllerDiscovery() {
|
|
llOwnerSay("📡 [" + DISPLAY_TYPE + "] Starting controller discovery...");
|
|
|
|
// Listen on discovery channel
|
|
llListen(DISCOVERY_CHANNEL, "", "", "");
|
|
|
|
// Broadcast discovery request
|
|
llRegionSay(DISCOVERY_CHANNEL, "FIND_CONTROLLER|" + DISPLAY_TYPE);
|
|
|
|
// Reset discovery attempts
|
|
DISCOVERY_ATTEMPTS = 0;
|
|
|
|
// Set timer for retry if needed
|
|
llSetTimerEvent(5.0);
|
|
}
|
|
|
|
// These will be loaded from config notecard
|
|
vector MY_OFFSET;
|
|
rotation MY_ROTATION;
|
|
integer MY_CHANNEL;
|
|
integer MY_DATA_CHANNEL;
|
|
|
|
// Controller tracking
|
|
key master_key;
|
|
vector last_master_pos;
|
|
rotation last_master_rot;
|
|
|
|
// Config reading
|
|
integer config_line = 0;
|
|
key config_query;
|
|
|
|
// Function to initialize channels and config after controller discovery
|
|
initializeAfterDiscovery() {
|
|
if (CONTROLLER_KEY != NULL_KEY) {
|
|
llOwnerSay("🆗 [" + DISPLAY_TYPE + "] Controller found! Initializing channels...");
|
|
} else {
|
|
llOwnerSay("⚠️ [" + DISPLAY_TYPE + "] Initializing in legacy mode (no controller found)...");
|
|
}
|
|
|
|
// Update channels with controller key (or legacy channels if NULL)
|
|
if (CONTROLLER_KEY != NULL_KEY) {
|
|
MY_CHANNEL = calculateChannelWithController(1, CONTROLLER_KEY); // Position sync channel
|
|
MY_DATA_CHANNEL = getDataChannelForType(DISPLAY_TYPE);
|
|
} else {
|
|
// Use legacy channels when no controller found
|
|
MY_CHANNEL = calculateChannel(1); // Position sync channel
|
|
MY_DATA_CHANNEL = getDataChannelForType(DISPLAY_TYPE); // This will use legacy calculation
|
|
}
|
|
|
|
llOwnerSay("🔧 [" + DISPLAY_TYPE + "] Dynamic channels initialized:");
|
|
llOwnerSay(" Position sync: " + (string)MY_CHANNEL);
|
|
llOwnerSay(" Data channel: " + (string)MY_DATA_CHANNEL);
|
|
|
|
// Start reading config notecard or use auto-detection
|
|
if (llGetInventoryType("config") == INVENTORY_NOTECARD) {
|
|
llOwnerSay("📜 Reading config notecard...");
|
|
config_query = llGetNotecardLine("config", 0);
|
|
config_line = 0;
|
|
} else if (CONTROLLER_KEY != NULL_KEY) {
|
|
// Only try auto-detection if we have a controller
|
|
llOwnerSay("⚠️ No 'config' notecard found - using auto-detection mode!");
|
|
llOwnerSay("📍 Looking for controller to auto-configure position...");
|
|
|
|
// Set up listeners for auto-detection with correct channels
|
|
llListen(MY_CHANNEL, "", "", ""); // Position sync channel
|
|
llListen(MY_DATA_CHANNEL, "", "", ""); // Data/scan channel
|
|
|
|
// Request auto-configuration from controller
|
|
llRegionSay(MY_DATA_CHANNEL, "AUTO_CONFIG_REQUEST|" + DISPLAY_TYPE + "|" +
|
|
(string)llGetPos() + "|" + (string)llGetRot());
|
|
|
|
// Reset auto-config retry counter
|
|
AUTO_CONFIG_ATTEMPTS = 0;
|
|
|
|
// Set a timer to retry if no response
|
|
llSetTimerEvent(5.0);
|
|
} else {
|
|
// No controller and no config - just set up basic listeners in legacy mode
|
|
llOwnerSay("⚠️ [" + DISPLAY_TYPE + "] No controller or config found - legacy mode with default positioning");
|
|
|
|
// Set up listeners for legacy mode
|
|
llListen(MY_CHANNEL, "", "", ""); // Position sync channel
|
|
llListen(MY_DATA_CHANNEL, "", "", ""); // Data/scan channel
|
|
|
|
// Set default positioning values
|
|
MY_OFFSET = <0.0, 0.0, 0.0>;
|
|
MY_ROTATION = <0.0, 0.0, 0.0, 1.0>;
|
|
|
|
llOwnerSay("🔄 [" + DISPLAY_TYPE + "] Ready in legacy mode - listening for position updates");
|
|
}
|
|
|
|
// Mark as initialized to prevent repeated initialization
|
|
INITIALIZED = TRUE;
|
|
}
|
|
|
|
default {
|
|
state_entry() {
|
|
llOwnerSay("🎮 Starting " + DISPLAY_TYPE + " follower - discovering controller...");
|
|
|
|
// Start controller discovery first
|
|
startControllerDiscovery();
|
|
}
|
|
|
|
dataserver(key query_id, string data) {
|
|
if (query_id == config_query) {
|
|
if (data == EOF) {
|
|
// Finished reading config
|
|
llOwnerSay("🔧 [" + DISPLAY_TYPE + " Follower] Dynamic channels initialized:");
|
|
llOwnerSay(" Position sync: " + (string)MY_CHANNEL);
|
|
llOwnerSay(" Data channel: " + (string)MY_DATA_CHANNEL);
|
|
llOwnerSay(DISPLAY_TYPE + " config loaded. Listening for controller...");
|
|
llListen(MY_CHANNEL, "", "", ""); // Position sync channel
|
|
llListen(MY_DATA_CHANNEL, "", "", ""); // Data/scan channel
|
|
llRegionSay(MY_CHANNEL, "FOLLOWER_READY|" + DISPLAY_TYPE);
|
|
} else if (data != "") {
|
|
// Parse config line
|
|
data = llStringTrim(data, STRING_TRIM);
|
|
if (llGetSubString(data, 0, 0) != "#") { // Skip comments
|
|
integer eq_pos = llSubStringIndex(data, "=");
|
|
if (eq_pos != -1) {
|
|
string config_key = llStringTrim(llGetSubString(data, 0, eq_pos - 1), STRING_TRIM);
|
|
string value = llStringTrim(llGetSubString(data, eq_pos + 1, -1), STRING_TRIM);
|
|
|
|
|
|
// Load values for this display type
|
|
if (config_key == DISPLAY_TYPE + "_offset") {
|
|
MY_OFFSET = (vector)value;
|
|
} else if (config_key == DISPLAY_TYPE + "_rotation") {
|
|
MY_ROTATION = (rotation)value;
|
|
} else if (config_key == DISPLAY_TYPE + "_channel") {
|
|
if (value == "DYNAMIC") {
|
|
MY_CHANNEL = calculateChannel(1); // Position sync channel
|
|
} else {
|
|
MY_CHANNEL = (integer)value; // Legacy hardcoded channel
|
|
}
|
|
} else if (config_key == DISPLAY_TYPE + "_data_channel") {
|
|
if (llSubStringIndex(value, "DYNAMIC") == 0) {
|
|
MY_DATA_CHANNEL = getDataChannelForType(DISPLAY_TYPE);
|
|
} else {
|
|
MY_DATA_CHANNEL = (integer)value; // Legacy hardcoded channel
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read next line
|
|
config_line++;
|
|
config_query = llGetNotecardLine("config", config_line);
|
|
} else {
|
|
// Read next line even if this one is empty
|
|
config_line++;
|
|
config_query = llGetNotecardLine("config", config_line);
|
|
}
|
|
}
|
|
}
|
|
|
|
listen(integer channel, string name, key id, string msg) {
|
|
// Handle controller discovery responses
|
|
if (channel == DISCOVERY_CHANNEL) {
|
|
if (llSubStringIndex(msg, "CONTROLLER_FOUND|") == 0) {
|
|
list parts = llParseString2List(msg, ["|"], []);
|
|
CONTROLLER_KEY = (key)llList2String(parts, 1);
|
|
|
|
llOwnerSay("✅ [" + DISPLAY_TYPE + "] Controller discovered: " + (string)CONTROLLER_KEY);
|
|
|
|
// Cancel discovery timer
|
|
llSetTimerEvent(0.0);
|
|
|
|
// Initialize with controller key
|
|
initializeAfterDiscovery();
|
|
return;
|
|
}
|
|
else if (llSubStringIndex(msg, "CONTROLLER_AVAILABLE|") == 0) {
|
|
// Handle broadcast availability messages - but only if not already initialized
|
|
if (INITIALIZED) {
|
|
// Already initialized, ignore repeated availability messages
|
|
return;
|
|
}
|
|
|
|
list parts = llParseString2List(msg, ["|"], []);
|
|
CONTROLLER_KEY = (key)llList2String(parts, 1);
|
|
|
|
llOwnerSay("✅ [" + DISPLAY_TYPE + "] Controller available: " + (string)CONTROLLER_KEY);
|
|
|
|
// Cancel discovery timer
|
|
llSetTimerEvent(0.0);
|
|
|
|
// Initialize with controller key
|
|
initializeAfterDiscovery();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (channel == MY_CHANNEL) {
|
|
if (llSubStringIndex(msg, "CONTROLLER_MOVE|") == 0) {
|
|
list parts = llParseString2List(msg, ["|"], []);
|
|
master_key = (key)llList2String(parts, 1);
|
|
vector master_pos = (vector)llList2String(parts, 2);
|
|
rotation master_rot = (rotation)llList2String(parts, 3);
|
|
|
|
// Calculate new position
|
|
vector new_pos = master_pos + (MY_OFFSET * master_rot);
|
|
rotation new_rot = MY_ROTATION * master_rot;
|
|
|
|
llSetPos(new_pos);
|
|
llSetRot(new_rot);
|
|
|
|
last_master_pos = master_pos;
|
|
last_master_rot = master_rot;
|
|
}
|
|
} else if (channel == MY_DATA_CHANNEL) {
|
|
if (llSubStringIndex(msg, "POSITION_SCAN|") == 0) {
|
|
// Respond to position scan for reset tool
|
|
llRegionSay(MY_DATA_CHANNEL, "POSITION_RESPONSE|" + DISPLAY_TYPE + "|" +
|
|
(string)llGetPos() + "|" + (string)llGetRot());
|
|
}
|
|
else if (llSubStringIndex(msg, "POSITION_UPDATE|") == 0) {
|
|
// Handle automatic position updates from integrated reset tool
|
|
list parts = llParseString2List(msg, ["|"], []);
|
|
if (llGetListLength(parts) >= 4) {
|
|
string updateType = llList2String(parts, 1);
|
|
if (updateType == DISPLAY_TYPE) {
|
|
vector newOffset = (vector)llList2String(parts, 2);
|
|
rotation newRotation = (rotation)llList2String(parts, 3);
|
|
|
|
// Update our stored offset and rotation
|
|
MY_OFFSET = newOffset;
|
|
MY_ROTATION = newRotation;
|
|
|
|
llOwnerSay("✨ " + DISPLAY_TYPE + " position updated automatically!");
|
|
llOwnerSay("New offset: " + (string)MY_OFFSET);
|
|
llOwnerSay("New rotation: " + (string)MY_ROTATION);
|
|
|
|
// If we have a master position, update our position immediately
|
|
if (master_key != NULL_KEY) {
|
|
vector new_pos = last_master_pos + (MY_OFFSET * last_master_rot);
|
|
rotation new_rot = MY_ROTATION * last_master_rot;
|
|
llSetPos(new_pos);
|
|
llSetRot(new_rot);
|
|
llOwnerSay("🎥 Moved to new position!");
|
|
}
|
|
|
|
// Cancel retry timer if we got a successful update
|
|
llSetTimerEvent(0.0);
|
|
}
|
|
}
|
|
}
|
|
else if (llSubStringIndex(msg, "AUTO_CONFIG_RESPONSE|") == 0) {
|
|
// Handle automatic configuration response from controller
|
|
list parts = llParseString2List(msg, ["|"], []);
|
|
if (llGetListLength(parts) >= 4) {
|
|
string responseType = llList2String(parts, 1);
|
|
if (responseType == DISPLAY_TYPE) {
|
|
vector autoOffset = (vector)llList2String(parts, 2);
|
|
rotation autoRotation = (rotation)llList2String(parts, 3);
|
|
|
|
// Set our position values from auto-detection
|
|
MY_OFFSET = autoOffset;
|
|
MY_ROTATION = autoRotation;
|
|
|
|
llOwnerSay("🎆 " + DISPLAY_TYPE + " auto-configured successfully!");
|
|
llOwnerSay("Auto-detected offset: " + (string)MY_OFFSET);
|
|
llOwnerSay("Auto-detected rotation: " + (string)MY_ROTATION);
|
|
llOwnerSay("🟢 Ready and listening for controller movement...");
|
|
|
|
// Cancel retry timer
|
|
llSetTimerEvent(0.0);
|
|
|
|
// Announce readiness
|
|
llRegionSay(MY_CHANNEL, "FOLLOWER_READY|" + DISPLAY_TYPE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
timer() {
|
|
if (CONTROLLER_KEY == NULL_KEY) {
|
|
// Still in controller discovery phase
|
|
DISCOVERY_ATTEMPTS++;
|
|
|
|
if (DISCOVERY_ATTEMPTS <= MAX_DISCOVERY_ATTEMPTS) {
|
|
llOwnerSay("🔄 [" + DISPLAY_TYPE + "] Retrying controller discovery (attempt " +
|
|
(string)DISCOVERY_ATTEMPTS + "/" + (string)MAX_DISCOVERY_ATTEMPTS + ")...");
|
|
|
|
// Retry controller discovery
|
|
llRegionSay(DISCOVERY_CHANNEL, "FIND_CONTROLLER|" + DISPLAY_TYPE);
|
|
|
|
// Set timer for next retry (exponential backoff)
|
|
float retryDelay = 5.0 * DISCOVERY_ATTEMPTS; // 5s, 10s, 15s, etc.
|
|
llSetTimerEvent(retryDelay);
|
|
} else {
|
|
llOwnerSay("❌ [" + DISPLAY_TYPE + "] Controller discovery failed after " +
|
|
(string)MAX_DISCOVERY_ATTEMPTS + " attempts");
|
|
llOwnerSay(" Operating in legacy mode with owner-based channels");
|
|
|
|
// Stop timer and initialize with legacy channels
|
|
llSetTimerEvent(0.0);
|
|
initializeAfterDiscovery(); // This will use legacy channels since CONTROLLER_KEY is NULL
|
|
}
|
|
} else {
|
|
// Controller found, retry auto-configuration request with limit
|
|
AUTO_CONFIG_ATTEMPTS++;
|
|
|
|
if (AUTO_CONFIG_ATTEMPTS <= MAX_AUTO_CONFIG_ATTEMPTS) {
|
|
llOwnerSay("🔄 [" + DISPLAY_TYPE + "] No auto-config response - retrying (" +
|
|
(string)AUTO_CONFIG_ATTEMPTS + "/" + (string)MAX_AUTO_CONFIG_ATTEMPTS + ")...");
|
|
|
|
if (MY_DATA_CHANNEL != 0) {
|
|
llRegionSay(MY_DATA_CHANNEL, "AUTO_CONFIG_REQUEST|" + DISPLAY_TYPE + "|" +
|
|
(string)llGetPos() + "|" + (string)llGetRot());
|
|
} else {
|
|
llOwnerSay("⚠️ [" + DISPLAY_TYPE + "] Data channel not initialized yet!");
|
|
}
|
|
|
|
// Set timer for next retry (10 seconds)
|
|
llSetTimerEvent(10.0);
|
|
} else {
|
|
llOwnerSay("❌ [" + DISPLAY_TYPE + "] Auto-config failed after " + (string)MAX_AUTO_CONFIG_ATTEMPTS + " attempts");
|
|
llOwnerSay("🔄 [" + DISPLAY_TYPE + "] Using default position - ready for manual configuration");
|
|
|
|
// Stop timer and set default positioning
|
|
llSetTimerEvent(0.0);
|
|
|
|
// Set default positioning values
|
|
MY_OFFSET = <0.0, 0.0, 0.0>;
|
|
MY_ROTATION = <0.0, 0.0, 0.0, 1.0>;
|
|
|
|
// Announce readiness with default positioning
|
|
llRegionSay(MY_CHANNEL, "FOLLOWER_READY|" + DISPLAY_TYPE);
|
|
llOwnerSay("🛫 [" + DISPLAY_TYPE + "] Ready and listening for controller movement (default position)");
|
|
}
|
|
}
|
|
}
|
|
|
|
on_rez(integer start_param) {
|
|
llResetScript();
|
|
}
|
|
}
|