虫群的自定义袭击

This commit is contained in:
2025-10-16 17:04:41 +08:00
parent 446888d443
commit e42c88ef5a
21 changed files with 1205 additions and 67 deletions

Binary file not shown.

View File

@@ -699,12 +699,12 @@
<sound>AcidSpray_Resolve</sound>
</li>
<li Class="ArachnaeSwarm.CompProperties_AbilityShowTemperatureRange">
<temperatureCheckBuilding>ARA_Cocoon_Weapon_2Stage</temperatureCheckBuilding>
<temperatureCheckBuilding>ARA_Cocoon_Medicine_From_Death</temperatureCheckBuilding>
<customLabel>温度要求</customLabel>
<showCurrentTemperature>true</showCurrentTemperature>
</li>
<li Class="ArachnaeSwarm.CompProperties_AbilityShowInteractiveThing">
<cocoonBuildingDef>ARA_Cocoon_Weapon_2Stage</cocoonBuildingDef>
<cocoonBuildingDef>ARA_Cocoon_Medicine_From_Death</cocoonBuildingDef>
<customLabel>可孵化物品列表</customLabel>
<showResearchRequirements>true</showResearchRequirements>
<showNutritionCost>true</showNutritionCost>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<FactionDef ParentName="FactionBase">
<defName>ARA_Hostile_Hive</defName>
<label>阿拉克涅军团断须</label>
<description>向闪耀世界方向侵略的阿拉克涅虫巢舰队被闪耀世界联军击败后,残留于星域中的虫群子个体。这些虫群子个体由于无法和蜂巢网络建立联系,其自行组织的反常网络只有低度的智能,只能让其成员如野兽般行动——然而她们依然是一群危险的敌人,于无数敌手交战留下的基因性反射使得她们的锋芒不减当年。\n\n她们不会对任何人展现仁慈即使她们的对手是自己的同族。</description>
<pawnSingular>虫群</pawnSingular>
<pawnsPlural>虫群</pawnsPlural>
<requiredCountAtGameStart>1</requiredCountAtGameStart>
<fixedName>阿拉克涅断须</fixedName>
<factionIconPath>World/WorldObjects/Expanding/Insects</factionIconPath>
<colorSpectrum>
<li>(0.44, 0.41, 0.32)</li>
<li>(0.61, 0.58, 0.49)</li>
<li>(0.60, 0.49, 0.36)</li>
</colorSpectrum>
<raidCommonalityFromPointsCurve>
<points>
<li>(0, 0)</li>
</points>
</raidCommonalityFromPointsCurve>
<maxPawnCostPerTotalPointsCurve>
<points>
<li>(100,100)</li>
<li>(10000,10000)</li>
</points>
</maxPawnCostPerTotalPointsCurve>
<pawnGroupMakers>
<!-- 虫巢不发起任何常规袭击,而是使用自定义袭击 -->
</pawnGroupMakers>
<humanlikeFaction>false</humanlikeFaction>
<hidden>true</hidden>
<autoFlee>false</autoFlee>
<canUseAvoidGrid>false</canUseAvoidGrid>
<techLevel>Animal</techLevel>
<animalsFleeDanger>false</animalsFleeDanger>
<permanentEnemyToEveryoneExcept>
<li MayRequire="Ludeon.RimWorld.Anomaly">Entities</li>
</permanentEnemyToEveryoneExcept>
<settlementTexturePath>World/WorldObjects/DefaultSettlement</settlementTexturePath>
<allowedArrivalTemperatureRange>-2000~2000</allowedArrivalTemperatureRange>
<maxConfigurableAtWorldCreation>1</maxConfigurableAtWorldCreation>
<configurationListOrderPriority>1000</configurationListOrderPriority>
</FactionDef>
</Defs>

View File

