我的世界·1.20.1·forge Mod教程·ep·方塊,方塊物品,方塊狀態(tài),方塊實體,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/
社區(qū)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 數(shù)據(jù)包生成器](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 發(fā)布之后是混淆過的,這意味所有的變量名和方法都是一些看不懂的東西。ForgeGradle可以消除混淆,但是他需要知道m(xù)apping。
- official 來自mojang的mapping
- parchment 來自mojang的mapping的基礎上,附帶了一些參數(shù)和文檔。[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個方向,具有六個不同的狀態(tài)。除此之外,熔爐也可以是添加和不添加煤炭的2狀態(tài),這樣共有12中狀態(tài)。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總線上觸發(fā)的事件。改總線用于監(jiān)聽mods應該初始化的生命周期
forge事件,在forge總線上觸發(fā),用于監(jiān)聽游戲中發(fā)生的事情。
一些例子:
`FMLCommonSetupEvent`是游戲啟動時觸發(fā)的事件,這是你進行大部分設置的地方。
`FMLClientSetupEvent`當客戶端啟動時觸發(fā)此事件,這是你進行客戶端設置的地方。
BuildCreativeModeTabContentsEvent 當構建創(chuàng)造模式tab時候會觸發(fā),你添加創(chuàng)造模式tab的位置
這些事件均在mod總線上。
下面是一些forge總線的例子
ServerStartingEvent:服務器啟動時候觸發(fā)的事件,這是你進行服務器設置的地方。
EntityJoinLevelEvent:當實體加入世界會觸發(fā)的事件
BlockEvent.BreakEvent:方塊被破壞時候會觸發(fā)的事件
還有更多的事件你可以在net.minecraftforge.event 包中找到
!IModEventBus 是Mod事件總線上觸發(fā)。
如果你在處理事件出現(xiàn)了問題一般是由于在不該使用靜態(tài)的地方使用了靜態(tài),或者在應該使用靜態(tài)的地方沒有使用靜態(tài)。


registration 和 time
forge遵循具體的時間規(guī)則,規(guī)定你的mod在設置期間必須執(zhí)行某些操作,你不能隨心所欲的進行注冊內容,必須在一個特定的事件進行你的工作,這些是由事件控制。
其中DeferredRegister是一種非常簡單的方法處理Minecraft游戲中的各種對象(例如物品,方塊,容器,維度,實體。。)的注冊。這個deferredRegister我們只能注冊單例,對于我們想要添加到mod的每個對象,應該是一個registryObject,當在合適的事件通過supplier(lambda)獲得我們的注冊的對象的實例。
對象的注冊是相當?shù)脑纾擣MLCommonSetupEvent觸發(fā)時,所有的registerobject應該已經注冊完畢。
由于注冊的事件很早,發(fā)生在處理配置之前,你不能在注冊的時候依賴配置值,不要有條件注冊!
我們需要為每個方塊創(chuàng)建對應的item,因為這樣的這些方塊才能放入到我們的incentory(背包獲取容器)

數(shù)據(jù)生成 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的紋理不正確,沒有一個正確的名稱,為了解決這個問題,我們需要創(chuàng)建一堆描述模型的json,我們可以使用data generation來生成這些數(shù)據(jù),這樣做有助于減少寫json文件帶來的錯誤。

ep 2
簡介
創(chuàng)建一個簡單的方塊和一個具有實體關聯(lián)的更加復雜的塊。

the main mod class
簡化了主類,將之前在這里做的事情移動到其他的class

注冊
注冊的內容移動到了registration類,定義了三個deferred registers在這里。
simple block
創(chuàng)建一個簡單的塊,沒有entity,具有一些特殊的功能,強度3.5f(影響敲打方塊所需要的時間)我們需要正確的工具破壞,并且啟動了random ticks
由于我們啟動了random ticks,所以需要從寫randomTick方法,每個tick都會回調該方法, 我們發(fā)送一些煙霧粒子。
我們重寫use方法,玩家右鍵點擊方塊時候調用此方法。在這種情況下,我們需要檢查我們是否位于客戶端,如果是客戶端,我們將方塊發(fā)生爆炸,并返回 InteractionResult.SUCCESS 表示我們處理了交互,如果是服務器端,我們返回PASS表示我們沒有處理交互。
之后我們會討論數(shù)據(jù)的生成,我們將展示如何定義model,loottable,recipe,tag

complex block
復雜的塊是具有實體的塊,這個方塊存儲一個物品,具有一些特殊的渲染。
在我們之前討論過游戲中的每種方塊僅有一個實體,這就是你為什么不能在block 類中存儲任何的數(shù)據(jù),因為在blockstate之間這些數(shù)據(jù)是共享的,這也是為什么我們需要一個entity,blockentity 會為世界上的每一個block創(chuàng)建,這意味著我們可以將數(shù)據(jù)存儲在entitiy中,她對于每一個block實例都是唯一的。 此外,blockentity還允許我們每個tick做一些操作,你可以按照計劃做一些事情,或者一些隨機的事情。

block class
構造函數(shù)和simple block類似
由于我們和一個blockentity實現(xiàn)關聯(lián),需要實現(xiàn)entityblock的接口,實現(xiàn)兩個方法, 第一個就是newBlockEntity,當方塊被放置在世界上時候,會調用此方法, 用于創(chuàng)建實體,第二個就是getTicker,創(chuàng)建實體塊會、會調用此方法,用于創(chuàng)建blockEntityticker,可用于在每個tick做一些操作。對于客戶端我們不需要哪里的代碼,對于服務器端,我們返回了一個ticker委托給block entity。這樣做,我們知道我們的block僅僅會在server 上運行。
每秒20tick,發(fā)生在客戶端和服務器端。

the block entity class
我們分段討論這里的代碼,從一些常量開始。我們?yōu)槲覀兇鎯υ赽lock entity中的items定義一個tag(之后會介紹)我們同樣會定義一定的slot和這些slots的索引供我們使用,這個block的inventory僅有一個slot

