Subject: Allow viewer to specify what default script it wants to use, either by language ID or by supplying an inventory item as a template.

From: Rider Linden <[email-masked]>
Date: Fri, 6 Feb 2026 11:01:23 -0800

---
 indra/newview/llpanelcontents.cpp          | 105 ++---------------------------
 indra/newview/llpreviewscript.h            |   4 +-
 indra/newview/lltooldraganddrop.cpp        |   2 +-
 indra/newview/llviewerinventory.cpp        |  64 +++++-------------
 indra/newview/llviewerinventory.h          |   6 +-
 indra/newview/llviewerobject.cpp           |  21 ++++--
 indra/newview/llviewerobject.h             |  10 +--
 scripts/messages/message_template.msg      |   5 ++
 scripts/messages/message_template.msg.sha1 |   2 +-
 9 files changed, 57 insertions(+), 162 deletions(-)

(limited to 'scripts/messages/message_template.msg')

diff --git a/indra/newview/llpanelcontents.cpp b/indra/newview/llpanelcontents.cpp
index bc99899c27..2f060ac2e6 100644
--- a/indra/newview/llpanelcontents.cpp
+++ b/indra/newview/llpanelcontents.cpp
@@ -221,19 +221,6 @@ void LLPanelContents::clearContents()
 // Static functions
 //
 
-// See below comment in `onClickNewScript()` about this hack :(
-static const std::string DEFAULT_SLUA_SCRIPT = R"(
-function LLEvents.touch_start(detected)
-    ll.Say(0, "Touched.")
-end
-
-local function main()
-    print("Hello, Avatar!")
-end
-
-main()
-)";
-
 // static
 void LLPanelContents::onClickNewScript(void *userdata)
 {
@@ -254,20 +241,9 @@ void LLPanelContents::onClickNewScript(void *userdata)
         std::string desc;
         LLViewerAssetType::generateDescriptionFor(LLAssetType::AT_LSL_TEXT, desc);
 
-        // --------------------------------------------------------------------------------------------------
-        // Begin hack
-        //
-        // The current state of the server doesn't allow specifying a default script template,
-        // so we have to update its code immediately after creation instead.
-        //
-        // Moreover, _PREHASH_RezScript has more complex server-side logic than _PREHASH_CreateInventoryItem,
-        // which changes the item's attributes, such as its name and UUID. The simplest way to mitigate this
-        // is to create a temporary item in the user's inventory, modify it as in create_inventory_item()'s
-        // callback, and then call _PREHASH_RezScript to move it into the object's inventory.
-        //
-        // This temporary workaround should be removed after a server-side fix.
-        // See https://github.com/secondlife/viewer/issues/3731 for more information.
-        //
+        U8 script_language = LANG_ID_LSL;
+        LLUUID template_id;
+
         LLViewerRegion* region = object->getRegion();
         if (region && region->simulatorFeaturesReceived())
         {
@@ -275,78 +251,11 @@ void LLPanelContents::onClickNewScript(void *userdata)
             region->getSimulatorFeatures(simulatorFeatures);
             if (simulatorFeatures["LuaScriptsEnabled"].asBoolean())
             {
-                if (std::string::size_type pos = desc.find("lsl2"); pos != std::string::npos)
-                {
-                    desc.replace(pos, 4, "SLua");
-                }
-
-                auto scriptCreationCallback = [object](const LLUUID& inv_item)
-                    {
-                        if (!inv_item.isNull())
-                        {
-                            LLViewerInventoryItem* item = gInventory.getItem(inv_item);
-                            if (item)
-                            {
-                                auto scriptUploadFinished = [object, item](LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD response)
-                                    {
-                                        LLPointer<LLViewerInventoryItem> new_script = new LLViewerInventoryItem(item);
-                                        object->saveScript(new_script, true, true);
-
-                                        // Delete the temporary item from the user's inventory after rezzing it in the object's inventory
-                                        ms_sleep(50);
-                                        LLMessageSystem* msg = gMessageSystem;
-                                        msg->newMessageFast(_PREHASH_RemoveInventoryItem);
-                                        msg->nextBlockFast(_PREHASH_AgentData);
-                                        msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
-                                        msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
-                                        msg->nextBlockFast(_PREHASH_InventoryData);
-                                        msg->addUUIDFast(_PREHASH_ItemID, item->getUUID());
-                                        gAgent.sendReliableMessage();
-
-                                        gInventory.deleteObject(item->getUUID());
-                                        gInventory.notifyObservers();
-                                    };
-
-                                std::string url = gAgent.getRegion()->getCapability("UpdateScriptAgent");
-                                if (!url.empty())
-                                {
-                                    LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLScriptAssetUpload>(
-                                        item->getUUID(),
-                                        "luau",
-                                        DEFAULT_SLUA_SCRIPT,
-                                        scriptUploadFinished,
-                                        nullptr));
-
-                                    LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo);
-                                }
-                            }
-                        }
-                    };
-                LLPointer<LLBoostFuncInventoryCallback> cb = new LLBoostFuncInventoryCallback(scriptCreationCallback);
-
-                LLMessageSystem* msg = gMessageSystem;
-                msg->newMessageFast(_PREHASH_CreateInventoryItem);
-                msg->nextBlock(_PREHASH_AgentData);
-                msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
-                msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
-                msg->nextBlock(_PREHASH_InventoryBlock);
-                msg->addU32Fast(_PREHASH_CallbackID, gInventoryCallbacks.registerCB(cb));
-                msg->addUUIDFast(_PREHASH_FolderID, gInventory.getRootFolderID());
-                msg->addUUIDFast(_PREHASH_TransactionID, LLTransactionID::tnull);
-                msg->addU32Fast(_PREHASH_NextOwnerMask, PERM_MOVE | PERM_TRANSFER);
-                msg->addS8Fast(_PREHASH_Type, LLAssetType::AT_LSL_TEXT);
-                msg->addS8Fast(_PREHASH_InvType, LLInventoryType::IT_LSL);
-                msg->addU8Fast(_PREHASH_WearableType, NO_INV_SUBTYPE);
-                msg->addStringFast(_PREHASH_Name, "New Script");
-                msg->addStringFast(_PREHASH_Description, desc);
-
-                gAgent.sendReliableMessage();
-                return;
+                script_language = LANG_ID_LUA;
             }
         }
