Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
Buy OSRS Gold

NeuroGenix

[SNIPPETS] Neuro's GE API

Recommended Posts

After originally posting this in the Scripting Help section I have decided it would be better suited here where there might be a larger audience to get feedback from to improve my own API and learn a thing or two in the process.

What calling the method Exchange#offer(int itemID, int price, int quantity, boolean buy) will do:

  1. If the GE is closed it will open it and continue.
  2. Search for the item to buy 1-3 chars at a time, checking each time whether or not the first 9 results contains the item, clicking it if it does.
  3. If the price is set to -1 it will use the +/-5% buttons to increase/decrease the price until it is at a price that will buy/sell instantly, otherwise it will use the custom amount interface to set the price.
  4. It will set the quantity using the 1/10/100/1000 buttons, using the custom amount interface if it accidently goes over by more than 10, otherwise it will use the -1 button to get to the correct amount if it is within 10.
  5. If it gets onto the history screen, or the wrong offer type screen, it will return to the selection screen and continue.
  6. It doesn't collect items after it has made the offer, returning true when the offer has been made. This is so that in my scripts I can be selling 9+ items, from toBuy and toSell HashMaps, in one go; only collecting when there are no more empty slots. This, in my mind, is more human-like than collecting the item straight away after each offer is made, thoughts?
  7. It will prioritise buy offers over sell overs. So if it is buying something and there are no empty slots, or offers to collect, then it will cancel a sell offer, collect it and continue now it has an empty slot. If it is a sell offer and there are no slots available it will return true. This is so that in my scripts the item is removed from the toSell HashMap so it can be put back into to bank to sell later as my script has the items it needs to continue botting. If it has a buy offer and there are no empty slots, offers to collect or sell offers to cancel it will wait until an offer had completed, collect it and continue. I should add a time-out function here, and I'm also thinking of implementing some sort of priority feature whereby it will cancel a buy offer if this new buy offer has a higher priority, should be simple enough.

It currently doesn't implement any antiban, I am hoping some of you can suggest the best way to implement that.

Below is an example of the implementation. In this example, starting with the ge closed but near it, the bot will open the ge, buy 276 impling jars instantly, wait a few seconds before collecting them and then sell them all again instantly.

All of the classes required to run this are below. I hope this can be helpful to somebody and I'm sure someone reading this can be helpful to me, thanks for reading, lemme know what you think.

Zoo Class - Example of the code working

Spoiler
package scripts.zoo;

import org.tribot.api.General;
import org.tribot.api2007.Inventory;
import org.tribot.api2007.types.RSItem;
import org.tribot.script.Script;
import org.tribot.script.ScriptManifest;

import scripts.api.data.GEInterfaces;
import scripts.api.utils.grandexchange.Exchange;
import scripts.api.utils.helpers.InfoHelper;

@ScriptManifest(authors = { "Kyle" }, category = "Zoo", name = "Zoo")

public class Zoo extends Script {

	@Override
	public void run() {
		while(true) {
			if(Exchange.offer(11260, -1, 276, true)) {
				General.println("Successfully bought");
				General.sleep(3000);
				if(GEInterfaces.COLLECT_BTN.isVisible()) {
					if(GEInterfaces.COLLECT_BTN.get().click()) {
						General.println("Successfully collected" );
						General.sleep(3000);
						
						RSItem[] impJars = Inventory.find(InfoHelper.getItemNameFromID(11260));
						if(impJars != null && impJars.length > 0) {
							if(Exchange.offer(impJars[0].getID()-1, -1, impJars[0].getStack(), false)) {
								General.println("Successfully sold");
								General.sleep(3000);
							} else {
								General.println("Failed");
							}
						}
					}
				}
			} else {
				General.println("Failed");
				General.sleep(5000);
			}
		}
	}

}

 

Exchange Class - The main class

Spoiler
package scripts.api.utils.grandexchange;

import org.tribot.api.Clicking;
import org.tribot.api.General;
import org.tribot.api.Timing;
import org.tribot.api.input.Keyboard;
import org.tribot.api2007.Interfaces;
import org.tribot.api2007.Inventory;
import org.tribot.api2007.NPCs;
import org.tribot.api2007.types.RSInterface;
import org.tribot.api2007.types.RSItem;
import org.tribot.api2007.types.RSNPC;

import scripts.api.data.GEInterfaces;
import scripts.api.utils.conditions.GEConditions;
import scripts.api.utils.helpers.InfoHelper;

public class Exchange {
	
	public static boolean offer(int itemID, int price, int quantity, boolean buy) {
		String name = InfoHelper.getItemNameFromID(itemID);
		if(name == null) return false;
		
		switch(getOfferState(itemID, price, quantity, buy)) {
		case GE_CLOSED:
			open();
			General.sleep(250,400);
			break;
		case HISTORY_SCREEN:
			exitHistoryScreen();
			General.sleep(250,400);
			break;
		case LOADING_SCREENS:
			General.sleep(300,600);
			break;
		case OFFER_SCREEN_ITEM_WRONG:
			handleWrongOfferItem(name, itemID, buy);
			General.sleep(250,400);
			break;
		case OFFER_SCREEN_OFFER_CORRECT:
			if(GEInterfaces.CONFIRM_NEW_OFFER.get().click())
				Timing.waitCondition(GEConditions.GESelectionWindowVisible(), General.random(4000, 7000));
			General.sleep(250,400);
			break;
		case OFFER_SCREEN_PRICE_WRONG:
			handleWrongOfferPrice(price, buy);
			General.sleep(600, 900);
			break;
		case OFFER_SCREEN_QUANTITY_WRONG:
			handleWrongOfferQuantity(quantity);
			General.sleep(150, 300);
			break;
		case OFFER_SCREEN_TYPE_WRONG:
			if(GEInterfaces.BACK_BTN.get().click())
				Timing.waitCondition(GEConditions.GESelectionWindowVisible(), General.random(4000, 7000));
			General.sleep(250,400);
			break;
		case SELECTION_SCREEN_CAN_COLLECT:
			if(GEInterfaces.COLLECT_BTN.get().click())
				Timing.waitCondition(GEConditions.hasCollected(), General.random(4000, 7000));
			General.sleep(250,400);
			break;
		case SELECTION_SCREEN_CAN_MAKE_OFFER:
			handleMakingOffer(name, buy);
			General.sleep(600,1200);
			break;
		case SELECTION_SCREEN_OFFER_ACTIVE:
			return true;
		case SELECTION_SCREEN_SHOULD_CANCEL_OFFER:
			handleCancellingOffer();
			General.sleep(400,700);
			break;
		case SELECTION_SCREEN_SHOULD_RETURN_TRUE:
			return true;
		case SELECTION_SCREEN_SHOULD_WAIT:
			General.sleep(3000, 6000);
			break;
		}
		
		return offer(itemID, price, quantity, buy);
	}
	
