修复 GestaltNode 相关逻辑的空指针异常 修复 Overlord 死亡或销毁时 HiveNode 的状态更新问题 增加 Pawn 不在当前地图时的检查
228 lines
10 KiB
C#
228 lines
10 KiB
C#
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.");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|