// IF YOU VALUE THIS WORK, PLEASE LEAVE ATTRIBUTION INTACT // // // // _|_| // // _| _| _|_|_|_| _|_| _|_|_| _|_| // // _| _| _| _| _| _| _| _|_|_|_| // // _| _| _| _| _| _| _| _| // // _|_| _|_|_|_| _|_| _| _| _|_|_| // // MiniVerse // //............................................................// //.....................▒▓▓▒▒▒▒▓▓▓▓▓▓▒▒▒▓▓░....................// //.................._▒▓▒.Ozone.MiniVerse.▒▓▒_.................// //................_▒▓▓▓▒____PRESENTS____▒▓▓▓▓▓▒_..............// //..............░▓▓▓▓▓▓▒___I.M.A.G.E.___▒▓▓▓▓▓▓▓░.............// //.............▒▓▓▓▓▓▓▓▒__IMAGE_MATRIX__▒▓▓▓▓▓▓▓▓▒............// //............▒▓▒▓▓▓▓▒_ACTION_GAME_ENGINE_▒▓▓▓▓▒▓▓▒...........// //..........▒▓▓▓▓░...▒▓▓▒_By_Spax_Orion_▒▓▓▒...░▓▓▓▓▒.........// //.........▒▓▓▓▓░......▒▓_& Dirty Helga_▓▓▒░.....░▒▓▓▓........// //.........▓▓▓▓▒░.._......░▒▓▓▓▓▓▓▓▓▓▓▓▒░......_..░▒▓▓▓.......// //.........▓▓▓▓░.._▓░▓░_....▓▓▓▓▓▓▓▓▓▓...._▒▓▒▓_..░▓▓▓▓.......// //.........▓▓▓▓.._▓█░▒█▒.....▒▓▓▓▓▓▓▒.....▒█▒░█▒_..▓▓▓▓.......// //........▒▓▓▓▓▓▒_..▒._░_▒▓▓▒░.▒▒.░▒▓▓▒_░_.▒.._▒▓▓▓▓▓▓▒.......// //.......▒▓▓▓▓▓▓▓▓▓_...▒▓▓▓▓▓░..▒▒..░▓▓▓▓▓▒..._▓▓▓▓▓▓▓▓▒......// //......▓▓▓▓▓▓▓▓▓▓▓_.▓▓▓▓▓▒.._▓▓▓█_..▒▓▓▓▓▓._▓▓▓▓▓▓▓▓▓▓▓......// //......▓▓▓▓▓▒▒▓▓▓▒▓▓▓▓▓▓▒░.▒▓▓▓▓▓█▒.░▒▓▓▓▓▓▒▒▓▓▓▒▓▓▓▓▓▓......// //......▓▓▓▓▓░.▓▓▓.▒▓▓▓▓▓░..▒▓▓▓▓▓▓▒..░▓▓▓▓▓░.▓▓▓.░▓▓▓▓▓......// //........▒▓▓█░.▓▓▓.▒▓▓▓▓▓__▓_..▒▒.._▓__▓▓▓▓▓░.▓▓▓.▒▓▓▓▒......// //.........░▒░░▒▓▒░▒▓▓▓▓▓▓▒▒▓▒▒▒▓▓▒▒▒▓▒▒▓▓▓▓▓▒░▒▓▒░░▒░░.......// //.........░▒.░▓▓░.▓▓▓▓█▒▒▒█▒▒▒█▒▒█▒▒▒█▒▒▒█▓▓▓▓.░▓▓░.▒░.......// //.............░▓▓░_▓▓▓▓█................█▓▓▓▓_░▓▓░...........// //..............░▒▓▓▓█▓._▒_.█░....█░._█_.▓█▓▓▓▒░..............// //.................░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░................// //.................░▒▓▓▓▒▒▓▒▒▓▓▓▓▒▒▓▓▓▓▒▒▓▓▒░.................// //..................▒▓▓░░▓░░▓▓▓▓░░▓▓▓▓░░▓▓▒...................// //.................▒▓░..█__▓▓▓▓__▓▓▓▓__█..░▓▒.................// //...................░...░..░▓▓░..░▓▓░..░...░.................// //.................... ..░...░▒░....░▒░...░...................// //............................................................// // Common sense not included and must be supplied by end user // // Read ENTIRE script first and LEARN: KNOW what you're using // // CC BY-NC https://creativecommons.org/licenses/by-nc/4.0/ // // ===========================================================// // IMAGE :: Killboard Easter Egg - Full Score DM // // OpenSim 0.9.2.1 (Mono/BulletSim) - LSL-safe, no ternaries // //============================================================// // CC BY-NC // https://creativecommons.org/licenses/by-nc/4.0/ //------------------------------ // CONFIGURATION //------------------------------ string NC_SCORES = "scores"; integer CHANNEL_DM = 0; // llRegionSayTo on public channel == direct message integer MAX_MSG_CHARS = 900; // safety margin under 1024 integer TOUCH_COOLDOWN_SEC = 6; // per-avatar cooldown (anti-spam) integer BUSY_PUNISH_SEC = 2; // if script is busy reading, tell them to retry after this //------------------------------ // CONSTANTS / INTEGERS //------------------------------ integer STRIDE_ENTRY = 2; // [score, name] pairs integer gLine = 0; integer gReading = FALSE; //------------------------------ // KEYS / RUNTIME VARIABLES //------------------------------ key gToucher = NULL_KEY; key gReq = NULL_KEY; integer gGrugsScore = 0; integer gHasGrugs = FALSE; list gEntries = []; // [score(int), name(string), score, name, ...] // cooldown tracking: [avatarKey, lastUnix, avatarKey, lastUnix, ...] list gCooldown = []; //------------------------------ // HELPERS //------------------------------ integer CooldownOk(key av) { integer now = llGetUnixTime(); integer i = 0; integer len = llGetListLength(gCooldown); for (i = 0; i < len; i += 2) { if (llList2Key(gCooldown, i) == av) { integer last = llList2Integer(gCooldown, i + 1); if ((now - last) < TOUCH_COOLDOWN_SEC) { return FALSE; } // update timestamp gCooldown = llListReplaceList(gCooldown, [now], i + 1, i + 1); return TRUE; } } // not found, add gCooldown += [av, now]; return TRUE; } integer SafeInt(string s) { // LSL casts fairly forgivingly, but keep it explicit return (integer)llStringTrim(s, STRING_TRIM); } string SafeName(string s) { return llStringTrim(s, STRING_TRIM); } list ParseLine(string line) { // returns [] if ignore // returns ["GRUGS", score] for grugs // returns ["ENTRY", score, name] for a normal entry line = llStringTrim(line, STRING_TRIM); if (line == "") { return []; } list parts = llParseStringKeepNulls(line, ["|"], []); integer n = llGetListLength(parts); if (n < 2) { return []; } string a = llList2String(parts, 0); string b = llList2String(parts, 1); // Ignore schema, anywhere it appears if (llToUpper(a) == "SCHEMA") { return []; } // GRUGS special if (llToUpper(a) == "GRUGS") { integer sc = SafeInt(b); return ["GRUGS", sc]; } // uuid|score|name (3+ parts) OR name|score (2 parts) if (n >= 3) { // treat as uuid|score|name (uuid ignored) integer score = SafeInt(llList2String(parts, 1)); string name = SafeName(llList2String(parts, 2)); if (name == "") { // if name missing, ignore return []; } return ["ENTRY", score, name]; } else { // name|score string name2 = SafeName(a); integer score2 = SafeInt(b); if (name2 == "") { return []; } return ["ENTRY", score2, name2]; } } list SortEntriesDesc(list entries) { // entries stride 2: [score, name, score, name ...] // simple O(n^2) sort because your kill list is not 50,000 lines long (I hope). integer len = llGetListLength(entries); integer i = 0; integer j = 0; for (i = 0; i < len; i += STRIDE_ENTRY) { for (j = i + STRIDE_ENTRY; j < len; j += STRIDE_ENTRY) { integer scoreI = llList2Integer(entries, i); integer scoreJ = llList2Integer(entries, j); if (scoreJ > scoreI) { // swap pairs (score,name) integer tmpScore = scoreI; string tmpName = llList2String(entries, i + 1); entries = llListReplaceList(entries, [scoreJ, llList2String(entries, j + 1)], i, i + 1); entries = llListReplaceList(entries, [tmpScore, tmpName], j, j + 1); } } } return entries; } SendDMChunked(key to, string header, list lines) { // Sends header + lines in multiple direct messages, chunked by length. string msg = header; integer i = 0; integer count = llGetListLength(lines); for (i = 0; i < count; ++i) { string add = llList2String(lines, i) + "\n"; if ((llStringLength(msg) + llStringLength(add)) > MAX_MSG_CHARS) { llRegionSayTo(to, CHANNEL_DM, msg); msg = header + add; // repeat header for readability } else { msg += add; } } if (llStringLength(msg) > llStringLength(header)) { llRegionSayTo(to, CHANNEL_DM, msg); } } PrintScoresTo(key av) { list sorted = SortEntriesDesc(gEntries); list out = []; integer i = 0; integer rank = 1; integer len = llGetListLength(sorted); for (i = 0; i < len; i += STRIDE_ENTRY) { integer sc = llList2Integer(sorted, i); string nm = llList2String(sorted, i + 1); // never show UUID (we never stored it), so we're safe out += [(string)rank + ". " + nm + " - " + (string)sc]; rank += 1; } // GRUGS always last if (gHasGrugs) { out += ["--", "GRUGS - " + (string)gGrugsScore]; } string header = "\n[IMAGE] ZETA GRUG - KILL BOARD\n"; SendDMChunked(av, header, out); } ResetReadState() { gLine = 0; gEntries = []; gGrugsScore = 0; gHasGrugs = FALSE; gReq = NULL_KEY; gReading = FALSE; gToucher = NULL_KEY; } //------------------------------ // STATES / LISTENERS //------------------------------ default { state_entry() { ResetReadState(); // Notecard existence check is “best effort”. // OpenSim/LSL doesn't provide a clean synchronous check. llOwnerSay("..."); //harmless developer tom-fuckery here... } touch_start(integer total_number) { key who = llDetectedKey(0); if (!CooldownOk(who)) { llRegionSayTo(who, CHANNEL_DM, "[IMAGE] Cooldown. Try again in a few seconds."); return; } if (gReading) { llRegionSayTo(who, CHANNEL_DM, "[IMAGE] Busy reading scores. Retry in " + (string)BUSY_PUNISH_SEC + " seconds."); return; } // Begin read gReading = TRUE; gToucher = who; gLine = 0; gEntries = []; gGrugsScore = 0; gHasGrugs = FALSE; gReq = llGetNotecardLine(NC_SCORES, gLine); } dataserver(key query_id, string data) { if (query_id != gReq) { return; } if (data == EOF) { // finished if (gToucher != NULL_KEY) { PrintScoresTo(gToucher); } ResetReadState(); return; } // parse and store list parsed = ParseLine(data); if (llGetListLength(parsed) > 0) { string kind = llList2String(parsed, 0); if (kind == "GRUGS") { gGrugsScore = llList2Integer(parsed, 1); gHasGrugs = TRUE; } else if (kind == "ENTRY") { integer sc = llList2Integer(parsed, 1); string nm = llList2String(parsed, 2); // keep even if score is 0; operators might want that gEntries += [sc, nm]; } } // next line gLine += 1; gReq = llGetNotecardLine(NC_SCORES, gLine); } changed(integer c) { // if notecard contents change, reset internal state safely if (c & (CHANGED_INVENTORY | CHANGED_ALLOWED_DROP)) { ResetReadState(); } } }