diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index 8dfdd79..54e8aea 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/Defs/AbilityDefs/Abilities_TrackingCharge.xml b/1.6/Defs/AbilityDefs/Abilities_TrackingCharge.xml
new file mode 100644
index 0000000..c1c2807
--- /dev/null
+++ b/1.6/Defs/AbilityDefs/Abilities_TrackingCharge.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+ ARA_Flyer_TrackingCharge
+ ArachnaeSwarm.PawnFlyer_TrackingCharge
+
+ 0.5
+ 2.0
+
+
+
+
+
+ ARA_Ability_TrackingCharge
+
+ Launch yourself towards a target, dealing damage to everything in your path. The damage increases the further you travel.
+ UI/Abilities/Charge
+ 600
+
+ ArachnaeSwarm.Verb_CastAbilityTrackingCharge
+
+
+ true
+ false
+ true
+ true
+ false
+
+ 30
+ 1.0
+
+
+
+ 1.5
+ 15
+ 2
+ 4
+ Blunt
+ ARA_Flyer_TrackingCharge
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Abilities/CompAbilityEffect_TrackingCharge.cs b/Source/ArachnaeSwarm/Abilities/CompAbilityEffect_TrackingCharge.cs
new file mode 100644
index 0000000..4ad20ec
--- /dev/null
+++ b/Source/ArachnaeSwarm/Abilities/CompAbilityEffect_TrackingCharge.cs
@@ -0,0 +1,10 @@
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class CompAbilityEffect_TrackingCharge : CompAbilityEffect
+ {
+ public new CompProperties_TrackingCharge Props => (CompProperties_TrackingCharge)this.props;
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Abilities/CompProperties_TrackingCharge.cs b/Source/ArachnaeSwarm/Abilities/CompProperties_TrackingCharge.cs
new file mode 100644
index 0000000..db76cb4
--- /dev/null
+++ b/Source/ArachnaeSwarm/Abilities/CompProperties_TrackingCharge.cs
@@ -0,0 +1,20 @@
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class CompProperties_TrackingCharge : CompProperties_AbilityEffect
+ {
+ public float homingSpeed = 1.0f;
+ public float initialDamage = 10f;
+ public float damagePerTile = 2f;
+ public float inertiaDistance = 3f;
+ public DamageDef collisionDamageDef;
+ public ThingDef flyerDef;
+
+ public CompProperties_TrackingCharge()
+ {
+ this.compClass = typeof(CompAbilityEffect_TrackingCharge);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Abilities/PawnFlyer_TrackingCharge.cs b/Source/ArachnaeSwarm/Abilities/PawnFlyer_TrackingCharge.cs
new file mode 100644
index 0000000..d15706d
--- /dev/null
+++ b/Source/ArachnaeSwarm/Abilities/PawnFlyer_TrackingCharge.cs
@@ -0,0 +1,156 @@
+using RimWorld;
+using UnityEngine;
+using Verse;
+using System.Reflection;
+using System.Linq;
+using Verse.AI;
+using System.Collections.Generic;
+
+namespace ArachnaeSwarm
+{
+ public class PawnFlyer_TrackingCharge : PawnFlyer
+ {
+ // --- Public fields to be set by the Verb ---
+ public float homingSpeed;
+ public float initialDamage;
+ public float damagePerTile;
+ public float inertiaDistance;
+ public DamageDef collisionDamageDef;
+ public LocalTargetInfo primaryTarget;
+
+ // --- Internal state ---
+ private Vector3 currentSpeed;
+ private float distanceTraveled = 0f;
+ private bool homing = true;
+ private int inertiaTicks = -1;
+ private Vector3 exactPosition;
+
+ // --- Reflection Fields ---
+ private static FieldInfo TicksFlyingInfo;
+ private static FieldInfo TicksFlightTimeInfo;
+ private static FieldInfo StartVecInfo;
+ private static FieldInfo DestCellInfo;
+ private static FieldInfo PawnWasDraftedInfo;
+ private static FieldInfo PawnCanFireAtWillInfo;
+ private static FieldInfo JobQueueInfo;
+ private static FieldInfo InnerContainerInfo;
+
+ static PawnFlyer_TrackingCharge()
+ {
+ TicksFlyingInfo = typeof(PawnFlyer).GetField("ticksFlying", BindingFlags.Instance | BindingFlags.NonPublic);
+ TicksFlightTimeInfo = typeof(PawnFlyer).GetField("ticksFlightTime", BindingFlags.Instance | BindingFlags.NonPublic);
+ StartVecInfo = typeof(PawnFlyer).GetField("startVec", BindingFlags.Instance | BindingFlags.NonPublic);
+ DestCellInfo = typeof(PawnFlyer).GetField("destCell", BindingFlags.Instance | BindingFlags.NonPublic);
+ PawnWasDraftedInfo = typeof(PawnFlyer).GetField("pawnWasDrafted", BindingFlags.NonPublic | BindingFlags.Instance);
+ PawnCanFireAtWillInfo = typeof(PawnFlyer).GetField("pawnCanFireAtWill", BindingFlags.NonPublic | BindingFlags.Instance);
+ JobQueueInfo = typeof(PawnFlyer).GetField("jobQueue", BindingFlags.NonPublic | BindingFlags.Instance);
+ InnerContainerInfo = typeof(PawnFlyer).GetField("innerContainer", BindingFlags.NonPublic | BindingFlags.Instance);
+ }
+
+ // Custom initializer called by the Verb
+ public void StartFlight(Pawn pawn, IntVec3 finalDest)
+ {
+ var innerContainer = (ThingOwner)InnerContainerInfo.GetValue(this);
+
+ StartVecInfo.SetValue(this, pawn.TrueCenter());
+ DestCellInfo.SetValue(this, finalDest);
+ PawnWasDraftedInfo.SetValue(this, pawn.Drafted);
+ if (pawn.drafter != null) PawnCanFireAtWillInfo.SetValue(this, pawn.drafter.FireAtWill);
+ if (pawn.CurJob != null) pawn.jobs.SuspendCurrentJob(JobCondition.InterruptForced);
+ JobQueueInfo.SetValue(this, pawn.jobs.CaptureAndClearJobQueue());
+
+ if (pawn.Spawned) pawn.DeSpawn(DestroyMode.WillReplace);
+ if (!innerContainer.TryAdd(pawn))
+ {
+ Log.Error("Could not add pawn to tracking flyer.");
+ pawn.Destroy();
+ }
+ }
+
+ public override void SpawnSetup(Map map, bool respawningAfterLoad)
+ {
+ base.SpawnSetup(map, respawningAfterLoad);
+ if (!respawningAfterLoad)
+ {
+ this.exactPosition = base.DrawPos;
+ }
+ }
+
+ protected override void Tick()
+ {
+ int ticksFlying = (int)TicksFlyingInfo.GetValue(this);
+
+ if (ticksFlying == 0)
+ {
+ Vector3 startVec = (Vector3)StartVecInfo.GetValue(this);
+ IntVec3 destCell = (IntVec3)DestCellInfo.GetValue(this);
+ Vector3 destinationPos = GenThing.TrueCenter(destCell, Rot4.North, this.FlyingThing.def.size, this.def.Altitude);
+ Vector3 direction = (destinationPos - startVec).normalized;
+ this.currentSpeed = direction * this.def.pawnFlyer.flightSpeed;
+ }
+
+ this.exactPosition += this.currentSpeed;
+ this.distanceTraveled += this.currentSpeed.magnitude;
+
+ if (inertiaTicks > 0)
+ {
+ inertiaTicks--;
+ if (inertiaTicks <= 0) { Land(); return; }
+ }
+ else
+ {
+ if (homing && primaryTarget.HasThing && primaryTarget.Thing.Spawned)
+ {
+ Vector3 desiredDirection = (primaryTarget.Thing.DrawPos - this.exactPosition).normalized;
+ this.currentSpeed = Vector3.RotateTowards(this.currentSpeed, desiredDirection, this.homingSpeed * 0.017f, 999f).normalized * this.def.pawnFlyer.flightSpeed;
+ }
+ else
+ {
+ homing = false;
+ }
+
+ float calculatedDamage = this.initialDamage + (this.distanceTraveled * this.damagePerTile);
+ var dinfo = new DamageInfo(this.collisionDamageDef, calculatedDamage, 1f, -1, this.FlyingPawn);
+
+ if (homing && primaryTarget.HasThing && (this.exactPosition - primaryTarget.Thing.DrawPos).sqrMagnitude < 1.5f * 1.5f)
+ {
+ primaryTarget.Thing.TakeDamage(dinfo);
+ homing = false;
+ this.inertiaTicks = (int)(this.inertiaDistance / this.currentSpeed.magnitude);
+ }
+
+ foreach (var thing in GenRadial.RadialDistinctThingsAround(this.exactPosition.ToIntVec3(), this.Map, 1.0f, false))
+ {
+ if (thing == this.FlyingPawn || thing == this || thing == primaryTarget.Thing) continue;
+ if (thing is Pawn pawn && !pawn.Downed && pawn.HostileTo(this.FlyingPawn)) pawn.TakeDamage(dinfo);
+ else if (thing.def.destroyable && thing.def.building != null) thing.TakeDamage(dinfo);
+ }
+ }
+
+ try
+ {
+ DestCellInfo.SetValue(this, this.exactPosition.ToIntVec3());
+ TicksFlightTimeInfo.SetValue(this, ticksFlying + 2);
+ }
+ catch (System.Exception ex)
+ {
+ Log.ErrorOnce($"Exception during reflection in PawnFlyer_TrackingCharge: {ex}", this.thingIDNumber);
+ }
+
+ TicksFlyingInfo.SetValue(this, ticksFlying + 1);
+
+ int flightTime = (int)TicksFlightTimeInfo.GetValue(this);
+ if (!this.exactPosition.ToIntVec3().InBounds(this.Map) || ticksFlying > flightTime * 2)
+ {
+ Land();
+ }
+ }
+
+ private void Land()
+ {
+ if (this.Destroyed) return;
+ base.RespawnPawn();
+ this.Destroy();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Abilities/Verb_CastAbilityTrackingCharge.cs b/Source/ArachnaeSwarm/Abilities/Verb_CastAbilityTrackingCharge.cs
new file mode 100644
index 0000000..0cb816c
--- /dev/null
+++ b/Source/ArachnaeSwarm/Abilities/Verb_CastAbilityTrackingCharge.cs
@@ -0,0 +1,47 @@
+using RimWorld;
+using Verse;
+using System.Linq;
+
+namespace ArachnaeSwarm
+{
+ public class Verb_CastAbilityTrackingCharge : Verb_CastAbility
+ {
+ protected override bool TryCastShot()
+ {
+ var props = this.ability.def.comps?.OfType().FirstOrDefault();
+ if (props == null)
+ {
+ Log.Error("Verb_CastAbilityTrackingCharge requires CompProperties_TrackingCharge on the ability def.");
+ return false;
+ }
+
+ if (props.flyerDef == null)
+ {
+ Log.Error("CompProperties_TrackingCharge requires a flyerDef.");
+ return false;
+ }
+
+ if (this.CasterPawn == null || !this.CasterPawn.Spawned)
+ {
+ return false;
+ }
+
+ // --- This is now a fully custom Thing, so we spawn it directly ---
+ var trackingCharge = (PawnFlyer_TrackingCharge)ThingMaker.MakeThing(props.flyerDef);
+
+ // Inject properties
+ trackingCharge.homingSpeed = props.homingSpeed;
+ trackingCharge.initialDamage = props.initialDamage;
+ trackingCharge.damagePerTile = props.damagePerTile;
+ trackingCharge.inertiaDistance = props.inertiaDistance;
+ trackingCharge.collisionDamageDef = props.collisionDamageDef;
+ trackingCharge.primaryTarget = this.currentTarget;
+
+ // Setup and spawn
+ trackingCharge.StartFlight(this.CasterPawn, this.currentTarget.Cell);
+ GenSpawn.Spawn(trackingCharge, this.CasterPawn.Position, this.CasterPawn.Map);
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index abfe60a..78f4b07 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -133,6 +133,12 @@
+
+
+
+
+
+
diff --git a/Source/Documents/design_doc_tracking_charge.md b/Source/Documents/design_doc_tracking_charge.md
new file mode 100644
index 0000000..e0d56a0
--- /dev/null
+++ b/Source/Documents/design_doc_tracking_charge.md
@@ -0,0 +1,97 @@
+# 设计文档:跟踪冲撞技能 (Tracking Charge)
+
+**版本:** 0.1
+
+## 1. 概述
+
+本文档旨在详细说明一个新的 RimWorld 技能:“跟踪冲撞”。该技能允许一个 Pawn(施法者)像制导导弹一样冲向一个目标 Pawn。在飞行过程中,它会对路径上接触到的所有敌对单位和可破坏建筑造成伤害。伤害值会随着飞行距离的增加而累积。当撞击到主目标后,施法者会因惯性继续向前滑行一小段距离。
+
+## 2. 核心功能需求
+
+* **技能类型**: 主动施放的 targeted ability。
+* **移动方式**: 动态追踪曲线移动,而非直线或抛物线。
+* **伤害机制**:
+ * **路径伤害**: 对飞行路径上碰撞到的所有有效目标(敌方、中立、可破坏建筑)造成伤害。
+ * **累积伤害**: 总伤害 = 基础伤害 + (飞行距离 * 每米伤害增量)。
+ * **主目标伤害**: 与路径伤害计算方式相同。
+* **最终效果**: 撞击主目标后,追踪停止,施法者沿最后的方向继续滑行一小段距离后停下。
+
+## 3. 技术架构
+
+我们将采用基于自定义 `PawnFlyer` 的核心架构。这种方法将移动和效果逻辑封装在一个临时的 `Thing` 中,而施法者 Pawn 本身在技能持续期间会从地图上暂时移除。
+
+### 3.1. 核心组件
+
+| 组件名称 (C# Class) | 类型 | 职责 |
+| ---------------------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
+| `Verb_CastAbilityTrackingCharge` | `Verb` | **技能启动器**: 验证目标,从 `CompProperties` 读取配置,创建并初始化 `PawnFlyer_TrackingCharge`。 |
+| `PawnFlyer_TrackingCharge` | `PawnFlyer` | **核心逻辑处理器**: 接收来自 `Verb` 的参数,实现追踪、碰撞、伤害、惯性等所有动态逻辑。 |
+| `CompProperties_TrackingCharge` | `CompProperties_AbilityEffect` | **XML配置接口**: 在 `AbilityDef` 的 `` 中定义技能的所有可调参数,如伤害、速度、惯性等。 |
+| `CompAbilityEffect_TrackingCharge` | `CompAbilityEffect` | **数据容器**: 作为一个轻量级的组件,其主要作用是让 `CompProperties_TrackingCharge` 能被游戏正确加载。它本身不执行复杂逻辑。 |
+
+### 3.2. 设计决策:配置的分层与传递
+
+* **采纳的方案 (混合模式)**: 我们将采用一种既符合直觉又技术稳健的混合模式。
+ 1. **配置集中在 `AbilityDef`**: 使用 `CompProperties_TrackingCharge` (继承自 `CompProperties_AbilityEffect`) 来存放所有技能参数。这使得 Mod 用户可以在 `AbilityDef` 的 `` 节点中方便地调整一切,符合 RimWorld 的标准实践。
+ 2. **`Verb` 作为数据中介**: `Verb_CastAbilityTrackingCharge` 在施法时,会从 `this.ability.def` 中轻松获取到 `CompProperties_TrackingCharge` 的实例,读取所有配置参数。
+ 3. **`PawnFlyer` 接收参数**: `Verb` 在创建 `PawnFlyer_TrackingCharge` 实例后,会通过一个自定义的初始化方法(例如 `Initialize(...)`),将所有读取到的参数“注入”到 `PawnFlyer` 实例中。
+* **优点**: 这个方案完美地解决了您的顾虑。配置的**易用性**(在 `AbilityDef` 中)和逻辑的**清晰性**(`PawnFlyer` 负责执行)都得到了保证。
+
+### 3.3. 数据流与交互
+
+```mermaid
+graph TD
+ subgraph Player Action
+ A[玩家选择目标并施放技能] --> B(AbilityDef);
+ end
+
+ subgraph XML Definitions
+ B -- contains --> E[CompProperties_TrackingCharge];
+ B -- uses --> C(VerbDef);
+ C -- verbClass --> D[Verb_CastAbilityTrackingCharge];
+ F(ThingDef for Flyer) -- thingClass --> G[PawnFlyer_TrackingCharge];
+ end
+
+ subgraph C# Logic
+ D -- 1. Reads Params from --> E;
+ D -- 2. Creates --> G;
+ D -- 3. Injects Params into --> G;
+ G -- 4. Executes Tick Logic --> H{追踪 & 碰撞};
+ H -- 5. Applies Damage to --> I[Things on Path];
+ G -- 6. Handles Inertia & Self-Destructs --> J[Final Position];
+ end
+
+ PlayerInput([施法者 Pawn]) -- temporarily removed --> G;
+ G -- places back --> PlayerInput;
+```
+
+## 4. 详细实现步骤
+
+### 步骤 1: 创建 C# 类骨架 (最终版)
+
+* **文件**: `CompProperties_TrackingCharge.cs`
+ * **继承**: `Verse.CompProperties_AbilityEffect`
+ * **职责**: 定义所有可在 XML 中配置的技能参数。
+ * **示例字段**: `public float homingSpeed;`, `public float initialDamage;`, `public float damagePerTile;`, `public float inertiaDistance;`, `public DamageDef collisionDamageDef;`, `public ThingDef flyerDef;` (用于指定飞行器的ThingDef)。
+
+* **文件**: `CompAbilityEffect_TrackingCharge.cs`
+ * **继承**: `Verse.CompAbilityEffect`
+ * **职责**: 轻量级组件,其存在是为了让 `CompProperties_TrackingCharge` 能被游戏正确加载和访问。
+
+* **文件**: `Verb_CastAbilityTrackingCharge.cs`
+ * **继承**: `Verse.Verb_CastAbility`
+ * **职责**:
+ 1. 在 `TryCastShot()` 中,从 `this.ability.GetComp().Props` 获取配置。
+ 2. 调用 `PawnFlyer.MakeFlyer(Props.flyerDef, ...)` 创建 `PawnFlyer_TrackingCharge` 实例。
+ 3. **将配置参数设置到 `PawnFlyer` 的公共字段上**。
+ 4. 调用 `flyer.Launch()` 启动。
+
+* **文件**: `PawnFlyer_TrackingCharge.cs`
+ * **继承**: `Verse.PawnFlyer`
+ * **职责**:
+ 1. 定义一系列**公共字段** (`public float homingSpeed;` 等) 来接收来自 `Verb` 的参数。
+ 2. 在 `Tick()` 中实现所有核心的追踪、碰撞和伤害逻辑。
+ * **注意**: **不**创建自定义的 `Initialize()` 方法,也**不**重写 `Launch()` 来接收参数,以保持设计的简洁和标准。
+
+---
+> **下一步**: 设计文档已最终确定。我将开始实施 **步骤 1**,为您创建这四个 C# 类的代码框架。
\ No newline at end of file