This commit is contained in:
2026-01-28 11:17:26 +08:00
parent c7098a07c0
commit ae529319a9
12 changed files with 995 additions and 675 deletions

Binary file not shown.

View File

@@ -50,6 +50,7 @@
<race> <race>
<body>BeetleLikeWithClaw</body> <body>BeetleLikeWithClaw</body>
<thinkTreeMain>ARA_Insect_WithPlanting</thinkTreeMain> <thinkTreeMain>ARA_Insect_WithPlanting</thinkTreeMain>
<thinkTreeConstant>ARA_Insect_Thinktree_Constant</thinkTreeConstant>
<foodType>CarnivoreAnimal,OvivoreAnimal</foodType> <foodType>CarnivoreAnimal,OvivoreAnimal</foodType>
<baseHungerRate>0.1</baseHungerRate> <baseHungerRate>0.1</baseHungerRate>
<baseBodySize>0.5</baseBodySize> <baseBodySize>0.5</baseBodySize>

View File

@@ -757,6 +757,36 @@
</thinkRoot> </thinkRoot>
</ThinkTreeDef> </ThinkTreeDef>
<ThinkTreeDef>
<defName>ARA_Insect_Thinktree_Constant</defName>
<thinkRoot Class="ThinkNode_Priority">
<subNodes>
<!-- Despawned -->
<li Class="ThinkNode_Subtree">
<treeDef>Despawned</treeDef>
</li>
<li Class="ThinkNode_ConditionalCanDoConstantThinkTreeJobNow">
<subNodes>
<!-- Flee explosion -->
<li Class="JobGiver_FleePotentialExplosion" />
<!-- Avoid vacuums -->
<li Class="JobGiver_FindOxygen" />
<!-- Board/leave gravship -->
<li Class="JobGiver_BoardOrLeaveGravship" />
<!-- Join auto joinable caravan -->
<li Class="ThinkNode_Subtree">
<treeDef>JoinAutoJoinableCaravan</treeDef>
</li>
</subNodes>
</li>
</subNodes>
</thinkRoot>
</ThinkTreeDef>
<ThinkTreeDef> <ThinkTreeDef>
<defName>ARA_Insect_Larva_Thinktree</defName> <defName>ARA_Insect_Larva_Thinktree</defName>
<thinkRoot Class="ThinkNode_Priority"> <thinkRoot Class="ThinkNode_Priority">

View File

@@ -1,92 +1,7 @@
{ {
"Version": 1, "Version": 1,
"WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\", "WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
"Documents": [ "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}"
}
],
"DocumentGroupContainers": [ "DocumentGroupContainers": [
{ {
"Orientation": 0, "Orientation": 0,
@@ -94,271 +9,11 @@
"DocumentGroups": [ "DocumentGroups": [
{ {
"DockedWidth": 200, "DockedWidth": 200,
"SelectedChildIndex": 6, "SelectedChildIndex": -1,
"Children": [ "Children": [
{ {
"$type": "Bookmark", "$type": "Bookmark",
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" "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"
} }
] ]
} }

View File

@@ -334,6 +334,7 @@
<Compile Include="Pawn_Comps\ARA_AutoMechCarrier\CompProducedByMechCarrier.cs" /> <Compile Include="Pawn_Comps\ARA_AutoMechCarrier\CompProducedByMechCarrier.cs" />
<Compile Include="Pawn_Comps\ARA_AutoMechCarrier\CompProperties_AutoMechCarrier.cs" /> <Compile Include="Pawn_Comps\ARA_AutoMechCarrier\CompProperties_AutoMechCarrier.cs" />
<Compile Include="Pawn_Comps\ARA_AutoMechCarrier\CompProperties_ProducedByMechCarrier.cs" /> <Compile Include="Pawn_Comps\ARA_AutoMechCarrier\CompProperties_ProducedByMechCarrier.cs" />
<Compile Include="Pawn_Comps\ARA_AutoMechCarrier\IBidirectionalValidator.cs" />
<Compile Include="Pawn_Comps\ARA_AutoMechCarrier\PawnProductionEntry.cs" /> <Compile Include="Pawn_Comps\ARA_AutoMechCarrier\PawnProductionEntry.cs" />
<Compile Include="Pawn_Comps\ARA_CompHediffGiver\CompHediffGiver.cs" /> <Compile Include="Pawn_Comps\ARA_CompHediffGiver\CompHediffGiver.cs" />
<Compile Include="Pawn_Comps\ARA_CompHediffGiver\CompProperties_HediffGiver.cs" /> <Compile Include="Pawn_Comps\ARA_CompHediffGiver\CompProperties_HediffGiver.cs" />

View File

@@ -561,20 +561,30 @@ namespace ArachnaeSwarm
} }
public override IEnumerable<Gizmo> GetGizmos() public override IEnumerable<Gizmo> 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(); var selectedOothecas = GetSelectedOothecas();
bool isMultiSelect = selectedOothecas.Count > 1; bool isMultiSelect = selectedOothecas.Count > 1;
if (Faction == Faction.OfPlayer) 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; var config = IncubatorData?.SelectedConfig;
// 添加订单按钮(多选时合并) // 添加订单按钮(多选时合并)
@@ -657,6 +667,7 @@ namespace ArachnaeSwarm
} }
} }
} }
/// <summary> /// <summary>
/// 为多选建筑显示订单菜单 /// 为多选建筑显示订单菜单
/// </summary> /// </summary>

