|
Page 1 of 1
|
[ 9 posts ] |
|
Lua scripting and viewer automation feature
Author |
Message |
Henri Beauchamp
Joined: 2009-03-17 18:42:51 Posts: 5981
|
Starting with v1.26.20.4, the Cool VL Viewer implements Lua ( Lua v5.4 since v1.28.0.1) scripting/automation support. EDIT: it became way too tedious for me to maintain a full (70+ pages worth) Lua documentation on this forum, and for you to read it as an immense wall of text... Please, refer to the PDF manual instead. There is a specific thread for Lua scripting related feature requests. Please use it to post any such request.
|
2017-01-14 11:14:35 |
|
 |
Henri Beauchamp
Joined: 2009-03-17 18:42:51 Posts: 5981
|
Sample/demonstration automation script (user_settings/automation.lua):  |  |  |  | Code: known_ids = {} known_sessions = {} tp_started = false tp_retry = false max_agents = 20 max_complexity_shield_off = 0 max_complexity_shield_on = 200000 max_area_shield_off = 0 max_area_shield_on = 200 max_memory_shield_off = 0 max_memory_shield_on = 100 shield_on=false derendered_objects = { "253538b0-56fe-1feb-8089-2cade9c5a413", "6b752a3c-c0a8-8df7-0d0e-d95c0cda33a1" } low_dd_regions = { ["Porten Hill"]=true } protected_attachments = { "hair", "collar", "cuff", "nipple", "penis", "pussy", "hoof", " ear", "horn", "tail", "muzzle" } protected_layers = { "shape", "eyes", "hair", "skin", "alpha" } avatars_colors = {} account_settings = {}
-- Here, we define a custom set of side bar buttons for performing useful -- tasks or opening commonly used floaters that do not have an associated -- toolbar button. -- We also perform tasks that can only happen after successful login. function OnLogin(location) -- Setup the sidebar SideBarHideOnRightClick() SideBarHide(false) SideBarButton(1, "\u{2699}", "OpenFloater('preferences')", "Opens the Preferences floater") -- Setup the side bar button for spelling language toggling: ToggleSpellCheckLanguage(false) SideBarButton(3, "inv_item_landmark_visited.tga", "OpenFloater('teleport history')", "Opens the Teleport history") SideBarButton(4, "inv_item_mesh.tga", "nop", "Toggles mesh queue info") SideBarButtonToggle(4, "DebugShowMeshQueue") SideBarButton(5, "43f0a590-f3d3-48b5-b460-f5b3e6e03626.tga", "OpenFloater('sounds list')", "Opens the Sounds list floater") -- Setup the side bar button for anti-griefers protection toggling: OnAutomationMessage("shield-off") -- Setup the side bar button for attachments and layers protection: local agent = GetAgentInfo() if agent.rlv then OnAutomationMessage("unprotect") end -- Retrieve our per-account settings and validate them (we use a table for -- our settings, since it is much simpler than dealing with serialization of -- settings to a data string ourselves). local settings = GetPerAccountData() if type(settings) == "table" then account_settings = settings end -- Restore last Windlight and water settings, if any if account_settings.last_sky then ApplySkySettings(account_settings.last_sky) end if account_settings.last_water then ApplyWaterSettings(account_settings.last_water) end -- Derender objects we never want to see for i = 1, #derendered_objects, 1 do DerenderObject(derendered_objects[i]) end -- Set the draw distance for the login sim, after letting a chance -- to the viewer to connect to potential neighbor sims. CallbackAfter(5, SetDrawDistance) -- Define the Lua pie menu for avatars DefineAvatarPieMenu() end
-- This callback is invoked each time the side bar visibility is changed. We -- use it to display the Lua icon in the status bar whenever the side bar gets -- hidden and we setup the command for that icon so that, when clicked, it -- shows the side bar (which in turn hides the status bar icon via this -- callback). function OnSideBarVisibilityChange(visible) if visible then StatusBarLuaIcon("") else StatusBarLuaIcon("SideBarHide(false)", "Shows the Lua buttons side bar") end end
-- This function toggles the spell checking language between English and -- French: much easier than doing it via the Preferences menu... Of course, -- you will have to make sure first that all the related dictionaries have -- been installed on your system (from "Preferences" -> "Cool features" -> -- "Chat, IM & text"). function ToggleSpellCheckLanguage(toggle) local lang = GetDebugSetting("SpellCheckLanguage") if toggle then if lang == "fr_fr" then lang = "en_us" else lang = "fr_fr" end SetDebugSetting("SpellCheckLanguage", lang) end if lang == "fr_fr" then SideBarButton(2, "En", "AutomationMessage('language')", "Toggles the spell checking language to English (US)") else SideBarButton(2, "Fr", "AutomationMessage('language')", "Toggles the spell checking language to French") end end
-- This callback receives the messages sent by the various buttons we setup via -- the AutomationMessage() function. function OnAutomationMessage(text) if text == "shield-on" then shield_on=true SetDebugSetting("AllowSwapping", false) SetDebugSetting("MainMemorySafetyCheck64", true) SetDebugSetting("RenderAvatarMaxComplexity", max_complexity_shield_on) SetDebugSetting("RenderAutoMuteSurfaceAreaLimit", max_area_shield_on) SetDebugSetting("RenderAutoMuteMemoryLimit", max_memory_shield_on) SetDebugSetting("KillBogusObjects", true) SideBarButton(6, "shield_off.png", "AutomationMessage('shield-off')", "Disables anti-griefers features") OpenNotification(0, "Anti-griefer features enabled") elseif text == "shield-off" then shield_on=false SetDebugSetting("AllowSwapping", true) SetDebugSetting("MainMemorySafetyCheck64", false) SetDebugSetting("RenderAvatarMaxComplexity", max_complexity_shield_off) SetDebugSetting("RenderAutoMuteSurfaceAreaLimit", max_area_shield_off) SetDebugSetting("RenderAutoMuteMemoryLimit", max_memory_shield_off) SetDebugSetting("KillBogusObjects", false) SideBarButton(6, "shield_on.png", "AutomationMessage('shield-on')", "Enables anti-griefers features") OpenNotification(0, "Anti-griefer features disabled") elseif text == "cancel-auto-tp" then tp_retry = false OverlayBarLuaButton("", "") elseif text == "language" then ToggleSpellCheckLanguage(true) elseif text == "protect" then SideBarButton(8, "9beb8cdd-3dce-53c2-b28e-e1f3bc2ec0a4.tga", "AutomationMessage('unprotect')", "Unlock protected layers and attachments") SideBarButtonToggle(8, true) LockAttachmentsAndLayers() OpenNotification(0, "Protected attachments and layers locked") elseif text == "unprotect" then SideBarButton(8, "9beb8cdd-3dce-53c2-b28e-e1f3bc2ec0a4.tga", "AutomationMessage('protect')", "Lock protected layers and attachments") SideBarButtonToggle(8, false) ExecuteRLV("clear") OpenNotification(0, "Protected attachments and layers unlocked") end end
-- This is just a demonstration of how Lua scripting may be used as a mean of -- defense against griefing attempts: here, when pushed and the anti-griefer -- measures are on, the avatar is automatically sat down on ground to prevent -- further effects of new pushes. function OnAgentPush(id, t, magnitude) if shield_on and mag > 3 then AgentSit() end OpenNotification(0, "Push detected, magnitude = " .. mag) end
-- Here we add pseudo commands to: -- * emulate Firestorm's "/dd" (draw distance) command (with "/dd" alone as an -- alias for "/dd 256"); -- * toggle the camera front view with "/fc"; -- * adjust the avatar Z offset with "/z " followed by a amount of centimeters -- (e.g.: "/z -8" to lower your avatar height by 8 cm), with "/z" alone to reset -- the offset to zero. -- We also create a pseudo-gesture ("/g " for "greetings") accepting a parameter -- (the name or title of the resident you want to greet). function OnSendChat(text) if string.sub(text, 1, 1) ~= "/" then -- Do not waste time searching for commands if the first character is -- not a slash... return text end if text == "/fc" then SetDebugSetting("CameraFrontView", not GetDebugSetting("CameraFrontView")) return "" end if text == "/dd" then SetDebugSetting("RenderFarClip", 256) return "" end local i, j = string.find(text, "/dd ") if i == 1 then local distance = tonumber(string.sub(text, j + 1)) if distance > 512 then distance = 512 elseif distance < 32 then distance = 32 end SetDebugSetting("RenderFarClip", distance) return "" end if text == "/z" then SetDebugSetting("AvatarOffsetZ", 0) return "" end i, j = string.find(text, "/z ") if i == 1 then local offset = tonumber(string.sub(text, j + 1)) / 100 if offset > 9 then offset = 9 elseif offset < -9 then offset = 9 end SetDebugSetting("AvatarOffsetZ", offset) return "" end i, j = string.find(text, "/g ") if i == 1 then text = "/me smiles softly, \"Greetings" .. string.sub(text, j) .. ".\"" end return text end
-- This is just a demonstration of SLURL dispatching and floater opening, all -- wrapped up in an OnReceivedChat() callback: it got no real practical use -- other than being a sample of what can be done (and may be dangerous to use -- "as is", due to the SLURL systematic auto-dispatching). function OnReceivedChat(t, id, is_avatar, name, text) local i, j = string.find(text, "secondlife://") if i then OpenNotification(0, name .. " sent an SURL, dispatching it") DispatchSLURL(string.sub(text, i)) return end if known_ids[id] then return end known_ids[id] = true if is_avatar then OpenNotification(0, name .. " is a new chatting avatar: displaying profile.") OpenFloater("avatar info", id) else OpenNotification(0, name .. " is a new chatting object: inspecting it.") OpenFloater("inspect", id) end end
-- Here is a demonstration on how to use the OnChatTextColoring() callback to -- color incoming chat text from avatars. In this example, the friends' chat -- text is colored in pink. function OnChatTextColoring(id, name, text) if IsAgentFriend(id) then return "pink2" end return "" end
-- This is just a demonstration of IM callbacks usage and of how to use the -- CallbackAfter() function: it got no real practical use other than being a -- sample of what can be done. function InstantMsgReply(session_id, name, text) OpenNotification(0, name .. " opened a new IM session: replying now.") SendIM(session_id, text) end
function OnInstantMsg(session_id, origin_id, t, name, text) if known_sessions[session_id] then return end known_sessions[session_id] = true -- Wait 3 seconds for the IM session to start (important for group sessions) before replying: CallbackAfter(3, InstantMsgReply, session_id, name, "Hello !") end
-- This function is used to automatically set the draw distance after TP: -- If the sim we arrive into is listed in the low_dd_regions table, then the -- draw distance is set to 128m. If it is a sim without neighbors (island), -- then the draw distance is set to 512m (to rez everything in sim). -- In all other cases, the draw distance is set to 256m. -- Note that the "speed rezzing" feature is also accounted for (i.e. there -- will not be conflict between this code and the feature: the draw distance -- final adjustment is done once the speed rezzing adjustments are over). function SetDrawDistance() if GetDebugSetting("SpeedRez") then local saved_dd = GetDebugSetting("SavedRenderFarClip") if saved_dd > 0 and saved_dd ~= GetDebugSetting("RenderFarClip") then CallbackAfter(GetDebugSetting("SpeedRezInterval") + 1, SetDrawDistance) return end end local dd = 256 local location = GetGridSimAndPos() local region = location.region if low_dd_regions[region] then dd = 128 elseif location.neighbors == 0 then dd = 512 end SetDebugSetting("RenderFarClip", dd) end
-- The code below allows to auto-retry failed teleports (provided the TP global -- coordinates were known when the TP was first attempted: this is the case for -- all TPs done from the world map floater, but may not be the case for landmarks -- TPs and TP invites, at least on the first TP attempt). It illustrates the use -- of the Lua dialog and overlay bar button, of the AutomationMessage() function -- and of the TP related callbacks and functions. function OnTPStateChange(state, reason) if state == 0 then -- TELEPORT_NONE tp_retry = false if tp_started and string.len(reason) > 0 and reason ~= "invalid_tport" and reason ~= "nolandmark_tport" and reason ~= "noaccess_tport" and reason ~= "no_inventory_host" then if reason == "no_host" then -- Auto-retry after sim comes back online tp_retry = true OverlayBarLuaButton("Cancel auto-TP", "AutomationMessage('cancel-auto-tp')") else MakeDialog("Retry TP", "To auto-retry the TP, give the max allowed number of agents for this sim, else press the Cancel button", max_agents, "Cancel", "", "OK", "DialogClose()", "", "DialogClose()") end else -- Set the draw distance for the arrival sim, after letting a chance -- to the viewer to connect to potential neighbor sims. CallbackAfter(8, SetDrawDistance) end tp_started = false elseif state == 1 then -- TELEPORT_START tp_started = true tp_retry = false elseif state ~= 2 then -- all other teleport states than TELEPORT_REQUESTED tp_started = false tp_retry = false end end
function OnLuaDialogClose(title, button, text) if title == "Retry TP" and button == 3 then max_agent = tonumber(text) if max_agent > 0 then tp_retry = true OverlayBarLuaButton("Cancel auto-TP", "AutomationMessage('cancel-auto-tp')") end end end
function OnFailedTPSimChange(agents, x, y, z) if tp_retry and agents < max_agents then TeleportAgentToPos(x, y, z) OverlayBarLuaButton("", "") end end
-- This is called each time the user loads new Windlight or water presets. -- We then save the new presets into the per-account settings, so to restore -- them on next login with the same avatar. function OnWindlightChange(sky_settings_name, water_settings_name) if sky_settings_name ~= "" then account_settings.last_sky = sky_settings_name end if water_settings_name ~= "" then account_settings.last_water = water_settings_name end SetPerAccountData(account_settings) end
-- Here is an example of how to use the Lua pie menu, the mini-map and tag -- colors, and the OnAvatarRezzing() callback. -- This defines an avatar pie menu with colors you can set for the corresponding -- avatar's dot in the mini-map and name tag. The color is remembered during the -- session, even if the avatar gets de-rezzed and re-rezzed. function DefineAvatarPieMenu() LuaPieMenuSlice(4, 1, "Blue", "nop") LuaPieMenuSlice(4, 2, "Cyan", "nop") LuaPieMenuSlice(4, 3, "Red", "nop") LuaPieMenuSlice(4, 4, "Magenta", "nop") LuaPieMenuSlice(4, 5, "Yellow", "nop") LuaPieMenuSlice(4, 6, "White", "nop") LuaPieMenuSlice(4, 7, "Default", "nop") LuaPieMenuSlice(4, 8, "Green", "nop") end
function OnLuaPieMenu(data) local color = "" if data.slice == 1 then color = "blue" elseif data.slice == 2 then color = "cyan" elseif data.slice == 3 then color = "red" elseif data.slice == 4 then color = "magenta" elseif data.slice == 5 then color = "yellow" elseif data.slice == 6 then color = "white" elseif data.slice == 8 then color = "green" end avatars_colors[data.object_id] = color SetAvatarMinimapColor(data.object_id, color) SetAvatarNameTagColor(data.object_id, color) end
function OnAvatarRezzing(id) local color = avatars_colors[id] if color then SetAvatarMinimapColor(id, color) SetAvatarNameTagColor(id, color) end end
-- This function allows to lock, via RestrainedLove, object attachments and clothing layers -- based on their name (for attachments) or type (for clothing layers). Only the joints and -- layers corresponding to protected items currently worn are locked. function LockAttachmentsAndLayers() local i, j, k, v, name local protected = {} local items = GetAgentWearables(); for k, v in pairs(items) do j = string.find(v, "|") name = string.lower(string.sub(v, j + 1)) for i = 1, #protected_layers, 1 do if protected_layers[i] == name then protected[name] = true break end end end for k, v in pairs(protected) do ExecuteRLV("remoutfit:" .. k .. "=n") end protected = {} items = GetAgentAttachments() for k, v in pairs(items) do j = string.find(v, "|") name = string.lower(string.sub(v, 0, j - 1)) for i = 1, #protected_attachments, 1 do if string.find(name, protected_attachments[i]) then protected[string.sub(v, j + 1)] = true break end end end for k, v in pairs(protected) do ExecuteRLV("remattach:" .. k .. "=n") end end
|  |  |  |  |
|
2017-01-28 14:53:34 |
|
 |
