// 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 (Top 5 Total Kills + Grugs Total) // // OpenSim 0.9.2.1 Yeti-friendly (OSSL drawing) // // Renders to FACE 2 // Persistence: notecard "scores" // // ===========================================================// // CC BY-NC // https://creativecommons.org/licenses/by-nc/4.0/ string NOTECARD = "scores"; integer FACE = 1; // Must match the main game script integer SCORE_MSG_NUM = 9001; // Canvas integer CANVAS_W = 512; integer CANVAS_H = 512; // Visuals integer BORDER = 10; // border placement reference integer BORDER_INSET = 6; // inner text safety integer DOUBLE_BORDER = 2; // second rectangle offset for thicker border // Font (monospace for space-padding centering) string FONT_NAME = "DejaVu Sans Mono"; integer FONT_SIZE_TITLE = 26; integer FONT_SIZE_BODY = 20; // Layout integer LINE_GAP = 24; // vertical line spacing // Blank lines requested integer TITLE_BLANK_LINES = 4; // 2 blank lines under title integer AFTER_SCORE_BLANK_LINES = 3; // 2 blank lines after score #5 (before GRUGS total) // Text behavior integer NAME_MAX = 13; // truncate long names (hypergrid-proof) // Centering widths (monospace padding) integer LINE_WIDTH_CHARS_BODY = 30; // tuned in your environment integer LINE_WIDTH_CHARS_TITLE = 22; // tuned so title comes left more // Persistence state list gUuidKeys = []; // strings uuid list gUuidScores = []; // integers list gUuidNames = []; // strings display label list gLegacyNames = []; // strings list gLegacyScores = []; // integers integer gGrugsTotal = 0; integer gDirty = FALSE; integer gSaving = FALSE; // Notecard read state integer gNcLine = 0; key gNcQuery; // Debounce save integer SAVE_INTERVAL = 10; // seconds // Drawlist string string gCmd = ""; // ------------------------------------------------------------ // Helpers string truncateName(string s) { // 29 + "…" = 30 visible chars. if (llStringLength(s) < NAME_MAX) return s; return llGetSubString(s, 0, NAME_MAX - 2) + "…"; } integer toIntSafe(string s) { integer v = (integer)s; if (v < 0) v = 0; return v; } integer findUuid(string uuid) { return llListFindList(gUuidKeys, [uuid]); } integer findLegacy(string name) { return llListFindList(gLegacyNames, [name]); } string repeatSpaces(integer n) { if (n <= 0) return ""; string out = ""; while (n > 0) { out += " "; // 20 spaces n -= 20; } if (n < 0) out = llGetSubString(out, 0, llStringLength(out) + n - 1); return out; } string centerMonoBody(string s) { integer len = llStringLength(s); if (len >= LINE_WIDTH_CHARS_BODY) return s; integer pad = (LINE_WIDTH_CHARS_BODY - len) / 2; return repeatSpaces(pad) + s; } string centerMonoTitle(string s) { integer len = llStringLength(s); if (len >= LINE_WIDTH_CHARS_TITLE) return s; integer pad = (LINE_WIDTH_CHARS_TITLE - len) / 2; return repeatSpaces(pad) + s; } // ------------------------------------------------------------ // Unified list building/sorting (triplets: keyTag, name, score) list buildUnified() { list u = []; integer i; for (i = 0; i < llGetListLength(gUuidKeys); i++) { u += [ llList2String(gUuidKeys, i), llList2String(gUuidNames, i), llList2Integer(gUuidScores, i) ]; } for (i = 0; i < llGetListLength(gLegacyNames); i++) { string nm = llList2String(gLegacyNames, i); u += [ "LEGACY:" + nm, nm, llList2Integer(gLegacyScores, i) ]; } return u; } list sortUnified(list u) { integer count = llGetListLength(u) / 3; integer i; integer j; for (i = 0; i < count; i++) { for (j = i + 1; j < count; j++) { integer iScore = llList2Integer(u, i*3 + 2); integer jScore = llList2Integer(u, j*3 + 2); if (jScore > iScore) { list ti = llList2List(u, i*3, i*3 + 2); list tj = llList2List(u, j*3, j*3 + 2); u = llListReplaceList(u, tj, i*3, i*3 + 2); u = llListReplaceList(u, ti, j*3, j*3 + 2); } } } return u; } list top5Unified() { list u = sortUnified(buildUnified()); integer count = llGetListLength(u) / 3; if (count <= 5) return u; return llList2List(u, 0, (5*3) - 1); } // ------------------------------------------------------------ // Drawing (vector drawlist) using pen-position rectangle calls string cmdInit() { gCmd = ""; // background black gCmd = osSetPenColor(gCmd, "Black"); gCmd = osMovePen(gCmd, 0, 0); gCmd = osDrawFilledRectangle(gCmd, CANVAS_W, CANVAS_H); // white border (double thickness) gCmd = osSetPenColor(gCmd, "White"); gCmd = osMovePen(gCmd, BORDER, BORDER); gCmd = osDrawRectangle(gCmd, CANVAS_W - (2*BORDER), CANVAS_H - (2*BORDER)); gCmd = osMovePen(gCmd, BORDER + DOUBLE_BORDER, BORDER + DOUBLE_BORDER); gCmd = osDrawRectangle( gCmd, CANVAS_W - (2*(BORDER + DOUBLE_BORDER)), CANVAS_H - (2*(BORDER + DOUBLE_BORDER)) ); // text setup gCmd = osSetPenColor(gCmd, "White"); gCmd = osSetFontName(gCmd, FONT_NAME); return gCmd; } integer textX() { return BORDER + DOUBLE_BORDER + BORDER_INSET; } drawLine(integer yTop, string text, integer fontSize) { gCmd = osSetFontSize(gCmd, fontSize); gCmd = osMovePen(gCmd, textX(), yTop); gCmd = osDrawText(gCmd, text); } integer computeStartY() { // Center entire block vertically within inner safe area. integer height = 0; // Title height += LINE_GAP; // 2 blank lines under title height += LINE_GAP * TITLE_BLANK_LINES; // Subtitle height += LINE_GAP; // 1 blank line after subtitle height += LINE_GAP; // 5 scores height += LINE_GAP * 5; // 2 blank lines after score #5 height += LINE_GAP * AFTER_SCORE_BLANK_LINES; // Grugs total height += LINE_GAP; integer innerTop = BORDER + DOUBLE_BORDER + BORDER_INSET; integer innerBottom = CANVAS_H - (BORDER + DOUBLE_BORDER + BORDER_INSET); integer innerH = innerBottom - innerTop; integer y = innerTop + ((innerH - height) / 2); if (y < innerTop) y = innerTop; return y; } renderBoard() { cmdInit(); integer y = computeStartY(); // Title drawLine(y, centerMonoTitle("GRUG ASSAULT!"), FONT_SIZE_TITLE); y += LINE_GAP; // 2 blank lines under title y += LINE_GAP * TITLE_BLANK_LINES; // Subtitle drawLine(y, centerMonoBody("Top 5 Total Kills"), FONT_SIZE_BODY); y += LINE_GAP; // 1 blank line after subtitle y += LINE_GAP; // Top 5 (no numbering) list t = top5Unified(); integer entries = llGetListLength(t) / 3; integer i; for (i = 0; i < 5; i++) { string line; if (i < entries) { string nm = llList2String(t, i*3 + 1); integer sc = llList2Integer(t, i*3 + 2); nm = truncateName(nm); line = " " + nm + " - " + (string)sc + " "; } else { line = " ..... "; } drawLine(y, centerMonoBody(line), FONT_SIZE_BODY); y += LINE_GAP; } // 2 blank lines under score #5 y += LINE_GAP * AFTER_SCORE_BLANK_LINES; // Grugs total drawLine(y, centerMonoBody(" GRUGS TOTAL: " + (string)gGrugsTotal + " "), FONT_SIZE_BODY); // Apply to face 2 string extra = "width:" + (string)CANVAS_W + ",height:" + (string)CANVAS_H; osSetDynamicTextureDataFace("", "vector", gCmd, extra, 0, FACE); } // ------------------------------------------------------------ // Persistence (Option A: remove 'scores' before rewriting) saveNotecard() { if (gSaving) return; gSaving = TRUE; list lines = []; lines += ["SCHEMA|2"]; lines += ["GRUGS|" + (string)gGrugsTotal]; integer i; // UUID records for (i = 0; i < llGetListLength(gUuidKeys); i++) { string uuid = llList2String(gUuidKeys, i); integer sc = llList2Integer(gUuidScores, i); string nm = llList2String(gUuidNames, i); lines += [uuid + "|" + (string)sc + "|" + nm]; } // Legacy records for (i = 0; i < llGetListLength(gLegacyNames); i++) { string nm2 = llList2String(gLegacyNames, i); integer sc2 = llList2Integer(gLegacyScores, i); lines += [nm2 + "|" + (string)sc2]; } // Option A: remove existing "scores" notecard before rewrite to avoid pile-up if (llGetInventoryType(NOTECARD) == INVENTORY_NOTECARD) { llRemoveInventory(NOTECARD); } osMakeNotecard(NOTECARD, lines); gDirty = FALSE; gSaving = FALSE; } // ------------------------------------------------------------ // Score updates addKillForAvatar(key avatar, string name) { string uuid = (string)avatar; if (uuid == (string)NULL_KEY) return; integer idx = findUuid(uuid); if (idx < 0) { gUuidKeys += [uuid]; gUuidScores += [1]; gUuidNames += [name]; } else { integer sc = llList2Integer(gUuidScores, idx) + 1; gUuidScores = llListReplaceList(gUuidScores, [sc], idx, idx); gUuidNames = llListReplaceList(gUuidNames, [name], idx, idx); } gDirty = TRUE; renderBoard(); } addGrugPoints(integer pts) { if (pts < 0) pts = 0; gGrugsTotal += pts; gDirty = TRUE; renderBoard(); } // ------------------------------------------------------------ default { state_entry() { gUuidKeys = []; gUuidScores = []; gUuidNames = []; gLegacyNames = []; gLegacyScores = []; gGrugsTotal = 0; gDirty = FALSE; gSaving = FALSE; if (llGetInventoryType(NOTECARD) != INVENTORY_NOTECARD) { osMakeNotecard(NOTECARD, []); } gNcLine = 0; gNcQuery = llGetNotecardLine(NOTECARD, gNcLine); renderBoard(); llSetTimerEvent((float)SAVE_INTERVAL); } changed(integer change) { if (change & CHANGED_INVENTORY) llResetScript(); } dataserver(key qid, string data) { if (qid != gNcQuery) return; if (data == EOF) { renderBoard(); return; } data = llStringTrim(data, STRING_TRIM); if (data != "" && llGetSubString(data, 0, 1) != "//") { list parts = llParseString2List(data, ["|"], []); integer n = llGetListLength(parts); if (n >= 2) { string a = llList2String(parts, 0); string b = llList2String(parts, 1); if (a == "SCHEMA") { // ignore } else if (a == "GRUGS") { gGrugsTotal = toIntSafe(b); } else if (n == 2) { // Legacy: Name|Score string nm = a; integer sc = toIntSafe(b); if (nm != "") { integer li = findLegacy(nm); if (li < 0) { gLegacyNames += [nm]; gLegacyScores += [sc]; } else { integer cur = llList2Integer(gLegacyScores, li); if (sc > cur) gLegacyScores = llListReplaceList(gLegacyScores, [sc], li, li); } } } else { // New: UUID|Score|Name string uuid = a; integer sc2 = toIntSafe(b); string nm2 = llList2String(parts, 2); if (uuid != "" && nm2 != "") { integer ui = findUuid(uuid); if (ui < 0) { gUuidKeys += [uuid]; gUuidScores += [sc2]; gUuidNames += [nm2]; } else { integer cur2 = llList2Integer(gUuidScores, ui); if (sc2 > cur2) gUuidScores = llListReplaceList(gUuidScores, [sc2], ui, ui); gUuidNames = llListReplaceList(gUuidNames, [nm2], ui, ui); } } } } } gNcQuery = llGetNotecardLine(NOTECARD, ++gNcLine); } link_message(integer sender_num, integer num, string str, key id) { if (num != SCORE_MSG_NUM) return; // Expected: // "KILL||" // "GRUG|" list p = llParseString2List(str, ["|"], []); integer n = llGetListLength(p); if (n < 2) return; string cmd = llList2String(p, 0); if (cmd == "KILL" && n >= 3) { string uuidStr = llList2String(p, 1); string name = llList2String(p, 2); key av = id; if (av == NULL_KEY) av = (key)uuidStr; if (name == "") name = llKey2Name(av); addKillForAvatar(av, name); } else if (cmd == "GRUG" && n >= 2) { integer pts = (integer)llList2String(p, 1); addGrugPoints(pts); } else if (cmd == "SAVE") { saveNotecard(); } else if (cmd == "REFRESH") { renderBoard(); } } timer() { if (gDirty) saveNotecard(); } }