From 1d1c3115a96b7719750226b0e7e515a90c37a24a Mon Sep 17 00:00:00 2001 From: Tourswen <565033799@qq.com> Date: Tue, 14 Oct 2025 17:43:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E9=81=97=E4=BC=A0=E7=89=A9=E8=B4=A8?= =?UTF-8?q?=E6=B6=88=E5=8C=96=E8=8C=A7=E5=88=9D=E7=A8=BF=E6=9C=AA=E7=BC=96?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Building_SacrificialFuelTank.cs | 458 ++++++++++++++++++ .../DefModExtension_SacrificialFuelTank.cs | 16 + 2 files changed, 474 insertions(+) create mode 100644 Source/ArachnaeSwarm/Building_Comps/ARA_SacrificialFuelTank/Building_SacrificialFuelTank.cs create mode 100644 Source/ArachnaeSwarm/Building_Comps/ARA_SacrificialFuelTank/DefModExtension_SacrificialFuelTank.cs diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_SacrificialFuelTank/Building_SacrificialFuelTank.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_SacrificialFuelTank/Building_SacrificialFuelTank.cs new file mode 100644 index 0000000..76f5f28 --- /dev/null +++ b/Source/ArachnaeSwarm/Building_Comps/ARA_SacrificialFuelTank/Building_SacrificialFuelTank.cs @@ -0,0 +1,458 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Verse; +using Verse.AI; + +namespace ArachnaeSwarm +{ + [StaticConstructorOnStartup] + public class Building_SacrificialFuelTank : Building_Enterable, IThingHolder, IThingHolderWithDrawnPawn + { + private CompRefuelable refuelableComp; + 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 const float DAMAGE_TO_FUEL_RATIO = 0.1f; // 每点伤害转换为0.1燃料 + private const float DAMAGE_INTERVAL_TICKS = 250; // 每250ticks造成一次伤害 + private const float DAMAGE_PER_INTERVAL = 5f; // 每次间隔造成5点伤害 + + 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; + } + } + + public CompRefuelable RefuelableComp + { + get + { + if (refuelableComp == null) + { + refuelableComp = this.TryGetComp(); + } + return refuelableComp; + } + } + + public override Vector3 PawnDrawOffset => Vector3.zero; + + public override void SpawnSetup(Map map, bool respawningAfterLoad) + { + base.SpawnSetup(map, respawningAfterLoad); + refuelableComp = this.TryGetComp(); + } + + public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish) + { + if (mode != DestroyMode.WillReplace && selectedPawn != null && innerContainer.Contains(selectedPawn)) + { + Notify_PawnRemoved(); + } + base.DeSpawn(mode); + } + + // 造成伤害并转换为燃料 + private void ApplySacrificialDamage(Pawn pawn) + { + try + { + if (pawn.Dead || pawn.Downed) return; + + // 选择随机的身体部位 + BodyPartRecord targetPart = GetRandomVulnerablePart(pawn); + if (targetPart == null) return; + + // 造成伤害 + DamageInfo damage = new DamageInfo( + DamageDefOf.Cut, // 使用切割伤害 + DAMAGE_PER_INTERVAL, + 0.5f, // 中等护甲穿透 + -1f, + instigator: null, + hitPart: targetPart + ); + + float damageDealt = pawn.TakeDamage(damage).TotalDamageDealt; + + // 将伤害转换为燃料 + if (damageDealt > 0 && RefuelableComp != null) + { + float fuelToAdd = damageDealt * DAMAGE_TO_FUEL_RATIO; + RefuelableComp.Refuel(fuelToAdd); + } + + // 如果pawn死亡,停止处理 + if (pawn.Dead) + { + OnPawnDied(); + } + } + catch (Exception ex) + { + Log.Error($"Error applying sacrificial damage to {pawn}: {ex.Message}"); + } + } + + private BodyPartRecord GetRandomVulnerablePart(Pawn pawn) + { + // 优先选择外部身体部位 + var vulnerableParts = pawn.health.hediffSet.GetNotMissingParts() + .Where(part => part.depth == BodyPartDepth.Outside && + !part.def.conceptual && + part.def.hitPoints > 0) + .ToList(); + + return vulnerableParts.Count > 0 ? vulnerableParts.RandomElement() : pawn.RaceProps.body.corePart; + } + + private int damageTickCounter = 0; + + protected override void Tick() + { + base.Tick(); + + if (selectedPawn != null && (selectedPawn.Destroyed || !innerContainer.Contains(selectedPawn))) + { + OnStop(); + return; + } + + if (base.Working && selectedPawn != null && !selectedPawn.Dead) + { + damageTickCounter++; + + // 定期造成伤害 + if (damageTickCounter >= DAMAGE_INTERVAL_TICKS) + { + ApplySacrificialDamage(selectedPawn); + damageTickCounter = 0; + } + + // 检查pawn是否死亡 + if (selectedPawn.Dead) + { + OnPawnDied(); + } + } + } + + private void OnPawnDied() + { + if (selectedPawn != null && innerContainer.Contains(selectedPawn)) + { + // 弹出死亡的pawn + Notify_PawnRemoved(); + innerContainer.TryDrop(selectedPawn, InteractionCell, base.Map, ThingPlaceMode.Near, 1, out var _); + OnStop(); + + // 可以选择在这里添加一些效果,比如声音或信息 + Messages.Message("SacrificialFuelTank_PawnDied".Translate(selectedPawn.LabelShort), this, MessageTypeDefOf.NeutralEvent); + } + } + + 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.Dead || pawn.Downed) + { + return "PawnMustBeAlive".Translate(); + } + + // 禁止虫群成员 + if (pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindMaster) || + pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindDrone) || + pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindWorker)) + { + return "PawnIsHiveMember".Translate(pawn.Named("PAWN")); + } + + // 允许殖民者、囚犯和奴隶,但不能是任务寄宿者 + bool isColonist = pawn.IsColonist; + bool isPrisoner = pawn.IsPrisonerOfColony; + bool isSlave = pawn.IsSlaveOfColony; + + return (isColonist || isPrisoner || isSlave) && !pawn.IsQuestLodger(); + } + + public override void TryAcceptPawn(Pawn pawn) + { + if (!CanAcceptPawn(pawn).Accepted) + { + return; + } + + selectedPawn = pawn; + bool deselected = pawn.DeSpawnOrDeselect(); + if (innerContainer.TryAddOrTransfer(pawn)) + { + startTick = Find.TickManager.TicksGame; + damageTickCounter = 0; // 重置伤害计数器 + } + if (deselected) + { + Find.Selector.Select(pawn, playSound: false, forceDesignatorDeselect: false); + } + } + + private void OnStop() + { + selectedPawn = null; + startTick = -1; + damageTickCounter = 0; + } + + private void Notify_PawnRemoved() + { + // 可以在这里添加声音效果 + } + + public override IEnumerable GetGizmos() + { + foreach (Gizmo gizmo in base.GetGizmos()) + { + yield return gizmo; + } + + if (!base.Working) + { + // 插入人员的操作 + var insertCommand = new Command_Action + { + defaultLabel = "InsertSacrificialPawn".Translate() + "...", + defaultDesc = "InsertSacrificialPawnDesc".Translate(), + icon = ContentFinder.Get("UI/Commands/Sacrifice"), + action = () => + { + List list = new List(); + foreach (Pawn p in base.Map.mapPawns.AllPawnsSpawned) + { + if (CanAcceptPawn(p).Accepted) + { + 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) + { + insertCommand.Disable("NoViablePawns".Translate()); + } + yield return insertCommand; + + // 指派搬运囚犯/奴隶的操作 + var command_CarryPrisoner = new Command_Action + { + defaultLabel = "AssignCarryPrisoner".Translate() + "...", + defaultDesc = "AssignCarryPrisonerSacrificialDesc".Translate(), + icon = ContentFinder.Get("UI/Commands/Attack"), + action = () => + { + List list = new List(); + foreach (Pawn p in base.Map.mapPawns.AllPawnsSpawned) + { + if (CanAcceptPawn(p).Accepted && (p.IsPrisonerOfColony || p.IsSlaveOfColony)) + { + list.Add(new FloatMenuOption( + p.LabelCap + " (" + (p.IsPrisonerOfColony ? "Prisoner" : "Slave") + ")", + () => AssignCarrierForPrisoner(p), + p, + Color.white + )); + } + } + if (!list.Any()) + { + list.Add(new FloatMenuOption("NoPrisonersOrSlaves".Translate(), null)); + } + Find.WindowStack.Add(new FloatMenu(list)); + } + }; + + bool hasPrisonersOrSlaves = base.Map.mapPawns.AllPawnsSpawned + .Any(p => CanAcceptPawn(p).Accepted && (p.IsPrisonerOfColony || p.IsSlaveOfColony)); + + if (!hasPrisonersOrSlaves) + { + command_CarryPrisoner.Disable("NoPrisonersOrSlaves".Translate()); + } + + yield return command_CarryPrisoner; + } + } + + 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()); + + // 显示燃料转换效率 + if (RefuelableComp != null) + { + stringBuilder.AppendLineIfNotEmpty().Append("FuelConversionRate".Translate() + ": " + (DAMAGE_TO_FUEL_RATIO * 100).ToString("F1") + "%"); + stringBuilder.AppendLineIfNotEmpty().Append("CurrentFuel".Translate() + ": " + RefuelableComp.Fuel.ToString("F1") + " / " + RefuelableComp.Props.fuelCapacity.ToString("F1")); + } + + // 显示pawn的健康状态 + if (selectedPawn.health.summaryHealth.SummaryHealthPercent < 1.0f) + { + stringBuilder.AppendLineIfNotEmpty().Append("PawnHealth".Translate() + ": " + selectedPawn.health.summaryHealth.SummaryHealthPercent.ToStringPercent()); + } + } + else if (selectedPawn != null) + { + stringBuilder.AppendLineIfNotEmpty().Append("WaitingForPawn".Translate(selectedPawn.Named("PAWN")).Resolve()); + } + + 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("EnterSacrificialTank".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); + if (TopGraphic != null) + { + TopGraphic.Draw(DrawPos + Altitudes.AltIncVect * 2f, base.Rotation, this); + } + } + + public Job CreateCarryJobForPrisoner(Pawn prisoner, Pawn carrier) + { + if (prisoner == null || carrier == null) + return null; + if (!CanAcceptPawn(prisoner).Accepted) + return null; + + JobDef carryJobDef = DefDatabase.GetNamed("ARA_CarryPrisonerToSacrificialTank"); + if (carryJobDef == null) + return null; + + Job job = JobMaker.MakeJob(carryJobDef, prisoner, this); + job.count = 1; + return job; + } + + public IEnumerable GetAvailableCarriers() + { + foreach (Pawn pawn in base.Map.mapPawns.AllPawnsSpawned) + { + if (pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindDrone) || + pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindWorker) || + pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindMaster)) + { + if (pawn.workSettings != null && pawn.workSettings.WorkIsActive(WorkTypeDefOf.Hauling) && + !pawn.Downed && !pawn.IsPrisoner && pawn.CanReach(this, PathEndMode.InteractionCell, Danger.Some)) + { + yield return pawn; + } + } + } + } + + public void AssignCarrierForPrisoner(Pawn prisoner) + { + if (prisoner == null) + return; + + var availableCarriers = GetAvailableCarriers().ToList(); + if (!availableCarriers.Any()) + { + Messages.Message("NoAvailableHiveCarriers".Translate(), MessageTypeDefOf.RejectInput); + return; + } + + List options = new List(); + foreach (Pawn carrier in availableCarriers) + { + options.Add(new FloatMenuOption( + carrier.LabelCap, + () => + { + Job carryJob = CreateCarryJobForPrisoner(prisoner, carrier); + if (carryJob != null) + { + carrier.jobs.TryTakeOrderedJob(carryJob, JobTag.MiscWork); + Messages.Message("CarrierAssigned".Translate(carrier.LabelShort, prisoner.LabelShort), MessageTypeDefOf.NeutralEvent); + } + }, + carrier, + Color.white + )); + } + + if (options.Any()) + { + Find.WindowStack.Add(new FloatMenu(options)); + } + else + { + Messages.Message("NoAvailableCarriers".Translate(), MessageTypeDefOf.RejectInput); + } + } + } +} diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_SacrificialFuelTank/DefModExtension_SacrificialFuelTank.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_SacrificialFuelTank/DefModExtension_SacrificialFuelTank.cs new file mode 100644 index 0000000..d13a2d5 --- /dev/null +++ b/Source/ArachnaeSwarm/Building_Comps/ARA_SacrificialFuelTank/DefModExtension_SacrificialFuelTank.cs @@ -0,0 +1,16 @@ +using System; +using Verse; + +namespace ArachnaeSwarm +{ + public class DefModExtension_SacrificialFuelTank : DefModExtension + { + public string topGraphicPath; + public Type graphicClass = typeof(Graphic_Multi); + + // 可配置的伤害和燃料转换参数 + public float damageToFuelRatio = 0.1f; + public int damageIntervalTicks = 250; + public float damagePerInterval = 5f; + } +}