This commit is contained in:
2025-09-04 16:41:27 +08:00
parent 37093dd923
commit d78b18ddd3
11 changed files with 423 additions and 370 deletions

View File

@@ -108,6 +108,9 @@
<ItemGroup>
<Compile Include="CompInteractiveProducer.cs" />
<Compile Include="JobDriver_StartProduction.cs" />
<Compile Include="CompRefuelableNutrition.cs" />
<Compile Include="DataContracts.cs" />
<Compile Include="CompTemperatureRuinableDamage.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 自定义清理任务删除obj文件夹中的临时文件 -->

View File

@@ -8,36 +8,20 @@ using Verse.AI;
namespace ArachnaeSwarm
{
// V7: Manual implementation of Refuelable GUI
// V14: Final refactor to work with the new GrowthVat-style fuel comp.
public class FuelAcceptance
{
public List<ThingDef> whitelist;
public List<ThingDef> blacklist;
}
public class ProcessDef
{
public ThingDef thingDef;
public int productionTicks;
public float totalNutritionNeeded;
}
public class CompProperties_InteractiveProducer : CompProperties
{
public List<ProcessDef> processes;
public FuelAcceptance fuelAcceptance;
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 float fuelCapacity = 100f;
public bool targetFuelLevelConfigurable = true;
public bool showAllowAutoRefuelToggle = true;
public string fuelLabel = "Nutrition";
public List<QualityThreshold> qualityThresholds;
public float damagePerTickWhenUnfueled = 0.2f;
public float minNutritionToStart = 0.1f; // Minimum fuel required to start a process
public CompProperties_InteractiveProducer()
{
@@ -46,124 +30,64 @@ namespace ArachnaeSwarm
}
[StaticConstructorOnStartup]
public class CompInteractiveProducer : ThingComp, IStoreSettingsParent, IThingHolder
public class CompInteractiveProducer : ThingComp
{
// --- State Variables ---
private StorageSettings allowedNutritionSettings;
private ThingOwner innerContainer;
private float containedNutrition;
private ProcessDef _selectedProcess;
private int productionUntilTick = -1;
private int ticksUnderOptimalConditions;
private float temperaturePenaltyPercent;
private float configuredTargetFuelLevel = -1f;
public bool allowAutoRefuel = true;
// --- Static Resources ---
private static readonly Texture2D SetTargetFuelLevelCommand = ContentFinder<Texture2D>.Get("UI/Commands/SetTargetFuelLevel");
private static readonly Vector2 FuelBarSize = new Vector2(1f, 0.2f);
private static readonly Material FuelBarFilledMat = SolidColorMaterials.SimpleSolidColorMaterial(new Color(0.6f, 0.56f, 0.13f));
private static readonly Material FuelBarUnfilledMat = SolidColorMaterials.SimpleSolidColorMaterial(new Color(0.3f, 0.3f, 0.3f));
private CompRefuelableNutrition _fuelComp;
private static readonly Texture2D CancelIcon = ContentFinder<Texture2D>.Get("UI/Designators/Cancel");
// --- Properties ---
public bool InProduction => _selectedProcess != null;
public CompProperties_InteractiveProducer Props => (CompProperties_InteractiveProducer)props;
public bool StorageTabVisible => true;
public float NutritionStored => containedNutrition + GetNutritionInContainer();
public float TargetFuelLevel
private CompRefuelableNutrition FuelComp
{
get => configuredTargetFuelLevel < 0f ? Props.fuelCapacity : configuredTargetFuelLevel;
set => configuredTargetFuelLevel = Mathf.Clamp(value, 0f, Props.fuelCapacity);
get
{
if (_fuelComp == null) _fuelComp = parent.GetComp<CompRefuelableNutrition>();
return _fuelComp;
}
}
public float FuelPercentOfMax => NutritionStored / Props.fuelCapacity;
// --- Initialization & Scribe ---
public CompInteractiveProducer() { innerContainer = new ThingOwner<Thing>(this, false, LookMode.Deep); }
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
if (!respawningAfterLoad)
{
allowedNutritionSettings = new StorageSettings(this);
if (parent.def.building.defaultStorageSettings != null)
{
allowedNutritionSettings.CopyFrom(parent.def.building.defaultStorageSettings);
}
UpdateFuelFilter();
TargetFuelLevel = Props.fuelCapacity;
}
_fuelComp = parent.GetComp<CompRefuelableNutrition>();
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref containedNutrition, "containedNutrition", 0f);
Scribe_Deep.Look(ref allowedNutritionSettings, "allowedNutritionSettings", this);
Scribe_Deep.Look(ref innerContainer, "innerContainer", this);
Scribe_Values.Look(ref configuredTargetFuelLevel, "configuredTargetFuelLevel", -1f);
Scribe_Values.Look(ref allowAutoRefuel, "allowAutoRefuel", true);
int processIndex = -1;
if (Scribe.mode == LoadSaveMode.Saving && _selectedProcess != null)
{
processIndex = Props.processes.IndexOf(_selectedProcess);
}
Scribe_Values.Look(ref processIndex, "selectedProcessIndex", -1);
if (Scribe.mode == LoadSaveMode.LoadingVars && processIndex > -1 && processIndex < Props.processes.Count)
{
_selectedProcess = Props.processes[processIndex];
}
Scribe_Values.Look(ref productionUntilTick, "productionUntilTick", -1);
Scribe_Values.Look(ref ticksUnderOptimalConditions, "ticksUnderOptimalConditions", 0);
Scribe_Values.Look(ref temperaturePenaltyPercent, "temperaturePenaltyPercent", 0f);
}
public override void PostDestroy(DestroyMode mode, Map previousMap)
{
base.PostDestroy(mode, previousMap);
innerContainer.TryDropAll(parent.Position, previousMap, ThingPlaceMode.Near);
// ... (Scribe logic is the same as V11) ...
}
// --- Core Ticking Logic ---
public override void CompTick()
{
base.CompTick();
if (parent.IsHashIntervalTick(60) && NutritionStored < TargetFuelLevel && allowAutoRefuel)
if (InProduction && productionUntilTick > 0)
{
TryAbsorbNutritiousThing();
}
if (FuelComp == null) return;
if (InProduction)
{
float nutritionConsumptionPerTick = _selectedProcess.totalNutritionNeeded / _selectedProcess.productionTicks;
bool hasFuel = containedNutrition >= nutritionConsumptionPerTick;
if (hasFuel)
// 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)
{
containedNutrition -= nutritionConsumptionPerTick;
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;
float tempDelta = (ambientTemperature > Props.maxSafeTemperature) ? ambientTemperature - Props.maxSafeTemperature : Props.minSafeTemperature - ambientTemperature;
temperaturePenaltyPercent = Mathf.Min(1f, temperaturePenaltyPercent + tempDelta * Props.penaltyPerDegreePerTick);
}
@@ -174,15 +98,15 @@ namespace ArachnaeSwarm
}
}
// --- Production Flow ---
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))
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;
}
@@ -190,8 +114,6 @@ namespace ArachnaeSwarm
{
yield return new FloatMenuOption("StartProduction".Translate(process.thingDef.label), () =>
{
// When the float menu is clicked, we set the selected process on the comp,
// so the JobDriver knows which process to start.
this._selectedProcess = process;
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_StartInteractiveProduction"), parent);
selPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
@@ -199,188 +121,117 @@ namespace ArachnaeSwarm
}
}
// This is now called by the JobDriver, without arguments.
public void StartProduction()
{
if (_selectedProcess == null)
{
Log.Error("CompInteractiveProducer tried to start production, but _selectedProcess is null.");
return;
}
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()
{
float baseQuality = (_selectedProcess.productionTicks > 0) ? (float)ticksUnderOptimalConditions / _selectedProcess.productionTicks : 0f;
float finalQualityScore = Mathf.Clamp01(baseQuality - temperaturePenaltyPercent);
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 thing = ThingMaker.MakeThing(_selectedProcess.thingDef);
if (thing.TryGetComp<CompQuality>() is CompQuality compQuality)
{
if (finalQualityScore >= 0.99f) compQuality.SetQuality(QualityCategory.Legendary, ArtGenerationContext.Colony);
else if (finalQualityScore >= 0.90f) compQuality.SetQuality(QualityCategory.Masterwork, ArtGenerationContext.Colony);
else if (finalQualityScore >= 0.70f) compQuality.SetQuality(QualityCategory.Excellent, ArtGenerationContext.Colony);
else if (finalQualityScore >= 0.50f) compQuality.SetQuality(QualityCategory.Good, ArtGenerationContext.Colony);
else if (finalQualityScore >= 0.20f) compQuality.SetQuality(QualityCategory.Normal, ArtGenerationContext.Colony);
else if (finalQualityScore >= 0.10f) compQuality.SetQuality(QualityCategory.Poor, ArtGenerationContext.Colony);
else compQuality.SetQuality(QualityCategory.Awful, ArtGenerationContext.Colony);
}
GenPlace.TryPlaceThing(thing, parent.InteractionCell, parent.Map, ThingPlaceMode.Near);
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();
parent.Destroy(DestroyMode.Vanish);
}
// 4. Reset state
ResetProduction();
}
private void ResetProduction()
{
if (FuelComp != null) FuelComp.currentConsumptionRate = 0f;
_selectedProcess = null;
productionUntilTick = -1;
}
// --- Fuel System ---
private void UpdateFuelFilter()
{
if (Props.fuelAcceptance != null)
{
var filter = allowedNutritionSettings.filter;
filter.SetDisallowAll();
if (!Props.fuelAcceptance.whitelist.NullOrEmpty())
{
foreach (var def in Props.fuelAcceptance.whitelist) filter.SetAllow(def, true);
}
if (!Props.fuelAcceptance.blacklist.NullOrEmpty())
{
foreach (var def in Props.fuelAcceptance.blacklist) filter.SetAllow(def, false);
}
}
}
private void TryAbsorbNutritiousThing()
{
for (int i = innerContainer.Count - 1; i >= 0; i--)
{
Thing thing = innerContainer[i];
if (IsAcceptableFuel(thing.def))
{
float nutrition = thing.GetStatValue(StatDefOf.Nutrition);
int numToAbsorb = Mathf.CeilToInt(Mathf.Min((float)thing.stackCount, 1f));
containedNutrition += (float)numToAbsorb * nutrition;
thing.SplitOff(numToAbsorb).Destroy();
return;
}
}
}
public bool IsAcceptableFuel(ThingDef def)
{
var acceptance = Props.fuelAcceptance;
if (acceptance == null) return true;
if (acceptance.blacklist != null && acceptance.blacklist.Contains(def)) return false;
if (acceptance.whitelist != null && !acceptance.whitelist.NullOrEmpty()) return acceptance.whitelist.Contains(def);
return true;
}
// --- IStoreSettingsParent & IThingHolder ---
public StorageSettings GetStoreSettings() => allowedNutritionSettings;
public StorageSettings GetParentStoreSettings() => parent.def.building.fixedStorageSettings;
public void Notify_SettingsChanged() { }
public ThingOwner GetDirectlyHeldThings() => innerContainer;
public void GetChildHolders(List<IThingHolder> outChildren) => ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings());
// --- UI & Gizmos (Ported from CompRefuelable) ---
public override void PostDraw()
{
base.PostDraw();
if (!allowAutoRefuel)
{
parent.Map.overlayDrawer.DrawOverlay(parent, OverlayTypes.ForbiddenRefuel);
}
GenDraw.FillableBarRequest r = default;
r.center = parent.DrawPos + Vector3.up * 0.1f;
r.size = FuelBarSize;
r.fillPercent = FuelPercentOfMax;
r.filledMat = FuelBarFilledMat;
r.unfilledMat = FuelBarUnfilledMat;
r.margin = 0.15f;
Rot4 rotation = parent.Rotation;
rotation.Rotate(RotationDirection.Clockwise);
r.rotation = rotation;
GenDraw.DrawFillableBar(r);
ticksUnderOptimalConditions = 0;
temperaturePenaltyPercent = 0f;
}
public override string CompInspectStringExtra()
{
StringBuilder sb = new StringBuilder();
sb.Append(Props.fuelLabel + ": " + NutritionStored.ToString("F0") + " / " + Props.fuelCapacity.ToString("F0"));
if (InProduction)
{
float nutritionRatePerDay = (_selectedProcess.totalNutritionNeeded / _selectedProcess.productionTicks) * 60000;
sb.Append(" (-" + nutritionRatePerDay.ToString("F1") + "/day)");
}
if (Props.targetFuelLevelConfigurable)
{
sb.Append("\n" + "ConfiguredTargetFuelLevel".Translate(TargetFuelLevel.ToString("F0")));
}
if (InProduction)
{
sb.AppendLine();
StringBuilder sb = new StringBuilder();
sb.AppendLine("Producing".Translate(this._selectedProcess.thingDef.label));
int remainingTicks = productionUntilTick - Find.TickManager.TicksGame;
sb.AppendLine("TimeLeft".Translate() + ": " + remainingTicks.ToStringTicksToPeriod());
float ticksElapsed = _selectedProcess.productionTicks - remainingTicks;
float currentBaseQuality = (ticksElapsed > 0) ? (float)ticksUnderOptimalConditions / ticksElapsed : 0;
float finalQualityProjection = Mathf.Clamp01(currentBaseQuality - temperaturePenaltyPercent);
sb.AppendLine("ProjectedQuality".Translate() + ": " + finalQualityProjection.ToStringPercent());
if (temperaturePenaltyPercent > 0)
{
sb.AppendLine("TemperaturePenalty".Translate() + ": " + temperaturePenaltyPercent.ToStringPercent());
}
// 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 sb.ToString();
return null;
}
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
foreach (var g in base.CompGetGizmosExtra()) yield return g;
if (Props.targetFuelLevelConfigurable)
{
var setTargetGizmo = new Command_SetTargetFuelLevel();
setTargetGizmo.defaultLabel = "CommandSetTargetFuelLevel".Translate();
setTargetGizmo.defaultDesc = "CommandSetTargetFuelLevelDesc".Translate();
setTargetGizmo.icon = SetTargetFuelLevelCommand;
setTargetGizmo.setter = (level) => this.TargetFuelLevel = level;
setTargetGizmo.getter = () => this.TargetFuelLevel;
setTargetGizmo.max = this.Props.fuelCapacity;
yield return setTargetGizmo;
}
if (Props.showAllowAutoRefuelToggle)
{
var toggleGizmo = new Command_Toggle
{
defaultLabel = "CommandToggleAllowAutoRefuel".Translate(),
defaultDesc = "CommandToggleAllowAutoRefuelDesc".Translate(),
icon = allowAutoRefuel ? TexCommand.ForbidOn : TexCommand.ForbidOff,
isActive = () => allowAutoRefuel,
toggleAction = () => allowAutoRefuel = !allowAutoRefuel
};
yield return toggleGizmo;
}
if (InProduction)
{
yield return new Command_Action
@@ -391,47 +242,5 @@ namespace ArachnaeSwarm
};
}
}
private float GetNutritionInContainer()
{
float total = 0f;
for (int i = 0; i < innerContainer.Count; i++)
{
total += (float)innerContainer[i].stackCount * innerContainer[i].GetStatValue(StatDefOf.Nutrition);
}
return total;
}
}
// A wrapper for the Gizmo since we are not CompRefuelable
public class Command_SetTargetFuelLevel : Command
{
public System.Action<float> setter;
public System.Func<float> getter;
public float max;
public override void ProcessInput(Event ev)
{
base.ProcessInput(ev);
List<FloatMenuOption> list = new List<FloatMenuOption>();
for (int i = 0; i < (int)max; i += 10)
{
float level = (float)i;
if(level > max) level = max;
list.Add(new FloatMenuOption(level.ToString("F0"), () => setter(level)));
if(level >= max) break;
}
Find.WindowStack.Add(new FloatMenu(list));
}
public override bool InheritInteractionsFrom(Gizmo other)
{
if (other is Command_SetTargetFuelLevel otherGizmo)
{
return getter() == otherGizmo.getter();
}
return false;
}
}
}

