三大系统

This commit is contained in:
Tourswen
2025-12-16 02:13:18 +08:00
parent 15404222fd
commit b17407b53f
55 changed files with 4892 additions and 644 deletions

View File

@@ -1,58 +1,58 @@
{
"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\\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\\building_equipmentootheca.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_equipmentootheca\\building_equipmentootheca.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|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_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\\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\\thing_comps\\ara_compextraincubationinfo\\compproperties_extraincubationinfo.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing_comps\\ara_compextraincubationinfo\\compproperties_extraincubationinfo.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\\thing_comps\\ara_compextraincubationinfo\\compextraincubationinfo.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing_comps\\ara_compextraincubationinfo\\compextraincubationinfo.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|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\placeworker\\compproperties_customradius.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:placeworker\\compproperties_customradius.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_swarmmaintenance\\comp_swarmmaintenance.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_swarmmaintenance\\comp_swarmmaintenance.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\\flyover\\ara_groundstrafing\\compgroundstrafing.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_groundstrafing\\compgroundstrafing.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_buildingterrainspawn\\compdelayedterrainspawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_buildingterrainspawn\\compdelayedterrainspawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\flyover\\ara_sectorsurveillance\\compsectorsurveillance.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_sectorsurveillance\\compsectorsurveillance.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\\comptemperatureruinabledamage.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\comptemperatureruinabledamage.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\\flyover\\thingclassflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\thingclassflyover.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\\flyover\\ara_aircrafthangar\\compabilityeffect_aircraftstrike.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_aircrafthangar\\compabilityeffect_aircraftstrike.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\abilities\\ara_givehediffwithskillduration\\compabilityeffect_givehediffwithskillduration.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_givehediffwithskillduration\\compabilityeffect_givehediffwithskillduration.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\abilities\\ara_showtemperaturerange\\compabilityeffect_abilityshowtemperaturerange.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\\abilities\\ara_showtemperaturerange\\compabilityeffect_abilityshowtemperaturerange.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_showtemperaturerange\\compabilityeffect_abilityshowtemperaturerange.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\abilities\\ara_showspawnablepawnslist\\compabilityeffect_abilityshowspawnablepawns.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_showspawnablepawnslist\\compabilityeffect_abilityshowspawnablepawns.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_swarmmaintain\\jobdriver_swarmmaintain.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:jobs\\jobdriver_swarmmaintain\\jobdriver_swarmmaintain.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\\flyover\\ara_spawnflyover\\compabilityeffect_spawnflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_spawnflyover\\compabilityeffect_spawnflyover.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\\flyover\\ara_aircrafthangar\\compaircrafthangar.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_aircrafthangar\\compaircrafthangar.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\\flyover\\ara_aircrafthangar\\worldcomponent_aircraftmanager.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_aircrafthangar\\worldcomponent_aircraftmanager.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\\flyover\\ara_flyoverescort\\compproperties_flyoverescort.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_flyoverescort\\compproperties_flyoverescort.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_feedwithhoney\\jobdriver_feedwithhoney.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:jobs\\jobdriver_feedwithhoney\\jobdriver_feedwithhoney.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}
],
"DocumentGroupContainers": [
@@ -62,21 +62,8 @@
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 0,
"SelectedChildIndex": 3,
"Children": [
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "Building_Ootheca.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Building_Ootheca.cs",
"RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\Building_Ootheca.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Building_Ootheca.cs",
"RelativeToolTip": "Buildings\\Building_Ootheca\\Building_Ootheca.cs",
"ViewState": "AgIAAK8CAAAAAAAAAAA9wPACAAAMAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-12-15T07:28:18.272Z",
"EditorCaption": ""
},
{
"$type": "Bookmark",
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
@@ -84,147 +71,171 @@
{
"$type": "Document",
"DocumentIndex": 1,
"Title": "CompProperties_CustomRadius.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Placeworker\\CompProperties_CustomRadius.cs",
"RelativeDocumentMoniker": "Placeworker\\CompProperties_CustomRadius.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Placeworker\\CompProperties_CustomRadius.cs",
"RelativeToolTip": "Placeworker\\CompProperties_CustomRadius.cs",
"ViewState": "AgIAAAIAAAAAAAAAAAAAACMAAAA/AAAAAAAAAA==",
"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": "AgIAAAQAAAAAAAAAAAAmwDMBAAAlAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-12-15T07:21:04.756Z",
"WhenOpened": "2025-12-15T17:55:40.041Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 3,
"Title": "CompSectorSurveillance.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_SectorSurveillance\\CompSectorSurveillance.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_SectorSurveillance\\CompSectorSurveillance.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_SectorSurveillance\\CompSectorSurveillance.cs",
"RelativeToolTip": "Flyover\\ARA_SectorSurveillance\\CompSectorSurveillance.cs",
"ViewState": "AgIAAPACAAAAAAAAAAAAABEDAAAAAAAAAAAAAA==",
"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": "AgIAAAAAAAAAAAAAAADwvxgAAAAdAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-30T13:52:54.896Z"
"WhenOpened": "2025-12-15T17:32:18.493Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 2,
"Title": "CompGroundStrafing.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_GroundStrafing\\CompGroundStrafing.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_GroundStrafing\\CompGroundStrafing.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_GroundStrafing\\CompGroundStrafing.cs",
"RelativeToolTip": "Flyover\\ARA_GroundStrafing\\CompGroundStrafing.cs",
"ViewState": "AgIAAGwBAAAAAAAAAAArwIYBAAAFAAAAAAAAAA==",
"DocumentIndex": 0,
"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": "AgIAADIBAAAAAAAAAAAAAEQBAAAIAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-30T13:00:11.18Z"
},
{
"$type": "Document",
"DocumentIndex": 6,
"Title": "CompAbilityEffect_GiveHediffWithSkillDuration.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_GiveHediffWithSkillDuration\\CompAbilityEffect_GiveHediffWithSkillDuration.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_GiveHediffWithSkillDuration\\CompAbilityEffect_GiveHediffWithSkillDuration.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_GiveHediffWithSkillDuration\\CompAbilityEffect_GiveHediffWithSkillDuration.cs",
"RelativeToolTip": "Abilities\\ARA_GiveHediffWithSkillDuration\\CompAbilityEffect_GiveHediffWithSkillDuration.cs",
"ViewState": "AgIAAEsAAAAAAAAAAAAWwGAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-29T14:40:47.422Z"
},
{
"$type": "Document",
"DocumentIndex": 7,
"Title": "CompAbilityEffect_AbilityShowTemperatureRange.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs",
"RelativeToolTip": "Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAABcAAAArAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-29T14:40:43.525Z"
"WhenOpened": "2025-12-15T17:32:08.87Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 5,
"Title": "CompAbilityEffect_AircraftStrike.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\CompAbilityEffect_AircraftStrike.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_AircraftHangar\\CompAbilityEffect_AircraftStrike.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\CompAbilityEffect_AircraftStrike.cs",
"RelativeToolTip": "Flyover\\ARA_AircraftHangar\\CompAbilityEffect_AircraftStrike.cs",
"ViewState": "AgIAAHYAAAAAAAAAAAAtwJcAAAArAAAAAAAAAA==",
"Title": "CompExtraIncubationInfo.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_CompExtraIncubationInfo\\CompExtraIncubationInfo.cs",
"RelativeDocumentMoniker": "Thing_Comps\\ARA_CompExtraIncubationInfo\\CompExtraIncubationInfo.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_CompExtraIncubationInfo\\CompExtraIncubationInfo.cs",
"RelativeToolTip": "Thing_Comps\\ARA_CompExtraIncubationInfo\\CompExtraIncubationInfo.cs",
"ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-29T11:22:34.783Z"
"WhenOpened": "2025-12-15T17:13:20.87Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 4,
"Title": "ThingclassFlyOver.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ThingclassFlyOver.cs",
"RelativeDocumentMoniker": "Flyover\\ThingclassFlyOver.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ThingclassFlyOver.cs",
"RelativeToolTip": "Flyover\\ThingclassFlyOver.cs",
"ViewState": "AgIAAIkCAAAAAAAAAAAawI8CAAANAAAAAAAAAA==",
"Title": "CompProperties_ExtraIncubationInfo.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_CompExtraIncubationInfo\\CompProperties_ExtraIncubationInfo.cs",
"RelativeDocumentMoniker": "Thing_Comps\\ARA_CompExtraIncubationInfo\\CompProperties_ExtraIncubationInfo.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_CompExtraIncubationInfo\\CompProperties_ExtraIncubationInfo.cs",
"RelativeToolTip": "Thing_Comps\\ARA_CompExtraIncubationInfo\\CompProperties_ExtraIncubationInfo.cs",
"ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-29T14:17:06.867Z"
"WhenOpened": "2025-12-15T17:13:20.069Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 2,
"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": "AgIAAAAAAAAAAAAAAADwvxIAAAA4AAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-12-15T17:11:54.114Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 6,
"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": "AgIAAA0DAAAAAAAAAAAAACAAAAArAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-12-15T17:10:05.509Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 7,
"Title": "Comp_SwarmMaintenance.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_SwarmMaintenance\\Comp_SwarmMaintenance.cs",
"RelativeDocumentMoniker": "Building_Comps\\ARA_SwarmMaintenance\\Comp_SwarmMaintenance.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_SwarmMaintenance\\Comp_SwarmMaintenance.cs",
"RelativeToolTip": "Building_Comps\\ARA_SwarmMaintenance\\Comp_SwarmMaintenance.cs",
"ViewState": "AgIAABkBAAAAAAAAAAAAAE0BAABIAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-12-15T16:59:28.717Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 8,
"Title": "CompAbilityEffect_AbilityShowSpawnablePawns.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowSpawnablePawnsList\\CompAbilityEffect_AbilityShowSpawnablePawns.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_ShowSpawnablePawnsList\\CompAbilityEffect_AbilityShowSpawnablePawns.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowSpawnablePawnsList\\CompAbilityEffect_AbilityShowSpawnablePawns.cs",
"RelativeToolTip": "Abilities\\ARA_ShowSpawnablePawnsList\\CompAbilityEffect_AbilityShowSpawnablePawns.cs",
"ViewState": "AgIAABYAAAAAAAAAAAAuwBYAAAArAAAAAAAAAA==",
"Title": "CompDelayedTerrainSpawn.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs",
"RelativeDocumentMoniker": "Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs",
"RelativeToolTip": "Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs",
"ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-29T14:40:40.237Z"
},
{
"$type": "Document",
"DocumentIndex": 10,
"Title": "CompAircraftHangar.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\CompAircraftHangar.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_AircraftHangar\\CompAircraftHangar.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\CompAircraftHangar.cs",
"RelativeToolTip": "Flyover\\ARA_AircraftHangar\\CompAircraftHangar.cs",
"ViewState": "AgIAABcAAAAAAAAAAAAQwCQAAAAnAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-29T11:39:22.563Z"
"WhenOpened": "2025-12-15T16:59:21.314Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 9,
"Title": "CompAbilityEffect_SpawnFlyOver.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_SpawnFlyOver\\CompAbilityEffect_SpawnFlyOver.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_SpawnFlyOver\\CompAbilityEffect_SpawnFlyOver.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_SpawnFlyOver\\CompAbilityEffect_SpawnFlyOver.cs",
"RelativeToolTip": "Flyover\\ARA_SpawnFlyOver\\CompAbilityEffect_SpawnFlyOver.cs",
"ViewState": "AgIAAFMDAAAAAAAAAAAawG8DAAAAAAAAAAAAAA==",
"Title": "CompTemperatureRuinableDamage.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\CompTemperatureRuinableDamage.cs",
"RelativeDocumentMoniker": "Building_Comps\\CompTemperatureRuinableDamage.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\CompTemperatureRuinableDamage.cs",
"RelativeToolTip": "Building_Comps\\CompTemperatureRuinableDamage.cs",
"ViewState": "AgIAAB8AAAAAAAAAAAAAwDoAAAAlAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-29T13:37:35.758Z"
"WhenOpened": "2025-12-15T16:52:05.743Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 10,
"Title": "CompAbilityEffect_AbilityShowTemperatureRange.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs",
"RelativeToolTip": "Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs",
"ViewState": "AgIAAHUAAAAAAAAAAAAAAJIAAABEAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-12-15T16:52:00.971Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 11,
"Title": "WorldComponent_AircraftManager.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\WorldComponent_AircraftManager.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_AircraftHangar\\WorldComponent_AircraftManager.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\WorldComponent_AircraftManager.cs",
"RelativeToolTip": "Flyover\\ARA_AircraftHangar\\WorldComponent_AircraftManager.cs",
"ViewState": "AgIAAJUAAAAAAAAAAAAowK4AAAAUAAAAAAAAAA==",
"Title": "JobDriver_SwarmMaintain.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_SwarmMaintain\\JobDriver_SwarmMaintain.cs",
"RelativeDocumentMoniker": "Jobs\\JobDriver_SwarmMaintain\\JobDriver_SwarmMaintain.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_SwarmMaintain\\JobDriver_SwarmMaintain.cs",
"RelativeToolTip": "Jobs\\JobDriver_SwarmMaintain\\JobDriver_SwarmMaintain.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAB0AAABPAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-29T14:05:39.817Z"
"WhenOpened": "2025-12-15T16:36:34.047Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 12,
"Title": "CompProperties_FlyOverEscort.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
"RelativeToolTip": "Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAABAAAAAvAAAAAAAAAA==",
"Title": "JobDriver_FeedWithHoney.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_FeedWithHoney\\JobDriver_FeedWithHoney.cs",
"RelativeDocumentMoniker": "Jobs\\JobDriver_FeedWithHoney\\JobDriver_FeedWithHoney.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_FeedWithHoney\\JobDriver_FeedWithHoney.cs",
"RelativeToolTip": "Jobs\\JobDriver_FeedWithHoney\\JobDriver_FeedWithHoney.cs",
"ViewState": "AgIAACEAAAAAAAAAAAAYwEsAAAAOAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-29T12:59:07.753Z"
"WhenOpened": "2025-12-15T16:35:50.511Z",
"EditorCaption": ""
}
]
}

View File

@@ -19,10 +19,22 @@ namespace ArachnaeSwarm
public static class ARA_JobDefOf
{
public static JobDef ARA_OperateIncubator;
public static JobDef ARA_OperateEquipmentIncubator;
public static JobDef ARA_SwarmMaintain;
static ARA_JobDefOf()
{
DefOfHelper.EnsureInitializedInCtor(typeof(ARA_JobDefOf));
}
}
[DefOf]
public static class ARA_EffecterDefOf
{
public static EffecterDef EatVegetarian;
static ARA_EffecterDefOf()
{
DefOfHelper.EnsureInitializedInCtor(typeof(ARA_EffecterDefOf));
}
}
}

View File

@@ -114,11 +114,16 @@
<Compile Include="Abilities\CompAbilityEffect_RandomHediff.cs" />
<Compile Include="Abilities\CompAbilityEffect_TransformCorpse.cs" />
<Compile Include="Buildings\Building_ArachnaeGravEngine.cs" />
<Compile Include="Buildings\Building_EquipmentOotheca\Building_EquipmentOotheca.cs" />
<Compile Include="Buildings\Building_EquipmentOotheca\CompProperties_EquipmentIncubatorData.cs" />
<Compile Include="Buildings\Building_EquipmentOotheca\ITab_EquipmentOotheca_Incubation.cs" />
<Compile Include="Buildings\Building_EquipmentOotheca\JobDriver_OperateEquipmentIncubator.cs" />
<Compile Include="Buildings\Building_Incubatable.cs" />
<Compile Include="Buildings\Building_Ootheca\Building_Ootheca.cs" />
<Compile Include="Buildings\Building_Ootheca\CompProperties_IncubatorData.cs" />
<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="Buildings\Building_TurretGunHasSpeed.cs" />
<Compile Include="Building_Comps\ARA_Building_RefuelingVat\Building_RefuelingVat.cs" />
@@ -131,6 +136,8 @@
<Compile Include="Building_Comps\ARA_ForwardClearance\PlaceWorker_ForwardClearance.cs" />
<Compile Include="Building_Comps\ARA_ProductStorage\CompProductStorage.cs" />
<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\CompBreakdownDisabler.cs" />
<Compile Include="EventSystem\CompOpenCustomUI.cs" />
<Compile Include="EventSystem\Condition.cs" />
@@ -164,7 +171,18 @@
<Compile Include="Hediffs\ARA_HediffTerrainSpawn\CompProperties_HediffTerrainSpawn.cs" />
<Compile Include="Hediffs\HediffComp_LifespanDisplay.cs" />
<Compile Include="Jobs\JobDriver_CarryPrisonerToRefuelingVat.cs" />
<Compile Include="Jobs\JobDriver_FeedWithHoney\JobDriver_ExtractHoney.cs" />
<Compile Include="Jobs\JobDriver_FeedWithHoney\JobDriver_FeedWithHoney.cs" />
<Compile Include="Jobs\JobDriver_FeedWithHoney\ThinkNode_JobGiver_ExtractHoney.cs" />
<Compile Include="Jobs\JobDriver_FeedWithHoney\ThinkNode_JobGiver_FeedWithHoney.cs" />
<Compile Include="Jobs\JobDriver_StripChitin\CompProperties_ChitinStripping.cs" />
<Compile Include="Jobs\JobDriver_StripChitin\Comp_ChitinStripping.cs" />
<Compile Include="Jobs\JobDriver_StripChitin\JobDriver_StripChitin.cs" />
<Compile Include="Jobs\JobDriver_StripChitin\ThinkNode_JobGiver_StripChitin.cs" />
<Compile Include="Jobs\JobDriver_SwarmMaintain\JobDriver_SwarmMaintain.cs" />
<Compile Include="MentalState\MentalState_HiveMindCascade.cs" />
<Compile Include="Needs\Need_ChitinArmor.cs" />
<Compile Include="Needs\Need_HoneyProduction.cs" />
<Compile Include="Pawn_Comps\ARA_UniquePawn\CompProperties_UniquePawn.cs" />
<Compile Include="Pawn_Comps\ARA_UniquePawn\CompUniquePawn.cs" />
<Compile Include="Pawn_Comps\ARA_UniquePawn\Patch_UniquePawn.cs" />
@@ -196,7 +214,7 @@
<Compile Include="Building_Comps\ARA_CompInteractiveProducer\CompInteractiveProducer.cs" />
<Compile Include="Building_Comps\ARA_CompInteractiveProducer\CompQueuedInteractiveProducer.cs" />
<Compile Include="Building_Comps\CompRefuelableNutrition.cs" />
<Compile Include="Building_Comps\ARA_CompInteractiveProducer\CompTemperatureRuinableDamage.cs" />
<Compile Include="Building_Comps\CompTemperatureRuinableDamage.cs" />
<Compile Include="Building_Comps\ARA_CompInteractiveProducer\DataContracts.cs" />
<Compile Include="Building_Comps\ARA_CompInteractiveProducer\JobDriver_AddProcessToQueue.cs" />
<Compile Include="Building_Comps\ARA_CompInteractiveProducer\JobDriver_StartProduction.cs" />

View File

@@ -1,91 +0,0 @@
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_TemperatureRuinableDamage : CompProperties
{
public float minSafeTemperature;
public float maxSafeTemperature = 100f;
public float progressPerDegreePerTick = 1E-05f; // 修改参数名以匹配标准调用方式
public float damagePerTick = 1f; // 每tick造成的伤害值
public float recoveryRate = 0.001f; // 温度恢复正常时的恢复速率
public CompProperties_TemperatureRuinableDamage()
{
compClass = typeof(CompTemperatureRuinableDamage);
}
}
public class CompTemperatureRuinableDamage : ThingComp
{
private float ruinedPercent; // 修改变量名以匹配标准
private bool isRuined; // 修改变量名以匹配标准
public CompProperties_TemperatureRuinableDamage Props => (CompProperties_TemperatureRuinableDamage)props;
public override void CompTick()
{
base.CompTick();
if (parent.AmbientTemperature < Props.minSafeTemperature || parent.AmbientTemperature > Props.maxSafeTemperature)
{
float tempDelta = 0f;
if (parent.AmbientTemperature < Props.minSafeTemperature)
{
tempDelta = Props.minSafeTemperature - parent.AmbientTemperature;
}
else if (parent.AmbientTemperature > Props.maxSafeTemperature)
{
tempDelta = parent.AmbientTemperature - Props.maxSafeTemperature;
}
// 累积损坏进度
ruinedPercent += tempDelta * Props.progressPerDegreePerTick;
// 只有在已损坏的情况下才每tick造成持续伤害
if (isRuined)
{
parent.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, Props.damagePerTick));
}
// 标记为已受损
isRuined = true;
}
else
{
// 当温度恢复正常时,逐渐减少损坏进度而不是重置
if (isRuined && ruinedPercent > 0f)
{
ruinedPercent -= Props.recoveryRate;
if (ruinedPercent <= 0f)
{
ruinedPercent = 0f;
isRuined = false;
}
}
// 即使温度正常,如果已损坏也要继续造成伤害直到恢复
if (isRuined)
{
parent.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, Props.damagePerTick));
}
}
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref ruinedPercent, "ruinedPercent", 0f);
Scribe_Values.Look(ref isRuined, "isRuined", false);
}
public override string CompInspectStringExtra()
{
if (ruinedPercent > 0f)
{
return "CocoonRuinedByTemperature".Translate() + ": " + ruinedPercent.ToStringPercent();
}
return base.CompInspectStringExtra();
}
}
}

