This commit is contained in:
2025-09-08 14:39:46 +08:00
parent 8cf1ec75b2
commit 493267be2c
6 changed files with 353 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
using RimWorld;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
// CompProperties: 在XML中配置组件的属性
public class CompProperties_QueuedPawnSpawner : CompProperties
{
public List<QueuedPawnSpawnEntry> spawnablePawns;
public List<PawnKindDef> whitelist;
public int productionQueueLimit = 1;
public float minNutritionToStart = 0.1f;
public CompProperties_QueuedPawnSpawner()
{
compClass = typeof(CompQueuedPawnSpawner);
}
}
// 主组件类
public class CompQueuedPawnSpawner : ThingComp
{
private List<QueuedProductionOrder> productionOrders = new List<QueuedProductionOrder>();
// 缓存对其他组件的引用以提高性能
private CompRefuelableNutrition _fuelComp;
private CompAffectedByFacilities _facilitiesComp;
public CompProperties_QueuedPawnSpawner Props => (CompProperties_QueuedPawnSpawner)props;
private CompRefuelableNutrition FuelComp
{
get
{
if (_fuelComp == null) _fuelComp = parent.GetComp<CompRefuelableNutrition>();
return _fuelComp;
}
}
private CompAffectedByFacilities FacilitiesComp
{
get
{
if (_facilitiesComp == null) _facilitiesComp = parent.GetComp<CompAffectedByFacilities>();
return _facilitiesComp;
}
}
public override void Initialize(CompProperties props)
{
base.Initialize(props);
// 在初始化时获取一次避免在每次访问时都调用GetComp
_fuelComp = parent.GetComp<CompRefuelableNutrition>();
_facilitiesComp = parent.GetComp<CompAffectedByFacilities>();
}
public override IEnumerable<FloatMenuOption> CompFloatMenuOptions(Pawn selPawn)
{
if (Props.whitelist == null || !Props.whitelist.Contains(selPawn.kindDef)) yield break;
if (FuelComp != null && (!FuelComp.HasFuel || FuelComp.NutritionStored < Props.minNutritionToStart))
{
yield return new FloatMenuOption("CannotStartProduction".Translate() + ": " + "NoFuel".Translate(), null);
yield break;
}
foreach (QueuedPawnSpawnEntry entry in Props.spawnablePawns)
{
if (entry.pawnKind == null) continue;
if (entry.requiredResearch != null && !entry.requiredResearch.IsFinished)
{
string disabledText = "ARA_Incubate".Translate(entry.pawnKind.label) + " (" + "Requires".Translate() + ": " + entry.requiredResearch.label + ")";
yield return new FloatMenuOption(disabledText, null);
}
else
{
string label = "ARA_Incubate".Translate(entry.pawnKind.label);
if (entry.totalNutritionNeeded > 0)
{
label += $" ({"NutritionNeeded".Translate()}: {entry.totalNutritionNeeded.ToString("0.0")})";
}
yield return new FloatMenuOption(label, () => AddToQueue(entry, selPawn));
}
}
}
private void AddToQueue(QueuedPawnSpawnEntry entry, Pawn selPawn)
{
productionOrders.Add(new QueuedProductionOrder { entry = entry });
// 给交互的Pawn一个反馈表示订单已接受
if (selPawn.jobs?.curJob != null)
{
selPawn.jobs.EndCurrentJob(JobCondition.Succeeded);
}
}
public override void CompTick()
{
base.CompTick();
var producingOrders = productionOrders.Where(o => o.spawnUntilTick > 0).ToList();
bool hasFuel = FuelComp?.HasFuel ?? true;
// 处理无燃料情况:仅暂停生产
if (!hasFuel && FuelComp != null && producingOrders.Any())
{
foreach (var order in producingOrders)
{
order.spawnUntilTick++;
}
}
// 动态计算总燃料消耗速率
if (FuelComp != null)
{
float totalConsumptionRatePerDay = 0f;
// 只计算有燃料时正在生产的订单
if(hasFuel)
{
foreach (var order in producingOrders)
{
if (order.entry.totalNutritionNeeded > 0 && order.entry.delayTicks > 0)
{
totalConsumptionRatePerDay += (order.entry.totalNutritionNeeded / order.entry.delayTicks) * 60000f;
}
}
}
FuelComp.currentConsumptionRate = totalConsumptionRatePerDay;
}
// 检查并完成订单
productionOrders.RemoveAll(order =>
{
if (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);
return true;
}
return false;
});
// 检查并启动新订单
int currentlyProducingCount = productionOrders.Count(o => o.spawnUntilTick > 0);
if (currentlyProducingCount < Props.productionQueueLimit)
{
QueuedProductionOrder waitingOrder = productionOrders.FirstOrDefault(o => o.spawnUntilTick == -1);
if (waitingOrder != null)
{
float speedFactor = 1f;
if (FacilitiesComp != null)
{
speedFactor += FacilitiesComp.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor"));
}
int modifiedDelay = (int)(waitingOrder.entry.delayTicks / speedFactor);
waitingOrder.spawnUntilTick = Find.TickManager.TicksGame + modifiedDelay;
}
}
}
public override string CompInspectStringExtra()
{
StringBuilder sb = new StringBuilder();
if (FuelComp != null) sb.AppendLine(FuelComp.CompInspectStringExtra());
int producingCount = productionOrders.Count(o => o.spawnUntilTick > 0);
int queuedCount = productionOrders.Count - producingCount;
sb.AppendLine($"生产槽位: {producingCount} / {Props.productionQueueLimit}");
if (queuedCount > 0) sb.AppendLine($"等待队列: {queuedCount}");
if (FacilitiesComp != null)
{
float speedFactor = 1f + FacilitiesComp.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor"));
if(speedFactor != 1f) sb.AppendLine($"孵化速度: {speedFactor.ToStringPercent()}");
}
var producingNow = productionOrders.Where(o => o.spawnUntilTick > 0).OrderBy(o => o.spawnUntilTick);
if (producingNow.Any())
{
sb.AppendLine("正在孵化:");
foreach (var order in producingNow)
{
int remainingTicks = order.spawnUntilTick - Find.TickManager.TicksGame;
sb.AppendLine($" - {order.entry.pawnKind.LabelCap}: {remainingTicks.ToStringTicksToPeriod(true, false, true, true)}");
}
}
return sb.ToString().TrimEnd();
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Collections.Look(ref productionOrders, "productionOrders", LookMode.Deep, new object[0]);
}
}
}

