This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm whether you accept or reject these cookies being set.

A cookie will be stored in your browser regardless of choice to prevent you being asked this question again. You will be able to change your cookie settings at any time using the link in the footer.

Paramour Line Dancer Script für Opensim in OSSL Regionen mit NPC Unterstützung
#1
Paramour Line Dancer Script für Opensim in OSSL Regionen mit NPC Unterstützung

Folgendes Script basiert auf der Arbeit von Mata Hari / Aine Caoimhe.

Und ist unter folgenden Lizenzbedingungen einzusetzten:           https://creativecommons.org/licenses/by-nc-sa/4.0/

Hier nun die 2. Versprochene Variante für OPENSIM Regionen die OSSL Befehle freigeschalten haben.
Dies ist die Basis damit auch NPCs funktionieren.

ACHTUNG: DIESE VERSION funktioniert nur wenn entsprechende OSSL Befehle für die Region freigegeben sind, und der Primbesitzer ausreichend Rechte hat diese zu nutzen.

Es gilt alles was ich schon im Artikel  "Paramour Line Dancer Script für SL und Opensim in nicht OSSL Regionen" gesagt habe.
Das werde ich an dieser Stelle auch nicht wiederholen.
Hier der Link zum Nachlesen:  https://service-silberwelten.de/showthread.php?tid=157

So- was ist hier anders? Und warum sollte man diese Version einsetzten wo es doch die Andere gibt, die in allen Regionen läuft?
Die Antwort sind die NPCs.
Die gab es auch schon im Original von Mata Hari / Aine Caoimhe.
Aber genau an diesem Punkt haben sich die meisten Verbesserungen in Opensim abgespielt.

Im Detail ist hier folgendes nun im Script verändert:
NPCs können auch auf gesperrten Sims gerezzt werden, wenn in dem Prim aus dem sie gestartet werden die gültige Landgruppe der Sim eingestellt ist.
Ebenso überleben sie dann auch ein nachträgliches Sperren der Sim. Die Gruppe muss aber erlaubt bleiben.

Ausserdem unterstützt dieses Script nun auch für NPCs Profilfotos und Profilinfos.
Ein Dancline NPC hat also nun auch ein Profilbild und eine Beschreibung sofern ihr die anlegt.

Dafür habe ich eine feste Notation eingeführt. Diese ermöglicht es mehrere NPCs unabhängig bereitzustellen.
Ausserdem ziehe ich die gleich Notation auch in allen anderen NPC Scripten nach, damit die Karten einfach austauschbar sind.
Bei dem NPC Beschreibungs Notecards habe ich die Nomenklatur von PMAC zugrunde gelegt.

Eine vollständige NPC Beschreibung besteht also künftig aus 3 Notecards.
Der erste Teil der Notecard ist die Kennung was für eine Karte das ist. Der 2.Teil jeder Notecard ist ein identischer NPC Name.

NPC Outfit Beschreibung:    .NPC123NPCNAME           PFLICHTEINTRAG
NPC Foto:                               .bildNPCNAME                  Optionaler Eintrag
NPC Beschreibung:               .infoNPCNAME                  Optionaler Eintrag

Beispiel:     .NPC123Susan Vogue    
                  .bildSusan Vogue
                  .infoSusan Vogue

Gross/Klein Schreibung und eventuelle Leerzeichen hinter den NPC Namen sind zu beachten.

Für die NPC Outfit Beschreibung gibt PMAC folgendes vor:
.NPC ist die Kennung das es sich um eine Outfit Karte handelt.
Danach folgt eine beliebige 3-stellige Zahl, die von PMAC aber zur Sortierung genutzt wird.
Anschliessend folgt der NPCName. Dieser darf auch 2-teilig mit Leerzeichen dazwischen sein.

Ausserdem neu ist das Berechtigungs System wer NPCs rezzen und den Poser steuern darf. Siehe Script Zeile 56 bis 58
Es bietet Optionen wie "jeder", die gleiche Gruppe" oder spezielle Avas die per UUID  angegeben werden.

Das Script benötigt genau wie PMAC eine Basisanimation niedrigster Priorität.  
Diese muss wie im Script angegeben heissen: "*****base_DO NOT DELETE ME!"


In der Version 3.7 werden die zum Rezzen angeforderten NPCs per Zufall ausgewählt.

Code:
// P604 PARAMOUR LINE-DANCE CONTROLLER OSSL v3.7 VSL by Tron 11.2021
// original OSSL Version by Mata Hari / Aine Caoimhe November 2014
// Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license.
// Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
// *** Dieses Script benötigt Regionen in denen OSSL Befehle freigeschalten sind, und der Scriptbesitzer diese Rechte hat***
//
// Unbedingt darauf achten das Prim dieses Scriptes im Kontext der erlaubten Simgruppe zu rezzen,
// ansonsten kommt es zu NPC Fehlern wenn der öffentliche Zugang abgeschalten wird!
//
// Diverse Änderungen: NPCs rezzen nun Fehlerfrei auf gesperrten Sims wenn sie die erlaubte Gruppe haben
// Änderungen by Tron: Script für Funktionsfähigkeit in Sl und Opensim umgeschrieben.
// Hinzufügung eines umfangreichen Berechtigungs Systems.
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// USER SETTINGS - stuff that any user should feel comfortable setting
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
//
integer textanzeige =TRUE;                      // TRUE = show floaty text, FALSE = hide it
string  textmessage;                            // name to appear in floaty text above the line dance object. Read from Prim description
vector  textcolour =<1.000, 0.847, 0.200>;      // colour to use for the floaty text
//
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// ADVANCED USER SETTINGS - a more advanced user might want to adjust these
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
integer  debug = FALSE;
integer  maxLineLength=5;                        // maximum number of dancers allowed in a single line
float    spacingX=1.5;                           // distance between each dancer in the line (side to side)
float    spacingY=-1.5;                          // when more than 1 line is needed, how far apart to space lines
vector   p1Pos=<0.0, 0.0, 1.25>;                 // sit target position for 1st dancer - all other dancers positioned relative to this position (and centered on it) <0.0, 0.0, -2.0>;
rotation p1Rot=ZERO_ROTATION;                    // sit target rotation for 1st dancer - making this non-zero could produce unusual results!
integer  randomOrder=FALSE;                      // TRUE = play dances in random order, FALSE = play in alphabetical order
float    danceTimer=60.0;                        // how long (in seconds) to play each line dance by default before moving on to the next one (owner can also change this via dialog)
//
//
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// DON'T CHANGE ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU'RE DOING!!!!!
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
string  baseAnim="*****base_DO NOT DELETE ME!";
list    animatliste;
integer animationsnr;
string  currentAn;
list    dancerliste;              // Liste von Avataren UND NPCs auf dem Poser
string  myState="OFF";
integer myChannel;
integer handle;
integer closeHandle;
list    npcList;
integer npcToRez;
integer taenzeranzahl;            // Integer! Anzahl der aktuellen Tänzer

