About

The following item is a scripted colorable and animated hairbrush that will appear periodically given a configurable persistent delay and make the avatar brush their hair. The item is designed to be worn at all times, perhaps as part of an outfit in order to potentially make idling avatars more dynamic.

Screenshots

Usage

  • attach the item to the avatar (wear / add),
  • wait for a number of seconds (configurable),
  • the hairbrush appears,
  • the avatar starts a sequence of brushing their hair,
  • the hairbrush disappears,
  • the avatar waits for an amount of time before the cycle repeats (configurable)

In order to configure the various parameters, simply touch the hairbrush and follow the popup dialogs.

Design

The hairbrush consists of two parts:

  • the hairbrush,
  • an invisible primitive that is used as persistent storage

Animations

Scripts

There are two scripts involved in this build:

  • one scripts is responsible for the animation,
  • the other script is a fork off the color changer script and allows the user to set the hairbrush color as well as configure a time delay between animations

Sequence Animate

The sequence animate script is a script that is designed to sequentially play animations based on the time that each animation takes to complete a full cycle. For instance, given the disposition of the hairbrush inventory items:

  • Brush_1,
  • Brush_4,
  • Brush_25,
  • Brush_30

the animations are sorted in increasing order via their name (the left-hand side of the _ marker) and then each animation is played alphabetically for the number of seconds on the right-hand side of the _ marker.

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2022 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//         Ord() function, written by Pedro Oval, 2010-05-28             //
///////////////////////////////////////////////////////////////////////////
integer Ord(string chr) {
    if (chr == "") return 0;
    string hex = llEscapeURL(chr);
    if (llGetSubString(hex, 0, 0) != "%") 
        return llBase64ToInteger("AAAA" + 
            llStringToBase64(llGetSubString(chr, 0, 0)));
    integer b = (integer)("0x" + llGetSubString(hex, 1, 2));
    if (b < 194 || b > 244) return b;
    if (b < 224) return ((b & 0x1F) << 6) | 
        (integer)("0x" + llGetSubString(hex, 4, 5)) & 0x3F;
    if (b < 240) return (b & 0x0F) << 12 + 
        ((integer)("0x" + llGetSubString(hex, 4, 5)) & 0x3F) << 6 + 
        (integer)("0x" + llGetSubString(hex, 7, 8)) & 0x3F;
    return (b & 0x07) << 18 + 
        ((integer)("0x" + llGetSubString(hex, 4, 5)) & 0x3F) << 12 + 
        ((integer)("0x" + llGetSubString(hex, 7, 8)) & 0x3F) << 6 + 
        (integer)("0x" + llGetSubString(hex, 10, 11)) & 0x3F;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasOrdCompare(string A, string B) {
    if(A == "" && B != "") return 1;
    if(A != "" && B == "") return -1;
    if(A == "" && B == "") return 0;
 
    integer i = Ord(llToUpper(llGetSubString(A, 0, 0)));
    integer j = Ord(llToUpper(llGetSubString(B, 0, 0)));
 
    if(i < j) return 1;
    if(i > j) return -1;
    if(i == j) return 0;
 
    return wasOrdCompare(
        llGetSubString(A, 1, -1), 
        llGetSubString(B, 1, -1)
    );
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasDualQuicksort(list a, list b) {
    if(llGetListLength(a) <= 1) return a+b;
 
    list pivot_a = llList2List(a, 0, 0);
    a = llDeleteSubList(a, 0, 0);
    list pivot_b = llList2List(b, 0, 0);
    b = llDeleteSubList(b, 0, 0);
 
    list less = [];
    list less_b = [];
    list more = [];
    list more_b = [];
 
    do {
        if(wasOrdCompare(llList2String(a, 0), llList2String(pivot_a, 0)) >= 0) {
            less += llList2List(a, 0, 0);
            less_b += llList2List(b, 0, 0);
            jump continue;
        }
        more += llList2List(a, 0, 0);
        more_b += llList2List(b, 0, 0);
@continue;
        a = llDeleteSubList(a, 0, 0);
        b = llDeleteSubList(b, 0, 0);
    } while(llGetListLength(a));
 
    return wasDualQuicksort(less, less_b) + 
        pivot_a + 
        pivot_b + 
        wasDualQuicksort(more, more_b);
}
 
///////////////////////////////////////////////////////////////////////////
//    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) 2014 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueSet(string k, string v, string data) {
    if(llStringLength(k) == 0) return "";
    if(llStringLength(v) == 0) return "";
    if(llStringLength(data) == 0) return k + "=" + v;
    integer i = llListFindList(
        llList2ListStrided(
            llParseString2List(data, ["&", "="], []), 
            0, -1, 2
        ), 
    [ k ]);
    if(i != -1) return llDumpList2String(
        llListReplaceList(
            llParseString2List(data, ["&"], []), 
            [ k + "=" + v ], 
        i, i), 
    "&");
    return data + "&" + k + "=" + v;
}
 
string SEPARATOR = "_";
 
list animations;
string animation;
list queue;
key owner;
integer intermission;
key script;
string exception;
 
default {
    state_entry() {
        script = llGetInventoryKey(
            llGetScriptName()
        );
 
        owner = llGetOwner();
 
        integer i = llGetInventoryNumber(INVENTORY_ANIMATION) - 1;
        list name;
        list time;
        do {
            list l = llParseString2List(
                llGetInventoryName(
                    INVENTORY_ANIMATION, 
                    i
                ),
                [ SEPARATOR ],
                []
            );
 
            name += llList2String(l, 0);
            time += llList2Integer(l, 1);
        } while(--i > -1);
 
        animations = wasDualQuicksort(name, time);
 
        llRequestPermissions(
            owner,
            PERMISSION_TRIGGER_ANIMATION
        );
    }
 
    run_time_permissions(integer perm) {
        if(perm & PERMISSION_TRIGGER_ANIMATION) {
            queue = animations;
            do {
                string name = llList2String(queue, 0);
                queue = llDeleteSubList(queue, 0, 0);
                integer time = llList2Integer(queue, 0);
                queue = llDeleteSubList(queue, 0, 0);
 
                animation = name + SEPARATOR + (string)time;
 
                llStopAnimation(animation);
            } while(queue);
 
            state pause;
        }
    }
 
    changed(integer change) {
        if(change & CHANGED_INVENTORY) {
            llResetScript();
        }
    }
 
    attach(key id) {
        llResetScript();
    }
}
 
state pause {
    state_entry() {
        llSetAlpha(0, ALL_SIDES);
 
        intermission = (integer)wasKeyValueGet(
            "intermission", 
            llList2String(
                llGetLinkPrimitiveParams(
                    2, 
                    [ 
                        PRIM_DESC 
                    ]
                ),
                0
            )
        );
        if(intermission == 0) {
            intermission = 120;
            llSetLinkPrimitiveParamsFast(
                2,
                [
                    PRIM_DESC,
                    wasKeyValueSet(
                        "intermission", 
                        "120", 
                        ""
                    )
                ]
            );
        }
 
        llSetTimerEvent(
            1 + 
            llFrand(intermission)
        );
    }
 
    timer() {
        queue = animations;
        state animate;
    }
 
    changed(integer change) {
        if(change & CHANGED_INVENTORY) {
            llResetScript();
        }
    }
 
    attach(key id) {
        llResetScript();
    }
 
    state_exit() {
        llSetTimerEvent(0);
    }
}
 
state animate_trampoline {
    state_entry() {
        llRequestPermissions(
            owner,
            PERMISSION_TRIGGER_ANIMATION
        );
    }
 
    run_time_permissions(integer perm) {
        if(perm & PERMISSION_TRIGGER_ANIMATION) {
            llStopAnimation(animation);
        }
 
        if(queue) {
            state animate;
        }
 
        state pause;
    }
 
    changed(integer change) {
        if(change & CHANGED_INVENTORY) {
            llResetScript();
        }
    }
 
    attach(key id) {
        llResetScript();
    }
}
 
state animate {
    state_entry() {
        llSetAlpha(1, ALL_SIDES);
        llRequestPermissions(
            owner,
            PERMISSION_TRIGGER_ANIMATION
        );
    }
 
    run_time_permissions(integer perm) {
        if(perm & PERMISSION_TRIGGER_ANIMATION) {
            string name = llList2String(queue, 0);
            queue = llDeleteSubList(queue, 0, 0);
            integer time = llList2Integer(queue, 0);
            queue = llDeleteSubList(queue, 0, 0);
 
            animation = name + SEPARATOR + (string)time;
 
            llStartAnimation(animation);
            llSetTimerEvent(time);
        }
    }
 
    timer() {
        state animate_trampoline;
    }
 
    changed(integer change) {
        if(change & CHANGED_INVENTORY) {
            llResetScript();
        }
    }
 
    attach(key id) {
        llResetScript();
    }
 
    state_exit() {
        llSetTimerEvent(0);
    }
}
 
state fault {
    state_entry() {
        llOwnerSay(exception);
    }
 
    changed(integer change) {
        if(change & CHANGED_LINK) {
            llResetScript();
        }
    }
 
    attach(key id) {
        llResetScript();
    }
 
    on_rez(integer param) { 
        llResetScript(); 
    }
}

The menu script is a derivative of the color changer script with the added ability to configure the pause between each animation sequence.

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2022 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2022 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
vector wasVectorClamp(vector v, float min, float max) {
    return
        <
            llListStatistics(
                LIST_STAT_MAX,
                [
                    llListStatistics(
                        LIST_STAT_MIN,
                        [
                            v.x,
                            max
                        ]
                    ),
                    min
                ]
            ),
            llListStatistics(
                LIST_STAT_MAX,
                [
                    llListStatistics(
                        LIST_STAT_MIN,
                        [
                            v.y,
                            max
                        ]
                    ),
                    min
                ]
            ),
            llListStatistics(
                LIST_STAT_MAX,
                [
                    llListStatistics(
                        LIST_STAT_MIN,
                        [
                            v.z,
                            max
                        ]
                    ),
                    min
                ]
            )
        >;
}
 
///////////////////////////////////////////////////////////////////////////
//    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) 2014 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueSet(string k, string v, string data) {
    if(llStringLength(k) == 0) return "";
    if(llStringLength(v) == 0) return "";
    if(llStringLength(data) == 0) return k + "=" + v;
    integer i = llListFindList(
        llList2ListStrided(
            llParseString2List(data, ["&", "="], []), 
            0, -1, 2
        ), 
    [ k ]);
    if(i != -1) return llDumpList2String(
        llListReplaceList(
            llParseString2List(data, ["&"], []), 
            [ k + "=" + v ], 
        i, i), 
    "&");
    return data + "&" + k + "=" + v;
}
 
