diff --git a/changelog.md b/changelog.md index fa79747..c8c89e6 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,9 @@ These changes will (most likely) be included in the next version. ## [Unreleased] +### Added +- Support for chest references in item syntax. The new `inv` syntax allows for referencing container indices in the config-file. This should help bridge the gap between class chests and various other parts of the config-file, such as rewards and upgrade waves. + ### Fixed - Explosion damage caused by Exploding Sheep now correctly counts as monster damage. This means that the explosions only affect other mobs if the per-arena setting `monster-infight` is set to `true`. - Explosion damage caused by the boss ability `obsidian-bomb` now correctly counts as monster damage. This means that the explosions only affect other mobs if the per-arena setting `monster-infight` is set to `true`. diff --git a/src/main/java/com/garbagemule/MobArena/things/InventoryGroupThing.java b/src/main/java/com/garbagemule/MobArena/things/InventoryGroupThing.java new file mode 100644 index 0000000..6e2c5a7 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/things/InventoryGroupThing.java @@ -0,0 +1,42 @@ +package com.garbagemule.MobArena.things; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +class InventoryGroupThing extends InventoryThing { + + InventoryGroupThing(Supplier location) { + super(location); + } + + @Override + Thing load() { + Inventory inventory = super.getInventory(); + if (inventory == null) { + return null; + } + + List things = new ArrayList<>(); + for (ItemStack stack : inventory) { + if (stack != null && stack.getType() != Material.AIR) { + things.add(new ItemStackThing(stack)); + } + } + + if (things.isEmpty()) { + return null; + } + if (things.size() == 1) { + return things.get(0); + } + + return new ThingGroup(things); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/things/InventoryIndexThing.java b/src/main/java/com/garbagemule/MobArena/things/InventoryIndexThing.java new file mode 100644 index 0000000..37cff6c --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/things/InventoryIndexThing.java @@ -0,0 +1,40 @@ +package com.garbagemule.MobArena.things; + +import org.bukkit.Location; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.function.Supplier; + +class InventoryIndexThing extends InventoryThing { + + private final int index; + + InventoryIndexThing( + Supplier location, + int index + ) { + super(location); + this.index = index; + } + + @Override + Thing load() { + Inventory inventory = super.getInventory(); + if (inventory == null) { + return null; + } + if (inventory.getSize() <= index) { + return null; + } + + ItemStack stack = inventory.getItem(index); + if (stack == null) { + return null; + } + + ItemStack clone = stack.clone(); + return new ItemStackThing(clone); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/things/InventoryRangeThing.java b/src/main/java/com/garbagemule/MobArena/things/InventoryRangeThing.java new file mode 100644 index 0000000..fa95d15 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/things/InventoryRangeThing.java @@ -0,0 +1,56 @@ +package com.garbagemule.MobArena.things; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +class InventoryRangeThing extends InventoryThing { + + private final int first; + private final int last; + + InventoryRangeThing( + Supplier location, + int first, + int last + ) { + super(location); + this.first = first; + this.last = last; + } + + @Override + Thing load() { + Inventory inventory = super.getInventory(); + if (inventory == null) { + return null; + } + if (inventory.getSize() <= last) { + return null; + } + + List things = new ArrayList<>(); + ItemStack[] content = inventory.getContents(); + for (int i = first; i <= last; i++) { + ItemStack stack = content[i]; + if (stack != null && stack.getType() != Material.AIR) { + things.add(new ItemStackThing(stack)); + } + } + + if (things.isEmpty()) { + return null; + } + if (things.size() == 1) { + return things.get(0); + } + + return new ThingGroup(things); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/things/InventoryThing.java b/src/main/java/com/garbagemule/MobArena/things/InventoryThing.java new file mode 100644 index 0000000..d032194 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/things/InventoryThing.java @@ -0,0 +1,68 @@ +package com.garbagemule.MobArena.things; + +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +import java.util.function.Supplier; + +abstract class InventoryThing implements Thing { + + private final Supplier location; + + protected InventoryThing(Supplier location) { + this.location = location; + } + + @Override + public boolean giveTo(Player player) { + Thing thing = load(); + if (thing == null) { + return false; + } + return thing.giveTo(player); + } + + @Override + public boolean takeFrom(Player player) { + Thing thing = load(); + if (thing == null) { + return false; + } + return thing.takeFrom(player); + } + + @Override + public boolean heldBy(Player player) { + Thing thing = load(); + if (thing == null) { + return false; + } + return thing.heldBy(player); + } + + abstract Thing load(); + + Inventory getInventory() { + Location location = this.location.get(); + if (location != null) { + Block block = location.getBlock(); + BlockState state = block.getState(); + if (state instanceof InventoryHolder) { + InventoryHolder holder = (InventoryHolder) state; + return holder.getInventory(); + } + } + return null; + } + + @Override + public String toString() { + Thing thing = load(); + return (thing != null) ? thing.toString() : "nothing"; + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/things/InventoryThingParser.java b/src/main/java/com/garbagemule/MobArena/things/InventoryThingParser.java new file mode 100644 index 0000000..14e361b --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/things/InventoryThingParser.java @@ -0,0 +1,80 @@ +package com.garbagemule.MobArena.things; + +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.World; + +import java.util.function.Supplier; + +public class InventoryThingParser implements ThingParser { + + private static final String PREFIX = "inv("; + private static final String SUFFIX = ")"; + + private final Server server; + + InventoryThingParser(Server server) { + this.server = server; + } + + @Override + public InventoryThing parse(String s) { + if (!s.startsWith(PREFIX) || !s.endsWith(SUFFIX)) { + return null; + } + + // Trim prefix and suffix + int start = PREFIX.length(); + int end = s.length() - SUFFIX.length(); + String inner = s.substring(start, end); + + // Split by whitespace to get all the parts + String[] parts = inner.split("\\s+"); + if (parts.length != 5) { + throw new IllegalArgumentException("Expected format " + PREFIX + "world x y z slot" + SUFFIX + ", got: " + s); + } + + // Extract location + String name = parts[0]; + int x = Integer.parseInt(parts[1]); + int y = Integer.parseInt(parts[2]); + int z = Integer.parseInt(parts[3]); + Supplier location = () -> { + World world = server.getWorld(name); + return new Location(world, x, y ,z); + }; + + // Determine type by slot value + String slot = parts[4]; + if (slot.equals("all")) { + return group(location); + } + if (slot.contains("-")) { + return range(location, slot); + } + return index(location, slot); + } + + private InventoryThing group(Supplier location) { + return new InventoryGroupThing(location); + } + + private InventoryThing range(Supplier location, String slot) { + String[] indices = slot.split("-"); + if (indices.length != 2) { + throw new IllegalArgumentException("Expected range format (e.g. 0-8), got: " + slot); + } + int first = Integer.parseInt(indices[0]); + int last = Integer.parseInt(indices[1]); + if (last < first) { + throw new IllegalArgumentException("Range end is less than range start: " + slot); + } + return new InventoryRangeThing(location, first, last); + } + + private InventoryThing index(Supplier location, String slot) { + int index = Integer.parseInt(slot); + return new InventoryIndexThing(location, index); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/things/ThingManager.java b/src/main/java/com/garbagemule/MobArena/things/ThingManager.java index 994fdee..3843c68 100644 --- a/src/main/java/com/garbagemule/MobArena/things/ThingManager.java +++ b/src/main/java/com/garbagemule/MobArena/things/ThingManager.java @@ -16,6 +16,7 @@ public class ThingManager implements ThingParser { parsers.add(new MoneyThingParser(plugin)); parsers.add(new PermissionThingParser(plugin)); parsers.add(new PotionEffectThingParser()); + parsers.add(new InventoryThingParser(plugin.getServer())); items = parser; } diff --git a/src/test/java/com/garbagemule/MobArena/things/InventoryThingParserTest.java b/src/test/java/com/garbagemule/MobArena/things/InventoryThingParserTest.java new file mode 100644 index 0000000..b5b42f5 --- /dev/null +++ b/src/test/java/com/garbagemule/MobArena/things/InventoryThingParserTest.java @@ -0,0 +1,124 @@ +package com.garbagemule.MobArena.things; + +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.*; +import static org.junit.Assert.assertThrows; + +public class InventoryThingParserTest { + + private InventoryThingParser subject; + + @Before + public void setup() { + subject = new InventoryThingParser(null); + } + + @Test + public void returnsNullOnWrongType() { + String input = "dirt"; + + Thing result = subject.parse(input); + + assertThat(result, nullValue()); + } + + @Test + public void returnsNullOnMismatchedParenthesis() { + String input = "inv(world -10 20 130 15"; + + Thing result = subject.parse(input); + + assertThat(result, nullValue()); + } + + @Test + public void throwsOnTooFewArguments() { + String input = "inv(world -10 20 130)"; + + assertThrows( + IllegalArgumentException.class, + () -> subject.parse(input) + ); + } + + @Test + public void throwsOnTooManyArguments() { + String input = "inv(world -10 20 130 15 16)"; + + assertThrows( + IllegalArgumentException.class, + () -> subject.parse(input) + ); + } + + @Test + public void throwsOnUnknownSlotType() { + String input = "inv(world -10 20 130 potato)"; + + assertThrows( + IllegalArgumentException.class, + () -> subject.parse(input) + ); + } + + @Test + public void parsesIndexThing() { + String input = "inv(world -10 20 130 15)"; + + Thing result = subject.parse(input); + + assertThat(result, instanceOf(InventoryIndexThing.class)); + } + + @Test + public void throwsIfIndexIsNegative() { + String input = "inv(world -10 20 130 -1)"; + + assertThrows( + IllegalArgumentException.class, + () -> subject.parse(input) + ); + } + + @Test + public void parsesRangeThing() { + String input = "inv(world -10 20 130 0-8)"; + + Thing result = subject.parse(input); + + assertThat(result, instanceOf(InventoryRangeThing.class)); + } + + @Test + public void throwsIfRangeStartIsLessThanZero() { + String input = "inv(world -10 20 130 -1-8)"; + + assertThrows( + IllegalArgumentException.class, + () -> subject.parse(input) + ); + } + + @Test + public void throwsIfRangeEndIsLessThanRangeStart() { + String input = "inv(world -10 20 130 5-2)"; + + assertThrows( + IllegalArgumentException.class, + () -> subject.parse(input) + ); + } + + @Test + public void parsesAllGroup() { + String input = "inv(world -10 20 130 all)"; + + Thing result = subject.parse(input); + + assertThat(result, instanceOf(InventoryGroupThing.class)); + } + +}