Skip to content

Asset Tutorial

Jasdac edited this page Mar 15, 2021 · 4 revisions

Let's create a custom obstacle! Each obstacle type has its own header file located in /headers/Obstacles. If your obstacle doesn't fall under any of those categories, you can create your own obstacle header. See Obstacle Header File for more information about how it's defined.

Let's assume we wanted to port over the anemone from GoThongs. There's already definitions for traps in the Trap.lsh obstacle header that we can use.

We see that the trap has its own channel TrapConst$CHAN to listen to. It has 3 tasks that can be used: seat, attack, unSeat. In our case we'll make a simple "you walk into it, it traps you" trap. So we'll handle the seat and unSeat tasks. You can pick any tasks you want to handle in your obstacle, but the more tasks you handle the more flexible it becomes for developers.

As far as events go, we can see LevelCustomEvt$TRAP$hit, LevelCustomEvt$TRAP$seated, LevelCustomEvt$TRAP$unseated, and all 3 makes sense for our trap. So we'll use them.

Full script:

/*
    Description is a JSON array:
    0 : (int)ID
    1 : (float)DURATION
*/
#define USE_STATE_ENTRY
#define USE_TIMER
#define USE_LISTEN
#define USE_PLAYERS
#define USE_RUN_TIME_PERMISSIONS
#define USE_CHANGED
#define USE_COLLISION_START
#include "ObstacleScript/index.lsl"

float END_TIME = 5;
key VICTIM;
integer ID;
integer BFL;
#define BFL_LOADED 0x1

removeSitter(){
    
    key ast = llAvatarOnSitTarget();
    if( ast == NULL_KEY )
        return;
        
    Level$raiseEvent( 
        LevelCustomType$TRAP, 
        LevelCustomEvt$TRAP$unseated,
        ast
    );
    Rlv$unSit( ast, TRUE );
    llUnSit(ast);
    
}

reset(){
    
    llStopObjectAnimation("grapple");
    llStopSound();
    
}

#include "ObstacleScript/begin.lsl"
    
    onStateEntry()
        
        llSitTarget(<0.7,0,.65>, ZERO_ROTATION);
        llStartObjectAnimation("idle");
        
        reset();    // Stops any active animations
        
        // Used for debugging, allowing you to restart the animation while a player is already seated
        llSleep(.5);
        VICTIM = llGetOwner();
        if( llAvatarOnSitTarget() )
            llRequestPermissions(
                llAvatarOnSitTarget(), 
                PERMISSION_TRIGGER_ANIMATION
            );
            
        llListen(TrapConst$CHAN, "", "", "");
        
        llVolumeDetect(TRUE);
            
    end
    
    onPortalLoadComplete( desc )
    
        list data = llJson2List(desc);
        ID = l2i(data, 0);
        END_TIME = l2f(data, 1);
        BFL = BFL|BFL_LOADED;
        
    end
    
    onListen( chan, msg )
    
        if( llGetOwnerKey(SENDER_KEY) != llGetOwner() || chan != TrapConst$CHAN )
            return;
        
        METHOD_ARGS = llJson2List(msg);
        if( 
            argStr(0) == (str)ID || 
            argStr(0) == "*"
        ){
            
            integer task = argInt(1);
            if( task == TrapTask$seat ){
                
                removeSitter();
                VICTIM = argKey(2);
                Rlv$sit( VICTIM, llGetKey(), TRUE );
                
            }
            else if( task == TrapTask$unSeat ){
                
                removeSitter();
                
            }
            
        }
    
    end
    
    onChanged( change )
    
        if( change & CHANGED_LINK ){
            
            key ast = llAvatarOnSitTarget();
            if( ast == NULL_KEY ){
                
                reset();
                return;
                
            }
                
            if( ast != VICTIM )
                llUnSit(ast);
            else
                llRequestPermissions(ast, PERMISSION_TRIGGER_ANIMATION);
        
        }
            
    end
    
    onCollisionStart( nr )
        
        Level$raiseEvent( 
            LevelCustomType$TRAP, 
            LevelCustomEvt$TRAP$hit,
            llDetectedKey(0)
        );
        
    end
    
    onRunTimePermissions( perm )
        
        if( perm & PERMISSION_TRIGGER_ANIMATION ){
            
            Level$raiseEvent( 
                LevelCustomType$TRAP, 
                LevelCustomEvt$TRAP$seated,
                llAvatarOnSitTarget()
            );
            
            llStartAnimation("av_grapple");
            llStartObjectAnimation("grapple");
            llLoopSound("62ecd156-b098-548d-e593-a59898dd8b5d", .5);
            
            setTimeout("END", END_TIME);
            
        }
        
    end
   
   
    handleTimer( "END" )
        removeSitter();
    end

#include "ObstacleScript/end.lsl"

Breakdown

/*
    Description is a JSON array:
    0 : (int)ID
    1 : (float)DURATION
*/
#define USE_STATE_ENTRY
#define USE_TIMER
#define USE_LISTEN
#define USE_PLAYERS
#define USE_RUN_TIME_PERMISSIONS
#define USE_CHANGED
#define USE_COLLISION_START
#include "ObstacleScript/index.lsl"

