User:Kephra Nurmi/lsDancemachine

From Second Life Wiki
Jump to: navigation, search

lsDancemachine

I wrote my lsDancemachine, because the most common Evil Fool Dancemachine sucks as a newbie trap, espically in combo with tiny dances, and lags the sim.

The lsDancemachine is a client server dancemachine to animate avatars and to cache their permissions. The protocol works region wide, so you can install one lsDancemachine to manage your animations in a central place, and distribute danceballs and other clients anywhere on the simulator. Clients could be in any object. E.g. a wall of multi prim animation vendors could use the lsDancemachine to cache the avatar permission, or several sit targets share the same animation.

The lsDancemachine is, unlike the most most common Evil Fool dancemachine, neither random nor syncron by default, but offers avatars a dialog of animations to choose. Animations with names longer than 24 characters are mapped to fit dialog limits, to allow the use of non-mod animations. Two players are treated special. Prims owned by the owner of the lsDancemachine can command the server and a player who is designated as DJ can command all dancers into a syncron dance of choice.

lsDancemachine Server

The lsDancemachine Server contains the animations, one .lsDanceServer and many .lsDanceXec scripts. The .lsDanceXec scripts must be named as a CSV to contain the slot number starting with 1. e.g. 'lsDanceXec, 1', 'lsDanceXec, 2', ...

Script: .lsDanceServer

// .lsDanceServer 08163 (c) Kephra Nurmi 2007
// published under Creative Commons Attribution-Share Alike 3.0 License
 
string configName = ".lsDancemachine";
key configKey = NULL_KEY;
integer configLine = 0;
key configReq = NULL_KEY;
integer chDance   = -19;
integer chDeejay  = -17;
string motto = "Lass uns Tanzen oder ...";
key     djKey     = NULL_KEY;
list    dancers   = [];
list    slots     = [];
list    dancemap  = [];
list    translate = [];
integer transmode = 0;
 
configRestart() {
    key conf = llGetInventoryKey(configName);
    if (conf == configKey) return;
    if (conf == NULL_KEY) return;
    if (NULL_KEY != configKey) llResetScript();
    configKey = conf;
    configLine = 0;
    configReq = llGetNotecardLine(configName,configLine);
}
 
updateText() {
    llSetText("lsDancemachine\n" +
        (string)(llGetListLength(dancers)/2) + " dancers\n" +
        (string)(llGetListLength(slots)/2) + " slots free\n" +
        (string)llGetFreeMemory() + " free memory",
        <llFrand(1.0),llFrand(1.0),llFrand(1.0)>, 1);
}
 
string twodigit(integer val) {
    return (string)((val/10)%10)+(string)(val%10);
}
 
string mapDance(string animation) {
    integer i = llListFindList(translate, [ animation ]);
    if (0 < i)
        return llList2String(translate, i-1);
    if (llStringLength(animation) < 24) 
        return animation;
    i = llListFindList(dancemap, [ animation ]);
    if (0 > i) {
        dancemap = dancemap + [ animation ];
        i = llGetListLength(dancemap)-1;
    }
    return llGetSubString(animation,0,21) + twodigit(i+1);
}
 
dialogDancer(key av, integer start) {
    if ((llGetListLength(slots) <= 0) &&
        (llListFindList(dancers, [ av ]) < 0)) {
        llInstantMessage(av,"Sorry all danceslots are used. Please come back later.");
        return;
    }
    list dances = [ ];
    integer n = llGetInventoryNumber(INVENTORY_ANIMATION);
    integer i = start;
    if ((1<transmode) && ([] != translate)) {
        n = llGetListLength(translate)/2;
        while ((i < n) && (i < start+9)) {
            dances = [ llList2String(translate,i*2) ] + dances;
            i++;
        }
    } else
    while ((i < n) && (i < start+9)) {
        dances = [ mapDance(llGetInventoryName(INVENTORY_ANIMATION,i)) ] + dances;
        i++;
    }
    string spc = "                  ";
    dances = dances + [ "stop" ];
    if (i < n) dances = dances + [ "next"+spc+twodigit(start+9) ];
    if (0 < start) dances = dances + [ "prev"+spc+twodigit(start-9) ];
    llDialog(av, motto, dances, chDance);
}
 
