From 760ddee0cc824a8a72363f3d97732bd7e1256ead Mon Sep 17 00:00:00 2001 From: "ProjectKoi-Kalo\\Kalo" Date: Thu, 4 Sep 2025 20:40:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1.6/1.6/Assemblies/ArachnaeSwarm.dll | Bin 62976 -> 68608 bytes .../HediffDefs/ARA_Hediffs_Possession.xml | 19 + 1.6/1.6/Defs/Misc/ARA_Possession_Defs.xml | 166 ++++ Source/ArachnaeSwarm/ArachnaeSwarm.csproj | 6 + .../Possession/CompAbilityEffect_Possess.cs | 33 + .../CompProperties_AbilityPossess.cs | 12 + .../Possession/Hediff_Possession.cs | 90 ++ .../Possession/PawnDataUtility.cs | 106 +++ Source/Documents/Human.xml | 787 ++++++++++++++++++ .../Documents/PawnStorageInHediff_Analysis.md | 188 +++++ .../Possession_Implementation_Guide.md | 569 +++++++++++++ 11 files changed, 1976 insertions(+) create mode 100644 1.6/1.6/Defs/HediffDefs/ARA_Hediffs_Possession.xml create mode 100644 1.6/1.6/Defs/Misc/ARA_Possession_Defs.xml create mode 100644 Source/ArachnaeSwarm/Possession/CompAbilityEffect_Possess.cs create mode 100644 Source/ArachnaeSwarm/Possession/CompProperties_AbilityPossess.cs create mode 100644 Source/ArachnaeSwarm/Possession/Hediff_Possession.cs create mode 100644 Source/ArachnaeSwarm/Possession/PawnDataUtility.cs create mode 100644 Source/Documents/Human.xml create mode 100644 Source/Documents/PawnStorageInHediff_Analysis.md create mode 100644 Source/Documents/Possession_Implementation_Guide.md diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 84e0e9cb4a40df643f471930bea70fa2b2f515d4..5157529b235c0f00a15906fb4eaa74818da7ae79 100644 GIT binary patch delta 24273 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