integer poserlinks;
integer taenzeraufposer;

key     toucher;               // UUID des Operator des Menüs

list    zugangsliste = ["2338a1d0-0d7b-4ec1-b841-029c49c9d461","8998586e-b754-4032-856f-f4f4b122eb6e"]; // UUIDS berechtigter AVAs
integer groupaccess  = TRUE;   // TRUE ore FALSE - darf die Gruppe steuern?
integer publicaccess = FALSE;  // TRUE ore FALSE - darf jeder steuern?

list    npccardlist;
list    npcnamelist;


integer berechtigungscheck(string clicker, integer selbegruppe)  // Zugangsberechtigung auswerten
     {
        integer zugang = FALSE;
        integer personalzugang = llGetListLength(zugangsliste);
        if (publicaccess)  { zugang = TRUE;}                     // Öffentlicher Zugang
        else if (selbegruppe && groupaccess) { zugang = TRUE;}   // Gruppen Zugang
        else if (clicker ==  llGetOwner()) { zugang = TRUE;}     // Besitzer Zugang immer erlaubt
        else if (personalzugang)                                 // Persönlicher Zugang über UUID Liste
        {
            string personal;
            do
            {  
                personal = llList2String(zugangsliste, personalzugang -1 );
                if( clicker ==  personal) { zugang = TRUE; }
                personalzugang--;
            }       
            while ( (integer)personal && (!zugang));
        }
        return zugang;
     }


showMenu(key operator)
{
    myChannel=0x80000000 | (integer)("0x"+(string)operator);
    string txtDia="Danceliner Optionen Menü: ADD/Kill NPCs, Animationswechel, Ändern der Tanzdauer";
    list butDia=["ABBRUCH","NextDance","Add NPC","Kill NPC","Kill ALL","60 SEC","90 SEC","120 SEC","300 SEC","Debugmode"];
    handle=llListen(myChannel,"",operator,"");
    llDialog(operator,txtDia,butDia,myChannel);
    closeHandle=TRUE;
}


positionDancers()
{
    dancerliste=[];
    integer link=llGetNumberOfPrims();
    while (link>0)
    {
        if (llGetAgentSize(llGetLinkKey(link))!=ZERO_VECTOR) dancerliste=[]+[llGetLinkKey(link),link]+dancerliste;
        link--;
    }
    integer laufvar5;
    taenzeranzahl=llGetListLength(dancerliste);
    integer row;
    integer pos;
    integer thisLineCount;
    while (laufvar5 < taenzeranzahl)
    {
        vector thisPos=p1Pos;
        rotation thisRot=p1Rot;
        thisPos.y+=(pos*spacingX);
        thisPos.x+=(row*spacingY);
        llSetLinkPrimitiveParamsFast(llList2Integer(dancerliste,laufvar5+1),[PRIM_POS_LOCAL,thisPos,PRIM_ROT_LOCAL,thisRot]);
        thisLineCount++;
        if(thisLineCount>=maxLineLength)
        {
            thisLineCount=0;
            pos=0;
            if (row==0) row++;
            else if (row>0) row=row*-1;
            else row=(row*-1)+1;
        }
        else
        {
            if (pos==0) pos++;
            else if (pos>0) pos=pos*-1;
            else pos=(pos*-1)+1;
        }
        laufvar5 +=2;
    }
}


startDancing(key who)
{
    llRequestPermissions(who, PERMISSION_TRIGGER_ANIMATION );
    key doNotStop=llGetInventoryKey(baseAnim);
    osAvatarPlayAnimation(who,baseAnim);
    list anToStop=llGetAnimationList(who);
    integer avacount=llGetListLength(anToStop);
    while (--avacount>=0) { if (llList2Key(anToStop,avacount)!=doNotStop) osAvatarStopAnimation(who,llList2Key(anToStop,avacount)); }
    osAvatarPlayAnimation(who,currentAn);
}


nextDance()
{
    animationsnr++;
   
    if (animationsnr>=llGetListLength(animatliste))
    {
        if (randomOrder) animatliste=[]+llListRandomize(animatliste,1);
        animationsnr=0;
    }
    string nextDance=llList2String(animatliste,animationsnr);
    taenzeranzahl=llGetListLength(dancerliste);  
    if (debug) llWhisper(0,"Starting " +(string)animationsnr +". Animation:  "  +nextDance+"  Stopping: "+currentAn );
    integer i;
    while (i<taenzeranzahl)
    {
        key who=llList2Key(dancerliste,i);
        llRequestPermissions(who, PERMISSION_TRIGGER_ANIMATION );
        osAvatarStopAnimation(who,currentAn);
        osAvatarPlayAnimation(who,nextDance);
        i+=2;
    }
    currentAn=nextDance;
    positionDancers();
    llSetTimerEvent(danceTimer);
}


