diff --git a/src/main/java/ee/futur/easygotr/EasyGOTRHighlightTarget.java b/src/main/java/ee/futur/easygotr/EasyGOTRHighlightTarget.java index dd21ade..97aa7b3 100644 --- a/src/main/java/ee/futur/easygotr/EasyGOTRHighlightTarget.java +++ b/src/main/java/ee/futur/easygotr/EasyGOTRHighlightTarget.java @@ -4,6 +4,7 @@ import lombok.Getter; import net.runelite.api.NPC; import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.widgets.Widget; import java.awt.*; import java.util.Optional; @@ -16,27 +17,33 @@ public class EasyGOTRHighlightTarget { private final TileObject tileObject; private final NPC npc; private final WorldPoint worldPoint; + private final Widget widget; private final Color color; private final String label; - private EasyGOTRHighlightTarget(TileObject tileObject, NPC npc, WorldPoint worldPoint, Color color, String label) { + private EasyGOTRHighlightTarget(TileObject tileObject, NPC npc, WorldPoint worldPoint, Widget widget, Color color, String label) { this.tileObject = tileObject; this.npc = npc; this.worldPoint = worldPoint; + this.widget = widget; this.color = color; this.label = label; } public static EasyGOTRHighlightTarget forTileObject(TileObject tileObject, Color color, String label) { - return new EasyGOTRHighlightTarget(tileObject, null, null, color, label); + return new EasyGOTRHighlightTarget(tileObject, null, null, null, color, label); } public static EasyGOTRHighlightTarget forNpc(NPC npc, Color color, String label) { - return new EasyGOTRHighlightTarget(null, npc, null, color, label); + return new EasyGOTRHighlightTarget(null, npc, null, null, color, label); } public static EasyGOTRHighlightTarget forWorldPoint(WorldPoint worldPoint, Color color, String label) { - return new EasyGOTRHighlightTarget(null, null, worldPoint, color, label); + return new EasyGOTRHighlightTarget(null, null, worldPoint, null, color, label); + } + + public static EasyGOTRHighlightTarget forWidget(Widget widget, Color color, String label) { + return new EasyGOTRHighlightTarget(null, null, null, widget, color, label); } public Optional getTileObjectOptional() { @@ -51,6 +58,10 @@ public class EasyGOTRHighlightTarget { return Optional.ofNullable(worldPoint); } + public Optional getWidgetOptional() { + return Optional.ofNullable(widget); + } + public Color getColorOrDefault(Color defaultColor) { return color != null ? color : defaultColor; } diff --git a/src/main/java/ee/futur/easygotr/EasyGOTRPlugin.java b/src/main/java/ee/futur/easygotr/EasyGOTRPlugin.java index 46e9e21..0c322d4 100644 --- a/src/main/java/ee/futur/easygotr/EasyGOTRPlugin.java +++ b/src/main/java/ee/futur/easygotr/EasyGOTRPlugin.java @@ -50,6 +50,8 @@ public class EasyGOTRPlugin extends Plugin { @Inject private EasyGOTRSceneOverlay sceneOverlay; @Inject + private EasyGOTRWidgetOverlay widgetOverlay; + @Inject private KeyManager keyManager; @Inject private OverlayManager overlayManager; @@ -127,6 +129,7 @@ public class EasyGOTRPlugin extends Plugin { keyManager.registerKeyListener(toggle); overlayManager.add(overlay); overlayManager.add(sceneOverlay); + overlayManager.add(widgetOverlay); timeout = 0; timer = Instant.now(); pouchManager.register(); @@ -134,6 +137,7 @@ public class EasyGOTRPlugin extends Plugin { currentHint = null; overlay.setHint(null); sceneOverlay.setHint(null); + widgetOverlay.setHint(null); } @Override @@ -141,6 +145,7 @@ public class EasyGOTRPlugin extends Plugin { keyManager.unregisterKeyListener(toggle); overlayManager.remove(overlay); overlayManager.remove(sceneOverlay); + overlayManager.remove(widgetOverlay); pouchManager.deregister(); riftState.deregister(); timeout = 0; @@ -149,6 +154,7 @@ public class EasyGOTRPlugin extends Plugin { currentHint = null; overlay.setHint(null); sceneOverlay.setHint(null); + widgetOverlay.setHint(null); } @Subscribe @@ -187,6 +193,7 @@ public class EasyGOTRPlugin extends Plugin { currentHint = buildHint(state); overlay.setHint(currentHint); sceneOverlay.setHint(currentHint); + widgetOverlay.setHint(currentHint); if (BaseApiPlugin.isMoving()) { //Attempt to put runes in pouch where possible @@ -217,10 +224,10 @@ public class EasyGOTRPlugin extends Plugin { } private EasyGOTRState getState() { - if (config.startingFrags() > 0) { - needsMoreStartingFragments = getFragmentCount() > config.startingFrags(); - } else if (config.startingFrags() == 0) { - needsMoreStartingFragments = true; + if (config.startingFrags() <= 0) { + needsMoreStartingFragments = false; + } else { + needsMoreStartingFragments = getFragmentCount() < config.startingFrags(); } if (!riftState.isGameStarted() && !riftState.isInAltar()) { @@ -280,8 +287,10 @@ public class EasyGOTRPlugin extends Plugin { return EasyGOTRState.LEAVE_ALTAR; } + boolean hasEssenceReady = hasEssenceToImbue(); + //If we're here, were in the game area - if (riftState.isPortalSpawned() && (pouchManager.hasEmptyPouches() || Inventory.getEmptySlots() > 15)) { + if (riftState.isPortalSpawned() && !hasEssenceReady && (pouchManager.hasEmptyPouches() || Inventory.getEmptySlots() > 15)) { return EasyGOTRState.ENTER_PORTAL; } @@ -645,9 +654,7 @@ public class EasyGOTRPlugin extends Plugin { pouchManager.setStarted(started); if (started) { timer = Instant.now(); - if (config.usePouches()) { - clientThread.invokeLater(() -> pouchManager.refreshPouches()); - } + clientThread.invokeLater(pouchManager::refreshPouches); //breakHandler.startPlugin(this); } else { @@ -811,13 +818,20 @@ public class EasyGOTRPlugin extends Plugin { builder.nextAction("Deposit runes or power the guardian."); findPortal().ifPresent(portal -> builder.highlight(EasyGOTRHighlightTarget.forTileObject(portal, Color.MAGENTA, "Use portal"))); + if (!pouchManager.hasFullPouch() && Inventory.getItemAmount(ItemID.GUARDIAN_ESSENCE) == 0) { + builder.note("Pouches and inventory are empty—leave now to stay on the max-points cycle."); + } break; case ENTER_ALTAR: builder.currentAction("Enter the highlighted guardian portal."); builder.nextAction("Craft runes inside the altar."); Optional nextAltar = Optional.ofNullable(riftState.getNextAltar()); - nextAltar.ifPresent(altar -> - builder.highlight(EasyGOTRHighlightTarget.forTileObject(altar, Color.GREEN, "Enter altar"))); + nextAltar.ifPresent(altar -> { + builder.highlight(EasyGOTRHighlightTarget.forTileObject(altar, Color.GREEN, "Enter altar")); + if (isElementalGuardian(altar)) { + builder.note("Cast Magic Imbue as you step in so every trip produces combination runes."); + } + }); if (nextAltar.isEmpty()) { builder.note("Wait for a guardian to become active."); } @@ -829,6 +843,8 @@ public class EasyGOTRPlugin extends Plugin { break; } + addGuideNotes(builder, state); + if (getInventoryRunes().isPresent() && config.dropRunes()) { builder.note("Drop leftover runes to free space (per config)."); } @@ -852,6 +868,8 @@ public class EasyGOTRPlugin extends Plugin { } }); + maybeHighlightBindingNecklace(builder); + EasyGOTRActionHint hint = builder.build(); if (hint.getCurrentAction() == null || hint.getCurrentAction().isEmpty()) { return hint.toBuilder().currentAction("Track the highlighted targets.").build(); @@ -860,6 +878,61 @@ public class EasyGOTRPlugin extends Plugin { return hint; } + private void addGuideNotes(EasyGOTRActionHint.EasyGOTRActionHintBuilder builder, EasyGOTRState state) { + switch (state) { + case WAITING_FOR_GAME: + builder.note("Max points prep: run north to the east mine as soon as the lobby opens and skip the cell table until portals spawn (per Jwell guide)."); + builder.note("Refresh binding necklace / NPC Contact between rounds and spam Quick-pass at 30 seconds."); + break; + case MOVE_TO_EAST_MINE: + builder.note("Use the starting phase to stand on the east rubble so you can instantly begin mining when the timer hits 0."); + builder.note("Dragon/infernal pick special can be queued around 2 seconds before the game starts."); + break; + case MINE_LARGE_GUARDIANS: + case MINE_REGULAR_GUARDIANS: + case MINING: + builder.note("Stay on the highlighted rocks until the portal timer hits ~1:40, then rotate to the centre to catch the huge guardian portal."); + getPortalTimerText().ifPresent(timer -> builder.note("Portal timer: " + timer + " (leave near 1:40 for max points).")); + break; + case ENTER_PORTAL: + builder.note("Use the portal immediately to access the huge guardian remains and fill every pouch before leaving."); + break; + case MINE_HUGE_GUARDIANS: + builder.note("Mine until both inventory and pouches are full, then exit to begin the combination rune cycle."); + break; + case CRAFT_RUNES: + builder.note("Cast Magic Imbue on entry, craft combination runes (fire + water) and convert them into polyelemental stones for +3 elemental energy."); + break; + case DEPOSIT_RUNES: + builder.note("Use the deposit pool (the guide binds key 4) and avoid the 'Deposit runes' button so your fire/water rune stack stays intact."); + break; + case CRAFT_ESSENCE: + case CRAFTING_ESSENCE: + builder.note("Immediately work the bench after every altar trip so fragments become essence before the next portal."); + break; + case POWER_GUARDIAN: + builder.note("Dump stones on the Great Guardian right away for energy + run energy refills before heading back to portals."); + break; + case ENTER_ALTAR: + builder.note("Prioritise elemental guardians first so you can keep crafting combination runes every trip."); + break; + case LEAVE_ALTAR: + builder.note("Exit quickly and deposit runes within ~5 seconds of the guardian reaching 100% to secure the third catalytic point."); + break; + case USE_CELL: + builder.note("Place charged cells in ascending order (weak → overcharged) to keep barriers stacked, matching the wiki strategy."); + break; + case ENTER_GAME: + builder.note("Spam Quick-pass as soon as the barrier reopens; standing north keeps travel time minimal."); + break; + case BREAK: + builder.note("Wait for the reward XP drop before leaving the arena so points are saved, just like the guide stresses."); + break; + default: + break; + } + } + private Optional determineCellPlacement() { if (!config.collectUnchargedCells()) { return Optional.empty(); @@ -1106,6 +1179,10 @@ public class EasyGOTRPlugin extends Plugin { return Inventory.search().idInList(PoweredCellList).first().isPresent(); } + private boolean hasEssenceToImbue() { + return Inventory.getItemAmount(ItemID.GUARDIAN_ESSENCE) > 0 || pouchManager.getEssenceInPouches() > 0; + } + private void enterPortal() { if (BaseApiPlugin.isMoving()) { return; @@ -1349,4 +1426,34 @@ public class EasyGOTRPlugin extends Plugin { Optional frags = Inventory.search().withId(ItemID.GUARDIAN_FRAGMENTS).first(); return frags.map(Widget::getItemQuantity).orElse(0); } + + private boolean isElementalGuardian(TileObject altar) { + if (altar == null) { + return false; + } + switch (altar.getId()) { + case GOTRState.AIR_ALTAR: + case GOTRState.WATER_ALTAR: + case GOTRState.EARTH_ALTAR: + case GOTRState.FIRE_ALTAR: + return true; + default: + return false; + } + } + + private void maybeHighlightBindingNecklace(EasyGOTRActionHint.EasyGOTRActionHintBuilder builder) { + if (hasBindingNecklaceEquipped()) { + return; + } + + Inventory.search().withId(ItemID.BINDING_NECKLACE).first().ifPresent(widget -> { + builder.highlight(EasyGOTRHighlightTarget.forWidget(widget, Color.MAGENTA, "Equip binding necklace")); + builder.note("Binding necklace broke—equip a fresh one before crafting combos."); + }); + } + + private boolean hasBindingNecklaceEquipped() { + return Equipment.search().withId(ItemID.BINDING_NECKLACE).first().isPresent(); + } } diff --git a/src/main/java/ee/futur/easygotr/EasyGOTRWidgetOverlay.java b/src/main/java/ee/futur/easygotr/EasyGOTRWidgetOverlay.java new file mode 100644 index 0000000..546b9ed --- /dev/null +++ b/src/main/java/ee/futur/easygotr/EasyGOTRWidgetOverlay.java @@ -0,0 +1,55 @@ +package ee.futur.easygotr; + +import lombok.Setter; +import net.runelite.api.widgets.Widget; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; + +import javax.inject.Inject; +import java.awt.*; + +/** + * Overlay that highlights UI widgets (inventory items) the player should interact with. + */ +public class EasyGOTRWidgetOverlay extends Overlay { + + @Setter + private EasyGOTRActionHint hint; + + @Inject + private EasyGOTRWidgetOverlay() { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + } + + @Override + public Dimension render(Graphics2D graphics) { + EasyGOTRActionHint localHint = hint; + if (localHint == null) { + return null; + } + + for (EasyGOTRHighlightTarget target : localHint.getHighlightsSafe()) { + target.getWidgetOptional().ifPresent(widget -> highlightWidget(graphics, widget, target.getColorOrDefault(Color.CYAN), target.getLabel())); + } + + return null; + } + + private void highlightWidget(Graphics2D graphics, Widget widget, Color color, String label) { + Rectangle bounds = widget.getBounds(); + if (bounds == null) { + return; + } + + graphics.setColor(color); + graphics.setStroke(new BasicStroke(2)); + graphics.draw(bounds); + + if (label != null && !label.isEmpty()) { + graphics.drawString(label, (int) bounds.getX(), (int) (bounds.getY() - 4)); + } + } +} + diff --git a/src/main/java/ee/futur/easygotr/PouchManager.java b/src/main/java/ee/futur/easygotr/PouchManager.java index 17de72f..7bf8c61 100644 --- a/src/main/java/ee/futur/easygotr/PouchManager.java +++ b/src/main/java/ee/futur/easygotr/PouchManager.java @@ -83,16 +83,9 @@ public class PouchManager { log.info("Setting Pouches: {}", pouches); } - private Optional getPouchWidget(int pouchId) { - return Inventory.search().withId(pouchId).first(); - } - private void syncPouchQuantities() { for (Pouch pouch : pouches) { - int essence = getPouchWidget(pouch.getPouchID()) - .map(Widget::getItemQuantity) - .orElse(0); - pouch.setCurrentEssence(essence); + pouch.setCurrentEssence(getPouchAmount(pouch.getPouchID(), pouch.getEssenceTotal())); } } @@ -231,4 +224,37 @@ public class PouchManager { syncPouchQuantities(); return pouches.stream().mapToInt(Pouch::getCurrentEssence).sum(); } + + private int getPouchAmount(int pouchId, int capacity) { + int varbit = getVarbitForPouch(pouchId); + if (varbit == -1 || client == null) { + return 0; + } + + int amount = client.getVarbitValue(varbit); + if (amount < 0) { + return 0; + } + if (amount > capacity) { + return capacity; + } + return amount; + } + + private int getVarbitForPouch(int pouchId) { + switch (pouchId) { + case ItemID.SMALL_POUCH: + return Varbits.ESSENCE_POUCH_SMALL_AMOUNT; + case ItemID.MEDIUM_POUCH: + return Varbits.ESSENCE_POUCH_MEDIUM_AMOUNT; + case ItemID.LARGE_POUCH: + return Varbits.ESSENCE_POUCH_LARGE_AMOUNT; + case ItemID.GIANT_POUCH: + return Varbits.ESSENCE_POUCH_GIANT_AMOUNT; + case ItemID.COLOSSAL_POUCH: + return Varbits.ESSENCE_POUCH_COLOSSAL_AMOUNT; + default: + return -1; + } + } }