diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index 6d23993..cb33305 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/Rooms/ARA_RoomRoles.xml b/1.6/1.6/Defs/Rooms/ARA_RoomRoles.xml
index 5169938..c5652a4 100644
--- a/1.6/1.6/Defs/Rooms/ARA_RoomRoles.xml
+++ b/1.6/1.6/Defs/Rooms/ARA_RoomRoles.xml
@@ -1,90 +1,12 @@
-
- None
-
- RoomRoleWorker_None
-
-
-
- Room
-
- RoomRoleWorker_Room
-
-
-
- Bedroom
-
- RoomRoleWorker_Bedroom
- true
+ ARA_Incubator_Room
+
+ ArachnaeSwarm.RoomRoleWorker_Incubator
- Beauty
- Cleanliness
- Wealth
Space
- Impressiveness
-
-
-
-
- PrisonCell
-
- RoomRoleWorker_PrisonCell
- true
-
- Beauty
- Cleanliness
- Wealth
- Space
- Impressiveness
-
-
-
-
- DiningRoom
-
- RoomRoleWorker_DiningRoom
-
- Beauty
- Cleanliness
- Wealth
- Space
- Impressiveness
-
-
-
-
- RecRoom
-
- RoomRoleWorker_RecRoom
-
- Beauty
- Cleanliness
- Wealth
- Space
- Impressiveness
-
-
-
-
- Hospital
-
- RoomRoleWorker_Hospital
- true
-
- Beauty
- Cleanliness
- Space
-
-
-
-
- Laboratory
-
- RoomRoleWorker_Laboratory
-
- Cleanliness
+ ARA_IncubatorRateFactor
diff --git a/1.6/1.6/Defs/Rooms/ARA_RoomStats.xml b/1.6/1.6/Defs/Rooms/ARA_RoomStats.xml
new file mode 100644
index 0000000..5027ede
--- /dev/null
+++ b/1.6/1.6/Defs/Rooms/ARA_RoomStats.xml
@@ -0,0 +1,20 @@
+
+
+
+ ARA_IncubatorQualityFactor
+
+ RoomStatWorker_FromStatByCurve
+ 0
+ true
+ 0.75
+ Space
+
+
+ (-5.0, 0.65 )
+ (-2.5, 0.75 )
+ ( 0.0, 0.85 )
+ ( 1.0, 1.00 )
+
+
+
+
diff --git a/1.6/1.6/Defs/Rooms/RoomStats.xml b/1.6/1.6/Defs/Rooms/RoomStats.xml
deleted file mode 100644
index 5619dd4..0000000
--- a/1.6/1.6/Defs/Rooms/RoomStats.xml
+++ /dev/null
@@ -1,308 +0,0 @@
-
-
-
-
-
-
- Impressiveness
-
- RoomStatWorker_Impressiveness
- 1
- true
- 0
-
-
-
-
-
- 20
-
-
-
- 30
-
-
-
- 40
-
-
-
- 50
-
-
-
- 65
-
-
-
- 85
-
-
-
- 120
-
-
-
- 170
-
-
-
- 240
-
-
-
-
-
-
- Wealth
-
- RoomStatWorker_Wealth
- 2
- true
- 0
-
-
-
-
-
- 500
-
-
-
- 700
-
-
-
- 2000
-
-
-
- 4000
-
-
-
- 10000
-
-
-
- 40000
-
-
-
- 100000
-
-
-
- 1000000
-
-
-
-
-
-
- Space
-
- RoomStatWorker_Space
- 2
- true
- 350
-
-
-
-
-
- 12.5
-
-
-
- 29
-
-
-
- 55
-
-
-
- 70
-
-
-
- 130
-
-
-
- 349.5
-
-
-
-
-
-
- Beauty
-
- RoomStatWorker_Beauty
- 2
- 0
-
-
-
-
-
- -3.5
-
-
-
- 0.0
-
-
-
- 2.4
-
-
-
- 5.0
-
-
-
- 15.0
-
-
-
- 50.0
-
-
-
- 100.0
-
-
-
-
-
-
- Cleanliness
-
- RoomStatWorker_Cleanliness
- 2
- 0
-
-
-
-
-
- -1.1
-
-
-
- -0.4
-
-
-
- -0.05
-
-
-
- 0.4
-
-
-
-
-
-
- ReadingBonus
-
- RoomStatWorker_ReadingBonus
- 1
- 0
- true
-
-
-
-
-
- InfectionChanceFactor
-
- RoomStatWorker_FromStatByCurve
- 0
- true
- 1.0
- Cleanliness
-
-
- (-5, 1.0)
- ( 0, 0.5)
- ( 1, 0.2)
-
-
-
-
-
- SurgerySuccessChanceCleanlinessFactor
-
- RoomStatWorker_FromStatByCurve
- 0
- true
- 0.6
- Cleanliness
-
-
- (-5, 0.6)
- ( 0, 1.0)
- ( 1, 1.10)
- ( 5, 1.15)
-
-
-
-
-
- ARA_ResearchSpeedFactor
-
- RoomStatWorker_FromStatByCurve
- 0
- true
- 0.75
- Space
-
-
- (-5.0, 0.75 )
- (-2.5, 0.85 )
- ( 0.0, 1.00 )
- ( 1.0, 1.15 )
-
-
-
-
-
- GraveVisitingJoyGainFactor
-
- RoomStatWorker_FromStatByCurve
- 0
- true
- 1
- Impressiveness
-
-
- (-150, 1.0)
- ( 0, 1.0)
- ( 150, 1.4)
-
-
-
-
-
- FoodPoisonChance
-
- RoomStatWorker_FromStatByCurve
- 0
- true
- 0.02
- Cleanliness
-
-
- (-5, 0.05)
- (-3.5, 0.025)
- (-2, 0)
-
-
-
-
-
diff --git a/1.6/1.6/Defs/Thing_Misc/Apparels/ARA_Apparel.xml b/1.6/1.6/Defs/Thing_Misc/Apparels/ARA_Apparel.xml
index a52de07..14efbed 100644
--- a/1.6/1.6/Defs/Thing_Misc/Apparels/ARA_Apparel.xml
+++ b/1.6/1.6/Defs/Thing_Misc/Apparels/ARA_Apparel.xml
@@ -773,7 +773,6 @@
-
ARA_Shield
diff --git a/1.6/1.6/Defs/Thing_building/ARA_Incubator.xml b/1.6/1.6/Defs/Thing_building/ARA_Incubator.xml
index 21e3f96..1e44266 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_Incubator.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_Incubator.xml
@@ -100,6 +100,7 @@
false
true
false
+ ARA_Incubator_Room
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
new file mode 100644
index 0000000..724dca0
--- /dev/null
+++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Building_Ootheca.xml
@@ -0,0 +1,100 @@
+
+
+
+ 正在孵化
+
+ 速度
+ 质量
+ 目标
+ 速度乘数
+ 质量乘数
+ 幼虫种种进入中
+ 剩余秒数
+ 幼虫种正在路上
+ 剩余时间
+ 天
+ 小时
+ 指派幼虫种
+ 取消孵化
+ 取消当前的孵化进度,幼虫种将死在卵囊中,卵囊则会重新张开以接纳其他幼虫种
+ 孵化
+ 无
+ 选择要孵化的目标,点击更改目标。
+ 未选择目标
+ 基础孵化时间
+ 天
+ 研究
+ 已完成
+ 需要
+ 点击选择不同的孵化目标。
+ 已经在孵化中
+ 请先取消当前孵化
+ 一只幼虫种已经在前往这个卵囊的路上
+ 没有找到可用的幼虫种
+ 必须是阿拉克涅幼虫种,并且属于你的派系
+ 已召唤幼虫种
+ 它将很快到达并激活卵囊
+ 幼虫种已到达
+ 正在激活卵囊...
+ 已开始孵化
+ 过程将在
+ 天后完成(基础时间)
+ 孵化已取消
+ 内容已丢失
+ 孵化完成
+ 已诞生,质量为
+ 优秀
+ 良好
+ 一般
+ 差
+ 极差
+ 孵化速度因子:
+ ✓ 在孵化间中 (100%)
+ ✗ 不在孵化间中 (80%速度)
+ ✓ 营养液
+ ✗ 附近没有营养液
+ 总速度乘数:{0}
+ 孵化质量因子:
+ ✓ 建筑血量:
+ ✓ 房间因子:100%
+ 房间因子:
+ ✗ 附近卵囊:
+ ✓ 附近没有卵囊
+ 总质量乘数:{0}
+ 召唤一只幼虫种来进入这个卵囊
+ 幼虫种将来到卵囊,进入它,然后开始孵化:
+ 当前速度乘数:
+ 当前质量乘数:
+ 取消当前的孵化过程,在其中的幼虫种将会死亡
+
+
+ 孵化:{0}
+ 点击更改目标
+ 选择孵化目标
+ 孵化目标:
+ 需要:
+ 无描述可用
+ 孵化时间
+ 孵化时间:{0}天
+
+
+ 孵化状态
+ 孵化进度
+ 这不是一个卵囊
+ 准备孵化:
+ 未选择孵化目标
+ 幼虫种正在激活卵囊...
+ 剩余秒数
+ 质量:低于目标(影响最终结果)
+ 质量进度
+ 孵化进度
+
+
+ 正在激活卵囊...
+
+
+ 质量低于目标
+
+
+ 需要研究
+
diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo
index 8c87dec..51cc3d0 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 625783b..fb5c6e3 100644
--- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
+++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
@@ -2,10 +2,6 @@
"Version": 1,
"WorkspaceRootPath": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
"Documents": [
- {
- "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\buildings\\building_ootheca\\itab_ootheca_incubation.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\itab_ootheca_incubation.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\buildings\\building_ootheca\\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}"
@@ -14,28 +10,36 @@
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\buildings\\building_ootheca\\compproperties_incubatordata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\compproperties_incubatordata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
+ {
+ "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\buildings\\building_ootheca\\itab_ootheca_incubation.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\itab_ootheca_incubation.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\buildings\\building_ootheca\\jobdriver_operateincubator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\jobdriver_operateincubator.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\\powerarmor\\jobdriver_enterpowerarmor.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\\roomroleworker_incubator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_ootheca\\roomroleworker_incubator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\powerarmor\\jobdriver_enterpowerarmor.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:powerarmor\\jobdriver_enterpowerarmor.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}",
+ "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\\powerarmor\\ara_powerarmor.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\\powerarmor\\ara_powerarmor.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:powerarmor\\ara_powerarmor.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_arachnaegravengine.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_arachnaegravengine.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:buildings\\building_arachnaegravengine.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\\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\\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}"
},
{
@@ -86,23 +90,49 @@
"DocumentGroups": [
{
"DockedWidth": 200,
- "SelectedChildIndex": 1,
+ "SelectedChildIndex": 2,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
},
+ {
+ "$type": "Document",
+ "DocumentIndex": 1,
+ "Title": "CompProperties_IncubatorData.cs",
+ "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
+ "RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
+ "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
+ "RelativeToolTip": "Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
+ "ViewState": "AgIAAH0AAAAAAAAAAAAAAK8AAAAJAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-12-14T12:41:01.454Z",
+ "EditorCaption": ""
+ },
{
"$type": "Document",
"DocumentIndex": 0,
+ "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": "AgIAAK4BAAAAAAAAAAAAAOUBAABIAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-12-14T13:27:29.463Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 2,
"Title": "ITab_Ootheca_Incubation.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\ITab_Ootheca_Incubation.cs",
"RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\ITab_Ootheca_Incubation.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\ITab_Ootheca_Incubation.cs",
"RelativeToolTip": "Buildings\\Building_Ootheca\\ITab_Ootheca_Incubation.cs",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAFQAAAAQAAAAAAAAAA==",
+ "ViewState": "AgIAAKwAAAAAAAAAAADwv+MAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-12-14T15:14:43.715Z",
+ "WhenOpened": "2025-12-14T16:21:37.25Z",
"EditorCaption": ""
},
{
@@ -113,40 +143,27 @@
"RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\JobDriver_OperateIncubator.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\JobDriver_OperateIncubator.cs",
"RelativeToolTip": "Buildings\\Building_Ootheca\\JobDriver_OperateIncubator.cs",
- "ViewState": "AgIAABQAAAAAAAAAAAA0wDgAAAAlAAAAAAAAAA==",
+ "ViewState": "AgIAAA4AAAAAAAAAAAAAwC0AAAAgAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-12-14T13:42:44.544Z",
"EditorCaption": ""
},
- {
- "$type": "Document",
- "DocumentIndex": 1,
- "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": "AgIAABAAAAAAAAAAAAAAwMsBAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-12-14T13:27:29.463Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 2,
- "Title": "CompProperties_IncubatorData.cs",
- "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
- "RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
- "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
- "RelativeToolTip": "Buildings\\Building_Ootheca\\CompProperties_IncubatorData.cs",
- "ViewState": "AgIAAFgAAAAAAAAAAIA+wJ8AAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-12-14T12:41:01.454Z",
- "EditorCaption": ""
- },
{
"$type": "Document",
"DocumentIndex": 4,
+ "Title": "RoomRoleWorker_Incubator.cs",
+ "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\RoomRoleWorker_Incubator.cs",
+ "RelativeDocumentMoniker": "Buildings\\Building_Ootheca\\RoomRoleWorker_Incubator.cs",
+ "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_Ootheca\\RoomRoleWorker_Incubator.cs",
+ "RelativeToolTip": "Buildings\\Building_Ootheca\\RoomRoleWorker_Incubator.cs",
+ "ViewState": "AgIAAAAAAAAAAAAAAADwvxYAAAA8AAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-12-14T16:16:56.635Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 5,
"Title": "JobDriver_EnterPowerArmor.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\JobDriver_EnterPowerArmor.cs",
"RelativeDocumentMoniker": "PowerArmor\\JobDriver_EnterPowerArmor.cs",
@@ -154,12 +171,11 @@
"RelativeToolTip": "PowerArmor\\JobDriver_EnterPowerArmor.cs",
"ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-12-14T13:53:00.443Z",
- "EditorCaption": ""
+ "WhenOpened": "2025-12-14T13:53:00.443Z"
},
{
"$type": "Document",
- "DocumentIndex": 5,
+ "DocumentIndex": 6,
"Title": "ARA_HediffDefOf.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\ARA_HediffDefOf.cs",
"RelativeDocumentMoniker": "ARA_HediffDefOf.cs",
@@ -167,12 +183,11 @@
"RelativeToolTip": "ARA_HediffDefOf.cs",
"ViewState": "AgIAAAAAAAAAAAAAAADwvxIAAAAkAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-12-14T13:29:05.1Z",
- "EditorCaption": ""
+ "WhenOpened": "2025-12-14T13:29:05.1Z"
},
{
"$type": "Document",
- "DocumentIndex": 6,
+ "DocumentIndex": 7,
"Title": "ARA_PowerArmor.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\ARA_PowerArmor.cs",
"RelativeDocumentMoniker": "PowerArmor\\ARA_PowerArmor.cs",
@@ -180,12 +195,11 @@
"RelativeToolTip": "PowerArmor\\ARA_PowerArmor.cs",
"ViewState": "AgIAAAAAAAAAAAAAAADwvxAAAABMAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-12-14T12:42:55.951Z",
- "EditorCaption": ""
+ "WhenOpened": "2025-12-14T12:42:55.951Z"
},
{
"$type": "Document",
- "DocumentIndex": 7,
+ "DocumentIndex": 8,
"Title": "Building_ArachnaeGravEngine.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_ArachnaeGravEngine.cs",
"RelativeDocumentMoniker": "Buildings\\Building_ArachnaeGravEngine.cs",
@@ -193,12 +207,11 @@
"RelativeToolTip": "Buildings\\Building_ArachnaeGravEngine.cs",
"ViewState": "AgIAAAAAAAAAAAAAAADwvxcAAAAiAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-12-14T12:37:14.137Z",
- "EditorCaption": ""
+ "WhenOpened": "2025-12-14T12:37:14.137Z"
},
{
"$type": "Document",
- "DocumentIndex": 9,
+ "DocumentIndex": 10,
"Title": "CompSectorSurveillance.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_SectorSurveillance\\CompSectorSurveillance.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_SectorSurveillance\\CompSectorSurveillance.cs",
@@ -210,7 +223,7 @@
},
{
"$type": "Document",
- "DocumentIndex": 8,
+ "DocumentIndex": 9,
"Title": "CompGroundStrafing.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_GroundStrafing\\CompGroundStrafing.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_GroundStrafing\\CompGroundStrafing.cs",
@@ -218,12 +231,11 @@
"RelativeToolTip": "Flyover\\ARA_GroundStrafing\\CompGroundStrafing.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAABsAAAAMAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-10-30T13:00:11.18Z",
- "EditorCaption": ""
+ "WhenOpened": "2025-10-30T13:00:11.18Z"
},
{
"$type": "Document",
- "DocumentIndex": 12,
+ "DocumentIndex": 13,
"Title": "CompAbilityEffect_GiveHediffWithSkillDuration.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_GiveHediffWithSkillDuration\\CompAbilityEffect_GiveHediffWithSkillDuration.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_GiveHediffWithSkillDuration\\CompAbilityEffect_GiveHediffWithSkillDuration.cs",
@@ -235,7 +247,7 @@
},
{
"$type": "Document",
- "DocumentIndex": 13,
+ "DocumentIndex": 14,
"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",
@@ -247,7 +259,7 @@
},
{
"$type": "Document",
- "DocumentIndex": 11,
+ "DocumentIndex": 12,
"Title": "CompAbilityEffect_AircraftStrike.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\CompAbilityEffect_AircraftStrike.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_AircraftHangar\\CompAbilityEffect_AircraftStrike.cs",
@@ -259,7 +271,7 @@
},
{
"$type": "Document",
- "DocumentIndex": 10,
+ "DocumentIndex": 11,
"Title": "ThingclassFlyOver.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ThingclassFlyOver.cs",
"RelativeDocumentMoniker": "Flyover\\ThingclassFlyOver.cs",
@@ -271,7 +283,7 @@
},
{
"$type": "Document",
- "DocumentIndex": 14,
+ "DocumentIndex": 15,
"Title": "CompAbilityEffect_AbilityShowSpawnablePawns.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowSpawnablePawnsList\\CompAbilityEffect_AbilityShowSpawnablePawns.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_ShowSpawnablePawnsList\\CompAbilityEffect_AbilityShowSpawnablePawns.cs",
@@ -283,7 +295,7 @@
},
{
"$type": "Document",
- "DocumentIndex": 16,
+ "DocumentIndex": 17,
"Title": "CompAircraftHangar.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\CompAircraftHangar.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_AircraftHangar\\CompAircraftHangar.cs",
@@ -295,7 +307,7 @@
},
{
"$type": "Document",
- "DocumentIndex": 15,
+ "DocumentIndex": 16,
"Title": "CompAbilityEffect_SpawnFlyOver.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_SpawnFlyOver\\CompAbilityEffect_SpawnFlyOver.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_SpawnFlyOver\\CompAbilityEffect_SpawnFlyOver.cs",
@@ -307,7 +319,7 @@
},
{
"$type": "Document",
- "DocumentIndex": 17,
+ "DocumentIndex": 18,
"Title": "WorldComponent_AircraftManager.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_AircraftHangar\\WorldComponent_AircraftManager.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_AircraftHangar\\WorldComponent_AircraftManager.cs",
@@ -319,7 +331,7 @@
},
{
"$type": "Document",
- "DocumentIndex": 18,
+ "DocumentIndex": 19,
"Title": "CompProperties_FlyOverEscort.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index 2131cef..3d6fc9f 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -119,6 +119,7 @@
+
diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs
new file mode 100644
index 0000000..ee53ef0
--- /dev/null
+++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs
@@ -0,0 +1,840 @@
+using RimWorld;
+using System.Collections.Generic;
+using System.Text;
+using UnityEngine;
+using Verse;
+using Verse.AI;
+
+namespace ArachnaeSwarm
+{
+ public class Building_Ootheca : Building
+ {
+ // 引用组件
+ public CompIncubatorData IncubatorData => this.TryGetComp();
+
+ // 孵化状态 - 设为public以便ITab访问
+ public bool isIncubating = false;
+ public float incubationProgress = 0f;
+ public float incubationDuration = 0f;
+ public PawnKindDef incubatingPawnKind = null;
+
+ // 幼虫交互相关 - 设为public以便ITab访问
+ public Pawn assignedLarva = null;
+ public int larvaOperateTicksRemaining = 0;
+
+ // 速度乘数系统
+ private float speedMultiplier = 1.0f;
+ private int lastMultiplierUpdateTick = -1;
+ private const int MultiplierUpdateInterval = 250; // 每250ticks更新一次
+
+ // 质量系统
+ private float qualityMultiplier = 1.0f;
+ private float qualityProgress = 0f;
+ private float qualityTotal = 0f;
+
+ // 属性访问器
+ public float SpeedMultiplier
+ {
+ get
+ {
+ // 每MultiplierUpdateInterval ticks更新一次
+ 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("IncubationSpeedFactors".Translate());
+ builder.AppendLine();
+
+ // 1. 检查是否在孵化间中
+ bool inIncubatorRoom = IsInIncubatorRoom();
+ builder.AppendLine(inIncubatorRoom ?
+ "InIncubatorRoom".Translate() :
+ "NotInIncubatorRoom".Translate());
+
+ // 2. 检查营养液数量
+ int nutrientSolutionCount = CountNearbyNutrientSolutions();
+ if (nutrientSolutionCount > 0)
+ {
+ builder.AppendLine("NutrientSolutions".Translate(nutrientSolutionCount, nutrientSolutionCount));
+ }
+ else
+ {
+ builder.AppendLine("NoNutrientSolutionsNearby".Translate());
+ }
+
+ builder.AppendLine();
+ builder.AppendLine("TotalSpeedMultiplier".Translate(SpeedMultiplier.ToStringPercent()));
+
+ return builder.ToString().TrimEndNewlines();
+ }
+ // 获取质量乘数的详细因子信息(用于工具提示)
+ public string GetQualityFactorsDescription()
+ {
+ var builder = new StringBuilder();
+
+ builder.AppendLine("IncubationQualityFactors".Translate());
+ builder.AppendLine();
+
+ // 1. 建筑血量损失百分比
+ float healthPercent = (float)HitPoints / MaxHitPoints;
+ builder.AppendLine("BuildingHealth".Translate(healthPercent.ToStringPercent()));
+
+ // 2. 房间的ARA_IncubatorQualityFactor
+ float roomFactor = GetRoomQualityFactor();
+ builder.AppendLine(roomFactor == 1.0f ?
+ "RoomFactorNormal".Translate() :
+ $"{(roomFactor > 1.0f ? "✓" : "✗")} {"RoomFactorModified".Translate()}{roomFactor.ToStringPercent()}");
+
+ // 3. 附近每一个ARA_Pawn_Ootheca,每一个-10%
+ int nearbyOothecaCount = CountNearbyOtherOothecas();
+ if (nearbyOothecaCount > 0)
+ {
+ builder.AppendLine("NearbyOothecas".Translate(nearbyOothecaCount, Mathf.Min(nearbyOothecaCount * 10, 100)));
+ }
+ else
+ {
+ builder.AppendLine("NoNearbyOothecas".Translate());
+ }
+
+ builder.AppendLine();
+ builder.AppendLine("TotalQualityMultiplier".Translate(QualityMultiplier.ToStringPercent()));
+
+ return builder.ToString().TrimEndNewlines();
+ }
+ // 构建呼叫幼虫描述
+ private string BuildCallLarvaDescription(IncubationConfig config)
+ {
+ var builder = new StringBuilder();
+
+ builder.AppendLine("CallALarvaToActivate".Translate());
+ builder.AppendLine();
+ builder.AppendLine("LarvaWillComeToTheOotheca".Translate());
+ builder.AppendLine(config.pawnKind.LabelCap);
+ builder.AppendLine();
+
+ return builder.ToString().TrimEndNewlines();
+ }
+ // 呼叫幼虫
+ private void CallLarva()
+ {
+ // 检查是否已经在孵化中或有幼虫在任务中
+ if (isIncubating)
+ {
+ Messages.Message("AlreadyIncubating".Translate() + " " + "CancelCurrentIncubationFirst".Translate(),
+ MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ if (assignedLarva != null)
+ {
+ Messages.Message("LarvaAlreadyOnTheWay".Translate(),
+ MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ // 查找可用的幼虫
+ var larva = FindLarva();
+ if (larva == null)
+ {
+ Messages.Message("NoAvailableLarvaeFound".Translate() + " " + "LarvaMustBeOfRace".Translate(),
+ MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ // 给幼虫分配任务
+ var job = JobMaker.MakeJob(ARA_JobDefOf.ARA_OperateIncubator, this);
+ job.count = 1;
+ larva.jobs.TryTakeOrderedJob(job, JobTag.MiscWork);
+
+ assignedLarva = larva;
+
+ Messages.Message("LarvaCalled".Translate() + " " + "ItWillArriveShortly".Translate(),
+ MessageTypeDefOf.PositiveEvent);
+ }
+ // 幼虫开始操作
+ public void NotifyLarvaArrived(Pawn larva)
+ {
+ // 验证幼虫种族
+ if (larva.def.defName != "ArachnaeBase_Race_Larva")
+ {
+ Log.Warning($"Invalid larva arrived: {larva.def.defName}");
+ return;
+ }
+
+ // 开始操作计时(3秒 = 180 ticks)
+ larvaOperateTicksRemaining = 180;
+ assignedLarva = larva;
+
+ // 显示消息
+ Messages.Message("LarvaHasArrived".Translate() + " " + "AndIsActivatingTheOotheca".Translate(),
+ MessageTypeDefOf.SilentInput);
+ }
+ // 幼虫完成操作(由JobDriver调用)
+ public void NotifyLarvaOperationComplete(Pawn larva)
+ {
+ // 验证是当前分配的幼虫
+ if (larva != assignedLarva)
+ {
+ Log.Warning("Larva operation complete called with wrong larva.");
+ return;
+ }
+
+ var config = IncubatorData?.SelectedConfig;
+ if (config == null)
+ {
+ Log.Error("No incubation config selected when larva completed operation.");
+ return;
+ }
+
+ // 保存孵化信息
+ incubatingPawnKind = config.pawnKind;
+ incubationDuration = config.daysRequired * 60000f; // RimWorld中1天=60000ticks
+ incubationProgress = 0f;
+ isIncubating = true;
+
+ // 初始化质量系统
+ qualityTotal = incubationDuration; // 质量总量与孵化时间相同
+ qualityProgress = 0f;
+ UpdateQualityMultiplier();
+
+ // 更新速度乘数
+ UpdateSpeedMultiplier();
+
+ // 清除幼虫引用(幼虫已在JobDriver中被删除)
+ assignedLarva = null;
+ larvaOperateTicksRemaining = 0;
+
+ // 显示消息
+ Messages.Message("IncubationStartedFor".Translate() + " " + incubatingPawnKind.LabelCap + ". " +
+ "ProcessWillCompleteIn".Translate() + " " + config.daysRequired + " " + "DaysBaseTime".Translate(),
+ MessageTypeDefOf.PositiveEvent);
+ }
+ // 取消孵化
+ private void CancelIncubation()
+ {
+ if (!isIncubating) return;
+
+ isIncubating = false;
+ incubationProgress = 0f;
+ incubationDuration = 0f;
+ incubatingPawnKind = null;
+ qualityProgress = 0f;
+ qualityTotal = 0f;
+
+ Messages.Message("IncubationCancelled".Translate() + " " + "ContentsLost".Translate(),
+ MessageTypeDefOf.NeutralEvent);
+ }
+ // 完成孵化
+ private void CompleteIncubation()
+ {
+ if (incubatingPawnKind == null) return;
+
+ // 计算最终质量百分比
+ float finalQualityPercent = QualityPercent;
+
+ // 生成新的pawn
+ var pawn = PawnGenerator.GeneratePawn(incubatingPawnKind, Faction);
+
+ // 应用质量影响
+ ApplyQualityEffects(pawn, finalQualityPercent);
+
+ // 尝试将pawn放置在地图上
+ var spawnPos = Position;
+ if (!pawn.Spawned)
+ {
+ GenSpawn.Spawn(pawn, spawnPos, Map);
+ }
+
+ // 重置孵化状态
+ isIncubating = false;
+ incubationProgress = 0f;
+ incubationDuration = 0f;
+ incubatingPawnKind = null;
+ qualityProgress = 0f;
+ 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();
+
+ Messages.Message("IncubationComplete".Translate() + " " + pawn.LabelCap + " " +
+ "HasEmergedWith".Translate() + " " + qualityText + " " +
+ "quality".Translate() + " (" + finalQualityPercent.ToStringPercent() + ").",
+ MessageTypeDefOf.PositiveEvent);
+ }
+ // 显示额外信息(简化版本,只显示速率,不显示因子)
+ public override string GetInspectString()
+ {
+ var baseString = base.GetInspectString();
+ var builder = new StringBuilder();
+
+ if (!string.IsNullOrEmpty(baseString))
+ {
+ builder.Append(baseString);
+ }
+
+ if (isIncubating && incubatingPawnKind != null)
+ {
+ float progressPercent = AdjustedProgressPercent;
+ float daysRemaining = GetRemainingDays();
+ float hoursRemaining = GetRemainingHours();
+
+ if (builder.Length > 0) builder.AppendLine();
+ builder.Append("Incubating".Translate() + ": " + incubatingPawnKind.LabelCap);
+ builder.AppendLine();
+ builder.Append("Progress".Translate() + ": " + progressPercent.ToStringPercent());
+ builder.AppendLine();
+
+ // 显示剩余时间
+ string timeText = "TimeRemaining".Translate() + ": " + daysRemaining.ToString("F1") + " " + "Days".Translate();
+ if (hoursRemaining > 0.1f && daysRemaining < 1f)
+ {
+ timeText += " (" + hoursRemaining.ToString("F1") + " " + "Hours".Translate() + ")";
+ }
+ builder.Append(timeText);
+
+ // 显示速度和质量(简化版本)
+ builder.AppendLine();
+ builder.Append("Speed".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " +
+ "Quality".Translate() + ": " + QualityMultiplier.ToStringPercent());
+ }
+ else if (assignedLarva != null)
+ {
+ if (builder.Length > 0) builder.AppendLine();
+ if (larvaOperateTicksRemaining > 0)
+ {
+ float secondsRemaining = larvaOperateTicksRemaining / 60f;
+ builder.Append("LarvaIsOperating".Translate() + ": " + secondsRemaining.ToString("F1") + " " + "SRemaining".Translate());
+ }
+ else
+ {
+ builder.Append("LarvaIsOnTheWay".Translate());
+ }
+ }
+ else if (!isIncubating)
+ {
+ var config = IncubatorData?.SelectedConfig;
+ if (config != null)
+ {
+ if (builder.Length > 0) builder.AppendLine();
+ builder.Append("Target".Translate() + ": " + config.pawnKind.LabelCap);
+
+ // 只显示当前乘数,不显示条件详情
+ builder.AppendLine();
+ builder.Append("SpeedMultiplier".Translate() + ": " + SpeedMultiplier.ToStringPercent() + ", " +
+ "QualityMultiplier".Translate() + ": " + QualityMultiplier.ToStringPercent());
+ }
+ }
+
+ return builder.ToString().TrimEndNewlines();
+ }
+
+ // 获取剩余时间(ticks)
+ public float GetRemainingTicks()
+ {
+ if (!isIncubating || incubationDuration <= incubationProgress) return 0f;
+
+ float remainingProgress = incubationDuration - incubationProgress;
+ float currentSpeed = SpeedMultiplier;
+
+ // 如果速度为0或负数,返回一个很大的数
+ if (currentSpeed <= 0) return float.MaxValue;
+
+ return remainingProgress / currentSpeed;
+ }
+
+ // 获取剩余天数
+ public float GetRemainingDays()
+ {
+ return GetRemainingTicks() / 60000f; // RimWorld中1天=60000ticks
+ }
+
+ // 获取剩余小时
+ public float GetRemainingHours()
+ {
+ float remainingTicks = GetRemainingTicks();
+ return (remainingTicks % 60000f) / 2500f;
+ }
+
+ // Gizmos
+ public override IEnumerable GetGizmos()
+ {
+ foreach (var gizmo in base.GetGizmos())
+ {
+ yield return gizmo;
+ }
+
+ // 只有玩家派系才显示Gizmo
+ if (Faction == Faction.OfPlayer)
+ {
+ // 不在孵化中时才显示切换目标按钮
+ if (!isIncubating && IncubatorData?.IncubationConfigs?.Count > 0)
+ {
+ yield return CreateTargetSwitchGizmo();
+ }
+
+ // 不在孵化中且研究完成时才显示呼叫按钮
+ var config = IncubatorData?.SelectedConfig;
+ if (!isIncubating && config != null && config.IsResearchComplete)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = "CallLarva".Translate(),
+ defaultDesc = BuildCallLarvaDescription(config),
+ icon = ContentFinder.Get("UI/Commands/CallLarva", false) ?? BaseContent.BadTex,
+ action = CallLarva,
+ hotKey = KeyBindingDefOf.Misc3
+ };
+ }
+
+ // 如果正在孵化,显示取消按钮
+ if (isIncubating)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = "CancelIncubation".Translate(),
+ defaultDesc = "CancelIncubationDesc".Translate(),
+ icon = ContentFinder.Get("UI/Commands/Cancel", false) ?? TexCommand.ClearPrioritizedWork,
+ action = CancelIncubation
+ };
+ }
+ }
+ }
+
+ // 创建目标切换Gizmo
+ private Gizmo CreateTargetSwitchGizmo()
+ {
+ var configs = IncubatorData?.IncubationConfigs;
+ if (configs == null || configs.Count == 0) return null;
+
+ var props = IncubatorData?.props as CompProperties_IncubatorData;
+ var selectedConfig = IncubatorData?.SelectedConfig;
+
+ var switchButton = new Command_Action
+ {
+ defaultLabel = BuildSwitchButtonLabel(selectedConfig, props),
+ defaultDesc = BuildSwitchButtonDescription(selectedConfig, props),
+ icon = LoadIcon(selectedConfig?.buttonIconPath ?? props?.defaultIconPath),
+ action = ShowSelectionMenu,
+ hotKey = KeyBindingDefOf.Misc2
+ };
+
+ // 如果当前配置不可用(研究未完成),禁用按钮
+ if (selectedConfig != null && !selectedConfig.IsResearchComplete)
+ {
+ if (selectedConfig.requiredResearch != null)
+ {
+ switchButton.Disable($"Requires research: {selectedConfig.requiredResearch.LabelCap}");
+ }
+ }
+
+ return switchButton;
+ }
+
+ // 构建切换按钮标签
+ private string BuildSwitchButtonLabel(IncubationConfig config, CompProperties_IncubatorData props)
+ {
+ if (config != null && config.pawnKind != null)
+ {
+ return (props?.buttonLabel ?? "Incubate: {0}").Translate(config.pawnKind.LabelCap);
+ }
+ return (props?.buttonLabel ?? "Incubate: {0}").Translate("None");
+ }
+
+ // 构建切换按钮描述
+ private string BuildSwitchButtonDescription(IncubationConfig config, CompProperties_IncubatorData props)
+ {
+ var builder = new StringBuilder();
+
+ // 第一部分:按钮功能说明
+ builder.AppendLine((props?.buttonDesc ?? "IncubatorButtonDesc").Translate());
+ builder.AppendLine();
+
+ if (config != null)
+ {
+ // 第二部分:当前选择的详细信息
+ if (config.pawnKind != null)
+ {
+ builder.AppendLine($"IncubatorButtonLabel".Translate(config.pawnKind.LabelCap));
+ if (!string.IsNullOrEmpty(config.pawnKind.description))
+ {
+ builder.AppendLine(config.pawnKind.description);
+ }
+ }
+
+ builder.AppendLine($"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)");
+ }
+ }
+ else
+ {
+ builder.AppendLine("No target selected");
+ }
+
+ builder.AppendLine();
+ builder.AppendLine("IncubatorButtonDesc".Translate());
+
+ return builder.ToString().TrimEndNewlines();
+ }
+
+ // 加载图标
+ private Texture2D LoadIcon(string path)
+ {
+ if (!string.IsNullOrEmpty(path))
+ {
+ var icon = ContentFinder.Get(path, false);
+ if (icon != null) return icon;
+ }
+ return ContentFinder.Get("UI/Commands/Default", false) ?? BaseContent.BadTex;
+ }
+
+ // 显示选择菜单
+ private void ShowSelectionMenu()
+ {
+ var configs = IncubatorData?.IncubationConfigs;
+ var props = IncubatorData?.props as CompProperties_IncubatorData;
+ if (configs == null || configs.Count == 0) return;
+
+ var options = new List();
+ int currentIndex = IncubatorData.GetSelectedIndex();
+
+ for (int i = 0; i < configs.Count; i++)
+ {
+ int index = i; // 捕获索引
+ var config = configs[i];
+ if (config == null || config.pawnKind == null) continue;
+
+ string label = config.pawnKind.LabelCap;
+ string description = config.GetDescription();
+
+ // 标记当前选择的项目
+ string prefix = (i == currentIndex) ? "✓ " : " ";
+
+ // 创建选项
+ var 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 "+ "ResearchRequired".Translate() + " " + config.requiredResearch.LabelCap;
+ }
+
+ options.Add(option);
+ }
+
+ if (options.Count > 0)
+ {
+ Find.WindowStack.Add(new FloatMenu(options,
+ (props?.menuTitle ?? "Select Incubation Target").Translate()));
+ }
+ }
+
+ // 切换到特定配置
+ private void SwitchToConfig(int index)
+ {
+ if (IncubatorData != null)
+ {
+ IncubatorData.SwitchToConfig(index);
+ var config = IncubatorData.SelectedConfig;
+ if (config != null && config.pawnKind != null)
+ {
+ Messages.Message($"Incubation target switched to: {config.pawnKind.LabelCap}",
+ this, MessageTypeDefOf.SilentInput);
+ }
+ }
+ }
+
+ // 查找幼虫 - 现在通过种族查找
+ private Pawn FindLarva()
+ {
+ // 查找地图中属于玩家派系的ArachnaeBase_Race_Larva幼虫
+ var map = Map;
+ if (map == null) return null;
+
+ foreach (var pawn in map.mapPawns.SpawnedPawnsInFaction(Faction))
+ {
+ // 检查pawn种族是否为幼虫
+ if (pawn.def.defName == "ArachnaeBase_Race_Larva")
+ {
+ // 检查pawn是否能够移动且没有其他重要任务
+ if (!pawn.Downed && !pawn.InMentalState &&
+ pawn.mindState != null &&
+ (pawn.CurJob == null || pawn.CurJob.def != ARA_JobDefOf.ARA_OperateIncubator))
+ {
+ return pawn;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ // 孵化进度 - 使用override而不是new
+ protected override void Tick()
+ {
+ base.Tick();
+
+ // 减少操作剩余时间(仅用于显示)
+ if (larvaOperateTicksRemaining > 0)
+ {
+ larvaOperateTicksRemaining--;
+ // 注意:孵化启动现在由JobDriver触发,这里只更新显示
+ }
+
+ // 更新孵化进度和质量进度
+ if (isIncubating)
+ {
+ // 更新质量乘数(与速度乘数同步更新)
+ if (lastMultiplierUpdateTick < 0 || Find.TickManager.TicksGame - lastMultiplierUpdateTick >= MultiplierUpdateInterval)
+ {
+ UpdateSpeedMultiplier();
+ UpdateQualityMultiplier();
+ }
+
+ // 应用速度乘数
+ float currentSpeed = SpeedMultiplier;
+ incubationProgress += currentSpeed;
+
+ // 质量进度与速度同步增长,但乘以质量乘数
+ qualityProgress += currentSpeed * QualityMultiplier;
+
+ if (incubationProgress >= incubationDuration)
+ {
+ CompleteIncubation();
+ }
+ }
+ }
+
+ // 应用质量效果到生成的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);
+ // }
+ // }
+ //}
+ }
+
+ // 检查是否在孵化间中
+ private bool IsInIncubatorRoom()
+ {
+ var room = this.GetRoom();
+ if (room == null) return false;
+
+ return room.Role != null && room.Role.defName == "ARA_Incubator_Room";
+ }
+
+ // 计算周围4格内的营养液数量
+ private int CountNearbyNutrientSolutions()
+ {
+ var map = Map;
+ if (map == null) return 0;
+
+ int count = 0;
+ int radius = 4; // 4格半径
+
+ // 检查周围4格范围内的所有单元格
+ 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()
+ {
+ var room = this.GetRoom();
+ if (room == null) return 1.0f;
+
+ // 获取房间的ARA_IncubatorQualityFactor统计值
+ // 如果没有这个统计,返回默认值
+ var statDef = DefDatabase.GetNamedSilentFail("ARA_IncubatorQualityFactor");
+ if (statDef != null)
+ {
+ return room.GetStat(statDef);
+ }
+
+ return 1.0f;
+ }
+
+ // 计算附近其他Ootheca的数量
+ 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;
+
+ // 检查是否为ARA_Pawn_Ootheca
+ if (building.def.defName == "ARA_Pawn_Ootheca")
+ {
+ // 检查是否在同一个房间或附近
+ var room = building.GetRoom();
+ if (room != null)
+ {
+ // 如果在同一个房间,或者距离较近(10格内)
+ float distance = building.Position.DistanceTo(this.Position);
+ if (room == this.GetRoom() || distance <= 10f)
+ {
+ count++;
+ }
+ }
+ }
+ }
+
+ return count;
+ }
+
+ // 更新速度乘数
+ private void UpdateSpeedMultiplier()
+ {
+ float multiplier = 1.0f;
+
+ // 1. 检查是否在孵化间中
+ if (!IsInIncubatorRoom())
+ {
+ multiplier *= 0.8f; // 不在孵化间中,速度80%
+ }
+
+ // 2. 计算周围营养液的加成
+ int nutrientSolutionCount = CountNearbyNutrientSolutions();
+ float nutrientBonus = 1.0f + (nutrientSolutionCount * 0.01f); // 每个+1%
+
+ multiplier *= nutrientBonus;
+
+ speedMultiplier = multiplier;
+ lastMultiplierUpdateTick = Find.TickManager.TicksGame;
+ }
+
+ // 更新质量乘数
+ private void UpdateQualityMultiplier()
+ {
+ float multiplier = 1.0f;
+
+ // 1. 建筑血量损失百分比
+ float healthPercent = (float)HitPoints / MaxHitPoints;
+ multiplier *= healthPercent;
+
+ // 2. 房间的ARA_IncubatorQualityFactor
+ float roomFactor = GetRoomQualityFactor();
+ multiplier *= roomFactor;
+
+ // 3. 附近每一个ARA_Pawn_Ootheca,每一个-10%
+ int nearbyOothecaCount = CountNearbyOtherOothecas();
+ float oothecaPenalty = Mathf.Max(0f, 1.0f - (nearbyOothecaCount * 0.10f)); // 最多减到0
+ multiplier *= oothecaPenalty;
+
+ // 确保乘数在合理范围内(0-1)
+ 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 incubatingPawnKind, "incubatingPawnKind");
+ 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_Ootheca/CompProperties_IncubatorData.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/CompProperties_IncubatorData.cs
new file mode 100644
index 0000000..c4608bb
--- /dev/null
+++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/CompProperties_IncubatorData.cs
@@ -0,0 +1,178 @@
+using System.Collections.Generic;
+using System.Text;
+using RimWorld;
+using UnityEngine;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ // IncubationConfig 保持不变
+ public class IncubationConfig : IExposable
+ {
+ public PawnKindDef pawnKind;
+ public ResearchProjectDef requiredResearch;
+ public float daysRequired;
+ public string buttonIconPath;
+
+ public IncubationConfig() { }
+
+ public IncubationConfig(PawnKindDef pawnKind, ResearchProjectDef requiredResearch = null,
+ float daysRequired = 1f, string buttonIconPath = null)
+ {
+ this.pawnKind = pawnKind;
+ this.requiredResearch = requiredResearch;
+ this.daysRequired = daysRequired;
+ this.buttonIconPath = buttonIconPath;
+ }
+
+ public void ExposeData()
+ {
+ Scribe_Defs.Look(ref pawnKind, "pawnKind");
+ Scribe_Defs.Look(ref requiredResearch, "requiredResearch");
+ Scribe_Values.Look(ref daysRequired, "daysRequired", 1f);
+ Scribe_Values.Look(ref buttonIconPath, "buttonIconPath");
+ }
+
+ // 检查是否满足研究要求
+ public bool IsResearchComplete => requiredResearch == null || requiredResearch.IsFinished;
+
+ // 在配置描述中使用翻译
+ public string GetDescription()
+ {
+ var builder = new StringBuilder();
+ builder.AppendLine(pawnKind?.description ?? "NoDescriptionAvailable".Translate());
+ builder.AppendLine();
+ builder.AppendLine("IncubationTime".Translate() + " " + daysRequired + " " + "DaysRequired".Translate());
+
+ if (requiredResearch != null)
+ {
+ if (requiredResearch.IsFinished)
+ builder.AppendLine("Research".Translate() + ": " + requiredResearch.LabelCap + " (" + "Completed".Translate() + ")");
+ else
+ builder.AppendLine("Research".Translate() + ": " + requiredResearch.LabelCap + " (" + "Required".Translate() + ")");
+ }
+
+ return builder.ToString().TrimEndNewlines();
+ }
+ }
+
+ // 组件属性定义
+ public class CompProperties_IncubatorData : CompProperties
+ {
+ public List incubationConfigs = new List();
+
+ // 默认选择索引
+ public int defaultIndex = 0;
+
+ // Gizmo相关配置
+ public string buttonLabel = "IncubatorButtonLabel"; // 按钮标签翻译键
+ public string buttonDesc = "IncubatorButtonDesc"; // 按钮描述翻译键
+ public string menuTitle = "IncubatorMenuTitle"; // 菜单标题翻译键
+ public string defaultIconPath = "UI/Commands/Default";
+
+ public CompProperties_IncubatorData()
+ {
+ compClass = typeof(CompIncubatorData);
+ }
+
+ public override void ResolveReferences(ThingDef parentDef)
+ {
+ base.ResolveReferences(parentDef);
+ if (incubationConfigs == null)
+ incubationConfigs = new List();
+ }
+ }
+
+ // 数据存储组件(仅数据,无Gizmo)
+ public class CompIncubatorData : ThingComp
+ {
+ private CompProperties_IncubatorData Props => (CompProperties_IncubatorData)props;
+
+ // 当前选择的配置索引
+ private int selectedIndex = -1;
+
+ // 公开获取孵化配置列表的方法
+ public List IncubationConfigs => Props?.incubationConfigs ?? new List();
+
+ // 获取当前选择的配置
+ public IncubationConfig 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];
+ }
+ }
+
+ // 获取当前选择的PawnKind
+ public PawnKindDef SelectedPawnKind => SelectedConfig?.pawnKind;
+
+ // 切换到特定索引
+ 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);
+
+ if (Scribe.mode == LoadSaveMode.PostLoadInit)
+ {
+ // 验证索引有效性
+ if (selectedIndex >= IncubationConfigs.Count)
+ selectedIndex = Mathf.Clamp(Props.defaultIndex, 0, IncubationConfigs.Count - 1);
+ }
+ }
+
+ // 在建筑信息中显示额外信息
+ public override string CompInspectStringExtra()
+ {
+ var current = SelectedConfig;
+ if (current != null)
+ {
+ string status = "IncubationTarget".Translate() + current.pawnKind.LabelCap;
+
+ if (current.requiredResearch != null && !current.requiredResearch.IsFinished)
+ {
+ status += " (" + "Requires".Translate() + ": " + current.requiredResearch.LabelCap + ")";
+ }
+
+ return status;
+ }
+ return base.CompInspectStringExtra();
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/ITab_Ootheca_Incubation.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/ITab_Ootheca_Incubation.cs
new file mode 100644
index 0000000..6309151
--- /dev/null
+++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/ITab_Ootheca_Incubation.cs
@@ -0,0 +1,369 @@
+using UnityEngine;
+using Verse;
+using System.Collections.Generic;
+using System.Linq;
+using RimWorld;
+using Verse.Sound;
+
+namespace ArachnaeSwarm
+{
+ public class ITab_Ootheca_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_Ootheca_Incubation()
+ {
+ size = new Vector2(TabWidth, TabHeight);
+ labelKey = "IncubationTab";
+ tutorTag = "Incubation";
+ }
+
+ protected override void FillTab()
+ {
+
+ Rect rect = new Rect(0f, 0f, size.x, size.y).ContractedBy(Margin);
+ Widgets.DrawMenuSection(rect);
+ Building_Ootheca ootheca = SelThing as Building_Ootheca;
+ if (ootheca == null)
+ {
+ Widgets.Label(rect, "NotAnOotheca".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 = "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 = "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 = "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.incubatingPawnKind != 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, "Target".Translate() + ": " + ootheca.incubatingPawnKind.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, "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, "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 = "TimeRemaining".Translate() + ": " + daysRemaining.ToString("F1") + " " + "Days".Translate();
+ if (hoursRemaining > 0.1f && daysRemaining < 1f)
+ {
+ timeText += " (" + hoursRemaining.ToString("F1") + " " + "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, "LarvaIsActivatingOotheca".Translate() + "\n" +
+ secondsRemaining.ToString("F1") + " " + "SecondsRemaining".Translate());
+ }
+ else
+ {
+ Widgets.Label(statusRect, "LarvaIsOnTheWay".Translate());
+ }
+
+ // 为幼虫状态增加一些间距
+ curY += SmallLabelHeight * 2 + 15f;
+
+ // 不在孵化中时,也显示目标配置
+ var config = ootheca.IncubatorData?.SelectedConfig;
+ if (config != null)
+ {
+ curY += 10f;
+ Rect targetRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight * 3);
+ string targetText = "ReadyToIncubate".Translate() + "\n" + config.pawnKind.LabelCap;
+
+ if (!config.IsResearchComplete && config.requiredResearch != null)
+ {
+ targetText += "\n" + "(" + "Requires".Translate() + ": " + config.requiredResearch.LabelCap + ")";
+ }
+
+ Widgets.Label(targetRect, targetText);
+ }
+ }
+ else
+ {
+ // 不在孵化中,显示当前选择的目标
+ var config = ootheca.IncubatorData?.SelectedConfig;
+ if (config != null)
+ {
+ Rect targetRect = new Rect(0f, curY, viewRect.width, SmallLabelHeight * 3);
+ string targetText = "ReadyToIncubate".Translate() + "\n" + config.pawnKind.LabelCap;
+
+ if (!config.IsResearchComplete && config.requiredResearch != null)
+ {
+ targetText += "\n" + "(" + "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, "NoIncubationTargetSelected".Translate());
+
+ curY += SmallLabelHeight + 10f;
+ }
+ }
+
+ // 添加一些底部空白区域,让布局看起来不那么拥挤
+ curY += 20f;
+
+ // 更新滚动区域的实际高度
+ viewRect.height = curY;
+
+ Widgets.EndScrollView();
+ }
+
+ // 检查是否在孵化间中(ITab内部使用)
+ private bool IsInIncubatorRoom(Building_Ootheca ootheca)
+ {
+ var room = ootheca.GetRoom();
+ if (room == null) return false;
+
+ return room.Role != null && room.Role.defName == "ARA_Incubator_Room";
+ }
+
+ // 计算周围4格内的营养液数量(ITab内部使用)
+ private int CountNearbyNutrientSolutions(Building_Ootheca ootheca)
+ {
+ var map = ootheca.Map;
+ if (map == null) return 0;
+
+ int count = 0;
+ int radius = 4; // 4格半径
+ var position = ootheca.Position;
+
+ // 检查周围4格范围内的所有单元格
+ 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;
+ }
+
+ // 计算房间质量因子(ITab内部使用)
+ private float GetRoomQualityFactor(Building_Ootheca ootheca)
+ {
+ var room = ootheca.GetRoom();
+ if (room == null) return 1.0f;
+
+ // 获取房间的ARA_IncubatorQualityFactor统计值
+ var statDef = DefDatabase.GetNamedSilentFail("ARA_IncubatorQualityFactor");
+ if (statDef != null)
+ {
+ return room.GetStat(statDef);
+ }
+
+ return 1.0f;
+ }
+
+ // 计算附近其他Ootheca的数量(ITab内部使用)
+ private int CountNearbyOtherOothecas(Building_Ootheca ootheca)
+ {
+ var map = ootheca.Map;
+ if (map == null) return 0;
+
+ int count = 0;
+ var position = ootheca.Position;
+ var room = ootheca.GetRoom();
+
+ // 查找地图上所有的Ootheca
+ var allOothecas = map.listerThings.ThingsOfDef(ootheca.def);
+
+ foreach (var thing in allOothecas)
+ {
+ // 排除自己
+ if (thing == ootheca) continue;
+
+ // 检查是否在同一个房间或附近(10格内)
+ float distance = thing.Position.DistanceTo(position);
+ var otherRoom = thing.GetRoom();
+
+ if ((room != null && room == otherRoom) || distance <= 10f)
+ {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+ protected override void UpdateSize()
+ {
+ // 固定大小
+ size = new Vector2(TabWidth, TabHeight);
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/JobDriver_OperateIncubator.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/JobDriver_OperateIncubator.cs
new file mode 100644
index 0000000..ae3ccae
--- /dev/null
+++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/JobDriver_OperateIncubator.cs
@@ -0,0 +1,80 @@
+using RimWorld;
+using System.Collections.Generic;
+using Verse;
+using Verse.AI;
+
+namespace ArachnaeSwarm
+{
+ public class JobDriver_OperateIncubator : JobDriver
+ {
+ private const int OperationDuration = 180; // 3 seconds = 180 ticks
+
+ // 目标建筑
+ private Building_Ootheca Ootheca => (Building_Ootheca)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(() => Ootheca == null);
+
+ // 1. 移动到建筑
+ yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.InteractionCell)
+ .FailOnSomeonePhysicallyInteracting(TargetIndex.A);
+
+ // 2. 等待片刻(让动物有时间转身)
+ yield return Toils_General.WaitWith(TargetIndex.A, 10, true, true);
+
+ // 3. 开始操作(3秒)
+ var operate = new Toil();
+ operate.initAction = () =>
+ {
+ // 通知建筑幼虫已到达
+ Ootheca?.NotifyLarvaArrived(pawn);
+ };
+ operate.tickAction = () =>
+ {
+ // 面向建筑
+ pawn.rotationTracker.FaceCell(Ootheca.Position);
+ };
+ operate.defaultCompleteMode = ToilCompleteMode.Delay;
+ operate.defaultDuration = OperationDuration;
+ operate.WithProgressBar(TargetIndex.A, () =>
+ (float)(OperationDuration - operate.actor.jobs.curDriver.ticksLeftThisToil) / OperationDuration);
+ yield return operate;
+
+ // 4. 完成操作并删除幼虫
+ yield return new Toil
+ {
+ initAction = () =>
+ {
+ // 操作完成,删除幼虫并开始孵化
+ if (Ootheca != null && pawn != null && pawn.def.defName == "ArachnaeBase_Race_Larva")
+ {
+ // 通知建筑幼虫操作完成
+ Ootheca.NotifyLarvaOperationComplete(pawn);
+
+ // 删除幼虫
+ pawn.Destroy(DestroyMode.Vanish);
+ }
+ },
+ defaultCompleteMode = ToilCompleteMode.Instant
+ };
+ }
+
+ public override string GetReport()
+ {
+ if (Ootheca != null)
+ {
+ return "ActivatingOotheca".Translate();
+ }
+ return base.GetReport();
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/RoomRoleWorker_Incubator.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/RoomRoleWorker_Incubator.cs
new file mode 100644
index 0000000..24da8eb
--- /dev/null
+++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/RoomRoleWorker_Incubator.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class RoomRoleWorker_Incubator : RoomRoleWorker
+ {
+ // 孵化间的评分 - 比重很小,设为10分(实验室是60分)
+ private const float ScorePerIncubator = 10f;
+
+ // 最低分数要求 - 需要至少1个孵化器才能成为孵化间
+ private const float MinimumScore = 10f;
+
+ public override float GetScore(Room room)
+ {
+ int incubatorCount = 0;
+
+ // 检查房间内和相邻的所有建筑
+ List containedAndAdjacentThings = room.ContainedAndAdjacentThings;
+ for (int i = 0; i < containedAndAdjacentThings.Count; i++)
+ {
+ Thing thing = containedAndAdjacentThings[i];
+
+ // 检查建筑的工作台房间角色是否为ARA_Incubator_Room
+ if (thing.def.building?.workTableRoomRole != null &&
+ thing.def.building.workTableRoomRole.defName == "ARA_Incubator_Room")
+ {
+ incubatorCount++;
+ }
+ }
+
+ // 如果有孵化器,返回分数
+ float score = ScorePerIncubator * incubatorCount;
+
+ // 必须有至少一个孵化器才能成为孵化间
+ return incubatorCount > 0 ? score : 0f;
+ }
+
+ public override float GetScoreDeltaIfBuildingPlaced(Room room, ThingDef buildingDef)
+ {
+ // 如果放置的建筑具有ARA_Incubator_Room工作台房间角色,返回分数增量
+ if (buildingDef.building?.workTableRoomRole != null &&
+ buildingDef.building.workTableRoomRole.defName == "ARA_Incubator_Room")
+ {
+ return ScorePerIncubator;
+ }
+ return 0f;
+ }
+ }
+}