WorkBlockScreenHandler可以视为屏幕的后端逻辑
这个类分为两个主体,一个是物品的输入输出类,一个是GUI屏幕类
我们需要在这里面完成对GUI屏幕的后端逻辑控制,以及输出输出类的后端逻辑控制
代码很长,但大部分内容是fabric约定的固定写法,可以直接CV,当然你也可以将此类封装一次以便更快速的开发 JAVA是这样的
WorkBlockInputInventory以及WorkBlockResultInventory将在以后编写
/**
* 因为这个GUI是一个合成台的GUI,需要继承AbstractRecipeScreenHandler,
* 而这个AbstractRecipeScreenHandler类中匹配配方的方法参数是其泛型中的类,故我们在这里使用了RecipeInputInventory作为其泛型
*/
public class WorkBlockScreenHandler extends AbstractRecipeScreenHandler<RecipeInputInventory> {
public static final String NAME = "work_block_screen_handler";
public static final Identifier ID = new Identifier(ModInfo.MOD_ID, NAME);
// 自定义屏幕类型
public static ScreenHandlerType<WorkBlockScreenHandler> WORK_BLOCK_SCREEN_HANDLER_TYPE;
// 给屏幕上看的输入,本质上是一个Inventory接口的实现,我们将在这里面进行触发本类中的配方匹配
private final WorkBlockInputInventory input = new WorkBlockInputInventory(this, 3, 3);
// 给屏幕上看的输出,是一个RecipeUnlocker的实现,RecipeUnlocker接口用于遵守合成配方是否可用的规范
private final WorkBlockResultInventory result = new WorkBlockResultInventory();
// 屏幕上下文句柄
private final ScreenHandlerContext context;
// 正在使用的玩家
private final PlayerEntity player;
public WorkBlockScreenHandler(int syncId, PlayerInventory playerInventory) {
// 这个屏幕需要同事显示玩家背包格以及合成台物品格
this(syncId, playerInventory, new SimpleInventory(9), ScreenHandlerContext.EMPTY);
}
public WorkBlockScreenHandler(int syncId, PlayerInventory playerInventory, Inventory inventory, ScreenHandlerContext context) {
super(WORK_BLOCK_SCREEN_HANDLER_TYPE, syncId);
this.context = context;
this.player = playerInventory.player;
// public方法验参常规写法,检查这个物品格子是否为第二个参数的大小,不相符时会抛出异常
checkSize(inventory, 9);
// 触发一下背包被打开时的方法,也可以不写
inventory.onOpen(playerInventory.player);
// 产物槽位
this.addSlot(new WorkBlockOutputSlot(this.result, 0, 124, 35, playerInventory.player, this.input));
// 耗材槽位
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
// 方块栏的物品从0开始到8结束
this.addSlot(new Slot(this.input, j + i * 3, 30 + j * 18, 17 + i * 18));
}
}
// 玩家快捷键
for (int i = 0; i < 9; i++) {
// 玩家快捷栏从0到8
this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142));
}
// 玩家物品栏
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 9; j++) {
// 玩家背包从9到35结束
this.addSlot(new Slot(playerInventory, i * 9 + j + 9, 8 + j * 18, 84 + i * 18));
}
}
}
// 注册方法,仅供参考
public static void registerScreenHandlerType() {
WORK_BLOCK_SCREEN_HANDLER_TYPE =
Registry.register(Registries.SCREEN_HANDLER, ID, new ScreenHandlerType<>(WorkBlockScreenHandler::new, FeatureFlags.VANILLA_FEATURES));
}
// 更新合成配方的结果
protected static void updateResult(ScreenHandler handler, World world, PlayerEntity player, WorkBlockInputInventory inputInventory, WorkBlockResultInventory resultInventory) {
ItemStack itemStack2;
WorkBlockRecipe workBlockRecipe;
if (world.isClient) {
return;
}
// 因为确认了是在服务端,我们需要使用到serverPlayerEntity中的方法发送数据包resultInventory的方法只支持serverPlayerEntity,因而在这里进行强转
ServerPlayerEntity serverPlayerEntity = (ServerPlayerEntity) player;
ItemStack itemStack = ItemStack.EMPTY;
// 这里去匹配配方
Optional<WorkBlockRecipe> optional = world.getRecipeManager().getFirstMatch(WorkBlockRecipe.Type.INSTANCE, inputInventory, world);
// 当我们找到的配方不为空,可以合成,且合 成结果是可以被使用的方块时我们将其...
if (optional.isPresent() && resultInventory.shouldCraftRecipe(world, serverPlayerEntity, workBlockRecipe = optional.get()) && (itemStack2 = workBlockRecipe.craft(inputInventory, world.getRegistryManager())).isItemEnabled(world.getEnabledFeatures())) {
itemStack = itemStack2;
}
// 接上文注释...我们将其设置到结果中
resultInventory.setStack(0, itemStack);
// 这里是固定写法,效仿MC自己的工作台方块写的
handler.setPreviousTrackedSlot(0, itemStack);
// 发送数据包给客户端
serverPlayerEntity.networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(handler.syncId, handler.nextRevision(), 0, itemStack));
}
@Override
public void onContentChanged(Inventory inventory) {
// 这个触发方法会在WorkBlockInputInventory中进行调用,触发更新
this.context.run((world, pos) -> WorkBlockScreenHandler.updateResult(this, world, this.player, this.input, this.result));
}
@Override
public void populateRecipeFinder(RecipeMatcher finder) {
// 由fabric调用,是配方相关的搜索器,复制就完事了嗷,每次更新配方fabric都会去调用这个方法
this.input.provideRecipeInputs(finder);
}
@Override
public void clearCraftingSlots() {
// 同时清空两个
this.input.clear();
this.result.clear();
}
@Override
public boolean matches(Recipe<? super RecipeInputInventory> recipe) {
// 去匹配配方,也由fabric调用
return recipe.matches(this.input, this.player.getWorld());
}
@Override
public void onClosed(PlayerEntity player) {
// 当界面被关闭时,父类方法会将物品归还给玩家。
super.onClosed(player);
// 固定写法,没有细看,有兴趣可以自己点进去看看
this.context.run((world, pos) -> this.dropInventory(player, this.input));
}
@Override
public boolean canUse(PlayerEntity player) {
// 8格方块以内的玩家才可以使用,你也可以在这里写一些仅一个玩家可以使用之类的逻辑
return WorkBlockScreenHandler.canUse(this.context, player, WorkBlock.INSTANCE);
}
// 快速移动,就是shift点击物品时的效果,主要更改里面那些整数,我这里合成表大小就是9输入+1输出,因而你能看到10这个整数,大于10就是玩家背包和物品栏4*9也就是10~45
@Override
public ItemStack quickMove(PlayerEntity player, int slot) {
ItemStack itemStack = ItemStack.EMPTY;
Slot slot2 = this.slots.get(slot);
if (slot2.hasStack()) {
ItemStack itemStack2 = slot2.getStack();
itemStack = itemStack2.copy();
if (slot == 0) {
this.context.run((world, pos) -> itemStack2.getItem().onCraft(itemStack2, world, player));
if (!this.insertItem(itemStack2, 10, 46, true)) {
return ItemStack.EMPTY;
}
slot2.onQuickTransfer(itemStack2, itemStack);
} else if (slot >= 10 && slot < 46 ? !this.insertItem(itemStack2, 1, 10, false) && (slot < 37 ? !this.insertItem(itemStack2, 37, 46, false) : !this.insertItem(itemStack2, 10, 37, false)) : !this.insertItem(itemStack2, 10, 46, false)) {
return ItemStack.EMPTY;
}
if (itemStack2.isEmpty()) {
slot2.setStack(ItemStack.EMPTY);
} else {
slot2.markDirty();
}
if (itemStack2.getCount() == itemStack.getCount()) {
return ItemStack.EMPTY;
}
slot2.onTakeItem(player, itemStack2);
if (slot == 0) {
player.dropItem(itemStack2, false);
}
}
return itemStack;
}
@Override
public boolean canInsertIntoSlot(ItemStack stack, Slot slot) {
// GUI上的结果栏不可插入,其余的栏位按自己的规则,一般是都可以插入,这里的插入不涉及满了不能插这种判断,算是约定
return slot.inventory != this.result && super.canInsertIntoSlot(stack, slot);
}
@Override
public int getCraftingResultSlotIndex() {
// 我们将输出栏的槽位视为0,在WorkBlockScreenHandler构造方法中已经设置好了,这里直接返回0即可
return 0;
}
@Override
public int getCraftingWidth() {
// fabric要这玩意,咱就写上
return this.input.getWidth();
}
@Override
public int getCraftingHeight() {
// fabric要这玩意,咱就写上
return this.input.getHeight();
}
@Override
public int getCraftingSlotCount() {
// fabric要这玩意,咱就写上
return 9;
}
@Override
public RecipeBookCategory getCategory() {
// fabric要这玩意,咱就写上,这个应该是小绿书的内容,没细看
return RecipeBookCategory.CRAFTING;
}
@Override
public boolean canInsertIntoSlot(int index) {
// 抽象配方类需要的一个检查槽位是否可插入的方法,也不涉及满了无法放入的约束
return index != this.getCraftingResultSlotIndex();
}
}