integer danceSlot(key av) {
    string name = llKey2Name(av);
    integer i = llListFindList(dancers, [ av ]);
    if (i>=0)
        return llList2Integer(dancers, i+1);
    if (llGetListLength(slots) <= 0) {
        llInstantMessage(av,"Sorry all danceslots are used. Please come back later.");
        return -1;
    }
    i = llListFindList(slots, [ av ]);
    if (i<0)
        i = 0;
    integer j = llList2Integer(slots, i+1);
    slots   = llDeleteSubList(slots,i,i+1);
    dancers = dancers + [ av, j ];
    return j;
}
 
updateSlot(integer slot, string msg, key av) {
    integer i;
    if ("stop" == msg) {
        i = llListFindList(dancers, [ slot ]);
        if (i>0) dancers = llDeleteSubList(dancers,i-1,i);
        i = llListFindList(slots, [ slot ]);
        if (i>0) return;
        if (NULL_KEY == av)
            slots = [ NULL_KEY, slot ] + slots;
        else
            slots = slots + [ av, slot ];
    } else {
        i = llListFindList(slots, [ slot ]);
        if (i>0) slots = llDeleteSubList(slots,i-1,i);
        i = llListFindList(dancers, [ slot ]);
        if (i>0) return;
        dancers = dancers + [ av, slot ];
    }
}
 
updateDancer(key av, string animation) {
    if ((av != djKey) && (av != NULL_KEY) && ((string)av != "*")) {
        integer slot = danceSlot(av);
        if (slot > 0) llMessageLinked(LINK_THIS, slot, animation, av);
    } else
        llMessageLinked(LINK_THIS, 0, animation, av);
}
 
default {
    state_entry() {
        llSetText("config",<1,0,0>,1);
        configRestart();
    }
    changed(integer type) {
        if (type & CHANGED_OWNER) llResetScript();
        if (type & CHANGED_INVENTORY) configRestart();
    }
    dataserver(key queryId, string data) {
        if (queryId != configReq) return;
        if (data != EOF) {
            llSetText("parsing line "+(string)configLine,<1,0,0>,1);
            integer k = llSubStringIndex(data,"=");
            if (k>0) {
                string cmd = llGetSubString(data,0,k-1);
                string val = llGetSubString(data,k+1,-1);
                if ("lsDeejay" == cmd) chDeejay = (integer)val;
                else
                if ("lsDance" == cmd) chDance = (integer)val;
                else
                if ("motto" == cmd) motto = val;
                else
                if (transmode > 0)
                    translate = translate + [ cmd, val ];
            } else {
                if ("[translate]" == data) transmode = 1;
                if ("[dialog]" == data) transmode = 2;
            }
            configReq = llGetNotecardLine(configName,++configLine);
        } else
            state running;
    }
}
 
state running {
    state_entry() {
        llListen( chDance, "", NULL_KEY, "" );
        llListen( chDeejay, "", NULL_KEY, "" );
        llShout( chDeejay, "Request,DJ" );
        llMessageLinked(LINK_THIS, 0, "status", NULL_KEY);
        updateText();
        llSetTimerEvent(900.0);
        llOwnerSay("ready, "+(string)llGetFreeMemory()+" free memory, listen on channel: "+(string)chDance);
    }
    changed(integer type) {
        if (type & CHANGED_OWNER) llResetScript();
        if (type & CHANGED_INVENTORY) configRestart();
    }
    timer() {
        llMessageLinked(LINK_THIS, 0, "online", NULL_KEY);
    }
    listen( integer channel, string name, key id, string msg ) {
        key k = id;
        if (llGetAgentInfo(k)<=0) k = llGetOwnerKey(k);
        if (channel == chDance) {
            if (("stop" == msg) || (INVENTORY_ANIMATION == llGetInventoryType(msg))) {
                updateDancer(k,msg);
                updateText();
                return;
            }
            integer i = llListFindList(translate, [ msg ]);
            if (0 <= i) {
                updateDancer(k,llList2String(translate,i+1));
                updateText();
                return;
            }
            if (24 == llStringLength(msg)) {
                i = (integer)llGetSubString(msg,22,23);
                if (("next" == llGetSubString(msg,0,3)) || ("prev" == llGetSubString(msg,0,3)))
                    dialogDancer(k,i);
                else
                if ((i <= llGetListLength(dancemap)) && (0 < i)) {
                    updateDancer(k,llList2String(dancemap,i-1));
                    updateText();
                    return;
                }
            }
        }
        if (llGetOwner() != k) return;
        list tmp = llCSV2List(msg);
        string cmd = llList2String(tmp,0);
        if ((channel == chDeejay) && ("DJ" == cmd))
            djKey=llList2Key(tmp,1);
        if ((channel == chDance) && ("dance" == cmd)) {
            if (llGetListLength(tmp) == 4) {
                string dance = llList2String(tmp,3);
                if (("stop" == dance) || (INVENTORY_ANIMATION == llGetInventoryType(dance))) {
                    updateDancer(llList2Key(tmp,1),dance);
                    updateText();
                }
            } else
                dialogDancer(llList2Key(tmp,1),0);
        }
    }
    touch_start(integer num_detected) {
        dialogDancer(llDetectedKey(0),0);
    }
    link_message(integer sender, integer num, string msg, key id) {
        if (num < 0) {
            updateSlot(0-num, msg, id);
            updateText();
        }
    }
    on_rez(integer sparam){
        llResetScript();
    }
}

