19 Commits

Author SHA1 Message Date
ProjectKoi-Kalo\Kalo
a98b4d2612 修反复割除 2025-09-03 13:26:48 +08:00
ProjectKoi-Kalo\Kalo
9a8b90be82 修割除 2025-09-03 12:50:35 +08:00
ProjectKoi-Kalo\Kalo
2d4d171eb9 暂存修收获 2025-09-03 12:45:40 +08:00
ProjectKoi-Kalo\Kalo
aca5fa287d 修种植传递 2025-09-03 12:27:38 +08:00
ProjectKoi-Kalo\Kalo
ed9ac9d389 暂存 2025-09-03 12:07:45 +08:00
ProjectKoi-Kalo\Kalo
254a621c55 暂存 2025-09-03 11:57:11 +08:00
ProjectKoi-Kalo\Kalo
b3be9897df Merge branch 'mechworkproxy' into animalwork
# Conflicts:
#	1.6/1.6/Assemblies/ArachnaeSwarm.dll
2025-09-03 11:05:41 +08:00
ProjectKoi-Kalo\Kalo
e5feb47e8b 暂存 2025-09-03 09:13:03 +08:00
ProjectKoi-Kalo\Kalo
9e4eda256f 修质量 2025-09-02 21:49:46 +08:00
ProjectKoi-Kalo\Kalo
9b5a8a58d3 怪了好使了 2025-09-02 21:43:35 +08:00
ProjectKoi-Kalo\Kalo
8a7a947669 暂存 2025-09-02 21:28:25 +08:00
ProjectKoi-Kalo\Kalo
ce3e84116e Merge branch 'master' of https://git.ra3battle.cn/Kalospacer/ArachnaeSwarm 2025-09-02 18:39:45 +08:00
ProjectKoi-Kalo\Kalo
f74c2b844b 失败 2025-09-02 18:39:28 +08:00
ProjectKoi-Kalo\Kalo
b21de9de91 晕晕 2025-09-02 17:52:41 +08:00
Tourswen
94e4cb54ba 各种改动巴拉巴拉 2025-09-02 17:29:53 +08:00
ProjectKoi-Kalo\Kalo
9acd5aac1e 暂存 2025-09-02 16:36:03 +08:00
Tourswen
418739ac5b 墙体、女王需求等巴拉巴拉 2025-09-02 16:06:41 +08:00
ProjectKoi-Kalo\Kalo
6129bb1b50 暂存文档 2025-09-02 15:48:16 +08:00
ProjectKoi-Kalo\Kalo
d335b2008c 暂存 2025-09-02 15:06:36 +08:00
26 changed files with 2495 additions and 79 deletions

Binary file not shown.

View File

@@ -135,7 +135,7 @@
<armorPenetrationBase>0.1</armorPenetrationBase>
<stoppingPower>1</stoppingPower>
<shadowSize>0</shadowSize>
<spawnTerrain>InsectSludge</spawnTerrain>
<spawnTerrain>ARA_InsectSludge</spawnTerrain>
<terrainChance>0.75</terrainChance>
<filth>Filth_Slime</filth>
<filthCount>1</filthCount>

View File

@@ -27,8 +27,8 @@
</AlienRace.AlienBackstoryDef>
<AlienRace.AlienBackstoryDef ParentName="ARA_BaseBackStory">
<defName>Arachnae_Queen_BS_Adult_1</defName>
<title>王道征途</title>
<titleShort>王道征途</titleShort>
<title>见习女皇</title>
<titleShort>见习女皇</titleShort>
<description>[PAWN_nameDef]顺利地在虫巢中长大后,她离开了生养她的巢穴并来到一片陌生的地域,着手准备建立自己的势力。\n\n[PAWN_nameDef]知道自己势单力薄,比起其他娇生惯养的姐妹,她在很多事情上更愿意亲力亲为。</description>
<slot>Adulthood</slot>
<skillGains>
@@ -47,9 +47,9 @@
<AlienRace.AlienBackstoryDef ParentName="ARA_BaseBackStory">
<defName>Arachnae_Node_BS_Child_1</defName>
<title>阿拉克涅虫</title>
<titleShort>阿拉克涅虫族</titleShort>
<description>[PAWN_nameDef]是一只阿拉克涅虫族</description>
<title>阿拉克涅</title>
<titleShort>督虫</titleShort>
<description>[PAWN_nameDef]是一只阿拉克涅督虫——这个种姓的阿拉克涅虫族是虫巢的中坚力量,服从于女皇种的指挥的同时,也指挥着麾下的辅虫。她们从诞生之初便肩负着不同的使命,并且在较短的寿命中尽心尽力地服侍虫群</description>
<slot>Childhood</slot>
<spawnCategories>
@@ -62,16 +62,15 @@
<requiresSpawnCategory>true</requiresSpawnCategory>
</AlienRace.AlienBackstoryDef>
<AlienRace.AlienBackstoryDef ParentName="ARA_BaseBackStory">
<defName>Arachnae_Node_BS_Adult_1</defName>
<title>阿拉克涅督虫</title>
<titleShort>阿拉克涅督虫</titleShort>
<description>[PAWN_nameDef]是一只阿拉克涅督虫种</description>
<defName>Arachnae_Node_BS_Adult_Myrmecocystus</defName>
<title>阿拉克涅蜜罐</title>
<titleShort>蜜罐</titleShort>
<description>[PAWN_nameDef]是一只阿拉克涅蜜罐种督虫。基因的选择性表达使其长出了肿胀的囊袋和复杂的口器,这使得她可以吞噬那些未经过处理的尸体和各种杂食,并通过消化器官将其转变为阿拉克涅虫蜜以供其他虫族食用。\n\n[PAWN_nameDef]在战斗中并不是一个值得正视的对手,她虽然可以将胃酸喷出以抵抗近距离的来访者,但是自身没有可以接入武装器官的副肢,脆弱臃肿的特性也决定了她几乎无法躲开任何攻击</description>
<slot>Adulthood</slot>
<spawnCategories>
<li>ArachnaeNode_spawnCategoriesB</li>
<li>ArachnaeNode_spawnCategories_Myrmecocystus</li>
</spawnCategories>
</AlienRace.AlienBackstoryDef>
</Defs>

View File

@@ -2,8 +2,8 @@
<Defs>
<HediffDef>
<defName>ARA_AcidCoverd</defName>
<label>阿拉克涅</label>
<description>一支拥有酸囊的阿拉克涅虫族向此人喷射了强酸,沾染的强酸将使得此人痛不欲生,盔甲和表皮软化,并持续灼烧它的身体。</description>
<label>阿拉克涅</label>
<description>一支拥有酸囊的阿拉克涅虫族向此人喷射了强酸,沾染的液体将使得此人痛不欲生,盔甲和表皮软化,并持续灼烧它的身体。</description>
<defaultLabelColor>(1, 1, 0.8)</defaultLabelColor>
<hediffClass>ArachnaeSwarm.HediffCurseFlame</hediffClass>
<comps>
@@ -18,7 +18,7 @@
<modExtensions>
<li Class="ArachnaeSwarm.CurseFlameModExt">
<damageDefName>AcidBurn</damageDefName>
<damageRange>1~5</damageRange>
<damageRange>1~2</damageRange>
<damageIntervalTicks>40</damageIntervalTicks>
</li>
</modExtensions>

View File

@@ -14,6 +14,16 @@
<label>连接至 {0} 个虫群工蜂</label>
<minSeverity>0</minSeverity>
<!-- Stat offsets will be added here later -->
<disablesNeeds>
<li>Joy</li>
<li>Beauty</li>
<li>Comfort</li>
<li>Outdoors</li>
<li>Chemical_Alcohol</li>
</disablesNeeds>
<enablesNeeds>
<li>Indoors</li>
</enablesNeeds>
</li>
</stages>
</HediffDef>

View File

@@ -100,7 +100,7 @@
</PawnKindDef>
<PawnKindDef Name="ArachnaeNodeA_Colonist" ParentName="ArachnaeNodeABasePawnKind">
<defName>ArachnaeNode_Race_Myrmecocystus</defName>
<label>阿拉克涅督虫</label>
<label>阿拉克涅蜜罐</label>
<race>ArachnaeNode_Race_Myrmecocystus</race>
<defaultFactionType>PlayerColony</defaultFactionType>
<invNutrition>2.55</invNutrition>
@@ -108,7 +108,7 @@
<li>
<categories>
<li>ArachnaeNode_spawnCategoriesA</li>
<li>ArachnaeNode_spawnCategoriesB</li>
<li>ArachnaeNode_spawnCategories_Myrmecocystus</li>
</categories>
</li>
</backstoryFiltersOverride>

View File

@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThingDef ParentName="BaseInsect">
<defName>ArachnaeBase_Race_Scavenger</defName>
<label>阿拉克涅食腐虫</label>
<description>是阿拉克涅食腐虫的描述。</description>
<statBases>
<MoveSpeed>3.65</MoveSpeed>
<ComfyTemperatureMin>-25</ComfyTemperatureMin>
<MarketValue>200</MarketValue>
<LeatherAmount>0</LeatherAmount>
<ArmorRating_Blunt>0.18</ArmorRating_Blunt>
<ArmorRating_Sharp>0.18</ArmorRating_Sharp>
<Wildness>0.3</Wildness>
</statBases>
<uiIconScale>1.1</uiIconScale>
<tools>
<li>
<label>head claw</label>
<capacities>
<li>Cut</li>
</capacities>
<power>7</power>
<cooldownTime>2</cooldownTime>
<linkedBodyPartsGroup>HeadClaw</linkedBodyPartsGroup>
</li>
<li>
<label>head</label>
<capacities>
<li>Blunt</li>
</capacities>
<power>6</power>
<cooldownTime>2</cooldownTime>
<linkedBodyPartsGroup>HeadAttackTool</linkedBodyPartsGroup>
<ensureLinkedBodyPartsGroupAlwaysUsable>true</ensureLinkedBodyPartsGroupAlwaysUsable>
<chanceFactor>0.2</chanceFactor>
</li>
</tools>
<race>
<thinkTreeMain>ARA_InsectWorker</thinkTreeMain>
<body>BeetleLikeWithClaw</body>
<thinkTreeMain>ARA_Insect_WithPlanting</thinkTreeMain>
<baseHungerRate>0.25</baseHungerRate>
<baseBodySize>0.8</baseBodySize>
<baseHealthScale>1.7</baseHealthScale>
<useMeatFrom>Megaspider</useMeatFrom>
<lifeExpectancy>6</lifeExpectancy>
<trainability>Advanced</trainability>
<specialTrainables>
<li MayRequire="Ludeon.RimWorld.Odyssey">Dig</li>
<li>ARA_Sowing</li>
</specialTrainables>
<lifeStageAges>
<li>
<def>EusocialInsectAdult</def>
<minAge>0</minAge>
<soundWounded>Pawn_Spelopede_Pain</soundWounded>
<soundDeath>Pawn_Spelopede_Death</soundDeath>
<soundCall>Pawn_Spelopede_Call</soundCall>
<soundAngry>Pawn_Spelopede_Angry</soundAngry>
</li>
</lifeStageAges>
<soundMeleeHitPawn>Pawn_Melee_SmallScratch_HitPawn</soundMeleeHitPawn>
<soundMeleeHitBuilding>Pawn_Melee_SmallScratch_HitBuilding</soundMeleeHitBuilding>
<soundMeleeMiss>Pawn_Melee_SmallScratch_Miss</soundMeleeMiss>
<headPosPerRotation>
<li>(0.0, 0, 0.14)</li>
<li>(0.07, 0, 0.08)</li>
<li>(0.0, 0, -0.12)</li>
<li>(-0.07, 0, 0.08)</li>
</headPosPerRotation>
<deathAction Class="DeathActionProperties_Vanish">
<workerClass>DeathActionWorker_Vanish</workerClass>
</deathAction>
</race>
<comps>
<li Class="ArachnaeSwarm.CompProperties_AdvancedTraining">
<skillLevels>
<li>
<skill>Plants</skill>
<level>10</level> <!-- 动物生成时种植技能固定为8级 -->
</li>
</skillLevels>
<instantTrainables>
<li>ARA_Sowing</li> <!-- 动物生成时,立即完成播种训练 -->
</instantTrainables>
<disableAllSkillDecay>true</disableAllSkillDecay> <!-- 阻止这个动物的所有技能衰减 -->
</li>
</comps>
<tradeTags>
<li>AnimalInsect</li>
</tradeTags>
</ThingDef>
<PawnKindDef ParentName="InsectKindBase">
<defName>ArachnaeBase_Race_Scavenger</defName>
<label>阿拉克涅食腐虫</label>
<race>ArachnaeBase_Race_Scavenger</race>
<combatPower>75</combatPower>
<ecoSystemWeight>0.35</ecoSystemWeight>
<forceDeathOnDowned>true</forceDeathOnDowned>
<forceNoDeathNotification>true</forceNoDeathNotification>
<lifeStages>
<li>
<bodyGraphicData>
<texPath>Things/Pawn/Animal/Spelopede/Spelopede</texPath>
<drawSize>1</drawSize>
<color>(156,148,125)</color>
<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

