diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 56971e3..4760967 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/Thing_building/Building_SmartThermostat.xml b/1.6/1.6/Defs/Thing_building/Building_SmartThermostat.xml index 71a92fa..bb8b505 100644 --- a/1.6/1.6/Defs/Thing_building/Building_SmartThermostat.xml +++ b/1.6/1.6/Defs/Thing_building/Building_SmartThermostat.xml @@ -65,4 +65,82 @@ + + ARA_GrowthVat + + 用来存放猎物的茧。 + ArachnaeSwarm.Building_NutrientVat + true + Normal + + ArachnaeSwarm/Building/ARA_GrowthVat + Graphic_Single + CutoutComplex + (2.5,2.5) + + (0.85, 0.3, 1.7) + + + true + North + (1,2) + + 500 + 8000 + 30 + 0.5 + + + 150 + 4 + + Building + PassThroughOnly + 42 + true + MapMeshAndRealTime + 0.5 + false + ARA_Buildings + 2200 + true + (0,0,-1) + false + +
  • ITab_BiosculpterNutritionStorage
  • +
  • ITab_Genes
  • +
    + +
  • GrowthVats
  • +
    + + false + 120 + Laboratory + + 4 + + +
  • + 100.0 + + +
  • Foods
  • + + + 生物质 + true + true + +
    + +
  • + + ArachnaeSwarm/Building/ARA_GrowthVatTop + + Graphic_Single +
  • +
    +
    + \ No newline at end of file diff --git a/Content/Textures/ArachnaeSwarm/Building/ARA_GrowthVat.png b/Content/Textures/ArachnaeSwarm/Building/ARA_GrowthVat.png new file mode 100644 index 0000000..c56bd54 Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Building/ARA_GrowthVat.png differ diff --git a/Content/Textures/ArachnaeSwarm/Building/ARA_GrowthVatTop.png b/Content/Textures/ArachnaeSwarm/Building/ARA_GrowthVatTop.png new file mode 100644 index 0000000..32e63ac Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Building/ARA_GrowthVatTop.png differ diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index e340c96..edcf401 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -133,6 +133,8 @@ + + diff --git a/Source/ArachnaeSwarm/Building_NutrientVat.cs b/Source/ArachnaeSwarm/Building_NutrientVat.cs new file mode 100644 index 0000000..c9bd04f --- /dev/null +++ b/Source/ArachnaeSwarm/Building_NutrientVat.cs @@ -0,0 +1,369 @@ +using RimWorld; +using System.Collections.Generic; +using System.Text; +using UnityEngine; +using Verse; +using Verse.AI; + +namespace ArachnaeSwarm +{ + [StaticConstructorOnStartup] + public class Building_NutrientVat : Building_Enterable, IThingHolder, IThingHolderWithDrawnPawn + { + private CompRefuelableNutrition cachedRefuelableComp; + private Graphic cachedTopGraphic; + + // IThingHolderWithDrawnPawn implementation + public float HeldPawnDrawPos_Y => DrawPos.y + 0.03658537f; + public float HeldPawnBodyAngle => base.Rotation.AsAngle; + public PawnPosture HeldPawnPosture => PawnPosture.LayingOnGroundFaceUp; + + private Graphic TopGraphic + { + get + { + if (cachedTopGraphic == null) + { + var modExtension = def.GetModExtension(); + if (modExtension != null && !modExtension.topGraphicPath.NullOrEmpty()) + { + cachedTopGraphic = GraphicDatabase.Get(modExtension.graphicClass, modExtension.topGraphicPath, ShaderDatabase.Transparent, def.graphicData.drawSize, Color.white, Color.white); + } + } + return cachedTopGraphic; + } + } + + // Constants for BioStarvation + private const float BiostarvationGainPerDayNoFood = 0.5f; + private const float BiostarvationFallPerDayFed = 0.1f; + + public override Vector3 PawnDrawOffset => Vector3.zero; + + public CompRefuelableNutrition RefuelableComp + { + get + { + if (cachedRefuelableComp == null) + { + cachedRefuelableComp = this.TryGetComp(); + } + return cachedRefuelableComp; + } + } + + public bool HasNutrition => RefuelableComp != null && RefuelableComp.HasFuel; + + public float BiostarvationDailyOffset + { + get + { + if (!base.Working) + { + return 0f; + } + return HasNutrition ? -BiostarvationFallPerDayFed : BiostarvationGainPerDayNoFood; + } + } + + public float NutritionConsumedPerDay + { + get + { + if (selectedPawn != null) + { + // Let's use the base consumption rate from the original GrowthVat + float num = 3f; + Hediff biostarvation = selectedPawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.BioStarvation); + if (biostarvation != null && biostarvation.Severity > 0) + { + // Increase consumption when biostarving, same as original + num *= 1.1f; + } + return num; + } + return 0f; + } + } + + public override void SpawnSetup(Map map, bool respawningAfterLoad) + { + base.SpawnSetup(map, respawningAfterLoad); + cachedRefuelableComp = this.TryGetComp(); + } + + public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish) + { + if (mode != DestroyMode.WillReplace) + { + if (selectedPawn != null && innerContainer.Contains(selectedPawn)) + { + Notify_PawnRemoved(); + } + } + base.DeSpawn(mode); + } + + protected override void Tick() + { + base.Tick(); + + if (selectedPawn != null && (selectedPawn.Destroyed || !innerContainer.Contains(selectedPawn))) + { + OnStop(); + return; + } + + if (base.Working && selectedPawn != null) + { + // Update BioStarvation + float biostarvationOffset = BiostarvationDailyOffset / 60000f * HediffDefOf.BioStarvation.maxSeverity; + Hediff biostarvation = selectedPawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.BioStarvation); + + if (biostarvation != null) + { + biostarvation.Severity += biostarvationOffset; + if (biostarvation.ShouldRemove) + { + selectedPawn.health.RemoveHediff(biostarvation); + } + } + else if (biostarvationOffset > 0f) + { + Hediff hediff = HediffMaker.MakeHediff(HediffDefOf.BioStarvation, selectedPawn); + hediff.Severity = biostarvationOffset; + selectedPawn.health.AddHediff(hediff); + } + + // Check for failure + if (biostarvation != null && biostarvation.Severity >= HediffDefOf.BioStarvation.maxSeverity) + { + Fail(); + return; + } + + // Update nutrition consumption rate on the component + RefuelableComp.currentConsumptionRate = NutritionConsumedPerDay; + } + else + { + // If not working, consumption is zero + if(RefuelableComp != null) + { + RefuelableComp.currentConsumptionRate = 0f; + } + } + } + + public override AcceptanceReport CanAcceptPawn(Pawn pawn) + { + if (base.Working) + { + return "Occupied".Translate(); + } + if (selectedPawn != null && selectedPawn != pawn) + { + return "WaitingForPawn".Translate(selectedPawn.Named("PAWN")); + } + if (pawn.health.hediffSet.HasHediff(HediffDefOf.BioStarvation)) + { + return "PawnBiostarving".Translate(pawn.Named("PAWN")); + } + return pawn.IsColonist && !pawn.IsQuestLodger(); + } + + public override void TryAcceptPawn(Pawn pawn) + { + if (!CanAcceptPawn(pawn)) + { + return; + } + + selectedPawn = pawn; + bool deselected = pawn.DeSpawnOrDeselect(); + if (innerContainer.TryAddOrTransfer(pawn)) + { + startTick = Find.TickManager.TicksGame; + } + if (deselected) + { + Find.Selector.Select(pawn, playSound: false, forceDesignatorDeselect: false); + } + } + + private void Finish() + { + if (selectedPawn != null && innerContainer.Contains(selectedPawn)) + { + Notify_PawnRemoved(); + innerContainer.TryDrop(selectedPawn, InteractionCell, base.Map, ThingPlaceMode.Near, 1, out var _); + OnStop(); + } + } + + private void Fail() + { + if (selectedPawn != null && innerContainer.Contains(selectedPawn)) + { + Notify_PawnRemoved(); + innerContainer.TryDrop(selectedPawn, InteractionCell, base.Map, ThingPlaceMode.Near, 1, out var _); + Hediff firstHediffOfDef = selectedPawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.BioStarvation); + selectedPawn.Kill(null, firstHediffOfDef); + } + OnStop(); + } + + private void OnStop() + { + selectedPawn = null; + startTick = -1; + if (RefuelableComp != null) + { + RefuelableComp.currentConsumptionRate = 0f; + } + } + + private void Notify_PawnRemoved() + { + // You can add sound effects here if you want, e.g., SoundDefOf.GrowthVat_Open.PlayOneShot(SoundInfo.InMap(this)); + } + + public override IEnumerable GetGizmos() + { + // Keep base gizmos + foreach (Gizmo gizmo in base.GetGizmos()) + { + yield return gizmo; + } + + if (base.Working) + { + yield return new Command_Action + { + defaultLabel = "CommandCancelGrowth".Translate(), // Label can be changed + defaultDesc = "CommandCancelGrowthDesc".Translate(), // Desc can be changed + icon = ContentFinder.Get("UI/Designators/Cancel"), + action = () => + { + Finish(); + innerContainer.TryDropAll(InteractionCell, base.Map, ThingPlaceMode.Near); + } + }; + } + else + { + if (selectedPawn != null) + { + yield return new Command_Action + { + defaultLabel = "CommandCancelLoad".Translate(), + defaultDesc = "CommandCancelLoadDesc".Translate(), + icon = ContentFinder.Get("UI/Designators/Cancel"), + action = () => + { + if (selectedPawn?.CurJobDef == JobDefOf.EnterBuilding) + { + selectedPawn.jobs.EndCurrentJob(JobCondition.InterruptForced); + } + OnStop(); + } + }; + } + + var command_Action = new Command_Action + { + defaultLabel = "InsertPerson".Translate() + "...", + defaultDesc = "InsertPersonGrowthVatDesc".Translate(), // Desc can be changed + icon = Building_GrowthVat.InsertPawnIcon.Texture, + action = () => + { + List list = new List(); + foreach (Pawn p in base.Map.mapPawns.AllPawnsSpawned) + { + if ((bool)CanAcceptPawn(p)) + { + list.Add(new FloatMenuOption(p.LabelCap, () => SelectPawn(p), p, Color.white)); + } + } + if (!list.Any()) + { + list.Add(new FloatMenuOption("NoViablePawns".Translate(), null)); + } + Find.WindowStack.Add(new FloatMenu(list)); + } + }; + if (!base.AnyAcceptablePawns) + { + command_Action.Disable("NoViablePawns".Translate()); + } + yield return command_Action; + } + } + + public override string GetInspectString() + { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append(base.GetInspectString()); + + if (base.Working && selectedPawn != null) + { + stringBuilder.AppendLineIfNotEmpty().Append("CasketContains".Translate().ToString() + ": " + selectedPawn.NameShortColored.Resolve()); + + Hediff biostarvation = selectedPawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.BioStarvation); + if (biostarvation != null && biostarvation.Severity > 0f) + { + string text = ((BiostarvationDailyOffset >= 0f) ? "+" : string.Empty); + stringBuilder.AppendLineIfNotEmpty().Append(string.Format("{0}: {1} ({2})", "Biostarvation".Translate(), biostarvation.Severity.ToStringPercent(), "PerDay".Translate(text + BiostarvationDailyOffset.ToStringPercent()))); + } + } + else if (selectedPawn != null) + { + stringBuilder.AppendLineIfNotEmpty().Append("WaitingForPawn".Translate(selectedPawn.Named("PAWN")).Resolve()); + } + + // The inspect string from CompRefuelableNutrition will be automatically added by the game. + return stringBuilder.ToString(); + } + + public override IEnumerable GetFloatMenuOptions(Pawn selPawn) + { + foreach (FloatMenuOption floatMenuOption in base.GetFloatMenuOptions(selPawn)) + { + yield return floatMenuOption; + } + if (!selPawn.CanReach(this, PathEndMode.InteractionCell, Danger.Deadly)) + { + yield return new FloatMenuOption("CannotEnterBuilding".Translate(this) + ": " + "NoPath".Translate().CapitalizeFirst(), null); + yield break; + } + AcceptanceReport acceptanceReport = CanAcceptPawn(selPawn); + if (acceptanceReport.Accepted) + { + yield return FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("EnterBuilding".Translate(this), () => SelectPawn(selPawn)), selPawn, this); + } + else if (!acceptanceReport.Reason.NullOrEmpty()) + { + yield return new FloatMenuOption("CannotEnterBuilding".Translate(this) + ": " + acceptanceReport.Reason.CapitalizeFirst(), null); + } + } + + public override void DynamicDrawPhaseAt(DrawPhase phase, Vector3 drawLoc, bool flip = false) + { + if (base.Working && selectedPawn != null && innerContainer.Contains(selectedPawn)) + { + selectedPawn.Drawer.renderer.DynamicDrawPhaseAt(phase, drawLoc + PawnDrawOffset, null, neverAimWeapon: true); + } + base.DynamicDrawPhaseAt(phase, drawLoc, flip); + } + + protected override void DrawAt(Vector3 drawLoc, bool flip = false) + { + base.DrawAt(drawLoc, flip); + // Draw the top graphic if it exists + if (TopGraphic != null) + { + TopGraphic.Draw(DrawPos + Altitudes.AltIncVect * 2f, base.Rotation, this); + } + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/DefModExtension_NutrientVat.cs b/Source/ArachnaeSwarm/DefModExtension_NutrientVat.cs new file mode 100644 index 0000000..896ebad --- /dev/null +++ b/Source/ArachnaeSwarm/DefModExtension_NutrientVat.cs @@ -0,0 +1,11 @@ +using System; +using Verse; + +namespace ArachnaeSwarm +{ + public class DefModExtension_NutrientVat : DefModExtension + { + public string topGraphicPath; + public Type graphicClass = typeof(Graphic_Multi); // Default to Graphic_Multi if not specified in XML + } +} \ No newline at end of file diff --git a/非公开资源/Content/Textures/Building/ARA_GrowthVat.psd b/非公开资源/Content/Textures/Building/ARA_GrowthVat.psd new file mode 100644 index 0000000..df45341 Binary files /dev/null and b/非公开资源/Content/Textures/Building/ARA_GrowthVat.psd differ