	public static OfferState getOfferState(int itemID, int price, int quantity, boolean buy) {
		if(isOpen()) {
			//The GE is open, what window are we on?
			if(GEInterfaces.SELECTION_WINDOW.isVisible()) {
				//We are on the selection window.
				for(int i = 7; i < 15 ; i++) {
					//Do we already have an offer of this item?
					RSInterface item = Interfaces.get(465, i, 18);
					if(item != null && !item.isHidden()) {
						if(item.getComponentItem() == itemID) {
							//Yes we do, is it the right type?
							RSInterface type = Interfaces.get(465, i, 16);
							if(type != null && !type.isHidden()) {
								if(type.getText().contains("Buy")) {
									if(buy == true) {
										//Yes it is, return we have made the offer.
										return OfferState.SELECTION_SCREEN_OFFER_ACTIVE;
									}
								} else {
									if(buy == false) {
										//Yes it is, return we have made the offer.
										return OfferState.SELECTION_SCREEN_OFFER_ACTIVE;
									}
								}
							}
						}
					}
				}	
				//No we don't already have an active offer
				for(int b = 7; b < 15 ; b++) {
					//Do we have an empty offer slot?
					RSInterface type = Interfaces.get(465, b, 16);
					if(type != null && !type.isHidden()) {
						if(type.getText().contains("Empty")) {
							//Yes we do, return we can make the offer
							return OfferState.SELECTION_SCREEN_CAN_MAKE_OFFER;
						}
					}
				}
				//No we don't, do we have any offers to collect?
				if(GEInterfaces.COLLECT_BTN.isVisible()) {
					//Yes we do, return we can collect offers
					return OfferState.SELECTION_SCREEN_CAN_COLLECT;
				}
						
				//No we don't, should we cancel an offer?
				if(buy == true) {
					//Check if there is a sell offer to cancel
					for(int c = 7; c < 15 ; c++) {
						RSInterface type = Interfaces.get(465, c, 16);
						if(type != null && !type.isHidden()) {
							if(type.getText().contains("Sell")) {
								//Yes there is, return we can abort a sell offer.
								return OfferState.SELECTION_SCREEN_SHOULD_CANCEL_OFFER;
							}
						}
					}
					
					//No there isn't, we should wait because we are trying to buy something
					return OfferState.SELECTION_SCREEN_SHOULD_WAIT;
				} else {
					return OfferState.SELECTION_SCREEN_SHOULD_RETURN_TRUE;
				}
			} else if(GEInterfaces.CONFIRM_NEW_OFFER.isVisible()) {
				//We are on an offer screen, check that it is the right type
				if(GEInterfaces.BUY_WINDOW.isVisible())
					if(buy == false)
						return OfferState.OFFER_SCREEN_TYPE_WRONG;
				
				if(GEInterfaces.SELL_WINDOW.isVisible())
					if(buy == true)
						return OfferState.OFFER_SCREEN_TYPE_WRONG;
				
				//The type is correct, check if the item is correct
				if(GEInterfaces.OFFER_ITEM.get().getComponentItem() == itemID) {
					//The item is correct, check if the price is correct
					int currentPrice = InfoHelper.stringToInt(GEInterfaces.OFFER_PRICE.get().getText(), " coins",",");
					if(price == -1) {
						int avrPrice = InfoHelper.stringToInt(GEInterfaces.OFFER_AVR_PRICE.get().getText(), ",");
						if(buy == true) {
							Double targ = avrPrice * 1.2;
							int target = targ.intValue();
							if(currentPrice > target) {
								//The price is correct, check if the quantity is correct
								int currentQuantity = InfoHelper.stringToInt(GEInterfaces.OFFER_QUANTITY.get().getText(), ",");
								if(currentQuantity == quantity) {
									//The quantity is correct, return confirm offer
									return OfferState.OFFER_SCREEN_OFFER_CORRECT;
								} else {
									//No the quantity isn't correct, return quantity wrong
									return OfferState.OFFER_SCREEN_QUANTITY_WRONG;
								}
							} else {
								//the price is wrong
								return OfferState.OFFER_SCREEN_PRICE_WRONG;
							}
						} else {
							Double targ = avrPrice * 0.67;
							int target = targ.intValue();
							if(currentPrice < target) {
								//The price is correct, check if the quantity is correct
								int currentQuantity = InfoHelper.stringToInt(GEInterfaces.OFFER_QUANTITY.get().getText(), ",");
								if(currentQuantity == quantity) {
									//The quantity is correct, return confirm offer
									return OfferState.OFFER_SCREEN_OFFER_CORRECT;
								} else {
									//No the quantity isn't correct, return quantity wrong
									return OfferState.OFFER_SCREEN_QUANTITY_WRONG;
								}
							} else {
								//the price is wrong
								return OfferState.OFFER_SCREEN_PRICE_WRONG;
							}
						}
					} else if(currentPrice == price) {
						//The price is correct, check if the quantity is correct
						int currentQuantity = InfoHelper.stringToInt(GEInterfaces.OFFER_QUANTITY.get().getText(), ",");
						if(currentQuantity == quantity) {
							//The quantity is correct, return confirm offer
							return OfferState.OFFER_SCREEN_OFFER_CORRECT;
						} else {
							//No the quantity isn't correct, return quantity wrong
							return OfferState.OFFER_SCREEN_QUANTITY_WRONG;
						}
					} else {
						//No the price isn't correct, return price wrong
						return OfferState.OFFER_SCREEN_PRICE_WRONG;
					}
				} else {
					//The item isn't correct
					return OfferState.OFFER_SCREEN_ITEM_WRONG;
				}
			} else if(GEInterfaces.HISTORY_WINDOW.isVisible()){
				//We are on the history screen, return on history screen
				return OfferState.HISTORY_SCREEN;
			} else {
				return OfferState.LOADING_SCREENS;
			}
		} else {
			return OfferState.GE_CLOSED;
		}
	}
	
	public static enum QuantityState {
		PLUS_1000, PLUS_100, PLUS_10, PLUS_1, CUSTOM, PLUS, MINUS, CORRECT
	}

	public static QuantityState getQuantityState(int goal, int current) {
		if(goal == current)
			return QuantityState.CORRECT;
		if(goal > current + 998)
			return QuantityState.PLUS_1000;
		if(goal > current + 98)
			return QuantityState.PLUS_100;
		if(goal > current + 9)
			return QuantityState.PLUS_10;
		if(goal > current)
			return General.random(1, 10) > 2 ? QuantityState.PLUS_1 : QuantityState.PLUS;
		if(goal + 10 < current)
			return QuantityState.CUSTOM;
		return QuantityState.MINUS;
	}
	
	public enum OfferState {
		 GE_CLOSED,
		 SELECTION_SCREEN_OFFER_ACTIVE,
		 SELECTION_SCREEN_CAN_MAKE_OFFER,
		 SELECTION_SCREEN_CAN_COLLECT,
		 SELECTION_SCREEN_SHOULD_RETURN_TRUE,
		 SELECTION_SCREEN_SHOULD_CANCEL_OFFER,
		 SELECTION_SCREEN_SHOULD_WAIT,
		 OFFER_SCREEN_TYPE_WRONG,
		 OFFER_SCREEN_ITEM_WRONG,
		 OFFER_SCREEN_PRICE_WRONG,
		 OFFER_SCREEN_QUANTITY_WRONG,
		 OFFER_SCREEN_OFFER_CORRECT,
		 HISTORY_SCREEN,
		 LOADING_SCREENS
	}
	
	public enum WindowState {
		CLOSED, SELECTION_SCREEN, BUY_SCREEN, SELL_SCREEN, HISTORY_SCREEN
	}
	
	public static WindowState getWindowState() {
		if(GEInterfaces.SELECTION_WINDOW.isVisible())
			return WindowState.SELECTION_SCREEN;
		
		if(GEInterfaces.HISTORY_WINDOW.isVisible())
			return WindowState.HISTORY_SCREEN;
		
		if(GEInterfaces.BUY_WINDOW.isVisible())
			return WindowState.BUY_SCREEN;
		
		if(GEInterfaces.SELL_WINDOW.isVisible())
			return WindowState.SELL_SCREEN;
		
		return WindowState.CLOSED;
	}
	
	public static void handleCancellingOffer() {
		for(int i = 7; i < 15 ; i++) {
			RSInterface type = Interfaces.get(465, i, 16);
			if(type != null && !type.isHidden()) {
				if(type.getText().contains("Sell")) {
					RSInterface slot = Interfaces.get(465, i, 2);
					if(slot != null && !slot.isHidden()) {
						if(slot.click("Abort offer")) {
							Timing.waitCondition(GEConditions.canCollect(), General.random(4000, 7000));
						}
					}
				}
			}
		}
	}
	
	public static void handleMakingOffer(String name, boolean buy) {
		if(buy == true) {
			for(int i = 7; i < 15 ; i++) {
				RSInterface type = Interfaces.get(465, i, 16);
				if(type != null && !type.isHidden()) {
					if(type.getText().contains("Empty")) {
						RSInterface buyBtn = Interfaces.get(465, i, 3);
						if(buyBtn != null && !buyBtn.isHidden()) {
							if(buyBtn.click()) {
								Timing.waitCondition(GEConditions.GEBuyWindowVisible(), General.random(4000, 7000));
							}
						}
					}
				}
			}
		} else {
			RSItem[] target = Inventory.find(name);
			if(target != null && target.length > 0) {
				if(target[0].click()) {
					Timing.waitCondition(GEConditions.GESellWindowVisible(), General.random(4000, 7000));
				}
			}
		}
	}
	
	public static void handleWrongOfferPrice(int price, boolean buy) {
		if(GEInterfaces.OFFER_AVR_PRICE.isVisible()) {
			if(price == -1) {
				if(buy == true) {
					GEInterfaces.PRICE_PLUS_5.get().click();
					return;
				} else {
					GEInterfaces.PRICE_MINUS_5.get().click();
					return;
				}
			}
		}
		
		if(GEInterfaces.SEARCH_ITEM_INPUT_TEXT.isVisible()) {
			Keyboard.typeSend(Integer.toString(price));
			Timing.waitCondition(GEConditions.GESearchBoxDissapeared(), General.random(4000, 7000));
			return;
		} 
		
		if(GEInterfaces.PRICE_CUSTOM.isVisible()) {
			if(GEInterfaces.PRICE_CUSTOM.get().click()) {
				Timing.waitCondition(GEConditions.GESearchBoxAppeared(), General.random(4000, 7000));
				return;
			}
		}
	}
	
