718 words
4 minutes
自定义实体 | Bukkit插件开发
2023-01-12
NOTE

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

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

引入依赖#

TIP

官方的Gradle Kotlin例子

PaperMC
/
paperweight-test-plugin
Waiting for api.github.com...
00K
0K
0K
Waiting...

这里我使用Gradle Groovy讲解

一共要改两处地方,对了,1.4.1版本记得修改

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'
}
pluginManagement {
    repositories {
        gradlePluginPortal()
        maven{ url = "https://repo.papermc.io/repository/maven-public/"}
    }
}

Then! 创建自定义实体#

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

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

继承已有实体#

欸,既然要弄一个不一样的船,但总不可能凭空变出来吧,我们还是得继承游戏原来的Boat代码

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

public class GameBoat extends Boat {}

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

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 的构造函数,使其调用后自动在设定坐标生成

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);
}

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

继续修改#

只能一个玩家乘坐#

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

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

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

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

哦,对了如果你想addPassenger的话,我们可以再写个方法,这样玩家能否上船也在你的掌握之中了!

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();,除非你真的想彻底更改这个实体

@Override
public void tick() {
    super.tick();
}

Tips: 一些有用的互转#

Entity entityPlayer = ((CraftPlayer) xxx).getHandle();
ServerLevel level = ((CraftWorld) xxx.getWorld()).getHandle();

编译#

(踩坑 +1) 小白本尊尝试了N多次,希望能让你避开这个坑

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

自定义实体 | Bukkit插件开发
https://www.noctiro.moe/posts/bukkit-custom-entity/
Author
Noctiro
Published at
2023-01-12