This commit is contained in:
2025-09-06 15:08:02 +08:00
parent 13a9b23b71
commit 514b7ff0f9
5 changed files with 226 additions and 27 deletions

Binary file not shown.

View File

@@ -46,11 +46,16 @@ namespace ArachnaeSwarm
private void DoPossession(Pawn caster, Pawn targetPawn)
{
if (targetPawn == null || caster == null) return;
Log.Message($"[夺舍] 开始执行。施法者: {caster.LabelShort}, 目标: {targetPawn.LabelShort}");
// 为了保留原始宿主的技能数据,在灵魂转移前先复制一份
var originalTargetSkills = new Dictionary<SkillDef, (int, Passion)>();
// 1. 捕获原宿主的完整数据,用于死亡后恢复尸体
OriginalPawnData originalHostData = new OriginalPawnData();
originalHostData.CaptureData(targetPawn);
Log.Message($"[夺舍] 已捕获原始宿主 {targetPawn.LabelShort} 的完整数据。");
// 2. 备份原宿主的技能,用于后续合并
var originalTargetSkills = new Dictionary<SkillDef, (int level, Passion passion)>();
if (targetPawn.skills != null)
{
foreach (var skill in targetPawn.skills.skills)
@@ -59,19 +64,20 @@ namespace ArachnaeSwarm
}
}
// 储存原始抱脸虫
// 3. 准备抱脸虫和Hediff
Pawn originalCaster = caster.SplitOff(1) as Pawn;
Hediff_Possession hediff = (Hediff_Possession)HediffMaker.MakeHediff(HediffDef.Named("ARA_Possession"), targetPawn);
if (hediff.GetDirectlyHeldThings().TryAdd(originalCaster, true))
hediff.originalHostData = originalHostData; // 将宿主数据存入Hediff
// 4. 将抱脸虫存入Hediff
if (hediff.casterContainer.TryAdd(originalCaster, true))
{
Log.Message($"[夺舍] 成功将 {caster.LabelShort} 的原始副本存入Hediff。");
// 灵魂转移,此时 targetPawn 的技能被 caster 的技能覆盖
// 5. 灵魂转移,此时 targetPawn 的技能被 caster 的技能覆盖
PawnDataUtility.TransferSoul(caster, targetPawn);
// --- 技能合并 ---
// 在灵魂转移后,直接在最终的身体 (targetPawn) 上进行合并
// 6. 技能合并:在灵魂转移后,直接在最终的身体 (targetPawn) 上进行合并
if (targetPawn.skills != null)
{
Log.Message("[夺舍] 开始合并技能...");
@@ -80,21 +86,21 @@ namespace ArachnaeSwarm
if (originalTargetSkills.TryGetValue(skillRecord.def, out var originalSkill))
{
// 比较等级
if (originalSkill.Item1 > skillRecord.levelInt)
if (originalSkill.level > skillRecord.levelInt)
{
skillRecord.levelInt = originalSkill.Item1;
skillRecord.levelInt = originalSkill.level;
}
// 比较热情
if (originalSkill.Item2 > skillRecord.passion)
if (originalSkill.passion > skillRecord.passion)
{
skillRecord.passion = originalSkill.Item2;
skillRecord.passion = originalSkill.passion;
}
}
}
Log.Message("[夺舍] 技能合并完成。");
}
// 7. 将Hediff添加到最终身体上
targetPawn.health.AddHediff(hediff);
if (Props.hediffToApplyOnSuccess != null)
@@ -107,6 +113,7 @@ namespace ArachnaeSwarm
else
{
Log.Error($"[夺舍] 无法将 {caster.LabelShort} 的副本存入Hediff。中止操作。");
if(originalCaster != null && !originalCaster.Destroyed) originalCaster.Destroy();
}
}

View File

