Files
Runelite-EssentialsPlus/item-charges.md
T

136 lines
13 KiB
Markdown
Raw Normal View History

2026-09-18 17:18:53 +02:00
# Item charges – Taskbeschreibung
Implementiere einen **„Item charges"**-Helper in **Essentials Plus** (`com.example.essentialsplus.helpers`): Auf dem **Icon jedes Items** mit bekanntem Charge/Füllstand wird eine **Zahl** gerendert. Manche Items zeigen **mehrere Zahlen** (z. B. Toxic Blowpipe: Scales + Darts). Togglebar per Checkbox, anzeige in Inventar, Ausrüstung und (optional) Bank.
**Beispiele:**
- Toxic Blowpipe (mit Scales) → `scales`-Zahl; mit geladenen Darts → zweiter Wert (Darts-Marker, s. Hinweise).
- Dizan's Quiver → zwei Zahlen: Ammo (`CHARGES_DIZANAS_QUIVER_QUANTITY`) + Blessing (`CHARGES_DIZANAS_QUIVER_BLESSING`).
- Trident/Shadow/Tome/Bowfa etc. → eine Zahl.
- **Lootingsack**: Geldwert (Coins) als Zahl auf dem Sack-Icon.
- **Coal Bag**: Füllstand via Chat-Tracking + Persistenz (kein Varbit!).
**Format:** Ganzzahlen / Stack-Format (Geld: `QuantityFormatter.quantityToStackSize`, z. B. `1.2M`). Unbekannter Wert → `?`.
## Datenquellen & Mechanik
### 1. `CHARGES_*`-Varbits (Hauptquelle, charakterweit)
Werte via `client.getVarbitValue(id)` (`net.runelite.api.gameval.VarbitID`). Mapping Item-Gruppe → Varbit (verifiziert, Release 1.12.39). Varbit aus der **ID-Variante** des Items ableiten (s. Quelle 2), nicht aus einem Katalog — zwei identische Waffen haben nur EINEN Varbit-Wert (OSRS-Grenze!).
| Item-Gruppe (ItemID-Konstanten) | VarbitID-Konstante |
|---|---|
| `TOXIC_BLOWPIPE`, `TOXIC_BLOWPIPE_LOADED`, `TOXIC_BLOWPIPE_ORNAMENT`, `TOXIC_BLOWPIPE_LOADED_ORNAMENT` | `CHARGES_TOXIC_BLOWPIPE_QUANTITY` |
| `SERPENTINE_HELM`, `SERPENTINE_HELM_CHARGED`, `_CYAN`, `_CYAN_CHARGED`, `_RED`, `_RED_CHARGED` | `CHARGES_SERPENTINE_HELM_QUANTITY` |
| `WARPED_SCEPTRE`, `WARPED_SCEPTRE_UNCHARGED` | `CHARGES_WARPED_SCEPTRE_QUANTITY` |
| `TUMEKENS_SHADOW`, `TUMEKENS_SHADOW_UNCHARGED`, `TUMEKENS_HEKA`, `TUMEKENS_HEKA_UNCHARGED` | `CHARGES_TUMEKENS_SHADOW_QUANTITY` |
| `SANGUINESTI_STAFF`, `SANGUINESTI_STAFF_UNCHARGED`, `_OR`, `_UNCHARGED_OR` | `CHARGES_SANGUINESTI_STAFF_QUANTITY` |
| `TRIDENT_OF_THE_SEAS` (+ `_E`), jew. gecharge/un-gecharge-Varianten | `CHARGES_TRIDENT_OF_THE_SEAS_QUANTITY` bzw. `..._E_QUANTITY` |
| `TOXIC_STAFF_OF_THE_DEAD` (+ `_UNCHARGED`, `_OR`-Varianten) | `CHARGES_TOXIC_STAFF_OF_THE_DEAD_QUANTITY` |
| `BOW_OF_FAERDHINEN`, `BOW_OF_FAERDHINEN_INACTIVE`, `_INFINITE*` | `CHARGES_BOW_OF_FAERDHINEN_QUANTITY` (+ `CHARGES_BOW_OF_FAERDHINEN_CORRUPTION` für korrupte Variante) |
| `BLADE_OF_SAELDOR`, `BLADE_OF_SAELDOR_INACTIVE`, `_INFINITE` | `CHARGES_BLADE_OF_SAELDOR_QUANTITY` (+ `_CORRUPTION`) |
| `VENATOR_BOW`, `VENATOR_BOW_UNCHARGED`, `VENATOR_BOW_ORNAMENT(_UNCHARGED)` | `CHARGES_VENATOR_BOW_QUANTITY` |
| `TONALZTICS_OF_RALOS_CHARGED`, `_UNCHARGED` | `CHARGES_TONALZTICS_OF_RALOS_QUANTITY` |
| `ARCLIGHT` | `CHARGES_ARCLIGHT_QUANTITY` |
| `DIZANAS_QUIVER_CHARGED`, `_UNCHARGED`, `_INFINITE`, `_BROKEN`, Trouver-Varianten | `CHARGES_DIZANAS_QUIVER_QUANTITY` (Ammo) + `CHARGES_DIZANAS_QUIVER_BLESSING` (Blessing) — zwei Zahlen |
| `TOME_OF_FIRE`, `TOME_OF_FIRE_UNCHARGED` | `CHARGES_TOME_OF_FIRE_QUANTITY` |
| `TOME_OF_WATER`, `TOME_OF_WATER_UNCHARGED` | `CHARGES_TOME_OF_WATER_QUANTITY` |
| `TOME_OF_EARTH`, `TOME_OF_EARTH_UNCHARGED` | `CHARGES_TOME_OF_EARTH_QUANTITY` |
| `ASH_SANCTIFIER` | `CHARGES_ASH_SANCTIFIER_QUANTITY` |
| `BONECRUSHER` | `CHARGES_BONECRUSHER_QUANTITY` |
| `BRYOPHYTAS_STAFF` | `CHARGES_BRYOPHYTAS_STAFF_QUANTITY` |
| `CIRCLET_OF_WATER` | `CHARGES_CIRCLET_OF_WATER_QUANTITY` |
| `SOUL_BEARER` | `CHARGES_SOUL_BEARER_QUANTITY` |
| `CELESTIAL_RING`, `CELESTIAL_RING_CHARGED` | `CHARGES_CELESTIAL_RING_QUANTITY` |
| `WILD_CAVE_SCEPTRE_CHARGED`, `WILD_CAVE_SCEPTRE_UNCHARGED` | `CHARGES_WILDERNESS_WEAPON_QUANTITY` |
| `BRACELET_OF_ETHEREUM` | `CHARGES_BRACELET_OF_ETHEREUM_QUANTITY` |
| `RING_OF_SUFFERING` | `CHARGES_RING_OF_SUFFERING_QUANTITY` |
| `XERIC_TALISMAN`, `XERIC_TALISMAN_EMPTY` | `CHARGES_XERICS_TALISMAN_QUANTITY` |
| `RING_OF_SHADOWS`, `RING_OF_SHADOWS_UNCHARGED` | `CHARGES_RING_OF_SHADOWS_QUANTITY` |
| `ECHO_BOOTS` | `CHARGES_ECHO_BOOTS_QUANTITY` |
| `AMULET_OF_BLOOD_FURY` | `CHARGES_AMULET_OF_BLOOD_FURY_QUANTITY` |
| `GIANTSOUL_AMULET_CHARGED`, `_UNCHARGED` | `CHARGES_GIANTSOUL_AMULET_QUANTITY` |
| `EYE_OF_AYAK`, `EYE_OF_AYAK_UNCHARGED` | `CHARGES_EYE_OF_AYAK_QUANTITY` |
| `HORN_OF_PLENTY`, `_UNCHARGED`, `SAILORS_AMULET`, `_EMPTY`, `COWBELL_AMULET`, `_EMPTY`, `PENDANT_OF_ATES`, `_EMPTY` | jew. `CHARGES_*_QUANTITY`-Varbit |
| Crystal Armour/Tools (Crystal, `CRYSTAL_*` piece IDs) | `CHARGES_CRYSTAL_ARMOUR_QUANTITY`, `CHARGES_CRYSTAL_TOOLS_QUANTITY`, `CHARGES_CRYSTAL_WEAK_QUANTITY` |
| `ALCHEMIST_RING` | `CHARGES_ALCHEMIST_AMULET_QUANTITY` |
Vollständige Liste ergänzen per `javap -constants net.runelite.api.gameval.VarbitID | grep 'CHARGES_.*_QUANTITY'` gegen das runelite-api-Jar (release 1.12.39; Pfad s. **Referenzen**). Das sind ~55 Konstanten; es kommen auch Solo-/Sailing-Charges vor (`CHARGES_PLANK_SACK*`, `CHARGES_BOWSTRING_SPOOL*`), nur relevante übernehmen. Stichproben-Hinweis: `DBTableID$Charges$Row` (in runelite-api `net.runelite.api.gameval.DBTableID`) listet dieselben Charge-Typen als DB-Rows (Referenz/Querschnitt, nicht zwingend zur Laufzeit nötig).
Varianten-Ableitung für die Tabelle: Einträge in `net.runelite.api.gameval.ItemID` mit typischen Suffixen (`_CHARGED`, `_UNCHARGED`, `_INACTIVE`, `_EMPTY`, `_LOADED`, `_OR`, `_CYAN/_RED`, `_TROUVER`, `_INFINITE`, `_BROKEN`) — per `javap -constants ... ItemID | grep` verifizieren.
### 2. Item-ID-Statusvarianten (Ladungs-Status)
Viele Items wechseln die **Item-ID** bei leer/laden — mehrere exakte IDs auf dieselbe Charge abbilden bzw. „0"-Semantik:
- `TOXIC_BLOWPIPE` (12924) vs `TOXIC_BLOWPIPE_LOADED` (12926): **Darts geladen: ja/nein** (zweiter Wert, Marker; die exakte **Dart-Anzahl ist NICHT in der API** — kein Varbit/Container). `DRYGORE_BLOWPIPE` vs `DRYGORE_BLOWPIPE_LOADED` analog.
- `BOW_OF_FAERDHINEN_INACTIVE` → 0; `BLADE_OF_SAELDOR_INACTIVE` → 0; `*_INFINITE` → „∞".
- Alle `*_UNCHARGED`/`*_EMPTY` → 0 bzw. kein Varbit abfragen.
### 3. Container (Echtzeit; per GameTick gelesen)
- **Lootingsack**: `client.getItemContainer(InventoryID.LOOTING_BAG)` (auch `LOOTING_BAG_OPEN`). Geldwert = Σ qty von `ItemID.COINS` im Container. (Plugin hat bereits `readContainer()`/WealthStore für Loot — hier eigenes Lesen im Overlay-Tick, da das Overlay nicht an den Container-Event gebunden ist.)
- **Dizan's Quiver Ammo**: `InventoryID.DIZANAS_QUIVER_AMMO` als Crosscheck/Geld-Anzeige zum Varbit-Wert möglich.
- **Coal/Gem/Herb sack**: kein Container, kein Varbit (verifiziert) → Quelle 4.
### 4. Coal Bag — Chat-Tracking + Persistenz
- Kein Varbit/Container in `VarbitID`/`InventoryID` (verifiziert; nur `ItemID.COAL_BAG`/`COAL_BAG_OPEN`).
- Füllstand (max **27**) ableiten aus **Chat-Nachrichten** + **letzten bekannten Wert pro Account** in `configManager` persistieren (key `coalBag.<accountKey>`, Wert int 0–27; `accountKey`-Konvention wie bestehende Persistenz im Plugin).
- Zu verarbeitende Muster (ChatMessage-Filter `Game`): Check-/Fill-Feedback „[…]`X` coal in your coal bag]" o. Ä. *exakt in-game verifizieren* (2024 wurde Feedback von Popups auf Chat umgestellt); zusätzlich Empty → 0; bei unbekannter Nachricht/Login ohne Check → **`?`** anzeigen statt falscher Zahl.
- `ChatMessage`-Events: Fehler-tolerant parsen (Regex), bei Update sofort speichern. Beim `LOGOUT` aktuellen Wert speichern. Optional Rechtsklick „Coal bag vergessen" → Wert nicht aus Chat-Nachrichten ableiten.
## Neue Dateien
### `helpers/ItemCharges.java` (`@Singleton`, Logik)
- **Datenmodell**: `ChargeValue { String text; Color color; }` (+ optional 2. Wert). Lookup-API `List<ChargeValue> chargesFor(int itemId)`:
1. exakte `itemId` in Tabelle (Load/Empty/Inactive-Varianten); dann
2. `itemManager.canonicalize(itemId)` (Noten/Placeholder) erneut prüfen.
- **`CHARGE_MAP`**: `Map<Integer itemId, ChargeSource>` statisch aufgebaut aus den ItemID-Konstanten (Quelle 1+2); `ChargeSource` = Varbit-Konstante bzw. `COAL_BAG`-Typ bzw. `LOOTING_BAG`-Container-Typ.
- **State**: Coal-Bag-Count pro Account + `Map<Integer, ...>`-Cache nur so weit nötig (Varbit-Lesen ist billig; kein harter Cache zwingend).
- **Events** (im Plugin registriert, Muster `TreeCooldowns`):
- `GameTick` → die zwei Bag-Varbits/Container aktualisieren.
- `VarbitChanged` → relevante Varbit-Id → Value merken.
- `ChatMessage` → Coal-Bag-Messages parsen (s. o.), speichern.
- `GameStateChanged` → `!= LOGGED_IN` → `reset()`, Coal-Bag-Count aus `configManager` laden bzw. auf „?" zurücksetzen.
- **Reset**: Coal-Bag-Cache auf `?` beim Logout (Persistenz bleibt).
### `helpers/ItemChargesOverlay.java` (`WidgetItemOverlay`)
- `showOnInventory(); showOnEquipment(); showOnBank();` — Bank über Config-Sub-Key steuerbar (s. unten).
- `renderItemOverlay(g, itemId, WidgetItem)`:
- `if (!config.itemCharges()) return;`
- `List<ChargeValue>` aus `chargesFor(itemId)`; leer → nichts rendern.
- Zahl unten-links am Icon (offizielles RuneLite-Muster): `bounds.x - 1, bounds.y + 15`, mit Schatten (`Fonts.RUNESCAPE_SMALL` bzw. `TimerTextSize`), Farbe aus `itemChargesColor`; **2. Wert** (Blowpipe-Darts, Quiver-Blessing) darüber (`bounds.y - 3`) in `itemChargesSecondaryColor`.
- Geld (Lootingsack): Stack-Size-Format; Low-Charge-Färbung ab `itemChargesLowAt` → `itemChargesLowColor`.
- Coins/Loot-Bag-Wert: aus `chargesFor(LOOTING_BAG_OPEN/LOOTING_BAG)`-Container-Lesung im Overlay-Tick (Falls Container null → kein Wert).
## Zu ändernde Dateien
- **`EssentialsPlusConfig.java`**: `@ConfigItem itemCharges` (bool, default `false`, position ~37). Optionen als `configManager`-Keys via `setHelperOption` (Muster wie treeCooldowns):
- `itemChargesWeapons` (bool, true) — Varbit-Charges an/aus
- `itemChargesBags` (bool, true) — Loot-Bag-Geld + Coal-Bag-Füllstand an/aus
- `itemChargesBank` (bool, false) — Overlay auch in Bank (Achtung Hinweise!)
- `itemChargesSize` (`TimerTextSize`, SMALL), `itemChargesColor` (Color, weiß), `itemChargesSecondaryColor` (Color, hellgrau `0xC0C0C0`), `itemChargesLowColor` (Color, rot `0xFF3333`), `itemChargesLowAt` (int, 0 = aus)
- **`EssentialsPlusPlugin.java`**: `@Inject ItemCharges` + `ItemChargesOverlay`; `startUp()`: `eventBus.register(itemCharges)`, `overlayManager.add(itemChargesOverlay)`; `shutDown()`: spiegeln + `itemCharges.reset()`. `isHelperOption()`: `case "itemCharges"` → `config.itemCharges()`. `setHelperOption`/`onConfigChanged`: Bestände für `itemcharges*`-Keys refresh (Muster solcher Bestands-Abschnitte).
- **`HelpersCard.java`**: neues `JCheckBox "Item charges"` (Tooltip: „Charge-Zahl auf Item-Icons (Blowpipe, Trident, Tome, …) + Loot-Bag-Geld & Coal-Bag-Füllstand. Rechtsklick für Optionen"). Rechtsklick-Menü analog `styleMenu`/`treeMenu`: Toggles `itemChargesWeapons`/`itemChargesBags`/`itemChargesBank`, Größe, Farben, Schwellenwert, Reset. `refresh()` erweitern.
## Verifikation
- `./gradlew compileJava` (bzw. `./gradlew deploy` für Bolt).
- Manuell: Blowpipe mit Scales (nur Scales-Zahl) → mit Darts laden (2. Zahl erscheint); Trident/Shadow/Sang/Tome (Zahl); Lootingsack mit Coins betreten (Geld auf Sack-Icon); Coal Bag füllen → Zahl steigt, Empty → 0, Relog → Wert bleibt; Bank öffnen mit `itemChargesBank` → Zahlen auch dort; beide Blowpipes (leer + geladen) im Inventar → korrekte per-ID-Darstellung.
## Hinweise / Einschränkungen
- `CHARGES_*`-Varbits sind **charakterweit, nicht pro Item-Exemplar**: zwei unterschiedlich geladene Tridents zeigen denselben Wert (OSRS-Systemgrenze, deckungsgleich mit dem In-Game-Charge-Overlay).
- **Bank-Anzeige** dementsprechend unzuverlässig für „unbenutzte" Kopien; default daher `itemChargesBank = false`.
- Coal Bag: nur Heuristik (Chat + Persistenz), kein API-Wert; nach Login unbekannt → `?`.
- Blowpipe: Dart-**Anzahl** nicht in der API → 2. Wert ist Darts-geladen-Marker, keine Zahl.
- Keine Chat-Popup-abhängigkeit (Popup = ist abgeschaltet hat), ausschließlich ChatMessage-Events verwenden.
## Referenzen
- `tree-cooldown.md` (gleiche Struktur), `reflect.txt`/Cachierte Jars für Konstanten:
- runelite-api: `/home/hikyu/.gradle/caches/modules-2/files-2.1/net.runelite/runelite-api/1.12.39/<hash>/runelite-api-1.12.39.jar` → `net/runelite/api/gameval/{VarbitID,ItemID,InventoryID,DBTableID$Charges$Row}`
- client: `/home/hikyu/.gradle/caches/modules-2/files-2.1/net.runelite/client/1.12.39/<hash>/client-1.12.39.jar` → `net.runelite.client.ui.overlay.WidgetItemOverlay`, `components.TextComponent`, `ui.FontManager`
- Konventionen: `helpers/TreeCooldowns.java` (Logik+Events), `helpers/TreeCooldownOverlay.java`/`ItemPriceOverlay.java` (Overlay-Zeichnung), `HelpersCard.java` (Checkbox+Menü), `EssentialsPlusPlugin.isHelperOption/setHelperOption/onConfigChanged`.