跳到主要内容

30方块实体

代码

block/custom目录下新建一个GemPolishingStationBlock

public class GemPolishingStationBlock extends BlockWithEntity implements BlockEntityProvider {
private static final VoxelShape SHAPE = Block.createCuboidShape(0, 0, 0, 16, 12, 16);

public GemPolishingStationBlock(Settings settings) {
super(settings);
}

@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return SHAPE;
}

@Override
public BlockRenderType getRenderType(BlockState state) {
return BlockRenderType.MODEL;
}

@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new GemPolishingStationBlockEntity(pos, state);
}

@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
if (state.getBlock() != newState.getBlock()) {
BlockEntity blockEntity = world.getBlockEntity(pos);
if (blockEntity instanceof GemPolishingStationBlockEntity) {
ItemScatterer.spawn(world, pos, (GemPolishingStationBlockEntity)blockEntity);
world.updateComparators(pos,this);
}
super.onStateReplaced(state, world, pos, newState, moved);
}
}

@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if (!world.isClient) {
NamedScreenHandlerFactory screenHandlerFactory = ((GemPolishingStationBlockEntity) world.getBlockEntity(pos));

if (screenHandlerFactory != null) {
player.openHandledScreen(screenHandlerFactory);
}
}

return ActionResult.SUCCESS;
}

@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(World world, BlockState state, BlockEntityType<T> type) {
return validateTicker(type, ModBlockEntities.GEM_POLISHING_STATION_BLOCK_ENTITY,
(world1, pos, state1, blockEntity) -> blockEntity.tick(world1, pos, state1));
}
}

block目录下新建一个entity目录,有三个类:

ModBlockEntities:

public class ModBlockEntities {

public static final BlockEntityType<GemPolishingStationBlockEntity> GEM_POLISHING_STATION_BLOCK_ENTITY =
Registry.register(Registries.BLOCK_ENTITY_TYPE, new Identifier(TutorialMod.MOD_ID, "gem_polishing_be"),
FabricBlockEntityTypeBuilder.create(GemPolishingStationBlockEntity::new,
ModBlocks.GEM_POLISHING_STATION).build());

public static void registerBlockEntities() {
TutorialMod.LOGGER.info("Registered block entities");
}
}

GemPolishingStationBlockEntity:

public class GemPolishingStationBlockEntity extends BlockEntity implements ExtendedScreenHandlerFactory, ImplementedInventory {
private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(2, ItemStack.EMPTY);

private static final int INPUT_SLOT = 0;
private static final int OUTPUT_SLOT = 1;

protected final PropertyDelegate propertyDelegate;
private int progress = 0;
private int maxProgress = 72;

public GemPolishingStationBlockEntity(BlockPos pos, BlockState state) {
super(ModBlockEntities.GEM_POLISHING_STATION_BLOCK_ENTITY, pos, state);
this.propertyDelegate = new PropertyDelegate() {
@Override
public int get(int index) {
return switch (index) {
case 0 -> GemPolishingStationBlockEntity.this.progress;
case 1 -> GemPolishingStationBlockEntity.this.maxProgress;
default -> 0;
};
}

@Override
public void set(int index, int value) {
switch (index) {
case 0 -> GemPolishingStationBlockEntity.this.progress = value;
case 1 -> GemPolishingStationBlockEntity.this.maxProgress = value;
}
}

@Override
public int size() {
return 2;
}
};
}

@Override
public void writeScreenOpeningData(ServerPlayerEntity player, PacketByteBuf buf) {
buf.writeBlockPos(this.pos);
}

@Override
public Text getDisplayName() {
return Text.literal("Gem Polishing Station");
}

@Override
public DefaultedList<ItemStack> getItems() {
return inventory;
}

@Override
protected void writeNbt(NbtCompound nbt) {
super.writeNbt(nbt);
Inventories.writeNbt(nbt, inventory);
nbt.putInt("gem_polishing_station.progress", progress);
}

@Override
public void readNbt(NbtCompound nbt) {
super.readNbt(nbt);
Inventories.readNbt(nbt, inventory);
progress = nbt.getInt("gem_polishing_station.progress");
}

@Nullable
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
return new GemPolishingScreenHandler(syncId, playerInventory, this, this.propertyDelegate);
}

