Skip to content

Commit 800a0c2

Browse files
committed
1.21.7 network update
1 parent 2d8dcda commit 800a0c2

3 files changed

Lines changed: 195 additions & 2 deletions

File tree

docs/holojaneway/0.2 服务端和客户端.md

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,28 @@ ServerLevel serverLevel = minecraftServer.getLevel(Level.OVERWORLD);
6262

6363
## OnlyIn!
6464

65+
<div style={{
66+
backgroundColor: 'transparent',
67+
border: '2px solid #3c91ff',
68+
borderRadius: '0.5em',
69+
padding: '1em',
70+
}}>
71+
<Tabs groupId="mc-version">
72+
<TabItem value="1217" label="1.21.7">
73+
:::caution
74+
75+
在1.21.7,OnlyIn遭到了彻底的降级。现在OnlyIn不再起任何实际作用,NeoForge会检查你是否还在用,并且在游戏启动时给出警告——如果你正在升级版本,应该很快就能发现这个问题。这也意味着,你需要自行处理这个双端分离的问题。参考[OnlyIn,但是为什么?](#onlyin但是为什么)了解更多内容。
76+
77+
:::
78+
79+
</TabItem>
80+
</Tabs>
81+
82+
<p></p>
83+
</div>
84+
85+
<p></p>
86+
6587
如果你因为好奇而查看了`ClientLevel`的具体内容,你可能已经注意到了这样一个注解`@OnlyIn(Dist.CLIENT)`。括号中的value一般是`Dist.CLIENT`,极少数时候会用到`Dist.DEDICATED_SERVER`。字段、方法等一旦被`@OnlyIn`注解,他们对于另一端就是逻辑上不可见的(即使这几行代码确实存在于磁盘之中)。请注意,`@OnlyIn`区分的是物理两端而不是逻辑两端。这意味着单人游戏中的服务端可以执行被注解`@OnlyIn(Dist.CLIENT)`的代码,而`@OnlyIn(Dist.DEDICATED_SERVER)`在单人游戏时意味着谁也不能执行到它。
6688

6789
对于被`@OnlyIn`标记的类,需要注意的是,即使你已经用了`isClientSide`来区分,你也不能在一个双端方法内使用它(的对象),需要单独拆出来:
@@ -117,7 +139,7 @@ public abstract class AbstractFireExtinguisher extends Item {
117139

118140
> 其中的`FollowingSoundInstance`继承自`SimpleSoundInstance`,而`SimpleSoundInstance`显然有着`@OnlyIn`的注解。
119141

120-
如此的话,你需要在你的单端方法上也加上`@OnlyIn`的注解(成员变量的定义处也加上):
142+
如此的话,你需要在你的单端方法上也加上`@OnlyIn`的注解(成员变量的定义处也加上),这样Forge/FML就会替你把这个方法也变成仅客户端可见的
121143

122144
```java
123145
...
@@ -152,3 +174,54 @@ public abstract class AbstractFireExtinguisher extends Item {
152174
显然我们需要时不时地把服务端的变动同步到客户端,把客户端的输入同步到服务端,不然两边都不知道对面在干嘛。
153175

154176
好消息是,MC已经给我们造好了一大堆轮子可以直接使用。坏消息是,有些时候这些原版提供的轮子并不够用,需要我们手动发包同步。不同类有不同的方法来进行同步,而最通用的方式是手动发包,我将会在[5 网络包](/holojaneway/5)一节讲解。其他方式的详细内容会在涉及到的时候再讲。
177+
178+
---
179+
180+
## 奇奇怪怪的注意事项
181+
182+
### OnlyIn,但是为什么?
183+
184+
从1.21.7开始,由于你不能再使用@OnlyIn,就需要额外注意客户端与服务端代码分离。考虑这样一个示例:
185+
186+
```java
187+
public class Example {
188+
public void foo(){
189+
Minecraft.getInstance()...;
190+
}
191+
192+
public void bar(){
193+
System.out.println("bar");
194+
}
195+
}
196+
```
197+
198+
显然你不能在服务端调用`example.foo()`,这会立即导致`ClassNotFoundException/NoClassDefFoundError`。
199+
200+
但是如果在服务端调用`example.bar()`呢?我们都知道实际的类加载只会在程序跑到那里时才会触发,是否意味着只要我们私下约定不在服务端调用`foo()`,就不会出问题?
201+
202+
并非如此。显然,在你获取`example`对象之前,JVM需要先加载`Example`这个类。在加载过程中,JVM还需要进行验证、解析、链接等操作,而在这些操作中,根据不同的实现,JVM可以把类的方法中局部用到的类也一并加载。
203+
204+
然后,boom——但是这样听起来很不合理,对吧?我明明可以信心十足地约定或保证`foo()`不会在服务端被用到,何苦一定要拆成`ExampleC`和`ExampleS`两个类呢?
205+
206+
当然也有办法,我们注意到刚才只是说“JVM可以把类的方法中局部用到的类也一并加载”,那我们可以把`foo()`的内容用一个`Runnable`套起来,就像这样:
207+
208+
```java
209+
public void foo(){
210+
Runnable run = new Runnable(){
211+
public void run(){
212+
Minecraft.getInstance()...;
213+
}
214+
}
215+
run.run();
216+
}
217+
```
218+
219+
当`Example`加载的时候,就只会加载到`Runnable`这个类——而这个类本身里面什么也没有。等到真正开始执行`foo()`,才会尝试加载`Minecraft`类。
220+
221+
:::info
222+
223+
编译器或许还会提示你,`new Runnable()`可以写成lambda形式——在这里是万万不能的。`new Runnable(){}`是创建了一个新的匿名内部类,而lambda表达式则会直接调用`lambdaMetaFactory`来进行处理,在此过程中仍然会导致加载方法体内的内容。这是一个值得注意的微妙差别。
224+
225+
:::
226+
227+
当然这样并不方便,你或许可以像T88一样搞个编译器插件,甚至是直接用Manifold插件写个宏——但尽量还是直接分成两个类吧。

docs/holojaneway/1.2i 网络包部分.md

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,101 @@ hide_table_of_contents: false
77

88
---
99

10-
#
10+
# 网络包注解处理器
11+
12+
基础的使用介绍参考[网络包](5)一节。
13+
14+
15+
16+
## ClientHandler
17+
18+
在1.21.7之前,你可能习惯于直接在网络包的`clientHandler`方法里调用仅客户端的类,然后在方法上加`OnlyIn`。显然这个方法在新版本已经不适用,为了方便你的编写,T88提供一个简单的编译器插件,能够在编译时自动把`clientHandler`方法体用`Runnable`包起来以实现延迟加载。
19+
20+
为了使用这个插件,除去常规的导入之外:
21+
22+
```groovy
23+
dependencies {
24+
implementation "net.neoforged:neoforge:${neo_version}"
25+
26+
implementation("cn.ussshenzhou:t88:0.+")
27+
annotationProcessor("cn.ussshenzhou:t88:0.+")
28+
}
29+
```
30+
31+
还需要在你的`build.gradle`里加上这么一段:
32+
33+
```groovy
34+
tasks.withType(JavaCompile).configureEach {
35+
options.fork = true
36+
options.forkOptions.jvmArgs += [
37+
'--add-exports', 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED',
38+
'--add-exports', 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
39+
'--add-exports', 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
40+
'--add-exports', 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
41+
]
42+
options.compilerArgs += ['-Xplugin:T88ClientHandlerWrapper']
43+
}
44+
```
45+
46+
然后重新编译项目,对比源代码的从字节码反编译的结果:
47+
48+
```java
49+
@ClientHandler
50+
public void clientHandler(IPayloadContext context) {
51+
Level level = Minecraft.getInstance().level;
52+
if (level != null) {
53+
Random r = new Random();
54+
var player = Minecraft.getInstance().level.getPlayerByUUID(sourcePlayerUUID);
55+
if (player == null) {
56+
return;
57+
}
58+
var posOffset = getNozzlePosInWorld(player, 4, 0);
59+
var speed = getSpeedVec(player);
60+
for (int i = 0; i < particleOption.amount(); i++) {
61+
level.addParticle(
62+
particleOption,
63+
true,
64+
particleOption.alwaysRender().get(),
65+
posOffset.x + MathHelper.signedRandom(r) * particleOption.xDiffuse(),
66+
posOffset.y + MathHelper.signedRandom(r) * particleOption.yDiffuse(),
67+
posOffset.z + MathHelper.signedRandom(r) * particleOption.zDiffuse(),
68+
speed.x + MathHelper.signedRandom(r) * particleOption.vxDiffuse(),
69+
speed.y + MathHelper.signedRandom(r) * particleOption.vyDiffuse(),
70+
speed.z + MathHelper.signedRandom(r) * particleOption.vzDiffuse()
71+
);
72+
}
73+
}
74+
}
75+
```
76+
77+
```java
78+
public void clientHandler(IPayloadContext context) {
79+
Runnable wrapped = new Runnable() {
80+
public void run() {
81+
Level level = Minecraft.getInstance().level;
82+
if (level != null) {
83+
Random r = new Random();
84+
Player player = Minecraft.getInstance().level.getPlayerByUUID(MadParticleTadaPacket.this.sourcePlayerUUID);
85+
if (player == null) {
86+
return;
87+
}
88+
89+
Vec3 posOffset = MadParticleTadaPacket.this.getNozzlePosInWorld(player, 4.0F, 0.0F);
90+
Vec3 speed = MadParticleTadaPacket.this.getSpeedVec(player);
91+
92+
for(int i = 0; i < MadParticleTadaPacket.this.particleOption.amount(); ++i) {
93+
level.addParticle(MadParticleTadaPacket.this.particleOption, true, MadParticleTadaPacket.this.particleOption.alwaysRender().get(), posOffset.x + MathHelper.signedRandom(r) * (double)MadParticleTadaPacket.this.particleOption.xDiffuse(), posOffset.y + MathHelper.signedRandom(r) * (double)MadParticleTadaPacket.this.particleOption.yDiffuse(), posOffset.z + MathHelper.signedRandom(r) * (double)MadParticleTadaPacket.this.particleOption.zDiffuse(), speed.x + MathHelper.signedRandom(r) * (double)MadParticleTadaPacket.this.particleOption.vxDiffuse(), speed.y + MathHelper.signedRandom(r) * (double)MadParticleTadaPacket.this.particleOption.vyDiffuse(), speed.z + MathHelper.signedRandom(r) * (double)MadParticleTadaPacket.this.particleOption.vzDiffuse());
94+
}
95+
}
96+
97+
}
98+
};
99+
wrapped.run();
100+
}
101+
```
102+
103+
:::tip
104+
105+
在此过程中,`T88ClientHandlerWrapper`会自动地将`this``super`转换成`OuterClass.this``OuterClass.super`,但需要注意的是,T88并不会主动处理匿名内部类方法要求参数均为effective final的特性,effective final依靠方法体内实现自行保证。
106+
107+
:::

docs/holojaneway/5 网络包.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,29 @@ Codec是一种更加面向对象的序列化/反序列化工具。你可以在
102102

103103
:::
104104

105+
<div style={{
106+
backgroundColor: 'transparent',
107+
border: '2px solid #3c91ff',
108+
borderRadius: '0.5em',
109+
padding: '1em',
110+
}}>
111+
<Tabs groupId="mc-version">
112+
<TabItem value="1217" label="1.21.7">
113+
114+
:::caution
115+
116+
1.21.7,使用T88网络包注解处理器可能还需要额外的步骤。参考[T88使用文档-网络包](1.2i)一页了解详情。
117+
118+
:::
119+
120+
</TabItem>
121+
</Tabs>
122+
123+
<p></p>
124+
</div>
125+
126+
<p></p>
127+
105128
首先你需要在[T88CurseForge文件页面](https://www.curseforge.com/minecraft/mc-mods/t88/files)找到右侧的项目编号`Project ID`,它应该始终是`663112`。然后在文件列表中找到最新的**不带**`source`字样的jar,将鼠标置于其上,在浏览器的下方便会显示出这个jar文件的链接,链接的最后是它的编号,记下它。
106129

107130
:::note

0 commit comments

Comments
 (0)