重构自主机械体系统:增强UI、AI和兼容性

1.  **核心架构重构**:
    *   将硬编码的 AutonomousWorkMode 枚举替换为基于 XML 定义的 DroneWorkModeDef,以提高扩展性。
    *   定义了基础工作模式:工作、充电、休眠、自动战斗。
    *   重构了 CompAutonomousMech 以支持新的 Def 系统。

2.  **UI 增强**:
    *   添加了 DroneGizmo:为选中的机械体提供控制面板,显示能量水平并允许切换模式。
    *   添加了 PawnColumnWorker_DroneWorkMode 和 PawnColumnWorker_DroneEnergy:在机械体列表中显示工作模式图标和能量条。
    *   通过 Harmony 补丁 Patch_MainTabWindow_Mechs_Pawns 将自主机械体集成到原版机械体主标签页中。
    *   扩展了 PawnTableDefOf.Mechs 以包含新的自定义列。

3.  **AI 与行为改进**:
    *   实现了 JobDriver_DroneSelfShutdown 和 JobGiver_DroneSelfShutdown:机械体现在会在低电量或被命令时寻找安全地点休眠。
    *   添加了 ThinkNode_ConditionalWorkMode_Drone 和 ThinkNode_ConditionalLowEnergy_Drone 用于行为树逻辑。

4.  **兼容性与修复**:
    *   添加了 Patch_MechanitorUtility_EverControllable:确保自主机械体始终可控,防止在没有监管者时失去控制。
    *   修复了机械体缺少监管者警报的误报问题。
This commit is contained in:
2025-11-23 14:58:13 +08:00
parent 44283c25b8
commit ea31c5f563
23 changed files with 698 additions and 193 deletions

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<WulaFallenEmpire.DroneWorkModeDef>
<defName>Work</defName>
<label>work</label>
<description>The drone will work normally.</description>
<iconPath>UI/Commands/Attack</iconPath>
<uiOrder>10</uiOrder>
</WulaFallenEmpire.DroneWorkModeDef>
<WulaFallenEmpire.DroneWorkModeDef>
<defName>Recharge</defName>
<label>recharge</label>
<description>The drone will seek a charger to recharge.</description>
<iconPath>UI/Commands/DesirePower</iconPath>
<uiOrder>20</uiOrder>
</WulaFallenEmpire.DroneWorkModeDef>
<WulaFallenEmpire.DroneWorkModeDef>
<defName>Shutdown</defName>
<label>shutdown</label>
<description>The drone will find a safe spot to self-shutdown.</description>
<iconPath>UI/Commands/ToggleVent</iconPath>
<uiOrder>30</uiOrder>
</WulaFallenEmpire.DroneWorkModeDef>
<WulaFallenEmpire.DroneWorkModeDef>
<defName>AutoFight</defName>
<label>auto fight</label>
<description>The drone will automatically seek and attack enemies.</description>
<iconPath>UI/Commands/Attack</iconPath>
<uiOrder>40</uiOrder>
</WulaFallenEmpire.DroneWorkModeDef>
</Defs>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<JobDef>
<defName>WULA_DroneSelfShutdown</defName>
<driverClass>WulaFallenEmpire.JobDriver_DroneSelfShutdown</driverClass>
<reportString>self-shutting down.</reportString>
<casualInterruptible>false</casualInterruptible>
<allowOpportunisticPrefix>true</allowOpportunisticPrefix>
</JobDef>
</Defs>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<!-- 扩展原版机械体表格,添加我们的自定义列 -->
<PawnTableDef>
<defName>Mechs</defName>
<columns>
<li>Label</li>
<li>WULA_DroneWorkMode</li>
<li>WULA_DroneEnergy</li>
<li>ControlGroup</li>
<li>Overseer</li>
<li>AllowedArea</li>
<li>WorkPriority</li>
</columns>
</PawnTableDef>
<PawnColumnDef>
<defName>WULA_DroneWorkMode</defName>
<workerClass>WulaFallenEmpire.PawnColumnWorker_DroneWorkMode</workerClass>
<sortable>true</sortable>
<headerIcon>UI/Commands/Attack</headerIcon>
<headerTip>Work Mode</headerTip>
<width>24</width>
</PawnColumnDef>
<PawnColumnDef>
<defName>WULA_DroneEnergy</defName>
<workerClass>WulaFallenEmpire.PawnColumnWorker_DroneEnergy</workerClass>
<sortable>true</sortable>
<headerTip>Energy</headerTip>
<width>120</width>
</PawnColumnDef>
</Defs>

View File