View File

@@ -1,4 +1,6 @@
using RimWorld; using RimWorld;
using System.Collections.Generic;
using System.Linq;
using Verse; using Verse;
using Verse.AI; using Verse.AI;
@@ -14,6 +16,12 @@ namespace ArachnaeSwarm
// 是否仅防御征召状态的生产者针对pawn类型生产者 // 是否仅防御征召状态的生产者针对pawn类型生产者
public bool onlyDefendDrafted = true; public bool onlyDefendDrafted = true;
// 是否排除休眠的机械族
public bool excludeDormantMechs = true;
// 是否将炮塔纳入考虑
public bool includeHostileTurrets = true;
protected override Pawn GetDefendee(Pawn pawn) protected override Pawn GetDefendee(Pawn pawn)
{ {
// 我们不需要返回Pawn类型的防御者因为我们实际上防御的是Thing // 我们不需要返回Pawn类型的防御者因为我们实际上防御的是Thing
@@ -79,7 +87,7 @@ namespace ArachnaeSwarm
float defendRadiusValue = GetFlagRadius(pawn); float defendRadiusValue = GetFlagRadius(pawn);
// 寻找附近的威胁 // 寻找附近的威胁
Pawn enemy = FindEnemy(pawn, defendRadiusValue); Thing enemy = FindEnemy(pawn, defendRadiusValue);
if (enemy != null) if (enemy != null)
{ {
// 创建攻击工作 // 创建攻击工作
@@ -94,7 +102,8 @@ namespace ArachnaeSwarm
return null; return null;
} }
private Pawn FindEnemy(Pawn pawn, float radius)
protected virtual Thing FindEnemy(Pawn pawn, float radius)
{ {
CompProducedByMechCarrier producerComp = pawn.TryGetComp<CompProducedByMechCarrier>(); CompProducedByMechCarrier producerComp = pawn.TryGetComp<CompProducedByMechCarrier>();
if (producerComp == null || !producerComp.HasValidProducer) if (producerComp == null || !producerComp.HasValidProducer)
@@ -103,10 +112,10 @@ namespace ArachnaeSwarm
Thing producer = producerComp.Producer; Thing producer = producerComp.Producer;
IntVec3 center = producer.Position; IntVec3 center = producer.Position;
return (Pawn)AttackTargetFinder.BestAttackTarget( return (Thing)AttackTargetFinder.BestAttackTarget(
pawn, pawn,
TargetScanFlags.NeedLOSToAll, TargetScanFlags.NeedThreat,
target => target is Pawn p && pawn.HostileTo(p) && p.Spawned && !p.Downed && !p.Dead, (Thing x) => ExtraTargetValidator(pawn, x),
0f, 0f,
radius, radius,
center, center,
@@ -121,6 +130,9 @@ namespace ArachnaeSwarm
JobGiver_AIDefendProducer obj = (JobGiver_AIDefendProducer)base.DeepCopy(resolve); JobGiver_AIDefendProducer obj = (JobGiver_AIDefendProducer)base.DeepCopy(resolve);
obj.attackMeleeThreatEvenIfNotHostile = attackMeleeThreatEvenIfNotHostile; obj.attackMeleeThreatEvenIfNotHostile = attackMeleeThreatEvenIfNotHostile;
obj.defendRadius = defendRadius; obj.defendRadius = defendRadius;
obj.onlyDefendDrafted = onlyDefendDrafted;
obj.excludeDormantMechs = excludeDormantMechs;
obj.includeHostileTurrets = includeHostileTurrets;
return obj; return obj;
} }

View File

@@ -10,18 +10,6 @@ namespace ArachnaeSwarm
/// </summary> /// </summary>
public class ThinkNode_ConditionalNotProducedByMechCarrier : ThinkNode_Conditional 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() public ThinkNode_ConditionalNotProducedByMechCarrier()
{ {
} }
@@ -29,7 +17,7 @@ namespace ArachnaeSwarm
protected override bool Satisfied(Pawn pawn) protected override bool Satisfied(Pawn pawn)
{ {
// 基础检查如果不是生产者生产的返回true // 基础检查如果不是生产者生产的返回true
bool isProduced = IsProducedByMechCarrier(pawn); bool isProduced = HasProducer(pawn);
// 如果是生产者生产的,再检查其他条件 // 如果是生产者生产的,再检查其他条件
if (isProduced) if (isProduced)
@@ -44,7 +32,7 @@ namespace ArachnaeSwarm
/// <summary> /// <summary>
/// 检查pawn是否由生产者生产 /// 检查pawn是否由生产者生产
/// </summary> /// </summary>
private bool IsProducedByMechCarrier(Pawn pawn) private bool HasProducer(Pawn pawn)
{ {
if (!pawn.Spawned) if (!pawn.Spawned)
return false; return false;
@@ -58,33 +46,6 @@ namespace ArachnaeSwarm
if (producer == null || producer.Destroyed) if (producer == null || producer.Destroyed)
return false; 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; return true;
} }
} }

View File

@@ -9,48 +9,63 @@ using Verse.AI.Group;
namespace ArachnaeSwarm namespace ArachnaeSwarm
{ {
public class CompAutoMechCarrier : CompMechCarrier public class CompAutoMechCarrier : CompMechCarrier, IBidirectionalValidator
{ {
#region Reflected Fields #region Reflected Fields
private static FieldInfo spawnedPawnsField; private static FieldInfo spawnedPawnsField;
private static FieldInfo cooldownTicksRemainingField; private static FieldInfo cooldownTicksRemainingField;
private static FieldInfo innerContainerField; private static FieldInfo innerContainerField;
// 安全锁
private object validationLock = new object();
private List<Pawn> SpawnedPawns private List<Pawn> SpawnedPawns
{ {
get get
{
lock (validationLock)
{ {
if (spawnedPawnsField == null) if (spawnedPawnsField == null)
spawnedPawnsField = typeof(CompMechCarrier).GetField("spawnedPawns", BindingFlags.NonPublic | BindingFlags.Instance); spawnedPawnsField = typeof(CompMechCarrier).GetField("spawnedPawns", BindingFlags.NonPublic | BindingFlags.Instance);
return (List<Pawn>)spawnedPawnsField.GetValue(this); return (List<Pawn>)spawnedPawnsField.GetValue(this);
} }
} }
}
private int CooldownTicksRemaining private int CooldownTicksRemaining
{ {
get get
{
lock (validationLock)
{ {
if (cooldownTicksRemainingField == null) if (cooldownTicksRemainingField == null)
cooldownTicksRemainingField = typeof(CompMechCarrier).GetField("cooldownTicksRemaining", BindingFlags.NonPublic | BindingFlags.Instance); cooldownTicksRemainingField = typeof(CompMechCarrier).GetField("cooldownTicksRemaining", BindingFlags.NonPublic | BindingFlags.Instance);
return (int)cooldownTicksRemainingField.GetValue(this); return (int)cooldownTicksRemainingField.GetValue(this);
} }
}
set set
{
lock (validationLock)
{ {
if (cooldownTicksRemainingField == null) if (cooldownTicksRemainingField == null)
cooldownTicksRemainingField = typeof(CompMechCarrier).GetField("cooldownTicksRemaining", BindingFlags.NonPublic | BindingFlags.Instance); cooldownTicksRemainingField = typeof(CompMechCarrier).GetField("cooldownTicksRemaining", BindingFlags.NonPublic | BindingFlags.Instance);
cooldownTicksRemainingField.SetValue(this, value); cooldownTicksRemainingField.SetValue(this, value);
} }
} }
}
private ThingOwner InnerContainer private ThingOwner InnerContainer
{ {
get get
{
lock (validationLock)
{ {
if (innerContainerField == null) if (innerContainerField == null)
innerContainerField = typeof(CompMechCarrier).GetField("innerContainer", BindingFlags.NonPublic | BindingFlags.Instance); innerContainerField = typeof(CompMechCarrier).GetField("innerContainer", BindingFlags.NonPublic | BindingFlags.Instance);
return (ThingOwner)innerContainerField.GetValue(this); return (ThingOwner)innerContainerField.GetValue(this);
} }
} }
}
#endregion #endregion
#region Custom Follow Position #region Custom Follow Position
@@ -60,7 +75,6 @@ namespace ArachnaeSwarm
/// <summary> /// <summary>
/// 获取或设置自定义跟随位置 /// 获取或设置自定义跟随位置
/// 如果此位置不为空,生成的单位将以此位置而非父单位位置为跟随中心点
/// </summary> /// </summary>
public IntVec3? CustomFollowPosition public IntVec3? CustomFollowPosition
{ {
@@ -85,14 +99,13 @@ namespace ArachnaeSwarm
customFollowPositionMap = null; customFollowPositionMap = null;
} }
// 通知所有已生成的单位更新他们的跟随位置
NotifyFollowPositionChanged(); NotifyFollowPositionChanged();
} }
} }
} }
/// <summary> /// <summary>
/// 检查自定义位置是否有效(位置不为空且在正确的地图上) /// 检查自定义位置是否有效
/// </summary> /// </summary>
public bool HasValidCustomFollowPosition public bool HasValidCustomFollowPosition
{ {
@@ -101,7 +114,6 @@ namespace ArachnaeSwarm
if (!customFollowPosition.HasValue || customFollowPositionMap == null) if (!customFollowPosition.HasValue || customFollowPositionMap == null)
return false; return false;
// 如果父单位已销毁或不在同一地图,自定义位置也无效
if (parent == null || parent.Destroyed || !parent.Spawned) if (parent == null || parent.Destroyed || !parent.Spawned)
return false; return false;
@@ -111,7 +123,6 @@ namespace ArachnaeSwarm
/// <summary> /// <summary>
/// 获取有效的跟随位置 /// 获取有效的跟随位置
/// 优先返回自定义位置,如果无效则返回父单位位置
/// </summary> /// </summary>
public IntVec3 GetEffectiveFollowPosition() public IntVec3 GetEffectiveFollowPosition()
{ {
@@ -120,7 +131,6 @@ namespace ArachnaeSwarm
return customFollowPosition.Value; return customFollowPosition.Value;
} }
// 返回父单位位置
if (parent != null && parent.Spawned) if (parent != null && parent.Spawned)
{ {
if (parent is Building building) if (parent is Building building)
@@ -156,19 +166,16 @@ namespace ArachnaeSwarm
/// </summary> /// </summary>
private void NotifyFollowPositionChanged() private void NotifyFollowPositionChanged()
{ {
if (SpawnedPawns == null || !SpawnedPawns.Any()) var spawned = GetSafeSpawnedPawns();
if (spawned == null || !spawned.Any())
return; return;
// 清理无效的Pawn引用 foreach (var pawn in spawned)
SpawnedPawns.RemoveAll(p => p == null || p.Destroyed);
// 通知每个Pawn的CompProducedByMechCarrier
foreach (Pawn spawnedPawn in SpawnedPawns)
{ {
if (spawnedPawn == null || spawnedPawn.Destroyed || !spawnedPawn.Spawned) if (pawn == null || pawn.Destroyed || !pawn.Spawned)
continue; continue;
var producedComp = spawnedPawn.TryGetComp<CompProducedByMechCarrier>(); var producedComp = pawn.TryGetComp<CompProducedByMechCarrier>();
if (producedComp != null) if (producedComp != null)
{ {
producedComp.TryUpdateProducerStatus(); producedComp.TryUpdateProducerStatus();
@@ -187,8 +194,6 @@ namespace ArachnaeSwarm
/// <summary> /// <summary>
/// 设置自定义跟随位置 /// 设置自定义跟随位置
/// </summary> /// </summary>
/// <param name="position">新的跟随位置</param>
/// <returns>是否设置成功</returns>
public bool SetCustomFollowPosition(IntVec3 position) public bool SetCustomFollowPosition(IntVec3 position)
{ {
if (parent == null || !parent.Spawned) if (parent == null || !parent.Spawned)
@@ -202,23 +207,18 @@ namespace ArachnaeSwarm
} }
/// <summary> /// <summary>
/// 临时设置自定义跟随位置(在一段时间后自动清除) /// 临时设置自定义跟随位置
/// </summary> /// </summary>
/// <param name="position">临时位置</param>
/// <param name="durationTicks">持续时间tick</param>
public void SetTemporaryFollowPosition(IntVec3 position, int durationTicks) public void SetTemporaryFollowPosition(IntVec3 position, int durationTicks)
{ {
if (!SetCustomFollowPosition(position)) if (!SetCustomFollowPosition(position))
return; return;
// 启动定时器清除临时位置
StartPositionClearTimer(durationTicks); StartPositionClearTimer(durationTicks);
} }
private void StartPositionClearTimer(int durationTicks) private void StartPositionClearTimer(int durationTicks)
{ {
// 这里可以使用TickManager来安排定时清除
// 简单实现记录时间并在Tick中检查
temporaryPositionExpiryTick = Find.TickManager.TicksGame + durationTicks; temporaryPositionExpiryTick = Find.TickManager.TicksGame + durationTicks;
} }
#endregion #endregion
@@ -238,35 +238,245 @@ namespace ArachnaeSwarm
} }
#endregion #endregion
#region
// 子单位死亡通知队列
private Queue<Thing> childDeathQueue = new Queue<Thing>();
private int lastChildCleanupTick = -1;
private const int CHILD_CLEANUP_INTERVAL = 30;
// 已验证的子单位缓存
private HashSet<Thing> validatedChildren = new HashSet<Thing>();
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<CompProducedByMechCarrier>();
bool hasValidReference = childComp != null && childComp.IsProducerValid && childComp.GetProducer() == parent;
bool isValid = isInList && hasValidReference;
if (isValid)
{
validatedChildren.Add(child);
}
return isValid;
}
/// <summary>
/// 验证子单位引用
/// </summary>
public bool ValidateChildReference(Thing child)
{
return ValidateBidirectionalReference(child);
}
/// <summary>
/// 通知子单位死亡
/// </summary>
public void NotifyChildDeath(Thing child)
{
lock (validationLock)
{
childDeathQueue.Enqueue(child);
}
}
/// <summary>
/// 安全移除子单位
/// </summary>
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}");
}
}
}
/// <summary>
/// 清理无效的子单位引用
/// </summary>
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; public CompProperties_AutoMechCarrier AutoProps => (CompProperties_AutoMechCarrier)props;
// 缓存的合并生产队列 // 缓存的合并生产队列
private List<PawnProductionEntry> cachedProductionQueue = null; private List<PawnProductionEntry> cachedProductionQueue = null;
private int lastProductionQueueUpdateTick = -1; private int lastProductionQueueUpdateTick = -1;
private const int PRODUCTION_QUEUE_CACHE_TICKS = 30; // 30 tick缓存 private const int PRODUCTION_QUEUE_CACHE_TICKS = 30;
/// <summary> /// <summary>
/// 获取合并后的生产队列 /// 获取合并后的生产队列
/// 包括从HediffComp扫描的和动态添加的
/// </summary> /// </summary>
public List<PawnProductionEntry> GetProductionQueue() public List<PawnProductionEntry> GetProductionQueue()
{ {
// 使用缓存提高性能
if (cachedProductionQueue != null && if (cachedProductionQueue != null &&
Find.TickManager.TicksGame - lastProductionQueueUpdateTick < PRODUCTION_QUEUE_CACHE_TICKS) Find.TickManager.TicksGame - lastProductionQueueUpdateTick < PRODUCTION_QUEUE_CACHE_TICKS)
{ {
return cachedProductionQueue; return cachedProductionQueue;
} }
// 获取Pawn引用 try
{
var pawn = parent as Pawn; var pawn = parent as Pawn;
// 从AutoProps获取合并后的生产队列
cachedProductionQueue = AutoProps.GetMergedProductionQueue(pawn); cachedProductionQueue = AutoProps.GetMergedProductionQueue(pawn);
lastProductionQueueUpdateTick = Find.TickManager.TicksGame; lastProductionQueueUpdateTick = Find.TickManager.TicksGame;
return cachedProductionQueue; return cachedProductionQueue;
} }
catch (System.Exception ex)
{
Log.Error($"获取生产队列失败: {ex.Message}");
return new List<PawnProductionEntry>();
}
}
/// <summary> /// <summary>
/// 强制刷新生产队列缓存 /// 强制刷新生产队列缓存
@@ -277,6 +487,23 @@ namespace ArachnaeSwarm
lastProductionQueueUpdateTick = -1; lastProductionQueueUpdateTick = -1;
} }
/// <summary>
/// 安全获取已生成的Pawn列表
/// </summary>
private List<Pawn> GetSafeSpawnedPawns()
{
lock (validationLock)
{
var spawned = SpawnedPawns;
if (spawned == null)
return new List<Pawn>();
spawned.RemoveAll(p => p == null || p.Destroyed || (p is Pawn pawn && pawn.Dead));
return spawned;
}
}
/// <summary> /// <summary>
/// 获取总容量 /// 获取总容量
/// </summary> /// </summary>
@@ -284,14 +511,17 @@ namespace ArachnaeSwarm
private int LiveSpawnedPawnsCount(PawnKindDef kind) private int LiveSpawnedPawnsCount(PawnKindDef kind)
{ {
SpawnedPawns.RemoveAll(p => p == null || p.Destroyed); var spawned = GetSafeSpawnedPawns();
return SpawnedPawns.Count(p => p.kindDef == kind); return spawned.Count(p => p.kindDef == kind);
} }
private AcceptanceReport CanSpawnNow(PawnKindDef kind) private AcceptanceReport CanSpawnNow(PawnKindDef kind)
{
try
{ {
if (parent is Pawn pawn && (pawn.IsSelfShutdown() || !pawn.Awake() || pawn.Downed || pawn.Dead || !pawn.Spawned)) if (parent is Pawn pawn && (pawn.IsSelfShutdown() || !pawn.Awake() || pawn.Downed || pawn.Dead || !pawn.Spawned))
return false; return false;
if (CooldownTicksRemaining > 0) if (CooldownTicksRemaining > 0)
return "CooldownTime".Translate() + " " + CooldownTicksRemaining.ToStringSecondsFromTicks(); return "CooldownTime".Translate() + " " + CooldownTicksRemaining.ToStringSecondsFromTicks();
@@ -304,10 +534,19 @@ namespace ArachnaeSwarm
if (!AutoProps.freeProduction && InnerContainer.TotalStackCountOfDef(Props.fixedIngredient) < cost) if (!AutoProps.freeProduction && InnerContainer.TotalStackCountOfDef(Props.fixedIngredient) < cost)
return "MechCarrierNotEnoughResources".Translate(); return "MechCarrierNotEnoughResources".Translate();
return true; return true;
} }
catch (System.Exception ex)
{
Log.Error($"CanSpawnNow检查失败: {ex.Message}");
return $"生产检查失败: {ex.Message}";
}
}
private void TrySpawnPawn(PawnKindDef kind) private void TrySpawnPawn(PawnKindDef kind)
{
try
{ {
PawnGenerationRequest request = new PawnGenerationRequest(kind, parent.Faction, PawnGenerationContext.NonPlayer, -1, forceGenerateNewPawn: true); PawnGenerationRequest request = new PawnGenerationRequest(kind, parent.Faction, PawnGenerationContext.NonPlayer, -1, forceGenerateNewPawn: true);
Pawn pawn = PawnGenerator.GeneratePawn(request); Pawn pawn = PawnGenerator.GeneratePawn(request);
@@ -316,7 +555,6 @@ namespace ArachnaeSwarm
var producedComp = pawn.TryGetComp<CompProducedByMechCarrier>(); var producedComp = pawn.TryGetComp<CompProducedByMechCarrier>();
if (producedComp == null) if (producedComp == null)
{ {
// 如果pawn没有这个Comp添加它
var compProps = new CompProperties_ProducedByMechCarrier(); var compProps = new CompProperties_ProducedByMechCarrier();
compProps.compClass = typeof(CompProducedByMechCarrier); compProps.compClass = typeof(CompProducedByMechCarrier);
var newComp = (CompProducedByMechCarrier)Activator.CreateInstance(compProps.compClass); var newComp = (CompProducedByMechCarrier)Activator.CreateInstance(compProps.compClass);
@@ -326,10 +564,15 @@ namespace ArachnaeSwarm
producedComp = newComp; producedComp = newComp;
} }
// 初始化生产者信息 // 初始化生产者信息 - 使用安全初始化
producedComp.Initialize(parent, this); bool initSuccess = producedComp.InitializeSafe(parent, this);
if (!initSuccess)
{
Log.Error($"初始化CompProducedByMechCarrier失败");
return;
}
// 生成位置:使用自定义跟随位置(如果有效),否则使用父单位位置 // 生成位置
IntVec3 spawnPosition; IntVec3 spawnPosition;
Map spawnMap; Map spawnMap;
@@ -344,8 +587,20 @@ namespace ArachnaeSwarm
spawnMap = parent.Map; spawnMap = parent.Map;
} }
// 验证生成位置
if (!spawnPosition.IsValid || !spawnPosition.InBounds(spawnMap))
{
spawnPosition = parent.Position;
spawnMap = parent.Map;
}
GenSpawn.Spawn(pawn, spawnPosition, spawnMap); GenSpawn.Spawn(pawn, spawnPosition, spawnMap);
SpawnedPawns.Add(pawn);
// 添加到已生成列表
lock (validationLock)
{
SpawnedPawns?.Add(pawn);
}
if (parent is Pawn p && p.GetLord() != null) if (parent is Pawn p && p.GetLord() != null)
p.GetLord().AddPawn(pawn); p.GetLord().AddPawn(pawn);
@@ -373,38 +628,61 @@ namespace ArachnaeSwarm
EffecterTrigger(Props.spawnedMechEffecter, Props.attachSpawnedMechEffecter, pawn); EffecterTrigger(Props.spawnedMechEffecter, Props.attachSpawnedMechEffecter, pawn);
if (Props.spawnEffecter != null) if (Props.spawnEffecter != null)
EffecterTrigger(Props.spawnEffecter, Props.attachSpawnedEffecter, parent); 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) private void EffecterTrigger(EffecterDef effecterDef, bool attach, Thing target)
{
try
{ {
Effecter effecter = new Effecter(effecterDef); Effecter effecter = new Effecter(effecterDef);
effecter.Trigger(attach ? ((TargetInfo)target) : new TargetInfo(target.Position, target.Map), TargetInfo.Invalid); effecter.Trigger(attach ? ((TargetInfo)target) : new TargetInfo(target.Position, target.Map), TargetInfo.Invalid);
effecter.Cleanup(); effecter.Cleanup();
} }
catch (System.Exception ex)
{
Log.Error($"触发效果失败: {ex.Message}");
}
}
public override void CompTick() public override void CompTick()
{
try
{ {
base.CompTick(); base.CompTick();
// 清理无效的子单位引用
if (Find.TickManager.TicksGame - lastChildCleanupTick > CHILD_CLEANUP_INTERVAL)
{
CleanupInvalidChildren();
lastChildCleanupTick = Find.TickManager.TicksGame;
}
// 检查临时位置是否过期 // 检查临时位置是否过期
CheckTemporaryPositionExpiry(); CheckTemporaryPositionExpiry();
if (parent.IsHashIntervalTick(60)) // 每秒检查一次 if (parent.IsHashIntervalTick(60))
{ {
// 检查是否有抑制生产的Hediff // 检查是否有抑制生产的Hediff
if (AutoProps.disableHediff != null && (parent as Pawn)?.health.hediffSet.HasHediff(AutoProps.disableHediff) == true) if (AutoProps.disableHediff != null && (parent as Pawn)?.health.hediffSet.HasHediff(AutoProps.disableHediff) == true)
{ {
return; // 有Hediff停止生产 return;
} }
// 获取当前生产队列
var productionQueue = GetProductionQueue(); var productionQueue = GetProductionQueue();
if (productionQueue == null || productionQueue.Count == 0) if (productionQueue == null || productionQueue.Count == 0)
{ {
return; // 生产队列为空,不进行生产 return;
} }
// 1. 先检查是否满员 // 检查是否满员
bool isFull = true; bool isFull = true;
foreach (var entry in productionQueue) foreach (var entry in productionQueue)
{ {
@@ -417,13 +695,11 @@ namespace ArachnaeSwarm
if (isFull) if (isFull)
{ {
return; // 如果已满员,则不进行任何操作,包括冷却计时 return;
} }
// 2. 如果未满员,才检查冷却时间
if (CooldownTicksRemaining > 0) return; if (CooldownTicksRemaining > 0) return;
// 3. 寻找空位并生产
foreach (var entry in productionQueue) foreach (var entry in productionQueue)
{ {
if (LiveSpawnedPawnsCount(entry.pawnKind) < entry.count) if (LiveSpawnedPawnsCount(entry.pawnKind) < entry.count)
@@ -431,12 +707,17 @@ namespace ArachnaeSwarm
if (CanSpawnNow(entry.pawnKind).Accepted) if (CanSpawnNow(entry.pawnKind).Accepted)
{ {
TrySpawnPawn(entry.pawnKind); TrySpawnPawn(entry.pawnKind);
break; // 每次只生产一个 break;
} }
} }
} }
} }
} }
catch (System.Exception ex)
{
Log.Error($"CompAutoMechCarrier Tick错误: {ex.Message}");
}
}
/// <summary> /// <summary>
/// 添加动态生产队列条目 /// 添加动态生产队列条目
@@ -523,21 +804,21 @@ namespace ArachnaeSwarm
public override IEnumerable<Gizmo> CompGetGizmosExtra() public override IEnumerable<Gizmo> CompGetGizmosExtra()
{ {
// 移除所有Gizmo逻辑
return Enumerable.Empty<Gizmo>(); return Enumerable.Empty<Gizmo>();
} }
public override string CompInspectStringExtra() public override string CompInspectStringExtra()
{ {
SpawnedPawns.RemoveAll(p => p == null || p.Destroyed); try
string text = "Pawns: " + SpawnedPawns.Count + " / " + TotalPawnCapacity; {
var spawned = GetSafeSpawnedPawns();
string text = "Pawns: " + spawned.Count + " / " + TotalPawnCapacity;
var productionQueue = GetProductionQueue(); var productionQueue = GetProductionQueue();
foreach (var entry in productionQueue) foreach (var entry in productionQueue)
{ {
text += $"\n- {entry.pawnKind.LabelCap}: {LiveSpawnedPawnsCount(entry.pawnKind)} / {entry.count}"; text += $"\n- {entry.pawnKind.LabelCap}: {LiveSpawnedPawnsCount(entry.pawnKind)} / {entry.count}";
// 显示自定义信息
if (entry.customInfo != null) if (entry.customInfo != null)
{ {
text += $" ({entry.customInfo})"; text += $" ({entry.customInfo})";
@@ -554,7 +835,6 @@ namespace ArachnaeSwarm
text += "\n" + base.CompInspectStringExtra(); text += "\n" + base.CompInspectStringExtra();
} }
// 显示自定义跟随位置信息
if (HasValidCustomFollowPosition) if (HasValidCustomFollowPosition)
{ {
text += $"\nCustom Follow Position: {customFollowPosition.Value}"; text += $"\nCustom Follow Position: {customFollowPosition.Value}";
@@ -568,44 +848,59 @@ namespace ArachnaeSwarm
} }
} }
// 显示验证状态
if (AutoProps.debugLogging)
{
text += $"\n验证缓存: {validatedChildren.Count}";
text += $"\n死亡队列: {childDeathQueue.Count}";
}
return text; return text;
} }
catch (System.Exception ex)
{
return $"状态获取错误: {ex.Message}";
}
}
/// <summary> /// <summary>
/// 获取调试信息 /// 获取调试信息
/// </summary> /// </summary>
public string GetDebugInfo() public string GetDebugInfo()
{ {
var pawn = parent as Pawn;
System.Text.StringBuilder sb = new System.Text.StringBuilder(); System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendLine("=== CompAutoMechCarrier 调试信息 ==="); sb.AppendLine("=== CompAutoMechCarrier 调试信息 ===");
// CompAutoMechCarrier基本信息 // 生产者信息
sb.AppendLine($"父单位: {parent?.LabelCap ?? "NULL"}"); sb.AppendLine($"生产者有效: {IsProducerValid}");
sb.AppendLine($"自定义跟随位置: {CustomFollowPosition}"); sb.AppendLine($"生产者存活: {IsProducerAlive}");
sb.AppendLine($"有效跟随位置: {GetEffectiveFollowPosition()}");
sb.AppendLine($"临时位置过期tick: {temporaryPositionExpiryTick}");
// 已生成的Pawn // 已生成的Pawn
sb.AppendLine($"已生成Pawn数: {SpawnedPawns?.Count ?? 0}"); var spawned = GetSafeSpawnedPawns();
if (SpawnedPawns != null && SpawnedPawns.Count > 0) 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<CompProducedByMechCarrier>();
bool valid = childComp != null && childComp.IsProducerValid;
sb.AppendLine($" 双向验证: {valid}");
} }
} }
// 冷却时间 // 验证缓存
sb.AppendLine($"冷却时间剩余: {CooldownTicksRemaining}"); sb.AppendLine($"验证缓存数: {validatedChildren.Count}");
sb.AppendLine($"死亡队列: {childDeathQueue.Count}");
return sb.ToString(); return sb.ToString();
} }
public override void PostExposeData() public override void PostExposeData()
{
try
{ {
base.PostExposeData(); base.PostExposeData();
@@ -613,12 +908,20 @@ namespace ArachnaeSwarm
Scribe_Values.Look(ref customFollowPosition, "customFollowPosition"); Scribe_Values.Look(ref customFollowPosition, "customFollowPosition");
Scribe_Values.Look(ref temporaryPositionExpiryTick, "temporaryPositionExpiryTick", -1); Scribe_Values.Look(ref temporaryPositionExpiryTick, "temporaryPositionExpiryTick", -1);
// 注意Map不能直接序列化需要在PostLoadInit中重新获取 // 保存验证缓存
Scribe_Collections.Look(ref validatedChildren, "validatedChildren", LookMode.Reference);
Scribe_Collections.Look(ref childDeathQueue, "childDeathQueue", LookMode.Reference);
if (Scribe.mode == LoadSaveMode.LoadingVars) if (Scribe.mode == LoadSaveMode.LoadingVars)
{ {
customFollowPositionMap = parent?.Map; customFollowPositionMap = parent?.Map;
} }
} }
catch (System.Exception ex)
{
Log.Error($"CompAutoMechCarrier 序列化错误: {ex.Message}");
}
}
public override void PostSpawnSetup(bool respawningAfterLoad) public override void PostSpawnSetup(bool respawningAfterLoad)
{ {
@@ -629,7 +932,6 @@ namespace ArachnaeSwarm
{ {
customFollowPositionMap = parent?.Map; customFollowPositionMap = parent?.Map;
// 如果自定义位置无效,清除它
if (customFollowPosition.HasValue && if (customFollowPosition.HasValue &&
(customFollowPositionMap == null || (customFollowPositionMap == null ||
!customFollowPosition.Value.InBounds(customFollowPositionMap))) !customFollowPosition.Value.InBounds(customFollowPositionMap)))
@@ -640,6 +942,45 @@ namespace ArachnaeSwarm
// 初始化生产队列缓存 // 初始化生产队列缓存
RefreshProductionQueue(); RefreshProductionQueue();
// 初始化验证系统
lock (validationLock)
{
if (validatedChildren == null)
validatedChildren = new HashSet<Thing>();
if (childDeathQueue == null)
childDeathQueue = new Queue<Thing>();
}
}
public override void PostDestroy(DestroyMode mode, Map previousMap)
{
try
{
// 清理所有子单位引用
lock (validationLock)
{
var spawned = GetSafeSpawnedPawns();
foreach (var pawn in spawned)
{
var comp = pawn.TryGetComp<CompProducedByMechCarrier>();
if (comp != null)
{
comp.TryUpdateProducerStatus();
}
}
validatedChildren.Clear();
childDeathQueue.Clear();
}
}
catch (System.Exception ex)
{
Log.Error($"销毁时清理失败: {ex.Message}");
}
base.PostDestroy(mode, previousMap);
} }
} }
} }

