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