更新类属性(UPDATE_AFTER)
概述
UPDATE_AFTER 在实体的属性数据发生更新后触发。所谓「属性更新」,是指实体的属性面板值发生了变化——比如:
- 玩家穿上了装备
- 玩家卸下了装备
- 通过命令添加/移除了属性源
- 临时属性源过期了
典型用途:
- 将面板生命值同步到原版血量(
生命值.groovy) - 将面板移速同步到原版移速(
移速加成.groovy) - 根据面板数据更新实体外观或状态
注意: 非玩家实体也会触发此事件。
需要重写的方法
@Override
void onEnable()
/**
* @param entity 触发者
* @param handler 处理器
* @return 属性是否触发
*/
@Override
boolean onUpdateAfter(LivingEntity entity, UpdateAfterHandler handler)function onEnable(attr)
function onUpdateAfter(attr, entity, handler)处理器能力
UPDATE_AFTER 的处理器实现了以下接口:
| 接口 | 能力 |
|---|---|
AttributeTracker | getRandomValue, getAttrData, trigger |
MetadataTracker | setMeta, getMeta, hasMeta |
注意: UPDATE_AFTER 没有 DamageTracker,所以你不能在这里调用 handler.addDamage() 等方法。这个类型是用于「同步状态」而不是「制造伤害」的。
第一级:简单的状态同步
一个最简单的 UPDATE_AFTER 属性——在面板更新时发条消息:
@CompileStatic
class UpdateNotifier extends GroovyAttribute {
UpdateNotifier() {
super(AttributeTypes.UPDATE_AFTER, "update_notify", "更新通知", 1, 0.0D, -1.0D)
}
@Override
boolean onUpdateAfter(LivingEntity entity, UpdateAfterHandler handler) {
// 仅玩家
if (entity.getType() != EntityType.PLAYER) return false
// 读取面板值
double value = handler.getRandomValue(entity, "更新通知")
entity.sendMessage("§7[属性更新] 当前值:${String.format("%.2f", value)}")
return true
}
}第二级:同步到原版属性
UPDATE_AFTER 最常见的用法是把面板属性同步到 Minecraft 原版属性系统。以生命值为例:
@CompileStatic
class HealthSync extends GroovyAttribute {
HealthSync() {
super(AttributeTypes.UPDATE_AFTER, "health_sync", "生命同步", 1, 1.0D, -1.0D)
// 关闭过滤符检测(生命值通常不需要过滤符)
this.setSkipFilter(true)
// 绑定属性源事件:添加/移除属性源时也触发
this.setMeta("bind source event", true)
}
@Override
boolean onUpdateAfter(LivingEntity entity, UpdateAfterHandler handler) {
if (entity.getType() != EntityType.PLAYER) return false
var player = (Player) entity
// 必须在主线程操作原版属性
SchedulerUtils.sync {
// 获取原版最大生命值属性实例
AttributeInstance attrInstance = entity.getAttribute(BukkitAttribute.MAX_HEALTH())
// 获取面板生命值
double value = handler.getRandomValue(entity, "生命同步")
// 创建属性修饰符
AttributeModifier modifier = BukkitUtils.createAttributeModifier(
"CraneAttribute 生命同步", value
)
// 移除旧修饰符(防止叠加)然后添加新的
double lastMax = attrInstance.getValue()
attrInstance.removeModifier(modifier)
attrInstance.addModifier(modifier)
double newMax = attrInstance.getValue()
// 修正溢出:如果当前血量超过新上限,削减到上限
if (lastMax != newMax) {
if (entity.getHealth() > newMax) {
entity.setHealth(newMax)
}
// 预防假死(生命压缩模式下的特殊情况)
if (player.isHealthScaled()) {
double scale = player.getHealthScale()
if (lastMax < newMax * 1 / scale) {
entity.setHealth(newMax * 1 / scale)
}
}
}
}
return true
}
}逐行理解:
SchedulerUtils.sync { ... }— 操作原版 Bukkit API 必须在主线程执行。onUpdateAfter可能在后台线程被调用,所以用SchedulerUtils.sync调度到主线程。BukkitAttribute.MAX_HEALTH()— 获取原版「最大生命值」属性的Attribute枚举。高版本 Minecraft 中,玩家属性需要用Attribute而非字符串来获取。AttributeModifier— 原版属性修改器。我们创建它来给玩家的最大生命值增加我们面板上的数值。removeModifier+addModifier— 先移除旧的再添加新的,防止数值叠加。因为onUpdateAfter可能被多次调用(每次属性源变动都触发),如果不移除旧的,数值会一直累加。血量溢出修正 — 如果玩家原本有 100 血,现在上限降到了 80,那么当前血量也应该降到 80。
第三级:移速加成
@CompileStatic
class SpeedSync extends GroovyAttribute {
SpeedSync() {
super(AttributeTypes.UPDATE_AFTER, "speed_sync", "移速同步", 1, 0.0D, -1.0D)
this.setSkipFilter(true)
this.setMeta("bind source event", true)
}
@Override
boolean onUpdateAfter(LivingEntity entity, UpdateAfterHandler handler) {
if (entity.getType() != EntityType.PLAYER) return false
SchedulerUtils.sync {
AttributeInstance attrInstance = entity.getAttribute(BukkitAttribute.MOVEMENT_SPEED())
double value = handler.getRandomValue(entity, "移速同步")
// 移速是乘算的!不能直接加绝对值,否则会快到飞起
// 基础移速约 0.1,我们把面板值作为百分比加成
double bonusSpeed = value / 100 * attrInstance.getBaseValue()
AttributeModifier modifier = BukkitUtils.createAttributeModifier(
"CraneAttribute 移速同步", bonusSpeed
)
attrInstance.removeModifier(modifier)
attrInstance.addModifier(modifier)
}
return true
}
}关键点: 原版移速是乘算的,基础值约 0.1。如果你直接 addModifier(AttributeModifier("name", value, ADD_NUMBER)),那么即使 value=1,玩家也会快到无法控制。正确的做法是:面板值作为百分比 × 基础值 = 实际加值。
bind source event 元数据
在构造函数中调用 this.setMeta("bind source event", true) 是一个重要的设置。
不加这个设置时: onUpdateAfter 只在属性被其他方式(如切换装备)触发更新时调用。
加了这个设置后: 当一个包含此属性的属性源被添加或移除时,也会触发 onUpdateAfter。
JavaScript 完整示例
var type = "UPDATE_AFTER"
var id = "my_health"
var name = "我的生命值"
var priority = 1
var power = 1.0
var max = -1.0
function onEnable(attr) {
// 绑定属性源事件——添加/移除装备时也触发
attr.setMeta("bind source event", true)
// 跳过过滤符检测
attr.setSkipFilter(true)
}
function onUpdateAfter(attr, entity, handler) {
if (entity.getType() != org.bukkit.entity.EntityType.PLAYER) return false
var value = handler.getRandomValue(entity, "我的生命值")
entity.sendMessage("当前生命值加成: " + value.toFixed(2))
return true
}常见问题
Q: 为什么穿装备后血量没变化? 检查是否设置了 this.setMeta("bind source event", true)。没有这个设置的话,只有属性更新时才会触发更新。
Q: 为什么血量一直增加? 检查是否在添加新的 AttributeModifier 之前移除了旧的。没有移除的话,每次更新都会叠加一个修饰符。
Q: UPDATE_AFTER 和 RUNTIME_AFTER 有什么区别?
UPDATE_AFTER:事件驱动——属性变了就触发RUNTIME_AFTER:时间驱动——每隔 N tick 触发一次
简单说:需要「即时响应属性变化」用 UPDATE_AFTER;需要「周期性自动执行」用 RUNTIME_AFTER。
