Minecraft:退出重進(jìn)大法的原理,到底是無敵時間還是重置高度
在玩MC的時候,總是會出現(xiàn)這樣尷尬的局面:腳滑了,掉入萬丈深淵。除了聽天由命外,另一種選擇就是快到地面時退出重進(jìn)。對此現(xiàn)象的解釋,玩家分成了兩派:無敵時間派和重置高度派。
下面,我們將從代碼層和用戶層進(jìn)行分析。
1.代碼層原理
警告:此處會涉及到大量的代碼邏輯,沒有學(xué)習(xí)過編程的人可以直接查看用戶層驗證。
Minecraft源碼來自由官方混淆表對1.14.4.jar進(jìn)行反混淆反編譯形成的源碼,官方混淆表地址能在1.14.4+的版本JSON文件中找到,名為client_mappings。使用官方混淆表以保證代碼純凈。
首先,我們了解一下玩家受到傷害的代碼,這也是我們研究這個問題的切入點:
玩家收到傷害是由hurt方法進(jìn)行處理,定義在net.minecraft.world.entity.Entity內(nèi),返回true為傷害成立,返回false則傷害取消。接著它被LivingEntity覆蓋(沒有調(diào)用super.hurt),之后被Player類覆蓋。


Player的子類有兩個:ServerPlayer和AbstractCilentPlayer。我們需要的hurt代碼在ServerPlayer中,因為另一個類是進(jìn)行客戶端渲染占位的。

在這里我們看到了spawnInvulnerableTime,翻譯為"出生無敵時間"。注意其他的條件,總結(jié)出來一共有五個條件:
服務(wù)器不能為專用服務(wù)器
PVP不被允許
傷害不能為掉落傷害
在出生無敵時間內(nèi)
傷害不能為虛空傷害
其中,1,2,3點滿足一點即可,4,5點必須滿足
所以我們可以證明無敵時間確實能阻止摔落傷害發(fā)生:單機條件下,服務(wù)器為IntegratedServer,它不是"專用服務(wù)器",滿足第一點。退出重進(jìn)后的傷害一瞬間,如果在無敵時間內(nèi),并且不是掉到虛空外或kill帶來的傷害(kill本質(zhì)是數(shù)值極大,也就是Float.MAX_VALUE的虛空傷害),那么這個傷害效果將被取消。
那么這么萬能的無敵時間是多少呢,代碼層得出的結(jié)果:60tick,也就是TPS為20時的3s。(判斷為60tick而不是60ms是因為下方的tick方法寫到了它的自減,所以推斷單位是tick)
注意:無敵時間不僅是在進(jìn)入世界時起效,死亡重生之后也會有3s的無敵時間,因為死亡時系統(tǒng)刪除了原先的Player,重生時會創(chuàng)建新的Player,因此存在無敵時間。


那么紅框上那個isInvulnerableTo又是什么呢,翻找之后發(fā)現(xiàn)它和無敵時間無關(guān),但是能解釋創(chuàng)造模式下為什么還會受到虛空傷害。

同時通過ServerPlayer的覆蓋,我們了解到變更維度過程中,也無法受到傷害。

說完了無敵時間,我們再從另一個角度——重置高度進(jìn)行分析。
為了這個分析,我們就要了解摔落傷害是怎么產(chǎn)生的:

然而到這里,我們就找不到線索了:是誰調(diào)用了它。但是突然想起來干草塊能降低摔落傷害,所以去找干草塊的類(HayBlock),找到了發(fā)生傷害的地方。

通過此處,我們看到causeFallDamage的第二個參數(shù)的真面目:摔落傷害比例。Block內(nèi),這里寫的是1f,而這里寫為了0.2f,也就是干草塊能阻止80%的摔落傷害。
同時,我們得出了摔落傷害的公式(不具有緩降效果):
當(dāng)沒有跳躍提升效果時:fallDamage = (fallDistance - 3.0f) * damageRatio
當(dāng)存在跳躍提升效果時:fallDamage = (fallDistance - 4.0f - amplifier) * damageRatio
fallDamage-摔落傷害 fallDistance-摔落高度
damageRatio-摔落傷害比例 amplifier-跳躍提升等級
但是到了這里,線索又消失了。
由于沒法通過eclipse的調(diào)用層次結(jié)構(gòu)尋找,我只好用了代碼探針,在Block類的fallOn那里動態(tài)插入了一個Thread.dumpStack()。

