545 lines
20 KiB
C#
545 lines
20 KiB
C#
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_RefuelingVat : Building_Enterable, IThingHolder, IThingHolderWithDrawnPawn
|
||
{
|
||
private CompRefuelable cachedRefuelableComp;
|
||
private CompRefuelingVat cachedRefuelingVatComp;
|
||
private CompAutoEjector cachedAutoEjectorComp;
|
||
private Graphic cachedTopGraphic;
|
||
|
||
// IThingHolderWithDrawnPawn implementation
|
||
public float HeldPawnDrawPos_Y => DrawPos.y + 0.03658537f;
|
||
public float HeldPawnBodyAngle => base.Rotation.AsAngle;
|
||
public PawnPosture HeldPawnPosture => PawnPosture.LayingOnGroundFaceUp;
|
||
|
||
// 从 Comp 中获取属性
|
||
public float FuelProductionPerDay => RefuelingVatComp?.FuelProductionPerDay ?? 10f;
|
||
public float FuelProductionEfficiency => RefuelingVatComp?.FuelProductionEfficiency ?? 1f;
|
||
|
||
private Graphic TopGraphic
|
||
{
|
||
get
|
||
{
|
||
if (cachedTopGraphic == null)
|
||
{
|
||
var modExtension = def.GetModExtension<DefModExtension_NutrientVat>();
|
||
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 override Vector3 PawnDrawOffset => Vector3.zero;
|
||
|
||
public CompRefuelable RefuelableComp
|
||
{
|
||
get
|
||
{
|
||
if (cachedRefuelableComp == null)
|
||
{
|
||
cachedRefuelableComp = this.TryGetComp<CompRefuelable>();
|
||
}
|
||
return cachedRefuelableComp;
|
||
}
|
||
}
|
||
|
||
public CompRefuelingVat RefuelingVatComp
|
||
{
|
||
get
|
||
{
|
||
if (cachedRefuelingVatComp == null)
|
||
{
|
||
cachedRefuelingVatComp = this.TryGetComp<CompRefuelingVat>();
|
||
}
|
||
return cachedRefuelingVatComp;
|
||
}
|
||
}
|
||
|
||
public CompAutoEjector AutoEjectorComp
|
||
{
|
||
get
|
||
{
|
||
if (cachedAutoEjectorComp == null)
|
||
{
|
||
cachedAutoEjectorComp = this.TryGetComp<CompAutoEjector>();
|
||
}
|
||
return cachedAutoEjectorComp;
|
||
}
|
||
}
|
||
|
||
public bool HasFuelCapacity => RefuelableComp != null && RefuelableComp.Fuel < RefuelableComp.Props.fuelCapacity;
|
||
public bool IsFuelFull => RefuelableComp != null && RefuelableComp.Fuel >= RefuelableComp.Props.fuelCapacity;
|
||
|
||
// 燃料生产速率(每Tick)
|
||
public float FuelProductionPerTick
|
||
{
|
||
get
|
||
{
|
||
if (selectedPawn != null && base.Working)
|
||
{
|
||
return (FuelProductionPerDay * FuelProductionEfficiency) / 60000f;
|
||
}
|
||
return 0f;
|
||
}
|
||
}
|
||
|
||
// 酸蚀伤害间隔(每6000 tick约1/10天)
|
||
private const int AcidDamageInterval = 6000;
|
||
private Dictionary<Pawn, int> pawnTickCounters = new Dictionary<Pawn, int>();
|
||
|
||
public override void SpawnSetup(Map map, bool respawningAfterLoad)
|
||
{
|
||
base.SpawnSetup(map, respawningAfterLoad);
|
||
cachedRefuelableComp = this.TryGetComp<CompRefuelable>();
|
||
cachedRefuelingVatComp = this.TryGetComp<CompRefuelingVat>();
|
||
cachedAutoEjectorComp = this.TryGetComp<CompAutoEjector>();
|
||
}
|
||
|
||
public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish)
|
||
{
|
||
if (mode != DestroyMode.WillReplace)
|
||
{
|
||
if (selectedPawn != null && innerContainer.Contains(selectedPawn))
|
||
{
|
||
Notify_PawnRemoved();
|
||
}
|
||
}
|
||
base.DeSpawn(mode);
|
||
}
|
||
|
||
private void ApplyAcidDamage(Pawn pawn)
|
||
{
|
||
try
|
||
{
|
||
BodyPartRecord targetPart = GetRandomVulnerablePart(pawn);
|
||
|
||
DamageDef acidDamageDef = DefDatabase<DamageDef>.GetNamed("AcidBurn") ?? DamageDefOf.Burn;
|
||
|
||
DamageInfo acidDamage = new DamageInfo(
|
||
acidDamageDef,
|
||
0.1f, // 每次0.1点伤害
|
||
2f, // 护甲穿透
|
||
-1f, // 随机角度
|
||
instigator: null,
|
||
hitPart: targetPart
|
||
);
|
||
|
||
// 应用伤害
|
||
pawn.TakeDamage(acidDamage);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error($"Error applying acid 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;
|
||
}
|
||
|
||
protected override void Tick()
|
||
{
|
||
base.Tick();
|
||
|
||
if (selectedPawn != null && (selectedPawn.Destroyed || !innerContainer.Contains(selectedPawn)))
|
||
{
|
||
OnStop();
|
||
return;
|
||
}
|
||
|
||
if (base.Working && selectedPawn != null)
|
||
{
|
||
// 酸蚀伤害逻辑
|
||
if (pawnTickCounters.TryGetValue(selectedPawn, out int tickCount))
|
||
{
|
||
tickCount++;
|
||
pawnTickCounters[selectedPawn] = tickCount;
|
||
|
||
if (tickCount >= AcidDamageInterval)
|
||
{
|
||
ApplyAcidDamage(selectedPawn);
|
||
pawnTickCounters[selectedPawn] = 0;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
pawnTickCounters[selectedPawn] = 0;
|
||
}
|
||
|
||
// 燃料生产逻辑
|
||
if (HasFuelCapacity)
|
||
{
|
||
float fuelToAdd = FuelProductionPerTick;
|
||
RefuelableComp.Refuel(fuelToAdd);
|
||
}
|
||
|
||
// 检查俘虏是否死亡
|
||
if (selectedPawn.Dead)
|
||
{
|
||
Finish();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
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(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;
|
||
pawnTickCounters[pawn] = 0; // 初始化伤害计数器
|
||
}
|
||
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 OnStop()
|
||
{
|
||
if (selectedPawn != null)
|
||
{
|
||
pawnTickCounters.Remove(selectedPawn);
|
||
}
|
||
selectedPawn = null;
|
||
startTick = -1;
|
||
}
|
||
|
||
private void Notify_PawnRemoved()
|
||
{
|
||
// 可以在这里添加音效
|
||
}
|
||
|
||
public override IEnumerable<Gizmo> GetGizmos()
|
||
{
|
||
// 原有的基础Gizmos
|
||
foreach (Gizmo gizmo in base.GetGizmos())
|
||
{
|
||
yield return gizmo;
|
||
}
|
||
|
||
if (base.Working)
|
||
{
|
||
}
|
||
else
|
||
{
|
||
// 选择殖民者的操作
|
||
var command_Action = new Command_Action
|
||
{
|
||
defaultLabel = "InsertPerson".Translate() + "...",
|
||
defaultDesc = "InsertPersonRefuelingVatDesc".Translate(),
|
||
icon = Building_GrowthVat.InsertPawnIcon.Texture,
|
||
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)
|
||
{
|
||
command_Action.Disable("NoViablePawns".Translate());
|
||
}
|
||
yield return command_Action;
|
||
|
||
// 指派搬运囚犯/奴隶的操作
|
||
var command_CarryPrisoner = new Command_Action
|
||
{
|
||
defaultLabel = "AssignCarryPrisoner".Translate() + "...",
|
||
defaultDesc = "AssignCarryPrisonerDesc".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 (HasFuelCapacity)
|
||
{
|
||
stringBuilder.AppendLineIfNotEmpty().Append("FuelProduction".Translate() + ": " + FuelProductionPerDay.ToString("F1") + " per day");
|
||
stringBuilder.AppendLineIfNotEmpty().Append("ProductionEfficiency".Translate() + ": " + FuelProductionEfficiency.ToStringPercent());
|
||
|
||
// 显示当前燃料状态
|
||
if (RefuelableComp != null)
|
||
{
|
||
stringBuilder.AppendLineIfNotEmpty().Append("CurrentFuel".Translate() + ": " + RefuelableComp.Fuel.ToString("F1") + " / " + RefuelableComp.Props.fuelCapacity);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
stringBuilder.AppendLineIfNotEmpty().Append("FuelTankFull".Translate());
|
||
}
|
||
|
||
// 显示酸蚀伤害信息
|
||
if (pawnTickCounters.TryGetValue(selectedPawn, out int tickCount))
|
||
{
|
||
float damageProgress = (float)tickCount / AcidDamageInterval;
|
||
stringBuilder.AppendLineIfNotEmpty().Append("AcidDamageProgress".Translate() + ": " + damageProgress.ToStringPercent());
|
||
}
|
||
}
|
||
else if (selectedPawn != null)
|
||
{
|
||
stringBuilder.AppendLineIfNotEmpty().Append("WaitingForPawn".Translate(selectedPawn.Named("PAWN")).Resolve());
|
||
}
|
||
else
|
||
{
|
||
// 显示空闲状态信息
|
||
stringBuilder.AppendLineIfNotEmpty().Append("RefuelingVatIdle".Translate());
|
||
if (RefuelableComp != null)
|
||
{
|
||
stringBuilder.AppendLineIfNotEmpty().Append("CurrentFuel".Translate() + ": " + RefuelableComp.Fuel.ToString("F1") + " / " + RefuelableComp.Props.fuelCapacity);
|
||
}
|
||
}
|
||
|
||
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("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);
|
||
// 绘制顶部图形
|
||
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
|
||
JobDef carryJobDef = DefDatabase<JobDef>.GetNamed("ARA_CarryPrisonerToRefuelingVat");
|
||
if (carryJobDef == null)
|
||
{
|
||
Log.Error("ARA_CarryPrisonerToRefuelingVat JobDef not found!");
|
||
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);
|
||
}
|
||
}
|
||
|
||
// 保存和加载数据
|
||
public override void ExposeData()
|
||
{
|
||
base.ExposeData();
|
||
Scribe_Collections.Look(ref pawnTickCounters, "pawnTickCounters", LookMode.Reference, LookMode.Value);
|
||
}
|
||
}
|
||
}
|