Skip to content

Latest commit

 

History

History
314 lines (204 loc) · 27.9 KB

File metadata and controls

314 lines (204 loc) · 27.9 KB

PSCFaction 完整设计

1. 目标与范围

PSCFaction 是基于 LabAPI 的 SCP:SL 派系插件。每回合从设施警卫中产生一名 PSC 情报探员;探员可在 SCP-127 收容室出发,并通过正常使用广播触发 PSC-S 增援。探员在呼叫成功前死亡则改为生成应急 PSC,并切换整个 PSC 派系的任务关系。

首版包含角色身份、装备、呼叫与死亡分支、分配权重、安全出生、门禁能力、敌友伤害、胜负检查、CustomInfo、HintServiceMeow HUD、C.A.S.S.I.E、自定义入场音乐、配置/翻译、Remote Admin 测试命令和完整生命周期清理。不包含独立结果面板、非门设施权限、自动补位或 EXILED 适配。

术语的精确定义见 CONTEXT.md

2. 技术与依赖

  • Northwood LabAPI 1.1.7。
  • .NET Framework 4.8.1,旧式 MSBuild 项目与 packages.config
  • HintServiceMeow 5.5.0:硬运行依赖,用于底部角色/任务说明和短时通知。服务器还需按该版本上游说明安装兼容 Harmony。
  • SCPSL-AudioManagerAPI 2.4.2:硬运行依赖,用于 PSC 成员专属入场音乐。实际 API 签名以项目内 2.4.2 XML 文档为准,不套用 1.x byte controllerId 示例。
  • 音频文件作为插件嵌入资源,由稳定逻辑名注册。Audio/cassie.wav 是正常与应急流程共用的全服 C.A.S.S.I.E 广播音轨;Audio/summon.wav 是正常与应急召唤共用、仅 PSC 成员可听的阵营入场音乐。

编译依赖不等于服务器安装完成。PSCFaction 的默认发布资产只包含第一方 DLL,HSM、Harmony、AudioManagerAPI 及其运行文件由各自上游安装包负责。

3. 领域身份与承载角色

3.1 插件身份

每个 PSC 成员由插件自有记录表示,至少包含:

  • 稳定玩家 ID 与当前回合 ID。
  • PSC 角色 ID。
  • 正常/应急来源。
  • 预期承载角色。
  • 身份序列号。
  • 装备是否已应用。
  • 原始 CustomInfo 与本插件拥有的 PlayerInfoArea 位。
  • HUD 与音频相关所有权信息。

不得通过“玩家当前是九尾角色”推断 PSC 身份。清理、HUD、音频、伤害、门禁和胜负判断都先查插件成员记录,再校验玩家在线、回合、生命状态和承载角色。

成员记录必须在调用 SetRole 之前写入状态机。SpawningReceivedLoadoutSpawned 都在 SetRole 内部同步触发,只有记录已经存在,这些事件才能写入出生点、装备和展示。SetRole 返回后立即复核:状态机实例未被替换、成员记录仍是同一实例、玩家在线且实际承载角色等于预期值;任一条不成立就回滚该玩家(恢复原生角色、清理展示/HUD、移除成员记录),本次分配不计入成功数。

3.2 公开角色 ID 与承载角色

ID 插件角色 承载角色 模型目的
1 PSC 情报探员 NtfSergeant 九尾中士
2 PSC-S 指挥官 NtfCaptain 九尾指挥官
3 PSC-S 下士 NtfSergeant 九尾中士
4 PSC-S 突击队员 NtfPrivate 九尾列兵
5 PSC-S 战斗工兵 NtfSpecialist 九尾收容专家

公开命令 ID 使用显式映射并长期稳定,不依赖内部枚举声明顺序。

4. 回合状态模型

状态所有者维护:当前回合标识、情报探员 ID、自动流程是否可用、呼叫处理中锁、正常呼叫成功锁、探员死亡待处理标记、应急结算标记、当前 PSC 模式、身份序列和成员表。出生预约与展示所有权保存在成员记录中;音频会话与结束请求锁由对应服务持有。

每次回合开始或 RA 重置都创建新的状态机实例。延迟回调通过“状态机实例是否仍是捕获时的那一个 + 成员身份序列是否未变”来判定是否过期,不需要额外的操作令牌。

