You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spigot 1.20.4雕像插件:自定义StatueAction序列化问题求助

问题背景

我正在开发基于Spigot 1.20.4(Java)的雕像插件,用于创建NPC雕像。目前创建雕像时,可通过StatueAction定义NPC被右键点击时的行为,代码示例:

Statue.StatueBuilder builder = new Statue.StatueBuilder(entityType)
.setName("Test")
.setOnClick(connectionPlayer -> System.out.println("TEST " + new Random().nextInt()));
Statue statue = builder.build();

其中setOnClick接收的StatueAction定义为:

public interface StatueAction {
    void perform(ConnectionPlayer player);
}

需要将雕像数据保存到文件(YML/JSON等),但无法序列化该自定义行为。尝试YML和JSON序列化后,onClick字段输出为空对象{},当前序列化代码如下:

序列化尝试代码:

StatueAction<ConnectionPlayer> r = (connectionPlayer) -> System.out.println("Hello");
Statue.StatueBuilder builder = new Statue.StatueBuilder(entityType).setName("Test").setOnClick(r);
Statue statue = builder.build();
statue.spawn(player.getLocation());
statue.createDefaultAttribs();
System.out.println(statue.serialize());

序列化输出:

{"name":"Test","entityType":"ARMOR_STAND","entityUUID":"6c2a484c-3168-4fb8-a3ee-8c2d93bddf1d","textDisplaysID":[],"onClick":{}}

Statue的序列化方法:

public static class StatueSerializer implements JsonSerializer<Statue> {
    @Override
    public JsonElement serialize(Statue statue, Type type, JsonSerializationContext jsonSerializationContext) {
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("name", statue.name);
        jsonObject.addProperty("entityType", statue.entityType.name());
        jsonObject.addProperty("entityUUID", statue.getEntityUUID().toString());
        jsonObject.add("textDisplaysID", jsonSerializationContext.serialize(statue.getTextDisplaysID()));

        // Serialize the CustomAction using its own serialization logic
        jsonObject.add("onClick", jsonSerializationContext.serialize(statue.getOnClick()));

        return jsonObject;
    }
}

public String serialize() {
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(Statue.class, new Statue.StatueSerializer())
            .create();
    return gson.toJson(this);
}
解决方案

直接序列化Lambda不可行:Lambda依赖JVM动态生成的内部类,类名不稳定,且反序列化需要完全一致的类加载环境,Spigot插件的类加载器机制会导致重启后反序列化失败,同时序列化内容不可读、不可维护,不适合插件配置场景。以下是推荐的替代方案:

方案1:动作标识符+注册表(推荐)

核心思路是给每个可复用的动作分配唯一标识符,序列化时仅存储标识符,反序列化时通过注册表匹配对应的动作实例。

  1. 创建动作注册表
public class StatueActionRegistry {
    private static final Map<String, StatueAction> ACTION_MAP = new HashMap<>();

    // 注册动作,插件加载时执行
    public static void register(String actionId, StatueAction action) {
        ACTION_MAP.put(actionId, action);
    }

    // 根据ID获取动作实例
    public static StatueAction getAction(String actionId) {
        return ACTION_MAP.get(actionId);
    }
}
  1. 修改Statue类存储动作ID
public class Statue {
    private String name;
    private EntityType entityType;
    private UUID entityUUID;
    private List<String> textDisplaysID;
    private String onClickActionId; // 存储动作ID而非动作实例

    // 更新Builder方法
    public static class StatueBuilder {
        private Statue statue = new Statue();

        public StatueBuilder setName(String name) {
            statue.name = name;
            return this;
        }

        public StatueBuilder setEntityType(EntityType type) {
            statue.entityType = type;
            return this;
        }

        public StatueBuilder setOnClickActionId(String actionId) {
            statue.onClickActionId = actionId;
            return this;
        }

        public Statue build() {
            return statue;
        }
    }

    // 获取动作实例
    public StatueAction getOnClickAction() {
        return StatueActionRegistry.getAction(onClickActionId);
    }

    // 更新序列化逻辑
    public static class StatueSerializer implements JsonSerializer<Statue> {
        @Override
        public JsonElement serialize(Statue statue, Type type, JsonSerializationContext ctx) {
            JsonObject json = new JsonObject();
            json.addProperty("name", statue.name);
            json.addProperty("entityType", statue.entityType.name());
            json.addProperty("entityUUID", statue.entityUUID.toString());
            json.add("textDisplaysID", ctx.serialize(statue.textDisplaysID));
            json.addProperty("onClickActionId", statue.onClickActionId);
            return json;
        }
    }

