OeM Application API
Fair warning: Consider this a living document, it will be expanded in time based on feedback.
Table of Contents:
Structure
The most important thing for developing an extension/application to OeM is to use a viewer that supports a precompiler and to include our include file (download OeM_Api.lsl).
We really like our #define macro trickery over at OeM, it helps us ensure our code is easy to read even when actually it’s doing a lot of things.
Our first example application should help elaborate: (download App_Minimal.lsl)
It works through most of the important bits of setting up an application.
// This file is important, you need it unless you want to do a lot of tedious work of copy pasting
// It's availabe from https://elvahardwick.github.io/Obedientiae-ex-Machina-Docs/application-api - Same as this sample code!
#include "OeM_Api.lsl"
#define APPLICATION_COMMANDS ["hello_world"]
default
{
state_entry()
{
// This is an important line that does.. a lot of work, it will tell the controller that this script exists, w
// who wrote it, what it's name is and what options it has. We're saying here that we have a menu, and commands.
OEM_APPLICATION_INIT( "Elva Hardwick", "Minimal", "v0.1.0", APPLICATION_FLAG_HAS_COMMANDS );
}
changed( integer change ) {
// Simple little thing that checks if owner changed and resets the script to ensure everything gets registred
CHANGED_DEFAULT_HANDLERS;
}
link_message(integer sender, integer number, string message, key data) {
// Another important line for an application, will make sure that if something gets reset we still work.
// And that all the necessary things are informed about us
REGISTER_APPLICATION_ON_MESSAGE();
// This is where we handle both the help functionallity, and the actual functionallity for the commands
START_COMMAND_HANDLE
START_HELP_HANDLE
HELP( "hello_world", "\nUsage: hello_world\n\nPrints 'hello world'.")
END_HELP_HANDLE
// This indicates that the command hello_world needs no permissions
// You could also change this to COMMAND_WITH_PERMISSIONS_HANDLE if you don't think it needs permissions
COMMAND_HANDLE( "hello_world" )
DO_OEM_ANNOUNCE( "Hello soon to be conquered world!" )
END_COMMAND_HANDLE
END_COMMAND_GROUP_HANDLE
}
}Our second example application is a lot more complex and we will include it at the end, but it’s a good place to start hacking: (download App_Rainbow.lsl)
It shows off how to do menus, and how commands can have permissions.
Commands
Think/Say/Stuff
This linked message is used when the controller has to ouput some text. The structure is simple: the string contains the message to be outputted while the key contains the output mode. There are several output modes, each one having their own reason to exist.
DO_OEM_WHISPER,DO_OEM_SHOUT,DO_OEM_NORMAL: These modes will output the text as if it was written by the unit. This means that if the unit has their mind or volume off, these won’t work.DO_OEM_THINK: Will relay the message ONLY to the unit, in the same way the chat command “relay” does. RLV commands won’t be relayed.DO_OEM_ANNOUNCE,DO_OEM_ANNOUNCE_S,DO_OEM_ANNOUNCE_W: Will act as if it was a message NOT produced by the mind of the unit, and thus will ignore the mind subsystem being off. S is for shout, W for whisper.
An example use is:
DO_OEM_THINK( "This unit likes writing extensions for OeM" );
Restrictions
The controller allows us to apply and remove restrictions from the unit. It is not a RLV relay, it has a limited number of restrictions it applies, identified with numbers. These numbers are powers of 2, so that they can all be used as flags inside an integer.
The controller keeps track of “who” has issued which restrictions and won’t lift them until everyone has released them. That means that the subsystem menu and the power management both have motor speed restricted. then the subsystem menu lifts its restriction. The controller won’t lift the restriction because the power management still has theirs issued.
Just a small note, to make everyone’s life simpler. Bitwise logic operators. To add a new flag:
flags = flags | new_flag.
To remove a flag
flags = flags & ~new_flag.
RESTRICTION_APPLY
This linked message is used to apply restrictions on the unit. The string must be a number that contains the flags for the restrictions that you want to apply. In the key you must use your identifier. This should be a short string (we want to save memory) but asunique as you can. The device will add the restriction to the restrictions you have applied and if that restriction hadn’t been applied yet, the system will issue the RLV command.
SEND_LINKED_MESSAGE( MESSAGEID_RESTRICTION_APPLY, ([RESTRICTION_SPEECH]), "Core_IO_Menu" );
RESTRICTION_RELEASE
The opposite of apply, you have to send an integer with the restrictions flags you want to lift and your identifier as the key. The system will remove these flags from the restrictions you have applied and if nobody else has those restrictions applied, then it will issue the RLV command to lift them.
RESTRICTION_RELEASE_ALL
Will release all the restrictions that have the identifier sent in the key. The rest works like RESTRICTION_RELEASE
RESTRICTION_SAFEWORD
Will release ALL the restrictions, no matter what the origin was for. If you receive this linked message, you can assume your restrictions have been forgotten by the system.
RESTRICTION_UPDATE
This is a linked message that is sent after any of the previous messages has been received. The string of this Linked Message contains the previous collective flags status and the new one, separated as a list.. This message is sent even if there has been no change.
RESTRICTION_REQUEST_STATUS
Linked message to request the current collective flag status.
RESTRICTION_STATUS
Anser to the previous linked message, containing only the current overall flag status.
Battery
SET_STANDARD_DRAIN
This linked message will add the quantity indicated in the message parameter from the battery capacity in a periodic manner. Note that quantity can be negative. The data identifies the source of this “drain” and replaces any previous value it had. The energy units are Watts second (Ws). So you have a reference, a unit with all subsystems turned on has a drain of -200 Ws and a 8 hour battery has around 6.3 million Ws
CHARGE
This linked message will add the Ws amount specified in the message parameter to the current capacity of the battery, just once. Note that the quantity can be negative.
GET_BATTERY_STATUS
This linked message is used to request the battery status, which is reported with MESSAGEID_BATTERY_STATUS
BATTERY_STATUS
This is a linked message sent when there is a relatively significant change in the battery capacity or when requested via MESSAGEID_GET_BATTERY_STATUS. The message parameter contains a list of three elements. The first one is an integer which indicates the current capacity, the second one is another integer which indicates the maximum capacity and the third one is a float which indicates the percentage (between 0 and 1) of battery.
Menus
MESSAGEID_APPLICATION_MENU_OPEN and MESSAGEID_APPLICATION_MENU_HANDLE_INPUT
We’ve tried to take a lot of the mess of dialogs away, you will need a few things (check the rainbow example for details) like menu_channel and a flag in OEM_APPLICATION_INIT, otherwise the dialog handling is hopefully fairly simple:
if ( ( number == MESSAGEID_APPLICATION_MENU_OPEN || number == MESSAGEID_APPLICATION_MENU_HANDLE_INPUT ) && llGetSubString( message, 0, 39 ) == __app_id ) {
if ( number == MESSAGEID_APPLICATION_MENU_HANDLE_INPUT ) toggle_rainbow( data );
llDialog( data, "Toggle rainbow mdoe", [ TEXT_MENU_ENDING, "Toggle" ], menu_channel );
}
The key field in the linked message (here data) contains the relevant avatar id. Note that the example doesn’t do any parsing of the message, that would be the following
list parameters = DECODE_LIST_2_STRING( message );
string menu_path = llList2String( parameters, 0 );
string button_value = llList2String( parameters, 1 );
Note the menu_path bit, our menu system is based on something similar to a folder structure, if you prefix your button with APPLICATION_MENU_CHARACTER you’ll get a MENU_OPEN message instead of a HANDLE_INPUT message, the system will automatically track what ‘path’ you’re in.
Probably you won’t need to use menu_path if your menu is fairly simple, but we want to offer it anyway.
Oh and the TEXT_MENU_ENDING, that’s a list of 3 buttons that will be automatically and neatly handled by the system so you don’t need to worry about it.
There’s a few more like that in the include file.
Rainbow example
Here is the full code of our rainbow example:
/////////////////////////////////////////////////////
// App_Rainbow, copyright OeM 2022
// You are free to use this code as a basis for your own applications for OeM products
//
// All other uses by request, contact by discord (https://discord.gg/j44BhKHBjw) or in Second Life (elvahardwick or gonkaotic)
/////////////////////////////////////////////////////
// This file is important, you need it unless you want to do a lot of tedious work of copy pasting, it's availabe from https://elvahardwick.github.io/Obedientiae-ex-Machina-Docs/application-api - Same as this sample code!
#include "OeM_Api.lsl"
#define APPLICATION_COMMANDS ["rainbow_color"]
// This is the channel our system expects dialog messages on, as you can see below we don't have an llListen in this script.
// The parsing and handling special buttons like [Back] and [Quit] are all handled for you so you can just make what you need
integer menu_channel;
// These variables are for the rainbow functions! unit_color is the name of the variable that specifies the current unit color, and stored_unit_color is so that when we disable the rainbow we can go back to the original color
float current_hue = -1;
vector unit_color;
vector stored_unit_color;
// Simple toggling method
toggle_rainbow( key data ) {
if ( current_hue == -1 ) {
stored_unit_color = unit_color;
llRegionSayTo( data, 0, "Enabling rainbow mode!" );
llSetTimerEvent( 0.25 );
current_hue = llFrand( TWO_PI );
} else {
llRegionSayTo( data, 0, "Disabling rainbow mode!" );
llSetTimerEvent( 0.0 );
// SET_VARIABLE_DIRECT is... basically: unit_color = stored_unit_color; Oh and inform the rest of the system we're doing it. You can also use SEND_VARIABLE_DIRECT which is used in the timer method
SET_VARIABLE_DIRECT( unit_color, stored_unit_color );
current_hue = -1;
}
}
default
{
state_entry()
{
// This is an important line that does.. a lot of work, it will tell the controller that this script exists, who wrote it, what it's name is and what options it has.
// We're saying here that we have a menu, and commands!
OEM_APPLICATION_INIT( "Elva Hardwick", "Rainbow mode", "v0.1.0", APPLICATION_FLAG_HAS_MENU | APPLICATION_FLAG_HAS_COMMANDS );
// This line is to ask the rest of the system to inform us of what the current unit_color is
REQUEST_VARIABLES;
}
changed( integer change ) {
// Simple little thing
CHANGED_DEFAULT_HANDLERS;
}
link_message(integer sender, integer number, string message, key data) {
// Another important line for an application, will make sure that if something gets reset we still work, and that all the necessary things are informed about us
REGISTER_APPLICATION_ON_MESSAGE();
// This is where we handle both the help functionallity, and the actual functionallity for the commands
START_COMMAND_HANDLE
START_HELP_HANDLE
HELP( "rainbow_color", "\nUsage: rainbow_color\n\Enables or disables the rainbow.")
END_HELP_HANDLE
// This indicates that the command rainbow_color *needs* the ADMINISTRATION premission, you could also change this to COMMAND_HANDLE if you don't think it needs permissions
COMMAND_WITH_PERMISSIONS_HANDLE( "rainbow_color", PERMISSION_ADMINISTRATION_FLAG )
// Just call our toggling method
toggle_rainbow( data );
END_COMMAND_HANDLE
END_COMMAND_GROUP_HANDLE
// In here we handle receiving the menu_channel (OEM_APPLICATION_INIT makes sure we get this) and the unit_color (REQUEST_VARIABLES gets us this one).
// Note that the handling of unit_color is special, we want to also set stored_unit_color if we get it so we just do that! READ_VAR_* all gets translated into a less complicated version of how we handle unit_color here
START_READ_VARIABLE_ON_MESSAGE
READ_VAR_INT( menu_channel )
else if ( m == "unit_color" ) { unit_color = (vector)llList2String( parameters, i + 1 ); if ( current_hue == -1 ) stored_unit_color = unit_color; }
END_READ_VARIABLE_ON_MESSAGE
// This is our menu handling. Yup, this is it, all of it. Okay the menu is pretty simple, we only have one button to handle so we can just pretend to know what the button says
if ( ( number == MESSAGEID_APPLICATION_MENU_OPEN || number == MESSAGEID_APPLICATION_MENU_HANDLE_INPUT ) && llGetSubString( message, 0, 39 ) == __app_id ) {
if ( number == MESSAGEID_APPLICATION_MENU_HANDLE_INPUT ) toggle_rainbow( data );
if ( current_hue == -1 ) {
llDialog( data, "Rainbow mode is disabled", [ TEXT_MENU_ENDING, "Enable" ], menu_channel );
} else {
llDialog( data, "Rainbow mode is enabled", [ TEXT_MENU_ENDING, "Disable" ], menu_channel );
}
}
}
// The code for the rainbow effect! Maybe you'd like to make it so you can set saturation? Or it isn't the full brightness? Try it out! Maybe you can add an extra command to set brightness.
timer() {
current_hue += 0.1;
unit_color = < llSin( current_hue ), llSin( current_hue + TWO_PI / 3 ), llSin( current_hue + TWO_PI / 3 * 2 ) > / 2 + < 0.5, 0.5, 0.5 >;
// This line pushes the new color out to all the other components, which will take care of pushing it to your external devices
SEND_VARIABLE_DIRECT( unit_color );
}
}