public void tick(World world, BlockPos pos, BlockState state) {
if(world.isClient()) {
return;
}

if(isOutputSlotEmptyOrReceivable()) {
if(this.hasRecipe()) {
this.increaseCraftProgress();
markDirty(world, pos, state);

if(hasCraftingFinished()) {
this.craftItem();
this.resetProgress();
}
} else {
this.resetProgress();
}
} else {
this.resetProgress();
markDirty(world, pos, state);
}
}

private void resetProgress() {
this.progress = 0;
}

private void craftItem() {
this.removeStack(INPUT_SLOT, 1);
ItemStack result = new ItemStack(ModItems.RUBY);

this.setStack(OUTPUT_SLOT, new ItemStack(result.getItem(), getStack(OUTPUT_SLOT).getCount() + result.getCount()));
}

private boolean hasCraftingFinished() {
return progress >= maxProgress;
}

private void increaseCraftProgress() {
progress++;
}

private boolean hasRecipe() {
ItemStack result = new ItemStack(ModItems.RUBY);
boolean hasInput = getStack(INPUT_SLOT).getItem() == ModItems.RAW_RUBY;

return hasInput && canInsertAmountIntoOutputSlot(result) && canInsertItemIntoOutputSlot(result.getItem());
}

private boolean canInsertItemIntoOutputSlot(Item item) {
return this.getStack(OUTPUT_SLOT).getItem() == item || this.getStack(OUTPUT_SLOT).isEmpty();
}

private boolean canInsertAmountIntoOutputSlot(ItemStack result) {
return this.getStack(OUTPUT_SLOT).getCount() + result.getCount() <= getStack(OUTPUT_SLOT).getMaxCount();
}

private boolean isOutputSlotEmptyOrReceivable() {
return this.getStack(OUTPUT_SLOT).isEmpty() || this.getStack(OUTPUT_SLOT).getCount() < this.getStack(OUTPUT_SLOT).getMaxCount();
}
}

ImplementedInventory:

package net.kaupenjoe.tutorialmod.block.entity;

import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.Inventories;
import net.minecraft.inventory.Inventory;
import net.minecraft.inventory.SidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.Nullable;

import java.util.List;

