diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index cb12f8a..8512649 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/EvolutionDefs/ARA_Evolution.xml b/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml index e3942d9..2ae5809 100644 --- a/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml +++ b/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml @@ -328,8 +328,8 @@ ARA_Myrmecocystus_Production_InsectJelly - - 这只阿拉克涅蜜罐种正在产出虫蜜,以滋养虫群。一只蜜罐种每天产出15份阿拉克涅虫蜜。 + + 这只阿拉克涅蜜罐种正在产出虫蜜,以滋养虫群。 ARA_InsectJelly ARA_Myrmecocystus_Production_Medicine @@ -368,61 +368,14 @@ - + -
  • - - - true - - ARA_InsectJelly - - 15 - - - 1 - - 1 - 0 - - - false - - true - - true - - 20 - - - true - - false -
  • ARA_Myrmecocystus_Production_Medicine
  • @@ -1203,51 +1156,6 @@ -
  • - - - true - - ARA_Carapace - - 15 - - - 1 - - 1 - 0 - - - false - - true - - true - - 20 - - - true - - false -
  • ARA_ShieldHead_Protector
  • 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 263123a..ed18012 100644 --- a/1.6/1.6/Defs/HediffDefs/ARA_Hediffs_HiveMind.xml +++ b/1.6/1.6/Defs/HediffDefs/ARA_Hediffs_HiveMind.xml @@ -22,6 +22,7 @@
  • Outdoors
  • +
  • ARA_ChitinArmor
  • Indoors
  • @@ -54,6 +55,9 @@
  • DrugDesire
  • RoomSize
  • + +
  • ARA_ChitinArmor
  • +
    @@ -95,6 +99,9 @@
  • DrugDesire
  • RoomSize
  • + +
  • ARA_ChitinArmor
  • +
    @@ -301,4 +308,76 @@ + + + ARA_ChitinArmor + + 阿拉克涅虫族身上的甲壳,可以根据厚度为阿拉克涅虫族提供保护。 + HediffWithComps + +
  • + 0 + + + 0.95 + +
  • +
  • + 0.99 + + + 0.9 + 0.9 + +
  • +
  • + 1.99 + + + 0.85 + 0.75 + +
  • +
  • + 2.99 + + + 0.7 + 0.5 + +
  • +
  • + 3.99 + + + 0.5 + 0.25 + +
  • +
  • + 4.99 + + + 0.35 + 0 + +
  • +
  • + 5.99 + + + 0.25 + 0 + +
  • +
  • + 6.99 + + + 0.1 + 0 + +
  • +
    +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/JobDefs/ARA_Jobs.xml b/1.6/1.6/Defs/JobDefs/ARA_Jobs.xml index b4f5c58..99b1e3b 100644 --- a/1.6/1.6/Defs/JobDefs/ARA_Jobs.xml +++ b/1.6/1.6/Defs/JobDefs/ARA_Jobs.xml @@ -38,4 +38,61 @@ false true + + ARA_OperateEquipmentIncubator + ArachnaeSwarm.JobDriver_OperateEquipmentIncubator + 激活阿拉克涅茧。 + true + false + true + false + true + + + + + ARA_FeedWithHoney + ArachnaeSwarm.JobDriver_FeedWithHoney + 正在喂养TargetA。 + false + true + true + + true + + + + + ARA_ExtractHoney + ArachnaeSwarm.JobDriver_ExtractHoney + 正在挤出虫蜜。 + false + true + true + + true + + + + + ARA_StripChitin + ArachnaeSwarm.JobDriver_StripChitin + 正在剥离甲壳 + true + false + true + + + + ARA_SwarmMaintain + ArachnaeSwarm.JobDriver_SwarmMaintain + 正在维护TargetA。 + true + + false + true + true + false + \ No newline at end of file diff --git a/1.6/1.6/Defs/NeedDefs/ARA_Needs.xml b/1.6/1.6/Defs/NeedDefs/ARA_Needs.xml index 5ecb541..797c71c 100644 --- a/1.6/1.6/Defs/NeedDefs/ARA_Needs.xml +++ b/1.6/1.6/Defs/NeedDefs/ARA_Needs.xml @@ -4,16 +4,57 @@ ARA_HoneyProduction ArachnaeSwarm.Need_HoneyProduction - 代表这个生物储存阿拉克涅虫蜜原浆的多少。当其他虫族饥饿时,会尝试直接从有蜜罐腔的生物身上获取虫蜜,如果蜜罐满溢,则生物会尝试将其提取出来。 + 代表这个生物储存阿拉克涅虫蜜原浆的多少。当其他虫族饥饿时,有蜜罐腔的生物会尝试喂养它们;如果蜜罐满溢,则生物会尝试将其提取出来。 800 true true -
  • Wula_Synth
  • +
  • ARA_Myrmecocystus_Production_InsectJelly
  • true Baby, Child, Adult true false + + +
  • + + 0.8 + + + 1 +
  • +
    + + + ARA_ChitinArmor + ArachnaeSwarm.Need_ChitinArmor + + 代表这个生物身上的阿拉克涅甲壳的厚度,越厚的甲壳越能为虫族带来强大的防御力。虫族也可以将其从身上剥离下来,以生产甲壳素。 + 800 + true + true + +
  • ARA_HiveMindMaster
  • +
  • ARA_HiveMindDrone
  • +
  • ARA_NonPlayer_HiveMindDroneHediff
  • +
    + true + Baby, Child, Adult + true + false + + +
  • + ARA_ChitinArmor + + 0.0 + 10.0 + + true + 0.1 + 0.1 +
  • +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml index 16468ae..a9f37b5 100644 --- a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml +++ b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml @@ -210,8 +210,6 @@
  • ARA_AcidSprayBurst
  • -
  • ARA_RaceBaseSwarmProduceOff
  • -
  • ARA_RaceBaseSwarmProduceOn
  • @@ -256,10 +254,6 @@ 0 - -
  • ARA_RaceBaseSwarmProduceOff
  • -
  • ARA_RaceBaseSwarmProduceOn
  • -
    ArachnaeNode_Race_Facehugger @@ -498,50 +492,6 @@ - - ArachnaeBase_Race_HardJaw - - ArachnaeBase_Race_HardJaw - -
  • - - ArachnaeSwarm/Things/ARA_Scavenger/HardJaw/Naked_Thin - 1 - (156,148,125) - - (0.4, 0.5, 0.37) - (0,0,-0.15) - - - - Things/Pawn/Animal/Spelopede/Dessicated_Spelopede - 1 - -
  • -
    -
    - - ArachnaeBase_Race_Maid - - ArachnaeBase_Race_Maid - -
  • - - ArachnaeSwarm/Things/ARA_Scavenger/Maid/Naked_Thin - 1 - (156,148,125) - - (0.4, 0.5, 0.37) - (0,0,-0.15) - - - - Things/Pawn/Animal/Spelopede/Dessicated_Spelopede - 1 - -
  • -
    -
    ArachnaeBase_Race_Acidcut diff --git a/1.6/1.6/Defs/Rooms/ARA_RoomRoles.xml b/1.6/1.6/Defs/Rooms/ARA_RoomRoles.xml index c5652a4..cd3cfbe 100644 --- a/1.6/1.6/Defs/Rooms/ARA_RoomRoles.xml +++ b/1.6/1.6/Defs/Rooms/ARA_RoomRoles.xml @@ -6,7 +6,7 @@ ArachnaeSwarm.RoomRoleWorker_Incubator
  • Space
  • -
  • ARA_IncubatorRateFactor
  • +
  • ARA_IncubatorQualityFactor
  • 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 9301adb..5b5f5ec 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml @@ -775,11 +775,6 @@ -
  • ARA_Cycle_Suppression_Hediff ARA_LifespanHediff @@ -937,7 +932,7 @@ false
  • - false + true ARA_RaceBaseSwarmProduceSwitchHediff ARA_InsectJelly 1 @@ -1063,24 +1058,6 @@ 1.0 false
  • -
  • - true - ARA_RaceBaseSwarmProduceSwitchHediff - ARA_InsectJelly - 1 - 1 - 999 - 9999 - -
  • - - ArachnaeBase_Race_Maid - 3 - 1000 -
  • - - CocoonDestroyed -
  • ARA_Cycle_Suppression_Hediff ARA_LifespanHediff @@ -1419,24 +1396,6 @@ 1.0 false
  • -
  • - true - ARA_RaceBaseSwarmProduceSwitchHediff - ARA_InsectJelly - 1 - 1 - 999 - 9999 - -
  • - - ArachnaeBase_Race_Maid - 5 - 1000 -
  • - - CocoonDestroyed -
  • ARA_Cycle_Suppression_Hediff ARA_LifespanHediff 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 7c332e5..01830b6 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml @@ -169,6 +169,14 @@ 1.0 false
  • + +
  • + true + 0.8 + 1 + 3000 + ARA_Carapace +
  • @@ -420,8 +428,7 @@
  • ArachnaeBase_Race_Scavenger
  • -
  • ArachnaeBase_Race_HardJaw
  • -
  • ArachnaeBase_Race_Maid
  • +
  • ArachnaeBase_Race_Larva
  • true diff --git a/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon.xml b/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon.xml index ce63b00..87dc862 100644 --- a/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon.xml +++ b/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon.xml @@ -73,9 +73,7 @@
  • -
  • ARA_Cocoon_Weapon
  • -
  • ARA_Cocoon_Weapon_From_Death
  • -
  • ARA_BioforgeIncubator_Thing
  • +
  • ARA_Equipment_Ootheca
  • diff --git a/1.6/1.6/Defs/Thing_building/ARA_Building.xml b/1.6/1.6/Defs/Thing_building/ARA_Building.xml index 9dc872e..828008c 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_Building.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_Building.xml @@ -495,9 +495,16 @@ -
  • - ARA_InsectCreep - 3 +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • +
    @@ -558,13 +565,20 @@ +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • + +
  • ResearchSpeedFactor
  • -
  • - ARA_InsectCreep - 4 -
  • @@ -627,6 +641,17 @@ ARA_Buildings +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • + +
  • UI/Commands/Vent @@ -638,10 +663,6 @@ 25
  • -
  • - ARA_InsectCreep - 3 -
  • @@ -691,9 +712,16 @@ -
  • - ARA_InsectCreep - 3 +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • +
  • 3 @@ -759,9 +787,16 @@ -
  • - ARA_InsectCreep - 2 +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • +
  • 3 @@ -833,9 +868,16 @@
  • PlaceWorker_GlowRadius
  • -
  • - ARA_InsectCreep - 2 +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • +
  • 7 @@ -998,9 +1040,16 @@ -
  • - ARA_InsectCreep - 5 +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • +
  • 4 @@ -1047,9 +1096,16 @@ -
  • - ARA_InsectCreep - 3 +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • +
  • diff --git a/1.6/1.6/Defs/Thing_building/ARA_DropPod.xml b/1.6/1.6/Defs/Thing_building/ARA_DropPod.xml index a297a9d..cda739c 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_DropPod.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_DropPod.xml @@ -33,19 +33,6 @@ 20 3 - -
  • - 300 - 0.8 - true - -
  • -
  • - ARA_DropPodLeaving - false - 53 -
  • -
  • ARA_Technology_8POD
  • @@ -58,6 +45,30 @@ 0.65 ARA_DropPodIncoming ARA_ActiveDropPod + +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • + + +
  • + 300 + 0.8 + true + +
  • +
  • + ARA_DropPodLeaving + false + 53 +
  • +
    diff --git a/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml b/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml index 42f9d51..fc48934 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml @@ -36,6 +36,17 @@ ARA_Creep +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • + +
  • 7.0 14 @@ -162,6 +173,18 @@ 4 ARA_Creep +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • + + +
  • 5.0 diff --git a/1.6/1.6/Defs/Thing_building/ARA_Ootheca.xml b/1.6/1.6/Defs/Thing_building/ARA_Ootheca.xml index 4629030..b32c1c9 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_Ootheca.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_Ootheca.xml @@ -98,11 +98,37 @@ false false - false + ARA_Incubator_Room + + +
  • + + 5 + + 5 + + false + + + + + + + + + + + + + + +
  • + +
  • ArachnaeSwarm.ITab_Ootheca_Incubation
  • @@ -112,6 +138,17 @@
  • ArachnaeSwarm.PlaceWorker_CustomRadius
  • +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • + +
  • 5 (0.5, 1, 1) @@ -144,7 +181,7 @@ ArachnaeNode_Race_Fighter 2 UI/Buttons/IncubateUnitA - + ARA_Technology_1KYC
  • ArachnaeNode_Race_Myrmecocystus @@ -203,4 +240,125 @@
  • + + + ARA_Equipment_Ootheca + + 一个脆弱、易燃、黏滑的囊状物,是阿拉克涅女皇种所诞之卵,内含哺育一只新督虫所需的营养和遗传物质。 + + 1000 + + ArachnaeSwarm.Building_EquipmentOotheca + Building + (1,1) + ARA_Buildings + + ArachnaeSwarm/Building/ARA_Cocoon + Graphic_Single + (1.1,1.1) + + (0.7, 0.4, 0.7) + (0,0,-0.1) + + + Building + PassThroughOnly + 0.3 + false + Normal + 0 + None + + ARA_Incubator_Nutrient_Solution + + (0, 0, 1) + true + + 10 + 150 + 1 + -6 + + + true + + false + false + + + ARA_Incubator_Room + + + + +
  • + + 3 + + 3 + + false + + 0.03 + + + + + + + + + + + + +
  • +
    + + + +
  • ArachnaeSwarm.ITab_EquipmentOotheca_Incubation
  • +
    + + +
  • ArachnaeSwarm.PlaceWorker_CustomRadius
  • +
    + +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • + + +
  • + 3 + (0.5, 1, 1) + 0 + true + + 这个卵在孵化过程中的吸收半径,确保这些地格中铺满阿拉克涅营养液,并且没有其他的卵,以获得最佳的孵化速度和孵化质量。 + false +
  • +
  • + +
  • +
  • + CocoonDestroyed +
  • +
  • + 6 + (113,141,117,0) +
  • +
  • + -10 + 20 + 0.015 +
  • +
    +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/Thing_building/ARA_RefuelingVat.xml b/1.6/1.6/Defs/Thing_building/ARA_RefuelingVat.xml index 37ac4ce..8bc3562 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_RefuelingVat.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_RefuelingVat.xml @@ -63,6 +63,18 @@ 4 ARA_Creep +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • + + +
  • 精华素 diff --git a/1.6/1.6/Defs/Thing_building/ARA_SwarmTurret.xml b/1.6/1.6/Defs/Thing_building/ARA_SwarmTurret.xml index 0f71e61..f92e1cd 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_SwarmTurret.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_SwarmTurret.xml @@ -288,6 +288,17 @@ Normal Heavy +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • + +
  • nutrition @@ -305,10 +316,6 @@ true
  • -
  • - ARA_InsectCreep - 6 -
  • ARA_NutrientNetworkTower
  • @@ -430,6 +437,17 @@ Normal Heavy +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • + +
  • nutrition @@ -446,10 +464,6 @@ true
  • -
  • - ARA_InsectCreep - 6 -
  • ARA_NutrientNetworkTower
  • @@ -595,6 +609,17 @@ Normal +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • + +
  • nutrition 50.0 @@ -611,10 +636,6 @@
  • -
  • - ARA_InsectCreep - 8 -
  • ARA_NutrientNetworkTower
  • diff --git a/1.6/1.6/Defs/Thing_building/ARA_WormholeDefs.xml b/1.6/1.6/Defs/Thing_building/ARA_WormholeDefs.xml index 34e32d4..61a4ee9 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_WormholeDefs.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_WormholeDefs.xml @@ -39,6 +39,17 @@ 200 +
  • + 100 + 15 + 2 + 0.2 + 1800 + 0.5 + +
  • ArachnaeNode_Race_WeaponSmith
  • + +
  • 7.0 14 diff --git a/1.6/1.6/Defs/ThinkTreeDefs/ARA_ThinkTrees.xml b/1.6/1.6/Defs/ThinkTreeDefs/ARA_ThinkTrees.xml index 7adc599..4033614 100644 --- a/1.6/1.6/Defs/ThinkTreeDefs/ARA_ThinkTrees.xml +++ b/1.6/1.6/Defs/ThinkTreeDefs/ARA_ThinkTrees.xml @@ -896,6 +896,35 @@ Humanlike_PreMain
  • + +
  • + ArachnaeNode_Race_Myrmecocystus + +
  • + +
  • +
  • + ArachnaeNode_Race_Myrmecocystus + +
  • + +
  • + +
  • + ARA_HiveMindMaster + 0~5 + +
  • + +
  • +
  • + ARA_HiveMindDrone + 0~5 + +
  • + +
  • +
  • diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Building_Ootheca.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Building_Ootheca.xml index 724dca0..05fb611 100644 --- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Building_Ootheca.xml +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Building_Ootheca.xml @@ -8,7 +8,7 @@ 目标 速度乘数 质量乘数 - 幼虫种种进入中 + 幼虫种进入中 剩余秒数 幼虫种正在路上 剩余时间 @@ -97,4 +97,76 @@ 需要研究 + + + + 速度因子 + ✓ 位于孵化间内 + ✗ 不在孵化间内 + 营养液: {0} (+{1}%) + 无附近营养液 + 营养液检测半径: {0}格 + 总速度乘数: {0} + + 质量因子 + 建筑健康度: {0} + 房间因子: 正常 + 房间因子: + 附近卵: {0} (-{1}%) + 无附近卵 + 卵检测半径: {0}格 + 总质量乘数: {0} + + 呼叫幼虫 + 幼虫将前往卵荚进行孵化 + 幼虫搜索半径: {0}格 + + 已经在孵化中 + 请先取消当前孵化 + 幼虫已在途中 + 未找到可用幼虫 + 必须是幼虫种 + 幼虫已呼叫 + 即将到达 + 幼虫已到达 + 正在激活卵荚 + 开始孵化 + 过程将在 + 天内完成(基础时间) + 孵化已取消 + 内容物已丢失 + 孵化完成 + 已出现,质量为 + 优秀 + 良好 + 一般 + 较差 + 很差 + 质量 + + 正在孵化 + 进度 + 剩余时间 + + 小时 + 速度 + 质量 + 幼虫正在操作 + 秒剩余 + 幼虫在途中 + 目标 + 速度乘数 + 质量乘数 + + 呼叫幼虫 + 取消孵化 + 取消当前孵化过程,卵荚内容物将丢失 + + 孵化: {0} + 选择要孵化的生物类型。幼虫将前来激活卵荚。 + 当前选择: {0} + 孵化时间: {0}天 + 选择孵化目标 + 需要研究: + 孵化目标已切换为: {0} diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_EquipmentIncubator.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_EquipmentIncubator.xml new file mode 100644 index 0000000..439bc1a --- /dev/null +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_EquipmentIncubator.xml @@ -0,0 +1,90 @@ + + + + 速度因子 + ✓ 位于孵化间内 + ✗ 不在孵化间内 + 营养液: {0} (+{1}%) + 无附近营养液 + 营养液检测半径: {0}格 + 总速度乘数: {0} + + 质量因子 + 建筑健康度: {0} + 房间因子: 正常 + 房间因子: + 附近卵: {0} (-{1}%) + 无附近卵 + 卵检测半径: {0}格 + 总质量乘数: {0} + + 呼叫幼虫 + 幼虫将前往卵荚生产装备 + 幼虫搜索半径: {0}格 + + 已经在生产中 + 请先取消当前生产 + 幼虫已在途中 + 未找到可用幼虫 + 幼虫必须是ArachnaeBase_Race_Larva种族 + 幼虫已呼叫 + 即将到达 + 幼虫已到达 + 正在激活装备卵荚 + 开始生产 + 过程将在 + 天内完成(基础时间) + 生产已取消 + 内容物已丢失 + 生产完成 + 已生产出,质量为 + 传奇 + 杰作 + 精良 + 良好 + 一般 + 质量 + + 正在生产 + 进度 + 剩余时间 + + 小时 + 速度 + 质量 + 幼虫正在操作 + 秒剩余 + 幼虫在途中 + 目标 + 速度乘数 + 质量乘数 + + 呼叫幼虫 + 取消生产 + 取消当前生产过程,卵荚内容物将丢失 + + 生产: {0} + 选择要生产的装备类型。幼虫将前来激活装备卵荚。 + 当前选择: {0} + 生产时间: {0}天 + 选择生产目标 + 需要研究: + 生产目标已切换为: {0} + + 装备生产 + 这不是装备卵荚 + 生产进度 + 生产进度 + 质量进度 + 幼虫正在激活卵荚 + 秒剩余 + 幼虫在途中 + 准备生产 + 未选择生产目标 + + 生产目标:{0} + 需要 + 无描述 + 研究: {0} (已完成) + 研究: {0} (需要) + diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_HoneyProduction.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_HoneyProduction.xml new file mode 100644 index 0000000..16f01fc --- /dev/null +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_HoneyProduction.xml @@ -0,0 +1,29 @@ + + + + 当前储量 + 储量等级 + 生产效率:(饮食消耗速率的) + 食物消耗速率 + 蜜罐增长速率 + 警告:蜜罐已满,停止生产! + 满仓 + 高存量 + 中存量 + 低存量 + 空仓 + + + 挤出了 {0} 个虫蜜 + + + {0} 用虫蜜喂养了 {1} + + + 挤出虫蜜 + 用虫蜜喂养 + + + 蜜罐生产 + 虫蜜 + \ No newline at end of file diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_InteractiveProducer_Keys.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_InteractiveProducer_Keys.xml index d015969..a2fbe03 100644 --- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_InteractiveProducer_Keys.xml +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_InteractiveProducer_Keys.xml @@ -18,7 +18,7 @@ 未孵化,需要 {0} 交互\n\n在生产完成时,剩余的营养将重新转变为物品。 任何阿拉克涅虫族 {0} 个物品可用 - 物品品质 + 孵化品质 所需营养 @@ -31,4 +31,14 @@ 生产槽位: {0} / {1} 等待队列: {0} 生产速度 + + 甲壳部位数量 + 生长速率 + 甲壳剥离 + 切换是否允许此单位自动剥离甲壳。当甲壳过厚,单位会自动剥离甲壳以产生甲壳素。 + 剥离了{0}个甲壳 + 甲壳剥离功能已禁用 + 冷却中:{0}秒后可以再次剥离 + 甲壳存量未达到阈值({0}%) + 可以开始剥离甲壳 \ No newline at end of file diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_SwarmMaintenance.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_SwarmMaintenance.xml new file mode 100644 index 0000000..6c47db5 --- /dev/null +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_SwarmMaintenance.xml @@ -0,0 +1,13 @@ + + + + 维护度: {0}/{1} ({2}) + 维护度耗尽!建筑正在损坏! + 维护度严重不足! + 需要维护 + 每日维护度递减: {0} + 正在寻找空闲的维护者... + 找不到符合条件的空闲维护者 + 允许维护的种族: + 未指定可维护的种族 + diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_TemperatureRuinable.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_TemperatureRuinable.xml new file mode 100644 index 0000000..b60fb0e --- /dev/null +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_TemperatureRuinable.xml @@ -0,0 +1,13 @@ + + + + 安全温度范围: {0} - {1} + 当前温度: {0} + 损坏进度: {0} + 正在受到温度伤害 + 未受到温度影响 + 过冷 + 过热 + {0} 受到温度伤害 + {0} 因{1}正在受到温度伤害。\n\n当前温度: {2}\n安全温度范围: {3} - {4}\n\n建议尽快调整温度至安全范围,以避免结构损坏。 + diff --git a/Content/Textures/ArachnaeSwarm/UI/Commands/ARA_CallLarva.png b/Content/Textures/ArachnaeSwarm/UI/Commands/ARA_CallLarva.png new file mode 100644 index 0000000..4eb5086 Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/UI/Commands/ARA_CallLarva.png differ diff --git a/Content/Textures/ArachnaeSwarm/UI/Commands/ARA_ShowRadius.png b/Content/Textures/ArachnaeSwarm/UI/Commands/ARA_ShowRadius.png new file mode 100644 index 0000000..bfeb9bf Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/UI/Commands/ARA_ShowRadius.png differ diff --git a/Content/Textures/ArachnaeSwarm/UI/Commands/ARA_StripChitin.png b/Content/Textures/ArachnaeSwarm/UI/Commands/ARA_StripChitin.png new file mode 100644 index 0000000..b98d7e4 Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/UI/Commands/ARA_StripChitin.png differ diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo index cd88601..4db06b4 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 20e9236..3bc5b0e 100644 --- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json +++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json @@ -1,58 +1,58 @@ { "Version": 1, - "WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\", + "WorkspaceRootPath": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\", "Documents": [ { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\buildings\\building_ootheca\\building_ootheca.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\buildings\\building_equipmentootheca\\building_equipmentootheca.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_equipmentootheca\\building_equipmentootheca.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\buildings\\building_equipmentootheca\\compproperties_equipmentincubatordata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_equipmentootheca\\compproperties_equipmentincubatordata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\buildings\\building_ootheca\\oothecaincubatorextension.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\oothecaincubatorextension.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\ara_hediffdefof.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:ara_hediffdefof.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing_comps\\ara_compextraincubationinfo\\compproperties_extraincubationinfo.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing_comps\\ara_compextraincubationinfo\\compproperties_extraincubationinfo.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing_comps\\ara_compextraincubationinfo\\compextraincubationinfo.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing_comps\\ara_compextraincubationinfo\\compextraincubationinfo.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\buildings\\building_ootheca\\building_ootheca.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\building_ootheca.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\placeworker\\compproperties_customradius.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:placeworker\\compproperties_customradius.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\building_comps\\ara_swarmmaintenance\\comp_swarmmaintenance.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_swarmmaintenance\\comp_swarmmaintenance.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\flyover\\ara_groundstrafing\\compgroundstrafing.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_groundstrafing\\compgroundstrafing.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\building_comps\\ara_buildingterrainspawn\\compdelayedterrainspawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_buildingterrainspawn\\compdelayedterrainspawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\flyover\\ara_sectorsurveillance\\compsectorsurveillance.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_sectorsurveillance\\compsectorsurveillance.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\building_comps\\comptemperatureruinabledamage.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\comptemperatureruinabledamage.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\flyover\\thingclassflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\thingclassflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\flyover\\ara_aircrafthangar\\compabilityeffect_aircraftstrike.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_aircrafthangar\\compabilityeffect_aircraftstrike.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\abilities\\ara_givehediffwithskillduration\\compabilityeffect_givehediffwithskillduration.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_givehediffwithskillduration\\compabilityeffect_givehediffwithskillduration.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\abilities\\ara_showtemperaturerange\\compabilityeffect_abilityshowtemperaturerange.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\abilities\\ara_showtemperaturerange\\compabilityeffect_abilityshowtemperaturerange.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_showtemperaturerange\\compabilityeffect_abilityshowtemperaturerange.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\abilities\\ara_showspawnablepawnslist\\compabilityeffect_abilityshowspawnablepawns.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_showspawnablepawnslist\\compabilityeffect_abilityshowspawnablepawns.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\jobs\\jobdriver_swarmmaintain\\jobdriver_swarmmaintain.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:jobs\\jobdriver_swarmmaintain\\jobdriver_swarmmaintain.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\flyover\\ara_spawnflyover\\compabilityeffect_spawnflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_spawnflyover\\compabilityeffect_spawnflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\flyover\\ara_aircrafthangar\\compaircrafthangar.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_aircrafthangar\\compaircrafthangar.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\flyover\\ara_aircrafthangar\\worldcomponent_aircraftmanager.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_aircrafthangar\\worldcomponent_aircraftmanager.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\flyover\\ara_flyoverescort\\compproperties_flyoverescort.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_flyoverescort\\compproperties_flyoverescort.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\jobs\\jobdriver_feedwithhoney\\jobdriver_feedwithhoney.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:jobs\\jobdriver_feedwithhoney\\jobdriver_feedwithhoney.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" } ], "DocumentGroupContainers": [ @@ -62,21 +62,8 @@ "DocumentGroups": [ { "DockedWidth": 200, - "SelectedChildIndex": 0, + "SelectedChildIndex": 3, "Children": [ - { - "$type": "Document", - "DocumentIndex": 0, - "Title": "Building_Ootheca.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Building_Ootheca.cs", - "RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\Building_Ootheca.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Building_Ootheca.cs", - "RelativeToolTip": "Buildings\\Building_Ootheca\\Building_Ootheca.cs", - "ViewState": "AgIAAK8CAAAAAAAAAAA9wPACAAAMAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-12-15T07:28:18.272Z", - "EditorCaption": "" - }, { "$type": "Bookmark", "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" @@ -84,147 +71,171 @@ { "$type": "Document", "DocumentIndex": 1, - "Title": "CompProperties_CustomRadius.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Placeworker\\CompProperties_CustomRadius.cs", - "RelativeDocumentMoniker": "Placeworker\\CompProperties_CustomRadius.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Placeworker\\CompProperties_CustomRadius.cs", - "RelativeToolTip": "Placeworker\\CompProperties_CustomRadius.cs", - "ViewState": "AgIAAAIAAAAAAAAAAAAAACMAAAA/AAAAAAAAAA==", + "Title": "CompProperties_EquipmentIncubatorData.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_EquipmentOotheca\\CompProperties_EquipmentIncubatorData.cs", + "RelativeDocumentMoniker": "Buildings\\Building_EquipmentOotheca\\CompProperties_EquipmentIncubatorData.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_EquipmentOotheca\\CompProperties_EquipmentIncubatorData.cs", + "RelativeToolTip": "Buildings\\Building_EquipmentOotheca\\CompProperties_EquipmentIncubatorData.cs", + "ViewState": "AgIAAAQAAAAAAAAAAAAmwDMBAAAlAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-12-15T07:21:04.756Z", + "WhenOpened": "2025-12-15T17:55:40.041Z", "EditorCaption": "" }, { "$type": "Document", "DocumentIndex": 3, - "Title": "CompSectorSurveillance.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_SectorSurveillance\\CompSectorSurveillance.cs", - "RelativeDocumentMoniker": "Flyover\\ARA_SectorSurveillance\\CompSectorSurveillance.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_SectorSurveillance\\CompSectorSurveillance.cs", - "RelativeToolTip": "Flyover\\ARA_SectorSurveillance\\CompSectorSurveillance.cs", - "ViewState": "AgIAAPACAAAAAAAAAAAAABEDAAAAAAAAAAAAAA==", + "Title": "ARA_HediffDefOf.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\ARA_HediffDefOf.cs", + "RelativeDocumentMoniker": "ARA_HediffDefOf.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\ARA_HediffDefOf.cs", + "RelativeToolTip": "ARA_HediffDefOf.cs", + "ViewState": "AgIAAAAAAAAAAAAAAADwvxgAAAAdAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-30T13:52:54.896Z" + "WhenOpened": "2025-12-15T17:32:18.493Z", + "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 2, - "Title": "CompGroundStrafing.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_GroundStrafing\\CompGroundStrafing.cs", - "RelativeDocumentMoniker": "Flyover\\ARA_GroundStrafing\\CompGroundStrafing.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_GroundStrafing\\CompGroundStrafing.cs", - "RelativeToolTip": "Flyover\\ARA_GroundStrafing\\CompGroundStrafing.cs", - "ViewState": "AgIAAGwBAAAAAAAAAAArwIYBAAAFAAAAAAAAAA==", + "DocumentIndex": 0, + "Title": "Building_EquipmentOotheca.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_EquipmentOotheca\\Building_EquipmentOotheca.cs", + "RelativeDocumentMoniker": "Buildings\\Building_EquipmentOotheca\\Building_EquipmentOotheca.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_EquipmentOotheca\\Building_EquipmentOotheca.cs*", + "RelativeToolTip": "Buildings\\Building_EquipmentOotheca\\Building_EquipmentOotheca.cs*", + "ViewState": "AgIAADIBAAAAAAAAAAAAAEQBAAAIAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-30T13:00:11.18Z" - }, - { - "$type": "Document", - "DocumentIndex": 6, - "Title": "CompAbilityEffect_GiveHediffWithSkillDuration.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_GiveHediffWithSkillDuration\\CompAbilityEffect_GiveHediffWithSkillDuration.cs", - "RelativeDocumentMoniker": "Abilities\\ARA_GiveHediffWithSkillDuration\\CompAbilityEffect_GiveHediffWithSkillDuration.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_GiveHediffWithSkillDuration\\CompAbilityEffect_GiveHediffWithSkillDuration.cs", - "RelativeToolTip": "Abilities\\ARA_GiveHediffWithSkillDuration\\CompAbilityEffect_GiveHediffWithSkillDuration.cs", - "ViewState": "AgIAAEsAAAAAAAAAAAAWwGAAAAAAAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-29T14:40:47.422Z" - }, - { - "$type": "Document", - "DocumentIndex": 7, - "Title": "CompAbilityEffect_AbilityShowTemperatureRange.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs", - "RelativeDocumentMoniker": "Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs", - "RelativeToolTip": "Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAABcAAAArAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-29T14:40:43.525Z" + "WhenOpened": "2025-12-15T17:32:08.87Z", + "EditorCaption": "" }, { "$type": "Document", "DocumentIndex": 5, - "Title": "CompAbilityEffect_AircraftStrike.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\CompAbilityEffect_AircraftStrike.cs", - "RelativeDocumentMoniker": "Flyover\\ARA_AircraftHangar\\CompAbilityEffect_AircraftStrike.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\CompAbilityEffect_AircraftStrike.cs", - "RelativeToolTip": "Flyover\\ARA_AircraftHangar\\CompAbilityEffect_AircraftStrike.cs", - "ViewState": "AgIAAHYAAAAAAAAAAAAtwJcAAAArAAAAAAAAAA==", + "Title": "CompExtraIncubationInfo.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_CompExtraIncubationInfo\\CompExtraIncubationInfo.cs", + "RelativeDocumentMoniker": "Thing_Comps\\ARA_CompExtraIncubationInfo\\CompExtraIncubationInfo.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_CompExtraIncubationInfo\\CompExtraIncubationInfo.cs", + "RelativeToolTip": "Thing_Comps\\ARA_CompExtraIncubationInfo\\CompExtraIncubationInfo.cs", + "ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-29T11:22:34.783Z" + "WhenOpened": "2025-12-15T17:13:20.87Z", + "EditorCaption": "" }, { "$type": "Document", "DocumentIndex": 4, - "Title": "ThingclassFlyOver.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ThingclassFlyOver.cs", - "RelativeDocumentMoniker": "Flyover\\ThingclassFlyOver.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ThingclassFlyOver.cs", - "RelativeToolTip": "Flyover\\ThingclassFlyOver.cs", - "ViewState": "AgIAAIkCAAAAAAAAAAAawI8CAAANAAAAAAAAAA==", + "Title": "CompProperties_ExtraIncubationInfo.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_CompExtraIncubationInfo\\CompProperties_ExtraIncubationInfo.cs", + "RelativeDocumentMoniker": "Thing_Comps\\ARA_CompExtraIncubationInfo\\CompProperties_ExtraIncubationInfo.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_CompExtraIncubationInfo\\CompProperties_ExtraIncubationInfo.cs", + "RelativeToolTip": "Thing_Comps\\ARA_CompExtraIncubationInfo\\CompProperties_ExtraIncubationInfo.cs", + "ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-29T14:17:06.867Z" + "WhenOpened": "2025-12-15T17:13:20.069Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 2, + "Title": "OothecaIncubatorExtension.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\OothecaIncubatorExtension.cs", + "RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\OothecaIncubatorExtension.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\OothecaIncubatorExtension.cs", + "RelativeToolTip": "Buildings\\Building_Ootheca\\OothecaIncubatorExtension.cs", + "ViewState": "AgIAAAAAAAAAAAAAAADwvxIAAAA4AAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2025-12-15T17:11:54.114Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 6, + "Title": "Building_Ootheca.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Building_Ootheca.cs", + "RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\Building_Ootheca.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\Building_Ootheca.cs", + "RelativeToolTip": "Buildings\\Building_Ootheca\\Building_Ootheca.cs", + "ViewState": "AgIAAA0DAAAAAAAAAAAAACAAAAArAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2025-12-15T17:10:05.509Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 7, + "Title": "Comp_SwarmMaintenance.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_SwarmMaintenance\\Comp_SwarmMaintenance.cs", + "RelativeDocumentMoniker": "Building_Comps\\ARA_SwarmMaintenance\\Comp_SwarmMaintenance.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_SwarmMaintenance\\Comp_SwarmMaintenance.cs", + "RelativeToolTip": "Building_Comps\\ARA_SwarmMaintenance\\Comp_SwarmMaintenance.cs", + "ViewState": "AgIAABkBAAAAAAAAAAAAAE0BAABIAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2025-12-15T16:59:28.717Z", + "EditorCaption": "" }, { "$type": "Document", "DocumentIndex": 8, - "Title": "CompAbilityEffect_AbilityShowSpawnablePawns.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowSpawnablePawnsList\\CompAbilityEffect_AbilityShowSpawnablePawns.cs", - "RelativeDocumentMoniker": "Abilities\\ARA_ShowSpawnablePawnsList\\CompAbilityEffect_AbilityShowSpawnablePawns.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowSpawnablePawnsList\\CompAbilityEffect_AbilityShowSpawnablePawns.cs", - "RelativeToolTip": "Abilities\\ARA_ShowSpawnablePawnsList\\CompAbilityEffect_AbilityShowSpawnablePawns.cs", - "ViewState": "AgIAABYAAAAAAAAAAAAuwBYAAAArAAAAAAAAAA==", + "Title": "CompDelayedTerrainSpawn.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs", + "RelativeDocumentMoniker": "Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs", + "RelativeToolTip": "Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs", + "ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-29T14:40:40.237Z" - }, - { - "$type": "Document", - "DocumentIndex": 10, - "Title": "CompAircraftHangar.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\CompAircraftHangar.cs", - "RelativeDocumentMoniker": "Flyover\\ARA_AircraftHangar\\CompAircraftHangar.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\CompAircraftHangar.cs", - "RelativeToolTip": "Flyover\\ARA_AircraftHangar\\CompAircraftHangar.cs", - "ViewState": "AgIAABcAAAAAAAAAAAAQwCQAAAAnAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-29T11:39:22.563Z" + "WhenOpened": "2025-12-15T16:59:21.314Z", + "EditorCaption": "" }, { "$type": "Document", "DocumentIndex": 9, - "Title": "CompAbilityEffect_SpawnFlyOver.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_SpawnFlyOver\\CompAbilityEffect_SpawnFlyOver.cs", - "RelativeDocumentMoniker": "Flyover\\ARA_SpawnFlyOver\\CompAbilityEffect_SpawnFlyOver.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_SpawnFlyOver\\CompAbilityEffect_SpawnFlyOver.cs", - "RelativeToolTip": "Flyover\\ARA_SpawnFlyOver\\CompAbilityEffect_SpawnFlyOver.cs", - "ViewState": "AgIAAFMDAAAAAAAAAAAawG8DAAAAAAAAAAAAAA==", + "Title": "CompTemperatureRuinableDamage.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\CompTemperatureRuinableDamage.cs", + "RelativeDocumentMoniker": "Building_Comps\\CompTemperatureRuinableDamage.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\CompTemperatureRuinableDamage.cs", + "RelativeToolTip": "Building_Comps\\CompTemperatureRuinableDamage.cs", + "ViewState": "AgIAAB8AAAAAAAAAAAAAwDoAAAAlAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-29T13:37:35.758Z" + "WhenOpened": "2025-12-15T16:52:05.743Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 10, + "Title": "CompAbilityEffect_AbilityShowTemperatureRange.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs", + "RelativeDocumentMoniker": "Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs", + "RelativeToolTip": "Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs", + "ViewState": "AgIAAHUAAAAAAAAAAAAAAJIAAABEAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2025-12-15T16:52:00.971Z", + "EditorCaption": "" }, { "$type": "Document", "DocumentIndex": 11, - "Title": "WorldComponent_AircraftManager.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\WorldComponent_AircraftManager.cs", - "RelativeDocumentMoniker": "Flyover\\ARA_AircraftHangar\\WorldComponent_AircraftManager.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\WorldComponent_AircraftManager.cs", - "RelativeToolTip": "Flyover\\ARA_AircraftHangar\\WorldComponent_AircraftManager.cs", - "ViewState": "AgIAAJUAAAAAAAAAAAAowK4AAAAUAAAAAAAAAA==", + "Title": "JobDriver_SwarmMaintain.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_SwarmMaintain\\JobDriver_SwarmMaintain.cs", + "RelativeDocumentMoniker": "Jobs\\JobDriver_SwarmMaintain\\JobDriver_SwarmMaintain.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_SwarmMaintain\\JobDriver_SwarmMaintain.cs", + "RelativeToolTip": "Jobs\\JobDriver_SwarmMaintain\\JobDriver_SwarmMaintain.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAB0AAABPAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-29T14:05:39.817Z" + "WhenOpened": "2025-12-15T16:36:34.047Z", + "EditorCaption": "" }, { "$type": "Document", "DocumentIndex": 12, - "Title": "CompProperties_FlyOverEscort.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs", - "RelativeDocumentMoniker": "Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs", - "RelativeToolTip": "Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAABAAAAAvAAAAAAAAAA==", + "Title": "JobDriver_FeedWithHoney.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_FeedWithHoney\\JobDriver_FeedWithHoney.cs", + "RelativeDocumentMoniker": "Jobs\\JobDriver_FeedWithHoney\\JobDriver_FeedWithHoney.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_FeedWithHoney\\JobDriver_FeedWithHoney.cs", + "RelativeToolTip": "Jobs\\JobDriver_FeedWithHoney\\JobDriver_FeedWithHoney.cs", + "ViewState": "AgIAACEAAAAAAAAAAAAYwEsAAAAOAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-29T12:59:07.753Z" + "WhenOpened": "2025-12-15T16:35:50.511Z", + "EditorCaption": "" } ] } diff --git a/Source/ArachnaeSwarm/ARA_HediffDefOf.cs b/Source/ArachnaeSwarm/ARA_HediffDefOf.cs index 419e1be..7620d1e 100644 --- a/Source/ArachnaeSwarm/ARA_HediffDefOf.cs +++ b/Source/ArachnaeSwarm/ARA_HediffDefOf.cs @@ -19,10 +19,22 @@ namespace ArachnaeSwarm public static class ARA_JobDefOf { public static JobDef ARA_OperateIncubator; - + public static JobDef ARA_OperateEquipmentIncubator; + public static JobDef ARA_SwarmMaintain; + static ARA_JobDefOf() { DefOfHelper.EnsureInitializedInCtor(typeof(ARA_JobDefOf)); } } + [DefOf] + public static class ARA_EffecterDefOf + { + public static EffecterDef EatVegetarian; + + static ARA_EffecterDefOf() + { + DefOfHelper.EnsureInitializedInCtor(typeof(ARA_EffecterDefOf)); + } + } } diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index 716e987..e1f2062 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -114,11 +114,16 @@ + + + + + @@ -131,6 +136,8 @@ + + @@ -164,7 +171,18 @@ + + + + + + + + + + + @@ -196,7 +214,7 @@ - + diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompTemperatureRuinableDamage.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompTemperatureRuinableDamage.cs deleted file mode 100644 index a4b6075..0000000 --- a/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompTemperatureRuinableDamage.cs +++ /dev/null @@ -1,91 +0,0 @@ -using RimWorld; -using Verse; - -namespace ArachnaeSwarm -{ - public class CompProperties_TemperatureRuinableDamage : CompProperties - { - public float minSafeTemperature; - public float maxSafeTemperature = 100f; - public float progressPerDegreePerTick = 1E-05f; // 修改参数名以匹配标准调用方式 - public float damagePerTick = 1f; // 每tick造成的伤害值 - public float recoveryRate = 0.001f; // 温度恢复正常时的恢复速率 - - public CompProperties_TemperatureRuinableDamage() - { - compClass = typeof(CompTemperatureRuinableDamage); - } - } - - public class CompTemperatureRuinableDamage : ThingComp - { - private float ruinedPercent; // 修改变量名以匹配标准 - private bool isRuined; // 修改变量名以匹配标准 - - public CompProperties_TemperatureRuinableDamage Props => (CompProperties_TemperatureRuinableDamage)props; - - public override void CompTick() - { - base.CompTick(); - if (parent.AmbientTemperature < Props.minSafeTemperature || parent.AmbientTemperature > Props.maxSafeTemperature) - { - float tempDelta = 0f; - if (parent.AmbientTemperature < Props.minSafeTemperature) - { - tempDelta = Props.minSafeTemperature - parent.AmbientTemperature; - } - else if (parent.AmbientTemperature > Props.maxSafeTemperature) - { - tempDelta = parent.AmbientTemperature - Props.maxSafeTemperature; - } - - // 累积损坏进度 - ruinedPercent += tempDelta * Props.progressPerDegreePerTick; - - // 只有在已损坏的情况下才每tick造成持续伤害 - if (isRuined) - { - parent.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, Props.damagePerTick)); - } - - // 标记为已受损 - isRuined = true; - } - else - { - // 当温度恢复正常时,逐渐减少损坏进度而不是重置 - if (isRuined && ruinedPercent > 0f) - { - ruinedPercent -= Props.recoveryRate; - if (ruinedPercent <= 0f) - { - ruinedPercent = 0f; - isRuined = false; - } - } - - // 即使温度正常,如果已损坏也要继续造成伤害直到恢复 - if (isRuined) - { - parent.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, Props.damagePerTick)); - } - } - } - - public override void PostExposeData() - { - base.PostExposeData(); - Scribe_Values.Look(ref ruinedPercent, "ruinedPercent", 0f); - Scribe_Values.Look(ref isRuined, "isRuined", false); - } - - public override string CompInspectStringExtra() - { - if (ruinedPercent > 0f) - { - return "CocoonRuinedByTemperature".Translate() + ": " + ruinedPercent.ToStringPercent(); - } - return base.CompInspectStringExtra(); - } - } -} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_SwarmMaintenance/CompProperties_SwarmMaintenance.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_SwarmMaintenance/CompProperties_SwarmMaintenance.cs new file mode 100644 index 0000000..e33b6b8 --- /dev/null +++ b/Source/ArachnaeSwarm/Building_Comps/ARA_SwarmMaintenance/CompProperties_SwarmMaintenance.cs @@ -0,0 +1,36 @@ +// File: Comps/CompProperties_SwarmMaintenance.cs +using RimWorld; +using System.Collections.Generic; +using Verse; + +namespace ArachnaeSwarm +{ + public class CompProperties_SwarmMaintenance : CompProperties + { + // 最大维护度 + public float maxMaintenance = 100f; + + // 每天下降的维护度 + public float maintenanceDecayPerDay = 10f; + + // 维护度为0时每秒受到的伤害 + public float damagePerSecondWhenEmpty = 1f; + + // 维护度达到多少时开始显示警告(百分比) + public float warningThreshold = 0.3f; + + // 可维护的种族列表 + public List allowedRaces; + + // 检查分配工作的间隔(ticks) + public int assignJobCheckInterval = 600; // 10秒 + + // 维护度低于多少时开始寻找维护者(百分比) + public float maintenanceThresholdForJob = 0.9f; + + public CompProperties_SwarmMaintenance() + { + compClass = typeof(Comp_SwarmMaintenance); + } + } +} diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_SwarmMaintenance/Comp_SwarmMaintenance.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_SwarmMaintenance/Comp_SwarmMaintenance.cs new file mode 100644 index 0000000..99fdb48 --- /dev/null +++ b/Source/ArachnaeSwarm/Building_Comps/ARA_SwarmMaintenance/Comp_SwarmMaintenance.cs @@ -0,0 +1,399 @@ +// File: Comps/Comp_SwarmMaintenance.cs +using System.Collections.Generic; +using RimWorld; +using UnityEngine; +using Verse; +using Verse.AI; + +namespace ArachnaeSwarm +{ + public class Comp_SwarmMaintenance : ThingComp + { + // 属性访问器 + public CompProperties_SwarmMaintenance Props => (CompProperties_SwarmMaintenance)props; + + // 当前维护度 + private float currentMaintenance; + + // 上次更新维护度的时间 + private int lastMaintenanceTick = -99999; + + // 上次检查分配工作的时间 + private int lastJobCheckTick = -99999; + + // 当维护度为0时,每秒受到的伤害计时器 + private int nextDamageTick = -99999; + + // 是否正在寻找维护者 + private bool seekingMaintainer = false; + + // 找不到维护者的提示信息 + private string noMaintainerWarning = ""; + private int lastWarningTick = -99999; + private const int WarningDuration = 2500; // 警告显示41.67秒 + + // 属性访问 + public float CurrentMaintenance => currentMaintenance; + public float MaxMaintenance => Props.maxMaintenance; + public float MaintenancePercentage => currentMaintenance / MaxMaintenance; + public bool NeedsMaintenance => currentMaintenance < MaxMaintenance * Props.maintenanceThresholdForJob; + public bool IsCritical => currentMaintenance <= MaxMaintenance * Props.warningThreshold; + public bool IsEmpty => currentMaintenance <= 0f; + + // 初始化 + public override void Initialize(CompProperties props) + { + base.Initialize(props); + currentMaintenance = Props.maxMaintenance; // 初始时满维护度 + lastMaintenanceTick = Find.TickManager.TicksGame; + lastJobCheckTick = Find.TickManager.TicksGame; + } + + // 序列化 + public override void PostExposeData() + { + base.PostExposeData(); + Scribe_Values.Look(ref currentMaintenance, "currentMaintenance", Props.maxMaintenance); + Scribe_Values.Look(ref lastMaintenanceTick, "lastMaintenanceTick", -99999); + Scribe_Values.Look(ref lastJobCheckTick, "lastJobCheckTick", -99999); + Scribe_Values.Look(ref nextDamageTick, "nextDamageTick", -99999); + Scribe_Values.Look(ref seekingMaintainer, "seekingMaintainer", false); + Scribe_Values.Look(ref noMaintainerWarning, "noMaintainerWarning", ""); + Scribe_Values.Look(ref lastWarningTick, "lastWarningTick", -99999); + } + + // 每tick更新 + public override void CompTick() + { + base.CompTick(); + + int currentTick = Find.TickManager.TicksGame; + + // 每2500tick(约41.67秒)更新一次维护度递减 + if (currentTick - lastMaintenanceTick >= 2500) + { + UpdateMaintenanceDecay(); + lastMaintenanceTick = currentTick; + } + + // 定期检查是否需要分配维护工作 + if (currentTick - lastJobCheckTick >= Props.assignJobCheckInterval) + { + CheckAndAssignMaintenanceJob(); + lastJobCheckTick = currentTick; + } + + // 如果维护度为0,每秒造成伤害 + if (currentMaintenance <= 0f && currentTick >= nextDamageTick) + { + ApplyDamageWhenEmpty(); + nextDamageTick = currentTick + 60; // 60ticks = 1秒 + } + + // 更新警告信息显示时间 + if (currentTick - lastWarningTick >= WarningDuration) + { + noMaintainerWarning = ""; + } + } + + // 更新维护度递减 + private void UpdateMaintenanceDecay() + { + if (parent == null || parent.Map == null) + return; + + // 计算递减量:每天递减量转换为每2500tick(约0.347天)的递减量 + float decayAmount = Props.maintenanceDecayPerDay * (2500f / 60000f); + currentMaintenance -= decayAmount; + + // 确保不低于0 + if (currentMaintenance < 0f) + currentMaintenance = 0f; + } + + // 当维护度为0时应用伤害 + private void ApplyDamageWhenEmpty() + { + if (parent == null || parent.Destroyed) + return; + + // 每秒造成伤害 + float damageAmount = Props.damagePerSecondWhenEmpty; + + // 应用伤害到建筑 + parent.TakeDamage(new DamageInfo( + DamageDefOf.Deterioration, + damageAmount, + armorPenetration: 999f, // 高穿甲,确保能造成伤害 + instigator: null + )); + } + + // 检查并分配维护工作 + private void CheckAndAssignMaintenanceJob() + { + if (parent == null || parent.Map == null || parent.Faction == null) + return; + + // 如果不需要维护,重置状态 + if (!NeedsMaintenance) + { + seekingMaintainer = false; + return; + } + + // 如果正在寻找维护者,尝试分配工作 + if (!seekingMaintainer) + { + seekingMaintainer = true; + } + + // 尝试寻找符合条件的Pawn + Pawn maintainer = FindAvailableMaintainer(); + + if (maintainer != null) + { + // 分配维护工作 + AssignMaintenanceJobTo(maintainer); + seekingMaintainer = false; + noMaintainerWarning = ""; + } + else + { + // 记录找不到维护者的警告 + if (noMaintainerWarning == "") + { + noMaintainerWarning = "ARA_SwarmMaintenance.NoMaintainerFound".Translate(); + lastWarningTick = Find.TickManager.TicksGame; + } + } + } + + // 寻找可用的维护者 + private Pawn FindAvailableMaintainer() + { + if (parent.Map == null || parent.Faction == null) + return null; + + // 查找地图中所有属于玩家阵营的Pawn + List allPawns = parent.Map.mapPawns.SpawnedPawnsInFaction(parent.Faction); + + foreach (Pawn pawn in allPawns) + { + if (CanPawnMaintain(pawn)) + { + return pawn; + } + } + + return null; + } + + // 判断Pawn是否可以维护 + private bool CanPawnMaintain(Pawn pawn) + { + if (pawn == null || pawn.Dead || pawn.Downed || pawn.InMentalState) + return false; + + // 检查种族是否在允许列表中 + if (Props.allowedRaces != null && Props.allowedRaces.Count > 0) + { + if (!Props.allowedRaces.Contains(pawn.def)) + return false; + } + + // 检查Pawn是否处于GotoWander或Wait_Wander状态 + if (!IsPawnWandering(pawn)) + return false; + + // 检查Pawn是否已经有维护工作 + if (HasMaintenanceJob(pawn)) + return false; + + return true; + } + + // 检查Pawn是否处于漫游状态 + private bool IsPawnWandering(Pawn pawn) + { + if (pawn.jobs == null || pawn.jobs.curJob == null) + return false; + + Job curJob = pawn.jobs.curJob; + + // 检查是否是漫游工作 + if (curJob.def == JobDefOf.GotoWander || curJob.def == JobDefOf.Wait_Wander) + { + return true; + } + + return false; + } + + // 检查Pawn是否已经有维护工作 + private bool HasMaintenanceJob(Pawn pawn) + { + if (pawn.jobs == null) + return false; + + // 检查当前工作是否是维护工作 + if (pawn.jobs.curJob != null && pawn.jobs.curJob.def == ARA_JobDefOf.ARA_SwarmMaintain) + { + return true; + } + + // 检查工作队列中是否有维护工作 + if (pawn.jobs.jobQueue != null && pawn.jobs.jobQueue.Count > 0) + { + foreach (QueuedJob queuedJob in pawn.jobs.jobQueue) + { + if (queuedJob.job.def == ARA_JobDefOf.ARA_SwarmMaintain) + { + return true; + } + } + } + + return false; + } + + // 为Pawn分配维护工作 + private void AssignMaintenanceJobTo(Pawn pawn) + { + if (pawn == null || pawn.jobs == null) + return; + + // 创建维护工作 + Job job = JobMaker.MakeJob(ARA_JobDefOf.ARA_SwarmMaintain, parent); + job.expiryInterval = 30000; // 工作过期时间 + job.ignoreForbidden = false; + + // 记录Pawn原来的工作以便恢复 + Job oldJob = pawn.jobs.curJob; + + // 将工作添加到队列末尾 + pawn.jobs.TryTakeOrderedJob(job, JobTag.MiscWork); + + // 如果Pawn原来在漫游,我们可以在维护工作完成后恢复漫游状态 + if (oldJob != null && (oldJob.def == JobDefOf.GotoWander || oldJob.def == JobDefOf.Wait_Wander)) + { + // 我们可以在维护工作完成后添加一个恢复漫游的工作 + // 这里暂时不做处理,因为维护工作完成后Pawn会回到空闲状态 + // 如果需要更精细的控制,可以在JobDriver_SwarmMaintain完成时添加漫游工作 + } + } + + // 添加维护度 + public void AddMaintenance(float amount) + { + currentMaintenance += amount; + if (currentMaintenance > MaxMaintenance) + currentMaintenance = MaxMaintenance; + } + + // 重置维护度 + public void ResetMaintenance() + { + currentMaintenance = MaxMaintenance; + } + + // 在建筑信息面板中追加维护信息 + public override string CompInspectStringExtra() + { + // 基础信息 + string text = "ARA_SwarmMaintenance.MaintenanceLevel".Translate(currentMaintenance.ToString("F1"), MaxMaintenance.ToString("F1"), MaintenancePercentage.ToString("P1")); + + // 添加维护度递减信息 + text += "\n" + "ARA_SwarmMaintenance.DailyDecay".Translate(Props.maintenanceDecayPerDay.ToString("F1")); + + + // 显示找不到维护者的警告 + if (noMaintainerWarning != "") + { + text += "\n" + noMaintainerWarning + ""; + } + + return text; + } + + // 获取Gizmos(命令按钮)- 只保留调试功能 + public override IEnumerable CompGetGizmosExtra() + { + // 只在玩家控制下显示 + if (parent.Faction?.IsPlayer == true) + { + // 调试按钮:手动触发寻找维护者 + if (Prefs.DevMode) + { + yield return new Command_Action + { + defaultLabel = "Debug: Find Maintainer", + action = () => + { + Pawn maintainer = FindAvailableMaintainer(); + if (maintainer != null) + { + Messages.Message($"找到维护者: {maintainer.LabelShort} (当前工作: {maintainer.jobs.curJob?.def.defName ?? "无"})", MessageTypeDefOf.PositiveEvent); + AssignMaintenanceJobTo(maintainer); + } + else + { + Messages.Message("未找到符合条件的空闲维护者", MessageTypeDefOf.NegativeEvent); + + // 列出所有符合条件的Pawn及其状态 + List allPawns = parent.Map.mapPawns.SpawnedPawnsInFaction(parent.Faction); + List pawnStatus = new List(); + + foreach (Pawn pawn in allPawns) + { + if (Props.allowedRaces != null && Props.allowedRaces.Count > 0 && !Props.allowedRaces.Contains(pawn.def)) + continue; + + string status = $"{pawn.LabelShort}: "; + if (pawn.Dead) status += "死亡"; + else if (pawn.Downed) status += "倒地"; + else if (pawn.InMentalState) status += "精神崩溃"; + else if (pawn.jobs?.curJob != null) + { + status += $"{pawn.jobs.curJob.def.defName}"; + if (HasMaintenanceJob(pawn)) status += " (已有维护工作)"; + } + else + { + status += "空闲"; + } + + pawnStatus.Add(status); + } + + if (pawnStatus.Count > 0) + { + Messages.Message("符合条件的Pawn状态:\n" + string.Join("\n", pawnStatus), MessageTypeDefOf.SilentInput); + } + } + } + }; + + yield return new Command_Action + { + defaultLabel = "Debug: Reset Maintenance", + action = () => ResetMaintenance() + }; + + yield return new Command_Action + { + defaultLabel = "Debug: Reduce 50%", + action = () => currentMaintenance = MaxMaintenance * 0.5f + }; + + yield return new Command_Action + { + defaultLabel = "Debug: Reduce to 0", + action = () => currentMaintenance = 0f + }; + } + } + } + } +} diff --git a/Source/ArachnaeSwarm/Building_Comps/CompTemperatureRuinableDamage.cs b/Source/ArachnaeSwarm/Building_Comps/CompTemperatureRuinableDamage.cs new file mode 100644 index 0000000..954759f --- /dev/null +++ b/Source/ArachnaeSwarm/Building_Comps/CompTemperatureRuinableDamage.cs @@ -0,0 +1,178 @@ +// File: Comps/CompTemperatureRuinableDamage.cs +using RimWorld; +using Verse; + +namespace ArachnaeSwarm +{ + public class CompProperties_TemperatureRuinableDamage : CompProperties + { + public float minSafeTemperature; + public float maxSafeTemperature = 100f; + public float progressPerDegreePerTick = 1E-05f; // 修改参数名以匹配标准调用方式 + public float damagePerTick = 1f; // 每tick造成的伤害值 + public float recoveryRate = 0.001f; // 温度恢复正常时的恢复速率 + public bool sendDamageLetter = true; // 是否发送伤害警告信件 + + public CompProperties_TemperatureRuinableDamage() + { + compClass = typeof(CompTemperatureRuinableDamage); + } + } + + public class CompTemperatureRuinableDamage : ThingComp + { + private float ruinedPercent; // 修改变量名以匹配标准 + private bool isRuined; // 修改变量名以匹配标准 + private bool damageLetterSent = false; // 是否已发送伤害警告信件 + private int lastDamageTick = -99999; // 上次造成伤害的时间 + private const int DamageLetterInterval = 60000; // 伤害信件间隔(60秒游戏时间) + + public CompProperties_TemperatureRuinableDamage Props => (CompProperties_TemperatureRuinableDamage)props; + + public override void CompTick() + { + base.CompTick(); + int currentTick = Find.TickManager.TicksGame; + bool wasRuined = isRuined; + + if (parent.AmbientTemperature < Props.minSafeTemperature || parent.AmbientTemperature > Props.maxSafeTemperature) + { + float tempDelta = 0f; + if (parent.AmbientTemperature < Props.minSafeTemperature) + { + tempDelta = Props.minSafeTemperature - parent.AmbientTemperature; + } + else if (parent.AmbientTemperature > Props.maxSafeTemperature) + { + tempDelta = parent.AmbientTemperature - Props.maxSafeTemperature; + } + + // 累积损坏进度 + ruinedPercent += tempDelta * Props.progressPerDegreePerTick; + + // 只有在已损坏的情况下才每tick造成持续伤害 + if (isRuined) + { + parent.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, Props.damagePerTick)); + lastDamageTick = currentTick; + + // 发送伤害警告信件(如果启用) + if (Props.sendDamageLetter && !damageLetterSent && currentTick - lastDamageTick >= DamageLetterInterval) + { + SendDamageLetter(); + damageLetterSent = true; + lastDamageTick = currentTick; + } + } + + // 标记为已受损 + isRuined = true; + + // 如果是刚刚变为损坏状态,发送警告信件 + if (!wasRuined && Props.sendDamageLetter) + { + SendDamageLetter(); + damageLetterSent = true; + } + } + else + { + // 当温度恢复正常时,逐渐减少损坏进度而不是重置 + if (isRuined && ruinedPercent > 0f) + { + ruinedPercent -= Props.recoveryRate; + if (ruinedPercent <= 0f) + { + ruinedPercent = 0f; + isRuined = false; + damageLetterSent = false; // 重置信件状态,以便下次损坏时可以再次发送 + } + } + + // 即使温度正常,如果已损坏也要继续造成伤害直到恢复 + if (isRuined) + { + parent.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, Props.damagePerTick)); + lastDamageTick = currentTick; + + // 发送伤害警告信件(如果启用) + if (Props.sendDamageLetter && !damageLetterSent && currentTick - lastDamageTick >= DamageLetterInterval) + { + SendDamageLetter(); + damageLetterSent = true; + lastDamageTick = currentTick; + } + } + } + } + + public override void PostExposeData() + { + base.PostExposeData(); + Scribe_Values.Look(ref ruinedPercent, "ruinedPercent", 0f); + Scribe_Values.Look(ref isRuined, "isRuined", false); + Scribe_Values.Look(ref damageLetterSent, "damageLetterSent", false); + Scribe_Values.Look(ref lastDamageTick, "lastDamageTick", -99999); + } + + public override string CompInspectStringExtra() + { + // 总是显示安全温度范围 + string text = "ARA_TemperatureRuinable.SafeTemperatureRange".Translate( + Props.minSafeTemperature.ToStringTemperature("F1"), + Props.maxSafeTemperature.ToStringTemperature("F1") + ); + + // 显示当前温度状态 + text += "\n" + "ARA_TemperatureRuinable.CurrentTemperature".Translate(parent.AmbientTemperature.ToStringTemperature("F1")); + + // 显示损坏状态 + if (ruinedPercent > 0f) + { + text += "\n" + "ARA_TemperatureRuinable.DamageProgress".Translate(ruinedPercent.ToStringPercent()); + + if (isRuined) + { + text += "\n" + "ARA_TemperatureRuinable.TakingDamage".Translate() + ""; + } + } + else + { + text += "\n" + "ARA_TemperatureRuinable.NoDamage".Translate(); + } + + return text; + } + + // 发送伤害警告信件 + private void SendDamageLetter() + { + if (parent == null || parent.Map == null) + return; + + // 确定伤害类型(太冷或太热) + string temperatureType; + if (parent.AmbientTemperature < Props.minSafeTemperature) + { + temperatureType = "ARA_TemperatureRuinable.TooCold".Translate(); + } + else + { + temperatureType = "ARA_TemperatureRuinable.TooHot".Translate(); + } + + // 创建信件 + string label = "ARA_TemperatureRuinable.DamageLetterLabel".Translate(parent.Label); + string text = "ARA_TemperatureRuinable.DamageLetterText".Translate( + parent.Label, + temperatureType, + parent.AmbientTemperature.ToStringTemperature("F1"), + Props.minSafeTemperature.ToStringTemperature("F1"), + Props.maxSafeTemperature.ToStringTemperature("F1") + ); + + // 发送信件 + Find.LetterStack.ReceiveLetter(label, text, LetterDefOf.NegativeEvent, new LookTargets(parent)); + } + } +} diff --git a/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Building_EquipmentOotheca.cs b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Building_EquipmentOotheca.cs new file mode 100644 index 0000000..7861254 --- /dev/null +++ b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Building_EquipmentOotheca.cs @@ -0,0 +1,882 @@ +// File: Buildings/Building_EquipmentOotheca.cs +using RimWorld; +using System.Collections.Generic; +using System.Text; +using UnityEngine; +using Verse; +using Verse.AI; +using System; + +namespace ArachnaeSwarm +{ + public class Building_EquipmentOotheca : Building + { + // 引用组件 + public CompEquipmentIncubatorData EquipmentIncubatorData => this.TryGetComp(); + + // 孵化状态 + public bool isIncubating = false; + public float incubationProgress = 0f; + public float incubationDuration = 0f; + public ThingDef incubatingThingDef = null; + + // 幼虫交互相关 + public Pawn assignedLarva = null; + public int larvaOperateTicksRemaining = 0; + + // 速度乘数系统 + private float speedMultiplier = 1.0f; + private int lastMultiplierUpdateTick = -1; + private const int MultiplierUpdateInterval = 250; + + // 质量系统 + private float qualityMultiplier = 1.0f; + private float qualityProgress = 0f; + private float qualityTotal = 0f; + + // 缓存的ModExtension + private OothecaIncubatorExtension cachedExtension; + + // 获取ModExtension的辅助属性 + private OothecaIncubatorExtension Ext + { + get + { + if (cachedExtension == null) + { + cachedExtension = def.GetModExtension() ?? OothecaIncubatorExtension.Default; + } + return cachedExtension; + } + } + + // 属性访问器 + public float SpeedMultiplier + { + get + { + if (lastMultiplierUpdateTick < 0 || Find.TickManager.TicksGame - lastMultiplierUpdateTick >= MultiplierUpdateInterval) + { + UpdateSpeedMultiplier(); + } + return speedMultiplier; + } + } + + // 质量属性 + public float QualityMultiplier => qualityMultiplier; + public float QualityProgress => qualityProgress; + public float QualityPercent => qualityTotal > 0 ? qualityProgress / qualityTotal : 0f; + + // 进度百分比 + public float AdjustedProgressPercent + { + get + { + if (incubationDuration <= 0) return 0f; + return incubationProgress / incubationDuration; + } + } + + // 获取速度因子描述 + public string GetSpeedFactorsDescription() + { + var builder = new StringBuilder(); + + builder.AppendLine("ARA_EquipmentIncubator.SpeedFactors".Translate()); + builder.AppendLine(); + + // 1. 检查是否在孵化间中 + bool inIncubatorRoom = IsInIncubatorRoom(); + if (Ext.requiresIncubatorRoom) + { + builder.AppendLine(inIncubatorRoom ? + "ARA_EquipmentIncubator.InIncubatorRoom".Translate() : + "ARA_EquipmentIncubator.NotInIncubatorRoom".Translate()); + } + + // 2. 检查营养液数量 + int nutrientSolutionCount = CountNearbyNutrientSolutions(); + if (nutrientSolutionCount > 0) + { + builder.AppendLine("ARA_EquipmentIncubator.NutrientSolutions".Translate( + nutrientSolutionCount, + nutrientSolutionCount * Ext.nutrientSolutionBonusPerTile * 100)); + } + else + { + builder.AppendLine("ARA_EquipmentIncubator.NoNutrientSolutionsNearby".Translate()); + } + + builder.AppendLine(); + builder.AppendLine("ARA_EquipmentIncubator.NutrientDetectionRadius".Translate(Ext.nutrientSolutionRadius)); + + builder.AppendLine(); + builder.AppendLine("ARA_EquipmentIncubator.TotalSpeedMultiplier".Translate(SpeedMultiplier.ToStringPercent())); + + return builder.ToString().TrimEndNewlines(); + } + + // 获取质量因子描述 + public string GetQualityFactorsDescription() + { + var builder = new StringBuilder(); + + builder.AppendLine("ARA_EquipmentIncubator.QualityFactors".Translate()); + builder.AppendLine(); + + // 1. 建筑血量损失百分比 + if (Ext.healthAffectsQuality) + { + float healthPercent = (float)HitPoints / MaxHitPoints; + builder.AppendLine("ARA_EquipmentIncubator.BuildingHealth".Translate(healthPercent.ToStringPercent())); + } + + // 2. 房间质量因子 + if (Ext.useRoomQualityFactor) + { + float roomFactor = GetRoomQualityFactor(); + builder.AppendLine(roomFactor == 1.0f ? + "ARA_EquipmentIncubator.RoomFactorNormal".Translate() : + $"{(roomFactor > 1.0f ? "✓" : "✗")} {"ARA_EquipmentIncubator.RoomFactorModified".Translate()}{roomFactor.ToStringPercent()}"); + } + + // 3. 附近其他卵 + int nearbyOothecaCount = CountNearbyOtherOothecas(); + if (nearbyOothecaCount > 0) + { + builder.AppendLine("ARA_EquipmentIncubator.NearbyOothecas".Translate( + nearbyOothecaCount, + Mathf.Min(nearbyOothecaCount * Ext.nearbyOothecaPenaltyPerUnit * 100, 100))); + } + else + { + builder.AppendLine("ARA_EquipmentIncubator.NoNearbyOothecas".Translate()); + } + + builder.AppendLine(); + builder.AppendLine("ARA_EquipmentIncubator.OothecaDetectionRadius".Translate(Ext.nearbyOothecaRadius)); + + builder.AppendLine(); + builder.AppendLine("ARA_EquipmentIncubator.TotalQualityMultiplier".Translate(QualityMultiplier.ToStringPercent())); + + return builder.ToString().TrimEndNewlines(); + } + + // 构建呼叫幼虫描述 + private string BuildCallLarvaDescription(EquipmentIncubationConfig config) + { + var builder = new StringBuilder(); + + builder.AppendLine("ARA_EquipmentIncubator.CallLarvaTitle".Translate()); + builder.AppendLine(); + builder.AppendLine("ARA_EquipmentIncubator.LarvaWillCome".Translate()); + builder.AppendLine(config.thingDef.LabelCap); + builder.AppendLine(); + + if (Ext.larvaSearchRadius < 999f) + { + builder.AppendLine("ARA_EquipmentIncubator.LarvaSearchRadius".Translate(Ext.larvaSearchRadius)); + } + + return builder.ToString().TrimEndNewlines(); + } + + // 呼叫幼虫 + private void CallLarva() + { + if (isIncubating) + { + Messages.Message("ARA_EquipmentIncubator.AlreadyIncubating".Translate() + " " + "ARA_EquipmentIncubator.CancelFirst".Translate(), + MessageTypeDefOf.RejectInput); + return; + } + + if (assignedLarva != null) + { + Messages.Message("ARA_EquipmentIncubator.LarvaAlreadyOnWay".Translate(), + MessageTypeDefOf.RejectInput); + return; + } + + var larva = FindLarva(); + if (larva == null) + { + Messages.Message("ARA_EquipmentIncubator.NoLarvaeFound".Translate() + " " + "ARA_EquipmentIncubator.LarvaMustBeRace".Translate(), + MessageTypeDefOf.RejectInput); + return; + } + + var job = JobMaker.MakeJob(ARA_JobDefOf.ARA_OperateEquipmentIncubator, this); + job.count = 1; + larva.jobs.TryTakeOrderedJob(job, JobTag.MiscWork); + + assignedLarva = larva; + + Messages.Message("ARA_EquipmentIncubator.LarvaCalled".Translate() + " " + "ARA_EquipmentIncubator.ArriveShortly".Translate(), + MessageTypeDefOf.PositiveEvent); + } + + // 幼虫到达 + public void NotifyLarvaArrived(Pawn larva) + { + if (larva.def.defName != "ArachnaeBase_Race_Larva") + { + ArachnaeLog.Debug($"Invalid larva arrived: {larva.def.defName}"); + return; + } + + larvaOperateTicksRemaining = 180; + assignedLarva = larva; + + Messages.Message("ARA_EquipmentIncubator.LarvaArrived".Translate() + " " + "ARA_EquipmentIncubator.ActivatingOotheca".Translate(), + MessageTypeDefOf.SilentInput); + } + + // 幼虫完成操作 + public void NotifyLarvaOperationComplete(Pawn larva) + { + if (larva != assignedLarva) + { + ArachnaeLog.Debug("Larva operation complete called with wrong larva."); + return; + } + + var config = EquipmentIncubatorData?.SelectedConfig; + if (config == null) + { + ArachnaeLog.Debug("No incubation config selected when larva completed operation."); + return; + } + + incubatingThingDef = config.thingDef; + incubationDuration = config.DaysRequired * 60000f; + incubationProgress = 0f; + isIncubating = true; + + qualityTotal = incubationDuration; + qualityProgress = 0f; + UpdateQualityMultiplier(); + + UpdateSpeedMultiplier(); + + assignedLarva = null; + larvaOperateTicksRemaining = 0; + + Messages.Message("ARA_EquipmentIncubator.IncubationStarted".Translate() + " " + incubatingThingDef.LabelCap + ". " + + "ARA_EquipmentIncubator.ProcessWillComplete".Translate() + " " + config.DaysRequired + " " + "ARA_EquipmentIncubator.DaysBaseTime".Translate(), + MessageTypeDefOf.PositiveEvent); + } + + // 取消孵化 + private void CancelIncubation() + { + if (!isIncubating) return; + + isIncubating = false; + incubationProgress = 0f; + incubationDuration = 0f; + incubatingThingDef = null; + qualityProgress = 0f; + qualityTotal = 0f; + + Messages.Message("ARA_EquipmentIncubator.IncubationCancelled".Translate() + " " + "ARA_EquipmentIncubator.ContentsLost".Translate(), + MessageTypeDefOf.NeutralEvent); + } + + // 完成孵化 + private void CompleteIncubation() + { + if (incubatingThingDef == null) return; + + float finalQualityPercent = QualityPercent; + + // 生成物品 + Thing thing = ThingMaker.MakeThing(incubatingThingDef); + + // 应用质量影响 + ApplyQualityEffects(thing, finalQualityPercent); + + // 放置物品 + var spawnPos = Position; + GenPlace.TryPlaceThing(thing, spawnPos, Map, ThingPlaceMode.Near); + + // 重置状态 + isIncubating = false; + incubationProgress = 0f; + incubationDuration = 0f; + incubatingThingDef = null; + qualityProgress = 0f; + qualityTotal = 0f; + + // 显示消息 + string qualityText = finalQualityPercent >= 0.9f ? "ARA_EquipmentIncubator.QualityExcellent".Translate() : + finalQualityPercent >= 0.7f ? "ARA_EquipmentIncubator.QualityGood".Translate() : + finalQualityPercent >= 0.5f ? "ARA_EquipmentIncubator.QualityAverage".Translate() : + finalQualityPercent >= 0.3f ? "ARA_EquipmentIncubator.QualityPoor".Translate() : "ARA_EquipmentIncubator.QualityVeryPoor".Translate(); + + Messages.Message("ARA_EquipmentIncubator.IncubationComplete".Translate() + " " + thing.LabelCap + " " + + "ARA_EquipmentIncubator.HasEmergedWith".Translate() + " " + qualityText + " " + + "ARA_EquipmentIncubator.Quality".Translate() + " (" + finalQualityPercent.ToStringPercent() + ").", + MessageTypeDefOf.PositiveEvent); + + Destroy(); + } + + // 应用质量效果 + private void ApplyQualityEffects(Thing thing, float qualityPercent) + { + // 应用质量效果到装备 + if (thing.TryGetComp() is CompQuality compQuality) + { + // 根据质量百分比设置质量等级 + QualityCategory qualityCategory = qualityPercent >= 0.99f ? QualityCategory.Legendary : + qualityPercent >= 0.75f ? QualityCategory.Masterwork : + qualityPercent >= 0.6f ? QualityCategory.Excellent : + qualityPercent >= 0.45f ? QualityCategory.Good : + qualityPercent >= 0.3f ? QualityCategory.Normal : QualityCategory.Poor; + + compQuality.SetQuality(qualityCategory, ArtGenerationContext.Outsider); + } + + // 设置生命值百分比 + if (qualityPercent < 1.0f) + { + float healthFactor = Mathf.Lerp(0.5f, 1.0f, qualityPercent); + thing.HitPoints = Mathf.RoundToInt(thing.MaxHitPoints * healthFactor); + } + } + + // 获取剩余时间 + public float GetRemainingTicks() + { + if (!isIncubating || incubationDuration <= incubationProgress) return 0f; + + float remainingProgress = incubationDuration - incubationProgress; + float currentSpeed = SpeedMultiplier; + + if (currentSpeed <= 0) return float.MaxValue; + + return remainingProgress / currentSpeed; + } + + public float GetRemainingDays() + { + return GetRemainingTicks() / 60000f; + } + + public float GetRemainingHours() + { + float remainingTicks = GetRemainingTicks(); + return (remainingTicks % 60000f) / 2500f; + } + + // 检查字符串 + public override string GetInspectString() + { + var baseString = base.GetInspectString(); + var builder = new StringBuilder(); + + if (!string.IsNullOrEmpty(baseString)) + { + builder.Append(baseString); + } + + if (isIncubating && incubatingThingDef != null) + { + float progressPercent = AdjustedProgressPercent; + float daysRemaining = GetRemainingDays(); + float hoursRemaining = GetRemainingHours(); + + if (builder.Length > 0) builder.AppendLine(); + builder.Append("ARA_EquipmentIncubator.Incubating".Translate() + ": " + incubatingThingDef.LabelCap); + builder.AppendLine(); + builder.Append("ARA_EquipmentIncubator.Progress".Translate() + ": " + progressPercent.ToStringPercent()); + builder.AppendLine(); + + string timeText = "ARA_EquipmentIncubator.TimeRemaining".Translate() + ": " + daysRemaining.ToString("F1") + " " + "ARA_EquipmentIncubator.Days".Translate(); + if (hoursRemaining > 0.1f && daysRemaining < 1f) + { + timeText += " (" + hoursRemaining.ToString("F1") + " " + "ARA_EquipmentIncubator.Hours".Translate() + ")"; + } + builder.Append(timeText); + + builder.AppendLine(); + builder.Append("ARA_EquipmentIncubator.Speed".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " + + "ARA_EquipmentIncubator.Quality".Translate() + ": " + QualityMultiplier.ToStringPercent()); + } + else if (assignedLarva != null) + { + if (builder.Length > 0) builder.AppendLine(); + if (larvaOperateTicksRemaining > 0) + { + float secondsRemaining = larvaOperateTicksRemaining / 60f; + builder.Append("ARA_EquipmentIncubator.LarvaOperating".Translate() + ": " + secondsRemaining.ToString("F1") + " " + "ARA_EquipmentIncubator.SRemaining".Translate()); + } + else + { + builder.Append("ARA_EquipmentIncubator.LarvaOnWay".Translate()); + } + } + else if (!isIncubating) + { + var config = EquipmentIncubatorData?.SelectedConfig; + if (config != null) + { + if (builder.Length > 0) builder.AppendLine(); + builder.Append("ARA_EquipmentIncubator.Target".Translate() + ": " + config.thingDef.LabelCap); + + builder.AppendLine(); + builder.Append("ARA_EquipmentIncubator.SpeedMultiplier".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " + + "ARA_EquipmentIncubator.QualityMultiplier".Translate() + ": " + QualityMultiplier.ToStringPercent()); + } + } + + return builder.ToString().TrimEndNewlines(); + } + + // Gizmos + public override IEnumerable GetGizmos() + { + foreach (var gizmo in base.GetGizmos()) + { + yield return gizmo; + } + + if (Faction == Faction.OfPlayer) + { + if (!isIncubating && EquipmentIncubatorData?.IncubationConfigs?.Count > 0) + { + yield return CreateTargetSwitchGizmo(); + } + + var config = EquipmentIncubatorData?.SelectedConfig; + if (!isIncubating && config != null && config.IsResearchComplete) + { + yield return new Command_Action + { + defaultLabel = "ARA_EquipmentIncubator.CallLarva".Translate(), + defaultDesc = BuildCallLarvaDescription(config), + icon = ContentFinder.Get("ArachnaeSwarm/UI/Commands/ARA_CallLarva", false) ?? BaseContent.BadTex, + action = CallLarva, + hotKey = KeyBindingDefOf.Misc3 + }; + } + + if (isIncubating) + { + yield return new Command_Action + { + defaultLabel = "ARA_EquipmentIncubator.CancelIncubation".Translate(), + defaultDesc = "ARA_EquipmentIncubator.CancelIncubationDesc".Translate(), + icon = ContentFinder.Get("UI/Commands/Cancel", false) ?? TexCommand.ClearPrioritizedWork, + action = CancelIncubation + }; + } + } + } + + // 创建切换目标Gizmo - 现在使用装备的图标 + private Gizmo CreateTargetSwitchGizmo() + { + var configs = EquipmentIncubatorData?.IncubationConfigs; + if (configs == null || configs.Count == 0) return null; + + var props = EquipmentIncubatorData?.props as CompProperties_EquipmentIncubatorData; + var selectedConfig = EquipmentIncubatorData?.SelectedConfig; + + var switchButton = new Command_Action + { + defaultLabel = BuildSwitchButtonLabel(selectedConfig, props), + defaultDesc = BuildSwitchButtonDescription(selectedConfig, props), + icon = GetConfigIcon(selectedConfig), + action = ShowSelectionMenu, + hotKey = KeyBindingDefOf.Misc2 + }; + + if (selectedConfig != null && !selectedConfig.IsResearchComplete) + { + if (selectedConfig.requiredResearch != null) + { + switchButton.Disable($"Requires research: {selectedConfig.requiredResearch.LabelCap}"); + } + } + + return switchButton; + } + + // 获取配置图标 - 现在直接从ThingDef获取 + private Texture2D GetConfigIcon(EquipmentIncubationConfig config) + { + if (config == null) + return BaseContent.BadTex; + + // 如果配置中没有缓存图标,尝试直接获取ThingDef的uiIcon + if (config.thingDef?.uiIcon != null) + return config.thingDef.uiIcon; + + // 回退到默认图标 + return ContentFinder.Get("UI/Commands/Default", false) ?? BaseContent.BadTex; + } + + private string BuildSwitchButtonLabel(EquipmentIncubationConfig config, CompProperties_EquipmentIncubatorData props) + { + if (config != null && config.thingDef != null) + { + return (props?.buttonLabel ?? "ARA_EquipmentIncubator.IncubateLabel").Translate(config.thingDef.LabelCap); + } + return (props?.buttonLabel ?? "ARA_EquipmentIncubator.IncubateLabel").Translate("None"); + } + + private string BuildSwitchButtonDescription(EquipmentIncubationConfig config, CompProperties_EquipmentIncubatorData props) + { + var builder = new StringBuilder(); + + builder.AppendLine((props?.buttonDesc ?? "ARA_EquipmentIncubator.ButtonDesc").Translate()); + builder.AppendLine(); + + if (config != null) + { + if (config.thingDef != null) + { + builder.AppendLine($"ARA_EquipmentIncubator.ButtonLabel".Translate(config.thingDef.LabelCap)); + if (!string.IsNullOrEmpty(config.thingDef.description)) + { + builder.AppendLine(config.thingDef.description); + } + } + + builder.AppendLine($"ARA_EquipmentIncubator.IncubationTime".Translate(config.DaysRequired)); + + if (config.requiredResearch != null) + { + if (config.requiredResearch.IsFinished) + builder.AppendLine($"Research: {config.requiredResearch.LabelCap} (Completed)"); + else + builder.AppendLine($"Research: {config.requiredResearch.LabelCap} (Required)"); + } + } + + builder.AppendLine(); + builder.AppendLine("ARA_EquipmentIncubator.ButtonDesc".Translate()); + + return builder.ToString().TrimEndNewlines(); + } + + private void ShowSelectionMenu() + { + var configs = EquipmentIncubatorData?.IncubationConfigs; + var props = EquipmentIncubatorData?.props as CompProperties_EquipmentIncubatorData; + if (configs == null || configs.Count == 0) return; + + var options = new List(); + int currentIndex = EquipmentIncubatorData.GetSelectedIndex(); + + for (int i = 0; i < configs.Count; i++) + { + int index = i; + var config = configs[i]; + if (config == null || config.thingDef == null) continue; + + string label = config.thingDef.LabelCap; + string description = config.GetDescription(); + + string prefix = (i == currentIndex) ? "✓ " : " "; + + // 使用原版FloatMenuOption的构造函数,直接传入图标 + FloatMenuOption option; + + // 尝试获取ThingDef的图标 + Texture2D icon = config.thingDef.uiIcon; + + if (icon != null) + { + // 使用带有Texture2D图标的构造函数 + option = new FloatMenuOption( + prefix + label, + () => SwitchToConfig(index), + icon, + Color.white, + MenuOptionPriority.Default, + null, // mouseoverGuiAction + null, // revalidateClickTarget + 0f, // extraPartWidth + null, // extraPartOnGUI + null, // revalidateWorldClickTarget + true, // playSelectionSound + 0, // orderInPriority + HorizontalJustification.Left, // iconJustification + false // extraPartRightJustified + ); + } + else + { + // 如果没有图标,使用普通构造函数 + option = new FloatMenuOption( + prefix + label, + () => SwitchToConfig(index) + ); + } + + // 设置工具提示 + option.tooltip = description; + + // 如果研究未完成,禁用选项 + if (!config.IsResearchComplete) + { + option.Label = prefix + label; + option.Disabled = true; + option.tooltip = description + "\n\n " + "ARA_EquipmentIncubator.ResearchRequired".Translate() + " " + config.requiredResearch.LabelCap; + } + + options.Add(option); + } + + if (options.Count > 0) + { + Find.WindowStack.Add(new FloatMenu(options, + (props?.menuTitle ?? "ARA_EquipmentIncubator.MenuTitle").Translate())); + } + } + + private void SwitchToConfig(int index) + { + if (EquipmentIncubatorData != null) + { + EquipmentIncubatorData.SwitchToConfig(index); + var config = EquipmentIncubatorData.SelectedConfig; + if (config != null && config.thingDef != null) + { + Messages.Message($"ARA_EquipmentIncubator.TargetSwitched".Translate(config.thingDef.LabelCap), + this, MessageTypeDefOf.SilentInput); + } + } + } + + // 查找幼虫 + private Pawn FindLarva() + { + var map = Map; + if (map == null) return null; + + float searchRadius = Ext.larvaSearchRadius; + + foreach (var pawn in map.mapPawns.SpawnedPawnsInFaction(Faction)) + { + if (pawn.def.defName == "ArachnaeBase_Race_Larva") + { + if (searchRadius < 999f) + { + float distance = pawn.Position.DistanceTo(Position); + if (distance > searchRadius) + continue; + } + + if (!pawn.Downed && !pawn.InMentalState && + pawn.mindState != null && + (pawn.CurJob == null || pawn.CurJob.def != ARA_JobDefOf.ARA_OperateEquipmentIncubator)) + { + return pawn; + } + } + } + + return null; + } + + // 每tick更新 + protected override void Tick() + { + base.Tick(); + + if (larvaOperateTicksRemaining > 0) + { + larvaOperateTicksRemaining--; + } + + if (isIncubating) + { + if (lastMultiplierUpdateTick < 0 || Find.TickManager.TicksGame - lastMultiplierUpdateTick >= MultiplierUpdateInterval) + { + UpdateSpeedMultiplier(); + UpdateQualityMultiplier(); + } + + float currentSpeed = SpeedMultiplier; + incubationProgress += currentSpeed; + + qualityProgress += currentSpeed * QualityMultiplier; + + if (incubationProgress >= incubationDuration) + { + CompleteIncubation(); + } + } + } + + // 检查是否在孵化间中 + private bool IsInIncubatorRoom() + { + if (!Ext.requiresIncubatorRoom) + return true; + + var room = this.GetRoom(); + if (room == null) return false; + + return room.Role != null && room.Role.defName == "ARA_Incubator_Room"; + } + + // 计算营养液数量 + private int CountNearbyNutrientSolutions() + { + var map = Map; + if (map == null) return 0; + + int count = 0; + int radius = Ext.NutrientSolutionRadiusInt; + + for (int x = -radius; x <= radius; x++) + { + for (int y = -radius; y <= radius; y++) + { + IntVec3 cell = Position + new IntVec3(x, 0, y); + + if (cell.InBounds(map)) + { + TerrainDef terrain = map.terrainGrid.TerrainAt(cell); + if (terrain != null && terrain.defName == "ARA_Incubator_Nutrient_Solution") + { + count++; + } + } + } + } + + return count; + } + + // 计算房间质量因子 + private float GetRoomQualityFactor() + { + if (!Ext.useRoomQualityFactor) + return 1.0f; + + var room = this.GetRoom(); + if (room == null) return 1.0f; + + var statDef = DefDatabase.GetNamedSilentFail("ARA_IncubatorQualityFactor"); + if (statDef != null) + { + return room.GetStat(statDef); + } + + return 1.0f; + } + + // 计算附近其他卵的数量 + private int CountNearbyOtherOothecas() + { + var map = Map; + if (map == null) return 0; + + int count = 0; + var allBuildings = map.listerThings.ThingsOfDef(this.def); + + foreach (var building in allBuildings) + { + if (building == this) continue; + + if (building.def.defName == "ARA_Pawn_Ootheca" || building.def.defName == "ARA_Equipment_Ootheca") + { + bool isNearby = false; + + if (Ext.checkSameRoomForOotheca) + { + var room = building.GetRoom(); + if (room != null && room == this.GetRoom()) + { + isNearby = true; + } + } + + if (!isNearby) + { + float distance = building.Position.DistanceTo(this.Position); + if (distance <= Ext.nearbyOothecaRadius) + { + isNearby = true; + } + } + + if (isNearby) + { + count++; + } + } + } + + return count; + } + + // 更新速度乘数 + private void UpdateSpeedMultiplier() + { + float multiplier = 1.0f; + + if (Ext.requiresIncubatorRoom && !IsInIncubatorRoom()) + { + multiplier *= Ext.speedPenaltyOutsideIncubator; + } + + int nutrientSolutionCount = CountNearbyNutrientSolutions(); + float nutrientBonus = 1.0f + (nutrientSolutionCount * Ext.nutrientSolutionBonusPerTile); + + multiplier *= nutrientBonus; + + speedMultiplier = multiplier; + lastMultiplierUpdateTick = Find.TickManager.TicksGame; + } + + // 更新质量乘数 + private void UpdateQualityMultiplier() + { + float multiplier = 1.0f; + + if (Ext.healthAffectsQuality) + { + float healthPercent = (float)HitPoints / MaxHitPoints; + multiplier *= healthPercent; + } + + if (Ext.useRoomQualityFactor) + { + float roomFactor = GetRoomQualityFactor(); + multiplier *= roomFactor; + } + + int nearbyOothecaCount = CountNearbyOtherOothecas(); + float oothecaPenalty = Mathf.Max(0f, 1.0f - (nearbyOothecaCount * Ext.nearbyOothecaPenaltyPerUnit)); + multiplier *= oothecaPenalty; + + qualityMultiplier = Mathf.Clamp(multiplier, 0f, 1.0f); + } + + // 保存/加载 + public override void ExposeData() + { + base.ExposeData(); + + Scribe_Values.Look(ref isIncubating, "isIncubating", false); + Scribe_Values.Look(ref incubationProgress, "incubationProgress", 0f); + Scribe_Values.Look(ref incubationDuration, "incubationDuration", 0f); + Scribe_Defs.Look(ref incubatingThingDef, "incubatingThingDef"); + Scribe_References.Look(ref assignedLarva, "assignedLarva"); + Scribe_Values.Look(ref larvaOperateTicksRemaining, "larvaOperateTicksRemaining", 0); + Scribe_Values.Look(ref speedMultiplier, "speedMultiplier", 1.0f); + Scribe_Values.Look(ref lastMultiplierUpdateTick, "lastMultiplierUpdateTick", -1); + Scribe_Values.Look(ref qualityMultiplier, "qualityMultiplier", 1.0f); + Scribe_Values.Look(ref qualityProgress, "qualityProgress", 0f); + Scribe_Values.Look(ref qualityTotal, "qualityTotal", 0f); + } + } +} diff --git a/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/CompProperties_EquipmentIncubatorData.cs b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/CompProperties_EquipmentIncubatorData.cs new file mode 100644 index 0000000..8be9e6a --- /dev/null +++ b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/CompProperties_EquipmentIncubatorData.cs @@ -0,0 +1,332 @@ +// File: Comps/CompProperties_EquipmentIncubatorData.cs +using RimWorld; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + // 装备孵化配置 + public class EquipmentIncubationConfig : IExposable + { + public ThingDef thingDef; + public ResearchProjectDef requiredResearch; + public float daysRequired; // 从stat中读取 + public string buttonIconPath; + + // 缓存的生产时间(避免重复获取stat) + private float? cachedDaysRequired; + + public EquipmentIncubationConfig() { } + + public EquipmentIncubationConfig(ThingDef thingDef, ResearchProjectDef requiredResearch = null, + string buttonIconPath = null) + { + this.thingDef = thingDef; + this.requiredResearch = requiredResearch; + this.buttonIconPath = buttonIconPath; + cachedDaysRequired = null; + } + + public void ExposeData() + { + Scribe_Defs.Look(ref thingDef, "thingDef"); + Scribe_Defs.Look(ref requiredResearch, "requiredResearch"); + Scribe_Values.Look(ref buttonIconPath, "buttonIconPath"); + + // 不保存缓存值,重新计算 + if (Scribe.mode == LoadSaveMode.LoadingVars) + { + cachedDaysRequired = null; + } + } + + // 获取生产时间(天) + public float DaysRequired + { + get + { + if (cachedDaysRequired.HasValue) + return cachedDaysRequired.Value; + + if (thingDef == null) + { + cachedDaysRequired = 1f; + return cachedDaysRequired.Value; + } + + // 从stat中读取ARA_IncubationTime + var statDef = DefDatabase.GetNamedSilentFail("ARA_IncubationTime"); + if (statDef != null) + { + cachedDaysRequired = thingDef.GetStatValueAbstract(statDef, null); + } + else + { + // 默认值 + cachedDaysRequired = 1f; + } + + return cachedDaysRequired.Value; + } + } + + // 检查是否满足研究要求 + public bool IsResearchComplete => requiredResearch == null || requiredResearch.IsFinished; + + // 获取描述 + public string GetDescription() + { + var builder = new StringBuilder(); + + if (thingDef != null) + { + builder.AppendLine(thingDef.description ?? "ARA_EquipmentIncubator.NoDescription".Translate()); + builder.AppendLine(); + builder.AppendLine("ARA_EquipmentIncubator.IncubationTime".Translate(DaysRequired)); + } + + if (requiredResearch != null) + { + if (requiredResearch.IsFinished) + builder.AppendLine("ARA_EquipmentIncubator.ResearchCompleted".Translate(requiredResearch.LabelCap)); + else + builder.AppendLine("ARA_EquipmentIncubator.ResearchRequired".Translate(requiredResearch.LabelCap)); + } + + return builder.ToString().TrimEndNewlines(); + } + } + + // 装备孵化器组件属性 + public class CompProperties_EquipmentIncubatorData : CompProperties + { + // 支持手动指定配置列表(可选) + public List incubationConfigs; + + // 默认选择索引 + public int defaultIndex = 0; + + // Gizmo相关配置 + public string buttonLabel = "ARA_EquipmentIncubator.IncubateLabel"; + public string buttonDesc = "ARA_EquipmentIncubator.ButtonDesc"; + public string menuTitle = "ARA_EquipmentIncubator.MenuTitle"; + public string defaultIconPath = "UI/Commands/Default"; + + // 是否自动扫描所有ThingDef来构建配置列表 + public bool autoScanThingDefs = true; + + // 手动指定要扫描的ThingDef类型(可选) + public List thingDefCategories; + public List thingCategoryDefs; + + public CompProperties_EquipmentIncubatorData() + { + compClass = typeof(CompEquipmentIncubatorData); + } + } + + // 装备孵化器数据组件 + public class CompEquipmentIncubatorData : ThingComp + { + private CompProperties_EquipmentIncubatorData Props => (CompProperties_EquipmentIncubatorData)props; + + // 当前选择的配置索引 + private int selectedIndex = -1; + + // 缓存的配置列表 + private List cachedConfigs; + private bool configsBuilt = false; + + // 公开获取孵化配置列表的方法 + public List IncubationConfigs + { + get + { + if (!configsBuilt) + { + BuildIncubationConfigs(); + } + return cachedConfigs ?? new List(); + } + } + + // 获取当前选择的配置 + public EquipmentIncubationConfig SelectedConfig + { + get + { + var configs = IncubationConfigs; + if (configs.Count == 0) return null; + + // 初始化选择 + if (selectedIndex == -1) + { + selectedIndex = Mathf.Clamp(Props.defaultIndex, 0, configs.Count - 1); + } + + if (selectedIndex < 0 || selectedIndex >= configs.Count) + selectedIndex = 0; + + return configs[selectedIndex]; + } + } + + // 获取当前选择的ThingDef + public ThingDef SelectedThingDef => SelectedConfig?.thingDef; + + // 构建孵化配置列表 + private void BuildIncubationConfigs() + { + cachedConfigs = new List(); + configsBuilt = true; + + // 优先使用手动配置的列表 + if (Props.incubationConfigs != null && Props.incubationConfigs.Count > 0) + { + foreach (var config in Props.incubationConfigs) + { + if (config?.thingDef != null) + { + cachedConfigs.Add(config); + } + } + + if (cachedConfigs.Count > 0) + return; + } + + // 如果没有手动配置,且启用了自动扫描,则扫描所有ThingDef + if (Props.autoScanThingDefs) + { + ScanThingDefsForConfigs(); + } + } + + // 扫描所有ThingDef来构建配置列表 + private void ScanThingDefsForConfigs() + { + var allThingDefs = DefDatabase.AllDefsListForReading; + + foreach (var thingDef in allThingDefs) + { + // 检查该ThingDef是否包含CompProperties_ExtraIncubationInfo组件 + var extraInfoProps = thingDef.comps?.FirstOrDefault(c => c is CompProperties_ExtraIncubationInfo) + as CompProperties_ExtraIncubationInfo; + + if (extraInfoProps == null) + continue; + + // 检查cocoonDefs是否包含当前建筑 + bool isForThisCocoon = false; + + if (extraInfoProps.cocoonDefs != null && extraInfoProps.cocoonDefs.Count > 0) + { + isForThisCocoon = extraInfoProps.cocoonDefs.Contains(parent.def); + } + else if (extraInfoProps.cocoonDef != null) + { + // 向后兼容:检查单个cocoonDef + isForThisCocoon = extraInfoProps.cocoonDef == parent.def; + } + + if (!isForThisCocoon) + continue; + + // 检查是否有ARA_IncubationTime这个stat + var incubationTimeStat = DefDatabase.GetNamedSilentFail("ARA_IncubationTime"); + if (incubationTimeStat == null) + { + Log.Warning($"ThingDef {thingDef.defName} has CompProperties_ExtraIncubationInfo but ARA_IncubationTime stat is not defined."); + continue; + } + + // 获取孵化时间 + float daysRequired = thingDef.GetStatValueAbstract(incubationTimeStat, null); + if (daysRequired <= 0) + { + Log.Warning($"ThingDef {thingDef.defName} has invalid incubation time: {daysRequired}"); + continue; + } + + // 创建配置 + var config = new EquipmentIncubationConfig + { + thingDef = thingDef, + daysRequired = daysRequired, + // 可以在这里添加其他配置,比如所需研究等 + // requiredResearch = ... + // buttonIconPath = ... + }; + + cachedConfigs.Add(config); + } + + // 按物品名称排序 + cachedConfigs.Sort((a, b) => string.Compare(a.thingDef?.label ?? "", b.thingDef?.label ?? "")); + + Log.Message($"Built {cachedConfigs.Count} equipment incubation configs for {parent.def.defName}"); + } + + // 切换到特定索引 + public void SwitchToConfig(int index) + { + if (index >= 0 && index < IncubationConfigs.Count) + { + selectedIndex = index; + } + } + + // 检查配置是否可用(研究是否完成) + public bool IsConfigAvailable(int index) + { + if (index < 0 || index >= IncubationConfigs.Count) + return false; + + var config = IncubationConfigs[index]; + return config?.IsResearchComplete ?? false; + } + + // 获取配置索引 + public int GetSelectedIndex() + { + return selectedIndex; + } + + // 存档加载 + public override void PostExposeData() + { + base.PostExposeData(); + Scribe_Values.Look(ref selectedIndex, "selectedIndex", -1); + Scribe_Values.Look(ref configsBuilt, "configsBuilt", false); + + if (Scribe.mode == LoadSaveMode.LoadingVars) + { + // 重置缓存,在需要时重新构建 + cachedConfigs = null; + configsBuilt = false; + } + } + + // 在建筑信息中显示额外信息 + public override string CompInspectStringExtra() + { + var current = SelectedConfig; + if (current != null && current.thingDef != null) + { + string status = "ARA_EquipmentIncubator.IncubationTarget".Translate(current.thingDef.LabelCap); + + if (current.requiredResearch != null && !current.requiredResearch.IsFinished) + { + status += " (" + "ARA_EquipmentIncubator.Requires".Translate() + ": " + current.requiredResearch.LabelCap + ")"; + } + + return status; + } + + return base.CompInspectStringExtra(); + } + } +} diff --git a/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/ITab_EquipmentOotheca_Incubation.cs b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/ITab_EquipmentOotheca_Incubation.cs new file mode 100644 index 0000000..2c6573a --- /dev/null +++ b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/ITab_EquipmentOotheca_Incubation.cs @@ -0,0 +1,237 @@ +// File: ITabs/ITab_EquipmentOotheca_Incubation.cs +using UnityEngine; +using Verse; +using System.Collections.Generic; +using RimWorld; +using Verse.Sound; + +namespace ArachnaeSwarm +{ + public class ITab_EquipmentOotheca_Incubation : ITab + { + private const float BarHeight = 20f; + private const float Margin = 10f; + private const float LabelHeight = 30f; + private const float SmallLabelHeight = 20f; + private const float ButtonHeight = 25f; + private const float TabWidth = 320f; + private const float TabHeight = 420f; + + private Vector2 scrollPosition = Vector2.zero; + private const float ViewHeight = 450f; + + public override bool IsVisible + { + get + { + return SelThing.Faction == Faction.OfPlayer; + } + } + + public ITab_EquipmentOotheca_Incubation() + { + size = new Vector2(TabWidth, TabHeight); + labelKey = "ARA_EquipmentIncubator.IncubationTab"; + tutorTag = "EquipmentIncubation"; + } + + protected override void FillTab() + { + Rect rect = new Rect(0f, 0f, size.x, size.y).ContractedBy(Margin); + Widgets.DrawMenuSection(rect); + Building_EquipmentOotheca ootheca = SelThing as Building_EquipmentOotheca; + if (ootheca == null) + { + Widgets.Label(rect, "ARA_EquipmentIncubator.NotAnEquipmentOotheca".Translate()); + return; + } + rect = rect.ContractedBy(5f); + + Rect viewRect = new Rect(0f, 0f, rect.width - 16f, ViewHeight); + Rect scrollRect = new Rect(rect.x, rect.y, rect.width, rect.height); + + Widgets.BeginScrollView(scrollRect, ref scrollPosition, viewRect); + + float curY = 0f; + + Rect titleRect = new Rect(0f, curY, viewRect.width, LabelHeight); + string title = "ARA_EquipmentIncubator.IncubationProgress".Translate(); + Text.Font = GameFont.Medium; + Widgets.Label(titleRect, title); + Text.Font = GameFont.Small; + curY += LabelHeight + 15f; + + float buttonWidth = (viewRect.width - 10f) / 2f; + + Rect speedButtonRect = new Rect(0f, curY, buttonWidth, ButtonHeight); + string speedText = "ARA_EquipmentIncubator.Speed".Translate() + ": " + ootheca.SpeedMultiplier.ToStringPercent(); + + Color speedColor = Color.white; + if (ootheca.SpeedMultiplier != 1.0f) + { + speedColor = ootheca.SpeedMultiplier > 1.0f ? + new Color(0.2f, 0.8f, 0.2f) : + new Color(0.8f, 0.8f, 0.2f); + } + + if (Widgets.ButtonText(speedButtonRect, speedText, true, true, speedColor)) + { + // 可选:显示详细信息 + } + + TooltipHandler.TipRegion(speedButtonRect, () => ootheca.GetSpeedFactorsDescription(), 987654321); + + Rect qualityButtonRect = new Rect(buttonWidth + 10f, curY, buttonWidth, ButtonHeight); + string qualityText = "ARA_EquipmentIncubator.Quality".Translate() + ": " + ootheca.QualityMultiplier.ToStringPercent(); + + Color qualityColor = Color.white; + float qualityMultiplier = ootheca.QualityMultiplier; + if (qualityMultiplier != 1.0f) + { + if (qualityMultiplier > 0.9f) + qualityColor = new Color(0.2f, 0.8f, 0.2f); + else if (qualityMultiplier > 0.7f) + qualityColor = new Color(0.8f, 0.8f, 0.2f); + else if (qualityMultiplier > 0.5f) + qualityColor = new Color(0.9f, 0.6f, 0.2f); + else + qualityColor = new Color(0.8f, 0.2f, 0.2f); + } + + if (Widgets.ButtonText(qualityButtonRect, qualityText, true, true, qualityColor)) + { + // 可选:显示详细信息 + } + + TooltipHandler.TipRegion(qualityButtonRect, () => ootheca.GetQualityFactorsDescription(), 987654322); + + curY += ButtonHeight + 25f; + + if (ootheca.isIncubating && ootheca.incubatingThingDef != null) + { + float progressPercent = ootheca.AdjustedProgressPercent; + float qualityPercent = ootheca.QualityPercent; + float daysRemaining = ootheca.GetRemainingDays(); + float hoursRemaining = ootheca.GetRemainingHours(); + + Rect targetRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight); + Widgets.Label(targetRect, "ARA_EquipmentIncubator.Target".Translate() + ": " + ootheca.incubatingThingDef.LabelCap); + curY += SmallLabelHeight + 20f; + + Rect progressBarRect = new Rect(0f, curY, viewRect.width, BarHeight); + Rect progressLabelRect = new Rect(progressBarRect.x, progressBarRect.y - 20, progressBarRect.width, 18); + Text.Anchor = TextAnchor.MiddleCenter; + GUI.color = new Color(0.9f, 0.9f, 0.9f, 1f); + Widgets.Label(progressLabelRect, "ARA_EquipmentIncubator.IncubationProgressLabel".Translate()); + Text.Anchor = TextAnchor.UpperLeft; + GUI.color = Color.white; + + Widgets.FillableBar(progressBarRect, progressPercent, SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.8f, 0.2f, 0.5f))); + Widgets.FillableBarChangeArrows(progressBarRect, progressPercent); + + string progressText = $"{progressPercent:P0}"; + Text.Anchor = TextAnchor.MiddleCenter; + Widgets.Label(progressBarRect, progressText); + Text.Anchor = TextAnchor.UpperLeft; + + curY += BarHeight + 30f; + + Rect qualityBarRect = new Rect(0f, curY, viewRect.width, BarHeight); + Rect qualityLabelRect = new Rect(qualityBarRect.x, qualityBarRect.y - 20, qualityBarRect.width, 18); + Text.Anchor = TextAnchor.MiddleCenter; + GUI.color = new Color(0.9f, 0.9f, 0.9f, 1f); + Widgets.Label(qualityLabelRect, "ARA_EquipmentIncubator.QualityProgress".Translate()); + Text.Anchor = TextAnchor.UpperLeft; + GUI.color = Color.white; + + Widgets.FillableBar(qualityBarRect, qualityPercent, SolidColorMaterials.NewSolidColorTexture(new Color(0.1f, 0.4f, 0.8f, 0.5f))); + + string qualityProgressText = $"{qualityPercent:P0}"; + Text.Anchor = TextAnchor.MiddleCenter; + Widgets.Label(qualityBarRect, qualityProgressText); + Text.Anchor = TextAnchor.UpperLeft; + + Rect targetQualityRect = new Rect(qualityBarRect.x + qualityBarRect.width - 40, qualityBarRect.y, 40, BarHeight); + GUI.color = new Color(0.8f, 0.8f, 0.8f, 0.7f); + Text.Anchor = TextAnchor.MiddleRight; + Widgets.Label(targetQualityRect, $"{ootheca.QualityMultiplier:P0}"); + Text.Anchor = TextAnchor.UpperLeft; + GUI.color = Color.white; + + curY += BarHeight + 25f; + + Rect timeRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight); + string timeText = "ARA_EquipmentIncubator.TimeRemaining".Translate() + ": " + daysRemaining.ToString("F1") + " " + "ARA_EquipmentIncubator.Days".Translate(); + if (hoursRemaining > 0.1f && daysRemaining < 1f) + { + timeText += " (" + hoursRemaining.ToString("F1") + " " + "ARA_EquipmentIncubator.Hours".Translate() + ")"; + } + Widgets.Label(timeRect, timeText); + } + else if (ootheca.assignedLarva != null) + { + Rect statusRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight * 2); + if (ootheca.larvaOperateTicksRemaining > 0) + { + float secondsRemaining = ootheca.larvaOperateTicksRemaining / 60f; + Widgets.Label(statusRect, "ARA_EquipmentIncubator.LarvaIsActivatingOotheca".Translate() + "\n" + + secondsRemaining.ToString("F1") + " " + "ARA_EquipmentIncubator.SecondsRemaining".Translate()); + } + else + { + Widgets.Label(statusRect, "ARA_EquipmentIncubator.LarvaIsOnTheWay".Translate()); + } + + curY += SmallLabelHeight * 2 + 15f; + + var config = ootheca.EquipmentIncubatorData?.SelectedConfig; + if (config != null) + { + curY += 10f; + Rect targetRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight * 3); + string targetText = "ARA_EquipmentIncubator.ReadyToIncubate".Translate() + "\n" + config.thingDef.LabelCap; + + if (!config.IsResearchComplete && config.requiredResearch != null) + { + targetText += "\n" + "(" + "ARA_EquipmentIncubator.Requires".Translate() + ": " + config.requiredResearch.LabelCap + ")"; + } + + Widgets.Label(targetRect, targetText); + } + } + else + { + var config = ootheca.EquipmentIncubatorData?.SelectedConfig; + if (config != null) + { + Rect targetRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight * 3); + string targetText = "ARA_EquipmentIncubator.ReadyToIncubate".Translate() + "\n" + config.thingDef.LabelCap; + + if (!config.IsResearchComplete && config.requiredResearch != null) + { + targetText += "\n" + "(" + "ARA_EquipmentIncubator.Requires".Translate() + ": " + config.requiredResearch.LabelCap + ")"; + } + + Widgets.Label(targetRect, targetText); + curY += SmallLabelHeight * 3 + 10f; + } + else + { + Rect noTargetRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight); + Widgets.Label(noTargetRect, "ARA_EquipmentIncubator.NoIncubationTargetSelected".Translate()); + curY += SmallLabelHeight + 10f; + } + } + + curY += 20f; + viewRect.height = curY; + + Widgets.EndScrollView(); + } + + protected override void UpdateSize() + { + size = new Vector2(TabWidth, TabHeight); + } + } +} diff --git a/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/JobDriver_OperateEquipmentIncubator.cs b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/JobDriver_OperateEquipmentIncubator.cs new file mode 100644 index 0000000..014676c --- /dev/null +++ b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/JobDriver_OperateEquipmentIncubator.cs @@ -0,0 +1,68 @@ +// File: JobDrivers/JobDriver_OperateEquipmentIncubator.cs +using RimWorld; +using System.Collections.Generic; +using Verse; +using Verse.AI; + +namespace ArachnaeSwarm +{ + public class JobDriver_OperateEquipmentIncubator : JobDriver + { + private const int OperationDuration = 180; + + private Building_EquipmentOotheca EquipmentOotheca => (Building_EquipmentOotheca)job.targetA.Thing; + + public override bool TryMakePreToilReservations(bool errorOnFailed) + { + return pawn.Reserve(job.targetA, job, 1, -1, null, errorOnFailed); + } + + protected override IEnumerable MakeNewToils() + { + this.FailOnDespawnedNullOrForbidden(TargetIndex.A); + this.FailOn(() => EquipmentOotheca == null); + + yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.InteractionCell) + .FailOnSomeonePhysicallyInteracting(TargetIndex.A); + + yield return Toils_General.WaitWith(TargetIndex.A, 10, true, true); + + var operate = new Toil(); + operate.initAction = () => + { + EquipmentOotheca?.NotifyLarvaArrived(pawn); + }; + operate.tickAction = () => + { + pawn.rotationTracker.FaceCell(EquipmentOotheca.Position); + }; + operate.defaultCompleteMode = ToilCompleteMode.Delay; + operate.defaultDuration = OperationDuration; + operate.WithProgressBar(TargetIndex.A, () => + (float)(OperationDuration - operate.actor.jobs.curDriver.ticksLeftThisToil) / OperationDuration); + yield return operate; + + yield return new Toil + { + initAction = () => + { + if (EquipmentOotheca != null && pawn != null && pawn.def.defName == "ArachnaeBase_Race_Larva") + { + EquipmentOotheca.NotifyLarvaOperationComplete(pawn); + pawn.Destroy(DestroyMode.Vanish); + } + }, + defaultCompleteMode = ToilCompleteMode.Instant + }; + } + + public override string GetReport() + { + if (EquipmentOotheca != null) + { + return "ARA_EquipmentIncubator.ActivatingOotheca".Translate(); + } + return base.GetReport(); + } + } +} diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs index 79977fb..4324a4d 100644 --- a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs +++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs @@ -1,4 +1,5 @@ -using RimWorld; +// File: Building_Ootheca.cs +using RimWorld; using System.Collections.Generic; using System.Text; using UnityEngine; @@ -32,6 +33,22 @@ namespace ArachnaeSwarm private float qualityProgress = 0f; private float qualityTotal = 0f; + // 缓存的ModExtension + private OothecaIncubatorExtension cachedExtension; + + // 获取ModExtension的辅助属性 + private OothecaIncubatorExtension Ext + { + get + { + if (cachedExtension == null) + { + cachedExtension = def.GetModExtension() ?? OothecaIncubatorExtension.Default; + } + return cachedExtension; + } + } + // 属性访问器 public float SpeedMultiplier { @@ -65,92 +82,122 @@ namespace ArachnaeSwarm { var builder = new StringBuilder(); - builder.AppendLine("IncubationSpeedFactors".Translate()); + builder.AppendLine("ARA_OothecaIncubator.SpeedFactors".Translate()); builder.AppendLine(); // 1. 检查是否在孵化间中 bool inIncubatorRoom = IsInIncubatorRoom(); - builder.AppendLine(inIncubatorRoom ? - "InIncubatorRoom".Translate() : - "NotInIncubatorRoom".Translate()); + if (Ext.requiresIncubatorRoom) + { + builder.AppendLine(inIncubatorRoom ? + "ARA_OothecaIncubator.InIncubatorRoom".Translate() : + "ARA_OothecaIncubator.NotInIncubatorRoom".Translate()); + } // 2. 检查营养液数量 int nutrientSolutionCount = CountNearbyNutrientSolutions(); if (nutrientSolutionCount > 0) { - builder.AppendLine("NutrientSolutions".Translate(nutrientSolutionCount, nutrientSolutionCount)); + builder.AppendLine("ARA_OothecaIncubator.NutrientSolutions".Translate( + nutrientSolutionCount, + nutrientSolutionCount * Ext.nutrientSolutionBonusPerTile * 100)); } else { - builder.AppendLine("NoNutrientSolutionsNearby".Translate()); + builder.AppendLine("ARA_OothecaIncubator.NoNutrientSolutionsNearby".Translate()); } + + // 显示检测半径 + builder.AppendLine(); + builder.AppendLine("ARA_OothecaIncubator.NutrientDetectionRadius".Translate(Ext.nutrientSolutionRadius)); builder.AppendLine(); - builder.AppendLine("TotalSpeedMultiplier".Translate(SpeedMultiplier.ToStringPercent())); + builder.AppendLine("ARA_OothecaIncubator.TotalSpeedMultiplier".Translate(SpeedMultiplier.ToStringPercent())); return builder.ToString().TrimEndNewlines(); } + // 获取质量乘数的详细因子信息(用于工具提示) public string GetQualityFactorsDescription() { var builder = new StringBuilder(); - builder.AppendLine("IncubationQualityFactors".Translate()); + builder.AppendLine("ARA_OothecaIncubator.QualityFactors".Translate()); builder.AppendLine(); - // 1. 建筑血量损失百分比 - float healthPercent = (float)HitPoints / MaxHitPoints; - builder.AppendLine("BuildingHealth".Translate(healthPercent.ToStringPercent())); + // 1. 建筑血量损失百分比(如果启用) + if (Ext.healthAffectsQuality) + { + float healthPercent = (float)HitPoints / MaxHitPoints; + builder.AppendLine("ARA_OothecaIncubator.BuildingHealth".Translate(healthPercent.ToStringPercent())); + } - // 2. 房间的ARA_IncubatorQualityFactor - float roomFactor = GetRoomQualityFactor(); - builder.AppendLine(roomFactor == 1.0f ? - "RoomFactorNormal".Translate() : - $"{(roomFactor > 1.0f ? "✓" : "✗")} {"RoomFactorModified".Translate()}{roomFactor.ToStringPercent()}"); + // 2. 房间的ARA_IncubatorQualityFactor(如果启用) + if (Ext.useRoomQualityFactor) + { + float roomFactor = GetRoomQualityFactor(); + builder.AppendLine(roomFactor == 1.0f ? + "ARA_OothecaIncubator.RoomFactorNormal".Translate() : + $"{(roomFactor > 1.0f ? "✓" : "✗")} {"ARA_OothecaIncubator.RoomFactorModified".Translate()}{roomFactor.ToStringPercent()}"); + } - // 3. 附近每一个ARA_Pawn_Ootheca,每一个-10% + // 3. 附近每一个ARA_Pawn_Ootheca的惩罚 int nearbyOothecaCount = CountNearbyOtherOothecas(); if (nearbyOothecaCount > 0) { - builder.AppendLine("NearbyOothecas".Translate(nearbyOothecaCount, Mathf.Min(nearbyOothecaCount * 10, 100))); + builder.AppendLine("ARA_OothecaIncubator.NearbyOothecas".Translate( + nearbyOothecaCount, + Mathf.Min(nearbyOothecaCount * Ext.nearbyOothecaPenaltyPerUnit * 100, 100))); } else { - builder.AppendLine("NoNearbyOothecas".Translate()); + builder.AppendLine("ARA_OothecaIncubator.NoNearbyOothecas".Translate()); } + + // 显示检测半径 + builder.AppendLine(); + builder.AppendLine("ARA_OothecaIncubator.OothecaDetectionRadius".Translate(Ext.nearbyOothecaRadius)); builder.AppendLine(); - builder.AppendLine("TotalQualityMultiplier".Translate(QualityMultiplier.ToStringPercent())); + builder.AppendLine("ARA_OothecaIncubator.TotalQualityMultiplier".Translate(QualityMultiplier.ToStringPercent())); return builder.ToString().TrimEndNewlines(); } + // 构建呼叫幼虫描述 private string BuildCallLarvaDescription(IncubationConfig config) { var builder = new StringBuilder(); - builder.AppendLine("CallALarvaToActivate".Translate()); + builder.AppendLine("ARA_OothecaIncubator.CallLarvaTitle".Translate()); builder.AppendLine(); - builder.AppendLine("LarvaWillComeToTheOotheca".Translate()); + builder.AppendLine("ARA_OothecaIncubator.LarvaWillCome".Translate()); builder.AppendLine(config.pawnKind.LabelCap); builder.AppendLine(); + + // 显示幼虫搜索半径 + if (Ext.larvaSearchRadius < 999f) + { + builder.AppendLine("ARA_OothecaIncubator.LarvaSearchRadius".Translate(Ext.larvaSearchRadius)); + } return builder.ToString().TrimEndNewlines(); } + // 呼叫幼虫 private void CallLarva() { // 检查是否已经在孵化中或有幼虫在任务中 if (isIncubating) { - Messages.Message("AlreadyIncubating".Translate() + " " + "CancelCurrentIncubationFirst".Translate(), + Messages.Message("ARA_OothecaIncubator.AlreadyIncubating".Translate() + " " + "ARA_OothecaIncubator.CancelFirst".Translate(), MessageTypeDefOf.RejectInput); return; } if (assignedLarva != null) { - Messages.Message("LarvaAlreadyOnTheWay".Translate(), + Messages.Message("ARA_OothecaIncubator.LarvaAlreadyOnWay".Translate(), MessageTypeDefOf.RejectInput); return; } @@ -159,7 +206,7 @@ namespace ArachnaeSwarm var larva = FindLarva(); if (larva == null) { - Messages.Message("NoAvailableLarvaeFound".Translate() + " " + "LarvaMustBeOfRace".Translate(), + Messages.Message("ARA_OothecaIncubator.NoLarvaeFound".Translate() + " " + "ARA_OothecaIncubator.LarvaMustBeRace".Translate(), MessageTypeDefOf.RejectInput); return; } @@ -171,9 +218,10 @@ namespace ArachnaeSwarm assignedLarva = larva; - Messages.Message("LarvaCalled".Translate() + " " + "ItWillArriveShortly".Translate(), + Messages.Message("ARA_OothecaIncubator.LarvaCalled".Translate() + " " + "ARA_OothecaIncubator.ArriveShortly".Translate(), MessageTypeDefOf.PositiveEvent); } + // 幼虫开始操作 public void NotifyLarvaArrived(Pawn larva) { @@ -189,9 +237,10 @@ namespace ArachnaeSwarm assignedLarva = larva; // 显示消息 - Messages.Message("LarvaHasArrived".Translate() + " " + "AndIsActivatingTheOotheca".Translate(), + Messages.Message("ARA_OothecaIncubator.LarvaArrived".Translate() + " " + "ARA_OothecaIncubator.ActivatingOotheca".Translate(), MessageTypeDefOf.SilentInput); } + // 幼虫完成操作(由JobDriver调用) public void NotifyLarvaOperationComplete(Pawn larva) { @@ -228,10 +277,11 @@ namespace ArachnaeSwarm larvaOperateTicksRemaining = 0; // 显示消息 - Messages.Message("IncubationStartedFor".Translate() + " " + incubatingPawnKind.LabelCap + ". " + - "ProcessWillCompleteIn".Translate() + " " + config.daysRequired + " " + "DaysBaseTime".Translate(), + Messages.Message("ARA_OothecaIncubator.IncubationStarted".Translate() + " " + incubatingPawnKind.LabelCap + ". " + + "ARA_OothecaIncubator.ProcessWillComplete".Translate() + " " + config.daysRequired + " " + "ARA_OothecaIncubator.DaysBaseTime".Translate(), MessageTypeDefOf.PositiveEvent); } + // 取消孵化 private void CancelIncubation() { @@ -244,9 +294,10 @@ namespace ArachnaeSwarm qualityProgress = 0f; qualityTotal = 0f; - Messages.Message("IncubationCancelled".Translate() + " " + "ContentsLost".Translate(), + Messages.Message("ARA_OothecaIncubator.IncubationCancelled".Translate() + " " + "ARA_OothecaIncubator.ContentsLost".Translate(), MessageTypeDefOf.NeutralEvent); } + // 完成孵化 private void CompleteIncubation() { @@ -277,16 +328,19 @@ namespace ArachnaeSwarm qualityTotal = 0f; // 显示消息 - string qualityText = finalQualityPercent >= 0.9f ? "QualityExcellent".Translate() : - finalQualityPercent >= 0.7f ? "QualityGood".Translate() : - finalQualityPercent >= 0.5f ? "QualityAverage".Translate() : - finalQualityPercent >= 0.3f ? "QualityPoor".Translate() : "QualityVeryPoor".Translate(); + string qualityText = finalQualityPercent >= 0.9f ? "ARA_OothecaIncubator.QualityExcellent".Translate() : + finalQualityPercent >= 0.7f ? "ARA_OothecaIncubator.QualityGood".Translate() : + finalQualityPercent >= 0.5f ? "ARA_OothecaIncubator.QualityAverage".Translate() : + finalQualityPercent >= 0.3f ? "ARA_OothecaIncubator.QualityPoor".Translate() : "ARA_OothecaIncubator.QualityVeryPoor".Translate(); - Messages.Message("IncubationComplete".Translate() + " " + pawn.LabelCap + " " + - "HasEmergedWith".Translate() + " " + qualityText + " " + - "quality".Translate() + " (" + finalQualityPercent.ToStringPercent() + ").", + Messages.Message("ARA_OothecaIncubator.IncubationComplete".Translate() + " " + pawn.LabelCap + " " + + "ARA_OothecaIncubator.HasEmergedWith".Translate() + " " + qualityText + " " + + "ARA_OothecaIncubator.Quality".Translate() + " (" + finalQualityPercent.ToStringPercent() + ").", MessageTypeDefOf.PositiveEvent); + + Destroy(); } + // 显示额外信息(简化版本,只显示速率,不显示因子) public override string GetInspectString() { @@ -305,23 +359,23 @@ namespace ArachnaeSwarm float hoursRemaining = GetRemainingHours(); if (builder.Length > 0) builder.AppendLine(); - builder.Append("Incubating".Translate() + ": " + incubatingPawnKind.LabelCap); + builder.Append("ARA_OothecaIncubator.Incubating".Translate() + ": " + incubatingPawnKind.LabelCap); builder.AppendLine(); - builder.Append("Progress".Translate() + ": " + progressPercent.ToStringPercent()); + builder.Append("ARA_OothecaIncubator.Progress".Translate() + ": " + progressPercent.ToStringPercent()); builder.AppendLine(); // 显示剩余时间 - string timeText = "TimeRemaining".Translate() + ": " + daysRemaining.ToString("F1") + " " + "Days".Translate(); + string timeText = "ARA_OothecaIncubator.TimeRemaining".Translate() + ": " + daysRemaining.ToString("F1") + " " + "ARA_OothecaIncubator.Days".Translate(); if (hoursRemaining > 0.1f && daysRemaining < 1f) { - timeText += " (" + hoursRemaining.ToString("F1") + " " + "Hours".Translate() + ")"; + timeText += " (" + hoursRemaining.ToString("F1") + " " + "ARA_OothecaIncubator.Hours".Translate() + ")"; } builder.Append(timeText); // 显示速度和质量(简化版本) builder.AppendLine(); - builder.Append("Speed".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " + - "Quality".Translate() + ": " + QualityMultiplier.ToStringPercent()); + builder.Append("ARA_OothecaIncubator.Speed".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " + + "ARA_OothecaIncubator.Quality".Translate() + ": " + QualityMultiplier.ToStringPercent()); } else if (assignedLarva != null) { @@ -329,11 +383,11 @@ namespace ArachnaeSwarm if (larvaOperateTicksRemaining > 0) { float secondsRemaining = larvaOperateTicksRemaining / 60f; - builder.Append("LarvaIsOperating".Translate() + ": " + secondsRemaining.ToString("F1") + " " + "SRemaining".Translate()); + builder.Append("ARA_OothecaIncubator.LarvaOperating".Translate() + ": " + secondsRemaining.ToString("F1") + " " + "ARA_OothecaIncubator.SRemaining".Translate()); } else { - builder.Append("LarvaIsOnTheWay".Translate()); + builder.Append("ARA_OothecaIncubator.LarvaOnWay".Translate()); } } else if (!isIncubating) @@ -342,12 +396,12 @@ namespace ArachnaeSwarm if (config != null) { if (builder.Length > 0) builder.AppendLine(); - builder.Append("Target".Translate() + ": " + config.pawnKind.LabelCap); + builder.Append("ARA_OothecaIncubator.Target".Translate() + ": " + config.pawnKind.LabelCap); // 只显示当前乘数,不显示条件详情 builder.AppendLine(); - builder.Append("SpeedMultiplier".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " + - "QualityMultiplier".Translate() + ": " + QualityMultiplier.ToStringPercent()); + builder.Append("ARA_OothecaIncubator.SpeedMultiplier".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " + + "ARA_OothecaIncubator.QualityMultiplier".Translate() + ": " + QualityMultiplier.ToStringPercent()); } } @@ -404,9 +458,9 @@ namespace ArachnaeSwarm { yield return new Command_Action { - defaultLabel = "CallLarva".Translate(), + defaultLabel = "ARA_OothecaIncubator.CallLarva".Translate(), defaultDesc = BuildCallLarvaDescription(config), - icon = ContentFinder.Get("UI/Commands/CallLarva", false) ?? BaseContent.BadTex, + icon = ContentFinder.Get("ArachnaeSwarm/UI/Commands/ARA_CallLarva", false) ?? BaseContent.BadTex, action = CallLarva, hotKey = KeyBindingDefOf.Misc3 }; @@ -417,8 +471,8 @@ namespace ArachnaeSwarm { yield return new Command_Action { - defaultLabel = "CancelIncubation".Translate(), - defaultDesc = "CancelIncubationDesc".Translate(), + defaultLabel = "ARA_OothecaIncubator.CancelIncubation".Translate(), + defaultDesc = "ARA_OothecaIncubator.CancelIncubationDesc".Translate(), icon = ContentFinder.Get("UI/Commands/Cancel", false) ?? TexCommand.ClearPrioritizedWork, action = CancelIncubation }; @@ -461,9 +515,9 @@ namespace ArachnaeSwarm { if (config != null && config.pawnKind != null) { - return (props?.buttonLabel ?? "Incubate: {0}").Translate(config.pawnKind.LabelCap); + return (props?.buttonLabel ?? "ARA_OothecaIncubator.IncubateLabel").Translate(config.pawnKind.LabelCap); } - return (props?.buttonLabel ?? "Incubate: {0}").Translate("None"); + return (props?.buttonLabel ?? "ARA_OothecaIncubator.IncubateLabel").Translate("None"); } // 构建切换按钮描述 @@ -472,7 +526,7 @@ namespace ArachnaeSwarm var builder = new StringBuilder(); // 第一部分:按钮功能说明 - builder.AppendLine((props?.buttonDesc ?? "IncubatorButtonDesc").Translate()); + builder.AppendLine((props?.buttonDesc ?? "ARA_OothecaIncubator.ButtonDesc").Translate()); builder.AppendLine(); if (config != null) @@ -480,14 +534,14 @@ namespace ArachnaeSwarm // 第二部分:当前选择的详细信息 if (config.pawnKind != null) { - builder.AppendLine($"IncubatorButtonLabel".Translate(config.pawnKind.LabelCap)); + builder.AppendLine($"ARA_OothecaIncubator.ButtonLabel".Translate(config.pawnKind.LabelCap)); if (!string.IsNullOrEmpty(config.pawnKind.description)) { builder.AppendLine(config.pawnKind.description); } } - builder.AppendLine($"IncubationTime".Translate(config.daysRequired)); + builder.AppendLine($"ARA_OothecaIncubator.IncubationTime".Translate(config.daysRequired)); if (config.requiredResearch != null) { @@ -503,7 +557,7 @@ namespace ArachnaeSwarm } builder.AppendLine(); - builder.AppendLine("IncubatorButtonDesc".Translate()); + builder.AppendLine("ARA_OothecaIncubator.ButtonDesc".Translate()); return builder.ToString().TrimEndNewlines(); } @@ -555,7 +609,7 @@ namespace ArachnaeSwarm { option.Label = prefix + label; option.Disabled = true; - option.tooltip = description + "\n\n "+ "ResearchRequired".Translate() + " " + config.requiredResearch.LabelCap; + option.tooltip = description + "\n\n "+ "ARA_OothecaIncubator.ResearchRequired".Translate() + " " + config.requiredResearch.LabelCap; } options.Add(option); @@ -564,7 +618,7 @@ namespace ArachnaeSwarm if (options.Count > 0) { Find.WindowStack.Add(new FloatMenu(options, - (props?.menuTitle ?? "Select Incubation Target").Translate())); + (props?.menuTitle ?? "ARA_OothecaIncubator.MenuTitle").Translate())); } } @@ -577,24 +631,35 @@ namespace ArachnaeSwarm var config = IncubatorData.SelectedConfig; if (config != null && config.pawnKind != null) { - Messages.Message($"Incubation target switched to: {config.pawnKind.LabelCap}", + Messages.Message($"ARA_OothecaIncubator.TargetSwitched".Translate(config.pawnKind.LabelCap), this, MessageTypeDefOf.SilentInput); } } } - // 查找幼虫 - 现在通过种族查找 + // 查找幼虫 - 现在通过种族查找,并考虑搜索半径 private Pawn FindLarva() { // 查找地图中属于玩家派系的ArachnaeBase_Race_Larva幼虫 var map = Map; if (map == null) return null; + // 获取搜索半径 + float searchRadius = Ext.larvaSearchRadius; + foreach (var pawn in map.mapPawns.SpawnedPawnsInFaction(Faction)) { // 检查pawn种族是否为幼虫 if (pawn.def.defName == "ArachnaeBase_Race_Larva") { + // 检查是否在搜索半径内(如果搜索半径小于999) + if (searchRadius < 999f) + { + float distance = pawn.Position.DistanceTo(Position); + if (distance > searchRadius) + continue; + } + // 检查pawn是否能够移动且没有其他重要任务 if (!pawn.Downed && !pawn.InMentalState && pawn.mindState != null && @@ -647,62 +712,33 @@ namespace ArachnaeSwarm // 应用质量效果到生成的pawn private void ApplyQualityEffects(Pawn pawn, float qualityPercent) { - //// 质量影响:根据质量百分比调整pawn的属性 - //if (qualityPercent < 1.0f) - //{ - // // 1. 健康影响 - // float healthFactor = Mathf.Lerp(0.5f, 1.0f, qualityPercent); - // if (healthFactor < 1.0f) - // { - // // 减少最大生命值 - // foreach (var part in pawn.health.hediffSet.GetNotMissingParts()) - // { - // var healthDiff = part.def.GetMaxHealth(pawn) * (1.0f - healthFactor); - // if (healthDiff > 0) - // { - // pawn.health.AddHediff(HediffDefOf.Bruise, part); - // } - // } - // } - - // // 2. 年龄影响(如果质量低,pawn可能会以较大年龄出生) - // if (qualityPercent < 0.6f) - // { - // float ageBonus = (1.0f - qualityPercent) * 5; // 最多增加5岁 - // pawn.ageTracker.AgeBiologicalTicks += (long)(ageBonus * 3600000f); // 每岁约3600000ticks - // } - - // // 3. 能力影响 - // if (pawn.skills != null && qualityPercent < 0.8f) - // { - // float skillFactor = Mathf.Lerp(0.5f, 1.0f, qualityPercent); - // foreach (var skill in pawn.skills.skills) - // { - // skill.levelInt = Mathf.RoundToInt(skill.levelInt * skillFactor); - // } - // } - //} + // 质量影响:根据质量百分比调整pawn的属性 + // 这里可以添加具体的质量效果逻辑 } // 检查是否在孵化间中 private bool IsInIncubatorRoom() { + // 如果不要求孵化间,总是返回true + if (!Ext.requiresIncubatorRoom) + return true; + var room = this.GetRoom(); if (room == null) return false; return room.Role != null && room.Role.defName == "ARA_Incubator_Room"; } - // 计算周围5格内的营养液数量 + // 计算周围指定半径内的营养液数量 private int CountNearbyNutrientSolutions() { var map = Map; if (map == null) return 0; int count = 0; - int radius = 5; // 4格半径 + int radius = Ext.NutrientSolutionRadiusInt; - // 检查周围5格范围内的所有单元格 + // 检查指定半径范围内的所有单元格 for (int x = -radius; x <= radius; x++) { for (int y = -radius; y <= radius; y++) @@ -728,6 +764,10 @@ namespace ArachnaeSwarm // 计算房间质量因子 private float GetRoomQualityFactor() { + // 如果不使用房间质量因子,返回1.0 + if (!Ext.useRoomQualityFactor) + return 1.0f; + var room = this.GetRoom(); if (room == null) return 1.0f; @@ -759,17 +799,32 @@ namespace ArachnaeSwarm // 检查是否为ARA_Pawn_Ootheca if (building.def.defName == "ARA_Pawn_Ootheca") { - // 检查是否在同一个房间或附近 - var room = building.GetRoom(); - if (room != null) + bool isNearby = false; + + // 检查是否在同房间内 + if (Ext.checkSameRoomForOotheca) { - // 如果在同一个房间,或者距离较近(5格内) - float distance = building.Position.DistanceTo(this.Position); - if (room == this.GetRoom() || distance <= 5f) + var room = building.GetRoom(); + if (room != null && room == this.GetRoom()) { - count++; + isNearby = true; } } + + // 检查是否在指定半径内 + if (!isNearby) + { + float distance = building.Position.DistanceTo(this.Position); + if (distance <= Ext.nearbyOothecaRadius) + { + isNearby = true; + } + } + + if (isNearby) + { + count++; + } } } @@ -781,15 +836,15 @@ namespace ArachnaeSwarm { float multiplier = 1.0f; - // 1. 检查是否在孵化间中 - if (!IsInIncubatorRoom()) + // 1. 检查是否在孵化间中(如果要求) + if (Ext.requiresIncubatorRoom && !IsInIncubatorRoom()) { - multiplier *= 0.8f; // 不在孵化间中,速度80% + multiplier *= Ext.speedPenaltyOutsideIncubator; // 应用惩罚 } // 2. 计算周围营养液的加成 int nutrientSolutionCount = CountNearbyNutrientSolutions(); - float nutrientBonus = 1.0f + (nutrientSolutionCount * 0.01f); // 每个+1% + float nutrientBonus = 1.0f + (nutrientSolutionCount * Ext.nutrientSolutionBonusPerTile); multiplier *= nutrientBonus; @@ -802,17 +857,23 @@ namespace ArachnaeSwarm { float multiplier = 1.0f; - // 1. 建筑血量损失百分比 - float healthPercent = (float)HitPoints / MaxHitPoints; - multiplier *= healthPercent; + // 1. 建筑血量损失百分比(如果启用) + if (Ext.healthAffectsQuality) + { + float healthPercent = (float)HitPoints / MaxHitPoints; + multiplier *= healthPercent; + } - // 2. 房间的ARA_IncubatorQualityFactor - float roomFactor = GetRoomQualityFactor(); - multiplier *= roomFactor; + // 2. 房间的ARA_IncubatorQualityFactor(如果启用) + if (Ext.useRoomQualityFactor) + { + float roomFactor = GetRoomQualityFactor(); + multiplier *= roomFactor; + } - // 3. 附近每一个ARA_Pawn_Ootheca,每一个-10% + // 3. 附近每一个ARA_Pawn_Ootheca的惩罚 int nearbyOothecaCount = CountNearbyOtherOothecas(); - float oothecaPenalty = Mathf.Max(0f, 1.0f - (nearbyOothecaCount * 0.10f)); // 最多减到0 + float oothecaPenalty = Mathf.Max(0f, 1.0f - (nearbyOothecaCount * Ext.nearbyOothecaPenaltyPerUnit)); multiplier *= oothecaPenalty; // 确保乘数在合理范围内(0-1) diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/OothecaIncubatorExtension.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/OothecaIncubatorExtension.cs new file mode 100644 index 0000000..b90dba3 --- /dev/null +++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/OothecaIncubatorExtension.cs @@ -0,0 +1,64 @@ +// File: ModExtensions/OothecaIncubatorExtension.cs +using RimWorld; +using Verse; + +namespace ArachnaeSwarm +{ + public class OothecaIncubatorExtension : DefModExtension + { + // 营养液检测半径(单位:格) + public float nutrientSolutionRadius = 5f; + + // 其他卵距离检测半径(单位:格) + public float nearbyOothecaRadius = 5f; + + // 是否检查同房间内的其他卵(默认:是) + public bool checkSameRoomForOotheca = true; + + // 营养液加成比例(每个营养液增加多少百分比的速度) + public float nutrientSolutionBonusPerTile = 0.01f; // 默认每个+1% + + // 附近其他卵的惩罚比例(每个减少多少百分比的质量) + public float nearbyOothecaPenaltyPerUnit = 0.10f; // 默认每个-10% + + // 幼虫搜索半径(单位:格) + public float larvaSearchRadius = 999f; // 默认在整个地图搜索 + + // 是否需要在孵化间内才能正常工作(默认:否) + public bool requiresIncubatorRoom = false; + + // 不在孵化间内的速度惩罚(百分比) + public float speedPenaltyOutsideIncubator = 0.8f; // 默认80% + + // 质量因子房间检查(默认:是) + public bool useRoomQualityFactor = true; + + // 建筑血量影响质量(默认:是) + public bool healthAffectsQuality = true; + + // 获取营养液检测半径的整数形式(用于循环) + public int NutrientSolutionRadiusInt => (int)nutrientSolutionRadius; + + // 获取其他卵距离检测半径的整数形式 + public int NearbyOothecaRadiusInt => (int)nearbyOothecaRadius; + + public static OothecaIncubatorExtension Get(Thing thing) + { + if (thing?.def?.GetModExtension() is OothecaIncubatorExtension ext) + return ext; + return null; + } + + // 验证配置是否有效 + public bool IsValid() + { + return nutrientSolutionRadius >= 0f && + nearbyOothecaRadius >= 0f && + nutrientSolutionBonusPerTile >= 0f && + nearbyOothecaPenaltyPerUnit >= 0f; + } + + // 获取默认扩展(用于兼容性) + public static OothecaIncubatorExtension Default => new OothecaIncubatorExtension(); + } +} diff --git a/Source/ArachnaeSwarm/Jobs/JobDriver_FeedWithHoney/JobDriver_ExtractHoney.cs b/Source/ArachnaeSwarm/Jobs/JobDriver_FeedWithHoney/JobDriver_ExtractHoney.cs new file mode 100644 index 0000000..d1b0f97 --- /dev/null +++ b/Source/ArachnaeSwarm/Jobs/JobDriver_FeedWithHoney/JobDriver_ExtractHoney.cs @@ -0,0 +1,156 @@ +// File: JobDriver_ExtractHoney.cs +using System.Collections.Generic; +using RimWorld; +using Verse; +using Verse.AI; + +namespace ArachnaeSwarm +{ + public class JobDriver_ExtractHoney : JobDriver + { + private const int ExtractDurationTicks = 180; // 3秒 = 180 ticks + + private Need_HoneyProduction HoneyNeed => pawn.needs?.TryGetNeed(); + + // 使用挤出效果 + private static readonly EffecterDef ExtractEffect = ARA_EffecterDefOf.EatVegetarian; + private static readonly SoundDef ExtractSound = SoundDefOf.RawMeat_Eat; + + public override bool TryMakePreToilReservations(bool errorOnFailed) + { + // 只需要保留目标位置 + return pawn.Reserve(job.targetA, job, 1, -1, null, errorOnFailed); + } + + protected override IEnumerable MakeNewToils() + { + // 确保自身有蜜罐需求且达到挤出阈值 + this.FailOn(() => HoneyNeed == null || !CanExtract(HoneyNeed)); + + // Toil 1: 移动到目标位置 + yield return Toils_Goto.GotoCell(TargetIndex.A, PathEndMode.Touch); + + // Toil 2: 执行挤出工作 + Toil extractToil = new Toil + { + initAction = delegate + { + // 初始化挤出动作 + }, + tickAction = delegate + { + // 什么都不做,等待完成 + }, + defaultCompleteMode = ToilCompleteMode.Delay, + defaultDuration = ExtractDurationTicks + }; + + // 添加特效和音效 + extractToil.WithEffect(() => ExtractEffect, TargetIndex.A); + extractToil.PlaySustainerOrSound(() => ExtractSound); + + extractToil.AddFinishAction(delegate + { + // 执行挤出逻辑 + ExtractHoney(); + }); + + extractToil.WithProgressBar(TargetIndex.A, + () => extractToil.actor.jobs.curDriver.ticksLeftThisToil / (float)ExtractDurationTicks); + + yield return extractToil; + + // Toil 3: 完成后的清理 + yield return new Toil + { + initAction = delegate + { + // 可以在这里检查是否还需要继续挤出 + // 如果蜜罐存量达到阈值,可以安排下一个挤出工作 + if (HoneyNeed != null && CanExtract(HoneyNeed)) + { + pawn.jobs.jobQueue.EnqueueLast(CreateExtractJob()); + } + }, + defaultCompleteMode = ToilCompleteMode.Instant + }; + } + + // 检查是否可以挤出(根据MaxLevel决定阈值) + private bool CanExtract(Need_HoneyProduction honeyNeed) + { + // 如果MaxLevel大于1.5,需要超过80%储量才考虑挤蜜 + if (honeyNeed.MaxLevel > 1.5f) + { + return honeyNeed.CurLevelPercentage > 0.8f; + } + // 否则在大于1时挤蜜 + else + { + return honeyNeed.CurLevel >= 1.0f; + } + } + + // 获取挤出阈值(用于显示等) + private float GetExtractThreshold(Need_HoneyProduction honeyNeed) + { + if (honeyNeed.MaxLevel > 1.5f) + { + return honeyNeed.MaxLevel * 0.8f; + } + else + { + return 1.0f; + } + } + + // 挤出蜂蜜的逻辑 + private void ExtractHoney() + { + if (HoneyNeed == null) + return; + + // 计算要生成的虫蜜数量(蜜罐等级整数部分) + int jellyCount = (int)HoneyNeed.CurLevel; + + // 确保至少生成1个 + if (jellyCount < 1) + return; + + // 创建虫蜜 + Thing jelly = ThingMaker.MakeThing(DefDatabase.GetNamed("ARA_InsectJelly")); + jelly.stackCount = jellyCount; + + // 尝试将虫蜜放在目标位置 + GenPlace.TryPlaceThing(jelly, job.targetA.Cell, pawn.Map, ThingPlaceMode.Near); + + // 减少蜜罐存量 + HoneyNeed.ExtractHoney(jellyCount); + + // 播放视觉反馈 + MoteMaker.ThrowText(job.targetA.Cell.ToVector3Shifted(), pawn.Map,"ARA_ExtractHoney_Message".Translate(jellyCount)); + } + + // 创建挤出工作的辅助方法 + private Job CreateExtractJob() + { + // 寻找最近的空地 + IntVec3 targetCell = FindNearestEmptyCell(pawn.Position, pawn.Map); + + Job job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_ExtractHoney"), targetCell); + return job; + } + + // 寻找最近的空单元格 + private IntVec3 FindNearestEmptyCell(IntVec3 from, Map map) + { + if (CellFinder.TryFindBestPawnStandCell(pawn, out IntVec3 cell, false)) + { + return cell; + } + + // 如果找不到最佳位置,使用原位置 + return from; + } + } +} diff --git a/Source/ArachnaeSwarm/Jobs/JobDriver_FeedWithHoney/JobDriver_FeedWithHoney.cs b/Source/ArachnaeSwarm/Jobs/JobDriver_FeedWithHoney/JobDriver_FeedWithHoney.cs new file mode 100644 index 0000000..bd56ad7 --- /dev/null +++ b/Source/ArachnaeSwarm/Jobs/JobDriver_FeedWithHoney/JobDriver_FeedWithHoney.cs @@ -0,0 +1,105 @@ +// File: JobDriver_FeedWithHoney.cs +using System.Collections.Generic; +using RimWorld; +using Verse; +using Verse.AI; + +namespace ArachnaeSwarm +{ + public class JobDriver_FeedWithHoney : JobDriver + { + private const int FeedDurationTicks = 180; // 3秒 = 180 ticks (60 ticks/秒) + private const float FoodTransferPerTick = 0.01f; // 每tick传输的食物量 + + private Pawn TargetPawn => job.targetA.Pawn; + private Need_HoneyProduction HoneyNeed => pawn.needs?.TryGetNeed(); + private Need_Food TargetFoodNeed => TargetPawn.needs?.TryGetNeed(); + + // 使用喂养效果 - 参考Toils_Ingest中的用法 + private static readonly EffecterDef FeedEffect = ARA_EffecterDefOf.EatVegetarian; + private static readonly SoundDef FeedSound = SoundDefOf.RawMeat_Eat; + + public override bool TryMakePreToilReservations(bool errorOnFailed) + { + if (TargetPawn == null || TargetFoodNeed == null || HoneyNeed == null) + return false; + + // 保留目标pawn的位置,以便接近 + return pawn.Reserve(TargetPawn, job, 1, -1, null, errorOnFailed); + } + + protected override IEnumerable MakeNewToils() + { + // 确保目标有效 + this.FailOnDespawnedOrNull(TargetIndex.A); + this.FailOn(() => TargetPawn.Dead || TargetFoodNeed == null || HoneyNeed == null); + + // Toil 1: 移动到目标面前 + yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.Touch) + .FailOnDespawnedOrNull(TargetIndex.A); + + // Toil 2: 进行喂养工作(参考Toils_Ingest中的效果添加方式) + Toil feedToil = new Toil + { + initAction = delegate + { + // 初始化喂养动作 + }, + tickAction = delegate + { + // 每tick检查是否还能继续喂养 + if (HoneyNeed.CurLevel <= 0 || TargetFoodNeed.CurLevelPercentage >= 1.0f) + { + ReadyForNextToil(); + return; + } + + // 计算本次tick传输的量 + float transferAmount = FoodTransferPerTick; + float honeyAvailable = HoneyNeed.CurLevel; + + // 确保不超过蜜罐存量 + if (transferAmount > honeyAvailable) + transferAmount = honeyAvailable; + + // 确保不超过目标的需求 + float targetFoodSpace = TargetFoodNeed.MaxLevel - TargetFoodNeed.CurLevel; + if (transferAmount > targetFoodSpace) + transferAmount = targetFoodSpace; + + // 执行传输 + HoneyNeed.ExtractHoney(transferAmount); + TargetFoodNeed.CurLevel += transferAmount; + }, + defaultCompleteMode = ToilCompleteMode.Delay, + defaultDuration = FeedDurationTicks + }; + + // 添加特效和音效(参考Toils_Ingest.AddIngestionEffects) + feedToil.WithEffect(() => FeedEffect, TargetIndex.A); + feedToil.PlaySustainerOrSound(() => FeedSound); + + feedToil.AddFinishAction(delegate + { + // 喂养完成后给予社交互动奖励 + TargetPawn.interactions?.TryInteractWith(pawn, InteractionDefOf.Chitchat); + }); + + feedToil.WithProgressBar(TargetIndex.A, + () => feedToil.actor.jobs.curDriver.ticksLeftThisToil / (float)FeedDurationTicks, + interpolateBetweenActorAndTarget: true); + + yield return feedToil; + + // 修改:移除了第三个Toil中的提示消息,只剩下一个简单的完成Toil + yield return new Toil + { + initAction = delegate + { + // 这里可以放置任何清理代码,但不要显示消息 + }, + defaultCompleteMode = ToilCompleteMode.Instant + }; + } + } +} diff --git a/Source/ArachnaeSwarm/Jobs/JobDriver_FeedWithHoney/ThinkNode_JobGiver_ExtractHoney.cs b/Source/ArachnaeSwarm/Jobs/JobDriver_FeedWithHoney/ThinkNode_JobGiver_ExtractHoney.cs new file mode 100644 index 0000000..cbe87e0 --- /dev/null +++ b/Source/ArachnaeSwarm/Jobs/JobDriver_FeedWithHoney/ThinkNode_JobGiver_ExtractHoney.cs @@ -0,0 +1,105 @@ +// File: ThinkNode_JobGiver_ExtractHoney.cs +using System.Collections.Generic; +using System.Linq; +using RimWorld; +using Verse; +using Verse.AI; + +namespace ArachnaeSwarm +{ + public class ThinkNode_JobGiver_ExtractHoney : ThinkNode_JobGiver + { + private const int ScanIntervalTicks = 300; // 5秒扫描一次 + + private int lastScanTick = -99999; + + protected override Job TryGiveJob(Pawn pawn) + { + // 检查是否应该扫描 + int currentTick = Find.TickManager.TicksGame; + if (currentTick - lastScanTick < ScanIntervalTicks) + return null; + + lastScanTick = currentTick; + + // 检查甲壳剥离组件和开关状态 + Comp_ChitinStripping stripComp = pawn.TryGetComp(); + + // 检查开关是否开启 + if (stripComp == null || !stripComp.CanStripChitin || !stripComp.CanStripNow(pawn)) + return null; + + // 检查自身需求 + Need_HoneyProduction honeyNeed = pawn.needs?.TryGetNeed(); + if (honeyNeed == null || !CanExtract(honeyNeed)) + return null; + + // 如果已经有挤出工作,则不分配新工作 + if (pawn.CurJob != null && pawn.CurJob.def == DefDatabase.GetNamed("ARA_ExtractHoney")) + return null; + + // 寻找最近的空地 + IntVec3 targetCell = FindNearestEmptyCell(pawn); + if (!targetCell.IsValid) + return null; + + // 确保可以到达 + if (!pawn.CanReach(targetCell, PathEndMode.Touch, Danger.Some)) + return null; + + // 创建挤出工作 + Job job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_ExtractHoney"), targetCell); + job.count = 1; + + return job; + } + + // 检查是否可以挤出(与JobDriver中的逻辑保持一致) + private bool CanExtract(Need_HoneyProduction honeyNeed) + { + // 如果MaxLevel大于1.5,需要超过80%储量才考虑挤蜜 + if (honeyNeed.MaxLevel > 1.5f) + { + return honeyNeed.CurLevelPercentage > 0.8f; + } + // 否则在大于1时挤蜜 + else + { + return honeyNeed.CurLevel >= 1.0f; + } + } + + // 寻找最近的空单元格 + private IntVec3 FindNearestEmptyCell(Pawn pawn) + { + // 首先检查当前位置周围3格内是否有空地 + for (int radius = 1; radius <= 3; radius++) + { + foreach (IntVec3 cell in GenRadial.RadialCellsAround(pawn.Position, radius, true)) + { + if (cell.InBounds(pawn.Map) && + cell.Standable(pawn.Map) && + cell.GetFirstItem(pawn.Map) == null && + !cell.GetTerrain(pawn.Map).IsWater) + { + return cell; + } + } + } + + // 如果找不到,使用CellFinder寻找 + if (CellFinder.TryFindBestPawnStandCell(pawn, out IntVec3 bestCell, false)) + { + return bestCell; + } + + // 最后尝试使用原位置(确保不是水面) + if (!pawn.Position.GetTerrain(pawn.Map).IsWater) + { + return pawn.Position; + } + + return IntVec3.Invalid; + } + } +} diff --git a/Source/ArachnaeSwarm/Jobs/JobDriver_FeedWithHoney/ThinkNode_JobGiver_FeedWithHoney.cs b/Source/ArachnaeSwarm/Jobs/JobDriver_FeedWithHoney/ThinkNode_JobGiver_FeedWithHoney.cs new file mode 100644 index 0000000..050efbd --- /dev/null +++ b/Source/ArachnaeSwarm/Jobs/JobDriver_FeedWithHoney/ThinkNode_JobGiver_FeedWithHoney.cs @@ -0,0 +1,107 @@ +// File: ThinkNode_JobGiver_FeedWithHoney.cs +using System.Collections.Generic; +using System.Linq; +using RimWorld; +using Verse; +using Verse.AI; + +namespace ArachnaeSwarm +{ + public class ThinkNode_JobGiver_FeedWithHoney : ThinkNode_JobGiver + { + private const float MinFoodNeedThreshold = 0.2f; // 目标需要喂养的阈值 + private const float MinHoneyThreshold = 0.1f; // 蜜罐低于10%时不喂养 + private const int ScanIntervalTicks = 250; // 扫描间隔 (约4秒) + private const float MaxDistance = 30f; // 最大寻找距离 + + private int lastScanTick = -99999; + + protected override Job TryGiveJob(Pawn pawn) + { + // 检查是否应该扫描 + int currentTick = Find.TickManager.TicksGame; + if (currentTick - lastScanTick < ScanIntervalTicks) + return null; + + lastScanTick = currentTick; + + // 检查自身需求 + Need_HoneyProduction honeyNeed = pawn.needs?.TryGetNeed(); + // 修改:检查蜜罐存量是否低于10%,如果是则不喂养 + if (honeyNeed == null || honeyNeed.IsEmpty || honeyNeed.CurLevelPercentage < MinHoneyThreshold) + return null; + + // 扫描潜在的喂养目标 + List potentialTargets = new List(); + + // 获取地图上所有pawn + foreach (Pawn target in pawn.Map.mapPawns.AllPawnsSpawned) + { + // 跳过自己 + if (target == pawn) + continue; + + // 检查是否属于己方势力(殖民者、奴隶、囚犯) + if (!IsFriendlyPawn(pawn, target)) + continue; + + // 检查距离 + if (pawn.Position.DistanceTo(target.Position) > MaxDistance) + continue; + + // 检查是否需要食物 + Need_Food targetFoodNeed = target.needs?.TryGetNeed(); + if (targetFoodNeed == null || targetFoodNeed.CurLevelPercentage > MinFoodNeedThreshold) + continue; + + // 检查目标是否可到达 + if (!pawn.CanReserveAndReach(target, PathEndMode.Touch, Danger.Some)) + continue; + + potentialTargets.Add(target); + } + + // 如果没有目标 + if (potentialTargets.Count == 0) + return null; + + // 按需求紧急程度排序(最饿的优先) + potentialTargets.Sort((a, b) => + { + Need_Food foodA = a.needs?.TryGetNeed(); + Need_Food foodB = b.needs?.TryGetNeed(); + return foodA?.CurLevelPercentage.CompareTo(foodB?.CurLevelPercentage) ?? 0; + }); + + // 选择最饥饿的目标 + Pawn targetPawn = potentialTargets[0]; + + // 创建喂养工作 + Job job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_FeedWithHoney"), targetPawn); + job.count = 1; + + return job; + } + + private bool IsFriendlyPawn(Pawn feeder, Pawn target) + { + // 检查目标是否是殖民者、奴隶或囚犯 + if (target.Faction == Faction.OfPlayer) + return true; + + // 检查是否是囚犯(属于玩家) + if (target.IsPrisonerOfColony) + return true; + + // 检查是否是奴隶 + if (target.IsSlaveOfColony) + return true; + + // 检查是否和喂养者同一派系 + if (target.Faction != null && target.Faction == feeder.Faction) + return true; + + return false; + } + } +} diff --git a/Source/ArachnaeSwarm/Jobs/JobDriver_StripChitin/CompProperties_ChitinStripping.cs b/Source/ArachnaeSwarm/Jobs/JobDriver_StripChitin/CompProperties_ChitinStripping.cs new file mode 100644 index 0000000..4322caa --- /dev/null +++ b/Source/ArachnaeSwarm/Jobs/JobDriver_StripChitin/CompProperties_ChitinStripping.cs @@ -0,0 +1,29 @@ +// File: Comps/CompProperties_ChitinStripping.cs +using RimWorld; +using Verse; + +namespace ArachnaeSwarm +{ + public class CompProperties_ChitinStripping : CompProperties + { + // 是否允许剥离甲壳 + public bool canStripChitin = true; + + // 剥离的阈值(甲壳存量的百分比) + public float stripThreshold = 0.8f; + + // 每次剥离产生的最小甲壳数量 + public int minStripAmount = 1; + + // 剥离工作的间隔(ticks) + public int stripInterval = 3000; // 50秒 + + // 甲壳物品定义 + public ThingDef carapaceThingDef = null; + + public CompProperties_ChitinStripping() + { + compClass = typeof(Comp_ChitinStripping); + } + } +} diff --git a/Source/ArachnaeSwarm/Jobs/JobDriver_StripChitin/Comp_ChitinStripping.cs b/Source/ArachnaeSwarm/Jobs/JobDriver_StripChitin/Comp_ChitinStripping.cs new file mode 100644 index 0000000..5b77165 --- /dev/null +++ b/Source/ArachnaeSwarm/Jobs/JobDriver_StripChitin/Comp_ChitinStripping.cs @@ -0,0 +1,103 @@ +// File: Comps/Comp_ChitinStripping.cs +using System.Collections.Generic; +using RimWorld; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + public class Comp_ChitinStripping : ThingComp + { + public CompProperties_ChitinStripping Props => (CompProperties_ChitinStripping)props; + + // 剥离开关状态 + private bool stripEnabled = true; + + // 上次剥离时间 + private int lastStripTick = -99999; + + // 属性访问器 + public bool StripEnabled + { + get => stripEnabled; + set => stripEnabled = value; + } + + public bool CanStripChitin => Props.canStripChitin && stripEnabled; + + public float StripThreshold => Props.stripThreshold; + + // 获取甲壳物品定义 + public ThingDef CarapaceThingDef + { + get + { + if (Props.carapaceThingDef != null) + return Props.carapaceThingDef; + + return DefDatabase.GetNamed("ARA_Carapace", false); + } + } + + // 检查是否可以剥离 + public bool CanStripNow(Pawn pawn) + { + if (!CanStripChitin || pawn == null || pawn.Dead) + return false; + + // 检查间隔 + int currentTick = Find.TickManager.TicksGame; + if (currentTick - lastStripTick < Props.stripInterval) + return false; + + // 检查甲壳需求 + Need_ChitinArmor chitinNeed = pawn.needs?.TryGetNeed(); + if (chitinNeed == null) + return false; + + // 检查是否达到阈值 + return chitinNeed.CurLevelPercentage >= StripThreshold; + } + + // 记录剥离时间 + public void NotifyStripped() + { + lastStripTick = Find.TickManager.TicksGame; + } + + // 获取上次剥离到现在的时间 + public int TicksSinceLastStrip => Find.TickManager.TicksGame - lastStripTick; + + // 获取剩余冷却时间 + public int CooldownTicksRemaining => Mathf.Max(0, Props.stripInterval - TicksSinceLastStrip); + + // 序列化 + public override void PostExposeData() + { + base.PostExposeData(); + Scribe_Values.Look(ref stripEnabled, "stripEnabled", true); + Scribe_Values.Look(ref lastStripTick, "lastStripTick", -99999); + } + + // 获取Gizmos(命令按钮) + public override IEnumerable CompGetGizmosExtra() + { + // 只在玩家控制的pawn上显示 + if (parent is Pawn pawn && pawn.Faction?.IsPlayer == true) + { + // 甲壳剥离开关按钮 + yield return new Command_Toggle + { + defaultLabel = "ARA_ChitinStripping_Toggle".Translate(), + defaultDesc = "ARA_ChitinStripping_ToggleDesc".Translate(), + icon = ContentFinder.Get("ArachnaeSwarm/UI/Commands/ARA_StripChitin"), + isActive = () => stripEnabled, + toggleAction = () => + { + stripEnabled = !stripEnabled; + } + }; + } + } + } +} diff --git a/Source/ArachnaeSwarm/Jobs/JobDriver_StripChitin/JobDriver_StripChitin.cs b/Source/ArachnaeSwarm/Jobs/JobDriver_StripChitin/JobDriver_StripChitin.cs new file mode 100644 index 0000000..1811cec --- /dev/null +++ b/Source/ArachnaeSwarm/Jobs/JobDriver_StripChitin/JobDriver_StripChitin.cs @@ -0,0 +1,158 @@ +// File: JobDriver_StripChitin.cs +using System.Collections.Generic; +using RimWorld; +using Verse; +using Verse.AI; + +namespace ArachnaeSwarm +{ + public class JobDriver_StripChitin : JobDriver + { + private const int StripDurationTicks = 300; // 5秒 = 300 ticks + + // 获取甲壳需求 + private Need_ChitinArmor ChitinNeed => pawn.needs?.TryGetNeed(); + + // 获取甲壳剥离组件 + private Comp_ChitinStripping StripComp + { + get + { + if (pawn == null) + return null; + + // 从pawn的def中获取Comp + CompProperties_ChitinStripping compProps = pawn.def.GetCompProperties(); + if (compProps != null) + { + return pawn.TryGetComp(); + } + return null; + } + } + + // 特效和音效 + private static readonly EffecterDef StripEffect = ARA_EffecterDefOf.EatVegetarian; + private static readonly SoundDef StripSound = SoundDefOf.RawMeat_Eat; + + public override bool TryMakePreToilReservations(bool errorOnFailed) + { + // 只需要保留目标位置 + return pawn.Reserve(job.targetA, job, 1, -1, null, errorOnFailed); + } + + protected override IEnumerable MakeNewToils() + { + // 检查条件 + this.FailOn(() => ChitinNeed == null); + this.FailOn(() => StripComp == null); + this.FailOn(() => !StripComp.CanStripNow(pawn)); + this.FailOn(() => !StripComp.CanStripChitin); // 检查开关 + + // Toil 1: 移动到目标位置 + yield return Toils_Goto.GotoCell(TargetIndex.A, PathEndMode.Touch); + + // Toil 2: 执行剥离工作 + Toil stripToil = new Toil + { + initAction = delegate + { + // 初始化剥离动作 + }, + tickAction = delegate + { + // 什么都不做,等待完成 + }, + defaultCompleteMode = ToilCompleteMode.Delay, + defaultDuration = StripDurationTicks + }; + + // 添加特效和音效 + stripToil.WithEffect(() => StripEffect, TargetIndex.A); + stripToil.PlaySustainerOrSound(() => StripSound); + + stripToil.AddFinishAction(delegate + { + // 执行剥离逻辑 + StripChitin(); + }); + + stripToil.WithProgressBar(TargetIndex.A, + () => stripToil.actor.jobs.curDriver.ticksLeftThisToil / (float)StripDurationTicks, + interpolateBetweenActorAndTarget: true); + + yield return stripToil; + + // Toil 3: 完成后的清理 + yield return new Toil + { + initAction = delegate + { + // 记录剥离时间 + StripComp?.NotifyStripped(); + + // 如果甲壳存量仍然达到阈值,可以安排下一个剥离工作 + if (ChitinNeed != null && StripComp != null && StripComp.CanStripNow(pawn)) + { + pawn.jobs.jobQueue.EnqueueLast(CreateStripJob()); + } + }, + defaultCompleteMode = ToilCompleteMode.Instant + }; + } + + // 剥离甲壳的逻辑 + private void StripChitin() + { + if (ChitinNeed == null || StripComp == null) + return; + + // 计算要剥离的甲壳数量 + // 通常剥离整数部分,但至少剥离1个 + int stripCount = (int)ChitinNeed.CurLevel; + if (stripCount < StripComp.Props.minStripAmount) + stripCount = StripComp.Props.minStripAmount; + + // 获取甲壳物品定义 + ThingDef carapaceDef = StripComp.CarapaceThingDef; + if (carapaceDef == null) + return; + + // 创建甲壳物品 + Thing carapace = ThingMaker.MakeThing(carapaceDef); + carapace.stackCount = stripCount; + + // 尝试将甲壳放在目标位置 + GenPlace.TryPlaceThing(carapace, job.targetA.Cell, pawn.Map, ThingPlaceMode.Near); + + // 减少甲壳存量 + ChitinNeed.ReduceChitin(stripCount); + + // 播放视觉反馈 + MoteMaker.ThrowText(job.targetA.Cell.ToVector3Shifted(), pawn.Map, + "ARA_StripChitin_Message".Translate(stripCount)); + } + + // 创建剥离工作的辅助方法 + private Job CreateStripJob() + { + // 寻找最近的空地 + IntVec3 targetCell = FindNearestEmptyCell(pawn.Position, pawn.Map); + + Job job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_StripChitin"), targetCell); + return job; + } + + // 寻找最近的空单元格 + private IntVec3 FindNearestEmptyCell(IntVec3 from, Map map) + { + if (CellFinder.TryFindBestPawnStandCell(pawn, out IntVec3 cell, false)) + { + return cell; + } + + // 如果找不到最佳位置,使用原位置 + return from; + } + } +} diff --git a/Source/ArachnaeSwarm/Jobs/JobDriver_StripChitin/ThinkNode_JobGiver_StripChitin.cs b/Source/ArachnaeSwarm/Jobs/JobDriver_StripChitin/ThinkNode_JobGiver_StripChitin.cs new file mode 100644 index 0000000..90a4ce7 --- /dev/null +++ b/Source/ArachnaeSwarm/Jobs/JobDriver_StripChitin/ThinkNode_JobGiver_StripChitin.cs @@ -0,0 +1,83 @@ +// File: ThinkNode_JobGiver_StripChitin.cs +using System.Collections.Generic; +using System.Linq; +using RimWorld; +using Verse; +using Verse.AI; + +namespace ArachnaeSwarm +{ + public class ThinkNode_JobGiver_StripChitin : ThinkNode_JobGiver + { + private const int ScanIntervalTicks = 500; // 8.3秒扫描一次 + + private int lastScanTick = -99999; + + protected override Job TryGiveJob(Pawn pawn) + { + // 检查是否应该扫描 + int currentTick = Find.TickManager.TicksGame; + if (currentTick - lastScanTick < ScanIntervalTicks) + return null; + + lastScanTick = currentTick; + + // 检查甲壳剥离组件 + Comp_ChitinStripping stripComp = pawn.TryGetComp(); + if (stripComp == null || !stripComp.CanStripNow(pawn)) + return null; + + // 如果已经有剥离工作,则不分配新工作 + if (pawn.CurJob != null && pawn.CurJob.def == DefDatabase.GetNamed("ARA_StripChitin")) + return null; + + // 寻找最近的空地 + IntVec3 targetCell = FindNearestEmptyCell(pawn); + if (!targetCell.IsValid) + return null; + + // 确保可以到达 + if (!pawn.CanReach(targetCell, PathEndMode.Touch, Danger.Some)) + return null; + + // 创建剥离工作 + Job job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_StripChitin"), targetCell); + job.count = 1; + + return job; + } + + // 寻找最近的空单元格 + private IntVec3 FindNearestEmptyCell(Pawn pawn) + { + // 首先检查当前位置周围3格内是否有空地 + for (int radius = 1; radius <= 3; radius++) + { + foreach (IntVec3 cell in GenRadial.RadialCellsAround(pawn.Position, radius, true)) + { + if (cell.InBounds(pawn.Map) && + cell.Standable(pawn.Map) && + cell.GetFirstItem(pawn.Map) == null && + !cell.GetTerrain(pawn.Map).IsWater) + { + return cell; + } + } + } + + // 如果找不到,使用CellFinder寻找 + if (CellFinder.TryFindBestPawnStandCell(pawn, out IntVec3 bestCell, false)) + { + return bestCell; + } + + // 最后尝试使用原位置(确保不是水面) + if (!pawn.Position.GetTerrain(pawn.Map).IsWater) + { + return pawn.Position; + } + + return IntVec3.Invalid; + } + } +} diff --git a/Source/ArachnaeSwarm/Jobs/JobDriver_SwarmMaintain/JobDriver_SwarmMaintain.cs b/Source/ArachnaeSwarm/Jobs/JobDriver_SwarmMaintain/JobDriver_SwarmMaintain.cs new file mode 100644 index 0000000..8f666cc --- /dev/null +++ b/Source/ArachnaeSwarm/Jobs/JobDriver_SwarmMaintain/JobDriver_SwarmMaintain.cs @@ -0,0 +1,78 @@ +// File: Jobs/JobDriver_SwarmMaintain.cs +using RimWorld; +using Verse; +using Verse.AI; +using System.Collections.Generic; + +namespace ArachnaeSwarm +{ + public class JobDriver_SwarmMaintain : JobDriver + { + // 工作完成后增加的维护度 + public const float MaintenancePerWork = 25f; + + // 工作时间(ticks) + private const int WorkDuration = 600; // 10秒 + // 使用喂养效果 - 参考Toils_Ingest中的用法 + private static readonly EffecterDef MaintainEffect = ARA_EffecterDefOf.EatVegetarian; + private static readonly SoundDef MaintainSound = SoundDefOf.RawMeat_Eat; + + + public override bool TryMakePreToilReservations(bool errorOnFailed) + { + return pawn.Reserve(job.targetA, job, 1, -1, null, errorOnFailed); + } + + protected override IEnumerable MakeNewToils() + { + // 1. 前往目标建筑 + this.FailOnDespawnedNullOrForbidden(TargetIndex.A); + yield return Toils_Goto.GotoCell(TargetIndex.A, PathEndMode.Touch); + + // 2. 执行维护工作 + Toil maintain = new Toil(); + maintain.tickAction = () => + { + Pawn actor = maintain.actor; + Thing target = actor.CurJob.targetA.Thing; + + // 检查目标是否仍然有效 + if (target == null || target.Destroyed) + { + actor.jobs.EndCurrentJob(JobCondition.Incompletable); + return; + } + + + // 如果有技能要求,可以在这里检查 + // actor.skills.Learn(SkillDefOf.Construction, 0.05f); + }; + // 添加特效和音效(参考Toils_Ingest.AddIngestionEffects) + maintain.WithEffect(() => MaintainEffect, TargetIndex.A); + maintain.PlaySustainerOrSound(() => MaintainSound); + maintain.FailOnDespawnedNullOrForbidden(TargetIndex.A); + maintain.FailOnCannotTouch(TargetIndex.A, PathEndMode.Touch); + maintain.WithProgressBar(TargetIndex.A, () => (float)maintain.actor.jobs.curDriver.ticksLeftThisToil / WorkDuration); + maintain.defaultCompleteMode = ToilCompleteMode.Delay; + maintain.defaultDuration = WorkDuration; + yield return maintain; + + // 3. 完成工作,增加维护度 + yield return new Toil + { + initAction = () => + { + Thing target = TargetA.Thing; + if (target != null && !target.Destroyed) + { + Comp_SwarmMaintenance comp = target.TryGetComp(); + if (comp != null) + { + comp.AddMaintenance(MaintenancePerWork); + } + } + } + }; + } + } +} diff --git a/Source/ArachnaeSwarm/Needs/Need_ChitinArmor.cs b/Source/ArachnaeSwarm/Needs/Need_ChitinArmor.cs new file mode 100644 index 0000000..71bd29e --- /dev/null +++ b/Source/ArachnaeSwarm/Needs/Need_ChitinArmor.cs @@ -0,0 +1,438 @@ +// File: Need_ChitinArmor.cs +using RimWorld; +using System.Collections.Generic; +using System.Xml.Serialization; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + public class NeedDefExtension_ChitinLevels : DefModExtension + { + // 关联的Hediff + public HediffDef hediff = null; + + // 严重性映射范围(默认是1:1映射,即Need值=严重性值) + [XmlElement("severityRange")] + public FloatRange severityRange = new FloatRange(0f, 1f); + + // 是否死亡时移除Hediff + public bool removeOnDeath = true; + + // 基础增长速率系数 + public float baseGrowthRate = 0.1f; + + // 甲壳数量平方的系数 + public float squareCoefficient = 1f / 100f; // 默认 1/100 + } + public class Need_ChitinArmor : Need + { + // 甲壳增长基础值(每tick) + private const float BaseChitinGainPerTick = 0.0001f; + + // 关联的Hediff + private Hediff chitinHediff; + + // 上次更新Hediff的时间 + private int lastHediffUpdateTick = -99999; + private const int HediffUpdateInterval = 60; // 每60tick更新一次Hediff + + // 甲壳部位名称 + private const string CHITIN_SHELL_PART_NAME = "ARA_Chitin_Shell"; + + // 缓存的身体部位数量(用于减少计算) + private int cachedShellPartCount = -1; + private int lastPartCountCheckTick = -99999; + private const int PartCountCheckInterval = 120; // 每120tick检查一次 + + // 获取ModExtension配置 + public NeedDefExtension_ChitinLevels Extension => def.GetModExtension(); + + // 计算甲壳最大值:基础值1 + 每个ARA_Chitin_Shell部位增加1 + public override float MaxLevel + { + get + { + int shellCount = GetChitinShellPartCount(); + return 1f + shellCount; + } + } + + // 获取当前等级百分比(用于UI显示) + public new float CurLevelPercentage => CurLevel / MaxLevel; + + // 获取甲壳增长速率 + public float GrowthRatePerTick + { + get + { + int shellCount = GetChitinShellPartCount(); + + // 计算增长速率:((甲壳数量的平方) * 系数) + 基础值 + float squareCoefficient = Extension?.squareCoefficient ?? (1f / 100f); + float baseRate = Extension?.baseGrowthRate ?? 0.1f; + + return ((shellCount * shellCount) * squareCoefficient) + baseRate; + } + } + + // 获取每秒增长速率(用于UI显示) + public float GrowthRatePerSecond + { + get + { + // RimWorld中,1秒 = 60ticks + // 实际每秒增长量 = 每tick增长速率 * 60 + // 注意:BaseChitinGainPerTick是基础增长值,GrowthRatePerTick是增长系数 + // 实际增长公式:每tick增长量 = BaseChitinGainPerTick * GrowthRatePerTick + // 所以每秒增长量 = BaseChitinGainPerTick * GrowthRatePerTick * 60 + return BaseChitinGainPerTick * GrowthRatePerTick * 60f; + } + } + + // 获取每2.5秒增长速率(用于UI显示,每150tick) + public float GrowthRatePer2_5Seconds => GrowthRatePerSecond * 2.5f; + + public Need_ChitinArmor(Pawn newPawn) : base(newPawn) + { + // 初始化时设置为空 + curLevelInt = 0f; + + // 设置默认阈值点 + SetDefaultThresholds(); + + // 初始化缓存 + UpdateCachedShellPartCount(); + } + + public override void ExposeData() + { + base.ExposeData(); + Scribe_References.Look(ref chitinHediff, "chitinHediff"); + Scribe_Values.Look(ref cachedShellPartCount, "cachedShellPartCount", -1); + Scribe_Values.Look(ref lastPartCountCheckTick, "lastPartCountCheckTick", -99999); + } + + private void SetDefaultThresholds() + { + // 默认阈值:25%、50%、75%(用于UI显示) + threshPercents = new List { 0.25f, 0.5f, 0.75f }; + } + + public override void NeedInterval() + { + // 检查是否需要冻结 + if (IsFrozen) + { + return; + } + + // 定期更新身体部位数量缓存 + int currentTick = Find.TickManager.TicksGame; + if (currentTick - lastPartCountCheckTick >= PartCountCheckInterval) + { + UpdateCachedShellPartCount(); + lastPartCountCheckTick = currentTick; + } + + // 计算增长量:基础增长速率 * 每150tick的间隔 * 甲壳增长速率系数 + float growthAmount = BaseChitinGainPerTick * 150f * GrowthRatePerTick; + + // 应用增长 + CurLevel += growthAmount; + + // 确保不超过最大值,不低于0 + float maxLevel = MaxLevel; // 计算一次并缓存 + if (CurLevel > maxLevel) + CurLevel = maxLevel; + else if (CurLevel < 0f) + CurLevel = 0f; + + // 定期更新Hediff(每60tick一次) + if (currentTick - lastHediffUpdateTick >= HediffUpdateInterval) + { + UpdateChitinHediff(); + lastHediffUpdateTick = currentTick; + } + } + + // 获取ARA_Chitin_Shell身体部位的数量 + private int GetChitinShellPartCount() + { + // 使用缓存值(如果有效) + if (cachedShellPartCount >= 0 && pawn != null && !pawn.Dead) + { + return cachedShellPartCount; + } + + // 重新计算 + UpdateCachedShellPartCount(); + return cachedShellPartCount; + } + + // 更新缓存的身体部位数量 + private void UpdateCachedShellPartCount() + { + if (pawn == null || pawn.Dead || pawn.RaceProps == null || pawn.RaceProps.body == null) + { + cachedShellPartCount = 0; + return; + } + + // 计算身体部位数量 + int count = 0; + + // 方法1:从pawn的身体部位记录中查找 + foreach (BodyPartRecord part in pawn.RaceProps.body.AllParts) + { + if (part.def.defName == CHITIN_SHELL_PART_NAME) + { + count++; + } + } + + // 方法2:如果找不到,检查Hediff中是否有甲壳(作为备用方法) + if (count == 0 && pawn.health != null && pawn.health.hediffSet != null) + { + foreach (Hediff hediff in pawn.health.hediffSet.hediffs) + { + if (hediff.Part != null && hediff.Part.def != null && + hediff.Part.def.defName == CHITIN_SHELL_PART_NAME) + { + count++; + } + } + } + + cachedShellPartCount = count; + } + + // 重新计算身体部位数量(当身体发生变化时调用) + public void RecalculateShellPartCount() + { + UpdateCachedShellPartCount(); + } + + // 更新甲壳Hediff + private void UpdateChitinHediff() + { + if (Extension == null || Extension.hediff == null || pawn.Dead) + return; + + // 直接使用CurLevel作为严重性,但可以根据配置的范围进行调整 + float severity = CurLevel; + + // 如果需要,可以应用范围限制 + FloatRange severityRange = Extension.severityRange; + if (severity > severityRange.max) + severity = severityRange.max; + else if (severity < severityRange.min) + severity = severityRange.min; + + // 如果严重性为0或接近0,移除Hediff + if (severity <= 0.001f) + { + RemoveChitinHediff(); + return; + } + + // 确保Hediff存在 + EnsureChitinHediff(); + + // 设置严重性 + if (chitinHediff != null) + { + chitinHediff.Severity = severity; + } + } + + // 确保Hediff存在 + private void EnsureChitinHediff() + { + if (chitinHediff == null || chitinHediff.pawn != pawn) + { + // 移除旧的Hediff + if (chitinHediff != null && chitinHediff.pawn == pawn) + { + pawn.health.RemoveHediff(chitinHediff); + } + + // 创建新的Hediff + chitinHediff = HediffMaker.MakeHediff(Extension.hediff, pawn); + pawn.health.AddHediff(chitinHediff); + } + } + + // 移除Hediff + private void RemoveChitinHediff() + { + if (chitinHediff != null && chitinHediff.pawn == pawn) + { + pawn.health.RemoveHediff(chitinHediff); + chitinHediff = null; + } + } + + // 减少甲壳存量(例如受伤时) + public void ReduceChitin(float amount) + { + CurLevel -= amount; + if (CurLevel < 0f) + CurLevel = 0f; + + // 立即更新Hediff + UpdateChitinHediff(); + } + + // 强制清空甲壳 + public void EmptyChitin() + { + CurLevel = 0f; + RemoveChitinHediff(); + } + + // 重置到初始状态 + public override void SetInitialLevel() + { + // 初始为空 + CurLevel = 0f; + RemoveChitinHediff(); + UpdateCachedShellPartCount(); + } + + // 获取当前严重性 + public float GetCurrentSeverity() + { + if (chitinHediff == null) + return 0f; + + return chitinHediff.Severity; + } + + // 获取提示字符串 + public override string GetTipString() + { + string text = (LabelCap + ": " + CurLevelPercentage.ToStringPercent()).Colorize(ColoredText.TipSectionTitleColor); + text += "\n" + def.description; + + int shellCount = GetChitinShellPartCount(); + text += $"\n{"ARA_Chitin.ShellParts".Translate()}: {shellCount}"; + + // 显示每秒增长速率(更直观) + text += $"\n{"ARA_Chitin.GrowthRate".Translate()}: {GrowthRatePerSecond:0.#####}/ {"LetterSecond".Translate()}"; + + return text; + } + + // 在UI上绘制 + public override void DrawOnGUI(Rect rect, int maxThresholdMarkers = int.MaxValue, float customMargin = -1f, bool drawArrows = true, bool doTooltip = true, Rect? rectForTooltip = null, bool drawLabel = true) + { + // 确保阈值已设置 + if (threshPercents == null) + { + SetDefaultThresholds(); + } + + base.DrawOnGUI(rect, maxThresholdMarkers, customMargin, false, doTooltip, rectForTooltip, drawLabel); + } + + // 是否冻结 + protected override bool IsFrozen + { + get + { + // 如果基础条件冻结,则甲壳生长也冻结 + if (base.IsFrozen) + return true; + + // 如果生物死亡,则冻结 + if (pawn.Dead) + return true; + + return false; + } + } + + // GUI变化箭头(总是显示增长) + public override int GUIChangeArrow => 1; + + // 调试调整百分比 + protected override void OffsetDebugPercent(float offsetPercent) + { + base.OffsetDebugPercent(offsetPercent); + UpdateChitinHediff(); + } + + // 当生物死亡时清理Hediff + public void OnPawnDeath() + { + if (Extension?.removeOnDeath ?? true) + { + RemoveChitinHediff(); + } + } + + // 当生物重生时恢复Hediff + public void OnPawnResurrected() + { + if (CurLevel > 0f && Extension?.hediff != null) + { + UpdateChitinHediff(); + } + UpdateCachedShellPartCount(); + } + + // 当身体部位发生变化时调用 + public void NotifyBodyPartChanged() + { + UpdateCachedShellPartCount(); + + // 如果最大容量减少,确保当前值不超过新最大值 + if (CurLevel > MaxLevel) + { + CurLevel = MaxLevel; + UpdateChitinHediff(); + } + } + + public Comp_ChitinStripping GetStripComp() + { + if (pawn == null) + return null; + + return pawn.TryGetComp(); + } + // 获取是否可以剥离 + public bool CanStripChitin() + { + var stripComp = GetStripComp(); + if (stripComp == null) + return false; + + return stripComp.CanStripNow(pawn); + } + // 获取剥离信息字符串 + public string GetStripInfoString() + { + var stripComp = GetStripComp(); + if (stripComp == null) + return "ARA_ChitinStripping_Disabled".Translate(); + + if (!stripComp.CanStripNow(pawn)) + { + int cooldownTicks = stripComp.CooldownTicksRemaining; + if (cooldownTicks > 0) + { + float cooldownSeconds = cooldownTicks / 60f; + return "ARA_ChitinStripping_Cooldown".Translate(cooldownSeconds.ToString("F1")); + } + else + { + return "ARA_ChitinStripping_Threshold".Translate((stripComp.StripThreshold * 100f).ToString("F0")); + } + } + + return "ARA_ChitinStripping_Ready".Translate(); + } + } +} diff --git a/Source/ArachnaeSwarm/Needs/Need_HoneyProduction.cs b/Source/ArachnaeSwarm/Needs/Need_HoneyProduction.cs index 006c1c8..a927dad 100644 --- a/Source/ArachnaeSwarm/Needs/Need_HoneyProduction.cs +++ b/Source/ArachnaeSwarm/Needs/Need_HoneyProduction.cs @@ -1,13 +1,52 @@ +// File: Need_HoneyProduction.cs using RimWorld; using UnityEngine; using Verse; namespace ArachnaeSwarm { + public class HoneyProductionExtension : DefModExtension + { + // 蜜罐生产的基础转化率(相对于食物流失的百分比) + public float baseConversionRate = 0.5f; + + // 最大蜜罐容量(可选,覆盖默认值) + public float maxHoneyCapacity = 1f; + + // 是否启用蜜罐生产 + public bool enableHoneyProduction = true; + + // 生产速率乘数(影响生产速度) + public float productionSpeedFactor = 1f; + + // 蜜罐类别对应的生产效率(可选,覆盖默认值) + public float fullProductionEfficiency = 1.5f; + public float highProductionEfficiency = 1.2f; + public float mediumProductionEfficiency = 1f; + public float lowProductionEfficiency = 0.5f; + public float emptyProductionEfficiency = 0f; + + // 生产间隔(ticks,可选覆盖默认值) + public int productionInterval = 150; + + public static HoneyProductionExtension Get(Pawn pawn) + { + if (pawn?.def?.GetModExtension() is HoneyProductionExtension ext) + return ext; + return null; + } + + // 获取转化率 + public float GetConversionRate() + { + return baseConversionRate * productionSpeedFactor; + } + } + public class Need_HoneyProduction : Need { // 基础流失速率(与食物需要对应) - private const float BaseHoneyGainPerTick = 2.6666667E-05f * 0.5f; // 食物流失速率的50% + private const float BaseHoneyGainPerTick = 2.6666667E-05f * 0.5f; // 食物流失速率的50%作为默认值 // 用于存储对食物需要的引用 private Need_Food cachedFoodNeed; @@ -18,8 +57,22 @@ namespace ArachnaeSwarm // 上次满的时间点 private int lastFullTick = -99999; - // 蜜罐的最大容量(可能需要调整) - public override float MaxLevel => 1f; + // 缓存的ModExtension + private HoneyProductionExtension cachedExtension; + + // 蜜罐的最大容量 - 优先使用ModExtension的值 + public override float MaxLevel + { + get + { + var ext = GetExtension(); + if (ext != null && ext.maxHoneyCapacity > 0) + { + return ext.maxHoneyCapacity; + } + return FoodNeed?.MaxLevel ?? 1f; + } + } // 当前类别 public HoneyProductionCategory CurCategory => curCategoryInt; @@ -35,6 +88,29 @@ namespace ArachnaeSwarm { get { + var ext = GetExtension(); + + // 如果有扩展定义,使用扩展的值 + if (ext != null) + { + switch (curCategoryInt) + { + case HoneyProductionCategory.Full: + return ext.fullProductionEfficiency; + case HoneyProductionCategory.High: + return ext.highProductionEfficiency; + case HoneyProductionCategory.Medium: + return ext.mediumProductionEfficiency; + case HoneyProductionCategory.Low: + return ext.lowProductionEfficiency; + case HoneyProductionCategory.Empty: + return ext.emptyProductionEfficiency; + default: + return 0f; + } + } + + // 默认值 switch (curCategoryInt) { case HoneyProductionCategory.Full: @@ -53,12 +129,32 @@ namespace ArachnaeSwarm } } + // 获取扩展 + private HoneyProductionExtension GetExtension() + { + if (cachedExtension == null && pawn != null) + { + cachedExtension = pawn.def?.GetModExtension(); + } + return cachedExtension; + } + + // 是否启用蜜罐生产 + private bool IsEnabled + { + get + { + var ext = GetExtension(); + return ext == null || ext.enableHoneyProduction; // 默认启用 + } + } + // 获取食物需要的引用 private Need_Food FoodNeed { get { - if (cachedFoodNeed == null || cachedFoodNeed.pawn != pawn) + if (cachedFoodNeed == null) { cachedFoodNeed = pawn.needs?.TryGetNeed(); } @@ -86,7 +182,9 @@ namespace ArachnaeSwarm public override void NeedInterval() { - base.NeedInterval(); + // 如果不启用,直接返回 + if (!IsEnabled) + return; // 检查是否需要冻结(与食物需要类似的条件) if (IsFrozen) @@ -97,11 +195,21 @@ namespace ArachnaeSwarm // 获取食物需要的流失速率 float foodFallRate = GetFoodFallRate(); - // 蜜罐的增长速率是食物流失速率的50% - float honeyGainRate = foodFallRate * 0.5f; + // 获取转化率(从ModExtension或使用默认值) + float conversionRate = GetExtension()?.GetConversionRate() ?? 0.5f; - // 应用150 ticks的间隔 - CurLevel += honeyGainRate * 150f; + // 蜜罐的增长速率 = 食物流失速率 × 转化率 + float honeyGainRate = foodFallRate * conversionRate; + + // 获取生产间隔 + int interval = GetExtension()?.productionInterval ?? 150; + + // 应用间隔 + CurLevel += honeyGainRate * interval; + + // 确保不超过最大容量 + if (CurLevel > MaxLevel) + CurLevel = MaxLevel; // 更新类别 UpdateCategory(); @@ -172,15 +280,39 @@ namespace ArachnaeSwarm { string text = (LabelCap + ": " + CurLevelPercentage.ToStringPercent()).Colorize(ColoredText.TipSectionTitleColor); text += "\n" + def.description; - text += $"\n\n{"AS.HoneyProduction.CurrentLevel".Translate()}: {CurLevel:0.##} / {MaxLevel:0.##}"; - text += $"\n{"AS.HoneyProduction.Category".Translate()}: {GetCategoryLabel().CapitalizeFirst()}"; - text += $"\n{"AS.HoneyProduction.Efficiency".Translate()}: {ProductionEfficiency.ToStringPercent()}"; - text += $"\n{"AS.HoneyProduction.FoodDrainRate".Translate()}: {GetFoodFallRate():0.#####}/tick"; - text += $"\n{"AS.HoneyProduction.HoneyGainRate".Translate()}: {(GetFoodFallRate() * 0.5f):0.#####}/tick"; + text += $"\n{"ARA_HoneyProduction.Efficiency".Translate()} {ProductionEfficiency.ToStringPercent()}"; + + // 获取每tick的速率 + float foodFallPerTick = GetFoodFallRate(); + float conversionRate = GetExtension()?.GetConversionRate() ?? 0.5f; + float honeyGainPerTick = foodFallPerTick * conversionRate; + + // 转换为每秒:1秒 = 60tick + float foodFallPerSecond = foodFallPerTick * 60f; + float honeyGainPerSecond = honeyGainPerTick * 60f; + + text += $"\n{"ARA_HoneyProduction.FoodDrainRate".Translate()}: {foodFallPerSecond:0.#####}/ {"LetterSecond".Translate()}"; + text += $"\n{"ARA_HoneyProduction.HoneyGainRate".Translate()}: {honeyGainPerSecond:0.#####}/ {"LetterSecond".Translate()}"; + + // 显示转化率信息 + if (GetExtension() != null) + { + text += $"\n{"ARA_HoneyProduction.ConversionRate".Translate()}: {conversionRate:P1}"; + if (GetExtension().productionSpeedFactor != 1f) + { + text += $"\n{"ARA_HoneyProduction.SpeedFactor".Translate()}: {GetExtension().productionSpeedFactor:F2}"; + } + } if (IsFull) { - text += $"\n\n{"AS.HoneyProduction.FullWarning".Translate()}"; + text += $"\n\n{"ARA_HoneyProduction.FullWarning".Translate()}"; + } + + // 显示是否启用 + if (!IsEnabled) + { + text += $"\n\n{"ARA_HoneyProduction.Disabled".Translate()}"; } return text; @@ -192,15 +324,15 @@ namespace ArachnaeSwarm switch (curCategoryInt) { case HoneyProductionCategory.Full: - return "AS.HoneyProduction.Full".Translate(); + return "ARA_HoneyProduction.Full".Translate(); case HoneyProductionCategory.High: - return "AS.HoneyProduction.High".Translate(); + return "ARA_HoneyProduction.High".Translate(); case HoneyProductionCategory.Medium: - return "AS.HoneyProduction.Medium".Translate(); + return "ARA_HoneyProduction.Medium".Translate(); case HoneyProductionCategory.Low: - return "AS.HoneyProduction.Low".Translate(); + return "ARA_HoneyProduction.Low".Translate(); case HoneyProductionCategory.Empty: - return "AS.HoneyProduction.Empty".Translate(); + return "ARA_HoneyProduction.Empty".Translate(); default: return "Unknown"; } @@ -209,6 +341,10 @@ namespace ArachnaeSwarm // 在UI上绘制 public override void DrawOnGUI(Rect rect, int maxThresholdMarkers = int.MaxValue, float customMargin = -1f, bool drawArrows = true, bool doTooltip = true, Rect? rectForTooltip = null, bool drawLabel = true) { + // 如果不启用,不显示 + if (!IsEnabled) + return; + if (threshPercents == null) { threshPercents = new System.Collections.Generic.List @@ -229,10 +365,6 @@ namespace ArachnaeSwarm if (base.IsFrozen) return true; - // 如果没有食物需要,或者食物需要被冻结,则蜜罐生产也冻结 - if (FoodNeed == null || FoodNeed.IsFrozen) - return true; - // 如果生物死亡,则冻结 if (pawn.Dead) return true; @@ -242,7 +374,7 @@ namespace ArachnaeSwarm } // GUI变化箭头(总是显示增长) - public override int GUIChangeArrow => 1; + public override int GUIChangeArrow => IsEnabled ? 1 : 0; // 调试调整百分比 protected override void OffsetDebugPercent(float offsetPercent) diff --git a/Source/ArachnaeSwarm/Placeworker/CompProperties_CustomRadius.cs b/Source/ArachnaeSwarm/Placeworker/CompProperties_CustomRadius.cs index 0144cc3..900502e 100644 --- a/Source/ArachnaeSwarm/Placeworker/CompProperties_CustomRadius.cs +++ b/Source/ArachnaeSwarm/Placeworker/CompProperties_CustomRadius.cs @@ -80,7 +80,7 @@ namespace ArachnaeSwarm // 尝试加载图标,如果失败则使用默认图标 try { - toggleCommand.icon = ContentFinder.Get("Wula/UI/Commands/WULA_ShowRadius", false); + toggleCommand.icon = ContentFinder.Get("ArachnaeSwarm/UI/Commands/ARA_ShowRadius", false); if (toggleCommand.icon == null) { // 使用一个简单的占位符图标 diff --git a/非公开资源/Content/Textures/UI/Commands/ARA_CallLarva.sai2 b/非公开资源/Content/Textures/UI/Commands/ARA_CallLarva.sai2 new file mode 100644 index 0000000..e7dc91f Binary files /dev/null and b/非公开资源/Content/Textures/UI/Commands/ARA_CallLarva.sai2 differ diff --git a/非公开资源/Content/Textures/UI/Commands/ARA_StripChitin.sai2 b/非公开资源/Content/Textures/UI/Commands/ARA_StripChitin.sai2 new file mode 100644 index 0000000..9ff5052 Binary files /dev/null and b/非公开资源/Content/Textures/UI/Commands/ARA_StripChitin.sai2 differ