@@ -19,7 +19,8 @@
</backstoryFilters>
<!-- 命名规则 -->
<factionNameMaker>ARA_New_Hive_NamerFaction</factionNameMaker>
<settlementNameMaker>NamerSettlementOutlander</settlementNameMaker>
<settlementNameMaker>ARA_NamerSettlement</settlementNameMaker>
<playerInitialSettlementNameMaker>ARA_NamerInitialSettlement</playerInitialSettlementNameMaker>
<allowedCultures><li>Astropolitan</li></allowedCultures>
<factionIconPath>World/WorldObjects/Expanding/Town</factionIconPath>
<startingResearchTags>
@@ -41,29 +42,58 @@
<rulePack>
<rulesStrings>
<li>r_name->[hivename1] [hivename2]</li>
<li>hivename1->猩红</li>
<li>hivename1->至高</li>
<li>hivename1->唯一</li>
<li>hivename1->蔓延</li>
<li>hivename1->永恒</li>
<li>hivename1->永续</li>
<li>hivename1->虚空</li>
<li>hivename1->深渊</li>
<li>hivename1->吞噬</li>
<li>hivename1->进化</li>
<li>hivename1->原生</li>
<li>hivename1->融合</li>
<li>hivename1->蚀骨</li>
<li>hivename2->核心</li>
<li>hivename2->内核</li>
<li>hivename2->要点</li>
<li>hivename2->中心</li>
<li>hivename2->焦点</li>
<li>hivename2->原点</li>
<li>hivename2->支点</li>
<li>hivename2->枢轴</li>
<li>hivename2->中枢</li>
<li>hivename2->母体</li>
<li>hivename2->源泉</li>
<li>hivename2->源地</li>
<li>hivename2->基体</li>
<li>hivename2->始祖</li>
<li>hivename2->主脑</li>
<li>hivename2->巢心</li>
<li>hivename2->蜂巢</li>
<li>hivename2->虫群</li>
<li>hivename2->爪牙</li>
<li>hivename2->兽群</li>
<li>hivename2->触须</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>ARA_NamerSettlement</defName>
<rulePack>
<rulesStrings>
<li>r_name->[hivesettlementname1] [hivesettlementname2]</li>
<li>hivesettlementname1->猩红</li>
<li>hivesettlementname1->至高</li>
<li>hivesettlementname1->唯一</li>
<li>hivesettlementname1->蔓延</li>
<li>hivesettlementname1->永恒</li>
<li>hivesettlementname1->永续</li>
<li>hivesettlementname2->核心</li>
<li>hivesettlementname2->内核</li>
<li>hivesettlementname2->要点</li>
<li>hivesettlementname2->中心</li>
<li>hivesettlementname2->焦点</li>
<li>hivesettlementname2->原点</li>
<li>hivesettlementname2->支点</li>
<li>hivesettlementname2->枢轴</li>
<li>hivesettlementname2->中枢</li>
<li>hivesettlementname2->母体</li>
<li>hivesettlementname2->源泉</li>
<li>hivesettlementname2->源地</li>
<li>hivesettlementname2->基体</li>
<li>hivesettlementname2->始祖</li>
<li>hivesettlementname2->主脑</li>
<li>hivesettlementname2->巢心</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>ARA_NamerInitialSettlement</defName>
<rulePack>
<rulesStrings>
<li>r_name->虫巢</li>
</rulesStrings>
</rulePack>
</RulePackDef>

View File