showText(integer anzeige)
{
    if (anzeige)
    { 
        if (myState=="ERROR") { llSetText("ERROR - owner needs to fix and reset",textcolour,1.0);}
        else llSetText(textmessage,textcolour,1.0);
    }
    else llSetText("",textcolour,0.0);
}


buildAnimList()
{
    animatliste=[];
    integer l=llGetInventoryNumber(INVENTORY_ANIMATION);
    while (--l>=0) { if (llGetInventoryName(INVENTORY_ANIMATION,l)!=baseAnim) animatliste=[]+[llGetInventoryName(INVENTORY_ANIMATION,l)]+animatliste; }
    if (llGetListLength(animatliste)==0)
    {
        llWhisper(0,"ERROR! No dance animations in inventory!");
        myState="ERROR";
    }
    else if (llGetListLength(animatliste)>1)
    {
        if (randomOrder) animatliste=[]+llListRandomize(animatliste,1);
        else animatliste=[]+llListSort(animatliste,1,TRUE);
    }
    if(debug) llSay(0,"Animations Liste: "+ (string)animatliste);
    animationsnr=-1;
}


integer buildNPClist()
{
    npccardlist = [];
    npcnamelist = [];
    integer npczahl;
    integer notecardzahl = llGetInventoryNumber(INVENTORY_NOTECARD);
    while (notecardzahl)
    {
        notecardzahl--;                                              // Anzahl ist 1 höher als Adressierung.
        string notecardname = llGetInventoryName(INVENTORY_NOTECARD,notecardzahl);
        if (llSubStringIndex(notecardname,".NPC")==0)               // Testen ob sie eine .NPC Notecard ist
        {
            string npc_name = llGetSubString(notecardname,8,-1);     // npc_namen ermitteln
            if(debug) llSay(0,"NPC Namen: " + npc_name );
            npccardlist += notecardname;                            // Liste mit NPC enthaltenden Notecards erstellen
            npcnamelist += npc_name;                                // Liste mit NPC Namen erstellen
            npczahl++;
        }
    }
    if(debug) llSay(0,"NPC Zahl: "+ (string)npczahl +"  NPC Liste: "+(string) npcnamelist);
    return (npczahl);
}


npc_alive_check()                                                    // Check ob die vorher gerezzten NPCs noch leben
{
    integer npczahl = llGetListLength(npcList);
    list    restnpcList = [];
    integer laufvar;
    while (laufvar < npczahl)
    {
        key npctocheck = llList2Key(npcList,laufvar);                // Check ob NPC Key noch vorhanden- als Lebenszeichen
        if (osIsNpc(npctocheck)) { restnpcList += npctocheck;}        // Rest NPC Liste erstellen
        laufvar++;
    }
    npcList = [] + restnpcList;                                        // NPC Liste aktualisieren
    if(debug) llSay(0,"Lebendige NPCs: "+ (string)npczahl +" UUID Liste gerezzter NPCs: "+(string) npcList);
}


key npc_rezzer(key sittarget, vector npcPos, string npcnotecard, string npc_name)    // NPC im GRUPPENKONTEXT rezzen, damit der auf Sims geht die öffentlich gesperrt, aber für Gruppe frei ist
{
    key npctorez = osNpcCreate(npc_name, "" , npcPos, npcnotecard, 8 | OS_NPC_NOT_OWNED | OS_NPC_SENSE_AS_AGENT | OS_NPC_OBJECT_GROUP );
    llSleep(0.5);
    osNpcSit(npctorez,sittarget,OS_NPC_SIT_NOW);
    if (llGetInventoryKey(".bild " + npc_name)) osNpcSetProfileImage(npctorez,".bild "+ npc_name);                   // Testen ob Profilbild vorhanden - dann laden
    if (llGetInventoryKey(".info " + npc_name) != (string)NULL_KEY )
    {
        string info = (string) osGetNotecard(".info " + npc_name);
        osNpcSetProfileAbout(npctorez, info);  // Testen ob Profilinfo Notecard vorhanden - dann laden
    }
    if(debug) llSay(0," NPC gerezzt: " + npc_name + " mit UUID: " + (string)npctorez);
    llSleep(1.0);
    osAvatarPlayAnimation(npctorez,currentAn);
    return npctorez;
}


npc_rez_selector(list npccardlist, list npcnamelist )    // NPC auswählen der gerezzt werden soll
{
    key sittarget = llGetKey();
    vector npcPos = llGetPos();
    integer randomnpc = llFloor(llFrand(llGetListLength(npccardlist)));
    string  npcnotecard = llList2String(npccardlist,randomnpc);
    string  npc_name    = llList2String(npcnamelist,randomnpc);
    if(debug) llSay(0,"NPC Selector: NPC "+ (string) llGetListLength(npccardlist) + " per Zufall gewählt: " + (string)randomnpc + "  " + npc_name);
    key npc = npc_rezzer(sittarget, npcPos, npcnotecard, npc_name);
    npcList=[]+npcList+[npc];
}