	public static void handleWrongOfferQuantity(int quantity) {
		// quantity isn't correct
		if(GEInterfaces.SEARCH_ITEM_INPUT_TEXT.isVisible()) {
			Keyboard.typeSend(Integer.toString(quantity));
			Timing.waitCondition(GEConditions.GESearchBoxDissapeared(), General.random(4000, 7000));
			return;
		} 
		int currentQuantity = InfoHelper.stringToInt(GEInterfaces.OFFER_QUANTITY.get().getText(), ",");
		switch(getQuantityState(quantity, currentQuantity)) {
		case CORRECT:
			break;
		case CUSTOM:
			if(GEInterfaces.QUAN_CUSTOM.isVisible()) {
				GEInterfaces.QUAN_CUSTOM.get().click();
				Timing.waitCondition(GEConditions.GESearchBoxAppeared(), General.random(4000, 7000));
			}
			break;
		case MINUS:
			if(GEInterfaces.QUAN_MINUS.isVisible())
				GEInterfaces.QUAN_MINUS.get().click();
			break;
		case PLUS:
			if(GEInterfaces.QUAN_PLUS.isVisible())
				GEInterfaces.QUAN_PLUS.get().click();
			break;
		case PLUS_1:
			if(GEInterfaces.QUAN_PLUS_1.isVisible())
				GEInterfaces.QUAN_PLUS_1.get().click();
			break;
		case PLUS_10:
			if(GEInterfaces.QUAN_PLUS_10.isVisible())
				GEInterfaces.QUAN_PLUS_10.get().click();
			break;
		case PLUS_100:
			if(GEInterfaces.QUAN_PLUS_100.isVisible())
				GEInterfaces.QUAN_PLUS_100.get().click();
			break;
		case PLUS_1000:
			if(GEInterfaces.QUAN_PLUS_1000.isVisible())
				GEInterfaces.QUAN_PLUS_1000.get().click();
			break;
		}
	}
	
	public static void handleWrongOfferItem(String name, int itemID, boolean buy) {
		if(buy == true) {
			if(GEInterfaces.SEARCH_ITEM_INPUT_TEXT.isVisible()) {
				if(GEInterfaces.SEARCH_ITEM_RESULTS_CONTAINER.isVisible()) {
					if(hasSearchTarget(itemID)) {
						if(clickSearchTarget(itemID)) {
							General.sleep(1300, 2100);
							return;
						}
					} else {
						String alreadyTyped = GEInterfaces.SEARCH_ITEM_INPUT_TEXT.get().getText();
						String trimmed = alreadyTyped.substring(46, alreadyTyped.length()-1);
						Keyboard.typeString(InfoHelper.partNameToType(name, trimmed.replace("*", "")));
						General.sleep(600,900);
						return;
					}
					
				}
			}
			
			if(GEInterfaces.OFFER_ITEM.isVisible()) {
				if(GEInterfaces.OFFER_ITEM.get().click()) {
					Timing.waitCondition(GEConditions.GESearchBoxAppeared(), General.random(4000, 7000));
					return;
				}
			}
		} else {
			RSItem[] target = Inventory.find(itemID);
			if(target != null && target.length < 0) {
				if(target[0].click()) {
					General.sleep(900, 2100);
				}
			}
		}
	}
	
	public static boolean hasSearchTarget(int itemID) {
		if(GEInterfaces.SEARCH_RESULT_1.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_1.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_2.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_2.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_3.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_3.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_4.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_4.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_5.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_5.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_6.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_6.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_7.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_7.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_8.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_8.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_9.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_9.get().getComponentItem() == itemID) {
				return true;
			}
		}
		return false;
	}
	
	public static boolean clickSearchTarget(int itemID) {
		if(GEInterfaces.SEARCH_RESULT_1.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_1.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_1.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_2.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_2.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_2.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_3.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_3.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_3.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_4.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_4.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_4.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_5.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_5.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_5.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_6.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_6.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_6.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_7.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_7.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_7.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_8.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_8.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_8.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_9.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_9.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_9.get().click();
			}
		}
		return false;
	}
	
	public static void exitHistoryScreen() {
		if(GEInterfaces.EXCHANGE_BTN.isVisible()) {
			if(GEInterfaces.EXCHANGE_BTN.get().click()) {
				Timing.waitCondition(GEConditions.GESelectionWindowVisible(), General.random(4000, 7000));
			}
		}
	}
	
	public static boolean isOpen() {
		return GEInterfaces.MASTER.isVisible() || GEInterfaces.HISTORY_WINDOW.isVisible();
	}
	
	public static boolean open() {
		if(isOpen()) return true;
		
		RSNPC[] geClerks = NPCs.findNearest("Grand Exchange Clerk");
		if (geClerks.length > 0)
			if(Clicking.click("Exchange Grand Exchange Clerk", geClerks[0])) 
				Timing.waitCondition(GEConditions.GEOpen(), General.random(3000, 7000));
				return isOpen();
	}

}

 

GEInterfaces Class

Spoiler
package scripts.api.data;

import org.tribot.api2007.Interfaces;
import org.tribot.api2007.types.RSInterface;

public enum GEInterfaces {

	MASTER(465), SELECTION_WINDOW(465, 7, 16), HISTORY_WINDOW(383, 3, 0), BUY_WINDOW(465, 24, 19, 1118), SELL_WINDOW(465, 24, 19, 1119),
	
	SLOT(465, 7), SLOT_ACTIVE(465, 7, 2), SLOT_TYPE(465, 7, 16), SLOT_ITEM(465, 7, 18), SLOT_NAME(465, 7, 19), 
	SLOT_PROGRESS_WIDTH(465, 7, 22), SLOT_PRICE(465, 7, 25), SLOT_BUY_BTN(465, 7, 3), SLOT_SELL_BTN(465, 7, 4),
	
	SEARCH_ITEM_RESULTS_CONTAINER(162, 46), SEARCH_ITEM_INPUT_TEXT(162, 38),
	SEARCH_RESULT_1(162, 46, 2), SEARCH_RESULT_2(162, 46, 5), SEARCH_RESULT_3(162, 46, 8), SEARCH_RESULT_4(162, 46, 11), 
	SEARCH_RESULT_5(162, 46, 14), SEARCH_RESULT_6(162, 46, 17), SEARCH_RESULT_7(162, 46, 20), SEARCH_RESULT_8(162, 46, 23),
	SEARCH_RESULT_9(162, 46, 26),
	
	OFFER_NAME(465, 24, 25), OFFER_ITEM(465, 24, 21), OFFER_PRICE(465, 24, 39), OFFER_QUANTITY(465, 24, 32), OFFER_TOTAL(465, 24, 43),
	OFFER_AVR_PRICE(465, 26),
	
	QUAN_MINUS(465, 24, 1), QUAN_PLUS(465, 24, 2), QUAN_PLUS_1(465, 24, 3), QUAN_PLUS_10(465, 24, 4), QUAN_PLUS_100(465, 24, 5),
	QUAN_PLUS_1000(465, 24, 6), QUAN_CUSTOM(465, 24, 7),
	
	PRICE_MINUS(465, 24, 8), PRICE_PLUS(465, 24, 9), PRICE_MINUS_5(465, 24, 10), PRICE_CUSTOM(465, 24, 12), PRICE_PLUS_5(465, 24, 13),
	
	CONFIRM_NEW_OFFER(465, 27), 
	
	HISTORY_BTN(465, 3), EXCHANGE_BTN(383,2), COLLECT_BTN(465, 6, 0),
	
	CLOSE_OFFER(465, 2, 11), CLOSE_HISTORY(383, 1, 11), BACK_BTN(465, 4);
	
	
	private int parent, child, component, texture;
	private int[] id;
	
	GEInterfaces(int... ids){
		this.id = ids;
		switch(id.length) {
		case 1:
			this.parent = id[0];
			break;
		case 2:
			this.parent = id[0];
			this.child = id[1];
			break;
		case 3:
			this.parent = id[0];
			this.child = id[1];
			this.component = id[2];
			break;
		case 4:
			this.parent = id[0];
			this.child = id[1];
			this.component = id[2];
			this.texture = id[3];
		}
	}
	
	public boolean isVisible() {
		switch(id.length) {
		case 1:
			return Interfaces.get(parent) != null && !Interfaces.get(parent).isHidden();
		case 2:
			return Interfaces.get(parent, child) != null && !Interfaces.get(parent, child).isHidden();
		case 3:
			return Interfaces.get(parent, child, component) != null && !Interfaces.get(parent, child, component).isHidden();
		case 4:
			return Interfaces.get(parent, child, component) != null && !Interfaces.get(parent, child, component).isHidden() && Interfaces.get(parent, child, component).getTextureID() == texture;
		}
			
		return false;
	}
	