@@ -0,0 +1,192 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<!-- 自定义袭击定义 -->
<ArachnaeSwarm.CustomRaidDef>
<defName>ARA_Stage1_Raid</defName>
<factionDef>ARA_Hostile_Hive</factionDef>
<pointWavePools>
<li>
<minPoints>0</minPoints>
<maxPoints>800</maxPoints> <!-- 第一阶段点数上限,点数为人数*100 -->
<wavePool>ARA_WavePool_Stage1</wavePool>
</li>
</pointWavePools>
<baseRaidNembers>3</baseRaidNembers>
<pointsGrowthPerWave>
<growthType>Linear</growthType>
<linearGrowth>1.2</linearGrowth>
</pointsGrowthPerWave>
</ArachnaeSwarm.CustomRaidDef>
<!-- 第一阶段波次池 -->
<ArachnaeSwarm.RaidWavePoolDef>
<defName>ARA_WavePool_Stage1</defName>
<waves>
<li>ARA_Wave_Scout_Patrol</li>
<li>ARA_Wave_Assault_Team</li>
<li>ARA_Wave_Acid_Swarm</li>
<li>ARA_Wave_Heavy_Defense</li>
<li>ARA_Wave_Mixed_Forces</li>
</waves>
<selectionWeights>
<li>
<key>ARA_Wave_Scout_Patrol</key>
<value>0.25</value>
</li>
<li>
<key>ARA_Wave_Assault_Team</key>
<value>0.25</value>
</li>
<li>
<key>ARA_Wave_Acid_Swarm</key>
<value>0.20</value>
</li>
<li>
<key>ARA_Wave_Heavy_Defense</key>
<value>0.15</value>
</li>
<li>
<key>ARA_Wave_Mixed_Forces</key>
<value>0.15</value>
</li>
</selectionWeights>
</ArachnaeSwarm.RaidWavePoolDef>
<!-- 波次1: 侦察巡逻队 -->
<ArachnaeSwarm.RaidWaveDef>
<defName>ARA_Wave_Scout_Patrol</defName>
<label>侦察巡逻队</label>
<description>一支小型侦察队伍,主要由远程单位组成,进行骚扰射击</description>
<pawnComposition>
<li>
<pawnKind>ARA_Raid_Shooter</pawnKind>
<ratio>0.7</ratio>
<minCount>2</minCount>
<maxCount>6</maxCount>
<DefaultUnit>true</DefaultUnit>
</li>
<li>
<pawnKind>ARA_Raid_Assault</pawnKind>
<ratio>0.3</ratio>
<minCount>1</minCount>
<maxCount>3</maxCount>
</li>
</pawnComposition>
</ArachnaeSwarm.RaidWaveDef>
<!-- 波次2: 突击小队 -->
<ArachnaeSwarm.RaidWaveDef>
<defName>ARA_Wave_Assault_Team</defName>
<label>突击小队</label>
<description>以近战单位为主的快速突击队伍,擅长冲锋陷阵</description>
<pawnComposition>
<li>
<pawnKind>ARA_Raid_Assault</pawnKind>
<ratio>0.6</ratio>
<minCount>3</minCount>
<maxCount>8</maxCount>
<DefaultUnit>true</DefaultUnit>
</li>
<li>
<pawnKind>ARA_Raid_Shooter</pawnKind>
<ratio>0.4</ratio>
<minCount>2</minCount>
<maxCount>4</maxCount>
</li>
</pawnComposition>
</ArachnaeSwarm.RaidWaveDef>
<!-- 波次3: 酸液虫群 -->
<ArachnaeSwarm.RaidWaveDef>
<defName>ARA_Wave_Acid_Swarm</defName>
<label>酸液虫群</label>
<description>大量酸噬种辅虫组成的虫海战术,数量庞大但个体脆弱</description>
<pawnComposition>
<li>
<pawnKind>ARA_Raid_AcidSwarm</pawnKind>
<ratio>0.8</ratio>
<minCount>8</minCount>
<maxCount>20</maxCount>
<DefaultUnit>true</DefaultUnit>
</li>
<li>
<pawnKind>ARA_Raid_Assault</pawnKind>
<ratio>0.2</ratio>
<minCount>2</minCount>
<maxCount>5</maxCount>
</li>
</pawnComposition>
</ArachnaeSwarm.RaidWaveDef>
<!-- 波次4: 重装防御 -->
<ArachnaeSwarm.RaidWaveDef>
<defName>ARA_Wave_Heavy_Defense</defName>
<label>重装防御队</label>
<description>以盾头种为主的防御型队伍,移动缓慢但防御力强</description>
<pawnComposition>
<li>
<pawnKind>ARA_Raid_Heavy</pawnKind>
<ratio>0.5</ratio>
<minCount>2</minCount>
<maxCount>6</maxCount>
<DefaultUnit>true</DefaultUnit>
</li>
<li>
<pawnKind>ARA_Raid_Shooter</pawnKind>
<ratio>0.3</ratio>
<minCount>2</minCount>
<maxCount>4</maxCount>
</li>
<li>
<pawnKind>ARA_Raid_Acidling</pawnKind>
<ratio>0.2</ratio>
<minCount>3</minCount>
<maxCount>8</maxCount>
</li>
</pawnComposition>
<strategy>Defensive</strategy> <!-- 防御战术 -->
</ArachnaeSwarm.RaidWaveDef>
<!-- 波次5: 混合部队 -->
<ArachnaeSwarm.RaidWaveDef>
<defName>ARA_Wave_Mixed_Forces</defName>
<label>混合部队</label>
<description>均衡配置的混合部队,包含各种单位类型</description>
<pawnComposition>
<li>
<pawnKind>ARA_Raid_Assault</pawnKind>
<ratio>0.3</ratio>
<minCount>2</minCount>
<maxCount>5</maxCount>
</li>
<li>
<pawnKind>ARA_Raid_Shooter</pawnKind>
<ratio>0.3</ratio>
<minCount>2</minCount>
<maxCount>5</maxCount>
</li>
<li>
<pawnKind>ARA_Raid_Heavy</pawnKind>
<ratio>0.2</ratio>
<minCount>1</minCount>
<maxCount>3</maxCount>
</li>
<li>
<pawnKind>ARA_Raid_AcidSwarm</pawnKind>
<ratio>0.2</ratio>
<minCount>3</minCount>
<maxCount>6</maxCount>
<DefaultUnit>true</DefaultUnit>
</li>
</pawnComposition>
<strategy>Balanced</strategy> <!-- 均衡战术 -->
</ArachnaeSwarm.RaidWaveDef>
<!-- 事件定义 -->
<IncidentDef>
<defName>ARA_Raid_Incident</defName>
<label>阿拉克涅虫群袭击</label>
<workerClass>ArachnaeSwarm.IncidentWorker_CustomRaid</workerClass>
<category>Special</category>
<minRefireDays>5</minRefireDays>
<baseChance>0</baseChance>
<targetTags>
<li>Map_PlayerHome</li>
</targetTags>
</IncidentDef>
</Defs>