key owner;
integer link;
list links;
integer channel;
float value = .5;
key script;
string exception;
integer intermission;
 
default {
    state_entry() {
        script = llGetInventoryKey(llGetScriptName());
        owner = llGetOwner();
        channel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
 
        links = [];
        integer i = llGetNumberOfPrims();
        do { 
            links += (string)i; 
        } while(--i > 0);
 
        if(llGetListLength(links) == 1) {
            links = [ 0 ];
        }
        link = llList2Integer(links, 0);
 
        state wait;
    }
 
    attach(key id) {
        llResetScript();
    }
 
    on_rez(integer param) { 
        llResetScript(); 
    }
}
 
state wait {
    touch_start(integer num) {
        if(llDetectedKey(0) != owner) {
            return;
        }
 
        state choose;
    }
 
    attach(key id) {
        llResetScript();
    }
 
    on_rez(integer param) { 
        llResetScript(); 
    }
}
 
state choose_trampoline {
    state_entry() {
        state choose;
    }
}
 
state choose {
    state_entry() {
        vector color = llList2Vector(
            llGetLinkPrimitiveParams(
                link, 
                [
                    PRIM_COLOR, 
                    ALL_SIDES
                ]
            ),
            0
        );
 
        intermission = (integer)wasKeyValueGet(
            "intermission", 
            llList2String(
                llGetLinkPrimitiveParams(
                    2, 
                    [ 
                        PRIM_DESC 
                    ]
                ),
                0
            )
        );
        if(intermission == 0) {
            intermission = 120;
            llSetLinkPrimitiveParamsFast(
                2,
                [
                    PRIM_DESC,
                    wasKeyValueSet(
                        "intermission", 
                        "120", 
                        ""
                    )
                ]
            );
        }
 
        llListen(channel, "", owner, "");
 
        llDialog(
            owner, 
            "Brush © 2022, Wizardry and Steamworks\n--------------------------------------\nPause: " + (string)intermission + "    \nColor: " + (string)color + "          \n",
            [
                "Pause",
                "Color",
                "⏏ Exit"
 
            ], 
            channel
        );
    }
 
    touch_start(integer num) {
        state choose_trampoline;
    }
 
    listen(integer channel, string name, key owner, string message) {
        if(message == "⏏ Exit") {
            state wait;
        }
        if(message == "Pause") {
            state timing;
        }
        if(message == "Color") {
            state colorize;
        }
    }
 
    attach(key id) {
        llResetScript();
    }
 
    on_rez(integer param) { 
        llResetScript(); 
    }
}
 
