diff --git a/src/main/java/dev/hephaestus/glowcase/Glowcase.java b/src/main/java/dev/hephaestus/glowcase/Glowcase.java index fe9400d..ef6ef5d 100644 --- a/src/main/java/dev/hephaestus/glowcase/Glowcase.java +++ b/src/main/java/dev/hephaestus/glowcase/Glowcase.java @@ -3,6 +3,7 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import dev.hephaestus.glowcase.block.HyperlinkBlock; +import dev.hephaestus.glowcase.block.ItemAcceptorBlock; import dev.hephaestus.glowcase.block.ItemDisplayBlock; import dev.hephaestus.glowcase.block.OutlineBlock; import dev.hephaestus.glowcase.block.ParticleDisplayBlock; @@ -10,6 +11,7 @@ import dev.hephaestus.glowcase.block.SpriteBlock; import dev.hephaestus.glowcase.block.TextBlock; import dev.hephaestus.glowcase.block.entity.HyperlinkBlockEntity; +import dev.hephaestus.glowcase.block.entity.ItemAcceptorBlockEntity; import dev.hephaestus.glowcase.block.entity.ItemDisplayBlockEntity; import dev.hephaestus.glowcase.block.entity.OutlineBlockEntity; import dev.hephaestus.glowcase.block.entity.ParticleDisplayBlockEntity; @@ -55,7 +57,6 @@ public class Glowcase implements ModInitializer { public static final Supplier PARTICLE_DISPLAY_ITEM = registerItem("particle_display", () -> new BlockItem(PARTICLE_DISPLAY.get(), new Item.Settings())); public static final Supplier> PARTICLE_DISPLAY_BLOCK_ENTITY = registerBlockEntity("particle_display", () -> BlockEntityType.Builder.create(ParticleDisplayBlockEntity::new, PARTICLE_DISPLAY.get()).build(null)); - public static final Supplier TEXT_BLOCK = registerBlock("text_block", TextBlock::new); public static final Supplier TEXT_BLOCK_ITEM = registerItem("text_block", () -> new BlockItem(TEXT_BLOCK.get(), new Item.Settings())); public static final Supplier> TEXT_BLOCK_ENTITY = registerBlockEntity("text_block", () -> BlockEntityType.Builder.create(TextBlockEntity::new, TEXT_BLOCK.get()).build(null)); @@ -72,6 +73,10 @@ public class Glowcase implements ModInitializer { public static final Supplier OUTLINE_BLOCK_ITEM = registerItem("outline_block", () -> new BlockItem(OUTLINE_BLOCK.get(), new Item.Settings())); public static final Supplier> OUTLINE_BLOCK_ENTITY = registerBlockEntity("outline_block", () -> BlockEntityType.Builder.create(OutlineBlockEntity::new, OUTLINE_BLOCK.get()).build(null)); + public static final Supplier ITEM_ACCEPTOR_BLOCK = registerBlock("item_acceptor_block", ItemAcceptorBlock::new); + public static final Supplier ITEM_ACCEPTOR_BLOCK_ITEM = registerItem("item_acceptor_block", () -> new BlockItem(ITEM_ACCEPTOR_BLOCK.get(), new Item.Settings())); + public static final Supplier> ITEM_ACCEPTOR_BLOCK_ENTITY = registerBlockEntity("item_acceptor_block", () -> BlockEntityType.Builder.create(ItemAcceptorBlockEntity::new, ITEM_ACCEPTOR_BLOCK.get()).build(null)); + public static final Supplier LOCK_ITEM = registerItem("lock", () -> new LockItem(new Item.Settings())); public static final Supplier ITEM_GROUP = registerItemGroup("items", () -> FabricItemGroup.builder() @@ -83,6 +88,7 @@ public class Glowcase implements ModInitializer { entries.add(OUTLINE_BLOCK_ITEM.get()); entries.add(PARTICLE_DISPLAY_ITEM.get()); entries.add(ITEM_DISPLAY_BLOCK_ITEM.get()); + entries.add(ITEM_ACCEPTOR_BLOCK_ITEM.get()); entries.add(HYPERLINK_BLOCK_ITEM.get()); entries.add(POPUP_BLOCK_ITEM.get()); entries.add(LOCK_ITEM.get()); diff --git a/src/main/java/dev/hephaestus/glowcase/GlowcaseCommonProxy.java b/src/main/java/dev/hephaestus/glowcase/GlowcaseCommonProxy.java index 6fca441..59efac1 100644 --- a/src/main/java/dev/hephaestus/glowcase/GlowcaseCommonProxy.java +++ b/src/main/java/dev/hephaestus/glowcase/GlowcaseCommonProxy.java @@ -38,4 +38,8 @@ public void openOutlineBlockEditScreen(BlockPos pos) { public void openParticleDisplayBlockEditScreen(BlockPos pos) { //No-op } + + public void openItemAcceptorBlockEditScreen(BlockPos pos) { + //No-op + } } diff --git a/src/main/java/dev/hephaestus/glowcase/GlowcaseNetworking.java b/src/main/java/dev/hephaestus/glowcase/GlowcaseNetworking.java index 58c76d3..4a4ff36 100644 --- a/src/main/java/dev/hephaestus/glowcase/GlowcaseNetworking.java +++ b/src/main/java/dev/hephaestus/glowcase/GlowcaseNetworking.java @@ -1,6 +1,7 @@ package dev.hephaestus.glowcase; import dev.hephaestus.glowcase.packet.C2SEditHyperlinkBlock; +import dev.hephaestus.glowcase.packet.C2SEditItemAcceptorBlock; import dev.hephaestus.glowcase.packet.C2SEditItemDisplayBlock; import dev.hephaestus.glowcase.packet.C2SEditOutlineBlock; import dev.hephaestus.glowcase.packet.C2SEditParticleDisplayBlock; @@ -19,6 +20,7 @@ public static void init() { PayloadTypeRegistry.playC2S().register(C2SEditSpriteBlock.ID, C2SEditSpriteBlock.PACKET_CODEC); PayloadTypeRegistry.playC2S().register(C2SEditOutlineBlock.ID, C2SEditOutlineBlock.PACKET_CODEC); PayloadTypeRegistry.playC2S().register(C2SEditParticleDisplayBlock.ID, C2SEditParticleDisplayBlock.PACKET_CODEC); + PayloadTypeRegistry.playC2S().register(C2SEditItemAcceptorBlock.ID, C2SEditItemAcceptorBlock.PACKET_CODEC); ServerPlayNetworking.registerGlobalReceiver(C2SEditHyperlinkBlock.ID, C2SEditHyperlinkBlock::receive); ServerPlayNetworking.registerGlobalReceiver(C2SEditItemDisplayBlock.ID, C2SEditItemDisplayBlock::receive); @@ -27,5 +29,6 @@ public static void init() { ServerPlayNetworking.registerGlobalReceiver(C2SEditSpriteBlock.ID, C2SEditSpriteBlock::receive); ServerPlayNetworking.registerGlobalReceiver(C2SEditOutlineBlock.ID, C2SEditOutlineBlock::receive); ServerPlayNetworking.registerGlobalReceiver(C2SEditParticleDisplayBlock.ID, C2SEditParticleDisplayBlock::receive); + ServerPlayNetworking.registerGlobalReceiver(C2SEditItemAcceptorBlock.ID, C2SEditItemAcceptorBlock::receive); } } diff --git a/src/main/java/dev/hephaestus/glowcase/block/ItemAcceptorBlock.java b/src/main/java/dev/hephaestus/glowcase/block/ItemAcceptorBlock.java new file mode 100644 index 0000000..1a8424f --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/block/ItemAcceptorBlock.java @@ -0,0 +1,236 @@ +package dev.hephaestus.glowcase.block; + +import dev.hephaestus.glowcase.Glowcase; +import dev.hephaestus.glowcase.block.entity.ItemAcceptorBlockEntity; +import net.minecraft.block.*; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.ChestBlockEntity; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.NbtComponent; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.Item; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.item.tooltip.TooltipType; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.BooleanProperty; +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Formatting; +import net.minecraft.util.Hand; +import net.minecraft.util.ItemActionResult; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class ItemAcceptorBlock extends GlowcaseBlock implements BlockEntityProvider { + private static final VoxelShape OUTLINE = VoxelShapes.cuboid(0, 0, 0, 1, 1, 1); + public static final DirectionProperty FACING = Properties.HORIZONTAL_FACING; + public static final BooleanProperty POWERED = Properties.POWERED; + + public ItemAcceptorBlock() { + super(); + this.setDefaultState(this.getDefaultState().with(FACING, Direction.NORTH).with(POWERED, false)); + } + + @Override + protected void appendProperties(StateManager.Builder builder) { + super.appendProperties(builder); + builder.add(FACING, POWERED); + } + + @Override + public BlockState getPlacementState(ItemPlacementContext ctx) { + Direction direction = ctx.getSide().getOpposite(); + return this.getDefaultState().with(FACING, ctx.getHorizontalPlayerFacing().getOpposite()); + } + + @Override + public void onPlaced(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack) { + if (world.isClient && placer instanceof PlayerEntity player && canEditGlowcase(player, pos)) { + //load any ctrl-picked NBT clientside + NbtComponent blockEntityTag = stack.get(DataComponentTypes.BLOCK_ENTITY_DATA); + if (blockEntityTag != null && world.getBlockEntity(pos) instanceof ItemAcceptorBlockEntity be) { + blockEntityTag.applyToBlockEntity(be, world.getRegistryManager()); + } + + Glowcase.proxy.openItemAcceptorBlockEditScreen(pos); + } + } + + @Override + protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) { + if (!(world.getBlockEntity(pos) instanceof ItemAcceptorBlockEntity be)) return ActionResult.CONSUME; + if (canEditGlowcase(player, pos)) { + if (world.isClient) { + Glowcase.proxy.openItemAcceptorBlockEditScreen(be.getPos()); + } + return ActionResult.SUCCESS; + } + return ActionResult.PASS; + } + + @Override + protected ItemActionResult onUseWithItem(ItemStack stack, BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { + if (!(world.getBlockEntity(pos) instanceof ItemAcceptorBlockEntity be)) { + return ItemActionResult.CONSUME; + } + + if (!be.isItemAccepted(stack)) { + return ItemActionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION; + } + + if (world.getBlockTickScheduler().isQueued(pos, this) || state.get(POWERED)) { + return ItemActionResult.SKIP_DEFAULT_BLOCK_INTERACTION; + } + + if (!world.isClient()) { + // Remove items + ItemStack newStack = stack.copyWithCount(be.count); + stack.decrementUnlessCreative(be.count, player); + + // Attempt to insert items + if (getInventoryAt(world, pos.offset(getOutputDirection(world, pos, state))) instanceof Inventory inventory) { + addToFirstFreeSlot(inventory, newStack); + } + + // Schedule redstone pulse + world.scheduleBlockTick(pos, this, 2); + } + return ItemActionResult.SUCCESS; + } + + private static Inventory getInventoryAt(World world, BlockPos pos) { + BlockState state = world.getBlockState(pos); + Block block = state.getBlock(); + + if (block instanceof InventoryProvider inventoryProvider) { + return inventoryProvider.getInventory(state, world, pos); + } else if (state.hasBlockEntity() && world.getBlockEntity(pos) instanceof Inventory inventory) { + if (inventory instanceof ChestBlockEntity && block instanceof ChestBlock chestBlock) { + return ChestBlock.getInventory(chestBlock, state, world, pos, true); + } + + return inventory; + } + + return null; + } + + private ItemStack addToFirstFreeSlot(Inventory inventory, ItemStack stack) { + int i = inventory.getMaxCount(stack); + + for (int j = 0; j < inventory.size(); j++) { + ItemStack itemStack = inventory.getStack(j); + if (itemStack.isEmpty() || ItemStack.areItemsAndComponentsEqual(stack, itemStack)) { + int k = Math.min(stack.getCount(), i - itemStack.getCount()); + if (k > 0) { + if (itemStack.isEmpty()) { + inventory.setStack(j, stack.split(k)); + } else { + stack.decrement(k); + itemStack.increment(k); + } + } + + if (stack.isEmpty()) { + break; + } + } + } + + return stack; + } + + @Override + protected void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { + if (state.get(POWERED)) { + world.setBlockState(pos, state.with(POWERED, false), Block.NOTIFY_LISTENERS); + } else { + world.setBlockState(pos, state.with(POWERED, true), Block.NOTIFY_LISTENERS); + world.scheduleBlockTick(pos, this, 4); + } + + this.updateNeighbors(world, pos, state); + } + + protected void updateNeighbors(World world, BlockPos pos, BlockState state) { + Direction direction = getOutputDirection(world, pos, state); + BlockPos blockPos = pos.offset(direction); + world.updateNeighbor(blockPos, this, pos); + world.updateNeighborsExcept(blockPos, this, direction.getOpposite()); + } + + @Override + protected boolean emitsRedstonePower(BlockState state) { + return true; + } + + @Override + protected int getStrongRedstonePower(BlockState state, BlockView world, BlockPos pos, Direction direction) { + return 0; + } + + @Override + protected int getWeakRedstonePower(BlockState state, BlockView world, BlockPos pos, Direction direction) { + return state.get(POWERED) && getOutputDirection(world, pos, state) == direction.getOpposite() ? 15 : 0; + } + + @Override + protected void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) { + if (!state.isOf(newState.getBlock())) { + if (!world.isClient && state.get(POWERED) && world.getBlockTickScheduler().isQueued(pos, this)) { + this.updateNeighbors(world, pos, state.with(POWERED, false)); + } + } + } + + public Direction getOutputDirection(BlockView world, BlockPos pos, BlockState state) { + if (world.getBlockEntity(pos) instanceof ItemAcceptorBlockEntity blockEntity) { + return switch (blockEntity.outputDirection) { + case BOTTOM -> Direction.DOWN; + case BACK -> state.get(FACING).getOpposite(); + case TOP -> Direction.UP; + }; + } + + return state.get(FACING).getOpposite(); + } + + @Nullable + @Override + public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new ItemAcceptorBlockEntity(pos, state); + } + + @Override + public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { + return OUTLINE; + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { + return OUTLINE; + } + + @Override + public void appendTooltip(ItemStack itemStack, Item.TooltipContext context, List tooltip, TooltipType options) { + tooltip.add(Text.translatable("block.glowcase.item_acceptor_block.tooltip.0").formatted(Formatting.GRAY)); + tooltip.add(Text.translatable("block.glowcase.item_acceptor_block.tooltip.1").formatted(Formatting.BLUE)); + tooltip.add(Text.translatable("block.glowcase.item_acceptor_block.tooltip.2").formatted(Formatting.BLUE)); + tooltip.add(Text.translatable("block.glowcase.item_acceptor_block.tooltip.3").formatted(Formatting.DARK_GRAY)); + } +} diff --git a/src/main/java/dev/hephaestus/glowcase/block/entity/ItemAcceptorBlockEntity.java b/src/main/java/dev/hephaestus/glowcase/block/entity/ItemAcceptorBlockEntity.java new file mode 100644 index 0000000..9748fde --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/block/entity/ItemAcceptorBlockEntity.java @@ -0,0 +1,117 @@ +package dev.hephaestus.glowcase.block.entity; + +import dev.hephaestus.glowcase.Glowcase; +import dev.hephaestus.glowcase.client.render.block.entity.BakedBlockEntityRenderer; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.listener.ClientPlayPacketListener; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; +import net.minecraft.util.math.BlockPos; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class ItemAcceptorBlockEntity extends BlockEntity { + private Identifier item = Identifier.ofVanilla("air"); + public int count = 1; + public OutputDirection outputDirection = OutputDirection.BACK; + public boolean isItemTag = false; + private List itemTagList = List.of(); + + public ItemAcceptorBlockEntity(BlockPos pos, BlockState state) { + super(Glowcase.ITEM_ACCEPTOR_BLOCK_ENTITY.get(), pos, state); + } + + @Override + public void writeNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) { + super.writeNbt(tag, registryLookup); + + tag.putString("item", this.item.toString()); + tag.putInt("count", this.count); + tag.putBoolean("is_item_tag", this.isItemTag); + tag.putString("output_direction", this.outputDirection.name()); + } + + @Override + public void readNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) { + super.readNbt(tag, registryLookup); + + setItem(Identifier.tryParse(tag.getString("item"))); + this.count = tag.getInt("count"); + this.isItemTag = tag.getBoolean("is_item_tag"); + this.outputDirection = OutputDirection.valueOf(tag.getString("output_direction")); + } + + public Identifier getItem() { + return item; + } + + public void setItem(Identifier item) { + this.item = item; + + TagKey itemTag = TagKey.of(RegistryKeys.ITEM, item); + itemTagList = Registries.ITEM.stream().filter(it -> it.getDefaultStack().isIn(itemTag)).toList(); + } + + public ItemStack getDisplayItemStack() { + if (isItemTag) { + if (itemTagList.isEmpty()) { + return ItemStack.EMPTY; + } + + return itemTagList.get((int) (Util.getMeasuringTimeMs() / 1000f) % itemTagList.size()).getDefaultStack(); + } else { + return Registries.ITEM.get(item).getDefaultStack(); + } + } + + public boolean isItemAccepted(ItemStack stack) { + boolean isEqual = isItemTag + ? stack.isIn(TagKey.of(RegistryKeys.ITEM, item)) + : stack.isOf(Registries.ITEM.get(item)); + + return isEqual && stack.getCount() >= count; + } + + @SuppressWarnings({"MethodCallSideOnly", "VariableUseSideOnly"}) + @Override + public void markRemoved() { + if (world != null && world.isClient) { + BakedBlockEntityRenderer.Manager.markForRebuild(getPos()); + } + super.markRemoved(); + } + + // standard blockentity boilerplate + + public void dispatch() { + if (world instanceof ServerWorld sworld) sworld.getChunkManager().markForUpdate(pos); + } + + @Override + public NbtCompound toInitialChunkDataNbt(RegistryWrapper.WrapperLookup registryLookup) { + return createNbt(registryLookup); + } + + @Nullable + @Override + public Packet toUpdatePacket() { + return BlockEntityUpdateS2CPacket.create(this); + } + + public enum OutputDirection + { + TOP, BACK, BOTTOM + } +} diff --git a/src/main/java/dev/hephaestus/glowcase/client/GlowcaseClient.java b/src/main/java/dev/hephaestus/glowcase/client/GlowcaseClient.java index 6b16237..a8a9636 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/GlowcaseClient.java +++ b/src/main/java/dev/hephaestus/glowcase/client/GlowcaseClient.java @@ -3,6 +3,7 @@ import dev.hephaestus.glowcase.Glowcase; import dev.hephaestus.glowcase.client.render.block.entity.BakedBlockEntityRenderer; import dev.hephaestus.glowcase.client.render.block.entity.HyperlinkBlockEntityRenderer; +import dev.hephaestus.glowcase.client.render.block.entity.ItemAcceptorBlockEntityRenderer; import dev.hephaestus.glowcase.client.render.block.entity.ItemDisplayBlockEntityRenderer; import dev.hephaestus.glowcase.client.render.block.entity.OutlineBlockEntityRenderer; import dev.hephaestus.glowcase.client.render.block.entity.ParticleDisplayBlockEntityRenderer; @@ -26,6 +27,7 @@ public void onInitializeClient() { BlockEntityRendererFactories.register(Glowcase.SPRITE_BLOCK_ENTITY.get(), SpriteBlockEntityRenderer::new); BlockEntityRendererFactories.register(Glowcase.OUTLINE_BLOCK_ENTITY.get(), OutlineBlockEntityRenderer::new); BlockEntityRendererFactories.register(Glowcase.PARTICLE_DISPLAY_BLOCK_ENTITY.get(), ParticleDisplayBlockEntityRenderer::new); + BlockEntityRendererFactories.register(Glowcase.ITEM_ACCEPTOR_BLOCK_ENTITY.get(), ItemAcceptorBlockEntityRenderer::new); WorldRenderEvents.AFTER_TRANSLUCENT.register(BakedBlockEntityRenderer.Manager::render); InvalidateRenderStateCallback.EVENT.register(BakedBlockEntityRenderer.Manager::reset); diff --git a/src/main/java/dev/hephaestus/glowcase/client/GlowcaseClientProxy.java b/src/main/java/dev/hephaestus/glowcase/client/GlowcaseClientProxy.java index 7cc2819..6545111 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/GlowcaseClientProxy.java +++ b/src/main/java/dev/hephaestus/glowcase/client/GlowcaseClientProxy.java @@ -89,4 +89,12 @@ public void openParticleDisplayBlockEditScreen(BlockPos pos) { MinecraftClient.getInstance().setScreen(new ParticleDisplayEditScreen(be)); } } + + @Override + public void openItemAcceptorBlockEditScreen(BlockPos pos) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.world != null && client.world.getBlockEntity(pos) instanceof ItemAcceptorBlockEntity be) { + MinecraftClient.getInstance().setScreen(new ItemAcceptorBlockEditScreen(be)); + } + } } diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ItemAcceptorBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ItemAcceptorBlockEditScreen.java new file mode 100644 index 0000000..3472589 --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ItemAcceptorBlockEditScreen.java @@ -0,0 +1,80 @@ +package dev.hephaestus.glowcase.client.gui.screen.ingame; + +import com.google.common.primitives.Ints; +import dev.hephaestus.glowcase.block.entity.ItemAcceptorBlockEntity; +import dev.hephaestus.glowcase.packet.C2SEditItemAcceptorBlock; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +public class ItemAcceptorBlockEditScreen extends GlowcaseScreen { + private final ItemAcceptorBlockEntity itemAcceptorBlockEntity; + + private TextFieldWidget itemWidget; + private TextFieldWidget countWidget; + private ButtonWidget outputDirectionToggle; + + public ItemAcceptorBlockEditScreen(ItemAcceptorBlockEntity itemAcceptorBlockEntity) { + this.itemAcceptorBlockEntity = itemAcceptorBlockEntity; + } + + @Override + public void init() { + super.init(); + + if (this.client == null) return; + + Identifier item = this.itemAcceptorBlockEntity.getItem(); + + this.itemWidget = new TextFieldWidget(this.textRenderer, width / 2 - 75, height / 2 - 40, 150, 20, Text.empty()); + this.itemWidget.setMaxLength(128); + if (!item.equals(Identifier.ofVanilla("air"))) { + this.itemWidget.setText((this.itemAcceptorBlockEntity.isItemTag ? "#" : "") + item); + } + this.itemWidget.setPlaceholder(Text.translatable("gui.glowcase.item_or_tag")); + this.itemWidget.setTextPredicate(s -> s.matches("#?[a-z0-9_.-]*:?[a-z0-9_./-]*")); + + this.countWidget = new TextFieldWidget(this.textRenderer, width / 2 - 75, height / 2 - 10, 150, 20, Text.empty()); + this.countWidget.setText(String.valueOf(this.itemAcceptorBlockEntity.count)); + this.countWidget.setPlaceholder(Text.translatable("gui.glowcase.count")); + this.countWidget.setTextPredicate(s -> s.matches("\\d*")); + + this.outputDirectionToggle = ButtonWidget.builder(Text.translatable("gui.glowcase.output_direction", this.itemAcceptorBlockEntity.outputDirection.toString()), action -> { + switch (itemAcceptorBlockEntity.outputDirection) { + case TOP -> itemAcceptorBlockEntity.outputDirection = ItemAcceptorBlockEntity.OutputDirection.BACK; + case BACK -> itemAcceptorBlockEntity.outputDirection = ItemAcceptorBlockEntity.OutputDirection.BOTTOM; + case BOTTOM -> itemAcceptorBlockEntity.outputDirection = ItemAcceptorBlockEntity.OutputDirection.TOP; + } + + this.outputDirectionToggle.setMessage(Text.translatable("gui.glowcase.output_direction", this.itemAcceptorBlockEntity.outputDirection.toString())); + }).dimensions(width / 2 - 75, height / 2 + 20, 150, 20).build(); + + this.addDrawableChild(this.itemWidget); + this.addDrawableChild(this.countWidget); + this.addDrawableChild(this.outputDirectionToggle); + } + + @Override + public void close() { + String text = itemWidget.getText(); + boolean isItemTag = text.startsWith("#"); + if (isItemTag) { + text = text.substring(1); + } + + if (!text.isEmpty() && Identifier.tryParse(text) instanceof Identifier id) { + this.itemAcceptorBlockEntity.setItem(id); + this.itemAcceptorBlockEntity.isItemTag = isItemTag; + } else { + this.itemAcceptorBlockEntity.setItem(Identifier.ofVanilla("air")); + } + + if (Ints.tryParse(countWidget.getText()) instanceof Integer integer) { + this.itemAcceptorBlockEntity.count = Math.max(0, integer); + } + + C2SEditItemAcceptorBlock.of(this.itemAcceptorBlockEntity).send(); + super.close(); + } +} diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/SpriteBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/SpriteBlockEditScreen.java index 7e21327..458a10c 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/SpriteBlockEditScreen.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/SpriteBlockEditScreen.java @@ -35,7 +35,7 @@ public void init() { } }); - this.rotationWidget = ButtonWidget.builder(Text.literal("Rotate"), (action) -> { + this.rotationWidget = ButtonWidget.builder(Text.translatable("gui.glowcase.rotate"), (action) -> { this.spriteBlockEntity.rotation += 45; if (this.spriteBlockEntity.rotation >= 360) { this.spriteBlockEntity.rotation = 0; diff --git a/src/main/java/dev/hephaestus/glowcase/client/render/block/entity/ItemAcceptorBlockEntityRenderer.java b/src/main/java/dev/hephaestus/glowcase/client/render/block/entity/ItemAcceptorBlockEntityRenderer.java new file mode 100644 index 0000000..4935bde --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/client/render/block/entity/ItemAcceptorBlockEntityRenderer.java @@ -0,0 +1,83 @@ +package dev.hephaestus.glowcase.client.render.block.entity; + +import dev.hephaestus.glowcase.Glowcase; +import dev.hephaestus.glowcase.block.ItemAcceptorBlock; +import dev.hephaestus.glowcase.block.entity.ItemAcceptorBlockEntity; +import dev.hephaestus.glowcase.mixin.client.RenderSystemAccessor; +import net.minecraft.block.BlockState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.render.DiffuseLighting; +import net.minecraft.client.render.LightmapTextureManager; +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.block.entity.BlockEntityRenderer; +import net.minecraft.client.render.block.entity.BlockEntityRendererFactory; +import net.minecraft.client.render.item.ItemRenderer; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.json.ModelTransformationMode; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.Entity; +import net.minecraft.util.Colors; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.RotationAxis; +import org.joml.Matrix4f; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public record ItemAcceptorBlockEntityRenderer(BlockEntityRendererFactory.Context context) implements BlockEntityRenderer { + private static final Quaternionf ITEM_LIGHT_ROTATION_3D = RotationAxis.POSITIVE_X.rotationDegrees(-15).mul(RotationAxis.POSITIVE_Y.rotationDegrees(15)); + private static final Quaternionf ITEM_LIGHT_ROTATION_FLAT = RotationAxis.POSITIVE_X.rotationDegrees(-45); + + public void render(ItemAcceptorBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { + Entity camera = MinecraftClient.getInstance().getCameraEntity(); + + if (camera == null) return; + + ItemRenderer itemRenderer = context.getItemRenderer(); + BakedModel itemModel = itemRenderer.getModel(entity.getDisplayItemStack(), entity.getWorld(), null, 0); + + // Render item + float yaw = 0; + matrices.push(); + matrices.translate(0.5, 0.5, 0.5); + BlockState blockState = entity.getWorld().getBlockState(entity.getPos()); + if (blockState.isOf(Glowcase.ITEM_ACCEPTOR_BLOCK.get())) { + yaw = getRotationYForSide2D(blockState.get(ItemAcceptorBlock.FACING)); + } + matrices.peek().getPositionMatrix().mul(new Matrix4f().rotateY(yaw).translate(-0.125f, 0.125f, 0.51F).scale(1, 1, 0.01F)); + matrices.scale(0.5F, 0.5F, 0.5F); + + Vector3f[] lights = new Vector3f[2]; + System.arraycopy(RenderSystemAccessor.getShaderLightDirections(), 0, lights, 0, 2); + + if (itemModel.isSideLit()) { + matrices.peek().getNormalMatrix().rotate(ITEM_LIGHT_ROTATION_3D); + DiffuseLighting.enableGuiDepthLighting(); + } else { + matrices.peek().getNormalMatrix().rotate(ITEM_LIGHT_ROTATION_FLAT); + DiffuseLighting.disableGuiDepthLighting(); + } + + itemRenderer.renderItem(entity.getDisplayItemStack(), ModelTransformationMode.GUI, light, OverlayTexture.DEFAULT_UV, matrices, vertexConsumers, entity.getWorld(), 0); + + System.arraycopy(lights, 0, RenderSystemAccessor.getShaderLightDirections(), 0, 2); + + // Render count + if (entity.count > 1) { + float scale = 0.0625F; + matrices.translate(0, 0, 1); + matrices.scale(scale, -scale, scale); + + TextRenderer textRenderer = context.getTextRenderer(); + String string = String.valueOf(entity.count); + textRenderer.draw(string, 9 - textRenderer.getWidth(string), 1, Colors.WHITE, false, matrices.peek().getPositionMatrix(), vertexConsumers, TextRenderer.TextLayerType.NORMAL, 0, LightmapTextureManager.MAX_LIGHT_COORDINATE); + } + + matrices.pop(); + } + + private static float getRotationYForSide2D(Direction side) { + return -side.asRotation() * (float) Math.PI / 180f; + } +} diff --git a/src/main/java/dev/hephaestus/glowcase/mixin/client/RenderSystemAccessor.java b/src/main/java/dev/hephaestus/glowcase/mixin/client/RenderSystemAccessor.java new file mode 100644 index 0000000..767547d --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/mixin/client/RenderSystemAccessor.java @@ -0,0 +1,15 @@ +package dev.hephaestus.glowcase.mixin.client; + +import com.mojang.blaze3d.systems.RenderSystem; +import org.joml.Vector3f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(RenderSystem.class) +public interface RenderSystemAccessor +{ + @Accessor(remap = false) + static Vector3f[] getShaderLightDirections() { + throw new AssertionError(); + } +} diff --git a/src/main/java/dev/hephaestus/glowcase/packet/C2SEditItemAcceptorBlock.java b/src/main/java/dev/hephaestus/glowcase/packet/C2SEditItemAcceptorBlock.java new file mode 100644 index 0000000..3318bd4 --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/packet/C2SEditItemAcceptorBlock.java @@ -0,0 +1,46 @@ +package dev.hephaestus.glowcase.packet; + +import dev.hephaestus.glowcase.Glowcase; +import dev.hephaestus.glowcase.block.entity.ItemAcceptorBlockEntity; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; + +public record C2SEditItemAcceptorBlock(BlockPos pos, Identifier item, int count, boolean isItemTag, ItemAcceptorBlockEntity.OutputDirection outputDirection) implements C2SEditBlockEntity { + public static final Id ID = new Id<>(Glowcase.id("channel.item_acceptor.save")); + public static final PacketCodec PACKET_CODEC = PacketCodec.tuple( + BlockPos.PACKET_CODEC, C2SEditItemAcceptorBlock::pos, + Identifier.PACKET_CODEC, C2SEditItemAcceptorBlock::item, + PacketCodecs.INTEGER, C2SEditItemAcceptorBlock::count, + PacketCodecs.BOOL, C2SEditItemAcceptorBlock::isItemTag, + PacketCodecs.BYTE.xmap(index -> ItemAcceptorBlockEntity.OutputDirection.values()[index], outputDirection -> (byte) outputDirection.ordinal()), C2SEditItemAcceptorBlock::outputDirection, + C2SEditItemAcceptorBlock::new + ); + + public static C2SEditItemAcceptorBlock of(ItemAcceptorBlockEntity be) { + return new C2SEditItemAcceptorBlock(be.getPos(), be.getItem(), be.count, be.isItemTag, be.outputDirection); + } + + @Override + public Id getId() { + return ID; + } + + @Override + public void receive(ServerWorld world, BlockEntity blockEntity) { + if (!(blockEntity instanceof ItemAcceptorBlockEntity be)) return; + + be.setItem(this.item()); + be.count = this.count(); + be.isItemTag = this.isItemTag(); + be.outputDirection = this.outputDirection(); + + be.markDirty(); + be.dispatch(); + } +} diff --git a/src/main/resources/assets/glowcase/blockstates/item_acceptor_block.json b/src/main/resources/assets/glowcase/blockstates/item_acceptor_block.json new file mode 100644 index 0000000..23ffb5a --- /dev/null +++ b/src/main/resources/assets/glowcase/blockstates/item_acceptor_block.json @@ -0,0 +1,34 @@ +{ + "variants": { + "facing=north,powered=false": { + "model": "glowcase:block/item_acceptor_block" + }, + "facing=east,powered=false": { + "model": "glowcase:block/item_acceptor_block", + "y": 90 + }, + "facing=south,powered=false": { + "model": "glowcase:block/item_acceptor_block", + "y": 180 + }, + "facing=west,powered=false": { + "model": "glowcase:block/item_acceptor_block", + "y": 270 + }, + "facing=north,powered=true": { + "model": "glowcase:block/item_acceptor_block_on" + }, + "facing=east,powered=true": { + "model": "glowcase:block/item_acceptor_block_on", + "y": 90 + }, + "facing=south,powered=true": { + "model": "glowcase:block/item_acceptor_block_on", + "y": 180 + }, + "facing=west,powered=true": { + "model": "glowcase:block/item_acceptor_block_on", + "y": 270 + } + } +} diff --git a/src/main/resources/assets/glowcase/lang/en_us.json b/src/main/resources/assets/glowcase/lang/en_us.json index 7b9382b..43fe6e6 100644 --- a/src/main/resources/assets/glowcase/lang/en_us.json +++ b/src/main/resources/assets/glowcase/lang/en_us.json @@ -7,6 +7,7 @@ "block.glowcase.popup_block": "Popup Block", "block.glowcase.sprite_block": "Sprite Block", "block.glowcase.outline_block": "Outline Block", + "block.glowcase.item_acceptor_block": "Item Acceptor Block", "gui.glowcase.scale_value": "Scale: %d", "gui.glowcase.alignment": "Alignment: %s", "gui.glowcase.gives_item": "Gives Item: %s", @@ -24,6 +25,7 @@ "gui.glowcase.url": "URL", "gui.glowcase.offset": "Offset", "gui.glowcase.scale": "Scale", + "gui.glowcase.rotate": "Rotate", "gui.glowcase.x": "X", "gui.glowcase.y": "Y", "gui.glowcase.z": "Z", @@ -35,6 +37,9 @@ "gui.glowcase.count_std_dev": "Amount Deviation", "gui.glowcase.tick_rate_mean": "Mean Tick Rate", "gui.glowcase.tick_rate_std_dev": "Tick Rate Deviation", + "gui.glowcase.item_or_tag": "Item or Item Tag", + "gui.glowcase.count": "Count", + "gui.glowcase.output_direction": "Output Direction: %s", "block.glowcase.generic.tooltip": "Interact with a glowcase item to edit", "block.glowcase.text_block.tooltip.0": "Displays formatted text", "block.glowcase.text_block.tooltip.1": "Supports Placeholder QuickText", @@ -48,5 +53,9 @@ "block.glowcase.sprite_block.tooltip.1": "Can be extended with resource packs", "block.glowcase.outline_block.tooltip.0": "Displays an outline", "block.glowcase.particle_display_block.tooltip.0": "Displays particles", + "block.glowcase.item_acceptor_block.tooltip.0": "Accepts an item stack as input", + "block.glowcase.item_acceptor_block.tooltip.1": " Pushes the item stack to an inventory", + "block.glowcase.item_acceptor_block.tooltip.2": " Emits a redstone pulse", + "block.glowcase.item_acceptor_block.tooltip.3": "Interact in Creative mode to edit", "item.glowcase.lock.tooltip.0": "Locks and unlocks containers" } diff --git a/src/main/resources/assets/glowcase/models/block/item_acceptor_block.json b/src/main/resources/assets/glowcase/models/block/item_acceptor_block.json new file mode 100644 index 0000000..3859e40 --- /dev/null +++ b/src/main/resources/assets/glowcase/models/block/item_acceptor_block.json @@ -0,0 +1,8 @@ +{ + "parent": "minecraft:block/orientable", + "textures": { + "front": "glowcase:block/item_acceptor_front", + "side": "minecraft:block/observer_side", + "top": "minecraft:block/furnace_top" + } +} diff --git a/src/main/resources/assets/glowcase/models/block/item_acceptor_block_on.json b/src/main/resources/assets/glowcase/models/block/item_acceptor_block_on.json new file mode 100644 index 0000000..d606fbf --- /dev/null +++ b/src/main/resources/assets/glowcase/models/block/item_acceptor_block_on.json @@ -0,0 +1,6 @@ +{ + "parent": "glowcase:block/item_acceptor_block", + "textures": { + "front": "glowcase:block/item_acceptor_front_on" + } +} diff --git a/src/main/resources/assets/glowcase/models/item/item_acceptor_block.json b/src/main/resources/assets/glowcase/models/item/item_acceptor_block.json new file mode 100644 index 0000000..3833156 --- /dev/null +++ b/src/main/resources/assets/glowcase/models/item/item_acceptor_block.json @@ -0,0 +1,3 @@ +{ + "parent": "glowcase:block/item_acceptor_block" +} diff --git a/src/main/resources/assets/glowcase/textures/block/invisible.png b/src/main/resources/assets/glowcase/textures/block/invisible.png new file mode 100644 index 0000000..dde5384 Binary files /dev/null and b/src/main/resources/assets/glowcase/textures/block/invisible.png differ diff --git a/src/main/resources/assets/glowcase/textures/block/item_acceptor_front.png b/src/main/resources/assets/glowcase/textures/block/item_acceptor_front.png new file mode 100644 index 0000000..74e40bf Binary files /dev/null and b/src/main/resources/assets/glowcase/textures/block/item_acceptor_front.png differ diff --git a/src/main/resources/assets/glowcase/textures/block/item_acceptor_front_on.png b/src/main/resources/assets/glowcase/textures/block/item_acceptor_front_on.png new file mode 100644 index 0000000..9039e19 Binary files /dev/null and b/src/main/resources/assets/glowcase/textures/block/item_acceptor_front_on.png differ diff --git a/src/main/resources/assets/glowcase/textures/item/particle_display.png b/src/main/resources/assets/glowcase/textures/item/particle_display.png index 644c04e..26bbfef 100644 Binary files a/src/main/resources/assets/glowcase/textures/item/particle_display.png and b/src/main/resources/assets/glowcase/textures/item/particle_display.png differ diff --git a/src/main/resources/data/glowcase/tags/item/items.json b/src/main/resources/data/glowcase/tags/item/items.json index 8867daa..e0cbe38 100644 --- a/src/main/resources/data/glowcase/tags/item/items.json +++ b/src/main/resources/data/glowcase/tags/item/items.json @@ -7,6 +7,7 @@ "glowcase:popup_block", "glowcase:sprite_block", "glowcase:outline_block", - "glowcase:particle_display" + "glowcase:particle_display", + "glowcase:item_acceptor_block" ] } diff --git a/src/main/resources/glowcase.mixins.json b/src/main/resources/glowcase.mixins.json index 6644548..58245ae 100644 --- a/src/main/resources/glowcase.mixins.json +++ b/src/main/resources/glowcase.mixins.json @@ -9,6 +9,7 @@ ], "client": [ "client.KeyboardMixin", + "client.RenderSystemAccessor", "client.TextureManagerAccessor" ], "server": [],