This commit is contained in:
2025-09-02 18:39:28 +08:00
parent b21de9de91
commit f74c2b844b
10 changed files with 308 additions and 98 deletions

Binary file not shown.

View File

@@ -22,6 +22,15 @@
</trainables>
</li>
<li Class="ArachnaeSwarm.CompProperties_AnimalWorkSettings">
<!-- 定义技能等级 -->
<skillLevels>
<li>
<skill>Plants</skill>
<level>10</level>
<disableDecay>true</disableDecay>
</li>
</skillLevels>
<!-- 定义训练项与工作类型的映射 -->
<workTypeMap>
<li>
<trainable>ARA_Sowing</trainable>

View File

@@ -107,7 +107,7 @@
<subNodes>
<!-- Wild insects with no lord will fight nearby enemies -->
<li Class="JobGiver_AIFightEnemies">
<targetAcquireRadius>30</targetAcquireRadius> <!-- Same as DefendAndExpandHive -->
<targetAcquireRadius>30</targetAcquireRadius> <!-- Same as DefendAndExpandHive -->
<targetKeepRadius>35</targetKeepRadius>
</li>
@@ -324,6 +324,45 @@
</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_ConditionalAnimalShouldSow">
<subNodes>
<!-- 优先执行收割 -->
<li Class="ArachnaeSwarm.JobGiver_PlantHarvest" />
</subNodes>
</li>
</subNodes>
</li>
<!-- OUR CUSTOM LOGIC INJECTION FOR PLANT SOWING -->
<li Class="ThinkNode_ConditionalTrainableCompleted">
<trainable>ARA_Sowing</trainable>
<subNodes>
<li Class="ArachnaeSwarm.ThinkNode_ConditionalAnimalShouldSow">
<subNodes>
<!-- 然后执行播种 -->
<li Class="ArachnaeSwarm.JobGiver_PlantSow" />
</subNodes>
</li>
</subNodes>
</li>
<!-- OUR CUSTOM LOGIC INJECTION FOR PLANT CUTTING -->
<li Class="ThinkNode_ConditionalTrainableCompleted">
<trainable>ARA_PlantCutting</trainable>
<subNodes>
<li Class="ArachnaeSwarm.ThinkNode_ConditionalAnimalShouldPlantCut">
<subNodes>
<!-- 最后执行割除 -->
<li Class="ArachnaeSwarm.JobGiver_PlantCutting" />
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</li>
@@ -335,31 +374,6 @@
<insertTag>Insect_PreWander</insertTag>
</li>
<!-- OUR CUSTOM LOGIC INJECTION -->
<li Class="ArachnaeSwarm.ThinkNode_ConditionalAnimalShouldSow">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>TrainedAnimalBehavior</tagToGive>
<subNodes>
<li Class="RimWorld.JobGiver_Work" />
</subNodes>
</li>
</subNodes>
</li>
<!-- END OF CUSTOM LOGIC -->
<!-- OUR CUSTOM LOGIC INJECTION FOR PLANT CUTTING -->
<li Class="ArachnaeSwarm.ThinkNode_ConditionalAnimalShouldPlantCut">
<subNodes>
<li Class="ThinkNode_Tagger">
<tagToGive>TrainedAnimalBehavior</tagToGive>
<subNodes>
<li Class="RimWorld.JobGiver_Work" />
</subNodes>
</li>
</subNodes>
</li>
<!-- Tame insect: wander near colony if possible -->
<li Class="ThinkNode_ConditionalOfPlayerFaction">
<subNodes>

View File

@@ -96,8 +96,10 @@
<Compile Include="CompNoTrainingDecay.cs" />
<Compile Include="ThinkNode_ConditionalAnimalShouldSow.cs" />
<Compile Include="ThinkNode_ConditionalAnimalShouldPlantCut.cs" />
<Compile Include="CompProperties_AnimalWorkSettings.cs" />
<Compile Include="CompAnimalWorkSettings.cs" />
<Compile Include="JobGiver_PlantCutting.cs" />
<Compile Include="JobGiver_PlantHarvest.cs" />
<Compile Include="JobGiver_PlantSow.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="WULA_AutoMechCarrier\CompAutoMechCarrier.cs" />

