diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 995e1cc..c1336e9 100644 Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.pdb b/1.6/1.6/Assemblies/ArachnaeSwarm.pdb index 38f6111..ad94408 100644 Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.pdb and b/1.6/1.6/Assemblies/ArachnaeSwarm.pdb differ diff --git a/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects.xml b/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects.xml index 0545910..4a45731 100644 --- a/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects.xml +++ b/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects.xml @@ -487,6 +487,18 @@
  • ARA_Technology_2NPT
  • + + ARA_Technology_4NPT + + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许虫族建造孵化池。一种专用于批量生产虫族的孵化场地。孵化池的孵化效率比孵化茧更高。 + 1800 + 12.00 + 2.10 + ARA_ResearchBench + +
  • ARA_Technology_1NPT
  • +
    +
    ARA_Technology_8CPE diff --git a/1.6/1.6/Defs/Thing_building/ARA_Ootheca.xml b/1.6/1.6/Defs/Thing_building/ARA_Ootheca.xml index ed68cb9..9cb13bf 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_Ootheca.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_Ootheca.xml @@ -701,89 +701,124 @@ - +
  • 5 0.5
  • ArachnaeBase_Race_Larva
  • + - - 10 - 30 - 0.00001 - -
  • - Legendary - 0.99 -
  • -
  • - Masterwork - 0.90 -
  • -
  • - Excellent - 0.70 -
  • -
  • - Good - 0.50 -
  • -
  • - Normal - 0.20 -
  • -
  • - Poor - 0.10 -
  • -
    - - -
  • - ArachnaeNode_Race_Myrmecocystus - 240000 - 100.0 -
  • + +
  • +
  • ArachnaeNode_Race_ShieldHead - 180000 - 40.0 + 3 + +
  • ARA_Incubator_1_Reward_Hediffs
  • +
  • ARA_Incubator_2_Reward_Hediffs
  • +
  • ARA_Incubator_3_Reward_Hediffs
  • +
  • ARA_Incubator_4_Reward_Hediffs
  • +
  • ARA_Incubator_5_Reward_Hediffs
  • +
  • ARA_Incubator_6_Reward_Hediffs
  • +
  • ARA_Incubator_7_Reward_Hediffs
  • +
  • ARA_Incubator_8_Reward_Hediffs
  • +
  • ArachnaeNode_Race_WeaponSmith - 180000 - 40.0 + 3 + +
  • ARA_Incubator_1_Reward_Hediffs
  • +
  • ARA_Incubator_2_Reward_Hediffs
  • +
  • ARA_Incubator_3_Reward_Hediffs
  • +
  • ARA_Incubator_4_Reward_Hediffs
  • +
  • ARA_Incubator_5_Reward_Hediffs
  • +
  • ARA_Incubator_6_Reward_Hediffs
  • +
  • ARA_Incubator_7_Reward_Hediffs
  • +
  • ARA_Incubator_8_Reward_Hediffs
  • +
  • ArachnaeNode_Race_Fighter - 90000 - 20.0 + 1.5 ARA_Technology_1KYC + +
  • ARA_Incubator_1_Reward_Hediffs
  • +
  • ARA_Incubator_2_Reward_Hediffs
  • +
  • ARA_Incubator_3_Reward_Hediffs
  • +
  • ARA_Incubator_4_Reward_Hediffs
  • +
  • ARA_Incubator_5_Reward_Hediffs
  • +
  • ARA_Incubator_6_Reward_Hediffs
  • +
  • ARA_Incubator_7_Reward_Hediffs
  • +
  • ARA_Incubator_8_Reward_Hediffs
  • + + +
  • + ArachnaeNode_Race_Myrmecocystus + 4 + +
  • ARA_Incubator_1_Reward_Hediffs
  • +
  • ARA_Incubator_2_Reward_Hediffs
  • +
  • ARA_Incubator_3_Reward_Hediffs
  • +
  • ARA_Incubator_4_Reward_Hediffs
  • +
  • ARA_Incubator_5_Reward_Hediffs
  • +
  • ARA_Incubator_6_Reward_Hediffs
  • +
  • ARA_Incubator_7_Reward_Hediffs
  • +
  • ARA_Incubator_8_Reward_Hediffs
  • +
  • ArachnaeNode_Race_Smokepop - 180000 - 60.0 + 3 ARA_Technology_5KYC + +
  • ARA_Incubator_1_Reward_Hediffs
  • +
  • ARA_Incubator_2_Reward_Hediffs
  • +
  • ARA_Incubator_3_Reward_Hediffs
  • +
  • ARA_Incubator_4_Reward_Hediffs
  • +
  • ARA_Incubator_5_Reward_Hediffs
  • +
  • ARA_Incubator_6_Reward_Hediffs
  • +
  • ARA_Incubator_7_Reward_Hediffs
  • +
  • ARA_Incubator_8_Reward_Hediffs
  • +
  • ArachnaeNode_Race_Skyraider - 120000 - 80.0 + 2 ARA_Technology_2KYC + +
  • ARA_Incubator_1_Reward_Hediffs
  • +
  • ARA_Incubator_2_Reward_Hediffs
  • +
  • ARA_Incubator_3_Reward_Hediffs
  • +
  • ARA_Incubator_4_Reward_Hediffs
  • +
  • ARA_Incubator_5_Reward_Hediffs
  • +
  • ARA_Incubator_6_Reward_Hediffs
  • +
  • ARA_Incubator_7_Reward_Hediffs
  • +
  • ARA_Incubator_8_Reward_Hediffs
  • +
  • ARA_MimicNematodeShamblerSwarmer - 600 - 10.0 + 0.01 ARA_Technology_6MEN + +
  • ARA_Incubator_1_Reward_Hediffs
  • +
  • ARA_Incubator_2_Reward_Hediffs
  • +
  • ARA_Incubator_3_Reward_Hediffs
  • +
  • ARA_Incubator_4_Reward_Hediffs
  • +
  • ARA_Incubator_5_Reward_Hediffs
  • +
  • ARA_Incubator_6_Reward_Hediffs
  • +
  • ARA_Incubator_7_Reward_Hediffs
  • +
  • ARA_Incubator_8_Reward_Hediffs
  • + -
    + - +
  • 300.0 @@ -796,7 +831,7 @@ true
  • - +
  • ARA_NutrientNetworkTower
  • diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index 4b9deeb..bd7fa47 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -124,6 +124,7 @@ + diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompQueuedInteractiveProducerWithFlux.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompQueuedInteractiveProducerWithFlux.cs index bc4a678..2c828e9 100644 --- a/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompQueuedInteractiveProducerWithFlux.cs +++ b/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompQueuedInteractiveProducerWithFlux.cs @@ -401,6 +401,8 @@ namespace ArachnaeSwarm } } + public void ShowOrderMenuPublic() => ShowOrderMenu(); + private void ShowOrderMenu() { var options = new List(); diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_SpawnPawnFromList/CompQueuedPawnSpawnerWithFlux.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_SpawnPawnFromList/CompQueuedPawnSpawnerWithFlux.cs index 9005f18..92415ec 100644 --- a/Source/ArachnaeSwarm/Building_Comps/ARA_SpawnPawnFromList/CompQueuedPawnSpawnerWithFlux.cs +++ b/Source/ArachnaeSwarm/Building_Comps/ARA_SpawnPawnFromList/CompQueuedPawnSpawnerWithFlux.cs @@ -11,8 +11,8 @@ namespace ArachnaeSwarm // 订单状态 public enum OrderStatus { - WaitingForLarva = 0, // 等待幼虫激活 - Incubating = 1, // 正在孵化 + WaitingForLarva = 0, + Incubating = 1, } // 用于 Gizmo 显示的订单信息 @@ -25,10 +25,10 @@ namespace ArachnaeSwarm public string estimatedQuality; } - // 带状态和品质的订单 + // 带状态和品质的督虫订单 public class QueuedPawnOrder : IExposable { - public QueuedPawnSpawnEntry entry; + public IncubationConfig config; // 使用 IncubationConfig 而不是 QueuedPawnSpawnEntry public OrderStatus status = OrderStatus.WaitingForLarva; public int spawnUntilTick = -1; @@ -40,7 +40,7 @@ namespace ArachnaeSwarm public void ExposeData() { - Scribe_Deep.Look(ref entry, "entry"); + Scribe_Deep.Look(ref config, "config"); Scribe_Values.Look(ref status, "status", OrderStatus.WaitingForLarva); Scribe_Values.Look(ref spawnUntilTick, "spawnUntilTick", -1); Scribe_Values.Look(ref qualityProgress, "qualityProgress", 0f); @@ -50,15 +50,11 @@ namespace ArachnaeSwarm public class CompProperties_QueuedPawnSpawnerWithFlux : CompProperties { - public List spawnablePawns; - public List whitelist; + public List whitelist; // 允许激活的 pawn 类型(幼虫) public int productionQueueLimit = 5; public float minNutritionToStart = 0.1f; // 质量系统 - public float minSafeTemperature = 7f; - public float maxSafeTemperature = 32f; - public float penaltyPerDegreePerTick = 0.00001f; public List qualityThresholds; public CompProperties_QueuedPawnSpawnerWithFlux() @@ -82,10 +78,15 @@ namespace ArachnaeSwarm // === 组件引用 === private CompRefuelableNutrition _fuelComp; private CompAffectedByFacilities _facilitiesComp; + private CompIncubatorData _incubatorDataComp; public CompProperties_QueuedPawnSpawnerWithFlux Props => (CompProperties_QueuedPawnSpawnerWithFlux)props; private CompRefuelableNutrition FuelComp => _fuelComp ?? (_fuelComp = parent.GetComp()); private CompAffectedByFacilities FacilitiesComp => _facilitiesComp ?? (_facilitiesComp = parent.GetComp()); + private CompIncubatorData IncubatorDataComp => _incubatorDataComp ?? (_incubatorDataComp = parent.GetComp()); + + // 获取可孵化配置列表(从 CompIncubatorData) + public List IncubationConfigs => IncubatorDataComp?.IncubationConfigs ?? new List(); // === IFluxController 接口实现 === public float NeutronFlux => neutronFlux; @@ -120,17 +121,18 @@ namespace ArachnaeSwarm base.Initialize(props); _fuelComp = parent.GetComp(); _facilitiesComp = parent.GetComp(); + _incubatorDataComp = parent.GetComp(); } // === 订单管理 === - public void AddOrder(QueuedPawnSpawnEntry entry) + public void AddOrder(IncubationConfig config) { if (orders.Count >= Props.productionQueueLimit) { Messages.Message("队列已满!", MessageTypeDefOf.RejectInput); return; } - orders.Add(new QueuedPawnOrder { entry = entry, status = OrderStatus.WaitingForLarva }); + orders.Add(new QueuedPawnOrder { config = config, status = OrderStatus.WaitingForLarva }); } public void RemoveOrder(QueuedPawnOrder order) => orders.Remove(order); @@ -148,14 +150,14 @@ namespace ArachnaeSwarm float prodProgress = GetProgress(order); result.Add(new PawnOrderDisplayInfo { - label = order.entry.pawnKind.LabelCap, + label = order.config?.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() + estimatedQuality = GetEstimatedQuality(order.QualityPercent) }); } return result; @@ -163,24 +165,21 @@ namespace ArachnaeSwarm private float GetProgress(QueuedPawnOrder order) { - if (order.status != OrderStatus.Incubating || order.spawnUntilTick <= 0) return 0f; - int totalTicks = order.entry.delayTicks; + if (order.status != OrderStatus.Incubating || order.spawnUntilTick <= 0 || order.config == null) return 0f; + int totalTicks = Mathf.RoundToInt(order.config.daysRequired * 60000); 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) + private string GetEstimatedQuality(float qualityPercent) { - 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; + if (qualityPercent >= 0.99f) return "传奇"; + if (qualityPercent >= 0.90f) return "杰作"; + if (qualityPercent >= 0.70f) return "优秀"; + if (qualityPercent >= 0.50f) return "良好"; + if (qualityPercent >= 0.20f) return "普通"; + return "较差"; } // === 幼虫激活逻辑 === @@ -194,9 +193,14 @@ namespace ArachnaeSwarm } int called = 0; - var availableLarvae = FindAvailableLarvae(neededLarvae); + int found = 0; + var availableLarvae = FindAvailableLarvae(neededLarvae * 2); + found = availableLarvae.Count; + foreach (var larva in availableLarvae) { + if (called >= neededLarvae) break; + var job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_OperateIncubator"), parent); if (larva.jobs.TryTakeOrderedJob(job, JobTag.Misc)) { @@ -206,28 +210,32 @@ namespace ArachnaeSwarm } if (called > 0) - Messages.Message($"已呼叫 {called} 只幼虫", MessageTypeDefOf.PositiveEvent); + Messages.Message($"已呼叫 {called} 只幼虫 (找到{found}只)", MessageTypeDefOf.PositiveEvent); else - Messages.Message("未找到可用的幼虫!", MessageTypeDefOf.RejectInput); + Messages.Message($"未找到可用的幼虫! (需要{neededLarvae}只,找到{found}只)", MessageTypeDefOf.RejectInput); } private List FindAvailableLarvae(int maxCount) { var result = new List(); - 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); - } + + if (pawn.def.defName != "ArachnaeBase_Race_Larva") continue; + if (pawn.Downed || pawn.Dead) continue; + if (pawn.Faction != parent.Faction) continue; + if (assignedLarvae.Contains(pawn)) continue; + + bool isBusy = pawn.CurJobDef != null && + pawn.CurJobDef != JobDefOf.Wait_Wander && + pawn.CurJobDef != JobDefOf.GotoWander && + pawn.CurJobDef.defName != "Wait"; + if (isBusy) continue; + + result.Add(pawn); } return result; } @@ -237,11 +245,12 @@ namespace ArachnaeSwarm public void NotifyLarvaOperationComplete(Pawn larva) { var waitingOrder = orders.FirstOrDefault(o => o.status == OrderStatus.WaitingForLarva); - if (waitingOrder != null) + if (waitingOrder != null && waitingOrder.config != null) { waitingOrder.status = OrderStatus.Incubating; - waitingOrder.spawnUntilTick = Find.TickManager.TicksGame + waitingOrder.entry.delayTicks; - waitingOrder.qualityTotal = waitingOrder.entry.delayTicks; + int totalTicks = Mathf.RoundToInt(waitingOrder.config.daysRequired * 60000); + waitingOrder.spawnUntilTick = Find.TickManager.TicksGame + totalTicks; + waitingOrder.qualityTotal = totalTicks; waitingOrder.qualityProgress = 0f; } assignedLarvae.Remove(larva); @@ -254,22 +263,19 @@ namespace ArachnaeSwarm assignedLarvae.RemoveAll(l => l == null || l.Dead || l.Destroyed); - bool hasFuel = FuelComp?.HasFuel ?? true; + bool hasFuel = (FuelComp?.Fuel ?? 10f) > 0.01f; - // 自动模式调节 if (IsAutoMode && parent.IsHashIntervalTick(250) && IsIncubating) { CalculateAutoFlux(); } - // 消耗燃料 if (IsIncubating && FuelComp != null && neutronFlux > 0.01f) { float fuelPerTick = (50f * FluxEfficiency * IncubatingCount) / 60000f; FuelComp.ConsumeFuel(fuelPerTick); } - // 处理正在孵化的订单 if (hasFuel && !IsDormant) { float speedFactor = 1f + (FacilitiesComp?.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor")) ?? 0f); @@ -277,7 +283,6 @@ namespace ArachnaeSwarm foreach (var order in orders.Where(o => o.status == OrderStatus.Incubating)) { - // 进度推进 float extraProgress = fluxSpeed - 1f; if (extraProgress > 0) { @@ -286,14 +291,12 @@ namespace ArachnaeSwarm order.spawnUntilTick -= extraTicks; } - // 品质累积(低通量时累积更快) float qualityBonus = 1f + (1f - neutronFlux) * 0.5f; float qualityGain = speedFactor * qualityBonus; order.qualityProgress = Mathf.Min(order.qualityProgress + qualityGain, order.qualityTotal); } } - // 完成订单 orders.RemoveAll(order => { if (order.status == OrderStatus.Incubating && @@ -309,30 +312,39 @@ namespace ArachnaeSwarm private void CompleteOrder(QueuedPawnOrder order) { - Pawn pawn = PawnGenerator.GeneratePawn(new PawnGenerationRequest(order.entry.pawnKind, parent.Faction)); + if (order.config?.pawnKind == null) return; + + Pawn pawn = PawnGenerator.GeneratePawn(new PawnGenerationRequest(order.config.pawnKind, parent.Faction)); if (pawn != null) { - // 应用品质效果到 Pawn - ApplyQualityEffects(pawn, order.QualityPercent); + ApplyQualityEffects(pawn, order.QualityPercent, order.config); GenPlace.TryPlaceThing(pawn, parent.Position, parent.Map, ThingPlaceMode.Near); } } - private void ApplyQualityEffects(Pawn pawn, float qualityPercent) + private void ApplyQualityEffects(Pawn pawn, float qualityPercent, IncubationConfig config) { - // 基于品质百分比调整能力 - // 0.0 = 最差, 1.0 = 最佳 - float statBonus = qualityPercent * 0.3f; // 最多+30% - - // 可以在这里添加特定的品质效果 - // 例如:pawn.health, pawn.skills 等 + // 应用 Hediff 奖励 + if (config != null && config.extraHediffs != null) + { + var rewardHediffs = config.GetRewardHediffs(qualityPercent); + if (rewardHediffs != null) + { + foreach (var hediffDef in rewardHediffs) + { + if (hediffDef != null) + { + pawn.health.AddHediff(hediffDef); + } + } + } + } } private void CalculateAutoFlux() { if (fluxMode == FluxMode.Manual) return; - // 找到品质最低的订单来决定通量 var incubating = orders.Where(o => o.status == OrderStatus.Incubating).ToList(); if (!incubating.Any()) return; @@ -367,13 +379,9 @@ namespace ArachnaeSwarm foreach (var g in base.CompGetGizmosExtra()) yield return g; 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) { yield return new Command_Action @@ -385,7 +393,6 @@ namespace ArachnaeSwarm }; } - // 呼叫幼虫按钮 int needed = WaitingForLarvaCount - assignedLarvae.Count; if (needed > 0) { @@ -399,26 +406,31 @@ namespace ArachnaeSwarm } } + public void ShowOrderMenuPublic() => ShowOrderMenu(); + private void ShowOrderMenu() { var options = new List(); + var configs = IncubationConfigs; - foreach (var entry in Props.spawnablePawns) + foreach (var config in configs) { - if (entry.requiredResearch != null && !entry.requiredResearch.IsFinished) + if (config?.pawnKind == null) continue; + + if (config.requiredResearch != null && !config.requiredResearch.IsFinished) { options.Add(new FloatMenuOption( - entry.pawnKind.LabelCap + " (需要研究: " + entry.requiredResearch.LabelCap + ")", + config.pawnKind.LabelCap + " (需要研究: " + config.requiredResearch.LabelCap + ")", null )); } else { - var capturedEntry = entry; + var capturedConfig = config; options.Add(new FloatMenuOption( - entry.pawnKind.LabelCap, + config.pawnKind.LabelCap, () => { - AddOrder(capturedEntry); + AddOrder(capturedConfig); if (orders.Count < Props.productionQueueLimit) ShowOrderMenu(); } @@ -429,7 +441,7 @@ namespace ArachnaeSwarm if (options.Count > 0) Find.WindowStack.Add(new FloatMenu(options, "选择孵化目标")); else - Messages.Message("没有可用的孵化选项", MessageTypeDefOf.RejectInput); + Messages.Message("没有可用的孵化选项(检查 CompIncubatorData 配置)", MessageTypeDefOf.RejectInput); } public override void PostExposeData() diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_DualProgressBar.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_DualProgressBar.cs index c4f97e8..2e948d0 100644 --- a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_DualProgressBar.cs +++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_DualProgressBar.cs @@ -7,16 +7,20 @@ namespace ArachnaeSwarm { /// /// 双向进度条 Gizmo - 用于物品孵化池 - /// 从中间向两边生长:左边品质进度,右边生产进度 - /// 支持滚动显示多个订单 + /// 样式与旧版 Gizmo_IncubationProgress 统一 /// + [StaticConstructorOnStartup] public class Gizmo_DualProgressBar : Gizmo { - private const float BarHeight = 22f; + private const float Width = 200f; + private const float BarHeight = 18f; private const float Spacing = 4f; private const float Padding = 8f; - private const float TitleHeight = 26f; - private const float MaxVisibleOrders = 4; + 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; private float scrollPosition = 0f; @@ -27,7 +31,7 @@ namespace ArachnaeSwarm this.Order = -98f; } - public override float GetWidth(float maxWidth) => 220f; + public override float GetWidth(float maxWidth) => Mathf.Min(Width, maxWidth); public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms) { @@ -36,42 +40,68 @@ namespace ArachnaeSwarm // 计算高度 int visibleCount = Mathf.Min(orderCount, (int)MaxVisibleOrders); - float contentHeight = TitleHeight + Spacing; - contentHeight += Mathf.Max(1, visibleCount) * (BarHeight + Spacing); - contentHeight += Padding; - + float contentHeight = Padding * 2 + Text.LineHeight + Spacing; + contentHeight += Mathf.Max(1, visibleCount) * (BarHeight + Spacing + 14f); float totalHeight = Mathf.Max(75f, contentHeight); - Rect gizmoRect = new Rect(topLeft.x, topLeft.y - (totalHeight - 75f), GetWidth(maxWidth), totalHeight); - Widgets.DrawWindowBackground(gizmoRect); + Rect rect = new Rect(topLeft.x, topLeft.y - (totalHeight - 75f), GetWidth(maxWidth), totalHeight); + Widgets.DrawWindowBackground(rect); - float curY = gizmoRect.y + Padding; + Rect innerRect = rect.ContractedBy(Padding); + float curY = innerRect.y; - // 标题 - 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)); - + Rect titleRect = new Rect(innerRect.x, curY, innerRect.width, Text.LineHeight); + + string title; + bool canAdd = orderCount < comp.Props.productionQueueLimit; + if (orderCount > 0) { - // 滚动处理 - if (orderCount > MaxVisibleOrders) + title = orders[0].label; + if (orderCount > 1) title += $" (+{orderCount - 1})"; + } + else + { + title = "选择生产目标..."; + } + + if (canAdd) + { + if (Mouse.IsOver(titleRect)) { - float scrollMax = (orderCount - MaxVisibleOrders) * (BarHeight + Spacing); - if (Mouse.IsOver(listRect)) - { - scrollPosition -= Event.current.delta.y * 0.5f; - scrollPosition = Mathf.Clamp(scrollPosition, 0f, scrollMax); - } + 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) + " (满)"); + GUI.color = Color.white; + } + curY += Text.LineHeight + Spacing; + + // === 订单列表 === + if (orderCount > 0) + { + float listHeight = Mathf.Min(visibleCount, orderCount) * (BarHeight + Spacing + 14f); + Rect listRect = new Rect(innerRect.x, curY, innerRect.width, listHeight); + + if (orderCount > MaxVisibleOrders && Mouse.IsOver(listRect)) + { + float scrollMax = (orderCount - MaxVisibleOrders) * (BarHeight + Spacing + 14f); + scrollPosition -= Event.current.delta.y * 0.5f; + scrollPosition = Mathf.Clamp(scrollPosition, 0f, scrollMax); } GUI.BeginClip(listRect); @@ -80,103 +110,87 @@ namespace ArachnaeSwarm for (int i = 0; i < orderCount; i++) { var order = orders[i]; - Rect barRect = new Rect(0, drawY, listRect.width, BarHeight); + float itemHeight = BarHeight + 14f; + Rect itemRect = new Rect(0, drawY, listRect.width, itemHeight); - if (barRect.yMax > 0 && barRect.y < listRect.height) + if (itemRect.yMax > 0 && itemRect.y < listRect.height) { - DrawOrderBar(barRect, order, i); + DrawOrderItem(itemRect, order, i); } - drawY += BarHeight + Spacing; + drawY += itemHeight + 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 = new Color(0.7f, 0.7f, 0.7f); + Widgets.Label(new Rect(innerRect.x, curY, innerRect.width, 20f), "就绪 - 点击上方选择目标"); 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) + private void DrawOrderItem(Rect rect, OrderDisplayInfo order, int index) { - // 背景 - Widgets.DrawBoxSolid(rect, new Color(0.08f, 0.08f, 0.1f, 0.9f)); - + float labelHeight = 14f; + + Text.Font = GameFont.Tiny; + Rect labelRect = new Rect(rect.x, rect.y, rect.width - 20f, labelHeight); + 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; + Widgets.Label(labelRect, $"{order.label} [等待幼虫]"); } 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); + Widgets.Label(labelRect, $"{order.label} {order.productionProgress.ToStringPercent("F0")}"); } + GUI.color = Color.white; - // 取消按钮(右侧X) - Rect cancelRect = new Rect(rect.xMax - 20f, rect.y + 2f, 18f, rect.height - 4f); - if (Widgets.ButtonText(cancelRect, "X", false)) + Rect cancelRect = new Rect(rect.xMax - 16f, rect.y, 16f, labelHeight); + if (Widgets.ButtonText(cancelRect, "×", false)) { comp.RemoveOrderByIndex(index); } - // 边框 - Widgets.DrawBox(rect); - Text.Anchor = TextAnchor.UpperLeft; + 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); + + string tooltip = $"{order.label}\n品质: {order.qualityProgress.ToStringPercent()} → {order.estimatedQuality}\n进度: {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, "等待幼虫激活"); + GUI.color = Color.white; + Text.Anchor = TextAnchor.UpperLeft; + } } } } diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_PawnProgressBar.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_PawnProgressBar.cs index 1c1aabb..4ca140c 100644 --- a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_PawnProgressBar.cs +++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_PawnProgressBar.cs @@ -7,16 +7,20 @@ namespace ArachnaeSwarm { /// /// 双向进度条 Gizmo - 用于督虫孵化池 - /// 从中间向两边生长:左边品质进度,右边生产进度 - /// 支持滚动显示多个订单 + /// 样式与旧版 Gizmo_IncubationProgress 统一 /// + [StaticConstructorOnStartup] public class Gizmo_PawnProgressBar : Gizmo { - private const float BarHeight = 22f; + private const float Width = 200f; + private const float BarHeight = 18f; private const float Spacing = 4f; private const float Padding = 8f; - private const float TitleHeight = 26f; - private const float MaxVisibleOrders = 4; + 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 CompQueuedPawnSpawnerWithFlux comp; private float scrollPosition = 0f; @@ -27,48 +31,81 @@ namespace ArachnaeSwarm this.Order = -98f; } - public override float GetWidth(float maxWidth) => 220f; + 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 = TitleHeight + Spacing; - contentHeight += Mathf.Max(1, visibleCount) * (BarHeight + Spacing); - contentHeight += Padding; - + float contentHeight = Padding * 2 + Text.LineHeight + Spacing; // 标题 + contentHeight += Mathf.Max(1, visibleCount) * (BarHeight + Spacing + 14f); // 订单(进度条+标签) float totalHeight = Mathf.Max(75f, contentHeight); - Rect gizmoRect = new Rect(topLeft.x, topLeft.y - (totalHeight - 75f), GetWidth(maxWidth), totalHeight); - Widgets.DrawWindowBackground(gizmoRect); + Rect rect = new Rect(topLeft.x, topLeft.y - (totalHeight - 75f), GetWidth(maxWidth), totalHeight); + Widgets.DrawWindowBackground(rect); - float curY = gizmoRect.y + Padding; + Rect innerRect = rect.ContractedBy(Padding); + float curY = innerRect.y; - // 标题 - 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)); - + Rect titleRect = new Rect(innerRect.x, curY, innerRect.width, Text.LineHeight); + + string title; + bool canAdd = orderCount < comp.Props.productionQueueLimit; + if (orderCount > 0) { - if (orderCount > MaxVisibleOrders) + // 显示第一个订单名称 + title = orders[0].label; + if (orderCount > 1) title += $" (+{orderCount - 1})"; + } + else + { + title = "选择孵化目标..."; + } + + // 标题按钮 + if (canAdd) + { + if (Mouse.IsOver(titleRect)) { - float scrollMax = (orderCount - MaxVisibleOrders) * (BarHeight + Spacing); - if (Mouse.IsOver(listRect)) - { - scrollPosition -= Event.current.delta.y * 0.5f; - scrollPosition = Mathf.Clamp(scrollPosition, 0f, scrollMax); - } + 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) + " (满)"); + GUI.color = Color.white; + } + curY += Text.LineHeight + Spacing; + + // === 订单列表 === + if (orderCount > 0) + { + float listHeight = Mathf.Min(visibleCount, orderCount) * (BarHeight + Spacing + 14f); + Rect listRect = new Rect(innerRect.x, curY, innerRect.width, listHeight); + + // 滚动支持 + if (orderCount > MaxVisibleOrders && Mouse.IsOver(listRect)) + { + float scrollMax = (orderCount - MaxVisibleOrders) * (BarHeight + Spacing + 14f); + scrollPosition -= Event.current.delta.y * 0.5f; + scrollPosition = Mathf.Clamp(scrollPosition, 0f, scrollMax); } GUI.BeginClip(listRect); @@ -77,97 +114,98 @@ namespace ArachnaeSwarm for (int i = 0; i < orderCount; i++) { var order = orders[i]; - Rect barRect = new Rect(0, drawY, listRect.width, BarHeight); + float itemHeight = BarHeight + 14f; + Rect itemRect = new Rect(0, drawY, listRect.width, itemHeight); - if (barRect.yMax > 0 && barRect.y < listRect.height) + if (itemRect.yMax > 0 && itemRect.y < listRect.height) { - DrawOrderBar(barRect, order, i); + DrawOrderItem(itemRect, order, i); } - drawY += BarHeight + Spacing; + drawY += itemHeight + 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 = new Color(0.7f, 0.7f, 0.7f); + Widgets.Label(new Rect(innerRect.x, curY, innerRect.width, 20f), "就绪 - 点击上方选择目标"); 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) + private void DrawOrderItem(Rect rect, PawnOrderDisplayInfo order, int index) { - Widgets.DrawBoxSolid(rect, new Color(0.08f, 0.08f, 0.1f, 0.9f)); - + float labelHeight = 14f; + + // 标签行 + Text.Font = GameFont.Tiny; + Rect labelRect = new Rect(rect.x, rect.y, rect.width - 20f, labelHeight); + 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; + Widgets.Label(labelRect, $"{order.label} [等待幼虫]"); } 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); + Widgets.Label(labelRect, $"{order.label} {order.progress.ToStringPercent("F0")}"); } + GUI.color = Color.white; - Rect cancelRect = new Rect(rect.xMax - 20f, rect.y + 2f, 18f, rect.height - 4f); - if (Widgets.ButtonText(cancelRect, "X", false)) + // 取消按钮 + Rect cancelRect = new Rect(rect.xMax - 16f, rect.y, 16f, labelHeight); + if (Widgets.ButtonText(cancelRect, "×", false)) { comp.RemoveOrderByIndex(index); } - Widgets.DrawBox(rect); - Text.Anchor = TextAnchor.UpperLeft; + // 进度条 + 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.progress; + 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品质: {order.qualityProgress.ToStringPercent()} → {order.estimatedQuality}\n进度: {order.progress.ToStringPercent()}\n剩余: {order.remainingTime}"; + 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, "等待幼虫激活"); + GUI.color = Color.white; + Text.Anchor = TextAnchor.UpperLeft; + } } } } diff --git a/Source/ArachnaeSwarm/Buildings/IncubatorUtils.cs b/Source/ArachnaeSwarm/Buildings/IncubatorUtils.cs new file mode 100644 index 0000000..f370a52 --- /dev/null +++ b/Source/ArachnaeSwarm/Buildings/IncubatorUtils.cs @@ -0,0 +1,275 @@ +using RimWorld; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + /// + /// 孵化器通用工具类 + /// 统一核心算法,保持各孵化器行为一致 + /// + public static class IncubatorUtils + { + // ============================================ + // 通量系统 + // ============================================ + + /// + /// 计算通量效率(0-1之间,通量越高效率越高) + /// + public static float GetFluxEfficiency(float neutronFlux) + { + // 与 IFluxControllerExtensions.GetEfficiency 保持一致 + return Mathf.Pow(neutronFlux, 0.7f); + } + + /// + /// 判断是否处于休眠状态 + /// + public static bool IsDormant(float neutronFlux) + { + return neutronFlux < 0.05f; + } + + /// + /// 获取模式名称 + /// + public static string GetFluxModeName(FluxMode mode) + { + return mode switch + { + FluxMode.Manual => "手动", + FluxMode.Quality => "品质", + FluxMode.Balance => "平衡", + FluxMode.Speed => "速度", + _ => "?" + }; + } + + /// + /// 获取模式简称 + /// + public static string GetFluxModeShort(FluxMode mode) + { + return mode switch + { + FluxMode.Manual => "M", + FluxMode.Quality => "Q", + FluxMode.Balance => "B", + FluxMode.Speed => "S", + _ => "?" + }; + } + + // ============================================ + // 品质系统 + // ============================================ + + /// + /// 计算品质增长(低通量时增长更快) + /// 公式:qualityGain = speedFactor * (1 + (1 - flux) * 0.5) + /// + public static float CalculateQualityGain(float neutronFlux, float speedFactor) + { + float qualityBonus = 1f + (1f - neutronFlux) * 0.5f; + return speedFactor * qualityBonus; + } + + /// + /// 计算休眠时品质衰减(10%/天) + /// + public static float CalculateQualityDecay(float qualityTotal) + { + return (qualityTotal * 0.1f) / 60000f; + } + + /// + /// 根据品质百分比获取品质等级 + /// + public static QualityCategory GetQualityFromPercent(float qualityPercent, List thresholds) + { + if (thresholds.NullOrEmpty()) return QualityCategory.Normal; + + foreach (var threshold in thresholds.OrderByDescending(q => q.threshold)) + { + if (qualityPercent >= threshold.threshold) + return threshold.quality; + } + + return thresholds.OrderBy(q => q.threshold).First().quality; + } + + /// + /// 默认品质阈值 + /// + public static List GetDefaultQualityThresholds() + { + return new List + { + new QualityThreshold { quality = QualityCategory.Legendary, threshold = 0.99f }, + new QualityThreshold { quality = QualityCategory.Masterwork, threshold = 0.90f }, + new QualityThreshold { quality = QualityCategory.Excellent, threshold = 0.70f }, + new QualityThreshold { quality = QualityCategory.Good, threshold = 0.50f }, + new QualityThreshold { quality = QualityCategory.Normal, threshold = 0.20f }, + new QualityThreshold { quality = QualityCategory.Poor, threshold = 0.10f }, + new QualityThreshold { quality = QualityCategory.Awful, threshold = 0f } + }; + } + + // ============================================ + // 幼虫系统 + // ============================================ + + /// + /// 搜索可用幼虫 + /// + public static Pawn FindLarva(Map map, IntVec3 position, Faction faction, float searchRadius = 50f) + { + if (map == null) return null; + + foreach (var pawn in map.mapPawns.AllPawnsSpawned) + { + if (pawn.def.defName == "ArachnaeBase_Race_Larva" && + !pawn.Downed && !pawn.Dead && + pawn.Faction == faction && + position.DistanceTo(pawn.Position) <= searchRadius) + { + return pawn; + } + } + return null; + } + + /// + /// 搜索多个可用幼虫 + /// + public static List FindLarvae(Map map, IntVec3 position, Faction faction, int maxCount, float searchRadius = 50f, List exclude = null) + { + var result = new List(); + if (map == null) return result; + + foreach (var pawn in map.mapPawns.AllPawnsSpawned) + { + if (result.Count >= maxCount) break; + if (pawn.def.defName == "ArachnaeBase_Race_Larva" && + !pawn.Downed && !pawn.Dead && + pawn.Faction == faction && + (exclude == null || !exclude.Contains(pawn)) && + position.DistanceTo(pawn.Position) <= searchRadius) + { + result.Add(pawn); + } + } + return result; + } + + // ============================================ + // 自动通量计算 + // ============================================ + + /// + /// 计算自动通量目标值 + /// + /// 通量模式 + /// 当前品质百分比 + /// 当前进度百分比 + /// 当前燃料量 + /// 目标通量值 + public static float CalculateAutoFlux(FluxMode mode, float qualityPercent, float progressPercent, float currentFuel) + { + if (mode == FluxMode.Manual) return -1f; // 手动模式不计算 + + float gap = qualityPercent - progressPercent; + float targetFlux; + + switch (mode) + { + case FluxMode.Quality: + // 品质优先:保持品质领先 + if (qualityPercent >= 0.98f) targetFlux = 1.0f; + else if (gap > 0.2f) targetFlux = 0.6f; + else if (gap > 0.1f) targetFlux = 0.45f; + else if (gap > 0f) targetFlux = 0.35f; + else targetFlux = 0.2f; + break; + + case FluxMode.Speed: + // 速度优先:尽快完成,必要时降速保品质 + if (qualityPercent >= 0.95f) targetFlux = 1.0f; + else if (qualityPercent < 0.3f && progressPercent > 0.5f) targetFlux = 0.7f; + else if (qualityPercent < 0.2f && progressPercent > 0.7f) targetFlux = 0.5f; + else targetFlux = 1.0f; + break; + + case FluxMode.Balance: + default: + // 平衡模式:动态调节 + if (qualityPercent >= 0.95f) targetFlux = 1.0f; + else if (gap > 0.15f) targetFlux = 0.8f; + else if (gap > 0.05f) targetFlux = 0.6f; + else if (gap > -0.05f) targetFlux = 0.5f; + else if (gap > -0.15f) targetFlux = 0.35f; + else targetFlux = 0.2f; + break; + } + + // 燃料保护 + if (currentFuel < 20f) + targetFlux = Mathf.Min(targetFlux, 0.3f); + else if (currentFuel < 50f) + targetFlux = Mathf.Min(targetFlux, 0.5f); + + return targetFlux; + } + + /// + /// 平滑调节通量(用于自动模式) + /// + public static float SmoothFlux(float currentFlux, float targetFlux, float lerpSpeed = 0.05f) + { + return Mathf.Lerp(currentFlux, targetFlux, lerpSpeed); + } + + // ============================================ + // 燃料消耗 + // ============================================ + + /// + /// 计算每tick燃料消耗 + /// + /// 每天基础消耗 + /// 通量效率 + /// 正在处理的订单数 + public static float CalculateFuelConsumption(float baseFuelPerDay, float fluxEfficiency, int orderCount = 1) + { + return (baseFuelPerDay * fluxEfficiency * orderCount) / 60000f; + } + + // ============================================ + // 速度计算 + // ============================================ + + /// + /// 计算实际进度速度 + /// + public static float CalculateProgressSpeed(float fluxEfficiency, float speedFactor, float baseMultiplier = 5f) + { + return speedFactor * fluxEfficiency * baseMultiplier; + } + + /// + /// 计算需要减少的ticks(带随机) + /// + public static int CalculateExtraTicks(float progressSpeed) + { + float extra = progressSpeed - 1f; + if (extra <= 0) return 0; + + int extraTicks = Mathf.FloorToInt(extra); + if (Rand.Value < (extra - extraTicks)) extraTicks++; + return extraTicks; + } + } +}