capability, IItemHandler instance, Direction side, Tag base) {
+ if (!(instance instanceof IItemHandlerModifiable)) {
+ throw new RuntimeException("IItemHandler instance does not implement IItemHandlerModifiable");
+ }
+
+ IItemHandlerModifiable itemHandlerModifiable = (IItemHandlerModifiable) instance;
+ ListTag tagList = (ListTag) base;
+
+ for (int i = 0; i < tagList.size(); i++) {
+ CompoundTag itemTags = tagList.getCompound(i);
+ int j = itemTags.getInt("Slot");
+
+ if (j >= 0 && j < instance.getSlots()) {
+ itemHandlerModifiable.setStackInSlot(j, ItemStack.fromTag(itemTags));
+ }
+ }
+ }
+ }, ItemStackHandler::new);
+ }
+}
diff --git a/patchwork-items/src/main/java/net/minecraftforge/items/IItemHandler.java b/patchwork-items/src/main/java/net/minecraftforge/items/IItemHandler.java
new file mode 100644
index 00000000..23851bb9
--- /dev/null
+++ b/patchwork-items/src/main/java/net/minecraftforge/items/IItemHandler.java
@@ -0,0 +1,112 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.minecraftforge.items;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.inventory.Inventory;
+import net.minecraft.item.ItemStack;
+
+public interface IItemHandler {
+ /**
+ * Returns the number of slots available.
+ *
+ * @return The number of slots available
+ */
+ int getSlots();
+
+ /**
+ * Returns the ItemStack in a given slot.
+ *
+ * The result's stack size may be greater than the itemstack's max size.
+ *
+ *
If the result is empty, then the slot is empty.
+ *
+ *
IMPORTANT: This ItemStack MUST NOT be modified. This method is not for
+ * altering an inventory's contents. Any implementers who are able to detect
+ * modification through this method should throw an exception.
+ *
+ *
SERIOUSLY: DO NOT MODIFY THE RETURNED ITEMSTACK
+ *
+ * @param slot Slot to query
+ * @return ItemStack in given slot. Empty Itemstack if the slot is empty.
+ */
+ @Nonnull
+ ItemStack getStackInSlot(int slot);
+
+ /**
+ *
Inserts an ItemStack into the given slot and return the remainder.
+ * The ItemStack should not be modified in this function!
+ *
+ * Note: This behaviour is subtly different from net.minecraftforge.fluids.capability.IFluidHandler#fill(net.minecraftforge.fluids.FluidStack, boolean)
+ *
+ * @param slot Slot to insert into.
+ * @param stack ItemStack to insert. This must not be modified by the item handler.
+ * @param simulate If true, the insertion is only simulated
+ * @return The remaining ItemStack that was not inserted (if the entire stack is accepted, then return an empty ItemStack).
+ * May be the same as the input ItemStack if unchanged, otherwise a new ItemStack.
+ * The returned ItemStack can be safely modified after.
+ */
+ @Nonnull
+ ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate);
+
+ /**
+ * Extracts an ItemStack from the given slot.
+ * The returned value must be empty if nothing is extracted,
+ * otherwise its stack size must be less than or equal to {@code amount} and {@link ItemStack#getMaxCount()} ()}.
+ *
+ *
+ * @param slot Slot to extract from.
+ * @param amount Amount to extract (may be greater than the current stack's max limit)
+ * @param simulate If true, the extraction is only simulated
+ * @return ItemStack extracted from the slot, must be empty if nothing can be extracted.
+ * The returned ItemStack can be safely modified after, so item handlers should return a new or copied stack.
+ */
+ @Nonnull
+ ItemStack extractItem(int slot, int amount, boolean simulate);
+
+ /**
+ * Retrieves the maximum stack size allowed to exist in the given slot.
+ *
+ * @param slot Slot to query.
+ * @return The maximum stack size allowed in the slot.
+ */
+ int getSlotLimit(int slot);
+
+ /**
+ * This function re-implements the vanilla function {@link Inventory#isValidInvStack(int, ItemStack)}.
+ * It should be used instead of simulated insertions in cases where the contents and state of the inventory are
+ * irrelevant, mainly for the purpose of automation and logic (for instance, testing if a minecart can wait
+ * to deposit its items into a full inventory, or if the items in the minecart can never be placed into the
+ * inventory and should move on).
+ *
+ *
+ * - isItemValid is false when insertion of the item is never valid.
+ * - When isItemValid is true, no assumptions can be made and insertion must be simulated case-by-case.
+ * - The actual items in the inventory, its fullness, or any other state are not considered by isItemValid.
+ *
+ *
+ * @param slot Slot to query for validity
+ * @param stack Stack to test with for validity
+ * @return true if the slot can insert the ItemStack, not considering the current state of the inventory.
+ * false if the slot can never insert the ItemStack in any situation.
+ */
+ boolean isItemValid(int slot, @Nonnull ItemStack stack);
+}
diff --git a/patchwork-items/src/main/java/net/minecraftforge/items/IItemHandlerModifiable.java b/patchwork-items/src/main/java/net/minecraftforge/items/IItemHandlerModifiable.java
new file mode 100644
index 00000000..e24731cc
--- /dev/null
+++ b/patchwork-items/src/main/java/net/minecraftforge/items/IItemHandlerModifiable.java
@@ -0,0 +1,39 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.minecraftforge.items;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.item.ItemStack;
+
+public interface IItemHandlerModifiable extends IItemHandler {
+ /**
+ * Overrides the stack in the given slot. This method is used by the
+ * standard Forge helper methods and classes. It is not intended for
+ * general use by other mods, and the handler may throw an error if it
+ * is called unexpectedly.
+ *
+ * @param slot Slot to modify
+ * @param stack ItemStack to set slot to (may be empty).
+ * @throws RuntimeException if the handler is called in a way that the handler
+ * was not expecting.
+ */
+ void setStackInSlot(int slot, @Nonnull ItemStack stack);
+}
diff --git a/patchwork-items/src/main/java/net/minecraftforge/items/ItemHandlerHelper.java b/patchwork-items/src/main/java/net/minecraftforge/items/ItemHandlerHelper.java
new file mode 100644
index 00000000..9fc6ed38
--- /dev/null
+++ b/patchwork-items/src/main/java/net/minecraftforge/items/ItemHandlerHelper.java
@@ -0,0 +1,219 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.minecraftforge.items;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.math.MathHelper;
+
+import net.patchworkmc.impl.capability.CapabilityProviderHolder;
+
+public class ItemHandlerHelper {
+ private static boolean patchwork$areItemStackCapsCompatible(ItemStack a, ItemStack b) {
+ return ((CapabilityProviderHolder) (Object) a).areCapsCompatible(((CapabilityProviderHolder) (Object) b).getCapabilityProvider());
+ }
+
+ @Nonnull
+ public static ItemStack insertItem(IItemHandler dest, @Nonnull ItemStack stack, boolean simulate) {
+ if (dest == null || stack.isEmpty()) {
+ return stack;
+ }
+
+ for (int i = 0; i < dest.getSlots(); i++) {
+ stack = dest.insertItem(i, stack, simulate);
+
+ if (stack.isEmpty()) {
+ return ItemStack.EMPTY;
+ }
+ }
+
+ return stack;
+ }
+
+ public static boolean canItemStacksStack(@Nonnull ItemStack a, @Nonnull ItemStack b) {
+ if (a.isEmpty() || !a.isItemEqualIgnoreDamage(b) || a.hasTag() != b.hasTag()) {
+ return false;
+ }
+
+ return (!a.hasTag() || a.getTag().equals(b.getTag())) && patchwork$areItemStackCapsCompatible(a, b);
+ }
+
+ /**
+ * A relaxed version of canItemStacksStack that stacks itemstacks with different metadata if they don't have subtypes.
+ * This usually only applies when players pick up items.
+ */
+ public static boolean canItemStacksStackRelaxed(@Nonnull ItemStack a, @Nonnull ItemStack b) {
+ if (a.isEmpty() || b.isEmpty() || a.getItem() != b.getItem()) {
+ return false;
+ }
+
+ if (!a.isStackable()) {
+ return false;
+ }
+
+ if (a.hasTag() != b.hasTag()) {
+ return false;
+ }
+
+ return (!a.hasTag() || a.getTag().equals(b.getTag())) && patchwork$areItemStackCapsCompatible(a, b);
+ }
+
+ @Nonnull
+ public static ItemStack copyStackWithSize(@Nonnull ItemStack itemStack, int size) {
+ if (size == 0) {
+ return ItemStack.EMPTY;
+ }
+
+ ItemStack copy = itemStack.copy();
+ copy.setCount(size);
+ return copy;
+ }
+
+ /**
+ * Inserts the ItemStack into the inventory, filling up already present stacks first.
+ * This is equivalent to the behaviour of a player picking up an item.
+ * Note: This function stacks items without subtypes with different metadata together.
+ */
+ @Nonnull
+ public static ItemStack insertItemStacked(IItemHandler inventory, @Nonnull ItemStack stack, boolean simulate) {
+ if (inventory == null || stack.isEmpty()) {
+ return stack;
+ }
+
+ // not stackable -> just insert into a new slot
+ if (!stack.isStackable()) {
+ return insertItem(inventory, stack, simulate);
+ }
+
+ int sizeInventory = inventory.getSlots();
+
+ // go through the inventory and try to fill up already existing items
+ for (int i = 0; i < sizeInventory; i++) {
+ ItemStack slot = inventory.getStackInSlot(i);
+
+ if (canItemStacksStackRelaxed(slot, stack)) {
+ stack = inventory.insertItem(i, stack, simulate);
+
+ if (stack.isEmpty()) {
+ break;
+ }
+ }
+ }
+
+ // insert remainder into empty slots
+ if (!stack.isEmpty()) {
+ // find empty slot
+ for (int i = 0; i < sizeInventory; i++) {
+ if (inventory.getStackInSlot(i).isEmpty()) {
+ stack = inventory.insertItem(i, stack, simulate);
+
+ if (stack.isEmpty()) {
+ break;
+ }
+ }
+ }
+ }
+
+ return stack;
+ }
+
+ /**
+ * giveItemToPlayer without preferred slot
+ */
+
+ /* TODO: PlayerMainInvWrapper
+ public static void giveItemToPlayer(PlayerEntity player, @Nonnull ItemStack stack) {
+ giveItemToPlayer(player, stack, -1);
+ }*/
+
+ /**
+ * Inserts the given itemstack into the players inventory.
+ * If the inventory can't hold it, the item will be dropped in the world at the players position.
+ *
+ * @param player The player to give the item to
+ * @param stack The itemstack to insert
+ */
+
+ /* TODO: PlayerMainInvWrapper
+ public static void giveItemToPlayer(PlayerEntity player, @Nonnull ItemStack stack, int preferredSlot) {
+ if (stack.isEmpty()) return;
+
+ IItemHandler inventory = new PlayerMainInvWrapper(player.inventory);
+ World world = player.world;
+
+ // try adding it into the inventory
+ ItemStack remainder = stack;
+
+ // insert into preferred slot first
+ if (preferredSlot >= 0 && preferredSlot < inventory.getSlots()) {
+ remainder = inventory.insertItem(preferredSlot, stack, false);
+ }
+
+ // then into the inventory in general
+ if (!remainder.isEmpty()) {
+ remainder = insertItemStacked(inventory, remainder, false);
+ }
+
+ // play sound if something got picked up
+ if (remainder.isEmpty() || remainder.getCount() != stack.getCount()) {
+ world.playSound(null, player.x, player.y + 0.5, player.z,
+ SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.PLAYERS, 0.2F, ((world.random.nextFloat() - world.random.nextFloat()) * 0.7F + 1.0F) * 2.0F);
+ }
+
+ // drop remaining itemstack into the world
+ if (!remainder.isEmpty() && !world.isClient) {
+ ItemEntity entityitem = new ItemEntity(world, player.x, player.y + 0.5, player.z, remainder);
+ entityitem.setPickupDelay(40);
+ entityitem.setVelocity(entityitem.getVelocity().multiply(0, 1, 0));
+
+ world.spawnEntity(entityitem);
+ }
+ }*/
+
+ /**
+ * This method uses the standard vanilla algorithm to calculate a comparator output for how "full" the inventory is.
+ * This method is an adaptation of Container#calculateComparatorOutput(Inventory).
+ *
+ * @param inv The inventory handler to test.
+ * @return A redstone value in the range [0,15] representing how "full" this inventory is.
+ */
+ public static int calcRedstoneFromInventory(@Nullable IItemHandler inv) {
+ if (inv == null) {
+ return 0;
+ } else {
+ int itemsFound = 0;
+ float proportion = 0.0F;
+
+ for (int j = 0; j < inv.getSlots(); ++j) {
+ ItemStack itemstack = inv.getStackInSlot(j);
+
+ if (!itemstack.isEmpty()) {
+ proportion += (float) itemstack.getCount() / (float) Math.min(inv.getSlotLimit(j), itemstack.getMaxCount());
+ ++itemsFound;
+ }
+ }
+
+ proportion = proportion / (float) inv.getSlots();
+ return MathHelper.floor(proportion * 14.0F) + (itemsFound > 0 ? 1 : 0);
+ }
+ }
+}
diff --git a/patchwork-items/src/main/java/net/minecraftforge/items/ItemStackHandler.java b/patchwork-items/src/main/java/net/minecraftforge/items/ItemStackHandler.java
new file mode 100644
index 00000000..064ec704
--- /dev/null
+++ b/patchwork-items/src/main/java/net/minecraftforge/items/ItemStackHandler.java
@@ -0,0 +1,210 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.minecraftforge.items;
+
+import javax.annotation.Nonnull;
+
+import net.minecraftforge.common.util.INBTSerializable;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.util.DefaultedList;
+
+import net.fabricmc.fabric.api.util.NbtType;
+
+public class ItemStackHandler implements IItemHandler, IItemHandlerModifiable, INBTSerializable {
+ protected DefaultedList stacks;
+
+ public ItemStackHandler() {
+ this(1);
+ }
+
+ public ItemStackHandler(int size) {
+ stacks = DefaultedList.ofSize(size, ItemStack.EMPTY);
+ }
+
+ public ItemStackHandler(DefaultedList stacks) {
+ this.stacks = stacks;
+ }
+
+ public void setSize(int size) {
+ stacks = DefaultedList.ofSize(size, ItemStack.EMPTY);
+ }
+
+ @Override
+ public void setStackInSlot(int slot, @Nonnull ItemStack stack) {
+ validateSlotIndex(slot);
+ this.stacks.set(slot, stack);
+ onContentsChanged(slot);
+ }
+
+ @Override
+ public int getSlots() {
+ return stacks.size();
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack getStackInSlot(int slot) {
+ validateSlotIndex(slot);
+ return this.stacks.get(slot);
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) {
+ if (stack.isEmpty()) {
+ return ItemStack.EMPTY;
+ }
+
+ if (!isItemValid(slot, stack)) {
+ return stack;
+ }
+
+ validateSlotIndex(slot);
+
+ ItemStack existing = this.stacks.get(slot);
+
+ int limit = getStackLimit(slot, stack);
+
+ if (!existing.isEmpty()) {
+ if (!ItemHandlerHelper.canItemStacksStack(stack, existing)) {
+ return stack;
+ }
+
+ limit -= existing.getCount();
+ }
+
+ if (limit <= 0) {
+ return stack;
+ }
+
+ boolean reachedLimit = stack.getCount() > limit;
+
+ if (!simulate) {
+ if (existing.isEmpty()) {
+ this.stacks.set(slot, reachedLimit ? ItemHandlerHelper.copyStackWithSize(stack, limit) : stack);
+ } else {
+ existing.increment(reachedLimit ? limit : stack.getCount());
+ }
+
+ onContentsChanged(slot);
+ }
+
+ return reachedLimit ? ItemHandlerHelper.copyStackWithSize(stack, stack.getCount() - limit) : ItemStack.EMPTY;
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack extractItem(int slot, int amount, boolean simulate) {
+ if (amount == 0) {
+ return ItemStack.EMPTY;
+ }
+
+ validateSlotIndex(slot);
+
+ ItemStack existing = this.stacks.get(slot);
+
+ if (existing.isEmpty()) {
+ return ItemStack.EMPTY;
+ }
+
+ int toExtract = Math.min(amount, existing.getMaxCount());
+
+ if (existing.getCount() <= toExtract) {
+ if (!simulate) {
+ this.stacks.set(slot, ItemStack.EMPTY);
+ onContentsChanged(slot);
+ }
+
+ return existing;
+ } else {
+ if (!simulate) {
+ this.stacks.set(slot, ItemHandlerHelper.copyStackWithSize(existing, existing.getCount() - toExtract));
+ onContentsChanged(slot);
+ }
+
+ return ItemHandlerHelper.copyStackWithSize(existing, toExtract);
+ }
+ }
+
+ @Override
+ public int getSlotLimit(int slot) {
+ return 64;
+ }
+
+ protected int getStackLimit(int slot, @Nonnull ItemStack stack) {
+ return Math.min(getSlotLimit(slot), stack.getMaxCount());
+ }
+
+ @Override
+ public boolean isItemValid(int slot, @Nonnull ItemStack stack) {
+ return true;
+ }
+
+ @Override
+ public CompoundTag serializeNBT() {
+ ListTag nbtTagList = new ListTag();
+
+ for (int i = 0; i < stacks.size(); i++) {
+ if (!stacks.get(i).isEmpty()) {
+ CompoundTag itemTag = new CompoundTag();
+ itemTag.putInt("Slot", i);
+ stacks.get(i).toTag(itemTag);
+ nbtTagList.add(itemTag);
+ }
+ }
+
+ CompoundTag nbt = new CompoundTag();
+ nbt.put("Items", nbtTagList);
+ nbt.putInt("Size", stacks.size());
+ return nbt;
+ }
+
+ @Override
+ public void deserializeNBT(CompoundTag nbt) {
+ setSize(nbt.contains("Size", NbtType.INT) ? nbt.getInt("Size") : stacks.size());
+ ListTag tagList = nbt.getList("Items", NbtType.COMPOUND);
+
+ for (int i = 0; i < tagList.size(); i++) {
+ CompoundTag itemTags = tagList.getCompound(i);
+ int slot = itemTags.getInt("Slot");
+
+ if (slot >= 0 && slot < stacks.size()) {
+ stacks.set(slot, ItemStack.fromTag(itemTags));
+ }
+ }
+
+ onLoad();
+ }
+
+ protected void validateSlotIndex(int slot) {
+ if (slot < 0 || slot >= stacks.size()) {
+ throw new RuntimeException("Slot " + slot + " not in valid range - [0," + stacks.size() + ")");
+ }
+ }
+
+ protected void onLoad() {
+ }
+
+ protected void onContentsChanged(int slot) {
+ }
+}
diff --git a/patchwork-items/src/main/java/net/patchworkmc/impl/items/ItemHandlerInventoryWrapper.java b/patchwork-items/src/main/java/net/patchworkmc/impl/items/ItemHandlerInventoryWrapper.java
new file mode 100644
index 00000000..219899e3
--- /dev/null
+++ b/patchwork-items/src/main/java/net/patchworkmc/impl/items/ItemHandlerInventoryWrapper.java
@@ -0,0 +1,92 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.impl.items;
+
+import net.minecraftforge.items.IItemHandlerModifiable;
+
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.inventory.Inventory;
+import net.minecraft.item.ItemStack;
+
+public class ItemHandlerInventoryWrapper implements Inventory {
+ private IItemHandlerModifiable itemHandler;
+
+ public ItemHandlerInventoryWrapper(IItemHandlerModifiable itemHandler) {
+ this.itemHandler = itemHandler;
+ }
+
+ @Override
+ public int getInvSize() {
+ return itemHandler.getSlots();
+ }
+
+ @Override
+ public boolean isInvEmpty() {
+ for (int i = 0; i < itemHandler.getSlots(); i++) {
+ if (!itemHandler.getStackInSlot(i).isEmpty()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public ItemStack getInvStack(int slot) {
+ return itemHandler.getStackInSlot(slot);
+ }
+
+ @Override
+ public ItemStack takeInvStack(int slot, int amount) {
+ return itemHandler.extractItem(slot, amount, false);
+ }
+
+ @Override
+ public ItemStack removeInvStack(int slot) {
+ ItemStack copy = itemHandler.getStackInSlot(slot).copy();
+ itemHandler.setStackInSlot(slot, ItemStack.EMPTY);
+ return copy;
+ }
+
+ @Override
+ public void setInvStack(int slot, ItemStack stack) {
+ itemHandler.setStackInSlot(slot, stack);
+ }
+
+ @Override
+ public void markDirty() {
+ }
+
+ @Override
+ public boolean canPlayerUseInv(PlayerEntity player) {
+ return false;
+ }
+
+ @Override
+ public void clear() {
+ for (int i = 0; i < itemHandler.getSlots(); i++) {
+ itemHandler.setStackInSlot(i, ItemStack.EMPTY);
+ }
+ }
+
+ public IItemHandlerModifiable getItemHandler() {
+ return this.itemHandler;
+ }
+}
diff --git a/patchwork-items/src/main/java/net/patchworkmc/impl/items/PatchworkItems.java b/patchwork-items/src/main/java/net/patchworkmc/impl/items/PatchworkItems.java
new file mode 100644
index 00000000..fba684d7
--- /dev/null
+++ b/patchwork-items/src/main/java/net/patchworkmc/impl/items/PatchworkItems.java
@@ -0,0 +1,38 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.impl.items;
+
+import net.minecraftforge.items.CapabilityItemHandler;
+
+import net.minecraft.util.math.Direction;
+
+import net.fabricmc.api.ModInitializer;
+
+public class PatchworkItems implements ModInitializer {
+ /**
+ * Shared ThreadLocal for {@link net.patchworkmc.mixin.items.MixinDropperBlock} and {@link net.patchworkmc.mixin.items.MixinHopperBlockEntity}.
+ */
+ public static final ThreadLocal currentSide = new ThreadLocal<>();
+
+ @Override
+ public void onInitialize() {
+ CapabilityItemHandler.register();
+ }
+}
diff --git a/patchwork-items/src/main/java/net/patchworkmc/mixin/items/MixinDropperBlock.java b/patchwork-items/src/main/java/net/patchworkmc/mixin/items/MixinDropperBlock.java
new file mode 100644
index 00000000..b4bd9c1f
--- /dev/null
+++ b/patchwork-items/src/main/java/net/patchworkmc/mixin/items/MixinDropperBlock.java
@@ -0,0 +1,39 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.mixin.items;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import net.minecraft.block.DropperBlock;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+
+import net.patchworkmc.impl.items.PatchworkItems;
+
+@Mixin(DropperBlock.class)
+public class MixinDropperBlock {
+ @Inject(method = "dispense", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/entity/HopperBlockEntity;getInventoryAt(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/inventory/Inventory;"))
+ private void onDispenseGetInventory(World world, BlockPos pos, CallbackInfo ci) {
+ PatchworkItems.currentSide.set(world.getBlockState(pos).get(DropperBlock.FACING));
+ }
+}
diff --git a/patchwork-items/src/main/java/net/patchworkmc/mixin/items/MixinHopperBlockEntity.java b/patchwork-items/src/main/java/net/patchworkmc/mixin/items/MixinHopperBlockEntity.java
new file mode 100644
index 00000000..bc8ffb7d
--- /dev/null
+++ b/patchwork-items/src/main/java/net/patchworkmc/mixin/items/MixinHopperBlockEntity.java
@@ -0,0 +1,93 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.mixin.items;
+
+import net.minecraftforge.common.capabilities.ICapabilityProvider;
+import net.minecraftforge.common.util.LazyOptional;
+import net.minecraftforge.items.CapabilityItemHandler;
+import net.minecraftforge.items.IItemHandler;
+import net.minecraftforge.items.IItemHandlerModifiable;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.HopperBlock;
+import net.minecraft.block.entity.BlockEntity;
+import net.minecraft.block.entity.Hopper;
+import net.minecraft.block.entity.HopperBlockEntity;
+import net.minecraft.inventory.Inventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Direction;
+import net.minecraft.world.World;
+
+import net.patchworkmc.impl.items.PatchworkItems;
+import net.patchworkmc.impl.items.ItemHandlerInventoryWrapper;
+
+@Mixin(HopperBlockEntity.class)
+public class MixinHopperBlockEntity {
+ @Inject(method = "getInputInventory", at = @At("HEAD"))
+ private static void onGetInputInventory(Hopper hopper, CallbackInfoReturnable cir) {
+ PatchworkItems.currentSide.set(Direction.UP);
+ }
+
+ @Inject(method = "getOutputInventory", at = @At("HEAD"))
+ private void onGetOutputInventory(CallbackInfoReturnable cir) {
+ PatchworkItems.currentSide.set(((BlockEntity) (Object) this).getCachedState().get(HopperBlock.FACING));
+ }
+
+ @Inject(method = "getInventoryAt(Lnet/minecraft/world/World;DDD)Lnet/minecraft/inventory/Inventory;", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;getEntities(Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/Box;Ljava/util/function/Predicate;)Ljava/util/List;"), cancellable = true)
+ private static void onGetInventoryAt(World world, double x, double y, double z, CallbackInfoReturnable cir) {
+ BlockPos blockPos = new BlockPos(x, y, z);
+ BlockState blockState = world.getBlockState(blockPos);
+ Block block = blockState.getBlock();
+
+ if (block.hasBlockEntity()) {
+ BlockEntity blockEntity = world.getBlockEntity(blockPos);
+
+ if (blockEntity != null) {
+ Direction direction = PatchworkItems.currentSide.get();
+
+ if (direction != null) {
+ LazyOptional capability = ((ICapabilityProvider) blockEntity).getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, direction.getOpposite());
+ PatchworkItems.currentSide.remove();
+ capability.ifPresent(result -> {
+ if (result instanceof IItemHandlerModifiable) {
+ cir.setReturnValue(new ItemHandlerInventoryWrapper((IItemHandlerModifiable) result));
+ }
+ });
+ }
+ }
+ }
+ }
+
+ @Inject(method = "transfer(Lnet/minecraft/inventory/Inventory;Lnet/minecraft/inventory/Inventory;Lnet/minecraft/item/ItemStack;ILnet/minecraft/util/math/Direction;)Lnet/minecraft/item/ItemStack;", at = @At("HEAD"), cancellable = true)
+ private static void onTransfer(Inventory from, Inventory to, ItemStack stack, int slot, Direction direction, CallbackInfoReturnable cir) {
+ IItemHandlerModifiable itemHandler;
+
+ if (to instanceof ItemHandlerInventoryWrapper) {
+ itemHandler = ((ItemHandlerInventoryWrapper) to).getItemHandler();
+ cir.setReturnValue(itemHandler.insertItem(slot, stack, false));
+ }
+ }
+}
diff --git a/patchwork-items/src/main/resources/assets/patchwork-items/icon.png b/patchwork-items/src/main/resources/assets/patchwork-items/icon.png
new file mode 100644
index 00000000..de75d2fb
Binary files /dev/null and b/patchwork-items/src/main/resources/assets/patchwork-items/icon.png differ
diff --git a/patchwork-items/src/main/resources/fabric.mod.json b/patchwork-items/src/main/resources/fabric.mod.json
new file mode 100644
index 00000000..93ef2083
--- /dev/null
+++ b/patchwork-items/src/main/resources/fabric.mod.json
@@ -0,0 +1,26 @@
+{
+ "schemaVersion": 1,
+ "id": "patchwork-items",
+ "version": "${version}",
+ "name": "Patchwork Items",
+ "description": "A reimplementation of the Minecraft Forge item handling APIs.",
+ "authors": [
+ "coderbot"
+ ],
+ "license": "LGPL-2.1-only",
+ "icon": "assets/patchwork-items/icon.png",
+ "environment": "*",
+ "depends": {
+ "patchwork-api-base": "*"
+ },
+ "mixins": [
+ "patchwork-items.mixins.json"
+ ],
+ "entrypoints": {
+ "main": ["net.patchworkmc.impl.items.PatchworkItems"]
+ },
+ "custom": {
+ "modmenu:api": true,
+ "modmenu:parent": "patchwork"
+ }
+}
diff --git a/patchwork-items/src/main/resources/patchwork-items.mixins.json b/patchwork-items/src/main/resources/patchwork-items.mixins.json
new file mode 100644
index 00000000..edf8c6d6
--- /dev/null
+++ b/patchwork-items/src/main/resources/patchwork-items.mixins.json
@@ -0,0 +1,12 @@
+{
+ "required": true,
+ "package": "net.patchworkmc.mixin.items",
+ "compatibilityLevel": "JAVA_8",
+ "mixins": [
+ "MixinDropperBlock",
+ "MixinHopperBlockEntity"
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/patchwork-key-bindings/build.gradle b/patchwork-key-bindings/build.gradle
new file mode 100644
index 00000000..33c56484
--- /dev/null
+++ b/patchwork-key-bindings/build.gradle
@@ -0,0 +1,6 @@
+archivesBaseName = "patchwork-key-bindings"
+version = getSubprojectVersion(project, "0.2.0")
+
+dependencies {
+ implementation project(path: ':patchwork-api-base', configuration: 'dev')
+}
diff --git a/patchwork-key-bindings/src/main/java/net/minecraftforge/client/extensions/IForgeKeybinding.java b/patchwork-key-bindings/src/main/java/net/minecraftforge/client/extensions/IForgeKeybinding.java
new file mode 100644
index 00000000..ee2384ac
--- /dev/null
+++ b/patchwork-key-bindings/src/main/java/net/minecraftforge/client/extensions/IForgeKeybinding.java
@@ -0,0 +1,69 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.minecraftforge.client.extensions;
+
+import javax.annotation.Nonnull;
+
+import net.minecraftforge.client.settings.IKeyConflictContext;
+import net.minecraftforge.client.settings.KeyModifier;
+
+import net.minecraft.client.options.KeyBinding;
+import net.minecraft.client.util.InputUtil;
+
+public interface IForgeKeybinding {
+ default KeyBinding getKeyBinding() {
+ return (KeyBinding) this;
+ }
+
+ @Nonnull
+ InputUtil.KeyCode getKey();
+
+ /**
+ * Checks that the key conflict context and modifier are active, and that the keyCode matches this binding.
+ */
+ default boolean isActiveAndMatches(InputUtil.KeyCode keyCode) {
+ return keyCode.getKeyCode() != 0 && keyCode.equals(getKey()) && getKeyConflictContext().isActive() && getKeyModifier().isActive(getKeyConflictContext());
+ }
+
+ default void setToDefault() {
+ setKeyModifierAndCode(getKeyModifierDefault(), getKeyBinding().getDefaultKeyCode());
+ }
+
+ IKeyConflictContext getKeyConflictContext();
+
+ void setKeyConflictContext(IKeyConflictContext keyConflictContext);
+
+ KeyModifier getKeyModifierDefault();
+
+ KeyModifier getKeyModifier();
+
+ void setKeyModifierAndCode(KeyModifier keyModifier, InputUtil.KeyCode keyCode);
+
+ /**
+ * Returns true when one of the bindings' key codes conflicts with the other's modifier.
+ */
+ default boolean hasKeyCodeModifierConflict(KeyBinding other) {
+ if (getKeyConflictContext().conflicts(((IForgeKeybinding) other).getKeyConflictContext()) || ((IForgeKeybinding) other).getKeyConflictContext().conflicts(getKeyConflictContext())) {
+ return getKeyModifier().matches(((IForgeKeybinding) other).getKey()) || ((IForgeKeybinding) other).getKeyModifier().matches(getKey());
+ }
+
+ return false;
+ }
+}
diff --git a/patchwork-key-bindings/src/main/java/net/minecraftforge/client/settings/IKeyConflictContext.java b/patchwork-key-bindings/src/main/java/net/minecraftforge/client/settings/IKeyConflictContext.java
new file mode 100644
index 00000000..78515753
--- /dev/null
+++ b/patchwork-key-bindings/src/main/java/net/minecraftforge/client/settings/IKeyConflictContext.java
@@ -0,0 +1,37 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.minecraftforge.client.settings;
+
+/**
+ * Defines the context that a {@link KeyBinding} is used.
+ * Key conflicts occur when a {@link KeyBinding} has the same {@link IKeyConflictContext} and has conflicting modifiers and keyCodes.
+ */
+public interface IKeyConflictContext {
+ /**
+ * @return true if conditions are met to activate {@link KeyBinding}s with this context
+ */
+ boolean isActive();
+
+ /**
+ * @return true if the other context can have {@link KeyBinding} conflicts with this one.
+ * This will be called on both contexts to check for conflicts.
+ */
+ boolean conflicts(IKeyConflictContext other);
+}
diff --git a/patchwork-key-bindings/src/main/java/net/minecraftforge/client/settings/KeyConflictContext.java b/patchwork-key-bindings/src/main/java/net/minecraftforge/client/settings/KeyConflictContext.java
new file mode 100644
index 00000000..b0ae3034
--- /dev/null
+++ b/patchwork-key-bindings/src/main/java/net/minecraftforge/client/settings/KeyConflictContext.java
@@ -0,0 +1,70 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.minecraftforge.client.settings;
+
+import net.minecraft.client.MinecraftClient;
+
+public enum KeyConflictContext implements IKeyConflictContext {
+ /**
+ * Universal key bindings are used in every context and will conflict with any other context.
+ * Key Bindings are universal by default.
+ */
+ UNIVERSAL {
+ @Override
+ public boolean isActive() {
+ return true;
+ }
+
+ @Override
+ public boolean conflicts(IKeyConflictContext other) {
+ return true;
+ }
+ },
+
+ /**
+ * Gui key bindings are only used when a {@link GuiScreen} is open.
+ */
+ GUI {
+ @Override
+ public boolean isActive() {
+ return MinecraftClient.getInstance().currentScreen != null;
+ }
+
+ @Override
+ public boolean conflicts(IKeyConflictContext other) {
+ return this == other;
+ }
+ },
+
+ /**
+ * In-game key bindings are only used when a {@link GuiScreen} is not open.
+ */
+ IN_GAME {
+ @Override
+ public boolean isActive() {
+ return !GUI.isActive();
+ }
+
+ @Override
+ public boolean conflicts(IKeyConflictContext other) {
+ return this == other;
+ }
+ }
+}
diff --git a/patchwork-key-bindings/src/main/java/net/minecraftforge/client/settings/KeyModifier.java b/patchwork-key-bindings/src/main/java/net/minecraftforge/client/settings/KeyModifier.java
new file mode 100644
index 00000000..22cb1165
--- /dev/null
+++ b/patchwork-key-bindings/src/main/java/net/minecraftforge/client/settings/KeyModifier.java
@@ -0,0 +1,150 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.minecraftforge.client.settings;
+
+import java.util.function.Supplier;
+
+import javax.annotation.Nullable;
+
+import org.lwjgl.glfw.GLFW;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.resource.language.I18n;
+import net.minecraft.client.util.InputUtil;
+
+public enum KeyModifier {
+ CONTROL {
+ @Override
+ public boolean matches(InputUtil.KeyCode key) {
+ int keyCode = key.getKeyCode();
+
+ if (MinecraftClient.IS_SYSTEM_MAC) {
+ return keyCode == GLFW.GLFW_KEY_LEFT_ALT || keyCode == GLFW.GLFW_KEY_RIGHT_ALT;
+ } else {
+ return keyCode == GLFW.GLFW_KEY_LEFT_CONTROL || keyCode == GLFW.GLFW_KEY_RIGHT_CONTROL;
+ }
+ }
+
+ @Override
+ public boolean isActive(@Nullable IKeyConflictContext conflictContext) {
+ return Screen.hasControlDown();
+ }
+
+ @Override
+ public String getLocalizedComboName(InputUtil.KeyCode key, Supplier defaultLogic) {
+ String keyName = defaultLogic.get();
+ String localizationFormatKey = MinecraftClient.IS_SYSTEM_MAC ? "forge.controlsgui.control.mac" : "forge.controlsgui.control";
+ return I18n.translate(localizationFormatKey, keyName);
+ }
+ },
+ SHIFT {
+ @Override
+ public boolean matches(InputUtil.KeyCode key) {
+ return key.getKeyCode() == GLFW.GLFW_KEY_LEFT_SHIFT || key.getKeyCode() == GLFW.GLFW_KEY_RIGHT_SHIFT;
+ }
+
+ @Override
+ public boolean isActive(@Nullable IKeyConflictContext conflictContext) {
+ return Screen.hasShiftDown();
+ }
+
+ @Override
+ public String getLocalizedComboName(InputUtil.KeyCode key, Supplier defaultLogic) {
+ return I18n.translate("forge.controlsgui.shift", defaultLogic.get());
+ }
+ },
+ ALT {
+ @Override
+ public boolean matches(InputUtil.KeyCode key) {
+ return key.getKeyCode() == GLFW.GLFW_KEY_LEFT_ALT || key.getKeyCode() == GLFW.GLFW_KEY_RIGHT_ALT;
+ }
+
+ @Override
+ public boolean isActive(@Nullable IKeyConflictContext conflictContext) {
+ return Screen.hasAltDown();
+ }
+
+ @Override
+ public String getLocalizedComboName(InputUtil.KeyCode keyCode, Supplier defaultLogic) {
+ return I18n.translate("forge.controlsgui.alt", defaultLogic.get());
+ }
+ },
+ NONE {
+ @Override
+ public boolean matches(InputUtil.KeyCode key) {
+ return false;
+ }
+
+ @Override
+ public boolean isActive(@Nullable IKeyConflictContext conflictContext) {
+ if (conflictContext != null && !conflictContext.conflicts(KeyConflictContext.IN_GAME)) {
+ for (KeyModifier keyModifier : MODIFIER_VALUES) {
+ if (keyModifier.isActive(conflictContext)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public String getLocalizedComboName(InputUtil.KeyCode key, Supplier defaultLogic) {
+ return defaultLogic.get();
+ }
+ };
+
+ public static final KeyModifier[] MODIFIER_VALUES = {SHIFT, CONTROL, ALT};
+
+ public static KeyModifier getActiveModifier() {
+ for (KeyModifier keyModifier : MODIFIER_VALUES) {
+ if (keyModifier.isActive(null)) {
+ return keyModifier;
+ }
+ }
+
+ return NONE;
+ }
+
+ public static boolean isKeyCodeModifier(InputUtil.KeyCode key) {
+ for (KeyModifier keyModifier : MODIFIER_VALUES) {
+ if (keyModifier.matches(key)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static KeyModifier valueFromString(String stringValue) {
+ try {
+ return valueOf(stringValue);
+ } catch (NullPointerException | IllegalArgumentException ignored) {
+ return NONE;
+ }
+ }
+
+ public abstract boolean matches(InputUtil.KeyCode key);
+
+ public abstract boolean isActive(@Nullable IKeyConflictContext conflictContext);
+
+ public abstract String getLocalizedComboName(InputUtil.KeyCode key, Supplier defaultLogic);
+}
diff --git a/patchwork-key-bindings/src/main/java/net/patchworkmc/api/keybindings/PatchworkKeyBinding.java b/patchwork-key-bindings/src/main/java/net/patchworkmc/api/keybindings/PatchworkKeyBinding.java
new file mode 100644
index 00000000..664b217b
--- /dev/null
+++ b/patchwork-key-bindings/src/main/java/net/patchworkmc/api/keybindings/PatchworkKeyBinding.java
@@ -0,0 +1,63 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.api.keybindings;
+
+import net.minecraftforge.client.extensions.IForgeKeybinding;
+import net.minecraftforge.client.settings.IKeyConflictContext;
+import net.minecraftforge.client.settings.KeyModifier;
+
+import net.minecraft.client.options.KeyBinding;
+import net.minecraft.client.util.InputUtil;
+
+import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
+
+import net.patchworkmc.impl.keybindings.ForgeKeyBindingConstruct;
+
+public class PatchworkKeyBinding extends KeyBinding {
+ public PatchworkKeyBinding(String id, int keyCode, String category) {
+ super(id, keyCode, category);
+ KeyBindingHelper.registerKeyBinding(this);
+ }
+
+ public PatchworkKeyBinding(String id, InputUtil.Type type, int code, String category) {
+ super(id, type, code, category);
+ KeyBindingHelper.registerKeyBinding(this);
+ }
+
+ public PatchworkKeyBinding(String id, IKeyConflictContext keyConflictContext, final InputUtil.Type inputType, final int keyCode, String category) {
+ this(id, keyConflictContext, inputType.createFromCode(keyCode), category);
+ }
+
+ public PatchworkKeyBinding(String id, IKeyConflictContext keyConflictContext, InputUtil.KeyCode keyCode, String category) {
+ this(id, keyConflictContext, KeyModifier.NONE, keyCode, category);
+ }
+
+ public PatchworkKeyBinding(String id, IKeyConflictContext keyConflictContext, KeyModifier keyModifier, final InputUtil.Type inputType, final int keyCode, String category) {
+ this(id, keyConflictContext, keyModifier, inputType.createFromCode(keyCode), category);
+ }
+
+ public PatchworkKeyBinding(String id, IKeyConflictContext keyConflictContext, KeyModifier keyModifier, InputUtil.KeyCode keyCode, String category) {
+ super(id, keyCode.getCategory(), keyCode.getKeyCode(), category);
+ ((IForgeKeybinding) this).setKeyConflictContext(keyConflictContext);
+ ((IForgeKeybinding) this).setKeyModifierAndCode(keyModifier, keyCode);
+ ((ForgeKeyBindingConstruct) this).patchwork$constructForgeKeyBindingOptions(keyConflictContext, keyModifier.matches(keyCode) ? KeyModifier.NONE : keyModifier);
+ KeyBindingHelper.registerKeyBinding(this);
+ }
+}
diff --git a/patchwork-key-bindings/src/main/java/net/patchworkmc/impl/keybindings/ForgeKeyBindingConstruct.java b/patchwork-key-bindings/src/main/java/net/patchworkmc/impl/keybindings/ForgeKeyBindingConstruct.java
new file mode 100644
index 00000000..29217348
--- /dev/null
+++ b/patchwork-key-bindings/src/main/java/net/patchworkmc/impl/keybindings/ForgeKeyBindingConstruct.java
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.impl.keybindings;
+
+import net.minecraftforge.client.settings.IKeyConflictContext;
+import net.minecraftforge.client.settings.KeyModifier;
+
+public interface ForgeKeyBindingConstruct {
+ void patchwork$constructForgeKeyBindingOptions(IKeyConflictContext keyConflictContext, KeyModifier keyModifier);
+}
diff --git a/patchwork-key-bindings/src/main/java/net/patchworkmc/mixin/keybindings/MixinKeyBinding.java b/patchwork-key-bindings/src/main/java/net/patchworkmc/mixin/keybindings/MixinKeyBinding.java
new file mode 100644
index 00000000..ad2bcff2
--- /dev/null
+++ b/patchwork-key-bindings/src/main/java/net/patchworkmc/mixin/keybindings/MixinKeyBinding.java
@@ -0,0 +1,103 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.mixin.keybindings;
+
+import net.minecraftforge.client.extensions.IForgeKeybinding;
+import net.minecraftforge.client.settings.IKeyConflictContext;
+import net.minecraftforge.client.settings.KeyConflictContext;
+import net.minecraftforge.client.settings.KeyModifier;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import net.minecraft.client.options.KeyBinding;
+import net.minecraft.client.util.InputUtil;
+
+import net.patchworkmc.impl.keybindings.ForgeKeyBindingConstruct;
+
+@Mixin(KeyBinding.class)
+public abstract class MixinKeyBinding implements Comparable, IForgeKeybinding, ForgeKeyBindingConstruct {
+ @Shadow
+ private InputUtil.KeyCode keyCode;
+
+ // These exist in forge to allow modifiers and conflicting keys to work, this is not implemented
+ // but these remain to avoid stubbing the methods
+ @Unique
+ private KeyModifier keyModifierDefault = KeyModifier.NONE;
+ @Unique
+ private KeyModifier keyModifier = KeyModifier.NONE;
+ @Unique
+ private IKeyConflictContext keyConflictContext = KeyConflictContext.UNIVERSAL;
+
+ @Inject(method = "isPressed()Z", at = @At("HEAD"))
+ public void isPressed(CallbackInfoReturnable info) {
+ if (!getKeyConflictContext().isActive()) {
+ info.setReturnValue(false);
+ info.cancel();
+ }
+ }
+
+ @Override
+ public InputUtil.KeyCode getKey() {
+ return this.keyCode;
+ }
+
+ @Override
+ public IKeyConflictContext getKeyConflictContext() {
+ return keyConflictContext;
+ }
+
+ @Override
+ public void setKeyConflictContext(IKeyConflictContext keyConflictContext) {
+ this.keyConflictContext = keyConflictContext;
+ }
+
+ @Override
+ public KeyModifier getKeyModifierDefault() {
+ return keyModifierDefault;
+ }
+
+ @Override
+ public KeyModifier getKeyModifier() {
+ return keyModifier;
+ }
+
+ @Override
+ public void setKeyModifierAndCode(KeyModifier keyModifier, InputUtil.KeyCode keyCode) {
+ this.keyCode = keyCode;
+
+ if (keyModifier.matches(keyCode)) {
+ keyModifier = KeyModifier.NONE;
+ }
+
+ this.keyModifier = keyModifier;
+ KeyBinding.updateKeysByCode();
+ }
+
+ @Override
+ public void patchwork$constructForgeKeyBindingOptions(IKeyConflictContext keyConflictContext, KeyModifier keyModifier) {
+ this.keyModifier = keyModifier;
+ this.keyModifierDefault = keyModifier;
+ this.keyConflictContext = keyConflictContext;
+ }
+}
diff --git a/patchwork-key-bindings/src/main/resources/assets/patchwork-key-bindings/icon.png b/patchwork-key-bindings/src/main/resources/assets/patchwork-key-bindings/icon.png
new file mode 100644
index 00000000..de75d2fb
Binary files /dev/null and b/patchwork-key-bindings/src/main/resources/assets/patchwork-key-bindings/icon.png differ
diff --git a/patchwork-key-bindings/src/main/resources/fabric.mod.json b/patchwork-key-bindings/src/main/resources/fabric.mod.json
new file mode 100644
index 00000000..bed15e69
--- /dev/null
+++ b/patchwork-key-bindings/src/main/resources/fabric.mod.json
@@ -0,0 +1,26 @@
+{
+ "schemaVersion": 1,
+ "id": "patchwork-key-bindings",
+ "name": "Patchwork Keybindings",
+ "version": "${version}",
+ "license": "LGPL-2.1-only",
+ "icon": "assets/patchwork-key-bindings/icon.png",
+ "contact": {
+ "issues": "https://github.com/PatchworkMC/patchwork-api/issues",
+ "sources": "https://github.com/PatchworkMC/patchwork-api"
+ },
+ "authors": [
+ "PatchworkMC"
+ ],
+ "depends": {
+ "patchwork-api-base": "*"
+ },
+ "mixins": [
+ "patchwork-key-bindings.mixins.json"
+ ],
+ "description": "Implementation of the Forge Keybinding system.",
+ "custom": {
+ "modmenu:api": true,
+ "modmenu:parent": "patchwork"
+ }
+}
diff --git a/patchwork-key-bindings/src/main/resources/patchwork-key-bindings.mixins.json b/patchwork-key-bindings/src/main/resources/patchwork-key-bindings.mixins.json
new file mode 100644
index 00000000..98f3d6ad
--- /dev/null
+++ b/patchwork-key-bindings/src/main/resources/patchwork-key-bindings.mixins.json
@@ -0,0 +1,11 @@
+{
+ "required": true,
+ "package": "net.patchworkmc.mixin.keybindings",
+ "compatibilityLevel": "JAVA_8",
+ "client": [
+ "MixinKeyBinding"
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/patchwork-level-generators/build.gradle b/patchwork-level-generators/build.gradle
index 597ee7b6..1f95fe0f 100644
--- a/patchwork-level-generators/build.gradle
+++ b/patchwork-level-generators/build.gradle
@@ -1,11 +1,12 @@
archivesBaseName = "patchwork-level-generators"
-version = getSubprojectVersion(project, "0.2.0")
+version = getSubprojectVersion(project, "0.3.0")
dependencies {
- compile project(path: ':patchwork-fml', configuration: 'dev')
- compile project(path: ':patchwork-events-world', configuration: 'dev')
+ implementation project(path: ':patchwork-api-base', configuration: 'dev')
+ implementation project(path: ':patchwork-fml', configuration: 'dev')
+ implementation project(path: ':patchwork-events-world', configuration: 'dev')
}
-minecraft {
+loom {
accessWidener "src/main/resources/patchwork-level-generators.accesswidener"
}
diff --git a/patchwork-level-generators/src/main/resources/fabric.mod.json b/patchwork-level-generators/src/main/resources/fabric.mod.json
index 01fc6bd6..018ec87d 100644
--- a/patchwork-level-generators/src/main/resources/fabric.mod.json
+++ b/patchwork-level-generators/src/main/resources/fabric.mod.json
@@ -13,7 +13,7 @@
"PatchworkMC"
],
"depends": {
- "fabricloader": ">=0.8.4"
+ "patchwork-api-base": "*"
},
"description": "Implementation of the Forge World Type API.",
"mixins": [
diff --git a/patchwork-loot/build.gradle b/patchwork-loot/build.gradle
index 9029bd24..7b270e2c 100644
--- a/patchwork-loot/build.gradle
+++ b/patchwork-loot/build.gradle
@@ -1,2 +1,7 @@
archivesBaseName = "patchwork-loot"
-version = getSubprojectVersion(project, "0.2.0")
+version = getSubprojectVersion(project, "0.3.0")
+
+dependencies {
+ implementation project(path: ':patchwork-api-base', configuration: 'dev')
+}
+
diff --git a/patchwork-loot/src/main/java/net/minecraftforge/event/LootTableLoadEvent.java b/patchwork-loot/src/main/java/net/minecraftforge/event/LootTableLoadEvent.java
new file mode 100644
index 00000000..93ecb6b8
--- /dev/null
+++ b/patchwork-loot/src/main/java/net/minecraftforge/event/LootTableLoadEvent.java
@@ -0,0 +1,67 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.minecraftforge.event;
+
+import net.minecraftforge.eventbus.api.Event;
+
+import net.minecraft.loot.LootManager;
+import net.minecraft.loot.LootTable;
+import net.minecraft.util.Identifier;
+
+/**
+ * Event fired when a LootTable json is loaded from json.
+ * This event is fired whenever resources are loaded, or when the server starts.
+ * This event will NOT be fired for LootTables loaded from the world folder, these are
+ * considered configurations files and should not be modified by mods.
+ *
+ * Canceling the event will make it load a empty loot table.
+ */
+public class LootTableLoadEvent extends Event {
+ private final Identifier name;
+ private LootTable table;
+ private LootManager lootTableManager;
+
+ public LootTableLoadEvent(Identifier name, LootTable table, LootManager lootTableManager) {
+ this.name = name;
+ this.table = table;
+ this.lootTableManager = lootTableManager;
+ }
+
+ public Identifier getName() {
+ return this.name;
+ }
+
+ public LootTable getTable() {
+ return this.table;
+ }
+
+ public void setTable(LootTable table) {
+ this.table = table;
+ }
+
+ public LootManager getLootTableManager() {
+ return this.lootTableManager;
+ }
+
+ @Override
+ public boolean isCancelable() {
+ return true;
+ }
+}
diff --git a/patchwork-loot/src/main/java/net/patchworkmc/api/loot/ForgeLootPool.java b/patchwork-loot/src/main/java/net/patchworkmc/api/loot/ForgeLootPool.java
new file mode 100644
index 00000000..7aef5158
--- /dev/null
+++ b/patchwork-loot/src/main/java/net/patchworkmc/api/loot/ForgeLootPool.java
@@ -0,0 +1,42 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.api.loot;
+
+import net.minecraft.loot.LootPool;
+import net.minecraft.loot.LootTableRange;
+import net.minecraft.loot.UniformLootTableRange;
+
+/**
+ * Interface for adding Forge methods added to LootPool and its inner classes.
+ */
+public interface ForgeLootPool {
+ // TODO: doesn't include methods having to do with freezing yet
+
+ String getName();
+ LootTableRange getRolls();
+ LootTableRange getBonusRolls();
+ void setRolls(UniformLootTableRange v);
+ void setBonusRolls(UniformLootTableRange v);
+
+ public interface Builder {
+ LootPool.Builder name(String name);
+ LootPool.Builder bonusRolls(float min, float max);
+ }
+}
diff --git a/patchwork-loot/src/main/java/net/patchworkmc/api/loot/ForgeLootTable.java b/patchwork-loot/src/main/java/net/patchworkmc/api/loot/ForgeLootTable.java
new file mode 100644
index 00000000..86650bac
--- /dev/null
+++ b/patchwork-loot/src/main/java/net/patchworkmc/api/loot/ForgeLootTable.java
@@ -0,0 +1,33 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.api.loot;
+
+import net.minecraft.loot.LootPool;
+
+/**
+ * Interface for adding Forge methods added to LootTable.
+ */
+public interface ForgeLootTable {
+ // TODO: doesn't include methods having to do with freezing yet
+
+ LootPool getPool(String name);
+ LootPool removePool(String name);
+ void addPool(LootPool pool);
+}
diff --git a/patchwork-loot/src/main/java/net/patchworkmc/impl/event/loot/LootEvents.java b/patchwork-loot/src/main/java/net/patchworkmc/impl/event/loot/LootEvents.java
new file mode 100644
index 00000000..c732f201
--- /dev/null
+++ b/patchwork-loot/src/main/java/net/patchworkmc/impl/event/loot/LootEvents.java
@@ -0,0 +1,39 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.impl.event.loot;
+
+import net.minecraftforge.event.LootTableLoadEvent;
+import net.minecraftforge.common.MinecraftForge;
+
+import net.minecraft.loot.LootManager;
+import net.minecraft.loot.LootTable;
+import net.minecraft.util.Identifier;
+
+public class LootEvents {
+ public static LootTable loadLootTable(Identifier name, LootTable table, LootManager manager) {
+ LootTableLoadEvent event = new LootTableLoadEvent(name, table, manager);
+
+ if (MinecraftForge.EVENT_BUS.post(event)) {
+ return LootTable.EMPTY;
+ }
+
+ return event.getTable();
+ }
+}
diff --git a/patchwork-loot/src/main/java/net/patchworkmc/impl/loot/LootHooks.java b/patchwork-loot/src/main/java/net/patchworkmc/impl/loot/LootHooks.java
new file mode 100644
index 00000000..88a5d840
--- /dev/null
+++ b/patchwork-loot/src/main/java/net/patchworkmc/impl/loot/LootHooks.java
@@ -0,0 +1,149 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.impl.loot;
+
+import java.util.Deque;
+import java.util.HashSet;
+
+import javax.annotation.Nullable;
+
+import com.google.common.collect.Queues;
+import com.google.common.collect.Sets;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+
+import net.minecraft.loot.LootManager;
+import net.minecraft.loot.LootTable;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.JsonHelper;
+
+import net.patchworkmc.impl.event.loot.LootEvents;
+
+// NOTE: this class is more or less a direct copy of parts of Forge's ForgeHooks.
+public class LootHooks {
+ // Made public for Patchwork's own use
+ public static ThreadLocal> lootContext = new ThreadLocal>();
+
+ public static LootTable loadLootTable(Gson gson, Identifier name, JsonElement data, boolean custom, LootManager lootTableManager) {
+ Deque que = lootContext.get();
+
+ if (que == null) {
+ que = Queues.newArrayDeque();
+ lootContext.set(que);
+ }
+
+ LootTable ret = null;
+
+ try {
+ que.push(new LootTableContext(name, custom));
+ ret = gson.fromJson(data, LootTable.class);
+ que.pop();
+ } catch (JsonParseException e) {
+ que.pop();
+ throw e;
+ }
+
+ if (!custom) {
+ ret = LootEvents.loadLootTable(name, ret, lootTableManager);
+ }
+
+ // if (ret != null) {
+ // ret.freeze();
+ // }
+
+ return ret;
+ }
+
+ private static LootTableContext getLootTableContext() {
+ LootTableContext ctx = lootContext.get().peek();
+
+ if (ctx == null) {
+ throw new JsonParseException("Invalid call stack, could not grab json context!"); // Should I throw this? Do we care about custom deserializers outside the manager?
+ }
+
+ return ctx;
+ }
+
+ public static String readPoolName(JsonObject json) {
+ LootTableContext ctx = LootHooks.getLootTableContext();
+ ctx.resetPoolCtx();
+
+ if (json.has("name")) {
+ return JsonHelper.getString(json, "name");
+ }
+
+ if (ctx.custom) {
+ return "custom#" + json.hashCode(); //We don't care about custom ones modders shouldn't be editing them!
+ }
+
+ ctx.poolCount++;
+
+ if (!ctx.vanilla) {
+ throw new JsonParseException("Loot Table \"" + ctx.name.toString() + "\" Missing `name` entry for pool #" + (ctx.poolCount - 1));
+ }
+
+ return ctx.poolCount == 1 ? "main" : "pool" + (ctx.poolCount - 1);
+ }
+
+ // Made public for Patchwork's own use
+ public static class LootTableContext {
+ public final Identifier name;
+ public final boolean custom;
+ private final boolean vanilla;
+ public int poolCount = 0;
+ public int entryCount = 0;
+ private HashSet entryNames = Sets.newHashSet();
+
+ protected LootTableContext(Identifier name, boolean custom) {
+ this.name = name;
+ this.custom = custom;
+ this.vanilla = "minecraft".equals(this.name.getNamespace());
+ }
+
+ private void resetPoolCtx() {
+ this.entryCount = 0;
+ this.entryNames.clear();
+ }
+
+ public String validateEntryName(@Nullable String name) {
+ if (name != null && !this.entryNames.contains(name)) {
+ this.entryNames.add(name);
+ return name;
+ }
+
+ if (!this.vanilla) {
+ throw new JsonParseException("Loot Table \"" + this.name.toString() + "\" Duplicate entry name \"" + name + "\" for pool #" + (this.poolCount - 1) + " entry #" + (this.entryCount - 1));
+ }
+
+ int x = 0;
+
+ while (this.entryNames.contains(name + "#" + x)) {
+ x++;
+ }
+
+ name = name + "#" + x;
+ this.entryNames.add(name);
+
+ return name;
+ }
+ }
+}
diff --git a/patchwork-loot/src/main/java/net/patchworkmc/impl/loot/PatchworkLootPool.java b/patchwork-loot/src/main/java/net/patchworkmc/impl/loot/PatchworkLootPool.java
new file mode 100644
index 00000000..68770139
--- /dev/null
+++ b/patchwork-loot/src/main/java/net/patchworkmc/impl/loot/PatchworkLootPool.java
@@ -0,0 +1,28 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.impl.loot;
+
+/**
+ * Forge does this through patching the constructor, we just add methods with
+ * mixins instead.
+ */
+public interface PatchworkLootPool {
+ void patchwork$setName(String name);
+}
diff --git a/patchwork-loot/src/main/java/net/patchworkmc/mixin/loot/MixinJsonDataLoader.java b/patchwork-loot/src/main/java/net/patchworkmc/mixin/loot/MixinJsonDataLoader.java
new file mode 100644
index 00000000..85b3b420
--- /dev/null
+++ b/patchwork-loot/src/main/java/net/patchworkmc/mixin/loot/MixinJsonDataLoader.java
@@ -0,0 +1,44 @@
+/*
+ * Minecraft Forge, Patchwork Project
+ * Copyright (c) 2016-2020, 2019-2020
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.patchworkmc.mixin.loot;
+
+import java.util.Map;
+
+import com.google.gson.JsonObject;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+
+import net.minecraft.resource.JsonDataLoader;
+import net.minecraft.resource.SinglePreparationResourceReloadListener;
+import net.minecraft.util.Identifier;
+
+// TODO: Is there a better place to put this?
+@Mixin(JsonDataLoader.class)
+public abstract class MixinJsonDataLoader extends SinglePreparationResourceReloadListener