11
This commit is contained in:
Binary file not shown.
@@ -1,25 +1,81 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
|
||||
"WorkspaceRootPath": "E:\\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\\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|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\jobs\\jobdriver_stripchitin\\jobdriver_stripchitin.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:jobs\\jobdriver_stripchitin\\jobdriver_stripchitin.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\\compproperties_incubatordata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\compproperties_incubatordata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\jobs\\jobdriver_stripchitin\\compproperties_chitinstripping.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:jobs\\jobdriver_stripchitin\\compproperties_chitinstripping.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_building_refuelingvat\\building_refuelingvat.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\jobs\\jobdriver_stripchitin\\comp_chitinstripping.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:jobs\\jobdriver_stripchitin\\comp_chitinstripping.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\verbs\\verb_shootselfunderfoot.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:verbs\\verb_shootselfunderfoot.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\hediffs\\ara_hediffcomp_topturret\\hediffcomp_topturret.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:hediffs\\ara_hediffcomp_topturret\\hediffcomp_topturret.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\roomrole\\roomroleworker_incubator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:roomrole\\roomroleworker_incubator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\building_comps\\ara_compinteractiveproducer\\compresearchproducer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_compinteractiveproducer\\compresearchproducer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\ara_hediffdefof.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:ara_hediffdefof.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\building_comps\\ara_corpseconverter\\compcorpseconverter.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_corpseconverter\\compcorpseconverter.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\building_comps\\ara_corpseconverter\\compproperties_corpseconverter.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_corpseconverter\\compproperties_corpseconverter.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\building_comps\\ara_terrainchanger\\compterrainchanger.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_terrainchanger\\compterrainchanger.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\building_comps\\ara_terrainchanger\\compproperties_terrainchanger.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_terrainchanger\\compproperties_terrainchanger.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\building_comps\\wula_mutifuelspawner\\comprefuelablenutrition_withkey.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\wula_mutifuelspawner\\comprefuelablenutrition_withkey.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\building_comps\\ara_building_refuelingvat\\building_refuelingvat.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_building_refuelingvat\\building_refuelingvat.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\\itab_ootheca_incubation.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\itab_ootheca_incubation.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\buildings\\building_ootheca\\compproperties_incubatordata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\compproperties_incubatordata.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}",
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\buildings\\building_ootheca\\oothecaincubatorextension.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\oothecaincubatorextension.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\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|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\buildings\\building_equipmentootheca\\compproperties_equipmentincubatordata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_equipmentootheca\\compproperties_equipmentincubatordata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\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}"
|
||||
}
|
||||
],
|
||||
@@ -30,76 +86,245 @@
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"SelectedChildIndex": 1,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"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": "AgIAAAAAAAAAAAAAAAAAABQAAAAMAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T08:35:48.647Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "CompProperties_IncubatorData.cs",
|
||||
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
|
||||
"RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
|
||||
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
|
||||
"RelativeToolTip": "Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
|
||||
"ViewState": "AgIAAP4AAAAAAAAAAAAWwAIBAABCAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T08:17:40.867Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 3,
|
||||
"Title": "ITab_Ootheca_Incubation.cs",
|
||||
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\ITab_Ootheca_Incubation.cs",
|
||||
"RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\ITab_Ootheca_Incubation.cs",
|
||||
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\ITab_Ootheca_Incubation.cs",
|
||||
"RelativeToolTip": "Buildings\\Building_Ootheca\\ITab_Ootheca_Incubation.cs",
|
||||
"ViewState": "AgIAAJUAAAAAAAAAAAAewMkAAAANAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T08:14:08.752Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 4,
|
||||
"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": "AgIAALgCAAAAAAAAAAAAAMQCAAAjAAAAAAAAAA==",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "JobDriver_StripChitin.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_StripChitin\\JobDriver_StripChitin.cs",
|
||||
"RelativeDocumentMoniker": "Jobs\\JobDriver_StripChitin\\JobDriver_StripChitin.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_StripChitin\\JobDriver_StripChitin.cs*",
|
||||
"RelativeToolTip": "Jobs\\JobDriver_StripChitin\\JobDriver_StripChitin.cs*",
|
||||
"ViewState": "AgIAAFcAAAAAAAAAAAAAAHMAAAAoAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T08:13:46.678Z",
|
||||
"WhenOpened": "2025-12-16T15:50:38.09Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "CompProperties_ChitinStripping.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_StripChitin\\CompProperties_ChitinStripping.cs",
|
||||
"RelativeDocumentMoniker": "Jobs\\JobDriver_StripChitin\\CompProperties_ChitinStripping.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_StripChitin\\CompProperties_ChitinStripping.cs",
|
||||
"RelativeToolTip": "Jobs\\JobDriver_StripChitin\\CompProperties_ChitinStripping.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAABcAAAAtAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T15:50:04.277Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": "Building_RefuelingVat.cs",
|
||||
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
|
||||
"RelativeDocumentMoniker": "Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
|
||||
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
|
||||
"RelativeToolTip": "Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
|
||||
"ViewState": "AgIAAP4AAAAAAAAAAAAewA8BAAAAAAAAAAAAAA==",
|
||||
"Title": "Comp_ChitinStripping.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_StripChitin\\Comp_ChitinStripping.cs",
|
||||
"RelativeDocumentMoniker": "Jobs\\JobDriver_StripChitin\\Comp_ChitinStripping.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_StripChitin\\Comp_ChitinStripping.cs",
|
||||
"RelativeToolTip": "Jobs\\JobDriver_StripChitin\\Comp_ChitinStripping.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAoAAABWAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T04:00:20.165Z",
|
||||
"WhenOpened": "2025-12-16T15:49:51.675Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 3,
|
||||
"Title": "Verb_ShootSelfUnderfoot.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Verbs\\Verb_ShootSelfUnderfoot.cs",
|
||||
"RelativeDocumentMoniker": "Verbs\\Verb_ShootSelfUnderfoot.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Verbs\\Verb_ShootSelfUnderfoot.cs",
|
||||
"RelativeToolTip": "Verbs\\Verb_ShootSelfUnderfoot.cs",
|
||||
"ViewState": "AgIAAJAAAAAAAAAAAAAcwJwAAAAoAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T15:07:22.127Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 4,
|
||||
"Title": "HediffComp_TopTurret.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_HediffComp_TopTurret\\HediffComp_TopTurret.cs",
|
||||
"RelativeDocumentMoniker": "Hediffs\\ARA_HediffComp_TopTurret\\HediffComp_TopTurret.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_HediffComp_TopTurret\\HediffComp_TopTurret.cs",
|
||||
"RelativeToolTip": "Hediffs\\ARA_HediffComp_TopTurret\\HediffComp_TopTurret.cs",
|
||||
"ViewState": "AgIAACcBAAAAAAAAAAAkwD0BAAAjAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T14:52:46.325Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 5,
|
||||
"Title": "RoomRoleWorker_Incubator.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\RoomRole\\RoomRoleWorker_Incubator.cs",
|
||||
"RelativeDocumentMoniker": "RoomRole\\RoomRoleWorker_Incubator.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\RoomRole\\RoomRoleWorker_Incubator.cs",
|
||||
"RelativeToolTip": "RoomRole\\RoomRoleWorker_Incubator.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAABsAAAARAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T14:32:31.389Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 6,
|
||||
"Title": "CompResearchProducer.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompResearchProducer.cs",
|
||||
"RelativeDocumentMoniker": "Building_Comps\\ARA_CompInteractiveProducer\\CompResearchProducer.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompResearchProducer.cs",
|
||||
"RelativeToolTip": "Building_Comps\\ARA_CompInteractiveProducer\\CompResearchProducer.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAADwvy0AAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T14:29:05.969Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 8,
|
||||
"Title": "CompCorpseConverter.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CorpseConverter\\CompCorpseConverter.cs",
|
||||
"RelativeDocumentMoniker": "Building_Comps\\ARA_CorpseConverter\\CompCorpseConverter.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CorpseConverter\\CompCorpseConverter.cs",
|
||||
"RelativeToolTip": "Building_Comps\\ARA_CorpseConverter\\CompCorpseConverter.cs",
|
||||
"ViewState": "AgIAABwDAAAAAAAAAAAIwCoDAAARAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T12:23:40.696Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 7,
|
||||
"Title": "ARA_HediffDefOf.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\ARA_HediffDefOf.cs",
|
||||
"RelativeDocumentMoniker": "ARA_HediffDefOf.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\ARA_HediffDefOf.cs",
|
||||
"RelativeToolTip": "ARA_HediffDefOf.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAADwvy0AAAAJAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-15T17:32:18.493Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 9,
|
||||
"Title": "CompProperties_CorpseConverter.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CorpseConverter\\CompProperties_CorpseConverter.cs",
|
||||
"RelativeDocumentMoniker": "Building_Comps\\ARA_CorpseConverter\\CompProperties_CorpseConverter.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CorpseConverter\\CompProperties_CorpseConverter.cs",
|
||||
"RelativeToolTip": "Building_Comps\\ARA_CorpseConverter\\CompProperties_CorpseConverter.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T12:23:39.636Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 12,
|
||||
"Title": "CompRefuelableNutrition_WithKey.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\WULA_MutiFuelSpawner\\CompRefuelableNutrition_WithKey.cs",
|
||||
"RelativeDocumentMoniker": "Building_Comps\\WULA_MutiFuelSpawner\\CompRefuelableNutrition_WithKey.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\WULA_MutiFuelSpawner\\CompRefuelableNutrition_WithKey.cs",
|
||||
"RelativeToolTip": "Building_Comps\\WULA_MutiFuelSpawner\\CompRefuelableNutrition_WithKey.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAACUAAABAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T10:38:33.135Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 10,
|
||||
"Title": "CompTerrainChanger.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_TerrainChanger\\CompTerrainChanger.cs",
|
||||
"RelativeDocumentMoniker": "Building_Comps\\ARA_TerrainChanger\\CompTerrainChanger.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_TerrainChanger\\CompTerrainChanger.cs",
|
||||
"RelativeToolTip": "Building_Comps\\ARA_TerrainChanger\\CompTerrainChanger.cs",
|
||||
"ViewState": "AgIAAK0CAAAAAAAAAAAcwPYCAAAMAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T10:30:16.921Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 11,
|
||||
"Title": "CompProperties_TerrainChanger.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_TerrainChanger\\CompProperties_TerrainChanger.cs",
|
||||
"RelativeDocumentMoniker": "Building_Comps\\ARA_TerrainChanger\\CompProperties_TerrainChanger.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_TerrainChanger\\CompProperties_TerrainChanger.cs",
|
||||
"RelativeToolTip": "Building_Comps\\ARA_TerrainChanger\\CompProperties_TerrainChanger.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T10:30:15.601Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 13,
|
||||
"Title": "Building_RefuelingVat.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
|
||||
"RelativeDocumentMoniker": "Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
|
||||
"RelativeToolTip": "Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T10:28:54.756Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 14,
|
||||
"Title": "CompProperties_IncubatorData.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
|
||||
"RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
|
||||
"RelativeToolTip": "Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
|
||||
"ViewState": "AgIAANcAAAAAAAAAAIA1wPoAAAAxAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T04:37:03.042Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 15,
|
||||
"Title": "OothecaIncubatorExtension.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\OothecaIncubatorExtension.cs",
|
||||
"RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\OothecaIncubatorExtension.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\OothecaIncubatorExtension.cs",
|
||||
"RelativeToolTip": "Buildings\\Building_Ootheca\\OothecaIncubatorExtension.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAADwvxUAAABBAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-16T04:36:59.394Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 18,
|
||||
"Title": "Building_EquipmentOotheca.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_EquipmentOotheca\\Building_EquipmentOotheca.cs",
|
||||
"RelativeDocumentMoniker": "Buildings\\Building_EquipmentOotheca\\Building_EquipmentOotheca.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_EquipmentOotheca\\Building_EquipmentOotheca.cs",
|
||||
"RelativeToolTip": "Buildings\\Building_EquipmentOotheca\\Building_EquipmentOotheca.cs",
|
||||
"ViewState": "AgIAACcAAAAAAAAAAAAAADoDAABSAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-15T18:22:14.171Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 16,
|
||||
"Title": "Building_Ootheca.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Building_Ootheca.cs",
|
||||
"RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\Building_Ootheca.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Building_Ootheca.cs",
|
||||
"RelativeToolTip": "Buildings\\Building_Ootheca\\Building_Ootheca.cs",
|
||||
"ViewState": "AgIAALcCAAAAAAAAAAAewNgCAAAVAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-15T18:22:12.217Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 17,
|
||||
"Title": "CompProperties_EquipmentIncubatorData.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_EquipmentOotheca\\CompProperties_EquipmentIncubatorData.cs",
|
||||
"RelativeDocumentMoniker": "Buildings\\Building_EquipmentOotheca\\CompProperties_EquipmentIncubatorData.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_EquipmentOotheca\\CompProperties_EquipmentIncubatorData.cs",
|
||||
"RelativeToolTip": "Buildings\\Building_EquipmentOotheca\\CompProperties_EquipmentIncubatorData.cs",
|
||||
"ViewState": "AgIAAA4AAAAAAAAAAADwvyYAAAAaAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-12-15T17:55:40.041Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -37,4 +37,14 @@ namespace ArachnaeSwarm
|
||||
DefOfHelper.EnsureInitializedInCtor(typeof(ARA_EffecterDefOf));
|
||||
}
|
||||
}
|
||||
[DefOf]
|
||||
public static class ARA_SoundDefOf
|
||||
{
|
||||
public static SoundDef AcidSpray_Resolve;
|
||||
|
||||
static ARA_SoundDefOf()
|
||||
{
|
||||
DefOfHelper.EnsureInitializedInCtor(typeof(ARA_SoundDefOf));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,13 +124,15 @@
|
||||
<Compile Include="Buildings\Building_Ootheca\ITab_Ootheca_Incubation.cs" />
|
||||
<Compile Include="Buildings\Building_Ootheca\JobDriver_OperateIncubator.cs" />
|
||||
<Compile Include="Buildings\Building_Ootheca\OothecaIncubatorExtension.cs" />
|
||||
<Compile Include="Buildings\Building_Ootheca\RoomRoleWorker_Incubator.cs" />
|
||||
<Compile Include="RoomRole\RoomRoleWorker_Incubator.cs" />
|
||||
<Compile Include="Buildings\Building_TurretGunHasSpeed.cs" />
|
||||
<Compile Include="Building_Comps\ARA_Building_RefuelingVat\Building_RefuelingVat.cs" />
|
||||
<Compile Include="Building_Comps\ARA_Building_RefuelingVat\CompProperties_RefuelingVat.cs" />
|
||||
<Compile Include="Building_Comps\ARA_Building_RefuelingVat\CompRefuelingVat.cs" />
|
||||
<Compile Include="Building_Comps\ARA_CompInteractiveProducer\CompResearchProducer.cs" />
|
||||
<Compile Include="Building_Comps\ARA_CompInteractiveProducer\JobDriver_StartResearchProduction.cs" />
|
||||
<Compile Include="Building_Comps\ARA_CorpseConverter\CompCorpseConverter.cs" />
|
||||
<Compile Include="Building_Comps\ARA_CorpseConverter\CompProperties_CorpseConverter.cs" />
|
||||
<Compile Include="Building_Comps\ARA_ForwardClearance\CompForwardClearance.cs" />
|
||||
<Compile Include="Building_Comps\ARA_ForwardClearance\CompProperties_ForwardClearance.cs" />
|
||||
<Compile Include="Building_Comps\ARA_ForwardClearance\PlaceWorker_ForwardClearance.cs" />
|
||||
@@ -138,6 +140,8 @@
|
||||
<Compile Include="Building_Comps\ARA_ProductStorage\CompProperties_ProductStorage.cs" />
|
||||
<Compile Include="Building_Comps\ARA_SwarmMaintenance\CompProperties_SwarmMaintenance.cs" />
|
||||
<Compile Include="Building_Comps\ARA_SwarmMaintenance\Comp_SwarmMaintenance.cs" />
|
||||
<Compile Include="Building_Comps\ARA_TerrainChanger\CompProperties_TerrainChanger.cs" />
|
||||
<Compile Include="Building_Comps\ARA_TerrainChanger\CompTerrainChanger.cs" />
|
||||
<Compile Include="Building_Comps\CompBreakdownDisabler.cs" />
|
||||
<Compile Include="EventSystem\CompOpenCustomUI.cs" />
|
||||
<Compile Include="EventSystem\Condition.cs" />
|
||||
@@ -195,6 +199,7 @@
|
||||
<Compile Include="Storyteller\RaidWavePoolDef.cs" />
|
||||
<Compile Include="Flyover\ThingclassFlyOver.cs" />
|
||||
<Compile Include="Flyover\ARA_FlyOverDropPod\CompProperties_FlyOverDropPod.cs" />
|
||||
<Compile Include="Verbs\Verb_ShootSelfUnderfoot.cs" />
|
||||
<Compile Include="Verbs\Verb_ShootWithOffset.cs" />
|
||||
<Compile Include="Abilities\ARA_ShowTemperatureRange\CompAbilityEffect_AbilityShowTemperatureRange.cs" />
|
||||
<Compile Include="Abilities\ARA_ShowTemperatureRange\CompProperties_AbilityShowTemperatureRange.cs" />
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace ArachnaeSwarm
|
||||
if (!Destroyed)
|
||||
{
|
||||
// 销毁建筑
|
||||
Destroy(DestroyMode.Vanish);
|
||||
Kill();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ namespace ArachnaeSwarm
|
||||
|
||||
DamageInfo acidDamage = new DamageInfo(
|
||||
acidDamageDef,
|
||||
1000f, // 每次1.5点伤害
|
||||
1.5f, // 每次1.5点伤害
|
||||
2f, // 护甲穿透
|
||||
-1f, // 随机角度
|
||||
null, // 将建筑设为伤害来源
|
||||
|
||||
@@ -0,0 +1,967 @@
|
||||
// File: Comps/CompCorpseConverter.cs
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.Sound;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using Verse.AI;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class CompCorpseConverter : ThingComp
|
||||
{
|
||||
// 属性引用
|
||||
private CompProperties_CorpseConverter Props => (CompProperties_CorpseConverter)props;
|
||||
|
||||
// 状态变量
|
||||
private int ticksUntilNextConversion;
|
||||
private bool isWorking = false;
|
||||
private int workTicksRemaining = 0;
|
||||
private Corpse targetCorpse = null;
|
||||
private Effecter effecter;
|
||||
private Effecter conversionEffecter;
|
||||
|
||||
// --- 新增:自动标记拆除状态 ---
|
||||
private bool autoMarkForDeconstructionEnabled = true;
|
||||
private int ticksUntilNextMarkDeconstruction;
|
||||
private bool isMarking = false;
|
||||
private int markingTicksRemaining = 0;
|
||||
private Thing markingTargetBuilding = null;
|
||||
private Effecter markingEffecter;
|
||||
|
||||
// 当前工作速度乘数
|
||||
private float currentWorkSpeedMultiplier = 1.0f;
|
||||
|
||||
// 缓存燃料组件
|
||||
private CompRefuelableNutrition_WithKey compRefuelable;
|
||||
private bool refuelableComponentCached = false;
|
||||
|
||||
// 储存区图标缓存
|
||||
private static readonly CachedTexture CreateCorpseStockpileIcon = new CachedTexture("UI/Icons/CorpseStockpileZone");
|
||||
|
||||
// 临时列表
|
||||
private List<IntVec3> tmpRadialCells = new List<IntVec3>();
|
||||
|
||||
// 获取径向单元格(转换半径内)
|
||||
private IEnumerable<IntVec3> RadialCells => GenRadial.RadialCellsAround(parent.Position, Props.conversionRadius, useCenter: true);
|
||||
|
||||
// 获取燃料组件
|
||||
private CompRefuelableNutrition_WithKey CompRefuelable
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!refuelableComponentCached)
|
||||
{
|
||||
compRefuelable = parent.TryGetComp<CompRefuelableNutrition_WithKey>();
|
||||
refuelableComponentCached = true;
|
||||
}
|
||||
return compRefuelable;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查燃料是否充足
|
||||
private bool HasSufficientFuel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Props.requiresFuel)
|
||||
return true;
|
||||
|
||||
if (CompRefuelable == null)
|
||||
return false;
|
||||
|
||||
return CompRefuelable.Fuel >= Props.minFuelToOperate;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有足够的燃料用于标记
|
||||
private bool HasSufficientFuelForMarking
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Props.requiresFuel || Props.fuelConsumptionPerMark <= 0)
|
||||
return true;
|
||||
|
||||
if (CompRefuelable == null)
|
||||
return false;
|
||||
|
||||
return CompRefuelable.Fuel >= Props.fuelConsumptionPerMark;
|
||||
}
|
||||
}
|
||||
|
||||
// 消耗燃料
|
||||
private bool ConsumeFuelIfNeeded()
|
||||
{
|
||||
if (!Props.requiresFuel || Props.fuelConsumptionPerConversion <= 0)
|
||||
return true;
|
||||
|
||||
if (CompRefuelable == null)
|
||||
return false;
|
||||
|
||||
if (CompRefuelable.Fuel >= Props.fuelConsumptionPerConversion)
|
||||
{
|
||||
CompRefuelable.ConsumeFuel(Props.fuelConsumptionPerConversion);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 消耗标记燃料
|
||||
private bool ConsumeMarkingFuelIfNeeded()
|
||||
{
|
||||
if (!Props.requiresFuel || Props.fuelConsumptionPerMark <= 0)
|
||||
return true;
|
||||
|
||||
if (CompRefuelable == null)
|
||||
return false;
|
||||
|
||||
if (CompRefuelable.Fuel >= Props.fuelConsumptionPerMark)
|
||||
{
|
||||
CompRefuelable.ConsumeFuel(Props.fuelConsumptionPerMark);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取电源状态
|
||||
private bool HasPower
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Props.requiresPower)
|
||||
return true;
|
||||
|
||||
var compPower = parent.TryGetComp<CompPowerTrader>();
|
||||
return compPower != null && compPower.PowerOn;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取房间
|
||||
private Room GetRoom()
|
||||
{
|
||||
var map = parent.Map;
|
||||
if (map == null)
|
||||
return null;
|
||||
|
||||
return parent.Position.GetRoom(map);
|
||||
}
|
||||
|
||||
// 检查是否满足操作条件
|
||||
private bool CanOperate()
|
||||
{
|
||||
// 检查是否有电
|
||||
if (Props.requiresPower && !HasPower)
|
||||
return false;
|
||||
|
||||
// 检查是否有足够的燃料
|
||||
if (Props.requiresFuel && !HasSufficientFuel)
|
||||
return false;
|
||||
|
||||
// 检查是否在房间内(如果需要)
|
||||
if (Props.requiresRoom)
|
||||
{
|
||||
var room = GetRoom();
|
||||
if (room == null || !room.ProperRoom)
|
||||
return false;
|
||||
|
||||
// 检查房间评分
|
||||
if (room.GetStat(RoomStatDefOf.Impressiveness) < Props.minRoomScore)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否可以执行标记操作
|
||||
private bool CanMarkForDeconstruction()
|
||||
{
|
||||
if (!Props.enableAutoMarkForDeconstruction || !autoMarkForDeconstructionEnabled)
|
||||
return false;
|
||||
|
||||
if (!CanOperate())
|
||||
return false;
|
||||
|
||||
// 检查标记燃料
|
||||
if (Props.requiresFuel && !HasSufficientFuelForMarking)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取有效的目标尸体
|
||||
private bool TryGetValidTargetCorpse(out Corpse result)
|
||||
{
|
||||
result = null;
|
||||
var map = parent.Map;
|
||||
if (map == null)
|
||||
return false;
|
||||
|
||||
// 获取搜索范围
|
||||
var center = parent.Position;
|
||||
int radius = Mathf.CeilToInt(Props.conversionRadius);
|
||||
|
||||
// 获取房间(如果需要)
|
||||
Room parentRoom = null;
|
||||
if (Props.requiresRoom)
|
||||
{
|
||||
parentRoom = GetRoom();
|
||||
if (parentRoom == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
// 收集所有候选尸体
|
||||
List<Corpse> candidateCorpses = new List<Corpse>();
|
||||
|
||||
for (int x = -radius; x <= radius; x++)
|
||||
{
|
||||
for (int z = -radius; z <= radius; z++)
|
||||
{
|
||||
IntVec3 cell = new IntVec3(center.x + x, 0, center.z + z);
|
||||
|
||||
if (!cell.InBounds(map))
|
||||
continue;
|
||||
|
||||
// 检查距离
|
||||
float distance = cell.DistanceTo(center);
|
||||
if (distance > Props.conversionRadius)
|
||||
continue;
|
||||
|
||||
// 检查房间(如果需要)
|
||||
if (Props.requiresRoom)
|
||||
{
|
||||
var cellRoom = cell.GetRoom(map);
|
||||
if (cellRoom == null || cellRoom != parentRoom)
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取单元格中的所有东西
|
||||
var things = cell.GetThingList(map);
|
||||
foreach (var thing in things)
|
||||
{
|
||||
if (thing is Corpse corpse)
|
||||
{
|
||||
// 检查尸体是否腐烂
|
||||
if (corpse.GetRotStage() == RotStage.Fresh || corpse.GetRotStage() == RotStage.Rotting)
|
||||
{
|
||||
// 检查是否在可接受尸体列表中(如果有定义)
|
||||
if (Props.acceptedCorpseDefs != null && Props.acceptedCorpseDefs.Count > 0)
|
||||
{
|
||||
if (!Props.acceptedCorpseDefs.Contains(corpse.def))
|
||||
continue;
|
||||
}
|
||||
|
||||
// 排除机械族尸体(如果启用)
|
||||
if (Props.excludeMechanoidCorpses)
|
||||
{
|
||||
var pawn = corpse.InnerPawn;
|
||||
if (pawn != null && pawn.RaceProps.IsMechanoid)
|
||||
{
|
||||
continue; // 跳过机械族尸体
|
||||
}
|
||||
}
|
||||
|
||||
candidateCorpses.Add(corpse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (candidateCorpses.Count == 0)
|
||||
return false;
|
||||
|
||||
// 选择最近的尸体
|
||||
float closestDistance = float.MaxValue;
|
||||
Corpse closestCorpse = null;
|
||||
|
||||
foreach (var corpse in candidateCorpses)
|
||||
{
|
||||
float distance = corpse.Position.DistanceTo(center);
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestCorpse = corpse;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestCorpse != null)
|
||||
{
|
||||
result = closestCorpse;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取有效的标记拆除建筑
|
||||
private bool TryGetValidMarkingBuilding(out Thing result)
|
||||
{
|
||||
result = null;
|
||||
var map = parent.Map;
|
||||
if (map == null)
|
||||
return false;
|
||||
|
||||
// 获取搜索范围
|
||||
var center = parent.Position;
|
||||
int radius = Mathf.CeilToInt(Props.markDeconstructionRadius);
|
||||
|
||||
// 获取房间(如果需要)
|
||||
Room parentRoom = null;
|
||||
if (Props.requiresRoom)
|
||||
{
|
||||
parentRoom = GetRoom();
|
||||
if (parentRoom == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
// 收集所有候选建筑
|
||||
List<Thing> candidateBuildings = new List<Thing>();
|
||||
|
||||
for (int x = -radius; x <= radius; x++)
|
||||
{
|
||||
for (int z = -radius; z <= radius; z++)
|
||||
{
|
||||
IntVec3 cell = new IntVec3(center.x + x, 0, center.z + z);
|
||||
|
||||
if (!cell.InBounds(map))
|
||||
continue;
|
||||
|
||||
// 检查距离
|
||||
float distance = cell.DistanceTo(center);
|
||||
if (distance > Props.markDeconstructionRadius)
|
||||
continue;
|
||||
|
||||
// 检查房间(如果需要)
|
||||
if (Props.requiresRoom)
|
||||
{
|
||||
var cellRoom = cell.GetRoom(map);
|
||||
if (cellRoom == null || cellRoom != parentRoom)
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取单元格中的所有东西
|
||||
var things = cell.GetThingList(map);
|
||||
foreach (var thing in things)
|
||||
{
|
||||
// 检查是否是目标建筑
|
||||
if (thing.def == Props.targetThingDef)
|
||||
{
|
||||
// 检查是否已经标记了拆除
|
||||
if (map.designationManager.DesignationOn(thing, DesignationDefOf.Deconstruct) != null)
|
||||
continue;
|
||||
|
||||
candidateBuildings.Add(thing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (candidateBuildings.Count == 0)
|
||||
return false;
|
||||
|
||||
// 选择最近的建筑
|
||||
float closestDistance = float.MaxValue;
|
||||
Thing closestBuilding = null;
|
||||
|
||||
foreach (var building in candidateBuildings)
|
||||
{
|
||||
float distance = building.Position.DistanceTo(center);
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestBuilding = building;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestBuilding != null)
|
||||
{
|
||||
result = closestBuilding;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 开始工作
|
||||
private void StartWorking(Corpse targetCorpse)
|
||||
{
|
||||
this.targetCorpse = targetCorpse;
|
||||
this.isWorking = true;
|
||||
|
||||
// 计算工作时间(基于距离)
|
||||
float distance = targetCorpse.Position.DistanceTo(parent.Position);
|
||||
float workTimeFactor = 1f + (distance / Props.conversionRadius) * 0.5f; // 距离越远,时间越长
|
||||
|
||||
int baseWorkTime = Mathf.RoundToInt(Props.conversionInterval * 0.1f); // 工作时间为间隔的10%
|
||||
workTicksRemaining = Mathf.RoundToInt(baseWorkTime * workTimeFactor / currentWorkSpeedMultiplier);
|
||||
|
||||
// 播放声音
|
||||
if (Props.workingSound != null)
|
||||
{
|
||||
Props.workingSound.PlayOneShot(new TargetInfo(parent.Position, parent.Map));
|
||||
}
|
||||
|
||||
// 启动工作效果器
|
||||
if (Props.showVisualEffects && Props.workingEffecter != null)
|
||||
{
|
||||
effecter = Props.workingEffecter.Spawn();
|
||||
effecter.Trigger(parent, targetCorpse);
|
||||
}
|
||||
|
||||
// 启动转换效果器(在尸体上)
|
||||
if (Props.showVisualEffects && Props.conversionEffecter != null)
|
||||
{
|
||||
conversionEffecter = Props.conversionEffecter.Spawn();
|
||||
conversionEffecter.Trigger(targetCorpse, parent);
|
||||
}
|
||||
}
|
||||
|
||||
// 开始标记工作
|
||||
private void StartMarking(Thing targetBuilding)
|
||||
{
|
||||
this.markingTargetBuilding = targetBuilding;
|
||||
this.isMarking = true;
|
||||
|
||||
// 计算标记时间(基于距离)
|
||||
float distance = targetBuilding.Position.DistanceTo(parent.Position);
|
||||
float workTimeFactor = 1f + (distance / Props.markDeconstructionRadius) * 0.5f;
|
||||
|
||||
int baseMarkTime = Mathf.RoundToInt(Props.markDeconstructionInterval * 0.05f); // 标记时间为间隔的5%
|
||||
markingTicksRemaining = Mathf.RoundToInt(baseMarkTime * workTimeFactor / currentWorkSpeedMultiplier);
|
||||
|
||||
// 播放标记声音
|
||||
if (Props.markSound != null)
|
||||
{
|
||||
Props.markSound.PlayOneShot(new TargetInfo(parent.Position, parent.Map));
|
||||
}
|
||||
}
|
||||
|
||||
// 完成转换
|
||||
private void CompleteConversion()
|
||||
{
|
||||
var map = parent.Map;
|
||||
if (map == null || targetCorpse == null || targetCorpse.Destroyed || !targetCorpse.Spawned)
|
||||
{
|
||||
ResetWorkState();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查并消耗燃料
|
||||
if (!ConsumeFuelIfNeeded())
|
||||
{
|
||||
Messages.Message("ARA_CorpseConverter.InsufficientFuel".Translate(),
|
||||
new TargetInfo(targetCorpse.Position, map), MessageTypeDefOf.NegativeEvent);
|
||||
ResetWorkState();
|
||||
return;
|
||||
}
|
||||
|
||||
// 记录尸体的位置和信息
|
||||
IntVec3 corpsePosition = targetCorpse.Position;
|
||||
string corpseName = targetCorpse.InnerPawn?.LabelShort ?? "unknown";
|
||||
|
||||
// 移除尸体
|
||||
targetCorpse.Destroy(DestroyMode.Vanish);
|
||||
|
||||
// 播放转换声音
|
||||
if (Props.conversionSound != null)
|
||||
{
|
||||
Props.conversionSound.PlayOneShot(new TargetInfo(corpsePosition, map));
|
||||
}
|
||||
|
||||
// 生成目标建筑
|
||||
if (Props.targetThingDef != null)
|
||||
{
|
||||
Thing convertedThing = ThingMaker.MakeThing(Props.targetThingDef);
|
||||
|
||||
// 检查是否是建筑
|
||||
if (convertedThing.def.category == ThingCategory.Building)
|
||||
{
|
||||
// 尝试在尸体原位置生成
|
||||
GenSpawn.Spawn(convertedThing, corpsePosition, map);
|
||||
|
||||
// 播放完成声音
|
||||
if (Props.completionSound != null)
|
||||
{
|
||||
Props.completionSound.PlayOneShot(new TargetInfo(corpsePosition, map));
|
||||
}
|
||||
|
||||
// 显示消息(仅开发模式)
|
||||
if (Prefs.DevMode)
|
||||
{
|
||||
Log.Message($"[CorpseConverter] Converted {corpseName} at {corpsePosition} to {convertedThing.LabelCap}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"CorpseConverter: targetThingDef {Props.targetThingDef.defName} is not a building! Cannot spawn.");
|
||||
}
|
||||
}
|
||||
|
||||
ResetWorkState();
|
||||
}
|
||||
|
||||
// 完成标记工作
|
||||
private void CompleteMarking()
|
||||
{
|
||||
var map = parent.Map;
|
||||
if (map == null || markingTargetBuilding == null || markingTargetBuilding.Destroyed || !markingTargetBuilding.Spawned)
|
||||
{
|
||||
ResetMarkingState();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查并消耗标记燃料
|
||||
if (!ConsumeMarkingFuelIfNeeded())
|
||||
{
|
||||
Messages.Message("ARA_CorpseConverter.InsufficientFuelForMarking".Translate(),
|
||||
new TargetInfo(markingTargetBuilding.Position, map), MessageTypeDefOf.NegativeEvent);
|
||||
ResetMarkingState();
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保是目标建筑(安全检查)
|
||||
if (markingTargetBuilding.def != Props.targetThingDef)
|
||||
{
|
||||
ResetMarkingState();
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加拆除标记
|
||||
map.designationManager.AddDesignation(new Designation(markingTargetBuilding, DesignationDefOf.Deconstruct));
|
||||
|
||||
// 播放完成声音
|
||||
if (Props.markCompleteSound != null)
|
||||
{
|
||||
Props.markCompleteSound.PlayOneShot(new TargetInfo(markingTargetBuilding.Position, map));
|
||||
}
|
||||
|
||||
// 显示消息(仅开发模式)
|
||||
if (Prefs.DevMode)
|
||||
{
|
||||
Log.Message($"[CorpseConverter] Marked building at {markingTargetBuilding.Position} ({markingTargetBuilding.LabelCap}) for deconstruction");
|
||||
}
|
||||
|
||||
ResetMarkingState();
|
||||
}
|
||||
|
||||
// 重置工作状态
|
||||
private void ResetWorkState()
|
||||
{
|
||||
isWorking = false;
|
||||
workTicksRemaining = 0;
|
||||
targetCorpse = null;
|
||||
|
||||
// 清理效果器
|
||||
if (effecter != null)
|
||||
{
|
||||
effecter.Cleanup();
|
||||
effecter = null;
|
||||
}
|
||||
|
||||
if (conversionEffecter != null)
|
||||
{
|
||||
conversionEffecter.Cleanup();
|
||||
conversionEffecter = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置标记状态
|
||||
private void ResetMarkingState()
|
||||
{
|
||||
isMarking = false;
|
||||
markingTicksRemaining = 0;
|
||||
markingTargetBuilding = null;
|
||||
|
||||
// 清理标记效果器
|
||||
if (markingEffecter != null)
|
||||
{
|
||||
markingEffecter.Cleanup();
|
||||
markingEffecter = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新工作速度
|
||||
private void UpdateWorkSpeed()
|
||||
{
|
||||
float multiplier = 1.0f;
|
||||
|
||||
if (Props.requiresPower && HasPower)
|
||||
{
|
||||
multiplier *= Props.poweredWorkSpeedMultiplier;
|
||||
}
|
||||
|
||||
if (Props.requiresFuel && HasSufficientFuel)
|
||||
{
|
||||
// 燃料充足时可能有额外的速度加成
|
||||
}
|
||||
|
||||
currentWorkSpeedMultiplier = multiplier;
|
||||
}
|
||||
|
||||
// Tick更新
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
|
||||
// --- 处理尸体转换 ---
|
||||
if (isWorking)
|
||||
{
|
||||
workTicksRemaining--;
|
||||
|
||||
if (workTicksRemaining <= 0)
|
||||
{
|
||||
CompleteConversion();
|
||||
}
|
||||
|
||||
// 更新效果器
|
||||
if (effecter != null)
|
||||
{
|
||||
effecter.EffectTick(parent, targetCorpse);
|
||||
}
|
||||
|
||||
if (conversionEffecter != null)
|
||||
{
|
||||
conversionEffecter.EffectTick(targetCorpse, parent);
|
||||
}
|
||||
}
|
||||
else if (CanOperate())
|
||||
{
|
||||
UpdateWorkSpeed();
|
||||
|
||||
// 等待下一次转换
|
||||
if (ticksUntilNextConversion <= 0)
|
||||
{
|
||||
// 尝试找到有效目标尸体
|
||||
if (TryGetValidTargetCorpse(out Corpse target))
|
||||
{
|
||||
// 在开始工作前再次检查燃料
|
||||
if (Props.requiresFuel && Props.fuelConsumptionPerConversion > 0)
|
||||
{
|
||||
if (CompRefuelable == null || CompRefuelable.Fuel < Props.fuelConsumptionPerConversion)
|
||||
{
|
||||
// 燃料不足,重置计时器但跳过这次工作
|
||||
ticksUntilNextConversion = Mathf.RoundToInt(Props.conversionInterval / currentWorkSpeedMultiplier);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
StartWorking(target);
|
||||
}
|
||||
|
||||
// 重置计时器,无论是否成功找到目标
|
||||
ticksUntilNextConversion = Mathf.RoundToInt(Props.conversionInterval / currentWorkSpeedMultiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
ticksUntilNextConversion--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isWorking)
|
||||
{
|
||||
ResetWorkState();
|
||||
}
|
||||
}
|
||||
|
||||
// --- 处理自动标记拆除 ---
|
||||
if (isMarking)
|
||||
{
|
||||
markingTicksRemaining--;
|
||||
|
||||
if (markingTicksRemaining <= 0)
|
||||
{
|
||||
CompleteMarking();
|
||||
}
|
||||
|
||||
// 更新标记效果器
|
||||
if (markingEffecter != null)
|
||||
{
|
||||
markingEffecter.EffectTick(parent, markingTargetBuilding);
|
||||
}
|
||||
}
|
||||
else if (CanMarkForDeconstruction())
|
||||
{
|
||||
// 等待下一次标记
|
||||
if (ticksUntilNextMarkDeconstruction <= 0)
|
||||
{
|
||||
// 尝试找到有效标记建筑
|
||||
if (TryGetValidMarkingBuilding(out Thing target))
|
||||
{
|
||||
// 在开始标记前再次检查燃料
|
||||
if (Props.requiresFuel && Props.fuelConsumptionPerMark > 0)
|
||||
{
|
||||
if (CompRefuelable == null || CompRefuelable.Fuel < Props.fuelConsumptionPerMark)
|
||||
{
|
||||
// 燃料不足,重置计时器但跳过这次标记
|
||||
ticksUntilNextMarkDeconstruction = Mathf.RoundToInt(Props.markDeconstructionInterval / currentWorkSpeedMultiplier);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
StartMarking(target);
|
||||
}
|
||||
|
||||
// 重置计时器,无论是否成功找到目标
|
||||
ticksUntilNextMarkDeconstruction = Mathf.RoundToInt(Props.markDeconstructionInterval / currentWorkSpeedMultiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
ticksUntilNextMarkDeconstruction--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 防止长时间不工作
|
||||
public override void CompTickRare()
|
||||
{
|
||||
base.CompTickRare();
|
||||
|
||||
if (!isWorking && ticksUntilNextConversion > Props.conversionInterval * 10)
|
||||
{
|
||||
// 防止计时器溢出
|
||||
ticksUntilNextConversion = Props.conversionInterval;
|
||||
}
|
||||
|
||||
if (!isMarking && ticksUntilNextMarkDeconstruction > Props.markDeconstructionInterval * 10)
|
||||
{
|
||||
// 防止标记计时器溢出
|
||||
ticksUntilNextMarkDeconstruction = Props.markDeconstructionInterval;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存/加载
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
|
||||
// 尸体转换状态
|
||||
Scribe_Values.Look(ref ticksUntilNextConversion, "ticksUntilNextConversion", Props.conversionInterval);
|
||||
Scribe_Values.Look(ref isWorking, "isWorking", false);
|
||||
Scribe_Values.Look(ref workTicksRemaining, "workTicksRemaining", 0);
|
||||
|
||||
// 标记拆除状态
|
||||
Scribe_Values.Look(ref autoMarkForDeconstructionEnabled, "autoMarkForDeconstructionEnabled", true);
|
||||
Scribe_Values.Look(ref ticksUntilNextMarkDeconstruction, "ticksUntilNextMarkDeconstruction", Props.markDeconstructionInterval);
|
||||
Scribe_Values.Look(ref isMarking, "isMarking", false);
|
||||
Scribe_Values.Look(ref markingTicksRemaining, "markingTicksRemaining", 0);
|
||||
|
||||
// 保存目标尸体的引用
|
||||
if (Scribe.mode == LoadSaveMode.Saving)
|
||||
{
|
||||
bool hasTarget = targetCorpse != null && targetCorpse.Spawned;
|
||||
Scribe_Values.Look(ref hasTarget, "hasTarget", false);
|
||||
if (hasTarget)
|
||||
{
|
||||
Scribe_References.Look(ref targetCorpse, "targetCorpse");
|
||||
}
|
||||
|
||||
bool hasMarkingTarget = markingTargetBuilding != null && markingTargetBuilding.Spawned;
|
||||
Scribe_Values.Look(ref hasMarkingTarget, "hasMarkingTarget", false);
|
||||
if (hasMarkingTarget)
|
||||
{
|
||||
Scribe_References.Look(ref markingTargetBuilding, "markingTargetBuilding");
|
||||
}
|
||||
}
|
||||
else if (Scribe.mode == LoadSaveMode.LoadingVars)
|
||||
{
|
||||
bool hasTarget = false;
|
||||
Scribe_Values.Look(ref hasTarget, "hasTarget", false);
|
||||
if (hasTarget)
|
||||
{
|
||||
Scribe_References.Look(ref targetCorpse, "targetCorpse");
|
||||
}
|
||||
|
||||
bool hasMarkingTarget = false;
|
||||
Scribe_Values.Look(ref hasMarkingTarget, "hasMarkingTarget", false);
|
||||
if (hasMarkingTarget)
|
||||
{
|
||||
Scribe_References.Look(ref markingTargetBuilding, "markingTargetBuilding");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查字符串
|
||||
public override string CompInspectStringExtra()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
if (Props.requiresPower)
|
||||
{
|
||||
builder.AppendLine("ARA_CorpseConverter.Power".Translate(HasPower ? "On".Translate() : "Off".Translate()));
|
||||
}
|
||||
|
||||
if (Props.requiresFuel)
|
||||
{
|
||||
if (CompRefuelable != null)
|
||||
{
|
||||
builder.AppendLine("ARA_CorpseConverter.Fuel".Translate(
|
||||
CompRefuelable.Fuel.ToString("F1"),
|
||||
CompRefuelable.TargetFuelLevel.ToString("F1")));
|
||||
|
||||
if (Props.fuelConsumptionPerConversion > 0)
|
||||
{
|
||||
builder.AppendLine("ARA_CorpseConverter.FuelPerConversion".Translate(
|
||||
Props.fuelConsumptionPerConversion.ToString("F1")));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine("ARA_CorpseConverter.NoFuelComponent".Translate());
|
||||
}
|
||||
}
|
||||
|
||||
if (isWorking)
|
||||
{
|
||||
float progressPercent = 1f - ((float)workTicksRemaining / (Props.conversionInterval * 0.1f));
|
||||
builder.AppendLine("ARA_CorpseConverter.WorkingProgress".Translate(
|
||||
progressPercent.ToStringPercent()));
|
||||
if (targetCorpse != null)
|
||||
{
|
||||
builder.AppendLine("ARA_CorpseConverter.TargetCorpse".Translate(
|
||||
targetCorpse.InnerPawn?.LabelShort ?? "unknown"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float daysUntilConversion = ticksUntilNextConversion / 60000f;
|
||||
builder.AppendLine("ARA_CorpseConverter.NextConversion".Translate(
|
||||
daysUntilConversion.ToString("F1")));
|
||||
}
|
||||
|
||||
builder.AppendLine("ARA_CorpseConverter.ConversionRadius".Translate(
|
||||
Props.conversionRadius.ToString("F1")));
|
||||
return builder.ToString().TrimEndNewlines();
|
||||
}
|
||||
|
||||
// 切换自动标记功能
|
||||
private void ToggleAutoMarking()
|
||||
{
|
||||
autoMarkForDeconstructionEnabled = !autoMarkForDeconstructionEnabled;
|
||||
|
||||
if (autoMarkForDeconstructionEnabled)
|
||||
{
|
||||
Messages.Message("ARA_CorpseConverter.AutoMarkEnabled".Translate(),
|
||||
parent, MessageTypeDefOf.PositiveEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages.Message("ARA_CorpseConverter.AutoMarkDisabled".Translate(),
|
||||
parent, MessageTypeDefOf.NeutralEvent);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取Gizmos
|
||||
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
||||
{
|
||||
foreach (var gizmo in base.CompGetGizmosExtra())
|
||||
{
|
||||
yield return gizmo;
|
||||
}
|
||||
|
||||
// 自动标记拆除切换按钮
|
||||
if (Props.enableAutoMarkForDeconstruction && parent.Faction == Faction.OfPlayer)
|
||||
{
|
||||
yield return new Command_Toggle
|
||||
{
|
||||
defaultLabel = "ARA_CorpseConverter.ToggleAutoMark".Translate(),
|
||||
defaultDesc = "ARA_CorpseConverter.ToggleAutoMarkDesc".Translate(),
|
||||
icon = ContentFinder<Texture2D>.Get("UI/Designators/Deconstruct", false) ?? BaseContent.BadTex,
|
||||
isActive = () => autoMarkForDeconstructionEnabled,
|
||||
toggleAction = ToggleAutoMarking,
|
||||
hotKey = KeyBindingDefOf.Misc4
|
||||
};
|
||||
}
|
||||
|
||||
// 开发模式下的调试命令
|
||||
if (DebugSettings.ShowDevGizmos)
|
||||
{
|
||||
yield return new Command_Action
|
||||
{
|
||||
defaultLabel = "DEV: Test Conversion",
|
||||
action = delegate
|
||||
{
|
||||
if (TryGetValidTargetCorpse(out Corpse corpse))
|
||||
{
|
||||
StartWorking(corpse);
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages.Message("No valid corpses found", parent, MessageTypeDefOf.RejectInput);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
yield return new Command_Action
|
||||
{
|
||||
defaultLabel = "DEV: Reset Timer",
|
||||
action = delegate
|
||||
{
|
||||
ticksUntilNextConversion = 0;
|
||||
}
|
||||
};
|
||||
|
||||
yield return new Command_Action
|
||||
{
|
||||
defaultLabel = "DEV: Test Marking",
|
||||
action = delegate
|
||||
{
|
||||
if (TryGetValidMarkingBuilding(out Thing building))
|
||||
{
|
||||
StartMarking(building);
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages.Message("No valid buildings found", parent, MessageTypeDefOf.RejectInput);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制选择时的额外效果
|
||||
public override void PostDrawExtraSelectionOverlays()
|
||||
{
|
||||
base.PostDrawExtraSelectionOverlays();
|
||||
|
||||
if (Props.showRadius)
|
||||
{
|
||||
// 显示转换半径
|
||||
GenDraw.DrawRadiusRing(parent.Position, Props.conversionRadius, Color.red);
|
||||
|
||||
// 显示标记半径(如果不同)
|
||||
if (Props.markDeconstructionRadius != Props.conversionRadius)
|
||||
{
|
||||
GenDraw.DrawRadiusRing(parent.Position, Props.markDeconstructionRadius, new Color(1f, 0.5f, 0f, 0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 建筑被销毁时清理
|
||||
public override void PostDestroy(DestroyMode mode, Map previousMap)
|
||||
{
|
||||
base.PostDestroy(mode, previousMap);
|
||||
ResetWorkState();
|
||||
ResetMarkingState();
|
||||
}
|
||||
|
||||
// 建筑生成时初始化
|
||||
public override void PostSpawnSetup(bool respawningAfterLoad)
|
||||
{
|
||||
base.PostSpawnSetup(respawningAfterLoad);
|
||||
refuelableComponentCached = false; // 重置缓存,重新获取组件
|
||||
|
||||
// 初始化计时器
|
||||
if (!respawningAfterLoad)
|
||||
{
|
||||
ticksUntilNextConversion = Props.conversionInterval;
|
||||
autoMarkForDeconstructionEnabled = Props.enableAutoMarkForDeconstruction;
|
||||
ticksUntilNextMarkDeconstruction = Props.markDeconstructionInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// File: Comps/CompProperties_CorpseConverter.cs
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class CompProperties_CorpseConverter : CompProperties
|
||||
{
|
||||
// 转换目标物品定义(必须是建筑)
|
||||
public ThingDef targetThingDef;
|
||||
|
||||
// 转换数量(每次转换生成多少个目标物品)
|
||||
public int targetThingCount = 1;
|
||||
|
||||
// 基础转换间隔(游戏刻)
|
||||
public int conversionInterval = 60000; // 默认1天
|
||||
|
||||
// 转换半径(以单元格为单位)
|
||||
public float conversionRadius = 8f;
|
||||
|
||||
// 是否需要电源
|
||||
public bool requiresPower = false;
|
||||
|
||||
// 电源开启时的工作速度乘数
|
||||
public float poweredWorkSpeedMultiplier = 1.5f;
|
||||
|
||||
// 需要燃料
|
||||
public bool requiresFuel = false;
|
||||
|
||||
// 每次转换消耗的燃料量
|
||||
public float fuelConsumptionPerConversion = 5f;
|
||||
|
||||
// 最小燃料量要求(低于此值不工作)
|
||||
public float minFuelToOperate = 0.1f;
|
||||
|
||||
// 接受哪些种类的尸体(可选,如果为空则接受所有尸体)
|
||||
public List<ThingDef> acceptedCorpseDefs;
|
||||
|
||||
// 是否显示视觉效果
|
||||
public bool showVisualEffects = true;
|
||||
|
||||
// 工作时的效果器
|
||||
public EffecterDef workingEffecter;
|
||||
|
||||
// 转换时的效果器
|
||||
public EffecterDef conversionEffecter;
|
||||
|
||||
// 工作时的声音
|
||||
public SoundDef workingSound;
|
||||
|
||||
// 转换时的声音
|
||||
public SoundDef conversionSound;
|
||||
|
||||
// 转换完成时的声音
|
||||
public SoundDef completionSound;
|
||||
|
||||
// 是否需要房间
|
||||
public bool requiresRoom = false;
|
||||
|
||||
// 需要的最低房间评分(可选)
|
||||
public float minRoomScore = -9999f;
|
||||
|
||||
// 是否显示转换进度
|
||||
public bool showProgress = true;
|
||||
|
||||
// 是否显示转换半径
|
||||
public bool showRadius = true;
|
||||
|
||||
// --- 新增:自动标记拆除功能 ---
|
||||
|
||||
// 是否启用自动标记拆除功能
|
||||
public bool enableAutoMarkForDeconstruction = true;
|
||||
|
||||
// 自动标记拆除间隔(游戏刻)
|
||||
public int markDeconstructionInterval = 120000; // 默认2天
|
||||
|
||||
// 标记拆除半径(可以不同于转换半径)
|
||||
public float markDeconstructionRadius = 8f;
|
||||
|
||||
// 每次标记消耗的燃料量(可选)
|
||||
public float fuelConsumptionPerMark = 2f;
|
||||
|
||||
// 标记效果器
|
||||
public EffecterDef markEffecter;
|
||||
|
||||
// 标记时的声音
|
||||
public SoundDef markSound;
|
||||
|
||||
// 标记完成时的声音
|
||||
public SoundDef markCompleteSound;
|
||||
|
||||
// 是否排除机械族尸体
|
||||
public bool excludeMechanoidCorpses = true;
|
||||
|
||||
public CompProperties_CorpseConverter()
|
||||
{
|
||||
compClass = typeof(CompCorpseConverter);
|
||||
}
|
||||
|
||||
// 验证配置
|
||||
public override void ResolveReferences(ThingDef parentDef)
|
||||
{
|
||||
base.ResolveReferences(parentDef);
|
||||
|
||||
if (targetThingDef == null)
|
||||
{
|
||||
Log.Warning($"CompProperties_CorpseConverter on {parentDef.defName} has no targetThingDef specified!");
|
||||
}
|
||||
|
||||
// 检查目标物品是否是建筑
|
||||
if (targetThingDef != null && targetThingDef.category != ThingCategory.Building)
|
||||
{
|
||||
Log.Warning($"CompProperties_CorpseConverter on {parentDef.defName}: targetThingDef {targetThingDef.defName} is not a building, but auto-deconstruction requires a building!");
|
||||
}
|
||||
|
||||
// 如果未指定标记半径,使用转换半径
|
||||
if (markDeconstructionRadius <= 0)
|
||||
{
|
||||
markDeconstructionRadius = conversionRadius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,8 +323,8 @@ namespace ArachnaeSwarm
|
||||
// 只在玩家控制下显示
|
||||
if (parent.Faction?.IsPlayer == true)
|
||||
{
|
||||
// 调试按钮:手动触发寻找维护者
|
||||
if (Prefs.DevMode)
|
||||
// 调试按钮:手动触发寻找维护者 - 仅在GodMode下显示
|
||||
if (DebugSettings.godMode)
|
||||
{
|
||||
yield return new Command_Action
|
||||
{
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// File: Comps/CompProperties_TerrainChanger.cs
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class CompProperties_TerrainChanger : CompProperties
|
||||
{
|
||||
// 目标地形定义
|
||||
public TerrainDef targetTerrain;
|
||||
|
||||
// 基础改变间隔(游戏刻)
|
||||
public int baseChangeInterval = 60000; // 默认1天
|
||||
|
||||
// 改变半径(以单元格为单位)
|
||||
public float changeRadius = 5f;
|
||||
|
||||
// 是否只在房间内改变
|
||||
public bool onlyInSameRoom = true;
|
||||
|
||||
// 是否优先改变最近的地形
|
||||
public bool prioritizeClosest = true;
|
||||
|
||||
// 需要的最低房间评分(可选)
|
||||
public float minRoomScore = -9999f;
|
||||
|
||||
// 是否需要电源
|
||||
public bool requiresPower = false;
|
||||
|
||||
// 电源开启时的工作速度乘数
|
||||
public float poweredWorkSpeedMultiplier = 2f;
|
||||
|
||||
// 需要燃料
|
||||
public bool requiresFuel = false;
|
||||
|
||||
// 每次地形改变消耗的燃料量
|
||||
public float fuelConsumptionPerChange = 1f;
|
||||
|
||||
// 最小燃料量要求(低于此值不工作)
|
||||
public float minFuelToOperate = 0.1f;
|
||||
|
||||
// 可接受的地形类型列表(可选,如果为空则接受所有可通行地形)
|
||||
public List<TerrainDef> acceptedTerrains;
|
||||
|
||||
// 是否显示视觉效果
|
||||
public bool showVisualEffects = true;
|
||||
|
||||
// 效果器定义
|
||||
public EffecterDef workingEffecter;
|
||||
|
||||
// 工作时的声音
|
||||
public SoundDef workingSound;
|
||||
|
||||
// 完成时的声音
|
||||
public SoundDef completionSound;
|
||||
|
||||
// --- 新增:自动标记拆除功能 ---
|
||||
|
||||
// 是否启用自动标记拆除功能
|
||||
public bool enableAutoMarkForRemoval = true;
|
||||
|
||||
// 自动标记拆除间隔(游戏刻)
|
||||
public int markRemovalInterval = 120000; // 默认2天
|
||||
|
||||
// 标记拆除半径(可以不同于改变半径)
|
||||
public float markRemovalRadius = 5f;
|
||||
|
||||
// 每次标记消耗的燃料量(可选)
|
||||
public float fuelConsumptionPerMark = 0.5f;
|
||||
|
||||
// 标记效果器
|
||||
public EffecterDef markEffecter;
|
||||
|
||||
// 标记时的声音
|
||||
public SoundDef markSound;
|
||||
|
||||
// 标记完成时的声音
|
||||
public SoundDef markCompleteSound;
|
||||
|
||||
public CompProperties_TerrainChanger()
|
||||
{
|
||||
compClass = typeof(CompTerrainChanger);
|
||||
}
|
||||
|
||||
// 验证配置
|
||||
public override void ResolveReferences(ThingDef parentDef)
|
||||
{
|
||||
base.ResolveReferences(parentDef);
|
||||
|
||||
if (targetTerrain == null)
|
||||
{
|
||||
Log.Warning($"CompProperties_TerrainChanger on {parentDef.defName} has no targetTerrain specified!");
|
||||
}
|
||||
|
||||
// 如果未指定标记半径,使用改变半径
|
||||
if (markRemovalRadius <= 0)
|
||||
{
|
||||
markRemovalRadius = changeRadius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,825 @@
|
||||
// File: Comps/CompTerrainChanger.cs
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.Sound;
|
||||
using System.Text;
|
||||
using Verse.AI;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class CompTerrainChanger : ThingComp
|
||||
{
|
||||
// 属性引用
|
||||
private CompProperties_TerrainChanger Props => (CompProperties_TerrainChanger)props;
|
||||
|
||||
// 状态变量
|
||||
private int ticksUntilNextChange;
|
||||
private bool isWorking = false;
|
||||
private int workTicksRemaining = 0;
|
||||
private IntVec3 targetCell = IntVec3.Invalid;
|
||||
private Effecter effecter;
|
||||
|
||||
// --- 新增:自动标记拆除状态 ---
|
||||
private bool autoMarkForRemovalEnabled = true;
|
||||
private int ticksUntilNextMarkRemoval;
|
||||
private bool isMarking = false;
|
||||
private int markingTicksRemaining = 0;
|
||||
private IntVec3 markingTargetCell = IntVec3.Invalid;
|
||||
private Effecter markingEffecter;
|
||||
|
||||
// 当前工作速度乘数
|
||||
private float currentWorkSpeedMultiplier = 1.0f;
|
||||
|
||||
// 缓存燃料组件
|
||||
private CompRefuelableNutrition_WithKey compRefuelable;
|
||||
private bool refuelableComponentCached = false;
|
||||
|
||||
// 获取燃料组件
|
||||
private CompRefuelableNutrition_WithKey CompRefuelable
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!refuelableComponentCached)
|
||||
{
|
||||
compRefuelable = parent.TryGetComp<CompRefuelableNutrition_WithKey>();
|
||||
refuelableComponentCached = true;
|
||||
}
|
||||
return compRefuelable;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查燃料是否充足
|
||||
private bool HasSufficientFuel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Props.requiresFuel)
|
||||
return true;
|
||||
|
||||
if (CompRefuelable == null)
|
||||
return false;
|
||||
|
||||
return CompRefuelable.Fuel >= Props.minFuelToOperate;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有足够的燃料用于标记
|
||||
private bool HasSufficientFuelForMarking
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Props.requiresFuel || Props.fuelConsumptionPerMark <= 0)
|
||||
return true;
|
||||
|
||||
if (CompRefuelable == null)
|
||||
return false;
|
||||
|
||||
return CompRefuelable.Fuel >= Props.fuelConsumptionPerMark;
|
||||
}
|
||||
}
|
||||
|
||||
// 消耗燃料(用于地形改变)
|
||||
private bool ConsumeFuelIfNeeded()
|
||||
{
|
||||
if (!Props.requiresFuel || Props.fuelConsumptionPerChange <= 0)
|
||||
return true;
|
||||
|
||||
if (CompRefuelable == null)
|
||||
return false;
|
||||
|
||||
if (CompRefuelable.Fuel >= Props.fuelConsumptionPerChange)
|
||||
{
|
||||
CompRefuelable.ConsumeFuel(Props.fuelConsumptionPerChange);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 消耗标记燃料
|
||||
private bool ConsumeMarkingFuelIfNeeded()
|
||||
{
|
||||
if (!Props.requiresFuel || Props.fuelConsumptionPerMark <= 0)
|
||||
return true;
|
||||
|
||||
if (CompRefuelable == null)
|
||||
return false;
|
||||
|
||||
if (CompRefuelable.Fuel >= Props.fuelConsumptionPerMark)
|
||||
{
|
||||
CompRefuelable.ConsumeFuel(Props.fuelConsumptionPerMark);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取电源状态
|
||||
private bool HasPower
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Props.requiresPower)
|
||||
return true;
|
||||
|
||||
var compPower = parent.TryGetComp<CompPowerTrader>();
|
||||
return compPower != null && compPower.PowerOn;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取房间
|
||||
private Room GetRoom()
|
||||
{
|
||||
var map = parent.Map;
|
||||
if (map == null)
|
||||
return null;
|
||||
|
||||
return parent.Position.GetRoom(map);
|
||||
}
|
||||
|
||||
// 检查是否满足基本操作条件
|
||||
private bool CanOperate()
|
||||
{
|
||||
// 检查是否有电
|
||||
if (Props.requiresPower && !HasPower)
|
||||
return false;
|
||||
|
||||
// 检查是否有足够的燃料
|
||||
if (Props.requiresFuel && !HasSufficientFuel)
|
||||
return false;
|
||||
|
||||
// 检查是否在房间内(如果需要)
|
||||
if (Props.onlyInSameRoom)
|
||||
{
|
||||
var room = GetRoom();
|
||||
if (room == null || !room.ProperRoom)
|
||||
return false;
|
||||
|
||||
// 检查房间评分
|
||||
if (room.GetStat(RoomStatDefOf.Impressiveness) < Props.minRoomScore)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否可以执行标记操作
|
||||
private bool CanMarkForRemoval()
|
||||
{
|
||||
if (!Props.enableAutoMarkForRemoval || !autoMarkForRemovalEnabled)
|
||||
return false;
|
||||
|
||||
if (!CanOperate())
|
||||
return false;
|
||||
|
||||
// 检查标记燃料
|
||||
if (Props.requiresFuel && !HasSufficientFuelForMarking)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取有效的工作单元格(地形改变)
|
||||
private bool TryGetValidTargetCell(out IntVec3 result)
|
||||
{
|
||||
result = IntVec3.Invalid;
|
||||
var map = parent.Map;
|
||||
if (map == null)
|
||||
return false;
|
||||
|
||||
// 获取搜索范围
|
||||
var center = parent.Position;
|
||||
int radius = Mathf.CeilToInt(Props.changeRadius);
|
||||
|
||||
// 获取房间(如果需要)
|
||||
Room parentRoom = null;
|
||||
if (Props.onlyInSameRoom)
|
||||
{
|
||||
parentRoom = GetRoom();
|
||||
if (parentRoom == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
// 收集所有候选单元格
|
||||
List<IntVec3> candidateCells = new List<IntVec3>();
|
||||
|
||||
for (int x = -radius; x <= radius; x++)
|
||||
{
|
||||
for (int z = -radius; z <= radius; z++)
|
||||
{
|
||||
IntVec3 cell = new IntVec3(center.x + x, 0, center.z + z);
|
||||
|
||||
if (!cell.InBounds(map))
|
||||
continue;
|
||||
|
||||
// 检查距离
|
||||
float distance = cell.DistanceTo(center);
|
||||
if (distance > Props.changeRadius)
|
||||
continue;
|
||||
|
||||
// 检查房间(如果需要)
|
||||
if (Props.onlyInSameRoom)
|
||||
{
|
||||
var cellRoom = cell.GetRoom(map);
|
||||
if (cellRoom == null || cellRoom != parentRoom)
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取当前地形
|
||||
TerrainDef currentTerrain = map.terrainGrid.TerrainAt(cell);
|
||||
|
||||
// 如果已经是目标地形,跳过
|
||||
if (currentTerrain == Props.targetTerrain)
|
||||
continue;
|
||||
|
||||
// 检查是否在可接受地形列表中(如果有定义)
|
||||
if (Props.acceptedTerrains != null && Props.acceptedTerrains.Count > 0)
|
||||
{
|
||||
if (!Props.acceptedTerrains.Contains(currentTerrain))
|
||||
continue;
|
||||
}
|
||||
|
||||
candidateCells.Add(cell);
|
||||
}
|
||||
}
|
||||
|
||||
if (candidateCells.Count == 0)
|
||||
return false;
|
||||
|
||||
// 根据设置选择单元格
|
||||
if (Props.prioritizeClosest)
|
||||
{
|
||||
// 找到最近的单元格
|
||||
float closestDistance = float.MaxValue;
|
||||
IntVec3 closestCell = IntVec3.Invalid;
|
||||
|
||||
foreach (var cell in candidateCells)
|
||||
{
|
||||
float distance = cell.DistanceTo(center);
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestCell = cell;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestCell.IsValid)
|
||||
{
|
||||
result = closestCell;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 随机选择一个单元格
|
||||
result = candidateCells.RandomElement();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取有效的标记拆除单元格
|
||||
private bool TryGetValidMarkingCell(out IntVec3 result)
|
||||
{
|
||||
result = IntVec3.Invalid;
|
||||
var map = parent.Map;
|
||||
if (map == null)
|
||||
return false;
|
||||
|
||||
// 获取搜索范围
|
||||
var center = parent.Position;
|
||||
int radius = Mathf.CeilToInt(Props.markRemovalRadius);
|
||||
|
||||
// 获取房间(如果需要)
|
||||
Room parentRoom = null;
|
||||
if (Props.onlyInSameRoom)
|
||||
{
|
||||
parentRoom = GetRoom();
|
||||
if (parentRoom == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
// 收集所有候选单元格
|
||||
List<IntVec3> candidateCells = new List<IntVec3>();
|
||||
|
||||
for (int x = -radius; x <= radius; x++)
|
||||
{
|
||||
for (int z = -radius; z <= radius; z++)
|
||||
{
|
||||
IntVec3 cell = new IntVec3(center.x + x, 0, center.z + z);
|
||||
|
||||
if (!cell.InBounds(map))
|
||||
continue;
|
||||
|
||||
// 检查距离
|
||||
float distance = cell.DistanceTo(center);
|
||||
if (distance > Props.markRemovalRadius)
|
||||
continue;
|
||||
|
||||
// 检查房间(如果需要)
|
||||
if (Props.onlyInSameRoom)
|
||||
{
|
||||
var cellRoom = cell.GetRoom(map);
|
||||
if (cellRoom == null || cellRoom != parentRoom)
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取当前地形
|
||||
TerrainDef currentTerrain = map.terrainGrid.TerrainAt(cell);
|
||||
|
||||
// 如果不是目标地形,跳过(只标记可以生成的地形)
|
||||
if (currentTerrain != Props.targetTerrain)
|
||||
continue;
|
||||
|
||||
// 检查是否已经标记了拆除
|
||||
if (map.designationManager.DesignationAt(cell, DesignationDefOf.RemoveFloor) != null)
|
||||
continue;
|
||||
|
||||
// 检查是否可以移除
|
||||
if (!map.terrainGrid.CanRemoveTopLayerAt(cell))
|
||||
continue;
|
||||
|
||||
// 检查是否有建筑阻挡
|
||||
if (WorkGiver_ConstructRemoveFloor.AnyBuildingBlockingFloorRemoval(cell, map))
|
||||
continue;
|
||||
|
||||
candidateCells.Add(cell);
|
||||
}
|
||||
}
|
||||
|
||||
if (candidateCells.Count == 0)
|
||||
return false;
|
||||
|
||||
// 根据设置选择单元格
|
||||
if (Props.prioritizeClosest)
|
||||
{
|
||||
// 找到最近的单元格
|
||||
float closestDistance = float.MaxValue;
|
||||
IntVec3 closestCell = IntVec3.Invalid;
|
||||
|
||||
foreach (var cell in candidateCells)
|
||||
{
|
||||
float distance = cell.DistanceTo(center);
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestCell = cell;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestCell.IsValid)
|
||||
{
|
||||
result = closestCell;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 随机选择一个单元格
|
||||
result = candidateCells.RandomElement();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 开始工作(地形改变)
|
||||
private void StartWorking(IntVec3 targetCell)
|
||||
{
|
||||
this.targetCell = targetCell;
|
||||
this.isWorking = true;
|
||||
|
||||
// 计算工作时间(基于距离)
|
||||
float distance = targetCell.DistanceTo(parent.Position);
|
||||
float workTimeFactor = 1f + (distance / Props.changeRadius) * 0.5f; // 距离越远,时间越长
|
||||
|
||||
int baseWorkTime = Mathf.RoundToInt(Props.baseChangeInterval * 0.1f); // 工作时间为间隔的10%
|
||||
workTicksRemaining = Mathf.RoundToInt(baseWorkTime * workTimeFactor / currentWorkSpeedMultiplier);
|
||||
|
||||
// 播放声音
|
||||
if (Props.workingSound != null)
|
||||
{
|
||||
Props.workingSound.PlayOneShot(new TargetInfo(parent.Position, parent.Map));
|
||||
}
|
||||
}
|
||||
|
||||
// 开始标记工作
|
||||
private void StartMarking(IntVec3 targetCell)
|
||||
{
|
||||
this.markingTargetCell = targetCell;
|
||||
this.isMarking = true;
|
||||
|
||||
// 计算标记时间(基于距离)
|
||||
float distance = targetCell.DistanceTo(parent.Position);
|
||||
float workTimeFactor = 1f + (distance / Props.markRemovalRadius) * 0.5f;
|
||||
|
||||
int baseMarkTime = Mathf.RoundToInt(Props.markRemovalInterval * 0.05f); // 标记时间为间隔的5%
|
||||
markingTicksRemaining = Mathf.RoundToInt(baseMarkTime * workTimeFactor / currentWorkSpeedMultiplier);
|
||||
|
||||
// 播放标记声音
|
||||
if (Props.markSound != null)
|
||||
{
|
||||
Props.markSound.PlayOneShot(new TargetInfo(parent.Position, parent.Map));
|
||||
}
|
||||
}
|
||||
|
||||
// 完成工作(地形改变)
|
||||
private void CompleteWork()
|
||||
{
|
||||
var map = parent.Map;
|
||||
if (map == null || !targetCell.IsValid || !targetCell.InBounds(map))
|
||||
{
|
||||
ResetWorkState();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查并消耗燃料
|
||||
if (!ConsumeFuelIfNeeded())
|
||||
{
|
||||
Messages.Message("ARA_TerrainChanger.InsufficientFuel".Translate(),
|
||||
new TargetInfo(targetCell, map), MessageTypeDefOf.NegativeEvent);
|
||||
ResetWorkState();
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前地形
|
||||
TerrainDef currentTerrain = map.terrainGrid.TerrainAt(targetCell);
|
||||
|
||||
// 记录之前的地形
|
||||
TerrainDef previousTerrain = currentTerrain;
|
||||
|
||||
// 改变地形
|
||||
map.terrainGrid.SetTerrain(targetCell, Props.targetTerrain);
|
||||
|
||||
// 播放完成声音
|
||||
if (Props.completionSound != null)
|
||||
{
|
||||
Props.completionSound.PlayOneShot(new TargetInfo(targetCell, map));
|
||||
}
|
||||
|
||||
// 显示消息(可选)
|
||||
if (Prefs.DevMode)
|
||||
{
|
||||
Log.Message($"[TerrainChanger] Changed terrain at {targetCell} from {previousTerrain?.defName ?? "null"} to {Props.targetTerrain.defName}");
|
||||
}
|
||||
|
||||
ResetWorkState();
|
||||
}
|
||||
|
||||
// 完成标记工作
|
||||
private void CompleteMarking()
|
||||
{
|
||||
var map = parent.Map;
|
||||
if (map == null || !markingTargetCell.IsValid || !markingTargetCell.InBounds(map))
|
||||
{
|
||||
ResetMarkingState();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查并消耗标记燃料
|
||||
if (!ConsumeMarkingFuelIfNeeded())
|
||||
{
|
||||
Messages.Message("ARA_TerrainChanger.InsufficientFuelForMarking".Translate(),
|
||||
new TargetInfo(markingTargetCell, map), MessageTypeDefOf.NegativeEvent);
|
||||
ResetMarkingState();
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前地形
|
||||
TerrainDef currentTerrain = map.terrainGrid.TerrainAt(markingTargetCell);
|
||||
|
||||
// 确保是目标地形(安全检查)
|
||||
if (currentTerrain != Props.targetTerrain)
|
||||
{
|
||||
ResetMarkingState();
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加拆除标记
|
||||
map.designationManager.AddDesignation(new Designation(markingTargetCell, DesignationDefOf.RemoveFloor));
|
||||
|
||||
// 播放完成声音
|
||||
if (Props.markCompleteSound != null)
|
||||
{
|
||||
Props.markCompleteSound.PlayOneShot(new TargetInfo(markingTargetCell, map));
|
||||
}
|
||||
|
||||
// 显示消息(可选)
|
||||
if (Prefs.DevMode)
|
||||
{
|
||||
Log.Message($"[TerrainChanger] Marked terrain at {markingTargetCell} ({currentTerrain.defName}) for removal");
|
||||
}
|
||||
|
||||
ResetMarkingState();
|
||||
}
|
||||
|
||||
// 重置工作状态
|
||||
private void ResetWorkState()
|
||||
{
|
||||
isWorking = false;
|
||||
workTicksRemaining = 0;
|
||||
targetCell = IntVec3.Invalid;
|
||||
|
||||
// 清理效果器
|
||||
if (effecter != null)
|
||||
{
|
||||
effecter.Cleanup();
|
||||
effecter = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置标记状态
|
||||
private void ResetMarkingState()
|
||||
{
|
||||
isMarking = false;
|
||||
markingTicksRemaining = 0;
|
||||
markingTargetCell = IntVec3.Invalid;
|
||||
|
||||
// 清理标记效果器
|
||||
if (markingEffecter != null)
|
||||
{
|
||||
markingEffecter.Cleanup();
|
||||
markingEffecter = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新工作速度
|
||||
private void UpdateWorkSpeed()
|
||||
{
|
||||
float multiplier = 1.0f;
|
||||
|
||||
if (Props.requiresPower && HasPower)
|
||||
{
|
||||
multiplier *= Props.poweredWorkSpeedMultiplier;
|
||||
}
|
||||
|
||||
if (Props.requiresFuel && HasSufficientFuel)
|
||||
{
|
||||
// 燃料充足时可能有额外的速度加成
|
||||
// 可以在这里添加燃料相关的速度加成
|
||||
}
|
||||
|
||||
currentWorkSpeedMultiplier = multiplier;
|
||||
}
|
||||
|
||||
// Tick更新
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
|
||||
// --- 处理地形改变 ---
|
||||
if (isWorking)
|
||||
{
|
||||
workTicksRemaining--;
|
||||
|
||||
if (workTicksRemaining <= 0)
|
||||
{
|
||||
CompleteWork();
|
||||
}
|
||||
}
|
||||
else if (CanOperate())
|
||||
{
|
||||
UpdateWorkSpeed();
|
||||
|
||||
// 等待下一次改变
|
||||
if (ticksUntilNextChange <= 0)
|
||||
{
|
||||
// 尝试找到有效目标单元格
|
||||
if (TryGetValidTargetCell(out IntVec3 target))
|
||||
{
|
||||
// 在开始工作前再次检查燃料
|
||||
if (Props.requiresFuel && Props.fuelConsumptionPerChange > 0)
|
||||
{
|
||||
if (CompRefuelable == null || CompRefuelable.Fuel < Props.fuelConsumptionPerChange)
|
||||
{
|
||||
// 燃料不足,重置计时器但跳过这次工作
|
||||
ticksUntilNextChange = Mathf.RoundToInt(Props.baseChangeInterval / currentWorkSpeedMultiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartWorking(target);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StartWorking(target);
|
||||
}
|
||||
}
|
||||
|
||||
// 重置计时器,无论是否成功找到目标
|
||||
ticksUntilNextChange = Mathf.RoundToInt(Props.baseChangeInterval / currentWorkSpeedMultiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
ticksUntilNextChange--;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 处理自动标记拆除 ---
|
||||
if (isMarking)
|
||||
{
|
||||
markingTicksRemaining--;
|
||||
|
||||
if (markingTicksRemaining <= 0)
|
||||
{
|
||||
CompleteMarking();
|
||||
}
|
||||
}
|
||||
else if (CanMarkForRemoval())
|
||||
{
|
||||
// 等待下一次标记
|
||||
if (ticksUntilNextMarkRemoval <= 0)
|
||||
{
|
||||
// 尝试找到有效标记单元格
|
||||
if (TryGetValidMarkingCell(out IntVec3 target))
|
||||
{
|
||||
// 在开始标记前再次检查燃料
|
||||
if (Props.requiresFuel && Props.fuelConsumptionPerMark > 0)
|
||||
{
|
||||
if (CompRefuelable == null || CompRefuelable.Fuel < Props.fuelConsumptionPerMark)
|
||||
{
|
||||
// 燃料不足,重置计时器但跳过这次标记
|
||||
ticksUntilNextMarkRemoval = Mathf.RoundToInt(Props.markRemovalInterval / currentWorkSpeedMultiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartMarking(target);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StartMarking(target);
|
||||
}
|
||||
}
|
||||
|
||||
// 重置计时器,无论是否成功找到目标
|
||||
ticksUntilNextMarkRemoval = Mathf.RoundToInt(Props.markRemovalInterval / currentWorkSpeedMultiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
ticksUntilNextMarkRemoval--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 防止长时间不工作
|
||||
public override void CompTickRare()
|
||||
{
|
||||
base.CompTickRare();
|
||||
|
||||
if (!isWorking && ticksUntilNextChange > Props.baseChangeInterval * 10)
|
||||
{
|
||||
// 防止计时器溢出
|
||||
ticksUntilNextChange = Props.baseChangeInterval;
|
||||
}
|
||||
|
||||
if (!isMarking && ticksUntilNextMarkRemoval > Props.markRemovalInterval * 10)
|
||||
{
|
||||
// 防止标记计时器溢出
|
||||
ticksUntilNextMarkRemoval = Props.markRemovalInterval;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存/加载
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
|
||||
// 地形改变状态
|
||||
Scribe_Values.Look(ref ticksUntilNextChange, "ticksUntilNextChange", Props.baseChangeInterval);
|
||||
Scribe_Values.Look(ref isWorking, "isWorking", false);
|
||||
Scribe_Values.Look(ref workTicksRemaining, "workTicksRemaining", 0);
|
||||
Scribe_Values.Look(ref targetCell, "targetCell", IntVec3.Invalid);
|
||||
|
||||
// 标记拆除状态
|
||||
Scribe_Values.Look(ref autoMarkForRemovalEnabled, "autoMarkForRemovalEnabled", true);
|
||||
Scribe_Values.Look(ref ticksUntilNextMarkRemoval, "ticksUntilNextMarkRemoval", Props.markRemovalInterval);
|
||||
Scribe_Values.Look(ref isMarking, "isMarking", false);
|
||||
Scribe_Values.Look(ref markingTicksRemaining, "markingTicksRemaining", 0);
|
||||
Scribe_Values.Look(ref markingTargetCell, "markingTargetCell", IntVec3.Invalid);
|
||||
}
|
||||
|
||||
// 检查字符串
|
||||
public override string CompInspectStringExtra()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
if (Props.requiresPower)
|
||||
{
|
||||
builder.AppendLine("ARA_TerrainChanger.Power".Translate(HasPower ? "On".Translate() : "Off".Translate()));
|
||||
}
|
||||
|
||||
if (Props.requiresFuel)
|
||||
{
|
||||
if (CompRefuelable != null)
|
||||
{
|
||||
builder.AppendLine("ARA_TerrainChanger.Fuel".Translate(
|
||||
CompRefuelable.Fuel.ToString("F1"),
|
||||
CompRefuelable.TargetFuelLevel.ToString("F1")));
|
||||
|
||||
if (Props.fuelConsumptionPerChange > 0)
|
||||
{
|
||||
builder.AppendLine("ARA_TerrainChanger.FuelPerChange".Translate(
|
||||
Props.fuelConsumptionPerChange.ToString("F1")));
|
||||
}
|
||||
|
||||
if (Props.fuelConsumptionPerMark > 0)
|
||||
{
|
||||
builder.AppendLine("ARA_TerrainChanger.FuelPerMark".Translate(
|
||||
Props.fuelConsumptionPerMark.ToString("F1")));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine("ARA_TerrainChanger.NoFuelComponent".Translate());
|
||||
}
|
||||
}
|
||||
|
||||
if (isWorking)
|
||||
{
|
||||
float progressPercent = 1f - ((float)workTicksRemaining / (Props.baseChangeInterval * 0.1f));
|
||||
builder.AppendLine("ARA_TerrainChanger.WorkingProgress".Translate(
|
||||
progressPercent.ToStringPercent()));
|
||||
builder.AppendLine("ARA_TerrainChanger.TargetCell".Translate(targetCell));
|
||||
}
|
||||
else
|
||||
{
|
||||
float daysUntilChange = ticksUntilNextChange / 60000f;
|
||||
builder.AppendLine("ARA_TerrainChanger.NextChange".Translate(
|
||||
daysUntilChange.ToString("F1")));
|
||||
}
|
||||
|
||||
builder.AppendLine("ARA_TerrainChanger.TargetTerrain".Translate(
|
||||
Props.targetTerrain.LabelCap));
|
||||
builder.AppendLine("ARA_TerrainChanger.ChangeRadius".Translate(
|
||||
Props.changeRadius.ToString("F1")));
|
||||
|
||||
return builder.ToString().TrimEndNewlines();
|
||||
}
|
||||
|
||||
// 切换自动标记功能
|
||||
private void ToggleAutoMarking()
|
||||
{
|
||||
autoMarkForRemovalEnabled = !autoMarkForRemovalEnabled;
|
||||
|
||||
if (autoMarkForRemovalEnabled)
|
||||
{
|
||||
Messages.Message("ARA_TerrainChanger.AutoMarkEnabled".Translate(),
|
||||
parent, MessageTypeDefOf.PositiveEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages.Message("ARA_TerrainChanger.AutoMarkDisabled".Translate(),
|
||||
parent, MessageTypeDefOf.NeutralEvent);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取Gizmos
|
||||
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
||||
{
|
||||
foreach (var gizmo in base.CompGetGizmosExtra())
|
||||
{
|
||||
yield return gizmo;
|
||||
}
|
||||
|
||||
// 只有在启用了自动标记功能时才显示切换按钮
|
||||
if (Props.enableAutoMarkForRemoval && parent.Faction == Faction.OfPlayer)
|
||||
{
|
||||
yield return new Command_Toggle
|
||||
{
|
||||
defaultLabel = "ARA_TerrainChanger.ToggleAutoMark".Translate(),
|
||||
defaultDesc = "ARA_TerrainChanger.ToggleAutoMarkDesc".Translate(),
|
||||
icon = ContentFinder<Texture2D>.Get("UI/Designators/RemoveFloor", false) ?? BaseContent.BadTex,
|
||||
isActive = () => autoMarkForRemovalEnabled,
|
||||
toggleAction = ToggleAutoMarking,
|
||||
hotKey = KeyBindingDefOf.Misc4
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 建筑被销毁时清理
|
||||
public override void PostDestroy(DestroyMode mode, Map previousMap)
|
||||
{
|
||||
base.PostDestroy(mode, previousMap);
|
||||
ResetWorkState();
|
||||
ResetMarkingState();
|
||||
}
|
||||
|
||||
// 建筑生成时初始化
|
||||
public override void PostSpawnSetup(bool respawningAfterLoad)
|
||||
{
|
||||
base.PostSpawnSetup(respawningAfterLoad);
|
||||
refuelableComponentCached = false; // 重置缓存,重新获取组件
|
||||
|
||||
// 初始化自动标记状态
|
||||
if (!respawningAfterLoad)
|
||||
{
|
||||
autoMarkForRemovalEnabled = Props.enableAutoMarkForRemoval;
|
||||
ticksUntilNextMarkRemoval = Props.markRemovalInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,29 +15,14 @@ namespace ArachnaeSwarm
|
||||
}
|
||||
|
||||
public ThingDef turretDef;
|
||||
|
||||
public float angleOffset;
|
||||
|
||||
public bool autoAttack = true;
|
||||
public bool defaultEnabled = true;
|
||||
}
|
||||
|
||||
[StaticConstructorOnStartup]
|
||||
public class HediffComp_TopTurret : HediffComp, IAttackTargetSearcher
|
||||
{
|
||||
// 添加 null 检查的属性
|
||||
private HediffCompProperties_TopTurret Props
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.props == null)
|
||||
{
|
||||
ArachnaeLog.Debug("HediffComp_TopTurret: props is null");
|
||||
return null;
|
||||
}
|
||||
return this.props as HediffCompProperties_TopTurret;
|
||||
}
|
||||
}
|
||||
|
||||
public Thing Thing
|
||||
{
|
||||
get
|
||||
@@ -46,6 +31,14 @@ namespace ArachnaeSwarm
|
||||
}
|
||||
}
|
||||
|
||||
private HediffCompProperties_TopTurret Props
|
||||
{
|
||||
get
|
||||
{
|
||||
return (HediffCompProperties_TopTurret)this.props;
|
||||
}
|
||||
}
|
||||
|
||||
public Verb CurrentEffectiveVerb
|
||||
{
|
||||
get
|
||||
@@ -74,11 +67,6 @@ namespace ArachnaeSwarm
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.gun == null)
|
||||
{
|
||||
ArachnaeLog.Debug("HediffComp_TopTurret: gun is null");
|
||||
return null;
|
||||
}
|
||||
return this.gun.TryGetComp<CompEquippable>();
|
||||
}
|
||||
}
|
||||
@@ -87,13 +75,7 @@ namespace ArachnaeSwarm
|
||||
{
|
||||
get
|
||||
{
|
||||
var comp = this.GunCompEq;
|
||||
if (comp == null)
|
||||
{
|
||||
ArachnaeLog.Debug("HediffComp_TopTurret: GunCompEq is null");
|
||||
return null;
|
||||
}
|
||||
return comp.PrimaryVerb;
|
||||
return this.GunCompEq.PrimaryVerb;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,10 +87,131 @@ namespace ArachnaeSwarm
|
||||
}
|
||||
}
|
||||
|
||||
public override void CompPostTick(ref float severityAdjustment)
|
||||
{
|
||||
base.CompPostTick(ref severityAdjustment);
|
||||
|
||||
if (!TurretEnabled)
|
||||
{
|
||||
ResetCurrentTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.CanShoot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.currentTarget.IsValid)
|
||||
{
|
||||
this.curRotation = (this.currentTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset;
|
||||
}
|
||||
|
||||
this.AttackVerb.VerbTick();
|
||||
|
||||
if (this.AttackVerb.state != VerbState.Bursting)
|
||||
{
|
||||
if (this.WarmingUp)
|
||||
{
|
||||
this.burstWarmupTicksLeft--;
|
||||
if (this.burstWarmupTicksLeft == 0)
|
||||
{
|
||||
bool attackSuccess = this.AttackVerb.TryStartCastOn(this.currentTarget, false, true, false, true);
|
||||
if (attackSuccess)
|
||||
{
|
||||
this.lastAttackTargetTick = Find.TickManager.TicksGame;
|
||||
this.lastAttackedTarget = this.currentTarget;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.burstCooldownTicksLeft > 0)
|
||||
{
|
||||
this.burstCooldownTicksLeft--;
|
||||
}
|
||||
|
||||
if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10))
|
||||
{
|
||||
// 自动寻找目标
|
||||
this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f);
|
||||
|
||||
if (this.currentTarget.IsValid)
|
||||
{
|
||||
this.burstWarmupTicksLeft = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
this.ResetCurrentTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 简化的Gizmos - 只有开关按钮
|
||||
public override IEnumerable<Gizmo> CompGetGizmos()
|
||||
{
|
||||
// 只有 pawn 被选中且是玩家派系时才显示按钮
|
||||
if (this.Pawn.Faction == Faction.OfPlayer && Find.Selector.IsSelected(this.Pawn))
|
||||
{
|
||||
yield return new Command_Toggle
|
||||
{
|
||||
defaultLabel = "CommandToggleTurret".Translate(),
|
||||
defaultDesc = "CommandToggleTurretDesc".Translate(),
|
||||
icon = ContentFinder<Texture2D>.Get("UI/Gizmos/ToggleTurret"),
|
||||
isActive = () => TurretEnabled,
|
||||
toggleAction = () => TurretEnabled = !TurretEnabled,
|
||||
hotKey = KeyBindingDefOf.Misc1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 简化的提示信息
|
||||
public override string CompTipStringExtra
|
||||
{
|
||||
get
|
||||
{
|
||||
string baseString = base.CompTipStringExtra;
|
||||
string turretStatus = TurretEnabled ? "Turret: Active" : "Turret: Inactive";
|
||||
string targetStatus = "Target: ";
|
||||
|
||||
if (this.currentTarget.IsValid)
|
||||
{
|
||||
targetStatus += $"{this.currentTarget.Thing?.LabelCap ?? this.currentTarget.Cell.ToString()}";
|
||||
}
|
||||
else
|
||||
{
|
||||
targetStatus += "None";
|
||||
}
|
||||
|
||||
string result = turretStatus + "\n" + targetStatus;
|
||||
return string.IsNullOrEmpty(baseString) ? result : baseString + "\n" + result;
|
||||
}
|
||||
}
|
||||
|
||||
// 炮塔启用状态
|
||||
public bool TurretEnabled
|
||||
{
|
||||
get { return turretEnabled; }
|
||||
set
|
||||
{
|
||||
turretEnabled = value;
|
||||
if (!turretEnabled)
|
||||
{
|
||||
ResetCurrentTarget(); // 禁用时重置目标
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanShoot
|
||||
{
|
||||
get
|
||||
{
|
||||
// 检查炮塔是否启用
|
||||
if (!TurretEnabled)
|
||||
return false;
|
||||
|
||||
Pawn pawn;
|
||||
if ((pawn = (this.Pawn)) != null)
|
||||
{
|
||||
@@ -147,7 +250,7 @@ namespace ArachnaeSwarm
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.turretMat == null && this.Props?.turretDef?.graphicData != null)
|
||||
if (this.turretMat == null)
|
||||
{
|
||||
this.turretMat = MaterialPool.MatFrom(this.Props.turretDef.graphicData.texPath);
|
||||
}
|
||||
@@ -159,71 +262,27 @@ namespace ArachnaeSwarm
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Props?.autoAttack ?? false;
|
||||
return this.Props.autoAttack;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CompPostMake()
|
||||
{
|
||||
base.CompPostMake();
|
||||
|
||||
// 添加 null 检查
|
||||
if (this.Props == null)
|
||||
{
|
||||
ArachnaeLog.Debug("HediffComp_TopTurret: Props is null in CompPostMake");
|
||||
return;
|
||||
}
|
||||
|
||||
this.MakeGun();
|
||||
// 设置默认启用状态
|
||||
TurretEnabled = Props.defaultEnabled;
|
||||
}
|
||||
|
||||
private void MakeGun()
|
||||
{
|
||||
// 添加详细的 null 检查
|
||||
if (this.Props == null)
|
||||
{
|
||||
ArachnaeLog.Debug("HediffComp_TopTurret: Props is null in MakeGun");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.Props.turretDef == null)
|
||||
{
|
||||
ArachnaeLog.Debug("HediffComp_TopTurret: Props.turretDef is null");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.gun = ThingMaker.MakeThing(this.Props.turretDef, null);
|
||||
if (this.gun == null)
|
||||
{
|
||||
ArachnaeLog.Debug($"HediffComp_TopTurret: Failed to create gun from turretDef '{this.Props.turretDef.defName}'");
|
||||
return;
|
||||
}
|
||||
this.UpdateGunVerbs();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ArachnaeLog.Debug($"HediffComp_TopTurret: Exception in MakeGun: {ex}");
|
||||
}
|
||||
this.gun = ThingMaker.MakeThing(this.Props.turretDef, null);
|
||||
this.UpdateGunVerbs();
|
||||
}
|
||||
|
||||
private void UpdateGunVerbs()
|
||||
{
|
||||
if (this.gun == null)
|
||||
{
|
||||
ArachnaeLog.Debug("HediffComp_TopTurret: gun is null in UpdateGunVerbs");
|
||||
return;
|
||||
}
|
||||
|
||||
var comp = this.gun.TryGetComp<CompEquippable>();
|
||||
if (comp == null)
|
||||
{
|
||||
ArachnaeLog.Debug("HediffComp_TopTurret: CompEquippable is null");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Verb> allVerbs = comp.AllVerbs;
|
||||
List<Verb> allVerbs = this.gun.TryGetComp<CompEquippable>().AllVerbs;
|
||||
for (int i = 0; i < allVerbs.Count; i++)
|
||||
{
|
||||
Verb verb = allVerbs[i];
|
||||
@@ -235,58 +294,6 @@ namespace ArachnaeSwarm
|
||||
}
|
||||
}
|
||||
|
||||
public override void CompPostTick(ref float severityAdjustment)
|
||||
{
|
||||
base.CompPostTick(ref severityAdjustment);
|
||||
|
||||
// 添加 null 检查
|
||||
if (this.AttackVerb == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.CanShoot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (this.currentTarget.IsValid)
|
||||
{
|
||||
this.curRotation = (this.currentTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset;
|
||||
}
|
||||
this.AttackVerb.VerbTick();
|
||||
if (this.AttackVerb.state != VerbState.Bursting)
|
||||
{
|
||||
if (this.WarmingUp)
|
||||
{
|
||||
this.burstWarmupTicksLeft--;
|
||||
if (this.burstWarmupTicksLeft == 0)
|
||||
{
|
||||
this.AttackVerb.TryStartCastOn(this.currentTarget, false, true, false, true);
|
||||
this.lastAttackTargetTick = Find.TickManager.TicksGame;
|
||||
this.lastAttackedTarget = this.currentTarget;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.burstCooldownTicksLeft > 0)
|
||||
{
|
||||
this.burstCooldownTicksLeft--;
|
||||
}
|
||||
if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10))
|
||||
{
|
||||
this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f);
|
||||
if (this.currentTarget.IsValid)
|
||||
{
|
||||
this.burstWarmupTicksLeft = 1;
|
||||
return;
|
||||
}
|
||||
this.ResetCurrentTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetCurrentTarget()
|
||||
{
|
||||
this.currentTarget = LocalTargetInfo.Invalid;
|
||||
@@ -301,11 +308,13 @@ namespace ArachnaeSwarm
|
||||
Scribe_TargetInfo.Look(ref this.currentTarget, "currentTarget");
|
||||
Scribe_Deep.Look<Thing>(ref this.gun, "gun", Array.Empty<object>());
|
||||
Scribe_Values.Look<bool>(ref this.fireAtWill, "fireAtWill", true, false);
|
||||
// 保存启用状态
|
||||
Scribe_Values.Look<bool>(ref this.turretEnabled, "turretEnabled", Props.defaultEnabled, false);
|
||||
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
{
|
||||
if (this.gun == null)
|
||||
{
|
||||
ArachnaeLog.Debug("CompTurrentGun had null gun after loading. Recreating.");
|
||||
this.MakeGun();
|
||||
return;
|
||||
}
|
||||
@@ -315,24 +324,18 @@ namespace ArachnaeSwarm
|
||||
|
||||
private const int StartShootIntervalTicks = 10;
|
||||
|
||||
private static readonly CachedTexture ToggleTurretIcon = new CachedTexture("UI/Gizmos/ToggleTurret");
|
||||
|
||||
public Thing gun;
|
||||
|
||||
protected int burstCooldownTicksLeft;
|
||||
|
||||
protected int burstWarmupTicksLeft;
|
||||
|
||||
protected LocalTargetInfo currentTarget = LocalTargetInfo.Invalid;
|
||||
|
||||
private bool fireAtWill = true;
|
||||
|
||||
private LocalTargetInfo lastAttackedTarget = LocalTargetInfo.Invalid;
|
||||
|
||||
private int lastAttackTargetTick;
|
||||
|
||||
private float curRotation;
|
||||
|
||||
// 炮塔启用状态字段
|
||||
private bool turretEnabled = true;
|
||||
|
||||
[Unsaved(false)]
|
||||
public Material turretMat;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,9 @@ namespace ArachnaeSwarm
|
||||
int stripCount = (int)ChitinNeed.CurLevel;
|
||||
if (stripCount < StripComp.Props.minStripAmount)
|
||||
stripCount = StripComp.Props.minStripAmount;
|
||||
|
||||
|
||||
stripCount = stripCount * 2;
|
||||
|
||||
// 获取甲壳物品定义
|
||||
ThingDef carapaceDef = StripComp.CarapaceThingDef;
|
||||
if (carapaceDef == null)
|
||||
|
||||
281
Source/ArachnaeSwarm/Verbs/Verb_ShootSelfUnderfoot.cs
Normal file
281
Source/ArachnaeSwarm/Verbs/Verb_ShootSelfUnderfoot.cs
Normal file
@@ -0,0 +1,281 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class Verb_ShootSelfUnderfoot : Verb_LaunchProjectile
|
||||
{
|
||||
// 重写ShotsPerBurst,与Verb_Shoot相同
|
||||
protected override int ShotsPerBurst => base.BurstShotCount;
|
||||
|
||||
// 重写WarmupComplete,添加射击技能学习
|
||||
public override void WarmupComplete()
|
||||
{
|
||||
base.WarmupComplete();
|
||||
|
||||
// 只有在目标是Pawn时才学习射击技能
|
||||
if (currentTarget.Thing is Pawn targetPawn &&
|
||||
!targetPawn.Downed &&
|
||||
!targetPawn.IsColonyMech &&
|
||||
CasterIsPawn &&
|
||||
CasterPawn.skills != null)
|
||||
{
|
||||
float xp = targetPawn.HostileTo(caster) ? 170f : 20f;
|
||||
float num2 = verbProps.AdjustedFullCycleTime(this, CasterPawn);
|
||||
CasterPawn.skills.Learn(SkillDefOf.Shooting, xp * num2);
|
||||
}
|
||||
}
|
||||
|
||||
// 核心重写:将目标改为脚下
|
||||
protected override bool TryCastShot()
|
||||
{
|
||||
// 保存原始目标
|
||||
LocalTargetInfo originalTarget = currentTarget;
|
||||
|
||||
try
|
||||
{
|
||||
// 将目标改为施法者自己的位置
|
||||
currentTarget = new LocalTargetInfo(caster.Position);
|
||||
|
||||
// 调用基类方法,但使用修改后的目标(脚下)
|
||||
bool result = base.TryCastShot();
|
||||
|
||||
// 如果成功发射,记录射击次数
|
||||
if (result && CasterIsPawn)
|
||||
{
|
||||
CasterPawn.records.Increment(RecordDefOf.ShotsFired);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 恢复原始目标(对于连续射击可能重要)
|
||||
currentTarget = originalTarget;
|
||||
}
|
||||
}
|
||||
|
||||
// 重写CanHitTarget,因为目标是脚下,总是可以命中
|
||||
public override bool CanHitTarget(LocalTargetInfo targ)
|
||||
{
|
||||
// 对于向脚下射击,我们总是允许(只要施法者存在)
|
||||
if (caster == null || !caster.Spawned)
|
||||
return false;
|
||||
|
||||
// 如果目标就是施法者自己,允许
|
||||
if (targ == caster)
|
||||
return true;
|
||||
|
||||
// 对于其他目标,使用默认逻辑
|
||||
return base.CanHitTarget(targ);
|
||||
}
|
||||
|
||||
// 重写CanHitTargetFrom,对于脚下射击总是返回true
|
||||
public override bool CanHitTargetFrom(IntVec3 root, LocalTargetInfo targ)
|
||||
{
|
||||
// 如果目标是施法者自己(或脚下),总是可以命中
|
||||
if (targ.Thing == caster || (targ.IsValid && targ.Cell == caster.Position))
|
||||
return true;
|
||||
|
||||
return base.CanHitTargetFrom(root, targ);
|
||||
}
|
||||
|
||||
// 重写TryFindShootLineFromTo,对于脚下射击简化逻辑
|
||||
public new bool TryFindShootLineFromTo(IntVec3 root, LocalTargetInfo targ, out ShootLine resultingLine, bool ignoreRange = false)
|
||||
{
|
||||
// 如果目标是脚下,直接返回射击线
|
||||
if (targ.IsValid && targ.Cell == caster.Position)
|
||||
{
|
||||
resultingLine = new ShootLine(root, targ.Cell);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 否则使用基类逻辑
|
||||
return base.TryFindShootLineFromTo(root, targ, out resultingLine, ignoreRange);
|
||||
}
|
||||
|
||||
// 重写DrawHighlight,简化高亮显示
|
||||
public override void DrawHighlight(LocalTargetInfo target)
|
||||
{
|
||||
// 绘制标准射程环
|
||||
verbProps.DrawRadiusRing(caster.Position, this);
|
||||
|
||||
// 如果目标是有效的,绘制目标高亮
|
||||
if (target.IsValid)
|
||||
{
|
||||
GenDraw.DrawTargetHighlight(target);
|
||||
|
||||
// 绘制目标周围的场半径
|
||||
DrawHighlightFieldRadiusAroundTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助方法:绘制彩色目标高亮
|
||||
private void GenDraw_DrawTargetHighlightWithColor(LocalTargetInfo target, Color color)
|
||||
{
|
||||
GenDraw.DrawTargetHighlight(target);
|
||||
}
|
||||
|
||||
// 重写OnGUI,显示自定义鼠标图标
|
||||
public override void OnGUI(LocalTargetInfo target)
|
||||
{
|
||||
// 使用自定义图标或默认攻击图标
|
||||
Texture2D icon;
|
||||
if (!target.IsValid)
|
||||
{
|
||||
icon = TexCommand.CannotShoot;
|
||||
}
|
||||
else if (target.Cell == caster.Position)
|
||||
{
|
||||
// 可以使用自定义图标,这里使用攻击图标
|
||||
icon = TexCommand.Attack;
|
||||
}
|
||||
else
|
||||
{
|
||||
icon = (UIIcon != BaseContent.BadTex) ? UIIcon : TexCommand.Attack;
|
||||
}
|
||||
|
||||
GenUI.DrawMouseAttachment(icon);
|
||||
}
|
||||
|
||||
// 重写ValidateTarget,允许向自己脚下射击
|
||||
public override bool ValidateTarget(LocalTargetInfo target, bool showMessages = true)
|
||||
{
|
||||
// 如果目标是脚下,总是允许
|
||||
if (target.IsValid && target.Cell == caster.Position)
|
||||
return true;
|
||||
|
||||
// 否则使用基类验证逻辑
|
||||
return base.ValidateTarget(target, showMessages);
|
||||
}
|
||||
|
||||
// 重写Available,确保有抛射体,并允许在近战状态下使用
|
||||
public override bool Available()
|
||||
{
|
||||
// 首先调用基类检查
|
||||
if (!base.Available())
|
||||
return false;
|
||||
|
||||
// 检查是否有抛射体
|
||||
if (Projectile == null)
|
||||
return false;
|
||||
|
||||
// 特殊处理:允许在近战威胁下使用
|
||||
if (CasterIsPawn && CasterPawn.mindState != null && CasterPawn.mindState.MeleeThreatStillThreat)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 重写OrderForceTarget,允许在近战距离内强制使用
|
||||
public override void OrderForceTarget(LocalTargetInfo target)
|
||||
{
|
||||
// 如果是近战攻击,使用近战逻辑
|
||||
if (verbProps.IsMeleeAttack)
|
||||
{
|
||||
Job job = JobMaker.MakeJob(JobDefOf.AttackMelee, target);
|
||||
job.playerForced = true;
|
||||
if (target.Thing is Pawn pawn)
|
||||
{
|
||||
job.killIncappedTarget = pawn.Downed;
|
||||
}
|
||||
CasterPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否在近战范围内,但允许向脚下射击
|
||||
float minRange = verbProps.EffectiveMinRange(target, CasterPawn);
|
||||
if (CasterIsPawn &&
|
||||
(float)CasterPawn.Position.DistanceToSquared(target.Cell) < minRange * minRange &&
|
||||
CasterPawn.Position.AdjacentTo8WayOrInside(target.Cell))
|
||||
{
|
||||
// 如果是向脚下射击,允许
|
||||
if (target.IsValid && target.Cell == CasterPawn.Position)
|
||||
{
|
||||
// 允许向脚下射击
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages.Message("MessageCantShootInMelee".Translate(), CasterPawn, MessageTypeDefOf.RejectInput, historical: false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建射击工作
|
||||
Job job2 = JobMaker.MakeJob(verbProps.ai_IsWeapon ? JobDefOf.AttackStatic : JobDefOf.UseVerbOnThing);
|
||||
job2.verbToUse = this;
|
||||
job2.targetA = target;
|
||||
job2.endIfCantShootInMelee = false; // 设置为false,允许在近战中射击
|
||||
CasterPawn.jobs.TryTakeOrderedJob(job2, JobTag.Misc);
|
||||
}
|
||||
|
||||
// 重写TryStartCastOn,允许在近战状态下开始射击
|
||||
public override bool TryStartCastOn(LocalTargetInfo castTarg, LocalTargetInfo destTarg, bool surpriseAttack = false, bool canHitNonTargetPawns = true, bool preventFriendlyFire = false, bool nonInterruptingSelfCast = false)
|
||||
{
|
||||
// 调用基类方法,但设置一个标志表示这是向脚下射击
|
||||
bool isShootingUnderfoot = castTarg.IsValid && castTarg.Cell == caster.Position;
|
||||
|
||||
// 如果是向脚下射击,临时修改一些属性以允许近战射击
|
||||
if (isShootingUnderfoot && CasterIsPawn && CasterPawn.mindState != null && CasterPawn.mindState.MeleeThreatStillThreat)
|
||||
{
|
||||
// 临时忽略近战威胁检查
|
||||
bool originalAIProjectileLaunchingIgnoresMeleeThreats = verbProps.ai_ProjectileLaunchingIgnoresMeleeThreats;
|
||||
verbProps.ai_ProjectileLaunchingIgnoresMeleeThreats = true;
|
||||
|
||||
try
|
||||
{
|
||||
return base.TryStartCastOn(castTarg, destTarg, surpriseAttack, canHitNonTargetPawns, preventFriendlyFire, nonInterruptingSelfCast);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 恢复原始值
|
||||
verbProps.ai_ProjectileLaunchingIgnoresMeleeThreats = originalAIProjectileLaunchingIgnoresMeleeThreats;
|
||||
}
|
||||
}
|
||||
|
||||
return base.TryStartCastOn(castTarg, destTarg, surpriseAttack, canHitNonTargetPawns, preventFriendlyFire, nonInterruptingSelfCast);
|
||||
}
|
||||
|
||||
// 添加一个方法,检查是否在近战状态下
|
||||
public bool IsInMeleeCombat()
|
||||
{
|
||||
if (!CasterIsPawn)
|
||||
return false;
|
||||
|
||||
return CasterPawn.mindState?.MeleeThreatStillThreat == true;
|
||||
}
|
||||
|
||||
// 重写BurstingTick,在近战状态下也继续射击
|
||||
public override void BurstingTick()
|
||||
{
|
||||
base.BurstingTick();
|
||||
|
||||
// 在近战状态下也继续射击逻辑
|
||||
if (IsInMeleeCombat() && state == VerbState.Bursting)
|
||||
{
|
||||
// 可以在这里添加近战状态下的特殊效果
|
||||
}
|
||||
}
|
||||
|
||||
// 添加自定义属性,用于控制是否总是向脚下发射
|
||||
private bool alwaysShootUnderfoot = true;
|
||||
|
||||
public bool AlwaysShootUnderfoot
|
||||
{
|
||||
get => alwaysShootUnderfoot;
|
||||
set => alwaysShootUnderfoot = value;
|
||||
}
|
||||
|
||||
// 添加一个方法,允许临时关闭向脚下射击
|
||||
public void SetShootUnderfoot(bool shootUnderfoot)
|
||||
{
|
||||
alwaysShootUnderfoot = shootUnderfoot;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user