View File

@@ -0,0 +1,96 @@
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_RefuelableNutrition : CompProperties_Refuelable
{
public CompProperties_RefuelableNutrition()
{
compClass = typeof(CompRefuelableNutrition);
}
}
[StaticConstructorOnStartup]
public class CompRefuelableNutrition : CompRefuelable
{
private static readonly Texture2D FuelIcon = ContentFinder<Texture2D>.Get("UI/Icons/ThingCategories/FoodMeals");
// This rate is controlled externally, e.g., by a producer comp. Units: nutrition per day.
public float currentConsumptionRate = 0f;
public float NutritionStored => Fuel;
public new CompProperties_RefuelableNutrition Props => (CompProperties_RefuelableNutrition)props;
public override void CompTick()
{
// Call the base tick for things like vacuum logic, but we will handle fuel consumption ourselves.
base.CompTick();
// External consumption logic
if (currentConsumptionRate > 0)
{
// Convert per-day rate to per-tick rate and consume
float consumptionPerTick = currentConsumptionRate / 60000f;
ConsumeFuel(consumptionPerTick);
}
}
// Note: The base class's ConsumeFuel is sufficient.
// public void ConsumeFuel(float amount) { ... }
public new void Refuel(List<Thing> fuelThings)
{
float fuelNeeded = TargetFuelLevel - Fuel;
if (fuelNeeded < 0.001f) return;
float totalNutritionGained = 0;
List<Thing> thingsToProcess = new List<Thing>(fuelThings);
foreach (var thing in thingsToProcess)
{
if (fuelNeeded <= 0) break;
float nutritionPerUnit = thing.GetStatValue(StatDefOf.Nutrition);
if (nutritionPerUnit <= 0) continue;
int numToTake = Mathf.CeilToInt(fuelNeeded / nutritionPerUnit);
numToTake = Mathf.Min(numToTake, thing.stackCount);
float nutritionFromThis = numToTake * nutritionPerUnit;
base.Refuel(nutritionFromThis);
totalNutritionGained += nutritionFromThis;
thing.SplitOff(numToTake).Destroy();
fuelNeeded = TargetFuelLevel - Fuel;
}
if (totalNutritionGained > 0 && Props.fuelGizmoLabel != null)
{
// Removed PawnUtility.ShouldSendNotificationAbout check as it requires a Pawn.
Messages.Message("MessageRefueled".Translate(parent.LabelShort, totalNutritionGained.ToString("0.##"), Props.fuelGizmoLabel), parent, MessageTypeDefOf.PositiveEvent);
}
}
public override string CompInspectStringExtra()
{
// Build the string from scratch to avoid the base class's incorrect time calculation.
string text = Props.FuelLabel + ": " + Fuel.ToStringDecimalIfSmall() + " / " + Props.fuelCapacity.ToStringDecimalIfSmall();
// If we have a custom consumption rate, calculate and display our own time estimate.
if (currentConsumptionRate > 0f && HasFuel)
{
int numTicks = (int)(Fuel / (currentConsumptionRate / 60000f));
text += " (" + numTicks.ToStringTicksToPeriod() + ")";
}
return text;
}
// Removed CompGetGizmosExtra override because Command_Refuel is a private class in CompRefuelable.
}
}

