最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

關(guān)于豬靈在交易結(jié)束后投擲產(chǎn)物相關(guān)特性的代碼分析

2023-08-14 02:47 作者:我才是小灰灰  | 我要投稿

作者:我才是小灰灰、VBFa、HeyBlack233

組織:BSR原版技術(shù)生存服務(wù)器

本文檔遵循知識(shí)共享協(xié)議CC-BY-NC-SA。

本文檔使用MCP-Reborn項(xiàng)目生成的代碼作為分析的源代碼,分析全程會(huì)使用MCP-Reborn采用的反混淆命名。

MCP-Reborn工具鏈接:https://github.com/Hexeption/MCP-Reborn

?本文pdf版百度云鏈接:https://pan.baidu.com/s/1njBnBUXa6ovDwSNuFGA81A?pwd=r78r?

提取碼:r78r

如果不想感受代碼的折磨可以直接跳到最后,看省流版總結(jié)。



正文

在MCP-Reborn項(xiàng)目中,豬靈行為相關(guān)的代碼位于net.minecraft.entity.monster.piglin包中。相較于其他實(shí)體而言豬靈是特別的,Mojang為其單獨(dú)構(gòu)建了整個(gè)包(12個(gè)類(lèi))來(lái)處理豬靈的行為邏輯,而其他生物則大多是一個(gè)或者兩個(gè)類(lèi)。

net.minecraft.entity.moster包

關(guān)于豬靈的投擲行為,我們可以用簡(jiǎn)單的關(guān)鍵字搜索定位到net.minecraft.entity.monster.piglin.PiglinTasks這個(gè)類(lèi)的第273行的throwItems方法:

throwItems方法的實(shí)現(xiàn)

首先第274行定義了一個(gè)optional對(duì)象,其泛型類(lèi)型為玩家實(shí)體,根據(jù)后面的賦值操作我們可以推測(cè),這里的optional對(duì)象表示這個(gè)豬靈記憶中最近的可視玩家。

緊接著第275行是一個(gè)if判斷,其條件是optional對(duì)象存在。可以猜測(cè)表示的是那個(gè)最近的可視玩家存在。(在程序設(shè)計(jì)中,Optional類(lèi)型通常用來(lái)封裝一個(gè)容納了對(duì)象本身和None的結(jié)構(gòu),這樣做可以把空指針類(lèi)型化,對(duì)程序員友好)。

那么第276行的行為就很好理解了,當(dāng)這個(gè)最近的可視玩家存在時(shí),則嘗試向這個(gè)玩家實(shí)體所在的位置投擲這些物品實(shí)體對(duì)象。

理解了存在時(shí)的行為,那么當(dāng)沒(méi)有最近的可視玩家存在的時(shí)候就是278行需要處理的行為了,根據(jù)字面意思它會(huì)任意(random我傾向于理解為任意而不是隨機(jī))向一個(gè)位置投擲這些物品實(shí)體對(duì)象。這部分行為則是我們想要更深入了解的,所以繼續(xù)閱讀關(guān)于throwItemsTowardRandomPos這個(gè)方法的定義。根據(jù)調(diào)用的方式不難看出,這個(gè)方法和我們目前分析的throwItems處于同一個(gè)類(lèi)中。實(shí)際它們的定義是緊挨著的。緊接著讀下面的283行:

throwItemsTowardRandomPos及相關(guān)方法的實(shí)現(xiàn)

分析圖中代碼不難看出,無(wú)論是投擲給玩家還是投擲到一個(gè)任意位置,它最終都是封裝成投擲給某個(gè)給定的坐標(biāo)位置。284行是投擲到任意位置的處理,它調(diào)用了一個(gè)方法getRandomNearbyPos來(lái)獲取一個(gè)坐標(biāo)位置,入?yún)⑹?strong>豬靈實(shí)體,說(shuō)明這個(gè)坐標(biāo)還是和豬靈實(shí)體相關(guān)的,這是我們稍后要特別關(guān)注的方法。而在288行處理的就是存在最近的可視玩家時(shí)的行為。不難理解就是獲取這個(gè)玩家實(shí)體的坐標(biāo)位置。緊接著的292到298行就是具體的投擲行為,它需要保證要投的東西不能是空的,然后遍歷這個(gè)要投的東西,一個(gè)一個(gè)的投出來(lái)(每個(gè)東西都是一個(gè)ItemStack類(lèi)對(duì)象,可以理解為同種物品的堆疊),這里要格外注意的是,無(wú)論投的什么東西,它都在給定這個(gè)方法的目標(biāo)坐標(biāo)位置上疊加了向量(0,1,0),也就是y軸加1。這對(duì)精確預(yù)測(cè)投擲位置還是很重要的(實(shí)際上由于不影響xz兩個(gè)軸,不會(huì)對(duì)后續(xù)的分析產(chǎn)生什么實(shí)質(zhì)性的影響)。

現(xiàn)在回到我們剛剛需要特別關(guān)注的getRandomNearbyPos方法。它的定義在第662行:

getRandomNearbyPos方法的實(shí)現(xiàn)

這個(gè)方法看起來(lái)短小精悍,實(shí)際上卻給出了豬靈投擲行為的一個(gè)關(guān)鍵可預(yù)測(cè)性質(zhì)。663行嘗試調(diào)用一個(gè)隨機(jī)位置生成器生成一個(gè)坐標(biāo),這里的入?yún)⒂腥齻€(gè),除了豬靈實(shí)體外還有兩個(gè)常數(shù)4和2,目前是不明所以的,但是沒(méi)關(guān)系,我們先記得有這么個(gè)常數(shù),之后分析getLandPos方法的時(shí)候自然可以理解。而664行表明,如果我們調(diào)用getLandPos方法獲取到的向量是空的話,方法就會(huì)直接返回豬靈實(shí)體的位置作為目標(biāo)位置。也就是說(shuō)這里的特性是:如果我們能讓getLandPos方法給出一個(gè)空對(duì)象,那么我們就可以精準(zhǔn)的讓豬靈每次的投擲物都是投在豬靈實(shí)體所在位置的上方1格處。

