diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index c16048f..fa9343a 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 a16c3aa..78fe037 100644 --- a/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml +++ b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml @@ -121,6 +121,36 @@ + + ARA_Suicide_Fast_Ability + + 只要一声令下,阿拉克涅虫族随时可以为了虫巢的大业立刻死去 + ArachnaeSwarm/UI/Abilities/ARA_Suicide_Ability + 1 + false + 9900 + true + false + AcidSpray_Warmup + + Verb_CastAbility + 24 + 2 + AcidSpray_Resolve + false + false + + true + + + +
  • + +
  • Brain
  • + + +
    +
    diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml index cfa1894..9e5cb95 100644 --- a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml +++ b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml @@ -42,8 +42,8 @@ - - ARA_ArachnaeQueen + + ArachnaeQueen_Race_Titan ArachnaeQueen_Race_Titan PlayerColony @@ -64,6 +64,26 @@
  • ARA_TumorSpew
  • + + ArachnaeQueen_Race_Neurotyrant + + ArachnaeQueen_Race_Neurotyrant + PlayerColony + 0 + +
  • + +
  • ArachnaeQueen_spawnCategoriesA
  • +
  • ArachnaeQueen_spawnCategoriesB
  • + + +
    + + + 0 + + +
    ArachnaeQueen_Race_TitanSettings @@ -72,7 +92,7 @@
  • -
  • ARA_ArachnaeQueen
  • +
  • ArachnaeQueen_Race_Titan
  • 100.0 @@ -87,7 +107,7 @@
  • -
  • ARA_ArachnaeQueen
  • +
  • ArachnaeQueen_Race_Titan
  • ArachnaeNode_Race_ShieldHead
  • ArachnaeNode_Race_WeaponSmith
  • ArachnaeNode_Race_Fighter
  • @@ -110,7 +130,7 @@
  • -
  • ARA_ArachnaeQueen
  • +
  • ArachnaeQueen_Race_Titan
  • ArachnaeNode_Race_ShieldHead
  • ArachnaeNode_Race_WeaponSmith
  • ArachnaeNode_Race_Fighter
  • @@ -128,7 +148,7 @@
  • -
  • ARA_ArachnaeQueen
  • +
  • ArachnaeQueen_Race_Titan
  • ArachnaeNode_Race_ShieldHead
  • ArachnaeNode_Race_WeaponSmith
  • ArachnaeNode_Race_Fighter
  • @@ -444,6 +464,9 @@ 3.0 + +
  • ARA_Suicide_Fast_Ability
  • +
    ArachnaeBase_Race_Larva diff --git a/1.6/1.6/Defs/Scenarios/ARA_Scenarios.xml b/1.6/1.6/Defs/Scenarios/ARA_Scenarios.xml index 222c634..f17e46f 100644 --- a/1.6/1.6/Defs/Scenarios/ARA_Scenarios.xml +++ b/1.6/1.6/Defs/Scenarios/ARA_Scenarios.xml @@ -22,7 +22,7 @@
  • True - ARA_ArachnaeQueen + ArachnaeQueen_Race_Titan
  • diff --git a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml index f3db801..5456f41 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml @@ -675,7 +675,7 @@ true
  • - Always + DraftedAndMove ARA_Psi_FlyNorth ARA_Psi_FlyNorth diff --git a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml index c11c6de..f1fd747 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml @@ -693,6 +693,11 @@ +
  • + true + ARA_RaceBaseSwarmProduceSwitchHediff + CocoonDestroyed +
  • Unique_Arachnae_Queen true @@ -786,11 +791,6 @@ -
  • - true - ARA_RaceBaseSwarmProduceSwitchHediff - CocoonDestroyed -
  • 3 180 @@ -805,601 +805,58 @@
  • - + ArachnaeQueen_Race_Neurotyrant 阿拉克涅灵吸种是女皇种亚种之一,归属于阿拉克涅的灵能触须。她们负责维持虫族蜂巢灵能网路的通讯,并作为中继节点链接各战区女皇种和虫巢舰队。\n\n但是灵吸种可不是脆弱的“文官”,她们强大的灵能使得她们可以通过超自然力量主宰当地战局,随着她们不断吞噬本地物种,其灵能能力还能得到不断的进化。 - - - - - 0 - - true - - 15 - - false - - 0 - - false - - false - - - - -
  • Female_AverageNormal
  • - -
    - - -
  • Thin
  • -
    - - - -
  • - skin - - 0 - 0 - -
  • - -
  • - hair - - -
  • - 15 - (0.4,0.3,0.5) - (0.6,0.1,0.7) -
  • -
  • - 6 - (0.9,0.9,0.9) - (0.9,0.9,0.9) -
  • -
  • - 6 - (1,0.8,0.8) - (1,0.9,0.9) -
  • -
  • - 3 - (1,1,1) - (1,1,1) -
  • - - - -
    - - - - - 4 - 4 - (4,4) - (1.0,1.0) - (2,2) -
    - -
  • 0
  • -
    - - - - true - - -
  • (0,1)
  • -
    -
    -
    - - true - - -
  • (0,1)
  • -
    -
    -
    - - true - - -
  • (0,1)
  • -
    -
    -
    - - true - - -
  • (0,1)
  • -
    -
    -
    - - true - - -
  • (0,1)
  • -
    -
    -
    - - true - - -
  • (0, 1)
  • -
    -
    -
    -
    - - - Male - - -
  • (0,0)
  • -
  • (1,0)
  • -
  • (999,0)
  • -
  • (9999,0)
  • -
    -
    -
    - - -
  • (0,0)
  • -
  • (1,0)
  • -
  • (999,0)
  • -
  • (9999,0)
  • -
    -
    -
    - - - - (1,1,1,1) - - Cutout - ArachnaeSwarm/Things/ARA_HiveQueen/Bodies/ - ArachnaeSwarm/Things/ARA_HiveQueen/Heads/ - Things/Pawn/Humanlike/Bodies/Dessicated/Dessicated_Thin - ArachnaeSwarm/Things/ARA_HiveQueen/Bodies/ - - - - -
  • - HairDef - - true - -
  • Bald
  • - - - - -
  • - TattooDef - - false - -
  • - -
  • - BeardDef - - false - -
  • -
    - - - - -
  • HairColor
  • -
  • Melanin
  • -
  • BodyType
  • -
  • Ears
  • -
  • Nose
  • -
  • Voice
  • -
  • Headbone
  • -
  • Head
  • -
  • Jaw
  • -
    - - -
  • EyeColor
  • -
  • Tail
  • -
  • BeardStyle
  • -
  • Fur
  • -
    - - -
  • Furskin
  • -
  • Brow_Heavy
  • -
    - - -
  • Dirtmole
  • -
  • Genie
  • -
  • Hussar
  • -
  • Sanguophage
  • -
  • Neanderthal
  • -
  • Pigskin
  • -
  • Impid
  • -
  • Waster
  • -
  • Yttakin
  • -
  • Highmate
  • -
    - - - - true - - -
  • ARA_InsectJelly
  • -
  • ARA_NutrientPasteMeal
  • -
  • ARA_PheromoneSolvent
  • -
    - true - - -
  • ArachnaeBase_Race_Scavenger
  • -
  • ArachnaeBase_Race_Larva
  • -
    - true - - - - - - - true - - true - false - - - - - - - - -
  • BodyPurist
  • -
  • Cannibal
  • -
  • CreepyBreathing
  • -
  • DislikesWomen
  • -
  • Gourmand
  • -
  • QuickSleeper
  • -
  • Wimp
  • -
  • Beauty
  • -
  • DrugDesire
  • -
  • Immunity
  • -
  • PsychicSensitivity
  • -
    -
    - - - - -
  • ColonistLost
  • -
  • KnowColonistDied
  • -
  • PawnWithGoodOpinionDied
  • -
  • PsychicDrone
  • -
  • Naked
  • -
  • AnyBodyPartButGroinCovered_Disapproved_Female
  • -
  • AnyBodyPartButGroinCovered_Disapproved_Male
  • -
  • AnyBodyPartButGroinCovered_Disapproved_Memory
  • -
  • AnyBodyPartButGroinCovered_Disapproved_Social_Female
  • -
  • AnyBodyPartButGroinCovered_Disapproved_Social_Male
  • -
  • AnyBodyPartCovered_Disapproved_Female
  • -
  • AnyBodyPartCovered_Disapproved_Male
  • -
  • AnyBodyPartCovered_Disapproved_Memory
  • -
  • AnyBodyPartCovered_Disapproved_Social_Female
  • -
  • AnyBodyPartCovered_Disapproved_Social_Male
  • -
  • GroinChestHairOrFaceUncovered_Disapproved_Female
  • -
  • GroinChestHairOrFaceUncovered_Disapproved_Male
  • -
  • GroinChestHairOrFaceUncovered_Disapproved_Social_Female
  • -
  • GroinChestHairOrFaceUncovered_Disapproved_Social_Male
  • -
  • GroinChestOrHairUncovered_Disapproved_Female
  • -
  • GroinChestOrHairUncovered_Disapproved_Male
  • -
  • GroinChestOrHairUncovered_Disapproved_Social_Female
  • -
  • GroinChestOrHairUncovered_Disapproved_Social_Male
  • -
  • GroinOrChestUncovered_Disapproved_Female
  • -
  • GroinOrChestUncovered_Disapproved_Male
  • -
  • GroinOrChestUncovered_Disapproved_Social_Female
  • -
  • GroinOrChestUncovered_Disapproved_Social_Male
  • -
  • GroinUncovered_Disapproved_Female
  • -
  • GroinUncovered_Disapproved_Male
  • -
  • GroinUncovered_Disapproved_Social_Female
  • -
  • GroinUncovered_Disapproved_Social_Male
  • -
  • WitnessedDeathAlly
  • -
  • ObservedLayingCorpse
  • -
  • ObservedLayingRottingCorpse
  • -
  • ApparelDamaged
  • -
  • ProsthophileHappy
  • -
  • ProsthophobeUnhappy
  • -
  • BrawlerUnhappy
  • -
  • PyromaniacHappy
  • -
  • Greedy
  • -
  • Jealous
  • -
  • SharedBed
  • -
  • AteWithoutTable
  • -
  • SleptOutside
  • -
  • SleptOnGround
  • -
  • SleptInCold
  • -
  • SleptInHeat
  • -
  • Ugly
  • -
  • AteKibble
  • -
  • AteInsectMeatDirect
  • -
  • AteInsectMeatAsIngredient
  • -
  • AteRawFood
  • -
  • AteHumanlikeMeatDirect
  • -
  • AteHumanlikeMeatAsIngredient
  • -
  • KnowButcheredHumanlikeCorpse
  • -
  • ButcheredHumanlikeCorpseOpinion
  • -
  • AteRawHumanlikeMeat
  • -
    - - - -
    - - - 0 - 0 - - 0 - 0 - 0 - - 0 - 0 - 0 - -
    - - -
  • ARA_CureBloodRot
  • -
  • ARA_CureAcid
  • -
  • RemovePorcupineQuill
  • -
  • SurgicalInspection
  • -
  • ARA_Surgery_Install_Plasteel
  • -
  • ARA_Surgery_Install_Carapace_Shell
  • -
  • ARA_Surgery_Install_Huge_Stomach
  • -
  • ARA_Surgery_Install_Cycle_Suppression
  • -
  • ARA_Surgery_Install_Shell_Thorn
  • -
  • ARA_Surgery_Install_Reactive_Shell
  • -
  • ARA_Surgery_Install_Strengthening_Tendon
  • -
  • ARA_Surgery_Install_Slide_Patagium
  • -
  • ARA_Surgery_Install_Baneling_Pouch
  • -
  • ARA_Surgery_Install_Tumor_Pouch
  • -
  • ARA_Surgery_Install_Internal_Circulation_Lung
  • -
    - - - 2000 - 5 - - 1.75 + 2.5 250 - - 2 - - 0.5 + 1 - 200 - 450 - 600 + 350 + 300 - - 1 - + 1.25 2.5 - 0 - 0.95 0.95 - - 0.1 + 0.2 - - 0.25 - + 0.4 - - - - + 0.35 + 0.35 + 0.25 - - 0.6 - 0.8 - 0.5 + 2 - - 5 - - - 0 - - 15 + 18 1 - ArachnaeQueen_Body - Normal - - ARA_Humanlike - - Humanlike - - HumanStandard - Filth_BloodInsect - Filth_BloodSmear - - 10 - - 10 - - ARA_Carapace - - Pawn_Melee_BigBash_HitPawn - Pawn_Melee_BigBash_HitBuilding - Pawn_Melee_BigBash_Miss - Pawn_MeleeDodge - - 300 - - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - - -
  • - HumanlikeBaby - 0 -
  • -
  • - HumanlikeChild - 0.25 -
  • -
  • - HumanlikePreTeenager - 0.5 -
  • -
  • - HumanlikeTeenager - 0.75 -
  • -
  • - HumanlikeAdult - 1 - Pawn_HiveQueen_Wounded - Pawn_HiveQueen_Death - Pawn_HiveQueen_Call - Pawn_HiveQueen_Angry -
  • -
    - false + 5 + 4 + true
    - - -
  • - - -
  • Poke
  • - - 16 - 2 - HeadAttackTool - true - 0.01 - -
  • - - -
  • Blunt
  • -
  • Poke
  • - - 35 - 2.5 - Legs - -
  • - - -
  • Stab
  • - - 50 - 3 - Legs - -
  • - - -
  • Cut
  • - - 30 - 2 - Hands - -
    - -
  • - Unique_Arachnae_Queen - true - ARA_QueenAlreadyExists - +
  • + DraftedAndMove + + ARA_Psi_FlyNorth + ARA_Psi_FlyEast + ARA_Psi_FlySouth
  • -
  • - -
  • ARA_HiveStrength
  • -
  • ARA_HiveMindMaster
  • - - 1.0 - false - -
  • - true - ARA_RaceBaseSwarmProduceSwitchHediff - CocoonDestroyed -
  • -
  • - 3 - 180 - Crush - 10 - false - ARA_Area_Crush - false - 践踏 - 这只阿拉克涅虫族的身躯是如此巨大,以至于靠近它的敌人会被直接一脚踩死 - ArachnaeSwarm/UI/Abilities/ARA_Area_Crush -
  • - -
  • \ No newline at end of file diff --git a/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml b/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml index 9dca102..929656a 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml @@ -76,7 +76,7 @@
  • -
  • ARA_ArachnaeQueen
  • +
  • ArachnaeQueen_Race_Titan
  • true @@ -124,12 +124,12 @@
  • - ARA_ArachnaeQueen + ArachnaeQueen_Race_Titan 180000
  • true diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo index 71d5ba1..8783c4a 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 990db67..a10e023 100644 --- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json +++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json @@ -1,7 +1,28 @@ { "Version": 1, "WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\", - "Documents": [], + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\pawn_comps\\ara_uniquepawn\\compuniquepawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_uniquepawn\\compuniquepawn.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_uniquepawn\\uniquepawnmanager.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_uniquepawn\\uniquepawnmanager.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_uniquepawn\\compproperties_uniquepawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_uniquepawn\\compproperties_uniquepawn.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_flight\\pawn_flighttrackerpatches.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_flight\\pawn_flighttrackerpatches.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_flight\\compproperties_pawnflight.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_flight\\compproperties_pawnflight.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + } + ], "DocumentGroupContainers": [ { "Orientation": 0, @@ -9,8 +30,73 @@ "DocumentGroups": [ { "DockedWidth": 200, - "SelectedChildIndex": -1, + "SelectedChildIndex": 1, "Children": [ + { + "$type": "Document", + "DocumentIndex": 1, + "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", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_UniquePawn\\UniquePawnManager.cs", + "RelativeToolTip": "Pawn_Comps\\ARA_UniquePawn\\UniquePawnManager.cs", + "ViewState": "AgIAAIIBAAAAAAAAAAAtwJUBAAANAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-01-28T09:15:15.046Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 0, + "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", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_UniquePawn\\CompUniquePawn.cs", + "RelativeToolTip": "Pawn_Comps\\ARA_UniquePawn\\CompUniquePawn.cs", + "ViewState": "AgIAAFwAAAAAAAAAAAApwHcAAAANAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-01-28T09:15:14.392Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 2, + "Title": "CompProperties_UniquePawn.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_UniquePawn\\CompProperties_UniquePawn.cs", + "RelativeDocumentMoniker": "Pawn_Comps\\ARA_UniquePawn\\CompProperties_UniquePawn.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_UniquePawn\\CompProperties_UniquePawn.cs", + "RelativeToolTip": "Pawn_Comps\\ARA_UniquePawn\\CompProperties_UniquePawn.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAABcAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-01-28T09:15:09.995Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 3, + "Title": "Pawn_FlightTrackerPatches.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_Flight\\Pawn_FlightTrackerPatches.cs", + "RelativeDocumentMoniker": "Pawn_Comps\\ARA_Flight\\Pawn_FlightTrackerPatches.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_Flight\\Pawn_FlightTrackerPatches.cs", + "RelativeToolTip": "Pawn_Comps\\ARA_Flight\\Pawn_FlightTrackerPatches.cs", + "ViewState": "AgIAAC8AAAAAAAAAAAAawEEAAAAQAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-01-28T08:32:19.011Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 4, + "Title": "CompProperties_PawnFlight.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_Flight\\CompProperties_PawnFlight.cs", + "RelativeDocumentMoniker": "Pawn_Comps\\ARA_Flight\\CompProperties_PawnFlight.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_Flight\\CompProperties_PawnFlight.cs", + "RelativeToolTip": "Pawn_Comps\\ARA_Flight\\CompProperties_PawnFlight.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAuwAgAAAAWAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-01-28T08:30:19.937Z", + "EditorCaption": "" + }, { "$type": "Bookmark", "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index ff583ad..05c750b 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -361,14 +361,14 @@ + + + - - - @@ -439,6 +439,7 @@ + diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_Flight/CompProperties_PawnFlight.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_Flight/CompProperties_PawnFlight.cs index f5a62e5..5d6e2a4 100644 --- a/Source/ArachnaeSwarm/Pawn_Comps/ARA_Flight/CompProperties_PawnFlight.cs +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_Flight/CompProperties_PawnFlight.cs @@ -6,6 +6,7 @@ namespace ArachnaeSwarm public enum FlightCondition { Drafted, + DraftedAndMove, Always } diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_Flight/Pawn_FlightTrackerPatches.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_Flight/Pawn_FlightTrackerPatches.cs index f3c7cd8..eb10b51 100644 --- a/Source/ArachnaeSwarm/Pawn_Comps/ARA_Flight/Pawn_FlightTrackerPatches.cs +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_Flight/Pawn_FlightTrackerPatches.cs @@ -68,6 +68,10 @@ namespace ArachnaeSwarm { shouldBeFlying = true; } + else if (compProps.flightCondition == FlightCondition.DraftedAndMove && ___pawn.Drafted || ___pawn.pather.MovingNow) + { + shouldBeFlying = true; + } else if (compProps.flightCondition == FlightCondition.Drafted && ___pawn.Drafted) { shouldBeFlying = true; diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/CompProperties_UniquePawn.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/CompProperties_UniquePawn.cs index 1d3afeb..39e4321 100644 --- a/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/CompProperties_UniquePawn.cs +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/CompProperties_UniquePawn.cs @@ -4,7 +4,7 @@ namespace ArachnaeSwarm { public class CompProperties_UniquePawn : CompProperties { - // 全局变量的唯一标识符 + // 唯一标识符 public string globalVariable; // 可选:杀死pawn时是否显示消息 @@ -15,6 +15,12 @@ namespace ArachnaeSwarm // 可选:杀死pawn的方式 public DamageDef killDamageDef = null; + + // 验证间隔(ticks) + public int verificationInterval = 300; + + // 是否启用调试日志 + public bool enableDebugLogging = false; public CompProperties_UniquePawn() { diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/CompUniquePawn.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/CompUniquePawn.cs index 1f273bf..3f07b0b 100644 --- a/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/CompUniquePawn.cs +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/CompUniquePawn.cs @@ -1,4 +1,4 @@ -using System; +using System; using RimWorld; using Verse; @@ -6,30 +6,61 @@ namespace ArachnaeSwarm { public class CompUniquePawn : ThingComp { + #region 字段 private bool _checked = false; private bool _scheduledForCheck = false; + private int _lastVerificationTick = -1; + private const int VERIFICATION_SIGNAL_INTERVAL = 300; // 每5秒发送一次验证信号 + #endregion + #region 属性 public CompProperties_UniquePawn Props => (CompProperties_UniquePawn)this.props; + + public string GlobalVariable + { + get + { + if (Props == null || string.IsNullOrEmpty(Props.globalVariable)) + { + Log.Warning("CompUniquePawn: Props.globalVariable is null or empty"); + return null; + } + return Props.globalVariable; + } + } + + public int SpawnTick { get; private set; } = -1; + #endregion + #region 生命周期方法 public override void PostSpawnSetup(bool respawningAfterLoad) { base.PostSpawnSetup(respawningAfterLoad); - // 基本安全检查 if (this.parent == null || !(this.parent is Pawn pawn) || !pawn.Spawned) return; - // 只在首次生成时检查,不是加载存档时 - if (respawningAfterLoad || _checked || _scheduledForCheck) + // 记录生成时间 + if (SpawnTick < 0) + SpawnTick = Find.TickManager.TicksGame; + + // 如果是重新生成,检查是否有效 + if (respawningAfterLoad) + { + HandleRespawnAfterLoad(pawn); + return; + } + + // 首次生成时检查 + if (_checked || _scheduledForCheck) return; - // 确保属性有效 - if (this.props == null || string.IsNullOrEmpty(Props?.globalVariable)) + if (string.IsNullOrEmpty(GlobalVariable)) return; try { - // 延迟整个检查过程到下一帧 + // 延迟检查 _scheduledForCheck = true; LongEventHandler.QueueLongEvent(() => @@ -45,50 +76,135 @@ namespace ArachnaeSwarm } catch (Exception ex) { - ArachnaeLog.Debug($"Error in delayed unique pawn check: {ex}"); + Log.Error($"Error in delayed unique pawn check: {ex}"); _scheduledForCheck = false; } }, "ArachnaeSwarm_UniquePawnCheck", false, null); } catch (Exception ex) { - ArachnaeLog.Debug($"Error in CompUniquePawn.PostSpawnSetup: {ex}"); + Log.Error($"Error in CompUniquePawn.PostSpawnSetup: {ex}"); _scheduledForCheck = false; } } + public override void CompTick() + { + base.CompTick(); + + // 定期发送验证信号 + if (Find.TickManager.TicksGame - _lastVerificationTick > VERIFICATION_SIGNAL_INTERVAL) + { + SendVerificationSignal(); + _lastVerificationTick = Find.TickManager.TicksGame; + } + } + + public override void PostDestroy(DestroyMode mode, Map previousMap) + { + base.PostDestroy(mode, previousMap); + + // Pawn被销毁时注销 + if (this.parent is Pawn pawn && !string.IsNullOrEmpty(GlobalVariable)) + { + UniquePawnManager.Instance?.UnregisterPawn(pawn, GlobalVariable); + } + } + + public void PostDeSpawn(Map map) + { + // Pawn被反生成时也注销 + if (this.parent is Pawn pawn && !string.IsNullOrEmpty(GlobalVariable)) + { + UniquePawnManager.Instance?.UnregisterPawn(pawn, GlobalVariable); + } + } + #endregion + + #region 公共方法 + /// + /// 发送验证信号给管理器 + /// + public void SendVerificationSignal() + { + if (this.parent is Pawn pawn && !string.IsNullOrEmpty(GlobalVariable) && pawn.Spawned && !pawn.Destroyed) + { + UniquePawnManager.Instance?.SendVerificationSignal(pawn, GlobalVariable); + } + } + + /// + /// 检查是否是有效的唯一Pawn + /// + public bool IsValidUniquePawn() + { + if (this.parent is Pawn pawn && !string.IsNullOrEmpty(GlobalVariable)) + { + return UniquePawnManager.Instance?.IsEarliestPawn(pawn, GlobalVariable) ?? false; + } + return false; + } + #endregion + + #region 私有方法 private void CheckAndHandleUniquePawn(Pawn pawn) { try { - string variable = Props.globalVariable; + string variable = GlobalVariable; if (string.IsNullOrEmpty(variable)) { - ArachnaeLog.Debug("CompUniquePawn: globalVariable is null or empty"); + Log.Error("CompUniquePawn: globalVariable is null or empty"); return; } - // 检查变量是否已存在 - if (GlobalVariableManager.HasVariable(variable)) + // 向管理器注册 + bool registered = UniquePawnManager.Instance.RegisterPawn(pawn, variable); + + if (!registered) { - // 变量已存在,杀死pawn + // 注册失败,说明有更早的Pawn存在 KillPawn(pawn, variable); } else { - // 变量不存在,添加变量 - GlobalVariableManager.SetVariable(variable); - - if (Prefs.DevMode) - { - ArachnaeLog.Debug($"Added global variable '{variable}' for pawn {pawn.Label}"); - } + Log.Message($"[唯一Pawn系统] 成功注册Pawn: {pawn.Label} (变量: {variable})"); } } catch (Exception ex) { - ArachnaeLog.Debug($"Error in CheckAndHandleUniquePawn: {ex}"); + Log.Error($"Error in CheckAndHandleUniquePawn: {ex}"); + } + } + + private void HandleRespawnAfterLoad(Pawn pawn) + { + try + { + string variable = GlobalVariable; + + if (string.IsNullOrEmpty(variable)) + return; + + // 检查是否仍然是最早的Pawn + bool isEarliest = UniquePawnManager.Instance?.IsEarliestPawn(pawn, variable) ?? false; + + if (!isEarliest) + { + // 有更早的Pawn存在,杀死当前Pawn + KillPawn(pawn, variable); + } + else + { + // 重新发送验证信号 + SendVerificationSignal(); + _checked = true; + } + } + catch (Exception ex) + { + Log.Error($"Error in HandleRespawnAfterLoad: {ex}"); } } @@ -102,16 +218,13 @@ namespace ArachnaeSwarm string deathMessage = Props.deathMessageKey.Translate(pawn.Label, variable); if (!string.IsNullOrEmpty(deathMessage)) { - Messages.Message(deathMessage, MessageTypeDefOf.NegativeEvent); + Messages.Message(deathMessage, pawn, MessageTypeDefOf.NegativeEvent); } } - if (Prefs.DevMode) - { - ArachnaeLog.Debug($"Killing pawn {pawn.Label} because global variable '{variable}' already exists"); - } + Log.Message($"[唯一Pawn系统] 移除重复Pawn: {pawn.Label} (变量: {variable})"); - // 使用更安全的延迟执行 + // 延迟执行杀死操作 LongEventHandler.QueueLongEvent(() => { try @@ -131,21 +244,24 @@ namespace ArachnaeSwarm } catch (Exception ex) { - ArachnaeLog.Debug($"Error in delayed pawn kill: {ex}"); + Log.Error($"Error in delayed pawn kill: {ex}"); } }, "ArachnaeSwarm_KillDuplicatePawn", false, null); } catch (Exception ex) { - ArachnaeLog.Debug($"Error in KillPawn: {ex}"); + Log.Error($"Error in KillPawn: {ex}"); } } + #endregion - public override void PostExposeData() + #region 序列化 + public void ExposeData() { - base.PostExposeData(); Scribe_Values.Look(ref _checked, "checked", false); Scribe_Values.Look(ref _scheduledForCheck, "scheduledForCheck", false); + Scribe_Values.Look(ref _lastVerificationTick, "lastVerificationTick", -1); } + #endregion } } diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/Patch_UniquePawn.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/Patch_UniquePawn.cs deleted file mode 100644 index bbb70d0..0000000 --- a/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/Patch_UniquePawn.cs +++ /dev/null @@ -1,299 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using HarmonyLib; -using RimWorld; -using Verse; - -namespace ArachnaeSwarm -{ - [StaticConstructorOnStartup] - public static class ModInit - { - static ModInit() - { - var harmony = new Harmony("ArachnaeSwarm.Mod"); - harmony.PatchAll(); - - // 初始化全局变量管理器 - GlobalVariableManager.Initialize(); - } - } - - [HarmonyPatch(typeof(Game), "ExposeData")] - public static class Game_ExposeData_Patch - { - public static void Postfix(Game __instance) - { - GlobalVariableManager.ExposeData(); - } - } - - // 新增:在游戏开始或结束时清理变量 - [HarmonyPatch(typeof(Game), "InitNewGame")] - public static class Game_InitNewGame_Patch - { - public static void Postfix() - { - // 新游戏开始时清理所有变量 - GlobalVariableManager.ClearAllVariables(); - if (Prefs.DevMode) - { - ArachnaeLog.Debug("GlobalVariableManager: Cleared all variables for new game"); - } - } - } - - // 新增:在游戏加载时确保变量正确 - [HarmonyPatch(typeof(Game), "LoadGame")] - public static class Game_LoadGame_Patch - { - public static void Postfix() - { - // 确保变量管理器已初始化 - GlobalVariableManager.Initialize(); - if (Prefs.DevMode) - { - ArachnaeLog.Debug("GlobalVariableManager: Initialized for loaded game"); - } - } - } - - // 新增:在返回主菜单时清理变量 - [HarmonyPatch(typeof(Game), "FinalizeInit")] - public static class Game_FinalizeInit_Patch - { - public static void Postfix() - { - // 确保变量管理器已初始化 - GlobalVariableManager.Initialize(); - } - } - - // 复活拦截补丁 - [HarmonyPatch] - public static class ResurrectionUtility_Patch - { - private static MethodInfo TargetMethod() - { - // 尝试找到复活方法 - return AccessTools.Method("ResurrectionUtility:TryResurrect") ?? - AccessTools.Method("RimWorld.ResurrectionUtility:TryResurrect"); - } - - [HarmonyPrefix] - public static bool Prefix(Pawn pawn) - { - try - { - // 检查 pawn 是否有 CompUniquePawn 组件 - var comp = pawn?.GetComp(); - if (comp != null && !string.IsNullOrEmpty(comp.Props?.globalVariable)) - { - string variable = comp.Props.globalVariable; - - // 如果全局变量已存在,阻止复活 - if (GlobalVariableManager.HasVariable(variable)) - { - // 显示阻止复活的消息 - if (comp.Props.showDeathMessage && !string.IsNullOrEmpty(comp.Props.deathMessageKey)) - { - string preventMessage = "ARA_ResurrectionPrevented".Translate(pawn.Label, variable); - if (string.IsNullOrEmpty(preventMessage) || preventMessage == "ARA_ResurrectionPrevented") - { - preventMessage = "无法复活 {0},因为 {1} 已经存在。".Translate(pawn.Label, variable); - } - Messages.Message(preventMessage, MessageTypeDefOf.NegativeEvent); - } - - if (Prefs.DevMode) - { - ArachnaeLog.Debug($"阻止复活 {pawn.Label},因为全局变量 '{variable}' 已存在"); - } - - // __result = false; // void methods cannot return a value - return false; // 跳过原始方法 - } - } - } - catch (System.Exception ex) - { - ArachnaeLog.Debug($"Error in resurrection prevention: {ex}"); - } - - return true; // 继续执行原始方法 - } - } - - // 带副作用的复活拦截补丁 - [HarmonyPatch] - public static class ResurrectionUtility_Patch2 - { - private static MethodInfo TargetMethod() - { - // 尝试找到带副作用的复活方法 - return AccessTools.Method("ResurrectionUtility:TryResurrectWithSideEffects") ?? - AccessTools.Method("RimWorld.ResurrectionUtility:TryResurrectWithSideEffects"); - } - - [HarmonyPrefix] - public static bool Prefix(Pawn pawn) - { - try - { - // 检查 pawn 是否有 CompUniquePawn 组件 - if (pawn == null) return true; - var comp = pawn.GetComp(); - if (comp != null && !string.IsNullOrEmpty(comp.Props?.globalVariable)) - { - string variable = comp.Props.globalVariable; - - // 如果全局变量已存在,阻止复活 - if (GlobalVariableManager.HasVariable(variable)) - { - // 显示阻止复活的消息 - if (comp.Props.showDeathMessage && !string.IsNullOrEmpty(comp.Props.deathMessageKey)) - { - string preventMessage = "ARA_ResurrectionPrevented".Translate(pawn.Label, variable); - if (string.IsNullOrEmpty(preventMessage) || preventMessage == "ARA_ResurrectionPrevented") - { - preventMessage = "无法复活 {0},因为 {1} 已经存在。".Translate(pawn.Label, variable); - } - Messages.Message(preventMessage, MessageTypeDefOf.NegativeEvent); - } - - if (Prefs.DevMode) - { - ArachnaeLog.Debug($"阻止复活 {pawn.Label},因为全局变量 '{variable}' 已存在"); - } - - // __result = false; // void methods cannot return a value - return false; // 跳过原始方法 - } - } - } - catch (System.Exception ex) - { - ArachnaeLog.Debug($"Error in resurrection prevention: {ex}"); - } - - return true; // 继续执行原始方法 - } - } - - public static class GlobalVariableManager - { - private static HashSet _globalVariables; - private static bool _initialized = false; - - public static void Initialize() - { - if (!_initialized) - { - _globalVariables = new HashSet(); - _initialized = true; - - if (Prefs.DevMode) - { - ArachnaeLog.Debug("GlobalVariableManager: Initialized"); - } - } - } - - public static void ExposeData() - { - try - { - // 确保在保存或加载前已初始化 - Initialize(); - - if (Scribe.mode == LoadSaveMode.Saving) - { - if (_globalVariables.Count > 0) - { - List variablesList = new List(_globalVariables); - Scribe_Collections.Look(ref variablesList, "ArachnaeSwarm_GlobalVariables", LookMode.Value); - - if (Prefs.DevMode) - { - ArachnaeLog.Debug($"GlobalVariableManager: Saved {_globalVariables.Count} variables"); - } - } - } - else if (Scribe.mode == LoadSaveMode.LoadingVars) - { - List variablesList = null; - Scribe_Collections.Look(ref variablesList, "ArachnaeSwarm_GlobalVariables", LookMode.Value); - - if (variablesList != null) - { - _globalVariables = new HashSet(variablesList); - if (Prefs.DevMode) - { - ArachnaeLog.Debug($"GlobalVariableManager: Loaded {_globalVariables.Count} variables"); - } - } - else - { - _globalVariables = new HashSet(); - if (Prefs.DevMode) - { - ArachnaeLog.Debug("GlobalVariableManager: No variables found in save, initializing empty set"); - } - } - } - } - catch (System.Exception ex) - { - ArachnaeLog.Debug($"Error in GlobalVariableManager.ExposeData: {ex}"); - } - } - - public static bool HasVariable(string variable) - { - Initialize(); - return _globalVariables.Contains(variable); - } - - public static void SetVariable(string variable) - { - Initialize(); - _globalVariables.Add(variable); - - if (Prefs.DevMode) - { - ArachnaeLog.Debug($"GlobalVariableManager: Added variable '{variable}'"); - } - } - - public static bool RemoveVariable(string variable) - { - Initialize(); - bool removed = _globalVariables.Remove(variable); - - if (removed && Prefs.DevMode) - { - ArachnaeLog.Debug($"GlobalVariableManager: Removed variable '{variable}'"); - } - - return removed; - } - - public static IEnumerable GetAllVariables() - { - Initialize(); - return _globalVariables; - } - - public static void ClearAllVariables() - { - Initialize(); - int count = _globalVariables.Count; - _globalVariables.Clear(); - - if (Prefs.DevMode) - { - ArachnaeLog.Debug($"GlobalVariableManager: Cleared {count} variables"); - } - } - } -} diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/UniquePawnManager.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/UniquePawnManager.cs new file mode 100644 index 0000000..e94309c --- /dev/null +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_UniquePawn/UniquePawnManager.cs @@ -0,0 +1,459 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + /// + /// 唯一Pawn管理器 + /// 管理所有具有唯一标识的Pawn,确保同一标识只有一个存活 + /// + public class UniquePawnManager : GameComponent + { + #region 单例和静态访问 + private static UniquePawnManager _instance; + public static UniquePawnManager Instance + { + get + { + if (_instance == null && Current.Game != null) + { + _instance = Current.Game.GetComponent(); + if (_instance == null) + { + _instance = new UniquePawnManager(Current.Game); + Current.Game.components.Add(_instance); + } + } + return _instance; + } + } + #endregion + + #region 数据定义 + /// + /// Pawn注册信息 + /// + public class UniquePawnInfo : IExposable + { + public string globalVariable; + public int thingID; + public int spawnTick; + public Map map; + public string pawnName; + public bool isValid = true; + public int lastVerificationTick = -1; + + public void ExposeData() + { + Scribe_Values.Look(ref globalVariable, "globalVariable"); + Scribe_Values.Look(ref thingID, "thingID"); + Scribe_Values.Look(ref spawnTick, "spawnTick"); + Scribe_References.Look(ref map, "map"); + Scribe_Values.Look(ref pawnName, "pawnName"); + Scribe_Values.Look(ref isValid, "isValid", true); + Scribe_Values.Look(ref lastVerificationTick, "lastVerificationTick", -1); + } + } + #endregion + + #region 字段 + private List registeredPawns = new List(); + private int lastVerificationTick = -1; + private const int VERIFICATION_INTERVAL = 600; // 每10秒验证一次 + private const int VERIFICATION_TIMEOUT = 1200; // 20秒无响应视为失效 + private object syncLock = new object(); + #endregion + + #region 构造函数 + public UniquePawnManager(Game game) : base() + { + Initialize(); + } + + private void Initialize() + { + lock (syncLock) + { + if (registeredPawns == null) + registeredPawns = new List(); + } + } + #endregion + + #region 公共接口 + /// + /// 注册唯一Pawn + /// + public bool RegisterPawn(Pawn pawn, string globalVariable) + { + if (pawn == null || string.IsNullOrEmpty(globalVariable)) + { + Log.Warning("尝试注册空Pawn或空全局变量"); + return false; + } + + lock (syncLock) + { + // 清理无效注册 + CleanupInvalidRegistrations(); + + // 获取当前tick + int currentTick = Find.TickManager.TicksGame; + + // 检查是否已存在相同全局变量的Pawn + var existingInfos = GetInfosForVariable(globalVariable); + var validExistingInfos = existingInfos.Where(info => info.isValid).ToList(); + + // 如果有有效的已注册Pawn + if (validExistingInfos.Count > 0) + { + // 按生成时间排序,保留最早的 + var earliestInfo = validExistingInfos.OrderBy(info => info.spawnTick).First(); + + // 如果当前Pawn不是最早的,则杀死 + if (earliestInfo.thingID != pawn.thingIDNumber) + { + Log.Message($"[唯一Pawn系统] 检测到重复Pawn: {pawn.Label} (变量: {globalVariable})"); + Log.Message($"[唯一Pawn系统] 保留最早Pawn: {earliestInfo.pawnName} (生成于tick: {earliestInfo.spawnTick})"); + + // 杀死当前Pawn + KillDuplicatePawn(pawn, globalVariable, earliestInfo.pawnName); + return false; + } + else + { + // 当前Pawn就是最早的,更新验证时间 + var info = registeredPawns.FirstOrDefault(i => i.globalVariable == globalVariable && i.thingID == pawn.thingIDNumber); + if (info != null) + { + info.lastVerificationTick = currentTick; + info.isValid = true; + } + return true; + } + } + else + { + // 创建新注册信息 + var newInfo = new UniquePawnInfo + { + globalVariable = globalVariable, + thingID = pawn.thingIDNumber, + spawnTick = currentTick, + map = pawn.Map, + pawnName = pawn.Label, + isValid = true, + lastVerificationTick = currentTick + }; + + registeredPawns.Add(newInfo); + Log.Message($"[唯一Pawn系统] 注册新Pawn: {pawn.Label} (变量: {globalVariable}, ID: {pawn.thingIDNumber})"); + return true; + } + } + } + + /// + /// 注销Pawn + /// + public void UnregisterPawn(Pawn pawn, string globalVariable) + { + if (pawn == null || string.IsNullOrEmpty(globalVariable)) + return; + + lock (syncLock) + { + var info = registeredPawns.FirstOrDefault(i => + i.globalVariable == globalVariable && + i.thingID == pawn.thingIDNumber); + + if (info != null) + { + info.isValid = false; + Log.Message($"[唯一Pawn系统] 注销Pawn: {pawn.Label} (变量: {globalVariable})"); + } + } + } + + /// + /// 发送验证信号 + /// + public void SendVerificationSignal(Pawn pawn, string globalVariable) + { + if (pawn == null || string.IsNullOrEmpty(globalVariable) || pawn.Destroyed || !pawn.Spawned) + return; + + lock (syncLock) + { + var info = registeredPawns.FirstOrDefault(i => + i.globalVariable == globalVariable && + i.thingID == pawn.thingIDNumber); + + if (info != null) + { + info.lastVerificationTick = Find.TickManager.TicksGame; + info.isValid = true; + info.map = pawn.Map; + } + else + { + // Pawn未注册,尝试重新注册 + RegisterPawn(pawn, globalVariable); + } + } + } + + /// + /// 检查Pawn是否是最早的 + /// + public bool IsEarliestPawn(Pawn pawn, string globalVariable) + { + if (pawn == null || string.IsNullOrEmpty(globalVariable)) + return false; + + lock (syncLock) + { + var existingInfos = GetInfosForVariable(globalVariable); + var validExistingInfos = existingInfos.Where(info => info.isValid).ToList(); + + if (validExistingInfos.Count == 0) + return true; + + var earliestInfo = validExistingInfos.OrderBy(info => info.spawnTick).First(); + return earliestInfo.thingID == pawn.thingIDNumber; + } + } + + /// + /// 获取指定变量的所有Pawn信息 + /// + public List GetInfosForVariable(string globalVariable) + { + lock (syncLock) + { + return registeredPawns.Where(info => info.globalVariable == globalVariable).ToList(); + } + } + + /// + /// 获取调试信息 + /// + public string GetDebugInfo() + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + sb.AppendLine("=== 唯一Pawn管理器调试信息 ==="); + sb.AppendLine($"已注册Pawn数量: {registeredPawns.Count}"); + + lock (syncLock) + { + var grouped = registeredPawns + .Where(info => info.isValid) + .GroupBy(info => info.globalVariable) + .OrderBy(g => g.Key); + + foreach (var group in grouped) + { + sb.AppendLine($"变量: {group.Key}"); + var ordered = group.OrderBy(info => info.spawnTick).ToList(); + + for (int i = 0; i < ordered.Count; i++) + { + var info = ordered[i]; + string status = i == 0 ? "[最早]" : "[重复]"; + int ageTicks = Find.TickManager.TicksGame - info.spawnTick; + int lastVerifyAgo = Find.TickManager.TicksGame - info.lastVerificationTick; + + sb.AppendLine($" {status} {info.pawnName} (ID: {info.thingID})"); + sb.AppendLine($" 生成时间: {info.spawnTick} ({ageTicks} ticks前)"); + sb.AppendLine($" 最后验证: {info.lastVerificationTick} ({lastVerifyAgo} ticks前)"); + sb.AppendLine($" 地图: {info.map?.Index ?? -1}"); + } + } + } + + return sb.ToString(); + } + #endregion + + #region 内部方法 + /// + /// 清理无效注册 + /// + private void CleanupInvalidRegistrations() + { + int currentTick = Find.TickManager.TicksGame; + int removedCount = 0; + + for (int i = registeredPawns.Count - 1; i >= 0; i--) + { + var info = registeredPawns[i]; + + // 移除无效的 + if (!info.isValid) + { + registeredPawns.RemoveAt(i); + removedCount++; + continue; + } + + // 检查超时 + if (info.lastVerificationTick > 0 && + currentTick - info.lastVerificationTick > VERIFICATION_TIMEOUT) + { + // 尝试查找Pawn + Pawn pawn = FindPawnByID(info.thingID, info.map); + if (pawn == null || pawn.Destroyed || !pawn.Spawned) + { + info.isValid = false; + registeredPawns.RemoveAt(i); + removedCount++; + Log.Message($"[唯一Pawn系统] 清理超时Pawn: {info.pawnName} (变量: {info.globalVariable})"); + } + else + { + // Pawn仍然存在,更新验证时间 + info.lastVerificationTick = currentTick; + } + } + } + } + + /// + /// 通过ID查找Pawn + /// + private Pawn FindPawnByID(int thingID, Map map) + { + if (map == null || thingID <= 0) + return null; + + try + { + return map.listerThings.ThingsInGroup(ThingRequestGroup.Pawn) + .OfType() + .FirstOrDefault(p => p.thingIDNumber == thingID); + } + catch (Exception ex) + { + Log.Error($"查找Pawn失败: {ex.Message}"); + return null; + } + } + + /// + /// 杀死重复的Pawn + /// + private void KillDuplicatePawn(Pawn pawn, string globalVariable, string earliestPawnName) + { + try + { + if (pawn == null || pawn.Destroyed || !pawn.Spawned) + return; + + // 显示死亡消息 + string deathMessage = $"{pawn.Label} 被移除,因为 {earliestPawnName} 是更早生成的唯一Pawn ({globalVariable})"; + Messages.Message(deathMessage, pawn, MessageTypeDefOf.NegativeEvent); + + // 使用安全的杀死方法 + pawn.Kill(null); + + Log.Message($"[唯一Pawn系统] 移除重复Pawn: {pawn.Label} (变量: {globalVariable})"); + } + catch (Exception ex) + { + Log.Error($"杀死重复Pawn失败: {ex.Message}"); + } + } + + /// + /// 定期验证所有Pawn + /// + private void VerifyAllPawns() + { + lock (syncLock) + { + int currentTick = Find.TickManager.TicksGame; + int verifiedCount = 0; + int timeoutCount = 0; + + foreach (var info in registeredPawns.Where(i => i.isValid)) + { + // 查找Pawn + Pawn pawn = FindPawnByID(info.thingID, info.map); + + if (pawn == null || pawn.Destroyed || !pawn.Spawned) + { + // Pawn已不存在 + info.isValid = false; + timeoutCount++; + } + else if (currentTick - info.lastVerificationTick > VERIFICATION_INTERVAL) + { + // 需要发送验证请求 + SendVerificationRequest(pawn, info.globalVariable); + } + else + { + verifiedCount++; + } + } + + // 清理无效的 + CleanupInvalidRegistrations(); + } + } + + /// + /// 发送验证请求给Pawn + /// + private void SendVerificationRequest(Pawn pawn, string globalVariable) + { + // 这里可以发送信号或事件给Pawn + // 为了简化,我们直接更新验证时间 + // 实际应用中,Pawn应该定期主动发送验证信号 + var comp = pawn.TryGetComp(); + if (comp != null) + { + comp.SendVerificationSignal(); + } + } + #endregion + + #region GameComponent实现 + public override void GameComponentTick() + { + base.GameComponentTick(); + + if (Find.TickManager.TicksGame - lastVerificationTick > VERIFICATION_INTERVAL) + { + VerifyAllPawns(); + lastVerificationTick = Find.TickManager.TicksGame; + } + } + + public override void ExposeData() + { + base.ExposeData(); + + lock (syncLock) + { + Scribe_Collections.Look(ref registeredPawns, "registeredPawns", LookMode.Deep); + + if (Scribe.mode == LoadSaveMode.PostLoadInit) + { + // 加载后初始化 + Initialize(); + + // 清理无效数据 + CleanupInvalidRegistrations(); + + Log.Message($"[唯一Pawn系统] 加载了 {registeredPawns?.Count ?? 0} 个Pawn注册信息"); + } + } + } + #endregion + } +} diff --git a/非公开资源/Content/Textures/Things/ARA_Neurotyrant/Bodies/Naked_Thin_east.sai2 b/非公开资源/Content/Textures/Things/ARA_Neurotyrant/Bodies/Naked_Thin_east.sai2 index 0d81feb..18cf89f 100644 Binary files a/非公开资源/Content/Textures/Things/ARA_Neurotyrant/Bodies/Naked_Thin_east.sai2 and b/非公开资源/Content/Textures/Things/ARA_Neurotyrant/Bodies/Naked_Thin_east.sai2 differ