Files
Runelite-EssentialsPlus/skill-reset-hourly.md
T
2026-09-18 17:18:57 +02:00

7.4 KiB
Raw Blame History

Skill "Reset Hourly" – Taskbeschreibung

Implementiere ein Rechtsklick-Feature „Reset Hourly" in Essentials Plus. Es erscheint an zwei Stellen:

  1. In-Game-XP-HUD (skills/SkillsHudOverlay.java): Rechtsklick auf das Overlay zeigt den Menüeintrag „Reset Hourly".
  2. Skills-Karte im Seitenpanel: auf der Overview-Karte als Eintrag im bestehenden Karten-Rechtsklick-Menü und im Skills-Tab als eigenes Rechtsklick-Popup auf der Karte.

Klick setzt die aktuell angezeigte XP-Zählung zurück. Es wirkt nur, wenn der global aktive Zeitraum Session oder Now ist. Alle anderen Zeiträume (1H, 1D, 2W, 3M, All) bleiben unberührt – insbesondere bleibt der Gesamt-XP-Wert (All) erhalten, weil er aus der ersten Sample-Spur abgeleitet wird.

Display-Sprache: Deutsch (Menütext trotzdem exakt Reset Hourly).

Hintergrund / Datenmodell

  • skills/SkillHistory.java speichert absolute XP-Samples pro Skill mit Zeitstempel (XpSample, List<XpSample> samples), ein File pro Charakter (JSON in essentials-plus/skills).
  • ui/TimeFilter.java + ui/TimeRange.java liefern den global geteilten Zeitraum: CURRENT("Now"), SESSION("Sess"), HOUR("1H"), DAY, TWO_WEEKS, QUARTER, ALL.
  • Gewinne werden als latest - at(start) berechnet; der Start kommt aus SkillHistory.start(filter), starts(filter) und gained(filter).
  • Es gibt noch kein Reset-/Baseline-Feld. Der Reset wird als Zeitmarker umgesetzt, nicht durch Löschen von Samples.

Neue Dateien

skills/SkillHistory.java (erweitern, keine neue Datei)

  • Zwei persistente Marker ergänzen (0 = nie zurückgesetzt):
    • public long resetSessionAt;
    • public long resetNowAt;
  • public synchronized void reset(TimeRange range):
    • SESSION → resetSessionAt = System.currentTimeMillis()
    • CURRENT → resetNowAt = System.currentTimeMillis()
    • sonst → no-op
    • dirty = true (Persistenz-Flag, existiert bereits)
  • Startlogik nur für die betroffenen Ranges clampen:
    • starts(TimeFilter): bei SESSION für jeden Skill Math.max(filter.start(), resetSessionAt); bei CURRENT pro Skill Math.max(currentStart(i, now), resetNowAt), idle (Long.MAX_VALUE) bleibt idle.
    • start(TimeFilter): bei SESSION Math.max(filter.start(), resetSessionAt); bei CURRENT nach currentStart(-1, now) clampen (Fallback now wenn idle).
    • gained(TimeFilter): Nicht-Current-Zweig von gained(filter.start()) auf gained(start(filter)) umstellen, damit der Session-Clamp greift. Current-Zweig nutzt bereits starts(filter).
  • Da range(long), levelUps(long) und activeMillis(long, int) ihren Start aus start(filter) bekommen, ziehen Charts, Stats/„XP per hour" und Level-Ups automatisch mit.
  • normalize() prüft nur skills/samples; die neuen Felder brauchen keine Migration (fehlend = 0 = nie zurückgesetzt).

skills/SkillStore.java (erweitern)

  • public void resetHourly(String key, TimeRange range): History per histories.get(key) holen; wenn vorhanden h.reset(range) und save(h) aufrufen.
  • Wird vom Plugin auf dem Executor aufgerufen (File-I/O), nicht auf dem Swing-/Client-Thread.

Zu ändernde Dateien

skills/SkillsHudOverlay.java

  • Im Konstruktor: addMenuEntry(net.runelite.api.MenuAction.RUNELITE_OVERLAY, "Reset Hourly", "Skills").
  • Die Render-Logik nutzt bereits history.starts(filter)/gained(filter) → greift den Reset automatisch, kein Render-Umbau nötig.
  • Verifikation der API (RuneLite 1.12.39): Overlay.addMenuEntry(MenuAction, option, target) und die 4-arg-Variante mit Consumer<MenuEntry> existieren; OverlayRenderer injiziert den Callback in MenuEntry.onClick(...) und feuert OverlayMenuClicked.

