Table of Contents

ChangeLog

29 December 2013

  • Full re-write of the trivia machine.

9 April 2012

28 November 2012

  • Scores now display with overhead text.
  • The Levenshtein distance is now used to allow for spelling mistakes as long as the answer is not a number and the Levehnstein distance is smaller than 2.
  • Added compatibility with OpenSim (kudos to Fernanda Antwerp for asking).

Introduction

The presented item is a trivia engine based on HamFon's original TriviaBot. This trivia script uses notecards as brain files with a specific syntax allowing it to be extended depending on the number of notecards you provide. A few notecards are supplied by HamFon and others. For a complete arsenal, download the full archive of text files from imatowns.com to supply to your trivia bot.

Features

Limitations

Setup

Place some of the notecards from the next section and name them accordingly in a primitive. After that, drop the script from this page into the prim. That's it, you should be ready to go. If memory is an issue for your SIM, simply set the primitive scripts to not running. To start again, set the scripts to running.

Notecards

The trivia brain files you place in the prim containing the trivia engine must be named after the pattern: Trivia_Brain_1 for the first notecard, Trivia_Brain_2 for the second notecard, Trivia_Brain_3 for the third notecard… Trivia_Brain_N for the Nth notecard.

FilenameFilesizeLast modified
trivia_brain_1.txt8.9 KiB2014/12/19 22:41
trivia_brain_10.txt4.9 KiB2014/12/19 22:41
trivia_brain_2.txt2.3 KiB2014/12/19 22:41
trivia_brain_3.txt4.8 KiB2014/12/19 22:41
trivia_brain_4.txt2.9 KiB2014/12/19 22:41
trivia_brain_5.txt5.1 KiB2014/12/19 22:41
trivia_brain_6.txt17.0 KiB2014/12/19 22:41
trivia_brain_7.txt4.0 KiB2014/12/19 22:41
trivia_brain_8.txt3.7 KiB2014/12/19 22:41
trivia_brain_9.txt5.3 KiB2014/12/19 22:41

Download, unpack and just paste the contents into a notecard making sure you follow the naming convention for the notecard brain files.

Notecard Syntax

If you are curious about the syntax, here is a description of the notecard syntax:

/<category>/<possible answer 1>/<possible answer 2>/.../<possible answer N>//<question>

where:

Example:

/Valentines/good luck//A white dove, also a symbol of Valentine's Day, symbolizes what?

where:

Code

This script was tested and works on OpenSim version 0.7.4!

trivia.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2012 - 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                              //
///////////////////////////////////////////////////////////////////////////
 
// The trivia machine pauses this number of seconds before revealing a 
// letter from the correct answer.
float TIME_PER_LETTER = 2;
 
///////////////////////////////////////////////////////////////////////////
//                              INTERNALS                                //
///////////////////////////////////////////////////////////////////////////
 
list cards = [];
string card = "";
integer cardIterator = 0;
integer lineIterator = 0;
 
list categories = [];
string selectedCategory = "";
string category = "";
string question = "";
string clue = "";
 
list store = [];
string topDisplay = "";
 
vector qC = ZERO_VECTOR;
 
list scores = [];
list names = [];
 
integer seconds = 0;
 
integer comHandle = 0;
integer shutdown = 10;
 
