# 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.`, 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 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` 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`-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` 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//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//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`.