View File

@@ -4,6 +4,35 @@ using RimWorld;
namespace ArachnaeSwarm
{
public class CompProperties_AnimalWorkSettings : CompProperties
{
// 使用列表存储键值对因为RimWorld的XML解析器对字典有特殊要求
public List<WorkTypeMapEntry> workTypeMap = new List<WorkTypeMapEntry>();
// 定义动物的技能等级
public List<SkillLevelEntry> skillLevels = new List<SkillLevelEntry>();
public CompProperties_AnimalWorkSettings()
{
this.compClass = typeof(CompAnimalWorkSettings);
}
}
// 定义键值对的结构
public class WorkTypeMapEntry
{
public TrainableDef trainable;
public WorkTypeDef workType;
}
// 定义技能和等级的结构
public class SkillLevelEntry
{
public SkillDef skill;
public int level = 0; // 默认等级为0
// 可选:是否阻止技能衰减
public bool disableDecay = true;
}
public class CompAnimalWorkSettings : ThingComp
{
public CompProperties_AnimalWorkSettings Props => (CompProperties_AnimalWorkSettings)this.props;
@@ -15,35 +44,54 @@ namespace ArachnaeSwarm
Pawn pawn = this.parent as Pawn;
if (pawn == null) return;
// 关键:如果 pawn 没有 workSettings,则为其创建一个
// 1. 确保 workSettings 存在
if (pawn.workSettings == null)
{
// Log.Message($"Initializing Pawn_WorkSettings for animal: {pawn.LabelShort}");
pawn.workSettings = new Pawn_WorkSettings(pawn);
pawn.workSettings.EnableAndInitializeIfNotAlreadyInitialized();
// 注意Pawn_WorkSettings 的构造函数和 EnableAndInitializeIfNotAlreadyInitialized()
// 通常会处理所有可用的 WorkTypeDef并将它们的优先级初始化为 0。
// 这正是我们想要的初始状态。具体的优先级将由其他逻辑(如你的 CompInstantTrain 或 ThinkNode来设置。
}
// 设置工作优先级
if (pawn.workSettings != null)
// 2. 设置技能等级 (如果定义了)
if (pawn.skills != null && Props.skillLevels != null)
{
foreach (var entry in Props.skillLevels)
{
if (entry.skill != null)
{
var skillRecord = pawn.skills.GetSkill(entry.skill);
if (skillRecord != null)
{
// 设置技能等级
skillRecord.Level = entry.level;
// 可选:阻止技能衰减
// 在新版本中,将激情设置为 None 可以阻止自然增长和衰减
if (entry.disableDecay)
{
skillRecord.passion = Passion.None;
// 注意:仅设置 passion=None 可能不足以完全阻止衰减
// 如果仍有问题,可能需要在 ThinkNode 或其他地方定期重置技能等级
}
}
}
}
}
// 3. 设置工作优先级 (如果定义了映射关系)
// 这里我们不检查 pawn.training.HasLearned因为这个组件只负责提供"能力"
// 具体是否执行工作由 ThinkNode_Conditional 控制
if (pawn.workSettings != null && Props.workTypeMap != null)
{
foreach (var entry in Props.workTypeMap)
{
TrainableDef trainable = entry.trainable;
WorkTypeDef workType = entry.workType;
// 检查动物是否学会了对应的 Trainable
if (pawn.training != null && pawn.training.HasLearned(trainable))
if (entry.trainable != null && entry.workType != null)
{
// 设置一个默认的非零优先级,例如 3 (Medium)
// 真正的"开关"由 ThinkNode_Conditional 的 GetWanted 控制
pawn.workSettings.SetPriority(workType, 3);
// 为相关工作类型设置默认优先级
// 实际是否执行由 ThinkNode_Conditional 的 GetWanted 控制
pawn.workSettings.SetPriority(entry.workType, 3);
}
}
}
}
}
}
}

View File