為了達(dá)到這個(gè)目的我們需要進(jìn)一步分析getLandPos方法,它屬于類(lèi)net.minecraft.entity.ai.RandomPositionGenerator。我們可以在這個(gè)類(lèi)的第27行找到我們調(diào)用的那個(gè)重載方法(我們的入?yún)⒂腥齻€(gè),一個(gè)實(shí)體對(duì)象,兩個(gè)整數(shù))的定義:

getLandPos及其重載方法的實(shí)現(xiàn)

可以看到,這個(gè)方法重載調(diào)用了32行定義的方法,多了一個(gè)入?yún)⑹且粋€(gè)方法指針,這種傳參方式往往是要給定參考的值(也就是說(shuō)其實(shí)可以直接給double類(lèi)型的BlockPos的,但是并不是所有參考值都是需要立刻計(jì)算的,通過(guò)這種方式傳遞值可以避免直接計(jì)算消耗計(jì)算資源,而是延后到需要的時(shí)候再計(jì)算,興許后面的條件就不需要計(jì)算了,這是一種常見(jiàn)的優(yōu)化方式),這里的類(lèi)型看起來(lái)是double。根據(jù)它的名稱(chēng)我們猜測(cè)它是當(dāng)前尋路的目標(biāo)位置。

進(jìn)一步我們繼續(xù)向下分析,可以看到34行調(diào)用了generateRandomPos方法,入?yún)?2個(gè),看起來(lái)都像是缺省值,沒(méi)有需要格外注意的常數(shù)。12個(gè)入?yún)⒌膅enerateRandomPos方法的定義在第78行:

generateRandomPos方法的實(shí)現(xiàn)

這個(gè)方法看起來(lái)相當(dāng)龐大,不過(guò)分析起來(lái)沒(méi)有那么復(fù)雜。還記得我們的目標(biāo)是什么嗎,我們需要讓這個(gè)方法返回一個(gè)空對(duì)象。根據(jù)137行的返回語(yǔ)句可知,返回空對(duì)象的方案就是保持flag1變量在最后還是false。

這個(gè)flag1對(duì)象是在89行定義的,所以我們只需要關(guān)注89-135行的代碼就好了。這里是一個(gè)看起來(lái)很大的for循環(huán),而我們的目標(biāo)是讓flag1保持false,而89行的定義里flag1一開(kāi)始就是false,所以我們只要關(guān)注什么行為會(huì)導(dǎo)致flag1變成true就可以了(順帶一提,這個(gè)for循環(huán)很簡(jiǎn)單,就是嘗試10次)。那么可以直觀的看到唯一的一句flag1=true在129行。也就是說(shuō)如果129行不會(huì)被執(zhí)行,那么執(zhí)行到137行跳出整個(gè)for循環(huán)后flag1一定是false,方法返回空指針。那么著眼于129行,我們可以看到它在一個(gè)5層if嵌套里。

把這5層的條件列出來(lái),分別是95行、115行、122行、124行和126行。我們只要確保這5層的條件里有任何一個(gè)能一直不滿足即可打成我們的目標(biāo):

先看第一層95行,它嘗試獲取到一個(gè)BlockPos對(duì)象,而賦值的右邊是getRandomDelta方法。其定義在這段程序緊接著的140行:

getRandomDelta方法的實(shí)現(xiàn)

它的入?yún)⒂?個(gè),我們回顧下29行、34行和94行的傳參,以及81行的定義,不難得出在我們要分析的問(wèn)題中(豬靈投擲物品這個(gè)行為下),這里的入?yún)⑹牵夯谪i靈實(shí)體得到的隨機(jī)數(shù)生成器、常數(shù)4、常數(shù)2、缺省值0、缺省值null、當(dāng)前尋路的目標(biāo)位置。142行的if會(huì)進(jìn)行判斷,第一個(gè)條件就是第4個(gè)入?yún)⑹欠駷閚ull,在我們的分析中這里恒為null,所以只需要關(guān)注154行到159行的else段。這里使用了第1個(gè)入?yún)⒌碾S機(jī)數(shù)生成器連續(xù)生成了三個(gè)隨機(jī)整數(shù)i, j, k。155-157這三行的寫(xiě)法不難看出是修改i, j, k三個(gè)整數(shù)的取值區(qū)間。這里使用到了第2、3、4個(gè)入?yún)⒁簿褪侨齻€(gè)常數(shù)4, 2, 0。代入可以得到:i和k表示[-4, 4]的隨機(jī)數(shù),j表示[-2, 2]的隨機(jī)數(shù)。結(jié)合這個(gè)方法的名稱(chēng)getRandomDelta不難理解,這個(gè)方法返回的是一個(gè)隨機(jī)的方塊坐標(biāo)偏移量。在我們的分析下這個(gè)偏移量會(huì)在x軸和z軸的±4以及y軸±2的范圍內(nèi)。

回歸我們一開(kāi)始的目的,我們嘗試尋找5重if中哪個(gè)可以穩(wěn)定的不滿足,但實(shí)際上我們的分析在154行就可以確定這第1重if是一定會(huì)被滿足的。不過(guò)不要?dú)怵H,我們還有4重if可以分析,并且之前的分析也不是沒(méi)有意義的,繼續(xù)看第2重if。

第2重if雖然只有一行,但是條件實(shí)際上有好多層,先看第1層的由and連接起來(lái)的4個(gè)條件。第一個(gè)是blockPos3.getY() >= 0,這個(gè)條件用到了blockpos3,在114行定義。114行的定義出現(xiàn)了三個(gè)整數(shù)j、k、l,這三個(gè)整數(shù)是什么呢?往上找找看96-98行,這不就是我們剛剛分析第一重if時(shí)的i、j、k嘛。也就是說(shuō)這里114行生成了一個(gè)從豬靈實(shí)體所在位置開(kāi)始計(jì)算加上這個(gè)偏移量的新的BlockPos。(這里有意的忽略了99-112行的行為,這里處理的是Restriction的一些行為,它會(huì)讓xz軸的偏移更小,只有原來(lái)的一半也就是±2,但是實(shí)際發(fā)生的事情是沒(méi)有本質(zhì)變化的。)

