diff --git a/counters/Hit Count by Albert/catch.png b/counters/Hit Count by Albert/catch.png new file mode 100644 index 0000000..c7ac9fb Binary files /dev/null and b/counters/Hit Count by Albert/catch.png differ diff --git a/counters/Hit Count by Albert/index.html b/counters/Hit Count by Albert/index.html new file mode 100644 index 0000000..db2685c --- /dev/null +++ b/counters/Hit Count by Albert/index.html @@ -0,0 +1,35 @@ + + + + + + + + + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/counters/Hit Count by Albert/main.css b/counters/Hit Count by Albert/main.css new file mode 100644 index 0000000..a620b11 --- /dev/null +++ b/counters/Hit Count by Albert/main.css @@ -0,0 +1,109 @@ +body, html { + padding: 0; + margin: 0; + overflow: hidden; + font-family: var(--main-font, Arial, sans-serif); +} + +#hitcount_box { + position: absolute; + top: 0; + left: 0; + transition: opacity 500ms ease-in-out; + opacity: 0; +} + +#countBox { + display: grid; + grid-template-columns: var(--label-width, 130px) 10px var(--val-width, 90px); + grid-auto-flow: dense; + line-height: var(--line-height, 1.25); + padding: 4px; +} + +#countBox.swapped { + grid-template-columns: var(--val-width, 90px) 10px var(--label-width, 140px); +} + +#countBox > div { + display: contents; +} + +#countBox > div.hidden { + display: none !important; +} + +.spacer { + display: block !important; + grid-column: 1 / -1; + height: 0.75em; +} + +.spacer.hidden { + display: none !important; +} + +.row-label, .row-val { + font-size: var(--scaled-font-size, 20pt); + white-space: nowrap; + color: inherit; + text-shadow: 1.5px 1.5px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; +} + +.row-label { + grid-column: 1; + text-align: left; + justify-self: start; +} + +.row-val { + grid-column: 3; + text-align: right; + justify-self: end; + font-variant-numeric: tabular-nums; +} + +.hidden { + display: none !important; +} + +/* Swapped State Modifiers */ +#countBox.swapped .row-label { + grid-column: 3; + text-align: right; + justify-self: end; +} + +#countBox.swapped .row-val { + grid-column: 1; + text-align: left; + justify-self: start; +} + +/* Specific Variable Map for Labels and Numbers */ +#pp .row-label { color: var(--color-pp-label); } +#pp .row-val { color: var(--color-pp-val); } +#ur .row-label { color: var(--color-ur-label); } +#ur .row-val { color: var(--color-ur-val); } +#ratio .row-label { color: var(--color-ratio-label); } +#ratio .row-val { color: var(--color-ratio-val); } +#maxCombo .row-label { color: var(--color-combo-label); } +#maxCombo .row-val { color: var(--color-combo-val); } + +#h300g .row-label { color: var(--color-300g-label); } +#h300g .row-val { color: var(--color-300g-val); } +#h300 .row-label { color: var(--color-300-label); } +#h300 .row-val { color: var(--color-300-val); } +#h200 .row-label { color: var(--color-200-label); } +#h200 .row-val { color: var(--color-200-val); } +#h100 .row-label { color: var(--color-100-label); } +#h100 .row-val { color: var(--color-100-val); } +#h50 .row-label { color: var(--color-50-label); } +#h50 .row-val { color: var(--color-50-val); } +#miss .row-label { color: var(--color-miss-label); } +#miss .row-val { color: var(--color-miss-val); } + +#earlyCount .row-label { color: var(--color-early-label); } +#earlyCount .row-val { color: var(--color-early-val); } +#lateCount .row-label { color: var(--color-late-label); } +#lateCount .row-val { color: var(--color-late-val); } \ No newline at end of file diff --git a/counters/Hit Count by Albert/main.js b/counters/Hit Count by Albert/main.js new file mode 100644 index 0000000..6dcc058 --- /dev/null +++ b/counters/Hit Count by Albert/main.js @@ -0,0 +1,472 @@ +class WebSocketManager { + constructor(host) { + this.host = host; + this.sockets = {}; + this.createConnection = this.createConnection.bind(this); + } + + createConnection(url, callback, filters) { + let interval; + const counterPath = window.COUNTER_PATH || ""; + const query = url.includes("?") ? `&l=${encodeURI(counterPath)}` : `?l=${encodeURI(counterPath)}`; + const fullUrl = `ws://${this.host}${url}${query}`; + this.sockets[url] = new WebSocket(fullUrl); + + this.sockets[url].onopen = () => { + if (interval) clearInterval(interval); + if (filters) this.sockets[url].send(`applyFilters:${JSON.stringify(filters)}`); + }; + + this.sockets[url].onclose = () => { + delete this.sockets[url]; + interval = setTimeout(() => this.createConnection(url, callback, filters), 1000); + }; + + this.sockets[url].onmessage = ({ data }) => { + if (!data) return; + try { + const parsed = JSON.parse(data); + if (parsed && typeof parsed === "object" && !("error" in parsed)) callback(parsed); + } catch (e) {} + }; + } + + api_v2(callback, filters) { this.createConnection("/websocket/v2", callback, filters); } + api_v2_precise(callback, filters) { this.createConnection("/websocket/v2/precise", callback, filters); } + commands(callback) { this.createConnection("/websocket/commands", callback); } + + sendCommand(name, command, retry = 1) { + const socket = this.sockets["/websocket/commands"]; + if (socket && socket.readyState === WebSocket.OPEN) { + socket.send(`${name}:${command}`); + } else if (retry <= 50) { + setTimeout(() => this.sendCommand(name, command, retry + 1), 100); + } + } +} + +const string = { + global: { pp: "PP", ur: "UR", ratio: "Ratio", combo: "Max Combo", early: "Early", late: "Late", miss: "Miss" }, + modes: { + mania: { h300g: "MAX", h300: "Perfect", h200: "Great", h100: "Good", h50: "Bad" }, + catch: { h300: "Fruit", h100: "Drop", h50: "Droplet" }, + fruits: { h300: "Fruit", h100: "Drop", h50: "Droplet" }, + taiko: { h300: "Great", h100: "Ok" }, + osu: { h300: "300", h100: "100", h50: "50" } + } +}; + +const getWindows = (mode, od, modsString = "") => { + let mOd = od; + if (mode === "mania") { + if (modsString.includes("EZ")) mOd = od * 0.5; + const hrMult = modsString.includes("HR") ? 5/7 : 1; + return [16, ((64 - 3 * mOd) * hrMult), ((97 - 3 * mOd) * hrMult), ((127 - 3 * mOd) * hrMult), ((151 - 3 * mOd) * hrMult)]; + } + + if (modsString.includes("EZ")) mOd = od / 2; + else if (modsString.includes("HR")) mOd = Math.min(od * 1.4, 10); + + if (mode === "taiko") return [(50 - 3 * mOd), (mOd >= 5 ? 119.5 - 8 * mOd : 110 - 6 * mOd), (mOd >= 5 ? 135 - 8 * mOd : 120 - 5 * mOd)]; + return [(80 - 6 * mOd), (140 - 8 * mOd), (200 - 10 * mOd)]; +}; + +function updateRow(element, label, value) { + if (element) element.innerHTML = `${label}${value}`; +} + +function distributeDelta(newTotal = 0, display, preciseTally, isMiss = false) { + const delta = newTotal - display.t; + if (delta <= 0) return; + + if (isMiss) { + const availableE = Math.max(0, preciseTally.e - display.e); + const addE = Math.min(delta, availableE); + display.e += addE; + display.l += (delta - addE); + } else { + const preciseTotal = preciseTally.e + preciseTally.l; + if (preciseTotal === 0) { + const half = Math.floor(delta / 2); + display.e += half; + display.l += (delta - half); + } else { + const expectedE = Math.round(newTotal * (preciseTally.e / preciseTotal)); + const addE = Math.max(0, Math.min(delta, expectedE - display.e)); + display.e += addE; + display.l += (delta - addE); + } + } + display.t = newTotal; +} + +function getRatioText(mode, hits) { + const h300g = hits.geki || 0, h300 = hits[300] || 0, h200 = hits.katu || 0, h100 = hits[100] || 0, h50 = hits[50] || 0, h0 = hits[0] || 0; + + if (mode === "mania") { + const total = h300g + h300 + h200 + h100 + h50 + h0; + if (total === 0) return "0:1"; + return (total - h300g) === 0 ? "∞:1" : `${(h300g / (total - h300g)).toFixed(1)}:1`; + } else { + const total = h300 + h100 + h50 + h0; + if (total === 0) return "0:1"; + const nonPerfect = h100 + h50 + h0; + return nonPerfect === 0 ? "∞:1" : `${(h300 / nonPerfect).toFixed(1)}:1`; + } +} + +function syncDeltaTallies(mode, hits) { + if (mode === "mania") { + distributeDelta(hits[300] || 0, displayTally.mania[0], hitTally.mania[0]); + distributeDelta(hits.katu || 0, displayTally.mania[1], hitTally.mania[1]); + distributeDelta(hits[100] || 0, displayTally.mania[2], hitTally.mania[2]); + distributeDelta(hits[50] || 0, displayTally.mania[3], hitTally.mania[3]); + distributeDelta(hits[0] || 0, displayTally.mania[4], hitTally.mania[4], true); + } else if (mode === "taiko") { + distributeDelta(hits[100] || 0, displayTally.taiko[0], hitTally.taiko[0]); + distributeDelta(hits[0] || 0, displayTally.taiko[1], hitTally.taiko[1], true); + } else { + distributeDelta(hits[100] || 0, displayTally.std[0], hitTally.std[0]); + distributeDelta(hits[50] || 0, displayTally.std[1], hitTally.std[1]); + distributeDelta(hits[0] || 0, displayTally.std[2], hitTally.std[2], true); + } +} + +function applyModeColors(mode, settings, fallback) { + const root = document.documentElement; + const applyHitCol = (prefix, labelVal, numVal) => { + root.style.setProperty(`--color-${prefix}-label`, settings.useCustomHitCountLabelColors ? (labelVal || fallback) : fallback); + root.style.setProperty(`--color-${prefix}-val`, settings.useCustomHitCountNumberColors ? (numVal || fallback) : fallback); + }; + + applyHitCol('miss', settings.colorMissLabel, settings.colorMissVal); + + if (mode === "osu") { + applyHitCol('300g', fallback, fallback); + applyHitCol('300', settings.colorOsu300Label, settings.colorOsu300Val); + applyHitCol('200', fallback, fallback); + applyHitCol('100', settings.colorOsu100Label, settings.colorOsu100Val); + applyHitCol('50', settings.colorOsu50Label, settings.colorOsu50Val); + } else if (mode === "taiko") { + applyHitCol('300g', fallback, fallback); + applyHitCol('300', settings.colorTaiko300Label, settings.colorTaiko300Val); + applyHitCol('200', fallback, fallback); + applyHitCol('100', settings.colorTaiko100Label, settings.colorTaiko100Val); + applyHitCol('50', fallback, fallback); + } else if (mode === "catch" || mode === "fruits") { + applyHitCol('300g', fallback, fallback); + applyHitCol('300', settings.colorCatch300Label, settings.colorCatch300Val); + applyHitCol('200', fallback, fallback); + applyHitCol('100', settings.colorCatch100Label, settings.colorCatch100Val); + applyHitCol('50', settings.colorCatch50Label, settings.colorCatch50Val); + } else { + applyHitCol('300g', settings.colorMania300gLabel, settings.colorMania300gVal); + applyHitCol('300', settings.colorMania300Label, settings.colorMania300Val); + applyHitCol('200', settings.colorMania200Label, settings.colorMania200Val); + applyHitCol('100', settings.colorMania100Label, settings.colorMania100Val); + applyHitCol('50', settings.colorMania50Label, settings.colorMania50Val); + } +} + +const toggleClass = (el, condition, className = "hidden") => { + if (el) condition ? el.classList.add(className) : el.classList.remove(className); +}; + +async function autoScaleFont() { + await document.fonts.ready; + + let measurer = document.getElementById("font-measurer"); + if (!measurer) { + measurer = document.createElement("span"); + measurer.id = "font-measurer"; + measurer.style.position = "absolute"; + measurer.style.visibility = "hidden"; + measurer.style.whiteSpace = "nowrap"; + measurer.style.fontSize = "20pt"; + document.body.appendChild(measurer); + } + + measurer.style.fontFamily = document.documentElement.style.getPropertyValue('--main-font'); + + const targetLabelW = settings.labelColumnWidth || 140; + const targetValW = settings.valueColumnWidth || 90; + + measurer.innerText = "Max Combo"; + const actualLabelW = measurer.offsetWidth || targetLabelW; + + measurer.innerText = "88888.8"; + const actualValW = measurer.offsetWidth || targetValW; + + const scaleLabel = targetLabelW / actualLabelW; + const scaleVal = targetValW / actualValW; + + const minScale = Math.min(scaleLabel, scaleVal, 1); + let finalSize = 20 * minScale; + + if (finalSize < 10) finalSize = 10; + + document.documentElement.style.setProperty('--scaled-font-size', `${finalSize}pt`); +} + +const wsManager = new WebSocketManager(window.location.host); + +const hitcountBox = document.getElementById("hitcount_box"); +const elPp = document.getElementById("pp"), elUr = document.getElementById("ur"), elRatio = document.getElementById("ratio"), elMaxCombo = document.getElementById("maxCombo"); +const elEarly = document.getElementById("earlyCount"), elLate = document.getElementById("lateCount"); +const el300g = document.getElementById("h300g"), el300 = document.getElementById("h300"), el200 = document.getElementById("h200"); +const el100 = document.getElementById("h100"), el50 = document.getElementById("h50"), elMiss = document.getElementById("miss"); +const brHits = document.getElementById("brHits"), brEarlyLate = document.getElementById("brEarlyLate"); + +let cache = { state: "", mode: "osu", od: 0, mods: "", processedHits: 0, curTotalHits: 0, lastTime: 0 }; +let hitTally = { + mania: [ {e:0, l:0}, {e:0, l:0}, {e:0, l:0}, {e:0, l:0}, {e:0, l:0} ], + taiko: [ {e:0, l:0}, {e:0, l:0} ], + std: [ {e:0, l:0}, {e:0, l:0}, {e:0, l:0} ] +}; +let displayTally = { + mania: [ {e:0, l:0, t:0}, {e:0, l:0, t:0}, {e:0, l:0, t:0}, {e:0, l:0, t:0}, {e:0, l:0, t:0} ], + taiko: [ {e:0, l:0, t:0}, {e:0, l:0, t:0} ], + std: [ {e:0, l:0, t:0}, {e:0, l:0, t:0}, {e:0, l:0, t:0} ] +}; + +let settings = { + labelColumnWidth: 130, valueColumnWidth: 90, lineHeight: 1.25, + fontName: "Arial", useCustomFont: false, customFontName: "font.ttf", + globalTextColor: "#ffffff", swapLabelValue: false, + hidePP: false, useCustomPPColors: false, colorPPLabel: "#ffffff", colorPPVal: "#ffffff", + hideUR: false, useCustomURColors: false, colorURLabel: "#ffffff", colorURVal: "#ffffff", + hideRatio: false, useCustomRatioColors: false, colorRatioLabel: "#ffffff", colorRatioVal: "#ffffff", + hideMaxCombo: false, useCustomComboColors: false, colorComboLabel: "#ffffff", colorComboVal: "#ffffff", + hideHitCounts: false, useCustomHitCountLabelColors: true, useCustomHitCountNumberColors: false, + colorOsu300Label: "#50b4ff", colorOsu300Val: "#50b4ff", colorOsu100Label: "#47e547", colorOsu100Val: "#47e547", colorOsu50Label: "#ffcc22", colorOsu50Val: "#ffcc22", + colorTaiko300Label: "#ffcc22", colorTaiko300Val: "#ffcc22", colorTaiko100Label: "#47e547", colorTaiko100Val: "#47e547", + colorCatch300Label: "#ffcc22", colorCatch300Val: "#ffcc22", colorCatch100Label: "#47e547", colorCatch100Val: "#47e547", colorCatch50Label: "#50b4ff", colorCatch50Val: "#50b4ff", + colorMania300gLabel: "#ffffff", colorMania300gVal: "#ffffff", colorMania300Label: "#ffcc22", colorMania300Val: "#ffcc22", colorMania200Label: "#47e547", colorMania200Val: "#47e547", colorMania100Label: "#50b4ff", colorMania100Val: "#50b4ff", colorMania50Label: "#888888", colorMania50Val: "#888888", + colorMissLabel: "#ff0000", colorMissVal: "#ff0000", + hideEarlyLate: false, useCustomEarlyLateColors: true, colorEarlyLabel: "#0000ff", colorEarlyVal: "#ffffff", colorLateLabel: "#ff0000", colorLateVal: "#ffffff" +}; + +wsManager.commands((data) => { + try { + if (data.command !== "getSettings") return; + Object.assign(settings, data.message); + applySettingsToUI(); + } catch (e) {} +}); + +wsManager.sendCommand("getSettings", window.COUNTER_PATH ? encodeURI(window.COUNTER_PATH) : ""); + +function applySettingsToUI() { + const root = document.documentElement; + const g = settings.globalTextColor || "#ffffff"; + const mode = cache.mode || "osu"; + + root.style.setProperty('--label-width', `${settings.labelColumnWidth || 140}px`); + root.style.setProperty('--val-width', `${settings.valueColumnWidth || 90}px`); + root.style.setProperty('--line-height', settings.lineHeight || 1.25); + + let fontStyle = document.getElementById("custom-font-style"); + const systemFont = settings.fontName ? `"${settings.fontName}", sans-serif` : "Arial, sans-serif"; + + if (settings.useCustomFont && settings.customFontName) { + if (!fontStyle) { + fontStyle = document.createElement("style"); + fontStyle.id = "custom-font-style"; + document.head.appendChild(fontStyle); + } + fontStyle.innerHTML = ` + @font-face { + font-family: 'CustomOverlayFont'; + src: url('./${settings.customFontName}'); + } + `; + root.style.setProperty('--main-font', `'CustomOverlayFont', ${systemFont}`); + } else { + if (fontStyle) fontStyle.innerHTML = ""; + root.style.setProperty('--main-font', systemFont); + } + + toggleClass(document.getElementById("countBox"), settings.swapLabelValue, "swapped"); + + toggleClass(elPp, settings.hidePP); + toggleClass(elUr, settings.hideUR); + toggleClass(elRatio, settings.hideRatio); + toggleClass(elMaxCombo, settings.hideMaxCombo); + + if (settings.hideHitCounts) { + [el300g, el300, el200, el100, el50, elMiss, brHits].forEach(el => toggleClass(el, true)); + } else { + toggleClass(elMiss, false); toggleClass(brHits, false); + } + + const hideEL = settings.hideEarlyLate; + [elEarly, elLate, brEarlyLate].forEach(el => toggleClass(el, hideEL)); + + const applyStatColor = (labelProp, valProp, condition, lCol, vCol) => { + root.style.setProperty(labelProp, condition ? (lCol || g) : g); + root.style.setProperty(valProp, condition ? (vCol || g) : g); + }; + + applyStatColor('--color-pp-label', '--color-pp-val', settings.useCustomPPColors, settings.colorPPLabel, settings.colorPPVal); + applyStatColor('--color-ur-label', '--color-ur-val', settings.useCustomURColors, settings.colorURLabel, settings.colorURVal); + applyStatColor('--color-ratio-label', '--color-ratio-val', settings.useCustomRatioColors, settings.colorRatioLabel, settings.colorRatioVal); + applyStatColor('--color-combo-label', '--color-combo-val', settings.useCustomComboColors, settings.colorComboLabel, settings.colorComboVal); + applyStatColor('--color-early-label', '--color-early-val', settings.useCustomEarlyLateColors, settings.colorEarlyLabel, settings.colorEarlyVal); + applyStatColor('--color-late-label', '--color-late-val', settings.useCustomEarlyLateColors, settings.colorLateLabel, settings.colorLateVal); + + applyModeColors(mode, settings, g); + + autoScaleFont(); +} + +function resetCounters() { + if (!settings.hideUR) updateRow(elUr, string.global.ur, "0.00"); + if (!settings.hideRatio) updateRow(elRatio, string.global.ratio, "0:1"); + if (!settings.hideEarlyLate) { updateRow(elEarly, string.global.early, "0"); updateRow(elLate, string.global.late, "0"); } + + hitTally.mania.forEach(t => { t.e = 0; t.l = 0; }); hitTally.taiko.forEach(t => { t.e = 0; t.l = 0; }); hitTally.std.forEach(t => { t.e = 0; t.l = 0; }); + displayTally.mania.forEach(t => { t.e = 0; t.l = 0; t.t = 0; }); displayTally.taiko.forEach(t => { t.e = 0; t.l = 0; t.t = 0; }); displayTally.std.forEach(t => { t.e = 0; t.l = 0; t.t = 0; }); + cache.processedHits = 0; cache.curTotalHits = 0; +} + +wsManager.api_v2((data) => { + if (!data.state?.name) return; + const state = data.state.name; + + if (cache.state !== state) { + if (hitcountBox) hitcountBox.style.opacity = (state === "play") ? 1 : 0; + if (state !== "play") resetCounters(); + } + + if (!settings.hidePP) { + const ppValue = (state === 'play' || state === 'resultScreen') ? (data.play?.pp?.current || 0) : (data.performance?.pp?.current || data.play?.pp?.current || 0); + updateRow(elPp, string.global.pp, Math.round(ppValue) + 'pp'); + } + + if (state === "play") { + const mode = data.play?.mode?.name ?? cache.mode; + + if (!settings.hideMaxCombo) updateRow(elMaxCombo, string.global.combo, data.play?.combo?.max || 0); + + let od = cache.od; + if (data.beatmap?.stats?.od !== undefined) { + od = (typeof data.beatmap.stats.od === "object" && data.beatmap.stats.od.original !== undefined) ? data.beatmap.stats.od.original : data.beatmap.stats.od; + } + + let mods = cache.mods; + if (data.play?.mods) { + mods = (typeof data.play.mods === "string") ? data.play.mods : (typeof data.play.mods.name === "string" ? data.play.mods.name : data.play.mods.join("")); + } + + if (cache.mode !== mode || cache.od !== od || cache.mods !== mods) { + cache.mode = mode; cache.od = od; cache.mods = mods; + cache.windows = getWindows(cache.mode, cache.od, cache.mods); + resetCounters(); + applySettingsToUI(); + } + + const hits = data.play?.hits || {}; + const modeLabels = string.modes[mode] || string.modes.osu; + + const updateHitRow = (el, key, val) => { + if (settings.hideHitCounts) { toggleClass(el, true); return; } + if (modeLabels[key]) { toggleClass(el, false); updateRow(el, modeLabels[key], val); } + else { toggleClass(el, true); } + }; + + updateHitRow(el300g, 'h300g', hits.geki || 0); + updateHitRow(el300, 'h300', hits[300] || 0); + updateHitRow(el200, 'h200', hits.katu || 0); + updateHitRow(el100, 'h100', hits[100] || 0); + updateHitRow(el50, 'h50', hits[50] || 0); + + if (!settings.hideHitCounts) updateRow(elMiss, string.global.miss, hits[0] || 0); + if (!settings.hideRatio) updateRow(elRatio, string.global.ratio, getRatioText(mode, hits)); + + const isCatch = (mode === "catch" || mode === "fruits"); + const hideEL = isCatch || settings.hideEarlyLate; + [elEarly, elLate, brEarlyLate].forEach(el => toggleClass(el, hideEL)); + + const totalHits = (hits.geki || 0) + (hits[300] || 0) + (hits.katu || 0) + (hits[100] || 0) + (hits[50] || 0) + (hits[0] || 0); + if (totalHits === 0 && cache.curTotalHits > 0) resetCounters(); + + if (totalHits >= cache.curTotalHits) { + cache.curTotalHits = totalHits; + syncDeltaTallies(mode, hits); + + if (!hideEL) { + let totalEarly = 0, totalLate = 0; + const currentTallyArr = mode === "mania" ? displayTally.mania : (mode === "taiko" ? displayTally.taiko : displayTally.std); + currentTallyArr.forEach(t => { totalEarly += t.e; totalLate += t.l; }); + updateRow(elEarly, string.global.early, totalEarly); + updateRow(elLate, string.global.late, totalLate); + } + } + } + cache.state = state; +}, ["state", { field: "play", keys: ["mode", "mods", "hits", "combo", "pp"] }, { field: "beatmap", keys: ["stats"] }, { field: "performance", keys: ["pp"] }]); + +wsManager.api_v2_precise((data) => { + if (cache.state !== "play") return; + const hitErrors = data.hitErrors || []; + + if (data.currentTime < (cache.lastTime || 0) - 50) { + resetCounters(); cache.lastTime = data.currentTime; cache.processedHits = hitErrors.length; return; + } + cache.lastTime = data.currentTime; + + if (hitErrors.length < cache.processedHits) { + if (hitErrors.length === 0 || (cache.processedHits - hitErrors.length > 5)) { + resetCounters(); cache.processedHits = hitErrors.length; + } + return; + } + + if (hitErrors.length > cache.processedHits) { + const newHits = hitErrors.slice(cache.processedHits); + cache.processedHits = hitErrors.length; + const mode = cache.mode, windows = cache.windows; + + newHits.forEach(ms => { + const msAbs = Math.abs(ms); + const isEarly = ms < 0; + + if (mode === "mania") { + const t = hitTally.mania; + if (msAbs <= windows[0]) {} + else if (msAbs <= windows[1]) { isEarly ? t[0].e++ : t[0].l++; } + else if (msAbs <= windows[2]) { isEarly ? t[1].e++ : t[1].l++; } + else if (msAbs <= windows[3]) { isEarly ? t[2].e++ : t[2].l++; } + else if (msAbs <= windows[4]) { isEarly ? t[3].e++ : t[3].l++; } + else { isEarly ? t[4].e++ : t[4].l++; } + } else if (mode === "taiko") { + const t = hitTally.taiko; + if (msAbs <= windows[0]) {} + else if (msAbs <= windows[1]) { isEarly ? t[0].e++ : t[0].l++; } + else { isEarly ? t[1].e++ : t[1].l++; } + } else { + const t = hitTally.std; + if (msAbs <= windows[0]) {} + else if (msAbs <= windows[1]) { isEarly ? t[0].e++ : t[0].l++; } + else if (msAbs <= windows[2]) { isEarly ? t[1].e++ : t[1].l++; } + else { isEarly ? t[2].e++ : t[2].l++; } + } + }); + } + + if (!settings.hideUR) { + if (hitErrors.length > 0) { + let sum = 0, sumSq = 0; + const len = hitErrors.length; + for (let i = 0; i < len; i++) { + sum += hitErrors[i]; + sumSq += hitErrors[i] * hitErrors[i]; + } + const mean = sum / len; + const ur = Math.sqrt((sumSq / len) - (mean * mean)) * 10; + updateRow(elUr, string.global.ur, ur.toFixed(2)); + } else { + updateRow(elUr, string.global.ur, "0.00"); + } + } +}, ["hitErrors", "currentTime"]); \ No newline at end of file diff --git a/counters/Hit Count by Albert/mania.png b/counters/Hit Count by Albert/mania.png new file mode 100644 index 0000000..7401f4e Binary files /dev/null and b/counters/Hit Count by Albert/mania.png differ diff --git a/counters/Hit Count by Albert/metadata.txt b/counters/Hit Count by Albert/metadata.txt new file mode 100644 index 0000000..bbf69fa --- /dev/null +++ b/counters/Hit Count by Albert/metadata.txt @@ -0,0 +1,7 @@ + Usecase: Ingame Overlay, OBS Overlay +Name: Hit Count +Version: 1.0.0 +Author: Albert +CompatibleWith: Tosu +Resolution: 240x400 +authorLinks: https://github.com/AlberttFrgk/ \ No newline at end of file diff --git a/counters/Hit Count by Albert/osu.png b/counters/Hit Count by Albert/osu.png new file mode 100644 index 0000000..5711a7d Binary files /dev/null and b/counters/Hit Count by Albert/osu.png differ diff --git a/counters/Hit Count by Albert/settings.json b/counters/Hit Count by Albert/settings.json new file mode 100644 index 0000000..82ca9a0 --- /dev/null +++ b/counters/Hit Count by Albert/settings.json @@ -0,0 +1,86 @@ +[ + { "uniqueID": "sep1", "type": "label", "title": "━━━━━━━━ GLOBAL SETTINGS ━━━━━━━━", "description": "", "options": [], "value": "" }, + { "uniqueID": "globalTextColor", "type": "color", "title": "Global Text Color", "description": "Base color for all text on the overlay.", "options": [], "value": "#ffffff" }, + { "uniqueID": "swapLabelValue", "type": "checkbox", "title": "Swap Labels & Counters Position", "description": "Moves counters to the left and labels to the right.", "options": [], "value": false }, + { "uniqueID": "fontName", "type": "text", "title": "Font Name", "description": "Name font installed on Windows (e.g., Verdana, Arial, etc).", "options": [], "value": "Arial" }, + { "uniqueID": "useCustomFont", "type": "checkbox", "title": "Use Custom Font", "description": "Loads a custom font file from the overlay folder.", "options": [], "value": false }, + { "uniqueID": "customFontName", "type": "text", "title": "Custom Font Filename", "description": "Example: font.ttf", "options": [], "value": "font.ttf" }, + { "uniqueID": "labelColumnWidth", "type": "number", "title": "Label Column Width (px)", "description": "Width for text labels. If swapped, this applies to the right side.", "options": [], "value": 130 }, + { "uniqueID": "valueColumnWidth", "type": "number", "title": "Value Column Width (px)", "description": "Width for numbers. If swapped, this applies to the left side.", "options": [], "value": 90 }, + { "uniqueID": "lineHeight", "type": "number", "title": "Line Height", "description": "Vertical spacing between rows (e.g. 1.25, 1.4).", "options": [], "value": 1.25 }, + + { "uniqueID": "sep2", "type": "label", "title": "━━━━━━━━ PP SETTINGS ━━━━━━━━", "description": "", "options": [], "value": "" }, + { "uniqueID": "hidePP", "type": "checkbox", "title": "Hide PP", "description": "Hides the PP display.", "options": [], "value": false }, + { "uniqueID": "useCustomPPColors", "type": "checkbox", "title": "Use Custom PP Colors", "description": "Enable specific colors for PP.", "options": [], "value": false }, + { "uniqueID": "colorPPLabel", "type": "color", "title": "PP Label Color", "description": "", "options": [], "value": "#ffffff" }, + { "uniqueID": "colorPPVal", "type": "color", "title": "PP Number Color", "description": "", "options": [], "value": "#ffffff" }, + + { "uniqueID": "sep3", "type": "label", "title": "━━━━━━━━ UR SETTINGS ━━━━━━━━", "description": "", "options": [], "value": "" }, + { "uniqueID": "hideUR", "type": "checkbox", "title": "Hide UR", "description": "Hides the Unstable Rate display.", "options": [], "value": false }, + { "uniqueID": "useCustomURColors", "type": "checkbox", "title": "Use Custom UR Colors", "description": "Enable specific colors for UR.", "options": [], "value": false }, + { "uniqueID": "colorURLabel", "type": "color", "title": "UR Label Color", "description": "", "options": [], "value": "#ffffff" }, + { "uniqueID": "colorURVal", "type": "color", "title": "UR Number Color", "description": "", "options": [], "value": "#ffffff" }, + + { "uniqueID": "sep4", "type": "label", "title": "━━━━━━━━ RATIO SETTINGS ━━━━━━━━", "description": "", "options": [], "value": "" }, + { "uniqueID": "hideRatio", "type": "checkbox", "title": "Hide Ratio", "description": "Hides the hit ratio display.", "options": [], "value": false }, + { "uniqueID": "useCustomRatioColors", "type": "checkbox", "title": "Use Custom Ratio Colors", "description": "Enable specific colors for Ratio.", "options": [], "value": false }, + { "uniqueID": "colorRatioLabel", "type": "color", "title": "Ratio Label Color", "description": "", "options": [], "value": "#ffffff" }, + { "uniqueID": "colorRatioVal", "type": "color", "title": "Ratio Number Color", "description": "", "options": [], "value": "#ffffff" }, + + { "uniqueID": "sep5", "type": "label", "title": "━━━━━━━━ MAX COMBO SETTINGS ━━━━━━━━", "description": "", "options": [], "value": "" }, + { "uniqueID": "hideMaxCombo", "type": "checkbox", "title": "Hide Max Combo", "description": "Hides the max combo display.", "options": [], "value": false }, + { "uniqueID": "useCustomComboColors", "type": "checkbox", "title": "Use Custom Combo Colors", "description": "Enable specific colors for Combo.", "options": [], "value": false }, + { "uniqueID": "colorComboLabel", "type": "color", "title": "Combo Label Color", "description": "", "options": [], "value": "#ffffff" }, + { "uniqueID": "colorComboVal", "type": "color", "title": "Combo Number Color", "description": "", "options": [], "value": "#ffffff" }, + + { "uniqueID": "sep6", "type": "label", "title": "━━━━━━━━ EARLY / LATE SETTINGS ━━━━━━━━", "description": "", "options": [], "value": "" }, + { "uniqueID": "hideEarlyLate", "type": "checkbox", "title": "Hide Early/Late", "description": "Hides the early and late hit counts.", "options": [], "value": false }, + { "uniqueID": "useCustomEarlyLateColors", "type": "checkbox", "title": "Use Custom Early/Late Colors", "description": "Enable specific colors for Early and Late.", "options": [], "value": true }, + { "uniqueID": "colorEarlyLabel", "type": "color", "title": "Early Label Color", "description": "", "options": [], "value": "#0000ff" }, + { "uniqueID": "colorEarlyVal", "type": "color", "title": "Early Number Color", "description": "", "options": [], "value": "#ffffff" }, + { "uniqueID": "colorLateLabel", "type": "color", "title": "Late Label Color", "description": "", "options": [], "value": "#ff0000" }, + { "uniqueID": "colorLateVal", "type": "color", "title": "Late Number Color", "description": "", "options": [], "value": "#ffffff" }, + + { "uniqueID": "sep7", "type": "label", "title": "━━━━━━━━ HIT COUNTS SETTINGS ━━━━━━━━", "description": "", "options": [], "value": "" }, + { "uniqueID": "hideHitCounts", "type": "checkbox", "title": "Hide Hit Counts", "description": "Hides 300g, 300, 200, 100, 50, and Miss counts.", "options": [], "value": false }, + { "uniqueID": "useCustomHitCountLabelColors", "type": "checkbox", "title": "Use Custom Hit Count Label Colors", "description": "Enable specific colors for judgement labels.", "options": [], "value": true }, + { "uniqueID": "useCustomHitCountNumberColors", "type": "checkbox", "title": "Use Custom Hit Count Number Colors", "description": "Enable specific colors for judgement numbers.", "options": [], "value": false }, + + { "uniqueID": "sep8", "type": "label", "title": "▶ osu! Colors", "description": "", "options": [], "value": "" }, + { "uniqueID": "colorOsu300Label", "type": "color", "title": "osu! 300 Label Color", "description": "", "options": [], "value": "#50b4ff" }, + { "uniqueID": "colorOsu300Val", "type": "color", "title": "osu! 300 Number Color", "description": "", "options": [], "value": "#50b4ff" }, + { "uniqueID": "colorOsu100Label", "type": "color", "title": "osu! 100 Label Color", "description": "", "options": [], "value": "#47e547" }, + { "uniqueID": "colorOsu100Val", "type": "color", "title": "osu! 100 Number Color", "description": "", "options": [], "value": "#47e547" }, + { "uniqueID": "colorOsu50Label", "type": "color", "title": "osu! 50 Label Color", "description": "", "options": [], "value": "#ffcc22" }, + { "uniqueID": "colorOsu50Val", "type": "color", "title": "osu! 50 Number Color", "description": "", "options": [], "value": "#ffcc22" }, + + { "uniqueID": "sep9", "type": "label", "title": "▶ Taiko Colors", "description": "", "options": [], "value": "" }, + { "uniqueID": "colorTaiko300Label", "type": "color", "title": "Taiko Great Label Color", "description": "", "options": [], "value": "#ffcc22" }, + { "uniqueID": "colorTaiko300Val", "type": "color", "title": "Taiko Great Number Color", "description": "", "options": [], "value": "#ffcc22" }, + { "uniqueID": "colorTaiko100Label", "type": "color", "title": "Taiko Ok Label Color", "description": "", "options": [], "value": "#47e547" }, + { "uniqueID": "colorTaiko100Val", "type": "color", "title": "Taiko Ok Number Color", "description": "", "options": [], "value": "#47e547" }, + + { "uniqueID": "sep10", "type": "label", "title": "▶ Catch Colors", "description": "", "options": [], "value": "" }, + { "uniqueID": "colorCatch300Label", "type": "color", "title": "Catch Fruit Label Color", "description": "", "options": [], "value": "#ffcc22" }, + { "uniqueID": "colorCatch300Val", "type": "color", "title": "Catch Fruit Number Color", "description": "", "options": [], "value": "#ffcc22" }, + { "uniqueID": "colorCatch100Label", "type": "color", "title": "Catch Drop Label Color", "description": "", "options": [], "value": "#47e547" }, + { "uniqueID": "colorCatch100Val", "type": "color", "title": "Catch Drop Number Color", "description": "", "options": [], "value": "#47e547" }, + { "uniqueID": "colorCatch50Label", "type": "color", "title": "Catch Droplet Label Color", "description": "", "options": [], "value": "#50b4ff" }, + { "uniqueID": "colorCatch50Val", "type": "color", "title": "Catch Droplet Number Color", "description": "", "options": [], "value": "#50b4ff" }, + + { "uniqueID": "sep11", "type": "label", "title": "▶ Mania Colors", "description": "", "options": [], "value": "" }, + { "uniqueID": "colorMania300gLabel", "type": "color", "title": "Mania MAX Label Color", "description": "", "options": [], "value": "#ffffff" }, + { "uniqueID": "colorMania300gVal", "type": "color", "title": "Mania MAX Number Color", "description": "", "options": [], "value": "#ffffff" }, + { "uniqueID": "colorMania300Label", "type": "color", "title": "Mania Perfect Label Color", "description": "", "options": [], "value": "#ffcc22" }, + { "uniqueID": "colorMania300Val", "type": "color", "title": "Mania Perfect Number Color", "description": "", "options": [], "value": "#ffcc22" }, + { "uniqueID": "colorMania200Label", "type": "color", "title": "Mania Great Label Color", "description": "", "options": [], "value": "#47e547" }, + { "uniqueID": "colorMania200Val", "type": "color", "title": "Mania Great Number Color", "description": "", "options": [], "value": "#47e547" }, + { "uniqueID": "colorMania100Label", "type": "color", "title": "Mania Good Label Color", "description": "", "options": [], "value": "#50b4ff" }, + { "uniqueID": "colorMania100Val", "type": "color", "title": "Mania Good Number Color", "description": "", "options": [], "value": "#50b4ff" }, + { "uniqueID": "colorMania50Label", "type": "color", "title": "Mania Bad Label Color", "description": "", "options": [], "value": "#888888" }, + { "uniqueID": "colorMania50Val", "type": "color", "title": "Mania Bad Number Color", "description": "", "options": [], "value": "#888888" }, + + { "uniqueID": "sep12", "type": "label", "title": "▶ Miss Color (All Modes)", "description": "", "options": [], "value": "" }, + { "uniqueID": "colorMissLabel", "type": "color", "title": "Miss Label Color", "description": "", "options": [], "value": "#ff0000" }, + { "uniqueID": "colorMissVal", "type": "color", "title": "Miss Number Color", "description": "", "options": [], "value": "#ff0000" } +] \ No newline at end of file diff --git a/counters/Hit Count by Albert/taiko.png b/counters/Hit Count by Albert/taiko.png new file mode 100644 index 0000000..fca65f2 Binary files /dev/null and b/counters/Hit Count by Albert/taiko.png differ