Skip to content
On this page

Java API

ChaEngine 的稳定入口位于 com.github.ginirohikocha.engine.api。业务插件应依赖这些 API 类,不要直接调用 managernetwork 包中的内部实现。

所有会读取 Bukkit 实体、修改物品或发送数据的方法都应在服务端主线程调用。返回成功只表示服务端完成校验并提交本次操作;涉及客户端显示时,还要求目标玩家在线、安装兼容 ChaEngineMod、完成连接并拥有对应资源。

模型目录 API

ModelCatalog 用于明确选择模型类别:ModelCatalog.ENTITY 表示实体模型,ModelCatalog.BLOCK 表示方块模型。即使两类模型使用了相同 ID,查询结果也不会混在一起。

方法返回值说明
ChaEngineModelCatalogAPI.getModelIds(ModelCatalog catalog)Set<String>返回所选类别当前已加载模型 ID 的当前快照;结果按自然排序排列且不可修改
ChaEngineModelCatalogAPI.hasModelId(ModelCatalog catalog, String id)boolean在指定类别中精确查询模型 ID;空白 ID 返回 false

传入 null 作为 catalog 时,两个方法都会抛出 IllegalArgumentExceptiongetModelIds 返回的是与后续配置变化分离的快照;执行 /chaengine reload 后,应重新调用该方法取得新目录,不要长期保存旧结果。

下面的示例会在插件启用时读取两类模型目录,并检查一个实体模型是否存在:

java
package com.example.modelcatalog;

import com.github.ginirohikocha.engine.api.catalog.ChaEngineModelCatalogAPI;
import com.github.ginirohikocha.engine.api.catalog.ModelCatalog;
import java.util.Set;
import org.bukkit.plugin.java.JavaPlugin;

public final class ModelCatalogExamplePlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        Set<String> entityModelIds =
                ChaEngineModelCatalogAPI.getModelIds(ModelCatalog.ENTITY);
        Set<String> blockModelIds =
                ChaEngineModelCatalogAPI.getModelIds(ModelCatalog.BLOCK);

        getLogger().info("实体模型:" + entityModelIds);
        getLogger().info("方块模型:" + blockModelIds);

        boolean hasGuardian = ChaEngineModelCatalogAPI.hasModelId(
                ModelCatalog.ENTITY, "crystal_guardian");
        getLogger().info("crystal_guardian 已加载:" + hasGuardian);
    }
}

与 ChaUI 3D 场景配合

ChaUI 3D 场景直接使用模型类别和模型 ID 选择模型:ModelCatalog.ENTITY 表示实体模型,ModelCatalog.BLOCK 表示方块模型,两者都与模型 ID 一起用于页面配置。不需要创建伪实体,也不需要依赖实体名称或 NBT 间接选中模型。

ChaUI 场景模型、工作室光照、实时阴影、材质和动画控制当前支持六个 ChaEngineMod 客户端模块:Forge 1.16.5、Forge 1.20.1,以及 NeoForge 1.21.11.21.41.21.81.21.11。页面端还需要安装同版本的 ChaUIMod;不同版本或加载器的文件不能混用。

模型首次成功渲染后,客户端会自动取得整个模型的点击范围。点击范围不读取配置中的 collision,也不需要页面作者手工填写点击盒;模型尚未成功显示时不会响应模型点击。

第一版只支持整个模型点击,不支持按骨骼或 part 分别点击。需要多个独立操作入口时,请放置多个模型节点,或使用普通 ChaUI 组件承载对应操作。

客户端模型接入由 ChaUI 与 ChaEngineMod 在内部完成,不是 Bukkit 实体动画 API。插件开发者只需通过公开的模型目录 API 检查类别和模型 ID,不要通过反射调用客户端模型接入或渲染能力。

ChaEngineBlockAPI