	public int[] getID() {
		switch(id.length) {
		case 1:
			return new int[] {parent};
		case 2:
			return new int[] {parent, child};
		}
		return new int[] {parent, child, component};
	}
	
	public RSInterface get() {
		switch(id.length) {
		case 1:
			return Interfaces.get(parent);
			
		case 2:
			return Interfaces.get(parent, child);
		}
		return Interfaces.get(parent, child, component);
	}
}

 

GEConditions Class

Spoiler
package scripts.api.utils.conditions;

import scripts.api.data.GEInterfaces;
import scripts.api.utils.grandexchange.Exchange;

public class GEConditions {

	public static Condition hasCollected() {
		return new Condition(() -> !GEInterfaces.COLLECT_BTN.isVisible());
	}
	
	public static Condition canCollect() {
		return new Condition(() -> GEInterfaces.COLLECT_BTN.isVisible());
	}
	
	public static Condition GEOpen() {
		return new Condition(() -> Exchange.isOpen());
	}
	
	public static Condition GEClosed() {
		return new Condition(() -> !Exchange.isOpen());
	}
	
	public static Condition GEBuyWindowVisible() {
		return new Condition(() -> GEInterfaces.BUY_WINDOW.isVisible());
	}
	
	public static Condition GESellWindowVisible() {
		return new Condition(() -> GEInterfaces.SELL_WINDOW.isVisible());
	}
	
	public static Condition GESelectionWindowVisible() {
		return new Condition(() -> GEInterfaces.SELECTION_WINDOW.isVisible());
	}
	
	public static Condition GESearchBoxDissapeared() {
		return new Condition(() -> !GEInterfaces.SEARCH_ITEM_INPUT_TEXT.isVisible());
	}
	
	public static Condition GESearchBoxAppeared() {
		return new Condition(() -> GEInterfaces.SEARCH_ITEM_INPUT_TEXT.isVisible());
	}
	
	private interface BooleanLambda{
        boolean active();
    }

    private static class Condition extends org.tribot.api.types.generic.Condition{
        private BooleanLambda lambda;

        public Condition(BooleanLambda lambda){
            super();
            this.lambda = lambda;
        }

        @Override
        public boolean active() {
            return lambda.active();
        }
    }
	
}

 

InfoHelper Class

Spoiler
package scripts.api.utils.helpers;

import org.tribot.api.General;
import org.tribot.api2007.types.RSItemDefinition;

public class InfoHelper {

	public static int stringToInt(String input, String... toRemove) {
		String[] forRemoving = toRemove;
		switch(forRemoving.length) {
		case 1:
			return Integer.parseInt(input.replace(forRemoving[0], ""));
		case 2:
			String first = input.replace(forRemoving[0], "");
			return Integer.parseInt(first.replace(forRemoving[1], ""));
		case 3:
			String first2 = input.replace(forRemoving[0], "");
			String second = first2.replace(forRemoving[1], "");
			return Integer.parseInt(second.replace(forRemoving[2], ""));
		default:
			return -1;
		}
	}
	
	public static String partNameToType(String fullName, String alreadyTyped) {
		String remaining = fullName.replaceFirst(alreadyTyped, "");
		return remaining.length() > 2 ? remaining.substring(0, General.random(1, 2)) : remaining;
	}
	
	public static String getItemNameFromID(int itemID){
        RSItemDefinition itemDef  = RSItemDefinition.get(itemID);
        if(itemDef !=null){
            String itemName = itemDef.getName();
            if(itemName!=null){
                return itemName;
            }
        }
        return "";
	}
	
}

 

 

  • Like 2
  • Thanks 2

Share this post


Link to post
Share on other sites
38 minutes ago, adamhackz said:

Damn looks sweet, thanks for the contribution

Thanks man, I'm glad it's appreciated :)Lemme if there's anything you'd add or change, I plan on adding a method int Exchange#getMargin(int itemID) tomorrow, and also the ability to start offers from the history screen which I envision happening after the getMargin method is called; if the margin returned meets our criteria for adding the item to our toBuy list (or toMerch list).

Share this post


Link to post
Share on other sites
On 1/28/2019 at 6:10 PM, botuser420 said:

Very nice. I'm using this now and it's working perfectly so far after updating a few interface id's 

That's awesome, great to hear! I haven't used it recently and don't actually think I fixed it myself. Getting back into it now I have more free time though. Let me know what else you change and if you have any ideas on improving it :)

Share this post


Link to post
Share on other sites
2 minutes ago, NeuroGenix said:

That's awesome, great to hear! I haven't used it recently and don't actually think I fixed it myself. Getting back into it now I have more free time though. Let me know what else you change and if you have any ideas on improving it :)

One improvement I can think of is also being able to use item names instead of ID's.

Share this post


Link to post
Share on other sites
2 minutes ago, botuser420 said:

One improvement I can think of is also being able to use item names instead of ID's.

I never did that because I found it easier to work with the ID's alone but it should be easy, by altering the constructor for this part since it works out the name straight away with the #getItemNameFromID method. Changing the "int itemID" to String itemName and then either giving it the ID or having a method that gets the ID from the name (like Inventory.Find(string name)) if it's something we're selling from the inventory or have just collected after buying.

public class Exchange {
	
