Code

Work in progress.

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2016 - License: CC BY 2.0      //
///////////////////////////////////////////////////////////////////////////
//
// This script automates the TWI Wolf Avatar with Corrade.
//
// For more information on Corrade, please see:
//     http://grimore.org/secondlife/scripted_agents/corrade
//
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//                         LIBRARY INCLUDES                              //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueGet(string k, string data) {
    if(llStringLength(data) == 0) return "";
    if(llStringLength(k) == 0) return "";
    list a = llParseStringKeepNulls(data, ["&", "="], []);
    integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]);
    if(i != -1) return llList2String(a, 2*i+1);
    return "";
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueEncode(list data) {
    list k = llList2ListStrided(data, 0, -1, 2);
    list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
    data = [];
    do {
        data += llList2String(k, 0) + "=" + llList2String(v, 0);
        k = llDeleteSubList(k, 0, 0);
        v = llDeleteSubList(v, 0, 0);
    } while(llGetListLength(k) != 0);
    return llDumpList2String(data, "&");
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
// escapes a string in conformance with RFC1738
string wasURLEscape(string i) {
    string o = "";
    do {
        string c = llGetSubString(i, 0, 0);
        i = llDeleteSubString(i, 0, 0);
        if(c == "") jump continue;
        if(c == " ") {
            o += "+";
            jump continue;
        }
        if(c == "\n") {
            o += "%0D" + llEscapeURL(c);
            jump continue;
        }
        o += llEscapeURL(c);
@continue;
    } while(i != "");
    return o;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
list wasCSVToList(string csv) {
    list l = [];
    list s = [];
    string m = "";
    do {
        string a = llGetSubString(csv, 0, 0);
        csv = llDeleteSubString(csv, 0, 0);
        if(a == ",") {
            if(llList2String(s, -1) != "\"") {
                l += m;
                m = "";
                jump continue;
            }
            m += a;
            jump continue;
        }
        if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
            m += a;
            csv = llDeleteSubString(csv, 0, 0);
            jump continue;
        }
        if(a == "\"") {
            if(llList2String(s, -1) != a) {
                s += a;
                jump continue;
            }
            s = llDeleteSubList(s, -1, -1);
            jump continue;
        }
        m += a;
@continue;
    } while(csv != "");
    // postcondition: length(s) = 0
    return l + m;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
// unescapes a string in conformance with RFC1738
string wasURLUnescape(string i) {
    return llUnescapeURL(
        llDumpList2String(
            llParseString2List(
                llDumpList2String(
                    llParseString2List(
                        i, 
                        ["+"], 
                        []
                    ), 
                    " "
                ), 
                ["%0D%0A"], 
                []
            ), 
            "\n"
        )
    );
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasMenuIndex = 0;
list wasDialogMenu(list input, list actions, string direction) {
    integer cut = 11-wasListCountExclude(actions, [""]);
    if(direction == ">" &&  (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) {
        ++wasMenuIndex;
        jump slice;
    }
    if(direction == "<" && wasMenuIndex-1 >= 0) {
        --wasMenuIndex;
        jump slice;
    }
@slice;
    integer multiple = wasMenuIndex*cut;
    input = llList2List(input, multiple+wasMenuIndex, multiple+cut+wasMenuIndex);
    input = wasListMerge(input, actions, "");
    return input;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasListCountExclude(list input, list exclude) {
    if(llGetListLength(input) == 0) return 0;
    if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) 
        return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
    return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasListMerge(list l, list m, string merge) {
    if(llGetListLength(l) == 0 && llGetListLength(m) == 0) return [];
    string a = llList2String(m, 0);
    if(a != merge) return [ a ] + wasListMerge(l, llDeleteSubList(m, 0, 0), merge);
    return [ llList2String(l, 0) ] + wasListMerge(llDeleteSubList(l, 0, 0), llDeleteSubList(m, 0, 0), merge);
}
 
///////////////////////////////////////////////////////////////////////////
//                             CONSTANTS                                 //
///////////////////////////////////////////////////////////////////////////
 
list WOLF_ACTIONS = [ "bark", "bark2", "bark3", "bark4", "bark5", "bite", "biteR", "bouncin", "dance", "dig", "drink", "facepaw", "fidget", "flop", "growl", "growlp", "headtilt", "howl", "parthowl", "laypaws", "layside", "laysideR", "licknose", "pant", "pawat", "pawatR", "pawbeat", "play", "prance", "roll", "scratch", "scratchR", "shake", "sit", "sleepbelly", "sleepcurl", "sleepcurlR", "sleepside", "sleepsideR", "sniff", "stretch", "submit", "survey", "surveyR", "tailchase", "tailchaseR", "whine" ];
 
list MAIN_MENU = [ "Actions", "Idle" ];
 
string MENU_TEXT = "Wizardry and Steamworks TMI Wolf Avatar Animator!\n\nˁ˚ᴥ˚ˀ\n\nCurrent Settings:\n\nIdle Animations: %idle%\n\nPlease select an option.";
list MENU_TOGGLES = [ "idle" ];
list MENU_TOGGLES_STATE = [ FALSE ];
 
///////////////////////////////////////////////////////////////////////////
//                          LOCAL FUNCTIONS                              //
///////////////////////////////////////////////////////////////////////////
 
string menuText() {
    list menuText = llParseString2List(MENU_TEXT, ["%"], []);
 
    string replace = "";
    do {
        string first = llList2String(menuText, 0);
        menuText = llDeleteSubList(menuText, 0, 0);
 
        if(llSubStringIndex(first, " ") != -1) {
            replace += first;
            jump continue;
        }
 
        integer i = llListFindList(MENU_TOGGLES, [ first ]);
        integer toggleState = llList2Integer(MENU_TOGGLES_STATE, i);
        if(toggleState) {
            replace += "ON";
            jump continue;
        }
 
        replace += "OFF";
 
@continue;
    } while(llGetListLength(menuText) != 0);
 
    return replace;
}
 
///////////////////////////////////////////////////////////////////////////
//                            INTERNALS                                  //
///////////////////////////////////////////////////////////////////////////
 
// callback URL
string callback = "";
// configuration data
string configuration = "";
// menu channel
integer menuChannel = -1;
integer channelHandle = -1;
integer menuTop = 0;
 
default {
    state_entry() {
        // Generate channel.
        menuChannel = ((integer)("0x"+llGetSubString((string)llGetKey(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
 
        // Start polling for configuration.
        llSetTimerEvent(1);
    }
    link_message(integer sender, integer num, string message, key id) {
        if(id != "configuration")
            return;
 
        configuration = message;
        state main_menu;
    }
    timer() {
        llMessageLinked(LINK_THIS, 0, "configuration", NULL_KEY);
    }
    attach(key id) {
        llResetScript();
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || 
            (change & CHANGED_REGION_START) || 
            (change & CHANGED_OWNER) ||
            (change & CHANGED_REGION)) {
            llResetScript();
        }
    }
    state_exit() {
        llSetTimerEvent(0);
    }
}
 
state main_menu {
    state_entry() {
        // If this is not a top button press, stop right here.
        if(menuTop != TRUE)
            return;
        menuTop = FALSE;
 
        // Coming from a top menu action so send the menu.
        llListenRemove(channelHandle);
        channelHandle = llListen(menuChannel, "", llGetOwner(), "");
 
        llDialog(
            llGetOwner(), 
            menuText(), 
            wasDialogMenu(
                MAIN_MENU, 
                [], 
                ""
            ),
            menuChannel
        );
    }
    touch_start(integer num) {
        llListenRemove(channelHandle);
        channelHandle = llListen(menuChannel, "", llGetOwner(), "");
 
        llDialog(
            llGetOwner(), 
            menuText(),
            wasDialogMenu(
                MAIN_MENU, 
                [], 
                ""
            ),
            menuChannel
        );
    }
    listen(integer channel, string name, key id, string message) {
        // Process menu operations.
        if(message == "Actions") {
            state wolf_actions;
        }
 
        // Process buttons.
        if(message == "Idle") {
            // Toggle the state.
            integer i = llListFindList(MENU_TOGGLES,[ "idle" ]);
            integer s = !llList2Integer(MENU_TOGGLES_STATE, i);
            MENU_TOGGLES_STATE = llListReplaceList(
                MENU_TOGGLES_STATE,
                [ 
                    s
                ],
                i,
                i
            );
 
            message = "idle off";
            if(s)
                message = "idle off";
 
            // Send the message.
            llInstantMessage((key)wasKeyValueGet(
                    "corrade", 
                    configuration
                ),
                wasKeyValueEncode(
                    [
                        "command", "tell",
                        "group", wasURLEscape(
                            wasKeyValueGet(
                                "group", 
                                configuration
                            )
                        ),
                        "password", wasURLEscape(
                            wasKeyValueGet(
                                "password", 
                                configuration
 
                            )
                        ),
                        "message", wasURLEscape(message),
                        "entity", "local",
                        "type", "Normal",
                        "channel", wasKeyValueGet(
                            "wolf channel", 
                            configuration
                        )
                    ]
                )
            );
 
            jump remenuMain;
        }
 
@remenuMain;
 
        // Re-menu.
        llDialog(
            llGetOwner(), 
            menuText(), 
            wasDialogMenu(
                MAIN_MENU, 
                [], 
                ""
            ),
            menuChannel
        );
    }
    attach(key id) {
        llResetScript();
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || 
            (change & CHANGED_REGION_START) || 
            (change & CHANGED_OWNER) ||
            (change & CHANGED_REGION)) {
            llResetScript();
        }
    }
    state_exit() {
        llListenRemove(channelHandle);
    }
}
 
state wolf_actions {
    state_entry() {
        llListenRemove(channelHandle);
        channelHandle = llListen(menuChannel, "", llGetOwner(), "");
 
        llDialog(
            llGetOwner(), 
            menuText(),
            wasDialogMenu(
                WOLF_ACTIONS, 
                [
                    "⟵ Back", 
                    "↑ Top", 
                    "Next ⟶"
                ], 
                ""
            ),
            menuChannel
        );
    }
    touch_start(integer num) {
        llListenRemove(channelHandle);
        channelHandle = llListen(menuChannel, "", llGetOwner(), "");
 
        llDialog(
            llGetOwner(), 
            menuText(), 
            wasDialogMenu(
                WOLF_ACTIONS, 
                [
                    "⟵ Back", 
                    "↑ Top", 
                    "Next ⟶"
                ], 
                ""
            ),
            menuChannel
        );
    }
    listen(integer channel, string name, key id, string message) {
        // Process menu operations.
        if(message == "⟵ Back") {
            llDialog(
                llGetOwner(), 
                menuText(), 
                wasDialogMenu(
                    WOLF_ACTIONS, 
                    [
                        "⟵ Back", 
                        "↑ Top", 
                        "Next ⟶"
                    ], 
                    "<"
                ),
                menuChannel
            );
            return;
        }
        if(message == "Next ⟶") {
            llDialog(
                llGetOwner(), 
                menuText(), 
                wasDialogMenu(
                    WOLF_ACTIONS, 
                    [
                        "⟵ Back", 
                        "↑ Top", 
                        "Next ⟶"
                    ], 
                    ">"
                ),
                channel
            );
            return;
        }
        if(message == "↑ Top") {
            menuTop = TRUE;
            state main_menu;
            return;
        }
 
        // Send the message.
        llInstantMessage((key)wasKeyValueGet(
                "corrade", 
                configuration
            ),
            wasKeyValueEncode(
                [
                    "command", "tell",
                    "group", wasURLEscape(
                        wasKeyValueGet(
                            "group", 
                            configuration
                        )
                    ),
                    "password", wasURLEscape(
                        wasKeyValueGet(
                            "password", 
                            configuration
                        )
                    ),
                    "message", wasURLEscape(message),
                    "entity", "local",
                    "type", "Normal",
                    "channel", wasKeyValueGet(
                        "wolf channel", 
                        configuration
                    )
                ]
            )
        );
 
        // Re-menu.
        llDialog(
            llGetOwner(), 
                menuText(), 
                wasDialogMenu(
                    WOLF_ACTIONS, 
                    [
                        "⟵ Back", 
                        "↑ Top", 
                        "Next ⟶"
                    ], 
                    ""
                ),
            menuChannel
        );
    }
    attach(key id) {
        llResetScript();
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || 
            (change & CHANGED_REGION_START) || 
            (change & CHANGED_OWNER) ||
            (change & CHANGED_REGION)) {
            llResetScript();
        }
    }
    state_exit() {
        llListenRemove(channelHandle);
    }
}