diff --git a/.snow/handoff_drop_pod_intercept.md b/.snow/handoff_drop_pod_intercept.md new file mode 100644 index 0000000..2004b12 --- /dev/null +++ b/.snow/handoff_drop_pod_intercept.md @@ -0,0 +1,112 @@ +# 落地交付文档:天巫集群掠食 - 空投拦截系统 + +## 1. 目标与结论 +- 目标:实现“敌方空投袭击可被天巫种拦截”的完整闭环(开关控制、拦截逻辑、视觉反馈、通知、持久化)。 +- 结论:核心功能已完成并通过编译,已具备交由 planer 进行方案审阅与验收测试的条件。 + +## 2. 实现范围(按 plan.md 对齐) + +### 2.1 GameComponent 全局状态与拦截逻辑 +- 新增文件:`Source/ArachnaeSwarm/Flyover/GameComponent_DropPodInterceptor.cs` +- 已实现: + - `bool interceptEnabled` 持久化(`ExposeData` + `Scribe_Values.Look`)。 + - `ToggleIntercept()` 开关切换与日志。 + - `HasAirborneTianwu()` 检查 `WorldComponent_AircraftManager.GetAvailableAircraftCount(...) > 0`。 + - `TryInterceptDropPods(...)`: + - 前置检查:开关、敌对派系、可用天巫、至少保留 1 名袭击者。 + - 随机拦截 1-3 名(上限由 `pawns.Count - 1` 约束)。 + - 拦截对象执行 `Pawn.Kill(DamageInfo)`,收集 `Corpse`。 + - 用 `DropPodUtility.DropThingsNear(...)` 以空投仓形式落尸。 + - 触发 FlyOver(复用 `ARA_HiveCorvette_Fake`)与信件通知。 + +### 2.2 Harmony 补丁 +- 新增文件:`Source/ArachnaeSwarm/HarmonyPatches/Patch_DropPodIntercept.cs` +- 已实现: + - Prefix 挂钩: + - `PawnsArrivalModeWorker_EdgeDrop.Arrive` + - `PawnsArrivalModeWorker_CenterDrop.Arrive` + - 两个入口共用 `InterceptPrefix(...)`。 + - **不跳过原方法**(`return true`),原方法继续处理剩余 `pawns`。 + +### 2.3 引航种能力(开关) +- 新增文件:`Source/ArachnaeSwarm/Abilities/CompAbilityEffect_ToggleDropPodIntercept.cs` +- 已实现: + - `CompProperties_ToggleDropPodIntercept` + - `CompAbilityEffect_ToggleDropPodIntercept` + - `Apply`:切换全局开关 + 消息提示。 + - `Valid`:无可用天巫时拒绝施放并提示。 + - `ExtraLabelMouseAttachment`:显示“开启/关闭”状态文本。 + +### 2.4 Ability Def +- 新增文件:`1.6/1.6/Defs/AbilityDefs/Ability_DropPodIntercept.xml` +- 已实现: + - 新能力 `ARA_ToggleDropPodIntercept` + - 自施放、`targetRequired=false`、`targetable=false`、无冷却切换。 + +### 2.5 挂载到 Skyraider +- 修改文件:`1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml` +- 已实现: + - 在 `ArachnaeNode_Race_Skyraider` 的 `abilities` 中追加:`ARA_ToggleDropPodIntercept` + +### 2.6 本地化 +- 修改文件:`1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/AirStrike_Keys.xml` +- 已实现 key: + - `ARA_ToggleDropPodIntercept_Label` + - `ARA_ToggleDropPodIntercept_Desc` + - `ARA_InterceptDropPod_Enabled` + - `ARA_InterceptDropPod_Disabled` + - `ARA_InterceptDropPod_NoAircraft` + - `ARA_InterceptDropPod_Status` + - `ARA_InterceptDropPod_StatusOn` + - `ARA_InterceptDropPod_StatusOff` + - `ARA_InterceptDropPod_LetterLabel` + - `ARA_InterceptDropPod_LetterText` + +### 2.7 工程文件同步 +- 修改文件:`Source/ArachnaeSwarm/ArachnaeSwarm.csproj` +- 已实现:新增 3 个 C# 文件的 ``。 + +## 3. 与 plan.md 的差异说明(供 planer 决策) +- FlyOver Def:按 plan 的“优先复用”策略,当前复用 `ARA_HiveCorvette_Fake`,**未新增** `ARA_HiveCorvette_Intercept` ThingDef。 +- `CompAbilityEffect_ToggleDropPodIntercept.Valid` 使用单目标签名(与现项目其他 Ability 风格一致),未采用数组签名版本。 +- 能力 label/description 当前写在 AbilityDef 内,key 已补全;若需严格 DefInjected 化,可由 planer 决定是否二次整理。 + +## 4. 构建与验证 +- 构建命令(已执行): + - `MSBuild ArachnaeSwarm.csproj -p:Configuration=Release -verbosity:minimal` +- 结果:通过。 +- 输出: + - `1.6/1.6/Assemblies/ArachnaeSwarm.dll` + - `1.6/1.6/Assemblies/ArachnaeSwarm.pdb` + +## 5. 建议的审阅清单(给 planer) +- 逻辑正确性: + - 是否接受“至少保留 1 名袭击者”的平衡策略。 + - 是否接受“仅敌对派系 + EdgeDrop/CenterDrop 生效”的作用域。 +- 体验反馈: + - FlyOver 速度、出现时机、信件文案是否符合预期。 +- 兼容性: + - 与其他修改袭击到场逻辑的 Harmony 补丁是否可能冲突。 +- 本地化策略: + - 是否要求将 AbilityDef `label/description` 进一步改为 DefInjected。 + +## 6. 建议测试用例(未在本轮自动化执行) +- Dev 触发 `Raid (EdgeDrop)`:验证拦截 1-3,尸体空投、FlyOver、信件。 +- Dev 触发 `Raid (CenterDrop)`:同上。 +- 关闭拦截后再触发空投:验证不拦截。 +- 触发 `EdgeWalkIn`:验证不拦截。 +- 触发友军空投:验证不拦截。 +- 存档/读档:验证 `interceptEnabled` 状态持久化。 + +## 7. 当前改动文件清单 +- `Source/ArachnaeSwarm/Flyover/GameComponent_DropPodInterceptor.cs`(新增) +- `Source/ArachnaeSwarm/HarmonyPatches/Patch_DropPodIntercept.cs`(新增) +- `Source/ArachnaeSwarm/Abilities/CompAbilityEffect_ToggleDropPodIntercept.cs`(新增) +- `1.6/1.6/Defs/AbilityDefs/Ability_DropPodIntercept.xml`(新增) +- `1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml`(修改) +- `1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/AirStrike_Keys.xml`(修改) +- `Source/ArachnaeSwarm/ArachnaeSwarm.csproj`(修改) + +--- + +如需我继续,可下一步直接输出“planer审阅意见处理版”补丁(按审阅结论二改)。 diff --git a/.snow/plan.md b/.snow/plan.md new file mode 100644 index 0000000..835be26 --- /dev/null +++ b/.snow/plan.md @@ -0,0 +1,134 @@ +# Plan: 天巫集群掠食 — 空投拦截系统 + +**TL;DR**:通过 Harmony Prefix 补丁拦截原版 `EdgeDrop` 和 `CenterDrop` 的 `Arrive()` 方法,在敌方空投仓落地前由天巫种拦截 1-3 个运输仓。击杀后尸体仍以运输仓形式落地。使用 `GameComponent` 全局管理开关状态,引航种 Pawn 通过自释放 `AbilityDef` 切换开关。拦截时生成天巫种 FlyOver 作为视觉反馈。 + +## 架构概览 + +``` +[敌方袭击触发] +IncidentWorker_RaidEnemy → Arrive(pawns, parms) + ↓ Harmony Prefix +[新增] Patch_DropPodIntercept + → GameComponent_DropPodInterceptor.TryIntercept() + ├── 检查 interceptEnabled (引航种toggle) + ├── 检查 WorldComponent_AircraftManager 有可用天巫 + ├── 验证 parms.faction.HostileTo(Player) + ├── 随机选取 1-3 pawns → Kill → 获取 Corpse + ├── 用 DropPodUtility.DropThingsNear 投掷尸体 + ├── FlyOver.MakeFlyOver() 生成拦截飞越视觉 + └── ReceiveLetter 通知玩家 + → 原 Arrive 继续执行(剩余 pawn 正常空投) + +[引航种能力] +ArachnaeNode_Race_Skyraider → AbilityDef: ARA_ToggleDropPodIntercept + → CompAbilityEffect_ToggleDropPodIntercept.Apply() + → GameComponent_DropPodInterceptor.ToggleIntercept() +``` + +--- + +## Steps + +### 1. 创建全局状态管理 `GameComponent_DropPodInterceptor` + +新文件:`Source/ArachnaeSwarm/Flyover/GameComponent_DropPodInterceptor.cs` + +- 继承 `GameComponent`,序列化字段 `bool interceptEnabled` +- 属性 `IsInterceptEnabled` — 外部查询开关状态 +- 方法 `ToggleIntercept()` — 翻转开关 + `ArachnaeLog.Debug` +- 方法 `HasAirborneTianwu()` — 查询 `WorldComponent_AircraftManager`:检查 `GetAvailableAircraftCount(ThingDef ARA_HiveCorvette_Entity, Faction.OfPlayer) > 0`(非全部冷却中即可,不消耗资源) +- 核心方法 `TryInterceptDropPods(List pawns, IncidentParms parms, out List interceptedPawns)` — 完整拦截逻辑: + - 前置条件检查(`interceptEnabled` && `HasAirborneTianwu()` && `parms.faction.HostileTo(Faction.OfPlayer)`) + - 从 `pawns` 中随机移除 `Rand.RangeInclusive(1, Mathf.Min(3, pawns.Count - 1))` 个 Pawn(至少保留 1 个 pawn 正常空投,避免完全吞掉袭击) + - 对每个被拦截的 Pawn:调用 `pawn.Kill(new DamageInfo(DamageDefOf.Bomb, 9999f))`,收集 `pawn.Corpse` 到尸体列表 + - 将尸体通过 `DropPodUtility.DropThingsNear(parms.spawnCenter, map, corpses, leaveSlag: true)` 投掷到同一空投区域 + - 调用 `SpawnInterceptionFlyOver(map, parms.spawnCenter)` 生成天巫飞越视觉 + - 调用 `SendInterceptionLetter(map, interceptedCount, parms.spawnCenter)` 发送信件 +- 方法 `SpawnInterceptionFlyOver(Map, IntVec3)` — 调用 `FlyOver.MakeFlyOver()` 生成 `ARA_HiveCorvette_Fake`(复用现有视觉 FlyOver ThingDef),起点从地图边缘到空投中心飞越 +- 方法 `SendInterceptionLetter(Map, int count, IntVec3)` — 发送自定义 `LetterDefOf.PositiveEvent` 信件,告知玩家拦截了多少运输仓 +- `ExposeData()` — `Scribe_Values.Look(ref interceptEnabled, "interceptEnabled", false)` + +### 2. 创建 Harmony 补丁 `Patch_DropPodIntercept` + +新文件:`Source/ArachnaeSwarm/HarmonyPatches/Patch_DropPodIntercept.cs` + +- `[HarmonyPatch(typeof(PawnsArrivalModeWorker_EdgeDrop), "Arrive")]` — Prefix +- `[HarmonyPatch(typeof(PawnsArrivalModeWorker_CenterDrop), "Arrive")]` — Prefix +- 两个 Prefix 共用同一个静态方法 `InterceptPrefix(List pawns, IncidentParms parms)` +- Prefix 逻辑:获取 `Current.Game.GetComponent()`,调用 `TryInterceptDropPods(pawns, parms)` +- **不 skip 原方法**(`return true`),原方法继续用被修改过的 `pawns` 列表正常空投剩余敌人 + +### 3. 创建引航种切换能力 `CompAbilityEffect_ToggleDropPodIntercept` + +新文件:`Source/ArachnaeSwarm/Abilities/CompAbilityEffect_ToggleDropPodIntercept.cs` + +- `CompProperties_ToggleDropPodIntercept` 继承 `CompProperties_AbilityEffect`,包含字段: + - `string enabledMessage` / `disabledMessage` — 开启/关闭时的消息文本 key + - `ThingDef requiredAircraftType` — 需要检查的战机类型(`ARA_HiveCorvette_Entity`) +- `CompAbilityEffect_ToggleDropPodIntercept` 继承 `CompAbilityEffect`: + - `Apply(LocalTargetInfo, LocalTargetInfo)` — 获取 `GameComponent_DropPodInterceptor`,调用 `ToggleIntercept()`,发送 `Messages.Message` 通知当前状态 + - `Valid(LocalTargetInfo[], bool)` — 检查 `WorldComponent_AircraftManager.HasAvailableAircraft` 是否有天巫升空;无天巫时禁用能力并显示 "无可用天巫种" 提示 + - `ExtraLabelMouseAttachment(LocalTargetInfo)` — 返回当前状态文本("掠食巡航: 开启/关闭") + +### 4. 创建拦截 FlyOver 视觉 ThingDef + +修改现有文件 `1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml`,新增 `ARA_HiveCorvette_Intercept` ThingDef: + +- 以 `ARA_HiveCorvette_Fake` 为模板(纯视觉 FlyOver,无攻击 Comp) +- `thingClass="ArachnaeSwarm.FlyOver"` +- 设置较快的 `flightSpeed`(拦截应该是快速掠过) +- 可选添加 `CompProperties_SendLetterAfterTicks` 以在飞越后发出完成通知 +- 或直接复用 `ARA_HiveCorvette_Fake` defName,不新建 ThingDef + +### 5. 创建能力 XML 定义 + +新文件:`1.6/1.6/Defs/AbilityDefs/Ability_DropPodIntercept.xml` + +- `AbilityDef` defName: `ARA_ToggleDropPodIntercept` +- `label`: 掠食巡航(或类似命名) +- `targetRequired: false`(自释放,无需目标) +- `cooldownTicksRange: 0`(无冷却,即时切换) +- `comps`: + - `CompProperties_ToggleDropPodIntercept`(`requiredAircraftType: ARA_HiveCorvette_Entity`) + +### 6. 将能力添加到引航种 + +查找并修改引航种 `ArachnaeNode_Race_Skyraider` 的能力列表定义(可能在 `1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml` 或 Race ThingDef 中),在 `abilities` 列表中添加 `ARA_ToggleDropPodIntercept` + +### 7. 添加本地化文本 + +修改 `1.6/1.6/Languages/ChineseSimplified/Keyed/` 下的翻译文件,添加: + +- `ARA_ToggleDropPodIntercept_Label` — "掠食巡航" +- `ARA_ToggleDropPodIntercept_Desc` — 能力描述 +- `ARA_InterceptDropPod_Enabled` — "掠食巡航已启动" +- `ARA_InterceptDropPod_Disabled` — "掠食巡航已关闭" +- `ARA_InterceptDropPod_LetterLabel` — "天巫种拦截空投" +- `ARA_InterceptDropPod_LetterText` — "天巫种在空中拦截了{0}个敌方运输仓…" +- `ARA_InterceptDropPod_NoAircraft` — "没有可用的天巫种兽虫" + +--- + +## Verification + +1. MSBuild 编译:`cd "ArachnaeSwarm\Source\ArachnaeSwarm" && MSBuild ArachnaeSwarm.csproj -p:Configuration=Release` +2. 游戏内测试流程: + - 建造天巫种机库 → 起飞(注册战机到 WorldComponent_AircraftManager) + - 使用引航种能力开启"掠食巡航" + - 使用 dev console 触发 `Raid (EdgeDrop)` → 验证 1-3 个运输仓被拦截(尸体掉落 + FlyOver 视觉 + 信件) + - 使用 dev console 触发 `Raid (CenterDrop)` → 同上 + - 关闭"掠食巡航" → 再次触发空投 → 验证不拦截 + - 触发非空投袭击(EdgeWalkIn) → 验证不触发拦截 + - 触发友方空投 → 验证不拦截(HostileTo 检查) +3. 存档/读档测试:验证 `interceptEnabled` 状态持久化 + +--- + +## Decisions + +- **Harmony 挂钩点**:选 `PawnsArrivalModeWorker_EdgeDrop.Arrive` + `CenterDrop.Arrive` 而非底层 `DropPodUtility`,因为只需响应这两种袭击到场模式,不影响其他空投场景(贸易、任务奖励等) +- **GameComponent vs MapComponent**:选 `GameComponent`(全局,跨地图),因为用户明确说"全局管理" +- **不消耗战机资源**:只检查 `HasAvailableAircraft` 判断天巫是否升空,不调用 `TryUseAircraft` +- **至少保留 1 个 pawn**:`Mathf.Min(3, pawns.Count - 1)` 确保不会完全吞掉袭击,玩家仍需应战 +- **Kill + Corpse 方案**:调用 `Pawn.Kill(DamageInfo)` 后获取 `Corpse`,再通过 `DropPodUtility.DropThingsNear` 以运输仓形式投掷尸体,既有"被击杀"的反馈感,又能剥削敌方装备 +- **复用 `ARA_HiveCorvette_Fake`**:优先复用现有纯视觉 FlyOver,避免新增无意义 ThingDef;若需要不同飞行参数再另建 diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index b4ef638..417599b 100644 Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.pdb b/1.6/1.6/Assemblies/ArachnaeSwarm.pdb index 39f8378..98059d8 100644 Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.pdb and b/1.6/1.6/Assemblies/ArachnaeSwarm.pdb differ diff --git a/1.6/1.6/Defs/AbilityDefs/Ability_DropPodIntercept.xml b/1.6/1.6/Defs/AbilityDefs/Ability_DropPodIntercept.xml new file mode 100644 index 0000000..1689770 --- /dev/null +++ b/1.6/1.6/Defs/AbilityDefs/Ability_DropPodIntercept.xml @@ -0,0 +1,31 @@ + + + + ARA_ToggleDropPodIntercept + + 切换天巫种对敌方空投袭击的拦截模式。开启后,敌方空投到达前会被随机拦截一部分运输仓。 + ArachnaeSwarm/UI/Abilities/ARA_Spawn_ARA_HiveCorvette_Strike + 0 + false + false + + Verb_CastAbility + false + false + true + 0 + false + + True + + + +
  • + ARA_InterceptDropPod_Enabled + ARA_InterceptDropPod_Disabled + ARA_InterceptDropPod_NoAircraft + ARA_HiveCorvette_Entity +
  • +
    +
    +
    diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml index 23af037..78664ef 100644 --- a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml +++ b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml @@ -356,6 +356,7 @@
  • ARA_Skyraider_jump
  • +
  • ARA_ToggleDropPodIntercept
  • @@ -699,4 +700,4 @@
  • ARA_Ability_SlayerCharge
  • - \ No newline at end of file + diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/AirStrike_Keys.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/AirStrike_Keys.xml index ff5d420..dfe7ad7 100644 --- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/AirStrike_Keys.xml +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/AirStrike_Keys.xml @@ -50,4 +50,16 @@ {0} 准备好再次进行打击 没有可用的兽虫 {0}:{1}/{2}(冷却中:{3}) - \ No newline at end of file + + + 掠食巡航 + 切换天巫种对敌方空投袭击的拦截模式。 + 掠食巡航已启动 + 掠食巡航已关闭 + 没有可用的天巫种兽虫 + 掠食巡航:{0} + 开启 + 关闭 + 天巫种拦截空投 + 天巫种在空中拦截了 {0} 个敌方运输仓,目标已坠毁并以空投舱形式落地。 + diff --git a/Source/ArachnaeSwarm/Abilities/CompAbilityEffect_ToggleDropPodIntercept.cs b/Source/ArachnaeSwarm/Abilities/CompAbilityEffect_ToggleDropPodIntercept.cs new file mode 100644 index 0000000..10242ce --- /dev/null +++ b/Source/ArachnaeSwarm/Abilities/CompAbilityEffect_ToggleDropPodIntercept.cs @@ -0,0 +1,79 @@ +using RimWorld; +using Verse; + +namespace ArachnaeSwarm +{ + public class CompProperties_ToggleDropPodIntercept : CompProperties_AbilityEffect + { + public string enabledMessage = "ARA_InterceptDropPod_Enabled"; + public string disabledMessage = "ARA_InterceptDropPod_Disabled"; + public string noAircraftMessage = "ARA_InterceptDropPod_NoAircraft"; + public ThingDef requiredAircraftType; + + public CompProperties_ToggleDropPodIntercept() + { + compClass = typeof(CompAbilityEffect_ToggleDropPodIntercept); + } + } + + public class CompAbilityEffect_ToggleDropPodIntercept : CompAbilityEffect + { + public new CompProperties_ToggleDropPodIntercept Props => (CompProperties_ToggleDropPodIntercept)props; + + public override void Apply(LocalTargetInfo target, LocalTargetInfo dest) + { + base.Apply(target, dest); + + GameComponent_DropPodInterceptor interceptor = Current.Game?.GetComponent(); + if (interceptor == null) + { + return; + } + + bool enabled = interceptor.ToggleIntercept(); + string messageKey = enabled ? Props.enabledMessage : Props.disabledMessage; + Messages.Message(messageKey.Translate(), parent.pawn, MessageTypeDefOf.PositiveEvent); + } + + public override bool Valid(LocalTargetInfo target, bool throwMessages = false) + { + if (!base.Valid(target, throwMessages)) + { + return false; + } + + GameComponent_DropPodInterceptor interceptor = Current.Game?.GetComponent(); + if (interceptor == null) + { + return false; + } + + if (!interceptor.HasAirborneTianwu(Props.requiredAircraftType)) + { + if (throwMessages) + { + Messages.Message(Props.noAircraftMessage.Translate(), parent.pawn, MessageTypeDefOf.RejectInput); + } + + return false; + } + + return true; + } + + public override string ExtraLabelMouseAttachment(LocalTargetInfo target) + { + GameComponent_DropPodInterceptor interceptor = Current.Game?.GetComponent(); + if (interceptor == null) + { + return base.ExtraLabelMouseAttachment(target); + } + + string status = interceptor.IsInterceptEnabled + ? "ARA_InterceptDropPod_StatusOn".Translate() + : "ARA_InterceptDropPod_StatusOff".Translate(); + + return "ARA_InterceptDropPod_Status".Translate(status); + } + } +} diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index 7137873..ad4f9f9 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -137,6 +137,7 @@ + @@ -269,10 +270,12 @@ + + @@ -467,4 +470,4 @@ - \ No newline at end of file + diff --git a/Source/ArachnaeSwarm/Flyover/GameComponent_DropPodInterceptor.cs b/Source/ArachnaeSwarm/Flyover/GameComponent_DropPodInterceptor.cs new file mode 100644 index 0000000..8633eb4 --- /dev/null +++ b/Source/ArachnaeSwarm/Flyover/GameComponent_DropPodInterceptor.cs @@ -0,0 +1,177 @@ +using RimWorld; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + public class GameComponent_DropPodInterceptor : GameComponent + { + private const string DefaultAircraftDefName = "ARA_HiveCorvette_Entity"; + private const string DefaultInterceptFlyOverDefName = "ARA_HiveCorvette_Fake"; + + private bool interceptEnabled; + + public bool IsInterceptEnabled => interceptEnabled; + + public GameComponent_DropPodInterceptor(Game game) + { + } + + public bool ToggleIntercept() + { + interceptEnabled = !interceptEnabled; + ArachnaeLog.Debug($"DropPodInterceptor toggled: {interceptEnabled}"); + return interceptEnabled; + } + + public bool HasAirborneTianwu(ThingDef requiredAircraftDef = null) + { + WorldComponent_AircraftManager manager = Find.World?.GetComponent(); + if (manager == null || Faction.OfPlayer == null) + { + return false; + } + + ThingDef aircraftDef = requiredAircraftDef ?? DefDatabase.GetNamedSilentFail(DefaultAircraftDefName); + if (aircraftDef == null) + { + ArachnaeLog.Debug($"DropPodInterceptor: missing aircraft def {DefaultAircraftDefName}"); + return false; + } + + return manager.GetAvailableAircraftCount(aircraftDef, Faction.OfPlayer) > 0; + } + + public bool TryInterceptDropPods(List pawns, IncidentParms parms, out List interceptedPawns) + { + interceptedPawns = new List(); + + if (pawns == null || pawns.Count <= 1 || !interceptEnabled) + { + return false; + } + + if (parms == null || parms.faction == null || Faction.OfPlayer == null || !parms.faction.HostileTo(Faction.OfPlayer)) + { + return false; + } + + if (!HasAirborneTianwu()) + { + return false; + } + + Map map = parms.target as Map; + if (map == null) + { + ArachnaeLog.Debug("DropPodInterceptor: target map missing."); + return false; + } + + int validPawnCount = pawns.Count(p => p != null); + if (validPawnCount <= 1) + { + return false; + } + + int maxInterceptCount = Mathf.Min(3, validPawnCount - 1); + int interceptCount = Rand.RangeInclusive(1, maxInterceptCount); + + List selected = pawns.Where(p => p != null).InRandomOrder().Take(interceptCount).ToList(); + if (selected.Count == 0) + { + return false; + } + + List corpses = new List(); + foreach (Pawn pawn in selected) + { + if (!pawns.Remove(pawn)) + { + continue; + } + + interceptedPawns.Add(pawn); + + if (!pawn.Dead) + { + pawn.Kill(new DamageInfo(DamageDefOf.Bite, 9999f)); + } + + Corpse corpse = pawn.Corpse; + if (corpse != null && !corpse.Spawned && !corpse.Destroyed) + { + corpses.Add(corpse); + } + } + + if (interceptedPawns.Count == 0) + { + return false; + } + + IntVec3 dropCenter = parms.spawnCenter.IsValid ? parms.spawnCenter : map.Center; + if (corpses.Count > 0) + { + DropPodUtility.DropThingsNear(dropCenter, map, corpses, leaveSlag: true); + } + + SpawnInterceptionFlyOver(map, dropCenter); + SendInterceptionLetter(map, interceptedPawns.Count, dropCenter); + + ArachnaeLog.Debug($"DropPodInterceptor: intercepted {interceptedPawns.Count} raid pawns."); + return true; + } + + private void SpawnInterceptionFlyOver(Map map, IntVec3 dropCenter) + { + ThingDef flyOverDef = DefDatabase.GetNamedSilentFail(DefaultInterceptFlyOverDefName); + if (flyOverDef == null) + { + ArachnaeLog.Debug($"DropPodInterceptor: missing fly over def {DefaultInterceptFlyOverDefName}."); + return; + } + + IntVec3 start = GetRandomMapEdgeCell(map); + IntVec3 end = dropCenter.IsValid && dropCenter.InBounds(map) ? dropCenter : map.Center; + + FlyOver.MakeFlyOver(flyOverDef, start, end, map, speed: 5f, height: 12f); + } + + private static IntVec3 GetRandomMapEdgeCell(Map map) + { + int edge = Rand.RangeInclusive(0, 3); + switch (edge) + { + case 0: + return new IntVec3(Rand.RangeInclusive(0, map.Size.x - 1), 0, 0); + case 1: + return new IntVec3(map.Size.x - 1, 0, Rand.RangeInclusive(0, map.Size.z - 1)); + case 2: + return new IntVec3(Rand.RangeInclusive(0, map.Size.x - 1), 0, map.Size.z - 1); + default: + return new IntVec3(0, 0, Rand.RangeInclusive(0, map.Size.z - 1)); + } + } + + private void SendInterceptionLetter(Map map, int interceptedCount, IntVec3 dropCenter) + { + string label = "ARA_InterceptDropPod_LetterLabel".Translate(); + string text = "ARA_InterceptDropPod_LetterText".Translate(interceptedCount); + + Find.LetterStack.ReceiveLetter( + label, + text, + LetterDefOf.PositiveEvent, + new TargetInfo(dropCenter, map)); + } + + public override void ExposeData() + { + base.ExposeData(); + Scribe_Values.Look(ref interceptEnabled, "interceptEnabled", false); + } + } +} diff --git a/Source/ArachnaeSwarm/HarmonyPatches/Patch_DropPodIntercept.cs b/Source/ArachnaeSwarm/HarmonyPatches/Patch_DropPodIntercept.cs new file mode 100644 index 0000000..904dab5 --- /dev/null +++ b/Source/ArachnaeSwarm/HarmonyPatches/Patch_DropPodIntercept.cs @@ -0,0 +1,46 @@ +using HarmonyLib; +using RimWorld; +using System; +using System.Collections.Generic; +using Verse; + +namespace ArachnaeSwarm +{ + [HarmonyPatch(typeof(PawnsArrivalModeWorker_EdgeDrop), nameof(PawnsArrivalModeWorker_EdgeDrop.Arrive))] + public static class Patch_DropPodIntercept_EdgeDrop + { + [HarmonyPrefix] + public static bool Prefix(List pawns, IncidentParms parms) + { + return Patch_DropPodIntercept.InterceptPrefix(pawns, parms); + } + } + + [HarmonyPatch(typeof(PawnsArrivalModeWorker_CenterDrop), nameof(PawnsArrivalModeWorker_CenterDrop.Arrive))] + public static class Patch_DropPodIntercept_CenterDrop + { + [HarmonyPrefix] + public static bool Prefix(List pawns, IncidentParms parms) + { + return Patch_DropPodIntercept.InterceptPrefix(pawns, parms); + } + } + + public static class Patch_DropPodIntercept + { + public static bool InterceptPrefix(List pawns, IncidentParms parms) + { + try + { + GameComponent_DropPodInterceptor interceptor = Current.Game?.GetComponent(); + interceptor?.TryInterceptDropPods(pawns, parms, out _); + } + catch (Exception ex) + { + ArachnaeLog.Debug($"DropPodInterceptor prefix exception: {ex}"); + } + + return true; + } + } +}