那么第一個(gè)條件就很好理解了,首先這個(gè)新的位置要是在y=0及以上。在我們的實(shí)際使用場(chǎng)景中這件事是肯定會(huì)被保證的(y=0以下的機(jī)器怎么做收集啊),那么我們分析第二個(gè)條件blockPos3.getY() <= getMaxBuildHeight(),這個(gè)條件也非常的直觀。這個(gè)獲取了世界的最大建造高度,超過(guò)這個(gè)高度也是不行的。這個(gè)也在實(shí)際場(chǎng)景中被保證了(讓豬靈在最大建造高度圍在一個(gè)區(qū)域里也是個(gè)超級(jí)困難的事情吧)。

這下只剩下兩個(gè)條件了,這倆條件看起來(lái)很復(fù)雜,仔細(xì)剝開(kāi),第三個(gè)條件是和剛剛的Restriction有關(guān)的,根據(jù)83-87行可以得知當(dāng)沒(méi)有發(fā)生Restriction時(shí),這里恒為true。(這里有意忽略發(fā)生Restriction的情況,因?yàn)橐懻摰拇a就太多太多了。簡(jiǎn)單來(lái)說(shuō)就是在發(fā)生Restriction時(shí),這里會(huì)判斷是否是within的,由于99-112行的偏移縮減,實(shí)際上within是成立的。)

最后一個(gè)條件則是尋路相關(guān)的。它調(diào)用了80行定義的尋路器,這是從豬靈實(shí)體對(duì)象里獲取到的。而根據(jù)方法名推斷,這個(gè)方法用于判斷blockPos3是否是一個(gè)穩(wěn)定的目的點(diǎn)。在net.minecraft.pathfinding.PathNavigator類(lèi)的第321行定義:

isStableDestination方法的實(shí)現(xiàn)

這里判斷的依據(jù)是323行的調(diào)用,其代碼在net.minecraft.block.AbstractBlock類(lèi)的497行定義:


isSolidRender方法的實(shí)現(xiàn)

根據(jù)定義可知,它判斷的是blockPos3所處位置下方的那個(gè)方塊是否為可遮擋的且形狀完整的。這和固體方塊并非是完全相同的定義。根據(jù)實(shí)驗(yàn)測(cè)定,我們已知滿足這些特性的方塊有:靈魂沙、收回的活塞、浮冰、紅石塊;而不滿足的方塊有:玻璃、伸出的活塞和活塞頭、粘液塊。我們找到了一個(gè)可能可以作為衡量標(biāo)準(zhǔn)的依據(jù):當(dāng)使用透視視角觀察完全處于實(shí)心墻體內(nèi)的這些方塊時(shí),它是否能被渲染出,如下圖所示。

具有可遮擋的且形狀完整性質(zhì)的方塊可以在實(shí)墻的透視視圖中渲染出來(lái)

這里似乎還有一個(gè)疑點(diǎn)是,靈魂沙是碰撞箱不完整的方塊(在上表面裸露的情況下有1/16的缺失),但它是形狀完整的,理由可見(jiàn)下圖。當(dāng)玩家實(shí)體踩在靈魂沙上時(shí),靈魂沙的渲染邊界依然是完整方塊的,但是實(shí)際的實(shí)體碰撞箱是小了1/16的。

靈魂沙的碰撞邊界少了1/16,但是渲染邊界是完全的

到這里我們似乎有了一個(gè)很好的發(fā)現(xiàn),簡(jiǎn)單總結(jié)一下剛剛的分析結(jié)論。我們分析了generateRandomPos方法的第93行的for循環(huán)。它表示豬靈在扔物品時(shí)候的行為邏輯。簡(jiǎn)單總結(jié),當(dāng)交易結(jié)算完畢,豬靈想要扔出物品時(shí),會(huì)選擇一個(gè)坐標(biāo)計(jì)算扔出方向。假如在它的記憶之中存在最近的玩家的時(shí)候,它會(huì)朝著那個(gè)玩家的方向扔物品;當(dāng)不存在最近的玩家的時(shí)候,它會(huì)在自身坐標(biāo)xz±4和y±2的范圍內(nèi)隨機(jī)選擇一個(gè)位置,判斷它下方的方塊是否是可遮擋的且形狀完整的(或者對(duì)尋路器而言是穩(wěn)定的位置),這個(gè)隨機(jī)選擇會(huì)進(jìn)行10次(根據(jù)之后的分析,10次隨機(jī)里選擇距離豬靈實(shí)體最近的那一次作為最終結(jié)果)。這里就得到了我們可以利用的特性:如果我們?cè)谪i靈實(shí)體所在位置周?chē)梢运阉鞯姆秶鷥?nèi)(注意考慮下方,這里y是要-1的)都沒(méi)有滿足可遮擋的且形狀完整的方塊,那么這10次搜索都無(wú)法使得flag1變成true。從而使得這個(gè)方法返回空指針。根據(jù)之前的分析就可以讓豬靈在自身位置投擲它的交易掉落物了。

到這里似乎我們已經(jīng)得到了一條完整可用的特性了,不過(guò)之前的分析還沒(méi)有進(jìn)行完全,也就是說(shuō)仍有更多特性存在的可能性在之后的if結(jié)構(gòu)里。接下來(lái)針對(duì)這部分進(jìn)行深入的分析。(對(duì)之前的分析已經(jīng)感到大腦膨脹的可以考慮跳過(guò)后續(xù)的部分了。對(duì)完整的特性保持好奇心的同學(xué)也可以稍微休息一下,緩口氣接著讀。)

generateRandomPos方法的實(shí)現(xiàn)(114-133行)