	public static boolean offer(int itemID, int price, int quantity, boolean buy) {
		String name = InfoHelper.getItemNameFromID(itemID);

I'm going to re-write it soon, if I update it here I'll be sure to include a method that does it by name instead of ID. I'd also like it to buy from the history screen after checking the price, at some variable rate so sometimes it gets it from the offer screen directly.

 

Share this post


Link to post
Share on other sites
On 1/30/2019 at 5:22 PM, NeuroGenix said:

I never did that because I found it easier to work with the ID's alone but it should be easy, by altering the constructor for this part since it works out the name straight away with the #getItemNameFromID method. Changing the "int itemID" to String itemName and then either giving it the ID or having a method that gets the ID from the name (like Inventory.Find(string name)) if it's something we're selling from the inventory or have just collected after buying.

public class Exchange {
	
	public static boolean offer(int itemID, int price, int quantity, boolean buy) {
		String name = InfoHelper.getItemNameFromID(itemID);

I'm going to re-write it soon, if I update it here I'll be sure to include a method that does it by name instead of ID. I'd also like it to buy from the history screen after checking the price, at some variable rate so sometimes it gets it from the offer screen directly.

 

Thanks. I didn't look into using names yet but this is very easy to alter indeed. Since I got a PM about wich interfaces needed to be updated here is what I use: https://bitbucket.org/snippets/botuser420/bej9nx

I don't know if that is all that needed to be updated

Share this post


Link to post
Share on other sites
On 2/1/2019 at 1:20 AM, botuser420 said:

Thanks. I didn't look into using names yet but this is very easy to alter indeed. Since I got a PM about wich interfaces needed to be updated here is what I use: https://bitbucket.org/snippets/botuser420/bej9nx

I don't know if that is all that needed to be updated

Sweet I'll check it out, thank you :)

Share this post


Link to post
Share on other sites
On 9/13/2018 at 1:24 AM, NeuroGenix said:

After originally posting this in the Scripting Help section I have decided it would be better suited here where there might be a larger audience to get feedback from to improve my own API and learn a thing or two in the process.

What calling the method Exchange#offer(int itemID, int price, int quantity, boolean buy) will do:

  1. If the GE is closed it will open it and continue.
  2. Search for the item to buy 1-3 chars at a time, checking each time whether or not the first 9 results contains the item, clicking it if it does.
  3. If the price is set to -1 it will use the +/-5% buttons to increase/decrease the price until it is at a price that will buy/sell instantly, otherwise it will use the custom amount interface to set the price.
  4. It will set the quantity using the 1/10/100/1000 buttons, using the custom amount interface if it accidently goes over by more than 10, otherwise it will use the -1 button to get to the correct amount if it is within 10.
  5. If it gets onto the history screen, or the wrong offer type screen, it will return to the selection screen and continue.
  6. It doesn't collect items after it has made the offer, returning true when the offer has been made. This is so that in my scripts I can be selling 9+ items, from toBuy and toSell HashMaps, in one go; only collecting when there are no more empty slots. This, in my mind, is more human-like than collecting the item straight away after each offer is made, thoughts?
  7. It will prioritise buy offers over sell overs. So if it is buying something and there are no empty slots, or offers to collect, then it will cancel a sell offer, collect it and continue now it has an empty slot. If it is a sell offer and there are no slots available it will return true. This is so that in my scripts the item is removed from the toSell HashMap so it can be put back into to bank to sell later as my script has the items it needs to continue botting. If it has a buy offer and there are no empty slots, offers to collect or sell offers to cancel it will wait until an offer had completed, collect it and continue. I should add a time-out function here, and I'm also thinking of implementing some sort of priority feature whereby it will cancel a buy offer if this new buy offer has a higher priority, should be simple enough.

It currently doesn't implement any antiban, I am hoping some of you can suggest the best way to implement that.

Below is an example of the implementation. In this example, starting with the ge closed but near it, the bot will open the ge, buy 276 impling jars instantly, wait a few seconds before collecting them and then sell them all again instantly.

All of the classes required to run this are below. I hope this can be helpful to somebody and I'm sure someone reading this can be helpful to me, thanks for reading, lemme know what you think.

Zoo Class - Example of the code working

  Reveal hidden contents

package scripts.zoo;

import org.tribot.api.General;
import org.tribot.api2007.Inventory;
import org.tribot.api2007.types.RSItem;
import org.tribot.script.Script;
import org.tribot.script.ScriptManifest;

import scripts.api.data.GEInterfaces;
import scripts.api.utils.grandexchange.Exchange;
import scripts.api.utils.helpers.InfoHelper;

@ScriptManifest(authors = { "Kyle" }, category = "Zoo", name = "Zoo")

public class Zoo extends Script {

	@Override
	public void run() {
		while(true) {
			if(Exchange.offer(11260, -1, 276, true)) {
				General.println("Successfully bought");
				General.sleep(3000);
				if(GEInterfaces.COLLECT_BTN.isVisible()) {
					if(GEInterfaces.COLLECT_BTN.get().click()) {
						General.println("Successfully collected" );
						General.sleep(3000);
						
						RSItem[] impJars = Inventory.find(InfoHelper.getItemNameFromID(11260));
						if(impJars != null && impJars.length > 0) {
							if(Exchange.offer(impJars[0].getID()-1, -1, impJars[0].getStack(), false)) {
								General.println("Successfully sold");
								General.sleep(3000);
							} else {
								General.println("Failed");
							}
						}
					}
				}
			} else {
				General.println("Failed");
				General.sleep(5000);
			}
		}
	}

}

 

Exchange Class - The main class

  Reveal hidden contents

package scripts.api.utils.grandexchange;

import org.tribot.api.Clicking;
import org.tribot.api.General;
import org.tribot.api.Timing;
import org.tribot.api.input.Keyboard;
import org.tribot.api2007.Interfaces;
import org.tribot.api2007.Inventory;
import org.tribot.api2007.NPCs;
import org.tribot.api2007.types.RSInterface;
import org.tribot.api2007.types.RSItem;
import org.tribot.api2007.types.RSNPC;

import scripts.api.data.GEInterfaces;
import scripts.api.utils.conditions.GEConditions;
import scripts.api.utils.helpers.InfoHelper;

public class Exchange {
	
	public static boolean offer(int itemID, int price, int quantity, boolean buy) {
		String name = InfoHelper.getItemNameFromID(itemID);
		if(name == null) return false;
		
		switch(getOfferState(itemID, price, quantity, buy)) {
		case GE_CLOSED:
			open();
			General.sleep(250,400);
			break;
		case HISTORY_SCREEN:
			exitHistoryScreen();
			General.sleep(250,400);
			break;
		case LOADING_SCREENS:
			General.sleep(300,600);
			break;
		case OFFER_SCREEN_ITEM_WRONG:
			handleWrongOfferItem(name, itemID, buy);
			General.sleep(250,400);
			break;
		case OFFER_SCREEN_OFFER_CORRECT:
			if(GEInterfaces.CONFIRM_NEW_OFFER.get().click())
				Timing.waitCondition(GEConditions.GESelectionWindowVisible(), General.random(4000, 7000));
			General.sleep(250,400);
			break;
		case OFFER_SCREEN_PRICE_WRONG:
			handleWrongOfferPrice(price, buy);
			General.sleep(600, 900);
			break;
		case OFFER_SCREEN_QUANTITY_WRONG:
			handleWrongOfferQuantity(quantity);
			General.sleep(150, 300);
			break;
		case OFFER_SCREEN_TYPE_WRONG:
			if(GEInterfaces.BACK_BTN.get().click())
				Timing.waitCondition(GEConditions.GESelectionWindowVisible(), General.random(4000, 7000));
			General.sleep(250,400);
			break;
		case SELECTION_SCREEN_CAN_COLLECT:
			if(GEInterfaces.COLLECT_BTN.get().click())
				Timing.waitCondition(GEConditions.hasCollected(), General.random(4000, 7000));
			General.sleep(250,400);
			break;
		case SELECTION_SCREEN_CAN_MAKE_OFFER:
			handleMakingOffer(name, buy);
			General.sleep(600,1200);
			break;
		case SELECTION_SCREEN_OFFER_ACTIVE:
			return true;
		case SELECTION_SCREEN_SHOULD_CANCEL_OFFER:
			handleCancellingOffer();
			General.sleep(400,700);
			break;
		case SELECTION_SCREEN_SHOULD_RETURN_TRUE:
			return true;
		case SELECTION_SCREEN_SHOULD_WAIT:
			General.sleep(3000, 6000);
			break;
		}
		
		return offer(itemID, price, quantity, buy);
	}
	
	public static OfferState getOfferState(int itemID, int price, int quantity, boolean buy) {
		if(isOpen()) {
			//The GE is open, what window are we on?
			if(GEInterfaces.SELECTION_WINDOW.isVisible()) {
				//We are on the selection window.
				for(int i = 7; i < 15 ; i++) {
					//Do we already have an offer of this item?
					RSInterface item = Interfaces.get(465, i, 18);
					if(item != null && !item.isHidden()) {
						if(item.getComponentItem() == itemID) {
							//Yes we do, is it the right type?
							RSInterface type = Interfaces.get(465, i, 16);
							if(type != null && !type.isHidden()) {
								if(type.getText().contains("Buy")) {
									if(buy == true) {
										//Yes it is, return we have made the offer.
										return OfferState.SELECTION_SCREEN_OFFER_ACTIVE;
									}
								} else {
									if(buy == false) {
										//Yes it is, return we have made the offer.
										return OfferState.SELECTION_SCREEN_OFFER_ACTIVE;
									}
								}
							}
						}
					}
				}	
				//No we don't already have an active offer
				for(int b = 7; b < 15 ; b++) {
					//Do we have an empty offer slot?
					RSInterface type = Interfaces.get(465, b, 16);
					if(type != null && !type.isHidden()) {
						if(type.getText().contains("Empty")) {
							//Yes we do, return we can make the offer
							return OfferState.SELECTION_SCREEN_CAN_MAKE_OFFER;
						}
					}
				}
				//No we don't, do we have any offers to collect?
				if(GEInterfaces.COLLECT_BTN.isVisible()) {
					//Yes we do, return we can collect offers
					return OfferState.SELECTION_SCREEN_CAN_COLLECT;
				}
						
				//No we don't, should we cancel an offer?
				if(buy == true) {
					//Check if there is a sell offer to cancel
					for(int c = 7; c < 15 ; c++) {
						RSInterface type = Interfaces.get(465, c, 16);
						if(type != null && !type.isHidden()) {
							if(type.getText().contains("Sell")) {
								//Yes there is, return we can abort a sell offer.
								return OfferState.SELECTION_SCREEN_SHOULD_CANCEL_OFFER;
							}
						}
					}
					
					//No there isn't, we should wait because we are trying to buy something
					return OfferState.SELECTION_SCREEN_SHOULD_WAIT;
				} else {
					return OfferState.SELECTION_SCREEN_SHOULD_RETURN_TRUE;
				}
			} else if(GEInterfaces.CONFIRM_NEW_OFFER.isVisible()) {
				//We are on an offer screen, check that it is the right type
				if(GEInterfaces.BUY_WINDOW.isVisible())
					if(buy == false)
						return OfferState.OFFER_SCREEN_TYPE_WRONG;
				
				if(GEInterfaces.SELL_WINDOW.isVisible())
					if(buy == true)
						return OfferState.OFFER_SCREEN_TYPE_WRONG;
				
				//The type is correct, check if the item is correct
				if(GEInterfaces.OFFER_ITEM.get().getComponentItem() == itemID) {
					//The item is correct, check if the price is correct
					int currentPrice = InfoHelper.stringToInt(GEInterfaces.OFFER_PRICE.get().getText(), " coins",",");
					if(price == -1) {
						int avrPrice = InfoHelper.stringToInt(GEInterfaces.OFFER_AVR_PRICE.get().getText(), ",");
						if(buy == true) {
							Double targ = avrPrice * 1.2;
							int target = targ.intValue();
							if(currentPrice > target) {
								//The price is correct, check if the quantity is correct
								int currentQuantity = InfoHelper.stringToInt(GEInterfaces.OFFER_QUANTITY.get().getText(), ",");
								if(currentQuantity == quantity) {
									//The quantity is correct, return confirm offer
									return OfferState.OFFER_SCREEN_OFFER_CORRECT;
								} else {
									//No the quantity isn't correct, return quantity wrong
									return OfferState.OFFER_SCREEN_QUANTITY_WRONG;
								}
							} else {
								//the price is wrong
								return OfferState.OFFER_SCREEN_PRICE_WRONG;
							}
						} else {
							Double targ = avrPrice * 0.67;
							int target = targ.intValue();
							if(currentPrice < target) {
								//The price is correct, check if the quantity is correct
								int currentQuantity = InfoHelper.stringToInt(GEInterfaces.OFFER_QUANTITY.get().getText(), ",");
								if(currentQuantity == quantity) {
									//The quantity is correct, return confirm offer
									return OfferState.OFFER_SCREEN_OFFER_CORRECT;
								} else {
									//No the quantity isn't correct, return quantity wrong
									return OfferState.OFFER_SCREEN_QUANTITY_WRONG;
								}
							} else {
								//the price is wrong
								return OfferState.OFFER_SCREEN_PRICE_WRONG;
							}
						}
					} else if(currentPrice == price) {
						//The price is correct, check if the quantity is correct
						int currentQuantity = InfoHelper.stringToInt(GEInterfaces.OFFER_QUANTITY.get().getText(), ",");
						if(currentQuantity == quantity) {
							//The quantity is correct, return confirm offer
							return OfferState.OFFER_SCREEN_OFFER_CORRECT;
						} else {
							//No the quantity isn't correct, return quantity wrong
							return OfferState.OFFER_SCREEN_QUANTITY_WRONG;
						}
					} else {
						//No the price isn't correct, return price wrong
						return OfferState.OFFER_SCREEN_PRICE_WRONG;
					}
				} else {
					//The item isn't correct
					return OfferState.OFFER_SCREEN_ITEM_WRONG;
				}
			} else if(GEInterfaces.HISTORY_WINDOW.isVisible()){
				//We are on the history screen, return on history screen
				return OfferState.HISTORY_SCREEN;
			} else {
				return OfferState.LOADING_SCREENS;
			}
		} else {
			return OfferState.GE_CLOSED;
		}
	}
	
	public static enum QuantityState {
		PLUS_1000, PLUS_100, PLUS_10, PLUS_1, CUSTOM, PLUS, MINUS, CORRECT
	}

	public static QuantityState getQuantityState(int goal, int current) {
		if(goal == current)
			return QuantityState.CORRECT;
		if(goal > current + 998)
			return QuantityState.PLUS_1000;
		if(goal > current + 98)
			return QuantityState.PLUS_100;
		if(goal > current + 9)
			return QuantityState.PLUS_10;
		if(goal > current)
			return General.random(1, 10) > 2 ? QuantityState.PLUS_1 : QuantityState.PLUS;
		if(goal + 10 < current)
			return QuantityState.CUSTOM;
		return QuantityState.MINUS;
	}
	
	public enum OfferState {
		 GE_CLOSED,
		 SELECTION_SCREEN_OFFER_ACTIVE,
		 SELECTION_SCREEN_CAN_MAKE_OFFER,
		 SELECTION_SCREEN_CAN_COLLECT,
		 SELECTION_SCREEN_SHOULD_RETURN_TRUE,
		 SELECTION_SCREEN_SHOULD_CANCEL_OFFER,
		 SELECTION_SCREEN_SHOULD_WAIT,
		 OFFER_SCREEN_TYPE_WRONG,
		 OFFER_SCREEN_ITEM_WRONG,
		 OFFER_SCREEN_PRICE_WRONG,
		 OFFER_SCREEN_QUANTITY_WRONG,
		 OFFER_SCREEN_OFFER_CORRECT,
		 HISTORY_SCREEN,
		 LOADING_SCREENS
	}
	
	public enum WindowState {
		CLOSED, SELECTION_SCREEN, BUY_SCREEN, SELL_SCREEN, HISTORY_SCREEN
	}
	
	public static WindowState getWindowState() {
		if(GEInterfaces.SELECTION_WINDOW.isVisible())
			return WindowState.SELECTION_SCREEN;
		
		if(GEInterfaces.HISTORY_WINDOW.isVisible())
			return WindowState.HISTORY_SCREEN;
		
		if(GEInterfaces.BUY_WINDOW.isVisible())
			return WindowState.BUY_SCREEN;
		
		if(GEInterfaces.SELL_WINDOW.isVisible())
			return WindowState.SELL_SCREEN;
		
		return WindowState.CLOSED;
	}
	
	public static void handleCancellingOffer() {
		for(int i = 7; i < 15 ; i++) {
			RSInterface type = Interfaces.get(465, i, 16);
			if(type != null && !type.isHidden()) {
				if(type.getText().contains("Sell")) {
					RSInterface slot = Interfaces.get(465, i, 2);
					if(slot != null && !slot.isHidden()) {
						if(slot.click("Abort offer")) {
							Timing.waitCondition(GEConditions.canCollect(), General.random(4000, 7000));
						}
					}
				}
			}
		}
	}
	
	public static void handleMakingOffer(String name, boolean buy) {
		if(buy == true) {
			for(int i = 7; i < 15 ; i++) {
				RSInterface type = Interfaces.get(465, i, 16);
				if(type != null && !type.isHidden()) {
					if(type.getText().contains("Empty")) {
						RSInterface buyBtn = Interfaces.get(465, i, 3);
						if(buyBtn != null && !buyBtn.isHidden()) {
							if(buyBtn.click()) {
								Timing.waitCondition(GEConditions.GEBuyWindowVisible(), General.random(4000, 7000));
							}
						}
					}
				}
			}
		} else {
			RSItem[] target = Inventory.find(name);
			if(target != null && target.length > 0) {
				if(target[0].click()) {
					Timing.waitCondition(GEConditions.GESellWindowVisible(), General.random(4000, 7000));
				}
			}
		}
	}
	
	public static void handleWrongOfferPrice(int price, boolean buy) {
		if(GEInterfaces.OFFER_AVR_PRICE.isVisible()) {
			if(price == -1) {
				if(buy == true) {
					GEInterfaces.PRICE_PLUS_5.get().click();
					return;
				} else {
					GEInterfaces.PRICE_MINUS_5.get().click();
					return;
				}
			}
		}
		
		if(GEInterfaces.SEARCH_ITEM_INPUT_TEXT.isVisible()) {
			Keyboard.typeSend(Integer.toString(price));
			Timing.waitCondition(GEConditions.GESearchBoxDissapeared(), General.random(4000, 7000));
			return;
		} 
		
		if(GEInterfaces.PRICE_CUSTOM.isVisible()) {
			if(GEInterfaces.PRICE_CUSTOM.get().click()) {
				Timing.waitCondition(GEConditions.GESearchBoxAppeared(), General.random(4000, 7000));
				return;
			}
		}
	}
	
	public static void handleWrongOfferQuantity(int quantity) {
		// quantity isn't correct
		if(GEInterfaces.SEARCH_ITEM_INPUT_TEXT.isVisible()) {
			Keyboard.typeSend(Integer.toString(quantity));
			Timing.waitCondition(GEConditions.GESearchBoxDissapeared(), General.random(4000, 7000));
			return;
		} 
		int currentQuantity = InfoHelper.stringToInt(GEInterfaces.OFFER_QUANTITY.get().getText(), ",");
		switch(getQuantityState(quantity, currentQuantity)) {
		case CORRECT:
			break;
		case CUSTOM:
			if(GEInterfaces.QUAN_CUSTOM.isVisible()) {
				GEInterfaces.QUAN_CUSTOM.get().click();
				Timing.waitCondition(GEConditions.GESearchBoxAppeared(), General.random(4000, 7000));
			}
			break;
		case MINUS:
			if(GEInterfaces.QUAN_MINUS.isVisible())
				GEInterfaces.QUAN_MINUS.get().click();
			break;
		case PLUS:
			if(GEInterfaces.QUAN_PLUS.isVisible())
				GEInterfaces.QUAN_PLUS.get().click();
			break;
		case PLUS_1:
			if(GEInterfaces.QUAN_PLUS_1.isVisible())
				GEInterfaces.QUAN_PLUS_1.get().click();
			break;
		case PLUS_10:
			if(GEInterfaces.QUAN_PLUS_10.isVisible())
				GEInterfaces.QUAN_PLUS_10.get().click();
			break;
		case PLUS_100:
			if(GEInterfaces.QUAN_PLUS_100.isVisible())
				GEInterfaces.QUAN_PLUS_100.get().click();
			break;
		case PLUS_1000:
			if(GEInterfaces.QUAN_PLUS_1000.isVisible())
				GEInterfaces.QUAN_PLUS_1000.get().click();
			break;
		}
	}
	
	public static void handleWrongOfferItem(String name, int itemID, boolean buy) {
		if(buy == true) {
			if(GEInterfaces.SEARCH_ITEM_INPUT_TEXT.isVisible()) {
				if(GEInterfaces.SEARCH_ITEM_RESULTS_CONTAINER.isVisible()) {
					if(hasSearchTarget(itemID)) {
						if(clickSearchTarget(itemID)) {
							General.sleep(1300, 2100);
							return;
						}
					} else {
						String alreadyTyped = GEInterfaces.SEARCH_ITEM_INPUT_TEXT.get().getText();
						String trimmed = alreadyTyped.substring(46, alreadyTyped.length()-1);
						Keyboard.typeString(InfoHelper.partNameToType(name, trimmed.replace("*", "")));
						General.sleep(600,900);
						return;
					}
					
				}
			}
			
			if(GEInterfaces.OFFER_ITEM.isVisible()) {
				if(GEInterfaces.OFFER_ITEM.get().click()) {
					Timing.waitCondition(GEConditions.GESearchBoxAppeared(), General.random(4000, 7000));
					return;
				}
			}
		} else {
			RSItem[] target = Inventory.find(itemID);
			if(target != null && target.length < 0) {
				if(target[0].click()) {
					General.sleep(900, 2100);
				}
			}
		}
	}
	
	public static boolean hasSearchTarget(int itemID) {
		if(GEInterfaces.SEARCH_RESULT_1.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_1.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_2.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_2.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_3.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_3.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_4.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_4.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_5.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_5.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_6.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_6.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_7.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_7.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_8.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_8.get().getComponentItem() == itemID) {
				return true;
			}
		}
		if(GEInterfaces.SEARCH_RESULT_9.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_9.get().getComponentItem() == itemID) {
				return true;
			}
		}
		return false;
	}
	
	public static boolean clickSearchTarget(int itemID) {
		if(GEInterfaces.SEARCH_RESULT_1.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_1.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_1.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_2.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_2.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_2.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_3.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_3.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_3.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_4.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_4.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_4.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_5.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_5.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_5.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_6.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_6.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_6.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_7.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_7.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_7.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_8.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_8.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_8.get().click();
			}
		}
		if(GEInterfaces.SEARCH_RESULT_9.isVisible()) {
			if(GEInterfaces.SEARCH_RESULT_9.get().getComponentItem() == itemID) {
				return GEInterfaces.SEARCH_RESULT_9.get().click();
			}
		}
		return false;
	}
	
	public static void exitHistoryScreen() {
		if(GEInterfaces.EXCHANGE_BTN.isVisible()) {
			if(GEInterfaces.EXCHANGE_BTN.get().click()) {
				Timing.waitCondition(GEConditions.GESelectionWindowVisible(), General.random(4000, 7000));
			}
		}
	}
	
	public static boolean isOpen() {
		return GEInterfaces.MASTER.isVisible() || GEInterfaces.HISTORY_WINDOW.isVisible();
	}
	
	public static boolean open() {
		if(isOpen()) return true;
		
		RSNPC[] geClerks = NPCs.findNearest("Grand Exchange Clerk");
		if (geClerks.length > 0)
			if(Clicking.click("Exchange Grand Exchange Clerk", geClerks[0])) 
				Timing.waitCondition(GEConditions.GEOpen(), General.random(3000, 7000));
				return isOpen();
	}

}

 

GEInterfaces Class

