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.
In order to configure the various parameters, simply touch the hairbrush and follow the popup dialogs.
The hairbrush consists of two parts:
There are two scripts involved in this build:
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(); } }