View File

@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<!-- 基础袭击者类型 -->
<PawnKindDef Name="ARA_RaidBase" Abstract="True">
<combatPower>150</combatPower>
<isFighter>true</isFighter>
<trader>false</trader>
<chemicalAddictionChance>0</chemicalAddictionChance>
<apparelIgnoreSeasons>true</apparelIgnoreSeasons>
<forceNormalGearQuality>true</forceNormalGearQuality>
<initialWillRange>99~99</initialWillRange>
<initialResistanceRange>99~99</initialResistanceRange>
<overrideDeathOnDownedChance>1</overrideDeathOnDownedChance>
<forceDeathOnDowned>true</forceDeathOnDowned>
<maxGenerationAge>2</maxGenerationAge>
<minGenerationAge>1</minGenerationAge>
<canBeScattered>false</canBeScattered>
<defaultFactionType>ARA_Hostile_Hive</defaultFactionType>
<techHediffsMoney>0</techHediffsMoney>
<requiredWorkTags>
<li>Violent</li>
</requiredWorkTags>
<moveSpeedFactorByTerrainTag>
<li>
<key>ARA_Creep</key>
<value>3.0</value>
</li>
</moveSpeedFactorByTerrainTag>
</PawnKindDef>
<!-- 传统类:以毒针、酸液和近战战士虫组成阵线,辅助以小辅虫 -->
<!-- 近战突击单位 -->
<PawnKindDef ParentName="ARA_RaidBase">
<defName>ARA_Raid_Assault</defName>
<label>阿拉克涅突击者</label>
<race>ArachnaeNode_Race_Fighter</race>
<combatPower>200</combatPower>
<weaponTags>
<li>ARA_Armed_Organ_Melee</li>
</weaponTags>
<apparelTags>
<li>ARA_Inner</li>
<li>ARA_Clothes</li>
</apparelTags>
<apparelMoney>200</apparelMoney>
<weaponMoney>200</weaponMoney>
<abilities>
<li>ARA_BaseRace_Acid_Launcher</li>
</abilities>
</PawnKindDef>
<!-- 远程射手单位 -->
<PawnKindDef ParentName="ARA_RaidBase">
<defName>ARA_Raid_Shooter</defName>
<label>阿拉克涅射手</label>
<race>ArachnaeNode_Race_Fighter</race>
<combatPower>180</combatPower>
<weaponTags>
<li>ARA_Armed_Organ_Small_Ranged_Needle</li>
<li>ARA_Armed_Organ_Small_Ranged_Acid</li>
</weaponTags>
<apparelTags>
<li>ARA_Inner</li>
</apparelTags>
<apparelMoney>150</apparelMoney>
<weaponMoney>400</weaponMoney>
<techHediffsMoney>100</techHediffsMoney>
<abilities>
<li>ARA_Hibernate_Ability</li>
</abilities>
</PawnKindDef>
<!-- 辅虫群单位 -->
<PawnKindDef ParentName="ARA_RaidBase">
<defName>ARA_Raid_AcidSwarm</defName>
<label>阿拉克涅酸噬群</label>
<combatPower>80</combatPower>
<apparelMoney>0</apparelMoney>
<weaponMoney>0</weaponMoney>
<techHediffsMoney>0</techHediffsMoney>
<race>ArachnaeBase_Race_Acidcut</race>
<lifeStages>
<li>
<bodyGraphicData>
<texPath>ArachnaeSwarm/Things/ARA_Acidcut/Bodies/Naked_Thin</texPath>
<drawSize>1</drawSize>
<shadowData>
<volume>(0.4, 0.5, 0.37)</volume>
<offset>(0,0,-0.15)</offset>
</shadowData>
</bodyGraphicData>
<dessicatedBodyGraphicData>
<texPath>Things/Pawn/Animal/Spelopede/Dessicated_Spelopede</texPath>
<drawSize>1</drawSize>
</dessicatedBodyGraphicData>
</li>
</lifeStages>
</PawnKindDef>
<!-- 重装类:远近盾头混搭,辅以自杀辅虫和攻击辅虫 -->
<!-- 重型单位,远近混搭 -->
<PawnKindDef ParentName="ARA_RaidBase">
<defName>ARA_Raid_Heavy</defName>
<label>阿拉克涅重装兵</label>
<race>ArachnaeNode_Race_ShieldHead</race>
<combatPower>350</combatPower>
<weaponTags>
<li>ARA_Armed_Organ_Melee</li>
<li>ARA_Armed_Organ_Small_Ranged_Needle</li>
<li>ARA_Armed_Organ_Small_Ranged_Acid</li>
</weaponTags>
<apparelTags>
<li>ARA_Inner</li>
<li>ARA_Clothes</li>
</apparelTags>
<apparelMoney>400</apparelMoney>
<weaponMoney>600</weaponMoney>
<techHediffsMoney>0</techHediffsMoney>
</PawnKindDef>
<!-- 自杀辅虫群单位 -->
<PawnKindDef ParentName="ARA_InsectKindBase">
<defName>ARA_Raid_Acidling</defName>
<label>阿拉克涅爆裂群</label>
<race>ArachnaeBase_Race_Acidling</race>
<combatPower>50</combatPower>
<apparelMoney>0</apparelMoney>
<weaponMoney>0</weaponMoney>
<techHediffsMoney>0</techHediffsMoney>
<lifeStages>
<li>
<bodyGraphicData>
<texPath>ArachnaeSwarm/Things/ARA_Acidling/Bodies/Naked_Thin</texPath>
<drawSize>1</drawSize>
<shadowData>
<volume>(0.4, 0.5, 0.37)</volume>
<offset>(0,0,-0.15)</offset>
</shadowData>
</bodyGraphicData>
<dessicatedBodyGraphicData>
<texPath>Things/Pawn/Animal/Spelopede/Dessicated_Spelopede</texPath>
<drawSize>1</drawSize>
</dessicatedBodyGraphicData>
</li>
</lifeStages>
</PawnKindDef>
</Defs>

