diff --git a/src/main/java/net/sys42/dakedres/grafting/Grafting.java b/src/main/java/net/sys42/dakedres/grafting/Grafting.java index 0407226..ae6a6d1 100644 --- a/src/main/java/net/sys42/dakedres/grafting/Grafting.java +++ b/src/main/java/net/sys42/dakedres/grafting/Grafting.java @@ -1,39 +1,57 @@ package net.sys42.dakedres.grafting; +import com.mojang.serialization.Codec; import eu.pb4.polymer.core.api.block.PolymerBlockUtils; -import eu.pb4.polymer.resourcepack.api.PolymerModelData; +import eu.pb4.polymer.core.api.other.PolymerComponent; import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.registry.DynamicRegistries; +import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder; +import net.fabricmc.fabric.api.event.registry.RegistryAttribute; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; import net.minecraft.block.AbstractBlock; import net.minecraft.block.Block; +import net.minecraft.block.Blocks; import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.component.ComponentType; import net.minecraft.item.BlockItem; import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.registry.Registries; -import net.minecraft.registry.Registry; +import net.minecraft.registry.*; +import net.minecraft.resource.Resource; import net.minecraft.sound.BlockSoundGroup; import net.minecraft.util.Identifier; -import net.sys42.dakedres.grafting.blocks.Workbench; -import net.sys42.dakedres.grafting.blocks.WorkbenchBlockEntity; +import net.sys42.dakedres.grafting.block.Workbench; +import net.sys42.dakedres.grafting.block.entity.WorkbenchBlockEntity; +import net.sys42.dakedres.grafting.data.PlacementOrder; +import net.sys42.dakedres.grafting.data.PlacementOrderReloadListener; +import net.sys42.dakedres.grafting.item.WorkbenchItem; +import net.sys42.dakedres.grafting.ui.Icons; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.stream.IntStream; + public class Grafting implements ModInitializer { public static final String MOD_ID = "grafting"; + public static final Identifier WORKBENCH_ID = Identifier.of(Grafting.MOD_ID, "workbench"); + public static final Identifier PLACEMENT_ORDER_COMPONENT_ID = Identifier.of(Grafting.MOD_ID, "placement_order"); // This logger is used to write text to the console and the log file. // It is considered best practice to use your mod id as the logger's name. // That way, it's clear which mod wrote info, warnings, and errors. public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); - public static final Identifier WORKBENCH_ID = Identifier.of(Grafting.MOD_ID, "workbench"); public static final Block WORKBENCH = Registry.register( Registries.BLOCK, WORKBENCH_ID, - new Workbench(AbstractBlock.Settings.create().sounds(BlockSoundGroup.WOOD)) + new Workbench( + // TODO: A bit easier to break? + Block.Settings.copy(Blocks.CRAFTING_TABLE) + ) ); public static final BlockEntityType WORKBENCH_BLOCK_ENTITY_TYPE = Registry.register( Registries.BLOCK_ENTITY_TYPE, @@ -42,9 +60,20 @@ public class Grafting implements ModInitializer { ); public static final BlockItem WORKBENCH_ITEM = Registry.register( Registries.ITEM, WORKBENCH_ID, - new Workbench.WorkbenchItem(WORKBENCH, new Item.Settings()) + new WorkbenchItem(WORKBENCH, new Item.Settings()) ); + public static final ComponentType PLACEMENT_ORDER_COMPONENT_TYPE = Registry.register( + Registries.DATA_COMPONENT_TYPE, + PLACEMENT_ORDER_COMPONENT_ID, + ComponentType.builder().codec(Identifier.CODEC).build() + ); + + public static final RegistryKey> PLACEMENT_ORDER_REGISTRY_KEY = RegistryKey.ofRegistry(Identifier.of(Grafting.MOD_ID, "placement_order")); +// public static final Registry PLACEMENT_ORDER_REGISTRY = FabricRegistryBuilder.createSimple(PLACEMENT_ORDER_REGISTRY_KEY) +// .attribute(RegistryAttribute.MODDED) +// .buildAndRegister(); + @Override public void onInitialize() { // This code runs as soon as Minecraft is in a mod-load-ready state. @@ -54,6 +83,11 @@ public class Grafting implements ModInitializer { LOGGER.info("Hello Fabric world!"); PolymerBlockUtils.registerBlockEntity(WORKBENCH_BLOCK_ENTITY_TYPE); + PolymerComponent.registerDataComponent(PLACEMENT_ORDER_COMPONENT_TYPE); + Icons.init(); // Static initialization +// PlacementOrderReloadListener.init(); + + DynamicRegistries.register(PLACEMENT_ORDER_REGISTRY_KEY, PlacementOrder.CODEC); if (PolymerResourcePackUtils.addModAssets(MOD_ID)) { LOGGER.info("Successfully added mod assets for " + MOD_ID); diff --git a/src/main/java/net/sys42/dakedres/grafting/PlacementMatrixUtils.java b/src/main/java/net/sys42/dakedres/grafting/PlacementUtils.java similarity index 54% rename from src/main/java/net/sys42/dakedres/grafting/PlacementMatrixUtils.java rename to src/main/java/net/sys42/dakedres/grafting/PlacementUtils.java index 55c1a7a..1e438ac 100644 --- a/src/main/java/net/sys42/dakedres/grafting/PlacementMatrixUtils.java +++ b/src/main/java/net/sys42/dakedres/grafting/PlacementUtils.java @@ -1,6 +1,11 @@ package net.sys42.dakedres.grafting; -public class PlacementMatrixUtils { +import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.sys42.dakedres.grafting.data.PlacementOrder; + +public class PlacementUtils { public static final int[][] ROTATION_MATRICES = { { 0,1,2,3,4,5,6,7,8 }, { 1,2,5,0,4,8,3,6,7 }, @@ -33,6 +38,10 @@ public class PlacementMatrixUtils { } public static int[] rotate(int[] matrix, int rotation) { + if(rotation == 0) { + return matrix; + } + int[] o = new int[matrix.length]; int[] rotationMatrix = ROTATION_MATRICES[rotation]; @@ -43,12 +52,34 @@ public class PlacementMatrixUtils { return o; } - public static int[] getPlacements() { - int[] placements = { - 2,0,8, - 1,3,4, - 7,6,5 - }; + public static int[] getPlacements(ItemStack itemStack, ServerWorld world) { +// int[] placements = { +// 2,0,8, +// 1,3,4, +// 7,6,5 +// }; +// return placements; + + + return getPlacementOrder(itemStack, world).getOrderMatrix(); + } + + public static PlacementOrder getPlacementOrder(ItemStack itemStack, ServerWorld world) { + Identifier placementIdentifier = itemStack.get(Grafting.PLACEMENT_ORDER_COMPONENT_TYPE); + + if(placementIdentifier == null) { + placementIdentifier = PlacementOrder.DEFAULT_ID; + } + + PlacementOrder placementOrder = world.getRegistryManager().get(Grafting.PLACEMENT_ORDER_REGISTRY_KEY).get(placementIdentifier); + if(placementOrder == null) { + return PlacementOrder.DEFAULT_FALLBACK; + } + + return placementOrder; + }; + + public static int[] asIndexes(int[] placements) { int[] out = new int[9]; for(int i = 0; i < 9; i++) { diff --git a/src/main/java/net/sys42/dakedres/grafting/block/Workbench.java b/src/main/java/net/sys42/dakedres/grafting/block/Workbench.java new file mode 100644 index 0000000..8d284c1 --- /dev/null +++ b/src/main/java/net/sys42/dakedres/grafting/block/Workbench.java @@ -0,0 +1,92 @@ +package net.sys42.dakedres.grafting.block; + +import com.mojang.serialization.MapCodec; +import eu.pb4.polymer.blocks.api.BlockModelType; +import eu.pb4.polymer.blocks.api.PolymerBlockModel; +import eu.pb4.polymer.blocks.api.PolymerBlockResourceUtils; +import eu.pb4.polymer.blocks.api.PolymerTexturedBlock; +import eu.pb4.polymer.core.api.block.PolymerBlock; +import net.minecraft.block.*; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.fluid.FluidState; +import net.minecraft.fluid.Fluids; + +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.Property; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Identifier; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.WorldAccess; +import net.sys42.dakedres.grafting.Grafting; +import net.sys42.dakedres.grafting.block.entity.WorkbenchBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static net.minecraft.state.property.Properties.WATERLOGGED; + +// TODO: Loot tables, including dropping contents when broken +public class Workbench extends BlockWithEntity implements PolymerBlock, Waterloggable, PolymerTexturedBlock { + private final BlockState waterloggedModel; + private final BlockState model; + + public Workbench(Settings settings) { + super(settings); + this.setDefaultState(this.getDefaultState().with(WATERLOGGED, false)); + + var model = PolymerBlockModel.of(Identifier.of(Grafting.MOD_ID, "block/workbench")); + this.model = PolymerBlockResourceUtils.requestBlock(BlockModelType.TRANSPARENT_BLOCK, model); + waterloggedModel = PolymerBlockResourceUtils.requestBlock(BlockModelType.TRANSPARENT_BLOCK_WATERLOGGED, model); + } + + @Override + protected MapCodec getCodec() { + return createCodec(Workbench::new); + } + + @Override + public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new WorkbenchBlockEntity(pos, state); + } + + @Override + public BlockState getPolymerBlockState(BlockState state) { + return state.get(WATERLOGGED) ? waterloggedModel : model; + } + + // Just so it plays the right break sound + @Override + public BlockState getPolymerBreakEventBlockState(BlockState state, ServerPlayerEntity player) { + return Blocks.CRAFTING_TABLE.getDefaultState(); + } + + @Nullable + public BlockState getPlacementState(@NotNull ItemPlacementContext ctx) { + WorldAccess worldAccess = ctx.getWorld(); + BlockPos blockPos = ctx.getBlockPos(); + return this.getDefaultState().with(WATERLOGGED, worldAccess.getFluidState(blockPos).getFluid() == Fluids.WATER); + } + + protected FluidState getFluidState(BlockState state) { + return state.get(WATERLOGGED) ? Fluids.WATER.getStill(false) : super.getFluidState(state); + } + + protected void appendProperties(StateManager.Builder builder) { + builder.add(new Property[]{WATERLOGGED}); + } + + @Override + protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) { + if(!player.isSneaking() && world.getBlockEntity(pos) instanceof WorkbenchBlockEntity blockEntity) { +// ((ServerPlayerEntity) player).openHandledScreen(blockEntity); + blockEntity.openMenu((ServerPlayerEntity) player); + return ActionResult.SUCCESS; + } + + return super.onUse(state, world, pos, player, hit); + } +} diff --git a/src/main/java/net/sys42/dakedres/grafting/block/entity/AttachedInventory.java b/src/main/java/net/sys42/dakedres/grafting/block/entity/AttachedInventory.java new file mode 100644 index 0000000..c29e9f2 --- /dev/null +++ b/src/main/java/net/sys42/dakedres/grafting/block/entity/AttachedInventory.java @@ -0,0 +1,43 @@ +package net.sys42.dakedres.grafting.block.entity; + +import eu.pb4.sgui.api.gui.SimpleGui; +import net.minecraft.inventory.Inventory; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.screen.slot.Slot; + +public class AttachedInventory { + private Inventory inventory; + public AttachedInventory(Inventory inventory) { + this.inventory = inventory; + } + + public static boolean valid(Inventory inventory) { + return inventory.size() >= 9 && (inventory.size() % 9) == 0; + } + + public int rows() { + return Math.min(inventory.size() / 9, 2); + } + + public ScreenHandlerType extendedScreenHandlerType(int basis) { + int r = rows() + basis; + + if(r <= 2) { + return ScreenHandlerType.GENERIC_9X3; + } else if(r == 3) { + return ScreenHandlerType.GENERIC_9X4; + } else if(r == 4) { + return ScreenHandlerType.GENERIC_9X5; + } else { + return ScreenHandlerType.GENERIC_9X6; + } + } + + public void redirectSlots(int basis, SimpleGui gui) { + int sizeBefore = (basis + 1) * 9; + for(int i = 0; i < rows() * 9; i++) { + gui.setSlotRedirect(sizeBefore + i, new Slot(inventory, i, i, 0)); + } + } +} diff --git a/src/main/java/net/sys42/dakedres/grafting/block/entity/OrderedContainer.java b/src/main/java/net/sys42/dakedres/grafting/block/entity/OrderedContainer.java new file mode 100644 index 0000000..a96d3d3 --- /dev/null +++ b/src/main/java/net/sys42/dakedres/grafting/block/entity/OrderedContainer.java @@ -0,0 +1,215 @@ +package net.sys42.dakedres.grafting.block.entity; + +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtList; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.util.math.BlockPos; + +import java.util.ArrayList; +import java.util.List; + +public abstract class OrderedContainer extends BlockEntity implements Inventory { + private final int size; + private final ArrayList inventory; + public OrderedContainer(BlockEntityType type, BlockPos pos, BlockState state, int size) { + super(type, pos, state); + this.size = size; + this.inventory = new ArrayList<>(size); + } + + protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { + super.readNbt(nbt, registries); + readInventoryNbt(nbt, registries); + } + + private void readInventoryNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { + NbtList nbtList = nbt.getList("Items", 10); + + for(int i = 0; i < nbtList.size(); ++i) { + NbtCompound nbtCompound = nbtList.getCompound(i); + inventory.add((ItemStack) ItemStack.fromNbt(registries, nbtCompound).orElse(ItemStack.EMPTY)); + } + } + + protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { + super.writeNbt(nbt, registries); + writeInventoryNbt(nbt, registries); + } + + private void writeInventoryNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { + NbtList nbtList = new NbtList(); + + for (ItemStack itemStack : inventory) { + if (!itemStack.isEmpty()) { + NbtCompound nbtCompound = new NbtCompound(); + nbtList.add(itemStack.encode(registries, nbtCompound)); + } + } + + nbt.put("Items", nbtList); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return inventory.isEmpty(); + } + + public int filledSlots() { + return inventory.size(); + } + + public boolean hasRoom() { + return filledSlots() < size; + } + + private boolean validSlot(int slot) { + return slot >= 0 && slot < filledSlots(); + } + + @Override + public ItemStack getStack(int slot) { + if(validSlot(slot)) { + return inventory.get(slot); + } else { + return ItemStack.EMPTY; + } + } + + // Sometimes Inventory#removeStack will be called in response to the + // count being 0, but not always. Here we check if that is the case + // before pulling the newest item. + // + // This is undesired behavior (as it allows the user to remove items + // that are not at the top of the stack), and should be avoided. This + // method prevents the side effects from being even worse, however. + private ItemStack checkSlot(int slot) { + if(!validSlot(slot)) { + return ItemStack.EMPTY; + } + + ItemStack stack = this.getStack(slot); + + if(stack.isEmpty()) { + inventory.remove(slot); + this.markDirty(); + return stack; + } + + return stack; + } + + @Override + public ItemStack removeStack(int slot, int amount) { + ItemStack stack = checkSlot(slot); + + if(stack.isEmpty() || amount <= 0) { + return stack; + } + + if(amount <= stack.getCount()) { + return stack.split(amount); + } + + return ItemStack.EMPTY; + } + + @Override + public ItemStack removeStack(int slot) { + ItemStack stack = checkSlot(slot); + + if(stack.isEmpty()) { + return stack; + } + + return pullStack(); + } + + @Override + public void setStack(int slot, ItemStack stack) { + if(stack.isEmpty()) { + if(validSlot(slot) && this.getStack(slot).isEmpty()) { + inventory.remove(slot); + this.markDirty(); + } + return; + } + + if(slot >= this.filledSlots()) { + pushStack(stack); + } else { + this.inventory.set(slot, stack); + } + + markDirty(); + } + + public void pushStack(ItemStack stack) { + if(stack.isEmpty()) { + return; + } + + if(hasRoom()) { + inventory.add(stack); + markDirty(); + } + } + + public ItemStack pullStack() { + if(this.filledSlots() > 0) { + ItemStack o = inventory.removeLast(); + markDirty(); + return o; + } else { + return ItemStack.EMPTY; + } + } + + public boolean hasItemsLike(ItemStack stack) { + for(ItemStack itemStack : inventory) { + if(ItemStack.areItemsAndComponentsEqual(itemStack, stack)) { + return true; + } + } + + return false; + } + + @Override + public void clear() { + inventory.clear(); + } + + public List getHeldStacks() { + return inventory; + } + + private void clean() { + for(int i = 0; i < inventory.size(); i++) { + if(inventory.get(i).isEmpty()) { + inventory.remove(i); + i--; + } + } + } + + //TODO: Implement according to LockedContainer + @Override + public void markDirty() { + clean(); + super.markDirty(); + } + + public ItemStack topStack() { + return this.inventory.getLast(); + } +} diff --git a/src/main/java/net/sys42/dakedres/grafting/block/entity/WorkbenchBlockEntity.java b/src/main/java/net/sys42/dakedres/grafting/block/entity/WorkbenchBlockEntity.java new file mode 100644 index 0000000..20a1fa4 --- /dev/null +++ b/src/main/java/net/sys42/dakedres/grafting/block/entity/WorkbenchBlockEntity.java @@ -0,0 +1,400 @@ +package net.sys42.dakedres.grafting.block.entity; + +import eu.pb4.polymer.resourcepack.api.PolymerModelData; +import eu.pb4.sgui.api.ClickType; +import eu.pb4.sgui.api.elements.GuiElement; +import eu.pb4.sgui.api.gui.SimpleGui; +import eu.pb4.sgui.virtual.inventory.VirtualSlot; +import net.minecraft.block.BlockState; +import net.minecraft.block.CrafterBlock; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.CraftingResultInventory; +import net.minecraft.inventory.Inventory; +import net.minecraft.inventory.RecipeInputInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.recipe.RecipeMatcher; +import net.minecraft.recipe.input.CraftingRecipeInput; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.screen.slot.CraftingResultSlot; +import net.minecraft.screen.slot.Slot; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.sys42.dakedres.grafting.Grafting; +import net.sys42.dakedres.grafting.PlacementUtils; +import net.sys42.dakedres.grafting.ui.Icons; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class WorkbenchBlockEntity extends OrderedContainer { + public static final String NBT_ORIENTATION_KEY = "CraftingOrientation"; + public static final int GRID_AREA = 9; + public static final int GRID_SIZE = 3; + + private int craftingOrientation = 0; + private final OrderedRecipeInventory recipeInventory = new OrderedRecipeInventory(); + private final ArrayList guis = new ArrayList<>(); + public WorkbenchBlockEntity(BlockPos blockPos, BlockState blockState) { + super(Grafting.WORKBENCH_BLOCK_ENTITY_TYPE, blockPos, blockState, GRID_AREA); + } + + @Override + public boolean canPlayerUse(PlayerEntity player) { + return false; + } + +// @Override +// public Text getDisplayName() { +// // TODO: Localize +// return Text.of("ASDF??"); +// } +// +// @Override +// public @Nullable ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) { +// return new Gui((ServerPlayerEntity) player) +// .openAsScreenHandler(syncId, playerInventory, player); +// } + + public void openMenu(ServerPlayerEntity player) { + Optional attachedInventory = Optional.empty(); + + for(BlockPos p : new BlockPos[]{ + pos.north(1), + pos.east(1), + pos.south(1), + pos.west(1) + }) { + assert world != null; + BlockEntity be = world.getBlockEntity(p); + if( + !(be instanceof WorkbenchBlockEntity) && // It gets annoying + be instanceof Inventory && + AttachedInventory.valid((Inventory) be) + ) { + attachedInventory = Optional.of(new AttachedInventory((Inventory) be)); + } + } + + new Gui(player, attachedInventory).open(); + } + + @Override + public void markDirty() { + for(Gui gui : guis) { + gui.updateResult(); + } + super.markDirty(); + } + + @Override + protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { + super.writeNbt(nbt, registries); + nbt.putByte(NBT_ORIENTATION_KEY, (byte) craftingOrientation); + } + + @Override + protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { + super.readNbt(nbt, registries); + craftingOrientation = nbt.getByte(NBT_ORIENTATION_KEY); + } + + public ItemStack getResult(ServerPlayerEntity player) { + World world = player.getWorld(); + CraftingRecipeInput craftingRecipeInput = recipeInventory.createRecipeInput(); + return CrafterBlock.getCraftingRecipe(world, craftingRecipeInput) + .map(recipe -> recipe.value() + .craft(craftingRecipeInput, world.getRegistryManager()) + ) + .orElse(ItemStack.EMPTY); + } + + public void rotate(int amount) { + craftingOrientation = PlacementUtils.clampRotation(craftingOrientation + amount); + markDirty(); + } + + public class OrderedRecipeInventory implements RecipeInputInventory { + public int[] getRotatedPlacementIndexes(int slot) { + return PlacementUtils.asIndexes( + PlacementUtils.rotate( + PlacementUtils.getPlacements(WorkbenchBlockEntity.this.getStack(slot), (ServerWorld) world), + craftingOrientation + ) + ); + } + + @Override + public List getHeldStacks() { + DefaultedList stacks = DefaultedList.ofSize(size(), ItemStack.EMPTY); + + for(int i = 0; i < filledSlots(); i++) { + int[] placements = getRotatedPlacementIndexes(i); + + for (int slot : placements) { + if(stacks.get(slot).isEmpty()) { + stacks.set(slot, WorkbenchBlockEntity.this.getStack(i)); + break; + } + } + } + + return stacks; + } + + private int getOrderedSlot(int positionalSlot) { + boolean[] filledSlotMatrix = new boolean[size()]; + Arrays.fill(filledSlotMatrix, false); + + for(int i = 0; i <= filledSlots(); i++) { + int[] placements = getRotatedPlacementIndexes(i); + + for (int slot : placements) { + if (slot == positionalSlot) { + return i; + } + + if (!filledSlotMatrix[slot]) { + filledSlotMatrix[slot] = true; + break; + } + } + } + + return -1; + } + + @Override + public int getWidth() { + return GRID_SIZE; + } + + @Override + public int getHeight() { + return GRID_SIZE; + } + + @Override + public int size() { + return WorkbenchBlockEntity.this.size(); + } + + @Override + public boolean isEmpty() { + return WorkbenchBlockEntity.this.isEmpty(); + } + + @Override + public ItemStack getStack(int slot) { + return WorkbenchBlockEntity.this.getStack(getOrderedSlot(slot)); + } + + @Override + public ItemStack removeStack(int slot, int amount) { + return WorkbenchBlockEntity.this.removeStack(getOrderedSlot(slot), amount); + } + + @Override + public ItemStack removeStack(int slot) { + return WorkbenchBlockEntity.this.removeStack(getOrderedSlot(slot)); + } + + @Override + public void setStack(int slot, ItemStack stack) { + int orderedSlot = getOrderedSlot(slot); + if(orderedSlot == -1) { + pushStack(stack); + } else { + WorkbenchBlockEntity.this.setStack(orderedSlot, stack); + } + } + + @Override + public void markDirty() { + WorkbenchBlockEntity.this.markDirty(); + } + + @Override + public boolean canPlayerUse(PlayerEntity player) { + return WorkbenchBlockEntity.this.canPlayerUse(player); + } + + @Override + public void provideRecipeInputs(RecipeMatcher finder) { + for(ItemStack itemStack : this.getHeldStacks()) { + finder.addUnenchantedInput(itemStack); + } + } + + @Override + public void clear() { + WorkbenchBlockEntity.this.clear(); + } + } + + public class Gui extends SimpleGui { + private static final int BUTTON_COLUMN = 1; + private static final int GRID_START_COLUMN = BUTTON_COLUMN + 1; + private static final int RESULT_COLUMN = GRID_START_COLUMN + GRID_SIZE + 2; + private static final int BASE_HEIGHT = 3; + + private static final Identifier GUI_FONT_ID = Identifier.of(Grafting.MOD_ID, "workbench_gui"); + + private final CraftingResultInventory resultInventory = new CraftingResultInventory(); + private boolean attachedInventory; + public Gui(ServerPlayerEntity player) { + this(player, Optional.empty()); + } + + public Gui(ServerPlayerEntity player, Optional attachedInventory) { + super( + attachedInventory.isEmpty() ? + ScreenHandlerType.GENERIC_9X3 : + attachedInventory.get().extendedScreenHandlerType(BASE_HEIGHT), + player, + false + ); + guis.add(this); + + setTitle( + Text.empty() + .append(Text.literal(attachedInventory.isEmpty() ? "-0." : "-1.").setStyle(Style.EMPTY.withFont(GUI_FONT_ID).withColor(Formatting.WHITE))) + .append(Text.translatable("container.crafting")) + ); + + setSlot(BUTTON_COLUMN, new RotateGridButton(Icons.ROTATE_CLOCKWISE,1)); + setSlot(BUTTON_COLUMN + 9, new RotateGridButton(Icons.ROTATE_COUNTER_CLOCKWISE, -1)); + setSlotRedirect( + RESULT_COLUMN + 9, + new CraftingResultGridSlot(player, recipeInventory, resultInventory, 0, 0, 0) + ); + for(int i = 0; i < GRID_AREA; i++) { + int row = (i / GRID_SIZE); + setSlotRedirect( + GRID_START_COLUMN + (i % GRID_SIZE) + (row * 9), + new GridSlot(i) + ); + } + + attachedInventory.ifPresent(inventory -> { + inventory.redirectSlots(BASE_HEIGHT, this); + }); + + updateResult(); + } + + @Override + public void close() { + guis.remove(this); + super.close(); + } + + protected void updateResult() { + resultInventory.setStack(0, getResult(player)); + } + + @Override + public ItemStack quickMove(int index) { + ItemStack returnStack = ItemStack.EMPTY; + Slot slot = this.getSlotRedirectOrPlayer(index); + + if(slot != null && slot.hasStack() && !(slot instanceof VirtualSlot)) { + ItemStack operatingStack; + + if(slot instanceof GridSlot) { + operatingStack = topStack(); + } else { + operatingStack = slot.getStack(); + } + returnStack = operatingStack.copy(); + + if(index < this.getVirtualSize()) { + if(!this.insertItem(operatingStack, this.getVirtualSize(), this.getVirtualSize() + 36, true)) { + return ItemStack.EMPTY; + } + } else if (!this.insertItem(operatingStack, 0, this.getVirtualSize(), false)) { + return ItemStack.EMPTY; + } + + if (operatingStack.isEmpty()) { + slot.setStack(ItemStack.EMPTY); + } else { + slot.markDirty(); + } + } else if (slot instanceof VirtualSlot) { + return slot.getStack(); + } + + return returnStack; + } + + // - Inserting single items changes the shape + // - Inserting a full stack will attempt to distribute the stack, or insert if that stack isn't in the workbench + public class GridSlot extends Slot { + public GridSlot(int index) { + super(WorkbenchBlockEntity.this.recipeInventory, index, index, 0); + } + +// @Override +// public ItemStack insertStack(ItemStack stack, int count) { +// +// if(stack.isEmpty()) { +// return stack; +// } +// +// int allowedCount = Math.min(count, stack.getCount()); +// if( +// (!hasItemsLike(stack) || count == 1) && +// WorkbenchBlockEntity.this.hasRoom() +// ) { +// pushStack(stack.split(allowedCount)); +// } +// +// return stack; +// } + } + + public class CraftingResultGridSlot extends CraftingResultSlot { + public CraftingResultGridSlot(PlayerEntity player, RecipeInputInventory input, Inventory inventory, int index, int x, int y) { + super(player, input, inventory, index, x, y); + } + + @Override + public void onTakeItem(PlayerEntity player, ItemStack stack) { + super.onTakeItem(player, stack); + WorkbenchBlockEntity.this.markDirty(); // Make sure the OrderedContainer gets cleaned so that everything is in its rightful place + } + } + + public class RotateGridButton extends GuiElement { + public RotateGridButton(PolymerModelData model, int bias) { + super( + Icons.getStack(model), + new ItemClickCallback() { + @Override + public void click(int i, ClickType clickType, SlotActionType slotActionType) { + switch(clickType) { + case ClickType.MOUSE_LEFT -> rotate(bias); + case ClickType.MOUSE_RIGHT -> rotate(bias * -1); + } + } + } + ); + } + } + } +} diff --git a/src/main/java/net/sys42/dakedres/grafting/blocks/Workbench.java b/src/main/java/net/sys42/dakedres/grafting/blocks/Workbench.java deleted file mode 100644 index fd5487e..0000000 --- a/src/main/java/net/sys42/dakedres/grafting/blocks/Workbench.java +++ /dev/null @@ -1,67 +0,0 @@ -package net.sys42.dakedres.grafting.blocks; - -import com.mojang.serialization.MapCodec; -import eu.pb4.polymer.core.api.block.PolymerBlock; -import eu.pb4.polymer.core.api.item.PolymerItem; -import net.minecraft.block.Block; -import net.minecraft.block.BlockState; -import net.minecraft.block.BlockWithEntity; -import net.minecraft.block.Blocks; -import net.minecraft.block.entity.BlockEntity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.util.ActionResult; -import net.minecraft.util.collection.DefaultedList; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; -import net.sys42.dakedres.grafting.Grafting; -import org.jetbrains.annotations.Nullable; - -public class Workbench extends BlockWithEntity implements PolymerBlock { - private final DefaultedList stacks = DefaultedList.ofSize(9, ItemStack.EMPTY); - - public Workbench(Settings settings) { - super(settings); - } - - @Override - protected MapCodec getCodec() { - return createCodec(Workbench::new); - } - - @Override - public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) { - return new WorkbenchBlockEntity(pos, state); - } - - @Override - public BlockState getPolymerBlockState(BlockState blockState) { - return Blocks.CRAFTING_TABLE.getDefaultState(); - } - - @Override - protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) { - if(!player.isSneaking() && world.getBlockEntity(pos) instanceof WorkbenchBlockEntity blockEntity) { - blockEntity.openMenu((ServerPlayerEntity) player); - return ActionResult.SUCCESS; - } - - return super.onUse(state, world, pos, player, hit); - } - - public static final class WorkbenchItem extends BlockItem implements PolymerItem { - public WorkbenchItem(Block block, Settings settings) { - super(block, settings); - } - - @Override - public Item getPolymerItem(ItemStack itemStack, @Nullable ServerPlayerEntity serverPlayerEntity) { - return Items.CRAFTING_TABLE; - } - } -} diff --git a/src/main/java/net/sys42/dakedres/grafting/blocks/WorkbenchBlockEntity.java b/src/main/java/net/sys42/dakedres/grafting/blocks/WorkbenchBlockEntity.java deleted file mode 100644 index c4281b7..0000000 --- a/src/main/java/net/sys42/dakedres/grafting/blocks/WorkbenchBlockEntity.java +++ /dev/null @@ -1,404 +0,0 @@ -package net.sys42.dakedres.grafting.blocks; - -import eu.pb4.polymer.resourcepack.api.PolymerModelData; -import eu.pb4.sgui.api.ClickType; -import eu.pb4.sgui.api.elements.GuiElement; -import eu.pb4.sgui.api.elements.GuiElementInterface; -import eu.pb4.sgui.api.gui.SimpleGui; -import eu.pb4.sgui.virtual.inventory.VirtualSlot; -import net.minecraft.block.BlockState; -import net.minecraft.block.CrafterBlock; -import net.minecraft.block.entity.LockableContainerBlockEntity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.entity.player.PlayerInventory; -import net.minecraft.inventory.CraftingResultInventory; -import net.minecraft.inventory.Inventories; -import net.minecraft.inventory.Inventory; -import net.minecraft.inventory.RecipeInputInventory; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.recipe.RecipeMatcher; -import net.minecraft.recipe.input.CraftingRecipeInput; -import net.minecraft.registry.RegistryWrapper; -import net.minecraft.screen.NamedScreenHandlerFactory; -import net.minecraft.screen.ScreenHandler; -import net.minecraft.screen.ScreenHandlerType; -import net.minecraft.screen.slot.CraftingResultSlot; -import net.minecraft.screen.slot.Slot; -import net.minecraft.screen.slot.SlotActionType; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.Text; -import net.minecraft.util.collection.DefaultedList; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; -import net.sys42.dakedres.grafting.Grafting; -import net.sys42.dakedres.grafting.PlacementMatrixUtils; -import net.sys42.dakedres.grafting.ui.Icons; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; - -public class WorkbenchBlockEntity extends LockableContainerBlockEntity implements RecipeInputInventory { - - public static final String NBT_GRID_ROTATION_KEY = "GridRotation"; - public static final int GRID_SIZE = 9; - public static final int GRID_WIDTH = 3; - - private DefaultedList grid = DefaultedList.ofSize(GRID_SIZE, ItemStack.EMPTY); - private int gridRotation = 0; - private final ArrayList guis = new ArrayList<>(); - public WorkbenchBlockEntity(BlockPos blockPos, BlockState blockState) { - super(Grafting.WORKBENCH_BLOCK_ENTITY_TYPE, blockPos, blockState); - } - - public void openMenu(ServerPlayerEntity playerEntity) { - playerEntity.openHandledScreen(new NamedScreenHandlerFactory() { - @Override - public Text getDisplayName() { - return WorkbenchBlockEntity.this.getDisplayName(); - } - - @Override - public @Nullable ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) { - return WorkbenchBlockEntity.this.createScreenHandler(syncId, playerInventory); - } - }); - } - - @Override - protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) { - super.readNbt(nbt, registryLookup); - this.gridRotation = nbt.getByte(NBT_GRID_ROTATION_KEY); - this.grid = DefaultedList.ofSize(this.size(), ItemStack.EMPTY); - } - - @Override - protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) { - super.writeNbt(nbt, registryLookup); - nbt.putByte(NBT_GRID_ROTATION_KEY, (byte) gridRotation); - Inventories.writeNbt(nbt, this.grid, registryLookup); - } - - @Override - protected Text getContainerName() { - // TODO: localize - return Text.of("Workbench"); - } - - @Override - public int getWidth() { - return GRID_WIDTH; - } - - @Override - public int getHeight() { - return GRID_WIDTH; - } - - @Override - public DefaultedList getHeldStacks() { - return this.grid; - } - - @Override - protected void setHeldStacks(DefaultedList inventory) { - this.grid = inventory; - } - - @Override - protected ScreenHandler createScreenHandler(int syncId, PlayerInventory playerInventory) { - return createScreenHandler(syncId, playerInventory, playerInventory.player); - } - - protected ScreenHandler createScreenHandler(int syncId, PlayerInventory playerInventory, PlayerEntity player) { - return new Gui((ServerPlayerEntity) player).openAsScreenHandler(syncId, playerInventory, player); - } - - @Override - public int size() { - return GRID_SIZE; - } - - public boolean hasEmptySlots() { - for(ItemStack itemStack : grid) { - if (itemStack.isEmpty()) { - return true; - } - } - - return false; - } - - private int[] getPlacementMatrix() { - return PlacementMatrixUtils.rotate(PlacementMatrixUtils.getPlacements(), gridRotation); - } - - public void placeStack(ItemStack stack) { - int[] placements = getPlacementMatrix(); - - for(int i = 0; i < GRID_SIZE; i++) { - int p = placements[i]; - if(getStack(p).isEmpty()) { - setStack(p, stack); - distributeStack(stack, 0); - return; - } - } - } - - public void distributeStack(ItemStack stack) { - distributeStack(stack, stack.getCount()); - } - - public void distributeStack(ItemStack stack, int count) { - ArrayList candidates = new ArrayList<>(); - int totalCount = count; - int maxTotalCount = 0; - int[] placements = getPlacementMatrix(); - - for(int i = 0; i < GRID_SIZE; i++) { - int p = placements[i]; - ItemStack ps = getStack(p); - if(!ps.isEmpty() && ItemStack.areItemsAndComponentsEqual(ps, stack)) { - totalCount += ps.getCount(); - maxTotalCount += ps.getMaxCount(); - ps.setCount(0); - candidates.add(ps); - } - } - - int remainder = 0; - if(totalCount > maxTotalCount) { - remainder = totalCount - maxTotalCount; - totalCount = maxTotalCount; - } - int i = 0; - while(totalCount > 0) { - candidates.get(i % candidates.size()).increment(1); - totalCount--; - i++; - } - - stack.decrement(count - remainder); - markDirty(); - } - - protected int lastPriorityIndexAlike(ItemStack stack) { - int[] placements = getPlacementMatrix(); - - for(int i = placements.length - 1; i >= 0; i--) { - int p = placements[i]; - ItemStack ps = getStack(p); - if(ItemStack.areItemsAndComponentsEqual(ps, stack)) { - return p; - } - } - - return -1; - } - - public ItemStack removeStack(ItemStack stack, int amount) { - int index = lastPriorityIndexAlike(stack); - - if(index >= 0) { - ItemStack o = removeStack(index, amount); - distributeStack(o, 0); - return o; - } - - return ItemStack.EMPTY; - } - - public boolean hasItemsLike(ItemStack stack) { - for(ItemStack itemStack : grid) { - if(ItemStack.areItemsAndComponentsEqual(itemStack, stack)) { - return true; - } - } - - return false; - } - -// Based on CrafterScreenHandler#updateResult - public ItemStack getResult(ServerPlayerEntity player) { - World world = player.getWorld(); - CraftingRecipeInput craftingRecipeInput = createRecipeInput(); - return CrafterBlock.getCraftingRecipe(world, craftingRecipeInput) - .map(recipe -> recipe.value() - .craft(craftingRecipeInput, world.getRegistryManager()) - ) - .orElse(ItemStack.EMPTY); - } - - @Override - public void markDirty() { - for(Gui gui : guis) { - gui.updateResult(); - } - super.markDirty(); - } - - @Override - public void provideRecipeInputs(RecipeMatcher finder) { - - } - - // TODO: Reimplement. Need to prevent any possible desync between the orientation as manipulated here, and the one in getPlacements - public void rotate(int direction) { - gridRotation = PlacementMatrixUtils.clampRotation(gridRotation + direction); - int[] rotationMatrix = PlacementMatrixUtils.ROTATION_MATRICES[PlacementMatrixUtils.clampRotation(direction)]; - DefaultedList newGrid = DefaultedList.ofSize(grid.size(), ItemStack.EMPTY); - - for(int i = 0; i < grid.size(); i++) { - newGrid.set(rotationMatrix[i], grid.get(i)); - } - - setHeldStacks(newGrid); - markDirty(); - } - - public class Gui extends SimpleGui { - private static final int BUTTONS_LEFT = 1; - private static final int GRID_LEFT = BUTTONS_LEFT + 1; - private static final int RESULT_LEFT = GRID_LEFT + GRID_WIDTH + 2; - - private final CraftingResultInventory resultInventory = new CraftingResultInventory(); - public Gui(ServerPlayerEntity player) { - super(ScreenHandlerType.GENERIC_9X3, player, false); - guis.add(this); - - this.setTitle(WorkbenchBlockEntity.this.getDisplayName()); - setSlot(BUTTONS_LEFT, new RotateGridButton(Icons.ROTATE_CLOCKWISE,1)); - setSlot(BUTTONS_LEFT + 9, new RotateGridButton(Icons.ROTATE_COUNTER_CLOCKWISE, -1)); - setSlotRedirect( - RESULT_LEFT + 9, - new CraftingResultSlot(player, WorkbenchBlockEntity.this, resultInventory, 0, 0, 0) - ); - for(int i = 0; i < GRID_SIZE; i++) { - int row = (i / GRID_WIDTH); - setSlotRedirect( - GRID_LEFT + (i % GRID_WIDTH) + (row * 9), - new GridSlot(i) - ); - } - - updateResult(); - } - - @Override - public void close() { - guis.remove(this); - super.close(); - } - - protected void updateResult() { - resultInventory.setStack(0, getResult(player)); - } - - /* - According to ScreenHandler#internalOnSlotClick, quickMove is continually called - while it returns a non-empty ItemStack that matches the original slot - */ - @Override - public ItemStack quickMove(int index) { - ItemStack returnStack = ItemStack.EMPTY; - Slot slot = this.getSlotRedirectOrPlayer(index); - - if(slot != null && slot.hasStack() && !(slot instanceof VirtualSlot)) { - ItemStack operatingStack = slot.getStack(); - returnStack = operatingStack.copy(); - - if(index < this.getVirtualSize()) { - int i = -1; - - if(slot instanceof GridSlot) { - i = lastPriorityIndexAlike(operatingStack); - operatingStack = i == -1 ? ItemStack.EMPTY : removeStack(i); - - if (!this.insertItem(operatingStack, this.getVirtualSize(), this.getVirtualSize() + 36, true)) { - return ItemStack.EMPTY; - } - } - - if (!this.insertItem(operatingStack, this.getVirtualSize(), this.getVirtualSize() + 36, true)) { - return ItemStack.EMPTY; - } - - if(slot.getIndex() == i) { - return ItemStack.EMPTY; - } - } else { - if(hasItemsLike(operatingStack)) { - distributeStack(operatingStack); - return ItemStack.EMPTY; - } else if(hasEmptySlots()) { - placeStack(operatingStack); - slot.setStack(ItemStack.EMPTY); - } else { - return ItemStack.EMPTY; - } - } - - if (operatingStack.isEmpty()) { - slot.setStack(ItemStack.EMPTY); - } else { - slot.markDirty(); - } - } else if (slot instanceof VirtualSlot) { - return slot.getStack(); - } - - return returnStack; - } - - // - Inserting single items changes the shape - // - Inserting a full stack will attempt to distribute the stack, or insert if that stack isn't in the workbench - public class GridSlot extends Slot { - public GridSlot(int index) { - super(WorkbenchBlockEntity.this, index, index, 0); - } - - @Override - public ItemStack insertStack(ItemStack stack, int count) { - - if(stack.isEmpty()) { - return stack; - } - - int allowedCount = Math.min(count, stack.getCount()); - if( - (!hasItemsLike(stack) || count == 1) && - WorkbenchBlockEntity.this.hasEmptySlots() - ) { - placeStack(stack.split(allowedCount)); - } else { - distributeStack(stack, allowedCount); - } - - return stack; - } - - @Override - public ItemStack takeStack(int amount) { - return removeStack(this.getStack(), amount); - } - } - - public class RotateGridButton extends GuiElement { - public RotateGridButton(PolymerModelData model, int bias) { - super( - Icons.getStack(model), - new GuiElementInterface.ItemClickCallback() { - @Override - public void click(int i, ClickType clickType, SlotActionType slotActionType) { - switch(clickType) { - case ClickType.MOUSE_LEFT -> rotate(bias); - case ClickType.MOUSE_RIGHT -> rotate(bias * -1); - } - } - } - ); - } - } - } -} diff --git a/src/main/java/net/sys42/dakedres/grafting/data/PlacementOrder.java b/src/main/java/net/sys42/dakedres/grafting/data/PlacementOrder.java new file mode 100644 index 0000000..9e03644 --- /dev/null +++ b/src/main/java/net/sys42/dakedres/grafting/data/PlacementOrder.java @@ -0,0 +1,66 @@ +package net.sys42.dakedres.grafting.data; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.codecs.PrimitiveCodec; +import net.minecraft.util.Identifier; +import net.sys42.dakedres.grafting.Grafting; + +import java.util.stream.IntStream; + +public class PlacementOrder { + public static final PlacementOrder DEFAULT_FALLBACK = new PlacementOrder(new int[]{ + 0,1,2, + 3,4,5, + 6,7,8 + }); + public static final Identifier DEFAULT_ID = Identifier.of(Grafting.MOD_ID, "default"); + + int[] orderMatrix; + public PlacementOrder(int[] orderMatrix) { + this.orderMatrix = orderMatrix; + } + + public int[] getOrderMatrix() { + return orderMatrix; + } + + public static final Codec CODEC = new PrimitiveCodec<>() { + @Override + public DataResult read(DynamicOps ops, T input) { + return ops.getIntStream(input).flatMap(intStream -> { + var om = intStream.toArray(); + try { + PlacementOrder.validate(om); + } catch (Exception e) { + return DataResult.error(e::getMessage); + } + + return DataResult.success(new PlacementOrder(om)); + }); + } + + @Override + public T write(DynamicOps ops, PlacementOrder value) { + return ops.createIntList(IntStream.of(value.getOrderMatrix())); + } + + @Override + public String toString() { + return "PlacementOrder"; + } + }; + + public static void validate(int[] orderMatrix) throws Exception { + if(orderMatrix.length != 9) { + throw new Exception("Not a complete matrix. Expected an array of at least 9 integers between 0 and 8."); + } + + for(int i = 0; i < 9; i++) { + if(orderMatrix[i] >= 9 || orderMatrix[i] < 0) { + throw new Exception(String.format("At index {}, expected integer between 8 and 0", i)); + } + } + } +} diff --git a/src/main/java/net/sys42/dakedres/grafting/data/PlacementOrderReloadListener.java b/src/main/java/net/sys42/dakedres/grafting/data/PlacementOrderReloadListener.java new file mode 100644 index 0000000..f504fb5 --- /dev/null +++ b/src/main/java/net/sys42/dakedres/grafting/data/PlacementOrderReloadListener.java @@ -0,0 +1,62 @@ +package net.sys42.dakedres.grafting.data; + +import com.google.gson.*; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.minecraft.registry.Registry; +import net.minecraft.resource.Resource; +import net.minecraft.util.Identifier; +import net.sys42.dakedres.grafting.Grafting; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.Map; + +public class PlacementOrderReloadListener { + static public void init() { + ServerLifecycleEvents.START_DATA_PACK_RELOAD.register((server, resourceManager) -> { + // Thank you tomalbrc! https://github.com/tomalbrc/filament/blob/main/src/main/java/de/tomalbrc/filament/util/FilamentSynchronousResourceReloadListener.java#L70 + + var resources = resourceManager.findResources( + "grafting/placement_order", + path -> path.getPath().endsWith(".json") + ); + + for(Map.Entry entry : resources.entrySet()) { + load(entry.getKey(), entry.getValue()); + } + +// Grafting.LOGGER.info("Loaded {} placement orders.", Grafting.PLACEMENT_ORDER_REGISTRY.getEntrySet().size()); + }); + } + + static private void load(Identifier id, Resource resource) { + JsonObject json; + + try(BufferedReader reader = resource.getReader()) { + load(id, reader); + } catch(IOException | IllegalStateException e) { + error(id, e); + return; + } + } + + static private void load(Identifier id, BufferedReader reader) { + Gson gson = new GsonBuilder().create(); + JsonElement rootElement = JsonParser.parseReader(reader); + + try { + int[] orderMatrix = gson.fromJson(rootElement, int[].class); + + PlacementOrder.validate(orderMatrix); + } catch(Exception e) { + error(id, e); + } + +// Grafting.PLACEMENT_ORDER_REGISTRY +// PlacementOrder.Data = gson.fromJson(rootElement, PlacementOrder.Data.class); + } + + static private void error(Identifier id, Exception e) { + Grafting.LOGGER.error("Could not load placement order \"{}\": {}", id, e); + } +} diff --git a/src/main/java/net/sys42/dakedres/grafting/item/WorkbenchItem.java b/src/main/java/net/sys42/dakedres/grafting/item/WorkbenchItem.java index aac7b92..d9a9903 100644 --- a/src/main/java/net/sys42/dakedres/grafting/item/WorkbenchItem.java +++ b/src/main/java/net/sys42/dakedres/grafting/item/WorkbenchItem.java @@ -1,36 +1,56 @@ package net.sys42.dakedres.grafting.item; import eu.pb4.polymer.core.api.item.PolymerItem; +import eu.pb4.polymer.resourcepack.api.PolymerModelData; +import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; import net.minecraft.block.Block; import net.minecraft.item.*; +import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket; +import net.minecraft.registry.Registries; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.sound.SoundCategory; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec3d; +import net.sys42.dakedres.grafting.Grafting; import org.jetbrains.annotations.Nullable; public class WorkbenchItem extends BlockItem implements PolymerItem { - public WorkbenchItem(Block block, Settings settings, String modelId) { + public final PolymerModelData model = PolymerResourcePackUtils.requestModel( + Items.BREEZE_ROD, + Identifier.of(Grafting.MOD_ID, "item/workbench") + ); + public WorkbenchItem(Block block, Settings settings) { super(block, settings); } @Override public Item getPolymerItem(ItemStack itemStack, @Nullable ServerPlayerEntity serverPlayerEntity) { - return Items.CRAFTING_TABLE; + return model.item(); + } + + @Override + public int getPolymerCustomModelData(ItemStack itemStack, @Nullable ServerPlayerEntity player) { + return model.value(); } // Thank you Mykhailo! // copied from: // https://github.com/MykhailoOpryshok/Borukva-Food/blob/master/src/main/java/com/opryshok/block/TexturedPolyBlockItem.java#L18 -// @Override -// public ActionResult useOnBlock(ItemUsageContext context) { -// var x = super.useOnBlock(context); -// if (x == ActionResult.CONSUME) { -// if (context.getPlayer() instanceof ServerPlayerEntity player) { -// var pos = Vec3d.ofCenter(context.getBlockPos().offset(context.getSide())); -// var blockSoundGroup = this.getBlock().getDefaultState().getSoundGroup(); -// player.networkHandler.sendPacket(new PlaySoundS2CPacket(Registries.SOUND_EVENT.getEntry(this.getPlaceSound(this.getBlock().getDefaultState())), SoundCategory.BLOCKS, pos.x, pos.y, pos.z, (blockSoundGroup.getVolume() + 1.0F) / 2.0F, blockSoundGroup.getPitch() * 0.8F, context.getPlayer().getRandom().nextLong())); -// } -// return ActionResult.SUCCESS; -// } -// return x; -// } + @Override + public ActionResult useOnBlock(ItemUsageContext context) { + var x = super.useOnBlock(context); + if (x == ActionResult.CONSUME) { + if (context.getPlayer() instanceof ServerPlayerEntity player) { + var pos = Vec3d.ofCenter(context.getBlockPos().offset(context.getSide())); + var blockSoundGroup = this.getBlock().getDefaultState().getSoundGroup(); + player.networkHandler.sendPacket( + new PlaySoundS2CPacket(Registries.SOUND_EVENT.getEntry(this.getPlaceSound(this.getBlock().getDefaultState())), SoundCategory.BLOCKS, pos.x, pos.y, pos.z, (blockSoundGroup.getVolume() + 1.0F) / 2.0F, blockSoundGroup.getPitch() * 0.8F, context.getPlayer().getRandom().nextLong()) + ); + } + return ActionResult.SUCCESS; + } + return x; + } } diff --git a/src/main/java/net/sys42/dakedres/grafting/ui/Icons.java b/src/main/java/net/sys42/dakedres/grafting/ui/Icons.java index 0936021..04bf79c 100644 --- a/src/main/java/net/sys42/dakedres/grafting/ui/Icons.java +++ b/src/main/java/net/sys42/dakedres/grafting/ui/Icons.java @@ -14,17 +14,24 @@ import net.sys42.dakedres.grafting.Grafting; public class Icons { public static final Item BASE_ITEM = Items.BREEZE_ROD; - public static final PolymerModelData ROTATE_CLOCKWISE = uiElementModel("icons/rotate_clockwise.png"); - public static final PolymerModelData ROTATE_COUNTER_CLOCKWISE = uiElementModel("icons/rotate_counter_clockwise.png"); + public static final PolymerModelData ROTATE_CLOCKWISE = uiElementModel("rotate_clockwise"); + public static final PolymerModelData ROTATE_COUNTER_CLOCKWISE = uiElementModel("rotate_counter_clockwise"); - private static PolymerModelData uiElementModel(String path) { - return PolymerResourcePackUtils.requestModel(BASE_ITEM, Identifier.of(Grafting.MOD_ID + path)); + private static PolymerModelData uiElementModel(String name) { + return PolymerResourcePackUtils.requestModel( + BASE_ITEM, + Identifier.of(Grafting.MOD_ID, "icon/" + name) + ); } public static ItemStack getStack(PolymerModelData model) { - ItemStack stack = Icons.ROTATE_CLOCKWISE.asStack(); + ItemStack stack = model.asStack(); stack.set(DataComponentTypes.HIDE_TOOLTIP, Unit.INSTANCE); return stack; } + + public static void init() { + + } } diff --git a/src/main/resources/assets/grafting/font/workbench_gui.json b/src/main/resources/assets/grafting/font/workbench_gui.json new file mode 100644 index 0000000..d6ae828 --- /dev/null +++ b/src/main/resources/assets/grafting/font/workbench_gui.json @@ -0,0 +1,29 @@ +{ + "providers": [ + { + "type": "space", + "advances": { + ".": -134, + "-": -8 + } + }, + { + "type": "bitmap", + "file": "grafting:gui/workbench_gui.png", + "ascent": 34, + "height": 256, + "chars": [ + "0" + ] + }, + { + "type": "bitmap", + "file": "grafting:gui/workbench_gui_with_inventory.png", + "ascent": 34, + "height": 256, + "chars": [ + "1" + ] + } + ] +} diff --git a/src/main/resources/assets/grafting/lang/en_us.json b/src/main/resources/assets/grafting/lang/en_us.json new file mode 100644 index 0000000..9bdafea --- /dev/null +++ b/src/main/resources/assets/grafting/lang/en_us.json @@ -0,0 +1,3 @@ +{ + "block.grafting.workbench": "Workbench" +} diff --git a/src/main/resources/assets/grafting/models/icon/rotate_clockwise.json b/src/main/resources/assets/grafting/models/icon/rotate_clockwise.json new file mode 100644 index 0000000..d469954 --- /dev/null +++ b/src/main/resources/assets/grafting/models/icon/rotate_clockwise.json @@ -0,0 +1,6 @@ +{ + "parent": "grafting:icon/shifted", + "textures": { + "layer0": "grafting:item/icon/rotate_clockwise" + } +} diff --git a/src/main/resources/assets/grafting/models/icon/rotate_counter_clockwise.json b/src/main/resources/assets/grafting/models/icon/rotate_counter_clockwise.json new file mode 100644 index 0000000..bf4cd88 --- /dev/null +++ b/src/main/resources/assets/grafting/models/icon/rotate_counter_clockwise.json @@ -0,0 +1,6 @@ +{ + "parent": "grafting:icon/shifted", + "textures": { + "layer0": "grafting:item/icon/rotate_counter_clockwise" + } +} diff --git a/src/main/resources/assets/grafting/models/icon/shifted.json b/src/main/resources/assets/grafting/models/icon/shifted.json new file mode 100644 index 0000000..9310032 --- /dev/null +++ b/src/main/resources/assets/grafting/models/icon/shifted.json @@ -0,0 +1,10 @@ +{ + "parent": "minecraft:item/generated", + "display": { + "gui": { + "rotation": [ 0, 0, 0 ], + "translation": [ -1, 0, 0 ], + "scale": [ 1, 1, 1 ] + } + } +} diff --git a/src/main/resources/assets/grafting/models/item/workbench.json b/src/main/resources/assets/grafting/models/item/workbench.json new file mode 100644 index 0000000..9e54284 --- /dev/null +++ b/src/main/resources/assets/grafting/models/item/workbench.json @@ -0,0 +1,3 @@ +{ + "parent": "grafting:block/workbench" +} \ No newline at end of file diff --git a/src/main/resources/assets/grafting/textures/gui/workbench_gui.png b/src/main/resources/assets/grafting/textures/gui/workbench_gui.png new file mode 100644 index 0000000..7cdec2e Binary files /dev/null and b/src/main/resources/assets/grafting/textures/gui/workbench_gui.png differ diff --git a/src/main/resources/assets/grafting/textures/gui/workbench_gui_with_inventory.png b/src/main/resources/assets/grafting/textures/gui/workbench_gui_with_inventory.png new file mode 100644 index 0000000..d11786a Binary files /dev/null and b/src/main/resources/assets/grafting/textures/gui/workbench_gui_with_inventory.png differ diff --git a/src/main/resources/assets/grafting/textures/gui/icons/rotate_clockwise.png b/src/main/resources/assets/grafting/textures/item/icon/rotate_clockwise.png similarity index 100% rename from src/main/resources/assets/grafting/textures/gui/icons/rotate_clockwise.png rename to src/main/resources/assets/grafting/textures/item/icon/rotate_clockwise.png diff --git a/src/main/resources/assets/grafting/textures/gui/icons/rotate_counter_clockwise.png b/src/main/resources/assets/grafting/textures/item/icon/rotate_counter_clockwise.png similarity index 100% rename from src/main/resources/assets/grafting/textures/gui/icons/rotate_counter_clockwise.png rename to src/main/resources/assets/grafting/textures/item/icon/rotate_counter_clockwise.png diff --git a/src/main/resources/data/grafting/grafting/placement_order/default.json b/src/main/resources/data/grafting/grafting/placement_order/default.json new file mode 100644 index 0000000..485e5dc --- /dev/null +++ b/src/main/resources/data/grafting/grafting/placement_order/default.json @@ -0,0 +1,5 @@ +[ + 0,1,2, + 3,4,5, + 6,7,8 +] \ No newline at end of file