-        //
-        // End hack
-        // --------------------------------------------------------------------------------------------------
+        // *TODO* Get a template ID and script_language based on user preferences.  Template ID is the inventory item UUID of a script
+        // in the user's inventory that is used as a template for new scripts.
 
         LLPointer<LLViewerInventoryItem> new_item =
             new LLViewerInventoryItem(
@@ -361,7 +270,7 @@ void LLPanelContents::onClickNewScript(void *userdata)
                 LLSaleInfo::DEFAULT,
                 LLInventoryItemFlags::II_FLAGS_NONE,
                 time_corrected());
-        object->saveScript(new_item, true, true);
+        object->saveScript(new_item, true, true, script_language, template_id);
 
         std::string name = new_item->getName();
 
diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h
index a85837065e..909e2183c2 100644
--- a/indra/newview/llpreviewscript.h
+++ b/indra/newview/llpreviewscript.h
@@ -24,8 +24,7 @@
  * $/LicenseInfo$
  */
 
-#ifndef LL_LLPREVIEWSCRIPT_H
-#define LL_LLPREVIEWSCRIPT_H
+#pragma once
 
 #include "llpreview.h"
 #include "lltabcontainer.h"
@@ -377,4 +376,3 @@ private:
     std::string         mObjectName;
 };
 
-#endif  // LL_LLPREVIEWSCRIPT_H
diff --git a/indra/newview/lltooldraganddrop.cpp b/indra/newview/lltooldraganddrop.cpp
index 5e2d91d31e..87329d204b 100644
--- a/indra/newview/lltooldraganddrop.cpp
+++ b/indra/newview/lltooldraganddrop.cpp
@@ -1828,7 +1828,7 @@ void LLToolDragAndDrop::dropScript(LLViewerObject* hit_obj,
                 }
             }
         }
-        hit_obj->saveScript(new_script, active, true);
+        hit_obj->saveScript(new_script, active, true, 0, LLUUID::null);
         if (gFloaterTools)
         {
             gFloaterTools->dirty();
diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp
index 4d91b7675d..1b80573a1a 100644
--- a/indra/newview/llviewerinventory.cpp
+++ b/indra/newview/llviewerinventory.cpp
@@ -1016,19 +1016,6 @@ void set_default_permissions(LLViewerInventoryItem* item, std::string perm_type)
     }
 }
 
