package net.scapeemulator.game.content.shop; import net.scapeemulator.game.model.definition.ItemDefinitions; import net.scapeemulator.game.model.player.Item; import net.scapeemulator.game.util.math.BasicMath; /** * Represents an in-game Shop with a certain databaseId, name & shopId. A Shop can have a main stock * with a (in)finite supply. A Shop can have a player stock. */ public class Shop { private final String name; private final int shopId; protected static final int AMOUNT_STOCK = 40; private Item[] mainStock; private Item[] playerStock; /** * Creates a Shop with a certain databaseId, name and shopId. The provided stockIds are used to * fill up the stock of this Shop along with the stockAmounts. The rest of the stock is filled * with -1. If stockAmounts contains less elements than stockIds, amount 1 is further used * instead. * * @param databaseId The databaseId for this Shop * @param name The name for this Shop. * @param shopId The id for this Shop. * @param hasPlayerStock Whether this Shop has a player stock. * @param stockIds The itemId's which are meant to be in this Shop, this shouldn't be more than * this shop can hold, {@link AMOUNT_STOCK}. * @param stockAmounts The amount for each itemId this Shop has in its main stock. */ public Shop(String name, int shopId, boolean hasPlayerStock, int[] stockIds, int[] stockAmounts) { this.name = name; this.shopId = shopId; mainStock = new Item[AMOUNT_STOCK]; if (hasPlayerStock) { playerStock = new Item[AMOUNT_STOCK]; } for (int i = 0; i < mainStock.length && i < stockIds.length; i++) { int amount = (i >= stockAmounts.length) ? 1 : stockAmounts[i]; mainStock[i] = new Item(stockIds[i], amount); } } /** * Creates a Shop with a certain databaseId, name and shopId. The provided stockIds are used to * fill up the stock of this Shop with amount being 1. The rest of the stock is filled with -1. * * @param databaseId The databaseId for this Shop * @param name The name for this Shop. * @param shopId The id for this Shop. * @param infinite Whether the main stock of this Shop is infinite. * @param hasPlayerStock Whether this Shop has a player stock. * @param stockIds The itemId's which are meant to be in this Shop, this shouldn't be more than * this shop can hold, {@link AMOUNT_STOCK}. */ public Shop(String name, int shopId, boolean hasPlayerStock, int[] stockIds) { this(name, shopId, hasPlayerStock, stockIds, new int[0]); } /** * Creates a Shop with a certain databaseId, name and shopId. The provided mainStock is used to * fill the main stock of this Shoplink Shop}. * * @param databaseId The databaseId for this Shop * @param name The name for this Shop. * @param shopId The id for this Shop. * @param infinite Whether the main stock of this Shop is infinite. * @param hasPlayerStock Whether this Shop has a player stock. * @param mainStock The itemsInStock used for the mainStock. A copy of this array will be used, * up to {@link AMOUNT_STOCK} elements maximum. */ public Shop(String name, int shopId, boolean infinite, boolean hasPlayerStock, Item[] mainStock) { this.name = name; this.shopId = shopId; mainStock = new Item[AMOUNT_STOCK]; if (hasPlayerStock) { playerStock = new Item[AMOUNT_STOCK]; } System.arraycopy(mainStock, 0, this.mainStock, 0, Math.min(this.mainStock.length, mainStock.length)); } /** * Creates a very basic, empty Shop with the main stock disabled. * * @param databaseId The databaseId for this Shop. * @param name The name for this Shop. * @param shopId The id for this Shop. */ public Shop(String name, int shopId) { this.name = name; this.shopId = shopId; this.playerStock = new Item[AMOUNT_STOCK]; } /** * Gets the name of this Shop. * * @return The name, which can be used as the title on top of the interface. */ public String getName() { return this.name; } /** * Gets the Id of this Shop. * * @return The id of this shop. */ public int getShopId() { return this.shopId; } /** * Gets whether this Shop has the stock specified. * * @param stock The {@link StockType} specifies which "stock" we want the information of. * @return True if such stock exists in this Shop. */ public boolean hasStock(StockType stock) { switch (stock) { case MAIN: return this.mainStock != null; case PLAYER: return this.playerStock != null; } return false; } /** * Gets the specified "stock". * * @param stock The {@link StockType} that has to be returned. * @return The Item[] used as the specified "stock". null if that "stock" isn't used. */ public Item[] getStock(StockType stock) { switch (stock) { case MAIN: return this.mainStock; case PLAYER: return this.playerStock; } return null; } /** * Whether this Shop accepts the item associated with the provided itemId. * * @param itemId The Id of the item we which to know about. * @return True if the {@link StockType#MAIN} "stock" contains the itemId. True if this Shop has * a {@link StockType#PLAYER} "stock" and it can be sold in there. This means {@link * getBestSlot(int)} > -1 and the {@link Item} associated with the itemId is tradeable. */ public boolean acceptsItem(int itemId) { if (hasStock(StockType.PLAYER)) { // TODO replace getValue() > 0 with a tradable check return (getBestSlot(StockType.PLAYER, itemId) > -1 && ItemDefinitions.forId(itemId).getLowAlchemyValue() > 0); } return false; } /** * Whether or not the specified "stock" in this Shop contains the provided id. * * @param stock The {@link StockType} to specify in which "stock" we check for the id. * @param id The id to check for in the specified "stock". * @return True if this Shop has the id in the specified "stock". */ public boolean contains(StockType stock, int id) { return getItemIndex(stock, id) >= 0; } /** * Gets the {@link Item} at the provided index in the specified stock. * * @param stock The {@link StockType} we go look in for the {@link Item}. * @param index The index of the {@link Item} we want to retrieve. * @return The {@link Item} at the specified index in the specified stock in this Shop. null if * no item resides in the index or if the index is out of boundaries or the stock isn't * in use. */ public Item getItemAtIndex(StockType stock, int index) { if (!hasStock(stock) || index < 0 || index >= getStock(stock).length) { return null; } switch (stock) { case MAIN: return mainStock[index]; case PLAYER: return playerStock[index]; } return null; } /** * Gets the index of the item associated with this id in the specified {@link StockType} in this * Shop. Iterates trough the "stock" associated with the {@link StockType} to find the index of * the id. * * @param stock The {@link StockType} to specify in which "stock" we look. * @param id The id to find in the specified "stock". * @return The index of the id in the "stock" of this Shop. -1 if no match was found. */ public int getItemIndex(StockType stock, int id) { if (!hasStock(stock) || id <= -1) { return -1; } Item[] items = getStock(stock); for (int i = 0; i < items.length; i++) { if (items[i] != null && items[i].getId() == id) { return i; } } return -1; } /** * Gets the best slot for the specified itemId in the "stock". This implies we seek a slot with * the same itemId, else the first empty slot is chosen. * * @param stock The {@link StockType} to find the best slot in. * @param itemId The {@link Item}'s id to find the best slot for. * @return The best slot for the itemId as described above. -1 if no slot was found, itemId * {@literal <}= -1 or !{@link hasStock(StockType)}. */ public int getBestSlot(StockType stock, int itemId) { if (!hasStock(stock) || itemId <= -1) { return -1; } int foundSlot = -1; Item[] itemsInStock = getStock(stock); for (int i = 0; i < itemsInStock.length; i++) { // Slot with same item found if (itemsInStock[i] != null && itemsInStock[i].getId() == itemId) { return i; } // First empty slot found if (foundSlot == -1 && itemsInStock[i] == null) { foundSlot = i; } } return foundSlot; } /** * Removes the specified amount of the {@link Item} with the itemId out of this Shop. If * {@link StockType#MAIN} contains the itemId, the item will be removed from the * {@link StockType#MAIN} "stock", else the {@link StockType#PLAYER} "stock". * * @param itemId The id of the item to remove. * @param amount The amount of the item to remove. * @return The amount of itemsInStock of the itemId removed in this Shop. * @see remove(StockType, int, int) */ public int remove(int itemId, int amount) { if (contains(StockType.MAIN, itemId)) { return remove(StockType.MAIN, itemId, amount); } else { return remove(StockType.PLAYER, itemId, amount); } } /** * Removes the specified amount of the {@link Item} with the itemId out of the specified "stock" * of this shop. * * @param stock The stock of which we remove the item. * @param itemId The id of the item to remove. * @param amount The amount of the item to remove. * @return The amount of itemsInStock of the itemId removed in this Shop. If the itemId isn't in * this Shop or the amount is {@literal <}= 0, 0 is returned. If stock.equals( * {@link StockType#MAIN}) && !{@link isMainFinite()}, 0 is returned as well as when the * stock specified isn't used by this Shop. */ public int remove(StockType stock, int itemId, int amount) { int index = getItemIndex(stock, itemId); if (!hasStock(stock) || index < 0 || amount <= 0) { return 0; } Item[] items = getStock(stock); Item item = items[index]; if (stock == StockType.MAIN) { return amount; } if (amount >= item.getAmount()) { items[index] = null; return item.getAmount(); } items[index] = new Item(itemId, item.getAmount() - amount); return amount; } /** * Add the specified amount of the {@link Item} itemId to this Shop. If {@link StockType#MAIN} * contains the itemId, the item will be added to the {@link StockType#MAIN} "stock", else the * {@link StockType#PLAYER} "stock". * * @param itemId The itemId to add an {@link Item} of. * @param amount The amount of the {@link Item} to add. * @return The amount added to the Shop. * @see add(StockType, int, int) */ public int add(int itemId, int amount) { if (contains(StockType.MAIN, itemId)) { return add(StockType.MAIN, itemId, amount); } else { return add(StockType.PLAYER, itemId, amount); } } /** * Add the specified amount of the {@link Item} itemId to the specified "stock" of this Shop. * * @param stock The {@link StockType} that specifies which stock to add this {@link Item} in. * @param itemId The itemId to add an {@link Item} of. * @param amount The amount of the {@link Item} to add. * @return The amount that was added to this Shop. If this Shop does not have the stock * specified or the amount {@literal <}= 0, 0 is returned. If stock.equals( * {@link StockType#MAIN}) && !{@link isMainFinite()}, 0 is returned. If there is no * slot available to add to in this "stock", 0 is returned as well. If the amount to add * is too high, the amount is scaled down to Integer.MAX_VALUE. */ public int add(StockType stock, int itemId, int amount) { if (!hasStock(stock) || amount <= 0) { return 0; } int index = getBestSlot(stock, itemId); if (index < 0) { return 0; } if (stock == StockType.MAIN) { return amount; } Item[] itemsInStock = getStock(stock); if (itemsInStock[index] != null) { amount -= BasicMath.integerOverflow(amount, itemsInStock[index].getAmount()); itemsInStock[index] = itemsInStock[index].add(new Item(itemId, amount)); } else { itemsInStock[index] = new Item(itemId, amount); } return amount; } }