Skip to content

Module Dissection

Jasdac edited this page Mar 10, 2021 · 4 revisions

Let's go through the Attachment module as an example:

Header file Attachment.lsh

#ifndef __Attachment
#define __Attachment


#define AttachmentMethod$reqPerm 1			// void - Sent from LevelRepo to let the attachment know that it can now request permissions
#define AttachmentMethod$detach 2			// (str)name/"*" - Detaches one or all objects for the user

#define AttachmentEvt$onAttach 1			// (key)id - Raised when the object is attached. Saves some memory vs using #define ON_ATTACH

#define Attachment$reqPerm( target ) \
	runMethod(target, "Attachment", AttachmentMethod$reqPerm, [])
#define Attachment$detachAll( target ) \
	runMethod(target, "Attachment", AttachmentMethod$detach, "*")
#define Attachment$detach( name ) \
	runMethod(llGetOwner(), "Attachment", AttachmentMethod$detach, name)
#define Attachment$detachOmni( name ) \
	runOmniMethod("Attachment", AttachmentMethod$detach, name)

#define onAttachmentAttached( id ) \
	if( SENDER_SCRIPT IS "Attachment" AND EVENT_TYPE IS AttachmentEvt$onAttach ){ \
		key id = argKey(0);

#endif

Breakdown

#ifndef __Attachment
#define __Attachment

The first part is needed so you don't run into issues when including the file multiple times.

#define AttachmentMethod$reqPerm 1			// void - Sent from LevelRepo to let the attachment know that it can now request permissions
#define AttachmentMethod$detach 2			// (str)name/"*" - Detaches one or all objects for the user

Next we define methods for this module, and we write down what arguments they expect, and what they do. If a method doesn't expect an argument, you can just enter "void".

The definition should be structured as <ScriptName>Method$<methodName> <uniqueID>. ID should be a unique positive integer for this module. Usually you just increment the number as you add methods.

#define AttachmentEvt$onAttach 1			// (key)id - Raised when the object is attached. Saves some memory vs using #define ON_ATTACH

Next we define an event that this module can raise. It's similar to method definitions. The naming convention is: <ScriptName>Evt$<evtName> <uniqueID>. ID should be a positive integer for this module.

#define Attachment$reqPerm( target ) \
	runMethod(target, "Attachment", AttachmentMethod$reqPerm, [])
#define Attachment$detachAll( target ) \
	runMethod(target, "Attachment", AttachmentMethod$detach, "*")
#define Attachment$detach( name ) \
	runMethod(llGetOwner(), "Attachment", AttachmentMethod$detach, name)
#define Attachment$detachOmni( name ) \
	runOmniMethod("Attachment", AttachmentMethod$detach, name)

Next we make macro definitions for the methods. The backslash just means to continue the definition on the next line. But you could just as well ignore it and put the whole definition on one line.

The naming convention is <ScriptName>$<methodName>( <arg1>, <arg2...> ). This is just a quick way of allowing you to call for an example Attachment$detach( name ) instead of having to manually type out runMethod(llGetOwner(), "Attachment", AttachmentMethod$detach, name).

#define onAttachmentAttached( id ) \
	if( SENDER_SCRIPT IS "Attachment" AND EVENT_TYPE IS AttachmentEvt$onAttach ){ \
		key id = argKey(0);

Finally we create macros for the events. This allows us to use onAttachmentAttached( id ) ...code... end instead of typing out a long if statement in every script that should capture the event.

Package file Attachment.lsl

#define USE_STATE_ENTRY
#define USE_RUN_TIME_PERMISSIONS
#define USE_ATTACH
#define USE_TIMER
#include "ObstacleScript/index.lsl"

bool DETACH;

reqAttach(){
    LevelRepo$canAttach();
}


detach(){

	DETACH = TRUE;
	if( !llGetAttached() )
		llDie();
	else if( llGetPermissions()&PERMISSION_ATTACH )
		llDetachFromAvatar();
	else
		llRequestPermissions(llGetOwner(), PERMISSION_ATTACH);
		
}


#include "ObstacleScript/begin.lsl"



onStateEntry()
    
    Portal$scriptOnline();
    LevelRepo$attSpawned();
    if( !llGetAttached() && llGetStartParameter() ){
        
        reqAttach();
        setInterval("chk", 3);
		setInterval("och", 1);
        
    } 
    
end

onAttach( id )
	
	raiseEvent(AttachmentEvt$onAttach, id);

end

onRunTimePermissions( perm )
    
    if( perm & PERMISSION_ATTACH ){
        
		if( DETACH )
			llDetachFromAvatar();
		else{
			llAttachToAvatarTemp(0);
			unsetTimer("chk");
        }
    }

end

handleOwnerMethod( AttachmentMethod$reqPerm )

    llOwnerSay("@acceptpermission=add");
    llSleep(.2);
    llRequestPermissions(llGetOwner(), PERMISSION_ATTACH);

end

handleOwnerMethod( AttachmentMethod$detach )

	str targ = argStr(0);
	if( targ != llGetObjectName() && targ != "*" )
		return;
		
	detach();
		

end


handleTimer( "chk" )
    
    reqAttach();
    
end

// Checks that the object that rezzed us is still present
handleTimer( "och" )

	if( llKey2Name(mySpawner()) == "" )
		detach();
		
end

#include "ObstacleScript/end.lsl"

Breakdown

A package file is a file that contains all the code for a module. Other scripts can interface with the module through the module's header file.

#define USE_STATE_ENTRY
#define USE_RUN_TIME_PERMISSIONS
#define USE_ATTACH
#define USE_TIMER

At the start of the file, you define what built in features you want to use. In this case we want to use the SL event state_entry, which is mapped to onStateEntry in the event handler. USE_RUN_TIME_PERMISSIONS says to use the SL event run_time_permissions, which is mapped to onRunTimePermissions( perms ). USE_ATTACH is, as you probably guessed by now, the SL attach event, which is mapped to onAttach( id ). USE_TIMER enables the ObstacleScript timer handler (see ObstacleScript Globals).

#include "ObstacleScript/index.lsl"

Next we include the index file, which in turn includes all the header files and built in functions.

bool DETACH;
reqAttach(){
    LevelRepo$canAttach();
}
detach(){

	DETACH = TRUE;
	if( !llGetAttached() )
		llDie();
	else if( llGetPermissions()&PERMISSION_ATTACH )
		llDetachFromAvatar();
	else
		llRequestPermissions(llGetOwner(), PERMISSION_ATTACH);
		
}

Next we define global functions and variables.

#include "ObstacleScript/begin.lsl"

Next we include the ObstacleScript event handler starter. After this point, all the code you enter will go into the event handler. You can now start using the built in events.

onStateEntry()
    
    Portal$scriptOnline();
    LevelRepo$attSpawned();
    if( !llGetAttached() && llGetStartParameter() ){
        
        reqAttach();
        setInterval("chk", 3);
	setInterval("och", 1);
        
    } 
    
end
  • onStateEntry is basically the same as state_entry. Note that you ObstacleScript events doesn't use {}, but instead uses end.
  • Portal$scriptOnline(); Needs to be called in any module loaded from the HUD in an Asset that uses the portal. In order to tell it that the script has been loaded. Usually it's put into onStateEntry since it's called when the Module is remoteloaded. Make sure you put any custom listeners before it though, to avoid asynchronisity issues.
  • setInterval sets a repeating timer. The first argument is the timer ID, the second is how often it should repeat.
  • end is just a fancy way of saying }, anyone's who's worked with Lua will recognize the syntax though.
handleOwnerMethod( AttachmentMethod$reqPerm )

    llOwnerSay("@acceptpermission=add");
    llSleep(.2);
    llRequestPermissions(llGetOwner(), PERMISSION_ATTACH);

end

This is a method handler that handles AttachmentMethod$reqPerm, and is limited to only calls by the owner. You can see a full list of global macros and functions in ObstacleScript Globals.

handleTimer( "chk" )
    
    reqAttach();
    
end

This is the callback of the "chk" interval. Note also that ObstacleScript naming convention means that in a handle, the argument passed is a specific identifier you want to handle. Whereas on, the argument passed is a new variable. Ex: handleTimer("chk") llOwnerSay("CHK was triggered"); end vs onTimer( id ) llOwnerSay(id+" was triggered"); end

#include "ObstacleScript/end.lsl"

This ends the script.