capabilities
為了表示我們的items我們的使用capability,Capability一種將額外數(shù)據(jù)添加到對象上的方法,在這個例子中,我們將inventory添加到我們的blockentity上,capabilities同樣可以添加到其他的對象,像entity,itemstack,chunks,etc,通過使用capabilities我們可以確保我們的方塊和其他的使用capabilities的mod的方塊可以一起的正常的工作。
在這里個blockentity的例子中使用的是ITEM_HANDLER功能,此功能用于庫存,使用LazyOptional創(chuàng)建一個怠惰的引用,通過他可以延遲實例的創(chuàng)建,直到真正需要這個功能時候才會創(chuàng)建。這很有用,因為capability并不是一直會用到,因為有時候或許根本不會用到。
此外forge還提供對于能量和流體功能,此外,如果需要,模組還可以定義自己的功能。
值得注意的是,當我們的方塊被破壞的時候,對應的capabilities也需要失效。在invalidateCaps中做這項工作。
我們使用ItemStackHandler,此類一個簡單的方法存儲items,它具有序列化serialize和反序列化desirealize方法,我們需要重寫onContentChanged方法,在item的內容發(fā)生變化的時候標記實體已更改,確保這些變化保存在磁盤上。
由于還需要告訴客戶端block entity已經更新,我們還需要發(fā)送方塊更新給客戶端,客戶端收到這些信息之后更新實體方塊。
最后我們從寫getCapability方法,返回我們的item handler capability,這個方法會被其他的mod訪問獲得capabilities,這個方法有一個可選項允許你指定方向,這被使用從某個特定的side獲得能力,在例子中我們不關心側面。


