This commit is contained in:
2025-12-25 17:22:25 +08:00
parent e3ffa7f920
commit 1ffd8a84e9
12 changed files with 250 additions and 719 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -227,4 +227,22 @@
<!-- 营养液状态 -->
<ARA_EquipmentIncubator.WaitingForNutrients>等待营养液...</ARA_EquipmentIncubator.WaitingForNutrients>
<ARA_Consumed>已消耗</ARA_Consumed>
<!-- 双向进度条 Gizmo 翻译键 -->
<ARA_Gizmo_SelectIncubationTarget>选择孵化目标...</ARA_Gizmo_SelectIncubationTarget>
<ARA_Label_Quality>品质:</ARA_Label_Quality>
<ARA_Label_Progress>进度:</ARA_Label_Progress>
<ARA_Status_SelectTarget>点击上方选择目标</ARA_Status_SelectTarget>
<ARA_Status_NeedResearch>需要完成研究</ARA_Status_NeedResearch>
<ARA_Status_LarvaActivating>幼虫激活中...</ARA_Status_LarvaActivating>
<ARA_Status_LarvaOnTheWay>幼虫赶路中...</ARA_Status_LarvaOnTheWay>
<ARA_Days></ARA_Days>
<ARA_Tooltip_Incubating>【孵化中】</ARA_Tooltip_Incubating>
<ARA_Tooltip_Target>目标: {0}</ARA_Tooltip_Target>
<ARA_Tooltip_Progress>进度: {0}</ARA_Tooltip_Progress>
<ARA_Tooltip_Quality>品质: {0}{1}</ARA_Tooltip_Quality>
<ARA_Tooltip_Remaining>剩余: {0}</ARA_Tooltip_Remaining>
<ARA_Tooltip_Ready>【就绪】</ARA_Tooltip_Ready>
<ARA_Tooltip_Duration>孵化时间: {0}</ARA_Tooltip_Duration>
<ARA_Tooltip_NoTarget>【未选择目标】\n点击标题选择孵化目标</ARA_Tooltip_NoTarget>
</LanguageData>

View File

@@ -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<Pawn> assignedLarvae = new List<Pawn>();
// === 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<OrderDisplayInfo> GetOrdersForGizmo()
public List<PawnOrderDisplayInfo> GetOrdersForGizmo()
{
var result = new List<OrderDisplayInfo>();
var result = new List<PawnOrderDisplayInfo>();
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<Gizmo> 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)
{

View File

@@ -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<Pawn> assignedLarvae = new List<Pawn>();
// === 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<PawnOrderDisplayInfo> GetOrdersForGizmo()

View File

@@ -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<PawnOrderDisplayInfo> GetOrdersForGizmo()
{
var list = new List<PawnOrderDisplayInfo>();
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;

View File

@@ -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<FloatMenuOption> options = new List<FloatMenuOption>();
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();
}
}
}

View File

@@ -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<PawnOrderDisplayInfo> GetOrdersForGizmo()
{
var list = new List<PawnOrderDisplayInfo>();
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;

View File

@@ -1,210 +0,0 @@
using RimWorld;
using System.Collections.Generic;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
/// <summary>
/// 双向进度条 Gizmo - 用于物品孵化池
/// 样式与旧版 Gizmo_IncubationProgress 统一
/// </summary>
[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;
}
}
}
}

View File

@@ -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<FloatMenuOption> options = new List<FloatMenuOption>();
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();
}
}
}

View File

@@ -6,8 +6,7 @@ using Verse;
namespace ArachnaeSwarm
{
/// <summary>
/// 双向进度条 Gizmo - 用于督虫孵化池
/// 样式与旧版 Gizmo_IncubationProgress 统一
/// 统一的双向进度条 Gizmo - 用于所有孵化建筑/组件
/// </summary>
[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" +

View File

@@ -0,0 +1,27 @@
// File: IOrderGizmoProvider.cs
// 订单 Gizmo 数据提供者接口 - 让所有孵化建筑使用同一个 Gizmo
using System.Collections.Generic;
namespace ArachnaeSwarm
{
/// <summary>
/// 订单 Gizmo 数据提供者接口
/// </summary>
public interface IOrderGizmoProvider
{
/// <summary>获取订单列表用于 Gizmo 显示</summary>
List<PawnOrderDisplayInfo> GetOrdersForGizmo();
/// <summary>最大队列数量单孵化为1</summary>
int QueueLimit { get; }
/// <summary>显示添加订单菜单</summary>
void ShowOrderMenu();
/// <summary>移除指定索引的订单</summary>
void RemoveOrderByIndex(int index);
/// <summary>Gizmo 滚动位置(用于多订单时)</summary>
float GizmoScrollPosition { get; set; }
}
}