state colorize {    
    state_entry() {
        llListen(channel, "", owner, "");
 
        list l = llGetLinkPrimitiveParams(
            link, 
            [
                PRIM_COLOR, 
                ALL_SIDES
            ]
        );
 
        vector color = llList2Vector(l, 0);
 
        llDialog(
            owner, 
            "Brush © 2022, Wizardry and Steamworks\n--------------------------------------\nModify: " + (string)value + "            \nColor: " + (string)color + "             \n",
            [
                "R ▼", 
                "G ▼", 
                "B ▼", 
                "R ▲", 
                "G ▲", 
                "B ▲",
                "⏏ Exit",
                "↑",
                "↓"
            ], 
            channel
        );
    }
 
    touch_start(integer num) {
        if(llDetectedKey(0) != owner) {
            return;
        }
 
        state choose;
    }
 
    listen(integer channel, string name, key owner, string message) {
        if(message == "⏏ Exit") {
            state choose;
        }
 
        list l = llGetLinkPrimitiveParams(
            link, 
            [
                PRIM_COLOR, 
                ALL_SIDES
            ]
        );
 
        vector color = llList2Vector(l, 0);
        float alpha = llList2Float(l, 1);
 
        if(message == "↑") {
            value += .05;
            jump menu;
        }
 
        if(message == "↓") {
            value -= .05;
            jump menu;
        }
 
        integer i = llListFindList(
            [
                "R ▼", 
                "G ▼", 
                "B ▼", 
                "R ▲", 
                "G ▲", 
                "B ▲"
            ], 
            [ 
                message 
            ]
        );
        if(i != -1) {
            list l = llParseString2List(
                llList2String(
                    [
                        "R ▼", 
                        "G ▼", 
                        "B ▼", 
                        "R ▲", 
                        "G ▲", 
                        "B ▲"
                    ], 
                    i
                ),
                [" "],
                []
            );
 
            color +=
                // sign
                (float)(
                    llList2String(
                        [ "-", "+" ], 
                        "▲" == llList2String(l, 1)
                    ) + "1"
                ) *
                // value
                llList2Vector(
                    [ 
                        llList2Vector(
                            [
                                llList2Vector(
                                    [ 
                                        ZERO_VECTOR,
                                        <0, 0, value>
                                    ], 
                                    llList2String(l, 0) == "B"
                                ),
                                <0, value, 0>
                            ], 
                            llList2String(l, 0) == "G"
                        ),
                        <value, 0, 0>
                    ], 
                    llList2String(l, 0) == "R"
                );
 
            jump menu;
        }
 
@menu;
 
        color = wasVectorClamp(
            color, 
            0, 
            1
        );
 
        llSetLinkPrimitiveParamsFast(
            link, 
            [
                PRIM_COLOR, 
                ALL_SIDES, 
                color, 
                alpha
            ]
        );
 
        llDialog(
            owner, 
            "Brush © 2022, Wizardry and Steamworks\n--------------------------------------\nModify: " + (string)value + "            \nColor: " + (string)color + "             \n",
            [
                "R ▼", 
                "G ▼", 
                "B ▼", 
                "R ▲", 
                "G ▲", 
                "B ▲",
                "⏏ Exit",
                "↑",
                "↓"
            ], 
            channel
        );
    }
 
    attach(key id) {
        llResetScript();
    }
 
    on_rez(integer param) { 
        llResetScript(); 
    }
}
 
