# 技术文档：RimWorld 中 `specialTrainables` 的工作机制解析 (V6 - 终极证据版)

## 1. 目标

本文档旨在深入剖析 RimWorld 中 `<specialTrainables>` 标签的底层工作机制，并最终得出一套可复用的“配方”，用于让动物学会在无需训练、无衰减的情况下，执行任何原版已存在的工作。本文档包含所有必要的、完整的原版代码引用作为证据，以确保其自包含性、正确性和健壮性，可供任何人在无上下文的情况下阅读并执行。

## 2. 核心机制深度解析

`specialTrainables` 的功能是一套精巧的、深度集成在 AI 思维逻辑中的 **条件行为分支**。

### 2.1. 玩家授权机制

**关键发现**: 游戏通过一个统一的 UI 列 (`PawnColumnWorker_Trainable_Special`) 来管理所有被标记为 `specialTrainable` 的技能。当玩家点击该列的复选框时，会调用 `pawn.training.SetWantedRecursive()` 来设置 `Pawn_TrainingTracker` 中 `wantedTrainables` 字段的值。

*   **证据**: 以下是 `PawnColumnWorker_Trainable_Special.txt` 的完整内容，它清晰地展示了 `SetWantedRecursive` 是如何被调用的。
    ```csharp
    using System.Text;
    using UnityEngine;
    using Verse;

    namespace RimWorld
    {
        public class PawnColumnWorker_Trainable_Special : PawnColumnWorker
        {
            public override void DoHeader(Rect rect, PawnTable table)
            {
                base.DoHeader(rect, table);
                MouseoverSounds.DoRegion(rect);
            }

            public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
            {
                if (pawn.training != null && !pawn.RaceProps.specialTrainables.NullOrEmpty())
                {
                    int num = (int)((rect.width - 24f) / 2f);
                    int num2 = Mathf.Max(3, 0);
                    Rect rect2 = new Rect(rect.x + (float)num, rect.y + (float)num2, 24f, 24f);
                    DoSpecialTrainableCheckbox(rect2, pawn, doTooltip: true);
                }
            }

            private void DoSpecialTrainableCheckbox(Rect rect, Pawn pawn, bool doTooltip)
            {
                GetStatus(pawn, out var learned, out var checkOn, out var canTrain, out var _);
                bool flag = checkOn;
                Texture2D texChecked = (learned ? TrainingCardUtility.LearnedTrainingTex : null);
                Texture2D texUnchecked = (learned ? TrainingCardUtility.LearnedNotTrainingTex : null);
                Widgets.Checkbox(rect.position, ref checkOn, rect.width, !canTrain, paintable: true, texChecked, texUnchecked);
                if (checkOn != flag)
                {
                    PlayerKnowledgeDatabase.KnowledgeDemonstrated(ConceptDefOf.AnimalTraining, KnowledgeAmount.Total);
                    foreach (TrainableDef specialTrainable in pawn.RaceProps.specialTrainables)
                    {
                        pawn.training.SetWantedRecursive(specialTrainable, checkOn);
                    }
                }
                if (doTooltip)
                {
                    DoSpecialTrainableTooltip(rect, pawn);
                }
            }

            private void DoSpecialTrainableTooltip(Rect rect, Pawn pawn)
            {
                if (!Mouse.IsOver(rect))
                {
                    return;
                }
                TooltipHandler.TipRegion(rect, delegate
                {
                    StringBuilder stringBuilder = new StringBuilder();
                    foreach (TrainableDef specialTrainable in pawn.RaceProps.specialTrainables)
                    {
                        bool visible;
                        AcceptanceReport acceptanceReport = pawn.training.CanAssignToTrain(specialTrainable, out visible);
                        stringBuilder.AppendLineIfNotEmpty();
                        stringBuilder.AppendLine(specialTrainable.LabelCap + "\n\n" + specialTrainable.description);
                        if (!acceptanceReport.Accepted)
                        {
                            stringBuilder.AppendLine().AppendLine(acceptanceReport.Reason);
                        }
                        else if (!specialTrainable.prerequisites.NullOrEmpty())
                        {
                            stringBuilder.AppendLine();
                            foreach (TrainableDef prerequisite in specialTrainable.prerequisites)
                            {
                                if (!pawn.training.HasLearned(prerequisite))
                                {
                                    stringBuilder.AppendLine("TrainingNeedsPrerequisite".Translate(prerequisite.LabelCap));
                                }
                            }
                        }
                    }
                    return stringBuilder.ToString();
                }, (int)(rect.y * 511f + rect.x));
            }

            public override int GetMinWidth(PawnTable table)
            {
                return Mathf.Max(base.GetMinWidth(table), 24);
            }

            public override int GetMaxWidth(PawnTable table)
            {
                return Mathf.Min(base.GetMaxWidth(table), GetMinWidth(table));
            }

            public override int GetMinCellHeight(Pawn pawn)
            {
                return Mathf.Max(base.GetMinCellHeight(pawn), 24);
            }

            public override int Compare(Pawn a, Pawn b)
            {
                return GetValueToCompare(a).CompareTo(GetValueToCompare(b));
            }

            private int GetValueToCompare(Pawn pawn)
            {
                if (pawn.training == null || pawn.RaceProps.specialTrainables.NullOrEmpty())
                {
                    return int.MinValue;
                }
                GetStatus(pawn, out var learned, out var checkOn, out var canTrain, out var visible);
                if (learned)
                {
                    return 4;
                }
                if (!visible)
                {
                    return 0;
                }
                if (!canTrain)
                {
                    return 1;
                }
                if (!checkOn)
                {
                    return 2;
                }
                return 3;
            }

            private static void GetStatus(Pawn pawn, out bool learned, out bool checkOn, out bool canTrain, out bool visible)
            {
                learned = true;
                checkOn = true;
                canTrain = true;
                visible = false;
                foreach (TrainableDef specialTrainable in pawn.RaceProps.specialTrainables)
                {
                    if (!pawn.training.HasLearned(specialTrainable))
                    {
                        learned = false;
                    }
                    if (!pawn.training.GetWanted(specialTrainable))
                    {
                        checkOn = false;
                    }
                    if (!pawn.training.CanAssignToTrain(specialTrainable, out var visible2))
                    {
                        canTrain = false;
                    }
                    if (visible2)
                    {
                        visible = true;
                    }
                }
            }
        }
    }
    ```

### 2.2. AI 决策机制

*   **关键发现**: 我们可以为每个技能创建一个继承自 `ThinkNode_Conditional` 的决策节点，在 `Satisfied` 方法中通过检查 `pawn.training.GetWanted(def)` 来判断玩家是否授权。

## 3. 最终实施蓝图

### 阶段一：核心机制实现 (通用)

*   **1.1: 实现瞬间训练 (`CompInstantTrain.cs`)**: 创建一个 `ThingComp`，在 `PostSpawnSetup` 中调用 `pawn.training.Train(def, null, true)`。
*   **1.2: 引入 Harmony**: 在项目中添加 `0Harmony.dll` 引用并创建 `MainHarmony.cs` 初始化补丁。
*   **1.3: 阻止训练衰减 (`Patch_TrainingTracker_TickRare.cs`)**: 创建一个对 `Pawn_TrainingTracker.TrainingTrackerTickRare` 的前缀补丁，对特殊动物返回 `false`。

### 阶段二：实现“种植” (`Growing`) 功能

*   **2.1: (XML) 创建 `TrainableDef`**: 创建 `Defs/TrainableDefs/ARA_Sowing.xml`，定义 `ARA_Sowing`，并设 `<specialTrainable>true</specialTrainable>`。
*   **2.2: (C#) 创建 AI 决策节点**: 创建 `Source/ArachnaeSwarm/ThinkNode_ConditionalAnimalShouldSow.cs`，并使用静态缓存 Def。
    ```csharp
    using Verse;
    using Verse.AI;

    namespace ArachnaeSwarm
    {
        [DefOf]
        public static class ARA_TrainableDefOf
        {
            public static TrainableDef ARA_Sowing;
            public static TrainableDef ARA_PlantCutting;
            static ARA_TrainableDefOf() { DefOfHelper.EnsureInitializedInCtor(typeof(ARA_TrainableDefOf)); }
        }

        public class ThinkNode_ConditionalAnimalShouldSow : ThinkNode_Conditional
        {
            protected override bool Satisfied(Pawn pawn)
            {
                if (pawn.training == null) return false;
                return pawn.training.HasLearned(ARA_TrainableDefOf.ARA_Sowing) && 
                       pawn.training.GetWanted(ARA_TrainableDefOf.ARA_Sowing);
            }
        }
    }
    ```
*   **2.3: (XML) 关联组件**:
    *   **PawnKindDef**: 在 `comps` 列表中添加 `CompProperties_InstantTrain` 并配置 `trainables`。
    *   **ThingDef**: 在 `specialTrainables` 列表中添加 `<li>ARA_Sowing</li>`。
    *   **ThinkTreeDef**: 添加 `<li Class="ArachnaeSwarm.ThinkNode_ConditionalAnimalShouldSow">` 节点，其子节点为 `<li Class="RimWorld.JobGiver_Work"><workType>Growing</workType></li>`。

### 阶段三：实现“植物割除” (`PlantCutting`) 功能

此阶段完全重复阶段二的模式，仅替换相应的名称 (`ARA_PlantCutting`, `ThinkNode_ConditionalAnimalShouldPlantCut`, `workType: PlantCutting`)。

### 阶段四：最终审查与打包

*   **4.1**: 审查所有代码和 XML。
*   **4.2**: 编译并进行游戏内测试。