saving and loading
還有一部分代碼,復制保存和加載block entity,我們將方法單獨卸載一個方法中,因為我們還需要同步客戶端和服務器的數(shù)據(jù)。
NBT(CompoundTag 類的表示)是一種將數(shù)據(jù)存儲在磁盤的方法,他包含其他標簽的層次結構,CompoundTag類是一個keys到tags的映射, tags具有不同的類型,像是stringTag,intTag,ListTag等。
一個常見的錯誤是認為block保證NBT數(shù)據(jù),這是不正確的,NBT是一個序列化的方式用于存儲數(shù)據(jù)在磁盤。在blockentitiy的一個字段中,唯一的例外就是NBT在itemstacks存儲在memory
每次當你對實體進行更改時,你都需要調用setchanged將block entity標記為已更改,否則,你的更改不會保存在磁盤,重新加載世界會導致失去數(shù)據(jù)。

server to client synchronization
服務器到客戶端同步
我們想要將服務器的數(shù)據(jù)同步到客戶端,需要重寫一些方法,其中每當chunck加載的時候都會調用getUpdatePacket和handleUpdateTag方法,getUpdateTag方法稱為服務器創(chuàng)建發(fā)給客戶端的標記,保持此標記盡可能的小是很有必要的。減少網絡開銷,因此我們只發(fā)送真正需要發(fā)送的數(shù)據(jù)即可。這也是我們闖將saveClientData的原因。
每當block發(fā)生了改變,就會調用getupdatePacket和onDataPacket方法,通知客戶端某個塊已經發(fā)生了更改,例如,blockstate更改或者block被服務器標記為更改。在我們的例子中我們通知客戶端block的inventory已經更改,我們使用CLientboundBlockEntityDataPacket類創(chuàng)建一個數(shù)據(jù)包包含了我們希望發(fā)送給客戶端的數(shù)據(jù),我們使用saveData方法保存數(shù)據(jù)到數(shù)據(jù)包,在客戶端使用onDataPacket方法加載數(shù)據(jù),使用了loadClientData方法從數(shù)據(jù)包加載data,注意,ClinetBoundBlockEntityDataPacket.create(this)實際上使用getUpdateTag創(chuàng)建數(shù)據(jù)包。
### block entitiy logic
最后,我們實際上需要為我們那的block entitiy增加一些邏輯,我們希望每秒增加庫存中的物品的耐久度,當耐久度達到了最大值,就從實體中彈出該item
使用level.getGameTime()獲得當前的游戲時間,這是一個每tick增加的計數(shù)器,我們使用%取模運算符獲得當前是否是20的倍數(shù),20tick =1s,如果是,我們就從庫存中獲得itemstack檢查是否可損壞,如果是就增加一點耐久度,如果耐久度滿了,就彈出該物品。
注意tickserver是我們的block(ticker)調用。

