Skip to content
On this page

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 后,应重新调用该方法取得新目录,不要长期保存旧结果。

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

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.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。

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)ItemStack 或 nullID 无效时返回 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
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)SetModelResultTEMPORARY 或 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 不能为空;目标实体必须仍在对应世界中可解析。

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 包含 SUCCESS、INVALID_ENTITY、INVALID_COLOR、COMMAND_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 包含 SUCCESS、INVALID_PLAYER、INVALID_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_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,后续只停止自己的会话。

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() 为 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() 读取数据,且不可取消。

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

动作与攻击判定 ​

通用人形动作工作室可以为攻击片段制作多个判定箱。第三方插件通过以下 API 提交攻击输入、停止动作,或接管箱体命中效果。

入口类为 com.github.ginirohikocha.engine.api.action.ChaEngineActionAPI;所有方法只能在服务器主线程调用。

方法返回值用途
getControllers()Set<String>内置规则与当前自定义工程规则名称的只读快照
requestAttack(Player)boolean按玩家当前主手物品提交一次攻击输入;需要客户端准备完成,且已设置自定义模型、穿戴时装或开启原版玩家动作
stop(Player)void清除当前连段、缓冲输入和攻击判定,并同步停止动作
getActiveAttack(Player)ActionAttack 或 null当前攻击段的只读信息
runEffect(Player, Runnable)void执行插件自己的攻击效果;用于事件外或延迟的玩家伤害,避免被识别为重复原版左键伤害

requestAttack 返回 true 表示已提交到有效规则。输入仍可能处于衔接缓冲区、早于允许时刻,或被事件取消,不能将返回值当作已命中目标。接受新一段时会触发 ActionAttackEvent,包括缓冲输入随后触发的攻击段。

已发布通用默认规则时,主手物品先匹配武器专用规则,未匹配再使用默认攻击;空手也可使用默认挥拳。动画所在的分类不参与规则选择。分类整理、普通动画播放与攻击输入是不同操作,第三方插件需要实际攻击时仍调用 requestAttack。

标准十部位玩家模型无需保存绑定就能使用默认动作;已有自定义绑定优先。模型实际骨骼是否满足自动适配条件由客户端加载模型后判断,提交成功不代表模型一定能显示该动作。内置规则不开启判定箱攻击,制作自定义判定箱仍需发布工程。

事件位于 com.github.ginirohikocha.engine.api.action.event:

事件可读取的数据取消或接管
ActionAttackEventgetAttack()setCancelled(true) 阻止这一段动画及判定箱启动
ActionHitEventgetAttack()、getHitbox()、getTargets()取消或 setHandled(true) 均跳过本批目标的默认普攻;exclude(target) 仅跳过某一个目标

ActionAttack 提供 getPlayer()、唯一段 ID getId()、规则名 getController()、片段名 getClip()、从 1 开始的连段序号 getStep() 和 isHitboxCombat()。没有启用判定箱攻击时,起手事件只控制动作,不会撤销已经发生的原版伤害。

ActionHitbox 提供 getName()、片段内采样秒数 getTime()、世界坐标中心 getCenter()、完整边长 getSize()、八个世界坐标角点 getCorners()。几何返回值均为副本;目标列表只读,修改这些数据不会改写动作工程。一次事件仅包含该箱体在本次采样新增命中的目标;同一目标在这一攻击段后续的箱体或帧中不会重复触发事件。

下面是完整的监听示例:潜行时禁止判定箱攻击;其他情况下接管“横斩范围”,给目标造成 6 点伤害。damage 仍经过 Bukkit 伤害事件,其他保护插件可以取消它。

java
package example;

import com.github.ginirohikocha.engine.api.action.event.ActionAttackEvent;
import com.github.ginirohikocha.engine.api.action.event.ActionHitEvent;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;

public final class ActionEffectsPlugin extends JavaPlugin implements Listener {
    @Override
    public void onEnable() {
        getServer().getPluginManager().registerEvents(this, this);
    }

    @EventHandler(ignoreCancelled = true)
    public void onAttack(ActionAttackEvent event) {
        if (event.getAttack().isHitboxCombat()
                && event.getAttack().getPlayer().isSneaking()) {
            event.setCancelled(true);
        }
    }

    @EventHandler(ignoreCancelled = true)
    public void onHit(ActionHitEvent event) {
        if (event.isHandled() || !event.getHitbox().getName().equals("横斩范围")) {
            return;
        }
        event.setHandled(true);
        Player attacker = event.getAttack().getPlayer();
        for (LivingEntity target : event.getTargets()) {
            if (!event.isExcluded(target)) {
                target.damage(6.0, attacker);
            }
        }
    }
}

配套 plugin.yml:

yaml
name: ActionEffects
version: 1.0.0
main: example.ActionEffectsPlugin
api-version: '1.16'
depend: [ChaEngine]

