Change font size
It is currently Sat Feb 16, 2019 9:07 am

Forum rules


{L_IMAGE}



Post a new topicPost a reply Page 1 of 2   [ 13 posts ]
Go to page 1, 2  Next
Author Message
 Post subject: Handy NPC scripts
PostPosted: Sat Nov 30, 2013 1:50 pm 
Furious Typer

Joined: Mon Sep 27, 2010 7:12 am
Posts: 53
Several people have asked me about NPC scripts since the Feriae party so I thought I'd share a few bits and pieces I've made. I'll post them in this thread.

For all of these scripts you will need a region where:

1. In OpenSim.ini in the [NPC] section you must set Enabled=true to allow NPC to exist

2. Also in OpenSim.ini in the [XEngine] section you will need to enable OSSL functions (generally) by setting AllowOSFunctions = true

3. In the same section you will need to allow the NPC and for many of my scripts also a few other OSSL functions that are normally blocked by the default VeryLow threat level allowed. The "easy" but terribly unsafe method is to just change the OSFunctionThreatLevel to Severe so I STRONGLY urge you not to do this. Instead, enable them one by one and allow them only for ESTATE_OWNER (and possibly ESTATE_MANAGER). This means that only scripts owned by the estate owner are allowed to use those functions while any script owned by someone else will still have access refused for those functions.

