Implement more stable WorkbenchBlockEntity, add buttons and GUIs, placement order components and dynamic registries, attachable inventories

This commit is contained in:
Dakedres 2025-04-16 06:24:46 -06:00
parent 2197c69f0f
commit 662aa41c63
23 changed files with 1069 additions and 508 deletions

View File

@ -1,39 +1,57 @@
package net.sys42.dakedres.grafting; package net.sys42.dakedres.grafting;
import com.mojang.serialization.Codec;
import eu.pb4.polymer.core.api.block.PolymerBlockUtils; 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 eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils;
import net.fabricmc.api.ModInitializer; 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.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
import net.minecraft.block.AbstractBlock; import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntityType; import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.component.ComponentType;
import net.minecraft.item.BlockItem; import net.minecraft.item.BlockItem;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.registry.*;
import net.minecraft.item.Items; import net.minecraft.resource.Resource;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.sound.BlockSoundGroup; import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.sys42.dakedres.grafting.blocks.Workbench; import net.sys42.dakedres.grafting.block.Workbench;
import net.sys42.dakedres.grafting.blocks.WorkbenchBlockEntity; 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.Logger;
import org.slf4j.LoggerFactory; 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 class Grafting implements ModInitializer {
public static final String MOD_ID = "grafting"; 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. // 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. // 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. // That way, it's clear which mod wrote info, warnings, and errors.
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); 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( public static final Block WORKBENCH = Registry.register(
Registries.BLOCK, WORKBENCH_ID, 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<WorkbenchBlockEntity> WORKBENCH_BLOCK_ENTITY_TYPE = Registry.register( public static final BlockEntityType<WorkbenchBlockEntity> WORKBENCH_BLOCK_ENTITY_TYPE = Registry.register(
Registries.BLOCK_ENTITY_TYPE, Registries.BLOCK_ENTITY_TYPE,
@ -42,9 +60,20 @@ public class Grafting implements ModInitializer {
); );
public static final BlockItem WORKBENCH_ITEM = Registry.register( public static final BlockItem WORKBENCH_ITEM = Registry.register(
Registries.ITEM, WORKBENCH_ID, Registries.ITEM, WORKBENCH_ID,
new Workbench.WorkbenchItem(WORKBENCH, new Item.Settings()) new WorkbenchItem(WORKBENCH, new Item.Settings())
); );
public static final ComponentType<Identifier> PLACEMENT_ORDER_COMPONENT_TYPE = Registry.register(
Registries.DATA_COMPONENT_TYPE,
PLACEMENT_ORDER_COMPONENT_ID,
ComponentType.<Identifier>builder().codec(Identifier.CODEC).build()
);
public static final RegistryKey<Registry<PlacementOrder>> PLACEMENT_ORDER_REGISTRY_KEY = RegistryKey.ofRegistry(Identifier.of(Grafting.MOD_ID, "placement_order"));
// public static final Registry<PlacementOrder> PLACEMENT_ORDER_REGISTRY = FabricRegistryBuilder.createSimple(PLACEMENT_ORDER_REGISTRY_KEY)
// .attribute(RegistryAttribute.MODDED)
// .buildAndRegister();
@Override @Override
public void onInitialize() { public void onInitialize() {
// This code runs as soon as Minecraft is in a mod-load-ready state. // 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!"); LOGGER.info("Hello Fabric world!");
PolymerBlockUtils.registerBlockEntity(WORKBENCH_BLOCK_ENTITY_TYPE); 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)) { if (PolymerResourcePackUtils.addModAssets(MOD_ID)) {
LOGGER.info("Successfully added mod assets for " + MOD_ID); LOGGER.info("Successfully added mod assets for " + MOD_ID);

View File

@ -1,6 +1,11 @@
package net.sys42.dakedres.grafting; 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 = { public static final int[][] ROTATION_MATRICES = {
{ 0,1,2,3,4,5,6,7,8 }, { 0,1,2,3,4,5,6,7,8 },
{ 1,2,5,0,4,8,3,6,7 }, { 1,2,5,0,4,8,3,6,7 },
@ -33,6 +38,10 @@ public class PlacementMatrixUtils {
} }
public static int[] rotate(int[] matrix, int rotation) { public static int[] rotate(int[] matrix, int rotation) {
if(rotation == 0) {
return matrix;
}
int[] o = new int[matrix.length]; int[] o = new int[matrix.length];
int[] rotationMatrix = ROTATION_MATRICES[rotation]; int[] rotationMatrix = ROTATION_MATRICES[rotation];
@ -43,12 +52,34 @@ public class PlacementMatrixUtils {
return o; return o;
} }
public static int[] getPlacements() { public static int[] getPlacements(ItemStack itemStack, ServerWorld world) {
int[] placements = { // int[] placements = {
2,0,8, // 2,0,8,
1,3,4, // 1,3,4,
7,6,5 // 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]; int[] out = new int[9];
for(int i = 0; i < 9; i++) { for(int i = 0; i < 9; i++) {

View File

@ -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<? extends BlockWithEntity> 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<Block, BlockState> 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);
}
}

View File

@ -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<GenericContainerScreenHandler> 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));
}
}
}

View File

@ -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<ItemStack> 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<ItemStack> 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();
}
}

View File

@ -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<Gui> 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> 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<ItemStack> getHeldStacks() {
DefaultedList<ItemStack> 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> 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);
}
}
}
);
}
}
}
}