考慮假如豬靈周?chē)伤阉鞯姆秶鷥?nèi)存在滿足可遮擋的且形狀完整的方塊,并且它上方的那個(gè)方塊被10次循環(huán)選中了,那么程序就會(huì)執(zhí)行到116行的if判斷上。這個(gè)判斷的條件是一個(gè)入?yún)?,根?jù)34行的調(diào)用可知,這個(gè)入?yún)⒉捎玫娜笔≈祎rue,也就是恒成立。那么我們就要分析117-119的這個(gè)moveUpToAboveSolid方法了。這個(gè)方法的調(diào)用是一個(gè)稍微復(fù)雜的語(yǔ)法,它的第三個(gè)入?yún)鬟f了一個(gè)lambda表達(dá)式的匿名方法作為入?yún)?。這個(gè)方法非常好理解,就是用來(lái)判斷給定的方塊是否是固體方塊(此固體方塊的定義可能和其它場(chǎng)景不同,為防止二義性,我們會(huì)在附錄列出所有符合固體方塊性質(zhì)的Material類(lèi)型,如果還需要具體的Block類(lèi)型請(qǐng)自行根據(jù)Material列表在net.minecraft.block.Blocks類(lèi)里查找)。這里另外需要注意的是第二個(gè)入?yún)?,根?jù)34行的調(diào)用可知,第二個(gè)入?yún)㈦S機(jī)數(shù)生成的兩個(gè)值都是缺省的0,那么這里的隨機(jī)數(shù)也只能生成為0,也就是在我們的分析中,這個(gè)方法第二個(gè)入?yún)⒎€(wěn)定是0。這個(gè)方法的定義在同一個(gè)文件的162行:

moveUpToAboveSolid方法的實(shí)現(xiàn)

針對(duì)這個(gè)方法逐行分析,首先第一個(gè)條件判斷,我們已知給定的第二個(gè)入?yún)⒑銥?,所以不滿足條件,不可能觸發(fā)164行的異常。第二個(gè)條件判斷,實(shí)際上是把傳進(jìn)來(lái)的坐標(biāo)(剛剛搜索到的blockPos3)進(jìn)行我們傳遞的那個(gè)是否為固體方塊的判斷,也就是說(shuō),如果剛剛檢索到的那個(gè)位置的方塊不是固體方塊,那么就直接返回那個(gè)位置(這里的邏輯是正確的,因?yàn)樯弦粚觟f的條件里囊括了這個(gè)位置的下方不是可遮擋的且形狀完整的方塊,因此不可能出現(xiàn)虛空投擲的情形)。如果這個(gè)搜索到的位置是固體方塊,對(duì)應(yīng)168-181行的語(yǔ)句,那么就會(huì)向上逐個(gè)尋找,直到找到不是固體方塊的方塊,或者達(dá)到世界最大建造高度。172-178行的循環(huán)是在找到那個(gè)最高方塊之后會(huì)向下尋找,但由于我們第二個(gè)入?yún)⑹?,所以向下尋找是直接不滿足for循環(huán)條件,一次都不會(huì)執(zhí)行。所以這一段都可以忽略掉。最后,返回找到的目標(biāo)坐標(biāo),賦值變成了新的blockPos3。

接下來(lái)便是第三重if的判斷了,在第122行,分為兩部分,以或運(yùn)算相連。一個(gè)是入?yún)?,結(jié)合34行的調(diào)用,傳入的是缺省值false,所以不需要關(guān)注。另一邊則是對(duì)剛剛向上檢索完的結(jié)果——新的blockPos3進(jìn)行了判斷。不難理解,這里是判斷選中的這個(gè)位置的流體狀態(tài)是否是流動(dòng)的水。如果是水也可以不用執(zhí)行flag1=true。此時(shí)可以總結(jié)第二個(gè)特性:當(dāng)在豬靈周?chē)鷛z±4和y±2的范圍內(nèi)存在可遮擋的且形狀完整的方塊時(shí),如果這個(gè)方塊向上檢索的第一個(gè)非固體方塊流體狀態(tài)是流動(dòng)的水,那么豬靈依然不會(huì)嘗試對(duì)這個(gè)位置扔物品,而是在自己所在的位置扔物品。

最后便是第4重if了,誒不是一共有5重嗎?那就讓我們先看下第5重,第5重是兩個(gè)數(shù)值的判斷,d0和d1。顯然d0一開(kāi)始被設(shè)置成了負(fù)無(wú)窮(第90行),而126-130行很容易看出是一個(gè)簡(jiǎn)單的狀態(tài)轉(zhuǎn)移,目標(biāo)是在10次檢索中找到最合適那個(gè)目標(biāo)執(zhí)行,忽略更差的目標(biāo)。也就是說(shuō),這一層if不可能改變最終的目標(biāo)(也就是導(dǎo)致無(wú)法執(zhí)行flag1=true,最后方法返回空指針)。它若是會(huì)被執(zhí)行到,無(wú)論如何都會(huì)被執(zhí)行一次(存在比負(fù)無(wú)窮更好的解)。所以我們可以放棄對(duì)第5重if的幻想。我們更應(yīng)該著眼于第4重if,發(fā)現(xiàn)我們最終完全版的特性。

generateRandomPos方法的實(shí)現(xiàn)(123-131行)

第4重if看起來(lái)和尋路息息相關(guān)。實(shí)際上第123行就嘗試調(diào)用了外部的WalkNodeProcessor來(lái)對(duì)剛剛選到的那個(gè)位置進(jìn)行PathNodeType的判斷。getBlockPathTypeStatic方法的定義在net.minecraft.pathfinding.WalkNodeProcessor的第398行:

getBlockPathTypeStatic方法的實(shí)現(xiàn)

簡(jiǎn)單來(lái)說(shuō)這個(gè)方法是用來(lái)對(duì)目標(biāo)位置進(jìn)行尋路類(lèi)型的判斷的,它會(huì)判斷目標(biāo)位置的尋路類(lèi)型,滿足一些特定情況還會(huì)進(jìn)一步的判斷它下方的位置的尋路類(lèi)型作為它的尋路類(lèi)型返回。具體判斷類(lèi)型的方法則是這個(gè)getBlockPathTypeRaw,定義在下面的464行:

getBlockPathTypeRaw方法的定義

這里的一大坨子特判看上去很多,但實(shí)際上我們只需要關(guān)心其中的部分就好,因?yàn)閷?shí)際上在先前的if判斷中已經(jīng)過(guò)濾了大部分條件,能挺到這一步的方塊必須是不是流動(dòng)水非固體方塊,在這個(gè)范圍內(nèi)我們分析這里的代碼(接下來(lái)會(huì)大量的查詢(xún)Material的表,見(jiàn)附錄1,針對(duì)哪個(gè)方塊是什么Material還望讀者可以自己動(dòng)手查閱,方法就是此前提到的在net.minecraft.block.Blocks類(lèi)中查找)。