@@ -6,14 +6,15 @@ namespace ArachnaeSwarm
{
public class Hediff_Possession : HediffWithComps, IThingHolder
{
private ThingOwner innerContainer;
public ThingOwner casterContainer;
public OriginalPawnData originalHostData;
public Hediff_Possession()
{
this.innerContainer = new ThingOwner<Thing>(this, false, LookMode.Deep);
this.casterContainer = new ThingOwner<Thing>(this, false, LookMode.Deep);
}
public Pawn StoredCasterPawn => innerContainer.Count > 0 ? innerContainer[0] as Pawn : null;
public Pawn StoredCasterPawn => casterContainer.Count > 0 ? casterContainer[0] as Pawn : null;
public IThingHolder ParentHolder => this.pawn;
@@ -24,7 +25,7 @@ namespace ArachnaeSwarm
public ThingOwner GetDirectlyHeldThings()
{
return innerContainer;
return casterContainer;
}
// PostAdd现在只在游戏加载时起作用我们不需要在这里做任何特殊操作。
@@ -37,19 +38,28 @@ namespace ArachnaeSwarm
Pawn deadBody = this.pawn;
Pawn storedCaster = this.StoredCasterPawn;
if (storedCaster == null)
if (originalHostData != null)
{
Log.Message($"[夺舍结束] 正在将 {deadBody.LabelShort}'s 的灵魂恢复为原始宿主数据。");
originalHostData.RestoreData(deadBody);
}
else
{
Log.Error("Possessed pawn died, but no original host data was found to restore.");
}
if (storedCaster != null)
{
EjectCaster();
}
else
{
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 EjectContents()
public void EjectCaster()
{
if (StoredCasterPawn == null) return;
@@ -68,13 +78,14 @@ namespace ArachnaeSwarm
}
Log.Message($"[夺舍] 准备在地图 {map.ToString()} 的位置 {cell.ToString()} 处重生 {StoredCasterPawn.LabelShort}。");
this.innerContainer.TryDropAll(cell, map, ThingPlaceMode.Near);
this.casterContainer.TryDropAll(cell, map, ThingPlaceMode.Near);
}
public override void ExposeData()
{
base.ExposeData();
Scribe_Deep.Look(ref innerContainer, "innerContainer", this);
Scribe_Deep.Look(ref casterContainer, "casterContainer", this);
Scribe_Deep.Look(ref originalHostData, "originalHostData");
}
}
}

View File

