Jump to content

dataserver event never fires (from llRequestInventoryData)


May Suki
 Share

You are about to reply to a thread that has been inactive for 110 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

Everything I'm seeing strongly suggests that using llRequestInventoryData() within a link_message event fails.

At this point, I've stripped everything in the link_message all the way down to just running the doTeleport function and waiting for a dataserver response - which never comes.

Seems I'll have to shift to a listener or something.

Link to comment
Share on other sites

Posted (edited)

I converted the code to respond to the link_message event by sending a listener message with the name of the Landmark on it.

This changed nothing. The function appears to work, and then fails quietly as it times out.

This is so frustrating!

This should be simple - even when I strip it down to near nothing, it fails. I'm at full-on desperation here.

This should not be this hard!

Sigh. Maybe I should just give up and throw the entire lot of this code out the window. What a waste of time.

Edited by May Suki
Link to comment
Share on other sites

Posted (edited)
//========================================
// Teleport.lsl
//========================================

// This script now exists solely to give llRequestInventoryData() a place to run.
// It only works for "Home" and not for arbitrary landmarks.

#define RUNNING TRUE
#define NOT_RUNNING FALSE
#define cdRunScript(a) llSetScriptState(a, RUNNING)
#define cdStopScript(a) llSetScriptState(a, NOT_RUNNING)

string tpLandmark = "Home";
key tpLandmarkQueryID;

//========================================
// STATES
//========================================
default {

    //----------------------------------------
    // STATE ENTRY
    //----------------------------------------
    state_entry() {
        tpLandmarkQueryID = llRequestInventoryData(tpLandmark);
    }

    //----------------------------------------
    // TIMER
    //----------------------------------------

    // Timer is used solely to follow the carrier

    timer() {

        if (tpLandmarkQueryID) {
            llSay(DEBUG_CHANNEL,"TP failed to occur; notify developer.");
            llSetTimerEvent(0.0);
            llSetScriptState("Teleport", NOT_RUNNING);
        }
    }

    //----------------------------------------
    // DATASERVER
    //----------------------------------------
    dataserver(key queryID, string queryData) {

#define getRegionLocation(d) (llGetRegionCorner() + ((vector)d))
#define locationToString(d) ((string)((integer)d.x) + "/" + (string)((integer)d.y) + "/" + (string)((integer)d.z))

        if (queryID == tpLandmarkQueryID) {

            string locationData = queryData;
            vector globalLocation = getRegionLocation(locationData);
            string globalPosition = locationToString(globalLocation);

            llOwnerSay("Dolly is now teleporting.");

            // Perform TP
            llOwnerSay("@unsit=y,tploc=y,tpto:" + globalPosition + "=force");

            llSetTimerEvent(0.0);
            tpLandmarkQueryID = NULL_KEY;
            llSetScriptState("Teleport", NOT_RUNNING);
        }
    }
}

//========== TELEPORT ==========

I tried a last ditch effort - created a script that was completely and hermetically sealed from the other scripts, and who would activate a fixed teleport on startup.

This script works fine alone in a test object, and fine in an unscripted Doll Key of the sort I'm using.

It fails with all of the other scripts present and active... Just outrageous. It has no dependencies on hidden macros, nor any inputs from the other keys, nor does it perform anything else other than the teleport on startup - then it stops running itself.

I'm going to sleep on this.

Edited by May Suki
Added code.
Link to comment
Share on other sites

2 hours ago, May Suki said:

//========================================
// Teleport.lsl
//========================================

// This script now exists solely to give llRequestInventoryData() a place to run.
// It only works for "Home" and not for arbitrary landmarks.

#define RUNNING TRUE
#define NOT_RUNNING FALSE
#define cdRunScript(a) llSetScriptState(a, RUNNING)
#define cdStopScript(a) llSetScriptState(a, NOT_RUNNING)

string tpLandmark = "Home";
key tpLandmarkQueryID;

//========================================
// STATES
//========================================
default {

    //----------------------------------------
    // STATE ENTRY
    //----------------------------------------
    state_entry() {
        tpLandmarkQueryID = llRequestInventoryData(tpLandmark);
    }

    //----------------------------------------
    // TIMER
    //----------------------------------------

    // Timer is used solely to follow the carrier

    timer() {

        if (tpLandmarkQueryID) {
            llSay(DEBUG_CHANNEL,"TP failed to occur; notify developer.");
            llSetTimerEvent(0.0);
            llSetScriptState("Teleport", NOT_RUNNING);
        }
    }

    //----------------------------------------
    // DATASERVER
    //----------------------------------------
    dataserver(key queryID, string queryData) {

#define getRegionLocation(d) (llGetRegionCorner() + ((vector)d))
#define locationToString(d) ((string)((integer)d.x) + "/" + (string)((integer)d.y) + "/" + (string)((integer)d.z))

        if (queryID == tpLandmarkQueryID) {

            string locationData = queryData;
            vector globalLocation = getRegionLocation(locationData);
            string globalPosition = locationToString(globalLocation);

            llOwnerSay("Dolly is now teleporting.");

            // Perform TP
            llOwnerSay("@unsit=y,tploc=y,tpto:" + globalPosition + "=force");

            llSetTimerEvent(0.0);
            tpLandmarkQueryID = NULL_KEY;
            llSetScriptState("Teleport", NOT_RUNNING);
        }
    }
}

//========== TELEPORT ==========

I tried a last ditch effort - created a script that was completely and hermetically sealed from the other scripts, and who would activate a fixed teleport on startup.

This script works fine alone in a test object, and fine in an unscripted Doll Key of the sort I'm using.

It fails with all of the other scripts present and active... Just outrageous. It has no dependencies on hidden macros, nor any inputs from the other keys, nor does it perform anything else other than the teleport on startup - then it stops running itself.

I'm going to sleep on this.

This script certainly works, and I think should work with your script.   If it does work, then you know the problem is something to do with the Firestorm preprocessor.

key kRequest;

string strLandmark;
string strTrigger = "Home";

default{

	state_entry(){
		strLandmark = llGetInventoryName(INVENTORY_LANDMARK,0);
	}

	changed(integer change){
		if(change & CHANGED_INVENTORY){
			strLandmark = llGetInventoryName(INVENTORY_LANDMARK,0);
		}
	}

	link_message(integer sender, integer number, string str, key id){

		if(~llSubStringIndex(str,strTrigger) && llGetInventoryType(strLandmark) == INVENTORY_LANDMARK){
			llResetTime();
			kRequest = llRequestInventoryData(strLandmark);
		}
		else if (llGetInventoryType(strLandmark)!=INVENTORY_LANDMARK){
			llOwnerSay("no landmark");
		}

	}

	dataserver(key id, string data){
		if(kRequest == id ){
			llOwnerSay("retrieved "+data+" after "+(string)llGetTime()+" seconds");

		}
	}
}

 

Link to comment
Share on other sites

20 hours ago, May Suki said:

I actually took the Firestorm post-processed (LSL-only) code, cleaned it up, and tried that. It too failed - but at least now the code is LSL-only and self-contained - *and* isolated. Here it is (hope this posting isn't too much):

So I did a little test with this code, the only changes I made to your post-processed source code was fix the indenting, renamed the jumps to sensible names (switchA1/switchA2/break1, etc.) and added three OwnerSays (that start with "wulfie") just to see that code executes correctly.

Quote




//========================================
// Teleport.lsl (#2)
//========================================
//
// vim:sw=4 et nowrap:

//========================================
// VARIABLES
//========================================

key keyID                   = NULL_KEY;
list split                  = [];
string dollName;
string script;
string myName;
integer code;
integer optHeader;

integer remoteSeq;
integer rlvOk               = -1;

integer debugLevel = 8;
integer mySeqNum;

key queryLandmarkData = NULL_KEY;
key tpLandmarkQueryID = NULL_KEY;
string tpLandmark;

//========================================
// FUNCTIONS
//========================================

rlvTeleport(string locationData) {

    vector globalLocation = (llGetRegionCorner() + ((vector)locationData));
    string globalPosition = ((string)((integer)globalLocation.x) + "/" + (string)((integer)globalLocation.y) + "/" + (string)((integer)globalLocation.z));

    if (debugLevel >= 6) llOwnerSay(     "DEBUG-LANDMARK"+"("+((string)6)+"):"+((string)71)+": "+("Dolly should be teleporting now..."));
    if (debugLevel >= 6) llOwnerSay(     "DEBUG-LANDMARK"+"("+((string)6)+"):"+((string)72)+": "+("Position = " + globalPosition));

    llOwnerSay("Dolly is now teleporting.");



    llOwnerSay("@unsit=y,tploc=y,tpto:" + globalPosition + "=force");
}

memReport(string script, float delay) {
    if (delay != 0.0) llSleep(delay);

    integer usedMemory = llGetUsedMemory();
    integer memoryLimit = llGetMemoryLimit();
    integer freeMemory = memoryLimit - usedMemory;
    integer availMemory = freeMemory + (65536 - memoryLimit);

    llMessageLinked((LINK_THIS), (((++mySeqNum) << 16) | (0 << 10) | 136), myName + "|" +  (string)usedMemory + "|" + (string)memoryLimit + "|" + (string)freeMemory + "|" + (string)availMemory, llGetKey());

}

key doTeleport(string tpLandmark) {

    if (!(llGetInventoryType(tpLandmark) == INVENTORY_LANDMARK)) {
        if (debugLevel >= 6) llOwnerSay(     "DEBUG-LANDMARK"+"("+((string)6)+"):"+((string)90)+": "+("No landmark by the name of \"" + tpLandmark + "\" is present in inventory."));
        return NULL_KEY;
    }

    tpLandmarkQueryID = llRequestInventoryData(tpLandmark);

    if (tpLandmarkQueryID == NULL_KEY) {
        llSay(0x7FFFFFFF,"Landmark data request failed.");
        return NULL_KEY;
    }

    if (debugLevel >= 6) llOwnerSay(     "DEBUG-LANDMARK"+"("+((string)6)+"):"+((string)103)+": "+("queryLandmarkData set to " + (string)tpLandmarkQueryID));
    if (debugLevel >= 6) llOwnerSay(     "DEBUG-LANDMARK"+"("+((string)6)+"):"+((string)104)+": "+("Teleporting dolly " + dollName + " to  inventory tpLandmark \"" + tpLandmark + "\"."));
    return tpLandmarkQueryID;
}

//========================================
// STATES
//========================================
default {

//========================================
// STATE_ENTRY
//========================================
    state_entry() {
        rlvOk = 1;
        myName = llGetScriptName();
        keyID = llGetKey();

        mySeqNum = llRound(llFrand(1<<15));
    }

//========================================
// ON_REZ
//========================================
    on_rez(integer start) {
        mySeqNum = llRound(llFrand(1<<15));
    }

//========================================
// LINK MESSAGE
//========================================
    link_message(integer lmSource, integer lmInteger, string lmData, key lmID) {

        llOwnerSay((string)["wulfie link_message"]);

        split     = llParseStringKeepNulls((lmData), [ "|" ], []);
        script    = llList2String(split,0);
        remoteSeq = (lmInteger & 0xFFFF0000) >> 16;
        optHeader = (lmInteger & 0x00000C00) >> 10;
        code      =  lmInteger & 0x000003FF;
        split     = llDeleteSubList(split, 0, 0 + optHeader);

        if (code == 300) {
            string name = llList2String(split,0);
            string value = llList2String(split,1);
            integer integerValue = (integer)value;

            {
                if((name) == ("debugLevel"))jump switchA2;
                if((name) == ("rlvOk"))jump switchA1;
                jump break1;

                @switchA1;
                {
                    rlvOk = integerValue;
                    jump break1;
                }

                @switchA2;
                {
                    debugLevel = integerValue;
                    jump break1;
                }

                @break1;
            }
            return;
        }
        else if (code == 305) {

            string cmd = llList2String(split,0);

            llOwnerSay((string)["wulfie 305, cmd:", cmd]);
            {
                if((cmd) == ("instantMessage"))jump switchB1;
                if((cmd) == ("teleport"))jump switchB2;
                jump break2;

                @switchB1;
                {
                    llInstantMessage(lmID,llList2String(split,0));
                    jump break2;
                }

                @switchB2;
                {
                    tpLandmark = llList2String(split,1);

                    llOwnerSay((string)["wulfie command ", cmd, ", landmark ", tpLandmark]);

                    queryLandmarkData = doTeleport(tpLandmark);
                    if (debugLevel >= 6) llOwnerSay(     "DEBUG-LANDMARK"+"("+((string)6)+"):"+((string)181)+": "+("queryLandmarkData now equals " + (string)queryLandmarkData));
                    llSetTimerEvent(20.0);
                    jump break2;
                }

                @break2;
            }
        }
        else if (code == 350) {
            rlvOk = llList2Integer(split,0);
        }
        else if (code < 200) {
            if (code == 142) {

                llOwnerSay("Teleport" + ": Compiled  by " + "May Suki" + " on " +  "Jun 28 2021" + " at " + "12:08:59" + " Options: " + llList2CSV((["Adult"] + ["Mode=Developer"] + ["RollOver"] + [] + [] + ["PreserveDirectory"] + ["DefaultWind=" + (string)llFloor(1800.0 / 60.0)] )));
            }
            else if (code == 135) {
                float delay = llList2Float(split,0);

                memReport(myName,delay);
            }
        }
    }

//========================================
// TIMER
//========================================
    timer() {

        if (queryLandmarkData) {
            llSay(0x7FFFFFFF,"TP failed to occur; notify developer.");
            llSetTimerEvent(0.0);
        }
    }

//========================================
// DATASERVER
//========================================
    dataserver(key queryID, string queryData) {

        if (debugLevel >= 6) llOwnerSay(     "DEBUG-LANDMARK"+"("+((string)6)+"):"+((string)224)+": "+("queryLandmarkData is equal to " + (string)queryLandmarkData));
        if (debugLevel >= 6) llOwnerSay(     "DEBUG-LANDMARK"+"("+((string)6)+"):"+((string)225)+": "+("queryID is equal to " + (string)queryID));

        if (queryID == queryLandmarkData) {
            rlvTeleport(queryData);
            llSetTimerEvent(0.0);
            queryLandmarkData = NULL_KEY;
        }
    }
}

//========== TELEPORT2 ==========

 

Then I sent this link message from another script in the same prim:

llMessageLinked(LINK_THIS, 305, "|teleport|home", "");

305 stands for "internal command" and the first "|" is removed by llParseStringKeepNulls, which is required because the script expects (and calls llDeleteSubList on) a "header" before the command.

This doesn't fail. From this I can conclude that the problem is not the LSL preprocessor (since this is the code it produces), nor the logic of the script itself.

The problem has to be in the other script that tries to send the link message. Maybe something was changed (or forgotten) about the parsing process. I would start looking there next, because the problem isn't in this code for a fact.

Edited by Wulfie Reanimator
  • Like 1
Link to comment
Share on other sites

Posted (edited)

Thank you all so much!

So far, with the testing, it doesn't seem to be anything actually connected with the code. Given my test, and Wulfie's test, the script doesn't seem to be the problem.

I did something interesting... I wondered if it was going to come to this.

In the past, I tested scripts in an unscripted key that came as part of the original set of Key stuff. (I didn't make the object the scripts are in.) That worked.

This time, I copied the Key I have been working with - my every day fully scripted Development key - and stripped out everything but the standalone Teleport script and the Home landmark.

This did not work.

I don't know how, but it appears to be embedded into the object itself.

To be sure, I tested it with my "testObject" (a sphere I had created earlier to test this script). The sphere only contains the script and landmark. Surprisingly, it failed here.

I created a new (cube) object and put the landmark and script into that. That worked.

It seems to be something to do with the object itself.

This doesn't surprise me that much - a while ago I had a Key object that would not turn, no matter what I did - stymied me such that I quit developing the Key for 6 years. When I came back to it, I "gave up" trying to make the working Key object turn and just used an old copy of that same Key that did spin - and all the scripts worked perfectly, including when the Key spin was stopped and started programmatically.

 

Edited by May Suki
Link to comment
Share on other sites

Posted (edited)

I turned off RLV just in case... and strangely, the results were the same:

  • The key failed.
  • The older test object failed.
  • The brand new cube succeeded.

This is determined by code in the dataserver event that directly uses llOwnerSay() to make message to the user, as well as to use RLV to perform the teleport:

[09:01:52] Object: Dolly is now teleporting.
[09:01:52] Object: @unsit=y,tploc=y,tpto:269931/357972/21=force

(It also generates an error as it tries to stop the script, and it is called "New Script" not "Teleport" as it expects.)

Just to be doubly sure I renamed it, and it still works.

Note I have my Open Collar on, as well as all three objects... So interaction doesn't seem to be part of the problem.

No wonder this problem was so infuriating: it appears to depend on the object itself, and perhaps its age.

Edited by May Suki
Link to comment
Share on other sites

I did more testing... the "testObject" (sphere) is cleared: that was an error on my part, as the script stops itself after running.

The bare-bones key still does not work. This is completely bizarre - but it's freeing in another way. Means no matter how much I change the script, that won't fix it.

Link to comment
Share on other sites

I took off that Key, and used a different model of Key - every Dolly needs at least a dozen :D

This secondary Key I've been using for testing the scripts when they are installed into a fresh key - the scripts are designed to be able to work in any physical Key object.

This secondary Key I stripped everything out of, then added the LM and the script. It worked.

The Key I've been working with has many prims (12?) and this key I think has 3. This key might also be mesh, I don't know.

Since this secondary Key is also one I've been using on a regular basis, I may switch to this for now (for daily use and development) and come back to the other for more testing. (I'm not giving up!)

Could the problems be something in the build of the more complicated (primwise) Key?

I'm pretty sure the scripts are all in the Root Prim.

Link to comment
Share on other sites

14 minutes ago, May Suki said:

Could the problems be something in the build of the more complicated (primwise) Key?

Following on Wulfie's results, before spending too much time on different objects, I'd suggest taking one instance that you've found to work, and one instance that doesn't, and add a separate script to each that monitors every link message flowing through the objects. Then painstakingly review every single character of the messages that are supposed to be triggering teleport, looking for the slightest difference between the working and non-working instances.

It also might ultimately come down to some other message, not the ones directly responsible for triggering teleport, somehow inhibiting the effect of the teleport messages, but either way, I'd go with Wulfie's intuition that "[t]he problem has to be in the other script that tries to send the link message."

Link to comment
Share on other sites

The testing I did had no link messages: it was done with a version of the script that is self-contained, uses no macros outside of those actually in the script, and performs a teleport to the Landmark "Home" on script startup.

Link to comment
Share on other sites

Now the new alternate key is showing the same symptoms as before. I moved the relevant code back into the script I want it in for the released product, and while llRequestInventoryData() works, no dataserver event fires.

It worked when it was an isolated item in the Key.

I don't know if this is a Firestorm thing, or an object thing, or if its something else. I hate to give up without resolving what is going on, but this feature is a "nice to have"... but still.

Link to comment
Share on other sites

So I went ahead and grabbed the source code from the 25th (before changes to the teleporting were being made) and moved all required code to Avatar.lsl so that anyone can compile it without any of the include files.

LSL Preprocessor is still required with Lazy Lists and Switch statements. These can be enabled from Preferences > Firestorm > Build 1.

Quote




//========================================
// Avatar.lsl
//========================================
//
// vim:sw=4 et nowrap filetype=lsl
//
// DATE: 24 November 2020

//#include "include/GlobalDefines.lsl"
#define LOCKMEISTER_CHANNEL         -8888
#define ANIMATION_NONE              ""
#define CARRY_TIMEOUT               300
#define MAIN                        "~Main Menu~"
#define ANIMATION_COLLAPSED         "collapse"
#define PERMISSION_MASK             0x8414
#define SEND_CONFIG                 300
#define INTERNAL_CMD                305
#define MENU_SELECTION              500
#define POSE_SELECTION              502
#define POSE_TIMEOUT                300
#define RLV_RESET                   350
#define INIT_STAGE5                 110
#define CONFIG_REPORT               142
#define cdIsDoll(id)                (id == dollID)
#define cdIsController(id)          cdGetControllerStatus(id)

#define debugSay(level,prefix,msg)  if (debugLevel >= level) llOwnerSay(prefix+"("+((string)level)+"):"+((string)__LINE__)+": "+(msg))
#define lmRunRlv(command)           cdLinkMessage(LINK_THIS,0,315,__SHORTFILE__+"|rlvRunCmd|"+command,keyID)

#define cdInitializeSeq()           mySeqNum = llRound(llFrand(1<<15))
#define cdSplitArgs(a)              llParseStringKeepNulls((a), [ "|" ], [])
#define cdSelfPosed()               (poserID == dollID)
#define cdUserProfile(id)           "secondlife:///app/agent/"+(string)id+"/about"
#define cdLinkMessage(a,b,c,d,e)    llMessageLinked((a), (((++mySeqNum) << 16) | (b << 10) | c), myName + "|" + d, e)

#define lmSendConfig(name,value)                    cdLinkMessage(LINK_THIS,0,300,name+"|"+value,keyID)
#define lmSetConfig(name,value)                     cdLinkMessage(LINK_THIS,0,301,name+"|"+value,keyID)
#define lmInternalCommand(command,parameter,id)     cdLinkMessage(LINK_THIS,0,305,command+"|"+parameter,id)
#define lmMenuReply(choice,name,id)                 cdLinkMessage(LINK_THIS,0,500,choice+"|"+name,id)
#define lmDialogListen()                            lmInternalCommand("dialogListen", "", NULL_KEY)

#define parseLinkHeader(a,i) \
        split     = cdSplitArgs(a); \
        script    = (string)split[0]; \
        remoteSeq = (i & 0xFFFF0000) >> 16; \
        optHeader = (i & 0x00000C00) >> 10; \
        code      =  i & 0x000003FF; \
        split     = llDeleteSubList(split, 0, 0 + optHeader)

integer cdGetControllerStatus(key id) {
    // Rules:
    //   Dolly is a Controller only if there aren't any User Controllers
    //   A User is a Controller if they are in the controller list
    //
    if (cdIsDoll(id))
        return (controllerList == []);
    else {
        return (llListFindList(controllerList, [ (string)id ]) != -1);
    }
}
list dialogSort(list srcButtons) {
    list outButtons;
    // This function realigns the buttons so we can
    // get the proper buttons in the proper places:
    //
    // (Placeholders only: in actual lists, strings are
    // required, not integers.)
    //
    // INPUT: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]
    //
    // OUTPUT: [ 10, 11, 12, 7, 8, 9, 4, 5, 6, 1, 2, 3 ]
    while (llGetListLength(srcButtons) != 0) {
        outButtons += (list)srcButtons[-3, -1];
        srcButtons = llDeleteSubList(srcButtons, -3, -1);
    }
    return outButtons;
}

#define isCollapseAnimationPresent()    (llGetInventoryType(ANIMATION_COLLAPSED) == INVENTORY_ANIMATION)
integer arePosesPresent() {
    integer poseCount;
    integer inventoryType;

    poseCount = llGetInventoryNumber(INVENTORY_ANIMATION);

    if (poseCount == 0) {
        llSay(DEBUG_CHANNEL, "No animations found!");
    }
    else {
        // Either there are more than two animations, which means at least one pose -
        // or there is one pose, which may or may not be a pose (could be a collapse animation)
        if (poseCount > 2) return TRUE;
        else if (!isCollapseAnimationPresent()) return TRUE;
    }
    return FALSE;
}

vector carrierPos;
vector carrierPosNew;

setCarrier(key carrierIDNew) {
    // This is only to set a proper carrier, NOT to drop one
    if (carrierIDNew == NULL_KEY) return;

    // This only happens when Dolly is first carried...
    // If Dolly is posed, then it still needs to be activated

    hasCarrier = TRUE;
    carrierID = carrierIDNew;
    carrierName = llGetDisplayName(carrierID);

    lmSendConfig("carrierID", (string)carrierID);
    lmSendConfig("carrierName", carrierName);
    lmSendConfig("carryExpire", (string)(carryExpire = llGetUnixTime() + CARRY_TIMEOUT));
}

dropCarrier(key id) {
    // Dolly is at Target

    // Full stop: avatar comes to stop
    carrierPos = ZERO_VECTOR;
    carrierID = NULL_KEY;
    carrierName = "";
    carryExpire = 0;
    hasCarrier = FALSE;

    stopFollow(id);

    // Could use Uncarry, but that is orient towards a user drop
    lmSendConfig("carrierID", (string)carrierID);
    lmSendConfig("carrierName", carrierName);
    lmSendConfig("carryExpire", (string)carryExpire);
}

startFollow(key id) {
    llSetTimerEvent(timerRate = adjustTimer());

    keepFollow(id);
}

stopFollow(key id) {
    // Stop going towards target
    llStopMoveToTarget();
    llSetTimerEvent(timerRate = adjustTimer());
}

keepFollow(key id) {
    // if Dolly is posed, stop moving
    if (poseAnimation != ANIMATION_NONE) {
        // Note we are still being carried - but being in a pose, we're
        // not going anywhere.
        llStopMoveToTarget();
        return;
    }

    // We are not posed...

    list objectDetails = llGetObjectDetails(id, [OBJECT_POS]);
    carrierPosNew = (vector)objectDetails[0];

    if (carrierPosNew) {

#ifdef DRUNKARDS_WALK
        list myDetails = llGetObjectDetails(llGetOwner(), [OBJECT_POS]);
#endif

        // Bump timer
        lmSendConfig("carryExpire", (string)(carryExpire = llGetUnixTime() + CARRY_TIMEOUT));

        if (llVecDist(carrierPosNew,llGetPos()) <= 2.0) {
            carrierPos = carrierPosNew;

            //stopFollow(); stopFollow() doesnt do what we want
            //if (timerRate != 2.0) {
            //    timerRate = 2.0;
            //    llSetTimerEvent(timerRate); // the usual follow timer is too tight... back off until carrier moves away
            //    llStopMoveToTarget();
            //}
            nearCarrier = TRUE;
            llSetTimerEvent(timerRate = 2.0); // the usual follow timer is too tight... back off until carrier moves away
            llStopMoveToTarget();

            return;
        }
        else if (nearCarrier == TRUE) {
            nearCarrier = FALSE;
            llSetTimerEvent(timerRate = adjustTimer()); // crank the timer back up
        }

        // Note that keepFollow() keeps running... just in case the carrier DOES move...
        // This also means that as long as the carrier doesn't move more than 2m away,
        // we won't move.

#ifdef DRUNKARDS_WALK
       if (myPos == (vector)myDetails[0]) {
            stuckFollowLimit--;
            if (struckFollowLimit == 0) {
                // Try to get unstuck
                drunkardsWalk();
                stuckFollowLimit = STUCK_LIMIT;
            }
        }
        else {
            stuckFollowLimit = STUCK_LIMIT;
        }

        myPos = (vector)myDetails[0];
#endif

#ifdef DEVELOPER_MODE
        // See if carrier has moved...
        if (carrierPos != carrierPosNew) {
            debugSay(6,"DEBUG-FOLLOW","Carrier moved to " + (string)(carrierPos));
        }
#endif

        carrierPos = carrierPosNew;

#ifdef TURN_TOWARDS
        // This just turns us to face toward the carrier
        // FIXME: Do we want this?
        pointTo = carrierPos - llGetPos();
        lmRunRlv("setrot:" + (string)(llAtan2(pointTo.x, pointTo.y)) + "=force");
#endif

        //debugSay(6,"DEBUG-FOLLOW","Moving toward carrier at " + (string)(carrierPos));

        // Do actual move
        llMoveToTarget(carrierPos, 0.7);
    }
    else {
        llOwnerSay("Your carrier has disappeared, and seems to have dropped you.");
        dropCarrier(id);
    }
}

key carrierID               = NULL_KEY;
key dollID                  = NULL_KEY;
key keyID                   = NULL_KEY;
key poseAnimationID         = NULL_KEY;
key poserID                 = NULL_KEY;

list controllerList         = [];
list split                  = [];

string backMenu             = MAIN;
string carrierName;
string dollName;

string dollType;
string poseAnimation;
string pronounHerDoll       = "her";
string pronounSheDoll       = "she";
string script;
string myName;
integer allowPose           = 1;

integer hardcore;
integer canTalkInPose       = 1;
integer code;
integer collapsed;
integer optHeader;

integer remoteSeq;
integer rlvOk               = -1;
integer timeReporting       = 1;


integer carryExpire;

integer debugLevel = 8;

integer dialogChannel;
integer mySeqNum;

// #include "include/Json.lsl"

//#define DEBUG_BADRLV
#define NOT_IN_REGION ZERO_VECTOR
#define cdLockMeisterCmd(a) llWhisper(LOCKMEISTER_CHANNEL,(string)dollID+a)
#define cdAOoff() llWhisper(LOCKMEISTER_CHANNEL,(string)dollID+"bootoff")
#define cdAOon()  llWhisper(LOCKMEISTER_CHANNEL,(string)dollID+"booton")
#define MAX_RLVCHECK_TRIES 5
#define RLV_TIMEOUT 20.0
#define POSE_CHANNEL_OFFSET 777
#define UNSET -1
#define ALL_CONTROLS (CONTROL_FWD|CONTROL_BACK|CONTROL_LEFT|CONTROL_RIGHT|CONTROL_ROT_LEFT|CONTROL_ROT_RIGHT|CONTROL_UP|CONTROL_DOWN|CONTROL_LBUTTON|CONTROL_ML_LBUTTON)

#define cdListenerDeactivate(a) llListenControl(a, 0)
#define cdListenerActivate(a) llListenControl(a, 1)
#define cdResetKey() llResetOtherScript("Start")
#define notCurrentAnimation(p) ((p) != poseAnimation)
#define getAnimationName(n) llGetInventoryName(INVENTORY_ANIMATION, (n));
#define getAnimationCount() llGetInventoryNumber(INVENTORY_ANIMATION);

#define MIN_FRAMES 20
#define ADD_FRAMES 20
#define cdMinRefresh() ((1.0/llGetRegionFPS()) * MIN_FRAMES)
#define cdAddRefresh() ((1.0/llGetRegionFPS()) * ADD_FRAMES)
#define currentlyPosed(p) ((p) != ANIMATION_NONE)
#define notCurrentlyPosed(p) ((p) == ANIMATION_NONE)
#define poseChanged (currentAnimation != poseAnimation)
#define keyDetached(id) (id == NULL_KEY)

key rlvTPrequest;

// Note that this is not the "speed" nor is it a slowing factor
// This is a vector of force applied against Dolly: headwind speed
// is a good way to think of it
float afkSlowWalkSpeed = 30;

// Note that this is just a starting point; timerRate is adaptive
float timerRate = 30.0;

string msg;
string name;
string value;
string currentAnimation;

#ifdef DEVELOPER_MODE
string myPath;
#endif

integer i;
integer posePage;
integer timerMark;
integer lastTimerMark;
integer timeMark;
integer reachedTarget = FALSE;
integer hasCarrier;
integer nearCarrier;

// This acts as a cache of
// current poses in inventory
list poseBufferedList;
integer poseCount;

integer poseChannel;

key grantorID;
integer permMask;

integer locked;
integer targetHandle;

//========================================
// FUNCTIONS [CARRY]
//========================================
//
// Because of the importance of these, and the possibility of different methods
// of performing these functions, these have been separated out.
//
// Functions:
//     * startFollow(id)
//     * keepFollow(id)
//     * stopFollow(id)
//     * drunkardsWalk()
//     * dropCarrier(id)
//
// Follow is begun either by the setting of the Carrier ID or by the Dolly
// being unposed. The latter is because Dolly could be carried and posed;
// when put down, Dolly follows. Before that, Dolly is static and in pose.

#define TURN_TOWARDS

// #include "include/Follow.inc" // Uses timer + llMoveToTarget()
//#include "include/Follow2.inc" // Uses llTarget and llMoveToTarget

//========================================
// FUNCTIONS
//========================================

float adjustTimer() {

#define LOW_SPEED_CARRY_RATE 15.0
#define HI_SPEED_CARRY_RATE 0.5
#define LOW_SCRIPT_RATE 60.0
#define NORMAL_RATE 30.0

    if (hasCarrier) {
        if (nearCarrier) return LOW_SPEED_CARRY_RATE;
        else return HI_SPEED_CARRY_RATE;
    }
    else {
        return 0.0;
    }
}

bufferPoses() {
    string poseEntry;
    integer foundCollapse;

    poseCount = getAnimationCount();

    if (arePosesPresent() == FALSE) {
        llSay(DEBUG_CHANNEL,"No animations! (must have collapse animation and one pose minimum)");
        return;
    }

    i = poseCount; // loopIndex

    while (i--) {
        // Build list of poses
        poseEntry = getAnimationName(i);

        if (poseEntry != ANIMATION_COLLAPSED) poseBufferedList += poseEntry;
        else foundCollapse = TRUE;
    }

    if (foundCollapse == FALSE) {
        llSay(DEBUG_CHANNEL,"No collapse animation (\"" + (string)(ANIMATION_COLLAPSED) + "\") found!");
        return;
    }

    poseBufferedList = llListSort(poseBufferedList,1,1);
}

posePageN(string choice, key id) {
    // posePage is the number of the page of poses;
    // poseIndex is a direct index into the list of
    // poses
    //
    integer poseIndex;

    list poseDialogButtons;
    integer isDoll = cdIsDoll(id);
    integer isController = cdIsController(id);

    debugSay(4,"DEBUG-AVATAR","poseBufferedList = " + llDumpList2String(poseBufferedList,","));
    debugSay(4,"DEBUG-AVATAR","poseCount = " + (string)poseCount);

    // Create the poseBufferedList and poseDialogButtons...
    if (poseBufferedList == []) bufferPoses();

    // remove the current pose if any to create poseDialogButtons
    if (currentlyPosed(poseAnimation)) {

        // If this was false, it would mean we have a poseAnimation that is
        // not in the Key's inventory...
        if (~(i = llListFindList(poseBufferedList, [ poseAnimation ]))) {

            poseDialogButtons = llDeleteSubList(poseBufferedList, i, i);

        }
    }
    else {
        poseDialogButtons = poseBufferedList;
    }

#define MAX_DIALOG_BUTTONS 9
#define numberOfDialogPages(p) (llFloor((p) / MAX_DIALOG_BUTTONS) + 1)
#define indexOfPage(p) (((p) - 1) * MAX_DIALOG_BUTTONS)

    list bottomDialogLine;

#ifdef ROLLOVER
    bottomDialogLine = [ "Poses Prev", "Poses Next" ];
#endif

    // Now select the appropriate slice of the total list, showing nine buttons
    // at a time
    //
    if (choice == "Poses Next") {
        posePage++;
        poseIndex = indexOfPage(posePage);

        if (poseIndex > poseCount) {
            // We've gone past the end of the list of poses...
#ifdef ROLLOVER
            // Reset to page one and continue
            posePage = 1;
            poseIndex = 0;
#else
            posePage--; // backtrack
            poseIndex = indexOfPage(posePage); // recompute
            bottomDialogLine = [ "Poses Prev", "-" ];
#endif
        }
    }
    else if (choice == "Poses Prev") {
        posePage--;

        if (posePage == 0) {
            // We've gone past the first entry in the list of poses
#ifdef ROLLOVER
            // Reset to the last page and continue
            posePage = numberOfDialogPages(poseCount);
            poseIndex = indexOfPage(posePage);
#else
            posePage = 1;
            poseIndex = 0;
            bottomDialogLine = [ "-", "Poses Next" ];
#endif
        }
    }

    debugSay(4,"DEBUG-AVATAR","Found " + (string)poseCount + " poses");
    debugSay(4,"DEBUG-AVATAR","Page = " + (string)posePage + "; Index = " + (string)poseIndex);
    debugSay(4,"DEBUG-AVATAR","poseDialogButtons = " + llDumpList2String(poseDialogButtons,","));

    poseDialogButtons = llListSort(poseDialogButtons, 1, 1);

    if (poseCount > MAX_DIALOG_BUTTONS) {
        // Get a 9-count slice from poseDialogButtons for dialog
        poseDialogButtons = (list)poseDialogButtons[poseIndex, poseIndex + 8] + bottomDialogLine + [ "Back..." ];
    }
    else {
        // Can display all poses on one page
        //
        // Note that if we get here, it is impossible for the Next and Prev sections to run: first time around,
        // they aren't options; second time around, we get here and don't offer the options.
        //
        // Note too, that even with ROLLOVER nothing other than ignoring Forward and Backwards makes sense.
        //
        poseDialogButtons += [ "-", "-", "Back..." ];
    }

    debugSay(4,"DEBUG-AVATAR","poseDialogButtons (revised) = " + llDumpList2String(poseDialogButtons,","));

    lmSendConfig("backMenu",(backMenu = MAIN));

    msg = "Select the pose to put dolly into";
    if (poseAnimation) msg += " (current pose is " + poseAnimation + ")";

    llDialog(id, msg, dialogSort(poseDialogButtons), poseChannel);
}

#define isFlying  (agentInfo & AGENT_FLYING)
#define isSitting (agentInfo & AGENT_SITTING)

clearPoseAnimation() {
    list animList;
    key animKey;
    integer agentInfo;

    agentInfo = llGetAgentInfo(llGetOwner());

    // Clear all animations

    // Get list of all current animations
    animList = llGetAnimationList(dollID);
    animKey = NULL_KEY;

    i = llGetListLength(animList);

    // Clear current saved animation if any
    poseAnimation = ANIMATION_NONE;
    poseAnimationID = NULL_KEY;

    // Stop all currently active animations
    //
    // Note that this will stop current system animations, but they
    // will not "stay down" and will return, although will not be playing
    //
    if (!isSitting) {
        while (i--) {
            animKey = (key)animList[i];
            if (animKey) llStopAnimation(animKey);
        }
    }

    llSay(99,"stop"); // Turns off dances: customarily on channel 99

    // Reset current animations
    llStartAnimation("Stand");

    if (hasCarrier) keepFollow(carrierID);
    llSetTimerEvent(timerRate = adjustTimer());
    cdAOon();
}

setPoseAnimation(string anim) {
    key animKey;

    //integer upRefresh;

    // Strip down to a single animation (poseAnimation)

    cdAOoff();

    // Already animated: stop it first
    if (poseAnimationID != NULL_KEY) {
        llStopAnimation(poseAnimationID);
        poseAnimationID = NULL_KEY;
        poseAnimation = ANIMATION_NONE;
    }

    if (notCurrentlyPosed(anim)) return;

    //if ((llGetPermissionsKey() != dollID) || (!(llGetPermissions() & PERMISSION_TRIGGER_ANIMATION)))
    //    return NULL_KEY;

    // We need the key of the animation, but this method only works on full perm animations
    //key animID = llGetInventoryKey(anim);
    llStartAnimation(anim);

    // We cant use lazy lists here, as this is a *generated* list not a named list
    list animList = llGetAnimationList(llGetPermissionsKey());
    animKey = (string)animList[-1];

    if (animKey != NULL_KEY) {
        // We have an actual pose...
        poseAnimationID = animKey;
        poseAnimation = anim;

        // Stop following carrier if we have one
        if (hasCarrier) {
            // Stop following carrier, and freeze
            stopFollow(carrierID);
        }

        // This adjusts the refresh rate
        llSetTimerEvent(timerRate = adjustTimer());
    }
}

//========================================
// STATES
//========================================
default {

    //----------------------------------------
    // STATE ENTRY
    //----------------------------------------
    state_entry() {
        dollName = llGetDisplayName(dollID = llGetOwner());
        keyID = llGetKey();

        rlvOk = UNSET;
        cdInitializeSeq();

        llRequestPermissions(dollID, PERMISSION_MASK);
        myName = llGetScriptName();
    }

    //----------------------------------------
    // ON REZ
    //----------------------------------------
    on_rez(integer start) {
        // Set up key when rezzed

        //rlvOk = UNSET;
        //llStopMoveToTarget();
        //llTargetRemove(targetHandle);

        //debugSay(5,"DEBUG-AVATAR","ifPermissions (on_rez)");
        //llRequestPermissions(dollID, PERMISSION_MASK);
    }

    //----------------------------------------
    // ATTACH
    //----------------------------------------
    // During attach, we perform:
    //
    //     * drop carrier
    //     * read poses into buffered List
    //
    attach(key id) {

        if (keyDetached(id)) return;

        rlvOk = UNSET;

        debugSay(2,"DEBUG-FOLLOW","dropCarrier(): from attach");
        dropCarrier(carrierID);

        if (id) {
#ifdef DEVELOPER_MODE
            debugSay(2,"DEBUG-AVATAR","Region FPS: " + formatFloat(llGetRegionFPS(),1) + "; Region Time Dilation: " + formatFloat(llGetRegionTimeDilation(),3));
            debugSay(5,"DEBUG-AVATAR","ifPermissions (attach)");
#endif
            llRequestPermissions(dollID, PERMISSION_MASK);
        }

        debugSay(4,"DEBUG-AVATAR","Checking poses on attach");

        poseBufferedList = [];
        bufferPoses();
    }

    //----------------------------------------
    // CHANGED
    //----------------------------------------
    changed(integer change) {
        if (change & CHANGED_INVENTORY) {
            poseBufferedList = [];
            bufferPoses();
        }
#ifdef DEVELOPER_MODE
        else if (change & (CHANGED_REGION | CHANGED_TELEPORT)) {
            // related to follow
            //llStopMoveToTarget();
            //llTargetRemove(targetHandle);

            msg = "Region ";
            if (llGetParcelFlags(llGetPos()) & PARCEL_FLAG_ALLOW_SCRIPTS) msg += "allows scripts";
            else msg += "does not allow scripts";

            debugSay(3,"DEBUG-AVATAR",msg);
            debugSay(3,"DEBUG-AVATAR","Region FPS: " + formatFloat(llGetRegionFPS(),1) + "; Region Time Dilation: " + formatFloat(llGetRegionTimeDilation(),3));
            //debugSay(5,"DEBUG-AVATAR","ifPermissions (changed)");
            //llRequestPermissions(dollID, PERMISSION_MASK);
        }
#endif
    }

    //----------------------------------------
    // LINK MESSAGE
    //----------------------------------------
    link_message(integer lmSource, integer lmInteger, string lmData, key lmID) {
        parseLinkHeader(lmData,lmInteger);

        if (code == SEND_CONFIG) {
            name = (string)split[0];
            value = (string)split[1];
            split = llDeleteSubList(split, 0, 0);

                 if (name == "collapsed") {
                    collapsed = (integer)value;

                    // Reset pose?
                    llRequestPermissions(dollID, PERMISSION_MASK);

                    llSetTimerEvent(timerRate = adjustTimer());
            }
            else if (name == "canTalkInPose")     canTalkInPose = (integer)value;
            else if (name == "carryExpire")         carryExpire = (integer)value;
            else if (name == "carrierID")             carrierID = value;
            else if (name == "rlvOk")                     rlvOk = (integer)value;
            else if (name == "carrierName")         carrierName = value;
#ifdef ADULT_MODE
            else if (name == "hardcore")               hardcore = (integer)value;
#endif
#ifdef DEVELOPER_MODE
            else if (name == "timeReporting")     timeReporting = (integer)value;
#endif
            else if (name == "poseAnimation") {
                // Note that poses are handled as a choice... not here
                poseAnimation = value;
            }
            else if (name == "poserID")                 poserID = (key)value;
            else if (name == "dollType")                   dollType = value;
#ifdef DEVELOPER_MODE
            else if (name == "debugLevel")               debugLevel = (integer)value;
#endif
            else if (name == "allowPose")                 allowPose = (integer)value;
            else if (name == "controllers") {
                if (split == [""]) controllerList = [];
                else controllerList = split;
            }
            else if (name == "pronounHerDoll")       pronounHerDoll = value;
            else if (name == "pronounSheDoll")       pronounSheDoll = value;
            else if (name == "dialogChannel") {
                dialogChannel = (integer)value;
                poseChannel = dialogChannel - POSE_CHANNEL_OFFSET;
            }
        }
        else if (code == INTERNAL_CMD) {
            string cmd = (string)split[0];
            split = llDeleteSubList(split, 0, 0);

            switch (cmd) {

                case "posePageN": {

                    string choice = (string)split[0];
                    posePageN(choice,lmID);
                    break;
                }

                case "teleport": {
                    string lm = (string)split[0];

                    llRegionSayTo(lmID, 0, "Teleporting dolly " + dollName + " to  landmark " + lm + ".");

                    lmRunRlv("tploc=y");

                    // This should trigger a dataserver event
                    rlvTPrequest = llRequestInventoryData(lm);
                    debugSay(6,"DEBUG-AVATAR","rlvTPrequest = " + (string)rlvTPrequest);
                    break;
                }

                case "startFollow": {
                    startFollow(carrierID);
                    break;
                }

                case "stopFollow": {
                    stopFollow(carrierID);
                    break;
                }
            }
        }
        else if (code == MENU_SELECTION) {
            string choice = (string)split[0];
            string choice5 = llGetSubString(choice,0,4);
            string name = (string)split[1];

            // Dolly is poseable:
            //    * by members of the Public IF allowed
            //    * by herself
            //    * by Controllers
            integer dollIsPoseable = ((!cdIsDoll(lmID) && (allowPose)) || cdIsController(lmID) || cdSelfPosed());

            // First: Quick ignores
            if (llGetSubString(choice,0,3) == "Wind") return;
            else if (choice == MAIN) return;

#ifdef ADULT_MODE
            else if (choice == "Strip") {
                lmInternalCommand("strip", "", lmID);
            }
#endif
            else if (choice == "Carry") {
                setCarrier(lmID);
                llSay(PUBLIC_CHANNEL, "Dolly " + dollName + " has been picked up by " + carrierName);
                startFollow(carrierID);
            }
            else if (choice == "Uncarry") {
                llSay(PUBLIC_CHANNEL, "Dolly " + dollName + " has been placed down by " + carrierName);

                debugSay(2,"DEBUG-FOLLOW","dropCarrier(): from Uncarry button");
                dropCarrier(carrierID);
            }

            // Unpose: remove animation and poser
            else if (choice == "Unpose") {
                lmSendConfig("poseAnimation", (string)(poseAnimation = ANIMATION_NONE));
                lmSendConfig("poserID", (string)(poserID = NULL_KEY));

                // poseLockExpire is being set elsewhere
                lmSetConfig("poseLockExpire", "0");

                clearPoseAnimation();

                // Whether Dolly can or can't talk in pose is irrelevant here
                lmRunRlv("sendchat=y");

                // if we have carrier, start following them again
                debugSay(2,"DEBUG-FOLLOW","startFollow(): from Unpose button");
                if (hasCarrier) startFollow(carrierID);
            }

            else if (choice == "Poses...") {
                if (!cdIsDoll(lmID))
#ifdef ADULT_MODE
                    if (!hardcore)
#endif
                        llOwnerSay(cdUserProfile(lmID) + " is looking at your poses menu.");

                posePage = 1;
                lmDialogListen();
                lmInternalCommand("posePageN",choice, lmID);
            }
        }
        else if (code == POSE_SELECTION) {
            string choice = (string)split[0];

            // it could be Poses Next or Poses Prev instead of an Anim
            if (choice == "Poses Next" || choice == "Poses Prev") {
                lmDialogListen();
                llSleep(0.5);
                lmInternalCommand("posePageN",choice, lmID);
            }

            // could have been "Back..."
            else if (choice == "Back...") {
                lmMenuReply(backMenu, llGetDisplayName(lmID), lmID);
                lmSendConfig("backMenu",(backMenu = MAIN));
            }

            else {
                // None of the other choices are valid: it's a pose
                string expire;

                llSay(PUBLIC_CHANNEL,"Pose " + choice + " selected.");

                if (poseAnimationID != NULL_KEY) {
                    llStopAnimation(poseAnimationID);
                    debugSay(4,"DEBUG-AVATAR","Stopping old animation " + poseAnimation + " (" + (string)poseAnimationID + ")");
                }

                // The Real Meat: We have an animation (pose) name
                lmSendConfig("poseAnimation", (string)(poseAnimation = choice));
                lmSendConfig("poserID", (string)(poserID = lmID));

                //debugSay(5,"DEBUG-AVATAR","ifPermissions (link_message 300/poseAnimation)");
                setPoseAnimation(poseAnimation); 

#ifdef ADULT_MODE
#define poseDoesNotExpire (dollType == "Display" || hardcore)
#else
#define poseDoesNotExpire (dollType == "Display")
#endif
                if (poseDoesNotExpire) expire = "0";
                else expire = (string)(llGetUnixTime() + POSE_TIMEOUT);
                lmSetConfig("poseLockExpire", expire);

                if (!canTalkInPose) lmRunRlv("sendchat=n");
            }
        }
        else if (code == RLV_RESET) {
            rlvOk = (integer)split[0];

            if (rlvOk) {
                // This should only happen when the RLVcheck is
                // done during login or attach
                if (poseAnimation != ANIMATION_NONE) {
                    setPoseAnimation(poseAnimation); 
                    if (!canTalkInPose) lmRunRlv("sendchat=n");
                }
            }
        }
        else if (code < 200) {
            if (code == INIT_STAGE5) {
                //debugSay(5,"DEBUG-AVATAR","ifPermissions (link_message 110)");

                poseAnimation = ANIMATION_NONE;
                poseAnimationID = NULL_KEY;
                clearPoseAnimation();
            }
#ifdef DEVELOPER_MODE
            else if (code == MEM_REPORT) {
                memReport("Avatar",(float)split[0]);
            }
#endif
            else if (code == CONFIG_REPORT) {
                // cdConfigureReport();
            }
        }
    }

    //----------------------------------------
    // TIMER
    //----------------------------------------

    // Timer is used solely to follow the carrier

    timer() {

        if (hasCarrier) keepFollow(carrierID);
        else llSetTimerEvent(0.0);

#ifdef DEVELOPER_MODE
        if (timeReporting) {
            timerMark = llGetUnixTime();

            if (lastTimerMark) {
                debugSay(5,"DEBUG-AVATAR","Avatar Timer fired, interval " + formatFloat(timerMark - lastTimerMark,2) + "s.");
            }

            lastTimerMark = timerMark;
        }
#endif
    }

#ifdef EMERGENCY_TP
    //----------------------------------------
    // DATASERVER
    //----------------------------------------
    dataserver(key queryID, string queryData) {
#define getRegionLocation(d) (llGetRegionCorner() + ((vector)data))
#define locationToString(d) ((string)(llFloor(d.x)) + "/" + ((string)(llFloor(d.y))) + "/" + ((string)(llFloor(d.z))))

        debugSay(6,"DEBUG-AVATAR","Data server running!");
        debugSay(6,"DEBUG-AVATAR","Request = " + (string)queryID);
        debugSay(6,"DEBUG-AVATAR","rlvTPrequest = " + (string)rlvTPrequest);
        llOwnerSay("dataserver fired!");

        if (queryID == rlvTPrequest) {
            vector global = getRegionLocation(queryData);

            debugSay(6,"DEBUG-AVATAR","Dolly should be teleporting now...");
            llOwnerSay("Dolly is now teleporting.");

            // Note this will be rejected if @unsit=n or @tploc=n are active
            lmRunRlvAs("TP", "tpto:" + locationToString(global) + "=force");
            lmRunRlv("tploc=n"); // restore restriction
        }
    }
#endif

    //----------------------------------------
    // RUN TIME PERMISSIONS
    //----------------------------------------
    run_time_permissions(integer perm) {
        debugSay(2,"DEBUG-AVATAR","ifPermissions (run_time_permissions)");

        permMask = perm;

        //----------------------------------------
        // PERMISSION_TRIGGER_ANIMATION

        if (permMask & PERMISSION_TRIGGER_ANIMATION) {

            // The big work is done in clearPoseAnimation() and setPoseAnimation()

            if (poseChanged) {
                if (notCurrentlyPosed(poseAnimation)) clearPoseAnimation();
                else setPoseAnimation(poseAnimation); 
                currentAnimation = poseAnimation;
            }

            llSetTimerEvent(timerRate = adjustTimer());
        }

        //----------------------------------------
        // PERMISSION_TAKE_CONTROLS

#define disableMovementControl() llTakeControls(ALL_CONTROLS, TRUE, FALSE)
#define enableMovementControl() llTakeControls(ALL_CONTROLS, FALSE, TRUE)

        if (permMask & PERMISSION_TAKE_CONTROLS) {

            if (currentlyPosed(poseAnimation))
                // Dolly is "frozen": either collapsed or posed

                // When collapsed or posed the doll should not be able to move at all; so the key will
                // accept their attempts to move, but ignore them
                disableMovementControl();

            else {
                // Dolly is not collapsed nor posed

                // We do not want to completely release the controls in case the current sim does not
                // allow scripts. If controls were released, key scripts would stop until entering a
                // script-enabled sim
                enableMovementControl();
            }
        }
    }
}

//========== AVATAR ==========

 

It's still expecting a link message to activate, so I'm using a second script:

#define cdLinkMessage(a,b,c,d,e) llMessageLinked((a), (((++mySeqNum) << 16) | (b << 10) | c), myName + "|" + d, e)
#define lmInternalCommand(command,parameter,id)     cdLinkMessage(LINK_THIS,0,305,command+"|"+parameter,id)

string myName = "Sender";
integer mySeqNum = 0;

default
{
    touch_start(integer n)
    {
        // lmInternalCommand("teleport", "home", llGetKey());
        // becomes
        // cdLinkMessage(LINK_THIS, 0, 305, "teleport"+"|"+"home", llGetKey());
        // becomes...

        llOwnerSay((string)["llMessageLinked(",
            LINK_THIS, ", ",
            (((mySeqNum + 1) << 16) | (0 << 10) | 305), ", \"",
            myName + "|teleport|home", "\", \"",
            llGetKey(), "\");"
        ]);

        llMessageLinked(LINK_THIS, (((++mySeqNum) << 16) | (0 << 10) | 305), myName + "|" + "teleport|home", llGetKey());
    }
}

To summarize a bit:

  • My object has two scripts (above) in it and one landmark called "home".
  • The script is very idle, no events are firing regularly.
  • An example call is made: llMessageLinked(-4, 131377, "Sender|teleport|home", "1124a70c-55c0-e9ed-2a3a-ba32deb8a22b");
  • Once received, the command is parsed as split = ["teleport", "home"] and code = 305. (line 711)
  • We arrive at case "teleport" with correct data, rlvTPrequest = llRequestInventoryData("home"); (line 771 & 779)
  • The dataserver triggers and a teleport is attempted. (Link message is sent to another script.)
    • The script that is supposed to perform the RLV command doesn't exist in my object, so the test ends here.
    • The dataserver event works fine.
      • It works even when all 11 scripts are assembled and working together.

When I got to this point the first time, I too became puzzled because dataserver was indeed never happening. Then I noted the #ifdef EMERGENCY_TP (if defined...) and went around looking for where that was defined. I found it in config.h:

/* enable optional emergency TP after collapse */
// #define EMERGENCY_TP 1

Basically, since that definition didn't exist, the dataserver event was completely left out of the compiled script. I sure hope that was intentional at the time the code was committed to the git repo, because the next commit is the rewritten version we started talking about in this thread with all the problems and test scripts. (So this missing definition is not directly related to the code shown in this thread so far, but according to OP's Issue log, the reason for the rewrite is that they were having the same problem.)