-// See below comment in `create_script_cb()` about this hack :(
-static const std::string DEFAULT_SLUA_SCRIPT = R"(
-function LLEvents.touch_start(detected)
-    ll.Say(0, "Touched.")
-end
-
-local function main()
-    print("Hello, Avatar!")
-end
-
-main()
-)";
-
 void create_script_cb(const LLUUID& inv_item)
 {
     if (!inv_item.isNull())
@@ -1036,40 +1023,6 @@ void create_script_cb(const LLUUID& inv_item)
         LLViewerInventoryItem* item = gInventory.getItem(inv_item);
         if (item)
         {
-            // ------------------------------------------------------------------------------------
-            // Begin hack
-            //
-            // The current state of the server doesn't allow specifying a default script template,
-            // so we have to update its code immediately after creation instead.
-            //
-            // This temporary workaround should be removed after a server-side fix.
-            // See https://github.com/secondlife/viewer/issues/3731 for more information.
-            //
-            LLViewerRegion* region = gAgent.getRegion();
-            if (region && region->simulatorFeaturesReceived())
-            {
-                LLSD simulatorFeatures;
-                region->getSimulatorFeatures(simulatorFeatures);
-                if (simulatorFeatures["LuaScriptsEnabled"].asBoolean())
-                {
-                    std::string url = gAgent.getRegion()->getCapability("UpdateScriptAgent");
-                    if (!url.empty())
-                    {
-                        LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLScriptAssetUpload>(
-                            item->getUUID(),
-                            "luau",
-                            DEFAULT_SLUA_SCRIPT,
-                            nullptr,
-                            nullptr));
-
-                        LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo);
-                    }
-                }
-            }
-            //
-            // End hack
-            // ------------------------------------------------------------------------------------
-
             set_default_permissions(item, "Scripts");
 
             // item was just created, update even if permissions did not changed