rendering渲染
除了本身具有視覺效果(數(shù)據(jù)生成章節(jié)之后介紹),還需要給方塊附加視覺效果,在這種情況下,我們希望將我們的物品渲染在塊的頂部。
在實現(xiàn)block的渲染的時候這里主要有兩種類型的rendering:
- 靜態(tài)模型:默認首選的方式,你創(chuàng)建一個json描述的model?;蛘呤褂闷渌暮媾嗄P椭谱鞲訌碗s的動態(tài)模型。
- 動態(tài)渲染:靜態(tài)渲染無法實現(xiàn)時候,可以使用此方法,通常這是你在制作動畫或者其他的效果的時候。
一般來說,應該盡可能的嘗試靜態(tài)模型,他高效已與實施,在實例中,我們采用動態(tài)blockentitiyRenderer,因為我們想對庫存進行動畫的渲染。
LIGHT Resource location 代表了我們想使用的特殊發(fā)光效果的textrue位置。注意此貼圖texture應該位于textures/block中, 因此不要將他放在sitching texture圖中,因為這是在此文件夾中自動完成的。之后會說stitching texture,
ComplexBlockRenderer類實現(xiàn)了BlockEntityRenderer接口,該接口有一個方法render,每一幀都會調用該方法。在此方法中,應該從實體中獲得ITEM_HEADLER功能,這是作為一個例子如何從block entity中獲得capabilities,在這種特殊的情況下,我們應該block entitiy添加一個api直接訪問項目的處理程序。
getCapability 方法返回一個LazyOptional,推薦使用map或者ifPresent來訪問實際的功能,當存在時候,將回調ifPresent內的lammbd,否則什么也不會發(fā)生。
不要直接使用OpenGL,應該使用MultiBufferSource獲得可用于渲染模型的bufferBuilder,PoseStack用于伸縮等操作模型,在本例中,我們將模型縮小50%并將其移動到塊中心,當render調用的時候,pose Stack已經轉移到實體的正確的位置,我們在當前的時間計算物品的旋轉的角度,mulpose用于旋轉在y軸的旋轉模型,最后我們使用從MultiBufferSource獲得ItemRenderer和BufferBuilder渲染模型。
修改poseStack時候,請務必push和pop,否則,你可能會損壞poseStack當他被其他render調用的時候。會導致一些渲染的問題,poseStack是一個matrix堆棧,當你壓入matrix,你會創(chuàng)建當前的matrix的一個副本,并將其壓入到stack,當你彈出一個matrix你從對戰(zhàn)的頂部恢復了一個matrix。
除了渲染物品外,我們還希望渲染特殊的發(fā)光的效果,這種效果通過渲染有特殊的billboard(始終面向玩家的四邊形)和一個特殊的textrue。這個texture是一個16*16的texture,中間是白色的圓,背景透明。該紋理關聯(lián)一個mcmeta文件,告訴Minecraft通過每隔幾幀現(xiàn)在不同的紋理進行渲染。
renderBillboardQuadBright方法負責渲染始終面向問價的四邊形,操作posestack使其面向玩家實現(xiàn)該目的,之后渲染通過給定的texture渲染一個四邊形。
該方法使用半透明的渲染,半透明渲染是Minecraft中一種特殊的渲染,對半透明的texture應該使用這種渲染類型,否則就無法正確的渲染。
我們還需要注冊renderer,創(chuàng)建一個新的類ClientSetup類,該類使用@Mod.EventBusSubscriber告訴Forge對于event bus 這是一個注冊的類,bus的參數(shù)告訴forge那一個event bus是被使用的(在本例中是modbus),value參數(shù)告訴forge該類只能在客戶端注冊,這很重要,因為EntitiyRenderersEvent.RegisterRenderers僅在客戶端觸發(fā)。
通過注冊我們將告訴Minecraft,每當渲染ComplexEnitity類型的實體方塊時,他應該使用ComplexBlockRnederer渲染。

data generation 數(shù)據(jù)生成
在Minecraft中,很多東西使用json格式來表示,包括block model,block state,item models, recipes。loot tables。advancements etc。當然可以手段創(chuàng)建這些json文件,當一個很大的模組時候,這會變的很乏味,datagen基于GagtherDataEvent事件,你可以在這里看到如何使用他。
在主類mod中,使用addListener將其添加到mod事件總線中。
所有的json都會放在名為generated/resources的文件中,在開發(fā)環(huán)境中運行游戲時,該文件夾會自動添加到類路徑中,意味這Minecraft能夠找到json并使用他們,構建mod時候,json會自動復制到jar文件中的resources文件中,你依然可以手動放入resources中,并使用他們替代生成的json。
為了獲得生成的數(shù)據(jù),你必須運行runData的gradle任務。
不要直接在生成的json中編輯,會在下次生成時候覆蓋。

blockstate
datagen block state provider是一個用于生成model json 和blockstate json 文件。其中TutBlockStates類是BlockStateProvider的子類,構成函數(shù)使用PackOutput和ExistingFileHelper。PackOut用于生成文件在正確的位置上,ExistingFileHelper檢查文件是否已經存在。當你希望你生成的json文件用于已經存在的原版的文件的時候這會非常有用。
registerStatesAndModels方法用于注冊blockstates 和models,在這個例子中,使用該方法simpleBlock生成model和blockstate

