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/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-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-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 0000000000..0783e0cd32 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/ge_icon.png differ diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlotTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlotTest.java new file mode 100644 index 0000000000..99b0753c9e --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlotTest.java @@ -0,0 +1,62 @@ +/* + * 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.image.BufferedImage; +import net.runelite.api.GrandExchangeOffer; +import net.runelite.api.GrandExchangeOfferState; +import net.runelite.api.ItemComposition; +import net.runelite.client.game.ItemManager; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import org.mockito.Mock; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class GrandExchangeOfferSlotTest +{ + @Mock + private ItemManager itemManager; + + @Mock + private GrandExchangeOffer offer; + + @Test + public void testUpdateOffer() + { + when(itemManager.getItemComposition(anyInt())).thenReturn(mock(ItemComposition.class)); + when(itemManager.getImage(anyInt(), anyInt(), anyBoolean())).thenReturn(mock(BufferedImage.class)); + when(offer.getState()).thenReturn(GrandExchangeOfferState.CANCELLED); + + GrandExchangeOfferSlot offerSlot = new GrandExchangeOfferSlot(itemManager); + offerSlot.updateOffer(offer); + } + +} 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/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/RSGrandExchangeOffer.java b/runescape-api/src/main/java/net/runelite/rs/api/RSGrandExchangeOffer.java index 572718976d..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,25 +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("progress") - byte getProgress(); + @Import("state") + byte getRSState(); + + } 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(); }