diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 632dc14..ba1c2ba 100644 Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ diff --git a/1.6/1.6/Defs/AbilityDefs/ARA_Possession_Defs.xml b/1.6/1.6/Defs/AbilityDefs/ARA_Possession_Defs.xml index 49dfee6..fbc0ddc 100644 --- a/1.6/1.6/Defs/AbilityDefs/ARA_Possession_Defs.xml +++ b/1.6/1.6/Defs/AbilityDefs/ARA_Possession_Defs.xml @@ -4,7 +4,7 @@ ARA_Ability_Possess - + 跃向目标,随后尝试使用长尾刺穿对象并截断其神经中枢信号,以自己的控制信号取而代之,进而完全侵占目标躯体。\n\n被侵占躯体的对象视为阿拉克涅虫族的一员,需要接受女皇种的信息素标记,并且不再拥有感情和高级需求,仅作为躯壳活着。目标的技能熟练度和背景故事会替换为原虫种的技能熟练度和背景故事,一旦宿主死亡,原虫种将从宿主身上逃离。 UI/Abilities/Longjump 800 @@ -13,7 +13,7 @@ ArachnaeSwarm.Verb_JumpAndCastOnLanding 1.0 - 19.9 + 9.9 true Longjump_Jump Longjump_Land @@ -26,11 +26,18 @@ -
  • -
  • - CompAbilityEffect_GiveHediff - ARA_HiveMindDrone -
  • +
  • + + 0.6~1.0 + + 0.01 + + ARA_HiveMindDrone + + +
  • ArachnaeQueen_Race
  • + +
    \ No newline at end of file diff --git a/Languages/ChineseSimplified/Keyed/ArachnaeSwarm_Keys.xml b/Languages/ChineseSimplified/Keyed/ArachnaeSwarm_Keys.xml new file mode 100644 index 0000000..9a71c32 --- /dev/null +++ b/Languages/ChineseSimplified/Keyed/ArachnaeSwarm_Keys.xml @@ -0,0 +1,9 @@ + + + + + 目标必须是类人生物。 + {0} 已被夺舍。 + 无法夺舍 {0} 种族。 + + \ No newline at end of file diff --git a/Source/ArachnaeSwarm/ARA_HuggingFace/CompAbilityEffect_Possess.cs b/Source/ArachnaeSwarm/ARA_HuggingFace/CompAbilityEffect_Possess.cs index d14db56..1b03b21 100644 --- a/Source/ArachnaeSwarm/ARA_HuggingFace/CompAbilityEffect_Possess.cs +++ b/Source/ArachnaeSwarm/ARA_HuggingFace/CompAbilityEffect_Possess.cs @@ -1,50 +1,110 @@ using RimWorld; using RimWorld.Planet; using Verse; +using UnityEngine; +using System.Linq; namespace ArachnaeSwarm { public class CompAbilityEffect_Possess : CompAbilityEffect, ICompAbilityEffectOnJumpCompleted { - public override void Apply(LocalTargetInfo target, LocalTargetInfo dest) + public new CompProperties_AbilityPossess Props => (CompProperties_AbilityPossess)props; + + public override bool Valid(LocalTargetInfo target, bool throwMessages = false) { - Log.Message($"[CompAbilityEffect_Possess] Apply called. Target: {target.Thing?.LabelShort ?? "null"}"); - base.Apply(target, dest); - - Pawn caster = this.parent.pawn; Pawn targetPawn = target.Pawn; + if (targetPawn == null) return false; - if (targetPawn == null || caster == null) + if (!targetPawn.RaceProps.Humanlike) { - Log.Warning($"[CompAbilityEffect_Possess] Apply aborted. TargetPawn or Caster is null."); - return; + 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); + DoPossession(this.parent.pawn, target.Pawn); + } + + private void DoPossession(Pawn caster, Pawn targetPawn) + { + if (targetPawn == null || caster == null) return; + Log.Message($"[夺舍] 开始执行。施法者: {caster.LabelShort}, 目标: {targetPawn.LabelShort}"); Hediff_Possession hediff = (Hediff_Possession)HediffMaker.MakeHediff(HediffDef.Named("ARA_Possession"), targetPawn); - if (hediff.GetDirectlyHeldThings().TryAdd(caster.SplitOff(1), true)) { Log.Message($"[夺舍] 成功将 {caster.LabelShort} 的副本存入Hediff。"); + PawnDataUtility.TransferSoul(caster, targetPawn); + targetPawn.health.AddHediff(hediff); + + if (Props.hediffToApplyOnSuccess != null) + { + targetPawn.health.AddHediff(Props.hediffToApplyOnSuccess, null, null); + Log.Message($"[夺舍] 成功为 {targetPawn.LabelShort} 添加额外Hediff: {Props.hediffToApplyOnSuccess.defName}"); + } + Log.Message($"[夺舍] {targetPawn.LabelShort} (原 {caster.LabelShort}) 夺舍完成。"); } else { - Log.Error($"[夺舍] 无法将 {caster.LabelShort} 的副本存入Hediff。中止操作。"); - return; + Log.Error($"[夺舍] 无法将 {caster.LabelShort} 的副本存入Hediff。中止操作。"); } - - PawnDataUtility.TransferSoul(caster, targetPawn); - - targetPawn.health.AddHediff(hediff); - - Log.Message($"[夺舍] {targetPawn.LabelShort} (原 {caster.LabelShort}) 夺舍完成。"); } public void OnJumpCompleted(IntVec3 JUMPINPOS_UNUSED, LocalTargetInfo landingTarget) { - Log.Message($"[CompAbilityEffect_Possess] OnJumpCompleted called. Landing target: {landingTarget.Thing?.LabelShort ?? "null"}"); - this.Apply(landingTarget, null); + Pawn caster = this.parent.pawn; + if (caster == null || !(landingTarget.Thing is Pawn targetPawn)) return; + + Verb bestMeleeVerb = caster.meleeVerbs.TryGetMeleeVerb(targetPawn); + if (bestMeleeVerb == null) + { + Log.Warning($"[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); + Log.Message($"[Possess] Dealt {damageResult.totalDamageDealt} damage to {targetPawn.LabelShort} using {damageDef.defName}."); + + if (damageResult.totalDamageDealt > 0) + { + float baseChance = Props.successChance.RandomInRange; + float bonusFromDamage = damageResult.totalDamageDealt * Props.successChanceBonusPerDamage; + float finalChance = Mathf.Clamp01(baseChance + bonusFromDamage); + Log.Message($"[Possess] Base chance: {baseChance}, Bonus: {bonusFromDamage}, Final chance: {finalChance}"); + + if (Rand.Chance(finalChance)) + { + Log.Message($"[Possess] Success! Applying possession effect."); + DoPossession(caster, targetPawn); + } + else + { + Log.Message($"[Possess] Failed possession check."); + } + } } } } \ No newline at end of file diff --git a/Source/ArachnaeSwarm/ARA_HuggingFace/CompProperties_AbilityPossess.cs b/Source/ArachnaeSwarm/ARA_HuggingFace/CompProperties_AbilityPossess.cs index b4402d0..d1fef88 100644 --- a/Source/ArachnaeSwarm/ARA_HuggingFace/CompProperties_AbilityPossess.cs +++ b/Source/ArachnaeSwarm/ARA_HuggingFace/CompProperties_AbilityPossess.cs @@ -1,9 +1,18 @@ using RimWorld; +using Verse; +using System.Collections.Generic; namespace ArachnaeSwarm { public class CompProperties_AbilityPossess : CompProperties_AbilityEffect { + public FloatRange successChance = new FloatRange(0.6f, 1.0f); + public float successChanceBonusPerDamage = 0.01f; + public List raceBlacklist = new List(); + + // Optional: A hediff to apply to the victim upon successful possession. + public HediffDef hediffToApplyOnSuccess; + public CompProperties_AbilityPossess() { this.compClass = typeof(CompAbilityEffect_Possess); diff --git a/Source/ArachnaeSwarm/ARA_HuggingFace/Verb_JumpAndCastOnLanding.cs b/Source/ArachnaeSwarm/ARA_HuggingFace/Verb_JumpAndCastOnLanding.cs index 0140dd1..9ad7792 100644 --- a/Source/ArachnaeSwarm/ARA_HuggingFace/Verb_JumpAndCastOnLanding.cs +++ b/Source/ArachnaeSwarm/ARA_HuggingFace/Verb_JumpAndCastOnLanding.cs @@ -8,14 +8,18 @@ namespace ArachnaeSwarm { public class Verb_JumpAndCastOnLanding : Verb_CastAbility { + // 1. A private field to store our specific target, separate from what the base class does. private LocalTargetInfo capturedTarget; + // 2. Capture the reliable target information early. public override bool TryStartCastOn(LocalTargetInfo castTarg, LocalTargetInfo destTarg, bool surpriseAttack = false, bool canHitNonTargetPawns = true, bool preventFriendlyFire = false, bool nonInterruptingSelfCast = false) { + Log.Message($"[Verb_JumpAndCastOnLanding] TryStartCastOn: Capturing our dedicated target: {castTarg.Thing?.LabelShort ?? "null"}."); this.capturedTarget = castTarg; return base.TryStartCastOn(castTarg, destTarg, surpriseAttack, canHitNonTargetPawns, preventFriendlyFire, nonInterruptingSelfCast); } + // --- Copied and adapted methods from Verb_CastAbilityJump --- public virtual ThingDef JumpFlyerDef => ThingDefOf.PawnFlyer; public override float EffectiveRange => this.verbProps.range; @@ -35,25 +39,34 @@ namespace ArachnaeSwarm return true; } + // --- Our custom implementation of TryCastShot --- protected override bool TryCastShot() { + // The physical destination for the jump. This might be adjusted by the game. We let it be. LocalTargetInfo physicalTarget = this.currentTarget; + + // Our dedicated logical target, captured earlier and guaranteed to be the Pawn. LocalTargetInfo logicalTargetForUs = this.capturedTarget; if (logicalTargetForUs == null || !logicalTargetForUs.HasThing) { + Log.Error($"[Verb_JumpAndCastOnLanding] TryCastShot: Our captured target is invalid!"); return false; } + + Log.Message($"[Verb_JumpAndCastOnLanding] TryCastShot: Using our captured target '{logicalTargetForUs.Thing.LabelShort}' for logic, and letting game use '{physicalTarget.Cell}' for jump physics."); - return JumpUtility.DoJump( + bool jumpStarted = JumpUtility.DoJump( CasterPawn, - physicalTarget, + physicalTarget, // For the jump itself this.ReloadableCompSource, verbProps, this.ability, - logicalTargetForUs, + logicalTargetForUs, // For our logic on landing this.JumpFlyerDef ); + + return jumpStarted; } } } \ No newline at end of file