@@ -508,9 +508,9 @@
<cooldownTicks>9999</cooldownTicks>
<productionQueue>
<li>
<pawnKind>Spelopede</pawnKind>
<pawnKind>ArachnaeBase_Race_Scavenger</pawnKind>
<count>3</count>
<cooldownTicks>600</cooldownTicks>
<cooldownTicks>6000</cooldownTicks>
</li>
</productionQueue>
<spawnEffecter>CocoonDestroyed</spawnEffecter>

View File

@@ -242,7 +242,7 @@
<!-- 手术继承自人类 -->
<humanRecipeImport>false</humanRecipeImport>
<!-- 睡觉定义 -->
<canLayDown>true</canLayDown>
<canLayDown>false</canLayDown>
<!-- 各种零件定义 -->
<alienPartGenerator Inherit="False">
<!-- 允许的头部 -->

View File

@@ -3,18 +3,18 @@
<ThingDef ParentName="OrganicProductBase">
<defName>ARA_InsectJelly</defName>
<label>阿拉克涅虫蜜</label>
<description>由虫族储存和用作食物的果冻。它顺滑、浓郁,能满足食用者的娱乐需求。由于其独特的生物特性,它几乎可以滋养任何生物,永不腐烂</description>
<possessionCount>10</possessionCount>
<description>阿拉克涅虫族储存和用作食物的果冻。它顺滑、浓郁、永不腐烂,能满足食用者的娱乐需求。由于其独特的生物特性,非阿拉克涅虫族也可以食用这种食物,并且不会引起食物中毒</description>
<possessionCount>20</possessionCount>
<graphicData>
<texPath>Things/Item/Resource/AnimalProductRaw/InsectJelly</texPath>
<graphicClass>Graphic_StackCount</graphicClass>
</graphicData>
<socialPropernessMatters>true</socialPropernessMatters>
<statBases>
<MarketValue>8.0</MarketValue>
<MarketValue>5.0</MarketValue>
<Mass>0.025</Mass>
<Nutrition>0.1</Nutrition>
<FoodPoisonChanceFixedHuman>0.02</FoodPoisonChanceFixedHuman>
<FoodPoisonChanceFixedHuman>0</FoodPoisonChanceFixedHuman>
</statBases>
<ingestible>
<foodType>AnimalProduct</foodType>

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<TerrainDef ParentName="FloorBase">
<defName>ARA_InsectSludge</defName>
<label>阿拉克涅菌毯</label>
<description>由阿拉克涅虫族所铺设的由真菌和分泌物混合得到的地面,清洁速度很快,但是对其他种族来说不太好看。</description>
<designationCategory>Floors</designationCategory>
<texturePath>Terrain/Surfaces/InsectSludge</texturePath>
<pollutionShaderType MayRequire="Ludeon.RimWorld.Biotech">TerrainFadeRoughLinearBurn</pollutionShaderType>
<pollutionOverlayTexturePath>Terrain/Surfaces/PollutionMud</pollutionOverlayTexturePath>
<pollutionColor>(1, 1, 1, 1)</pollutionColor>
<edgeType>FadeRough</edgeType>
<resourcesFractionWhenDeconstructed>0</resourcesFractionWhenDeconstructed>
<renderPrecedence>330</renderPrecedence>
<pathCost>0</pathCost>
<generatedFilth>Filth_Slime</generatedFilth>
<filthAcceptanceMask>
<li>Unnatural</li>
</filthAcceptanceMask>
<natural>true</natural>
<fertility>0</fertility>
<takeFootprints>True</takeFootprints>
<avoidWander>false</avoidWander>
<statBases>
<Beauty>-6</Beauty>
<CleaningTimeFactor>0.25</CleaningTimeFactor>
<Cleanliness>0</Cleanliness>
<Flammability>0.2</Flammability>
</statBases>
<!-- <tags>
<li>Insect</li>
</tags> -->
</TerrainDef>
<ThingDef ParentName="Wall">
<defName>ARA_InsectWall</defName>
<label>堆积山岩</label>
<description>阿拉克涅工蜂将硬质岩石堆起来形成的墙壁,非常坚硬并且不会花费材料,就是看起来不太美观。</description>
<uiOrder>1800</uiOrder>
<uiIconPath>Wula/Building/Linked/WULA_Fortress_Wall_MenuIcon</uiIconPath>
<graphicData>
<texPath>ArachnaeSwarm/Building/ARA_InsectWall</texPath>
<graphicClass>Graphic_Single</graphicClass>
<shaderType>CutoutComplex</shaderType>
</graphicData>
<designationCategory>Structure</designationCategory>
<mineable>true</mineable>
<blockLight>true</blockLight>
<statBases>
<MarketValue>0</MarketValue>
<Beauty>-6</Beauty>
<MaxHitPoints>1000</MaxHitPoints>
<WorkToBuild>1500</WorkToBuild>
<Flammability>0</Flammability>
<Cleanliness>0</Cleanliness>
</statBases>
<building>
<isAirtight>true</isAirtight>
<isNaturalRock>true</isNaturalRock>
</building>
<terrainAffordanceNeeded>Heavy</terrainAffordanceNeeded>
<costStuffCount>0</costStuffCount>
<stuffCategories Inherit="False"/>
<!-- <placeWorkers>
<li>PlaceWorker_OnSubstructure</li>
</placeWorkers> -->
<damageMultipliers Inherit="False">
<li>
<damageDef>Bomb</damageDef>
<multiplier>0.1</multiplier>
</li>
<li>
<damageDef>Thump</damageDef>
<multiplier>0.1</multiplier>
</li>
</damageMultipliers>
<comps>
<li Class="ArachnaeSwarm.CompProperties_DelayedTerrainSpawn">
<delayTicks>60</delayTicks> <!-- 60 ticks = 1 second -->
<terrainToSpawn>ARA_InsectSludge</terrainToSpawn>
<spawnRadius>1.35</spawnRadius>
</li>
</comps>
</ThingDef>
</Defs>

View File

@@ -3,7 +3,7 @@
<ThingDef ParentName="BuildingNaturalBase">
<defName>ARA_InteractiveEggSac</defName>
<label>阿拉克涅虫卵囊-休眠中</label>
<description>一个脆弱、易燃、黏滑的囊状物,是阿拉克涅女皇种所诞之卵,内含哺育一只新督虫所需的营养和遗传物质——可以通过阿拉克涅女皇种的交互完成激活进程。</description>
<description>一个脆弱、易燃、黏滑的囊状物,是阿拉克涅女皇种所诞之卵,内含哺育一只新督虫所需的营养和遗传物质可以通过阿拉克涅女皇种的交互完成激活进程——参阅虫卵的具体信息,了解各个督虫的特点</description>
<thingClass>Building</thingClass>
<descriptionHyperlinks>
<ThingDef>ArachnaeNode_Race_Myrmecocystus</ThingDef>
@@ -96,38 +96,5 @@
<spawnRadius>4.6</spawnRadius>
</li>
</comps>
</ThingDef>
<TerrainDef ParentName="FloorBase">
<defName>ARA_InsectSludge</defName>
<label>阿拉克涅菌毯</label>
<description>由阿拉克涅虫族所铺设的由真菌和分泌物混合得到的地面,清洁速度很快,但是对其他种族来说不太好看。</description>
<designationCategory>Floors</designationCategory>
<texturePath>Terrain/Surfaces/InsectSludge</texturePath>
<pollutionShaderType MayRequire="Ludeon.RimWorld.Biotech">TerrainFadeRoughLinearBurn</pollutionShaderType>
<pollutionOverlayTexturePath>Terrain/Surfaces/PollutionMud</pollutionOverlayTexturePath>
<pollutionColor>(1, 1, 1, 1)</pollutionColor>
<edgeType>FadeRough</edgeType>
<resourcesFractionWhenDeconstructed>0</resourcesFractionWhenDeconstructed>
<renderPrecedence>330</renderPrecedence>
<pathCost>0</pathCost>
<generatedFilth>Filth_Slime</generatedFilth>
<filthAcceptanceMask>
<li>Unnatural</li>
</filthAcceptanceMask>
<natural>true</natural>
<fertility>0</fertility>
<takeFootprints>True</takeFootprints>
<avoidWander>false</avoidWander>
<statBases>
<Beauty>-6</Beauty>
<CleaningTimeFactor>0.25</CleaningTimeFactor>
<Cleanliness>0</Cleanliness>
<Flammability>0.2</Flammability>
</statBases>
<!-- <tags>
<li>Insect</li>
</tags> -->
</TerrainDef>
</Defs>

View File