/**
* A simple {@code SidedInventory} implementation with only default methods + an item list getter.
*
* <h2>Reading and writing to tags</h2>
* Use {@link Inventories#writeNbt(NbtCompound, DefaultedList)} and {@link Inventories#readNbt(NbtCompound, DefaultedList)}
* on {@linkplain #getItems() the item list}.
*
* License: <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0</a>
* @author Juuz
*/
@FunctionalInterface
public interface ImplementedInventory extends SidedInventory {
/**
* Gets the item list of this inventory.
* Must return the same instance every time it's called.
*
* @return the item list
*/
DefaultedList<ItemStack> getItems();

/**
* Creates an inventory from the item list.
*
* @param items the item list
* @return a new inventory
*/
static ImplementedInventory of(DefaultedList<ItemStack> items) {
return () -> items;
}

/**
* Creates a new inventory with the size.
*
* @param size the inventory size
* @return a new inventory
*/
static ImplementedInventory ofSize(int size) {
return of(DefaultedList.ofSize(size, ItemStack.EMPTY));
}

// SidedInventory

/**
* Gets the available slots to automation on the side.
*
* <p>The default implementation returns an array of all slots.
*
* @param side the side
* @return the available slots
*/
@Override
default int[] getAvailableSlots(Direction side) {
int[] result = new int[getItems().size()];
for (int i = 0; i < result.length; i++) {
result[i] = i;
}

return result;
}

/**
* Returns true if the stack can be inserted in the slot at the side.
*
* <p>The default implementation returns true.
*
* @param slot the slot
* @param stack the stack
* @param side the side
* @return true if the stack can be inserted
*/
@Override
default boolean canInsert(int slot, ItemStack stack, @Nullable Direction side) {
return true;
}

/**
* Returns true if the stack can be extracted from the slot at the side.
*
* <p>The default implementation returns true.
*
* @param slot the slot
* @param stack the stack
* @param side the side
* @return true if the stack can be extracted
*/
@Override
default boolean canExtract(int slot, ItemStack stack, Direction side) {
return true;
}

// Inventory

/**
* Returns the inventory size.
*
* <p>The default implementation returns the size of {@link #getItems()}.
*
* @return the inventory size
*/
@Override
default int size() {
return getItems().size();
}

/**
* @return true if this inventory has only empty stacks, false otherwise
*/
@Override
default boolean isEmpty() {
for (int i = 0; i < size(); i++) {
ItemStack stack = getStack(i);
if (!stack.isEmpty()) {
return false;
}
}

return true;
}

/**
* Gets the item in the slot.
*
* @param slot the slot
* @return the item in the slot
*/
@Override
default ItemStack getStack(int slot) {
return getItems().get(slot);
}

/**
* Takes a stack of the size from the slot.
*
* <p>(default implementation) If there are less items in the slot than what are requested,
* takes all items in that slot.
*
* @param slot the slot
* @param count the item count
* @return a stack
*/
@Override
default ItemStack removeStack(int slot, int count) {
ItemStack result = Inventories.splitStack(getItems(), slot, count);
if (!result.isEmpty()) {
markDirty();
}

return result;
}

/**
* Removes the current stack in the {@code slot} and returns it.
*
* <p>The default implementation uses {@link Inventories#removeStack(List, int)}
*
* @param slot the slot
* @return the removed stack
*/
@Override
default ItemStack removeStack(int slot) {
return Inventories.removeStack(getItems(), slot);
}

/**
* Replaces the current stack in the {@code slot} with the provided stack.
*
* <p>If the stack is too big for this inventory ({@link Inventory#getMaxCountPerStack()}),
* it gets resized to this inventory's maximum amount.
*
* @param slot the slot
* @param stack the stack
*/
@Override
default void setStack(int slot, ItemStack stack) {
getItems().set(slot, stack);
if (stack.getCount() > getMaxCountPerStack()) {
stack.setCount(getMaxCountPerStack());
}
markDirty();
}

/**
* Clears {@linkplain #getItems() the item list}}.
*/
@Override
default void clear() {
getItems().clear();
}

@Override
default void markDirty() {
// Override if you want behavior.
}

@Override
default boolean canPlayerUse(PlayerEntity player) {
return true;
}
}

在ModBlocks中注册宝石抛光台块

public static final Block GEM_POLISHING_STATION = registerBlock("gem_polishing_station",
new GemPolishingStationBlock(FabricBlockSettings.copyOf(Blocks.IRON_BLOCK).nonOpaque()));

将其加入到组中:ModItemGroups

entries.add(ModBlocks.GEM_POLISHING_STATION);

为了实现GUI模块:screen目录:

GemPolishingScreen:

public class GemPolishingScreen extends HandledScreen<GemPolishingScreenHandler> {
private static final Identifier TEXTURE = new Identifier(TutorialMod.MOD_ID, "textures/gui/gem_polishing_station_gui.png");

public GemPolishingScreen(GemPolishingScreenHandler handler, PlayerInventory inventory, Text title) {
super(handler, inventory, title);
}

@Override
protected void init() {
super.init();
titleY = 1000;
playerInventoryTitleY = 1000;
}

@Override
protected void drawBackground(DrawContext context, float delta, int mouseX, int mouseY) {
RenderSystem.setShader(GameRenderer::getPositionTexProgram);
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
RenderSystem.setShaderTexture(0, TEXTURE);
int x = (width - backgroundWidth) / 2;
int y = (height - backgroundHeight) / 2;

context.drawTexture(TEXTURE, x, y, 0, 0, backgroundWidth, backgroundHeight);

renderProgressArrow(context, x, y);
}

private void renderProgressArrow(DrawContext context, int x, int y) {
if(handler.isCrafting()) {
context.drawTexture(TEXTURE, x + 85, y + 30, 176, 0, 8, handler.getScaledProgress());
}
}

@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
renderBackground(context, mouseX, mouseY, delta);
super.render(context, mouseX, mouseY, delta);
drawMouseoverTooltip(context, mouseX, mouseY);
}
}