Script: lsDanceXec

 
// .lsDanceXec 08099 lsDancemachine Animation (c) Kephra Nurmi 2007
// published under Creative Commons Attribution-Share Alike 3.0 License
 
key dancerKey = NULL_KEY;
string animation = "stop";
integer scriptnum;
 
sendStatus() {
    if (0 == scriptnum) return;
    if (NULL_KEY != dancerKey) {
        if (llGetAgentInfo(dancerKey) == 0) animation = "stop";
    }
    llMessageLinked(LINK_THIS, 0-scriptnum, animation, dancerKey);
}
 
changeAnimation() {
    sendStatus();
    if (NULL_KEY == dancerKey) return;
    if (llGetAgentInfo(dancerKey) == 0) return;
    if ((llGetPermissions() & PERMISSION_TRIGGER_ANIMATION) &&
        (llGetPermissionsKey() == dancerKey)) {
        list anims = llGetAnimationList(dancerKey);
        integer len = llGetListLength(anims);
        integer i;
        for (i = 0; i < len; ++i) llStopAnimation(llList2Key(anims, i));
        if (len > 0) llSleep(0.1);
        if ("stop" == animation)
            llStartAnimation("stand");
        else
            llStartAnimation(animation);
    } else
    if ("stop" != animation) {
        llRequestPermissions(dancerKey, PERMISSION_TRIGGER_ANIMATION);
        llSetTimerEvent(10.0);
    }
}
 
stopAnimation() {
    animation = "stop"; 
    changeAnimation();
}
 
default {
    state_entry() {
        scriptnum = llList2Integer(llCSV2List(llGetScriptName()),1);
        sendStatus();
    }
 
    link_message(integer sender_num, integer num, string msg, key id) {
        if (0 == scriptnum) return;
        if (num == scriptnum) {
            if ("status" == msg) {
                sendStatus();
                return;
            }
            dancerKey = id;
            if ("stop" == msg) {
                stopAnimation();
                return;
            }
            if (INVENTORY_ANIMATION == llGetInventoryType(msg)) {
                animation = msg;
                changeAnimation();
                return;
            }
            return;
        }
        if (num != 0) return;
        if ("status" == msg) {
            sendStatus();
            return;
        }
        if (NULL_KEY == dancerKey) return;
        if ("online" == msg) {
            sendStatus();
            return;
        }
        if (llGetAgentInfo(dancerKey) == 0) return;
        if ("stop" == animation) return;
        if ("stop" == msg) {
            stopAnimation();
            return;
        }
        if (INVENTORY_ANIMATION != llGetInventoryType(msg)) return;
        animation = msg;
        changeAnimation();
    }
 
    run_time_permissions(integer perms) {
        llSetTimerEvent(0);
        if (perms & PERMISSION_TRIGGER_ANIMATION)
            changeAnimation();
        else
            stopAnimation();
    }
 
    timer() {
        llSetTimerEvent(0);
        stopAnimation();
    }
}

Configuration

The lsDancemachine server contains a notecard .lsDancemachine with default configuration :

lsDance=-19
lsDeejay=-17
motto=Schwing das Tanzbein

You most likely want to change the object name of the lsDancemachine server and the motto to give a nice dialog of your choice, and add or remove dances. You might want to change the default negative channels to positive ones, to allow avatars to use them directly and you need to change the lsDance channel, if you group you animations or cluster permissions into several servers.

You can extend this notecard by a [translate] or [dialog] section to translate dialog names to animation names. A [translate] section will only translate those animations named, and offers all other animations untranslated. A [dialog] section restricts the dialog to the named animations, even if there are more animations in the lsDancemachine. Those unnamed animations will then only be available to DJ or owner command scripts. Example:

