Java API
ChaEngine 的稳定入口位于 com.github.ginirohikocha.engine.api。业务插件应依赖这些 API 类,不要直接调用 manager 或 network 包中的内部实现。
所有会读取 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 时,两个方法都会抛出 IllegalArgumentException。getModelIds 返回的是与后续配置变化分离的快照;执行 /chaengine reload 后,应重新调用该方法取得新目录,不要长期保存旧结果。
下面的示例会在插件启用时读取两类模型目录,并检查一个实体模型是否存在:
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.1、1.21.4、1.21.8、1.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) | String 或 null | 读取方块自身模型 ID |
getCarrierItemModelId(ItemStack itemStack) | String 或 null | 读取方块载体物品模型 ID |
SetModelResult 包含 SUCCESS、INVALID_MODEL_ID、INVALID_LOCATION、NOT_CARRIER_BLOCK、COMMAND_FAILED。AnimationResult 包含 SUCCESS、INVALID_LOCATION、INVALID_ANIMATION、NOT_CARRIER_BLOCK。
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) | ItemStack 或 null | ID 无效时返回 null;数量最小为 1 |
getCarrierItemModelId(ItemStack itemStack) | String 或 null | 非 ChaEngine 物品返回 null |
setCarrierItemScale(ItemStack itemStack, double multiplier) | boolean | 写入单件物品缩放倍率;只接受大于 0 的有限数 |
getCarrierItemScale(ItemStack itemStack) | Double 或 null | 读取单件物品倍率;未设置或数据无效时返回 null |
clearCarrierItemScale(ItemStack itemStack) | boolean | 删除单件物品倍率并恢复默认 1.0 |
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) | SetModelResult | TEMPORARY 或 PERMANENT;永久模式写入实体自身数据 |
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 包含 SUCCESS、INVALID_ENTITY、INVALID_MODEL_ID、COMMAND_FAILED;AnimationResult 包含 SUCCESS、INVALID_ENTITY、INVALID_ANIMATION。
跨封装边界定位实体时,可使用 WorldEntityTarget.of(String dimensionId, UUID entityUuid)。维度 ID 不能为空或包含 |,UUID 不能为空;目标实体必须仍在对应世界中可解析。
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 包含 SUCCESS、INVALID_ENTITY、INVALID_COLOR、COMMAND_FAILED。颜色必须是带 # 的六位十六进制 RGB,例如 #33CCFF;重复设置会替换旧颜色。EntityTarget 找不到实体或解析到非生物实体时返回 INVALID_ENTITY。
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 包含 SUCCESS、INVALID_PLAYER、INVALID_EQUIP_ID。玩家版本要求玩家在线;实体目标版本要求目标可解析。玩家“保留”状态不写数据库,服务器重启后需由业务插件恢复。
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_PERSON、THIRD_BACK、THIRD_FRONT;“第二人称”使用 THIRD_FRONT,不要在 API 中自造第二套枚举名。
CameraRequestResult.status() 可能返回 SENT、UNSUPPORTED_CLIENT、INVALID_ARGUMENT、PRESET_NOT_FOUND、SCENE_NOT_FOUND、PAYLOAD_TOO_LARGE、SEND_FAILED、UNKNOWN_SESSION。成功创建会话时用 sessionId() 保存 UUID,后续只停止自己的会话。
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() 为 CONTROL 或 SCENE;status() 为 STARTED、FINISHED、ABORTED、INTERRUPTED、REJECTED 或 UNSUPPORTED。
正常结束、管理员停止、玩家跳过和异常中止是不同状态。业务插件应分别处理 FINISHED、ABORTED 和 INTERRUPTED,并在玩家退出或自身停用时清理保存的会话 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) | KeyBindConfig 或 null | 返回配置快照 |
completeKeyBindIds(String current) | List<String> | 返回按前缀过滤并排序的 ID |
注册要求 Plugin、id、name、defaultKey 有效。布尔返回值表示服务端注册表是否改变,不表示每个客户端都已显示按键。客户端上报已注册 ID 后,服务端先触发 KeyBindPressEvent 或 KeyBindReleaseEvent,再执行对应动作;两个事件都通过 getPlayer() 和 getId() 读取数据,且不可取消。
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 字符。
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。
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 事件,不可取消。
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));
}
}调用检查表
- 在插件依赖中声明 ChaEngine,并等待它启用后再注册动态能力。
- 只在服务端主线程读写 Bukkit 对象或调用发送类 API。
- 显式处理枚举和布尔返回值,不把“已发送”解释为“已渲染”。
- 玩家退出、页面关闭或业务对象销毁时,停止粒子、相机会话并清理临时预览。
- 服务端重载后重新核对业务保存的模型、时装与按键 ID 是否仍存在。
猹件开发组