View File

@@ -0,0 +1,36 @@
// File: Comps/CompProperties_SwarmMaintenance.cs
using RimWorld;
using System.Collections.Generic;
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_SwarmMaintenance : CompProperties
{
// 最大维护度
public float maxMaintenance = 100f;
// 每天下降的维护度
public float maintenanceDecayPerDay = 10f;
// 维护度为0时每秒受到的伤害
public float damagePerSecondWhenEmpty = 1f;
// 维护度达到多少时开始显示警告(百分比)
public float warningThreshold = 0.3f;
// 可维护的种族列表
public List<ThingDef> allowedRaces;
// 检查分配工作的间隔ticks
public int assignJobCheckInterval = 600; // 10秒
// 维护度低于多少时开始寻找维护者(百分比)
public float maintenanceThresholdForJob = 0.9f;
public CompProperties_SwarmMaintenance()
{
compClass = typeof(Comp_SwarmMaintenance);
}
}
}

View File

@@ -0,0 +1,399 @@
// File: Comps/Comp_SwarmMaintenance.cs
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
public class Comp_SwarmMaintenance : ThingComp
{
// 属性访问器
public CompProperties_SwarmMaintenance Props => (CompProperties_SwarmMaintenance)props;
// 当前维护度
private float currentMaintenance;
// 上次更新维护度的时间
private int lastMaintenanceTick = -99999;
// 上次检查分配工作的时间
private int lastJobCheckTick = -99999;
// 当维护度为0时每秒受到的伤害计时器
private int nextDamageTick = -99999;
// 是否正在寻找维护者
private bool seekingMaintainer = false;
// 找不到维护者的提示信息
private string noMaintainerWarning = "";
private int lastWarningTick = -99999;
private const int WarningDuration = 2500; // 警告显示41.67秒
// 属性访问
public float CurrentMaintenance => currentMaintenance;
public float MaxMaintenance => Props.maxMaintenance;
public float MaintenancePercentage => currentMaintenance / MaxMaintenance;
public bool NeedsMaintenance => currentMaintenance < MaxMaintenance * Props.maintenanceThresholdForJob;
public bool IsCritical => currentMaintenance <= MaxMaintenance * Props.warningThreshold;
public bool IsEmpty => currentMaintenance <= 0f;
// 初始化
public override void Initialize(CompProperties props)
{
base.Initialize(props);
currentMaintenance = Props.maxMaintenance; // 初始时满维护度
lastMaintenanceTick = Find.TickManager.TicksGame;
lastJobCheckTick = Find.TickManager.TicksGame;
}
// 序列化
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref currentMaintenance, "currentMaintenance", Props.maxMaintenance);
Scribe_Values.Look(ref lastMaintenanceTick, "lastMaintenanceTick", -99999);
Scribe_Values.Look(ref lastJobCheckTick, "lastJobCheckTick", -99999);
Scribe_Values.Look(ref nextDamageTick, "nextDamageTick", -99999);
Scribe_Values.Look(ref seekingMaintainer, "seekingMaintainer", false);
Scribe_Values.Look(ref noMaintainerWarning, "noMaintainerWarning", "");
Scribe_Values.Look(ref lastWarningTick, "lastWarningTick", -99999);
}
// 每tick更新
public override void CompTick()
{
base.CompTick();
int currentTick = Find.TickManager.TicksGame;
// 每2500tick约41.67秒)更新一次维护度递减
if (currentTick - lastMaintenanceTick >= 2500)
{
UpdateMaintenanceDecay();
lastMaintenanceTick = currentTick;
}
// 定期检查是否需要分配维护工作
if (currentTick - lastJobCheckTick >= Props.assignJobCheckInterval)
{
CheckAndAssignMaintenanceJob();
lastJobCheckTick = currentTick;
}
// 如果维护度为0每秒造成伤害
if (currentMaintenance <= 0f && currentTick >= nextDamageTick)
{
ApplyDamageWhenEmpty();
nextDamageTick = currentTick + 60; // 60ticks = 1秒
}
// 更新警告信息显示时间
if (currentTick - lastWarningTick >= WarningDuration)
{
noMaintainerWarning = "";
}
}
// 更新维护度递减
private void UpdateMaintenanceDecay()
{
if (parent == null || parent.Map == null)
return;
// 计算递减量每天递减量转换为每2500tick约0.347天)的递减量
float decayAmount = Props.maintenanceDecayPerDay * (2500f / 60000f);
currentMaintenance -= decayAmount;
// 确保不低于0
if (currentMaintenance < 0f)
currentMaintenance = 0f;
}
// 当维护度为0时应用伤害
private void ApplyDamageWhenEmpty()
{
if (parent == null || parent.Destroyed)
return;
// 每秒造成伤害
float damageAmount = Props.damagePerSecondWhenEmpty;
// 应用伤害到建筑
parent.TakeDamage(new DamageInfo(
DamageDefOf.Deterioration,
damageAmount,
armorPenetration: 999f, // 高穿甲,确保能造成伤害
instigator: null
));
}
// 检查并分配维护工作
private void CheckAndAssignMaintenanceJob()
{
if (parent == null || parent.Map == null || parent.Faction == null)
return;
// 如果不需要维护,重置状态
if (!NeedsMaintenance)
{
seekingMaintainer = false;
return;
}
// 如果正在寻找维护者,尝试分配工作
if (!seekingMaintainer)
{
seekingMaintainer = true;
}
// 尝试寻找符合条件的Pawn
Pawn maintainer = FindAvailableMaintainer();
if (maintainer != null)
{
// 分配维护工作
AssignMaintenanceJobTo(maintainer);
seekingMaintainer = false;
noMaintainerWarning = "";
}
else
{
// 记录找不到维护者的警告
if (noMaintainerWarning == "")
{
noMaintainerWarning = "ARA_SwarmMaintenance.NoMaintainerFound".Translate();
lastWarningTick = Find.TickManager.TicksGame;
}
}
}
// 寻找可用的维护者
private Pawn FindAvailableMaintainer()
{
if (parent.Map == null || parent.Faction == null)
return null;
// 查找地图中所有属于玩家阵营的Pawn
List<Pawn> allPawns = parent.Map.mapPawns.SpawnedPawnsInFaction(parent.Faction);
foreach (Pawn pawn in allPawns)
{
if (CanPawnMaintain(pawn))
{
return pawn;
}
}
return null;
}
// 判断Pawn是否可以维护
private bool CanPawnMaintain(Pawn pawn)
{
if (pawn == null || pawn.Dead || pawn.Downed || pawn.InMentalState)
return false;
// 检查种族是否在允许列表中
if (Props.allowedRaces != null && Props.allowedRaces.Count > 0)
{
if (!Props.allowedRaces.Contains(pawn.def))
return false;
}
// 检查Pawn是否处于GotoWander或Wait_Wander状态
if (!IsPawnWandering(pawn))
return false;
// 检查Pawn是否已经有维护工作
if (HasMaintenanceJob(pawn))
return false;
return true;
}
// 检查Pawn是否处于漫游状态
private bool IsPawnWandering(Pawn pawn)
{
if (pawn.jobs == null || pawn.jobs.curJob == null)
return false;
Job curJob = pawn.jobs.curJob;
// 检查是否是漫游工作
if (curJob.def == JobDefOf.GotoWander || curJob.def == JobDefOf.Wait_Wander)
{
return true;
}
return false;
}
// 检查Pawn是否已经有维护工作
private bool HasMaintenanceJob(Pawn pawn)
{
if (pawn.jobs == null)
return false;
// 检查当前工作是否是维护工作
if (pawn.jobs.curJob != null && pawn.jobs.curJob.def == ARA_JobDefOf.ARA_SwarmMaintain)
{
return true;
}
// 检查工作队列中是否有维护工作
if (pawn.jobs.jobQueue != null && pawn.jobs.jobQueue.Count > 0)
{
foreach (QueuedJob queuedJob in pawn.jobs.jobQueue)
{
if (queuedJob.job.def == ARA_JobDefOf.ARA_SwarmMaintain)
{
return true;
}
}
}
return false;
}
// 为Pawn分配维护工作
private void AssignMaintenanceJobTo(Pawn pawn)
{
if (pawn == null || pawn.jobs == null)
return;
// 创建维护工作
Job job = JobMaker.MakeJob(ARA_JobDefOf.ARA_SwarmMaintain, parent);
job.expiryInterval = 30000; // 工作过期时间
job.ignoreForbidden = false;
// 记录Pawn原来的工作以便恢复
Job oldJob = pawn.jobs.curJob;
// 将工作添加到队列末尾
pawn.jobs.TryTakeOrderedJob(job, JobTag.MiscWork);
// 如果Pawn原来在漫游我们可以在维护工作完成后恢复漫游状态
if (oldJob != null && (oldJob.def == JobDefOf.GotoWander || oldJob.def == JobDefOf.Wait_Wander))
{
// 我们可以在维护工作完成后添加一个恢复漫游的工作
// 这里暂时不做处理因为维护工作完成后Pawn会回到空闲状态
// 如果需要更精细的控制可以在JobDriver_SwarmMaintain完成时添加漫游工作
}
}
// 添加维护度
public void AddMaintenance(float amount)
{
currentMaintenance += amount;
if (currentMaintenance > MaxMaintenance)
currentMaintenance = MaxMaintenance;
}
// 重置维护度
public void ResetMaintenance()
{
currentMaintenance = MaxMaintenance;
}
// 在建筑信息面板中追加维护信息
public override string CompInspectStringExtra()
{
// 基础信息
string text = "ARA_SwarmMaintenance.MaintenanceLevel".Translate(currentMaintenance.ToString("F1"), MaxMaintenance.ToString("F1"), MaintenancePercentage.ToString("P1"));
// 添加维护度递减信息
text += "\n" + "ARA_SwarmMaintenance.DailyDecay".Translate(Props.maintenanceDecayPerDay.ToString("F1"));
// 显示找不到维护者的警告
if (noMaintainerWarning != "")
{
text += "\n<color=orange>" + noMaintainerWarning + "</color>";
}
return text;
}
// 获取Gizmos命令按钮- 只保留调试功能
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
// 只在玩家控制下显示
if (parent.Faction?.IsPlayer == true)
{
// 调试按钮:手动触发寻找维护者
if (Prefs.DevMode)
{
yield return new Command_Action
{
defaultLabel = "Debug: Find Maintainer",
action = () =>
{
Pawn maintainer = FindAvailableMaintainer();
if (maintainer != null)
{
Messages.Message($"找到维护者: {maintainer.LabelShort} (当前工作: {maintainer.jobs.curJob?.def.defName ?? ""})", MessageTypeDefOf.PositiveEvent);
AssignMaintenanceJobTo(maintainer);
}
else
{
Messages.Message("未找到符合条件的空闲维护者", MessageTypeDefOf.NegativeEvent);
// 列出所有符合条件的Pawn及其状态
List<Pawn> allPawns = parent.Map.mapPawns.SpawnedPawnsInFaction(parent.Faction);
List<string> pawnStatus = new List<string>();
foreach (Pawn pawn in allPawns)
{
if (Props.allowedRaces != null && Props.allowedRaces.Count > 0 && !Props.allowedRaces.Contains(pawn.def))
continue;
string status = $"{pawn.LabelShort}: ";
if (pawn.Dead) status += "死亡";
else if (pawn.Downed) status += "倒地";
else if (pawn.InMentalState) status += "精神崩溃";
else if (pawn.jobs?.curJob != null)
{
status += $"{pawn.jobs.curJob.def.defName}";
if (HasMaintenanceJob(pawn)) status += " (已有维护工作)";
}
else
{
status += "空闲";
}
pawnStatus.Add(status);
}
if (pawnStatus.Count > 0)
{
Messages.Message("符合条件的Pawn状态:\n" + string.Join("\n", pawnStatus), MessageTypeDefOf.SilentInput);
}
}
}
};
yield return new Command_Action
{
defaultLabel = "Debug: Reset Maintenance",
action = () => ResetMaintenance()
};
yield return new Command_Action
{
defaultLabel = "Debug: Reduce 50%",
action = () => currentMaintenance = MaxMaintenance * 0.5f
};
yield return new Command_Action
{
defaultLabel = "Debug: Reduce to 0",
action = () => currentMaintenance = 0f
};
}
}
}
}
}

View File

@@ -0,0 +1,178 @@
// File: Comps/CompTemperatureRuinableDamage.cs
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_TemperatureRuinableDamage : CompProperties
{
public float minSafeTemperature;
public float maxSafeTemperature = 100f;
public float progressPerDegreePerTick = 1E-05f; // 修改参数名以匹配标准调用方式
public float damagePerTick = 1f; // 每tick造成的伤害值
public float recoveryRate = 0.001f; // 温度恢复正常时的恢复速率
public bool sendDamageLetter = true; // 是否发送伤害警告信件
public CompProperties_TemperatureRuinableDamage()
{
compClass = typeof(CompTemperatureRuinableDamage);
}
}
public class CompTemperatureRuinableDamage : ThingComp
{
private float ruinedPercent; // 修改变量名以匹配标准
private bool isRuined; // 修改变量名以匹配标准
private bool damageLetterSent = false; // 是否已发送伤害警告信件
private int lastDamageTick = -99999; // 上次造成伤害的时间
private const int DamageLetterInterval = 60000; // 伤害信件间隔60秒游戏时间
public CompProperties_TemperatureRuinableDamage Props => (CompProperties_TemperatureRuinableDamage)props;
public override void CompTick()
{
base.CompTick();
int currentTick = Find.TickManager.TicksGame;
bool wasRuined = isRuined;
if (parent.AmbientTemperature < Props.minSafeTemperature || parent.AmbientTemperature > Props.maxSafeTemperature)
{
float tempDelta = 0f;
if (parent.AmbientTemperature < Props.minSafeTemperature)
{
tempDelta = Props.minSafeTemperature - parent.AmbientTemperature;
}
else if (parent.AmbientTemperature > Props.maxSafeTemperature)
{
tempDelta = parent.AmbientTemperature - Props.maxSafeTemperature;
}
// 累积损坏进度
ruinedPercent += tempDelta * Props.progressPerDegreePerTick;
// 只有在已损坏的情况下才每tick造成持续伤害
if (isRuined)
{
parent.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, Props.damagePerTick));
lastDamageTick = currentTick;
// 发送伤害警告信件(如果启用)
if (Props.sendDamageLetter && !damageLetterSent && currentTick - lastDamageTick >= DamageLetterInterval)
{
SendDamageLetter();
damageLetterSent = true;
lastDamageTick = currentTick;
}
}
// 标记为已受损
isRuined = true;
// 如果是刚刚变为损坏状态,发送警告信件
if (!wasRuined && Props.sendDamageLetter)
{
SendDamageLetter();
damageLetterSent = true;
}
}
else
{
// 当温度恢复正常时,逐渐减少损坏进度而不是重置
if (isRuined && ruinedPercent > 0f)
{
ruinedPercent -= Props.recoveryRate;
if (ruinedPercent <= 0f)
{
ruinedPercent = 0f;
isRuined = false;
damageLetterSent = false; // 重置信件状态,以便下次损坏时可以再次发送
}
}
// 即使温度正常,如果已损坏也要继续造成伤害直到恢复
if (isRuined)
{
parent.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, Props.damagePerTick));
lastDamageTick = currentTick;
// 发送伤害警告信件(如果启用)
if (Props.sendDamageLetter && !damageLetterSent && currentTick - lastDamageTick >= DamageLetterInterval)
{
SendDamageLetter();
damageLetterSent = true;
lastDamageTick = currentTick;
}
}
}
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref ruinedPercent, "ruinedPercent", 0f);
Scribe_Values.Look(ref isRuined, "isRuined", false);
Scribe_Values.Look(ref damageLetterSent, "damageLetterSent", false);
Scribe_Values.Look(ref lastDamageTick, "lastDamageTick", -99999);
}
public override string CompInspectStringExtra()
{
// 总是显示安全温度范围
string text = "ARA_TemperatureRuinable.SafeTemperatureRange".Translate(
Props.minSafeTemperature.ToStringTemperature("F1"),
Props.maxSafeTemperature.ToStringTemperature("F1")
);
// 显示当前温度状态
text += "\n" + "ARA_TemperatureRuinable.CurrentTemperature".Translate(parent.AmbientTemperature.ToStringTemperature("F1"));
// 显示损坏状态
if (ruinedPercent > 0f)
{
text += "\n" + "ARA_TemperatureRuinable.DamageProgress".Translate(ruinedPercent.ToStringPercent());
if (isRuined)
{
text += "\n<color=red>" + "ARA_TemperatureRuinable.TakingDamage".Translate() + "</color>";
}
}
else
{
text += "\n" + "ARA_TemperatureRuinable.NoDamage".Translate();
}
return text;
}
// 发送伤害警告信件
private void SendDamageLetter()
{
if (parent == null || parent.Map == null)
return;
// 确定伤害类型(太冷或太热)
string temperatureType;
if (parent.AmbientTemperature < Props.minSafeTemperature)
{
temperatureType = "ARA_TemperatureRuinable.TooCold".Translate();
}
else
{
temperatureType = "ARA_TemperatureRuinable.TooHot".Translate();
}
// 创建信件
string label = "ARA_TemperatureRuinable.DamageLetterLabel".Translate(parent.Label);
string text = "ARA_TemperatureRuinable.DamageLetterText".Translate(
parent.Label,
temperatureType,
parent.AmbientTemperature.ToStringTemperature("F1"),
Props.minSafeTemperature.ToStringTemperature("F1"),
Props.maxSafeTemperature.ToStringTemperature("F1")
);
// 发送信件
Find.LetterStack.ReceiveLetter(label, text, LetterDefOf.NegativeEvent, new LookTargets(parent));
}
}
}

View File