View File

@@ -0,0 +1,49 @@
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
// 重命名以避免冲突
public class QueuedPawnSpawnEntry : IExposable
{
public PawnKindDef pawnKind;
public int delayTicks = 0;
public ResearchProjectDef requiredResearch;
public float totalNutritionNeeded = 0f;
public void ExposeData()
{
Scribe_Defs.Look(ref pawnKind, "pawnKind");
Scribe_Values.Look(ref delayTicks, "delayTicks", 0);
Scribe_Defs.Look(ref requiredResearch, "requiredResearch");
Scribe_Values.Look(ref totalNutritionNeeded, "totalNutritionNeeded", 0f);
}
}
// 重命名以避免冲突
public class QueuedProductionOrder : IExposable
{
public QueuedPawnSpawnEntry entry;
public int spawnUntilTick = -1;
public void ExposeData()
{
if (Scribe.mode == LoadSaveMode.Saving || Scribe.mode == LoadSaveMode.LoadingVars)
{
if (Scribe.EnterNode("entry"))
{
try
{
if (entry == null) entry = new QueuedPawnSpawnEntry();
entry.ExposeData();
}
finally
{
Scribe.ExitNode();
}
}
}
Scribe_Values.Look(ref spawnUntilTick, "spawnUntilTick", -1);
}
}
}

View File

@@ -71,6 +71,8 @@
<Compile Include="ARA_SpawnPawnFromList\CompProperties_SpawnPawnFromList.cs" />
<Compile Include="ARA_SpawnPawnFromList\CompSpawnPawnFromList.cs" />
<Compile Include="ARA_SpawnPawnFromList\JobDriver_Incubate.cs" />
<Compile Include="ARA_SpawnPawnFromList\CompQueuedPawnSpawner.cs" />
<Compile Include="ARA_SpawnPawnFromList\ProductionContracts.cs" />
<Compile Include="ARA_QueenAbility\CompProperties_AbilitySprayLiquidMulti.cs" />
<Compile Include="ARA_QueenAbility\CompAbilityEffect_SprayLiquidMulti.cs" />
<Compile Include="DRM_HediffCurseFlame\Hediff_CurseFlame.cs" />