View File

@@ -9,8 +9,14 @@
<listOrder>20</listOrder>
<comps>
<!-- Intro -->
<!-- <li Class="StorytellerCompProperties_OnOffCycle">
<incident>ARA_Raid_Incident</incident>
<onDays>1</onDays>
<offDays>0</offDays>
<minSpacingDays>0.01</minSpacingDays>
<numIncidentsRange>1~2</numIncidentsRange>
</li> -->
<!-- <li Class="StorytellerCompProperties_ClassicIntro"/> -->
<!-- 袭击生成器 -->
<li Class="StorytellerCompProperties_OnOffCycle">
<category>ThreatBig</category> <!-- 大型袭击 -->
<minDaysPassed>15.0</minDaysPassed> <!-- 最低在15日后开始生成 -->

View File

@@ -11,6 +11,8 @@
<tools>
</tools>
<race>
<!-- <nameGenerator>ARA_NamerHivePawnGeneric</nameGenerator> -->
<useMeatFrom>Megaspider</useMeatFrom>
<thinkTreeMain>Humanlike</thinkTreeMain>
<thinkTreeConstant>HumanlikeConstant</thinkTreeConstant>

View File

@@ -31,6 +31,7 @@
<li Class="ArachnaeSwarm.CompProperties_ExtraIncubationInfo">
<cocoonDefs>
<li>ARA_Cocoon_Medicine</li>
<li>ARA_Cocoon_Medicine_From_Death</li>
<li>ARA_BioforgeIncubator_Thing</li>
</cocoonDefs>
</li>
@@ -87,6 +88,7 @@
<li Class="ArachnaeSwarm.CompProperties_ExtraIncubationInfo">
<cocoonDefs>
<li>ARA_Cocoon_Medicine</li>
<li>ARA_Cocoon_Medicine_From_Death</li>
<li>ARA_BioforgeIncubator_Thing</li>
</cocoonDefs>
</li>

