Implement more stable WorkbenchBlockEntity, add buttons and GUIs, placement order components and dynamic registries, attachable inventories
This commit is contained in:
parent
2197c69f0f
commit
662aa41c63
@ -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<WorkbenchBlockEntity> 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<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
|
||||
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);
|
||||
|
@ -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++) {
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
29
src/main/resources/assets/grafting/font/workbench_gui.json
Normal file
29
src/main/resources/assets/grafting/font/workbench_gui.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
3
src/main/resources/assets/grafting/lang/en_us.json
Normal file
3
src/main/resources/assets/grafting/lang/en_us.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"block.grafting.workbench": "Workbench"
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "grafting:icon/shifted",
|
||||
"textures": {
|
||||
"layer0": "grafting:item/icon/rotate_clockwise"
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "grafting:icon/shifted",
|
||||
"textures": {
|
||||
"layer0": "grafting:item/icon/rotate_counter_clockwise"
|
||||
}
|
||||
}
|
10
src/main/resources/assets/grafting/models/icon/shifted.json
Normal file
10
src/main/resources/assets/grafting/models/icon/shifted.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"display": {
|
||||
"gui": {
|
||||
"rotation": [ 0, 0, 0 ],
|
||||
"translation": [ -1, 0, 0 ],
|
||||
"scale": [ 1, 1, 1 ]
|
||||
}
|
||||
}
|
||||
}
|
@ -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 |
Before Width: | Height: | Size: 677 B After Width: | Height: | Size: 677 B |
Before Width: | Height: | Size: 670 B After Width: | Height: | Size: 670 B |
@ -0,0 +1,5 @@
|
||||
[
|
||||
0,1,2,
|
||||
3,4,5,
|
||||
6,7,8
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user