This commit is contained in:
2025-12-23 16:52:06 +08:00
parent d11f0eb280
commit fb4903058a
10 changed files with 705 additions and 316 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -487,6 +487,18 @@
<li>ARA_Technology_2NPT</li>
</prerequisites>
</ResearchProjectDef>
<ResearchProjectDef ParentName="ARA_techBase">
<defName>ARA_Technology_4NPT</defName>
<label>节点NPT-4"孵化池"</label>
<description>&lt;color=#887E78>&lt;i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径它们奠定了虫群在生物学上的优越性。&lt;/i>&lt;/color>\n\n允许虫族建造孵化池。一种专用于批量生产虫族的孵化场地。孵化池的孵化效率比孵化茧更高。</description>
<baseCost>1800</baseCost>
<researchViewX>12.00</researchViewX>
<researchViewY>2.10</researchViewY>
<requiredResearchBuilding>ARA_ResearchBench</requiredResearchBuilding> <!-- ARA_MorphableResearchBench-->
<prerequisites>
<li>ARA_Technology_1NPT</li>
</prerequisites>
</ResearchProjectDef>
<!-- 作物发展 -->
<ResearchProjectDef ParentName="ARA_techBase_Needtechprint">
<defName>ARA_Technology_8CPE</defName>

View File

@@ -701,89 +701,124 @@
</relatedTerrain>
</building>
<comps>
<!-- a. 我们自己的队列生产组件 (带通量控制) -->
<!-- a. 队列生产组件 -->
<li Class="ArachnaeSwarm.CompProperties_QueuedPawnSpawnerWithFlux">
<productionQueueLimit>5</productionQueueLimit>
<minNutritionToStart>0.5</minNutritionToStart>
<whitelist>
<li>ArachnaeBase_Race_Larva</li>
</whitelist>
</li>
<!-- 质量系统设置 -->
<minSafeTemperature>10</minSafeTemperature>
<maxSafeTemperature>30</maxSafeTemperature>
<penaltyPerDegreePerTick>0.00001</penaltyPerDegreePerTick>
<qualityThresholds>
<li>
<quality>Legendary</quality>
<threshold>0.99</threshold>
</li>
<li>
<quality>Masterwork</quality>
<threshold>0.90</threshold>
</li>
<li>
<quality>Excellent</quality>
<threshold>0.70</threshold>
</li>
<li>
<quality>Good</quality>
<threshold>0.50</threshold>
</li>
<li>
<quality>Normal</quality>
<threshold>0.20</threshold>
</li>
<li>
<quality>Poor</quality>
<threshold>0.10</threshold>
</li>
</qualityThresholds>
<spawnablePawns>
<li>
<pawnKind>ArachnaeNode_Race_Myrmecocystus</pawnKind>
<delayTicks>240000</delayTicks>
<totalNutritionNeeded>100.0</totalNutritionNeeded>
</li>
<!-- b. 孵化配置数据组件(提供可孵化单位列表) -->
<li Class="ArachnaeSwarm.CompProperties_IncubatorData">
<incubationConfigs>
<li>
<pawnKind>ArachnaeNode_Race_ShieldHead</pawnKind>
<delayTicks>180000</delayTicks>
<totalNutritionNeeded>40.0</totalNutritionNeeded>
<daysRequired>3</daysRequired>
<extraHediffs>
<li>ARA_Incubator_1_Reward_Hediffs</li>
<li>ARA_Incubator_2_Reward_Hediffs</li>
<li>ARA_Incubator_3_Reward_Hediffs</li>
<li>ARA_Incubator_4_Reward_Hediffs</li>
<li>ARA_Incubator_5_Reward_Hediffs</li>
<li>ARA_Incubator_6_Reward_Hediffs</li>
<li>ARA_Incubator_7_Reward_Hediffs</li>
<li>ARA_Incubator_8_Reward_Hediffs</li>
</extraHediffs>
</li>
<li>
<pawnKind>ArachnaeNode_Race_WeaponSmith</pawnKind>
<delayTicks>180000</delayTicks>
<totalNutritionNeeded>40.0</totalNutritionNeeded>
<daysRequired>3</daysRequired>
<extraHediffs>
<li>ARA_Incubator_1_Reward_Hediffs</li>
<li>ARA_Incubator_2_Reward_Hediffs</li>
<li>ARA_Incubator_3_Reward_Hediffs</li>
<li>ARA_Incubator_4_Reward_Hediffs</li>
<li>ARA_Incubator_5_Reward_Hediffs</li>
<li>ARA_Incubator_6_Reward_Hediffs</li>
<li>ARA_Incubator_7_Reward_Hediffs</li>
<li>ARA_Incubator_8_Reward_Hediffs</li>
</extraHediffs>
</li>
<li>
<pawnKind>ArachnaeNode_Race_Fighter</pawnKind>
<delayTicks>90000</delayTicks>
<totalNutritionNeeded>20.0</totalNutritionNeeded>
<daysRequired>1.5</daysRequired>
<requiredResearch>ARA_Technology_1KYC</requiredResearch>
<extraHediffs>
<li>ARA_Incubator_1_Reward_Hediffs</li>
<li>ARA_Incubator_2_Reward_Hediffs</li>
<li>ARA_Incubator_3_Reward_Hediffs</li>
<li>ARA_Incubator_4_Reward_Hediffs</li>
<li>ARA_Incubator_5_Reward_Hediffs</li>
<li>ARA_Incubator_6_Reward_Hediffs</li>
<li>ARA_Incubator_7_Reward_Hediffs</li>
<li>ARA_Incubator_8_Reward_Hediffs</li>
</extraHediffs>
</li>
<li>
<pawnKind>ArachnaeNode_Race_Myrmecocystus</pawnKind>
<daysRequired>4</daysRequired>
<extraHediffs>
<li>ARA_Incubator_1_Reward_Hediffs</li>
<li>ARA_Incubator_2_Reward_Hediffs</li>
<li>ARA_Incubator_3_Reward_Hediffs</li>
<li>ARA_Incubator_4_Reward_Hediffs</li>
<li>ARA_Incubator_5_Reward_Hediffs</li>
<li>ARA_Incubator_6_Reward_Hediffs</li>
<li>ARA_Incubator_7_Reward_Hediffs</li>
<li>ARA_Incubator_8_Reward_Hediffs</li>
</extraHediffs>
</li>
<li>
<pawnKind>ArachnaeNode_Race_Smokepop</pawnKind>
<delayTicks>180000</delayTicks>
<totalNutritionNeeded>60.0</totalNutritionNeeded>
<daysRequired>3</daysRequired>
<requiredResearch>ARA_Technology_5KYC</requiredResearch>
<extraHediffs>
<li>ARA_Incubator_1_Reward_Hediffs</li>
<li>ARA_Incubator_2_Reward_Hediffs</li>
<li>ARA_Incubator_3_Reward_Hediffs</li>
<li>ARA_Incubator_4_Reward_Hediffs</li>
<li>ARA_Incubator_5_Reward_Hediffs</li>
<li>ARA_Incubator_6_Reward_Hediffs</li>
<li>ARA_Incubator_7_Reward_Hediffs</li>
<li>ARA_Incubator_8_Reward_Hediffs</li>
</extraHediffs>
</li>
<li>
<pawnKind>ArachnaeNode_Race_Skyraider</pawnKind>
<delayTicks>120000</delayTicks>
<totalNutritionNeeded>80.0</totalNutritionNeeded>
<daysRequired>2</daysRequired>
<requiredResearch>ARA_Technology_2KYC</requiredResearch>
<extraHediffs>
<li>ARA_Incubator_1_Reward_Hediffs</li>
<li>ARA_Incubator_2_Reward_Hediffs</li>
<li>ARA_Incubator_3_Reward_Hediffs</li>
<li>ARA_Incubator_4_Reward_Hediffs</li>
<li>ARA_Incubator_5_Reward_Hediffs</li>
<li>ARA_Incubator_6_Reward_Hediffs</li>
<li>ARA_Incubator_7_Reward_Hediffs</li>
<li>ARA_Incubator_8_Reward_Hediffs</li>
</extraHediffs>
</li>
<li>
<pawnKind>ARA_MimicNematodeShamblerSwarmer</pawnKind>
<delayTicks>600</delayTicks>
<totalNutritionNeeded>10.0</totalNutritionNeeded>
<daysRequired>0.01</daysRequired>
<requiredResearch>ARA_Technology_6MEN</requiredResearch>
<extraHediffs>
<li>ARA_Incubator_1_Reward_Hediffs</li>
<li>ARA_Incubator_2_Reward_Hediffs</li>
<li>ARA_Incubator_3_Reward_Hediffs</li>
<li>ARA_Incubator_4_Reward_Hediffs</li>
<li>ARA_Incubator_5_Reward_Hediffs</li>
<li>ARA_Incubator_6_Reward_Hediffs</li>
<li>ARA_Incubator_7_Reward_Hediffs</li>
<li>ARA_Incubator_8_Reward_Hediffs</li>
</extraHediffs>
</li>
</spawnablePawns>
</incubationConfigs>
</li>
<!-- b. 我们的营养燃料组件 -->
<!-- c. 营养燃料组件 -->
<li Class="ArachnaeSwarm.CompProperties_RefuelableNutrition">
<fuelCapacity>300.0</fuelCapacity>
<fuelFilter>
@@ -796,7 +831,7 @@
<targetFuelLevelConfigurable>true</targetFuelLevelConfigurable>
</li>
<!-- c. 原版的设施链接接收组件 -->
<!-- d. 设施链接组件 -->
<li Class="CompProperties_AffectedByFacilities">
<linkableFacilities>
<li>ARA_NutrientNetworkTower</li>

