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