@@ -4,6 +4,36 @@ using RimWorld;
namespace ArachnaeSwarm
{
public class CompInstantTrain : ThingComp
{
public CompProperties_InstantTrain Props => (CompProperties_InstantTrain)this.props;
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
// 只在初次生成时执行,加载存档时不需要重新训练
if (respawningAfterLoad) return;
Pawn pawn = this.parent as Pawn;
if (pawn?.training == null) return;
// 瞬间训练所有在 Props 中指定的技能
foreach (var trainable in Props.trainables)
{
if (trainable != null && !pawn.training.HasLearned(trainable))
{
// 注意:直接调用 Train 方法,它会内部处理 SetWanted 的逻辑
// 原版的 SetWanted 方法可能已被移除或变为内部方法
pawn.training.Train(trainable, null, true); // true表示瞬间完成
}
}
// CompInstantTrain 的职责到此结束
// 工作优先级设置由 CompAnimalWorkSettings 负责
}
}
public class CompProperties_InstantTrain : CompProperties
{
public List<TrainableDef> trainables = new List<TrainableDef>();
@@ -13,32 +43,4 @@ namespace ArachnaeSwarm
this.compClass = typeof(CompInstantTrain);
}
}
public class CompInstantTrain : ThingComp
{
public CompProperties_InstantTrain Props => (CompProperties_InstantTrain)this.props;
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
if (!respawningAfterLoad) // 只在初次生成时执行
{
Pawn pawn = this.parent as Pawn;
if (pawn == null || pawn.training == null)
{
return;
}
// 瞬间训练技能
foreach (TrainableDef trainableDef in Props.trainables)
{
if (!pawn.training.HasLearned(trainableDef))
{
pawn.training.Train(trainableDef, null, true);
}
}
}
}
}
}
}

View File

@@ -1,25 +0,0 @@
using System.Collections.Generic;
using Verse;
using RimWorld;
namespace ArachnaeSwarm
{
public class CompProperties_AnimalWorkSettings : CompProperties
{
// 使用列表存储键值对因为RimWorld的XML解析器对字典有特殊要求
public List<WorkTypeMapEntry> workTypeMap = new List<WorkTypeMapEntry>();
// 可以在这里添加一些配置选项,比如默认优先级等,但现在简单起见,可以留空或只做基本初始化
public CompProperties_AnimalWorkSettings()
{
this.compClass = typeof(CompAnimalWorkSettings);
}
}
// 定义键值对的结构
public class WorkTypeMapEntry
{
public TrainableDef trainable;
public WorkTypeDef workType;
}
}

View File

@@ -0,0 +1,56 @@
using System.Collections.Generic;
using Verse;
using Verse.AI;
using RimWorld;
namespace ArachnaeSwarm
{
public class JobGiver_PlantCutting : ThinkNode_JobGiver
{
private WorkGiverDef cutWorkGiverDef = null;
private bool triedToLoad = false;
protected override Job TryGiveJob(Pawn pawn)
{
if (!triedToLoad || cutWorkGiverDef == null)
{
triedToLoad = true;
cutWorkGiverDef = DefDatabase<WorkGiverDef>.GetNamed("PlantsCut", false);
if (cutWorkGiverDef == null)
{
Log.ErrorOnce("[ArachnaeSwarm] Could not find WorkGiverDef named 'PlantsCut'. Plant Cutting will not work.", 847595);
return null;
}
}
if (cutWorkGiverDef?.Worker is WorkGiver_Scanner workGiver)
{
// ... (你的逻辑,调用 workGiver 的方法) ...
IEnumerable<Thing> potentialPlants = workGiver.PotentialWorkThingsGlobal(pawn);
// ... (后续逻辑,确保都用 workGiver 实例) ...
if (!workGiver.ShouldSkip(pawn) && workGiver.MissingRequiredCapacity(pawn) == null)
{
return workGiver.NonScanJob(pawn);
}
// ...
}
else
{
Log.Warning($"[ArachnaeSwarm] WorkGiverDef 'PlantsCut' Worker is not a WorkGiver_Scanner for pawn {pawn?.LabelShort ?? "NULL"}.");
}
return null;
}
// ... (Validator 方法也需要修改) ...
private static bool Validator(Pawn pawn, WorkGiver_Scanner workGiver, Thing t)
{
if (t.IsForbidden(pawn))
{
return false;
}
// if (workGiver.ShouldSkip(pawn)) { return false; } // 这个检查可以保留或去掉
return workGiver.JobOnThing(pawn, t) != null; // 使用传入的实例
}
}
}

