我的世界·1.20.1·forge Mod教程·ep·方块,方块物品,方块状态,方块实体,GUI。
以下的大部分内容来自Mcjty
源地址为:[1.20 | Mcjty](https://www.mcjty.eu/docs/1.20/)

文档
官方的文档。
[The official Forge documentation. Very well written and good explanation on various subjects](https://mcforge.readthedocs.org/en/latest/)
https://mcforge.readthedocs.org/en/latest/
社区ForgeWiki
[Very good Wiki with all kinds of Forge related info](https://forge.gemwire.uk/wiki/Main_Page)
https://forge.gemwire.uk/wiki/Main_Page

一些有用的链接
JSON生成
* [DataPack Generator 数据包生成器](https://misode.github.io/)
结构教程
[Structure Tutorial by TelepathicGrunt](https://github.com/TelepathicGrunt/StructureTutorialMod)
自定义维度的一些信息
[Minecraft Wiki with information on custom dimensions](https://minecraft.fandom.com/wiki/Custom_dimension)

ep1
简介
基础设置
在之前的内容中以及进行过了
Mappings
Minecraft 发布之后是混淆过的,这意味所有的变量名和方法都是一些看不懂的东西。ForgeGradle可以消除混淆,但是他需要知道mapping。
- official 来自mojang的mapping
- parchment 来自mojang的mapping的基础上,附带了一些参数和文档。[here](https://parchmentmc.org/docs/getting-started)
之类有更多的关于parchment的信息.[here](https://parchmentmc.org/docs/getting-started)
parchment我们之前使用过。

JEI和TOP的依赖关系
如何添加JEI和TOP
在build.gradle中进行配置
修改dependencies
其中的版本要和游戏版本和MOD具体的版本对应起来,可以到仓库查看




其中这里的就是版本了
完成设置之后点击reload gradle即可,或者点击gradle的刷新


生成运行runClient
运行genIntellijRuns任务获得runClient,runServer,runData几个目标



我的世界的一些概念
Definitions:指的是游戏中只存在一个实例,例如一把钻石剑,不过你的背包中可以有两把,这两把钻石剑分别是两个Itemstack的实例。不过这两把钻石剑引用的是同一个item实例。
Inventory:库存中的所有对象都用itemstack表示(玩家的背包或者箱子类的容器),itemstack是游戏中的实例。
world:当块被放置在世界中的时候,被放置的是blockstate,Blockstate是方块的配置,例如熔炉可以有6个方向,具有六个不同的状态。除此之外,熔炉也可以是添加和不添加煤炭的2状态,这样共有12中状态。blockentity是帮助block携带更多的信息,例如存放inventory,或者做一些其他的事情。
图来自myjty


sides
Minecraft具有服务器和客户端,客户端是玩家交互的一端,服务器是逻辑运行的一个端。
forge文档:[https://docs.minecraftforge.net/en/1.20.x/concepts/sides/](https://docs.minecraftforge.net/en/1.20.x/concepts/sides/)

events
forge文档[https://docs.minecraftforge.net/en/1.20.x/concepts/events/](https://docs.minecraftforge.net/en/1.20.x/concepts/events/)
event是forge重要的概念,用于连接不同的地方在Minecraft中,主要有
mod事件,在mod总线上触发的事件。改总线用于监听mods应该初始化的生命周期
forge事件,在forge总线上触发,用于监听游戏中发生的事情。
一些例子:
`FMLCommonSetupEvent`是游戏启动时触发的事件,这是你进行大部分设置的地方。
`FMLClientSetupEvent`当客户端启动时触发此事件,这是你进行客户端设置的地方。
BuildCreativeModeTabContentsEvent 当构建创造模式tab时候会触发,你添加创造模式tab的位置
这些事件均在mod总线上。
下面是一些forge总线的例子
ServerStartingEvent:服务器启动时候触发的事件,这是你进行服务器设置的地方。
EntityJoinLevelEvent:当实体加入世界会触发的事件
BlockEvent.BreakEvent:方块被破坏时候会触发的事件
还有更多的事件你可以在net.minecraftforge.event 包中找到
!IModEventBus 是Mod事件总线上触发。
如果你在处理事件出现了问题一般是由于在不该使用静态的地方使用了静态,或者在应该使用静态的地方没有使用静态。


registration 和 time
forge遵循具体的时间规则,规定你的mod在设置期间必须执行某些操作,你不能随心所欲的进行注册内容,必须在一个特定的事件进行你的工作,这些是由事件控制。
其中DeferredRegister是一种非常简单的方法处理Minecraft游戏中的各种对象(例如物品,方块,容器,维度,实体。。)的注册。这个deferredRegister我们只能注册单例,对于我们想要添加到mod的每个对象,应该是一个registryObject,当在合适的事件通过supplier(lambda)获得我们的注册的对象的实例。
对象的注册是相当的早,当FMLCommonSetupEvent触发时,所有的registerobject应该已经注册完毕。
由于注册的事件很早,发生在处理配置之前,你不能在注册的时候依赖配置值,不要有条件注册!
我们需要为每个方块创建对应的item,因为这样的这些方块才能放入到我们的incentory(背包获取容器)

数据生成 Data Generation
forge : [https://docs.minecraftforge.net/en/1.20.x/concepts/lifecycle/#data-generation](https://docs.minecraftforge.net/en/1.20.x/concepts/lifecycle/#data-generation)
如果此时运行我们的mod将看到block和item的纹理不正确,没有一个正确的名称,为了解决这个问题,我们需要创建一堆描述模型的json,我们可以使用data generation来生成这些数据,这样做有助于减少写json文件带来的错误。

ep 2
简介
创建一个简单的方块和一个具有实体关联的更加复杂的块。

the main mod class
简化了主类,将之前在这里做的事情移动到其他的class

注册
注册的内容移动到了registration类,定义了三个deferred registers在这里。
simple block
创建一个简单的块,没有entity,具有一些特殊的功能,强度3.5f(影响敲打方块所需要的时间)我们需要正确的工具破坏,并且启动了random ticks
由于我们启动了random ticks,所以需要从写randomTick方法,每个tick都会回调该方法, 我们发送一些烟雾粒子。
我们重写use方法,玩家右键点击方块时候调用此方法。在这种情况下,我们需要检查我们是否位于客户端,如果是客户端,我们将方块发生爆炸,并返回 InteractionResult.SUCCESS 表示我们处理了交互,如果是服务器端,我们返回PASS表示我们没有处理交互。
之后我们会讨论数据的生成,我们将展示如何定义model,loottable,recipe,tag

complex block
复杂的块是具有实体的块,这个方块存储一个物品,具有一些特殊的渲染。
在我们之前讨论过游戏中的每种方块仅有一个实体,这就是你为什么不能在block 类中存储任何的数据,因为在blockstate之间这些数据是共享的,这也是为什么我们需要一个entity,blockentity 会为世界上的每一个block创建,这意味着我们可以将数据存储在entitiy中,她对于每一个block实例都是唯一的。 此外,blockentity还允许我们每个tick做一些操作,你可以按照计划做一些事情,或者一些随机的事情。

block class
构造函数和simple block类似
由于我们和一个blockentity实现关联,需要实现entityblock的接口,实现两个方法, 第一个就是newBlockEntity,当方块被放置在世界上时候,会调用此方法, 用于创建实体,第二个就是getTicker,创建实体块会、会调用此方法,用于创建blockEntityticker,可用于在每个tick做一些操作。对于客户端我们不需要哪里的代码,对于服务器端,我们返回了一个ticker委托给block entity。这样做,我们知道我们的block仅仅会在server 上运行。
每秒20tick,发生在客户端和服务器端。

the block entity class
我们分段讨论这里的代码,从一些常量开始。我们为我们存储在block entity中的items定义一个tag(之后会介绍)我们同样会定义一定的slot和这些slots的索引供我们使用,这个block的inventory仅有一个slot

capabilities
为了表示我们的items我们的使用capability,Capability一种将额外数据添加到对象上的方法,在这个例子中,我们将inventory添加到我们的blockentity上,capabilities同样可以添加到其他的对象,像entity,itemstack,chunks,etc,通过使用capabilities我们可以确保我们的方块和其他的使用capabilities的mod的方块可以一起的正常的工作。
在这里个blockentity的例子中使用的是ITEM_HANDLER功能,此功能用于库存,使用LazyOptional创建一个怠惰的引用,通过他可以延迟实例的创建,直到真正需要这个功能时候才会创建。这很有用,因为capability并不是一直会用到,因为有时候或许根本不会用到。
此外forge还提供对于能量和流体功能,此外,如果需要,模组还可以定义自己的功能。
值得注意的是,当我们的方块被破坏的时候,对应的capabilities也需要失效。在invalidateCaps中做这项工作。
我们使用ItemStackHandler,此类一个简单的方法存储items,它具有序列化serialize和反序列化desirealize方法,我们需要重写onContentChanged方法,在item的内容发生变化的时候标记实体已更改,确保这些变化保存在磁盘上。
由于还需要告诉客户端block entity已经更新,我们还需要发送方块更新给客户端,客户端收到这些信息之后更新实体方块。
最后我们从写getCapability方法,返回我们的item handler capability,这个方法会被其他的mod访问获得capabilities,这个方法有一个可选项允许你指定方向,这被使用从某个特定的side获得能力,在例子中我们不关心侧面。


saving and loading
还有一部分代码,复制保存和加载block entity,我们将方法单独卸载一个方法中,因为我们还需要同步客户端和服务器的数据。
NBT(CompoundTag 类的表示)是一种将数据存储在磁盘的方法,他包含其他标签的层次结构,CompoundTag类是一个keys到tags的映射, tags具有不同的类型,像是stringTag,intTag,ListTag等。
一个常见的错误是认为block保证NBT数据,这是不正确的,NBT是一个序列化的方式用于存储数据在磁盘。在blockentitiy的一个字段中,唯一的例外就是NBT在itemstacks存储在memory
每次当你对实体进行更改时,你都需要调用setchanged将block entity标记为已更改,否则,你的更改不会保存在磁盘,重新加载世界会导致失去数据。

server to client synchronization
服务器到客户端同步
我们想要将服务器的数据同步到客户端,需要重写一些方法,其中每当chunck加载的时候都会调用getUpdatePacket和handleUpdateTag方法,getUpdateTag方法称为服务器创建发给客户端的标记,保持此标记尽可能的小是很有必要的。减少网络开销,因此我们只发送真正需要发送的数据即可。这也是我们闯将saveClientData的原因。
每当block发生了改变,就会调用getupdatePacket和onDataPacket方法,通知客户端某个块已经发生了更改,例如,blockstate更改或者block被服务器标记为更改。在我们的例子中我们通知客户端block的inventory已经更改,我们使用CLientboundBlockEntityDataPacket类创建一个数据包包含了我们希望发送给客户端的数据,我们使用saveData方法保存数据到数据包,在客户端使用onDataPacket方法加载数据,使用了loadClientData方法从数据包加载data,注意,ClinetBoundBlockEntityDataPacket.create(this)实际上使用getUpdateTag创建数据包。
### block entitiy logic
最后,我们实际上需要为我们那的block entitiy增加一些逻辑,我们希望每秒增加库存中的物品的耐久度,当耐久度达到了最大值,就从实体中弹出该item
使用level.getGameTime()获得当前的游戏时间,这是一个每tick增加的计数器,我们使用%取模运算符获得当前是否是20的倍数,20tick =1s,如果是,我们就从库存中获得itemstack检查是否可损坏,如果是就增加一点耐久度,如果耐久度满了,就弹出该物品。
注意tickserver是我们的block(ticker)调用。

rendering渲染
除了本身具有视觉效果(数据生成章节之后介绍),还需要给方块附加视觉效果,在这种情况下,我们希望将我们的物品渲染在块的顶部。
在实现block的渲染的时候这里主要有两种类型的rendering:
- 静态模型:默认首选的方式,你创建一个json描述的model。或者使用其他的烘培模型制作更加复杂的动态模型。
- 动态渲染:静态渲染无法实现时候,可以使用此方法,通常这是你在制作动画或者其他的效果的时候。
一般来说,应该尽可能的尝试静态模型,他高效已与实施,在实例中,我们采用动态blockentitiyRenderer,因为我们想对库存进行动画的渲染。
LIGHT Resource location 代表了我们想使用的特殊发光效果的textrue位置。注意此贴图texture应该位于textures/block中, 因此不要将他放在sitching texture图中,因为这是在此文件夹中自动完成的。之后会说stitching texture,
ComplexBlockRenderer类实现了BlockEntityRenderer接口,该接口有一个方法render,每一帧都会调用该方法。在此方法中,应该从实体中获得ITEM_HEADLER功能,这是作为一个例子如何从block entity中获得capabilities,在这种特殊的情况下,我们应该block entitiy添加一个api直接访问项目的处理程序。
getCapability 方法返回一个LazyOptional,推荐使用map或者ifPresent来访问实际的功能,当存在时候,将回调ifPresent内的lammbd,否则什么也不会发生。
不要直接使用OpenGL,应该使用MultiBufferSource获得可用于渲染模型的bufferBuilder,PoseStack用于伸缩等操作模型,在本例中,我们将模型缩小50%并将其移动到块中心,当render调用的时候,pose Stack已经转移到实体的正确的位置,我们在当前的时间计算物品的旋转的角度,mulpose用于旋转在y轴的旋转模型,最后我们使用从MultiBufferSource获得ItemRenderer和BufferBuilder渲染模型。
修改poseStack时候,请务必push和pop,否则,你可能会损坏poseStack当他被其他render调用的时候。会导致一些渲染的问题,poseStack是一个matrix堆栈,当你压入matrix,你会创建当前的matrix的一个副本,并将其压入到stack,当你弹出一个matrix你从对战的顶部恢复了一个matrix。
除了渲染物品外,我们还希望渲染特殊的发光的效果,这种效果通过渲染有特殊的billboard(始终面向玩家的四边形)和一个特殊的textrue。这个texture是一个16*16的texture,中间是白色的圆,背景透明。该纹理关联一个mcmeta文件,告诉Minecraft通过每隔几帧现在不同的纹理进行渲染。
renderBillboardQuadBright方法负责渲染始终面向问价的四边形,操作posestack使其面向玩家实现该目的,之后渲染通过给定的texture渲染一个四边形。
该方法使用半透明的渲染,半透明渲染是Minecraft中一种特殊的渲染,对半透明的texture应该使用这种渲染类型,否则就无法正确的渲染。
我们还需要注册renderer,创建一个新的类ClientSetup类,该类使用@Mod.EventBusSubscriber告诉Forge对于event bus 这是一个注册的类,bus的参数告诉forge那一个event bus是被使用的(在本例中是modbus),value参数告诉forge该类只能在客户端注册,这很重要,因为EntitiyRenderersEvent.RegisterRenderers仅在客户端触发。
通过注册我们将告诉Minecraft,每当渲染ComplexEnitity类型的实体方块时,他应该使用ComplexBlockRnederer渲染。

data generation 数据生成
在Minecraft中,很多东西使用json格式来表示,包括block model,block state,item models, recipes。loot tables。advancements etc。当然可以手段创建这些json文件,当一个很大的模组时候,这会变的很乏味,datagen基于GagtherDataEvent事件,你可以在这里看到如何使用他。
在主类mod中,使用addListener将其添加到mod事件总线中。
所有的json都会放在名为generated/resources的文件中,在开发环境中运行游戏时,该文件夹会自动添加到类路径中,意味这Minecraft能够找到json并使用他们,构建mod时候,json会自动复制到jar文件中的resources文件中,你依然可以手动放入resources中,并使用他们替代生成的json。
为了获得生成的数据,你必须运行runData的gradle任务。
不要直接在生成的json中编辑,会在下次生成时候覆盖。

blockstate
datagen block state provider是一个用于生成model json 和blockstate json 文件。其中TutBlockStates类是BlockStateProvider的子类,构成函数使用PackOutput和ExistingFileHelper。PackOut用于生成文件在正确的位置上,ExistingFileHelper检查文件是否已经存在。当你希望你生成的json文件用于已经存在的原版的文件的时候这会非常有用。
registerStatesAndModels方法用于注册blockstates 和models,在这个例子中,使用该方法simpleBlock生成model和blockstate

item models
物品也需要model,本教程model十分简单,因为他只是引用了block模型
modLoc函数为mod创建一个ResourceLocation,这是我们引用模型的方式。
Language provider
在代码中通常使用语言字符串,然后这些字符串翻译成正确的语言,翻译也在json中处理
block tags
Minecraft 使用标签对方块,物品,生物群系和其他的事情进行了分组。这些tags存在json中,TutBlockTags是BlockTagsProvider的子类,构造函数参数有PackOutput、HolderLookup.Provider和ExistingFileHepler。
在本教程中,我们将两个block和两个原版的tags关联。
minecraft:minebale/pickaxe 此tag表示是否可以使用pickaxe开采方块。
minecraft:needs_iron_tool 此tag用于指定一个block至少需要一个iron才能开采。
item tags
item tags 同样存储在json文件中,TutItemTags是ItemTagsProvider的子类,项目标签提供程序需要先前创建的blocktag提供程序的实例。
因为我们没有任何的tag需要和我们的item关联,所以此类是空的。
recipes
recipes同样存储在json中,TuRecipes类是RecipeProvider的子类,构造函数参数PackOutput。
recipe datagen 可以适用于所有类型的和传播,在这个教程中,我们生成无须合成表合成一个简单的方块,和一个有序的合成表合成复杂的方块。
对于每个recipce,我们需要指定一个类别,这是一个自定义的字符串,用于对recipes进行分类,在这里都会使用RecipeCategory.MISC。
还需要检查配方是否受到特定的进度的限制,使用InventoryChangeTrigger检查玩家库存是否有钻石。
请注意,recipe可以基于block同样可以基于tag,tag更好,配方更加灵活。
loot tables
loot tables用于方块或者实体被破坏时候的凋落物。TutLootTable类是VanillaBlockLoot的子类,我们拓展这个类的原因该类有很多方法可以帮助我们更加容易的生成战利品表。
对于简单的block来说我们可以使用dropself的方法,这个方法是让方块在破坏的时候自行掉落,对于复杂的方块,我么使用createStandaradTable方法,此方法生成一个战利品的表,表示破坏该方块时候,会掉落本身以及实体中的项目,这和潜影盒类似。

ep3
简介
继续添加一更加复杂的块。
- block properties
- user interface
- Integration with other mods
- networking

Block properties
添加一个新的方块,定义方块的属性,我们同样需要blockEntity,添加新的entity 和 getTicker
使用createBlockStatDefintion定义块的属性,我们使用四个boolean 属性指出那一个按钮被按下或者没有被按下,稍后是使用这些properties渲染块,此外我们还添加了一个标准的FACING属性指示块所面向的方向。
添加这些属性导致我们的block通过不同的组合就有不同的状态,16 * 6 = 96中,一个方块有数百的属性是正常的,但是也不要太多。有其他更好的方法处理过多的状态。
getPlacementState方法用于设置方块放置的初始状态,在本例中,FACING属性设置为玩家正在查看的方向,案件属性设置为false。
因为我们的block不是完全不透明的,需要为其定义一个形状,使用getShape方法做到这一点,为每个方向定义不同的形状,该形状为voxelShape,他是boxes的集合,在本例中我们顶一个和我们稍后定义的model相匹配的box。

BlockState datagen
我们希望我们的方块看起来是这样的

所以这是一个具有四个按钮的block,按钮可以被按下或者不安下。
让我们渲染这个块,我们可以使用静态的json模型,并且我们想要使用datagen,让我们看看TutBlockStates,基本上我们添加了一个新的registerProcessor方法,该方法首先创建基本的模型,然后为所有可能的状态下的所有按钮创建一个多部分的model,基础model是一个所有面一样贴图的cube,使用原版的多部分系统尽可能让我们的model更加符合情况。这意味着仅当按下相应的按钮时才会添加所有的按钮模型,这是通过检查block state属性完成。
我们还需要为该项目添加一个条目
当我们生成这个,我们可以获得很多个model,你可以在generated文件夹中看到。

TheBlockEntity
让我们从我们基础的block entity开始,这和之前的complex block entity 类似,在这个例子中我们将会有一个输入和六个输出slot,我们允许侧面访问,output slots仅仅在底部是允许的。inputslot从其他的side访问,没有指定边的时候我们返回baseitemhandler。
输入和输出是单独的属性,然而,我们有三个LazyOptional itemhandler因为我们希望返回一个组合项目处理程序,还有在side为null的处理,Forge combinedinvWrapper是一个多项目的处理程序的包装器,使得他们可以显示为单个项目的处理程序。
我们定义自己的AdaptedItemHander,他允许我们调整现有的项目处理程序并限制允许的操作。例如input itemhandler 仅仅允许 插入 output item handler仅仅允许提取。
我们不限制这些操作在我们的item handler中,因为我们希望在我们的block entity中不限制的使用inputhandler 和outputhandler。该限制应该在自动化中限制。
我们需要一个新的工具类(AdaptedItemHander)帮助我们处理itemhander,这是一个基本的ItemStackHandler的包裹器,它允许我们限制另一个itemhander的存在。该类的所做的唯一一件事情就是将所有的操作委托给包装类处理程序。

registration 注册
我们需要注册我们的block和block entity

UserInterface 用户界面
我们想要一个如下的用户界面:

Minecraft中的用户界面有点复杂,因为他同时具有客户端和服务器组件,将双方关联在一起的是一个 `AbstractContainerMenu`的类表示的容器,该类负责将数据从服务器发送给客户端并返回。客户端由Screen类表示,该类负责呈现用户界面并将用户输入的内容返回给服务器。

Container
从容器开始,我们需要创建一个拓展AbstractContainerMenu的类。
在container中我们需要在用户界面中添加显示的slot,我们需要显示三个inventory的slot,输入的slot,输出的slot,和玩家的slot,每一个slot都有一个源inventory和一个索引,在屏幕上也有一个实际位置。
你必须重写container中的一个重要的方法quckMoveStack,当玩家按下shift键点击点击某个slot时候会调用此函数,她负责上下文信息将item移动到合适的位置。例如,在我们的例子中,我们希望玩家从玩家的inventory中移动item到input slot ,从output slot 移动到 玩家的inventory中。
stillvalid函数是为了检查玩家是否距离方块足够近以便能够和其交互。

Screen
现在我们有了container,我们就有创建screnn了,screen负责将用户输入的信息返回给服务器,screen尽在客户端创建,因此我们无需担心服务端。
使用ResourceLocation指向我们想要的background,我们还需要重写renderBg函数来实际渲染它。
registration
最后一步是注册容器和屏幕,容器在Registration中注册。请注意我们使用的是IFrogeMenuType.create创建menu type,因为我们需要知道我们processor在container中的位置,所以我们使用data(packetBuffer实例)从数据包读取位置,将这个位置传递给数据包。
屏幕在ClientSetup中注册,我们将容器和屏幕连接起来,以便客户端收到某个container容器被打开的通知时,能正确的打开screen
请注意这里的enqueueWork函数,该函数用于主线程运行代码,这是必须的,因为MenuScreens.register函数需要在主线程上允许。并且初始化原则上可以在另一个线程上个发生。
警告:在主线程上运行代码的时候,请始终在数据包处理程序中使用enqueueWork!
现在我们需要向block添加代码以实际打开screen。将此代码添加到块类中。请注意,我们实际上是在服务器上打开用户界面!这很重要,因为服务器端启动容器,客户端渲染屏幕。通过打开服务器上的容器,客户端将收到通知并打开相应的屏幕。
NetworkHooks.openScreen将postition发送给container

数据生成器
我们已经介绍了块状态、块模型和项目模型。在本节中,我们将快速浏览数据生成的其余部分。首先,您需要将此块添加到 `TutBlockTags` 。
对于language provider提供程序,添加以下行:
将以下配方添加到 `TutRecipes`
最后,我们需要生成战利品表。因为我们的处理器有两个库存(一个用于输入,一个用于输出),所以我们需要稍微概括一下我们的函数以实现这一点。
基本上在 `createStandardTable` 中,我们添加了一个额外的参数 `tags` ,它是我们想要从块实体复制到战利品表的内容的列表。然后我们循环遍历这个列表并为每个标签添加一个 `CopyNbtFunction` 。

和世界交互
### Quadrant detection 象限检测
我们增加一个帮我们的函数,这个函数会指出方块的那一部分被点击了,这个例子有些棘手,因为我们需要考虑当前方块的朝向,我们使用hit参数的确定玩家的点击位置,这个是一个3D的坐标,然后我们将这个坐标转化为2维坐标,然后我们就可以很容易的找出击中的是哪个象限。
打击block
添加交互的代码,因为右键是打开GUI,所以我们应该使用左键,但是左键单击会出现一些问题,
1. 左键单击打破方块,我们不想左键点击时候破坏我们的方块,尤其是在创造模式下。
2. Block中最合适的方法是attack他不会导致我们击中方块后的位置破坏他,但是他无法指出那个象限。
幸运的是Forge为我们提供了一个可以解决我们两个问题的方法,该事件成为PlayerInternalEvent.LeftClickBlock。我们可以通过使用此事件来取消破坏并自行处理交互。我们还可以通过hitVec确定击中了那个象限。
为此我们添加了一个名为ForgeEventHandlers的新类,请注意,玩家每次左键都会触发该事件,因为我们需要确保只处理我们的block,如如果点击了带了button的面,我们就取消这个事件,这样就不会破坏我们的方块,这样即使在创造模式下玩家同样可以使用buttons,攻击其他的侧面就可以破坏该方块。
请注意这里的SafeClientTools.getClientMouseOver()用法,这是一个辅助的函数,用于获取我们点击方块的位置,这是一个单独的类的原因是一位内该函数仅在客户端工作,并且LeftClientBlock事件在客户端和服务器都会触发,我们不知道是在客户端和服务器。
这意味我们只能在客户端检测这一点,但是服务器需要知道那个象限被击中了,因为i我们需要想服务器发送网络数据包。在下一节中,我们解释原理,假设packhtiToServer将象限发送给服务器。
我们需要将event handler注册在forgebus,添加到构造函数中。
这里是SafeClientTools

网络
为了设置网络我们需要添加一些东西。首先我们需要创建一个message handler目的是为了添加一个channel类。register方法将创建一个新的通道并注册我们所需要的所有包。目前,只有这一种包,为了注册数据包,我们需要提供decoder ,encoder 和 consumer方法,decoder和encoder方法用于将字节流转化为数据包或者反过来,接收数据包是调用consumer。我们需要给数据包提供唯一的id,没法注册返回数据包的唯一一个id
它同样需要注册在commonsetup中
创建数据包本身,我们发送到服务器的数据是注册块的位置和被击中的象限位置。我们使用FriendlyByBuf类将数据写入到字节缓冲区,创建一个可以从缓冲区读取这些数据的构造函数。通常在网络数据包在网络线程上处理。但是我们使用了consumerMainThread方法所以我们的handle方法在收到packet之后会在主线程上调用,在处理程序中,我们获得发哦是那个数据包的玩家并i盗用处理器块实体上的hit方法。

执行操作
我们拓展block entity实现hit方法,在hit方法中,我们需要检查那个象限被击中了,并将对应的button设置为true,我们还向玩家发送了一条消息,告知设置为true还是false,每个按钮都是一个开关,让处理器输入执行一些操作。
请注意block的state是无法改变的,因此,当我们按下了个那个按钮需要改变那个属性的时候,我们就创建一个block的状态,我们可以通过block的setvalue做到这点。我们需要一个转化的开关,可以使用cycle代替,这个循环遍历属性的可能值,如果是boolean就是false和true取反,然后我们更新世界中方块的状态。
在tickeserver中,我们检查是否按下了任何button,这样我们将对当前的item进行一些处理,我们可以烧制他们,播放声音,打破他们或者生成一个生物。依赖于这些的操作,我们将消耗该物品,例如消耗刷怪蛋返回实体,或者将物品原封不动的返回(声音操作),或者将凋落物的table中的一项返回,或者将物品熔化的结果放回输出中。
insertOrinject方法是将结果翻入到输出的slot中,如果没有空间了,就弹出到世界中来。使用ItemEntity创建一个实体,并生成在世界中。
meltItem通过查询recipe获得物品的熔炉烧制的配方,如果有就返回烧纸后的物品。
breankAsBlock尝试打破方块,从所有的战利品中随机获得一个。
playSound将会播放方块被破坏时候的声音。
spawnMob仅对刷怪蛋有效,生成生物并消耗刷怪蛋

the one probe intergration的集成
one Probe 是一个模组,当你查看方块的时候,它会显示有关的信息,默认情况下,他将显示标准信息,(例如打破方块所需要的工具),TOP有一个API,你可以使用它添加自己的信息,
为了添加自己的信息,我们需要在build.gradle中添加TOP作为Maven的依赖。
继承The One Probe非常的简单,只需要添加TopCompatibility类即可,获得The One Probe的API,使用ItemModComms并调用getTheOneProbe函数。register方法首先检查TOP是否已记载,因此即使为安装TOP,调用该方法也是安全的。
请注意您通常系统您的mod在没有安装TOP的情况下正常工作,因此你应该确保你的代码中不要直接使用TOP的接口,始终是通过兼容层完成工作, 在本例中,他是TOPCompatIbiliity类
TOP将调用的注册函数(如果存在)将允许我们注册provider 给 probe information,我们在apply方法中执行操作。在addprobeInfo中我们首先检查是否是processorblock。如果是,我们取得方块的状态,并获取button的属性,使用api显示正确的信息。
注意,我们使用ProcessorBlock。getQuadrant()获得我们当前关注的block的button
我们需要在我们的主类中的commonSetup方法中调用register