This commit is contained in:
2025-11-22 15:31:10 +02:00
parent e555d5a114
commit 6faff7a032
4 changed files with 221 additions and 22 deletions

View File

@@ -4,6 +4,7 @@ import lombok.Getter;
import net.runelite.api.NPC; import net.runelite.api.NPC;
import net.runelite.api.TileObject; import net.runelite.api.TileObject;
import net.runelite.api.coords.WorldPoint; import net.runelite.api.coords.WorldPoint;
import net.runelite.api.widgets.Widget;
import java.awt.*; import java.awt.*;
import java.util.Optional; import java.util.Optional;
@@ -16,27 +17,33 @@ public class EasyGOTRHighlightTarget {
private final TileObject tileObject; private final TileObject tileObject;
private final NPC npc; private final NPC npc;
private final WorldPoint worldPoint; private final WorldPoint worldPoint;
private final Widget widget;
private final Color color; private final Color color;
private final String label; 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.tileObject = tileObject;
this.npc = npc; this.npc = npc;
this.worldPoint = worldPoint; this.worldPoint = worldPoint;
this.widget = widget;
this.color = color; this.color = color;
this.label = label; this.label = label;
} }
public static EasyGOTRHighlightTarget forTileObject(TileObject tileObject, Color color, String 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) { 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) { 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<TileObject> getTileObjectOptional() { public Optional<TileObject> getTileObjectOptional() {
@@ -51,6 +58,10 @@ public class EasyGOTRHighlightTarget {
return Optional.ofNullable(worldPoint); return Optional.ofNullable(worldPoint);
} }
public Optional<Widget> getWidgetOptional() {
return Optional.ofNullable(widget);
}
public Color getColorOrDefault(Color defaultColor) { public Color getColorOrDefault(Color defaultColor) {
return color != null ? color : defaultColor; return color != null ? color : defaultColor;
} }

View File

@@ -50,6 +50,8 @@ public class EasyGOTRPlugin extends Plugin {
@Inject @Inject
private EasyGOTRSceneOverlay sceneOverlay; private EasyGOTRSceneOverlay sceneOverlay;
@Inject @Inject
private EasyGOTRWidgetOverlay widgetOverlay;
@Inject
private KeyManager keyManager; private KeyManager keyManager;
@Inject @Inject
private OverlayManager overlayManager; private OverlayManager overlayManager;
@@ -127,6 +129,7 @@ public class EasyGOTRPlugin extends Plugin {
keyManager.registerKeyListener(toggle); keyManager.registerKeyListener(toggle);
overlayManager.add(overlay); overlayManager.add(overlay);
overlayManager.add(sceneOverlay); overlayManager.add(sceneOverlay);
overlayManager.add(widgetOverlay);
timeout = 0; timeout = 0;
timer = Instant.now(); timer = Instant.now();
pouchManager.register(); pouchManager.register();
@@ -134,6 +137,7 @@ public class EasyGOTRPlugin extends Plugin {
currentHint = null; currentHint = null;
overlay.setHint(null); overlay.setHint(null);
sceneOverlay.setHint(null); sceneOverlay.setHint(null);
widgetOverlay.setHint(null);
} }
@Override @Override
@@ -141,6 +145,7 @@ public class EasyGOTRPlugin extends Plugin {
keyManager.unregisterKeyListener(toggle); keyManager.unregisterKeyListener(toggle);
overlayManager.remove(overlay); overlayManager.remove(overlay);
overlayManager.remove(sceneOverlay); overlayManager.remove(sceneOverlay);
overlayManager.remove(widgetOverlay);
pouchManager.deregister(); pouchManager.deregister();
riftState.deregister(); riftState.deregister();
timeout = 0; timeout = 0;
@@ -149,6 +154,7 @@ public class EasyGOTRPlugin extends Plugin {
currentHint = null; currentHint = null;
overlay.setHint(null); overlay.setHint(null);
sceneOverlay.setHint(null); sceneOverlay.setHint(null);
widgetOverlay.setHint(null);
} }
@Subscribe @Subscribe
@@ -187,6 +193,7 @@ public class EasyGOTRPlugin extends Plugin {
currentHint = buildHint(state); currentHint = buildHint(state);
overlay.setHint(currentHint); overlay.setHint(currentHint);
sceneOverlay.setHint(currentHint); sceneOverlay.setHint(currentHint);
widgetOverlay.setHint(currentHint);
if (BaseApiPlugin.isMoving()) { if (BaseApiPlugin.isMoving()) {
//Attempt to put runes in pouch where possible //Attempt to put runes in pouch where possible
@@ -217,10 +224,10 @@ public class EasyGOTRPlugin extends Plugin {
} }
private EasyGOTRState getState() { private EasyGOTRState getState() {
if (config.startingFrags() > 0) { if (config.startingFrags() <= 0) {
needsMoreStartingFragments = getFragmentCount() > config.startingFrags(); needsMoreStartingFragments = false;
} else if (config.startingFrags() == 0) { } else {
needsMoreStartingFragments = true; needsMoreStartingFragments = getFragmentCount() < config.startingFrags();
} }
if (!riftState.isGameStarted() && !riftState.isInAltar()) { if (!riftState.isGameStarted() && !riftState.isInAltar()) {
@@ -280,8 +287,10 @@ public class EasyGOTRPlugin extends Plugin {
return EasyGOTRState.LEAVE_ALTAR; return EasyGOTRState.LEAVE_ALTAR;
} }
boolean hasEssenceReady = hasEssenceToImbue();
//If we're here, were in the game area //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; return EasyGOTRState.ENTER_PORTAL;
} }
@@ -645,9 +654,7 @@ public class EasyGOTRPlugin extends Plugin {
pouchManager.setStarted(started); pouchManager.setStarted(started);
if (started) { if (started) {
timer = Instant.now(); timer = Instant.now();
if (config.usePouches()) { clientThread.invokeLater(pouchManager::refreshPouches);
clientThread.invokeLater(() -> pouchManager.refreshPouches());
}
//breakHandler.startPlugin(this); //breakHandler.startPlugin(this);
} else { } else {
@@ -811,13 +818,20 @@ public class EasyGOTRPlugin extends Plugin {
builder.nextAction("Deposit runes or power the guardian."); builder.nextAction("Deposit runes or power the guardian.");
findPortal().ifPresent(portal -> findPortal().ifPresent(portal ->
builder.highlight(EasyGOTRHighlightTarget.forTileObject(portal, Color.MAGENTA, "Use 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; break;
case ENTER_ALTAR: case ENTER_ALTAR:
builder.currentAction("Enter the highlighted guardian portal."); builder.currentAction("Enter the highlighted guardian portal.");
builder.nextAction("Craft runes inside the altar."); builder.nextAction("Craft runes inside the altar.");
Optional<TileObject> nextAltar = Optional.ofNullable(riftState.getNextAltar()); Optional<TileObject> nextAltar = Optional.ofNullable(riftState.getNextAltar());
nextAltar.ifPresent(altar -> nextAltar.ifPresent(altar -> {
builder.highlight(EasyGOTRHighlightTarget.forTileObject(altar, Color.GREEN, "Enter 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()) { if (nextAltar.isEmpty()) {
builder.note("Wait for a guardian to become active."); builder.note("Wait for a guardian to become active.");
} }
@@ -829,6 +843,8 @@ public class EasyGOTRPlugin extends Plugin {
break; break;
} }
addGuideNotes(builder, state);
if (getInventoryRunes().isPresent() && config.dropRunes()) { if (getInventoryRunes().isPresent() && config.dropRunes()) {
builder.note("Drop leftover runes to free space (per config)."); 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(); EasyGOTRActionHint hint = builder.build();
if (hint.getCurrentAction() == null || hint.getCurrentAction().isEmpty()) { if (hint.getCurrentAction() == null || hint.getCurrentAction().isEmpty()) {
return hint.toBuilder().currentAction("Track the highlighted targets.").build(); return hint.toBuilder().currentAction("Track the highlighted targets.").build();
@@ -860,6 +878,61 @@ public class EasyGOTRPlugin extends Plugin {
return hint; 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<EasyGOTRHighlightTarget> determineCellPlacement() { private Optional<EasyGOTRHighlightTarget> determineCellPlacement() {
if (!config.collectUnchargedCells()) { if (!config.collectUnchargedCells()) {
return Optional.empty(); return Optional.empty();
@@ -1106,6 +1179,10 @@ public class EasyGOTRPlugin extends Plugin {
return Inventory.search().idInList(PoweredCellList).first().isPresent(); return Inventory.search().idInList(PoweredCellList).first().isPresent();
} }
private boolean hasEssenceToImbue() {
return Inventory.getItemAmount(ItemID.GUARDIAN_ESSENCE) > 0 || pouchManager.getEssenceInPouches() > 0;
}
private void enterPortal() { private void enterPortal() {
if (BaseApiPlugin.isMoving()) { if (BaseApiPlugin.isMoving()) {
return; return;
@@ -1349,4 +1426,34 @@ public class EasyGOTRPlugin extends Plugin {
Optional<Widget> frags = Inventory.search().withId(ItemID.GUARDIAN_FRAGMENTS).first(); Optional<Widget> frags = Inventory.search().withId(ItemID.GUARDIAN_FRAGMENTS).first();
return frags.map(Widget::getItemQuantity).orElse(0); 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();
}
} }

View File

@@ -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));
}
}
}

View File

@@ -83,16 +83,9 @@ public class PouchManager {
log.info("Setting Pouches: {}", pouches); log.info("Setting Pouches: {}", pouches);
} }
private Optional<Widget> getPouchWidget(int pouchId) {
return Inventory.search().withId(pouchId).first();
}
private void syncPouchQuantities() { private void syncPouchQuantities() {
for (Pouch pouch : pouches) { for (Pouch pouch : pouches) {
int essence = getPouchWidget(pouch.getPouchID()) pouch.setCurrentEssence(getPouchAmount(pouch.getPouchID(), pouch.getEssenceTotal()));
.map(Widget::getItemQuantity)
.orElse(0);
pouch.setCurrentEssence(essence);
} }
} }
@@ -231,4 +224,37 @@ public class PouchManager {
syncPouchQuantities(); syncPouchQuantities();
return pouches.stream().mapToInt(Pouch::getCurrentEssence).sum(); 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;
}
}
} }