This commit is contained in:
RebeccaNod1 2025-12-14 15:45:29 -07:00
parent abb4fe34c7
commit bc15d84b55
4 changed files with 265 additions and 560 deletions

View file

@ -1,49 +1,66 @@
// ====================================================================
// Peril Dice GitHub Updater - External Updater Box
// Peril Dice Updater - External Update Box
// ====================================================================
// This script goes in a separate updater box object that users rez temporarily.
// Downloads scripts directly from GitHub and installs them automatically
// in target Peril Dice games via llRemoteLoadScriptPin().
//
// USAGE:
// 1. User rezzes this updater box near their Peril Dice game
// 2. User touches their game -> "Check for Updates" -> "Install Update"
// 3. Game communicates with this updater box for automatic installation
// 4. User deletes updater box when finished
// This script goes in a standalone object (the "Updater Box")
// It downloads scripts from GitHub and installs them into the game
// ====================================================================
string GITHUB_MANIFEST_URL = "https://raw.githubusercontent.com/RebeccaNod1/Peril/main/peril-dice-manifest.json";
string UPDATER_VERSION = "2.8.8";
string CURRENT_VERSION = "2.8.8";
string GITHUB_API_URL = "https://api.github.com/repos/RebeccaNod1/Peril/releases/latest";
string GITHUB_RAW_URL = "https://raw.githubusercontent.com/RebeccaNod1/Peril/main/";
// Communication channels
integer UPDATER_CHANNEL = -7723847; // Unique channel for updater communication
integer RESPONSE_TIMEOUT = 30; // seconds
integer UPDATER_CHANNEL = -7723847; // Must match Update_Receiver.lsl
// Current operation state
string currentOperation = "";
key targetGameKey = NULL_KEY;
integer updatePin = 0;
list scriptQueue = [];
integer currentScriptIndex = 0;
string manifestData = "";
// State tracking
key targetGame = NULL_KEY;
integer targetPin = 0;
list downloadQueue = [];
string currentFile = "";
integer installIndex = 0;
integer isBusy = FALSE;
// HTTP tracking
key currentHttpRequest = NULL_KEY;
string downloadingScript = "";
string currentOperation = "";
// UI and status
integer isListening = FALSE;
// Script manifest - List of all scripts to update
// Format: "ScriptName.lsl|LinkNumber"
// Note: Link numbers will be verified via UUID request
list SCRIPT_MANIFEST = [
"Main_Controller_Linkset.lsl|1",
"Game_Manager.lsl|1",
"Controller_Memory.lsl|1",
"Controller_MessageHandler.lsl|1",
"Player_RegistrationManager.lsl|1",
"Player_DialogHandler.lsl|1",
"NumberPicker_DialogHandler.lsl|1",
"Floater_Manager.lsl|1",
"Roll_ConfettiModule.lsl|1",
"Bot_Manager.lsl|1",
"Game_Calculator.lsl|1",
"Verbose_Logger.lsl|1",
"System_Debugger.lsl|1",
"Update_Receiver.lsl|1", // Updates the receiver itself!
"Game_Scoreboard_Manager_Linkset.lsl|12",
"Leaderboard_Communication_Linkset.lsl|35",
"XyzzyText_Dice_Bridge_Linkset.lsl|83",
"xyzzy_Master_script.lsl|XYZZY" // Special handling for xyzzy
];
// Memory usage reporting
reportMemoryUsage(string scriptName) {
integer memory = llGetUsedMemory();
integer freeMemory = llGetFreeMemory();
float memoryPercent = (float)memory / (memory + freeMemory) * 100.0;
llOwnerSay("📊 " + scriptName + ": " + (string)memory + " bytes used (" +
llGetSubString((string)memoryPercent, 0, 4) + "% memory)");
// Map of Link Number -> UUID (populated by game response)
list linkUUIDs = []; // Format: [LinkNum, UUID, LinkNum, UUID...]
// Helper to get UUID for a link number
key getLinkUUID(integer linkNum) {
integer idx = llListFindList(linkUUIDs, [linkNum]);
if (idx != -1) {
return llList2Key(linkUUIDs, idx + 1);
}
return NULL_KEY;
}
// Extract JSON value manually (works around LSL 2048-char limit)
// Manual JSON parsing (same as receiver)
string extractJsonValue(string json, string keyName) {
string quote = "\"";
string searchPattern = quote + keyName + quote + ":" + quote;
@ -57,221 +74,208 @@ string extractJsonValue(string json, string keyName) {
return llGetSubString(json, valueStart, valueStart + valueEnd - 1);
}
// Parse script list from manifest (simplified for LSL limitations)
list parseScriptList(string manifest) {
list scripts = [];
// This is a simplified parser - in production might need more robust parsing
// For now, we'll use the script URLs directly
// Add all essential scripts in priority order (URLs keep .lsl, but script names don't)
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Main_Controller_Linkset.lsl|1|Main_Controller_Linkset"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Game_Manager.lsl|1|Game_Manager"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Controller_Memory.lsl|1|Controller_Memory"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Controller_MessageHandler.lsl|1|Controller_MessageHandler"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Player_RegistrationManager.lsl|1|Player_RegistrationManager"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Player_DialogHandler.lsl|1|Player_DialogHandler"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/NumberPicker_DialogHandler.lsl|1|NumberPicker_DialogHandler"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Floater_Manager.lsl|1|Floater_Manager"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Roll_ConfettiModule.lsl|1|Roll_ConfettiModule"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Bot_Manager.lsl|1|Bot_Manager"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Game_Calculator.lsl|1|Game_Calculator"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Verbose_Logger.lsl|1|Verbose_Logger"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/System_Debugger.lsl|1|System_Debugger"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Update_Receiver.lsl|1|Update_Receiver"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Game_Scoreboard_Manager_Linkset.lsl|12|Game_Scoreboard_Manager_Linkset"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/Leaderboard_Communication_Linkset.lsl|35|Leaderboard_Communication_Linkset"];
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/XyzzyText_Dice_Bridge_Linkset.lsl|83|XyzzyText_Dice_Bridge_Linkset"];
// Add XyzzyText script for links 35-82 (48 prims)
integer i;
for (i = 35; i <= 82; i++) {
scripts += ["https://raw.githubusercontent.com/RebeccaNod1/Peril/main/xyzzy_Master_script.lsl|" + (string)i + "|xyzzy_Master_script"];
}
return scripts;
}
// Start update process
startUpdate(key gameKey, integer pin) {
targetGameKey = gameKey;
updatePin = pin;
currentOperation = "updating";
currentScriptIndex = 0;
// Get script list (simplified - using hardcoded list for now)
scriptQueue = parseScriptList("");
llOwnerSay("🔄 Starting update process...");
llOwnerSay("📊 Installing " + (string)llGetListLength(scriptQueue) + " scripts");
// Start with first script
downloadNextScript();
}
// Download next script in queue
downloadNextScript() {
if (currentScriptIndex >= llGetListLength(scriptQueue)) {
// Update complete!
completeUpdate();
// Start the update process for a specific game
startUpdate(key gameId, string version, integer pin) {
if (isBusy) {
llRegionSayTo(gameId, UPDATER_CHANNEL, "UPDATE_BUSY");
return;
}
string scriptEntry = llList2String(scriptQueue, currentScriptIndex);
list parts = llParseString2List(scriptEntry, ["|"], []);
string url = llList2String(parts, 0);
string linkNum = llList2String(parts, 1);
string scriptName = llList2String(parts, 2); // Now using explicit script name
isBusy = TRUE;
targetGame = gameId;
targetPin = pin;
downloadingScript = scriptName;
llOwnerSay("🚀 Starting update for game " + (string)gameId);
llRegionSayTo(targetGame, UPDATER_CHANNEL, "UPDATE_STARTING|" + version);
llOwnerSay("📥 Downloading " + scriptName + " for link " + linkNum +
" (" + (string)(currentScriptIndex + 1) + "/" + (string)llGetListLength(scriptQueue) + ")");
// Step 1: Request Link UUIDs from the game
// We need these to install scripts to specific links
llRegionSayTo(targetGame, UPDATER_CHANNEL, "REQUEST_LINK_UUIDS");
// Set timeout for UUID response
llSetTimerEvent(10.0);
}
// Continue update after getting UUIDs
startDownloadSequence() {
installIndex = 0;
downloadNextScript();
}
downloadNextScript() {
if (installIndex >= llGetListLength(SCRIPT_MANIFEST)) {
// All done!
finishUpdate();
return;
}
string entry = llList2String(SCRIPT_MANIFEST, installIndex);
list parts = llParseString2List(entry, ["|"], []);
currentFile = llList2String(parts, 0);
string linkTarget = llList2String(parts, 1);
llOwnerSay("📥 Downloading " + currentFile + "...");
string url = GITHUB_RAW_URL + currentFile;
currentOperation = "download_script";
currentHttpRequest = llHTTPRequest(url, [HTTP_METHOD, "GET"], "");
}
// EMERGENCY SAFE MODE - Only add new scripts, don't delete anything
installScript(string scriptContent, string scriptName, integer linkNumber) {
llOwnerSay("🆘 SAFE MODE: Adding " + scriptName + " as new script");
llOwnerSay("📊 Size: " + (string)llStringLength(scriptContent) + " characters");
installScript(string content) {
string scriptEntry = llList2String(SCRIPT_MANIFEST, installIndex);
list scriptParts = llParseString2List(scriptEntry, ["|"], []);
string filename = llList2String(scriptParts, 0);
string linkTargetStr = llList2String(scriptParts, 1);
// Only create new script - DO NOT DELETE ANYTHING
// This creates a script with auto-generated name containing the new content
llRemoteLoadScriptPin(targetGameKey, scriptContent, updatePin, TRUE, 0);
llOwnerSay("💾 Installing " + filename + "...");
llOwnerSay("✅ Added new script with updated content");
llOwnerSay("📝 You can now copy content from the new script to replace your old " + scriptName);
// Determine target UUIDs
list targets = [];
key targetUUID;
// Move to next script
currentScriptIndex++;
llSetTimerEvent(2.0);
}
// Complete the update process
completeUpdate() {
llOwnerSay("🎉 Update installation complete!");
llOwnerSay("📊 Installed " + (string)llGetListLength(scriptQueue) + " scripts successfully");
// Notify target game
if (targetGameKey != NULL_KEY) {
llRegionSayTo(targetGameKey, UPDATER_CHANNEL, "UPDATE_COMPLETE|" + UPDATER_VERSION);
if (linkTargetStr == "XYZZY") {
// Install to all xyzzy prims (links 35-82)
integer i;
for (i = 35; i <= 82; i++) {
targetUUID = getLinkUUID(i);
if (targetUUID != NULL_KEY) targets += [targetUUID];
}
} else {
integer linkNum = (integer)linkTargetStr;
if (linkNum == 1) {
targets = [targetGame]; // Root prim is the game object itself
} else {
targetUUID = getLinkUUID(linkNum);
if (targetUUID != NULL_KEY) targets += [targetUUID];
}
}
// Reset state
currentOperation = "";
targetGameKey = NULL_KEY;
updatePin = 0;
scriptQueue = [];
currentScriptIndex = 0;
llOwnerSay("💡 Update complete! You can now delete this updater box.");
}
// Handle update errors
handleUpdateError(string error) {
llOwnerSay("❌ Update failed: " + error);
// Notify target game of failure
if (targetGameKey != NULL_KEY) {
llRegionSayTo(targetGameKey, UPDATER_CHANNEL, "UPDATE_FAILED|" + error);
if (llGetListLength(targets) == 0) {
llOwnerSay("⚠️ No valid targets found for " + filename + " (Target: " + linkTargetStr + ")");
// Skip but continue
installIndex++;
downloadNextScript();
return;
}
// Reset state
currentOperation = "";
targetGameKey = NULL_KEY;
updatePin = 0;
scriptQueue = [];
currentScriptIndex = 0;
// Install to all targets
integer t;
for (t = 0; t < llGetListLength(targets); t++) {
key target = llList2Key(targets, t);
llRemoteLoadScriptPin(target, filename, targetPin, TRUE, 0);
}
// Move to next
installIndex++;
downloadNextScript();
}
finishUpdate() {
llOwnerSay("✅ Update sequence complete!");
llRegionSayTo(targetGame, UPDATER_CHANNEL, "UPDATE_COMPLETE|" + CURRENT_VERSION);
isBusy = FALSE;
targetGame = NULL_KEY;
targetPin = 0;
linkUUIDs = [];
}
default {
state_entry() {
reportMemoryUsage("Peril Dice GitHub Updater");
llOwnerSay("🔄 Peril Dice GitHub Updater v" + UPDATER_VERSION);
llOwnerSay("📍 Ready to update nearby Peril Dice games");
llOwnerSay("👆 Touch your Peril Dice game and select 'Check for Updates'");
// Listen for update requests from games
if (!isListening) {
llListen(UPDATER_CHANNEL, "", NULL_KEY, "");
isListening = TRUE;
}
// Set text to show status
llSetText("🔄 Peril Dice GitHub Updater v" + UPDATER_VERSION + "\\n" +
"Ready to install updates from GitHub\\n" +
"Touch your game to begin", <0.2, 1.0, 0.2>, 1.0);
llSetText("📦 Peril Dice Updater\nv" + CURRENT_VERSION + "\nTouch to check version", <0,1,0>, 1.0);
llListen(UPDATER_CHANNEL, "", NULL_KEY, "");
}
on_rez(integer start_param) {
llResetScript();
touch_start(integer num) {
llOwnerSay("🔍 Checking GitHub for latest version...");
currentOperation = "version_check";
currentHttpRequest = llHTTPRequest(GITHUB_API_URL, [HTTP_METHOD, "GET"], "");
}
listen(integer channel, string name, key id, string message) {
if (channel != UPDATER_CHANNEL) return;
list parts = llParseString2List(message, ["|"], []);
string command = llList2String(parts, 0);
if (command == "UPDATE_REQUEST") {
string version = llList2String(parts, 1);
integer pin = (integer)llList2String(parts, 2);
if (channel == UPDATER_CHANNEL) {
list parts = llParseString2List(message, ["|"], []);
string command = llList2String(parts, 0);
llOwnerSay("📨 Update request from " + name + " (current: " + version + ")");
if (currentOperation != "") {
llRegionSayTo(id, UPDATER_CHANNEL, "UPDATE_BUSY|Another update in progress");
return;
if (command == "PING_UPDATER") {
llRegionSayTo(id, UPDATER_CHANNEL, "UPDATER_AVAILABLE|" + CURRENT_VERSION);
}
else if (command == "UPDATE_REQUEST") {
string version = llList2String(parts, 1); // Version requested (not used currently, we install latest)
integer pin = (integer)llList2String(parts, 2);
startUpdate(id, version, pin);
}
else if (command == "LINK_UUIDS_RESPONSE") {
if (id != targetGame) return;
// Parse UUIDs: "12:uuid,35:uuid,..."
string payload = llList2String(parts, 1);
list pairs = llParseString2List(payload, [","], []);
integer i;
for (i = 0; i < llGetListLength(pairs); i++) {
string pair = llList2String(pairs, i);
list kv = llParseString2List(pair, [":"], []);
if (llGetListLength(kv) == 2) {
integer ln = (integer)llList2String(kv, 0);
key uid = (key)llList2String(kv, 1);
// Update or add
integer idx = llListFindList(linkUUIDs, [ln]);
if (idx != -1) {
linkUUIDs = llListReplaceList(linkUUIDs, [uid], idx+1, idx+1);
} else {
linkUUIDs += [ln, uid];
}
}
}
// Reset timer as we got data
llSetTimerEvent(5.0); // Wait a bit more for other chunks if any
// If we have enough critical data, we can start
// For now, let's just wait for the timer to trigger the start
// This allows multiple chunks to arrive
}
// Start the update process
startUpdate(id, pin);
llRegionSayTo(id, UPDATER_CHANNEL, "UPDATE_STARTING|" + UPDATER_VERSION);
}
else if (command == "PING_UPDATER") {
// Respond to updater detection pings
llRegionSayTo(id, UPDATER_CHANNEL, "UPDATER_AVAILABLE|" + UPDATER_VERSION);
}
}
timer() {
// Continue with next script installation
if (currentOperation == "updating") {
downloadNextScript();
}
llSetTimerEvent(0.0);
if (isBusy && installIndex == 0) {
// Timer fired while waiting for UUIDs
if (llGetListLength(linkUUIDs) > 0) {
llOwnerSay("✅ Received link data. Starting installation...");
startDownloadSequence();
} else {
llOwnerSay("❌ Timed out waiting for Link UUIDs.");
llRegionSayTo(targetGame, UPDATER_CHANNEL, "UPDATE_FAILED|Timeout waiting for link data");
isBusy = FALSE;
}
}
}
http_response(key request_id, integer status, list metadata, string body) {
if (request_id != currentHttpRequest) return;
currentHttpRequest = NULL_KEY;
if (status == 200) {
// Successfully downloaded script
string scriptEntry = llList2String(scriptQueue, currentScriptIndex);
list parts = llParseString2List(scriptEntry, ["|"], []);
integer linkNum = (integer)llList2String(parts, 1);
// Install the script
installScript(body, downloadingScript, linkNum);
} else {
handleUpdateError("Failed to download " + downloadingScript + " (HTTP " + (string)status + ")");
if (currentOperation == "version_check") {
if (status == 200) {
string latestVersion = extractJsonValue(body, "tag_name");
llOwnerSay("📊 Latest GitHub Version: " + latestVersion);
llOwnerSay("📦 Updater Version: v" + CURRENT_VERSION);
} else {
llOwnerSay("❌ GitHub check failed: " + (string)status);
}
}
}
touch_start(integer total_number) {
key toucher = llDetectedKey(0);
if (currentOperation == "updating") {
llOwnerSay("⏳ Update in progress - please wait...");
llOwnerSay("📊 Installing script " + (string)(currentScriptIndex + 1) +
" of " + (string)llGetListLength(scriptQueue));
} else {
llOwnerSay("🔄 Peril Dice GitHub Updater v" + UPDATER_VERSION);
llOwnerSay("📍 Ready to update Peril Dice games from GitHub");
llOwnerSay("👆 Touch your Peril Dice game (not this updater) and select 'Check for Updates'");
llOwnerSay("💡 This updater will automatically download and install scripts");
else if (currentOperation == "download_script") {
if (status == 200) {
installScript(body);
} else {
llOwnerSay("❌ Failed to download " + currentFile + ": " + (string)status);
llRegionSayTo(targetGame, UPDATER_CHANNEL, "UPDATE_FAILED|Download failed for " + currentFile);
isBusy = FALSE;
}
}
}
}

View file

@ -1,351 +0,0 @@
// ====================================================================
// Traditional Peril Dice Updater - Inventory-Based Script Copying
// ====================================================================
// This script goes in an updater box that contains all the updated scripts
// in its inventory. It copies scripts from its inventory to the target game.
//
// SETUP:
// 1. Copy all updated LSL scripts into this updater box's inventory
// 2. User rezzes updater box near their Peril Dice game
// 3. User touches their game -> "Check for Updates" -> "Install Update"
// 4. Updater copies scripts from its inventory to their game
// 5. User deletes updater box when finished
// ====================================================================
string UPDATER_VERSION = "2.8.8";
integer UPDATER_CHANNEL = -7723847;
// Update state
key targetGameKey = NULL_KEY;
integer updatePin = 0;
list scriptList = [];
integer currentScriptIndex = 0;
string currentOperation = "";
// Script to link mapping - each script knows where it belongs
list SCRIPT_LINK_MAP = [
// Root prim scripts (Link 1)
"Main_Controller_Linkset", "1",
"Game_Manager", "1",
"Controller_Memory", "1",
"Controller_MessageHandler", "1",
"Player_RegistrationManager", "1",
"Player_DialogHandler", "1",
"NumberPicker_DialogHandler", "1",
"Floater_Manager", "1",
"Roll_ConfettiModule", "1",
"Bot_Manager", "1",
"Game_Calculator", "1",
"Verbose_Logger", "1",
"System_Debugger", "1",
"Update_Receiver", "1",
// Other link scripts
"Game_Scoreboard_Manager_Linkset", "12",
"Leaderboard_Communication_Linkset", "35",
"XyzzyText_Dice_Bridge_Linkset", "83",
"xyzzy_Master_script", "35-82" // Special case - multiple links
];
// Extract unique script names for inventory checking
// NOTE: Update_Receiver is listed LAST to avoid interrupting the update process
list REQUIRED_SCRIPTS = [
"Main_Controller_Linkset",
"Game_Manager",
"Controller_Memory",
"Controller_MessageHandler",
"Player_RegistrationManager",
"Player_DialogHandler",
"NumberPicker_DialogHandler",
"Floater_Manager",
"Roll_ConfettiModule",
"Bot_Manager",
"Game_Calculator",
"Verbose_Logger",
"System_Debugger",
"Game_Scoreboard_Manager_Linkset",
"Leaderboard_Communication_Linkset",
"XyzzyText_Dice_Bridge_Linkset",
"xyzzy_Master_script",
"Update_Receiver" // ALWAYS LAST - don't interrupt update process!
];
// Required objects that should be in updater inventory
list REQUIRED_OBJECTS = [
"StatFloat" // Player status floater object
];
// Memory reporting
reportMemoryUsage(string scriptName) {
integer memory = llGetUsedMemory();
integer freeMemory = llGetFreeMemory();
float memoryPercent = (float)memory / (memory + freeMemory) * 100.0;
llOwnerSay("📊 " + scriptName + ": " + (string)memory + " bytes used (" +
llGetSubString((string)memoryPercent, 0, 4) + "% memory)");
}
// Get target link number for a script
string getTargetLink(string scriptName) {
integer i;
for (i = 0; i < llGetListLength(SCRIPT_LINK_MAP); i += 2) {
if (llList2String(SCRIPT_LINK_MAP, i) == scriptName) {
return llList2String(SCRIPT_LINK_MAP, i + 1);
}
}
return "1"; // Default to root prim if not found
}
// Install script to specific link using llRemoteLoadScriptPin
installScriptToLink(string scriptName, string targetLink) {
if (targetLink == "35-82") {
// Special case: install xyzzy_Master_script to multiple links
llOwnerSay("🔄 Installing " + scriptName + " to links 35-82 (48 copies)...");
integer i;
for (i = 35; i <= 82; i++) {
// Use link number as start_param to target specific link
llRemoteLoadScriptPin(targetGameKey, scriptName, updatePin, TRUE, i);
llOwnerSay("✅ Installed " + scriptName + " to link " + (string)i);
llSleep(0.1); // Brief pause between installations
}
} else {
// Regular installation to single link
integer linkNum = (integer)targetLink;
llOwnerSay("📝 Installing " + scriptName + " to link " + targetLink);
llRemoteLoadScriptPin(targetGameKey, scriptName, updatePin, TRUE, linkNum);
llOwnerSay("✅ Installed " + scriptName + " to link " + targetLink);
}
}
// Check which scripts and objects are available in inventory
checkInventory() {
integer totalItems = 0;
integer availableItems = 0;
llOwnerSay("📋 Checking updater inventory with link mapping...");
// Check scripts
integer i;
for (i = 0; i < llGetListLength(REQUIRED_SCRIPTS); i++) {
string scriptName = llList2String(REQUIRED_SCRIPTS, i);
string targetLink = getTargetLink(scriptName);
totalItems++;
if (llGetInventoryType(scriptName) == INVENTORY_SCRIPT) {
availableItems++;
llOwnerSay("✅ [SCRIPT] " + scriptName + " → Link " + targetLink + " - Ready");
} else {
llOwnerSay("❌ [SCRIPT] " + scriptName + " → Link " + targetLink + " - MISSING");
}
}
// Check objects
for (i = 0; i < llGetListLength(REQUIRED_OBJECTS); i++) {
string objectName = llList2String(REQUIRED_OBJECTS, i);
totalItems++;
if (llGetInventoryType(objectName) == INVENTORY_OBJECT) {
availableItems++;
llOwnerSay("✅ [OBJECT] " + objectName + " - Ready");
} else {
llOwnerSay("❌ [OBJECT] " + objectName + " - MISSING");
}
}
llOwnerSay("📊 Inventory Status: " + (string)availableItems + "/" + (string)totalItems + " items available");
if (availableItems == totalItems) {
llOwnerSay("✅ All required scripts and objects present - updater ready!");
llOwnerSay("🔗 Scripts will be installed to their correct links automatically");
llOwnerSay("📦 Objects will be given to target game inventory");
} else {
llOwnerSay("⚠️ Missing items - please add them to updater inventory");
}
}
// Transfer all objects to target game first
transferObjects() {
llOwnerSay("📦 Transferring objects to target game...");
integer i;
for (i = 0; i < llGetListLength(REQUIRED_OBJECTS); i++) {
string objectName = llList2String(REQUIRED_OBJECTS, i);
if (llGetInventoryType(objectName) == INVENTORY_OBJECT) {
llGiveInventory(targetGameKey, objectName);
llOwnerSay("✅ Transferred " + objectName + " to target game");
}
}
llOwnerSay("📦 Object transfer complete - starting script installation...");
}
// Start the update process
startUpdate(key gameKey, integer pin) {
targetGameKey = gameKey;
updatePin = pin;
currentOperation = "updating";
currentScriptIndex = 0;
// Build script list for this specific update
scriptList = [];
// Add all available scripts
integer i;
for (i = 0; i < llGetListLength(REQUIRED_SCRIPTS); i++) {
string scriptName = llList2String(REQUIRED_SCRIPTS, i);
if (llGetInventoryType(scriptName) == INVENTORY_SCRIPT) {
scriptList += [scriptName];
}
}
llOwnerSay("🔄 Starting update process...");
llOwnerSay("📊 Installing " + (string)llGetListLength(scriptList) + " scripts");
// Transfer objects first, then install scripts
transferObjects();
llSetTimerEvent(2.0); // Brief pause before starting script installation
}
// Install next script in the queue
installNextScript() {
if (currentScriptIndex >= llGetListLength(scriptList)) {
// Update complete!
completeUpdate();
return;
}
string scriptName = llList2String(scriptList, currentScriptIndex);
string targetLink = getTargetLink(scriptName);
llOwnerSay("🚀 Progress: " + (string)(currentScriptIndex + 1) + "/" + (string)llGetListLength(scriptList));
// Install script to correct link(s)
installScriptToLink(scriptName, targetLink);
// Move to next script
currentScriptIndex++;
// Longer pause for xyzzy_Master_script (48 installations)
if (targetLink == "35-82") {
llSetTimerEvent(10.0); // Extra time for multiple installations
} else {
llSetTimerEvent(2.0); // Normal pause
}
}
// Complete the update process
completeUpdate() {
llOwnerSay("🎉 Update installation complete!");
llOwnerSay("📊 Successfully installed " + (string)llGetListLength(scriptList) + " scripts");
// Notify target game
if (targetGameKey != NULL_KEY) {
llRegionSayTo(targetGameKey, UPDATER_CHANNEL, "UPDATE_COMPLETE|" + UPDATER_VERSION);
}
// Reset state and stop timer
currentOperation = "";
targetGameKey = NULL_KEY;
updatePin = 0;
scriptList = [];
currentScriptIndex = 0;
llSetTimerEvent(0.0); // Stop the timer
llOwnerSay("💡 Update complete! You can now delete this updater box.");
llSetText("🎉 Update Complete!\nYou can delete this updater box\nThank you for updating!",
<0.2, 1.0, 0.2>, 1.0);
}
// Handle update errors
handleUpdateError(string error) {
llOwnerSay("❌ Update failed: " + error);
// Notify target game of failure
if (targetGameKey != NULL_KEY) {
llRegionSayTo(targetGameKey, UPDATER_CHANNEL, "UPDATE_FAILED|" + error);
}
// Reset state and stop timer
currentOperation = "";
targetGameKey = NULL_KEY;
updatePin = 0;
scriptList = [];
currentScriptIndex = 0;
llSetTimerEvent(0.0); // Stop the timer
}
default {
state_entry() {
reportMemoryUsage("Traditional Peril Dice Updater");
llOwnerSay("🔄 Traditional Peril Dice Updater v" + UPDATER_VERSION);
llOwnerSay("📦 Inventory-based script copying system");
llOwnerSay("👆 Touch your Peril Dice game and select 'Check for Updates'");
// Check our inventory
checkInventory();
// Listen for update requests
llListen(UPDATER_CHANNEL, "", NULL_KEY, "");
// Set helpful text
llSetText("🔄 Peril Dice Updater v" + UPDATER_VERSION + "\n" +
"Traditional inventory-based system\n" +
"Touch your game, not this updater", <0.2, 1.0, 0.2>, 1.0);
}
on_rez(integer start_param) {
llResetScript();
}
listen(integer channel, string name, key id, string message) {
if (channel != UPDATER_CHANNEL) return;
list parts = llParseString2List(message, ["|"], []);
string command = llList2String(parts, 0);
if (command == "UPDATE_REQUEST") {
string version = llList2String(parts, 1);
integer pin = (integer)llList2String(parts, 2);
llOwnerSay("📨 Update request from " + name + " (current: " + version + ", PIN: " + (string)pin + ")");
if (currentOperation != "") {
llRegionSayTo(id, UPDATER_CHANNEL, "UPDATE_BUSY|Another update in progress");
return;
}
// Start the update process
startUpdate(id, pin);
llRegionSayTo(id, UPDATER_CHANNEL, "UPDATE_STARTING|" + UPDATER_VERSION);
}
else if (command == "PING_UPDATER") {
// Respond to updater detection pings
llRegionSayTo(id, UPDATER_CHANNEL, "UPDATER_AVAILABLE|" + UPDATER_VERSION);
}
}
timer() {
// Continue with next script installation
if (currentOperation == "updating") {
installNextScript();
// Don't turn off timer - installNextScript() will manage timing
} else {
// Only turn off timer if we're not updating
llSetTimerEvent(0.0);
}
}
touch_start(integer total_number) {
key toucher = llDetectedKey(0);
if (currentOperation == "updating") {
llOwnerSay("⏳ Update in progress for " + llKey2Name(targetGameKey));
llOwnerSay("📊 Installing script " + (string)(currentScriptIndex + 1) +
" of " + (string)llGetListLength(scriptList));
} else {
llOwnerSay("🔄 Traditional Peril Dice Updater v" + UPDATER_VERSION);
llOwnerSay("📦 Ready to update Peril Dice games using inventory scripts");
llOwnerSay("👆 Touch your Peril Dice game (not this updater) and select 'Check for Updates'");
llOwnerSay("📋 Checking inventory...");
checkInventory();
}
}
}

View file

@ -316,6 +316,53 @@ default {
else if (command == "UPDATE_BUSY") {
llOwnerSay("⏳ Updater is busy with another installation");
}
else if (command == "REQUEST_LINK_UUIDS") {
// Updater is requesting our link UUIDs for proper script installation
llOwnerSay("📡 Updater requesting link UUIDs for linkset script installation...");
integer linkCount = llGetNumberOfPrims();
// Send critical links first
list criticalLinks = [12, 35, 83]; // Scoreboard, Leaderboard, Dice Bridge
string criticalPayload = "";
integer i;
for (i = 0; i < llGetListLength(criticalLinks); i++) {
integer linkNum = llList2Integer(criticalLinks, i);
if (linkNum <= linkCount) {
key linkUUID = llGetLinkKey(linkNum);
if (criticalPayload != "") criticalPayload += ",";
criticalPayload += (string)linkNum + ":" + (string)linkUUID;
}
}
llRegionSayTo(id, UPDATER_CHANNEL, "LINK_UUIDS_RESPONSE|" + criticalPayload);
llOwnerSay("✅ Sent critical link UUIDs (" + (string)llGetListLength(criticalLinks) + " links)");
if (VERBOSE_LOGGING) llOwnerSay("📊 Critical UUID Data: " + criticalPayload);
// Now send all xyzzy links (35-82) in safe-sized chunks as additional LINK_UUIDS_RESPONSE messages
if (linkCount >= 82) {
string chunk = "";
integer countInChunk = 0;
integer j;
for (j = 35; j <= 82; j++) {
key luuid = llGetLinkKey(j);
string piece = (string)j + ":" + (string)luuid;
// Flush if adding would exceed ~900 chars or ~20 entries
if ((llStringLength(chunk) + 1 + llStringLength(piece)) > 900 || countInChunk >= 20) {
llRegionSayTo(id, UPDATER_CHANNEL, "LINK_UUIDS_RESPONSE|" + chunk);
chunk = "";
countInChunk = 0;
}
if (chunk != "") chunk += ",";
chunk += piece;
countInChunk++;
}
if (chunk != "") {
llRegionSayTo(id, UPDATER_CHANNEL, "LINK_UUIDS_RESPONSE|" + chunk);
}
llOwnerSay("✅ Sent xyzzy UUIDs (links 35-82) in chunks");
}
}
}
}

View file

@ -28,6 +28,11 @@ class LSLValidator:
# Events
'touch_start', 'touch_end', 'listen', 'timer', 'link_message',
'collision_start', 'collision', 'collision_end', 'http_response',
'sensor', 'no_sensor', 'control', 'land_collision_start', 'land_collision',
'land_collision_end', 'at_target', 'not_at_target', 'at_rot_target',
'not_at_rot_target', 'money', 'email', 'run_time_permissions', 'changed',
'attach', 'dataserver', 'moving_start', 'moving_end', 'transaction_result',
'path_update', 'remote_data',
# String functions
'llStringLength', 'llGetSubString', 'llSubStringIndex', 'llToUpper', 'llToLower',
'llMD5String', 'llSHA1String', 'llEscapeURL', 'llUnescapeURL',