Minecraft 服务端插件-笔记

Spigot 1.8

Lombok

Project Lombok

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again. Early access to future java features such as val, and much more.

Lombok将我们从频繁书写getter和equals等方法中的麻烦中脱离出来,源码中通过注解标记,搭配各种编辑器、构建工具的相应插件,Lombok将这些注解在编译前转化为对应的方法实现。

这样,实际上对于编译器来说,两种方法的源码都是一样的,但对于程序员来说,使用注解能简化操作,让源码更加整洁,突出实际需要的代码逻辑。

@Data这个实际上是集成了@ToString,@EqualsAndHashCode,@Getter,Setter@RequiredArgsConstructor这几个注解的功能,为一个常见的数据类提供了基本上需要的各种模板方法。

@Builder则提供了一种链式的建造者模式。

1
2
3
4
5
@Data
@Builder
public class MapPath {
String difficulty, scene, level;
}

看,这样多简洁。如果需要自定义实现其中的方法,Lombok在分析源码时会跳过已实现的方法。

AABB

Axis-Aligned Bounding Box

轴对齐包围盒,是一种计算和存储上有效的表示体积的方法,常用比较看两个物体是否可能会碰撞。

如果物体是不规则的,可以先判断两个物体的AABB是否有重叠,再进一步比较具体的数据。

ConfigurationSerializable

Spigot 是一种Minecraft服务端。

之前写插件的时候,碰到有要序列化到config.yml的需求,一般也是实现serializedeserialize这两个方法,但是是自己调用的方法。

实际上Spigot是提供了一种序列化到Config的机制的,其中一点要求就是实现ConfigurationSerializable这个接口。

1
2
3
public interface ConfigurationSerializable {
Map<String, Object> serialize();
}

可以看到,这个接口中也是要让你去实现序列化方法,所以这个接口的主要作用还是标记自定义的类是可以被序列化到配置文件(SnakeYaml)中的。

这样,只要一个类中的非原始类型都实现了这个接口,那么再去实现序列化方法就很方便了。

实现了这个接口以及需要的方法之后,还需要通过ConfigurationSerialization.registerClass(Class<? extends ConfigurationSerializable>)来注册这个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Data
@Builder
public class FormatEntity implements ConfigurationSerializable {
//略去部分具体实现

Material model;
String nameTemplate;
List<String> loreTemplate;

@Override
public Map<String, Object> serialize() {
Map<String, Object> map = new HashMap<>(3);
map.put("model", model.name());
map.put("nameTemplate", nameTemplate);
map.put("loreTemplate", loreTemplate);
return map;
}

@SuppressWarnings("unchecked")
public static FormatEntity deserialize(Map<String, Object> args) {
Material model = Material.getMaterial((String) args.getOrDefault("model", "STAINED_GLASS_PANE"));
if(model == null) model = Material.STAINED_GLASS_PANE;
return FormatEntity.builder()
.model(model)
.nameTemplate((String) args.getOrDefault("nameTemplate", UNDEFINED_TEMPLATE))
.loreTemplate((List<String>) args.getOrDefault("loreTemplate", Collections.singletonList(UNDEFINED_TEMPLATE)))
.build();
}
}

@Data
@Builder
public class SceneFormat implements ConfigurationSerializable {
FormatEntity base, lock, unlock;

@Override
public Map<String, Object> serialize() {
Map<String, Object> map = new HashMap<>(3);
map.put("base", base);
map.put("lock", lock);
map.put("unlock", unlock);
return map;
}

public static SceneFormat deserialize(Map<String, Object> args) {
return SceneFormat.builder()
.base((FormatEntity) args.getOrDefault("base", FormatEntity.UNDEFINED_TEMPLATE))
.lock((FormatEntity) args.getOrDefault("lock", FormatEntity.UNDEFINED_TEMPLATE))
.unlock((FormatEntity) args.getOrDefault("unlock", FormatEntity.UNDEFINED_TEMPLATE))
.build();
}
}

Enum Material

序列化org.bukkit.Material时出现了这样的报错”could not determine a constructor for the tag tag:yaml.org,2002:org.bukkit.Material”

原因是,这个枚举类没有实现ConfigruationSerializable,所以反序列化时出现了问题,找不到合适的构造器。

解决方案是序列化时存储enum的name,反序列化通过Material.getMaterial。

TabComplete

之前实现命令的时候一直没注意到Spigot实际上是提供了Tab补全机制的,命令类实现TabComplete接口的onTabComplete就行了。

PlayerInventory

Hotbar

想设置玩家Hotbar处的物品,发现Inventory.setItem是没办法实现的,最后是通过PlayerInventory.setHeldItemSlot设置玩家当前手持物品所处的位置(0-8),然后使用PlayerInventory.setItemInHand设置当前手持物品来实现的。

Save and restore

保存然后恢复玩家背包物品的话,通过PlayerInventory.getContentsPlayerInventory.setContents两个方法就可以了。

Tellraw

Tellraw命令可以发送更丰富的文本,包含颜色,下划线,悬浮和点击事件。

找到了一个几年前我自己写的实现,完全是通过分析对应Json的格式然后通过StringBuilder一个个字符串拼接成的 -。-ll

因为基本上这个类都是自己在用,所以这次写插件也没想着去重写,功能上实现也没什么问题。

事实上现在官方的相关API也已经出来了。

Farmland

玩家跳跃导致耕地退化为泥土,事件为PlayerInteractEvent,其中event.getAction == Action.PHYSICAL

ENDER_PEARL

监听到末影珍珠丢出,取消事件,这个时候玩家背包里显示没有末影珍珠了,实际上是还在的,所以需要手动调用Player.updateInventory来强制更新玩家背包。