feat(tools): replace broken main-hand tools
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
package games.dmg.spigotinventoryhelper;
|
||||
|
||||
import java.util.Objects;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerItemBreakEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public final class BrokenToolListener implements Listener {
|
||||
private final ToolReplacementService replacements;
|
||||
private final TaskScheduler scheduler;
|
||||
|
||||
public BrokenToolListener(Plugin plugin, ToolReplacementService replacements) {
|
||||
this(replacements, task -> plugin.getServer().getScheduler().runTask(plugin, task));
|
||||
}
|
||||
|
||||
BrokenToolListener(ToolReplacementService replacements, TaskScheduler scheduler) {
|
||||
this.replacements = Objects.requireNonNull(replacements, "replacements");
|
||||
this.scheduler = Objects.requireNonNull(scheduler, "scheduler");
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onItemBreak(PlayerItemBreakEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
PlayerInventory inventory = player.getInventory();
|
||||
ItemStack broken = event.getBrokenItem();
|
||||
if (inventory.getItemInMainHand() != broken) {
|
||||
return;
|
||||
}
|
||||
|
||||
Material material = broken.getType();
|
||||
int selectedSlot = inventory.getHeldItemSlot();
|
||||
scheduler.schedule(() -> {
|
||||
if (replacements.replace(inventory, material, selectedSlot)) {
|
||||
player.sendActionBar(Component.text(
|
||||
"Equipped another " + readableName(material) + ".",
|
||||
NamedTextColor.GREEN
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static String readableName(Material material) {
|
||||
return material.name().toLowerCase(java.util.Locale.ROOT).replace('_', ' ');
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface TaskScheduler {
|
||||
void schedule(Runnable task);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,10 @@ public final class SpigotInventoryHelperPlugin extends JavaPlugin {
|
||||
new SortingStickListener(sortingSticks, sorting),
|
||||
this
|
||||
);
|
||||
getServer().getPluginManager().registerEvents(
|
||||
new BrokenToolListener(this, new ToolReplacementService()),
|
||||
this
|
||||
);
|
||||
getLogger().info("Spigot Inventory Helper enabled.");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package games.dmg.spigotinventoryhelper;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
|
||||
public final class ToolReplacementService {
|
||||
private static final int HOTBAR_SIZE = 9;
|
||||
private static final int PLAYER_STORAGE_SIZE = 36;
|
||||
|
||||
public boolean replace(
|
||||
PlayerInventory inventory,
|
||||
Material brokenMaterial,
|
||||
int selectedSlot
|
||||
) {
|
||||
Objects.requireNonNull(inventory, "inventory");
|
||||
Objects.requireNonNull(brokenMaterial, "brokenMaterial");
|
||||
if (selectedSlot < 0 || selectedSlot >= HOTBAR_SIZE || !isEmpty(inventory.getItem(selectedSlot))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemStack[] storage = inventory.getStorageContents();
|
||||
int source = find(storage, brokenMaterial, HOTBAR_SIZE, PLAYER_STORAGE_SIZE, selectedSlot);
|
||||
if (source < 0) {
|
||||
source = find(storage, brokenMaterial, 0, HOTBAR_SIZE, selectedSlot);
|
||||
}
|
||||
if (source < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemStack replacement = storage[source];
|
||||
inventory.setItem(selectedSlot, replacement);
|
||||
inventory.setItem(source, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int find(
|
||||
ItemStack[] storage,
|
||||
Material material,
|
||||
int from,
|
||||
int to,
|
||||
int excludedSlot
|
||||
) {
|
||||
int limit = Math.min(to, storage.length);
|
||||
for (int slot = from; slot < limit; slot++) {
|
||||
ItemStack candidate = storage[slot];
|
||||
if (slot != excludedSlot
|
||||
&& candidate != null
|
||||
&& candidate.getAmount() > 0
|
||||
&& candidate.getType() == material) {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static boolean isEmpty(ItemStack item) {
|
||||
return item == null || item.getType() == Material.AIR || item.getAmount() == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package games.dmg.spigotinventoryhelper;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerItemBreakEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class BrokenToolListenerTest {
|
||||
@Test
|
||||
void replacesAMainHandToolOnTheNextServerTick() {
|
||||
ToolReplacementService replacements = mock(ToolReplacementService.class);
|
||||
AtomicReference<Runnable> scheduled = new AtomicReference<>();
|
||||
BrokenToolListener listener = new BrokenToolListener(replacements, scheduled::set);
|
||||
PlayerItemBreakEvent event = mock(PlayerItemBreakEvent.class);
|
||||
Player player = mock(Player.class);
|
||||
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||
ItemStack broken = mock(ItemStack.class);
|
||||
when(event.getPlayer()).thenReturn(player);
|
||||
when(event.getBrokenItem()).thenReturn(broken);
|
||||
when(player.getInventory()).thenReturn(inventory);
|
||||
when(inventory.getItemInMainHand()).thenReturn(broken);
|
||||
when(inventory.getHeldItemSlot()).thenReturn(3);
|
||||
when(broken.getType()).thenReturn(Material.DIAMOND_PICKAXE);
|
||||
|
||||
listener.onItemBreak(event);
|
||||
scheduled.get().run();
|
||||
|
||||
verify(replacements).replace(inventory, Material.DIAMOND_PICKAXE, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoresAnOffHandToolBreak() {
|
||||
ToolReplacementService replacements = mock(ToolReplacementService.class);
|
||||
AtomicReference<Runnable> scheduled = new AtomicReference<>();
|
||||
BrokenToolListener listener = new BrokenToolListener(replacements, scheduled::set);
|
||||
PlayerItemBreakEvent event = mock(PlayerItemBreakEvent.class);
|
||||
Player player = mock(Player.class);
|
||||
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||
ItemStack mainHand = mock(ItemStack.class);
|
||||
ItemStack brokenOffHand = mock(ItemStack.class);
|
||||
when(event.getPlayer()).thenReturn(player);
|
||||
when(event.getBrokenItem()).thenReturn(brokenOffHand);
|
||||
when(player.getInventory()).thenReturn(inventory);
|
||||
when(inventory.getItemInMainHand()).thenReturn(mainHand);
|
||||
|
||||
listener.onItemBreak(event);
|
||||
|
||||
verifyNoInteractions(replacements);
|
||||
org.junit.jupiter.api.Assertions.assertNull(scheduled.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package games.dmg.spigotinventoryhelper;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class ToolReplacementServiceTest {
|
||||
@Test
|
||||
void movesAnExactMainStorageReplacementIntoTheBrokenSlot() {
|
||||
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||
ItemStack[] storage = new ItemStack[36];
|
||||
ItemStack hotbarCandidate = tool(Material.DIAMOND_PICKAXE);
|
||||
ItemStack mainCandidate = tool(Material.DIAMOND_PICKAXE);
|
||||
storage[1] = hotbarCandidate;
|
||||
storage[10] = mainCandidate;
|
||||
when(inventory.getStorageContents()).thenReturn(storage);
|
||||
|
||||
boolean replaced = new ToolReplacementService().replace(
|
||||
inventory,
|
||||
Material.DIAMOND_PICKAXE,
|
||||
0
|
||||
);
|
||||
|
||||
assertTrue(replaced);
|
||||
verify(inventory).setItem(0, mainCandidate);
|
||||
verify(inventory).setItem(10, null);
|
||||
verify(inventory, never()).setItem(1, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fallsBackToAnotherHotbarSlot() {
|
||||
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||
ItemStack[] storage = new ItemStack[36];
|
||||
ItemStack replacement = tool(Material.IRON_AXE);
|
||||
storage[7] = replacement;
|
||||
when(inventory.getStorageContents()).thenReturn(storage);
|
||||
|
||||
assertTrue(new ToolReplacementService().replace(
|
||||
inventory,
|
||||
Material.IRON_AXE,
|
||||
2
|
||||
));
|
||||
verify(inventory).setItem(2, replacement);
|
||||
verify(inventory).setItem(7, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void leavesInventoryUnchangedWithoutAnExactMaterialMatch() {
|
||||
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||
ItemStack[] storage = new ItemStack[36];
|
||||
storage[10] = tool(Material.IRON_PICKAXE);
|
||||
when(inventory.getStorageContents()).thenReturn(storage);
|
||||
|
||||
assertFalse(new ToolReplacementService().replace(
|
||||
inventory,
|
||||
Material.DIAMOND_PICKAXE,
|
||||
0
|
||||
));
|
||||
verify(inventory, never()).setItem(0, storage[10]);
|
||||
verify(inventory, never()).setItem(10, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotOverwriteAnItemPlacedIntoTheBrokenSlot() {
|
||||
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||
ItemStack occupied = tool(Material.COBBLESTONE);
|
||||
when(inventory.getItem(0)).thenReturn(occupied);
|
||||
|
||||
assertFalse(new ToolReplacementService().replace(
|
||||
inventory,
|
||||
Material.DIAMOND_PICKAXE,
|
||||
0
|
||||
));
|
||||
verify(inventory, never()).getStorageContents();
|
||||
}
|
||||
|
||||
private static ItemStack tool(Material material) {
|
||||
ItemStack item = mock(ItemStack.class);
|
||||
when(item.getType()).thenReturn(material);
|
||||
when(item.getAmount()).thenReturn(1);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user