方法返回值说明
hasModelId(String id)boolean检查已加载方块模型 ID
giveModelBlock(Player player, String id)boolean玩家在线且 ID 有效时创建并发放载体物品
setCarrierBlockModelAtLocation(String id, String locationString)SetModelResult修改 world,x,y,z 指向的载体方块自身数据
playCarrierBlockAnimationAtLocation(String locationString, String animationName)AnimationResult播放指定位置模型动画
stopCarrierBlockAnimationAtLocation(String locationString)AnimationResult停止指定位置模型动画
isCarrierBlockModel(Block block, String id)boolean检查方块自身保存的模型 ID
isCarrierItemModel(ItemStack itemStack, String id)boolean检查方块载体物品的模型 ID
getCarrierBlockModelId(Block block)Stringnull读取方块自身模型 ID
getCarrierItemModelId(ItemStack itemStack)Stringnull读取方块载体物品模型 ID

SetModelResult 包含 SUCCESSINVALID_MODEL_IDINVALID_LOCATIONNOT_CARRIER_BLOCKCOMMAND_FAILEDAnimationResult 包含 SUCCESSINVALID_LOCATIONINVALID_ANIMATIONNOT_CARRIER_BLOCK

java
import com.github.ginirohikocha.engine.api.block.ChaEngineBlockAPI;
import com.github.ginirohikocha.engine.manager.block.BlockModelManager;

BlockModelManager.SetModelResult result =
        ChaEngineBlockAPI.setCarrierBlockModelAtLocation(
                "altar", "world,120,64,-30");
if (result != BlockModelManager.SetModelResult.SUCCESS) {
    getLogger().warning("设置方块模型失败:" + result);
}

ChaEngineItemAPI

方法返回值拒绝规则
hasItemId(String id)boolean空或未知 ID 返回 false
giveCarrierItem(Player player, String id, int amount)boolean玩家无效或 ID 未加载时返回 false;数量最小为 1
createCarrierItem(String id, int amount)ItemStacknullID 无效时返回 null;数量最小为 1
getCarrierItemModelId(ItemStack itemStack)Stringnull非 ChaEngine 物品返回 null
setCarrierItemScale(ItemStack itemStack, double multiplier)boolean写入单件物品缩放倍率;只接受大于 0 的有限数
getCarrierItemScale(ItemStack itemStack)Doublenull读取单件物品倍率;未设置或数据无效时返回 null
clearCarrierItemScale(ItemStack itemStack)boolean删除单件物品倍率并恢复默认 1.0
java
import com.github.ginirohikocha.engine.api.item.ChaEngineItemAPI;
import org.bukkit.inventory.ItemStack;

ItemStack reward = ChaEngineItemAPI.createCarrierItem("crystal_sword", 1);
if (reward != null) {
    ChaEngineItemAPI.setCarrierItemScale(reward, 1.25D);
    player.getInventory().addItem(reward);
}

动态倍率保存在物品自身数据中,同时作用于三维物品模型和平面 item-texture。调用方必须把修改后的 ItemStack 放回实际背包、容器或持久化位置。

ChaEngineEntityAPI

实体模型与动画

方法返回值说明
setCarrierEntityModel(Entity entity, String id)SetModelResult默认临时模式
setCarrierEntityModel(Entity entity, String id, SetMode mode)SetModelResultTEMPORARYPERMANENT;永久模式写入实体自身数据
clearCarrierEntityModel(Entity entity)SetModelResult清除强制模型
setCarrierEntityModel(EntityTarget target, String id)SetModelResult按目标定位实体并验证配置 ID
setCarrierEntityModel(EntityTarget target, String id, boolean validateConfig)SetModelResult仅可信内部数据才应关闭配置验证
clearCarrierEntityModel(EntityTarget target)SetModelResult清除目标实体强制模型
playCarrierEntityAnimation(Entity entity, String animationName)AnimationResult播放特殊动画
stopCarrierEntityAnimation(Entity entity)AnimationResult恢复客户端自动动画
playCarrierEntityOverlayAnimation(Entity entity, String animationName)AnimationResult在基础动画上叠加并重新开始覆盖动画
stopCarrierEntityOverlayAnimation(Entity entity)AnimationResult停止覆盖层,保留基础动画

SetModelResult 包含 SUCCESSINVALID_ENTITYINVALID_MODEL_IDCOMMAND_FAILEDAnimationResult 包含 SUCCESSINVALID_ENTITYINVALID_ANIMATION