之后進(jìn)行摔落,產(chǎn)生了兩組堆棧:
Client端(不負(fù)責(zé)實體傷害)

Server端(真正需要的地方)

通過這些,我們?nèi)フ襍erverPlayer#doCheckFallDamage:

通過這里,我們了解到了這個方法是用于檢查下方的方塊的,但是還是沒有出現(xiàn)下落距離等的出現(xiàn),所以繼續(xù)向下查。

這里出現(xiàn)了一個激動人心的字段:fallDistance,下落距離。也就是說,下落距離是根據(jù)此字段保存的,那么只要證明它被保存了,就能說明重置高度的說法是錯誤的。
可是LivingEntity沒有定義該字段,那么這個字段就一定定義在它的上一級:Entity。

通過這一張圖,我們的代碼驗證走到了盡頭:我們可以清晰地看到這句
compoundTag.putFloat("FallDistance", this.fallDistance);
這句話意味著保存了下落高度,也就是說,在下次進(jìn)入世界時,下落高度會從文件中重新讀取回來,也就是你上一次退出游戲時已經(jīng)下落的高度,所以說,重置高度說是錯誤的。
為了進(jìn)行嚴(yán)謹(jǐn)?shù)恼撟C,下面用客戶端進(jìn)行驗證。
2.客戶端驗證
首先,驗證無敵時間,這個很容易驗證,你在出生60tick內(nèi)泡個巖漿澡就能驗證:

而重置高度,就不好驗證了。我想到了一種方案:生命提升后看摔落傷害——只要在超過3s的摔落高度下進(jìn)行摔落測試,就能受到傷害并且能看出與不退出的關(guān)系:如果摔落之后血量有差異(誤差保持在±5內(nèi)),那么就證明了確實有重置高度;相反的,如果基本沒有差異,那么就沒有重置。
首先給予生命提升255級,把血條變成了1024滴,然后用瞬間治療10級10秒,把心補滿。之后第一次試驗,從Y=1000直接摔到1,看血量。

第二次測試也是在Y=1000落下,不同的是,這次讓它在Y=700左右的時候退出重進(jìn),然后再落到Y(jié)=1。


這兩次摔落結(jié)果很明顯,幾乎沒有任何血量差異。如果進(jìn)行了重置,那么血量會差250滴左右。因此,我們知道了重置高度是錯誤的。
結(jié)論
退出重進(jìn)的原理是無敵時間而非重置高度,所以下回使用這個方法的時候,還是要注意一下你距地面的高度(雖然我知道3s能下落200格高度,早死了)
一些其他的話
fallDistance其實并不是只有在掉落時才會改變,這里我們說一些和運動的內(nèi)容(具體的一些東西可以去CV7593366去看),不同狀態(tài)下,這些值也會改變(這里的detlaMovement是Y軸上的,簡寫為dY):
1.地面上的普通移動(不包括跳):dY=-0.0784000015258789(趨勢位移,在實際運動中不體現(xiàn))
2.水中:fallDistance逐漸變?yōu)?.025000002,dY逐漸變?yōu)?0.02500000149011622
3.飛行:dY逐漸變?yōu)椤?.22500000894069672
4.水中飛行:dY逐漸變?yōu)椤?.28500000759959215
5.在水中站立:dY=-0.005(水中的趨勢位移)
6.水中向上移動:dY逐漸變?yōu)?.13500000685453442
7.梯子下行:fallDistance=0.15,dY=-0.22540001022815717
由于浮點數(shù)誤差,小數(shù)點后四位基本上是準(zhǔn)確極限。不過還是能從這里看出了比如說水中飛行比普通飛行快的結(jié)論。

文章中用到的Minecraft反混淆和動態(tài)修改程序包含反混淆程序(直接運行)和動態(tài)修改替換MC類的功能(原版Java Agent而非模組):https://github.com/Nickid2018/MCDynamicExchanger,該程序正在被完善中。
反編譯器:Eclipse的插件,選擇了CFK反編譯器。
啟動器:HMCL版本3.3.172
MC客戶端:反混淆的1.14.4客戶端,反混淆過程中不會改變代碼邏輯,只有當(dāng)進(jìn)行Agent代理時才修改了一部分net.minecraft.commands.Commands的代碼。
驗證視頻:

如果你有任何問題或是文章有數(shù)據(jù)和代碼錯誤,可以在評論區(qū)留言。