As an aside, I seriously doubt that events could ever malfunction because of the age of the object containing the scripts.

I may do another look at the rewritten version from the 27th, but moving those macros over by hand was an incredible chore. It doesn't help that Windows is a garbage system and I couldn't even clone the repository properly. (All fault goes to Microsoft.)

Edited by Wulfie Reanimator
Link to comment
Share on other sites

In my current working git repo, I don't have EMERGENCY_TP: I think I pulled all of that when I tried to get Teleport working again.

Now the equivalent would be TP_HOME.

Since I'm using Ubuntu Linux 20.04 for development:

$ pwd
/home/emd/LSL/work/cdkey/src
$ grep EMERGENCY_TP *lsl */*    
$ grep TP_HOME *lsl */*     
Aux.lsl:#ifdef TP_HOME
Aux.lsl:#ifdef TP_HOME
MenuHandler.lsl:#ifdef TP_HOME
StatusRLV.lsl:#ifdef TP_HOME
StatusRLV.lsl:#ifdef TP_HOME
StatusRLV.lsl:#ifdef TP_HOME
include/config.h:// #define TP_HOME 1
include/config.h:#ifdef TP_HOME

The idea is that all Teleport functions can be activated by setting TP_HOME to 1, and all can be removed by not setting TP_HOME at all.Idea with EMERGENCY_TP was the same, as it is with the other settings in include/config.h. (Side note, HOMING_BEACON is related to TP_HOME, and only works if TP_HOME is set.)

