Skip to content

fix(Economics): Core 修正 NPC 伤害结算漏算扣血 + NPC 配置语义修复#1142

Merged
Controllerdestiny merged 8 commits intoUnrealMultiple:masterfrom
Arispex:master
Apr 11, 2026
Merged

fix(Economics): Core 修正 NPC 伤害结算漏算扣血 + NPC 配置语义修复#1142
Controllerdestiny merged 8 commits intoUnrealMultiple:masterfrom
Arispex:master

Conversation

@Arispex
Copy link
Copy Markdown
Contributor

@Arispex Arispex commented Apr 11, 2026

Summary

Economics.Core v2.1.0.0

  • 修复击杀 Boss 时按伤害发放的货币常少于实际应得的问题。此前用武器的原始伤害(ServerApi.Hooks.NpcStrike.args.Damage)作为结算依据,忽略了防御、Ichor、暴击以及最后一击触发时 args.Npc.life 已被扣到 0/负数等多个环节,导致累计伤害与 NPC 实际扣血不一致。现在改走 GetDataHandlers.NPCStrike(攻前时机),用 Main.CalculateDamageNPCsTake + 防御 + Ichor + 暴击换算出真实掉血量,并以攻前 npc.life 作为每次攻击的裁剪上界
  • 修复中毒 / 灼烧等持续伤害(DoT debuff)以及岩浆 / 陷阱等环境伤害没被 NpcStrike 包含进累计伤害,导致按输出瓜分的奖励池玩家拿不到满额的问题。OnKillNpc 现在会把累计伤害按 lifeMax 做比例校正,solo 击杀能稳定拿到完整奖励,多人击杀按各自对 Boss 的实际伤害占比瓜分

Economics.NPC v2.1.0.0

  • 破坏性变更转换率更改转换率 字段语义修正为"默认击杀奖励的加成系数",例如填 `1.5` 表示拿到默认奖励的 150%。此前实现错误地把它当作奖励池总量使用,导致设置 `1.5` 的 Boss 玩家实际只能拿到 1-5 块钱。升级后请根据新语义重新评估配置值
  • 修复同时配置 `转换率更改` 和 `额外奖励列表` 时,额外奖励的系统提示不触发的问题,两段配置现在可以对同一只怪共存生效
  • 修复 `转换率更改` 分支下货币飘字不受全局 `悬浮文本.启用` 开关和 `指定ID` 过滤控制的问题,行为与默认击杀结算保持一致
  • 当玩家不满足 `转换率更改` 里配置的进度条件时,改为放行默认击杀奖励(以及 `额外奖励列表`),而不是一分钱也拿不到

Test plan

Economics.Core

  • 纯物理武器 solo 击杀配置了 10w 按输出瓜分奖励的 Boss,实际到账恰好 10w
  • 带火焰 / 中毒等 DoT 武器 solo 击杀同一只 Boss,反复测试 5-10 次均稳定拿到 10w(验证 DoT / 环境伤害不再漏账)
  • 用伤害远超 Boss 剩余血量的武器秒杀低血怪,到账不会超过 `lifeMax × 转换率`(验证 v2.0.0.13 溢出裁剪行为保留)
  • 多人击杀同一只 Boss,各玩家按伤害贡献的相对占比瓜分 10w,总发放 = 10w

Economics.NPC

  • 只配 `转换率更改: { "4": { "转换率": 1.5, "进度条件": [] } }`,solo 击杀克眼,实际到账 = 默认击杀奖励 × 1.5
  • 同时配 `转换率更改` 和 `额外奖励列表`(用户报告的原始配置),进度达标时两段配置都生效,既拿到按加成系数计算的默认奖励,也能看到 `你因击杀xxx,获得额外奖励yyy个` 的系统提示
  • 同上配置,进度条件设成明显达不到的(如 `["月球领主"]` 在前期档),fallback 到 Core 默认结算,仍然能拿到 `额外奖励列表` 的奖励,不再被吞
  • `转换率更改` 命中时,关闭某种货币的 `悬浮文本.启用` 后,该货币不飘字但仍然入账;设置该货币的 `指定ID` 为 `[17]` 后,击杀克眼(ID=4)不再发放该货币

Summary by Sourcery

更新 Economics.Core、Economics.NPC 和 Economics.Deal,以提升基于 NPC 伤害的奖励精确度,优化 NPC 货币转换语义,并为交易系统新增物品搜索功能。

New Features:

  • 新增 /deal search 指令,用于按名称搜索已挂单的交易物品。

Bug Fixes:

  • 修正 NPC 伤害追踪逻辑,使用攻击前生命值与实际计算伤害,使 Boss 击杀奖励与配置数值保持一致。
  • 在按比例分配奖励时,将追踪伤害按 NPC 最大生命值进行缩放,从而纳入减益效果与环境伤害。
  • 修复 NPC 货币转换逻辑,使配置的转换倍率作为默认击杀奖励的乘数,而不是直接定义总奖励池。
  • 确保 NPC 额外奖励列表可以与转换倍率改动共存,使两种效果可同时作用于同一个 NPC。
  • 使 NPC 转换倍率分支遵循全局战斗文字开关与 ID 过滤设置,与默认击杀奖励行为保持一致。
  • 允许在玩家未满足配置的进度条件时,仍然发放默认击杀奖励(以及额外奖励),而不是完全不给予奖励。