View File

@@ -124,6 +124,7 @@
<Compile Include="Buildings\Building_Incubatable.cs" />
<Compile Include="Buildings\IFluxController.cs" />
<Compile Include="Buildings\ILarvaActivatable.cs" />
<Compile Include="Buildings\IncubatorUtils.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" />

View File

@@ -401,6 +401,8 @@ namespace ArachnaeSwarm
}
}
public void ShowOrderMenuPublic() => ShowOrderMenu();
private void ShowOrderMenu()
{
var options = new List<FloatMenuOption>();

View File

@@ -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<QueuedPawnSpawnEntry> spawnablePawns;
public List<PawnKindDef> whitelist;
public List<PawnKindDef> 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<QualityThreshold> 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<CompRefuelableNutrition>());
private CompAffectedByFacilities FacilitiesComp => _facilitiesComp ?? (_facilitiesComp = parent.GetComp<CompAffectedByFacilities>());
private CompIncubatorData IncubatorDataComp => _incubatorDataComp ?? (_incubatorDataComp = parent.GetComp<CompIncubatorData>());
// 获取可孵化配置列表(从 CompIncubatorData
public List<IncubationConfig> IncubationConfigs => IncubatorDataComp?.IncubationConfigs ?? new List<IncubationConfig>();
// === IFluxController 接口实现 ===
public float NeutronFlux => neutronFlux;
@@ -120,17 +121,18 @@ namespace ArachnaeSwarm
base.Initialize(props);
_fuelComp = parent.GetComp<CompRefuelableNutrition>();
_facilitiesComp = parent.GetComp<CompAffectedByFacilities>();
_incubatorDataComp = parent.GetComp<CompIncubatorData>();
}
// === 订单管理 ===
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<JobDef>.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<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);
}
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<FloatMenuOption>();
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()