@@ -0,0 +1,444 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThinkTreeDef>
<defName>ARA_InsectWorker</defName>
<thinkRoot Class="ThinkNode_Priority">
<subNodes>
<!-- Keep lying down if we have to -->
<li Class="ThinkNode_ConditionalMustKeepLyingDown">
<subNodes>
<!-- Do a queued job if possible -->
<li Class="ThinkNode_QueuedJob">
<inBedOnly>true</inBedOnly>
</li>
<!-- Keep lying down -->
<li Class="JobGiver_KeepLyingDown"/>
</subNodes>
</li>
<li Class="ThinkNode_Subtree">
<treeDef>Downed</treeDef>
</li>
<li Class="ThinkNode_Subtree">
<treeDef>BurningResponse</treeDef>
</li>
<li Class="ThinkNode_Subtree">
<treeDef>MentalStateCritical</treeDef>
</li>
<!-- React to close melee threat -->
<li Class="JobGiver_ReactToCloseMeleeThreat"/>
<!-- Do a queued job -->
<li Class="ThinkNode_QueuedJob"/>
<!-- Wild insects dig out if no path to map edge and starving -->
<li Class="ThinkNode_ConditionalHasFaction">
<invert>true</invert>
<subNodes>
<li Class="ThinkNode_ConditionalStarving">
<subNodes>
<li Class="ThinkNode_ConditionalBodySize">
<min>0.7</min>
<subNodes>
<li Class="ThinkNode_Subtree">
<treeDef>DigOutIfCannotReachMapEdge</treeDef>
</li>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Leave if timed out -->
<li Class="ThinkNode_ConditionalExitTimedOut">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_ExitMapRandom">
<defaultLocomotion>Walk</defaultLocomotion>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Mental state non critical -->
<li Class="ThinkNode_Subtree">
<treeDef>MentalStateNonCritical</treeDef>
</li>
<!-- Forced goto -->
<li Class="ThinkNode_ConditionalForcedGoto">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_ForcedGoto"/>
</subNodes>
</li>
</subNodes>
</li>
<!-- Behavior when roped -->
<li Class="ThinkNode_Subtree">
<treeDef>RopedPawn</treeDef>
</li>
<!-- Lord directives -->
<li Class="ThinkNode_Subtree">
<treeDef>LordDuty</treeDef>
</li>
<!-- Insertion hook for modders -->
<li Class="ThinkNode_SubtreesByTag">
<insertTag>Animal_PreMain</insertTag>
</li>
<li Class="ThinkNode_SubtreesByTag">
<insertTag>Insect_PreMain</insertTag>
</li>
<li Class="ThinkNode_ConditionalHasFaction">
<invert>true</invert>
<subNodes>
<!-- Wild insects with no lord will fight nearby enemies -->
<li Class="JobGiver_AIFightEnemies">
<targetAcquireRadius>30</targetAcquireRadius> <!-- Same as DefendAndExpandHive -->
<targetKeepRadius>35</targetKeepRadius>
</li>
<!-- Wild insects leave map in some conditions -->
<li Class="ThinkNode_Subtree">
<treeDef>LeaveIfWrongSeason</treeDef>
</li>
<li Class="ThinkNode_Subtree">
<treeDef>LeaveIfStarving</treeDef>
</li>
<!-- Wild insects randomly leave map -->
<li Class="ThinkNode_ChancePerHour_Constant">
<mtbDays>60</mtbDays>
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_ExitMapRandom">
<defaultLocomotion>Walk</defaultLocomotion>
</li>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Insects of a faction that's not the players without a lord leave randomly -->
<li Class="ThinkNode_ConditionalOfPlayerFaction">
<invert>true</invert>
<subNodes>
<li Class="ThinkNode_ConditionalHasFaction">
<subNodes>
<li Class="ThinkNode_ConditionalNoLord">
<subNodes>
<li Class="ThinkNode_ChancePerHour_Constant">
<mtbDays>60</mtbDays>
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_ExitMapRandom">
<defaultLocomotion>Walk</defaultLocomotion>
</li>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Tame insects -->
<li Class="ThinkNode_ConditionalOfPlayerFaction">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>TrainedAnimalBehavior</tagToGive>
<subNodes>
<!-- Trained behavior: obedience: Follow and defend master -->
<li Class="ThinkNode_ConditionalTrainableCompleted">
<trainable>Obedience</trainable>
<subNodes>
<li Class="ThinkNode_ConditionalShouldFollowMaster">
<subNodes>
<li Class="JobGiver_AIDefendMaster">
<attackMeleeThreatEvenIfNotHostile>true</attackMeleeThreatEvenIfNotHostile>
</li>
<li Class="JobGiver_AIFollowMaster"/>
<li Class="JobGiver_WanderNearMaster"/>
</subNodes>
</li>
</subNodes>
</li>
<!-- Trained behavior: Rescue-->
<li Class="ThinkNode_ConditionalTrainableCompleted">
<trainable>Rescue</trainable>
<subNodes>
<li Class="JobGiver_RescueNearby">
<radius>75</radius>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Take care of critical needs (below rescue - so heroic!)-->
<li Class="ThinkNode_Tagger">
<tagToGive>RestingForMedicalReasons</tagToGive>
<subNodes>
<li Class="JobGiver_PatientGoToBed"/>
</subNodes>
</li>
<li Class="JobGiver_SeekAllowedArea"/>
<li Class="JobGiver_SeekSafeTemperature"/>
</subNodes>
</li>
<!-- Eat random things out of curiosity -->
<li Class="ThinkNode_ChancePerHour_Constant">
<mtbDays>60</mtbDays>
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>SatisfyingNeeds</tagToGive>
<subNodes>
<li Class="JobGiver_EatRandom"/>
</subNodes>
</li>
</subNodes>
</li>
<!-- Satisfy basic needs -->
<li Class="ThinkNode_Subtree">
<treeDef>SatisfyBasicNeeds</treeDef>
</li>
<!-- Tame insect: do useful things for the colony-->
<li Class="ThinkNode_ConditionalHasFaction">
<subNodes>
<!-- Do a queued job -->
<li Class="ThinkNode_QueuedJob" />
<li Class="JobGiver_SeekAllowedArea" />
<li Class="JobGiver_Work">
<emergency>true</emergency>
</li>
<li Class="JobGiver_Work" />
<!-- Try to mate -->
<li Class="ThinkNode_ChancePerHour_Mate">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>SatisfyingNeeds</tagToGive>
<subNodes>
<li Class="JobGiver_Mate"/>
</subNodes>
</li>
</subNodes>
</li>
<!-- Nuzzle randoms -->
<li Class="ThinkNode_ChancePerHour_Nuzzle">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_Nuzzle"/>
</subNodes>
</li>
</subNodes>
</li>
<!-- Roamers gonna roam -->
<li Class="ThinkNode_ChancePerDay_Roam">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_StartRoaming"/>
</subNodes>
</li>
</subNodes>
</li>
<!-- Trained behavior: Haul-->
<li Class="ThinkNode_ChancePerHour_Constant">
<mtbHours>1.5</mtbHours>
<subNodes>
<li Class="ThinkNode_ConditionalRequireCapacities">
<requiredCapacities>
<li>Manipulation</li>
</requiredCapacities>
<subNodes>
<li Class="ThinkNode_ConditionalTrainableCompleted">
<trainable>Haul</trainable>
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>TrainedAnimalBehavior</tagToGive>
<subNodes>
<li Class="JobGiver_Haul"/>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Trained behavior: Forage -->
<li Class="ThinkNode_ConditionalTrainableCompleted" MayRequire="Ludeon.RimWorld.Odyssey">
<trainable>Forage</trainable>
<subNodes>
<li Class="ThinkNode_ChancePerHour_Forage">
<subNodes>
<li Class="ThinkNode_ConditionalAnimalShouldForage">
<subNodes>
<li Class="JobGiver_Forage" />
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Trained behavior: Mine -->
<li Class="ThinkNode_ConditionalTrainableCompleted" MayRequire="Ludeon.RimWorld.Odyssey">
<trainable>Dig</trainable>
<subNodes>
<li Class="ThinkNode_ConditionalAnimalShouldDig">
<subNodes>
<li Class="JobGiver_Mine" />
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Insertion hook for modders -->
<li Class="ThinkNode_SubtreesByTag">
<insertTag>Animal_PreWander</insertTag>
</li>
<li Class="ThinkNode_SubtreesByTag">
<insertTag>Insect_PreWander</insertTag>
</li>
<!-- Tame insect: wander near colony if possible -->
<li Class="ThinkNode_ConditionalOfPlayerFaction">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Idle</tagToGive>
<subNodes>
<!-- Wander near your current position if in hostile map -->
<li Class="ThinkNode_ConditionalAnyEnemyInHostileMap">
<subNodes>
<li Class="JobGiver_WanderAnywhere">
<maxDanger>None</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
</li>
</subNodes>
</li>
<li Class="ThinkNode_ConditionalRoamer">
<subNodes>
<li Class="JobGiver_WanderInRoofedCellsInPen">
<maxDanger>None</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
<expiryInterval>500</expiryInterval>
</li>
<!-- tame roamers should not wander too far, and if unenclosed wander near a suitable pen marker 10% of the time -->
<li Class="ThinkNode_ConditionalRandom">
<chance>0.1</chance>
<subNodes>
<li Class="JobGiver_WanderInPen">
<maxDanger>None</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
<expiryInterval>500</expiryInterval>
</li>
</subNodes>
</li>
<li Class="JobGiver_WanderAnywhere">
<maxDanger>None</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
</li>
</subNodes>
</li>
<!-- Wander near colony -->
<li Class="JobGiver_WanderColony">
<maxDanger>None</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Of neutral faction: rest and then exit the map -->
<li Class="ThinkNode_ConditionalNonPlayerNonHostileFaction">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>RestingForMedicalReasons</tagToGive>
<subNodes>
<li Class="JobGiver_PatientGoToBed"/>
</subNodes>
</li>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_ExitMapBest">
<defaultLocomotion>Walk</defaultLocomotion>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Wander -->
<li Class="ThinkNode_Tagger">
<tagToGive>Idle</tagToGive>
<subNodes>
<li Class="ThinkNode_ConditionalHerdAnimal">
<subNodes>
<li Class="JobGiver_WanderHerd">
<maxDanger>Deadly</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
</li>
</subNodes>
</li>
<li Class="JobGiver_WanderAnywhere">
<maxDanger>Deadly</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
</li>
</subNodes>
</li>
<li Class="JobGiver_IdleError"/>
</subNodes>
</thinkRoot>
</ThinkTreeDef>
</Defs>

View File

