diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 903bfd1..ff0850c 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/Assemblies/ArachnaeSwarm.pdb b/1.6/1.6/Assemblies/ArachnaeSwarm.pdb index e19bbea..527b991 100644 Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.pdb and b/1.6/1.6/Assemblies/ArachnaeSwarm.pdb differ diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Building_Ootheca.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Building_Ootheca.xml index 9547b6b..2811907 100644 --- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Building_Ootheca.xml +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ARA_Building_Ootheca.xml @@ -227,4 +227,22 @@ 等待营养液... 已消耗 + + + 选择孵化目标... + 品质: + 进度: + 点击上方选择目标 + 需要完成研究 + 幼虫激活中... + 幼虫赶路中... + + 【孵化中】 + 目标: {0} + 进度: {0} + 品质: {0}{1} + 剩余: {0} + 【就绪】 + 孵化时间: {0} + 【未选择目标】\n点击标题选择孵化目标 diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompQueuedInteractiveProducerWithFlux.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompQueuedInteractiveProducerWithFlux.cs index e176a06..8df50e8 100644 --- a/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompQueuedInteractiveProducerWithFlux.cs +++ b/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompQueuedInteractiveProducerWithFlux.cs @@ -67,7 +67,7 @@ namespace ArachnaeSwarm } } - public class CompQueuedInteractiveProducerWithFlux : ThingComp, IFluxController, ILarvaActivatable + public class CompQueuedInteractiveProducerWithFlux : ThingComp, IFluxController, ILarvaActivatable, IOrderGizmoProvider { // === 通量系统字段 === private float neutronFlux = 0.5f; @@ -79,8 +79,10 @@ namespace ArachnaeSwarm // === 幼虫管理 === private List assignedLarvae = new List(); - // === UI 状态(供 Gizmo 使用,不保存) === - public float GizmoScrollPosition = 0f; + // === IOrderGizmoProvider 实现 === + public float GizmoScrollPosition { get; set; } = 0f; + public int QueueLimit => Props.productionQueueLimit; + void IOrderGizmoProvider.ShowOrderMenu() => ShowOrderMenuPublic(); // === 组件引用 === private CompRefuelableNutrition _fuelComp; @@ -160,18 +162,19 @@ namespace ArachnaeSwarm public void RemoveOrderByIndex(int index) { if (index >= 0 && index < orders.Count) orders.RemoveAt(index); } // === Gizmo 用的订单信息 === - public List GetOrdersForGizmo() + public List GetOrdersForGizmo() { - var result = new List(); + var result = new List(); foreach (var order in orders) { - result.Add(new OrderDisplayInfo + result.Add(new PawnOrderDisplayInfo { label = order.process?.thingDef?.LabelCap ?? "?", status = order.status, - productionProgress = GetProgress(order), + progress = GetProgress(order), qualityProgress = order.QualityPercent, - estimatedQuality = GetQualityCategory(order).GetLabel() + estimatedQuality = GetQualityCategory(order).GetLabel(), + remainingTime = "" }); } return result; @@ -394,7 +397,7 @@ namespace ArachnaeSwarm public override IEnumerable CompGetGizmosExtra() { yield return new Gizmo_NeutronFlux(this) { Order = -150f }; - yield return new Gizmo_DualProgressBar(this) { Order = -140f }; + yield return new Gizmo_PawnProgressBar(this) { Order = -140f }; if (orders.Count < Props.productionQueueLimit) { diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_SpawnPawnFromList/CompQueuedPawnSpawnerWithFlux.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_SpawnPawnFromList/CompQueuedPawnSpawnerWithFlux.cs index d7c0b78..88eca79 100644 --- a/Source/ArachnaeSwarm/Building_Comps/ARA_SpawnPawnFromList/CompQueuedPawnSpawnerWithFlux.cs +++ b/Source/ArachnaeSwarm/Building_Comps/ARA_SpawnPawnFromList/CompQueuedPawnSpawnerWithFlux.cs @@ -80,7 +80,7 @@ namespace ArachnaeSwarm } } - public class CompQueuedPawnSpawnerWithFlux : ThingComp, IFluxController, ILarvaActivatable + public class CompQueuedPawnSpawnerWithFlux : ThingComp, IFluxController, ILarvaActivatable, IOrderGizmoProvider { // === 通量系统字段 === private float neutronFlux = 0.5f; @@ -93,7 +93,7 @@ namespace ArachnaeSwarm private List assignedLarvae = new List(); // === UI 状态(供 Gizmo 使用,不保存) === - public float GizmoScrollPosition = 0f; + public float GizmoScrollPosition { get; set; } = 0f; public string LarvaStatusText = null; // 幼虫状态文本 // === 组件引用 === @@ -161,6 +161,10 @@ namespace ArachnaeSwarm public int WaitingForLarvaCount => orders.Count(o => o.status == OrderStatus.WaitingForLarva); public int IncubatingCount => orders.Count(o => o.status == OrderStatus.Incubating); + + // === IOrderGizmoProvider 实现 === + public int QueueLimit => Props.productionQueueLimit; + void IOrderGizmoProvider.ShowOrderMenu() => ShowOrderMenuPublic(); // === Gizmo 用的订单信息 === public List GetOrdersForGizmo() diff --git a/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Building_EquipmentOotheca.cs b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Building_EquipmentOotheca.cs index b48af4d..e64913d 100644 --- a/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Building_EquipmentOotheca.cs +++ b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Building_EquipmentOotheca.cs @@ -8,7 +8,7 @@ using System; namespace ArachnaeSwarm { - public class Building_EquipmentOotheca : Building, IFluxController, ILarvaActivatable + public class Building_EquipmentOotheca : Building, IFluxController, ILarvaActivatable, IOrderGizmoProvider { // === 通量系统字段 === private float neutronFlux = 0.5f; @@ -85,6 +85,84 @@ namespace ArachnaeSwarm public float QualityMultiplier => qualityMultiplier; + // === IOrderGizmoProvider 实现 === + private float gizmoScrollPosition = 0f; + public float GizmoScrollPosition { get => gizmoScrollPosition; set => gizmoScrollPosition = value; } + public int QueueLimit => 1; // 单孵化只能有1个 + + public List GetOrdersForGizmo() + { + var list = new List(); + var config = EquipmentIncubatorData?.SelectedConfig; + + if (isIncubating && incubatingThingDef != null) + { + list.Add(new PawnOrderDisplayInfo + { + label = incubatingThingDef.LabelCap, + progress = IncubationProgress, + qualityProgress = QualityPercent, + estimatedQuality = QualityPercent > 0.8f ? "优秀" : QualityPercent > 0.5f ? "良好" : "普通", + remainingTime = incubationDuration > incubationProgress ? + ((int)(incubationDuration - incubationProgress)).ToStringTicksToPeriod() : "", + status = OrderStatus.Incubating + }); + } + else if (assignedLarva != null) + { + // 幼虫正在前来 + string targetLabel = config?.thingDef?.LabelCap ?? "ARA_Menu_SelectProductionTarget".Translate(); + list.Add(new PawnOrderDisplayInfo + { + label = targetLabel, + progress = 0f, + qualityProgress = 0f, + estimatedQuality = "", + remainingTime = "", + status = OrderStatus.WaitingForLarva + }); + } + else if (config != null) + { + // 已选目标,等待就绪 + list.Add(new PawnOrderDisplayInfo + { + label = config.thingDef.LabelCap, + progress = 0f, + qualityProgress = 0f, + estimatedQuality = "", + remainingTime = config.IsResearchComplete ? "ARA_Status_Ready".Translate() : "ARA_Status_NeedResearch".Translate(), + status = OrderStatus.WaitingForLarva + }); + } + + return list; + } + + public void ShowOrderMenu() => EquipmentIncubatorData?.ShowFloatMenu(); + + public void RemoveOrderByIndex(int index) + { + if (index != 0) return; + + // 取消孵化中的订单 + if (isIncubating) + { + CancelIncubation(); + } + // 释放幼虫 + else if (assignedLarva != null) + { + assignedLarva = null; + larvaOperateTicksRemaining = 0; + } + // 清除已选目标 + else if (EquipmentIncubatorData?.SelectedConfig != null) + { + EquipmentIncubatorData.SwitchToConfig(-1); // 清除选择 + } + } + private void InitializeNutrientInfo() { } // 清理完毕 // === Tick方法(带活性系统)=== @@ -413,7 +491,7 @@ namespace ArachnaeSwarm if (Faction == Faction.OfPlayer) { - yield return new Gizmo_EquipmentIncubationProgress(this); + yield return new Gizmo_PawnProgressBar(this); yield return new Gizmo_NeutronFlux(this); var config = EquipmentIncubatorData?.SelectedConfig; diff --git a/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Gizmo_EquipmentIncubationProgress.cs b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Gizmo_EquipmentIncubationProgress.cs deleted file mode 100644 index c9a571b..0000000 --- a/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Gizmo_EquipmentIncubationProgress.cs +++ /dev/null @@ -1,241 +0,0 @@ -// File: Gizmo_EquipmentIncubationProgress.cs -// 双向进度条 Gizmo - 品质向左,进度向右(装备孵化版) -using System.Collections.Generic; -using RimWorld; -using UnityEngine; -using Verse; - -namespace ArachnaeSwarm -{ - [StaticConstructorOnStartup] - public class Gizmo_EquipmentIncubationProgress : Gizmo - { - private readonly Building_EquipmentOotheca building; - - // 尺寸常量 - private const float Width = 180f; - private const float BarHeight = 18f; - private const float Spacing = 4f; - private const float Padding = 8f; - - // 进度条材质 - private static readonly Texture2D IncubationBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.7f, 0.2f, 0.8f)); - private static readonly Texture2D QualityBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.5f, 0.9f, 0.8f)); - private static readonly Texture2D EmptyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.1f, 0.1f, 0.1f, 0.5f)); - - public Gizmo_EquipmentIncubationProgress(Building_EquipmentOotheca building) - { - this.building = building; - Order = -140f; - } - - public override float GetWidth(float maxWidth) - { - return Mathf.Min(Width, maxWidth); - } - - public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms) - { - // 主矩形区域 (75px高度) - Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), 75f); - Widgets.DrawWindowBackground(rect); - - Rect innerRect = rect.ContractedBy(Padding); - float curY = innerRect.y; - - // 状态判断 - bool isIncubating = building.isIncubating; - bool hasLarva = building.assignedLarva != null; - var config = building.EquipmentIncubatorData?.SelectedConfig; - - // === 标题行(可点击切换目标) === - Text.Font = GameFont.Small; - Rect titleRect = new Rect(innerRect.x, curY, innerRect.width, Text.LineHeight); - - string title; - if (isIncubating && building.incubatingThingDef != null) - title = building.incubatingThingDef.LabelCap; - else if (config != null) - title = config.thingDef.LabelCap; - else - title = "ARA_Menu_SelectProductionTarget".Translate(); - - // 标题按钮(只有非孵化状态可点击) - bool canSwitch = !isIncubating && !hasLarva && building.EquipmentIncubatorData?.IncubationConfigs?.Count > 0; - if (canSwitch) - { - if (Mouse.IsOver(titleRect)) - { - Widgets.DrawHighlight(titleRect); - } - - if (Widgets.ButtonInvisible(titleRect)) - { - ShowTargetSwitchMenu(); - } - - GUI.color = new Color(0.7f, 0.9f, 1f); - Widgets.Label(titleRect, title.Truncate(titleRect.width - 20f) + " ▼"); - GUI.color = Color.white; - } - else - { - Widgets.Label(titleRect, title.Truncate(titleRect.width)); - } - curY += Text.LineHeight + Spacing; - - // === 双向进度条区域 === - if (isIncubating) - { - // 标签行:品质进度: :孵化进度 - Text.Font = GameFont.Tiny; - Rect labelRect = new Rect(innerRect.x, curY, innerRect.width, 14f); - - GUI.color = new Color(0.6f, 0.7f, 0.9f); - Text.Anchor = TextAnchor.MiddleLeft; - Widgets.Label(new Rect(labelRect.x, labelRect.y, labelRect.width / 2f, labelRect.height), - "ARA_Label_Quality".Translate()); - - GUI.color = new Color(0.6f, 0.9f, 0.6f); - Text.Anchor = TextAnchor.MiddleRight; - Widgets.Label(new Rect(labelRect.x + labelRect.width / 2f, labelRect.y, labelRect.width / 2f, labelRect.height), - "ARA_Label_Progress".Translate()); - - Text.Anchor = TextAnchor.UpperLeft; - GUI.color = Color.white; - curY += 14f; - - // 双向进度条 - Rect barRect = new Rect(innerRect.x, curY, innerRect.width, BarHeight); - float midX = barRect.x + barRect.width / 2f; - float halfWidth = barRect.width / 2f; - - // 背景 - GUI.DrawTexture(barRect, EmptyBarTex); - - // 品质进度(向左) - float qualityWidth = halfWidth * building.QualityPercent; - Rect qualityRect = new Rect(midX - qualityWidth, barRect.y, qualityWidth, barRect.height); - GUI.DrawTexture(qualityRect, QualityBarTex); - - // 孵化进度(向右) - float progressWidth = halfWidth * building.IncubationProgress; - Rect progressRect = new Rect(midX, barRect.y, progressWidth, barRect.height); - GUI.DrawTexture(progressRect, IncubationBarTex); - - // 中线 - Widgets.DrawLineVertical(midX, barRect.y, barRect.height); - - // 进度条边框 - Widgets.DrawBox(barRect, 1); - - // Tooltip - string tooltip = GetTooltip(); - TooltipHandler.TipRegion(barRect, tooltip); - } - else if (hasLarva) - { - Text.Font = GameFont.Tiny; - GUI.color = new Color(0.9f, 0.9f, 0.5f); - string statusText = building.larvaOperateTicksRemaining > 0 - ? "ARA_Status_LarvaActivating".Translate() - : "ARA_Status_LarvaOnTheWay".Translate(); - Widgets.Label(new Rect(innerRect.x, curY, innerRect.width, 30f), statusText); - GUI.color = Color.white; - } - else - { - // 无目标或等待状态 - Text.Font = GameFont.Tiny; - GUI.color = new Color(0.7f, 0.7f, 0.7f); - string statusText; - if (config == null) - statusText = "ARA_Status_SelectTarget".Translate(); - else if (!config.IsResearchComplete) - statusText = "ARA_Status_NeedResearch".Translate(); - else - statusText = "ARA_Status_Ready".Translate(); - Widgets.Label(new Rect(innerRect.x, curY, innerRect.width, 20f), statusText); - GUI.color = Color.white; - } - - // 工具提示(整体) - if (Mouse.IsOver(rect) && !isIncubating) - { - Widgets.DrawHighlight(rect); - } - - Text.Font = GameFont.Small; - return new GizmoResult(GizmoState.Clear); - } - - private void ShowTargetSwitchMenu() - { - var configs = building.EquipmentIncubatorData?.IncubationConfigs; - if (configs == null || configs.Count == 0) return; - - List options = new List(); - - for (int i = 0; i < configs.Count; i++) - { - var cfg = configs[i]; - int index = i; - - Texture2D icon = cfg.thingDef?.uiIcon; - string label = cfg.thingDef.LabelCap; - - if (cfg.requiredResearch != null && !cfg.requiredResearch.IsFinished) - { - label += $" ({cfg.requiredResearch.LabelCap})"; - options.Add(new FloatMenuOption(label, null, icon, Color.white)); - } - else - { - label += $" ({cfg.DaysRequired}" + "ARA_Days".Translate() + ")"; - options.Add(new FloatMenuOption(label, () => - { - building.EquipmentIncubatorData.SwitchToConfig(index); - }, icon, Color.white)); - } - } - - Find.WindowStack.Add(new FloatMenu(options)); - } - - private string GetTooltip() - { - var sb = new System.Text.StringBuilder(); - bool isIncubating = building.isIncubating; - - if (isIncubating && building.incubatingThingDef != null) - { - sb.AppendLine("ARA_Tooltip_Incubating".Translate()); - sb.AppendLine("ARA_Tooltip_Target".Translate(building.incubatingThingDef.LabelCap)); - sb.AppendLine("ARA_Tooltip_Progress".Translate(building.IncubationProgress.ToStringPercent())); - sb.AppendLine("ARA_Tooltip_Quality".Translate(building.QualityPercent.ToStringPercent(), "")); - - float remaining = building.incubationDuration - building.incubationProgress; - if (remaining > 0) - { - sb.AppendLine("ARA_Tooltip_Remaining".Translate(((int)remaining).ToStringTicksToPeriod())); - } - } - else - { - var config = building.EquipmentIncubatorData?.SelectedConfig; - if (config != null) - { - sb.AppendLine("ARA_Tooltip_Ready".Translate()); - sb.AppendLine("ARA_Tooltip_Target".Translate(config.thingDef.LabelCap)); - sb.AppendLine("ARA_Tooltip_Duration".Translate(config.DaysRequired + " " + "ARA_Days".Translate())); - } - else - { - sb.AppendLine("ARA_Tooltip_NoTarget".Translate()); - } - } - - return sb.ToString().TrimEndNewlines(); - } - } -} diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs index d98e7ea..fa99108 100644 --- a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs +++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs @@ -8,7 +8,7 @@ using Verse.AI; namespace ArachnaeSwarm { - public class Building_Ootheca : Building, IFluxController, ILarvaActivatable + public class Building_Ootheca : Building, IFluxController, ILarvaActivatable, IOrderGizmoProvider { // === 通量系统字段 === private float neutronFlux = 0.5f; @@ -86,6 +86,84 @@ namespace ArachnaeSwarm public float QualityPercent => qualityTotal > 0 ? qualityProgress / qualityTotal : 0f; public float AdjustedProgressPercent => incubationDuration > 0 ? incubationProgress / incubationDuration : 0f; + // === IOrderGizmoProvider 实现 === + private float gizmoScrollPosition = 0f; + public float GizmoScrollPosition { get => gizmoScrollPosition; set => gizmoScrollPosition = value; } + public int QueueLimit => 1; // 单孵化只能有1个 + + public List GetOrdersForGizmo() + { + var list = new List(); + var config = IncubatorData?.SelectedConfig; + + if (isIncubating && incubatingPawnKind != null) + { + list.Add(new PawnOrderDisplayInfo + { + label = incubatingPawnKind.LabelCap, + progress = AdjustedProgressPercent, + qualityProgress = QualityPercent, + estimatedQuality = QualityPercent > 0.8f ? "优秀" : QualityPercent > 0.5f ? "良好" : "普通", + remainingTime = GetRemainingDays() > 0 ? GetRemainingDays().ToString("F1") + " " + "ARA_Days".Translate() : "", + status = OrderStatus.Incubating + }); + } + else if (assignedLarva != null) + { + // 幼虫正在前来 + string targetLabel = config?.pawnKind?.LabelCap ?? "ARA_Gizmo_SelectIncubationTarget".Translate(); + list.Add(new PawnOrderDisplayInfo + { + label = targetLabel, + progress = 0f, + qualityProgress = 0f, + estimatedQuality = "", + remainingTime = "", + status = OrderStatus.WaitingForLarva + }); + } + else if (config != null) + { + // 已选目标,等待就绪(研究完成则显示就绪状态) + list.Add(new PawnOrderDisplayInfo + { + label = config.pawnKind.LabelCap, + progress = 0f, + qualityProgress = 0f, + estimatedQuality = "", + remainingTime = config.IsResearchComplete ? "ARA_Status_Ready".Translate() : "ARA_Status_NeedResearch".Translate(), + status = OrderStatus.WaitingForLarva + }); + } + // 如果没有选择目标,返回空列表(Gizmo 会显示"选择目标"状态) + + return list; + } + + public void ShowOrderMenu() => IncubatorData?.ShowFloatMenu(); + + public void RemoveOrderByIndex(int index) + { + if (index != 0) return; + + // 取消孵化中的订单 + if (isIncubating) + { + CancelIncubation(); + } + // 释放幼虫 + else if (assignedLarva != null) + { + assignedLarva = null; + larvaOperateTicksRemaining = 0; + } + // 清除已选目标 + else if (IncubatorData?.SelectedConfig != null) + { + IncubatorData.SwitchToConfig(-1); // 清除选择 + } + } + // === 描述方法 === public string GetSpeedFactorsDescription() { @@ -428,7 +506,7 @@ namespace ArachnaeSwarm if (Faction == Faction.OfPlayer) { - yield return new Gizmo_IncubationProgress(this); + yield return new Gizmo_PawnProgressBar(this); yield return new Gizmo_NeutronFlux(this); var config = IncubatorData?.SelectedConfig; diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_DualProgressBar.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_DualProgressBar.cs deleted file mode 100644 index 1cccc06..0000000 --- a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_DualProgressBar.cs +++ /dev/null @@ -1,210 +0,0 @@ -using RimWorld; -using System.Collections.Generic; -using UnityEngine; -using Verse; - -namespace ArachnaeSwarm -{ - /// - /// 双向进度条 Gizmo - 用于物品孵化池 - /// 样式与旧版 Gizmo_IncubationProgress 统一 - /// - [StaticConstructorOnStartup] - public class Gizmo_DualProgressBar : Gizmo - { - private const float Width = 200f; - private const float BarHeight = 18f; - private const float Spacing = 4f; - private const float Padding = 8f; - private const float MaxVisibleOrders = 3; - - private static readonly Texture2D ProgressBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.7f, 0.2f, 0.8f)); - private static readonly Texture2D QualityBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.5f, 0.9f, 0.8f)); - private static readonly Texture2D EmptyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.1f, 0.1f, 0.1f, 0.5f)); - - private readonly CompQueuedInteractiveProducerWithFlux comp; - // scrollPosition 现在存储在 comp 中以在刷新间保持 - - public Gizmo_DualProgressBar(CompQueuedInteractiveProducerWithFlux comp) - { - this.comp = comp; - this.Order = -140f; - } - - public override float GetWidth(float maxWidth) => Mathf.Min(Width, maxWidth); - - public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms) - { - var orders = comp.GetOrdersForGizmo(); - int orderCount = orders.Count; - - // 计算高度 - int visibleCount = Mathf.Min(orderCount, (int)MaxVisibleOrders); - float contentHeight = Padding * 2 + Text.LineHeight + Spacing; - contentHeight += Mathf.Max(1, visibleCount) * (BarHeight + Spacing + 14f); - float totalHeight = Mathf.Max(75f, contentHeight); - - Rect rect = new Rect(topLeft.x, topLeft.y - (totalHeight - 75f), GetWidth(maxWidth), totalHeight); - Widgets.DrawWindowBackground(rect); - - Rect innerRect = rect.ContractedBy(Padding); - float curY = innerRect.y; - - // === 标题区域(可点击打开菜单) === - Text.Font = GameFont.Small; - Rect titleRect = new Rect(innerRect.x, curY, innerRect.width, Text.LineHeight); - - string title; - bool canAdd = orderCount < comp.Props.productionQueueLimit; - - if (orderCount > 0) - { - title = orders[0].label; - if (orderCount > 1) title += $" (+{orderCount - 1})"; - } - else - { - title = "ARA_Menu_SelectProductionTarget".Translate(); - } - - if (canAdd) - { - if (Mouse.IsOver(titleRect)) - { - Widgets.DrawHighlight(titleRect); - } - - if (Widgets.ButtonInvisible(titleRect)) - { - comp.ShowOrderMenuPublic(); - } - - GUI.color = new Color(0.7f, 0.9f, 1f); - Widgets.Label(titleRect, title.Truncate(titleRect.width - 20f) + " ▼"); - GUI.color = Color.white; - } - else - { - GUI.color = new Color(0.5f, 0.5f, 0.5f); - Widgets.Label(titleRect, title.Truncate(titleRect.width) + " " + "ARA_Gizmo_QueueFull".Translate()); - GUI.color = Color.white; - } - curY += Text.LineHeight + Spacing; - - // === 订单列表 === - if (orderCount > 0) - { - float itemHeight = BarHeight + Spacing + 14f; - float listHeight = Mathf.Min(visibleCount, orderCount) * itemHeight; - float totalContentHeight = orderCount * itemHeight; - bool needsScrollbar = orderCount > MaxVisibleOrders; - - float scrollbarWidth = needsScrollbar ? 12f : 0f; - Rect listRect = new Rect(innerRect.x, curY, innerRect.width - scrollbarWidth, listHeight); - - // 滚动条 - if (needsScrollbar) - { - Rect scrollbarRect = new Rect(innerRect.xMax - scrollbarWidth, curY, scrollbarWidth, listHeight); - float scrollMax = totalContentHeight - listHeight; - comp.GizmoScrollPosition = GUI.VerticalScrollbar(scrollbarRect, comp.GizmoScrollPosition, listHeight, 0f, totalContentHeight); - - if (Mouse.IsOver(listRect)) - { - comp.GizmoScrollPosition -= Event.current.delta.y * 1.5f; - comp.GizmoScrollPosition = Mathf.Clamp(comp.GizmoScrollPosition, 0f, scrollMax); - } - } - - GUI.BeginClip(listRect); - float drawY = -comp.GizmoScrollPosition; - - for (int i = 0; i < orderCount; i++) - { - var order = orders[i]; - Rect itemRect = new Rect(0, drawY, listRect.width, itemHeight - Spacing); - - if (itemRect.yMax > 0 && itemRect.y < listRect.height) - { - DrawOrderItem(itemRect, order, i); - } - drawY += itemHeight; - } - - GUI.EndClip(); - } - else - { - Text.Font = GameFont.Tiny; - GUI.color = new Color(0.7f, 0.7f, 0.7f); - Widgets.Label(new Rect(innerRect.x, curY, innerRect.width, 20f), "ARA_Status_Ready".Translate()); - GUI.color = Color.white; - } - - Text.Font = GameFont.Small; - return new GizmoResult(GizmoState.Clear); - } - - private void DrawOrderItem(Rect rect, OrderDisplayInfo order, int index) - { - float labelHeight = 14f; - - Text.Font = GameFont.Tiny; - Rect labelRect = new Rect(rect.x, rect.y, rect.width - 20f, labelHeight); - - if (order.status == OrderStatus.WaitingForLarva) - { - GUI.color = new Color(1f, 0.8f, 0.4f); - Widgets.Label(labelRect, $"{order.label} [" + "ARA_Status_WaitingForLarva".Translate() + "]"); - } - else - { - GUI.color = Color.white; - Widgets.Label(labelRect, $"{order.label} {order.productionProgress.ToStringPercent("F0")}"); - } - GUI.color = Color.white; - - Rect cancelRect = new Rect(rect.xMax - 16f, rect.y, 16f, labelHeight); - if (Widgets.ButtonText(cancelRect, "×", false)) - { - comp.RemoveOrderByIndex(index); - } - - Rect barRect = new Rect(rect.x, rect.y + labelHeight, rect.width, BarHeight); - - if (order.status == OrderStatus.Incubating) - { - float midX = barRect.x + barRect.width / 2f; - float halfWidth = barRect.width / 2f; - - GUI.DrawTexture(barRect, EmptyBarTex); - - float qualityWidth = halfWidth * order.qualityProgress; - Rect qualityRect = new Rect(midX - qualityWidth, barRect.y, qualityWidth, barRect.height); - GUI.DrawTexture(qualityRect, QualityBarTex); - - float progressWidth = halfWidth * order.productionProgress; - Rect progressRect = new Rect(midX, barRect.y, progressWidth, barRect.height); - GUI.DrawTexture(progressRect, ProgressBarTex); - - Widgets.DrawLineVertical(midX, barRect.y, barRect.height); - - // Tooltip - string tooltip = $"{order.label}\n" + - "ARA_Tooltip_Quality".Translate(order.qualityProgress.ToStringPercent(), order.estimatedQuality) + "\n" + - "ARA_Tooltip_Progress".Translate(order.productionProgress.ToStringPercent()); - TooltipHandler.TipRegion(barRect, tooltip); - } - else - { - GUI.DrawTexture(barRect, EmptyBarTex); - Text.Font = GameFont.Tiny; - Text.Anchor = TextAnchor.MiddleCenter; - GUI.color = new Color(0.8f, 0.6f, 0.2f); - Widgets.Label(barRect, "ARA_Status_WaitingForLarva".Translate()); - GUI.color = Color.white; - Text.Anchor = TextAnchor.UpperLeft; - } - } - } -} diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_IncubationProgress.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_IncubationProgress.cs deleted file mode 100644 index f6b9459..0000000 --- a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_IncubationProgress.cs +++ /dev/null @@ -1,239 +0,0 @@ -// File: Gizmo_IncubationProgress.cs -// 双向进度条 Gizmo - 品质向左,进度向右 -using System.Collections.Generic; -using RimWorld; -using UnityEngine; -using Verse; - -namespace ArachnaeSwarm -{ - [StaticConstructorOnStartup] - public class Gizmo_IncubationProgress : Gizmo - { - private readonly Building_Ootheca ootheca; - - // 尺寸常量 - private const float Width = 180f; - private const float BarHeight = 18f; - private const float Spacing = 4f; - private const float Padding = 8f; - - // 进度条材质 - private static readonly Texture2D IncubationBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.7f, 0.2f, 0.8f)); - private static readonly Texture2D QualityBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.5f, 0.9f, 0.8f)); - private static readonly Texture2D EmptyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.1f, 0.1f, 0.1f, 0.5f)); - - public Gizmo_IncubationProgress(Building_Ootheca ootheca) - { - this.ootheca = ootheca; - Order = -140f; - } - - public override float GetWidth(float maxWidth) - { - return Mathf.Min(Width, maxWidth); - } - - public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms) - { - // 主矩形区域 (75px高度) - Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), 75f); - Widgets.DrawWindowBackground(rect); - - Rect innerRect = rect.ContractedBy(Padding); - float curY = innerRect.y; - - // 状态判断 - bool isIncubating = ootheca.isIncubating; - bool hasLarva = ootheca.assignedLarva != null; - var config = ootheca.IncubatorData?.SelectedConfig; - - // === 标题行(可点击切换目标) === - Text.Font = GameFont.Small; - Rect titleRect = new Rect(innerRect.x, curY, innerRect.width, Text.LineHeight); - - string title; - if (isIncubating && ootheca.incubatingPawnKind != null) - title = ootheca.incubatingPawnKind.LabelCap; - else if (config != null) - title = config.pawnKind.LabelCap; - else - title = "ARA_Gizmo_SelectIncubationTarget".Translate(); - - // 标题按钮(只有非孵化状态可点击) - bool canSwitch = !isIncubating && !hasLarva && ootheca.IncubatorData?.IncubationConfigs?.Count > 0; - if (canSwitch) - { - if (Mouse.IsOver(titleRect)) - { - Widgets.DrawHighlight(titleRect); - } - - if (Widgets.ButtonInvisible(titleRect)) - { - ShowTargetSwitchMenu(); - } - - GUI.color = new Color(0.7f, 0.9f, 1f); - Widgets.Label(titleRect, title.Truncate(titleRect.width - 20f) + " ▼"); - GUI.color = Color.white; - } - else - { - Widgets.Label(titleRect, title.Truncate(titleRect.width)); - } - curY += Text.LineHeight + Spacing; - - // === 双向进度条区域 === - if (isIncubating) - { - // 标签行:品质进度: :孵化进度 - Text.Font = GameFont.Tiny; - Rect labelRect = new Rect(innerRect.x, curY, innerRect.width, 14f); - - GUI.color = new Color(0.6f, 0.7f, 0.9f); - Text.Anchor = TextAnchor.MiddleLeft; - Widgets.Label(new Rect(labelRect.x, labelRect.y, labelRect.width / 2f, labelRect.height), - "ARA_Label_Quality".Translate()); - - GUI.color = new Color(0.6f, 0.9f, 0.6f); - Text.Anchor = TextAnchor.MiddleRight; - Widgets.Label(new Rect(labelRect.x + labelRect.width / 2f, labelRect.y, labelRect.width / 2f, labelRect.height), - "ARA_Label_Progress".Translate()); - - Text.Anchor = TextAnchor.UpperLeft; - GUI.color = Color.white; - curY += 14f; - - // 双向进度条 - Rect barRect = new Rect(innerRect.x, curY, innerRect.width, BarHeight); - float midX = barRect.x + barRect.width / 2f; - float halfWidth = barRect.width / 2f; - - // 背景 - GUI.DrawTexture(barRect, EmptyBarTex); - - // 品质进度(向左) - float qualityWidth = halfWidth * ootheca.QualityPercent; - Rect qualityRect = new Rect(midX - qualityWidth, barRect.y, qualityWidth, barRect.height); - GUI.DrawTexture(qualityRect, QualityBarTex); - - // 孵化进度(向右) - float progressWidth = halfWidth * ootheca.AdjustedProgressPercent; - Rect progressRect = new Rect(midX, barRect.y, progressWidth, barRect.height); - GUI.DrawTexture(progressRect, IncubationBarTex); - - // 中线 - Widgets.DrawLineVertical(midX, barRect.y, barRect.height); - - // 进度条边框 - Widgets.DrawBox(barRect, 1); - - // Tooltip - string tooltip = GetTooltip(); - TooltipHandler.TipRegion(barRect, tooltip); - } - else if (hasLarva) - { - Text.Font = GameFont.Tiny; - GUI.color = new Color(0.9f, 0.9f, 0.5f); - string statusText = ootheca.larvaOperateTicksRemaining > 0 - ? "ARA_Status_LarvaActivating".Translate() - : "ARA_Status_LarvaOnTheWay".Translate(); - Widgets.Label(new Rect(innerRect.x, curY, innerRect.width, 30f), statusText); - GUI.color = Color.white; - } - else - { - // 无目标或等待状态 - Text.Font = GameFont.Tiny; - GUI.color = new Color(0.7f, 0.7f, 0.7f); - string statusText; - if (config == null) - statusText = "ARA_Status_SelectTarget".Translate(); - else if (!config.IsResearchComplete) - statusText = "ARA_Status_NeedResearch".Translate(); - else - statusText = "ARA_Status_Ready".Translate(); - Widgets.Label(new Rect(innerRect.x, curY, innerRect.width, 20f), statusText); - GUI.color = Color.white; - } - - // 工具提示(整体) - if (Mouse.IsOver(rect) && !isIncubating) - { - Widgets.DrawHighlight(rect); - } - - Text.Font = GameFont.Small; - return new GizmoResult(GizmoState.Clear); - } - - private void ShowTargetSwitchMenu() - { - var configs = ootheca.IncubatorData?.IncubationConfigs; - if (configs == null || configs.Count == 0) return; - - List options = new List(); - - for (int i = 0; i < configs.Count; i++) - { - var cfg = configs[i]; - int index = i; - - string label = cfg.pawnKind.LabelCap; - if (cfg.requiredResearch != null && !cfg.requiredResearch.IsFinished) - { - label += $" ({cfg.requiredResearch.LabelCap})"; - options.Add(new FloatMenuOption(label, null)); - } - else - { - label += $" ({cfg.daysRequired}" + "ARA_Days".Translate() + ")"; - options.Add(new FloatMenuOption(label, () => - { - ootheca.IncubatorData.SwitchToConfig(index); - })); - } - } - - Find.WindowStack.Add(new FloatMenu(options)); - } - - private string GetTooltip() - { - var sb = new System.Text.StringBuilder(); - bool isIncubating = ootheca.isIncubating; - - if (isIncubating && ootheca.incubatingPawnKind != null) - { - sb.AppendLine("ARA_Tooltip_Incubating".Translate()); - sb.AppendLine("ARA_Tooltip_Target".Translate(ootheca.incubatingPawnKind.LabelCap)); - sb.AppendLine("ARA_Tooltip_Progress".Translate(ootheca.AdjustedProgressPercent.ToStringPercent())); - sb.AppendLine("ARA_Tooltip_Quality".Translate(ootheca.QualityPercent.ToStringPercent(), "")); - - float daysRemaining = ootheca.GetRemainingDays(); - if (daysRemaining > 0 && daysRemaining < float.MaxValue) - { - sb.AppendLine("ARA_Tooltip_Remaining".Translate(daysRemaining.ToString("F1") + " " + "ARA_Days".Translate())); - } - } - else - { - var config = ootheca.IncubatorData?.SelectedConfig; - if (config != null) - { - sb.AppendLine("ARA_Tooltip_Ready".Translate()); - sb.AppendLine("ARA_Tooltip_Target".Translate(config.pawnKind.LabelCap)); - sb.AppendLine("ARA_Tooltip_Duration".Translate(config.daysRequired + " " + "ARA_Days".Translate())); - } - else - { - sb.AppendLine("ARA_Tooltip_NoTarget".Translate()); - } - } - - return sb.ToString().TrimEndNewlines(); - } - } -} diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_PawnProgressBar.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_PawnProgressBar.cs index 7284bde..75899c3 100644 --- a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_PawnProgressBar.cs +++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_PawnProgressBar.cs @@ -6,8 +6,7 @@ using Verse; namespace ArachnaeSwarm { /// - /// 双向进度条 Gizmo - 用于督虫孵化池 - /// 样式与旧版 Gizmo_IncubationProgress 统一 + /// 统一的双向进度条 Gizmo - 用于所有孵化建筑/组件 /// [StaticConstructorOnStartup] public class Gizmo_PawnProgressBar : Gizmo @@ -22,12 +21,11 @@ namespace ArachnaeSwarm private static readonly Texture2D QualityBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.5f, 0.9f, 0.8f)); private static readonly Texture2D EmptyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.1f, 0.1f, 0.1f, 0.5f)); - private readonly CompQueuedPawnSpawnerWithFlux comp; - // scrollPosition 现在存储在 comp 中以在刷新间保持 + private readonly IOrderGizmoProvider provider; - public Gizmo_PawnProgressBar(CompQueuedPawnSpawnerWithFlux comp) + public Gizmo_PawnProgressBar(IOrderGizmoProvider provider) { - this.comp = comp; + this.provider = provider; this.Order = -140f; } @@ -35,7 +33,7 @@ namespace ArachnaeSwarm public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms) { - var orders = comp.GetOrdersForGizmo(); + var orders = provider.GetOrdersForGizmo(); int orderCount = orders.Count; // 计算高度 @@ -55,7 +53,7 @@ namespace ArachnaeSwarm Rect titleRect = new Rect(innerRect.x, curY, innerRect.width, Text.LineHeight); string title; - bool canAdd = orderCount < comp.Props.productionQueueLimit; + bool canAdd = orderCount < provider.QueueLimit; if (orderCount > 0) { @@ -78,7 +76,7 @@ namespace ArachnaeSwarm if (Widgets.ButtonInvisible(titleRect)) { - comp.ShowOrderMenuPublic(); + provider.ShowOrderMenu(); } // 带下拉箭头的标题 @@ -111,18 +109,18 @@ namespace ArachnaeSwarm { Rect scrollbarRect = new Rect(innerRect.xMax - scrollbarWidth, curY, scrollbarWidth, listHeight); float scrollMax = totalContentHeight - listHeight; - comp.GizmoScrollPosition = GUI.VerticalScrollbar(scrollbarRect, comp.GizmoScrollPosition, listHeight, 0f, totalContentHeight); + provider.GizmoScrollPosition = GUI.VerticalScrollbar(scrollbarRect, provider.GizmoScrollPosition, listHeight, 0f, totalContentHeight); // 也支持滚轮 if (Mouse.IsOver(listRect)) { - comp.GizmoScrollPosition -= Event.current.delta.y * 1.5f; - comp.GizmoScrollPosition = Mathf.Clamp(comp.GizmoScrollPosition, 0f, scrollMax); + provider.GizmoScrollPosition -= Event.current.delta.y * 1.5f; + provider.GizmoScrollPosition = Mathf.Clamp(provider.GizmoScrollPosition, 0f, scrollMax); } } GUI.BeginClip(listRect); - float drawY = -comp.GizmoScrollPosition; + float drawY = -provider.GizmoScrollPosition; for (int i = 0; i < orderCount; i++) { @@ -175,7 +173,7 @@ namespace ArachnaeSwarm Rect cancelRect = new Rect(rect.xMax - 16f, rect.y, 16f, labelHeight); if (Widgets.ButtonText(cancelRect, "×", false)) { - comp.RemoveOrderByIndex(index); + provider.RemoveOrderByIndex(index); } // 进度条 @@ -202,6 +200,21 @@ namespace ArachnaeSwarm // 中线 Widgets.DrawLineVertical(midX, barRect.y, barRect.height); + + // 百分比文字(在进度条内部) + Text.Font = GameFont.Tiny; + Text.Anchor = TextAnchor.MiddleLeft; + GUI.color = new Color(0.8f, 0.9f, 1f); + Widgets.Label(new Rect(barRect.x + 2f, barRect.y, halfWidth - 4f, barRect.height), + order.qualityProgress.ToStringPercent("F0")); + + Text.Anchor = TextAnchor.MiddleRight; + GUI.color = new Color(0.8f, 1f, 0.8f); + Widgets.Label(new Rect(midX + 2f, barRect.y, halfWidth - 4f, barRect.height), + order.progress.ToStringPercent("F0")); + + Text.Anchor = TextAnchor.UpperLeft; + GUI.color = Color.white; // Tooltip string tooltip = $"{order.label}\n" + diff --git a/Source/ArachnaeSwarm/Buildings/IOrderGizmoProvider.cs b/Source/ArachnaeSwarm/Buildings/IOrderGizmoProvider.cs new file mode 100644 index 0000000..1d0b322 --- /dev/null +++ b/Source/ArachnaeSwarm/Buildings/IOrderGizmoProvider.cs @@ -0,0 +1,27 @@ +// File: IOrderGizmoProvider.cs +// 订单 Gizmo 数据提供者接口 - 让所有孵化建筑使用同一个 Gizmo +using System.Collections.Generic; + +namespace ArachnaeSwarm +{ + /// + /// 订单 Gizmo 数据提供者接口 + /// + public interface IOrderGizmoProvider + { + /// 获取订单列表用于 Gizmo 显示 + List GetOrdersForGizmo(); + + /// 最大队列数量(单孵化为1) + int QueueLimit { get; } + + /// 显示添加订单菜单 + void ShowOrderMenu(); + + /// 移除指定索引的订单 + void RemoveOrderByIndex(int index); + + /// Gizmo 滚动位置(用于多订单时) + float GizmoScrollPosition { get; set; } + } +}