From 84b8b00814e471b46c6b1b2776f7878f843e5df1 Mon Sep 17 00:00:00 2001 From: rdbt <87152661+rdbtCVS@users.noreply.github.com> Date: Mon, 8 Jun 2026 22:27:49 +0100 Subject: [PATCH 1/2] Simplify module history snapshots --- gui/OverlayUtils.js | 23 +++++++ utils/Config.js | 1 + utils/ModuleBase.js | 12 ++++ utils/ModuleHistory.js | 143 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 utils/ModuleHistory.js diff --git a/gui/OverlayUtils.js b/gui/OverlayUtils.js index cae157bc..746e0cdc 100644 --- a/gui/OverlayUtils.js +++ b/gui/OverlayUtils.js @@ -16,6 +16,7 @@ import { } from './Utils'; import { GuiState, Overlays } from './core/GuiState'; import { ServerInfo } from '../utils/player/ServerInfo'; +import { ModuleHistory } from '../utils/ModuleHistory'; const { loadSettings } = require('./GuiSave'); @@ -207,6 +208,8 @@ class OverlayUtils { } resetAll() { + ModuleHistory.endAllSessions('game_unload'); + this.ids = []; this.animations = {}; this.startTimes = {}; @@ -307,6 +310,26 @@ class OverlayUtils { return this.setTrackedValue(idName, key, current + amount); } + getSessionSnapshot(idName) { + const overlay = this.ids.find((id) => id.name === idName); + const sections = (overlay?.sections || []).map((section) => { + const data = {}; + Object.entries(section.data || {}).forEach(([key, value]) => { + data[key] = typeof value === 'function' ? value() : value; + }); + + return { + title: section.title, + data, + }; + }); + + return { + trackedValues: { ...this.resolveTrackedValues(idName) }, + sections, + }; + } + getSessionElapsedMs(idName) { const startedAt = this.startTimes[idName]; if (startedAt !== undefined) { diff --git a/utils/Config.js b/utils/Config.js index 91cbb078..c27f369e 100644 --- a/utils/Config.js +++ b/utils/Config.js @@ -103,6 +103,7 @@ const manifest = { 'OverlayPositions/music_overlay.json': {}, 'webhook.json': {}, 'miningstats.json': {}, + 'module_history.json': { version: 1, sessions: [] }, 'GemstoneRoutes/empty.json': {}, 'RoutewalkerRoutes/empty.json': {}, 'TunnelMinerRoutes/empty.json': {}, diff --git a/utils/ModuleBase.js b/utils/ModuleBase.js index b39842b4..2464487d 100644 --- a/utils/ModuleBase.js +++ b/utils/ModuleBase.js @@ -4,6 +4,7 @@ import { Categories } from '../gui/categories/CategorySystem'; import { Chat } from './Chat'; import { KeyBindUtils } from './Constants'; import { MacroState } from './MacroState'; +import { ModuleHistory } from './ModuleHistory'; import { Mixin } from './MixinManager'; import { ScheduleTask } from './ScheduleTask'; import { manager } from './SkyblockEvents'; @@ -160,6 +161,15 @@ export class ModuleBase { OverlayManager.startTime(this.oid, this.isMacro); } + ModuleHistory.startSession(this.name, { + overlayId: this.oid, + category: this.subcategory, + isMacro: this.isMacro, + parentManaged: this.isParentManaged, + toggleContext, + getOverlayData: () => (this.oid ? OverlayManager.getSessionSnapshot(this.oid) : null), + }); + try { this.onEnable(); } catch (e) { @@ -173,6 +183,8 @@ export class ModuleBase { Mixin.set('macroEnabled', MacroState.isMacroRunning()); } + ModuleHistory.endSession(this.name, toggleContext); + if (this.oid) { if (this.isMacro) { OverlayManager.pauseTime(this.oid); diff --git a/utils/ModuleHistory.js b/utils/ModuleHistory.js new file mode 100644 index 00000000..de05a732 --- /dev/null +++ b/utils/ModuleHistory.js @@ -0,0 +1,143 @@ +import { Utils } from './Utils'; + +const HISTORY_FILE = 'module_history.json'; +const SAMPLE_INTERVAL_MS = 60 * 1000; + +class ModuleHistoryTracker { + constructor() { + this.history = this.loadHistory(); + this.activeSessions = {}; + this.recoverStaleSessions(); + + register('step', () => this.sampleDueSessions()).setFps(1); + register('gameUnload', () => this.endAllSessions('game_unload')); + } + + loadHistory() { + const data = Utils.getConfigFile(HISTORY_FILE); + return data.sessions ? data : { version: 1, sessions: [] }; + } + + recoverStaleSessions() { + let changed = false; + + this.history.sessions.forEach((session) => { + if (!session || !session.active) return; + + const lastPoint = session.minuteData[session.minuteData.length - 1]; + const endedAtMs = lastPoint?.timestampMs || session.enabledAtMs; + + session.active = false; + session.disabledAtMs = endedAtMs; + session.disabledAt = this.toIso(endedAtMs); + session.durationMs = Math.max(0, endedAtMs - (session.enabledAtMs || endedAtMs)); + session.endReason = 'startup_recovery'; + changed = true; + }); + + if (changed) this.saveHistory(); + } + + startSession(moduleName, options = {}) { + if (!moduleName) return; + + if (this.activeSessions[moduleName]) { + this.endSession(moduleName, 'restart'); + } + + const now = Date.now(); + const session = { + id: `${moduleName}-${now}`, + module: moduleName, + overlayId: options.overlayId || null, + category: options.category || null, + isMacro: options.isMacro === true, + parentManaged: options.parentManaged === true, + toggleContext: options.toggleContext || 'user', + enabledAtMs: now, + enabledAt: this.toIso(now), + disabledAtMs: null, + disabledAt: null, + durationMs: 0, + active: true, + overlayData: null, + minuteData: [], + }; + + this.history.sessions.push(session); + this.activeSessions[moduleName] = { + session, + getOverlayData: options.getOverlayData, + nextSampleAt: now, + }; + + this.recordSample(moduleName, now); + this.activeSessions[moduleName].nextSampleAt = now + SAMPLE_INTERVAL_MS; + this.saveHistory(); + } + + sampleDueSessions() { + const now = Date.now(); + let changed = false; + + Object.keys(this.activeSessions).forEach((moduleName) => { + const active = this.activeSessions[moduleName]; + if (!active || now < active.nextSampleAt) return; + + this.recordSample(moduleName, now); + active.nextSampleAt = Math.floor(now / SAMPLE_INTERVAL_MS) * SAMPLE_INTERVAL_MS + SAMPLE_INTERVAL_MS; + changed = true; + }); + + if (changed) this.saveHistory(); + } + + endSession(moduleName, reason = 'disabled') { + const active = this.activeSessions[moduleName]; + if (!active) return; + + const now = Date.now(); + this.recordSample(moduleName, now); + + const session = active.session; + session.active = false; + session.disabledAtMs = now; + session.disabledAt = this.toIso(now); + session.durationMs = Math.max(0, now - session.enabledAtMs); + session.endReason = reason; + + delete this.activeSessions[moduleName]; + this.saveHistory(); + } + + endAllSessions(reason = 'disabled') { + Object.keys(this.activeSessions).forEach((moduleName) => this.endSession(moduleName, reason)); + } + + recordSample(moduleName, timestampMs = Date.now()) { + const active = this.activeSessions[moduleName]; + if (!active) return; + + const session = active.session; + const overlayData = active.getOverlayData ? active.getOverlayData() : null; + session.overlayData = overlayData; + session.durationMs = Math.max(0, timestampMs - session.enabledAtMs); + session.minuteData.push({ + minute: Math.floor(timestampMs / SAMPLE_INTERVAL_MS), + timestampMs, + timestamp: this.toIso(timestampMs), + elapsedMs: session.durationMs, + overlayData, + }); + } + + saveHistory() { + Utils.writeConfigFile(HISTORY_FILE, this.history); + } + + toIso(timestampMs) { + return new Date(timestampMs).toISOString(); + } +} + +export const ModuleHistory = new ModuleHistoryTracker(); From 5b7f65ffb37e3e3b1596280b0de388d3ace6cdb8 Mon Sep 17 00:00:00 2001 From: rdbt <87152661+rdbtCVS@users.noreply.github.com> Date: Mon, 8 Jun 2026 22:46:51 +0100 Subject: [PATCH 2/2] Refine macro history tracking --- gui/OverlayUtils.js | 3 --- utils/ModuleBase.js | 22 +++++++++++++--------- utils/ModuleHistory.js | 20 +++++++++++++++----- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/gui/OverlayUtils.js b/gui/OverlayUtils.js index 746e0cdc..f9e5c0fe 100644 --- a/gui/OverlayUtils.js +++ b/gui/OverlayUtils.js @@ -16,7 +16,6 @@ import { } from './Utils'; import { GuiState, Overlays } from './core/GuiState'; import { ServerInfo } from '../utils/player/ServerInfo'; -import { ModuleHistory } from '../utils/ModuleHistory'; const { loadSettings } = require('./GuiSave'); @@ -208,8 +207,6 @@ class OverlayUtils { } resetAll() { - ModuleHistory.endAllSessions('game_unload'); - this.ids = []; this.animations = {}; this.startTimes = {}; diff --git a/utils/ModuleBase.js b/utils/ModuleBase.js index 2464487d..5a5d6622 100644 --- a/utils/ModuleBase.js +++ b/utils/ModuleBase.js @@ -161,14 +161,16 @@ export class ModuleBase { OverlayManager.startTime(this.oid, this.isMacro); } - ModuleHistory.startSession(this.name, { - overlayId: this.oid, - category: this.subcategory, - isMacro: this.isMacro, - parentManaged: this.isParentManaged, - toggleContext, - getOverlayData: () => (this.oid ? OverlayManager.getSessionSnapshot(this.oid) : null), - }); + if (this.isMacro) { + ModuleHistory.startSession(this.name, { + overlayId: this.oid, + category: this.subcategory, + isMacro: this.isMacro, + parentManaged: this.isParentManaged, + toggleContext, + getOverlayData: () => (this.oid ? OverlayManager.getSessionSnapshot(this.oid) : null), + }); + } try { this.onEnable(); @@ -183,7 +185,9 @@ export class ModuleBase { Mixin.set('macroEnabled', MacroState.isMacroRunning()); } - ModuleHistory.endSession(this.name, toggleContext); + if (this.isMacro) { + ModuleHistory.endSession(this.name, toggleContext); + } if (this.oid) { if (this.isMacro) { diff --git a/utils/ModuleHistory.js b/utils/ModuleHistory.js index de05a732..f4db6c4e 100644 --- a/utils/ModuleHistory.js +++ b/utils/ModuleHistory.js @@ -24,6 +24,7 @@ class ModuleHistoryTracker { this.history.sessions.forEach((session) => { if (!session || !session.active) return; + session.minuteData = session.minuteData || []; const lastPoint = session.minuteData[session.minuteData.length - 1]; const endedAtMs = lastPoint?.timestampMs || session.enabledAtMs; @@ -32,6 +33,14 @@ class ModuleHistoryTracker { session.disabledAt = this.toIso(endedAtMs); session.durationMs = Math.max(0, endedAtMs - (session.enabledAtMs || endedAtMs)); session.endReason = 'startup_recovery'; + session.minuteData.push({ + type: 'end', + minute: Math.floor(endedAtMs / SAMPLE_INTERVAL_MS), + timestampMs: endedAtMs, + timestamp: session.disabledAt, + elapsedMs: session.durationMs, + overlayData: session.overlayData, + }); changed = true; }); @@ -39,7 +48,7 @@ class ModuleHistoryTracker { } startSession(moduleName, options = {}) { - if (!moduleName) return; + if (!moduleName || !options.isMacro) return; if (this.activeSessions[moduleName]) { this.endSession(moduleName, 'restart'); @@ -71,7 +80,7 @@ class ModuleHistoryTracker { nextSampleAt: now, }; - this.recordSample(moduleName, now); + this.recordSample(moduleName, now, 'start'); this.activeSessions[moduleName].nextSampleAt = now + SAMPLE_INTERVAL_MS; this.saveHistory(); } @@ -84,7 +93,7 @@ class ModuleHistoryTracker { const active = this.activeSessions[moduleName]; if (!active || now < active.nextSampleAt) return; - this.recordSample(moduleName, now); + this.recordSample(moduleName, now, 'minute'); active.nextSampleAt = Math.floor(now / SAMPLE_INTERVAL_MS) * SAMPLE_INTERVAL_MS + SAMPLE_INTERVAL_MS; changed = true; }); @@ -97,7 +106,7 @@ class ModuleHistoryTracker { if (!active) return; const now = Date.now(); - this.recordSample(moduleName, now); + this.recordSample(moduleName, now, 'end'); const session = active.session; session.active = false; @@ -114,7 +123,7 @@ class ModuleHistoryTracker { Object.keys(this.activeSessions).forEach((moduleName) => this.endSession(moduleName, reason)); } - recordSample(moduleName, timestampMs = Date.now()) { + recordSample(moduleName, timestampMs = Date.now(), type = 'minute') { const active = this.activeSessions[moduleName]; if (!active) return; @@ -123,6 +132,7 @@ class ModuleHistoryTracker { session.overlayData = overlayData; session.durationMs = Math.max(0, timestampMs - session.enabledAtMs); session.minuteData.push({ + type, minute: Math.floor(timestampMs / SAMPLE_INTERVAL_MS), timestampMs, timestamp: this.toIso(timestampMs),