API 与集成
TemporaryAttributeService
TemporaryAttributeService 用于给玩家附加带过期时间的临时属性。典型场景:Buff/Debuff、技能增益、药水效果等需要"持续一段时间后自动消失"的属性变化。
两种模式
| 模式 | 说明 |
|---|---|
ADD | 在现有属性值上叠加。同一个 effectId 重复调用会累加数值并刷新过期时间 |
SET | 直接覆盖属性值。后写入的覆盖先写入的,适合"强制设定某个属性为固定值"的场景 |
Java API
// 通过 AttributeService 获取
TemporaryAttributeService tempService = attributeService.temporaryAttributeService();
// 叠加临时属性:给玩家加 50 点物理攻击,持续 200 tick(10 秒)
tempService.add(player, "buff_atk", "physical_attack", 50.0, 200);
// 覆盖临时属性:把玩家的物理防御强制设为 100,持续 100 tick(5 秒)
tempService.set(player, "override_def", "physical_defense", 100.0, 100);
// 移除临时属性
tempService.remove(player, "buff_atk");| 方法 | 参数 | 说明 |
|---|---|---|
add() | player, effectId, attributeId, value, durationTicks | 叠加模式。同 effectId + 同 attributeId 会累加 value |
set() | player, effectId, attributeId, value, durationTicks | 覆盖模式。直接设定属性值 |
remove() | player, effectId | 立即移除指定 effectId 的临时属性 |
effectId 是临时属性的唯一标识,用来区分不同来源的效果。比如一个 Buff 技能用 "skill_buff_atk",一个药水用 "potion_str",互不干扰。
在动作系统中使用
配套的 attribute_add / attribute_set / attribute_remove 动作让你可以在 YAML 配置中直接操作临时属性:
actions:
# 叠加 50 点物理攻击,持续 200 tick
- "attribute_add effect_id=buff_atk attribute=physical_attack value=50 duration_ticks=200"
# 覆盖物理防御为 100,持续 100 tick
- "attribute_set effect_id=override_def attribute=physical_defense value=100 duration_ticks=100"
# 移除指定效果
- "attribute_remove effect_id=buff_atk"| 参数 | 必填 | 说明 |
|---|---|---|
mode | 是 | add、set 或 remove |
effect_id | 是 | 效果唯一标识 |
attribute | add/set 时必填 | 属性 ID |
value | add/set 时必填 | 属性值 |
duration | add/set 时必填 | 持续时间(tick) |
内部机制
- 过期检查由独立的守护线程执行,间隔 250ms,不占用主线程
- 临时属性变化后会立即清除玩家的战斗快照缓存并触发属性重新同步
ADD模式的临时属性在属性快照收集时通过mergeValues叠加到装备属性之上SET模式的临时属性通过overlayValues直接覆盖对应属性值- 临时属性的签名会参与属性快照的签名计算,确保快照在临时属性变化时正确失效
- 插件关闭时(
shutdown())会自动停止清理线程并清空所有临时属性数据
注意
临时属性数据只存在内存中,不会持久化。服务器重启后所有临时属性都会丢失。如果需要跨重启的持久 Buff,应该用 PDC 属性或其他持久化方案。
EmakiAttributeBridge
EmakiAttributeBridge 是定义在 CoreLib 中的桥接接口,让其他模块(比如 EmakiSkills)可以查询玩家的属性值和资源状态。EmakiAttribute 通过 ServiceBackedEmakiAttributeBridge 实现这个接口,并注册到 Bukkit 的 ServicesManager。
public interface EmakiAttributeBridge {
// 桥接是否可用(EmakiAttribute 是否已加载)
boolean available();
// 读取资源当前值,不存在返回 -1
double readResourceCurrent(Player player, String resourceId);
// 读取资源最大值,不存在返回 -1
double readResourceMax(Player player, String resourceId);
// 消耗资源,余额不足返回 false
boolean consumeResource(Player player, String resourceId, double amount);
// 读取属性值,不存在返回 0
double readAttributeValue(Player player, String attributeId);
}获取 Bridge 实例
RegisteredServiceProvider<EmakiAttributeBridge> provider =
Bukkit.getServicesManager().getRegistration(EmakiAttributeBridge.class);
if (provider != null) {
EmakiAttributeBridge bridge = provider.getProvider();
if (bridge.available()) {
double health = bridge.readResourceCurrent(player, "health");
}
}注意先检查 available()——如果 EmakiAttribute 还没加载完成或者被禁用了,调用其他方法可能拿不到正确的数据。
PdcAttributeApi
PdcAttributeApi 用于在物品上读写 PDC 属性数据。如果你的插件需要给物品添加自定义属性(比如锻造系统给武器加攻击力),就用这个接口。
使用前需要先注册一个"属性源 ID",这个 ID 用来标识属性数据的来源,方便后续按来源读取或清除。
public interface PdcAttributeApi {
// 注册属性源 ID,返回是否新注册(已存在返回 false)
boolean registerSource(String sourceId);
// 注销属性源 ID
void unregisterSource(String sourceId);
// 检查源 ID 是否已注册
boolean isRegisteredSource(String sourceId);
// 获取所有已注册的源 ID
Set<String> registeredSources();
// 写入属性数据到物品
boolean write(ItemStack itemStack, PdcAttributePayload payload);
// 便捷写入方法
boolean write(ItemStack itemStack, String sourceId,
Map<String, Double> attributes, Map<String, String> meta);
// 读取指定源的属性数据
PdcAttributePayload read(ItemStack itemStack, String sourceId);
// 读取物品上所有来源的属性数据
Map<String, PdcAttributePayload> readAll(ItemStack itemStack);
// 清除指定源的属性数据
boolean clear(ItemStack itemStack, String sourceId);
// 清除物品上所有属性数据
void clearAll(ItemStack itemStack);
}PdcAttributeApi 使用示例
// 获取 API
PdcAttributeApi api = Bukkit.getServicesManager()
.getRegistration(PdcAttributeApi.class).getProvider();
// 注册你的插件作为属性源
api.registerSource("my_plugin");
// 给物品写入属性
Map<String, Double> attributes = Map.of(
"physical_attack", 50.0,
"physical_crit_rate", 10.0
);
Map<String, String> meta = Map.of(
"tier", "legendary"
);
api.write(itemStack, "my_plugin", attributes, meta);
// 读取属性
PdcAttributePayload payload = api.read(itemStack, "my_plugin");
// 清除属性
api.clear(itemStack, "my_plugin");meta 参数可以存放任意键值对,比如物品品质、强化等级等。这些 meta 数据会和属性一起存在 PDC 中,条件系统可以读取它们做校验。
AttributeContributionProvider
如果你的插件需要在属性快照收集阶段注入额外的属性(比如 Buff 系统给玩家临时加攻击力),可以实现 AttributeContributionProvider 接口。
public interface AttributeContributionProvider {
// 提供者唯一 ID
String id();
// 优先级,数值越大越先处理
int priority();
// 收集指定实体的属性贡献
Collection<AttributeContribution> collect(LivingEntity entity);
}AttributeContribution 是一个简单的记录类:
public record AttributeContribution(
String attributeId, // 属性 ID
double value, // 贡献值
String sourceId // 来源标识(用于调试和追踪)
) {}内置 Provider
插件自带两个 Provider:
mythic_mob_attributes(priority=250):从 MythicMobs 怪物配置中读取属性mmoitems_attribute_mapping(priority=220):从 MMOItems 物品中映射属性
你自定义的 Provider 可以根据需要设置优先级。优先级高的先执行,但最终结果是所有 Provider 的贡献累加,所以优先级主要影响的是处理顺序而非覆盖关系。
MythicMobs 集成
emaki_damage 机制
在 MythicMobs 技能中触发 EmakiAttribute 的伤害计算。这是法术伤害最常用的触发方式。
# MythicMobs 技能配置示例
Skills:
- emaki_damage{damage=20;damage_type=spell;allow_critical=true} @target| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
damage / base | double | 技能 power | 基础伤害值。不填的话用技能的 power 值 |
damage_type | String | 攻击者覆盖或默认类型 | 伤害类型 ID |
allow_critical / critical | boolean | true | 是否允许暴击 |
allow_target_dodge / target_dodge / allow_dodge / dodge | boolean | false | 是否允许目标闪避 |
calculate_target_defense / target_defense / calculate_defense / defense | boolean | true | 是否计算目标防御 |
trigger_mythic_on_damaged / trigger_on_damaged / mythic_on_damaged | boolean | false | 是否触发 MythicMobs 的 onDamaged 事件 |
机制别名:emakiattribute_damage、attribute_damage
闪避默认关闭
allow_target_dodge 默认是 false——MythicMobs 技能默认不允许目标闪避。这是因为大多数技能(尤其是 Boss 技能)不应该被闪避。如果你希望某个技能可以被闪避,需要显式设置 allow_target_dodge=true。
emaki_attribute / attribute_resource 条件
在 MythicMobs 条件中检查实体的属性值或资源状态。
# 检查属性值:物理攻击 >= 100
Conditions:
- emaki_attribute{attribute=physical_attack;operator=>=;value=100} true
# 检查资源:当前法力 >= 50
Conditions:
- attribute_resource{resource=mana;field=current;operator=>=;value=50} true
# 范围检查:闪避率在 10 到 50 之间
Conditions:
- emaki_attribute{attribute=dodge_chance;operator=between;value=10;value_2=50} true| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
attribute / id | String | — | 属性 ID |
resource | String | "" | 资源 ID。填了这个就进入资源模式,检查资源而非属性 |
field | String | 属性模式 value,资源模式 current_value | 要检查的字段 |
operator / compare | String | >= | 比较运算符 |
value / min | double | 0 | 比较值 |
value_2 / max | double | 同 value | 范围上限(仅 between 运算符用) |
支持的运算符:>、>=、<、<=、!=(<>、ne)、between
资源字段选项:current(current_value、value)、max(current_max)、default(default_max)、bonus(bonus_max)、percent
条件别名:emakiattribute_attribute、attribute_value、attribute_resource
MythicMobs 怪物属性配置
可以直接在 MythicMobs 怪物配置中定义 Emaki 属性,不需要额外的配置文件:
# MythicMobs 怪物配置
ExampleMob:
Type: ZOMBIE
Display: '&c精英僵尸'
Health: 100
EmakiAttribute:
- "physical_attack: 25"
- "physical_defense: 10"
- "physical_crit_rate: 15"
- "dodge_chance: 5"怪物属性格式
每个条目格式为 属性名: 值,属性名可以用 id 或 display_name。值支持数学表达式(通过 exp4j 解析),比如你可以写 "physical_attack: 10 + 5 * 2" 这样的表达式。
MMOItems 集成
当服务器装了 MMOItems 时,EmakiAttribute 会自动启用桥接,不需要额外配置:
- 通过 MMOItems 公开 API 读取物品的 Stat 值,根据属性定义中的
mmoitems_stat_id字段映射到 Emaki 属性 - 自动拦截 MMOItems 的伤害事件(
SpecialWeaponAttackEvent),转由 Emaki 伤害系统处理,避免两套伤害系统冲突 - 支持投射物快照机制
MMOItems 桥接改进(3.3.0)
从 3.3.0 开始,MMOItems 桥接改为直接调用 MMOItems 的公开 API,不再使用反射。这意味着 MMOItems 版本更新时桥接不容易失效了。如果你之前遇到过 MMOItems 更新后桥接报错的问题,升级到 3.3.0 应该能解决。
EmakiAttributeDamageEvent
自定义 Bukkit 事件,在伤害计算完成后、实际扣血前触发。你可以监听这个事件来修改最终伤害、取消伤害,或者做伤害统计之类的事情。
@EventHandler
public void onEmakiDamage(EmakiAttributeDamageEvent event) {
LivingEntity attacker = event.getAttacker();
LivingEntity target = event.getTarget();
String damageType = event.getDamageTypeId();
double baseDamage = event.getBaseDamage();
double finalDamage = event.getFinalDamage();
boolean critical = event.isCritical();
// 修改最终伤害(比如某个 Buff 让伤害翻 1.5 倍)
event.setFinalDamage(finalDamage * 1.5);
// 或者取消这次伤害
// event.setCancelled(true);
}可读取的字段
| 方法 | 返回类型 | 说明 |
|---|---|---|
getAttacker() | LivingEntity | 攻击者 |
getTarget() | LivingEntity | 目标 |
getProjectile() | Projectile | 投射物(近战时为 null) |
getDamageTypeId() | String | 伤害类型 ID |
getBaseDamage() | double | 基础伤害值(进入阶段计算前的值) |
getFinalDamage() | double | 最终伤害值(所有阶段计算后的值) |
isCritical() | boolean | 是否暴击 |
getRoll() | double | 暴击随机数(0–100),可以用来判断"差多少就暴击了" |
getCause() | DamageCause | Bukkit 伤害原因 |
getContext() | Map | 上下文变量(不可变副本) |
getVariables() | DamageContextVariables | 完整变量对象 |
getDamageContext() | DamageContext | 完整伤害上下文 |
getDamageResult() | DamageResult | 完整伤害结果(含各阶段输出值) |
可修改的字段
| 方法 | 说明 |
|---|---|
setFinalDamage(double) | 修改最终伤害值 |
setCancelled(boolean) | 取消这次伤害 |
CombatDebugService
战斗调试服务,通过 /ea debug 命令控制。开启后,每次伤害计算都会在控制台输出详细日志,方便排查数值问题。
日志内容包括:
- 伤害上下文(攻击者、目标、投射物、伤害原因、伤害类型)
- 攻击者和目标的属性快照
- 各阶段的计算值
- 暴击判定结果和随机数
- 最终伤害值
调试日志格式
[CombatDebug][PHASE] message日志以 [CombatDebug] 前缀标识,PHASE 表示当前计算阶段(如 TRACE、STAGE 等)。在控制台中搜索 [CombatDebug] 就能快速定位相关日志。