补上传

This commit is contained in:
2025-09-05 17:34:06 +08:00
parent 11d3d5d340
commit 64afb0cbd6
6 changed files with 127 additions and 29 deletions

Binary file not shown.

View File

@@ -4,7 +4,7 @@
<!-- ==================== Jump Possession ==================== -->
<AbilityDef>
<defName>ARA_Ability_Possess</defName>
<label>阿拉克涅突袭寄生</label>
<label>原虫种突袭寄生</label>
<description>跃向目标,随后尝试使用长尾刺穿对象并截断其神经中枢信号,以自己的控制信号取而代之,进而完全侵占目标躯体。\n\n被侵占躯体的对象视为阿拉克涅虫族的一员需要接受女皇种的信息素标记并且不再拥有感情和高级需求仅作为躯壳活着。目标的技能熟练度和背景故事会替换为原虫种的技能熟练度和背景故事一旦宿主死亡原虫种将从宿主身上逃离。</description>
<iconPath>UI/Abilities/Longjump</iconPath>
<cooldownTicksRange>800</cooldownTicksRange>
@@ -13,7 +13,7 @@
<!-- 使用我们新的Verb -->
<verbClass>ArachnaeSwarm.Verb_JumpAndCastOnLanding</verbClass>
<warmupTime>1.0</warmupTime>
<range>19.9</range>
<range>9.9</range>
<requireLineOfSight>true</requireLineOfSight>
<soundCast>Longjump_Jump</soundCast>
<soundLanding>Longjump_Land</soundLanding>
@@ -26,11 +26,18 @@
</verbProperties>
<comps>
<!-- comps部分现在只包含夺舍效果因为跳跃由Verb处理 -->
<li Class="ArachnaeSwarm.CompProperties_AbilityPossess"/>
<li Class="CompProperties_AbilityGiveHediff">
<compClass>CompAbilityEffect_GiveHediff</compClass>
<hediffDef>ARA_HiveMindDrone</hediffDef>
</li>
<li Class="ArachnaeSwarm.CompProperties_AbilityPossess">
<!-- Base success chance range (60% to 100%) -->
<successChance>0.6~1.0</successChance>
<!-- Bonus success chance per point of damage dealt (1%) -->
<successChanceBonusPerDamage>0.01</successChanceBonusPerDamage>
<!-- Optional: Hediff to apply on successful possession -->
<hediffToApplyOnSuccess>ARA_HiveMindDrone</hediffToApplyOnSuccess>
<!-- Optional: A list of race ThingDefs that cannot be possessed -->
<raceBlacklist>
<li>ArachnaeQueen_Race</li>
</raceBlacklist>
</li>
</comps>
</AbilityDef>
</Defs>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<LanguageData>
<!-- Possession Ability Keys -->
<ARA_MustBeHumanlike>目标必须是类人生物。</ARA_MustBeHumanlike>
<ARA_AlreadyPossessed>{0} 已被夺舍。</ARA_AlreadyPossessed>
<ARA_CannotPossessRace>无法夺舍 {0} 种族。</ARA_CannotPossessRace>
</LanguageData>

View File

@@ -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.");
}
}
}
}
}

View File

@@ -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<ThingDef> raceBlacklist = new List<ThingDef>();
// Optional: A hediff to apply to the victim upon successful possession.
public HediffDef hediffToApplyOnSuccess;
public CompProperties_AbilityPossess()
{
this.compClass = typeof(CompAbilityEffect_Possess);

View File

@@ -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;
}
}
}