@@ -1763,6 +1716,7 @@ void create_new_item(const std::string& name,
                    std::function<void(const LLUUID&)> created_cb = nullptr)
 {
     std::string desc;
+    U8 subtype = NO_INV_SUBTYPE;
     LLViewerAssetType::generateDescriptionFor(asset_type, desc);
     next_owner_perm = (next_owner_perm) ? next_owner_perm : PERM_MOVE | PERM_TRANSFER;
 
@@ -1774,6 +1728,20 @@ void create_new_item(const std::string& name,
         {
             cb = new LLBoostFuncInventoryCallback(create_script_cb);
             next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Scripts");
+
+            LLViewerRegion* region = gAgent.getRegion();
+            if (region && region->simulatorFeaturesReceived())
+            {
+                // *TODO* Setting the subtype for the script will cause the server to select
+                // either the LSL or Lau default script.  We should perhaps allow the user to
+                // select which type of script they want to create.
+                LLSD simulatorFeatures;
+                region->getSimulatorFeatures(simulatorFeatures);
+                if (simulatorFeatures["LuaScriptsEnabled"].asBoolean())
+                {
+                    subtype = LANG_ID_LUA;
+                }
+            }
             break;
         }
 
@@ -1816,7 +1784,7 @@ void create_new_item(const std::string& name,
                           desc,
                           asset_type,
                           inv_type,
-                          NO_INV_SUBTYPE,
+                          subtype,
                           next_owner_perm,
                           cb);
 }
diff --git a/indra/newview/llviewerinventory.h b/indra/newview/llviewerinventory.h
index a42bdaa2b0..4147efe3a9 100644
--- a/indra/newview/llviewerinventory.h
+++ b/indra/newview/llviewerinventory.h
@@ -42,6 +42,10 @@ class LLViewerInventoryCategory;
 class LLInventoryCallback;
 class LLAvatarName;
 
+constexpr U8 NO_INV_SUBTYPE{ 0 };
+constexpr U8 LANG_ID_LSL = 0;
+constexpr U8 LANG_ID_LUA = 1;
+
 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // Class LLViewerInventoryItem
 //
@@ -359,8 +363,6 @@ public:
 extern LLInventoryCallbackManager gInventoryCallbacks;

-const U8 NO_INV_SUBTYPE{ 0 };
-
 // *TODO: Find a home for these
 void create_inventory_item(const LLUUID& agent_id, const LLUUID& session_id,
                            const LLUUID& parent, const LLTransactionID& transaction_id,
diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp
index 0f4cbdd25c..a07c64f252 100644
--- a/indra/newview/llviewerobject.cpp
+++ b/indra/newview/llviewerobject.cpp
@@ -2762,10 +2762,14 @@ void LLViewerObject::doUpdateInventory(
 // save a script, which involves removing the old one, and rezzing
 // in the new one. This method should be called with the asset id
 // of the new and old script AFTER the bytecode has been saved.
-void LLViewerObject::saveScript(
-    const LLViewerInventoryItem* item,
-    bool active,
-    bool is_new)
+// When creating a new script, the asset should be null.  The server
+// will create the new script based on the script_language (LSL or Lua)
+// If a template_id is provided, the new script will be a copy of that item.
+// 
+// *IMPORTANT* If template_id is provided, it must be the ITEM ID of a 
+// copy/mod-able script in the user's inventory.
+void LLViewerObject::saveScript(const LLViewerInventoryItem* item,
+    bool active, bool is_new, U8 script_language, const LLUUID& template_id)
 {
     /*
      * XXXPAM Investigate not making this copy.  Seems unecessary, but I'm unsure about the
@@ -2794,6 +2798,15 @@ void LLViewerObject::saveScript(
     msg->addBOOLFast(_PREHASH_Enabled, enabled);
     msg->nextBlockFast(_PREHASH_InventoryBlock);
     task_item->packMessage(msg);
+
+    // This is a completely new script (no asset id)
+    if (task_item->getAssetUUID().isNull())
+    {
+        msg->nextBlock("NewScriptInfo");
+        msg->addU8("Type", script_language);
+        msg->addUUID("TemplateID", template_id);
+    }
+
     msg->sendReliable(mRegionp->getHost());
 
     // do the internal logic
diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h
index cccf59a319..271c35836f 100644
--- a/indra/newview/llviewerobject.h
+++ b/indra/newview/llviewerobject.h
@@ -25,8 +25,7 @@
  * $/LicenseInfo$
  */
 
-#ifndef LL_LLVIEWEROBJECT_H
-#define LL_LLVIEWEROBJECT_H
+#pragma once
 
 #include <map>
 #include <unordered_map>
@@ -549,7 +548,10 @@ public:
     // save a script, which involves removing the old one, and rezzing
     // in the new one. This method should be called with the asset id
     // of the new and old script AFTER the bytecode has been saved.
-    void saveScript(const LLViewerInventoryItem* item, bool active, bool is_new);
+    // If is_new is true, then we are saving a new script. In this case,
+    // template_id will be used by the simulator and the new script will be a copy of that script
+    // asset, otherwise script_language will request the correct generic starting script.
+    void saveScript(const LLViewerInventoryItem* item, bool active, bool is_new, U8 script_language, const LLUUID& template_id);
 
     // move an inventory item out of the task and into agent
     // inventory. This operation is based on messaging. No permissions
@@ -1102,5 +1104,3 @@ public:
     virtual void updateDrawable(bool force_damped);
 };
 
-
-#endif
diff --git a/scripts/messages/message_template.msg b/scripts/messages/message_template.msg
index 40ba2cc6b6..d3d60ea584 100644
--- a/scripts/messages/message_template.msg
+++ b/scripts/messages/message_template.msg
@@ -6818,6 +6818,11 @@ version 2.0
         {   CreationDate    S32         }
         {   CRC             U32         }
     }
+    {
+        NewScriptInfo Variable
+        {   Type            U8          }
+        {   TemplateID      LLUUID      }
+    }
 }
 
 // Create inventory
diff --git a/scripts/messages/message_template.msg.sha1 b/scripts/messages/message_template.msg.sha1
index baa4f3f12b..745eec1971 100644
--- a/scripts/messages/message_template.msg.sha1
+++ b/scripts/messages/message_template.msg.sha1
@@ -1 +1 @@
-aaecaf01b6954c156662f572dc3ecaf26de0ca67
\ No newline at end of file
+2244732a49f7b3fa00694b9870d5d6d3dc0c5cb2
\ No newline at end of file
-- 
cgit v1.3