diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 29adbd5..7120700 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_Abilities_HiveMind.xml b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities_HiveMind.xml new file mode 100644 index 0000000..abad623 --- /dev/null +++ b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities_HiveMind.xml @@ -0,0 +1,35 @@ + + + + + ARA_BindDrone + + Allows the hive mind master to bind with an unlinked drone, bringing it under direct psychic control. + UI/Commands/EggSpew + + CastAbilityOnThing + true + 60 + + + Verb_CastAbility + 0.5 + 99 + + true + false + true + false + false + false + + + + +
  • + +
  • +
    +
    + +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/BackstoryDefs/ARA_BackstoryDef.xml b/1.6/1.6/Defs/BackstoryDefs/ARA_BackstoryDef.xml index 1b755ad..c63cee7 100644 --- a/1.6/1.6/Defs/BackstoryDefs/ARA_BackstoryDef.xml +++ b/1.6/1.6/Defs/BackstoryDefs/ARA_BackstoryDef.xml @@ -15,6 +15,9 @@ 2 2 + +
  • ARA_HiveMindMaster
  • +
  • ArachnaeQueen_spawnCategoriesA
  • @@ -53,9 +56,13 @@
  • ArachnaeNode_spawnCategoriesA
  • + +
  • ARA_HiveMindDrone
  • +
    + true - + Arachnae_Node_BS_Adult_1 阿拉克涅督虫种 diff --git a/1.6/Defs/HediffDefs/ARA_Hediffs_HiveMind.xml b/1.6/1.6/Defs/HediffDefs/ARA_Hediffs_HiveMind.xml similarity index 79% rename from 1.6/Defs/HediffDefs/ARA_Hediffs_HiveMind.xml rename to 1.6/1.6/Defs/HediffDefs/ARA_Hediffs_HiveMind.xml index 02f3fac..421cc2a 100644 --- a/1.6/Defs/HediffDefs/ARA_Hediffs_HiveMind.xml +++ b/1.6/1.6/Defs/HediffDefs/ARA_Hediffs_HiveMind.xml @@ -3,7 +3,7 @@ ARA_HiveMindMaster - + The central node of a hive mind, connected to multiple drone beings. ArachnaeSwarm.Hediff_HiveMindMaster (0.8, 0.3, 0.8) @@ -19,18 +19,20 @@ - + ARA_HiveMindDrone - + A drone being, psychically linked to a master node. If the master dies, this unit will cease to function. ArachnaeSwarm.Hediff_HiveMindDrone (0.6, 0.4, 0.8) false true - -
  • - + +
  • + 6400
  • + +
    diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml index 9388dba..ddc159f 100644 --- a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml +++ b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml @@ -16,6 +16,7 @@
  • ARA_EggSpew
  • ARA_AcidSprayBurst
  • +
  • ARA_BindDrone
  • diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index 115002f..73f91ed 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -77,6 +77,12 @@ + + + + + + diff --git a/Source/ArachnaeSwarm/CompAbilityEffect_BindDrone.cs b/Source/ArachnaeSwarm/CompAbilityEffect_BindDrone.cs new file mode 100644 index 0000000..c206b95 --- /dev/null +++ b/Source/ArachnaeSwarm/CompAbilityEffect_BindDrone.cs @@ -0,0 +1,108 @@ +using RimWorld; +using Verse; +using System.Linq; // For LINQ operations + +namespace ArachnaeSwarm +{ + public class CompAbilityEffect_BindDrone : CompAbilityEffect + { + public override void Apply(LocalTargetInfo target, LocalTargetInfo dest) + { + base.Apply(target, dest); + + Pawn dronePawn = target.Pawn; + Pawn masterPawn = parent.pawn; // The pawn casting the ability + + if (masterPawn != null && dronePawn != null) + { + Hediff_HiveMindMaster masterHediff = masterPawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("ARA_HiveMindMaster")) as Hediff_HiveMindMaster; + if (masterHediff != null) + { + if (masterHediff.TryBindDrone(dronePawn)) + { + Messages.Message($"Successfully bound {dronePawn.LabelShort} to {masterPawn.LabelShort}'s hive mind.", MessageTypeDefOf.PositiveEvent, historical: false); + } + else + { + Messages.Message($"Failed to bind {dronePawn.LabelShort} to {masterPawn.LabelShort}'s hive mind. Check logs for details.", MessageTypeDefOf.NegativeEvent, historical: false); + } + } + else + { + Log.Error($"[ArachnaeSwarm] Master {masterPawn.LabelShort} tried to bind a drone but does not have Hediff_HiveMindMaster."); + } + } + } + + public override bool Valid(LocalTargetInfo target, bool throwMessages = false) + { + if (!base.Valid(target, throwMessages)) + { + return false; + } + + Pawn dronePawn = target.Pawn; + Pawn masterPawn = parent.pawn; + + // Target must be a pawn + if (dronePawn == null) + { + if (throwMessages) + { + Messages.Message("MustTargetPawn".Translate(parent.def.label), MessageTypeDefOf.RejectInput, historical: false); + } + return false; + } + + // Target must be on the same map as the caster + if (dronePawn.Map != masterPawn.Map) + { + if (throwMessages) + { + Messages.Message("CannotTargetDifferentMap".Translate(), MessageTypeDefOf.RejectInput, historical: false); + } + return false; + } + + // Target must have ARA_HiveMindDrone hediff + Hediff_HiveMindDrone droneHediff = dronePawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("ARA_HiveMindDrone")) as Hediff_HiveMindDrone; + if (droneHediff == null) + { + if (throwMessages) + { + Messages.Message($"Target {dronePawn.LabelShort} does not have the 'ARA_HiveMindDrone' hediff.", MessageTypeDefOf.RejectInput, historical: false); + } + return false; + } + + // Target must not be already bound to another master + if (droneHediff.target != null && droneHediff.target != masterPawn) + { + if (throwMessages) + { + Messages.Message($"Target {dronePawn.LabelShort} is already bound to {droneHediff.target.LabelShort}.", MessageTypeDefOf.RejectInput, historical: false); + } + return false; + } + + // Caster must have ARA_HiveMindMaster hediff + Hediff_HiveMindMaster masterHediff = masterPawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("ARA_HiveMindMaster")) as Hediff_HiveMindMaster; + if (masterHediff == null) + { + if (throwMessages) + { + Messages.Message($"Caster {masterPawn.LabelShort} does not have the 'ARA_HiveMindMaster' hediff.", MessageTypeDefOf.RejectInput, historical: false); + } + return false; + } + + // All checks passed + return true; + } + + public override bool CanApplyOn(LocalTargetInfo target, LocalTargetInfo dest) + { + return Valid(target); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/CompProperties_AbilityBindDrone.cs b/Source/ArachnaeSwarm/CompProperties_AbilityBindDrone.cs new file mode 100644 index 0000000..959f8d2 --- /dev/null +++ b/Source/ArachnaeSwarm/CompProperties_AbilityBindDrone.cs @@ -0,0 +1,13 @@ +using Verse; +using RimWorld; + +namespace ArachnaeSwarm +{ + public class CompProperties_AbilityBindDrone : CompProperties_AbilityEffect + { + public CompProperties_AbilityBindDrone() + { + this.compClass = typeof(CompAbilityEffect_BindDrone); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/HediffCompProperties_HiveMindDrone.cs b/Source/ArachnaeSwarm/HediffCompProperties_HiveMindDrone.cs new file mode 100644 index 0000000..921f2ea --- /dev/null +++ b/Source/ArachnaeSwarm/HediffCompProperties_HiveMindDrone.cs @@ -0,0 +1,14 @@ +using Verse; + +namespace ArachnaeSwarm +{ + public class HediffCompProperties_HiveMindDrone : HediffCompProperties + { + public int unlinkedDieDelayTicks = 1800; // Default to 30 seconds + + public HediffCompProperties_HiveMindDrone() + { + this.compClass = typeof(HediffComp_HiveMindDrone); // Reference the Comp class + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/HediffComp_HiveMindDrone.cs b/Source/ArachnaeSwarm/HediffComp_HiveMindDrone.cs new file mode 100644 index 0000000..09302cd --- /dev/null +++ b/Source/ArachnaeSwarm/HediffComp_HiveMindDrone.cs @@ -0,0 +1,57 @@ +using Verse; +using RimWorld; + +namespace ArachnaeSwarm +{ + public class HediffComp_HiveMindDrone : HediffComp + { + public HediffCompProperties_HiveMindDrone Props => (HediffCompProperties_HiveMindDrone)this.props; + + public int TicksUnlinked => ticksUnlinked; // Expose as public property + private int ticksUnlinked = 0; + + public override void CompExposeData() + { + base.CompExposeData(); + Scribe_Values.Look(ref ticksUnlinked, "ticksUnlinked", 0); + } + + public override void CompPostTick(ref float severityAdjustment) + { + base.CompPostTick(ref severityAdjustment); + + // Only check if pawn is spawned and on a map + if (parent.pawn.Spawned && parent.pawn.Map != null) + { + // We use parent.pawn.IsHashIntervalTick(60) for performance + if (!parent.pawn.IsHashIntervalTick(60)) + return; + + Hediff_HiveMindDrone droneHediff = parent as Hediff_HiveMindDrone; + if (droneHediff == null) return; // Should not happen + + Pawn masterPawn = droneHediff.target as Pawn; + + if (masterPawn == null || masterPawn.Destroyed || masterPawn.Dead || !masterPawn.health.hediffSet.HasHediff(HediffDef.Named("ARA_HiveMindMaster"))) + { + // Master is invalid or unlinked, start/continue unlinked timer + ticksUnlinked += 60; // Increment by 60 because we check every 60 ticks + if (ticksUnlinked >= Props.unlinkedDieDelayTicks) + { + Log.Message($"[ArachnaeSwarm] Drone {parent.pawn.LabelShort} was unlinked from master for too long and will die. Forcing death."); + // Ensure the pawn is killed only once and prevent further ticks + if (!parent.pawn.Dead && !parent.pawn.Destroyed) + { + parent.pawn.Kill(null, parent); + } + } + } + else + { + // Master is valid, reset unlinked timer + ticksUnlinked = 0; + } + } + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Hediff_HiveMindDrone.cs b/Source/ArachnaeSwarm/Hediff_HiveMindDrone.cs new file mode 100644 index 0000000..87760f0 --- /dev/null +++ b/Source/ArachnaeSwarm/Hediff_HiveMindDrone.cs @@ -0,0 +1,73 @@ +using System.Linq; +using RimWorld; +using Verse; + +namespace ArachnaeSwarm +{ + public class Hediff_HiveMindDrone : HediffWithTarget + { + public override string LabelBase + { + get + { + string baseLabel = base.LabelBase + " (" + (target != null ? target.LabelShortCap : "未连接") + ")"; + + // Get the HediffComp_HiveMindDrone to access ticksUnlinked + HediffComp_HiveMindDrone comp = this.TryGetComp(); + if (comp != null) + { + // Safely cast target to Pawn for Dead and health checks + Pawn masterPawn = target as Pawn; + + if (masterPawn == null || masterPawn.Destroyed || masterPawn.Dead || !masterPawn.health.hediffSet.HasHediff(HediffDef.Named("ARA_HiveMindMaster"))) + { + float timeLeftSecs = (comp.Props.unlinkedDieDelayTicks - comp.TicksUnlinked) / 60f; + if (timeLeftSecs > 0) + { + return baseLabel + " (死亡倒计时: " + timeLeftSecs.ToString("F1") + "s)"; + } + } + } + return baseLabel; + } + } + + public override void PostAdd(DamageInfo? dinfo) + { + base.PostAdd(dinfo); + // No direct linking in PostAdd, master will link manually + } + + public override bool ShouldRemove + { + get + { + // Only remove if base ShouldRemove is true, OR if pawn is dead + // We do NOT want to remove it just because target is invalid, as the Comp will handle delayed death + if (base.ShouldRemove && pawn.Dead) + { + return true; + } + // Also remove if pawn is no longer spawned or on a map + if (!pawn.Spawned || pawn.Map == null) + { + return true; + } + return false; // Let the Comp handle the unlinked death + } + } + + public override void PostRemoved() + { + base.PostRemoved(); + // Deregister from the master when this hediff is removed + // Ensure target is a Pawn and not dead before attempting to deregister + if (this.target is Pawn master && master != null && !master.Destroyed && !master.Dead) + { + var masterHediff = master.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("ARA_HiveMindMaster")) as Hediff_HiveMindMaster; + masterHediff?.DeregisterDrone(this.pawn); + } + } + // PostTick logic moved to HediffComp_HiveMindDrone + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Hediff_HiveMindMaster.cs b/Source/ArachnaeSwarm/Hediff_HiveMindMaster.cs new file mode 100644 index 0000000..2558b1e --- /dev/null +++ b/Source/ArachnaeSwarm/Hediff_HiveMindMaster.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Linq; +using RimWorld; +using Verse; + +namespace ArachnaeSwarm +{ + public class Hediff_HiveMindMaster : Hediff + { + private List drones = new List(); + + public override string LabelInBrackets => drones.Count.ToString(); + + public override void ExposeData() + { + base.ExposeData(); + Scribe_Collections.Look(ref drones, "drones", LookMode.Reference); + if (drones == null) + { + drones = new List(); + } + } + + public bool TryBindDrone(Pawn drone) + { + if (drone == null || drone.Dead || !drone.Spawned || drone.Map != this.pawn.Map) + { + Log.Message($"[ArachnaeSwarm] Cannot bind drone {drone?.LabelShort ?? "null"}: Invalid pawn state."); + return false; + } + + Hediff_HiveMindDrone droneHediff = drone.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("ARA_HiveMindDrone")) as Hediff_HiveMindDrone; + if (droneHediff == null) + { + Log.Message($"[ArachnaeSwarm] Cannot bind drone {drone.LabelShort}: Does not have ARA_HiveMindDrone hediff."); + return false; + } + + if (droneHediff.target != null && droneHediff.target != this.pawn) + { + Log.Message($"[ArachnaeSwarm] Cannot bind drone {drone.LabelShort}: Already bound to another master ({droneHediff.target.LabelShort})."); + return false; + } + + if (drones.Contains(drone)) + { + Log.Message($"[ArachnaeSwarm] Drone {drone.LabelShort} is already bound to this master."); + return false; + } + + droneHediff.target = this.pawn; // Set the drone's target to this master + drones.Add(drone); + UpdateSeverity(); + Log.Message($"[ArachnaeSwarm] Master {this.pawn.LabelShort} successfully bound drone {drone.LabelShort}."); + return true; + } + + public void DeregisterDrone(Pawn drone) + { + if (drones.Contains(drone)) + { + drones.Remove(drone); + UpdateSeverity(); + } + } + + private void UpdateSeverity() + { + this.Severity = drones.Count; + } + + public override void PostRemoved() + { + base.PostRemoved(); + // Kill all drones when the master hediff is removed (e.g., master dies) + foreach (var drone in drones.ToList()) // ToList() to avoid collection modification issues + { + if (drone != null && !drone.Dead) + { + Log.Message($"[ArachnaeSwarm] Master {pawn.LabelShort} died, killing drone {drone.LabelShort}."); + drone.Kill(null, this); + } + } + } + } +} \ No newline at end of file