Change font size
It is currently Sat Feb 16, 2019 8:38 am

Forum rules


{L_IMAGE}



Post a new topicPost a reply Page 1 of 1   [ 4 posts ]
Author Message
 Post subject: OSSL Group Dance Ball for clubs
PostPosted: Sun Apr 13, 2014 7:02 am 
Furious Typer

Joined: Mon Sep 27, 2010 7:12 am
Posts: 53
Almost all the clubs I visit in OSG are still using the old SL-style dance ball (if I recall correctly it was one that Tasha made -- or adapted from an SL one -- years ago) which works okay but...

- the SL version requires a master controller script and then 1 additional script per potential dancer so if your group dance ball needs to be able to handle up 30 dancers you'll need it to contain 31 scripts! That's hugely inefficient compared to the OSSL alternative and means that you're driving up the active script count just when your sim is the busiest and you need to minimize such things.

- the SL-based group dance ball is well scripted, but the method it uses to keep track of dancers and which of its sub-scripts is active is prone to getting "confused" due to asynchronous Opensim handling of commands in the region. This can lead to people being unable to dance even though there's an available space for them

Luckily, there's an alternative: using the much more powerful OSSL function capabilities. I've made several variations of group dance balls in the last few years, but in this thread I'll just post three variations that you're welcome -- encouraged! -- to use and/or modify to the needs of your sim. They appear in the next 2 posts in this thread.

To use them, though, you first need to enable OSSL in the region which involves editing the Opensim.ini file's [XEngine] section. First, you'll need to ensure that it is set to "AllowOSFunctions = true" (this is the default in the OSG build). Next, by default the threat level of allowed functions is (by default) set to VeryLow which is very safe but unfortunately doesn't give us access to a couple of the functions we need to use. They're "VeryHigh" threat functions because they animate an avatar without asking for permission, thus *could* be abused.