建议模式:

  • Inactive:本回合未能产生情报探员,或探员非死亡原因失效。
  • AwaitingCall:探员有效,尚未成功呼叫。
  • NormalCallInProgress:正在生成正常 PSC。
  • NormalActive:至少一名正常 PSC 已确认生成。
  • EmergencyInProgress:正在生成应急 PSC。
  • EmergencyActive:至少一名应急 PSC 已确认生成,整个 PSC 派系进入叛变状态。
  • Closed:回合结束或插件已禁用,不接受新操作。

处理中锁阻止并发;成功锁只在至少一名成员的实际承载角色确认后设置。候选不足或零分配成功时释放处理中锁,不设置成功锁。

4.1 广播与死亡竞态

广播开始后设置处理中锁,但广播本身保持正常,不取消、不替换,也不把已被其他插件拒绝的事件重新允许。

若探员在分配完成前死亡,只设置死亡待处理标记:

  • 至少一名正常 PSC 分配成功:提交正常呼叫,不生成应急 PSC。
  • 正常呼叫候选不足或零人成功:结束正常操作后立即基于最新候选池生成应急 PSC。

所有异步或延迟步骤捕获状态机实例、玩家 ID 和成员身份序列;执行效果前重新校验,旧回调不得影响新回合或新操作。

5. 情报探员流程

回合角色稳定后,从原本的设施警卫候选中随机选择一人。将其承载角色设为 NtfSergeant,确认实际角色后提交“PSC 情报探员”身份。

探员生成于 SCP-127 原生收容室。该房间是地图不变量;若代码无法解析,应作为 API 适配/实现错误记录,不静默选择其他房间。房内使用可配置相对偏移并进行有限安全检查。

SCP-127 房间 Transform 原点不视为可站立地面。出生服务从配置局部锚点上下搜索多个碰撞面,按锚点高度接近度逐个检查坡度、缩小胶囊净空、地面安全间隙以及由 Spawn.PlayerEyeHeight 明确表示的眼高位置仍属于 Hcz127;落点根节点默认高于地面 0.35 米。Spawned 后对情报探员使用更严格的位置容差,偏差过大时执行一次序列保护修正。无法解析同房间安全点时保留原生九尾出生点。

只有探员本人在广播室成功开始正常广播时才触发 PSC 呼叫。生产主触发使用宿主公开静态事件 Intercom.OnServerBeginUsage,它在服务器确认 ReferenceHub 真正开始占用广播时进入协调器;LabAPI 的可取消 UsingIntercom 与后置 UsedIntercom 保留为兼容和诊断入口:前置路径延后一帧确认最终 IsAllowed,后置路径兼容可能报告的 StartingInUse。所有路径最终进入同一个受双锁保护的协调器入口。非探员广播、被其他插件拒绝的广播、流程关闭、正在处理或已经成功时不触发;原生广播行为不受影响。调试日志记录宿主确认、LabAPI 事件阶段、状态与拒绝原因。宿主事件时序属于 SCP:SL 版本敏感点,每次游戏更新后必须实服复测。

探员在成功呼叫前发生真实死亡才触发应急流程。主动离开、管理员换角、其他插件换角、承载角色丢失或插件清理不触发应急 PSC,而是关闭本回合自动 PSC 流程。

6. 候选池与编制

6.1 候选池

正常与应急流程从当前 Spectator 玩家中取候选。真实玩家始终可参与;处于旁观状态的 Dummy 只有在 AllowDummyCandidates 显式启用时参与,默认关闭。按稳定玩家 ID 去重,并记录真实旁观者数、Dummy 数、去重总数和配置边界。

正常 PSC 默认最少 5、最多 20 人。候选不足最低值时本次正常呼叫失败且可重试;超过最大值时随机选择到上限。

6.2 正常编制

计划阶段先放置:1 指挥官、1 战斗工兵、1 突击队员、1 下士。剩余名额按权重逐人分配:下士默认权重 30,突击队员默认权重 70;战斗工兵也可配置权重,但总上限为 2;指挥官总上限为 1。所有上限和权重可配置,校验后必须仍能形成基础编制。

