This commit is contained in:
2025-10-14 17:54:37 +08:00
2 changed files with 474 additions and 0 deletions

View File

@@ -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<DefModExtension_SacrificialFuelTank>();
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<CompRefuelable>();
}
return refuelableComp;
}
}
public override Vector3 PawnDrawOffset => Vector3.zero;
public override void SpawnSetup(Map map, bool respawningAfterLoad)
{
base.SpawnSetup(map, respawningAfterLoad);
refuelableComp = this.TryGetComp<CompRefuelable>();
}
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<Gizmo> 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<Texture2D>.Get("UI/Commands/Sacrifice"),
action = () =>
{
List<FloatMenuOption> list = new List<FloatMenuOption>();
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<Texture2D>.Get("UI/Commands/Attack"),
action = () =>
{
List<FloatMenuOption> list = new List<FloatMenuOption>();
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<FloatMenuOption> 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<JobDef>.GetNamed("ARA_CarryPrisonerToSacrificialTank");
if (carryJobDef == null)
return null;
Job job = JobMaker.MakeJob(carryJobDef, prisoner, this);
job.count = 1;
return job;
}
public IEnumerable<Pawn> 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<FloatMenuOption> options = new List<FloatMenuOption>();
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);
}
}
}
}

View File

@@ -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;
}
}