list mem = [];
integer memCacheHit = 0;
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasDualQuicksort(list a, list b) {
    if(llGetListLength(a) <= 1) return a+b;
 
    integer pivot_a = llList2Integer(a, 0);
    a = llDeleteSubList(a, 0, 0);
    string pivot_b = llList2String(b, 0);
    b = llDeleteSubList(b, 0, 0);
 
    list less = [];
    list less_b = [];
    list more = [];
    list more_b = [];
 
    do {
        if(llList2Integer(a, 0) > pivot_a) {
            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) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasLevenshteinDistance(string a, string b) {
    integer cost = 0;
    if(llGetSubString(a, 0, 0) != llGetSubString(b, 0, 0)) cost = 1;
    integer len_a = llStringLength(a);
    integer len_b = llStringLength(b);
    a = llDeleteSubString(a, 0, 0);
    b = llDeleteSubString(b, 0, 0);
    if(len_a == 0) return cost;
    if(len_b == 0) return cost;
    return cost + wasLevenshteinDistance(a, b);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasDialogMenu(list input, list actions, string direction) {
    integer cut = 11-wasListCountExclude(actions, [""]);
    integer wasMenuIndex = (integer)wasKeyValueGet("index", llGetObjectDesc());
    if(direction == ">" &&  (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) {
        ++wasMenuIndex;
        llSetObjectDesc(wasKeyValueSet("index", (string)wasMenuIndex, llGetObjectDesc()));
        jump slice;
    }
    if(direction == "<" && wasMenuIndex-1 >= 0) {
        --wasMenuIndex;
        llSetObjectDesc(wasKeyValueSet("index", (string)wasMenuIndex, llGetObjectDesc()));
        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);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueGet(string var, string kvp) {
    list dVars = llParseString2List(kvp, ["%"], []);
    do {
        list data = llParseString2List(llList2String(dVars, 0), ["="], []);
        string k = llList2String(data, 0);
        if(k != var) jump continue;
        return llList2String(data, 1);
@continue;
        dVars = llDeleteSubList(dVars, 0, 0);
    } while(llGetListLength(dVars));
    return "";
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueSet(string var, string val, string kvp) {
    list dVars = llParseString2List(kvp, ["%"], []);
    if(llGetListLength(dVars) == 0) return var + "=" + val;
    list result = [];
    do {
        list data = llParseString2List(llList2String(dVars, 0), ["="], []);
        string k = llList2String(data, 0);
        if(k == "") jump continue;
        if(k == var && val == "") jump continue;
        if(k == var) {
            result += k + "=" + val;
            val = "";
            jump continue;
        }
        string v = llList2String(data, 1);
        if(v == "") jump continue;
        result += k + "=" + v;
@continue;
        dVars = llDeleteSubList(dVars, 0, 0);
    } while(llGetListLength(dVars));
    if(val != "") result += var + "=" + val;
    return llDumpList2String(result, "%");
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueDelete(string var, string kvp) {
    list dVars = llParseString2List(kvp, ["%"], []);
    list result = [];
    list added = [];
    do {
        list data = llParseString2List(llList2String(dVars, 0), ["="], []);
        string k = llList2String(data, 0);
        if(k == var) jump continue;
        string v = llList2String(data, 1);
        if(v == "") jump continue;
        if(llListFindList(added, (list)k) != -1) jump continue;
        result += k + "=" + v;
        added += k;
@continue;
        dVars = llDeleteSubList(dVars, 0 ,0);
    } while(llGetListLength(dVars));
    return llDumpList2String(result, "%");
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list spinChars = [ "↑", "↗", "→", "↘", "↓", "↙", "←" ];
string spin() {
    string text = llList2String(llGetLinkPrimitiveParams(LINK_THIS, [PRIM_TEXT]), 0);
    do {
        string tok = llGetSubString(text, llStringLength(text)-1, llStringLength(text)-1);
        if(!~llListFindList(spinChars, (list)tok) && tok != "\n" && tok != "[" && tok != "]") jump show;
        text = llDeleteSubString(text, llStringLength(text)-1, llStringLength(text)-1);
    } while(llStringLength(text));
@show;
    string next = llList2String(spinChars,  0);
    spinChars = llDeleteSubList(spinChars, 0, 0);
    spinChars += next;
    return "[" + next + "]";
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasProgress(integer percent, integer length, list symbols) {
    percent /= (integer)((float)100.0/(length));
    string p = llList2String(symbols,0);
    integer itra = 0;
    do {
        if(itra>percent-1) p += llList2String(symbols,2);
        else p += llList2String(symbols,1);
    } while(++itra<length);
    return p + llList2String(symbols,3);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
vector wasPercentToGradient(float percent, string rgb) {
    if(llStringLength(rgb) != 2) return ZERO_VECTOR;
    string a = llGetSubString(rgb, 0, 0);
    string b = llGetSubString(rgb, 1, 1);
    list col = [ "r", "g", "b" ];
    integer ax = llListFindList(col, (list)a);
    integer bx = llListFindList(col, (list)b);
    if(ax == -1 || bx == -1) return ZERO_VECTOR;
    col = llListReplaceList(col, (list)((100-percent)/100), ax, ax);
    col = llListReplaceList(col, (list)(percent/100), bx, bx);
    return 2*<llList2Float(col, 0), llList2Float(col, 1), llList2Float(col, 2)>;
}
 
default {
    state_entry() {
        llSetObjectDesc(wasKeyValueSet("index", "0", llGetObjectDesc()));
        llSetTextureAnim(ANIM_ON | SMOOTH | LOOP, ALL_SIDES,1,1,1,1,0.1);
        llSetText("-=:[ Please wait, reading brains... ]:=-\n", <1,1,0>, 1);
        integer i = llGetInventoryNumber(INVENTORY_NOTECARD)-1;
        do {
            list bCard = llParseString2List(llGetInventoryName(INVENTORY_NOTECARD, i), ["_"], [""]);
            if(llList2String(bCard, 0) != "Trivia" || llList2String(bCard, 1) != "Brain") jump continue;
            cards += llGetInventoryName(INVENTORY_NOTECARD, i);
@continue;
        } while(--i>-1);
        if(llGetListLength(cards) != 0) state categorize;
        llSay(DEBUG_CHANNEL, "Failed to find any Trivia brains.");
    }
    on_rez(integer param) {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) llResetScript();
    }
}
 
state categorize {
    state_entry() {
        cardIterator = llGetListLength(cards)-1;
        card = llList2String(cards, cardIterator);
        llSetText("-=:[ Please wait, categorizing brains... ]:=-\nBrain: " + card + " " + spin(), <1,1,0>, 1);
        lineIterator = 0;
        llGetNotecardLine(card, lineIterator);
    }
    dataserver(key id, string data) {
        if(data == EOF) {
            if(cardIterator-- == 0) state start;
            card = llList2String(cards, cardIterator);
            lineIterator = 0;
            llGetNotecardLine(card, lineIterator);
            return;
        }
        if(data == "") jump continue;
        string category = llList2String(
                            llParseString2List(
                                llList2String(
                                    llParseString2List(data, ["//"], [""]), 0), ["/"], [""]), 0);
        if(llListFindList(categories, (list)category) != -1) jump continue;
        categories += category;
@continue;
        llSetText("-=:[ Please wait, categorizing brains... ]:=-\nBrain: " + card + " " + spin(), <1,1,0>, 1);
        llGetNotecardLine(card, ++lineIterator);
    }
 
}
 
state start {
    state_entry() {
        qC = <llFrand(1), llFrand(1), llFrand(1)>;
        llParticleSystem([PSYS_SRC_TEXTURE,llGetInventoryName(0,0),PSYS_PART_START_SCALE,<.2,.3,0>,6,<.2,.3,0>,PSYS_PART_START_COLOR,
qC,PSYS_PART_END_COLOR,qC,PSYS_PART_START_ALPHA,.05,PSYS_PART_END_ALPHA,.05,PSYS_SRC_BURST_PART_COUNT,0x7FFFFFFF,PSYS_PART_MAX_AGE,1,PSYS_SRC_MAX_AGE,0,PSYS_SRC_PATTERN,(integer)4,PSYS_SRC_BURST_SPEED_MIN,0,PSYS_SRC_BURST_SPEED_MAX,0,PSYS_SRC_ANGLE_BEGIN,
(float)0.03*3.141593,PSYS_SRC_ANGLE_END,(float)0*3.141593,8,
ZERO_VECTOR,PSYS_PART_FLAGS,(integer)(0|1|PSYS_PART_EMISSIVE_MASK)]);
        llSetText("-=:[ Touch to Start Trivia ]:=-", <1,1,0>, 1);
    }
    touch_start(integer num) {
        llSetText("", <0,0,0>, 1);
        // only for owner
        if(llDetectedKey(0) != llGetOwner()) state proximity;
        // fallthrough user
        state configure;
    }
    on_rez(integer param) {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) llResetScript();
    }
}
 
state proximity {
    state_entry() {
        qC = <llFrand(1), llFrand(1), llFrand(1)>;
        llParticleSystem([PSYS_SRC_TEXTURE,llGetInventoryName(0,0),PSYS_PART_START_SCALE,<.2,.3,0>,6,<.2,.3,0>,PSYS_PART_START_COLOR,
qC,PSYS_PART_END_COLOR,qC,PSYS_PART_START_ALPHA,.05,PSYS_PART_END_ALPHA,.05,PSYS_SRC_BURST_PART_COUNT,0x7FFFFFFF,PSYS_PART_MAX_AGE,1,PSYS_SRC_MAX_AGE,0,PSYS_SRC_PATTERN,(integer)4,PSYS_SRC_BURST_SPEED_MIN,0,PSYS_SRC_BURST_SPEED_MAX,0,PSYS_SRC_ANGLE_BEGIN,
(float)0.03*3.141593,PSYS_SRC_ANGLE_END,(float)0*3.141593,8,
ZERO_VECTOR,PSYS_PART_FLAGS,(integer)(0|1|PSYS_PART_EMISSIVE_MASK)]);
        shutdown = 10;
        llSensorRepeat("", "", AGENT, 64, TWO_PI, 1);
    }
    touch_start(integer num) {
        // abort, only for owner
        if(llDetectedKey(0) != llGetOwner()) return;
        state configure;
    }
    sensor(integer num) {
        shutdown = 10;
        state select;
    }
    no_sensor() {
        if(--shutdown > 0) return;
        state start;
    }
}
 
state select {
    state_entry() {
        if(cardIterator < 0) cardIterator = llGetListLength(cards)-1;
        card = llList2String(cards, cardIterator);
        llGetNumberOfNotecardLines(card);
    }
    touch_start(integer num) {
        // abort, only for owner
        if(llDetectedKey(0) != llGetOwner()) return;
        state configure;
    }
    dataserver(key id, string data) {
        lineIterator = (integer)data-1;
        state search;
    }
    on_rez(integer param) {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) llResetScript();
    }
}
 
state search {
    state_entry() {
        card = llList2String(cards, cardIterator);
        llGetNotecardLine(card, lineIterator);
    }
    touch_start(integer num) {
        // abort, only for owner
        if(llDetectedKey(0) != llGetOwner()) return;
        state configure;
    }
    dataserver(key id, string data) {
        if(data == EOF) {
            --cardIterator;
            state select;
        }
        if(data == "") jump continue;
        list qaSplit = llParseString2List(data, ["//"], [""]);
        list caSplit = llParseString2List(llList2String(qaSplit, 0), ["/"], [""]);
        string selectedCategory = wasKeyValueGet("category", llGetObjectDesc());
        if(selectedCategory != "" && selectedCategory != llList2String(caSplit, 0)) jump continue;
        category = llList2String(caSplit, 0);
        question = llList2String(qaSplit, 1);
        caSplit = llDeleteSubList(caSplit, 0, 0);
        string head = llList2String(caSplit, 0);
        if(llGetFreeMemory() < 2048) {
            memCacheHit = 0;
            mem = [];
        }
        if(llListFindList(mem, (list)head) != -1) {
            if(++memCacheHit > 4) state start;
            jump continue;
        }
        memCacheHit = 0;
        integer s = llStringLength(head)-1;
        clue = "";
        do {
            clue += "-";
        } while(--s>-1);
        mem += [ head ];
        store = [ head ];
        do {
            store += llList2String(caSplit, 0);
            caSplit = llDeleteSubList(caSplit, 0, 0);
        } while(llGetListLength(caSplit) != 0);
        state ask;
@continue;
        string text = "-=:[ Searching question... ]:=-\n" + "Brain: " + card + " " + spin();
        if(llStringLength(topDisplay) != 0) text += "\n\n\n-=:[ Top ]:=-\n" + topDisplay;
        llSetText(text, <1,1,0>, 1);
        llGetNotecardLine(card, --lineIterator);
    }
    on_rez(integer param) {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) llResetScript();
    }
}
 
state ask {
    state_entry() {
        comHandle = llListen(0, "", "", "");
        llSay(0, "[" + category + "]: " + question);
        seconds = llStringLength(clue);
        llSetTimerEvent(TIME_PER_LETTER);
    }
    touch_start(integer num) {
        // abort, only for owner
        if(llDetectedKey(0) != llGetOwner()) return;
        state configure;
    }
    listen(integer chan,string name,key id,string mes) {
        // if it's a number, we want a perfect match
        if((integer)mes > 0 && llListFindList(store, (list)llToLower(mes)) == -1) return;
        integer i = llGetListLength(store)-1;
        do {
            string answer = llList2String(store, i);
            if(llStringLength(answer) != llStringLength(mes)) jump continue;
            if(wasLevenshteinDistance(llToLower(answer), llToLower(mes)) >= 2) jump continue;
            // lock
            llListenRemove(comHandle);
            llSetTimerEvent(0);
            jump correct;
@continue;
        } while(--i>-1);
        return;
@correct;
        llSay(0, name + "'s answer is correct! " + llList2String(store, 0) + ", is the correct answer!");
        integer idx = llListFindList(names, (list)name);
        if(idx == -1) {
            names = llListInsertList(names, (list)name, 0);
            scores = llListInsertList(scores, (list)1, 0);
            jump compute;
        }
        integer score = llList2Integer(scores, idx)+1;
        scores=llListReplaceList(scores, (list)score, idx, idx);
@compute;
        list sort = wasDualQuicksort(scores, names);
        topDisplay = "";
        integer count = 0;
        do {
            topDisplay += llList2String(sort, 1);
            topDisplay += " -> ";
            topDisplay += llList2String(sort, 0);
            topDisplay += "\n";
            sort = llDeleteSubList(sort, 0, 0);
            sort = llDeleteSubList(sort, 0, 0);
            if(++count==5) jump show;
        } while(llGetListLength(sort) != 0);
@show;
        llSetText("-=:[ Top ]:=-\n" + topDisplay, <1,1,0>, 1);
        state proximity;
    }
    timer() {
        llParticleSystem([
           PSYS_SRC_TEXTURE, llGetInventoryName(INVENTORY_TEXTURE, 0), 
           PSYS_PART_START_SCALE, <.01, .014, 0>, PSYS_PART_END_SCALE, <.1, .14, 0>, 
           PSYS_PART_START_COLOR, qC,    PSYS_PART_END_COLOR, <.5 + qC.x,.5 + qC.y, .5 + qC.z>,
           PSYS_SRC_BURST_PART_COUNT, 0x7FFFFFFF,
           PSYS_PART_MAX_AGE, 1.25,
           PSYS_SRC_MAX_AGE, 1.25,
           PSYS_SRC_PATTERN, 8,
           PSYS_SRC_BURST_SPEED_MIN, (float)1,   PSYS_SRC_BURST_SPEED_MAX, (float)1.2, 
           PSYS_SRC_ANGLE_BEGIN, 0,    PSYS_SRC_ANGLE_END, TWO_PI,       
           PSYS_PART_FLAGS, ( 0 | PSYS_PART_INTERP_SCALE_MASK
                                | PSYS_PART_INTERP_COLOR_MASK    
                                | PSYS_PART_EMISSIVE_MASK     
                            )                  
        ]);
        string answer = llList2String(store, 0);
        integer i = (integer)llFrand(llStringLength(answer));
        do {
            --i;
            if(i < 0) i = llStringLength(answer);
        } while(llGetSubString(clue, i, i) != "-");
        clue = llDeleteSubString(clue, i, i);
        clue = llInsertString(clue, i, llGetSubString(answer, i, i));
        string text = "Clue: " + "[" + clue +  "]";
        integer progress;
        i = llStringLength(clue)-1;
        do {
            if(llGetSubString(answer, i, i) != "-") ++progress;
        } while(--i>-1);
        if(llStringLength(topDisplay) != 0) text += "\n\n\n-=:[ Top ]:=-\n" + topDisplay;
        llSetText(text, wasPercentToGradient(100*progress/llStringLength(clue), "rg"), 1.0);
        if(--seconds == 0) {
            llSay(0, "A correct answer would have been: " + answer + ".");
            state proximity;
        }
    }
    on_rez(integer param) {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) llResetScript();
    }
}
 
state configure {
    state_entry() {
        qC = <llFrand(1), llFrand(1), llFrand(1)>;
        llParticleSystem([PSYS_SRC_TEXTURE,llGetInventoryName(0,0),PSYS_PART_START_SCALE,<.2,.3,0>,6,<.2,.3,0>,PSYS_PART_START_COLOR,
qC,PSYS_PART_END_COLOR,qC,PSYS_PART_START_ALPHA,.05,PSYS_PART_END_ALPHA,.05,PSYS_SRC_BURST_PART_COUNT,0x7FFFFFFF,PSYS_PART_MAX_AGE,1,PSYS_SRC_MAX_AGE,0,PSYS_SRC_PATTERN,(integer)4,PSYS_SRC_BURST_SPEED_MIN,0,PSYS_SRC_BURST_SPEED_MAX,0,PSYS_SRC_ANGLE_BEGIN,
(float)0.03*3.141593,PSYS_SRC_ANGLE_END,(float)0*3.141593,8,
ZERO_VECTOR,PSYS_PART_FLAGS,(integer)(0|1|PSYS_PART_EMISSIVE_MASK)]);
        llSetText("-=:[ Configuring... ]:=-", <1,1,0>, 1);
        integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
        llListen(comChannel, "", llGetOwner(), "");
        llDialog(llGetOwner(), "Select a category for the Trivia machine from the following options:\n", wasDialogMenu(categories, ["⟵ Back", "⦡ Any", "Next ⟶", "◯ STOP", "", "◉ START"], ""), comChannel);
        seconds = 30;
        llSetTimerEvent(1);
    }
    touch_start(integer num) {
        if(llDetectedKey(0) != llGetOwner()) return;
        llSetText("-=:[ Configuring... ]:=-", <1,1,0>, 1);
        integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
        llListen(comChannel, "", llGetOwner(), "");
        llDialog(llGetOwner(), "Select a category for the Trivia machine from the following options:\n", wasDialogMenu(categories, ["⟵ Back", "⦡ Any", "Next ⟶", "◯ STOP", "", "◉ START"], ""), comChannel);
        seconds = 30;
        llSetTimerEvent(1);
    }
    listen(integer channel, string name, key id, string message) {
        if(message == "◉ START") state proximity;
        if(message == "◯ STOP") state start;
        seconds = 30;
        if(message == "⟵ Back") {
            llDialog(id, "Select a category for the Trivia machine from the following options:\n", wasDialogMenu(categories, ["⟵ Back", "⦡ Any", "Next ⟶", "◯ STOP", "", "◉ START"], "<"), channel);
            return;
        }
        if(message == "Next ⟶") {
            llDialog(id, "Select a category for the Trivia machine from the following options:\n", wasDialogMenu(categories, ["⟵ Back", "⦡ Any", "Next ⟶", "◯ STOP", "", "◉ START"], ">"), channel);
            return;
        }
        if(message == "⦡ Any") {
            llSetObjectDesc(wasKeyValueDelete("category", llGetObjectDesc()));
            llDialog(id, "Select a category for the Trivia machine from the following options:\n", wasDialogMenu(categories, ["⟵ Back", "⦡ Any", "Next ⟶", "◯ STOP", "", "◉ START"], ""), channel);
            return;
        }
        llSetObjectDesc(wasKeyValueSet("category", message, llGetObjectDesc()));
        llDialog(id, "Select a category for the Trivia machine from the following options:\n", wasDialogMenu(categories, ["⟵ Back", "⦡ Any", "Next ⟶", "◯ STOP", "", "◉ START"], ""), channel);
    }
    timer() {
        if(seconds <= 0) state start;
        integer progress = 100*(seconds--)/30;
        string text = "-=:[ Configuring... ]:=-\n" + wasProgress(progress, 10, ["[", "#", "o", "]"]);
        if(llStringLength(topDisplay) != 0) text += "\n\n-=:[ Top ]:=-\n" + topDisplay;
        llSetText(text, wasPercentToGradient(progress, "rg"), 1);
    }
    on_rez(integer param) {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) llResetScript();
    }
}