[MD]
### 动作语法迁移
- **动作写法整体换新, 大部分不需要你自己动手改。** 首次启动时 CoreLib 会自动把 `plugins/` 下所有 Emaki 插件的配置文件改写成新写法, 只做一次, 之后不再重复。每个被改过的文件旁边都会留一份 `.legacy-backup` 后缀的原文件备份。万一某一行转换不成功, 插件不会拿这行去覆盖你的配置, 而是单独写到一个 `.v2-failed` 文件里、原配置保持不动, 控制台也会列出是哪个文件的哪一行。
- **属性伤害动作要你手工改名字**, 自动改写不包括它: `attributedamage` → `attribute_damage`。旧名字不再保留, 升级后还写旧名字的话这个动作不会执行。
- 属性的另外 12 个动作名字没变, 照旧写就行: `attribute_add`、`attribute_set`、`attribute_remove`、`attribute_tag_add`、`attribute_tag_remove`、`attribute_tag_clear`、`attribute_resource_add`、`attribute_resource_set`、`attribute_resource_remove`、`attribute_resource_consume`、`attribute_sync`、`attribute_refresh`。
- **但这些属性动作所在的行也不会被自动改写**, 所以行里如果写了旧的占位符, 要你自己换: `%player%` / `%player_name%` → `%caster.name%`, `%player_uuid%` → `%caster.uuid%`, `%target_name%` → `%target.name%`, 其余 `%player_*%` 与 `%target_*%` 同理换成 `%caster.*%` / `%target.*%`。
- 技能配置里写过这个动作的地方也要一起改名, 同样是 `attributedamage` → `attribute_damage`。
### JS 脚本取消
- **JS 脚本功能整体取消了, 没有自动转换的办法。** 你自己写的 `.js` 脚本会直接失效, 要改用配置文件里的动作重做同样的效果。
- 具体到属性这边, 用 JS 写的伤害钩子、用 JS 注册的属性定义与属性来源、用 JS 写的伤害类型与伤害管线全部不再支持。
- 随包的三个示例脚本一并删除: `scripts/examples/attribute_buff.js`、`scripts/examples/js_fire_mastery.js`、`scripts/mythic/mythic_js_damage.js`。
### 伤害飘字
- **新增伤害飘字**, 在目标头顶显示伤害数字, 默认开启。覆盖四种情况: 普通攻击、暴击、生命恢复、闪避, 对应 `damage_indicator.triggers` 下的 `normal`、`critical`、`heal`、`dodge` 四个开关。原版战斗本身不判定闪避, 所以闪避飘字要等目标带上 `dodge_chance` 这类闪避属性、真的闪掉一次攻击才会出现。随包的 `attributes/dodge_chance.yml` 默认值是 `0`, 想用就给怪物或玩家配上这个属性。
- 飘字文字内容在语言文件 `damage.indicator` 下改, 四个模板分别是 `normal`、`critical`、`heal`、`dodge`; 伤害值占位符是 `%damage%`, 恢复量是 `%amount%`。
- 飘字会做抛物飞行并在出场时放大、退场时缩小, 整段动画由客户端插值完成, 飘字本身不移动, 所以不会因为挪动位置产生额外开销。`damage_indicator.motion` 下可以调初速度 `speed`、下坠加速度 `gravity`、关键帧间隔 `step_ticks`, 以及抛出方向 `direction.mode`: `random` 随机、`fixed` 固定方向、`away_from_attacker` 沿攻击者到目标的方向抛出。
- `damage_indicator` 顶层写的是所有触发器的默认表现, 每个触发器可以只写要改的键, 没写的继承默认值。随包默认里暴击抛得更高更快、治疗改成垂直缓慢上浮。
- 防刷屏的两个键: `merge_window_ms` (默认 `500`) 把同一目标在这个毫秒窗口内的多次伤害累加成一个飘字, 和 `same_signature_ignores_invulnerability.window_ms` 填一样的值时, 多重射击的三支箭会显示成一个总伤害而不是三个数字; `max_per_target_per_second` (默认 `4`) 限制同一目标每秒最多几个飘字, 用来压住群伤刷屏。两个键填 `0` 都表示不限制。
- 持续伤害每 tick 都会结算, 所以 `ignored_causes` 默认屏蔽了 `FIRE_TICK`、`POISON`、`WITHER`、`DROWNING`、`FREEZE`、`SUFFOCATION`、`STARVATION` 七种原因不显示飘字。
- 谁能看到飘字取决于 CoreLib 的 `display.backend`。用发包后端时 `visible_to` 生效, `involved` 只有攻击者和受击者能看到 (默认), `all` 是附近所有玩家都能看到; 用真实体后端时飘字是真实体, 附近所有玩家都能看到, `visible_to` 不起作用。
### 盾牌格挡
- **新增盾牌格挡按属性结算**, 可以做成百分比格挡或者概率格挡。开关是 `config.yml` 的 `shield.mode`, 默认 `vanilla` 保持原版行为不变, 举盾按原版规则完全免除这次伤害, 插件算出来的攻击、暴击、穿透在举盾时都不生效。
- 想做百分比格挡就把 `shield.mode` 改成 `attribute`, 这时原版的举盾减伤被清零, 改由伤害类型里的格挡阶段来算。配套新增两个属性: `block_rate` 格挡率决定举盾时按多大概率触发格挡, `block_reduction` 格挡减伤决定触发后削掉伤害的百分比。两个属性的基线值写在 `default_profile.attributes` 下, 默认都是 `100`, 也就是必定触发且完全免伤, 和原版手感一致, 切过去不会突然变难。想只挡四成就把 `block_reduction` 调成 `40`, 想做概率格挡就调低 `block_rate`。盾牌物品上的属性词条会叠加在基线之上。
- **注意 `block_rate` 配成 `0` 的话格挡永远不会触发, 举盾会变成完全不减伤。**
- `attribute` 模式下还有正面判定: `shield.require_facing` 默认 `true`, 只有正对伤害来源才算格挡成功。原版举盾自带正面判定, 换成插件接管后这一步要自己开, 不开就变成四面八方都能挡。张角用 `shield.facing_angle_degrees` 调, 默认 `180` 等价于原版行为, 调小会要求玩家对得更准。环境伤害没有来源方向, 一律按不构成正面处理。
- **沿用旧配置文件的话, `attribute` 模式要你手工改一处。** 插件不会覆盖你已有的 `damage_types/*.yml`, 所以要自己往 `physical.yml` 和 `projectile.yml` 的 `stages` 里补一段 `id: block` 的格挡阶段。新生成的默认文件里已经有了, 照着抄一份就行。两个新属性文件 `attributes/block_rate.yml` 与 `attributes/block_reduction.yml` 是新文件, 会自动生成, 不用你管。
### 衰减曲线开始真正生效
- **`scaling_curves` 以前写了没有任何作用, 现在开始生效。** 这一段配置早就写在 `config.yml` 里, 但插件从来没去读它, 曲线列表永远是空的, 算属性时直接返回原值, 等于这个功能不存在。现在它被接进属性计算里了, **你以前写过的 `scaling_curves` 会第一次真正产生效果**。这是本次唯一会改变玩家数值的变化, 升级后请先复核一遍数值, 不然玩家属性会突然被削。默认是空的, 没配过就不受影响。
- 顺带修正了两处配置说明与实际行为不一致的地方。一是 `sqrt` 平方根衰减, 同一个 `factor` 下它实际比对数衰减更弱, 而且超出量小于 `factor` 时会放大而不是衰减, 取值要小心。二是 `factor` 在不同曲线下含义不同: 对数和平方根曲线里它是除数, 填 `0` 或负数会被抬成 `1`; `piecewise_linear` 线性折扣里它是超出部分的保留比例, 填 `0` 表示超出部分全部作废, 这是合法取值, 不会被抬成 `1`。
- `curve_type` 拼错时会回退成 `logarithmic`, 并且在配置预检里告警指出是哪个属性、写的是什么, 不再默默按错的算。
- `piecewise_linear` 就是「超出部分乘以 `factor`」这一种折扣, 不支持分多段, 配置注释里已经写清楚了。
### 伤害计算与展示
- 伤害阶段新增 `variables:`, 可以在阶段里先定义自己的变量再在 `expression` 里引用, 变量的值可以是数值、表达式或者随机数值区间。例如先定义 `soft_cap: 120` 和 `overflow: "max(0, %input% - %soft_cap%)"`, 再写 `expression: "%input% - %overflow% * 0.5"`。同名变量会覆盖内置变量; 一个随机变量在表达式里引用多次只掷一次, 但每个阶段、每次命中都会重新掷。
- `/emakiattribute dump` 在控制台的输出改了。以前签名和属性数值要靠鼠标悬停才能看到, 控制台没法悬停, 等于看不了。现在控制台改成标题加缩进逐行打印, 玩家在游戏里看到的还是原来的悬停样式。
- 配置预检的问题行现在按级别整行着色, 警告是黄色、错误是红色, 一眼能看出哪几行要处理。
- 控制台列出已加载属性时会带上序号, 方便对着数。
### BetterHUD 集成
- **新增 BetterHUD 2.0.0 软依赖**, 装了就自动接上, 没装也不影响插件运行。接上以后可以在 BetterHUD 自己的配置文件里, 把任意 Popup 或 HUD 元素绑到属性插件的战斗事件上, 不需要写代码。
- 一共注册了 11 个触发器。攻击方五个: `emaki_melee_hit` 近战命中、`emaki_ranged_hit` 远程命中、`emaki_sweep_hit` 横扫命中、`emaki_critical_hit` 暴击、`emaki_kill` 击杀。受击方三个: `emaki_damage_taken` 受到伤害、`emaki_dodge_success` 闪避成功、`emaki_shield_block` 举盾格挡。资源与点数三个: `emaki_resource_consumed` 资源被消耗、`emaki_resource_low` 资源过低、`emaki_point_allocated` 分配了属性点。
### 生命与回血修复
- **修复玩家最大生命被钉死在插件自己算的数值上, 别的来源加的血量上限全都不算数。** 影响所有装了别的插件或者用到生命提升效果的服务器。表现为: 喝了生命提升的药水血条不变长、别的插件给玩家加的最大生命没反应、原版属性映射加的血量上限被吃掉, 而且因为上限没跟着涨, 玩家血量会被顶回插件算的那个值, 怎么回血都回不上去。原因是插件只把自己这边的属性加起来当成血量天花板, 完全没算别人贴上来的加成。现在改成以服务端实际生效的最大生命为天花板, 别的来源加的血量上限会正常算进去。
- **修复外部回血被撤销**, 表现为: `/heal` 之类的指令刚回满血, 过一会儿又掉回原来的数值; 吃了金苹果、附魔金苹果没效果; 上线时血量被改回下线前的旧值。原因是插件推算玩家当前血量时以自己存的那份记录为准, 而别的插件、指令、原版饱食度回血改血量时插件观测不到, 于是下一次同步或者自然回血就把这些变化整个抹掉了。现在改成以玩家实体的实际血量为准, 外部回血不会再被撤销。
- **修复玩家死后被血量同步救活, 卡在死亡界面动不了。** 表现为: 玩家死了, 死亡画面出来但重生按钮点了没反应, 或者服务端那边玩家其实已经活了、客户端还停在死亡界面, 只能重新登录。原因是玩家从死亡到点重生这段时间里, 自然回血仍然会照常给他算血量并写回去, 写进一个正数就等于把人救活了。现在这段时间里不再写血量, 重生时该回满血还是照常回满。
### 开发者相关
- `EmakiAttributeApi` 门面重做, 原先挂在门面上的静态方法 `available()`、`readResourceCurrent`、`readResourceMax`、`consumeResource`、`readAttributeValue`、`scheduleEquipmentSync`、`applyDamage`、`registerItemContributionGate`、`isItemContributionActive` 全部移除, 改为三层访问器: `catalog()` 只读查询、`operations()` 状态变更、`extensions()` 扩展点注册与 PDC 访问。`available()` 由 `status()` 替代 (返回 `ApiStatus`)。
- 三层访问器在插件未加载时返回空实现, 不再抛异常。业务方法统一返回 `EmakiResult<T>`。
- `PdcAttributeApi` 与 `PdcAttributeApiProvider` 已移除, 改为 `EmakiAttributeApi.extensions().pdc()` 返回的 `PdcAttributeAccess`。
- 包结构调整: 事件类移入 `api.event`, 扩展点 (`AttributeContribution`、`AttributeContributionProvider`、`ItemContributionGate` 及其注册句柄) 移入 `api.extension`, 模型类 (`AttributeSnapshot`、`DamageContext`、`DamageContextVariables`、`DamageResult`、`PdcAttributePayload`、`AttributeApiValues`) 移入 `api.model`。
- 新增 `ResourceDefinitionView`, 由 `catalog().resources()` 返回。
- `catalog()` 另外提供 `attributeIds()`、`damageTypeIds()`、`itemSnapshot`、`combatSnapshot`; `operations()` 提供 `calculateDamage` 与 `resyncPlayer`。
### 升级说明
- 需要先把 EmakiCoreLib 升到 `4.7.0`。
- **升级前请备份 `plugins/EmakiAttribute/` 整个目录。**
- 大部分动作写法由插件自动改写, 但下面三处要你自己动手:
- `attributedamage` 改名成 `attribute_damage`。
- 属性动作所在行里的旧占位符要换成 `%caster.*%` / `%target.*%` 写法。
- 用 JS 脚本做的属性扩展与伤害钩子要改成用动作实现。
- **升级后先复核 `scaling_curves` 的实际效果**, 这是本次唯一会改变玩家数值的变化。以前写了不生效, 现在会生效。
- 想用按属性结算的盾牌格挡, 除了把 `shield.mode` 改成 `attribute`, 还要往已有的 `damage_types/physical.yml` 与 `projectile.yml` 里补 `id: block` 阶段, 插件不会覆盖你已有的文件。不改这个开关的话保持原版行为, 什么都不用动。
- 其余新增配置键都带默认值, 现有配置照常加载并自动补齐。玩家的属性数据、属性点、资源状态都不受影响, 不需要迁移。
- 伤害飘字默认开启。不想要就把 `damage_indicator.enabled` 改成 `false`。
- BetterHUD 是可选的, 不装照常运行。
[/MD]