default
{
    on_rez (integer foo) {llResetScript();}


    state_entry()
    {
        poserlinks = llGetNumberOfPrims();  // Primzahl leeren Posers ermittlen und speichern
        textmessage  = llGetObjectDesc();   // Prim Beschreibungsfeld auslesen
        llSetSitText(textmessage);
        llSitTarget(<0.0,0.0,0.0001>,ZERO_ROTATION);
        if (llGetInventoryType(baseAnim)!=INVENTORY_ANIMATION) {llWhisper(0,"ERROR! Unable to locate the base animation which MUST be in the ball."); myState="ERROR";}
        buildAnimList();
        integer npczahl = buildNPClist();
        if (debug) llSay(0, "Anzahl gefundenen NPCs: "+(string)npczahl );
        if (myState!="ERROR")
        {
            myState="READY";
            dancerliste=[];
            animationsnr=0;
            currentAn=llList2String(animatliste,animationsnr);
        }
        showText(textanzeige); // dient auch zum Löschen
    }

   
    changed (integer change)
    {
        if (change & CHANGED_REGION_START) llResetScript();
        if (change & CHANGED_OWNER) llResetScript();
        if (change & CHANGED_LINK)
        {
            list newDancerslist;
            integer aktlinkzahl=llGetNumberOfPrims();
            taenzeraufposer = aktlinkzahl - poserlinks;  // Tänzerzahl aus Primdifferenz ermitteln und speichern
            if(taenzeraufposer) {showText(FALSE);} else {if(textanzeige) showText(TRUE);}
            if(debug) llWhisper(0,"Aktuelle Tänzerzahl auf Poser: "+ (string)taenzeraufposer);
            npc_alive_check(); // Testen ob NPCs verschwunden sind, und NPC Liste bereinigen.
            while (aktlinkzahl>0)  // Taenzer abzählen und Liste erstellen
            {
                if (llGetAgentSize(llGetLinkKey(aktlinkzahl))!=ZERO_VECTOR) newDancerslist=[]+newDancerslist+[llGetLinkKey(aktlinkzahl),aktlinkzahl];
                aktlinkzahl--;
            }
            taenzeranzahl =llGetListLength(dancerliste);      
            integer laufvar1;
            while (laufvar1 < taenzeranzahl)
            {
                key who=llList2Key(dancerliste,laufvar1);
                integer ind=llListFindList(newDancerslist,[who]);
                if (ind>=0) // jemand sitzt neu
                {
                    dancerliste=[]+llListReplaceList(dancerliste,[llList2Integer(newDancerslist,ind+1)],laufvar1+1,laufvar1+1);
                    newDancerslist=[]+llDeleteSubList(newDancerslist,ind,ind+1);
                }
                else // jemand steht auf
                {
                    osAvatarPlayAnimation(who,"stand");
                    osAvatarStopAnimation(who,currentAn);
                    osAvatarStopAnimation(who,baseAnim);
                    dancerliste=[]+llDeleteSubList(dancerliste,laufvar1,laufvar1+1);
                }
                laufvar1+=2;
            }

            integer neutaenzer =llGetListLength(newDancerslist);  // Anzahl neuer Tänzer ermitteln- Achtung gibt doppelte Zahl aus.
            integer laufvar2 =0;

            while (laufvar2<neutaenzer)
            {
                startDancing(llList2Key(newDancerslist,laufvar2));
                laufvar2+=2;
            }

            dancerliste=[]+dancerliste+newDancerslist;
            if (llGetListLength(dancerliste)==0) { myState="READY"; llSetTimerEvent(0.0);}
            else if (myState=="READY") { myState="ON"; nextDance();}   // have to advance dance to keep them all synched and to trigger positioning
        }
    }


    touch_start(integer dummy)  // Menü Zugriff regeln
     {
        toucher = llDetectedKey(0);
        integer gruppe = llDetectedGroup(0);
        integer access;
        access = berechtigungscheck(toucher,gruppe);
        if(access) {showMenu(toucher); if(debug) llInstantMessage(toucher,"Menü Zugriff gewährt");}
     }


    listen(integer channel, string name, key who, string message)
    {
        llListenRemove(handle);
        if(debug) llSay(0,"Message enpfangen: " + message);
        closeHandle=FALSE;
   
        if (message=="ABBRUCH") return;

        else if (message == "NextDance") {if (myState=="ON") nextDance();}

        else if (message == "Add NPC")
        {
            if (llGetInventoryNumber(INVENTORY_NOTECARD)<1) { llInstantMessage(toucher,"I can't find any NPC notecards to use!");}
            else { npc_rez_selector(npccardlist, npcnamelist ); llSetTimerEvent(2.0); ;
            }
        }

        else if (message == "Kill NPC")
        {
            osNpcRemove(llList2Key(npcList,-1));
            npcList=[]+llDeleteSubList(npcList,-1,-1);
            showMenu(toucher);
        }

        else if (message == "Kill ALL")
        {
            while(llGetListLength(npcList))
            {
                osNpcRemove(llList2Key(npcList,-1));
                npcList=[]+llDeleteSubList(npcList,-1,-1);
            }
        }

        else if (llSubStringIndex(message," SEC")>=0)
        {
            danceTimer=(float)(llGetSubString(message,0,llSubStringIndex(message," MIN")-1));
            llInstantMessage(toucher,"Animations Dauer ist nun auf " + message + " gestellt");
            if (myState=="ON")
            {
                llSetTimerEvent(danceTimer);
                nextDance();
            }
            showMenu(toucher);
        }

        else if (message == "Debugmode") { debug = !debug; llInstantMessage(toucher,"Debugmodus [1 an / 0 aus] = "+(string)debug);}
    }
   
   
    timer()
    {
        if(closeHandle)
        {
            closeHandle++;
            if (closeHandle>2)
            {
                closeHandle=0;
                llInstantMessage(toucher, "Dialog has timed out...touch me again to re-active the dialog menu");
                llListenRemove(handle);
            }
        }
        if (myState=="ON") { nextDance(); }
        else llSetTimerEvent(0.0);
    }
}