首先468行的isAir是判斷Material是否為空氣的,這個(gè)是符合條件的,它會(huì)把PathNodeType設(shè)置為OPEN,這里可以畫(huà)個(gè)重點(diǎn)留意一下。接著往下看,470行判斷了活板門(mén)和荷葉,它使用了兩個(gè)取反的條件做與判斷,如果我們反著看,他就變成了兩個(gè)不取反的條件的或判斷,也就對(duì)應(yīng)之后503行的else域是活板門(mén)或者是荷葉的情況。其中活板門(mén)固體方塊,可以直接排除;荷葉的Material是PLANT,所以不是固體方塊,此時(shí)的PathNodeType為T(mén)RAPDOOR。接著往下看,實(shí)際上連著的if就是471行開(kāi)頭的這一系列if-else-if結(jié)構(gòu)了。首先第一個(gè)是仙人掌,仙人掌的Material不是PLANT而是單獨(dú)的CACTUS,CACTUS固體方塊,所以不會(huì)被選中。緊接著是473行的漿果叢,漿果叢的Material是PLANT所以不是固體方塊,所以漿果叢是可以被選中的,它會(huì)把PathNodeType設(shè)置為DAMAGE_OTHER。再往下是475行的蜂蜜塊,蜂蜜塊的Material是CLAY所以固體方塊。477行是可可豆,可可豆的Material是PLANT,不是固體方塊,所以能夠選中,此時(shí)的PathNodeType為COCOA。繼續(xù)往下開(kāi)始一些其他條件的判斷了,481行判斷是否是水,實(shí)際上如果是水已經(jīng)在上一層if被否決掉了,所以這里可以跳過(guò)。483行是流動(dòng)巖漿的判斷,流動(dòng)巖漿其實(shí)沒(méi)有被過(guò)濾,而且流動(dòng)巖漿不是固體方塊,所以流動(dòng)巖漿會(huì)被選中,PathNodeType為L(zhǎng)AVA。485行判斷方塊是否是燃燒著的方塊,這個(gè)方法定義就在緊接著的第508行:

isBurningBlock方法的定義

根據(jù)定義,火、巖漿、巖漿塊和燃燒中的營(yíng)火都會(huì)被判斷為燃燒著的方塊,其中只有巖漿是可以被選中的,它們的Material是FIRE,不是固體方塊。487行、489行和491行是對(duì)門(mén)的判斷,但是所有的門(mén)都固體方塊。493行是對(duì)鐵軌的判斷,鐵軌的Material是DECORATION,不是固體方塊,所以鐵軌是可以被選中的,它的PathNodeType會(huì)被設(shè)置為RAIL。495行是對(duì)樹(shù)葉的判斷,但是樹(shù)葉固體方塊。最后497行也是我們熟悉了多個(gè)條件取反的與運(yùn)算,可以反著看作是多個(gè)條件的或運(yùn)算,其中的條件就是這個(gè)方塊是柵欄、墻、柵欄門(mén),但是這三者都是固體方塊,所以都不會(huì)被選中,不需要關(guān)心500行的else塊。而剩下的就是不滿足之前這些特判的最后條件了,第498行,如果不是前述的所有類(lèi)型,那么就會(huì)調(diào)用blockstate的isPathfindable方法來(lái)獲得是否是可循路的,以此來(lái)給定PathNodeType是BLOCKED還是OPEN。至此,這個(gè)getBlockPathTypeRaw方法分析完畢,我們知道了在滿足前述條件——向上搜索到的第一個(gè)不是固體的方塊作為新的位置,不同的方塊對(duì)應(yīng)不同的Material會(huì)使得獲取到的PathNodeType有什么樣的不同。這時(shí)候我們?cè)俅位貧w主線,重新看那個(gè)generateRamdomPos方法的邏輯,也就是我們第4層if里的內(nèi)容:

generateRandomPos方法的實(shí)現(xiàn)(123-131行)

在123行獲取到PathNodeType后,緊接著124行的判斷會(huì)把這個(gè)type扔給豬靈實(shí)體對(duì)象的getPathfindingMalus方法,得到一個(gè)浮點(diǎn)數(shù)之后和0進(jìn)行比較。關(guān)于getPathfindingMalus的定義在net.minecraft.entity.MobEntity類(lèi)的140行:

getPathfindingMalus方法的實(shí)現(xiàn)

這里可以看到它是有個(gè)稍微復(fù)雜的機(jī)制的,所以我們精簡(jiǎn)來(lái)看,長(zhǎng)話短說(shuō),直接看返回值的可能性。返回值是一個(gè)三元表達(dá)式,根據(jù)條件不同可以是那個(gè)變量f,也可以是調(diào)用這個(gè)type的getMalus方法獲得一個(gè)值出來(lái)。關(guān)于那個(gè)f的部分比較復(fù)雜,這里不過(guò)多深入,我們不妨假設(shè)f是我們不好控制的部分,那么那個(gè)getMalus方法就是這里的關(guān)鍵了。另外要注意的一點(diǎn)是還記得上面的目標(biāo)嗎?我們希望讓這個(gè)malus不是0,這樣就不會(huì)進(jìn)入126行開(kāi)始的第5重if,我們的flag1就不會(huì)變成true,進(jìn)而方法的返回值變成空指針,豬靈就會(huì)朝著自己所在的位置扔?xùn)|西了。這個(gè)getMalus方法在net.minecraft.pathfinding.PathNodeType這個(gè)枚舉類(lèi)的第35行:

PathNodeType枚舉類(lèi)的實(shí)現(xiàn)

