This commit is contained in:
2025-09-04 14:28:26 +08:00
parent 5d79465213
commit cd880be9a4
7 changed files with 347 additions and 80 deletions

View File

@@ -105,6 +105,10 @@
<Compile Include="WULA_AutoMechCarrier\CompProperties_AutoMechCarrier.cs" />
<Compile Include="WULA_AutoMechCarrier\PawnProductionEntry.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="CompInteractiveProducer.cs" />
<Compile Include="JobDriver_StartProduction.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 自定义清理任务删除obj文件夹中的临时文件 -->
<Target Name="CleanDebugFiles" AfterTargets="Build">

View File

@@ -23,7 +23,6 @@ namespace ArachnaeSwarm
public float totalNutritionNeeded;
}
// We do NOT inherit from CompProperties_Refuelable anymore
public class CompProperties_InteractiveProducer : CompProperties
{
public List<ProcessDef> processes;
@@ -35,12 +34,10 @@ namespace ArachnaeSwarm
public float maxSafeTemperature = 32f;
public float penaltyPerDegreePerTick = 0.00001f;
// Manually added properties from CompProperties_Refuelable
public float fuelCapacity = 100f;
public bool targetFuelLevelConfigurable = true;
public bool showAllowAutoRefuelToggle = true;
public string fuelLabel = "Nutrition";
public Texture2D fuelIcon = null; // Let it default or specify
public CompProperties_InteractiveProducer()
{
@@ -61,11 +58,10 @@ namespace ArachnaeSwarm
private int ticksUnderOptimalConditions;
private float temperaturePenaltyPercent;
// --- Manually added state from CompRefuelable ---
private float configuredTargetFuelLevel = -1f;
public bool allowAutoRefuel = true;
// --- Manually added static resources from CompRefuelable ---
// --- 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));
@@ -79,7 +75,6 @@ namespace ArachnaeSwarm
public bool StorageTabVisible => true;
public float NutritionStored => containedNutrition + GetNutritionInContainer();
// --- Manually added properties from CompRefuelable ---
public float TargetFuelLevel
{
get => configuredTargetFuelLevel < 0f ? Props.fuelCapacity : configuredTargetFuelLevel;
@@ -91,16 +86,19 @@ namespace ArachnaeSwarm
// --- Initialization & Scribe ---
public CompInteractiveProducer() { innerContainer = new ThingOwner<Thing>(this, false, LookMode.Deep); }
public override void PostMake()
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostMake();
allowedNutritionSettings = new StorageSettings(this);
if (parent.def.building.defaultStorageSettings != null)
base.PostSpawnSetup(respawningAfterLoad);
if (!respawningAfterLoad)
{
allowedNutritionSettings.CopyFrom(parent.def.building.defaultStorageSettings);
allowedNutritionSettings = new StorageSettings(this);
if (parent.def.building.defaultStorageSettings != null)
{
allowedNutritionSettings.CopyFrom(parent.def.building.defaultStorageSettings);
}
UpdateFuelFilter();
TargetFuelLevel = Props.fuelCapacity;
}
UpdateFuelFilter();
TargetFuelLevel = Props.fuelCapacity; // Initialize target level
}
public override void PostExposeData()
@@ -139,9 +137,7 @@ namespace ArachnaeSwarm
public override void CompTick()
{
base.CompTick();
innerContainer.ThingOwnerTick();
if (this.IsHashIntervalTick(60) && NutritionStored < TargetFuelLevel)
if (parent.IsHashIntervalTick(60) && NutritionStored < TargetFuelLevel && allowAutoRefuel)
{
TryAbsorbNutritiousThing();
}
@@ -178,12 +174,120 @@ namespace ArachnaeSwarm
}
}
// ... (Production Flow methods remain the same) ...
// --- 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))
{
yield break;
}
foreach (var process in Props.processes)
{
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_IncubateJob"), parent);
selPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
});
}
}
// 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;
}
productionUntilTick = Find.TickManager.TicksGame + _selectedProcess.productionTicks;
ticksUnderOptimalConditions = 0;
temperaturePenaltyPercent = 0f;
}
private void FinishProduction()
{
float baseQuality = (_selectedProcess.productionTicks > 0) ? (float)ticksUnderOptimalConditions / _selectedProcess.productionTicks : 0f;
float finalQualityScore = Mathf.Clamp01(baseQuality - temperaturePenaltyPercent);
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);
}
if (Props.destroyOnSpawn)
{
parent.Destroy();
}
ResetProduction();
}
private void ResetProduction()
{
_selectedProcess = null;
productionUntilTick = -1;
}
// --- Fuel System ---
private void UpdateFuelFilter() { /* ... */ }
private void TryAbsorbNutritiousThing() { /* ... */ }
public bool IsAcceptableFuel(ThingDef def) { /* ... */ }
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;
@@ -218,19 +322,17 @@ namespace ArachnaeSwarm
{
StringBuilder sb = new StringBuilder();
// Ported logic from CompRefuelable
sb.Append(Props.fuelLabel + ": " + NutritionStored.ToString("F0") + " / " + Props.fuelCapacity.ToString("F0"));
if (InProduction)
{
float ticksRemaining = _selectedProcess.productionTicks * (NutritionStored / _selectedProcess.totalNutritionNeeded);
sb.Append(" (" + ((int)ticksRemaining).ToStringTicksToPeriod() + ")");
float nutritionRatePerDay = (_selectedProcess.totalNutritionNeeded / _selectedProcess.productionTicks) * 60000;
sb.Append(" (-" + nutritionRatePerDay.ToString("F1") + "/day)");
}
if (Props.targetFuelLevelConfigurable)
{
sb.Append("\n" + "ConfiguredTargetFuelLevel".Translate(TargetFuelLevel.ToString("F0")));
}
// Our production info
if (InProduction)
{
sb.AppendLine();
@@ -251,18 +353,16 @@ namespace ArachnaeSwarm
return sb.ToString();
}
public override IEnumerable<Gizmo> GetGizmos()
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
foreach (var g in base.GetGizmos()) yield return g;
foreach (var g in base.CompGetGizmosExtra()) yield return g;
// Ported Gizmos from CompRefuelable
if (Props.targetFuelLevelConfigurable)
{
var setTargetGizmo = new Command_SetTargetFuelLevel();
setTargetGizmo.defaultLabel = "CommandSetTargetFuelLevel".Translate();
setTargetGizmo.defaultDesc = "CommandSetTargetFuelLevelDesc".Translate();
setTargetGizmo.icon = SetTargetFuelLevelCommand;
// We need to create a simple wrapper to make it work
setTargetGizmo.setter = (level) => this.TargetFuelLevel = level;
setTargetGizmo.getter = () => this.TargetFuelLevel;
setTargetGizmo.max = this.Props.fuelCapacity;
@@ -292,6 +392,46 @@ namespace ArachnaeSwarm
}
}
// ... (The rest of the methods: FinishProduction, ResetProduction, GetNutritionInContainer etc.) ...
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,36 @@
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
public class JobDriver_StartProduction : JobDriver
{
private const TargetIndex BuildingInd = TargetIndex.A;
protected Building Building => (Building)job.GetTarget(BuildingInd).Thing;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
return pawn.Reserve(Building, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
this.FailOnDespawnedNullOrForbidden(BuildingInd);
this.FailOnBurningImmobile(BuildingInd);
yield return Toils_Goto.GotoThing(BuildingInd, PathEndMode.InteractionCell);
Toil work = ToilMaker.MakeToil("MakeNewToils");
work.initAction = delegate
{
var comp = Building.GetComp<CompInteractiveProducer>();
comp.StartProduction();
};
work.defaultCompleteMode = ToilCompleteMode.Instant;
yield return work;
}
}
}