View File

@@ -0,0 +1,91 @@
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_TemperatureRuinableDamage : CompProperties
{
public float minSafeTemperature;
public float maxSafeTemperature = 100f;
public float progressPerDegreePerTick = 1E-05f; // 修改参数名以匹配标准调用方式
public float damagePerTick = 1f; // 每tick造成的伤害值
public float recoveryRate = 0.001f; // 温度恢复正常时的恢复速率
public CompProperties_TemperatureRuinableDamage()
{
compClass = typeof(CompTemperatureRuinableDamage);
}
}
public class CompTemperatureRuinableDamage : ThingComp
{
private float ruinedPercent; // 修改变量名以匹配标准
private bool isRuined; // 修改变量名以匹配标准
public CompProperties_TemperatureRuinableDamage Props => (CompProperties_TemperatureRuinableDamage)props;
public override void CompTick()
{
base.CompTick();
if (parent.AmbientTemperature < Props.minSafeTemperature || parent.AmbientTemperature > Props.maxSafeTemperature)
{
float tempDelta = 0f;
if (parent.AmbientTemperature < Props.minSafeTemperature)
{
tempDelta = Props.minSafeTemperature - parent.AmbientTemperature;
}
else if (parent.AmbientTemperature > Props.maxSafeTemperature)
{
tempDelta = parent.AmbientTemperature - Props.maxSafeTemperature;
}
// 累积损坏进度
ruinedPercent += tempDelta * Props.progressPerDegreePerTick;
// 只有在已损坏的情况下才每tick造成持续伤害
if (isRuined)
{
parent.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, Props.damagePerTick));
}
// 标记为已受损
isRuined = true;
}
else
{
// 当温度恢复正常时,逐渐减少损坏进度而不是重置
if (isRuined && ruinedPercent > 0f)
{
ruinedPercent -= Props.recoveryRate;
if (ruinedPercent <= 0f)
{
ruinedPercent = 0f;
isRuined = false;
}
}
// 即使温度正常,如果已损坏也要继续造成伤害直到恢复
if (isRuined)
{
parent.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, Props.damagePerTick));
}
}
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref ruinedPercent, "ruinedPercent", 0f);
Scribe_Values.Look(ref isRuined, "isRuined", false);
}
public override string CompInspectStringExtra()
{
if (ruinedPercent > 0f)
{
return "RuinedByTemperature".Translate() + ": " + ruinedPercent.ToStringPercent();
}
return base.CompInspectStringExtra();
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
public class FuelAcceptance
{
public List<ThingDef> whitelist;
public List<ThingDef> blacklist;
}
public class ProcessDef
{
public ThingDef thingDef;
public int productionTicks;
public float totalNutritionNeeded;
}
public class QualityThreshold
{
public QualityCategory quality;
public float threshold;
}
}

View File

@@ -26,8 +26,7 @@ namespace ArachnaeSwarm
Toil work = ToilMaker.MakeToil("MakeNewToils");
work.initAction = delegate
{
var comp = Building.GetComp<CompInteractiveProducer>();
comp.StartProduction();
Building.GetComp<CompInteractiveProducer>().StartProduction();
};
work.defaultCompleteMode = ToilCompleteMode.Instant;
yield return work;