@@ -0,0 +1,882 @@
// File: Buildings/Building_EquipmentOotheca.cs
using RimWorld;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using Verse;
using Verse.AI;
using System;
namespace ArachnaeSwarm
{
public class Building_EquipmentOotheca : Building
{
// 引用组件
public CompEquipmentIncubatorData EquipmentIncubatorData => this.TryGetComp<CompEquipmentIncubatorData>();
// 孵化状态
public bool isIncubating = false;
public float incubationProgress = 0f;
public float incubationDuration = 0f;
public ThingDef incubatingThingDef = null;
// 幼虫交互相关
public Pawn assignedLarva = null;
public int larvaOperateTicksRemaining = 0;
// 速度乘数系统
private float speedMultiplier = 1.0f;
private int lastMultiplierUpdateTick = -1;
private const int MultiplierUpdateInterval = 250;
// 质量系统
private float qualityMultiplier = 1.0f;
private float qualityProgress = 0f;
private float qualityTotal = 0f;
// 缓存的ModExtension
private OothecaIncubatorExtension cachedExtension;
// 获取ModExtension的辅助属性
private OothecaIncubatorExtension Ext
{
get
{
if (cachedExtension == null)
{
cachedExtension = def.GetModExtension<OothecaIncubatorExtension>() ?? OothecaIncubatorExtension.Default;
}
return cachedExtension;
}
}
// 属性访问器
public float SpeedMultiplier
{
get
{
if (lastMultiplierUpdateTick < 0 || Find.TickManager.TicksGame - lastMultiplierUpdateTick >= MultiplierUpdateInterval)
{
UpdateSpeedMultiplier();
}
return speedMultiplier;
}
}
// 质量属性
public float QualityMultiplier => qualityMultiplier;
public float QualityProgress => qualityProgress;
public float QualityPercent => qualityTotal > 0 ? qualityProgress / qualityTotal : 0f;
// 进度百分比
public float AdjustedProgressPercent
{
get
{
if (incubationDuration <= 0) return 0f;
return incubationProgress / incubationDuration;
}
}
// 获取速度因子描述
public string GetSpeedFactorsDescription()
{
var builder = new StringBuilder();
builder.AppendLine("ARA_EquipmentIncubator.SpeedFactors".Translate());
builder.AppendLine();
// 1. 检查是否在孵化间中
bool inIncubatorRoom = IsInIncubatorRoom();
if (Ext.requiresIncubatorRoom)
{
builder.AppendLine(inIncubatorRoom ?
"ARA_EquipmentIncubator.InIncubatorRoom".Translate() :
"ARA_EquipmentIncubator.NotInIncubatorRoom".Translate());
}
// 2. 检查营养液数量
int nutrientSolutionCount = CountNearbyNutrientSolutions();
if (nutrientSolutionCount > 0)
{
builder.AppendLine("ARA_EquipmentIncubator.NutrientSolutions".Translate(
nutrientSolutionCount,
nutrientSolutionCount * Ext.nutrientSolutionBonusPerTile * 100));
}
else
{
builder.AppendLine("ARA_EquipmentIncubator.NoNutrientSolutionsNearby".Translate());
}
builder.AppendLine();
builder.AppendLine("ARA_EquipmentIncubator.NutrientDetectionRadius".Translate(Ext.nutrientSolutionRadius));
builder.AppendLine();
builder.AppendLine("ARA_EquipmentIncubator.TotalSpeedMultiplier".Translate(SpeedMultiplier.ToStringPercent()));
return builder.ToString().TrimEndNewlines();
}
// 获取质量因子描述
public string GetQualityFactorsDescription()
{
var builder = new StringBuilder();
builder.AppendLine("ARA_EquipmentIncubator.QualityFactors".Translate());
builder.AppendLine();
// 1. 建筑血量损失百分比
if (Ext.healthAffectsQuality)
{
float healthPercent = (float)HitPoints / MaxHitPoints;
builder.AppendLine("ARA_EquipmentIncubator.BuildingHealth".Translate(healthPercent.ToStringPercent()));
}
// 2. 房间质量因子
if (Ext.useRoomQualityFactor)
{
float roomFactor = GetRoomQualityFactor();
builder.AppendLine(roomFactor == 1.0f ?
"ARA_EquipmentIncubator.RoomFactorNormal".Translate() :
$"{(roomFactor > 1.0f ? "" : "")} {"ARA_EquipmentIncubator.RoomFactorModified".Translate()}{roomFactor.ToStringPercent()}");
}
// 3. 附近其他卵
int nearbyOothecaCount = CountNearbyOtherOothecas();
if (nearbyOothecaCount > 0)
{
builder.AppendLine("ARA_EquipmentIncubator.NearbyOothecas".Translate(
nearbyOothecaCount,
Mathf.Min(nearbyOothecaCount * Ext.nearbyOothecaPenaltyPerUnit * 100, 100)));
}
else
{
builder.AppendLine("ARA_EquipmentIncubator.NoNearbyOothecas".Translate());
}
builder.AppendLine();
builder.AppendLine("ARA_EquipmentIncubator.OothecaDetectionRadius".Translate(Ext.nearbyOothecaRadius));
builder.AppendLine();
builder.AppendLine("ARA_EquipmentIncubator.TotalQualityMultiplier".Translate(QualityMultiplier.ToStringPercent()));
return builder.ToString().TrimEndNewlines();
}
// 构建呼叫幼虫描述
private string BuildCallLarvaDescription(EquipmentIncubationConfig config)
{
var builder = new StringBuilder();
builder.AppendLine("ARA_EquipmentIncubator.CallLarvaTitle".Translate());
builder.AppendLine();
builder.AppendLine("ARA_EquipmentIncubator.LarvaWillCome".Translate());
builder.AppendLine(config.thingDef.LabelCap);
builder.AppendLine();
if (Ext.larvaSearchRadius < 999f)
{
builder.AppendLine("ARA_EquipmentIncubator.LarvaSearchRadius".Translate(Ext.larvaSearchRadius));
}
return builder.ToString().TrimEndNewlines();
}
// 呼叫幼虫
private void CallLarva()
{
if (isIncubating)
{
Messages.Message("ARA_EquipmentIncubator.AlreadyIncubating".Translate() + " " + "ARA_EquipmentIncubator.CancelFirst".Translate(),
MessageTypeDefOf.RejectInput);
return;
}
if (assignedLarva != null)
{
Messages.Message("ARA_EquipmentIncubator.LarvaAlreadyOnWay".Translate(),
MessageTypeDefOf.RejectInput);
return;
}
var larva = FindLarva();
if (larva == null)
{
Messages.Message("ARA_EquipmentIncubator.NoLarvaeFound".Translate() + " " + "ARA_EquipmentIncubator.LarvaMustBeRace".Translate(),
MessageTypeDefOf.RejectInput);
return;
}
var job = JobMaker.MakeJob(ARA_JobDefOf.ARA_OperateEquipmentIncubator, this);
job.count = 1;
larva.jobs.TryTakeOrderedJob(job, JobTag.MiscWork);
assignedLarva = larva;
Messages.Message("ARA_EquipmentIncubator.LarvaCalled".Translate() + " " + "ARA_EquipmentIncubator.ArriveShortly".Translate(),
MessageTypeDefOf.PositiveEvent);
}
// 幼虫到达
public void NotifyLarvaArrived(Pawn larva)
{
if (larva.def.defName != "ArachnaeBase_Race_Larva")
{
ArachnaeLog.Debug($"Invalid larva arrived: {larva.def.defName}");
return;
}
larvaOperateTicksRemaining = 180;
assignedLarva = larva;
Messages.Message("ARA_EquipmentIncubator.LarvaArrived".Translate() + " " + "ARA_EquipmentIncubator.ActivatingOotheca".Translate(),
MessageTypeDefOf.SilentInput);
}
// 幼虫完成操作
public void NotifyLarvaOperationComplete(Pawn larva)
{
if (larva != assignedLarva)
{
ArachnaeLog.Debug("Larva operation complete called with wrong larva.");
return;
}
var config = EquipmentIncubatorData?.SelectedConfig;
if (config == null)
{
ArachnaeLog.Debug("No incubation config selected when larva completed operation.");
return;
}
incubatingThingDef = config.thingDef;
incubationDuration = config.DaysRequired * 60000f;
incubationProgress = 0f;
isIncubating = true;
qualityTotal = incubationDuration;
qualityProgress = 0f;
UpdateQualityMultiplier();
UpdateSpeedMultiplier();
assignedLarva = null;
larvaOperateTicksRemaining = 0;
Messages.Message("ARA_EquipmentIncubator.IncubationStarted".Translate() + " " + incubatingThingDef.LabelCap + ". " +
"ARA_EquipmentIncubator.ProcessWillComplete".Translate() + " " + config.DaysRequired + " " + "ARA_EquipmentIncubator.DaysBaseTime".Translate(),
MessageTypeDefOf.PositiveEvent);
}
// 取消孵化
private void CancelIncubation()
{
if (!isIncubating) return;
isIncubating = false;
incubationProgress = 0f;
incubationDuration = 0f;
incubatingThingDef = null;
qualityProgress = 0f;
qualityTotal = 0f;
Messages.Message("ARA_EquipmentIncubator.IncubationCancelled".Translate() + " " + "ARA_EquipmentIncubator.ContentsLost".Translate(),
MessageTypeDefOf.NeutralEvent);
}
// 完成孵化
private void CompleteIncubation()
{
if (incubatingThingDef == null) return;
float finalQualityPercent = QualityPercent;
// 生成物品
Thing thing = ThingMaker.MakeThing(incubatingThingDef);
// 应用质量影响
ApplyQualityEffects(thing, finalQualityPercent);
// 放置物品
var spawnPos = Position;
GenPlace.TryPlaceThing(thing, spawnPos, Map, ThingPlaceMode.Near);
// 重置状态
isIncubating = false;
incubationProgress = 0f;
incubationDuration = 0f;
incubatingThingDef = null;
qualityProgress = 0f;
qualityTotal = 0f;
// 显示消息
string qualityText = finalQualityPercent >= 0.9f ? "ARA_EquipmentIncubator.QualityExcellent".Translate() :
finalQualityPercent >= 0.7f ? "ARA_EquipmentIncubator.QualityGood".Translate() :
finalQualityPercent >= 0.5f ? "ARA_EquipmentIncubator.QualityAverage".Translate() :
finalQualityPercent >= 0.3f ? "ARA_EquipmentIncubator.QualityPoor".Translate() : "ARA_EquipmentIncubator.QualityVeryPoor".Translate();
Messages.Message("ARA_EquipmentIncubator.IncubationComplete".Translate() + " " + thing.LabelCap + " " +
"ARA_EquipmentIncubator.HasEmergedWith".Translate() + " " + qualityText + " " +
"ARA_EquipmentIncubator.Quality".Translate() + " (" + finalQualityPercent.ToStringPercent() + ").",
MessageTypeDefOf.PositiveEvent);
Destroy();
}
// 应用质量效果
private void ApplyQualityEffects(Thing thing, float qualityPercent)
{
// 应用质量效果到装备
if (thing.TryGetComp<CompQuality>() is CompQuality compQuality)
{
// 根据质量百分比设置质量等级
QualityCategory qualityCategory = qualityPercent >= 0.99f ? QualityCategory.Legendary :
qualityPercent >= 0.75f ? QualityCategory.Masterwork :
qualityPercent >= 0.6f ? QualityCategory.Excellent :
qualityPercent >= 0.45f ? QualityCategory.Good :
qualityPercent >= 0.3f ? QualityCategory.Normal : QualityCategory.Poor;
compQuality.SetQuality(qualityCategory, ArtGenerationContext.Outsider);
}
// 设置生命值百分比
if (qualityPercent < 1.0f)
{
float healthFactor = Mathf.Lerp(0.5f, 1.0f, qualityPercent);
thing.HitPoints = Mathf.RoundToInt(thing.MaxHitPoints * healthFactor);
}
}
// 获取剩余时间
public float GetRemainingTicks()
{
if (!isIncubating || incubationDuration <= incubationProgress) return 0f;
float remainingProgress = incubationDuration - incubationProgress;
float currentSpeed = SpeedMultiplier;
if (currentSpeed <= 0) return float.MaxValue;
return remainingProgress / currentSpeed;
}
public float GetRemainingDays()
{
return GetRemainingTicks() / 60000f;
}
public float GetRemainingHours()
{
float remainingTicks = GetRemainingTicks();
return (remainingTicks % 60000f) / 2500f;
}
// 检查字符串
public override string GetInspectString()
{
var baseString = base.GetInspectString();
var builder = new StringBuilder();
if (!string.IsNullOrEmpty(baseString))
{
builder.Append(baseString);
}
if (isIncubating && incubatingThingDef != null)
{
float progressPercent = AdjustedProgressPercent;
float daysRemaining = GetRemainingDays();
float hoursRemaining = GetRemainingHours();
if (builder.Length > 0) builder.AppendLine();
builder.Append("ARA_EquipmentIncubator.Incubating".Translate() + ": " + incubatingThingDef.LabelCap);
builder.AppendLine();
builder.Append("ARA_EquipmentIncubator.Progress".Translate() + ": " + progressPercent.ToStringPercent());
builder.AppendLine();
string timeText = "ARA_EquipmentIncubator.TimeRemaining".Translate() + ": " + daysRemaining.ToString("F1") + " " + "ARA_EquipmentIncubator.Days".Translate();
if (hoursRemaining > 0.1f && daysRemaining < 1f)
{
timeText += " (" + hoursRemaining.ToString("F1") + " " + "ARA_EquipmentIncubator.Hours".Translate() + ")";
}
builder.Append(timeText);
builder.AppendLine();
builder.Append("ARA_EquipmentIncubator.Speed".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " +
"ARA_EquipmentIncubator.Quality".Translate() + ": " + QualityMultiplier.ToStringPercent());
}
else if (assignedLarva != null)
{
if (builder.Length > 0) builder.AppendLine();
if (larvaOperateTicksRemaining > 0)
{
float secondsRemaining = larvaOperateTicksRemaining / 60f;
builder.Append("ARA_EquipmentIncubator.LarvaOperating".Translate() + ": " + secondsRemaining.ToString("F1") + " " + "ARA_EquipmentIncubator.SRemaining".Translate());
}
else
{
builder.Append("ARA_EquipmentIncubator.LarvaOnWay".Translate());
}
}
else if (!isIncubating)
{
var config = EquipmentIncubatorData?.SelectedConfig;
if (config != null)
{
if (builder.Length > 0) builder.AppendLine();
builder.Append("ARA_EquipmentIncubator.Target".Translate() + ": " + config.thingDef.LabelCap);
builder.AppendLine();
builder.Append("ARA_EquipmentIncubator.SpeedMultiplier".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " +
"ARA_EquipmentIncubator.QualityMultiplier".Translate() + ": " + QualityMultiplier.ToStringPercent());
}
}
return builder.ToString().TrimEndNewlines();
}
// Gizmos
public override IEnumerable<Gizmo> GetGizmos()
{
foreach (var gizmo in base.GetGizmos())
{
yield return gizmo;
}
if (Faction == Faction.OfPlayer)
{
if (!isIncubating && EquipmentIncubatorData?.IncubationConfigs?.Count > 0)
{
yield return CreateTargetSwitchGizmo();
}
var config = EquipmentIncubatorData?.SelectedConfig;
if (!isIncubating && config != null && config.IsResearchComplete)
{
yield return new Command_Action
{
defaultLabel = "ARA_EquipmentIncubator.CallLarva".Translate(),
defaultDesc = BuildCallLarvaDescription(config),
icon = ContentFinder<Texture2D>.Get("ArachnaeSwarm/UI/Commands/ARA_CallLarva", false) ?? BaseContent.BadTex,
action = CallLarva,
hotKey = KeyBindingDefOf.Misc3
};
}
if (isIncubating)
{
yield return new Command_Action
{
defaultLabel = "ARA_EquipmentIncubator.CancelIncubation".Translate(),
defaultDesc = "ARA_EquipmentIncubator.CancelIncubationDesc".Translate(),
icon = ContentFinder<Texture2D>.Get("UI/Commands/Cancel", false) ?? TexCommand.ClearPrioritizedWork,
action = CancelIncubation
};
}
}
}
// 创建切换目标Gizmo - 现在使用装备的图标
private Gizmo CreateTargetSwitchGizmo()
{
var configs = EquipmentIncubatorData?.IncubationConfigs;
if (configs == null || configs.Count == 0) return null;
var props = EquipmentIncubatorData?.props as CompProperties_EquipmentIncubatorData;
var selectedConfig = EquipmentIncubatorData?.SelectedConfig;
var switchButton = new Command_Action
{
defaultLabel = BuildSwitchButtonLabel(selectedConfig, props),
defaultDesc = BuildSwitchButtonDescription(selectedConfig, props),
icon = GetConfigIcon(selectedConfig),
action = ShowSelectionMenu,
hotKey = KeyBindingDefOf.Misc2
};
if (selectedConfig != null && !selectedConfig.IsResearchComplete)
{
if (selectedConfig.requiredResearch != null)
{
switchButton.Disable($"Requires research: {selectedConfig.requiredResearch.LabelCap}");
}
}
return switchButton;
}
// 获取配置图标 - 现在直接从ThingDef获取
private Texture2D GetConfigIcon(EquipmentIncubationConfig config)
{
if (config == null)
return BaseContent.BadTex;
// 如果配置中没有缓存图标尝试直接获取ThingDef的uiIcon
if (config.thingDef?.uiIcon != null)
return config.thingDef.uiIcon;
// 回退到默认图标
return ContentFinder<Texture2D>.Get("UI/Commands/Default", false) ?? BaseContent.BadTex;
}
private string BuildSwitchButtonLabel(EquipmentIncubationConfig config, CompProperties_EquipmentIncubatorData props)
{
if (config != null && config.thingDef != null)
{
return (props?.buttonLabel ?? "ARA_EquipmentIncubator.IncubateLabel").Translate(config.thingDef.LabelCap);
}
return (props?.buttonLabel ?? "ARA_EquipmentIncubator.IncubateLabel").Translate("None");
}
private string BuildSwitchButtonDescription(EquipmentIncubationConfig config, CompProperties_EquipmentIncubatorData props)
{
var builder = new StringBuilder();
builder.AppendLine((props?.buttonDesc ?? "ARA_EquipmentIncubator.ButtonDesc").Translate());
builder.AppendLine();
if (config != null)
{
if (config.thingDef != null)
{
builder.AppendLine($"ARA_EquipmentIncubator.ButtonLabel".Translate(config.thingDef.LabelCap));
if (!string.IsNullOrEmpty(config.thingDef.description))
{
builder.AppendLine(config.thingDef.description);
}
}
builder.AppendLine($"ARA_EquipmentIncubator.IncubationTime".Translate(config.DaysRequired));
if (config.requiredResearch != null)
{
if (config.requiredResearch.IsFinished)
builder.AppendLine($"Research: {config.requiredResearch.LabelCap} (Completed)");
else
builder.AppendLine($"Research: {config.requiredResearch.LabelCap} (Required)");
}
}
builder.AppendLine();
builder.AppendLine("ARA_EquipmentIncubator.ButtonDesc".Translate());
return builder.ToString().TrimEndNewlines();
}
private void ShowSelectionMenu()
{
var configs = EquipmentIncubatorData?.IncubationConfigs;
var props = EquipmentIncubatorData?.props as CompProperties_EquipmentIncubatorData;
if (configs == null || configs.Count == 0) return;
var options = new List<FloatMenuOption>();
int currentIndex = EquipmentIncubatorData.GetSelectedIndex();
for (int i = 0; i < configs.Count; i++)
{
int index = i;
var config = configs[i];
if (config == null || config.thingDef == null) continue;
string label = config.thingDef.LabelCap;
string description = config.GetDescription();
string prefix = (i == currentIndex) ? "✓ " : " ";
// 使用原版FloatMenuOption的构造函数直接传入图标
FloatMenuOption option;
// 尝试获取ThingDef的图标
Texture2D icon = config.thingDef.uiIcon;
if (icon != null)
{
// 使用带有Texture2D图标的构造函数
option = new FloatMenuOption(
prefix + label,
() => SwitchToConfig(index),
icon,
Color.white,
MenuOptionPriority.Default,
null, // mouseoverGuiAction
null, // revalidateClickTarget
0f, // extraPartWidth
null, // extraPartOnGUI
null, // revalidateWorldClickTarget
true, // playSelectionSound
0, // orderInPriority
HorizontalJustification.Left, // iconJustification
false // extraPartRightJustified
);
}
else
{
// 如果没有图标,使用普通构造函数
option = new FloatMenuOption(
prefix + label,
() => SwitchToConfig(index)
);
}
// 设置工具提示
option.tooltip = description;
// 如果研究未完成,禁用选项
if (!config.IsResearchComplete)
{
option.Label = prefix + label;
option.Disabled = true;
option.tooltip = description + "\n\n " + "ARA_EquipmentIncubator.ResearchRequired".Translate() + " " + config.requiredResearch.LabelCap;
}
options.Add(option);
}
if (options.Count > 0)
{
Find.WindowStack.Add(new FloatMenu(options,
(props?.menuTitle ?? "ARA_EquipmentIncubator.MenuTitle").Translate()));
}
}
private void SwitchToConfig(int index)
{
if (EquipmentIncubatorData != null)
{
EquipmentIncubatorData.SwitchToConfig(index);
var config = EquipmentIncubatorData.SelectedConfig;
if (config != null && config.thingDef != null)
{
Messages.Message($"ARA_EquipmentIncubator.TargetSwitched".Translate(config.thingDef.LabelCap),
this, MessageTypeDefOf.SilentInput);
}
}
}
// 查找幼虫
private Pawn FindLarva()
{
var map = Map;
if (map == null) return null;
float searchRadius = Ext.larvaSearchRadius;
foreach (var pawn in map.mapPawns.SpawnedPawnsInFaction(Faction))
{
if (pawn.def.defName == "ArachnaeBase_Race_Larva")
{
if (searchRadius < 999f)
{
float distance = pawn.Position.DistanceTo(Position);
if (distance > searchRadius)
continue;
}
if (!pawn.Downed && !pawn.InMentalState &&
pawn.mindState != null &&
(pawn.CurJob == null || pawn.CurJob.def != ARA_JobDefOf.ARA_OperateEquipmentIncubator))
{
return pawn;
}
}
}
return null;
}
// 每tick更新
protected override void Tick()
{
base.Tick();
if (larvaOperateTicksRemaining > 0)
{
larvaOperateTicksRemaining--;
}
if (isIncubating)
{
if (lastMultiplierUpdateTick < 0 || Find.TickManager.TicksGame - lastMultiplierUpdateTick >= MultiplierUpdateInterval)
{
UpdateSpeedMultiplier();
UpdateQualityMultiplier();
}
float currentSpeed = SpeedMultiplier;
incubationProgress += currentSpeed;
qualityProgress += currentSpeed * QualityMultiplier;
if (incubationProgress >= incubationDuration)
{
CompleteIncubation();
}
}
}
// 检查是否在孵化间中
private bool IsInIncubatorRoom()
{
if (!Ext.requiresIncubatorRoom)
return true;
var room = this.GetRoom();
if (room == null) return false;
return room.Role != null && room.Role.defName == "ARA_Incubator_Room";
}
// 计算营养液数量
private int CountNearbyNutrientSolutions()
{
var map = Map;
if (map == null) return 0;
int count = 0;
int radius = Ext.NutrientSolutionRadiusInt;
for (int x = -radius; x <= radius; x++)
{
for (int y = -radius; y <= radius; y++)
{
IntVec3 cell = Position + new IntVec3(x, 0, y);
if (cell.InBounds(map))
{
TerrainDef terrain = map.terrainGrid.TerrainAt(cell);
if (terrain != null && terrain.defName == "ARA_Incubator_Nutrient_Solution")
{
count++;
}
}
}
}
return count;
}
// 计算房间质量因子
private float GetRoomQualityFactor()
{
if (!Ext.useRoomQualityFactor)
return 1.0f;
var room = this.GetRoom();
if (room == null) return 1.0f;
var statDef = DefDatabase<RoomStatDef>.GetNamedSilentFail("ARA_IncubatorQualityFactor");
if (statDef != null)
{
return room.GetStat(statDef);
}
return 1.0f;
}
// 计算附近其他卵的数量
private int CountNearbyOtherOothecas()
{
var map = Map;
if (map == null) return 0;
int count = 0;
var allBuildings = map.listerThings.ThingsOfDef(this.def);
foreach (var building in allBuildings)
{
if (building == this) continue;
if (building.def.defName == "ARA_Pawn_Ootheca" || building.def.defName == "ARA_Equipment_Ootheca")
{
bool isNearby = false;
if (Ext.checkSameRoomForOotheca)
{
var room = building.GetRoom();
if (room != null && room == this.GetRoom())
{
isNearby = true;
}
}
if (!isNearby)
{
float distance = building.Position.DistanceTo(this.Position);
if (distance <= Ext.nearbyOothecaRadius)
{
isNearby = true;
}
}
if (isNearby)
{
count++;
}
}
}
return count;
}
// 更新速度乘数
private void UpdateSpeedMultiplier()
{
float multiplier = 1.0f;
if (Ext.requiresIncubatorRoom && !IsInIncubatorRoom())
{
multiplier *= Ext.speedPenaltyOutsideIncubator;
}
int nutrientSolutionCount = CountNearbyNutrientSolutions();
float nutrientBonus = 1.0f + (nutrientSolutionCount * Ext.nutrientSolutionBonusPerTile);
multiplier *= nutrientBonus;
speedMultiplier = multiplier;
lastMultiplierUpdateTick = Find.TickManager.TicksGame;
}
// 更新质量乘数
private void UpdateQualityMultiplier()
{
float multiplier = 1.0f;
if (Ext.healthAffectsQuality)
{
float healthPercent = (float)HitPoints / MaxHitPoints;
multiplier *= healthPercent;
}
if (Ext.useRoomQualityFactor)
{
float roomFactor = GetRoomQualityFactor();
multiplier *= roomFactor;
}
int nearbyOothecaCount = CountNearbyOtherOothecas();
float oothecaPenalty = Mathf.Max(0f, 1.0f - (nearbyOothecaCount * Ext.nearbyOothecaPenaltyPerUnit));
multiplier *= oothecaPenalty;
qualityMultiplier = Mathf.Clamp(multiplier, 0f, 1.0f);
}
// 保存/加载
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref isIncubating, "isIncubating", false);
Scribe_Values.Look(ref incubationProgress, "incubationProgress", 0f);
Scribe_Values.Look(ref incubationDuration, "incubationDuration", 0f);
Scribe_Defs.Look(ref incubatingThingDef, "incubatingThingDef");
Scribe_References.Look(ref assignedLarva, "assignedLarva");
Scribe_Values.Look(ref larvaOperateTicksRemaining, "larvaOperateTicksRemaining", 0);
Scribe_Values.Look(ref speedMultiplier, "speedMultiplier", 1.0f);
Scribe_Values.Look(ref lastMultiplierUpdateTick, "lastMultiplierUpdateTick", -1);
Scribe_Values.Look(ref qualityMultiplier, "qualityMultiplier", 1.0f);
Scribe_Values.Look(ref qualityProgress, "qualityProgress", 0f);
Scribe_Values.Look(ref qualityTotal, "qualityTotal", 0f);
}
}
}

