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] 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