item models
物品也需要model,本教程model十分簡單,因為他只是引用了block模型
modLoc函數(shù)為mod創(chuàng)建一個ResourceLocation,這是我們引用模型的方式。
Language provider
在代碼中通常使用語言字符串,然后這些字符串翻譯成正確的語言,翻譯也在json中處理
block tags
Minecraft 使用標簽對方塊,物品,生物群系和其他的事情進行了分組。這些tags存在json中,TutBlockTags是BlockTagsProvider的子類,構造函數(shù)參數(shù)有PackOutput、HolderLookup.Provider和ExistingFileHepler。
在本教程中,我們將兩個block和兩個原版的tags關聯(lián)。
minecraft:minebale/pickaxe? 此tag表示是否可以使用pickaxe開采方塊。
minecraft:needs_iron_tool 此tag用于指定一個block至少需要一個iron才能開采。
item tags
item tags 同樣存儲在json文件中,TutItemTags是ItemTagsProvider的子類,項目標簽提供程序需要先前創(chuàng)建的blocktag提供程序的實例。
因為我們沒有任何的tag需要和我們的item關聯(lián),所以此類是空的。
recipes
recipes同樣存儲在json中,TuRecipes類是RecipeProvider的子類,構造函數(shù)參數(shù)PackOutput。
recipe datagen 可以適用于所有類型的和傳播,在這個教程中,我們生成無須合成表合成一個簡單的方塊,和一個有序的合成表合成復雜的方塊。
對于每個recipce,我們需要指定一個類別,這是一個自定義的字符串,用于對recipes進行分類,在這里都會使用RecipeCategory.MISC。
還需要檢查配方是否受到特定的進度的限制,使用InventoryChangeTrigger檢查玩家?guī)齑媸欠裼秀@石。
請注意,recipe可以基于block同樣可以基于tag,tag更好,配方更加靈活。
loot tables
loot tables用于方塊或者實體被破壞時候的凋落物。TutLootTable類是VanillaBlockLoot的子類,我們拓展這個類的原因該類有很多方法可以幫助我們更加容易的生成戰(zhàn)利品表。
對于簡單的block來說我們可以使用dropself的方法,這個方法是讓方塊在破壞的時候自行掉落,對于復雜的方塊,我么使用createStandaradTable方法,此方法生成一個戰(zhàn)利品的表,表示破壞該方塊時候,會掉落本身以及實體中的項目,這和潛影盒類似。

ep3
簡介
繼續(xù)添加一更加復雜的塊。
- block properties
- user interface
- Integration with other mods
- networking

Block properties
添加一個新的方塊,定義方塊的屬性,我們同樣需要blockEntity,添加新的entity 和 getTicker
使用createBlockStatDefintion定義塊的屬性,我們使用四個boolean 屬性指出那一個按鈕被按下或者沒有被按下,稍后是使用這些properties渲染塊,此外我們還添加了一個標準的FACING屬性指示塊所面向的方向。
添加這些屬性導致我們的block通過不同的組合就有不同的狀態(tài),16 * 6 = 96中,一個方塊有數(shù)百的屬性是正常的,但是也不要太多。有其他更好的方法處理過多的狀態(tài)。
getPlacementState方法用于設置方塊放置的初始狀態(tài),在本例中,F(xiàn)ACING屬性設置為玩家正在查看的方向,案件屬性設置為false。
因為我們的block不是完全不透明的,需要為其定義一個形狀,使用getShape方法做到這一點,為每個方向定義不同的形狀,該形狀為voxelShape,他是boxes的集合,在本例中我們頂一個和我們稍后定義的model相匹配的box。

BlockState datagen
我們希望我們的方塊看起來是這樣的

所以這是一個具有四個按鈕的block,按鈕可以被按下或者不安下。
讓我們渲染這個塊,我們可以使用靜態(tài)的json模型,并且我們想要使用datagen,讓我們看看TutBlockStates,基本上我們添加了一個新的registerProcessor方法,該方法首先創(chuàng)建基本的模型,然后為所有可能的狀態(tài)下的所有按鈕創(chuàng)建一個多部分的model,基礎model是一個所有面一樣貼圖的cube,使用原版的多部分系統(tǒng)盡可能讓我們的model更加符合情況。這意味著僅當按下相應的按鈕時才會添加所有的按鈕模型,這是通過檢查block state屬性完成。
我們還需要為該項目添加一個條目
當我們生成這個,我們可以獲得很多個model,你可以在generated文件夾中看到。