编制保证只针对分配计划。执行逐人尽力完成,玩家退出、失效或被外部因素阻止时不递补、不回滚其他成功成员。至少一名实际确认成功即提交正常呼叫;零人成功才视为失败。

6.3 应急编制

不设最低人数,最多选择 4 人,不自动补发:

候选数 编制
0 不生成,流程结算一次后关闭
1 1 战斗工兵
2 1 战斗工兵、1 下士
3 1 战斗工兵、2 下士
4+ 1 战斗工兵、2 下士、1 指挥官

应急流程不使用正常权重。

7. 分配事务与装备

每名玩家的分配步骤:解析承载角色;清理该玩家可能存在的旧 PSC 身份;带出生预约创建成员记录并写入状态机;调用 LabAPI 角色设置;SetRole 返回后复核状态机实例、成员实例、在线状态和实际承载角色;确认后执行一次装备初始化、位置校正、展示与 HUD;再安排一次序列保护的延迟刷新。

装备初始化的最终结果必须由 PSC 配置决定。PlayerEvents.ReceivedLoadout 表示宿主刚写完原生物品栏,因此该事件允许覆盖一次;其余路径(SetRole 返回后、展示刷新、延迟刷新)都受一次性门控保护,不得重复清空物品栏。不得用固定延迟猜测原生装备稳定时机。

角色设置失败或复核不通过时回滚该玩家并跳过。不得因调用过 SetRole 就计为成功。执行队列在每名候选之前检查状态机实例是否仍是本次操作的实例;回合切换、RA 重置或状态关闭时立即停止剩余候选,旧状态机的成员由生命周期清理统一处理。单个候选失败不递补、不回滚其他已确认成员。

装备初始化清空并重建物品栏,但默认保留承载角色原生弹药池。配置可为每个角色显式覆盖弹种和数量;留空表示保留。枪械初始弹匣正常装填。展示刷新、Spawned 和延迟刷新都只更新展示与 HUD,不得再次清空物品栏或重复发放装备。

默认装备:

角色 装备
PSC-S 指挥官 GunE11SR×1、Medkit×2、GunCOM18×1、ArmorHeavy×1、GrenadeHE×1、Radio×1、KeycardMTFCaptain×1
PSC-S 下士 GunE11SR×1、Medkit×1、GunCOM15×1、ArmorCombat×1、GrenadeHE×1、Radio×1、KeycardMTFOperative×1
PSC-S 突击队员 GunFSP9×1、Medkit×1、ArmorCombat×1、GrenadeFlash×1、Radio×1、KeycardMTFOperative×1
PSC-S 战斗工兵 ParticleDisruptor×1、Medkit×1、ArmorHeavy×1、GrenadeHE×3、Radio×1、KeycardMTFCaptain×1
PSC 情报探员 GunCOM18×1、Medkit×1、Radio×1、ArmorLight×1、KeycardContainmentEngineer×1

8. B 门安全出生

正常与应急 PSC 共用 B 门出生服务,在分配前一次性解析本批所需的全部落点。

基准点解析顺序:

  1. Spawn.GateBBasePosition 非零时作为基准,按 Spawn.GateBOffsets 顺序尝试槽位;槽位用尽后在 Spawn.GateBSpreadRadius 内按黄金角环形撒点补足。
  2. 未配置基准点,或配置基准点解析出的安全落点不足时,回退到 RoleSpawnpointManager 提供的原生九尾地表出生点,同样按环形撒点铺开。
  3. 两条来源都失败时记录错误,本批成员保留承载角色的原生出生位置。

每个候选落点都要验证有限数值、向下射线命中地面、坡度、缩小胶囊的身体净空,以及与本批已确认落点的最小距离。

落点通过成员记录随身携带:Spawning 事件用 SetSpawnpoint 写入并置位“已应用”,Spawned 事件与一次序列保护的延迟刷新负责在偏差超过容差时用 Position 校正。延迟刷新在执行前重新确认状态机实例、成员身份序列、玩家在线、存活和承载角色。

9. 阵营、伤害与胜负

9.1 正常模式

正常 PSC 与基金会友好,敌人为所有非基金会敌对阵营。正常 PSC 不修改原生友伤关系。PSC 全灭后插件停止干预胜负,基金会仍可按原生规则继续取胜。

