246 lines
9.9 KiB
C#
246 lines
9.9 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using RimWorld;
|
|
using UnityEngine;
|
|
using Verse;
|
|
using Verse.AI;
|
|
|
|
namespace ArachnaeSwarm
|
|
{
|
|
// V14: Final refactor to work with the new GrowthVat-style fuel comp.
|
|
|
|
public class CompProperties_InteractiveProducer : CompProperties
|
|
{
|
|
public List<ProcessDef> processes;
|
|
public List<PawnKindDef> whitelist;
|
|
public IntRange spawnCount = new IntRange(1, 1);
|
|
public bool destroyOnSpawn;
|
|
public float minSafeTemperature = 7f;
|
|
public float maxSafeTemperature = 32f;
|
|
public float penaltyPerDegreePerTick = 0.00001f;
|
|
public List<QualityThreshold> qualityThresholds;
|
|
public float damagePerTickWhenUnfueled = 0.2f;
|
|
public float minNutritionToStart = 0.1f; // Minimum fuel required to start a process
|
|
|
|
public CompProperties_InteractiveProducer()
|
|
{
|
|
compClass = typeof(CompInteractiveProducer);
|
|
}
|
|
}
|
|
|
|
[StaticConstructorOnStartup]
|
|
public class CompInteractiveProducer : ThingComp
|
|
{
|
|
private ProcessDef _selectedProcess;
|
|
private int productionUntilTick = -1;
|
|
private int ticksUnderOptimalConditions;
|
|
private float temperaturePenaltyPercent;
|
|
|
|
private CompRefuelableNutrition _fuelComp;
|
|
private static readonly Texture2D CancelIcon = ContentFinder<Texture2D>.Get("UI/Designators/Cancel");
|
|
|
|
public bool InProduction => _selectedProcess != null;
|
|
public CompProperties_InteractiveProducer Props => (CompProperties_InteractiveProducer)props;
|
|
private CompRefuelableNutrition FuelComp
|
|
{
|
|
get
|
|
{
|
|
if (_fuelComp == null) _fuelComp = parent.GetComp<CompRefuelableNutrition>();
|
|
return _fuelComp;
|
|
}
|
|
}
|
|
|
|
public override void PostSpawnSetup(bool respawningAfterLoad)
|
|
{
|
|
base.PostSpawnSetup(respawningAfterLoad);
|
|
_fuelComp = parent.GetComp<CompRefuelableNutrition>();
|
|
}
|
|
|
|
public override void PostExposeData()
|
|
{
|
|
base.PostExposeData();
|
|
// ... (Scribe logic is the same as V11) ...
|
|
}
|
|
|
|
public override void CompTick()
|
|
{
|
|
base.CompTick();
|
|
if (InProduction && productionUntilTick > 0)
|
|
{
|
|
if (FuelComp == null) return;
|
|
|
|
// Nutrition consumption is now handled by CompRefuelableNutrition's CompTick.
|
|
// We just need to check if there is any fuel left.
|
|
bool hasFuel = FuelComp.HasFuel;
|
|
|
|
if (!hasFuel)
|
|
{
|
|
parent.TakeDamage(new DamageInfo(DamageDefOf.Rotting, Props.damagePerTickWhenUnfueled));
|
|
}
|
|
|
|
float ambientTemperature = parent.AmbientTemperature;
|
|
bool isTempSafe = ambientTemperature >= Props.minSafeTemperature && ambientTemperature <= Props.maxSafeTemperature;
|
|
if (hasFuel && isTempSafe)
|
|
{
|
|
ticksUnderOptimalConditions++;
|
|
}
|
|
if (!isTempSafe)
|
|
{
|
|
float tempDelta = (ambientTemperature > Props.maxSafeTemperature) ? ambientTemperature - Props.maxSafeTemperature : Props.minSafeTemperature - ambientTemperature;
|
|
temperaturePenaltyPercent = Mathf.Min(1f, temperaturePenaltyPercent + tempDelta * Props.penaltyPerDegreePerTick);
|
|
}
|
|
|
|
if (Find.TickManager.TicksGame >= productionUntilTick)
|
|
{
|
|
FinishProduction();
|
|
}
|
|
}
|
|
}
|
|
|
|
public override IEnumerable<FloatMenuOption> CompFloatMenuOptions(Pawn selPawn)
|
|
{
|
|
if (InProduction || !selPawn.CanReach(parent, PathEndMode.InteractionCell, Danger.Deadly)) yield break;
|
|
if (Props.whitelist != null && !Props.whitelist.Contains(selPawn.kindDef)) yield break;
|
|
if (FuelComp == null) yield break;
|
|
|
|
if (!FuelComp.HasFuel || FuelComp.NutritionStored < Props.minNutritionToStart)
|
|
{
|
|
yield return new FloatMenuOption("CannotStartProduction".Translate() + ": " + "NoFuel".Translate(), null);
|
|
yield break;
|
|
}
|
|
|
|
foreach (var process in Props.processes)
|
|
{
|
|
yield return new FloatMenuOption("StartProduction".Translate(process.thingDef.label), () =>
|
|
{
|
|
this._selectedProcess = process;
|
|
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_StartInteractiveProduction"), parent);
|
|
selPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
|
|
});
|
|
}
|
|
}
|
|
|
|
public void StartProduction()
|
|
{
|
|
if (_selectedProcess == null) return;
|
|
productionUntilTick = Find.TickManager.TicksGame + _selectedProcess.productionTicks;
|
|
ticksUnderOptimalConditions = 0;
|
|
temperaturePenaltyPercent = 0f;
|
|
// Set the consumption rate on the fuel comp (nutrition per day)
|
|
float nutritionPerDay = (_selectedProcess.totalNutritionNeeded / _selectedProcess.productionTicks) * 60000f;
|
|
FuelComp.currentConsumptionRate = nutritionPerDay;
|
|
}
|
|
|
|
public (QualityCategory quality, float baseScore, float penalty) GetEstimatedQualityDetails()
|
|
{
|
|
if (!InProduction || Props.qualityThresholds.NullOrEmpty())
|
|
{
|
|
return (QualityCategory.Normal, 0f, 0f); // Default or no quality system
|
|
}
|
|
|
|
// Estimate progress based on optimal ticks vs total ticks
|
|
float progress = (float)ticksUnderOptimalConditions / _selectedProcess.productionTicks;
|
|
// Apply temperature penalty
|
|
float finalQualityPercent = Mathf.Clamp01(progress - temperaturePenaltyPercent);
|
|
|
|
QualityCategory finalQuality = QualityCategory.Awful;
|
|
// Find the best quality that meets the threshold
|
|
foreach (var threshold in Props.qualityThresholds.OrderByDescending(q => q.threshold))
|
|
{
|
|
if (finalQualityPercent >= threshold.threshold)
|
|
{
|
|
finalQuality = threshold.quality;
|
|
break; // Exit after finding the highest met quality
|
|
}
|
|
}
|
|
// If no threshold is met, it will remain the lowest quality
|
|
if (finalQuality == QualityCategory.Awful && Props.qualityThresholds.Any())
|
|
{
|
|
finalQuality = Props.qualityThresholds.OrderBy(q => q.threshold).First().quality;
|
|
}
|
|
|
|
return (finalQuality, progress, temperaturePenaltyPercent);
|
|
}
|
|
|
|
private void FinishProduction()
|
|
{
|
|
if (_selectedProcess == null)
|
|
{
|
|
ResetProduction();
|
|
return;
|
|
}
|
|
|
|
// 1. Determine final quality
|
|
var qualityDetails = GetEstimatedQualityDetails();
|
|
QualityCategory finalQuality = qualityDetails.quality;
|
|
|
|
// 2. Create and spawn the item
|
|
for (int i = 0; i < Props.spawnCount.RandomInRange; i++)
|
|
{
|
|
Thing product = ThingMaker.MakeThing(_selectedProcess.thingDef);
|
|
product.TryGetComp<CompQuality>()?.SetQuality(finalQuality, ArtGenerationContext.Colony);
|
|
|
|
// Spawn the item near the parent building
|
|
GenPlace.TryPlaceThing(product, parent.Position, parent.Map, ThingPlaceMode.Near);
|
|
}
|
|
|
|
// 3. Destroy self if configured
|
|
if (Props.destroyOnSpawn)
|
|
{
|
|
parent.Destroy(DestroyMode.Vanish);
|
|
}
|
|
|
|
// 4. Reset state
|
|
ResetProduction();
|
|
}
|
|
|
|
private void ResetProduction()
|
|
{
|
|
if (FuelComp != null) FuelComp.currentConsumptionRate = 0f;
|
|
_selectedProcess = null;
|
|
productionUntilTick = -1;
|
|
ticksUnderOptimalConditions = 0;
|
|
temperaturePenaltyPercent = 0f;
|
|
}
|
|
|
|
public override string CompInspectStringExtra()
|
|
{
|
|
if (InProduction)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.AppendLine("Producing".Translate(this._selectedProcess.thingDef.label));
|
|
int remainingTicks = productionUntilTick - Find.TickManager.TicksGame;
|
|
sb.AppendLine("TimeLeft".Translate() + ": " + remainingTicks.ToStringTicksToPeriod());
|
|
|
|
// Quality Details
|
|
var qualityDetails = GetEstimatedQualityDetails();
|
|
sb.AppendLine("EstimatedQuality".Translate() + ": " + qualityDetails.quality.GetLabel());
|
|
sb.AppendLine($" {"QualityScore".Translate()}: {qualityDetails.baseScore.ToStringPercent("F0")}");
|
|
sb.AppendLine($" {"TemperaturePenalty".Translate()}: -{qualityDetails.penalty.ToStringPercent("F0")}");
|
|
|
|
// Temperature Details
|
|
string tempStr = "CurrentTemperature".Translate(parent.AmbientTemperature.ToStringTemperature("F0"));
|
|
tempStr += $" ({"SafeTemperatureRange".Translate()}: {Props.minSafeTemperature.ToStringTemperature("F0")} ~ {Props.maxSafeTemperature.ToStringTemperature("F0")})";
|
|
sb.AppendLine(tempStr);
|
|
|
|
return sb.ToString().TrimEnd();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
|
{
|
|
foreach (var g in base.CompGetGizmosExtra()) yield return g;
|
|
if (InProduction)
|
|
{
|
|
yield return new Command_Action
|
|
{
|
|
defaultLabel = "CommandCancelProduction".Translate(),
|
|
icon = CancelIcon,
|
|
action = () => ResetProduction()
|
|
};
|
|
}
|
|
}
|
|
}
|
|
} |