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

23 KiB
Raw Blame History

“抱脸虫夺舍”技能实现说明书 (V3 - 最终版)

1. 功能概述

本功能实现了一个名为“阿拉克涅寄生”的特殊技能允许一个特定的“阿拉克涅原虫”抱脸虫Pawn将自己的意识灵魂注入另一个生物的身体从而完全占据并控制它。

核心玩法循环:

  1. 夺舍: 抱脸虫使用技能后,其物理实体消失,其“灵魂”(名字、背景、技能、特性等)将完全覆盖目标的身体。
  2. 成长: 玩家将控制这个新的身体进行游戏,所有获得的经验、技能和记忆都将积累在这个身体上。
  3. 重生: 当被夺舍的身体死亡时抱脸虫的灵魂会带着所有新的成长从尸体中“重生”变回一个独立的、更强大的阿拉克涅原虫Pawn准备寻找下一个宿主。

这是一个高风险、高回报的玩法,允许玩家以一种独特的方式延续一个核心角色的“生命”和成长。

2. 实现流程

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在世界中的位置、姿态和寻路信息。

// 路径: 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是整个功能的核心。它作为“灵魂容器”负责存储抱脸虫的本体并在恰当的时机执行“夺舍”和“重生”的逻辑。

// 路径: 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数据复制相关的复杂逻辑使得其他部分的代码更整洁。

// 路径: 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定义上。

// 路径: 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

<!-- 路径: 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 - 定义技能、种族和身体

这个文件定义了技能本身,以及我们的“阿拉克涅原虫”作为一个完整的生物所需的一切。

<!-- 路径: 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并附有详细的注释解释了每一步的作用和它们之间的关联。