至少一名正常 PSC 存活且所有非基金会敌对阵营被消灭时,请求按基金会原生胜利结束。结束请求必须单次锁定,并接受原生结果面板能力限制。

9.2 应急模式

应急流程激活后,所有 PSC 成员彼此友好,包括通过管理员命令产生的正常 PSC;所有非 PSC 玩家均为敌人。

因为 PSC 承载角色属于九尾,插件需定向覆盖伤害许可:PSC 可伤害非 PSC,非 PSC 可伤害 PSC;PSC 内部伤害继续遵守服务器全局友伤设置。不得开启或修改全服友伤。

LabAPI 1.1.7 的 PlayerHurtingEventArgs.IsAllowed 无法区分拒绝来源,因此插件绝不把已经为 false 的事件恢复为 true。应急模式下仅当事件当前仍允许、攻击者是有效玩家且攻击双方恰有一方为有效 PSC 时,清除本次 AttackerDamageHandler 的原生 IsFriendlyFire 标记;PSC 对 PSC、非 PSC 对非 PSC、环境伤害和已拒绝事件均保持原判定。这样只绕过九尾承载角色造成的宿主友伤限制,同时保留其他插件对 IsAllowed 的否决权。

AttackerDamageHandler.IsFriendlyFire 没有公开 setter,当前适配通过反射访问 SCP:SL 宿主自动属性 backing field,属于版本敏感的非公开 API。启用前执行静态字段存在检查,运行失败时记录中文警告并保持宿主判定;每次 SCP:SL 更新后必须重新构建并实服验证应急双向伤害。

至少一名 PSC 存活且所有非 PSC 玩家被消灭时,以原生“陷入僵局”结束回合。PSC 全灭时停止干预,其他阵营继续原生对局。

10. 战斗工兵门禁

战斗工兵与有效 PSC 身份同时成立时,可绕过门的钥匙卡权限。能力只适用于门,不适用于地表核弹室盖板、发电机、广播控制台、电梯或其他交互设施。

如果门因系统状态、净化、核弹流程、管理员或其他插件被锁定,工兵不得解除或绕过。事件已被其他处理器拒绝时不得重新允许。

实现顺序为:先保留 IsAllowed == falseDoor.IsLocked,再确认有效工兵身份;仅当当前 CanOpen == false 且宿主 DoorPermissionsPolicy.CheckPermissions(...) 重新计算为“原生钥匙卡权限不足”时,才将本次 CanOpen 改为 true。LabAPI 事件没有提供 CanOpen == false 的来源;若另一插件只改 CanOpen=false,同时玩家也缺少原生钥匙卡,PSCFaction 无法可靠区分两种原因。因此跨插件硬锁必须使用 IsAllowed=false 或原生 Door.IsLocked;“仅改 CanOpen”的拒绝属于不可观测兼容边界,必须列入实服组合测试。

11. 展示与 HUD

11.1 CustomInfo

PSC 成员使用自定义名称:PSC 情报探员、PSC-S 指挥官、PSC-S 下士、PSC-S 突击队员、PSC-S 战斗工兵。写入前使用框架验证器检查文本;显示 CustomInfo,隐藏被替代的原生 Role,九尾承载角色还隐藏 UnitName

CustomInfo 使用行级所有权:本插件自有行固定编码为 <b>{RoleName}</b>,先验证拼接后的完整文本,再将该完整行追加到末尾;成员记录保存最后一次写入的完整富文本行。刷新和清理至多移除一条匹配行,优先移除最后一条,以避免删掉外部同名纯文本或多条重复文本。若外部插件恰好写入完全相同的富文本行,文本层无法证明归属,此不可观测冲突必须记录为兼容边界,不能使用 RemoveAll 扩大删除范围。稳定事件和序列保护刷新都持续维护该自有行,即使其他插件改写了其余内容也要重新追加,不得因检测到变化而放弃刷新。PlayerInfoArea 只记录并恢复本插件实际修改的位;刷新前发现某个已拥有位已被外部改写时,立即放弃该位的所有权并保留外部当前值,后续不再强制覆盖或恢复该位。