@@ -1,7 +1,455 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThinkTreeDef>
<defName>ARA_Humanlike</defName> <!-- 更改defName以避免与原版Humanlike冲突 -->
<defName>ARA_Insect_WithPlanting</defName>
<thinkRoot Class="ThinkNode_Priority">
<subNodes>
<!-- Keep lying down if we have to -->
<li Class="ThinkNode_ConditionalMustKeepLyingDown">
<subNodes>
<!-- Do a queued job if possible -->
<li Class="ThinkNode_QueuedJob">
<inBedOnly>true</inBedOnly>
</li>
<!-- Keep lying down -->
<li Class="JobGiver_KeepLyingDown"/>
</subNodes>
</li>
<li Class="ThinkNode_Subtree">
<treeDef>Downed</treeDef>
</li>
<li Class="ThinkNode_Subtree">
<treeDef>BurningResponse</treeDef>
</li>
<li Class="ThinkNode_Subtree">
<treeDef>MentalStateCritical</treeDef>
</li>
<!-- React to close melee threat -->
<li Class="JobGiver_ReactToCloseMeleeThreat"/>
<!-- Do a queued job -->
<li Class="ThinkNode_QueuedJob"/>
<!-- Wild insects dig out if no path to map edge and starving -->
<li Class="ThinkNode_ConditionalHasFaction">
<invert>true</invert>
<subNodes>
<li Class="ThinkNode_ConditionalStarving">
<subNodes>
<li Class="ThinkNode_ConditionalBodySize">
<min>0.7</min>
<subNodes>
<li Class="ThinkNode_Subtree">
<treeDef>DigOutIfCannotReachMapEdge</treeDef>
</li>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Leave if timed out -->
<li Class="ThinkNode_ConditionalExitTimedOut">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_ExitMapRandom">
<defaultLocomotion>Walk</defaultLocomotion>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Mental state non critical -->
<li Class="ThinkNode_Subtree">
<treeDef>MentalStateNonCritical</treeDef>
</li>
<!-- Forced goto -->
<li Class="ThinkNode_ConditionalForcedGoto">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_ForcedGoto"/>
</subNodes>
</li>
</subNodes>
</li>
<!-- Behavior when roped -->
<li Class="ThinkNode_Subtree">
<treeDef>RopedPawn</treeDef>
</li>
<!-- Lord directives -->
<li Class="ThinkNode_Subtree">
<treeDef>LordDuty</treeDef>
</li>
<!-- Insertion hook for modders -->
<li Class="ThinkNode_SubtreesByTag">
<insertTag>Animal_PreMain</insertTag>
</li>
<li Class="ThinkNode_SubtreesByTag">
<insertTag>Insect_PreMain</insertTag>
</li>
<li Class="ThinkNode_ConditionalHasFaction">
<invert>true</invert>
<subNodes>
<!-- Wild insects with no lord will fight nearby enemies -->
<li Class="JobGiver_AIFightEnemies">
<targetAcquireRadius>30</targetAcquireRadius> <!-- Same as DefendAndExpandHive -->
<targetKeepRadius>35</targetKeepRadius>
</li>
<!-- Wild insects leave map in some conditions -->
<li Class="ThinkNode_Subtree">
<treeDef>LeaveIfWrongSeason</treeDef>
</li>
<li Class="ThinkNode_Subtree">
<treeDef>LeaveIfStarving</treeDef>
</li>
<!-- Wild insects randomly leave map -->
<li Class="ThinkNode_ChancePerHour_Constant">
<mtbDays>60</mtbDays>
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_ExitMapRandom">
<defaultLocomotion>Walk</defaultLocomotion>
</li>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Insects of a faction that's not the players without a lord leave randomly -->
<li Class="ThinkNode_ConditionalOfPlayerFaction">
<invert>true</invert>
<subNodes>
<li Class="ThinkNode_ConditionalHasFaction">
<subNodes>
<li Class="ThinkNode_ConditionalNoLord">
<subNodes>
<li Class="ThinkNode_ChancePerHour_Constant">
<mtbDays>60</mtbDays>
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_ExitMapRandom">
<defaultLocomotion>Walk</defaultLocomotion>
</li>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Tame insects -->
<li Class="ThinkNode_ConditionalOfPlayerFaction">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>TrainedAnimalBehavior</tagToGive>
<subNodes>
<!-- Trained behavior: obedience: Follow and defend master -->
<li Class="ThinkNode_ConditionalTrainableCompleted">
<trainable>Obedience</trainable>
<subNodes>
<li Class="ThinkNode_ConditionalShouldFollowMaster">
<subNodes>
<li Class="JobGiver_AIDefendMaster">
<attackMeleeThreatEvenIfNotHostile>true</attackMeleeThreatEvenIfNotHostile>
</li>
<li Class="JobGiver_AIFollowMaster"/>
<li Class="JobGiver_WanderNearMaster"/>
</subNodes>
</li>
</subNodes>
</li>
<!-- Trained behavior: Rescue-->
<li Class="ThinkNode_ConditionalTrainableCompleted">
<trainable>Rescue</trainable>
<subNodes>
<li Class="JobGiver_RescueNearby">
<radius>75</radius>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Take care of critical needs (below rescue - so heroic!)-->
<li Class="ThinkNode_Tagger">
<tagToGive>RestingForMedicalReasons</tagToGive>
<subNodes>
<li Class="JobGiver_PatientGoToBed"/>
</subNodes>
</li>
<li Class="JobGiver_SeekAllowedArea"/>
<li Class="JobGiver_SeekSafeTemperature"/>
</subNodes>
</li>
<!-- Eat random things out of curiosity -->
<li Class="ThinkNode_ChancePerHour_Constant">
<mtbDays>60</mtbDays>
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>SatisfyingNeeds</tagToGive>
<subNodes>
<li Class="JobGiver_EatRandom"/>
</subNodes>
</li>
</subNodes>
</li>
<!-- Satisfy basic needs -->
<li Class="ThinkNode_Subtree">
<treeDef>SatisfyBasicNeeds</treeDef>
</li>
<!-- Tame insect: do useful things for the colony-->
<li Class="ThinkNode_ConditionalHasFaction">
<subNodes>
<!-- Try to mate -->
<li Class="ThinkNode_ChancePerHour_Mate">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>SatisfyingNeeds</tagToGive>
<subNodes>
<li Class="JobGiver_Mate"/>
</subNodes>
</li>
</subNodes>
</li>
<!-- Nuzzle randoms -->
<li Class="ThinkNode_ChancePerHour_Nuzzle">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_Nuzzle"/>
</subNodes>
</li>
</subNodes>
</li>
<!-- Roamers gonna roam -->
<li Class="ThinkNode_ChancePerDay_Roam">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_StartRoaming"/>
</subNodes>
</li>
</subNodes>
</li>
<!-- Trained behavior: Haul-->
<li Class="ThinkNode_ChancePerHour_Constant">
<mtbHours>1.5</mtbHours>
<subNodes>
<li Class="ThinkNode_ConditionalRequireCapacities">
<requiredCapacities>
<li>Manipulation</li>
</requiredCapacities>
<subNodes>
<li Class="ThinkNode_ConditionalTrainableCompleted">
<trainable>Haul</trainable>
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>TrainedAnimalBehavior</tagToGive>
<subNodes>
<li Class="JobGiver_Haul"/>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Trained behavior: Forage -->
<li Class="ThinkNode_ConditionalTrainableCompleted" MayRequire="Ludeon.RimWorld.Odyssey">
<trainable>Forage</trainable>
<subNodes>
<li Class="ThinkNode_ChancePerHour_Forage">
<subNodes>
<li Class="ThinkNode_ConditionalAnimalShouldForage">
<subNodes>
<li Class="JobGiver_Forage" />
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Trained behavior: Mine -->
<li Class="ThinkNode_ConditionalTrainableCompleted" MayRequire="Ludeon.RimWorld.Odyssey">
<trainable>Dig</trainable>
<subNodes>
<li Class="ThinkNode_ConditionalAnimalShouldDig">
<subNodes>
<li Class="JobGiver_Mine" />
</subNodes>
</li>
</subNodes>
</li>
<!-- OUR CUSTOM LOGIC INJECTION FOR PLANT HARVESTING -->
<li Class="ThinkNode_ConditionalTrainableCompleted">
<trainable>ARA_Sowing</trainable> <!-- Harvesting is part of Sowing skill -->
<subNodes>
<!-- 统一的农业工作节点 -->
<li Class="ArachnaeSwarm.ThinkNode_ConditionalAnimalShouldDoGrowingWork">
<subNodes>
<!-- 统一的、智能的农业 JobGiver -->
<li Class="ArachnaeSwarm.JobGiver_Grower" />
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Insertion hook for modders -->
<li Class="ThinkNode_SubtreesByTag">
<insertTag>Animal_PreWander</insertTag>
</li>
<li Class="ThinkNode_SubtreesByTag">
<insertTag>Insect_PreWander</insertTag>
</li>
<!-- Tame insect: wander near colony if possible -->
<li Class="ThinkNode_ConditionalOfPlayerFaction">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>Idle</tagToGive>
<subNodes>
<!-- Wander near your current position if in hostile map -->
<li Class="ThinkNode_ConditionalAnyEnemyInHostileMap">
<subNodes>
<li Class="JobGiver_WanderAnywhere">
<maxDanger>None</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
</li>
</subNodes>
</li>
<li Class="ThinkNode_ConditionalRoamer">
<subNodes>
<li Class="JobGiver_WanderInRoofedCellsInPen">
<maxDanger>None</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
<expiryInterval>500</expiryInterval>
</li>
<!-- tame roamers should not wander too far, and if unenclosed wander near a suitable pen marker 10% of the time -->
<li Class="ThinkNode_ConditionalRandom">
<chance>0.1</chance>
<subNodes>
<li Class="JobGiver_WanderInPen">
<maxDanger>None</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
<expiryInterval>500</expiryInterval>
</li>
</subNodes>
</li>
<li Class="JobGiver_WanderAnywhere">
<maxDanger>None</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
</li>
</subNodes>
</li>
<!-- Wander near colony -->
<li Class="JobGiver_WanderColony">
<maxDanger>None</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Of neutral faction: rest and then exit the map -->
<li Class="ThinkNode_ConditionalNonPlayerNonHostileFaction">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>RestingForMedicalReasons</tagToGive>
<subNodes>
<li Class="JobGiver_PatientGoToBed"/>
</subNodes>
</li>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
<li Class="JobGiver_ExitMapBest">
<defaultLocomotion>Walk</defaultLocomotion>
</li>
</subNodes>
</li>
</subNodes>
</li>
<!-- Wander -->
<li Class="ThinkNode_Tagger">
<tagToGive>Idle</tagToGive>
<subNodes>
<li Class="ThinkNode_ConditionalHerdAnimal">
<subNodes>
<li Class="JobGiver_WanderHerd">
<maxDanger>Deadly</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
</li>
</subNodes>
</li>
<li Class="JobGiver_WanderAnywhere">
<maxDanger>Deadly</maxDanger>
<ticksBetweenWandersRange>120~240</ticksBetweenWandersRange>
</li>
</subNodes>
</li>
<li Class="JobGiver_IdleError"/>
</subNodes>
</thinkRoot>
</ThinkTreeDef>
<ThinkTreeDef>
<defName>ARA_Humanlike</defName> <!-- 更改defName以避免与原版Humanlike冲突 -->
<thinkRoot Class="ThinkNode_Priority">
<subNodes>
@@ -20,7 +468,7 @@
</li>
</subNodes>
</li>
<!-- If we HAVE to keep lying down... -->
<li Class="ThinkNode_ConditionalMustKeepLyingDown">
<subNodes>
@@ -45,12 +493,12 @@
</li>
</subNodes>
</li>
<!-- Keep lying down -->
<li Class="JobGiver_KeepLyingDown" />
</subNodes>
</li>
<li Class="ThinkNode_Subtree">
<treeDef>Downed</treeDef>
</li>
@@ -65,7 +513,7 @@
<li Class="ThinkNode_Subtree" MayRequire="Ludeon.RimWorld.Biotech">
<treeDef>Abilities_Escape</treeDef>
</li>
<!-- React to close melee threat -->
<li Class="JobGiver_ReactToCloseMeleeThreat" />
@@ -83,7 +531,7 @@
<li Class="ThinkNode_SubtreesByTag">
<insertTag>Humanlike_PostMentalState</insertTag>
</li>
<!-- Do a queued job -->
<li Class="ThinkNode_QueuedJob" />
@@ -121,21 +569,21 @@
<li Class="ThinkNode_SubtreesByTag">
<insertTag>Humanlike_PostDuty</insertTag>
</li>
<!-- JobGiver_MaintainBuildings start -->
<li Class="ThinkNode_ChancePerHour_Constant">
<mtbHours>2.5</mtbHours>
<subNodes>
<li Class="ArachnaeSwarm.JobGiver_MaintainBuildings">
<maintainableThingDefs>
<li>ARA_InteractiveEggSac</li> <!-- 默认维护Hive -->
<li>ARA_InteractiveEggSac</li> <!-- 默认维护Hive -->
</maintainableThingDefs>
</li>
</subNodes>
</li>
<li Class="ArachnaeSwarm.JobGiver_MaintainBuildings">
<maintainableThingDefs>
<li>ARA_InteractiveEggSac</li> <!-- 默认维护Hive -->
<li>ARA_InteractiveEggSac</li> <!-- 默认维护Hive -->
</maintainableThingDefs>
<onlyIfDamagingState>true</onlyIfDamagingState>
</li>
@@ -158,7 +606,7 @@
</li>
</subNodes>
</li>
<!-- Escape -->
<li Class="ThinkNode_Tagger">
<tagToGive>Escaping</tagToGive>
@@ -168,7 +616,7 @@
</li>
</subNodes>
</li>
<!-- Exit map if released -->
<li Class="ThinkNode_ConditionalReleased">
<subNodes>
@@ -189,14 +637,14 @@
<li Class="JobGiver_PatientGoToBed" />
</subNodes>
</li>
<li Class="ThinkNode_Tagger">
<tagToGive>ChangingApparel</tagToGive>
<subNodes>
<li Class="JobGiver_PrisonerGetDressed" />
</subNodes>
</li>
<li Class="ThinkNode_Tagger">
<tagToGive>SatisfyingNeeds</tagToGive>
<subNodes>
@@ -220,7 +668,7 @@
</li>
</subNodes>
</li>
<!-- If in non-PlayerHomeMap -->
<li Class="ThinkNode_ConditionalInNonPlayerHomeMap">
<subNodes>
@@ -247,7 +695,7 @@
</li>
</subNodes>
</li>
<li Class="ThinkNode_Tagger">
<tagToGive>Idle</tagToGive>
<subNodes>
@@ -256,7 +704,7 @@
</li>
</subNodes>
</li>
<li Class="JobGiver_IdleError" />
</subNodes>
</li>
@@ -279,7 +727,7 @@
<leaveJoinableLordIfIssuesJob>true</leaveJoinableLordIfIssuesJob>
<emergency>true</emergency>
</li>
<!-- Get food (only if starving) -->
<li Class="ThinkNode_ConditionalStarving">
<subNodes>
@@ -375,7 +823,7 @@
<li Class="ThinkNode_SubtreesByTag">
<insertTag>Humanlike_PreMain</insertTag>
</li>
<!-- Main colonist behavior core -->
<li Class="ThinkNode_ConditionalColonist">
<subNodes>
@@ -416,7 +864,7 @@
<li Class="JobGiver_IdleJoy" />
</subNodes>
</li>
<!-- Wander -->
<li Class="JobGiver_WanderColony">
<maxDanger>None</maxDanger>
@@ -454,7 +902,7 @@
<li Class="JobGiver_PatientGoToBed" />
</subNodes>
</li>
<li Class="ThinkNode_Tagger">
<tagToGive>Misc</tagToGive>
<subNodes>
@@ -498,4 +946,5 @@
</subNodes>
</thinkRoot>
</ThinkTreeDef>
</Defs>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<TrainableDef>
<defName>ARA_Sowing</defName>
<label>种植</label>
<description>允许该生物执行种植任务。</description>
<!-- 标记为特殊训练,这样它就会被 PawnColumnWorker_Trainable_Special 统一管理 -->
<specialTrainable>true</specialTrainable>
<!-- 训练难度和所需智力 -->
<difficulty>5</difficulty>
<requiredTrainability>Advanced</requiredTrainability>
<!-- 训练所需步骤 -->
<steps>3</steps>
<!-- 在UI中的排序 -->
<listPriority>100</listPriority>
</TrainableDef>
</Defs>

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -85,6 +85,14 @@
<Compile Include="CompProperties_AbilityBindDrone.cs" />
<Compile Include="JobGiver_MaintainBuildings.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainHarmony.cs" />
<Compile Include="ThinkNode_ConditionalAnimalShouldDoGrowingWork.cs" />
<Compile Include="CompAdvancedTraining.cs" />
<Compile Include="JobGiver_Grower.cs" />
<Compile Include="WorkGiver_ArachnaeSow.cs" />
<Compile Include="TrainingSystem_Patcher.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="CompProperties_DelayedTerrainSpawn.cs" />
<Compile Include="CompDelayedTerrainSpawn.cs" />

View File

