diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 00fd49b..362b2ce 100644 Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ diff --git a/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml index 503783a..e556a18 100644 --- a/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml +++ b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml @@ -1388,7 +1388,7 @@ ArachnaeSwarm/UI/Abilities/ARA_Suicide_Ability 1 true - 300 + 9900 true false AcidSpray_Warmup @@ -1419,7 +1419,7 @@ 144000 false false - 301 + 9901 Verb_CastAbility false diff --git a/1.6/1.6/Defs/AbilityDefs/Abilities_EggSpew.xml b/1.6/1.6/Defs/AbilityDefs/Abilities_EggSpew.xml index 5d583ce..2eab46a 100644 --- a/1.6/1.6/Defs/AbilityDefs/Abilities_EggSpew.xml +++ b/1.6/1.6/Defs/AbilityDefs/Abilities_EggSpew.xml @@ -229,6 +229,13 @@ 温度要求 true +
  • + ARA_Cocoon_Cloth + 可孵化物品列表 + true + true + true +
  • @@ -245,6 +252,13 @@ 温度要求 true +
  • + ARA_Cocoon_Weapon + 可孵化物品列表 + true + true + true +
  • @@ -262,6 +276,13 @@ 温度要求 true +
  • + ARA_Cocoon_Cloth_1Stage + 可孵化物品列表 + true + true + true +
  • @@ -278,6 +299,13 @@ 温度要求 true +
  • + ARA_Cocoon_Weapon_1Stage + 可孵化物品列表 + true + true + true +
  • @@ -295,10 +323,17 @@ 温度要求 true +
  • + ARA_Cocoon_Cloth_2Stage + 可孵化物品列表 + true + true + true +
  • - ARA_Cocoon_Weapon_2Stage_Ability + ARA_Cocoon_Weapon_2Stage 投放一枚武装器官茧,内含可以孵化一套基础武装器官的营养和遗传物质——参阅茧的超链接,了解其能生产的所有装备的特点。 ArachnaeSwarm/UI/Abilities/ARA_Cocoon_Weapon_2Stage @@ -311,6 +346,13 @@ 温度要求 true +
  • + ARA_Cocoon_Weapon_2Stage + 可孵化物品列表 + true + true + true +
  • diff --git a/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml b/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml index ed830bd..cf5ff6b 100644 --- a/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml +++ b/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml @@ -319,7 +319,7 @@
  • ARA_Cocoon_Cloth_2Stage
  • -
  • ARA_Cocoon_Weapon_2Stage_Ability
  • +
  • ARA_Cocoon_Weapon_2Stage
  • diff --git a/1.6/1.6/Defs/Thing_building/ARA_Building.xml b/1.6/1.6/Defs/Thing_building/ARA_Building.xml index cf27de9..4edde78 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_Building.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_Building.xml @@ -26,7 +26,7 @@ True false - -6 + 0 0.25 0 0 @@ -75,7 +75,7 @@
  • Substructure
  • - -6 + 0 0.25 0 0 @@ -122,7 +122,7 @@ 0 0 - -6 + 0 1000 1500 0 @@ -198,7 +198,7 @@ 1200 0 1000 - -10 + 0 2 10 @@ -687,7 +687,7 @@ 750 5 1.0 - -5 + 0 0.70 5 @@ -760,7 +760,7 @@ 750 10 1.0 - 5 + 1 10
    diff --git a/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml b/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml index 7d58605..162a226 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml @@ -727,7 +727,7 @@
  • InsectJelly
  • - 50 + 20 0 true @@ -757,7 +757,7 @@
  • 200 - 1 + 0.25 2000 1
  • diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Alerts_Keys.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Alerts_Keys.xml index f939b5f..cc265e5 100644 --- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Alerts_Keys.xml +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Alerts_Keys.xml @@ -1,4 +1,4 @@ - + 未连接的虫群工蜂 diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_InteractiveProducer_Keys.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_InteractiveProducer_Keys.xml index 1daa519..e607442 100644 --- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_InteractiveProducer_Keys.xml +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_InteractiveProducer_Keys.xml @@ -1,6 +1,5 @@ - + - 预计品质 品质评分 温度惩罚 @@ -8,17 +7,20 @@ 安全范围 无法开始生产 无燃料 - 开始生产 {0} + 开始生产 {0}({1} 营养) 取消生产 停止当前的生产流程。 正在生产 {0} 剩余时间 预计品质 未在生产 - 未孵化,需要阿拉克涅工艺种交互 + 未孵化,需要 {0} 交互({1}) + 任何阿拉克涅虫族 + {0} 个物品可用 所需营养 孵化 {0} + 茧正在稳定中:{0} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo index bf83840..8b9eb18 100644 Binary files a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo and b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo differ diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json index 9b226ca..0d24df3 100644 --- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json +++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json @@ -3,8 +3,16 @@ "WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\", "Documents": [ { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\abilities\\ara_queenability\\compabilityeffect_needcost.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_queenability\\compabilityeffect_needcost.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\building_comps\\ara_compinteractiveproducer\\compinteractiveproducer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_compinteractiveproducer\\compinteractiveproducer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\hediffs\\moharhediffs\\hediffcomp_spawner.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:hediffs\\moharhediffs\\hediffcomp_spawner.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\abilities\\ara_showtemperaturerange\\compabilityeffect_abilityshowtemperaturerange.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_showtemperaturerange\\compabilityeffect_abilityshowtemperaturerange.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" } ], "DocumentGroupContainers": [ @@ -23,14 +31,40 @@ { "$type": "Document", "DocumentIndex": 0, - "Title": "CompAbilityEffect_NeedCost.cs", - "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_QueenAbility\\CompAbilityEffect_NeedCost.cs", - "RelativeDocumentMoniker": "Abilities\\ARA_QueenAbility\\CompAbilityEffect_NeedCost.cs", - "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_QueenAbility\\CompAbilityEffect_NeedCost.cs", - "RelativeToolTip": "Abilities\\ARA_QueenAbility\\CompAbilityEffect_NeedCost.cs", - "ViewState": "AgIAACUAAAAAAAAAAAAAAEgAAAAAAAAAAAAAAA==", + "Title": "CompInteractiveProducer.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs", + "RelativeDocumentMoniker": "Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs", + "RelativeToolTip": "Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs", + "ViewState": "AgIAAEgAAAAAAAAAAAA4wDcAAABHAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-08T08:23:27.533Z", + "WhenOpened": "2025-10-09T03:57:32.708Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 1, + "Title": "HediffComp_Spawner.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\MoharHediffs\\HediffComp_Spawner.cs", + "RelativeDocumentMoniker": "Hediffs\\MoharHediffs\\HediffComp_Spawner.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\MoharHediffs\\HediffComp_Spawner.cs", + "RelativeToolTip": "Hediffs\\MoharHediffs\\HediffComp_Spawner.cs", + "ViewState": "AgIAAAQAAAAAAAAAAAAswBIAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2025-10-09T03:45:03.599Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 2, + "Title": "CompAbilityEffect_AbilityShowTemperatureRange.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs", + "RelativeDocumentMoniker": "Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs", + "RelativeToolTip": "Abilities\\ARA_ShowTemperatureRange\\CompAbilityEffect_AbilityShowTemperatureRange.cs", + "ViewState": "AgIAAC8AAAAAAAAAAAAUwAYAAAAXAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2025-10-09T02:18:27.532Z", "EditorCaption": "" } ] diff --git a/Source/ArachnaeSwarm/Abilities/ARA_ShowInteractiveThing/CompAbilityEffect_ShowInteractiveThing.cs b/Source/ArachnaeSwarm/Abilities/ARA_ShowInteractiveThing/CompAbilityEffect_ShowInteractiveThing.cs new file mode 100644 index 0000000..6789039 --- /dev/null +++ b/Source/ArachnaeSwarm/Abilities/ARA_ShowInteractiveThing/CompAbilityEffect_ShowInteractiveThing.cs @@ -0,0 +1,214 @@ +using RimWorld; +using Verse; +using System.Text; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace ArachnaeSwarm +{ + public class CompAbilityEffect_ShowInteractiveThing : CompAbilityEffect + { + public new CompProperties_AbilityShowInteractiveThing Props => (CompProperties_AbilityShowInteractiveThing)props; + + public override void Apply(LocalTargetInfo target, LocalTargetInfo dest) + { + // 这个组件只用于显示信息,不执行实际效果 + } + + public override bool Valid(LocalTargetInfo target, bool throwMessages = false) + { + return true; + } + + // 重写工具提示方法,显示可孵化的物品列表 + public override string ExtraTooltipPart() + { + StringBuilder stringBuilder = new StringBuilder(); + + // 获取所有可孵化的物品 + var incubatableItems = GetIncubatableItems(); + if (incubatableItems.Count == 0) + { + return null; // 没有可显示的内容 + } + + stringBuilder.AppendLine(Props.customLabel + ":"); + stringBuilder.AppendLine(); + + // 显示每个可孵化的物品 + foreach (var item in incubatableItems) + { + string entryText = $" • {item.thingDef.LabelCap}"; + + // 显示营养需求 + if (Props.showNutritionCost) + { + float nutritionCost = GetIncubationCost(item.thingDef); + entryText += $" ({nutritionCost.ToString("F1")} 营养)"; + } + + // 显示孵化时间 + if (Props.showIncubationTime) + { + int incubationTicks = GetIncubationTimeTicks(item.thingDef); + entryText += $" [{incubationTicks.ToStringTicksToPeriod()}]"; + } + + // 显示科技需求 + if (Props.showResearchRequirements && item.requiredResearch != null) + { + bool researched = item.requiredResearch.IsFinished; + string researchStatus = researched ? "✓" : "✗"; + entryText += $" [{researchStatus}{item.requiredResearch.LabelCap}]"; + } + + stringBuilder.AppendLine(entryText); + } + + return stringBuilder.ToString().TrimEndNewlines(); + } + + // 获取所有可孵化的物品(基于 CompExtraIncubationInfo) + private List GetIncubatableItems() + { + var result = new List(); + + if (Props.cocoonBuildingDef == null) + { + Log.Error("CompAbilityEffect_ShowInteractiveThing: cocoonBuildingDef is null"); + return result; + } + + // 扫描所有定义了 CompExtraIncubationInfo 的物品 + foreach (ThingDef thingDef in DefDatabase.AllDefs) + { + var incubationCompProps = thingDef.GetCompProperties(); + if (incubationCompProps != null) + { + bool isMatch = false; + if (!incubationCompProps.cocoonDefs.NullOrEmpty()) + { + isMatch = incubationCompProps.cocoonDefs.Contains(Props.cocoonBuildingDef); + } + else if (incubationCompProps.cocoonDef != null) + { + isMatch = incubationCompProps.cocoonDef == Props.cocoonBuildingDef; + } + + if (isMatch) + { + // 获取研究前提 + ResearchProjectDef researchPrerequisite = GetResearchPrerequisite(thingDef); + + // 创建 ProcessDef(与 CompInteractiveProducer 中的结构一致) + ProcessDef process = new ProcessDef + { + thingDef = thingDef, + productionTicks = GetIncubationTimeTicks(thingDef), + totalNutritionNeeded = GetIncubationCost(thingDef), + requiredResearch = researchPrerequisite + }; + + result.Add(process); + } + } + } + + // 按物品名称排序 + result.SortBy(p => p.thingDef.label); + return result; + } + + // 获取研究前提(与 CompInteractiveProducer 中的逻辑一致) + private ResearchProjectDef GetResearchPrerequisite(ThingDef thingDef) + { + ResearchProjectDef researchPrerequisite = null; + + // 方法1:从 recipeMaker.researchPrerequisite 获取 + if (thingDef.recipeMaker?.researchPrerequisite != null) + { + researchPrerequisite = thingDef.recipeMaker.researchPrerequisite; + } + // 方法2:从 recipeMaker.researchPrerequisites 获取第一个 + else if (thingDef.recipeMaker?.researchPrerequisites?.Count > 0) + { + researchPrerequisite = thingDef.recipeMaker.researchPrerequisites[0]; + } + // 方法3:从 thingDef.researchPrerequisites 获取(备用) + else if (thingDef.researchPrerequisites?.Count > 0) + { + researchPrerequisite = thingDef.researchPrerequisites[0]; + } + + return researchPrerequisite; + } + + // 获取孵化时间(与 CompInteractiveProducer 中的逻辑一致) + private int GetIncubationTimeTicks(ThingDef thingDef) + { + StatDef incubationTimeStat = DefDatabase.GetNamedSilentFail("ARA_IncubationTime"); + if (incubationTimeStat != null && thingDef.statBases != null) + { + var statValue = thingDef.statBases.FirstOrDefault(s => s.stat == incubationTimeStat); + if (statValue != null) + { + // ARA_IncubationTime 是以天为单位,转换为 ticks (1天 = 60000 ticks) + return Mathf.RoundToInt(statValue.value * 60000f); + } + } + + // 默认值:1 天 + return 60000; + } + + // 获取孵化所需营养(与 CompInteractiveProducer 中的逻辑一致) + private float GetIncubationCost(ThingDef thingDef) + { + StatDef incubationCostStat = DefDatabase.GetNamedSilentFail("ARA_IncubationCost"); + if (incubationCostStat != null && thingDef.statBases != null) + { + var statValue = thingDef.statBases.FirstOrDefault(s => s.stat == incubationCostStat); + if (statValue != null) + { + return statValue.value; + } + } + + // 默认值:10 营养 + return 10f; + } + + // 获取所有可孵化的物品种类(用于其他用途) + public List GetIncubatableThingDefs() + { + var incubatableItems = GetIncubatableItems(); + var result = new List(); + + foreach (var item in incubatableItems) + { + if (item.thingDef != null && (item.requiredResearch == null || item.requiredResearch.IsFinished)) + { + result.Add(item.thingDef); + } + } + + return result; + } + + // 检查特定物品是否可孵化 + public bool CanIncubateThingDef(ThingDef thingDef) + { + var incubatableItems = GetIncubatableItems(); + foreach (var item in incubatableItems) + { + if (item.thingDef == thingDef && + (item.requiredResearch == null || item.requiredResearch.IsFinished)) + { + return true; + } + } + return false; + } + } +} diff --git a/Source/ArachnaeSwarm/Abilities/ARA_ShowInteractiveThing/CompProperties_AbilityShowInteractiveThing.cs b/Source/ArachnaeSwarm/Abilities/ARA_ShowInteractiveThing/CompProperties_AbilityShowInteractiveThing.cs new file mode 100644 index 0000000..4e3bc52 --- /dev/null +++ b/Source/ArachnaeSwarm/Abilities/ARA_ShowInteractiveThing/CompProperties_AbilityShowInteractiveThing.cs @@ -0,0 +1,19 @@ +using RimWorld; +using Verse; + +namespace ArachnaeSwarm +{ + public class CompProperties_AbilityShowInteractiveThing : CompProperties_AbilityEffect + { + public ThingDef cocoonBuildingDef; // 指向的茧建筑定义 + public string customLabel = "可孵化物品"; // 自定义标签 + public bool showResearchRequirements = true; // 是否显示科技需求 + public bool showNutritionCost = true; // 是否显示营养需求 + public bool showIncubationTime = true; // 是否显示孵化时间 + + public CompProperties_AbilityShowInteractiveThing() + { + this.compClass = typeof(CompAbilityEffect_ShowInteractiveThing); + } + } +} diff --git a/Source/ArachnaeSwarm/Abilities/ARA_ShowTemperatureRange/CompAbilityEffect_AbilityShowTemperatureRange.cs b/Source/ArachnaeSwarm/Abilities/ARA_ShowTemperatureRange/CompAbilityEffect_AbilityShowTemperatureRange.cs index 7854896..e7c7bd0 100644 --- a/Source/ArachnaeSwarm/Abilities/ARA_ShowTemperatureRange/CompAbilityEffect_AbilityShowTemperatureRange.cs +++ b/Source/ArachnaeSwarm/Abilities/ARA_ShowTemperatureRange/CompAbilityEffect_AbilityShowTemperatureRange.cs @@ -2,6 +2,7 @@ using RimWorld; using Verse; using System.Text; using System.Collections.Generic; +using UnityEngine; namespace ArachnaeSwarm { @@ -19,7 +20,7 @@ namespace ArachnaeSwarm return true; } - // Override tooltip method to display temperature range information + // Override tooltip method to display temperature range information with progress bar public override string ExtraTooltipPart() { StringBuilder stringBuilder = new StringBuilder(); @@ -31,23 +32,84 @@ namespace ArachnaeSwarm return null; // No content to display } + // 第一行:安全温度范围 stringBuilder.AppendLine(Props.customLabel.Translate() + ":"); - stringBuilder.AppendLine(); + + // 第二行:当前温度 + float currentTemp = parent.pawn != null && parent.pawn.Map != null ? + GenTemperature.GetTemperatureForCell(parent.pawn.Position, parent.pawn.Map) : 0f; + stringBuilder.AppendLine($" 当前温度: {currentTemp:0.0}°C"); + + // 第三行和第四行:温度标签和进度条 + var (tempLabels, progressBar) = GetTemperatureProgressBar(currentTemp, tempCompProps.minSafeTemperature, tempCompProps.maxSafeTemperature); + stringBuilder.AppendLine(tempLabels); + stringBuilder.AppendLine(progressBar); + + // 第五行:温度状态 + string tempStatus = GetTemperatureStatus(currentTemp, tempCompProps.minSafeTemperature, tempCompProps.maxSafeTemperature); + string statusColor = GetTemperatureStatusColor(currentTemp, tempCompProps.minSafeTemperature, tempCompProps.maxSafeTemperature); + stringBuilder.AppendLine($" {tempStatus}"); - // Display temperature range - stringBuilder.AppendLine(" " + "AbilityCheckSafeTemperatureRange".Translate(tempCompProps.minSafeTemperature, tempCompProps.maxSafeTemperature)); - - // Display current temperature (if enabled) - if (Props.showCurrentTemperature && parent.pawn != null && parent.pawn.Map != null) - { - float currentTemp = GenTemperature.GetTemperatureForCell(parent.pawn.Position, parent.pawn.Map); - string tempStatus = GetTemperatureStatus(currentTemp, tempCompProps.minSafeTemperature, tempCompProps.maxSafeTemperature); - stringBuilder.AppendLine(); - stringBuilder.AppendLine("AbilityCheckCurrentTemperature".Translate(currentTemp.ToString("F1"), tempStatus)); - } return stringBuilder.ToString().TrimEndNewlines(); } + // Get temperature progress bar display + private (string tempLabels, string progressBar) GetTemperatureProgressBar(float currentTemp, float minSafe, float maxSafe) + { + const int barLength = 20; + + // Use only the safe temperature range for display + float displayMin = minSafe; + float displayMax = maxSafe; + + // Calculate current position in the bar + float normalizedPos = Mathf.Clamp01((currentTemp - displayMin) / (displayMax - displayMin)); + int currentPos = Mathf.RoundToInt(normalizedPos * (barLength - 1)); + + // Build temperature labels line (第三行) + string minTempLabel = $"{displayMin:0}°C"; + string maxTempLabel = $"{displayMax:0}°C"; + + // Calculate spacing to align labels with progress bar + int totalSpaces = barLength + 2 - minTempLabel.Length - maxTempLabel.Length; // +2 for brackets + string spacing = new string(' ', Mathf.Max(1, totalSpaces)); + + string tempLabels = $" {minTempLabel}{spacing}{maxTempLabel}"; + + // Build progress bar (第四行) + StringBuilder bar = new StringBuilder(); + bar.Append(" ["); + + for (int i = 0; i < barLength; i++) + { + if (i == currentPos) + { + // Current temperature indicator - color based on position + string markerColor = GetMarkerColor(currentTemp, minSafe, maxSafe); + bar.Append($"|"); + } + else + { + // Regular segment - always green since we're only showing the safe range + bar.Append("─"); + } + } + bar.Append("]"); + + return (tempLabels, bar.ToString()); + } + + // Get color for the current temperature marker + private string GetMarkerColor(float currentTemp, float minSafe, float maxSafe) + { + if (currentTemp < minSafe) + return "blue"; // Below minimum - blue + else if (currentTemp > maxSafe) + return "red"; // Above maximum - red + else + return "green"; // Within safe range - green + } + // Get temperature status description private string GetTemperatureStatus(float currentTemp, float minSafe, float maxSafe) { @@ -59,6 +121,17 @@ namespace ArachnaeSwarm return "AbilityCheckTemperatureSafe".Translate(); } + // Get color for temperature status + private string GetTemperatureStatusColor(float currentTemp, float minSafe, float maxSafe) + { + if (currentTemp < minSafe) + return "blue"; + else if (currentTemp > maxSafe) + return "red"; + else + return "green"; + } + // Get temperature control component from building definition private CompProperties_TemperatureRuinableDamage GetTemperatureCompFromBuilding() { @@ -106,4 +179,4 @@ namespace ArachnaeSwarm return currentTemp >= tempComp.minSafeTemperature && currentTemp <= tempComp.maxSafeTemperature; } } -} \ No newline at end of file +} diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index 30f7c49..2702294 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -95,6 +95,8 @@ + + diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompInteractiveProducer.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompInteractiveProducer.cs index 981aa4e..34b4cb3 100644 --- a/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompInteractiveProducer.cs +++ b/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompInteractiveProducer.cs @@ -35,13 +35,28 @@ namespace ArachnaeSwarm private int ticksUnderOptimalConditions; private float temperaturePenaltyPercent; private List _cachedProcesses; + private int spawnTick = -1; // 新增:记录生成时间 + private const int COOLDOWN_TICKS = 120; // 新增:120 tick冷却时间 private CompRefuelableNutrition _fuelComp; private static readonly Texture2D CancelIcon = ContentFinder.Get("UI/Designators/Cancel"); public bool InProduction => _selectedProcess != null; + public bool InCooldown => spawnTick > 0 && Find.TickManager.TicksGame < spawnTick + COOLDOWN_TICKS; // 新增:冷却状态检查 public CompProperties_InteractiveProducer Props => (CompProperties_InteractiveProducer)props; + // 生产进度(0-1) + public float ProductionProgress + { + get + { + if (!InProduction || productionUntilTick <= 0) return 0f; + int totalTicks = _selectedProcess.productionTicks; + int elapsedTicks = totalTicks - (productionUntilTick - Find.TickManager.TicksGame); + return Mathf.Clamp01((float)elapsedTicks / totalTicks); + } + } + // 自动生成的 ProcessDef 列表 public List Processes { @@ -69,6 +84,12 @@ namespace ArachnaeSwarm base.PostSpawnSetup(respawningAfterLoad); _fuelComp = parent.GetComp(); BuildProcessList(); // 确保进程列表在生成时构建 + + // 新增:记录生成时间(仅在首次生成时,不是加载后) + if (!respawningAfterLoad) + { + spawnTick = Find.TickManager.TicksGame; + } } public override void PostExposeData() @@ -82,6 +103,7 @@ namespace ArachnaeSwarm Scribe_Values.Look(ref productionUntilTick, "productionUntilTick", -1); Scribe_Values.Look(ref ticksUnderOptimalConditions, "ticksUnderOptimalConditions", 0); Scribe_Values.Look(ref temperaturePenaltyPercent, "temperaturePenaltyPercent", 0f); + Scribe_Values.Look(ref spawnTick, "spawnTick", -1); // 新增:序列化生成时间 // 加载时重建 selectedProcess if (Scribe.mode == LoadSaveMode.LoadingVars && selectedProcessThingDef != null) @@ -261,30 +283,41 @@ namespace ArachnaeSwarm } } } - + public override IEnumerable CompFloatMenuOptions(Pawn selPawn) { + // 新增:冷却期内不提供任何交互选项 + if (InCooldown) + { + int remainingTicks = (spawnTick + COOLDOWN_TICKS) - Find.TickManager.TicksGame; + yield return new FloatMenuOption($"ARA_CocoonCooldown".Translate(remainingTicks.ToStringTicksToPeriod()), null); + yield break; + } if (InProduction || !selPawn.CanReach(parent, PathEndMode.InteractionCell, Danger.Deadly)) yield break; if (Props.whitelist != null && !Props.whitelist.Contains(selPawn.kindDef)) yield break; if (FuelComp == null) yield break; - if (!FuelComp.HasFuel || FuelComp.NutritionStored < Props.minNutritionToStart) { yield return new FloatMenuOption("CannotStartProduction".Translate() + ": " + "NoFuel".Translate(), null); yield break; } - // 使用自动生成的 Processes 列表 foreach (var process in Processes) { + // 获取营养需求 - 直接从 ProcessDef 中读取 + float nutritionCost = process.totalNutritionNeeded; + if (process.requiredResearch != null && !process.requiredResearch.IsFinished) { - string disabledText = "StartProduction".Translate(process.thingDef.label) + " (" + "Requires".Translate() + ": " + process.requiredResearch.label + ")"; + // 修改:在未研究完成的情况下也显示营养需求 + string disabledText = "StartProduction".Translate(process.thingDef.label, nutritionCost.ToString("F1")) + + " (" + "Requires".Translate() + ": " + process.requiredResearch.label + ")"; yield return new FloatMenuOption(disabledText, null); } else { - yield return new FloatMenuOption("StartProduction".Translate(process.thingDef.label), () => + // 修改:使用新的翻译键显示营养需求 + yield return new FloatMenuOption("StartProduction".Translate(process.thingDef.label, nutritionCost.ToString("F1")), () => { this._selectedProcess = process; Job job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_StartInteractiveProduction"), parent); @@ -296,6 +329,13 @@ namespace ArachnaeSwarm public void StartProduction() { + // 新增:冷却期内不允许开始生产 + if (InCooldown) + { + Log.Warning("Attempted to start production during cooldown period."); + return; + } + if (_selectedProcess == null) return; productionUntilTick = Find.TickManager.TicksGame + _selectedProcess.productionTicks; ticksUnderOptimalConditions = 0; @@ -377,38 +417,173 @@ namespace ArachnaeSwarm temperaturePenaltyPercent = 0f; } + // 新增:绘制进度条的方法 + private string GetProgressBar(float progress, int barLength = 20) + { + int filledLength = Mathf.RoundToInt(progress * barLength); + int emptyLength = barLength - filledLength; + + StringBuilder bar = new StringBuilder(); + bar.Append("["); + for (int i = 0; i < filledLength; i++) + { + bar.Append("="); + } + for (int i = 0; i < emptyLength; i++) + { + bar.Append("-"); + } + bar.Append("]"); + + return bar.ToString(); + } + + // 新增:绘制质量进度条的方法 + private string GetQualityProgressBar(float baseScore, float penalty, int barLength = 20) + { + int baseLength = Mathf.RoundToInt(baseScore * barLength); + int penaltyLength = Mathf.RoundToInt(penalty * barLength); + int actualLength = Mathf.Max(0, baseLength - penaltyLength); + int penaltyStart = actualLength; + int emptyLength = barLength - baseLength; + + StringBuilder bar = new StringBuilder(); + bar.Append("["); + + // 实际质量部分(绿色) + for (int i = 0; i < actualLength; i++) + { + bar.Append("="); + } + + // 温度惩罚部分(黄色) + for (int i = penaltyStart; i < baseLength; i++) + { + bar.Append("-"); + } + + // 剩余部分(灰色) + for (int i = baseLength; i < barLength; i++) + { + bar.Append("."); + } + + bar.Append("]"); + + return bar.ToString(); + } + public override string CompInspectStringExtra() { + // 新增:显示冷却信息 + if (InCooldown) + { + int remainingTicks = (spawnTick + COOLDOWN_TICKS) - Find.TickManager.TicksGame; + return "ARA_CocoonCooldown".Translate(remainingTicks.ToStringTicksToPeriod()); + } + if (InProduction) { StringBuilder sb = new StringBuilder(); sb.AppendLine("Producing".Translate(this._selectedProcess.thingDef.label)); + + // 生产进度条 + float progress = ProductionProgress; int remainingTicks = productionUntilTick - Find.TickManager.TicksGame; + sb.AppendLine("Progress".Translate() + ": " + GetProgressBar(progress) + " " + progress.ToStringPercent("F0")); sb.AppendLine("TimeLeft".Translate() + ": " + remainingTicks.ToStringTicksToPeriod()); - + + // 质量进度条 var qualityDetails = GetEstimatedQualityDetails(); 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("QualityProgress".Translate() + ": " + + GetQualityProgressBar(qualityDetails.baseScore, qualityDetails.penalty) + " " + + (qualityDetails.baseScore - qualityDetails.penalty).ToStringPercent("F0")); + + // 温度信息 string tempStr = "CurrentTemperature".Translate(parent.AmbientTemperature.ToStringTemperature("F0")); tempStr += $" ({"SafeTemperatureRange".Translate()}: {Props.minSafeTemperature.ToStringTemperature("F0")} ~ {Props.maxSafeTemperature.ToStringTemperature("F0")})"; sb.AppendLine(tempStr); - + return sb.ToString().TrimEnd(); } + if (!InProduction) { // 显示可生产的物品数量 int availableProcesses = Processes.Count(p => p.requiredResearch == null || p.requiredResearch.IsFinished); - return "ARA_NeedArachnaeToStartIncubation".Translate() + $" ({availableProcesses} items available)"; + + // 构建可交互的种族名称列表 + string allowedRaces = ""; + if (Props.whitelist != null && Props.whitelist.Count > 0) + { + // 获取所有可交互种族的显示名称 + var raceNames = Props.whitelist.Select(pkd => pkd.LabelCap).Distinct(); + allowedRaces = string.Join(", ", raceNames); + } + else + { + allowedRaces = "ARA_AnyArachnaeRace".Translate(); // 如果没有白名单,显示"任何阿拉克涅虫族" + } + + // 使用新的翻译键,包含种族信息和物品数量 + return "ARA_NeedSpecificArachnaeToStartIncubation".Translate(allowedRaces, availableProcesses); } return null; } + // 新增:在物体上方绘制进度条 + public override void PostDraw() + { + base.PostDraw(); + + if (InProduction) + { + // 在物体上方绘制生产进度条 + Vector3 drawPos = parent.DrawPos; + drawPos.y += 0.15f; // 稍微抬高一点 + + float progress = ProductionProgress; + GenDraw.DrawFillableBar(new GenDraw.FillableBarRequest + { + center = drawPos, + size = new Vector2(1f, 0.15f), + fillPercent = progress, + filledMat = SolidColorMaterials.SimpleSolidColorMaterial(Color.green), + unfilledMat = SolidColorMaterials.SimpleSolidColorMaterial(Color.gray), + margin = 0.1f, + rotation = Rot4.North + }); + + // 在进度条上方绘制质量进度条 + if (Props.qualityThresholds != null && Props.qualityThresholds.Count > 0) + { + var qualityDetails = GetEstimatedQualityDetails(); + float qualityProgress = Mathf.Clamp01(qualityDetails.baseScore - qualityDetails.penalty); + + drawPos.y += 0.2f; + + GenDraw.DrawFillableBar(new GenDraw.FillableBarRequest + { + center = drawPos, + size = new Vector2(1f, 0.1f), + fillPercent = qualityProgress, + filledMat = SolidColorMaterials.SimpleSolidColorMaterial(Color.blue), + unfilledMat = SolidColorMaterials.SimpleSolidColorMaterial(Color.gray), + margin = 0.1f, + rotation = Rot4.North + }); + } + } + } + public override IEnumerable CompGetGizmosExtra() { foreach (var g in base.CompGetGizmosExtra()) yield return g; + + // 新增:冷却期内不显示任何Gizmo + if (InCooldown) yield break; + if (InProduction) { yield return new Command_Action @@ -426,6 +601,19 @@ namespace ArachnaeSwarm defaultLabel = "Debug: Force Finish", action = () => FinishProduction() }; + + // 调试命令:显示详细进度信息 + yield return new Command_Action + { + defaultLabel = "Debug: Show Progress Info", + action = () => + { + float progress = ProductionProgress; + var qualityDetails = GetEstimatedQualityDetails(); + Messages.Message($"Progress: {progress:P0}\nBase Score: {qualityDetails.baseScore:P0}\nPenalty: {qualityDetails.penalty:P0}\nFinal: {qualityDetails.quality}", + MessageTypeDefOf.SilentInput); + } + }; } } } diff --git a/Source/ArachnaeSwarm/Hediffs/MoharHediffs/HediffComp_Spawner.cs b/Source/ArachnaeSwarm/Hediffs/MoharHediffs/HediffComp_Spawner.cs index 87a1de1..4ee91a8 100644 --- a/Source/ArachnaeSwarm/Hediffs/MoharHediffs/HediffComp_Spawner.cs +++ b/Source/ArachnaeSwarm/Hediffs/MoharHediffs/HediffComp_Spawner.cs @@ -350,24 +350,28 @@ namespace ArachnaeSwarm.MoharHediffs public bool TryDoSpawn() { Pawn pawn = this.parent.pawn; - if (this.Props.spawnMaxAdjacent > 0 && pawn.Map.mapPawns.AllPawns.Where(delegate(Pawn mP) + + if (this.Props.animalThing) { - ThingDef defToCompare = this.Props.animalThing ? this.Props.animalToSpawn?.race : this.Props.thingToSpawn; - if (defToCompare?.race == null) + // 动物生成逻辑保持不变 + if (this.Props.spawnMaxAdjacent > 0 && pawn.Map.mapPawns.AllPawns.Where(delegate(Pawn mP) + { + ThingDef defToCompare = this.Props.animalThing ? this.Props.animalToSpawn?.race : this.Props.thingToSpawn; + if (defToCompare?.race == null) + { + return false; + } + return mP.def == defToCompare && mP.Position.InHorDistOf(pawn.Position, (float)this.Props.spawnMaxAdjacent); + }).Count() >= this.Props.spawnMaxAdjacent) { return false; } - return mP.def == defToCompare && mP.Position.InHorDistOf(pawn.Position, (float)this.Props.spawnMaxAdjacent); - }).Count() >= this.Props.spawnMaxAdjacent) - { - return false; - } - if (this.Props.animalThing) - { + if (this.Props.animalToSpawn == null) { return false; } + Faction faction = this.Props.factionOfPlayerAnimal ? Faction.OfPlayer : null; int i = 0; while (i < this.calculatedQuantity) @@ -401,27 +405,53 @@ namespace ArachnaeSwarm.MoharHediffs } else { - IntVec3 intVec2; - if (!this.TryFindSpawnCell(out intVec2)) - { - return false; - } + // 修改:物品直接添加到pawn的物品栏中 Thing thing = ThingMaker.MakeThing(this.Props.thingToSpawn, null); if (thing == null) { return false; } thing.stackCount = this.calculatedQuantity; - if (this.Props.spawnForbidden) + + // 检查pawn是否有物品栏 + if (pawn.inventory == null) { - thing.SetForbidden(true, true); + Tools.Warn($"Pawn {pawn.Label} does not have an inventory to receive spawned items", this.myDebug); + return false; } - GenPlace.TryPlaceThing(thing, intVec2, pawn.Map, ThingPlaceMode.Direct, null, null, default(Rot4)); - if (PawnUtility.ShouldSendNotificationAbout(pawn)) + + // 尝试将物品添加到pawn的物品栏 + if (pawn.inventory.innerContainer.TryAdd(thing)) { - Messages.Message(this.Props.spawnVerb.Translate(pawn.Named("PAWN"), thing.Named("THING")), thing, MessageTypeDefOf.PositiveEvent, true); + if (PawnUtility.ShouldSendNotificationAbout(pawn)) + { + Messages.Message(this.Props.spawnVerb.Translate(pawn.Named("PAWN"), thing.Named("THING")), pawn, MessageTypeDefOf.PositiveEvent, true); + } + return true; + } + else + { + // 如果物品栏添加失败,回退到原来的地面生成方式 + Tools.Warn($"Failed to add {thing.Label} to {pawn.Label}'s inventory, falling back to ground spawn", this.myDebug); + + IntVec3 intVec2; + if (!this.TryFindSpawnCell(out intVec2)) + { + return false; + } + + if (this.Props.spawnForbidden) + { + thing.SetForbidden(true, true); + } + GenPlace.TryPlaceThing(thing, intVec2, pawn.Map, ThingPlaceMode.Direct, null, null, default(Rot4)); + + if (PawnUtility.ShouldSendNotificationAbout(pawn)) + { + Messages.Message(this.Props.spawnVerb.Translate(pawn.Named("PAWN"), thing.Named("THING")), thing, MessageTypeDefOf.PositiveEvent, true); + } + return true; } - return true; } } @@ -442,19 +472,19 @@ namespace ArachnaeSwarm.MoharHediffs } else { - // ޸뾶5ٵ2λøpawn + // 修改这里:将半径从5减少到2,让生成位置更靠近pawn int searchRadius = 2; - // ȳpawnڵԪɣ뾶Ϊ1 + // 首先尝试在pawn的相邻单元格生成(半径为1) result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, 1, null); - // ڵԪҲλãٳԶһ㣨뾶Ϊ2 + // 如果相邻单元格找不到合适位置,再尝试稍远一点(半径为2) if (!result.IsValid) { result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, searchRadius, null); } - // ҲpawnǰλãΪֶΣ + // 如果还是找不到,尝试pawn当前位置(作为最后手段) if (!result.IsValid && this.pawn.Position.IsValid && this.pawn.Position.Walkable(map)) { result = this.pawn.Position; @@ -580,4 +610,4 @@ namespace ArachnaeSwarm.MoharHediffs private readonly int errorSpawnCount = 750; } -} \ No newline at end of file +}