View File

@@ -6,15 +6,39 @@ using Verse.AI;
namespace ArachnaeSwarm namespace ArachnaeSwarm
{ {
public class CompProducedByMechCarrier : ThingComp public class CompProducedByMechCarrier : ThingComp, IBidirectionalValidator
{ {
private Thing producer; private Thing producer;
private CompAutoMechCarrier producerComp; private CompAutoMechCarrier producerComp;
private int lastProducerCheckTick = -1; private int lastProducerCheckTick = -1;
private const int PRODUCER_CHECK_INTERVAL = 60; 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 public bool HasValidProducer
@@ -32,12 +56,121 @@ namespace ArachnaeSwarm
} }
} }
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<CompAutoMechCarrier>();
}
return producerComp;
}
}
// 检查是否应该跟随生产者 // 检查是否应该跟随生产者
public bool ShouldFollowProducer public bool ShouldFollowProducer
{ {
get get
{ {
if (!HasValidProducer) lock (validationLock)
{
if (!IsProducerValid)
return false; return false;
// 确保pawn是有效的 // 确保pawn是有效的
@@ -49,16 +182,39 @@ namespace ArachnaeSwarm
if (producer.Map != pawn.Map || !pawn.CanReach(producer, PathEndMode.OnCell, Danger.Deadly)) if (producer.Map != pawn.Map || !pawn.CanReach(producer, PathEndMode.OnCell, Danger.Deadly))
return false; return false;
// 可以根据需要添加更多条件 return true;
}
}
}
// 初始化方法 - 安全版本
public bool InitializeSafe(Thing producer, CompAutoMechCarrier producerComp)
{
lock (validationLock)
{
// 验证生产者
if (producer == null || producer.Destroyed)
return false;
// 验证生产者组件
if (producerComp == null || producerComp.parent != producer)
return false;
this.producer = producer;
this.producerComp = producerComp;
// 记录初始化时间
lastProducerCheckTick = Find.TickManager.TicksGame;
Log.Message($"双向验证: {parent?.LabelCap} -> {producer.LabelCap} 初始化成功");
return true; return true;
} }
} }
// 初始化方法 // 原始初始化方法(保持向后兼容)
public void Initialize(Thing producer, CompAutoMechCarrier producerComp) public void Initialize(Thing producer, CompAutoMechCarrier producerComp)
{ {
this.producer = producer; InitializeSafe(producer, producerComp);
this.producerComp = producerComp;
} }
// 尝试更新生产者状态 // 尝试更新生产者状态
@@ -69,9 +225,61 @@ namespace ArachnaeSwarm
lastProducerCheckTick = Find.TickManager.TicksGame; lastProducerCheckTick = Find.TickManager.TicksGame;
lock (validationLock)
{
// 检查生产者是否仍然有效 // 检查生产者是否仍然有效
if (producer != null && (producer.Destroyed || (producer is Pawn p && (p.Dead || p.Downed)))) 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; producer = null;
producerComp = null; producerComp = null;
} }
@@ -80,16 +288,21 @@ namespace ArachnaeSwarm
// 获取生产者的位置 // 获取生产者的位置
public IntVec3 GetProducerPosition() public IntVec3 GetProducerPosition()
{ {
if (!HasValidProducer) lock (validationLock)
{
if (!IsProducerValid)
return IntVec3.Invalid; return IntVec3.Invalid;
return producer.Position; return producer.Position;
} }
}
// 获取生产者的交互单元格 // 获取生产者的交互单元格
public IntVec3 GetProducerInteractionCell() public IntVec3 GetProducerInteractionCell()
{ {
if (!HasValidProducer) lock (validationLock)
{
if (!IsProducerValid)
return IntVec3.Invalid; return IntVec3.Invalid;
if (producer is Building building) if (producer is Building building)
@@ -97,21 +310,90 @@ namespace ArachnaeSwarm
return producer.Position; return producer.Position;
} }
}
// 检查是否在生产者附近 // 检查是否在生产者附近
public bool IsNearProducer(Pawn pawn, float radius) public bool IsNearProducer(Pawn pawn, float radius)
{ {
if (!HasValidProducer) lock (validationLock)
{
if (!IsProducerValid)
return false; return false;
return pawn.Position.DistanceTo(producer.Position) <= radius; 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() public override void PostExposeData()
{ {
base.PostExposeData(); base.PostExposeData();
try
{
Scribe_References.Look(ref producer, "producer"); Scribe_References.Look(ref producer, "producer");
Scribe_Values.Look(ref lastProducerCheckTick, "lastProducerCheckTick", -1); 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();
}
} }
} }
} }

View File

@@ -0,0 +1,26 @@
using RimWorld;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
/// <summary>
/// 双向安全校验接口
/// 确保生产者和子单位在操作前互相验证存在
/// </summary>
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);
}
}