@@ -0,0 +1,77 @@
using System.Collections.Generic;
using Verse;
using RimWorld;
namespace ArachnaeSwarm
{
public class CompProperties_AdvancedTraining : CompProperties
{
// 1. 用于设置固定技能等级
public List<SkillLevelEntry> skillLevels = new List<SkillLevelEntry>();
// 2. 用于指定生成时立即完成的训练
public List<TrainableDef> instantTrainables = new List<TrainableDef>();
// 3. 全局开关:是否阻止所有技能衰减
public bool disableAllSkillDecay = false;
public CompProperties_AdvancedTraining()
{
this.compClass = typeof(CompAdvancedTraining);
}
}
public class SkillLevelEntry
{
public SkillDef skill;
public int level = 0;
// 这里的 disableDecay 字段现在是冗余的,因为我们有全局的 disableAllSkillDecay
// 但为了兼容性或未来可能的需求,可以保留。
// 在当前方案中,它的值将被忽略。
public bool disableDecay = true;
}
public class CompAdvancedTraining : ThingComp
{
public CompProperties_AdvancedTraining Props => (CompProperties_AdvancedTraining)this.props;
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
Pawn pawn = this.parent as Pawn;
if (pawn == null) return;
// --- 1. 设置固定技能等级 ---
if (pawn.skills != null && !Props.skillLevels.NullOrEmpty())
{
foreach (var entry in Props.skillLevels)
{
if (entry.skill != null)
{
var skillRecord = pawn.skills.GetSkill(entry.skill);
if (skillRecord != null)
{
skillRecord.Level = entry.level;
// 注意: 激情 (passion) 影响学习速度,不直接阻止衰减。
// 实际的衰减阻止逻辑在 TrainingSystem_Patcher.cs 中处理。
// 默认情况下,我们不改变 passion除非有特殊需求。
}
}
}
}
// --- 2. 执行瞬间训练 (只在初次生成时) ---
if (!respawningAfterLoad && pawn.training != null && !Props.instantTrainables.NullOrEmpty())
{
foreach (var trainable in Props.instantTrainables)
{
if (trainable != null && !pawn.training.HasLearned(trainable))
{
pawn.training.Train(trainable, null, complete: true);
}
}
}
}
}
}

View File

@@ -0,0 +1,158 @@
using System.Collections.Generic;
using Verse;
using Verse.AI;
using RimWorld;
namespace ArachnaeSwarm
{
public class JobGiver_Grower : ThinkNode_JobGiver
{
private WorkGiver_GrowerHarvest _workGiverHarvest;
private WorkGiver_Scanner _workGiverPlantsCut; // 通用扫描器类型
private WorkGiver_ArachnaeSow _workGiverArachnaeSow;
protected override Job TryGiveJob(Pawn pawn)
{
// 懒加载 WorkGiver 实例,确保 DefOf 已被初始化
if (_workGiverHarvest == null)
{
_workGiverHarvest = DefDatabase<WorkGiverDef>.GetNamed("GrowerHarvest").Worker as WorkGiver_GrowerHarvest;
_workGiverPlantsCut = DefDatabase<WorkGiverDef>.GetNamed("PlantsCut").Worker as WorkGiver_Scanner;
_workGiverArachnaeSow = new WorkGiver_ArachnaeSow();
if (_workGiverHarvest == null || _workGiverPlantsCut == null || _workGiverArachnaeSow == null)
{
Log.ErrorOnce("JobGiver_Grower: Failed to get a required WorkGiver. DefOfs might not be initialized.", 123458);
return null;
}
}
// 1. 优先收获(自动)
IntVec3 bestHarvestCell = FindClosestHarvestableCell(pawn);
if (bestHarvestCell.IsValid)
{
Job harvestJob = _workGiverHarvest.JobOnCell(pawn, bestHarvestCell);
if (harvestJob != null)
{
return harvestJob;
}
}
// 2. 其次处理手动指定的砍伐/收获任务
Thing bestCuttable = FindClosestWorkableThing(pawn, _workGiverPlantsCut);
if (bestCuttable != null)
{
Job cutJob = _workGiverPlantsCut.JobOnThing(pawn, bestCuttable);
if (cutJob != null)
{
return cutJob;
}
}
// 3. 最后处理播种或清理障碍(由 WorkGiver_ArachnaeSow 处理)
Job sowOrClearJob = FindClosestSowableOrClearJob(pawn, _workGiverArachnaeSow);
if (sowOrClearJob != null)
{
return sowOrClearJob;
}
return null;
}
private Thing FindClosestWorkableThing(Pawn pawn, WorkGiver_Scanner scanner)
{
return GenClosest.ClosestThing_Global(
pawn.Position,
scanner.PotentialWorkThingsGlobal(pawn),
maxDistance: 9999f,
validator: t => t != null && !t.IsForbidden(pawn) && scanner.HasJobOnThing(pawn, t) && pawn.CanReach(t, PathEndMode.Touch, Danger.Deadly)
);
}
private IntVec3 FindClosestHarvestableCell(Pawn pawn)
{
IntVec3 bestCell = IntVec3.Invalid;
float bestDistSq = float.MaxValue;
foreach (Zone zone in pawn.Map.zoneManager.AllZones)
{
if (zone is Zone_Growing growingZone)
{
foreach (IntVec3 cell in growingZone.Cells)
{
float distSq = pawn.Position.DistanceToSquared(cell);
if (distSq < bestDistSq && pawn.CanReach(cell, PathEndMode.ClosestTouch, Danger.Deadly))
{
if (_workGiverHarvest.HasJobOnCell(pawn, cell))
{
bestDistSq = distSq;
bestCell = cell;
}
}
}
}
}
return bestCell;
}
// 修改后的方法:寻找最近的播种或清理 Job
private Job FindClosestSowableOrClearJob(Pawn pawn, WorkGiver_ArachnaeSow scanner)
{
IntVec3 bestClearCell = IntVec3.Invalid;
Job bestClearJob = null;
float bestClearDistSq = float.MaxValue;
IntVec3 bestSowCell = IntVec3.Invalid;
Job bestSowJob = null;
float bestSowDistSq = float.MaxValue;
foreach (Zone zone in pawn.Map.zoneManager.AllZones)
{
if (zone is Zone_Growing growingZone)
{
ThingDef wantedPlant = growingZone.GetPlantDefToGrow();
if (wantedPlant == null) continue;
foreach (IntVec3 cell in growingZone.Cells)
{
float distSq = pawn.Position.DistanceToSquared(cell);
if (pawn.CanReach(cell, PathEndMode.ClosestTouch, Danger.Deadly))
{
Job potentialJob = scanner.JobOnCell(pawn, cell);
if (potentialJob != null)
{
if (potentialJob.def == JobDefOf.CutPlant || potentialJob.def == JobDefOf.HaulToContainer || potentialJob.def == JobDefOf.HaulToCell)
{
if (distSq < bestClearDistSq)
{
bestClearDistSq = distSq;
bestClearJob = potentialJob;
}
}
else if (potentialJob.def == JobDefOf.Sow)
{
if (distSq < bestSowDistSq)
{
bestSowDistSq = distSq;
bestSowJob = potentialJob;
}
}
}
}
}
}
}
// 优先返回清理 Job
if (bestClearJob != null)
{
return bestClearJob;
}
// 其次返回播种 Job
if (bestSowJob != null)
{
return bestSowJob;
}
return null;
}
}
}

View File

@@ -0,0 +1,21 @@
using Verse;
using HarmonyLib;
using System.Reflection;
namespace ArachnaeSwarm
{
// [StaticConstructorOnStartup] 属性确保这个类的静态构造函数在游戏启动时被调用
[StaticConstructorOnStartup]
public static class MainHarmony
{
static MainHarmony()
{
// 创建一个 Harmony 实例。ID 应该是唯一的,通常使用 "作者.Mod名称" 的格式。
var harmony = new Harmony("com.kalospacer.arachnaeswarm");
// Harmony 会自动扫描当前整个程序集(我们的 .dll 文件),
// 寻找所有带有 [HarmonyPatch] 属性的类,并应用它们。
harmony.PatchAll(Assembly.GetExecutingAssembly());
}
}
}

View File

@@ -0,0 +1,38 @@
using Verse;
using Verse.AI;
using RimWorld;
namespace ArachnaeSwarm
{
// 将 DefOf 类放在这里,以便在命名空间内共享
[DefOf]
public static class ARA_TrainableDefOf
{
public static TrainableDef ARA_Sowing;
static ARA_TrainableDefOf()
{
DefOfHelper.EnsureInitializedInCtor(typeof(ARA_TrainableDefOf));
}
}
// 这个新的条件节点将检查动物是否应该执行任何农业工作(播种或切割/收获)
public class ThinkNode_ConditionalAnimalShouldDoGrowingWork : ThinkNode_Conditional
{
protected override bool Satisfied(Pawn pawn)
{
// 首先,进行安全检查,确保 pawn.training 存在
if (pawn.training == null)
{
return false;
}
// 检查动物是否学会并被允许执行“播种”工作
bool canSow = pawn.training.HasLearned(ARA_TrainableDefOf.ARA_Sowing) &&
pawn.training.GetWanted(ARA_TrainableDefOf.ARA_Sowing);
// 现在只需要检查播种技能,因为切割功能已合并
return canSow;
}
}
}

View File

@@ -0,0 +1,48 @@
using HarmonyLib;
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
// Patcher 1: 阻止训练退化
[HarmonyPatch(typeof(Pawn_TrainingTracker), "TrainingTrackerTickRare")]
public static class Patch_TrainingTracker_TickRare
{
public static bool Prefix(Pawn_TrainingTracker __instance)
{
Pawn pawn = Traverse.Create(__instance).Field("pawn").GetValue<Pawn>();
if (pawn == null) return true;
var comp = pawn.GetComp<CompAdvancedTraining>();
if (comp != null && comp.Props.disableAllSkillDecay)
{
return false; // 阻止原版方法运行
}
return true;
}
}
// Patcher 2: 阻止特定技能的衰减
[HarmonyPatch(typeof(SkillRecord), "Interval")]
public static class Patch_SkillRecord_Interval
{
// 使用 __instance 来获取 SkillRecord 对象, __pawn 为 SkillRecord 内部的私有字段
public static bool Prefix(SkillRecord __instance, Pawn ___pawn)
{
if (___pawn == null) return true;
var comp = ___pawn.GetComp<CompAdvancedTraining>();
if (comp == null || comp.Props.skillLevels.NullOrEmpty())
{
return true; // 没有组件或配置,正常执行原版衰减
}
// 检查全局开关:如果设置了 disableAllSkillDecay 为 true则阻止衰减
if (comp.Props.disableAllSkillDecay)
{
return false; // 阻止原版 Interval 方法的执行
}
return true; // 正常执行原版衰减
}
}
}

View File