View File

@@ -0,0 +1,63 @@
using System.Collections.Generic;
using Verse;
using Verse.AI;
using RimWorld;
namespace ArachnaeSwarm
{
public class JobGiver_PlantHarvest : ThinkNode_JobGiver
{
// 声明变量,但不立即初始化
private WorkGiverDef harvestWorkGiverDef = null;
// 用一个标志位确保只尝试加载一次
private bool triedToLoad = false;
protected override Job TryGiveJob(Pawn pawn)
{
// 如果还没尝试加载,或者之前加载失败了
if (!triedToLoad || harvestWorkGiverDef == null)
{
triedToLoad = true; // 标记为已尝试
// 在需要时才查找 Def
harvestWorkGiverDef = DefDatabase<WorkGiverDef>.GetNamed("GrowerHarvest", false);
if (harvestWorkGiverDef == null)
{
// 这个 Log 可以保留,方便调试
Log.ErrorOnce("[ArachnaeSwarm] Could not find WorkGiverDef named 'GrowerHarvest'. Harvesting will not work.", 847593);
return null; // 找不到就直接返回
}
}
// 现在可以安全地使用 harvestWorkGiverDef 了
if (harvestWorkGiverDef?.Worker is WorkGiver_Scanner workGiver)
{
// ... (你原来的逻辑) ...
IEnumerable<Thing> potentialPlants = workGiver.PotentialWorkThingsGlobal(pawn);
// ... (后续逻辑) ...
// 确保调用的是 workGiver (已解析的实例)
if (!workGiver.ShouldSkip(pawn) && workGiver.MissingRequiredCapacity(pawn) == null)
{
return workGiver.NonScanJob(pawn);
}
// ... 其他逻辑 ...
}
else
{
Log.Warning($"[ArachnaeSwarm] WorkGiverDef 'GrowerHarvest' Worker is not a WorkGiver_Scanner for pawn {pawn?.LabelShort ?? "NULL"}.");
}
return null;
}
// ... (Validator 方法也需要确保使用传入的 workGiver 实例) ...
private static bool HarvestValidator(Pawn pawn, WorkGiver_Scanner workGiver, Thing t)
{
if (t.IsForbidden(pawn))
{
return false;
}
// 使用传入的已解析实例
return workGiver.JobOnThing(pawn, t) != null;
}
}
}

View File

@@ -0,0 +1,41 @@
using Verse;
using Verse.AI;
using RimWorld;
namespace ArachnaeSwarm
{
public class JobGiver_PlantSow : ThinkNode_JobGiver
{
private WorkGiverDef sowWorkGiverDef = null;
private bool triedToLoad = false;
protected override Job TryGiveJob(Pawn pawn)
{
if (!triedToLoad || sowWorkGiverDef == null)
{
triedToLoad = true;
sowWorkGiverDef = DefDatabase<WorkGiverDef>.GetNamed("GrowerSow", false);
if (sowWorkGiverDef == null)
{
Log.ErrorOnce("[ArachnaeSwarm] Could not find WorkGiverDef named 'GrowerSow'. Sowing will not work.", 847594);
return null;
}
}
if (sowWorkGiverDef?.Worker is WorkGiver_Scanner sowerScanner) // 直接用 Scanner 接口
{
// ... (你的逻辑,调用 sowerScanner 的方法) ...
if (!sowerScanner.ShouldSkip(pawn) && sowerScanner.MissingRequiredCapacity(pawn) == null)
{
return sowerScanner.NonScanJob(pawn); // 使用 Scanner 接口
}
}
else
{
Log.Warning($"[ArachnaeSwarm] WorkGiverDef 'GrowerSow' Worker is not a WorkGiver_Scanner for pawn {pawn?.LabelShort ?? "NULL"}.");
}
return null;
}
}
}