不取消、未标记已处理且未排除的目标会执行一次原生近战攻击,包括攻击冷却、附魔和其他插件的伤害监听。取消或接管的目标也会计入本段已处理集合。事件内可以直接处理自定义伤害;需要延后处理时先标记 setHandled(true),保存攻击 ID,在服务器主线程核对玩家与目标仍有效,再用 ChaEngineActionAPI.runEffect(attacker, () -> target.damage(6.0, attacker)) 执行。该方法只放行 ChaEngine 的重复左键防护,不跳过其他插件的伤害事件。

直接生成模型残影 ​

ChaEngineActionAPI.spawnAfterimage 可在技能执行时记录玩家当前模型的姿态和位置,不需要指定动画或在工作室添加特效。一次调用即可生成整批残影,不要再用服务器定时任务逐道调用。

所有调用必须在服务器主线程;插件依赖仍为 depend: [ChaEngine]。例如在已有技能处理方法中调用下面的 cast:

java
import com.github.ginirohikocha.engine.api.action.ActionAfterimageResult;
import com.github.ginirohikocha.engine.api.action.ChaEngineActionAPI;
import org.bukkit.entity.Player;

public final class AfterimageSkill {
    public static ActionAfterimageResult cast(Player player) {
        // 8 道冷青色残影,间隔 0.08 秒;每道在生成后 0.6 秒内淡出。
        return ChaEngineActionAPI.spawnAfterimage(player, "#80BDEFFF", 0.6, 8, 0.08);
    }
}
方法含义
spawnAfterimage(player, color, lifetimeSeconds)生成 1 道残影
spawnAfterimage(player, color, lifetimeSeconds, count)指定数量,间隔默认 0.08 秒
spawnAfterimage(player, color, lifetimeSeconds, count, intervalSeconds)一次提交完整批次

color 接受 #RRGGBB(默认 40% 不透明度)或 #AARRGGBB(前两位指定不透明度,不能为 00)。存留时间为 0.03~10 秒,数量为 1~32,间隔为 0.02~1 秒。非有限数字、越界值和无效颜色会抛出 IllegalArgumentException;异步调用抛出 IllegalStateException,请先调度回服务器主线程。

返回 ActionAfterimageResult:

结果含义
SUBMITTED至少向一个附近就绪客户端提交成功,不表示已经绘制
PLAYER_OFFLINE玩家为空或已离线
TARGET_NOT_ACTIVE玩家没有已应用的实体或时装模型
NO_READY_VIEWERS没有可接收并成功提交的附近客户端,可能未同步、不可见或发送失败

优先选择当前实体模型,没有时选择当前全身时装。没有显式全身绑定时,按当前首个时装提交,客户端仍须将它识别为隐藏原人物的全身模型才会捕获。此能力不要求人形标准骨骼;记录的是实际绘制姿态,不打断主动动画和独立分组,也不影响伤害、移动或视角。支持范围与原有模型残影相同:模型本体,不包括原版皮肤、局部时装、第一人称手部或手持物。观察者须位于同世界 128 格内,且已完成同步、能够看到目标玩家。

每个客户端从首次成功捕获开始,按同一批次的固定时间点采样;只传时间长度,不依赖服务器和客户端系统时间一致。错过采样点时跳过,不会补出一串相同姿态,也不会把误差累积到下一次。不同客户端的网络延迟和帧率仍可能导致开始时刻、实际数量不同。目标须在画面中实际绘制;3 秒内未开始捕获的请求会失效。每个模型最多同时保留 8 批请求,复杂模型或大量同时采样会受性能限制。

重载动作库、资源变化、断线及切换世界会清理残影;模型切换后不补采样旧模型。残影是一次性显示效果,不保存到动画库。请同时更新并重启服务端和客户端。无需写插件时可使用残影指令。

分组动画播放 ​

所有主动播放入口均读取动画的“允许移动”和“允许转动视角”两个独立开关。有限动画结束或停止后移除该动画的限制,并行组仍按各自设置生效;此 API 本身不提供技能位移。持续效果的条件只控制自动播放,显式 API 调用可独立开始或停止该组;持续效果管理说明加入、移出与起始动画。

ChaEngineActionAPI 可单独播放人物动作、光环或悬浮物。一个组同时执行一个片段,切换或停止该组不重置其他组;character 是固定人物组,独立组 ID 来自动作工程。所有调用都在服务器主线程。

职业技能只需让当前人物做一个动作时,可以直接调用:

java
ActionPlaybackResult result = ChaEngineActionAPI.playAnimation(player, "后翻滚");
if (result != ActionPlaybackResult.SUBMITTED) {
    player.sendMessage("动画未提交:" + result.name());
}
// 需要中途结束或停止循环时,再调用:
// ChaEngineActionAPI.stopAnimation(player);

动画名来自 getAnimations(),无需添加进人物分组。简化方法自动选择当前实体模型;没有实体模型时优先已配置的整身时装,否则选择当前首个时装。没有实体 / 时装模型返回 TARGET_NOT_ACTIVE,不会替玩家穿戴模型;只需要原版皮肤默认动作时使用服务端分段四肢开关。多个时装需要精确控制时,使用下面显式目标的方法。

