diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/ClickOperation.java b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/ClickOperation.java new file mode 100644 index 0000000000..cf56d56268 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/ClickOperation.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Hydrox6 + * Copyright (c) 2019 Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.runecraft; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +class ClickOperation +{ + Pouch pouch; + int tick; // timeout for operation + int delta; + + ClickOperation(Pouch pouch, int tick) + { + this(pouch, tick, 0); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/EssencePouchOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/EssencePouchOverlay.java new file mode 100644 index 0000000000..8acaf71aba --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/EssencePouchOverlay.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Hydrox6 + * Copyright (c) 2019 Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.runecraft; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import javax.inject.Inject; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.ui.overlay.WidgetItemOverlay; +import net.runelite.client.ui.overlay.components.TextComponent; + +class EssencePouchOverlay extends WidgetItemOverlay +{ + @Inject + EssencePouchOverlay() + { + showOnInventory(); + } + + @Override + public void renderItemOverlay(Graphics2D graphics, int itemId, WidgetItem itemWidget) + { + final Pouch pouch = Pouch.forItem(itemId); + if (pouch == null) + { + return; + } + + final Rectangle bounds = itemWidget.getCanvasBounds(); + final TextComponent textComponent = new TextComponent(); + textComponent.setPosition(new Point(bounds.x - 1, bounds.y + 8)); + textComponent.setColor(Color.CYAN); + if (pouch.isUnknown()) + { + textComponent.setText("?"); + } + else + { + textComponent.setText(Integer.toString(pouch.getHolding())); + } + textComponent.render(graphics); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/Pouch.java b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/Pouch.java new file mode 100644 index 0000000000..1806071f6a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/Pouch.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019 Hydrox6 + * Copyright (c) 2019 Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.runecraft; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import net.runelite.api.ItemID; + +enum Pouch +{ + SMALL(3), + MEDIUM(6, 3), + LARGE(9, 7), + GIANT(12, 9); + + private final int baseHoldAmount; + private final int degradedBaseHoldAmount; + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private int holding; + @Getter(AccessLevel.PACKAGE) + private boolean degraded; + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private boolean unknown = true; + + Pouch(int holdAmount) + { + this(holdAmount, -1); + } + + Pouch(int holdAmount, int degradedHoldAmount) + { + this.baseHoldAmount = holdAmount; + this.degradedBaseHoldAmount = degradedHoldAmount; + } + + static Pouch forItem(int itemId) + { + switch (itemId) + { + case ItemID.SMALL_POUCH: + return SMALL; + case ItemID.MEDIUM_POUCH: + case ItemID.MEDIUM_POUCH_5511: + return MEDIUM; + case ItemID.LARGE_POUCH: + case ItemID.LARGE_POUCH_5513: + return LARGE; + case ItemID.GIANT_POUCH: + case ItemID.GIANT_POUCH_5515: + return GIANT; + default: + return null; + } + } + + int getHoldAmount() + { + return degraded ? degradedBaseHoldAmount : baseHoldAmount; + } + + int getRemaining() + { + final int holdAmount = degraded ? degradedBaseHoldAmount : baseHoldAmount; + return holdAmount - holding; + } + + void addHolding(int delta) + { + holding += delta; + + final int holdAmount = degraded ? degradedBaseHoldAmount : baseHoldAmount; + if (holding < 0) + { + holding = 0; + } + if (holding > holdAmount) + { + holding = holdAmount; + } + } + + void degrade(boolean state) + { + if (state != degraded) + { + degraded = state; + final int holdAmount = degraded ? degradedBaseHoldAmount : baseHoldAmount; + holding = Math.min(holding, holdAmount); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftConfig.java index ae4a2f00a5..d203bcc015 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftConfig.java @@ -107,6 +107,18 @@ public interface RunecraftConfig extends Config return true; } + @ConfigItem( + keyName = "essenceOverlay", + name = "Show number of essence in pouch", + description = "This will show how many essence in your pouch.", + position = 7, + titleSection = "utilTitle" + ) + default boolean essenceOverlay() + { + return true; + } + @ConfigTitleSection( keyName = "riftsTitle", name = "Rifts", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftPlugin.java index 911e684d83..6546a54f2f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftPlugin.java @@ -24,10 +24,16 @@ */ package net.runelite.client.plugins.runecraft; +import static java.lang.Math.min; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.inject.Provides; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashSet; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Singleton; import lombok.AccessLevel; @@ -50,6 +56,7 @@ import net.runelite.api.events.DecorativeObjectSpawned; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.NpcDespawned; import net.runelite.api.events.NpcSpawned; import net.runelite.client.Notifier; @@ -99,39 +106,52 @@ public class RunecraftPlugin extends Plugin ItemID.LARGE_POUCH_5513, ItemID.GIANT_POUCH_5515 ); + private static final int INVENTORY_SIZE = 28; + private static final Pattern POUCH_CHECK_MESSAGE = Pattern.compile("^There (?:is|are) ([a-z]+)(?: pure)? essences? in this pouch\\.$"); + private static final ImmutableMap TEXT_TO_NUMBER = ImmutableMap.builder() + .put("no", 0) + .put("one", 1) + .put("two", 2) + .put("three", 3) + .put("four", 4) + .put("five", 5) + .put("six", 6) + .put("seven", 7) + .put("eight", 8) + .put("nine", 9) + .put("ten", 10) + .put("eleven", 11) + .put("twelve", 12) + .build(); - @Inject - private Client client; - - @Inject - private OverlayManager overlayManager; - - @Inject - private AbyssOverlay abyssOverlay; - - @Inject - private AbyssMinimapOverlay abyssMinimapOverlay; - - @Inject - private RunecraftOverlay runecraftOverlay; - - @Inject - private PouchOverlay pouchOverlay; - - @Inject - private RunecraftConfig config; - - @Inject - private Notifier notifier; - - @Inject - private MenuManager menuManager; - - @Inject - private EventBus eventBus; - + private final Deque clickedItems = new ArrayDeque<>(); + private final Deque checkedPouches = new ArrayDeque<>(); private final Set rifts = new HashSet<>(); private final Set abyssObjects = new HashSet<>(); + private int lastEssence; + private int lastSpace; + @Inject + private Client client; + @Inject + private OverlayManager overlayManager; + @Inject + private AbyssOverlay abyssOverlay; + @Inject + private AbyssMinimapOverlay abyssMinimapOverlay; + @Inject + private RunecraftOverlay runecraftOverlay; + @Inject + private EssencePouchOverlay essencePouchOverlay; + @Inject + private PouchOverlay pouchOverlay; + @Inject + private RunecraftConfig config; + @Inject + private Notifier notifier; + @Inject + private MenuManager menuManager; + @Inject + private EventBus eventBus; private boolean degradedPouchInInventory; private boolean degradingNotification; private boolean essPouch; @@ -140,6 +160,7 @@ public class RunecraftPlugin extends Plugin private boolean showClickBox; private boolean showRifts; private boolean degradeOverlay; + private boolean essenceOverlay; private boolean medDegrade; private boolean largeDegrade; private boolean giantDegrade; @@ -162,7 +183,17 @@ public class RunecraftPlugin extends Plugin overlayManager.add(abyssOverlay); overlayManager.add(abyssMinimapOverlay); overlayManager.add(runecraftOverlay); + overlayManager.add(essencePouchOverlay); handleSwaps(); + + for (Pouch pouch : Pouch.values()) + { + pouch.setHolding(0); + pouch.setUnknown(true); + pouch.degrade(false); + } + + lastEssence = lastSpace = -1; } @Override @@ -174,6 +205,7 @@ public class RunecraftPlugin extends Plugin overlayManager.remove(abyssOverlay); overlayManager.remove(abyssMinimapOverlay); overlayManager.remove(runecraftOverlay); + overlayManager.remove(essencePouchOverlay); removeSwaps(); } @@ -256,6 +288,153 @@ public class RunecraftPlugin extends Plugin } } } + + if (InventoryID.INVENTORY.getId() != event.getContainerId()) + { + return; + } + + final Item[] items = event.getItemContainer().getItems(); + + int newEss = 0; + int newSpace = 0; + + // Count ess/space, and change pouch states + for (Item item : items) + { + switch (item.getId()) + { + case ItemID.PURE_ESSENCE: + newEss += 1; + break; + case -1: + newSpace += 1; + break; + case ItemID.MEDIUM_POUCH: + case ItemID.LARGE_POUCH: + case ItemID.GIANT_POUCH: + Pouch pouch = Pouch.forItem(item.getId()); + pouch.degrade(false); + break; + case ItemID.MEDIUM_POUCH_5511: + case ItemID.LARGE_POUCH_5513: + case ItemID.GIANT_POUCH_5515: + pouch = Pouch.forItem(item.getId()); + pouch.degrade(true); + break; + } + } + if (items.length < INVENTORY_SIZE) + { + // Pad newSpace for unallocated inventory slots + newSpace += INVENTORY_SIZE - items.length; + } + + if (clickedItems.isEmpty()) + { + lastSpace = newSpace; + lastEssence = newEss; + return; + } + + if (lastEssence == -1 || lastSpace == -1) + { + lastSpace = newSpace; + lastEssence = newEss; + clickedItems.clear(); + return; + } + + final int tick = client.getTickCount(); + + int essence = lastEssence; + int space = lastSpace; + + + while (essence != newEss) + { + ClickOperation op = clickedItems.poll(); + if (op == null) + { + break; + } + + if (tick > op.tick) + { + continue; + } + + Pouch pouch = op.pouch; + + final boolean fill = op.delta > 0; + // How much ess can either be deposited or withdrawn + final int required = fill ? pouch.getRemaining() : pouch.getHolding(); + // Bound to how much ess or free space we actually have, and optionally negate + final int essenceGot = op.delta * min(required, fill ? essence : space); + + // if we have enough essence or space to fill or empty the entire pouch, it no + // longer becomes unknown + if (pouch.isUnknown() && (fill ? essence : space) >= pouch.getHoldAmount()) + { + pouch.setUnknown(false); + } + + + essence -= essenceGot; + space += essenceGot; + + pouch.addHolding(essenceGot); + } + + if (!clickedItems.isEmpty()) + { + return; + } + + lastSpace = newSpace; + lastEssence = newEss; + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked event) + { + switch (event.getMenuOpcode()) + { + case ITEM_FIRST_OPTION: + case ITEM_SECOND_OPTION: + case ITEM_THIRD_OPTION: + case ITEM_FOURTH_OPTION: + case ITEM_FIFTH_OPTION: + case GROUND_ITEM_THIRD_OPTION: // Take + break; + default: + return; + } + + final int id = event.getIdentifier(); + final Pouch pouch = Pouch.forItem(id); + if (pouch == null) + { + return; + } + + final int tick = client.getTickCount() + 3; + switch (event.getOption()) + { + case "Fill": + clickedItems.add(new ClickOperation(pouch, tick, 1)); + break; + case "Empty": + clickedItems.add(new ClickOperation(pouch, tick, -1)); + break; + case "Check": + checkedPouches.add(new ClickOperation(pouch, tick)); + break; + case "Take": + // Dropping pouches clears them, so clear when picked up + pouch.setHolding(0); + break; + } } @Subscribe @@ -287,6 +466,28 @@ public class RunecraftPlugin extends Plugin { notifier.notify(POUCH_DECAYED_NOTIFICATION_MESSAGE); } + + if (!checkedPouches.isEmpty()) + { + Matcher matcher = POUCH_CHECK_MESSAGE.matcher(event.getMessage()); + if (matcher.matches()) + { + final int num = TEXT_TO_NUMBER.get(matcher.group(1)); + // Keep getting operations until we get a valid one + do + { + final ClickOperation op = checkedPouches.pop(); + if (op.tick >= client.getTickCount()) + { + Pouch pouch = op.pouch; + pouch.setHolding(num); + pouch.setUnknown(false); + break; + } + } + while (!checkedPouches.isEmpty()); + } + } } @Subscribe @@ -414,6 +615,16 @@ public class RunecraftPlugin extends Plugin this.showRifts = config.showRifts(); this.showClickBox = config.showClickBox(); this.degradeOverlay = config.degradeOverlay(); + this.essenceOverlay = config.essenceOverlay(); + + if (this.essenceOverlay) + { + overlayManager.add(essencePouchOverlay); + } + else + { + overlayManager.remove(essencePouchOverlay); + } if (this.degradeOverlay) {