@@ -0,0 +1,227 @@
using System.Collections.Generic;
using Verse;
using Verse.AI;
using RimWorld;
namespace ArachnaeSwarm
{
public class WorkGiver_ArachnaeSow : WorkGiver_Grower
{
protected static string CantSowCavePlantBecauseOfLightTrans;
protected static string CantSowCavePlantBecauseUnroofedTrans;
public override PathEndMode PathEndMode => PathEndMode.ClosestTouch;
public static void ResetStaticData()
{
CantSowCavePlantBecauseOfLightTrans = "CantSowCavePlantBecauseOfLight".Translate();
CantSowCavePlantBecauseUnroofedTrans = "CantSowCavePlantBecauseUnroofed".Translate();
}
protected override bool ExtraRequirements(IPlantToGrowSettable settable, Pawn pawn)
{
if (!settable.CanAcceptSowNow())
{
return false;
}
IntVec3 c;
if (settable is Zone_Growing zone_Growing)
{
if (!zone_Growing.allowSow)
{
return false;
}
c = zone_Growing.Cells[0];
}
else
{
c = ((Thing)settable).Position;
}
ThingDef wantedPlantDef = WorkGiver_Grower.CalculateWantedPlantDef(c, pawn.Map);
if (wantedPlantDef == null)
{
return false;
}
return true;
}
public override Job JobOnCell(Pawn pawn, IntVec3 c, bool forced = false)
{
Map map = pawn.Map;
if (c.GetVacuum(pawn.Map) >= 0.5f)
{
return null;
}
// 直接计算 wantedPlantDef
ThingDef wantedPlantDefLocal = WorkGiver_Grower.CalculateWantedPlantDef(c, map);
if (wantedPlantDefLocal == null)
{
return null;
}
if (!PlantUtility.GrowthSeasonNow(c, map, wantedPlantDefLocal))
{
return null;
}
List<Thing> thingList = c.GetThingList(map);
Zone_Growing zone_Growing = c.GetZone(map) as Zone_Growing;
bool flag = false;
for (int i = 0; i < thingList.Count; i++)
{
Thing thing = thingList[i];
if (thing.def == wantedPlantDef)
{
return null;
}
if ((thing is Blueprint || thing is Frame) && thing.Faction == pawn.Faction)
{
flag = true;
}
}
if (flag)
{
Thing edifice = c.GetEdifice(map);
if (edifice == null || edifice.def.fertility < 0f)
{
return null;
}
}
if (wantedPlantDefLocal.plant.diesToLight)
{
if (!c.Roofed(map) && !map.GameConditionManager.IsAlwaysDarkOutside)
{
JobFailReason.Is(CantSowCavePlantBecauseUnroofedTrans);
return null;
}
if (map.glowGrid.GroundGlowAt(c, ignoreCavePlants: true) > 0f)
{
JobFailReason.Is(CantSowCavePlantBecauseOfLightTrans);
return null;
}
}
if (wantedPlantDefLocal.plant.interferesWithRoof && c.Roofed(pawn.Map))
{
return null;
}
Plant plant = c.GetPlant(map);
if (plant != null) // 只要地块上有植物
{
// 如果地块上的植物不是我们想要种植的植物,就割除
if (plant.def != wantedPlantDefLocal)
{
if (!pawn.CanReserve(plant, 1, -1, null, forced) || plant.IsForbidden(pawn))
{
return null;
}
if (zone_Growing != null && !zone_Growing.allowCut)
{
return null;
}
if (!forced && plant.TryGetComp<CompPlantPreventCutting>(out var comp) && comp.PreventCutting)
{
return null;
}
return JobMaker.MakeJob(JobDefOf.CutPlant, plant);
}
// 如果地块上的植物是我们想要种植的植物,并且它阻碍了相邻播种,则不割除
// 因为它已经是我们想要种植的植物了
if (plant.def.plant.blockAdjacentSow)
{
return null;
}
}
Thing thing2 = PlantUtility.AdjacentSowBlocker(wantedPlantDefLocal, c, map);
if (thing2 != null)
{
if (thing2 is Plant plant2)
{
// 如果阻碍播种的是植物,并且不是我们想要种植的植物,就割除
if (plant2.def != wantedPlantDefLocal)
{
if (pawn.CanReserveAndReach(plant2, PathEndMode.Touch, Danger.Deadly, 1, -1, null, forced) && !plant2.IsForbidden(pawn))
{
IPlantToGrowSettable plantToGrowSettable = plant2.Position.GetPlantToGrowSettable(plant2.Map);
if (plantToGrowSettable == null || plantToGrowSettable.GetPlantDefToGrow() != plant2.def)
{
Zone_Growing zone_Growing2 = c.GetZone(map) as Zone_Growing;
Zone_Growing zone_Growing3 = c.GetZone(map) as Zone_Growing;
if ((zone_Growing2 != null && !zone_Growing2.allowCut) || (zone_Growing3 != null && !zone_Growing3.allowCut && plant2.def == zone_Growing3.GetPlantDefToGrow()))
{
return null;
}
}
if (!forced && thing2.TryGetComp(out CompPlantPreventCutting comp2) && comp2.PreventCutting)
{
return null;
}
if (PlantUtility.TreeMarkedForExtraction(plant2))
{
return null;
}
return JobMaker.MakeJob(JobDefOf.CutPlant, plant2);
}
}
}
else if (thing2.def.EverHaulable)
{
return HaulAIUtility.HaulAsideJobFor(pawn, thing2);
}
return null;
}
if (wantedPlantDefLocal.plant.sowMinSkill > 0 && ((pawn.skills != null && pawn.skills.GetSkill(SkillDefOf.Plants).Level < wantedPlantDefLocal.plant.sowMinSkill) || (pawn.IsColonyMech && pawn.RaceProps.mechFixedSkillLevel < wantedPlantDefLocal.plant.sowMinSkill)))
{
JobFailReason.Is("UnderAllowedSkill".Translate(wantedPlantDefLocal.plant.sowMinSkill), def.label);
return null;
}
for (int j = 0; j < thingList.Count; j++)
{
Thing thing3 = thingList[j];
if (!thing3.def.BlocksPlanting())
{
continue;
}
if (!pawn.CanReserve(thing3, 1, -1, null, forced))
{
return null;
}
if (thing3.def.category == ThingCategory.Plant)
{
// 如果阻碍播种的是植物,并且不是我们想要种植的植物,就割除
if (thing3.def != wantedPlantDefLocal)
{
if (thing3.IsForbidden(pawn))
{
return null;
}
if (zone_Growing != null && !zone_Growing.allowCut)
{
return null;
}
if (!forced && thing3.TryGetComp<CompPlantPreventCutting>(out var comp3) && comp3.PreventCutting)
{
return null;
}
if (PlantUtility.TreeMarkedForExtraction(thing3))
{
return null;
}
return JobMaker.MakeJob(JobDefOf.CutPlant, thing3);
}
}
else if (thing3.def.EverHaulable)
{
return HaulAIUtility.HaulAsideJobFor(pawn, thing3);
}
return null;
}
if (!wantedPlantDefLocal.CanNowPlantAt(c, map) || !PlantUtility.GrowthSeasonNow(c, map, wantedPlantDefLocal) || !pawn.CanReserve(c, 1, -1, null, forced))
{
return null;
}
Job job = JobMaker.MakeJob(JobDefOf.Sow, c);
job.plantDefToSow = wantedPlantDefLocal;
return job;
}
}
}

View File