EssentialsPlusPlugin.java

  • Import net.runelite.client.events.OverlayMenuClicked (+ net.runelite.client.ui.overlay.Overlay falls nötig).
  • public void resetSkillsHourly(String key):
    • if (key == null) return;
    • executor.execute(() -> skillStore.resetHourly(key, timeFilter.getRange()));
    • danach Panel-Refresh: EssentialsPlusPanel p = panel; if (p != null) p.refreshSkillsLater();
  • @Subscribe public void onOverlayMenuClicked(OverlayMenuClicked event):
    • wenn event.getOverlay() == skillsHudOverlay und "Reset Hourly".equals(event.getEntry().getOption()) → resetSkillsHourly(skillKey).
  • getSkillStore(), getSkillKey(), getTimeFilter() sind vorhandene Lombok-Getter; skillsHudOverlay ist injiziert (EssentialsPlusPlugin.java:232).
  • Alternativ den Consumer<MenuEntry> der 4-arg-addMenuEntry nutzen; dann ist kein OverlayMenuClicked-Subscriber nötig (beides funktioniert). Empfehlung: Subscriber, damit die Aktion im Plugin kapselt bleibt.

ui/EssentialsPlusPanel.java (Overview-Karte)

  • In menu(String id) im case SKILLS (ca. Zeile 560) einen Eintrag ergänzen:
    • JMenuItem reset = new JMenuItem("Reset Hourly");
    • Action: Character über SkillsPanel.resolve(plugin.getSkillStore(), null, plugin.getSkillKey()) auflösen; wenn nicht null → plugin.resetSkillsHourly(h.key); und danach refreshSkills();.
  • Kein zweites Popup auf der Overview-Karte installieren: CardDrag (ui/CardDrag.java) konsumiert dort bereits Rechtsklick und öffnet menu(id) – sonst gäbe es zwei Menüs.

ui/SkillsPanel.java (Skills-Tab)

  • Nach private final SkillsCard card = new SkillsCard(null); (Zeile 61) im Konstruktor einen MouseAdapter auf card installieren:
    • bei SwingUtilities.isRightMouseButton(e) (oder e.isPopupTrigger()) ein JPopupMenu mit JMenuItem "Reset Hourly" bauen und menu.show(card, e.getX(), e.getY()) zeigen.
    • Action: plugin.resetSkillsHourly(shownKey); danach refresh(); (bzw. refreshLater()).
  • Im Skills-Tab ist keine CardDrag installiert, daher kollidiert das Popup nicht.

Verifikation

  • ./gradlew test (bestehende Tests + neuer Test unten) und ./gradlew build.
  • Neuer Test in src/test/java/com/example/essentialsplus/SkillHistoryTest.java (Muster: record per Reflection, package-private):
    • Samples über ca. eine Stunde anlegen, dann h.reset(TimeRange.CURRENT) bzw. h.reset(TimeRange.SESSION).
    • Erwartung: gained(filter) für den betroffenen Range startet neu (Baseline = Sample bei resetAt); gained(TimeRange.ALL) bleibt unverändert; 1H/1D bleiben unverändert.
  • Manuell:
    • Filter auf Sess oder Now stellen → Rechtsklick aufs XP-HUD → „Reset Hourly" → XP gained zählt ab 0 neu; Gesamt-XP (All) unverändert.
    • Filter auf 1H/1D/All stellen → „Reset Hourly" ändert die Anzeige nicht.
    • Overview-Skills-Karte rechtsklicken → „Reset Hourly"; Skills-Tab-Karte rechtsklicken → „Reset Hourly".
    • Nach Reset Datei essentials-plus/skills/<key>.json prüfen (resetSessionAt/resetNowAt gesetzt); Relog → Session wird ohnehin vom neuen Login übersteuert, Now-Marker bleibt gespeichert.

Hinweise / Einschränkungen

  • „Reset Hourly" wirkt bewusst nur auf Session und Now; bei 1H/1D/2W/3M/All passiert nichts (TimeRange-Vergleich im reset(...)).
  • Der Gesamtwert (All) bleibt in jedem Fall erhalten, da er aus der ersten Sample-Spur (samples.get(0)) berechnet wird und der Reset nur einen Start-Clamp setzt.
  • Marker sind pro Charakter im SkillHistory-JSON persistiert; ein fehlendes Feld nach älterem Stand bedeutet 0 (nie zurückgesetzt) und ist damit abwärtskompatibel.
  • record(...) bleibt unverändert: Es fügt weiterhin Samples hinzu; der Reset löscht keine Historie.