diff --git a/Server/src/main/content/data/consumables/Consumables.java b/Server/src/main/content/data/consumables/Consumables.java index 551fd1d0e..c70db0b5f 100644 --- a/Server/src/main/content/data/consumables/Consumables.java +++ b/Server/src/main/content/data/consumables/Consumables.java @@ -2,12 +2,12 @@ package content.data.consumables; import content.data.consumables.effects.*; import core.game.consumable.*; -import org.rs09.consts.Items; import core.game.node.entity.player.link.diary.DiaryType; -import core.game.world.update.flag.context.Animation; import core.game.node.entity.skill.Skills; -import content.data.consumables.effects.KegOfBeerEffect; +import core.game.world.update.flag.context.Animation; +import org.rs09.consts.Items; +import java.util.ArrayList; import java.util.HashMap; import static core.tools.TickUtilsKt.minutesToTicks; @@ -377,6 +377,8 @@ public enum Consumables { public static HashMap consumables = new HashMap<>(); + public static ArrayList potions = new ArrayList<>(); + private final Consumable consumable; public boolean isIgnoreMainClock = false; @@ -405,6 +407,11 @@ public enum Consumables { static { for (Consumables consumable : Consumables.values()) { add(consumable); + if (consumable.consumable instanceof Potion) { + for (int pot : consumable.consumable.getIds()) { + potions.add(pot); + } + } } } } diff --git a/Server/src/main/content/global/handlers/npc/DecantListener.kt b/Server/src/main/content/global/handlers/npc/DecantListener.kt index e8d78a71f..9a63c78c2 100644 --- a/Server/src/main/content/global/handlers/npc/DecantListener.kt +++ b/Server/src/main/content/global/handlers/npc/DecantListener.kt @@ -1,18 +1,21 @@ package content.global.handlers.npc -import core.api.* import content.data.consumables.Consumables +import core.api.* import core.game.consumable.Potion -import core.game.node.entity.player.Player -import core.game.node.item.Item import core.game.dialogue.DialogueFile -import core.game.interaction.InteractionListener import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.item.Item import core.tools.END_DIALOGUE -import org.rs09.consts.NPCs +import org.rs09.consts.Items class DecantListener : InteractionListener { + companion object { + val potions = Consumables.potions.toIntArray() + } + override fun defineListeners() { on(IntType.NPC,"decant"){ player, node -> val (toRemove, toAdd) = decantContainer(player.inventory) @@ -23,6 +26,63 @@ class DecantListener : InteractionListener { player.dialogueInterpreter.open(DecantingDialogue(),node.asNpc()) return@on true } + + onUseWith(IntType.ITEM, potions, *potions) { player, used, with -> + if (used !is Item) return@onUseWith false + if (with !is Item) return@onUseWith false + + // Verify these are both the same potion types + val potionUsed = Consumables.getConsumableById(used.id)?.consumable as? Potion ?: return@onUseWith false + val potionWith = Consumables.getConsumableById(with.id)?.consumable as? Potion ?: return@onUseWith false + + if (potionUsed != potionWith) { + return@onUseWith false + } + + // Dosage math + val usedDose = potionUsed.getDose(used) + val withDose = potionWith.getDose(with) + + // Shouldn't be able to combine a 4 dose potion + if (usedDose == 4 || withDose == 4) { + return@onUseWith false + } + + val totalDosage = usedDose + withDose + val fullDoses = totalDosage / 4 + val leftoverDose = totalDosage % 4 + + // Replace the targeted potion item with a (4) dose potion + if (fullDoses != 0) { + replaceSlot(player, with.slot, Item(potionWith.ids.first()), with, Container.INVENTORY) + } + + // Replace the targeted potion item with the updated dosage amount + if (leftoverDose != 0 && fullDoses == 0) { + replaceSlot(player, with.slot, Item(potionUsed.ids[potionUsed.ids.size - totalDosage]), with, Container.INVENTORY) + + // Replace the used with potion item with the updated dosage amount + } else if (leftoverDose != 0) { + replaceSlot(player, used.slot, Item(potionUsed.ids[potionUsed.ids.size - leftoverDose]), used, Container.INVENTORY) + } + + // Replace the used with potion item with an empty vial + if (leftoverDose == 0 || fullDoses == 0) { + replaceSlot(player, used.slot, Item(Items.VIAL_229), used, Container.INVENTORY) + } + + // Send message/Play sound + val amountString = when { + totalDosage >= 4 -> "four" + totalDosage == 3 -> "three" + else -> "two" + } + + sendMessage(player, "You have combined the liquid into $amountString doses.") + playAudio(player, 2401) + + return@onUseWith true + } } internal class DecantingDialogue : DialogueFile(){ @@ -33,5 +93,4 @@ class DecantListener : InteractionListener { } } } - -} +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/firemaking/FireMakingPulse.java b/Server/src/main/content/global/skill/firemaking/FireMakingPulse.java index 0f8ae1a66..50b969efd 100644 --- a/Server/src/main/content/global/skill/firemaking/FireMakingPulse.java +++ b/Server/src/main/content/global/skill/firemaking/FireMakingPulse.java @@ -1,5 +1,6 @@ package content.global.skill.firemaking; +import core.api.Container; import core.game.event.LitFireEvent; import core.game.node.entity.skill.SkillPulse; import core.game.node.entity.skill.Skills; @@ -13,6 +14,10 @@ import core.game.world.GameWorld; import core.game.world.map.RegionManager; import core.game.world.update.flag.context.Animation; import core.tools.RandomFunction; +import org.rs09.consts.Items; + +import static core.api.ContentAPIKt.inInventory; +import static core.api.ContentAPIKt.replaceSlot; /** * Represents the pulse used to light a log. @@ -28,7 +33,8 @@ public final class FireMakingPulse extends SkillPulse { /** * Represents the tinderbox item. */ - private static final Item TINDERBOX = new Item(590); + private static final Item TINDERBOX = new Item(Items.TINDERBOX_590); + /** * Represents the log being burned. @@ -82,7 +88,8 @@ public final class FireMakingPulse extends SkillPulse { } if (player.getAttribute("remove-log", false)) { player.removeAttribute("remove-log"); - if (player.getInventory().remove(node)) { + if (inInventory(player, node.getId(), 1)) { + replaceSlot(player, node.getSlot(), new Item(node.getId(), (node.getAmount() - 1)), node, Container.INVENTORY); GroundItemManager.create(groundItem); } } @@ -139,7 +146,7 @@ public final class FireMakingPulse extends SkillPulse { @Override public void message(int type) { - String name = node.getId() == 3125 ? "bones" : "logs"; + String name = node.getId() == Items.JOGRE_BONES_3125 ? "bones" : "logs"; switch (type) { case 0: player.getPacketDispatch().sendMessage("You attempt to light the " + name + ".."); @@ -171,7 +178,7 @@ public final class FireMakingPulse extends SkillPulse { * @return {@code GroundItem} the itemm. */ public static GroundItem getAsh(final Player player, Log fire, final Scenery object) { - final GroundItem ash = new GroundItem(new Item(592), object.getLocation(), player); + final GroundItem ash = new GroundItem(new Item(Items.ASHES_592), object.getLocation(), player); ash.setDecayTime(fire.getLife() + 200); return ash; } diff --git a/Server/src/main/content/global/skill/herblore/PotionDecantingPlugin.java b/Server/src/main/content/global/skill/herblore/PotionDecantingPlugin.java deleted file mode 100644 index de68de98e..000000000 --- a/Server/src/main/content/global/skill/herblore/PotionDecantingPlugin.java +++ /dev/null @@ -1,259 +0,0 @@ -package content.global.skill.herblore; - -import core.cache.def.impl.ItemDefinition; -import core.game.interaction.NodeUsageEvent; -import core.game.interaction.UseWithHandler; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; -import core.plugin.Initializable; -import core.plugin.Plugin; - -import java.util.HashMap; -import java.util.Map; - -/** - * Represents the plugin used to handle the decanting of potions. - * @author 'Vexia - */ -@Initializable -public final class PotionDecantingPlugin extends UseWithHandler { - - /** - * Represents the empty item vial. - */ - private static final Item EMPTY_VIAL = new Item(229, 1); - - /** - * Represents the array of potions to utilitize. - */ - public static final int[] POTIONS = new int[] { - /* empty vial */229, - /* Strength potion(4) */113, - /* Strength potion(3) */115, - /* Strength potion(2) */117, - /* Strength potion(1) */119, - /* Attack potion(3) */121, - /* Attack potion(2) */123, - /* Attack potion(1) */125, - /* Restore potion(3) */127, - /* Restore potion(2) */129, - /* Restore potion(1) */131, - /* Defence potion(3) */133, - /* Defence potion(2) */135, - /* Defence potion(1) */137, - /* Prayer potion(3) */139, - /* Prayer potion(2) */141, - /* Prayer potion(1) */143, - /* Super attack(3) */145, - /* Super attack(2) */147, - /* Super attack(1) */149, - /* Fishing potion(3) */151, - /* Fishing potion(2) */153, - /* Fishing potion(1) */155, - /* Super strength(3) */157, - /* Super strength(2) */159, - /* Super strength(1) */161, - /* Super defence(3) */163, - /* Super defence(2) */165, - /* Super defence(1) */167, - /* Ranging potion(3) */169, - /* Ranging potion(2) */171, - /* Ranging potion(1) */173, - /* Antipoison(3) */175, - /* Antipoison(2) */177, - /* Antipoison(1) */179, - /* Super antipoison(3) */181, - /* Super antipoison(2) */183, - /* Super antipoison(1) */185, - /* Bravery potion */739, - /* Cadava potion */756, - /* Magic ogre potion */2395, - /* Attack potion(4) */2428, - /* Restore potion(4) */2430, - /* Defence potion(4) */2432, - /* Prayer potion(4) */2434, - /* Super attack(4) */2436, - /* Fishing potion(4) */2438, - /* Super strength(4) */2440, - /* Super defence(4) */2442, - /* Ranging potion(4) */2444, - /* Antipoison(4) */2446, - /* Super antipoison(4) */2448, - /* Antifire potion(4) */2452, - /* Antifire potion(3) */2454, - /* Antifire potion(2) */2456, - /* Antifire potion(1) */2458, - /* Energy potion(4) */3008, - /* Energy potion(3) */3010, - /* Energy potion(2) */3012, - /* Energy potion(1) */3014, - /* Super energy(4) */3016, - /* Super energy(3) */3018, - /* Super energy(2) */3020, - /* Super energy(1) */3022, - /* Super restore(4) */3024, - /* Super restore(3) */3026, - /* Super restore(2) */3028, - /* Super restore(1) */3030, - /* Agility potion(4) */3032, - /* Agility potion(3) */3034, - /* Agility potion(2) */3036, - /* Agility potion(1) */3038, - /* Magic potion(4) */3040, - /* Magic potion(3) */3042, - /* Magic potion(2) */3044, - /* Magic potion(1) */3046, - /* Troll potion */3265, - /* Explosive potion */4045, - /* Super kebab */4608, - /* Strange potion */4836, - /* Antipoison+(4) */5943, - /* Antipoison+(3) */5945, - /* Antipoison+(2) */5947, - /* Antipoison+(1) */5949, - /* Antipoison++(4) */5952, - /* Antipoison++(3) */5954, - /* Antipoison++(2) */5956, - /* Antipoison++(1) */5958, - /* Supercompost */6034, - /* Compost potion(4) */6470, - /* Compost potion(3) */6472, - /* Compost potion(2) */6474, - /* Compost potion(1) */6476, - /* Combat potion(4) */9739, - /* Combat potion(3) */9741, - /* Combat potion(2) */9743, - /* Combat potion(1) */9745, - /* Hunter potion(4) */9998, - /* Hunter potion(3) */10000, - /* Hunter potion(2) */10002, - /* Hunter potion(1) */10004, - /* Antipoison mix(2) */11433, - /* Antipoison mix(1) */11435, - /* Superattack mix(2) */11469, - /* Superattack mix(1) */11471, - /* Antifire mix(2) */11505, - /* Antifire mix(1) */11507, - /* Goblin potion (4) */11809, - /* Goblin potion (3) */11810, - /* Goblin potion (2) */11811, - /* Goblin potion (1) */11812, - /* Summoning potion(4) */12140, - /* Summoning potion(3) */12142, - /* Summoning potion(2) */12144, - /* Summoning potion(1) */12146, - /* Saradomin brews */ 6685, 6687, 6689, 6691, - /* Zamorak brews*/ 2450, 189, 191, 193, - /* Extended antifire potions*/ 14753, 14755, 14757, 14759, - /* Super ranging */ 14776, 14777, 14778, 14779, - /* Super magic */ 14780, 14781, 14782, 14783, - /* Overloads */ 14784, 14785, 14786, 14787, - /*Super Combat Pots */ 14871, 14869, 14867, 14865}; - - /** - * Constructs a new {@code PotionDecantingPlugin} {@code Object}. - */ - public PotionDecantingPlugin() { - super(POTIONS); - } - - @Override - public Plugin newInstance(Object arg) throws Throwable { - for (int i : POTIONS) { - addHandler(i, ITEM_TYPE, this); - } - return this; - } - - @Override - public boolean handle(NodeUsageEvent event) { - final Player player = event.getPlayer(); - final Item item = event.getUsedItem(); - final Item other = event.getBaseItem(); - final String itemName = formatName(item); - final String otherName = formatName(other); - if (!itemName.equals(otherName) && (item.getId() != 229 && other.getId() != 229)) { - return false; - } - final int itemDose = getPotionDose(item); - final int otherDose = getPotionDose(other); - if (flagged(itemDose, otherDose)) { - return false; - } - - if (item.getId() == item.getNoteChange() || other.getId() == item.getNoteChange()) { - return false; - } - - final int[] newDoses = getDoses(itemDose, otherDose); - if (itemDose == 4 && otherDose == 0 || otherDose == 4 && itemDose == 0) { - player.getInventory().replace(getItem(item.getName().contains("Vial") ? otherName : itemName, newDoses[1]), item.getSlot()); - player.getInventory().replace(getItem(item.getName().contains("Vial") ? otherName : itemName, newDoses[1]), other.getSlot()); - player.getPacketDispatch().sendMessage("You decant the potion into two equal parts."); - } else { - player.getInventory().replace(getItem(item.getName().contains("Vial") ? otherName : itemName, newDoses[0]), other.getSlot()); - player.getInventory().replace(getItem(item.getName().contains("Vial") ? otherName : itemName, newDoses[1]), item.getSlot()); - player.getPacketDispatch().sendMessage("You have combined the liquid into " + (newDoses[0] == 0 ? newDoses[1] : newDoses[0]) + " doses."); - } - return true; - } - - /** - * Method used to format a name of a potion. - * @param item the item. - * @return the name. - */ - public static String formatName(final Item item) { - return item.getName().replace("(1)", "").replace("(2)", "").replace("(3)", "").replace("(4)", "").trim(); - } - - /** - * Method used to get the potion does between two potions. - * @param item the item. - * @return the doses. - */ - public static int getPotionDose(final Item item) { - return item.getName().contains("(1)") ? 1 : item.getName().contains("(2)") ? 2 : item.getName().contains("(3)") ? 3 : item.getName().contains("(4)") ? 4 : 0; - } - - /** - * Method used to check if a new potion ca be created based on the doses. - * @param itemDose the item dose. - * @param otherDose the other dose. - * @return True if flagged. - */ - public boolean flagged(final int itemDose, final int otherDose) { - return (itemDose == 4 && otherDose != 0 || itemDose != 4 && otherDose == 0 || itemDose == 4 && otherDose == 3 || itemDose == 0 && otherDose != 4) || (otherDose == 4 && itemDose != 0 || otherDose != 4 && itemDose == 0 || otherDose == 4 && itemDose == 3 || otherDose == 0 && itemDose != 4); - } - - /** - * Method used to return the doses of the new items. - * @param itemDose the item dose. - * @param otherDose the other dose. - * @return the new doses. - */ - public int[] getDoses(final int itemDose, final int otherDose) { - return itemDose == 1 && otherDose == 1 ? new int[] { 2, 0 } : itemDose == 3 && otherDose == 3 ? new int[] { 4, 2 } : itemDose == 2 && otherDose == 2 ? new int[] { 4, 0 } : itemDose == 2 && otherDose == 3 ? new int[] { 4, 1 } : itemDose == 1 && otherDose == 2 ? new int[] { 0, 3 } : itemDose == 4 && otherDose == 0 ? new int[] { 2, 2 } : itemDose == 1 && otherDose == 3 ? new int[] { 0, 4 } : otherDose == 2 && itemDose == 3 ? new int[] { 1, 4 } : otherDose == 1 && itemDose == 2 ? new int[] { 0, 3 } : otherDose == 4 && itemDose == 0 ? new int[] { 2, 2 } : otherDose == 1 && itemDose == 3 ? new int[] { 0, 4 } : itemDose == 0 && otherDose == 4 ? new int[] { 2, 2 } : otherDose == 4 && itemDose == 0 ? new int[] { 2, 2 } : new int[] { 0, 0 }; - } - - /** - * Method used to get the new item. - * @param name the name. - * @param dose the dose of the new item. - * @return the new item. - */ - public static Item getItem(String name, int dose) { - ItemDefinition def = null; - name += "(" + dose + ")"; - if (dose == 0) { - return EMPTY_VIAL; - } - for (int id : POTIONS) { - def = ItemDefinition.forId(id); - if (def.getName().equals(name)) { - return new Item(id); - } - } - return null; - } -} diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index a79d15c38..032b7647b 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -337,12 +337,18 @@ fun replaceSlot(player: Player, slot: Int, item: Item, currentItem: Item? = null Container.BANK -> player.bank } + if (item.id == -1 || item.amount <= 0) { + return cont.replace(null, slot) + } + if (currentItem == null) { return cont.replace(item, slot) } - if (cont.remove(currentItem)) + if (cont.remove(currentItem, slot, true)) { return cont.replace(item, slot) + } + PlayerMonitor.log(player, LogType.DUPE_ALERT, "Potential slot-replacement-based dupe attempt, slot: $slot, item: $item") val other = when (container) { Container.INVENTORY -> Container.EQUIPMENT diff --git a/Server/src/main/core/game/consumable/Consumable.java b/Server/src/main/core/game/consumable/Consumable.java index 0d50dc3cd..f30084740 100644 --- a/Server/src/main/core/game/consumable/Consumable.java +++ b/Server/src/main/core/game/consumable/Consumable.java @@ -50,13 +50,14 @@ public abstract class Consumable { executeConsumptionActions(player); final int nextItemId = getNextItemId(item.getId()); - if(item.getAmount() > 1){ - removeItem(player, item.getId(), Container.INVENTORY); - } else removeItem(player, item, Container.INVENTORY); - - if (nextItemId != -1) { - addItem(player, nextItemId, 1, Container.INVENTORY); + // STACKABLE + NON-RETURN + if (ids.length == 1) { + replaceSlot(player, item.getSlot(), new Item(item.getId(), (item.getAmount() - 1)), item, Container.INVENTORY); + } else { + // ITEM HAS RETURN + replaceSlot(player, item.getSlot(), new Item(nextItemId, 1), item, Container.INVENTORY); } + final int initialLifePoints = player.getSkills().getLifepoints(); Consumables.getConsumableById(item.getId()).getConsumable().effect.activate(player); sendMessages(player, initialLifePoints, item, messages); diff --git a/Server/src/main/core/game/interaction/InteractionListeners.kt b/Server/src/main/core/game/interaction/InteractionListeners.kt index 1527942c1..c28113500 100644 --- a/Server/src/main/core/game/interaction/InteractionListeners.kt +++ b/Server/src/main/core/game/interaction/InteractionListeners.kt @@ -1,7 +1,5 @@ package core.game.interaction -import core.api.forceWalk -import core.api.queueScript import core.game.event.InteractionEvent import core.game.event.UseWithEvent import core.game.node.Node @@ -218,8 +216,8 @@ object InteractionListeners { } else { if(flipped) player.dispatch(UseWithEvent(with.id, used.id)) else player.dispatch(UseWithEvent(used.id, with.id)) - if(flipped) method.invoke(player,with,used) - else method.invoke(player,used,with) + if(flipped) return method.invoke(player,with,used) + else return method.invoke(player,used,with) } return true } diff --git a/Server/src/main/core/game/node/entity/player/link/BankPinManager.java b/Server/src/main/core/game/node/entity/player/link/BankPinManager.java index 96f8e15f7..b4dc61d26 100644 --- a/Server/src/main/core/game/node/entity/player/link/BankPinManager.java +++ b/Server/src/main/core/game/node/entity/player/link/BankPinManager.java @@ -220,36 +220,36 @@ public class BankPinManager { public void handleConfirmInterface(int button) { boolean confirm = button != 91; switch (status) { - case NO_PIN: - if (!confirm) { - toggleConfirmInterface(false); - player.getInterfaceManager().close(); - openSettings("No changes made."); - break; - } - openPin(); - break; - case PENDING: - if (confirm) { - unlock(); - } else { - cancelPin("The PIN has been cancelled", "and will NOT be set.", "", "You still do not have a Bank", "PIN."); - } - break; - case ACTIVE: - if (confirm) { - if (unlocked) { - cancelPin("Your Bank PIN has now been", "deleted.", "", "This means that there is no", "PIN protection on your bank", "account."); - } else { - deleting = true; - openPin(); + case NO_PIN: + if (!confirm) { + toggleConfirmInterface(false); + player.getInterfaceManager().close(); + openSettings("No changes made."); + break; } - } else { - openSettings("No changes made."); - } - break; - default: - break; + openPin(); + break; + case PENDING: + if (confirm) { + unlock(); + } else { + cancelPin("The PIN has been cancelled", "and will NOT be set.", "", "You still do not have a Bank", "PIN."); + } + break; + case ACTIVE: + if (confirm) { + if (unlocked) { + cancelPin("Your Bank PIN has now been", "deleted.", "", "This means that there is no", "PIN protection on your bank", "account."); + } else { + deleting = true; + openPin(); + } + } else { + openSettings("No changes made."); + } + break; + default: + break; } } @@ -348,7 +348,7 @@ public class BankPinManager { return; } else { if (!tempPin.equals(pin)) { - playAudio(player, Sounds.PIN_CANCEL_1042); + playAudio(player, Sounds.PILLORY_WRONG_2277); player.getInterfaceManager().close(); player.sendMessage("The PIN you entered is incorrect."); if (setTries(getTries() + 1) >= 2) { @@ -368,6 +368,7 @@ public class BankPinManager { return; } unlock(); + playAudio(player, Sounds.PILLORY_SUCCESS_2274); player.sendMessage("You have correctly entered your PIN."); } return; @@ -440,8 +441,8 @@ public class BankPinManager { if (stage >= 4) { stage = stage - 4; } - setVarp(player, 562, bitValue); - setVarp(player, 563, digits.get(8) | digits.get(9) << 4 | stage << 26); + setVarp(player, 562, bitValue); + setVarp(player, 563, digits.get(8) | digits.get(9) << 4 | stage << 26); for (int i = 0; i < 9; i++) { int child = (i > 2 ? i + 1 : i) + 11; int positionX = 37 + ((i % 3) * 95) + RandomFunction.random(2, 45); @@ -820,4 +821,4 @@ public class BankPinManager { public long getPendingDelay() { return pendingDelay; } -} +} \ No newline at end of file diff --git a/Server/src/test/kotlin/APITests.kt b/Server/src/test/kotlin/APITests.kt index 0047a74f4..4bd3cb512 100644 --- a/Server/src/test/kotlin/APITests.kt +++ b/Server/src/test/kotlin/APITests.kt @@ -1,12 +1,15 @@ +import content.data.consumables.Consumables import core.api.IfaceSettingsBuilder import core.api.splitLines import content.global.skill.slayer.Master import content.global.skill.slayer.SlayerManager import content.global.skill.slayer.Tasks +import core.game.node.item.Item import org.json.simple.JSONObject import org.json.simple.parser.JSONParser import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test +import org.rs09.consts.Items class APITests { var testPlayer: MockPlayer @@ -178,4 +181,171 @@ class APITests { Assertions.assertEquals(expectedLine2, lines.getOrNull(1) ?: "") Assertions.assertEquals(expectedLine3, lines.getOrNull(2) ?: "") } + + @Test fun consumableStackableItemShouldNotRemoveStack() { + val stackableItem = Item(Items.PURPLE_SWEETS_10476, 999) + TestUtils.getMockPlayer("Inventory Consumable Stack Slot Tester").use { player -> + // Inventory setup + player.inventory.clear() + player.inventory.add(stackableItem, false, 0) + + // Setup + val consumable = Consumables.getConsumableById(stackableItem.id) + consumable.consumable.consume(player.inventory.get(0), player) + TestUtils.advanceTicks(2, false) + + // Get item in that slot, + val updatedConsumable = player.inventory.get(0) + + // Maintains slot clicked + Amount is decremented + Assertions.assertEquals(0, updatedConsumable.slot) + Assertions.assertEquals(998, updatedConsumable.amount) + } + } + + @Test fun consumableMultiPieceItemShouldBeRemovedFromCorrectSlot() { + val consumables: Array = arrayOf( + Item(Items.CAKE_1891, 8), + Item(Items.TWO_THIRDS_CAKE_1893, 8), + Item(Items.SLICE_OF_CAKE_1895, 8) + ) + + TestUtils.getMockPlayer("Inventory Consumable Multi Piece Tester").use { player -> + // Inventory setup + player.inventory.clear() + player.inventory.add(*consumables) + + val lastWholeCakeContainerIndex = 7 + val lastWholeCake = player.inventory.get(lastWholeCakeContainerIndex) + + val consumable = Consumables.getConsumableById(lastWholeCake.id) + consumable.consumable.consume(player.inventory.get(lastWholeCakeContainerIndex), player) + TestUtils.advanceTicks(2, false) + + // Cake amounts are correct + val wholeCakeAmount = player.inventory.getAmount(Items.CAKE_1891) + val twoThirdsCakeAmount = player.inventory.getAmount(Items.TWO_THIRDS_CAKE_1893) + Assertions.assertEquals(7, wholeCakeAmount) + Assertions.assertEquals(9, twoThirdsCakeAmount) + + // Cake was replaced in correct spot + val inventorySlot0 = player.inventory.get(0) + val inventorySlot7 = player.inventory.get(7) + Assertions.assertEquals(Items.CAKE_1891, inventorySlot0.id) + Assertions.assertEquals(Items.TWO_THIRDS_CAKE_1893, inventorySlot7.id) + } + } + + @Test fun consumableMultiPieceItemShouldAddReturnItemToCorrectSlot() { + val PIE_DISH_NONCONSUMABLE_2313 = Items.PIE_DISH_2313 + val consumables: Array = arrayOf( + Item(Items.APPLE_PIE_2323, 8), + Item(Items.HALF_AN_APPLE_PIE_2335, 8), + Item(Items.REDBERRY_PIE_2325, 8) + ) + + TestUtils.getMockPlayer("Inventory Consumable Multi Piece With Return Tester").use { player -> + // Inventory setup + player.inventory.clear() + player.inventory.add(*consumables) + + val lastWholePieContainerIndex = 7 + val lastWholePie = player.inventory.get(lastWholePieContainerIndex) + + val wholePieConsumable = Consumables.getConsumableById(lastWholePie.id) + wholePieConsumable.consumable.consume(player.inventory.get(lastWholePieContainerIndex), player) + TestUtils.advanceTicks(2, false) + + // Pie amounts are correct + var wholePieAmount = player.inventory.getAmount(Items.APPLE_PIE_2323) + var halfPieAmount = player.inventory.getAmount(Items.HALF_AN_APPLE_PIE_2335) + Assertions.assertEquals(7, wholePieAmount) + Assertions.assertEquals(9, halfPieAmount) + + // Pie was replaced in correct spot + val inventorySlot0 = player.inventory.get(0) + val inventorySlot7 = player.inventory.get(7) + Assertions.assertEquals(Items.APPLE_PIE_2323, inventorySlot0.id) + Assertions.assertEquals(Items.HALF_AN_APPLE_PIE_2335, inventorySlot7.id) + + // Tests for pie halves + pie tins + val firstHalfPieContainerIndex = 7 + val firstHalfPie = player.inventory.get(firstHalfPieContainerIndex) + val halfPieConsumable = Consumables.getConsumableById(firstHalfPie.id) + halfPieConsumable.consumable.consume(player.inventory.get(firstHalfPieContainerIndex), player) + TestUtils.advanceTicks(2, false) + + // Pie amounts are correct + halfPieAmount = player.inventory.getAmount(Items.HALF_AN_APPLE_PIE_2335) + val pieDishAmount = player.inventory.getAmount(PIE_DISH_NONCONSUMABLE_2313) + Assertions.assertEquals(8, halfPieAmount) + Assertions.assertEquals(1, pieDishAmount) + + val updatedSlot7 = player.inventory.get(7) + Assertions.assertEquals(PIE_DISH_NONCONSUMABLE_2313, updatedSlot7.id) + } + } + + @Test fun consumableItemShouldNotHaveReturnItem() { + val consumables: Array = arrayOf( + Item(Items.TROUT_333, 8), + Item(Items.SHARK_385, 8), + Item(Items.LOBSTER_379, 8) + ) + TestUtils.getMockPlayer("Inventory Consumable No Return Item Tester").use { player -> + // Inventory setup + player.inventory.clear() + player.inventory.add(*consumables) + + // Feed the player copious amounts of fish + val lastTroutContainerIndex = 7 + val lastTrout = player.inventory.get(lastTroutContainerIndex) + + val troutConsumable = Consumables.getConsumableById(lastTrout.id) + troutConsumable.consumable.consume(player.inventory.get(lastTroutContainerIndex), player) + TestUtils.advanceTicks(4, false) + + val sharkConsumable = Consumables.getConsumableById(Items.SHARK_385) + for (n in 0..7) { + sharkConsumable.consumable.consume(player.inventory.get(n + 8), player) + TestUtils.advanceTicks(4, false) + } + + val lobsterConsumable = Consumables.getConsumableById(Items.LOBSTER_379) + for (n in 16..23 step 2) { + lobsterConsumable.consumable.consume(player.inventory.get(n), player) + TestUtils.advanceTicks(4, false) + } + + // Trout amounts are correct + val troutAmount = player.inventory.getAmount(Items.TROUT_333) + Assertions.assertEquals(7, troutAmount) + + // Trout was removed from the correct spot + val inventorySlot0 = player.inventory.get(0) + val inventorySlot7: Item? = player.inventory.get(7) + Assertions.assertEquals(Items.TROUT_333, inventorySlot0.id) + Assertions.assertNull(inventorySlot7) + + val sharkAmount = player.inventory.getAmount(Items.SHARK_385) + Assertions.assertEquals(0, sharkAmount) + for (n in 8..15) { + val inventoryItem: Item? = player.inventory.get(n) + Assertions.assertNull(inventoryItem) + } + + val lobsterAmount = player.inventory.getAmount(Items.LOBSTER_379) + Assertions.assertEquals(4, lobsterAmount) + + for (n in 16..23) { + if (n % 2 == 0) { + val inventoryItem: Item? = player.inventory.get(n) + Assertions.assertNull(inventoryItem) + } else { + val inventoryItem: Item = player.inventory.get(n) + Assertions.assertEquals(Items.LOBSTER_379, inventoryItem.id) + } + } + } + } } \ No newline at end of file