Henri Beauchamp
Joined: 2009-03-17 18:42:51 Posts: 5981
|
Here is what the Lua status bar icon looks like:  And here is an example of a side-bar corresponding to the sample script given in the previous post: 
|
2017-02-13 09:37:11 |
|
 |
Henri Beauchamp
Joined: 2009-03-17 18:42:51 Posts: 5981
|
Here is a simple way to emulate Firestorm's parcel Windlight support in the Cool VL Viewer, via Lua (just add/merge to your automation.lua script): Notes: - I only scripted above the "basic" support, without the altitude zoning syntax (it could be added, however): the Windlight settings will therefore be applied whatever the altitude of your avatar.
- Since the Cool VL Viewer does not include all the default Windlight settings Firestorm got, you will have to add the latter into the user_settings/windlight/ directory, so that the Cool VL Viewer can find them.
|
2017-03-28 09:18:38 |
|
 |
Henri Beauchamp
Joined: 2009-03-17 18:42:51 Posts: 5981
|
Custom Lua floaters are supported via specific functions. Here is an example of how to use them. First, add a "floater_lua_test.xml" file to your custom skins directory ("~/.secondlife/skins/default/xui/en-us/" under Linux and "%appdata%\SecondLife\skins\default\xui\en-us\" under Windows) containing the following XUI floater definition: Then, add this code to your automation.lua script:  |  |  |  | Code: function OnLuaFloaterOpen(name, parameter) print("Opened floater: " .. name .. " with parameter: " .. parameter) if name == "test" then SetLuaFloaterCommand("test","spin1","print('Spinner=' .. GetValue())") SetLuaFloaterCommand("test","check1","print('Check=' .. GetValue())") SetLuaFloaterCommand("test","combo1","print('Combo=' .. GetValue())") SetLuaFloaterCommand("test","radio1","print('Radio=' .. GetValue())") SetLuaFloaterCommand("test","flyout1","if GetValue() == 'send_line' then;print('Input line=' .. GetLuaFloaterValue(GetFloaterName(),'lineedit1'));else;print('Text=' .. GetLuaFloaterValue(GetFloaterName(),'textedit1'));end") SetLuaFloaterCommand("test","button1","SetLuaFloaterValue(GetFloaterName(),'inventory1',GetLuaFloaterValue(GetFloaterName(),'lineedit1'))") SetLuaFloaterCommand("test","button2","print('Cancel');FloaterClose()") SetLuaFloaterCommand("test","button3","print('Magnitude=' .. GetLuaFloaterValue(GetFloaterName(),'slider1'));FloaterClose()") SetLuaFloaterValue("test","list1","<BOLD>Item 1|<1.0,0.5,0.25>2019-01-08") SetLuaFloaterValue("test","list1","<ITALIC>Item 2|<red2>2019-01-09") SetLuaFloaterValue("test","list1","<BOLD><ITALIC><blue2>Item 3|2019-01-01") SetLuaFloaterValue("test","list1","<green3>Item 4|<ITALIC>2019-01-11") SetLuaFloaterValue("test","namelist1",GetAgentInfo().id) SetLuaFloaterValue("test","namelist1","<GROUP>d1cc93e1-b51e-d0c2-e699-6c06e3015e9d") end end
function OnLuaFloaterClose(name, parameter) print("Closed floater: " .. name .. " - Parameter: " .. parameter) end
function OnLuaFloaterAction(name, control, value) print("Floater: " .. name .. " - Control: " .. control .. " - Value: " .. value) if value ~= "" and (string.find(control, "list") or string.find(control, "inventory")) then local values = GetLuaFloaterValues(name, control) local i for i = 1, #values, 1 do print(" --> value #" .. i .. " = " .. values[i]) end end end
|  |  |  |  |
Finally, log in with the viewer and type in the chat: to test the result. Have fun ! 
|
2019-01-05 11:49:39 |
|
 |
Henri Beauchamp
Joined: 2009-03-17 18:42:51 Posts: 5981
|
The Cool VL Viewer v1.26.22.36 released today implements Lua script files preprocessing support via a built-in prepocessor. When loading a Lua script file (be it the automation script or any script you would load via the "Advanced" -> "Lua scripting" menu), the viewer first attempts to load the file as a genuine Lua script file and, on failure (which happens if it encounters preprocessor #directives elsewhere than on the first line of the script (*)), it hands over the file to the built-in preprocessor, which returns a (long) string containing the preprocessed sources, the Lua interpreter then being fed again with it in a second (and last) attempt to execute the script.
(*) As per Lua specifications, the first line of any script that would start with a '#' is ignored. This is to cope with shebang lines.
|
2019-02-23 14:40:41 |
|
 |
Henri Beauchamp
Joined: 2009-03-17 18:42:51 Posts: 5981
|
The Cool VL Viewer v1.28.0.6 released today implements Lua automation threads. So far, the Cool VL Viewer could only execute (relatively) short sequences of Lua code based on automation script callbacks (or command lines), and that Lua code was executed within the main thread/loop of the viewer, meaning the more complex the code and the more the frame rate would 'hiccup' (given the burst-like load of such Lua callbacks). Also, to avoid freezes or infinite loops, the Lua code executed by the automation script is subject to a watchdog (short) timeout, meaning you cannot perform very complex and long processing this way. With automation threads, you may now run a full Lua program (and in fact up to 8 Lua programs) in the background (your OS will affect those threads to unused or lightly loaded CPU cores, meaning they won't even slow down the viewer itself !). A Lua thread is started (from the automation script only) by loading a separate Lua file and executing it as a separate "Lua state". However, that thread still gets the opportunity to call the viewer-specific Lua commands (even non-thread-safe ones, thanks to a special and transparent mechanism), just like if they would be executed by the automation script itself. It may also exchange data with the automation script (and other threads) via "signals". A Lua thread source is a Lua program file containing at least a ThreadRun() function as an entry point for the thread looping code. ThreadRun() is called at each thread loop and must return a boolean; when the latter is true, the thread keeps running and ThreadRun() is called again after a 1ms "sleep" (which is used both to yield to the OS and allow threads rescheduling by the latter, and to avoid "eating up" a full CPU core when ThreadRun() executes very short sequences of Lua code). Whenever ThreadRun() returns false, the thread is stopped and destroyed. When it is launched by the automation script (via the StartThread() function), the thread may receive parameters in a global "argv" table. Note that to avoid infinite loops and to allow timely detection of thread stopping requests from the viewer, the ThreadRun() function execution time is still bound by a 0.5s watchdog. However, a special Sleep() Lua function is available to threads, which resets the watchdog when invoked (because signals and thread stopping requests are checked/processed during the Sleep() call, even when sleeping for 0ms); so, even if each ThreadRun() involves complex/long processing, you can ensure it will not be interrupted by the watchdog under normal operation, by calling Sleep() appropriately. The Lua thread program file may also contain an OnSignal() callback (and the automation script may also have such a callback to receive data from threads). This callback is entered whenever another thread (or the automation script) invokes SendSignal(), directing it to our thread. As for the viewer-specific Lua functions which are not thread-safe (e.g. SendChat()), the mechanism I implemented ensures they can be called from threads nonetheless (they cause the thread to pause and set a variable indicating to the viewer main thread that it needs the corresponding code to be ran on its behalf, which is performed during the (badly named) "idle loop" of the viewer). The print() and warn() Lua functions receive a special treatment: they use an internal print buffer when invoked from a Lua thread and the text gets printed in the viewer chat on the next return of ThreadRun(), on the next invocation of Sleep() or of a non-thread-safe viewer-specific Lua function invocation (whichever happens first). Here is a (dummy) example of how to use threads: 1.- Add this code to your automation.lua script:  |  |  |  | Code: function OnAutomationRequest(request) -- Starts a new thread if request == "thread" then local argv = { "foo", "bar", name="My thread" } local thead_id = StartThread("thread.lua", argv) if thead_id then return "Thread started with Id: " .. string.format("%d", thead_id) else return "Failure to start a new thread" end end -- Stops a thread by Id local i, j = string.find(request, "-thread ") if i == 1 then local thead_id = tonumber(string.sub(request, j + 1)) if StopThread(thead_id) then return "Stopped" else return "No such thread" end end -- Checks for thread existence, by Id local i, j = string.find(request, "thread%? ") if i == 1 then local thead_id = tonumber(string.sub(request, j + 1)) return tostring(HasThread(thead_id)) end -- Sends a signal to a thread, by Id local i, j = string.find(request, "thread! ") if i == 1 then local thead_id = tonumber(string.sub(request, j + 1)) return tostring(SendSignal(thead_id, { "signal" })) end return "" end
function OnSignal(from_id, timestamp, sig) print("Signal received from thread Id: " .. string.format("%d", from_id) .. " - Timestamp: " .. tostring(timestamp) .. "s. Contents:") for k, v in pairs(sig) do print(tostring(k) .. ": " .. tostring(v)) end print("---------------") end
|  |  |  |  |
2.- Create a "thread.lua" file and place it in user_settings/include/ (or in user_settings): 3.- Start the viewer, and from the chat, enter: /lua print(AutomationRequest("thread")) /lua print(AutomationRequest("thread")) /lua print(AutomationRequest("thread! 2")) /lua print(AutomationRequest("thread")) /lua print(AutomationRequest("thread? 1")) /lua print(AutomationRequest("-thread 1")) /lua print(AutomationRequest("thread? 1")) etc...
|
2020-08-22 09:22:48 |
|
 |
Henri Beauchamp
Joined: 2009-03-17 18:42:51 Posts: 5981
|
This is just an example of what can be done when using LSL and Lua in conjunction (here to automatically invite residents to a group based on a list of names stored in a note card). Do not forget to enable "Advanced" -> "Lua scripting" -> "Accept Lua from LSL scripts". To work, this scripts needs to be executed while logged in with the Cool VL Viewer is v1.28.2.52 or newer (it needs the new AgentGroupInvite() Lua function).  |  |  |  | Code: // Cool Group Inviter script v1.00 (c)2021 Henri Beauchamp. // Released under the GPL license: https://www.gnu.org/licenses/gpl-3.0.en.html
// The UUID of the group you want to invite to. // (this is an invalid group Id in this example). string GroupId = "e13c93e1-b51e-d0c2-e889-6cf6e3015ead"; // The UUID of the role in the group (keep empty for the default "Member" role). string RoleId = ""; // The name of the notecard (which must be placed along this script in the // inviter object) containing the names of the avatars to invite: one per line. string Notecard = "*Invited*"; // The Lua prefix configured in the Cool VL Viewer "LuaScriptCommandPrefix" // debug setting. string LuaPrefix = "/lua ";
key NotecardQueryId; key AvatarQueryId; integer Counter; integer Channel; integer Handle; integer AvatarIndex; list Invited;
SetHoverText(string msg) { llSetText(msg, <0.0, 1.0, 1.0>, 1.0); }
// This function asks the viewer to perform via Lua a group data request and // sends the reply to our private 'Channel'. The "roles_list_ok" flag in the // group data Lua table is either 'nil' (when the agent groups have not yet // been received, or when the group does not exist, or when the agent is not a // member of this group), 'false' (roles data not yet fully received) or 'true' // (all data received by the viewer, and we are therefore ready for invites). CheckGroupData() { string cmd = LuaPrefix + "t=GetAgentGroupData(\"" + GroupId; cmd += "\");SendChat(\"/" + (string)Channel + "roles_list_ok = \""; cmd += " .. tostring(t[\"roles_list_ok\"]))"; llOwnerSay(cmd); }
default { state_entry() { if (llGetInventoryType(Notecard) == INVENTORY_NOTECARD) { NotecardQueryId = AvatarQueryId = NULL_KEY; Channel = 30000 - (integer)llFrand(10000.0); SetHoverText("Group inviter ready. Touch to start."); } else { // Leave NotecardQueryId and AvatarQueryId as empty strings SetHoverText("Missing \"" + Notecard + "\" notecard."); } }
on_rez(integer param) { llResetScript(); }
changed(integer change) { if (change & CHANGED_INVENTORY) { llResetScript(); } }
listen(integer chan, string name, key id, string msg) { if (msg == "roles_list_ok = nil") { if (Counter++ == 0) { // Wait for the viewer to receive the agent groups list and // check again... llSleep(5.0); CheckGroupData(); return; } SetHoverText("No such group or not a member of this group !"); llListenRemove(Handle); Handle = 0; } else if (msg == "roles_list_ok = false") { // Wait a bit for the viewer to receive all data and check again... llSleep(10.0); CheckGroupData(); } else if (msg == "roles_list_ok = true") { SetHoverText("Reading invitations list..."); Invited = []; Counter = 0; NotecardQueryId = llGetNotecardLine(Notecard, Counter); llListenRemove(Handle); Handle = 0; } }
touch_start(integer n) { if (llDetectedKey(0) != llGetOwner() || Handle != 0 || NotecardQueryId != NULL_KEY || AvatarQueryId != NULL_KEY) { // Either the touch was not from our owner or work is in progress, // or the notecard is absent (Ids are then empty strings). return; } SetHoverText("Checking for group data availability..."); // Open our channel to use for sending Lua commands replies back to us Handle = llListen(Channel, "", llGetOwner(), ""); Counter = 0; // We need to check at least twice... CheckGroupData(); }
dataserver(key query_id, string data) { if (query_id == NotecardQueryId) { if (data == EOF) { NotecardQueryId = NULL_KEY; Counter = llGetListLength(Invited); if (Counter > 0) { AvatarIndex = 0; AvatarQueryId = llRequestUserKey(llList2String(Invited, AvatarIndex)); } } else { data = llStringTrim(data, STRING_TRIM); if (llStringLength(data) > 2) { SetHoverText("Adding:\n" + data); Invited += data; } NotecardQueryId = llGetNotecardLine(Notecard, ++Counter); } } else if (query_id == AvatarQueryId) { if ((key)data != NULL_KEY) { string tmp = "Invitation " + (string)(AvatarIndex + 1) + "/"; tmp += (string)Counter + ":\n"; tmp += llList2String(Invited, AvatarIndex); SetHoverText(tmp); tmp = LuaPrefix + "AgentGroupInvite(\"" + data; tmp += "\", \"" + GroupId; if (RoleId != "") { tmp += "\", \"" + RoleId; } llOwnerSay(tmp + "\")"); } if (++AvatarIndex < Counter) { llSleep(0.55); // Requests throttled server-side at 1.9 per second AvatarQueryId = llRequestUserKey(llList2String(Invited, AvatarIndex)); } else { SetHoverText("Invitations finished."); AvatarQueryId = NULL_KEY; } } } }
|  |  |  |  |
|
2021-12-18 09:16:56 |
|
 |
Henri Beauchamp
Joined: 2009-03-17 18:42:51 Posts: 5981
|
I just updated the PDF manual with functions for side bar buttons which can toggle fps limiting and optionally the auto-maximization of the draw distance based on the frame render time (i.e. when there is enough time left after rendering a frame when compared to the chosen fps limit, the DD is increased, and on the contrary, it is decreased when the fps actual rate falls below that chosen limit). The latter feature is very handy when sailing or flying, since it maximizes your view distance without ruining your frame rate. I will not update the script above (it is updated in the PDF manual however), but here is what you need to change in that example automation.lua script or your own script:  |  |  |  | Code: -- Side bar buttons assignment SBB_FPSLIMIT = 6 SBB_FPSAUTO = 8
-- Minimum draw distance for automatic FPS-based adjustments MIN_DRAW_DISTANCE = 256
-- Global flag, set when auto-adjusting the draw distance auto_tune_fps = false
function OnLogin(location, moved, autologin) .../... -- Initialize the side bar buttons OnAutomationMessage("fps_unlimit") OnAutomationMessage("fps_no_tuning") .../... end
-- These functions are used to limit the FPS rate and adjust the draw distance
function ToggleFPSLimiting(on) if on then SideBarButton(SBB_FPSLIMIT, "\u{229F}", "AutomationMessage('fps_unlimit')", "Lift the limit on the fps rate.") SideBarButtonToggle(SBB_FPSLIMIT, true) SetDebugSetting("FrameRateLimit", 60) else SideBarButton(SBB_FPSLIMIT, "\u{229F}", "AutomationMessage('fps_limit')", "Limit the fps rate.") SideBarButtonToggle(SBB_FPSLIMIT, false) SetDebugSetting("FrameRateLimit", 0) end end
function OnAveragedFPS(fps, limited, frame_time) if not auto_tune_fps then return end -- When below 60fps, we need to reduce the draw distance when possible if fps < 60 then local dd = GetDebugSetting("RenderFarClip") -- Frame rate is way too low, reset to minimum draw distance if fps < 30 then if dd > MIN_DRAW_DISTANCE then SetDebugSetting("RenderFarClip", MIN_DRAW_DISTANCE) end -- Frame rate below the 60fps target, reduce the draw distance if possible elseif dd >= MIN_DRAW_DISTANCE then dd = dd - 32 if dd < MIN_DRAW_DISTANCE then dd = MIN_DRAW_DISTANCE end SetDebugSetting("RenderFarClip", dd) end -- The actual frame render time is small enough: increase the draw distance if possible elseif frame_time < 8.333 then local dd = GetDebugSetting("RenderFarClip") if dd < 512 then dd = dd + 32 if dd > 512 then dd = 512 end SetDebugSetting("RenderFarClip", dd) end end end
function OnAutomationMessage(text) .../.... -- Used to limit the frame rate to 60 fps elseif text == "fps_limit" then OnAutomationMessage("fps_no_tuning") ToggleFPSLimiting(true) OpenNotification(0, "Frame rate limited to 60 fps") -- Used to lift the limitation on the frame rate elseif text == "fps_unlimit" then ToggleFPSLimiting(false) OpenNotification(0, "Frame rate now unlimited") -- Used to limit the frame rate to 60 fps *and* to auto-adjust the draw distance -- to be the largest possible without falling below 60fps. elseif text == "fps_tuning" then OnAutomationMessage("fps_unlimit"); SideBarButton(SBB_FPSAUTO, "\u{22A1}", "AutomationMessage('fps_no_tuning')", "Do not tune draw distance based on fps rate.") auto_tune_fps = true SideBarButtonToggle(SBB_FPSAUTO, true) SetDebugSetting("FrameRateLimit", 60) OpenNotification(0, "FPS-rate based draw distance tuning turned on") -- Used to stop the auto-tuning of the draw distance elseif text == "fps_no_tuning" then ToggleFPSLimiting(GetDebugSetting("FrameRateLimit")) auto_tune_fps = false SideBarButtonToggle(SBB_FPSAUTO, false) SideBarButton(SBB_FPSAUTO, "\u{22A1}", "AutomationMessage('fps_tuning')", "Tune draw distance based on fps rate.") OpenNotification(0, "FPS-rate based draw distance tuning turned off") end end
|  |  |  |  |
|
2024-01-15 15:05:37 |
|
|
|
Page 1 of 1
|
[ 9 posts ] |
|
Who is online |
Users browsing this forum: No registered users and 1 guest |
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot post attachments in this forum
|
|