TP_HOME is a manually activated Teleport to the "Home" landmark; HOMING_BEACON activates an automatic activation of the same Teleport.

Also, Firestorm optimizes and removes all unused variables and functions - so the post-processed code is probably considerably simpler.

The full Development key should, when the teleport functions are enabled, react to a chat command on channel 75 (by default) like this:

/75 <prefix> inject 305#teleport|Home

Where prefix is your initials - for me, it's ms now (May Suki) whereas it used to be mr (Maystone Resident before user name change).

During normal Development Key (quiet resting) operation, the only things that should be seen is the activation of the two main timers, and a link message counting down the number of seconds remaining on Dolly's timer. (I'm rather proud of the total reduction of link messages - I worked hard on that!)

In terms of TP_HOME, enabling it will only require a recompile of Aux.lsl, MenuHandler.lsl, and StatusRLV.lsl with the code as I have it in git.

Link to comment
Share on other sites

13 hours ago, Qie Niangao said:

Following on Wulfie's results, before spending too much time on different objects, I'd suggest taking one instance that you've found to work, and one instance that doesn't, and add a separate script to each that monitors every link message flowing through the objects. Then painstakingly review every single character of the messages that are supposed to be triggering teleport, looking for the slightest difference between the working and non-working instances.

It also might ultimately come down to some other message, not the ones directly responsible for triggering teleport, somehow inhibiting the effect of the teleport messages, but either way, I'd go with Wulfie's intuition that "[t]he problem has to be in the other script that tries to send the link message."