View File

@@ -12,6 +12,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Melee</li>
<li>ARA_Armed_Organ_T1</li>
<li>ARA_MW_Bone_Sword</li>
</weaponTags>
<graphicData>
<texPath>ArachnaeSwarm/Weapon/ARA_MW_Bone_Sword</texPath>
@@ -313,6 +314,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T1</li>
<li>ARA_Armed_Organ_Small_Ranged_Needle</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>
@@ -427,6 +429,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T2</li>
<li>ARA_Armed_Organ_Small_Ranged_Needle</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>
@@ -524,6 +527,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T2</li>
<li>ARA_Armed_Organ_Small_Ranged_Needle</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>
@@ -637,6 +641,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T2</li>
<li>ARA_Armed_Organ_Huge_Ranged_Needle</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>
@@ -752,6 +757,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T2</li>
<li>ARA_Armed_Organ_Small_Ranged_Needle</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>
@@ -854,6 +860,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T1</li>
<li>ARA_Armed_Organ_Small_Ranged_Acid</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>
@@ -969,6 +976,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T2</li>
<li>ARA_Armed_Organ_Huge_Ranged_Acid</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>
@@ -1118,6 +1126,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T2</li>
<li>ARA_Armed_Organ_Huge_Ranged_Acid</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>
@@ -1236,6 +1245,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T3</li>
<li>ARA_Armed_Organ_Small_Ranged_Acid</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>
@@ -1370,6 +1380,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T3</li>
<li>ARA_Armed_Organ_Huge_Ranged_Acid</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>
@@ -1506,6 +1517,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T1</li>
<li>ARA_Armed_Organ_Small_Ranged_SP</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>
@@ -1658,6 +1670,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T2</li>
<li>ARA_Armed_Organ_Small_Ranged_Energy</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>
@@ -1764,6 +1777,7 @@
<li>ARA_Armed_Organ</li>
<li>ARA_Armed_Organ_Ranged</li>
<li>ARA_Armed_Organ_T2</li>
<li>ARA_Armed_Organ_Huge_Ranged_Energy</li>
</weaponTags>
<generateCommonality>0</generateCommonality>
<tradeability>None</tradeability>

View File

