diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index d47b472..692993c 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_GuardianPsyField_Abilities.xml b/1.6/1.6/Defs/AbilityDefs/ARA_GuardianPsyField_Abilities.xml
new file mode 100644
index 0000000..d3fc45c
--- /dev/null
+++ b/1.6/1.6/Defs/AbilityDefs/ARA_GuardianPsyField_Abilities.xml
@@ -0,0 +1,60 @@
+
+
+
+
+ ARA_GuardianPsyField_On
+
+ 投射一个强大的灵能防御力场。
+ ArachnaeSwarm/UI/Abilities/ARA_RaceBaseSwarmProduceOn
+ 601
+ false
+ true
+ false
+
+ Verb_CastAbility
+ 0
+ false
+ false
+ false
+
+ true
+
+
+
+
+ ArachnaeSwarm.CompAbilityEffect_GiveSwitchHediff
+ ARA_GuardianPsyField
+ true
+
+
+
+
+
+ ARA_GuardianPsyField_Off
+
+ 关闭灵能防御力场。
+ ArachnaeSwarm/UI/Abilities/ARA_RaceBaseSwarmProduceOff
+ 601
+ false
+ true
+ false
+
+ Verb_CastAbility
+ 0
+ false
+ false
+ false
+
+ true
+
+
+
+
+ ArachnaeSwarm.CompAbilityEffect_RemoveSwitchHediff
+ ARA_GuardianPsyField
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/HediffDefs/ARA_GuardianPsyField_Hediff.xml b/1.6/1.6/Defs/HediffDefs/ARA_GuardianPsyField_Hediff.xml
new file mode 100644
index 0000000..c197be1
--- /dev/null
+++ b/1.6/1.6/Defs/HediffDefs/ARA_GuardianPsyField_Hediff.xml
@@ -0,0 +1,38 @@
+
+
+
+
+ ARA_GuardianPsyField
+
+ 一个强大的灵能防御力场。它会自动拦截进入其作用范围的敌对飞行物,并能将附近友军受到的伤害转移到施法者身上。每次成功的守护都会消耗施法者的精神力。
+ ArachnaeSwarm.Hediff_DynamicInterceptor
+ false
+ (0.6, 0.2, 0.9)
+ false
+
+
+
+
+ 5.9
+ 1500
+ 3200
+ 60
+
+
+
+ 0.001
+ 0.1
+ 0.01
+
+
+ (0.5, 0.3, 0.9, 0.5)
+ Interceptor_BlockedProjectile
+ Interceptor_BlockedProjectile
+ Shield_Break
+ BulletShieldGenerator_Reactivate
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml
index 35d52f8..05e370b 100644
--- a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml
+++ b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml
@@ -291,6 +291,8 @@
ARA_PsychicBrainburn
ARA_NeuroSwarm_jump
+ ARA_GuardianPsyField_On
+ ARA_GuardianPsyField_Off
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index 40c27db..eb83722 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -169,6 +169,9 @@
+
+
+
diff --git a/Source/ArachnaeSwarm/Harmony_ProjectileInterceptor.cs b/Source/ArachnaeSwarm/Harmony_ProjectileInterceptor.cs
new file mode 100644
index 0000000..43f34bb
--- /dev/null
+++ b/Source/ArachnaeSwarm/Harmony_ProjectileInterceptor.cs
@@ -0,0 +1,41 @@
+using HarmonyLib;
+using RimWorld;
+using Verse;
+using UnityEngine;
+
+namespace ArachnaeSwarm
+{
+ [HarmonyPatch(typeof(Projectile), "CheckForFreeInterceptBetween")]
+ public static class Projectile_CheckForFreeInterceptBetween_Patch
+ {
+ // This patch will find our custom ThingComp on pawns and call its intercept method.
+ public static bool Prefix(Projectile __instance, Vector3 lastExactPos, Vector3 newExactPos)
+ {
+ if (__instance.Map == null || __instance.Destroyed)
+ {
+ return true; // Let original method run if something is wrong
+ }
+
+ // Iterate through all pawns on the map
+ foreach (Pawn pawn in __instance.Map.mapPawns.AllPawnsSpawned)
+ {
+ // Our comp is directly on the pawn, not on apparel
+ if (pawn.TryGetComp(out var interceptor))
+ {
+ // Call our custom intercept method
+ if (interceptor.TryIntercept(__instance))
+ {
+ // If interception is successful, destroy the projectile
+ __instance.Destroy(DestroyMode.Vanish);
+
+ // Prevent the original game method from running, as we've handled it
+ return false;
+ }
+ }
+ }
+
+ // If no interception happened, let the original method run
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Hediff_DynamicInterceptor.cs b/Source/ArachnaeSwarm/Hediff_DynamicInterceptor.cs
new file mode 100644
index 0000000..3c939c3
--- /dev/null
+++ b/Source/ArachnaeSwarm/Hediff_DynamicInterceptor.cs
@@ -0,0 +1,68 @@
+using Verse;
+using RimWorld;
+using System.Linq;
+using System; // For Activator
+
+namespace ArachnaeSwarm
+{
+ public class Hediff_DynamicInterceptor : HediffWithComps
+ {
+ public CompProperties_GuardianPsyField GuardianProps
+ {
+ get
+ {
+ var hediffCompProps = def.comps?.FirstOrDefault(c => c is HediffCompProperties_DynamicInterceptor) as HediffCompProperties_DynamicInterceptor;
+ return hediffCompProps?.guardianProps;
+ }
+ }
+
+ public override void PostAdd(DamageInfo? dinfo)
+ {
+ base.PostAdd(dinfo);
+ var props = GuardianProps;
+ if (pawn != null && props != null)
+ {
+ if (pawn.GetComp() == null)
+ {
+ Log.Message($"[DynamicInterceptor] Adding ThingComp_GuardianPsyField to {pawn.LabelShort}.");
+ var newComp = (ThingComp_GuardianPsyField)Activator.CreateInstance(typeof(ThingComp_GuardianPsyField));
+ newComp.parent = pawn;
+ // Initialize with the actual properties from the HediffDef
+ newComp.Initialize(props);
+ pawn.AllComps.Add(newComp);
+ }
+ }
+ }
+
+ public override void PostRemoved()
+ {
+ base.PostRemoved();
+ if (pawn != null)
+ {
+ var comp = pawn.GetComp();
+ if (comp != null)
+ {
+ Log.Message($"[DynamicInterceptor] Removing ThingComp_GuardianPsyField from {pawn.LabelShort}.");
+ pawn.AllComps.Remove(comp);
+ }
+ }
+ }
+ }
+
+ // This comp will hold the properties for our custom interceptor
+ public class HediffCompProperties_DynamicInterceptor : HediffCompProperties
+ {
+ public CompProperties_GuardianPsyField guardianProps; // Nested properties
+
+ public HediffCompProperties_DynamicInterceptor()
+ {
+ this.compClass = typeof(HediffComp_DynamicInterceptor);
+ }
+ }
+
+ // A simple HediffComp to go with the properties
+ public class HediffComp_DynamicInterceptor : HediffComp
+ {
+ public HediffCompProperties_DynamicInterceptor Props => props as HediffCompProperties_DynamicInterceptor;
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/ThingComp_GuardianPsyField.cs b/Source/ArachnaeSwarm/ThingComp_GuardianPsyField.cs
new file mode 100644
index 0000000..d8efac6
--- /dev/null
+++ b/Source/ArachnaeSwarm/ThingComp_GuardianPsyField.cs
@@ -0,0 +1,320 @@
+using RimWorld;
+using Verse;
+using UnityEngine;
+using Verse.Sound;
+
+namespace ArachnaeSwarm
+{
+ // 1. Expanded CompProperties to match the user's example
+ public class CompProperties_GuardianPsyField : CompProperties
+ {
+ public float radius = 5.9f;
+ public int hitPoints = 100;
+ public int rechargeDelay = 3200; // Ticks after breaking
+ public int rechargeHitPointsIntervalTicks = 60; // Ticks to restore 1 HP
+
+ // New properties for psyfocus/entropy mechanics
+ // Removed psyfocusCostForFullRecharge
+ public float psyfocusCostPerInterval = 0.001f; // e.g., 0.1% of max psyfocus per recharge interval
+ public float entropyGainPerDamage = 0.5f; // For self
+ // Removed entropyGainPerAllyDamage
+ public float hitPointsPctPerInterval = 0.01f; // Restore 1% of max HP per interval
+ public EffecterDef absorbEffecter;
+ // Removed transferAllyDamage
+
+ public EffecterDef interceptEffecter;
+ public EffecterDef breakEffecter;
+ public EffecterDef reactivateEffecter;
+
+ public Color color = Color.cyan;
+
+ public CompProperties_GuardianPsyField()
+ {
+ compClass = typeof(ThingComp_GuardianPsyField);
+ }
+ }
+
+ [StaticConstructorOnStartup]
+ public class ThingComp_GuardianPsyField : ThingComp
+ {
+ // --- State Variables ---
+ private int lastInterceptTicks = -999999;
+ private int ticksToReset = 0; // Cooldown timer
+ public int currentHitPoints;
+ private bool wasNotAtFullHp = false; // Tracks if shield was damaged before recharge
+
+ // --- Properties ---
+ public CompProperties_GuardianPsyField Props => (CompProperties_GuardianPsyField)props;
+ private Pawn PawnOwner => parent as Pawn;
+ public bool IsOnCooldown => ticksToReset > 0;
+ public int HitPointsMax => Props.hitPoints;
+
+ // --- Visuals ---
+ private static readonly Material ForceFieldMat = MaterialPool.MatFrom("Other/ForceField", ShaderDatabase.MoteGlow);
+ private static readonly MaterialPropertyBlock MatPropertyBlock = new MaterialPropertyBlock();
+
+ public bool Active
+ {
+ get
+ {
+ // Shield is active if pawn is valid, hediff is present, not on cooldown, and has psyfocus.
+ // currentHitPoints <= 0 should NOT prevent activation for recharge.
+ if (PawnOwner == null || !PawnOwner.Spawned || PawnOwner.Dead || PawnOwner.Downed || IsOnCooldown)
+ return false;
+
+ var hediff = PawnOwner.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("ARA_GuardianPsyField"));
+ if (hediff == null) return false;
+
+ // Shield is only active if pawn has psyfocus
+ if (PawnOwner.psychicEntropy == null || PawnOwner.psychicEntropy.CurrentPsyfocus <= 0) return false;
+
+ return true;
+ }
+ }
+
+ public override void PostPostMake()
+ {
+ base.PostPostMake();
+ currentHitPoints = HitPointsMax;
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref lastInterceptTicks, "lastInterceptTicks", -999999);
+ Scribe_Values.Look(ref ticksToReset, "ticksToReset", 0);
+ Scribe_Values.Look(ref currentHitPoints, "currentHitPoints", 0);
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+ if (PawnOwner == null) return;
+
+ if (IsOnCooldown)
+ {
+ ticksToReset--;
+ if (ticksToReset <= 0)
+ {
+ Reset();
+ }
+ }
+ // Only allow recharge if the shield is 'Active' (i.e., has psyfocus) and not on cooldown
+ else if (Active && currentHitPoints < HitPointsMax)
+ {
+ // Check if there's enough psyfocus to pay for this interval's recharge
+ if (PawnOwner.psychicEntropy != null && PawnOwner.psychicEntropy.CurrentPsyfocus >= Props.psyfocusCostPerInterval)
+ {
+ wasNotAtFullHp = true; // Mark that the shield was damaged
+ if(this.parent.IsHashIntervalTick(Props.rechargeHitPointsIntervalTicks))
+ {
+ currentHitPoints += (int)(HitPointsMax * Props.hitPointsPctPerInterval);
+ if(currentHitPoints > HitPointsMax) currentHitPoints = HitPointsMax;
+
+ // Deduct psyfocus for this interval
+ PawnOwner.psychicEntropy.OffsetPsyfocusDirectly(-Props.psyfocusCostPerInterval);
+ }
+ }
+ else
+ {
+ // Not enough psyfocus to recharge, log for debugging
+ Log.Message($"[GuardianFieldComp] {PawnOwner.LabelShort} has insufficient psyfocus ({PawnOwner.psychicEntropy?.CurrentPsyfocus ?? 0}) to recharge shield (cost: {Props.psyfocusCostPerInterval}).");
+ }
+ }
+ // The full recharge psyfocus deduction is removed, as it's now gradual.
+ // The wasNotAtFullHp check is still useful for other potential effects when full.
+ else if (wasNotAtFullHp && currentHitPoints >= HitPointsMax)
+ {
+ wasNotAtFullHp = false; // Reset flag when full
+ }
+ }
+
+ // Projectile interception logic remains the same, as it's a separate Harmony patch
+ public bool TryIntercept(Projectile projectile)
+ {
+ if (!Active) return false;
+
+ // We now intercept all projectiles, the filter will be done by the PostPreApplyDamage on self
+ // and the TryAbsorbDamageForAllyOnly for allies.
+ // This method is only for projectile visual/sound and psyfocus cost.
+
+ // --- Interception Success ---
+ lastInterceptTicks = Find.TickManager.TicksGame;
+
+ // Spawn effect at the point of interception, not the shield center
+ Props.interceptEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), PawnOwner.Map).Cleanup();
+
+ // No longer consuming hitpoints here, PostPreApplyDamage will handle it for self.
+ // The Harmony_DamageWorker for allies will handle it for allies.
+
+ return true;
+ }
+
+ // --- NEW: PostPreApplyDamage for self-protection (all damage types) ---
+ public override void PostPreApplyDamage(ref DamageInfo dinfo, out bool absorbed)
+ {
+ absorbed = false;
+ if (!Active || PawnOwner == null) return; // Only intercept if shield is active and for the owner
+
+ // We intercept ALL damage types for the owner
+ if (currentHitPoints < dinfo.Amount) return; // Not enough HP to absorb
+
+ // --- Absorption Success for self ---
+ Props.absorbEffecter?.Spawn(PawnOwner.Position, PawnOwner.Map).Cleanup(); // Effect at center for self-damage
+
+ // Add entropy based on damage taken for self
+ if (PawnOwner.psychicEntropy != null && Props.entropyGainPerDamage > 0)
+ {
+ PawnOwner.psychicEntropy.TryAddEntropy(dinfo.Amount * Props.entropyGainPerDamage, overLimit: true);
+ }
+
+ // Consume Hitpoints
+ currentHitPoints -= (int)dinfo.Amount;
+ if (currentHitPoints <= 0)
+ {
+ Break();
+ }
+ absorbed = true; // Damage was absorbed
+ }
+
+
+ // --- REMOVED: Method for allies, called by Harmony patch ---
+ // public bool TryAbsorbDamageForAllyOnly(DamageInfo dinfo, Pawn allyPawn)
+ // {
+ // Log.Message($"[GuardianFieldComp] TryAbsorbDamageForAllyOnly for {allyPawn.LabelShort} (target) by {PawnOwner.LabelShort} (caster).");
+ // Log.Message($" - transferAllyDamage: {Props.transferAllyDamage}");
+ // Log.Message($" - Active: {Active}");
+ // Log.Message($" - allyPawn == PawnOwner: {allyPawn == PawnOwner}");
+ // Log.Message($" - allyPawn.Faction: {allyPawn.Faction?.Name ?? "Null"}, PawnOwner.Faction: {PawnOwner.Faction?.Name ?? "Null"}, FactionMatch: {allyPawn.Faction == PawnOwner.Faction}");
+ // Log.Message($" - InRange: {Vector3.Distance(allyPawn.TrueCenter(), PawnOwner.TrueCenter()) <= Props.radius}");
+ // Log.Message($" - HasHP: {currentHitPoints >= dinfo.Amount}");
+
+ // // Check if ally damage transfer is enabled
+ // if (!Props.transferAllyDamage) { Log.Message(" -> transferAllyDamage is false."); return false; }
+
+ // // Shield must be active
+ // if (!Active) { Log.Message(" -> Shield is not Active."); return false; }
+
+ // // Cannot absorb damage for self (handled by PostPreApplyDamage)
+ // if (allyPawn == PawnOwner) { Log.Message(" -> Target is self."); return false; }
+
+ // // Only protect friendly pawns (same faction)
+ // if (allyPawn.Faction == null || PawnOwner.Faction == null || allyPawn.Faction != PawnOwner.Faction) { Log.Message(" -> Faction mismatch or null faction."); return false; }
+
+ // // Target must be in range
+ // if (Vector3.Distance(allyPawn.TrueCenter(), PawnOwner.TrueCenter()) > Props.radius) { Log.Message(" -> Target out of range."); return false; }
+
+ // // Check if shield has enough HP
+ // if (currentHitPoints < dinfo.Amount) { Log.Message(" -> Not enough HP to absorb damage."); return false; }
+
+ // // --- Absorption Success for ally ---
+ // Props.absorbEffecter?.Spawn(allyPawn.Position, allyPawn.Map).Cleanup();
+
+ // // Add entropy based on damage taken for ally
+ // if (PawnOwner.psychicEntropy != null && Props.entropyGainPerAllyDamage > 0)
+ // {
+ // PawnOwner.psychicEntropy.TryAddEntropy(dinfo.Amount * Props.entropyGainPerAllyDamage, overLimit: true);
+ // }
+
+ // // Consume Hitpoints
+ // currentHitPoints -= (int)dinfo.Amount;
+ // if (currentHitPoints <= 0)
+ // {
+ // Break();
+ // }
+
+ // Log.Message(" -> Damage absorption successful!");
+ // return true; // Damage was absorbed
+ // }
+
+ private void Break()
+ {
+ Props.breakEffecter?.Spawn(PawnOwner.Position, PawnOwner.Map).Cleanup();
+ ticksToReset = Props.rechargeDelay;
+ currentHitPoints = 0;
+ }
+
+ private void Reset()
+ {
+ if (PawnOwner.Spawned)
+ {
+ Props.reactivateEffecter?.Spawn(PawnOwner.Position, PawnOwner.Map).Cleanup();
+ }
+ currentHitPoints = HitPointsMax;
+ }
+
+ public override void PostDraw()
+ {
+ base.PostDraw();
+ if (!Active || PawnOwner.Map == null) return;
+
+ Vector3 drawPos = PawnOwner.Drawer.DrawPos;
+ drawPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
+
+ float alpha = GetCurrentAlpha();
+ if (alpha > 0f)
+ {
+ Color color = Props.color;
+ color.a *= alpha;
+ MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color);
+ Matrix4x4 matrix = default;
+ matrix.SetTRS(drawPos, Quaternion.identity, new Vector3(Props.radius * 2f, 1f, Props.radius * 2f));
+ Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldMat, 0, null, 0, MatPropertyBlock);
+ }
+ }
+
+ private float GetCurrentAlpha()
+ {
+ float idleAlpha = Mathf.Lerp(0.3f, 0.6f, (Mathf.Sin((float)parent.thingIDNumber + Time.realtimeSinceStartup * 1.5f) + 1f) / 2f);
+ float interceptAlpha = Mathf.Clamp01(1f - (float)(Find.TickManager.TicksGame - lastInterceptTicks) / 40f);
+ return Mathf.Max(idleAlpha, interceptAlpha);
+ }
+
+ // --- GIZMO ---
+ public override System.Collections.Generic.IEnumerable CompGetGizmosExtra()
+ {
+ if (PawnOwner != null && Find.Selector.SingleSelectedThing == PawnOwner)
+ {
+ yield return new Gizmo_GuardianShieldStatus { shield = this };
+ }
+ }
+ }
+
+ // Gizmo class copied from the user's example and adapted
+ [StaticConstructorOnStartup]
+ public class Gizmo_GuardianShieldStatus : Gizmo
+ {
+ public ThingComp_GuardianPsyField shield;
+ private static readonly Texture2D FullShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.3f, 0.8f, 0.8f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D;
+ private static readonly Texture2D EmptyShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.2f, 0.2f, 0.2f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D;
+
+ public override float GetWidth(float maxWidth) => 140f;
+
+ public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
+ {
+ Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), 75f);
+ Rect rect2 = rect.ContractedBy(6f);
+ Widgets.DrawWindowBackground(rect);
+
+ Rect labelRect = rect2;
+ labelRect.height = rect.height / 2f;
+ Text.Font = GameFont.Tiny;
+ Widgets.Label(labelRect, "Guardian Field");
+
+ Rect barRect = rect2;
+ barRect.yMin = rect2.y + rect2.height / 2f;
+ float fillPercent = (float)shield.currentHitPoints / shield.HitPointsMax;
+ Widgets.FillableBar(barRect, fillPercent, FullShieldBarTex, EmptyShieldBarTex, false);
+
+ Text.Font = GameFont.Small;
+ Text.Anchor = TextAnchor.MiddleCenter;
+
+ TaggedString statusText = shield.IsOnCooldown ? "Cooldown" : new TaggedString(shield.currentHitPoints + " / " + shield.HitPointsMax);
+ Widgets.Label(barRect, statusText);
+
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ return new GizmoResult(GizmoState.Clear);
+ }
+ }
+}
\ No newline at end of file