Good idea.

I would, however, note that there is already such a script. While a lot of the items passing back and forth may be marked elsewhere, the UpdaterClient script - normally quiescent and waiting for an Update - on the Development Key it is watching every link message going back and forth, and reporting on them:

[00:02:34] Dolly Mei's Key: DEBUG-CHAT(5):1000: Got a chat channel message: Lady Mei メイ Mэй Suki/c5e11d0a-694f-46cc-864b-e42340890934/ms inject 305#teleport|Home
[00:02:34] Dolly Mei's Key: DEBUG-CHAT(5):1022: Got a chat message: inject
[00:02:34] Dolly Mei's Key: Injected link message code 305 with data ChatHandler|teleport|Home and key 
[00:02:34] Dolly Mei's Key: DEBUG-LINKMONITOR(8):199: Link message #INTERNAL_CMD cmd: teleport [ChatHandler] = Home
[00:02:36] Dolly Mei's Key: DEBUG-LANDMARK(6):83: queryLandmarkData set to 9a410471-58bc-9d54-cc86-456e8a9a7e20
[00:02:36] Dolly Mei's Key: DEBUG-LANDMARK(6):84: Teleporting dolly  to  inventory tpLandmark "Home".
[00:02:36] Dolly Mei's Key: DEBUG-LANDMARK(6):186: queryLandmarkData now equals 9a410471-58bc-9d54-cc86-456e8a9a7e20