跨封装边界定位实体时,可使用 WorldEntityTarget.of(String dimensionId, UUID entityUuid)。维度 ID 不能为空或包含 |,UUID 不能为空;目标实体必须仍在对应世界中可解析。

java
import com.github.ginirohikocha.engine.api.entity.ChaEngineEntityAPI;
import com.github.ginirohikocha.engine.manager.entity.EntityModelManager;
import org.bukkit.entity.Entity;

EntityModelManager.SetModelResult result =
        ChaEngineEntityAPI.setCarrierEntityModel(
                entity, "crystal_guardian", EntityModelManager.SetMode.PERMANENT);

自定义荧光描边

方法返回值说明
setCarrierEntityGlow(LivingEntity entity, String color)GlowResult为有效生物设置严格 #RRGGBB 自定义描边颜色
clearCarrierEntityGlow(LivingEntity entity)GlowResult清除该生物的自定义颜色;未设置时同样成功
setCarrierEntityGlow(EntityTarget target, String color)GlowResult定位目标并确认它仍是有效生物后设置颜色
clearCarrierEntityGlow(EntityTarget target)GlowResult定位目标并清除自定义颜色

GlowResult 包含 SUCCESSINVALID_ENTITYINVALID_COLORCOMMAND_FAILED。颜色必须是带 # 的六位十六进制 RGB,例如 #33CCFF;重复设置会替换旧颜色。EntityTarget 找不到实体或解析到非生物实体时返回 INVALID_ENTITY

java
import com.github.ginirohikocha.engine.api.entity.ChaEngineEntityAPI;
import com.github.ginirohikocha.engine.manager.entity.EntityModelManager;
import org.bukkit.entity.LivingEntity;

public void applyQuestGlow(LivingEntity entity) {
    EntityModelManager.GlowResult result =
            ChaEngineEntityAPI.setCarrierEntityGlow(entity, "#33CCFF");
    if (result != EntityModelManager.GlowResult.SUCCESS) {
        getLogger().warning("设置实体荧光失败:" + result);
    }
}

该 API 不调用实体的 setGlowing,也不写入实体 NBT 或 PDC,因此不会覆盖其他插件维护的原版发光状态。颜色对所有收到该实体状态的玩家一致;清除后恢复客户端原版发光和队伍颜色逻辑。状态不持久化,实体卸载、切换维度、插件重载或服务器重启时都会清理。

玩家与实体时装

方法返回值说明
addCarrierEquip(Player player, String id)EquipResult添加进程期保留的玩家时装
addTemporaryCarrierEquip(Player player, String id)EquipResult添加临时时装
removeCarrierEquip(Player player, String id)EquipResult按配置 ID 移除其部位时装
clearCarrierEquips(Player player)EquipResult清空玩家全部时装
addCarrierEquip(EntityTarget target, String id, String part)EquipResult给已定位实体添加指定部位时装并验证 ID
addCarrierEquip(EntityTarget target, String id, String part, boolean validateConfig)EquipResult可控制是否验证配置 ID
removeCarrierEquip(EntityTarget target, String part)EquipResult清除该部位对应的已知时装

EquipResult 包含 SUCCESSINVALID_PLAYERINVALID_EQUIP_ID。玩家版本要求玩家在线;实体目标版本要求目标可解析。玩家“保留”状态不写数据库,服务器重启后需由业务插件恢复。

java
import com.github.ginirohikocha.engine.api.entity.ChaEngineEntityAPI;
import com.github.ginirohikocha.engine.manager.entity.EquipModelManager;

EquipModelManager.EquipResult result =
        ChaEngineEntityAPI.addCarrierEquip(player, "winter_hat");

ChaUI 时装预览的四个方法也位于此类,生命周期和返回值见 ChaUI 时装模型预览

ChaEngineCameraAPI 与 CameraStatusEvent

调用相机前先用 isSupported(Player) 检查当前连接是否已经声明相机能力。不要按 Minecraft 版本硬编码支持状态。