DO NOT change the threat level globally to VeryHigh since this would also enable other functions that can be used by griefers to inflict serious damage. Instead, you want to explicitly allow the required functions only for the ESTATE_OWNER (and/or ESTATE_MANAGER, PARCEL_OWNER, a specific user's UUID, etc) which means that only a script that is owned by that person is allowed to use those functions in the region....nobody else can. This is far safer...

Here's what I generally use in my Opensim.ini [XEngine] section:

{L_CODE}:
[XEngine]
    AllowOSFunctions = true
    OSFunctionThreatLevel = Low

    ; OS Functions enable/disable
    ; For each function, you can add one line, as shown
    ; The default for all functions allows them if below threat level

    ; true allows the use of the function unconditionally
    ; Allow_osSetRegionWaterHeight = true
   
    ; false disables the function completely
    ; Allow_osSetRegionWaterHeight = false
   
    ; Comma separated list of UUIDS allows the function for that list of UUIDS
    ; Allow_osSetRegionWaterHeight = 888760cb-a3cf-43ac-8ea4-8732fd3ee2bb
   
    ; Comma separated list of owner classes that allow the function for a particular class of owners. Choices are
    ; - PARCEL_GROUP_MEMBER:  allow if the object group is the same group as the parcel
    ; - PARCEL_OWNER:         allow if the object owner is the parcel owner
    ; - ESTATE_MANAGER:       allow if the object owner is an estate manager
    ; - ESTATE_OWNER:         allow if the object owner is the estate owner
    ; Allow_osSetRegionWaterHeight = 888760cb-a3cf-43ac-8ea4-8732fd3ee2bb, PARCEL_OWNER, ESTATE_OWNER>, ...

    ; You can also use script creators as the uuid
    ; Creators_osSetRegionWaterHeight = <uuid>, ...

    ; If both Allow_ and Creators_ are given, effective permissions
    ; are the union of the two.

    ;; *** Threat-Level=Moderate
    Allow_osMessageAttachments = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetNumberOfAttachments = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osDropAttachment = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osDropAttachmentAt = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetGridCustom = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetGridHomeURI = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetGridLoginURI = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetGridName = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetGridNick = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetRegionStats = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetSimulatorMemory = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osSetSpeed = ESTATE_OWNER, ESTATE_MANAGER
    ;;   
    ;; *** Threat-Level=High
    Allow_osForceDropAttachment = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osForceDropAttachmentAt = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osOwnerSaveAppearance = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osCauseDamage = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osCauseHealing = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetAgentIP = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetLinkPrimitiveParams = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetPrimitiveParams = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetRegionMapTexture = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetScriptEngineName = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osGetSimulatorVersion = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osMakeNotecard = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osMatchString = 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_osNpcShout = 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_osNpcWhisper = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osParcelJoin = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osParcelSubdivide = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osRegionRestart = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osSetParcelDetails = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osSetPrimitiveParams = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osSetProjectionParams = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osSetRegionWaterHeight = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osSetStateEvents = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osSetTerrainHeight = ESTATE_OWNER, ESTATE_MANAGER
    ;;   
    ;; *** Threat-Level=VeryHigh
    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_osRegionNotice = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osAgentSaveAppearance = ESTATE_OWNER, ESTATE_MANAGER
    ;(missing from IOSSL_API.cs)
    Allow_osSetRot = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osSetParcelDetails = ESTATE_OWNER, ESTATE_MANAGER
    ;(missing from IOSSL_API.cs)
    ;;   
    ;; *** Threat-Level=Severe
    Allow_osConsoleCommand = ESTATE_OWNER
    Allow_osKickAvatar = ESTATE_OWNER, ESTATE_MANAGER
    Allow_osTeleportAgent = ESTATE_OWNER, ESTATE_MANAGER


If you don't want to enable all of those for yourself you can just enable the ones that the scripts (below) use, but using the above means your own scripts will always be able to access any OSSL function. Just make sure you fully trust anyone you make an estate manager!

For more information see:
http://opensimulator.org/wiki/Category:OSSL_Functions
http://opensimulator.org/wiki/Threat_level
http://opensimulator.org/wiki/OSSL_Enabling_Functions


Last edited by Mata Hari on Sun Apr 13, 2014 7:41 am, edited 1 time in total.

Top
 Profile  
 
 Post subject: Re: OSSL Group Dance Ball for clubs
PostPosted: Sun Apr 13, 2014 7:11 am 
Furious Typer

Joined: Mon Sep 27, 2010 7:12 am
Posts: 53
SCRIPT VERSION #1 -- the simple one

This is just a very basic one and requires minimal/no configuration. Just drop a bunch of dance animations into a prim (or linkset or whatever) and then add this script. More details are in the script's top comments section

There are comments throughout to help you figure out how it works and adapt it to your needs.

{L_CODE}:
// PARAMOUR OSSL DANCE BALL V1.0
// by Mata Hari/Aine Caoimhe, Feb. 2013
//
// DESCRIPTION:
//
// This script is a very low lag alternative to the conventional group dance ball that takes advantage of
// the OSSL animation functions to handle all avatars from a single script. It bahaves slightly differently
// for owners vs users:
//
// - Users (non-owners) who touch the ball will start dancing immediately, and touching it again stops them dancing.
// - When the owner touches the ball they are presented with a dialogue menu instead, since they may want to control
//   the danceball options instead of start/stop dancing.
// - When nobody is using the ball it goes into "OFF" mode to reduce the tiny amount of lag caused by its timer. As soon
//   as someone touches it the ball will switch "ON" again.
// - Optional: when ON, the texture of the danceball is animated to rotate (like a mirror ball)
//
// PERMISSIONS:
// This script is supplied under the terms of the GPU General Public License 3.0
// You are free to modify, copy,and/or redistribute this work provided you:
// 1. Supply it free of charge
// 2. Attribute it as the modified work of the original creator (Mata Hari/Aine Caolmhe)
// 3. Also make your modified version freely available to copy, modify, and redistribute
//    under the same GPU3.0 terms that this script was given.
// Full details of the GPU General Public License are available at
// http://www.gnu.org/licenses/gpl.html
//
// USING THIS SCRIPT:
//
// 1. Confirm that your region's OSSL settings are correct (see below)
// 2. Drop at least one dance animation into the prim along with this script
// 3. Change any global variables you want to customize in the script
// 4. That's it...you're ready to dance!
//
// Note: if animateTexture is set to TRUE (and the ball is on) the ball will animate the texture on the assumption that it's in a sphere where
// the sphere has been rotated 90 degrees on the x- or y-axis and will then look like a mirror ball rotating around the (region) z-axis.
// You can disable this, or if you want a different texture animation method you'll need to adjust that directly in the script -- the
// texture animation is turned on in the user function turnOn().
//
// OWNER MENU:
//
// When anyone other than the owner touches the ball they'll simply start to dance. When the owner touches it the following options will be shown
//      Start/Stop dancing      - you will start or stop dancing -- only the owner ever has to do this...everyone else starts/stops just by touching the ball
//      Force Quit              - all avatars will be stopped and released and the dance ball will switch to off state
//      Script Reset            - almost the same thing except after all avatars are released the script will be reset and all defaults will be read
//      Sequential              - set the dance ball to sequential mode where each dance is played in order
//      Random                  - set the dance ball to random mode where dances are picked randomly (but a dance won't repeat until all dances have played)
//      Manual                  - each current dance will be played until the owner clicks the dance ball and selects the next (or previous) dance
//                                recommended not to leave the ball in manual mode if you won't be there to change dances periodically
//      Previous                - play the previous dance - this option is only shown then the ball is on and not in random mode
//      Pause/Unpause           - pauses the ball at the current dance...click ball again and unpause to resume - this option is only shown when the ball is on
//      Next                    - advances the dance ball to the next dance (works even in sequential or random mode) - only shown if the ball is on
//      Timers                  - shows timer 1, 2, and 3 - the text in the dialogue box indicates what their settings are (in seconds)
//
// REQUIRED OSSL IN REGION:
//
// This script will only work if the following OSSL functions are enabled in the region:
//      osAvatarPlayAnimation();
//      osAvatarStopAnimation();
//
// This script is not SL-compatible.
//
// HOW IT WORKS:
//
// Most conventional danceballs (SL-compatible ones) require a controller script and then 1 animation handler script
// for each dancer you want to be able to support, so if you want your ball to handle 30 dancers you'd need 31 scripts
// all running in that ball at that same time. That's because of the permission system and the way animations are done using the
// llStartAnimation() and llStopAnimation() functions. The conventional ball needs to ask for animation permissions before
// it can begin to animate an avatar -- a request that noobs don't always notice popping up on their screen -- and each script can
// only animate the one (most recent) avatar that granted permissions (hence the need for 1 script per dancer). By using OSSL
// animation functions, this script completely removes those restrictions because the OSSL functions don't require the permission
// check and are assigned to a specific avatar when called. This allows 1 script to handle far more avatars than a region could
// ever support (the limit is the script memory limit for the list of UUIDs so theoritically it could handle thousands of avatars
// simultaneously without breaking a sweat....but of course no region can support those numbers :P
//
// This script is the same basic controller script that most danceballs use (I wrote it from scratch though since I've given it features
// I wanted the danceball in my own regions to have and wanted to have it NPC-compatible too, though those functions aren't included in
// this general release version). OSSL animation functions means that it doesn't need all the animation scripts...this one script handles everyone!
//
// The mechanics are simple: the script maintains a list of avatar UUIDs it is animating. When someone touches the ball it checks to see
// if the toucher's UUID is in the list. If it isn't, the UUID is added and the script starts animating the avatar. If the UUID is
// already in the list we assume they want to stop dancing so we halt the dance animation and send the avatar a "stand" animation
// (essentially releasing the animation permissions that we never had to ask for in the first place). The script also performs a periodic
// check to make sure that the avatars it is handling are still in the region and in fairly close proximity to the dance ball. If an avatar
// wanders too far away from the ball they're removed from the list (after responsibly stopping their dance animation and replacing it with
// the basic "stand" instead). If they leave the region all we can do is remove them from the list...they'll have to manually stop dancing.
//
// USER SETTINGS:
// Adjust the following to suit your preference
string myName = "Group Dance Ball";        // name you want displayed in floaty text above the ball
vector textColour=<1.0,0.85,0.20>;                          // RGB colour of the text (<0,0,0>=black, <1,1,1> = white)
float textAlpha=1.0;                                        // transparency of the text (0=opaque, 1 = invisible (effectively off))
float proximity=20;                                         // radius (in meters) for priximity check - if avatar is outside this range auto-stops animation
integer animateTexture=TRUE;                                // set to FALSE if you want to disable the rotating ball texture animation
float textureRpm=3.0;                                       // if animationTexture is TRUE, rotation rate of dance ball texture (rotations per minute)
integer chgDance1=120;                                      // default time (in seconds) to play a dance before advancing to the next one
integer chgDance2=60;                                       // an alternate time you might wish to switch to using the owner menu
integer chgDance3=360;                                      // another alternative time (you can easily switch back and forth between all 3)
string nextDanceMode="RANDOM";                          // default mode for the danceball: SEQUENTIAL | RANDOM | MANUAL
//
//
// ***************************************************************************************************
// ***** MAIN SCRIPT STARTS - DON'T CHANGE ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU'RE DOING *****
// ***************************************************************************************************
list dancerList=[];     // currently being animated
list danceList=[];      // animations in inventory
list dancesNotUsed=[];  // in random mode, tracks which dances haven't been used yet
integer danceIndex;     // current index in danceList
string currentDance;    // name of index
string stopDance;       // name of old dance to stop when starting next one
float danceTimer=10.0;  // how often to check dancer list to confirm still in range - you can change this if you want but 10 seconds is low lag and usually often enough
integer cycleCount;
integer cycleNext;
integer isOn=FALSE;
integer myChannel;
integer handle;
float diaTimeout=60.0;
integer countDiaTimeout;
integer isPaused=FALSE;
integer pauseCount;

handleOwnerTouch()
{
    list butR1=[];
    if (llListFindList(dancerList,[llGetOwner()])>-1) butR1+=["Stop Dancing"];
    else butR1+=["Start Dancing"];
    butR1+=["Force Quit","Script Reset"];
    list butR2=["Sequential","Random","Manual"];
    list butR3=[];
    // R3 only needed when unit is in use
    if (isOn)
    {
        // in random mode can't do previous
        if (nextDanceMode=="RANDOM") butR3+=["-"];
        else butR3=["Previous"];
        if (isPaused) butR3+=["Unpause"];
        else butR3+=["Pause"];
        butR3+=["Next"];
    }
    list butR4=["Timer 1","Timer 2","Timer 3"];
    list butDia=butR4+butR3+butR2+butR1;
    string txtDia="SELECT AN ACTION:\n\n"
        +"Timers options are currently set to\n"
        +"Timer 1: "+(string)chgDance1+"\n"
        +"Timer 2: "+(string)chgDance2+"\n"
        +"Timer 3: "+(string)chgDance3+"\n";
    // turn on listen and send dialogue...set timer to 1 so it knows to start counting
    llListenControl(handle,TRUE);
    countDiaTimeout=1;
    llDialog(llGetOwner(),txtDia,butDia,myChannel);
}
startDancing(key who)
{
    // start the new animation first, then stop all others (always start before stopping so there's an active animation to fall back to)
    osAvatarPlayAnimation(who,currentDance);
    list animToStop=llGetAnimationList(who);
    key currentDanceKey=llGetInventoryKey(currentDance);
    integer i=llGetListLength(animToStop);
    while (--i>=0)
    {
        if(llList2Key(animToStop,i)!=currentDanceKey) osAvatarStopAnimation(who,llList2String(animToStop,i));
    }
    // announce new dancer
    llRegionSayTo(who,0,"Touch me again when you wish to stop dancing.\nHave fun!");
    llSay(0,llKey2Name(who)+" has joined the dance");
    // add to list and run a dancer check which will activate the ball if this is the first dancer
    dancerList+=[who];
    checkDancers();
}

stopDancing(key who)
{
    // start the basic stand animation first, then stop the dancing one
    osAvatarPlayAnimation(who,"stand");
    osAvatarStopAnimation(who,currentDance);
    // remove from list, announce the departure, and then run a dancer check which will deactivate the ball if no more dancers
    integer indexLeaving=llListFindList(dancerList,[who]);
    dancerList=llDeleteSubList(dancerList,indexLeaving,indexLeaving);
    // could be triggered by a force shut down so only announce and do dancer check if state is on
    if (isOn)
    {
        llSay(0,llKey2Name(who)+" has left the dance floor");
        checkDancers();
    }
}

nextDance(string triggerBy)
{
    // advancing to the next dance so reset cycle counter (can do this even if not in cycle mode)
    cycleCount=0;
    // store name of old dance and get name of next one based on current mode
    stopDance=currentDance;
    if (nextDanceMode=="RANDOM")
    {
        // just in case this was done after deleting the current animation, recheck index before trying to delete
        danceIndex=llListFindList(dancesNotUsed,[currentDance]);
        if (danceIndex>-1) dancesNotUsed=llDeleteSubList(dancesNotUsed,danceIndex,danceIndex);
        // regenerate the list if we've now used all of them
        if (llGetListLength(dancesNotUsed)==0) dancesNotUsed=danceList;
        // pseudo-randomly get the next dance's index
        danceIndex=(integer)llFrand((llGetListLength(dancesNotUsed)-1));
    }
    else // all other methods are sequential in some way
    {
        if (triggerBy=="PREV") danceIndex--;
        else danceIndex++;
        // check if out of range and cycle around if it is
        if (danceIndex==llGetListLength(danceList)) danceIndex=0;
        else if (danceIndex<0) danceIndex=llGetListLength(danceList)-1;
    }
    // now that we have the next index, fetch the name
    currentDance=llList2String(danceList,danceIndex);
    // send out the new dance and stop the old dance on all dancers (this function is only triggered from a dancerCheck so list is current)
    // however, if there is only 1 dance or mode is random it is possible that the same dance was picked twice so only change dance if it's actualyl changing
    if (currentDance!=stopDance)
    {
        integer dancerIndex=llGetListLength(dancerList);
        while (--dancerIndex>=0)
        {
            osAvatarPlayAnimation(llList2Key(dancerList,dancerIndex),currentDance);
            osAvatarStopAnimation(llList2Key(dancerList,dancerIndex),stopDance);
        }
    }
    // update text
    updateText();
}
turnOn()
{
    // first dancer detected so switch to ON, start timer and update text
    cycleCount=0;
    isOn=TRUE;
    llSetTimerEvent(danceTimer);
    updateText();
    // activate texture animation if it's being used
    if (animateTexture) llSetTextureAnim(ANIM_ON | SMOOTH | LOOP, ALL_SIDES, 1, 1, 0, 0, textureRpm/60);
    // confirm with owner that ball is now in use
    llOwnerSay("Someone has started dancing so activating the dance ball");
}

turnOff()
{
    // turn off listen in case it's open, stop timer, set unit to off
    llListenControl(handle,FALSE);
    isOn=FALSE;
    isPaused=FALSE;
    llSetTimerEvent(0);
    // in case turned off via forced shut down make sure we release any avatars currently being animated
    while (llGetListLength(dancerList)>0)
    {
        stopDancing(llList2Key(dancerList,0));
    }
    updateText();
    // stop animate textures if set to do so
    if (animateTexture) llSetTextureAnim(FALSE,ALL_SIDES,0,0,0.0,0.0,1.0);
    // confirm with owner that now off
    llOwnerSay("There are no more dancerList so turning dance ball off");
}

checkDancers()
{
    // check each dancer key to see if has left region (llAgentSize is most reliable since any of the look-ups will still show avatar for a while after tp)
    // or else if in region check to see if no longer in proximity
    integer dancerCount=llGetListLength(dancerList);
    key thisDancer;
    while(--dancerCount>-1)
    {
        thisDancer=llList2Key(dancerList,dancerCount);
        if(llGetAgentSize(thisDancer)==ZERO_VECTOR) dancerList=llDeleteSubList(dancerList,dancerCount,dancerCount); // out of region...can only remove from list
        else if (llVecDist(llGetPos(),llList2Vector(llGetObjectDetails(thisDancer,[OBJECT_POS]),0))>proximity) stopDancing(thisDancer); // release anims
    }
    // turn off if no dancers
    if (!llGetListLength(dancerList)) turnOff();
    // or if there are dancers but ball is off, we need to turn it on
    else if (!isOn) turnOn();
    // or else things are running normally...depending on mode we need to advance the cycle counter and start the next dance if we're reached the end of the cycle
    else if (nextDanceMode=="SEQUENTIAL" || nextDanceMode=="RANDOM")
    {
        if (isPaused) pauseCount++;
        else if (++cycleCount>=cycleNext) nextDance("CYCLE");
    }
}

buildDanceList()
{
    // build the list of dances from the animations in inventory
    danceList=[];
    integer i=llGetInventoryNumber(INVENTORY_ANIMATION);
    while (--i>=0)
    {
        danceList+=llGetInventoryName(INVENTORY_ANIMATION,i);
    }
    danceList=llListSort(danceList,1,TRUE);
    // if random dance mode all we can do is reset since there's no easy way to tell what was added/subtracted by the inventory change vs having already played
    if (nextDanceMode=="RANDOM") dancesNotUsed=[]+danceList;
    // to allow adding/removing animations during use, find out if we're currently playing an animation and if so reindex to it
    if (currentDance!="") danceIndex=llListFindList(danceList,[currentDance]);
    else currentDance=llList2String(danceList,0);
    // catch the case where current dance was deleted, in which case "force" next dance by triggering the dance check with the cycle counter set to roll over
    if (danceIndex==-1)
    {
        llOwnerSay("The current dance has just been deleted from inventory. Resetting to the first dance");
        cycleCount=cycleNext-1;
        checkDancers();
    }
}

updateText()
{
    // basic function to display the text...tweak script if you want something different
    string ballText=myName;
    if (isOn)
    {
        ballText+=(string)llGetListLength(danceList) +  " dances playing\n"
                +"for "+(string)llGetListLength(dancerList)+" dancers\n";
        if (isPaused) ballText+="* PAUSED *";
        else if (nextDanceMode=="SEQUENTIAL") ballText+="in sequential mode";
        else if (nextDanceMode=="RANDOM") ballText+="in shuffle mode";
        else ballText+="in manual mode";
    }
    else ballText+="Touch me to activate";
    llSetText(ballText,textColour,textAlpha);
}       

default
{
    on_rez(integer param)
    {
        // sometimes scripts dont reset on rez so...
        llResetScript();
    }
    state_entry()
    {
        // calculate the cycles based on timer settings (will only be precise timing if chgDance is a multiple of danceTimer but should be close enough
        cycleNext=llRound(chgDance1/danceTimer);
        // build the dance list
        buildDanceList();
        // set my com channel with owner but deactivate it until actually needed to avoid an unncessary listen running
        myChannel=( -1 * (integer)("0x"+llGetSubString((string)llGetKey(),-5,-1)) );
        handle=llListen(myChannel,"",NULL_KEY,"");
        llListenControl(handle,FALSE);
        // set up the text
        updateText();
    }
    changed(integer change)
    {
        // if there's an inventory change we might have added or removed an animation so build a new dance list
        if (change & CHANGED_INVENTORY) buildDanceList();
    }
    timer()
    {
        // since the timer has to do tripple-duty, we check to see if we're waiting on dialogue with owner
        if (countDiaTimeout>0)
        {
            countDiaTimeout++;
            if (countDiaTimeout>(1+diaTimeout/danceTimer)) // have to add 1 since counter starts at 1, not 0)
            {
                llOwnerSay("Dialogue timed out. Closing dialogue handle");
                llListenControl(handle,FALSE);
                // deactivate timer if the owner was doing something while ball was off
                if (!isOn) llSetTimerEvent(0);
            }
        }
        // if we are paused, we need to increment the pause counter and handle that
        else if (isPaused)
        {
            // arbitary time out for pause is 10 minutes...unpause and tell owner if longer than that
            if (pauseCount*danceTimer>600)
            {
                llOwnerSay("Timed out after 10 minutes in pause mode...unpausing");
                pauseCount=0;
                isPaused=FALSE;
            }
            // check dancers which also increments the pauseCount
            checkDancers();
        }
        // otherwise we do the basic check dancer tick which will trigger next animation if cycle end is reached
        else
        {
            // and for all cases where timer is active send to check dancers which will then set
            checkDancers();
            // and catch a case where timer is somehow active when it shouldn't be
            if (!isOn)
            {
                llOwnerSay("Somehow the timer activated but the dance ball was off and wasn't waiting for a dialogue response");
                llSetTimerEvent(0);
            }
        }
    }
    touch_start(integer num_detected)
    {
        key toucherID=llDetectedKey(0);
        if (toucherID==llGetOwner()) handleOwnerTouch();
        else if (llListFindList(dancerList,[toucherID])>-1) stopDancing(toucherID);
        else startDancing(toucherID);
    }
    listen(integer channel, string name, key id, string message)
    {
        // have message so stop listening and unset timeout counter
        llListenControl(handle,FALSE);
        countDiaTimeout=0;
        // handle the assorted possible messages
        if (message=="Stop Dancing") stopDancing(llGetOwner());
        else if (message=="Start Dancing") startDancing(llGetOwner());
        else if (message=="Force Quit")
        {
            llSay(0,"The owner has asked the dance ball to shut down");
            turnOff();
        }
        else if (message=="Script Reset")
        {
            llSay(0,"The owner has asked the dance ball to shut down and reset");
            turnOff();
            llResetScript();
        }
        else if  (message=="Sequential")
        {
            llOwnerSay("The dance ball will now play dances sequentially");
            nextDanceMode="SEQUENTIAL";
            if (isOn) llSetTimerEvent(danceTimer);
            updateText();
        }
        else if  (message=="Random")
        {
            llOwnerSay("The dance ball will now play dances in random order");
            nextDanceMode="RANDOM";
            dancesNotUsed=danceList;
            if (isOn) llSetTimerEvent(danceTimer);
            updateText();
        }
        else if  (message=="Manual")
        {
           llOwnerSay("The dance ball will now wait for you to change dances");
            nextDanceMode="MANUAL";
            llSetTimerEvent(0);
            cycleCount=0;
            updateText();
        }
        else if  (message=="-"); //do nothing
        else if  (message=="Previous")
        {
            if (isPaused) isPaused=FALSE;
            llOwnerSay("Selecting the previous dance");
            nextDance("PREV");
        }
        else if  (message=="Next")
        {
            if (isPaused) isPaused=FALSE;
            llOwnerSay("Selecting the previous dance");
            nextDance("NEXT");
        }
        else if  (message=="Pause")
        {
            isPaused=TRUE;
            pauseCount=0;
            llOwnerSay("Pausing. The dance ball will unpause automatically in 10 minutes unless you do so sooner");
            updateText();
        }
        else if  (message=="Unpause")
        {
            isPaused=FALSE;
            llOwnerSay("Reactivating timer and resuming play");
            updateText();
        }
        else if  (message=="Timer 1")
        {
            isPaused=FALSE;
            cycleNext=llRound(chgDance1/danceTimer);
            llOwnerSay("Setting timer to "+(string)chgDance1+" seconds");
            if (isOn) llSetTimerEvent(danceTimer);
            updateText();
        }
        else if  (message=="Timer 2")
        {
            isPaused=FALSE;
            cycleNext=llRound(chgDance2/danceTimer);
            llOwnerSay("Setting timer to "+(string)chgDance2+" seconds");
            if (isOn) llSetTimerEvent(danceTimer);
            updateText();
        }
        else if  (message=="Timer 3")
        {
            isPaused=FALSE;
            cycleNext=llRound(chgDance3/danceTimer);
            llOwnerSay("Setting timer to "+(string)chgDance3+" seconds");
            if (isOn) llSetTimerEvent(danceTimer);
            updateText();
        }
        else
        {
            llOwnerSay("Heard a message that I didn't understand: "+message);
        }
    }
}


Top
 Profile  
 
 Post subject: Re: OSSL Group Dance Ball for clubs
PostPosted: Sun Apr 13, 2014 7:31 am 
Furious Typer

Joined: Mon Sep 27, 2010 7:12 am
Posts: 53
SCRIPT #2 - a fancier one

This script is (roughly) the one that I wrote for Dorothea Lundquist to use in Feriae. It requires a little configuration to get the most out of it, but also has an auto-configure option to let you get up and running very quickly. This biggest difference between it and the previous script is the "dance styles" menu options...it uses a notecard to store and retrieve categories of dances ("styles") that you assign to the ball's dance animations. Then the owner can select which style to play at any given time.

It also has the option to display a little more information about the danceball's current use that resembles the older SL danceballs.

More information included in the comments at the top of the script

{L_CODE}:
// Paramour Dance Ball Script v2.0
// by Mata Hari/Aine Caoimhe, May 2013
//
// This dance ball script is intended for larger party situations where there may be any number of visitors
// This is a "singles" dance ball -- all avatars that use it will play the same animation
// The owner has a special menu used to control the dance ball's features. All other users will not receive
// any dialogue menus and need only to touch it once to start dancing and touch it again to stop dancing.
// If an avatar goes out of the pre-set range of the ball they will also be released from dancing.
// The goal of this script is to keep the load on the region server to an absolute minimum so its resources
// can be devoted to handling the (potentially) large number of avatars using it. This necessitates the use of
// OSSL functions to handle animation.
//
// New in V2:
// - The script now supports "dance styles" which allows the owner to configure the danceball to play only a
//   certain group of dances that are all assigned to a "style" (or it can still play them all just like V1
//   did). Both sequential and random advance methods are supported for styles. This change necessitates the use
//   of a notecard to define the styles and which animations are assigned to them so if you don't need this you
//   may want to continue using V1.
// - The script now allows you to select a specific dance to play. If you have a style selected, dance selection
//   is limited to ones assigned to that style. In "ALL" styles mode, all dances will be listed for you to choose from.
// - By request, V2 now has a "synch" option that will restart all dancers to the beginning of the animation.
// - Because of these additions, the script is very slighly less efficient on server load than V1, but not significantly so.
//
// IMPORTANT! This script is NOT like the other danceball scripts you find in SL and Opensim. You DO NOT
// need to make a copy of it for each avatar you want to be able to handle...this single script handles them all.
// It can single-handedly animate more avatars than your server can host (trust me...the script's only limit is the maximum
// list length which means it could easily cope with 1000+ avatars...your region server can't).
//
// This script requires that the region have the certain OSSL functions enabled for the script owner (which also
// means that it cannot be used in Second Life since it has no OSSL support).
// You can do this by adding the following lines to the [XEngine] section of Opensim.ini
//      AllowOSFunctions = true
//      Allow_osAvatarPlayAnimation = ESTATE_OWNER, ESTATE_MANAGER
//      Allow_osAvatarStopAnimation = ESTATE_OWNER, ESTATE_MANAGER
//      Allow_osMakeNotecard = ESTATE_OWNER, ESTATE_MANAGER
//      Allow_osGetNotecard = ESTATE_OWNER, ESTATE_MANAGER
//      Allow_osGetAvatarList = ESTATE_OWNER, ESTATE_MANAGER
// or you can change this to add PARCEL_OWNER and/or PARCEL_GROUP_MEMBER and/or a specic avatar UUID
// (I do not recommend setting any to "true" or to globally allow the necessary VeryHigh threat level for the region).
//
// A side benefit of using OSSL is that it also makes the danceball fully compatible with NPCs. Just have the script
// that controls your NPC tell it to touch the danceball to start dancing and then touch it again (or kill the NPC) to stop.
// Your NPC will need to be owned by the same UUID as the danceball script owner or create the NPC as unowned (but it doesn't
// matter if the NPC is set to be sensed as agent).
//
// HOW TO USE:
// When you receive this script in an danceball that Aine has made, it will already be set up for use
// If you place the script into an object of your own it will attempt to build a default setup notecard with a single
// dance default style (it will not do this until you place animations in the object and then reset the script). You
// can then edit the notecard to add styles or new animations. The "Status" menu option provides some help for you
// when doing this. The notecard format is extremely basic to allow the script to initialize very rapidly when reset.
// See the accompanying READ ME notecard for more details.
//
// For the "synch" feature to work reliably, you will need a basic looped priority 1 stand animation which needs
// to be specified as the "synchPose" in the User Variables section below.
//
// This script is supplied under the terms of GPU 3.0 which means you are free to copy, transfer, and modify it
// any way you want, provided you credit the original creator and supply it free of charge under GPU 3.0 as well.
//
// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
// #                                                                                   #
// # USER VARIABLES -- set these to the general options you want the dance ball to use #
// #                                                                                   #
// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
//
string myName="Group Dance Ball";                      // name that you want to have displayed in chat or leave blank to use the object name
string myDanceData=".DanceData";                    // name of the notecard to read the danceball data from -- if card doesn't exist you will be prompted to create one. See README.
string danceMode="AUTO";                            // default timer mode you want to use
                                                    //      AUTO = advance to the next dance automatically after a certain amount of time
                                                    //      MANUAL = owner must manually advance to the next dance
string danceOrder="RANDOM";                         // default order you want the dance ball to use
                                                    //      RANDOM = advance to a random dance
                                                    //      SEQUENTIAL = play the animations in alphabetical order
                                                    //      SELECTED = keep playing the same dance until the owner manually selects a new one (automatically sets timer mode to manual too)
string danceStyle="ALL";                            // what category of dances you want to play by default
                                                    //      ALL = use all dance animations in the ball
                                                    //      <stylename> = play only dances of the style (must be one that is in the notecard - case sensitive! and max of 10 styles)
float maxRange=64.0;                                // stop playing dances for an avatar who gets further away from the dance ball than this many meters (or leaves the region)
float avCheckFrequency=10.0;                        // how often (in seconds) to check whether each avatars is still within range of the dance ball (very small values = more sim load)
integer showFloatyText=TRUE;                        // should floaty text be shown above the dance ball? (TRUE | FALSE)
vector floatyTextColour=<1.0,0.85,0.2>;             // colour of the floaty text if shown
integer includeUserCount=TRUE;                      // include the number of dancers currently using the ball in the floaty text information?
integer includeDancesCount=TRUE;                    // include the total number of dances in the ball's inventory in the floaty text information?
integer includeCurrentMode=FALSE;                   // include the current timer mode, order and style information in the floaty text?
integer includeDanceName=TRUE;                      // include the name of the current dance in the floaty text?
string synchPose="*****base__stand priority 1";     // a priority 1 looped stand animation to allow synch to work - enter "DO_NOT_SYNCH" if you don't want to use one for some reason
integer synchResetsTimer=TRUE;                      // If set to TRUE, the timer of the current dance will be reset when you click the synch button. If FALSE, it won't.
list timerOptions=[                                 // a maximum of 10 different STRING options that you might want to use as the timer for advancing to the next dance
        "30 sec", 30.0,                             // paired with the float value in seconds to use for the actual timer (dialog menu looks best if you have 3, 6 or 9 options)
        "60 sec", 60.0,                             // Keep the strings short since they are used for the button names in the dialog
        "90 sec", 90.0,
        "2 min", 120.0,
        "3 min", 180.0,
        "5 min", 300.0
    ];
//
// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
// #                                                                                   #
// # MAIN SCRIPT -- don't change anything below here unless you know what you're doing #
// #                                                                                   #
// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
// *** GLOBALS ***
string myState="OFF";        // ON, READY, INITIALIZE, ERROR (in spite of the name, I don't actually use state changes)
key ownerID;
list ballUsers=[];
list danceData=[];
list danceList=[];
list stylesList=[];
string currentDance;
integer myChannel;
integer handle;
integer listenActive=FALSE;
string txtDia;
list butDia=[];
list butR1=[];
list butR2=[];
list butR3=[];
list butR4=[];
string menuLevel="MAIN";
float danceTimer=120.0;
string errorMessage;
integer enableSynch=FALSE;
integer indexDanceList;

// *** UDF ***
doStatusReport()
{
    if (myState=="ERROR")
    {
        llOwnerSay("The danceball is currently in ERROR state which probably means it was unable to find a dance notecard. A status report cannot be run until this is corrected");
        return;
    }
    llOwnerSay("About to performing status report checks...starting step 1: memory analysis...");
    llSleep(0.2);
    string msg;
    list memoryCatAnCounts=[];
    integer l=llGetListLength(danceData);
    integer i;
    while (i<l)
    {
        integer listIndex=llListFindList(memoryCatAnCounts,llList2List(danceData,i,i));
        if (listIndex==-1) memoryCatAnCounts+=[llList2String(danceData,i),1];
        else memoryCatAnCounts=llListReplaceList(memoryCatAnCounts,[llList2Integer(memoryCatAnCounts,listIndex+1)+1],listIndex+1,listIndex+1);
        i+=2;
    }
    integer catCount=(integer)(llGetListLength(memoryCatAnCounts)/2);
    msg="\n*** CURRENT DANCE DATA LOADED IN MEMORY ***\n"
        +"There are "+(string)catCount+" dance categories:\n";
    i=0;
    while(i<catCount)
    {
        msg+="  -"+llList2String(memoryCatAnCounts,i*2)+" has ";
        integer anCount=llList2Integer(memoryCatAnCounts,i*2+1);
        if (anCount==1) msg+=(string)anCount+" animation\n";
        else msg+=(string)anCount+" animations\n";
        i++;
    }
    msg+="...now starting step 2: animation usage...";
    llOwnerSay(msg);
    llSleep(0.2);
    msg="\n*** ANIMATION USAGE INFORMATION ***\n";
    list unusedList=[];
    integer animsFound=llGetInventoryNumber(INVENTORY_ANIMATION);
    if (synchPose=="DO_NOT_SYNCH") msg+="Not using a synch pose because user settings specifies not to\n";
    else if (enableSynch) msg+="The synch pose "+synchPose+" was successfully found in inventory\n";
    else msg+="Dance synch has been disabled because the animation "+synchPose+" was not found in inventory\n";
    msg+="A total of "+(string)(animsFound-enableSynch)+" animations were found in inventory.\n";
    string anName;
    integer countDuplicateUsage;
    i=0;
    while (i<animsFound)
    {
       anName=llGetInventoryName(INVENTORY_ANIMATION,i);
        if (anName!=synchPose)
        {
            integer j=0;
            list usedInCat=[];
            while(j<l)
            {
                if (llList2String(danceData,j+1)==anName) usedInCat+=[llList2String(danceData,j)];
                j+=2;
            }
            integer timesUsed=llGetListLength(usedInCat);
            if (timesUsed==0) unusedList+=[anName];
            else if (timesUsed>1)
            {
                msg+="The animation "+anName+" was found in more than 1 category: "+llDumpList2String(usedInCat," and ")+"\n";
                countDuplicateUsage++;
            }
        }
        i++;
    }
    if (countDuplicateUsage) msg+="(Having an animation in more than 1 category is not a problem if you intended to set it up that way)\n";
    else msg+="Did not find any animations that were assigned to more than one category\n";
    if (llGetListLength(unusedList)>0) msg+="The following animations were found in inventory but are not assigned to a category and "
                                            +"are therefore not being used:\n  -" +llDumpList2String(unusedList,"\n  -")+"\n";
    else msg+="Did not find any animations in inventory that have not been assigned to a category\n";
    msg+="...now starting step 3: default dance notecard analysis...";
    llOwnerSay(msg);
    llSleep(0.2);
    msg="\n*** DEFAULT DANCE NOTECARD AMALYSIS ***\n";
    list testDanceData=[]+llParseString2List(osGetNotecard(myDanceData),["\n"],[]);
    if (llListFindList(danceData,testDanceData)==0) msg+="The data in memory is identical to the data stored in the notecard "+myDanceData+"\n";
    else
    {
        integer missingAnimations;
        i=0;
        l=llGetListLength(testDanceData);
        while (i<l)
        {
            if (llGetInventoryType(llList2String(testDanceData,i+1))!=INVENTORY_ANIMATION)
            {
                missingAnimations++;
                msg+="The animation "+llList2String(testDanceData,i+1)+" assigned to style "+llList2String(testDanceData,i)
                    +" in the notecard was not found in inventory so it was not loaded into current memory\n";
            }
            i+=2;
        }
        if (missingAnimations) msg+="Because at least one animation is missing it is also possible that a category is also missing if that animation"
                                +" was the only one assigned to it.\n";
    }
    llOwnerSay(msg);
    llSleep(0.2);
    llOwnerSay("Status check completed.");
    showMain();
}
doShowDialog()
{
    listenActive=TRUE;
    llListenControl(handle,listenActive);
    while (llListFindList(butDia,["-","-","-"])>-1)
    {
        butDia=llDeleteSubList(butDia,llListFindList(butDia,["-","-","-"]),llListFindList(butDia,["-","-","-"])+2);
    }
    llDialog(ownerID,txtDia,butDia,myChannel);
}
doMakeNewDefaultCard()
{
    llOwnerSay("Attempting to creating a basic notecard based on the animations found in inventory. This may take a minute");
    llSleep(0.2);
    integer animsFound=llGetInventoryNumber(INVENTORY_ANIMATION);
    integer i;
    integer foundSynch=FALSE;
    string anName;
    while (i<animsFound)
    {
        anName=llGetInventoryName(INVENTORY_ANIMATION,i);
        if (anName==synchPose) foundSynch=TRUE;
        else danceData+=["default",anName];
        i++;
    }
    if (foundSynch) animsFound--;
    if (animsFound<1)
    {
        llOwnerSay("ERROR! Unable to find any dance animations in inventory. Please add some and then reset this script to run the set-up again");
        myState="ERROR";
        return;
    }
    doStoreNotecard();
    doBuildStylesList();
    danceStyle="ALL";
    doBuildDanceList();
    llOwnerSay("A new basic notecard has been created and stored. Found and added "+(string)animsFound+" animations to the generic style \"default\" which you"
                +" can now edit according to your preferences. Now resetting script to load the new card.");
    llSleep(0.2);
    llResetScript();
}
doChooseMode()
{
    clearButtons();
    integer listLen=llGetListLength(timerOptions);
    integer i;
    while (i<listLen && i<20)
    {
        if (i<6) butR1+=llList2String(timerOptions,i);
        else if (i<12) butR2+=llList2String(timerOptions,i);
        else if (i<18) butR3+=llList2String(timerOptions,i);
        else butR4+=llList2String(timerOptions,i);
        i+=2;
    }
    while (i<20)
    {
        if (i<6) butR1+=["-"];
        else if (i<12) butR2+=["-"];
        else if (i<18) butR3+=["-"];
        else butR4+=["-"];
        i+=2;
    }
    butR4+=["MANUAL","BACK"];
    butDia=[]+butR4+butR3+butR2+butR1;
    txtDia="Select a timer mode:\n\nPick an auto-timer value or MANUAL for manual advance mode. Click BACK to return to previous menu.";
    menuLevel="CHOOSE_DANCE_MODE";
    doShowDialog();
}
doChooseStyle()
{
    clearButtons();
    integer listLen=llGetListLength(stylesList);
    integer i;
    while (i<listLen && i<10)
    {
        if (i<3) butR1+=llList2String(stylesList,i);
        else if (i<6) butR2+=llList2String(stylesList,i);
        else if (i<9) butR3+=llList2String(stylesList,i);
        else butR4+=llList2String(stylesList,i);
        i++;
    }
    while (i<10)
    {
        if (i<3) butR1+=["-"];
        else if (i<6) butR2+=["-"];
        else if (i<9) butR3+=["-"];
        else butR4+=["-"];
        i++;
    }
    butR4+=["ALL","BACK"];
    butDia=[]+butR4+butR3+butR2+butR1;
    txtDia="Select a dance style:\n\nPick a dance style to play or ALL to play all. Click BACK to return to previous menu.";
    menuLevel="CHOOSE_DANCE_STYLE";
    doShowDialog();
}
doChooseOrder()
{
    butDia=[]+["RANDOM","SEQUENTIAL","SELECTED","BACK"];
    txtDia="Select the order you want dances to play in the current style: RANDOM or SEQUENTIAL\nOr SELECTED to have you pick each one manually, or BACK to return to main menu";
    menuLevel="CHOOSE_DANCE_ORDER";
    doShowDialog();
}
doSelectDance()
{
    clearButtons();
    txtDia="Select a dance to play from the current list of "+danceStyle+" dances\n\n";
    integer ttlDances=llGetListLength(danceList);
    integer i;
    string butTxt;
    while (i<9)
    {
        if ((indexDanceList+i)<ttlDances)
        {
            txtDia+=(string)(indexDanceList+i+1)+". "+llList2String(danceList,indexDanceList+i)+"\n";
            butTxt=(string)(indexDanceList+i+1);
        }
        else butTxt="-";
        if (i<3) butR1+=[butTxt];
        else if (i<6) butR2+=[butTxt];
        else if (i<9) butR3+=[butTxt];
        i++;
    }
    if (ttlDances>11)
    {
        butR4=["< PREV","MAIN MENU","NEXT >"];
    }
    else
    {
        while (i<11)
        {
            if ((indexDanceList+i)<ttlDances)
            {
                txtDia+=(string)(indexDanceList+i+1)+". "+llList2String(danceList,indexDanceList+i)+"\n";
                butTxt=(string)(indexDanceList+i+1);
            }
            else butTxt="-";
            butR4+=[butTxt];
            i++;
        }
        butR4+=["MAIN MENU"];
    }
    butDia=[]+butR4+butR3+butR2+butR1;
    menuLevel="SELECT_DANCE";
    doShowDialog();
}
showMain()
{
    if (llListFindList(ballUsers,ownerID)<0) butDia=["Start Dancing"];
    else butDia=["Stop Dancing"];
    butDia+=["Status","Exit","Pick Timer","Pick Style","Pick Order","Next Dance","Pick Dance","Synch"];
    txtDia="\n\nCurrent Settings:\n  Mode: "+danceMode+"\n  Style: "+danceStyle+"\n  Order: "+danceOrder;
    txtDia+="\n\nCurrent dance: "+currentDance;
    menuLevel="MAIN";
    doShowDialog();
}
doShowMenu()
{
    if (listenActive)
    {
        llOwnerSay(myName+" was expecting a response to a previous dialog but didn't receive one. Re-displaying the last menu");
        doShowDialog();
    }
    else showMain();
}
doBuildStylesList()
{
    stylesList=[];
    integer i;
    integer l=llGetListLength(danceData);
    while (i<l)
    {
        if (llListFindList(stylesList,[llList2String(danceData,i)])==-1) stylesList+=[llList2String(danceData,i)];
        i+=2;
    }
    stylesList=llListSort(stylesList,1,TRUE);
    integer styleCount=llGetListLength(stylesList);
    if (styleCount==0)
    {
        llOwnerSay("ERROR! Unable to find any dance styles specified in your notecard! Please run set-up as soon as possible");
        danceStyle="ALL";
    }
    else if (styleCount>10) llOwnerSay("ERROR! Found "+(string)styleCount+" dance styles but only the first 10 can be displayed during style selection");
}
doBuildDanceList()
{
    danceList=[];
    integer i;
    integer l=llGetListLength(danceData);
    while (i<l)
    {
        if ((danceStyle=="ALL")||(llList2String(danceData,i)==danceStyle))
        {
            danceList+=llList2List(danceData,i+1,i+1);
        }
        i+=2;
    }
    if (llGetListLength(danceList)==0)
    {
        if (danceStyle=="ALL") llOwnerSay("ERROR! Unable to find any animations listed in your notecard");
        else llOwnerSay("ERROR! Unable to find any animations of the style "+danceStyle+" in inventory");
        myState="ERROR";
        doUpdateFloatyText();
        return;
    }
    if (danceOrder=="RANDOM") danceList=llListRandomize(danceList,1);
    else danceList=llListSort(danceList,1,TRUE);
    indexDanceList=0;
}
doCheckDancers()
{
    list avsInRegion=osGetAvatarList();
    key avToCheck;
    // this method for distance checks is far more efficient than using llVectDist but checking pos each time in case ball needs to be able to move
    vector myPos=llGetPos();
    vector targetPos;
    vector difPos;
    integer i=llGetListLength(ballUsers);
    while (--i>-1)
    {
        avToCheck=llList2Key(ballUsers,i);
        if (llGetAgentSize(avToCheck)==ZERO_VECTOR) ballUsers=llDeleteSubList(ballUsers,i,i); // no longer in region so remove but can't stop anim
        else
        {
            // owner isn't returned in the ossl function so have to get target using another function call
            if (avToCheck==ownerID) targetPos=llList2Vector(llGetObjectDetails(ownerID,[OBJECT_POS]),0);
            else targetPos=llList2Vector(avsInRegion,llListFindList(avsInRegion,avToCheck)+1);
            difPos=targetPos-myPos;
            if ((difPos*difPos)>(maxRange*maxRange)) doStopDancing(avToCheck);
        }
    }
}
doNextDance()
{
    if (danceOrder=="SELECTED")
    {
        llSetTimerEvent(0.0);
        doSelectDance();
        return;
    }
    integer danceIndex=llListFindList(danceList,currentDance);
    danceIndex++;
    if (danceIndex>=llGetListLength(danceList))
    {
        danceIndex=0;
        if (danceOrder=="RANDOM") danceList=llListRandomize(danceList,1);
    }
    string nextDance=llList2String(danceList,danceIndex);
    doNewDance(nextDance);
    if (danceMode=="AUTO")
    {
        llSetTimerEvent(0.0);
        llSetTimerEvent(danceTimer);
    }
}
doNewDance(string nextDance)
{
    integer i=llGetListLength(ballUsers);
    key avToDo;
    while (--i>-1)
    {
        avToDo=llList2Key(ballUsers,i);
        osAvatarPlayAnimation(avToDo,nextDance);
        osAvatarStopAnimation(avToDo,currentDance);
    }
    currentDance=nextDance;
    doUpdateFloatyText();
}
doSynch()
{
    integer i=llGetListLength(ballUsers);
    key avToDo;
    while (--i>-1)
    {
        avToDo=llList2Key(ballUsers,i);
        if (!enableSynch) osAvatarPlayAnimation(avToDo,"stand");
        osAvatarStopAnimation(avToDo,currentDance);
        osAvatarPlayAnimation(avToDo,currentDance);
        if (!enableSynch) osAvatarStopAnimation(avToDo,"stand");
    }
    llOwnerSay("Dancers should all now be synchronized");
    if (synchResetsTimer && danceMode=="AUTO")
    {
        llSetTimerEvent(0.0);
        llSetTimerEvent(danceTimer);
    }
    showMain();
}
doStartDancing(key avID)
{
    key dontStop;
    if (enableSynch)
    {
        dontStop=llGetInventoryKey(synchPose);
        osAvatarPlayAnimation(avID,synchPose);
    }
    else
    {
        dontStop=llGetInventoryKey(currentDance);
        osAvatarPlayAnimation(avID,currentDance);
    }
    llSleep(0.2);
    list anToStop=llGetAnimationList(avID);
    integer i=llGetListLength(anToStop);
    while (--i>-1)
    {
        if (llList2Key(anToStop,i)!=dontStop) osAvatarStopAnimation(avID,llList2Key(anToStop,i));
    }
    if (enableSynch) osAvatarPlayAnimation(avID,currentDance);
    ballUsers+=[avID];
    if (llGetListLength(ballUsers)==1) doFirstDancer(); // if this is the first dancer we need to do extra stuff
    doUpdateFloatyText();
}
doStopDancing(key avID)
{
    osAvatarPlayAnimation(avID,"stand");
    osAvatarStopAnimation(avID,currentDance);
    if (enableSynch) osAvatarStopAnimation(avID,synchPose);
    ballUsers=llDeleteSubList(ballUsers,llListFindList(ballUsers,avID),llListFindList(ballUsers,avID));
    if (llGetListLength(ballUsers)<1) doLastDancer(); // if this is the last dancer we need to do extra stuff
    doUpdateFloatyText();
}
doFirstDancer()
{
    if (danceMode=="AUTO") llSetTimerEvent(danceTimer);
    llSensorRepeat("I_DO_NOT_EXIST","",SCRIPTED,0.5,PI,avCheckFrequency);
    myState="ON";
    doUpdateFloatyText();
}
doLastDancer()
{
    llSetTimerEvent(0.0);
    llSensorRemove();
    myState="READY";
    doUpdateFloatyText();
}
integer doGetNotecardData()
{
    if (llGetInventoryType(myDanceData)!=INVENTORY_NOTECARD)
    {
        errorMessage="Failed to find the required danceball setup notecard in inventory";
        return FALSE;
    }
    danceData=[]+llParseString2List(osGetNotecard(myDanceData),["\n"],[]);
    integer i=llGetListLength(danceData);
    while (i>-1)
    {
        i-=2;
        if (llGetInventoryType(llList2String(danceData,i+1))!=INVENTORY_ANIMATION)
        {
            llOwnerSay("NON-FATAL ERROR! Unable to locate the animation "+llList2String(danceData,i+1)+" in inventory so temporarily removing it from your list"
                    +" To re-enable it, place the animation in the danceball and reset the script");
            danceData=llDeleteSubList(danceData,i,i+1);
        }
    }
    return TRUE;
}
doStoreNotecard()
{
    if (llGetInventoryType(myDanceData)==INVENTORY_NOTECARD)
    {
        llRemoveInventory(myDanceData);
        llSleep(0.5);
    }
    osMakeNotecard(myDanceData,danceData);
}
doUpdateFloatyText()
{
    if (!showFloatyText) return;
    string textToShow=myName;
    if (includeDancesCount) textToShow+="\nDances loaded: "+(string)(llGetListLength(danceData)/2);
    if (includeUserCount) textToShow+="\nPeople dancing: "+(string)llGetListLength(ballUsers);
    if (includeCurrentMode)
    {
        if (myState=="READY") textToShow+="\nReady";
        else if (myState=="ERROR") textToShow+="\nERROR: danceball requires a reset!";
        else textToShow+="\nPlaying "+danceStyle+" dances\nin "+danceOrder+" order\nusing "+danceMode+" advance";
    }
    if (includeDanceName)
    {
        if (myState=="ON") textToShow+="\nCurrently playing: "+currentDance;
        else textToShow+="\n"+myState;
    }
    llSetText(textToShow,floatyTextColour,1.0);
}
doSetBasicGlobals()
{
    ownerID=llGetOwner();
    if (myName=="") myName=llGetObjectName();
    myChannel=(integer)("0x"+llGetSubString((string)llGetKey(),-8,-1));
    if (llGetInventoryType(synchPose)!=INVENTORY_ANIMATION)
        llOwnerSay("NON-FATAL ERROR: unable to find the synchPose animation in the danceball inventory."
        +" The dance ball will still work but the synch feature might not. To enable synch please"
        +" put the animation in inventory and update the user variable in the script with its name");
    else enableSynch=TRUE;
    handle=llListen(myChannel,"",ownerID,"");
    llListenControl(handle,listenActive);
}
string doCleanUpTextMessage(string message)
{
    while(llSubStringIndex(message,"\n")!=-1)
    {
        message=llDeleteSubString(message,llSubStringIndex(message,"\n"),llSubStringIndex(message,"\n")+1);
    }
    message=llStringTrim(message,STRING_TRIM);
    return message;
}
clearButtons()
{
    butR1=[];
    butR2=[];
    butR3=[];
    butR4=[];
}
// *** MAIN STARTS ***
default
{
    on_rez(integer num)
    {
        llResetScript();
    }
    state_entry()
    {
        doSetBasicGlobals();
        if (doGetNotecardData())
        {
            // determine which dance to start with
            doBuildStylesList();
            doBuildDanceList();
            currentDance=llList2String(danceList,0);
            myState="READY";
            if (showFloatyText) doUpdateFloatyText();
            else llSetText("",<0,0,0>,0);
            llOwnerSay(myName+" is ready for use");
        }
        else // there was no notecard
        {
            if (llGetAgentSize(ownerID)==ZERO_VECTOR) // owner isn't in the region
            {
                myState="ERROR";
                showFloatyText=TRUE;
                includeCurrentMode=TRUE;
                includeUserCount=FALSE;
                includeDancesCount=FALSE;
                includeDanceName=FALSE;
                doUpdateFloatyText();
            }
            else
            {
                llOwnerSay("ERROR! "+errorMessage+"\nAttempting to make a default notecard based on animations in inventory. This may take a few moments.");
                llSleep(0.2);
                doMakeNewDefaultCard();
            }
        }
    }
    touch_end(integer num)
    {
        key toucher=llDetectedKey(0);
        if (myState=="INITIALIZE")
        {
            llRegionSayTo(toucher,0,"The dance ball is currently initializing. Please wait a few moments until it is ready, then try again.");
            return;
        }
        if (myState=="ERROR")
        {
            if (toucher!=ownerID)
            {
                llRegionSayTo(toucher,0,"Sorry, the dance ball is currently experiencing an error which needs to be fixed by the owner before it can be used.");
                return;
            }
            else
            {
                llRegionSayTo(ownerID,0,"While you were away the danceball experienced an error which requires your attention."
                                +"\nThe error message is: "+errorMessage
                                +"\nThe script will now reset so you can monitor the progress and correct any errors.");
                llResetScript();
                return;
            }
        }
        // otherwise state will either be READY or ON
        if (toucher==ownerID) doShowMenu();   // owner gets dialog menu
        else    // everyone else just toggles between dancing or not dancing
        {
            if (llListFindList(ballUsers,[toucher])<0) doStartDancing(toucher); // new dancer
            else doStopDancing(toucher);
        }
    }
    changed (integer change)
    {
        if ((change & CHANGED_OWNER) || (change & CHANGED_REGION_START))
        {
            llResetScript();
            return;
        }
    }
    sensor(integer num)
    {
        llOwnerSay("Seriously?! You put a scripted object with the name I_DO_NOT_EXIST less than a meter away from this dance ball? Maybe you should move it "
                +"because this sensor event is only being used as a sneaky way to get a second, independent timer.");
        doCheckDancers();
    }
    no_sensor()
    {
        doCheckDancers();
    }
    timer()
    {
        // should only be possible if in random or sequence mode and if there is at least 1 dancer and state is on
        if (myState!="ON" || llGetListLength(ballUsers)<1)
        {
            llSetTimerEvent(0.0);
            listenActive=FALSE;
            llListenControl(handle,listenActive);
            llSensorRemove();
            myState="READY";
            return;
        }
        if (danceMode!="AUTO") // shouldn't be possible
        {
            llSetTimerEvent(0.0);
            return;
        }
        doNextDance();
    }
    listen(integer channel, string name, key id, string message)
    {
        // sim load: always remove listen before doing anything else
        listenActive=FALSE;
        llListenControl(handle,listenActive);
        if (menuLevel=="MAIN")
        {
            if (message=="Exit") return;
            else if (message=="Status") doStatusReport();
            else if (message=="Pick Timer") doChooseMode();
            else if (message=="Pick Style") doChooseStyle();
            else if (message=="Pick Order") doChooseOrder();
            else if (message=="Synch") doSynch();
            else if (message=="Pick Dance") doSelectDance();
            else if (message=="Next Dance")
            {
                doNextDance();
                showMain();;
            }
            else if (message=="Start Dancing")
            {
                doStartDancing(ownerID);
                showMain();
            }
            else if (message=="Stop Dancing")
            {
                doStopDancing(ownerID);
                showMain();
            }
            else
            {
                llOwnerSay(myName+" received an unknown response "+message+" while expecting a main menu selection. Returning to main");
                showMain();
            }
        }
        else if (menuLevel=="CHOOSE_DANCE_MODE")
        {
            if (message=="BACK") showMain();
            else if (message=="MANUAL")
            {
                danceMode="MANUAL";
                llOwnerSay("Dances will now advance manually when you click NEXT on the main menu");
                llSetTimerEvent(0.0);
                showMain();
            }
            else // response should be a value in the timer list
            {
                integer timerIndex=llListFindList(timerOptions,[message]);
                if (timerIndex<0)
                {
                    llOwnerSay(myName+" expected to receive a dialog response for choosing a dance mode but the value "+message+" was not in the option list. Redisplaying menu");
                    doShowDialog();
                }
                else
                {
                    danceTimer=llList2Float(timerOptions,timerIndex+1);
                    llSetTimerEvent(danceTimer);
                    string extraMsg;
                    if (danceOrder=="SELECTED")
                    {
                        danceOrder="SEQUENTIAL";
                        extraMsg=". The dance order setting was SELECTED which can't be used with an auto timer so this has now been changed to SEQUENTIAL instead.";
                    }
                    llOwnerSay("Dances will now advance automatically every "+message+extraMsg);
                    showMain();
                }
            }
        }
        else if (menuLevel=="CHOOSE_DANCE_STYLE")
        {
            if (message=="BACK") showMain();
            else if (message=="-") doShowDialog();
            else if (message=="ALL")
            {
                danceStyle="ALL";
                doBuildDanceList();
                doNextDance();
                llOwnerSay("All dance styles will now be used for dance selection");
                showMain();
            }
            else if (llListFindList(stylesList,[message])==-1)
            {
                llOwnerSay(myName+" expected to receive a dialog response for choosing a dance style but the value "+message+" was not in the styles list. Redisplaying menu");
                doShowDialog();
            }
            else
            {
                danceStyle=message;
                doBuildDanceList();
                doNextDance();
                llOwnerSay(myName+" will now only use dances from the style "+danceStyle);
                showMain();
            }
        }
        else if (menuLevel=="CHOOSE_DANCE_ORDER")
        {
            if (message=="BACK") showMain();
            else if (message=="-") doShowDialog();
            else if ((message=="RANDOM")||(message=="SEQUENTIAL"))
            {
                llOwnerSay("Dances will now be played in "+message+" order");
                danceOrder=message;
                doBuildDanceList();
                showMain();
            }
            else if (message=="SELECTED")
            {
                danceOrder="SELECTED";
                danceMode="MANUAL";
                doBuildDanceList();
                llSetTimerEvent(0.0);
                llOwnerSay("A dance will now continue to play until you select a new one (timer mode now also set to MANUAL if it was previously AUTO)");
                doSelectDance();
            }
            else
            {
                llOwnerSay(myName+" expected to receive a dialog response for choosing a dance order but the value "+message+" was not expected. Redisplaying menu");
                doShowDialog();
            }
        }
        else if (menuLevel=="SELECT_DANCE")
        {
            if (message=="MAIN MENU") showMain();
            else if (message=="-") doShowDialog();
            else if (message=="< PREV")
            {
                indexDanceList-=9;
                if(indexDanceList<0)
                {
                    if(indexDanceList>-9) indexDanceList=0;
                    else indexDanceList=llGetListLength(danceList)-10;
                }
                doSelectDance();
            }
            else if (message=="NEXT >")
            {
                indexDanceList+=9;
                if (indexDanceList>=llGetListLength(danceList)) indexDanceList=0;
                doSelectDance();
            }
            else
            {
                // should be a dance selection
                string nextDance=llList2String(danceList,((integer)message-1));
                doNewDance(nextDance);
                showMain();
            }
        }
    }
}


And the "READ ME" notecard that I supplied with it to explain how to set up the notecard:

The Paramour danceball is designed to offer the maximum possible performance with minimum possible server load on a sim (since the goal is to keep server resource usage to a minimum so it can handle the anticipated load of having many avatars in the region) which means that is uses a somewhat not-user-friendly notecard system for storing and adding/editing the dance data. However, by using this format we can keep the load times for even a large number of dances (100+) to less than 1 second and the total script size and complexity can be reduced by a huge amount -- a menu-driven edit system would necessitate hundreds of additional lines of script, if not a whole addition script which then eats into the resources. Because of the intended use, it is expected that edits will be rare so the benefits of NOT scripting the menu-driven system should far outweigh the annoyance factor.

The format is actually very, very simple:

Each dance animation that you want to use in your danceball needs to have a corresponding entry in the dance notecard. An entry is 2 lines:
<dance style name>
<animation name>

The dance style name can be anything you want, except:
- a style name cannot be more than 24 characters (it must fit on a dialog button)
- the dialog buttons can usually only display about 12-15 characters before cutting off the rest, so keeping it to 12 or less is ideal
- a style name cannot contain a linefeed (the pair of symbols "\n")
- style names are case-sensitive so "My Favs" and "my favs" would be treated as separate styles
- the dialog menu system is designed to handle a maximum of 10 styles. If you have more, only the first 10 will be shown for you to pick from

The animation name must be identical to the name of the animation in the danceball's inventory and also cannot contain the linefeed pair of symbols (\n). There is no limit to the number of dances assigned to a style (you will see a multi-page dialog if there are more than 11 assigned to the same style).

The order in the notecard doesn't matter at all so you can add new ones at the start or end or middle without worrying about it at all. The only critical order is that the line with the style name must come immediately before the name of the animation that should be assigned to that style.

When the script is reset, it reads the notecard and confirms that all animations are actually present in inventory. If it doesn't find the animation it removes that entry from the active memory list but doesn't delete it from the notecard. This means that you can't use a UUID to assign an animation instead of having it in inventory (which is something you can do in other scripts). If it removes an animation from memory that is the only one assigned to a style, it will also remove that style from the list.

The script does not detect when you add or remove animations from the danceball's inventory (another resource-saver) or when you edit the notecard, so you will need to do a script reset whenever you do that.

The is a small utility in the script that you can use by clicking on the "Status" button in the main menu. This will analyze the danceball in 3 steps: first checking and confirming the data loaded into memory, then checking and reporting if there are any animations in inventory that aren't being used (so you can delete them or add them to your notecard) and then finally comparing your notecard to the active memory and reporting any differences. This information is "said" to the owner (only) in chat which should help to make it a little easier to edit your notecards.

One final bit of help that the script does: if you delete the notecard (or don't have one there in the first place) and reset the script, it will automatically create a new notecard for you with all animations it finds in inventory added to a default style (except for the synch pose which it knows not to add). This should make it easy to quickly get a notecard with a working list of animations and you can then edit some/all of the style lines to whatever names you like.

NOTE: if you don't like the idea of messing around with notecards you can use Aine's OSSL Dance Ball V1 which does not have the "dance styles" feature but is even lower server load and requires no set-up or notecard at all.


Top
 Profile  
 
 Post subject: Re: OSSL Group Dance Ball for clubs
PostPosted: Sun Apr 13, 2014 7:40 am 
Furious Typer

Joined: Mon Sep 27, 2010 7:12 am
Posts: 53
VERSION #3

Just in case, I guess I should also add a variation the previous one. This is the one I wrote to replace Doro's "white dance ball" in Close Encounter (script #2 above is what she uses for her "red dance ball" and in Feriae). Instead of the owner globally controlling the dance that everyone uses, this one allows each dancer to pick their own (so you're not all playing the same animation, but one script is still handling the entire thing).

{L_CODE}:
// PICK-YOUR-OWN PUBLIC DANCE BALL
// by Mata Hari/Aine Caoimhe January 2014
//
// NOTES:
// This public dance ball script was written specifically to replace the white dance ball at Close Encounter and
// does not have all thefeatures of a more traditional general use public dance ball (but could be adapted).
// It simply allows each person to click and select the dance they want to play (which can be any dance
// in inventory) or stop dancing.
// It is scripted to keep sim load to the absolute minimum when there are a potentially large
// number of users, so this wouldn't be the recommended method for other applications
// (for instance I wouldn't normally have a listen perminently registered, but this method is lower
// sim load for a dialog that expects to accomodate multiple simultaneous users rather than
// registering and later removing unique handles for each user during use since the greatest
// script load will coincide with greatest user load which we definitely don't want!)
//
// REQUIRES:
// - the two necessary OSSL functions: osAvatarStopAnimation() and osAvatarPlayAnimation()
// - a minimum of 1 dance animation in inventory but I recommend having at least 9
//
// USAGE:
// - Place some animations into the prim's inventory and add this script...it will take care of the rest. You could just delete all of the scripts from your existing dance ball and then drop this into the prim and it will work right away
// - You can add or remove animations at any time...even during use. It will detect the change and build a new dances list automatically.
// - If you need to, you can reset the script even while the dance ball is in use! Users will probably not even notice :)
// - If yuo don't have full perm (copy/mod/trans) on an animation that is in the ball's inventory there could be
//   some slightly unpredictable results when someone tries to play it...I think it should still work but I'm not 100% sure. They don't
//   have to be set that way for next user...it's only the perms that you (the script owner) have on them that matter.
//
// CUSTOMIZATION:
// Set the values below to whatever you like...
//
// these two are just the text that appears in the top of the dialog box
string diaName="Pick-Your-Own-Dance Ball";
string diaMessage="Please select a dance below, or click NEXT or PREV to see more, or click STOP to stop dancing";
// this controls if you want floaty text above the ball...
integer showText=TRUE;                              // set it to FALSE if you don't want any text at all or TRUE if you do
string floatyName="D&J Pick-Your-Own-Dance Ball";   // name to show above it when using floaty text (or set to ="" for no name shown)
vector floatyTextColour=<0.0,0.3,0.0>;              // the vector colour of the text if it's shown (I set it to a dark green for now)
float floatyTextAlpha=0.65;                         // alpha of the floaty text.....0.0 would be invisible (transparent), 1.0 is no transparency at all
integer showNumberOfDancesLoaded=TRUE;              // set to TRUE to have the floaty text include the number of differenet dances available or FALSE not to
integer showNumberOfDancers=TRUE;                   // set to TRUE to have the floaty text show how many poeple it thinks are using it, or FALSE not to

// ***************************************************************************************************
// **        Please don't change anything below this line unless you know what you're doing         **
// ***************************************************************************************************
integer myChannel;
integer handle;
list danceList;
list diaIndexList; // strided [UUID of avi, last index]
float cleanTimer=600;

showMenu(key toucher, integer diaIndex)
{
    string diaText=diaName+"\n"+diaMessage;
    list butDia;
    list butRow1;
    list butRow2;
    list butRow3;
    list butRow4=["< PREV","STOP","NEXT >"];
    integer wrap=llGetListLength(danceList);
    integer d;
    while (++d<10)
    {
        if (d<4) butRow1+=llList2String(danceList,diaIndex);
        else if (d<7) butRow2+=llList2String(danceList,diaIndex);
        else butRow3+=llList2String(danceList,diaIndex);
        diaIndex++;
        if (diaIndex>=wrap) diaIndex=0;
    }
    butDia=butRow4+butRow3+butRow2+butRow1;
    llDialog(toucher,diaText,butDia,myChannel);
}
updateText()
{
    if (showText)
    {
        string floatyText;
        floatyText+=floatyName+"\n";
        if (showNumberOfDancesLoaded) floatyText+=(string)llGetListLength(danceList)+" dances loaded\n";
        if (showNumberOfDancers)
        {
            integer dancerCount=(integer)(llGetListLength(diaIndexList)/2);
            if (dancerCount==0) floatyText+="Currently no dancers";
            else if (dancerCount==1) floatyText+="Currently 1 dancer";
            else floatyText+="Currently "+(string)dancerCount+" dancers";
        }
        llSetText(floatyText,floatyTextColour,floatyTextAlpha);
    }
    else llSetText("",ZERO_VECTOR,0);
}
buildDanceList()
{
    // builds a list of the animations in inventory (ignoring any animation with a name length that exceeds button string limit) and sorts then alphabetically
    danceList=[];
    diaIndexList=[];
    integer i=llGetInventoryNumber(INVENTORY_ANIMATION);
    while (--i>-1)
    {
        string danceName=llGetInventoryName(INVENTORY_ANIMATION,i);
        if (llStringLength(danceName)>24)
        {
            llOwnerSay("Error...you have an animation in the dance ball's inventory with a name that is too long.\n"
                    +"Animation name: "+danceName+"\n"
                    +"The maximum allowed name length is 24 (for a button) but I recommend using much shorter names so they'll fit");
        }
        else danceList+=[danceName];
    }
    danceList=llListSort(danceList,1,TRUE);
    integer danceCount=llGetListLength(danceList);
    if (danceCount<1) llOwnerSay("ERROR! Unable to detect any usable animations in the dance ball's inventory. Please add at least one");
    while (danceCount<9) // if less than 9 dances, fill up to that with blanks
    {
        danceList+=["-"];
        danceCount++;
    }
    updateText();
}
playDance(key dancer,string danceName)
{
    // stop any currently running animations and start playing the desired dance
    // if danceName is "controlDanceBallStopDancing" release animation controls instead
    list animToStop=llGetAnimationList(dancer);
    integer anim=llGetListLength(animToStop);
    if (danceName=="controlDanceBallStopDancing") osAvatarPlayAnimation(dancer,"Stand");
    else osAvatarPlayAnimation(dancer,danceName);
    key dontStop=llGetInventoryKey(danceName);
    while (--anim>-1)
    {
        if (llList2Key(animToStop,anim) != dontStop) osAvatarStopAnimation(dancer,llList2String(animToStop,anim));
    }
}
default
{
    on_rez(integer onRez)
    {
        llResetScript();
    }
    changed(integer change)
    {
        if (change & CHANGED_INVENTORY) buildDanceList();
        else if (change & CHANGED_OWNER) llResetScript();
        else if (change & CHANGED_REGION_START) llResetScript();
    }
    state_entry()
    {
        myChannel= 0x80000000 | (integer)("0x"+(string)llGetKey());
        handle=llListen(myChannel,"",NULL_KEY,"");
        buildDanceList();
        updateText();
        llSetTimerEvent(cleanTimer);
    }
    touch_start(integer num_detected)
    {
        while (--num_detected>-1)
        {
            integer diaIndex;
            key toucher=llDetectedKey(num_detected);
            integer found=llListFindList(diaIndexList,[toucher]);
            if (found>-1) diaIndex=llList2Integer(diaIndexList,found+1);
            else
            {
                diaIndexList+=[toucher,0];
                updateText();
            }
            showMenu(toucher,diaIndex);
        }
    }
    timer()
    {
        // load reduction: periodically check to ensure we're not storing unnecessary diaIndexes and purge any entries for avi not in region
        integer check=llGetListLength(diaIndexList);
        while (check>0)
        {
            check-=2;
            if (llGetAgentSize(llList2Key(diaIndexList,check))==ZERO_VECTOR) diaIndexList=llDeleteSubList(diaIndexList,check, check+1);
        }
        updateText();
    }
    listen(integer channel, string name, key id, string message)
    {
        integer found=llListFindList(diaIndexList,id); // should always return a result
        if (found==-1)
        {
            // just in case it somehow got purged add to list and set index to 0...could happen if script is reset while
            // someone has a dialog open... their index will be lost but otherwise it won't interrupt their use
            diaIndexList+=[id,0];
            found=llListFindList(diaIndexList,id);
            updateText();
        }
        integer newIndex=llList2Integer(diaIndexList,found+1);
        // handle possible responses
        if (message=="-")
        {
            // reshow last dialog
            showMenu(id,newIndex);
            return;
        }
        if (message=="STOP")
        {
            // remove from diaIndexList and release animations
            if (found>-1) diaIndexList=llDeleteSubList(diaIndexList,found,found+1);
            playDance(id,"controlDanceBallStopDancing");
            updateText();
            return;
        }
        if (message=="< PREV")
        {
            // show previous page of dances (or wrap to end)
            newIndex-=9;
            if (newIndex<0) newIndex+=llGetListLength(danceList);
            diaIndexList=llListReplaceList(diaIndexList,[newIndex],found+1,found+1);
            showMenu(id,newIndex);
            return;
        }
        if (message=="NEXT >")
        {
            // show next page of dances (or wrap to start)
            newIndex+=9;
            if (newIndex>=llGetListLength(danceList)) newIndex=0;
            diaIndexList=llListReplaceList(diaIndexList,[newIndex],found+1,found+1);
            showMenu(id,newIndex);
            return;
        }
        // getting here means message was the name of a dance so play it (fall-through default)
        playDance(id,message);
    }
}


Top
 Profile  
 
Display posts from previous:  Sort by  
Post a new topicPost a reply Page 1 of 1   [ 4 posts ]


Who is online

Users browsing this forum: No registered users and 2 guests


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:  


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