View File

@ -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<ItemStack> stacks = DefaultedList.ofSize(9, ItemStack.EMPTY);
public Workbench(Settings settings) {
super(settings);
}
@Override
protected MapCodec<? extends BlockWithEntity> 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;
}
}
}

View File

@ -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<ItemStack> grid = DefaultedList.ofSize(GRID_SIZE, ItemStack.EMPTY);
private int gridRotation = 0;
private final ArrayList<Gui> 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<ItemStack> getHeldStacks() {
return this.grid;
}
@Override
protected void setHeldStacks(DefaultedList<ItemStack> 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<ItemStack> 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<ItemStack> 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);
}
}
}
);
}
}
}
}

View File

@ -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<PlacementOrder> CODEC = new PrimitiveCodec<>() {
@Override
public <T> DataResult<PlacementOrder> read(DynamicOps<T> 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> T write(DynamicOps<T> 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));
}
}
}
}

View File

@ -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<Identifier, Resource> 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);
}
}

View File

@ -1,36 +1,56 @@
package net.sys42.dakedres.grafting.item; package net.sys42.dakedres.grafting.item;
import eu.pb4.polymer.core.api.item.PolymerItem; 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.block.Block;
import net.minecraft.item.*; 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.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; import org.jetbrains.annotations.Nullable;
public class WorkbenchItem extends BlockItem implements PolymerItem { 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); super(block, settings);
} }
@Override @Override
public Item getPolymerItem(ItemStack itemStack, @Nullable ServerPlayerEntity serverPlayerEntity) { 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! // Thank you Mykhailo!
// copied from: // copied from:
// https://github.com/MykhailoOpryshok/Borukva-Food/blob/master/src/main/java/com/opryshok/block/TexturedPolyBlockItem.java#L18 // https://github.com/MykhailoOpryshok/Borukva-Food/blob/master/src/main/java/com/opryshok/block/TexturedPolyBlockItem.java#L18
// @Override @Override
// public ActionResult useOnBlock(ItemUsageContext context) { public ActionResult useOnBlock(ItemUsageContext context) {
// var x = super.useOnBlock(context); var x = super.useOnBlock(context);
// if (x == ActionResult.CONSUME) { if (x == ActionResult.CONSUME) {
// if (context.getPlayer() instanceof ServerPlayerEntity player) { if (context.getPlayer() instanceof ServerPlayerEntity player) {
// var pos = Vec3d.ofCenter(context.getBlockPos().offset(context.getSide())); var pos = Vec3d.ofCenter(context.getBlockPos().offset(context.getSide()));
// var blockSoundGroup = this.getBlock().getDefaultState().getSoundGroup(); 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())); 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; return ActionResult.SUCCESS;
// } }
return x;
}
} }

View File

@ -14,17 +14,24 @@ import net.sys42.dakedres.grafting.Grafting;
public class Icons { public class Icons {
public static final Item BASE_ITEM = Items.BREEZE_ROD; 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_CLOCKWISE = uiElementModel("rotate_clockwise");
public static final PolymerModelData ROTATE_COUNTER_CLOCKWISE = uiElementModel("icons/rotate_counter_clockwise.png"); public static final PolymerModelData ROTATE_COUNTER_CLOCKWISE = uiElementModel("rotate_counter_clockwise");
private static PolymerModelData uiElementModel(String path) { private static PolymerModelData uiElementModel(String name) {
return PolymerResourcePackUtils.requestModel(BASE_ITEM, Identifier.of(Grafting.MOD_ID + path)); return PolymerResourcePackUtils.requestModel(
BASE_ITEM,
Identifier.of(Grafting.MOD_ID, "icon/" + name)
);
} }
public static ItemStack getStack(PolymerModelData model) { public static ItemStack getStack(PolymerModelData model) {
ItemStack stack = Icons.ROTATE_CLOCKWISE.asStack(); ItemStack stack = model.asStack();
stack.set(DataComponentTypes.HIDE_TOOLTIP, Unit.INSTANCE); stack.set(DataComponentTypes.HIDE_TOOLTIP, Unit.INSTANCE);
return stack; return stack;
} }
public static void init() {
}
} }

View File

@ -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"
]
}
]
}

View File

@ -0,0 +1,3 @@
{
"block.grafting.workbench": "Workbench"
}

View File

@ -0,0 +1,6 @@
{
"parent": "grafting:icon/shifted",
"textures": {
"layer0": "grafting:item/icon/rotate_clockwise"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "grafting:icon/shifted",
"textures": {
"layer0": "grafting:item/icon/rotate_counter_clockwise"
}
}

View File

@ -0,0 +1,10 @@
{
"parent": "minecraft:item/generated",
"display": {
"gui": {
"rotation": [ 0, 0, 0 ],
"translation": [ -1, 0, 0 ],
"scale": [ 1, 1, 1 ]
}
}
}

View File

@ -0,0 +1,3 @@
{
"parent": "grafting:block/workbench"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,5 @@
[
0,1,2,
3,4,5,
6,7,8
]