方法返回值说明
isSupported(Player player)boolean当前在线玩家是否已完成相机能力准备
setPerspective(Player player, CameraPerspective perspective, boolean locked)CameraRequestResult切换第一人称、背面第三人称或正面第三人称,并可锁定玩家主动切换
applyPreset(Player player, String presetId)CameraRequestResult应用 camera/presets/ 下的相机预设
playScene(Player player, String sceneId)CameraRequestResult播放 camera/scenes/ 下的场景镜头
stop(Player player, UUID sessionId)CameraRequestResult只停止指定会话
interrupt(Player player, UUID sessionId)CameraRequestResult独立打断指定场景并等待 interrupt 衔接;不会降级为 stop
reset(Player player)CameraRequestResult清空该玩家全部远程相机会话并恢复原状态

CameraPerspective 包含 FIRST_PERSONTHIRD_BACKTHIRD_FRONT;“第二人称”使用 THIRD_FRONT,不要在 API 中自造第二套枚举名。

CameraRequestResult.status() 可能返回 SENTUNSUPPORTED_CLIENTINVALID_ARGUMENTPRESET_NOT_FOUNDSCENE_NOT_FOUNDPAYLOAD_TOO_LARGESEND_FAILEDUNKNOWN_SESSION。成功创建会话时用 sessionId() 保存 UUID,后续只停止自己的会话。

java
import com.github.ginirohikocha.engine.api.camera.CameraRequestResult;
import com.github.ginirohikocha.engine.api.camera.ChaEngineCameraAPI;

CameraRequestResult result =
        ChaEngineCameraAPI.playScene(player, "intro");
if (result.sent()) {
    activeScenes.put(player.getUniqueId(), result.sessionId());
} else {
    getLogger().warning("场景镜头发送失败:" + result.status());
}

SENT 只表示请求已发送。客户端确认后触发 CameraStatusEvent,可通过 player()sessionId()revision()kind()status()reason() 读取结果。kind()CONTROLSCENEstatus()STARTEDFINISHEDABORTEDINTERRUPTEDREJECTEDUNSUPPORTED

正常结束、管理员停止、玩家跳过和异常中止是不同状态。业务插件应分别处理 FINISHEDABORTEDINTERRUPTED,并在玩家退出或自身停用时清理保存的会话 ID。配置与使用说明见相机系统相机预设场景镜头

ChaEngineKeyBindAPI 与事件

方法返回值说明
registerKeyBind(Plugin plugin, KeyBindConfig config)boolean校验并以插件名登记;成功后广播完整快照
unregisterKeyBind(Plugin plugin, String id)boolean只有同一所有者可注销
clearPluginKeyBinds(Plugin plugin)int返回清除数量
hasKeyBind(String id)boolean检查当前注册表
getKeyBind(String id)KeyBindConfignull返回配置快照
completeKeyBindIds(String current)List<String>返回按前缀过滤并排序的 ID

注册要求 PluginidnamedefaultKey 有效。布尔返回值表示服务端注册表是否改变,不表示每个客户端都已显示按键。客户端上报已注册 ID 后,服务端先触发 KeyBindPressEventKeyBindReleaseEvent,再执行对应动作;两个事件都通过 getPlayer()getId() 读取数据,且不可取消。

java
import com.github.ginirohikocha.engine.api.key.ChaEngineKeyBindAPI;
import com.github.ginirohikocha.engine.api.key.event.KeyBindPressEvent;
import com.github.ginirohikocha.engine.entity.config.KeyBindConfig;
import org.bukkit.event.EventHandler;

KeyBindConfig key = new KeyBindConfig();
key.setId("myplugin_dash");
key.setName("冲刺");
key.setDefaultKey("SHIFT+R");
key.setCategory("MyPlugin");
ChaEngineKeyBindAPI.registerKeyBind(this, key);

@EventHandler
public void onDash(KeyBindPressEvent event) {
    if (event.getId().equals("myplugin_dash")) {
        // 在服务端主线程处理按下事件
    }
}

插件停用前调用 clearPluginKeyBinds(plugin),不要留下已失去所有者的运行期定义。

ChaEngineParticleAPI

粒子程序按四种范围提供同构方法:

