diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index b1e57e8..fffe6fb 100644
Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ
diff --git a/1.6/1.6/Defs/AbilityDefs/Abilities_EggSpew.xml b/1.6/1.6/Defs/AbilityDefs/Abilities_EggSpew.xml
index 7a5cb08..e71af7e 100644
--- a/1.6/1.6/Defs/AbilityDefs/Abilities_EggSpew.xml
+++ b/1.6/1.6/Defs/AbilityDefs/Abilities_EggSpew.xml
@@ -699,12 +699,12 @@
AcidSpray_Resolve
- ARA_Cocoon_Weapon_2Stage
+ ARA_Cocoon_Medicine_From_Death
温度要求
true
- ARA_Cocoon_Weapon_2Stage
+ ARA_Cocoon_Medicine_From_Death
可孵化物品列表
true
true
diff --git a/1.6/1.6/Defs/FactionDefs/ARA_Factions_Hostile_Hive.xml b/1.6/1.6/Defs/FactionDefs/ARA_Factions_Hostile_Hive.xml
new file mode 100644
index 0000000..ab74b32
--- /dev/null
+++ b/1.6/1.6/Defs/FactionDefs/ARA_Factions_Hostile_Hive.xml
@@ -0,0 +1,45 @@
+
+
+
+ ARA_Hostile_Hive
+
+ 向闪耀世界方向侵略的阿拉克涅虫巢舰队被闪耀世界联军击败后,残留于星域中的虫群子个体。这些虫群子个体由于无法和蜂巢网络建立联系,其自行组织的反常网络只有低度的智能,只能让其成员如野兽般行动——然而她们依然是一群危险的敌人,于无数敌手交战留下的基因性反射使得她们的锋芒不减当年。\n\n她们不会对任何人展现仁慈,即使她们的对手是自己的同族。
+ 虫群
+ 虫群
+ 1
+ 阿拉克涅断须
+ World/WorldObjects/Expanding/Insects
+
+ (0.44, 0.41, 0.32)
+ (0.61, 0.58, 0.49)
+ (0.60, 0.49, 0.36)
+
+
+
+ (0, 0)
+
+
+
+
+ (100,100)
+ (10000,10000)
+
+
+
+
+
+ false
+ true
+ false
+ false
+ Animal
+ false
+
+ Entities
+
+ World/WorldObjects/DefaultSettlement
+ -2000~2000
+ 1
+ 1000
+
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/FactionDefs/ARA_Factions_Player.xml b/1.6/1.6/Defs/FactionDefs/ARA_Factions_Player.xml
index 1d71bc0..4c95cb5 100644
--- a/1.6/1.6/Defs/FactionDefs/ARA_Factions_Player.xml
+++ b/1.6/1.6/Defs/FactionDefs/ARA_Factions_Player.xml
@@ -19,7 +19,8 @@
ARA_New_Hive_NamerFaction
- NamerSettlementOutlander
+ ARA_NamerSettlement
+ ARA_NamerInitialSettlement
Astropolitan
World/WorldObjects/Expanding/Town
@@ -41,29 +42,58 @@
r_name->[hivename1] [hivename2]
- hivename1->猩红
- hivename1->至高
- hivename1->唯一
- hivename1->蔓延
- hivename1->永恒
- hivename1->永续
+ hivename1->虚空
+ hivename1->深渊
+ hivename1->吞噬
+ hivename1->进化
+ hivename1->原生
+ hivename1->融合
+ hivename1->蚀骨
- hivename2->核心
- hivename2->内核
- hivename2->要点
- hivename2->中心
- hivename2->焦点
- hivename2->原点
- hivename2->支点
- hivename2->枢轴
- hivename2->中枢
- hivename2->母体
- hivename2->源泉
- hivename2->源地
- hivename2->基体
- hivename2->始祖
- hivename2->主脑
- hivename2->巢心
+ hivename2->蜂巢
+ hivename2->虫群
+ hivename2->爪牙
+ hivename2->兽群
+ hivename2->触须
+
+
+
+
+ ARA_NamerSettlement
+
+
+ r_name->[hivesettlementname1] [hivesettlementname2]
+ hivesettlementname1->猩红
+ hivesettlementname1->至高
+ hivesettlementname1->唯一
+ hivesettlementname1->蔓延
+ hivesettlementname1->永恒
+ hivesettlementname1->永续
+
+ hivesettlementname2->核心
+ hivesettlementname2->内核
+ hivesettlementname2->要点
+ hivesettlementname2->中心
+ hivesettlementname2->焦点
+ hivesettlementname2->原点
+ hivesettlementname2->支点
+ hivesettlementname2->枢轴
+ hivesettlementname2->中枢
+ hivesettlementname2->母体
+ hivesettlementname2->源泉
+ hivesettlementname2->源地
+ hivesettlementname2->基体
+ hivesettlementname2->始祖
+ hivesettlementname2->主脑
+ hivesettlementname2->巢心
+
+
+
+
+ ARA_NamerInitialSettlement
+
+
+ r_name->虫巢
diff --git a/1.6/1.6/Defs/HiveRaidDef/ARA_CustomRaidDef.xml b/1.6/1.6/Defs/HiveRaidDef/ARA_CustomRaidDef.xml
new file mode 100644
index 0000000..9ef4f2f
--- /dev/null
+++ b/1.6/1.6/Defs/HiveRaidDef/ARA_CustomRaidDef.xml
@@ -0,0 +1,192 @@
+
+
+
+
+ ARA_Stage1_Raid
+ ARA_Hostile_Hive
+
+
+ 0
+ 800
+ ARA_WavePool_Stage1
+
+
+ 3
+
+ Linear
+ 1.2
+
+
+
+
+ ARA_WavePool_Stage1
+
+ ARA_Wave_Scout_Patrol
+ ARA_Wave_Assault_Team
+ ARA_Wave_Acid_Swarm
+ ARA_Wave_Heavy_Defense
+ ARA_Wave_Mixed_Forces
+
+
+
+ ARA_Wave_Scout_Patrol
+ 0.25
+
+
+ ARA_Wave_Assault_Team
+ 0.25
+
+
+ ARA_Wave_Acid_Swarm
+ 0.20
+
+
+ ARA_Wave_Heavy_Defense
+ 0.15
+
+
+ ARA_Wave_Mixed_Forces
+ 0.15
+
+
+
+
+
+
+ ARA_Wave_Scout_Patrol
+
+ 一支小型侦察队伍,主要由远程单位组成,进行骚扰射击
+
+
+ ARA_Raid_Shooter
+ 0.7
+ 2
+ 6
+ true
+
+
+ ARA_Raid_Assault
+ 0.3
+ 1
+ 3
+
+
+
+
+
+ ARA_Wave_Assault_Team
+
+ 以近战单位为主的快速突击队伍,擅长冲锋陷阵
+
+
+ ARA_Raid_Assault
+ 0.6
+ 3
+ 8
+ true
+
+
+ ARA_Raid_Shooter
+ 0.4
+ 2
+ 4
+
+
+
+
+
+ ARA_Wave_Acid_Swarm
+
+ 大量酸噬种辅虫组成的虫海战术,数量庞大但个体脆弱
+
+
+ ARA_Raid_AcidSwarm
+ 0.8
+ 8
+ 20
+ true
+
+
+ ARA_Raid_Assault
+ 0.2
+ 2
+ 5
+
+
+
+
+
+ ARA_Wave_Heavy_Defense
+
+ 以盾头种为主的防御型队伍,移动缓慢但防御力强
+
+
+ ARA_Raid_Heavy
+ 0.5
+ 2
+ 6
+ true
+
+
+ ARA_Raid_Shooter
+ 0.3
+ 2
+ 4
+
+
+ ARA_Raid_Acidling
+ 0.2
+ 3
+ 8
+
+
+ Defensive
+
+
+
+ ARA_Wave_Mixed_Forces
+
+ 均衡配置的混合部队,包含各种单位类型
+
+
+ ARA_Raid_Assault
+ 0.3
+ 2
+ 5
+
+
+ ARA_Raid_Shooter
+ 0.3
+ 2
+ 5
+
+
+ ARA_Raid_Heavy
+ 0.2
+ 1
+ 3
+
+
+ ARA_Raid_AcidSwarm
+ 0.2
+ 3
+ 6
+ true
+
+
+ Balanced
+
+
+
+
+ ARA_Raid_Incident
+
+ ArachnaeSwarm.IncidentWorker_CustomRaid
+ Special
+ 5
+ 0
+
+ Map_PlayerHome
+
+
+
diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_Hostile_Hive_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_Hostile_Hive_PawnKinds.xml
new file mode 100644
index 0000000..39e1e51
--- /dev/null
+++ b/1.6/1.6/Defs/PawnKindDef/ARA_Hostile_Hive_PawnKinds.xml
@@ -0,0 +1,144 @@
+
+
+
+
+ 150
+ true
+ false
+ 0
+ true
+ true
+ 99~99
+ 99~99
+ 1
+ true
+ 2
+ 1
+ false
+ ARA_Hostile_Hive
+ 0
+
+ Violent
+
+
+
+ ARA_Creep
+ 3.0
+
+
+
+
+
+
+
+ ARA_Raid_Assault
+
+ ArachnaeNode_Race_Fighter
+ 200
+
+ ARA_Armed_Organ_Melee
+
+
+ ARA_Inner
+ ARA_Clothes
+
+ 200
+ 200
+
+ ARA_BaseRace_Acid_Launcher
+
+
+
+
+ ARA_Raid_Shooter
+
+ ArachnaeNode_Race_Fighter
+ 180
+
+ ARA_Armed_Organ_Small_Ranged_Needle
+ ARA_Armed_Organ_Small_Ranged_Acid
+
+
+ ARA_Inner
+
+ 150
+ 400
+ 100
+
+ ARA_Hibernate_Ability
+
+
+
+
+ ARA_Raid_AcidSwarm
+
+ 80
+ 0
+ 0
+ 0
+ ArachnaeBase_Race_Acidcut
+
+
+
+ ArachnaeSwarm/Things/ARA_Acidcut/Bodies/Naked_Thin
+ 1
+
+ (0.4, 0.5, 0.37)
+ (0,0,-0.15)
+
+
+
+ Things/Pawn/Animal/Spelopede/Dessicated_Spelopede
+ 1
+
+
+
+
+
+
+
+
+ ARA_Raid_Heavy
+
+ ArachnaeNode_Race_ShieldHead
+ 350
+
+ ARA_Armed_Organ_Melee
+ ARA_Armed_Organ_Small_Ranged_Needle
+ ARA_Armed_Organ_Small_Ranged_Acid
+
+
+ ARA_Inner
+ ARA_Clothes
+
+ 400
+ 600
+ 0
+
+
+
+ ARA_Raid_Acidling
+
+ ArachnaeBase_Race_Acidling
+ 50
+ 0
+ 0
+ 0
+
+
+
+ ArachnaeSwarm/Things/ARA_Acidling/Bodies/Naked_Thin
+ 1
+
+ (0.4, 0.5, 0.37)
+ (0,0,-0.15)
+
+
+
+ Things/Pawn/Animal/Spelopede/Dessicated_Spelopede
+ 1
+
+
+
+
+
diff --git a/1.6/1.6/Defs/StoryTellers/ARA_Storytellers.xml b/1.6/1.6/Defs/StoryTellers/ARA_Storytellers.xml
index b08d1e2..26545dd 100644
--- a/1.6/1.6/Defs/StoryTellers/ARA_Storytellers.xml
+++ b/1.6/1.6/Defs/StoryTellers/ARA_Storytellers.xml
@@ -9,8 +9,14 @@
20
+
-
ThreatBig
15.0
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 c74d103..b5bcb12 100644
--- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml
+++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceQueen.xml
@@ -11,6 +11,8 @@
+
+
Megaspider
Humanlike
HumanlikeConstant
diff --git a/1.6/1.6/Defs/Thing_Misc/ARA_Medicine.xml b/1.6/1.6/Defs/Thing_Misc/ARA_Medicine.xml
index b278580..bbf88a2 100644
--- a/1.6/1.6/Defs/Thing_Misc/ARA_Medicine.xml
+++ b/1.6/1.6/Defs/Thing_Misc/ARA_Medicine.xml
@@ -31,6 +31,7 @@
@@ -87,6 +88,7 @@
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 079db41..d43b40d 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
@@ -12,6 +12,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Melee
ARA_Armed_Organ_T1
+ ARA_MW_Bone_Sword
ArachnaeSwarm/Weapon/ARA_MW_Bone_Sword
@@ -313,6 +314,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T1
+ ARA_Armed_Organ_Small_Ranged_Needle
0
None
@@ -427,6 +429,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T2
+ ARA_Armed_Organ_Small_Ranged_Needle
0
None
@@ -524,6 +527,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T2
+ ARA_Armed_Organ_Small_Ranged_Needle
0
None
@@ -637,6 +641,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T2
+ ARA_Armed_Organ_Huge_Ranged_Needle
0
None
@@ -752,6 +757,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T2
+ ARA_Armed_Organ_Small_Ranged_Needle
0
None
@@ -854,6 +860,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T1
+ ARA_Armed_Organ_Small_Ranged_Acid
0
None
@@ -969,6 +976,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T2
+ ARA_Armed_Organ_Huge_Ranged_Acid
0
None
@@ -1118,6 +1126,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T2
+ ARA_Armed_Organ_Huge_Ranged_Acid
0
None
@@ -1236,6 +1245,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T3
+ ARA_Armed_Organ_Small_Ranged_Acid
0
None
@@ -1370,6 +1380,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T3
+ ARA_Armed_Organ_Huge_Ranged_Acid
0
None
@@ -1506,6 +1517,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T1
+ ARA_Armed_Organ_Small_Ranged_SP
0
None
@@ -1658,6 +1670,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T2
+ ARA_Armed_Organ_Small_Ranged_Energy
0
None
@@ -1764,6 +1777,7 @@
ARA_Armed_Organ
ARA_Armed_Organ_Ranged
ARA_Armed_Organ_T2
+ ARA_Armed_Organ_Huge_Ranged_Energy
0
None
diff --git a/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml b/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml
index 141b984..4d958b8 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml
@@ -694,7 +694,7 @@
true
0.2
1
- 999
+ 20
@@ -715,7 +715,7 @@
true
1
1
- 999
+ 20
@@ -804,7 +804,7 @@
true
0.2
1
- 999
+ 20
@@ -825,7 +825,7 @@
true
1
1
- 999
+ 20
@@ -922,7 +922,7 @@
true
0.2
1
- 999
+ 60
@@ -943,7 +943,7 @@
true
1
1
- 999
+ 60
@@ -1035,7 +1035,7 @@
true
0.2
1
- 999
+ 60
@@ -1056,7 +1056,7 @@
true
1
1
- 999
+ 60
@@ -1145,7 +1145,7 @@
true
0.2
1
- 999
+ 110
@@ -1166,7 +1166,7 @@
true
1
1
- 999
+ 110
@@ -1279,7 +1279,7 @@
true
0.2
1
- 999
+ 110
@@ -1300,7 +1300,7 @@
true
1
1
- 999
+ 110
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 d81b3da..8616d04 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_RefuelingVat.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_RefuelingVat.xml
@@ -71,9 +71,8 @@
20
0
true
- 0
+ -1
true
- false
@@ -82,7 +81,7 @@
false
- true
+ false
true
diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo
index 3730808..c151d56 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 3073341..1c86b0f 100644
--- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
+++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
@@ -3,12 +3,8 @@
"WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
"Documents": [
{
- "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\abilities\\compabilityeffect_transformcorpse.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\compabilityeffect_transformcorpse.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\\building_comps\\comprefuelablenutrition.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\comprefuelablenutrition.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\\storyteller\\incidentworker_customraid.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:storyteller\\incidentworker_customraid.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}
],
"DocumentGroupContainers": [
@@ -18,35 +14,23 @@
"DocumentGroups": [
{
"DockedWidth": 200,
- "SelectedChildIndex": 2,
+ "SelectedChildIndex": 1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
},
- {
- "$type": "Document",
- "DocumentIndex": 1,
- "Title": "CompRefuelableNutrition.cs",
- "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\CompRefuelableNutrition.cs",
- "RelativeDocumentMoniker": "Building_Comps\\CompRefuelableNutrition.cs",
- "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\CompRefuelableNutrition.cs",
- "RelativeToolTip": "Building_Comps\\CompRefuelableNutrition.cs",
- "ViewState": "AgIAABAAAAAAAAAAAAAuwBYAAAAhAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-10-15T08:04:45.513Z"
- },
{
"$type": "Document",
"DocumentIndex": 0,
- "Title": "CompAbilityEffect_TransformCorpse.cs",
- "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\CompAbilityEffect_TransformCorpse.cs",
- "RelativeDocumentMoniker": "Abilities\\CompAbilityEffect_TransformCorpse.cs",
- "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\CompAbilityEffect_TransformCorpse.cs",
- "RelativeToolTip": "Abilities\\CompAbilityEffect_TransformCorpse.cs",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAABIAAABCAAAAAAAAAA==",
+ "Title": "IncidentWorker_CustomRaid.cs",
+ "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Storyteller\\IncidentWorker_CustomRaid.cs",
+ "RelativeDocumentMoniker": "Storyteller\\IncidentWorker_CustomRaid.cs",
+ "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Storyteller\\IncidentWorker_CustomRaid.cs",
+ "RelativeToolTip": "Storyteller\\IncidentWorker_CustomRaid.cs",
+ "ViewState": "AgIAAAYBAAAAAAAAAAAgwBYBAAAyAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-10-15T08:02:12.842Z",
+ "WhenOpened": "2025-10-16T07:14:58.682Z",
"EditorCaption": ""
}
]
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index ee5f244..e32f2e2 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -120,6 +120,12 @@
+
+
+
+
+
+
diff --git a/Source/ArachnaeSwarm/Storyteller/CustomRaidDef.cs b/Source/ArachnaeSwarm/Storyteller/CustomRaidDef.cs
new file mode 100644
index 0000000..b20c625
--- /dev/null
+++ b/Source/ArachnaeSwarm/Storyteller/CustomRaidDef.cs
@@ -0,0 +1,51 @@
+using RimWorld;
+using System.Collections.Generic;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class CustomRaidDef : Def
+ {
+ public FactionDef factionDef;
+ public List pointWavePools;
+ public int baseRaidNembers;
+ public PointsGrowthPerWave pointsGrowthPerWave;
+
+ public override IEnumerable ConfigErrors()
+ {
+ foreach (string error in base.ConfigErrors())
+ {
+ yield return error;
+ }
+
+ if (factionDef == null)
+ {
+ yield return "factionDef is not defined";
+ }
+
+ if (pointWavePools.NullOrEmpty())
+ {
+ yield return "pointWavePools is empty";
+ }
+
+ if (baseRaidNembers <= 0)
+ {
+ yield return "baseRaidNembers must be positive";
+ }
+ }
+ }
+
+ public class PointWavePool
+ {
+ public float minPoints;
+ public float maxPoints = 99999f; // 默认值表示无上限
+ public RaidWavePoolDef wavePool;
+ }
+
+ public class PointsGrowthPerWave
+ {
+ public string growthType = "Linear"; // Linear/Exponential
+ public float linearGrowth = 1f;
+ public float exponentialBase = 1.15f;
+ }
+}
diff --git a/Source/ArachnaeSwarm/Storyteller/CustomRaidTracker.cs b/Source/ArachnaeSwarm/Storyteller/CustomRaidTracker.cs
new file mode 100644
index 0000000..8f3bb18
--- /dev/null
+++ b/Source/ArachnaeSwarm/Storyteller/CustomRaidTracker.cs
@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class CustomRaidTracker : GameComponent
+ {
+ private Dictionary waveCounters = new Dictionary();
+
+ public CustomRaidTracker(Game game)
+ {
+ // 构造函数
+ }
+
+ public override void ExposeData()
+ {
+ base.ExposeData();
+ Scribe_Collections.Look(ref waveCounters, "waveCounters", LookMode.Value, LookMode.Value);
+
+ // 如果waveCounters为null(加载旧存档时可能发生),初始化它
+ if (waveCounters == null)
+ {
+ waveCounters = new Dictionary();
+ }
+ }
+
+ public int GetCurrentWave(CustomRaidDef raidDef)
+ {
+ if (raidDef == null)
+ {
+ Log.Warning("GetCurrentWave called with null raidDef");
+ return 0;
+ }
+
+ string key = raidDef.defName;
+ if (!waveCounters.ContainsKey(key))
+ {
+ waveCounters[key] = 0;
+ }
+
+ return waveCounters[key];
+ }
+
+ public void IncrementWave(CustomRaidDef raidDef)
+ {
+ if (raidDef != null)
+ {
+ string key = raidDef.defName;
+ int currentWave = GetCurrentWave(raidDef);
+ waveCounters[key] = currentWave + 1;
+
+ Log.Message($"CustomRaidTracker: Incremented wave for {raidDef.defName} to {waveCounters[key]}");
+ }
+ else
+ {
+ Log.Warning("IncrementWave called with null raidDef");
+ }
+ }
+
+ public void ResetWave(CustomRaidDef raidDef)
+ {
+ if (raidDef != null)
+ {
+ waveCounters[raidDef.defName] = 0;
+ }
+ }
+
+ public void ResetAllWaves()
+ {
+ waveCounters.Clear();
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Storyteller/IncidentParmsExtensions.cs b/Source/ArachnaeSwarm/Storyteller/IncidentParmsExtensions.cs
new file mode 100644
index 0000000..e1996b2
--- /dev/null
+++ b/Source/ArachnaeSwarm/Storyteller/IncidentParmsExtensions.cs
@@ -0,0 +1,111 @@
+using RimWorld;
+using System.Collections.Generic;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public static class IncidentParmsExtensions
+ {
+ // 使用静态字典来存储自定义数据
+ private static Dictionary customRaidData = new Dictionary();
+
+ public class CustomRaidData
+ {
+ public RaidWaveDef WaveDef { get; set; }
+ public int RaidSize { get; set; } = -1;
+ public CustomRaidDef RaidDef { get; set; }
+ public int WaveNumber { get; set; }
+ }
+
+ public static void SetCustomRaidWave(this IncidentParms parms, RaidWaveDef waveDef)
+ {
+ if (!customRaidData.ContainsKey(parms))
+ customRaidData[parms] = new CustomRaidData();
+
+ customRaidData[parms].WaveDef = waveDef;
+ }
+
+ public static RaidWaveDef GetCustomRaidWave(this IncidentParms parms)
+ {
+ if (!customRaidData.ContainsKey(parms))
+ return null;
+
+ return customRaidData[parms].WaveDef;
+ }
+
+ public static void SetCustomRaidSize(this IncidentParms parms, int raidSize)
+ {
+ if (!customRaidData.ContainsKey(parms))
+ customRaidData[parms] = new CustomRaidData();
+
+ customRaidData[parms].RaidSize = raidSize;
+ }
+
+ public static int GetCustomRaidSize(this IncidentParms parms)
+ {
+ if (!customRaidData.ContainsKey(parms))
+ return -1;
+
+ return customRaidData[parms].RaidSize;
+ }
+
+ public static void SetCustomRaidDef(this IncidentParms parms, CustomRaidDef raidDef)
+ {
+ if (!customRaidData.ContainsKey(parms))
+ customRaidData[parms] = new CustomRaidData();
+
+ customRaidData[parms].RaidDef = raidDef;
+ }
+
+ public static CustomRaidDef GetCustomRaidDef(this IncidentParms parms)
+ {
+ if (!customRaidData.ContainsKey(parms))
+ return null;
+
+ return customRaidData[parms].RaidDef;
+ }
+
+ public static void SetCustomRaidWaveNumber(this IncidentParms parms, int waveNumber)
+ {
+ if (!customRaidData.ContainsKey(parms))
+ customRaidData[parms] = new CustomRaidData();
+
+ customRaidData[parms].WaveNumber = waveNumber;
+ }
+
+ public static int GetCustomRaidWaveNumber(this IncidentParms parms)
+ {
+ if (!customRaidData.ContainsKey(parms))
+ return 0;
+
+ return customRaidData[parms].WaveNumber;
+ }
+
+ public static bool IsCustomRaid(this IncidentParms parms)
+ {
+ return parms.GetCustomRaidWave() != null;
+ }
+
+ // 清理方法,在事件完成后调用
+ public static void ClearCustomData(this IncidentParms parms)
+ {
+ if (customRaidData.ContainsKey(parms))
+ customRaidData.Remove(parms);
+ }
+
+ // 批量清理方法,用于清理所有不再使用的 IncidentParms
+ public static void CleanupOrphanedData()
+ {
+ // 这里可以添加逻辑来清理不再使用的 IncidentParms 引用
+ // 例如,如果 IncidentParms 已经被销毁,我们可以从字典中移除
+ // 由于 RimWorld 没有提供弱引用,这个清理可能需要手动触发
+ // 或者定期调用
+ }
+
+ // 获取所有存储的自定义数据(用于调试)
+ public static int GetStoredDataCount()
+ {
+ return customRaidData.Count;
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Storyteller/IncidentWorker_CustomRaid.cs b/Source/ArachnaeSwarm/Storyteller/IncidentWorker_CustomRaid.cs
new file mode 100644
index 0000000..2f13f16
--- /dev/null
+++ b/Source/ArachnaeSwarm/Storyteller/IncidentWorker_CustomRaid.cs
@@ -0,0 +1,418 @@
+using System.Collections.Generic;
+using System.Linq;
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class IncidentWorker_CustomRaid : IncidentWorker_Raid
+ {
+ private CustomRaidTracker GetTracker()
+ {
+ if (Current.ProgramState != ProgramState.Playing) return null;
+
+ Game game = Current.Game;
+ if (game == null) return null;
+
+ CustomRaidTracker tracker = game.GetComponent();
+ if (tracker == null)
+ {
+ tracker = new CustomRaidTracker(game);
+ game.components.Add(tracker);
+ }
+ return tracker;
+ }
+
+ protected override bool CanFireNowSub(IncidentParms parms)
+ {
+ // 获取自定义袭击定义
+ CustomRaidDef raidDef = GetCustomRaidDef();
+ if (raidDef == null)
+ {
+ Log.Warning("CustomRaidDef not found in CanFireNowSub");
+ return false;
+ }
+
+ // 检查最小天数
+ if (GenDate.DaysPassedSinceSettle < 15f) // 可以配置化
+ {
+ Log.Message($"Custom raid cannot fire: only {GenDate.DaysPassedSinceSettle} days passed, need 15");
+ return false;
+ }
+
+ // 检查目标是否有效
+ if (parms.target == null)
+ {
+ Log.Warning("Custom raid target is null");
+ return false;
+ }
+
+ // 检查目标是否有有效的地图
+ Map map = parms.target as Map;
+ if (map == null)
+ {
+ Log.Warning("Custom raid target is not a Map or map is null");
+ return false;
+ }
+
+ // 检查派系是否存在
+ Faction faction = Find.FactionManager.FirstFactionOfDef(raidDef.factionDef);
+ if (faction == null)
+ {
+ Log.Warning($"Faction {raidDef.factionDef?.defName} not found for custom raid");
+ return false;
+ }
+
+ return base.CanFireNowSub(parms);
+ }
+
+ protected override bool TryExecuteWorker(IncidentParms parms)
+ {
+ Log.Message("=== Custom Raid Incident Started ===");
+
+ // 检查目标地图
+ Map map = parms.target as Map;
+ if (map == null)
+ {
+ Log.Error("Custom raid target is not a valid Map");
+ return false;
+ }
+
+ CustomRaidDef raidDef = GetCustomRaidDef();
+ if (raidDef == null)
+ {
+ Log.Error("CustomRaidDef not found");
+ return false;
+ }
+
+ CustomRaidTracker tracker = GetTracker();
+ if (tracker == null)
+ {
+ Log.Error("CustomRaidTracker not found");
+ return false;
+ }
+
+ // 获取当前波次
+ int currentWave = tracker.GetCurrentWave(raidDef);
+ Log.Message($"Current wave: {currentWave}");
+
+ // 计算袭击规模
+ int raidSize = CalculateRaidSize(currentWave, raidDef);
+ Log.Message($"Calculated raid size: {raidSize}");
+
+ // 选择波次定义
+ RaidWaveDef waveDef = SelectWaveForSize(raidSize, raidDef);
+ if (waveDef == null)
+ {
+ Log.Error($"No wave found for raid size {raidSize}");
+ return false;
+ }
+ Log.Message($"Selected wave: {waveDef.defName}");
+
+ // 设置派系
+ parms.faction = Find.FactionManager.FirstFactionOfDef(raidDef.factionDef);
+ if (parms.faction == null)
+ {
+ Log.Error($"Faction {raidDef.factionDef.defName} not found");
+ return false;
+ }
+
+ // 设置点数
+ parms.points = CalculateThreatPoints(raidSize);
+ Log.Message($"Threat points: {parms.points}");
+
+ // 设置袭击策略
+ parms.raidStrategy = RaidStrategyDefOf.ImmediateAttack;
+
+ // 设置自定义参数
+ parms.SetCustomRaidWave(waveDef);
+ parms.SetCustomRaidSize(raidSize);
+ parms.SetCustomRaidDef(raidDef);
+ parms.SetCustomRaidWaveNumber(currentWave);
+
+ Log.Message($"Custom raid parameters set: wave={waveDef.defName}, size={raidSize}, waveNum={currentWave}");
+
+ // 执行袭击
+ bool success = base.TryExecuteWorker(parms);
+
+ if (success)
+ {
+ // 成功执行后增加波次
+ tracker.IncrementWave(raidDef);
+ Log.Message($"Custom raid wave {currentWave + 1} executed successfully. Next wave will be {currentWave + 2}");
+ }
+ else
+ {
+ Log.Error("Custom raid execution failed");
+ }
+
+ Log.Message("=== Custom Raid Incident Finished ===");
+ return success;
+ }
+
+ protected override bool TryResolveRaidFaction(IncidentParms parms)
+ {
+ // 对于自定义袭击,我们已经通过扩展设置了派系
+ if (parms.faction != null)
+ {
+ Log.Message($"Raid faction resolved: {parms.faction.Name}");
+ return true;
+ }
+
+ // 如果没有设置派系,尝试从自定义袭击定义中获取
+ CustomRaidDef raidDef = parms.GetCustomRaidDef();
+ if (raidDef?.factionDef != null)
+ {
+ parms.faction = Find.FactionManager.FirstFactionOfDef(raidDef.factionDef);
+ bool success = parms.faction != null;
+ Log.Message($"Resolved faction from raidDef: {raidDef.factionDef.defName}, success: {success}");
+ return success;
+ }
+
+ Log.Warning("Could not resolve raid faction");
+ return false;
+ }
+
+ public override void ResolveRaidStrategy(IncidentParms parms, PawnGroupKindDef groupKind)
+ {
+ // 如果已经设置了袭击策略,直接使用
+ if (parms.raidStrategy != null)
+ {
+ Log.Message($"Raid strategy already set: {parms.raidStrategy.defName}");
+ return;
+ }
+
+ // 从自定义波次定义中获取策略
+ RaidWaveDef waveDef = parms.GetCustomRaidWave();
+ if (waveDef != null)
+ {
+ // 这里可以根据 waveDef 的内容设置不同的策略
+ // 例如,如果有特定标签就使用特定策略
+ parms.raidStrategy = RaidStrategyDefOf.ImmediateAttack;
+ Log.Message($"Set raid strategy from waveDef: {parms.raidStrategy.defName}");
+ return;
+ }
+
+ // 默认策略
+ parms.raidStrategy = RaidStrategyDefOf.ImmediateAttack;
+ Log.Message($"Set default raid strategy: {parms.raidStrategy.defName}");
+ }
+
+ protected override void ResolveRaidPoints(IncidentParms parms)
+ {
+ // 如果已经设置了点数,直接使用
+ if (parms.points > 0)
+ {
+ Log.Message($"Raid points already set: {parms.points}");
+ return;
+ }
+
+ // 从自定义袭击规模计算点数
+ int raidSize = parms.GetCustomRaidSize();
+ if (raidSize > 0)
+ {
+ parms.points = CalculateThreatPoints(raidSize);
+ Log.Message($"Set raid points from custom size: {raidSize} -> {parms.points}");
+ return;
+ }
+
+ // 回退到原版点数计算
+ parms.points = StorytellerUtility.DefaultThreatPointsNow(parms.target);
+ Log.Message($"Set raid points from default calculation: {parms.points}");
+ }
+
+ protected override string GetLetterLabel(IncidentParms parms)
+ {
+ // 自定义袭击的信件标签
+ RaidWaveDef waveDef = parms.GetCustomRaidWave();
+ int waveNumber = parms.GetCustomRaidWaveNumber();
+ CustomRaidDef raidDef = parms.GetCustomRaidDef();
+
+ if (waveDef != null && raidDef != null)
+ {
+ return $"Special Attack Wave {waveNumber + 1} - {waveDef.label ?? waveDef.defName}";
+ }
+
+ return "Special Attack";
+ }
+
+ protected override string GetLetterText(IncidentParms parms, List pawns)
+ {
+ // 自定义袭击的信件文本
+ RaidWaveDef waveDef = parms.GetCustomRaidWave();
+ int waveNumber = parms.GetCustomRaidWaveNumber();
+ Faction faction = parms.faction;
+
+ string waveName = waveDef?.label ?? waveDef?.defName ?? "Unknown";
+ string baseText = $"A special attack wave {waveNumber + 1} - {waveName} from {faction.Name} is approaching!";
+
+ // 添加袭击策略信息
+ if (parms.raidStrategy != null)
+ {
+ baseText += "\n\n" + parms.raidStrategy.arrivalTextEnemy;
+ }
+
+ return baseText;
+ }
+
+ protected override LetterDef GetLetterDef()
+ {
+ // 使用威胁大的信件定义
+ return LetterDefOf.ThreatBig;
+ }
+
+ protected override string GetRelatedPawnsInfoLetterText(IncidentParms parms)
+ {
+ // 如果有相关pawn的信息,返回相应的文本
+ return "LetterRelatedPawnsRaid".Translate(Faction.OfPlayer.def.pawnsPlural, parms.faction.def.pawnsPlural);
+ }
+
+ // 自定义方法
+ private CustomRaidDef GetCustomRaidDef()
+ {
+ // 从 DefDatabase 获取自定义袭击定义
+ return DefDatabase.GetNamedSilentFail("ARA_SpecialAttack");
+ }
+
+ private int CalculateRaidSize(int currentWave, CustomRaidDef raidDef)
+ {
+ int baseSize = raidDef.baseRaidNembers;
+ var growthConfig = raidDef.pointsGrowthPerWave;
+
+ Log.Message($"Calculating raid size: base={baseSize}, wave={currentWave}, growthType={growthConfig.growthType}");
+
+ if (growthConfig.growthType == "Linear")
+ {
+ int result = baseSize + (int)(currentWave * growthConfig.linearGrowth);
+ Log.Message($"Linear growth: {baseSize} + ({currentWave} * {growthConfig.linearGrowth}) = {result}");
+ return result;
+ }
+ else if (growthConfig.growthType == "Exponential")
+ {
+ int result = (int)(baseSize * System.Math.Pow(growthConfig.exponentialBase, currentWave));
+ Log.Message($"Exponential growth: {baseSize} * {growthConfig.exponentialBase}^{currentWave} = {result}");
+ return result;
+ }
+
+ // 默认线性增长
+ int defaultResult = baseSize + currentWave;
+ Log.Message($"Default growth: {baseSize} + {currentWave} = {defaultResult}");
+ return defaultResult;
+ }
+
+ private RaidWaveDef SelectWaveForSize(int raidSize, CustomRaidDef raidDef)
+ {
+ Log.Message($"Selecting wave for size: {raidSize}");
+
+ foreach (var poolRange in raidDef.pointWavePools)
+ {
+ bool minCondition = raidSize >= poolRange.minPoints;
+ bool maxCondition = poolRange.maxPoints >= 99999f || raidSize < poolRange.maxPoints;
+
+ Log.Message($"Checking pool range: min={poolRange.minPoints}, max={poolRange.maxPoints}, matches={minCondition && maxCondition}");
+
+ if (minCondition && maxCondition)
+ {
+ var selectedWave = SelectWaveFromPool(poolRange.wavePool);
+ Log.Message($"Selected wave from pool {poolRange.wavePool.defName}: {selectedWave?.defName}");
+ return selectedWave;
+ }
+ }
+
+ // 如果没有匹配的区间,返回最后一个池
+ if (raidDef.pointWavePools.Count > 0)
+ {
+ var lastPool = raidDef.pointWavePools[raidDef.pointWavePools.Count - 1];
+ var selectedWave = SelectWaveFromPool(lastPool.wavePool);
+ Log.Message($"Selected wave from last pool {lastPool.wavePool.defName}: {selectedWave?.defName}");
+ return selectedWave;
+ }
+
+ Log.Error("No wave pools found in CustomRaidDef");
+ return null;
+ }
+
+ private RaidWaveDef SelectWaveFromPool(RaidWavePoolDef wavePool)
+ {
+ if (wavePool == null)
+ {
+ Log.Error("WavePool is null");
+ return null;
+ }
+
+ if (wavePool.waves.NullOrEmpty())
+ {
+ Log.Error($"WavePool {wavePool.defName} has no waves");
+ return null;
+ }
+
+ // 如果有权重配置,使用权重随机
+ if (wavePool.selectionWeights != null && wavePool.selectionWeights.Count > 0)
+ {
+ var weightedWaves = wavePool.waves.Where(w => wavePool.selectionWeights.ContainsKey(w.defName)).ToList();
+ if (weightedWaves.Any())
+ {
+ var selected = weightedWaves.RandomElementByWeight(waveDef => wavePool.selectionWeights[waveDef.defName]);
+ Log.Message($"Selected weighted wave: {selected.defName}");
+ return selected;
+ }
+ }
+
+ // 否则均匀随机
+ var randomWave = wavePool.waves.RandomElement();
+ Log.Message($"Selected random wave: {randomWave.defName}");
+ return randomWave;
+ }
+
+ private float CalculateThreatPoints(int raidSize)
+ {
+ // 根据袭击规模计算威胁点数
+ // 这里可以基于原版的威胁点数计算逻辑进行调整
+ float points = raidSize * 100f;
+ Log.Message($"Calculated threat points: {raidSize} * 100 = {points}");
+ return points;
+ }
+
+ // 重写生成pawn的方法,确保使用自定义波次定义
+ public override void ResolveRaidArriveMode(IncidentParms parms)
+ {
+ if (parms.raidArrivalMode != null)
+ {
+ Log.Message($"Raid arrival mode already set: {parms.raidArrivalMode.defName}");
+ return;
+ }
+ // 对于自定义袭击,默认使用边缘进入
+ parms.raidArrivalMode = PawnsArrivalModeDefOf.EdgeWalkIn;
+ Log.Message($"Set raid arrival mode: {parms.raidArrivalMode.defName}");
+ }
+
+ // 可选:重写其他方法以提供更好的调试信息
+ public override string ToString()
+ {
+ return base.ToString() + " (CustomRaid)";
+ }
+
+ public static void TestCustomRaid()
+ {
+ Map map = Find.CurrentMap;
+ if (map == null)
+ {
+ Log.Error("No current map found");
+ return;
+ }
+
+ IncidentDef raidIncident = DefDatabase.GetNamed("CustomRaidIncident");
+ if (raidIncident != null)
+ {
+ var parms = StorytellerUtility.DefaultParmsNow(raidIncident.category, map);
+ bool success = raidIncident.Worker.TryExecute(parms);
+ Messages.Message(success ? "Custom raid test executed!" : "Custom raid test failed",
+ success ? MessageTypeDefOf.PositiveEvent : MessageTypeDefOf.NegativeEvent);
+ }
+ else
+ {
+ Log.Error("CustomRaidIncident not found");
+ }
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Storyteller/RaidWaveDef.cs b/Source/ArachnaeSwarm/Storyteller/RaidWaveDef.cs
new file mode 100644
index 0000000..800508b
--- /dev/null
+++ b/Source/ArachnaeSwarm/Storyteller/RaidWaveDef.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class RaidWaveDef : Def
+ {
+ public List pawnComposition;
+
+ public override IEnumerable ConfigErrors()
+ {
+ foreach (string error in base.ConfigErrors())
+ {
+ yield return error;
+ }
+
+ if (pawnComposition.NullOrEmpty())
+ {
+ yield return "pawnComposition is empty";
+ }
+ }
+ }
+
+ public class PawnComposition
+ {
+ public PawnKindDef pawnKind;
+ public float ratio = 1f;
+ public int minCount = 0;
+ public int maxCount = 0; // 0表示无限制
+ public bool DefaultUnit = false;
+
+ public override string ToString()
+ {
+ return $"{pawnKind?.defName ?? "null"} (ratio: {ratio}, min: {minCount}, max: {maxCount}, default: {DefaultUnit})";
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Storyteller/RaidWavePoolDef.cs b/Source/ArachnaeSwarm/Storyteller/RaidWavePoolDef.cs
new file mode 100644
index 0000000..ff69b5f
--- /dev/null
+++ b/Source/ArachnaeSwarm/Storyteller/RaidWavePoolDef.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class RaidWavePoolDef : Def
+ {
+ public List waves;
+ public Dictionary selectionWeights;
+
+ public override IEnumerable ConfigErrors()
+ {
+ foreach (string error in base.ConfigErrors())
+ {
+ yield return error;
+ }
+
+ if (waves.NullOrEmpty())
+ {
+ yield return "waves list is empty";
+ }
+ }
+ }
+}