First off we add a comment detailing how the obstacle's description should be defined. The first value is usually a unique ID. In this case we set the second one to the duration before releasing the player. Noe that the description is only ready when spawned from the Level Controller.

Then we add all the events and features we want to use, and include the header file.

float END_TIME = 5;
key VICTIM;
integer ID;
integer BFL;
#define BFL_LOADED 0x1

Create a few global variables and definitions.

  • END_TIME will store how long it should hold a player.
  • VICTIM will be the UUID of the player we're supposed to grab.
  • ID will be the unique ID passed to the description.
  • BFL will hold some flags.
  • BFL_LOADED will be set when all other scripts have been initialized, and the description has been read.
removeSitter(){
    
    key ast = llAvatarOnSitTarget();
    if( ast == NULL_KEY )
        return;
        
    Level$raiseEvent( 
        LevelCustomType$TRAP, 
        LevelCustomEvt$TRAP$unseated,
        ast
    );
    Rlv$unSit( ast, TRUE );
    llUnSit(ast);
    
}

reset(){
    
    llStopObjectAnimation("grapple");
    llStopSound();
    
}

Two global helper functions:

  • removeSitter() Unsits the currently seated player and raises the unseated event on the level.
  • reset() Resets the animation back to the default idle animation.
#include "ObstacleScript/begin.lsl"

Start the ObstacleScript event handler.

onStateEntry()
        
        llSitTarget(<0.7,0,.65>, ZERO_ROTATION);
        llStartObjectAnimation("idle");
        
        reset();    // Stops any active animations
        
        // Used for debugging, allowing you to restart the animation while a player is already seated
        llSleep(.5);
        VICTIM = llGetOwner();
        if( llAvatarOnSitTarget() )
            llRequestPermissions(
                llAvatarOnSitTarget(), 
                PERMISSION_TRIGGER_ANIMATION
            );
            
        llListen(TrapConst$CHAN, "", "", "");
        
        llVolumeDetect(TRUE);
            
end

State entry handler. Setup default things like sit target, idle animation, listen and volume detect.

    onPortalLoadComplete( desc )
    
        list data = llJson2List(desc);
        ID = l2i(data, 0);
        END_TIME = l2f(data, 1);
        BFL = BFL|BFL_LOADED;
        
    end

This is raised when the portal has loaded (when all scripts have been setup, and the description has been retrieved from the spawner). Here we fetch the description arguments.

    onListen( chan, msg )
    
        if( llGetOwnerKey(SENDER_KEY) != llGetOwner() || chan != TrapConst$CHAN )
            return;
        
        METHOD_ARGS = llJson2List(msg);
        if( 
            argStr(0) == (str)ID || 
            argStr(0) == "*"
        ){
            
            integer task = argInt(1);
            if( task == TrapTask$seat ){
                
                removeSitter();
                VICTIM = argKey(2);
                Rlv$sit( VICTIM, llGetKey(), TRUE );
                
            }
            else if( task == TrapTask$unSeat ){
                
                removeSitter();
                
            }
            
        }
    
    end

Listen handler. Here we setup handling of any tasks that should be viable for this obstacle. Namely seat and unSeat. The tasks are usually sent in the format [id, task, arg1, arg2...]

    onChanged( change )
    
        if( change & CHANGED_LINK ){
            
            key ast = llAvatarOnSitTarget();
            if( ast == NULL_KEY ){
                
                reset();
                return;
                
            }
                
            if( ast != VICTIM )
                llUnSit(ast);
            else
                llRequestPermissions(ast, PERMISSION_TRIGGER_ANIMATION);
        
        }
            
    end
    onRunTimePermissions( perm )
        
        if( perm & PERMISSION_TRIGGER_ANIMATION ){
            
            Level$raiseEvent( 
                LevelCustomType$TRAP, 
                LevelCustomEvt$TRAP$seated,
                llAvatarOnSitTarget()
            );
            
            llStartAnimation("av_grapple");
            llStartObjectAnimation("grapple");
            llLoopSound("62ecd156-b098-548d-e593-a59898dd8b5d", .5);
            
            setTimeout("END", END_TIME);
            
        }
        
    end

If this looks like your average run of the mill poseball handler, it's because it is. We request permissions from the avatar and once these permissions are granted we raise an event on the level and start the animation.

    onCollisionStart( nr )
        
        Level$raiseEvent( 
            LevelCustomType$TRAP, 
            LevelCustomEvt$TRAP$hit,
            llDetectedKey(0)
        );
        
    end

Raise the trap hit event on the level when collided with.

    handleTimer( "END" )
        removeSitter();
    end

Unsit the player once the end timer has been reached.

#include "ObstacleScript/end.lsl"

End the script.

Testing

In order to test it, I've setup a small level with a very simple #Game script for the Game Controller. Now when I walk into the trap it seats me for 5 sec and then unseats me and starts a 5 second cooldown.