@@ -148,57 +148,6 @@
<WULA_MaintenanceRepairedDamage>修复了 {0} 的结构损伤</WULA_MaintenanceRepairedDamage>
<WULA_DaysSinceMaintenance>距上次维护</WULA_DaysSinceMaintenance>
<!-- Global Production System -->
<WULA_GlobalBillsTab>舰队生产</WULA_GlobalBillsTab>
<WULA_GlobalProduction>舰队生产订单</WULA_GlobalProduction>
<WULA_AddProductionOrder>添加生产订单</WULA_AddProductionOrder>
<WULA_Resume>恢复</WULA_Resume>
<WULA_Pause>暂停</WULA_Pause>
<WULA_Delete>删除</WULA_Delete>
<WULA_WaitingForResources>等待资源</WULA_WaitingForResources>
<WULA_Completed>完成</WULA_Completed>
<WULA_Unknown>未知</WULA_Unknown>
<WULA_InsufficientResources>帝国舰队存储的资源不足</WULA_InsufficientResources>
<WULA_NoAvailableRecipes>无可用配方</WULA_NoAvailableRecipes>
<!-- 添加到您的语言文件中 -->
<WULA_RequiredIngredients>可用配方</WULA_RequiredIngredients>
<WULA_Products>产物</WULA_Products>
<WULA_WorkAmount>工作量</WULA_WorkAmount>
<!-- 中文翻译 -->
<WULA_ViewStorage>查看存储</WULA_ViewStorage>
<WULA_InputStorage>输入存储(原材料)</WULA_InputStorage>
<WULA_OutputStorage>输出存储(产品)</WULA_OutputStorage>
<WULA_NoGlobalStorage>未找到全局存储组件</WULA_NoGlobalStorage>
<WULA_NoItems>无物品</WULA_NoItems>
<WULA_StorageStats>存储统计</WULA_StorageStats>
<WULA_InputItems>种输入物品</WULA_InputItems>
<WULA_OutputItems>种输出物品</WULA_OutputItems>
<!-- 中文翻译 -->
<WULA_AirdropProducts>空投成品</WULA_AirdropProducts>
<WULA_AirdropProductsDesc>将乌拉帝国母舰和工程舰上完成加工的所有物品通过空投舱投放到指定区域</WULA_AirdropProductsDesc>
<WULA_CannotAirdrop>无法执行空投:工作台未就绪</WULA_CannotAirdrop>
<WULA_NoProductsToAirdrop>没有可空投的物品</WULA_NoProductsToAirdrop>
<WULA_AirdropTargetTooFar>目标距离超出最大空投范围({0}</WULA_AirdropTargetTooFar>
<WULA_NoValidDropSpots>没有有效的空投落点。请选择另一个位置</WULA_NoValidDropSpots>
<WULA_FailedToDistributeItems>无法分配物品到空投舱</WULA_FailedToDistributeItems>
<WULA_AirdropSuccessful>成功空投了{0}个空投舱</WULA_AirdropSuccessful>
<WULA_NoFactoryFlyOver>需要拥有-生产设施-的战舰部署在殖民地轨道上才能空投</WULA_NoFactoryFlyOver>
<WULA_NoFactoryFlyOverDesc>当前地图上没有拥有-生产设施-的战舰部署在殖民地轨道上,无法进行空投</WULA_NoFactoryFlyOverDesc>
<WULA_LaunchToGlobalStorage>发射到乌拉帝国舰队</WULA_LaunchToGlobalStorage>
<WULA_LaunchToGlobalStorageDesc>将物品发送到乌拉帝国舰队,以便其使用这些材料进行加工。\n\n如果装备、武器和尸体被送到乌拉帝国舰队则它们会在下一次成品空投被扔回来其他的物资若被乌拉帝国舰队接收则一概不退。</WULA_LaunchToGlobalStorageDesc>
<WULA_NoItemsToSendToGlobalStorage>没有物品可以发送到全局存储</WULA_NoItemsToSendToGlobalStorage>
<!-- CompLaunchable_ToGlobalStorage.cs 新增翻译 -->
<WULA_ItemsSentToBothStorages>{0}件物资被舰队接收,{1}件物资被舰队退回(随着下一次成品空投一起退回)</WULA_ItemsSentToBothStorages>
<WULA_ItemsSentToInputStorage>{0}件物资被舰队接收</WULA_ItemsSentToInputStorage>
<WULA_ItemsSentToOutputStorage>{0}件物资被舰队退回(随着下一次成品空投一起退回)</WULA_ItemsSentToOutputStorage>
<WULA_InputStorageItems>输入存储: {0}</WULA_InputStorageItems>
<WULA_OutputStorageItems>输出存储: {0}</WULA_OutputStorageItems>
<WULA_LaunchCancelledDueToForbiddenItems>舰队拒绝接收物资——里面包含了殖民者、动物、尸体或有毒垃圾</WULA_LaunchCancelledDueToForbiddenItems>
<WULA_ChooseStuff>材质</WULA_ChooseStuff>
<WULA_ChooseStuffTooltip>为此武器选择材质</WULA_ChooseStuffTooltip>
<WULA_CurrentStuff>当前材质:{0}</WULA_CurrentStuff>

View File

@@ -24,49 +24,77 @@
<WULA_AutonomousMechsSection>自律机械体</WULA_AutonomousMechsSection>
<!-- Global Work Table -->
<WULA_GatheringMaterials>准备材料中</WULA_GatheringMaterials>
<WULA_OrderStarted>订单 {0} 已开始生产</WULA_OrderStarted>
<WULA_Uploading>上传中</WULA_Uploading>
<WULA_GlobalBillsTab>全球订单</WULA_GlobalBillsTab>
<WULA_GlobalProduction>全球生产</WULA_GlobalProduction>
<WULA_Equipment>装备</WULA_Equipment>
<WULA_Weapon>武器</WULA_Weapon>
<WULA_Mechanoid>机械体</WULA_Mechanoid>
<WULA_AddOrder>添加订单</WULA_AddOrder>
<!-- Global Production System -->
<WULA_GlobalBillsTab>舰队生产</WULA_GlobalBillsTab>
<WULA_GlobalProduction>舰队生产订单</WULA_GlobalProduction>
<WULA_AddProductionOrder>添加生产订单</WULA_AddProductionOrder>
<WULA_Resume>恢复</WULA_Resume>
<WULA_Pause>暂停</WULA_Pause>
<WULA_Delete>删除</WULA_Delete>
<WULA_WaitingForResources>等待资源</WULA_WaitingForResources>
<WULA_Completed>完成</WULA_Completed>
<WULA_Unknown>未知</WULA_Unknown>
<WULA_Delete>删除</WULA_Delete>
<WULA_Pause>暂停</WULA_Pause>
<WULA_Paused>已暂停</WULA_Paused>
<WULA_Resume>恢复</WULA_Resume>
<WULA_InsufficientResources>资源不足</WULA_InsufficientResources>
<WULA_FixedIngredients>固定原料</WULA_FixedIngredients>
<WULA_InsufficientResources>帝国舰队存储的资源不足</WULA_InsufficientResources>
<WULA_NoAvailableRecipes>无可用配方</WULA_NoAvailableRecipes>
<!-- 添加到您的语言文件中 -->
<WULA_RequiredIngredients>可用配方</WULA_RequiredIngredients>
<WULA_Products>产物</WULA_Products>
<WULA_WorkAmount>工作量</WULA_WorkAmount>
<WULA_GlobalStorage>全球存储</WULA_GlobalStorage>
<WULA_InputStorage>输入存储</WULA_InputStorage>
<WULA_OutputStorage>输出存储</WULA_OutputStorage>
<!-- 中文翻译 -->
<WULA_ViewStorage>查看存储</WULA_ViewStorage>
<WULA_InputStorage>输入存储(原材料)</WULA_InputStorage>
<WULA_OutputStorage>输出存储(产品)</WULA_OutputStorage>
<WULA_NoGlobalStorage>未找到全局存储组件</WULA_NoGlobalStorage>
<WULA_NoItems>无物品</WULA_NoItems>
<WULA_NoGlobalStorage>未找到全球存储组件</WULA_NoGlobalStorage>
<WULA_AirdropProducts>空投产物</WULA_AirdropProducts>
<WULA_AirdropProductsDesc>将云端存储的产物空投到指定位置。</WULA_AirdropProductsDesc>
<WULA_SelectDropLocation>选择空投位置</WULA_SelectDropLocation>
<WULA_ConfirmAirdrop>确认空投</WULA_ConfirmAirdrop>
<WULA_ConfirmAirdropDesc>将消耗 {0} 个空投舱,空投 {1} 个物品。</WULA_ConfirmAirdropDesc>
<WULA_NoOutputItems>没有可空投的产物。</WULA_NoOutputItems>
<WULA_NoFactoryFlyOver>没有工厂设施飞船。</WULA_NoFactoryFlyOver>
<WULA_FailedToDistributeItems>无法分配物品到空投舱。</WULA_FailedToDistributeItems>
<WULA_AirdropSuccessful>成功发射了 {0} 个空投舱。</WULA_AirdropSuccessful>
<WULA_LaunchToGlobalStorage>发射到全球存储</WULA_LaunchToGlobalStorage>
<WULA_LaunchToGlobalStorageDesc>将发射舱内的物品发送到全球存储。</WULA_LaunchToGlobalStorageDesc>
<WULA_NoItemsToSendToGlobalStorage>没有物品可发送。</WULA_NoItemsToSendToGlobalStorage>
<WULA_LaunchCancelledDueToForbiddenItems>发射取消:包含禁止物品 {0}</WULA_LaunchCancelledDueToForbiddenItems>
<WULA_ItemsSentToBothStorages>已发送 {0} 个物品到输入存储,{1} 个物品到输出存储。</WULA_ItemsSentToBothStorages>
<WULA_InputStorageItems>输入物品</WULA_InputStorageItems>
<WULA_OutputStorageItems>输出物品</WULA_OutputStorageItems>
<WULA_ItemsSentToInputStorage>已发送 {0} 个物品到输入存储。</WULA_ItemsSentToInputStorage>
<WULA_ItemsSentToOutputStorage>已发送 {0} 个物品到输出存储。</WULA_ItemsSentToOutputStorage>
<WULA_NoItemsProcessed>没有处理任何物品。</WULA_NoItemsProcessed>
<WULA_StorageStats>存储统计</WULA_StorageStats>
<WULA_InputItems>种输入物品</WULA_InputItems>
<WULA_OutputItems>种输出物品</WULA_OutputItems>
<!-- 中文翻译 -->
<WULA_AirdropProducts>空投成品</WULA_AirdropProducts>
<WULA_AirdropProductsDesc>将乌拉帝国母舰和工程舰上完成加工的所有物品通过空投舱投放到指定区域</WULA_AirdropProductsDesc>
<WULA_CannotAirdrop>无法执行空投:工作台未就绪</WULA_CannotAirdrop>
<WULA_NoProductsToAirdrop>没有可空投的物品</WULA_NoProductsToAirdrop>
<WULA_AirdropTargetTooFar>目标距离超出最大空投范围({0}</WULA_AirdropTargetTooFar>
<WULA_NoValidDropSpots>没有有效的空投落点。请选择另一个位置</WULA_NoValidDropSpots>
<WULA_FailedToDistributeItems>无法分配物品到空投舱</WULA_FailedToDistributeItems>
<WULA_AirdropSuccessful>成功空投了{0}个空投舱</WULA_AirdropSuccessful>
<WULA_NoFactoryFlyOver>需要拥有-生产设施-的战舰部署在殖民地轨道上才能空投</WULA_NoFactoryFlyOver>
<WULA_NoFactoryFlyOverDesc>当前地图上没有拥有-生产设施-的战舰部署在殖民地轨道上,无法进行空投</WULA_NoFactoryFlyOverDesc>
<WULA_LaunchToGlobalStorage>发射到乌拉帝国舰队</WULA_LaunchToGlobalStorage>
<WULA_LaunchToGlobalStorageDesc>将物品发送到乌拉帝国舰队,以便其使用这些材料进行加工。\n\n如果装备、武器和尸体被送到乌拉帝国舰队则它们会在下一次成品空投被扔回来其他的物资若被乌拉帝国舰队接收则一概不退。</WULA_LaunchToGlobalStorageDesc>
<WULA_NoItemsToSendToGlobalStorage>没有物品可以发送到全局存储</WULA_NoItemsToSendToGlobalStorage>
<!-- CompLaunchable_ToGlobalStorage.cs 新增翻译 -->
<WULA_ItemsSentToBothStorages>{0}件物资被舰队接收,{1}件物资被舰队退回(随着下一次成品空投一起退回)</WULA_ItemsSentToBothStorages>
<WULA_ItemsSentToInputStorage>{0}件物资被舰队接收</WULA_ItemsSentToInputStorage>
<WULA_ItemsSentToOutputStorage>{0}件物资被舰队退回(随着下一次成品空投一起退回)</WULA_ItemsSentToOutputStorage>
<WULA_InputStorageItems>输入存储: {0}</WULA_InputStorageItems>
<WULA_OutputStorageItems>输出存储: {0}</WULA_OutputStorageItems>
<WULA_LaunchCancelledDueToForbiddenItems>舰队拒绝接收物资——里面包含了殖民者、动物、尸体或有毒垃圾</WULA_LaunchCancelledDueToForbiddenItems>
<!-- 自主机械能量系统 -->
<WULA_EnergyInfo> [能量: {0}]</WULA_EnergyInfo>
<WULA_CurrentEnergy>当前能量: {0}</WULA_CurrentEnergy>
<WULA_EnergyLow> [低能量!]</WULA_EnergyLow>
<WULA_EnergyCritical> [临界能量!]</WULA_EnergyCritical>
<WULA_Switch_Mech_WorkMode>切换机械工作模式</WULA_Switch_Mech_WorkMode>
<WULA_Mech_WorkMode>机械模式: {0}</WULA_Mech_WorkMode>
<WULA_WorkMode_Work>工作</WULA_WorkMode_Work>
<WULA_WorkMode_Recharge>充电</WULA_WorkMode_Recharge>
<WULA_WorkMode_Shutdown>关机</WULA_WorkMode_Shutdown>
<WULA_WorkMode_Unknown>未知</WULA_WorkMode_Unknown>
<WULA_WorkMode_Work_Desc>切换到工作模式 - 机械将执行分配的工作</WULA_WorkMode_Work_Desc>
<WULA_WorkMode_Recharge_Desc>切换到充电模式 - 机械将寻找充电站并充电</WULA_WorkMode_Recharge_Desc>
<WULA_WorkMode_Shutdown_Desc>切换到关机模式 - 机械将立即进入休眠状态</WULA_WorkMode_Shutdown_Desc>
<WULA_SwitchedToMode>{0} 已切换到 {1} 模式</WULA_SwitchedToMode>
<WULA_EnergyInfoShort>(能量: {0})</WULA_EnergyInfoShort>
<WULA_Autonomous_Drafted>自主控制</WULA_Autonomous_Drafted>
<WULA_Autonomous_Mode>自主模式: {0}</WULA_Autonomous_Mode>
<WULA_LowEnergySwitchToRecharge>{0} 能量低,自动切换到充电模式</WULA_LowEnergySwitchToRecharge>
<WULA_FullyChargedSwitchToWork>{0} 已充满电,自动切换到工作模式</WULA_FullyChargedSwitchToWork>
<WULA_CriticalEnergyLevels>{0} 能量临界!</WULA_CriticalEnergyLevels>
</LanguageData>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LanguageData>
<!-- WULA_Example_RandomList -->
<WULA_Example_RandomList.characterName>系统管理员</WULA_Example_RandomList.characterName>
<WULA_Example_RandomList.description>这是一个演示随机效果列表功能的事件。
点击下面的选项,你会看到一条固定消息,然后会随机获得一件物品。</WULA_Example_RandomList.description>
<WULA_Example_RandomList.option.0.label>试一试!</WULA_Example_RandomList.option.0.label>
<WULA_Example_RandomList.option.1.label>关闭</WULA_Example_RandomList.option.1.label>
</LanguageData>

View File

@@ -0,0 +1,58 @@
# 自主机械体系统改进计划
基于对 `AncotLibrary` 的分析,我们将对现有的 `WULA_AutonomousMech` 系统进行全面升级,旨在提供更灵活的配置、更友好的 UI 交互以及更智能的 AI 行为。
## 1. 核心架构重构
### 1.1 工作模式数据驱动化
* **目标**: 废弃硬编码的 `AutonomousWorkMode` 枚举,转为使用 XML 定义的 `DroneWorkModeDef`
* **实现**:
* 创建 `DroneWorkModeDef` 类,包含 `iconPath` (图标路径), `uiOrder` (排序), `label` (名称), `description` (描述) 等字段。
*`CompAutonomousMech` 中使用 `DroneWorkModeDef` 类型的字段替代原有的枚举。
* 预定义基础模式:`Work` (工作), `Recharge` (充电), `Shutdown` (休眠), `AutoFight` (自动战斗)。
### 1.2 自动战斗系统 (`AutoFight`)
* **目标**: 允许机械体在非征召状态下自动寻找并攻击敌人。
* **实现**:
* 引入 `CompMechAutoFight` 组件(或集成到 `CompAutonomousMech` 中)。
* 添加 `ThinkNode_ConditionalAutoFight` 行为树节点。
* 实现自动索敌和攻击的 AI 逻辑(参考 `JobGiver_AIFightEnemies`)。
* **威胁判定**: 确保开启自动战斗的机械体能被敌人正确识别为威胁(已部分实现,需完善)。
## 2. UI 交互增强
### 2.1 高级 Gizmo (`DroneGizmo`)
* **目标**: 提供更直观的控制面板。
* **实现**:
* **能量条**: 在 Gizmo 上直接显示当前能量百分比和剩余工作时间。
* **拖动设置**: 允许玩家通过拖动条设置“自动充电阈值”(例如:低于 30% 去充电)。
* **模式切换**: 点击图标弹出 `FloatMenu` 选择工作模式。
* **批量操作**: 当选中多个同类机械体时Gizmo 操作应同步应用到所有选中的单位。
### 2.2 列表视图增强 (`PawnColumnWorker`)
* **目标**: 在“动物/机械体”概览面板中提供关键信息。
* **实现**:
* `PawnColumnWorker_DroneEnergy`: 显示能量条。
* `PawnColumnWorker_DroneWorkMode`: 显示当前工作模式图标,点击可快速切换。
## 3. AI 行为优化
### 3.1 智能充电与休眠
* **目标**: 防止机械体在工作途中突然断电倒地。
* **实现**:
* **低电量保护**: 当能量低于临界值(如 5%)且无法到达充电站时,自动寻找最近的安全地点(如室内、屋顶下)进入休眠状态 (`JobDriver_DroneSelfShutdown`)。
* **智能充电**: 优化 `JobGiver_GetDroneEnergy`,根据距离和当前工作优先级动态决定何时去充电。
### 3.2 永远可控 (`EverControllable`)
* **目标**: 确保无论发生什么(如断网、无监管者),玩家始终能控制机械体。
* **实现**:
* 参考 `AncotPatch_MechanitorUtility_EverControllable`,通过 Harmony 补丁强制 `MechanitorUtility.EverControllable` 返回 true。
## 4. 实施步骤
1. **定义 Defs**: 创建 `DroneWorkModeDef` 及相关 XML 配置。
2. **重构 Comp**: 修改 `CompAutonomousMech` 以支持新的 Def 和逻辑。
3. **UI 开发**: 实现 `DroneGizmo``PawnColumnWorker`
4. **AI 移植**: 移植并适配 `JobDriver_DroneSelfShutdown` 和相关 ThinkNodes。
5. **补丁完善**: 添加 `EverControllable` 等缺失的 Harmony 补丁。
6. **测试与验证**: 确保新旧系统平滑过渡,无红字报错。

View File

@@ -0,0 +1,56 @@
using HarmonyLib;
using RimWorld;
using RimWorld.Planet;
using System.Collections.Generic;
using System.Linq;
using Verse;
namespace WulaFallenEmpire
{
[HarmonyPatch(typeof(Alert_SubjectHasNowOverseer), "GetReport")]
public static class Patch_Alert_SubjectHasNowOverseer
{
[HarmonyPostfix]
public static void Postfix(ref AlertReport __result)
{
if (!__result.active)
{
return;
}
// AlertReport 是一个结构体,没有 culprits 属性,需要通过 AllCulprits 获取
// 并且 AlertReport 的字段是只读的或者不方便直接修改,所以我们需要重新构建
List<GlobalTargetInfo> allCulprits = __result.AllCulprits.ToList();
bool changed = false;
for (int i = allCulprits.Count - 1; i >= 0; i--)
{
Pawn pawn = allCulprits[i].Thing as Pawn;
if (pawn != null)
{
var comp = pawn.GetComp<CompAutonomousMech>();
// 如果是自主机械体,且允许自主工作,则从警报列表中移除
if (comp != null && comp.CanBeAutonomous)
{
allCulprits.RemoveAt(i);
changed = true;
}
}
}
// 如果列表发生了变化,重新生成 AlertReport
if (changed)
{
if (allCulprits.Count > 0)
{
__result = AlertReport.CulpritsAre(allCulprits);
}
else
{
__result = AlertReport.Inactive;
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
using HarmonyLib;
using RimWorld;
using System.Collections.Generic;
using System.Linq;
using Verse;
namespace WulaFallenEmpire
{
[HarmonyPatch(typeof(MainTabWindow_Mechs), "Pawns", MethodType.Getter)]
public static class Patch_MainTabWindow_Mechs_Pawns
{
[HarmonyPostfix]
public static void Postfix(ref IEnumerable<Pawn> __result)
{
// 获取所有自主机械体
var autonomousMechs = Find.CurrentMap.mapPawns.PawnsInFaction(Faction.OfPlayer)
.Where(p => p.RaceProps.IsMechanoid && p.GetComp<CompAutonomousMech>()?.CanBeAutonomous == true);
// 将自主机械体合并到结果中,并去重
__result = __result.Concat(autonomousMechs).Distinct();
}
}
}

View File

@@ -0,0 +1,19 @@
using HarmonyLib;
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
[HarmonyPatch(typeof(MechanitorUtility), "EverControllable")]
public static class Patch_MechanitorUtility_EverControllable
{
[HarmonyPostfix]
public static void Postfix(Pawn mech, ref bool __result)
{
if (!__result && mech.TryGetComp<CompAutonomousMech>() != null)
{
__result = true;
}
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
@@ -30,14 +31,6 @@ namespace WulaFallenEmpire
}
}
// 新增:自主工作模式枚举
public enum AutonomousWorkMode
{
Work, // 工作模式:通过 thinktree 寻找工作
Recharge, // 充电模式:优先充电,完成后休眠
Shutdown // 关机模式:立即休眠
}
public class CompProperties_AutonomousMech : CompProperties
{
public bool enableAutonomousDrafting = true;
@@ -50,6 +43,8 @@ namespace WulaFallenEmpire
public float criticalEnergyThreshold = 0.1f; // 临界能量阈值
public float rechargeCompleteThreshold = 0.9f; // 充电完成阈值
public DroneWorkModeDef initialWorkMode;
public CompProperties_AutonomousMech()
{
compClass = typeof(CompAutonomousMech);
@@ -62,7 +57,7 @@ namespace WulaFallenEmpire
public Pawn MechPawn => parent as Pawn;
private AutonomousWorkMode currentWorkMode = AutonomousWorkMode.Work;
private DroneWorkModeDef currentWorkMode;
private bool wasLowEnergy = false; // 记录上次是否处于低能量状态
public bool CanBeAutonomous
@@ -147,7 +142,7 @@ namespace WulaFallenEmpire
}
}
public AutonomousWorkMode CurrentWorkMode => currentWorkMode;
public DroneWorkModeDef CurrentWorkMode => currentWorkMode;
// 新增:能量状态检查方法
public float GetEnergyLevel()
@@ -164,6 +159,11 @@ namespace WulaFallenEmpire
{
base.PostSpawnSetup(respawningAfterLoad);
if (currentWorkMode == null)
{
currentWorkMode = Props.initialWorkMode ?? WulaDefOf.Work;
}
// 确保使用独立战斗系统
InitializeAutonomousCombat();
}
@@ -208,10 +208,10 @@ namespace WulaFallenEmpire
if (isLowEnergyNow)
{
// 进入低能量状态
if (currentWorkMode == AutonomousWorkMode.Work)
if (currentWorkMode == WulaDefOf.Work)
{
// 自动切换到充电模式
SetWorkMode(AutonomousWorkMode.Recharge);
SetWorkMode(WulaDefOf.Recharge);
Messages.Message("WULA_LowEnergySwitchToRecharge".Translate(MechPawn.LabelCap),
MechPawn, MessageTypeDefOf.CautionInput);
}
@@ -219,10 +219,10 @@ namespace WulaFallenEmpire
else
{
// 恢复能量状态
if (currentWorkMode == AutonomousWorkMode.Recharge && IsFullyCharged)
if (currentWorkMode == WulaDefOf.Recharge && IsFullyCharged)
{
// 充满电后自动切换回工作模式
SetWorkMode(AutonomousWorkMode.Work);
SetWorkMode(WulaDefOf.Work);
Messages.Message("WULA_FullyChargedSwitchToWork".Translate(MechPawn.LabelCap),
MechPawn, MessageTypeDefOf.PositiveEvent);
}
@@ -232,12 +232,12 @@ namespace WulaFallenEmpire
}
// 临界能量警告
if (IsCriticalEnergy && currentWorkMode != AutonomousWorkMode.Recharge && currentWorkMode != AutonomousWorkMode.Shutdown)
if (IsCriticalEnergy && currentWorkMode != WulaDefOf.Recharge && currentWorkMode != WulaDefOf.Shutdown)
{
Messages.Message("WULA_CriticalEnergyLevels".Translate(MechPawn.LabelCap),
MechPawn, MessageTypeDefOf.ThreatBig);
// 强制切换到充电模式
SetWorkMode(AutonomousWorkMode.Recharge);
SetWorkMode(WulaDefOf.Recharge);
}
}
@@ -249,77 +249,11 @@ namespace WulaFallenEmpire
// 工作模式切换按钮
if (CanWorkAutonomously)
{
string energyInfo = "WULA_EnergyInfo".Translate(GetEnergyLevel().ToStringPercent());
yield return new Command_Action
{
defaultLabel = "WULA_Mech_WorkMode".Translate(GetCurrentWorkModeDisplay()) + energyInfo,
defaultDesc = GetWorkModeDescription(),
icon = GetWorkModeIcon(),
action = () => ShowWorkModeMenu()
};
yield return new DroneGizmo(this);
}
}
// 修改:返回包含能量信息的描述
private string GetWorkModeDescription()
{
string baseDesc = "WULA_Switch_Mech_WorkMode".Translate();
string energyInfo = "WULA_CurrentEnergy".Translate(GetEnergyLevel().ToStringPercent());
if (IsLowEnergy)
energyInfo += "WULA_EnergyLow".Translate();
if (IsCriticalEnergy)
energyInfo += "WULA_EnergyCritical".Translate();
return baseDesc + "\n" + energyInfo;
}
// 新增:根据能量状态返回不同的图标
private UnityEngine.Texture2D GetWorkModeIcon()
{
if (IsCriticalEnergy)
return TexCommand.DesirePower;
else if (IsLowEnergy)
return TexCommand.ToggleVent;
else
return TexCommand.Attack;
}
private string GetCurrentWorkModeDisplay()
{
switch (currentWorkMode)
{
case AutonomousWorkMode.Work:
return "WULA_WorkMode_Work".Translate();
case AutonomousWorkMode.Recharge:
return "WULA_WorkMode_Recharge".Translate();
case AutonomousWorkMode.Shutdown:
return "WULA_WorkMode_Shutdown".Translate();
default:
return "WULA_WorkMode_Unknown".Translate();
}
}
private void ShowWorkModeMenu()
{
List<FloatMenuOption> list = new List<FloatMenuOption>();
// 工作模式
list.Add(new FloatMenuOption("WULA_WorkMode_Work_Desc".Translate(),
() => SetWorkMode(AutonomousWorkMode.Work)));
// 充电模式
list.Add(new FloatMenuOption("WULA_WorkMode_Recharge_Desc".Translate(),
() => SetWorkMode(AutonomousWorkMode.Recharge)));
// 休眠模式
list.Add(new FloatMenuOption("WULA_WorkMode_Shutdown_Desc".Translate(),
() => SetWorkMode(AutonomousWorkMode.Shutdown)));
Find.WindowStack.Add(new FloatMenu(list));
}
private void SetWorkMode(AutonomousWorkMode mode)
public void SetWorkMode(DroneWorkModeDef mode)
{
currentWorkMode = mode;
@@ -329,8 +263,7 @@ namespace WulaFallenEmpire
MechPawn.jobs.StopAll();
}
string modeName = GetCurrentWorkModeDisplay();
Messages.Message("WULA_SwitchedToMode".Translate(MechPawn.LabelCap, modeName),
Messages.Message("WULA_SwitchedToMode".Translate(MechPawn.LabelCap, mode.label),
MechPawn, MessageTypeDefOf.NeutralEvent);
}
@@ -352,13 +285,13 @@ namespace WulaFallenEmpire
if (MechPawn.Drafted)
return "WULA_Autonomous_Drafted".Translate() + energyInfo;
else
return "WULA_Autonomous_Mode".Translate(GetCurrentWorkModeDisplay()) + energyInfo;
return "WULA_Autonomous_Mode".Translate(currentWorkMode?.label ?? "Unknown") + energyInfo;
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref currentWorkMode, "currentWorkMode", AutonomousWorkMode.Work);
Scribe_Defs.Look(ref currentWorkMode, "currentWorkMode");
Scribe_Values.Look(ref wasLowEnergy, "wasLowEnergy", false);
}
}

View File

@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
[StaticConstructorOnStartup]
public class DroneGizmo : Gizmo
{
private CompAutonomousMech comp;
private HashSet<CompAutonomousMech> groupedComps;
private static readonly Texture2D BarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.34f, 0.42f, 0.43f));
private static readonly Texture2D BarHighlightTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.43f, 0.54f, 0.55f));
private static readonly Texture2D EmptyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.03f, 0.035f, 0.05f));
// private static readonly Texture2D DragBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.74f, 0.97f, 0.8f));
// private static bool draggingBar;
public DroneGizmo(CompAutonomousMech comp)
{
this.comp = comp;
}
public override float GetWidth(float maxWidth)
{
return 160f;
}
public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
{
Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), 75f);
Rect rect2 = rect.ContractedBy(10f);
Widgets.DrawWindowBackground(rect);
string text = "WULA_AutonomousMech".Translate();
Rect rect3 = new Rect(rect2.x, rect2.y, rect2.width, Text.CalcHeight(text, rect2.width) + 8f);
Text.Font = GameFont.Small;
Widgets.Label(rect3, text);
Rect rect4 = new Rect(rect2.x, rect3.yMax, rect2.width, rect2.height - rect3.height);
DraggableBarForGroup(rect4);
Text.Anchor = TextAnchor.MiddleCenter;
string energyText = comp.GetEnergyLevel().ToStringPercent();
Widgets.Label(rect4, energyText);
Text.Anchor = TextAnchor.UpperLeft;
TooltipHandler.TipRegion(rect4, () => "WULA_EnergyInfo".Translate(energyText), Gen.HashCombineInt(comp.GetHashCode(), 34242419));
// Work Mode Button
Rect rect6 = new Rect(rect2.x + rect2.width - 24f, rect2.y, 24f, 24f);
if (Widgets.ButtonImageFitted(rect6, comp.CurrentWorkMode?.uiIcon ?? BaseContent.BadTex))
{
Find.WindowStack.Add(new FloatMenu(GetWorkModeOptions(comp, groupedComps).ToList()));
}
TooltipHandler.TipRegion(rect6, "WULA_Switch_Mech_WorkMode".Translate());
Widgets.DrawHighlightIfMouseover(rect6);
return new GizmoResult(GizmoState.Clear);
}
private void DraggableBarForGroup(Rect rect)
{
// We are not actually dragging the energy level, but maybe a threshold?
// For now, just display the energy level.
// If we want to set recharge threshold, we need a property in CompAutonomousMech for that.
// Assuming we want to visualize energy level:
Widgets.FillableBar(rect, comp.GetEnergyLevel(), BarTex, EmptyBarTex, false);
}
public static IEnumerable<FloatMenuOption> GetWorkModeOptions(CompAutonomousMech comp, HashSet<CompAutonomousMech> groupedComps = null)
{
foreach (DroneWorkModeDef mode in DefDatabase<DroneWorkModeDef>.AllDefs.OrderBy(d => d.uiOrder))
{
yield return new FloatMenuOption(mode.LabelCap, delegate
{
comp.SetWorkMode(mode);
if (groupedComps != null)
{
foreach (CompAutonomousMech groupedComp in groupedComps)
{
groupedComp.SetWorkMode(mode);
}
}
}, mode.uiIcon, Color.white);
}
}
public override bool GroupsWith(Gizmo other)
{
return other is DroneGizmo;
}
public override void MergeWith(Gizmo other)
{
base.MergeWith(other);
if (other is DroneGizmo droneGizmo)
{
if (groupedComps == null)
{
groupedComps = new HashSet<CompAutonomousMech>();
}
groupedComps.Add(droneGizmo.comp);
if (droneGizmo.groupedComps != null)
{
groupedComps.AddRange(droneGizmo.groupedComps);
}
}
}
}
}

