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.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<TileObject> getTileObjectOptional() {
@@ -51,6 +58,10 @@ public class EasyGOTRHighlightTarget {
return Optional.ofNullable(worldPoint);
}
public Optional<Widget> getWidgetOptional() {
return Optional.ofNullable(widget);
}
public Color getColorOrDefault(Color defaultColor) {
return color != null ? color : defaultColor;
}

View File

@@ -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<TileObject> 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<EasyGOTRHighlightTarget> 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<Widget> 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();
}
}

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);
}
private Optional<Widget> 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;
}
}
}