@@ -0,0 +1,180 @@
using RimWorld;
using Verse;
using System.Collections.Generic;
using System.Linq;
using RimWorld.Planet;
namespace ArachnaeSwarm
{
// A container for all essential data of a pawn that needs to be restored.
public class OriginalPawnData : IExposable
{
// Core Identity
public Name name;
public BackstoryDef childhood;
public BackstoryDef adulthood;
public List<Trait> traits = new List<Trait>();
public Faction faction;
// Growth & Experience
public List<SkillRecordData> skills = new List<SkillRecordData>();
// Mind & Settings
public List<Thought_Memory> memories = new List<Thought_Memory>();
public Dictionary<WorkTypeDef, int> workSettings = new Dictionary<WorkTypeDef, int>();
public List<TimeAssignmentDef> timetable;
public HostilityResponseMode hostilityResponse;
public MedicalCareCategory medCare;
public bool selfTend;
public ApparelPolicy apparelPolicy;
public DrugPolicy drugPolicy;
public FoodPolicy foodPolicy;
// DLC & Social
public Ideo ideo;
public List<RoyalTitleData> royalTitles = new List<RoyalTitleData>();
public void ExposeData()
{
Scribe_Deep.Look(ref name, "name");
Scribe_Defs.Look(ref childhood, "childhood");
Scribe_Defs.Look(ref adulthood, "adulthood");
Scribe_Collections.Look(ref traits, "traits", LookMode.Deep);
Scribe_References.Look(ref faction, "faction");
Scribe_Collections.Look(ref skills, "skills", LookMode.Deep);
Scribe_Collections.Look(ref memories, "memories", LookMode.Deep);
Scribe_Collections.Look(ref workSettings, "workSettings", LookMode.Def, LookMode.Value);
Scribe_Collections.Look(ref timetable, "timetable", LookMode.Def);
Scribe_Values.Look(ref hostilityResponse, "hostilityResponse");
Scribe_Values.Look(ref medCare, "medCare");
Scribe_Values.Look(ref selfTend, "selfTend");
Scribe_References.Look(ref apparelPolicy, "apparelPolicy");
Scribe_References.Look(ref drugPolicy, "drugPolicy");
Scribe_References.Look(ref foodPolicy, "foodPolicy");
if (ModsConfig.IdeologyActive) Scribe_References.Look(ref ideo, "ideo");
if (ModsConfig.RoyaltyActive)
{
Scribe_Collections.Look(ref royalTitles, "royalTitles", LookMode.Deep);
}
}
// Populates this object with data from a living pawn.
public void CaptureData(Pawn pawn)
{
this.name = pawn.Name;
this.childhood = pawn.story.Childhood;
this.adulthood = pawn.story.Adulthood;
this.traits = new List<Trait>(pawn.story.traits.allTraits);
this.faction = pawn.Faction;
this.skills = pawn.skills.skills.Select(s => new SkillRecordData
{
def = s.def,
level = s.levelInt,
xpSinceLastLevel = s.xpSinceLastLevel,
passion = s.passion
}).ToList();
if (pawn.needs?.mood?.thoughts?.memories != null) this.memories = new List<Thought_Memory>(pawn.needs.mood.thoughts.memories.Memories);
if (pawn.workSettings != null) this.workSettings = DefDatabase<WorkTypeDef>.AllDefs.ToDictionary(def => def, def => pawn.workSettings.GetPriority(def));
if (pawn.timetable != null) this.timetable = new List<TimeAssignmentDef>(pawn.timetable.times);
if (pawn.playerSettings != null)
{
this.hostilityResponse = pawn.playerSettings.hostilityResponse;
this.medCare = pawn.playerSettings.medCare;
this.selfTend = pawn.playerSettings.selfTend;
}
if (pawn.outfits != null) this.apparelPolicy = pawn.outfits.CurrentApparelPolicy;
if (pawn.drugs != null) this.drugPolicy = pawn.drugs.CurrentPolicy;
if (pawn.foodRestriction != null) this.foodPolicy = pawn.foodRestriction.CurrentFoodPolicy;
if (ModsConfig.IdeologyActive && pawn.ideo != null) this.ideo = pawn.ideo.Ideo;
if (ModsConfig.RoyaltyActive && pawn.royalty != null)
{
this.royalTitles = pawn.royalty.AllTitlesForReading.Select(t => new RoyalTitleData { defName = t.def.defName, faction = t.faction }).ToList();
}
}
// Applies the stored data back to a living pawn.
public void RestoreData(Pawn pawn)
{
pawn.Name = this.name;
pawn.story.Childhood = this.childhood;
pawn.story.Adulthood = this.adulthood;
pawn.story.traits.allTraits.Clear();
this.traits.ForEach(t => pawn.story.traits.GainTrait(t));
if (pawn.Faction != this.faction) pawn.SetFaction(this.faction);
pawn.skills.skills.Clear();
this.skills.ForEach(s => pawn.skills.skills.Add(new SkillRecord(pawn, s.def) { levelInt = s.level, xpSinceLastLevel = s.xpSinceLastLevel, passion = s.passion }));
if (pawn.needs?.mood?.thoughts?.memories != null)
{
pawn.needs.mood.thoughts.memories.Memories.Clear();
this.memories.ForEach(m => pawn.needs.mood.thoughts.memories.TryGainMemory(m));
}
if (pawn.workSettings != null)
{
pawn.workSettings.EnableAndInitialize();
foreach(var ws in this.workSettings) pawn.workSettings.SetPriority(ws.Key, ws.Value);
}
if (pawn.timetable != null) pawn.timetable.times = new List<TimeAssignmentDef>(this.timetable);
if (pawn.playerSettings != null)
{
pawn.playerSettings.hostilityResponse = this.hostilityResponse;
pawn.playerSettings.medCare = this.medCare;
pawn.playerSettings.selfTend = this.selfTend;
}
if (pawn.outfits != null) pawn.outfits.CurrentApparelPolicy = this.apparelPolicy;
if (pawn.drugs != null) pawn.drugs.CurrentPolicy = this.drugPolicy;
if (pawn.foodRestriction != null) pawn.foodRestriction.CurrentFoodPolicy = this.foodPolicy;
if (ModsConfig.IdeologyActive && pawn.ideo != null && this.ideo != null) pawn.ideo.SetIdeo(this.ideo);
if (ModsConfig.RoyaltyActive && pawn.royalty != null)
{
pawn.royalty.AllTitlesForReading.Clear();
pawn.royalty.AllFactionPermits.Clear(); // Clear existing permits on the body
this.royalTitles.ForEach(t => {
RoyalTitleDef titleDef = DefDatabase<RoyalTitleDef>.GetNamed(t.defName, false);
if (titleDef != null)
{
pawn.royalty.SetTitle(t.faction, titleDef, true, false, false);
}
});
pawn.royalty.UpdateAvailableAbilities();
}
pawn.Drawer.renderer.SetAllGraphicsDirty();
}
}
public class SkillRecordData : IExposable
{
public SkillDef def;
public int level;
public float xpSinceLastLevel;
public Passion passion;
public void ExposeData()
{
Scribe_Defs.Look(ref def, "def");
Scribe_Values.Look(ref level, "level");
Scribe_Values.Look(ref xpSinceLastLevel, "xpSinceLastLevel");
Scribe_Values.Look(ref passion, "passion");
}
}
public class RoyalTitleData : IExposable
{
public Faction faction;
public string defName;
public void ExposeData()
{
Scribe_References.Look(ref faction, "faction");
Scribe_Values.Look(ref defName, "defName");
}
}
}

View File

@@ -121,6 +121,7 @@
<Compile Include="ARA_HuggingFace\CompAbilityEffect_Possess.cs" />
<Compile Include="ARA_HuggingFace\CompProperties_AbilityPossess.cs" />
<Compile Include="ARA_HuggingFace\Verb_JumpAndCastOnLanding.cs" />
<Compile Include="ARA_HuggingFace\OriginalPawnData.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="ARA_TrainingWork\JobClean\ARA_TrainableDefOf_Cleaning.cs" />