state timing {
    state_entry() {
        llListen(channel, "", owner, "");
 
        intermission = (integer)wasKeyValueGet(
            "intermission",
            llList2String(
                llGetLinkPrimitiveParams(
                    2, 
                    [ 
                        PRIM_DESC 
                    ]
                ),
                0
            )
        );
 
        llDialog(
            owner, 
            "Brush © 2022, Wizardry and Steamworks\n--------------------------------------\nPause: " + (string)intermission + "   \n",
            [
                "↑",
                "↓",
                "⏏ Exit"
 
            ], 
            channel
        );   
    }
 
    touch_start(integer num) {
        if(llDetectedKey(0) != owner) {
            return;
        }
 
        state choose;
    }
 
    listen(integer channel, string name, key owner, string message) {
        if(message == "⏏ Exit") {
            state choose;
        }
 
        intermission +=
            llList2Integer(
                [
                    "-5",
                    "5"
                ],
                message == "↑"
            );
 
        llSetLinkPrimitiveParamsFast(
            2,
            [
                PRIM_DESC,
                wasKeyValueSet(
                    "intermission", 
                    (string)intermission, 
                    llList2String(
                        llGetLinkPrimitiveParams(
                            2, 
                            [ 
                                PRIM_DESC 
                            ]
                        ),
                        0
                    )
                )
            ]
        );
 
        llDialog(
            owner, 
            "Brush © 2022, Wizardry and Steamworks\n--------------------------------------\nPause: " + (string)intermission + "   \n",
            [
                "↑",
                "↓",
                "⏏ Exit"
 
            ], 
            channel
        );   
 
    }
 
    attach(key id) {
        llResetScript();
    }
 
    on_rez(integer param) { 
        llResetScript(); 
    }
}
 
state fault {
    state_entry() {
        llOwnerSay(exception);
    }
 
    attach(key id) {
        llResetScript();
    }
 
    on_rez(integer param) { 
        llResetScript(); 
    }
}

Technologies Used

  • storing and retrieving configuration parameters is performed using key-value pairs

secondlife/hairbrush.txt · Last modified: 2022/12/31 22:30 by office

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.