View File

@@ -0,0 +1,332 @@
// File: Comps/CompProperties_EquipmentIncubatorData.cs
using RimWorld;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
// 装备孵化配置
public class EquipmentIncubationConfig : IExposable
{
public ThingDef thingDef;
public ResearchProjectDef requiredResearch;
public float daysRequired; // 从stat中读取
public string buttonIconPath;
// 缓存的生产时间避免重复获取stat
private float? cachedDaysRequired;
public EquipmentIncubationConfig() { }
public EquipmentIncubationConfig(ThingDef thingDef, ResearchProjectDef requiredResearch = null,
string buttonIconPath = null)
{
this.thingDef = thingDef;
this.requiredResearch = requiredResearch;
this.buttonIconPath = buttonIconPath;
cachedDaysRequired = null;
}
public void ExposeData()
{
Scribe_Defs.Look(ref thingDef, "thingDef");
Scribe_Defs.Look(ref requiredResearch, "requiredResearch");
Scribe_Values.Look(ref buttonIconPath, "buttonIconPath");
// 不保存缓存值,重新计算
if (Scribe.mode == LoadSaveMode.LoadingVars)
{
cachedDaysRequired = null;
}
}
// 获取生产时间(天)
public float DaysRequired
{
get
{
if (cachedDaysRequired.HasValue)
return cachedDaysRequired.Value;
if (thingDef == null)
{
cachedDaysRequired = 1f;
return cachedDaysRequired.Value;
}
// 从stat中读取ARA_IncubationTime
var statDef = DefDatabase<StatDef>.GetNamedSilentFail("ARA_IncubationTime");
if (statDef != null)
{
cachedDaysRequired = thingDef.GetStatValueAbstract(statDef, null);
}
else
{
// 默认值
cachedDaysRequired = 1f;
}
return cachedDaysRequired.Value;
}
}
// 检查是否满足研究要求
public bool IsResearchComplete => requiredResearch == null || requiredResearch.IsFinished;
// 获取描述
public string GetDescription()
{
var builder = new StringBuilder();
if (thingDef != null)
{
builder.AppendLine(thingDef.description ?? "ARA_EquipmentIncubator.NoDescription".Translate());
builder.AppendLine();
builder.AppendLine("ARA_EquipmentIncubator.IncubationTime".Translate(DaysRequired));
}
if (requiredResearch != null)
{
if (requiredResearch.IsFinished)
builder.AppendLine("ARA_EquipmentIncubator.ResearchCompleted".Translate(requiredResearch.LabelCap));
else
builder.AppendLine("ARA_EquipmentIncubator.ResearchRequired".Translate(requiredResearch.LabelCap));
}
return builder.ToString().TrimEndNewlines();
}
}
// 装备孵化器组件属性
public class CompProperties_EquipmentIncubatorData : CompProperties
{
// 支持手动指定配置列表(可选)
public List<EquipmentIncubationConfig> incubationConfigs;
// 默认选择索引
public int defaultIndex = 0;
// Gizmo相关配置
public string buttonLabel = "ARA_EquipmentIncubator.IncubateLabel";
public string buttonDesc = "ARA_EquipmentIncubator.ButtonDesc";
public string menuTitle = "ARA_EquipmentIncubator.MenuTitle";
public string defaultIconPath = "UI/Commands/Default";
// 是否自动扫描所有ThingDef来构建配置列表
public bool autoScanThingDefs = true;
// 手动指定要扫描的ThingDef类型可选
public List<string> thingDefCategories;
public List<ThingCategoryDef> thingCategoryDefs;
public CompProperties_EquipmentIncubatorData()
{
compClass = typeof(CompEquipmentIncubatorData);
}
}
// 装备孵化器数据组件
public class CompEquipmentIncubatorData : ThingComp
{
private CompProperties_EquipmentIncubatorData Props => (CompProperties_EquipmentIncubatorData)props;
// 当前选择的配置索引
private int selectedIndex = -1;
// 缓存的配置列表
private List<EquipmentIncubationConfig> cachedConfigs;
private bool configsBuilt = false;
// 公开获取孵化配置列表的方法
public List<EquipmentIncubationConfig> IncubationConfigs
{
get
{
if (!configsBuilt)
{
BuildIncubationConfigs();
}
return cachedConfigs ?? new List<EquipmentIncubationConfig>();
}
}
// 获取当前选择的配置
public EquipmentIncubationConfig SelectedConfig
{
get
{
var configs = IncubationConfigs;
if (configs.Count == 0) return null;
// 初始化选择
if (selectedIndex == -1)
{
selectedIndex = Mathf.Clamp(Props.defaultIndex, 0, configs.Count - 1);
}
if (selectedIndex < 0 || selectedIndex >= configs.Count)
selectedIndex = 0;
return configs[selectedIndex];
}
}
// 获取当前选择的ThingDef
public ThingDef SelectedThingDef => SelectedConfig?.thingDef;
// 构建孵化配置列表
private void BuildIncubationConfigs()
{
cachedConfigs = new List<EquipmentIncubationConfig>();
configsBuilt = true;
// 优先使用手动配置的列表
if (Props.incubationConfigs != null && Props.incubationConfigs.Count > 0)
{
foreach (var config in Props.incubationConfigs)
{
if (config?.thingDef != null)
{
cachedConfigs.Add(config);
}
}
if (cachedConfigs.Count > 0)
return;
}
// 如果没有手动配置且启用了自动扫描则扫描所有ThingDef
if (Props.autoScanThingDefs)
{
ScanThingDefsForConfigs();
}
}
// 扫描所有ThingDef来构建配置列表
private void ScanThingDefsForConfigs()
{
var allThingDefs = DefDatabase<ThingDef>.AllDefsListForReading;
foreach (var thingDef in allThingDefs)
{
// 检查该ThingDef是否包含CompProperties_ExtraIncubationInfo组件
var extraInfoProps = thingDef.comps?.FirstOrDefault(c => c is CompProperties_ExtraIncubationInfo)
as CompProperties_ExtraIncubationInfo;
if (extraInfoProps == null)
continue;
// 检查cocoonDefs是否包含当前建筑
bool isForThisCocoon = false;
if (extraInfoProps.cocoonDefs != null && extraInfoProps.cocoonDefs.Count > 0)
{
isForThisCocoon = extraInfoProps.cocoonDefs.Contains(parent.def);
}
else if (extraInfoProps.cocoonDef != null)
{
// 向后兼容检查单个cocoonDef
isForThisCocoon = extraInfoProps.cocoonDef == parent.def;
}
if (!isForThisCocoon)
continue;
// 检查是否有ARA_IncubationTime这个stat
var incubationTimeStat = DefDatabase<StatDef>.GetNamedSilentFail("ARA_IncubationTime");
if (incubationTimeStat == null)
{
Log.Warning($"ThingDef {thingDef.defName} has CompProperties_ExtraIncubationInfo but ARA_IncubationTime stat is not defined.");
continue;
}
// 获取孵化时间
float daysRequired = thingDef.GetStatValueAbstract(incubationTimeStat, null);
if (daysRequired <= 0)
{
Log.Warning($"ThingDef {thingDef.defName} has invalid incubation time: {daysRequired}");
continue;
}
// 创建配置
var config = new EquipmentIncubationConfig
{
thingDef = thingDef,
daysRequired = daysRequired,
// 可以在这里添加其他配置,比如所需研究等
// requiredResearch = ...
// buttonIconPath = ...
};
cachedConfigs.Add(config);
}
// 按物品名称排序
cachedConfigs.Sort((a, b) => string.Compare(a.thingDef?.label ?? "", b.thingDef?.label ?? ""));
Log.Message($"Built {cachedConfigs.Count} equipment incubation configs for {parent.def.defName}");
}
// 切换到特定索引
public void SwitchToConfig(int index)
{
if (index >= 0 && index < IncubationConfigs.Count)
{
selectedIndex = index;
}
}
// 检查配置是否可用(研究是否完成)
public bool IsConfigAvailable(int index)
{
if (index < 0 || index >= IncubationConfigs.Count)
return false;
var config = IncubationConfigs[index];
return config?.IsResearchComplete ?? false;
}
// 获取配置索引
public int GetSelectedIndex()
{
return selectedIndex;
}
// 存档加载
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref selectedIndex, "selectedIndex", -1);
Scribe_Values.Look(ref configsBuilt, "configsBuilt", false);
if (Scribe.mode == LoadSaveMode.LoadingVars)
{
// 重置缓存,在需要时重新构建
cachedConfigs = null;
configsBuilt = false;
}
}
// 在建筑信息中显示额外信息
public override string CompInspectStringExtra()
{
var current = SelectedConfig;
if (current != null && current.thingDef != null)
{
string status = "ARA_EquipmentIncubator.IncubationTarget".Translate(current.thingDef.LabelCap);
if (current.requiredResearch != null && !current.requiredResearch.IsFinished)
{
status += " (" + "ARA_EquipmentIncubator.Requires".Translate() + ": " + current.requiredResearch.LabelCap + ")";
}
return status;
}
return base.CompInspectStringExtra();
}
}
}

View File

@@ -0,0 +1,237 @@
// File: ITabs/ITab_EquipmentOotheca_Incubation.cs
using UnityEngine;
using Verse;
using System.Collections.Generic;
using RimWorld;
using Verse.Sound;
namespace ArachnaeSwarm
{
public class ITab_EquipmentOotheca_Incubation : ITab
{
private const float BarHeight = 20f;
private const float Margin = 10f;
private const float LabelHeight = 30f;
private const float SmallLabelHeight = 20f;
private const float ButtonHeight = 25f;
private const float TabWidth = 320f;
private const float TabHeight = 420f;
private Vector2 scrollPosition = Vector2.zero;
private const float ViewHeight = 450f;
public override bool IsVisible
{
get
{
return SelThing.Faction == Faction.OfPlayer;
}
}
public ITab_EquipmentOotheca_Incubation()
{
size = new Vector2(TabWidth, TabHeight);
labelKey = "ARA_EquipmentIncubator.IncubationTab";
tutorTag = "EquipmentIncubation";
}
protected override void FillTab()
{
Rect rect = new Rect(0f, 0f, size.x, size.y).ContractedBy(Margin);
Widgets.DrawMenuSection(rect);
Building_EquipmentOotheca ootheca = SelThing as Building_EquipmentOotheca;
if (ootheca == null)
{
Widgets.Label(rect, "ARA_EquipmentIncubator.NotAnEquipmentOotheca".Translate());
return;
}
rect = rect.ContractedBy(5f);
Rect viewRect = new Rect(0f, 0f, rect.width - 16f, ViewHeight);
Rect scrollRect = new Rect(rect.x, rect.y, rect.width, rect.height);
Widgets.BeginScrollView(scrollRect, ref scrollPosition, viewRect);
float curY = 0f;
Rect titleRect = new Rect(0f, curY, viewRect.width, LabelHeight);
string title = "ARA_EquipmentIncubator.IncubationProgress".Translate();
Text.Font = GameFont.Medium;
Widgets.Label(titleRect, title);
Text.Font = GameFont.Small;
curY += LabelHeight + 15f;
float buttonWidth = (viewRect.width - 10f) / 2f;
Rect speedButtonRect = new Rect(0f, curY, buttonWidth, ButtonHeight);
string speedText = "ARA_EquipmentIncubator.Speed".Translate() + ": " + ootheca.SpeedMultiplier.ToStringPercent();
Color speedColor = Color.white;
if (ootheca.SpeedMultiplier != 1.0f)
{
speedColor = ootheca.SpeedMultiplier > 1.0f ?
new Color(0.2f, 0.8f, 0.2f) :
new Color(0.8f, 0.8f, 0.2f);
}
if (Widgets.ButtonText(speedButtonRect, speedText, true, true, speedColor))
{
// 可选:显示详细信息
}
TooltipHandler.TipRegion(speedButtonRect, () => ootheca.GetSpeedFactorsDescription(), 987654321);
Rect qualityButtonRect = new Rect(buttonWidth + 10f, curY, buttonWidth, ButtonHeight);
string qualityText = "ARA_EquipmentIncubator.Quality".Translate() + ": " + ootheca.QualityMultiplier.ToStringPercent();
Color qualityColor = Color.white;
float qualityMultiplier = ootheca.QualityMultiplier;
if (qualityMultiplier != 1.0f)
{
if (qualityMultiplier > 0.9f)
qualityColor = new Color(0.2f, 0.8f, 0.2f);
else if (qualityMultiplier > 0.7f)
qualityColor = new Color(0.8f, 0.8f, 0.2f);
else if (qualityMultiplier > 0.5f)
qualityColor = new Color(0.9f, 0.6f, 0.2f);
else
qualityColor = new Color(0.8f, 0.2f, 0.2f);
}
if (Widgets.ButtonText(qualityButtonRect, qualityText, true, true, qualityColor))
{
// 可选:显示详细信息
}
TooltipHandler.TipRegion(qualityButtonRect, () => ootheca.GetQualityFactorsDescription(), 987654322);
curY += ButtonHeight + 25f;
if (ootheca.isIncubating && ootheca.incubatingThingDef != null)
{
float progressPercent = ootheca.AdjustedProgressPercent;
float qualityPercent = ootheca.QualityPercent;
float daysRemaining = ootheca.GetRemainingDays();
float hoursRemaining = ootheca.GetRemainingHours();
Rect targetRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight);
Widgets.Label(targetRect, "ARA_EquipmentIncubator.Target".Translate() + ": " + ootheca.incubatingThingDef.LabelCap);
curY += SmallLabelHeight + 20f;
Rect progressBarRect = new Rect(0f, curY, viewRect.width, BarHeight);
Rect progressLabelRect = new Rect(progressBarRect.x, progressBarRect.y - 20, progressBarRect.width, 18);
Text.Anchor = TextAnchor.MiddleCenter;
GUI.color = new Color(0.9f, 0.9f, 0.9f, 1f);
Widgets.Label(progressLabelRect, "ARA_EquipmentIncubator.IncubationProgressLabel".Translate());
Text.Anchor = TextAnchor.UpperLeft;
GUI.color = Color.white;
Widgets.FillableBar(progressBarRect, progressPercent, SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.8f, 0.2f, 0.5f)));
Widgets.FillableBarChangeArrows(progressBarRect, progressPercent);
string progressText = $"{progressPercent:P0}";
Text.Anchor = TextAnchor.MiddleCenter;
Widgets.Label(progressBarRect, progressText);
Text.Anchor = TextAnchor.UpperLeft;
curY += BarHeight + 30f;
Rect qualityBarRect = new Rect(0f, curY, viewRect.width, BarHeight);
Rect qualityLabelRect = new Rect(qualityBarRect.x, qualityBarRect.y - 20, qualityBarRect.width, 18);
Text.Anchor = TextAnchor.MiddleCenter;
GUI.color = new Color(0.9f, 0.9f, 0.9f, 1f);
Widgets.Label(qualityLabelRect, "ARA_EquipmentIncubator.QualityProgress".Translate());
Text.Anchor = TextAnchor.UpperLeft;
GUI.color = Color.white;
Widgets.FillableBar(qualityBarRect, qualityPercent, SolidColorMaterials.NewSolidColorTexture(new Color(0.1f, 0.4f, 0.8f, 0.5f)));
string qualityProgressText = $"{qualityPercent:P0}";
Text.Anchor = TextAnchor.MiddleCenter;
Widgets.Label(qualityBarRect, qualityProgressText);
Text.Anchor = TextAnchor.UpperLeft;
Rect targetQualityRect = new Rect(qualityBarRect.x + qualityBarRect.width - 40, qualityBarRect.y, 40, BarHeight);
GUI.color = new Color(0.8f, 0.8f, 0.8f, 0.7f);
Text.Anchor = TextAnchor.MiddleRight;
Widgets.Label(targetQualityRect, $"{ootheca.QualityMultiplier:P0}");
Text.Anchor = TextAnchor.UpperLeft;
GUI.color = Color.white;
curY += BarHeight + 25f;
Rect timeRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight);
string timeText = "ARA_EquipmentIncubator.TimeRemaining".Translate() + ": " + daysRemaining.ToString("F1") + " " + "ARA_EquipmentIncubator.Days".Translate();
if (hoursRemaining > 0.1f && daysRemaining < 1f)
{
timeText += " (" + hoursRemaining.ToString("F1") + " " + "ARA_EquipmentIncubator.Hours".Translate() + ")";
}
Widgets.Label(timeRect, timeText);
}
else if (ootheca.assignedLarva != null)
{
Rect statusRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight * 2);
if (ootheca.larvaOperateTicksRemaining > 0)
{
float secondsRemaining = ootheca.larvaOperateTicksRemaining / 60f;
Widgets.Label(statusRect, "ARA_EquipmentIncubator.LarvaIsActivatingOotheca".Translate() + "\n" +
secondsRemaining.ToString("F1") + " " + "ARA_EquipmentIncubator.SecondsRemaining".Translate());
}
else
{
Widgets.Label(statusRect, "ARA_EquipmentIncubator.LarvaIsOnTheWay".Translate());
}
curY += SmallLabelHeight * 2 + 15f;
var config = ootheca.EquipmentIncubatorData?.SelectedConfig;
if (config != null)
{
curY += 10f;
Rect targetRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight * 3);
string targetText = "ARA_EquipmentIncubator.ReadyToIncubate".Translate() + "\n" + config.thingDef.LabelCap;
if (!config.IsResearchComplete && config.requiredResearch != null)
{
targetText += "\n" + "(" + "ARA_EquipmentIncubator.Requires".Translate() + ": " + config.requiredResearch.LabelCap + ")";
}
Widgets.Label(targetRect, targetText);
}
}
else
{
var config = ootheca.EquipmentIncubatorData?.SelectedConfig;
if (config != null)
{
Rect targetRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight * 3);
string targetText = "ARA_EquipmentIncubator.ReadyToIncubate".Translate() + "\n" + config.thingDef.LabelCap;
if (!config.IsResearchComplete && config.requiredResearch != null)
{
targetText += "\n" + "(" + "ARA_EquipmentIncubator.Requires".Translate() + ": " + config.requiredResearch.LabelCap + ")";
}
Widgets.Label(targetRect, targetText);
curY += SmallLabelHeight * 3 + 10f;
}
else
{
Rect noTargetRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight);
Widgets.Label(noTargetRect, "ARA_EquipmentIncubator.NoIncubationTargetSelected".Translate());
curY += SmallLabelHeight + 10f;
}
}
curY += 20f;
viewRect.height = curY;
Widgets.EndScrollView();
}
protected override void UpdateSize()
{
size = new Vector2(TabWidth, TabHeight);
}
}
}