TheBlockEntity
讓我們從我們基礎的block entity開始,這和之前的complex block entity 類似,在這個例子中我們將會有一個輸入和六個輸出slot,我們允許側面訪問,output slots僅僅在底部是允許的。inputslot從其他的side訪問,沒有指定邊的時候我們返回baseitemhandler。
輸入和輸出是單獨的屬性,然而,我們有三個LazyOptional itemhandler因為我們希望返回一個組合項目處理程序,還有在side為null的處理,F(xiàn)orge combinedinvWrapper是一個多項目的處理程序的包裝器,使得他們可以顯示為單個項目的處理程序。
我們定義自己的AdaptedItemHander,他允許我們調整現(xiàn)有的項目處理程序并限制允許的操作。例如input itemhandler 僅僅允許 插入 output item handler僅僅允許提取。
我們不限制這些操作在我們的item handler中,因為我們希望在我們的block entity中不限制的使用inputhandler 和outputhandler。該限制應該在自動化中限制。
我們需要一個新的工具類(AdaptedItemHander)幫助我們處理itemhander,這是一個基本的ItemStackHandler的包裹器,它允許我們限制另一個itemhander的存在。該類的所做的唯一一件事情就是將所有的操作委托給包裝類處理程序。

registration 注冊
我們需要注冊我們的block和block entity

UserInterface 用戶界面
我們想要一個如下的用戶界面:

Minecraft中的用戶界面有點復雜,因為他同時具有客戶端和服務器組件,將雙方關聯(lián)在一起的是一個 `AbstractContainerMenu`的類表示的容器,該類負責將數(shù)據(jù)從服務器發(fā)送給客戶端并返回。客戶端由Screen類表示,該類負責呈現(xiàn)用戶界面并將用戶輸入的內容返回給服務器。

Container
從容器開始,我們需要創(chuàng)建一個拓展AbstractContainerMenu的類。
在container中我們需要在用戶界面中添加顯示的slot,我們需要顯示三個inventory的slot,輸入的slot,輸出的slot,和玩家的slot,每一個slot都有一個源inventory和一個索引,在屏幕上也有一個實際位置。
你必須重寫container中的一個重要的方法quckMoveStack,當玩家按下shift鍵點擊點擊某個slot時候會調用此函數(shù),她負責上下文信息將item移動到合適的位置。例如,在我們的例子中,我們希望玩家從玩家的inventory中移動item到input slot ,從output slot 移動到 玩家的inventory中。
stillvalid函數(shù)是為了檢查玩家是否距離方塊足夠近以便能夠和其交互。

Screen
現(xiàn)在我們有了container,我們就有創(chuàng)建screnn了,screen負責將用戶輸入的信息返回給服務器,screen盡在客戶端創(chuàng)建,因此我們無需擔心服務端。
使用ResourceLocation指向我們想要的background,我們還需要重寫renderBg函數(shù)來實際渲染它。
registration
最后一步是注冊容器和屏幕,容器在Registration中注冊。請注意我們使用的是IFrogeMenuType.create創(chuàng)建menu type,因為我們需要知道我們processor在container中的位置,所以我們使用data(packetBuffer實例)從數(shù)據(jù)包讀取位置,將這個位置傳遞給數(shù)據(jù)包。
屏幕在ClientSetup中注冊,我們將容器和屏幕連接起來,以便客戶端收到某個container容器被打開的通知時,能正確的打開screen
請注意這里的enqueueWork函數(shù),該函數(shù)用于主線程運行代碼,這是必須的,因為MenuScreens.register函數(shù)需要在主線程上允許。并且初始化原則上可以在另一個線程上個發(fā)生。
警告:在主線程上運行代碼的時候,請始終在數(shù)據(jù)包處理程序中使用enqueueWork!
現(xiàn)在我們需要向block添加代碼以實際打開screen。將此代碼添加到塊類中。請注意,我們實際上是在服務器上打開用戶界面!這很重要,因為服務器端啟動容器,客戶端渲染屏幕。通過打開服務器上的容器,客戶端將收到通知并打開相應的屏幕。
NetworkHooks.openScreen將postition發(fā)送給container

