diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index 456ab82..1ae0634 100644
Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ
diff --git a/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml
index 827dc78..c19bd9b 100644
--- a/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml
+++ b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml
@@ -70,6 +70,40 @@
+
+
+ ARA_EggSpew
+
+ 工艺卵
+ UI/Commands/EggSpew
+ 5000
+ true
+ 300
+ true
+ false
+ AcidSpray_Warmup
+
+ Verb_CastAbility
+ 1
+ 12
+ AcidSpray_Resolve
+ false
+ false
+
+ True
+
+
+
+
+ ARA_Proj_BioforgeIncubator
+
+
+ Food
+ 0
+ 营养值不足,需要进食
+
+
+
ARA_AcidSprayBurst
@@ -159,6 +193,23 @@
true
+
+
+ ARA_Proj_BioforgeIncubator
+
+ Projectile_SpawnsThing
+
+ ArachnaeSwarm/Building/ARA_EggSac
+ Graphic_Single
+
+
+ Bullet
+ 21
+ 0
+ ARA_BioforgeIncubator
+ true
+
+
ARA_AcidSprayBurst_Myrmecocystus
diff --git a/1.6/1.6/Defs/JobDefs/ARA_Jobs_Interactive.xml b/1.6/1.6/Defs/JobDefs/ARA_Jobs_Interactive.xml
new file mode 100644
index 0000000..b745ae9
--- /dev/null
+++ b/1.6/1.6/Defs/JobDefs/ARA_Jobs_Interactive.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ ARA_IncubateJob
+ ArachnaeSwarm.JobDriver_StartProduction
+ 正在启动生产 TargetA.
+ true
+
+
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/Thing_building/ARA_InteractiveProducer.xml b/1.6/1.6/Defs/Thing_building/ARA_InteractiveProducer.xml
index 2f12cf2..2849bec 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_InteractiveProducer.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_InteractiveProducer.xml
@@ -3,68 +3,64 @@
ARA_BioforgeIncubator
-
- 一个先进的孵化器,可以使用化学燃料将有机物和矿物重组成有用的物品。生产过程对温度非常敏感,并且需要由特定的操作员进行启动。
+
+ 一个脆弱、易燃、黏滑的囊状物,是阿拉克涅工艺种所诞之卵,内含哺育阿拉克涅武器种虫族所需的营养和遗传物质,可以通过阿拉克涅工艺种的交互完成激活进程。
Building
+
+
+ Building
+ (1,1)
+ MinifiedThing
+
+ BuildingsMisc
+
- Things/Building/Production/BiofuelRefinery
- Graphic_Multi
- (2,2)
-
- Damage/Corner
- Damage/Corner
- Damage/Corner
- Damage/Corner
-
+ ArachnaeSwarm/Building/ARA_EggSac
+ Graphic_Single
+ (1.5,1.5)
- (2,2)
-
- 150
- 6
-
Building
- Impassable
+ PassThroughOnly
+ 0.3
false
- Production
+ Normal
+ Light
- 250
- 3000
- 1.0
- -10
+ 10
+ 50
+ 1
+ -6
+
+ true
+
+ false
+ false
+ true
+ true
+ false
+
Normal
-
- CompPowerTrader
- 250
-
- ComponentIndustrial
- 90000
- 25
+ Gun_ChainShotgun
+ 60000
+ 20
- Plasteel
- 120000
- 50
+ Gun_AssaultRifle
+ 60000
+ 15
-
- WoodLog
- RawFungus
- Meat_Insect
-
-
- MealSimple
-
@@ -73,23 +69,52 @@
- 5~10
- false
- 7
- 32
+ 1
+ True
+ 18
+ 23
0.00001
+
+
+ 13
+ 28
+ 0.00005
+
+
+ CompHeatPusherPowered
+ 6
+
-
- Important
+ 120
+
-
- WoodLog
- RawFungus
- Meat_Insect
-
+
+ Foods
+
+
+ AllowPlantFood
+
+
+
+
+
+
+ Foods
+
+
+ EggsFertilized
+
+
+ InsectJelly
+ MealLavish
+ MealLavish_Veg
+ MealLavish_Meat
+ HemogenPack
+ Chocolate
+
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index 9e8ae6b..c996e05 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -105,6 +105,10 @@
+
+
+
+
diff --git a/Source/ArachnaeSwarm/CompInteractiveProducer.cs b/Source/ArachnaeSwarm/CompInteractiveProducer.cs
index 00f2c5b..8d65c8a 100644
--- a/Source/ArachnaeSwarm/CompInteractiveProducer.cs
+++ b/Source/ArachnaeSwarm/CompInteractiveProducer.cs
@@ -23,7 +23,6 @@ namespace ArachnaeSwarm
public float totalNutritionNeeded;
}
- // We do NOT inherit from CompProperties_Refuelable anymore
public class CompProperties_InteractiveProducer : CompProperties
{
public List 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.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(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 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.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() 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 GetGizmos()
+ public override IEnumerable 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 setter;
+ public System.Func getter;
+ public float max;
+
+ public override void ProcessInput(Event ev)
+ {
+ base.ProcessInput(ev);
+ List list = new List();
+ 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;
+ }
}
}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/JobDriver_StartProduction.cs b/Source/ArachnaeSwarm/JobDriver_StartProduction.cs
new file mode 100644
index 0000000..e78624f
--- /dev/null
+++ b/Source/ArachnaeSwarm/JobDriver_StartProduction.cs
@@ -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 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();
+ comp.StartProduction();
+ };
+ work.defaultCompleteMode = ToilCompleteMode.Instant;
+ yield return work;
+ }
+ }
+}
\ No newline at end of file