
真实战斗模组 - 弓箭与十字弩装填设置修复
这个修改过的“RBMCombat.dll”强制让MCM的装填速度设置(原版、半真实、真实)公平地应用于玩家和所有NPC/AI部队。
查看大图[heading]💢 原版 RBM 的问题(我们的不满)[/heading]
在原版真实战斗模组(RBM)中,关于远程战斗节奏存在一个严重的逻辑不公平问题。模组配置菜单(MCM)提供了弓和弩装填速度的选项:真实、半真实和原版。
然而,深入查看 [b]RBMCombat.dll[/b] 中的原始代码后
我们发现了两个主要问题:
[list=1] []硬编码偏见:开发者将 MCM 配置锁定为仅影响玩家角色 [b]agent.IsPlayerControlled[/b]——如果你为了自己的舒适度更改设置,AI 会完全忽略它们。[/] [*]隐藏的数学不对称(作弊 AI):如果你分析他们的公式,AI 甚至不使用玩家真正的真实参数!
[list] []玩家在真实模式("1"): [code]0.25f * (0.85f + 0.0184f * skill)[/code][/] []AI 单位(硬编码): [code]0.25f * (1f + 0.016f * skill)[/code][/] [/list][/*] [/list]作者实际上专门为 AI 构建了一个隐藏的、不可配置的第 4 个预设。低级 AI 弓箭手被人工增强,装填速度比初始玩家还快,同时完全无视你在 MCM 菜单中设置的任何选项。
这在实践中意味着什么?如果你作为玩家觉得真实弓箭节奏太慢,决定在菜单中切换到"半真实"或"原版"——这一更改只对你自己生效。战场上的每一个 AI 士兵、精英 NPC 弓箭手和同伴都严格锁定在缓慢的硬编码参数下。这完全破坏了战术军队配置,打破了共享战斗规则的沉浸感,并造成了一种奇怪的割裂——玩家和 AI 在完全不同的弓箭物理法则下运作。
我们认为这在根本上是错误的。如果一个模组提供了游戏配置菜单,那些设置应该诚实且公平地应用于整个战场,而不仅仅是单个玩家。
[line]
[heading]🛠️ 这个独立补丁做了什么[/heading]
我们的模组作为一个独立的、即插即用的模块运作。它向引擎注入一个高优先级的 Harmony Postfix [b]Priority.Last[/b],在 RBM 的代码之后执行,以全局覆盖其缺陷:
[list] []真正的战场平等:它完全剥离了带有偏见的 [b]IsPlayerControlled[/b] 限制。你在 MCM 中选择的装填速度选项现在公平地适用于所有人——包括你自己、你的同伴和每一个 AI 部队,同步生效。[/] []全局数学一致性:没有隐藏的 AI 增益,没有不对称计算。整个战场遵循单一、统一的规则集。[/] []真正的原版模式修复:我们成功修补了模式: [code]"0"[/code]由于 RBM 将基础武器统计数据压低到 45%,我们的补丁注入了精确的、经过测试的补偿乘数: [code]0.60f[/code]用于弓: [code]1.10f[/code]用于快速弩: [code]0.55f[/code]用于重型弩)为所有部队恢复真正的、流畅的、闪电般的原版节奏,同时不破坏引擎的动画循环,并完全恢复单位技能的影响。[/] [*]完美的模式支持:
[list] []如果你选择真实: [code]1[/code] 每个人都以严格的、沉浸式的真实时间射击。[/] []如果你选择半真实: [code]2[/code]整个军队过渡到半真实乘数。[/] []如果你选择原版: [code]0[/code]代码优雅地退出,让原生《霸主》引擎为所有单位决定标准的、高速的弓箭节奏,没有人工膨胀。[/] [/list][/] []性能优化:与原始代码在无尽嵌套循环中反复进行技能检查不同,此修复在最初一次性预先计算单位的有效技能曲线: [code]GetEffectiveSkillWithDR[/code]。它将变量传入干净、高速的: [code]switch/case[/code]结构,并带有提前返回的出口。这种干净的重构在 1000+ 单位的大规模冲突中略微节省了 CPU 周期。[/*] [/list][line]
[heading]⚠️ 需求与兼容性[/heading]
[list] []需要安装原版真实战斗模组。[/] []重要:此文件严格针对《霸主》v1.4.7 的 RBM 版本构建和测试。[/] [/list][line]
[heading]📦 安装(标准子模组流程)[/heading] 此模组作为常规独立模块安装,保持你的原始 RBM 文件夹完全干净。
[list=1] []确保已安装原版真实战斗模组。[/] []下载我们的独立补丁压缩包。[/] []将 [b]RBM_reload_setup_fix[/b] 文件夹直接解压到游戏的 Modules 目录: [code]...\Mount & Blade II Bannerlord\Modules[/code][/] []打开《霸主》启动器,找到 RBM Reload Speed Fix,启用它,并确保它排在主真实战斗模组下方(之后)。[/] []启动游戏,在 MCM 中设置你偏好的装填速度,享受无缝的战场一致性![/] [/list][line]
[heading]💻 源代码[/heading] 我们信奉开放和干净的代码。 为了透明度和方便其他模组作者,以下是为此模块编译的精确独立补丁,它直接覆盖了 [b]RBMCombat.dll[/b] 中带有偏见的逻辑。
原版 RBM 代码: [spoiler][code] using System; using System.Collections.Generic; using System.Reflection; using HarmonyLib; using JetBrains.Annotations; using RBMConfig; using TaleWorlds.Core; using TaleWorlds.Engine; using TaleWorlds.Library; using TaleWorlds.Localization; using TaleWorlds.MountAndBlade;
namespace RBMCombat { // Token: 0x0200000F RID: 15 public partial class RangedRework { // Token: 0x02000049 RID: 73 [HarmonyPatch(typeof(RangedSiegeWeapon))] internal partial class OverrideRangedSiegeWeapon { // Token: 0x02000052 RID: 82 [HarmonyPatch(typeof(AgentStatCalculateModel))] [HarmonyPatch("SetAiRelatedProperties")] private partial class OverrideSetAiRelatedProperties { // Token: 0x060000FE RID: 254 RVA: 0x000127E4 File Offset: 0x000109E4 private static void Postfix(Agent agent, ref AgentDrivenProperties agentDrivenProperties, WeaponComponentData equippedItem, WeaponComponentData secondaryItem, AgentStatCalculateModel __instance) { bool isPlayerControlled = agent.IsPlayerControlled; if (isPlayerControlled) { bool flag = RBMConfig.realisticRangedReload.Equals("1"); if (flag) { SkillObject skill = (equippedItem == null) ? DefaultSkills.Athletics : equippedItem.RelevantSkill; bool flag2 = skill != null; if (flag2) { int ef = __instance.GetEffectiveSkill(agent, skill); float effectiveSkill = Utilities.GetEffectiveSkillWithDR(ef); bool flag3 = equippedItem != null; if (flag3) { string itemUsage = equippedItem.ItemUsage; string a = itemUsage; if (!(a == "bow") && !(a == "long_bow")) { if (!(a == "crossbow_fast")) { if (a == "crossbow") { agentDrivenProperties.ReloadSpeed = 0.2f * (1f + 0.0045f * effectiveSkill); } } else { agentDrivenProperties.ReloadSpeed = 0.3f * (1f + 0.0045f * effectiveSkill); } } else { agentDrivenProperties.ReloadSpeed = 0.25f * (0.85f + 0.0184f * effectiveSkill); } } } } } else { bool flag4 = RBMConfig.realisticRangedReload.Equals("2"); if (flag4) { SkillObject skill2 = (equippedItem == null) ? DefaultSkills.Athletics : equippedItem.RelevantSkill; bool flag5 = skill2 != null; if (flag5) { int ef2 = __instance.GetEffectiveSkill(agent, skill2); float effectiveSkill2 = Utilities.GetEffectiveSkillWithDR(ef2); bool flag6 = equippedItem != null; if (flag6) { string itemUsage2 = equippedItem.ItemUsage; string a2 = itemUsage2; if (!(a2 == "bow") && !(a2 == "long_bow")) { if (!(a2 == "crossbow_fast")) { if (a2 == "crossbow") { agentDrivenProperties.ReloadSpeed = 0.36f * (1f + 0.0035f * effectiveSkill2); } } else { agentDrivenProperties.ReloadSpeed = 0.72f * (1f + 0.0035f * effectiveSkill2); } } else { agentDrivenProperties.ReloadSpeed = 0.38f * (1.5f + 0.0075f * effectiveSkill2); } } } } } } } else { SkillObject skill3 = (equippedItem == null) ? DefaultSkills.Athletics : equippedItem.RelevantSkill; bool flag7 = skill3 != null; if (flag7) { int ef3 = __instance.GetEffectiveSkill(agent, skill3); float effectiveSkill3 = Utilities.GetEffectiveSkillWithDR(ef3); bool flag8 = equippedItem != null; if (flag8) { string itemUsage3 = equippedItem.ItemUsage; string a3 = itemUsage3; if (!(a3 == "bow") && !(a3 == "long_bow")) { if (!(a3 == "crossbow_fast")) { if (a3 == "crossbow") { agentDrivenProperties.ReloadSpeed = 0.2f * (1f + 0.0045f * effectiveSkill3); } } else { agentDrivenProperties.ReloadSpeed = 0.3f * (1f + 0.0045f * effectiveSkill3); } } else { agentDrivenProperties.ReloadSpeed = 0.25f * (1f + 0.016f * effectiveSkill3); } } } } } } } } } } }[/code][/spoiler]
RBM 代码修复: [spoiler] [code] using System; using HarmonyLib; using TaleWorlds.Core; using TaleWorlds.Library; using TaleWorlds.MountAndBlade;
namespace RBM_reload_setup_fix { public class SubModule : MBSubModuleBase { protected override void OnSubModuleLoad() { base.OnSubModuleLoad(); var harmony = new Harmony("Oleksandrovych.RBM.ReloadFix"); harmony.PatchAll(); } }
[HarmonyPatch]
internal static class ReloadSpeedPatch
{
[HarmonyTargetMethod]
public static System.Reflection.MethodInfo TargetMethod()
{
return AccessTools.Method(typeof(AgentStatCalculateModel), "SetAiRelatedProperties");
}
[HarmonyPostfix]
[HarmonyPriority(Priority.Last)]
private static void Postfix(
Agent agent,
ref AgentDrivenProperties agentDrivenProperties,
WeaponComponentData equippedItem,
WeaponComponentData secondaryItem,
AgentStatCalculateModel __instance)
{
if (equippedItem == null)
return;
SkillObject skill = equippedItem.RelevantSkill ?? DefaultSkills.Athletics;
if (skill == null)
return;
float effectiveSkill = GetEffectiveSkillWithDR(__instance.GetEffectiveSkill(agent, skill));
string mode = RBMConfig.RBMConfig.realisticRangedReload;
string usage = equippedItem.ItemUsage;
// --- 1 (REALISTIC) ---
if (mode == "1")
{
switch (usage)
{
case "bow":
case "long_bow":
agentDrivenProperties.ReloadSpeed = 0.25f * (0.85f + 0.0184f * effectiveSkill);
break;
case "crossbow_fast":
agentDrivenProperties.ReloadSpeed = 0.3f * (1f + 0.0045f * effectiveSkill);
break;
case "crossbow":
agentDrivenProperties.ReloadSpeed = 0.2f * (1f + 0.0045f * effectiveSkill);
break;
}
}
// --- 2 (SEMI-REALISTIC) ---
else if (mode == "2")
{
switch (usage)
{
case "bow":
case "long_bow":
agentDrivenProperties.ReloadSpeed = 0.38f * (1.5f + 0.0075f * effectiveSkill);
break;
case "crossbow_fast":
agentDrivenProperties.ReloadSpeed = 0.72f * (1f + 0.0035f * effectiveSkill);
break;
case "crossbow":
agentDrivenProperties.ReloadSpeed = 0.36f * (1f + 0.0035f * effectiveSkill);
break;
}
}
// --- 0 (VANILLA) ---
else if (mode == "0")
{
switch (usage)
{
case "bow":
case "long_bow":
agentDrivenProperties.ReloadSpeed = 0.60f * (1.5f + 0.0075f * effectiveSkill);
break;
case "crossbow_fast":
agentDrivenProperties.ReloadSpeed = 1.10f * (1f + 0.0035f * effectiveSkill);
break;
case "crossbow":
agentDrivenProperties.ReloadSpeed = 0.55f * (1f + 0.0035f * effectiveSkill);
break;
}
}
}
private static float GetEffectiveSkillWithDR(int effectiveSkill)
{
return 600f / (600f + effectiveSkill) * effectiveSkill;
}
}
}[/code][/spoiler] [line]
[heading]📜 致谢[/heading] 所有功劳归于原版 RBM 团队,感谢他们创建了这款出色的战斗大修。这只是为了方便社区在 1.4.7 版本上使用而分享的一个小功能调整。
正在加载版本记录…
正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。