  Reveal hidden contents

package scripts.api.data;

import org.tribot.api2007.Interfaces;
import org.tribot.api2007.types.RSInterface;

public enum GEInterfaces {

	MASTER(465), SELECTION_WINDOW(465, 7, 16), HISTORY_WINDOW(383, 3, 0), BUY_WINDOW(465, 24, 19, 1118), SELL_WINDOW(465, 24, 19, 1119),
	
	SLOT(465, 7), SLOT_ACTIVE(465, 7, 2), SLOT_TYPE(465, 7, 16), SLOT_ITEM(465, 7, 18), SLOT_NAME(465, 7, 19), 
	SLOT_PROGRESS_WIDTH(465, 7, 22), SLOT_PRICE(465, 7, 25), SLOT_BUY_BTN(465, 7, 3), SLOT_SELL_BTN(465, 7, 4),
	
	SEARCH_ITEM_RESULTS_CONTAINER(162, 46), SEARCH_ITEM_INPUT_TEXT(162, 38),
	SEARCH_RESULT_1(162, 46, 2), SEARCH_RESULT_2(162, 46, 5), SEARCH_RESULT_3(162, 46, 8), SEARCH_RESULT_4(162, 46, 11), 
	SEARCH_RESULT_5(162, 46, 14), SEARCH_RESULT_6(162, 46, 17), SEARCH_RESULT_7(162, 46, 20), SEARCH_RESULT_8(162, 46, 23),
	SEARCH_RESULT_9(162, 46, 26),
	