這個(gè)枚舉類(lèi)理解起來(lái)非常簡(jiǎn)單,可以看到,PathNode被規(guī)定了好幾種可能性,每個(gè)都有一個(gè)默認(rèn)的malus。我們的需求是讓malus不為0,那么可以剔除掉OPEN、WALKABLE、WALKABLE_DOOR、TRAPDOOR、RAIL、DOOR_OPEN以及COCOA。結(jié)合我們剛剛探索過(guò)的那一堆特判的方塊,只剩下了漿果叢、巖漿(流動(dòng)和非流動(dòng)的都可以)和火是可以的。值得注意的是剛剛分析到的最后邏輯,也就是滿足不是固體方塊的同時(shí)又不是我們特判的那些方塊的,這個(gè)時(shí)候會(huì)調(diào)用那個(gè)isPathfindable方法來(lái)判斷是否可循路。這個(gè)方法的定義在net.minecraft.block.AbstractBlock類(lèi)的96行:

isPathfindable方法的實(shí)現(xiàn)

簡(jiǎn)單來(lái)說(shuō)就是判斷被選中的那個(gè)方塊是不是碰撞邊緣完整的,如果是碰撞邊緣完整的,它的PathNodeType就會(huì)是BLOCKED,不是則為OPEN。我們能想到的滿足之前的條件并且還有碰撞邊緣的方塊就是雪片了,但是我們實(shí)測(cè)下來(lái)只有5、6、7層的雪片會(huì)讓豬靈不朝它扔?xùn)|西,8層會(huì)讓豬靈向它上方的空氣扔?xùn)|西(表現(xiàn)的和固體方塊一致),小于5層則會(huì)朝著它扔?xùn)|西。這部分邏輯我們翻看了雪片的代碼實(shí)現(xiàn)發(fā)現(xiàn),它覆寫(xiě)了這個(gè)isPathfidable方法,在net.minecraft.block.SnowBlock類(lèi)的30行:

SnowBlock關(guān)于isPathfindable方法的覆寫(xiě)實(shí)現(xiàn)

小于5層的雪片是被特判了的,所以行為不一致。如果有同學(xué)能夠想到其他的滿足非固體方塊但又不在之前的特判里的方塊類(lèi)型,還不是雪片的,歡迎自行實(shí)驗(yàn)驗(yàn)證這里的特性,即被選中的那個(gè)方塊是不是碰撞邊緣完整的,如果是碰撞邊緣完整的,它的PathNodeType就會(huì)是BLOCKED,不是則為OPEN。表現(xiàn)為BLOCK是-1不是0,所以不會(huì)觸發(fā)第5重if,不會(huì)更新flag1為true,也就不會(huì)讓豬靈朝著它扔?xùn)|西。

心細(xì)一點(diǎn)的同學(xué)可能會(huì)還記得,我們似乎遺忘了一個(gè)方法。關(guān)注剛剛第4重if那里123行的調(diào)用。似乎調(diào)用的是getBlockPathTypeStatic方法,而我們只分析了getBlockPathTypeRaw方法。這倆似乎是有區(qū)別的。沒(méi)錯(cuò)!我剛剛也提到了之后會(huì)分析這倆的差異,現(xiàn)在搞清楚getBlockPathTypeRaw了我們就可以專(zhuān)心的搞清楚getBlockPathTypeStatic和getBlockPathTypeRaw的差異了。

getBlockPathTypeStatic方法的實(shí)現(xiàn)

可以看到,getBlockPathTypeStatic方法里在402行和404行分別調(diào)用了兩次getBlockPathTypeRaw方法,402行的調(diào)用是獲取選擇到的這個(gè)方塊本身對(duì)應(yīng)的PathNodeType,而404行的則是它下方一格的那個(gè)方塊對(duì)應(yīng)的PathNodeType。而這里403行if的條件就是敲門(mén)磚。如果我們所選中的那個(gè)方塊的PathNodeType是OPEN,那么就嘗試獲取它下方一格的方塊,當(dāng)滿足406-418行的某個(gè)特殊判斷時(shí),用它下方方塊的PathNodeType替換它本身的PathNodeType返回?;仡櫼幌聞倓偟姆治?,只有一種情況會(huì)給出OPEN,那就是被選中的方塊是AIR(其實(shí)還有另一種情況,就是不滿足之前的特判,同時(shí)isPathfidable是true的,目前可能的是5層以下的雪片)。這個(gè)時(shí)候會(huì)判斷這個(gè)方塊下方的方塊的PathNodeType。406行是多個(gè)特判的堆疊,如果下方方塊是WALKABLE、OPEN、WATER、LAVA中的任何一個(gè),那么這個(gè)pathnodetype就變成OPEN;反之變成WALKABLE。此后406行判斷下方方塊的PathNodeType是否是DAMAGE_FIRE的,在之前的分析對(duì)應(yīng)的是燃燒著的方塊,對(duì)應(yīng)著火、巖漿、巖漿塊和燃燒中的營(yíng)火,此時(shí)這四種都是滿足條件的。也就是說(shuō)在選中方塊的PathNodeType是OPEN的情況下,其下方的方塊是火、巖漿、巖漿塊和燃燒中的營(yíng)火的任何一個(gè)都會(huì)讓它的PathNodeType變成DAMAGE_FIRE,使得第4層if比較的結(jié)果不為0,跳過(guò)flag1=true的變化,豬靈會(huì)朝所在位置扔物品。410行的判斷是仙人掌,它和燃燒著的方塊這里的邏輯是一樣的,同時(shí)DAMAGE_CACTUS也不為0,所以仙人掌也會(huì)滿足特性。414行的是漿果叢的特判,但是漿果叢本來(lái)就會(huì)被選中,所以不會(huì)走到這里的邏輯。最后的418行判斷的是蜂蜜塊,蜂蜜塊是固體方塊,所以它本身不會(huì)被選中,但是它上方若是空氣,則會(huì)滿足這里的邏輯,所以蜂蜜塊是可以的。最后是423行的判斷,如果這時(shí)候還沒(méi)有被任何一個(gè)特判選中,并且沒(méi)有滿足405行的條件,那么就會(huì)進(jìn)入424行的邏輯。這個(gè)checkNeighbourBlocks方法的定義在緊接著的430行:

checkNeighbourBlocks方法的實(shí)現(xiàn)