ANWENDUNG:

- Rezze einen Würfel
- lege die Basisanimation "*****base_DO NOT DELETE ME!" hinein.
- füge die gewünschten Soll Animationen hinzu.
- (optional) dann die vorbereiteten NPC Karten (-Sets)
dann das Script
Änder die Primbeschreibung von "touch" auf "sit" damit man den Stuhl angezeigt bekommt wenn man mit dem Mauscursor über das Prim fährt.


Viel Spaß damit Tron
Zitieren
#2
UPDATE des Paramour Line Dancer Script für Opensim in OSSL Regionen mit NPC Unterstützung auf Version 3.8


Die Version 3.7 wählte die zu rezzenden NPCs per Zufall aus.
Das ist ok, wenn man nur wenige NPCs rezzen will, aber viele in den Poser eingestellt hat.

Aber es konnte vorkommen das NPCs doppelt gerezzt wurden, dafür Andere gar nicht.

Ich habe das Scirpt nun auf die Version 3.8 erweitert um die NPC Auswahl wahlweise sequentiell oder per Zufall zu erlauben.

Verantwortlich ist nun dafür die Zeile 18:

integer randomselectNPC = FALSE;                // rezzt NPCs sequentiell bis alle auf dem Poser stehen. Dann wird wieder beim Ersten angefangen

integer randomselectNPC = TRUE;                 // rezzt NPCs per Zufall, dies entspricht dem Verhalten der Version 3.7



Code:
// P604 PARAMOUR LINE-DANCE CONTROLLER OSSL v3.8 VSL by Tron 10.2023
// original OSSL Version by Mata Hari / Aine Caoimhe November 2014
// Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license.
// Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
// *** Dieses Script benötigt Regionen in denen OSSL Befehle freigeschalten sind, und der Scriptbesitzer diese Rechte hat***
//
// Unbedingt darauf achten das Prim dieses Scriptes im Kontext der erlaubten Simgruppe zu rezzen,
// ansonsten kommt es zu NPC Fehlern wenn der öffentliche Zugang abgeschalten wird!
//
// Diverse Änderungen: NPCs rezzen nnun Fehlerfrei auf gesperrten Sims wenn sie die erlaubte Gruppe haben
// Änderungen by Tron: Script für Funktionsfähigkeit in Sl und Opensim umgeschrieben.
// Hinzufügung eines umfangreichen Berechtigungs Systems.
// Änderung Version 3.8 NPC AUswahl nun schaltbar ob sequentiell oder random
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// USER SETTINGS - stuff that any user should feel comfortable setting
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
//
integer randomselectNPC = FALSE;                // Sollen NPCs per Zufall [TRUE] oder sequentiell [FALSE] gewählt werden
integer textanzeige =TRUE;                      // TRUE = show floaty text, FALSE = hide it
string  textmessage;                            // name to appear in floaty text above the line dance object. Read from Prim description
vector  textcolour =<1.000, 0.847, 0.200>;      // colour to use for the floaty text
//
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// ADVANCED USER SETTINGS - a more advanced user might want to adjust these
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
integer  debug = FALSE;
integer  maxLineLength=5;                        // maximum number of dancers allowed in a single line
float    spacingX=1.5;                           // distance between each dancer in the line (side to side)
float    spacingY=-1.5;                          // when more than 1 line is needed, how far apart to space lines
vector   p1Pos=<0.0, 0.0, 1.25>;                 // sit target position for 1st dancer - all other dancers positioned relative to this position (and centered on it) <0.0, 0.0, -2.0>;
rotation p1Rot=ZERO_ROTATION;                    // sit target rotation for 1st dancer - making this non-zero could produce unusual results!
integer  randomOrder=FALSE;                      // TRUE = play dances in random order, FALSE = play in alphabetical order
float    danceTimer=60.0;                        // how long (in seconds) to play each line dance by default before moving on to the next one (owner can also change this via dialog)
//
//
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// DON'T CHANGE ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU'RE DOING!!!!!
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
string  baseAnim="~~~~~base_DO_NOT_DELETE_ME!!!!!";
list    animatliste;
integer animationsnr;
string  currentAn;
list    dancerliste;              // Liste von Avataren UND NPCs auf dem Poser
string  myState="OFF";
integer myChannel;
integer handle;
integer closeHandle;
list    npcList;
integer npcToRez;
integer taenzeranzahl;            // Integer! Anzahl der aktuellen Tänzer

integer poserlinks;
integer taenzeraufposer;

key     toucher;               // UUID des Operator des Menüs

list    zugangsliste = ["2338a1d0-0d7b-4ec1-b841-029c49c9d461","8998586e-b754-4032-856f-f4f4b122eb6e"]; // UUIDS berechtigter AVAs
integer groupaccess  = TRUE;   // TRUE ore FALSE - darf die Gruppe steuern?
integer publicaccess = FALSE;  // TRUE ore FALSE - darf jeder steuern?

list    npccardlist;
list    npcnamelist;
integer lastrezzednpc = -1;
integer npcanzahl;

integer berechtigungscheck(string clicker, integer selbegruppe)  // Zugangsberechtigung auswerten
     {
        integer zugang = FALSE;
        integer personalzugang = llGetListLength(zugangsliste);
        if (publicaccess)  { zugang = TRUE;}                     // Öffentlicher Zugang
        else if (selbegruppe && groupaccess) { zugang = TRUE;}   // Gruppen Zugang
        else if (clicker ==  llGetOwner()) { zugang = TRUE;}     // Besitzer Zugang immer erlaubt
        else if (personalzugang)                                 // Persönlicher Zugang über UUID Liste
        {
            string personal;
            do
            {  
                personal = llList2String(zugangsliste, personalzugang -1 );
                if( clicker ==  personal) { zugang = TRUE; }
                personalzugang--;
            }       
            while ( (integer)personal && (!zugang));
        }
        return zugang;
     }


