diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 3c6968a..658be22 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/HediffDefs/ARA_GuardianPsyField_Hediff.xml b/1.6/1.6/Defs/HediffDefs/ARA_GuardianPsyField_Hediff.xml index 0b5e5e1..c197be1 100644 --- a/1.6/1.6/Defs/HediffDefs/ARA_GuardianPsyField_Hediff.xml +++ b/1.6/1.6/Defs/HediffDefs/ARA_GuardianPsyField_Hediff.xml @@ -19,18 +19,15 @@ 60 - 0.5 + + 0.001 0.1 0.01 - - true - true - true - - (0.3, 0.8, 0.8) + (0.5, 0.3, 0.9, 0.5) Interceptor_BlockedProjectile + Interceptor_BlockedProjectile Shield_Break BulletShieldGenerator_Reactivate diff --git a/Source/ArachnaeSwarm/Hediff_DynamicInterceptor.cs b/Source/ArachnaeSwarm/Hediff_DynamicInterceptor.cs index 05a3809..3c939c3 100644 --- a/Source/ArachnaeSwarm/Hediff_DynamicInterceptor.cs +++ b/Source/ArachnaeSwarm/Hediff_DynamicInterceptor.cs @@ -7,19 +7,12 @@ namespace ArachnaeSwarm { public class Hediff_DynamicInterceptor : HediffWithComps { - // This Hediff's job is to add and remove our custom ThingComp_GuardianPsyField. - public CompProperties_GuardianPsyField GuardianProps { get { - var compProps = def.comps?.FirstOrDefault(c => c is HediffCompProperties_DynamicInterceptor); - if (compProps is HediffCompProperties_DynamicInterceptor customProps) - { - // Note: The XML now needs to contain CompProperties_GuardianPsyField - return customProps.guardianProps; - } - return null; + var hediffCompProps = def.comps?.FirstOrDefault(c => c is HediffCompProperties_DynamicInterceptor) as HediffCompProperties_DynamicInterceptor; + return hediffCompProps?.guardianProps; } } @@ -34,6 +27,7 @@ namespace ArachnaeSwarm 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); } @@ -58,8 +52,7 @@ namespace ArachnaeSwarm // This comp will hold the properties for our custom interceptor public class HediffCompProperties_DynamicInterceptor : HediffCompProperties { - // This will now point to our custom properties class - public CompProperties_GuardianPsyField guardianProps; + public CompProperties_GuardianPsyField guardianProps; // Nested properties public HediffCompProperties_DynamicInterceptor() { @@ -70,6 +63,6 @@ namespace ArachnaeSwarm // A simple HediffComp to go with the properties public class HediffComp_DynamicInterceptor : HediffComp { - public HediffCompProperties_DynamicInterceptor Props => (HediffCompProperties_DynamicInterceptor)props; + 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 index fde8b4a..d8efac6 100644 --- a/Source/ArachnaeSwarm/ThingComp_GuardianPsyField.cs +++ b/Source/ArachnaeSwarm/ThingComp_GuardianPsyField.cs @@ -14,14 +14,14 @@ namespace ArachnaeSwarm public int rechargeHitPointsIntervalTicks = 60; // Ticks to restore 1 HP // New properties for psyfocus/entropy mechanics - public float psyfocusCostForFullRecharge = 0.5f; // 50% psyfocus cost - public float entropyGainPerDamage = 0.5f; // 1 entropy per 2 damage + // 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 bool interceptGroundProjectiles = true; - public bool interceptAirProjectiles = true; - public bool interceptNonHostileProjectiles = false; - public EffecterDef interceptEffecter; public EffecterDef breakEffecter; public EffecterDef reactivateEffecter; @@ -57,11 +57,18 @@ namespace ArachnaeSwarm { get { - if (PawnOwner == null || !PawnOwner.Spawned || PawnOwner.Dead || PawnOwner.Downed || IsOnCooldown || currentHitPoints <= 0) + // 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")); - return hediff != null; + 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; } } @@ -92,60 +99,134 @@ namespace ArachnaeSwarm Reset(); } } - else if (currentHitPoints < HitPointsMax) + // Only allow recharge if the shield is 'Active' (i.e., has psyfocus) and not on cooldown + else if (Active && currentHitPoints < HitPointsMax) { - wasNotAtFullHp = true; // Mark that the shield was damaged - if(this.parent.IsHashIntervalTick(Props.rechargeHitPointsIntervalTicks)) + // Check if there's enough psyfocus to pay for this interval's recharge + if (PawnOwner.psychicEntropy != null && PawnOwner.psychicEntropy.CurrentPsyfocus >= Props.psyfocusCostPerInterval) { - currentHitPoints += (int)(HitPointsMax * Props.hitPointsPctPerInterval); - if(currentHitPoints > HitPointsMax) currentHitPoints = HitPointsMax; + 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) { - // Shield just reached full charge - wasNotAtFullHp = false; - if (PawnOwner.psychicEntropy != null && Props.psyfocusCostForFullRecharge > 0) - { - float maxPsyfocus = PawnOwner.GetStatValue(StatDefOf.PsychicEntropyMax); - PawnOwner.psychicEntropy.OffsetPsyfocusDirectly(-maxPsyfocus * Props.psyfocusCostForFullRecharge); - } + 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; - bool isHostile = projectile.Launcher != null && projectile.Launcher.HostileTo(PawnOwner.Faction); - if (!isHostile && !Props.interceptNonHostileProjectiles) return false; - - if (Vector3.Distance(projectile.ExactPosition, PawnOwner.TrueCenter()) > Props.radius) 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(); - float damageAmount = projectile.DamageAmount; + // No longer consuming hitpoints here, PostPreApplyDamage will handle it for self. + // The Harmony_DamageWorker for allies will handle it for allies. - // Add entropy based on damage + 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(damageAmount * Props.entropyGainPerDamage, overLimit: true); + PawnOwner.psychicEntropy.TryAddEntropy(dinfo.Amount * Props.entropyGainPerDamage, overLimit: true); } // Consume Hitpoints - currentHitPoints -= (int)damageAmount; + currentHitPoints -= (int)dinfo.Amount; if (currentHitPoints <= 0) { Break(); } - - return true; + 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();