diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 77027aa..c16048f 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 92df577..a16c3aa 100644 --- a/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml +++ b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml @@ -469,7 +469,7 @@ ARA_HiveMindDrone -
  • ArachnaeQueen_Race
  • +
  • ArachnaeQueen_Race_Titan
  • diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml index f8cc1cb..cfa1894 100644 --- a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml +++ b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml @@ -45,7 +45,7 @@ ARA_ArachnaeQueen - ArachnaeQueen_Race + ArachnaeQueen_Race_Titan PlayerColony 0 @@ -65,7 +65,7 @@ - ArachnaeQueen_RaceSettings + ArachnaeQueen_Race_TitanSettings
  • diff --git a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml index 6ec54eb..f3db801 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml @@ -50,6 +50,7 @@ BeetleLikeWithClaw ARA_Insect_WithPlanting + ARA_Insect_Thinktree_Constant CarnivoreAnimal,OvivoreAnimal 0.1 0.5 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 0af022b..c11c6de 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml @@ -178,10 +178,637 @@
  • - - ArachnaeQueen_Race + + + + + + 0 + + true + + 15 + + false + + 0 + + false + + false + + + + +
  • Female_AverageNormal
  • + +
    + + +
  • Thin
  • +
    + + + +
  • + skin + + 0 + 0 + +
  • + +
  • + hair + + +
  • + 15 + (0.4,0.3,0.5) + (0.6,0.1,0.7) +
  • +
  • + 6 + (0.9,0.9,0.9) + (0.9,0.9,0.9) +
  • +
  • + 6 + (1,0.8,0.8) + (1,0.9,0.9) +
  • +
  • + 3 + (1,1,1) + (1,1,1) +
  • + + + +
    + + + + + 4 + 4 + (4,4) + (1.0,1.0) + (2,2) +
    + +
  • 0
  • +
    + + + + true + + +
  • (0,1)
  • +
    +
    +
    + + true + + +
  • (0,1)
  • +
    +
    +
    + + true + + +
  • (0,1)
  • +
    +
    +
    + + true + + +
  • (0,1)
  • +
    +
    +
    + + true + + +
  • (0,1)
  • +
    +
    +
    + + true + + +
  • (0, 1)
  • +
    +
    +
    +
    + + + Male + + +
  • (0,0)
  • +
  • (1,0)
  • +
  • (999,0)
  • +
  • (9999,0)
  • +
    +
    +
    + + +
  • (0,0)
  • +
  • (1,0)
  • +
  • (999,0)
  • +
  • (9999,0)
  • +
    +
    +
    + + + + (1,1,1,1) + + Cutout + ArachnaeSwarm/Things/ARA_HiveQueen/Bodies/ + ArachnaeSwarm/Things/ARA_HiveQueen/Heads/ + Things/Pawn/Humanlike/Bodies/Dessicated/Dessicated_Thin + ArachnaeSwarm/Things/ARA_HiveQueen/Bodies/ + + + + +
  • + HairDef + + true + +
  • Bald
  • + + + + +
  • + TattooDef + + false + +
  • + +
  • + BeardDef + + false + +
  • +
    + + + + +
  • HairColor
  • +
  • Melanin
  • +
  • BodyType
  • +
  • Ears
  • +
  • Nose
  • +
  • Voice
  • +
  • Headbone
  • +
  • Head
  • +
  • Jaw
  • +
    + + +
  • EyeColor
  • +
  • Tail
  • +
  • BeardStyle
  • +
  • Fur
  • +
    + + +
  • Furskin
  • +
  • Brow_Heavy
  • +
    + + +
  • Dirtmole
  • +
  • Genie
  • +
  • Hussar
  • +
  • Sanguophage
  • +
  • Neanderthal
  • +
  • Pigskin
  • +
  • Impid
  • +
  • Waster
  • +
  • Yttakin
  • +
  • Highmate
  • +
    + + + + true + + +
  • ARA_InsectJelly
  • +
  • ARA_NutrientPasteMeal
  • +
  • ARA_PheromoneSolvent
  • +
    + true + + +
  • ArachnaeBase_Race_Scavenger
  • +
  • ArachnaeBase_Race_Larva
  • +
    + true + + + + + + + true + + true + false + + + + + + + + +
  • BodyPurist
  • +
  • Cannibal
  • +
  • CreepyBreathing
  • +
  • DislikesWomen
  • +
  • Gourmand
  • +
  • QuickSleeper
  • +
  • Wimp
  • +
  • Beauty
  • +
  • DrugDesire
  • +
  • Immunity
  • +
  • PsychicSensitivity
  • +
    +
    + + + + +
  • ColonistLost
  • +
  • KnowColonistDied
  • +
  • PawnWithGoodOpinionDied
  • +
  • PsychicDrone
  • +
  • Naked
  • +
  • AnyBodyPartButGroinCovered_Disapproved_Female
  • +
  • AnyBodyPartButGroinCovered_Disapproved_Male
  • +
  • AnyBodyPartButGroinCovered_Disapproved_Memory
  • +
  • AnyBodyPartButGroinCovered_Disapproved_Social_Female
  • +
  • AnyBodyPartButGroinCovered_Disapproved_Social_Male
  • +
  • AnyBodyPartCovered_Disapproved_Female
  • +
  • AnyBodyPartCovered_Disapproved_Male
  • +
  • AnyBodyPartCovered_Disapproved_Memory
  • +
  • AnyBodyPartCovered_Disapproved_Social_Female
  • +
  • AnyBodyPartCovered_Disapproved_Social_Male
  • +
  • GroinChestHairOrFaceUncovered_Disapproved_Female
  • +
  • GroinChestHairOrFaceUncovered_Disapproved_Male
  • +
  • GroinChestHairOrFaceUncovered_Disapproved_Social_Female
  • +
  • GroinChestHairOrFaceUncovered_Disapproved_Social_Male
  • +
  • GroinChestOrHairUncovered_Disapproved_Female
  • +
  • GroinChestOrHairUncovered_Disapproved_Male
  • +
  • GroinChestOrHairUncovered_Disapproved_Social_Female
  • +
  • GroinChestOrHairUncovered_Disapproved_Social_Male
  • +
  • GroinOrChestUncovered_Disapproved_Female
  • +
  • GroinOrChestUncovered_Disapproved_Male
  • +
  • GroinOrChestUncovered_Disapproved_Social_Female
  • +
  • GroinOrChestUncovered_Disapproved_Social_Male
  • +
  • GroinUncovered_Disapproved_Female
  • +
  • GroinUncovered_Disapproved_Male
  • +
  • GroinUncovered_Disapproved_Social_Female
  • +
  • GroinUncovered_Disapproved_Social_Male
  • +
  • WitnessedDeathAlly
  • +
  • ObservedLayingCorpse
  • +
  • ObservedLayingRottingCorpse
  • +
  • ApparelDamaged
  • +
  • ProsthophileHappy
  • +
  • ProsthophobeUnhappy
  • +
  • BrawlerUnhappy
  • +
  • PyromaniacHappy
  • +
  • Greedy
  • +
  • Jealous
  • +
  • SharedBed
  • +
  • AteWithoutTable
  • +
  • SleptOutside
  • +
  • SleptOnGround
  • +
  • SleptInCold
  • +
  • SleptInHeat
  • +
  • Ugly
  • +
  • AteKibble
  • +
  • AteInsectMeatDirect
  • +
  • AteInsectMeatAsIngredient
  • +
  • AteRawFood
  • +
  • AteHumanlikeMeatDirect
  • +
  • AteHumanlikeMeatAsIngredient
  • +
  • KnowButcheredHumanlikeCorpse
  • +
  • ButcheredHumanlikeCorpseOpinion
  • +
  • AteRawHumanlikeMeat
  • +
    + + + +
    + + + 0 + 0 + + 0 + 0 + 0 + + 0 + 0 + 0 + +
    + + +
  • ARA_CureBloodRot
  • +
  • ARA_CureAcid
  • +
  • RemovePorcupineQuill
  • +
  • SurgicalInspection
  • +
  • ARA_Surgery_Install_Plasteel
  • +
  • ARA_Surgery_Install_Carapace_Shell
  • +
  • ARA_Surgery_Install_Huge_Stomach
  • +
  • ARA_Surgery_Install_Cycle_Suppression
  • +
  • ARA_Surgery_Install_Shell_Thorn
  • +
  • ARA_Surgery_Install_Reactive_Shell
  • +
  • ARA_Surgery_Install_Strengthening_Tendon
  • +
  • ARA_Surgery_Install_Slide_Patagium
  • +
  • ARA_Surgery_Install_Baneling_Pouch
  • +
  • ARA_Surgery_Install_Tumor_Pouch
  • +
  • ARA_Surgery_Install_Internal_Circulation_Lung
  • +
    + + + + + 2000 + 5 + + + 1.75 + 250 + + + + 2 + + 0.5 + + + 200 + 450 + 600 + + + 1 + + 2.5 + + 0 + + 0.95 + 0.95 + + 0.1 + + + 0.25 + + + + + + + + + 0.6 + 0.8 + 0.5 + + + 5 + + + 0 + + 15 + 1 + + + + + ArachnaeQueen_Body + Normal + + ARA_Humanlike + + Humanlike + + HumanStandard + Filth_BloodInsect + Filth_BloodSmear + + 10 + + 10 + + ARA_Carapace + + Pawn_Melee_BigBash_HitPawn + Pawn_Melee_BigBash_HitBuilding + Pawn_Melee_BigBash_Miss + Pawn_MeleeDodge + + 300 + + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + +
  • + HumanlikeBaby + 0 +
  • +
  • + HumanlikeChild + 0.25 +
  • +
  • + HumanlikePreTeenager + 0.5 +
  • +
  • + HumanlikeTeenager + 0.75 +
  • +
  • + HumanlikeAdult + 1 + Pawn_HiveQueen_Wounded + Pawn_HiveQueen_Death + Pawn_HiveQueen_Call + Pawn_HiveQueen_Angry +
  • +
    + false +
    + + +
  • + Unique_Arachnae_Queen + true + ARA_QueenAlreadyExists + +
  • +
  • + +
  • ARA_HiveStrength
  • +
  • ARA_HiveMindMaster
  • + + 1.0 + false + +
  • + + + + ArachnaeQueen_Race_Titan - 阿拉克涅泰坦种是女皇种亚种之一,归属于阿拉克涅的泰坦触须。她们指挥着阿拉克涅虫群中最坚韧、最强大的主力集团,承担在战场上正面 + 阿拉克涅泰坦种是女皇种亚种之一,归属于阿拉克涅的泰坦触须。她们指挥着阿拉克涅虫群中最坚韧、最具有适应力的主力集团族群,承担在战场上维持战线的任务。\n\n泰坦种女皇除了可以提供泰坦触须的独特科技外,自身也有强大的甲壳作为防御层,还可以通过践踏和高额的近战伤害攻击靠近的敌人。 + + + 2000 + + 1.75 + 250 + + 200 + 450 + 600 + + 1 + 2.5 + + 0.95 + 0.95 + 0.1 + + 0.25 + + 0.6 + 0.8 + 0.5 + + 12 + 1 + + + + +
  • + + +
  • Poke
  • + + 16 + 2 + HeadAttackTool + true + 0.01 + +
  • + + +
  • Blunt
  • +
  • Poke
  • + + 35 + 2.5 + Legs + +
  • + + +
  • Stab
  • + + 50 + 3 + Legs + +
  • + + +
  • Cut
  • + + 30 + 2 + Hands + + + + +
  • + true + ARA_RaceBaseSwarmProduceSwitchHediff + CocoonDestroyed +
  • +
  • + 3 + 180 + Crush + 10 + false + ARA_Area_Crush + false + 践踏 + 这只阿拉克涅虫族的身躯是如此巨大,以至于靠近它的敌人会被直接一脚踩死 + ArachnaeSwarm/UI/Abilities/ARA_Area_Crush +
  • +
    +
    + + ArachnaeQueen_Race_Neurotyrant + + 阿拉克涅灵吸种是女皇种亚种之一,归属于阿拉克涅的灵能触须。她们负责维持虫族蜂巢灵能网路的通讯,并作为中继节点链接各战区女皇种和虫巢舰队。\n\n但是灵吸种可不是脆弱的“文官”,她们强大的灵能使得她们可以通过超自然力量主宰当地战局,随着她们不断吞噬本地物种,其灵能能力还能得到不断的进化。 diff --git a/1.6/1.6/Defs/Thing_Misc/ARA_Things_Items.xml b/1.6/1.6/Defs/Thing_Misc/ARA_Things_Items.xml index bee7425..1f42b92 100644 --- a/1.6/1.6/Defs/Thing_Misc/ARA_Things_Items.xml +++ b/1.6/1.6/Defs/Thing_Misc/ARA_Things_Items.xml @@ -68,7 +68,7 @@
  • ArachnaeNode_Race_Skyraider
  • ArachnaeNode_Race_NeuroSwarm
  • ArachnaeNode_Race_Praetorian
  • -
  • ArachnaeQueen_Race
  • +
  • ArachnaeQueen_Race_Titan
  • diff --git a/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml b/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml index b8456e8..9dca102 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml @@ -95,7 +95,7 @@ 用于孵化阿拉克涅女皇种的超巨型卵囊,表皮坚硬地堪比堡垒,内部蕴含的遗传物质和营养足以孵化出这个星球闻所未闻的庞然大物。 - ArachnaeQueen_Race + ArachnaeQueen_Race_Titan (0.9, 0.9 ,0.5) diff --git a/1.6/1.6/Defs/ThinkTreeDefs/ARA_ThinkTrees.xml b/1.6/1.6/Defs/ThinkTreeDefs/ARA_ThinkTrees.xml index 46adc40..6e831ce 100644 --- a/1.6/1.6/Defs/ThinkTreeDefs/ARA_ThinkTrees.xml +++ b/1.6/1.6/Defs/ThinkTreeDefs/ARA_ThinkTrees.xml @@ -757,6 +757,45 @@ + + ARA_Insect_Thinktree_Constant + + + +
  • + Despawned +
  • + +
  • + + +
  • + + +
  • + + +
  • + + +
  • + JoinAutoJoinableCaravan +
  • +
    + + +
  • + + +
  • + LordDutyConstant +
  • + + + +
    +
    + ARA_Insect_Larva_Thinktree @@ -784,11 +823,17 @@ MentalStateCritical - -
  • -
  • + +
  • + HighPriority + +
  • + LordDuty +
  • + +
  • @@ -811,11 +856,10 @@
  • RopedPawn
  • - -
  • +
  • true @@ -883,6 +927,16 @@ SatisfyBasicNeeds
  • + +
  • + MediumPriority + +
  • + LordDuty +
  • + + +
  • @@ -1011,6 +1065,15 @@
  • + +
  • + HighPriority + +
  • + LordDuty +
  • + +
  • @@ -1035,9 +1098,9 @@
  • -
  • +
  • true @@ -1170,6 +1233,16 @@ SatisfyBasicNeeds
  • + +
  • + MediumPriority + +
  • + LordDuty +
  • + + +
  • diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo index f110a70..71d5ba1 100644 Binary files a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo and b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo differ diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json index 6f3bf4c..990db67 100644 --- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json +++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json @@ -1,92 +1,7 @@ { "Version": 1, "WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\", - "Documents": [ - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\buildings\\building_ootheca\\building_ootheca.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\building_ootheca.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\building_comps\\comprefuelablenutrition.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\comprefuelablenutrition.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\building_comps\\ara_productstorage\\compproperties_productstorage.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_productstorage\\compproperties_productstorage.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\building_comps\\ara_compinteractiveproducer\\compinteractiveproducer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_compinteractiveproducer\\compinteractiveproducer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\abilities\\compabilityeffect_transformcorpse.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\compabilityeffect_transformcorpse.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\jobs\\jobdriver_followproducer\\thinknode_conditionalnotproducedbymechcarrier.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:jobs\\jobdriver_followproducer\\thinknode_conditionalnotproducedbymechcarrier.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\buildings\\building_ootheca\\gizmo_pawnprogressbar.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\gizmo_pawnprogressbar.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\buildings\\building_ootheca\\gizmo_neutronflux.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\gizmo_neutronflux.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\hediffs\\ara_configurablemutant\\necrotictransformationutility.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:hediffs\\ara_configurablemutant\\necrotictransformationutility.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\pawn_comps\\ara_nodeswarmlifetime\\compnodeswarmlifetime.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_nodeswarmlifetime\\compnodeswarmlifetime.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\buildings\\building_ootheca\\gizmo_queuedincubationprogress.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\gizmo_queuedincubationprogress.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\buildings\\building_equipmentootheca\\building_equipmentootheca.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_equipmentootheca\\building_equipmentootheca.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\buildings\\building_corpsevat\\building_corpsevat.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_corpsevat\\building_corpsevat.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\buildings\\building_corpsevat\\corpsevatextension.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_corpsevat\\corpsevatextension.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\jobs\\jobdriver_plant\\jobgiver_grower.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:jobs\\jobdriver_plant\\jobgiver_grower.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\jobs\\jobdriver_clean\\jobgiver_cleaner.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:jobs\\jobdriver_clean\\jobgiver_cleaner.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\abilities\\ara_huggingface\\hediff_possession.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_huggingface\\hediff_possession.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\abilities\\ara_huggingface\\compabilityeffect_possess.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_huggingface\\compabilityeffect_possess.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\building_comps\\ara_buildingterrainspawn\\compdelayedterrainspawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_buildingterrainspawn\\compdelayedterrainspawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\hediffs\\ara_hediffterrainspawn\\compproperties_hediffterrainspawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:hediffs\\ara_hediffterrainspawn\\compproperties_hediffterrainspawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\hediffs\\ara_hediffterrainspawn\\comphediffterrainspawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:hediffs\\ara_hediffterrainspawn\\comphediffterrainspawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - } - ], + "Documents": [], "DocumentGroupContainers": [ { "Orientation": 0, @@ -94,271 +9,11 @@ "DocumentGroups": [ { "DockedWidth": 200, - "SelectedChildIndex": 6, + "SelectedChildIndex": -1, "Children": [ { "$type": "Bookmark", "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" - }, - { - "$type": "Document", - "DocumentIndex": 1, - "Title": "CompRefuelableNutrition.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\CompRefuelableNutrition.cs", - "RelativeDocumentMoniker": "Building_Comps\\CompRefuelableNutrition.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\CompRefuelableNutrition.cs", - "RelativeToolTip": "Building_Comps\\CompRefuelableNutrition.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAABAAAAAyAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-27T03:51:40.77Z", - "EditorCaption": "" - }, - { - "$type": "Document", - "DocumentIndex": 2, - "Title": "CompProperties_ProductStorage.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_ProductStorage\\CompProperties_ProductStorage.cs", - "RelativeDocumentMoniker": "Building_Comps\\ARA_ProductStorage\\CompProperties_ProductStorage.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_ProductStorage\\CompProperties_ProductStorage.cs", - "RelativeToolTip": "Building_Comps\\ARA_ProductStorage\\CompProperties_ProductStorage.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAAAUAAABKAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-27T03:51:33.86Z", - "EditorCaption": "" - }, - { - "$type": "Document", - "DocumentIndex": 3, - "Title": "CompInteractiveProducer.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs", - "RelativeDocumentMoniker": "Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs", - "RelativeToolTip": "Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs", - "ViewState": "AgIAAFICAAAAAAAAAAAAAFICAAAtAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-27T03:51:32.573Z", - "EditorCaption": "" - }, - { - "$type": "Document", - "DocumentIndex": 4, - "Title": "CompAbilityEffect_TransformCorpse.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\CompAbilityEffect_TransformCorpse.cs", - "RelativeDocumentMoniker": "Abilities\\CompAbilityEffect_TransformCorpse.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\CompAbilityEffect_TransformCorpse.cs", - "RelativeToolTip": "Abilities\\CompAbilityEffect_TransformCorpse.cs", - "ViewState": "AgIAAFQAAAAAAAAAAAAuwGkAAABdAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-27T03:51:29.604Z", - "EditorCaption": "" - }, - { - "$type": "Document", - "DocumentIndex": 5, - "Title": "ThinkNode_ConditionalNotProducedByMechCarrier.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_FollowProducer\\ThinkNode_ConditionalNotProducedByMechCarrier.cs", - "RelativeDocumentMoniker": "Jobs\\JobDriver_FollowProducer\\ThinkNode_ConditionalNotProducedByMechCarrier.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_FollowProducer\\ThinkNode_ConditionalNotProducedByMechCarrier.cs", - "RelativeToolTip": "Jobs\\JobDriver_FollowProducer\\ThinkNode_ConditionalNotProducedByMechCarrier.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAABQAAAAIAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-27T00:51:15.459Z", - "EditorCaption": "" - }, - { - "$type": "Document", - "DocumentIndex": 0, - "Title": "Building_Ootheca.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Building_Ootheca.cs", - "RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\Building_Ootheca.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Building_Ootheca.cs", - "RelativeToolTip": "Buildings\\Building_Ootheca\\Building_Ootheca.cs", - "ViewState": "AgIAABwCAAAAAAAAAAAvwDICAAAJAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-23T08:31:14.555Z", - "EditorCaption": "" - }, - { - "$type": "Document", - "DocumentIndex": 8, - "Title": "NecroticTransformationUtility.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_ConfigurableMutant\\NecroticTransformationUtility.cs", - "RelativeDocumentMoniker": "Hediffs\\ARA_ConfigurableMutant\\NecroticTransformationUtility.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_ConfigurableMutant\\NecroticTransformationUtility.cs", - "RelativeToolTip": "Hediffs\\ARA_ConfigurableMutant\\NecroticTransformationUtility.cs", - "ViewState": "AgIAAA8AAAAAAAAAAAAtwCIAAAAcAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-26T08:44:42.184Z" - }, - { - "$type": "Document", - "DocumentIndex": 9, - "Title": "CompNodeSwarmLifetime.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_NodeSwarmLifetime\\CompNodeSwarmLifetime.cs", - "RelativeDocumentMoniker": "Pawn_Comps\\ARA_NodeSwarmLifetime\\CompNodeSwarmLifetime.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_NodeSwarmLifetime\\CompNodeSwarmLifetime.cs", - "RelativeToolTip": "Pawn_Comps\\ARA_NodeSwarmLifetime\\CompNodeSwarmLifetime.cs", - "ViewState": "AgIAADYAAAAAAAAAAAAtwEoAAAAbAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-26T08:44:35.266Z" - }, - { - "$type": "Document", - "DocumentIndex": 10, - "Title": "Gizmo_QueuedIncubationProgress.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Gizmo_QueuedIncubationProgress.cs", - "RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\Gizmo_QueuedIncubationProgress.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Gizmo_QueuedIncubationProgress.cs", - "RelativeToolTip": "Buildings\\Building_Ootheca\\Gizmo_QueuedIncubationProgress.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAABEAAAAuAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-26T08:11:04.23Z" - }, - { - "$type": "Document", - "DocumentIndex": 6, - "Title": "Gizmo_PawnProgressBar.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Gizmo_PawnProgressBar.cs", - "RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\Gizmo_PawnProgressBar.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Gizmo_PawnProgressBar.cs", - "RelativeToolTip": "Buildings\\Building_Ootheca\\Gizmo_PawnProgressBar.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAAAwAAAAFAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-26T08:12:03.772Z", - "EditorCaption": "" - }, - { - "$type": "Document", - "DocumentIndex": 7, - "Title": "Gizmo_NeutronFlux.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Gizmo_NeutronFlux.cs", - "RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\Gizmo_NeutronFlux.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Gizmo_NeutronFlux.cs", - "RelativeToolTip": "Buildings\\Building_Ootheca\\Gizmo_NeutronFlux.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAAAkAAAAiAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-26T08:11:53.324Z", - "EditorCaption": "" - }, - { - "$type": "Document", - "DocumentIndex": 11, - "Title": "Building_EquipmentOotheca.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_EquipmentOotheca\\Building_EquipmentOotheca.cs", - "RelativeDocumentMoniker": "Buildings\\Building_EquipmentOotheca\\Building_EquipmentOotheca.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_EquipmentOotheca\\Building_EquipmentOotheca.cs", - "RelativeToolTip": "Buildings\\Building_EquipmentOotheca\\Building_EquipmentOotheca.cs", - "ViewState": "AgIAAEIBAAAAAAAAAAAAAEMBAAAAAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-26T07:52:41.869Z" - }, - { - "$type": "Document", - "DocumentIndex": 12, - "Title": "Building_CorpseVat.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_CorpseVat\\Building_CorpseVat.cs", - "RelativeDocumentMoniker": "Buildings\\Building_CorpseVat\\Building_CorpseVat.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_CorpseVat\\Building_CorpseVat.cs", - "RelativeToolTip": "Buildings\\Building_CorpseVat\\Building_CorpseVat.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAAAgAAAASAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-23T10:24:46.264Z" - }, - { - "$type": "Document", - "DocumentIndex": 14, - "Title": "JobGiver_Grower.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_Plant\\JobGiver_Grower.cs", - "RelativeDocumentMoniker": "Jobs\\JobDriver_Plant\\JobGiver_Grower.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_Plant\\JobGiver_Grower.cs", - "RelativeToolTip": "Jobs\\JobDriver_Plant\\JobGiver_Grower.cs", - "ViewState": "AgIAAFMAAAAAAAAAAAAQwGkAAAANAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-23T08:51:03.439Z" - }, - { - "$type": "Document", - "DocumentIndex": 16, - "Title": "Hediff_Possession.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_HuggingFace\\Hediff_Possession.cs", - "RelativeDocumentMoniker": "Abilities\\ARA_HuggingFace\\Hediff_Possession.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_HuggingFace\\Hediff_Possession.cs", - "RelativeToolTip": "Abilities\\ARA_HuggingFace\\Hediff_Possession.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAABMAAAAAAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-23T08:50:05.221Z" - }, - { - "$type": "Document", - "DocumentIndex": 17, - "Title": "CompAbilityEffect_Possess.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_HuggingFace\\CompAbilityEffect_Possess.cs", - "RelativeDocumentMoniker": "Abilities\\ARA_HuggingFace\\CompAbilityEffect_Possess.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_HuggingFace\\CompAbilityEffect_Possess.cs", - "RelativeToolTip": "Abilities\\ARA_HuggingFace\\CompAbilityEffect_Possess.cs", - "ViewState": "AgIAAHcAAAAAAAAAAAAAAIkAAAA5AAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-23T08:50:01.549Z" - }, - { - "$type": "Document", - "DocumentIndex": 13, - "Title": "CorpseVatExtension.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_CorpseVat\\CorpseVatExtension.cs", - "RelativeDocumentMoniker": "Buildings\\Building_CorpseVat\\CorpseVatExtension.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_CorpseVat\\CorpseVatExtension.cs", - "RelativeToolTip": "Buildings\\Building_CorpseVat\\CorpseVatExtension.cs", - "ViewState": "AgIAAAAAAAAAAAAAAADwvx0AAAAoAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-23T10:25:01.777Z" - }, - { - "$type": "Document", - "DocumentIndex": 18, - "Title": "CompDelayedTerrainSpawn.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs", - "RelativeDocumentMoniker": "Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs", - "RelativeToolTip": "Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs", - "ViewState": "AgIAAFEAAAAAAAAAAAAAAF8AAAA+AAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-23T08:46:06.784Z" - }, - { - "$type": "Document", - "DocumentIndex": 19, - "Title": "CompProperties_HediffTerrainSpawn.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_HediffTerrainSpawn\\CompProperties_HediffTerrainSpawn.cs", - "RelativeDocumentMoniker": "Hediffs\\ARA_HediffTerrainSpawn\\CompProperties_HediffTerrainSpawn.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_HediffTerrainSpawn\\CompProperties_HediffTerrainSpawn.cs", - "RelativeToolTip": "Hediffs\\ARA_HediffTerrainSpawn\\CompProperties_HediffTerrainSpawn.cs", - "ViewState": "AgIAAAUAAAAAAAAAAAAtwBkAAAAxAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-23T08:44:14.682Z" - }, - { - "$type": "Document", - "DocumentIndex": 15, - "Title": "JobGiver_Cleaner.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_Clean\\JobGiver_Cleaner.cs", - "RelativeDocumentMoniker": "Jobs\\JobDriver_Clean\\JobGiver_Cleaner.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_Clean\\JobGiver_Cleaner.cs", - "RelativeToolTip": "Jobs\\JobDriver_Clean\\JobGiver_Cleaner.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAABYAAABwAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-23T08:51:58.899Z" - }, - { - "$type": "Document", - "DocumentIndex": 20, - "Title": "CompHediffTerrainSpawn.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_HediffTerrainSpawn\\CompHediffTerrainSpawn.cs", - "RelativeDocumentMoniker": "Hediffs\\ARA_HediffTerrainSpawn\\CompHediffTerrainSpawn.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_HediffTerrainSpawn\\CompHediffTerrainSpawn.cs", - "RelativeToolTip": "Hediffs\\ARA_HediffTerrainSpawn\\CompHediffTerrainSpawn.cs", - "ViewState": "AgIAAKIAAAAAAAAAAAAawLQAAAAMAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-23T08:44:10.075Z" } ] } diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index 83c33fd..ff583ad 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -334,6 +334,7 @@ + diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs index 477b8ae..3af71b2 100644 --- a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs +++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs @@ -561,20 +561,30 @@ namespace ArachnaeSwarm } public override IEnumerable GetGizmos() { - base.GetGizmos(); + foreach (var gizmo in base.GetGizmos()) + { + if (gizmo is Command_Action cmd && cmd.defaultLabel != null) + { + string label = cmd.defaultLabel.ToString(); + if (label.Contains("拆除") || label.Contains("Deconstruct") || label.Contains("半径") || label.Contains("Radius")) + continue; + } + + // 强制将基础组件(如 Refuelable)甚至默认排序为 -100 的东西移到后面 + if (gizmo.Order >= -100f && gizmo.Order <= 0f) + { + gizmo.Order = -90f; + } + + yield return gizmo; + } + // 首先获取选中的同类建筑 var selectedOothecas = GetSelectedOothecas(); bool isMultiSelect = selectedOothecas.Count > 1; if (Faction == Faction.OfPlayer) { - // 在多选时,只在第一个建筑上显示进度条和通量条 - if (!isMultiSelect || (isMultiSelect && selectedOothecas.First() == this)) - { - yield return new Gizmo_PawnProgressBar(this); - yield return new Gizmo_NeutronFlux(this); - } - var config = IncubatorData?.SelectedConfig; // 添加订单按钮(多选时合并) @@ -657,6 +667,7 @@ namespace ArachnaeSwarm } } } + /// /// 为多选建筑显示订单菜单 /// diff --git a/Source/ArachnaeSwarm/Jobs/JobDriver_FollowProducer/JobGiver_AIDefendProducer.cs b/Source/ArachnaeSwarm/Jobs/JobDriver_FollowProducer/JobGiver_AIDefendProducer.cs index f38f4ec..b3a8d45 100644 --- a/Source/ArachnaeSwarm/Jobs/JobDriver_FollowProducer/JobGiver_AIDefendProducer.cs +++ b/Source/ArachnaeSwarm/Jobs/JobDriver_FollowProducer/JobGiver_AIDefendProducer.cs @@ -1,4 +1,6 @@ using RimWorld; +using System.Collections.Generic; +using System.Linq; using Verse; using Verse.AI; @@ -14,6 +16,12 @@ namespace ArachnaeSwarm // 是否仅防御征召状态的生产者(针对pawn类型生产者) public bool onlyDefendDrafted = true; + // 是否排除休眠的机械族 + public bool excludeDormantMechs = true; + + // 是否将炮塔纳入考虑 + public bool includeHostileTurrets = true; + protected override Pawn GetDefendee(Pawn pawn) { // 我们不需要返回Pawn类型的防御者,因为我们实际上防御的是Thing @@ -21,18 +29,18 @@ namespace ArachnaeSwarm CompProducedByMechCarrier producerComp = pawn.TryGetComp(); if (producerComp == null || !producerComp.HasValidProducer) return null; - + Thing producer = producerComp.Producer; - + // 对于Pawn类型生产者,检查是否需要征召状态 if (producer is Pawn pawnProducer) { if (onlyDefendDrafted && !pawnProducer.Drafted) return null; - + return pawnProducer; } - + return null; } @@ -42,44 +50,44 @@ namespace ArachnaeSwarm CompProducedByMechCarrier producerComp = pawn.TryGetComp(); if (producerComp == null || !producerComp.HasValidProducer) return defendRadius; - + // 如果生产者是建筑,使用扩展防御半径 if (producerComp.Producer is Building) return extendedDefendRadius; - + return defendRadius; } - + // 重写以支持非Pawn生产者 protected override Job TryGiveJob(Pawn pawn) { CompProducedByMechCarrier producerComp = pawn.TryGetComp(); if (producerComp == null || !producerComp.HasValidProducer) return null; - + Thing producer = producerComp.Producer; if (producer == null || producer.Destroyed) return null; - + // 对于Pawn类型生产者,检查是否需要征召状态 if (producer is Pawn pawnProducer && onlyDefendDrafted) { if (!pawnProducer.Drafted) return null; } - + // 如果生产者在战斗中,让pawn参与防御 if (producer is Pawn pawnProducer2 && pawnProducer2.InAggroMentalState) { // 使用基类逻辑来防御Pawn类型生产者 return base.TryGiveJob(pawn); } - + // 对于非Pawn生产者或非战斗状态,检查是否需要防御 float defendRadiusValue = GetFlagRadius(pawn); - + // 寻找附近的威胁 - Pawn enemy = FindEnemy(pawn, defendRadiusValue); + Thing enemy = FindEnemy(pawn, defendRadiusValue); if (enemy != null) { // 创建攻击工作 @@ -90,23 +98,24 @@ namespace ArachnaeSwarm job.expireRequiresEnemiesNearby = true; return job; } - + return null; } - - private Pawn FindEnemy(Pawn pawn, float radius) + + + protected virtual Thing FindEnemy(Pawn pawn, float radius) { CompProducedByMechCarrier producerComp = pawn.TryGetComp(); if (producerComp == null || !producerComp.HasValidProducer) return null; - + Thing producer = producerComp.Producer; IntVec3 center = producer.Position; - - return (Pawn)AttackTargetFinder.BestAttackTarget( + + return (Thing)AttackTargetFinder.BestAttackTarget( pawn, - TargetScanFlags.NeedLOSToAll, - target => target is Pawn p && pawn.HostileTo(p) && p.Spawned && !p.Downed && !p.Dead, + TargetScanFlags.NeedThreat, + (Thing x) => ExtraTargetValidator(pawn, x), 0f, radius, center, @@ -121,6 +130,9 @@ namespace ArachnaeSwarm JobGiver_AIDefendProducer obj = (JobGiver_AIDefendProducer)base.DeepCopy(resolve); obj.attackMeleeThreatEvenIfNotHostile = attackMeleeThreatEvenIfNotHostile; obj.defendRadius = defendRadius; + obj.onlyDefendDrafted = onlyDefendDrafted; + obj.excludeDormantMechs = excludeDormantMechs; + obj.includeHostileTurrets = includeHostileTurrets; return obj; } diff --git a/Source/ArachnaeSwarm/Jobs/JobDriver_FollowProducer/ThinkNode_ConditionalNotProducedByMechCarrier.cs b/Source/ArachnaeSwarm/Jobs/JobDriver_FollowProducer/ThinkNode_ConditionalNotProducedByMechCarrier.cs index 528b4ab..6481cbd 100644 --- a/Source/ArachnaeSwarm/Jobs/JobDriver_FollowProducer/ThinkNode_ConditionalNotProducedByMechCarrier.cs +++ b/Source/ArachnaeSwarm/Jobs/JobDriver_FollowProducer/ThinkNode_ConditionalNotProducedByMechCarrier.cs @@ -10,18 +10,6 @@ namespace ArachnaeSwarm /// public class ThinkNode_ConditionalNotProducedByMechCarrier : ThinkNode_Conditional { - // 可选:是否检查生产者是否存活 - private bool checkProducerAlive = true; - - // 可选:是否检查生产者是否在同一地图 - private bool checkSameMap = false; - - // 可选:是否检查生产者是否可到达 - private bool checkReachable = false; - - // 可选:是否检查生产者类型(pawn必须征召才跟随) - private bool checkProducerTypeConditions = true; - public ThinkNode_ConditionalNotProducedByMechCarrier() { } @@ -29,7 +17,7 @@ namespace ArachnaeSwarm protected override bool Satisfied(Pawn pawn) { // 基础检查:如果不是生产者生产的,返回true - bool isProduced = IsProducedByMechCarrier(pawn); + bool isProduced = HasProducer(pawn); // 如果是生产者生产的,再检查其他条件 if (isProduced) @@ -44,7 +32,7 @@ namespace ArachnaeSwarm /// /// 检查pawn是否由生产者生产 /// - private bool IsProducedByMechCarrier(Pawn pawn) + private bool HasProducer(Pawn pawn) { if (!pawn.Spawned) return false; @@ -58,33 +46,6 @@ namespace ArachnaeSwarm if (producer == null || producer.Destroyed) return false; - // 检查生产者是否在同一地图 - if (checkSameMap && producer.Map != pawn.Map) - return false; - - // 检查是否可以到达生产者 - if (checkReachable && !pawn.CanReach(producer, PathEndMode.OnCell, Danger.Deadly)) - return false; - - // 根据生产者类型检查特定条件 - if (checkProducerTypeConditions && producer is Pawn pawnProducer) - { - // 如果生产者是pawn,则只在征召状态下才跟随 - // 这是为了模拟原版动物跟随主人的逻辑 - if (checkProducerAlive && (pawnProducer.Dead || pawnProducer.Downed)) - return false; - - // 只有在征召状态下才认为需要跟随 - if (!pawnProducer.Drafted) - return false; - } - else if (checkProducerTypeConditions && producer is Building buildingProducer) - { - // 如果生产者是建筑,默认返回true - // 可以根据需要添加其他条件 - return true; - } - return true; } } diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_AutoMechCarrier/CompAutoMechCarrier.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_AutoMechCarrier/CompAutoMechCarrier.cs index 8e13249..0081688 100644 --- a/Source/ArachnaeSwarm/Pawn_Comps/ARA_AutoMechCarrier/CompAutoMechCarrier.cs +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_AutoMechCarrier/CompAutoMechCarrier.cs @@ -9,20 +9,26 @@ using Verse.AI.Group; namespace ArachnaeSwarm { - public class CompAutoMechCarrier : CompMechCarrier + public class CompAutoMechCarrier : CompMechCarrier, IBidirectionalValidator { #region Reflected Fields private static FieldInfo spawnedPawnsField; private static FieldInfo cooldownTicksRemainingField; private static FieldInfo innerContainerField; + // 安全锁 + private object validationLock = new object(); + private List SpawnedPawns { get { - if (spawnedPawnsField == null) - spawnedPawnsField = typeof(CompMechCarrier).GetField("spawnedPawns", BindingFlags.NonPublic | BindingFlags.Instance); - return (List)spawnedPawnsField.GetValue(this); + lock (validationLock) + { + if (spawnedPawnsField == null) + spawnedPawnsField = typeof(CompMechCarrier).GetField("spawnedPawns", BindingFlags.NonPublic | BindingFlags.Instance); + return (List)spawnedPawnsField.GetValue(this); + } } } @@ -30,15 +36,21 @@ namespace ArachnaeSwarm { get { - if (cooldownTicksRemainingField == null) - cooldownTicksRemainingField = typeof(CompMechCarrier).GetField("cooldownTicksRemaining", BindingFlags.NonPublic | BindingFlags.Instance); - return (int)cooldownTicksRemainingField.GetValue(this); + lock (validationLock) + { + 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); + lock (validationLock) + { + if (cooldownTicksRemainingField == null) + cooldownTicksRemainingField = typeof(CompMechCarrier).GetField("cooldownTicksRemaining", BindingFlags.NonPublic | BindingFlags.Instance); + cooldownTicksRemainingField.SetValue(this, value); + } } } @@ -46,9 +58,12 @@ namespace ArachnaeSwarm { get { - if (innerContainerField == null) - innerContainerField = typeof(CompMechCarrier).GetField("innerContainer", BindingFlags.NonPublic | BindingFlags.Instance); - return (ThingOwner)innerContainerField.GetValue(this); + lock (validationLock) + { + if (innerContainerField == null) + innerContainerField = typeof(CompMechCarrier).GetField("innerContainer", BindingFlags.NonPublic | BindingFlags.Instance); + return (ThingOwner)innerContainerField.GetValue(this); + } } } #endregion @@ -60,7 +75,6 @@ namespace ArachnaeSwarm /// /// 获取或设置自定义跟随位置 - /// 如果此位置不为空,生成的单位将以此位置而非父单位位置为跟随中心点 /// public IntVec3? CustomFollowPosition { @@ -85,14 +99,13 @@ namespace ArachnaeSwarm customFollowPositionMap = null; } - // 通知所有已生成的单位更新他们的跟随位置 NotifyFollowPositionChanged(); } } } /// - /// 检查自定义位置是否有效(位置不为空且在正确的地图上) + /// 检查自定义位置是否有效 /// public bool HasValidCustomFollowPosition { @@ -101,7 +114,6 @@ namespace ArachnaeSwarm if (!customFollowPosition.HasValue || customFollowPositionMap == null) return false; - // 如果父单位已销毁或不在同一地图,自定义位置也无效 if (parent == null || parent.Destroyed || !parent.Spawned) return false; @@ -111,7 +123,6 @@ namespace ArachnaeSwarm /// /// 获取有效的跟随位置 - /// 优先返回自定义位置,如果无效则返回父单位位置 /// public IntVec3 GetEffectiveFollowPosition() { @@ -120,7 +131,6 @@ namespace ArachnaeSwarm return customFollowPosition.Value; } - // 返回父单位位置 if (parent != null && parent.Spawned) { if (parent is Building building) @@ -156,19 +166,16 @@ namespace ArachnaeSwarm /// private void NotifyFollowPositionChanged() { - if (SpawnedPawns == null || !SpawnedPawns.Any()) + var spawned = GetSafeSpawnedPawns(); + if (spawned == null || !spawned.Any()) return; - // 清理无效的Pawn引用 - SpawnedPawns.RemoveAll(p => p == null || p.Destroyed); - - // 通知每个Pawn的CompProducedByMechCarrier - foreach (Pawn spawnedPawn in SpawnedPawns) + foreach (var pawn in spawned) { - if (spawnedPawn == null || spawnedPawn.Destroyed || !spawnedPawn.Spawned) + if (pawn == null || pawn.Destroyed || !pawn.Spawned) continue; - var producedComp = spawnedPawn.TryGetComp(); + var producedComp = pawn.TryGetComp(); if (producedComp != null) { producedComp.TryUpdateProducerStatus(); @@ -187,8 +194,6 @@ namespace ArachnaeSwarm /// /// 设置自定义跟随位置 /// - /// 新的跟随位置 - /// 是否设置成功 public bool SetCustomFollowPosition(IntVec3 position) { if (parent == null || !parent.Spawned) @@ -202,23 +207,18 @@ namespace ArachnaeSwarm } /// - /// 临时设置自定义跟随位置(在一段时间后自动清除) + /// 临时设置自定义跟随位置 /// - /// 临时位置 - /// 持续时间(tick) public void SetTemporaryFollowPosition(IntVec3 position, int durationTicks) { if (!SetCustomFollowPosition(position)) return; - // 启动定时器清除临时位置 StartPositionClearTimer(durationTicks); } private void StartPositionClearTimer(int durationTicks) { - // 这里可以使用TickManager来安排定时清除 - // 简单实现:记录时间并在Tick中检查 temporaryPositionExpiryTick = Find.TickManager.TicksGame + durationTicks; } #endregion @@ -238,34 +238,244 @@ namespace ArachnaeSwarm } #endregion + #region 双向验证字段 + // 子单位死亡通知队列 + private Queue childDeathQueue = new Queue(); + private int lastChildCleanupTick = -1; + private const int CHILD_CLEANUP_INTERVAL = 30; + + // 已验证的子单位缓存 + private HashSet validatedChildren = new HashSet(); + private int lastValidationCacheClearTick = -1; + private const int VALIDATION_CACHE_CLEAR_INTERVAL = 300; + #endregion + + #region IBidirectionalValidator 实现 + public bool IsProducerValid + { + get + { + lock (validationLock) + { + if (parent == null || parent.Destroyed) + return false; + + if (parent is Pawn pawn && (pawn.Dead || pawn.Downed)) + return false; + + return true; + } + } + } + + public Thing GetProducer() + { + lock (validationLock) + { + return IsProducerValid ? parent : null; + } + } + + public bool IsProducerAlive + { + get + { + lock (validationLock) + { + return IsProducerValid && parent.Spawned; + } + } + } + + public bool IsChildValid(Thing child) + { + if (child == null || child.Destroyed) + return false; + + if (!(child is Pawn)) + return false; + + if (child is Pawn pawn && (pawn.Dead || pawn.Downed)) + return false; + + return true; + } + + public bool IsChildAlive(Thing child) + { + return IsChildValid(child) && child.Spawned; + } + + public bool ValidateBidirectional(Thing child) + { + lock (validationLock) + { + if (!IsProducerValid) + return false; + + if (!IsChildValid(child)) + return false; + + return ValidateBidirectionalReference(child); + } + } + + public bool ValidateAndExecute(Thing child, System.Action action) + { + if (!ValidateBidirectional(child)) + return false; + + try + { + action?.Invoke(); + return true; + } + catch (System.Exception ex) + { + Log.Error($"生产者双向验证执行失败: {ex.Message}"); + return false; + } + } + + private bool ValidateBidirectionalReference(Thing child) + { + if (validatedChildren.Contains(child)) + return true; + + var spawned = GetSafeSpawnedPawns(); + bool isInList = spawned.Contains(child as Pawn); + + var childComp = (child as Pawn)?.TryGetComp(); + bool hasValidReference = childComp != null && childComp.IsProducerValid && childComp.GetProducer() == parent; + + bool isValid = isInList && hasValidReference; + + if (isValid) + { + validatedChildren.Add(child); + } + + return isValid; + } + + /// + /// 验证子单位引用 + /// + public bool ValidateChildReference(Thing child) + { + return ValidateBidirectionalReference(child); + } + + /// + /// 通知子单位死亡 + /// + public void NotifyChildDeath(Thing child) + { + lock (validationLock) + { + childDeathQueue.Enqueue(child); + } + } + + /// + /// 安全移除子单位 + /// + private void RemoveChildSafe(Thing child) + { + lock (validationLock) + { + try + { + var spawned = SpawnedPawns; + if (spawned != null && child is Pawn pawn) + { + bool removed = spawned.Remove(pawn); + if (removed && AutoProps.debugLogging) + { + Log.Message($"安全移除子单位: {pawn.LabelCap}"); + } + } + + validatedChildren.Remove(child); + } + catch (System.Exception ex) + { + Log.Error($"移除子单位失败: {ex.Message}"); + } + } + } + + /// + /// 清理无效的子单位引用 + /// + private void CleanupInvalidChildren() + { + lock (validationLock) + { + // 处理死亡通知队列 + while (childDeathQueue.Count > 0) + { + var deadChild = childDeathQueue.Dequeue(); + if (deadChild != null) + { + RemoveChildSafe(deadChild); + } + } + + // 清理spawnedPawns列表 + var spawned = SpawnedPawns; + if (spawned != null) + { + int before = spawned.Count; + spawned.RemoveAll(p => p == null || p.Destroyed || (p is Pawn pawn && pawn.Dead)); + + if (before != spawned.Count && AutoProps.debugLogging) + { + Log.Message($"清理无效子单位: {before} -> {spawned.Count}"); + } + } + + // 清理验证缓存 + if (Find.TickManager.TicksGame - lastValidationCacheClearTick > VALIDATION_CACHE_CLEAR_INTERVAL) + { + validatedChildren.RemoveWhere(c => c == null || c.Destroyed); + lastValidationCacheClearTick = Find.TickManager.TicksGame; + } + } + } + #endregion + public CompProperties_AutoMechCarrier AutoProps => (CompProperties_AutoMechCarrier)props; // 缓存的合并生产队列 private List cachedProductionQueue = null; private int lastProductionQueueUpdateTick = -1; - private const int PRODUCTION_QUEUE_CACHE_TICKS = 30; // 30 tick缓存 + private const int PRODUCTION_QUEUE_CACHE_TICKS = 30; /// /// 获取合并后的生产队列 - /// 包括从HediffComp扫描的和动态添加的 /// public List GetProductionQueue() { - // 使用缓存提高性能 if (cachedProductionQueue != null && Find.TickManager.TicksGame - lastProductionQueueUpdateTick < PRODUCTION_QUEUE_CACHE_TICKS) { return cachedProductionQueue; } - // 获取Pawn引用 - var pawn = parent as Pawn; - - // 从AutoProps获取合并后的生产队列 - cachedProductionQueue = AutoProps.GetMergedProductionQueue(pawn); - lastProductionQueueUpdateTick = Find.TickManager.TicksGame; - - return cachedProductionQueue; + try + { + var pawn = parent as Pawn; + cachedProductionQueue = AutoProps.GetMergedProductionQueue(pawn); + lastProductionQueueUpdateTick = Find.TickManager.TicksGame; + + return cachedProductionQueue; + } + catch (System.Exception ex) + { + Log.Error($"获取生产队列失败: {ex.Message}"); + return new List(); + } } /// @@ -277,6 +487,23 @@ namespace ArachnaeSwarm lastProductionQueueUpdateTick = -1; } + /// + /// 安全获取已生成的Pawn列表 + /// + private List GetSafeSpawnedPawns() + { + lock (validationLock) + { + var spawned = SpawnedPawns; + if (spawned == null) + return new List(); + + spawned.RemoveAll(p => p == null || p.Destroyed || (p is Pawn pawn && pawn.Dead)); + + return spawned; + } + } + /// /// 获取总容量 /// @@ -284,158 +511,212 @@ namespace ArachnaeSwarm private int LiveSpawnedPawnsCount(PawnKindDef kind) { - SpawnedPawns.RemoveAll(p => p == null || p.Destroyed); - return SpawnedPawns.Count(p => p.kindDef == kind); + var spawned = GetSafeSpawnedPawns(); + return spawned.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(); - - var productionQueue = GetProductionQueue(); - PawnProductionEntry entry = productionQueue.FirstOrDefault(e => e.pawnKind == kind); - if (entry == null) - return "生产队列中未找到指定的PawnKindDef"; - - int cost = entry.cost ?? Props.costPerPawn; + try + { + 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(); + + var productionQueue = GetProductionQueue(); + PawnProductionEntry entry = productionQueue.FirstOrDefault(e => e.pawnKind == kind); + if (entry == null) + return "生产队列中未找到指定的PawnKindDef"; + + int cost = entry.cost ?? Props.costPerPawn; - if (!AutoProps.freeProduction && InnerContainer.TotalStackCountOfDef(Props.fixedIngredient) < cost) - return "MechCarrierNotEnoughResources".Translate(); - return true; + if (!AutoProps.freeProduction && InnerContainer.TotalStackCountOfDef(Props.fixedIngredient) < cost) + return "MechCarrierNotEnoughResources".Translate(); + + return true; + } + catch (System.Exception ex) + { + Log.Error($"CanSpawnNow检查失败: {ex.Message}"); + return $"生产检查失败: {ex.Message}"; + } } private void TrySpawnPawn(PawnKindDef kind) { - PawnGenerationRequest request = new PawnGenerationRequest(kind, parent.Faction, PawnGenerationContext.NonPlayer, -1, forceGenerateNewPawn: true); - Pawn pawn = PawnGenerator.GeneratePawn(request); - - // 为pawn添加CompProducedByMechCarrier并初始化 - var producedComp = pawn.TryGetComp(); - if (producedComp == null) + try { - // 如果pawn没有这个Comp,添加它 - var compProps = new CompProperties_ProducedByMechCarrier(); - compProps.compClass = typeof(CompProducedByMechCarrier); - var newComp = (CompProducedByMechCarrier)Activator.CreateInstance(compProps.compClass); - newComp.parent = pawn; - newComp.props = compProps; - pawn.AllComps.Add(newComp); - producedComp = newComp; - } + PawnGenerationRequest request = new PawnGenerationRequest(kind, parent.Faction, PawnGenerationContext.NonPlayer, -1, forceGenerateNewPawn: true); + Pawn pawn = PawnGenerator.GeneratePawn(request); - // 初始化生产者信息 - producedComp.Initialize(parent, this); - - // 生成位置:使用自定义跟随位置(如果有效),否则使用父单位位置 - IntVec3 spawnPosition; - Map spawnMap; - - if (HasValidCustomFollowPosition) - { - spawnPosition = customFollowPosition.Value; - spawnMap = customFollowPositionMap; - } - else - { - spawnPosition = parent.Position; - spawnMap = parent.Map; - } - - GenSpawn.Spawn(pawn, spawnPosition, spawnMap); - SpawnedPawns.Add(pawn); - - if (parent is Pawn p && p.GetLord() != null) - p.GetLord().AddPawn(pawn); - - if (!AutoProps.freeProduction) - { - var productionQueue = GetProductionQueue(); - PawnProductionEntry entry = 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++) + // 为pawn添加CompProducedByMechCarrier并初始化 + var producedComp = pawn.TryGetComp(); + if (producedComp == null) { - Thing thing = InnerContainer.Take(things[j], Mathf.Min(things[j].stackCount, costLeft)); - costLeft -= thing.stackCount; - thing.Destroy(); - if (costLeft <= 0) break; + var compProps = new CompProperties_ProducedByMechCarrier(); + compProps.compClass = typeof(CompProducedByMechCarrier); + var newComp = (CompProducedByMechCarrier)Activator.CreateInstance(compProps.compClass); + newComp.parent = pawn; + newComp.props = compProps; + pawn.AllComps.Add(newComp); + producedComp = newComp; } - } - - var spawnEntry = GetProductionQueue().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); + // 初始化生产者信息 - 使用安全初始化 + bool initSuccess = producedComp.InitializeSafe(parent, this); + if (!initSuccess) + { + Log.Error($"初始化CompProducedByMechCarrier失败"); + return; + } + + // 生成位置 + IntVec3 spawnPosition; + Map spawnMap; + + if (HasValidCustomFollowPosition) + { + spawnPosition = customFollowPosition.Value; + spawnMap = customFollowPositionMap; + } + else + { + spawnPosition = parent.Position; + spawnMap = parent.Map; + } + + // 验证生成位置 + if (!spawnPosition.IsValid || !spawnPosition.InBounds(spawnMap)) + { + spawnPosition = parent.Position; + spawnMap = parent.Map; + } + + GenSpawn.Spawn(pawn, spawnPosition, spawnMap); + + // 添加到已生成列表 + lock (validationLock) + { + SpawnedPawns?.Add(pawn); + } + + if (parent is Pawn p && p.GetLord() != null) + p.GetLord().AddPawn(pawn); + + if (!AutoProps.freeProduction) + { + var productionQueue = GetProductionQueue(); + PawnProductionEntry entry = 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; + } + } + + var spawnEntry = GetProductionQueue().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); + + if (AutoProps.debugLogging) + Log.Message($"成功生成子单位: {pawn.LabelCap}"); + } + catch (System.Exception ex) + { + Log.Error($"生成Pawn失败: {ex.Message}"); + } } 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(); + try + { + Effecter effecter = new Effecter(effecterDef); + effecter.Trigger(attach ? ((TargetInfo)target) : new TargetInfo(target.Position, target.Map), TargetInfo.Invalid); + effecter.Cleanup(); + } + catch (System.Exception ex) + { + Log.Error($"触发效果失败: {ex.Message}"); + } } public override void CompTick() { - base.CompTick(); - - // 检查临时位置是否过期 - CheckTemporaryPositionExpiry(); - - if (parent.IsHashIntervalTick(60)) // 每秒检查一次 + try { - // 检查是否有抑制生产的Hediff - if (AutoProps.disableHediff != null && (parent as Pawn)?.health.hediffSet.HasHediff(AutoProps.disableHediff) == true) + base.CompTick(); + + // 清理无效的子单位引用 + if (Find.TickManager.TicksGame - lastChildCleanupTick > CHILD_CLEANUP_INTERVAL) { - return; // 有Hediff,停止生产 + CleanupInvalidChildren(); + lastChildCleanupTick = Find.TickManager.TicksGame; } - // 获取当前生产队列 - var productionQueue = GetProductionQueue(); - if (productionQueue == null || productionQueue.Count == 0) + // 检查临时位置是否过期 + CheckTemporaryPositionExpiry(); + + if (parent.IsHashIntervalTick(60)) { - return; // 生产队列为空,不进行生产 - } - - // 1. 先检查是否满员 - bool isFull = true; - foreach (var entry in productionQueue) - { - if (LiveSpawnedPawnsCount(entry.pawnKind) < entry.count) + // 检查是否有抑制生产的Hediff + if (AutoProps.disableHediff != null && (parent as Pawn)?.health.hediffSet.HasHediff(AutoProps.disableHediff) == true) { - isFull = false; - break; + return; } - } - - if (isFull) - { - return; // 如果已满员,则不进行任何操作,包括冷却计时 - } - - // 2. 如果未满员,才检查冷却时间 - if (CooldownTicksRemaining > 0) return; - - // 3. 寻找空位并生产 - foreach (var entry in productionQueue) - { - if (LiveSpawnedPawnsCount(entry.pawnKind) < entry.count) + + var productionQueue = GetProductionQueue(); + if (productionQueue == null || productionQueue.Count == 0) { - if (CanSpawnNow(entry.pawnKind).Accepted) + return; + } + + // 检查是否满员 + bool isFull = true; + foreach (var entry in productionQueue) + { + if (LiveSpawnedPawnsCount(entry.pawnKind) < entry.count) { - TrySpawnPawn(entry.pawnKind); - break; // 每次只生产一个 + isFull = false; + break; + } + } + + if (isFull) + { + return; + } + + if (CooldownTicksRemaining > 0) return; + + foreach (var entry in productionQueue) + { + if (LiveSpawnedPawnsCount(entry.pawnKind) < entry.count) + { + if (CanSpawnNow(entry.pawnKind).Accepted) + { + TrySpawnPawn(entry.pawnKind); + break; + } } } } } + catch (System.Exception ex) + { + Log.Error($"CompAutoMechCarrier Tick错误: {ex.Message}"); + } } /// @@ -523,52 +804,63 @@ namespace ArachnaeSwarm 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; - - var productionQueue = GetProductionQueue(); - foreach (var entry in productionQueue) + try { - text += $"\n- {entry.pawnKind.LabelCap}: {LiveSpawnedPawnsCount(entry.pawnKind)} / {entry.count}"; + var spawned = GetSafeSpawnedPawns(); + string text = "Pawns: " + spawned.Count + " / " + TotalPawnCapacity; - // 显示自定义信息 - if (entry.customInfo != null) + var productionQueue = GetProductionQueue(); + foreach (var entry in productionQueue) { - text += $" ({entry.customInfo})"; - } - } - - if (CooldownTicksRemaining > 0) - { - text += "\n" + "CooldownTime".Translate() + ": " + CooldownTicksRemaining.ToStringSecondsFromTicks(); - } - - if (!AutoProps.freeProduction) - { - text += "\n" + base.CompInspectStringExtra(); - } - - // 显示自定义跟随位置信息 - if (HasValidCustomFollowPosition) - { - text += $"\nCustom Follow Position: {customFollowPosition.Value}"; - if (temporaryPositionExpiryTick > 0) - { - int remainingTicks = temporaryPositionExpiryTick - Find.TickManager.TicksGame; - if (remainingTicks > 0) + text += $"\n- {entry.pawnKind.LabelCap}: {LiveSpawnedPawnsCount(entry.pawnKind)} / {entry.count}"; + + if (entry.customInfo != null) { - text += $"\nTemporary Position (expires in: {remainingTicks.ToStringSecondsFromTicks()})"; + text += $" ({entry.customInfo})"; } } + + if (CooldownTicksRemaining > 0) + { + text += "\n" + "CooldownTime".Translate() + ": " + CooldownTicksRemaining.ToStringSecondsFromTicks(); + } + + if (!AutoProps.freeProduction) + { + text += "\n" + base.CompInspectStringExtra(); + } + + if (HasValidCustomFollowPosition) + { + text += $"\nCustom Follow Position: {customFollowPosition.Value}"; + if (temporaryPositionExpiryTick > 0) + { + int remainingTicks = temporaryPositionExpiryTick - Find.TickManager.TicksGame; + if (remainingTicks > 0) + { + text += $"\nTemporary Position (expires in: {remainingTicks.ToStringSecondsFromTicks()})"; + } + } + } + + // 显示验证状态 + if (AutoProps.debugLogging) + { + text += $"\n验证缓存: {validatedChildren.Count}"; + text += $"\n死亡队列: {childDeathQueue.Count}"; + } + + return text; + } + catch (System.Exception ex) + { + return $"状态获取错误: {ex.Message}"; } - - return text; } /// @@ -576,47 +868,58 @@ namespace ArachnaeSwarm /// public string GetDebugInfo() { - var pawn = parent as Pawn; System.Text.StringBuilder sb = new System.Text.StringBuilder(); sb.AppendLine("=== CompAutoMechCarrier 调试信息 ==="); - // CompAutoMechCarrier基本信息 - sb.AppendLine($"父单位: {parent?.LabelCap ?? "NULL"}"); - sb.AppendLine($"自定义跟随位置: {CustomFollowPosition}"); - sb.AppendLine($"有效跟随位置: {GetEffectiveFollowPosition()}"); - sb.AppendLine($"临时位置过期tick: {temporaryPositionExpiryTick}"); + // 生产者信息 + sb.AppendLine($"生产者有效: {IsProducerValid}"); + sb.AppendLine($"生产者存活: {IsProducerAlive}"); // 已生成的Pawn - sb.AppendLine($"已生成Pawn数: {SpawnedPawns?.Count ?? 0}"); - if (SpawnedPawns != null && SpawnedPawns.Count > 0) + var spawned = GetSafeSpawnedPawns(); + sb.AppendLine($"已生成Pawn数: {spawned.Count}"); + foreach (var spawnedPawn in spawned) { - foreach (var spawnedPawn in SpawnedPawns) + if (spawnedPawn != null && !spawnedPawn.Destroyed) { - if (spawnedPawn != null && !spawnedPawn.Destroyed) - { - sb.AppendLine($" • {spawnedPawn.LabelCap} ({spawnedPawn.kindDef?.label ?? "未知"})"); - } + sb.AppendLine($" • {spawnedPawn.LabelCap} ({spawnedPawn.kindDef?.label ?? "未知"})"); + + // 检查双向验证 + var childComp = spawnedPawn.TryGetComp(); + bool valid = childComp != null && childComp.IsProducerValid; + sb.AppendLine($" 双向验证: {valid}"); } } - // 冷却时间 - sb.AppendLine($"冷却时间剩余: {CooldownTicksRemaining}"); + // 验证缓存 + sb.AppendLine($"验证缓存数: {validatedChildren.Count}"); + sb.AppendLine($"死亡队列: {childDeathQueue.Count}"); return sb.ToString(); } public override void PostExposeData() { - base.PostExposeData(); - - // 保存自定义跟随位置 - Scribe_Values.Look(ref customFollowPosition, "customFollowPosition"); - Scribe_Values.Look(ref temporaryPositionExpiryTick, "temporaryPositionExpiryTick", -1); - - // 注意:Map不能直接序列化,需要在PostLoadInit中重新获取 - if (Scribe.mode == LoadSaveMode.LoadingVars) + try { - customFollowPositionMap = parent?.Map; + base.PostExposeData(); + + // 保存自定义跟随位置 + Scribe_Values.Look(ref customFollowPosition, "customFollowPosition"); + Scribe_Values.Look(ref temporaryPositionExpiryTick, "temporaryPositionExpiryTick", -1); + + // 保存验证缓存 + Scribe_Collections.Look(ref validatedChildren, "validatedChildren", LookMode.Reference); + Scribe_Collections.Look(ref childDeathQueue, "childDeathQueue", LookMode.Reference); + + if (Scribe.mode == LoadSaveMode.LoadingVars) + { + customFollowPositionMap = parent?.Map; + } + } + catch (System.Exception ex) + { + Log.Error($"CompAutoMechCarrier 序列化错误: {ex.Message}"); } } @@ -629,7 +932,6 @@ namespace ArachnaeSwarm { customFollowPositionMap = parent?.Map; - // 如果自定义位置无效,清除它 if (customFollowPosition.HasValue && (customFollowPositionMap == null || !customFollowPosition.Value.InBounds(customFollowPositionMap))) @@ -640,6 +942,45 @@ namespace ArachnaeSwarm // 初始化生产队列缓存 RefreshProductionQueue(); + + // 初始化验证系统 + lock (validationLock) + { + if (validatedChildren == null) + validatedChildren = new HashSet(); + + if (childDeathQueue == null) + childDeathQueue = new Queue(); + } + } + + public override void PostDestroy(DestroyMode mode, Map previousMap) + { + try + { + // 清理所有子单位引用 + lock (validationLock) + { + var spawned = GetSafeSpawnedPawns(); + foreach (var pawn in spawned) + { + var comp = pawn.TryGetComp(); + if (comp != null) + { + comp.TryUpdateProducerStatus(); + } + } + + validatedChildren.Clear(); + childDeathQueue.Clear(); + } + } + catch (System.Exception ex) + { + Log.Error($"销毁时清理失败: {ex.Message}"); + } + + base.PostDestroy(mode, previousMap); } } } diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_AutoMechCarrier/CompProducedByMechCarrier.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_AutoMechCarrier/CompProducedByMechCarrier.cs index bc02236..d2e3faa 100644 --- a/Source/ArachnaeSwarm/Pawn_Comps/ARA_AutoMechCarrier/CompProducedByMechCarrier.cs +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_AutoMechCarrier/CompProducedByMechCarrier.cs @@ -6,16 +6,40 @@ using Verse.AI; namespace ArachnaeSwarm { - public class CompProducedByMechCarrier : ThingComp + public class CompProducedByMechCarrier : ThingComp, IBidirectionalValidator { private Thing producer; private CompAutoMechCarrier producerComp; private int lastProducerCheckTick = -1; private const int PRODUCER_CHECK_INTERVAL = 60; - public Thing Producer => producer; - public CompAutoMechCarrier ProducerComp => producerComp; + // 死亡检测相关 + private bool wasDead = false; + private int deathTick = -1; + private const int DEATH_CLEANUP_DELAY = 60; // 死后60tick进行清理 + // 安全锁 + private object validationLock = new object(); + + // === IBidirectionalValidator 实现 === + public bool IsProducerValid + { + get + { + lock (validationLock) + { + if (producer == null || producer.Destroyed) + return false; + + // 如果是pawn,检查是否死亡或倒下 + if (producer is Pawn pawn && (pawn.Dead || pawn.Downed)) + return false; + + return true; + } + } + } + // 公开属性,用于其他类访问 public bool HasValidProducer { @@ -23,42 +47,174 @@ namespace ArachnaeSwarm { if (producer == null || producer.Destroyed) return false; - + // 如果是pawn,检查是否死亡或倒下 if (producer is Pawn pawn && (pawn.Dead || pawn.Downed)) return false; - + return true; } } + + public Thing GetProducer() + { + lock (validationLock) + { + return IsProducerValid ? producer : null; + } + } + + public bool IsProducerAlive + { + get + { + lock (validationLock) + { + return IsProducerValid && producer.Spawned; + } + } + } + + public bool IsChildValid(Thing child) + { + if (child == null || child.Destroyed) + return false; + + // 检查是否为pawn + if (!(child is Pawn)) + return false; + + // 检查是否死亡 + if (child is Pawn pawn && (pawn.Dead || pawn.Downed)) + return false; + + return true; + } + + public bool IsChildAlive(Thing child) + { + return IsChildValid(child) && child.Spawned; + } + + public bool ValidateBidirectional(Thing child) + { + lock (validationLock) + { + // 验证子单位 + if (!IsChildValid(child)) + return false; + + // 验证生产者 + if (!IsProducerValid) + return false; + + // 验证双向引用 + return ValidateBidirectionalReference(child); + } + } + + public bool ValidateAndExecute(Thing child, System.Action action) + { + if (!ValidateBidirectional(child)) + return false; + + try + { + action?.Invoke(); + return true; + } + catch (System.Exception ex) + { + Log.Error($"双向验证执行失败: {ex.Message}"); + return false; + } + } + + private bool ValidateBidirectionalReference(Thing child) + { + // 检查生产者是否包含这个子单位 + var producerCarrier = GetProducerComp(); + if (producerCarrier == null) + return false; + + // 验证生产者是否知道这个子单位 + return producerCarrier.ValidateChildReference(child); + } + // === 接口实现结束 === + + public Thing Producer => GetProducer(); + public CompAutoMechCarrier ProducerComp => GetProducerComp(); + + // 获取生产者组件的安全方法 + private CompAutoMechCarrier GetProducerComp() + { + lock (validationLock) + { + if (!IsProducerValid) + return null; + + // 如果组件为空,尝试获取 + if (producerComp == null || producerComp.parent != producer) + { + producerComp = producer.TryGetComp(); + } + + return producerComp; + } + } // 检查是否应该跟随生产者 public bool ShouldFollowProducer { get { - if (!HasValidProducer) + lock (validationLock) + { + if (!IsProducerValid) + return false; + + // 确保pawn是有效的 + Pawn pawn = parent as Pawn; + if (pawn == null || !pawn.Spawned || pawn.Downed || pawn.Dead) + return false; + + // 确保生产者在同一地图且可以到达 + if (producer.Map != pawn.Map || !pawn.CanReach(producer, PathEndMode.OnCell, Danger.Deadly)) + return false; + + return true; + } + } + } + + // 初始化方法 - 安全版本 + public bool InitializeSafe(Thing producer, CompAutoMechCarrier producerComp) + { + lock (validationLock) + { + // 验证生产者 + if (producer == null || producer.Destroyed) return false; - // 确保pawn是有效的 - Pawn pawn = parent as Pawn; - if (pawn == null || !pawn.Spawned || pawn.Downed || pawn.Dead) + // 验证生产者组件 + if (producerComp == null || producerComp.parent != producer) return false; - // 确保生产者在同一地图且可以到达 - if (producer.Map != pawn.Map || !pawn.CanReach(producer, PathEndMode.OnCell, Danger.Deadly)) - return false; - - // 可以根据需要添加更多条件 + this.producer = producer; + this.producerComp = producerComp; + + // 记录初始化时间 + lastProducerCheckTick = Find.TickManager.TicksGame; + + Log.Message($"双向验证: {parent?.LabelCap} -> {producer.LabelCap} 初始化成功"); return true; } } - // 初始化方法 + // 原始初始化方法(保持向后兼容) public void Initialize(Thing producer, CompAutoMechCarrier producerComp) { - this.producer = producer; - this.producerComp = producerComp; + InitializeSafe(producer, producerComp); } // 尝试更新生产者状态 @@ -69,9 +225,61 @@ namespace ArachnaeSwarm lastProducerCheckTick = Find.TickManager.TicksGame; - // 检查生产者是否仍然有效 - if (producer != null && (producer.Destroyed || (producer is Pawn p && (p.Dead || p.Downed)))) + lock (validationLock) { + // 检查生产者是否仍然有效 + if (producer != null && (producer.Destroyed || (producer is Pawn p && (p.Dead || p.Downed)))) + { + Log.Warning($"生产者无效: {producer?.LabelCap}, 清除引用"); + producer = null; + producerComp = null; + } + + // 检查自身状态 + CheckSelfStatus(); + } + } + + // 检查自身状态 + private void CheckSelfStatus() + { + // 检查是否死亡 + if (parent is Pawn pawn) + { + if (pawn.Dead) + { + if (!wasDead) + { + wasDead = true; + deathTick = Find.TickManager.TicksGame; + Log.Message($"子单位死亡: {pawn.LabelCap}, 准备通知生产者"); + } + + // 死亡后延迟清理 + if (deathTick >= 0 && Find.TickManager.TicksGame - deathTick >= DEATH_CLEANUP_DELAY) + { + NotifyProducerOfDeath(); + } + } + else + { + wasDead = false; + deathTick = -1; + } + } + } + + // 通知生产者自己死亡 + private void NotifyProducerOfDeath() + { + lock (validationLock) + { + if (producerComp != null && !producerComp.parent.Destroyed) + { + producerComp.NotifyChildDeath(parent); + } + + // 清除引用 producer = null; producerComp = null; } @@ -80,38 +288,112 @@ namespace ArachnaeSwarm // 获取生产者的位置 public IntVec3 GetProducerPosition() { - if (!HasValidProducer) - return IntVec3.Invalid; - - return producer.Position; + lock (validationLock) + { + if (!IsProducerValid) + return IntVec3.Invalid; + + return producer.Position; + } } // 获取生产者的交互单元格 public IntVec3 GetProducerInteractionCell() { - if (!HasValidProducer) - return IntVec3.Invalid; - - if (producer is Building building) - return building.InteractionCell; - - return producer.Position; + lock (validationLock) + { + if (!IsProducerValid) + return IntVec3.Invalid; + + if (producer is Building building) + return building.InteractionCell; + + return producer.Position; + } } // 检查是否在生产者附近 public bool IsNearProducer(Pawn pawn, float radius) { - if (!HasValidProducer) - return false; - - return pawn.Position.DistanceTo(producer.Position) <= radius; + lock (validationLock) + { + if (!IsProducerValid) + return false; + + return pawn.Position.DistanceTo(producer.Position) <= radius; + } + } + + // === Tick 方法 === + public override void CompTick() + { + base.CompTick(); + + try + { + // 定期检查生产者状态 + TryUpdateProducerStatus(); + } + catch (System.Exception ex) + { + Log.Error($"CompProducedByMechCarrier Tick错误: {ex.Message}"); + } + } + + public override void CompTickRare() + { + base.CompTickRare(); + + try + { + // 定期清理 + lock (validationLock) + { + if (producer != null && producer.Destroyed) + { + producer = null; + producerComp = null; + } + } + } + catch (System.Exception ex) + { + Log.Error($"CompProducedByMechCarrier TickRare错误: {ex.Message}"); + } } public override void PostExposeData() { base.PostExposeData(); - Scribe_References.Look(ref producer, "producer"); - Scribe_Values.Look(ref lastProducerCheckTick, "lastProducerCheckTick", -1); + + try + { + Scribe_References.Look(ref producer, "producer"); + Scribe_Values.Look(ref lastProducerCheckTick, "lastProducerCheckTick", -1); + Scribe_Values.Look(ref wasDead, "wasDead", false); + Scribe_Values.Look(ref deathTick, "deathTick", -1); + } + catch (System.Exception ex) + { + Log.Error($"CompProducedByMechCarrier 序列化错误: {ex.Message}"); + } + } + + // === 调试方法 === + public string GetValidationStatus() + { + lock (validationLock) + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + sb.AppendLine($"=== {parent?.LabelCap} 验证状态 ==="); + sb.AppendLine($"生产者有效: {IsProducerValid}"); + sb.AppendLine($"生产者存活: {IsProducerAlive}"); + sb.AppendLine($"生产者: {producer?.LabelCap ?? "NULL"}"); + sb.AppendLine($"生产者组件: {producerComp != null}"); + sb.AppendLine($"上次检查: {lastProducerCheckTick}"); + + return sb.ToString(); + } } } } diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_AutoMechCarrier/IBidirectionalValidator.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_AutoMechCarrier/IBidirectionalValidator.cs new file mode 100644 index 0000000..0a51f3a --- /dev/null +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_AutoMechCarrier/IBidirectionalValidator.cs @@ -0,0 +1,26 @@ +using RimWorld; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + /// + /// 双向安全校验接口 + /// 确保生产者和子单位在操作前互相验证存在 + /// + public interface IBidirectionalValidator + { + // 生产者相关 + bool IsProducerValid { get; } + Thing GetProducer(); + bool IsProducerAlive { get; } + + // 子单位相关 + bool IsChildValid(Thing child); + bool IsChildAlive(Thing child); + + // 双向验证 + bool ValidateBidirectional(Thing child); + bool ValidateAndExecute(Thing child, System.Action action); + } +}