數(shù)據(jù)生成器
我們已經介紹了塊狀態(tài)、塊模型和項目模型。在本節(jié)中,我們將快速瀏覽數(shù)據(jù)生成的其余部分。首先,您需要將此塊添加到 `TutBlockTags` 。
對于language provider提供程序,添加以下行:
將以下配方添加到 `TutRecipes`
最后,我們需要生成戰(zhàn)利品表。因為我們的處理器有兩個庫存(一個用于輸入,一個用于輸出),所以我們需要稍微概括一下我們的函數(shù)以實現(xiàn)這一點。
基本上在 `createStandardTable` 中,我們添加了一個額外的參數(shù) `tags` ,它是我們想要從塊實體復制到戰(zhàn)利品表的內容的列表。然后我們循環(huán)遍歷這個列表并為每個標簽添加一個 `CopyNbtFunction` 。

和世界交互
### Quadrant detection 象限檢測
我們增加一個幫我們的函數(shù),這個函數(shù)會指出方塊的那一部分被點擊了,這個例子有些棘手,因為我們需要考慮當前方塊的朝向,我們使用hit參數(shù)的確定玩家的點擊位置,這個是一個3D的坐標,然后我們將這個坐標轉化為2維坐標,然后我們就可以很容易的找出擊中的是哪個象限。
打擊block
添加交互的代碼,因為右鍵是打開GUI,所以我們應該使用左鍵,但是左鍵單擊會出現(xiàn)一些問題,
1. 左鍵單擊打破方塊,我們不想左鍵點擊時候破壞我們的方塊,尤其是在創(chuàng)造模式下。
2. Block中最合適的方法是attack他不會導致我們擊中方塊后的位置破壞他,但是他無法指出那個象限。
幸運的是Forge為我們提供了一個可以解決我們兩個問題的方法,該事件成為PlayerInternalEvent.LeftClickBlock。我們可以通過使用此事件來取消破壞并自行處理交互。我們還可以通過hitVec確定擊中了那個象限。
為此我們添加了一個名為ForgeEventHandlers的新類,請注意,玩家每次左鍵都會觸發(fā)該事件,因為我們需要確保只處理我們的block,如如果點擊了帶了button的面,我們就取消這個事件,這樣就不會破壞我們的方塊,這樣即使在創(chuàng)造模式下玩家同樣可以使用buttons,攻擊其他的側面就可以破壞該方塊。
請注意這里的SafeClientTools.getClientMouseOver()用法,這是一個輔助的函數(shù),用于獲取我們點擊方塊的位置,這是一個單獨的類的原因是一位內該函數(shù)僅在客戶端工作,并且LeftClientBlock事件在客戶端和服務器都會觸發(fā),我們不知道是在客戶端和服務器。
這意味我們只能在客戶端檢測這一點,但是服務器需要知道那個象限被擊中了,因為i我們需要想服務器發(fā)送網絡數(shù)據(jù)包。在下一節(jié)中,我們解釋原理,假設packhtiToServer將象限發(fā)送給服務器。
我們需要將event handler注冊在forgebus,添加到構造函數(shù)中。
這里是SafeClientTools

