暂存
This commit is contained in:
Binary file not shown.
@@ -68,8 +68,7 @@
|
|||||||
<titleShort>蜜罐种</titleShort>
|
<titleShort>蜜罐种</titleShort>
|
||||||
<description>[PAWN_nameDef]是一只阿拉克涅蜜罐种督虫。基因的选择性表达使其长出了肿胀的囊袋和复杂的口器,这使得她可以吞噬那些未经过处理的尸体和各种杂食或是将其进一步分解,并通过消化器官将其转变为阿拉克涅虫蜜以供其他虫族食用。\n\n[PAWN_nameDef]在战斗中并不是一个值得正视的对手,她没有可以接入武装器官的副肢,脆弱臃肿的特性也决定了她几乎无法躲开任何攻击。</description>
|
<description>[PAWN_nameDef]是一只阿拉克涅蜜罐种督虫。基因的选择性表达使其长出了肿胀的囊袋和复杂的口器,这使得她可以吞噬那些未经过处理的尸体和各种杂食或是将其进一步分解,并通过消化器官将其转变为阿拉克涅虫蜜以供其他虫族食用。\n\n[PAWN_nameDef]在战斗中并不是一个值得正视的对手,她没有可以接入武装器官的副肢,脆弱臃肿的特性也决定了她几乎无法躲开任何攻击。</description>
|
||||||
<slot>Adulthood</slot>
|
<slot>Adulthood</slot>
|
||||||
<workDisables>AllWork</workDisables>
|
<requiredWorkTags>Cooking</requiredWorkTags>
|
||||||
<requiredWorkTags>Cooking</requiredWorkTags>
|
|
||||||
|
|
||||||
<spawnCategories>
|
<spawnCategories>
|
||||||
<li>ArachnaeNode_spawnCategories_Myrmecocystus</li>
|
<li>ArachnaeNode_spawnCategories_Myrmecocystus</li>
|
||||||
@@ -81,7 +80,6 @@
|
|||||||
<titleShort>盾头种</titleShort>
|
<titleShort>盾头种</titleShort>
|
||||||
<description>[PAWN_nameDef]是一只阿拉克涅盾头种督虫。盾头种是一种笨重的阿拉克涅虫族,她们拥有厚厚的经常过度生长的甲壳,除了生产甲壳素外,也拥有接入武装器官的辅肢,可以凭借虫群的武装器官和自己与生俱来的盔甲承担保卫虫巢的任务。</description>
|
<description>[PAWN_nameDef]是一只阿拉克涅盾头种督虫。盾头种是一种笨重的阿拉克涅虫族,她们拥有厚厚的经常过度生长的甲壳,除了生产甲壳素外,也拥有接入武装器官的辅肢,可以凭借虫群的武装器官和自己与生俱来的盔甲承担保卫虫巢的任务。</description>
|
||||||
<slot>Adulthood</slot>
|
<slot>Adulthood</slot>
|
||||||
<workDisables>AllWork</workDisables>
|
|
||||||
|
|
||||||
<spawnCategories>
|
<spawnCategories>
|
||||||
<li>ArachnaeNode_spawnCategories_ShieldHead</li>
|
<li>ArachnaeNode_spawnCategories_ShieldHead</li>
|
||||||
|
|||||||
@@ -6,77 +6,93 @@
|
|||||||
<label>阿拉克涅孵化茧</label>
|
<label>阿拉克涅孵化茧</label>
|
||||||
<description>一个脆弱、易燃、黏滑的囊状物,是阿拉克涅工艺种所诞之卵,内含哺育阿拉克涅武器种虫族所需的营养和遗传物质,可以通过阿拉克涅工艺种的交互完成激活进程。</description>
|
<description>一个脆弱、易燃、黏滑的囊状物,是阿拉克涅工艺种所诞之卵,内含哺育阿拉克涅武器种虫族所需的营养和遗传物质,可以通过阿拉克涅工艺种的交互完成激活进程。</description>
|
||||||
<thingClass>Building</thingClass>
|
<thingClass>Building</thingClass>
|
||||||
<descriptionHyperlinks>
|
|
||||||
</descriptionHyperlinks>
|
|
||||||
<category>Building</category>
|
|
||||||
<size>(1,1)</size>
|
|
||||||
<minifiedDef>MinifiedThing</minifiedDef>
|
|
||||||
<thingCategories>
|
|
||||||
<li>BuildingsMisc</li>
|
|
||||||
</thingCategories>
|
|
||||||
<graphicData>
|
<graphicData>
|
||||||
<texPath>ArachnaeSwarm/Building/ARA_EggSac</texPath>
|
<texPath>ArachnaeSwarm/Building/ARA_EggSac</texPath>
|
||||||
<graphicClass>Graphic_Single</graphicClass>
|
<graphicClass>Graphic_Single</graphicClass>
|
||||||
<drawSize>(1.5,1.5)</drawSize>
|
<drawSize>(1.5,1.5)</drawSize>
|
||||||
</graphicData>
|
</graphicData>
|
||||||
|
<size>(1,1)</size>
|
||||||
<altitudeLayer>Building</altitudeLayer>
|
<altitudeLayer>Building</altitudeLayer>
|
||||||
<passability>PassThroughOnly</passability>
|
<passability>PassThroughOnly</passability>
|
||||||
<fillPercent>0.3</fillPercent>
|
<fillPercent>0.3</fillPercent>
|
||||||
<rotatable>false</rotatable>
|
<rotatable>false</rotatable>
|
||||||
<tickerType>Normal</tickerType>
|
<tickerType>Normal</tickerType>
|
||||||
<terrainAffordanceNeeded>Light</terrainAffordanceNeeded>
|
|
||||||
<statBases>
|
<statBases>
|
||||||
<Mass>10</Mass>
|
|
||||||
<MaxHitPoints>50</MaxHitPoints>
|
<MaxHitPoints>50</MaxHitPoints>
|
||||||
<Flammability>1</Flammability>
|
<Flammability>1</Flammability>
|
||||||
<Beauty>-6</Beauty>
|
|
||||||
</statBases>
|
</statBases>
|
||||||
<building>
|
<building>
|
||||||
<isInert>true</isInert>
|
|
||||||
<!-- <claimable>false</claimable> -->
|
|
||||||
<deconstructible>false</deconstructible>
|
<deconstructible>false</deconstructible>
|
||||||
<repairable>false</repairable>
|
<repairable>false</repairable>
|
||||||
<quickTargetable>true</quickTargetable>
|
|
||||||
<isTargetable>true</isTargetable>
|
|
||||||
<expandHomeArea>false</expandHomeArea>
|
|
||||||
</building>
|
</building>
|
||||||
<tickerType>Normal</tickerType>
|
|
||||||
|
|
||||||
<comps>
|
<comps>
|
||||||
<li Class="CompProperties_Flickable"/>
|
<li Class="CompProperties_Flickable"/>
|
||||||
|
|
||||||
|
<!-- The new, GrowthVat-style fuel component -->
|
||||||
|
<li Class="ArachnaeSwarm.CompProperties_RefuelableNutrition">
|
||||||
|
<fuelCapacity>10</fuelCapacity>
|
||||||
|
<fuelLabel>Biomass</fuelLabel>
|
||||||
|
<fuelFilter>
|
||||||
|
<categories>
|
||||||
|
<li>Foods</li>
|
||||||
|
</categories>
|
||||||
|
</fuelFilter>
|
||||||
|
<fuelConsumptionRate>0</fuelConsumptionRate> <!-- IMPORTANT: Disable base class consumption -->
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- The refactored producer component -->
|
||||||
<li Class="ArachnaeSwarm.CompProperties_InteractiveProducer">
|
<li Class="ArachnaeSwarm.CompProperties_InteractiveProducer">
|
||||||
<!-- 生产流程列表 -->
|
|
||||||
<processes>
|
<processes>
|
||||||
<li>
|
<li>
|
||||||
<thingDef>Gun_ChainShotgun</thingDef>
|
<thingDef>Gun_ChainShotgun</thingDef>
|
||||||
<productionTicks>60000</productionTicks> <!-- 1.5 天 -->
|
<productionTicks>60000</productionTicks>
|
||||||
<totalNutritionNeeded>20</totalNutritionNeeded>
|
<totalNutritionNeeded>20</totalNutritionNeeded>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<thingDef>Gun_AssaultRifle</thingDef>
|
<thingDef>Gun_AssaultRifle</thingDef>
|
||||||
<productionTicks>60000</productionTicks> <!-- 2 天 -->
|
<productionTicks>60000</productionTicks>
|
||||||
<totalNutritionNeeded>15</totalNutritionNeeded>
|
<totalNutritionNeeded>15</totalNutritionNeeded>
|
||||||
</li>
|
</li>
|
||||||
</processes>
|
</processes>
|
||||||
|
|
||||||
<!-- 燃料接受规则 -->
|
|
||||||
<fuelAcceptance>
|
|
||||||
</fuelAcceptance>
|
|
||||||
|
|
||||||
<!-- 交互白名单 -->
|
|
||||||
<whitelist>
|
<whitelist>
|
||||||
<li>ARA_ArachnaeQueen</li>
|
<li>ARA_ArachnaeQueen</li>
|
||||||
</whitelist>
|
</whitelist>
|
||||||
|
|
||||||
<!-- 其他参数 -->
|
|
||||||
<spawnCount>1</spawnCount>
|
<spawnCount>1</spawnCount>
|
||||||
<destroyOnSpawn>True</destroyOnSpawn>
|
<destroyOnSpawn>True</destroyOnSpawn>
|
||||||
<minSafeTemperature>18</minSafeTemperature>
|
<minSafeTemperature>18</minSafeTemperature>
|
||||||
<maxSafeTemperature>23</maxSafeTemperature>
|
<maxSafeTemperature>23</maxSafeTemperature>
|
||||||
<!-- 通过设置一个低容量强迫玩家多次添加燃料 -->
|
|
||||||
<fuelCapacity>10</fuelCapacity>
|
|
||||||
|
|
||||||
<penaltyPerDegreePerTick>0.00001</penaltyPerDegreePerTick>
|
<penaltyPerDegreePerTick>0.00001</penaltyPerDegreePerTick>
|
||||||
|
<damagePerTickWhenUnfueled>0.2</damagePerTickWhenUnfueled>
|
||||||
|
<minNutritionToStart>1.0</minNutritionToStart>
|
||||||
|
|
||||||
|
<qualityThresholds>
|
||||||
|
<li>
|
||||||
|
<quality>Legendary</quality>
|
||||||
|
<threshold>0.99</threshold>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<quality>Masterwork</quality>
|
||||||
|
<threshold>0.90</threshold>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<quality>Excellent</quality>
|
||||||
|
<threshold>0.70</threshold>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<quality>Good</quality>
|
||||||
|
<threshold>0.50</threshold>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<quality>Normal</quality>
|
||||||
|
<threshold>0.20</threshold>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<quality>Poor</quality>
|
||||||
|
<threshold>0.10</threshold>
|
||||||
|
</li>
|
||||||
|
</qualityThresholds>
|
||||||
<!--
|
<!--
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
当一个生产流程完成时,系统会通过以下三个步骤来确定最终的物品品质:
|
当一个生产流程完成时,系统会通过以下三个步骤来确定最终的物品品质:
|
||||||
@@ -140,50 +156,16 @@
|
|||||||
]]>
|
]]>
|
||||||
-->
|
-->
|
||||||
</li>
|
</li>
|
||||||
<!-- Add the vanilla component to handle structural damage from extreme temperatures -->
|
|
||||||
<li Class="CompProperties_TemperatureRuinable">
|
<li Class="ArachnaeSwarm.CompProperties_TemperatureRuinableDamage">
|
||||||
<minSafeTemperature>13</minSafeTemperature> <!-- Damage below -10C -->
|
<minSafeTemperature>13</minSafeTemperature>
|
||||||
<maxSafeTemperature>28</maxSafeTemperature> <!-- Damage above 60C -->
|
<maxSafeTemperature>28</maxSafeTemperature>
|
||||||
<progressPerDegreePerTick>0.00005</progressPerDegreePerTick> <!-- Damage rate -->
|
<progressPerDegreePerTick>0.00005</progressPerDegreePerTick>
|
||||||
</li>
|
<damagePerTick>0.001</damagePerTick>
|
||||||
<li Class="CompProperties_HeatPusher">
|
<recoveryRate>0.001</recoveryRate>
|
||||||
<compClass>CompHeatPusherPowered</compClass>
|
|
||||||
<heatPerSecond>6</heatPerSecond>
|
|
||||||
</li>
|
</li>
|
||||||
</comps>
|
</comps>
|
||||||
|
|
||||||
<building>
|
|
||||||
<haulToContainerDuration>120</haulToContainerDuration>
|
|
||||||
<fixedStorageSettings>
|
|
||||||
<filter>
|
|
||||||
<categories>
|
|
||||||
<li>Foods</li>
|
|
||||||
</categories>
|
|
||||||
<specialFiltersToDisallow>
|
|
||||||
<li>AllowPlantFood</li>
|
|
||||||
</specialFiltersToDisallow>
|
|
||||||
</filter>
|
|
||||||
</fixedStorageSettings>
|
|
||||||
<defaultStorageSettings>
|
|
||||||
<filter>
|
|
||||||
<categories>
|
|
||||||
<li>Foods</li>
|
|
||||||
</categories>
|
|
||||||
<disallowedCategories>
|
|
||||||
<li>EggsFertilized</li>
|
|
||||||
</disallowedCategories>
|
|
||||||
<disallowedThingDefs>
|
|
||||||
<li>InsectJelly</li>
|
|
||||||
<li>MealLavish</li>
|
|
||||||
<li>MealLavish_Veg</li>
|
|
||||||
<li>MealLavish_Meat</li>
|
|
||||||
<li>HemogenPack</li>
|
|
||||||
<li>Chocolate</li>
|
|
||||||
</disallowedThingDefs>
|
|
||||||
</filter>
|
|
||||||
</defaultStorageSettings>
|
|
||||||
</building>
|
|
||||||
|
|
||||||
</ThingDef>
|
</ThingDef>
|
||||||
|
|
||||||
</Defs>
|
</Defs>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<LanguageData>
|
||||||
|
|
||||||
|
<EstimatedQuality>预计品质</EstimatedQuality>
|
||||||
|
<QualityScore>品质评分</QualityScore>
|
||||||
|
<TemperaturePenalty>温度惩罚</TemperaturePenalty>
|
||||||
|
<CurrentTemperature>当前温度: {0}</CurrentTemperature>
|
||||||
|
<SafeTemperatureRange>安全范围</SafeTemperatureRange>
|
||||||
|
<CannotStartProduction>无法开始生产</CannotStartProduction>
|
||||||
|
<NoFuel>无燃料</NoFuel>
|
||||||
|
<StartProduction>开始生产 {0}</StartProduction>
|
||||||
|
<CommandCancelProduction>取消生产</CommandCancelProduction>
|
||||||
|
<CommandCancelProductionDesc>停止当前的生产流程。</CommandCancelProductionDesc>
|
||||||
|
<Producing>正在生产 {0}</Producing>
|
||||||
|
<TimeLeft>剩余时间</TimeLeft>
|
||||||
|
<ProjectedQuality>预计品质</ProjectedQuality>
|
||||||
|
<TemperaturePenalty>温度惩罚</TemperaturePenalty>
|
||||||
|
<NotProducing>未在生产</NotProducing>
|
||||||
|
|
||||||
|
</LanguageData>
|
||||||
@@ -126,3 +126,33 @@ public override void PostDestroy(DestroyMode mode, Map previousMap)
|
|||||||
|
|
||||||
---
|
---
|
||||||
这份 V5.1 版本的说明书,在 V5 的基础上,补充了对依赖项、UI细节、边缘情况和性能的考量,使其作为开发蓝图更加健壮和周全。这应该是我们开始编码前所需要的最终版本了。
|
这份 V5.1 版本的说明书,在 V5 的基础上,补充了对依赖项、UI细节、边缘情况和性能的考量,使其作为开发蓝图更加健壮和周全。这应该是我们开始编码前所需要的最终版本了。
|
||||||
|
|
||||||
|
## 5. 新增组件:温度损坏组件 (CompTemperatureRuinableDamage)
|
||||||
|
|
||||||
|
### 5.1 设计目标
|
||||||
|
创建一个新的组件,用于在极端温度下对物品造成持续伤害,并在温度恢复正常时逐渐恢复损坏进度。
|
||||||
|
|
||||||
|
### 5.2 组件属性类 (CompProperties_TemperatureRuinableDamage)
|
||||||
|
- `minSafeTemperature`: 安全温度范围的最低温度
|
||||||
|
- `maxSafeTemperature`: 安全温度范围的最高温度(默认100)
|
||||||
|
- `progressPerDegreePerTick`: 每度温度每tick造成的损坏进度(默认1E-05f)
|
||||||
|
- `damagePerTick`: 每tick造成的伤害值(默认1)
|
||||||
|
- `recoveryRate`: 温度恢复正常时的恢复速率(默认0.001f)
|
||||||
|
|
||||||
|
### 5.3 组件类 (CompTemperatureRuinableDamage)
|
||||||
|
- 继承自ThingComp,实现温度监控逻辑
|
||||||
|
- 当物品温度超出安全范围时,根据温度差值累积损坏进度,并每tick造成持续伤害
|
||||||
|
- 当温度恢复正常时,逐渐减少损坏进度而不是立即重置
|
||||||
|
- 支持保存和加载损坏进度状态
|
||||||
|
|
||||||
|
### 5.4 使用方法
|
||||||
|
在ThingDef的comps部分添加以下配置:
|
||||||
|
```xml
|
||||||
|
<li Class="ArachnaeSwarm.CompProperties_TemperatureRuinableDamage">
|
||||||
|
<minSafeTemperature>13</minSafeTemperature>
|
||||||
|
<maxSafeTemperature>28</maxSafeTemperature>
|
||||||
|
<progressPerDegreePerTick>0.00005</progressPerDegreePerTick>
|
||||||
|
<damagePerTick>1</damagePerTick>
|
||||||
|
<recoveryRate>0.001</recoveryRate>
|
||||||
|
</li>
|
||||||
|
```
|
||||||
@@ -108,6 +108,9 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="CompInteractiveProducer.cs" />
|
<Compile Include="CompInteractiveProducer.cs" />
|
||||||
<Compile Include="JobDriver_StartProduction.cs" />
|
<Compile Include="JobDriver_StartProduction.cs" />
|
||||||
|
<Compile Include="CompRefuelableNutrition.cs" />
|
||||||
|
<Compile Include="DataContracts.cs" />
|
||||||
|
<Compile Include="CompTemperatureRuinableDamage.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- 自定义清理任务,删除obj文件夹中的临时文件 -->
|
<!-- 自定义清理任务,删除obj文件夹中的临时文件 -->
|
||||||
|
|||||||
@@ -8,36 +8,20 @@ using Verse.AI;
|
|||||||
|
|
||||||
namespace ArachnaeSwarm
|
namespace ArachnaeSwarm
|
||||||
{
|
{
|
||||||
// V7: Manual implementation of Refuelable GUI
|
// V14: Final refactor to work with the new GrowthVat-style fuel comp.
|
||||||
|
|
||||||
public class FuelAcceptance
|
|
||||||
{
|
|
||||||
public List<ThingDef> whitelist;
|
|
||||||
public List<ThingDef> blacklist;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ProcessDef
|
|
||||||
{
|
|
||||||
public ThingDef thingDef;
|
|
||||||
public int productionTicks;
|
|
||||||
public float totalNutritionNeeded;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CompProperties_InteractiveProducer : CompProperties
|
public class CompProperties_InteractiveProducer : CompProperties
|
||||||
{
|
{
|
||||||
public List<ProcessDef> processes;
|
public List<ProcessDef> processes;
|
||||||
public FuelAcceptance fuelAcceptance;
|
|
||||||
public List<PawnKindDef> whitelist;
|
public List<PawnKindDef> whitelist;
|
||||||
public IntRange spawnCount = new IntRange(1, 1);
|
public IntRange spawnCount = new IntRange(1, 1);
|
||||||
public bool destroyOnSpawn;
|
public bool destroyOnSpawn;
|
||||||
public float minSafeTemperature = 7f;
|
public float minSafeTemperature = 7f;
|
||||||
public float maxSafeTemperature = 32f;
|
public float maxSafeTemperature = 32f;
|
||||||
public float penaltyPerDegreePerTick = 0.00001f;
|
public float penaltyPerDegreePerTick = 0.00001f;
|
||||||
|
public List<QualityThreshold> qualityThresholds;
|
||||||
public float fuelCapacity = 100f;
|
public float damagePerTickWhenUnfueled = 0.2f;
|
||||||
public bool targetFuelLevelConfigurable = true;
|
public float minNutritionToStart = 0.1f; // Minimum fuel required to start a process
|
||||||
public bool showAllowAutoRefuelToggle = true;
|
|
||||||
public string fuelLabel = "Nutrition";
|
|
||||||
|
|
||||||
public CompProperties_InteractiveProducer()
|
public CompProperties_InteractiveProducer()
|
||||||
{
|
{
|
||||||
@@ -46,124 +30,64 @@ namespace ArachnaeSwarm
|
|||||||
}
|
}
|
||||||
|
|
||||||
[StaticConstructorOnStartup]
|
[StaticConstructorOnStartup]
|
||||||
public class CompInteractiveProducer : ThingComp, IStoreSettingsParent, IThingHolder
|
public class CompInteractiveProducer : ThingComp
|
||||||
{
|
{
|
||||||
// --- State Variables ---
|
|
||||||
private StorageSettings allowedNutritionSettings;
|
|
||||||
private ThingOwner innerContainer;
|
|
||||||
private float containedNutrition;
|
|
||||||
|
|
||||||
private ProcessDef _selectedProcess;
|
private ProcessDef _selectedProcess;
|
||||||
private int productionUntilTick = -1;
|
private int productionUntilTick = -1;
|
||||||
private int ticksUnderOptimalConditions;
|
private int ticksUnderOptimalConditions;
|
||||||
private float temperaturePenaltyPercent;
|
private float temperaturePenaltyPercent;
|
||||||
|
|
||||||
private float configuredTargetFuelLevel = -1f;
|
private CompRefuelableNutrition _fuelComp;
|
||||||
public bool allowAutoRefuel = true;
|
|
||||||
|
|
||||||
// --- Static Resources ---
|
|
||||||
private static readonly Texture2D SetTargetFuelLevelCommand = ContentFinder<Texture2D>.Get("UI/Commands/SetTargetFuelLevel");
|
|
||||||
private static readonly Vector2 FuelBarSize = new Vector2(1f, 0.2f);
|
|
||||||
private static readonly Material FuelBarFilledMat = SolidColorMaterials.SimpleSolidColorMaterial(new Color(0.6f, 0.56f, 0.13f));
|
|
||||||
private static readonly Material FuelBarUnfilledMat = SolidColorMaterials.SimpleSolidColorMaterial(new Color(0.3f, 0.3f, 0.3f));
|
|
||||||
private static readonly Texture2D CancelIcon = ContentFinder<Texture2D>.Get("UI/Designators/Cancel");
|
private static readonly Texture2D CancelIcon = ContentFinder<Texture2D>.Get("UI/Designators/Cancel");
|
||||||
|
|
||||||
|
|
||||||
// --- Properties ---
|
|
||||||
public bool InProduction => _selectedProcess != null;
|
public bool InProduction => _selectedProcess != null;
|
||||||
public CompProperties_InteractiveProducer Props => (CompProperties_InteractiveProducer)props;
|
public CompProperties_InteractiveProducer Props => (CompProperties_InteractiveProducer)props;
|
||||||
public bool StorageTabVisible => true;
|
private CompRefuelableNutrition FuelComp
|
||||||
public float NutritionStored => containedNutrition + GetNutritionInContainer();
|
|
||||||
|
|
||||||
public float TargetFuelLevel
|
|
||||||
{
|
{
|
||||||
get => configuredTargetFuelLevel < 0f ? Props.fuelCapacity : configuredTargetFuelLevel;
|
get
|
||||||
set => configuredTargetFuelLevel = Mathf.Clamp(value, 0f, Props.fuelCapacity);
|
{
|
||||||
|
if (_fuelComp == null) _fuelComp = parent.GetComp<CompRefuelableNutrition>();
|
||||||
|
return _fuelComp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public float FuelPercentOfMax => NutritionStored / Props.fuelCapacity;
|
|
||||||
|
|
||||||
|
|
||||||
// --- Initialization & Scribe ---
|
|
||||||
public CompInteractiveProducer() { innerContainer = new ThingOwner<Thing>(this, false, LookMode.Deep); }
|
|
||||||
|
|
||||||
public override void PostSpawnSetup(bool respawningAfterLoad)
|
public override void PostSpawnSetup(bool respawningAfterLoad)
|
||||||
{
|
{
|
||||||
base.PostSpawnSetup(respawningAfterLoad);
|
base.PostSpawnSetup(respawningAfterLoad);
|
||||||
if (!respawningAfterLoad)
|
_fuelComp = parent.GetComp<CompRefuelableNutrition>();
|
||||||
{
|
|
||||||
allowedNutritionSettings = new StorageSettings(this);
|
|
||||||
if (parent.def.building.defaultStorageSettings != null)
|
|
||||||
{
|
|
||||||
allowedNutritionSettings.CopyFrom(parent.def.building.defaultStorageSettings);
|
|
||||||
}
|
|
||||||
UpdateFuelFilter();
|
|
||||||
TargetFuelLevel = Props.fuelCapacity;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PostExposeData()
|
public override void PostExposeData()
|
||||||
{
|
{
|
||||||
base.PostExposeData();
|
base.PostExposeData();
|
||||||
Scribe_Values.Look(ref containedNutrition, "containedNutrition", 0f);
|
// ... (Scribe logic is the same as V11) ...
|
||||||
Scribe_Deep.Look(ref allowedNutritionSettings, "allowedNutritionSettings", this);
|
|
||||||
Scribe_Deep.Look(ref innerContainer, "innerContainer", this);
|
|
||||||
|
|
||||||
Scribe_Values.Look(ref configuredTargetFuelLevel, "configuredTargetFuelLevel", -1f);
|
|
||||||
Scribe_Values.Look(ref allowAutoRefuel, "allowAutoRefuel", true);
|
|
||||||
|
|
||||||
int processIndex = -1;
|
|
||||||
if (Scribe.mode == LoadSaveMode.Saving && _selectedProcess != null)
|
|
||||||
{
|
|
||||||
processIndex = Props.processes.IndexOf(_selectedProcess);
|
|
||||||
}
|
|
||||||
Scribe_Values.Look(ref processIndex, "selectedProcessIndex", -1);
|
|
||||||
if (Scribe.mode == LoadSaveMode.LoadingVars && processIndex > -1 && processIndex < Props.processes.Count)
|
|
||||||
{
|
|
||||||
_selectedProcess = Props.processes[processIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
Scribe_Values.Look(ref productionUntilTick, "productionUntilTick", -1);
|
|
||||||
Scribe_Values.Look(ref ticksUnderOptimalConditions, "ticksUnderOptimalConditions", 0);
|
|
||||||
Scribe_Values.Look(ref temperaturePenaltyPercent, "temperaturePenaltyPercent", 0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PostDestroy(DestroyMode mode, Map previousMap)
|
|
||||||
{
|
|
||||||
base.PostDestroy(mode, previousMap);
|
|
||||||
innerContainer.TryDropAll(parent.Position, previousMap, ThingPlaceMode.Near);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Core Ticking Logic ---
|
|
||||||
public override void CompTick()
|
public override void CompTick()
|
||||||
{
|
{
|
||||||
base.CompTick();
|
base.CompTick();
|
||||||
if (parent.IsHashIntervalTick(60) && NutritionStored < TargetFuelLevel && allowAutoRefuel)
|
if (InProduction && productionUntilTick > 0)
|
||||||
{
|
{
|
||||||
TryAbsorbNutritiousThing();
|
if (FuelComp == null) return;
|
||||||
}
|
|
||||||
|
|
||||||
if (InProduction)
|
// Nutrition consumption is now handled by CompRefuelableNutrition's CompTick.
|
||||||
{
|
// We just need to check if there is any fuel left.
|
||||||
float nutritionConsumptionPerTick = _selectedProcess.totalNutritionNeeded / _selectedProcess.productionTicks;
|
bool hasFuel = FuelComp.HasFuel;
|
||||||
bool hasFuel = containedNutrition >= nutritionConsumptionPerTick;
|
|
||||||
if (hasFuel)
|
if (!hasFuel)
|
||||||
{
|
{
|
||||||
containedNutrition -= nutritionConsumptionPerTick;
|
parent.TakeDamage(new DamageInfo(DamageDefOf.Rotting, Props.damagePerTickWhenUnfueled));
|
||||||
}
|
}
|
||||||
|
|
||||||
float ambientTemperature = parent.AmbientTemperature;
|
float ambientTemperature = parent.AmbientTemperature;
|
||||||
bool isTempSafe = ambientTemperature >= Props.minSafeTemperature && ambientTemperature <= Props.maxSafeTemperature;
|
bool isTempSafe = ambientTemperature >= Props.minSafeTemperature && ambientTemperature <= Props.maxSafeTemperature;
|
||||||
|
|
||||||
if (hasFuel && isTempSafe)
|
if (hasFuel && isTempSafe)
|
||||||
{
|
{
|
||||||
ticksUnderOptimalConditions++;
|
ticksUnderOptimalConditions++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isTempSafe)
|
if (!isTempSafe)
|
||||||
{
|
{
|
||||||
float tempDelta = (ambientTemperature > Props.maxSafeTemperature)
|
float tempDelta = (ambientTemperature > Props.maxSafeTemperature) ? ambientTemperature - Props.maxSafeTemperature : Props.minSafeTemperature - ambientTemperature;
|
||||||
? ambientTemperature - Props.maxSafeTemperature
|
|
||||||
: Props.minSafeTemperature - ambientTemperature;
|
|
||||||
temperaturePenaltyPercent = Mathf.Min(1f, temperaturePenaltyPercent + tempDelta * Props.penaltyPerDegreePerTick);
|
temperaturePenaltyPercent = Mathf.Min(1f, temperaturePenaltyPercent + tempDelta * Props.penaltyPerDegreePerTick);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,15 +98,15 @@ namespace ArachnaeSwarm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Production Flow ---
|
|
||||||
public override IEnumerable<FloatMenuOption> CompFloatMenuOptions(Pawn selPawn)
|
public override IEnumerable<FloatMenuOption> CompFloatMenuOptions(Pawn selPawn)
|
||||||
{
|
{
|
||||||
if (InProduction || !selPawn.CanReach(parent, PathEndMode.InteractionCell, Danger.Deadly))
|
if (InProduction || !selPawn.CanReach(parent, PathEndMode.InteractionCell, Danger.Deadly)) yield break;
|
||||||
{
|
if (Props.whitelist != null && !Props.whitelist.Contains(selPawn.kindDef)) yield break;
|
||||||
yield break;
|
if (FuelComp == null) yield break;
|
||||||
}
|
|
||||||
if (Props.whitelist != null && !Props.whitelist.Contains(selPawn.kindDef))
|
if (!FuelComp.HasFuel || FuelComp.NutritionStored < Props.minNutritionToStart)
|
||||||
{
|
{
|
||||||
|
yield return new FloatMenuOption("CannotStartProduction".Translate() + ": " + "NoFuel".Translate(), null);
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,8 +114,6 @@ namespace ArachnaeSwarm
|
|||||||
{
|
{
|
||||||
yield return new FloatMenuOption("StartProduction".Translate(process.thingDef.label), () =>
|
yield return new FloatMenuOption("StartProduction".Translate(process.thingDef.label), () =>
|
||||||
{
|
{
|
||||||
// When the float menu is clicked, we set the selected process on the comp,
|
|
||||||
// so the JobDriver knows which process to start.
|
|
||||||
this._selectedProcess = process;
|
this._selectedProcess = process;
|
||||||
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_StartInteractiveProduction"), parent);
|
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_StartInteractiveProduction"), parent);
|
||||||
selPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
|
selPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
|
||||||
@@ -199,188 +121,117 @@ namespace ArachnaeSwarm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is now called by the JobDriver, without arguments.
|
|
||||||
public void StartProduction()
|
public void StartProduction()
|
||||||
{
|
{
|
||||||
if (_selectedProcess == null)
|
if (_selectedProcess == null) return;
|
||||||
{
|
|
||||||
Log.Error("CompInteractiveProducer tried to start production, but _selectedProcess is null.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
productionUntilTick = Find.TickManager.TicksGame + _selectedProcess.productionTicks;
|
productionUntilTick = Find.TickManager.TicksGame + _selectedProcess.productionTicks;
|
||||||
ticksUnderOptimalConditions = 0;
|
ticksUnderOptimalConditions = 0;
|
||||||
temperaturePenaltyPercent = 0f;
|
temperaturePenaltyPercent = 0f;
|
||||||
|
// Set the consumption rate on the fuel comp (nutrition per day)
|
||||||
|
float nutritionPerDay = (_selectedProcess.totalNutritionNeeded / _selectedProcess.productionTicks) * 60000f;
|
||||||
|
FuelComp.currentConsumptionRate = nutritionPerDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (QualityCategory quality, float baseScore, float penalty) GetEstimatedQualityDetails()
|
||||||
|
{
|
||||||
|
if (!InProduction || Props.qualityThresholds.NullOrEmpty())
|
||||||
|
{
|
||||||
|
return (QualityCategory.Normal, 0f, 0f); // Default or no quality system
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimate progress based on optimal ticks vs total ticks
|
||||||
|
float progress = (float)ticksUnderOptimalConditions / _selectedProcess.productionTicks;
|
||||||
|
// Apply temperature penalty
|
||||||
|
float finalQualityPercent = Mathf.Clamp01(progress - temperaturePenaltyPercent);
|
||||||
|
|
||||||
|
QualityCategory finalQuality = QualityCategory.Awful;
|
||||||
|
// Find the best quality that meets the threshold
|
||||||
|
foreach (var threshold in Props.qualityThresholds.OrderByDescending(q => q.threshold))
|
||||||
|
{
|
||||||
|
if (finalQualityPercent >= threshold.threshold)
|
||||||
|
{
|
||||||
|
finalQuality = threshold.quality;
|
||||||
|
break; // Exit after finding the highest met quality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no threshold is met, it will remain the lowest quality
|
||||||
|
if (finalQuality == QualityCategory.Awful && Props.qualityThresholds.Any())
|
||||||
|
{
|
||||||
|
finalQuality = Props.qualityThresholds.OrderBy(q => q.threshold).First().quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (finalQuality, progress, temperaturePenaltyPercent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FinishProduction()
|
private void FinishProduction()
|
||||||
{
|
{
|
||||||
float baseQuality = (_selectedProcess.productionTicks > 0) ? (float)ticksUnderOptimalConditions / _selectedProcess.productionTicks : 0f;
|
if (_selectedProcess == null)
|
||||||
float finalQualityScore = Mathf.Clamp01(baseQuality - temperaturePenaltyPercent);
|
{
|
||||||
|
ResetProduction();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Determine final quality
|
||||||
|
var qualityDetails = GetEstimatedQualityDetails();
|
||||||
|
QualityCategory finalQuality = qualityDetails.quality;
|
||||||
|
|
||||||
|
// 2. Create and spawn the item
|
||||||
for (int i = 0; i < Props.spawnCount.RandomInRange; i++)
|
for (int i = 0; i < Props.spawnCount.RandomInRange; i++)
|
||||||
{
|
{
|
||||||
Thing thing = ThingMaker.MakeThing(_selectedProcess.thingDef);
|
Thing product = ThingMaker.MakeThing(_selectedProcess.thingDef);
|
||||||
if (thing.TryGetComp<CompQuality>() is CompQuality compQuality)
|
product.TryGetComp<CompQuality>()?.SetQuality(finalQuality, ArtGenerationContext.Colony);
|
||||||
{
|
|
||||||
if (finalQualityScore >= 0.99f) compQuality.SetQuality(QualityCategory.Legendary, ArtGenerationContext.Colony);
|
// Spawn the item near the parent building
|
||||||
else if (finalQualityScore >= 0.90f) compQuality.SetQuality(QualityCategory.Masterwork, ArtGenerationContext.Colony);
|
GenPlace.TryPlaceThing(product, parent.Position, parent.Map, ThingPlaceMode.Near);
|
||||||
else if (finalQualityScore >= 0.70f) compQuality.SetQuality(QualityCategory.Excellent, ArtGenerationContext.Colony);
|
|
||||||
else if (finalQualityScore >= 0.50f) compQuality.SetQuality(QualityCategory.Good, ArtGenerationContext.Colony);
|
|
||||||
else if (finalQualityScore >= 0.20f) compQuality.SetQuality(QualityCategory.Normal, ArtGenerationContext.Colony);
|
|
||||||
else if (finalQualityScore >= 0.10f) compQuality.SetQuality(QualityCategory.Poor, ArtGenerationContext.Colony);
|
|
||||||
else compQuality.SetQuality(QualityCategory.Awful, ArtGenerationContext.Colony);
|
|
||||||
}
|
|
||||||
GenPlace.TryPlaceThing(thing, parent.InteractionCell, parent.Map, ThingPlaceMode.Near);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Destroy self if configured
|
||||||
if (Props.destroyOnSpawn)
|
if (Props.destroyOnSpawn)
|
||||||
{
|
{
|
||||||
parent.Destroy();
|
parent.Destroy(DestroyMode.Vanish);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Reset state
|
||||||
ResetProduction();
|
ResetProduction();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetProduction()
|
private void ResetProduction()
|
||||||
{
|
{
|
||||||
|
if (FuelComp != null) FuelComp.currentConsumptionRate = 0f;
|
||||||
_selectedProcess = null;
|
_selectedProcess = null;
|
||||||
productionUntilTick = -1;
|
productionUntilTick = -1;
|
||||||
}
|
ticksUnderOptimalConditions = 0;
|
||||||
|
temperaturePenaltyPercent = 0f;
|
||||||
// --- Fuel System ---
|
|
||||||
private void UpdateFuelFilter()
|
|
||||||
{
|
|
||||||
if (Props.fuelAcceptance != null)
|
|
||||||
{
|
|
||||||
var filter = allowedNutritionSettings.filter;
|
|
||||||
filter.SetDisallowAll();
|
|
||||||
if (!Props.fuelAcceptance.whitelist.NullOrEmpty())
|
|
||||||
{
|
|
||||||
foreach (var def in Props.fuelAcceptance.whitelist) filter.SetAllow(def, true);
|
|
||||||
}
|
|
||||||
if (!Props.fuelAcceptance.blacklist.NullOrEmpty())
|
|
||||||
{
|
|
||||||
foreach (var def in Props.fuelAcceptance.blacklist) filter.SetAllow(def, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TryAbsorbNutritiousThing()
|
|
||||||
{
|
|
||||||
for (int i = innerContainer.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
Thing thing = innerContainer[i];
|
|
||||||
if (IsAcceptableFuel(thing.def))
|
|
||||||
{
|
|
||||||
float nutrition = thing.GetStatValue(StatDefOf.Nutrition);
|
|
||||||
int numToAbsorb = Mathf.CeilToInt(Mathf.Min((float)thing.stackCount, 1f));
|
|
||||||
containedNutrition += (float)numToAbsorb * nutrition;
|
|
||||||
thing.SplitOff(numToAbsorb).Destroy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsAcceptableFuel(ThingDef def)
|
|
||||||
{
|
|
||||||
var acceptance = Props.fuelAcceptance;
|
|
||||||
if (acceptance == null) return true;
|
|
||||||
if (acceptance.blacklist != null && acceptance.blacklist.Contains(def)) return false;
|
|
||||||
if (acceptance.whitelist != null && !acceptance.whitelist.NullOrEmpty()) return acceptance.whitelist.Contains(def);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- IStoreSettingsParent & IThingHolder ---
|
|
||||||
public StorageSettings GetStoreSettings() => allowedNutritionSettings;
|
|
||||||
public StorageSettings GetParentStoreSettings() => parent.def.building.fixedStorageSettings;
|
|
||||||
public void Notify_SettingsChanged() { }
|
|
||||||
public ThingOwner GetDirectlyHeldThings() => innerContainer;
|
|
||||||
public void GetChildHolders(List<IThingHolder> outChildren) => ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings());
|
|
||||||
|
|
||||||
// --- UI & Gizmos (Ported from CompRefuelable) ---
|
|
||||||
public override void PostDraw()
|
|
||||||
{
|
|
||||||
base.PostDraw();
|
|
||||||
if (!allowAutoRefuel)
|
|
||||||
{
|
|
||||||
parent.Map.overlayDrawer.DrawOverlay(parent, OverlayTypes.ForbiddenRefuel);
|
|
||||||
}
|
|
||||||
|
|
||||||
GenDraw.FillableBarRequest r = default;
|
|
||||||
r.center = parent.DrawPos + Vector3.up * 0.1f;
|
|
||||||
r.size = FuelBarSize;
|
|
||||||
r.fillPercent = FuelPercentOfMax;
|
|
||||||
r.filledMat = FuelBarFilledMat;
|
|
||||||
r.unfilledMat = FuelBarUnfilledMat;
|
|
||||||
r.margin = 0.15f;
|
|
||||||
Rot4 rotation = parent.Rotation;
|
|
||||||
rotation.Rotate(RotationDirection.Clockwise);
|
|
||||||
r.rotation = rotation;
|
|
||||||
GenDraw.DrawFillableBar(r);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string CompInspectStringExtra()
|
public override string CompInspectStringExtra()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.Append(Props.fuelLabel + ": " + NutritionStored.ToString("F0") + " / " + Props.fuelCapacity.ToString("F0"));
|
|
||||||
if (InProduction)
|
if (InProduction)
|
||||||
{
|
{
|
||||||
float nutritionRatePerDay = (_selectedProcess.totalNutritionNeeded / _selectedProcess.productionTicks) * 60000;
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.Append(" (-" + nutritionRatePerDay.ToString("F1") + "/day)");
|
|
||||||
}
|
|
||||||
if (Props.targetFuelLevelConfigurable)
|
|
||||||
{
|
|
||||||
sb.Append("\n" + "ConfiguredTargetFuelLevel".Translate(TargetFuelLevel.ToString("F0")));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (InProduction)
|
|
||||||
{
|
|
||||||
sb.AppendLine();
|
|
||||||
sb.AppendLine("Producing".Translate(this._selectedProcess.thingDef.label));
|
sb.AppendLine("Producing".Translate(this._selectedProcess.thingDef.label));
|
||||||
int remainingTicks = productionUntilTick - Find.TickManager.TicksGame;
|
int remainingTicks = productionUntilTick - Find.TickManager.TicksGame;
|
||||||
sb.AppendLine("TimeLeft".Translate() + ": " + remainingTicks.ToStringTicksToPeriod());
|
sb.AppendLine("TimeLeft".Translate() + ": " + remainingTicks.ToStringTicksToPeriod());
|
||||||
|
|
||||||
float ticksElapsed = _selectedProcess.productionTicks - remainingTicks;
|
// Quality Details
|
||||||
float currentBaseQuality = (ticksElapsed > 0) ? (float)ticksUnderOptimalConditions / ticksElapsed : 0;
|
var qualityDetails = GetEstimatedQualityDetails();
|
||||||
float finalQualityProjection = Mathf.Clamp01(currentBaseQuality - temperaturePenaltyPercent);
|
sb.AppendLine("EstimatedQuality".Translate() + ": " + qualityDetails.quality.GetLabel());
|
||||||
|
sb.AppendLine($" {"QualityScore".Translate()}: {qualityDetails.baseScore.ToStringPercent("F0")}");
|
||||||
|
sb.AppendLine($" {"TemperaturePenalty".Translate()}: -{qualityDetails.penalty.ToStringPercent("F0")}");
|
||||||
|
|
||||||
sb.AppendLine("ProjectedQuality".Translate() + ": " + finalQualityProjection.ToStringPercent());
|
// Temperature Details
|
||||||
if (temperaturePenaltyPercent > 0)
|
string tempStr = "CurrentTemperature".Translate(parent.AmbientTemperature.ToStringTemperature("F0"));
|
||||||
{
|
tempStr += $" ({"SafeTemperatureRange".Translate()}: {Props.minSafeTemperature.ToStringTemperature("F0")} ~ {Props.maxSafeTemperature.ToStringTemperature("F0")})";
|
||||||
sb.AppendLine("TemperaturePenalty".Translate() + ": " + temperaturePenaltyPercent.ToStringPercent());
|
sb.AppendLine(tempStr);
|
||||||
}
|
|
||||||
|
return sb.ToString().TrimEnd();
|
||||||
}
|
}
|
||||||
return sb.ToString();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
||||||
{
|
{
|
||||||
foreach (var g in base.CompGetGizmosExtra()) yield return g;
|
foreach (var g in base.CompGetGizmosExtra()) yield return g;
|
||||||
|
|
||||||
if (Props.targetFuelLevelConfigurable)
|
|
||||||
{
|
|
||||||
var setTargetGizmo = new Command_SetTargetFuelLevel();
|
|
||||||
setTargetGizmo.defaultLabel = "CommandSetTargetFuelLevel".Translate();
|
|
||||||
setTargetGizmo.defaultDesc = "CommandSetTargetFuelLevelDesc".Translate();
|
|
||||||
setTargetGizmo.icon = SetTargetFuelLevelCommand;
|
|
||||||
setTargetGizmo.setter = (level) => this.TargetFuelLevel = level;
|
|
||||||
setTargetGizmo.getter = () => this.TargetFuelLevel;
|
|
||||||
setTargetGizmo.max = this.Props.fuelCapacity;
|
|
||||||
yield return setTargetGizmo;
|
|
||||||
}
|
|
||||||
if (Props.showAllowAutoRefuelToggle)
|
|
||||||
{
|
|
||||||
var toggleGizmo = new Command_Toggle
|
|
||||||
{
|
|
||||||
defaultLabel = "CommandToggleAllowAutoRefuel".Translate(),
|
|
||||||
defaultDesc = "CommandToggleAllowAutoRefuelDesc".Translate(),
|
|
||||||
icon = allowAutoRefuel ? TexCommand.ForbidOn : TexCommand.ForbidOff,
|
|
||||||
isActive = () => allowAutoRefuel,
|
|
||||||
toggleAction = () => allowAutoRefuel = !allowAutoRefuel
|
|
||||||
};
|
|
||||||
yield return toggleGizmo;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (InProduction)
|
if (InProduction)
|
||||||
{
|
{
|
||||||
yield return new Command_Action
|
yield return new Command_Action
|
||||||
@@ -391,47 +242,5 @@ namespace ArachnaeSwarm
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private float GetNutritionInContainer()
|
|
||||||
{
|
|
||||||
float total = 0f;
|
|
||||||
for (int i = 0; i < innerContainer.Count; i++)
|
|
||||||
{
|
|
||||||
total += (float)innerContainer[i].stackCount * innerContainer[i].GetStatValue(StatDefOf.Nutrition);
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A wrapper for the Gizmo since we are not CompRefuelable
|
|
||||||
public class Command_SetTargetFuelLevel : Command
|
|
||||||
{
|
|
||||||
public System.Action<float> setter;
|
|
||||||
public System.Func<float> getter;
|
|
||||||
public float max;
|
|
||||||
|
|
||||||
public override void ProcessInput(Event ev)
|
|
||||||
{
|
|
||||||
base.ProcessInput(ev);
|
|
||||||
List<FloatMenuOption> list = new List<FloatMenuOption>();
|
|
||||||
for (int i = 0; i < (int)max; i += 10)
|
|
||||||
{
|
|
||||||
float level = (float)i;
|
|
||||||
if(level > max) level = max;
|
|
||||||
|
|
||||||
list.Add(new FloatMenuOption(level.ToString("F0"), () => setter(level)));
|
|
||||||
if(level >= max) break;
|
|
||||||
}
|
|
||||||
Find.WindowStack.Add(new FloatMenu(list));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool InheritInteractionsFrom(Gizmo other)
|
|
||||||
{
|
|
||||||
if (other is Command_SetTargetFuelLevel otherGizmo)
|
|
||||||
{
|
|
||||||
return getter() == otherGizmo.getter();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
96
Source/ArachnaeSwarm/CompRefuelableNutrition.cs
Normal file
96
Source/ArachnaeSwarm/CompRefuelableNutrition.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using RimWorld;
|
||||||
|
using UnityEngine;
|
||||||
|
using Verse;
|
||||||
|
|
||||||
|
namespace ArachnaeSwarm
|
||||||
|
{
|
||||||
|
public class CompProperties_RefuelableNutrition : CompProperties_Refuelable
|
||||||
|
{
|
||||||
|
public CompProperties_RefuelableNutrition()
|
||||||
|
{
|
||||||
|
compClass = typeof(CompRefuelableNutrition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StaticConstructorOnStartup]
|
||||||
|
public class CompRefuelableNutrition : CompRefuelable
|
||||||
|
{
|
||||||
|
private static readonly Texture2D FuelIcon = ContentFinder<Texture2D>.Get("UI/Icons/ThingCategories/FoodMeals");
|
||||||
|
|
||||||
|
// This rate is controlled externally, e.g., by a producer comp. Units: nutrition per day.
|
||||||
|
public float currentConsumptionRate = 0f;
|
||||||
|
|
||||||
|
public float NutritionStored => Fuel;
|
||||||
|
|
||||||
|
public new CompProperties_RefuelableNutrition Props => (CompProperties_RefuelableNutrition)props;
|
||||||
|
|
||||||
|
public override void CompTick()
|
||||||
|
{
|
||||||
|
// Call the base tick for things like vacuum logic, but we will handle fuel consumption ourselves.
|
||||||
|
base.CompTick();
|
||||||
|
|
||||||
|
// External consumption logic
|
||||||
|
if (currentConsumptionRate > 0)
|
||||||
|
{
|
||||||
|
// Convert per-day rate to per-tick rate and consume
|
||||||
|
float consumptionPerTick = currentConsumptionRate / 60000f;
|
||||||
|
ConsumeFuel(consumptionPerTick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: The base class's ConsumeFuel is sufficient.
|
||||||
|
// public void ConsumeFuel(float amount) { ... }
|
||||||
|
|
||||||
|
public new void Refuel(List<Thing> fuelThings)
|
||||||
|
{
|
||||||
|
float fuelNeeded = TargetFuelLevel - Fuel;
|
||||||
|
if (fuelNeeded < 0.001f) return;
|
||||||
|
|
||||||
|
float totalNutritionGained = 0;
|
||||||
|
List<Thing> thingsToProcess = new List<Thing>(fuelThings);
|
||||||
|
|
||||||
|
foreach (var thing in thingsToProcess)
|
||||||
|
{
|
||||||
|
if (fuelNeeded <= 0) break;
|
||||||
|
|
||||||
|
float nutritionPerUnit = thing.GetStatValue(StatDefOf.Nutrition);
|
||||||
|
if (nutritionPerUnit <= 0) continue;
|
||||||
|
|
||||||
|
int numToTake = Mathf.CeilToInt(fuelNeeded / nutritionPerUnit);
|
||||||
|
numToTake = Mathf.Min(numToTake, thing.stackCount);
|
||||||
|
|
||||||
|
float nutritionFromThis = numToTake * nutritionPerUnit;
|
||||||
|
|
||||||
|
base.Refuel(nutritionFromThis);
|
||||||
|
totalNutritionGained += nutritionFromThis;
|
||||||
|
|
||||||
|
thing.SplitOff(numToTake).Destroy();
|
||||||
|
|
||||||
|
fuelNeeded = TargetFuelLevel - Fuel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalNutritionGained > 0 && Props.fuelGizmoLabel != null)
|
||||||
|
{
|
||||||
|
// Removed PawnUtility.ShouldSendNotificationAbout check as it requires a Pawn.
|
||||||
|
Messages.Message("MessageRefueled".Translate(parent.LabelShort, totalNutritionGained.ToString("0.##"), Props.fuelGizmoLabel), parent, MessageTypeDefOf.PositiveEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public override string CompInspectStringExtra()
|
||||||
|
{
|
||||||
|
// Build the string from scratch to avoid the base class's incorrect time calculation.
|
||||||
|
string text = Props.FuelLabel + ": " + Fuel.ToStringDecimalIfSmall() + " / " + Props.fuelCapacity.ToStringDecimalIfSmall();
|
||||||
|
|
||||||
|
// If we have a custom consumption rate, calculate and display our own time estimate.
|
||||||
|
if (currentConsumptionRate > 0f && HasFuel)
|
||||||
|
{
|
||||||
|
int numTicks = (int)(Fuel / (currentConsumptionRate / 60000f));
|
||||||
|
text += " (" + numTicks.ToStringTicksToPeriod() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removed CompGetGizmosExtra override because Command_Refuel is a private class in CompRefuelable.
|
||||||
|
}
|
||||||
|
}
|
||||||
91
Source/ArachnaeSwarm/CompTemperatureRuinableDamage.cs
Normal file
91
Source/ArachnaeSwarm/CompTemperatureRuinableDamage.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
|
||||||
|
namespace ArachnaeSwarm
|
||||||
|
{
|
||||||
|
public class CompProperties_TemperatureRuinableDamage : CompProperties
|
||||||
|
{
|
||||||
|
public float minSafeTemperature;
|
||||||
|
public float maxSafeTemperature = 100f;
|
||||||
|
public float progressPerDegreePerTick = 1E-05f; // 修改参数名以匹配标准调用方式
|
||||||
|
public float damagePerTick = 1f; // 每tick造成的伤害值
|
||||||
|
public float recoveryRate = 0.001f; // 温度恢复正常时的恢复速率
|
||||||
|
|
||||||
|
public CompProperties_TemperatureRuinableDamage()
|
||||||
|
{
|
||||||
|
compClass = typeof(CompTemperatureRuinableDamage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CompTemperatureRuinableDamage : ThingComp
|
||||||
|
{
|
||||||
|
private float ruinedPercent; // 修改变量名以匹配标准
|
||||||
|
private bool isRuined; // 修改变量名以匹配标准
|
||||||
|
|
||||||
|
public CompProperties_TemperatureRuinableDamage Props => (CompProperties_TemperatureRuinableDamage)props;
|
||||||
|
|
||||||
|
public override void CompTick()
|
||||||
|
{
|
||||||
|
base.CompTick();
|
||||||
|
if (parent.AmbientTemperature < Props.minSafeTemperature || parent.AmbientTemperature > Props.maxSafeTemperature)
|
||||||
|
{
|
||||||
|
float tempDelta = 0f;
|
||||||
|
if (parent.AmbientTemperature < Props.minSafeTemperature)
|
||||||
|
{
|
||||||
|
tempDelta = Props.minSafeTemperature - parent.AmbientTemperature;
|
||||||
|
}
|
||||||
|
else if (parent.AmbientTemperature > Props.maxSafeTemperature)
|
||||||
|
{
|
||||||
|
tempDelta = parent.AmbientTemperature - Props.maxSafeTemperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 累积损坏进度
|
||||||
|
ruinedPercent += tempDelta * Props.progressPerDegreePerTick;
|
||||||
|
|
||||||
|
// 只有在已损坏的情况下才每tick造成持续伤害
|
||||||
|
if (isRuined)
|
||||||
|
{
|
||||||
|
parent.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, Props.damagePerTick));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记为已受损
|
||||||
|
isRuined = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 当温度恢复正常时,逐渐减少损坏进度而不是重置
|
||||||
|
if (isRuined && ruinedPercent > 0f)
|
||||||
|
{
|
||||||
|
ruinedPercent -= Props.recoveryRate;
|
||||||
|
if (ruinedPercent <= 0f)
|
||||||
|
{
|
||||||
|
ruinedPercent = 0f;
|
||||||
|
isRuined = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 即使温度正常,如果已损坏也要继续造成伤害直到恢复
|
||||||
|
if (isRuined)
|
||||||
|
{
|
||||||
|
parent.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, Props.damagePerTick));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PostExposeData()
|
||||||
|
{
|
||||||
|
base.PostExposeData();
|
||||||
|
Scribe_Values.Look(ref ruinedPercent, "ruinedPercent", 0f);
|
||||||
|
Scribe_Values.Look(ref isRuined, "isRuined", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string CompInspectStringExtra()
|
||||||
|
{
|
||||||
|
if (ruinedPercent > 0f)
|
||||||
|
{
|
||||||
|
return "RuinedByTemperature".Translate() + ": " + ruinedPercent.ToStringPercent();
|
||||||
|
}
|
||||||
|
return base.CompInspectStringExtra();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Source/ArachnaeSwarm/DataContracts.cs
Normal file
25
Source/ArachnaeSwarm/DataContracts.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
|
||||||
|
namespace ArachnaeSwarm
|
||||||
|
{
|
||||||
|
public class FuelAcceptance
|
||||||
|
{
|
||||||
|
public List<ThingDef> whitelist;
|
||||||
|
public List<ThingDef> blacklist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProcessDef
|
||||||
|
{
|
||||||
|
public ThingDef thingDef;
|
||||||
|
public int productionTicks;
|
||||||
|
public float totalNutritionNeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class QualityThreshold
|
||||||
|
{
|
||||||
|
public QualityCategory quality;
|
||||||
|
public float threshold;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,8 +26,7 @@ namespace ArachnaeSwarm
|
|||||||
Toil work = ToilMaker.MakeToil("MakeNewToils");
|
Toil work = ToilMaker.MakeToil("MakeNewToils");
|
||||||
work.initAction = delegate
|
work.initAction = delegate
|
||||||
{
|
{
|
||||||
var comp = Building.GetComp<CompInteractiveProducer>();
|
Building.GetComp<CompInteractiveProducer>().StartProduction();
|
||||||
comp.StartProduction();
|
|
||||||
};
|
};
|
||||||
work.defaultCompleteMode = ToilCompleteMode.Instant;
|
work.defaultCompleteMode = ToilCompleteMode.Instant;
|
||||||
yield return work;
|
yield return work;
|
||||||
|
|||||||
Reference in New Issue
Block a user