Minecraft1.12.2 方块实体教程
MC中有许多很有趣的方块实体如旗帜、钟、附魔台…我们今天在1.18的版本下实现一个类似于熔炉的方块实体。
1.在blocks包中新建一个我们的方块实体包virusgenerator,包中新建一个我们的方块类VirusGeneratorBlock
:
VirusGeneratorBlock.java
package com.joy187.re8joymod.blocks.virusgenerator;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraftforge.network.NetworkHooks;
import org.jetbrains.annotations.Nullable;
import com.joy187.re8joymod.init.BlockEntityInit;
public class VirusGeneratorBlock extends BaseEntityBlock {
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public static final BooleanProperty LIT = BlockStateProperties.LIT;
public VirusGeneratorBlock(Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(LIT, Boolean.valueOf(false)));
}
private static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 15, 16);
@Override
public VoxelShape getShape(BlockState pState, BlockGetter pLevel, BlockPos pPos, CollisionContext pContext) {
return SHAPE;
}
/* FACING */
@Override
public BlockState getStateForPlacement(BlockPlaceContext pContext) {
return this.defaultBlockState().setValue(FACING, pContext.getHorizontalDirection().getOpposite());
}
@Override
public BlockState rotate(BlockState pState, Rotation pRotation) {
return pState.setValue(FACING, pRotation.rotate(pState.getValue(FACING)));
}
@Override
public BlockState mirror(BlockState pState, Mirror pMirror) {
return pState.rotate(pMirror.getRotation(pState.getValue(FACING)));
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> pBuilder) {
pBuilder.add(FACING,LIT);
}
@Override
public RenderShape getRenderShape(BlockState pState) {
return RenderShape.MODEL;
}
@Override
public void onRemove(BlockState pState, Level pLevel, BlockPos pPos, BlockState pNewState, boolean pIsMoving) {
if (pState.getBlock() != pNewState.getBlock()) {
BlockEntity blockEntity = pLevel.getBlockEntity(pPos);
if (blockEntity instanceof VirusGeneratorBlockEntity) {
((VirusGeneratorBlockEntity) blockEntity).drops();
}
}
super.onRemove(pState, pLevel, pPos, pNewState, pIsMoving);
}
@Override
public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos,
Player pPlayer, InteractionHand pHand, BlockHitResult pHit) {
if (!pLevel.isClientSide()) {
BlockEntity entity = pLevel.getBlockEntity(pPos);
if(entity instanceof VirusGeneratorBlockEntity) {
NetworkHooks.openGui(((ServerPlayer)pPlayer), (VirusGeneratorBlockEntity)entity, pPos);
} else {
throw new IllegalStateException("Our Container provider is missing!");
}
}
return InteractionResult.sidedSuccess(pLevel.isClientSide());
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) {
return new VirusGeneratorBlockEntity(pPos, pState);
}
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level pLevel, BlockState pState, BlockEntityType<T> pBlockEntityType) {
return createTickerHelper(pBlockEntityType, BlockEntityInit.VIRUS_GENERATOR_BLOCK_ENTITY.get(),
VirusGeneratorBlockEntity::tick);
}
}
在BlockInit中注册我们的方块:
public static final RegistryObject<Block> VIRUS_GENERATOR_BLOCK = registerBlock("virus_generator",
() -> new VirusGeneratorBlock(BlockBehaviour.Properties.copy(Blocks.IRON_BLOCK).noOcclusion()),
Main.TUTORIAL_TAB);
2.在virusgenerator包中新建一个我们的实体类VirusGeneratorBlockEntity
:
VirusGeneratorBlockEntity.java
package com.joy187.re8joymod.blocks.virusgenerator;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.world.Containers;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.alchemy.PotionUtils;
import net.minecraft.world.item.alchemy.Potions;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.AbstractFurnaceBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemStackHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.joy187.re8joymod.init.BlockEntityInit;
import com.joy187.re8joymod.init.ItemInit;
import com.joy187.re8joymod.screen.VirusGeneratorMenu;
import javax.annotation.Nonnull;
import java.util.Optional;
import java.util.Random;
public class VirusGeneratorBlockEntity extends BlockEntity implements MenuProvider {
private BlockState blockState;
private final ItemStackHandler itemHandler = new ItemStackHandler(4) {
@Override
protected void onContentsChanged(int slot) {
setChanged();
}
};
private LazyOptional<IItemHandler> lazyItemHandler = LazyOptional.empty();
protected final ContainerData data;
private int progress = 0;
//你熔炼一个物品所需要的时间刻tick
private int maxProgress = 72;
public VirusGeneratorBlockEntity(BlockPos pWorldPosition, BlockState pBlockState) {
super(BlockEntityInit.VIRUS_GENERATOR_BLOCK_ENTITY.get(), pWorldPosition, pBlockState);
this.data = new ContainerData() {
public int get(int index) {
switch (index) {
case 0: return VirusGeneratorBlockEntity.this.progress;
case 1: return VirusGeneratorBlockEntity.this.maxProgress;
default: return 0;
}
}
public void set(int index, int value) {
switch(index) {
case 0: VirusGeneratorBlockEntity.this.progress = value; break;
case 1: VirusGeneratorBlockEntity.this.maxProgress = value; break;
}
}
public int getCount() {
return 2;
}
};
}
@Override
public Component getDisplayName() {
return new TranslatableComponent("container.virus_generator");
}
// protected Component getDefaultName() {
// return new TranslatableComponent("container.virus_generator");
// }
@Nullable
@Override
public AbstractContainerMenu createMenu(int pContainerId, Inventory pInventory, Player pPlayer) {
return new VirusGeneratorMenu(pContainerId, pInventory, this, this.data);
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @javax.annotation.Nullable Direction side) {
if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
return lazyItemHandler.cast();
}
return super.getCapability(cap, side);
}
@Override
public void onLoad() {
super.onLoad();
lazyItemHandler = LazyOptional.of(() -> itemHandler);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
lazyItemHandler.invalidate();
}
@Override
protected void saveAdditional(@NotNull CompoundTag tag) {
tag.put("inventory", itemHandler.serializeNBT());
tag.putInt("virus_generator.progress", progress);
super.saveAdditional(tag);
}
@Override
public void load(CompoundTag nbt) {
super.load(nbt);
itemHandler.deserializeNBT(nbt.getCompound("inventory"));
progress = nbt.getInt("virus_generator.progress");
}
public void drops() {
SimpleContainer inventory = new SimpleContainer(itemHandler.getSlots());
for (int i = 0; i < itemHandler.getSlots(); i++) {
inventory.setItem(i, itemHandler.getStackInSlot(i));
}
Containers.dropContents(this.level, this.worldPosition, inventory);
}
public static void tick(Level pLevel, BlockPos pPos, BlockState pState, VirusGeneratorBlockEntity pBlockEntity) {
if(hasRecipe(pBlockEntity)) {
pState.setValue(VirusGeneratorBlock.LIT, Boolean.valueOf(true));
pBlockEntity.progress++;
setChanged(pLevel, pPos, pState);
if(pBlockEntity.progress > pBlockEntity.maxProgress) {
craftItem(pBlockEntity);
}
} else {
pState.setValue(VirusGeneratorBlock.LIT, Boolean.valueOf(false));
pBlockEntity.resetProgress();
setChanged(pLevel, pPos, pState);
}
// if(hasRecipe(pBlockEntity) && notReachLimit(pBlockEntity)) {
// craftItem(pBlockEntity);
// }
}
private static boolean hasRecipe(VirusGeneratorBlockEntity entity) {
Level level = entity.level;
SimpleContainer inventory = new SimpleContainer(entity.itemHandler.getSlots());
for (int i = 0; i < entity.itemHandler.getSlots(); i++) {
inventory.setItem(i, entity.itemHandler.getStackInSlot(i));
}
Optional<VirusGeneratorRecipe> match = level.getRecipeManager()
.getRecipeFor(VirusGeneratorRecipe.Type.INSTANCE, inventory, level);
return match.isPresent() && canInsertAmountIntoOutputSlot(inventory)
&& canInsertItemIntoOutputSlot(inventory, match.get().getResultItem())
&& hasFuelSlot(entity); // && has0Slot(entity) && has1Slot(entity);
// boolean canUse = has0Slot(entity) && hasFuelSlot(entity) && has1Slot(entity);
// if(canUse) entity.blockState = entity.blockState.setValue(VirusGeneratorBlock.LIT, Boolean.valueOf(true));
// else entity.blockState = entity.blockState.setValue(VirusGeneratorBlock.LIT, Boolean.valueOf(false));
// return canUse;
}
// private static boolean has0Slot(VirusGeneratorBlockEntity entity) {
// return entity.itemHandler.getStackInSlot(0).getItem() == Items.ROTTEN_FLESH;
// }
// private static boolean has1Slot(VirusGeneratorBlockEntity entity) {
// return entity.itemHandler.getStackInSlot(1).getItem() == ItemInit.LYEYE.get();
// }
//你的燃料槽
private static boolean hasFuelSlot(VirusGeneratorBlockEntity entity) {
return entity.itemHandler.getStackInSlot(2).getItem() == ItemInit.BLACKSHEEP.get()
|| entity.itemHandler.getStackInSlot(2).getItem() == Items.FERMENTED_SPIDER_EYE;
}
//检查物品槽是否放了原料
private static void craftItem(VirusGeneratorBlockEntity entity) {
Level level = entity.level;
SimpleContainer inventory = new SimpleContainer(entity.itemHandler.getSlots());
for (int i = 0; i < entity.itemHandler.getSlots(); i++) {
inventory.setItem(i, entity.itemHandler.getStackInSlot(i));
}
//检查是否有这个配方
Optional<VirusGeneratorRecipe> match = level.getRecipeManager()
.getRecipeFor(VirusGeneratorRecipe.Type.INSTANCE, inventory, level);
if(match.isPresent()) {
//if(has0Slot(entity) && has1Slot(entity) && hasFuelSlot(entity)) {
entity.itemHandler.extractItem(0,1, false);
entity.itemHandler.extractItem(1,1, false);
entity.itemHandler.extractItem(2,1, false);
//产出物品
entity.itemHandler.setStackInSlot(3, new ItemStack(match.get().getResultItem().getItem(),
entity.itemHandler.getStackInSlot(3).getCount() + 1));
// entity.itemHandler.setStackInSlot(3, new ItemStack(ItemInit.HUMUS.get(),
// entity.itemHandler.getStackInSlot(3).getCount() + 1));
entity.resetProgress();
}
}
private void resetProgress() {
this.progress = 0;
}
//我们设定了3号槽是产物槽
private static boolean canInsertItemIntoOutputSlot(SimpleContainer inventory, ItemStack output) {
return inventory.getItem(3).getItem() == output.getItem() || inventory.getItem(3).isEmpty();
}
private static boolean canInsertAmountIntoOutputSlot(SimpleContainer inventory) {
return inventory.getItem(3).getMaxStackSize() > inventory.getItem(3).getCount();
}
private static boolean notReachLimit(VirusGeneratorBlockEntity entity) {
return entity.itemHandler.getStackInSlot(3).getCount()<64;
}
// @Override
// public BlockState getBlockState() {
// return this.blockState;
// }
}
在init包中新建一个BlockEntityInit
类,注册我们模组中所有的方块实体:
BlockEntityInit.java
package com.joy187.re8joymod.init;
import com.joy187.re8joymod.Main;
import com.joy187.re8joymod.blocks.virusgenerator.VirusGeneratorBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class BlockEntityInit {
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES =
DeferredRegister.create(ForgeRegistries.BLOCK_ENTITIES, Main.MOD_ID);
public static final RegistryObject<BlockEntityType<VirusGeneratorBlockEntity>> VIRUS_GENERATOR_BLOCK_ENTITY =
BLOCK_ENTITIES.register("virus_generator_block_entity", () ->
BlockEntityType.Builder.of(VirusGeneratorBlockEntity::new,
BlockInit.VIRUS_GENERATOR_BLOCK.get()).build(null));
// public static final RegistryObject<BlockEntityType<GeneratorBlockEntity>> GENERATOR_BLOCK_ENTITY =
// BLOCK_ENTITIES.register("generator_block_entity", () ->
// BlockEntityType.Builder.of(GeneratorBlockEntity::new,
// BlockInit.GENERATOR_BLOCK.get()).build(null));
public static void register(IEventBus eventBus) {
BLOCK_ENTITIES.register(eventBus);
}
}
在我们的项目主类中的Main函数中将BlockEntityInit
类进行注册:
public Main() {
IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
ItemInit.ITEMS.register(bus);
BlockInit.BLOCKS.register(bus);
EntityInit.ENTITY_TYPES.register(bus);
EffectInit.EFFECTS.register(bus);
PotionInit.POTIONS.register(bus);
SoundInit.SOUNDS.register(bus);
//添加这个
BlockEntityInit.register(bus);
MinecraftForge.EVENT_BUS.register(this);
}
3.在virusgenerator包中新建一个我们的配方类VirusGeneratorRecipe
:
VirusGeneratorRecipe.java
package com.joy187.re8joymod.blocks.virusgenerator;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.joy187.re8joymod.Main;
import com.joy187.re8joymod.init.BlockInit;
import com.joy187.re8joymod.init.ItemInit;
import net.minecraft.core.NonNullList;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.*;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nullable;
public class VirusGeneratorRecipe implements Recipe<SimpleContainer> {
private final ResourceLocation id;
private final ItemStack output;
private final NonNullList<Ingredient> recipeItems;
public VirusGeneratorRecipe(ResourceLocation id, ItemStack output,
NonNullList<Ingredient> recipeItems) {
this.id = id;
this.output = output;
this.recipeItems = recipeItems;
}
@Override
public boolean matches(SimpleContainer pContainer, Level pLevel) {
return recipeItems.get(0).test(pContainer.getItem(1))
&& recipeItems.get(1).test(pContainer.getItem(0));
}
@Override
public NonNullList<Ingredient> getIngredients() {
return recipeItems;
}
@Override
public ItemStack assemble(SimpleContainer pContainer) {
return output;
}
@Override
public boolean canCraftInDimensions(int pWidth, int pHeight) {
return true;
}
@Override
public ItemStack getResultItem() {
return output.copy();
}
@Override
public ResourceLocation getId() {
return id;
}
@Override
public RecipeSerializer<?> getSerializer() {
return Serializer.INSTANCE;
}
@Override
public RecipeType<?> getType() {
return Type.INSTANCE;
}
public static class Type implements RecipeType<VirusGeneratorRecipe> {
private Type() { }
public static final Type INSTANCE = new Type();
//这个就是我们之后的配方类的字段
public static final String ID = "virus_generator";
}
public static class Serializer implements RecipeSerializer<VirusGeneratorRecipe> {
public static final Serializer INSTANCE = new Serializer();
public static final ResourceLocation ID = new ResourceLocation(Main.MOD_ID,"virus_generator");
@Override
public VirusGeneratorRecipe fromJson(ResourceLocation id, JsonObject json) {
ItemStack output = ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "output"));
JsonArray ingredients = GsonHelper.getAsJsonArray(json, "ingredients");
//这里说明一共有几个原料,我们需要2个,就写2,需要3个,就改成3
NonNullList<Ingredient> inputs = NonNullList.withSize(2, Ingredient.EMPTY);
for (int i = 0; i < inputs.size(); i++) {
inputs.set(i, Ingredient.fromJson(ingredients.get(i)));
}
return new VirusGeneratorRecipe(id, output, inputs);
}
@Override
public VirusGeneratorRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf buf) {
NonNullList<Ingredient> inputs = NonNullList.withSize(buf.readInt(), Ingredient.EMPTY);
for (int i = 0; i < inputs.size(); i++) {
inputs.set(i, Ingredient.fromNetwork(buf));
}
ItemStack output = buf.readItem();
return new VirusGeneratorRecipe(id, output, inputs);
}
@Override
public void toNetwork(FriendlyByteBuf buf, VirusGeneratorRecipe recipe) {
buf.writeInt(recipe.getIngredients().size());
for (Ingredient ing : recipe.getIngredients()) {
ing.toNetwork(buf);
}
buf.writeItemStack(recipe.getResultItem(), false);
}
@Override
public RecipeSerializer<?> setRegistryName(ResourceLocation name) {
return INSTANCE;
}
@Nullable
@Override
public ResourceLocation getRegistryName() {
return ID;
}
@Override
public Class<RecipeSerializer<?>> getRegistryType() {
return Serializer.castClass(RecipeSerializer.class);
}
@SuppressWarnings("unchecked") // Need this wrapper, because generics
private static <G> Class<G> castClass(Class<?> cls) {
return (Class<G>)cls;
}
}
}
4.方块和实体的代码部分结束,进入到GUI的制作环节。在Java包中新建一个screen包 -> screen包中新建VirusGeneratorScreen
类指明我们GUI贴图存放的位置:
VirusGeneratorScreen.java
package com.joy187.re8joymod.screen;
import com.joy187.re8joymod.Main;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Inventory;
public class VirusGeneratorScreen extends AbstractContainerScreen<VirusGeneratorMenu> {
//指明我们材质的位置
private static final ResourceLocation TEXTURE =
new ResourceLocation(Main.MOD_ID, "textures/gui/virus_generator.png");
public VirusGeneratorScreen(VirusGeneratorMenu pMenu, Inventory pPlayerInventory, Component pTitle) {
super(pMenu, pPlayerInventory, pTitle);
}
@Override
protected void renderBg(PoseStack pPoseStack, float pPartialTick, int pMouseX, int pMouseY) {
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
RenderSystem.setShaderTexture(0, TEXTURE);
int x = (width - imageWidth) / 2;
int y = (height - imageHeight) / 2;
this.blit(pPoseStack, x, y, 0, 0, imageWidth, imageHeight);
if(menu.isCrafting()) {
//blit(pPoseStack, x + 102, y + 41, 176, 0, 8, menu.getScaledProgress());
blit(pPoseStack, x + 8, y + 54+12-13, 176, 12-13, 14, menu.getScaledProgress());
}
}
//this.drawTexturedModalRect(this.guiLeft + 8, this.guiTop + 54 + 12 - k, 176, 12 - k, 14, k + 1);
@Override
public void render(PoseStack pPoseStack, int mouseX, int mouseY, float delta) {
renderBackground(pPoseStack);
super.render(pPoseStack, mouseX, mouseY, delta);
renderTooltip(pPoseStack, mouseX, mouseY);
}
}
在我们项目主类的clientSetup()函数中将screen时间进行注册:
private void clientSetup(final FMLClientSetupEvent event) {
//EntityRenderers.register(EntityInit.DUND1.get(), RenderDund::new);
ItemBlockRenderTypes.setRenderLayer(BlockInit.EBONY_LEAVES.get(), RenderType.cutout());
ItemBlockRenderTypes.setRenderLayer(BlockInit.HERB_BLOCK.get(), RenderType.cutout());
ItemBlockRenderTypes.setRenderLayer(BlockInit.EBONY_SAPLING.get(), RenderType.cutout());
//添加这个,第一个参数机器方块的GUI,后面是这个机器方块的screen
MenuScreens.register(MenuInit.VIRUS_GENERATOR_MENU.get(), VirusGeneratorScreen::new);
}
screen包中新建VirusGeneratorMenu
类将所有的槽位的位置都指出来:
VirusGeneratorMenu.java
package com.joy187.re8joymod.screen;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.*;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.SlotItemHandler;
import org.jetbrains.annotations.Nullable;
import com.joy187.re8joymod.blocks.virusgenerator.VirusGeneratorBlock;
import com.joy187.re8joymod.blocks.virusgenerator.VirusGeneratorBlockEntity;
import com.joy187.re8joymod.init.BlockInit;
import com.joy187.re8joymod.init.MenuInit;
import com.joy187.re8joymod.screen.slot.ModResultSlot;
public class VirusGeneratorMenu extends AbstractContainerMenu {
private final VirusGeneratorBlockEntity blockEntity;
private final Level level;
private final ContainerData data;
public VirusGeneratorMenu(int pContainerId, Inventory inv, FriendlyByteBuf extraData) {
this(pContainerId, inv, inv.player.level.getBlockEntity(extraData.readBlockPos()), new SimpleContainerData(2));
}
public VirusGeneratorMenu(int pContainerId, Inventory inv, BlockEntity entity, ContainerData data) {
super(MenuInit.VIRUS_GENERATOR_MENU.get(), pContainerId);
checkContainerSize(inv, 4);
blockEntity = ((VirusGeneratorBlockEntity) entity);
this.level = inv.player.level;
this.data = data;
addPlayerInventory(inv);
addPlayerHotbar(inv);
//将所有我们的槽位的位置都标注出来
this.blockEntity.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).ifPresent(handler -> {
this.addSlot(new SlotItemHandler(handler, 0, 26, 11));
this.addSlot(new SlotItemHandler(handler, 1, 26, 59));
this.addSlot(new SlotItemHandler(handler, 2, 7, 35));
this.addSlot(new ModResultSlot(handler, 3, 81, 36));
});
addDataSlots(data);
}
public boolean isCrafting() {
return data.get(0) > 0;
}
//这个是播放我们方块工作时的执行进度的动画,类似于熔炉燃烧时中间的进度条
public int getScaledProgress() {
int progress = this.data.get(0);
int maxProgress = this.data.get(1); // Max Progress
//我们的进度条高13像素
int progressArrowSize = 13;
return maxProgress != 0 && progress != 0 ? progress * progressArrowSize / maxProgress : 0;
}
// CREDIT GOES TO: diesieben07 | https://github.com/diesieben07/SevenCommons
// must assign a slot number to each of the slots used by the GUI.
// For this container, we can see both the tile inventory's slots as well as the player inventory slots and the hotbar.
// Each time we add a Slot to the container, it automatically increases the slotIndex, which means
// 0 - 8 = hotbar slots (which will map to the InventoryPlayer slot numbers 0 - 8)
// 9 - 35 = player inventory slots (which map to the InventoryPlayer slot numbers 9 - 35)
// 36 - 44 = TileInventory slots, which map to our TileEntity slot numbers 0 - 8)
private static final int HOTBAR_SLOT_COUNT = 9;
private static final int PLAYER_INVENTORY_ROW_COUNT = 3;
private static final int PLAYER_INVENTORY_COLUMN_COUNT = 9;
private static final int PLAYER_INVENTORY_SLOT_COUNT = PLAYER_INVENTORY_COLUMN_COUNT * PLAYER_INVENTORY_ROW_COUNT;
private static final int VANILLA_SLOT_COUNT = HOTBAR_SLOT_COUNT + PLAYER_INVENTORY_SLOT_COUNT;
private static final int VANILLA_FIRST_SLOT_INDEX = 0;
private static final int TE_INVENTORY_FIRST_SLOT_INDEX = VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT;
// THIS YOU HAVE TO DEFINE!
private static final int TE_INVENTORY_SLOT_COUNT = 4; // must be the number of slots you have!
@Override
public ItemStack quickMoveStack(Player playerIn, int index) {
Slot sourceSlot = slots.get(index);
if (sourceSlot == null || !sourceSlot.hasItem()) return ItemStack.EMPTY; //EMPTY_ITEM
ItemStack sourceStack = sourceSlot.getItem();
ItemStack copyOfSourceStack = sourceStack.copy();
// Check if the slot clicked is one of the vanilla container slots
if (index < VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT) {
// This is a vanilla container slot so merge the stack into the tile inventory
if (!moveItemStackTo(sourceStack, TE_INVENTORY_FIRST_SLOT_INDEX, TE_INVENTORY_FIRST_SLOT_INDEX
+ TE_INVENTORY_SLOT_COUNT, false)) {
return ItemStack.EMPTY; // EMPTY_ITEM
}
} else if (index < TE_INVENTORY_FIRST_SLOT_INDEX + TE_INVENTORY_SLOT_COUNT) {
// This is a TE slot so merge the stack into the players inventory
if (!moveItemStackTo(sourceStack, VANILLA_FIRST_SLOT_INDEX, VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT, false)) {
return ItemStack.EMPTY;
}
} else {
System.out.println("Invalid slotIndex:" + index);
return ItemStack.EMPTY;
}
// If stack size == 0 (the entire stack was moved) set slot contents to null
if (sourceStack.getCount() == 0) {
sourceSlot.set(ItemStack.EMPTY);
} else {
sourceSlot.setChanged();
}
sourceSlot.onTake(playerIn, sourceStack);
return copyOfSourceStack;
}
@Override
public boolean stillValid(Player pPlayer) {
return stillValid(ContainerLevelAccess.create(level, blockEntity.getBlockPos()),
pPlayer, BlockInit.VIRUS_GENERATOR_BLOCK.get());
}
//3行物品栏
private void addPlayerInventory(Inventory 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(Inventory playerInventory) {
for (int i = 0; i < 9; ++i) {
this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142));
}
}
}
我们的槽位对应关系,左上角为坐标原点:
我们13像素的进度条就是用右上角的图案覆盖左下角的图案。
在screen包中新建ModResultSlot
类,将我们的产物放置进行预设。
ModResultSlot.java
public class ModResultSlot extends SlotItemHandler {
public ModResultSlot(IItemHandler itemHandler, int index, int x, int y) {
super(itemHandler, index, x, y);
}
@Override
public boolean mayPlace(ItemStack stack) {
return false;
}
}
5.在init包中新建MenuInit
类,将我们第四步中的菜单进行注册:
MenuInit.java
package com.joy187.re8joymod.init;
import com.joy187.re8joymod.Main;
import com.joy187.re8joymod.screen.VirusGeneratorMenu;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraftforge.common.extensions.IForgeMenuType;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.network.IContainerFactory;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class MenuInit {
public static final DeferredRegister<MenuType<?>> MENUS =
DeferredRegister.create(ForgeRegistries.CONTAINERS, Main.MOD_ID);
//注册我们的方块菜单界面
public static final RegistryObject<MenuType<VirusGeneratorMenu>> VIRUS_GENERATOR_MENU =
registerMenuType(VirusGeneratorMenu::new, "virus_generator_menu");
private static <T extends AbstractContainerMenu>RegistryObject<MenuType<T>> registerMenuType(IContainerFactory<T> factory,
String name) {
return MENUS.register(name, () -> IForgeMenuType.create(factory));
}
public static void register(IEventBus eventBus) {
MENUS.register(eventBus);
}
}
在我们的项目主类中的Main函数中将MenuInit
类进行注册:
public Main() {
IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
ItemInit.ITEMS.register(bus);
BlockInit.BLOCKS.register(bus);
EntityInit.ENTITY_TYPES.register(bus);
EffectInit.EFFECTS.register(bus);
PotionInit.POTIONS.register(bus);
SoundInit.SOUNDS.register(bus);
BlockEntityInit.register(bus);
//添加这个
MenuInit.register(bus);
MinecraftForge.EVENT_BUS.register(this);
}
6.在init包中新建RecipeInit
类,将我们第三步中的配方类进行注册:
RecipeInit.java
package com.joy187.re8joymod.init;
import com.joy187.re8joymod.Main;
import com.joy187.re8joymod.blocks.virusgenerator.VirusGeneratorRecipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class RecipeInit {
public static final DeferredRegister<RecipeSerializer<?>> SERIALIZERS =
DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, Main.MOD_ID);
public static final RegistryObject<RecipeSerializer<VirusGeneratorRecipe>> VIRUS_GENERATOR_SERIALIZER =
SERIALIZERS.register("virus_generator", () -> VirusGeneratorRecipe.Serializer.INSTANCE);
public static void register(IEventBus eventBus) {
SERIALIZERS.register(eventBus);
}
}
在我们的项目主类中的Main函数中将RecipeInit
类进行注册:
public Main() {
IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
ItemInit.ITEMS.register(bus);
BlockInit.BLOCKS.register(bus);
EntityInit.ENTITY_TYPES.register(bus);
EffectInit.EFFECTS.register(bus);
PotionInit.POTIONS.register(bus);
SoundInit.SOUNDS.register(bus);
BlockEntityInit.register(bus);
MenuInit.register(bus);
//添加这个
RecipeInit.register(bus);
MinecraftForge.EVENT_BUS.register(this);
}
在ClientModEventSubscriber
类中添加我们的配方类型的类进行注册
ClientModEventSubscriber.java
@SubscribeEvent
public static void registerRecipeTypes(final RegistryEvent.Register<RecipeSerializer<?>> event) {
Registry.register(Registry.RECIPE_TYPE, VirusGeneratorRecipe.Type.ID, VirusGeneratorRecipe.Type.INSTANCE);
}
7.代码部分结束,来到资源包制做。在src\main\resources\assets\你的modid\blockstates
中新建我们的方块的状态文件:
virus_generator.json
{
"variants": {
"facing=east,lit=false": {
"model": "re8joymod:block/virus_generator",
"y": 90
},
"facing=east,lit=true": {
"model": "re8joymod:block/virus_generator_on",
"y": 90
},
"facing=north,lit=false": {
"model": "re8joymod:block/virus_generator"
},
"facing=north,lit=true": {
"model": "re8joymod:block/virus_generator_on"
},
"facing=south,lit=false": {
"model": "re8joymod:block/virus_generator",
"y": 180
},
"facing=south,lit=true": {
"model": "re8joymod:block/virus_generator_on",
"y": 180
},
"facing=west,lit=false": {
"model": "re8joymod:block/virus_generator",
"y": 270
},
"facing=west,lit=true": {
"model": "re8joymod:block/virus_generator_on",
"y": 270
}
}
}
在src\main\resources\assets\你的modid\models\block
中新建两个我们的方块模型文件:
方块平时的模型
virus_generator.json
{
"parent": "block/orientable",
"textures": {
"top": "re8joymod:block/virus_generator_side",
"front": "re8joymod:block/virus_generator",
"side": "re8joymod:block/virus_generator_side"
}
}
方块工作时的模型
virus_generator_on.json
{
"parent": "block/orientable",
"textures": {
"top": "re8joymod:block/virus_generator_side",
"front": "re8joymod:block/virus_generator_on",
"side": "re8joymod:block/virus_generator_side"
}
}
在models\item
中添加我们手拿方块时的模型文件
virus_generator.json
{
"parent": "re8joymod:block/virus_generator"
}
在textures\block
中添加我们方块的侧面、正面不工作、正面工作时的贴图:
在textures包中新建gui包 -> gui包中把我们的gui(大小为256×256像素点)放进去:
在lang包中的en_us.json
文件中加上我们方块实体的名称和打开机器后上面显示的名称:
"block.re8joymod.virus_generator":"Virus Analyser",
"container.virus_generator":"Virus Analyser",
8.在src\main\resources\data\你的modid\recipes
中新建几个属于我们的方块实体的配方:
记得在第三步中我们把配方类型设置为virus_generator
,所以配方的type就写为virus_generator
:
evirus.json
{
"type":"re8joymod:virus_generator",
"ingredients":[
{
"item":"re8joymod:humus"
},
{
"item":"re8joymod:herbglass"
}
],
"output":{
"item":"re8joymod:evirus"
}
}