Here is an example from my ini that I consider secure (assuming you are the Estate Owner and that you trust anyone you've granted Estate Manager rights to) and will let you run any of the scripts I place in this thread

{L_CODE}:
[XEngine]
    AllowOSFunctions = true
    OSFunctionThreatLevel = Low
    ;; *** Threat-Level=Moderate
    Allow_osOwnerSaveAppearance = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetLinkPrimitiveParams = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetPrimitiveParams = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osMakeNotecard = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcCreate = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcGetPos = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcGetRot = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcLoadAppearance = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcMoveTo = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcMoveToTarget = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcPlayAnimation = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcRemove = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcSaveAppearance = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcSay = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcSetRot = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcSit = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcStand = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcStopAnimation = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osNpcTouch = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osSetPrimitiveParams = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osAvatarPlayAnimation = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osAvatarStopAnimation = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetNotecard = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetNotecardLine = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetNumberOfNotecardLines = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osAgentSaveAppearance = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osTeleportAgent = ESTATE_OWNER, ESTATE_MANAGER


If you use someone else to host your sim you will need to discuss this with them.

Full details are at http://opensimulator.org/wiki/Threat_level


All scripts in this thread are provided under the terms of GPU General Public License 3.0 with one additional condition: You agree not to modify them and/or redistribute them in any way such that you will enable the cloning of anyone other than the owner without the expressed informed consent of the person being cloned. I consider non-consensual cloning to be piracy. not to mention one of the more despicable things you could do.


Top
 Profile  
 
 Post subject: Re: Handy NPC scripts
PostPosted: Sat Nov 30, 2013 1:55 pm 
Furious Typer

Joined: Mon Sep 27, 2010 7:12 am
Posts: 53
Handy NPC SCRIPT #1

As soon as you start working with NPCs you'll "orphan" them by editing or resetting a script while its NPC is still active in the region. Here's a quick script that will remove ALL NPC from a region.

Place this in any prim and touch it to activate.

{L_CODE}:

default
{
    state_entry()
    {
        llSay(0, "NPC Killer ready - touch me to remove all NPCs from your region");
    }
    touch_start(integer num)
    {
        list agentList=osGetAvatarList();
        integer listLength=llGetListLength(agentList);
        integer i;
        while (i<listLength)
        {
            osNpcRemove(llList2Key(agentList,i));
            i+=3;
        }
        llOwnerSay("All NPCs should now be dead");
    }
}


Top
 Profile  
 
 Post subject: Re: Handy NPC scripts
PostPosted: Sat Nov 30, 2013 2:02 pm 
Furious Typer

Joined: Mon Sep 27, 2010 7:12 am
Posts: 53
HANDY NPC Script #2

NPCs are handy to use as posing dummies. Here's a very basic one.

Place this script in any prim along with an animation. When you touch the prim it will clone you (if it hasn't already) and then rez an NPC, have it stand on the ball, and play the animation. Touching the prim again will remove the NPC.

{L_CODE}:
// Lots of comments included in this script since it was written to help someone learn the basics...
//
key npc=NULL_KEY;
string notecardName="NPC card to use";          // if no notecard in inventory, toucher's appearance will be cloned to this cardname and used
string npcFirstName="Ima";                      // change to whatever you like
string npcLastName="Clone";                     // ditto
vector sitTargetPos= <0.0,0.0,0.75>;            // set sit target as per any poseball (Magic Sit Kit makes it easy)
rotation sitTargetRot= ZERO_ROTATION;           // or whatever you like
string animToPlay;                              // will use the 1st one if finds in inventory if blank, or the "built in" dance1 if none is found

default
{
    state_entry()
    {
        if (sitTargetPos==ZERO_VECTOR) sitTargetPos=<0,0,0.00001> ;     // sit target cannot be zero vector
    }
    touch_start(integer num)
    {
        // ignore anyone other than the owner touching it
        if (llDetectedKey(0)!=llGetOwner()) return;
        // if an NPC is already active, touching the prim kills it and stops
        if (npc!=NULL_KEY)
        {
            osNpcRemove(npc);
            npc=NULL_KEY;
            return;
        }
        // Otherwise the touch gets things started...
        // first, check whether we have a notecard in inventory to use for our NPC. If none is found, clone and store the toucher's appearance, else use the first one
        if (!llGetInventoryNumber(INVENTORY_NOTECARD))
        {
            osOwnerSaveAppearance(notecardName);
        }
        else notecardName=llGetInventoryName(INVENTORY_NOTECARD,0);
       
        // next, if an animation has been specified make sure it's in inventory - otherwise see if we can find one to play or use fallback of dance
        if ((animToPlay=="") || (llStringLength(animToPlay)>0 && llGetInventoryType(animToPlay)!=INVENTORY_ANIMATION))
        {
            if (!llGetInventoryNumber(INVENTORY_ANIMATION)) animToPlay="dance1";
            else animToPlay=llGetInventoryName(INVENTORY_ANIMATION,0);
        }
       
        // now all we have to do is rez the NPC and have it sit on this prim which will trigger the changed event
        npc = osNpcCreate(npcFirstName,npcLastName,llGetPos()+<0.0,0.0,1>,notecardName,OS_NPC_SENSE_AS_AGENT);
        osNpcSit(npc, llGetKey(), OS_NPC_SIT_NOW);
    }
    changed(integer change)
    {
    // Animate our NPC when it sits down
        if (change & CHANGED_LINK)
        {
            osNpcStopAnimation(npc,"Sit");
            osNpcPlayAnimation(npc,animToPlay);
        }
    }
}


Last edited by Mata Hari on Mon Aug 04, 2014 12:05 pm, edited 1 time in total.

Top
 Profile  
 
 Post subject: Re: Handy NPC scripts
PostPosted: Sat Nov 30, 2013 2:08 pm 
Furious Typer

Joined: Mon Sep 27, 2010 7:12 am
Posts: 53
HANDY NPC SCRIPT #3

This one is similar to #2 but a little more "advanced" and was written as a "dancer" script for a club. Place this in a prim along with at least 1 animation. When you first touch it you will be cloned to notecard, then an NPC will rez, jump on the poseball and begin to play the animation(s) in its inventory. Subsequent touched of the prim will rez/unrez the dancer. On region restart, the dancer will auto-rez by default. You can add more animations or delete them during use (but it will reset the NPC's dance queue). Deleting the appearance notecard will disable the ball until you touch it again.

Note that this script uses a very handy "orphan checker" that helps to prevent the accidentaly orphaning of a dancer. If an unexpected NPC is detected as already being on the poseball the ball will "take control" of that NPC instead of rezzing a new one.

{L_CODE}:
// NPC General Utility Rez & Pose Dancer
// Written by Aine Caoimhe (aka Mata Hari) 2012/2013
//
// OVERVIEW
//
// This basic script is designed to be placed in any object (usually a poseball) along with at least one animation.
// When the owner touches the poseball for the first time their appearance will be cloned and stored to use for
// an NPC who will then rez, sit on the poseball, and begin to play the animation. Subsequent touches of the poseball
// will remove or restore the NPC. The script doesn't provide any "advanced" features such as variable timers, variable NPCs,
// dance selection/controls, etc.
//
// Because this was written by special request, several default behaviours are part of the script but can be altered
// easily either by changing the settings in the USE VARIABLES section below (even a novice can do this!) or more
// drastic changes can be made by altering the main body of the script.
//
// This script requires a region that is configured to allow the OSSL functions necessary to create and animate NPC
// (uses osAvatarPlayAnimation() and osAvatarStopAnimation() rather than the NPC versions of those functions because
// at the time I wrote most of it the NPC versions didn't work correctly).
//
// TERMS OF USE
//
// This script is provided as a courtesy to other users of OpenSim on an as-is basis. I'll try to help you if you ask nicely
// but I won't promise to fix or resolve any issues you might encounter or further customize it for your uses.
// You are free to use and modify it as desired, provided you:
// - also provide it free of charge with full perms as per GPU General Public Licence 3.0
// - never alter the script to allow it to clone another avatar appearance without that owner's explicit and informed consent (avi theft)
//
//
// -----------------------------------------------------------------------
// USER VARIABLES
// you can change these default values to suit your preferences
// -----------------------------------------------------------------------

// The name of your dancer...this is the name she will show in world
string dancerFirstName="Club";
string dancerLastName="Dancer";

// How she changes dances...one of the two following lines must be commented (disabled using // at the start of the line) and the
// other line must be active (no // at the start)

string danceSeq="random";       // dancer will pick the next animation randomly
// string danceSeq="seq";          // dancer will pick the next animation in the poseball

// How often she changes dances -- set a value here in seconds that you want her to play each dance before advancing to the next
float danceTimer=120.0;

// Is the poseball active? Set this to TRUE to have her automatically rezzed whenever the region is restarted. Otherwise set to FALSE.
integer active=TRUE;

// Positioning...how far from the ball to place the dancer (essentially this is her sit target) as a (x,y,z) vector
vector offSet=<0.0, 0.0, 1.0>;

// -----------------------------------------------------------------------
// DON'T CHANGE ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU ARE DOING :)
// -----------------------------------------------------------------------

string npcCard="My Dancer";
key dancerID;
integer danceIndex;
list danceList;

updateDanceList()
{
    // build list of animations in inventory
    integer anims=llGetInventoryNumber(INVENTORY_ANIMATION);
    while(--anims>-1)
    {
        danceList+=llGetInventoryName(INVENTORY_ANIMATION,anims);
    }
    if (danceSeq=="seq") danceList=llListSort(danceList,1,TRUE);
    else danceList=llListRandomize(danceList,1);
    danceIndex=0;
}
rezDancer()
{
    // make sure there are animations
    if (!llGetListLength(danceList))
    {
        llOwnerSay("Cannot create the dancer because there are no animations in the poseball inventory for her to play");
        return;
    }
    // make sure there is a dancer to rez (shouldn't be possible to get this result but included just in case
    if (llGetInventoryType(npcCard)!=INVENTORY_NOTECARD)
    {
        llOwnerSay("Cannot create the dancer because there is no stored appearance in inventory for her.");
        return;
    }
    // see if an npc is already sitting...helps to recover from accidental script reset with active NPC
    if (checkForSitter()) startDancing();
    // safe to proceed with rezzing new NPC if we get to this point
    else
    {
        dancerID=osNpcCreate(dancerFirstName,dancerLastName,llGetPos()+<0,0,1>,npcCard);
        llSleep(0.5);
        osNpcSit(dancerID,llGetKey(),OS_NPC_SIT_NOW);
    }
}
removeDancer()
{
    // kill active npc
    osNpcRemove(dancerID);
    dancerID=NULL_KEY;
}
integer checkForSitter()
{
    // a safety net to try to catch stray NPCs cause by script edit/reset while an NPC is active
    // if an NPC is detected already on the ball but no dancerID is set...this is only called at
    // a time when a new NPC would otherwise be created
    key sitterID=llAvatarOnSitTarget();
    if (sitterID!=NULL_KEY)
    {
        if (osIsNpc(sitterID))
        {
            llOwnerSay("Detected an NPC already using the ball...setting this as my npc");
            dancerID=sitterID;
            return TRUE;
        }
        else
        {
            llOwnerSay("Unexpectedly found an avatar sitting on the poseball...it's going to get crowded!");
            return FALSE;
        }
    }
    else return FALSE;
}
startDancing()
{
    // called when an NPC first sits
    string dance=llList2String(danceList,danceIndex);
    // start currently indexed dance
    osAvatarPlayAnimation(dancerID,dance);
    llSleep(0.25);
    // now stop any other animations the NPC is playing (sit, etc)
    list animToStop=llGetAnimationList(dancerID);
    integer stop=llGetListLength(animToStop);
    key dontStop=llGetInventoryKey(dance);
    while(--stop)
    {
        if (llList2Key(animToStop,stop)!=dontStop) osAvatarStopAnimation(dancerID,llList2Key(animToStop,stop));
    }
    // set the timer for advancing to next dance
    llSetTimerEvent(danceTimer);
}
playNextDance()
{
    // play the next dance
    osAvatarStopAnimation(dancerID,llList2String(danceList,danceIndex));
    danceIndex++;
    if (danceIndex==llGetListLength(danceList)) danceIndex=0;   // cycle back to beginning when reaching the end
    osAvatarPlayAnimation(dancerID,llList2String(danceList,danceIndex));
}
default
{
    state_entry()
    {
        // ensure sit target set
        if (offSet==ZERO_VECTOR) offSet.z+=0.0001;
        llSitTarget(offSet,ZERO_ROTATION);
        // update the animations list
        updateDanceList();
        // rez dancer automatically if set to do so
        if (active && (llGetInventoryType(npcCard)==INVENTORY_NOTECARD)) rezDancer();
    }
    timer()
    {
        // time to advance to next dance...make sure there is a dancer first
        if ((dancerID==NULL_KEY) || (llAvatarOnSitTarget()!=dancerID)) llSetTimerEvent(0.0);   // kill timer if NPC unrezzed
        else playNextDance();
    }
    on_rez(integer start)
    {
        // always reset on rez
        llResetScript();
    }
    changed(integer change)
    {
        // reset script if owner changes or region restarts
        if (change & CHANGED_OWNER) llResetScript();
        else if (change & CHANGED_REGION_START) llResetScript();
        // handle changes in inventory that might affect operation
        else if (change & CHANGED_INVENTORY)
        {
            // safety check on deleting notecard during use
            if ((llGetInventoryType(npcCard)!=INVENTORY_NOTECARD) && (dancerID==NULL_KEY))
            {
                llOwnerSay("You have deleted the dancer notecard. Removing the dancer");
                removeDancer();
                return;
            }
            // else see if it's a change in animations
            integer anims=llGetInventoryNumber(INVENTORY_ANIMATION);
            if (!anims && (dancerID!=NULL_KEY))
            {
                // user deleted the last animation...kill active dancer
                llOwnerSay("There are no animations in the poseball...removing your dancer");
                removeDancer();
            }
            else if (anims!=llGetListLength(danceList))
            {
                updateDanceList();
                if (dancerID!=NULL_KEY) startDancing();
            }
        }
        // handle changes in link...will usually be triggered by the NPC sitting or being removed
        else if (change & CHANGED_LINK)
        {
            // start dancing when an npc sits
            if (dancerID!=NULL_KEY && llAvatarOnSitTarget()==dancerID) startDancing();
            // can ignore npc standing (derez) because key reset is handled by the remove routine
            // also ignoring any non-npc who sits here
        }
    }
    touch_start(integer num)
    {
        // only owner can play with this
        if (llDetectedKey(0)!=llGetOwner()) return;
        // first, clone owner if no appearance card has been stored
        if (llGetInventoryType(npcCard)!=INVENTORY_NOTECARD)
        {
            llOwnerSay("One moment while your appearance is saved for the npc to use");
            osOwnerSaveAppearance(npcCard);
            llSleep(2.0);
        }
        // if no dancer, rez one
        if (dancerID==NULL_KEY)
        {
            if (checkForSitter()) startDancing();
            else rezDancer();
        }
        // else there's a dancer so this touch means we want to remove it
        else removeDancer();
    }
}


Last edited by Mata Hari on Sat Nov 30, 2013 2:29 pm, edited 1 time in total.

Top
 Profile  
 
 Post subject: Re: Handy NPC scripts
PostPosted: Sat Nov 30, 2013 2:15 pm 
Furious Typer

Joined: Mon Sep 27, 2010 7:12 am
Posts: 53
HANDY NPC SCRIPT #4

When writing more complex scripts you often just want to make a quick appearance notecard. Drop this in a prim and touch it to have a notecard given to you and (optionally) also stored in the prim's inventory. This script will allow you to have someone else touch the prim and they will be given the appearance notecard however in that case the notecard will never be stored to the prim's inventory (it is deleted as soon as the notecard is handed to the recipient).

{L_CODE}:
// USER VARIABLES
integer storeCopy=TRUE;            // TRUE will store a copy of each appearance notecard in the prim's inventory
string cardPrefix="~~~NPC ";        // notecard will have this string + First Name + Last Name + a number if it duplicates an existing card name
default
{
    state_entry()
    {
        llSetText("Clone-Me Utility",<1.0,0.85,0.2>,1.0);
        llOwnerSay("Clone-Me utility is ready for use. Please ensure that the OSSL function osAgentSaveAppearance() is enabled in your region.");
    }
    touch_start(integer num)
    {
        list toucherData=[llDetectedKey(0),llDetectedName(0)];
        llRegionSayTo(llList2Key(toucherData,0),0,"Please wait while your appearance data is stored - it will take several seconds...");
        string cardName;
        integer i=1;
        integer nameAvailable;
        while (!nameAvailable)
        {
            if (i==1) cardName=cardPrefix+llList2String(toucherData,1);
            else cardName=cardPrefix+llList2String(toucherData,1)+" "+(string)i;
            if(llGetInventoryType(cardName)==INVENTORY_NOTECARD) i++;
            else nameAvailable=TRUE;
        }
        osAgentSaveAppearance(llList2Key(toucherData,0),cardName);
        llSleep(3.0); // typically takes at least this long to bake the appearance textures and save the card so wait quietly until then
        integer cardReady;
        while(!cardReady)
        {
            if(llGetInventoryType(cardName)==INVENTORY_NOTECARD) cardReady=TRUE;
            else llSleep(0.5); // not ready so wait a little longer before checking again
        }
        llRegionSayTo(llList2Key(toucherData,0),0,"Your notecard is ready");
        llGiveInventory(llList2Key(toucherData,0),cardName);
        if (!storeCopy || (llList2Key(toucherData,0)!=llGetOwner()))
        {
            llRemoveInventory(cardName);
            llRegionSayTo(llList2Key(toucherData,0),0,"The notecard has also been removed from the prim's inventory");
        }
    }
}


Top
 Profile  
 
 Post subject: Re: Handy NPC scripts
PostPosted: Sat Nov 30, 2013 2:22 pm 
Furious Typer

Joined: Mon Sep 27, 2010 7:12 am
Posts: 53
HANDY NPC SCRIPT #5

There are times when you might want the option to have an NPC sit/stand/whatever on a poseball but also have that poseball be useful for an avi. Here is a script that you can place in a prim, then link it to your "main" poseball prim. If you touch the small linked prim it will clone you, rez the NPC and have it pose on the root prim. Touching it again removes the NPC. This assumes that your root prim contains a script to actually control the animation.

{L_CODE}:
integer inUse;
key npc;
string firstName="Posing";
string lastName="Clone";
key ownerID;

default
{
    state_entry()
    {
        ownerID=llGetOwner();
    }
    touch_start(integer num)
    {
        if (llDetectedKey(0)!=ownerID) return;
        if (inUse)
        {
            osNpcRemove(npc);
            npc=NULL_KEY;
            inUse=FALSE;
        }
        else
        {
            npc=osNpcCreate(firstName,lastName,llGetPos(),ownerID);
            inUse=TRUE;
            osNpcSit(npc,llGetLinkKey(LINK_ROOT),OS_NPC_SIT_NOW);
        }
    }
}


Top
 Profile  
 
 Post subject: Re: Handy NPC scripts
PostPosted: Sat Nov 30, 2013 2:29 pm 
Furious Typer

Joined: Mon Sep 27, 2010 7:12 am
Posts: 53
HANDY NPC SCRIPT #5B

While not strictly an NPC script, here is a typical multi-pose script you'd drop into the root prim from #5 along with a handful of animations. Details of how it works are in the header of the script

{L_CODE}:
// Paramour Multi-Method Multi-Animation Script
// by Aine Caolmhe (aka Mata Hari) - visit me by hypergrid at: aine.x64.me:9000:Paramour
// This script is full perm and can be altered, adjusted or changed any way you like
// (though I'd appreciate credit for having written the original)
//
// WHAT IT DOES
// This script offers five different methods for animating an avatar seated on an object
// that contains more than one animation (a far simpler script can be used if there's only 1)
// Assumes that all animations placed in the object play correctly using the SAME sit target
// If you use the dialog method (to pick which animation you want to play) it will only
// display the first 12 animations found in inventory.
//
// HOW TO USE IT
// Place this in any prim (root prim of a linkset) and set the user variables according to
// your requirements. You will also need to place at least one animation in the same prim.
// You can add or remove animations at any time (even during use) and the animation list will be updated
// automatically. The script will automatically reset itself in most (all?) situations that would require it.
//
// USER VARIABLES
// set the following to suit your needs
string floatyText="";               // if you want floating text to appear above your object, enter it here
vector floatyColour=<0.0,0.0,0.0>;  // colour of the text -- black = <0,0,0>, white = <1,1,1>
float floatyAlpha=1.0;              // transparency of the text -- 0.0 = invisible, 1.0 = opaque
vector sitPos=<0.0,0.0,0.0001>;     // sit target position and rotation to use -- I suggest using the Magic
rotation sitRot=ZERO_ROTATION;      //  Sit Kit to determine the best position and rotation for your sit target
                                    // Note: Sit target position cannot be <0,0,0>
// Uncomment one of the following five lines to choose your animation method:
string animMethod="SEQUENCE_TIMED"; // each animation will be played in sequence, cycling back to the first upon reaching the end
// string animMethod="RANDOM_TIMED";   // as above, except the order of animations is randomly shuffled
// string animMethod="SEQUENCE_TOUCH"; // each animation will be played in sequence, but only advances when the seated person touches the object
// string animMethod="RANDOM_TOUCH";   // as above, in random order
// string animMethod="DIALOG_SELECT"; // upon sitting the avatar will be asked to pick the animation they wish to play. Once seated, touching
                                    // the object will display the dialog again, allowing you to pick a different animation. Maximum of 12 animations.
float animTimer=45.0;               // if using either of the two TIMED methods, how long (in seconds) to wait before advancing to the next animation
                                    // this is ignored if you are using one of the other 3 methods

// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
// #    DO NOT CHANGE ANYTHING BELOW THIS LINE UNLESS YOU KNOW WHAT YOU'RE DOING     #
// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
string currentAnim;
list anList;
integer anQty;
key mySitter=NULL_KEY;
integer handle;
integer myChannel;
float diaWait=60;
integer diaFirst=TRUE;

buildAnimList()
{
    anQty=llGetInventoryNumber(INVENTORY_ANIMATION);
    if (!anQty)
    {
        llOwnerSay("Unable to find any animations in inventory");
        return;
    }
    anList=[];
    integer i;
    while (i<anQty)
    {
        anList+=llGetInventoryName(INVENTORY_ANIMATION,i);
        i++;
    }
    if (llGetSubString(animMethod,0,5)=="RANDOM") anList=llListRandomize(anList,1);
    if (mySitter==NULL_KEY) currentAnim=llList2String(anList,0);
}
startAnim()
{
    llStartAnimation(currentAnim);
    key dontStop=llGetInventoryKey(currentAnim);
    list anToStop=llGetAnimationList(mySitter);
    integer indexToStop=llGetListLength(anToStop);
    while (--indexToStop>-1)
    {
        if (llList2Key(anToStop,indexToStop)!=dontStop) llStopAnimation(llList2Key(anToStop,indexToStop));
    }
    if (llSubStringIndex(animMethod,"TIMED")>-1) llSetTimerEvent(animTimer);
}
nextAnim()
{
    integer curInd=llListFindList(anList,[currentAnim]);
    if (curInd==-1) // either no animations or current was deleted
    {
        if (!anQty) return; // just keep playing current if there are no animations at all
        else curInd=anQty--; // set to last so it rolls over
    }
    if (++curInd==anQty)
    {
        curInd=0;
        if (llSubStringIndex(animMethod,"RANDOM")>-1) anList=llListRandomize(anList,1);
    }
    string nextAnim=llList2String(anList,curInd);
    if (nextAnim!=currentAnim)
    {
        llStartAnimation(nextAnim);
        llStopAnimation(currentAnim);
    }
    currentAnim=nextAnim;
}
getAnim()
{
    // uses numbered buttons and a txt list in the dialogue since animation names usually don't fit on buttons
    // and could also result in error if they exceed maximum button text length limit
    list butR1;
    list butR2;
    list butR3;
    list butR4;
    list butDia;
    integer i;
    string txtDia="Please select the animation you would like to play:\n\n";
    while (i<anQty)
    {
        if (i<3) butR1+=(string)(i+1);
        else if (i<6) butR2+=(string)(i+1);
        else if (i<9) butR3+=(string)(i+1);
        else butR4+=(string)(i+1);
        txtDia+=(string)(i+1)+". "+llList2String(anList,i)+"\n";
        i++;
    }
    while (i<12)
    {
        if (i<3) butR1+=["-"];
        else if (i==3) i=12;
        else if (i<6) butR2+=["-"];
        else if (i==6) i=12;
        else if (i<9) butR3+=["-"];
        else if (i==9) i=12;
        else butR4+=["-"];
        i++;
    }
    butDia=butR4+butR3+butR2+butR1;
    handle=llListen(myChannel,"",mySitter,"");
    llDialog(mySitter,txtDia,butDia,myChannel);
    llSetTimerEvent(diaWait);
}
shutDown()
{
    llSetTimerEvent(0.0);
    llStartAnimation("stand");
    llStopAnimation(currentAnim);
    mySitter=NULL_KEY;
    diaFirst=TRUE;
    if (handle) llListenRemove(handle);
    if (llGetSubString(animMethod,0,5)=="RANDOM") anList=llListRandomize(anList,1);
}
default
{
    state_entry()
    {
        llSetText(floatyText,floatyColour,floatyAlpha);
        if (sitPos==ZERO_VECTOR) sitPos=<0.0,0.0,0.00001>;
        llSitTarget(sitPos,sitRot);
        myChannel=(integer)("0x"+llGetSubString((string)llGetKey(),-8,-1)); // set unique channel based on key
        buildAnimList();
    }
    on_rez(integer nullInt)
    {
        llResetScript();
    }
    touch_end(integer num_detected)
    {
        if (mySitter==NULL_KEY) return; // ignore if no sitter
        if (llDetectedKey(0)!=mySitter) return; // ignore if not current sitter
        if (llSubStringIndex(animMethod,"TIMED")>-1) return; // ignore if using timed method
        if (animMethod=="DIALOG_SELECT") getAnim();
        else nextAnim();
    }
    timer()
    {
        if (llSubStringIndex(animMethod,"TIMED")>-1)
        {
            if (llAvatarOnSitTarget() ==NULL_KEY) shutDown(); // make sure there is actually an avatar there
            else nextAnim();
        }
        else if (animMethod=="DIALOG_SELECT")
        {
            llRegionSayTo(mySitter,0,"Sorry, I did not hear your response to the dialog asking for your next animation. Please touch me to try again.");
            llSetTimerEvent(0.0);
            llListenRemove(handle);
        }
        else llResetScript(); // shouldn't be possible anyway
    }
    changed (integer change)
    {
        if (change & CHANGED_INVENTORY)
        {
            if (llGetInventoryNumber(INVENTORY_ANIMATION)!=anQty) buildAnimList();
            return;
        }
        if (change & CHANGED_LINK)
        {
            key sitterKey=llAvatarOnSitTarget();
            if (mySitter==NULL_KEY && sitterKey!=NULL_KEY) // someone sitting down
            {
                mySitter=sitterKey;
                llRequestPermissions(mySitter,PERMISSION_TRIGGER_ANIMATION);
            }
            else if (mySitter!=NULL_KEY && sitterKey==NULL_KEY) shutDown(); // someone standing up
            else if (mySitter!=NULL_KEY && sitterKey!=mySitter) // someone sitting but object already occupied by someone else
            {
                llUnSit(sitterKey);
                llRegionSayTo(sitterKey,0,"Sorry, someone else is already sitting here");
            }
            return;
        }
        if (change & CHANGED_REGION_START) llResetScript();
        if (change & CHANGED_OWNER) llResetScript();
    }
    run_time_permissions(integer perm)
    {
        if (perm & PERMISSION_TRIGGER_ANIMATION)
        {
            if (animMethod=="DIALOG_SELECT") getAnim();
            else startAnim();
        }
    }
    listen(integer channel, string name, key id, string message)
    {
        llListenRemove(handle);
        llSetTimerEvent(0.0);
        string nextAnim=llList2String(anList,((integer)message) - 1);
        if (diaFirst)
        {
            diaFirst=FALSE;
            currentAnim=nextAnim;
            startAnim();
        }
        else if (nextAnim!=currentAnim)
        {
            llStartAnimation(nextAnim);
            llStopAnimation(currentAnim);
            currentAnim=nextAnim;
        }
    }
}


Top
 Profile  
 
 Post subject: Re: Handy NPC scripts
PostPosted: Sat Nov 30, 2013 3:10 pm 
Furious Typer

Joined: Mon Sep 27, 2010 7:12 am
Posts: 53
I should add that none of the above will accomplish something as complex as the Feriae set-up where a single script was responsible for controlling all 11 NPC throughout the region. Those tend to be highly customized scripts which defeats the point of posting it here. The basic technique, though, is the same as the above with a few additional points worth mentioning:

1. There is no limit to how many NPC a script can rez at once. Just build a list with their UUIDs as you rez them so you'll be able to remove them later. All 11 of Feriae's were handled by one script.

2. Although the above examples have the NPC rez and immediately jump on a poseball, that's not essential. You can have them simply stand in the environment and animate them at that way. However from a practical standpoint it's far better to place them on posepalls whenever possible because the server then doesn't need to monitor and calculate physics for them -- typically the cause of most NPC-related errors. All of Feriae's NPCs were using (invisible) poseballs and I'd suggest a good rule of thumb is to have an exceptionally good reason if you're not going to use a poseball to animate them.

3. NPC's can't sit on something more than 10m away from them. If you want to be able to place an NPC on something more distant you'll have to either have them walk there, or (as I did in Feriae) teleport them close to their poseball. The osNpcSit() command is issued region-wide so as long as your tp gets them within 10m of their target poseball you can then tell them to sit.

4. You can easily use one script to animate multiple NPCs (or avi for that matter) using osAvatarPlayAnimation() and a list of UUIDs who need to play that animation. That's how the Feriae group danceball works and was able to keep 30+ avi all dancing in synch while only giving the server a single script as load (unlike the normal LSL-based group dance balls that require 1 script per dancer).

5. NPCs don't have the limitation of name duplication so you can rez 2+ with identical name. The only thing the server cares about is their UUID. Needless to say, this could become confusing if you have 15-20 of them all running around with identical names.


Top
 Profile  
 
 Post subject: Re: Handy NPC scripts
PostPosted: Mon Aug 04, 2014 10:19 pm 

Joined: Mon Aug 04, 2014 10:19 pm
Posts: 3
Does anyone know if there is an SL version of these?


Top
 Profile  
 
 Post subject: Re: Handy NPC scripts
PostPosted: Wed Aug 06, 2014 7:42 pm 
Site Admin

Joined: Sun Jul 04, 2010 8:20 pm
Posts: 480
havok,
NPC in opensim are server side bots created with OSSL functions. Afaik the only kind of bots you can use in SL are client based.

dan


Top
 Profile  
 
Display posts from previous:  Sort by  
Post a new topicPost a reply Page 1 of 2   [ 13 posts ]
Go to page 1, 2  Next


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron


Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
610nm Style by Daniel St. Jules of Gamexe.net