    // 添加反序列化逻辑
    public static class StatueDeserializer implements JsonDeserializer<Statue> {
        @Override
        public Statue deserialize(JsonElement json, Type type, JsonDeserializationContext ctx) throws JsonParseException {
            JsonObject obj = json.getAsJsonObject();
            Statue statue = new Statue();
            statue.name = obj.get("name").getAsString();
            statue.entityType = EntityType.valueOf(obj.get("entityType").getAsString());
            statue.entityUUID = UUID.fromString(obj.get("entityUUID").getAsString());
            statue.textDisplaysID = ctx.deserialize(obj.get("textDisplaysID"), new TypeToken<List<String>>(){}.getType());
            statue.onClickActionId = obj.get("onClickActionId").getAsString();
            return statue;
        }
    }

    // 更新序列化方法
    public String serialize() {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(Statue.class, new StatueSerializer())
                .registerTypeAdapter(Statue.class, new StatueDeserializer())
                .create();
        return gson.toJson(this);
    }
}
  1. 注册预定义动作
    在插件主类的onEnable方法中注册所有需要的动作:
@Override
public void onEnable() {
    StatueActionRegistry.register("print_random", player -> {
        System.out.println("TEST " + new Random().nextInt());
    });
    StatueActionRegistry.register("print_hello", player -> {
        System.out.println("Hello");
    });
}
  1. 创建雕像时使用动作ID
Statue.StatueBuilder builder = new Statue.StatueBuilder(EntityType.ARMOR_STAND)
        .setName("Test")
        .setOnClickActionId("print_random");
Statue statue = builder.build();

方案2:预定义动作枚举(适合固定动作场景)

如果动作数量少且逻辑固定,可以用枚举类封装所有动作,序列化时存储枚举名称。

  1. 定义动作枚举
public enum PredefinedStatueAction implements StatueAction {
    PRINT_RANDOM {
        @Override
        public void perform(ConnectionPlayer player) {
            System.out.println("TEST " + new Random().nextInt());
        }
    },
    PRINT_HELLO {
        @Override
        public void perform(ConnectionPlayer player) {
            System.out.println("Hello");
        }
    };
}
  1. 修改Statue类存储枚举实例
public class Statue {
    private PredefinedStatueAction onClickAction;

    // 更新Builder方法
    public StatueBuilder setOnClickAction(PredefinedStatueAction action) {
        this.onClickAction = action;
        return this;
    }

    // 序列化时存储枚举名称
    public static class StatueSerializer implements JsonSerializer<Statue> {
        @Override
        public JsonElement serialize(Statue statue, Type type, JsonSerializationContext ctx) {
            JsonObject json = new JsonObject();
            // ... 其他字段序列化
            json.addProperty("onClickAction", statue.onClickAction.name());
            return json;
        }
    }

    // 反序列化时解析枚举
    public static class StatueDeserializer implements JsonDeserializer<Statue> {
        @Override
        public Statue deserialize(JsonElement json, Type type, JsonDeserializationContext ctx) throws JsonParseException {
            JsonObject obj = json.getAsJsonObject();
            Statue statue = new Statue();
            // ... 其他字段解析
            statue.onClickAction = PredefinedStatueAction.valueOf(obj.get("onClickAction").getAsString());
            return statue;
        }
    }
}

方案3:脚本引擎(适合动态自定义动作)

如果需要支持用户通过配置文件编写动态动作,可以使用Java内置的脚本引擎(如Nashorn),序列化时存储脚本内容,反序列化时编译执行。

  1. 修改Statue类存储脚本
public class Statue {
    private String onClickScript;

    // 更新Builder方法
    public StatueBuilder setOnClickScript(String script) {
        this.onClickScript = script;
        return this;
    }

    // 执行脚本的方法
    public void performOnClick(ConnectionPlayer player) {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
        try {
            engine.put("player", player); // 将玩家对象传入脚本
            engine.eval(onClickScript);
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }

    // 序列化时存储脚本字符串
    public static class StatueSerializer implements JsonSerializer<Statue> {
        @Override
        public JsonElement serialize(Statue statue, Type type, JsonSerializationContext ctx) {
            JsonObject json = new JsonObject();
            // ... 其他字段序列化
            json.addProperty("onClickScript", statue.onClickScript);
            return json;
        }
    }
}
  1. 创建雕像时传入脚本
Statue.StatueBuilder builder = new Statue.StatueBuilder(EntityType.ARMOR_STAND)
        .setName("Test")
        .setOnClickScript("print('Hello ' + player.getName());");
Statue statue = builder.build();

注意:此方法存在安全风险,需限制脚本权限,仅适合可控环境使用。

内容的提问来源于stack exchange,提问作者A_Brave_Panda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 04:15:59