The notes about "chat message" and "chat channel" and "Injected" refer to the chat control message I recently mentioned.

The script monitoring the link messages marks itself with the "DEBUG-LINKMONITOR" tag you see there.

"DEBUG-LANDMARK" messages are specifically related to the TP/Landmark processing.

After these messages, the timer fires off after 20 seconds and gives an error message: no teleport occurred.

Normal quiet processing looks like this:

[00:03:54] Dolly Mei's Key: DEBUG-TRANSFORM(5):375: Transform Timer fired, interval 30.00s.
[00:03:55] Dolly Mei's Key: DEBUG-MAIN(5):448: Main Timer fired, interval 29.98s.
[00:03:55] Dolly Mei's Key: DEBUG-LINKMONITOR(8):199: Link message #SEND_CONFIG cmd: timeLeftOnKey [Main] = 1515
[00:04:24] Dolly Mei's Key: DEBUG-TRANSFORM(5):375: Transform Timer fired, interval 30.00s.
[00:04:25] Dolly Mei's Key: DEBUG-MAIN(5):448: Main Timer fired, interval 30.05s.
[00:04:25] Dolly Mei's Key: DEBUG-LINKMONITOR(8):199: Link message #SEND_CONFIG cmd: timeLeftOnKey [Main] = 1485
[00:04:54] Dolly Mei's Key: DEBUG-TRANSFORM(5):375: Transform Timer fired, interval 30.00s.
[00:04:55] Dolly Mei's Key: DEBUG-MAIN(5):448: Main Timer fired, interval 29.96s.
[00:04:55] Dolly Mei's Key: DEBUG-LINKMONITOR(8):199: Link message #SEND_CONFIG cmd: timeLeftOnKey [Main] = 1456

The timers don't actually result in any link messages, nor in any listen messages going back and forth: only the variable "timeLeftOnKey" (in seconds) is sent around (by the Main timer processing I believe). When the variable "timeLeftOnKey" reaches zero Dolly goes *splat!*

Link to comment
Share on other sites

I should have mentioned this up front - but this Key will be available to all for free. I have the scripts in a couple Key objects (which I did not make) but only some of the Keys I have a transferable.

The scripts are available to all, and should work in any Key object - or at least, they are designed to!

I also have Updaters that you can use to update the Key when it is worn.

If someone wants to go down the rabbit hole with me, let me know! I'll send you a copy of a Key object with current Development code or an Updater or both.

Link to comment
Share on other sites

16 hours ago, Wulfie Reanimator said:

As an aside, I seriously doubt that events could ever malfunction because of the age of the object containing the scripts.

The only time this applies is when LL breaks a script's compiled type, size or experience, not to mention blacklisting scripts or deprecate functions/constants.

Each of these have happened in the past.

Edited by Lucia Nightfire
  • Thanks 1
Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 110 days.

Please take a moment to consider if this thread is worth bumping.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...