@@ -0,0 +1,514 @@
# 动物工作模式解决方案文档(最终实现版)
## 1. 项目概述
本项目旨在实现一个功能,使动物(特别是昆虫)能够像机械体一样持续工作,而不需要通过机械师控制 WorkMode。通过为动物启用工作系统并创建自定义行为树动物将始终处于工作状态持续执行允许的工作类型。
## 2. 原版代码分析
### 2.1 昆虫 ThinkTreeDef 结构
原版昆虫的 ThinkTree (`Insect.xml`) 包含以下主要逻辑:
1. **紧急情况处理**:倒地、燃烧、精神状态等
2. **基本需求满足**:睡觉、吃饭等
3. **驯服动物行为**:跟随主人、救援等
4. **闲置行为**:漫游、待命等
### 2.2 机械体 ThinkTreeDef 结构
原版机械体的 ThinkTree (`Mechanoid.xml`) 包含 WorkMode 系统,这是动物不具备的。
## 3. 为什么机械体有 workSettings 而动物没有
### 3.1 核心原因分析
机械体和动物虽然都是 Pawn但它们的 workSettings 初始化逻辑不同:
1. **`Pawn_WorkSettings` 的存在性**
- `Pawn` 类内部持有一个 `Pawn_WorkSettings` 类型的引用 `priorities`,默认为 `null`
- `public bool EverWork => priorities != null;` 属性表明只有当 `priorities` 不为 `null`Pawn 才有工作能力
2. **初始化条件**
- `EnableAndInitializeIfNotAlreadyInitialized()` 方法检查 `priorities` 是否为 `null`,如果为 `null` 则调用 `EnableAndInitialize()`
- `EnableAndInitialize()` 方法是真正执行初始化的地方,创建 `priorities` 对象并根据条件设置初始优先级
3. **Mechanoid 有 WorkSettings 的证据**
- `EnableAndInitialize()` 方法明确包含对机械体的特殊处理:
```csharp
if (ModsConfig.BiotechActive && pawn.RaceProps.IsMechanoid && !pawn.RaceProps.mechWorkTypePriorities.NullOrEmpty())
{
for (int i = 0; i < pawn.RaceProps.mechWorkTypePriorities.Count; i++)
{
MechWorkTypePriority mechWorkTypePriority = pawn.RaceProps.mechWorkTypePriorities[i];
SetPriority(mechWorkTypePriority.def, mechWorkTypePriority.priority);
}
}
```
- 这表明机械体的设计意图就是拥有并使用 `Pawn_WorkSettings`
4. **Animal 没有 WorkSettings 的证据**
- **直接初始化逻辑**`EnableAndInitialize()` 方法在为 Pawn 分配默认"活跃"工作类型时,会过滤掉 `pawn.WorkTypeIsDisabled(w)` 的工作类型。对于大多数动物来说,它们的 `RaceProps` 会禁用几乎所有需要手动分配的 `WorkTypeDef`
- **工作类型的定义**:绝大多数工作类型都包含限制,使得动物 Pawn 默认无法执行这些工作
- **特定工作提供者的处理**:针对动物的操作是通过独立的 `WorkGiver` 系统直接处理的,不依赖于通用的 `workSettings` 优先级系统
### 3.2 结论
机械体被设计为拥有 `Pawn_WorkSettings`,因为它们的工作优先级可以通过其 `RaceProps.mechWorkTypePriorities` 进行预设,并且游戏逻辑会在需要时初始化它。
动物通常没有有效的 `Pawn_WorkSettings`,因为:
1. 它们被设计为不能执行绝大多数需要手动分配的工作
2. 它们与玩家互动的特定工作是通过独立的 `WorkGiver` 系统直接处理的
3. 因此,除非有特定的游戏逻辑或 Mod 显式地为动物调用 `EnableAndInitialize()`,否则它们的 `priorities` 对象很可能保持 `null` 状态
## 4. 为什么 `RaceProps.mechWorkTypePriorities` 只对机械体有效
### 4.1 核心解释
`mechWorkTypePriorities` 是 `RaceProperties` 类中的一个字段专门用于定义机械体Mechanoid在生成时其工作类型Work Types的默认优先级。它的设计和使用都与机械体的独特机制紧密相关而这些机制在动物或其他生物上并不存在或不以相同方式运作。
### 4.2 详细证据和解释
1. **字段定义与命名**
- `mechWorkTypePriorities` 被明确定义为:
```csharp
public List<MechWorkTypePriority> mechWorkTypePriorities;
```
- 其名称 `mechWorkTypePriorities` 包含了 "Mech" 前缀,明确指示其用途是针对机械体的。
2. **`MechWorkTypePriority` 类型**
- 该字段存储的是 `MechWorkTypePriority` 对象的列表。虽然代码片段中没有直接给出 `MechWorkTypePriority` 的定义,但其名称和用途(存储工作类型及其优先级)强烈暗示它是专门为机械体设计的数据结构。
3. **使用场景 - 初始化工作设置**
- 关键证据出现在 `Pawn_WorkSettings.EnableAndInitialize()` 方法中:
```csharp
// ... (其他初始化代码) ...
if (ModsConfig.BiotechActive && pawn.RaceProps.IsMechanoid && !pawn.RaceProps.mechWorkTypePriorities.NullOrEmpty())
{
for (int i = 0; i < pawn.RaceProps.mechWorkTypePriorities.Count; i++)
{
MechWorkTypePriority mechWorkTypePriority = pawn.RaceProps.mechWorkTypePriorities[i];
SetPriority(mechWorkTypePriority.def, mechWorkTypePriority.priority);
}
}
// ... (其他初始化代码,如处理禁用的工作类型) ...
```
- 这段代码明确地检查了三个条件:
* `ModsConfig.BiotechActive`生物技术Biotech模组是否激活。这表明该功能是与 Biotech 模组引入的机械体相关的。
* `pawn.RaceProps.IsMechanoid`:当前 Pawn单位的种族属性是否为机械体。
* `!pawn.RaceProps.mechWorkTypePriorities.NullOrEmpty()`:该机械体的 `mechWorkTypePriorities` 列表是否存在且非空。
- **只有当这三个条件都满足时,才会遍历 `mechWorkTypePriorities` 列表,并根据其中定义的 `WorkTypeDef` 和优先级来设置该机械体 Pawn 的工作优先级。**
- 对于动物或其他非机械体生物,`pawn.RaceProps.IsMechanoid` 条件为假,因此即使在它们的 `ThingDef` 中定义了 `mechWorkTypePriorities`,这段代码也不会执行,这些优先级设置就会被忽略。
4. **机制差异 - 动物 vs 机械体**
- **动物**:动物的工作能力(如驯服、训练、特定任务)通常是通过 `trainability`(可训练性)、`trainableTags`(可训练标签)等属性来定义的。它们的工作行为(如觅食、繁殖、战斗)主要由其 `intelligence`(智力)、`thinkTreeMain`(主思考树)和 `dutyBoss`(职责)等属性驱动。它们的初始工作优先级(如果有的话)通常是固定的或基于简单规则随机分配,而不是通过一个专门的优先级列表来精确控制。
- **机械体**:机械体是 Biotech 模组引入的复杂单位,它们拥有类似殖民者的工作系统。它们可以被指派执行各种殖民者能做的工作(如建造、种植、烹饪等),其能力范围和初始工作优先级需要更精细的控制。`mechWorkTypePriorities` 就是为了满足这种需求而设计的,允许设计者为每种机械体明确指定哪些工作类型应该默认开启以及它们的优先级。
5. **辅助证据 - MechWorkUtility**
- 在 `MechWorkUtility` 类中,多个方法(如 `SpecialDisplayStats`, `AnyWorkMechCouldDo`)在处理与机械体工作相关逻辑时,都会首先检查 `parentDef.race.IsMechanoid`。这进一步证明了与机械体工作类型相关的逻辑是严格限定于机械体的。
### 4.3 结论
`RaceProperties.mechWorkTypePriorities` 虽然是 `RaceProperties` 类的一个通用属性,但它被设计并实现为**仅对机械体Mechanoid生效**。这是因为:
1. 它的命名和数据类型 (`MechWorkTypePriority`) 明确指向机械体。
2. 核心的使用代码 (`Pawn_WorkSettings.EnableAndInitialize`) 通过 `pawn.RaceProps.IsMechanoid` 条件严格限制了其应用范围。
3. 机械体和动物在工作系统上的根本差异决定了需要不同的机制来管理其初始工作能力,`mechWorkTypePriorities` 是为满足机械体特有需求而生的。
因此,即使在动物的 `ThingDef` 文件中添加了 `mechWorkTypePriorities` 部分,游戏代码也不会读取或应用这些设置,因为动物不是机械体,相关的初始化逻辑不会被执行。
## 5. 通过 Harmony 补丁拦截 `pawn.RaceProps.IsMechanoid` 的分析
### 5.1 用户提供的实现思路
用户提供了一种更精确和安全的实现思路:通过 Harmony 补丁拦截 `Pawn_WorkSettings.EnableAndInitialize` 方法,在特定条件下局部伪装成机械体来为动物启用工作系统。
### 5.2 实现方法
1. **引入 Harmony**:确保你的 Mod 项目中引用了 Harmony 库。
2. **定义补丁类**:创建一个静态类来存放你的 Harmony 补丁。
3. **编写前缀补丁**
```csharp
[HarmonyPatch(typeof(Pawn_WorkSettings), nameof(Pawn_WorkSettings.EnableAndInitialize))]
public static class Patch_Pawn_WorkSettings_EnableAndInitialize
{
public static bool Prefix(Pawn_WorkSettings __instance, Pawn ___pawn)
{
// 检查是否是我们想要启用工作系统的动物
if (___pawn.Faction != null && ___pawn.Faction.IsPlayer &&
!___pawn.RaceProps.IsMechanoid && // 真实身份不是机械体
ShouldEnableWorkSystem(___pawn)) // 但我们需要为它启用工作系统
{
// 局部伪装成机械体来执行初始化逻辑
// 执行机械体工作优先级初始化逻辑
if (ModsConfig.BiotechActive && !___pawn.RaceProps.mechWorkTypePriorities.NullOrEmpty())
{
for (int i = 0; i < ___pawn.RaceProps.mechWorkTypePriorities.Count; i++)
{
var priority = ___pawn.RaceProps.mechWorkTypePriorities[i];
__instance.SetPriority(priority.def, priority.priority);
}
}
// 同时也可以初始化 mechEnabledWorkTypes 中的工作类型
if (!___pawn.RaceProps.mechEnabledWorkTypes.NullOrEmpty())
{
foreach (var workType in ___pawn.RaceProps.mechEnabledWorkTypes)
{
if (!__instance.WorkIsActive(workType) && !___pawn.WorkTypeIsDisabled(workType))
{
__instance.SetPriority(workType, 3); // 默认优先级
}
}
}
// 阻止原方法继续执行
return false;
}
return true; // 其他情况正常执行原逻辑
}
private static bool ShouldEnableWorkSystem(Pawn pawn)
{
// 检查是否有特定的 Comp
if (pawn.TryGetComp<CompWorkForNonMechs>() != null)
return true;
// 检查是否有 mechWorkTypePriorities 或 mechEnabledWorkTypes 配置
if (!pawn.RaceProps.mechWorkTypePriorities.NullOrEmpty() ||
!pawn.RaceProps.mechEnabledWorkTypes.NullOrEmpty())
return true;
return false;
}
}
```
4. **应用补丁**
```csharp
[StaticConstructorOnStartup]
public static class AnimalWorkSystemPatcher
{
static AnimalWorkSystemPatcher()
{
var harmony = new Harmony("com.yourname.animalworksystem");
harmony.PatchAll();
}
}
```
### 5.3 潜在问题和风险
1. **副作用**
- 这种方法比直接补丁 `IsMechanoid` 属性更加精确,因为它只在 `Pawn_WorkSettings.EnableAndInitialize` 方法中进行伪装,不影响其他系统。
- 但仍需注意确保伪装逻辑只在特定条件下触发,避免意外影响其他 Pawn。
2. **补丁冲突**
- 其他 Mod 也可能使用 Harmony 来修改 `Pawn_WorkSettings` 或相关逻辑。
- 需要确保补丁逻辑具有良好的兼容性。
3. **性能影响**
- Harmony 补丁会增加方法调用的开销,但在这个场景下影响很小。
4. **维护困难**
- 如果游戏更新修改了相关逻辑,你的补丁可能失效或产生新的问题,需要随之更新。
### 5.4 结论和建议
用户提供的实现思路是一种更加精确和安全的方法,通过局部伪装成机械体来为动物启用工作系统。这种方法的优点包括:
1. **精确性**:只在特定条件下(动物是玩家阵营且需要启用工作系统)才进行伪装。
2. **安全性**:只在 `Pawn_WorkSettings.EnableAndInitialize` 方法中进行伪装,不影响其他系统。
3. **兼容性**:通过检查特定的 Comp 或配置来决定是否启用工作系统,具有良好的可扩展性。
## 6. 完整实现方案
### 6.1 核心思路
1. 为动物添加 `CompWorkForNonMechs` 以启用工作系统
2. 创建 `ThinkNode_AnimalWorker` 替代默认行为,强制工作
3. 使用 Harmony 补丁局部伪装成机械体来为动物启用工作系统
4. 使用自定义 ThinkTree 替代默认动物行为树
5. 定义允许的工作类型,并确保其与 WorkGiver 兼容
### 6.2 设计要点
1. **为动物启用 WorkSettings 系统**:通过 Harmony 补丁局部伪装成机械体来为动物启用工作系统
2. **使用自定义 ThinkNode 强制工作行为**:创建 ThinkNode跳过所有动物默认行为直接进入工作逻辑
3. **修改动物定义**:添加 ThinkTree 和 Comp
4. **为动物添加工作类型支持**:定义允许的工作类型
## 7. 实现细节
### 7.1 CompWorkForNonMechs.cs
```csharp
using System.Collections.Generic;
using RimWorld;
using Verse;
namespace YourModName
{
public class CompWorkForNonMechs : ThingComp
{
public class CompProperties_WorkForNonMechs : CompProperties
{
public List<WorkTypeDef> workTypes;
public CompProperties_WorkForNonMechs()
{
compClass = typeof(CompWorkForNonMechs);
}
}
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
var pawn = parent as Pawn;
if (pawn == null || pawn.Faction == null || !pawn.Faction.IsPlayer) return;
// 启用工作设置
if (pawn.workSettings == null)
{
pawn.workSettings = new Pawn_WorkSettings(pawn);
}
pawn.workSettings.EnableAndInitialize();
// 启用指定的工作类型
var props = (CompProperties_WorkForNonMechs)props;
if (props.workTypes != null)
{
foreach (var workType in props.workTypes)
{
if (!pawn.WorkTypeIsDisabled(workType))
{
pawn.workSettings.SetPriority(workType, 3); // 默认优先级
}
}
}
}
}
}
```
### 7.2 ThinkNode_AnimalWorker.cs
```csharp
using RimWorld;
using Verse;
namespace YourModName
{
public class ThinkNode_AnimalWorker : ThinkNode_Priority
{
public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobParams)
{
if (pawn.workSettings == null || !pawn.Faction.IsPlayer)
{
return ThinkResult.NoJob;
}
// 优先执行紧急工作
var job = WorkGiverUtility.GetPriorityWork(pawn, emergency: true);
if (job != null) return new ThinkResult(job, this);
// 然后执行普通工作
job = WorkGiverUtility.GetPriorityWork(pawn, emergency: false);
if (job != null) return new ThinkResult(job, this);
return ThinkResult.NoJob;
}
}
}
```
### 7.3 AnimalWorkSystemPatcher.cs
```csharp
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using RimWorld;
using Verse;
namespace YourModName
{
[StaticConstructorOnStartup]
public static class AnimalWorkSystemPatcher
{
static AnimalWorkSystemPatcher()
{
var harmony = new Harmony("com.yourname.animalworksystem");
harmony.PatchAll();
}
}
[HarmonyPatch(typeof(Pawn_WorkSettings), nameof(Pawn_WorkSettings.EnableAndInitialize))]
public static class Patch_Pawn_WorkSettings_EnableAndInitialize
{
// 缓存原始的 IsMechanoid 属性 Getter
private static PropertyInfo isMechanoidProperty =
typeof(RaceProperties).GetProperty("IsMechanoid", BindingFlags.Public | BindingFlags.Instance);
public static bool Prefix(Pawn_WorkSettings __instance, Pawn ___pawn)
{
// 检查是否是我们想要启用工作系统的动物
if (___pawn.Faction != null && ___pawn.Faction.IsPlayer &&
!___pawn.RaceProps.IsMechanoid && // 真实身份不是机械体
ShouldEnableWorkSystem(___pawn)) // 但我们需要为它启用工作系统
{
// 局部伪装成机械体来执行初始化逻辑
// 执行机械体工作优先级初始化逻辑(来自 Pawn_WorkSettings.EnableAndInitialize
if (ModsConfig.BiotechActive && !___pawn.RaceProps.mechWorkTypePriorities.NullOrEmpty())
{
for (int i = 0; i < ___pawn.RaceProps.mechWorkTypePriorities.Count; i++)
{
var priority = ___pawn.RaceProps.mechWorkTypePriorities[i];
__instance.SetPriority(priority.def, priority.priority);
}
}
// 同时也可以初始化 mechEnabledWorkTypes 中的工作类型(如果你需要)
if (!___pawn.RaceProps.mechEnabledWorkTypes.NullOrEmpty())
{
foreach (var workType in ___pawn.RaceProps.mechEnabledWorkTypes)
{
if (!__instance.WorkIsActive(workType) && !___pawn.WorkTypeIsDisabled(workType))
{
__instance.SetPriority(workType, 3); // 默认优先级
}
}
}
// 阻止原方法继续执行(因为我们已经手动处理了初始化)
return false;
}
return true; // 其他情况正常执行原逻辑
}
private static bool ShouldEnableWorkSystem(Pawn pawn)
{
// 你可以通过多种方式判断:
// 1. 检查是否有特定的 Comp
if (pawn.TryGetComp<CompWorkForNonMechs>() != null)
return true;
// 2. 检查是否有 mechWorkTypePriorities 或 mechEnabledWorkTypes 配置
if (!pawn.RaceProps.mechWorkTypePriorities.NullOrEmpty() ||
!pawn.RaceProps.mechEnabledWorkTypes.NullOrEmpty())
return true;
// 3. 检查特定的标签或 defName
// return pawn.def.defName.Contains("Worker");
return false;
}
}
}
```
### 7.4 修改动物定义
```xml
<ThingDef ParentName="BaseInsect">
<defName>Megascarab_Worker</defName>
<label>worker megascarab</label>
<description>A genetically modified megascarab, capable of performing simple mechanical tasks.</description>
<statBases>
<MoveSpeed>3.0</MoveSpeed>
<MarketValue>300</MarketValue>
</statBases>
<race>
<thinkTreeMain>InsectWorker</thinkTreeMain>
<thinkTreeConstant>AnimalConstant</thinkTreeConstant>
<mechWorkTypePriorities>
<li>
<def>Hauling</def>
<priority>3</priority>
</li>
<li>
<def>Cleaning</def>
<priority>2</priority>
</li>
</mechWorkTypePriorities>
</race>
<comps>
<li Class="YourModName.CompWorkForNonMechs+CompProperties_WorkForNonMechs">
<workTypes>
<li>Hauling</li>
<li>Cleaning</li>
<li>BasicWorker</li>
</workTypes>
</li>
</comps>
</ThingDef>
```
### 7.5 自定义 ThinkTreeDef
```xml
<ThinkTreeDef>
<defName>InsectWorker</defName>
<thinkRoot Class="ThinkNode_Priority">
<subNodes>
<!-- 紧急情况处理 -->
<li Class="ThinkNode_Subtree"><treeDef>Downed</treeDef></li>
<li Class="ThinkNode_Subtree"><treeDef>BurningResponse</treeDef></li>
<li Class="ThinkNode_Subtree"><treeDef>MentalStateCritical</treeDef></li>
<!-- 强制工作模式 -->
<li Class="YourModName.ThinkNode_AnimalWorker" />
<!-- 闲置行为 -->
<li Class="JobGiver_IdleError"/>
</subNodes>
</thinkRoot>
</ThinkTreeDef>
```
## 8. 原始方案的反驳分析
### 8.1 项目概述:不成立
**反驳证据:**
- RimWorld 原版设计中,动物的行为树是完全独立于机械体的,没有 WorkMode 系统。
- 动物不能直接复用机械体的工作逻辑。
### 8.2 ThinkNode_AnimalAlwaysWork 类:不成立
**反驳证据:**
- `ThinkNode_Priority` 是一个标准的 ThinkNode它会按顺序尝试子节点直到某一个返回 `ThinkResult`。
- 该类不会实现"始终工作"的功能,因为它没有改变行为树的执行逻辑。
### 8.3 InsectWorker ThinkTreeDef不成立
**反驳证据:**
- RimWorld 中,动物的 ThinkTree 并不会调用 `JobGiver_Work`,因为该类是为机械体设计的。
- 即使强制插入 `JobGiver_Work`,它也会因为动物的 `WorkSettings` 未初始化而失败。
### 8.4 Harmony 补丁拦截 IsMechanoid不成立
**反驳证据:**
- `RaceProperties.IsMechanoid` 是一个只读属性,由 `<race><isMechanoid>true</isMechanoid></race>` 控制。
- Patch 该属性会导致所有依赖该属性的逻辑被干扰,风险极高。
## 9. 使用说明
1. **编译代码**:将所有 C# 文件编译到您的 Mod 中
2. **添加 XML 文件**:将新的 ThinkTreeDef 和动物定义添加到您的 Mod 中
3. **配置工作类型**:在动物定义中配置 `mechWorkTypePriorities` 和 `CompWorkForNonMechs` 组件
4. **测试**:在游戏中生成修改后的动物,观察其工作行为
## 10. 注意事项
1. **兼容性**:此方案绕过了 WorkMode 系统,动物将始终处于工作状态
2. **性能**:频繁的工作查找可能会有轻微性能影响
3. **调试**:如果遇到问题,请检查日志文件中的错误信息
4. **避免 patch IsMechanoid**:不干扰原版逻辑
5. **副作用风险**:直接 patch `IsMechanoid` 会导致严重的副作用,强烈不推荐使用
6. **精确补丁**:用户提供的实现通过局部伪装成机械体来为动物启用工作系统,是一种更加精确和安全的方法

