Files
ArachnaeSwarm/Source/Documents/Possession_Implementation_Guide.md
2025-09-04 21:01:54 +08:00

618 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# “抱脸虫夺舍”技能实现说明书 (V3 - 最终版)
## 1. 功能概述
本功能实现了一个名为“阿拉克涅寄生”的特殊技能允许一个特定的“阿拉克涅原虫”抱脸虫Pawn将自己的意识灵魂注入另一个生物的身体从而完全占据并控制它。
**核心玩法循环:**
1. **夺舍**: 抱脸虫使用技能后,其物理实体消失,其“灵魂”(名字、背景、技能、特性等)将完全覆盖目标的身体。
2. **成长**: 玩家将控制这个新的身体进行游戏,所有获得的经验、技能和记忆都将积累在这个身体上。
3. **重生**: 当被夺舍的身体死亡时抱脸虫的灵魂会带着所有新的成长从尸体中“重生”变回一个独立的、更强大的阿拉克涅原虫Pawn准备寻找下一个宿主。
这是一个高风险、高回报的玩法,允许玩家以一种独特的方式延续一个核心角色的“生命”和成长。
## 2. 实现流程
```mermaid
graph TD
A[抱脸虫] -- 使用“阿拉克涅寄生”技能 --> B(目标Pawn);
B -- 添加 Hediff_Possession --> C{执行灵魂覆盖};
C -- 1. 存储抱脸虫Pawn的完整数据 --> D[Hediff容器];
C -- 2. 将抱脸虫的“灵魂”数据覆盖到目标身上 --> E[被夺舍的身体];
D -- 3. 抱脸虫物理实体消失 --> F([Vanish]);
E -- 玩家控制,积累经验和记忆 --> E;
E -- 受到致命伤害 --> G{身体死亡};
G -- 触发Hediff.Notify_PawnDied --> H{反向同步成长};
D -- 获取存储的抱脸虫数据 --> H;
E -- 获取身体上的成长数据 --> H;
H -- 将成长更新到抱脸虫数据上 --> I[更新后的抱脸虫];
I -- 从Hediff中释放 --> J(更强大的抱脸虫重生);
J -- 等待下一次夺舍 --> A;
```
---
## 3. 代码详解
### 3.1 `CompAbilityEffect_Possess.cs` - 技能效果的起点
这是技能被使用时第一个被调用的C#文件。它的职责是创建`Hediff_Possession`并将其附加到目标身上,从而启动整个夺舍流程。
## 3. 最终数据迁移规范 (Final Data Transfer Specification)
通过对真实存档文件 (`Human.xml`) 的深度分析,我们最终确定了“灵魂”与“肉体”的数据边界。`PawnDataUtility.TransferSoul` 方法将严格遵循以下规范进行数据迁移:
### 3.1 必须复制的“灵魂”数据
这些数据定义了Pawn的身份、经历、思想和核心能力将**完全从抱脸虫(源)复制到宿主(目标)**。
- **核心身份 (`Name`, `Story`, `Faction`)**:
- `Name`: 姓名与昵称。
- `Story`: 童年和成年背景 (`Childhood`, `Adulthood`)。
- `Traits`: 所有特性。
- `Faction`: 所属阵营。
- **成长与经历 (`Skills`, `Records`)**:
- `Skills`: 所有技能的等级、经验和热情。
- `Records`: 全部生平记录 (如击杀数、建造数等)。
- **思想与设定 (`Needs`, `WorkSettings`, etc.)**:
- `Needs`: 主要是指`thoughts.memories` (思想和记忆)。
- `WorkSettings`: 工作优先级。
- `Timetable`: 时间表。
- `PlayerSettings`: 玩家设定 (如医疗策略)。
- `Ownership`: 对床、王座等的所有权。
- `Outfits` & `Drugs`: 穿着和药物策略。
- `FoodRestriction`: 食物策略。
- **DLC核心数据 (`Ideo`, `Royalty`)**:
- `Ideo`: 完整的信仰体系。
- `Royalty`: 完整的贵族系统,包括头衔、恩惠、许可、灵能和相关技能 (`abilities`)。
- **社交 (`Relations`)**:
- 将采用**简化处理**:清空目标的旧关系,然后只复制源的**非亲属**直接关系 (如朋友、对手、爱人)。这可以避免破坏家族树。
### 3.2 必须保留的“肉体”数据
这些数据属于物理身体的范畴,在夺舍过程中将**完全保留宿主原有的数据**,不进行任何复制。
- **健康与生理 (`Health`, `Age`)**:
- `Health`: 所有伤口、疤痕、疾病和植入物。
- `Age`: 生物年龄和时间年龄。
- **外观与基因 (`Style`, `Genes`, `BodyType`)**:
- `Style`: 发型、胡须、纹身。
- `Genes`: 所有内生和异种基因。
- `BodyType`, `HeadType`, `HairColor`: 身体类型、头型和发色。
- **装备与物品 (`Apparel`, `Equipment`, `Inventory`)**:
- `Apparel`: 身上穿着的衣物。
- `Equipment`: 手中持有的装备。
- `Inventory`: 物品栏中的物品。
- **物理状态 (`Position`, `Stances`, `Pather`)**:
- Pawn在世界中的位置、姿态和寻路信息。
---
```csharp
// 路径: Source/ArachnaeSwarm/Possession/CompAbilityEffect_Possess.cs
using RimWorld;
using Verse;
namespace ArachnaeSwarm.Possession
{
// 继承自CompAbilityEffect这是所有技能效果组件的基类
public class CompAbilityEffect_Possess : CompAbilityEffect
{
// 当技能成功施放时游戏会调用这个Apply方法
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
{
base.Apply(target, dest); // 调用基类方法,确保标准流程执行
// 获取施法者 (我们的抱脸虫)
Pawn caster = this.parent.pawn;
// 获取目标Pawn
Pawn targetPawn = target.Pawn;
// 安全检查如果目标不是一个Pawn则直接返回
if (targetPawn == null)
{
return;
}
// TODO: 在此可以添加更多的限制条件,例如:
// 1. 不能夺舍机械体
// if (targetPawn.RaceProps.IsMechanoid) { ... }
// 2. 不能夺舍已经被夺舍的目标
// if (targetPawn.health.hediffSet.HasHediff(HediffDef.Named("ARA_Possession"))) { ... }
// 步骤1: 创建Hediff实例
// HediffMaker.MakeHediff会根据XML定义创建一个新的Hediff对象
Hediff_Possession hediff = (Hediff_Possession)HediffMaker.MakeHediff(HediffDef.Named("ARA_Possession"), targetPawn);
// 步骤2: 注入施法者灵魂
// 在Hediff被正式添加到目标身上之前将施法者的引用传递进去。
// 这是关键一步确保Hediff在执行PostAdd逻辑时能知道谁是施法者。
hediff.SetCaster(caster);
// 步骤3: 将Hediff添加到目标身上
// 这会触发Hediff_Possession类中的PostAdd方法从而启动真正的夺舍逻辑。
targetPawn.health.AddHediff(hediff);
}
}
}
```
### 3.2 `Hediff_Possession.cs` - 夺舍与重生的核心
这个Hediff是整个功能的核心。它作为“灵魂容器”负责存储抱脸虫的本体并在恰当的时机执行“夺舍”和“重生”的逻辑。
```csharp
// 路径: Source/ArachnaeSwarm/Possession/Hediff_Possession.cs
using System.Collections.Generic;
using RimWorld;
using Verse;
namespace ArachnaeSwarm.Possession
{
// 继承HediffWithComps以支持组件并实现IThingHolder接口来表明自己是容器
public class Hediff_Possession : HediffWithComps, IThingHolder
{
// --- 核心字段 ---
private ThingOwner innerContainer; // 实际存储灵魂抱脸虫Pawn的容器
private Pawn originalCaster; // 临时保存对原始施法者的引用
// --- 构造与属性 ---
public Hediff_Possession()
{
// 初始化容器。LookMode.Deep是关键它能确保Pawn的所有数据都被完整保存。
this.innerContainer = new ThingOwner<Thing>(this, false, LookMode.Deep);
}
// 提供一个方便的只读属性来获取容器中存储的Pawn
public Pawn StoredCasterPawn => innerContainer.Count > 0 ? innerContainer[0] as Pawn : null;
// --- IThingHolder 接口实现 ---
public IThingHolder ParentHolder => this.pawn; // 容器的父级就是持有该Hediff的Pawn
public void GetChildHolders(List<IThingHolder> outChildren)
{
ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, this.GetDirectlyHeldThings());
}
public ThingOwner GetDirectlyHeldThings()
{
return innerContainer;
}
// --- 核心逻辑 ---
// 当Hediff被成功添加到目标身上后此方法被自动调用
public override void PostAdd(DamageInfo? dinfo)
{
base.PostAdd(dinfo);
if (this.originalCaster == null)
{
Log.Error("Hediff_Possession was added without an original caster.");
return;
}
// 1. 存储灵魂将施法者的Pawn对象完整地存入容器
this.innerContainer.TryAdd(this.originalCaster);
// 2. 灵魂覆盖:调用工具类,执行数据迁移
PawnDataUtility.TransferSoul(this.originalCaster, this.pawn);
// 3. 销毁施法者的物理实体,因为它现在“活”在目标的身体里了
if (!this.originalCaster.Destroyed)
{
this.originalCaster.Destroy(DestroyMode.Vanish);
}
Log.Message($"{this.pawn.LabelShort} has been possessed by {StoredCasterPawn.LabelShort}!");
}
// 当持有此Hediff的Pawn即宿主死亡时此方法被自动调用
public override void Notify_PawnDied(DamageInfo? dinfo, Hediff culprit = null)
{
base.Notify_PawnDied(dinfo, culprit);
Pawn deadBody = this.pawn;
Pawn storedCaster = this.StoredCasterPawn;
if (storedCaster == null)
{
Log.Error("Possessed pawn died, but no caster soul was found inside.");
return;
}
Log.Message($"Host {deadBody.LabelShort} died. Transferring experience back to {storedCaster.LabelShort} and ejecting.");
// 1. 灵魂更新:反向调用工具类,将宿主身体上的成长同步回抱脸虫的灵魂
PawnDataUtility.TransferSoul(deadBody, storedCaster);
// 2. 重生:将更新后的抱脸虫灵魂从容器中释放到地图上
this.EjectContents();
}
// --- 公共方法 ---
// 由CompAbilityEffect调用用于在添加Hediff前设置施法者
public void SetCaster(Pawn caster)
{
this.originalCaster = caster;
}
// 将容器内的东西(抱脸虫)扔到地图上
public void EjectContents()
{
if (StoredCasterPawn != null)
{
this.innerContainer.TryDropAll(this.pawn.Position, this.pawn.Map, ThingPlaceMode.Near);
}
}
// --- 存档/读档 ---
public override void ExposeData()
{
base.ExposeData();
// Scribe_Deep是关键确保容器内的Pawn被深度保存
Scribe_Deep.Look(ref innerContainer, "innerContainer", this);
// 保存对原始施法者的引用(虽然它很快会被销毁,但以防万一)
Scribe_References.Look(ref originalCaster, "originalCaster");
}
}
}
```
### 3.3 `PawnDataUtility.cs` - “灵魂”数据迁移的执行者
这是一个静态工具类集中处理所有与Pawn数据复制相关的复杂逻辑使得其他部分的代码更整洁。
```csharp
// 路径: Source/ArachnaeSwarm/Possession/PawnDataUtility.cs
using System.Collections.Generic;
using RimWorld;
using Verse;
namespace ArachnaeSwarm.Possession
{
public static class PawnDataUtility
{
// 核心方法将soulSource的“灵魂”数据转移到bodyTarget上
public static void TransferSoul(Pawn soulSource, Pawn bodyTarget)
{
if (soulSource == null || bodyTarget == null)
{
Log.Error("Cannot transfer soul: source or target is null.");
return;
}
Log.Message($"Beginning soul transfer from {soulSource.LabelShort} to {bodyTarget.LabelShort}.");
// --- 1. 核心身份数据 ---
// 姓名
bodyTarget.Name = soulSource.Name;
// 故事 (背景和特性)
bodyTarget.story.Childhood = soulSource.story.Childhood;
bodyTarget.story.Adulthood = soulSource.story.Adulthood;
// 先清空目标的所有特性,再逐一添加源的特性
bodyTarget.story.traits.allTraits.Clear();
foreach (Trait trait in soulSource.story.traits.allTraits)
{
bodyTarget.story.traits.GainTrait(trait);
}
// 技能
// 同样地,清空后逐一添加
bodyTarget.skills.skills.Clear();
foreach (SkillRecord skill in soulSource.skills.skills)
{
SkillRecord newSkill = new SkillRecord(bodyTarget, skill.def)
{
levelInt = skill.levelInt,
xpSinceLastLevel = skill.xpSinceLastLevel,
passion = skill.passion
};
bodyTarget.skills.skills.Add(newSkill);
}
// 阵营
if (bodyTarget.Faction != soulSource.Faction)
{
bodyTarget.SetFaction(soulSource.Faction, soulSource);
}
// --- 2. 思想、社交和设定 ---
// 思想和记忆
// 清空目标的记忆,然后复制源的记忆
if (bodyTarget.needs.mood?.thoughts?.memories != null)
{
bodyTarget.needs.mood.thoughts.memories.Memories.Clear();
}
if (soulSource.needs.mood?.thoughts?.memories != null)
{
foreach (Thought_Memory memory in soulSource.needs.mood.thoughts.memories.Memories)
{
bodyTarget.needs.mood.thoughts.memories.TryGainMemory(memory);
}
}
// 工作设置
if (soulSource.workSettings != null && bodyTarget.workSettings != null)
{
bodyTarget.workSettings.EnableAndInitialize();
foreach (WorkTypeDef workDef in DefDatabase<WorkTypeDef>.AllDefs)
{
bodyTarget.workSettings.SetPriority(workDef, soulSource.workSettings.GetPriority(workDef));
}
}
// 时间表
if (soulSource.timetable != null && bodyTarget.timetable != null)
{
bodyTarget.timetable.times = new List<TimeAssignmentDef>(soulSource.timetable.times);
}
// 社交关系 (简化处理)
// 警告: 直接复制关系可能很危险,这里采用清空再添加直接关系的方式
if (soulSource.relations != null && bodyTarget.relations != null)
{
bodyTarget.relations.ClearAllRelations();
foreach (DirectPawnRelation relation in soulSource.relations.DirectRelations)
{
bodyTarget.relations.AddDirectRelation(relation.def, relation.otherPawn);
}
}
// 访客/囚犯状态
if (soulSource.guest != null && bodyTarget.guest != null)
{
// 使用游戏提供的标准方法来设置,而不是直接赋值
bodyTarget.guest.SetGuestStatus(soulSource.guest.HostFaction, soulSource.guest.GuestStatus);
if (soulSource.guest.IsPrisoner)
{
bodyTarget.guest.SetExclusiveInteraction(soulSource.guest.ExclusiveInteractionMode);
}
bodyTarget.guest.joinStatus = soulSource.guest.joinStatus;
}
// --- 3. 收尾工作 ---
// 强制刷新Pawn的渲染缓存确保外观如名字能立刻更新
bodyTarget.Drawer.renderer.SetAllGraphicsDirty();
Log.Message("Soul transfer complete.");
}
}
}
```
### 3.4 `CompProperties_AbilityPossess.cs` - 技能属性类
这是一个简单的属性类,用于将我们的技能效果组件(`CompAbilityEffect_Possess`连接到XML定义上。
```csharp
// 路径: Source/ArachnaeSwarm/Possession/CompProperties_AbilityPossess.cs
using RimWorld;
namespace ArachnaeSwarm.Possession
{
// CompProperties类用于在XML中配置Comp组件的参数
public class CompProperties_AbilityPossess : CompProperties_AbilityEffect
{
public CompProperties_AbilityPossess()
{
// 将这个属性类与我们的技能效果实现类关联起来
this.compClass = typeof(CompAbilityEffect_Possess);
}
}
}
```
---
## 4. XML 定义详解
### 4.1 `ARA_Hediffs_Possession.xml` - 定义Hediff
```xml
<!-- 路径: 1.6/1.6/Defs/HediffDefs/ARA_Hediffs_Possession.xml -->
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<HediffDef>
<!-- 内部唯一的定义名 -->
<defName>ARA_Possession</defName>
<!-- 游戏中显示的标签 -->
<label>阿拉克涅原虫寄生</label>
<!-- 鼠标悬浮时的描述 -->
<description>这个生物的身体正被另一个实体所控制。</description>
<!-- 关键将这个XML定义与我们的C#实现类关联起来 -->
<hediffClass>ArachnaeSwarm.Possession.Hediff_Possession</hediffClass>
<!-- 其他标准Hediff属性 -->
<isBad>false</isBad>
<scenarioCanAdd>false</scenarioCanAdd>
<maxSeverity>1.0</maxSeverity>
<stages>
<li>
<label>被寄生</label>
</li>
</stages>
</HediffDef>
</Defs>
```
### 4.2 `ARA_Possession_Defs.xml` - 定义技能、种族和身体
这个文件定义了技能本身,以及我们的“阿拉克涅原虫”作为一个完整的生物所需的一切。
```xml
<!-- 路径: 1.6/1.6/Defs/Misc/ARA_Possession_Defs.xml -->
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<!-- ==================== 技能定义 ==================== -->
<AbilityDef>
<defName>ARA_Ability_Possess</defName>
<label>阿拉克涅寄生</label>
<description>将你的意识注入另一个生物的身体,完全占据它。</description>
<iconPath>UI/Abilities/Possess</iconPath> <!-- TODO: 需要一张图标 -->
<cooldownTicks>600</cooldownTicks>
<verbProperties>
<verbClass>Verb_CastAbility</verbClass>
<warmupTime>1.5</warmupTime>
<range>5.9</range>
<targetParams>
<canTargetPawns>true</canTargetPawns>
<canTargetBuildings>false</canTargetBuildings>
<canTargetSelf>false</canTargetSelf>
<canTargetLocations>false</canTargetLocations>
</targetParams>
</verbProperties>
<comps>
<!-- 关键将这个技能与我们的C#属性类关联起来 -->
<li Class="ArachnaeSwarm.Possession.CompProperties_AbilityPossess"/>
</comps>
</AbilityDef>
<!-- ==================== 生物AI定义 (可选) ==================== -->
<ThinkTreeDef>
<defName>ARA_Facehugger</defName>
<insertTag>Humanlike_PostMentalState</insertTag>
<insertPriority>100</insertPriority>
<thinkRoot Class="ThinkNode_Priority">
<subNodes>
<!-- 在这里可以为抱脸虫添加自定义AI例如当它空闲时自动寻找宿主 -->
</subNodes>
</thinkRoot>
</ThinkTreeDef>
<!-- ==================== 生物类型定义 ==================== -->
<PawnKindDef>
<defName>ARA_Facehugger</defName>
<label>阿拉克涅原虫</label>
<!-- 关联下面的种族定义 -->
<race>ARA_FacehuggerRace</race>
<combatPower>25</combatPower>
<lifeStages>
<li>
<bodyGraphicData>
<texPath>Things/Pawn/Animal/ARA_Facehugger</texPath> <!-- TODO: 需要贴图 -->
<drawSize>0.8</drawSize>
</bodyGraphicData>
<dessicatedBodyGraphicData>
<texPath>Things/Pawn/Animal/Dessicated/CritterDessicated</texPath>
<drawSize>0.8</drawSize>
</dessicatedBodyGraphicData>
</li>
</lifeStages>
<aiThinkTree>ARA_Facehugger</aiThinkTree>
<abilities>
<!-- 赋予该生物我们的夺舍技能 -->
<li>ARA_Ability_Possess</li>
</abilities>
</PawnKindDef>
<!-- ==================== 种族定义 ==================== -->
<ThingDef ParentName="AnimalThingBase">
<defName>ARA_FacehuggerRace</defName>
<label>阿拉克涅原虫</label>
<description>一种小型的、脆弱的寄生生物,其唯一的生存目的就是寻找并占据一个更强大的宿主。它通过将自己的意识注入目标来完成这一过程。</description>
<statBases>
<MoveSpeed>4.0</MoveSpeed>
<MarketValue>50</MarketValue>
<ComfyTemperatureMin>-10</ComfyTemperatureMin>
<ComfyTemperatureMax>50</ComfyTemperatureMax>
</statBases>
<tools>
<li>
<label>tiny claws</label>
<capacities>
<li>Scratch</li>
</capacities>
<power>2</power>
<cooldownTime>1.5</cooldownTime>
</li>
</tools>
<race>
<thinkTreeMain>Animal</thinkTreeMain>
<!-- 关联下面的身体定义 -->
<body>ARA_FacehuggerBody</body>
<baseBodySize>0.2</baseBodySize>
<baseHealthScale>0.3</baseHealthScale>
<baseHungerRate>0.1</baseHungerRate>
<lifeStageAges>
<li>
<def>AnimalAdult</def>
<minAge>0</minAge>
</li>
</lifeStageAges>
</race>
</ThingDef>
<!-- ==================== 身体结构定义 ==================== -->
<BodyDef>
<defName>ARA_FacehuggerBody</defName>
<label>facehugger</label>
<corePart>
<def>Body</def>
<height>20</height>
<depth>20</depth>
<parts>
<li>
<def>Head</def>
<coverage>0.3</coverage>
<parts>
<li>
<def>Skull</def>
<coverage>0.2</coverage>
<depth>Inside</depth>
<parts>
<li>
<def>Brain</def>
<coverage>0.1</coverage>
<depth>Inside</depth>
</li>
</parts>
</li>
<li>
<def>Eye</def>
<customLabel>left eye</customLabel>
<coverage>0.07</coverage>
</li>
<li>
<def>Eye</def>
<customLabel>right eye</customLabel>
<coverage>0.07</coverage>
</li>
</parts>
</li>
<li>
<def>Leg</def>
<customLabel>front left leg</customLabel>
<coverage>0.1</coverage>
</li>
<li>
<def>Leg</def>
<customLabel>front right leg</customLabel>
<coverage>0.1</coverage>
</li>
<li>
<def>Leg</def>
<customLabel>rear left leg</customLabel>
<coverage>0.1</coverage>
</li>
<li>
<def>Leg</def>
<customLabel>rear right leg</customLabel>
<coverage>0.1</coverage>
</li>
</parts>
</corePart>
</BodyDef>
</Defs>
```
---
这份详尽的文档现在包含了我们所有的最终代码和XML并附有详细的注释解释了每一步的作用和它们之间的关联。