liverare Posted January 21, 2014 Share Posted January 21, 2014 (edited) Shop V2 Port Shop Item V2 Port I'll update more in the future, and feel free to contribute! Shop.scr import java.awt.Graphics2D; import java.util.HashSet; import java.util.Set; import org.osbot.script.MethodProvider; import org.osbot.script.Script; import org.osbot.script.rs2.model.Item; import org.osbot.script.rs2.model.NPC; import org.osbot.script.rs2.ui.RS2Interface; import org.osbot.script.rs2.ui.RS2InterfaceChild; /** * * @author LiveRare * * Shop class that provides simple access mechanisms to purchase items * from a shop interface. * */ public class Shop { /* * CONSTANT VARIABLES */ /** * Common parent ID of most (if not all) shops. */ public static final int COMMON_PARENT_ID = 300; /** * Common child ID for the component that contains the data for the items. */ public static final int COMMON_CONTENT_CHILD_ID = 75; /** * Common child ID for the shop's title. */ public static final int COMMON_TITLE_CHILD_ID = 76; /** * Common child ID for the shop's close button. */ public static final int COMMON_CLOSE_CHILD_ID = 92; /** * Maximum amount of slots available in a shop interface */ public static final int MAXIMUM_SLOT_AMOUNT = 40; /* * DYNAMIC VARIABLES */ /** * Script instance that will be accessing the interfaces from. */ private final Script script; /** * A cache of 40 available items. * * Note: 40 Is the maximum amount of item slots a shop can hold. */ private final ShopItem[] items; /** * ID values for the parent interface and child components. */ private final int parentID, childContentID, childTitleID, childCloseID; /** * Parent instance that will be accessed elsewhere in this class. */ private RS2Interface parent; /** * Name of the clerk to interact with. */ private String npcName; /* * Constructors */ public Shop(Script script, String npcName, int parentID, int childContentID, int childTitleID, int childCloseID) { this.script = script; this.npcName = npcName; this.parentID = parentID; this.childContentID = childContentID; this.childTitleID = childTitleID; this.childCloseID = childCloseID; this.items = new ShopItem[MAXIMUM_SLOT_AMOUNT]; init(); } public Shop(Script script, String npcName) { this(script, npcName, COMMON_PARENT_ID, COMMON_CONTENT_CHILD_ID, COMMON_TITLE_CHILD_ID, COMMON_CLOSE_CHILD_ID); } /* * Analytics methods */ /** * This class provides a property for an NPC name. This will allow the * {@link Shop#open(String)} function to execute with a viable NPC name. * * @param npcName * Name of the NPC to find */ public void setNPCName(String npcName) { this.npcName = npcName; } /** * This method will initialise the ShopItem 2D array that can contain up to * 40 ({@link Shop#MAXIMUM_SLOT_AMOUNT}). */ public void init() { for (int i = 0; i < MAXIMUM_SLOT_AMOUNT; i++) items[i] = new ShopItem(script, i); } /** * @Deprecated this method is really inefficient usage of the item cache * acquired from the shop interface. Since only one index is * required, the rest are disregarded. This method should only * be regarded if it's absolutely necessary! * * @param slot * Slot to validate * @return <tt>Validation was successful</tt> */ @Deprecated public boolean validate(int slot) { try { Item[] items = getItems(); this.items[slot].setDate(slot < items.length ? items[slot] : null); return true; } catch (Exception e) { this.items[slot].setDate(null); e.printStackTrace(); return false; } } /** * This method will analyse the shop's contents and stores the data in the * 2D ShopItem array. * * @return <tt>Successfully validated the shop's content</tt> */ public boolean validate() { try { Item[] items = getItems(); for (int i = 0; i < MAXIMUM_SLOT_AMOUNT; i++) this.items[i].setDate(i < items.length ? items[i] : null); return true; } catch (Exception e) { // If error occurs reset all ShopItem cache init(); e.printStackTrace(); return false; } } /** * * @return <tt>Shop interface is valid, thus; shop is open</tt> */ public boolean isOpen() { return (parent = script.client.getInterface(parentID)) != null && parent.isValid() && parent.isVisible(); } /** * * @return Shop's title */ public String getTitle() { return parent.getChild(childTitleID).getMessage(); } /** * * @return Items in the shop */ public Item[] getItems() { return parent.getItems(childContentID); } public int getAmount(int... itemIDs) { if (itemIDs == null || itemIDs.length == 0) return -1; int amount = 0; i: for (ShopItem item : items) if (item != null) for (int id : itemIDs) { if (item.getID() == id) { amount += item.getAmount(); continue i; } } return amount; } public int getAmount(String... itemNames) { if (itemNames == null || itemNames.length == 0) return -1; int amount = 0; i: for (ShopItem item : items) if (item != null && isStringValid(item.getName())) for (String name : itemNames) if (isStringValid(name)) { if (item.getName().equalsIgnoreCase(name)) { amount += item.getAmount(); continue i; } } return amount; } /** * Acquire an item based on its slot in the shop interface. * * ONLY USE THIS METHOD IF YOU <u>KNOW</u> FOR CERTAIN THE SLOT'S ITEM WILL * BE CONSISTENT, OTHERWISE USE ANOTHER GETTER. * * @param slot * Slot position within the shop * @return ShopItem by index */ public ShopItem getItemBySlot(int slot) { return slot >= 0 && slot < MAXIMUM_SLOT_AMOUNT ? this.items[slot] : null; } /** * Acquire an item based on item ID. * * @param ids * IDs of the item to search for * @return First shop item with a corresponding ID */ public ShopItem getItemByID(int... ids) { if (ids != null && ids.length > 0) for (ShopItem nextItem : items) for (int nextID : ids) if (nextItem.getID() == nextID) return nextItem; return null; } public ShopItem[] getItemsByID(int... ids) { Set<ShopItem> cache = new HashSet<>(); if (ids != null && ids.length > 0) for (ShopItem nextItem : items) for (int nextID : ids) if (nextItem.getID() == nextID) cache.add(nextItem); return cache.isEmpty() ? null : cache .toArray(new ShopItem[cache.size()]); } /** * Acquire an item based on item name. * * @param names * Names of the item to search for * @return First shop item with a corresponding name */ public ShopItem getItemByName(String... names) { if (names != null && names.length > 0) for (ShopItem nextItem : items) { String name = nextItem.getName(); if (!isStringValid(name)) continue; else for (String nextName : names) if (isStringValid(nextName) && name.equalsIgnoreCase(nextName)) return nextItem; } return null; } public ShopItem[] getItemsByName(String... names) { Set<ShopItem> cache = new HashSet<>(); if (names != null && names.length > 0) for (ShopItem nextItem : items) { String name = nextItem.getName(); if (!isStringValid(name)) continue; else for (String nextName : names) if (isStringValid(nextName) && name.equalsIgnoreCase(nextName)) cache.add(nextItem); } return cache.isEmpty() ? null : cache .toArray(new ShopItem[cache.size()]); } public void paint(Graphics2D g) { for (ShopItem next : items) next.draw(g); } /* * Interact methods */ public int tryPurchaseInvAmount(ShopItem item) throws InterruptedException { final int empty = script.client.getInventory() .getEmptySlots(); return tryPurchase(item, item.getAmount() > empty ? empty : item.getAmount()); } public int tryPurchase(ShopItem item, int amount) throws InterruptedException { if (item != null && amount > 0 && this.isOpen()) { this.validate(); int total = amount, onesPurchase = 0, fivesPurchase = 0, tensPurchase = 0; if (total >= 10) { tensPurchase = total / 10; total -= tensPurchase * 10; } if (total >= 5) { fivesPurchase = 1; total -= 5; } onesPurchase = total; int count = 0; try { for (int i = 0; i < tensPurchase; i++) if (purchase(item, 10)) count += 10; for (int i = 0; i < fivesPurchase; i++) if (purchase(item, 5)) count += 5; for (int i = 0; i < onesPurchase; i++) if (purchase(item, 1)) count += 1; } catch (RuntimeException e) { script.log(e.getMessage()); } return amount - count; } return -1; } private boolean purchase(ShopItem item, int amount) throws InterruptedException { final int oldID = item.getID(); validate(item.getSlot()); if (oldID != item.getID()) throw new RuntimeException("Inconsistent item! Required: " + oldID + ", but found: " + item.getID()); else if (item.getAmount() <= 0) return false; switch (amount) { case 1: return item.purchaseOne(); case 5: return item.purchaseFive(); case 10: return item.purchaseTen(); } return false; } public int trySell(Item item, int amount) throws InterruptedException { if (item != null && amount > 0 && this.isOpen()) { this.validate(); int total = amount, onesPurchase = 0, fivesPurchase = 0, tensPurchase = 0; if (total >= 10) { tensPurchase = total / 10; total -= tensPurchase * 10; } if (total >= 5) { fivesPurchase = 1; total -= 5; } onesPurchase = total; int count = 0; try { for (int i = 0; i < tensPurchase; i++) if (sell(item, 10)) count += 10; for (int i = 0; i < fivesPurchase; i++) if (sell(item, 5)) count += 5; for (int i = 0; i < onesPurchase; i++) if (sell(item, 1)) count += 1; } catch (RuntimeException e) { script.log(e.getMessage()); } return amount - count; } return -1; } private boolean sell(Item item, int amount) throws InterruptedException { return script.client.getInventory().interactWithId(item.getId(), "Sell " + amount); } /** * This method requires a pre-initialised <i>valid</i> NPC name or one needs * to be provided on the constructor. * * @param altNPCName * Alternative NPC to search for * @return <tt>Shop is open</tt> */ public boolean tryOpen(String altNPCName) throws InterruptedException { boolean open = isOpen(); try { if (open) return true; NPC npc = script .closestNPCForName(isStringValid(altNPCName) ? altNPCName : npcName); if (npc != null && npc.exists() && npc.interact("Trade")) script.sleep(MethodProvider.random(350, 600)); return open = isOpen(); } catch (Exception e) { e.printStackTrace(); return false; } finally { if (open) validate(); } } public boolean tryOpen() throws InterruptedException { return tryOpen(null); } public boolean close() throws InterruptedException { if (!isOpen()) // Prevent unnecessary re-closing return false; RS2InterfaceChild child = parent.getChild(childCloseID); return child != null && child.interact("Close"); } /* * Static methods */ private static boolean isStringValid(String arg0) { return arg0 != null && !arg0.isEmpty(); } } ShopItem.scr import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Stroke; import org.osbot.script.Script; import org.osbot.script.mouse.RectangleDestination; import org.osbot.script.rs2.model.Item; /** * * @author LiveRare * * This class is designed to store the details for every item * within a shop interface. There should be only 40 instances * of this object stored in an array because there's a maximum * of 40 available item slots in a shop interface. * */ public class ShopItem { public static final Stroke STROKE = new BasicStroke(0.655f); public static final Color FOREGROUND_COLOR = new Color(255, 255, 255, 150); public static final Point ITEM_STARTING_POSITION = new Point(80, 70); public static final Dimension ITEM_BOUNDS = new Dimension(30, 25); public static final Dimension SPACE_MARGIN = new Dimension(17, 23); private final Script script; private final int slot; private final int slotColumn; private final int slotRow; private final Rectangle slotBounds; private final RectangleDestination slotDestination; private int id; private String name; private int amount; public ShopItem(Script script, int slot) { this.script = script; this.slot = slot; this.slotColumn = (slot % 8); this.slotRow = (int) (slot / (double) 8); this.slotBounds = new Rectangle( ITEM_STARTING_POSITION.x + (ITEM_BOUNDS.width + SPACE_MARGIN.width) * (slotColumn), ITEM_STARTING_POSITION.y + (ITEM_BOUNDS.height + SPACE_MARGIN.height) * (slotRow), ITEM_BOUNDS.width, ITEM_BOUNDS.height); this.slotDestination = new RectangleDestination(slotBounds); } @Override public boolean equals(Object obj) { return obj != null && obj instanceof ShopItem && ((ShopItem) obj).getSlot() == getSlot(); } @Override public String toString() { return "[Slot: " + slot + " | Name: " + name + " | Item ID: " + id + " | Amount: " + amount + "]"; } /* * Item profile values */ public int getSlot() { return slot; } public int getID() { return id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void setID(int id) { this.id = id; } public int getAmount() { return amount; } public void setAmount(int amount) { this.amount = amount; } public int getSlotColumn() { return slotColumn; } public void setDate(Item item) { if (item != null) { this.id = item.getId(); this.name = item.getName(); this.amount = item.getAmount(); } else { this.id = -1; this.name = "null"; this.amount = -1; } } /* * Other methods */ public int getSlotRow() { return slotRow; } public Rectangle getSlotBounds() { return slotBounds; } public RectangleDestination getSlotDestination() { return slotDestination; } public boolean interact(String interact) throws InterruptedException { script.client.moveMouse(getSlotDestination(), false); return script.selectOption(null, getSlotDestination(), interact); } public boolean purchaseOne() throws InterruptedException { return interact("Buy 1"); } public boolean purchaseFive() throws InterruptedException { return interact("Buy 5"); } public boolean purchaseTen() throws InterruptedException { return interact("Buy 10"); } public void draw(Graphics2D g) { Rectangle r = new Rectangle(slotBounds.x - 4, slotBounds.y - 6, slotBounds.width + 4, slotBounds.height + 10); { // Draw bounding box g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); g.setStroke(STROKE); g.setColor(FOREGROUND_COLOR); g.draw(r); g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT); } { // Draw text g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); FontMetrics fm = g.getFontMetrics(); String id = String.valueOf(this.id); int width = (int) fm.getStringBounds(id, g).getWidth(); Point p = new Point(r.x + ((r.width - width) / 2), r.y + 32); g.setColor(Color.BLACK); g.drawString(id, p.x - 1, p.y - 1); g.drawString(id, p.x - 1, p.y + 1); g.drawString(id, p.x + 1, p.y - 1); g.drawString(id, p.x + 1, p.y + 1); g.setColor(Color.WHITE); g.drawString(id, p.x, p.y); g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); } } } Example of usage: Shop shop; @Override public void onStart() { shop = new Shop(this, "Shop keeper"); // Shop keeper in the Varrock general store } @Override public int onLoop() throws InterruptedException { if (shop.isOpen() && shop.validate()) { //Validate method is required to launch at least once before item profiling ShopItem item = shop.getItemByName("Willow shortbow (u)"); // Common item dumped in that store if (item != null) { // ENSURE TO NULL-CHECK int failedToPurchaseCount = shop.tryPurchase(item, 26); // Item amount that wasn't purchased! log("Need to purchase: " + failedToPurchaseCount + " more..."); // Debug string } } else { shop.tryOpen(); // shop.tryOpen(String) can be used instead if you didn't already assign ab NPC name on constructor } return 300; } @Override public void onPaint(Graphics arg0) { Graphics2D g = (Graphics2D) arg0; if (shop != null && shop.isOpen()) { shop.paint(g); } } Note: There may be a problem with items with a quantity of 0. I will look into this tomorrow. Edited June 22, 2014 by liverare 2 Link to comment Share on other sites More sharing options...
Swizzbeat Posted January 21, 2014 Share Posted January 21, 2014 Looks awesome! Would have came in handy and saved me some time a few days ago when I need this Link to comment Share on other sites More sharing options...
liverare Posted January 21, 2014 Author Share Posted January 21, 2014 Last-second update push. I added in a fail-safe to prevent the continued purchasing of items for slots that were previously holding your original required item. It was a logical error I spotted. Link to comment Share on other sites More sharing options...
Sex Posted January 21, 2014 Share Posted January 21, 2014 Wow nice Link to comment Share on other sites More sharing options...
Nezz Posted January 23, 2014 Share Posted January 23, 2014 I love this. Link to comment Share on other sites More sharing options...
liverare Posted January 23, 2014 Author Share Posted January 23, 2014 I love this. Thanks! I hope it serves well. Link to comment Share on other sites More sharing options...
Artemis Posted January 24, 2014 Share Posted January 24, 2014 There's still a small bug if the stock of the shop is zero, you can get a NullPointerException. shop.getItemByName("zzz").getAmount() == 1 gets an error if the stock of zzz is 0. I managed to "fix" this by assigning each item I'm buying as a variable and running a null check before the shop.getItemByName("zzz").getAmount() == 1 portion of the code. However, it would be nice and it would save a lot of space when doing multiple items if this could be programmed into the API. Also the shop.close() method doesn't close the store 100% of the time. I use a do until !shop.isOpen around my shop.close(). Maybe this could be programmed into the API as well. Other than that, this has been working perfectly for me. Very nice release. Should be something that's added to OSBot's API IMO. Link to comment Share on other sites More sharing options...
texx Posted January 28, 2014 Share Posted January 28, 2014 How can I sell an item from my inventory to shop? Can I assign mouse to click in certain cordinates (inside client window) to sell and buy item in certain slot?(meaning cordinates wont change). Link to comment Share on other sites More sharing options...
Cyro Posted January 28, 2014 Share Posted January 28, 2014 How can I sell an item from my inventory to shop? Can I assign mouse to click in certain cordinates (inside client window) to sell and buy item in certain slot?(meaning cordinates wont change). Check the inventory class there is interact methodshttp://osbot.org/api/org/osbot/script/rs2/ui/Inventory.html Link to comment Share on other sites More sharing options...
liverare Posted January 29, 2014 Author Share Posted January 29, 2014 I should really get around to updating this class. There are a few tweaks I need to adjust. While I'm at it I'll import through features from the inventory class to allow for the selling of stock. Link to comment Share on other sites More sharing options...
lolmanden Posted February 1, 2014 Share Posted February 1, 2014 Looking good ty for contrib.. Link to comment Share on other sites More sharing options...
Nezz Posted February 3, 2014 Share Posted February 3, 2014 (edited) I think there was a runescape update or something, the shop API doesn't work anymore. It won't find the items I'm looking for anymore. I'm guessing it's just a child ID issue, but I'm not sure. EDIT: ..nevermind it's working fine now. Not a single idea what happened. EDIT2: Figured it out.Here's what happened:Created shop object. Sold pure essence. Since I created the shop object when there was no pure essence, it didn't register that there was now pure essence, so it was returning null. I was trying to recreate the shop object, and that just messed all kinds of things up. Could you add a "refresh shop" method maybe? Or I'm wrong and it worked perfectly fine just...not for a bit there. Edited February 3, 2014 by Nezz Link to comment Share on other sites More sharing options...
liverare Posted February 3, 2014 Author Share Posted February 3, 2014 if (shop.isOpen() && shop.validate()) { //Validate method is required to launch at least once before item profiling Perhaps my choice of words were bad. Would you prefer Shop#validate() to be renamed to Shop#update() ? It actual does read rather nicely compared to the current. And also I may take into consideration the ability to declare a ShopItem as a field variable, but to then validate it later on. Hmm...I think it can already be done (to an extent), but for more dev-friendly I may remove the abstract modifier and implement a new interface that links through to Shop. AKA: I think I've got a nice idea - inspired by you. Link to comment Share on other sites More sharing options...
Nezz Posted February 4, 2014 Share Posted February 4, 2014 I wasn't even sure what shop.validate did :P I'm not sure what any of it does. :') Link to comment Share on other sites More sharing options...
Trap Fiend Posted February 18, 2014 Share Posted February 18, 2014 Are you ever going to get around to implementing selling support? I don't know why my selling method isn't working. It will sell my whole stack of Pure Essence before stopping... I don't even know... Link to comment Share on other sites More sharing options...