View File

@@ -0,0 +1,68 @@
// File: JobDrivers/JobDriver_OperateEquipmentIncubator.cs
using RimWorld;
using System.Collections.Generic;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
public class JobDriver_OperateEquipmentIncubator : JobDriver
{
private const int OperationDuration = 180;
private Building_EquipmentOotheca EquipmentOotheca => (Building_EquipmentOotheca)job.targetA.Thing;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
return pawn.Reserve(job.targetA, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
this.FailOnDespawnedNullOrForbidden(TargetIndex.A);
this.FailOn(() => EquipmentOotheca == null);
yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.InteractionCell)
.FailOnSomeonePhysicallyInteracting(TargetIndex.A);
yield return Toils_General.WaitWith(TargetIndex.A, 10, true, true);
var operate = new Toil();
operate.initAction = () =>
{
EquipmentOotheca?.NotifyLarvaArrived(pawn);
};
operate.tickAction = () =>
{
pawn.rotationTracker.FaceCell(EquipmentOotheca.Position);
};
operate.defaultCompleteMode = ToilCompleteMode.Delay;
operate.defaultDuration = OperationDuration;
operate.WithProgressBar(TargetIndex.A, () =>
(float)(OperationDuration - operate.actor.jobs.curDriver.ticksLeftThisToil) / OperationDuration);
yield return operate;
yield return new Toil
{
initAction = () =>
{
if (EquipmentOotheca != null && pawn != null && pawn.def.defName == "ArachnaeBase_Race_Larva")
{
EquipmentOotheca.NotifyLarvaOperationComplete(pawn);
pawn.Destroy(DestroyMode.Vanish);
}
},
defaultCompleteMode = ToilCompleteMode.Instant
};
}
public override string GetReport()
{
if (EquipmentOotheca != null)
{
return "ARA_EquipmentIncubator.ActivatingOotheca".Translate();
}
return base.GetReport();
}
}
}

View File

