23 KiB
23 KiB
“抱脸虫夺舍”技能实现说明书 (V3 - 最终版)
1. 功能概述
本功能实现了一个名为“阿拉克涅寄生”的特殊技能,允许一个特定的“阿拉克涅原虫”(抱脸虫)Pawn将自己的意识(灵魂)注入另一个生物的身体,从而完全占据并控制它。
核心玩法循环:
- 夺舍: 抱脸虫使用技能后,其物理实体消失,其“灵魂”(名字、背景、技能、特性等)将完全覆盖目标的身体。
- 成长: 玩家将控制这个新的身体进行游戏,所有获得的经验、技能和记忆都将积累在这个身体上。
- 重生: 当被夺舍的身体死亡时,抱脸虫的灵魂会带着所有新的成长,从尸体中“重生”,变回一个独立的、更强大的阿拉克涅原虫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,并附有详细的注释,解释了每一步的作用和它们之间的关联。