展示应用发生在承载角色确认后、稳定生成/装备事件以及一次可配置的序列保护延迟刷新。延迟刷新捕获回合 ID、玩家 ID 和身份序列,并重新校验在线、存活与承载角色。每次写入后回读 CustomInfo/InfoArea,不一致时记录期望值和实际值。展示失败不撤销有效身份、装备或 HUD。

清理时从当前 CustomInfo 中移除本插件自有角色行;Role/UnitName 只有在对应位仍由本插件拥有时才恢复,其他插件后来修改的位或文本保持不变。

11.2 HintServiceMeow

每名 PSC 成员使用一个常驻底部说明 Hint,同时显示角色与任务。五个 PSC 角色分别拥有正常/应急两套可配置任务文本;旧版全局正常/应急文本仅作为角色字段缺失或为空时的兼容回退:

  • 正常:角色名 + “协助基金会,消灭所有非基金会敌对阵营”。
  • 应急:角色名 + “消灭所有非 PSC 人员”。

另设一个短时通知 Hint,用于呼叫结果、权限错误和任务更新。ID 使用 PSCFaction.RoleTask.<PlayerId>PSCFaction.Notification.<PlayerId>。布局、字号、对齐、颜色和翻译可配置;布局变化时移除并重建 Hint,不使用全局 ClearHint()

12. C.A.S.S.I.E 与音频

正常 PSC 入场向全服发送可配置 C.A.S.S.I.E 播报/字幕,默认含义为“警告,一支不明武装力量于 B 门进入设施”。应急 PSC 使用独立的可配置播报与字幕,明确其敌对任务变化。原生 C.A.S.S.I.E 文本是默认可靠播报源;只有 Cassie.PreferCustomAudioTrack 显式启用且 AudioManagerAPI 成功创建会话时,才以嵌入 cassie.wav 承担声音并让原生播报只负责句点时序与字幕。

自定义入场音乐类似原生九尾/混沌增援音乐,仅向当前有效 PSC 成员播放。正常与应急保留独立资源键和音量配置,但首版两个键默认共同映射 Audio/summon.wav。听众过滤依据插件身份、当前回合、在线状态和承载角色,不按九尾角色泛化;不得使用角色切换瞬间可能尚未稳定的 IsAlive 作为首次建立听众列表的必要条件。

启用时注册每个嵌入音频资源,流提供器每次返回新流。资源键按 StringComparer.Ordinal 作为进程内精确键管理;正常与应急可使用完全相同的共享键,但配置不得出现仅大小写不同的别名。AudioManagerAPI 2.4.2 没有注销或查询注册表 API,因此成功注册后的键由跨 AudioService 实例的进程内门控保存;重载时不会再次注册同一精确键。保存 AudioManagerAPI 返回的非零 int sessionId;同用途播放前停止旧会话;若 Stop 抛异常,必须再查询 IsValidSession,仅在已确认无效时清空句柄;会话仍有效或查询失败时保留句柄并拒绝启动同用途替换会话,避免叠音。回合结束和卸载只停止本插件保存的会话。资源缺失、注册异常、播放返回 0 或运行异常只记录并跳过音频,不回滚玩法状态。C.A.S.S.I.E 与阵营入场音乐使用独立异常边界,前者失败不得阻止后者调用;成功提交召唤、资源注册、C.A.S.S.I.E 触发和音频会话 ID 都必须留下可检索日志。AudioManagerAPI 的无注销契约、停止失败与插件禁用/重载后的残留会话均需真实服务器冒烟验证。

C.A.S.S.I.E 字幕和独立音乐是两条生命周期。若使用英文句点延时技巧,其时长只表示近似字幕展示时间,不从音频长度推导,并必须实服验证。

13. 配置与翻译

数据可配置、流程固定。LabAPI 端口级插件目录中固定使用两个独立文件:config.yml 保存玩法与集成参数,translation.yml 保存全部玩家/管理员可见文本。翻译不得作为 PluginConfig 的嵌套节点再次写入主配置。