%l6co0_`%1zVpuK_y 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 E03`Pw + + + + ARA_Possession + + 这个生物的身体正被另一个实体所控制。 + ArachnaeSwarm.Possession.Hediff_Possession + false + false + 1.0 + +

  • + +
  • + + + + \ No newline at end of file diff --git a/1.6/1.6/Defs/Misc/ARA_Possession_Defs.xml b/1.6/1.6/Defs/Misc/ARA_Possession_Defs.xml new file mode 100644 index 0000000..40efc0a --- /dev/null +++ b/1.6/1.6/Defs/Misc/ARA_Possession_Defs.xml @@ -0,0 +1,166 @@ + + + + + + + ARA_Ability_Possess + + 将你的意识注入另一个生物的身体,完全占据它。 + UI/Abilities/Possess + 600 + + Verb_CastAbility + 1.5 + 5.9 + + true + false + false + false + + + +
  • + + + + + ARA_Facehugger + Humanlike_PostMentalState + 100 + + + + +
  • + Possess + +
  • + + + + + + + + ARA_Facehugger + + ARA_FacehuggerRace + 25 + +
  • + + Things/Pawn/Animal/ARA_Facehugger + 0.8 + + + Things/Pawn/Animal/Dessicated/CritterDessicated + 0.8 + +
  • +
    + ARA_Facehugger + +
  • ARA_Ability_Possess
  • +
    +
    + + + + + ARA_FacehuggerRace + + 一种小型的、脆弱的寄生生物,其唯一的生存目的就是寻找并占据一个更强大的宿主。它通过将自己的意识注入目标来完成这一过程。 + + 4.0 + 50 + -10 + 50 + + +
  • + + +
  • Scratch
  • + + 2 + 1.5 + +
    + + Animal + ARA_FacehuggerBody + 0.2 + 0.3 + 0.1 + +
  • + AnimalAdult + 0 +
  • +
    +
    +
    + + + ARA_FacehuggerBody + + + Body + 20 + 20 + +
  • + Head + 0.3 + +
  • + Skull + 0.2 + Inside + +
  • + Brain + 0.1 + Inside +
  • +
    + +
  • + Eye + left eye + 0.07 +
  • +
  • + Eye + right eye + 0.07 +
  • + + +
  • + Leg + front left leg + 0.1 +
  • +
  • + Leg + front right leg + 0.1 +
  • +
  • + Leg + rear left leg + 0.1 +
  • +
  • + Leg + rear right leg + 0.1 +
  • + +
    +
    + +
    \ No newline at end of file diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index fa02310..39029b2 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -112,6 +112,12 @@ + + + + + + diff --git a/Source/ArachnaeSwarm/Possession/CompAbilityEffect_Possess.cs b/Source/ArachnaeSwarm/Possession/CompAbilityEffect_Possess.cs new file mode 100644 index 0000000..f0e2b8b --- /dev/null +++ b/Source/ArachnaeSwarm/Possession/CompAbilityEffect_Possess.cs @@ -0,0 +1,33 @@ +using RimWorld; +using Verse; + +namespace ArachnaeSwarm.Possession +{ + public class CompAbilityEffect_Possess : CompAbilityEffect + { + public override void Apply(LocalTargetInfo target, LocalTargetInfo dest) + { + base.Apply(target, dest); + + Pawn caster = this.parent.pawn; + Pawn targetPawn = target.Pawn; + + if (targetPawn == null) + { + return; + } + + // Optional: Add checks here. E.g., cannot possess mechanical, already possessed, etc. + // if (targetPawn.RaceProps.IsMechanoid) + // { + // Messages.Message("Cannot possess a mechanoid.", MessageTypeDefOf.RejectInput); + // return; + // } + + Hediff_Possession hediff = (Hediff_Possession)HediffMaker.MakeHediff(HediffDef.Named("ARA_Possession"), targetPawn); + hediff.SetCaster(caster); + + targetPawn.health.AddHediff(hediff); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Possession/CompProperties_AbilityPossess.cs b/Source/ArachnaeSwarm/Possession/CompProperties_AbilityPossess.cs new file mode 100644 index 0000000..7cea6a7 --- /dev/null +++ b/Source/ArachnaeSwarm/Possession/CompProperties_AbilityPossess.cs @@ -0,0 +1,12 @@ +using RimWorld; + +namespace ArachnaeSwarm.Possession +{ + public class CompProperties_AbilityPossess : CompProperties_AbilityEffect + { + public CompProperties_AbilityPossess() + { + this.compClass = typeof(CompAbilityEffect_Possess); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Possession/Hediff_Possession.cs b/Source/ArachnaeSwarm/Possession/Hediff_Possession.cs new file mode 100644 index 0000000..7054207 --- /dev/null +++ b/Source/ArachnaeSwarm/Possession/Hediff_Possession.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using RimWorld; +using Verse; + +namespace ArachnaeSwarm.Possession +{ + public class Hediff_Possession : HediffWithComps, IThingHolder + { + private ThingOwner innerContainer; + private Pawn originalCaster; + + public Hediff_Possession() + { + this.innerContainer = new ThingOwner(this, false, LookMode.Deep); + } + + public Pawn StoredCasterPawn => innerContainer.Count > 0 ? innerContainer[0] as Pawn : null; + + public IThingHolder ParentHolder => this.pawn; + + public void GetChildHolders(List outChildren) + { + ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, this.GetDirectlyHeldThings()); + } + + public ThingOwner GetDirectlyHeldThings() + { + return innerContainer; + } + + public override void PostAdd(DamageInfo? dinfo) + { + base.PostAdd(dinfo); + + if (this.originalCaster == null) + { + Log.Error("Hediff_Possession was added without an original caster."); + return; + } + + this.innerContainer.TryAdd(this.originalCaster); + PawnDataUtility.TransferSoul(this.originalCaster, this.pawn); + + if (!this.originalCaster.Destroyed) + { + this.originalCaster.Destroy(DestroyMode.Vanish); + } + + Log.Message($"{this.pawn.LabelShort} has been possessed by {StoredCasterPawn.LabelShort}!"); + } + + 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."); + PawnDataUtility.TransferSoul(deadBody, storedCaster); + this.EjectContents(); + } + + 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.Look(ref innerContainer, "innerContainer", this); + Scribe_References.Look(ref originalCaster, "originalCaster"); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Possession/PawnDataUtility.cs b/Source/ArachnaeSwarm/Possession/PawnDataUtility.cs new file mode 100644 index 0000000..6b78f42 --- /dev/null +++ b/Source/ArachnaeSwarm/Possession/PawnDataUtility.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using RimWorld; +using Verse; + +namespace ArachnaeSwarm.Possession +{ + public static class PawnDataUtility + { + 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}."); + + // Name + bodyTarget.Name = soulSource.Name; + + // Story (Backstory and Traits) + 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); + } + + // Skills + 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); + } + + // Faction + if (bodyTarget.Faction != soulSource.Faction) + { + bodyTarget.SetFaction(soulSource.Faction, soulSource); + } + + // Thoughts and Memories + 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); + } + } + + // Work Settings + if (soulSource.workSettings != null && bodyTarget.workSettings != null) + { + bodyTarget.workSettings.EnableAndInitialize(); + foreach (WorkTypeDef workDef in DefDatabase.AllDefs) + { + bodyTarget.workSettings.SetPriority(workDef, soulSource.workSettings.GetPriority(workDef)); + } + } + + // Timetable + if (soulSource.timetable != null && bodyTarget.timetable != null) + { + bodyTarget.timetable.times = new List(soulSource.timetable.times); + } + + // Social Relations + if (soulSource.relations != null && bodyTarget.relations != null) + { + bodyTarget.relations.ClearAllRelations(); + foreach (DirectPawnRelation relation in soulSource.relations.DirectRelations) + { + bodyTarget.relations.AddDirectRelation(relation.def, relation.otherPawn); + } + } + + // Guest status + 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; + } + + // Refresh the UI and game state to reflect the changes. + bodyTarget.Drawer.renderer.SetAllGraphicsDirty(); + + Log.Message("Soul transfer complete."); + } + } +} \ No newline at end of file diff --git a/Source/Documents/Human.xml b/Source/Documents/Human.xml new file mode 100644 index 0000000..2879174 --- /dev/null +++ b/Source/Documents/Human.xml @@ -0,0 +1,787 @@ + + Human + 2 + Human846 + 0 + (146, 0, 131) + 1 + Faction_18 + + 0 + -1 + True + null + -9999999 + + + + + +
  • base
  • +
  • hair
  • +
  • skin
  • +
  • skinBase
  • +
  • tattoo
  • +
  • favorite
  • +
  • ideo
  • +
  • mech
  • +
    + +
  • + RGBA(1.000, 1.000, 1.000, 1.000) + RGBA(1.000, 1.000, 1.000, 1.000) +
  • +
  • + RGBA(0.343, 0.310, 0.288, 1.000) +
  • +
  • + RGBA(1.000, 0.937, 0.788, 1.000) +
  • +
  • + RGBA(1.000, 0.937, 0.788, 1.000) +
  • +
  • + RGBA(1.000, 0.937, 0.788, 0.800) +
  • +
  • + RGBA(0.890, 0.451, 1.000, 1.000) + RGBA(0.890, 0.451, 1.000, 1.000) +
  • +
  • + RGBA(0.600, 0.500, 0.900, 1.000) + RGBA(0.549, 0.458, 0.824, 1.000) +
  • +
  • + RGBA(0.000, 0.737, 0.847, 1.000) +
  • +
    +
    + + + + + 0 + 0 + 0 + 0 +
    + Colonist + Female + + Yue + Moon + Ren + + null + + null + null + null + null + null + (0, 0, 0) + + + + + Idle + 6673 + -99999 + -99999 + True + -99999 + + + + + + + + + + + (-1000, -1000, -1000) + + -99999 + + + + + + + + + + + 0 + 16777 + + + + null + null + null + null + null + GotoWander + 221 + (156, 0, 116) + + + + 1576 + True + + Walk + Humanlike + -1 + null + null + -1 + -1672709817 + + + 0 + -203 + 1576 + null + + + + + -1 + + + + True + + + + + + + + + + True + + + + + + + +
  • + Thing_Human846_0_Smash + (0, 0, 0) + (0, 0, 0) + -999999 + True +
  • +
  • + Thing_Human846_1_Smash + (0, 0, 0) + (0, 0, 0) + -999999 + True +
  • +
  • + Thing_Human846_2_Bite + (0, 0, 0) + (0, 0, 0) + -999999 + True +
  • +
  • + Thing_Human846_3_Smash + (0, 0, 0) + (0, 0, 0) + -999999 + True +
  • +
    +
    + + + + + + + null + + + + + (147, 0, 130) + 48 + 50 + OnCell + 4 + 1777 + 1779 + (156, 0, 116) + + + + 1 + + + + + + +
  • + Apparel_Pants + Apparel_Pants847 + 130 + 1 + Synthread + + -1 + Normal + null + True + +
  • +
  • + Apparel_Parka + Apparel_Parka848 + 235 + 1 + Synthread + + -1 + Normal + null + True + +
  • +
    +
    + + 111 +
    + + Fat + Elisabeth + RGBA(0.343, 0.310, 0.288, 1.000) + + +
  • + SpeedOffset + null + -1 + null +
  • +
  • + Jealous + null + null +
  • +
    +
    + Ren + LightPurple + Female_AveragePointy + WarRefugee51 + MedievalMinstrel95 +
    + + + + + null + + + + + + 139350193 + -297730916 + 1 + 9223372036854775807 + 141208416 + + + + + + + + + + + + + + + +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 1840
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
    +
    + null +
    + + + + + + + + + Filth_Sand + +
  • + Filth_Sand + Filth_Sand14506 + + -1 +
  • +
    +
    + + null + + + + +
  • + Mood + 0.586400032 + + + +
  • + CrashedTogether + null + Thing_Human849 + 1800 + 25 +
  • +
  • + CrashedTogether + null + Thing_Human852 + 1800 + 25 +
  • +
  • + NewColonyOptimism + null + null + 1800 +
  • +
  • + Chitchat + null + Thing_Human852 + 0.773389459 + 1650 + 0.510437071 +
  • + + + + + 1711 + 1711 + + +
  • + Food + 0.751999915 + 1711 +
  • +
  • + Rest + 0.879273593 +
  • +
  • + Joy + 0.513921499 + + +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • + + + + +
  • False
  • +
  • False
  • +
  • False
  • +
  • False
  • +
  • False
  • +
  • False
  • +
  • False
  • +
  • False
  • +
  • False
  • +
  • False
  • +
    +
    + +
  • + Beauty + 0.442400098 +
  • +
  • + Comfort + 0.47119987 +
  • +
  • + Outdoors + 1 +
  • +
  • + DrugDesire + 0.5 +
  • +
  • + RoomSize + 1 +
  • +
    +
    + + null + null + JoinAsColonist + MaintainOnly + NoInteraction + (-1000, -1000, -1000) + -1 + null + False + + + + + + + + + + + + + + + + + + + + + + + + +
  • + ExSpouse + Thing_Human849 +
  • +
  • + Parent + Thing_Human853 +
  • +
  • + Parent + Thing_Human857 +
  • +
    + + null + + + + + -1 +
    + + True + + + + null + null + null + null + null + + + Chitchat + 121 + Chitchat + + + +
  • + Shooting + 3 +
  • +
  • + Melee + 5 +
  • +
  • + Construction + 2 +
  • +
  • + Mining + 2 +
  • +
  • + Cooking + 7 +
  • +
  • + Plants + 1 +
  • +
  • + Animals + 3 + Minor +
  • +
  • + Crafting + 3 +
  • +
  • + Artistic + 5 + Minor +
  • +
  • + Medicine + 2 + Minor +
  • +
  • + Social + 7 + Major +
  • +
  • + Intellectual +
  • +
    + -1 +
    + + + + + Ideo_10 + + 0.605378687 + + + + + +
  • 3
  • +
  • 3
  • +
  • 0
  • +
  • 3
  • +
  • 3
  • +
  • 3
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 0
  • +
  • 3
  • +
  • 0
  • +
  • 3
  • +
  • 0
  • +
  • 0
  • +
    +
    +
    + + + ApparelPolicy_任意_1 + + + + + + DrugPolicy_社交成瘾品_1 + + + + null + + + + +
  • Sleep
  • +
  • Sleep
  • +
  • Sleep
  • +
  • Sleep
  • +
  • Sleep
  • +
  • Sleep
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Anything
  • +
  • Sleep
  • +
  • Sleep
  • +
    +
    + + Best + + + + + null + 1 + + + + + + + + + + + + + + + + + + + + + + + +
  • + Skin_Melanin3 + Thing_Human846 + null + 132 +
  • +
  • + Hair_MidBlack + Thing_Human846 + null + 133 +
  • +
    + Baseliner +
    + + + null + + + + +
    \ No newline at end of file diff --git a/Source/Documents/PawnStorageInHediff_Analysis.md b/Source/Documents/PawnStorageInHediff_Analysis.md new file mode 100644 index 0000000..62cdef5 --- /dev/null +++ b/Source/Documents/PawnStorageInHediff_Analysis.md @@ -0,0 +1,188 @@ +# RimWorld Modding: 利用Hediff存储Pawn的深度解析 + +在RimWorld的Mod开发中,有时需要将一个`Pawn`(人物、动物等)从游戏世界中临时移除,并将其数据完整保存起来,之后再释放回游戏中。一个非常精妙且强大的实现方式就是让`Hediff`(健康效果)扮演一个“容器”的角色。 + +本文档将以`HediffAbility_PaintedSkin`为例,深入剖析其实现`Pawn`存储的核心机制。 + +## 核心概念 + +该功能主要依赖于RimWorld框架中的两个核心组件: + +1. **`IThingHolder`接口**: 一个对象(如建筑、Hediff、Pawn的装备栏等)如果实现了这个接口,就等于向游戏声明:“我是一个可以容纳其他物品(`Thing`)的容器”。 +2. **`ThingOwner`类**: 这是实现存储功能的“袋子”。它是一个专门用于管理一组`Thing`对象的集合,并负责处理这些物品的保存、加载和所有权关系。 + +## 案例分析: `HediffAbility_PaintedSkin` + +以下是`HediffAbility_PaintedSkin`的完整源代码,它完美地展示了如何利用`Hediff`来存储一个`Pawn`。 + +```csharp +using System; +using System.Collections.Generic; +using RimWorld; +using Verse; +using Verse.AI; +using Verse.Sound; + +namespace RigorMortis +{ + public class HediffAbility_PaintedSkin : HediffWithComps, IHediffAbility, IThingHolder + { + // 1. 核心存储容器 + protected ThingOwner innerContainer; + + private CompYinAndMalevolent compYin; + public Pawn victim; + + // 构造函数:初始化容器 + public HediffAbility_PaintedSkin() + { + // 'this'表示容器的所有者是当前Hediff实例 + // 'LookMode.Deep'是关键,确保能完整保存Pawn的所有数据 + this.innerContainer = new ThingOwner(this, false, LookMode.Deep, true); + } + + // --- IThingHolder 接口实现 --- + + public IThingHolder ParentHolder + { + get + { + // 对于Hediff来说,它的父容器就是持有它的Pawn + return this.pawn; + } + } + + public void GetChildHolders(List outChildren) + { + ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, this.GetDirectlyHeldThings()); + } + + + + public ThingOwner GetDirectlyHeldThings() + { + return this.innerContainer; + } + + // --- 容器内容访问 --- + + public Thing ContainedThing + { + get + { + return this.innerContainer.Count > 0 ? this.innerContainer[0] : null; + } + } + + public Pawn Zombie + { + get + { + // 提供一个便捷的属性来访问被存储的Pawn + return this.ContainedThing as Pawn; + } + } + + public bool HasAnyContents + { + get + { + return this.innerContainer.Count > 0; + } + } + + // --- 存入/取出逻辑 --- + + public virtual bool Accepts(Thing thing) + { + return this.innerContainer.CanAcceptAnyOf(thing, true); + } + + public virtual bool TryAcceptThing(Thing thing, bool allowSpecialEffects = true) + { + if (!this.Accepts(thing)) + { + return false; + } + bool flag; + if (thing.holdingOwner != null) + { + // 将Pawn从当前持有者(通常是地图)转移到我们的容器中 + thing.holdingOwner.TryTransferToContainer(thing, this.innerContainer, thing.stackCount, true); + flag = true; + } + else + { + // 如果Pawn没有持有者(例如是新生成的),直接添加 + flag = this.innerContainer.TryAdd(thing, true); + } + return flag; + } + + public virtual void EjectContents() + { + // 决定在何处释放Pawn + Map map = this.pawn.MapHeld ?? Find.AnyPlayerHomeMap; + IntVec3 cell = (this.pawn.Spawned || (this.pawn.Corpse != null && this.pawn.Corpse.Spawned)) ? this.pawn.PositionHeld : ((this.pawn.CarriedBy != null) ? this.pawn.CarriedBy.PositionHeld : map.Center); + + // 将容器内的所有东西(即被存储的Pawn)扔到地图上 + this.innerContainer.TryDropAll(cell, map, ThingPlaceMode.Direct, null, null, true); + } + + // --- 存档/读档 --- + + public override void ExposeData() + { + base.ExposeData(); + Scribe_References.Look(ref this.victim, "victim", false); + + // 2. 深度保存容器内容 + // 'Scribe_Deep.Look' 会序列化容器内的Pawn的所有数据 + Scribe_Deep.Look(ref this.innerContainer, "innerContainer", new object[] + { + this + }); + + // 兼容性处理:确保旧存档在加载后也能正确初始化容器 + if (Scribe.mode == LoadSaveMode.PostLoadInit) + { + if (this.innerContainer == null) + { + this.innerContainer = new ThingOwner(this, false, LookMode.Deep, true); + } + } + } + + // --- 其他逻辑 --- + // (为了简洁,此处省略了PostTick, End, AbsolutelyKill等与存储机制非直接相关的代码) + // ... + } +} +``` + +## 机制剖析 + +### 1. 声明容器身份 (`IThingHolder`) + +通过在类声明中加入 `IThingHolder`,`HediffAbility_PaintedSkin` 就获得了“容器”的资格。这要求它必须实现接口定义的属性和方法,如 `ParentHolder` 和 `GetDirectlyHeldThings()`。`GetDirectlyHeldThings()` 方法必须返回真正的存储实例,也就是我们的 `innerContainer`。 + +### 2. 初始化存储核心 (`ThingOwner`) + +在构造函数中,我们创建了一个 `ThingOwner` 实例。这里的关键在于 `LookMode.Deep` 参数。 + +* `LookMode.Value`: 只保存简单值类型(如int, float, string)。 +* `LookMode.Reference`: 只保存一个对物体的引用ID。加载时,游戏会尝试在世界中找到这个ID对应的物体。如果物体已被销毁,引用会丢失。**这不适用于存储Pawn**,因为Pawn在被存入容器时已经从世界中移除了。 +* **`LookMode.Deep`**: 这才是我们的选择。它告诉序列化系统:“请将这个物体(`Pawn`)的所有数据——健康、技能、装备、Hediff、人际关系、思想等等——完完整整地打包保存起来。” 当游戏加载时,它会用这些数据重建一个一模一样的`Pawn`实例。 + +### 3. 序列化 (`ExposeData`) + +`ExposeData` 方法是RimWorld存档机制的核心。 + +* `Scribe_Deep.Look(ref this.innerContainer, ...)`: 这行代码是魔法发生的地方。当游戏保存时,`Scribe_Deep` 会深入到 `innerContainer` 内部,并因为我们之前设置了 `LookMode.Deep`,它会对容器里的每一个 `Pawn` 进行递归式的深度保存。 +* 当游戏加载时,`Scribe_Deep` 会读取存档中的数据,重建 `innerContainer`,并利用深度保存的数据重建一个与存入时状态完全一致的 `Pawn`。 + +## 总结 + +通过实现 `IThingHolder` 接口并利用一个配置为 `LookMode.Deep` 的 `ThingOwner` 容器,我们可以将一个 `Hediff` 转变为一个功能强大的、能够随宿主移动的“Pawn胶囊”。这个“胶囊”可以安全地携带一个`Pawn`穿越存档的海洋,确保其数据的完整性和一致性。 + +这项技术是实现诸如吞噬、俘获、传送、特殊休眠仓等高级Mod功能的基石。 \ No newline at end of file diff --git a/Source/Documents/Possession_Implementation_Guide.md b/Source/Documents/Possession_Implementation_Guide.md new file mode 100644 index 0000000..d10dcdc --- /dev/null +++ b/Source/Documents/Possession_Implementation_Guide.md @@ -0,0 +1,569 @@ +# “抱脸虫夺舍”技能实现说明书 (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`并将其附加到目标身上,从而启动整个夺舍流程。 + +```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(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 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.AllDefs) + { + bodyTarget.workSettings.SetPriority(workDef, soulSource.workSettings.GetPriority(workDef)); + } + } + + // 时间表 + if (soulSource.timetable != null && bodyTarget.timetable != null) + { + bodyTarget.timetable.times = new List(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 + + + + + + + ARA_Possession + + + + 这个生物的身体正被另一个实体所控制。 + + ArachnaeSwarm.Possession.Hediff_Possession + + false + false + 1.0 + +
  • + +
  • +
    +
    + +
    +``` + +### 4.2 `ARA_Possession_Defs.xml` - 定义技能、种族和身体 + +这个文件定义了技能本身,以及我们的“阿拉克涅原虫”作为一个完整的生物所需的一切。 + +```xml + + + + + + + ARA_Ability_Possess + + 将你的意识注入另一个生物的身体,完全占据它。 + UI/Abilities/Possess + 600 + + Verb_CastAbility + 1.5 + 5.9 + + true + false + false + false + + + + +
  • + + + + + + ARA_Facehugger + Humanlike_PostMentalState + 100 + + + + + + + + + + ARA_Facehugger + + + ARA_FacehuggerRace + 25 + +
  • + + Things/Pawn/Animal/ARA_Facehugger + 0.8 + + + Things/Pawn/Animal/Dessicated/CritterDessicated + 0.8 + +
  • + + ARA_Facehugger + + +
  • ARA_Ability_Possess
  • +
    + + + + + ARA_FacehuggerRace + + 一种小型的、脆弱的寄生生物,其唯一的生存目的就是寻找并占据一个更强大的宿主。它通过将自己的意识注入目标来完成这一过程。 + + 4.0 + 50 + -10 + 50 + + +
  • + + +
  • Scratch
  • + + 2 + 1.5 + +
    + + Animal + + ARA_FacehuggerBody + 0.2 + 0.3 + 0.1 + +
  • + AnimalAdult + 0 +
  • +
    +
    +
    + + + + ARA_FacehuggerBody + + + Body + 20 + 20 + +
  • + Head + 0.3 + +
  • + Skull + 0.2 + Inside + +
  • + Brain + 0.1 + Inside +
  • +
    + +
  • + Eye + left eye + 0.07 +
  • +
  • + Eye + right eye + 0.07 +
  • + + +
  • + Leg + front left leg + 0.1 +
  • +
  • + Leg + front right leg + 0.1 +
  • +
  • + Leg + rear left leg + 0.1 +
  • +
  • + Leg + rear right leg + 0.1 +
  • + +
    +
    + +
    +``` + +--- + +这份详尽的文档现在包含了我们所有的最终代码和XML,并附有详细的注释,解释了每一步的作用和它们之间的关联。 \ No newline at end of file