Enhancements:

  • 优化 NPC 击杀货币处理方式,为每种配置货币分别应用转换倍率,并在启用时显示可自定义的战斗消息。
  • 在 Economics.NPC 与 Economics.Deal 模块中补充署名一位额外作者。

Documentation:

  • 更新 Economics.Core、Economics.NPC 和 Economics.Deal 的 README,涵盖新行为、配置语义、指令语法,以及 v2.1.0.0 的更新日志条目。
Original summary in English

Summary by Sourcery

Update Economics.Core, Economics.NPC, and Economics.Deal to improve NPC damage-based rewards accuracy, refine NPC currency conversion semantics, and add item search to the trading system.

New Features:

  • Add a /deal search command to search listed trade items by name.

Bug Fixes:

  • Correct NPC damage tracking to use pre-attack life and calculated actual damage so boss kill rewards match configured amounts.
  • Include debuff and environmental damage in proportional reward distribution by scaling tracked damage to the NPC's maximum life.
  • Fix NPC currency conversion so the configured conversion rate acts as a multiplier on the default kill reward instead of defining the total reward pool.
  • Ensure NPC extra reward lists can coexist with conversion-rate changes so both effects apply to the same NPC.
  • Make NPC conversion-rate branch respect global combat text enable switches and ID filters, matching default kill reward behavior.
  • Allow default kill rewards (and extra rewards) to be granted when the player does not meet configured progression conditions instead of awarding nothing.

Enhancements:

  • Refine NPC kill currency handling to apply conversion rates per configured currency and show customizable combat messages when enabled.
  • Credit an additional author across Economics.NPC and Economics.Deal modules.

Documentation:

  • Update Economics.Core, Economics.NPC, and Economics.Deal READMEs with new behaviors, configuration semantics, command syntax, and changelog entries for v2.1.0.0.

@Arispex Arispex requested a review from a team as a code owner April 11, 2026 15:05
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题

给 AI Agents 的提示
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/Economics.Deal/Command.cs" line_range="99-100" />
<code_context>
+            {
+                MaxLinesPerPage = Config.Instance.PageMax,
+                NothingToDisplayString = GetString($"没有找到包含\"{keyword}\"的交易物品"),
+                HeaderFormat = GetString($"搜索\"{keyword}\"的结果 ({{0}}/{{1}}):"),
+                FooterFormat = GetString($"输入 {Commands.Specifier}deal search {keyword} {{0}} 查看更多")
+            });
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** 为了本地化和格式化的正确性,Header/FooterFormat 也应当在调用 GetString 之前避免使用字符串插值。

在 `GetString` 调用中插入 `keyword`/`Commands.Specifier` 会使查找键变成动态的,从而破坏本地化;同时还会把运行时的值和 `PaginationTools` 应该使用的 `{0}`/`{1}` 占位符混在一起。

相反,应当保持资源键是静态的,并将 `HeaderFormat`/`FooterFormat` 视为格式字符串。比如:

```csharp
HeaderFormat = string.Format(
    GetString("搜索\"{0}\"的结果 ({1}/{2}):"),
    keyword,
    "{0}",
    "{1}"),
FooterFormat = string.Format(
    GetString("输入 {0}deal search {1} {2} 查看更多"),
    Commands.Specifier,
    keyword,
    "{0}");
```

或者,让 `GetString` 返回的字符串中已经包含供 `PaginationTools` 使用的字面量 `{0}`/`{1}` 占位符,然后在本地化之后再替换 `keyword`/`specifier`。
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/Economics.Deal/Command.cs" line_range="99-100" />
<code_context>
+            {
+                MaxLinesPerPage = Config.Instance.PageMax,
+                NothingToDisplayString = GetString($"没有找到包含\"{keyword}\"的交易物品"),
+                HeaderFormat = GetString($"搜索\"{keyword}\"的结果 ({{0}}/{{1}}):"),
+                FooterFormat = GetString($"输入 {Commands.Specifier}deal search {keyword} {{0}} 查看更多")
+            });
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** Header/FooterFormat should also avoid string interpolation before GetString for localization and formatting correctness.

Interpolating `keyword`/`Commands.Specifier` inside the `GetString` call makes the lookup key dynamic, which will break localization, and also mixes runtime values with `{0}`/`{1}` placeholders that `PaginationTools` is supposed to use.

Instead, keep the resource key static and treat `HeaderFormat`/`FooterFormat` as format strings. For example:

```csharp
HeaderFormat = string.Format(
    GetString("搜索\"{0}\"的结果 ({1}/{2}):"),
    keyword,
    "{0}",
    "{1}"),
FooterFormat = string.Format(
    GetString("输入 {0}deal search {1} {2} 查看更多"),
    Commands.Specifier,
    keyword,
    "{0}");
```