相信大家對(duì)剛剛的代碼已經(jīng)很熟悉了,這里就加快點(diǎn)節(jié)奏,這個(gè)方法直接遍歷以剛剛獲取到的非固體方塊為中心的3x3x3的全部方塊,挨個(gè)獲取他們的PathNodeType。如果這些方塊是仙人掌、漿果叢和燃燒著的方塊(火、巖漿、巖漿塊和燃燒中的營(yíng)火),就會(huì)返回一個(gè)DANGER_CACTUS、DANGER_OTHER和DANGER_FIRE的PathNodeType。在之前的枚舉列表里這三個(gè)的值都不為0,也就是滿足特性。最后453行的判斷是周?chē)@3x3x3方塊有沒(méi)有流動(dòng)水,會(huì)觸發(fā)一個(gè)WATER_BORDED的TYPE,也不是0(但是說(shuō)很多豬靈交易所在地獄,所以大概率很難搞到水),因此也滿足。

至此,我們完全了解了getBlockPathTypeStatic方法生成PathNodeType的邏輯。簡(jiǎn)單總結(jié)一下,在默認(rèn)情況下,經(jīng)歷第2重if之后選擇了一個(gè)blockPos3,從這個(gè)位置開(kāi)始向上進(jìn)行方塊選擇,選擇第一個(gè)不是固體方塊的方塊。第3重if檢驗(yàn)這個(gè)方塊不是水,然后嘗試獲取這個(gè)方塊的BlockPathType,并且當(dāng)這個(gè)BlockPathType的權(quán)重不是0的時(shí)候跳過(guò)第5層if的執(zhí)行,也就不會(huì)有flag1=true,最后表現(xiàn)為豬靈直接把物品扔在自己所在的位置。滿足這個(gè)權(quán)重不是0的條件可以歸納成:向上選取到的不是固體方塊的方塊是漿果叢、巖漿(流動(dòng)和非流動(dòng))、火和5-7層的雪片;如果選取到的是空氣或者小于5層的雪片,則其下方的方塊需要是火、巖漿(非流動(dòng))、巖漿塊、燃燒中的營(yíng)火、仙人掌、蜂蜜塊;如果下方方塊不是剛剛說(shuō)的這些,同時(shí)也不是流動(dòng)水和流動(dòng)巖漿,那就會(huì)進(jìn)行最后的判斷,以選中方塊為中心的3x3x3的方塊中至少一個(gè)方塊是仙人掌、漿果叢、火、巖漿(非流動(dòng))、巖漿塊、燃燒中的營(yíng)火或者流動(dòng)水。這三個(gè)判斷依次執(zhí)行。

到這里我們的分析基本上完成了,關(guān)于generateRandomPos方法我們已經(jīng)完整的分析完畢。這個(gè)方法就是處理豬靈投擲產(chǎn)物的關(guān)鍵代碼。以下是對(duì)于特性的總結(jié)和結(jié)語(yǔ)。


總結(jié)(極度省流版)

針對(duì)豬靈在交易結(jié)束后的投擲產(chǎn)物相關(guān)的特性,我們自上而下的對(duì)源代碼進(jìn)行了分析。通過(guò)源代碼我們可以對(duì)整個(gè)特性的流程做出以下總結(jié):

1.?? 豬靈在接收到金錠,計(jì)算完交換的產(chǎn)物后,會(huì)進(jìn)行投擲方向的計(jì)算;

2.?? 投擲方向的計(jì)算會(huì)優(yōu)先考慮獲取它AI記憶中最近的玩家,如果這個(gè)玩家存在,則朝著玩家投擲物品;

3.?? 如果不存在記憶中最近的玩家(實(shí)際實(shí)驗(yàn)可以用遮擋視線完成),會(huì)任意選取一個(gè)位置投擲;任意選取位置的過(guò)程總結(jié)如下:

a)?? 首先劃定豬靈實(shí)體所在位置xz±4,y±2的范圍,在其中會(huì)進(jìn)行10次隨機(jī)選取位置;

b)?? 如果選取到的位置是可遮擋的且形狀完整的方塊(另稱(chēng):可固體渲染方塊或者尋路穩(wěn)定方塊,不同于固體方塊或者完整方塊的定義),則繼續(xù)執(zhí)行本次選取。(目前測(cè)試已知滿足這個(gè)性質(zhì)的方塊有:靈魂沙、收回的活塞、浮冰、紅石塊;而不滿足的方塊有:玻璃、伸出的活塞和活塞頭、粘液塊。針對(duì)這個(gè)性質(zhì)的判別我們提出了一個(gè)衡量標(biāo)準(zhǔn),但不能完全證明,在文章中部位置有提到。)

c)?? 從這個(gè)選取到的可遮擋的且形狀完整的方塊向上尋找第一個(gè)非固體方塊,如果不存在非固體方塊則搜索到y=世界最大建造高度的那個(gè)方塊;

d)?? 判斷那個(gè)方塊不是流體水,之后計(jì)算那個(gè)方塊的PathNodeType進(jìn)行進(jìn)一步判斷,實(shí)際上是對(duì)那個(gè)方塊和周?chē)綁K類(lèi)型的判斷;

e)?? 如果那個(gè)方塊是漿果叢、巖漿(流動(dòng)和非流動(dòng)的都可以)、5-7層的雪片(或者碰撞完整的非固體方塊,這里不同的方塊可能有不同的判斷方式,具體可以看文中的定義),則這次選取不會(huì)產(chǎn)生結(jié)果;

f)?? 如果那個(gè)方塊是空氣、1-5層的雪片(或者碰撞不完整的非固體方塊,這里同樣的不同方塊有不同的判斷方式,文中有詳細(xì)的定義分析,但仍缺乏實(shí)驗(yàn)驗(yàn)證),并且選中的方塊y>1(實(shí)際上機(jī)器不會(huì)建到那么低),則判斷其下方方塊,其下方方塊若是巖漿(靜態(tài))、巖漿塊、燃燒中的營(yíng)火仙人掌或者蜂蜜塊,則這次選取不會(huì)產(chǎn)生結(jié)果;