View File

@@ -7,16 +7,20 @@ namespace ArachnaeSwarm
{
/// <summary>
/// 双向进度条 Gizmo - 用于物品孵化池
/// 从中间向两边生长:左边品质进度,右边生产进度
/// 支持滚动显示多个订单
/// 样式与旧版 Gizmo_IncubationProgress 统一
/// </summary>
[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;
}
}
}
}

View File

@@ -7,16 +7,20 @@ namespace ArachnaeSwarm
{
/// <summary>
/// 双向进度条 Gizmo - 用于督虫孵化池
/// 从中间向两边生长:左边品质进度,右边生产进度
/// 支持滚动显示多个订单
/// 样式与旧版 Gizmo_IncubationProgress 统一
/// </summary>
[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;
}
}
}
}

View File

@@ -0,0 +1,275 @@
using RimWorld;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
/// <summary>
/// 孵化器通用工具类
/// 统一核心算法,保持各孵化器行为一致
/// </summary>
public static class IncubatorUtils
{
// ============================================
// 通量系统
// ============================================
/// <summary>
/// 计算通量效率0-1之间通量越高效率越高
/// </summary>
public static float GetFluxEfficiency(float neutronFlux)
{
// 与 IFluxControllerExtensions.GetEfficiency 保持一致
return Mathf.Pow(neutronFlux, 0.7f);
}
/// <summary>
/// 判断是否处于休眠状态
/// </summary>
public static bool IsDormant(float neutronFlux)
{
return neutronFlux < 0.05f;
}
/// <summary>
/// 获取模式名称
/// </summary>
public static string GetFluxModeName(FluxMode mode)
{
return mode switch
{
FluxMode.Manual => "手动",
FluxMode.Quality => "品质",
FluxMode.Balance => "平衡",
FluxMode.Speed => "速度",
_ => "?"
};
}
/// <summary>
/// 获取模式简称
/// </summary>
public static string GetFluxModeShort(FluxMode mode)
{
return mode switch
{
FluxMode.Manual => "M",
FluxMode.Quality => "Q",
FluxMode.Balance => "B",
FluxMode.Speed => "S",
_ => "?"
};
}
// ============================================
// 品质系统
// ============================================
/// <summary>
/// 计算品质增长(低通量时增长更快)
/// 公式qualityGain = speedFactor * (1 + (1 - flux) * 0.5)
/// </summary>
public static float CalculateQualityGain(float neutronFlux, float speedFactor)
{
float qualityBonus = 1f + (1f - neutronFlux) * 0.5f;
return speedFactor * qualityBonus;
}
/// <summary>
/// 计算休眠时品质衰减10%/天)
/// </summary>
public static float CalculateQualityDecay(float qualityTotal)
{
return (qualityTotal * 0.1f) / 60000f;
}
/// <summary>
/// 根据品质百分比获取品质等级
/// </summary>
public static QualityCategory GetQualityFromPercent(float qualityPercent, List<QualityThreshold> 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;
}
/// <summary>
/// 默认品质阈值
/// </summary>
public static List<QualityThreshold> GetDefaultQualityThresholds()
{
return new List<QualityThreshold>
{
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 }
};
}
// ============================================
// 幼虫系统
// ============================================
/// <summary>
/// 搜索可用幼虫
/// </summary>
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;
}
/// <summary>
/// 搜索多个可用幼虫
/// </summary>
public static List<Pawn> FindLarvae(Map map, IntVec3 position, Faction faction, int maxCount, float searchRadius = 50f, List<Pawn> exclude = null)
{
var result = new List<Pawn>();
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;
}
// ============================================
// 自动通量计算
// ============================================
/// <summary>
/// 计算自动通量目标值
/// </summary>
/// <param name="mode">通量模式</param>
/// <param name="qualityPercent">当前品质百分比</param>
/// <param name="progressPercent">当前进度百分比</param>
/// <param name="currentFuel">当前燃料量</param>
/// <returns>目标通量值</returns>
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;
}
/// <summary>
/// 平滑调节通量(用于自动模式)
/// </summary>
public static float SmoothFlux(float currentFlux, float targetFlux, float lerpSpeed = 0.05f)
{
return Mathf.Lerp(currentFlux, targetFlux, lerpSpeed);
}
// ============================================
// 燃料消耗
// ============================================
/// <summary>
/// 计算每tick燃料消耗
/// </summary>
/// <param name="baseFuelPerDay">每天基础消耗</param>
/// <param name="fluxEfficiency">通量效率</param>
/// <param name="orderCount">正在处理的订单数</param>
public static float CalculateFuelConsumption(float baseFuelPerDay, float fluxEfficiency, int orderCount = 1)
{
return (baseFuelPerDay * fluxEfficiency * orderCount) / 60000f;
}
// ============================================
// 速度计算
// ============================================
/// <summary>
/// 计算实际进度速度
/// </summary>
public static float CalculateProgressSpeed(float fluxEfficiency, float speedFactor, float baseMultiplier = 5f)
{
return speedFactor * fluxEfficiency * baseMultiplier;
}
/// <summary>
/// 计算需要减少的ticks带随机
/// </summary>
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;
}
}
}