模型目标使用 ActionModelTarget,显式区分 Catalog.ENTITY 和 Catalog.EQUIP,模型 ID 填服务端配置中的 ID。玩家必须已设置对应实体模型或穿戴对应时装。

分组设为“所有同名骨骼模型”后,getGroups(target) 也会返回适用于该目标的通用组。播放与停止仍填写玩家当前使用的实际模型 ID,例如同一法阵组可分别用于魔法少女和魔法少年;停止其中一个目标不会停止其他模型。通用组缺失骨骼时跳过对应轨道,其他匹配骨骼继续播放,不会仅因某个模型没有法阵而报 MISSING_MAPPING。

方法结果
getAnimations()当前动画 ID 的只读集合
getGroups(target)目标的独立组 ID,加固定 character 组
playAnimation(player, animation)在玩家当前实体 / 时装上播放人物动作,返回提交结果
stopAnimation(player)停止当前模型上的主动人物动画,不停止装饰组
playAnimation(player, target, group, animation)提交对应组的播放,返回 ActionPlaybackResult
stopAnimation(player, target, group)停止对应组,返回结果;独立组在模型重新出现前不会自动重新启动
getPlayingAnimation(player, target, group)服务器当前片段 ID;已停止或单次结束返回空字符串
getPlaybackResult(player, target, group)查询该次播放的提交、客户端应用或失败状态

工作室导入并保存的片段使用服务器动画库 ID。旧目录迁移的标准文件仍保留相对文件名加 :: 和原始动画名,例如 剑类职业普攻.animation.json::剑客_attack1;新增动画请通过工作室导入。先用 getAnimations() 获取实际 ID,不要由分类名猜测。独立组只能播放其 clips 列表中的动画;人物组可直接播放库中的动画。每段动画选择的骨架形式会同时用于工作室与实际人物播放。

SUBMITTED 表示服务器接受并提交,CLIENT_APPLIED 表示玩家自己的客户端已经在渲染该角色时成功应用姿态;不代表所有观察者都显示完成。角色暂未绘制时可能一直处于提交状态。NOT_READY、TARGET_NOT_ACTIVE、UNKNOWN_GROUP、UNKNOWN_ANIMATION、UNSUPPORTED_ANIMATION、NOT_IN_GROUP 分别表示尚未就绪、模型未启用、组或动画不存在、数据不支持、动画不属于该组。MISSING_MAPPING、GROUP_CONFLICT、INVALID_POSE 是客户端应用失败原因,STOPPED 表示没有当前播放。

对于通用组和直接播放的人物动画,CLIENT_APPLIED 也包含缺失骨骼已按规则跳过的情况,并不保证出现可见变化。重名、范围冲突和无效姿态仍会返回相应失败状态。简化停止方法返回 STOPPED 表示停止请求已提交;它不是客户端已显示停止的确认。

这些播放方法只改变显示,不触发攻击判定或伤害。 需要职业普攻时,发布武器与判定规则并调用 requestAttack;取消命中或接管伤害仍使用上面的攻击事件。

下面插件使用示例工程的光环组。输入 /aurapreview 重播光环,/aurapreview stop 停止光环,/aurapreview status 查询状态;人物动作与悬浮物继续运行。

java
package example;

import com.github.ginirohikocha.engine.api.action.ActionModelTarget;
import com.github.ginirohikocha.engine.api.action.ActionPlaybackResult;
import com.github.ginirohikocha.engine.api.action.ChaEngineActionAPI;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;

public final class AuraPreviewPlugin extends JavaPlugin {
    private final ActionModelTarget target =
        new ActionModelTarget(ActionModelTarget.Catalog.EQUIP, "magical_girl");

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (!(sender instanceof Player)) {
            sender.sendMessage("请在游戏中执行。");
            return true;
        }
        Player player = (Player) sender;
        String operation = args.length == 0 ? "play" : args[0];
        ActionPlaybackResult result;
        if (operation.equalsIgnoreCase("stop")) {
            result = ChaEngineActionAPI.stopAnimation(player, target, "aura");
        } else if (operation.equalsIgnoreCase("status")) {
            result = ChaEngineActionAPI.getPlaybackResult(player, target, "aura");
        } else {
            result = ChaEngineActionAPI.playAnimation(player, target, "aura", "aura_spin");
        }
        player.sendMessage("光环播放状态:" + result.name());
        return true;
    }
}

配套完整 plugin.yml:

yaml
name: AuraPreview
version: 1.0.0
main: example.AuraPreviewPlugin
api-version: '1.16'
depend: [ChaEngine]
commands:
  aurapreview:
    description: 播放、停止或查询示例光环动画
    usage: /aurapreview [play|stop|status]
    permission: aurapreview.use
permissions:
  aurapreview.use:
    default: op

调用检查表 ​

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