自1.17之后, SpigotMC 开始提供 Mojang 混淆表版本的 Spigot 服务端,这意味着大大简化了开发难度 —— 不需要再对照混淆表一个一个看NMS 方法名

本篇我来介绍使用Paper API的插件如何使用Mojang mappings自定义实体

引入依赖

这里使用Gradle Groovy讲解

这里有用Gradle Kotlin的例子
https://github.com/PaperMC/paperweight-test-plugin/

一共要改两处地方

build.gradle
1
2
3
4
5
6
7
8
9
plugins {
...
id "io.papermc.paperweight.userdev" version "1.4.1"
}
dependencies {
...
paperweightDevelopmentBundle "io.papermc.paper:dev-bundle:1.19.3-R0.1-SNAPSHOT"
// compileOnly 'io.papermc.paper:paper-api:1.19.3-R0.1-SNAPSHOT'
}
settings.gradle
1
2
3
4
5
6
pluginManagement {
repositories {
gradlePluginPortal()
maven{ url = "https://repo.papermc.io/repository/maven-public/"}
}
}

创建自定义实体

很明显,根据已有的API,甚至是Paper API,都没有向我们提供创建自定义实体的功能。因此,想要创建自定义实体,需要使用 Mojang mappings 我们刚才依赖引入的就是(1.17- 需要使用NMS)

本例中,我们创建一个不会被破坏,只能坐一个玩家的船(不是箱船),来自我的项目 – IceBoat

继承已有实体

让我们创建 GameBoat 类,继承 net.minecraft.world.entity.vehicle.Boat 类

1
public class GameBoat extends Boat {}

接下来,初始化该实体,实现超类构造器

1
2
3
4
5
public class GameBoat extends Boat {
public GameBoat(EntityType<? extends Boat> type, Level world) {
super(type, world);
}
}

要想生成该实体,则应该调用 Boat#setPos(x,y,z) 设置坐标,然后调用 ServerLevel#addFreshEntityWithPassengers(Entity) 方法生成实体。为了简便流程,我们可以自定义一个可传入 Bukkit Location 的构造函数,使其调用后自动在设定坐标生成

1
2
3
4
5
6
public GameBoat(Location location) {
this(EntityType.BOAT, ((CraftWorld) location.getWorld()).getHandle());
this.setPos(location.getX(), location.getY(), location.getZ());
ServerLevel level = ((CraftWorld) location.getWorld()).getHandle();
level.addFreshEntityWithPassengers(this);
}

然后你就可以看到你自定义的船实体了

继续修改

只能一个玩家乘坐

我们观察 net.minecraft.world.entity.vehicle.Boat 的源码,可以发现乘坐调用的是 Boat#addPassenger(Entity),所以我们重写这个方法就行

1
2
3
4
5
6
@Override
public boolean addPassenger(@NotNull Entity entity) {
if (entity instanceof Player && this.getPassengers().size() == 0) {
return super.addPassenger(entity);
} return false;
}

既然Passengers(List<Entity>)只有一个,那我们可以顺便写个获取获取乘坐者的方法

1
2
3
4
public org.bukkit.entity.Player getPassenger() {
List<Entity> passengers = this.getPassengers();
return passengers.size() == 1 ? (org.bukkit.entity.Player) passengers.get(0) : null;
}

哦,对了如果你想addPassenger的话,我们可以再写个方法

1
2
3
4
5
public void addPassenger(org.bukkit.entity.Player player) {
// org.bukkit.entity.Player to net.minecraft.world.entity.Entity
Entity entityPlayer = ((CraftPlayer) player).getHandle();
entityPlayer.startRiding(this);
}

修改Tick

重写tick()就行,别忘记加上super.tick();,除非你真的想彻底更改这个实体

1
2
3
4
@Override
public void tick() {
super.tick();
}

一些有用的互转

org.bukkit.entity.Player to net.minecraft.world.entity.Entity
1
Entity entityPlayer = ((CraftPlayer) xxx).getHandle();
org.bukkit.craftbukkit.v1_19_R2.CraftWorld to net.minecraft.server.level.ServerLevel(net.minecraft.world.level.Level)
1
ServerLevel level = ((CraftWorld) xxx.getWorld()).getHandle();

编译

(踩坑 +1)

你现在不是用 gradle build 来编译插件了,应改用paperweight提供的reobfJar也就是命令变成了 gradle reobfJar