feat: Upgrade server with HyNB v2.4 modular architecture, minigames, admin GM web panel, companion summons, and car radio
This commit is contained in:
1 parent
74e2963a12
commit
3e5adf1dc5
1571 files changed
+336468
-6348
No files matched your search
@@ -0,0 +1,240 @@
|
||||
using Ananta.Server.ClientData.Client4229938;
|
||||
using Ananta.Server.Configuration;
|
||||
|
||||
namespace Ananta.Server.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Character-owned capability buffs that unlock stock client modules.
|
||||
///
|
||||
/// Ownership source: <c>UrbanBadgeConfig.json</c> (ConfigDump_v3) — rows that set both
|
||||
/// <c>Buff[]</c> and a non-zero <c>FightspiritId</c>. Of the 109 controllable spirits only 9
|
||||
/// own anything; see <c>_role_capability_scan.txt</c> for the full list.
|
||||
///
|
||||
/// Everything else in that table is either a numeric passive (damage/HP/driving modifiers),
|
||||
/// a skill variant, or an <c>OnlyServer</c> progress marker — none of them add exploration
|
||||
/// verbs, so they are deliberately not published here.
|
||||
///
|
||||
/// Keep this list small: BuffConfig (4123 rows) also contains executable NPC, boss and
|
||||
/// timeline actions that must never be published as player unlocks.
|
||||
/// </summary>
|
||||
internal static class CharacterCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// <c>UrbanJobConfig</c> 401 Professional Hacker. Authored per character in
|
||||
/// <c>FightSpiritConfig.DefaultUrbanJob</c> — Seymour(15021023) and 赛默迪斯科球人(15022089)
|
||||
/// carry it. Data-driven on purpose: the donor build hard-codes three spirit ids and
|
||||
/// gets one of them wrong (15022061 is jobless).
|
||||
/// </summary>
|
||||
internal const uint HackerUrbanJob = 401;
|
||||
|
||||
/// <summary>Jobless marker from <c>UrbanJobConfig</c>; 0 is never a valid job id.</summary>
|
||||
internal const uint JoblessUrbanJob = 100;
|
||||
|
||||
internal const uint DilaSpiritId = 15_020_997;
|
||||
|
||||
/// <summary>
|
||||
/// 白靖 Bai Jing(spirit 15021039)。原生职业 = 赛车手(class 11300002 / job 4 Racer)。
|
||||
/// 见 <c>GameRouter.BaiJingTime.cs</c> 文件头的完整取证。
|
||||
/// </summary>
|
||||
internal const uint BaiJingSpiritId = 15_021_039;
|
||||
|
||||
/// <summary>
|
||||
/// BuffConfig 52959495 「可以激活斯安威斯坦」— Duration -1、IsHidden、Description「职业标记buff」。
|
||||
/// 斯安威斯坦的<b>能力门闸</b>:客户端职业技能按钮(BattleMgr 一侧)查的就是这个 buff,
|
||||
/// 不下发它,玩家就算挂着赛车手职业也看不到 / 按不动那个按钮。
|
||||
/// </summary>
|
||||
internal const uint BaiJingTimeScaleGateBuff = 52_959_495;
|
||||
|
||||
/// <summary>
|
||||
/// BuffConfig 52959496 「斯安威斯坦」— Duration 5s、Description「子弹时间」、EffectIds [53500525]。
|
||||
/// 真正生效那一份(5 秒子弹时间)。⛑ 它<b>不进</b>常驻清单:
|
||||
/// 常驻清单的含义是「永久能力标记」,而这个是有时长的技能状态,
|
||||
/// 由 <c>GameRouter.BaiJingTime.cs</c> 在玩家按下按钮时现发(Permanent=false)。
|
||||
/// 塞进常驻会让玩家一登录就永久挂着子弹时间。
|
||||
/// </summary>
|
||||
internal const uint BaiJingTimeScaleActiveBuff = 52_959_496;
|
||||
|
||||
/// <summary>
|
||||
/// BuffConfig 52606175 — 名字写的是「移动射击时造成伤害增加15%」,但它是
|
||||
/// <b>徽章 19002851「You Talkin' to Me?」</b>(Cab Driver 出租车司机系列 19002850/51/52)
|
||||
/// 唯一挂在身上的 buff,而捐赠端 7k-v0.5 把白靖的 <c>ResidentBuffIds</c> 就写成
|
||||
/// <c>[52606175]</c> + <c>BadgeIds = [19002850, 19002851, 19002852]</c>。
|
||||
///
|
||||
/// ⛑ 为什么仍然下发它:客户端 <c>DriveControlDriverStore.ShowProfessionSkillButton</c> 查的
|
||||
/// <c>BuffConfig.TimeScaleBadgeBuff</c> 我们读不到字面量(LuaGen/ClientConfig.lua 的键名是
|
||||
/// 混淆过的哈希),52959495 与 52606175 都是候选:
|
||||
/// · 52959495「可以激活斯安威斯坦」Duration -1,且出现在 <c>UberSimConfig.TimeStopPara</c>
|
||||
/// (TimeStopPara 的另一条 52980302 名字直接写着「货车职业徽章…(子弹时间持续时间提高)」)
|
||||
/// ⇒ <b>TimeStopPara 里的 BuffId 都是徽章 buff</b>,这一点偏向 52959495 = TimeScaleBadgeBuff;
|
||||
/// · 但 52606175 是唯一字面意义上的「badge buff」。
|
||||
/// 两个都下发即可覆盖两种可能;52606175 本身是一个隐藏的 +15% 移动射击伤害,无副作用。
|
||||
/// </summary>
|
||||
internal const uint BaiJingCabDriverBadgeBuff = 52_606_175;
|
||||
|
||||
/// <summary>
|
||||
/// BuffConfig 52606125 「可以念力飞行」— permanent, hidden client-side capability flag.
|
||||
/// Badge 19002141 "Zero-Gravity Allure", quality 4.
|
||||
/// </summary>
|
||||
internal const uint DilaFloatFlightBuff = 52_606_125;
|
||||
|
||||
/// <summary>
|
||||
/// Hacker capability set, "safe" shape — the proven round-1 baseline (2026-09-20,
|
||||
/// "middle-mouse entry visible but grey"). Kept selectable because the round-1 experiments
|
||||
/// showed the middle-mouse GadgetManager entry can disappear on wider sets; switching
|
||||
/// <c>gameplay.hackerAbilitySet</c> back to "safe" restores this exact shape without a rebuild.
|
||||
///
|
||||
/// Observed behaviour per set (all measured on the real client, Seymour 15021023):
|
||||
/// | set | middle-mouse hack entry |
|
||||
/// |---------------------------------------|-------------------------|
|
||||
/// | 33/167/168 (this one, no battery) | visible but grey |
|
||||
/// | 33/167/168 + 49/50 | gone |
|
||||
/// | donor's 9 (33/134/135/137/161/167/168/178/179) | gone |
|
||||
/// | donor's 9 + 52899024 | gone |
|
||||
/// </summary>
|
||||
internal static readonly uint[] HackerBuffsSafe =
|
||||
[
|
||||
52_606_133, // 黑客骇入能力总开关 — without it every other one stays inert.
|
||||
52_606_167, // 黑客-骇入车流载具能力
|
||||
52_606_168, // 骇入车流载具-加速技能
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Hacker capability set, "full" shape — everything job 401 can own (2026-09-20 explicit
|
||||
/// request: unlock ALL of Seymour's hacker abilities, drone and spider included).
|
||||
///
|
||||
/// Layout (BuffConfig names, cross-checked against donor_7k + ConfigDump_v3):
|
||||
/// · 52606133 黑客骇入能力总开关 — global gate, same as safe.
|
||||
/// · 52606134/135/137/161 — atmosphere-NPC hack actions (distract / rebel / press).
|
||||
/// · 52606167/168 — traffic-vehicle hacking + its rush skill.
|
||||
/// · 52606178/179 — HackerConfig.HackNPCAbilityBuff[6..7] (EnemyDistract / EnemyRebel);
|
||||
/// the ordinary-NPC flags alone yield an empty enemy menu, so these ship together.
|
||||
/// · 52606149 蜘蛛机器人 / 52606150 黑客无人机 — the two EonBug-app skill entries.
|
||||
/// ⛑️ NOT in donor_7k. They are what makes the spider/drone menu items cast
|
||||
/// (HackScriptFunc.SpiderSkill/DroneSkill → UseSkillByPid 51938181/51938183); the same
|
||||
/// two skills are authored on Seymour's own FightSpiritConfig row as TempSkill, so the
|
||||
/// battle pipeline accepts them (IsKnownSkill covers SkillConfig).
|
||||
/// Known risk from round 1: publishing 49/50 coincided with the middle-mouse entry
|
||||
/// disappearing. The EonBug APP path (HackerAppPanelStore) does not consult these buffs
|
||||
/// at all, so if that regression shows up again the app still works — flip
|
||||
/// hackerAbilitySet to "safe" and the trio comes back.
|
||||
/// </summary>
|
||||
internal static readonly uint[] HackerBuffsFull =
|
||||
[
|
||||
52_606_133,
|
||||
52_606_134,
|
||||
52_606_135,
|
||||
52_606_137,
|
||||
52_606_161,
|
||||
52_606_167,
|
||||
52_606_168,
|
||||
52_606_178,
|
||||
52_606_179,
|
||||
52_606_149, // 蜘蛛机器人 (UrbanBadgeConfig 19001108, job-401 award)
|
||||
52_606_150, // 黑客无人机 (UrbanBadgeConfig 19001109, job-401 award)
|
||||
52_606_170, // 黑客-俄罗斯方块大停电能力 (2026-09-23 补;Donor Blackout §6.1 要的就是这条,
|
||||
// 名字在 BuffConfig 里逐字对得上)。注意别和 T0-12 的
|
||||
// 52606118「可以横梁刺杀」混 —— 那是完全不同的能力。
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Talent-tree ability buffs (see <c>gameplay.hackerTalentBuffIds</c>). Configurable rather than
|
||||
/// authored here because they are not yet a proven shape — T1-6 only lands the C2S round-trip,
|
||||
/// so publishing them here is the flat replacement, not an actual talent grant.
|
||||
/// </summary>
|
||||
private static IReadOnlyList<uint> TalentBuffIds
|
||||
=> PrivateServerConfigStore.Current.Gameplay.HackerTalentBuffIds;
|
||||
|
||||
/// <summary>How many talent buffs actually landed, for the publish log.</summary>
|
||||
internal static int TalentBuffCount => TalentBuffIds.Count;
|
||||
|
||||
/// <summary><c>HackerHackTypeConfig</c> prices hacks in battery points; the donor build seeds 6/6.</summary>
|
||||
internal const uint HackerBatteryCapacity = 6;
|
||||
|
||||
/// <summary>
|
||||
/// BuffConfig 52606233 「潜水服buff」— 不会掉氧气,且会更快的恢复氧气。
|
||||
/// Published to <b>every</b> controllable spirit (<c>gameplay.divingMode == "on"</c>) because
|
||||
/// diving is a client-global capability, not a character one: the gate lives in
|
||||
/// <c>DiveManager.CheckIsEnableDiving()</c> and oxygen is a player attribute, so every role
|
||||
/// needs the same suit. Id is configurable so an id that turns out to be wrong on a future
|
||||
/// build can be swapped without a rebuild.
|
||||
/// </summary>
|
||||
internal const uint DivingSuitBuff = 52_606_233;
|
||||
|
||||
/// <summary>
|
||||
/// Character-independent capability buffs: the diving suit when <c>gameplay.divingMode</c> is
|
||||
/// "on", plus the Cat Express support buffs (<c>gameplay.deliverySupportBuffIds</c>).
|
||||
///
|
||||
/// The courier half is here for one reason: <c>RefreshCallFunctionListView</c> filters a support
|
||||
/// button out unless the player carries its <c>UberSimOrderSupportConfig.UnlockBuff</c>
|
||||
/// (52800660 叫车 / 52800661 无人机). Publishing them with the regular spirit buff snapshot means
|
||||
/// they are present in the app and re-published on every character switch — no dedicated S2C.
|
||||
/// Set the list to <c>[]</c> to restore the pre-2026-09-24 buff snapshot byte-for-byte.
|
||||
/// </summary>
|
||||
internal static IReadOnlyList<uint> GlobalCapabilityBuffIds
|
||||
{
|
||||
get
|
||||
{
|
||||
var gameplay = PrivateServerConfigStore.Current.Gameplay;
|
||||
List<uint> ids = [];
|
||||
if (string.Equals(gameplay.DivingMode, "on", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var suit = gameplay.DivingSuitBuffId;
|
||||
if (suit != 0)
|
||||
ids.Add(suit);
|
||||
}
|
||||
|
||||
foreach (var id in gameplay.DeliverySupportBuffIds ?? [])
|
||||
{
|
||||
if (id != 0 && !ids.Contains(id))
|
||||
ids.Add(id);
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Authored urban job of a controllable spirit; 0 normalizes to Jobless(100).</summary>
|
||||
internal static uint UrbanJobOf(uint templateId)
|
||||
{
|
||||
var job = ClientConfigRepository.Characters()
|
||||
.FirstOrDefault(x => x.TemplateId == templateId)?.DefaultUrbanJob ?? 0;
|
||||
return job == 0 ? JoblessUrbanJob : job;
|
||||
}
|
||||
|
||||
/// <summary>True when the character's authored job grants the hacking kit.</summary>
|
||||
internal static bool CanHack(uint templateId) => UrbanJobOf(templateId) == HackerUrbanJob;
|
||||
|
||||
/// <summary>
|
||||
/// Capability buffs for a character. <paramref name="fullHackerKit"/> selects between the
|
||||
/// safe trio and the full 11-buff hacker set (see <see cref="HackerBuffsSafe"/> /
|
||||
/// <see cref="HackerBuffsFull"/>); the caller reads it from
|
||||
/// <c>gameplay.hackerAbilitySet</c> so the shape stays runtime-switchable.
|
||||
/// </summary>
|
||||
internal static IReadOnlyList<uint> BuffIds(uint templateId, bool fullHackerKit = false)
|
||||
{
|
||||
var traversal = WebTraversal.CapabilityBuffIds(templateId);
|
||||
IEnumerable<uint> characterCapabilities = templateId switch
|
||||
{
|
||||
DilaSpiritId => new uint[] { DilaFloatFlightBuff },
|
||||
// 斯安威斯坦的能力门闸(永久标记,不是技能本体 —— 见常量注释)
|
||||
// + 出租车司机徽章 19002851 的 buff(对齐捐赠端 ResidentBuffIds,见常量注释)
|
||||
// + 排查旋钮 <c>gameplay.baiJingTimeScaleProbeBuffs</c>(默认空,见配置注释)。
|
||||
BaiJingSpiritId => new uint[] { BaiJingTimeScaleGateBuff, BaiJingCabDriverBadgeBuff }
|
||||
.Concat(PrivateServerConfigStore.Current.Gameplay.BaiJingTimeScaleProbeBuffs ?? Array.Empty<uint>()),
|
||||
_ => Array.Empty<uint>()
|
||||
};
|
||||
// Diving suit is role-independent: it rides the same snapshot as the traversal kit so it
|
||||
// needs no extra packet and survives a role switch (the snapshot is republished per switch).
|
||||
characterCapabilities = characterCapabilities.Concat(GlobalCapabilityBuffIds);
|
||||
if (CanHack(templateId))
|
||||
{
|
||||
characterCapabilities = characterCapabilities.Concat(fullHackerKit ? HackerBuffsFull : HackerBuffsSafe);
|
||||
// Talent layer rides the same switch as the rest of the widened set, so flipping
|
||||
// hackerAbilitySet to "safe" rolls back to the proven round-1 shape in one edit.
|
||||
if (fullHackerKit)
|
||||
characterCapabilities = characterCapabilities.Concat(TalentBuffIds);
|
||||
}
|
||||
|
||||
return traversal.Concat(characterCapabilities).Distinct().ToArray();
|
||||
}
|
||||
}
|
||||
@@ -27,20 +27,181 @@ internal static class WebTraversal
|
||||
internal static int GrappleRearmDelayMs => Settings.GrappleRearmDelayMs;
|
||||
internal static IReadOnlyList<uint> ForbidStates => Settings.ForbidStateIds;
|
||||
|
||||
/// <summary>
|
||||
/// 主角原生飞索能力门(C 键定点飞跃)。
|
||||
///
|
||||
/// <c>AbilityLimitConfig.HasFeisuoAbility = 3013</c>(<c>dump.cs:1026235</c>),挂上后客户端
|
||||
/// 追加 <c>State_HasFeisuoAbility = 2318</c>(<c>dump.cs:1226889</c>)并放开 C 键与原生目标查询。
|
||||
/// 这一行带 <c>GamePlayTags[] / ButtonEnum[] / QueryId</c> ⇒ 是<b>能力开关</b>,不是「限制」。
|
||||
///
|
||||
/// ⛑⛑ 光有飞索 buff(<see cref="FeiSuoBuff"/>)打不开客户端的原生飞索 HUD:本服日志里
|
||||
/// <c>AskEnterFeiSuoCrouch</c> 命中恒为 0,即客户端从来没进过飞索蹲伏态。必须再发一次
|
||||
/// <c>SyncPaokuLimitChange</c>(68974941)(见 <c>GameRouter.Profiles.PublishProtagonistGrapnelGate4229938</c>)。
|
||||
/// </summary>
|
||||
internal const uint DefaultFeisuoAbilityLimitId = 3013;
|
||||
internal static uint FeisuoAbilityLimitId
|
||||
=> Settings.FeisuoAbilityLimitId != 0 ? Settings.FeisuoAbilityLimitId : DefaultFeisuoAbilityLimitId;
|
||||
|
||||
/// <summary>
|
||||
/// 暗杀能力门 <c>AbilityLimitConfig.UnlockAssassin = 5051</c>(<c>dump.cs:1026213</c>)
|
||||
/// → 挂 <c>State_UnlockAssassin = 1333</c>(<c>dump.cs:1226857</c>)。
|
||||
///
|
||||
/// ⛑⛑ 与飞索门 3013 <b>同表同构但互相独立</b>:只发 3013 的话客户端能进飞索蹲伏态
|
||||
/// (<c>AskEnterFeiSuoCrouch</c> 正常上报),但 <c>StealthManager</c> 的
|
||||
/// <c>CrouchAssassinationUpdate</c> 不启用 ⇒ <b>从不发 ReportCanAssassinateEnemies</b>
|
||||
/// ⇒ 服务端就算回了 SyncEnemyCanAssassinate 也没用(2026-09-26 17:4x 实机验证:
|
||||
/// AskEnterFeiSuoCrouch 命中 19 次,ReportCanAssassinateEnemies 命中 0 次)。
|
||||
/// 两者都通过同一个 <c>SyncPaokuLimitChange</c>(68974941) 下发。
|
||||
/// </summary>
|
||||
internal const uint DefaultAssassinationAbilityLimitId = 5051;
|
||||
internal static uint AssassinationAbilityLimitId
|
||||
=> Settings.AssassinationAbilityLimitId != 0 ? Settings.AssassinationAbilityLimitId : DefaultAssassinationAbilityLimitId;
|
||||
|
||||
/// <summary>是否下发暗杀能力门。关掉即回滚(HUD 无暗杀提示)。</summary>
|
||||
internal static bool PushAssassinationAbilityGate => Settings.PushAssassinationAbilityGate;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 璎珑(Yinglong / 15021040)专属「虐杀原形跑酷」门。
|
||||
//
|
||||
// 取证链(2026-10-01,全部用我们自己的 dump + ConfigDump,不依赖捐赠端):
|
||||
// · AbilityLimitConfig.Id=3040 Name=KillingPrototype Desc="开启樱珑八步赶蝉(史晨阳)"
|
||||
// → GamePlayTags=[1306] = State_CanKilling(GameplayTagConfig)。
|
||||
// · 反向门 Id=3032 ForbidKilling「禁止璎珑跑酷」→ 1331 State_ForbidKilling(本服从不发)。
|
||||
// · 动作态:ParkourState 10062「璎珑跑酷」(zone 1062 P_Killing)、10081「八步赶蝉jump」;
|
||||
// Killing tag 族 574 Rush / 575 Jump / 577 Edge / 2502 ClimbRun / 2503 ClimbStay
|
||||
// ⇒ 能力本身覆盖平地疾跑与跳跃,不是只有攀爬。
|
||||
// · 服务端此前只发 3013/5051,且 PublishProtagonistGrapnelGate4229938 有 IsProtagonist 闸
|
||||
// ⇒ 3040 一次都没下发过(全量日志 abilities=[3013,5051] ×351,ParkourState 10062 命中 0 次)。
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/// <summary><c>AbilityLimitConfig.KillingPrototype</c> 的 id —— 3040。填 0 = 用内置默认 3040。</summary>
|
||||
internal const uint DefaultKillingAbilityLimitId = 3040;
|
||||
internal static uint KillingAbilityLimitId
|
||||
=> Settings.KillingAbilityLimitId != 0 ? Settings.KillingAbilityLimitId : DefaultKillingAbilityLimitId;
|
||||
|
||||
/// <summary>唯一拿到这张门的角色 —— 15021040 璎珑(Yinglong)。填 0 = 用内置默认。</summary>
|
||||
internal const uint DefaultKillingSpiritId = 15021040;
|
||||
internal static uint KillingSpiritId
|
||||
=> Settings.KillingSpiritId != 0 ? Settings.KillingSpiritId : DefaultKillingSpiritId;
|
||||
|
||||
/// <summary>是否下发璎珑跑酷门。关掉即回滚(回到只有基础爬墙跑/空中冲刺)。</summary>
|
||||
internal static bool PushKillingAbilityGate => Settings.PushKillingAbilityGate;
|
||||
|
||||
/// <summary>
|
||||
/// 角色 → 专属能力门列表(<c>AbilityLimitConfig.Id</c>),走同一条 <c>SyncPaokuLimitChange</c>。
|
||||
///
|
||||
/// 这是上面那三个「killing*」单值开关的<b>通用化替代</b>:一个角色可以带多张门。
|
||||
/// 当前用途:15021040 璎珑 → 3040 虐杀原形跑酷;15020990 → 5040 连跳 + 5052 大跳。
|
||||
/// 优先级:<b>字典里有该角色就用字典</b>,查不到才回落 killing* 单值配置。
|
||||
///
|
||||
/// ⛑ 只给配进来的角色发:其余角色即便挂上 tag,也未必有对应动画数据
|
||||
/// (例:Killing 需要 <c>MovementModeType.Yinglong</c>(10) 的 ABP)。
|
||||
/// ⛑ 空的字典 = 只走旧的 killing 单值路径,行为与改动前一致 ⇒ 回滚安全。
|
||||
/// </summary>
|
||||
internal static IReadOnlyList<uint> AbilityGatesForSpirit(uint templateId)
|
||||
{
|
||||
if (Settings.SpiritAbilityGates.TryGetValue(templateId, out var ids) && ids is { Length: > 0 })
|
||||
return ids;
|
||||
|
||||
return PushKillingAbilityGate && templateId == KillingSpiritId
|
||||
? new[] { KillingAbilityLimitId }
|
||||
: [];
|
||||
}
|
||||
|
||||
/// <summary>★ 跑酷状态 → 临时能力门(<c>gameplay.webTraversal.parkourStateGates</c>)。
|
||||
///
|
||||
/// <b>严格跟状态</b>:进入该 ParkourState 才下发,离开就 <c>isAdd=false</c> 撤回。
|
||||
/// 与 <see cref="AbilityGatesForSpirit"/>(角色常驻门)是两条独立的路径,
|
||||
/// 同一个 limitId 可以同时出现在两边(常驻下发一次 + 状态进入时再下发一次,客户端按集合去重)。
|
||||
/// 留空字典 = 整层不生效 ⇒ 回滚安全。</summary>
|
||||
internal static IReadOnlyList<uint> ParkourStateGateIds(uint parkourState)
|
||||
=> Settings.ParkourStateGates.TryGetValue(parkourState, out var ids) && ids is { Length: > 0 }
|
||||
? ids
|
||||
: [];
|
||||
|
||||
/// <summary>是否配置了任何「跑酷状态 → 门」映射。空字典时整个 handler 分支直接跳过(零开销)。</summary>
|
||||
internal static bool HasParkourStateGates => Settings.ParkourStateGates.Count > 0;
|
||||
|
||||
/// <summary>跑酷门是否在离开状态时撤回。<c>"onLeave"</c> = 严格跟状态;其余(默认 "session")= 只发不撤。
|
||||
/// 见 <c>gameplay.webTraversal.parkourStateGateRelease</c> 的注释(瞬时态下 onLeave 会抖动到不可用)。</summary>
|
||||
internal static bool ReleaseParkourGatesOnLeave
|
||||
=> string.Equals(Settings.ParkourStateGateRelease, "onLeave", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>飞索 + 暗杀两张能力门,一起通过 SyncPaokuLimitChange 下发。</summary>
|
||||
internal static uint[] AbilityLimitIds
|
||||
{
|
||||
get
|
||||
{
|
||||
var ids = new List<uint> { FeisuoAbilityLimitId };
|
||||
if (PushAssassinationAbilityGate)
|
||||
ids.Add(AssassinationAbilityLimitId);
|
||||
return ids.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>是否下发原生飞索能力门。关掉即回滚(回到只有 buff、没有 C 键白圈的状态)。</summary>
|
||||
internal static bool PushFeisuoAbilityGate => Settings.PushFeisuoAbilityGate;
|
||||
|
||||
/// <summary>
|
||||
/// 是否回应蹲伏暗杀裁决(<c>ReportCanAssassinateEnemies</c> → <c>SyncEnemyCanAssassinate</c>)。
|
||||
/// 关掉即回滚到「HUD 没有暗杀提示」的状态。
|
||||
/// </summary>
|
||||
internal static bool AnswerCrouchAssassination => Settings.AnswerCrouchAssassination;
|
||||
|
||||
/// <summary>
|
||||
/// ★ 主动播种 <c>SyncEnemyCanAssassinate</c> —— 不等客户端上报(硬规则 192)。
|
||||
///
|
||||
/// 客户端的候选筛选是<b>自举死锁</b>:<c>PreCheckCanAss</c> 要求目标
|
||||
/// <c>BattleDataModule.canAssassinate</c>(0xB1) == true,而它只能由这条 S2C 写入;
|
||||
/// 这条 S2C 名义上又是 <c>ReportCanAssassinateEnemies</c> 的回应,上报却必须先过
|
||||
/// <c>canAssassinate</c>。不主动播种 ⇒ 循环永远启动不了。
|
||||
/// </summary>
|
||||
internal static bool PushAssassinationSeed => Settings.PushAssassinationSeed;
|
||||
|
||||
/// <summary>播种粗筛半径(米)。</summary>
|
||||
internal static float AssassinationSeedRadiusMeters
|
||||
=> MathF.Max(0.5f, Settings.AssassinationSeedRadiusMeters);
|
||||
|
||||
/// <summary>播种扫描间隔(毫秒)。</summary>
|
||||
internal static int AssassinationSeedIntervalMs
|
||||
=> Math.Clamp(Settings.AssassinationSeedIntervalMs, 100, 10_000);
|
||||
|
||||
/// <summary>Resident build/web buffs, published only when the config keeps them on.
|
||||
/// These carry real damage bonuses but one of them is the only non-hidden entry in the
|
||||
/// whole capability set, which is what puts a permanent icon on the HUD.</summary>
|
||||
private static uint[] ResidentBuildBuffs
|
||||
=> Settings.PushResidentBuildBuffs ? ResidentBuildWebBuffs4229938 : [];
|
||||
|
||||
/// <summary>Capability buffs every controllable spirit gets, protagonists included.
|
||||
/// See <c>gameplay.webTraversal.commonCapabilityBuffIds</c>.</summary>
|
||||
internal static IReadOnlyList<uint> CommonCapabilityBuffIds => Settings.CommonCapabilityBuffIds;
|
||||
|
||||
/// <summary>Per-spirit overrides appended after <see cref="CommonCapabilityBuffIds"/>.
|
||||
/// Empty by default — <c>CharacterCapabilities</c> already covers this data-driven.</summary>
|
||||
internal static IReadOnlyList<uint> SpiritExtraBuffIds(uint templateId)
|
||||
=> Settings.SpiritCapabilityBuffIds.TryGetValue(templateId, out var ids) ? ids : [];
|
||||
|
||||
internal static IReadOnlyList<uint> InitialCapabilityBuffIds
|
||||
=> Combine(Settings.SharedBuffIds, new uint[] { Settings.PersistentGrappleBuffId }, Settings.MaleExtraBuffIds, ResidentBuildWebBuffs4229938);
|
||||
=> Combine(Settings.SharedBuffIds, new uint[] { Settings.PersistentGrappleBuffId }, Settings.MaleExtraBuffIds, ResidentBuildBuffs, CommonCapabilityBuffIds);
|
||||
|
||||
internal static IReadOnlyList<uint> FemaleCapabilityBuffIds
|
||||
=> Combine(Settings.SharedBuffIds, new uint[] { Settings.PersistentGrappleBuffId }, Settings.FemaleExtraBuffIds, ResidentBuildWebBuffs4229938);
|
||||
=> Combine(Settings.SharedBuffIds, new uint[] { Settings.PersistentGrappleBuffId }, Settings.FemaleExtraBuffIds, ResidentBuildBuffs, CommonCapabilityBuffIds);
|
||||
|
||||
internal static bool IsProtagonist(uint templateId)
|
||||
=> true;
|
||||
=> templateId == Settings.MaleProtagonistId || templateId == Settings.FemaleProtagonistId;
|
||||
|
||||
internal static IReadOnlyList<uint> CapabilityBuffIds(uint templateId)
|
||||
{
|
||||
if (templateId == Settings.MaleProtagonistId)
|
||||
return InitialCapabilityBuffIds;
|
||||
if (templateId == Settings.FemaleProtagonistId)
|
||||
return FemaleCapabilityBuffIds;
|
||||
return InitialCapabilityBuffIds;
|
||||
|
||||
// Non-protagonist spirits used to get an empty kit, which is why the whole exploration
|
||||
// kit vanished the moment you switched characters. They now receive the common kit plus
|
||||
// any per-spirit override; CharacterCapabilities layers authored unlocks (Dila's
|
||||
// telekinetic flight, the hacker set, ...) on top of this in Profiles.cs.
|
||||
return Combine(CommonCapabilityBuffIds, SpiritExtraBuffIds(templateId));
|
||||
}
|
||||
|
||||
internal static bool IsClientLifecycleBuff(uint buffId)
|
||||
|
||||
Reference in new issue
Block a user