View File

@@ -0,0 +1,227 @@
# 技术文档RimWorld 中 `specialTrainables` 的工作机制解析 (V6 - 终极证据版)
## 1. 目标
本文档旨在深入剖析 RimWorld 中 `<specialTrainables>` 标签的底层工作机制,并最终得出一套可复用的“配方”,用于让动物学会在无需训练、无衰减的情况下,执行任何原版已存在的工作。本文档包含所有必要的、完整的原版代码引用作为证据,以确保其自包含性、正确性和健壮性,可供任何人在无上下文的情况下阅读并执行。
## 2. 核心机制深度解析
`specialTrainables` 的功能是一套精巧的、深度集成在 AI 思维逻辑中的 **条件行为分支**
### 2.1. 玩家授权机制
**关键发现**: 游戏通过一个统一的 UI 列 (`PawnColumnWorker_Trainable_Special`) 来管理所有被标记为 `specialTrainable` 的技能。当玩家点击该列的复选框时,会调用 `pawn.training.SetWantedRecursive()` 来设置 `Pawn_TrainingTracker``wantedTrainables` 字段的值。
* **证据**: 以下是 `PawnColumnWorker_Trainable_Special.txt` 的完整内容,它清晰地展示了 `SetWantedRecursive` 是如何被调用的。
```csharp
using System.Text;
using UnityEngine;
using Verse;
namespace RimWorld
{
public class PawnColumnWorker_Trainable_Special : PawnColumnWorker
{
public override void DoHeader(Rect rect, PawnTable table)
{
base.DoHeader(rect, table);
MouseoverSounds.DoRegion(rect);
}
public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
{
if (pawn.training != null && !pawn.RaceProps.specialTrainables.NullOrEmpty())
{
int num = (int)((rect.width - 24f) / 2f);
int num2 = Mathf.Max(3, 0);
Rect rect2 = new Rect(rect.x + (float)num, rect.y + (float)num2, 24f, 24f);
DoSpecialTrainableCheckbox(rect2, pawn, doTooltip: true);
}
}
private void DoSpecialTrainableCheckbox(Rect rect, Pawn pawn, bool doTooltip)
{
GetStatus(pawn, out var learned, out var checkOn, out var canTrain, out var _);
bool flag = checkOn;
Texture2D texChecked = (learned ? TrainingCardUtility.LearnedTrainingTex : null);
Texture2D texUnchecked = (learned ? TrainingCardUtility.LearnedNotTrainingTex : null);
Widgets.Checkbox(rect.position, ref checkOn, rect.width, !canTrain, paintable: true, texChecked, texUnchecked);
if (checkOn != flag)
{
PlayerKnowledgeDatabase.KnowledgeDemonstrated(ConceptDefOf.AnimalTraining, KnowledgeAmount.Total);
foreach (TrainableDef specialTrainable in pawn.RaceProps.specialTrainables)
{
pawn.training.SetWantedRecursive(specialTrainable, checkOn);
}
}
if (doTooltip)
{
DoSpecialTrainableTooltip(rect, pawn);
}
}
private void DoSpecialTrainableTooltip(Rect rect, Pawn pawn)
{
if (!Mouse.IsOver(rect))
{
return;
}
TooltipHandler.TipRegion(rect, delegate
{
StringBuilder stringBuilder = new StringBuilder();
foreach (TrainableDef specialTrainable in pawn.RaceProps.specialTrainables)
{
bool visible;
AcceptanceReport acceptanceReport = pawn.training.CanAssignToTrain(specialTrainable, out visible);
stringBuilder.AppendLineIfNotEmpty();
stringBuilder.AppendLine(specialTrainable.LabelCap + "\n\n" + specialTrainable.description);
if (!acceptanceReport.Accepted)
{
stringBuilder.AppendLine().AppendLine(acceptanceReport.Reason);
}
else if (!specialTrainable.prerequisites.NullOrEmpty())
{
stringBuilder.AppendLine();
foreach (TrainableDef prerequisite in specialTrainable.prerequisites)
{
if (!pawn.training.HasLearned(prerequisite))
{
stringBuilder.AppendLine("TrainingNeedsPrerequisite".Translate(prerequisite.LabelCap));
}
}
}
}
return stringBuilder.ToString();
}, (int)(rect.y * 511f + rect.x));
}
public override int GetMinWidth(PawnTable table)
{
return Mathf.Max(base.GetMinWidth(table), 24);
}
public override int GetMaxWidth(PawnTable table)
{
return Mathf.Min(base.GetMaxWidth(table), GetMinWidth(table));
}
public override int GetMinCellHeight(Pawn pawn)
{
return Mathf.Max(base.GetMinCellHeight(pawn), 24);
}
public override int Compare(Pawn a, Pawn b)
{
return GetValueToCompare(a).CompareTo(GetValueToCompare(b));
}
private int GetValueToCompare(Pawn pawn)
{
if (pawn.training == null || pawn.RaceProps.specialTrainables.NullOrEmpty())
{
return int.MinValue;
}
GetStatus(pawn, out var learned, out var checkOn, out var canTrain, out var visible);
if (learned)
{
return 4;
}
if (!visible)
{
return 0;
}
if (!canTrain)
{
return 1;
}
if (!checkOn)
{
return 2;
}
return 3;
}
private static void GetStatus(Pawn pawn, out bool learned, out bool checkOn, out bool canTrain, out bool visible)
{
learned = true;
checkOn = true;
canTrain = true;
visible = false;
foreach (TrainableDef specialTrainable in pawn.RaceProps.specialTrainables)
{
if (!pawn.training.HasLearned(specialTrainable))
{
learned = false;
}
if (!pawn.training.GetWanted(specialTrainable))
{
checkOn = false;
}
if (!pawn.training.CanAssignToTrain(specialTrainable, out var visible2))
{
canTrain = false;
}
if (visible2)
{
visible = true;
}
}
}
}
}
```
### 2.2. AI 决策机制
* **关键发现**: 我们可以为每个技能创建一个继承自 `ThinkNode_Conditional` 的决策节点,在 `Satisfied` 方法中通过检查 `pawn.training.GetWanted(def)` 来判断玩家是否授权。
## 3. 最终实施蓝图
### 阶段一:核心机制实现 (通用)
* **1.1: 实现瞬间训练 (`CompInstantTrain.cs`)**: 创建一个 `ThingComp`,在 `PostSpawnSetup` 中调用 `pawn.training.Train(def, null, true)`。
* **1.2: 引入 Harmony**: 在项目中添加 `0Harmony.dll` 引用并创建 `MainHarmony.cs` 初始化补丁。
* **1.3: 阻止训练衰减 (`Patch_TrainingTracker_TickRare.cs`)**: 创建一个对 `Pawn_TrainingTracker.TrainingTrackerTickRare` 的前缀补丁,对特殊动物返回 `false`。
### 阶段二:实现“种植” (`Growing`) 功能
* **2.1: (XML) 创建 `TrainableDef`**: 创建 `Defs/TrainableDefs/ARA_Sowing.xml`,定义 `ARA_Sowing`,并设 `<specialTrainable>true</specialTrainable>`。
* **2.2: (C#) 创建 AI 决策节点**: 创建 `Source/ArachnaeSwarm/ThinkNode_ConditionalAnimalShouldSow.cs`,并使用静态缓存 Def。
```csharp
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
[DefOf]
public static class ARA_TrainableDefOf
{
public static TrainableDef ARA_Sowing;
public static TrainableDef ARA_PlantCutting;
static ARA_TrainableDefOf() { DefOfHelper.EnsureInitializedInCtor(typeof(ARA_TrainableDefOf)); }
}
public class ThinkNode_ConditionalAnimalShouldSow : ThinkNode_Conditional
{
protected override bool Satisfied(Pawn pawn)
{
if (pawn.training == null) return false;
return pawn.training.HasLearned(ARA_TrainableDefOf.ARA_Sowing) &&
pawn.training.GetWanted(ARA_TrainableDefOf.ARA_Sowing);
}
}
}
```
* **2.3: (XML) 关联组件**:
* **PawnKindDef**: 在 `comps` 列表中添加 `CompProperties_InstantTrain` 并配置 `trainables`。
* **ThingDef**: 在 `specialTrainables` 列表中添加 `<li>ARA_Sowing</li>`。
* **ThinkTreeDef**: 添加 `<li Class="ArachnaeSwarm.ThinkNode_ConditionalAnimalShouldSow">` 节点,其子节点为 `<li Class="RimWorld.JobGiver_Work"><workType>Growing</workType></li>`。
### 阶段三:实现“植物割除” (`PlantCutting`) 功能
此阶段完全重复阶段二的模式,仅替换相应的名称 (`ARA_PlantCutting`, `ThinkNode_ConditionalAnimalShouldPlantCut`, `workType: PlantCutting`)。
### 阶段四:最终审查与打包
* **4.1**: 审查所有代码和 XML。
* **4.2**: 编译并进行游戏内测试。