	OFFER_NAME(465, 24, 25), OFFER_ITEM(465, 24, 21), OFFER_PRICE(465, 24, 39), OFFER_QUANTITY(465, 24, 32), OFFER_TOTAL(465, 24, 43),
	OFFER_AVR_PRICE(465, 26),
	
	QUAN_MINUS(465, 24, 1), QUAN_PLUS(465, 24, 2), QUAN_PLUS_1(465, 24, 3), QUAN_PLUS_10(465, 24, 4), QUAN_PLUS_100(465, 24, 5),
	QUAN_PLUS_1000(465, 24, 6), QUAN_CUSTOM(465, 24, 7),
	
	PRICE_MINUS(465, 24, 8), PRICE_PLUS(465, 24, 9), PRICE_MINUS_5(465, 24, 10), PRICE_CUSTOM(465, 24, 12), PRICE_PLUS_5(465, 24, 13),
	
	CONFIRM_NEW_OFFER(465, 27), 
	
	HISTORY_BTN(465, 3), EXCHANGE_BTN(383,2), COLLECT_BTN(465, 6, 0),
	
	CLOSE_OFFER(465, 2, 11), CLOSE_HISTORY(383, 1, 11), BACK_BTN(465, 4);
	
	
	private int parent, child, component, texture;
	private int[] id;
	
	GEInterfaces(int... ids){
		this.id = ids;
		switch(id.length) {
		case 1:
			this.parent = id[0];
			break;
		case 2:
			this.parent = id[0];
			this.child = id[1];
			break;
		case 3:
			this.parent = id[0];
			this.child = id[1];
			this.component = id[2];
			break;
		case 4:
			this.parent = id[0];
			this.child = id[1];
			this.component = id[2];
			this.texture = id[3];
		}
	}
	
	public boolean isVisible() {
		switch(id.length) {
		case 1:
			return Interfaces.get(parent) != null && !Interfaces.get(parent).isHidden();
		case 2:
			return Interfaces.get(parent, child) != null && !Interfaces.get(parent, child).isHidden();
		case 3:
			return Interfaces.get(parent, child, component) != null && !Interfaces.get(parent, child, component).isHidden();
		case 4:
			return Interfaces.get(parent, child, component) != null && !Interfaces.get(parent, child, component).isHidden() && Interfaces.get(parent, child, component).getTextureID() == texture;
		}
			
		return false;
	}
	
	public int[] getID() {
		switch(id.length) {
		case 1:
			return new int[] {parent};
		case 2:
			return new int[] {parent, child};
		}
		return new int[] {parent, child, component};
	}
	
	public RSInterface get() {
		switch(id.length) {
		case 1:
			return Interfaces.get(parent);
			
		case 2:
			return Interfaces.get(parent, child);
		}
		return Interfaces.get(parent, child, component);
	}
}

 

GEConditions Class

  Reveal hidden contents

package scripts.api.utils.conditions;

import scripts.api.data.GEInterfaces;
import scripts.api.utils.grandexchange.Exchange;

public class GEConditions {

	public static Condition hasCollected() {
		return new Condition(() -> !GEInterfaces.COLLECT_BTN.isVisible());
	}
	
