diff --git a/minecord-bot/src/main/java/com/tisawesomeness/minecord/mc/item/Container.java b/minecord-bot/src/main/java/com/tisawesomeness/minecord/mc/item/Container.java new file mode 100644 index 00000000..b33ce227 --- /dev/null +++ b/minecord-bot/src/main/java/com/tisawesomeness/minecord/mc/item/Container.java @@ -0,0 +1,19 @@ +package com.tisawesomeness.minecord.mc.item; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * A container that can hold one or more stacks of Minecraft items. + */ +@Getter +@RequiredArgsConstructor +public enum Container { + STACK(1), + CHEST(27), + DOUBLE_CHEST(2 * 27), + CHEST_SHULKER(27 * 27), + DOUBLE_CHEST_SHULKER(2 * 27 * 27); + + private final int slots; +} diff --git a/minecord-bot/src/main/java/com/tisawesomeness/minecord/mc/item/ItemCount.java b/minecord-bot/src/main/java/com/tisawesomeness/minecord/mc/item/ItemCount.java new file mode 100644 index 00000000..19e612f3 --- /dev/null +++ b/minecord-bot/src/main/java/com/tisawesomeness/minecord/mc/item/ItemCount.java @@ -0,0 +1,102 @@ +package com.tisawesomeness.minecord.mc.item; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * A specific number of Minecraft items. Item count may be negative. + */ +public class ItemCount { + + private final long itemCount; + @Getter private final int stackSize; + + /** + * Creates a new item count. + * @param itemCount number of starting items + * @param stackSize stack size of the item + * @throws IllegalArgumentException if the stack size is not within 1-64 + */ + public ItemCount(long itemCount, int stackSize) { + this.itemCount = itemCount; + if (stackSize < 1 || 64 < stackSize) { + throw new IllegalArgumentException("stackSize must be between 1 and 64 but was " + stackSize); + } + this.stackSize = stackSize; + + } + + /** + * Creates a new item count with the added items and same stack size. + * @param items number of items to add + * @return new item count + */ + public ItemCount addItems(long items) { + return new ItemCount(itemCount + items, stackSize); + } + /** + * Creates a new item count with the added stacks and same stack size. + * @param stacks number of stacks to add + * @return new item count + */ + public ItemCount addStacks(long stacks) { + return addItems(stackSize * stacks); + } + /** + * Creates a new item count, adding the given number of containers, keeping the same stack size. + * @param container container holding the items + * @param count number of containers to add + * @return new item count + */ + public ItemCount addContainers(Container container, long count) { + return addStacks(container.getSlots() * count); + } + + /** + * @return item count + */ + public long getCount() { + return itemCount; + } + + /** + * Gets the exact number of containers needed to hold this item count. + * @param container container holding the items + * @return number of containers, possibly fractional + */ + public double getExact(Container container) { + return (double) itemCount / (stackSize * container.getSlots()); + } + + /** + * Computes a combination of containers that holds this item count. + * Containers with higher capacities are prioritized. + *
Example: 1863 items is equal to 1 chest, 2 stacks, 7 items. + * @param containers containers allowed to be used + * @return list of length {@code containers.size() + 1}, each element is the amount of containers necessary in + * descending order, and one extra element at the end for leftover items (above example would return + * {@code [1, 2, 7]}) + */ + public List distribute(Collection containers) { + long stacks = itemCount / stackSize; + long items = itemCount % stackSize; + + List list = new ArrayList<>(); + for (int i = Container.values().length - 1; i >= 0; i--) { + Container container = Container.values()[i]; + if (containers.contains(container)) { + + list.add(stacks / container.getSlots()); + stacks %= container.getSlots(); + + } + } + + list.add(items); + return list; + } + +} diff --git a/minecord-bot/src/test/java/com/tisawesomeness/minecord/mc/item/ItemCountTest.java b/minecord-bot/src/test/java/com/tisawesomeness/minecord/mc/item/ItemCountTest.java new file mode 100644 index 00000000..cf742ef0 --- /dev/null +++ b/minecord-bot/src/test/java/com/tisawesomeness/minecord/mc/item/ItemCountTest.java @@ -0,0 +1,116 @@ +package com.tisawesomeness.minecord.mc.item; + +import com.tisawesomeness.minecord.util.Mth; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.EnumSet; +import java.util.Set; + +import static org.assertj.core.api.Assertions.*; + +public class ItemCountTest { + + @ParameterizedTest + @ValueSource(longs = {0, 1, 5, 65}) + public void testNew(long count) { + assertThat(new ItemCount(count, 64).getCount()) + .isEqualTo(count); + } + @ParameterizedTest + @ValueSource(ints = {0, 65}) + public void testNewInvalid(int stackSize) { + assertThatThrownBy(() -> new ItemCount(0, stackSize)) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @ValueSource(longs = {-1, 0, 1, 65}) + public void testAddItems(long count) { + assertThat(new ItemCount(5, 64).addItems(count).getCount()) + .isEqualTo(5 + count); + } + @ParameterizedTest + @CsvSource({ + "0, 64, 1, 64", + "2, 1, 5, 7", + "37, 16, 5, 117", + "15, 64, -2, -113" + }) + public void testAddStacks(long initialCount, int stackSize, long stacks, long expectedCount) { + assertThat(new ItemCount(initialCount, stackSize).addStacks(stacks).getCount()) + .isEqualTo(expectedCount); + } + @ParameterizedTest + @CsvSource({ + "0, 64, STACK, 1, 64", + "0, 64, CHEST, -2, -3456", + "7, 16, DOUBLE_CHEST, 5, 4327", + "-165, 64, CHEST_SHULKER, 1, 46491", + "9164, 64, DOUBLE_CHEST_SHULKER, 3, 289100" + }) + public void testAddContainers(long initialCount, int stackSize, Container container, long count, long expectedCount) { + assertThat(new ItemCount(initialCount, stackSize).addContainers(container, count).getCount()) + .isEqualTo(expectedCount); + } + + @ParameterizedTest + @CsvSource({ + "0, 64, STACK, 0.0", + "32, 64, STACK, 0.5", + "63, 64, STACK, 0.984375", + "64, 64, STACK, 1.0", + "128, 64, STACK, 2.0", + "-63, 64, STACK, -0.984375", + "2, 16, STACK, 0.125", + "83, 1, STACK, 83.0", + "27000, 64, CHEST, 15.625", + "27000, 64, DOUBLE_CHEST, 7.8125", + "139968, 64, CHEST_SHULKER, 3.0", + "93312, 64, DOUBLE_CHEST_SHULKER, 1.0" + }) + public void testExact(long count, int stackSize, Container container, double expected) { + assertThat(new ItemCount(count, stackSize).getExact(container)) + .isCloseTo(expected, within(Mth.EPSILON)); + } + + @ParameterizedTest + @CsvSource({ + "0, 64, 0, 0", + "2, 64, 0, 2", + "2, 1, 2, 0", + "69, 64, 1, 5", + "1863, 64, 29, 7", + "-1863, 64, -29, -7" + }) + public void testDistributeStack(long count, int stackSize, long stacks, long items) { + assertThat(new ItemCount(count, stackSize).distribute(EnumSet.of(Container.STACK))) + .containsExactly(stacks, items); + } + @ParameterizedTest + @CsvSource({ + "0, 64, 0, 0, 0", + "27, 1, 1, 0, 0", + "1863, 64, 1, 2, 7", + "-1863, 64, -1, -2, -7" + }) + public void testDistributeChest(long count, int stackSize, long chests, long stacks, long items) { + assertThat(new ItemCount(count, stackSize).distribute(EnumSet.of(Container.STACK, Container.CHEST))) + .containsExactly(chests, stacks, items); + } + @ParameterizedTest + @CsvSource({ + "0, 64, 0, 0, 0, 0, 0, 0", + "99999, 64, 1, 0, 1, 1, 23, 31", + "-99999, 64, -1, 0, -1, -1, -23, -31" + }) + public void testDistributeAll(long count, int stackSize, long doubleChestShulkers, long chestShulkers, + long doubleChests, long chests, long stacks, long items) { + Set containers = EnumSet.of(Container.STACK, Container.CHEST, Container.DOUBLE_CHEST, + Container.CHEST_SHULKER, Container.DOUBLE_CHEST_SHULKER); + assertThat(new ItemCount(count, stackSize).distribute(containers)) + .containsExactly(doubleChestShulkers, chestShulkers, doubleChests, chests, stacks, items); + } + +}