diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index f30f9b2..3eceb4e 100644 Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ diff --git a/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml index d183457..030fab5 100644 --- a/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml +++ b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml @@ -4,7 +4,7 @@ ARA_BindDrone - 使用信息素标记一只阿拉克涅督虫(包括野生虫群子个体),受到标记的督虫和其麾下的辅虫将誓死效忠于女皇种,并与其建立心灵链接。\n\n除了手动链接外,女皇种也会每60秒尝试自动链接己方所有未链接的阿拉克涅督虫,此类链接和手动链接不一样,不需要目视。 + 使用信息素标记一只野生阿拉克涅督虫或兽虫个体,其将脱离野兽状态,誓死效忠于女皇种,并与其建立心灵链接。\n\n对于己方的督虫和兽虫,女皇种会自动与其建立链接,不需要技能。 ArachnaeSwarm/UI/Abilities/ARA_BindDrone Misc1 diff --git a/1.6/1.6/Defs/HediffDefs/ARA_Hediffs_HiveMind.xml b/1.6/1.6/Defs/HediffDefs/ARA_Hediffs_HiveMind.xml index d88280f..046b475 100644 --- a/1.6/1.6/Defs/HediffDefs/ARA_Hediffs_HiveMind.xml +++ b/1.6/1.6/Defs/HediffDefs/ARA_Hediffs_HiveMind.xml @@ -256,6 +256,25 @@ 0 + +
  • + HiveNode +
  • + + +
  • + +
  • ARA_NonPlayer_HiveMindDroneHediff
  • + + +
    diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_Hostile_Hive_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_Hostile_Hive_PawnKinds.xml index 45abbde..7a886e6 100644 --- a/1.6/1.6/Defs/PawnKindDef/ARA_Hostile_Hive_PawnKinds.xml +++ b/1.6/1.6/Defs/PawnKindDef/ARA_Hostile_Hive_PawnKinds.xml @@ -297,6 +297,9 @@ CrumblingMind 0.01 +
  • + ARA_NonPlayer_HiveMindDroneHediff +
  • diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml index 61476af..b593314 100644 --- a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml +++ b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml @@ -60,6 +60,7 @@ 0 +
  • ARA_BindDrone
  • ARA_AcidSprayBurst_Queen
  • ARA_TumorSpew
  • @@ -169,6 +170,7 @@ 0 +
  • ARA_BindDrone
  • ARA_Neurotyrant_Harvest
  • ARA_Neurotyrant_PsychicLoadDump
  • diff --git a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml index f51f553..b9777ac 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml @@ -1750,8 +1750,19 @@
  • ARA_Psi_Master
  • -
  • PsychicAmplifier
  • + + + +
  • + PsychicAmplifier + Brain + 1 + false + true +
  • +
    + 1.0 false diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo index 01775ab..f2f24f8 100644 Binary files a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo and b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo differ diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json index 890f3f7..416c581 100644 --- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json +++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json @@ -3,27 +3,55 @@ "WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\", "Documents": [ { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\harmonypatches\\patch_pawn_needstracker_shouldhaveneed.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\pawn_comps\\ara_pawnresearchblueprintreader\\comp_pawnresearchblueprintreader.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_pawnresearchblueprintreader\\comp_pawnresearchblueprintreader.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\pawn_comps\\ara_pawnresearchblueprintreader\\gizmo_pawnresearchprogress.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_pawnresearchblueprintreader\\gizmo_pawnresearchprogress.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\pawn_comps\\ara_pawnresearchblueprintreader\\compproperties_pawnresearchblueprintreader.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_pawnresearchblueprintreader\\compproperties_pawnresearchblueprintreader.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\hediffs\\ara_hivemind\\compabilityeffect_binddrone.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:hediffs\\ara_hivemind\\compabilityeffect_binddrone.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\pawn_comps\\ara_swarmspellholder\\comp_swarmspellholder.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_swarmspellholder\\comp_swarmspellholder.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\buildings\\building_researchblueprintreader\\building_researchblueprintreader.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_researchblueprintreader\\building_researchblueprintreader.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\pawn_comps\\ara_comphediffgiver\\comphediffgiver.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_comphediffgiver\\comphediffgiver.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\\harmonypatches\\patch_pawn_needstracker_shouldhaveneed.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:harmonypatches\\patch_pawn_needstracker_shouldhaveneed.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\\harmonypatches\\patch_draftableanimals.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\\harmonypatches\\patch_draftableanimals.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:harmonypatches\\patch_draftableanimals.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\\harmonypatches\\projectile_launch_patch.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\\harmonypatches\\projectile_launch_patch.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:harmonypatches\\projectile_launch_patch.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\\harmonypatches\\patch_researchmanager_addremovemethod.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\\harmonypatches\\patch_researchmanager_addremovemethod.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:harmonypatches\\patch_researchmanager_addremovemethod.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\\harmonypatches\\patch_plantpollutionnullcheck.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\\harmonypatches\\patch_plantpollutionnullcheck.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:harmonypatches\\patch_plantpollutionnullcheck.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\\harmonypatches\\patch_forcetargetable.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\\harmonypatches\\patch_forcetargetable.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:harmonypatches\\patch_forcetargetable.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, { @@ -62,10 +90,6 @@ "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\abilities\\ara_psychicloaddump\\compproperties_abilitypsychicloaddump.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_psychicloaddump\\compproperties_abilitypsychicloaddump.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\pawn_comps\\ara_swarmspellholder\\comp_swarmspellholder.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_swarmspellholder\\comp_swarmspellholder.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, { "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\pawn_comps\\ara_swarmspellholder\\gizmo_swarmspellstatus.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_swarmspellholder\\gizmo_swarmspellstatus.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" @@ -134,28 +158,91 @@ "DocumentGroups": [ { "DockedWidth": 200, - "SelectedChildIndex": 6, + "SelectedChildIndex": 3, "Children": [ - { - "$type": "Document", - "DocumentIndex": 2, - "Title": "Projectile_Launch_Patch.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\HarmonyPatches\\Projectile_Launch_Patch.cs", - "RelativeDocumentMoniker": "HarmonyPatches\\Projectile_Launch_Patch.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\HarmonyPatches\\Projectile_Launch_Patch.cs", - "RelativeToolTip": "HarmonyPatches\\Projectile_Launch_Patch.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-02-11T03:03:01.656Z", - "EditorCaption": "" - }, { "$type": "Bookmark", "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" }, + { + "$type": "Document", + "DocumentIndex": 1, + "Title": "Gizmo_PawnResearchProgress.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_PawnResearchBlueprintReader\\Gizmo_PawnResearchProgress.cs", + "RelativeDocumentMoniker": "Pawn_Comps\\ARA_PawnResearchBlueprintReader\\Gizmo_PawnResearchProgress.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_PawnResearchBlueprintReader\\Gizmo_PawnResearchProgress.cs*", + "RelativeToolTip": "Pawn_Comps\\ARA_PawnResearchBlueprintReader\\Gizmo_PawnResearchProgress.cs*", + "ViewState": "AgIAAMQAAAAAAAAAAAAhwNYAAAAUAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-02-13T03:54:02.036Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 2, + "Title": "CompProperties_PawnResearchBlueprintReader.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_PawnResearchBlueprintReader\\CompProperties_PawnResearchBlueprintReader.cs", + "RelativeDocumentMoniker": "Pawn_Comps\\ARA_PawnResearchBlueprintReader\\CompProperties_PawnResearchBlueprintReader.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_PawnResearchBlueprintReader\\CompProperties_PawnResearchBlueprintReader.cs*", + "RelativeToolTip": "Pawn_Comps\\ARA_PawnResearchBlueprintReader\\CompProperties_PawnResearchBlueprintReader.cs*", + "ViewState": "AgIAAC0AAAAAAAAAAAAawD8AAAAJAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-02-13T03:54:00.997Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "Comp_PawnResearchBlueprintReader.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_PawnResearchBlueprintReader\\Comp_PawnResearchBlueprintReader.cs", + "RelativeDocumentMoniker": "Pawn_Comps\\ARA_PawnResearchBlueprintReader\\Comp_PawnResearchBlueprintReader.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_PawnResearchBlueprintReader\\Comp_PawnResearchBlueprintReader.cs*", + "RelativeToolTip": "Pawn_Comps\\ARA_PawnResearchBlueprintReader\\Comp_PawnResearchBlueprintReader.cs*", + "ViewState": "AgIAAA8DAAAAAAAAAAAewCIDAAANAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-02-13T03:53:59.347Z", + "EditorCaption": "" + }, { "$type": "Document", "DocumentIndex": 3, + "Title": "CompAbilityEffect_BindDrone.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_HiveMind\\CompAbilityEffect_BindDrone.cs", + "RelativeDocumentMoniker": "Hediffs\\ARA_HiveMind\\CompAbilityEffect_BindDrone.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_HiveMind\\CompAbilityEffect_BindDrone.cs", + "RelativeToolTip": "Hediffs\\ARA_HiveMind\\CompAbilityEffect_BindDrone.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAALYAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-02-13T02:39:33.878Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 5, + "Title": "Building_ResearchBlueprintReader.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_ResearchBlueprintReader\\Building_ResearchBlueprintReader.cs", + "RelativeDocumentMoniker": "Buildings\\Building_ResearchBlueprintReader\\Building_ResearchBlueprintReader.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_ResearchBlueprintReader\\Building_ResearchBlueprintReader.cs", + "RelativeToolTip": "Buildings\\Building_ResearchBlueprintReader\\Building_ResearchBlueprintReader.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAACIAAAAPAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-02-13T02:24:37.654Z" + }, + { + "$type": "Document", + "DocumentIndex": 6, + "Title": "CompHediffGiver.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_CompHediffGiver\\CompHediffGiver.cs", + "RelativeDocumentMoniker": "Pawn_Comps\\ARA_CompHediffGiver\\CompHediffGiver.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_CompHediffGiver\\CompHediffGiver.cs", + "RelativeToolTip": "Pawn_Comps\\ARA_CompHediffGiver\\CompHediffGiver.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAHoAAAAJAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-02-13T02:14:47.422Z" + }, + { + "$type": "Document", + "DocumentIndex": 10, "Title": "Patch_ResearchManager_AddRemoveMethod.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\HarmonyPatches\\Patch_ResearchManager_AddRemoveMethod.cs", "RelativeDocumentMoniker": "HarmonyPatches\\Patch_ResearchManager_AddRemoveMethod.cs", @@ -163,12 +250,11 @@ "RelativeToolTip": "HarmonyPatches\\Patch_ResearchManager_AddRemoveMethod.cs", "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-02-11T03:02:58.905Z", - "EditorCaption": "" + "WhenOpened": "2026-02-11T03:02:58.905Z" }, { "$type": "Document", - "DocumentIndex": 4, + "DocumentIndex": 11, "Title": "Patch_PlantPollutionNullCheck.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\HarmonyPatches\\Patch_PlantPollutionNullCheck.cs", "RelativeDocumentMoniker": "HarmonyPatches\\Patch_PlantPollutionNullCheck.cs", @@ -176,12 +262,11 @@ "RelativeToolTip": "HarmonyPatches\\Patch_PlantPollutionNullCheck.cs", "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-02-11T03:02:49.024Z", - "EditorCaption": "" + "WhenOpened": "2026-02-11T03:02:49.024Z" }, { "$type": "Document", - "DocumentIndex": 5, + "DocumentIndex": 12, "Title": "Patch_ForceTargetable.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\HarmonyPatches\\Patch_ForceTargetable.cs", "RelativeDocumentMoniker": "HarmonyPatches\\Patch_ForceTargetable.cs", @@ -189,12 +274,35 @@ "RelativeToolTip": "HarmonyPatches\\Patch_ForceTargetable.cs", "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-02-11T03:02:45.4Z", - "EditorCaption": "" + "WhenOpened": "2026-02-11T03:02:45.4Z" }, { "$type": "Document", - "DocumentIndex": 1, + "DocumentIndex": 4, + "Title": "Comp_SwarmSpellHolder.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_SwarmSpellHolder\\Comp_SwarmSpellHolder.cs", + "RelativeDocumentMoniker": "Pawn_Comps\\ARA_SwarmSpellHolder\\Comp_SwarmSpellHolder.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_SwarmSpellHolder\\Comp_SwarmSpellHolder.cs", + "RelativeToolTip": "Pawn_Comps\\ARA_SwarmSpellHolder\\Comp_SwarmSpellHolder.cs", + "ViewState": "AgIAAFYCAAAAAAAAAAAAAHADAAAyAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-01-30T07:11:59.797Z" + }, + { + "$type": "Document", + "DocumentIndex": 9, + "Title": "Projectile_Launch_Patch.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\HarmonyPatches\\Projectile_Launch_Patch.cs", + "RelativeDocumentMoniker": "HarmonyPatches\\Projectile_Launch_Patch.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\HarmonyPatches\\Projectile_Launch_Patch.cs", + "RelativeToolTip": "HarmonyPatches\\Projectile_Launch_Patch.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-02-11T03:03:01.656Z" + }, + { + "$type": "Document", + "DocumentIndex": 8, "Title": "Patch_DraftableAnimals.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\HarmonyPatches\\Patch_DraftableAnimals.cs", "RelativeDocumentMoniker": "HarmonyPatches\\Patch_DraftableAnimals.cs", @@ -202,25 +310,23 @@ "RelativeToolTip": "HarmonyPatches\\Patch_DraftableAnimals.cs", "ViewState": "AgIAAAAAAAAAAAAAAAAAAA8AAAANAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-02-11T03:01:45.903Z", - "EditorCaption": "" + "WhenOpened": "2026-02-11T03:01:45.903Z" }, { "$type": "Document", - "DocumentIndex": 0, + "DocumentIndex": 7, "Title": "Patch_Pawn_NeedsTracker_ShouldHaveNeed.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\HarmonyPatches\\Patch_Pawn_NeedsTracker_ShouldHaveNeed.cs", "RelativeDocumentMoniker": "HarmonyPatches\\Patch_Pawn_NeedsTracker_ShouldHaveNeed.cs", "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\HarmonyPatches\\Patch_Pawn_NeedsTracker_ShouldHaveNeed.cs", "RelativeToolTip": "HarmonyPatches\\Patch_Pawn_NeedsTracker_ShouldHaveNeed.cs", - "ViewState": "AgIAAA4AAAAAAAAAAADwvxYAAAAdAAAAAAAAAA==", + "ViewState": "AgIAAA4AAAAAAAAAAAAwwBYAAAAdAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-02-11T02:46:06.897Z", - "EditorCaption": "" + "WhenOpened": "2026-02-11T02:46:06.897Z" }, { "$type": "Document", - "DocumentIndex": 6, + "DocumentIndex": 13, "Title": "TravelingWormhole.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Wormhole\\TravelingWormhole.cs", "RelativeDocumentMoniker": "Buildings\\Wormhole\\TravelingWormhole.cs", @@ -232,7 +338,7 @@ }, { "$type": "Document", - "DocumentIndex": 7, + "DocumentIndex": 14, "Title": "CompLaunchableWormhole.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Wormhole\\CompLaunchableWormhole.cs", "RelativeDocumentMoniker": "Buildings\\Wormhole\\CompLaunchableWormhole.cs", @@ -244,7 +350,7 @@ }, { "$type": "Document", - "DocumentIndex": 8, + "DocumentIndex": 15, "Title": "UniquePawnManager.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_UniquePawn\\UniquePawnManager.cs", "RelativeDocumentMoniker": "Pawn_Comps\\ARA_UniquePawn\\UniquePawnManager.cs", @@ -256,7 +362,7 @@ }, { "$type": "Document", - "DocumentIndex": 9, + "DocumentIndex": 16, "Title": "CompUniquePawn.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_UniquePawn\\CompUniquePawn.cs", "RelativeDocumentMoniker": "Pawn_Comps\\ARA_UniquePawn\\CompUniquePawn.cs", @@ -268,7 +374,7 @@ }, { "$type": "Document", - "DocumentIndex": 11, + "DocumentIndex": 18, "Title": "JobDriver_ExtractHoney.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_ExtractHoney\\JobDriver_ExtractHoney.cs", "RelativeDocumentMoniker": "Jobs\\JobDriver_ExtractHoney\\JobDriver_ExtractHoney.cs", @@ -280,7 +386,7 @@ }, { "$type": "Document", - "DocumentIndex": 10, + "DocumentIndex": 17, "Title": "Need_HoneyProduction.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Needs\\Need_HoneyProduction.cs", "RelativeDocumentMoniker": "Needs\\Need_HoneyProduction.cs", @@ -292,7 +398,7 @@ }, { "$type": "Document", - "DocumentIndex": 12, + "DocumentIndex": 19, "Title": "Need_ChitinArmor.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Needs\\Need_ChitinArmor.cs", "RelativeDocumentMoniker": "Needs\\Need_ChitinArmor.cs", @@ -304,7 +410,7 @@ }, { "$type": "Document", - "DocumentIndex": 14, + "DocumentIndex": 21, "Title": "CompProperties_AbilityPsychicLoadDump.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_PsychicLoadDump\\CompProperties_AbilityPsychicLoadDump.cs", "RelativeDocumentMoniker": "Abilities\\ARA_PsychicLoadDump\\CompProperties_AbilityPsychicLoadDump.cs", @@ -316,7 +422,7 @@ }, { "$type": "Document", - "DocumentIndex": 13, + "DocumentIndex": 20, "Title": "CompAbilityEffect_PsychicLoadDump.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_PsychicLoadDump\\CompAbilityEffect_PsychicLoadDump.cs", "RelativeDocumentMoniker": "Abilities\\ARA_PsychicLoadDump\\CompAbilityEffect_PsychicLoadDump.cs", @@ -328,7 +434,7 @@ }, { "$type": "Document", - "DocumentIndex": 18, + "DocumentIndex": 24, "Title": "CompAbilityEffect_PsychicResearchHarvest.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_PsychicResearchHarvest\\CompAbilityEffect_PsychicResearchHarvest.cs", "RelativeDocumentMoniker": "Abilities\\ARA_PsychicResearchHarvest\\CompAbilityEffect_PsychicResearchHarvest.cs", @@ -340,7 +446,7 @@ }, { "$type": "Document", - "DocumentIndex": 22, + "DocumentIndex": 28, "Title": "CompProperties_AbilityPsychicLoadCost.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_PsychicLoadCost\\CompProperties_AbilityPsychicLoadCost.cs", "RelativeDocumentMoniker": "Abilities\\ARA_PsychicLoadCost\\CompProperties_AbilityPsychicLoadCost.cs", @@ -352,7 +458,7 @@ }, { "$type": "Document", - "DocumentIndex": 21, + "DocumentIndex": 27, "Title": "SwarmSpellUtility.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_SwarmSpellHolder\\SwarmSpellUtility.cs", "RelativeDocumentMoniker": "Pawn_Comps\\ARA_SwarmSpellHolder\\SwarmSpellUtility.cs", @@ -364,7 +470,7 @@ }, { "$type": "Document", - "DocumentIndex": 17, + "DocumentIndex": 23, "Title": "CompAbilityEffect_PsychicLoadCost.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_PsychicLoadCost\\CompAbilityEffect_PsychicLoadCost.cs", "RelativeDocumentMoniker": "Abilities\\ARA_PsychicLoadCost\\CompAbilityEffect_PsychicLoadCost.cs", @@ -376,19 +482,7 @@ }, { "$type": "Document", - "DocumentIndex": 15, - "Title": "Comp_SwarmSpellHolder.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_SwarmSpellHolder\\Comp_SwarmSpellHolder.cs", - "RelativeDocumentMoniker": "Pawn_Comps\\ARA_SwarmSpellHolder\\Comp_SwarmSpellHolder.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_SwarmSpellHolder\\Comp_SwarmSpellHolder.cs", - "RelativeToolTip": "Pawn_Comps\\ARA_SwarmSpellHolder\\Comp_SwarmSpellHolder.cs", - "ViewState": "AgIAAFgCAAAAAAAAAAAYwGkCAAASAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2026-01-30T07:11:59.797Z" - }, - { - "$type": "Document", - "DocumentIndex": 16, + "DocumentIndex": 22, "Title": "Gizmo_SwarmSpellStatus.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_SwarmSpellHolder\\Gizmo_SwarmSpellStatus.cs", "RelativeDocumentMoniker": "Pawn_Comps\\ARA_SwarmSpellHolder\\Gizmo_SwarmSpellStatus.cs", @@ -400,7 +494,7 @@ }, { "$type": "Document", - "DocumentIndex": 23, + "DocumentIndex": 29, "Title": "CompAbilityEffect_HediffGacha.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_HediffGacha\\CompAbilityEffect_HediffGacha.cs", "RelativeDocumentMoniker": "Abilities\\ARA_HediffGacha\\CompAbilityEffect_HediffGacha.cs", @@ -412,7 +506,7 @@ }, { "$type": "Document", - "DocumentIndex": 20, + "DocumentIndex": 26, "Title": "CompFighterInvisible.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_Fighter_Invisible\\CompFighterInvisible.cs", "RelativeDocumentMoniker": "Pawn_Comps\\ARA_Fighter_Invisible\\CompFighterInvisible.cs", @@ -424,7 +518,7 @@ }, { "$type": "Document", - "DocumentIndex": 19, + "DocumentIndex": 25, "Title": "CompProperties_AbilityPsychicResearchHarvest.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_PsychicResearchHarvest\\CompProperties_AbilityPsychicResearchHarvest.cs", "RelativeDocumentMoniker": "Abilities\\ARA_PsychicResearchHarvest\\CompProperties_AbilityPsychicResearchHarvest.cs", @@ -436,7 +530,7 @@ }, { "$type": "Document", - "DocumentIndex": 24, + "DocumentIndex": 30, "Title": "CompAbilityEffect_HediffRestriction.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_HediffRestriction\\CompAbilityEffect_HediffRestriction.cs", "RelativeDocumentMoniker": "Abilities\\ARA_HediffRestriction\\CompAbilityEffect_HediffRestriction.cs", @@ -448,7 +542,7 @@ }, { "$type": "Document", - "DocumentIndex": 25, + "DocumentIndex": 31, "Title": "Window_HediffSelection.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_HediffGacha\\Window_HediffSelection.cs", "RelativeDocumentMoniker": "Abilities\\ARA_HediffGacha\\Window_HediffSelection.cs", @@ -460,7 +554,7 @@ }, { "$type": "Document", - "DocumentIndex": 26, + "DocumentIndex": 32, "Title": "ARA_PowerArmor.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\ARA_PowerArmor.cs", "RelativeDocumentMoniker": "PowerArmor\\ARA_PowerArmor.cs", @@ -472,7 +566,7 @@ }, { "$type": "Document", - "DocumentIndex": 27, + "DocumentIndex": 33, "Title": "PawnCapacityWorker_PsychicStange.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PawnCapacityWorker\\PawnCapacityWorker_PsychicStange.cs", "RelativeDocumentMoniker": "PawnCapacityWorker\\PawnCapacityWorker_PsychicStange.cs", @@ -484,7 +578,7 @@ }, { "$type": "Document", - "DocumentIndex": 29, + "DocumentIndex": 35, "Title": "CompAbilityEffect_PsychicBrainburn.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\PsychicBrainburn\\CompAbilityEffect_PsychicBrainburn.cs", "RelativeDocumentMoniker": "Abilities\\PsychicBrainburn\\CompAbilityEffect_PsychicBrainburn.cs", @@ -496,7 +590,7 @@ }, { "$type": "Document", - "DocumentIndex": 28, + "DocumentIndex": 34, "Title": "ARA_DefOf.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\ARA_DefOf.cs", "RelativeDocumentMoniker": "ARA_DefOf.cs", @@ -508,7 +602,7 @@ }, { "$type": "Document", - "DocumentIndex": 30, + "DocumentIndex": 36, "Title": "CompAutoMechCarrier.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_AutoMechCarrier\\CompAutoMechCarrier.cs", "RelativeDocumentMoniker": "Pawn_Comps\\ARA_AutoMechCarrier\\CompAutoMechCarrier.cs", diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index 4e83cdd..9bde2e6 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -366,6 +366,9 @@ + + + diff --git a/Source/ArachnaeSwarm/Hediffs/ARA_HiveMind/CompAbilityEffect_BindDrone.cs b/Source/ArachnaeSwarm/Hediffs/ARA_HiveMind/CompAbilityEffect_BindDrone.cs index 85c246d..a2938ce 100644 --- a/Source/ArachnaeSwarm/Hediffs/ARA_HiveMind/CompAbilityEffect_BindDrone.cs +++ b/Source/ArachnaeSwarm/Hediffs/ARA_HiveMind/CompAbilityEffect_BindDrone.cs @@ -66,13 +66,16 @@ namespace ArachnaeSwarm } // 3. 添加 ARA_HiveMindDrone - HediffDef hiveMindDroneDef = HediffDef.Named("ARA_HiveMindDrone"); - if (hiveMindDroneDef != null && !dronePawn.health.hediffSet.HasHediff(hiveMindDroneDef)) + if (dronePawn.IsAnimal) { - dronePawn.health.AddHediff(hiveMindDroneDef); - dronePawn.health.AddHediff(HediffDef.Named("ARA_LifespanHediff")); - ArachnaeLog.Debug($"Added ARA_HiveMindDrone to {dronePawn.LabelShort}"); + dronePawn.health.AddHediff(HediffDef.Named("ARA_HiveMindBeast")); } + else + { + dronePawn.health.AddHediff(HediffDef.Named("ARA_HiveMindDrone")); + } + dronePawn.health.AddHediff(HediffDef.Named("ARA_LifespanHediff")); + ArachnaeLog.Debug($"Added ARA_HiveMindDrone to {dronePawn.LabelShort}"); // 4. 尝试绑定到主节点 Hediff_HiveMindMaster masterHediff = masterPawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("ARA_HiveMindMaster")) as Hediff_HiveMindMaster; diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_CompHediffGiver/CompHediffGiver.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_CompHediffGiver/CompHediffGiver.cs index 15485b6..d323029 100644 --- a/Source/ArachnaeSwarm/Pawn_Comps/ARA_CompHediffGiver/CompHediffGiver.cs +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_CompHediffGiver/CompHediffGiver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Verse; using RimWorld; @@ -7,7 +8,11 @@ namespace ArachnaeSwarm { public class CompHediffGiver : ThingComp { - private bool hediffsApplied = false; // 新增:标记是否已经应用过hediff + private bool hediffsApplied = false; // 标记是否已经应用过hediff + + // 用于记录添加的hediff和部位(用于调试和撤销) + private Dictionary> appliedHediffParts = + new Dictionary>(); public CompProperties_HediffGiver Props => (CompProperties_HediffGiver)this.props; @@ -18,7 +23,7 @@ namespace ArachnaeSwarm // 只有当thing是pawn时才添加hediff if (this.parent is Pawn pawn) { - // 新增:检查是否已经应用过hediff,或者是否是读档 + // 检查是否已经应用过hediff,或者是否是读档 if (!hediffsApplied && !respawningAfterLoad) { AddHediffsToPawn(pawn); @@ -36,6 +41,16 @@ namespace ArachnaeSwarm // 检查概率 if (Props.addChance < 1.0f && Rand.Value > Props.addChance) return; + + // 显示应用消息(如果有) + if (Props.showApplicationMessage && pawn.Faction == Faction.OfPlayer) + { + string message = Props.applicationMessageKey != null + ? Props.applicationMessageKey.Translate(pawn.LabelShort) + : "ARA_HediffGiver_Applied".Translate(pawn.LabelShort, Props.hediffs.Count); + + Messages.Message(message, pawn, MessageTypeDefOf.NeutralEvent); + } // 为每个hediff添加到pawn foreach (HediffDef hediffDef in Props.hediffs) @@ -46,60 +61,272 @@ namespace ArachnaeSwarm // === 新增:获取应应用的部位 === BodyPartDef bodyPartDef = Props.GetBodyPartForHediff(hediffDef); - BodyPartRecord bodyPartRecord = null; + List bodyParts = GetBodyPartsForHediff(pawn, hediffDef, bodyPartDef); - if (bodyPartDef != null) + // 如果没有指定部位,或者没有找到匹配部位,则添加到全身 + if (bodyParts == null || bodyParts.Count == 0) { - bodyPartRecord = GetFirstMatchingBodyPart(pawn, bodyPartDef); + AddHediffToPawn(pawn, hediffDef, null); + continue; } - // 添加hediff - if (bodyPartRecord != null) + // 根据选择规则添加hediff到相应部位 + foreach (var bodyPart in bodyParts) { - pawn.health.AddHediff(hediffDef, bodyPartRecord); + AddHediffToPawn(pawn, hediffDef, bodyPart); } - else + + // 记录应用到的部位 + if (bodyParts.Count > 0) { - pawn.health.AddHediff(hediffDef); + if (!appliedHediffParts.ContainsKey(hediffDef)) + { + appliedHediffParts[hediffDef] = new List(); + } + appliedHediffParts[hediffDef].AddRange(bodyParts); } } + + // 播放应用效果(如果有) + if (Props.applicationEffect != null && pawn.Spawned) + { + Effecter effecter = Props.applicationEffect.Spawn(); + effecter.Trigger(pawn, pawn); + effecter.Cleanup(); + } } /// - /// 获取第一个匹配的身体部位记录 + /// 添加hediff到pawn的指定部位 /// - private BodyPartRecord GetFirstMatchingBodyPart(Pawn pawn, BodyPartDef bodyPartDef) + private void AddHediffToPawn(Pawn pawn, HediffDef hediffDef, BodyPartRecord bodyPart) { - if (pawn == null || bodyPartDef == null || pawn.RaceProps?.body == null) - return null; + try + { + Hediff hediff = HediffMaker.MakeHediff(hediffDef, pawn, bodyPart); + // 设置严重度(如果有指定) + if (Props.initialSeverity >= 0f) + { + hediff.Severity = Props.initialSeverity; + } + + // 添加hediff + pawn.health.AddHediff(hediff); + + ArachnaeLog.Debug($"Added hediff {hediffDef.defName} to {pawn.Label} " + + $"{(bodyPart != null ? $"on {bodyPart.def.defName}" : "to whole body")}"); + } + catch (Exception) + { + } + } + + /// + /// 获取hediff应应用的身体部位列表 + /// + private List GetBodyPartsForHediff(Pawn pawn, HediffDef hediffDef, BodyPartDef bodyPartDef) + { + if (pawn == null || pawn.RaceProps?.body == null) + return null; + + // 如果没有指定身体部位,返回空列表 + if (bodyPartDef == null) + return null; + try { // 获取所有匹配的身体部位 - List matchingParts = pawn.RaceProps.body.GetPartsWithDef(bodyPartDef); + List allMatchingParts = pawn.RaceProps.body.GetPartsWithDef(bodyPartDef); - if (matchingParts != null && matchingParts.Count > 0) + if (allMatchingParts == null || allMatchingParts.Count == 0) { - // 返回第一个可用的部位 - return matchingParts[0]; + // 如果没有找到匹配部位,检查是否有备用部位 + var mapping = Props.GetMappingForHediff(hediffDef); + if (mapping != null && mapping.fallbackToDefault) + { + // 使用默认方式添加(全身) + return null; + } + return new List(); + } + + // 检查部位有效性 + if (Props.checkPartValidity) + { + allMatchingParts = allMatchingParts.Where(part => IsBodyPartValid(pawn, part)).ToList(); + } + + if (allMatchingParts.Count == 0) + { + return new List(); + } + + // 获取选择规则 + BodyPartSelectionRule rule = Props.GetPartSelectionRuleForHediff(hediffDef); + var mappingConfig = Props.GetMappingForHediff(hediffDef); + + // 根据规则选择部位 + List selectedParts = new List(); + + if (rule == null) + { + // 默认规则:使用第一个匹配的部位 + selectedParts.Add(allMatchingParts[0]); + } + else + { + // 根据规则选择部位 + switch (rule.mode) + { + case BodyPartSelectionMode.First: + selectedParts.Add(allMatchingParts[0]); + break; + + case BodyPartSelectionMode.Random: + int count = Math.Min(rule.maxParts, allMatchingParts.Count); + selectedParts = allMatchingParts.InRandomOrder().Take(count).ToList(); + break; + + case BodyPartSelectionMode.All: + selectedParts = allMatchingParts; + break; + + case BodyPartSelectionMode.MostDamaged: + var mostDamaged = allMatchingParts.OrderByDescending(p => GetPartDamage(pawn, p)).FirstOrDefault(); + if (mostDamaged != null) selectedParts.Add(mostDamaged); + break; + + case BodyPartSelectionMode.LeastDamaged: + var leastDamaged = allMatchingParts.OrderBy(p => GetPartDamage(pawn, p)).FirstOrDefault(); + if (leastDamaged != null) selectedParts.Add(leastDamaged); + break; + } + + // 如果配置了随机选择部位 + if (mappingConfig != null && mappingConfig.chooseRandomPart) + { + selectedParts = allMatchingParts.InRandomOrder().Take(1).ToList(); + } + + // 处理对称性 + if (rule.symmetrical && selectedParts.Count > 0) + { + selectedParts = GetSymmetricalParts(pawn, selectedParts[0], allMatchingParts); + } + } + + return selectedParts; + } + catch (Exception) + { + return null; + } + } + + /// + /// 获取对称的身体部位 + /// + private List GetSymmetricalParts(Pawn pawn, BodyPartRecord referencePart, List allParts) + { + List symmetricalParts = new List { referencePart }; + + // 查找对称部位(如左臂对应的右臂) + foreach (var part in allParts) + { + if (part != referencePart && part.def == referencePart.def) + { + // 简单的对称检测:检查部位标签或名称 + if (IsSymmetricalPart(referencePart, part)) + { + symmetricalParts.Add(part); + } } } - catch (Exception ex) + + return symmetricalParts; + } + + /// + /// 检查两个部位是否对称 + /// + private bool IsSymmetricalPart(BodyPartRecord part1, BodyPartRecord part2) + { + // 简单的对称检测逻辑 + string label1 = part1.customLabel ?? part1.def.label; + string label2 = part2.customLabel ?? part2.def.label; + + // 检查是否包含对称标识符 + string[] symmetryKeywords = { "Left", "Right", "Left", "Right", "Front", "Back", "Upper", "Lower" }; + + foreach (var keyword in symmetryKeywords) { - ArachnaeLog.Debug($"Error getting body part for {bodyPartDef.defName}: {ex.Message}"); + if (label1.Contains(keyword) && label2.Contains(keyword)) + { + // 检查是否是对称的关键词对 + if ((keyword == "Left" && label2.Contains("Right")) || + (keyword == "Right" && label2.Contains("Left")) || + (keyword == "Front" && label2.Contains("Back")) || + (keyword == "Back" && label2.Contains("Front"))) + { + return true; + } + } } - return null; + return false; + } + + /// + /// 检查身体部位是否有效 + /// + private bool IsBodyPartValid(Pawn pawn, BodyPartRecord part) + { + if (part == null) + return false; + + // 检查部位是否已完全损坏 + if (pawn.health.hediffSet.PartIsMissing(part)) + return false; + + // 检查部位是否被覆盖(如被装备遮挡) + // 这里可以添加更多有效性检查 + + return true; + } + + /// + /// 获取部位的伤害值 + /// + private float GetPartDamage(Pawn pawn, BodyPartRecord part) + { + if (part == null) + return 0f; + + float totalDamage = 0f; + foreach (var hediff in pawn.health.hediffSet.hediffs) + { + if (hediff.Part == part && hediff is Hediff_Injury injury) + { + totalDamage += injury.Severity; + } + } + + return totalDamage; } - // 新增:序列化hediffsApplied标记 + // 序列化hediffsApplied标记 public override void PostExposeData() { base.PostExposeData(); Scribe_Values.Look(ref hediffsApplied, "hediffsApplied", false); + + // 新增:序列化已应用的hediff部位记录 + Scribe_Collections.Look(ref appliedHediffParts, "appliedHediffParts", + LookMode.Def, LookMode.Deep); } - // 新增:调试方法,用于手动触发hediff添加(仅开发模式) + // 调试方法,用于手动触发hediff添加(仅开发模式) public void DebugApplyHediffs() { if (this.parent is Pawn pawn && !hediffsApplied) @@ -111,7 +338,7 @@ namespace ArachnaeSwarm } /// - /// === 新增:获取已应用的hediff信息(用于调试) === + /// 获取已应用的hediff信息(用于调试) /// public string GetAppliedHediffInfo() { @@ -123,11 +350,16 @@ namespace ArachnaeSwarm foreach (var hediffDef in Props.hediffs) { - var hediff = pawn.health.hediffSet.GetFirstHediffOfDef(hediffDef); - if (hediff != null) + var hediffs = pawn.health.hediffSet.hediffs.Where(h => h.def == hediffDef).ToList(); + + if (hediffs.Count > 0) { - string partInfo = hediff.Part?.def?.defName ?? "No specific part"; - result.AppendLine($"- {hediffDef.defName} on {partInfo}"); + result.AppendLine($"- {hediffDef.defName} ({hediffs.Count} instances):"); + foreach (var hediff in hediffs) + { + string partInfo = hediff.Part?.def?.defName ?? "No specific part"; + result.AppendLine($" * On {partInfo}, Severity: {hediff.Severity:F2}"); + } } else { @@ -137,5 +369,42 @@ namespace ArachnaeSwarm return result.ToString(); } + + /// + /// 新增:移除所有由该组件添加的hediff + /// + public void RemoveAppliedHediffs() + { + if (!(this.parent is Pawn pawn) || !hediffsApplied) + return; + + int removedCount = 0; + foreach (var hediffDef in Props.hediffs) + { + var hediffs = pawn.health.hediffSet.hediffs.Where(h => h.def == hediffDef).ToList(); + foreach (var hediff in hediffs) + { + pawn.health.RemoveHediff(hediff); + removedCount++; + } + } + + hediffsApplied = false; + appliedHediffParts.Clear(); + + ArachnaeLog.Debug($"Removed {removedCount} hediffs from {pawn.Label}"); + } + + /// + /// 新增:检查特定hediff是否已应用到指定部位 + /// + public bool HasHediffOnPart(HediffDef hediffDef, BodyPartRecord part) + { + if (!(this.parent is Pawn pawn) || !hediffsApplied) + return false; + + return pawn.health.hediffSet.hediffs + .Any(h => h.def == hediffDef && h.Part == part); + } } } diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_CompHediffGiver/CompProperties_HediffGiver.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_CompHediffGiver/CompProperties_HediffGiver.cs index 201de1f..1aef7f9 100644 --- a/Source/ArachnaeSwarm/Pawn_Comps/ARA_CompHediffGiver/CompProperties_HediffGiver.cs +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_CompHediffGiver/CompProperties_HediffGiver.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Verse; namespace ArachnaeSwarm @@ -10,6 +11,51 @@ namespace ArachnaeSwarm { public HediffDef hediff; public BodyPartDef bodyPart; + + /// + /// 可选:优先级(数值越大优先级越高,默认1) + /// + public int priority = 1; + + /// + /// 可选:是否随机选择多个部位(如果pawn有多个相同部位) + /// + public bool chooseRandomPart = false; + + /// + /// 可选:如果找不到指定部位,是否使用默认添加方式 + /// + public bool fallbackToDefault = true; + } + + /// + /// 身体部位选择规则 + /// + public class BodyPartSelectionRule + { + /// + /// 选择模式:First(第一个匹配的)、Random(随机一个)、All(所有匹配的) + /// + public BodyPartSelectionMode mode = BodyPartSelectionMode.First; + + /// + /// 仅当mode为Random时有效:最多选择多少个部位 + /// + public int maxParts = 1; + + /// + /// 是否对称(如果pawn有左右对称的相同部位) + /// + public bool symmetrical = false; + } + + public enum BodyPartSelectionMode + { + First, // 第一个匹配的部位 + Random, // 随机选择一个匹配的部位 + All, // 所有匹配的部位 + MostDamaged, // 受伤最严重的部位 + LeastDamaged // 受伤最轻的部位 } public class CompProperties_HediffGiver : CompProperties @@ -28,33 +74,135 @@ namespace ArachnaeSwarm // === 新增:自定义部位映射(使用List替代Dictionary) === public List customBodyPartMapping = null; + + // === 新增:全局身体部位选择规则 === + public BodyPartSelectionRule globalPartSelectionRule = null; + + // === 新增:按hediff分类的身体部位选择规则 === + public List hediffPartSelectionRules = null; + + // === 新增:添加hediff时的严重度设置 === + public float initialSeverity = -1f; // 负数表示使用hediff默认值 + + // === 新增:是否检查部位有效性(是否存在、是否已损坏等) === + public bool checkPartValidity = true; + + // === 新增:如果指定部位不存在时的备用部位 === + public BodyPartDef fallbackBodyPart = null; + + // === 新增:添加hediff时的效果 === + public EffecterDef applicationEffect = null; + + // === 新增:是否在添加时显示消息 === + public bool showApplicationMessage = false; + + // === 新增:添加消息的翻译键 === + public string applicationMessageKey = null; + + // === 新增:添加hediff时的声音 === + public SoundDef applicationSound = null; public CompProperties_HediffGiver() { this.compClass = typeof(CompHediffGiver); } + /// + /// 根据hediff获取对应的身体部位定义 + /// public BodyPartDef GetBodyPartForHediff(HediffDef hediffDef) { if (hediffDef == null) return null; + // 1. 首先检查自定义映射 if (customBodyPartMapping != null) { + // 查找优先级最高的映射 + HediffBodyPartMapping bestMapping = null; + int highestPriority = int.MinValue; + foreach (var mapping in customBodyPartMapping) { if (mapping.hediff == hediffDef) - return mapping.bodyPart; + { + if (mapping.priority > highestPriority) + { + bestMapping = mapping; + highestPriority = mapping.priority; + } + } + } + + if (bestMapping != null) + { + return bestMapping.bodyPart; } } + // 2. 然后检查默认安装部位 if (useDefaultInstallPart && hediffDef.defaultInstallPart != null) { return hediffDef.defaultInstallPart; } - return null; + // 3. 最后检查备用部位 + return fallbackBodyPart; + } + + /// + /// 获取hediff对应的身体部位选择规则 + /// + public BodyPartSelectionRule GetPartSelectionRuleForHediff(HediffDef hediffDef) + { + if (hediffDef == null) + return globalPartSelectionRule; + + // 1. 首先检查hediff特定的规则 + if (hediffPartSelectionRules != null) + { + var rule = hediffPartSelectionRules.FirstOrDefault(r => r.hediff == hediffDef); + if (rule != null) + { + return rule.selectionRule; + } + } + + // 2. 返回全局规则 + return globalPartSelectionRule; + } + + /// + /// 获取hediff对应的映射配置 + /// + public HediffBodyPartMapping GetMappingForHediff(HediffDef hediffDef) + { + if (hediffDef == null || customBodyPartMapping == null) + return null; + + // 查找优先级最高的映射 + HediffBodyPartMapping bestMapping = null; + int highestPriority = int.MinValue; + + foreach (var mapping in customBodyPartMapping) + { + if (mapping.hediff == hediffDef && mapping.priority > highestPriority) + { + bestMapping = mapping; + highestPriority = mapping.priority; + } + } + + return bestMapping; } } + + /// + /// Hediff与部位选择规则的映射 + /// + public class HediffPartSelectionRule + { + public HediffDef hediff; + public BodyPartSelectionRule selectionRule; + } } - diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_PawnResearchBlueprintReader/CompProperties_PawnResearchBlueprintReader.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_PawnResearchBlueprintReader/CompProperties_PawnResearchBlueprintReader.cs new file mode 100644 index 0000000..62107ca --- /dev/null +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_PawnResearchBlueprintReader/CompProperties_PawnResearchBlueprintReader.cs @@ -0,0 +1,72 @@ +using RimWorld; +using Verse; + +namespace ArachnaeSwarm +{ + public class CompProperties_PawnResearchBlueprintReader : CompProperties + { + /// + /// 是否允许该Pawn进行科技研究 + /// + public bool canResearch = true; + + /// + /// 研究速度乘数(基于智力等级) + /// + public float researchSpeedMultiplier = 1.0f; + + /// + /// 是否显示研究进度Gizmo + /// + public bool showResearchGizmo = true; + + /// + /// 是否自动开始研究(当有未完成科技时) + /// + public bool autoStartResearch = true; + + /// + /// 所需研究标签页(默认为阿拉克涅科技标签页) + /// + public ResearchTabDef requiredResearchTab = null; // 将在PostLoad中设置默认值 + + /// + /// 每秒研究的点数(基础值,会乘以智力等级) + /// + public float baseResearchPointsPerSecond = 1.0f; + + /// + /// 是否使用灵能科研点(如果为true,需要Comp_SwarmSpellHolder) + /// + public bool usePsychicResearchPoints = true; + + /// + /// 研究完成的提示消息翻译键 + /// + public string researchCompletedMessageKey = "ARA_PawnResearch_Completed"; + + /// + /// 研究开始的提示消息翻译键 + /// + public string researchStartedMessageKey = "ARA_PawnResearch_Started"; + + /// + /// 灵能科研点不足的提示消息翻译键 + /// + public string researchNoPointsMessageKey = "ARA_PawnResearch_NoPoints"; + + public CompProperties_PawnResearchBlueprintReader() + { + this.compClass = typeof(Comp_PawnResearchBlueprintReader); + } + + public void PostLoad() + { + // 如果没有指定研究标签页,使用默认的阿拉克涅科技标签页 + if (requiredResearchTab == null) + { + requiredResearchTab = DefDatabase.GetNamedSilentFail("ARA_ResearchTab"); + } + } + } +} diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_PawnResearchBlueprintReader/Comp_PawnResearchBlueprintReader.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_PawnResearchBlueprintReader/Comp_PawnResearchBlueprintReader.cs new file mode 100644 index 0000000..7462a18 --- /dev/null +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_PawnResearchBlueprintReader/Comp_PawnResearchBlueprintReader.cs @@ -0,0 +1,849 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + public class Comp_PawnResearchBlueprintReader : ThingComp + { + // === 字段定义 === + + /// + /// 当前研究的科技项目 + /// + private ResearchProjectDef currentResearch = null; + + /// + /// 是否正在研究中 + /// + private bool isResearching = false; + + /// + /// 灵能术法组件引用 + /// + public Comp_SwarmSpellHolder spellHolder = null; + + /// + /// 上次研究更新的tick数 + /// + private int lastResearchTick = -1; + + /// + /// 研究进度(已添加到ResearchManager中的进度) + /// + private float currentProgress = 0f; + + /// + /// 研究开始时间(游戏内ticks) + /// + private int researchStartTick = -1; + + /// + /// 临时存储的研究点数(用于累积小数) + /// + private float accumulatedResearchPoints = 0f; + + // === 属性 === + + public CompProperties_PawnResearchBlueprintReader Props => + (CompProperties_PawnResearchBlueprintReader)this.props; + + public Pawn Pawn => this.parent as Pawn; + + /// + /// 当前研究的科技 + /// + public ResearchProjectDef CurrentResearch + { + get => currentResearch; + private set + { + currentResearch = value; + if (currentResearch != null) + { + // 获取当前全局进度 + currentProgress = Find.ResearchManager.GetProgress(currentResearch); + } + else + { + currentProgress = 0f; + } + } + } + + /// + /// 当前进度 + /// + public float CurrentProgress => currentProgress; + + /// + /// 是否正在研究中 + /// + public bool IsResearching => isResearching && currentResearch != null && !currentResearch.IsFinished; + + /// + /// 研究速度(每秒点数) + /// + public float ResearchSpeedPerSecond + { + get + { + if (Pawn == null || Pawn.skills == null) + return Props.baseResearchPointsPerSecond * Props.researchSpeedMultiplier; + + // 基础速度乘以智力等级 + float intellectualLevel = Pawn.skills.GetSkill(SkillDefOf.Intellectual).Level; + float baseSpeed = Props.baseResearchPointsPerSecond * Props.researchSpeedMultiplier; + + // 智力等级影响(每级增加10%速度) + float intellectualMultiplier = 1.0f + (intellectualLevel * 0.1f); + + return baseSpeed * intellectualMultiplier; + } + } + + /// + /// 每秒消耗的灵能科研点 + /// + public float PsychicResearchPointsPerSecond + { + get + { + // 如果不需要灵能科研点,返回0 + if (!Props.usePsychicResearchPoints || spellHolder == null) + return 0f; + + // 每研究1点需要1点灵能科研点 + return ResearchSpeedPerSecond; + } + } + + /// + /// Pawn是否可以进行研究 + /// + public bool CanResearch + { + get + { + if (!Props.canResearch) + return false; + + if (Pawn == null || Pawn.Dead || Pawn.Downed || !Pawn.Spawned) + return false; + + // 如果需要灵能科研点,检查是否有足够的灵能科研点 + if (Props.usePsychicResearchPoints && spellHolder != null) + { + // 检查是否有足够的灵能科研点来支持每秒的研究 + if (spellHolder.PsychicResearchPoints < PsychicResearchPointsPerSecond) + return false; + } + + return true; + } + } + + /// + /// 研究进度百分比(0-1) + /// + public float ResearchProgressPercent + { + get + { + if (currentResearch == null || currentResearch.baseCost <= 0) + return 0f; + + return Mathf.Clamp01(currentProgress / currentResearch.baseCost); + } + } + + /// + /// 剩余研究时间(秒) + /// + public float RemainingResearchTimeSeconds + { + get + { + if (currentResearch == null || currentResearch.baseCost <= 0 || !IsResearching) + return -1f; + + float remainingPoints = currentResearch.baseCost - currentProgress; + if (ResearchSpeedPerSecond <= 0) + return -1f; + + return remainingPoints / ResearchSpeedPerSecond; + } + } + + // === 生命周期方法 === + + public override void PostSpawnSetup(bool respawningAfterLoad) + { + base.PostSpawnSetup(respawningAfterLoad); + + // 获取灵能术法组件 + spellHolder = Pawn?.GetComp(); + + // 如果自动开始研究且当前有研究项目,开始研究 + if (Props.autoStartResearch && currentResearch != null && !currentResearch.IsFinished) + { + StartResearching(); + } + + // 重置上次研究更新时间 + lastResearchTick = Find.TickManager.TicksGame; + } + + public override void CompTick() + { + base.CompTick(); + + // 每秒更新一次研究进度 + if (Find.TickManager.TicksGame % 60 == 0) + { + UpdateResearch(); + } + } + + public override void PostExposeData() + { + base.PostExposeData(); + + Scribe_Defs.Look(ref currentResearch, "currentResearch"); + Scribe_Values.Look(ref isResearching, "isResearching", false); + Scribe_Values.Look(ref lastResearchTick, "lastResearchTick", -1); + Scribe_Values.Look(ref currentProgress, "currentProgress", 0f); + Scribe_Values.Look(ref researchStartTick, "researchStartTick", -1); + Scribe_Values.Look(ref accumulatedResearchPoints, "accumulatedResearchPoints", 0f); + + if (Scribe.mode == LoadSaveMode.PostLoadInit) + { + // 确保数据一致性 + if (currentResearch == null) + { + isResearching = false; + currentProgress = 0f; + researchStartTick = -1; + accumulatedResearchPoints = 0f; + } + } + } + + // === 研究逻辑方法 === + + /// + /// 开始研究新科技 + /// + public void StartNewResearch(ResearchProjectDef project) + { + if (project == null) + { + return; + } + + if (project.IsFinished) + { + Messages.Message("ARA_PawnResearch_AlreadyCompleted".Translate(project.LabelCap), + Pawn, MessageTypeDefOf.RejectInput); + return; + } + + // 检查前置条件 + if (!AreAllPrerequisitesCompleted(project)) + { + Messages.Message("ARA_PawnResearch_PrerequisitesNotMet".Translate(project.LabelCap), + Pawn, MessageTypeDefOf.RejectInput); + return; + } + + // 检查是否有足够的灵能科研点 + if (Props.usePsychicResearchPoints && spellHolder != null) + { + float requiredPoints = project.baseCost - Find.ResearchManager.GetProgress(project); + if (!spellHolder.HasEnoughPsychicResearchPoints(requiredPoints)) + { + Messages.Message(Props.researchNoPointsMessageKey.Translate(project.LabelCap), + Pawn, MessageTypeDefOf.RejectInput); + return; + } + } + + // 设置当前研究 + CurrentResearch = project; + + // 开始研究 + StartResearching(); + + // 发送消息 + if (!Props.researchStartedMessageKey.NullOrEmpty()) + { + Messages.Message(Props.researchStartedMessageKey.Translate(Pawn.LabelShortCap, project.LabelCap), + Pawn, MessageTypeDefOf.PositiveEvent); + } + + ArachnaeLog.Debug($"[PawnResearch] {Pawn.LabelShort} 开始研究: {project.defName}"); + } + + /// + /// 开始研究(内部方法) + /// + private void StartResearching() + { + if (currentResearch == null || currentResearch.IsFinished) + { + isResearching = false; + return; + } + + isResearching = true; + researchStartTick = Find.TickManager.TicksGame; + lastResearchTick = researchStartTick; + + // 获取当前全局进度 + currentProgress = Find.ResearchManager.GetProgress(currentResearch); + + ArachnaeLog.Debug($"[PawnResearch] {Pawn?.LabelShort} 开始研究中: {currentResearch.defName}"); + } + + /// + /// 停止研究 + /// + public void StopResearching() + { + isResearching = false; + researchStartTick = -1; + + ArachnaeLog.Debug($"[PawnResearch] {Pawn?.LabelShort} 停止研究"); + } + + /// + /// 更新研究进度 + /// + private void UpdateResearch() + { + if (!IsResearching || !CanResearch) + { + // 如果不能研究,停止研究 + if (isResearching) + { + StopResearching(); + } + return; + } + + try + { + // 计算本次应该研究多少点 + float researchPoints = ResearchSpeedPerSecond; + accumulatedResearchPoints += researchPoints; + + // 如果累积的研究点数大于等于1,则添加到全局进度 + if (accumulatedResearchPoints >= 1.0f) + { + int pointsToAdd = Mathf.FloorToInt(accumulatedResearchPoints); + + // 消耗灵能科研点 + if (Props.usePsychicResearchPoints && spellHolder != null) + { + if (!spellHolder.ConsumePsychicResearchPoints(pointsToAdd, $"研究 {currentResearch.LabelCap}")) + { + // 灵能科研点不足,停止研究 + StopResearching(); + Messages.Message(Props.researchNoPointsMessageKey.Translate(currentResearch.LabelCap), + Pawn, MessageTypeDefOf.NegativeEvent); + return; + } + } + + // 添加到全局研究进度 + AddResearchProgress(pointsToAdd); + + // 减去已添加的点数 + accumulatedResearchPoints -= pointsToAdd; + } + + // 检查是否完成 + CheckResearchCompletion(); + + // 更新上次研究时间 + lastResearchTick = Find.TickManager.TicksGame; + } + catch (Exception ex) + { + ArachnaeLog.Debug($"[PawnResearch] 更新研究进度时出错: {ex.Message}"); + StopResearching(); + } + } + + /// + /// 添加研究进度到全局 + /// + private void AddResearchProgress(float points) + { + if (currentResearch == null || currentResearch.IsFinished || points <= 0) + return; + + // 获取当前全局进度 + float globalProgress = Find.ResearchManager.GetProgress(currentResearch); + float remaining = currentResearch.baseCost - globalProgress; + + // 确保不会超过总需求 + float pointsToAdd = Mathf.Min(points, remaining); + if (pointsToAdd > 0) + { + Find.ResearchManager.AddProgress(currentResearch, pointsToAdd); + + // 更新本地进度 + currentProgress = Find.ResearchManager.GetProgress(currentResearch); + } + } + + /// + /// 检查研究是否完成 + /// + private void CheckResearchCompletion() + { + if (currentResearch == null) + return; + + if (currentResearch.IsFinished) + { + // 研究完成 + OnResearchCompleted(); + } + } + + /// + /// 研究完成时的处理 + /// + private void OnResearchCompleted() + { + if (currentResearch == null) + return; + + // 发送完成消息 + if (!Props.researchCompletedMessageKey.NullOrEmpty()) + { + Messages.Message(Props.researchCompletedMessageKey.Translate(Pawn.LabelShortCap, currentResearch.LabelCap), + Pawn, MessageTypeDefOf.PositiveEvent); + } + + ArachnaeLog.Debug($"[PawnResearch] {Pawn.LabelShort} 完成研究: {currentResearch.defName}"); + + // 停止研究 + StopResearching(); + + // 清空当前研究 + CurrentResearch = null; + currentProgress = 0f; + accumulatedResearchPoints = 0f; + } + + /// + /// 强制完成当前研究(调试用) + /// + public void ForceCompleteResearch() + { + if (currentResearch == null || currentResearch.IsFinished) + return; + + // 消耗剩余的灵能科研点 + if (Props.usePsychicResearchPoints && spellHolder != null) + { + float remainingPoints = currentResearch.baseCost - Find.ResearchManager.GetProgress(currentResearch); + spellHolder.ConsumePsychicResearchPoints(remainingPoints, $"强制完成研究 {currentResearch.LabelCap}"); + } + + // 直接完成研究 + Find.ResearchManager.FinishProject(currentResearch, false); + OnResearchCompleted(); + } + + // === 条件检查方法 === + + /// + /// 检查所有前置条件是否完成 + /// + private bool AreAllPrerequisitesCompleted(ResearchProjectDef project) + { + if (project == null) + return false; + + // 检查普通前置 + if (project.prerequisites != null) + { + foreach (var prereq in project.prerequisites) + { + if (!prereq.IsFinished) + return false; + } + } + + // 检查隐藏前置 + if (project.hiddenPrerequisites != null) + { + foreach (var hiddenPrereq in project.hiddenPrerequisites) + { + if (hiddenPrereq != null && !hiddenPrereq.IsFinished) + return false; + } + } + + return true; + } + + private bool HasMissingHiddenPrerequisites(ResearchProjectDef project) + { + if (project.hiddenPrerequisites != null) + { + foreach (var hiddenPrereq in project.hiddenPrerequisites) + { + if (hiddenPrereq != null && !hiddenPrereq.IsFinished) + return true; + } + } + return false; + } + + // === Gizmo 和 UI 方法 === + + public override IEnumerable CompGetGizmosExtra() + { + foreach (Gizmo gizmo in base.CompGetGizmosExtra()) + { + yield return gizmo; + } + + if (!Props.showResearchGizmo || Pawn == null || Pawn.Faction != Faction.OfPlayer) + yield break; + + // 研究进度 Gizmo + yield return new Gizmo_PawnResearchProgress(this); + + // 选择研究按钮 + if (CanResearch || currentResearch == null) + { + var selectCmd = new Command_Action(); + selectCmd.defaultLabel = "ARA_PawnResearch_SelectProject".Translate(); + selectCmd.defaultDesc = "ARA_PawnResearch_SelectProjectDesc".Translate(); + selectCmd.icon = ContentFinder.Get("ArachnaeSwarm/UI/Abilities/ARA_EggSpew_Techprint", false); + selectCmd.action = delegate + { + ShowResearchMenu(); + }; + yield return selectCmd; + } + + // 开始/停止研究按钮 + if (currentResearch != null && !currentResearch.IsFinished) + { + if (IsResearching) + { + var stopCmd = new Command_Action(); + stopCmd.defaultLabel = "ARA_PawnResearch_StopResearch".Translate(); + stopCmd.defaultDesc = "ARA_PawnResearch_StopResearchDesc".Translate(); + stopCmd.icon = ContentFinder.Get("UI/Commands/Halt", false); + stopCmd.action = delegate + { + StopResearching(); + Messages.Message("ARA_PawnResearch_Stopped".Translate(Pawn.LabelShortCap, currentResearch.LabelCap), + Pawn, MessageTypeDefOf.NeutralEvent); + }; + yield return stopCmd; + } + else + { + var startCmd = new Command_Action(); + startCmd.defaultLabel = "ARA_PawnResearch_StartResearch".Translate(); + startCmd.defaultDesc = "ARA_PawnResearch_StartResearchDesc".Translate(); + startCmd.icon = ContentFinder.Get("UI/Commands/Play", false); + startCmd.action = delegate + { + StartResearching(); + Messages.Message("ARA_PawnResearch_Started".Translate(Pawn.LabelShortCap, currentResearch.LabelCap), + Pawn, MessageTypeDefOf.PositiveEvent); + }; + yield return startCmd; + } + } + + // 调试按钮 + if (DebugSettings.godMode && currentResearch != null && !currentResearch.IsFinished) + { + var debugCmd = new Command_Action(); + debugCmd.defaultLabel = "ARA_PawnResearch_DebugComplete".Translate(); + debugCmd.defaultDesc = "ARA_PawnResearch_DebugCompleteDesc".Translate(); + debugCmd.icon = ContentFinder.Get("UI/Designators/Dev", false); + debugCmd.action = delegate + { + ForceCompleteResearch(); + }; + yield return debugCmd; + } + } + + /// + /// 显示研究选择菜单 + /// + public void ShowResearchMenu() + { + try + { + // 使用指定的研究标签页 + ResearchTabDef researchTab = Props.requiredResearchTab; + if (researchTab == null) + { + // 如果没有指定,使用默认的阿拉克涅科技标签页 + researchTab = DefDatabase.GetNamedSilentFail("ARA_ResearchTab"); + if (researchTab == null) + { + Messages.Message("ARA_PawnResearch_TabNotFound".Translate(), MessageTypeDefOf.RejectInput); + return; + } + } + + // 获取可用的研究项目 + var availableProjects = DefDatabase.AllDefsListForReading + .Where(p => p.tab == researchTab && p.techprintCount > 0 && !p.IsFinished) + .ToList(); + + if (availableProjects.Count == 0) + { + Messages.Message("ARA_PawnResearch_NoProjectsAvailable".Translate(), MessageTypeDefOf.NeutralEvent); + return; + } + + // 创建菜单选项 + List options = new List(); + var sortedProjects = availableProjects.OrderBy(p => p.defName).ToList(); + + foreach (var project in sortedProjects) + { + bool allPrerequisitesMet = AreAllPrerequisitesCompleted(project); + + // 检查是否有足够的灵能科研点 + bool hasEnoughPoints = true; + if (Props.usePsychicResearchPoints && spellHolder != null) + { + float requiredPoints = project.baseCost - Find.ResearchManager.GetProgress(project); + hasEnoughPoints = spellHolder.HasEnoughPsychicResearchPoints(requiredPoints); + } + + string label = project.LabelCap.RawText ?? project.defName; + float globalProgress = Find.ResearchManager.GetProgress(project); + + // 显示进度 + if (globalProgress > 0) + label += $" ({globalProgress:F0}/{project.baseCost:F0})"; + + // 显示所需灵能科研点 + if (Props.usePsychicResearchPoints && spellHolder != null) + { + float requiredPoints = project.baseCost - globalProgress; + label += $" [{requiredPoints:F0}点]"; + } + + // 根据条件设置颜色 + if (!allPrerequisitesMet) + { + bool missingHidden = HasMissingHiddenPrerequisites(project); + if (missingHidden) + label = $"{label} [{"ARA_PawnResearch_HiddenPrerequisite".Translate()}]"; + else + label = $"{label} [{"ARA_PawnResearch_PrerequisitesNeeded".Translate()}]"; + } + else if (!hasEnoughPoints) + { + label = $"{label} [{"ARA_PawnResearch_InsufficientPoints".Translate()}]"; + } + + // 创建菜单选项 + var option = new FloatMenuOption(label, () => StartNewResearch(project)) + { + Disabled = !allPrerequisitesMet || !hasEnoughPoints, + tooltip = GetProjectTooltip(project, allPrerequisitesMet, hasEnoughPoints) + }; + + options.Add(option); + } + + if (options.Count > 0) + { + Find.WindowStack.Add(new FloatMenu(options)); + } + } + catch (Exception ex) + { + ArachnaeLog.Debug($"[PawnResearch] Error in ShowResearchMenu: {ex}"); + Messages.Message("ARA_PawnResearch_MenuError".Translate(ex.Message), + MessageTypeDefOf.NegativeEvent); + } + } + + /// + /// 获取研究项目的工具提示 + /// + private string GetProjectTooltip(ResearchProjectDef project, bool prerequisitesMet, bool hasEnoughPoints) + { + var builder = new System.Text.StringBuilder(); + + builder.AppendLine(project.description.StripTags()); + builder.AppendLine(); + + builder.AppendLine("ARA_PawnResearch_ProjectCost".Translate() + $": {project.baseCost}"); + builder.AppendLine("ARA_PawnResearch_BlueprintCount".Translate() + $": {project.techprintCount}"); + + float globalProgress = Find.ResearchManager.GetProgress(project); + builder.AppendLine("ARA_PawnResearch_CurrentProgress".Translate() + $": {globalProgress:F0}/{project.baseCost:F0}"); + + // 显示所需灵能科研点 + if (Props.usePsychicResearchPoints && spellHolder != null) + { + float requiredPoints = project.baseCost - globalProgress; + builder.AppendLine("ARA_PawnResearch_RequiredPoints".Translate() + $": {requiredPoints:F0}"); + builder.AppendLine("ARA_PawnResearch_CurrentPoints".Translate() + $": {spellHolder.PsychicResearchPoints:F0}"); + } + + // 显示研究速度 + builder.AppendLine("ARA_PawnResearch_Speed".Translate() + $": {ResearchSpeedPerSecond:F1}/秒"); + + // 显示研究时间估算 + if (prerequisitesMet && hasEnoughPoints) + { + float remainingPoints = project.baseCost - globalProgress; + float timeSeconds = remainingPoints / ResearchSpeedPerSecond; + int ticks = Mathf.RoundToInt(timeSeconds * 60f); + string timeStr = ticks.ToStringTicksToPeriod(allowSeconds: true, shortForm: true); + builder.AppendLine("ARA_PawnResearch_EstimatedTime".Translate() + $": {timeStr}"); + } + + // 检查未完成的前置 + if (!prerequisitesMet) + { + List missingPrereqs = new List(); + + // 普通前置 + if (project.prerequisites != null) + { + foreach (var prereq in project.prerequisites) + { + if (!prereq.IsFinished) + missingPrereqs.Add(prereq); + } + } + + // 隐藏前置 + if (project.hiddenPrerequisites != null) + { + foreach (var hiddenPrereq in project.hiddenPrerequisites) + { + if (hiddenPrereq != null && !hiddenPrereq.IsFinished) + missingPrereqs.Add(hiddenPrereq); + } + } + + if (missingPrereqs.Count > 0) + { + builder.AppendLine(); + builder.AppendLine("" + "ARA_PawnResearch_MissingPrerequisites".Translate() + ":"); + + foreach (var prereq in missingPrereqs) + { + string label = prereq.LabelCap.RawText ?? prereq.defName; + bool isHidden = project.hiddenPrerequisites != null && + project.hiddenPrerequisites.Contains(prereq); + + if (isHidden) + builder.AppendLine($" • {label} [{"ARA_PawnResearch_MissingHiddenPrerequisites".Translate()}]"); + else + builder.AppendLine($" • {label}"); + } + } + } + + // 检查灵能科研点是否足够 + if (!hasEnoughPoints && Props.usePsychicResearchPoints && spellHolder != null) + { + float requiredPoints = project.baseCost - globalProgress; + builder.AppendLine(); + builder.AppendLine("" + "ARA_PawnResearch_InsufficientPoints".Translate() + ":"); + builder.AppendLine($" • {"ARA_PawnResearch_Required".Translate()}: {requiredPoints:F0}"); + builder.AppendLine($" • {"ARA_PawnResearch_Available".Translate()}: {spellHolder.PsychicResearchPoints:F0}"); + } + + return builder.ToString(); + } + + // === 调试和信息方法 === + + /// + /// 获取研究状态描述 + /// + public string GetResearchStatus() + { + if (Pawn == null) + return "无Pawn"; + + if (currentResearch == null) + return $"{Pawn.LabelShort}: 未选择研究项目"; + + if (currentResearch.IsFinished) + return $"{Pawn.LabelShort}: 已完成研究 {currentResearch.LabelCap}"; + + string status = IsResearching ? "研究中" : "暂停中"; + string progress = $"{currentProgress:F0}/{currentResearch.baseCost:F0} ({ResearchProgressPercent:P0})"; + + string pointsInfo = ""; + if (Props.usePsychicResearchPoints && spellHolder != null) + { + pointsInfo = $", 灵能科研点: {spellHolder.PsychicResearchPoints:F0}"; + } + + return $"{Pawn.LabelShort}: {status} {currentResearch.LabelCap} ({progress}{pointsInfo})"; + } + + /// + /// 获取调试信息 + /// + public string GetDebugInfo() + { + var builder = new System.Text.StringBuilder(); + + builder.AppendLine("=== Pawn研究蓝图解读器调试信息 ==="); + builder.AppendLine($"Pawn: {Pawn?.LabelShort ?? "无"}"); + builder.AppendLine($"可研究: {CanResearch}"); + builder.AppendLine($"正在研究: {IsResearching}"); + + if (currentResearch != null) + { + builder.AppendLine($"当前研究: {currentResearch.defName}"); + builder.AppendLine($"研究进度: {currentProgress:F0}/{currentResearch.baseCost:F0} ({ResearchProgressPercent:P0})"); + builder.AppendLine($"研究速度: {ResearchSpeedPerSecond:F1}/秒"); + } + else + { + builder.AppendLine("当前研究: 无"); + } + + if (spellHolder != null) + { + builder.AppendLine($"灵能术法组件: 已连接"); + builder.AppendLine($"灵能科研点: {spellHolder.PsychicResearchPoints:F0}"); + builder.AppendLine($"每秒消耗: {PsychicResearchPointsPerSecond:F1}"); + } + else + { + builder.AppendLine($"灵能术法组件: 未找到"); + } + + builder.AppendLine($"使用灵能科研点: {Props.usePsychicResearchPoints}"); + builder.AppendLine($"自动开始研究: {Props.autoStartResearch}"); + builder.AppendLine($"研究标签页: {Props.requiredResearchTab?.defName ?? "无"}"); + + return builder.ToString(); + } + } +} diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_PawnResearchBlueprintReader/Gizmo_PawnResearchProgress.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_PawnResearchBlueprintReader/Gizmo_PawnResearchProgress.cs new file mode 100644 index 0000000..570d70d --- /dev/null +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_PawnResearchBlueprintReader/Gizmo_PawnResearchProgress.cs @@ -0,0 +1,274 @@ +using RimWorld; +using System.Text; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + [StaticConstructorOnStartup] + public class Gizmo_PawnResearchProgress : Gizmo + { + private readonly Comp_PawnResearchBlueprintReader researchComp; + + // 尺寸常量 + private const float Width = 180f; + private const float GizmoHeight = 75f; + private const float Padding = 6f; + private const float BarHeight = 18f; + + // 材质颜色 + private static readonly Texture2D ProgressBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.3f, 0.6f, 0.9f, 0.9f)); + private static readonly Texture2D CompletedBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.8f, 0.3f, 0.9f)); + private static readonly Texture2D PausedBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.7f, 0.7f, 0.3f, 0.9f)); + private static readonly Texture2D NoPowerBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.5f, 0.5f, 0.5f, 0.9f)); + private static readonly Texture2D EmptyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.15f, 0.15f, 0.15f, 0.8f)); + + public Gizmo_PawnResearchProgress(Comp_PawnResearchBlueprintReader researchComp) + { + this.researchComp = researchComp; + Order = -130f; + } + + public override float GetWidth(float maxWidth) + { + return Mathf.Min(Width, maxWidth); + } + + public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms) + { + Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), GizmoHeight); + Widgets.DrawWindowBackground(rect); + + Rect innerRect = rect.ContractedBy(Padding); + float curY = innerRect.y; + + var currentResearch = researchComp.CurrentResearch; + + // === 第一行:标题(可点击选择研究) === + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.MiddleLeft; + Rect titleRect = new Rect(innerRect.x, curY, innerRect.width, 18f); + + string title; + if (currentResearch != null) + { + title = currentResearch.LabelCap.RawText ?? currentResearch.defName; + // 截断过长的标题 + title = title.Truncate(innerRect.width - 20f); + } + else + { + title = "ARA_PawnResearch_NoProject".Translate(); + } + + // 标题可点击 + if (Mouse.IsOver(titleRect)) + { + Widgets.DrawHighlight(titleRect); + } + + if (Widgets.ButtonInvisible(titleRect) && researchComp.CanResearch) + { + researchComp.ShowResearchMenu(); + } + + // 显示下拉箭头(如果可以选择研究) + if (researchComp.CanResearch) + { + GUI.color = new Color(0.7f, 0.9f, 1f); + Widgets.Label(titleRect, title + " ▼"); + GUI.color = Color.white; + } + else + { + Widgets.Label(titleRect, title); + } + curY += 20f; + + // === 第二行:状态信息 === + Text.Font = GameFont.Tiny; + Text.Anchor = TextAnchor.MiddleLeft; + Rect statusRect = new Rect(innerRect.x, curY, innerRect.width, 14f); + + if (currentResearch != null) + { + string statusText; + if (currentResearch.IsFinished) + { + GUI.color = new Color(0.3f, 0.9f, 0.3f); + statusText = "ARA_PawnResearch_Completed".Translate(); + } + else if (researchComp.IsResearching && researchComp.CanResearch) + { + GUI.color = new Color(0.5f, 0.8f, 1f); + statusText = "ARA_PawnResearch_Researching".Translate(); + } + else if (!researchComp.CanResearch) + { + GUI.color = new Color(0.8f, 0.2f, 0.2f); + statusText = "ARA_PawnResearch_CannotResearch".Translate(); + } + else + { + GUI.color = new Color(0.9f, 0.7f, 0.2f); + statusText = "ARA_PawnResearch_Paused".Translate(); + } + Widgets.Label(statusRect, statusText); + } + else + { + GUI.color = new Color(0.5f, 0.5f, 0.5f); + Widgets.Label(statusRect, "ARA_PawnResearch_SelectProject".Translate()); + } + curY += 15f; + + // === 第三行:进度条 === + GUI.color = Color.white; + Rect barRect = new Rect(innerRect.x, curY, innerRect.width, BarHeight); + + // 背景 + GUI.DrawTexture(barRect, EmptyBarTex); + + if (currentResearch != null) + { + float percentage = researchComp.ResearchProgressPercent; + + // 根据状态选择颜色 + Texture2D barTex; + if (currentResearch.IsFinished) + barTex = CompletedBarTex; + else if (researchComp.IsResearching && researchComp.CanResearch) + barTex = ProgressBarTex; + else if (!researchComp.CanResearch) + barTex = NoPowerBarTex; + else + barTex = PausedBarTex; + + // 填充条 + Rect fillRect = new Rect(barRect.x, barRect.y, barRect.width * percentage, barRect.height); + GUI.DrawTexture(fillRect, barTex); + + // 百分比文字 + Text.Font = GameFont.Tiny; + Text.Anchor = TextAnchor.MiddleCenter; + GUI.color = Color.white; + + string progressText; + if (currentResearch.IsFinished) + { + progressText = "ARA_PawnResearch_CompletedShort".Translate(); + } + else + { + progressText = $"{researchComp.CurrentProgress:F0} / {currentResearch.baseCost:F0}"; + } + + Widgets.Label(barRect, progressText); + } + else + { + // 无项目时显示空状态 + Text.Font = GameFont.Tiny; + Text.Anchor = TextAnchor.MiddleCenter; + GUI.color = new Color(0.5f, 0.5f, 0.5f); + Widgets.Label(barRect, "---"); + } + + // === 工具提示 === + if (Mouse.IsOver(rect)) + { + Widgets.DrawHighlight(rect); + TooltipHandler.TipRegion(rect, GetTooltip()); + } + + GUI.color = Color.white; + Text.Anchor = TextAnchor.UpperLeft; + Text.Font = GameFont.Small; + + return new GizmoResult(GizmoState.Clear); + } + + private string GetTooltip() + { + var sb = new System.Text.StringBuilder(); + var currentResearch = researchComp.CurrentResearch; + + sb.AppendLine("ARA_PawnResearch_TooltipTitle".Translate()); + sb.AppendLine(); + + if (currentResearch != null) + { + sb.AppendLine("ARA_PawnResearch_TooltipProject".Translate(currentResearch.LabelCap)); + sb.AppendLine("ARA_PawnResearch_TooltipProgress".Translate( + researchComp.CurrentProgress.ToString("F0"), + currentResearch.baseCost.ToString("F0"))); + + sb.AppendLine("ARA_PawnResearch_TooltipPercentage".Translate(researchComp.ResearchProgressPercent.ToStringPercent())); + sb.AppendLine(); + + if (currentResearch.IsFinished) + { + sb.AppendLine("" + "ARA_PawnResearch_TooltipCompleted".Translate() + ""); + } + else if (researchComp.IsResearching && researchComp.CanResearch) + { + // 显示研究速度 + sb.AppendLine("ARA_PawnResearch_TooltipSpeed".Translate(researchComp.ResearchSpeedPerSecond.ToString("F1"))); + + // 显示灵能科研点消耗 + if (researchComp.Props.usePsychicResearchPoints) + { + sb.AppendLine("ARA_PawnResearch_TooltipPointsConsumption".Translate(researchComp.PsychicResearchPointsPerSecond.ToString("F1"))); + } + } + else if (!researchComp.CanResearch) + { + sb.AppendLine("" + "ARA_PawnResearch_TooltipCannotResearch".Translate() + ""); + sb.AppendLine("ARA_PawnResearch_TooltipCannotResearchReason".Translate()); + + // 检查具体原因 + if (researchComp.Pawn != null) + { + if (researchComp.Pawn.Dead) + sb.AppendLine(" • " + "Dead".Translate()); + if (researchComp.Pawn.Downed) + sb.AppendLine(" • " + "Downed".Translate()); + if (!researchComp.Pawn.Spawned) + sb.AppendLine(" • " + "NotSpawned".Translate()); + } + + if (researchComp.Props.usePsychicResearchPoints && researchComp.spellHolder != null) + { + if (researchComp.spellHolder.PsychicResearchPoints < researchComp.PsychicResearchPointsPerSecond) + { + sb.AppendLine(" • " + "ARA_PawnResearch_InsufficientPointsShort".Translate()); + } + } + } + else + { + sb.AppendLine("" + "ARA_PawnResearch_TooltipPaused".Translate() + ""); + } + } + else + { + sb.AppendLine("ARA_PawnResearch_TooltipNoProject".Translate()); + } + + // 显示Pawn信息 + if (researchComp.Pawn != null) + { + sb.AppendLine(); + sb.AppendLine("ARA_PawnResearch_TooltipPawnInfo".Translate()); + sb.AppendLine($" • {"Intellectual".Translate()}: {researchComp.Pawn.skills.GetSkill(SkillDefOf.Intellectual).Level}"); + + if (researchComp.spellHolder != null) + { + sb.AppendLine($" • {"ARA_PawnResearch_PsychicPoints".Translate()}: {researchComp.spellHolder.PsychicResearchPoints:F0}"); + } + } + + return sb.ToString().TrimEndNewlines(); + } + } +}