Alternatively, have `GetString` return a string that already contains the literal `{0}`/`{1}` placeholders for `PaginationTools`, and only substitute `keyword`/`specifier` after localization.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +99 to +100
HeaderFormat = GetString($"搜索\"{keyword}\"的结果 ({{0}}/{{1}}):"),
FooterFormat = GetString($"输入 {Commands.Specifier}deal search {keyword} {{0}} 查看更多")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): 为了本地化和格式化的正确性,Header/FooterFormat 也应当在调用 GetString 之前避免使用字符串插值。

GetString 调用中插入 keyword/Commands.Specifier 会使查找键变成动态的,从而破坏本地化;同时还会把运行时的值和 PaginationTools 应该使用的 {0}/{1} 占位符混在一起。

相反,应当保持资源键是静态的,并将 HeaderFormat/FooterFormat 视为格式字符串。比如:

HeaderFormat = string.Format(
    GetString("搜索\"{0}\"的结果 ({1}/{2}):"),
    keyword,
    "{0}",
    "{1}"),
FooterFormat = string.Format(
    GetString("输入 {0}deal search {1} {2} 查看更多"),
    Commands.Specifier,
    keyword,
    "{0}");

或者,让 GetString 返回的字符串中已经包含供 PaginationTools 使用的字面量 {0}/{1} 占位符,然后在本地化之后再替换 keyword/specifier

Original comment in English

issue (bug_risk): Header/FooterFormat should also avoid string interpolation before GetString for localization and formatting correctness.

Interpolating keyword/Commands.Specifier inside the GetString call makes the lookup key dynamic, which will break localization, and also mixes runtime values with {0}/{1} placeholders that PaginationTools is supposed to use.

Instead, keep the resource key static and treat HeaderFormat/FooterFormat as format strings. For example:

HeaderFormat = string.Format(
    GetString("搜索\"{0}\"的结果 ({1}/{2}):"),
    keyword,
    "{0}",
    "{1}"),
FooterFormat = string.Format(
    GetString("输入 {0}deal search {1} {2} 查看更多"),
    Commands.Specifier,
    keyword,
    "{0}");

Alternatively, have GetString return a string that already contains the literal {0}/{1} placeholders for PaginationTools, and only substitute keyword/specifier after localization.

Arispex added 8 commits April 11, 2026 23:18
在 OnStrike 里将本次计入的伤害裁剪到 NPC 当前剩余血量,防止一击秒杀兔子等低血怪时,玩家按武器原始伤害(而非实际造成伤害)获得货币。
记录 OnStrike 溢出伤害裁剪修复对应的版本更新与 changelog。
NpcStrike hook 触发时 args.Npc.life 已是扣血后的值,致命一击时为 0 或负数,
直接用作上界会导致最后一击的有效伤害被整个丢弃,solo 击杀无法累计到满血,
rw = damage / lifeMax 恒小于 1,下游按输出瓜分的奖励永远拿不到满额。改为
以 "lifeMax - 已累计伤害" 作为上界,同时保留对原始伤害溢出剩余血量的裁剪。
将伤害追踪从 ServerApi.Hooks.NpcStrike 迁移到 TShock 的 GetDataHandlers.NPCStrike:
前者在 StrikeNPC 之后触发,args.Npc.life 已被扣到 ≤0,最后一击容易丢失;后者在
客户端 NpcStrike 包处理阶段触发,npc.life 仍是攻前剩余血量,可直接用作裁剪上界。
新实现走 Main.CalculateDamageNPCsTake + Ichor + 暴击的公式(参考 SEconomy 的
WorldEconomy.AddNPCDamage),把 raw 武器伤害换算成实际掉血。

针对 debuff DoT 和环境伤害直接修改 npc.life、不经过 NpcStrike 路径导致 Strike
累加器总和小于 lifeMax 的场景,OnKillNpc 在触发玩家奖励事件前按 lifeMax 对每个
玩家的累计伤害做比例校正,确保 solo 击杀能拿到 rw=1 对应的满额奖励,多人瓜分也
按各自贡献的相对占比分配。
"转换率更改" 原实现把 AllocationRatio 当成奖励池总量用(rw × AllocationRatio),
与字段命名严重脱节,导致配置 "转换率": 1.5 的 Boss 玩家只能拿到 1-5 块钱。改为
作为 Core 默认 KillNpc 奖励的加成系数(damage × ConversionRate × AllocationRatio),
且对每种 KillNpc 型货币分别结算,行为与 Core 默认路径一致,包括 ContainsID 过滤
和 CombatMsgOption.Enable 检查。

同时修复转换率分支末尾 return 吞掉后续"额外奖励列表"分支的问题,两段配置现在可以
共存同一只 NPC。转换率分支在玩家进度不达标时也改为 fallback 到 Core 默认结算,而
不是吞掉全部奖励。

版本号从 2.0.0.4 跃迁到 2.1.0.0,体现"转换率更改"字段的语义变更是 breaking change,
按旧语义填写的 AllocationRatio 值升级后行为会有明显差异。
@Controllerdestiny Controllerdestiny merged commit ec3e07d into UnrealMultiple:master Apr 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants