diff --git a/Source/Documents/How_SpecialTrainables_Work.md b/Source/Documents/How_SpecialTrainables_Work.md new file mode 100644 index 0000000..826a1ba --- /dev/null +++ b/Source/Documents/How_SpecialTrainables_Work.md @@ -0,0 +1,227 @@ +# 技术文档:RimWorld 中 `specialTrainables` 的工作机制解析 (V6 - 终极证据版) + +## 1. 目标 + +本文档旨在深入剖析 RimWorld 中 `` 标签的底层工作机制,并最终得出一套可复用的“配方”,用于让动物学会在无需训练、无衰减的情况下,执行任何原版已存在的工作。本文档包含所有必要的、完整的原版代码引用作为证据,以确保其自包含性、正确性和健壮性,可供任何人在无上下文的情况下阅读并执行。 + +## 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`,并设 `true`。 +* **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` 列表中添加 `
  • ARA_Sowing
  • `。 + * **ThinkTreeDef**: 添加 `
  • ` 节点,其子节点为 `
  • Growing
  • `。 + +### 阶段三:实现“植物割除” (`PlantCutting`) 功能 + +此阶段完全重复阶段二的模式,仅替换相应的名称 (`ARA_PlantCutting`, `ThinkNode_ConditionalAnimalShouldPlantCut`, `workType: PlantCutting`)。 + +### 阶段四:最终审查与打包 + +* **4.1**: 审查所有代码和 XML。 +* **4.2**: 编译并进行游戏内测试。 \ No newline at end of file