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.