config.yml 按“基础开关与候选范围 → 情报探员偏移 → Spawn → Hud → Audio → Cassie → Roles”排列。所有管理员可编辑字段使用 YamlDotNet YamlMember.Description 输出自然中文注释,说明语义、单位、范围和风险。插件只读取和校验已有文件,绝不在加载时自动备份、重写、重排、删除未知键或删除人工排版。需要新版带注释模板时,管理员先自行备份并重命名旧 config.ymltranslation.yml,让 LabAPI 生成新文件,再人工迁入经过核对的有效值;原始文件保留为回退依据。配置包括:启用开关、候选最小/最大值、Dummy 开关、角色上限与权重、承载角色、装备、弹药覆盖、127 房偏移、情报探员地面搜索距离/离地间隙/生成后容差、B 门基准点/槽位/自动撒点半径与安全参数、HUD 布局与展示刷新延迟、音频资源键/音量、C.A.S.S.I.E 文本和日志详细度。

翻译包括:角色名、正常/应急任务、短时通知、命令帮助/描述/格式/错误/成功响应和 C.A.S.S.I.E 字幕。

流程固定项包括:广播触发、死亡应急、一次性成功锁、竞态处理、应急人数表、敌友关系、胜负模式和清理所有权。

校验规则:

  • 枚举字段仅接受已定义名称,大小写可不敏感,但拒绝数字文本和未定义值。
  • 人数、上限、权重、音量、时长、坐标和距离检查范围、有限值及相互约束。
  • 正常配置必须能形成四角色基础编制;无效关键值回退安全默认并记录。
  • 无效装备项只跳过该项;翻译必须保留所需占位符。
  • 旧版 config.yml 中遗留的 Translation 节点允许由 LabAPI/YamlDotNet 忽略;独立 translation.yml 是唯一生效的翻译来源。迁移由管理员人工完成,插件不删除旧节点,也不要求删除整份主配置。
  • 注释只在首次生成或显式保存配置时写出;现有无注释文件不会在读取时自动改写。需要注释模板时先备份并重命名旧 config.ymltranslation.yml,让插件生成新文件,再人工迁移原值,避免自动重写破坏未知键或手工排版。

14. 管理员命令

Remote Admin 根命令为 psc。命令只注册到 Remote Admin 命令处理器;能够在 RA 面板执行命令的管理员无需再配置 pscfaction.admin 或其他插件自定义权限节点。直接输入 pscpsc help 时显示插件说明、全部命令格式和角色 ID 对照表;任何参数错误都回显相关格式和有效值。

命令 作用
psc status 显示回合模式、探员、锁、成员、出生预约和音频会话摘要
psc summon normal 开启新的 RA 测试周期后复用生产正常路径;可反复执行
psc summon emergency 开启新的 RA 测试周期后复用生产应急路径;可反复执行
psc role <playerId> <roleId> 通过正式分配服务赋予角色;重新指定情报探员时开启新测试周期
psc cleanup 清理测试状态并立即建立新的可测试状态;不改变玩家当前原生角色

角色 ID 为 1 情报探员、2 指挥官、3 下士、4 突击队员、5 战斗工兵。0、负数和未定义数字无效。

服务器 GameConsole 不注册 psc。命令实例在启用时只向 Remote Admin 注册一次,卸载时注销同一实例。响应全部从独立 translation.yml 读取。

生产自动流程继续遵守每回合一次性成功锁。RA 命令不直接绕过生产锁,而是通过新建测试周期使旧回合 ID、操作序列、成员、成功锁、应急结算锁、出生预约、HUD 和音频失效。测试复位不修改上一批玩家的原生角色;后续 summon 只使用当时实际处于 Spectator 的候选。Closed 仅用于真实回合结束、插件卸载和终止级清理,不是 RA cleanup 的最终状态。

15. 生命周期与清理矩阵

事件 处理
玩家死亡 移除成员展示/HUD/出生预约;探员仅在呼叫成功前真实死亡时触发或挂起应急流程
玩家离开 清理该玩家;探员离开关闭自动流程但不触发应急
承载角色丢失 清理 PSC 身份与拥有资源;探员非死亡失效不触发应急
PSC 自定义角色替换 先清理旧角色拥有状态,再通过正式分配路径应用新角色
回合开始 新建回合状态、清空旧序列/锁/成员/音频,选择探员
回合结束 关闭入口,停止音频,清理 HUD/展示/成员/出生与延迟所有权
插件卸载 先注销命令和事件,再关闭入口并幂等清理全部插件资源