@@ -1,4 +1,5 @@
using RimWorld;
// File: Building_Ootheca.cs
using RimWorld;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
@@ -32,6 +33,22 @@ namespace ArachnaeSwarm
private float qualityProgress = 0f;
private float qualityTotal = 0f;
// 缓存的ModExtension
private OothecaIncubatorExtension cachedExtension;
// 获取ModExtension的辅助属性
private OothecaIncubatorExtension Ext
{
get
{
if (cachedExtension == null)
{
cachedExtension = def.GetModExtension<OothecaIncubatorExtension>() ?? OothecaIncubatorExtension.Default;
}
return cachedExtension;
}
}
// 属性访问器
public float SpeedMultiplier
{
@@ -65,92 +82,122 @@ namespace ArachnaeSwarm
{
var builder = new StringBuilder();
builder.AppendLine("IncubationSpeedFactors".Translate());
builder.AppendLine("ARA_OothecaIncubator.SpeedFactors".Translate());
builder.AppendLine();
// 1. 检查是否在孵化间中
bool inIncubatorRoom = IsInIncubatorRoom();
builder.AppendLine(inIncubatorRoom ?
"InIncubatorRoom".Translate() :
"NotInIncubatorRoom".Translate());
if (Ext.requiresIncubatorRoom)
{
builder.AppendLine(inIncubatorRoom ?
"ARA_OothecaIncubator.InIncubatorRoom".Translate() :
"ARA_OothecaIncubator.NotInIncubatorRoom".Translate());
}
// 2. 检查营养液数量
int nutrientSolutionCount = CountNearbyNutrientSolutions();
if (nutrientSolutionCount > 0)
{
builder.AppendLine("NutrientSolutions".Translate(nutrientSolutionCount, nutrientSolutionCount));
builder.AppendLine("ARA_OothecaIncubator.NutrientSolutions".Translate(
nutrientSolutionCount,
nutrientSolutionCount * Ext.nutrientSolutionBonusPerTile * 100));
}
else
{
builder.AppendLine("NoNutrientSolutionsNearby".Translate());
builder.AppendLine("ARA_OothecaIncubator.NoNutrientSolutionsNearby".Translate());
}
// 显示检测半径
builder.AppendLine();
builder.AppendLine("ARA_OothecaIncubator.NutrientDetectionRadius".Translate(Ext.nutrientSolutionRadius));
builder.AppendLine();
builder.AppendLine("TotalSpeedMultiplier".Translate(SpeedMultiplier.ToStringPercent()));
builder.AppendLine("ARA_OothecaIncubator.TotalSpeedMultiplier".Translate(SpeedMultiplier.ToStringPercent()));
return builder.ToString().TrimEndNewlines();
}
// 获取质量乘数的详细因子信息(用于工具提示)
public string GetQualityFactorsDescription()
{
var builder = new StringBuilder();
builder.AppendLine("IncubationQualityFactors".Translate());
builder.AppendLine("ARA_OothecaIncubator.QualityFactors".Translate());
builder.AppendLine();
// 1. 建筑血量损失百分比
float healthPercent = (float)HitPoints / MaxHitPoints;
builder.AppendLine("BuildingHealth".Translate(healthPercent.ToStringPercent()));
// 1. 建筑血量损失百分比(如果启用)
if (Ext.healthAffectsQuality)
{
float healthPercent = (float)HitPoints / MaxHitPoints;
builder.AppendLine("ARA_OothecaIncubator.BuildingHealth".Translate(healthPercent.ToStringPercent()));
}
// 2. 房间的ARA_IncubatorQualityFactor
float roomFactor = GetRoomQualityFactor();
builder.AppendLine(roomFactor == 1.0f ?
"RoomFactorNormal".Translate() :
$"{(roomFactor > 1.0f ? "" : "")} {"RoomFactorModified".Translate()}{roomFactor.ToStringPercent()}");
// 2. 房间的ARA_IncubatorQualityFactor(如果启用)
if (Ext.useRoomQualityFactor)
{
float roomFactor = GetRoomQualityFactor();
builder.AppendLine(roomFactor == 1.0f ?
"ARA_OothecaIncubator.RoomFactorNormal".Translate() :
$"{(roomFactor > 1.0f ? "" : "")} {"ARA_OothecaIncubator.RoomFactorModified".Translate()}{roomFactor.ToStringPercent()}");
}
// 3. 附近每一个ARA_Pawn_Ootheca,每一个-10%
// 3. 附近每一个ARA_Pawn_Ootheca的惩罚
int nearbyOothecaCount = CountNearbyOtherOothecas();
if (nearbyOothecaCount > 0)
{
builder.AppendLine("NearbyOothecas".Translate(nearbyOothecaCount, Mathf.Min(nearbyOothecaCount * 10, 100)));
builder.AppendLine("ARA_OothecaIncubator.NearbyOothecas".Translate(
nearbyOothecaCount,
Mathf.Min(nearbyOothecaCount * Ext.nearbyOothecaPenaltyPerUnit * 100, 100)));
}
else
{
builder.AppendLine("NoNearbyOothecas".Translate());
builder.AppendLine("ARA_OothecaIncubator.NoNearbyOothecas".Translate());
}
// 显示检测半径
builder.AppendLine();
builder.AppendLine("ARA_OothecaIncubator.OothecaDetectionRadius".Translate(Ext.nearbyOothecaRadius));
builder.AppendLine();
builder.AppendLine("TotalQualityMultiplier".Translate(QualityMultiplier.ToStringPercent()));
builder.AppendLine("ARA_OothecaIncubator.TotalQualityMultiplier".Translate(QualityMultiplier.ToStringPercent()));
return builder.ToString().TrimEndNewlines();
}
// 构建呼叫幼虫描述
private string BuildCallLarvaDescription(IncubationConfig config)
{
var builder = new StringBuilder();
builder.AppendLine("CallALarvaToActivate".Translate());
builder.AppendLine("ARA_OothecaIncubator.CallLarvaTitle".Translate());
builder.AppendLine();
builder.AppendLine("LarvaWillComeToTheOotheca".Translate());
builder.AppendLine("ARA_OothecaIncubator.LarvaWillCome".Translate());
builder.AppendLine(config.pawnKind.LabelCap);
builder.AppendLine();
// 显示幼虫搜索半径
if (Ext.larvaSearchRadius < 999f)
{
builder.AppendLine("ARA_OothecaIncubator.LarvaSearchRadius".Translate(Ext.larvaSearchRadius));
}
return builder.ToString().TrimEndNewlines();
}
// 呼叫幼虫
private void CallLarva()
{
// 检查是否已经在孵化中或有幼虫在任务中
if (isIncubating)
{
Messages.Message("AlreadyIncubating".Translate() + " " + "CancelCurrentIncubationFirst".Translate(),
Messages.Message("ARA_OothecaIncubator.AlreadyIncubating".Translate() + " " + "ARA_OothecaIncubator.CancelFirst".Translate(),
MessageTypeDefOf.RejectInput);
return;
}
if (assignedLarva != null)
{
Messages.Message("LarvaAlreadyOnTheWay".Translate(),
Messages.Message("ARA_OothecaIncubator.LarvaAlreadyOnWay".Translate(),
MessageTypeDefOf.RejectInput);
return;
}
@@ -159,7 +206,7 @@ namespace ArachnaeSwarm
var larva = FindLarva();
if (larva == null)
{
Messages.Message("NoAvailableLarvaeFound".Translate() + " " + "LarvaMustBeOfRace".Translate(),
Messages.Message("ARA_OothecaIncubator.NoLarvaeFound".Translate() + " " + "ARA_OothecaIncubator.LarvaMustBeRace".Translate(),
MessageTypeDefOf.RejectInput);
return;
}
@@ -171,9 +218,10 @@ namespace ArachnaeSwarm
assignedLarva = larva;
Messages.Message("LarvaCalled".Translate() + " " + "ItWillArriveShortly".Translate(),
Messages.Message("ARA_OothecaIncubator.LarvaCalled".Translate() + " " + "ARA_OothecaIncubator.ArriveShortly".Translate(),
MessageTypeDefOf.PositiveEvent);
}
// 幼虫开始操作
public void NotifyLarvaArrived(Pawn larva)
{
@@ -189,9 +237,10 @@ namespace ArachnaeSwarm
assignedLarva = larva;
// 显示消息
Messages.Message("LarvaHasArrived".Translate() + " " + "AndIsActivatingTheOotheca".Translate(),
Messages.Message("ARA_OothecaIncubator.LarvaArrived".Translate() + " " + "ARA_OothecaIncubator.ActivatingOotheca".Translate(),
MessageTypeDefOf.SilentInput);
}
// 幼虫完成操作由JobDriver调用
public void NotifyLarvaOperationComplete(Pawn larva)
{
@@ -228,10 +277,11 @@ namespace ArachnaeSwarm
larvaOperateTicksRemaining = 0;
// 显示消息
Messages.Message("IncubationStartedFor".Translate() + " " + incubatingPawnKind.LabelCap + ". " +
"ProcessWillCompleteIn".Translate() + " " + config.daysRequired + " " + "DaysBaseTime".Translate(),
Messages.Message("ARA_OothecaIncubator.IncubationStarted".Translate() + " " + incubatingPawnKind.LabelCap + ". " +
"ARA_OothecaIncubator.ProcessWillComplete".Translate() + " " + config.daysRequired + " " + "ARA_OothecaIncubator.DaysBaseTime".Translate(),
MessageTypeDefOf.PositiveEvent);
}
// 取消孵化
private void CancelIncubation()
{
@@ -244,9 +294,10 @@ namespace ArachnaeSwarm
qualityProgress = 0f;
qualityTotal = 0f;
Messages.Message("IncubationCancelled".Translate() + " " + "ContentsLost".Translate(),
Messages.Message("ARA_OothecaIncubator.IncubationCancelled".Translate() + " " + "ARA_OothecaIncubator.ContentsLost".Translate(),
MessageTypeDefOf.NeutralEvent);
}
// 完成孵化
private void CompleteIncubation()
{
@@ -277,16 +328,19 @@ namespace ArachnaeSwarm
qualityTotal = 0f;
// 显示消息
string qualityText = finalQualityPercent >= 0.9f ? "QualityExcellent".Translate() :
finalQualityPercent >= 0.7f ? "QualityGood".Translate() :
finalQualityPercent >= 0.5f ? "QualityAverage".Translate() :
finalQualityPercent >= 0.3f ? "QualityPoor".Translate() : "QualityVeryPoor".Translate();
string qualityText = finalQualityPercent >= 0.9f ? "ARA_OothecaIncubator.QualityExcellent".Translate() :
finalQualityPercent >= 0.7f ? "ARA_OothecaIncubator.QualityGood".Translate() :
finalQualityPercent >= 0.5f ? "ARA_OothecaIncubator.QualityAverage".Translate() :
finalQualityPercent >= 0.3f ? "ARA_OothecaIncubator.QualityPoor".Translate() : "ARA_OothecaIncubator.QualityVeryPoor".Translate();
Messages.Message("IncubationComplete".Translate() + " " + pawn.LabelCap + " " +
"HasEmergedWith".Translate() + " " + qualityText + " " +
"quality".Translate() + " (" + finalQualityPercent.ToStringPercent() + ").",
Messages.Message("ARA_OothecaIncubator.IncubationComplete".Translate() + " " + pawn.LabelCap + " " +
"ARA_OothecaIncubator.HasEmergedWith".Translate() + " " + qualityText + " " +
"ARA_OothecaIncubator.Quality".Translate() + " (" + finalQualityPercent.ToStringPercent() + ").",
MessageTypeDefOf.PositiveEvent);
Destroy();
}
// 显示额外信息(简化版本,只显示速率,不显示因子)
public override string GetInspectString()
{
@@ -305,23 +359,23 @@ namespace ArachnaeSwarm
float hoursRemaining = GetRemainingHours();
if (builder.Length > 0) builder.AppendLine();
builder.Append("Incubating".Translate() + ": " + incubatingPawnKind.LabelCap);
builder.Append("ARA_OothecaIncubator.Incubating".Translate() + ": " + incubatingPawnKind.LabelCap);
builder.AppendLine();
builder.Append("Progress".Translate() + ": " + progressPercent.ToStringPercent());
builder.Append("ARA_OothecaIncubator.Progress".Translate() + ": " + progressPercent.ToStringPercent());
builder.AppendLine();
// 显示剩余时间
string timeText = "TimeRemaining".Translate() + ": " + daysRemaining.ToString("F1") + " " + "Days".Translate();
string timeText = "ARA_OothecaIncubator.TimeRemaining".Translate() + ": " + daysRemaining.ToString("F1") + " " + "ARA_OothecaIncubator.Days".Translate();
if (hoursRemaining > 0.1f && daysRemaining < 1f)
{
timeText += " (" + hoursRemaining.ToString("F1") + " " + "Hours".Translate() + ")";
timeText += " (" + hoursRemaining.ToString("F1") + " " + "ARA_OothecaIncubator.Hours".Translate() + ")";
}
builder.Append(timeText);
// 显示速度和质量(简化版本)
builder.AppendLine();
builder.Append("Speed".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " +
"Quality".Translate() + ": " + QualityMultiplier.ToStringPercent());
builder.Append("ARA_OothecaIncubator.Speed".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " +
"ARA_OothecaIncubator.Quality".Translate() + ": " + QualityMultiplier.ToStringPercent());
}
else if (assignedLarva != null)
{
@@ -329,11 +383,11 @@ namespace ArachnaeSwarm
if (larvaOperateTicksRemaining > 0)
{
float secondsRemaining = larvaOperateTicksRemaining / 60f;
builder.Append("LarvaIsOperating".Translate() + ": " + secondsRemaining.ToString("F1") + " " + "SRemaining".Translate());
builder.Append("ARA_OothecaIncubator.LarvaOperating".Translate() + ": " + secondsRemaining.ToString("F1") + " " + "ARA_OothecaIncubator.SRemaining".Translate());
}
else
{
builder.Append("LarvaIsOnTheWay".Translate());
builder.Append("ARA_OothecaIncubator.LarvaOnWay".Translate());
}
}
else if (!isIncubating)
@@ -342,12 +396,12 @@ namespace ArachnaeSwarm
if (config != null)
{
if (builder.Length > 0) builder.AppendLine();
builder.Append("Target".Translate() + ": " + config.pawnKind.LabelCap);
builder.Append("ARA_OothecaIncubator.Target".Translate() + ": " + config.pawnKind.LabelCap);
// 只显示当前乘数,不显示条件详情
builder.AppendLine();
builder.Append("SpeedMultiplier".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " +
"QualityMultiplier".Translate() + ": " + QualityMultiplier.ToStringPercent());
builder.Append("ARA_OothecaIncubator.SpeedMultiplier".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " +
"ARA_OothecaIncubator.QualityMultiplier".Translate() + ": " + QualityMultiplier.ToStringPercent());
}
}
@@ -404,9 +458,9 @@ namespace ArachnaeSwarm
{
yield return new Command_Action
{
defaultLabel = "CallLarva".Translate(),
defaultLabel = "ARA_OothecaIncubator.CallLarva".Translate(),
defaultDesc = BuildCallLarvaDescription(config),
icon = ContentFinder<Texture2D>.Get("UI/Commands/CallLarva", false) ?? BaseContent.BadTex,
icon = ContentFinder<Texture2D>.Get("ArachnaeSwarm/UI/Commands/ARA_CallLarva", false) ?? BaseContent.BadTex,
action = CallLarva,
hotKey = KeyBindingDefOf.Misc3
};
@@ -417,8 +471,8 @@ namespace ArachnaeSwarm
{
yield return new Command_Action
{
defaultLabel = "CancelIncubation".Translate(),
defaultDesc = "CancelIncubationDesc".Translate(),
defaultLabel = "ARA_OothecaIncubator.CancelIncubation".Translate(),
defaultDesc = "ARA_OothecaIncubator.CancelIncubationDesc".Translate(),
icon = ContentFinder<Texture2D>.Get("UI/Commands/Cancel", false) ?? TexCommand.ClearPrioritizedWork,
action = CancelIncubation
};
@@ -461,9 +515,9 @@ namespace ArachnaeSwarm
{
if (config != null && config.pawnKind != null)
{
return (props?.buttonLabel ?? "Incubate: {0}").Translate(config.pawnKind.LabelCap);
return (props?.buttonLabel ?? "ARA_OothecaIncubator.IncubateLabel").Translate(config.pawnKind.LabelCap);
}
return (props?.buttonLabel ?? "Incubate: {0}").Translate("None");
return (props?.buttonLabel ?? "ARA_OothecaIncubator.IncubateLabel").Translate("None");
}
// 构建切换按钮描述
@@ -472,7 +526,7 @@ namespace ArachnaeSwarm
var builder = new StringBuilder();
// 第一部分:按钮功能说明
builder.AppendLine((props?.buttonDesc ?? "IncubatorButtonDesc").Translate());
builder.AppendLine((props?.buttonDesc ?? "ARA_OothecaIncubator.ButtonDesc").Translate());
builder.AppendLine();
if (config != null)
@@ -480,14 +534,14 @@ namespace ArachnaeSwarm
// 第二部分:当前选择的详细信息
if (config.pawnKind != null)
{
builder.AppendLine($"IncubatorButtonLabel".Translate(config.pawnKind.LabelCap));
builder.AppendLine($"ARA_OothecaIncubator.ButtonLabel".Translate(config.pawnKind.LabelCap));
if (!string.IsNullOrEmpty(config.pawnKind.description))
{
builder.AppendLine(config.pawnKind.description);
}
}
builder.AppendLine($"IncubationTime".Translate(config.daysRequired));
builder.AppendLine($"ARA_OothecaIncubator.IncubationTime".Translate(config.daysRequired));
if (config.requiredResearch != null)
{
@@ -503,7 +557,7 @@ namespace ArachnaeSwarm
}
builder.AppendLine();
builder.AppendLine("IncubatorButtonDesc".Translate());
builder.AppendLine("ARA_OothecaIncubator.ButtonDesc".Translate());
return builder.ToString().TrimEndNewlines();
}
@@ -555,7 +609,7 @@ namespace ArachnaeSwarm
{
option.Label = prefix + label;
option.Disabled = true;
option.tooltip = description + "\n\n "+ "ResearchRequired".Translate() + " " + config.requiredResearch.LabelCap;
option.tooltip = description + "\n\n "+ "ARA_OothecaIncubator.ResearchRequired".Translate() + " " + config.requiredResearch.LabelCap;
}
options.Add(option);
@@ -564,7 +618,7 @@ namespace ArachnaeSwarm
if (options.Count > 0)
{
Find.WindowStack.Add(new FloatMenu(options,
(props?.menuTitle ?? "Select Incubation Target").Translate()));
(props?.menuTitle ?? "ARA_OothecaIncubator.MenuTitle").Translate()));
}
}
@@ -577,24 +631,35 @@ namespace ArachnaeSwarm
var config = IncubatorData.SelectedConfig;
if (config != null && config.pawnKind != null)
{
Messages.Message($"Incubation target switched to: {config.pawnKind.LabelCap}",
Messages.Message($"ARA_OothecaIncubator.TargetSwitched".Translate(config.pawnKind.LabelCap),
this, MessageTypeDefOf.SilentInput);
}
}
}
// 查找幼虫 - 现在通过种族查找
// 查找幼虫 - 现在通过种族查找,并考虑搜索半径
private Pawn FindLarva()
{
// 查找地图中属于玩家派系的ArachnaeBase_Race_Larva幼虫
var map = Map;
if (map == null) return null;
// 获取搜索半径
float searchRadius = Ext.larvaSearchRadius;
foreach (var pawn in map.mapPawns.SpawnedPawnsInFaction(Faction))
{
// 检查pawn种族是否为幼虫
if (pawn.def.defName == "ArachnaeBase_Race_Larva")
{
// 检查是否在搜索半径内如果搜索半径小于999
if (searchRadius < 999f)
{
float distance = pawn.Position.DistanceTo(Position);
if (distance > searchRadius)
continue;
}
// 检查pawn是否能够移动且没有其他重要任务
if (!pawn.Downed && !pawn.InMentalState &&
pawn.mindState != null &&
@@ -647,62 +712,33 @@ namespace ArachnaeSwarm
// 应用质量效果到生成的pawn
private void ApplyQualityEffects(Pawn pawn, float qualityPercent)
{
//// 质量影响根据质量百分比调整pawn的属性
//if (qualityPercent < 1.0f)
//{
// // 1. 健康影响
// float healthFactor = Mathf.Lerp(0.5f, 1.0f, qualityPercent);
// if (healthFactor < 1.0f)
// {
// // 减少最大生命值
// foreach (var part in pawn.health.hediffSet.GetNotMissingParts())
// {
// var healthDiff = part.def.GetMaxHealth(pawn) * (1.0f - healthFactor);
// if (healthDiff > 0)
// {
// pawn.health.AddHediff(HediffDefOf.Bruise, part);
// }
// }
// }
// // 2. 年龄影响如果质量低pawn可能会以较大年龄出生
// if (qualityPercent < 0.6f)
// {
// float ageBonus = (1.0f - qualityPercent) * 5; // 最多增加5岁
// pawn.ageTracker.AgeBiologicalTicks += (long)(ageBonus * 3600000f); // 每岁约3600000ticks
// }
// // 3. 能力影响
// if (pawn.skills != null && qualityPercent < 0.8f)
// {
// float skillFactor = Mathf.Lerp(0.5f, 1.0f, qualityPercent);
// foreach (var skill in pawn.skills.skills)
// {
// skill.levelInt = Mathf.RoundToInt(skill.levelInt * skillFactor);
// }
// }
//}
// 质量影响根据质量百分比调整pawn的属性
// 这里可以添加具体的质量效果逻辑
}
// 检查是否在孵化间中
private bool IsInIncubatorRoom()
{
// 如果不要求孵化间总是返回true
if (!Ext.requiresIncubatorRoom)
return true;
var room = this.GetRoom();
if (room == null) return false;
return room.Role != null && room.Role.defName == "ARA_Incubator_Room";
}
// 计算周围5格内的营养液数量
// 计算周围指定半径内的营养液数量
private int CountNearbyNutrientSolutions()
{
var map = Map;
if (map == null) return 0;
int count = 0;
int radius = 5; // 4格半径
int radius = Ext.NutrientSolutionRadiusInt;
// 检查周围5格范围内的所有单元格
// 检查指定半径范围内的所有单元格
for (int x = -radius; x <= radius; x++)
{
for (int y = -radius; y <= radius; y++)
@@ -728,6 +764,10 @@ namespace ArachnaeSwarm
// 计算房间质量因子
private float GetRoomQualityFactor()
{
// 如果不使用房间质量因子返回1.0
if (!Ext.useRoomQualityFactor)
return 1.0f;
var room = this.GetRoom();
if (room == null) return 1.0f;
@@ -759,17 +799,32 @@ namespace ArachnaeSwarm
// 检查是否为ARA_Pawn_Ootheca
if (building.def.defName == "ARA_Pawn_Ootheca")
{
// 检查是否在同一个房间或附近
var room = building.GetRoom();
if (room != null)
bool isNearby = false;
// 检查是否在同房间内
if (Ext.checkSameRoomForOotheca)
{
// 如果在同一个房间或者距离较近5格内
float distance = building.Position.DistanceTo(this.Position);
if (room == this.GetRoom() || distance <= 5f)
var room = building.GetRoom();
if (room != null && room == this.GetRoom())
{
count++;
isNearby = true;
}
}
// 检查是否在指定半径内
if (!isNearby)
{
float distance = building.Position.DistanceTo(this.Position);
if (distance <= Ext.nearbyOothecaRadius)
{
isNearby = true;
}
}
if (isNearby)
{
count++;
}
}
}
@@ -781,15 +836,15 @@ namespace ArachnaeSwarm
{
float multiplier = 1.0f;
// 1. 检查是否在孵化间中
if (!IsInIncubatorRoom())
// 1. 检查是否在孵化间中(如果要求)
if (Ext.requiresIncubatorRoom && !IsInIncubatorRoom())
{
multiplier *= 0.8f; // 不在孵化间中速度80%
multiplier *= Ext.speedPenaltyOutsideIncubator; // 应用惩罚
}
// 2. 计算周围营养液的加成
int nutrientSolutionCount = CountNearbyNutrientSolutions();
float nutrientBonus = 1.0f + (nutrientSolutionCount * 0.01f); // 每个+1%
float nutrientBonus = 1.0f + (nutrientSolutionCount * Ext.nutrientSolutionBonusPerTile);
multiplier *= nutrientBonus;
@@ -802,17 +857,23 @@ namespace ArachnaeSwarm
{
float multiplier = 1.0f;
// 1. 建筑血量损失百分比
float healthPercent = (float)HitPoints / MaxHitPoints;
multiplier *= healthPercent;
// 1. 建筑血量损失百分比(如果启用)
if (Ext.healthAffectsQuality)
{
float healthPercent = (float)HitPoints / MaxHitPoints;
multiplier *= healthPercent;
}
// 2. 房间的ARA_IncubatorQualityFactor
float roomFactor = GetRoomQualityFactor();
multiplier *= roomFactor;
// 2. 房间的ARA_IncubatorQualityFactor(如果启用)
if (Ext.useRoomQualityFactor)
{
float roomFactor = GetRoomQualityFactor();
multiplier *= roomFactor;
}
// 3. 附近每一个ARA_Pawn_Ootheca,每一个-10%
// 3. 附近每一个ARA_Pawn_Ootheca的惩罚
int nearbyOothecaCount = CountNearbyOtherOothecas();
float oothecaPenalty = Mathf.Max(0f, 1.0f - (nearbyOothecaCount * 0.10f)); // 最多减到0
float oothecaPenalty = Mathf.Max(0f, 1.0f - (nearbyOothecaCount * Ext.nearbyOothecaPenaltyPerUnit));
multiplier *= oothecaPenalty;
// 确保乘数在合理范围内0-1

View File

@@ -0,0 +1,64 @@
// File: ModExtensions/OothecaIncubatorExtension.cs
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
public class OothecaIncubatorExtension : DefModExtension
{
// 营养液检测半径(单位:格)
public float nutrientSolutionRadius = 5f;
// 其他卵距离检测半径(单位:格)
public float nearbyOothecaRadius = 5f;
// 是否检查同房间内的其他卵(默认:是)
public bool checkSameRoomForOotheca = true;
// 营养液加成比例(每个营养液增加多少百分比的速度)
public float nutrientSolutionBonusPerTile = 0.01f; // 默认每个+1%
// 附近其他卵的惩罚比例(每个减少多少百分比的质量)
public float nearbyOothecaPenaltyPerUnit = 0.10f; // 默认每个-10%
// 幼虫搜索半径(单位:格)
public float larvaSearchRadius = 999f; // 默认在整个地图搜索
// 是否需要在孵化间内才能正常工作(默认:否)
public bool requiresIncubatorRoom = false;
// 不在孵化间内的速度惩罚(百分比)
public float speedPenaltyOutsideIncubator = 0.8f; // 默认80%
// 质量因子房间检查(默认:是)
public bool useRoomQualityFactor = true;
// 建筑血量影响质量(默认:是)
public bool healthAffectsQuality = true;
// 获取营养液检测半径的整数形式(用于循环)
public int NutrientSolutionRadiusInt => (int)nutrientSolutionRadius;
// 获取其他卵距离检测半径的整数形式
public int NearbyOothecaRadiusInt => (int)nearbyOothecaRadius;
public static OothecaIncubatorExtension Get(Thing thing)
{
if (thing?.def?.GetModExtension<OothecaIncubatorExtension>() is OothecaIncubatorExtension ext)
return ext;
return null;
}
// 验证配置是否有效
public bool IsValid()
{
return nutrientSolutionRadius >= 0f &&
nearbyOothecaRadius >= 0f &&
nutrientSolutionBonusPerTile >= 0f &&
nearbyOothecaPenaltyPerUnit >= 0f;
}
// 获取默认扩展(用于兼容性)
public static OothecaIncubatorExtension Default => new OothecaIncubatorExtension();
}
}

View File

@@ -0,0 +1,156 @@
// File: JobDriver_ExtractHoney.cs
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
public class JobDriver_ExtractHoney : JobDriver
{
private const int ExtractDurationTicks = 180; // 3秒 = 180 ticks
private Need_HoneyProduction HoneyNeed => pawn.needs?.TryGetNeed<Need_HoneyProduction>();
// 使用挤出效果
private static readonly EffecterDef ExtractEffect = ARA_EffecterDefOf.EatVegetarian;
private static readonly SoundDef ExtractSound = SoundDefOf.RawMeat_Eat;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
// 只需要保留目标位置
return pawn.Reserve(job.targetA, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
// 确保自身有蜜罐需求且达到挤出阈值
this.FailOn(() => HoneyNeed == null || !CanExtract(HoneyNeed));
// Toil 1: 移动到目标位置
yield return Toils_Goto.GotoCell(TargetIndex.A, PathEndMode.Touch);
// Toil 2: 执行挤出工作
Toil extractToil = new Toil
{
initAction = delegate
{
// 初始化挤出动作
},
tickAction = delegate
{
// 什么都不做,等待完成
},
defaultCompleteMode = ToilCompleteMode.Delay,
defaultDuration = ExtractDurationTicks
};
// 添加特效和音效
extractToil.WithEffect(() => ExtractEffect, TargetIndex.A);
extractToil.PlaySustainerOrSound(() => ExtractSound);
extractToil.AddFinishAction(delegate
{
// 执行挤出逻辑
ExtractHoney();
});
extractToil.WithProgressBar(TargetIndex.A,
() => extractToil.actor.jobs.curDriver.ticksLeftThisToil / (float)ExtractDurationTicks);
yield return extractToil;
// Toil 3: 完成后的清理
yield return new Toil
{
initAction = delegate
{
// 可以在这里检查是否还需要继续挤出
// 如果蜜罐存量达到阈值,可以安排下一个挤出工作
if (HoneyNeed != null && CanExtract(HoneyNeed))
{
pawn.jobs.jobQueue.EnqueueLast(CreateExtractJob());
}
},
defaultCompleteMode = ToilCompleteMode.Instant
};
}
// 检查是否可以挤出根据MaxLevel决定阈值
private bool CanExtract(Need_HoneyProduction honeyNeed)
{
// 如果MaxLevel大于1.5需要超过80%储量才考虑挤蜜
if (honeyNeed.MaxLevel > 1.5f)
{
return honeyNeed.CurLevelPercentage > 0.8f;
}
// 否则在大于1时挤蜜
else
{
return honeyNeed.CurLevel >= 1.0f;
}
}
// 获取挤出阈值(用于显示等)
private float GetExtractThreshold(Need_HoneyProduction honeyNeed)
{
if (honeyNeed.MaxLevel > 1.5f)
{
return honeyNeed.MaxLevel * 0.8f;
}
else
{
return 1.0f;
}
}
// 挤出蜂蜜的逻辑
private void ExtractHoney()
{
if (HoneyNeed == null)
return;
// 计算要生成的虫蜜数量(蜜罐等级整数部分)
int jellyCount = (int)HoneyNeed.CurLevel;
// 确保至少生成1个
if (jellyCount < 1)
return;
// 创建虫蜜
Thing jelly = ThingMaker.MakeThing(DefDatabase<ThingDef>.GetNamed("ARA_InsectJelly"));
jelly.stackCount = jellyCount;
// 尝试将虫蜜放在目标位置
GenPlace.TryPlaceThing(jelly, job.targetA.Cell, pawn.Map, ThingPlaceMode.Near);
// 减少蜜罐存量
HoneyNeed.ExtractHoney(jellyCount);
// 播放视觉反馈
MoteMaker.ThrowText(job.targetA.Cell.ToVector3Shifted(), pawn.Map,"ARA_ExtractHoney_Message".Translate(jellyCount));
}
// 创建挤出工作的辅助方法
private Job CreateExtractJob()
{
// 寻找最近的空地
IntVec3 targetCell = FindNearestEmptyCell(pawn.Position, pawn.Map);
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_ExtractHoney"), targetCell);
return job;
}
// 寻找最近的空单元格
private IntVec3 FindNearestEmptyCell(IntVec3 from, Map map)
{
if (CellFinder.TryFindBestPawnStandCell(pawn, out IntVec3 cell, false))
{
return cell;
}
// 如果找不到最佳位置,使用原位置
return from;
}
}
}

View File

@@ -0,0 +1,105 @@
// File: JobDriver_FeedWithHoney.cs
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
public class JobDriver_FeedWithHoney : JobDriver
{
private const int FeedDurationTicks = 180; // 3秒 = 180 ticks (60 ticks/秒)
private const float FoodTransferPerTick = 0.01f; // 每tick传输的食物量
private Pawn TargetPawn => job.targetA.Pawn;
private Need_HoneyProduction HoneyNeed => pawn.needs?.TryGetNeed<Need_HoneyProduction>();
private Need_Food TargetFoodNeed => TargetPawn.needs?.TryGetNeed<Need_Food>();
// 使用喂养效果 - 参考Toils_Ingest中的用法
private static readonly EffecterDef FeedEffect = ARA_EffecterDefOf.EatVegetarian;
private static readonly SoundDef FeedSound = SoundDefOf.RawMeat_Eat;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
if (TargetPawn == null || TargetFoodNeed == null || HoneyNeed == null)
return false;
// 保留目标pawn的位置以便接近
return pawn.Reserve(TargetPawn, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
// 确保目标有效
this.FailOnDespawnedOrNull(TargetIndex.A);
this.FailOn(() => TargetPawn.Dead || TargetFoodNeed == null || HoneyNeed == null);
// Toil 1: 移动到目标面前
yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.Touch)
.FailOnDespawnedOrNull(TargetIndex.A);
// Toil 2: 进行喂养工作参考Toils_Ingest中的效果添加方式
Toil feedToil = new Toil
{
initAction = delegate
{
// 初始化喂养动作
},
tickAction = delegate
{
// 每tick检查是否还能继续喂养
if (HoneyNeed.CurLevel <= 0 || TargetFoodNeed.CurLevelPercentage >= 1.0f)
{
ReadyForNextToil();
return;
}
// 计算本次tick传输的量
float transferAmount = FoodTransferPerTick;
float honeyAvailable = HoneyNeed.CurLevel;
// 确保不超过蜜罐存量
if (transferAmount > honeyAvailable)
transferAmount = honeyAvailable;
// 确保不超过目标的需求
float targetFoodSpace = TargetFoodNeed.MaxLevel - TargetFoodNeed.CurLevel;
if (transferAmount > targetFoodSpace)
transferAmount = targetFoodSpace;
// 执行传输
HoneyNeed.ExtractHoney(transferAmount);
TargetFoodNeed.CurLevel += transferAmount;
},
defaultCompleteMode = ToilCompleteMode.Delay,
defaultDuration = FeedDurationTicks
};
// 添加特效和音效参考Toils_Ingest.AddIngestionEffects
feedToil.WithEffect(() => FeedEffect, TargetIndex.A);
feedToil.PlaySustainerOrSound(() => FeedSound);
feedToil.AddFinishAction(delegate
{
// 喂养完成后给予社交互动奖励
TargetPawn.interactions?.TryInteractWith(pawn, InteractionDefOf.Chitchat);
});
feedToil.WithProgressBar(TargetIndex.A,
() => feedToil.actor.jobs.curDriver.ticksLeftThisToil / (float)FeedDurationTicks,
interpolateBetweenActorAndTarget: true);
yield return feedToil;
// 修改移除了第三个Toil中的提示消息只剩下一个简单的完成Toil
yield return new Toil
{
initAction = delegate
{
// 这里可以放置任何清理代码,但不要显示消息
},
defaultCompleteMode = ToilCompleteMode.Instant
};
}
}
}

View File

@@ -0,0 +1,105 @@
// File: ThinkNode_JobGiver_ExtractHoney.cs
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
public class ThinkNode_JobGiver_ExtractHoney : ThinkNode_JobGiver
{
private const int ScanIntervalTicks = 300; // 5秒扫描一次
private int lastScanTick = -99999;
protected override Job TryGiveJob(Pawn pawn)
{
// 检查是否应该扫描
int currentTick = Find.TickManager.TicksGame;
if (currentTick - lastScanTick < ScanIntervalTicks)
return null;
lastScanTick = currentTick;
// 检查甲壳剥离组件和开关状态
Comp_ChitinStripping stripComp = pawn.TryGetComp<Comp_ChitinStripping>();
// 检查开关是否开启
if (stripComp == null || !stripComp.CanStripChitin || !stripComp.CanStripNow(pawn))
return null;
// 检查自身需求
Need_HoneyProduction honeyNeed = pawn.needs?.TryGetNeed<Need_HoneyProduction>();
if (honeyNeed == null || !CanExtract(honeyNeed))
return null;
// 如果已经有挤出工作,则不分配新工作
if (pawn.CurJob != null && pawn.CurJob.def == DefDatabase<JobDef>.GetNamed("ARA_ExtractHoney"))
return null;
// 寻找最近的空地
IntVec3 targetCell = FindNearestEmptyCell(pawn);
if (!targetCell.IsValid)
return null;
// 确保可以到达
if (!pawn.CanReach(targetCell, PathEndMode.Touch, Danger.Some))
return null;
// 创建挤出工作
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_ExtractHoney"), targetCell);
job.count = 1;
return job;
}
// 检查是否可以挤出与JobDriver中的逻辑保持一致
private bool CanExtract(Need_HoneyProduction honeyNeed)
{
// 如果MaxLevel大于1.5需要超过80%储量才考虑挤蜜
if (honeyNeed.MaxLevel > 1.5f)
{
return honeyNeed.CurLevelPercentage > 0.8f;
}
// 否则在大于1时挤蜜
else
{
return honeyNeed.CurLevel >= 1.0f;
}
}
// 寻找最近的空单元格
private IntVec3 FindNearestEmptyCell(Pawn pawn)
{
// 首先检查当前位置周围3格内是否有空地
for (int radius = 1; radius <= 3; radius++)
{
foreach (IntVec3 cell in GenRadial.RadialCellsAround(pawn.Position, radius, true))
{
if (cell.InBounds(pawn.Map) &&
cell.Standable(pawn.Map) &&
cell.GetFirstItem(pawn.Map) == null &&
!cell.GetTerrain(pawn.Map).IsWater)
{
return cell;
}
}
}
// 如果找不到使用CellFinder寻找
if (CellFinder.TryFindBestPawnStandCell(pawn, out IntVec3 bestCell, false))
{
return bestCell;
}
// 最后尝试使用原位置(确保不是水面)
if (!pawn.Position.GetTerrain(pawn.Map).IsWater)
{
return pawn.Position;
}
return IntVec3.Invalid;
}
}
}

View File

@@ -0,0 +1,107 @@
// File: ThinkNode_JobGiver_FeedWithHoney.cs
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
public class ThinkNode_JobGiver_FeedWithHoney : ThinkNode_JobGiver
{
private const float MinFoodNeedThreshold = 0.2f; // 目标需要喂养的阈值
private const float MinHoneyThreshold = 0.1f; // 蜜罐低于10%时不喂养
private const int ScanIntervalTicks = 250; // 扫描间隔 (约4秒)
private const float MaxDistance = 30f; // 最大寻找距离
private int lastScanTick = -99999;
protected override Job TryGiveJob(Pawn pawn)
{
// 检查是否应该扫描
int currentTick = Find.TickManager.TicksGame;
if (currentTick - lastScanTick < ScanIntervalTicks)
return null;
lastScanTick = currentTick;
// 检查自身需求
Need_HoneyProduction honeyNeed = pawn.needs?.TryGetNeed<Need_HoneyProduction>();
// 修改检查蜜罐存量是否低于10%,如果是则不喂养
if (honeyNeed == null || honeyNeed.IsEmpty || honeyNeed.CurLevelPercentage < MinHoneyThreshold)
return null;
// 扫描潜在的喂养目标
List<Pawn> potentialTargets = new List<Pawn>();
// 获取地图上所有pawn
foreach (Pawn target in pawn.Map.mapPawns.AllPawnsSpawned)
{
// 跳过自己
if (target == pawn)
continue;
// 检查是否属于己方势力(殖民者、奴隶、囚犯)
if (!IsFriendlyPawn(pawn, target))
continue;
// 检查距离
if (pawn.Position.DistanceTo(target.Position) > MaxDistance)
continue;
// 检查是否需要食物
Need_Food targetFoodNeed = target.needs?.TryGetNeed<Need_Food>();
if (targetFoodNeed == null || targetFoodNeed.CurLevelPercentage > MinFoodNeedThreshold)
continue;
// 检查目标是否可到达
if (!pawn.CanReserveAndReach(target, PathEndMode.Touch, Danger.Some))
continue;
potentialTargets.Add(target);
}
// 如果没有目标
if (potentialTargets.Count == 0)
return null;
// 按需求紧急程度排序(最饿的优先)
potentialTargets.Sort((a, b) =>
{
Need_Food foodA = a.needs?.TryGetNeed<Need_Food>();
Need_Food foodB = b.needs?.TryGetNeed<Need_Food>();
return foodA?.CurLevelPercentage.CompareTo(foodB?.CurLevelPercentage) ?? 0;
});
// 选择最饥饿的目标
Pawn targetPawn = potentialTargets[0];
// 创建喂养工作
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_FeedWithHoney"), targetPawn);
job.count = 1;
return job;
}
private bool IsFriendlyPawn(Pawn feeder, Pawn target)
{
// 检查目标是否是殖民者、奴隶或囚犯
if (target.Faction == Faction.OfPlayer)
return true;
// 检查是否是囚犯(属于玩家)
if (target.IsPrisonerOfColony)
return true;
// 检查是否是奴隶
if (target.IsSlaveOfColony)
return true;
// 检查是否和喂养者同一派系
if (target.Faction != null && target.Faction == feeder.Faction)
return true;
return false;
}
}
}

View File

@@ -0,0 +1,29 @@
// File: Comps/CompProperties_ChitinStripping.cs
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_ChitinStripping : CompProperties
{
// 是否允许剥离甲壳
public bool canStripChitin = true;
// 剥离的阈值(甲壳存量的百分比)
public float stripThreshold = 0.8f;
// 每次剥离产生的最小甲壳数量
public int minStripAmount = 1;
// 剥离工作的间隔ticks
public int stripInterval = 3000; // 50秒
// 甲壳物品定义
public ThingDef carapaceThingDef = null;
public CompProperties_ChitinStripping()
{
compClass = typeof(Comp_ChitinStripping);
}
}
}

View File

@@ -0,0 +1,103 @@
// File: Comps/Comp_ChitinStripping.cs
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
public class Comp_ChitinStripping : ThingComp
{
public CompProperties_ChitinStripping Props => (CompProperties_ChitinStripping)props;
// 剥离开关状态
private bool stripEnabled = true;
// 上次剥离时间
private int lastStripTick = -99999;
// 属性访问器
public bool StripEnabled
{
get => stripEnabled;
set => stripEnabled = value;
}
public bool CanStripChitin => Props.canStripChitin && stripEnabled;
public float StripThreshold => Props.stripThreshold;
// 获取甲壳物品定义
public ThingDef CarapaceThingDef
{
get
{
if (Props.carapaceThingDef != null)
return Props.carapaceThingDef;
return DefDatabase<ThingDef>.GetNamed("ARA_Carapace", false);
}
}
// 检查是否可以剥离
public bool CanStripNow(Pawn pawn)
{
if (!CanStripChitin || pawn == null || pawn.Dead)
return false;
// 检查间隔
int currentTick = Find.TickManager.TicksGame;
if (currentTick - lastStripTick < Props.stripInterval)
return false;
// 检查甲壳需求
Need_ChitinArmor chitinNeed = pawn.needs?.TryGetNeed<Need_ChitinArmor>();
if (chitinNeed == null)
return false;
// 检查是否达到阈值
return chitinNeed.CurLevelPercentage >= StripThreshold;
}
// 记录剥离时间
public void NotifyStripped()
{
lastStripTick = Find.TickManager.TicksGame;
}
// 获取上次剥离到现在的时间
public int TicksSinceLastStrip => Find.TickManager.TicksGame - lastStripTick;
// 获取剩余冷却时间
public int CooldownTicksRemaining => Mathf.Max(0, Props.stripInterval - TicksSinceLastStrip);
// 序列化
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref stripEnabled, "stripEnabled", true);
Scribe_Values.Look(ref lastStripTick, "lastStripTick", -99999);
}
// 获取Gizmos命令按钮
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
// 只在玩家控制的pawn上显示
if (parent is Pawn pawn && pawn.Faction?.IsPlayer == true)
{
// 甲壳剥离开关按钮
yield return new Command_Toggle
{
defaultLabel = "ARA_ChitinStripping_Toggle".Translate(),
defaultDesc = "ARA_ChitinStripping_ToggleDesc".Translate(),
icon = ContentFinder<Texture2D>.Get("ArachnaeSwarm/UI/Commands/ARA_StripChitin"),
isActive = () => stripEnabled,
toggleAction = () =>
{
stripEnabled = !stripEnabled;
}
};
}
}
}
}

View File

@@ -0,0 +1,158 @@
// File: JobDriver_StripChitin.cs
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
public class JobDriver_StripChitin : JobDriver
{
private const int StripDurationTicks = 300; // 5秒 = 300 ticks
// 获取甲壳需求
private Need_ChitinArmor ChitinNeed => pawn.needs?.TryGetNeed<Need_ChitinArmor>();
// 获取甲壳剥离组件
private Comp_ChitinStripping StripComp
{
get
{
if (pawn == null)
return null;
// 从pawn的def中获取Comp
CompProperties_ChitinStripping compProps = pawn.def.GetCompProperties<CompProperties_ChitinStripping>();
if (compProps != null)
{
return pawn.TryGetComp<Comp_ChitinStripping>();
}
return null;
}
}
// 特效和音效
private static readonly EffecterDef StripEffect = ARA_EffecterDefOf.EatVegetarian;
private static readonly SoundDef StripSound = SoundDefOf.RawMeat_Eat;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
// 只需要保留目标位置
return pawn.Reserve(job.targetA, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
// 检查条件
this.FailOn(() => ChitinNeed == null);
this.FailOn(() => StripComp == null);
this.FailOn(() => !StripComp.CanStripNow(pawn));
this.FailOn(() => !StripComp.CanStripChitin); // 检查开关
// Toil 1: 移动到目标位置
yield return Toils_Goto.GotoCell(TargetIndex.A, PathEndMode.Touch);
// Toil 2: 执行剥离工作
Toil stripToil = new Toil
{
initAction = delegate
{
// 初始化剥离动作
},
tickAction = delegate
{
// 什么都不做,等待完成
},
defaultCompleteMode = ToilCompleteMode.Delay,
defaultDuration = StripDurationTicks
};
// 添加特效和音效
stripToil.WithEffect(() => StripEffect, TargetIndex.A);
stripToil.PlaySustainerOrSound(() => StripSound);
stripToil.AddFinishAction(delegate
{
// 执行剥离逻辑
StripChitin();
});
stripToil.WithProgressBar(TargetIndex.A,
() => stripToil.actor.jobs.curDriver.ticksLeftThisToil / (float)StripDurationTicks,
interpolateBetweenActorAndTarget: true);
yield return stripToil;
// Toil 3: 完成后的清理
yield return new Toil
{
initAction = delegate
{
// 记录剥离时间
StripComp?.NotifyStripped();
// 如果甲壳存量仍然达到阈值,可以安排下一个剥离工作
if (ChitinNeed != null && StripComp != null && StripComp.CanStripNow(pawn))
{
pawn.jobs.jobQueue.EnqueueLast(CreateStripJob());
}
},
defaultCompleteMode = ToilCompleteMode.Instant
};
}
// 剥离甲壳的逻辑
private void StripChitin()
{
if (ChitinNeed == null || StripComp == null)
return;
// 计算要剥离的甲壳数量
// 通常剥离整数部分但至少剥离1个
int stripCount = (int)ChitinNeed.CurLevel;
if (stripCount < StripComp.Props.minStripAmount)
stripCount = StripComp.Props.minStripAmount;
// 获取甲壳物品定义
ThingDef carapaceDef = StripComp.CarapaceThingDef;
if (carapaceDef == null)
return;
// 创建甲壳物品
Thing carapace = ThingMaker.MakeThing(carapaceDef);
carapace.stackCount = stripCount;
// 尝试将甲壳放在目标位置
GenPlace.TryPlaceThing(carapace, job.targetA.Cell, pawn.Map, ThingPlaceMode.Near);
// 减少甲壳存量
ChitinNeed.ReduceChitin(stripCount);
// 播放视觉反馈
MoteMaker.ThrowText(job.targetA.Cell.ToVector3Shifted(), pawn.Map,
"ARA_StripChitin_Message".Translate(stripCount));
}
// 创建剥离工作的辅助方法
private Job CreateStripJob()
{
// 寻找最近的空地
IntVec3 targetCell = FindNearestEmptyCell(pawn.Position, pawn.Map);
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_StripChitin"), targetCell);
return job;
}
// 寻找最近的空单元格
private IntVec3 FindNearestEmptyCell(IntVec3 from, Map map)
{
if (CellFinder.TryFindBestPawnStandCell(pawn, out IntVec3 cell, false))
{
return cell;
}
// 如果找不到最佳位置,使用原位置
return from;
}
}
}

View File

@@ -0,0 +1,83 @@
// File: ThinkNode_JobGiver_StripChitin.cs
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
public class ThinkNode_JobGiver_StripChitin : ThinkNode_JobGiver
{
private const int ScanIntervalTicks = 500; // 8.3秒扫描一次
private int lastScanTick = -99999;
protected override Job TryGiveJob(Pawn pawn)
{
// 检查是否应该扫描
int currentTick = Find.TickManager.TicksGame;
if (currentTick - lastScanTick < ScanIntervalTicks)
return null;
lastScanTick = currentTick;
// 检查甲壳剥离组件
Comp_ChitinStripping stripComp = pawn.TryGetComp<Comp_ChitinStripping>();
if (stripComp == null || !stripComp.CanStripNow(pawn))
return null;
// 如果已经有剥离工作,则不分配新工作
if (pawn.CurJob != null && pawn.CurJob.def == DefDatabase<JobDef>.GetNamed("ARA_StripChitin"))
return null;
// 寻找最近的空地
IntVec3 targetCell = FindNearestEmptyCell(pawn);
if (!targetCell.IsValid)
return null;
// 确保可以到达
if (!pawn.CanReach(targetCell, PathEndMode.Touch, Danger.Some))
return null;
// 创建剥离工作
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_StripChitin"), targetCell);
job.count = 1;
return job;
}
// 寻找最近的空单元格
private IntVec3 FindNearestEmptyCell(Pawn pawn)
{
// 首先检查当前位置周围3格内是否有空地
for (int radius = 1; radius <= 3; radius++)
{
foreach (IntVec3 cell in GenRadial.RadialCellsAround(pawn.Position, radius, true))
{
if (cell.InBounds(pawn.Map) &&
cell.Standable(pawn.Map) &&
cell.GetFirstItem(pawn.Map) == null &&
!cell.GetTerrain(pawn.Map).IsWater)
{
return cell;
}
}
}
// 如果找不到使用CellFinder寻找
if (CellFinder.TryFindBestPawnStandCell(pawn, out IntVec3 bestCell, false))
{
return bestCell;
}
// 最后尝试使用原位置(确保不是水面)
if (!pawn.Position.GetTerrain(pawn.Map).IsWater)
{
return pawn.Position;
}
return IntVec3.Invalid;
}
}
}