	public static Condition canCollect() {
		return new Condition(() -> GEInterfaces.COLLECT_BTN.isVisible());
	}
	
	public static Condition GEOpen() {
		return new Condition(() -> Exchange.isOpen());
	}
	
	public static Condition GEClosed() {
		return new Condition(() -> !Exchange.isOpen());
	}
	
	public static Condition GEBuyWindowVisible() {
		return new Condition(() -> GEInterfaces.BUY_WINDOW.isVisible());
	}
	
	public static Condition GESellWindowVisible() {
		return new Condition(() -> GEInterfaces.SELL_WINDOW.isVisible());
	}
	
	public static Condition GESelectionWindowVisible() {
		return new Condition(() -> GEInterfaces.SELECTION_WINDOW.isVisible());
	}
	
	public static Condition GESearchBoxDissapeared() {
		return new Condition(() -> !GEInterfaces.SEARCH_ITEM_INPUT_TEXT.isVisible());
	}
	
	public static Condition GESearchBoxAppeared() {
		return new Condition(() -> GEInterfaces.SEARCH_ITEM_INPUT_TEXT.isVisible());
	}
	
	private interface BooleanLambda{
        boolean active();
    }

    private static class Condition extends org.tribot.api.types.generic.Condition{
        private BooleanLambda lambda;

        public Condition(BooleanLambda lambda){
            super();
            this.lambda = lambda;
        }

        @Override
        public boolean active() {
            return lambda.active();
        }
    }
	
}

 

InfoHelper Class

  Reveal hidden contents

package scripts.api.utils.helpers;

import org.tribot.api.General;
import org.tribot.api2007.types.RSItemDefinition;

public class InfoHelper {

	public static int stringToInt(String input, String... toRemove) {
		String[] forRemoving = toRemove;
		switch(forRemoving.length) {
		case 1:
			return Integer.parseInt(input.replace(forRemoving[0], ""));
		case 2:
			String first = input.replace(forRemoving[0], "");
			return Integer.parseInt(first.replace(forRemoving[1], ""));
		case 3:
			String first2 = input.replace(forRemoving[0], "");
			String second = first2.replace(forRemoving[1], "");
			return Integer.parseInt(second.replace(forRemoving[2], ""));
		default:
			return -1;
		}
	}
	
	public static String partNameToType(String fullName, String alreadyTyped) {
		String remaining = fullName.replaceFirst(alreadyTyped, "");
		return remaining.length() > 2 ? remaining.substring(0, General.random(1, 2)) : remaining;
	}
	
	public static String getItemNameFromID(int itemID){
        RSItemDefinition itemDef  = RSItemDefinition.get(itemID);
        if(itemDef !=null){
            String itemName = itemDef.getName();
            if(itemName!=null){
                return itemName;
            }
        }
        return "";
	}
	
}

 

 

Had a look and change some Interface IDs as well as added a couple things,

Couldn't buy a "knife" with the original setup as even if you type the full word "knife" it was still way past the first 9 search results. I added up to 27 results as well as a check that if the item's name has been fully typed and its still not there then scroll down. Added a failsafe for if the script starts searching before interface is fully loaded(would once in a while catch it writing nife, then it would try to write knife on to and would end up with nifeknifeknifeknife). Now if the first char type doesn't match the first char of the item name it will backspace first.

I'm going to try to keep maintaining it, I put it on my github in case anyone else wants to look at what I changed. GrandExchangeSnippet

Share this post


Link to post
Share on other sites
On 11/7/2019 at 10:33 PM, kryptonite93 said:

Had a look and change some Interface IDs as well as added a couple things,

Couldn't buy a "knife" with the original setup as even if you type the full word "knife" it was still way past the first 9 search results. I added up to 27 results as well as a check that if the item's name has been fully typed and its still not there then scroll down. Added a failsafe for if the script starts searching before interface is fully loaded(would once in a while catch it writing nife, then it would try to write knife on to and would end up with nifeknifeknifeknife). Now if the first char type doesn't match the first char of the item name it will backspace first.

I'm going to try to keep maintaining it, I put it on my github in case anyone else wants to look at what I changed. GrandExchangeSnippet

Thats great man, thank you.

Share this post


Link to post
Share on other sites
On 7/17/2020 at 10:26 PM, Encoded said:

Lots of missing null checks on interfaces and and unhandled exceptions from Integer.parseInt

Thanks for looking through and giving feedback, I've been considering rewriting this completely now I've learnt more because I made some big mistakes. I might try doing it in kotlin as I'm learning that rn.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Our picks

    • Gradle is a build tool used to accelerate developer productivity.

      We recently setup a Maven repository (TRiBot Central) to make it easier for scripters to create scripts. Check it out here: https://gitlab.com/trilez-software/tribot/tribot-central/-/packages

      Furthermore, we've released a simple Gradle project to make it easy to run TRiBot and develop scripts for it. Check it out here: https://gitlab.com/trilez-software/tribot/tribot-gradle-launcher

      The goals of TRiBot Central are to:

      Deliver updates to TRiBot faster


      Better organize TRiBot's dependencies (AKA dependancies)


      Make it easier to develop scripts for TRiBot


      Make it easier to use and run TRiBot


      Note: TRiBot won't be able to run scripts from within this project until TRiBot's next release.
        • Like
      • 8 replies
    • Hi everyone,

      I'd like to thank everyone for their patience in this transition period. Since last week, we've worked out the remaining bugs with this integration.

      Some users have still been having issues with connecting their forums account to their Auth0 account. To resolve this, we've imported all forums accounts into Auth0.

      Unfortunately, the accounts which were imported today were using an unsupported password hashing algorithm. Hence, random passwords were set during the import.

      What does this mean for me?

      If you've previously linked your forums account to your Auth0 account, you don't have to do anything. Nothing changes for you.


      If you haven't logged in via our new login yet,

      Try logging in with your forums email address and the last password you used


      If you are unable to login, please use the "Forgot password" tool on the login page:
      Follow the instructions to reset your password
       
        • thonking
        • Like
      • 13 replies
    • Hello everyone,

      Last week we tried to roll out Auth0 Login, but we lost that battle. Now it's time to win the war!

      Important changes

      When logging into the client, you'll now have to enter your Auth0 account credentials instead of your forums credentials

      Note: 2FA is still handled through your forums account (for the time being)



      Changes for existing users

      You'll have to link your Auth0 account to your forums account here: https://tribot.org/forums/settings/login/?service=11


      Auth0 accounts have been created for most existing users. Please use your forums email address and password to login.



      Important notes

      Make sure to verify your email address upon creating a new Auth0 account


      When we mention your Auth0 account, we mean your account used for auth.tribot.org as displayed below
      • 81 replies
    • To better support the upcoming changes (TRiBot X, new repository), we're switching our login handler to Auth0. Instead of logging in with the standard form, you'll now be required to login through our Auth0 application.

      All existing accounts which have been used within approximately the past year have been imported into Auth0 using the same email and password combination which has been stored on the forums.

      What does this mean for users?

      Your account credentials are now even more securely stored


      You'll be able to login via Facebook, Google, and others in the future


      Is there anything users have to do differently now?

      Existing users: You'll have to login with the standard login, open your Account Settings, then link your Auth0 account


      New users: You'll be redirected to our Auth0 app (auth.tribot.org) where you'll be able to create an account


      Why was this change made?

      The new apps we are creating (such as the new repository) aren't able to use the forums to handle user logins


      To centralize all user accounts in one area


      To ensure that the client login doesn't go down when the forums are having problems


      To speed up our development


      Other considerations

      There's no documentation or official support for using Invision Community combined with Auth0, so there are still a few kinks we're working out


      We're in the works of creating an account management panel specifically for Auth0 accounts (ETA August)


      It's not possible to change email addresses for the time being (this will be resolved this August)


      Changing passwords is a weird process for the time being. To change your password, you'll have to use the "Don't remember your password" tool on the Auth0 login page
        • Like
      • 11 replies
    • Over the past month, we've been working hard on TRiBot's new repository - a much needed update. This change has been deemed necessary for TRiBot X, and will allow us to really speed up development of all aspects of TRiBot.

      Today we are going to share what we've been working on!


      Now you must be wondering what kind of features the new repository will have.... well, you'll have to be patient for a little while longer. We're still figuring out various technical aspects so we can't provide answers to all possible questions. We're also focusing on development rather than writing about it so that everyone can get access to our latest developments at lightning speed. I will however answer a few users' questions.

      We're planning on a release of this early to mid August, giving users some goodies before TRiBot X's release.

      Thank you all for being patient. I hope everyone is excited as much as I am!
        • Like
      • 17 replies
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...