View File

@@ -0,0 +1,26 @@
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
public class DroneWorkModeDef : Def
{
[NoTranslate]
public string iconPath;
public Texture2D uiIcon;
public int uiOrder;
public override void PostLoad()
{
if (!string.IsNullOrEmpty(iconPath))
{
LongEventHandler.ExecuteWhenFinished(delegate
{
uiIcon = ContentFinder<Texture2D>.Get(iconPath);
});
}
}
}
}

View File

@@ -0,0 +1,45 @@
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class JobDriver_DroneSelfShutdown : JobDriver
{
public const TargetIndex RestSpotIndex = TargetIndex.A;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
return pawn.Reserve(base.TargetA, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
yield return Toils_Goto.GotoCell(TargetIndex.A, PathEndMode.OnCell);
Toil layDown = SelfShutdown();
layDown.PlaySoundAtStart(SoundDefOf.MechSelfShutdown);
yield return layDown;
}
public static Toil SelfShutdown()
{
Toil layDown = ToilMaker.MakeToil("WULA_DroneSelfShutdown");
layDown.initAction = delegate
{
Pawn actor = layDown.actor;
actor.pather?.StopDead();
JobDriver curDriver = actor.jobs.curDriver;
actor.jobs.posture = PawnPosture.Standing;
actor.mindState.lastBedDefSleptIn = null;
curDriver.asleep = true;
};
layDown.defaultCompleteMode = ToilCompleteMode.Never;
layDown.AddFinishAction(delegate
{
layDown.actor.jobs.curDriver.asleep = false;
});
return layDown;
}
}
}