View File

@@ -0,0 +1,78 @@
// File: Jobs/JobDriver_SwarmMaintain.cs
using RimWorld;
using Verse;
using Verse.AI;
using System.Collections.Generic;
namespace ArachnaeSwarm
{
public class JobDriver_SwarmMaintain : JobDriver
{
// 工作完成后增加的维护度
public const float MaintenancePerWork = 25f;
// 工作时间ticks
private const int WorkDuration = 600; // 10秒
// 使用喂养效果 - 参考Toils_Ingest中的用法
private static readonly EffecterDef MaintainEffect = ARA_EffecterDefOf.EatVegetarian;
private static readonly SoundDef MaintainSound = SoundDefOf.RawMeat_Eat;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
return pawn.Reserve(job.targetA, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
// 1. 前往目标建筑
this.FailOnDespawnedNullOrForbidden(TargetIndex.A);
yield return Toils_Goto.GotoCell(TargetIndex.A, PathEndMode.Touch);
// 2. 执行维护工作
Toil maintain = new Toil();
maintain.tickAction = () =>
{
Pawn actor = maintain.actor;
Thing target = actor.CurJob.targetA.Thing;
// 检查目标是否仍然有效
if (target == null || target.Destroyed)
{
actor.jobs.EndCurrentJob(JobCondition.Incompletable);
return;
}
// 如果有技能要求,可以在这里检查
// actor.skills.Learn(SkillDefOf.Construction, 0.05f);
};
// 添加特效和音效参考Toils_Ingest.AddIngestionEffects
maintain.WithEffect(() => MaintainEffect, TargetIndex.A);
maintain.PlaySustainerOrSound(() => MaintainSound);
maintain.FailOnDespawnedNullOrForbidden(TargetIndex.A);
maintain.FailOnCannotTouch(TargetIndex.A, PathEndMode.Touch);
maintain.WithProgressBar(TargetIndex.A, () => (float)maintain.actor.jobs.curDriver.ticksLeftThisToil / WorkDuration);
maintain.defaultCompleteMode = ToilCompleteMode.Delay;
maintain.defaultDuration = WorkDuration;
yield return maintain;
// 3. 完成工作,增加维护度
yield return new Toil
{
initAction = () =>
{
Thing target = TargetA.Thing;
if (target != null && !target.Destroyed)
{
Comp_SwarmMaintenance comp = target.TryGetComp<Comp_SwarmMaintenance>();
if (comp != null)
{
comp.AddMaintenance(MaintenancePerWork);
}
}
}
};
}
}
}

View File

