zc
This commit is contained in:
@@ -123,10 +123,14 @@
|
||||
<Compile Include="Buildings\Building_EquipmentOotheca\JobDriver_OperateEquipmentIncubator.cs" />
|
||||
<Compile Include="Buildings\Building_Incubatable.cs" />
|
||||
<Compile Include="Buildings\IFluxController.cs" />
|
||||
<Compile Include="Buildings\ILarvaActivatable.cs" />
|
||||
<Compile Include="Buildings\Building_Ootheca\Building_Ootheca.cs" />
|
||||
<Compile Include="Buildings\Building_Ootheca\CompProperties_IncubatorData.cs" />
|
||||
<Compile Include="Buildings\Building_Ootheca\Gizmo_IncubationProgress.cs" />
|
||||
<Compile Include="Buildings\Building_Ootheca\Gizmo_NeutronFlux.cs" />
|
||||
<Compile Include="Buildings\Building_Ootheca\Gizmo_DualProgressBar.cs" />
|
||||
<Compile Include="Buildings\Building_Ootheca\Gizmo_PawnProgressBar.cs" />
|
||||
<Compile Include="Buildings\Building_Ootheca\Gizmo_QueuedIncubationProgress.cs" />
|
||||
<Compile Include="Buildings\Building_Ootheca\JobDriver_OperateIncubator.cs" />
|
||||
<Compile Include="Buildings\Building_Ootheca\OothecaIncubatorExtension.cs" />
|
||||
<Compile Include="Buildings\Building_ResearchBlueprintReader\Building_ResearchBlueprintReader.cs" />
|
||||
|
||||
@@ -4,30 +4,89 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class CompProperties_QueuedInteractiveProducerWithFlux : CompProperties_QueuedInteractiveProducer
|
||||
// 带状态和品质的物品订单(通量品质系统)
|
||||
public class QueuedItemOrder : IExposable
|
||||
{
|
||||
public ProcessDef process;
|
||||
public string tempThingDefName;
|
||||
public OrderStatus status = OrderStatus.WaitingForLarva;
|
||||
public int productionUntilTick = -1;
|
||||
|
||||
// 通量品质系统
|
||||
public float qualityProgress = 0f;
|
||||
public float qualityTotal = 0f;
|
||||
|
||||
public float QualityPercent => qualityTotal > 0 ? qualityProgress / qualityTotal : 0f;
|
||||
|
||||
public void ExposeData()
|
||||
{
|
||||
if (Scribe.mode == LoadSaveMode.Saving && process != null)
|
||||
{
|
||||
tempThingDefName = process.thingDef.defName;
|
||||
}
|
||||
Scribe_Values.Look(ref tempThingDefName, "thingDefName");
|
||||
Scribe_Values.Look(ref status, "status", OrderStatus.WaitingForLarva);
|
||||
Scribe_Values.Look(ref productionUntilTick, "productionUntilTick", -1);
|
||||
Scribe_Values.Look(ref qualityProgress, "qualityProgress", 0f);
|
||||
Scribe_Values.Look(ref qualityTotal, "qualityTotal", 0f);
|
||||
}
|
||||
}
|
||||
|
||||
public class CompProperties_QueuedInteractiveProducerWithFlux : CompProperties
|
||||
{
|
||||
public List<PawnKindDef> whitelist;
|
||||
public int productionQueueLimit = 3;
|
||||
public float minNutritionToStart = 0.1f;
|
||||
public List<QualityThreshold> qualityThresholds;
|
||||
public IntRange spawnCount = new IntRange(1, 1);
|
||||
|
||||
public CompProperties_QueuedInteractiveProducerWithFlux()
|
||||
{
|
||||
compClass = typeof(CompQueuedInteractiveProducerWithFlux);
|
||||
}
|
||||
}
|
||||
|
||||
public class CompQueuedInteractiveProducerWithFlux : CompQueuedInteractiveProducer, IFluxController
|
||||
public class CompQueuedInteractiveProducerWithFlux : ThingComp, IFluxController, ILarvaActivatable
|
||||
{
|
||||
// === 通量系统字段 ===
|
||||
private float neutronFlux = 0.5f;
|
||||
private FluxMode fluxMode = FluxMode.Balance;
|
||||
|
||||
// === 接口实现 ===
|
||||
// === 生产队列 ===
|
||||
private List<QueuedItemOrder> orders = new List<QueuedItemOrder>();
|
||||
|
||||
// === 幼虫管理 ===
|
||||
private List<Pawn> assignedLarvae = new List<Pawn>();
|
||||
|
||||
// === 组件引用 ===
|
||||
private CompRefuelableNutrition _fuelComp;
|
||||
private CompAffectedByFacilities _facilitiesComp;
|
||||
private List<ProcessDef> _cachedProcesses;
|
||||
|
||||
public CompProperties_QueuedInteractiveProducerWithFlux Props => (CompProperties_QueuedInteractiveProducerWithFlux)props;
|
||||
private CompRefuelableNutrition FuelComp => _fuelComp ?? (_fuelComp = parent.GetComp<CompRefuelableNutrition>());
|
||||
private CompAffectedByFacilities FacilitiesComp => _facilitiesComp ?? (_facilitiesComp = parent.GetComp<CompAffectedByFacilities>());
|
||||
|
||||
public List<ProcessDef> Processes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cachedProcesses == null) BuildProcessList();
|
||||
return _cachedProcesses;
|
||||
}
|
||||
}
|
||||
|
||||
// === IFluxController 接口实现 ===
|
||||
public float NeutronFlux => neutronFlux;
|
||||
public float RawFlux => neutronFlux;
|
||||
public FluxMode CurrentFluxMode => fluxMode;
|
||||
public float FluxEfficiency => IFluxControllerExtensions.GetEfficiency(neutronFlux);
|
||||
public bool IsAutoMode => fluxMode != FluxMode.Manual;
|
||||
public bool IsIncubating => IsAnyOrderActive;
|
||||
public bool IsIncubating => orders.Any(o => o.status == OrderStatus.Incubating);
|
||||
public bool IsDormant => neutronFlux < 0.05f;
|
||||
|
||||
public void SetNeutronFlux(float value) => neutronFlux = Mathf.Clamp01(value);
|
||||
@@ -49,82 +108,199 @@ namespace ArachnaeSwarm
|
||||
_ => "?"
|
||||
};
|
||||
|
||||
private bool IsAnyOrderActive => productionOrders.Any(o => o.productionUntilTick > 0);
|
||||
public override void Initialize(CompProperties props)
|
||||
{
|
||||
base.Initialize(props);
|
||||
_fuelComp = parent.GetComp<CompRefuelableNutrition>();
|
||||
_facilitiesComp = parent.GetComp<CompAffectedByFacilities>();
|
||||
BuildProcessList();
|
||||
}
|
||||
|
||||
// 覆盖 Tick 逻辑
|
||||
public override void PostSpawnSetup(bool respawningAfterLoad)
|
||||
{
|
||||
base.PostSpawnSetup(respawningAfterLoad);
|
||||
if (_cachedProcesses == null) BuildProcessList();
|
||||
}
|
||||
|
||||
// === 订单管理 ===
|
||||
public int WaitingForLarvaCount => orders.Count(o => o.status == OrderStatus.WaitingForLarva);
|
||||
public int IncubatingCount => orders.Count(o => o.status == OrderStatus.Incubating);
|
||||
|
||||
public void AddOrder(ProcessDef process)
|
||||
{
|
||||
if (orders.Count >= Props.productionQueueLimit)
|
||||
{
|
||||
Messages.Message("队列已满!", MessageTypeDefOf.RejectInput);
|
||||
return;
|
||||
}
|
||||
orders.Add(new QueuedItemOrder { process = process, status = OrderStatus.WaitingForLarva });
|
||||
}
|
||||
|
||||
public void RemoveOrder(QueuedItemOrder order) => orders.Remove(order);
|
||||
public void RemoveOrderByIndex(int index) { if (index >= 0 && index < orders.Count) orders.RemoveAt(index); }
|
||||
|
||||
// === Gizmo 用的订单信息 ===
|
||||
public List<OrderDisplayInfo> GetOrdersForGizmo()
|
||||
{
|
||||
var result = new List<OrderDisplayInfo>();
|
||||
foreach (var order in orders)
|
||||
{
|
||||
result.Add(new OrderDisplayInfo
|
||||
{
|
||||
label = order.process?.thingDef?.LabelCap ?? "?",
|
||||
status = order.status,
|
||||
productionProgress = GetProgress(order),
|
||||
qualityProgress = order.QualityPercent,
|
||||
estimatedQuality = GetQualityCategory(order).GetLabel()
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float GetProgress(QueuedItemOrder order)
|
||||
{
|
||||
if (order.status != OrderStatus.Incubating || order.process == null) return 0f;
|
||||
int totalTicks = order.process.productionTicks;
|
||||
int elapsed = totalTicks - order.productionUntilTick;
|
||||
return totalTicks > 0 ? Mathf.Clamp01((float)elapsed / totalTicks) : 0f;
|
||||
}
|
||||
|
||||
private QualityCategory GetQualityCategory(QueuedItemOrder order)
|
||||
{
|
||||
if (Props.qualityThresholds.NullOrEmpty()) return QualityCategory.Normal;
|
||||
float qualityPercent = order.QualityPercent;
|
||||
foreach (var threshold in Props.qualityThresholds.OrderByDescending(q => q.threshold))
|
||||
{
|
||||
if (qualityPercent >= threshold.threshold) return threshold.quality;
|
||||
}
|
||||
return Props.qualityThresholds.Any()
|
||||
? Props.qualityThresholds.OrderBy(q => q.threshold).First().quality
|
||||
: QualityCategory.Awful;
|
||||
}
|
||||
|
||||
// === 幼虫激活逻辑 ===
|
||||
private void CallLarvae()
|
||||
{
|
||||
int neededLarvae = WaitingForLarvaCount - assignedLarvae.Count;
|
||||
if (neededLarvae <= 0)
|
||||
{
|
||||
Messages.Message("没有需要激活的订单", MessageTypeDefOf.RejectInput);
|
||||
return;
|
||||
}
|
||||
|
||||
int called = 0;
|
||||
var availableLarvae = FindAvailableLarvae(neededLarvae);
|
||||
foreach (var larva in availableLarvae)
|
||||
{
|
||||
var job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_OperateIncubator"), parent);
|
||||
if (larva.jobs.TryTakeOrderedJob(job, JobTag.Misc))
|
||||
{
|
||||
assignedLarvae.Add(larva);
|
||||
called++;
|
||||
}
|
||||
}
|
||||
|
||||
if (called > 0)
|
||||
Messages.Message($"已呼叫 {called} 只幼虫", MessageTypeDefOf.PositiveEvent);
|
||||
else
|
||||
Messages.Message("未找到可用的幼虫!", MessageTypeDefOf.RejectInput);
|
||||
}
|
||||
|
||||
private List<Pawn> FindAvailableLarvae(int maxCount)
|
||||
{
|
||||
var result = new List<Pawn>();
|
||||
float searchRadius = 50f;
|
||||
if (parent.Map == null) return result;
|
||||
|
||||
foreach (var pawn in parent.Map.mapPawns.AllPawnsSpawned)
|
||||
{
|
||||
if (result.Count >= maxCount) break;
|
||||
if (pawn.def.defName == "ArachnaeBase_Race_Larva" &&
|
||||
!pawn.Downed && !pawn.Dead &&
|
||||
pawn.Faction == parent.Faction &&
|
||||
!assignedLarvae.Contains(pawn) &&
|
||||
parent.Position.DistanceTo(pawn.Position) <= searchRadius)
|
||||
{
|
||||
result.Add(pawn);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void NotifyLarvaArrived(Pawn larva) { }
|
||||
|
||||
public void NotifyLarvaOperationComplete(Pawn larva)
|
||||
{
|
||||
var waitingOrder = orders.FirstOrDefault(o => o.status == OrderStatus.WaitingForLarva);
|
||||
if (waitingOrder != null)
|
||||
{
|
||||
waitingOrder.status = OrderStatus.Incubating;
|
||||
waitingOrder.productionUntilTick = waitingOrder.process.productionTicks;
|
||||
waitingOrder.qualityTotal = waitingOrder.process.productionTicks;
|
||||
waitingOrder.qualityProgress = 0f;
|
||||
}
|
||||
assignedLarvae.Remove(larva);
|
||||
}
|
||||
|
||||
// === Tick 逻辑 ===
|
||||
public override void CompTick()
|
||||
{
|
||||
float hasFuelVal = FuelComp?.Fuel ?? 10f;
|
||||
bool hasFuel = hasFuelVal > 0.01f;
|
||||
base.CompTick();
|
||||
|
||||
// 自动模式
|
||||
if (IsAutoMode && parent.IsHashIntervalTick(250) && IsAnyOrderActive)
|
||||
assignedLarvae.RemoveAll(l => l == null || l.Dead || l.Destroyed);
|
||||
|
||||
bool hasFuel = (FuelComp?.Fuel ?? 10f) > 0.01f;
|
||||
|
||||
// 自动模式调节
|
||||
if (IsAutoMode && parent.IsHashIntervalTick(250) && IsIncubating)
|
||||
{
|
||||
CalculateAutoFlux();
|
||||
}
|
||||
|
||||
if (IsAnyOrderActive)
|
||||
// 消耗燃料
|
||||
if (IsIncubating && FuelComp != null && neutronFlux > 0.01f)
|
||||
{
|
||||
// 消耗燃料
|
||||
if (FuelComp != null && neutronFlux > 0.01f)
|
||||
float fuelPerTick = (80f * FluxEfficiency * IncubatingCount) / 60000f;
|
||||
FuelComp.ConsumeFuel(fuelPerTick);
|
||||
}
|
||||
|
||||
// 处理正在生产的订单
|
||||
if (hasFuel && !IsDormant)
|
||||
{
|
||||
float speedFactor = 1f + (FacilitiesComp?.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor")) ?? 0f);
|
||||
float fluxSpeed = speedFactor * FluxEfficiency * 5f;
|
||||
|
||||
foreach (var order in orders.Where(o => o.status == OrderStatus.Incubating && o.productionUntilTick > 0))
|
||||
{
|
||||
float fuelPerTick = (80f * FluxEfficiency) / 60000f; // 物品孵化池消耗略高
|
||||
FuelComp.ConsumeFuel(fuelPerTick);
|
||||
}
|
||||
|
||||
if (hasFuel)
|
||||
{
|
||||
float ambientTemperature = parent.AmbientTemperature;
|
||||
bool isTempSafe = ambientTemperature >= Props.minSafeTemperature && ambientTemperature <= Props.maxSafeTemperature;
|
||||
|
||||
// 计算通量速度倍率:100%活性时5倍速度
|
||||
float speedFactor = 1f + (FacilitiesComp?.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor")) ?? 0f);
|
||||
float fluxSpeed = speedFactor * FluxEfficiency * 5f;
|
||||
|
||||
foreach (var order in productionOrders.Where(o => o.productionUntilTick > 0))
|
||||
// 进度推进
|
||||
float extraProgress = fluxSpeed - 1f;
|
||||
if (extraProgress > 0)
|
||||
{
|
||||
// 1. 进度推进(受通量倍率影响)
|
||||
// 由于基类逻辑是每 Tick 递减,我们这里手动控制递减量
|
||||
float progressStep = fluxSpeed;
|
||||
int ticksToSubtract = Mathf.FloorToInt(progressStep);
|
||||
if (Rand.Value < (progressStep - ticksToSubtract)) ticksToSubtract++;
|
||||
order.productionUntilTick = Mathf.Max(0, order.productionUntilTick - ticksToSubtract);
|
||||
|
||||
// 2. 品质累积(基础:每 Tick 增加 1)
|
||||
// 注意:如果 fluxSpeed > 1,生产变快,但品质累积速度不变,
|
||||
// 这样总的 ticksUnderOptimalConditions 就会减少,从而降低品质分数。
|
||||
// 这完美地实现了“速度越快,品质越低”的平衡逻辑。
|
||||
if (!IsDormant)
|
||||
{
|
||||
if (isTempSafe)
|
||||
{
|
||||
order.ticksUnderOptimalConditions++;
|
||||
}
|
||||
else
|
||||
{
|
||||
float tempDelta = (ambientTemperature > Props.maxSafeTemperature) ? ambientTemperature - Props.maxSafeTemperature : Props.minSafeTemperature - ambientTemperature;
|
||||
order.temperaturePenaltyPercent = Mathf.Min(1f, order.temperaturePenaltyPercent + tempDelta * Props.penaltyPerDegreePerTick);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 休眠状态下品质会缓慢衰减
|
||||
order.ticksUnderOptimalConditions = Mathf.Max(0, order.ticksUnderOptimalConditions - 2);
|
||||
}
|
||||
int extraTicks = Mathf.FloorToInt(extraProgress);
|
||||
if (Rand.Value < (extraProgress - extraTicks)) extraTicks++;
|
||||
order.productionUntilTick = Mathf.Max(0, order.productionUntilTick - extraTicks);
|
||||
}
|
||||
|
||||
// 通量品质系统:低通量时品质增长快
|
||||
float qualityBonus = 1f + (1f - neutronFlux) * 0.5f;
|
||||
float qualityGain = speedFactor * qualityBonus;
|
||||
order.qualityProgress = Mathf.Min(order.qualityProgress + qualityGain, order.qualityTotal);
|
||||
}
|
||||
}
|
||||
else if (IsDormant)
|
||||
{
|
||||
// 休眠时品质下降
|
||||
foreach (var order in orders.Where(o => o.status == OrderStatus.Incubating))
|
||||
{
|
||||
float qualityDecay = (order.qualityTotal * 0.1f) / 60000f;
|
||||
order.qualityProgress = Mathf.Max(0f, order.qualityProgress - qualityDecay);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理订单完成和消耗率更新(UI用)
|
||||
UpdateLogic();
|
||||
}
|
||||
|
||||
private void UpdateLogic()
|
||||
{
|
||||
// 完成订单
|
||||
productionOrders.RemoveAll(order =>
|
||||
orders.RemoveAll(order =>
|
||||
{
|
||||
if (order.productionUntilTick == 0)
|
||||
if (order.status == OrderStatus.Incubating && order.productionUntilTick == 0)
|
||||
{
|
||||
FinishProduction(order);
|
||||
return true;
|
||||
@@ -132,26 +308,10 @@ namespace ArachnaeSwarm
|
||||
return false;
|
||||
});
|
||||
|
||||
// 启动新订单
|
||||
int currentlyProducingCount = productionOrders.Count(o => o.productionUntilTick > 0);
|
||||
if (currentlyProducingCount < Props.productionQueueLimit)
|
||||
{
|
||||
var waitingOrder = productionOrders.FirstOrDefault(o => o.productionUntilTick == -1);
|
||||
if (waitingOrder != null)
|
||||
{
|
||||
// 初始 Tick 数,会被后续 Tick 动态加速
|
||||
waitingOrder.productionUntilTick = waitingOrder.process.productionTicks;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新燃料消耗率显示
|
||||
// 更新燃料消耗率
|
||||
if (FuelComp != null)
|
||||
{
|
||||
float totalConsumption = 0f;
|
||||
if (IsAnyOrderActive && NeutronFlux > 0.01f)
|
||||
{
|
||||
totalConsumption = currentlyProducingCount * 80f * FluxEfficiency;
|
||||
}
|
||||
float totalConsumption = IsIncubating && NeutronFlux > 0.01f ? IncubatingCount * 80f * FluxEfficiency : 0f;
|
||||
FuelComp.currentConsumptionRate = totalConsumption;
|
||||
}
|
||||
}
|
||||
@@ -159,38 +319,191 @@ namespace ArachnaeSwarm
|
||||
private void CalculateAutoFlux()
|
||||
{
|
||||
if (fluxMode == FluxMode.Manual) return;
|
||||
|
||||
float targetFlux = 0.5f;
|
||||
|
||||
// 简单逻辑:平衡模式趋向于 0.5;品质模式倾向于 0.1;速度模式倾向于 1.0
|
||||
switch (fluxMode)
|
||||
{
|
||||
case FluxMode.Speed: targetFlux = 1.0f; break;
|
||||
case FluxMode.Quality: targetFlux = 0.1f; break;
|
||||
case FluxMode.Balance:
|
||||
default: targetFlux = 0.5f; break;
|
||||
}
|
||||
|
||||
// 资源保护
|
||||
if (FuelComp != null && FuelComp.Fuel < 50f) targetFlux = Mathf.Min(targetFlux, 0.2f);
|
||||
|
||||
// 找到品质最低的订单来决定通量
|
||||
var incubating = orders.Where(o => o.status == OrderStatus.Incubating).ToList();
|
||||
if (!incubating.Any()) return;
|
||||
|
||||
float minQuality = incubating.Min(o => o.QualityPercent);
|
||||
float avgProgress = incubating.Average(o => GetProgress(o));
|
||||
float gap = minQuality - avgProgress;
|
||||
|
||||
float targetFlux = fluxMode switch
|
||||
{
|
||||
FluxMode.Speed => gap > 0.1f ? 1.0f : 0.8f,
|
||||
FluxMode.Quality => gap < 0 ? 0.2f : (gap < 0.1f ? 0.4f : 0.6f),
|
||||
_ => gap > 0.1f ? 0.7f : (gap < -0.1f ? 0.3f : 0.5f)
|
||||
};
|
||||
|
||||
if (FuelComp != null && FuelComp.Fuel < 50f)
|
||||
targetFlux = Mathf.Min(targetFlux, 0.2f);
|
||||
|
||||
neutronFlux = Mathf.Lerp(neutronFlux, targetFlux, 0.05f);
|
||||
}
|
||||
|
||||
private void FinishProduction(QueuedItemOrder order)
|
||||
{
|
||||
if (order.process == null) return;
|
||||
try
|
||||
{
|
||||
QualityCategory finalQuality = GetQualityCategory(order);
|
||||
for (int i = 0; i < Props.spawnCount.RandomInRange; i++)
|
||||
{
|
||||
Thing product = ThingMaker.MakeThing(order.process.thingDef);
|
||||
product.TryGetComp<CompQuality>()?.SetQuality(finalQuality, ArtGenerationContext.Colony);
|
||||
GenPlace.TryPlaceThing(product, parent.Position, parent.Map, ThingPlaceMode.Near);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
ArachnaeLog.Debug($"Error in FinishProduction: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// === UI ===
|
||||
public override string CompInspectStringExtra()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine($"订单: {orders.Count} / {Props.productionQueueLimit}");
|
||||
sb.AppendLine($"等待幼虫: {WaitingForLarvaCount} 正在生产: {IncubatingCount}");
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
||||
{
|
||||
foreach (var g in base.CompGetGizmosExtra()) yield return g;
|
||||
if (parent.Faction == Faction.OfPlayer)
|
||||
if (parent.Faction != Faction.OfPlayer) yield break;
|
||||
|
||||
yield return new Gizmo_NeutronFlux(this);
|
||||
yield return new Gizmo_DualProgressBar(this);
|
||||
|
||||
if (orders.Count < Props.productionQueueLimit)
|
||||
{
|
||||
yield return new Gizmo_NeutronFlux(this);
|
||||
yield return new Command_Action
|
||||
{
|
||||
defaultLabel = $"添加订单 ({orders.Count}/{Props.productionQueueLimit})",
|
||||
defaultDesc = "选择要生产的物品(可多次点击)",
|
||||
icon = ContentFinder<Texture2D>.Get("ArachnaeSwarm/UI/Commands/ARA_NodeSwarmIcon", false),
|
||||
action = ShowOrderMenu
|
||||
};
|
||||
}
|
||||
|
||||
int needed = WaitingForLarvaCount - assignedLarvae.Count;
|
||||
if (needed > 0)
|
||||
{
|
||||
yield return new Command_Action
|
||||
{
|
||||
defaultLabel = $"呼叫幼虫 ({needed})",
|
||||
defaultDesc = $"呼叫 {needed} 只幼虫来激活等待中的订单",
|
||||
icon = ContentFinder<Texture2D>.Get("ArachnaeSwarm/UI/Commands/ARA_CallLarva", false),
|
||||
action = CallLarvae
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowOrderMenu()
|
||||
{
|
||||
var options = new List<FloatMenuOption>();
|
||||
foreach (var process in Processes)
|
||||
{
|
||||
if (process.requiredResearch != null && !process.requiredResearch.IsFinished)
|
||||
{
|
||||
options.Add(new FloatMenuOption(process.thingDef.LabelCap + " (需要研究: " + process.requiredResearch.LabelCap + ")", null));
|
||||
}
|
||||
else
|
||||
{
|
||||
var capturedProcess = process;
|
||||
options.Add(new FloatMenuOption(process.thingDef.LabelCap, () => {
|
||||
AddOrder(capturedProcess);
|
||||
if (orders.Count < Props.productionQueueLimit) ShowOrderMenu();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.Count > 0)
|
||||
Find.WindowStack.Add(new FloatMenu(options, "选择生产目标"));
|
||||
else
|
||||
Messages.Message("没有可生产的物品(检查建筑是否正确配置)", MessageTypeDefOf.RejectInput);
|
||||
}
|
||||
|
||||
private void BuildProcessList()
|
||||
{
|
||||
_cachedProcesses = new List<ProcessDef>();
|
||||
foreach (ThingDef thingDef in DefDatabase<ThingDef>.AllDefs)
|
||||
{
|
||||
if (thingDef.IsApparel || thingDef.IsWeapon)
|
||||
{
|
||||
var incubationCompProps = thingDef.GetCompProperties<CompProperties_ExtraIncubationInfo>();
|
||||
if (incubationCompProps != null)
|
||||
{
|
||||
bool isMatch = (!incubationCompProps.cocoonDefs.NullOrEmpty() && incubationCompProps.cocoonDefs.Contains(parent.def))
|
||||
|| (incubationCompProps.cocoonDef != null && incubationCompProps.cocoonDef == parent.def);
|
||||
|
||||
if (isMatch)
|
||||
{
|
||||
ResearchProjectDef researchPrerequisite = thingDef.recipeMaker?.researchPrerequisite
|
||||
?? thingDef.recipeMaker?.researchPrerequisites?.FirstOrDefault()
|
||||
?? thingDef.researchPrerequisites?.FirstOrDefault();
|
||||
|
||||
_cachedProcesses.Add(new ProcessDef
|
||||
{
|
||||
thingDef = thingDef,
|
||||
productionTicks = GetIncubationTimeTicks(thingDef),
|
||||
totalNutritionNeeded = GetIncubationCost(thingDef),
|
||||
requiredResearch = researchPrerequisite
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_cachedProcesses.SortBy(p => p.thingDef.label);
|
||||
}
|
||||
|
||||
private int GetIncubationTimeTicks(ThingDef thingDef)
|
||||
{
|
||||
StatDef stat = DefDatabase<StatDef>.GetNamedSilentFail("ARA_IncubationTime");
|
||||
if (stat != null && thingDef.statBases != null)
|
||||
{
|
||||
var sv = thingDef.statBases.FirstOrDefault(s => s.stat == stat);
|
||||
if (sv != null) return Mathf.RoundToInt(sv.value * 60000f);
|
||||
}
|
||||
return 60000;
|
||||
}
|
||||
|
||||
private float GetIncubationCost(ThingDef thingDef)
|
||||
{
|
||||
StatDef stat = DefDatabase<StatDef>.GetNamedSilentFail("ARA_IncubationCost");
|
||||
if (stat != null && thingDef.statBases != null)
|
||||
{
|
||||
var sv = thingDef.statBases.FirstOrDefault(s => s.stat == stat);
|
||||
if (sv != null) return sv.value;
|
||||
}
|
||||
return 10f;
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
Scribe_Collections.Look(ref orders, "orders", LookMode.Deep);
|
||||
Scribe_Collections.Look(ref assignedLarvae, "assignedLarvae", LookMode.Reference);
|
||||
Scribe_Values.Look(ref neutronFlux, "neutronFlux", 0.5f);
|
||||
Scribe_Values.Look(ref fluxMode, "fluxMode", FluxMode.Balance);
|
||||
|
||||
if (orders == null) orders = new List<QueuedItemOrder>();
|
||||
if (assignedLarvae == null) assignedLarvae = new List<Pawn>();
|
||||
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
{
|
||||
var _ = Processes;
|
||||
foreach (var order in orders)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(order.tempThingDefName) && order.process == null)
|
||||
{
|
||||
order.process = _cachedProcesses.FirstOrDefault(p => p.thingDef.defName == order.tempThingDefName);
|
||||
}
|
||||
}
|
||||
orders.RemoveAll(o => o.process == null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,30 +4,96 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class CompProperties_QueuedPawnSpawnerWithFlux : CompProperties_QueuedPawnSpawner
|
||||
// 订单状态
|
||||
public enum OrderStatus
|
||||
{
|
||||
WaitingForLarva = 0, // 等待幼虫激活
|
||||
Incubating = 1, // 正在孵化
|
||||
}
|
||||
|
||||
// 用于 Gizmo 显示的订单信息
|
||||
public struct OrderDisplayInfo
|
||||
{
|
||||
public string label;
|
||||
public OrderStatus status;
|
||||
public float productionProgress;
|
||||
public float qualityProgress;
|
||||
public string estimatedQuality;
|
||||
}
|
||||
|
||||
// 带状态和品质的订单
|
||||
public class QueuedPawnOrder : IExposable
|
||||
{
|
||||
public QueuedPawnSpawnEntry entry;
|
||||
public OrderStatus status = OrderStatus.WaitingForLarva;
|
||||
public int spawnUntilTick = -1;
|
||||
|
||||
// 品质系统
|
||||
public float qualityProgress = 0f;
|
||||
public float qualityTotal = 0f;
|
||||
|
||||
public float QualityPercent => qualityTotal > 0 ? qualityProgress / qualityTotal : 0f;
|
||||
|
||||
public void ExposeData()
|
||||
{
|
||||
Scribe_Deep.Look(ref entry, "entry");
|
||||
Scribe_Values.Look(ref status, "status", OrderStatus.WaitingForLarva);
|
||||
Scribe_Values.Look(ref spawnUntilTick, "spawnUntilTick", -1);
|
||||
Scribe_Values.Look(ref qualityProgress, "qualityProgress", 0f);
|
||||
Scribe_Values.Look(ref qualityTotal, "qualityTotal", 0f);
|
||||
}
|
||||
}
|
||||
|
||||
public class CompProperties_QueuedPawnSpawnerWithFlux : CompProperties
|
||||
{
|
||||
public List<QueuedPawnSpawnEntry> spawnablePawns;
|
||||
public List<PawnKindDef> whitelist;
|
||||
public int productionQueueLimit = 5;
|
||||
public float minNutritionToStart = 0.1f;
|
||||
|
||||
// 质量系统
|
||||
public float minSafeTemperature = 7f;
|
||||
public float maxSafeTemperature = 32f;
|
||||
public float penaltyPerDegreePerTick = 0.00001f;
|
||||
public List<QualityThreshold> qualityThresholds;
|
||||
|
||||
public CompProperties_QueuedPawnSpawnerWithFlux()
|
||||
{
|
||||
compClass = typeof(CompQueuedPawnSpawnerWithFlux);
|
||||
}
|
||||
}
|
||||
|
||||
public class CompQueuedPawnSpawnerWithFlux : CompQueuedPawnSpawner, IFluxController
|
||||
public class CompQueuedPawnSpawnerWithFlux : ThingComp, IFluxController, ILarvaActivatable
|
||||
{
|
||||
// === 通量系统字段 ===
|
||||
private float neutronFlux = 0.5f;
|
||||
private FluxMode fluxMode = FluxMode.Balance;
|
||||
|
||||
// === 接口实现 ===
|
||||
// === 生产队列 ===
|
||||
private List<QueuedPawnOrder> orders = new List<QueuedPawnOrder>();
|
||||
|
||||
// === 幼虫管理 ===
|
||||
private List<Pawn> assignedLarvae = new List<Pawn>();
|
||||
|
||||
// === 组件引用 ===
|
||||
private CompRefuelableNutrition _fuelComp;
|
||||
private CompAffectedByFacilities _facilitiesComp;
|
||||
|
||||
public CompProperties_QueuedPawnSpawnerWithFlux Props => (CompProperties_QueuedPawnSpawnerWithFlux)props;
|
||||
private CompRefuelableNutrition FuelComp => _fuelComp ?? (_fuelComp = parent.GetComp<CompRefuelableNutrition>());
|
||||
private CompAffectedByFacilities FacilitiesComp => _facilitiesComp ?? (_facilitiesComp = parent.GetComp<CompAffectedByFacilities>());
|
||||
|
||||
// === IFluxController 接口实现 ===
|
||||
public float NeutronFlux => neutronFlux;
|
||||
public float RawFlux => neutronFlux;
|
||||
public FluxMode CurrentFluxMode => fluxMode;
|
||||
public float FluxEfficiency => IFluxControllerExtensions.GetEfficiency(neutronFlux);
|
||||
public bool IsAutoMode => fluxMode != FluxMode.Manual;
|
||||
public bool IsIncubating => IsAnyOrderActive;
|
||||
public bool IsIncubating => orders.Any(o => o.status == OrderStatus.Incubating);
|
||||
public bool IsDormant => neutronFlux < 0.05f;
|
||||
|
||||
public void SetNeutronFlux(float value) => neutronFlux = Mathf.Clamp01(value);
|
||||
@@ -49,163 +115,344 @@ namespace ArachnaeSwarm
|
||||
_ => "?"
|
||||
};
|
||||
|
||||
// 辅助属性
|
||||
private bool IsAnyOrderActive => productionOrders.Any(o => o.spawnUntilTick > 0);
|
||||
public override void Initialize(CompProperties props)
|
||||
{
|
||||
base.Initialize(props);
|
||||
_fuelComp = parent.GetComp<CompRefuelableNutrition>();
|
||||
_facilitiesComp = parent.GetComp<CompAffectedByFacilities>();
|
||||
}
|
||||
|
||||
// 覆盖 Tick 逻辑以处理通量效果
|
||||
// === 订单管理 ===
|
||||
public void AddOrder(QueuedPawnSpawnEntry entry)
|
||||
{
|
||||
if (orders.Count >= Props.productionQueueLimit)
|
||||
{
|
||||
Messages.Message("队列已满!", MessageTypeDefOf.RejectInput);
|
||||
return;
|
||||
}
|
||||
orders.Add(new QueuedPawnOrder { entry = entry, status = OrderStatus.WaitingForLarva });
|
||||
}
|
||||
|
||||
public void RemoveOrder(QueuedPawnOrder order) => orders.Remove(order);
|
||||
public void RemoveOrderByIndex(int index) { if (index >= 0 && index < orders.Count) orders.RemoveAt(index); }
|
||||
|
||||
public int WaitingForLarvaCount => orders.Count(o => o.status == OrderStatus.WaitingForLarva);
|
||||
public int IncubatingCount => orders.Count(o => o.status == OrderStatus.Incubating);
|
||||
|
||||
// === Gizmo 用的订单信息 ===
|
||||
public List<PawnOrderDisplayInfo> GetOrdersForGizmo()
|
||||
{
|
||||
var result = new List<PawnOrderDisplayInfo>();
|
||||
foreach (var order in orders)
|
||||
{
|
||||
float prodProgress = GetProgress(order);
|
||||
result.Add(new PawnOrderDisplayInfo
|
||||
{
|
||||
label = order.entry.pawnKind.LabelCap,
|
||||
status = order.status,
|
||||
progress = prodProgress,
|
||||
qualityProgress = order.QualityPercent,
|
||||
remainingTime = order.status == OrderStatus.Incubating && order.spawnUntilTick > 0
|
||||
? (order.spawnUntilTick - Find.TickManager.TicksGame).ToStringTicksToPeriod()
|
||||
: "等待中",
|
||||
estimatedQuality = GetEstimatedQuality(order).GetLabel()
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float GetProgress(QueuedPawnOrder order)
|
||||
{
|
||||
if (order.status != OrderStatus.Incubating || order.spawnUntilTick <= 0) return 0f;
|
||||
int totalTicks = order.entry.delayTicks;
|
||||
int startTick = order.spawnUntilTick - totalTicks;
|
||||
int elapsed = Find.TickManager.TicksGame - startTick;
|
||||
return totalTicks > 0 ? Mathf.Clamp01((float)elapsed / totalTicks) : 0f;
|
||||
}
|
||||
|
||||
private QualityCategory GetEstimatedQuality(QueuedPawnOrder order)
|
||||
{
|
||||
if (Props.qualityThresholds.NullOrEmpty()) return QualityCategory.Normal;
|
||||
float qualityPercent = order.QualityPercent;
|
||||
foreach (var threshold in Props.qualityThresholds.OrderByDescending(q => q.threshold))
|
||||
{
|
||||
if (qualityPercent >= threshold.threshold) return threshold.quality;
|
||||
}
|
||||
return Props.qualityThresholds.Any()
|
||||
? Props.qualityThresholds.OrderBy(q => q.threshold).First().quality
|
||||
: QualityCategory.Awful;
|
||||
}
|
||||
|
||||
// === 幼虫激活逻辑 ===
|
||||
private void CallLarvae()
|
||||
{
|
||||
int neededLarvae = WaitingForLarvaCount - assignedLarvae.Count;
|
||||
if (neededLarvae <= 0)
|
||||
{
|
||||
Messages.Message("没有需要激活的订单", MessageTypeDefOf.RejectInput);
|
||||
return;
|
||||
}
|
||||
|
||||
int called = 0;
|
||||
var availableLarvae = FindAvailableLarvae(neededLarvae);
|
||||
foreach (var larva in availableLarvae)
|
||||
{
|
||||
var job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_OperateIncubator"), parent);
|
||||
if (larva.jobs.TryTakeOrderedJob(job, JobTag.Misc))
|
||||
{
|
||||
assignedLarvae.Add(larva);
|
||||
called++;
|
||||
}
|
||||
}
|
||||
|
||||
if (called > 0)
|
||||
Messages.Message($"已呼叫 {called} 只幼虫", MessageTypeDefOf.PositiveEvent);
|
||||
else
|
||||
Messages.Message("未找到可用的幼虫!", MessageTypeDefOf.RejectInput);
|
||||
}
|
||||
|
||||
private List<Pawn> FindAvailableLarvae(int maxCount)
|
||||
{
|
||||
var result = new List<Pawn>();
|
||||
float searchRadius = 50f;
|
||||
if (parent.Map == null) return result;
|
||||
|
||||
foreach (var pawn in parent.Map.mapPawns.AllPawnsSpawned)
|
||||
{
|
||||
if (result.Count >= maxCount) break;
|
||||
if (pawn.def.defName == "ArachnaeBase_Race_Larva" &&
|
||||
!pawn.Downed && !pawn.Dead &&
|
||||
pawn.Faction == parent.Faction &&
|
||||
!assignedLarvae.Contains(pawn) &&
|
||||
parent.Position.DistanceTo(pawn.Position) <= searchRadius)
|
||||
{
|
||||
result.Add(pawn);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void NotifyLarvaArrived(Pawn larva) { }
|
||||
|
||||
public void NotifyLarvaOperationComplete(Pawn larva)
|
||||
{
|
||||
var waitingOrder = orders.FirstOrDefault(o => o.status == OrderStatus.WaitingForLarva);
|
||||
if (waitingOrder != null)
|
||||
{
|
||||
waitingOrder.status = OrderStatus.Incubating;
|
||||
waitingOrder.spawnUntilTick = Find.TickManager.TicksGame + waitingOrder.entry.delayTicks;
|
||||
waitingOrder.qualityTotal = waitingOrder.entry.delayTicks;
|
||||
waitingOrder.qualityProgress = 0f;
|
||||
}
|
||||
assignedLarvae.Remove(larva);
|
||||
}
|
||||
|
||||
// === Tick 逻辑 ===
|
||||
public override void CompTick()
|
||||
{
|
||||
// 注意:我们直接重写逻辑,而不是仅仅调用 base.CompTick,
|
||||
// 这样我们可以更精确地控制进度增加速度。
|
||||
|
||||
base.CompTick();
|
||||
|
||||
assignedLarvae.RemoveAll(l => l == null || l.Dead || l.Destroyed);
|
||||
|
||||
bool hasFuel = FuelComp?.HasFuel ?? true;
|
||||
|
||||
// 自动模式调节
|
||||
if (IsAutoMode && parent.IsHashIntervalTick(250) && IsAnyOrderActive)
|
||||
if (IsAutoMode && parent.IsHashIntervalTick(250) && IsIncubating)
|
||||
{
|
||||
CalculateAutoFlux();
|
||||
}
|
||||
|
||||
if (IsAnyOrderActive)
|
||||
// 消耗燃料
|
||||
if (IsIncubating && FuelComp != null && neutronFlux > 0.01f)
|
||||
{
|
||||
// 消耗燃料(基于通量效率)
|
||||
if (FuelComp != null && neutronFlux > 0.01f)
|
||||
{
|
||||
float fuelPerTick = (50f * FluxEfficiency) / 60000f;
|
||||
FuelComp.ConsumeFuel(fuelPerTick);
|
||||
}
|
||||
float fuelPerTick = (50f * FluxEfficiency * IncubatingCount) / 60000f;
|
||||
FuelComp.ConsumeFuel(fuelPerTick);
|
||||
}
|
||||
|
||||
if (!hasFuel)
|
||||
{
|
||||
// 没燃料时进度停滞
|
||||
}
|
||||
else if (IsDormant)
|
||||
{
|
||||
// 休眠逻辑:目前排队组件暂不支持品质损耗,仅停滞
|
||||
}
|
||||
else
|
||||
{
|
||||
// 正常孵化:受通量加速影响
|
||||
float speedFactor = 1f + (FacilitiesComp?.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor")) ?? 0f);
|
||||
float fluxSpeed = speedFactor * FluxEfficiency * 5f;
|
||||
// 处理正在孵化的订单
|
||||
if (hasFuel && !IsDormant)
|
||||
{
|
||||
float speedFactor = 1f + (FacilitiesComp?.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor")) ?? 0f);
|
||||
float fluxSpeed = speedFactor * FluxEfficiency * 5f;
|
||||
|
||||
// 更新所有激活订单的进度
|
||||
// 由于基类逻辑是基于 TickManager.TicksGame 的(spawnUntilTick),
|
||||
// 我们需要手动调整这个目标 Tick 来实现加速。
|
||||
//
|
||||
// 复杂点:基类认为每 Tick 进度 +1。我们要实现每 Tick 进度 +fluxSpeed。
|
||||
// 所以我们每 Tick 实际上让目标 Tick 靠近当前 Tick (fluxSpeed - 1) 个单位。
|
||||
foreach (var order in productionOrders.Where(o => o.spawnUntilTick > 0))
|
||||
foreach (var order in orders.Where(o => o.status == OrderStatus.Incubating))
|
||||
{
|
||||
// 进度推进
|
||||
float extraProgress = fluxSpeed - 1f;
|
||||
if (extraProgress > 0)
|
||||
{
|
||||
// 计算需要减少的剩余时间量
|
||||
float extraProgress = fluxSpeed - 1f;
|
||||
// 我们通过增加一个随机概率或累积器来处理小数
|
||||
// 这里简单处理:直接修改 spawnUntilTick
|
||||
// (注意:如果 fluxSpeed < 1,spawnUntilTick 会延后)
|
||||
if (extraProgress > 0)
|
||||
{
|
||||
// 实际应该减少的值。如果 fluxSpeed=5,则每 tick 应该减少 5 tick 的等待时间。
|
||||
// 基类自然减少了 1,我们额外减少 4。
|
||||
int extraTicks = Mathf.FloorToInt(extraProgress);
|
||||
if (Rand.Value < (extraProgress - extraTicks)) extraTicks++;
|
||||
order.spawnUntilTick -= extraTicks;
|
||||
}
|
||||
else if (extraProgress < 0)
|
||||
{
|
||||
// 减速逻辑
|
||||
float delayExtra = 1f - fluxSpeed;
|
||||
int delayTicks = Mathf.FloorToInt(delayExtra);
|
||||
if (Rand.Value < (delayExtra - delayTicks)) delayTicks++;
|
||||
order.spawnUntilTick += delayTicks;
|
||||
}
|
||||
int extraTicks = Mathf.FloorToInt(extraProgress);
|
||||
if (Rand.Value < (extraProgress - extraTicks)) extraTicks++;
|
||||
order.spawnUntilTick -= extraTicks;
|
||||
}
|
||||
|
||||
// 品质累积(低通量时累积更快)
|
||||
float qualityBonus = 1f + (1f - neutronFlux) * 0.5f;
|
||||
float qualityGain = speedFactor * qualityBonus;
|
||||
order.qualityProgress = Mathf.Min(order.qualityProgress + qualityGain, order.qualityTotal);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理订单完成和启动新订单(逻辑基本同基类,但我们需要确保燃料消耗正常更新)
|
||||
TickProductionLogic();
|
||||
}
|
||||
|
||||
private void TickProductionLogic()
|
||||
{
|
||||
// 完成订单
|
||||
productionOrders.RemoveAll(order =>
|
||||
orders.RemoveAll(order =>
|
||||
{
|
||||
if (order.spawnUntilTick > 0 && Find.TickManager.TicksGame >= order.spawnUntilTick)
|
||||
if (order.status == OrderStatus.Incubating &&
|
||||
order.spawnUntilTick > 0 &&
|
||||
Find.TickManager.TicksGame >= order.spawnUntilTick)
|
||||
{
|
||||
Pawn pawn = PawnGenerator.GeneratePawn(new PawnGenerationRequest(order.entry.pawnKind, parent.Faction));
|
||||
if (pawn != null) GenPlace.TryPlaceThing(pawn, parent.Position, parent.Map, ThingPlaceMode.Near);
|
||||
CompleteOrder(order);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// 启动待办订单
|
||||
int currentlyProducingCount = productionOrders.Count(o => o.spawnUntilTick > 0);
|
||||
if (currentlyProducingCount < Props.productionQueueLimit)
|
||||
private void CompleteOrder(QueuedPawnOrder order)
|
||||
{
|
||||
Pawn pawn = PawnGenerator.GeneratePawn(new PawnGenerationRequest(order.entry.pawnKind, parent.Faction));
|
||||
if (pawn != null)
|
||||
{
|
||||
var waitingOrder = productionOrders.FirstOrDefault(o => o.spawnUntilTick == -1);
|
||||
if (waitingOrder != null)
|
||||
{
|
||||
// 初始目标 Tick,之后会在 CompTick 中被通量系统动态调整
|
||||
waitingOrder.spawnUntilTick = Find.TickManager.TicksGame + waitingOrder.entry.delayTicks;
|
||||
}
|
||||
// 应用品质效果到 Pawn
|
||||
ApplyQualityEffects(pawn, order.QualityPercent);
|
||||
GenPlace.TryPlaceThing(pawn, parent.Position, parent.Map, ThingPlaceMode.Near);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新燃料消耗显示(UI用)
|
||||
if (FuelComp != null)
|
||||
{
|
||||
float totalConsumptionPerDay = 0f;
|
||||
if (IsAnyOrderActive && NeutronFlux > 0.01f)
|
||||
{
|
||||
// 基础:每激活一个槽位在 100% 活性下消耗 50/天?
|
||||
// 或者统一由组件控制
|
||||
totalConsumptionPerDay = currentlyProducingCount * 50f * FluxEfficiency;
|
||||
}
|
||||
FuelComp.currentConsumptionRate = totalConsumptionPerDay;
|
||||
}
|
||||
private void ApplyQualityEffects(Pawn pawn, float qualityPercent)
|
||||
{
|
||||
// 基于品质百分比调整能力
|
||||
// 0.0 = 最差, 1.0 = 最佳
|
||||
float statBonus = qualityPercent * 0.3f; // 最多+30%
|
||||
|
||||
// 可以在这里添加特定的品质效果
|
||||
// 例如:pawn.health, pawn.skills 等
|
||||
}
|
||||
|
||||
private void CalculateAutoFlux()
|
||||
{
|
||||
if (fluxMode == FluxMode.Manual) return;
|
||||
|
||||
// 排队组件目前主要影响速度。平衡模式下,如果燃料充足,保持中等偏高速度。
|
||||
float targetFlux = 0.5f;
|
||||
|
||||
switch (fluxMode)
|
||||
{
|
||||
case FluxMode.Speed: targetFlux = 1.0f; break;
|
||||
case FluxMode.Quality: targetFlux = 0.2f; break; // 排队组件目前无品质机制,所以此模式意义较小
|
||||
case FluxMode.Balance:
|
||||
default: targetFlux = 0.6f; break;
|
||||
}
|
||||
|
||||
// 资源保护
|
||||
if (FuelComp != null && FuelComp.Fuel < 20f) targetFlux = Mathf.Min(targetFlux, 0.3f);
|
||||
|
||||
// 找到品质最低的订单来决定通量
|
||||
var incubating = orders.Where(o => o.status == OrderStatus.Incubating).ToList();
|
||||
if (!incubating.Any()) return;
|
||||
|
||||
float minQuality = incubating.Min(o => o.QualityPercent);
|
||||
float avgProgress = incubating.Average(o => GetProgress(o));
|
||||
float gap = minQuality - avgProgress;
|
||||
|
||||
float targetFlux = fluxMode switch
|
||||
{
|
||||
FluxMode.Speed => gap > 0.1f ? 1.0f : 0.8f,
|
||||
FluxMode.Quality => gap < 0 ? 0.2f : (gap < 0.1f ? 0.4f : 0.6f),
|
||||
_ => gap > 0.1f ? 0.7f : (gap < -0.1f ? 0.3f : 0.5f)
|
||||
};
|
||||
|
||||
if (FuelComp != null && FuelComp.Fuel < 20f)
|
||||
targetFlux = Mathf.Min(targetFlux, 0.3f);
|
||||
|
||||
neutronFlux = Mathf.Lerp(neutronFlux, targetFlux, 0.05f);
|
||||
}
|
||||
|
||||
// === UI ===
|
||||
public override string CompInspectStringExtra()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine($"订单: {orders.Count} / {Props.productionQueueLimit}");
|
||||
sb.AppendLine($"等待幼虫: {WaitingForLarvaCount} 正在孵化: {IncubatingCount}");
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
||||
{
|
||||
foreach (var g in base.CompGetGizmosExtra()) yield return g;
|
||||
|
||||
if (parent.Faction == Faction.OfPlayer)
|
||||
if (parent.Faction != Faction.OfPlayer) yield break;
|
||||
|
||||
// 通量控制 Gizmo
|
||||
yield return new Gizmo_NeutronFlux(this);
|
||||
|
||||
// 进度 Gizmo
|
||||
yield return new Gizmo_PawnProgressBar(this);
|
||||
|
||||
// 添加订单按钮
|
||||
if (orders.Count < Props.productionQueueLimit)
|
||||
{
|
||||
// 注意:进度 Gizmo 由 Building 负责显示,
|
||||
// 但如果是通用 Building 挂载此组件,可能需要在此提供。
|
||||
// 鉴于目前是特定 Building 类,我们暂不在此重复,保持现有的逻辑。
|
||||
// 不过,我们需要确保通量控制 Gizmo 可用。
|
||||
yield return new Gizmo_NeutronFlux(this);
|
||||
yield return new Command_Action
|
||||
{
|
||||
defaultLabel = $"添加订单 ({orders.Count}/{Props.productionQueueLimit})",
|
||||
defaultDesc = "选择要孵化的单位类型(可多次点击)",
|
||||
icon = ContentFinder<Texture2D>.Get("ArachnaeSwarm/UI/Commands/ARA_NodeSwarmIcon", false),
|
||||
action = ShowOrderMenu
|
||||
};
|
||||
}
|
||||
|
||||
// 呼叫幼虫按钮
|
||||
int needed = WaitingForLarvaCount - assignedLarvae.Count;
|
||||
if (needed > 0)
|
||||
{
|
||||
yield return new Command_Action
|
||||
{
|
||||
defaultLabel = $"呼叫幼虫 ({needed})",
|
||||
defaultDesc = $"呼叫 {needed} 只幼虫来激活等待中的订单",
|
||||
icon = ContentFinder<Texture2D>.Get("ArachnaeSwarm/UI/Commands/ARA_CallLarva", false),
|
||||
action = CallLarvae
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowOrderMenu()
|
||||
{
|
||||
var options = new List<FloatMenuOption>();
|
||||
|
||||
foreach (var entry in Props.spawnablePawns)
|
||||
{
|
||||
if (entry.requiredResearch != null && !entry.requiredResearch.IsFinished)
|
||||
{
|
||||
options.Add(new FloatMenuOption(
|
||||
entry.pawnKind.LabelCap + " (需要研究: " + entry.requiredResearch.LabelCap + ")",
|
||||
null
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
var capturedEntry = entry;
|
||||
options.Add(new FloatMenuOption(
|
||||
entry.pawnKind.LabelCap,
|
||||
() => {
|
||||
AddOrder(capturedEntry);
|
||||
if (orders.Count < Props.productionQueueLimit)
|
||||
ShowOrderMenu();
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.Count > 0)
|
||||
Find.WindowStack.Add(new FloatMenu(options, "选择孵化目标"));
|
||||
else
|
||||
Messages.Message("没有可用的孵化选项", MessageTypeDefOf.RejectInput);
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
Scribe_Collections.Look(ref orders, "orders", LookMode.Deep);
|
||||
Scribe_Collections.Look(ref assignedLarvae, "assignedLarvae", LookMode.Reference);
|
||||
Scribe_Values.Look(ref neutronFlux, "neutronFlux", 0.5f);
|
||||
Scribe_Values.Look(ref fluxMode, "fluxMode", FluxMode.Balance);
|
||||
|
||||
if (orders == null) orders = new List<QueuedPawnOrder>();
|
||||
if (assignedLarvae == null) assignedLarvae = new List<Pawn>();
|
||||
}
|
||||
}
|
||||
|
||||
// 督虫订单显示信息(带品质)
|
||||
public struct PawnOrderDisplayInfo
|
||||
{
|
||||
public string label;
|
||||
public OrderStatus status;
|
||||
public float progress;
|
||||
public float qualityProgress;
|
||||
public string remainingTime;
|
||||
public string estimatedQuality;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using System;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class Building_EquipmentOotheca : Building, IFluxController
|
||||
public class Building_EquipmentOotheca : Building, IFluxController, ILarvaActivatable
|
||||
{
|
||||
// === 通量系统字段 ===
|
||||
private float neutronFlux = 0.5f;
|
||||
|
||||
@@ -8,7 +8,7 @@ using Verse.AI;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class Building_Ootheca : Building, IFluxController
|
||||
public class Building_Ootheca : Building, IFluxController, ILarvaActivatable
|
||||
{
|
||||
// === 通量系统字段 ===
|
||||
private float neutronFlux = 0.5f;
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
/// <summary>
|
||||
/// 双向进度条 Gizmo - 用于物品孵化池
|
||||
/// 从中间向两边生长:左边品质进度,右边生产进度
|
||||
/// 支持滚动显示多个订单
|
||||
/// </summary>
|
||||
public class Gizmo_DualProgressBar : Gizmo
|
||||
{
|
||||
private const float BarHeight = 22f;
|
||||
private const float Spacing = 4f;
|
||||
private const float Padding = 8f;
|
||||
private const float TitleHeight = 26f;
|
||||
private const float MaxVisibleOrders = 4;
|
||||
|
||||
private readonly CompQueuedInteractiveProducerWithFlux comp;
|
||||
private float scrollPosition = 0f;
|
||||
|
||||
public Gizmo_DualProgressBar(CompQueuedInteractiveProducerWithFlux comp)
|
||||
{
|
||||
this.comp = comp;
|
||||
this.Order = -98f;
|
||||
}
|
||||
|
||||
public override float GetWidth(float maxWidth) => 220f;
|
||||
|
||||
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 = TitleHeight + Spacing;
|
||||
contentHeight += Mathf.Max(1, visibleCount) * (BarHeight + Spacing);
|
||||
contentHeight += Padding;
|
||||
|
||||
float totalHeight = Mathf.Max(75f, contentHeight);
|
||||
|
||||
Rect gizmoRect = new Rect(topLeft.x, topLeft.y - (totalHeight - 75f), GetWidth(maxWidth), totalHeight);
|
||||
Widgets.DrawWindowBackground(gizmoRect);
|
||||
|
||||
float curY = gizmoRect.y + Padding;
|
||||
|
||||
// 标题
|
||||
Rect titleRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, TitleHeight);
|
||||
Text.Font = GameFont.Small;
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
|
||||
string title = $"孵化进度 ({orderCount}/{comp.Props.productionQueueLimit})";
|
||||
Widgets.Label(titleRect, title);
|
||||
|
||||
curY += TitleHeight + Spacing;
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
// 订单列表区域
|
||||
Rect listRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, visibleCount * (BarHeight + Spacing));
|
||||
|
||||
if (orderCount > 0)
|
||||
{
|
||||
// 滚动处理
|
||||
if (orderCount > MaxVisibleOrders)
|
||||
{
|
||||
float scrollMax = (orderCount - MaxVisibleOrders) * (BarHeight + Spacing);
|
||||
if (Mouse.IsOver(listRect))
|
||||
{
|
||||
scrollPosition -= Event.current.delta.y * 0.5f;
|
||||
scrollPosition = Mathf.Clamp(scrollPosition, 0f, scrollMax);
|
||||
}
|
||||
}
|
||||
|
||||
GUI.BeginClip(listRect);
|
||||
float drawY = -scrollPosition;
|
||||
|
||||
for (int i = 0; i < orderCount; i++)
|
||||
{
|
||||
var order = orders[i];
|
||||
Rect barRect = new Rect(0, drawY, listRect.width, BarHeight);
|
||||
|
||||
if (barRect.yMax > 0 && barRect.y < listRect.height)
|
||||
{
|
||||
DrawOrderBar(barRect, order, i);
|
||||
}
|
||||
drawY += BarHeight + Spacing;
|
||||
}
|
||||
|
||||
GUI.EndClip();
|
||||
|
||||
// 滚动条指示
|
||||
if (orderCount > MaxVisibleOrders)
|
||||
{
|
||||
float scrollMax = (orderCount - MaxVisibleOrders) * (BarHeight + Spacing);
|
||||
float scrollBarHeight = listRect.height * (MaxVisibleOrders / (float)orderCount);
|
||||
float scrollBarY = curY + (scrollPosition / scrollMax) * (listRect.height - scrollBarHeight);
|
||||
|
||||
Rect scrollBarRect = new Rect(gizmoRect.xMax - 6f, scrollBarY, 4f, scrollBarHeight);
|
||||
Widgets.DrawBoxSolid(scrollBarRect, new Color(1f, 1f, 1f, 0.3f));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rect emptyRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, BarHeight);
|
||||
Text.Font = GameFont.Tiny;
|
||||
GUI.color = Color.gray;
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
Widgets.Label(emptyRect, "无订单 - 点击添加");
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
Text.Font = GameFont.Small;
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
return new GizmoResult(GizmoState.Clear);
|
||||
}
|
||||
|
||||
private void DrawOrderBar(Rect rect, OrderDisplayInfo order, int index)
|
||||
{
|
||||
// 背景
|
||||
Widgets.DrawBoxSolid(rect, new Color(0.08f, 0.08f, 0.1f, 0.9f));
|
||||
|
||||
if (order.status == OrderStatus.WaitingForLarva)
|
||||
{
|
||||
// 等待幼虫状态 - 显示标签
|
||||
Text.Font = GameFont.Tiny;
|
||||
Text.Anchor = TextAnchor.MiddleLeft;
|
||||
Rect labelRect = new Rect(rect.x + 4f, rect.y, rect.width - 28f, rect.height);
|
||||
GUI.color = new Color(1f, 0.8f, 0.4f);
|
||||
Widgets.Label(labelRect, $"🐛 {order.label} [等待幼虫]");
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 正在孵化 - 双向进度条
|
||||
float midX = rect.x + rect.width / 2f;
|
||||
float halfWidth = (rect.width - 28f) / 2f; // 留出取消按钮空间
|
||||
|
||||
// 品质进度(向左生长,青色)
|
||||
float qualityWidth = halfWidth * order.qualityProgress;
|
||||
Rect qualityRect = new Rect(midX - qualityWidth, rect.y + 2f, qualityWidth, rect.height - 4f);
|
||||
Color qualityColor = Color.Lerp(new Color(0.2f, 0.5f, 0.6f), new Color(0.3f, 0.8f, 0.9f), order.qualityProgress);
|
||||
Widgets.DrawBoxSolid(qualityRect, qualityColor);
|
||||
|
||||
// 生产进度(向右生长,绿色)
|
||||
float progressWidth = halfWidth * order.productionProgress;
|
||||
Rect progressRect = new Rect(midX, rect.y + 2f, progressWidth, rect.height - 4f);
|
||||
Color progressColor = Color.Lerp(new Color(0.3f, 0.5f, 0.3f), new Color(0.4f, 0.9f, 0.4f), order.productionProgress);
|
||||
Widgets.DrawBoxSolid(progressRect, progressColor);
|
||||
|
||||
// 中线
|
||||
Widgets.DrawLineVertical(midX, rect.y + 2f, rect.height - 4f);
|
||||
|
||||
// 标签
|
||||
Text.Font = GameFont.Tiny;
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
Rect labelRect = new Rect(rect.x, rect.y, rect.width - 24f, rect.height);
|
||||
string displayText = $"{order.label} {order.productionProgress.ToStringPercent("F0")}";
|
||||
GUI.color = Color.white;
|
||||
Widgets.Label(labelRect, displayText);
|
||||
|
||||
// 品质提示
|
||||
string tooltip = $"{order.label}\n生产进度: {order.productionProgress.ToStringPercent()}\n品质进度: {order.qualityProgress.ToStringPercent()}\n预计品质: {order.estimatedQuality}";
|
||||
TooltipHandler.TipRegion(rect, tooltip);
|
||||
}
|
||||
|
||||
// 取消按钮(右侧X)
|
||||
Rect cancelRect = new Rect(rect.xMax - 20f, rect.y + 2f, 18f, rect.height - 4f);
|
||||
if (Widgets.ButtonText(cancelRect, "X", false))
|
||||
{
|
||||
comp.RemoveOrderByIndex(index);
|
||||
}
|
||||
|
||||
// 边框
|
||||
Widgets.DrawBox(rect);
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
/// <summary>
|
||||
/// 双向进度条 Gizmo - 用于督虫孵化池
|
||||
/// 从中间向两边生长:左边品质进度,右边生产进度
|
||||
/// 支持滚动显示多个订单
|
||||
/// </summary>
|
||||
public class Gizmo_PawnProgressBar : Gizmo
|
||||
{
|
||||
private const float BarHeight = 22f;
|
||||
private const float Spacing = 4f;
|
||||
private const float Padding = 8f;
|
||||
private const float TitleHeight = 26f;
|
||||
private const float MaxVisibleOrders = 4;
|
||||
|
||||
private readonly CompQueuedPawnSpawnerWithFlux comp;
|
||||
private float scrollPosition = 0f;
|
||||
|
||||
public Gizmo_PawnProgressBar(CompQueuedPawnSpawnerWithFlux comp)
|
||||
{
|
||||
this.comp = comp;
|
||||
this.Order = -98f;
|
||||
}
|
||||
|
||||
public override float GetWidth(float maxWidth) => 220f;
|
||||
|
||||
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 = TitleHeight + Spacing;
|
||||
contentHeight += Mathf.Max(1, visibleCount) * (BarHeight + Spacing);
|
||||
contentHeight += Padding;
|
||||
|
||||
float totalHeight = Mathf.Max(75f, contentHeight);
|
||||
|
||||
Rect gizmoRect = new Rect(topLeft.x, topLeft.y - (totalHeight - 75f), GetWidth(maxWidth), totalHeight);
|
||||
Widgets.DrawWindowBackground(gizmoRect);
|
||||
|
||||
float curY = gizmoRect.y + Padding;
|
||||
|
||||
// 标题
|
||||
Rect titleRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, TitleHeight);
|
||||
Text.Font = GameFont.Small;
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
|
||||
string title = $"孵化进度 ({orderCount}/{comp.Props.productionQueueLimit})";
|
||||
Widgets.Label(titleRect, title);
|
||||
|
||||
curY += TitleHeight + Spacing;
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
Rect listRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, visibleCount * (BarHeight + Spacing));
|
||||
|
||||
if (orderCount > 0)
|
||||
{
|
||||
if (orderCount > MaxVisibleOrders)
|
||||
{
|
||||
float scrollMax = (orderCount - MaxVisibleOrders) * (BarHeight + Spacing);
|
||||
if (Mouse.IsOver(listRect))
|
||||
{
|
||||
scrollPosition -= Event.current.delta.y * 0.5f;
|
||||
scrollPosition = Mathf.Clamp(scrollPosition, 0f, scrollMax);
|
||||
}
|
||||
}
|
||||
|
||||
GUI.BeginClip(listRect);
|
||||
float drawY = -scrollPosition;
|
||||
|
||||
for (int i = 0; i < orderCount; i++)
|
||||
{
|
||||
var order = orders[i];
|
||||
Rect barRect = new Rect(0, drawY, listRect.width, BarHeight);
|
||||
|
||||
if (barRect.yMax > 0 && barRect.y < listRect.height)
|
||||
{
|
||||
DrawOrderBar(barRect, order, i);
|
||||
}
|
||||
drawY += BarHeight + Spacing;
|
||||
}
|
||||
|
||||
GUI.EndClip();
|
||||
|
||||
if (orderCount > MaxVisibleOrders)
|
||||
{
|
||||
float scrollMax = (orderCount - MaxVisibleOrders) * (BarHeight + Spacing);
|
||||
float scrollBarHeight = listRect.height * (MaxVisibleOrders / (float)orderCount);
|
||||
float scrollBarY = curY + (scrollPosition / scrollMax) * (listRect.height - scrollBarHeight);
|
||||
|
||||
Rect scrollBarRect = new Rect(gizmoRect.xMax - 6f, scrollBarY, 4f, scrollBarHeight);
|
||||
Widgets.DrawBoxSolid(scrollBarRect, new Color(1f, 1f, 1f, 0.3f));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rect emptyRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, BarHeight);
|
||||
Text.Font = GameFont.Tiny;
|
||||
GUI.color = Color.gray;
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
Widgets.Label(emptyRect, "无订单 - 点击添加");
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
Text.Font = GameFont.Small;
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
return new GizmoResult(GizmoState.Clear);
|
||||
}
|
||||
|
||||
private void DrawOrderBar(Rect rect, PawnOrderDisplayInfo order, int index)
|
||||
{
|
||||
Widgets.DrawBoxSolid(rect, new Color(0.08f, 0.08f, 0.1f, 0.9f));
|
||||
|
||||
if (order.status == OrderStatus.WaitingForLarva)
|
||||
{
|
||||
Text.Font = GameFont.Tiny;
|
||||
Text.Anchor = TextAnchor.MiddleLeft;
|
||||
Rect labelRect = new Rect(rect.x + 4f, rect.y, rect.width - 28f, rect.height);
|
||||
GUI.color = new Color(1f, 0.8f, 0.4f);
|
||||
Widgets.Label(labelRect, $"🐛 {order.label} [等待幼虫]");
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 双向进度条
|
||||
float midX = rect.x + rect.width / 2f;
|
||||
float halfWidth = (rect.width - 28f) / 2f;
|
||||
|
||||
// 品质进度(向左生长,青色)
|
||||
float qualityWidth = halfWidth * order.qualityProgress;
|
||||
Rect qualityRect = new Rect(midX - qualityWidth, rect.y + 2f, qualityWidth, rect.height - 4f);
|
||||
Color qualityColor = Color.Lerp(new Color(0.2f, 0.5f, 0.6f), new Color(0.3f, 0.8f, 0.9f), order.qualityProgress);
|
||||
Widgets.DrawBoxSolid(qualityRect, qualityColor);
|
||||
|
||||
// 生产进度(向右生长,绿色)
|
||||
float progressWidth = halfWidth * order.progress;
|
||||
Rect progressRect = new Rect(midX, rect.y + 2f, progressWidth, rect.height - 4f);
|
||||
Color progressColor = Color.Lerp(new Color(0.3f, 0.5f, 0.3f), new Color(0.4f, 0.9f, 0.4f), order.progress);
|
||||
Widgets.DrawBoxSolid(progressRect, progressColor);
|
||||
|
||||
// 中线
|
||||
Widgets.DrawLineVertical(midX, rect.y + 2f, rect.height - 4f);
|
||||
|
||||
// 标签
|
||||
Text.Font = GameFont.Tiny;
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
Rect labelRect = new Rect(rect.x, rect.y, rect.width - 24f, rect.height);
|
||||
GUI.color = Color.white;
|
||||
Widgets.Label(labelRect, $"{order.label} {order.progress.ToStringPercent("F0")}");
|
||||
|
||||
// 品质提示
|
||||
string tooltip = $"{order.label}\n生产进度: {order.progress.ToStringPercent()}\n品质进度: {order.qualityProgress.ToStringPercent()}\n预计品质: {order.estimatedQuality}\n剩余时间: {order.remainingTime}";
|
||||
TooltipHandler.TipRegion(rect, tooltip);
|
||||
}
|
||||
|
||||
Rect cancelRect = new Rect(rect.xMax - 20f, rect.y + 2f, 18f, rect.height - 4f);
|
||||
if (Widgets.ButtonText(cancelRect, "X", false))
|
||||
{
|
||||
comp.RemoveOrderByIndex(index);
|
||||
}
|
||||
|
||||
Widgets.DrawBox(rect);
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
/// <summary>
|
||||
/// 多订单进度 Gizmo - 用于大型孵化池
|
||||
/// 支持显示多个同时进行的订单进度
|
||||
/// </summary>
|
||||
public class Gizmo_QueuedIncubationProgress : Gizmo
|
||||
{
|
||||
private const float BarHeight = 18f;
|
||||
private const float Spacing = 4f;
|
||||
private const float Padding = 6f;
|
||||
private const float TitleHeight = 24f;
|
||||
|
||||
private readonly ThingComp comp;
|
||||
private readonly string title;
|
||||
|
||||
// 用于获取订单信息的委托
|
||||
public delegate List<(string label, float progress, string tooltip)> GetOrdersDelegate();
|
||||
public delegate void SelectTargetDelegate();
|
||||
public delegate void CallLarvaDelegate();
|
||||
public delegate bool CanAddOrderDelegate();
|
||||
public delegate bool HasSelectedTargetDelegate();
|
||||
|
||||
private readonly GetOrdersDelegate getOrders;
|
||||
private readonly SelectTargetDelegate selectTarget;
|
||||
private readonly CallLarvaDelegate callLarva;
|
||||
private readonly CanAddOrderDelegate canAddOrder;
|
||||
private readonly HasSelectedTargetDelegate hasSelectedTarget;
|
||||
|
||||
public Gizmo_QueuedIncubationProgress(
|
||||
ThingComp comp,
|
||||
string title,
|
||||
GetOrdersDelegate getOrders,
|
||||
SelectTargetDelegate selectTarget,
|
||||
CallLarvaDelegate callLarva,
|
||||
CanAddOrderDelegate canAddOrder,
|
||||
HasSelectedTargetDelegate hasSelectedTarget)
|
||||
{
|
||||
this.comp = comp;
|
||||
this.title = title;
|
||||
this.getOrders = getOrders;
|
||||
this.selectTarget = selectTarget;
|
||||
this.callLarva = callLarva;
|
||||
this.canAddOrder = canAddOrder;
|
||||
this.hasSelectedTarget = hasSelectedTarget;
|
||||
this.Order = -99f;
|
||||
}
|
||||
|
||||
public override float GetWidth(float maxWidth) => 200f;
|
||||
|
||||
public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
|
||||
{
|
||||
var orders = getOrders?.Invoke() ?? new List<(string, float, string)>();
|
||||
int orderCount = orders.Count;
|
||||
|
||||
// 计算动态高度
|
||||
float contentHeight = TitleHeight + Spacing;
|
||||
if (orderCount > 0)
|
||||
{
|
||||
contentHeight += orderCount * (BarHeight + Spacing);
|
||||
}
|
||||
else
|
||||
{
|
||||
contentHeight += BarHeight + Spacing; // 空状态提示
|
||||
}
|
||||
contentHeight += Padding;
|
||||
|
||||
float totalHeight = Mathf.Max(75f, contentHeight);
|
||||
|
||||
Rect gizmoRect = new Rect(topLeft.x, topLeft.y - (totalHeight - 75f), GetWidth(maxWidth), totalHeight);
|
||||
Widgets.DrawWindowBackground(gizmoRect);
|
||||
|
||||
float curY = gizmoRect.y + Padding;
|
||||
|
||||
// 标题区域
|
||||
Rect titleRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, TitleHeight);
|
||||
Text.Font = GameFont.Small;
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
|
||||
// 标题可点击用于选择目标
|
||||
bool canAdd = canAddOrder?.Invoke() ?? false;
|
||||
bool hasTarget = hasSelectedTarget?.Invoke() ?? false;
|
||||
|
||||
if (canAdd && !hasTarget)
|
||||
{
|
||||
GUI.color = new Color(0.5f, 0.9f, 1f);
|
||||
if (Widgets.ButtonText(titleRect, title + " ▼"))
|
||||
{
|
||||
selectTarget?.Invoke();
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
else if (hasTarget)
|
||||
{
|
||||
GUI.color = new Color(0.5f, 1f, 0.5f);
|
||||
if (Widgets.ButtonText(titleRect, "呼叫幼虫"))
|
||||
{
|
||||
callLarva?.Invoke();
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
else
|
||||
{
|
||||
Widgets.Label(titleRect, title);
|
||||
}
|
||||
|
||||
curY += TitleHeight + Spacing;
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
// 订单进度条
|
||||
if (orderCount > 0)
|
||||
{
|
||||
foreach (var order in orders)
|
||||
{
|
||||
Rect barRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, BarHeight);
|
||||
DrawOrderBar(barRect, order.label, order.progress, order.tooltip);
|
||||
curY += BarHeight + Spacing;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rect emptyRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, BarHeight);
|
||||
Text.Font = GameFont.Tiny;
|
||||
GUI.color = Color.gray;
|
||||
Widgets.Label(emptyRect, "无正在进行的订单");
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
Text.Font = GameFont.Small;
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
return new GizmoResult(GizmoState.Clear);
|
||||
}
|
||||
|
||||
private void DrawOrderBar(Rect rect, string label, float progress, string tooltip)
|
||||
{
|
||||
// 背景
|
||||
Widgets.DrawBoxSolid(rect, new Color(0.1f, 0.1f, 0.1f, 0.8f));
|
||||
|
||||
// 进度条
|
||||
Rect filledRect = new Rect(rect.x, rect.y, rect.width * Mathf.Clamp01(progress), rect.height);
|
||||
Color barColor = Color.Lerp(new Color(0.4f, 0.6f, 0.4f), new Color(0.3f, 0.8f, 0.3f), progress);
|
||||
Widgets.DrawBoxSolid(filledRect, barColor);
|
||||
|
||||
// 边框
|
||||
Widgets.DrawBox(rect);
|
||||
|
||||
// 标签
|
||||
Text.Font = GameFont.Tiny;
|
||||
Text.Anchor = TextAnchor.MiddleLeft;
|
||||
Rect labelRect = new Rect(rect.x + 4f, rect.y, rect.width - 50f, rect.height);
|
||||
Widgets.Label(labelRect, label);
|
||||
|
||||
// 百分比
|
||||
Text.Anchor = TextAnchor.MiddleRight;
|
||||
Rect pctRect = new Rect(rect.x, rect.y, rect.width - 4f, rect.height);
|
||||
Widgets.Label(pctRect, progress.ToStringPercent("F0"));
|
||||
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
// 提示
|
||||
if (!string.IsNullOrEmpty(tooltip))
|
||||
{
|
||||
TooltipHandler.TipRegion(rect, tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
@@ -9,12 +10,33 @@ namespace ArachnaeSwarm
|
||||
{
|
||||
private const int OperationDuration = 180; // 3 seconds = 180 ticks
|
||||
|
||||
// 目标建筑
|
||||
private Building_Ootheca Ootheca => (Building_Ootheca)job.targetA.Thing;
|
||||
// 目标:可以是建筑本身实现接口,也可以是建筑的组件实现接口
|
||||
private ILarvaActivatable GetActivatable()
|
||||
{
|
||||
var thing = job.targetA.Thing;
|
||||
if (thing == null) return null;
|
||||
|
||||
// 首先检查建筑本身是否实现接口
|
||||
if (thing is ILarvaActivatable activatable)
|
||||
return activatable;
|
||||
|
||||
// 然后检查建筑的组件
|
||||
if (thing is ThingWithComps thingWithComps)
|
||||
{
|
||||
foreach (var comp in thingWithComps.AllComps)
|
||||
{
|
||||
if (comp is ILarvaActivatable compActivatable)
|
||||
return compActivatable;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Thing TargetThing => job.targetA.Thing;
|
||||
|
||||
public override bool TryMakePreToilReservations(bool errorOnFailed)
|
||||
{
|
||||
// 动物和殖民者都可以预订
|
||||
return pawn.Reserve(job.targetA, job, 1, -1, null, errorOnFailed);
|
||||
}
|
||||
|
||||
@@ -22,26 +44,25 @@ namespace ArachnaeSwarm
|
||||
{
|
||||
// 验证目标建筑
|
||||
this.FailOnDespawnedNullOrForbidden(TargetIndex.A);
|
||||
this.FailOn(() => Ootheca == null);
|
||||
this.FailOn(() => GetActivatable() == null);
|
||||
|
||||
// 1. 移动到建筑
|
||||
yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.InteractionCell)
|
||||
.FailOnSomeonePhysicallyInteracting(TargetIndex.A);
|
||||
|
||||
// 2. 等待片刻(让动物有时间转身)
|
||||
// 2. 等待片刻
|
||||
yield return Toils_General.WaitWith(TargetIndex.A, 10, true, true);
|
||||
|
||||
// 3. 开始操作(3秒)
|
||||
// 3. 开始操作
|
||||
var operate = new Toil();
|
||||
operate.initAction = () =>
|
||||
{
|
||||
// 通知建筑幼虫已到达
|
||||
Ootheca?.NotifyLarvaArrived(pawn);
|
||||
GetActivatable()?.NotifyLarvaArrived(pawn);
|
||||
};
|
||||
operate.tickAction = () =>
|
||||
{
|
||||
// 面向建筑
|
||||
pawn.rotationTracker.FaceCell(Ootheca.Position);
|
||||
if (TargetThing != null)
|
||||
pawn.rotationTracker.FaceCell(TargetThing.Position);
|
||||
};
|
||||
operate.defaultCompleteMode = ToilCompleteMode.Delay;
|
||||
operate.defaultDuration = OperationDuration;
|
||||
@@ -54,13 +75,10 @@ namespace ArachnaeSwarm
|
||||
{
|
||||
initAction = () =>
|
||||
{
|
||||
// 操作完成,删除幼虫并开始孵化
|
||||
if (Ootheca != null && pawn != null && pawn.def.defName == "ArachnaeBase_Race_Larva")
|
||||
var activatable = GetActivatable();
|
||||
if (activatable != null && pawn != null && pawn.def.defName == "ArachnaeBase_Race_Larva")
|
||||
{
|
||||
// 通知建筑幼虫操作完成
|
||||
Ootheca.NotifyLarvaOperationComplete(pawn);
|
||||
|
||||
// 删除幼虫
|
||||
activatable.NotifyLarvaOperationComplete(pawn);
|
||||
pawn.Destroy(DestroyMode.Vanish);
|
||||
}
|
||||
},
|
||||
@@ -70,7 +88,7 @@ namespace ArachnaeSwarm
|
||||
|
||||
public override string GetReport()
|
||||
{
|
||||
if (Ootheca != null)
|
||||
if (GetActivatable() != null)
|
||||
{
|
||||
return "ActivatingOotheca".Translate();
|
||||
}
|
||||
|
||||
14
Source/ArachnaeSwarm/Buildings/ILarvaActivatable.cs
Normal file
14
Source/ArachnaeSwarm/Buildings/ILarvaActivatable.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
/// <summary>
|
||||
/// 可由幼虫激活的孵化建筑接口
|
||||
/// Building_Ootheca 和 Building_EquipmentOotheca 都实现此接口
|
||||
/// </summary>
|
||||
public interface ILarvaActivatable
|
||||
{
|
||||
void NotifyLarvaArrived(Pawn larva);
|
||||
void NotifyLarvaOperationComplete(Pawn larva);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user