showMenu(key operator)
{
    myChannel=0x80000000 | (integer)("0x"+(string)operator);
    string txtDia="Danceliner Optionen Menü: ADD/Kill NPCs, Animationswechel, Ändern der Tanzdauer";
    list butDia=["ABBRUCH","NextDance","Add NPC","Kill NPC","Kill ALL","60 SEC","90 SEC","120 SEC","300 SEC","Debugmode"];
    handle=llListen(myChannel,"",operator,"");
    llDialog(operator,txtDia,butDia,myChannel);
    closeHandle=TRUE;
}


positionDancers()
{
    dancerliste=[];
    integer link=llGetNumberOfPrims();
    while (link>0)
    {
        if (llGetAgentSize(llGetLinkKey(link))!=ZERO_VECTOR) dancerliste=[]+[llGetLinkKey(link),link]+dancerliste;
        link--;
    }
    integer laufvar5;
    taenzeranzahl=llGetListLength(dancerliste);
    integer row;
    integer pos;
    integer thisLineCount;
    while (laufvar5 < taenzeranzahl)
    {
        vector thisPos=p1Pos;
        rotation thisRot=p1Rot;
        thisPos.y+=(pos*spacingX);
        thisPos.x+=(row*spacingY);
        llSetLinkPrimitiveParamsFast(llList2Integer(dancerliste,laufvar5+1),[PRIM_POS_LOCAL,thisPos,PRIM_ROT_LOCAL,thisRot]);
        thisLineCount++;
        if(thisLineCount>=maxLineLength)
        {
            thisLineCount=0;
            pos=0;
            if (row==0) row++;
            else if (row>0) row=row*-1;
            else row=(row*-1)+1;
        }
        else
        {
            if (pos==0) pos++;
            else if (pos>0) pos=pos*-1;
            else pos=(pos*-1)+1;
        }
        laufvar5 +=2;
    }
}


startDancing(key who)
{
    llRequestPermissions(who, PERMISSION_TRIGGER_ANIMATION );
    key doNotStop=llGetInventoryKey(baseAnim);
    osAvatarPlayAnimation(who,baseAnim);
    list anToStop=llGetAnimationList(who);
    integer avacount=llGetListLength(anToStop);
    while (--avacount>=0) { if (llList2Key(anToStop,avacount)!=doNotStop) osAvatarStopAnimation(who,llList2Key(anToStop,avacount)); }
    osAvatarPlayAnimation(who,currentAn);
}


nextDance()
{
    animationsnr++;
   
    if (animationsnr>=llGetListLength(animatliste))
    {
        if (randomOrder) animatliste=[]+llListRandomize(animatliste,1);
        animationsnr=0;
    }
    string nextDance=llList2String(animatliste,animationsnr);
    taenzeranzahl=llGetListLength(dancerliste);  
    if (debug) llWhisper(0,"Starting " +(string)animationsnr +". Animation:  "  +nextDance+"  Stopping: "+currentAn );
    integer i;
    while (i<taenzeranzahl)
    {
        key who=llList2Key(dancerliste,i);
        llRequestPermissions(who, PERMISSION_TRIGGER_ANIMATION );
        osAvatarStopAnimation(who,currentAn);
        osAvatarPlayAnimation(who,nextDance);
        i+=2;
    }
    currentAn=nextDance;
    positionDancers();
    llSetTimerEvent(danceTimer);
}


showText(integer anzeige)
{
    if (anzeige)
    { 
        if (myState=="ERROR") { llSetText("ERROR - owner needs to fix and reset",textcolour,1.0);}
        else llSetText(textmessage,textcolour,1.0);
    }
    else llSetText("",textcolour,0.0);
}


buildAnimList()
{
    animatliste=[];
    integer l=llGetInventoryNumber(INVENTORY_ANIMATION);
    while (--l>=0) { if (llGetInventoryName(INVENTORY_ANIMATION,l)!=baseAnim) animatliste=[]+[llGetInventoryName(INVENTORY_ANIMATION,l)]+animatliste; }
    if (llGetListLength(animatliste)==0)
    {
        llWhisper(0,"ERROR! No dance animations in inventory!");
        myState="ERROR";
    }
    else if (llGetListLength(animatliste)>1)
    {
        if (randomOrder) animatliste=[]+llListRandomize(animatliste,1);
        else animatliste=[]+llListSort(animatliste,1,TRUE);
    }
    if(debug) llSay(0,"Animations Liste: "+ (string)animatliste);
    animationsnr=-1;
}


integer buildNPClist()
{
    npccardlist = [];
    npcnamelist = [];
    integer npczahl;
    integer notecardzahl = llGetInventoryNumber(INVENTORY_NOTECARD);
    while (notecardzahl)
    {
        notecardzahl--;                                              // Anzahl ist 1 höher als Adressierung.
        string notecardname = llGetInventoryName(INVENTORY_NOTECARD,notecardzahl);
        if (llSubStringIndex(notecardname,".NPC")==0)               // Testen ob sie eine .NPC Notecard ist
        {
            string npc_name = llGetSubString(notecardname,8,-1);     // npc_namen ermitteln
            if(debug) llSay(0,"NPC Namen: " + npc_name );
            npccardlist += notecardname;                            // Liste mit NPC enthaltenden Notecards erstellen
            npcnamelist += npc_name;                                // Liste mit NPC Namen erstellen
            npczahl++;
        }
    }
    if(debug) llSay(0,"NPC Zahl: "+ (string)npczahl +"  NPC Liste: "+(string) npcnamelist);
    return (npczahl);
}