[translate]
Club1=Club Dance 1 (looped)
Club2=Club Dance 2 (looped)

this would only translate those two dances, leaving other animation names untouched.

[dialog]
Aero=aerobicdance(looped)
Boogie=boogie

this would restrict the dialog only to offer 'Aero' and 'Boogie' even if there are more animations available.

Protocol

Avatars talk to the lsDancemachine on lsDance channel, even if this is a negative channel, by replying to a menu dialog. The reply is either "stop", or "next", or "prev" or a selected animation. Normaly everybody chooses their own animation, but the DJ can overwrite this choice and command all dancers into a syncronus animation.

Objects owned by the owner of the lsDancemachine server can also use dance and DJ commands formatted as comma separated lists.

The dance command on lsDance channel has either 3 or 4 elements. The short form will offer a dialog to the avatar, while the later also incudes the animation name. e.g. a typical but short danceball dialog:

[11:12]  :lsDanceball 08101: dance,2682eb10-ee55-46a6-bc49-0f09a39d152b,Kephra Nurmi
[11:12]  Kephra Nurmi: next                  09
[11:12]  Kephra Nurmi: Groove
[11:13]  :lsDanceball 08101: dance,2682eb10-ee55-46a6-bc49-0f09a39d152b,Kephra Nurmi
[11:13]  Kephra Nurmi: stop

while the long form is useful for sit targets or vendors:

[11:15]  :Table: dance,2682eb10-ee55-46a6-bc49-0f09a39d152b,Kephra Nurmi,Boogie
[11:15]  :Table: dance,2682eb10-ee55-46a6-bc49-0f09a39d152b,Kephra Nurmi,stop

the long form could also be used with ,*,* as a wildcard to command all avatars:

[11:15]  :DJ: dance,*,*,Boogie
[11:15]  :DJ: dance,*,*,stop

Most commands of the lsDeejay product line but DJ command itself are ignored on lsDeejay channel. The later has the following form to login and logout a DJ.

[11:21]  :lsDeejay server 08113: DJ,2682eb10-ee55-46a6-bc49-0f09a39d152b,Kephra Nurmi
[11:21]  :lsDeejay server 08113: DJ,00000000-0000-0000-0000-000000000000,

Client Scripts

The following open source scripts could be used to talk to the lsDancemachine:

Script: lsDanceball

A simple touch client for danceballs.

// a simple danceball (c) Kephra Nurmi
// published under Creative Commons Attribution-Share Alike 3.0 License
 
integer channel = -19;
default {
    touch_start(integer total_number) {
        llRegionSay(channel,"dance,"+(string)llDetectedKey(0)+","+llDetectedName(0));
    }
}


Script: lsDancepose

// lsDancepose a simple plugin for poseballs (c) Kephra Nurmi
// published under Creative Commons Attribution-Share Alike 3.0 License
// a pose stand - beware of the comma in the dance variable ;)
 
vector SIT_POS = <0.0, 0.0, 0.9>; 
vector SIT_ROT = <0,0,0>;
integer channel = -19;
string dance = ",Boogie";
integer invis = FALSE;
 
key dancer;
default { 
    state_entry() {
        llSetSitText(llGetObjectDesc());
        llSitTarget(SIT_POS, llEuler2Rot(SIT_ROT*DEG_TO_RAD));
    }
    touch_start(integer n) {
        key av = llDetectedKey(0);
        if (av != dancer) return;
        llRegionSay(channel,"dance,"+(string)av+","+llKey2Name(av));
    }
    changed(integer change) {
        if (change & CHANGED_LINK) {
            key av = llAvatarOnSitTarget();
            if (NULL_KEY != av) {
                if (invis) llSetAlpha(0.0, ALL_SIDES); 
                llRegionSay(channel,"dance,"+(string)av+","+llKey2Name(av)+dance);
                dancer = av;
            } else
            if (NULL_KEY != dancer) {
                if (invis) llSetAlpha(1.0, ALL_SIDES); 
                llRegionSay(channel,"dance,"+(string)dancer+","+llKey2Name(dancer)+",stop");
                dancer = NULL_KEY;
            }
        }
    }
}

Ready to use lsDancemachine

You can pick a ready to use lsDancemachine with 64 slots and lots of freebee dances in my home at [Apoda].