@@ -694,7 +694,7 @@
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<initialFuelPercent>0.2</initialFuelPercent>
<autoRefuelPercent>1</autoRefuelPercent>
<initialConfigurableTargetFuelLevel>999</initialConfigurableTargetFuelLevel>
<initialConfigurableTargetFuelLevel>20</initialConfigurableTargetFuelLevel>
</li>
</comps>
</ThingDef>
@@ -715,7 +715,7 @@
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<initialFuelPercent>1</initialFuelPercent>
<autoRefuelPercent>1</autoRefuelPercent>
<initialConfigurableTargetFuelLevel>999</initialConfigurableTargetFuelLevel>
<initialConfigurableTargetFuelLevel>20</initialConfigurableTargetFuelLevel>
</li>
</comps>
</ThingDef>
@@ -804,7 +804,7 @@
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<initialFuelPercent>0.2</initialFuelPercent>
<autoRefuelPercent>1</autoRefuelPercent>
<initialConfigurableTargetFuelLevel>999</initialConfigurableTargetFuelLevel>
<initialConfigurableTargetFuelLevel>20</initialConfigurableTargetFuelLevel>
</li>
</comps>
</ThingDef>
@@ -825,7 +825,7 @@
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<initialFuelPercent>1</initialFuelPercent>
<autoRefuelPercent>1</autoRefuelPercent>
<initialConfigurableTargetFuelLevel>999</initialConfigurableTargetFuelLevel>
<initialConfigurableTargetFuelLevel>20</initialConfigurableTargetFuelLevel>
</li>
</comps>
</ThingDef>
@@ -922,7 +922,7 @@
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<initialFuelPercent>0.2</initialFuelPercent>
<autoRefuelPercent>1</autoRefuelPercent>
<initialConfigurableTargetFuelLevel>999</initialConfigurableTargetFuelLevel>
<initialConfigurableTargetFuelLevel>60</initialConfigurableTargetFuelLevel>
</li>
</comps>
</ThingDef>
@@ -943,7 +943,7 @@
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<initialFuelPercent>1</initialFuelPercent>
<autoRefuelPercent>1</autoRefuelPercent>
<initialConfigurableTargetFuelLevel>999</initialConfigurableTargetFuelLevel>
<initialConfigurableTargetFuelLevel>60</initialConfigurableTargetFuelLevel>
</li>
</comps>
</ThingDef>
@@ -1035,7 +1035,7 @@
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<initialFuelPercent>0.2</initialFuelPercent>
<autoRefuelPercent>1</autoRefuelPercent>
<initialConfigurableTargetFuelLevel>999</initialConfigurableTargetFuelLevel>
<initialConfigurableTargetFuelLevel>60</initialConfigurableTargetFuelLevel>
</li>
</comps>
</ThingDef>
@@ -1056,7 +1056,7 @@
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<initialFuelPercent>1</initialFuelPercent>
<autoRefuelPercent>1</autoRefuelPercent>
<initialConfigurableTargetFuelLevel>999</initialConfigurableTargetFuelLevel>
<initialConfigurableTargetFuelLevel>60</initialConfigurableTargetFuelLevel>
</li>
</comps>
</ThingDef>
@@ -1145,7 +1145,7 @@
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<initialFuelPercent>0.2</initialFuelPercent>
<autoRefuelPercent>1</autoRefuelPercent>
<initialConfigurableTargetFuelLevel>999</initialConfigurableTargetFuelLevel>
<initialConfigurableTargetFuelLevel>110</initialConfigurableTargetFuelLevel>
</li>
</comps>
</ThingDef>
@@ -1166,7 +1166,7 @@
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<initialFuelPercent>1</initialFuelPercent>
<autoRefuelPercent>1</autoRefuelPercent>
<initialConfigurableTargetFuelLevel>999</initialConfigurableTargetFuelLevel>
<initialConfigurableTargetFuelLevel>110</initialConfigurableTargetFuelLevel>
</li>
</comps>
</ThingDef>
@@ -1279,7 +1279,7 @@
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<initialFuelPercent>0.2</initialFuelPercent>
<autoRefuelPercent>1</autoRefuelPercent>
<initialConfigurableTargetFuelLevel>999</initialConfigurableTargetFuelLevel>
<initialConfigurableTargetFuelLevel>110</initialConfigurableTargetFuelLevel>
</li>
</comps>
</ThingDef>
@@ -1300,7 +1300,7 @@
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<initialFuelPercent>1</initialFuelPercent>
<autoRefuelPercent>1</autoRefuelPercent>
<initialConfigurableTargetFuelLevel>999</initialConfigurableTargetFuelLevel>
<initialConfigurableTargetFuelLevel>110</initialConfigurableTargetFuelLevel>
</li>
</comps>
</ThingDef>