npc_alive_check()                                                    // Check ob die vorher gerezzten NPCs noch leben
{
    integer npczahl = llGetListLength(npcList);
    list    restnpcList = [];
    integer laufvar;
    while (laufvar < npczahl)
    {
        key npctocheck = llList2Key(npcList,laufvar);                // Check ob NPC Key noch vorhanden- als Lebenszeichen
        if (osIsNpc(npctocheck)) { restnpcList += npctocheck;}        // Rest NPC Liste erstellen
        laufvar++;
    }
    npcList = [] + restnpcList;                                        // NPC Liste aktualisieren
    if(debug) llSay(0,"Lebendige NPCs: "+ (string)npczahl +" UUID Liste gerezzter NPCs: "+(string) npcList);
}


key npc_rezzer(key sittarget, vector npcPos, string npcnotecard, string npc_name)    // NPC im GRUPPENKONTEXT rezzen, damit der auf Sims geht die öffentlich gesperrt, aber für Gruppe frei ist
{
    key npctorez = osNpcCreate(npc_name, "" , npcPos, npcnotecard, 8 | OS_NPC_NOT_OWNED | OS_NPC_SENSE_AS_AGENT | OS_NPC_OBJECT_GROUP );
    llSleep(0.5);
    osNpcSit(npctorez,sittarget,OS_NPC_SIT_NOW);
    if (llGetInventoryKey(".bild " + npc_name)) osNpcSetProfileImage(npctorez,".bild "+ npc_name);                   // Testen ob Profilbild vorhanden - dann laden
    if (llGetInventoryKey(".info " + npc_name) != (string)NULL_KEY )
    {
        string info = (string) osGetNotecard(".info " + npc_name);
        osNpcSetProfileAbout(npctorez, info);  // Testen ob Profilinfo Notecard vorhanden - dann laden
    }
    if(debug) llSay(0," NPC gerezzt: " + npc_name + " mit UUID: " + (string)npctorez);
    llSleep(1.0);
    osAvatarPlayAnimation(npctorez,currentAn);
    return npctorez;
}


npc_rez_selector(list npccardlist, list npcnamelist )    // NPC auswählen der gerezzt werden soll
{
    key sittarget = llGetKey();
    vector npcPos = llGetPos();
    integer randomnpc;
   
    if(randomselectNPC)
    {
        randomnpc = llFloor(llFrand(llGetListLength(npccardlist)));
    }
    else
    {
        lastrezzednpc++;
        if (lastrezzednpc >= npcanzahl) lastrezzednpc = 0;
        randomnpc = lastrezzednpc;
    }
    if(debug) llSay(0,"gewählter NPC:"+(string)randomnpc);

    string  npcnotecard = llList2String(npccardlist,randomnpc);
    string  npc_name    = llList2String(npcnamelist,randomnpc);
    if(debug) llSay(0,"NPC Selector: NPC "+ (string) llGetListLength(npccardlist) + " per Zufall gewählt: " + (string)randomnpc + "  " + npc_name);
    key npc = npc_rezzer(sittarget, npcPos, npcnotecard, npc_name);
    npcList=[]+npcList+[npc];
}