调度器无法取消的回调仍通过递增序列失效。不得调用 HSM 或 AudioManagerAPI 的全局清理功能。

16. 建议项目结构

PSCFaction/
├─ Plugin.cs                     生命周期、只读配置加载、入口注册
├─ Config/
│  ├─ PluginConfig.cs            玩法与集成参数(config.yml)
│  ├─ RoleConfig.cs
│  ├─ SerializableVector3.cs
│  ├─ Translation.cs             全部可见文本(translation.yml)
│  └─ ConfigValidator.cs         枚举名解析、窄范围修复、翻译占位符检查
├─ Domain/                       纯逻辑,可离线测试
│  ├─ PscRoleId.cs
│  ├─ PscMode.cs
│  ├─ PscMember.cs               插件自有身份 + 出生预约 + 展示所有权
│  ├─ RoundState.cs              回合状态机与双锁
│  └─ RosterPlanner.cs           候选筛选、正常权重编制、应急人数表
├─ Services/
│  ├─ PscService.cs              唯一状态所有者:回合、召唤、分配、清理
│  ├─ SpawnService.cs            127 房与 B 门安全落点解析
│  ├─ LoadoutService.cs          一次性装备与弹药覆盖
│  ├─ PresentationService.cs     CustomInfo 行级所有权与 InfoArea 位所有权
│  ├─ HudService.cs              HintServiceMeow 两个自有 Hint
│  ├─ AudioService.cs            AudioManagerAPI 资源注册与会话
│  ├─ CombatService.cs           应急伤害绕过、工兵门禁、胜负判定
│  └─ NativeFriendlyFireAdapter.cs  宿主非公开友伤标记的反射适配
├─ Events/EventHandlers.cs       只做事件适配
├─ Commands/PscCommand.cs        Remote Admin 测试命令
├─ Tests/PSCFaction.OfflineRegression/  纯逻辑离线回归
├─ Audio/
└─ Properties/

PscService 是唯一流程状态所有者。事件、命令和依赖适配器不得各自实现第二套分配或清理逻辑。Domain/ 不引用 LabAPI 包装类型,以便离线回归直接覆盖编制与状态机。

17. 验收矩阵

至少验证:无警卫、探员正常产生、非探员广播、探员广播、候选不足重试、5/20/超上限正常波、探员在呼叫处理中死亡、零分配成功转应急、应急 0–4+ 人、玩家分配中退出不补位、工兵门与非门设施、系统锁门、正常与应急双方伤害、PSC 内部友军、CustomInfo 恢复、HUD 首帧布局、音频成员过滤/会话停止、B 门多人间距、死亡/离开/换角/回合结束/卸载清理、命令权限与错误提示、正常基金会胜利、应急僵局结算。

编译只能证明类型和引用正确。HUD 坐标、广播事件时序、角色同步、B 门出生、音频可听范围、定向伤害与回合结算必须在真实服务器确认。

18. 已知风险与待输入

  • 音频资产已提供:cassie.wav 为单声道 24 kHz/16-bit PCM C.A.S.S.I.E 音轨,summon.wav 为双声道 48 kHz/16-bit PCM 阵营入场音乐;正常与应急共用同一入场音乐。默认音量仍需实服试听校准。
  • AudioManagerAPI 参考知识常见于 2.3.x,项目实际为 2.4.2;编码必须读取本地 2.4.2 XML/程序集契约。
  • SCP-127 房间和广播/生成事件的 LabAPI 1.1.7 枚举与时序需从本地 XML/程序集核对,并在实服确认。
  • HSM、Harmony、AudioManagerAPI 的服务器实际版本只能由启动日志证明。
  • LabAPI 1.1.7 无法识别 PlayerHurtingEventArgs.IsAllowed == false 的拒绝来源,应急阵营跨九尾友伤的安全实现仍需实服确认;不得用无条件 IsAllowed = true 破坏其他插件拒绝。
  • 当前无 Git 元数据,无法记录基线提交或变更集。
  • Spawning/ReceivedLoadout/SpawnedSetRole 内部同步触发是本实现的前提。若 SCP:SL 或 LabAPI 改为异步派发,必须重新验证出生点与装备的最终结果。