范围启动 / 更新停止 / 全部停止
全局startGlobal(players, groupId, ttlTicks, scripts) / updateGlobal(...)stopGlobal(players, groupId) / stopAllGlobal(players)
维度startDimension(players, world, groupId, origin, ttlTicks, scripts) / updateDimension(...)stopDimension(players, world, groupId) / stopAllDimension(players, world)
实体startEntity(players, entity, groupId, ttlTicks, scripts) / updateEntity(...)stopEntity(players, entity, groupId) / stopAllEntity(players, entity)
方块startBlock(players, location, groupId, ttlTicks, scripts) / updateBlock(...)stopBlock(players, location, groupId) / stopAllBlock(players, location)

另有单玩家便捷重载 startGlobal(Player, String, int, List<String>)stopGlobal(Player, String)。所有方法返回 boolean:至少向一个在线接收者提交数据时为 true。它不证明客户端资源、维度、距离或脚本解析成功。TTL 限制为 1..36000 tick,非正数归一为 20;最多发送 64 行,每行去空白后最多 1024 字符。

java
import com.github.ginirohikocha.engine.api.particle.ChaEngineParticleAPI;
import java.util.List;

boolean sent = ChaEngineParticleAPI.startEntity(
        viewers, boss, "myplugin_boss_aura", 20 * 15,
        List.of("ring minecraft:end_rod radius 1.2 points 20 spin 8 every 1"));

完整 DSL、范围和客户端预算见粒子程序

BedrockParticleAPI 与 BedrockParticleOptions

方法返回值说明
playOnce(players, location, effectId, parameters)boolean生成随机实例 ID,使用位置朝向播放一次
start(players, location, instanceId, effectId, ttlTicks, options)boolean启动或替换具名实例
stop(players, instanceId)boolean停止单个实例
stopAll(players)boolean清除接收客户端上的全部基岩版粒子实例

BedrockParticleOptions 是不可变对象。用 defaults() 创建,再链式调用 withRotation(float,float,float)withSeed(long)withParameter(String,double);读取方法为 getYaw()getPitch()getRoll()getSeed()getParameters()

实例 ID 与效果 ID 必须是命名空间 ID。参数名只接受小写字母、数字、下划线和点,最多 64 UTF-8 字节;最多 32 个参数,旋转和参数值必须为有限数。API 对空接收者、空参数或非法 ID 返回 false;构建选项时的非法参数由 with... 方法抛出 IllegalArgumentException

java
import com.github.ginirohikocha.engine.api.particle.BedrockParticleAPI;
import com.github.ginirohikocha.engine.api.particle.BedrockParticleOptions;

BedrockParticleOptions options = BedrockParticleOptions.defaults()
        .withSeed(42L)
        .withRotation(location.getYaw(), location.getPitch(), 0.0F)
        .withParameter("speed", 0.8D);
boolean sent = BedrockParticleAPI.start(
        viewers, location, "myplugin:portal_01", "myplugin:portal", 200, options);

资源定义和表达式语法见基岩版粒子

实体动画事件

EntityModelAnimationStartEvent 在服务端接受有效的特殊动画播放操作后触发,提供 getEntity()getAnimationName()EntityModelAnimationEndEvent 在特殊动画停止时触发,提供 getEntity() 与可空的 getAnimationName();无法确认结束名称时为 null。二者均为同步 Bukkit 事件,不可取消。

java
import com.github.ginirohikocha.engine.api.entity.event.EntityModelAnimationEndEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public final class AnimationListener implements Listener {
    @EventHandler
    public void onAnimationEnd(EntityModelAnimationEndEvent event) {
        String name = event.getAnimationName();
        getLogger().info("实体动画结束:" + (name == null ? "未知" : name));
    }
}

调用检查表

  1. 在插件依赖中声明 ChaEngine,并等待它启用后再注册动态能力。
  2. 只在服务端主线程读写 Bukkit 对象或调用发送类 API。
  3. 显式处理枚举和布尔返回值,不把“已发送”解释为“已渲染”。
  4. 玩家退出、页面关闭或业务对象销毁时,停止粒子、相机会话并清理临时预览。
  5. 服务端重载后重新核对业务保存的模型、时装与按键 ID 是否仍存在。