default
{
    on_rez (integer foo) {llResetScript();}


    state_entry()
    {
        poserlinks = llGetNumberOfPrims();  // Primzahl leeren Posers ermittlen und speichern
        textmessage  = llGetObjectDesc();   // Prim Beschreibungsfeld auslesen
        llSetSitText(textmessage);
        llSitTarget(<0.0,0.0,0.0001>,ZERO_ROTATION);
        if (llGetInventoryType(baseAnim)!=INVENTORY_ANIMATION) {llWhisper(0,"ERROR! Unable to locate the base animation which MUST be in the ball."); myState="ERROR";}
        buildAnimList();
        npcanzahl = buildNPClist();
        if (debug) llSay(0, "Anzahl gefundenen NPCs: "+(string)npcanzahl );
        if (myState!="ERROR")
        {
            myState="READY";
            dancerliste=[];
            animationsnr=0;
            currentAn=llList2String(animatliste,animationsnr);
        }
        showText(textanzeige); // dient auch zum Löschen
    }

   
    changed (integer change)
    {
        if (change & CHANGED_REGION_START) llResetScript();
        if (change & CHANGED_OWNER) llResetScript();
        if (change & CHANGED_LINK)
        {
            list newDancerslist;
            integer aktlinkzahl=llGetNumberOfPrims();
            taenzeraufposer = aktlinkzahl - poserlinks;  // Tänzerzahl aus Primdifferenz ermitteln und speichern
            if(taenzeraufposer) {showText(FALSE);} else {showText(textanzeige);}
            if(debug) llWhisper(0,"Aktuelle Tänzerzahl auf Poser: "+ (string)taenzeraufposer);
            npc_alive_check(); // Testen ob NPCs verschwunden sind, und NPC Liste bereinigen.
            while (aktlinkzahl>0)  // Taenzer abzählen und Liste erstellen
            {
                if (llGetAgentSize(llGetLinkKey(aktlinkzahl))!=ZERO_VECTOR) newDancerslist=[]+newDancerslist+[llGetLinkKey(aktlinkzahl),aktlinkzahl];
                aktlinkzahl--;
            }
            taenzeranzahl =llGetListLength(dancerliste);      
            integer laufvar1;
            while (laufvar1 < taenzeranzahl)
            {
                key who=llList2Key(dancerliste,laufvar1);
                integer ind=llListFindList(newDancerslist,[who]);
                if (ind>=0) // jemand sitzt neu
                {
                    dancerliste=[]+llListReplaceList(dancerliste,[llList2Integer(newDancerslist,ind+1)],laufvar1+1,laufvar1+1);
                    newDancerslist=[]+llDeleteSubList(newDancerslist,ind,ind+1);
                }
                else // jemand steht auf
                {
                    osAvatarPlayAnimation(who,"stand");
                    osAvatarStopAnimation(who,currentAn);
                    osAvatarStopAnimation(who,baseAnim);
                    dancerliste=[]+llDeleteSubList(dancerliste,laufvar1,laufvar1+1);
                }
                laufvar1+=2;
            }

            integer neutaenzer =llGetListLength(newDancerslist);  // Anzahl neuer Tänzer ermitteln- Achtung gibt doppelte Zahl aus.
            integer laufvar2 =0;
            while (laufvar2<neutaenzer)
            {
                startDancing(llList2Key(newDancerslist,laufvar2));
                laufvar2+=2;
            }

            dancerliste=[]+dancerliste+newDancerslist;
            if (llGetListLength(dancerliste)==0) { myState="READY"; llSetTimerEvent(0.0);}
            else if (myState=="READY") { myState="ON";}   // have to advance dance to keep them all synched and to trigger positioning
            nextDance();
        }
    }


    touch_start(integer dummy)  // Menü Zugriff regeln
     {
        toucher = llDetectedKey(0);
        integer gruppe = llDetectedGroup(0);
        integer access;
        access = berechtigungscheck(toucher,gruppe);
        if(access) {showMenu(toucher); if(debug) llInstantMessage(toucher,"Menü Zugriff gewährt");}
     }


    listen(integer channel, string name, key who, string message)
    {
        llListenRemove(handle);
        if(debug) llSay(0,"Message enpfangen: " + message);
        closeHandle=FALSE;
   
        if (message=="ABBRUCH") return;

        else if (message == "NextDance") {if (myState=="ON") nextDance();}

        else if (message == "Add NPC")
        {
            if (llGetInventoryNumber(INVENTORY_NOTECARD)<1) { llInstantMessage(toucher,"I can't find any NPC notecards to use!");}
            else { npc_rez_selector(npccardlist, npcnamelist ); llSetTimerEvent(2.0); ;
            }
        }

        else if (message == "Kill NPC")
        {
            osNpcRemove(llList2Key(npcList,-1));
            npcList=[]+llDeleteSubList(npcList,-1,-1);
            showMenu(toucher);
        }

        else if (message == "Kill ALL")
        {
            while(llGetListLength(npcList))
            {
                osNpcRemove(llList2Key(npcList,-1));
                npcList=[]+llDeleteSubList(npcList,-1,-1);
            }
        }

        else if (llSubStringIndex(message," SEC")>=0)
        {
            danceTimer=(float)(llGetSubString(message,0,llSubStringIndex(message," MIN")-1));
            llInstantMessage(toucher,"Animations Dauer ist nun auf " + message + " gestellt");
            if (myState=="ON")
            {
                llSetTimerEvent(danceTimer);
                nextDance();
            }
            showMenu(toucher);
        }

        else if (message == "Debugmode") { debug = !debug; llInstantMessage(toucher,"Debugmodus [1 an / 0 aus] = "+(string)debug);}
    }
   
   
    timer()
    {
        if(closeHandle)
        {
            closeHandle++;
            if (closeHandle>2)
            {
                closeHandle=0;
                llInstantMessage(toucher, "Dialog has timed out...touch me again to re-active the dialog menu");
                llListenRemove(handle);
            }
        }
        if (myState=="ON") { nextDance(); }
        else llSetTimerEvent(0.0);
    }
}


Weiterhin viel Spaß


Tron
Zitieren
#3
Hallo Tron,

Parmour ist ja mittlerweile in die Jahre gekommen... Habe mich mal daran versucht was eigenes zu schreiben.. Klappt sogar zu 99%
Smile

Kann ich Dir bei Interesse mal zeigen.
Zitieren
#4
Hallo Dark,


klar ist das Script schon etwas älter, aber ich sehe die gleiche Funktionalität noch in fast jeder Partylocation als Linedancer.
Die Nutzung ist also noch da, ebenso wie der Paramour Danceball noch nahezu überall präsent ist.
Und beide bieten ja die Möglichkeit NPCs einzusetzten.
Und der Danceliner läuft absolut stabil selbst bei voller Sim. Klar sieht man dann irgendwann die Avas nicht mehr Synchron, aber es gibt keine Simprobleme.

Insoweit sage ich mal: Alt, aber noch Aktuell - daher sollten wir die Dinge weiter Pflegen. Hinzu kommt das ich eine Variante für SL und OSSL Freie Sims veröffentlicht habe.

Natürlich sollten man neue Ideen und Lösungen entwickeln und veröffentlichen.

Ich selber nutze in unseren Partybereichen zusätzlich für Paar- und Gruppentanz noch Dancepads auf Basis des PMAC2.8.
Damit kann sehr schöne Duo Tanzposer machen, oder auch wie ich es in der TikiWini gemacht habe: Hexa Danceposer für 3 Paare die in 120° Formation gemeinsam tanzen. 

Wichtig ist die Stärken und Schwächen der verschiedenen Lösungen zu kennen und in das Gesamtkonzept einzubauen.
Ich werde das mal getrennt posten.

Daher veröffentliche auch gerne deine Lösung, denn es gibt viele Leute hier die von Scripten keine Ahnung haben.
Und je mehr Mittel wir haben, umso mehr Gestaltungsspielraum ergibt sich.
Zitieren


Gehe zu:


Benutzer, die gerade dieses Thema anschauen: 1 Gast/Gäste