g)?? 不滿足上面條件的同時(shí),若其下方方塊若不是流動(dòng)水或者流動(dòng)巖漿,則以選中的那個(gè)方塊為中心,xyz軸±1的范圍內(nèi)進(jìn)行新的搜索,若范圍內(nèi)存在仙人掌、漿果叢、巖漿、巖漿塊或者燃燒中的營(yíng)火,則這次選取不會(huì)產(chǎn)生結(jié)果;

h)?? e、fg都沒(méi)有成立,則這次選取會(huì)進(jìn)行更新,具體來(lái)說(shuō)會(huì)計(jì)算豬靈投擲到這個(gè)位置的優(yōu)先度,若當(dāng)前的優(yōu)先度比記錄中更高,則更新投擲位置,初始的優(yōu)先度是負(fù)無(wú)窮,也就是說(shuō)這個(gè)行為表現(xiàn)為執(zhí)行了10次隨機(jī)選取中找到優(yōu)先度最高的那個(gè)位置進(jìn)行投擲。

4.?? 10次選取都沒(méi)有找到任何的目標(biāo)位置,則豬靈會(huì)在把物品投擲在自身實(shí)體所在的位置。

至此,整個(gè)投擲產(chǎn)物的計(jì)算流程完畢??紤]我們能夠精確控制的情況,也就是我們希望豬靈把物品投擲在自身實(shí)體所在的位置。為了做到這一點(diǎn),最簡(jiǎn)單的方案是確保豬靈周?chē)?/span>xz±4y±2的范圍內(nèi)沒(méi)有任何的可遮擋的且形狀完整的方塊。稍微復(fù)雜點(diǎn)(但是實(shí)際生產(chǎn)環(huán)境中不一定可行)是要求每一個(gè)在這個(gè)區(qū)域內(nèi)的可遮擋的且形狀完整的方塊上面必須連續(xù)保證有固體方塊,直到有一個(gè)位置沒(méi)有固體方塊,這個(gè)沒(méi)有固體方塊位置必須滿足上面cg所要求的條件。

到這里,文章的主體部分結(jié)束了。這次的性質(zhì)探究是在服務(wù)器設(shè)計(jì)豬靈交易所的時(shí)候開(kāi)始的,始于一些實(shí)驗(yàn)提出的問(wèn)題。當(dāng)時(shí)為了給出一個(gè)完備的解決這些提出問(wèn)題的答案,我們選擇了分析代碼,但是當(dāng)時(shí)對(duì)代碼只做了一個(gè)宏觀的分析。最后是結(jié)合了實(shí)驗(yàn)找出了我們實(shí)際應(yīng)用的那個(gè)特性,也就是我這里總結(jié)的:確保豬靈周?chē)?/span>xz±4y±2的范圍內(nèi)沒(méi)有任何的可遮擋的且形狀完整的方塊。這也是BV1dh4y1F7Ui這個(gè)視頻講的十分籠統(tǒng)的一個(gè)原因。后續(xù)我想要寫(xiě)完這篇文章,其實(shí)是單純的想要給這個(gè)問(wèn)題一個(gè)完備的解這個(gè)事情的執(zhí)念。在寫(xiě)這篇文章的過(guò)程中,我逐步深入的挖掘了這些特性,遇到不理解的部分還是進(jìn)游戲內(nèi)去做了很多的對(duì)照實(shí)驗(yàn)。在實(shí)驗(yàn)和理論分析的交錯(cuò)縱橫下,我最后給出了目前這樣的一個(gè)似乎是完備的解。但這個(gè)解并不是完全的,這里還有兩朵烏云沒(méi)有完全解釋?zhuān)旱谝徊糠质?/span>Restriction相關(guān)的,我沒(méi)有解釋的理由是這里要分析的代碼會(huì)膨脹很多,我個(gè)人是對(duì)Restriction涉及到的特性一無(wú)所知的,希望以后有大佬可以對(duì)這些部分做出完備的解釋?zhuān)涣硪徊糠质?/span>getPathfindingMalus方法,我只討論了默認(rèn)值的情況,它是可能被其他的情況改變的,這部分我沒(méi)有分析,也是礙于篇幅和分析的代碼量。嚴(yán)格來(lái)說(shuō),我做的這些所有的分析其實(shí)都不是那么完備,因?yàn)槲抑豢紤]了最基層類(lèi)的實(shí)現(xiàn),如果子類(lèi)覆寫(xiě)了我分析的方法,那么特性可能完全不同于我講述的部分,這是有可能發(fā)生的。另外一點(diǎn)就是我們的實(shí)驗(yàn)并沒(méi)有徹底完全的覆蓋我所講述的特性,一來(lái)是我們想不到更合理的測(cè)試樣例了,比如說(shuō)碰撞完全的非固體方塊,我們能想到的可能只有雪片了。二來(lái)是實(shí)際上這里的分支是非常多的,測(cè)試這些東西的過(guò)程是枯燥的。所以這篇文章提到的特性是理論分析的結(jié)果,不保證完全的可用性。如果有遺漏或者分析錯(cuò)誤的地方,還望各位理性討論,不吝賜教。最后我想要說(shuō)的,對(duì)于特性的研究應(yīng)該是嚴(yán)謹(jǐn)且科學(xué)的,無(wú)論是實(shí)驗(yàn)還是理論分析,二者同等重要。需要交替進(jìn)行,互相補(bǔ)充,方可找到最優(yōu)的路徑,以更好的效率理解特性的本質(zhì),從而運(yùn)用特性。


關(guān)于豬靈在交易結(jié)束后投擲產(chǎn)物相關(guān)特性的代碼分析的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
江川县| 金昌市| 禄劝| 杭锦旗| 隆尧县| 武夷山市| 正蓝旗| 常德市| 临邑县| 潞西市| 大埔县| 新郑市| 凤庆县| 伊宁县| 永登县| 怀柔区| 府谷县| 城口县| 陇南市| 布拖县| 井陉县| 安多县| 墨玉县| 景东| 光山县| 新巴尔虎左旗| 衡水市| 新安县| 山东| 澄城县| 新余市| 阳江市| 新宾| 孙吴县| 苍梧县| 阿鲁科尔沁旗| 全南县| 边坝县| 陵水| 平顺县| 衡阳市|