GemPolishingScreenHandler:

public class GemPolishingScreenHandler extends ScreenHandler {
private final Inventory inventory;
private final PropertyDelegate propertyDelegate;
public final GemPolishingStationBlockEntity blockEntity;

public GemPolishingScreenHandler(int syncId, PlayerInventory inventory, PacketByteBuf buf) {
this(syncId, inventory, inventory.player.getWorld().getBlockEntity(buf.readBlockPos()),
new ArrayPropertyDelegate(2));
}

public GemPolishingScreenHandler(int syncId, PlayerInventory playerInventory,
BlockEntity blockEntity, PropertyDelegate arrayPropertyDelegate) {
super(ModScreenHandlers.GEM_POLISHING_SCREEN_HANDLER, syncId);
checkSize(((Inventory) blockEntity), 2);
this.inventory = ((Inventory) blockEntity);
inventory.onOpen(playerInventory.player);
this.propertyDelegate = arrayPropertyDelegate;
this.blockEntity = ((GemPolishingStationBlockEntity) blockEntity);

this.addSlot(new Slot(inventory, 0, 80, 11));
this.addSlot(new Slot(inventory, 1, 80, 59));


addPlayerInventory(playerInventory);
addPlayerHotbar(playerInventory);

addProperties(arrayPropertyDelegate);
}

public boolean isCrafting() {
return propertyDelegate.get(0) > 0;
}

public int getScaledProgress() {
int progress = this.propertyDelegate.get(0);
int maxProgress = this.propertyDelegate.get(1); // Max Progress
int progressArrowSize = 26; // This is the width in pixels of your arrow

return maxProgress != 0 && progress != 0 ? progress * progressArrowSize / maxProgress : 0;
}

@Override
public ItemStack quickMove(PlayerEntity player, int invSlot) {
ItemStack newStack = ItemStack.EMPTY;
Slot slot = this.slots.get(invSlot);
if (slot != null && slot.hasStack()) {
ItemStack originalStack = slot.getStack();
newStack = originalStack.copy();
if (invSlot < this.inventory.size()) {
if (!this.insertItem(originalStack, this.inventory.size(), this.slots.size(), true)) {
return ItemStack.EMPTY;
}
} else if (!this.insertItem(originalStack, 0, this.inventory.size(), false)) {
return ItemStack.EMPTY;
}

if (originalStack.isEmpty()) {
slot.setStack(ItemStack.EMPTY);
} else {
slot.markDirty();
}
}

return newStack;
}

@Override
public boolean canUse(PlayerEntity player) {
return this.inventory.canPlayerUse(player);
}

private void addPlayerInventory(PlayerInventory playerInventory) {
for (int i = 0; i < 3; ++i) {
for (int l = 0; l < 9; ++l) {
this.addSlot(new Slot(playerInventory, l + i * 9 + 9, 8 + l * 18, 84 + i * 18));
}
}
}

private void addPlayerHotbar(PlayerInventory playerInventory) {
for (int i = 0; i < 9; ++i) {
this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142));
}
}
}

ModScreenHandlers:

public class ModScreenHandlers {
public static final ScreenHandlerType<GemPolishingScreenHandler> GEM_POLISHING_SCREEN_HANDLER =
Registry.register(Registries.SCREEN_HANDLER, new Identifier(TutorialMod.MOD_ID, "gem_polishing"),
new ExtendedScreenHandlerType<>(GemPolishingScreenHandler::new));

public static void registerScreenHandlers() {
TutorialMod.LOGGER.info("Registering Screen Handlers for " + TutorialMod.MOD_ID);
}
}

TutorialMod:

ModBlockEntities.registerBlockEntities();
ModScreenHandlers.registerScreenHandlers();

TutorialModClient:

HandledScreens.register(ModScreenHandlers.GEM_POLISHING_SCREEN_HANDLER, GemPolishingScreen::new);

效果

可以将红宝石原矿变为红宝石

image-20240825161138497