avatar_puppeteer.lsl2
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2013 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//                          CONFIGURATION                                //
///////////////////////////////////////////////////////////////////////////
key CONTROLLING_AVATAR = "a5eff01c-442f-11e3-aa1f-8fe2e364ba2a";
 
///////////////////////////////////////////////////////////////////////////
//                              INTERNALS                                //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
vector wasStringToVector(string in) {
    list v = llParseString2List(in, ["<", ",", ">"], []);
    return <llList2Float(v, 0), llList2Float(v, 1), llList2Float(v, 2)>;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
moveTo(vector position) {
    llTargetRemove(_gia);
    _gia = llTarget(position, 1);
    vector pointTo = position - llGetPos();
    llOwnerSay("@setrot:" + (string)llAtan2(pointTo.x, pointTo.y) + "=force");
    llMoveToTarget(position, 3);    
}
 
integer _gia = 0;
list _gla = [];
integer lines = 0;
vector next = ZERO_VECTOR;
 
default {
    state_entry() {
        llListen(1, "", CONTROLLING_AVATAR, "pathon");
    }
    listen(integer channel, string name, key id, string message) {
        if(message != "pathon") return;
        state closest;
    }
}
 
state setup {
    state_entry() {
        llInstantMessage(CONTROLLING_AVATAR, "Reading path, please wait...");
        llListen(1, "", CONTROLLING_AVATAR, "pathoff"); 
        if(llGetInventoryType("[WaS-K]:Avatar Puppeteer:Data") == INVENTORY_NOTECARD) jump schedule;
        llSay(DEBUG_CHANNEL, "Puppet notecard not found.");
        return;
@schedule;
        llGetNumberOfNotecardLines("[WaS-K]:Avatar Puppeteer:Data");
    }
    listen(integer channel, string name, key id, string message) {
        if(message != "pathoff") return;
        state stop;
    }
    dataserver(key id, string data) {
        lines = (integer)data;
        state read;
    }
}
 
state read {
    state_entry() {
        llListen(1, "", CONTROLLING_AVATAR, "pathoff"); 
        llGetNotecardLine("[WaS-K]:Avatar Puppeteer:Data", lines--);
    }
    listen(integer channel, string name, key id, string message) {
        if(message != "pathoff") return;
        state stop;
    }    
    dataserver(key id, string data) {
        list i = llParseString2List(data, ["#"], []);
        next = wasStringToVector(llList2String(i, 0));
        if(lines == 0) state setup;
        if(next == ZERO_VECTOR) {
            llGetNotecardLine("[WaS-K]:Avatar Puppeteer:Data", lines--);
            return;
        }
        float wait = llList2Float(i, 1);
        if(wait == 0) state walk;
        llSetTimerEvent(wait);
    }
    timer() {
        state walk;
    }
}
state walk {
    state_entry() {
        llListen(1, "", CONTROLLING_AVATAR, "pathoff"); 
        moveTo(next);
    }
    listen(integer channel, string name, key id, string message) {
        if(message != "pathoff") return;
        state stop;
    }
    at_target(integer tnum, vector targetpos, vector ourpos) {
        if(tnum != _gia) return;
        state read;
    }
}
 
state stop {
    state_entry() {
        llInstantMessage(CONTROLLING_AVATAR, "Pathfinding stopped...");
        llStopMoveToTarget();
        llTargetRemove(_gia);
        llListen(1, "", CONTROLLING_AVATAR, "pathon");
    }
    listen(integer channel, string name, key id, string message) {
        if(message != "pathon") return;
        _gia = 1;
        state closest;
    }
}
 
//ok, so this could be done by reading all the points and just going through
//all of them just once, instead of using the dataserver and looping over it
//on the other hand, using a list will increase memory consumption and given
//that most simulators are already eaten up and scripts race each other for
//memory, let's just go for an average O(n^{notecard length)}) algorithm.
//this should also allow longer notecards and thus longer paths which would
//not be possible by loading all the coordinates in lists.
//exit to read
state closest {
    state_entry() {
        llListen(1, "", CONTROLLING_AVATAR, "pathoff");
        llInstantMessage(CONTROLLING_AVATAR, "Finding closest point, please wait...");
        lines = 0;
        if(llGetInventoryType("[WaS-K]:Avatar Puppeteer:Data") == INVENTORY_NOTECARD) jump schedule;
        llSay(DEBUG_CHANNEL, "Puppet notecard not found.");
        return;
@schedule;
        llGetNotecardLine("[WaS-K]:Avatar Puppeteer:Data", ++lines);
    }
    listen(integer channel, string name, key id, string message) {
        if(message != "pathoff") return;
        state stop;
    }
    dataserver(key id, string data) {
        if(data == EOF) {
            // point not found
            ++_gia;
            state loop_closest;
            return;
        }
        if(data == "") jump continue;
        vector n = wasStringToVector(llList2String(llParseString2List(data, ["#"], []), 0));
        if(n == ZERO_VECTOR) jump continue;
        if(llVecDist(n, llGetPos()) > _gia) jump continue;
        // point found in vicinity - switch to read.
        llInstantMessage(CONTROLLING_AVATAR, "Pathfinding started...");
        state read;
@continue;
        llGetNotecardLine("[WaS-K]:Avatar Puppeteer:Data", ++lines);
    }
}
 
state loop_closest { state_entry() { state closest; } }