From 9b50f16339270431a7f9de658c26bcb23ad99fb7 Mon Sep 17 00:00:00 2001 From: SomeoneWithAnInternetConnection <34518321+SomeoneWithAnInternetConnection@users.noreply.github.com> Date: Wed, 21 Feb 2018 14:30:53 -0500 Subject: [PATCH 1/4] Expose isStackable on ItemComposition objects --- .../net/runelite/api/ItemComposition.java | 7 ++++ .../mixins/RSItemCompositionMixin.java | 41 +++++++++++++++++++ .../runelite/rs/api/RSItemComposition.java | 9 ++++ 3 files changed, 57 insertions(+) create mode 100644 runelite-mixins/src/main/java/net/runelite/mixins/RSItemCompositionMixin.java diff --git a/runelite-api/src/main/java/net/runelite/api/ItemComposition.java b/runelite-api/src/main/java/net/runelite/api/ItemComposition.java index 600628e9ec..f4ee757ad6 100644 --- a/runelite-api/src/main/java/net/runelite/api/ItemComposition.java +++ b/runelite-api/src/main/java/net/runelite/api/ItemComposition.java @@ -74,4 +74,11 @@ public interface ItemComposition * @return true if members-only, false otherwise. */ boolean isMembers(); + + /** + * Returns whether or not the item stacks in the players' inventories + * + * @return true if stackable, false otherwise + */ + boolean isStackable(); } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSItemCompositionMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemCompositionMixin.java new file mode 100644 index 0000000000..766fb30735 --- /dev/null +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemCompositionMixin.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * 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.mixins; + +import net.runelite.api.mixins.Inject; +import net.runelite.api.mixins.Mixin; +import net.runelite.rs.api.RSItemComposition; + +@Mixin(RSItemComposition.class) +public abstract class RSItemCompositionMixin implements RSItemComposition +{ + @Inject + @Override + public boolean isStackable() + { + return getIsStackable() != 0; + } +} diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSItemComposition.java b/runescape-api/src/main/java/net/runelite/rs/api/RSItemComposition.java index 3237a7dfd7..7a738f671f 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSItemComposition.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSItemComposition.java @@ -58,6 +58,15 @@ public interface RSItemComposition extends ItemComposition @Override boolean isMembers(); + /** + * You probably want {@link #isStackable} + *

+ * This is the {@code int} that client code uses internally to represent this true/false value. It appears to only ever be set to 1 or 0 + * @return 0 when this type of item isn't stackable, 1 otherwise + */ + @Import("isStackable") + int getIsStackable(); + @Import("maleModel") int getMaleModel(); } From 7705d17c275736913caa8e8e8d9419d4bded1f92 Mon Sep 17 00:00:00 2001 From: SomeoneWithAnInternetConnection <34518321+SomeoneWithAnInternetConnection@users.noreply.github.com> Date: Sat, 3 Feb 2018 21:44:22 -0500 Subject: [PATCH 2/4] Rename progress to state in GrandExchangeOffer The field formerly known as "progress" encoded what state the offer was in which is a weird 4-bit thing that includes information about whether the offer is a buy or sell offer, whether it's finished, and some other, less well-defined info about (what seems to be) the offer's live-ness. How close an offer is to being finished (ie its progress) is only really based on how many items have bought/sold out of the total amount requested/offered. --- .../main/java/net/runelite/rs/api/RSGrandExchangeOffer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSGrandExchangeOffer.java b/runescape-api/src/main/java/net/runelite/rs/api/RSGrandExchangeOffer.java index 572718976d..eb505f9367 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSGrandExchangeOffer.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSGrandExchangeOffer.java @@ -43,6 +43,7 @@ public interface RSGrandExchangeOffer @Import("spent") int getSpent(); - @Import("progress") - byte getProgress(); + @Import("state") + byte getRSState(); + } From 971b10c26c7268c4ce657aba6072f23b9ef5368b Mon Sep 17 00:00:00 2001 From: SomeoneWithAnInternetConnection <34518321+SomeoneWithAnInternetConnection@users.noreply.github.com> Date: Sun, 25 Feb 2018 13:42:09 -0500 Subject: [PATCH 3/4] runelite-api: Add Grand Exchange offer support This includes a new event: GrandExchangeOfferChanged which is fired whenever a grand exchange offer is updated, either manually by the player, or as a result of the offer being fufilled (partially or otherwise) --- .../main/java/net/runelite/api/Client.java | 2 + .../net/runelite/api/GrandExchangeOffer.java | 40 +++++++ .../runelite/api/GrandExchangeOfferState.java | 57 ++++++++++ .../api/events/GrandExchangeOfferChanged.java | 35 ++++++ .../net/runelite/mixins/RSClientMixin.java | 24 ++++ .../mixins/RSGrandExchangeOfferMixin.java | 107 ++++++++++++++++++ .../runelite/rs/api/RSGrandExchangeOffer.java | 9 +- 7 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 runelite-api/src/main/java/net/runelite/api/GrandExchangeOffer.java create mode 100644 runelite-api/src/main/java/net/runelite/api/GrandExchangeOfferState.java create mode 100644 runelite-api/src/main/java/net/runelite/api/events/GrandExchangeOfferChanged.java create mode 100644 runelite-mixins/src/main/java/net/runelite/mixins/RSGrandExchangeOfferMixin.java diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index 4a234ed84a..18f2bb6052 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -138,6 +138,8 @@ public interface Client extends GameEngine HashTable getComponentTable(); + GrandExchangeOffer[] getGrandExchangeOffers(); + boolean isPrayerActive(Prayer prayer); int getSkillExperience(Skill skill); diff --git a/runelite-api/src/main/java/net/runelite/api/GrandExchangeOffer.java b/runelite-api/src/main/java/net/runelite/api/GrandExchangeOffer.java new file mode 100644 index 0000000000..639908a1fe --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/GrandExchangeOffer.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017, 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.api; + +public interface GrandExchangeOffer +{ + int getQuantitySold(); + + int getItemId(); + + int getTotalQuantity(); + + int getPrice(); + + int getSpent(); + + GrandExchangeOfferState getState(); +} diff --git a/runelite-api/src/main/java/net/runelite/api/GrandExchangeOfferState.java b/runelite-api/src/main/java/net/runelite/api/GrandExchangeOfferState.java new file mode 100644 index 0000000000..0c68e493ba --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/GrandExchangeOfferState.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017, 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.api; + +/** + * Describes the state of a Grand Exchange offer + */ +public enum GrandExchangeOfferState +{ + /** + * An empty slot. + */ + EMPTY, + /** + * Any offer that's been cancelled + */ + CANCELLED, + /** + * A buy offer that is currently in progress. + */ + BUYING, + /** + * A buy offer that has completed. + */ + BOUGHT, + /** + * A sell offer that is currently in progress. + */ + SELLING, + /** + * A sell offer that has completed. + */ + SOLD; +} diff --git a/runelite-api/src/main/java/net/runelite/api/events/GrandExchangeOfferChanged.java b/runelite-api/src/main/java/net/runelite/api/events/GrandExchangeOfferChanged.java new file mode 100644 index 0000000000..72db3b901d --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/GrandExchangeOfferChanged.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 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.api.events; + +import lombok.Data; +import net.runelite.api.GrandExchangeOffer; + +@Data +public class GrandExchangeOfferChanged +{ + private GrandExchangeOffer offer; + private int slot; +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java index 93d07ca64e..98a2dc5918 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -29,6 +29,7 @@ import java.util.List; import net.runelite.api.ChatMessageType; import net.runelite.api.ClanMember; import net.runelite.api.GameState; +import net.runelite.api.GrandExchangeOffer; import net.runelite.api.IndexedSprite; import net.runelite.api.InventoryID; import net.runelite.api.MenuAction; @@ -44,6 +45,7 @@ import net.runelite.api.Skill; import net.runelite.api.Varbits; import net.runelite.api.events.ExperienceChanged; import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GrandExchangeOfferChanged; import net.runelite.api.events.MapRegionChanged; import net.runelite.api.events.PlayerMenuOptionsChanged; import net.runelite.api.events.ResizeableChanged; @@ -483,6 +485,28 @@ public abstract class RSClientMixin implements RSClient eventBus.post(gameStateChange); } + @Inject + @FieldHook("grandExchangeOffers") + public static void onGrandExchangeOffersChanged(int idx) + { + if (idx == -1) + { + return; + } + + GrandExchangeOffer internalOffer = client.getGrandExchangeOffers()[idx]; + + if (internalOffer == null) + { + return; + } + + GrandExchangeOfferChanged offerChangedEvent = new GrandExchangeOfferChanged(); + offerChangedEvent.setOffer(internalOffer); + offerChangedEvent.setSlot(idx); + eventBus.post(offerChangedEvent); + } + @FieldHook("settings") @Inject public static void settingsChanged(int idx) diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSGrandExchangeOfferMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSGrandExchangeOfferMixin.java new file mode 100644 index 0000000000..f132e84ab4 --- /dev/null +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSGrandExchangeOfferMixin.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * 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.mixins; + +import net.runelite.api.GrandExchangeOfferState; +import static net.runelite.api.GrandExchangeOfferState.BOUGHT; +import static net.runelite.api.GrandExchangeOfferState.BUYING; +import static net.runelite.api.GrandExchangeOfferState.CANCELLED; +import static net.runelite.api.GrandExchangeOfferState.EMPTY; +import static net.runelite.api.GrandExchangeOfferState.SELLING; +import static net.runelite.api.GrandExchangeOfferState.SOLD; +import net.runelite.api.mixins.Inject; +import net.runelite.api.mixins.Mixin; +import net.runelite.rs.api.RSGrandExchangeOffer; + +@Mixin(RSGrandExchangeOffer.class) +public abstract class RSGrandExchangeOfferMixin implements RSGrandExchangeOffer +{ + + /* + Internally a GrandExchangeOffer's state is represented as 4 flags + packed into the lower half of a byte. They are: + */ + + //Set for sell offers, unset for buy offers + private static final int IS_SELLING = 1 << 3; // 0b1000 + + + /* + Set for offers that have finished, either because they've + been filled, or because they were cancelled + */ + private static final int COMPLETED = 1 << 2; // 0b0100 + + /* + Set for offers that are actually live + NB: Insta-buy/sell offers will be simultaneously LIVE and LOCAL + */ + private static final int LIVE = 1 << 1; // 0b0010 + + //True for just-made, just-cancelled, completely cancelled, and completed offers + private static final int LOCAL = 1; + + @Inject + @Override + public GrandExchangeOfferState getState() + { + byte code = getRSState(); + boolean isSelling = (code & IS_SELLING) == IS_SELLING; + boolean isFinished = (code & COMPLETED) == COMPLETED; + + + if (code == 0) + { + return EMPTY; + } + else if (isFinished && getQuantitySold() < getTotalQuantity()) + { + return CANCELLED; + } + else if (isSelling) + { + if (isFinished) + { + return SOLD; + } + else // if isUnfinished + { + return SELLING; + } + } + else // if isBuying + { + if (isFinished) + { + return BOUGHT; + } + else // if isUnfinished + { + return BUYING; + } + } + } +} diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSGrandExchangeOffer.java b/runescape-api/src/main/java/net/runelite/rs/api/RSGrandExchangeOffer.java index eb505f9367..d88e582ef7 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSGrandExchangeOffer.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSGrandExchangeOffer.java @@ -24,26 +24,33 @@ */ package net.runelite.rs.api; +import net.runelite.api.GrandExchangeOffer; import net.runelite.mapping.Import; -public interface RSGrandExchangeOffer +public interface RSGrandExchangeOffer extends GrandExchangeOffer { @Import("quantitySold") + @Override int getQuantitySold(); @Import("itemId") + @Override int getItemId(); @Import("totalQuantity") + @Override int getTotalQuantity(); @Import("price") + @Override int getPrice(); @Import("spent") + @Override int getSpent(); @Import("state") byte getRSState(); + } From 926bc9374fcb52e859ce53cf2187aeacc37233f9 Mon Sep 17 00:00:00 2001 From: SomeoneWithAnInternetConnection <34518321+SomeoneWithAnInternetConnection@users.noreply.github.com> Date: Sun, 25 Feb 2018 13:42:57 -0500 Subject: [PATCH 4/4] Create Grand Exchange plugin This plugin tracks the player's current GE offers, displaying them in a sidebar panel. Based off of PR #193 --- .../grandexchange/GrandExchangeOfferSlot.java | 195 ++++++++++++++++++ .../grandexchange/GrandExchangePanel.java | 61 ++++++ .../grandexchange/GrandExchangePlugin.java | 78 +++++++ .../client/plugins/grandexchange/ge_icon.png | Bin 0 -> 15952 bytes .../GrandExchangeOfferSlotTest.java | 62 ++++++ 5 files changed, 396 insertions(+) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlot.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/ge_icon.png create mode 100644 runelite-client/src/test/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlotTest.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlot.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlot.java new file mode 100644 index 0000000000..ac3bde95cf --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlot.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * 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.grandexchange; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Color; +import javax.annotation.Nullable; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.border.TitledBorder; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.GrandExchangeOffer; +import net.runelite.api.GrandExchangeOfferState; +import static net.runelite.api.GrandExchangeOfferState.EMPTY; +import net.runelite.api.ItemComposition; +import net.runelite.client.game.ItemManager; + +@Slf4j +public class GrandExchangeOfferSlot extends JPanel +{ + private static final Color GE_INPROGRESS_ORANGE = new Color(0xd8, 0x80, 0x20).brighter(); + private static final Color GE_FINISHED_GREEN = new Color(0, 0x5f, 0); + private static final Color GE_CANCELLED_RED = new Color(0x8f, 0, 0); + + private static final String INFO_CARD = "INFO_CARD"; + private static final String EMPTY_CARD = "EMPTY_CARD"; + + private final ItemManager itemManager; + + private final CardLayout cardLayout = new CardLayout(); + private final JLabel itemIcon = new JLabel(); + private final TitledBorder itemName = BorderFactory.createTitledBorder("Nothing"); + private final JLabel offerState = new JLabel("Text so the label has height"); + private final JProgressBar progressBar = new JProgressBar(); + + /** + * This (sub)panel is used for each GE slot displayed + * in the sidebar + */ + GrandExchangeOfferSlot(ItemManager itemManager) + { + this.itemManager = itemManager; + buildPanel(); + } + + private void buildPanel() + { + setBorder(BorderFactory.createCompoundBorder( + // Add a margin underneath each slot panel to space them out + BorderFactory.createEmptyBorder(0, 0, 3, 0), + itemName + )); + + // The default border color is kind of dark, so we change it to something lighter + itemName.setBorder(BorderFactory.createLineBorder(getBackground().brighter())); + + progressBar.setStringPainted(true); + + setLayout(cardLayout); + + // Card for when the slot has an offer in it + JPanel infoCard = new JPanel(); + add(infoCard, INFO_CARD); + // Add padding to give the icon and progress bar room to breathe + infoCard.setBorder(BorderFactory.createEmptyBorder(0, 2, 2, 2)); + + infoCard.setLayout(new BoxLayout(infoCard, BoxLayout.X_AXIS)); + // Icon on the left + infoCard.add(itemIcon); + + // Info on the right + JPanel offerStatePanel = new JPanel(); + offerStatePanel.setLayout(new BoxLayout(offerStatePanel, BoxLayout.Y_AXIS)); + offerStatePanel.add(offerState); + offerStatePanel.add(progressBar); + infoCard.add(offerStatePanel); + + // Card for when the slot is empty + JPanel emptySlotCard = new JPanel(); + add(emptySlotCard, EMPTY_CARD); + // Counteract the height lost to the text at the top of the TitledBorder + int itemNameBorderHeight = itemName.getBorderInsets(this).top; + emptySlotCard.setBorder(BorderFactory.createEmptyBorder(0, 0, (itemNameBorderHeight - 1) / 2, 0)); + // Center the "Empty" label horizontally + emptySlotCard.setLayout( new BoxLayout(emptySlotCard, BoxLayout.X_AXIS)); + emptySlotCard.add(Box.createHorizontalGlue()); + emptySlotCard.add(new JLabel(getNameForState(EMPTY)), BorderLayout.CENTER); + emptySlotCard.add(Box.createHorizontalGlue()); + + cardLayout.show(this, EMPTY_CARD); + + + + } + + void updateOffer(@Nullable GrandExchangeOffer newOffer) + { + if (newOffer == null || newOffer.getState() == EMPTY) + { + cardLayout.show(this, EMPTY_CARD); + itemName.setTitle("Nothing"); + } + else + { + cardLayout.show(this, INFO_CARD); + + ItemComposition offerItem = itemManager.getItemComposition(newOffer.getItemId()); + + itemName.setTitle(offerItem.getName()); + + boolean shouldStack = offerItem.isStackable() || newOffer.getTotalQuantity() > 1; + ImageIcon newItemIcon = new ImageIcon(itemManager.getImage(newOffer.getItemId(), newOffer.getTotalQuantity(), shouldStack)); + itemIcon.setIcon(newItemIcon); + + offerState.setText(getNameForState(newOffer.getState()) + " at " + newOffer.getPrice() + (newOffer.getTotalQuantity() > 1 ? "gp ea" : "gp")); + + progressBar.setMaximum(newOffer.getTotalQuantity()); + progressBar.setValue(newOffer.getQuantitySold()); + progressBar.setBackground(getColorForState(newOffer.getState())); + progressBar.setString(newOffer.getQuantitySold() + "/" + newOffer.getTotalQuantity()); + } + repaint(); + } + + private String getNameForState(GrandExchangeOfferState state) + { + switch (state) + { + case CANCELLED: + return "Cancelled"; + case BUYING: + return "Buying"; + case BOUGHT: + return "Bought"; + case SELLING: + return "Selling"; + case SOLD: + return "Sold"; + case EMPTY: + default: + return "Empty"; + + } + } + + private Color getColorForState(GrandExchangeOfferState state) + { + switch (state) + { + case CANCELLED: + return GE_CANCELLED_RED; + case BUYING: + case SELLING: + return GE_INPROGRESS_ORANGE; + case BOUGHT: + case SOLD: + return GE_FINISHED_GREEN; + case EMPTY: + default: + return null; + } + } + +} + + diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java new file mode 100644 index 0000000000..c7db78ab89 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * 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.grandexchange; + +import javax.inject.Inject; +import javax.swing.BoxLayout; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.GrandExchangeOffer; +import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.PluginPanel; + +@Slf4j +class GrandExchangePanel extends PluginPanel +{ + private static final int MAX_OFFERS = 8; + + private GrandExchangeOfferSlot[] offerSlotPanels = new GrandExchangeOfferSlot[MAX_OFFERS]; + + @Inject + GrandExchangePanel(ItemManager itemManager) + { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + for (int i = 0; i < offerSlotPanels.length; ++i) + { + offerSlotPanels[i] = new GrandExchangeOfferSlot(itemManager); + add(offerSlotPanels[i]); + } + + setVisible(true); + } + + void updateOffer(GrandExchangeOffer newOffer, int slot) + { + offerSlotPanels[slot].updateOffer(newOffer); + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java new file mode 100644 index 0000000000..9e1cd336b9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2017, Robbie + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * 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.grandexchange; + +import com.google.common.eventbus.Subscribe; +import java.awt.image.BufferedImage; +import java.io.IOException; +import javax.imageio.ImageIO; +import javax.inject.Inject; +import javax.swing.SwingUtilities; +import net.runelite.api.events.GrandExchangeOfferChanged; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.ClientUI; +import net.runelite.client.ui.NavigationButton; + +@PluginDescriptor( + name = "Grand Exchange offers" +) +public class GrandExchangePlugin extends Plugin +{ + private NavigationButton button; + + private GrandExchangePanel panel; + + @Inject + private ClientUI ui; + + @Override + protected void startUp() throws IOException + { + panel = injector.getInstance(GrandExchangePanel.class); + BufferedImage icon = ImageIO.read(getClass().getResourceAsStream("ge_icon.png")); + button = new NavigationButton("GE Offers", icon, () -> panel); + ui.getPluginToolbar().addNavigation(button); + } + + @Override + protected void shutDown() + { + ui.getPluginToolbar().removeNavigation(button); + } + + @Subscribe + public void onGrandExchangeOfferChanged(GrandExchangeOfferChanged offerEvent) + { + SwingUtilities.invokeLater(() -> + { + panel.updateOffer(offerEvent.getOffer(), offerEvent.getSlot()); + }); + } + +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/ge_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/ge_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0783e0cd32feed230a3cc4288e9f6d9dbd791093 GIT binary patch literal 15952 zcmeI3dsGv57RP7TqgxTQz7bIL_`G@Ws;e|Xp#v@Ac0!n7Fbjk z6h#lmN7qudinU@r!m-vKUnq#SY7y(Rz7QW(a20Epog_fw518ZWcF+DZIVX?%yT9+f z^ZWen{bl}{oS5iIK3=|F5Cr)|MXF+{@4nP~;0HaZ&+Tu!Kc>EVnj#Y{5Y(%`Tit%<6s$GOzSsFZx&)cMg3BkF`YbCHL*1vWrea+tX7j8L2Nb~$0p#A=46B?lgSV+AK~+1Y6fh{Fj`SN zY_#~hg0#g^;TEl#Fj)!G$a2I*HDsDKl+AV|YJat^%V26xWVE!hqbMSF)P(RjT% zL9JD;G~=k1G{=#oKFn2RF>MH0N~N=^SYu}sMvSys0u=}-aqqV~8m>aEcoV7l=J$c+3Q2Q2m3UR`k81 zc+T~RgK6bD(riGjVT1uq#u1Y-S%J6*J!ZC7m0U^cNi%g>c$h$ebT;gva#or=%4o5o zMlBwt3Zn)%1cAwQ7%IjjG6{@h5&?|rgcvO4ONFpVi0Zf^o=$^eA}2((+Ed;kw2IWG zIl6@k?K)U6QcHz*UzV5(B+~JFKiG*5M!;|V@Tp+@zHSr}HfwnD0Ct*6IiX<#l zwPtu!F;&aXlWhi``tE3(Lr*wQC=+V7;Ew(aWw$k8`;pduVmUO}is*v~Td{@+W zWng#(;`lGn6aRyau%PMx-8Q^mSN*YV==6m8|80Z2inS@IF&W3gkhTJD8+gB+bM5E5 zoz>Q@9XnCs_;Zp|T})LJ>g*n#0_ia3&g_~W@90K-nsYvN5^r19%x+01$eU!9~vp@G`gnAoM1Ki=GeQWpDvN=uHL}Js-f!-~xcqn+z^` zK7g0O1puKp8C>*y055|J077pvxaj!+UIrHcgx+Lu(enYk3@!i&y~*IB=L2{dTmTSy zlfgyL2k?d;l+l z3jjiIGPvmZ0A2ugK_1vb7dO&j;Jc4@e6Jlv2W7QCpIU0hNt%RWF7V5hmg3`DU z^e72}bYo4_{JNBf!@SBE-Kiw|xb^YjS zpLyt$E9zDI_T~Z{q zeG#!UR#g{q>HCV82g6kb-cPc2)l~Uzo0z!qGq`9fz9fjf;-=vr_qd6J@{f!vFG*}F zc{m_AX78B`^*!OFEE}(@57vpA6I*i-*9UE+T8j*CVtG5 zTZf*;h7IW{7*VtRN;2!S-qi=!W$)O1^!Xj^#?Q0D$y)X4*kSvPf%mWKMvmJ5X}^s7 zPlFm$G}B)sp7=5)h&yCW`H)=rqoPuO_V2H{>6RA>4^^VG4u{YGaU@51Cc5tmy(e{J$*a+Z<3d!iwQ_FAS!rFtq;p<~&%Mo$ch5hOn}_8SKP_#pu+PdaE6ng7 zvt>|f#)6{F2Skfg%kp<*=iM&tXI#*5w{eqi?fmthW*2XX=uaT#z%e0jiyP*iZ;Dx! zcY4@s(^4O#59h(-dij!L7tieg)w)jvv4G(YMbuIX6#Q{uX{BJh!OM_x{~qnuUV{cBrCTLMj_B>II<#1J_7GziUb= z%A5El5vcxr>2^Qk(>t{fCzo!`eO{T9S-S1!naWb=pQ7GpLw(sl1dEkZPQ0k{OZnA| z?kbqpN0N#!-H@|!-lM;Au1MZww8R!SEHK=uy0maiz@FD;`T3Z_+>*uJDiS_{qsG7o zg03Zfb)|aqaJ*{Gf6Mj#}C0`F3dDy6buzL9Fy)V6n zR?Z18sLs^vyE%E%g|qR&D`$&0nee4PHHUx8s6b8}TA5lpa_;Gile$@LgHjt$!)v^= z;_r(ddd>N@HZENFdE&g=m#@ZPUmX?~`ZpYuRs1$#9qD6A+xhn|e9zS$fuW_nzpGAs z+f=R19?KpoA2wxQZB<=F-~7wX(Gkk6IX=s=9*eH~B|OkBoU&~9`S48t5pQL^=L|FF ztm4b0Ul-iSUjN2#`1*oZPeZ>6Fc9g5Wt zHv6?qO-dR3?_UDSV}BPb{fms{{c8jVh@3*hz`(K|hNhWy_49T98W)J5LhRZ