Files
ArachnaeSwarm/Source/ArachnaeSwarm/Abilities/ARA_HuggingFace/CompAbilityEffect_Possess.cs
ProjectKoi-Kalo\Kalo c04d0bdba6 fix: 修复夺舍能力在跳跃前执行导致空指针的问题
修复 GestaltNode 相关逻辑的空指针异常
修复 Overlord 死亡或销毁时 HiveNode 的状态更新问题
增加 Pawn 不在当前地图时的检查
2026-02-12 17:31:10 +08:00

228 lines
10 KiB
C#
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.
using RimWorld;
using RimWorld.Planet;
using Verse;
using UnityEngine;
using System.Linq;
using System.Collections.Generic;
namespace ArachnaeSwarm
{
public class CompAbilityEffect_Possess : CompAbilityEffect, ICompAbilityEffectOnJumpCompleted
{
public new CompProperties_AbilityPossess Props => (CompProperties_AbilityPossess)props;
public override bool Valid(LocalTargetInfo target, bool throwMessages = false)
{
Pawn targetPawn = target.Pawn;
if (targetPawn == null) return false;
if (!targetPawn.RaceProps.Humanlike)
{
if (throwMessages) Messages.Message("ARA_MustBeHumanlike".Translate(), targetPawn, MessageTypeDefOf.RejectInput, false);
return false;
}
if (targetPawn.health.hediffSet.HasHediff(HediffDef.Named("ARA_Possession")))
{
if (throwMessages) Messages.Message("ARA_AlreadyPossessed".Translate(targetPawn.LabelShort), targetPawn, MessageTypeDefOf.RejectInput, false);
return false;
}
if (Props.raceBlacklist.Contains(targetPawn.def))
{
if (throwMessages) Messages.Message("ARA_CannotPossessRace".Translate(targetPawn.def.label), targetPawn, MessageTypeDefOf.RejectInput, false);
return false;
}
return base.Valid(target, throwMessages);
}
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
{
base.Apply(target, dest);
// 修复:不再在 Apply 中直接执行 DoPossession。
// 因为该能力使用 Verb_JumpAndCastOnLanding会在落地时通过 OnJumpCompleted 触发夺舍。
// 在此处执行会导致施法者在跳跃开始前或期间被销毁,导致 PawnFlyer 抛出空指针异常。
ArachnaeLog.Debug($"[夺舍] Apply 触发,目标: {target.Pawn?.LabelShort ?? ""}。等待跳跃落地后执行夺舍逻辑。");
}
// 新增:检查目标是否无法行动
private bool IsTargetImmobilized(Pawn target)
{
if (target == null) return false;
// 检查是否倒地
if (target.Downed)
{
ArachnaeLog.Debug($"[夺舍] 目标 {target.LabelShort} 处于倒地状态");
return true;
}
// 检查是否无法移动
if (!target.health.capacities.CapableOf(PawnCapacityDefOf.Moving))
{
ArachnaeLog.Debug($"[夺舍] 目标 {target.LabelShort} 无法移动");
return true;
}
// 检查是否有严重的移动障碍
if (target.health.hediffSet.HasHediff(HediffDefOf.Anesthetic) ||
target.health.hediffSet.HasHediff(HediffDefOf.CryptosleepSickness) ||
target.health.hediffSet.HasHediff(HediffDefOf.FoodPoisoning))
{
ArachnaeLog.Debug($"[夺舍] 目标 {target.LabelShort} 有严重的移动障碍");
return true;
}
// 检查是否被束缚或囚禁
if (target.IsPrisoner || target.HostFaction != null)
{
ArachnaeLog.Debug($"[夺舍] 目标 {target.LabelShort} 被囚禁或束缚");
return true;
}
return false;
}
// 新增:计算寄生成功率
private float CalculateSuccessChance(Pawn targetPawn, float damageDealt = 0f)
{
// 如果目标无法行动100%成功率
if (IsTargetImmobilized(targetPawn))
{
ArachnaeLog.Debug($"[夺舍] 目标 {targetPawn.LabelShort} 无法行动,寄生成功率: 100%");
return 1f;
}
// 正常计算成功率
float baseChance = Props.successChance.RandomInRange;
float bonusFromDamage = damageDealt * Props.successChanceBonusPerDamage;
float finalChance = Mathf.Clamp01(baseChance + bonusFromDamage);
ArachnaeLog.Debug($"[夺舍] 目标 {targetPawn.LabelShort} 可以行动,寄生成功率: {finalChance:P0} (基础: {baseChance:P0}, 伤害加成: {bonusFromDamage:P0})");
return finalChance;
}
private void DoPossession(Pawn caster, Pawn targetPawn)
{
if (targetPawn == null || caster == null) return;
ArachnaeLog.Debug($"[夺舍] 开始执行。施法者: {caster.LabelShort}, 目标: {targetPawn.LabelShort}");
// 1. 捕获原宿主的完整数据,用于死亡后恢复尸体
OriginalPawnData originalHostData = new OriginalPawnData();
originalHostData.CaptureData(targetPawn);
ArachnaeLog.Debug($"[夺舍] 已捕获原始宿主 {targetPawn.LabelShort} 的完整数据。");
// 2. 备份原宿主的技能,用于后续合并
var originalTargetSkills = new Dictionary<SkillDef, (int level, Passion passion)>();
if (targetPawn.skills != null)
{
foreach (var skill in targetPawn.skills.skills)
{
originalTargetSkills[skill.def] = (skill.levelInt, skill.passion);
}
}
// 3. 准备抱脸虫和Hediff
Pawn originalCaster = PawnGenerator.GeneratePawn(caster.kindDef, caster.Faction);
PawnDataUtility.TransferSoul(caster, originalCaster); // 确保克隆体有完全一致的数据
Hediff_Possession hediff = (Hediff_Possession)HediffMaker.MakeHediff(HediffDef.Named("ARA_Possession"), targetPawn);
hediff.originalHostData = originalHostData; // 将宿主数据存入Hediff
// 4. 将抱脸虫存入Hediff
if (hediff.casterContainer.TryAdd(originalCaster, true))
{
ArachnaeLog.Debug($"[夺舍] 成功将 {caster.LabelShort} 的原始副本存入Hediff。");
// 5. 灵魂转移,此时 targetPawn 的技能被 caster 的技能覆盖
PawnDataUtility.TransferSoul(caster, targetPawn);
// 夺舍成功后,原始的抱脸虫应该消失
if (!caster.Destroyed)
{
caster.Destroy(DestroyMode.Vanish);
}
// 6. 技能合并:在灵魂转移后,直接在最终的身体 (targetPawn) 上进行合并
if (targetPawn.skills != null)
{
ArachnaeLog.Debug("[夺舍] 开始合并技能...");
foreach (var skillRecord in targetPawn.skills.skills)
{
if (originalTargetSkills.TryGetValue(skillRecord.def, out var originalSkill))
{
// 比较等级
if (originalSkill.level > skillRecord.levelInt)
{
skillRecord.levelInt = originalSkill.level;
}
// 比较热情
if (originalSkill.passion > skillRecord.passion)
{
skillRecord.passion = originalSkill.passion;
}
}
}
ArachnaeLog.Debug("[夺舍] 技能合并完成。");
}
// 7. 将Hediff添加到最终身体上
targetPawn.health.AddHediff(hediff);
if (Props.hediffToApplyOnSuccess != null)
{
targetPawn.health.AddHediff(Props.hediffToApplyOnSuccess, null, null);
ArachnaeLog.Debug($"[夺舍] 成功为 {targetPawn.LabelShort} 添加额外Hediff: {Props.hediffToApplyOnSuccess.defName}");
}
ArachnaeLog.Debug($"[夺舍] {targetPawn.LabelShort} (原 {caster.LabelShort}) 夺舍完成。");
}
else
{
ArachnaeLog.Debug($"[夺舍] 无法将 {caster.LabelShort} 的副本存入Hediff。中止操作。");
if(originalCaster != null && !originalCaster.Destroyed) originalCaster.Destroy();
}
}
public void OnJumpCompleted(IntVec3 JUMPINPOS_UNUSED, LocalTargetInfo landingTarget)
{
Pawn caster = this.parent.pawn;
if (caster == null || !(landingTarget.Thing is Pawn targetPawn)) return;
Verb bestMeleeVerb = caster.meleeVerbs.TryGetMeleeVerb(targetPawn);
if (bestMeleeVerb == null)
{
ArachnaeLog.Debug($"[Possess] Caster {caster.LabelShort} has no melee verb.");
return;
}
float damageAmount = bestMeleeVerb.verbProps.AdjustedMeleeDamageAmount(bestMeleeVerb, caster);
float armorPenetration = bestMeleeVerb.verbProps.AdjustedArmorPenetration(bestMeleeVerb, caster);
DamageDef damageDef = bestMeleeVerb.verbProps.meleeDamageDef;
var dinfo = new DamageInfo(damageDef, damageAmount, armorPenetration, -1, caster);
DamageWorker.DamageResult damageResult = targetPawn.TakeDamage(dinfo);
ArachnaeLog.Debug($"[Possess] Dealt {damageResult.totalDamageDealt} damage to {targetPawn.LabelShort} using {damageDef.defName}.");
if (damageResult.totalDamageDealt > 0)
{
// 修改:使用新的成功率计算方法
float finalChance = CalculateSuccessChance(targetPawn, damageResult.totalDamageDealt);
ArachnaeLog.Debug($"[Possess] Final chance: {finalChance:P0}");
if (Rand.Chance(finalChance))
{
ArachnaeLog.Debug($"[Possess] Success! Applying possession effect.");
DoPossession(caster, targetPawn);
}
else
{
ArachnaeLog.Debug($"[Possess] Failed possession check.");
}
}
}
}
}