This commit is contained in:
2025-09-17 12:54:07 +08:00
parent 9e992fc08b
commit c77def63c2
123 changed files with 130 additions and 130 deletions

View File

@@ -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("ARA_BindDrone_Success".Translate(dronePawn.LabelShort, masterPawn.LabelShort), MessageTypeDefOf.PositiveEvent, historical: false);
}
else
{
Messages.Message("ARA_BindDrone_Failure".Translate(dronePawn.LabelShort, masterPawn.LabelShort), 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("ARA_BindDrone_NoDroneHediff".Translate(dronePawn.LabelShort), 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("ARA_BindDrone_AlreadyBound".Translate(dronePawn.LabelShort, 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("ARA_BindDrone_NoMasterHediff".Translate(masterPawn.LabelShort), MessageTypeDefOf.RejectInput, historical: false);
}
return false;
}
// All checks passed
return true;
}
public override bool CanApplyOn(LocalTargetInfo target, LocalTargetInfo dest)
{
return Valid(target);
}
}
}

View File

@@ -0,0 +1,13 @@
using Verse;
using RimWorld;
namespace ArachnaeSwarm
{
public class CompProperties_AbilityBindDrone : CompProperties_AbilityEffect
{
public CompProperties_AbilityBindDrone()
{
this.compClass = typeof(CompAbilityEffect_BindDrone);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,44 @@
using Verse;
namespace ArachnaeSwarm
{
public class HediffCompProperties_HiveMindMaster : HediffCompProperties
{
public int scanIntervalTicks = 60; // Default to 1 second
public HediffCompProperties_HiveMindMaster()
{
this.compClass = typeof(HediffComp_HiveMindMaster);
}
}
public class HediffComp_HiveMindMaster : HediffComp
{
private Hediff_HiveMindMaster parentHediff;
private HediffCompProperties_HiveMindMaster Props => (HediffCompProperties_HiveMindMaster)this.props;
public override void CompPostMake()
{
base.CompPostMake();
parentHediff = (Hediff_HiveMindMaster)this.parent;
// Immediately try to bind drones when the hediff is first applied.
parentHediff.TryBindAllAvailableDrones();
}
public override void CompPostTick(ref float severityAdjustment)
{
base.CompPostTick(ref severityAdjustment);
// Periodically check for new unbound drones based on XML value.
if (Find.TickManager.TicksGame % Props.scanIntervalTicks == 0)
{
if (parentHediff == null)
{
parentHediff = (Hediff_HiveMindMaster)this.parent;
}
parentHediff.TryBindAllAvailableDrones();
}
}
}
}

View File

@@ -0,0 +1,72 @@
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<HediffComp_HiveMindDrone>();
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
{
return this.pawn.Dead;
}
}
public override void PostRemoved()
{
base.PostRemoved();
// Deregister from the master when this hediff is removed
if (this.target is Pawn master && master != null && !master.Destroyed)
{
var masterHediff = master.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("ARA_HiveMindMaster")) as Hediff_HiveMindMaster;
masterHediff?.DeregisterDrone(this.pawn);
}
}
public override string TipStringExtra
{
get
{
if (this.target is Pawn master && master != null)
{
return "ARA_TipString_LinkedTo".Translate(master.LabelShortCap);
}
return "ARA_TipString_NotLinked".Translate();
}
}
// PostTick logic moved to HediffComp_HiveMindDrone
}
}

View File

@@ -0,0 +1,147 @@
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
public class Hediff_HiveMindMaster : HediffWithComps
{
private List<Pawn> drones = new List<Pawn>();
public override string LabelInBrackets => drones.Count.ToString();
public override bool ShouldRemove => false;
public override string TipStringExtra
{
get
{
if (drones.NullOrEmpty())
{
return "\n" + "ARA_TipString_NoDronesBound".Translate().Colorize(ColoredText.SubtleGrayColor);
}
System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder();
stringBuilder.AppendLine("\n" + "ARA_TipString_ControlledDrones".Translate().Colorize(ColoredText.TipSectionTitleColor));
foreach (var drone in drones)
{
string backstoryInfo = "";
if (drone.story?.TitleShort is string adulthoodTitle && !adulthoodTitle.NullOrEmpty())
{
backstoryInfo = $" ({adulthoodTitle.Colorize(ColoredText.SubtleGrayColor)})";
}
stringBuilder.AppendLine($" - {drone.LabelShortCap}{backstoryInfo}");
}
return stringBuilder.ToString();
}
}
public override void ExposeData()
{
base.ExposeData();
Scribe_Collections.Look(ref drones, "drones", LookMode.Reference);
if (drones == null)
{
drones = new List<Pawn>();
}
}
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 TryBindAllAvailableDrones()
{
if (this.pawn?.Map == null) return;
// First, clean up any dead, despawned, or destroyed drones from the list.
int removedCount = drones.RemoveAll(drone => drone == null || drone.Dead || drone.Destroyed);
// Find all pawns on the map that could be potential drones
List<Pawn> potentialDrones = this.pawn.Map.mapPawns.AllPawnsSpawned
.Where(p => p.health.hediffSet.HasHediff(HediffDef.Named("ARA_HiveMindDrone")))
.ToList();
int boundCount = 0;
foreach (var drone in potentialDrones)
{
Hediff_HiveMindDrone droneHediff = drone.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("ARA_HiveMindDrone")) as Hediff_HiveMindDrone;
// Check if the drone is unlinked (target is null) and not already in our list
if (droneHediff != null && droneHediff.target == null && !drones.Contains(drone))
{
droneHediff.target = this.pawn; // Set the drone's target to this master
drones.Add(drone);
Log.Message($"[ArachnaeSwarm] Master {this.pawn.LabelShort} automatically bound drone {drone.LabelShort}.");
boundCount++;
}
}
// Update severity if anything changed (drones were added or removed)
if (boundCount > 0 || removedCount > 0)
{
UpdateSeverity();
}
}
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);
}
}
}
}
}