View File

@@ -0,0 +1,20 @@
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class JobGiver_DroneSelfShutdown : ThinkNode_JobGiver
{
protected override Job TryGiveJob(Pawn pawn)
{
if (RCellFinder.TryFindNearbyMechSelfShutdownSpot(pawn.Position, pawn, pawn.Map, out var result, allowForbidden: true))
{
Job job = JobMaker.MakeJob(WulaDefOf.WULA_DroneSelfShutdown, result);
job.forceSleep = true;
return job;
}
return null;
}
}
}

View File

@@ -0,0 +1,42 @@
using RimWorld;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
[StaticConstructorOnStartup]
public class PawnColumnWorker_DroneEnergy : PawnColumnWorker
{
private const int Width = 120;
private const int BarPadding = 4;
public static readonly Texture2D EnergyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color32(252, byte.MaxValue, byte.MaxValue, 65));
public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
{
CompAutonomousMech comp = pawn.TryGetComp<CompAutonomousMech>();
if (comp == null || !comp.CanBeAutonomous)
{
return;
}
Widgets.FillableBar(rect.ContractedBy(4f), comp.GetEnergyLevel(), EnergyBarTex, BaseContent.ClearTex, doBorder: false);
Text.Font = GameFont.Small;
Text.Anchor = TextAnchor.MiddleCenter;
Widgets.Label(rect, comp.GetEnergyLevel().ToStringPercent());
Text.Anchor = TextAnchor.UpperLeft;
Text.Font = GameFont.Small;
}
public override int GetMinWidth(PawnTable table)
{
return Mathf.Max(base.GetMinWidth(table), 120);
}
public override int GetMaxWidth(PawnTable table)
{
return Mathf.Min(base.GetMaxWidth(table), GetMinWidth(table));
}
}
}

