Refactored equipment.add, fixes bug #420 (blaze it)

This commit is contained in:
Ceikry 2022-05-16 11:54:23 +00:00 committed by Ryan
parent 9ab9885eef
commit 8835799230
2 changed files with 280 additions and 59 deletions

View file

@ -1,5 +1,6 @@
package core.game.container.impl; package core.game.container.impl;
import api.EquipmentSlot;
import core.game.container.Container; import core.game.container.Container;
import core.game.container.ContainerEvent; import core.game.container.ContainerEvent;
import core.game.container.ContainerListener; import core.game.container.ContainerListener;
@ -13,9 +14,14 @@ import core.net.packet.context.ContainerContext;
import core.net.packet.out.ContainerPacket; import core.net.packet.out.ContainerPacket;
import core.net.packet.out.WeightUpdate; import core.net.packet.out.WeightUpdate;
import core.plugin.Plugin; import core.plugin.Plugin;
import org.jetbrains.annotations.Nullable;
import rs09.game.interaction.InteractionListeners;
import rs09.game.node.entity.skill.skillcapeperks.SkillcapePerks; import rs09.game.node.entity.skill.skillcapeperks.SkillcapePerks;
import rs09.game.system.SystemLogger;
import rs09.game.system.config.ItemConfigParser; import rs09.game.system.config.ItemConfigParser;
import java.util.ArrayList;
/** /**
* Represents the equipment container. * Represents the equipment container.
* @author Emperor * @author Emperor
@ -64,87 +70,174 @@ public final class EquipmentContainer extends Container {
/** /**
* Adds an item to the equipment container. * Adds an item to the equipment container.
* @param item The item to add. * @param newItem The item to add.
* @param inventorySlot The inventory slot of the item. * @param inventorySlot The inventory slot of the item.
* @param fire If we should refresh. * @param fire If we should refresh.
* @param fromInventory If the item is being equipped from the inventory. * @param fromInventory If the item is being equipped from the inventory.
* @return {@code True} if succesful, {@code false} if not. * @return {@code True} if succesful, {@code false} if not.
*/ */
public boolean add(Item item, int inventorySlot, boolean fire, boolean fromInventory) { public boolean add(Item newItem, int inventorySlot, boolean fire, boolean fromInventory) {
int slot = item.getDefinition().getConfiguration(ItemConfigParser.EQUIP_SLOT, -1); int equipmentSlot = newItem.getDefinition().getConfiguration(ItemConfigParser.EQUIP_SLOT, -1);
if (slot == -1 && item.getDefinition().getConfiguration(ItemConfigParser.WEAPON_INTERFACE, -1) != -1) { if (!isEquippable(newItem, equipmentSlot)) return false;
slot = SLOT_WEAPON;
} ArrayList<Item> itemsToRemove = new ArrayList<>();
// slot = 3;
if (slot < 0) { Item currentItem = super.get(equipmentSlot);
return false; // Item can't be equipped. if(currentItem != null) itemsToRemove.add(currentItem);
}
if (!item.getDefinition().hasRequirement(player, true, true)) { Item secondaryEquip = getSecondaryEquipIfApplicable(newItem, equipmentSlot);
if(secondaryEquip != null) itemsToRemove.add(secondaryEquip);
if(fromInventory && !player.getInventory().remove(newItem, inventorySlot, true)) {
return false; return false;
} }
Item current = super.get(slot);
if (current != null && current.getId() == item.getId() && current.getDefinition().isStackable()) { if (!itemsToRemove.isEmpty()) {
int amount = getMaximumAdd(item); ArrayList<Item> invalidatedEntries = new ArrayList<>();
if (item.getAmount() > amount) { for(Item current : itemsToRemove) {
amount += current.getAmount(); if(current.getId() == newItem.getId() && current.getDefinition().isStackable()) {
} else { addStackableItemToExistingStack(newItem, fromInventory, equipmentSlot, current);
amount = current.getAmount() + item.getAmount(); invalidatedEntries.add(current);
}
} }
if (fromInventory) { itemsToRemove.removeAll(invalidatedEntries);
player.getInventory().remove(new Item(item.getId(), amount - current.getAmount()));
if(itemsToRemove.isEmpty()) {
return true;
}
boolean successfullyRemovedAll = tryUnequipCurrent(itemsToRemove, newItem);
if(!successfullyRemovedAll) {
if (fromInventory) player.getInventory().add(newItem); //add the item back in case we weren't able to remove the currently equipped item(s)
return false;
} }
replace(new Item(item.getId(), amount), slot);
return true;
} }
if (fromInventory && current != null) {
Plugin<Object> plugin = current.getDefinition().getConfiguration("equipment", null); super.replace(newItem, equipmentSlot, fire);
if (plugin != null) { setWeaponInterfaceWeaponName(newItem);
Object object = plugin.fireEvent("unequip", player, current, item); return true;
if (object != null && !((Boolean) object)) { }
return true;
private boolean isEquippable(Item newItem, int slot) {
if (slot < 0) {
return false;
}
return newItem.getDefinition().hasRequirement(player, true, true);
}
private void setWeaponInterfaceWeaponName(Item newItem) {
if (newItem.getSlot() == SLOT_WEAPON) {
player.getPacketDispatch().sendString(newItem.getName(), 92, 0);
}
}
private boolean tryUnequipCurrent(ArrayList<Item> current, Item newItem) {
if(current.isEmpty()) return true;
int freeSlots = player.getInventory().freeSlots();
int neededSlots = getNeededSlotsToUnequip(current);
boolean hasSpaceForUnequippedItems = freeSlots >= neededSlots;
if(!hasSpaceForUnequippedItems) {
player.getPacketDispatch().sendMessage("Not enough space in your inventory!");
return false;
}
boolean listenersSayWeCanUnequip = runUnequipHooks(current, newItem);
boolean allRemoved = true;
for(Item item : current) {
if(!remove(item)) {
allRemoved = false;
break;
}
}
boolean allAdded = allRemoved;
if(allRemoved) {
for (Item item : current) {
if (!player.getInventory().add(item)) {
allAdded = false;
break;
} }
} }
} }
if (fromInventory && !player.getInventory().remove(item, inventorySlot, true)) {
return false; if (listenersSayWeCanUnequip && allRemoved && allAdded) return true;
} else {
Item secondary = null; //put things back if we couldn't remove everything
if (item.getDefinition().getConfiguration(ItemConfigParser.TWO_HANDED, false)) { for(Item item : current) {
secondary = get(SLOT_SHIELD); if(!containsItem(item)) {
} else if (slot == SLOT_SHIELD) { add(item);
secondary = get(SLOT_WEAPON); }
if (secondary != null && !secondary.getDefinition().getConfiguration(ItemConfigParser.TWO_HANDED, false)) {
secondary = null;
} }
} }
int currentSlot = -1;
if (current != null) { return false;
currentSlot = inventorySlot; }
if (current.getDefinition().isStackable() && player.getInventory().contains(current.getId(), 1)) {
currentSlot = -1; private int getNeededSlotsToUnequip(ArrayList<Item> current) {
int neededSlots = 0;
for(Item item : current) {
if(!item.getDefinition().isStackable()) {
neededSlots++;
} else {
if(player.getInventory().getAmount(item.getId()) == 0) {
neededSlots++;
}
} }
} }
if (current != null && !player.getInventory().add(current, true, inventorySlot)) { return neededSlots;
player.getInventory().add(item); }
player.getPacketDispatch().sendMessage("Not enough space in your inventory!");
return false; @Nullable
private Item getSecondaryEquipIfApplicable(Item newItem, int equipmentSlot) {
Item secondaryEquipItem = null;
if (newItem.getDefinition().getConfiguration(ItemConfigParser.TWO_HANDED, false)) {
secondaryEquipItem = get(SLOT_SHIELD);
} else if (equipmentSlot == SLOT_SHIELD) {
Item inSlot = player.getEquipment().get(SLOT_WEAPON);
if(inSlot != null && inSlot.getDefinition().getConfiguration(ItemConfigParser.TWO_HANDED, false))
secondaryEquipItem = get(SLOT_WEAPON);
} }
if (secondary != null && !player.getInventory().add(secondary)) { return secondaryEquipItem;
if (current != null && currentSlot != -1) { }
player.getInventory().remove(current, currentSlot, false);
private boolean runUnequipHooks(ArrayList<Item> currentItems, Item newItem) {
boolean canContinue = true;
for(Item currentItem : currentItems) {
Plugin<Object> plugin = currentItem.getDefinition().getConfiguration("equipment", null);
if (plugin != null) {
Object object = plugin.fireEvent("unequip", player, currentItem, newItem);
if (object != null && !((Boolean) object)) {
canContinue = false;
break;
}
} }
player.getInventory().add(item);
player.getPacketDispatch().sendMessage("Not enough space in your inventory!"); canContinue = InteractionListeners.run(currentItem.getId(), player, currentItem, false);
return false;
if(!canContinue) break;
} }
super.replace(item, slot, fire);
if (item.getSlot() == SLOT_WEAPON) { return canContinue;
player.getPacketDispatch().sendString(item.getName(), 92, 0); }
private void addStackableItemToExistingStack(Item item, boolean fromInventory, int slot, Item current) {
int amount = getMaximumAdd(item);
if (item.getAmount() > amount) {
amount += current.getAmount();
} else {
amount = current.getAmount() + item.getAmount();
} }
if (secondary != null) { Item transferItem = new Item(current.getId(), amount);
super.remove(secondary); if (fromInventory) {
player.getInventory().remove(transferItem);
} }
return true; replace(transferItem, slot);
} }
/** /**

View file

@ -0,0 +1,128 @@
package content
import TestUtils
import api.EquipmentSlot
import core.game.node.entity.skill.Skills
import core.game.node.item.Item
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.rs09.consts.Items
import rs09.game.content.global.action.EquipHandler
import rs09.game.interaction.InteractionListener
import rs09.game.interaction.InteractionListeners
class EquipTests {
companion object {
init {TestUtils.preTestSetup(); EquipHandler().defineListeners()}
}
@Test fun equipShouldFireEquipListeners() {
var didRun = false
val listener = object : InteractionListener {
override fun defineListeners() {
onEquip(4151) {_,_ -> didRun = true; return@onEquip true}
}
}
listener.defineListeners()
val p = TestUtils.getMockPlayer("bill")
p.inventory.add(Item(4151))
InteractionListeners.run(4151, InteractionListener.ITEM, "equip", p, p.inventory[0])
Assertions.assertEquals(true, didRun)
}
@Test fun unequipShouldFireUnequipListeners() {
var didRun = false
val listener = object : InteractionListener {
override fun defineListeners() {
onUnequip(4151) {_,_ -> didRun = true; return@onUnequip true}
}
}
listener.defineListeners()
val p = TestUtils.getMockPlayer("bill")
p.equipment.replace(Item(4151), EquipmentSlot.WEAPON.ordinal)
EquipHandler.unequip(p, EquipmentSlot.WEAPON.ordinal, 4151)
Assertions.assertEquals(true, didRun)
}
@Test fun equippingItemThatReplacesAnotherItemShouldCallUnequipListenersForTheReplacedItem() {
var didRun = false
val listener = object : InteractionListener {
override fun defineListeners() {
onUnequip(4151) {_,_ -> didRun = true; return@onUnequip true}
}
}
listener.defineListeners()
val p = TestUtils.getMockPlayer("bill")
p.equipment.replace(Item(4151), EquipmentSlot.WEAPON.ordinal)
p.inventory.add(Item(1333))
p.skills.staticLevels[Skills.ATTACK] = 40
InteractionListeners.run(1333, InteractionListener.ITEM, "equip", p, p.inventory[0])
Assertions.assertEquals(true, didRun, p.equipment.toString())
}
@Test fun equippingItemShouldAddUnequippedItemToExistingStackInInventory() {
val p = TestUtils.getMockPlayer("bill")
p.skills.staticLevels[Skills.ATTACK] = 99
p.equipment.replace(Item(Items.BRONZE_DART_806, 1000), EquipmentSlot.WEAPON.ordinal)
p.inventory.add(Item(Items.BRONZE_DART_806, 1000))
p.inventory.add(Item(Items.RUNE_SCIMITAR_1333))
InteractionListeners.run(Items.RUNE_SCIMITAR_1333, InteractionListener.ITEM, "equip", p, p.inventory[1])
Assertions.assertEquals(2000, p.inventory.getAmount(Items.BRONZE_DART_806), "\n" + p.inventory.toString() + "\n" + p.equipment.toString())
}
@Test fun equippingItemThatUnequipsTwoItemsShouldBeAllowedWithOnlyOneInitiallyFreeSlot() {
val p = TestUtils.getMockPlayer("bill")
p.equipment.replace(Item(Items.BRONZE_SWORD_1277), EquipmentSlot.WEAPON.ordinal)
p.equipment.replace(Item(Items.WOODEN_SHIELD_1171), EquipmentSlot.SHIELD.ordinal)
p.inventory.add(Item(Items.CUP_OF_TEA_1978, 26))
p.inventory.add(Item(Items.BRONZE_2H_SWORD_1307))
Assertions.assertEquals(1, p.inventory.freeSlots())
InteractionListeners.run(Items.BRONZE_2H_SWORD_1307, InteractionListener.ITEM, "equip", p, p.inventory[26])
Assertions.assertEquals(0, p.inventory.freeSlots())
Assertions.assertEquals(Items.BRONZE_SWORD_1277, p.inventory[26].id)
Assertions.assertEquals(Items.WOODEN_SHIELD_1171, p.inventory[27].id)
}
@Test fun shouldNotBeAbleToEquipA2HWeaponAndAShieldAtTheSameTime() {
val p = TestUtils.getMockPlayer("bill")
p.inventory.add(Item(Items.BRONZE_2H_SWORD_1307))
p.inventory.add(Item(Items.WOODEN_SHIELD_1171))
InteractionListeners.run(Items.BRONZE_2H_SWORD_1307, InteractionListener.ITEM, "equip", p, p.inventory[0])
Assertions.assertEquals(Items.BRONZE_2H_SWORD_1307, p.equipment.get(EquipmentSlot.WEAPON.ordinal).id)
InteractionListeners.run(Items.WOODEN_SHIELD_1171, InteractionListener.ITEM, "equip", p, p.inventory[1])
Assertions.assertEquals(Items.BRONZE_2H_SWORD_1307, p.inventory[0]?.id ?: -1)
}
@Test fun equippingShieldShouldNotUnequipOneHandedWeapon() {
val p = TestUtils.getMockPlayer("bill")
p.inventory.add(Item(Items.BRONZE_SWORD_1277))
p.inventory.add(Item(Items.WOODEN_SHIELD_1171))
InteractionListeners.run(Items.BRONZE_SWORD_1277, InteractionListener.ITEM, "equip", p, p.inventory[0])
Assertions.assertEquals(Items.BRONZE_SWORD_1277, p.equipment[EquipmentSlot.WEAPON.ordinal].id)
InteractionListeners.run(Items.WOODEN_SHIELD_1171, InteractionListener.ITEM, "equip", p, p.inventory[1])
Assertions.assertEquals(Items.BRONZE_SWORD_1277, p.equipment[EquipmentSlot.WEAPON.ordinal]?.id ?: -1)
}
@Test fun equippingStackableItemShouldAddToExistingStackInEquipmentIfApplicable() {
val p = TestUtils.getMockPlayer("bill")
p.equipment.replace(Item(Items.BRONZE_ARROW_882, 100), EquipmentSlot.AMMO.ordinal)
p.inventory.add(Item(Items.BRONZE_ARROW_882, 200))
InteractionListeners.run(Items.BRONZE_ARROW_882, InteractionListener.ITEM, "equip", p, p.inventory[0])
Assertions.assertEquals(300, p.equipment[EquipmentSlot.AMMO.ordinal].amount)
}
}