diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 4b840a2..2bd6e5d 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.xml b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml index e4b4b28..98ca5d0 100644 --- a/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml +++ b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml @@ -21,7 +21,7 @@ Verb_CastAbility 2 - -1 + 80 true true true diff --git a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml index a0dbb15..7e0e8f2 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml @@ -498,6 +498,24 @@ 1 14 +
  • + true + + ARA_InsectJelly + 500 + 500 + 999 + 9999 + +
  • + Spelopede + 3 + 600 +
  • + + CocoonDestroyed + + diff --git a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml index a7cd62e..c263409 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml @@ -497,233 +497,4 @@
  • AteWithoutTable
  • SleptOutside
  • SleptOnGround
  • -
  • SleptInCold
  • -
  • SleptInHeat
  • -
  • Ugly
  • -
  • AteKibble
  • -
  • AteInsectMeatDirect
  • -
  • AteInsectMeatAsIngredient
  • -
  • AteRawFood
  • -
  • AteHumanlikeMeatDirect
  • -
  • AteHumanlikeMeatAsIngredient
  • -
  • KnowButcheredHumanlikeCorpse
  • -
  • ButcheredHumanlikeCorpseOpinion
  • -
  • AteRawHumanlikeMeat
  • - - - - - - - - 0 - 0 - - 0 - 0 - 0 - - 0 - 0 - 0 - - - - - - - 2000 - 5 - - - 1.75 - 250 - - - - 2 - - 0.5 - - - 0 - 450 - 600 - - - 1 - - 5 - - 0 - - 0.95 - 0.95 - - 0.1 - - - 0.25 - - - - - - - - - 0.6 - 0.8 - 0.5 - - - 5 - - - 0 - - - - - ArachnaeQueen_Body - Normal - - ARA_Humanlike - - Humanlike - - Leather_Light - Meat_Megaspider - HumanStandard - Filth_BloodInsect - Filth_BloodSmear - - 10 - - 10 - - Steel - Steel - - Pawn_Melee_BigBash_HitPawn - Pawn_Melee_BigBash_HitBuilding - Pawn_Melee_BigBash_Miss - Pawn_MeleeDodge - - - - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - - -
  • - ARA_Queen_Adult - 0 - Pawn_HiveQueen_Wounded - Pawn_HiveQueen_Death - Pawn_HiveQueen_Call - Pawn_HiveQueen_Angry -
  • -
    - false - -
    - - - -
  • - - -
  • Poke
  • - - 16 - 2 - HeadAttackTool - true - 0.01 - -
  • - - -
  • Blunt
  • -
  • Poke
  • - - 35 - 2.5 - Legs - -
  • - - -
  • Stab
  • - - 50 - 3 - Legs - -
  • - - -
  • Cut
  • - - 30 - 2 - Hands - -
    - - - - - - - - - - - \ No newline at end of file +
  • SleptInCold
  • \ No newline at end of file diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index 51db4f4..a9ea442 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -85,7 +85,11 @@ - + + + + + diff --git a/Source/ArachnaeSwarm/WULA_AutoMechCarrier/CompAutoMechCarrier.cs b/Source/ArachnaeSwarm/WULA_AutoMechCarrier/CompAutoMechCarrier.cs new file mode 100644 index 0000000..312faf0 --- /dev/null +++ b/Source/ArachnaeSwarm/WULA_AutoMechCarrier/CompAutoMechCarrier.cs @@ -0,0 +1,195 @@ +using RimWorld; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using Verse; +using Verse.AI.Group; + +namespace ArachnaeSwarm +{ + public class CompAutoMechCarrier : CompMechCarrier + { + #region Reflected Fields + private static FieldInfo spawnedPawnsField; + private static FieldInfo cooldownTicksRemainingField; + private static FieldInfo innerContainerField; + + private List SpawnedPawns + { + get + { + if (spawnedPawnsField == null) + spawnedPawnsField = typeof(CompMechCarrier).GetField("spawnedPawns", BindingFlags.NonPublic | BindingFlags.Instance); + return (List)spawnedPawnsField.GetValue(this); + } + } + + private int CooldownTicksRemaining + { + get + { + if (cooldownTicksRemainingField == null) + cooldownTicksRemainingField = typeof(CompMechCarrier).GetField("cooldownTicksRemaining", BindingFlags.NonPublic | BindingFlags.Instance); + return (int)cooldownTicksRemainingField.GetValue(this); + } + set + { + if (cooldownTicksRemainingField == null) + cooldownTicksRemainingField = typeof(CompMechCarrier).GetField("cooldownTicksRemaining", BindingFlags.NonPublic | BindingFlags.Instance); + cooldownTicksRemainingField.SetValue(this, value); + } + } + + private ThingOwner InnerContainer + { + get + { + if (innerContainerField == null) + innerContainerField = typeof(CompMechCarrier).GetField("innerContainer", BindingFlags.NonPublic | BindingFlags.Instance); + return (ThingOwner)innerContainerField.GetValue(this); + } + } + #endregion + + public CompProperties_AutoMechCarrier AutoProps => (CompProperties_AutoMechCarrier)props; + + private int TotalPawnCapacity => AutoProps.productionQueue.Sum(e => e.count); + + private int LiveSpawnedPawnsCount(PawnKindDef kind) + { + SpawnedPawns.RemoveAll(p => p == null || p.Destroyed); + return SpawnedPawns.Count(p => p.kindDef == kind); + } + + private AcceptanceReport CanSpawnNow(PawnKindDef kind) + { + if (parent is Pawn pawn && (pawn.IsSelfShutdown() || !pawn.Awake() || pawn.Downed || pawn.Dead || !pawn.Spawned)) + return false; + if (CooldownTicksRemaining > 0) + return "CooldownTime".Translate() + " " + CooldownTicksRemaining.ToStringSecondsFromTicks(); + + PawnProductionEntry entry = AutoProps.productionQueue.First(e => e.pawnKind == kind); + int cost = entry.cost ?? Props.costPerPawn; + + if (!AutoProps.freeProduction && InnerContainer.TotalStackCountOfDef(Props.fixedIngredient) < cost) + return "MechCarrierNotEnoughResources".Translate(); + return true; + } + + private void TrySpawnPawn(PawnKindDef kind) + { + PawnGenerationRequest request = new PawnGenerationRequest(kind, parent.Faction, PawnGenerationContext.NonPlayer, -1, forceGenerateNewPawn: true); + Pawn pawn = PawnGenerator.GeneratePawn(request); + GenSpawn.Spawn(pawn, parent.Position, parent.Map); + SpawnedPawns.Add(pawn); + + if (parent is Pawn p && p.GetLord() != null) + p.GetLord().AddPawn(pawn); + + if (!AutoProps.freeProduction) + { + PawnProductionEntry entry = AutoProps.productionQueue.First(e => e.pawnKind == kind); + int costLeft = entry.cost ?? Props.costPerPawn; + + List things = new List(InnerContainer); + for (int j = 0; j < things.Count; j++) + { + Thing thing = InnerContainer.Take(things[j], Mathf.Min(things[j].stackCount, costLeft)); + costLeft -= thing.stackCount; + thing.Destroy(); + if (costLeft <= 0) break; + } + } + + PawnProductionEntry spawnEntry = AutoProps.productionQueue.First(e => e.pawnKind == kind); + CooldownTicksRemaining = spawnEntry.cooldownTicks ?? Props.cooldownTicks; + + if (Props.spawnedMechEffecter != null) + EffecterTrigger(Props.spawnedMechEffecter, Props.attachSpawnedMechEffecter, pawn); + if (Props.spawnEffecter != null) + EffecterTrigger(Props.spawnEffecter, Props.attachSpawnedEffecter, parent); + } + + private void EffecterTrigger(EffecterDef effecterDef, bool attach, Thing target) + { + Effecter effecter = new Effecter(effecterDef); + effecter.Trigger(attach ? ((TargetInfo)target) : new TargetInfo(target.Position, target.Map), TargetInfo.Invalid); + effecter.Cleanup(); + } + + public override void CompTick() + { + base.CompTick(); + + if (parent.IsHashIntervalTick(60)) // 每秒检查一次 + { + // 检查是否有抑制生产的Hediff + if (AutoProps.disableHediff != null && (parent as Pawn)?.health.hediffSet.HasHediff(AutoProps.disableHediff) == true) + { + return; // 有Hediff,停止生产 + } + + // 1. 先检查是否满员 + bool isFull = true; + foreach (var entry in AutoProps.productionQueue) + { + if (LiveSpawnedPawnsCount(entry.pawnKind) < entry.count) + { + isFull = false; + break; + } + } + + if (isFull) + { + return; // 如果已满员,则不进行任何操作,包括冷却计时 + } + + // 2. 如果未满员,才检查冷却时间 + if (CooldownTicksRemaining > 0) return; + + // 3. 寻找空位并生产 + foreach (var entry in AutoProps.productionQueue) + { + if (LiveSpawnedPawnsCount(entry.pawnKind) < entry.count) + { + if (CanSpawnNow(entry.pawnKind).Accepted) + { + TrySpawnPawn(entry.pawnKind); + break; // 每次只生产一个 + } + } + } + } + } + + public override IEnumerable CompGetGizmosExtra() + { + // 移除所有Gizmo逻辑 + return Enumerable.Empty(); + } + + public override string CompInspectStringExtra() + { + SpawnedPawns.RemoveAll(p => p == null || p.Destroyed); + string text = "Pawns: " + SpawnedPawns.Count + " / " + TotalPawnCapacity; + + foreach (var entry in AutoProps.productionQueue) + { + text += $"\n- {entry.pawnKind.LabelCap}: {LiveSpawnedPawnsCount(entry.pawnKind)} / {entry.count}"; + } + + if (CooldownTicksRemaining > 0) + { + text += "\n" + "CooldownTime".Translate() + ": " + CooldownTicksRemaining.ToStringSecondsFromTicks(); + } + + if (!AutoProps.freeProduction) + { + text += "\n" + base.CompInspectStringExtra(); + } + return text; + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/WULA_AutoMechCarrier/CompProperties_AutoMechCarrier.cs b/Source/ArachnaeSwarm/WULA_AutoMechCarrier/CompProperties_AutoMechCarrier.cs new file mode 100644 index 0000000..a1fe812 --- /dev/null +++ b/Source/ArachnaeSwarm/WULA_AutoMechCarrier/CompProperties_AutoMechCarrier.cs @@ -0,0 +1,54 @@ +using RimWorld; +using Verse; +using System.Collections.Generic; + +namespace ArachnaeSwarm +{ + public class CompProperties_AutoMechCarrier : CompProperties_MechCarrier + { + // XML中定义,生产是否消耗资源 + public bool freeProduction = false; + + // 如果单位拥有这个Hediff,则停止生产 + public HediffDef disableHediff; + + // 定义生产队列 + public List productionQueue = new List(); + + public CompProperties_AutoMechCarrier() + { + // 确保这个属性类指向我们新的功能实现类 + compClass = typeof(CompAutoMechCarrier); + } + + public override IEnumerable ConfigErrors(ThingDef parentDef) + { + foreach (string error in base.ConfigErrors(parentDef)) + { + yield return error; + } + + if (productionQueue.NullOrEmpty()) + { + yield return "CompProperties_AutoMechCarrier must have at least one entry in productionQueue."; + } + } + + public override void ResolveReferences(ThingDef parentDef) + { + base.ResolveReferences(parentDef); + // Prevent division by zero if costPerPawn is not set, which the base game AI might try to access. + if (costPerPawn <= 0) + { + costPerPawn = 1; + } + + // 如果spawnPawnKind为空(因为我们用了新的队列系统), + // 就从队列里取第一个作为“假”值,以防止基类方法在生成Gizmo标签时出错。 + if (spawnPawnKind == null && !productionQueue.NullOrEmpty()) + { + spawnPawnKind = productionQueue[0].pawnKind; + } + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/WULA_AutoMechCarrier/PawnProductionEntry.cs b/Source/ArachnaeSwarm/WULA_AutoMechCarrier/PawnProductionEntry.cs new file mode 100644 index 0000000..b8e8c9e --- /dev/null +++ b/Source/ArachnaeSwarm/WULA_AutoMechCarrier/PawnProductionEntry.cs @@ -0,0 +1,23 @@ +using Verse; + +namespace ArachnaeSwarm +{ + /// + /// A data class to hold information about a pawn to be produced in a queue. + /// Used in XML definitions. + /// + public class PawnProductionEntry + { + // The PawnKindDef of the unit to spawn. + public PawnKindDef pawnKind; + + // The maximum number of this kind of unit to maintain. + public int count = 1; + + // Optional: specific cooldown for this entry. If not set, the parent comp's cooldown is used. + public int? cooldownTicks; + + // Optional: specific cost for this entry. If not set, the parent comp's costPerPawn is used. + public int? cost; + } +} \ No newline at end of file