View File

@@ -0,0 +1,42 @@
using System.Linq;
using RimWorld;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
public class PawnColumnWorker_DroneWorkMode : PawnColumnWorker_Icon
{
protected override int Padding => 0;
public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
{
CompAutonomousMech comp = pawn.TryGetComp<CompAutonomousMech>();
if (comp == null || !comp.CanBeAutonomous)
{
return;
}
if (Widgets.ButtonInvisible(rect))
{
Find.WindowStack.Add(new FloatMenu(DroneGizmo.GetWorkModeOptions(comp).ToList()));
}
base.DoCell(rect, pawn, table);
}
protected override Texture2D GetIconFor(Pawn pawn)
{
return pawn?.TryGetComp<CompAutonomousMech>()?.CurrentWorkMode?.uiIcon;
}
protected override string GetIconTip(Pawn pawn)
{
string text = pawn.TryGetComp<CompAutonomousMech>()?.CurrentWorkMode?.description;
if (!text.NullOrEmpty())
{
return text;
}
return null;
}
}
}

View File

@@ -6,7 +6,7 @@ namespace WulaFallenEmpire
{
public class ThinkNode_ConditionalAutonomousWorkMode : ThinkNode_Conditional
{
public AutonomousWorkMode requiredMode = AutonomousWorkMode.Work;
public DroneWorkModeDef requiredMode;
protected override bool Satisfied(Pawn pawn)
{

View File

@@ -0,0 +1,18 @@
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class ThinkNode_ConditionalLowEnergy_Drone : ThinkNode_Conditional
{
protected override bool Satisfied(Pawn pawn)
{
CompAutonomousMech compDrone = pawn.TryGetComp<CompAutonomousMech>();
if (compDrone != null && compDrone.IsLowEnergy)
{
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,32 @@
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class ThinkNode_ConditionalWorkMode_Drone : ThinkNode_Conditional
{
public DroneWorkModeDef workMode;
public override ThinkNode DeepCopy(bool resolve = true)
{
ThinkNode_ConditionalWorkMode_Drone thinkNode_ConditionalWorkMode_Drone = (ThinkNode_ConditionalWorkMode_Drone)base.DeepCopy(resolve);
thinkNode_ConditionalWorkMode_Drone.workMode = workMode;
return thinkNode_ConditionalWorkMode_Drone;
}
protected override bool Satisfied(Pawn pawn)
{
if (!pawn.RaceProps.IsMechanoid || pawn.Faction != Faction.OfPlayer)
{
return false;
}
CompAutonomousMech compDrone = pawn.TryGetComp<CompAutonomousMech>();
if (compDrone == null)
{
return false;
}
return compDrone.CurrentWorkMode == workMode;
}
}
}

View File

@@ -3,6 +3,7 @@ using Verse;
namespace WulaFallenEmpire
{
[DefOf]
public static class ThingDefOf_WULA
{
@@ -75,4 +76,18 @@ namespace WulaFallenEmpire
}
}
[DefOf]
public static class WulaDefOf
{
public static JobDef WULA_DroneSelfShutdown;
public static DroneWorkModeDef Work;
public static DroneWorkModeDef Recharge;
public static DroneWorkModeDef Shutdown;
public static DroneWorkModeDef AutoFight;
static WulaDefOf()
{
DefOfHelper.EnsureInitializedInCtor(typeof(WulaDefOf));
}
}
}

View File

@@ -170,6 +170,7 @@
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_CaravanFormingUtility_AllSendablePawns.cs" />
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_Pawn_ThreatDisabled.cs" />
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_UncontrolledMechDrawPulse.cs" />
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_Alert_MechLacksOverseer.cs" />
<Compile Include="HediffComp\HediffCompProperties_DisappearWithEffect.cs" />
<Compile Include="HediffComp\HediffCompProperties_NanoRepair.cs" />
<Compile Include="HediffComp\HediffCompProperties_SwitchableHediff.cs" />
@@ -180,6 +181,16 @@
<Compile Include="Pawn\WULA_AutoMechCarrier\CompAutoMechCarrier.cs" />
<Compile Include="Pawn\WULA_AutoMechCarrier\CompProperties_AutoMechCarrier.cs" />
<Compile Include="Pawn\WULA_AutoMechCarrier\PawnProductionEntry.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\DroneWorkModeDef.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\DroneGizmo.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\PawnColumnWorker_DroneWorkMode.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\PawnColumnWorker_DroneEnergy.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\JobDriver_DroneSelfShutdown.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\JobGiver_DroneSelfShutdown.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\ThinkNode_ConditionalWorkMode_Drone.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\ThinkNode_ConditionalLowEnergy_Drone.cs" />
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_MechanitorUtility_EverControllable.cs" />
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_MainTabWindow_Mechs_Pawns.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\CompAutonomousMech.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\ThinkNode_ConditionalAutonomousWorkMode.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\ThinkNode_ConditionalNeedRecharge.cs" />