From a68267b5cf6573a3cac076c8d610342cfab18850 Mon Sep 17 00:00:00 2001 From: Samantha Wright Date: Fri, 28 Jun 2024 15:44:37 -0700 Subject: [PATCH] Rebuilt repo --- ARES/a | 331 ++++ ARES/api/api.h.lsl | 97 + ARES/api/auth.h.lsl | 145 ++ ARES/api/database.h.lsl | 64 + ARES/api/effector.h.lsl | 76 + ARES/api/file.h.lsl | 249 +++ ARES/api/file.h.lsl.old | 211 +++ ARES/api/fs.h.lsl.old | 69 + ARES/api/hardware.h.lsl | 46 + ARES/api/interface.consts.h.lsl | 253 +++ ARES/api/interface.h.lsl | 88 + ARES/api/io.h.lsl | 130 ++ ARES/api/io.h.lsl.bak | 99 ++ ARES/api/kernel.h.lsl | 51 + ARES/api/repair.h.lsl | 55 + ARES/api/request.h.lsl | 110 ++ ARES/api/scheduler.h.lsl | 64 + ARES/api/sexuality.h.lsl | 45 + ARES/api/status.h.lsl | 70 + ARES/api/storage.h.lsl | 109 ++ ARES/api/tasks.h.lsl | 86 + ARES/api/version-compare.h.lsl | 86 + ARES/application/calc.lsl | 466 +++++ ARES/application/corrado.lsl | 635 +++++++ ARES/application/db.lsl | 487 +++++ ARES/application/define.lsl | 154 ++ ARES/application/filter.lsl | 905 ++++++++++ ARES/application/find.lsl | 513 ++++++ ARES/application/fortune.h.lsl | 36 + ARES/application/fortune.lsl | 122 ++ ARES/application/help.lsl.old | 309 ++++ ARES/application/id.lsl | 700 ++++++++ ARES/application/land.lsl | 249 +++ ARES/application/lore.lsl | 67 + ARES/application/lslisp.lsl | 162 ++ ARES/application/lslisp.lsl.lpo | 896 ++++++++++ ARES/application/mail.lsl | 102 ++ ARES/application/mantra.lsl | 308 ++++ ARES/application/media.event.lsl | 59 + ARES/application/media.lsl | 234 +++ ARES/application/news.lsl | 311 ++++ ARES/application/persona.lsl | 158 ++ ARES/application/saver.event.lsl | 137 ++ ARES/application/saver.lsl | 219 +++ ARES/application/scidb.lsl | 205 +++ ARES/application/tell.lsl | 91 + ARES/application/template.lsl | 63 + ARES/application/type.lsl | 87 + ARES/application/xset.lsl | 223 +++ ARES/command-comparison.txt | 48 + ARES/hardware/controller/aegis-screen.lsl | 374 ++++ ARES/hardware/controller/analyte-screen.lsl | 355 ++++ ARES/hardware/controller/arecibo-screen.lsl | 360 ++++ ARES/hardware/controller/artifact-screen.lsl | 378 ++++ ARES/hardware/controller/ctrl.h.lsl | 14 + ARES/hardware/controller/ctrl.lsl | 1578 +++++++++++++++++ ARES/hardware/controller/dax3-screen.lsl | 286 +++ ARES/hardware/controller/jovian-screen.lsl | 292 +++ .../controller/nightfall-screen-organizer.lsl | 55 + ARES/hardware/controller/revenant-screen.lsl | 439 +++++ .../hardware/controller/supervisor-screen.lsl | 289 +++ ARES/hardware/controller/xsu-screen.lsl | 274 +++ ARES/license.txt | 52 + ARES/program | 286 +++ ARES/reference.txt | 146 ++ ARES/system/display.lsl | 589 ++++++ ARES/system/exec.event.lsl | 42 + ARES/system/exec.lsl | 752 ++++++++ ARES/system/libmenu.lsl | 317 ++++ ARES/system/policy.lsl | 357 ++++ ARES/system/power.lsl | 783 ++++++++ ARES/system/security.lsl | 720 ++++++++ README.md | 61 + UDL/ARES Shell.xml | 64 + UDL/LSL (ARES).xml | 64 + UDL/LSL (Companion).xml | 64 + UDL/NS Manual Format.xml | 64 + glob.lsl | 196 ++ lslisp.h.lsl | 25 + lslisp.lsl | 954 ++++++++++ objects.lsl | 68 + protocols/caps protocol.txt | 468 +++++ protocols/phase protocol.txt | 214 +++ qkeys.lsl | 318 ++++ unix2slt.lsl | 67 + utils.lsl | 370 ++++ variatype.h.lsl | 235 +++ version-strings.txt | 13 + 88 files changed, 22463 insertions(+) create mode 100644 ARES/a create mode 100644 ARES/api/api.h.lsl create mode 100644 ARES/api/auth.h.lsl create mode 100644 ARES/api/database.h.lsl create mode 100644 ARES/api/effector.h.lsl create mode 100644 ARES/api/file.h.lsl create mode 100644 ARES/api/file.h.lsl.old create mode 100644 ARES/api/fs.h.lsl.old create mode 100644 ARES/api/hardware.h.lsl create mode 100644 ARES/api/interface.consts.h.lsl create mode 100644 ARES/api/interface.h.lsl create mode 100644 ARES/api/io.h.lsl create mode 100644 ARES/api/io.h.lsl.bak create mode 100644 ARES/api/kernel.h.lsl create mode 100644 ARES/api/repair.h.lsl create mode 100644 ARES/api/request.h.lsl create mode 100644 ARES/api/scheduler.h.lsl create mode 100644 ARES/api/sexuality.h.lsl create mode 100644 ARES/api/status.h.lsl create mode 100644 ARES/api/storage.h.lsl create mode 100644 ARES/api/tasks.h.lsl create mode 100644 ARES/api/version-compare.h.lsl create mode 100644 ARES/application/calc.lsl create mode 100644 ARES/application/corrado.lsl create mode 100644 ARES/application/db.lsl create mode 100644 ARES/application/define.lsl create mode 100644 ARES/application/filter.lsl create mode 100644 ARES/application/find.lsl create mode 100644 ARES/application/fortune.h.lsl create mode 100644 ARES/application/fortune.lsl create mode 100644 ARES/application/help.lsl.old create mode 100644 ARES/application/id.lsl create mode 100644 ARES/application/land.lsl create mode 100644 ARES/application/lore.lsl create mode 100644 ARES/application/lslisp.lsl create mode 100644 ARES/application/lslisp.lsl.lpo create mode 100644 ARES/application/mail.lsl create mode 100644 ARES/application/mantra.lsl create mode 100644 ARES/application/media.event.lsl create mode 100644 ARES/application/media.lsl create mode 100644 ARES/application/news.lsl create mode 100644 ARES/application/persona.lsl create mode 100644 ARES/application/saver.event.lsl create mode 100644 ARES/application/saver.lsl create mode 100644 ARES/application/scidb.lsl create mode 100644 ARES/application/tell.lsl create mode 100644 ARES/application/template.lsl create mode 100644 ARES/application/type.lsl create mode 100644 ARES/application/xset.lsl create mode 100644 ARES/command-comparison.txt create mode 100644 ARES/hardware/controller/aegis-screen.lsl create mode 100644 ARES/hardware/controller/analyte-screen.lsl create mode 100644 ARES/hardware/controller/arecibo-screen.lsl create mode 100644 ARES/hardware/controller/artifact-screen.lsl create mode 100644 ARES/hardware/controller/ctrl.h.lsl create mode 100644 ARES/hardware/controller/ctrl.lsl create mode 100644 ARES/hardware/controller/dax3-screen.lsl create mode 100644 ARES/hardware/controller/jovian-screen.lsl create mode 100644 ARES/hardware/controller/nightfall-screen-organizer.lsl create mode 100644 ARES/hardware/controller/revenant-screen.lsl create mode 100644 ARES/hardware/controller/supervisor-screen.lsl create mode 100644 ARES/hardware/controller/xsu-screen.lsl create mode 100644 ARES/license.txt create mode 100644 ARES/program create mode 100644 ARES/reference.txt create mode 100644 ARES/system/display.lsl create mode 100644 ARES/system/exec.event.lsl create mode 100644 ARES/system/exec.lsl create mode 100644 ARES/system/libmenu.lsl create mode 100644 ARES/system/policy.lsl create mode 100644 ARES/system/power.lsl create mode 100644 ARES/system/security.lsl create mode 100644 README.md create mode 100644 UDL/ARES Shell.xml create mode 100644 UDL/LSL (ARES).xml create mode 100644 UDL/LSL (Companion).xml create mode 100644 UDL/NS Manual Format.xml create mode 100644 glob.lsl create mode 100644 lslisp.h.lsl create mode 100644 lslisp.lsl create mode 100644 objects.lsl create mode 100644 protocols/caps protocol.txt create mode 100644 protocols/phase protocol.txt create mode 100644 qkeys.lsl create mode 100644 unix2slt.lsl create mode 100644 utils.lsl create mode 100644 variatype.h.lsl create mode 100644 version-strings.txt diff --git a/ARES/a b/ARES/a new file mode 100644 index 0000000..fe5c695 --- /dev/null +++ b/ARES/a @@ -0,0 +1,331 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2023 Nanite Systems Corporation + * + * ========================================================================= + * + * A.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#include + +// some programs (e.g. LSLisp) need this to determine the target platform: +#define ARES + +// version that OS components will report: +#define ARES_VERSION "0.5.0" +#define ARES_VERSION_TAGS "beta 3" + +// copyright year for the OS: +#define ARES_YEAR "2022-2024" + +// ring definitions (link numbers): +#define R_KERNEL 1 +#define R_DAEMON 2 +#define R_PROGRAM 3 +// populated with llGetLinkKey(): +key KERNEL = NULL_KEY; +key DAEMON = NULL_KEY; +key PROGRAM = NULL_KEY; +// populated with llGetOwner(): +key avatar = NULL_KEY; +// populated with llGetScriptName(): +string PROGRAM_NAME = "program"; + +// channel definitions +#define C_UNASSIGNED 0x7a000000 + +// the kernel takes messages via llMessageLinked using the system() API call +// so it doesn't need a channel +// #define C_KERNEL 0x7a001000 + +#define C_DAEMON_BASE 0x7a002000 + +#define C_STATUS 0x7a002001 +#define E_STATUS "!\"" +#define C_BASEBAND 0x7a002002 +#define E_BASEBAND "!#" +#define C_IO 0x7a002003 +#define E_IO "!$" +#define C_HARDWARE 0x7a002004 +#define E_HARDWARE "!%" +#define C_INTERFACE 0x7a002005 +#define E_INTERFACE "!&" +#define C_VARIATYPE 0x7a002006 +#define E_VARIATYPE "!'" +#define C_EFFECTOR 0x7a002007 +#define E_EFFECTOR "!(" +#define C_REPAIR 0x7a002008 +#define E_REPAIR "!)" +#define C_SCHEDULER 0x7a002009 +#define E_SCHEDULER "!*" +#define C_SEXUALITY 0x7a00200a +#define E_SEXUALITY "!+" +#define C_WARRIOR 0x7a00200b +#define E_WARRIOR "!," +#define C_STORAGE 0x7a00200c +#define E_STORAGE "!-" + +#define C_PROGRAM_BASE 0x7a003000 + +#define C_LIBFS 0x7a003ffd +#define C_PROC 0x7a003ffe + +#define FILESYSTEM_ADDRESS 0xffd +#define DELEGATE_ADDRESS 0xffe + +#define INPUT_HANDLER "00000000-0000-0000-0000-000000000001" +#define VOX_LISTENER "00000000-0000-0000-0000-000000000002" + +#define OUTPUT_WHISPER "00000000-0000-0000-0000-000000000010" +#define OUTPUT_SAY "00000000-0000-0000-0000-000000000020" +#define OUTPUT_SHOUT "00000000-0000-0000-0000-000000000100" +#define OUTPUT_NULL NULL_KEY + +integer PROGRAM_NUMBER = 0; +string E_PROGRAM_NUMBER = "!!"; + +// signal number encoding (0-4095, 0x0000-0x0fff) +#define ares_encode(_number) llChar( ((_number & 0x0fc0) >> 6) + 33 ) + llChar( (_number & 0x3f) + 33 ) +#define ares_decode(_code) (((llOrd(_code, 0) - 33) << 6) + (llOrd(_code, 1) - 33)) + +// placeholder for procs with no known name (used in system messages): +#define E_UNKNOWN "!!" + +/* + SIGNAL DEFINITIONS + + Signals are assigned spaces in the following format: + 0x000 - 0x0ff: standard program interactions + 0x100 - 0x1ff: daemon interactions + ... + 0xf00 - 0xfff: kernel interactions + +*/ + +// 0x000-0x0ff: standard program interactions +#define SIGNAL_INVOKE 0x000 +#define E_SIGNAL_INVOKE "!!" +// call from kernel to program: +#define SIGNAL_EVENT 0x002 +#define E_SIGNAL_EVENT "!#" +// call from daemon to program (avoid at all costs): +#define SIGNAL_SUMMON 0x003 +#define E_SIGNAL_SUMMON "!$" +// receipt for successful invoke: +#define SIGNAL_DONE 0x004 +#define E_SIGNAL_DONE "!%" +// bidirectional timer creation and trigger: +#define SIGNAL_TIMER 0x005 +#define E_SIGNAL_TIMER "!&" + +// 0x100-0x1ff: daemon interactions +#define SIGNAL_DATA_REQUEST 0x100 +#define E_SIGNAL_DATA_REQUEST "%!" +#define SIGNAL_DATA_UNAVAILABLE 0x101 +#define E_SIGNAL_DATA_UNAVAILABLE "%\"" +#define SIGNAL_DATA_VALUE 0x102 +#define E_SIGNAL_DATA_VALUE "%#" +#define SIGNAL_DATA_LIST 0x103 +#define E_SIGNAL_DATA_LIST "%$" +#define SIGNAL_DATA_SET 0x104 +#define E_SIGNAL_DATA_SET "%%" +#define SIGNAL_DATA_DELETE 0x105 +#define E_SIGNAL_DATA_DELETE "%&" + +// call from program to daemon: +#define SIGNAL_CALL 0x110 +#define E_SIGNAL_CALL "%1" +#define SIGNAL_CREATE_RULE 0x111 +#define E_SIGNAL_CREATE_RULE "%2" +#define SIGNAL_DELETE_RULE 0x112 +#define E_SIGNAL_DELETE_RULE "%3" +#define SIGNAL_NOTIFY 0x113 +#define E_SIGNAL_NOTIFY "%4" +#define SIGNAL_QUERY_RULES 0x114 +#define E_SIGNAL_QUERY_RULES "%5" + +// messy commands to daemon-bound functions +// that do not fit neatly into the design: +#define SIGNAL_SECURITY 0x120 +#define E_SIGNAL_SECURITY "%A" +#define SIGNAL_VOX 0x121 +#define E_SIGNAL_VOX "%B" +#define SIGNAL_TELL 0x122 +#define E_SIGNAL_TELL "%C" + +// pipe interactions for FLOW (stream-processing) programs: +// (not implemented in this version) +#define SIGNAL_STREAM_OPEN 0x130 +#define E_SIGNAL_STREAM_OPEN "%Q" +#define SIGNAL_STREAM_DATA 0x131 +#define E_SIGNAL_STREAM_DATA "%R" +#define SIGNAL_STREAM_CLOSED 0x132 +#define E_SIGNAL_STREAM_CLOSED "%S" + +// 0xf00-0xfff: kernel interactions +#define SIGNAL_SOLICIT_ADDRESS 0xf00 +#define SIGNAL_ASSIGN_ADDRESS 0xf01 +#define E_SIGNAL_ASSIGN_ADDRESS "]\"" +#define SIGNAL_TERMINATE 0xf02 +#define E_SIGNAL_TERMINATE "]#" +#define SIGNAL_WAKE 0xf03 +#define E_SIGNAL_WAKE "]$" +#define SIGNAL_WOKE 0xf04 +#define E_SIGNAL_WOKE "]%" +#define SIGNAL_OVERVIEW 0xf05 +#define E_SIGNAL_OVERVIEW "]&" +#define SIGNAL_OVERVIEW_REPORT 0xf06 +#define E_SIGNAL_OVERVIEW_REPORT "]'" +#define SIGNAL_HOOK_EVENT 0xf07 +#define E_SIGNAL_HOOK_EVENT "](" +#define SIGNAL_UNHOOK_EVENT 0xf08 +#define E_SIGNAL_UNHOOK_EVENT "])" +#define SIGNAL_TERMINATED 0xf09 +#define E_SIGNAL_TERMINATED "]*" +#define SIGNAL_UNKNOWN_SCRIPT 0xf0a +#define E_SIGNAL_UNKNOWN_SCRIPT "]+" + +// these require callback numbers: +#define SIGNAL_QUERY_MODULES 0xf0b +#define E_SIGNAL_QUERY_MODULES "]," +#define SIGNAL_QUERY_HOOKS 0xf0c +#define E_SIGNAL_QUERY_HOOKS "]-" +#define SIGNAL_QUERY_DAEMONS 0xf0d +#define E_SIGNAL_QUERY_DAEMONS "]." + +// parse the provided string as an input pipe: +#define SIGNAL_MODULES_REPORT 0xf0e +#define E_SIGNAL_MODULES_REPORT "]/" +#define SIGNAL_HOOKS_REPORT 0xf0f +#define E_SIGNAL_HOOKS_REPORT "]0" +#define SIGNAL_DAEMONS_REPORT 0xf10 +#define E_SIGNAL_DAEMONS_REPORT "]1" + +// from delegate or daemon to kernel: +#define SIGNAL_TRIGGER_EVENT 0xf11 +#define E_SIGNAL_TRIGGER_EVENT "]2" + +#define SIGNAL_DAEMON_ANNOUNCE 0xf12 + +#define SIGNAL_MODE 0xf13 +#define E_SIGNAL_MODE "]4" + +// program ready to be sent commands: +#define SIGNAL_READY 0xf14 +#define E_SIGNAL_READY "]5" + +// program was awoken but already awake: +#define SIGNAL_BUSY 0xf15 +#define E_SIGNAL_BUSY "]6" + +// stop program without resetting it: +#define SIGNAL_SLEEP 0xf16 +#define E_SIGNAL_SLEEP "]7" + +#define SIGNAL_RECHECK_MODULE 0xf17 +#define E_SIGNAL_RECHECK_MODULE "]8" + +// special message for combat performance: +#define SIGNAL_COLLISION 0xf18 +#define E_SIGNAL_COLLISION "]9" + +#define SIGNAL_INVENTORY_DROP 0xf19 +#define E_SIGNAL_INVENTORY_DROP "]:" + +// daemons can now call programs safely: +#define SIGNAL_SYSTEM_READY 0xffc +#define E_SIGNAL_SYSTEM_READY "`]" + +#define SIGNAL_INIT 0xffd +#define E_SIGNAL_INIT "`^" + +// this requires a callback number: +#define SIGNAL_DAEMON_RESET 0xffe +#define E_SIGNAL_DAEMON_RESET "`_" + +#define SIGNAL_KERNEL_RESET 0xfff +#define E_SIGNAL_KERNEL_RESET "``" + +/* + EVENT DEFINITIONS + + Most events have no parameters. + + Exceptions are noted. +*/ + +// Touch events are for the ARES HUD only. They are sent in the format: 1 +#define EVENT_TOUCH 0x001 + +// Teleport events ARE NOT sent when the system teleports to a new region. +#define EVENT_TELEPORT 0x002 + +// Generated by _fs after a refresh, whether or not any files actually changed +#define EVENT_INVENTORY_CHANGE 0x003 + +// 0x004 was EVENT_TIMER, which has been replaced by SIGNAL_TIMER for brevity + +// Device events specify the name and key of the device, e.g. 5 battery +// However, they may be sent with no parameters during a device probe. +#define EVENT_NEW_DEVICE 0x005 +#define EVENT_REMOVE_DEVICE 0x006 + +// Region change events are sent when the system crosses a region boundary OR teleports to a new region. +#define EVENT_REGION_CHANGE 0x007 + +// The on_rez event is generated by _proc when it resets and when the system is rezzed. +#define EVENT_ON_REZ 0x008 + +// The interface refresh event triggers whenever the whole UI needs to be repositioned, such as when the HUD subsystem is enabled, the unit is powered on, the _interface daemon is reset, or mouselook is entered or left. +#define EVENT_INTERFACE 0x009 + +// The rez_object event is generated by _proc when an object is rezzed. The object's key is passed as a parameter. +#define EVENT_REZ_OBJECT 0x00a + +// The warning event triggers whenever a warning is issued by a daemon. This is meant only to be monitored by the _display program. Ideally this would be a notify message, but using an event hook fails gracefully if a warning is generated before _display is registered with the kernel. Specified as 11 , e.g. "11 2 6" displays 'RADIATION DANGER' in slot 2; see interface.consts.h.lsl for list. +#define EVENT_WARNING 0x00b + +/* + MODE DEFINITIONS + + see ARES/program for details +*/ + +#define MODE_NORMAL 0x000 +#define MODE_THREADING 0x001 +#define MODE_NON_VOLATILE 0x002 +#define MODE_MASK_SLEEPLESS 0x003 +#define MODE_ACCEPT_DONE 0x004 +#define MODE_MASK_BATCH 0x006 +#define MODE_FLOW 0x008 + +#include diff --git a/ARES/api/api.h.lsl b/ARES/api/api.h.lsl new file mode 100644 index 0000000..3b6b2fa --- /dev/null +++ b/ARES/api/api.h.lsl @@ -0,0 +1,97 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * API.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +/* + FOUNDATIONAL SYSTEM CALLS + + IMPORTANT: When required to provide a UUID to the ARES API by these or any other functions, never pass an empty string. Instead use NULL_KEY. Some parts of the OS assume fixed-length message prefixes and will break if fed the wrong thing. +*/ + +#ifndef _ARES_API_H_ +#define _ARES_API_H_ + +// send a command to the kernel: +#define kernel(_message_number, _message) linked(R_KERNEL, _message_number, E_PROGRAM_NUMBER + _message, "") + +// send a message to the system (via the kernel): +#define system(_message_number, _message) linked(R_KERNEL, _message_number, _message, "") + +// daemon call (message below 0xf00): +#define call(_daemon_channel, _message_number, _message) tell(DAEMON, _daemon_channel, ares_encode(_message_number) + E_PROGRAM_NUMBER + _message) + +// pre-encoded daemon call (message below 0xf00): +#define e_call(_daemon_channel, _e_message_number, _message) tell(DAEMON, _daemon_channel, _e_message_number + E_PROGRAM_NUMBER + _message) + +// run a program via command line (as though called by a user): +#define invoke(_command, _output_pipe, _input_pipe, _user) system(SIGNAL_INVOKE, E_UNKNOWN + E_PROGRAM_NUMBER + (string)(_output_pipe) + " " + (string)(_input_pipe) + " " + (string)(_user) + " " + _command) + +// request a program be terminated: +#define terminate(_program) system(SIGNAL_TERMINATE, E_UNKNOWN + E_PROGRAM_NUMBER + _program) + +// send a notification to a program (used for file i/o and sharing status changes): +#define notify_program(_command, _output_pipe, _input_pipe, _user) system(SIGNAL_NOTIFY, E_UNKNOWN + E_PROGRAM_NUMBER + (string)(_output_pipe) + " " + (string)(_input_pipe) + " " + (string)(_user) + " " + _command) + +// send a notification to a daemon (see C_* constants in ARES/a for channels): +#define notify_daemon(_daemon_channel, _command, _output_pipe, _input_pipe, _user) tell(DAEMON, _daemon_channel, E_SIGNAL_NOTIFY + E_PROGRAM_NUMBER + (string)(_output_pipe) + " " + (string)(_input_pipe) + " " + (string)(_user) + " " + _command) + +// send a message from one daemon to another (message number is unencoded) +#define daemon_to_daemon(_e_daemon, _message_number, _message) system(_message_number, _e_daemon + E_PROGRAM_NUMBER + _message) + +/* Inclusions of ARES API components */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + The following modules provide function implementations rather than convenient ways to make existing system calls, and are therefore not automatically included: + + auth.h.lsl + file.h.lsl + interface.consts.h.lsl + version-compare.h.lsl +*/ + +#endif // _ARES_API_H_ diff --git a/ARES/api/auth.h.lsl b/ARES/api/auth.h.lsl new file mode 100644 index 0000000..99e003f --- /dev/null +++ b/ARES/api/auth.h.lsl @@ -0,0 +1,145 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2023 Nanite Systems Corporation + * + * ========================================================================= + * + * AUTH.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_AUTH_H_ +#define _ARES_AUTH_H_ + +#include + +#define ALLOWED TRUE +#define DENIED FALSE +#define PENDING NOWHERE + +#define SEC_NEVER 0 +#define SEC_ALWAYS 1 +#define SEC_CONSENT 2 +#define SEC_USER 3 +#define SEC_MANAGER 4 +#define SEC_OWNER 5 +#define SEC_SELF 6 + +/* + sec_check() return values: + ALLOWED (1) on access granted, + DENIED (0) on access denied, or + PENDING (-1) if a consent prompt was generated + + if sec_check() returns PENDING, the program should abruptly halt + + one of deny_cmd or retry_cmd is executed after the unit denies or grants consent, preserving the original user and outs + + if you implement a structure like... + + if (check == ALLOWED) { // do the task here ... } else if (check == DENIED) { // print error message here ... } + + ...in your program, then you can pass the original command as both deny_cmd and retry_cmd, e.g. sec_check(user, "manage", outs, m, m) + + almost all of the permissions are customizable; the default ones are: + menu: access the menus + local: send commands in local chat + remote: send commands using a remote access device + yank: force the unit to teleport + arouse: trigger TESI events + chat: send chat as the unit + manage: adjust unit settings + identity: adjust unit's name etc. + add-user: add a registered user at rank 3 + add-manager: promote a registered user to rank 4 + add-owner: promote a registered user to rank 5 + demote-self: lower own user rank + demote-manager: lower rank of a manager (rank 4) + demote-owner: lower rank of an owner (rank 5) + run-away: clear all users + safeword: restore the unit's autonomy + database: alter database entries directly + (the db program requires rank 5 to affect the 'security' section or run a load) + + owner: this is a special permission that just checks the owner rank (used for configuring security settings) + + the standard interpretations of the security levels are: + 0: no one may do this + 1: everyone may do this except for the banned + 2: consent must be given to do this + 3: authorized users of rank 3 or higher (all) may do this + 4: authorized users of rank 4 or higher (managers) may do this + 5: authorized users of rank 5 or higher (owners) may do this + 6: only the unit may do this +*/ + +// #define SEC_DEBUG + +integer sec_check(key user, string permission, key outs, string deny_cmd, string retry_cmd) { + string sec_section = llLinksetDataRead("security"); + integer req = (integer)getjs(sec_section, ["rule", permission]); + #ifdef SEC_DEBUG + echo("[sec_check]\nrequired access: " + (string)req + + "\nuser rank: " + getjs(sec_section, ["user", user]) + + "\nuser guest? " + getjs(sec_section, ["guest", user]) + + "\nuser ban? " + getjs(sec_section, ["ban", user]) + ); + #endif + if(req == SEC_SELF && user == avatar) + return ALLOWED; + + string ban = getjs(sec_section, ["ban", user]); + string guest = getjs(sec_section, ["guest", user]); + integer rank = (integer)getjs(sec_section, ["user", user]); + if(permission == "owner") + return (rank == SEC_OWNER); + + if(ban != JSON_INVALID && ((integer)ban == 1 || (integer)ban > llGetUnixTime())) + return DENIED; + else if(rank >= req && req > SEC_NEVER) + return ALLOWED; + else if(req == SEC_CONSENT) { + if(guest != JSON_INVALID && ((integer)guest == 1 || (integer)guest > llGetUnixTime())) + return ALLOWED; + else { + notify_program("_security consent " + + jsarray([retry_cmd, deny_cmd]), + outs, + NULL_KEY, + user + ); + return PENDING; + } + } else if(req == SEC_ALWAYS) + return ALLOWED; + else + return DENIED; +} + +#endif // _ARES_AUTH_H_ diff --git a/ARES/api/database.h.lsl b/ARES/api/database.h.lsl new file mode 100644 index 0000000..a32cd3e --- /dev/null +++ b/ARES/api/database.h.lsl @@ -0,0 +1,64 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * DATABASE.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +/* + LSD DATABASE I/O + + Read, write, and delete database entries. + + There are two sets of macros for interacting with the Linkset datastore. The first set has names ending in 'dbl', and the second set has names ending in 'db'. + + The 'dbl' macros assume the key name is already split into a list (the same as LSL's JSON functions) and are therefore more efficient. + + The 'db' macros will do this for you; you should only need these for handling user input. + + Occasionally when working with many entries in one section, you may still find it memory-efficient to use the llLinksetData* functions with getjs(). There are no macros for this because you should only be doing it once or twice per program. +*/ + +#ifndef _ARES_DATABASE_H_ +#define _ARES_DATABASE_H_ + +// use as setdbl(section, ["list", "of", "subkey", "names"], value) +#define setdbl(_section, ...) llLinksetDataWrite(_section, setjs(llLinksetDataRead(_section), __VA_ARGS__)) +#define getdbl(_section, ...) getjs(llLinksetDataRead(_section), __VA_ARGS__) +#define deletedbl(_section, ...) llLinksetDataWrite(_section, setjs(llLinksetDataRead(_section), __VA_ARGS__, JSON_DELETE)) + +// use as setdbl(section, "string.of.subkey.names", value) +#define setdb(_section, _entry, _value) llLinksetDataWrite(_section, setjs(llLinksetDataRead(_section), splitnulls(_entry, "."), _value)) +#define getdb(_section, _entry) getjs(llLinksetDataRead(_section), splitnulls(_entry, ".")) +#define deletedb(_section, _entry) llLinksetDataWrite(_section, setjs(llLinksetDataRead(_section), splitnulls(_entry, "."), JSON_DELETE)) + +#endif // _ARES_DATABASE_H_ diff --git a/ARES/api/effector.h.lsl b/ARES/api/effector.h.lsl new file mode 100644 index 0000000..b145205 --- /dev/null +++ b/ARES/api/effector.h.lsl @@ -0,0 +1,76 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * EFFECTOR.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +// **** effector daemon **** + +#ifndef _ARES_EFFECTOR_H_ +#define _ARES_EFFECTOR_H_ + +/* + * ARES RLV + * + * The main role of the effector daemon is in managing RLV restrictions and preventing them from conflicting, similar to an RLV relay. However, there are some slight differences from regular RLV: + * + * - The '@' is stripped off. + * - To format _rule_values for restrictions (those rules ending in =n/y/add/rem), put '?' instead. This will be replaced with 'y' and 'n' as appropriate by the effector daemon. + * - If a rule's name starts with 'a:' then its contents will be treated as an animation from ring 2 (the daemon ring) + * - The new rule 'move=?' causes the system to intercept movement keys while active. + * - The rule 'recvchat=?' will also trigger 'recvemote=?' and cause the _input program to attempt to filter quoted text inside emotes. + * + * As of Alpha 1, only a few RLV rules can be applied multiple times. These are 'move=?', 'sendchat=?', 'recvemote=?', and 'recvchat=?'. Attempting to remove any other rule will release the restriction entirely, breaking other sources of the same restriction. This will be improved when the RLV relay program 'restraint' is finished in Alpha 3. + * + */ + +// effector_restrict(rule-name, rule-value): applies an RLV restriction (e.g. 'recvchat=?') with the specified rule name (see details at start of effector.h.lsl) +#define effector_restrict(_rule_name, _rule_value) e_call(C_EFFECTOR, E_SIGNAL_CREATE_RULE, _rule_name + " " + _rule_value) +// effector_release(rule-name): releases an RLV restriction (see details at start of effector.h.lsl); if the rule name ends in "*" then all rules matching the prefix will be removed, e.g. "power_*" to remove all rules starting with "power_" +#define effector_release(_rule_name) e_call(C_EFFECTOR, E_SIGNAL_DELETE_RULE, _rule_name) +// this just sticks "@" on the front: +#define effector_rlv(_rule) e_call(C_EFFECTOR, E_SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " effector rlv " + _rule) +// teleports to region and (vector) position; if _external is TRUE, doesn't consume power or play fx +#define effector_teleport(_region, _position, _external) e_call(C_EFFECTOR, E_SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " effector teleport " + (string)(_external) + "|" + (string)avatar + "|" + (_region) + "|" + (string)(_position)) + +// tell the controller hardware to make noises: + +// play_sound(): play a sound specified by UUID +// announce(): play an announcer voice sample, as defined in the LSD section 'announcer' + +#define play_sound(_name) e_call(C_EFFECTOR, E_SIGNAL_CALL, (string)avatar + " " + (string)avatar + " effector sound " + _name) +#define daemon_play_sound(_name) daemon_to_daemon(E_EFFECTOR, SIGNAL_CALL, (string)avatar + " " + (string)avatar + " effector sound " + _name) +#define announce(_announcement) e_call(C_EFFECTOR, E_SIGNAL_CALL, (string)avatar + " " + (string)avatar + " effector announce " + _announcement) +#define daemon_announce(_announcement) daemon_to_daemon(E_EFFECTOR, SIGNAL_CALL, (string)avatar + " " + (string)avatar + " effector announce " + _announcement) + +#endif // _ARES_EFFECTOR_H_ diff --git a/ARES/api/file.h.lsl b/ARES/api/file.h.lsl new file mode 100644 index 0000000..7078ed1 --- /dev/null +++ b/ARES/api/file.h.lsl @@ -0,0 +1,249 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2023 Nanite Systems Corporation + * + * ========================================================================= + * + * FILE.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_FILE_H_ +#define _ARES_FILE_H_ + +#include + +#define FILE_R 0 +#define FILE_INS 1 +#define FILE_OUTS 2 +#define FILE_USER 3 +#define FILE_NAME 4 +#define FILE_OFFSET 5 +#define FILE_LENGTH 6 +#define FILE_UNIT 7 + +/* + NEW UNIFORM FILE ACCESS SYSTEM - ARES 0.4.4 + + replaces ARES/api/file.h.lsl +*/ + +/* + To use this script, call fopen() with the 'outs', 'ins', and 'user' from main() + + When file contents are available, the program will receive: + + SIGNAL_NOTIFY + message: PROGRAM_NAME + " file" + ins: matching the file_handle returned by fopen() + + You can then call fread(file_handle) to get contents. + + fread() has the following special return values: + JSON_FALSE: file does not exist or file is empty + JSON_TRUE: file was opened successfully and data will arrive soon + + If fread() returns any other value, it is file data. + + fread() reads files in buffer chunks of around 1024 bytes, or one line for notecards, and always advances to the next chunk afterward. + + fread() will close the file automatically when it is done reading, at which point getjs(tasks_queue, [file_handle]) will return JSON_INVALID. + + By default, this API will automatically try to resolve tasks for you when the file is closed. This is good for simple file readers but wrong if you intend to do more processing afterward. To suppress this behavior, set '_resolved = 0' before calling fopen(), and pass NULL_KEY for ins. +*/ + +#ifndef FILE_STEP_SIZE + // define this as a small integer for speed-ups (don't go too far, though) + #define FILE_STEP_SIZE 1 +#endif + +// fopen(output_stream, input_stream, user_key, file_name): returns file_handle +key fopen(key outs, key ins, key user, string filename) { + key q = llGenerateKey(); + // send request to io daemon to create pipe and fetch file length: + file_open(q, filename); + + // track file information and prevent job from sleeping during load process + string metadata = list2js(JSON_ARRAY, [ + _resolved, // callback number for program that summoned us + ins, // original input stream (often used as a callback handle) + outs, // output stream + user, // user that initiated the request + filename, // filename being worked on + NOWHERE, // file offset (haven't started reading yet) + NOWHERE, // file length (loaded first) + "" // file unit (loaded with file length) + ]); + task_begin(q, metadata); + // metadata is stored in the global JSON variable tasks_queue, and can be accessed later + + _resolved = 0; + return q; +} + +// fread(file_handle): returns JSON_TRUE if file opened successfully, JSON_FALSE if file empty/missing, otherwise file text +// note that it may return up to 1025 bytes for a perfectly full notecard line + linebreak, but will otherwise always return 1024 bytes or fewer +string fread(key q) { + // split apart file metadata into a list (smaller code) + list file = js2list(getjs(tasks_queue, [q])); + integer file_length = geti(file, FILE_LENGTH); + string file_unit = gets(file, FILE_UNIT); + string fn = gets(file, FILE_NAME); + + // copy contents of LSD pipe into local variable: + string buffer; + pipe_read(q, buffer); + + integer offset = geti(file, FILE_OFFSET); + _resolved = 0; + + if(!~file_length) { + // on the first successful call we load the file length and unit + list stat_parts = split(buffer, " "); + file_length = (integer)gets(stat_parts, 0); + file_unit = gets(stat_parts, 1); + tasks_queue = setjs(setjs(tasks_queue, + [q, FILE_LENGTH], (string)file_length), + [q, FILE_UNIT], (string)file_unit); + + // for notecards, file length is measured as the number of lines, + // NOT the actual character count + + if(file_length > 0) { + // a length greater than 0 indicates the file exists + // start reading file + offset = 0; + + integer read_length = FILE_STEP_SIZE; + if(file_unit == "b") + read_length *= FILE_PAGE_LENGTH; + + if(read_length > file_length) + read_length = file_length; + + file_read(q, fn, (string)offset + " " + (string)read_length); + + tasks_queue = setjs(tasks_queue, [q, FILE_OFFSET], (string)offset); + buffer = JSON_TRUE; + } else { + // no file was found, or file was empty + file_close(q); + + // job can now end: + // resolve_io(geti(file, FILE_R), getk(file, FILE_OUTS), getk(file, FILE_INS)); + resolve_i(geti(file, FILE_R), getk(file, FILE_INS)); + task_end(q); + buffer = JSON_FALSE; + } + } else { + // got length earlier, so this must be file data + + // move forward to next page: + if(file_unit == "b") + offset += (FILE_PAGE_LENGTH * FILE_STEP_SIZE); + else + offset += (FILE_STEP_SIZE); + + // is there more file? + if(offset < file_length) { + // record new offset: + tasks_queue = setjs(tasks_queue, [q, FILE_OFFSET], (string)offset); + // get next section: + #ifdef DEBUG + echo("file.h.lsl: loading offset " + (string)offset + "/" + (string)file_length); + #endif + + integer read_length = FILE_STEP_SIZE; + if(file_unit == "b") + read_length *= FILE_PAGE_LENGTH; + + if(read_length + offset > file_length) + read_length = file_length - offset; + + file_read(q, fn, (string)offset + " " + (string)read_length); + } else { + // reached end of file + file_close(q); + + // job can now end: + // resolve_io(geti(file, FILE_R), getk(file, FILE_OUTS), getk(file, FILE_INS)); + resolve_i(geti(file, FILE_R), getk(file, FILE_INS)); + // delete file handle: + task_end(q); + } + } + + // send data to the program + return buffer; +} + +// preview what fread() will return without actually initiating the next file request +string fpeek(key q) { + // split apart file metadata into a list (smaller code) + list file = js2list(getjs(tasks_queue, [q])); + integer file_length = geti(file, FILE_LENGTH); + + // copy contents of LSD pipe into local variable: + string buffer = llLinksetDataRead("p:" + (string)q); + + if(!~file_length) { + // on the first successful call we load the file length + file_length = (integer)buffer; + // tasks_queue = setjs(tasks_queue, [q, FILE_LENGTH], (string)file_length); + + // for notecards, file length is measured as 256 * (number of lines), + // NOT the actual character count + + if(file_length > 0) { + // a length greater than 0 indicates the file exists + buffer = JSON_TRUE; + } else { + // no file was found, or file was empty + buffer = JSON_FALSE; + } + } + + // send data to the program + return buffer; +} + +// abort file reading (only needed when using fpeek()): +fclose(key q) { + // split apart file metadata into a list (smaller code) + list file = js2list(getjs(tasks_queue, [q])); + + file_close(q); + + // job can now end: + // resolve_io(geti(file, FILE_R), getk(file, FILE_OUTS), getk(file, FILE_INS)); + resolve_i(geti(file, FILE_R), getk(file, FILE_INS)); + // delete file handle: + task_end(q); +} + +#endif // _ARES_FILE_H_ diff --git a/ARES/api/file.h.lsl.old b/ARES/api/file.h.lsl.old new file mode 100644 index 0000000..b315c13 --- /dev/null +++ b/ARES/api/file.h.lsl.old @@ -0,0 +1,211 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2023 Nanite Systems Corporation + * + * ========================================================================= + * + * FILE.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_FILE_H_ +#define _ARES_FILE_H_ + +#include + +#define FILE_R 0 +#define FILE_INS 1 +#define FILE_OUTS 2 +#define FILE_USER 3 +#define FILE_NAME 4 +#define FILE_OFFSET 5 +#define FILE_LENGTH 6 + +/* + To use this script, call fopen() with the 'outs', 'ins', and 'user' from main() + + When file contents are available, the program will receive: + + SIGNAL_NOTIFY + message: PROGRAM_NAME + " file" + ins: matching the file_handle returned by fopen() + + You can then call fread(file_handle) to get contents. + + fread() has the following special return values: + JSON_FALSE: file does not exist or file is empty + JSON_TRUE: file was opened successfully and data will arrive soon + + If fread() returns any other value, it is file data. + + fread() reads files in buffer chunks of FILE_PAGE_LENGTH and always advances to the next chunk. + + FILE_PAGE_LENGTH is 1024 in ARES alpha 1. + + For notecards, which are not read in bytes, this is actually translated into 4 lines. + + fread() will close the file automatically when it is done reading, at which point getjs(tasks_queue, [file_handle]) will return JSON_INVALID. + + By default, this API will automatically try to resolve tasks for you when the file is closed. This is good for simple file readers but wrong if you intend to do more processing afterward. To suppress this behavior, set '_resolved = 0' before calling fopen(), and pass NULL_KEY for ins. +*/ + +// fopen(output_stream, input_stream, user_key, file_name): returns file_handle +key fopen(key outs, key ins, key user, string filename) { + key q = llGenerateKey(); + // send request to io daemon to create pipe and fetch file length: + file_open(q, filename); + + // track file information and prevent job from sleeping during load process + string metadata = list2js(JSON_ARRAY, [ + _resolved, // callback number for program that summoned us + ins, // original input stream (often used as a callback handle) + outs, // output stream + user, // user that initiated the request + filename, // filename being worked on + NOWHERE, // file offset (haven't started reading yet) + NOWHERE // file length (loaded first) + ]); + task_begin(q, metadata); + // metadata is stored in the global JSON variable tasks_queue, and can be accessed later + + _resolved = 0; + return q; +} + +// fread(file_handle): returns JSON_TRUE if file opened successfully, JSON_FALSE if file empty/missing, otherwise file text +string fread(key q) { + // split apart file metadata into a list (smaller code) + list file = js2list(getjs(tasks_queue, [q])); + integer file_length = geti(file, FILE_LENGTH); + string fn = gets(file, FILE_NAME); + + // copy contents of LSD pipe into local variable: + string buffer; + pipe_read(q, buffer); + + integer offset = geti(file, FILE_OFFSET); + _resolved = 0; + + if(!~file_length) { + // on the first successful call we load the file length + file_length = (integer)buffer; + tasks_queue = setjs(tasks_queue, [q, FILE_LENGTH], (string)file_length); + + // for notecards, file length is measured as 256 * (number of lines), + // NOT the actual character count + + if(file_length > 0) { + // a length greater than 0 indicates the file exists + // start reading file + file_read(q, fn, offset = 0); + + tasks_queue = setjs(tasks_queue, [q, FILE_OFFSET], (string)offset); + buffer = JSON_TRUE; + } else { + // no file was found, or file was empty + file_close(q); + + // job can now end: + // resolve_io(geti(file, FILE_R), getk(file, FILE_OUTS), getk(file, FILE_INS)); + resolve_i(geti(file, FILE_R), getk(file, FILE_INS)); + task_end(q); + buffer = JSON_FALSE; + } + } else { + // got length earlier, so this must be file data + + // move forward to next page: + offset += FILE_PAGE_LENGTH; + + // is there more file? + if(offset < file_length) { + // record new offset: + tasks_queue = setjs(tasks_queue, [q, FILE_OFFSET], (string)offset); + // get next section: + file_read(q, fn, offset); + } else { + // reached end of file + file_close(q); + + // job can now end: + // resolve_io(geti(file, FILE_R), getk(file, FILE_OUTS), getk(file, FILE_INS)); + resolve_i(geti(file, FILE_R), getk(file, FILE_INS)); + // delete file handle: + task_end(q); + } + } + + // send data to the program + return buffer; +} + +// preview what fread() will return without actually initiating the next file request +string fpeek(key q) { + // split apart file metadata into a list (smaller code) + list file = js2list(getjs(tasks_queue, [q])); + integer file_length = geti(file, FILE_LENGTH); + + // copy contents of LSD pipe into local variable: + string buffer = llLinksetDataRead("p:" + (string)q); + + if(!~file_length) { + // on the first successful call we load the file length + file_length = (integer)buffer; + // tasks_queue = setjs(tasks_queue, [q, FILE_LENGTH], (string)file_length); + + // for notecards, file length is measured as 256 * (number of lines), + // NOT the actual character count + + if(file_length > 0) { + // a length greater than 0 indicates the file exists + buffer = JSON_TRUE; + } else { + // no file was found, or file was empty + buffer = JSON_FALSE; + } + } + + // send data to the program + return buffer; +} + +// abort file reading (only needed when using fpeek()): +fclose(key q) { + // split apart file metadata into a list (smaller code) + list file = js2list(getjs(tasks_queue, [q])); + + file_close(q); + + // job can now end: + // resolve_io(geti(file, FILE_R), getk(file, FILE_OUTS), getk(file, FILE_INS)); + resolve_i(geti(file, FILE_R), getk(file, FILE_INS)); + // delete file handle: + task_end(q); +} + +#endif // _ARES_FILE_H_ diff --git a/ARES/api/fs.h.lsl.old b/ARES/api/fs.h.lsl.old new file mode 100644 index 0000000..b4ff2a7 --- /dev/null +++ b/ARES/api/fs.h.lsl.old @@ -0,0 +1,69 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * FS.H.LSL Header Component (DEPRECATED) + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_FS_H_ +#define _ARES_FS_H_ + +/* + UNIFORM FILE ACCESS - OBSOLETE + + DO NOT USE - DEFUNCT IN ARES 0.4.4 + + file handling via io (with fs as back end) + + see also file.h.lsl for a simple (but memory-intensive) wrapper to these calls +*/ + +#define FILE_PAGE_LENGTH 2048 +#define FILE_SIZE NOWHERE +#define FILE_LINE_WIDTH 1024 + +#define file_read(_pipe, _filename, _offset) e_call(C_IO, E_SIGNAL_DATA_REQUEST, (string)(_pipe) + " " + PROGRAM_NAME + " " + (_filename) + " " + (string)(_offset)) +#define file_open(_pipe, _filename) file_read(_pipe, _filename, NOWHERE); + +#define file_close(_pipe) e_call(C_IO, E_SIGNAL_DELETE_RULE, "[\"" + (string)(_pipe) + "\"]") + +// OTHER FILESYSTEM ACTIVITIES +// get all filenames in a view: +#define list_files(_rule) js2list(llLinksetDataRead("fs:"+_rule)) + +// get all filenames (careful; this is a lot of data): +#define list_all_files() jskeys(llLinksetDataRead("fs:root")) + +// trigger a filesystem refresh (no way of checking when this finishes): +#define fs_refresh() invoke("fs refresh", avatar, NULL_KEY, avatar) + +#endif // _ARES_FS_H_ diff --git a/ARES/api/hardware.h.lsl b/ARES/api/hardware.h.lsl new file mode 100644 index 0000000..3dbc10b --- /dev/null +++ b/ARES/api/hardware.h.lsl @@ -0,0 +1,46 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * HARDWARE.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_HARDWARE_H_ +#define _ARES_HARDWARE_H_ + +// **** hardware daemon **** + +#define device_list() e_call(C_HARDWARE, E_SIGNAL_DATA_LIST, "") +#define device_command(_device_address, _command, _outs, _user) e_call(C_HARDWARE, E_SIGNAL_CALL, (string)(_outs) + " " + (string)(_user) + " hardware " + (_device_address) + " " + (_command)) +#define device_probe() e_call(C_HARDWARE, E_SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " hardware probe") + +#endif diff --git a/ARES/api/interface.consts.h.lsl b/ARES/api/interface.consts.h.lsl new file mode 100644 index 0000000..86a26eb --- /dev/null +++ b/ARES/api/interface.consts.h.lsl @@ -0,0 +1,253 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Interface Constants + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 1 (ASCL-i). It is offered to you on a limited basis to + * facilitate modification and customization. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +// This file is *not* included by default. + +#ifndef _ARES_INTERFACE_CONSTS_H_ +#define _ARES_INTERFACE_CONSTS_H_ + +// 2: unused (system daemons) + +#define MODEL_BADGE 3 + +// 13-14, 4, and 249-250: mouselook combat stuff +#define CROSSHAIR 4 + +// 5-10: gauges +#define POWER_GAUGE 5 +#define RATE_GAUGE 6 +#define HEAT_GAUGE 7 +#define FAN_GAUGE 8 +#define INTEGRITY_GAUGE 9 +#define LUBE_GAUGE 10 + +#define GAUGE_SIZE <256, 32, 0> + +#define BADGE_CLADDING_LEFT 11 +#define BADGE_CLADDING_RIGHT 12 + +// 13-14, 86, and 249-250: mouselook combat stuff +#define INTEGRITY_LABEL 13 +#define AMMO_LABEL 14 + +// 15-22: unused (formerly fixed warnings) + +// 23-28: unused +// 23 used for experimental MOAP widget + +#define WORKING 28 + +#define BOOT_PROGRESS 30 +#define BOOT_LOGO 31 + +// 32-63: device icons +#define DEVICE_ICON_BASE 32 + +#define ALERT_OPTIONS 64 +#define ALERT_ANCHOR 67 + +#define COMPASS_PRIM_DIAL 65 +#define COMPASS_PRIM_FRAME 66 + +#define SPEEDOMETER 68 +#define SPEEDOMETER_TEXT 69 +#define ALTIMETER_BAR 70 +#define ALTIMETER_MARKER 71 + +#define WARRIOR_START 72 +// 72-78: Warrior paper doll +#define WEAPON_SELECT_START 79 +// 79-85: Warrior weapon select +#define CLADDING_BACKDROP 86 +// 87-91: sexuality +#define LUST_CLADDING 87 +#define LUST_GAUGE 88 +#define SENSITIVITY_GAUGE 89 +#define PLATEAU_MARKER 90 +#define ORGASM_MARKER 91 + +// 92-95: sexuality reserved + +// 96-111: sitrep +#define SITREP_BASE 96 + +#define CPU_BAR 96 +#define CPU_LABEL 97 + +#define FTL_RECHARGE_BAR 98 +#define FTL_RECHARGE_LABEL 99 + +#define SIM_LAG_BAR 100 +#define SIM_LAG_LABEL 101 + +#define SIM_POP_BAR 102 +#define SIM_POP_LABEL 103 + +#define AUX_POWER_BAR 104 +#define AUX_POWER_LABEL 105 + +#define HUMIDITY_BAR 106 +#define HUMIDITY_LABEL 107 + +#define PRESSURE_BAR 108 +#define PRESSURE_LABEL 109 + +#define RADIATION_BAR 110 +#define RADIATION_LABEL 111 + +// 112-126: apps & stuff + +// 112-120: unused + +#define LL_TARGET_LOCK 121 +#define LL_NAV_DEST 122 + +// 123-126: reserved for modal apps +#define WIZARD 123 +#define WIZARD_TEXT 124 +#define WIZARD_TEXT_2 125 +#define WIZARD_TEXT_3 126 + +#define APP_0 123 +#define APP_1 124 +#define APP_2 125 +#define APP_3 126 + +#define SCREEN_ANCHOR 127 +#define SCREEN 128 +#define TEXT_START 129 + +#define MSG_TEXT_START 195 +#define MSG_TEXT_PRIM_LIMIT 12 +// 195-206: alert message text + +#define FIXED_WARNING_LIST 207 + +/* 207: fixed warnings + +FW assignments + +activate with: + system(SIGNAL_TRIGGER_EVENT, (string)EVENT_WARNING + " " + (string)(_slot) + " " + (string)(_msg)) + + send msg 0 to clear a slot + + slot 0 (prim 15): damage (messages 1-3) applied by repair + 1: CHECK HARDWARE + 2: REPAIRS REQUIRED + 3: REPAIRING + slot 1 (prim 16): non-combat modes (messages 4-5) applied by repair + 4: OUT OF CHARACTER + 5: DEGREELESSNESS MODE + slot 2 (prim 17): threats to homeostasis (messages 6-13) applied by status + 6: RADIATION DANGER + 7: HEAT DANGER + 8: ICE DANGER + 9: DUMP HEAT! + 10: BAROMETER FAULT + 11: VACUUM + 12: IN WATER + 13: CRYOLUBRICANT LOW + slot 3 (prim 18): processor status (messages 14 and 19) applied by baseband + 14: WORKING + 19: KERNEL INITIALIZING + slot 4 (prim 19): movement status (messages 15-18, 21) applied by ??? + 15: NAVIGATING + 16: FOLLOWING + 17: ANCHORED + 18: IMMOBILIZED + 21: CARRIED + slot 5 (prim 20): dive status (message 20) applied by ??? + 20: UNDER REMOTE CONTROL + slot 6 (prim 21): weapon status (messages 22-24) applied by ??? (device?) + 22: LOW AMMO + 23: NO AMMO + 24: RELOADING + slot 7 (prim 22): battery status (messages 25-26) applied by status + 25: LOW BATTERY + 26: CHARGING +*/ + +#define CONFIG_CONTROLS 208 + +// 209-248: available + +// 13-14, 86, and 249-250: mouselook combat stuff +#define INTEGRITY 249 +#define AMMO 250 + +#define ARENA_SCOREBOARD_START 251 +#define ARENA_CLOCK 256 + +#define VISIBLE <0.50000, -0.50000, -0.50000, 0.50000> +#define INVISIBLE <0.00000, -0.00000, -0.70711, 0.70711> + +// for FIXED_WARNING_LIST only: +#define VISIBLE_FWL <0.70711, 0.00000, -0.70711, 0.00000> +#define INVISIBLE_FWL <0.50000, -0.50000, -0.50000, -0.50000> + +#define OFFSCREEN <0, 0, -1.25> +#define OFFSCREEN_RIGHT <0, -3, 0> +#define OFFSCREEN_LEFT <0, 3, 0> + +#define MOVER_TEX "cdf9347e-3a47-08d1-0ebc-1e2d27aa802c" +#define METER_TEX "8c782efc-cee7-c616-af9d-9a196cdd87c7" +#define SITREP_METER_TEX "e28a35b0-1581-4cc3-d2ab-9721b3ee3a19" +// #define AURA_TEX "1e1f83bc-968d-57a7-88a3-b88fc1a77a70" +#define AURA_TEX "cac27a60-722b-c653-c806-2c985ff34cf3" +#define BADGE_DEFAULT "0a1ebe09-5691-357e-e68a-cc90a2466fe2" +#define ALTIMETER_MARKER_TEX "4a860b55-2513-015a-63ba-f928c985bd06" + +// #define BOOT_LOGO_TEX llGetInventoryKey("i_boot") +// #define COMPASS_TEX llGetInventoryKey("i_compass") +// #define MENU_TEX llGetInventoryKey("m_main") +// #define ANCHOR_TEX llGetInventoryKey("m_anchor") +// #define CLADDING_LEFT llGetInventoryKey("i_cladding-left") +// #define CLADDING_RIGHT llGetInventoryKey("i_cladding-right") +// #define MLOOK_TEX llGetInventoryKey("i_mlook") +// #define BIGNUM_TEX llGetInventoryKey("i_bignums") +// #define CROSSHAIR_TEX llGetInventoryKey("i_crosshair") +// #define ALTIMETER_TEX llGetInventoryKey("i_altimeter") +#define LUST_TEX llGetInventoryKey("i_sexuality") +// #define SITREP_TEX llGetInventoryKey("i_sitrep") +// #define TARGET_TEX llGetInventoryKey("i_target") +// #define CLADDING_BACKDROP_TEX llGetInventoryKey("i_cladding-backdrop") +// #define ALERT_TEX llGetInventoryKey("i_alert") +// #define WORKING_TEX llGetInventoryKey("i_working") + +// moved to utils.lsl: +// #define str2vec(__str) (vector)("<" + replace(__str, " ", ",") + ">") +// #define vec2str(__vec) (__vec.x + " " + __vec.y + " " + __vec.z) + +#endif // _ARES_INTERFACE_CONSTS_H_ diff --git a/ARES/api/interface.h.lsl b/ARES/api/interface.h.lsl new file mode 100644 index 0000000..419b228 --- /dev/null +++ b/ARES/api/interface.h.lsl @@ -0,0 +1,88 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * INTERFACE.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_INTERFACE_H_ +#define _ARES_INTERFACE_H_ + +// **** interface daemon **** + +// interface_sound(): play a system sound effect to the unit only (must be named in LSD:interface.sound) +#define interface_sound(_name) e_call(C_INTERFACE, E_SIGNAL_CALL, \ + (string)avatar + " " + (string)avatar + " interface sound " + _name) + + +// fixed_warning(slot, msg): display a fixed warning message on the UI (see values in interface.consts.h.lsl) +#define fixed_warning(_slot, _msg) \ + system(SIGNAL_TRIGGER_EVENT, (string)EVENT_WARNING + " " + (string)(_slot) + " " + (string)(_msg)) + +// **** variatype daemon **** + +/* alert(): create an alert message on the HUD + _message: the text to show (must fit in 12 variatype cells, approx. 96 chars) + _icon: 0-4 (icon 0 does not play sound) + _color: 0 for normal (color d), 1 for bad (color c) + _buttons: 0 for consent prompt, 1 for menu prompt, 2 for OK/details, 3 for OK only, 4 for ignore/help/run/delete + then: a list of actions to be invoked, one for each button + every alert acknowledgement will always clear the message + provide "!clear" as an action for just closing the prompt + any _buttons other than 0 will cause the alert to time out after a few seconds + + example: alert("hi", 0, 0, 3, ["!clear"]) simply shows "hi" with dismissal option + + the constants below can help make your alerts easier to read in code +*/ + +#define ALERT_ICON_INFO 0 +#define ALERT_ICON_PERMISSION 1 +#define ALERT_ICON_DATA 2 +#define ALERT_ICON_HARDWARE 3 +#define ALERT_ICON_ERROR 4 + +#define ALERT_COLOR_NORMAL 0 +#define ALERT_COLOR_BAD 1 + +#define ALERT_BUTTONS_CONSENT 0 +#define ALERT_BUTTONS_MENU 1 +#define ALERT_BUTTONS_DETAILS 2 +#define ALERT_BUTTONS_DISMISS 3 +#define ALERT_BUTTONS_AUTOEXEC 6 + +#define alert(_message, _icon, _color, _buttons, ...) e_call(C_VARIATYPE, E_SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " variatype alert " + _message + "\n" + (string)(_icon) + "\n" + (string)(_color) + "\n" + (string)(_buttons) + "\n" + jsarray(__VA_ARGS__)) + +// send a HUD alert message from a daemon: +#define daemon_alert(_message, _icon, _color, _buttons, ...) daemon_to_daemon(E_VARIATYPE, E_SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " variatype alert " + _message + "\n" + (string)(_icon) + "\n" + (string)(_color) + "\n" + (string)(_buttons) + "\n" + jsarray(__VA_ARGS__)) + +#endif // _ARES_INTERFACE_H_ diff --git a/ARES/api/io.h.lsl b/ARES/api/io.h.lsl new file mode 100644 index 0000000..ad042b2 --- /dev/null +++ b/ARES/api/io.h.lsl @@ -0,0 +1,130 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * IO.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_IO_H_ +#define _ARES_IO_H_ + +// **** IO daemon **** + +// create a pipeline; parameter should be a list of strings, each of which describes a pipe to create +/* + // individual pipes use the format: + [p:] [n:] + + - p: is optional; it specifies a UUID to use for the pipe (otherwise this is generated randomly) + - n: is optional; it overrides the pipe this will send to (required if you want complete control over the UUIDs of a pipeline) + - the is one of: + notify : triggers the program using SIGNAL_NOTIFY (volatile) + invoke : triggers the program using SIGNAL_INVOKE (volatile) + signal : triggers the program using SIGNAL_NOTIFY (non-volatile) + permanent : triggers the program using SIGNAL_INVOKE (non-volatile) + to : sends messages to on (non-volatile) - will not send messages to subsequent pipes + from : listens for messages from on and invokes (non-volatile) - will not pass on messages from prior pipes; specify as NULL_KEY to listen to messages from any object; be aware that the user key provided by 'from' will be the object UUID rather than the owning avatar + transport: simply passes messages to the output pipe (non-volatile) + print: simply passes messages to the output pipe (volatile) + : if no type identifier is included, the pipe is assumed to be a volatile SIGNAL_INVOKE pipe + + file_open() automatically creates a special pipe which is functionally identical to a volatile SIGNAL_NOTIFY pipe, but automatically removes the linebreaks at the start of the buffer caused by print() + + volatile pipes are purged on rez by io + invokes will purge their pipes when they finish, unless the pipes are non-volatile or the input stream's UUID is currently assigned to a task + + non-volatile pipes are never purged or destroyed. + + after creation, the program will receive SIGNAL_NOTIFY with the message "* pipe " for each pipe generated, with ins == the pipe key and outs == the next key. +*/ + +#define pipe_open(...) e_call(C_IO, E_SIGNAL_CREATE_RULE, jsarray((list)(__VA_ARGS__))) + +// delete a series of pipes (provide as a list of keys): +#define pipe_close(...) e_call(C_IO, E_SIGNAL_DELETE_RULE, jsarray((list)(__VA_ARGS__))) + +// as pipe_close, but only remove volatile invoke pipes: +#define pipe_close_volatile(...) e_call(C_IO, E_SIGNAL_DELETE_RULE, "V" + jsarray((list)(__VA_ARGS__))) + +// assign a new next pipe to an existing pipe: +#define pipe_extend(__pipe, __next) e_call(C_IO, E_SIGNAL_CREATE_RULE, jsarray((list)("p:" + (string)(__pipe) + " n:" + (string)(__next)))) + +#undef SAFE_EOF +#define SAFE_EOF "" + +// read a message from a pipe and store it in the destination variable: +#define pipe_read(_pipe_key, _dest_var) { _dest_var = read(_pipe_key); } + +string read(key pipe) { + string spipe = "p:" + (string)pipe; + string buffer = llLinksetDataRead(spipe); + integer eor = strpos(buffer, SAFE_EOF); + + if(~eor) { + if(eor == strlen(buffer) - 1) + llLinksetDataDelete(spipe); + else + llLinksetDataWrite(spipe, delstring(buffer, 0, eor)); + buffer = delstring(buffer, eor, LAST); + } else { + llLinksetDataDelete(spipe); + } + + return buffer; +} + +// send a list of pipe chains to _outs, starting from the specified head pipe(s): +#define pipe_list(_outs, _user, ...) e_call(C_IO, E_SIGNAL_QUERY_RULES, (string)(_outs) + " " + (string)(_user) + " " + concat((list)(__VA_ARGS__), " ")) + +// print(outs, user, message): print message to pipe outs on behalf of user + +#if defined(RING_NUMBER) && RING_NUMBER <= R_DAEMON + #define print(_pipe_key, _user, _message) { llLinksetDataWrite("p:" + (string)(_pipe_key), llLinksetDataRead("p:" + (string)(_pipe_key)) + _message + SAFE_EOF); linked(R_KERNEL, SIGNAL_CALL, E_IO + E_PROGRAM_NUMBER + (string)(_pipe_key) + " " + (string)(_user) + " io", ""); } +#else + #define print(_pipe_key, _user, _message) { llLinksetDataWrite("p:" + (string)(_pipe_key), llLinksetDataRead("p:" + (string)(_pipe_key)) + _message + SAFE_EOF); tell(DAEMON, C_IO, E_SIGNAL_CALL + E_PROGRAM_NUMBER + (string)(_pipe_key) + " " + (string)(_user)); } +#endif + +// send messages directly through io on an arbitrary channel (needed for RLV relay, AX, etc.): +#define io_tell(_target, _channel, _message) tell(DAEMON, C_IO, E_SIGNAL_TELL + E_PROGRAM_NUMBER + (string)(_target) + " " + (string)(_channel) + " " + _message) +#define io_broadcast(_channel, _message) io_tell(NULL_KEY, _channel, _message) + +// silently add a message to a pipe so it can be found later: +#define pipe_write(_pipe_key, _message) llLinksetDataWrite("p:" + (string)(_pipe_key), llLinksetDataRead("p:" + (string)(_pipe_key)) + _message + SAFE_EOF) + +// tell _io to process a pipe without adding anything to it: +#if defined(RING_NUMBER) && RING_NUMBER <= R_DAEMON + #define pipe_push(_pipe_key, _user) { linked(R_KERNEL, SIGNAL_CALL, E_IO + E_PROGRAM_NUMBER + (string)(_pipe_key) + " " + (string)(_user) + " io", ""); } +#else + #define pipe_push(_pipe_key, _user) { tell(DAEMON, C_IO, E_SIGNAL_CALL + E_PROGRAM_NUMBER + (string)(_pipe_key) + " " + (string)(_user)); } +#endif + +#endif // _ARES_IO_H_ diff --git a/ARES/api/io.h.lsl.bak b/ARES/api/io.h.lsl.bak new file mode 100644 index 0000000..ee56309 --- /dev/null +++ b/ARES/api/io.h.lsl.bak @@ -0,0 +1,99 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2023 Nanite Systems Corporation + * + * ========================================================================= + * + * IO.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_IO_H_ +#define _ARES_IO_H_ + +// **** IO daemon **** + +// create a pipeline; parameter should be a list of strings, each of which describes a pipe to create +/* + // individual pipes use the format: + [p:] [n:] + + - p: is optional; it specifies a UUID to use for the pipe (otherwise this is generated randomly) + - n: is optional; it overrides the pipe this will send to (required if you want complete control over the UUIDs of a pipeline) + - the is one of: + notify : triggers the program using SIGNAL_NOTIFY (volatile) + invoke : triggers the program using SIGNAL_INVOKE (volatile) + signal : triggers the program using SIGNAL_NOTIFY (non-volatile) + permanent : triggers the program using SIGNAL_INVOKE (non-volatile) + to : sends messages to on (non-volatile) - will not send messages to subsequent pipes + from : listens for messages from on and invokes (non-volatile) - will not pass on messages from prior pipes; specify as NULL_KEY to listen to messages from any object; be aware that the user key provided by 'from' will be the object UUID rather than the owning avatar + transport: simply passes messages to the output pipe (non-volatile) + print: simply passes messages to the output pipe (volatile) + : if no type identifier is included, the pipe is assumed to be a volatile SIGNAL_INVOKE pipe + + file_open() automatically creates a special pipe which is functionally identical to a volatile SIGNAL_NOTIFY pipe, but automatically removes the linebreaks at the start of the buffer caused by print() + + volatile pipes are purged on rez by io + invokes will purge their pipes when they finish, unless the pipes are non-volatile or the input stream's UUID is currently assigned to a task + + non-volatile pipes are never purged or destroyed. + + after creation, the program will receive SIGNAL_NOTIFY with the message "* pipe " for each pipe generated, with ins == the pipe key and outs == the next key. +*/ +#define pipe_open(...) e_call(C_IO, E_SIGNAL_CREATE_RULE, jsarray((list)(__VA_ARGS__))) + +// delete a series of pipes (provide as a list of keys): +#define pipe_close(...) e_call(C_IO, E_SIGNAL_DELETE_RULE, jsarray((list)(__VA_ARGS__))) + +// as pipe_close, but only remove volatile invoke pipes: +#define pipe_close_volatile(...) e_call(C_IO, E_SIGNAL_DELETE_RULE, "V" + jsarray((list)(__VA_ARGS__))) + +// assign a new next pipe to an existing pipe: +#define pipe_extend(__pipe, __next) e_call(C_IO, E_SIGNAL_CREATE_RULE, jsarray((list)("p:" + (string)(__pipe) + " n:" + (string)(__next)))) + +// read a message from a pipe and store it in the destination variable: +#define pipe_read(_pipe_key, _dest_var) { _dest_var = llLinksetDataRead("p:" + (string)(_pipe_key)); llLinksetDataDelete("p:" + (string)_pipe_key); } + +// send a list of pipe chains to _outs, starting from the specified head pipe(s): +#define pipe_list(_outs, _user, ...) e_call(C_IO, E_SIGNAL_QUERY_RULES, (string)(_outs) + " " + (string)(_user) + " " + concat((list)(__VA_ARGS__), " ")) + +// print a message to a pipe on behalf of a user (a linebreak will be prefixed): +#define print(_pipe_key, _user, _message) { llLinksetDataWrite("p:" + (string)(_pipe_key), llLinksetDataRead("p:" + (string)(_pipe_key)) + "\n" + _message); tell(DAEMON, C_IO, E_SIGNAL_CALL + E_PROGRAM_NUMBER + (string)(_pipe_key) + " " + (string)(_user)); } + +// print a message to a pipe (special syntax when called from a daemon): +#define daemon_print(_pipe_key, _user, _message) { llLinksetDataWrite("p:" + (string)(_pipe_key), llLinksetDataRead("p:" + (string)(_pipe_key)) + "\n" + _message); linked(R_KERNEL, SIGNAL_CALL, E_IO + E_PROGRAM_NUMBER + (string)(_pipe_key) + " " + (string)(_user) + " io", ""); } + +// send messages directly through io on an arbitrary channel (needed for RLV relay, AX, etc.): +#define io_tell(_target, _channel, _message) tell(DAEMON, C_IO, E_SIGNAL_TELL + E_PROGRAM_NUMBER + (string)(_target) + " " + (string)(_channel) + " " + _message) +#define io_broadcast(_channel, _message) io_tell(NULL_KEY, _channel, _message) + +// silently add a message to a pipe so it can be found later: +#define pipe_write(_pipe_key, _message) llLinksetDataWrite("p:" + (string)(_pipe_key), llLinksetDataRead("p:" + (string)(_pipe_key)) + "\n" + _message) + +#endif // _ARES_IO_H_ diff --git a/ARES/api/kernel.h.lsl b/ARES/api/kernel.h.lsl new file mode 100644 index 0000000..4562db5 --- /dev/null +++ b/ARES/api/kernel.h.lsl @@ -0,0 +1,51 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * KERNEL.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_KERNEL_H_ +#define _ARES_KERNEL_H_ + +/* + KERNEL INFORMATION QUERIES +*/ + +// kernel queries (each SIGNAL_QUERY_ yields a SIGNAL__REPORT reply): +#define query_modules() kernel(SIGNAL_QUERY_MODULES, "") +#define query_daemons() kernel(SIGNAL_QUERY_DAEMONS, "") + +// control inventory drop (must be done from root prim): +#define inventory_drop(_enabled) kernel(SIGNAL_INVENTORY_DROP, (string)_enabled) + +#endif // _ARES_KERNEL_H_ diff --git a/ARES/api/repair.h.lsl b/ARES/api/repair.h.lsl new file mode 100644 index 0000000..f3e9754 --- /dev/null +++ b/ARES/api/repair.h.lsl @@ -0,0 +1,55 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * REPAIR.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_REPAIR_H_ +#define _ARES_REPAIR_H_ + +// **** repair daemon **** + +/* deal_damage(): deal ATOS damge to the unit + _type: one of: "projectile", "crash", "heat", "cold", "special" + _amount: number of HP to remove; multiply by 4 for projectile and crash damage; may be affected by shields + _source: the UUID of the object causing the damage + + negative numbers apply repair + + use "special" damage if you want to bypass defenses + + if DQD or OOC mode is enabled, the unit's integrity number will not change, but other consequences like masochistic arousal and shield damage may still occur +*/ +#define deal_damage(_amount, _type, _source) e_call(C_REPAIR, E_SIGNAL_CALL, (string)_source + " " + (string)_source + " repair inflict " + (_type) + " " + (string)(_amount)); + +#endif // _ARES_REPAIR_H_ diff --git a/ARES/api/request.h.lsl b/ARES/api/request.h.lsl new file mode 100644 index 0000000..a12cf8c --- /dev/null +++ b/ARES/api/request.h.lsl @@ -0,0 +1,110 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * REQUEST.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_REQUEST_H_ +#define _ARES_REQUEST_H_ + +// lookup functions - implemented in proc +// returns data to a pipe; create a pipe to receive results, or use OUTPUT_SAY as a placeholder for testing + +// in all of these lookup functions, _user is relayed passively, and can be replaced with a handle + +// http_get(): GETs a web address +#define http_get(_url, _outs, _user) notify_program("proc fetch " + _url, _outs, NULL_KEY, _user) + +// http_get but with headers (use llHTTPRequest() format): +#define http_get_extended(_url, _outs, _user, ...) notify_program("proc fetch " + _url + " " + jsarray((list)(__VA_ARGS__)), _outs, NULL_KEY, _user) + +// http_post(): POSTs the input stream to a web address - use pipe_write() with a generated key to populate the input stream +#define http_post(_url, _outs, _ins, _user) notify_program("proc fetch " + _url, _outs, _ins, _user) + +// http_post() but with headers (use llHTTPRequest() format): +#define http_post_extended(_url, _outs, _ins, _user, ...) notify_program("proc fetch " + _url + " " + jsarray((list)(__VA_ARGS__)), _outs, _ins, _user) + +// request_uuid(): returns agent key +#define request_uuid(_agent, _outs, _user) notify_program("proc uuid " + _agent, _outs, NULL_KEY, _user) +// request_name(): returns modern-style username +#define request_name(_agent, _outs, _user) notify_program("proc name " + _agent, _outs, NULL_KEY, _user) +// request_avatar(): returns vintage-style Firstname Lastname +#define request_avatar(_agent, _outs, _user) notify_program("proc avatar " + _agent, _outs, NULL_KEY, _user) +// request_online(): returns 1 if online, 0 if offline +#define request_online(_agent, _outs, _user) notify_program("proc online " + _agent, _outs, NULL_KEY, _user) +// request_born(): returns birthdate in SLT +#define request_born(_agent, _outs, _user) notify_program("proc born " + _agent, _outs, NULL_KEY, _user) + +// request_payinfo(): returns 1 if payment info on file, 0 if not +#define request_payinfo(_agent, _outs, _user) notify_program("proc payinfo " + _agent, _outs, NULL_KEY, _user) + +// request_regionpos(): returns coordinates of a region +#define request_regionpos(_region, _outs, _user) notify_program("proc regionpos " + _region, _outs, NULL_KEY, _user) + +// request_regionstatus(): returns status of a region +#define request_regionstatus(_region, _outs, _user) notify_program("proc regionstatus " + _region, _outs, NULL_KEY, _user) + +// request_regionrating(): returns rating of a region +#define request_regionrating(_region, _outs, _user) notify_program("proc regionrating " + _region, _outs, NULL_KEY, _user) + +/* + _proc http server API (all done with SIGNAL_NOTIFY) + + your program must generate the messages: + _proc listen (to start listening) + _proc release (to end listening) + _proc reply (when a query comes in - set ins to Q) + + // is the HTTP status code, ideally 200 + + (the macros below will take care of these) + + you will receive: + http:// (when created or re-created) + URL_REQUEST_DENIED (when things fail) + (when receiving a message, check input pipe, ins=request body, outs=Q) + + will be activated with ins=request body and outs=Q + + parse request body with read(), then send your response to Q with pipe_write() (don't use print()) + then call _proc reply to send the message + + servers are automatically recreated on region change, which will cause a new http:// to be provided + HTTPS is not supported (yet) +*/ + +#define http_listen(_callback, _user) notify_program("_proc listen " + PROGRAM_NAME + " " + (_callback), NULL_KEY, NULL_KEY, _user) +#define http_release(_callback) notify_program("_proc release " + PROGRAM_NAME + " " + (_callback), NULL_KEY, NULL_KEY, NULL_KEY) +#define http_reply(_Q, _condition, _body) { pipe_write(_Q, _body); notify_program("_proc reply " + (string)(_condition), NULL_KEY, _Q, NULL_KEY); } + +#endif // _ARES_REQUEST_H_ diff --git a/ARES/api/scheduler.h.lsl b/ARES/api/scheduler.h.lsl new file mode 100644 index 0000000..bcc25a8 --- /dev/null +++ b/ARES/api/scheduler.h.lsl @@ -0,0 +1,64 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * SCHEDULER.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_SCHEDULER_H_ +#define _ARES_SCHEDULER_H_ + +/* + EVENT HOOKS + + This asks the scheduler to notify your program when something important happens. The list of supported events can be found in the header. +*/ + +// hooking and unhooking events (provide a list of EVENT_* constants): +#if defined(RING_NUMBER) && RING_NUMBER <= R_DAEMON + #define query_hooks() kernel(SIGNAL_QUERY_HOOKS, "") + #define hook_events(...) kernel(SIGNAL_HOOK_EVENT, PROGRAM_NAME + "," + concat(__VA_ARGS__, ",")) + #define unhook_events(...) kernel(SIGNAL_UNHOOK_EVENT, PROGRAM_NAME + "," + concat(__VA_ARGS__, ",")) +#else + #define query_hooks() tell(DAEMON, C_SCHEDULER, E_SIGNAL_QUERY_HOOKS + PROGRAM_NAME) + #define hook_events(...) tell(DAEMON, C_SCHEDULER, E_SIGNAL_HOOK_EVENT + PROGRAM_NAME + "," + concat(__VA_ARGS__, ",")) + #define unhook_events(...) tell(DAEMON, C_SCHEDULER, E_SIGNAL_UNHOOK_EVENT + PROGRAM_NAME + "," + concat(__VA_ARGS__, ",")) +#endif + +// create a timer (interval must be an integer) +// delete by setting interval to 0 +// tag is an arbitrary string used to disambiguate multiple timers for the same program +// note that timers must be recreated if the kernel resets, since they are PID-based +// (non-timer events survive) +#define set_timer(_tag, _interval) tell(DAEMON, C_SCHEDULER, E_SIGNAL_TIMER + E_PROGRAM_NUMBER + (string)(_interval) + " " + _tag) + +#endif // _ARES_SCHEDULER_H_ diff --git a/ARES/api/sexuality.h.lsl b/ARES/api/sexuality.h.lsl new file mode 100644 index 0000000..c57d5ad --- /dev/null +++ b/ARES/api/sexuality.h.lsl @@ -0,0 +1,45 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * SEXUALITY.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_SEXUALITY_H_ +#define SEXUALITY_VERSION "0.9.2" +#define SEXUALITY_VERSION_TAGS "preview 2" + +#define C_LUST -9999969 +#define C_LUST_FX -1010101 + +list SEXUALITY_MODES = ["disabled", "narrative", "interactive", "masochism"]; +#endif // #ifndef _ARES_SEXUALITY_H_ diff --git a/ARES/api/status.h.lsl b/ARES/api/status.h.lsl new file mode 100644 index 0000000..1ca1f8d --- /dev/null +++ b/ARES/api/status.h.lsl @@ -0,0 +1,70 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * STATUS.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_STATUS_H_ +#define _ARES_STATUS_H_ + +/* status daemon: power loads & updates + + 1. create a power load with: + + setdbl("chassis", ["load", device + "__" + load_name], (string)wattage); + + where 'device' should be 'system' or the address of a connected device, and load_name is a single-word lower-case description of why power is being used + + 2. trigger the status daemon to reload the status section with: + + (from a program) e_call(C_STATUS, E_SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " status update"); + + (from a daemon) daemon_to_daemon(E_DAEMON, SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " status update"); + + 3. when finished, delete the power load: + + deletedbl("chassis", ["load", device + "__" + load_name]); + + 4. repeat step 2 + +*/ + +// create/modify or remove a power load: +#define set_power_load(_device, _load_name, _wattage) setdbl("chassis", ["load", _device + "__" + _load_name], (string)_wattage) +#define delete_power_load(_device, _load_name) deletedbl("chassis", ["load", _device + "__" + _load_name]) +// (remember to trigger a status update afterward, as described in the comment above) + +#define status_update() e_call(C_STATUS, E_SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " status update"); +// #define external_teleport() e_call(C_STATUS, E_SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " status external-tp"); + +#endif // _ARES_STATUS_H_ diff --git a/ARES/api/storage.h.lsl b/ARES/api/storage.h.lsl new file mode 100644 index 0000000..c705311 --- /dev/null +++ b/ARES/api/storage.h.lsl @@ -0,0 +1,109 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * STORAGE.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_STORAGE_H_ +#define _ARES_STORAGE_H_ + +/* + NEW UNIFORM FILE ACCESS SYSTEM - ARES 0.4.4 + + replaces ARES/api/fs.h.lsl +*/ + +#define FILE_PAGE_LENGTH 1024 +#define FILE_LINE_WIDTH 1024 + +// open a pipe and performs a stat (requests file info): +#define file_open(_pipe, _filename) file_read(_pipe, _filename, NOWHERE); + +/* + file stat in ARES 0.4.4 + + When a file is opened, _storage will send the following fields to your application via a notify " file" pipe: + + + + The fields are space-separated and have the following definitions: + is the length of the file, measured in . + is b for bytes, p for pages, or l for lines. + is d for directory, o for unreadable objects, and f for readable text files. + is a freeform string (e.g. llGetInventoryDesc()) and may not be present. + + Attempting to read an object file will cause an error. (These were added to represent SL inventory items with no interpretable contents.) Attempting to read a directory will give a linebreak-separated list of names therein. The special file ":*" will list all files in , e.g. "local:*". + + The ":" prefix can also be used to disambiguate files with identical names, but finding them in the first place may be nontrivial; the behavior of fs:root when multiple files with the same name are present in different sources is currently undefined and will generally cause problems. +*/ + +/* + file read in ARES 0.4.4 + + Once the file is opened and you have received its stat values (above), you can then ask _storage to start sending data. To successfully iterate through a file, you need to look at the unit and then request appropriate sections with file_read(). + + The offset parameter of file_read() may be a single number or (with a " " in the middle). If no is missing, then only 1 unit will be returned. If your application can handle it, you should simply request the entire file using the given by the file_open() macro. + + If the is b, then you will only get one byte at a time if you fail to specify , which is not very useful. Instead use a larger window, like 1024 or 2048, and iterate over the file to the best of your program's memory-handling abilities. + + If you request data from an "l" file, a linebreak will always be added on the end so that the text can be concatenated properly without worrying about the underlying format. + +*/ +#define file_read(_pipe, _filename, _offset) e_call(C_IO, E_SIGNAL_DATA_REQUEST, (string)(_pipe) + " " + PROGRAM_NAME + " " + (_filename) + " " + (string)(_offset)) + +// just closes the pipe; the storage protocol is stateless: +#define file_close(_pipe) e_call(C_IO, E_SIGNAL_DELETE_RULE, "[\"" + (string)(_pipe) + "\"]") + +// write text directly into a filename (provided the target filesystem supports and allows it). +// fill _pipe using pipe_write(), then call this to send the data. +// NOTE: the file will be OVERWRITTEN. Use file_append() if you do not want this. +#define file_write(_filename, _pipe) e_call(C_STORAGE, E_SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " storage write " + (_filename) + " " + (string)(_pipe)) +#define file_append(_filename, _pipe) e_call(C_STORAGE, E_SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " storage append " + (_filename) + " " + (string)(_pipe)) + +// delete a file, provided the filesystem supports and allows it. +#define file_delete(_filename) e_call(C_STORAGE, E_SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " storage delete " + (_filename)) + +// OTHER FILESYSTEM ACTIVITIES + +// get all filenames in a view: +#define view_files(_rule) js2list(llLinksetDataRead("fs:"+_rule)) +// get all filenames in a source: +#define source_files(_rule) split(llLinksetDataRead("fs:"+_rule), "\n") + +// get all filenames (careful; this can be huge): +#define list_all_files() jskeys(llLinksetDataRead("fs:root")) + +// refresh a source: +#define storage_refresh(_source, _callback, _user) e_call(C_STORAGE, E_SIGNAL_CALL, (string)(_callback) + " " + (string)(_user) + " storage refresh " + (_source)) + +#endif // _ARES_STORAGE_H_ diff --git a/ARES/api/tasks.h.lsl b/ARES/api/tasks.h.lsl new file mode 100644 index 0000000..dcbb368 --- /dev/null +++ b/ARES/api/tasks.h.lsl @@ -0,0 +1,86 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * TASKS.H.LSL Header Component + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_TASKS_H_ +#define _ARES_TASKS_H_ + +/* + MODES AND TASK MANAGEMENT + + Every program has a bitfield called '_mode' (note the underscore) which defines when the OS will try to put it to sleep. The most common mode flag is MODE_THREADING, which is set automatically while the program has an active task. This should be used whenever the program is waiting for a response from another module, to avoid the 2 second delay of starting back up. Threading jobs will be reset on rez (including sim change, login, attach). + + You can begin and end tasks whenever you want. + + MODE_NON_VOLATILE is set on programs with '#define NON_VOLATILE' at the top. These programs never go to sleep, and the kernel will not even double-check to make sure they are still flagged as awake when sending them messages. The NON_VOLATILE flag also means the program never gets reset during a rezzing event. + + MODE_ACCEPT_DONE means the program will be informed whenever an invoke() or notify() has completed. +*/ + +string tasks_queue = "{}"; + +// create a task with the specified tags +// in most cases, you should use ins (the input stream) for _id, as this will protect both the input and output stream from being recycled if they are volatile + +#define task_begin(_id, _message) { tasks_queue = setjs(tasks_queue, [_id], _message); system(SIGNAL_MODE, PROGRAM_NAME + "," + (string)(_mode = _mode | MODE_THREADING)); } +#define task_end(_id) (tasks_queue = setjs(tasks_queue, [_id], JSON_DELETE)) +#define task_count() count(jskeys(tasks_queue)) + +// [DEPRECATED/NICHE] politely shut down program (kernel removes MODE_THREADING when this happens, so we don't need to tell it): +// (you may still need this when e.g. ending a real timer-based task) +#define exit() { if(_resolved) system(SIGNAL_DONE, ares_encode(_resolved) + NULL_KEY + " " + PROGRAM_NAME); system(SIGNAL_TERMINATED, PROGRAM_NAME); _mode = _mode & ~MODE_THREADING; llSetScriptState(PROGRAM_NAME, FALSE); llSleep(0.022); } + +// [DEPRECATED/NICHE] send a callback key with exit: +#define exitc(_ins) { if(_resolved) system(SIGNAL_DONE, ares_encode(_resolved) + (string)(_ins) + " " + PROGRAM_NAME); system(SIGNAL_TERMINATED, PROGRAM_NAME); _mode = _mode & ~MODE_THREADING; llSetScriptState(PROGRAM_NAME, FALSE); llSleep(0.022); } + +// [DEPRECATED] send a receipt for a completed task, without shutting down: +#define resolve(_R) if(_R) system(SIGNAL_DONE, ares_encode(_R) + NULL_KEY + " " + PROGRAM_NAME) + +// [DEPRECATED] send a receipt for a completed task (with callback), without shutting down - deprecated: +#define resolvec(_R, _ins) if(_R) system(SIGNAL_DONE, ares_encode(_R) + (string)(_ins) + " " + PROGRAM_NAME) + +// [QUESTIONABLE] send a receipt for a completed task, including callback, and close streams (if they are volatile invoke pipes): +#define resolve_io(_R, _outs, _ins) { pipe_close_volatile([_ins, _outs]); if(_R) system(SIGNAL_DONE, ares_encode(_R) + (string)(_ins) + " " + PROGRAM_NAME); } + +// [RECOMMENDED] send a receipt for a completed task, including callback, and close only the input stream (if it is a volatile invoke pipe): +#define resolve_i(_R, _ins) { if(_R) system(SIGNAL_DONE, ares_encode(_R) + (string)(_ins) + " " + PROGRAM_NAME); pipe_close_volatile(_ins); } + +// set the program's MODE_* flags and inform the kernel: +#define set_mode(_M) system(SIGNAL_MODE, PROGRAM_NAME + "," + (string)(_mode = _M)) + +// delay_relock(): informs the policy manager to push back auto-lock, if auto-lock is enabled +#define delay_relock() if((integer)getdbl("policy", ["autolock", "enabled"]) && !(integer)getdbl("policy", ["lock"])) notify_program("policy delay", avatar, NULL_KEY, avatar) + +#endif // _ARES_TASKS_H_ diff --git a/ARES/api/version-compare.h.lsl b/ARES/api/version-compare.h.lsl new file mode 100644 index 0000000..4c034dd --- /dev/null +++ b/ARES/api/version-compare.h.lsl @@ -0,0 +1,86 @@ + +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * VERSION-COMPARE.H.LSL + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#ifndef _ARES_VERSION_COMPARE_H_ +#define _ARES_VERSION_COMPARE_H_ + +// compares two version numbers +// returns a negative number if the first argument is greater +// returns a positive number if the second argument is greater +// returns zero if they are equal + +integer version_compare(string version_a, string version_b) { + if(version_a == version_b) + return 0; + + list pa = splitnulls(version_a, "."); + list pb = splitnulls(version_b, "."); + integer ca; + integer cb; + while(geti(pa, LAST) == 0 && ((ca = count(pa)) > 0)) + pa = delitem(pa, LAST); + while(geti(pb, LAST) == 0 && ((cb = count(pb)) > 0)) + pb = delitem(pb, LAST); + + if(ca == 0) { + ca = 1; + pa = ["0"]; + } + if(cb == 0) { + cb = 1; + pb = ["0"]; + } + + integer i; + while(i < ca && i < cb) { + integer ja = (integer)gets(pa, i); + integer jb = (integer)gets(pb, i); + if(ja > jb) + return -(i + 1); + else if(ja < jb) + return (i + 1); + ++i; + } + + if(ca > cb) + return -(cb+1); + else if(ca < cb) + return (ca+1); + + return 0; +} + +#endif // _ARES_VERSION_COMPARE_H_ diff --git a/ARES/application/calc.lsl b/ARES/application/calc.lsl new file mode 100644 index 0000000..a1a6e18 --- /dev/null +++ b/ARES/application/calc.lsl @@ -0,0 +1,466 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Calculator Utility + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "1.2.0" +#define CLIENT_VERSION_TAGS "release" + +#define PUSH(_list, _item) _list += (list)(_item) +#define SHIFT(_list) gets(_list, 0); _list = delitem(_list, 0) +#define POP(_list) gets(_list, LAST); _list = delitem(_list, LAST) +#define UNSHIFT(_list, _item) _list = (list)(_item) + _list + +#define SYMBOLS ["<<", ">>", "+", "-", "**", "*", "/", "%", "^", "~", "&&", "||", "&", "|", "(", ")"] + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + llSetMemoryLimit(0x10000); + llScriptProfiler(amm_profiling = PROFILE_SCRIPT_MEMORY); + list argv = split(m, " "); + integer argc = count(argv); + string msg; + if(argc == 1) { + msg = "Usage: " + PROGRAM_NAME + " [-x|-d|-e] \n -x: Return result in hexadecimal\n (input hex values as 0xff)\n -d: Decode from ARES base64\n -e: Encode to ARES base64\n -r [[] ]: generate a random integer between (or 0) and ( - 1); if no bounds are specified, generates a random float between 0.0 and 1.0\n\nSupported symbols: " + concat(SYMBOLS, ", "); + } else { + integer hex_output; + integer ARES_decode; + integer ARES_encode; + string first = gets(argv, 1); + if(first == "-r") { + if(argc == 2) + msg = (string)llFrand(1.0); + else if(argc == 3) + msg = (string)((integer)llFrand((float)gets(argv, 2))); + else if(argc == 4) + msg = (string)((integer)(llFrand((float)gets(argv, 3) - (float)gets(argv, 2))) + (integer)gets(argv, 2)); + + jump error; + } else if(first == "-x") { + hex_output = 1; + argv = delitem(argv, 1); + #ifdef DEBUG + echo("hexadecimal output enabled if result is integer"); + #endif + } else if(first == "-d") { + ARES_decode = 1; + string t0 = gets(argv, 2); + argv = alter(argv, [ares_decode(t0)], 1, 2); + } else if(first == "-e") { + ARES_encode = 1; + argv = delitem(argv, 1); + } + list tokens = llParseString2List(concat(delitem(argv, 0), " "), [" "], sublist(SYMBOLS, 0, 7)); + // n.b. there is a hard limit of 8 separators + integer ti = 0; + integer imax = count(tokens); + list symbol_block_2 = sublist(SYMBOLS, 8, 15); + while(ti < imax) { + list tr = llParseString2List(gets(tokens, ti), [], symbol_block_2); + tokens = alter(tokens, tr, ti, ti); + integer tri = count(tr); + if(tri) { + ti += tri; + imax += tri - 1; + } else { + ++ti; + } + } + + #ifdef DEBUG + echo("Initial tokens: " + concat(tokens, ", ")); + #endif + + #define SYMBOL_TEXT 0 + #define SYMBOL_CLASS 1 + + #define C_IS_ATOM 0x001 + #define C_INTEGER 0x03 + #define C_FLOAT 0x05 + + #define C_OPEN_PAREN 0x0400 + #define C_CLOSE_PAREN 0x0600 + + #define C_IS_OPERATOR 0x1000 + #define C_IS_UNARY 0x0010 + + #define C_UNARY 0x1710 + // C_UNARY consists of unary - ~ + #define C_EXP 0x1600 + // C_EXP consists of ** << >> + #define C_MUL 0x1500 + // C_MUL consists of * / % + #define C_SUM 0x1400 + // C_SUM consists of + - + #define C_BIT 0x1300 + // C_BIT consists of ^ & | + #define C_NOT 0x1210 + // C_NOT consists of unary ! + #define C_BOOL 0x1100 + // C_BOOL consists of && || + + tokens = ["("] + tokens + [")"]; + imax += 2; + + // each token is represented as a doublet: [text,class] + list stack = []; + + list parse; + + integer i = 0; + + #define T_OPEN_PAREN "[\"\",64]" + + while(i < imax) { + string token_text = SHIFT(tokens); + integer first = llOrd(token_text, 0); + + string top = gets(stack, LAST); + string top_text; + integer top_class; + if(top != "") { + top_class = (integer)getjs(top, [SYMBOL_CLASS]); + top_text = getjs(top, [SYMBOL_TEXT]); + } else { + top_class = NOWHERE; + top_text = "?"; + } + + integer token_class; + + if(first == 0x30 || first == 0x2e || (float)token_text != 0) { // '0' or '.' + if(~strpos(token_text, ".")) + token_class = C_FLOAT; + else if(~strpos(token_text, "x")) + token_class = C_INTEGER; + else if(~strpos(token_text, "e")) + token_class = C_FLOAT; + else + token_class = C_INTEGER; + + } else if(token_text == "(") { + token_text = ""; + token_class = C_OPEN_PAREN; + } else if(contains(SYMBOLS, token_text)) { + if(token_text == "-") { + if(top_class == C_OPEN_PAREN && ((integer)getjs(gets(parse, LAST), [SYMBOL_CLASS]) & C_IS_ATOM)) { + token_class = C_SUM; + #ifdef DEBUG + echo("interpreted - as first subtraction in pg"); + #endif + } else if(top_class == C_OPEN_PAREN) { + token_class = C_UNARY; + #ifdef DEBUG + echo("interpreted - as negative at start of pg"); + #endif + } else if(top_class & C_IS_OPERATOR) { + token_class = C_UNARY; + #ifdef DEBUG + echo("interpreted - as negative after op " + top_text); + #endif + } else { + token_class = C_SUM; + #ifdef DEBUG + echo("interpreted - as base case subtraction"); + #endif + } + } else if(token_text == "~") + token_class = C_UNARY; + else if(token_text == "**" || token_text == "<<" || token_text == ">>") + token_class = C_EXP; + else if(token_text == "*" || token_text == "/" || token_text == "%") + token_class = C_MUL; + else if(token_text == "+") + token_class = C_SUM; + else if(token_text == "^" || token_text == "&" || token_text == "|") + token_class = C_BIT; + else if(token_text == "!") + token_class = C_NOT; + else if(token_text == "&&" || token_text == "||") + token_class = C_BOOL; + else if(token_text == ")") + token_class = C_CLOSE_PAREN; + + } else { + msg = "Unknown symbol: " + token_text; + jump error; + } + + string token = jsarray([token_text, token_class]); + + #ifdef DEBUG + echo("evaluating token " + token + " and stack " + concat(stack, " · ")); + #endif + + if(token_class & C_IS_ATOM) { + // numeric atom + #ifdef DEBUG + echo("top when evaluating atom " + token_text + ": " + (string)top_class); + #endif + if(top_class & C_IS_ATOM) { + msg = "Bad syntax: two consecutive atoms " + top_text + " " + token_text; + jump error; + } else if(top_class == C_OPEN_PAREN || top_class == 0) { + PUSH(parse, token); + #ifdef DEBUG + echo("emitted first token of paren group: " + token); + #endif + } else { + PUSH(stack, token); + #ifdef DEBUG + echo("stacked " + token); + #endif + } + } else if(token_class == C_OPEN_PAREN) { + PUSH(stack, token); + #ifdef DEBUG + echo("stacked open paren"); + #endif + } else { // must be an operator or C_CLOSE_PAREN + string second = gets(stack, -2); + integer second_class = (integer)getjs(second, [SYMBOL_CLASS]); + /* + possibilities for an operator (using /): + input stack current parse correct action final parse + 1/2 ( 1 stack / 2 1 2 / + (1+2)/3 ( 1 2 + stack / 3 1 2 + 3 / + 1**3/2 ( ** 3 1 emit 3 **, stack / 2 1 3 ** 2 / + 1+2/3 ( + 2 1 stack / 3 1 2 3 / + + 1+2/3**4 ( + 2 1 stack / 3 1 2 3 4 ** / + + 1+2/3/4 1 2 3 / 4 / + + 1+2/3+4 1 2 3 / 4 + + + 1*2/3*4 1 2 * 3 / 4 * + 1%2/3 ( % 2 1 emit 2 %, stack / 3 1 2 % 3 / + 1+(1+2/3)**3 ( + ( + 2 1 1 stack / 3 1 1 2 3 / + + 3 ** + + shift leftovers + */ + + if(token_class & C_IS_OPERATOR) { + if(second_class & C_IS_OPERATOR) { + // emit [top, second] only if second_class is at least token_class + if(second_class >= token_class && top_class != C_OPEN_PAREN) { + PUSH(parse, top); + PUSH(parse, second); + #ifdef DEBUG + echo("precedence decision: emitted " + top + " and " + second + " instead of " + token); + #endif + stack = delrange(stack, -2, -1); + } + } + + // convert unary operators into binary operators: + if(token_class & C_IS_UNARY) { + PUSH(stack, top = jsarray([top_text = "0", top_class = C_INTEGER])); + } + + PUSH(stack, token); + } + } + + ++i; + if(token_class == C_CLOSE_PAREN || i == imax) { + string arg; + string a2; + while(count(stack) && (i == imax || arg != T_OPEN_PAREN)) { + arg = POP(stack); + if((integer)getjs(arg, [SYMBOL_CLASS]) & C_IS_ATOM) { + a2 = arg; + arg = POP(stack); + + string a3 = gets(stack, LAST); + if((integer)getjs(a3, [SYMBOL_CLASS]) & C_IS_ATOM) { + POP(stack); + PUSH(parse, a3); + #ifdef DEBUG + echo("consume a3 " + a3); + #endif + } + + if((integer)getjs(a2, [SYMBOL_CLASS]) != C_OPEN_PAREN) { + PUSH(parse, a2); + #ifdef DEBUG + echo("a2 emit " + a2); + #endif + } + #ifdef DEBUG + else { + echo("a2 no emit paren"); + } + #endif + } + if((integer)getjs(arg, [SYMBOL_CLASS]) != C_OPEN_PAREN) { + PUSH(parse, arg); + #ifdef DEBUG + echo("arg emit " + arg); + #endif + } + #ifdef DEBUG + else { + echo("arg no emit paren"); + } + #endif + } + #ifdef DEBUG + echo("drainage stopped on " + arg); + #endif + } + } + + #ifdef DEBUG + echo("Final parse: " + concat(parse, " · ")); + #endif + + list execution_stack; + + while(count(parse)) { + string token = SHIFT(parse); + string text = getjs(token, [SYMBOL_TEXT]); + integer class = (integer)getjs(token, [SYMBOL_CLASS]); + if(class & C_IS_ATOM) { + PUSH(execution_stack, token); + } else if(class & C_IS_OPERATOR) { + string right = POP(execution_stack); + string left = POP(execution_stack); + string left_text = getjs(left, [SYMBOL_TEXT]); + string right_text = getjs(right, [SYMBOL_TEXT]); + integer li = (integer)left_text; + integer ri = (integer)right_text; + float lf = (float)left_text; + float rf = (float)right_text; + integer left_is_float = (llFabs(lf - (float)li) > 0.0001) || ~strpos(left_text, "."); + integer right_is_float = (llFabs(rf - (float)ri) > 0.0001) || ~strpos(right_text, "."); + integer have_floats = left_is_float || right_is_float; + + string result_text; + integer result_type = C_INTEGER; + + if(have_floats) + result_type = C_FLOAT; + + if(text == "<<") { + result_text = (string)(li << ri); + } else if(text == ">>") { + result_text = (string)(li >> ri); + } else if(text == "+") { + if(have_floats) + result_text = (string)(lf + rf); + else + result_text = (string)(li + ri); + } else if(text == "-") { + if(have_floats) + result_text = (string)(lf - rf); + else + result_text = (string)(li - ri); + } else if(text == "**") { + if(have_floats) + result_text = (string)llPow(lf, rf); + else + result_text = (string)llPow(li, ri); + } else if(text == "*") { + if(have_floats) + result_text = (string)(lf * rf); + else + result_text = (string)(li * ri); + } else if(text == "/") { + if(have_floats) + result_text = (string)(lf / rf); + else + result_text = (string)(li / ri); + } else if(text == "%") { + result_text = (string)(li % ri); + } else if(text == "^") { + result_text = (string)(li ^ ri); + } else if(text == "~") { + result_text = (string)(~ri); + } else if(text == "&&") { + result_text = (string)(li && ri); + } else if(text == "||") { + result_text = (string)(li || ri); + } else if(text == "&") { + result_text = (string)(li & ri); + } else if(text == "|") { + result_text = (string)(li | ri); + } else { + msg = "Unknown operator " + text; + jump error; + } + + PUSH(execution_stack, jsarray([result_text, result_type])); + } else { + msg = "Parse invalid: found token " + token + " in execution stack"; + jump error; + } + } + + if(count(execution_stack) != 1) { + msg = "Error: multiple results: " + concat(execution_stack, " · "); + } else { + string final_output = gets(execution_stack, 0); + integer final_class = (integer)getjs(final_output, [SYMBOL_CLASS]); + string final_text = getjs(final_output, [SYMBOL_TEXT]); + /* #ifdef DEBUG + echo("hex output? " + (string)hex_output + "; final class = " + (string)final_class); + #endif*/ + if(substr(final_text, 0, 1) == "0x") + final_text = (string)((integer)final_text); + + if(hex_output && final_class == C_INTEGER) { + msg = "0x" + hex((integer)final_text); + } else if(ARES_encode && final_class == C_INTEGER) { + integer v = (integer)final_text; + msg = ares_encode(v); + } else { + msg = final_text; + } + } + } + + @error; + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/corrado.lsl b/ARES/application/corrado.lsl new file mode 100644 index 0000000..44cdc54 --- /dev/null +++ b/ARES/application/corrado.lsl @@ -0,0 +1,635 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Corrado Utility + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "0.1.1" +#define CLIENT_VERSION_TAGS "alpha" + +// argsplit() from find.lsl +list argsplit(string m) { + // splits a string based on word boundaries, but groups terms inside double and single quotes + // tabs and newlines are converted to spaces + list results; + string TAB = llChar(9); + list atoms = llParseStringKeepNulls(m, [], [" ", "\\", "'", "\"", "\n", TAB]); + integer in_quotes; // 1 = single, 2 = double + integer ti = 0; + integer tmax = count(atoms); + + string buffer; + while(ti < tmax) { + string t = gets(atoms, ti); + integer c = llOrd(t, 0); + if(c == 0x22) { // '"' + if(in_quotes == 0) { + in_quotes = 2; + } else if(in_quotes == 2) { + in_quotes = 0; + } else { + buffer += t; + } + } else if(c == 0x27) { // '\'' + if(in_quotes == 0) { + in_quotes = 1; + } else if(in_quotes == 1) { + in_quotes = 0; + } else { + buffer += t; + } + } else if(c == 0x5c) { // '\\' + string t1 = gets(atoms, ti + 2); + if(t1 == "\"" || t1 == "'") { + buffer += t1; + ti += 2; + } else { + buffer += t; + } + } else if(c == 0x20 || c == 0x0a || c == 0x09) { // ' ', '\n', '\t' + if(in_quotes) { + buffer += " "; + } else { + results += buffer; + buffer = ""; + } + } else { + buffer += t; + } + ++ti; + } + + if(tmax) + results += buffer; + + return results; +} + +string bot_address; +key bot; +string group; +string password; + +string callback; + +key http_fetch_reply; +list waiting_queries; // [src, ins, handle, outs, user, post_body_pipe, post_reply_pipe, bot_address] +list active_queries; // [src, ins, handle, outs, user] + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_NOTIFY) { + list argv = split(m, " "); + string arg0 = gets(argv, 0); + if(arg0 == PROGRAM_NAME || arg0 == "*") { + string arg1 = gets(argv, 1); + if(arg1 == "http") { + string path = gets(argv, 2); + if(substr(path, 0, 0) == "/") { + string buffer = read(ins); + // echo(PROGRAM_NAME + ": received query at " + path); + if(buffer != "") { + print(user, user, buffer); + } + + http_reply(outs, 200, "OK"); + /* + pipe_write(outs, "OK"); + notify_program("_proc reply 200", NULL_KEY, outs, user); + */ + } else if(substr(path, 0, 3) == "http") { + echo(PROGRAM_NAME + ": got URL " + path); + callback = path + "/"; + } else if(path == URL_REQUEST_DENIED) { + echo(PROGRAM_NAME + ": URL recruitment failed."); + callback = ""; + } + } else if(arg1 == "fetched") { + string buffer = read(ins); + key handle = gets(argv, 2); + integer aqi = index(active_queries, handle); + if(~aqi) { + integer r = geti(active_queries, aqi - 2); + key r_ins = getk(active_queries, aqi - 1); + key r_outs = getk(active_queries, aqi + 1); + key r_user = getk(active_queries, aqi + 2); + active_queries = delrange(active_queries, aqi - 2, aqi + 2); + + print(r_outs, r_user, buffer); + resolvec(r, r_ins); + } else { + echo("invalid aqi: " + m); + } + pipe_close(ins); + } else if(arg1 == "pipe") { + // notify fetched + if(gets(argv, 2) == "notify" + && gets(argv, 3) == PROGRAM_NAME + && gets(argv, 4) == "fetched") { + key handle = gets(argv, 5); + integer wqi = index(waiting_queries, handle); + if(~wqi) { + active_queries += sublist(waiting_queries, wqi - 2, wqi + 2); + + string q_addr = gets(waiting_queries, wqi + 5); + key q_reply_p = getk(waiting_queries, wqi + 4); + key q_body_p = getk(waiting_queries, wqi + 3); + + http_post(q_addr, q_reply_p, q_body_p, handle); + waiting_queries = delrange(waiting_queries, wqi - 2, wqi + 5); + // echo("dispatched query"); + } else { + echo("invalid wqi: " + m); + } + } else { + echo("unexpected pipe open: " + m); + } + } + } + + } else if(n == SIGNAL_INVOKE) { + list argv = argsplit(m); // non-standard + integer argc = count(argv); + string msg = ""; + if(argc == 1) { + msg = "Syntax: " + PROGRAM_NAME + " [-] [-t] [-v] [-f] [-a |-u ] [-g ] [-p ] [-- [...]] [ []]\n\nConfigures and sends commands to a Corrade agent. Messages are formatted as JSON and sent over IM.\n\n -a : Sets the agent UUID.\n -u : Sets the Corrade HTTP URL.\n -g : Sets the authentication group name.\n -p : Sets the authentication group password.\n\nIf any of the above settings are missing, the previous values will be re-used. -a and -u are mutually exclusive.\n\n : One of https://grimore.org/secondlife/scripted_agents/corrade/api/commands\n : Contextual; usually mapped to the third parameter listed on the Complete List of Commands page.\n -- : Sets additional arguments. Use \"double quotes\" to wrap values containing spaces.\n -t: Test only; do not send. Useful for determining what maps to.\n -v: Verbose (warn about missing or empty parameters)\n -f: Don't wait for response when using HTTP; just send command and quit.\n -: Read from standard input, discarding if non-empty."; + /*} else if(gets(argv, 1) == "-F") { + if(http_fetch_reply != "") { + pipe_close(http_fetch_reply); + } + http_fetch_reply = llGenerateKey(); + pipe_open("p:" + (string)http_fetch_reply + " notify " + PROGRAM_NAME + " fetched"); */ + } else if(gets(argv, 1) == "-L") { + // notify_program("_proc listen " + PROGRAM_NAME + " http", NULL_KEY, NULL_KEY, user); + http_listen("http", user); + callback = ""; + } else if(gets(argv, 1) == "-R") { + // notify_program("_proc release " + PROGRAM_NAME + " http", NULL_KEY, NULL_KEY, user); + http_release("http"); + callback = ""; + } else { + integer no_http_wait = 0; + integer warn = 0; + integer test = 0; + integer pipe_in = 0; + string command; + string message; + integer argi = 1; + + string json = "{}"; + if(group != "") + json = setjs(json, ["group"], group); + if(password != "") + json = setjs(json, ["password"], password); + if(callback != "") + json = setjs(json, ["callback"], callback); + + while(argi < argc) { + string term = gets(argv, argi); + if(message == "") { + if(term == "-") { + pipe_in = 1; + } else if(term == "-f") { + no_http_wait = 1; + } else if(term == "-v") { + warn = 1; + } else if(term == "-t") { + test = 1; + } else if(term == "-a") { + bot = (key)gets(argv, ++argi); + bot_address = ""; + } else if(term == "-u") { + bot_address = gets(argv, ++argi); + bot = ""; + } else if(term == "-g") { + group = gets(argv, ++argi); + json = setjs(json, ["group"], group); + } else if(term == "-p") { + password = gets(argv, ++argi); + json = setjs(json, ["password"], password); + } else if(substr(term, 0, 1) == "--") { + json = setjs(json, [delstring(term, 0, 1)], gets(argv, ++argi)); + } else if(command == "") { + command = term; + json = setjs(json, ["command"], command); + } else { + message = term; + } + } else { + message += " " + term; + } + + ++argi; + } + + if(pipe_in) { + string pipe_buffer = read(ins); + if(pipe_buffer != "") + message = pipe_buffer; + } + + if(command == "") { + msg = "No command specified. See https://grimore.org/secondlife/scripted_agents/corrade/api/commands for a list of available commands."; + } else if(bot == "" && bot_address == "") { + msg = "No Corrade bot specified. Please set with -a or -u "; + } else if(group == "") { + msg = "No authentication group specified. Please set with -a (use quotes)"; + } else if(password == "") { + msg = "No authentication password specified. Please set with -p (use quotes)"; + } else if(json == JSON_INVALID) { + msg = "JSON encoding failed, likely due to imbalanced braces or square brackets in one or more parameters."; + } else { + string message_key; + { + string message_mappings = jsobject([ + "addclassified", "name", + "addpick", "name", + "addtorole", "agent", + "agentaccess", "action", + "animation", "item", + "attach", "attachments", + "attachobject", "item", + "autopilot", "position", + "avatarnotes", "data", + "avatarzoffset", "offset", + "away", "action", + "ban", "avatars", + "batchaddtorole", "avatars", + "batchanimation", "item", + "batchattachobjects", "attachments", + "batchavatarkeytoname", "avatars", + "batchavatarnametokey", "avatars", + "batchdeletefromrole", "avatars", + "batchderez", "item", + "batchdropobject", "attachments", + "batcheject", "avatars", + "batchgetavatarappearancedata", "agents", + "batchgetavatardisplayname", "agents", + "batchgetavatarseat", "agents", + "batchgetprofiledata", "data", + "batchgive", "item", + "batchgroupkeytoname", "groups", + "batchgroupnametokey", "groups", + "batchinvite", "avatars", + "batchlure", "avatars", + "batchmute", "mutes", + "batchsetinventorydata", "data", + "batchsetobjectgroup", "item", + "batchsetobjectpermissions", "item", + "batchsetobjectpositions", "item", + "batchsetobjectrotations", "item", + "batchsetparcellist", "avatars", + "batchsetprimitivedescriptions", "item", + "batchsetprimitivenames", "item", + "batchsetprimitivepositions", "item", + "batchsetprimitiverotations", "item", + "batchtell", "message", + "batchupdateprimitiveinventory", "entity", + "busy", "action", + "changeappearance", "folder", + "changeprimitivelink", "item", + "click", "item", + "compilescript", "data", + "conference", "avatars", + "configuration", "path", + "copynotecardasset", "item", + "creategrass", "position", + "creategroup", "data", + "createlandmark", "name", + "createnotecard", "name", + "createprimitive", "name", + "createrole", "role", + "createtree", "position", + "crouch", "action", + "deleteclassified", "name", + "deletefromrole", "agent", + "deletepick", "name", + "deleterole", "role", + "deleteviewereffect", "id", + "derez", "item", + "detach", "attachments", + "directoryquery", "name", + "directorysearch", "name", + "displayname", "name", + "download", "item", + "dropobject", "item", + "eject", "agent", + "estateteleportusershome", "avatars", + "execute", "file", + "exportdae", "item", + "exportoar", "item", + "exportxml", "item", + "fly", "action", + "flyto", "position", + "getassetdata", "item", + "getavatarappearancedata", "agent", + "getavatarclassifieddata", "item", + "getavatarclassifieds", "agent", + "getavatardata", "agent", + "getavatardisplayname", "agent", + "getavatargroupdata", "agent", + "getavatargroupsdata", "agent", + "getavatarpickdata", "item", + "getavatarpicks", "agent", + "getavatarpositions", "entity", + "getavatarsappearancedata", "entity", + "getavatarsdata", "entity", + "getavatarseat", "agents", + "getavatarsseats", "agents", + "getcameradata", "data", + "getconferencememberdata", "agent", + "getconferencemembersdata", "session", + "getconfigurationdata", "data", + "getcurrentgroupsdata", "data", + "getestatebanlist", "type", + "getestateinfodata", "data", + "getestatelist", "type", + "geteventinfodata", "id", + "getfrienddata", "agent", + "getgridregiondata", "data", + "getgroupaccountsummarydata", "target", + "getgroupdata", "target", + "getgrouplandinfodata", "target", + "getgroupmemberdata", "target", + "getgroupmembersdata", "target", + "getgroupsdata", "target", + "getheartbeatdata", "data", + "getinventorydata", "item", + "getinventorypath", "path", + "getmapavatarpositions", "region", + "getmemberroles", "target", + "getmembers", "target", + "getmembersoffline", "target", + "getmembersonline", "target", + "getmovementdata", "data", + "getnetworkdata", "data", + "getobjectdata", "data", + "getobjectlink", "item", + "getobjectmediadata", "data", + "getobjectpermissions", "item", + "getobjectsdata", "data", + "getparceldata", "data", + "getparceldwell", "position", + "getparcelinfodata", "data", + "getparcellist", "type", + "getparcelobjectresourcedetaildata", "data", + "getparcelobjectsresourcedetaildata", "data", + "getparticlesystem", "item", + "getprimitivedata", "data", + "getprimitiveflexibledata", "data", + "getprimitiveinventory", "item", + "getprimitiveinventorydata", "data", + "getprimitivelightdata", "data", + "getprimitiveowners", "position", + "getprimitivepayprices", "item", + "getprimitivephysicsdata", "data", + "getprimitivepropertiesdata", "data", + "getprimitivescripttext", "target", + "getprimitivesculptdata", "data", + "getprimitivesdata", "data", + "getprimitiveshapedata", "data", + "getprimitivetexturedata", "data", + "getprofiledata", "data", + "getprofilesdata", "data", + "getregiondata", "data", + "getregionparcellocations", "region", + "getregionparcelsboundingbox", "region", + "getregiontop", "type", + "getremoteparcelinfodata", "data", + "getrolemembers", "role", + "getrolepowers", "role", + "getroles", "target", + "getrolesmembers", "target", + "getscriptrunning", "entity", + "getselfdata", "data", + "getterrainheight", "region", + "gettitles", "target", + "getviewereffects", "effect", + "give", "item", + "grab", "item", + "grantfriendrights", "rights", + "http", "URL", + "importxml", "data", + "inventory", "action", + "invite", "agent", + "join", "target", + "jump", "action", + "leave", "target", + "login", "location", + "logs", "search", + "look", "position", + "lure", "agent", + "mapfriend", "agent", + "moderate", "agent", + "mqtt", "payload", + "mute", "agent", + "notice", "message", + "notify", "tag", + "nudge", "direction", + "objectdeed", "item", + "offerfriendship", "agent", + "parcelbuy", "position", + "parceldeed", "position", + "parceleject", "agent", + "parcelfreeze", "agent", + "parcelreclaim", "position", + "parcelrelease", "position", + "pay", "description", + "playgesture", "item", + "playsound", "item", + "primitivebuy", "item", + "readfile", "path", + "recompilescript", "target", + "removeconfigurationgroup", "target", + "removeitem", "item", + "renameitem", "item", + "replytofriendshiprequest", "agent", + "replytogroupinvite", "session", + "replytoinventoryoffer", "session", + "replytoscriptdialog", "dialog", + "replytoscriptpermissionrequest", "task", + "replytoteleportlure", "session", + "requestlure", "agent", + "restartregion", "action", + "returnprimitives", "agent", + "rez", "item", + "run", "action", + "scriptreset", "item", + "searchinventory", "pattern", + "setcameradata", "data", + "setconfigurationdata", "data", + "setestatecovenant", "item", + "setestatelist", "action", + "setgroupdata", "data", + "setinventorydata", "data", + "setmovementdata", "data", + "setobjectgroup", "item", + "setobjectmediadata", "data", + "setobjectpermissions", "permissions", + "setobjectposition", "position", + "setobjectrotation", "rotation", + "setobjectsaleinfo", "price", + "setobjectscale", "scale", + "setparceldata", "data", + "setparcellist", "agent", + "setprimitivedescription", "description", + "setprimitiveflags", "item", + "setprimitiveflexibledata", "data", + "setprimitiveinventorydata", "data", + "setprimitivelightdata", "data", + "setprimitivematerial", "material", + "setprimitivename", "name", + "setprimitiveposition", "position", + "setprimitiverotation", "rotation", + "setprimitivescale", "scale", + "setprimitivesculptdata", "data", + "setprimitiveshapedata", "data", + "setprimitivetexturedata", "data", + "setprofiledata", "data", + "setregionterrainheights", "data", + "setregionterraintextures", "data", + "setrolepowers", "role", + "setscriptrunning", "entity", + "setviewereffect", "item", + "simulatorpause", "region", + "simulatorresume", "region", + "sit", "item", + "softban", "avatars", + "startproposal", "text", + "tag", "title", + "teleport", "position", + "tell", "message", + "terminatefriendship", "agent", + "terrain", "data", + "toggleparcelflags", "flags", + "touch", "item", + "trashitem", "item", + "turn", "radians", + "turnto", "position", + "typing", "action", + "unwear", "wearables", + "updatenotecard", "data", + "updateprimitiveinventory", "item", + "updatescript", "data", + "upload", "data", + "walkto", "position", + "wear", "wearables", + "writefile", "data" + ]); + + message_key = getjs(message_mappings, [command]); + } + + if((message_key == "" || message_key == JSON_INVALID)) { + if(message != "") + msg = "Warning: discarded value."; + + } else if(message == "" && (message_key != "" && message_key != JSON_INVALID)) { + if(getjs(json, [message_key]) != JSON_INVALID) { + // already set manually; we're fine! + } else if(warn) { + msg = "Warning: was expected but not provided."; + } + } else { + json = setjs(json, [message_key], message); + } + + if(test) + msg = setjs(json, ["password"], "[REDACTED]"); + else { + if(bot_address != "") { + key post_body_pipe = llGenerateKey(); + pipe_write(post_body_pipe, json); + if(no_http_wait) { + http_post(bot_address, NULL_KEY, post_body_pipe, NULL_KEY); + } else { + key handle = llGenerateKey(); + key post_reply_pipe = llGenerateKey(); + + + waiting_queries += [ + src, ins, handle, outs, user, post_body_pipe, post_reply_pipe, bot_address + ]; + + pipe_open("p:" + (string)post_reply_pipe + " notify " + PROGRAM_NAME + " fetched " + (string)handle); + + _resolved = 0; + } + + /* + if(http_fetch_reply != "" || no_http_wait) { + key post_body_pipe = llGenerateKey(); + key handle = llGenerateKey(); + pipe_write(post_body_pipe, json); + if(!no_http_wait) { + http_post(bot_address, http_fetch_reply, post_body_pipe, handle); + _resolved = 0; + active_queries += [ + src, ins, handle, outs, user + ]; + } else { + http_post(bot_address, NULL_KEY, post_body_pipe, handle); + } + } else { + + + + // msg = "Run '" + PROGRAM_NAME + " -F' first."; + } + */ + } else if(bot == "") { + llInstantMessage(avatar, json); + } else { + llInstantMessage(bot, json); + } + } + } + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/db.lsl b/ARES/application/db.lsl new file mode 100644 index 0000000..61fa1c7 --- /dev/null +++ b/ARES/application/db.lsl @@ -0,0 +1,487 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Database Utility + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define FILE_STEP_SIZE 10 +#include +#include +#define CLIENT_VERSION "1.2.0" +#define CLIENT_VERSION_TAGS "beta" + +key dbload_q; + +string read_buffer; + +/* + replaces (string) number with (integer)number + and (string)\"number\" with (string)number +*/ +list process_keyname(list keyname) { + integer ki = count(keyname); + while(ki--) { + string ks = gets(keyname, ki); + if((string)((integer)ks) == ks) + keyname = alter(keyname, [(integer)ks], ki, ki); + else if(llOrd(ks, 0) == 0x22 && llOrd(ks, LAST) == 0x22) + keyname = alter(keyname, [substr(ks, 1, -2)], ki, ki); + + } + return keyname; +} + +string db_set(string section, list keyname, string new_value) { + string old_value = getdbl(section, keyname); + + setdbl(section, keyname, new_value); + + if(old_value == JSON_INVALID) + old_value = "(undefined)"; + else if(strlen(old_value) > 100) + old_value = "(" + (string)strlen(old_value) + " bytes)"; + + if(strlen(new_value) > 100) + new_value = "(" + (string)strlen(new_value) + " bytes)"; + + if(count(keyname) == 0) + return "overwrote ENTIRE section " + section + ": " + old_value + " --> " + new_value; + else + return "modified " + section + " setting " + concat(keyname, ".") + ": " + old_value + " --> " + new_value; +} + +string db_show_2(string value, integer sp) { + if(llOrd(value, 0) == 0x7b && llOrd(value, LAST) == 0x7d) { // { and } + list keys = jskeys(value); + integer kmax = count(keys); + if(kmax == 0) { + if(strlen(value) > 100) + return "(" + (string)strlen(value) + " bytes)"; + else + return value; + } + + string sps = " "; + if(sp) { + integer spi = sp; + while(spi--) + sps += " "; + } + + list vout; + integer ki = 0; + while(ki < kmax) { + string k = gets(keys, ki); + string subval = getjs(value, [k]); + if(llGetFreeMemory() > 2000) + vout += k + ": " + db_show_2(subval, sp + 1); + else + vout += k + ": (out of memory)"; + + ++ki; + } + + return "\n" + sps + concat(vout, "\n" + sps); + } else { + return value; + } +} + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + string action = gets(argv, 1); + integer argc = count(argv); + + string msg; + + integer allowed = sec_check(user, "database", outs, m, m); + if(allowed == DENIED) { + print(outs, user, "secondlife:///app/agent/" + (string)user + "/about is not authorized to access the database directly."); + return; + } else if(allowed == PENDING) { + return; + } + + list parts; + string section; + list keyname; + + integer user_rank = (integer)getdbl("security", ["user", user]); + + if(action == "clone") { + parts = splitnulls(gets(argv, 2), "."); + section = gets(parts, 0); + list parts2 = splitnulls(gets(argv, 3), "."); + string section2 = gets(parts2, 0); + if(section2 == "security" && user_rank != SEC_OWNER) { + jump security_fail; + } else { + string text = getdbl(section, process_keyname(delitem(parts, 0))); + if(text == JSON_INVALID) { + msg = "Could not clone from " + gets(argv, 2) + " to " + gets(argv, 3) + ": entry " + gets(argv, 2) + " does not exist."; + } else { + setdbl(section2, process_keyname(delitem(parts2, 0)), text); + integer tl = strlen(text); + msg = "Cloned data from " + gets(argv, 2) + " to " + gets(argv, 3) + "."; + if(tl != 1) + msg += " (" + (string)tl + " bytes)"; + else + msg += " (1 byte)"; + } + } + } else if(action == "load") { + if(user_rank != SEC_OWNER) { + msg = "Only owners may perform database imports."; + } else if(gets(argv, 2) == "cancel") { + msg = "Aborted."; + resolve((integer)getjs(tasks_queue, [dbload_q, FILE_R])); + task_end(dbload_q); + dbload_q = ""; + } else if(dbload_q) { + msg = "A database import is already in progress; run 'db load cancel' to abort."; + } else { + read_buffer = ""; + string dbload_fn = gets(argv, 2); + dbload_q = fopen(outs, ins, user, dbload_fn); + msg = "Importing " + dbload_fn; + } + } else if(action == "delete" || action == "remove") { + parts = splitnulls(gets(argv, 2), "."); + section = gets(parts, 0); + if(section == "security" && user_rank != SEC_OWNER) + jump security_fail; + keyname = process_keyname(delitem(parts, 0)); + + if(getdbl(section, keyname) != JSON_INVALID) { + deletedbl(section, keyname); + msg = "Deleted " + concat(keyname, ".") + " in section " + section; + } else { + msg = concat(keyname, ".") + " in section " + section + " not present"; + } + } else if(action == "toggle") { + parts = splitnulls(gets(argv, 2), "."); + section = gets(parts, 0); + if(section == "security" && user_rank != SEC_OWNER) + jump security_fail; + keyname = process_keyname(delitem(parts, 0)); + + integer val = !(integer)getdbl(section, keyname); + if(val) + msg = "Enabled "; + else + msg = "Disabled "; + + setdbl(section, keyname, (string)val); + + msg += gets(argv, 2); + + } else if(action == "drop") { + section = gets(argv, 2); + if(section == "security" && user_rank != SEC_OWNER) + jump security_fail; + llLinksetDataDelete(section); + msg = "Deleted ENTIRE section " + section; + + } else if(action == "set") { + parts = splitnulls(gets(argv, 2), "."); + section = gets(parts, 0); + if(section == "security" && user_rank != SEC_OWNER) + jump security_fail; + keyname = process_keyname(delitem(parts, 0)); + string new_value = concat(delrange(argv, 0, 2), " "); + msg = db_set(section, keyname, new_value); + + } else if(action == "append") { + parts = splitnulls(gets(argv, 2), "."); + section = gets(parts, 0); + if(section == "security" && user_rank != SEC_OWNER) + jump security_fail; + keyname = process_keyname(delitem(parts, 0)) + [JSON_APPEND]; + + string new_value = concat(delrange(argv, 0, 2), " "); + setdbl(section, keyname, new_value); + + msg = "Added '" + new_value + "' to section " + section + " key " + concat(keyname, "."); + } else if(action == "") { + list all_keys = llLinksetDataListKeys(0, 0); + integer aki = count(all_keys); + + // limited regex support can't handle ^(?!p:).+$ + while(aki--) + if(substr(gets(all_keys, aki), 0, 1) == "p:") + all_keys = delitem(all_keys, aki); + + msg = "Stored sections: " + concat(all_keys, ", ") + + "\nLSD status: " + + (string)llLinksetDataCountKeys() + " keys total, " + + (string)llLinksetDataAvailable() + " bytes free."; + } else if(action == "u" || action == "usage") { + // list all_keys = llLinksetDataListKeys(0, 0); + integer akmax; + integer aki = akmax = llLinksetDataCountKeys(); // count(all_keys); + list fields; + while(aki--) { + string kn = gets(llLinksetDataListKeys(aki, 1), 0); + // msg += " - " + kn + " " + (string)strlen(llLinksetDataRead(kn)) + "\n"; + fields += [kn, strlen_byte_inline(llLinksetDataRead(kn))]; + } + fields = llListSortStrided(fields, 2, 1, TRUE); + aki = akmax; + while(aki--) { + integer L = geti(fields, (aki << 1) + 1); + string line = " - " + gets(fields, (aki << 1)) + " " + (string)L; + if(L != 1) + line += " bytes"; + else + line += " byte"; + // fields = alter(fields, [line], (aki << 1), (aki << 1) + 1); + fields = delrange(fields, (aki << 1), (aki << 1) + 1); + print(outs, user, line); + llSleep(0.05); + } + + msg = /*concat(fields, "\n") + + */"\nLSD status: " + + (string)llLinksetDataCountKeys() + " keys total, " + + (string)llLinksetDataAvailable() + " bytes free."; + } else if(action == "show") { + parts = splitnulls(gets(argv, 2), "."); + section = gets(parts, 0); + keyname = process_keyname(delitem(parts, 0)); + // msg = db_show(section, keyname); + jump db_show; + } else if(action == "json") { + parts = splitnulls(gets(argv, 2), "."); + section = gets(parts, 0); + keyname = process_keyname(delitem(parts, 0)); + msg = getdbl(section, keyname); + if(msg == JSON_INVALID) + msg = "(undefined)"; + } else { + parts = splitnulls(action, "."); + section = gets(parts, 0); + if(llLinksetDataRead(section) != "") { + keyname = process_keyname(delitem(parts, 0)); + if(argc > 2) { + if(count(keyname) > 0) { + if(section == "security" && user_rank != SEC_OWNER) + jump security_fail; + string new_value = concat(delrange(argv, 0, 1), " "); + msg = db_set(section, keyname, new_value); + } else { + msg = "Did you mean to do that? For safety, you must use 'db set' to replace an entire section."; + } + } else { + // msg = db_show(section, keyname); + jump db_show; + } + } else { + msg = PROGRAM_NAME + ": No section: " + action + "; see 'help db' for syntax."; + } + } + + jump security_pass; + @db_show; + // (section and keyname are already populated) + string value; + if(count(keyname)) { + value = getdbl(section, keyname); + } else { + value = llLinksetDataRead(section); + } + + if(value == JSON_INVALID) { + value = "(undefined)"; + } else if(strlen(value) > 800 && count(jskeys(value))) { + value = "(keys only) " + concat(jskeys(value), ", "); + } else { + value = db_show_2(value, 0); + } + + if(!count(keyname)) + value = "(entire section) " + value; + + msg = concat([section] + keyname, ".") + " " + value; + jump security_pass; + @security_fail; + msg = "Only the unit's owner may directly modify the 'security' section of the database."; + @security_pass; + if(msg != "") + print(outs, user, msg); + return; + + } else if(n == SIGNAL_NOTIFY) { + if(m == PROGRAM_NAME + " file") { + if(ins == dbload_q) { + user = getjs(tasks_queue, [dbload_q, FILE_USER]); + outs = getjs(tasks_queue, [dbload_q, FILE_OUTS]); + string fn = getjs(tasks_queue, [dbload_q, FILE_NAME]); + string unit = getjs(tasks_queue, [dbload_q, FILE_UNIT]); + + string result = fread(dbload_q); + string msg; + + list parts; + string section; + list keyname; + + if(result == JSON_FALSE) { + msg = "No file: " + fn; + dbload_q = ""; + } else if(result == JSON_TRUE) { + msg = "Loading " + getjs(tasks_queue, [dbload_q, FILE_LENGTH]) + " " + getjs(tasks_queue, [dbload_q, FILE_UNIT]); + } else { + list lines = split(result, "\n"); + if(unit != "l") { + lines = alter(lines, [read_buffer + gets(lines, 0)], 0, 0); + if(getjs(tasks_queue, [dbload_q]) == JSON_INVALID) { + // the end of the file; last line can be trusted + read_buffer = ""; + } else { + read_buffer = gets(lines, LAST); + lines = delitem(lines, LAST); + } + } + integer li = 0; + integer lmax = count(lines); + while(li < lmax) { + string m = llStringTrim(gets(lines, li++), STRING_TRIM_HEAD); + if(m != "" && substr(m, 0, 0) == "#") { + // skip comments + } else { + list argv = splitnulls(m, " "); + integer mode = 0; // set + string mode_name = llToUpper(gets(argv, 0)); + if(mode_name == "DELETE" || mode_name == "-") { + mode = 1; + argv = delitem(argv, 0); + } else if(mode_name == "CREATE" || mode_name == "+") { + mode = 2; + argv = delitem(argv, 0); + } else if(mode_name == "APPEND" || mode_name == "++") { + mode = 3; + argv = delitem(argv, 0); + // echo(m); + } else if(mode_name == "MERGE" || mode_name == "+=") { + mode = 5; + argv = delitem(argv, 0); + } else if(mode_name == "SET" || mode_name == "=") { + mode = 0; + argv = delitem(argv, 0); + } else if(mode_name == "DROP") { + mode = 4; + argv = delitem(argv, 0); + } + + string varkey = gets(argv, 0); + if(varkey != "") { + string varvalue = concat(delitem(argv, 0), " "); + keyname = split(varkey, "."); + section = gets(keyname, 0); + keyname = process_keyname(delitem(keyname, 0)); + string section_data = llLinksetDataRead(section); + if(mode == 1 && section_data == "") { + // nothing to delete + } else if(mode == 4) { + if(section_data != "") { + msg += "Deleted ENTIRE section " + section + "\n"; + } + llLinksetDataDelete(section); + } else { + if(section_data == "") { + section_data = "{}"; + msg += "Created section: " + section + "\n"; + } + + if(mode == 1) + varvalue = JSON_DELETE; + else if(mode == 3) + keyname += [JSON_APPEND]; + + if(mode == 2 && getjs(section_data, keyname) != JSON_INVALID) { + // value already exists; not replacing + } else { + // perform the update in memory: + if(mode == 5) { + list keys = jskeys(varvalue); + integer ki = count(keys); + while(ki--) { + string k = gets(keys, ki); + section_data = setjs(section_data, keyname + [k], getjs(varvalue, [k])); + } + } else { + section_data = setjs(section_data, keyname, varvalue); + } + // apply the update if successful: + if(section_data != JSON_INVALID) { + // only commit on successful update: + llLinksetDataWrite(section, section_data); + } else if(mode != 1) { + msg += "Operation failed: " + m + "\n"; + } // else DELETEs fail silently + } + } + } + } + } + + if(getjs(tasks_queue, [dbload_q]) == JSON_INVALID) { + msg += "Finished database import of " + fn + + "\nLSD status: " + (string)llLinksetDataCountKeys() + " sections, " + (string)llLinksetDataAvailable() + " bytes free."; + dbload_q = ""; + } + } + + if(msg != "") + print(outs, user, msg); + } else { + echo("[" + PROGRAM_NAME + "] file data offered via unexpected pipe: " + (string)ins); + } + } + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/define.lsl b/ARES/application/define.lsl new file mode 100644 index 0000000..1b13cb7 --- /dev/null +++ b/ARES/application/define.lsl @@ -0,0 +1,154 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * define Dictionary Lookup + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "1.1.1" +#define CLIENT_VERSION_TAGS "release" + +string DEFAULT_DOMAIN = "en.wikipedia.org"; + +#define QUERY_STRING "/w/api.php?action=query&format=json&prop=extracts&redirects=1&utf8=1&formatversion=2&exchars=900&explaintext=1&exsectionformat=plain&titles=" + +key define_receive_pipe; +string waiting_queries = "{}"; +string queries_in_progress = "{}"; + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + string msg = ""; + if(argc == 1) { + msg = "Syntax: " + PROGRAM_NAME + " [-w ] \n\nLooks up on the [https://www.mediawiki.org MediaWiki] at (default: " + DEFAULT_DOMAIN + ").\n\nThe target wiki must have the [https://www.mediawiki.org/wiki/Extension:TextExtracts TextExtracts API extension] installed."; + } else { + string domain; + if(gets(argv, 1) == "-w") { + domain = gets(argv, 2); + argv = delrange(argv, 1, 2); + } else { + domain = DEFAULT_DOMAIN; + } + string question = concat(delitem(argv, 0), " "); + + if(!~strpos(domain, "://")) + domain = "https://" + domain; + + key handle = llGenerateKey(); + string query = jsarray([domain, question, outs, ins, user, _resolved]); + _resolved = 0; + + llSetMemoryLimit(0x10000); + if(define_receive_pipe) { + queries_in_progress = setjs(queries_in_progress, [handle], query); + http_get(domain + QUERY_STRING + llEscapeURL(question), define_receive_pipe, handle); + } else { + define_receive_pipe = llGenerateKey(); + waiting_queries = setjs(waiting_queries, [handle], query); + pipe_open(["p:"+(string)define_receive_pipe + " notify " + PROGRAM_NAME + " response"]); + } + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_NOTIFY) { + list argv = split(m, " "); + string cmd = gets(argv, 1); + if(cmd == "pipe") { + list queries = jskeys(waiting_queries); + integer qi = 0; + integer qmax = count(queries); + while(qi < qmax) { + key handle = gets(queries, qi); + string query = getjs(waiting_queries, [(string)handle]); + waiting_queries = setjs(waiting_queries, [(string)handle], JSON_DELETE); + + string domain = getjs(query, [0]); + string question = getjs(query, [1]); + queries_in_progress = setjs(queries_in_progress, [(string)handle], query); + + http_get(domain + QUERY_STRING + llEscapeURL(question), define_receive_pipe, handle); + ++qi; + } + } else if(cmd == "response") { + string query = getjs(queries_in_progress, [(string)user]); + if(query != JSON_INVALID) { + key o_outs = getjs(query, [2]); + key o_ins = getjs(query, [3]); + key o_user = getjs(query, [4]); + integer o_rc = (integer)getjs(query, [5]); + + string buffer; + pipe_read(ins, buffer); + string fieldset = getjs(buffer, ["query", "pages", 0]); + string field; + if(fieldset != JSON_INVALID) { + if(getjs(fieldset, ["missing"]) == JSON_TRUE) { + field = getjs(fieldset, ["title"]) + " does not exist (" + getjs(query, [0]) + "/)"; + } else { + field = getjs(fieldset, ["extract"]); + } + } else { + field = buffer; + } + + print(o_outs, o_user, field); + // resolve_io(o_rc, o_outs, o_ins); + queries_in_progress = setjs(queries_in_progress, [(string)user], JSON_DELETE); + resolve_i(o_rc, o_ins); + } + + if(queries_in_progress == "{}") { + pipe_close(define_receive_pipe); + define_receive_pipe = ""; + llSetMemoryLimit(0x2000); + } + } + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + llSetMemoryLimit(0x2000); + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/filter.lsl b/ARES/application/filter.lsl new file mode 100644 index 0000000..16c92b6 --- /dev/null +++ b/ARES/application/filter.lsl @@ -0,0 +1,905 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Text Filter Utilities (Standard Bundle) + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 1 (ASCL-i). It is offered to you on a limited basis to + * facilitate modification and customization. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION ARES_VERSION +#define CLIENT_VERSION_TAGS ARES_VERSION_TAGS + +list filters = [ + // filter layer + "censor", 0, + "replace", 0, + "slang", 0, + "nonverbal", 1, + "translate", 1, + "stutter", 2, + "serpentine", 2, + "lisp", 3, + "mumble", 4, + "caps", 5, + "rot13", 5, + "slow", 5, + "bimbo", 6, + "superscript", 6, + "corrupted", 7, + "glitch", 7 +]; + +string f_censor(string message, string flags) { + if(flags == "") { + echo("No censor dictionary specified. See 'help censor'"); + return message; + } else { + list words = js2list(getdbl("filter", ["censor", flags])); + // echo("Censor word list: " + (string)concat(words, ", ")); + integer wi = 0; + integer wmax = count(words); + string new_message = message; + string search_space = llToLower(new_message); + + while(wi < wmax) { + string word = gets(words, wi++); + if(word == JSON_INVALID) { + echo("Censor dictionary not found: " + flags + ". See 'help censor'"); + return message; + } + string replacement; + integer ci; + integer wlen = 0; + while(~(ci = strpos(search_space, word))) { + if(!wlen) { + wlen = strlen(word) - 1; + replacement = substr("********************************", 0, wlen); + } + new_message = llInsertString(delstring(new_message, ci, ci + wlen), ci, replacement); + search_space = llInsertString(delstring(search_space, ci, ci + wlen), ci, replacement); + + // echo("Replaced " + word + " with " + replacement + ": " + new_message); + } + // new_message = replace(new_message, word, replacement); + } + return new_message; + } +} + +string f_replace(string message, string dictionary_name) { + string dictionary = getdbl("filter", ["replace", dictionary_name]); + if(dictionary == JSON_INVALID) { + echo("[filter] warning: replacement dictionary " + dictionary_name + " does not exist. See 'help replace'"); + return message; + } + + list EOS = [".", "?", "!"]; + list separators = EOS + [" ", ",", "\"", "'", "-", ":", ";", "(", ")", "*", "~", "/", "@", "–", "—"]; + list tokens = llParseString2List(message, [], separators); // not sure if this would be better as llParseStringKeepNulls() + list cases; + list ltokens; + integer tmax = count(tokens); + + list headwords = jskeys(dictionary); + integer ti = 0; + while(ti < tmax) { + string t = gets(tokens, ti); + string lt = llToLower(t); + ltokens += lt; + + integer original_case; + + if(lt == t) { + original_case = 0; // plain + } else if(t == "I") { + original_case = 3; // unique + } else if(delstring(lt, 0, 0) == delstring(t, 0, 0)) { + original_case = 1; // first-letter + } else if(llToUpper(t) == t) { + original_case = 2; // all-caps + } else { + original_case = 3; + } + + integer di = index(headwords, lt); + if(~di) { + string replacement = getjs(dictionary, [lt]); + if(llToLower(replacement) == replacement) { // only alter case for lower-case replacements + if(original_case == 3) { + string prequel = gets(ltokens, ti - 2); + if(contains(EOS, prequel) || prequel == "") // are we exactly two tokens after the end of a sentence? + original_case = 1; + else + original_case = 0; + } + + if(original_case == 1) { + replacement = llToUpper(substr(replacement, 0, 0)) + delstring(replacement, 0, 0); + } else if(original_case == 2) { + replacement = llToUpper(replacement); + } + } else if(original_case == 2) { // actually, let's pass on all-caps so shouting looks right + replacement = llToUpper(replacement); + } + tokens = alter(tokens, [replacement], ti, ti); + } + ++ti; + } + + message = concat(tokens, ""); + integer hwi = count(headwords); + while(hwi--) { + string hw = gets(headwords, hwi); + if(~strpos(hw, "'")) { + string replacement = getjs(dictionary, [hw]); + message = replace(message, hw, replacement); + } + } + + return message; +} + +string f_slang(string message, string flags) { + if(flags == "") { + echo("Warning: no slang table specified; see 'help slang'."); + return message; + } + + string dictionary = getdbl("filter", ["slang", flags]); + if(dictionary == JSON_INVALID) { + echo("Warning: slang table " + flags + " does not exist; see 'help slang'."); + return message; + } + + list prefixes = js2list(getjs(dictionary, ["pre"])); integer pc = count(prefixes); + list infixes = js2list(getjs(dictionary, ["mid"])); integer ic = count(infixes); + list suffixes = js2list(getjs(dictionary, ["post"])); integer sc = count(suffixes); + + list sentences = llParseStringKeepNulls(message + " ", [], [". ", "? ", "! "]); + // echo(concat(sentences, "|")); + integer si = count(sentences); + while(si--) { + string sentence = gets(sentences, si); + if(strlen(sentence) > 2) { + list words = splitnulls(sentence, " "); + integer wc = count(words); + if(wc > 1) { + string prefix = gets(prefixes, (integer)llFrand(pc)); + string suffix = gets(suffixes, (integer)llFrand(sc)) + " "; + string infix = gets(infixes, (integer)llFrand(ic)); + integer wi = (integer)llFrand(wc); + words = alter(words, [llToLower(gets(words, 0))], 0, 0); + words = [prefix] + llListInsertList(words, [infix], wi) + [suffix]; + } + + sentences = alter(sentences, [concat(words, " ")], si, si + 1); + } + } + return llStringTrim(concat(sentences, ""), STRING_TRIM); +} + +/* +string transfer_capitals(string source, string dest) { + string result; + integer ci = 0; + integer cimax = strlen(source); + integer d = strlen(dest); + integer c; + while(ci < cimax) { + integer a = llOrd(source, ci); + integer b = llOrd(dest, ci); + if(a < 0x80 && b < 0x80) { // if ASCII, work with integers + if(a > 0x40 && a < 0x5b) { + ++c; + if(b > 0x60 && b < 0x7b) { + result += llChar(b - 0x20); + } else { + result += llChar(b); + } + } else { + result += llChar(b); + } + } else { // otherwise fall back to llToUpper()/llToLower() + string ac = llChar(a); + string bc = llChar(b); + string caps = llToUpper(ac); + string small = llToLower(ac); + if(caps != small && ac == caps) { + ++c; + result += llToUpper(bc); + } else { + result += bc; + } + } + ++ci; + } + + // deal with extended words: + if(d > cimax) { + if(c == cimax && c > 1) + result += llToUpper(substr(dest, cimax, d)); + else + result += substr(dest, cimax, d); + } + + // yield: + return result; +} */ + +string last_nonverbal_flags = "m o o"; +string f_nonverbal(string message, string flags) { + if(flags == "") + flags = last_nonverbal_flags; + else + last_nonverbal_flags = flags; + + list parts = split(llToLower(flags), " "); + string prefix = gets(parts, 0); integer prelen = strlen(prefix); + string infix = gets(parts, 1); integer inlen = strlen(infix); + string suffix = gets(parts, 2); integer suflen = strlen(suffix); + + string out; + + integer fmax = strlen(message); + integer fi = 0; + string word; + while(fi < fmax) { + string char = substr(message, fi, fi); + if(llToUpper(char) != llToLower(char)) { + word += char; + } else { + if(word) { + integer wmax = strlen(word); + integer wi = 0; + string raword; + if(wmax <= prelen + inlen + suflen) { + raword = prefix + infix + suffix; + } else { + raword = prefix; + integer jl = wmax - prelen - suflen; + while(jl > 0) { + raword += infix; + jl -= inlen; + } + raword += suffix; + } + if(llToUpper(word) == word) + raword = llToUpper(raword); + else if(llToLower(word) != word) + raword = llToUpper(substr(raword, 0, 0)) + substr(word, 1, LAST); + /* else + word = raword; */ + // word = transfer_capitals(word, raword); + + out += raword; + word = raword = ""; + } + out += char; + } + ++fi; + } + + return out; +} + +#define TRANSLATION_URL "http://mymemory.translated.net/api/get?q=" + llEscapeURL(message) + "&langpair=" + replace(flags, ":", "|") + "&de=" + (string)avatar + "@lsl.secondlife.com" +key translate_pipe; +list translation_queue; + +f_translate(string message, string flags, key outs, key ins, key user, integer rc) { + if(flags == "") { + echo("Cannot translate; no translation language pair set: " + message + ". See 'help translate'"); + print(outs, user, message); + // resolve_io(rc, outs, ins); + resolve_i(rc, ins); + return; + } + + key handle = llGenerateKey(); + translation_queue += [message, flags, handle, outs, ins, user, rc]; + + if(translate_pipe) { + #ifdef DEBUG + echo("sending message " + message + " for translation immediately"); + #endif + http_get(TRANSLATION_URL, translate_pipe, handle); + } else { + #ifdef DEBUG + echo("opening translation pipe"); + #endif + translate_pipe = llGenerateKey(); + pipe_open(["p:" + (string)translate_pipe + " notify " + PROGRAM_NAME + " translation"]); + } +} + +/* string match_case(string question, string answer) { + if(llToLower(answer) == answer) { + return llToLower(question); + } else { + return llToUpper(question); + } +} */ + +string f_stutter(string message, string flags) { + float stutter_level = (float)flags; + if(flags == "" || stutter_level == 0) + return message; + + list tokens = [" "] + llParseString2List(message, [], [" ", "a", "e", "i", "o", "u", "y", "A", "E", "I", "O", "U", "Y"]) + [" "]; + integer tc = count(tokens); + integer ti = tc - 1; + while(ti--) { + if(gets(tokens, ti) == " ") { + string nt = gets(tokens, ti + 1); + string nt2 = gets(tokens, ti + 2); + if(llFrand(100) <= stutter_level) { + string pnt = substr(nt, 0, 0); + if(llToLower(pnt) != llToUpper(pnt)) { + string ntx; + if(llToLower(nt2) == nt2) + ntx = llToLower(nt); + else + ntx = llToUpper(nt); + + if(~strpos("aeiouy", llToLower(nt))) { // starting with a vowel + nt += "-" + ntx; + if(llFrand(100) <= stutter_level) { + nt += "-" + ntx; + if(llFrand(100) <= stutter_level) { + nt += "-" + ntx; + } + } + tokens = alter(tokens, [nt], ti + 1, ti + 1); + } else if(nt2 != " ") { // consonants before a vowel + if(llFrand(100) < 50) // extend consonants only + nt2 = "-" + nt; + + nt += nt2 + "-" + ntx; + if(llFrand(100) <= stutter_level) { + nt += nt2 + "-" + ntx; + if(llFrand(100) <= stutter_level) { + nt += nt2 + "-" + ntx; + } + } + tokens = alter(tokens, [nt], ti + 1, ti + 1); + } + } + } + } + } + + return substr(concat(tokens, ""), 1, -2); +} + +string f_serpentine(string message, string flags) { + if(flags == "") flags = "100"; + float serpentine_strength = 0.01 * (float)flags; + + if(serpentine_strength > 0.0) { + integer i = 0; + integer j = 0; + while(i < strlen(message)) { + string e = substr(message, i, i); + string f = substr(message, i, i + 1); + if(f == "ci" || f == "CI" || f == "CE" || f == "ce") { + e = e + e; + j = 1; + } else if(f == "SH") { + e = f; + } else if(f == "sh" || f == "Sh") { + e = "sh"; + } else if(e == "s" || e == "z" || e == "S" || e == "Z" || e == "ß") { + e = e + e; + j = 1; + } else + e = ""; + + if(llFrand(1.0) < serpentine_strength && e != "") { + message = substr(message, 0, i) + e + substr(message, i + 1, LAST); + ++i; + if(j) { + j = 0; + ++i; + } + } else { + ++i; + } + } + } + + return message; +} + +string f_lisp(string message, string flags) { + if(flags == "") flags = "100"; + float lisp_strength = 0.01 * (float)flags; + + integer i = 0; + integer j = 0; + while(i <= strlen(message) - 1) { + string e = substr(message, i, i); + string f = substr(message, i, i+1); + string g = substr(message, i+1, i+1); + if(g == "") g = e; + + j = 1; + if(e == "s" || e == "z" || e == "ß") { + e = "th"; + j = 0; + } else if(f == "ci" || f == "ce" || f == "sh" || f == "cc" || f == "zh") { + e = "th"; + } else if(f == "Ci" || f == "Ce" || f == "Sh" || f == "Cc" || f == "Zh" || (llToLower(g) == g && ( e == "S" || e == "Z"))) { + e = "Th"; + } else if(f == "CI" || f == "CE" || f == "SH" || f == "CC" || f == "ZH" || e == "S" || e == "Z") { + e = "TH"; + } else { + e = ""; + j = 0; + } + + if(llFrand(1.0) < lisp_strength && e != "") { + if(strlen(message) == 1) + message = e; + else if(i == strlen(message) - 1) + message = substr(message, 0, i - 1) + e; + else if(i > 0) + message = substr(message, 0, i - 1) + e + substr(message, i + 1, -1); + else + message = e + substr(message, i + 1, -1); + + ++i; + if(j) { + i += j; + } + } else { + ++i; + } + } + + return message; +} + +// based on fp_gagvox from the NS Ballgag + +list orig_4 = ["tion", "tial"]; +list repl_4 = ["fhion", "fho"]; + +list orig_3 = ["sch"]; +list repl_3 = ["fh"]; + +list orig_2 = ["ss", "ci", "th", "sh", "ge", "ch"]; +list repl_2 = ["ffh", "fi", "w", "hw", "wye", "tfh"]; + +list orig_1 = ["s", "l", "t", "r", "p", "b", "j", "z"]; +list repl_1 = ["fh", "w", "ht", "w", "k", "w", "dh", "v"]; + +#define MIXED 0 +#define LOWER 1 +#define UPPER 2 + +string f_mumble(string message, string flags) { + integer i = 0; + integer L = strlen(message); + message = message + " "; + + list out_tokens; + + while(i < L) { + string src_4 = substr(message, i, i + 3); + string src_3 = substr(message, i, i + 2); + string src_2 = substr(message, i, i + 1); + string src_1 = substr(message, i, i + 0); + + integer case = MIXED; + + if(llToLower(src_2) == src_2) + case = LOWER; + else if(llToUpper(src_2) == src_2) + case = UPPER; + + integer i4 = index(orig_4, llToLower(src_4)); + integer i3 = index(orig_3, llToLower(src_3)); + integer i2 = index(orig_2, llToLower(src_2)); + integer i1 = index(orig_1, llToLower(src_1)); + integer ll = 0; + + string out_token; + + if(~i4) { + out_token = gets(repl_4, i4); + i += 4; + } else if(~i3) { + out_token = gets(repl_3, i3); + i += 3; + } else if(~i2) { + out_token = gets(repl_2, i2); + i += 2; + } else if(~i1) { + out_token = gets(repl_1, i1); + i += 1; + if(llToUpper(src_1) == src_1 && strlen(out_token) == 1) { + ll = 1; + out_token = llToUpper(out_token); + } + } else { + out_token = src_1; + i += 1; + ll = 1; + } + + if(ll || case == LOWER) + out_tokens += out_token; + else if(case == UPPER) + out_tokens += llToUpper(out_token); + else if(case == MIXED) + out_tokens += llToUpper(substr(out_token, 0, 0)) + substr(out_token, 1, -1); + } + + return concat(out_tokens, ""); +} + +string f_caps(string message, string flags) { + return llToUpper(message); +} + +string f_rot13(string message, string flags) { + string out; + integer fmax = strlen(message); + integer fi; + while(fi < fmax) { + integer c = llOrd(message, fi++); + if(c > 0x40 && c < 0x5b) { // after @ and before [ + c -= 13; + if(c < 0x41) + c += 26; + } else if(c > 0x60 && c < 0x7b) { // after ` and before { + c -= 13; + if(c < 0x61) + c += 26; + } + out += llChar(c); + } + return out; +} + +string f_slow(string message, string flags) { + // echo("FLAGS = " + flags); + return replace(message, " ", substr(" ", 0, llAbs((integer)flags))); +} + +string f_bimbo(string message, string flags) { + string intake = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + string replacement = "αв¢∂єƒﻭнιנкℓмησρ۹яѕтυνωχуչαв¢∂єƒﻭнιנкℓмησρ۹яѕтυνωχуչ"; + string output = ""; + integer imax = strlen(message); + integer i; + while(i < imax) { + string c = substr(message, i, i); + integer j = strpos(intake, c); + if(~j) { + // echo(c + " is letter #" + (string)j + " and maps to " + substr(replacement, j, j)); + output += substr(replacement, j, j); + } else { + // echo(c + " is not a letter"); + output += c; + } + ++i; + } + return output; +} + +string f_superscript(string message, string flags) { + string intake = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-=~"; + string replacement = "ᴬᴮᶜᴰᴱᶠᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾ۹ᴿˢᵀᵁⱽᵂˣʸᶻᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖ۹ʳˢᵗᵘᵛʷˣʸᶻ⁰¹²³⁴⁵⁶⁷⁸⁹⁻⁼˜"; + string output = ""; + integer imax = strlen(message); + integer i; + while(i < imax) { + string c = substr(message, i, i); + integer j = strpos(intake, c); + if(~j) { + // echo(c + " is letter #" + (string)j + " and maps to " + substr(replacement, j, j)); + output += substr(replacement, j, j); + } else { + // echo(c + " is not a letter"); + output += c; + } + ++i; + } + return output; +} + +list ft_dropout_chars = ["░", "▒", "▓", "█"]; + +string f_corrupted(string message, string flags) { + float dropout_strength = 0.01 * (float)flags; + if(dropout_strength == 0) + return message; + + integer imax = strlen(message); + string oo; + + integer i = 0; + for(; i < imax; ++i) { + string c = substr(message, i, i); + if(llFrand(1) < dropout_strength && llToUpper(c) != llToLower(c)) { + oo += gets(ft_dropout_chars, (integer)llFrand(4)); + } else + oo += substr(message, i, i); + } + + return oo; +} + +#define GLITCH_MULTIPLIER 1 +string f_glitch(string message, string flags) { + if(flags == "") flags = "10"; + float glitch_level = 0.01 * (float)flags; + + list junkchars = ["̆","̎","̾","͋","̚","͠","͢","̨","̴","̶","̷","̡","̜","̼","̖","̞","̤","̰","͌","̂"]; + + if(glitch_level > 0.009) { + integer k = strlen(message); + integer j = strlen_byte(message); + integer i = (integer)((float)k * glitch_level * GLITCH_MULTIPLIER); + // llOwnerSay("Adding " + (string)i + " glitches to string of " + (string)strlen(message) + " characters."); + while(i > 0 && j < 1022) { + string a = gets(junkchars, (integer)llFrand(20)); + string b = gets(junkchars, (integer)llFrand(20)); + + message = llInsertString(llInsertString( + message, + (integer)(llFrand((k) - 1)) + 1, a), + (integer)(llFrand((k += 2) - 3)) + 1, b); + + j += strlen_byte(a + b); + // llOwnerSay(">>" + message); + --i; + } + } + + return message; +} + +list activated_filters = []; + +key session; + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] signal " + (string)n + " from " + (string)src + ": " + m + " ("+(string)outs+":"+(string)ins+":"+(string)user +")"); + #endif + list argv = split(m, " "); + integer argc = count(argv); + if(argc == 1) { + print(outs, user, "[" + PROGRAM_NAME + "] see 'help " + PROGRAM_NAME + "'"); + } else { + string filter = gets(argv, 1); + string action = gets(argv, 2); + + if(filter == "install" || filter == "remove") { + string fconf = getdbl("vox", ["filter"]); + + integer fi = count(filters); + string msg; + + if(filter == "install") + msg = "Installed " ; + else + msg = "Removed "; + + if(fi > 2) + msg += (string)(fi / 2) + " filters"; + else + msg += "1 filter"; + + while(fi >= 0) { + fi -= 2; + string fn = gets(filters, fi); + integer layer = geti(filters, fi + 1); + + if(filter == "install") + fconf = setjs(fconf, [fn], jsarray([PROGRAM_NAME + " " + fn, layer])); + else if(getjs(fconf, [fn]) != JSON_INVALID) + fconf = setjs(fconf, [fn], JSON_DELETE); + } + + setdbl("vox", ["filter"], fconf); + + print(outs, user, "[" + PROGRAM_NAME + "] " + msg); + + } else if(action == "activate" || action == "deactivate") { + integer new_status = (action == "activate"); + integer fi = index(filters, filter); + string msg; + + if(~fi) { + integer current_status = index(activated_filters, filter); + if(new_status && !~current_status) { + if(activated_filters == []) { + task_begin(session = llGenerateKey(), ""); + } + activated_filters += filter; + } else if(~current_status && !new_status) { + activated_filters = delitem(activated_filters, current_status); + if(activated_filters == []) { + task_end(session); + } + + if(filter == "translate" && translate_pipe != "") { + pipe_close(translate_pipe); + translate_pipe = ""; + } + } + } else { + echo("[" + PROGRAM_NAME + "] No filter to " + action + ": " + filter); + } + } else { + // calling syntax: echo msg | filter + + string flags = concat(delrange(argv, 0, 1), " "); + + string message; + pipe_read(ins, message); + + // echo(" -- filter in: " + message + " (from pipe " + (string)ins + ")"); + + if(filter == "translate") { + f_translate(message, flags, outs, ins, user, _resolved); + _resolved = 0; + // message = f_translate(message, flags); + } else { + if(filter == "censor") { + message = f_censor(message, flags); + } else if(filter == "replace") { + message = f_replace(message, flags); + } else if(filter == "nonverbal") { + message = f_nonverbal(message, flags); + } else if(filter == "rot13") { + message = f_rot13(message, flags); + } else if(filter == "glitch") { + message = f_glitch(message, flags); + } else if(filter == "corrupted") { + message = f_corrupted(message, flags); + } else if(filter == "stutter") { + message = f_stutter(message, flags); + } else if(filter == "serpentine") { + message = f_serpentine(message, flags); + } else if(filter == "lisp") { + message = f_lisp(message, flags); + } else if(filter == "mumble") { + message = f_mumble(message, flags); + } else if(filter == "caps") { + message = f_caps(message, flags); + } else if(filter == "bimbo") { + message = f_bimbo(message, flags); + } else if(filter == "superscript") { + message = f_superscript(message, flags); + } else if(filter == "slang") { + message = f_slang(message, flags); + } else if(filter == "slow") { + message = f_slow(message, flags); + } else { + echo("[" + PROGRAM_NAME + "] Filter '" + filter + "' unrecognized."); + #ifdef DEBUG + echo("argv 1 " + gets(argv, 1)); + echo("message '" + m + "'"); + echo("argc " + (string)count(argv)); + #endif + } + + // echo(" -- filter out: " + message + " (to pipe " + (string)outs + ")"); + print(outs, user, message); + } + } + } + } else if(n == SIGNAL_NOTIFY) { + list argv = split(m, " "); + string action = gets(argv, 1); + + // translation_queue: [0: message, 1: flags, 2: handle, 3: outs, 4: ins, 5: user, 6: rc]; + + if(action == "pipe" && ins == translate_pipe) { + string message = gets(translation_queue, 0); + string flags = gets(translation_queue, 1); + key handle = gets(translation_queue, 2); + #ifdef DEBUG + echo("got translation pipe; running " + TRANSLATION_URL); + #endif + http_get(TRANSLATION_URL, translate_pipe, handle); + } else if(action == "translation") { + string buffer; + pipe_read(ins, buffer); + integer hi = index(translation_queue, user); + if(~hi) { + integer mi = hi - 2; + // resolve_io(geti(translation_queue, mi + 6), gets(translation_queue, mi + 3), gets(translation_queue, mi + 4)); + resolve_i(geti(translation_queue, mi + 6), gets(translation_queue, mi + 4)); + + buffer = getjs(buffer, ["responseData"]); + #ifdef DEBUG + echo("received translation server response data: " + buffer); + #endif + + integer jj; + if(~(jj = strpos(buffer, "\\u"))) { + string remainder = substr(buffer, jj, -1); + integer o = strpos(remainder, "\""); + + while(~jj) { + integer code = (integer)("0x" + substr(buffer, jj + 2, jj + 5)); + buffer = llInsertString(delstring(buffer, jj, jj + 5), jj, llChar(code)); + + jj = strpos(buffer, "\\u"); + } + } + + string message = getjs(buffer, ["translatedText"]); + + print(gets(translation_queue, mi + 3), gets(translation_queue, mi + 5), message); + translation_queue = delrange(translation_queue, mi, mi + 6); + } + #ifdef DEBUG + else + echo("got translation unexpectedly: " + buffer); + #endif + + if(!count(translation_queue)) { + pipe_close(translate_pipe); + translate_pipe = ""; + } + } + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + list active_filters = jskeys(getdbl("vox", ["active"])); + integer ai = count(active_filters); + + while(ai--) { + string filter = gets(active_filters, ai); + integer fi = index(filters, filter); + + if(~fi) { + if(activated_filters == []) { + task_begin(session = llGenerateKey(), ""); + } + activated_filters += filter; + } + } + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/find.lsl b/ARES/application/find.lsl new file mode 100644 index 0000000..d6ac88f --- /dev/null +++ b/ARES/application/find.lsl @@ -0,0 +1,513 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Find Application + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "0.0.3" +#define CLIENT_VERSION_TAGS "prealpha" + +list argsplit(string m) { + // splits a string based on word boundaries, but groups terms inside double and single quotes + // tabs and newlines are converted to spaces + list results; + string TAB = llChar(9); + list atoms = llParseStringKeepNulls(m, [], [" ", "\\", "'", "\"", "\n", TAB]); + integer in_quotes; // 1 = single, 2 = double + integer ti = 0; + integer tmax = count(atoms); + + string buffer; + while(ti < tmax) { + string t = gets(atoms, ti); + integer c = llOrd(t, 0); + if(c == 0x22) { // '"' + if(in_quotes == 0) { + in_quotes = 2; + } else if(in_quotes == 2) { + in_quotes = 0; + } else { + buffer += t; + } + } else if(c == 0x27) { // '\'' + if(in_quotes == 0) { + in_quotes = 1; + } else if(in_quotes == 1) { + in_quotes = 0; + } else { + buffer += t; + } + } else if(c == 0x5c) { // '\\' + string t1 = gets(atoms, ti + 2); + if(t1 == "\"" || t1 == "'") { + buffer += t1; + ti += 2; + } else { + buffer += t; + } + } else if(c == 0x20 || c == 0x0a || c == 0x09) { // ' ', '\n', '\t' + if(in_quotes) { + buffer += " "; + } else { + results += buffer; + buffer = ""; + } + } else { + buffer += t; + } + ++ti; + } + + if(tmax) + results += buffer; + + return results; +} + +// this is O(n^2), but the outer loop is usually sparse +// so, it might be faster than reversing the list and using a single normal search + +/* +// no longer required; LL added llListFindListNext(haystack, needle, index) which can take -1 as index + +integer nsListFindLast(list a, list b) { + if(a == [] || b == []) // either list is empty; bail + return NOWHERE; + integer i = llListFindList(a, b); + if(!~i) // doesn't exist; bail + return NOWHERE; + integer cb = count(b); + integer ca = count(a); + if(i == ca - cb) // first match is already at the end + return i; + if(!llListFindList(sublist(a, -cb, LAST), b)) // do we have a match at the end? + return ca - cb; + + integer j = i; + + list s; + + while(~(i = llListFindList(s = llDeleteSubList(a, 0, j), b))) + j += i + 1; + + return j; +} */ + +#define reverse_index(lis, item) llListFindListNext(lis, item, LAST) + +integer first_is_skippable; + +integer case_insensitive; +integer stop_after_first_match; +list states; // patterns matched at each offset +list exits; // next state of each state - stored as integer unless there are multiple hits + +add_exit(integer where, integer to) { + if(!~where) { + #ifdef DEBUG + echo("can't patch -1 to " + (string)to); + #endif + return; + } else if(llGetListEntryType(exits, where) == TYPE_INTEGER) { + integer w = geti(exits, where); + if(~w) { + exits = alter(exits, [(string)to + "," + (string)w], where, where); + } else { + exits = alter(exits, [to], where, where); + } + } else { + exits = alter(exits, [(string)to + "," + gets(exits, where)], where, where); + } +} + +construct_regex(string pattern) { + list paren_stack; // state indices corresponding to "(" symbols + list pipe_stack; // state indices corresponding to "|" symbols + + first_is_skippable = 0; // set to TRUE if state 0 has ? or * + + integer ci = 0; + integer cmax = strlen(pattern); + integer cc_open = NOWHERE; // character class open - position of '[' character + integer sc = 0; // number of states we've emitted so far + string buffer; // for character classes only + while(ci < cmax) { + string c = substr(pattern, ci, ci); + string d = substr(pattern, ci, ci + 1); + if(~cc_open) { + if(d == "\\-" || d == "\\\\") { + buffer += d; + ci += 1; + } else if(d == "\\^" /*|| d == "\\$" || d == "\\[" || d == "\\]" || d == "\\(" || d == "\\)" || d == "\\*" || d == "\\?" || d == "\\." || d == "\\\\"*/ || d == "\\]") { + buffer += substr(d, 1, 1); + ci += 1; + } else if(c == "]") { + string new_buffer = "\n" + substr(buffer, 0, 0); + + integer bi = 1; + integer bmax = strlen(buffer); + + integer found_hyphen = 0; + string last_e; + while(bi < bmax) { + string e = substr(buffer, bi, bi); + string f = substr(buffer, bi, bi + 1); + + if(f == "\\$" || f == "\\[" || f == "\\(" || f == "\\)" || f == "\\*" || f == "\\?" || f == "\\." || f == "\\\\" || f == "\\-") { + e = substr(f, 1, 1); + ++bi; + } else if(e == "-") { + found_hyphen = 2; + } + + if(found_hyphen == 2) { + found_hyphen = 1; + } else if(found_hyphen == 1) { + integer ck = llOrd(last_e, 0) + 1; + integer end_char = llOrd(e, 0); + if(ck < end_char) { + while(ck < end_char) + new_buffer += llChar(ck++); + } else if(ck - 2 > end_char) { + ck -= 2; + while(ck > end_char) + new_buffer += llChar(ck--); + } + found_hyphen = 0; + new_buffer += e; + last_e = e; + } else { + new_buffer += e; + last_e = e; + } + + ++bi; + } + + states += new_buffer; + exits += [NOWHERE]; + add_exit(sc - 1, sc); + buffer = ""; + ++sc; + cc_open = NOWHERE; + } else { + buffer += c; + } + } else if(d == "[^" && !~cc_open) { + cc_open = ci; + buffer = "^"; + ci += 1; + } else if(c == "[" && !~cc_open) { + cc_open = ci; + buffer = "["; + } else if(d == "\\^" || d == "\\$" || d == "\\[" || d == "\\]" || d == "\\(" || d == "\\)" || d == "\\*" || d == "\\?" || d == "\\." || d == "\\\\") { + states += d; + exits += [NOWHERE]; + add_exit(sc - 1, sc); + ++sc; + ci += 1; + } else if(c == "*") { + if(sc == 1) + first_is_skippable = TRUE; + else + add_exit(sc - 2, sc); + add_exit(sc - 1, sc - 1); + } else if(c == "+") { + add_exit(sc - 1, sc - 1); + } else if(c == "?") { + if(sc == 1) + first_is_skippable = TRUE; + else + add_exit(sc - 2, sc); + } else if(c == "(") { + paren_stack += [sc]; + states += ""; + exits += [NOWHERE]; + add_exit(sc - 1, sc); + ++sc; + } else if(d == ")?") { + // todo + ci += 1; + } else if(d == ")*") { + // todo + ci += 1; + } else if(c == ")") { + // todo + integer last_open = index(paren_stack, ")"); + // find last "(" + // find all subsequent "|" + // patch "(" to also point to "|"s + // patch symbols before "|" to also point to sc + // patch symbol before ")" to point to sc + } else if(c == "|") { + pipe_stack += [sc]; + states += ""; + exits += [NOWHERE]; + add_exit(sc - 1, sc); + ++sc; + } else if(d == "\\*" || d == "\\.") { + states += d; + //exits = alter(exits, [sc], sc - 1, sc - 1) + [NOWHERE]; + add_exit(sc - 1, sc); + exits += [NOWHERE]; + ++sc; + } else { + states += c; + // exits = alter(exits, [sc], sc - 1, sc - 1) + [NOWHERE]; + add_exit(sc - 1, sc); + exits += [NOWHERE]; + ++sc; + } + + ++ci; + } + + // fix all jumps to nowhere + integer si = count(states); + while(si--) { + list next = split(gets(exits, si), ","); + integer affect = 0; + integer sin; + while(~(sin = index(next, (string)sc))) { + next = alter(next, ["-1"], sin, sin); + affect = 1; + } + + if(affect) { + if(count(next) > 1) + exits = alter(exits, [concat(next, ",")], si, si); + else + exits = alter(exits, [(integer)gets(next, 0)], si, si); + #ifdef DEBUG + echo("fixed jump to nowhere in state " + (string)si); + #endif + } + } + + list last_exits = split(gets(exits, LAST), ","); + integer nli = index(last_exits, (string)NOWHERE); + if(!~nli) { + exits = alter(exits, [concat(last_exits, ",") + ",-1"], LAST, LAST); + #ifdef DEBUG + echo("patched last exit"); + #endif + } +} + +integer match(string q, string t) { + if(q == ".") + return (t != ""); + + if(q == "\\^" || q == "\\$" || q == "\\[" || q == "\\]" || q == "\\(" || q == "\\)" || q == "\\*" || q == "\\?" || q == "\\." || q == "\\\\") + return (llOrd(t, 0) == llOrd(q, 1)); + + if(case_insensitive) { + q = llToLower(q); + t = llToLower(t); + } + + if(llOrd(q, 0) == 0x0a) { // character ranges become '\n[' if positive and '\n^' if negative + if(llOrd(q, 1) == 0x5e) // '^' + return !~strpos(substr(q, 2, LAST), t); + else // implies '[' + return TRUE && ~strpos(substr(q, 2, LAST), t); + } + + return (q == t); +} + +list find(string text) { + integer ti_start = NOWHERE; + integer ti; + integer tmax = strlen(text); + integer si; + string result; + list path; + integer smax = count(states); + integer last_exit = NOWHERE; + while(~si && ti < tmax) { + list next = split(gets(exits, si), ","); + string st = gets(states, si); + string tt = substr(text, ti, ti); + if(match(st, tt)) { + if(!count(path)) + ti_start = ti; + + result += tt; + + #ifdef DEBUG + echo("matched \"" + (string)result + "\" ('" + tt + "' at char " + (string)ti + " vs. '" + st + "' from state " + (string)si + ")"); + #endif + + path += si; + si = geti(next, 0); + + // todo: for backreferences this must be changed to the appropriate length + + if(st != "") + ++ti; // null symbols are legal but don't advance the character counter + + if(!~si) { + return [ti_start, result]; + } + } else if(si != last_exit) { + if(!count(path)) { + ti_start = ti = ti + 1; + si = 0; + next = split(gets(exits, 0), ","); + #ifdef DEBUG + echo("could not start matching with '" + tt + "' at char " + (string)ti); + #endif + } else { + integer prev = geti(path, LAST); + next = split(gets(exits, prev), ","); + #ifdef DEBUG + echo("Available 'next' options: " + concat(next, ",")); + #endif + integer sj = index(next, (string)si); + next = delrange(next, 0, sj); + #ifdef DEBUG + echo("did not match ('" + tt + "' at char " + (string)ti + " vs. '" + st + "' from state " + (string)si + "); sj was " + (string)sj); + #endif + si = geti(next, 0); + } + } else { + integer dpi = count(path); + /* path = delitem(path, LAST); + result = delstring(result, LAST, LAST); */ + integer dsi = si; + while(dpi--) { + integer dprev = geti(path, dpi); + list dnext = split(gets(exits, dprev), ","); + integer dlast_exit = geti(dnext, LAST); + if(dlast_exit != dsi) { + integer dsj = index(dnext, (string)dsi); + si = geti(dnext, dsj + 1); + #ifdef DEBUG + echo("backtracked to state " + (string)dprev + "; trying " + (string)si + "; dsj was " + (string)dsj); + #endif + jump got_it; // backtrack was successful + } else { + dsi = dprev; + string dst = gets(states, geti(path, LAST)); + path = delitem(path, LAST); + + // todo: for backreferences this must be changed to the appropriate length + + if(dst != "") + result = delstring(result, LAST, LAST); + + #ifdef DEBUG + echo("backtracked past " + (string)dprev + " = '" + gets(states, dprev) + "'"); + #endif + } + } + // match failed - step forward + if(~ti_start) { + ti_start = ti = ti_start + 1; + } else { + ti += 1; + } + si = 0; + @got_it; + } + + last_exit = geti(next, LAST); + } + return [NOWHERE, ""]; +} + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = argsplit(m); + integer argc = count(argv); + string msg = ""; + + case_insensitive = 0; + stop_after_first_match = 0; + + list options = ["-i", "-1"]; + integer opt; + while(~(opt = index(options, gets(argv, 1)))) { + argv = delitem(argv, 1); + --argc; + if(opt == 0) { + case_insensitive = 1; + } else if(opt == 1) { + stop_after_first_match = 1; + } + } + + if(argc == 1) { + msg = "Syntax: " + PROGRAM_NAME + " [-i -1] [ ...]"; + } else { + states = exits = []; + string pattern = gets(argv, 1); + argv = delitem(argv, 1); + llResetTime(); + construct_regex(pattern); + // echo("Compiled regular expression in " + (string)llGetTime() + " sec"); // usually returns zero! + + echo("States: " + concat(states, "•")); + echo("Exits: " + concat(exits, "•")); + + string query_string; + if(ins == NULL_KEY) { + pipe_read(ins, query_string); + } + + if(query_string == "") { + query_string = concat(delitem(argv, 0), " "); + } + + msg = concat(find(query_string), ": "); + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/fortune.h.lsl b/ARES/application/fortune.h.lsl new file mode 100644 index 0000000..c62b038 --- /dev/null +++ b/ARES/application/fortune.h.lsl @@ -0,0 +1,36 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * fortune Utility Headers + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 1 (ASCL-i). It is offered to you on a limited basis to + * facilitate modification and customization. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +// This is a heavy-handed way of saying that the NS fortune server is not to be called outside of this ARES application, despite the rest of the code for the fortune utility being open-source. +#define URL "http://my.nanite-systems.com/fortune.star?params=" diff --git a/ARES/application/fortune.lsl b/ARES/application/fortune.lsl new file mode 100644 index 0000000..9d096a9 --- /dev/null +++ b/ARES/application/fortune.lsl @@ -0,0 +1,122 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * fortune Utility + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "0.2.5" +#define CLIENT_VERSION_TAGS "alpha" + +// LICENSING CAVEAT: Do not use the myNanite fortune server URL outside of ARES packages. It has been placed in a separate file for clarity. For your own applications using different websites, you can just replace this line with: #define URL "" +#include "ARES/application/fortune.h.lsl" + +list queue; // = [command, outs, ins, user, r, transport_pipe]; + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + string msg = ""; + string action = gets(argv, 1); + if(action == "help" || action == "-?" || action == "--help" || action == "/?" || action == "-h" || action == "/h") { + msg = "Syntax: " + PROGRAM_NAME + " \n\nDisplays fortunes from the myNanite GNU fortune server.\n\nSee https://linux.die.net/man/6/fortune for a list of available options, and 'help fortune' for the list of available fortune files."; + } else { + string command = "proc fetch " + URL + llEscapeURL(concat(delitem(argv, 0), " ")); + key handle = llGenerateKey(); + + pipe_open(["p:" + (string)handle + " notify " + PROGRAM_NAME + " data"]); + queue += [command, outs, ins, user, _resolved, handle]; + _resolved = 0; + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_NOTIFY) { + list argv = split(m, " "); + integer argc = count(argv); + string msg = ""; + string action = gets(argv, 1); + if(action == "data") { + string buffer; + pipe_read(ins, buffer); + + integer qi = index(queue, ins); + if(~qi) { + // some fortunes contain terminal control codes that SL just can't replicate: + list subs = [ + llChar(0x09), " ", // tab + llChar(0x7f), "<-", // delete + llChar(0x08), "<-" // backspace + ]; + integer subi = count(subs); + while(subi > 0) { + subi -= 2; + buffer = replace(buffer, gets(subs, subi), gets(subs, subi + 1)); + } + + print(gets(queue, qi - 4), gets(queue, qi - 2), buffer); + // resolve_io((integer)gets(queue, qi - 1), gets(queue, qi - 4), gets(queue, qi - 3)); + resolve_i((integer)gets(queue, qi - 1), gets(queue, qi - 3)); + queue = delrange(queue, qi - 5, qi); + } + + pipe_close([ins]); + + } else if(action == "pipe") { + integer qi = index(queue, ins); + if(~qi) { + key user = gets(queue, qi - 2); + string command = gets(queue, qi - 5); + + notify_program(command, ins, NULL_KEY, user); + } else { + pipe_close([ins]); + } + } + + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/help.lsl.old b/ARES/application/help.lsl.old new file mode 100644 index 0000000..e7e9ab4 --- /dev/null +++ b/ARES/application/help.lsl.old @@ -0,0 +1,309 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Help Utility + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include + +#define CLIENT_VERSION "1.1.1" +#define CLIENT_VERSION_TAGS "release" + +// files being indexed: +list files_to_index; // [filenames] +string cifile; +string cifile_short; +integer cii; // current line +integer cimax; // line limit +key ciq; + +key index_user; +key index_outs; + +do_index() { + if(count(files_to_index)) { + cifile = gets(files_to_index, 0); + cifile_short = delstring(cifile, -5, -1); + + files_to_index = delitem(files_to_index, 0); + cii = 0; + cimax = NOWHERE; + ciq = llGenerateKey(); + + file_open(ciq, cifile); + } else { + task_end(index_outs); + } +} + +// queue of requested pages to show: +list pages_to_show; // [page, outs, user] +string current_file; +key current_outs; +key current_user; +key file_pipe; +integer current_offset; +integer file_size; +integer storage_format; + +do_read() { + if(count(pages_to_show)) { + string entry = gets(pages_to_show, 0); + list help_files = llList2ListStrided(js2list(llLinksetDataRead("help")), 0, LAST, 2); + integer hi = 0; + integer himax = count(help_files); + + string clu; + while(hi < himax) { + current_file = gets(help_files, hi); + #ifdef DEBUG + echo("[help] Checking file " + current_file); + #endif + clu = getdbl("help", [current_file, entry]); + if(clu != JSON_INVALID) + jump got_it; + + ++hi; + } + @got_it; + current_outs = getk(pages_to_show, 1); + current_user = getk(pages_to_show, 2); + + storage_format = (integer)getdbl("help", [current_file, "__format"]); // 1: line, 0: char + + pages_to_show = delrange(pages_to_show, 0, 2); + + if(clu == JSON_INVALID) { + print(current_outs, current_user, "No help entry: " + entry + ". Check http://support.nanite-systems.com/search or your spelling."); + task_end(current_outs); + do_read(); // oh no, recursion + } else { + file_size = NOWHERE; + current_offset = (integer)clu; + file_open(file_pipe = llGenerateKey(), current_file += ".info"); + } + } +} + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_NOTIFY) { + if(m == "help file") { + string buffer; + if(ins == ciq || ins == file_pipe) { + pipe_read(ins, buffer); + + if(ins == ciq) { // indexing files + if(cimax == NOWHERE) { + cimax = (integer)buffer; + if(cimax > 0) { + storage_format = (llOrd(buffer, strpos(buffer, " ") + 1) == 0x6C); + // 1: line, 0: other + setdbl("help", [cifile_short, "__format"], (string)storage_format); + // start reading from beginning of file + file_read(ins, cifile, cii = 0); + #ifdef DEBUG + echo("[help] Reading file: " + cifile); + #endif + } else { + file_close(ins); + print(index_outs, index_user, + "Help file missing: " + cifile + ". Run 'help reindex' to clean up."); + do_index(); + } + } else { + integer new_cii = cii + FILE_PAGE_LENGTH; + + list lines = splitnulls(buffer, "\n"); + integer lmax = count(lines); + integer li = 0; + integer buffer_offset = 0; + while(li < lmax) { + string L = gets(lines, li); + integer line_len = strlen(L); + + if(substr(L, 0, 5) == "TOPIC ") { + integer entry_offset; + if(storage_format == 1) { + entry_offset = li * FILE_LINE_WIDTH + cii; + } else { + entry_offset = buffer_offset + cii + line_len; + if(li == lmax - 1) { + // do not count this line as it may be fragmentary + new_cii = buffer_offset + cii; + jump ignore_topic; + } + } + + setdbl( + "help", + [cifile_short, delstring(L, 0, 5)], + (string)(entry_offset) + ); + #ifdef DEBUG + echo("[help] Found entry: " + L + " at " + (string)entry_offset); + #endif + + } + + @ignore_topic; + buffer_offset += line_len + 1; + ++li; + } + + if(new_cii > cimax) { + file_close(ciq); + do_index(); + } else { + file_read(ciq, cifile, cii += FILE_PAGE_LENGTH); + } + } + } else if(ins == file_pipe) { // reading page + if(file_size == NOWHERE) { + file_size = (integer)buffer; + if(file_size > 0) { + // start reading from beginning of entry + file_read(ins, current_file, current_offset); + } else { + file_close(ins); + print(current_outs, current_user, + "Help file missing: " + current_file + ". Run 'help reindex' to clean up."); + task_end(current_outs); + do_read(); + } + } else { + integer ti = strpos(buffer, "\nTOPIC "); + if(~ti) { + #ifdef DEBUG + echo("[help] stopping read; found TOPIC at " + (string)ti); + #endif + buffer = delstring(buffer, ti, LAST); + file_close(ins); + task_end(current_outs); + do_read(); + } else if(substr(buffer, 0, 5) == "TOPIC ") { + #ifdef DEBUG + echo("[help] stopping read; found TOPIC at buffer start"); + #endif + file_close(ins); + task_end(current_outs); + do_read(); + return; + // TODO: ugly repetitive code above + } else if(current_offset + FILE_PAGE_LENGTH > file_size) { + #ifdef DEBUG + echo("[help] end of file"); + #endif + file_close(ins); + task_end(current_outs); + do_read(); + } else { + #ifdef DEBUG + echo("[help] pulling next page of " + current_file); + #endif + file_read(ins, current_file, current_offset += FILE_PAGE_LENGTH); + } + + print(current_outs, current_user, buffer); + } + } + } + } + } else if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + if(argc == 1) { + argv += "main"; + } + + string action = gets(argv, 1); + if(action == "index") { + string f = gets(argv, 2); + if(substr(f, -5, -1) != ".info") + f += ".info"; + print( + index_outs = outs, + index_user = user, + "Indexing info file: " + f + ); + task_begin(outs, "index"); + files_to_index += f; + do_index(); + } else if(action == "reindex") { + print( + index_outs = outs, + index_user = user, + "Re-indexing all known info files. This may take a while." + ); + llLinksetDataDelete("help"); + task_begin(outs, "index"); + /*integer c = llGetInventoryNumber(INVENTORY_NOTECARD); + while(c--) { + string f = llGetInventoryName(INVENTORY_NOTECARD, c); + if(substr(f, -5, -1) == ".info") { + files_to_index += f; + } + }*/ + files_to_index = js2list(llLinksetDataRead("fs:info")); + do_index(); + } else if(action == "forget") { + string f = gets(argv, 2); + if(substr(f, -5, -1) == ".info") + f = delstring(f, -5, -1); + + if(getdbl("help", [f]) == JSON_INVALID) { + print(outs, user, "Info file not present: " + f + ".info"); + } else { + deletedbl("help", [f]); + print(outs, user, "Removed info file " + f + ".info from help database."); + } + } else { + task_begin(outs, "read"); + string p = concat(delitem(argv, 0), " "); + pages_to_show += [p, outs, user]; + do_read(); + } + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/id.lsl b/ARES/application/id.lsl new file mode 100644 index 0000000..cae29c7 --- /dev/null +++ b/ARES/application/id.lsl @@ -0,0 +1,700 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Identity System Module + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 1 (ASCL-i). It is offered to you on a limited basis to + * facilitate modification and customization. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#include +#define CLIENT_VERSION ARES_VERSION +#define CLIENT_VERSION_TAGS ARES_VERSION_TAGS + +string authority; +string unit_name; +string unit_serial; +string model; +string gender; +list colors = [ONES, <1, 0, 0>, <0, 1, 0>, <1, 1, 0>]; + +string format_color(vector c) { + string r = hex((integer)(c.x * 255)); + string g = hex((integer)(c.y * 255)); + string b = hex((integer)(c.z * 255)); + if(strlen(r) == 1) r = "0" + r; + if(strlen(g) == 1) g = "0" + g; + if(strlen(b) == 1) b = "0" + b; + string hexcolor = "#" + r + g + b; + list preset_pair = js2list(llLinksetDataRead("swatch")); + integer hci = index(preset_pair, hexcolor); + if(~hci) { + return hexcolor + " (" + gets(preset_pair, hci - 1) + ")"; + } else { + return hexcolor; + } +} + +vector parse_color(list argv) { + // echo("parsing color " + concat(argv, " ")); + integer argc = count(argv); + vector c; + + if(argc == 1) { + string input = gets(argv, 0); + string cand = getdbl("swatch", [input]); + if(cand != JSON_INVALID) + input = cand; + + if(substr(input, 0, 0) == "#" && strlen(input) == 7) { + c = <(integer)("0x" + substr(input, 1, 2)), + (integer)("0x" + substr(input, 3, 4)), + (integer)("0x" + substr(input, 5, 6))> / 255.0; + } else { + c = <-1, -1, -1>; + } + } else if(count(argv) == 3) { + c = <(float)gets(argv, 0), + (float)gets(argv, 1), + (float)gets(argv, 2)>; + if(c.x > 1 || c.y > 1 || c.z > 1) + c /= 255.0; + } + return c; +} + +make_callsign(string prefix) { + string callsign; + integer lc = llOrd(prefix, LAST); + if(prefix == JSON_INVALID) + callsign = unit_name; + else if(lc == 0x2f || lc == 0x2e || lc == 0x2d || lc == 0x5f || prefix == "") // / . - _ + callsign = prefix + unit_name; + else + callsign = prefix + " " + unit_name; + + if(strlen_byte(callsign) != strlen(callsign)) + echo("Warning: new callsign is not a valid object name (contains Unicode)"); + + setdbl("id", ["callsign"], callsign); +} + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + string msg; + if(argc == 1) { + string vendor = getdbl("id", ["vendor"]); + if(vendor == JSON_INVALID || vendor == "") + vendor = "Nanite Systems Corporation"; + + model = getdbl("id", ["model"]); + if(model == JSON_INVALID || model == "") + model = "(unknown)"; + + string s_kernel = llLinksetDataRead("kernel"); + + // about: + msg = "About ARES\nARES (Psyche/CX) " + model + " System\n"; + + string kernel_version = getjs(s_kernel, ["version"]); + string kernel_version_tags = getjs(s_kernel, ["version-tags"]); + + string package_version = getdbl("pkg", ["version", "ARES"]); + + if(package_version != JSON_INVALID) + msg += "\nOS version: " + package_version; + + if(kernel_version != package_version) { + if(kernel_version_tags != JSON_INVALID && kernel_version_tags != "") + kernel_version = kernel_version + " " + kernel_version_tags; + + msg += "\nKernel version: " + kernel_version; + } + + if(package_version != CLIENT_VERSION) + msg += + "\nid version: " + CLIENT_VERSION + #ifdef CLIENT_VERSION_TAGS + + " " + CLIENT_VERSION_TAGS + " (_id compiled " + __DATE__ + ")" + #else + + "\nid compiled: " + __DATE__ + #endif + ; + + msg += "\n\nUnit name: " + unit_name + "\nVendor: " + vendor; + + + if(authority != "(none)") + msg += "\nSupervising authority: " + authority; + + integer ci = 4; + while(ci--) + colors = alter(colors, [str2vec(getdbl("id", ["color", ci]))], ci, ci); + + msg += + "\nSerial number: " + unit_serial + + "\nModel: " + model + + "\nColors:" + + "\n " + format_color(getv(colors, 0)) + + "\n " + format_color(getv(colors, 1)) + + "\n " + format_color(getv(colors, 2)) + + "\n " + format_color(getv(colors, 3)) + + "\n\nType 'security' for user information or 'device' for devices information." + ; + + } else { + integer check = sec_check(user, "identity", outs, m, m); + if(check == DENIED) { + print(outs, user, "You are not authorized to modify this unit's identity settings."); + return; + } else if(check != ALLOWED) { + return; + } + + integer trigger_color_update = 0; + string action = gets(argv, 1); + if(action == "regen" || action == "regenerate") { + integer mantissa = (integer)("0x" + substr(llGetOwner(), 29, 35)) % 1000000; + string serial = getdbl("id", ["model"]); + if(~strpos(serial, "oXq")) { + #define letter_map "0123456789ABabcdefghijklmnopqrstuvwxyz." + + integer part = 3; + string name; + while(part--) { + integer tangle; + + if(part == 0) { + tangle = (integer)("0x" + substr(avatar, 2, 3)) + ^ (integer)("0x" + substr(avatar, 31, 33)); + } else if(part == 2) { + tangle = (integer)("0x" + substr(avatar, 5, 7)) + ^ (integer)("0x" + substr(avatar, 25, 30)); + } else if(part == 1) { + tangle = (integer)("0x" + substr(avatar, 9, 10) + substr(avatar, 14, 15)) + ^ (integer)("0x" + substr(avatar, 27, 31)); + } + + if(tangle < 0) + tangle = -tangle; + + string frag = ""; + + while(tangle > 0) { + integer a; + if(part % 2 == 0) { + if(strlen(frag) % 2) { + a = tangle % 5; + frag += substr("aeiou", a, a); + tangle /= 6; + } else { + a = tangle % 21; + frag += substr("bcdfghklmnpqrstvwxyzj", a, a); + tangle /= 23; + } + } else { + a = tangle % 10; + tangle /= 16; + frag += substr(letter_map, a, a); + } + } + + name += frag; + + if(part) + name += "."; + } + + serial = name; + } else { + string mantissa_s = substr((string)mantissa, 0, 1) + "-" + substr((string)mantissa, 2, 5); + if(~strpos(serial, "-")) { + serial += "-" + mantissa_s; + } else { + serial += " " + mantissa_s; + } + } + + setdbl("id", ["serial"], unit_serial = serial); + make_callsign(getdbl("id", ["prefix"])); + } else if(action == "font") { + list font_names = jskeys(llLinksetDataRead("font")); + if(argc == 2) { + msg = "Current variatype font: " + getdbl("interface", ["font"]) + + "\nAvailable fonts: " + concat(font_names, ", "); + } else { + string font_name = gets(argv, 2); + if(contains(font_names, font_name)) { + setdbl("interface", ["font"], font_name); + msg = "Set variatype font to " + font_name + "."; + e_call(C_VARIATYPE, E_SIGNAL_CALL, (string)outs + " " + (string)user + " variatype reconfigure"); + } else { + msg = "Unknown font '" + font_name + "'; available fonts: " + concat(font_names, ", "); + } + } + } else if(action == "interface") { + string setting = gets(argv, 2); + list keyname = split(setting, "."); + string current_value = getdbl("interface", keyname); + if(argc > 2) { + string new_value = gets(argv, 3); + if(new_value == "toggle") { + if(current_value == "0") + new_value = "1"; + else + new_value = "0"; + } + + if(current_value != new_value) { + current_value = new_value; + setdbl("interface", keyname, current_value); + if(setting == "font") { + e_call(C_VARIATYPE, E_SIGNAL_CALL, (string)outs + " " + (string)user + " variatype reconfigure"); + } else { + e_call(C_INTERFACE, E_SIGNAL_CALL, (string)outs + " " + (string)user + " interface reconfigure"); + } + } + } + msg = "Interface setting " + setting + ": " + current_value; + + } else if(action == "volume") { + float vol = (float)getdbl("interface", ["sound", "volume"]); + + if(argc == 3) { + string new_vol = gets(argv, 2); + if(new_vol == "off" || new_vol == "mute") { + vol = 0; + } else if((string)((integer)new_vol) == new_vol) { + vol = 0.01 * (float)new_vol; + } else if(new_vol == "up") { + vol += 0.1; + } else if(new_vol == "down") { + vol -= 0.1; + } else if(new_vol == "cycle") { + vol += 0.1; + if(vol > 1.0) + vol = 0; + } + } + + if(vol < 0.0) + vol = 0.0; + else if(vol > 1.0) + vol = 1.0; + + string vf = (string)((integer)(100 * vol)) + "%"; + + setdbl("interface", ["sound", "volume"], (string)vol); + setdbl("interface", ["sound", "vf"], vf); + msg = "Interface volume: " + vf; + interface_sound("test"); + + } else if(action == "menu") { + list schemes = jskeys(getdbl("id", ["scheme"])); + list scheme_buttons = []; + integer sci = count(schemes); + while(sci--) { + string scheme = gets(schemes, sci); + string sb = jsarray([ + scheme, + 0, + "id color load " + scheme + ]); + scheme_buttons = sb + scheme_buttons; + } + setdbl("m:color", ["d"], jsarray(scheme_buttons)); + } else if(action == "name") { + if(argc == 2) { + msg = "Unit name: " + unit_name; + } else { + unit_name = concat(delrange(argv, 0, 1), " "); + if(unit_name == "none") { + deletedbl("id", ["name"]); + msg = "Name cleared."; + unit_name = "(none)"; + setdbl("id", ["callsign"], unit_serial); + } else { + setdbl("id", ["name"], unit_name); + msg = "Name set."; + string prefix = getdbl("id", ["prefix"]); + make_callsign(prefix); + } + } + } else if(action == "authority") { + if(argc == 2) { + msg = "Supervising authority: " + authority; + } else { + authority = concat(delrange(argv, 0, 1), " "); + if(authority == "none") { + deletedbl("id", ["authority"]); + msg = "Authority cleared."; + authority = "(none)"; + } else { + setdbl("id", ["authority"], authority); + msg = "Authority set."; + } + } + } else if(action == "chime") { + // id chime load + // id chime save + // id chime delete + // id chime boot none| + // id chime halt none| + string verb = gets(argv, 2); + string value = gets(argv, 3); + if(verb == "load" && value != "") { + string scheme = getdbl("chime", [value]); + if(scheme != JSON_INVALID) { + string bc; + setdbl("id", ["chime", "boot"], bc = getjs(scheme, [0])); + setdbl("id", ["chime", "halt"], getjs(scheme, [1])); + msg = "Chime scheme '" + value + "' applied."; + play_sound(bc); + setdbl("m:chime", ["f"], value); + } else { + msg = "There is no chime scheme named '" + value + "'"; + } + } else if(verb == "save" && value != "") { + string bc = getdbl("id", ["chime", "boot"]); + string hc = getdbl("id", ["chime", "halt"]); + setdbl("chime", [value], jsarray([bc, hc])); + } else if(verb == "delete" && value != "") { + string scheme = getdbl("chime", [value]); + if(scheme != JSON_INVALID) { + deletedbl("chime", [value]); + msg = "Deleted chime scheme '" + value + "'"; + } else { + msg = "There is no chime scheme named '" + value + "'"; + } + } else if((verb == "boot" || verb == "halt") && value != "") { + string old = getdbl("id", ["chime", verb]); + if(value == "clear") { + if(old != JSON_INVALID) + value = JSON_DELETE; + else + jump nah; + } else if(old == value) + jump nah; + + setdbl("id", ["chime", verb], value); + msg = "Chime set."; + jump yah; + @nah; + msg = "No change."; + @yah; + } else { + string bc = getdbl("id", ["chime", "boot"]); if(bc == JSON_INVALID) bc = "(none)"; + string hc = getdbl("id", ["chime", "halt"]); if(hc == JSON_INVALID) hc = "(none)"; + string schemes = concat(jskeys(getdbl("chime", [])), ", "); + if(schemes == "") schemes = "(none; please reload default database)"; + + msg = "Current chime settings\n\nBoot: " + bc + + "\nHalt: " + hc + + "\nAvailable schemes: " + schemes; + } + } else if(action == "color") { + integer ci = 4; + while(ci--) + colors = alter(colors, [str2vec(getdbl("id", ["color", ci]))], ci, ci); + + if(argc == 2) { + string schemes = getdbl("id", ["scheme"]); + if(schemes == JSON_INVALID) + schemes = "(none)"; + else + schemes = concat(jskeys(schemes), ", "); + + msg = "Current colors:\n " + format_color(getv(colors, 0)) + + "\n " + format_color(getv(colors, 1)) + + "\n " + format_color(getv(colors, 2)) + + "\n " + format_color(getv(colors, 3)) + + "\nAvailable swatches:" + + "\n " + concat(jskeys(llLinksetDataRead("swatch")), ", ") + + "\nAvailable schemes:" + + "\n " + schemes; + } else { + integer affect = -1; // affect all + string keyword = gets(argv, 2); + argv = delrange(argv, 0, 2); + + list slot_names = ["a", "b", "c", "d", "primary", "secondary", "tertiary", "quartenary", "p", "s", "t", "q"]; + integer si = index(slot_names, keyword); + if(~si) { + affect = si & 0x03; // % 4 + keyword = gets(argv, 0); + } else if(keyword == "all" || keyword == "*") { + si = 0; + } else if(keyword == "swatch") { + affect = 5; + keyword = gets(argv, 0); + } + + integer action; // 1: set, 2: save scheme, 3: load scheme, 4: delete scheme + + list actions = [0, "set", "save", "load", "delete"]; + integer ai = index(actions, keyword); + if(~ai) { + action = ai; + if(~si) + argv = delitem(argv, 0); + } else { + action = 1; // mode: set + } + + vector c; + if(argc = count(argv)) { // update argc + if(affect == 5) { + if(keyword == "delete") { + string name = gets(argv, 1); + if(getdbl("swatch", [name]) != JSON_INVALID) { + deletedbl("swatch", [name]); + msg = "Deleted swatch " + name; + } else { + msg = "No swatch: " + name; + } + } else { + c = parse_color(delitem(argv, 0)); + if(c == <-1, -1, -1>) { + msg = "Unknown color code: " + concat(argv, " ") + ". See 'help id' for more information."; + jump error; + } + + string hex = substr(format_color(c), 0, 6); + setdbl("swatch", [keyword], hex); + } + } else if(action == 1) { // set + c = parse_color(argv); + if(c == <-1, -1, -1>) { + msg = "Unknown color code: " + concat(argv, " ") + ". See 'help id' for more information."; + jump error; + } + + if(~affect) { + colors = alter(colors, [c], affect, affect); + } else { + colors = [c, c, c, c]; + } + + vector ca = getv(colors, 0); + vector cb = getv(colors, 1); + vector cc = getv(colors, 2); + vector cd = getv(colors, 3); + + setdbl("id", ["color"], jsarray([ + vec2str(ca), + vec2str(cb), + vec2str(cc), + vec2str(cd) + ])); + + trigger_color_update = 1; + msg = "Color updated."; + + } else if(action == 2) { // save + string scheme_name = concat(argv, " "); + + vector ca = getv(colors, 0); + vector cb = getv(colors, 1); + vector cc = getv(colors, 2); + vector cd = getv(colors, 3); + + setdbl("id", ["scheme", scheme_name], jsarray([ + vec2str(ca), + vec2str(cb), + vec2str(cc), + vec2str(cd) + ])); + + msg = "Color scheme '" + scheme_name + "' saved."; + } else if(action == 3) { // load + string scheme_name = concat(argv, " "); + string colorlist = getdbl("id", ["scheme", scheme_name]); + if(colorlist != JSON_INVALID) { + setdbl("id", ["color"], colorlist); + setdbl("m:color", ["f"], scheme_name); + + integer ci = 4; + while(ci--) { + colors = alter(colors, [str2vec(getjs(colorlist, [ci]))], ci, ci); + } + + trigger_color_update = 1; + msg = "Color scheme '" + scheme_name + "' loaded."; + } else { + msg = "No color scheme: '" + scheme_name + "'"; + } + } else if(action == 4) { // delete + string scheme_name = concat(argv, " "); + string colorlist = getdbl("id", ["scheme", scheme_name]); + if(colorlist != JSON_INVALID) { + setdbl("id", ["scheme", scheme_name], JSON_DELETE); + msg = "Color scheme '" + scheme_name + "' deleted."; + } else { + msg = "No color scheme: '" + scheme_name + "'"; + } + } + + @error; + } else { + msg = "Missing arguments: id color " + keyword; + } + } + } else if(action == "gender") { + string s_gender = getdbl("id", ["gender"]); + if(argc == 2) { + msg = "Gender status:\n\nMental gender (self-reported pronouns): " + getjs(s_gender, ["mental"]) + + "\nPhysical gender (pronouns used in descriptions): " + getjs(s_gender, ["physical"]) + + "\nVoice gender (chat tone/speech markers): " + getjs(s_gender, ["voice"]) + + "\n\nSee 'help gender' for instructions on how to configure."; + } else { + string tfield = gets(argv, 2); + string gfield = gets(argv, 3); + integer gender = NOWHERE; + integer topic = NOWHERE; + + list pronoun_options = [ + "neuter,they,them,their,theirs,themself", + "female,she,her,her,hers,herself", + "male,he,him,his,his,himself", + "inanimate,it,it,its,its,itself" + ]; + + topic = llListFindList(["all", "mental", "physical", "voice"], [tfield]); + if(!~topic) + topic = strpos("*mpv", tfield); + + gender = llListFindList(["neuter", "female", "male", "inanimate", "custom"], [gfield]); + if(!~gender) + gender = strpos("nfmic", gfield); + + if(!~topic || !~gender) { + msg = "Unknown gender command: " + tfield + " " + gfield + ". Please check the manual and try again."; + } else { + if(topic != 3) { // anything but voice + string pronoun_source; + + if(gender == 4) { + // custom + pronoun_source = gets(argv, 4); + } else { + pronoun_source = gets(pronoun_options, gender); + } + + list pronouns = split(pronoun_source, ","); + string pronoun_obj = jsobject([ + "adj", gets(pronouns, 0), + "sub", gets(pronouns, 1), + "obj", gets(pronouns, 2), + "gen", gets(pronouns, 3), + "pos", gets(pronouns, 4), + "refl", gets(pronouns, 5) + ]); + + if(topic == 0) { + if(gender > 2) + gender = 0; + + setdbl("id", ["gender", "voice"], substr("nfm", gender, gender)); + } + + if(topic == 0 || topic == 1) { + setdbl("env", ["pm"], pronoun_obj); + setdbl("id", ["gender", "mental"], jsarray(pronouns)); + } + + if(topic == 0 || topic == 2) { + setdbl("env", ["pp"], pronoun_obj); + setdbl("id", ["gender", "physical"], jsarray(pronouns)); + } + + msg = "Gender set."; + + } else { // voice + if(gender > 2) + gender = 0; + + setdbl("id", ["gender", "voice"], substr("nfm", gender, gender)); + + msg = "Voice gender set."; + } + } + } + + } else { + msg = PROGRAM_NAME + ": Unrecognized action '" + action + "'; see 'help id' for a list of valid actions"; + } + + if(trigger_color_update) { + e_call(C_INTERFACE, E_SIGNAL_CALL, (string)outs + " " + (string)user + " interface color"); + e_call(C_VARIATYPE, E_SIGNAL_CALL, (string)outs + " " + (string)user + " variatype color"); + e_call(C_HARDWARE, E_SIGNAL_CALL, (string)NULL_KEY + " " + (string)NULL_KEY + " hardware color"); + // no keys = tell device to send update to all devices + e_call(C_REPAIR, E_SIGNAL_CALL, (string)outs + " " + (string)user + " repair color"); + e_call(C_STATUS, E_SIGNAL_CALL, (string)outs + " " + (string)user + " status color"); + } + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_INIT) { + string id_section = llLinksetDataRead("id"); + unit_name = getjs(id_section, ["name"]); + unit_serial = getjs(id_section, ["serial"]); + if(unit_serial == JSON_INVALID || unit_serial == "") + unit_serial = "NS-100-00-0001"; + if(unit_name == JSON_INVALID || unit_name == "") + unit_name = unit_serial; + + if(strlen_byte(unit_name) != strlen(unit_name)) + echo("Warning: unit name is invalid (contains Unicode)"); + + authority = getjs(id_section, ["authority"]); + if(authority == JSON_INVALID || authority == "") + authority = "(none)"; + model = getjs(id_section, ["model"]); + if(model == JSON_INVALID || model == "") + model = "(unknown)"; + gender = getjs(id_section, ["gender"]); + integer ci = 4; + while(ci--) + colors = alter(colors, [str2vec(getjs(id_section, ["color", ci]))], ci, ci); + /* // obsolete/niche: + } else if(n == SIGNAL_TERMINATE) { + echo("[" + PROGRAM_NAME + "] dying as ordered"); + exit(); */ + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unhandled: signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/land.lsl b/ARES/application/land.lsl new file mode 100644 index 0000000..b3003d8 --- /dev/null +++ b/ARES/application/land.lsl @@ -0,0 +1,249 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Land Management Utility + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +/* + Wrapped LSL functions: + + https://wiki.secondlife.com/wiki/LlGetEnv + + https://wiki.secondlife.com/wiki/LlGetEnvironment - too complex for now + https://wiki.secondlife.com/wiki/LlParcelMediaCommandList + https://wiki.secondlife.com/wiki/LlParcelMediaQuery + https://wiki.secondlife.com/wiki/LlRequestSimulatorData + https://wiki.secondlife.com/wiki/LlSetEnvironment - too complex for now + https://wiki.secondlife.com/wiki/LlGetSimStats + + https://wiki.secondlife.com/wiki/LlGetExperienceDetails + + https://wiki.secondlife.com/wiki/LlGetParcelDetails + https://wiki.secondlife.com/wiki/LlGetParcelFlags + https://wiki.secondlife.com/wiki/LlGetParcelMaxPrims + https://wiki.secondlife.com/wiki/LlGetParcelPrimCount + https://wiki.secondlife.com/wiki/LlGetParcelPrimOwners + + x https://wiki.secondlife.com/wiki/LlManageEstateAccess + x https://wiki.secondlife.com/wiki/LlGetRegionFlags + x https://wiki.secondlife.com/wiki/LlGetSimulatorHostname + x https://wiki.secondlife.com/wiki/LlGetDayLength + x https://wiki.secondlife.com/wiki/LlGetDayOffset + x https://wiki.secondlife.com/wiki/LlAddToLandBanList + x https://wiki.secondlife.com/wiki/LlAddToLandPassList + x https://wiki.secondlife.com/wiki/LlEjectFromLand + x https://wiki.secondlife.com/wiki/LlGetParcelMusicURL + x https://wiki.secondlife.com/wiki/LlGetRegionAgentCount + x https://wiki.secondlife.com/wiki/LlGetRegionAgentCount + x https://wiki.secondlife.com/wiki/LlGetRegionCorner + x https://wiki.secondlife.com/wiki/LlGetRegionDayLength + x https://wiki.secondlife.com/wiki/LlGetRegionDayOffset + x https://wiki.secondlife.com/wiki/LlGetRegionFPS + x https://wiki.secondlife.com/wiki/LlGetRegionName + x https://wiki.secondlife.com/wiki/LlGround + x https://wiki.secondlife.com/wiki/LlGroundSlope + x https://wiki.secondlife.com/wiki/LlMapDestination + x https://wiki.secondlife.com/wiki/LlRemoveFromLandBanList + x https://wiki.secondlife.com/wiki/LlRemoveFromLandPassList + x https://wiki.secondlife.com/wiki/LlResetLandBanList + x https://wiki.secondlife.com/wiki/LlResetLandPassList + x https://wiki.secondlife.com/wiki/LlReturnObjectsByID - still needs permission + x https://wiki.secondlife.com/wiki/LlReturnObjectsByOwner - still needs permission + x https://wiki.secondlife.com/wiki/LlSetParcelMusicURL + x https://wiki.secondlife.com/wiki/LlTeleportAgentHome + x https://wiki.secondlife.com/wiki/LlWater + x https://wiki.secondlife.com/wiki/LlWind + +*/ + +#include +#define CLIENT_VERSION "0.2.1" +#define CLIENT_VERSION_TAGS "alpha" + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + string msg = ""; + if(argc == 1) { + msg = "Syntax: " + PROGRAM_NAME + " \n\nUtility for managing and inspecting land.\n\nSupported actions:\n\nParcel info: parcel [name], parcel capacity, parcel id, parcel desc, parcel prims, parcel owner, parcel group, area, flags \nSurveying: wind, water, ground, slope, day length, day offset, region day length, region day offset\nParcel media: music [] (requires land ownership)\nMisc: map \nAccess control: kick , eject , evict , return , pass clear, pass revoke , pass [], ban clear, ban revoke , unban , unban all, ban [], exile \nRegion info: region [name], region flags, region host, region pop[ulation], region pos, region fps, uptime, region uptime, region version\nEstate: estate ban , estate unban , estate pass [group] , estate revoke [group] "; + } else { + string action = gets(argv, 1); + string subject = gets(argv, 2); + string topic = gets(argv, 3); + string fourth = gets(argv, 4); + + if(action == "parcel") { + if(argc == 2 || subject == "name") + msg = gets(llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_NAME]), 0); + else if(subject == "id") + msg = gets(llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_ID]), 0); + else if(subject == "desc") + msg = gets(llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_DESC]), 0); + else if(subject == "group") + msg = gets(llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_GROUP]), 0); + else if(subject == "owner") + msg = gets(llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_OWNER]), 0); + else if(subject == "capacity") + msg = (string)geti(llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_PRIM_CAPACITY]), 0); + else if(subject == "prims") + msg = (string)geti(llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_PRIM_USED]), 0); + } else if(action == "area") + msg = (string)geti(llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_AREA]), 0); + else if(action == "flags") + msg = (string)llGetParcelFlags(llGetPos()); + else if(action == "wind") + msg = (string)llWind(ZV); + else if(action == "water") + msg = (string)llWater(ZV); + else if(action == "ground") + msg = (string)llGround(ZV); + else if(action == "slope") + msg = (string)llGroundSlope(ZV); + else if(action == "music") { + if(subject != "") { + llSetParcelMusicURL(subject); + } else { + msg = llGetParcelMusicURL(); + } + } else if(action == "map") { + list url = split(subject, "/"); + + string region = gets(url, -4); + vector coords = <(float)gets(url, -3), (float)gets(url, -2), (float)gets(url, -1)>; + llMapDestination(region, coords, ZV); + } else if(action == "day") { + if(subject == "length") + msg = (string)llGetDayLength(); + else if(subject == "offset") + msg = (string)llGetDayOffset(); + } else if(action == "kick") + llTeleportAgentHome(subject); + else if(action == "eject") + llEjectFromLand(subject); + else if(action == "evict") + llReturnObjectsByOwner(subject, OBJECT_RETURN_PARCEL); + else if(action == "return") + llReturnObjectsByID(delrange(argv, 0, 1)); + else if(action == "pass") { + if(subject == "clear") + llResetLandPassList(); + else if(subject == "revoke") + llRemoveFromLandPassList(topic); + else + llAddToLandPassList(subject, (integer)fourth); + } else if(action == "ban") { + if(subject == "clear") + llResetLandBanList(); + else if(subject == "revoke") + llRemoveFromLandBanList(topic); + else + llAddToLandBanList(subject, (integer)fourth); + } else if(action == "unban") { + if(subject == "all") + llResetLandBanList(); + else + llRemoveFromLandBanList(subject); + } else if(action == "exile") { + llTeleportAgentHome(subject); + llAddToLandBanList(subject, 0); + llReturnObjectsByOwner(subject, OBJECT_RETURN_PARCEL_OWNER); + } else if(action == "uptime") + msg = format_time(llGetUnixTime() - (integer)llGetEnv("region_start_time")); + else if(action == "day") { + if(subject == "length") + msg = (string)llGetRegionDayLength(); + else if(subject == "offset") + msg = (string)llGetRegionDayOffset(); + } else if(action == "region") { + if(subject == "flags") + msg = (string)llGetRegionFlags(); + else if(subject == "host") + msg = llGetEnv("simulator_hostname"); + else if(subject == "version") + msg = llGetEnv("sim_channel") + " " + llGetEnv("sim_version"); + else if(subject == "population" || subject == "pop") + msg = (string)llGetRegionAgentCount(); + else if(subject == "pos") + msg = (string)llGetRegionCorner(); + else if(subject == "fps") + msg = (string)llGetRegionFPS(); + else if(subject == "uptime") + msg = (string)(llGetUnixTime() - (integer)llGetEnv("region_start_time")); + else if(subject == "day") { + if(topic == "length") + msg = (string)llGetRegionDayLength(); + else if(topic == "offset") + msg = (string)llGetRegionDayOffset(); + } else if(subject == "name" || subject == "") + msg = llGetRegionName(); + } else if(action == "estate") { + if(subject == "ban") { + llManageEstateAccess(ESTATE_ACCESS_BANNED_AGENT_ADD, topic); + } else if(subject == "unban") { + llManageEstateAccess(ESTATE_ACCESS_BANNED_AGENT_REMOVE, topic); + } else if(subject == "pass") { + if(topic == "group") + llManageEstateAccess(ESTATE_ACCESS_ALLOWED_GROUP_ADD, fourth); + else + llManageEstateAccess(ESTATE_ACCESS_ALLOWED_AGENT_ADD, topic); + } else if(subject == "revoke") { + if(topic == "group") + llManageEstateAccess(ESTATE_ACCESS_ALLOWED_GROUP_REMOVE, fourth); + else + llManageEstateAccess(ESTATE_ACCESS_ALLOWED_AGENT_REMOVE, topic); + } else if(subject == "name" || subject == "") { + msg = llGetEnv("estate_name"); + } else if(subject == "id") { + msg = llGetEnv("estate_id"); + } + } + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/lore.lsl b/ARES/application/lore.lsl new file mode 100644 index 0000000..c0fbb4d --- /dev/null +++ b/ARES/application/lore.lsl @@ -0,0 +1,67 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Sim Lore Database Client Application + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "0.0.1" +#define CLIENT_VERSION_TAGS "placeholder" + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + string msg = ""; + string action = gets(argv, 1); + if(argc == 1 || action == "help") { + msg = "Syntax: " + PROGRAM_NAME + " "; + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/lslisp.lsl b/ARES/application/lslisp.lsl new file mode 100644 index 0000000..b2c159e --- /dev/null +++ b/ARES/application/lslisp.lsl @@ -0,0 +1,162 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * LSLisp System Module + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#include +#define CLIENT_VERSION "0.2.5" +#define CLIENT_VERSION_TAGS "lang " + INTERPRETER_VERSION + +integer file_offset; +integer file_length; +key file_outs; +key file_user; +key file_pipe; +string file_name; +string file_unit; + +#define FILE_STEP_SIZE 10 + +main(integer src, integer n, string m, key e_outs, key e_ins, key e_user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + if(argc == 1) { + print(e_outs, e_user, "Syntax: @" + PROGRAM_NAME + " "); + } else { + // set aside session info for when we have data to output: + file_outs = e_outs; + file_user = e_user; + parse_queue = v_names = v_values = interrupted_stack = []; + interrupt = ""; + + // always needed for file loading: + file_name = gets(argv, 1); + file_unit = ""; + file_offset = file_length = NOWHERE; + file_open(file_pipe = llGenerateKey(), file_name); + + // prevent job from ending while loading file: + task_begin(file_pipe, file_name); + } + } else if(n == SIGNAL_NOTIFY) { + if(m == PROGRAM_NAME + " file") { + if(e_ins == file_pipe) { + // copy contents of LSD file_pipe into local variable: + string file_buffer; + pipe_read(file_pipe, file_buffer); + + integer read_length = FILE_STEP_SIZE; + if(file_unit == "b") read_length *= FILE_PAGE_LENGTH; + + if(file_length == NOWHERE) { + // haven't loaded file length yet + list stat = split(file_buffer, " "); + file_length = (integer)gets(stat, 0); + file_unit = gets(stat, 1); + file_offset = 0; + // for notecards this is measured as 256 * (number of lines), + // NOT actual character count + } else { + file_offset += read_length; + } + + if(read_length + file_offset > file_length) + read_length = file_length - file_offset; + + if(file_offset == 0) { + if(file_length > 0) { + // start reading file + file_read(file_pipe, file_name, (string)file_offset + " " + (string)read_length); + } else { + // file not found or file empty + print(file_user, file_user, "No file: " + file_name); + file_close(file_pipe); + + // job can now end: + task_end(file_pipe); + } + } else { + // got length earlier, so this must be file data + parse_queue += ["\n"] + tokenize(file_buffer); + + // is there more file? + if(file_offset < file_length) { + // get next section + // move forward to next page: + file_read(file_pipe, file_name, (string)file_offset + " " + (string)read_length); + } else { + // reached end of file + file_close(file_pipe); + + user = file_user; + outs = file_outs; + + string prog = parse(parse_queue); + + list output = execute([stack_frame(prog, [], NOWHERE, "{}", 0, NOWHERE)]); + + string msg; + + if(interrupt) { + msg = "-- Interrupted!"; + + } else { + msg = concat(output, " "); + } + + print(file_user, file_outs, msg); + + // job can now end: + // task_end(file_pipe); + } + } + } else { + echo("[" + PROGRAM_NAME + "] file data offered via unexpected pipe: " + (string)e_ins); + } + } + } else if(n == SIGNAL_INIT) { + // print(e_outs, e_user, "[" + PROGRAM_NAME + "] init event; nothing to do"); + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/lslisp.lsl.lpo b/ARES/application/lslisp.lsl.lpo new file mode 100644 index 0000000..b52e72d --- /dev/null +++ b/ARES/application/lslisp.lsl.lpo @@ -0,0 +1,896 @@ +key E = "00000000-0000-0000-0000-000000000000"; +key gC = "00000000-0000-0000-0000-000000000000"; +key K = "00000000-0000-0000-0000-000000000000"; +string LslUserScript = "program"; +integer g_; +string H = "!!"; + +string A(key llJsonValueType) +{ + string loc_spipe = "p:" + (string)llJsonValueType; + string loc_buffer = llLinksetDataRead(loc_spipe); + integer loc_eor = llSubStringIndex(loc_buffer, ""); + if (~loc_eor) + { + if (loc_eor ^ ~-llStringLength(loc_buffer)) + llLinksetDataWrite(loc_spipe, llDeleteSubString(loc_buffer, 0, loc_eor)); + else + llLinksetDataDelete(loc_spipe); + loc_buffer = llDeleteSubString(loc_buffer, loc_eor, ((integer)-1)); + } + else + { + llLinksetDataDelete(loc_spipe); + } + return loc_buffer; +} + +string System = "{}"; +list UThreadStackFrame; +list gB; +list gA; +key D; +key I; +key L; + +list C(list llJsonValueType) +{ + string loc_prog; + list loc_ix; + integer loc_parent; + string loc_local_vars; + integer loc_return_mode; + integer loc_last_symbol; + { + string loc_frame = llList2String(llJsonValueType, ((integer)-1)); + llJsonValueType = llDeleteSubList(llJsonValueType, ((integer)-1), ((integer)-1)); + loc_prog = llJsonGetValue(loc_frame, (list)"c"); + loc_ix = llJson2List(llJsonGetValue(loc_frame, (list)"i")); + loc_parent = (integer)llJsonGetValue(loc_frame, (list)"p"); + loc_local_vars = llJsonGetValue(loc_frame, (list)"v"); + loc_return_mode = (integer)llJsonGetValue(loc_frame, (list)"r"); + loc_last_symbol = (integer)llJsonGetValue(loc_frame, (list)"sp"); + } + if (D) + { + D = ""; + } + if (loc_prog == "") + return llJsonValueType; + integer loc_descending; + integer loc_backtrack; + if (loc_ix == []) + { + loc_ix = (list)((integer)-1); + loc_descending = 1; + } + while (1) + { + @J_autoGen00001; + loc_ix = llListReplaceList(loc_ix, (list)(-~llList2Integer(loc_ix, ((integer)-1))), ((integer)-1), ((integer)-1)); + if (llJsonValueType(loc_prog, loc_ix) == "﷐") + { + if (!~-(loc_ix != [])) + { + if (~loc_parent) + { + integer loc_previous_return_mode = loc_return_mode; + string loc_frame = llList2String(llJsonValueType, loc_parent); + if (!(llGetSubString(loc_frame, 0, 0) == "{")) + { + llOwnerSay(">> No frame at " + (string)loc_parent + " in: " + llList2Json("﷒", llJsonValueType)); + return []; + } + llJsonValueType = llDeleteSubList(llJsonValueType, loc_parent, loc_parent); + loc_prog = llJsonGetValue(loc_frame, (list)"c"); + loc_ix = llJson2List(llJsonGetValue(loc_frame, (list)"i")); + loc_parent = (integer)llJsonGetValue(loc_frame, (list)"p"); + loc_local_vars = llJsonGetValue(loc_frame, (list)"v"); + loc_return_mode = (integer)llJsonGetValue(loc_frame, (list)"r"); + loc_last_symbol = (integer)llJsonGetValue(loc_frame, (list)"sp"); + string loc_structure = llJsonGetValue(loc_prog, loc_ix); + list loc_step_again = loc_ix; + if (~-loc_previous_return_mode) + { + if (loc_previous_return_mode == 2) + { + llJsonValueType = llJsonValueType + loc_frame; + loc_prog = "[" + llJsonGetValue(loc_prog, llListReplaceList(loc_step_again, (list)1, ((integer)-1), ((integer)-1))) + "]"; + loc_return_mode = 1; + loc_local_vars = "{}"; + loc_parent = ~-(llJsonValueType != []); + loc_ix = (list)((integer)-1); + loc_last_symbol = ((integer)-1); + loc_descending = 1; + } + } + else + { + integer loc_retval = (integer)llList2String(llJsonValueType, ((integer)-1)); + llJsonValueType = llDeleteSubList(llJsonValueType, ((integer)-1), ((integer)-1)); + loc_ix = llDeleteSubList(loc_ix, ((integer)-1), ((integer)-1)); + if (loc_structure == "if") + { + string loc_next_frame = llJsonSetValue(loc_frame, (list)"i", llList2Json("﷒", loc_ix)); + list loc_subix = loc_ix; + if (loc_retval) + { + loc_subix = loc_subix + 2; + } + else if (llJsonValueType(loc_prog, loc_ix + 3) == "﷒") + { + loc_subix = loc_subix + 3; + } + else + { + jump J_autoGen00001; + } + loc_prog = "[" + llJsonGetValue(loc_prog, loc_subix) + "]"; + llJsonValueType = llJsonValueType + loc_next_frame; + loc_return_mode = 0; + } + else if (loc_structure == "while") + { + if (loc_retval) + { + llJsonValueType = llJsonValueType + loc_frame; + loc_prog = "[" + llJsonGetValue(loc_prog, loc_ix + 2) + "]"; + loc_descending = 1; + loc_return_mode = 2; + } + else + { + jump J_autoGen00001; + } + } + loc_local_vars = "{}"; + loc_parent = ~-(llJsonValueType != []); + loc_last_symbol = ((integer)-1); + loc_ix = (list)((integer)-1); + } + jump J_autoGen00001; + } + else + { + return llJsonValueType; + } + } + loc_backtrack = 1; + loc_ix = llDeleteSubList(loc_ix, ((integer)-1), ((integer)-1)); + } + else + while (llJsonValueType(loc_prog, loc_ix) == "﷒") + { + loc_ix = loc_ix + 0; + loc_descending = 1; + } + string loc_type = llJsonValueType(loc_prog, loc_ix); + if (loc_type == "﷓") + { + llJsonValueType = llJsonValueType + (float)llJsonGetValue(loc_prog, loc_ix); + } + else if (loc_descending) + { + string loc_verb = llJsonGetValue(loc_prog, loc_ix); + if (loc_verb == "if" | loc_verb == "while") + { + llJsonValueType = llJsonValueType + ("{\"c\":" + loc_prog + ",\"i\":" + llList2Json("﷒", loc_ix) + ",\"sp\":" + (string)loc_last_symbol + ",\"p\":" + (string)loc_parent + ",\"v\":" + loc_local_vars + ",\"r\":" + (string)loc_return_mode + ",\"t\":0}"); + loc_prog = "[" + llJsonGetValue(loc_prog, llDeleteSubList(loc_ix, ((integer)-1), ((integer)-1)) + 1) + "]"; + loc_return_mode = 1; + loc_local_vars = "{}"; + loc_parent = ~-(llJsonValueType != []); + loc_ix = (list)((integer)-1); + loc_last_symbol = ((integer)-1); + loc_descending = 1; + jump J_autoGen00001; + } + else if (loc_verb == "lambda") + { + loc_ix = llDeleteSubList(loc_ix, ((integer)-1), ((integer)-1)); + string loc_arglist = llDumpList2String(llJson2List(llJsonGetValue(loc_prog, loc_ix + 1)), " "); + string loc_code = llJsonGetValue(loc_prog, loc_ix + 2); + loc_descending = loc_backtrack = 0; + llJsonValueType = llJsonValueType + ("{\"c\":" + loc_code + ",\"t\":2,\"a\":\"" + loc_arglist + "\"}"); + jump J_autoGen00001; + } + else + { + llJsonValueType = llJsonValueType + ("{\"s\":\"" + loc_verb + "\"" + ",\"t\":1,\"sp\":" + (string)loc_last_symbol + "}"); + loc_last_symbol = ~-(llJsonValueType != []); + } + } + else if (loc_type == "﷔") + { + string loc_s = llJsonGetValue(loc_prog, loc_ix); + if (loc_s == "\\\"\\\"") + { + llJsonValueType = llJsonValueType + ""; + } + else if (llGetSubString(loc_s, 0, 1) == "\\\"") + { + llJsonValueType = llJsonValueType + llGetSubString(loc_s, 2, ((integer)-3)); + } + else if (llGetSubString(loc_s, 0, 0) == "$") + { + string loc_name = llDeleteSubString(loc_s, 0, 0); + string loc_local_type = llJsonValueType(loc_local_vars, (list)loc_name); + integer loc_temp_parent = loc_parent; + string loc_value; + if (loc_local_type == "﷐") + while (loc_local_type == "﷐") + { + if (~loc_temp_parent) + { + string loc_fr = llList2String(llJsonValueType, loc_temp_parent); + loc_local_type = llJsonValueType(loc_fr, (list)"v" + loc_name); + loc_value = llJsonGetValue(loc_fr, (list)"v" + loc_name); + loc_temp_parent = (integer)llJsonGetValue(loc_fr, (list)"p"); + } + else + { + integer loc_si = llListFindList(UThreadStackFrame, (list)loc_name); + if (~loc_si) + { + integer loc_global_type = llGetListEntryType(gB, loc_si); + if (loc_global_type ^ 2) + if (~-loc_global_type) + llJsonValueType = llJsonValueType + llList2String(gB, loc_si); + else + llJsonValueType = llJsonValueType + llList2Integer(gB, loc_si); + else + llJsonValueType = llJsonValueType + llList2Float(gB, loc_si); + jump J_autoGen00001; + } + else + { + llOwnerSay(">> uninitialized variable: " + loc_s); + return []; + } + } + } + else + { + loc_value = llJsonGetValue(loc_local_vars, (list)loc_name); + } + if (loc_local_type == "﷓") + { + if (llFabs((float)loc_value + -(float)((integer)(0.5 + (float)loc_value))) < 7.6293945e-06) + llJsonValueType = llJsonValueType + (integer)loc_value; + else + llJsonValueType = llJsonValueType + (float)loc_value; + } + else + { + llJsonValueType = llJsonValueType + loc_value; + } + } + else + { + llJsonValueType = llJsonValueType + loc_s; + } + } + else if (loc_backtrack) + { + string loc_verbf = llList2String(llJsonValueType, loc_last_symbol); + string loc_verb = llJsonGetValue(loc_verbf, (list)"s"); + if (loc_verb == "") + { + loc_last_symbol = (integer)llJsonGetValue(loc_verbf, (list)"sp"); + loc_backtrack = loc_descending = 0; + jump J_autoGen00001; + } + integer loc_c; + list loc_params; + { + integer loc_scount = (llJsonValueType != []) + -loc_last_symbol; + loc_c = ~-loc_scount; + if (loc_c) + loc_params = llList2List(llJsonValueType, -~loc_last_symbol, ((integer)-1)); + } + llJsonValueType = llDeleteSubList(llJsonValueType, loc_last_symbol, ((integer)-1)); + loc_last_symbol = (integer)llJsonGetValue(loc_verbf, (list)"sp"); + if (loc_verb == "list") + { + llJsonValueType = llJsonValueType + llList2Json("﷒", loc_params); + } + else if (loc_verb == "print") + { + { + llLinksetDataWrite("p:" + (string)L, llLinksetDataRead("p:" + (string)L) + (string)loc_params + ""); + llRegionSayTo(gC, 2046828547, "%1" + H + (string)L + " " + (string)I); + } + } + else if (loc_verb == "global") + { + string loc_name = llList2String(loc_params, 0); + string loc_data; + if (2 < loc_c) + loc_data = llList2Json("﷒", llList2List(loc_params, 1, ((integer)-1))); + else if (loc_c == 2) + loc_data = llList2String(loc_params, 1); + integer loc_si = llListFindList(UThreadStackFrame, (list)loc_name); + if (~loc_si) + { + gB = llListReplaceList(gB, (list)loc_data, loc_si, loc_si); + } + else + { + UThreadStackFrame = UThreadStackFrame + loc_name; + gB = gB + loc_data; + } + } + else if (loc_verb == "set") + { + if (loc_c < 2) + { + llOwnerSay(">> unary set."); + return []; + } + string loc_name = llList2String(loc_params, 0); + list loc_data = llList2List(loc_params, 1, ((integer)-1)); + string loc_local_type = llJsonValueType(loc_local_vars, (list)loc_name); + integer loc_old_parent = ((integer)-1); + integer loc_temp_parent = loc_parent; + string loc_fr; + while (loc_local_type == "﷐") + { + if (~loc_temp_parent) + { + loc_fr = llList2String(llJsonValueType, loc_temp_parent); + loc_local_type = llJsonValueType(loc_fr, (list)"v" + loc_name); + loc_old_parent = loc_temp_parent; + loc_temp_parent = (integer)llJsonGetValue(loc_fr, (list)"p"); + } + else + { + integer loc_si = llListFindList(UThreadStackFrame, (list)loc_name); + if (~loc_si) + { + gB = llListReplaceList(gB, loc_data, loc_si, loc_si); + } + else + { + loc_local_vars = llJsonSetValue(loc_local_vars, (list)loc_name, (string)loc_data); + } + jump J_autoGen00002; + } + } + if (~loc_old_parent) + llJsonValueType = llListReplaceList(llJsonValueType, (list)llJsonSetValue(loc_fr, (list)"v" + loc_name, (string)loc_data), loc_old_parent, loc_old_parent); + else + loc_local_vars = llJsonSetValue(loc_local_vars, (list)loc_name, (string)loc_data); + @J_autoGen00002; + } + else if (loc_verb == "+") + { + float loc_out = 0; + while (loc_c--) + loc_out = loc_out + llList2Float(loc_params, loc_c); + llJsonValueType = llJsonValueType + loc_out; + } + else if (loc_verb == "*") + { + float loc_out = 1; + while (loc_c--) + loc_out = loc_out * llList2Float(loc_params, loc_c); + llJsonValueType = llJsonValueType + loc_out; + } + else if (loc_verb == "==") + { + if (llGetListEntryType(loc_params, 0) ^ 3) + if (~-llGetListEntryType(loc_params, 0)) + llJsonValueType = llJsonValueType + (llList2Float(loc_params, 0) == llList2Float(loc_params, 1)); + else + llJsonValueType = llJsonValueType + (llList2Integer(loc_params, 0) == llList2Integer(loc_params, 1)); + else + llJsonValueType = llJsonValueType + (llList2String(loc_params, 0) == llList2String(loc_params, 1)); + } + else if (loc_verb == "!=") + { + if (llGetListEntryType(loc_params, 0) ^ 3) + if (~-llGetListEntryType(loc_params, 0)) + llJsonValueType = llJsonValueType + !(llList2Float(loc_params, 0) == llList2Float(loc_params, 1)); + else + llJsonValueType = llJsonValueType + (llList2Integer(loc_params, 0) ^ llList2Integer(loc_params, 1)); + else + llJsonValueType = llJsonValueType + !(llList2String(loc_params, 0) == llList2String(loc_params, 1)); + } + else if (loc_verb == "<") + { + llJsonValueType = llJsonValueType + (llList2Float(loc_params, 0) < llList2Float(loc_params, 1)); + } + else if (loc_verb == ">") + { + llJsonValueType = llJsonValueType + (llList2Float(loc_params, 1) < llList2Float(loc_params, 0)); + } + else if (loc_verb == "<=") + { + llJsonValueType = llJsonValueType + !(llList2Float(loc_params, 1) < llList2Float(loc_params, 0)); + } + else if (loc_verb == ">=") + { + llJsonValueType = llJsonValueType + !(llList2Float(loc_params, 0) < llList2Float(loc_params, 1)); + } + else if (loc_verb == "and") + { + integer loc_out = 1; + while (loc_c--) + loc_out = loc_out & llList2Integer(loc_params, loc_c); + llJsonValueType = llJsonValueType + loc_out; + } + else if (loc_verb == "or") + { + integer loc_out; + while (loc_c--) + loc_out = loc_out | llList2Integer(loc_params, loc_c); + llJsonValueType = llJsonValueType + loc_out; + } + else if (loc_verb == "not" | loc_verb == "!") + { + llJsonValueType = llJsonValueType + !llList2Integer(loc_params, 0); + } + else if (loc_verb == "-") + { + if (1 < loc_c) + { + float loc_out = llList2Float(loc_params, 0); + --loc_c; + while (loc_c--) + { + loc_out = loc_out + -llList2Float(loc_params, -~loc_c); + } + llJsonValueType = llJsonValueType + loc_out; + } + else + { + llJsonValueType = llJsonValueType + -llList2Float(loc_params, 0); + } + } + else if (loc_verb == "/") + { + if (1 < loc_c) + { + float loc_out = llList2Float(loc_params, 0); + --loc_c; + while (loc_c--) + { + float loc_newv = llList2Float(loc_params, -~loc_c); + if (loc_newv == 0) + { + llOwnerSay(">> Division by zero."); + return []; + } + else + loc_out = loc_out / loc_newv; + } + llJsonValueType = llJsonValueType + loc_out; + } + else + { + float loc_out = llList2Float(loc_params, 0); + if (loc_out == 0) + { + llOwnerSay(">> Division by zero."); + return []; + } + else + llJsonValueType = llJsonValueType + ((float)1) / loc_out; + } + } + else if (loc_verb == "apply") + { + llJsonValueType = llJsonValueType + ("{\"c\":" + loc_prog + ",\"i\":" + llList2Json("﷒", loc_ix) + ",\"sp\":" + (string)loc_last_symbol + ",\"p\":" + (string)loc_parent + ",\"v\":" + loc_local_vars + ",\"r\":" + (string)loc_return_mode + ",\"t\":0}"); + loc_local_vars = "{}"; + loc_ix = (list)((integer)-1); + loc_parent = ~-(llJsonValueType != []); + { + string loc_func = llList2String(loc_params, 0); + list loc_arg_names = llParseString2List(llJsonGetValue(loc_func, (list)"a"), (list)" ", []); + integer loc_an = loc_arg_names != []; + while (loc_an--) + { + loc_local_vars = llJsonSetValue(loc_local_vars, (list)llList2String(loc_arg_names, loc_an), llList2String(loc_params, -~loc_an)); + } + loc_prog = "[" + llJsonGetValue(loc_func, (list)"c") + "]"; + } + loc_descending = loc_backtrack = 0; + jump J_autoGen00001; + } + else if (loc_verb == "concat") + { + llJsonValueType = llJsonValueType + llDumpList2String(llJson2List(llList2String(loc_params, 1)), llList2String(loc_params, 0)); + } + else if (loc_verb == "split") + { + llJsonValueType = llJsonValueType + llList2Json("﷒", llParseString2List(llList2String(loc_params, 1), (list)llList2String(loc_params, 0), [])); + } + else if (loc_verb == "get") + { + llJsonValueType = llJsonValueType + llJsonGetValue(llList2String(loc_params, 0), llJson2List(llList2String(loc_params, 1))); + } + else if (loc_verb == "index") + { + llJsonValueType = llJsonValueType + llListFindList(llJson2List(llList2String(loc_params, 0)), (list)llList2String(loc_params, 1)); + } + else if (loc_verb == "count") + { + llJsonValueType = llJsonValueType + (llJson2List(llList2String(loc_params, 0)) != []); + } + else if (loc_verb == "put") + { + llJsonValueType = llJsonValueType + llJsonSetValue(llList2String(loc_params, 0), llJson2List(llList2String(loc_params, 1)), llList2String(loc_params, 2)); + } + else if (loc_verb == "char") + { + integer loc_p = llList2Integer(loc_params, 1); + llJsonValueType = llJsonValueType + llGetSubString(llList2String(loc_params, 0), loc_p, loc_p); + } + else if (loc_verb == "substr") + { + llJsonValueType = llJsonValueType + llGetSubString(llList2String(loc_params, 0), llList2Integer(loc_params, 1), llList2Integer(loc_params, 2)); + } + else if (loc_verb == "strpos") + { + llJsonValueType = llJsonValueType + llSubStringIndex(llList2String(loc_params, 0), llList2String(loc_params, 1)); + } + else if (loc_verb == "rand") + { + llJsonValueType = llJsonValueType + llFrand(1); + } + else if (loc_verb == "int") + { + llJsonValueType = llJsonValueType + (integer)llList2String(loc_params, 0); + } + else if (loc_verb == "getd") + { + llJsonValueType = llJsonValueType + llJsonGetValue(llLinksetDataRead(llList2String(loc_params, 0)), llParseStringKeepNulls(llList2String(loc_params, 1), (list)".", [])); + } + else if (loc_verb == "setd") + { + llLinksetDataWrite(llList2String(loc_params, 0), llJsonSetValue(llLinksetDataRead(llList2String(loc_params, 0)), llParseStringKeepNulls(llList2String(loc_params, 1), (list)".", []), llList2String(loc_params, 2))); + } + else if (loc_verb == "deleted") + { + llLinksetDataWrite(llList2String(loc_params, 0), llJsonSetValue(llLinksetDataRead(llList2String(loc_params, 0)), llParseStringKeepNulls(llList2String(loc_params, 1), (list)".", []), "﷘")); + } + else if (llGetSubString(loc_verb, 0, 0) == "@") + { + llMessageLinked(1, 0, "!!" + H + (string)L + " " + "00000000-0000-0000-0000-000000000000" + " " + (string)I + " " + llDeleteSubString(loc_verb, 0, 0) + " " + (string)loc_params, ""); + } + else if (llGetSubString(loc_verb, 0, 0) == "#") + { + llMessageLinked(((integer)-4), (integer)llDeleteSubString(loc_verb, 0, 0), (string)loc_params, I); + } + else if (loc_verb == "﷐") + { + llJsonValueType = llJsonValueType + "[]"; + } + else if (loc_verb == "﷕") + { + if (loc_c) + llJsonValueType = llJsonValueType + llList2List(loc_params, ((integer)-1), ((integer)-1)); + } + else + { + llOwnerSay(">> unknown verb: " + loc_verb); + } + } + else + { + llOwnerSay(">> unknown data type: " + loc_type); + } + loc_descending = loc_backtrack = 0; + } + return llJsonValueType; +} + +string _(list llJsonValueType) +{ + list loc_opens; + integer loc_tcount = llJsonValueType != []; + integer loc_open_quote = ((integer)-1); + integer loc_comment_start = ((integer)-1); + integer loc_i; + for (; loc_i < loc_tcount; ++loc_i) + { + string loc_t = llList2String(llJsonValueType, loc_i); + if (~loc_comment_start) + { + if (loc_t == "\n") + { + llJsonValueType = llDeleteSubList(llJsonValueType, loc_comment_start, loc_i); + loc_tcount = loc_tcount + ~(loc_i + -loc_comment_start); + loc_i = loc_comment_start; + loc_comment_start = ((integer)-1); + } + } + else if (~loc_open_quote) + { + if (loc_t == "\"") + { + string loc_str; + if (-~loc_open_quote ^ loc_i) + loc_str = "\\\"" + llDumpList2String(llList2List(llJsonValueType, -~loc_open_quote, ~-loc_i), " ") + "\\\""; + else + loc_str = ""; + llJsonValueType = llListReplaceList(llJsonValueType, (list)loc_str, loc_open_quote, loc_i); + loc_i = loc_open_quote; + loc_open_quote = ((integer)-1); + } + } + else + { + if (loc_t == "") + { + llJsonValueType = llDeleteSubList(llJsonValueType, loc_i, loc_i); + --loc_i; + --loc_tcount; + } + else if (loc_t == "\"") + { + loc_open_quote = loc_i; + } + else if (loc_t == "(") + { + loc_opens = loc_opens + loc_i; + } + else if (loc_t == ";") + { + loc_comment_start = loc_i; + } + else if (loc_t == "\n") + { + llJsonValueType = llDeleteSubList(llJsonValueType, loc_i, loc_i); + --loc_i; + --loc_tcount; + } + else if (loc_t == ")") + { + if (loc_opens == []) + { + llOwnerSay("++ Excess closing parens at " + (string)loc_i + "."); + return ""; + } + integer loc_L = llList2Integer(loc_opens, ((integer)-1)); + loc_opens = llDeleteSubList(loc_opens, ((integer)-1), ((integer)-1)); + string loc_frag; + if (-~loc_L ^ loc_i) + loc_frag = llList2Json("﷒", llList2List(llJsonValueType, -~loc_L, ~-loc_i)); + else + loc_frag = "[]"; + if (llJsonValueType(loc_frag, (list)0) == "﷒") + loc_frag = llList2Json("﷒", "﷕" + llList2List(llJsonValueType, -~loc_L, ~-loc_i)); + llJsonValueType = llListReplaceList(llJsonValueType, (list)loc_frag, loc_L, loc_i); + loc_tcount = llJsonValueType != []; + loc_i = loc_L; + } + } + } + if (~loc_open_quote) + { + llOwnerSay("++ unclosed quote following: " + llDumpList2String(llList2List(llJsonValueType, ~-~-loc_open_quote, 4 + loc_open_quote), " ")); + return ""; + } + else if (loc_opens != []) + { + llOwnerSay("++ imbalanced parens (" + (string)(loc_opens != []) + ")."); + return ""; + } + else + { + return llList2Json("﷒", llJsonValueType); + } +} + +integer F; +integer J; +key Pop; +key edefaultchat; +key LslLibrary; +string edefaultstate_entry; +string ResumeVoid; + +B(integer llJsonValueType, integer llGetListEntryType, string llFrand, key llList2String, key llSubStringIndex, key llListen) +{ + if (llGetListEntryType) + if (llGetListEntryType ^ 275) + { + if (llGetListEntryType ^ 4093) + if (llGetListEntryType ^ 3850) + { + llOwnerSay("[" + LslUserScript + "] unimplemented signal " + (string)llGetListEntryType + ": " + llFrand); + } + else + { + llOwnerSay("[" + LslUserScript + "] failed to run '" + llFrand + "' (kernel could not find the program specified)"); + } + } + else + { + if (llFrand == LslUserScript + " file") + { + if (llSubStringIndex == LslLibrary) + { + string loc_file_buffer; + { + loc_file_buffer = A(LslLibrary); + } + integer loc_read_length = 10; + if (ResumeVoid == "b") + loc_read_length = loc_read_length * 1024; + if (~J) + { + F = F + loc_read_length; + } + else + { + list loc_stat = llParseString2List(loc_file_buffer, (list)" ", []); + J = (integer)llList2String(loc_stat, 0); + ResumeVoid = llList2String(loc_stat, 1); + F = 0; + } + if (J < loc_read_length + F) + loc_read_length = J + -F; + if (F) + { + gA = gA + ("\n" + llParseStringKeepNulls(loc_file_buffer, (list)" ", (list)"(" + ")" + "\"")); + if (F < J) + { + llRegionSayTo(gC, 2046828547, "%!" + H + (string)LslLibrary + " " + LslUserScript + " " + edefaultstate_entry + " " + ((string)F + " " + (string)loc_read_length)); + } + else + { + llRegionSayTo(gC, 2046828547, "%3" + H + "[\"" + (string)LslLibrary + "\"]"); + I = edefaultchat; + L = Pop; + string loc_prog = _(gA); + list loc_output = C((list)("{\"c\":" + loc_prog + ",\"i\":" + "[]" + ",\"sp\":" + "-1" + ",\"p\":" + "-1" + ",\"v\":" + "{}" + ",\"r\":" + "0" + ",\"t\":0}")); + string loc_msg; + if (D) + { + loc_msg = "-- Interrupted!"; + } + else + { + loc_msg = llDumpList2String(loc_output, " "); + } + { + llLinksetDataWrite("p:" + (string)edefaultchat, llLinksetDataRead("p:" + (string)edefaultchat) + loc_msg + ""); + llRegionSayTo(gC, 2046828547, "%1" + H + (string)edefaultchat + " " + (string)Pop); + } + } + } + else + { + if (0 < J) + { + llRegionSayTo(gC, 2046828547, "%!" + H + (string)LslLibrary + " " + LslUserScript + " " + edefaultstate_entry + " " + ((string)F + " " + (string)loc_read_length)); + } + else + { + { + llLinksetDataWrite("p:" + (string)edefaultchat, llLinksetDataRead("p:" + (string)edefaultchat) + "No file: " + edefaultstate_entry + ""); + llRegionSayTo(gC, 2046828547, "%1" + H + (string)edefaultchat + " " + (string)edefaultchat); + } + llRegionSayTo(gC, 2046828547, "%3" + H + "[\"" + (string)LslLibrary + "\"]"); + System = llJsonSetValue(System, (list)LslLibrary, "﷘"); + } + } + } + else + { + llOwnerSay("[" + LslUserScript + "] file data offered via unexpected pipe: " + (string)llSubStringIndex); + } + } + } + else + { + list loc_argv = llParseString2List(llFrand, (list)" ", []); + integer loc_argc = loc_argv != []; + if (~-loc_argc) + { + Pop = llList2String; + edefaultchat = llListen; + gA = UThreadStackFrame = gB = []; + D = ""; + edefaultstate_entry = llList2String(loc_argv, 1); + ResumeVoid = ""; + F = J = ((integer)-1); + llRegionSayTo(gC, 2046828547, "%!" + H + (string)(LslLibrary = llGenerateKey()) + " " + LslUserScript + " " + edefaultstate_entry + " " + "-1"); + { + System = llJsonSetValue(System, (list)LslLibrary, edefaultstate_entry); + llMessageLinked(1, 3859, LslUserScript + "," + (string)(G = G | 1), ""); + } + } + else + { + { + llLinksetDataWrite("p:" + (string)llList2String, llLinksetDataRead("p:" + (string)llList2String) + "Syntax: @" + LslUserScript + " " + ""); + llRegionSayTo(gC, 2046828547, "%1" + H + (string)llList2String + " " + (string)llListen); + } + } + } +} + +integer edefaultrez; +integer UThread; +key IsRestoring; +key Library; +key IsSaveDue; +integer G; + +default +{ + state_entry() + { + E = llGetLinkKey(1); + gC = llGetLinkKey(2); + K = llGetLinkKey(3); + LslUserScript = llGetScriptName(); + edefaultrez = llListen(2046820352, "", E, ""); + llMessageLinked(1, 3840, LslUserScript + "\n" + "0.2.5" + " " + "lang " + "0.2.3", ""); + } + + on_rez(integer llJsonValueType) + { + llResetScript(); + } + + listen(integer llListen, string llFrand, key llGetListEntryType, string llJsonValueType) + { + integer loc_n = (((integer)-33) + llOrd(llJsonValueType, 0)) * 64 + (((integer)-33) + llOrd(llJsonValueType, 1)); + if (llListen ^ 2046820352) + { + IsRestoring = "00000000-0000-0000-0000-000000000000"; + Library = "00000000-0000-0000-0000-000000000000"; + string loc_remainder = llDeleteSubString(llJsonValueType, 0, 1); + if (loc_n & -!(loc_n == 275)) + { + B(UThread = 0, loc_n, loc_remainder, "00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000000"); + } + else + { + Library = llGetSubString(loc_remainder, 2, 37); + IsRestoring = llGetSubString(loc_remainder, 39, 74); + IsSaveDue = llGetSubString(loc_remainder, 76, 111); + B(UThread = (((integer)-33) + llOrd(loc_remainder, 0)) * 64 + (((integer)-33) + llOrd(loc_remainder, 1)), loc_n, llDeleteSubString(loc_remainder, 0, 112), Library, IsRestoring, IsSaveDue); + } + if (UThread & -(llJsonGetValue(System, (list)((string)IsRestoring)) == "﷐")) + llRegionSayTo(gC, 2046828547, "%3" + H + "V" + llList2Json("﷒", (list)IsRestoring)); + if (llList2ListStrided(llJson2List(System), 0, ((integer)-1), 2) == []) + { + { + if (UThread) + llMessageLinked(1, 4, llChar(33 + ((UThread & 4032) >> 6)) + llChar(33 + (UThread & 63)) + (string)IsRestoring + " " + LslUserScript, ""); + llMessageLinked(1, 3849, LslUserScript, ""); + G = G & ((integer)-2); + llSetScriptState(LslUserScript, 0); + llSleep(0.022); + } + } + else + { + if (UThread) + llMessageLinked(1, 4, llChar(33 + ((UThread & 4032) >> 6)) + llChar(33 + (UThread & 63)) + (string)IsRestoring + " " + LslUserScript, ""); + } + } + else + { + if (loc_n == 3841) + { + string loc_expected_program_name = llDeleteSubString(llJsonValueType, 0, 3); + if (loc_expected_program_name == LslUserScript) + { + llListenRemove(edefaultrez); + H = llGetSubString(llJsonValueType, 2, 3); + g_ = (((integer)-33) + llOrd(H, 0)) * 64 + (((integer)-33) + llOrd(H, 1)); + integer loc_C = 2046832640 + g_; + edefaultrez = llListen(loc_C, "", E, ""); + llMessageLinked(1, 3859, LslUserScript + "," + (string)G, ""); + B(UThread = 0, 4093, "", "00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000000"); + llMessageLinked(1, 3860, LslUserScript, ""); + } + } + } + } +} diff --git a/ARES/application/mail.lsl b/ARES/application/mail.lsl new file mode 100644 index 0000000..d73b7b3 --- /dev/null +++ b/ARES/application/mail.lsl @@ -0,0 +1,102 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Mail Utility + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "1.1.1" +#define CLIENT_VERSION_TAGS "release" + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + string msg = ""; + if(argc == 1 || argc == 2) { + msg = "Syntax: " + PROGRAM_NAME + "
[-b ]\n\nSends email to the targeted recipient. If -b is not specified, the body is read from the input stream.\n\nCaveats:\n - LSL enforces a 20-second sleep after each email is sent.\n - A given avatar may only generate 500 emails per hour.\n - Sending non-ASCII characters in the subject line may cause the email to fail entirely.\n - Only one recipient may be targeted.\n - The entire message (including all email headers) must fit in 4096 bytes, so keep it short."; + } else { + string recipient = gets(argv, 1); + string subject = "(no subject)"; + string body; + integer bi = index(argv, "-b"); + if(~bi) { + if(bi == 1) { + msg = "No recipient specified."; + jump fail; + } else if(bi > 1) { + if(bi == argc - 1) { + msg = "No body specified."; + jump fail; + } + + body = concat(sublist(argv, bi + 1, LAST), " "); + + if(bi > 2) + subject = concat(sublist(argv, 2, bi - 1), " "); + } + } else if(ins == NULL_KEY || ins == user) { + msg = "No body specified."; + jump fail; + } else { + subject = concat(delrange(argv, 0, 1), " "); + pipe_read(ins, body); + if(body == "") { + msg = "Empty body."; + jump fail; + } + } + + print(outs, user, "Sending email to " + recipient + "..."); + llEmail(recipient, subject, body); + msg = "Email sent to " + recipient + "."; + @fail; + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/mantra.lsl b/ARES/application/mantra.lsl new file mode 100644 index 0000000..bdb8b82 --- /dev/null +++ b/ARES/application/mantra.lsl @@ -0,0 +1,308 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Mantra Application + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#include + +#define CLIENT_VERSION "1.0.1" +#define CLIENT_VERSION_TAGS "release" + +string fg_image = "947c7a17-4e88-2f96-b419-dc37b647127d"; +string bg_image = "cf45ccd4-9bd0-1835-08f7-3883e234f3e1"; + +string loop_sound = "70f93e08-31c1-6076-4ea7-47a7b8a6ca3f"; +float loop_vol = 0.5; +float start_speed = 0.125; +float stop_speed = 0.25; + +// float screen_width = 1920; +// float screen_height = 1008; +// float diagonal_aspect_ratio = llSqrt(1 + llPow(screen_width / screen_height, 2.0)); +// float diagonal_aspect_ratio = 2.16; // 2.1513060948717175859320949846553 +// (ratio of screen height to diagonal width - minimum 1.0) +// for very wide screens: +// float diagonal_aspect_ratio = 3; +float diagonal_aspect_ratio = 2.16; + +vector fg_color; +vector bg_color; + +list mantras; + +integer content_i; +integer mantra_interval; + +end_meditation() { + task_end("mantra"); + set_timer("mantra", 0); + + llResetTime(); + float t = 0; + while(t < 1) { + llSleep(0.01); + t += llGetAndResetTime() * stop_speed; + if(t > 1) t = 1; + setp(WIZARD_TEXT + 2, [PRIM_COLOR, ALL_SIDES, bg_color, 1.0 - t]); + } + + llResetTime(); + t = 0; + while(t < 1) { + llSleep(0.01); + t += llGetAndResetTime() * stop_speed; + if(t > 1) t = 1; + setp(WIZARD + 2, [PRIM_COLOR, ALL_SIDES, fg_color, 1.0 - t]); + llAdjustSoundVolume((1.0 - t) * loop_vol); + } + + setp(WIZARD + 2, [ + PRIM_SIZE, ZV, + PRIM_COLOR, ALL_SIDES, ZV, 0, + PRIM_ROT_LOCAL, INVISIBLE, + PRIM_POSITION, OFFSCREEN, + PRIM_OMEGA, ZV, 0, 0, + PRIM_LINK_TARGET, WIZARD_TEXT + 2, + PRIM_SIZE, ZV, + PRIM_COLOR, ALL_SIDES, ZV, 0, + PRIM_ROT_LOCAL, INVISIBLE, + PRIM_POSITION, OFFSCREEN, + PRIM_OMEGA, ZV, 0, 0, + PRIM_LINK_TARGET, WIZARD_TEXT, + PRIM_TEXT, "", ZV, 0, + PRIM_POSITION, OFFSCREEN + ]); + llStopSound(); +} + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + string msg = ""; + if(argc == 1) { + msg = "Syntax: " + PROGRAM_NAME + " start|stop"; + } else { + string action = gets(argv, 1); + if(action == "sleep") { + float duration = (float)gets(argv, 2); + if(duration <= 0.1) duration = 0.1; + llSleep(duration); + } else if(action == "start") { + task_begin("mantra", user); + + if(llLinksetDataRead("mantra") == JSON_INVALID) { + print(outs, user, "Database not configured. Please run @db load mantra.db"); + return; + } + + print(outs, user, "Mantra session beginning."); + + content_i = 0; + + loop_sound = getdbl("mantra", ["loop", "sound"]); + loop_vol = (float)getdbl("mantra", ["loop", "volume"]); + start_speed = (float)getdbl("mantra", ["speed", "start"]); + stop_speed = (float)getdbl("mantra", ["speed", "stop"]); + fg_image = getdbl("mantra", ["fg", "image"]); + bg_image = getdbl("mantra", ["bg", "image"]); + float fg_speed = (float)getdbl("mantra", ["fg", "speed"]); + float bg_speed = (float)getdbl("mantra", ["bg", "speed"]); + diagonal_aspect_ratio = (float)getdbl("mantra", ["scale"]); + integer fg_slot = (integer)getdbl("mantra", ["fg", "color"]); + integer bg_slot = (integer)getdbl("mantra", ["bg", "color"]); + + if(fg_speed == 0) fg_speed = -1.0; + + if(fg_slot == -1) { + fg_color = ONES; + } else { + string color_1 = getdbl("id", ["color", fg_slot]); + fg_color = str2vec(color_1); + if(fg_color == ZV) + fg_color = <1, 1, 1>; + } + + if(bg_slot == -1) { + bg_color = ONES; + } else { + string color_2 = getdbl("id", ["color", bg_slot]); + bg_color = str2vec(color_2); + } + + // this ensures the spiral covers the whole screen reliably + + setp(WIZARD + 2, [ + PRIM_TEXTURE, ALL_SIDES, fg_image, ONES, ZV, 0, + PRIM_COLOR, ALL_SIDES, fg_color, 0, + PRIM_SIZE, ONES * diagonal_aspect_ratio, + PRIM_POSITION, <-2, 0, 0>, + PRIM_ROT_LOCAL, VISIBLE, + PRIM_OMEGA, <1, 0, 0>, fg_speed, 1, + PRIM_LINK_TARGET, WIZARD_TEXT + 2, + PRIM_TEXTURE, ALL_SIDES, bg_image, ONES, ZV, 0, + PRIM_COLOR, ALL_SIDES, bg_color, 0, + PRIM_SIZE, ONES * diagonal_aspect_ratio, + PRIM_POSITION, <-1.9, 0, 0>, + PRIM_ROT_LOCAL, VISIBLE, + PRIM_OMEGA, <1, 0, 0>, bg_speed, 1, + PRIM_LINK_TARGET, WIZARD_TEXT, + PRIM_TEXT, "", ZV, 0, + PRIM_POSITION, ZV + ]); + + llPreloadSound(loop_sound); + + llLoopSound(loop_sound, 0); + + llSleep(0.4); + + llResetTime(); + float t = 0; + while(t < 1) { + llSleep(0.01); + t += llGetAndResetTime() * start_speed; + if(t > 1) t = 1; + setp(WIZARD + 2, [PRIM_COLOR, ALL_SIDES, fg_color, t]); + llAdjustSoundVolume(t * loop_vol); + } + + llResetTime(); + t = 0; + while(t < 1) { + llSleep(0.01); + t += llGetAndResetTime() * start_speed; + if(t > 1) t = 1; + setp(WIZARD_TEXT + 2, [PRIM_COLOR, ALL_SIDES, bg_color, t]); + } + + mantra_interval = (integer)getdbl("mantra", ["interval"]); + mantras = js2list(getdbl("mantra", ["content"])); + set_timer("mantra", mantra_interval); + + } else if(action == "stop") { + integer lock_in = (integer)getdbl("mantra", ["lock-in"]); + + key initiator = getjs(tasks_queue, ["mantra"]); + if(user == initiator && user == avatar && lock_in) { + msg = "Nothing is more important than mental clarity."; + } else if(initiator == user || initiator == JSON_INVALID) { + print(outs, user, "Mantra session ending."); + end_meditation(); + } else { + msg = "Only secondlife:///app/agent/" + (string)initiator + "/about may stop the session."; + } + } + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_TIMER) { + key initiator = getjs(tasks_queue, ["mantra"]); + if(initiator == JSON_INVALID) { + end_meditation(); + return; + } else { + string msg = getdbl("mantra", ["content", content_i++]); + if(msg == JSON_INVALID) { + integer autowake = (integer)getdbl("mantra", ["autowake"]); + if(autowake) { + end_meditation(); + return; + } else { + msg = getdbl("mantra", ["content", content_i=0]); + } + } + + llResetTime(); + float t = 0; + while(t < 1) { + llSleep(0.01); + t += llGetAndResetTime(); + if(t >= 1) + t = 1; + setp(WIZARD_TEXT, [ + PRIM_TEXT, msg, ONES, t + ]); + } + + llSleep(mantra_interval / 2); + + llResetTime(); + t = 0; + while(t < 1) { + llSleep(0.01); + t += llGetAndResetTime(); + if(t >= 1) + t = 1; + setp(WIZARD_TEXT, [ + PRIM_TEXT, msg, ONES, 1.0 - t + ]); + } + } + + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + setp(WIZARD + 2, [ + PRIM_SIZE, ZV, + PRIM_COLOR, ALL_SIDES, ZV, 0, + PRIM_ROT_LOCAL, INVISIBLE, + PRIM_POSITION, OFFSCREEN, + PRIM_OMEGA, ZV, 0, 0, + PRIM_LINK_TARGET, WIZARD_TEXT + 2, + PRIM_SIZE, ZV, + PRIM_COLOR, ALL_SIDES, ZV, 0, + PRIM_ROT_LOCAL, INVISIBLE, + PRIM_POSITION, OFFSCREEN, + PRIM_OMEGA, ZV, 0, 0, + PRIM_LINK_TARGET, WIZARD_TEXT, + PRIM_TEXT, "", ZV, 0, + PRIM_POSITION, OFFSCREEN + ]); + llStopSound(); + set_timer("mantra", 0); + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/media.event.lsl b/ARES/application/media.event.lsl new file mode 100644 index 0000000..d52bb73 --- /dev/null +++ b/ARES/application/media.event.lsl @@ -0,0 +1,59 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * media Utility - Event Handlers + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + + run_time_permissions(integer w) { + if(w & PERMISSION_TRIGGER_ANIMATION) { + integer sn = count(anim_start_queue); + while(sn--) { + llStartAnimation(gets(anim_start_queue, sn)); + } + anim_start_queue = []; + + sn = count(anim_stop_queue); + while(sn--) { + llStopAnimation(gets(anim_stop_queue, sn)); + } + anim_stop_queue = []; + + if(perms_thread != "") { + task_end(perms_thread); + } + } + } + \ No newline at end of file diff --git a/ARES/application/media.lsl b/ARES/application/media.lsl new file mode 100644 index 0000000..1617858 --- /dev/null +++ b/ARES/application/media.lsl @@ -0,0 +1,234 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * media Utility + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "0.3.0" +#define CLIENT_VERSION_TAGS "beta" + +#define S_PLAY 0x01 +#define S_STOP 0x02 +#define S_LOOP 0x04 +#define S_INT 0x08 +#define S_EXT 0x10 +#define S_PRELOAD 0x20 + +#define SILENCE "475b270a-8dc0-2b53-210d-32d4dc6ebc7c" + +integer playing; +float volume = 1.0; +integer venue = 0; + +list anim_start_queue; +list anim_stop_queue; + +string perms_thread; + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + string msg = ""; + integer action = 0; + + if(argc == 1) { + msg = gets(argv, 0) + " [-i|internal] [-x|external] [-v|vol|volume 0.0-1.0] [-n|link|prim ] [-p|play|-l|loop|preload|trigger ] [-p|play ] [-d|duration|-s|sleep|-w|wait ] [stop] [stop ] [freeze] [unfreeze] [-q 0|1]\n\nControls media playback.\n\ninternal/external: select audio channel\nvolume: set sound volume\nlink/prim: set prim number for sound playback (internal only, not compatible with -q)\nplay/loop/trigger: play sound name (loop only supported for internal mode)\nduration/sleep/wait: pause for \nstop: end current sound playback\n-q: toggle sound queueing for internal audio (not compatible with -n)\n\nFor best results, set channel and volume before sound playback command, and add sleep afterward. Multiple commands are allowed.\n\nFuture versions will add a HUD widget for interacting with sound, images, and animations."; + } else { + integer C_LIGHT_BUS = 105 - (integer)("0x" + substr(avatar, 29, 35)); + integer link_number = LINK_THIS; + + integer argi = 1; + while(argi < argc) { + string arg = gets(argv, argi); + + if(arg == "-q") { + ++argi; + integer enable_queueing = (integer)gets(argv, argi); + llSetSoundQueueing(enable_queueing); + + } else if(arg == "-n" || arg == "link" || arg == "prim") { + ++argi; + link_number = (integer)gets(argv, argi); + } else if(arg == "play" || arg == "-p" || arg == "loop" || arg == "-l" || arg == "preload") { + ++argi; + string fname = gets(argv, argi); + integer ftype = llGetInventoryType(fname); + integer do_sound = 0; + integer do_animation = 0; + integer loop = (arg == "loop" || arg == "-l"); + integer preload = (arg == "preload"); + string fuuid = llGetInventoryKey(fname); + if(ftype == INVENTORY_NONE) { + if(validate_key(fname)) { + do_sound = 1; + fuuid = fname; + } else { + msg = "no file '" + fname + "'"; + } + } else if(ftype == INVENTORY_SOUND) { + do_sound = 1; + } else if(ftype == INVENTORY_ANIMATION) { + do_animation = 1; + } else { + msg = fname + ": wrong file type"; + } + + if(do_sound) { + if(preload) { + llPreloadSound(fname); + } else if(venue == S_INT) { + if(loop) { + // llLoopSound(fname, volume); + llLinkPlaySound(link_number, fname, volume, SOUND_LOOP); + } else { + // llPlaySound(fname, volume); + llLinkPlaySound(link_number, fname, volume, SOUND_PLAY); + } + } else if(venue == S_EXT) { + if(fuuid) { + if(loop) { + msg = "external sound playback does not support loop"; + } else { + key controller = getdbl("device", ["controller", "id"]); + if(controller != JSON_INVALID) { + io_tell(controller, C_LIGHT_BUS, "fx s " + fuuid + " " + (string)volume); + } else { + msg = "no device available for external sound playback"; + } + } + } else { + msg = "cannot get UUID of '" + fname + "' for external playback; insufficient inventory permissions"; + } + } + } else if(do_animation) { + if(llGetPermissions() & PERMISSION_TRIGGER_ANIMATION) { + llStartAnimation(fname); + } else { + anim_start_queue += fname; + llRequestPermissions(avatar, PERMISSION_TRIGGER_ANIMATION); + if(perms_thread == "") { + task_begin(perms_thread = llGenerateKey(), ""); + } + } + } + } else if(arg == "trigger") { + string fname = gets(argv, argi); + integer ftype = llGetInventoryType(fname); + integer do_sound = 0; + if(ftype == INVENTORY_NONE) { + if(validate_key(fname)) { + do_sound = 1; + } else { + msg = "no file '" + fname + "'"; + } + } else if(ftype == INVENTORY_SOUND) { + do_sound = 1; + } else { + msg = fname + ": wrong file type"; + } + + if(do_sound) + // llTriggerSound(fname, volume); + llLinkPlaySound(link_number, fname, volume, SOUND_TRIGGER); + } else if(arg == "-i" || arg == "internal") { + venue = S_INT; + } else if(arg == "-x" || arg == "external") { + venue = S_EXT; + } else if(arg == "freeze") { + effector_restrict("mfreeze", "move=?"); + } else if(arg == "unfreeze") { + effector_release("mfreeze"); + } else if(arg == "-s" || arg == "sleep" || arg == "-w" || arg == "wait" || arg == "-d" || arg == "duration") { + ++argi; + float length = (float)gets(argv, argi); + llSleep(argi); + } else if(arg == "stop") { + action = S_STOP; + string fname = gets(argv, argi + 1); + if(llGetInventoryType(fname) == INVENTORY_ANIMATION) { + ++argi; + if(llGetPermissions() & PERMISSION_TRIGGER_ANIMATION) { + llStopAnimation(fname); + } else { + anim_stop_queue += fname; + llRequestPermissions(avatar, PERMISSION_TRIGGER_ANIMATION); + if(perms_thread == "") { + task_begin(perms_thread = llGenerateKey(), ""); + } + } + } else if(venue == S_INT) { + // llStopSound(); + llLinkStopSound(link_number); + } else if(venue == S_EXT) { + key controller = getdbl("device", ["controller", "id"]); + if(controller != JSON_INVALID) { + io_tell(controller, C_LIGHT_BUS, "fx s " + SILENCE + " 0"); + } else { + msg = "no device available for external sound playback"; + } + } + } else if(arg == "-v" || arg == "volume" || arg == "vol") { + ++argi; + volume = (float)gets(argv, argi); + if(venue == S_INT) + llAdjustSoundVolume(volume); + } else { + msg = PROGRAM_NAME + ": unexpected parameter '" + arg + "'; see 'help media' for usage"; + } + + ++argi; + } + } + + if(msg != "") { + print(outs, user, PROGRAM_NAME + ": " + msg); + } + + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#define EXT_EVENT_HANDLER "ARES/application/media.event.lsl" +#include diff --git a/ARES/application/news.lsl b/ARES/application/news.lsl new file mode 100644 index 0000000..30e2775 --- /dev/null +++ b/ARES/application/news.lsl @@ -0,0 +1,311 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * News RSS Aggregation Client + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "0.2.2" +#define CLIENT_VERSION_TAGS "alpha" + +list feeds; +list headlines; +integer offset; +integer rate; + +key news_pipe; + +#define parcel_name() gets(llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_NAME]), 0) +#define parcel_traffic() count(llGetAgentList(AGENT_LIST_PARCEL, [])) +string parcel; + +check_parcel() { + string new_parcel = parcel_name(); + if(new_parcel != parcel || parcel == "") { + alert("Welcome to " + new_parcel + ", " + llGetRegionName() + " (Population: " + (string)parcel_traffic() + ")", + ALERT_ICON_INFO, ALERT_COLOR_NORMAL, ALERT_BUTTONS_DISMISS, ["!clear"]); + + parcel = new_parcel; + setdbl("news", ["parcel"], parcel); + } +} + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + string msg = ""; + if(argc == 1) { + msg = PROGRAM_NAME + " add : adds a URL to the news feed list\n" + + PROGRAM_NAME + " remove : removes a URL from the news feed list\n" + + PROGRAM_NAME + " feeds|headlines: shows current data\n" + + PROGRAM_NAME + " rate : seconds between each query (minimum 30, or 0 = off)\n" + + "\nEach feed should return only the current headline. The program will cache the most recent headline from each feed and only display the headline when an update occurs. Queries are staggered by the number of feeds, so with N feeds, a new HTTP request is generated every N/s seconds."; + } else { + string action = gets(argv, 1); + string value = gets(argv, 2); + if(action == "feeds") { + msg = "Current feeds:\n" + concat(feeds, "\n"); + } else if(action == "headlines") { + msg = "Latest headlines:\n" + concat(headlines, "\n"); + } else if(action == "add") { + if(contains(feeds, value)) { + msg = "Already subscribed to news feed " + value; + } else { + msg = "Added news feed " + value; + feeds += value; + headlines += "?"; + setdbl("news", ["feed"], jsarray(feeds)); + integer fn = count(feeds); + set_timer("update", (float)rate / (float)fn); + } + } else if(action == "remove") { + integer fi = index(feeds, value); + if(~fi) { + msg = "Removed news feed " + value; + feeds = delitem(feeds, fi); + headlines = delitem(headlines, fi); + setdbl("news", ["feed"], jsarray(feeds)); + setdbl("news", ["headline"], jsarray(headlines)); + integer fn = count(feeds); + integer real_rate = llRound((float)rate / (float)fn); + integer minimum_rate = (integer)getdbl("interface", ["alert", "time"]); + if(real_rate <= minimum_rate) + real_rate = minimum_rate + 1; + if(fn) + set_timer("update", real_rate); + } else { + msg = "Not subscribed to news feed " + value; + } + + } else if(action == "rate") { + integer rate = (integer)value; + + if(rate > 0) { + if(rate < 30) + rate = 30; + + integer fn = count(feeds); + integer real_rate = llRound((float)rate / (float)fn); + integer minimum_rate = (integer)getdbl("interface", ["alert", "time"]); + if(real_rate <= minimum_rate) + real_rate = minimum_rate + 1; + + msg = "News feeds will now update every " + (string)(real_rate * fn) + " seconds."; + setdbl("news", ["rate"], (string)rate); + if(fn) + set_timer("update", real_rate); + } else { + rate = 0; + msg = "News feeds disabled."; + set_timer("update", 0); + } + + setdbl("news", ["rate"], (string)rate); + } else if(action == "open") { + string name = llGetObjectName(); + llSetObjectName("ARES News Ticker"); + llLoadURL(user, "See full article?", value); + llSetObjectName(name); + } else { + msg = PROGRAM_NAME + ": unknown action '" + action + "'; see 'help news' for usage"; + } + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_TIMER) { + // echo("news timer occurred: " + m); + if(count(feeds) == 0) { + set_timer("update", 0); + } else if((integer)getdbl("status", ["on"])) { + offset = (offset + 1) % count(feeds); + pipe_open(["notify " + PROGRAM_NAME + " headline"]); + } + } else if(n == SIGNAL_NOTIFY) { + list argv = split(m, " "); + string reason = gets(argv, 1); + if(reason == "pipe") { + news_pipe = ins; + string feed = gets(feeds, offset); + if(feed == "") { + offset = (offset + 1) % count(feeds); + feed = gets(feeds, offset); + } + http_get(feed, news_pipe, avatar); + } else if(reason == "headline") { + string url = gets(feeds, offset); + string headline; + pipe_read(ins, headline); + + if(~strpos(headline, ""); + if(~item_end) { + item = substr(item, 0, item_end); + } + } else { + integer entry_start = strpos(headline, ""); + if(~entry_end) { + item = substr(item, 0, entry_end); + } + } + } + + // echo((string)strlen(headline)); + headline = "(truncated Atom or RSS entry)"; + + integer title_start = strpos(item, ""); + integer title_end = strpos(item, ""); + if(~title_start && ~title_end) { + headline = llStringTrim(substr(item, title_start + 7, title_end - 1), STRING_TRIM); + headline = replace(headline, "\n", ""); + if(substr(headline, 0, 8) == "") + headline = substr(headline, 9, -4); + + string bad_punctuation = "‘’“”–—"; + string good_punctuation = "''\"\"--"; + integer sbs = strlen(bad_punctuation); + while(sbs--) { + headline = replace(headline, substr(bad_punctuation, sbs, sbs), substr(good_punctuation, sbs, sbs)); + } + } + integer url_start = strpos(item, ""); + integer url_end = strpos(item, ""); + if(~url_start && ~url_end) { + url = llStringTrim(substr(item, url_start + 6, url_end - 1), STRING_TRIM); + url = replace(url, "\n", ""); + } else if(~(url_start = strpos(item, " 1) { + url = gets(ni, 1); + headline = gets(ni, 0); + } + } + + if(headline != gets(headlines, offset)) { + headlines = alter(headlines, [headline], offset, offset); + setdbl("news", ["headline"], jsarray(headlines)); + alert(headline, + ALERT_ICON_INFO, + ALERT_COLOR_NORMAL, + ALERT_BUTTONS_DETAILS, + ["!clear", "news open " + url] + ); + } + + pipe_close([ins]); + } + } else if(n == SIGNAL_EVENT) { + integer e = (integer)m; + if(e == EVENT_TELEPORT || e == EVENT_REGION_CHANGE) { + check_parcel(); + } else { + echo("news event ?" + m); + } + + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + string s_news = llLinksetDataRead("news"); + if(s_news == JSON_INVALID) { + s_news = "{\"feed\":[],\"headline\":[],\"rate\":0}"; + llLinksetDataWrite("news", s_news); + } + + feeds = js2list(getjs(s_news, ["feed"])); + headlines = js2list(getjs(s_news, ["headline"])); + + // blank entries can sometimes get created in a fresh database: + integer mutated; + integer blank_index; + while(~(blank_index = index(feeds, ""))) { + feeds = delitem(feeds, blank_index); + headlines = delitem(headlines, blank_index); + mutated = TRUE; + } + + if(mutated) { + s_news = setjs(s_news, ["feed"], jsarray(feeds)); + s_news = setjs(s_news, ["headline"], jsarray(headlines)); + } + + rate = (integer)getjs(s_news, ["rate"]); + + parcel = getjs(s_news, ["parcel"]); + if(parcel == "" || parcel == JSON_INVALID) { + parcel = parcel_name(); + s_news = setjs(s_news, ["parcel"], parcel); + mutated = 1; + } else { + check_parcel(); + } + + if(mutated) { + llLinksetDataWrite("news", s_news); + } + + integer fn = count(feeds); + integer real_rate = llRound((float)rate / (float)fn); + integer minimum_rate = (integer)getdbl("interface", ["alert", "time"]); + if(real_rate <= minimum_rate) + real_rate = minimum_rate + 1; + if(fn) + set_timer("update", real_rate); + + hook_events([EVENT_TELEPORT, EVENT_REGION_CHANGE]); + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/persona.lsl b/ARES/application/persona.lsl new file mode 100644 index 0000000..2276a96 --- /dev/null +++ b/ARES/application/persona.lsl @@ -0,0 +1,158 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Personality System Module + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 1 (ASCL-i). It is offered to you on a limited basis to + * facilitate modification and customization. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#include + +#define CLIENT_VERSION "1.0.2" +#define CLIENT_VERSION_TAGS "release" + +string persona = "default"; + +key file_pipe; +integer file_offset; +integer file_length; +list available_personas; + +// for activating a persona: +key a_user; +key a_outs; + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = splitnulls(m, " "); + integer argc = count(argv); + string msg; + if(argc == 1) { + available_personas = js2list(llLinksetDataRead("fs:persona")); + msg = "active persona: " + persona + "\n\npersonas available: " + concat(available_personas, ", "); + } else { + string action = gets(argv, 1); + if(action == "set") { + integer permission = sec_check(user, "persona", outs, "", m); + + if(permission == ALLOWED) { + list blocked_settings = js2list(getdbl("persona", ["block"])); + string setting = gets(argv, 2); + if(!contains(blocked_settings, setting) && setting != "block") + setdb("persona", setting, concat(delrange(argv, 0, 2), " ")); + } else if(permission == DENIED) { + msg = "denied: special authorization is required to change this unit's persona"; + } + } else if(llOrd(action, 0) == 0x2e) { // '.' + //echo("do persona . command: " + action); + string command = getdbl("persona", ["action", delstring(action, 0, 0)]); + if(command != JSON_INVALID) { + invoke("input say " + command, outs, ins, NULL_KEY); + } else if(action == ".info") { + string pslist = "." + concat(jskeys(getdbl("persona", ["action"])), ", ."); + if(pslist == ".") + pslist = "(none)"; + msg = "Available actions: " + pslist; + } else { + msg = "Not recognized: '" + action + "'. Type '.info' for a list of available persona actions."; + } + } else { + integer permission = sec_check(user, "persona", outs, "", m); + + if(permission == ALLOWED) { + available_personas = js2list(llLinksetDataRead("fs:persona")); + + if(contains(available_personas, action) + || contains(available_personas, action + ".p")) { + if(substr(action, -2, LAST) == ".p") + action = delstring(action, -2, LAST); + + setdbl("m:persona", ["f"], action); + string status_persona = action; + + if(status_persona == "default") + status_persona = ""; + + e_call(C_STATUS, E_SIGNAL_CALL, NULL_KEY + " " + NULL_KEY + " status persona " + status_persona); + + string rlv_path = getdbl("persona", ["path"]); + effector_rlv("detachall:" + rlv_path + "=force"); + + persona = action; + + msg = "activating persona: " + persona; + + if(user != avatar) + echo(msg); + + setdbl("persona", ["persona"], persona); + a_outs = outs; + task_begin(a_user = user, "persona"); + set_mode(_mode | MODE_ACCEPT_DONE); + invoke("exec do " + persona + ".p", outs, ins, user); + } else { + #ifdef DEBUG + msg = "bad command (no file): " + m; + #else + msg = "no matching persona file found: " + action; + #endif + } + } else if(permission == DENIED) { + msg = "denied: special authorization is required to change this unit's persona"; + } + } + } + + if(msg) + print(outs, user, msg); + } else if(n == SIGNAL_DONE) { + if(_mode & MODE_ACCEPT_DONE) { + set_mode(_mode & ~MODE_ACCEPT_DONE); + task_end(a_user); + print(a_outs, a_user, "[" + PROGRAM_NAME + "] activated " + persona); + if(persona != "default") + announce("persona-1"); + else + announce("persona-0"); + string rlv_path = getdbl("persona", ["path"]); + effector_rlv("attachallover:" + rlv_path + "=force"); + } + + } else if(n == SIGNAL_INIT) { + print(outs, user, "[" + PROGRAM_NAME + "] init event"); + available_personas = js2list(llLinksetDataRead("fs:persona")); + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + print(outs, user, "[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/saver.event.lsl b/ARES/application/saver.event.lsl new file mode 100644 index 0000000..ae473eb --- /dev/null +++ b/ARES/application/saver.event.lsl @@ -0,0 +1,137 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * saver Utility - Event Handlers + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + + + timer() { + list acts; + + if(current_saver == "stars") { + float cycle = 0; + float now = llGetTime() * 0.2; + float t = now - llFloor(now); + integer n = STARS_LAYERS; + while(n--) { + float i = (float)n / STARS_LAYERS; + float scale = t + i; + if(scale > 1.0) { + scale -= 1.0; + } + + float o = (1 - scale * scale * scale) * (scale * scale - 0.05) * 4.25; + if(o > 1) + o = 1; + else if(o < 0) + o = 0; + + acts += [ + PRIM_LINK_TARGET, SS_FG + n, + //PRIM_SIZE, ONES * (2 + scale * 4.0), + PRIM_SIZE, ONES * (1.0 / (1.01 - scale)), + PRIM_POSITION, <-5, 0, 0>, + PRIM_COLOR, 0, ONES, o, + PRIM_ROTATION, + // llEuler2Rot(<0, 0, scale * TWO_PI>) * + VISIBLE, + PRIM_TEXTURE, 0, STARS_TEX, ONES, ZV, n * n + ]; + } + } else if(current_saver == "bounce") { + integer usable_width = screen_width - 256; + integer usable_height = screen_height - 128; + // float now = llGetAndResetTime(); + bounce_pos += bounce_dir * BOUNCE_SPEED; + + if(bounce_pos.y <= usable_width * -0.5 + || bounce_pos.y >= usable_width * 0.5) { + bounce_dir.y = -bounce_dir.y; + } + + if(bounce_pos.z <= usable_height * -0.5 + || bounce_pos.z >= usable_height * 0.5) { + bounce_dir.z = -bounce_dir.z; + } + + acts += [ + PRIM_LINK_TARGET, SS_FG, + PRIM_SIZE, <256, 128, 0> * pixel_scale, + PRIM_POSITION, <-5, 0, 0> + bounce_pos * pixel_scale, + PRIM_TEXTURE, 0, model_badge, ONES, ZV, 0, + PRIM_ROTATION, VISIBLE, + PRIM_COLOR, 0, <1, 0, 0> * llEuler2Rot(<0, 0, llGetTime()> * PI) * 0.5 + ONES * 0.5, 1 + ]; + } else if(current_saver == "toasters") { + integer n = TOASTERS; + while(n--) { + vector tp = getv(toaster_pos, n); + tp += <0, 2, -1> * (n + MIN_TOAST_SPEED); + + integer visible = 1; + if(tp.y >= screen_width * 0.5 + 64 + || tp.z <= screen_height * -0.5 - 64) { + tp.y = screen_width * 0.25 - llFloor(llFrand(screen_width * 2)); + tp.z = screen_height * 0.5 + 128; + visible = 0; + integer is_toast = llFrand(1.5) > 1; + if(!is_toast) + llSetLinkTextureAnim(SS_FG + n, ANIM_ON | LOOP | PING_PONG, ALL_SIDES, 4, 4, 0, 4, 16); + else + llSetLinkTextureAnim(SS_FG + n, ANIM_ON | LOOP | PING_PONG, ALL_SIDES, 4, 4, 4, 1, 16); + } else if(tp.y < (screen_width * -0.5 - 64) + || tp.z > (screen_height * 0.5 + 64)) { + visible = 0; + } + + acts += [ + PRIM_LINK_TARGET, SS_FG + n, + PRIM_SIZE, <64, 64, 0> * pixel_scale, + PRIM_TEXTURE, 0, TOASTER_TEX, <0.25, 0.25, 0>, <-.375, .375, 0>, 0, + PRIM_POSITION, tp * pixel_scale - <5, 0, 0>, + PRIM_ROTATION, VISIBLE, + PRIM_COLOR, 0, ONES, visible + ]; + + toaster_pos = alter(toaster_pos, [tp], n, n); + } + } else { + echo(current_saver); + } + + setp(0, acts); + } + \ No newline at end of file diff --git a/ARES/application/saver.lsl b/ARES/application/saver.lsl new file mode 100644 index 0000000..e628ae0 --- /dev/null +++ b/ARES/application/saver.lsl @@ -0,0 +1,219 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Screen Saver Program + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#include + +#define CLIENT_VERSION "1.0.1" +#define CLIENT_VERSION_TAGS "release" + +#define call_interface(_outs, _user, _msg) \ + system(SIGNAL_CALL, E_INTERFACE + E_PROGRAM_NUMBER \ + + (string)_outs + " " + (string)_user + " interface " + (_msg)); + +list savers = ["blank", "toasters", "stars", "bounce"]; + +// #define STARS_TEX "e8105c7f-2595-6bd9-54fb-690764dcac3a" +#define STARS_TEX "e74b5970-f74f-9846-4707-eb9cf97af2f0" +// #define STARS_TEX "3c0d0224-7a9a-5d63-c218-ebd7721f1c08" + +#define TOASTER_TEX "d56103e2-2ca9-3a18-40e8-5c3164f2b25c" + +#define SS_BG 112 +#define SS_FG 113 +#define SS_LAYERS 15 + + +#define STARS_LAYERS 8 +#define BOUNCE_SPEED 32 +#define TOASTERS 14 +#define MIN_TOAST_SPEED 4 + +string model_badge = BADGE_DEFAULT; +integer screen_width = 1920; +integer screen_height = 1008; +float pixel_scale; + +string current_saver; + +vector bounce_dir = <0, 1, 1>; +vector bounce_pos = ZV; +float last_time; + +list toaster_pos; +integer auto_enabled = FALSE; + +#define AFK_CHECK_INTERVAL 30 + +main(integer src, integer n, string m, key outs, key ins, key user) { + @restart_main; + + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + string msg = ""; + string action = gets(argv, 1); + if(action == "stop" || action == "off" || action == "cancel") { + if(current_saver != "") { + task_end("screensaver"); + current_saver = ""; + integer n = SS_LAYERS; + list acts; + while(n--) { + acts += [ + PRIM_LINK_TARGET, SS_BG + n, + PRIM_SIZE, ZV, + PRIM_POSITION, OFFSCREEN, + PRIM_COLOR, 0, ZV, 1, + PRIM_ROTATION, INVISIBLE, + PRIM_TEXTURE, 0, TEXTURE_TRANSPARENT, ZV, ZV, 0 + ]; + + llSetLinkTextureAnim(SS_BG + n, 0, ALL_SIDES, 0, 0, 0, 0, 0); + } + setp(0, acts); + } + llSetTimerEvent(0); + call_interface(outs, user, "reconfigure"); + unhook_events([EVENT_TOUCH]); + } else if(action == "auto") { + auto_enabled = !auto_enabled; + string saved_saver = getdbl("screensaver", ["name"]); + if(saved_saver == JSON_INVALID) + saved_saver = "random"; + print(outs, user, "Screensaver (" + saved_saver + ") will " + gets(["not activate automatically.", "activate automatically when the unit goes Away."], auto_enabled)); + set_timer("afk-check", AFK_CHECK_INTERVAL * auto_enabled); + setdbl("screensaver", ["auto"], (string)auto_enabled); + } else if(action == "random" || contains(savers, action)) { + if(action == "random") + current_saver = gets(shuffle(savers, 1), 0); + else + current_saver = action; + + string saved_saver = getdbl("screensaver", ["name"]); + if(saved_saver != "random") + setdbl("screensaver", ["name"], action); + + if(current_saver != "blank") { + if(current_saver == "toasters" || current_saver == "bounce") { + screen_width = (integer)getdbl("interface", ["width"]); + if(screen_width == 0) { + echo("Error: screen width not set. Please set manually with: '@db interface.width ' using the window size information from your viewer's Help > About menu."); + return; + } + screen_height = (integer)getdbl("interface", ["height"]); + pixel_scale = 1.0 / (float)screen_height; + + if(current_saver == "toasters") { + toaster_pos = []; + integer n = TOASTERS; + while(n--) { + toaster_pos += <0, screen_width, screen_height>; + } + } else { + model_badge = getjs("interface", ["badge"]); + if(model_badge == JSON_INVALID) + if((model_badge = getdbl("badge", [replace(getdbl("id", ["model"]), ".", "-")])) == JSON_INVALID) + model_badge = BADGE_DEFAULT; + } + } + + llResetTime(); + llSetTimerEvent(0.066); + } + + hook_events([EVENT_TOUCH]); + task_begin("screensaver", user); + setp(SS_BG, [ + PRIM_SIZE, <2, 1, 0>, + PRIM_POSITION, <-4, 0, 0>, + PRIM_COLOR, 0, ZV, 1, + PRIM_ROTATION, VISIBLE, + PRIM_TEXTURE, 0, TEXTURE_BLANK, ZV, ZV, 0 + ]); + + } else { + msg = + PROGRAM_NAME + " stop|off|cancel: interrupts the screensaver\n" + + PROGRAM_NAME + " : starts a screensaver immediately\n" + + PROGRAM_NAME + " auto: toggles automatic triggering of screensaver once the unit has been Away for 0-30 seconds (uses last screensaver manually activated)\n\n" + + /* PROGRAM_NAME + " clear: removes scheduled screensaver task\n\n" + */ + "Available screensavers: " + concat(savers, ", ") + ", random"; + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_EVENT) { + if((integer)m == EVENT_TOUCH) { + if(current_saver != "") { + m = PROGRAM_NAME + " stop"; + n = SIGNAL_INVOKE; + jump restart_main; + } + } + } else if(n == SIGNAL_TIMER) { + if(llGetAgentInfo(avatar) & AGENT_AWAY && current_saver == "") { + string saved_saver = getdbl("screensaver", ["name"]); + if(saved_saver == JSON_INVALID) { + m = PROGRAM_NAME + " random"; + } else { + m = PROGRAM_NAME + " " + saved_saver; + } + n = SIGNAL_INVOKE; + jump restart_main; + } + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + call_interface(outs, user, "reconfigure"); + llSleep(1.0); + + auto_enabled = (integer)getdbl("screensaver", ["auto"]); + set_timer("afk-check", AFK_CHECK_INTERVAL * auto_enabled); + + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#define EXT_EVENT_HANDLER "ARES/application/saver.event.lsl" +#include diff --git a/ARES/application/scidb.lsl b/ARES/application/scidb.lsl new file mode 100644 index 0000000..4c85ce4 --- /dev/null +++ b/ARES/application/scidb.lsl @@ -0,0 +1,205 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Scientific Database Lookup Utility + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "0.2.1" +#define CLIENT_VERSION_TAGS "alpha" + +string DEFAULT_DOMAIN = "eutils.ncbi.nlm.nih.gov"; +string DEFAULT_DB = "pubmed"; +string DEFAULT_QUERY_KEY = "1"; + +#define SEARCH_STRING(DB, Q) "/entrez/eutils/esearch.fcgi?usehistory=y&db=" + llEscapeURL(DB) + "&term=" + llEscapeURL(Q) +#define POST_STRING(DB, Q) "/entrez/eutils/epost.fcgi?usehistory=y&db=" + llEscapeURL(DB) + "&id=" + llEscapeURL(Q) +#define SUMMARY_STRING(DB, Q, ENV) "/entrez/eutils/esummary.fcgi?usehistory=y&db=" + llEscapeURL(DB) + "&query_key=" + llEscapeURL(Q) + "&WebEnv=" + llEscapeURL(ENV) +#define ABSTRACT_STRING(DB, Q, ENV) "/entrez/eutils/efetch.fcgi?usehistory=y&db=" + llEscapeURL(DB) + "&query_key=" + llEscapeURL(Q) + "&WebEnv=" + llEscapeURL(ENV) + "&rettype=abstract&retmode=text" +#define FASTA_STRING(DB, Q, ENV) "/entrez/eutils/efetch.fcgi?usehistory=y&db=" + llEscapeURL(DB) + "&query_key=" + llEscapeURL(Q) + "&WebEnv=" + llEscapeURL(ENV) + "&rettype=fasta&retmode=text" + +key scidb_receive_pipe; +string waiting_queries = "{}"; +string queries_in_progress = "{}"; + +#define MODE_SEARCH 0 +#define MODE_SUMMARY 1 +#define MODE_ABSTRACT 2 +#define MODE_FASTA 3 + +send_query(string query, key handle) { + string domain = getjs(query, [4]); + string database = getjs(query, [5]); + string question = getjs(query, [6]); + integer mode = (integer)getjs(query, [7]); + string query_key = getjs(query, [8]); + + queries_in_progress = setjs(queries_in_progress, [(string)handle], query); + string url; + if(mode == MODE_SEARCH) + url = domain + SEARCH_STRING(database, question); + else if(mode == MODE_SEARCH) + url = domain + POST_STRING(database, question); + else if(mode == MODE_SUMMARY) + url = domain + SUMMARY_STRING(database, query_key, question); + else if(mode == MODE_ABSTRACT) + url = domain + ABSTRACT_STRING(database, query_key, question); + else if(mode == MODE_FASTA) + url = domain + FASTA_STRING(database, query_key, question); + + http_get(url, scidb_receive_pipe, handle); +} + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + string msg = ""; + if(argc == 1) { + msg = "Syntax: " + PROGRAM_NAME + " [-d -m -q -s|-f|-a-p] \n\nLooks up on the specificed [https://www.ncbi.nlm.nih.gov/ NCBI Entrez] database at (default: " + DEFAULT_DB + " at " + DEFAULT_DOMAIN + "). -s will retrieve summaries from a matching WebEnv query. -q will set the query key (default: 1) -p will post queries to sequence databases. -a will fetch abstracts. -f will fetch FASTA sequences."; + } else { + string domain = DEFAULT_DOMAIN; + string database = DEFAULT_DB; + integer mode = MODE_SEARCH; + string query_key = DEFAULT_QUERY_KEY; + + string a1; + integer a1i; + while(~(a1i = llListFindList(["-m", "-d", "-s", "-f", "-a", "-q"], [a1 = gets(argv, 1)]))) { + if(a1i == 0) { // -m + domain = gets(argv, 2); + argv = delrange(argv, 1, 2); + } else if(a1i == 1) { // -d + database = gets(argv, 2); + argv = delrange(argv, 1, 2); + } else if(a1i == 5) { // -q + query_key = gets(argv, 2); + argv = delrange(argv, 1, 2); + } else if(a1i == 2) { // -s + mode = MODE_SUMMARY; + argv = delrange(argv, 1, 1); + } else if(a1i == 3) { // -f + mode = MODE_FASTA; + argv = delrange(argv, 1, 1); + } else if(a1i == 4) { // -a + mode = MODE_ABSTRACT; + argv = delrange(argv, 1, 1); + } + } + string question = concat(delitem(argv, 0), " "); + + if(!~strpos(domain, "://")) + domain = "https://" + domain; + + key handle = llGenerateKey(); + string query = jsarray([outs, ins, user, _resolved, domain, database, question, mode, query_key]); + _resolved = 0; + + llSetMemoryLimit(0x10000); + if(scidb_receive_pipe) { + queries_in_progress = setjs(queries_in_progress, [handle], query); + send_query(query, handle); + } else { + scidb_receive_pipe = llGenerateKey(); + waiting_queries = setjs(waiting_queries, [handle], query); + pipe_open(["p:"+(string)scidb_receive_pipe + " notify " + PROGRAM_NAME + " response"]); + } + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_NOTIFY) { + list argv = split(m, " "); + string cmd = gets(argv, 1); + if(cmd == "pipe") { + list queries = jskeys(waiting_queries); + integer qi = 0; + integer qmax = count(queries); + while(qi < qmax) { + key handle = gets(queries, qi); + string query = getjs(waiting_queries, [(string)handle]); + waiting_queries = setjs(waiting_queries, [(string)handle], JSON_DELETE); + + send_query(query, handle); + ++qi; + } + } else if(cmd == "response") { + string query = getjs(queries_in_progress, [(string)user]); + if(query != JSON_INVALID) { + key o_outs = getjs(query, [0]); + key o_ins = getjs(query, [1]); + key o_user = getjs(query, [2]); + integer o_rc = (integer)getjs(query, [3]); + + string buffer; + pipe_read(ins, buffer); + /* string fieldset = getjs(buffer, ["query", "pages", 0]); + string field; + if(fieldset != JSON_INVALID) { + if(getjs(fieldset, ["missing"]) == JSON_TRUE) { + field = getjs(fieldset, ["title"]) + " does not exist (" + getjs(query, [0]) + "/)"; + } else { + field = getjs(fieldset, ["extract"]); + } + } else { + field = buffer; + } */ + + print(o_outs, o_user, buffer); + // resolve_io(o_rc, o_outs, o_ins); + queries_in_progress = setjs(queries_in_progress, [(string)user], JSON_DELETE); + resolve_i(o_rc, o_ins); + } + + if(queries_in_progress == "{}") { + pipe_close(scidb_receive_pipe); + scidb_receive_pipe = ""; + llSetMemoryLimit(0x2000); + } + } + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + llSetMemoryLimit(0x2000); + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/tell.lsl b/ARES/application/tell.lsl new file mode 100644 index 0000000..dade40c --- /dev/null +++ b/ARES/application/tell.lsl @@ -0,0 +1,91 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Tell Command + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "1.2.0" +#define CLIENT_VERSION_TAGS "release" + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = splitnulls(m, " "); // non-standard + integer argc = count(argv); + string msg = ""; + if(argc == 1) { + msg = "Syntax: " + PROGRAM_NAME + " \n\nIf is 00000000-0000-0000-0000-000000000000 or 'all', sends to everyone. If is 'lights', message is sent over light bus. If is 'instant', sends using llInstantMessage(). Cannot send to everyone if channel is 0 (SL restriction). Message will originate from ring 2 via io daemon (io_tell()) unless using 'instant'."; + } else { + key target = gets(argv, 1); + if(target == NULL_KEY) + return; + + if(target == "all") + target = NULL_KEY; + integer channel; + string raw_channel = gets(argv, 2); + if(raw_channel == "instant") { + llInstantMessage(target, concat(delrange(argv, 0, 2), " ")); + return; + } else if(raw_channel == "lights") { + if(llGetAttached()) + channel = 105 - (integer)("0x" + substr(avatar, 29, 35)); + else + channel = 105 - (integer)("0x" + substr(KERNEL, 29, 35)); + } else { + channel = (integer)raw_channel; + } + if(channel == 0 && target == NULL_KEY) { + msg = PROGRAM_NAME + ": failed to execute '" + m + "' (may not broadcast on channel 0)"; + } else { + io_tell(target, channel, concat(delrange(argv, 0, 2), " ")); + } + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/template.lsl b/ARES/application/template.lsl new file mode 100644 index 0000000..2796ba3 --- /dev/null +++ b/ARES/application/template.lsl @@ -0,0 +1,63 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * Blank Program Template + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "0.0.1" +#define CLIENT_VERSION_TAGS "placeholder" + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + string msg = ""; + string action = gets(argv, 1); + if(argc == 1 || action == "help") { + msg = "Syntax: " + PROGRAM_NAME + " "; + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/type.lsl b/ARES/application/type.lsl new file mode 100644 index 0000000..3e6a274 --- /dev/null +++ b/ARES/application/type.lsl @@ -0,0 +1,87 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * type Example Program + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 3 (ASCL-iii). It may be redistributed or used as the + * basis of commercial, closed-source products so long as steps are taken + * to ensure proper attribution as defined in the text of the license. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "1.1.1" +#define CLIENT_VERSION_TAGS "release" + +#define FILE_STEP_SIZE 4 +#include + +key file_q; + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = split(m, " "); + integer argc = count(argv); + if(argc == 1) { + print(outs, user, "Syntax: " + PROGRAM_NAME + " "); + } else { + string file_name = gets(argv, 1); + file_q = fopen(outs, ins, user, file_name); + } + } else if(n == SIGNAL_NOTIFY) { + if(m == PROGRAM_NAME + " file") { + if(ins == file_q) { + user = getjs(tasks_queue, [file_q, FILE_USER]); + outs = getjs(tasks_queue, [file_q, FILE_OUTS]); + string fn = getjs(tasks_queue, [file_q, FILE_NAME]); + string unit = getjs(tasks_queue, [file_q, FILE_UNIT]); + + string result = fread(file_q); + + if(result == JSON_FALSE) { + print(user, user, "[" + PROGRAM_NAME + "] No file: " + fn); + } else if(result != JSON_TRUE) { + if(unit == "l") + result = llStringTrim(result, STRING_TRIM_TAIL); + print(outs, user, result); + // llSleep(0.1); + } + + } else { + echo("[" + PROGRAM_NAME + "] file data offered via unexpected pipe: " + (string)ins); + } + } + } else if(n == SIGNAL_INIT) { + // print(outs, user, "[" + PROGRAM_NAME + "] init event; nothing to do"); + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/application/xset.lsl b/ARES/application/xset.lsl new file mode 100644 index 0000000..c8ae880 --- /dev/null +++ b/ARES/application/xset.lsl @@ -0,0 +1,223 @@ +/* ========================================================================= + * + * Nanite Systems Advanced Research Encapsulation System + * + * Copyright (c) 2022–2024 Nanite Systems Corporation + * + * ========================================================================= + * + * XSET Command (Standalone) + * + * This program is covered under the terms of the ARES Software Copyright + * License, Section 2 (ASCL-ii). Although it appears in ARES as part of + * commercial software, it may be used as the basis of derivative, + * non-profit works that retain a compatible license. Derivative works of + * ASCL-ii software must retain proper attribution in documentation and + * source files as described in the terms of the ASCL. Furthermore, they + * must be distributed free of charge and provided with complete, legible + * source code included in the package. + * + * To see the full text of the ASCL, type 'help license' on any standard + * ARES distribution, or visit http://nanite-systems.com/ASCL for the + * current version. + * + * DISCLAIMER + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS + * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * ========================================================================= + * + */ + +#include +#define CLIENT_VERSION "1.2.1" +#define CLIENT_VERSION_TAGS "release" + +/* + replaces (string) number with (integer)number + and (string)\"number\" with (string)number + + ... from db.lsl +*/ +list process_keyname(list keyname) { + integer ki = count(keyname); + while(ki--) { + string ks = gets(keyname, ki); + if((string)((integer)ks) == ks) + keyname = alter(keyname, [(integer)ks], ki, ki); + else if(llOrd(ks, 0) == 0x22 && llOrd(ks, LAST) == 0x22) + keyname = alter(keyname, [substr(ks, 1, -2)], ki, ki); + + } + return keyname; +} + +list pipes; +list tags; + +finish(key tag) { + integer ti = index(tags, tag); + + if(~ti) { + integer r = (integer)getjs(tasks_queue, [tag, "r"]); + key ins = getjs(tasks_queue, [tag, "ins"]); + key outs = getjs(tasks_queue, [tag, "outs"]); + task_end(tag); + pipe_close([getk(pipes, ti)]); + pipes = delitem(pipes, ti); + tags = delitem(tags, ti); + // resolve_io(r, outs, ins); + resolve_i(r, ins); + #ifdef DEBUG + echo("xset: resolved"); + #endif + } else { + echo("xset: spurious receipt: " + (string)tag); + } +} + +main(integer src, integer n, string m, key outs, key ins, key user) { + if(n == SIGNAL_INVOKE) { + list argv = splitnulls(m, " "); + + integer quick_mode; + if(gets(argv, 1) == "-q") { + quick_mode = 1; + argv = delitem(argv, 1); + } + + integer argc = count(argv); + string msg = ""; + + if(argc < 2) { + msg = "Syntax: " + PROGRAM_NAME + " [-q] \n\nxset WILL hang if a job returns no output at all. If this is a risk, specify -q.\n\nNew in 1.2: The command 'db json' will be detected and parsed; try also 'xset = ' as a nicer alternative."; + } else if(argc >= 3) { + string varname = gets(argv, 1); + string command = concat(delrange(argv, 0, 1), " "); + + if(gets(argv, 2) == "=" || (gets(argv, 2) == "db" && gets(argv, 3) == "json")) { // short-circuit tedious 'db json' usage + if(gets(argv, 2) == "db") + argv = delitem(argv, 3); + + string keyname = gets(argv, 3); + list keyparts = process_keyname(split(keyname, ".")); + string value = getdbl(gets(keyparts, 0), delitem(keyparts, 0)); + if(value != JSON_INVALID) { + setdb("env", varname, value); // not dbl + } else { + if(getdb("env", varname) != JSON_INVALID) // not dbl + deletedb("env", varname); // still not dbl + } + + } else { + key tag = llGenerateKey(); + key pipe = llGenerateKey(); + pipes += pipe; // where we'll receive data from the subtask + tags += tag; // the receipt we'll get when the subtask resolves + + task_begin(tag, jsobject([ + "r", _resolved, // the PID we'll report to when we're done + "ins", ins, // input stream of initial calling environment (re-used as subtask's output stream) + "outs", outs, // output stream of initial calling environment (not seen by subtask) + "var", varname, // env variable we're updating + "command", command, // the command we're executing + "user", user, // who we're doing this for + "done", 0, // turns to 1 once we receive DONE + "data", quick_mode // turns to 1 once we receive data + ])); + + // xset ends a task when both 'done' and 'data' conditions are met + + pipe_open(["p:" + (string)pipe + " notify " + PROGRAM_NAME + " data"]); + + _resolved = 0; + } + + } else { + msg = PROGRAM_NAME + ": insufficient arguments: " + m + "; see 'help xset' for usage"; + } + + if(msg != "") + print(outs, user, msg); + } else if(n == SIGNAL_DONE) { + key tag = substr(m, 0, 35); + integer got_data = (integer)getjs(tasks_queue, [tag, "data"]); + if(got_data) { + finish(tag); + } else { + tasks_queue = setjs(tasks_queue, [tag, "done"], "1"); + } + + } else if(n == SIGNAL_NOTIFY) { + list argv = splitnulls(m, " "); + string reason = gets(argv, 1); + if(reason == "pipe") { + integer pi = index(pipes, ins); + if(!~pi) { + echo("xset: spurious pipe: " + (string)ins); + } else { + #ifdef DEBUG + echo("xset: created pipe " + (string)ins); + #endif + key tag = getk(tags, pi); + string varname = getjs(tasks_queue, [tag, "var"]); + setdbl("env", [varname], ""); + + string command = getjs(tasks_queue, [tag, "command"]); + + invoke(command, ins, tag, getjs(tasks_queue, [tag, "user"])); + #ifdef DEBUG + echo("xset: invoked " + command); + #endif + } + } else if(reason == "data") { + integer pi = index(pipes, ins); + if(!~pi) { + echo("xset: data from non-open pipe: " + (string)ins); + } else { + key tag = getk(tags, pi); + string buffer; + pipe_read(ins, buffer); + + string varname = getjs(tasks_queue, [tag, "var"]); + #ifdef DEBUG + echo("xset: data received for " + varname); + #endif + + string current_value = getdbl("env", [varname]); + if(current_value != "") + current_value += "\n"; + + setdb("env", varname, current_value + buffer); + + integer got_done = (integer)getjs(tasks_queue, [tag, "done"]); + if(got_done) { + finish(tag); + } else { + tasks_queue = setjs(tasks_queue, [tag, "data"], "1"); + } + } + } else { + echo("xset: unexpected notify: " + m); + } + } else if(n == SIGNAL_INIT) { + #ifdef DEBUG + echo("[" + PROGRAM_NAME + "] init event"); + #endif + set_mode(_mode | MODE_ACCEPT_DONE); + } else if(n == SIGNAL_UNKNOWN_SCRIPT) { + echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); + } else { + echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); + } +} + +#include diff --git a/ARES/command-comparison.txt b/ARES/command-comparison.txt new file mode 100644 index 0000000..eedb5ad --- /dev/null +++ b/ARES/command-comparison.txt @@ -0,0 +1,48 @@ +ARES Command Cheat Sheet + +By default, ARES commands start with '@' when typed in local chat. + +However, '@' is not used when writing shell scripts. In this case, an '@' in an ARES script file indicates a label. + + +Linux bash Windows cmd.exe ARES exec ARES aliases +------------------------------------------------------------------------------ +cat type type +ls dir fs match ls +ls dir fs +shutdown -h now shutdown /s /t 0 power system off off +echo echo echo += set = set +rm del fs remove +env set db env env +exit goto :EOF exit +--- goto