@@ -0,0 +1,438 @@
// File: Need_ChitinArmor.cs
using RimWorld;
using System.Collections.Generic;
using System.Xml.Serialization;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
public class NeedDefExtension_ChitinLevels : DefModExtension
{
// 关联的Hediff
public HediffDef hediff = null;
// 严重性映射范围默认是1:1映射即Need值=严重性值)
[XmlElement("severityRange")]
public FloatRange severityRange = new FloatRange(0f, 1f);
// 是否死亡时移除Hediff
public bool removeOnDeath = true;
// 基础增长速率系数
public float baseGrowthRate = 0.1f;
// 甲壳数量平方的系数
public float squareCoefficient = 1f / 100f; // 默认 1/100
}
public class Need_ChitinArmor : Need
{
// 甲壳增长基础值每tick
private const float BaseChitinGainPerTick = 0.0001f;
// 关联的Hediff
private Hediff chitinHediff;
// 上次更新Hediff的时间
private int lastHediffUpdateTick = -99999;
private const int HediffUpdateInterval = 60; // 每60tick更新一次Hediff
// 甲壳部位名称
private const string CHITIN_SHELL_PART_NAME = "ARA_Chitin_Shell";
// 缓存的身体部位数量(用于减少计算)
private int cachedShellPartCount = -1;
private int lastPartCountCheckTick = -99999;
private const int PartCountCheckInterval = 120; // 每120tick检查一次
// 获取ModExtension配置
public NeedDefExtension_ChitinLevels Extension => def.GetModExtension<NeedDefExtension_ChitinLevels>();
// 计算甲壳最大值基础值1 + 每个ARA_Chitin_Shell部位增加1
public override float MaxLevel
{
get
{
int shellCount = GetChitinShellPartCount();
return 1f + shellCount;
}
}
// 获取当前等级百分比用于UI显示
public new float CurLevelPercentage => CurLevel / MaxLevel;
// 获取甲壳增长速率
public float GrowthRatePerTick
{
get
{
int shellCount = GetChitinShellPartCount();
// 计算增长速率:((甲壳数量的平方) * 系数) + 基础值
float squareCoefficient = Extension?.squareCoefficient ?? (1f / 100f);
float baseRate = Extension?.baseGrowthRate ?? 0.1f;
return ((shellCount * shellCount) * squareCoefficient) + baseRate;
}
}
// 获取每秒增长速率用于UI显示
public float GrowthRatePerSecond
{
get
{
// RimWorld中1秒 = 60ticks
// 实际每秒增长量 = 每tick增长速率 * 60
// 注意BaseChitinGainPerTick是基础增长值GrowthRatePerTick是增长系数
// 实际增长公式每tick增长量 = BaseChitinGainPerTick * GrowthRatePerTick
// 所以每秒增长量 = BaseChitinGainPerTick * GrowthRatePerTick * 60
return BaseChitinGainPerTick * GrowthRatePerTick * 60f;
}
}
// 获取每2.5秒增长速率用于UI显示每150tick
public float GrowthRatePer2_5Seconds => GrowthRatePerSecond * 2.5f;
public Need_ChitinArmor(Pawn newPawn) : base(newPawn)
{
// 初始化时设置为空
curLevelInt = 0f;
// 设置默认阈值点
SetDefaultThresholds();
// 初始化缓存
UpdateCachedShellPartCount();
}
public override void ExposeData()
{
base.ExposeData();
Scribe_References.Look(ref chitinHediff, "chitinHediff");
Scribe_Values.Look(ref cachedShellPartCount, "cachedShellPartCount", -1);
Scribe_Values.Look(ref lastPartCountCheckTick, "lastPartCountCheckTick", -99999);
}
private void SetDefaultThresholds()
{
// 默认阈值25%、50%、75%用于UI显示
threshPercents = new List<float> { 0.25f, 0.5f, 0.75f };
}
public override void NeedInterval()
{
// 检查是否需要冻结
if (IsFrozen)
{
return;
}
// 定期更新身体部位数量缓存
int currentTick = Find.TickManager.TicksGame;
if (currentTick - lastPartCountCheckTick >= PartCountCheckInterval)
{
UpdateCachedShellPartCount();
lastPartCountCheckTick = currentTick;
}
// 计算增长量:基础增长速率 * 每150tick的间隔 * 甲壳增长速率系数
float growthAmount = BaseChitinGainPerTick * 150f * GrowthRatePerTick;
// 应用增长
CurLevel += growthAmount;
// 确保不超过最大值不低于0
float maxLevel = MaxLevel; // 计算一次并缓存
if (CurLevel > maxLevel)
CurLevel = maxLevel;
else if (CurLevel < 0f)
CurLevel = 0f;
// 定期更新Hediff每60tick一次
if (currentTick - lastHediffUpdateTick >= HediffUpdateInterval)
{
UpdateChitinHediff();
lastHediffUpdateTick = currentTick;
}
}
// 获取ARA_Chitin_Shell身体部位的数量
private int GetChitinShellPartCount()
{
// 使用缓存值(如果有效)
if (cachedShellPartCount >= 0 && pawn != null && !pawn.Dead)
{
return cachedShellPartCount;
}
// 重新计算
UpdateCachedShellPartCount();
return cachedShellPartCount;
}
// 更新缓存的身体部位数量
private void UpdateCachedShellPartCount()
{
if (pawn == null || pawn.Dead || pawn.RaceProps == null || pawn.RaceProps.body == null)
{
cachedShellPartCount = 0;
return;
}
// 计算身体部位数量
int count = 0;
// 方法1从pawn的身体部位记录中查找
foreach (BodyPartRecord part in pawn.RaceProps.body.AllParts)
{
if (part.def.defName == CHITIN_SHELL_PART_NAME)
{
count++;
}
}
// 方法2如果找不到检查Hediff中是否有甲壳作为备用方法
if (count == 0 && pawn.health != null && pawn.health.hediffSet != null)
{
foreach (Hediff hediff in pawn.health.hediffSet.hediffs)
{
if (hediff.Part != null && hediff.Part.def != null &&
hediff.Part.def.defName == CHITIN_SHELL_PART_NAME)
{
count++;
}
}
}
cachedShellPartCount = count;
}
// 重新计算身体部位数量(当身体发生变化时调用)
public void RecalculateShellPartCount()
{
UpdateCachedShellPartCount();
}
// 更新甲壳Hediff
private void UpdateChitinHediff()
{
if (Extension == null || Extension.hediff == null || pawn.Dead)
return;
// 直接使用CurLevel作为严重性但可以根据配置的范围进行调整
float severity = CurLevel;
// 如果需要,可以应用范围限制
FloatRange severityRange = Extension.severityRange;
if (severity > severityRange.max)
severity = severityRange.max;
else if (severity < severityRange.min)
severity = severityRange.min;
// 如果严重性为0或接近0移除Hediff
if (severity <= 0.001f)
{
RemoveChitinHediff();
return;
}
// 确保Hediff存在
EnsureChitinHediff();
// 设置严重性
if (chitinHediff != null)
{
chitinHediff.Severity = severity;
}
}
// 确保Hediff存在
private void EnsureChitinHediff()
{
if (chitinHediff == null || chitinHediff.pawn != pawn)
{
// 移除旧的Hediff
if (chitinHediff != null && chitinHediff.pawn == pawn)
{
pawn.health.RemoveHediff(chitinHediff);
}
// 创建新的Hediff
chitinHediff = HediffMaker.MakeHediff(Extension.hediff, pawn);
pawn.health.AddHediff(chitinHediff);
}
}
// 移除Hediff
private void RemoveChitinHediff()
{
if (chitinHediff != null && chitinHediff.pawn == pawn)
{
pawn.health.RemoveHediff(chitinHediff);
chitinHediff = null;
}
}
// 减少甲壳存量(例如受伤时)
public void ReduceChitin(float amount)
{
CurLevel -= amount;
if (CurLevel < 0f)
CurLevel = 0f;
// 立即更新Hediff
UpdateChitinHediff();
}
// 强制清空甲壳
public void EmptyChitin()
{
CurLevel = 0f;
RemoveChitinHediff();
}
// 重置到初始状态
public override void SetInitialLevel()
{
// 初始为空
CurLevel = 0f;
RemoveChitinHediff();
UpdateCachedShellPartCount();
}
// 获取当前严重性
public float GetCurrentSeverity()
{
if (chitinHediff == null)
return 0f;
return chitinHediff.Severity;
}
// 获取提示字符串
public override string GetTipString()
{
string text = (LabelCap + ": " + CurLevelPercentage.ToStringPercent()).Colorize(ColoredText.TipSectionTitleColor);
text += "\n" + def.description;
int shellCount = GetChitinShellPartCount();
text += $"\n{"ARA_Chitin.ShellParts".Translate()}: {shellCount}";
// 显示每秒增长速率(更直观)
text += $"\n{"ARA_Chitin.GrowthRate".Translate()}: {GrowthRatePerSecond:0.#####}/ {"LetterSecond".Translate()}";
return text;
}
// 在UI上绘制
public override void DrawOnGUI(Rect rect, int maxThresholdMarkers = int.MaxValue, float customMargin = -1f, bool drawArrows = true, bool doTooltip = true, Rect? rectForTooltip = null, bool drawLabel = true)
{
// 确保阈值已设置
if (threshPercents == null)
{
SetDefaultThresholds();
}
base.DrawOnGUI(rect, maxThresholdMarkers, customMargin, false, doTooltip, rectForTooltip, drawLabel);
}
// 是否冻结
protected override bool IsFrozen
{
get
{
// 如果基础条件冻结,则甲壳生长也冻结
if (base.IsFrozen)
return true;
// 如果生物死亡,则冻结
if (pawn.Dead)
return true;
return false;
}
}
// GUI变化箭头总是显示增长
public override int GUIChangeArrow => 1;
// 调试调整百分比
protected override void OffsetDebugPercent(float offsetPercent)
{
base.OffsetDebugPercent(offsetPercent);
UpdateChitinHediff();
}
// 当生物死亡时清理Hediff
public void OnPawnDeath()
{
if (Extension?.removeOnDeath ?? true)
{
RemoveChitinHediff();
}
}
// 当生物重生时恢复Hediff
public void OnPawnResurrected()
{
if (CurLevel > 0f && Extension?.hediff != null)
{
UpdateChitinHediff();
}
UpdateCachedShellPartCount();
}
// 当身体部位发生变化时调用
public void NotifyBodyPartChanged()
{
UpdateCachedShellPartCount();
// 如果最大容量减少,确保当前值不超过新最大值
if (CurLevel > MaxLevel)
{
CurLevel = MaxLevel;
UpdateChitinHediff();
}
}
public Comp_ChitinStripping GetStripComp()
{
if (pawn == null)
return null;
return pawn.TryGetComp<Comp_ChitinStripping>();
}
// 获取是否可以剥离
public bool CanStripChitin()
{
var stripComp = GetStripComp();
if (stripComp == null)
return false;
return stripComp.CanStripNow(pawn);
}
// 获取剥离信息字符串
public string GetStripInfoString()
{
var stripComp = GetStripComp();
if (stripComp == null)
return "ARA_ChitinStripping_Disabled".Translate();
if (!stripComp.CanStripNow(pawn))
{
int cooldownTicks = stripComp.CooldownTicksRemaining;
if (cooldownTicks > 0)
{
float cooldownSeconds = cooldownTicks / 60f;
return "ARA_ChitinStripping_Cooldown".Translate(cooldownSeconds.ToString("F1"));
}
else
{
return "ARA_ChitinStripping_Threshold".Translate((stripComp.StripThreshold * 100f).ToString("F0"));
}
}
return "ARA_ChitinStripping_Ready".Translate();
}
}
}

View File

@@ -1,13 +1,52 @@
// File: Need_HoneyProduction.cs
using RimWorld;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
public class HoneyProductionExtension : DefModExtension
{
// 蜜罐生产的基础转化率(相对于食物流失的百分比)
public float baseConversionRate = 0.5f;
// 最大蜜罐容量(可选,覆盖默认值)
public float maxHoneyCapacity = 1f;
// 是否启用蜜罐生产
public bool enableHoneyProduction = true;
// 生产速率乘数(影响生产速度)
public float productionSpeedFactor = 1f;
// 蜜罐类别对应的生产效率(可选,覆盖默认值)
public float fullProductionEfficiency = 1.5f;
public float highProductionEfficiency = 1.2f;
public float mediumProductionEfficiency = 1f;
public float lowProductionEfficiency = 0.5f;
public float emptyProductionEfficiency = 0f;
// 生产间隔ticks可选覆盖默认值
public int productionInterval = 150;
public static HoneyProductionExtension Get(Pawn pawn)
{
if (pawn?.def?.GetModExtension<HoneyProductionExtension>() is HoneyProductionExtension ext)
return ext;
return null;
}
// 获取转化率
public float GetConversionRate()
{
return baseConversionRate * productionSpeedFactor;
}
}
public class Need_HoneyProduction : Need
{
// 基础流失速率(与食物需要对应)
private const float BaseHoneyGainPerTick = 2.6666667E-05f * 0.5f; // 食物流失速率的50%
private const float BaseHoneyGainPerTick = 2.6666667E-05f * 0.5f; // 食物流失速率的50%作为默认值
// 用于存储对食物需要的引用
private Need_Food cachedFoodNeed;
@@ -18,8 +57,22 @@ namespace ArachnaeSwarm
// 上次满的时间点
private int lastFullTick = -99999;
// 蜜罐的最大容量(可能需要调整)
public override float MaxLevel => 1f;
// 缓存的ModExtension
private HoneyProductionExtension cachedExtension;
// 蜜罐的最大容量 - 优先使用ModExtension的值
public override float MaxLevel
{
get
{
var ext = GetExtension();
if (ext != null && ext.maxHoneyCapacity > 0)
{
return ext.maxHoneyCapacity;
}
return FoodNeed?.MaxLevel ?? 1f;
}
}
// 当前类别
public HoneyProductionCategory CurCategory => curCategoryInt;
@@ -35,6 +88,29 @@ namespace ArachnaeSwarm
{
get
{
var ext = GetExtension();
// 如果有扩展定义,使用扩展的值
if (ext != null)
{
switch (curCategoryInt)
{
case HoneyProductionCategory.Full:
return ext.fullProductionEfficiency;
case HoneyProductionCategory.High:
return ext.highProductionEfficiency;
case HoneyProductionCategory.Medium:
return ext.mediumProductionEfficiency;
case HoneyProductionCategory.Low:
return ext.lowProductionEfficiency;
case HoneyProductionCategory.Empty:
return ext.emptyProductionEfficiency;
default:
return 0f;
}
}
// 默认值
switch (curCategoryInt)
{
case HoneyProductionCategory.Full:
@@ -53,12 +129,32 @@ namespace ArachnaeSwarm
}
}
// 获取扩展
private HoneyProductionExtension GetExtension()
{
if (cachedExtension == null && pawn != null)
{
cachedExtension = pawn.def?.GetModExtension<HoneyProductionExtension>();
}
return cachedExtension;
}
// 是否启用蜜罐生产
private bool IsEnabled
{
get
{
var ext = GetExtension();
return ext == null || ext.enableHoneyProduction; // 默认启用
}
}
// 获取食物需要的引用
private Need_Food FoodNeed
{
get
{
if (cachedFoodNeed == null || cachedFoodNeed.pawn != pawn)
if (cachedFoodNeed == null)
{
cachedFoodNeed = pawn.needs?.TryGetNeed<Need_Food>();
}
@@ -86,7 +182,9 @@ namespace ArachnaeSwarm
public override void NeedInterval()
{
base.NeedInterval();
// 如果不启用,直接返回
if (!IsEnabled)
return;
// 检查是否需要冻结(与食物需要类似的条件)
if (IsFrozen)
@@ -97,11 +195,21 @@ namespace ArachnaeSwarm
// 获取食物需要的流失速率
float foodFallRate = GetFoodFallRate();
// 蜜罐的增长速率是食物流失速率的50%
float honeyGainRate = foodFallRate * 0.5f;
// 获取转化率从ModExtension或使用默认值
float conversionRate = GetExtension()?.GetConversionRate() ?? 0.5f;
// 应用150 ticks的间隔
CurLevel += honeyGainRate * 150f;
// 蜜罐的增长速率 = 食物流失速率 × 转化率
float honeyGainRate = foodFallRate * conversionRate;
// 获取生产间隔
int interval = GetExtension()?.productionInterval ?? 150;
// 应用间隔
CurLevel += honeyGainRate * interval;
// 确保不超过最大容量
if (CurLevel > MaxLevel)
CurLevel = MaxLevel;
// 更新类别
UpdateCategory();
@@ -172,15 +280,39 @@ namespace ArachnaeSwarm
{
string text = (LabelCap + ": " + CurLevelPercentage.ToStringPercent()).Colorize(ColoredText.TipSectionTitleColor);
text += "\n" + def.description;
text += $"\n\n{"AS.HoneyProduction.CurrentLevel".Translate()}: {CurLevel:0.##} / {MaxLevel:0.##}";
text += $"\n{"AS.HoneyProduction.Category".Translate()}: {GetCategoryLabel().CapitalizeFirst()}";
text += $"\n{"AS.HoneyProduction.Efficiency".Translate()}: {ProductionEfficiency.ToStringPercent()}";
text += $"\n{"AS.HoneyProduction.FoodDrainRate".Translate()}: {GetFoodFallRate():0.#####}/tick";
text += $"\n{"AS.HoneyProduction.HoneyGainRate".Translate()}: {(GetFoodFallRate() * 0.5f):0.#####}/tick";
text += $"\n{"ARA_HoneyProduction.Efficiency".Translate()} {ProductionEfficiency.ToStringPercent()}";
// 获取每tick的速率
float foodFallPerTick = GetFoodFallRate();
float conversionRate = GetExtension()?.GetConversionRate() ?? 0.5f;
float honeyGainPerTick = foodFallPerTick * conversionRate;
// 转换为每秒1秒 = 60tick
float foodFallPerSecond = foodFallPerTick * 60f;
float honeyGainPerSecond = honeyGainPerTick * 60f;
text += $"\n{"ARA_HoneyProduction.FoodDrainRate".Translate()}: {foodFallPerSecond:0.#####}/ {"LetterSecond".Translate()}";
text += $"\n{"ARA_HoneyProduction.HoneyGainRate".Translate()}: {honeyGainPerSecond:0.#####}/ {"LetterSecond".Translate()}";
// 显示转化率信息
if (GetExtension() != null)
{
text += $"\n{"ARA_HoneyProduction.ConversionRate".Translate()}: {conversionRate:P1}";
if (GetExtension().productionSpeedFactor != 1f)
{
text += $"\n{"ARA_HoneyProduction.SpeedFactor".Translate()}: {GetExtension().productionSpeedFactor:F2}";
}
}
if (IsFull)
{
text += $"\n\n{"AS.HoneyProduction.FullWarning".Translate()}";
text += $"\n\n{"ARA_HoneyProduction.FullWarning".Translate()}";
}
// 显示是否启用
if (!IsEnabled)
{
text += $"\n\n<color=orange>{"ARA_HoneyProduction.Disabled".Translate()}</color>";
}
return text;
@@ -192,15 +324,15 @@ namespace ArachnaeSwarm
switch (curCategoryInt)
{
case HoneyProductionCategory.Full:
return "AS.HoneyProduction.Full".Translate();
return "ARA_HoneyProduction.Full".Translate();
case HoneyProductionCategory.High:
return "AS.HoneyProduction.High".Translate();
return "ARA_HoneyProduction.High".Translate();
case HoneyProductionCategory.Medium:
return "AS.HoneyProduction.Medium".Translate();
return "ARA_HoneyProduction.Medium".Translate();
case HoneyProductionCategory.Low:
return "AS.HoneyProduction.Low".Translate();
return "ARA_HoneyProduction.Low".Translate();
case HoneyProductionCategory.Empty:
return "AS.HoneyProduction.Empty".Translate();
return "ARA_HoneyProduction.Empty".Translate();
default:
return "Unknown";
}
@@ -209,6 +341,10 @@ namespace ArachnaeSwarm
// 在UI上绘制
public override void DrawOnGUI(Rect rect, int maxThresholdMarkers = int.MaxValue, float customMargin = -1f, bool drawArrows = true, bool doTooltip = true, Rect? rectForTooltip = null, bool drawLabel = true)
{
// 如果不启用,不显示
if (!IsEnabled)
return;
if (threshPercents == null)
{
threshPercents = new System.Collections.Generic.List<float>
@@ -229,10 +365,6 @@ namespace ArachnaeSwarm
if (base.IsFrozen)
return true;
// 如果没有食物需要,或者食物需要被冻结,则蜜罐生产也冻结
if (FoodNeed == null || FoodNeed.IsFrozen)
return true;
// 如果生物死亡,则冻结
if (pawn.Dead)
return true;
@@ -242,7 +374,7 @@ namespace ArachnaeSwarm
}
// GUI变化箭头总是显示增长
public override int GUIChangeArrow => 1;
public override int GUIChangeArrow => IsEnabled ? 1 : 0;
// 调试调整百分比
protected override void OffsetDebugPercent(float offsetPercent)

View File

@@ -80,7 +80,7 @@ namespace ArachnaeSwarm
// 尝试加载图标,如果失败则使用默认图标
try
{
toggleCommand.icon = ContentFinder<Texture2D>.Get("Wula/UI/Commands/WULA_ShowRadius", false);
toggleCommand.icon = ContentFinder<Texture2D>.Get("ArachnaeSwarm/UI/Commands/ARA_ShowRadius", false);
if (toggleCommand.icon == null)
{
// 使用一个简单的占位符图标