網絡
為了設置網絡我們需要添加一些東西。首先我們需要創(chuàng)建一個message handler目的是為了添加一個channel類。register方法將創(chuàng)建一個新的通道并注冊我們所需要的所有包。目前,只有這一種包,為了注冊數(shù)據(jù)包,我們需要提供decoder ,encoder 和 consumer方法,decoder和encoder方法用于將字節(jié)流轉化為數(shù)據(jù)包或者反過來,接收數(shù)據(jù)包是調用consumer。我們需要給數(shù)據(jù)包提供唯一的id,沒法注冊返回數(shù)據(jù)包的唯一一個id
它同樣需要注冊在commonsetup中
創(chuàng)建數(shù)據(jù)包本身,我們發(fā)送到服務器的數(shù)據(jù)是注冊塊的位置和被擊中的象限位置。我們使用FriendlyByBuf類將數(shù)據(jù)寫入到字節(jié)緩沖區(qū),創(chuàng)建一個可以從緩沖區(qū)讀取這些數(shù)據(jù)的構造函數(shù)。通常在網絡數(shù)據(jù)包在網絡線程上處理。但是我們使用了consumerMainThread方法所以我們的handle方法在收到packet之后會在主線程上調用,在處理程序中,我們獲得發(fā)哦是那個數(shù)據(jù)包的玩家并i盜用處理器塊實體上的hit方法。

執(zhí)行操作
我們拓展block entity實現(xiàn)hit方法,在hit方法中,我們需要檢查那個象限被擊中了,并將對應的button設置為true,我們還向玩家發(fā)送了一條消息,告知設置為true還是false,每個按鈕都是一個開關,讓處理器輸入執(zhí)行一些操作。
請注意block的state是無法改變的,因此,當我們按下了個那個按鈕需要改變那個屬性的時候,我們就創(chuàng)建一個block的狀態(tài),我們可以通過block的setvalue做到這點。我們需要一個轉化的開關,可以使用cycle代替,這個循環(huán)遍歷屬性的可能值,如果是boolean就是false和true取反,然后我們更新世界中方塊的狀態(tài)。
在tickeserver中,我們檢查是否按下了任何button,這樣我們將對當前的item進行一些處理,我們可以燒制他們,播放聲音,打破他們或者生成一個生物。依賴于這些的操作,我們將消耗該物品,例如消耗刷怪蛋返回實體,或者將物品原封不動的返回(聲音操作),或者將凋落物的table中的一項返回,或者將物品熔化的結果放回輸出中。
insertOrinject方法是將結果翻入到輸出的slot中,如果沒有空間了,就彈出到世界中來。使用ItemEntity創(chuàng)建一個實體,并生成在世界中。
meltItem通過查詢recipe獲得物品的熔爐燒制的配方,如果有就返回燒紙后的物品。
breankAsBlock嘗試打破方塊,從所有的戰(zhàn)利品中隨機獲得一個。
playSound將會播放方塊被破壞時候的聲音。
spawnMob僅對刷怪蛋有效,生成生物并消耗刷怪蛋

the one probe intergration的集成
one Probe 是一個模組,當你查看方塊的時候,它會顯示有關的信息,默認情況下,他將顯示標準信息,(例如打破方塊所需要的工具),TOP有一個API,你可以使用它添加自己的信息,
為了添加自己的信息,我們需要在build.gradle中添加TOP作為Maven的依賴。
繼承The One Probe非常的簡單,只需要添加TopCompatibility類即可,獲得The One Probe的API,使用ItemModComms并調用getTheOneProbe函數(shù)。register方法首先檢查TOP是否已記載,因此即使為安裝TOP,調用該方法也是安全的。
請注意您通常系統(tǒng)您的mod在沒有安裝TOP的情況下正常工作,因此你應該確保你的代碼中不要直接使用TOP的接口,始終是通過兼容層完成工作, 在本例中,他是TOPCompatIbiliity類
TOP將調用的注冊函數(shù)(如果存在)將允許我們注冊provider 給 probe information,我們在apply方法中執(zhí)行操作。在addprobeInfo中我們首先檢查是否是processorblock。如果是,我們取得方塊的狀態(tài),并獲取button的屬性,使用api顯示正確的信息。
注意,我們使用ProcessorBlock。getQuadrant()獲得我們當前關注的block的button
我們需要在我們的主類中的commonSetup方法中調用register