View File

@@ -71,9 +71,8 @@
<fuelCapacity>20</fuelCapacity>
<fuelConsumptionRate>0</fuelConsumptionRate>
<consumeFuelOnlyWhenUsed>true</consumeFuelOnlyWhenUsed>
<autoRefuelPercent>0</autoRefuelPercent>
<autoRefuelPercent>-1</autoRefuelPercent>
<initialAllowAutoRefuel>true</initialAllowAutoRefuel>
<showAllowAutoRefuelToggle>false</showAllowAutoRefuelToggle>
<fuelFilter>
<thingDefs>
@@ -82,7 +81,7 @@
</fuelFilter>
<targetFuelLevelConfigurable>false</targetFuelLevelConfigurable>
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<showAllowAutoRefuelToggle>false</showAllowAutoRefuelToggle>
<canEjectFuel>true</canEjectFuel>
</li>

View File

@@ -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": ""
}
]

View File

@@ -120,6 +120,12 @@
<Compile Include="EventSystem\Letter_EventChoice.cs" />
<Compile Include="EventSystem\QuestNode_Root_EventLetter.cs" />
<Compile Include="Jobs\JobDriver_CarryPrisonerToRefuelingVat.cs" />
<Compile Include="Storyteller\CustomRaidDef.cs" />
<Compile Include="Storyteller\CustomRaidTracker.cs" />
<Compile Include="Storyteller\IncidentParmsExtensions.cs" />
<Compile Include="Storyteller\IncidentWorker_CustomRaid.cs" />
<Compile Include="Storyteller\RaidWaveDef.cs" />
<Compile Include="Storyteller\RaidWavePoolDef.cs" />
<Compile Include="Verbs\Verb_ShootWithOffset.cs" />
<Compile Include="Abilities\ARA_ShowTemperatureRange\CompAbilityEffect_AbilityShowTemperatureRange.cs" />
<Compile Include="Abilities\ARA_ShowTemperatureRange\CompProperties_AbilityShowTemperatureRange.cs" />

View File

@@ -0,0 +1,51 @@
using RimWorld;
using System.Collections.Generic;
using Verse;
namespace ArachnaeSwarm
{
public class CustomRaidDef : Def
{
public FactionDef factionDef;
public List<PointWavePool> pointWavePools;
public int baseRaidNembers;
public PointsGrowthPerWave pointsGrowthPerWave;
public override IEnumerable<string> 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;
}
}

View File

@@ -0,0 +1,73 @@
using System.Collections.Generic;
using Verse;
namespace ArachnaeSwarm
{
public class CustomRaidTracker : GameComponent
{
private Dictionary<string, int> waveCounters = new Dictionary<string, int>();
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<string, int>();
}
}
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();
}
}
}

View File

@@ -0,0 +1,111 @@
using RimWorld;
using System.Collections.Generic;
using Verse;
namespace ArachnaeSwarm
{
public static class IncidentParmsExtensions
{
// 使用静态字典来存储自定义数据
private static Dictionary<IncidentParms, CustomRaidData> customRaidData = new Dictionary<IncidentParms, CustomRaidData>();
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;
}
}
}

View File

@@ -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<CustomRaidTracker>();
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<Pawn> 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<CustomRaidDef>.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<IncidentDef>.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");
}
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using Verse;
namespace ArachnaeSwarm
{
public class RaidWaveDef : Def
{
public List<PawnComposition> pawnComposition;
public override IEnumerable<string> 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})";
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
using Verse;
namespace ArachnaeSwarm
{
public class RaidWavePoolDef : Def
{
public List<RaidWaveDef> waves;
public Dictionary<string, float> selectionWeights;
public override IEnumerable<string> ConfigErrors()
{
foreach (string error in base.ConfigErrors())
{
yield return error;
}
if (waves.NullOrEmpty())
{
yield return "waves list is empty";
}
}
}
}