✅ 创建了 ArachnaeLog.cs - 中央化日志类,仅检查mod设置(不检查DevMode) ✅ 创建了 ArachnaeSwarmMod.cs - Mod主类,提供UI设置选项 ✅ 修改了 MainHarmony.cs - 移除重复的Harmony初始化(现在由ArachnaeSwarmMod处理) ✅ 修改了 .csproj - 添加了3个新文件到编译列表 ✅ 替换了所有582个 Log.Message/Error/Warning 调用为 ArachnaeLog.Debug()
773 lines
30 KiB
C#
773 lines
30 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;
|
||
|
||
// 新增字段:跟踪被建筑杀死的pawn
|
||
private HashSet<Pawn> pawnsKilledByVat = new HashSet<Pawn>();
|
||
|
||
// 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>();
|
||
|
||
// 安全字典操作方法
|
||
private bool SafeTryGetTickCount(Pawn pawn, out int tickCount)
|
||
{
|
||
if (pawn == null || pawn.Destroyed)
|
||
{
|
||
tickCount = 0;
|
||
return false;
|
||
}
|
||
return pawnTickCounters.TryGetValue(pawn, out tickCount);
|
||
}
|
||
|
||
private void SafeSetTickCount(Pawn pawn, int tickCount)
|
||
{
|
||
if (pawn != null && !pawn.Destroyed)
|
||
{
|
||
pawnTickCounters[pawn] = tickCount;
|
||
}
|
||
}
|
||
|
||
private void SafeRemoveFromDictionaries(Pawn pawn)
|
||
{
|
||
if (pawn != null)
|
||
{
|
||
pawnTickCounters.Remove(pawn);
|
||
pawnsKilledByVat.Remove(pawn);
|
||
}
|
||
}
|
||
|
||
private void CleanupInvalidDictionaryEntries()
|
||
{
|
||
// 清理无效的pawn引用
|
||
var invalidPawns = pawnTickCounters.Keys.Where(pawn => pawn == null || pawn.Destroyed).ToList();
|
||
foreach (var invalidPawn in invalidPawns)
|
||
{
|
||
pawnTickCounters.Remove(invalidPawn);
|
||
}
|
||
|
||
pawnsKilledByVat.RemoveWhere(pawn => pawn == null || pawn.Destroyed);
|
||
}
|
||
|
||
public override void SpawnSetup(Map map, bool respawningAfterLoad)
|
||
{
|
||
base.SpawnSetup(map, respawningAfterLoad);
|
||
cachedRefuelableComp = this.TryGetComp<CompRefuelable>();
|
||
cachedRefuelingVatComp = this.TryGetComp<CompRefuelingVat>();
|
||
cachedAutoEjectorComp = this.TryGetComp<CompAutoEjector>();
|
||
|
||
// 确保字典不为null
|
||
pawnTickCounters ??= new Dictionary<Pawn, int>();
|
||
pawnsKilledByVat ??= new HashSet<Pawn>();
|
||
|
||
CleanupInvalidDictionaryEntries();
|
||
}
|
||
|
||
public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish)
|
||
{
|
||
if (mode != DestroyMode.WillReplace)
|
||
{
|
||
if (selectedPawn != null && innerContainer.Contains(selectedPawn))
|
||
{
|
||
ArachnaeLog.Debug($"RefuelingVat despawned with pawn inside, forcing ejection.");
|
||
Finish(); // 使用修改后的Finish方法
|
||
}
|
||
}
|
||
|
||
// 清理字典
|
||
CleanupInvalidDictionaryEntries();
|
||
base.DeSpawn(mode);
|
||
}
|
||
|
||
private void ApplyAcidDamage(Pawn pawn)
|
||
{
|
||
try
|
||
{
|
||
if (pawn == null || pawn.Destroyed || pawn.Dead)
|
||
return;
|
||
|
||
BodyPartRecord targetPart = GetRandomVulnerablePart(pawn);
|
||
|
||
DamageDef acidDamageDef = DefDatabase<DamageDef>.GetNamed("AcidBurn") ?? DamageDefOf.Burn;
|
||
|
||
DamageInfo acidDamage = new DamageInfo(
|
||
acidDamageDef,
|
||
1.5f, // 每次1.5点伤害
|
||
2f, // 护甲穿透
|
||
-1f, // 随机角度
|
||
instigator: this, // 将建筑设为伤害来源
|
||
hitPart: targetPart
|
||
);
|
||
|
||
// 标记这个pawn将被建筑杀死
|
||
pawnsKilledByVat.Add(pawn);
|
||
|
||
// 应用伤害
|
||
pawn.TakeDamage(acidDamage);
|
||
|
||
// 立即检查pawn是否死亡
|
||
if (pawn.Dead)
|
||
{
|
||
HandlePawnDeath(pawn);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ArachnaeLog.Debug($"Error applying acid damage to {pawn}: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 新增方法:处理pawn死亡
|
||
private void HandlePawnDeath(Pawn pawn)
|
||
{
|
||
try
|
||
{
|
||
if (pawn == null || pawn.Destroyed)
|
||
return;
|
||
|
||
// 检查是否是被建筑杀死的
|
||
if (pawnsKilledByVat.Contains(pawn))
|
||
{
|
||
ArachnaeLog.Debug($"Pawn {pawn.Label} killed by RefuelingVat, destroying corpse.");
|
||
|
||
// 从容器中移除pawn
|
||
if (innerContainer.Contains(pawn))
|
||
{
|
||
innerContainer.Remove(pawn);
|
||
}
|
||
|
||
// 销毁pawn的尸体
|
||
if (!pawn.Destroyed)
|
||
{
|
||
pawn.Destroy();
|
||
}
|
||
|
||
// 从跟踪列表中移除
|
||
SafeRemoveFromDictionaries(pawn);
|
||
|
||
// 立即调用完成逻辑
|
||
if (selectedPawn == pawn)
|
||
{
|
||
selectedPawn = null;
|
||
startTick = -1;
|
||
}
|
||
|
||
return; // 直接返回,不执行后续弹出逻辑
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ArachnaeLog.Debug($"Error handling pawn death for {pawn}: {ex}");
|
||
}
|
||
}
|
||
|
||
private BodyPartRecord GetRandomVulnerablePart(Pawn pawn)
|
||
{
|
||
if (pawn == null || pawn.Destroyed)
|
||
return null;
|
||
|
||
// 优先选择外部身体部位
|
||
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 (selectedPawn.Dead || selectedPawn.Destroyed)
|
||
{
|
||
// 检查是否是被建筑杀死的
|
||
if (pawnsKilledByVat.Contains(selectedPawn))
|
||
{
|
||
HandlePawnDeath(selectedPawn);
|
||
}
|
||
else
|
||
{
|
||
Finish(); // 其他原因的死亡正常弹出
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 酸蚀伤害逻辑 - 使用安全的方法
|
||
if (SafeTryGetTickCount(selectedPawn, out int tickCount))
|
||
{
|
||
tickCount++;
|
||
SafeSetTickCount(selectedPawn, tickCount);
|
||
|
||
if (tickCount >= AcidDamageInterval)
|
||
{
|
||
ApplyAcidDamage(selectedPawn);
|
||
// 检查pawn是否还存在(可能在ApplyAcidDamage中被销毁)
|
||
if (selectedPawn != null && !selectedPawn.Destroyed)
|
||
{
|
||
SafeSetTickCount(selectedPawn, 0);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
SafeSetTickCount(selectedPawn, 0);
|
||
}
|
||
|
||
// 燃料生产逻辑
|
||
if (selectedPawn != null && HasFuelCapacity)
|
||
{
|
||
float fuelToAdd = FuelProductionPerTick;
|
||
RefuelableComp.Refuel(fuelToAdd);
|
||
}
|
||
}
|
||
}
|
||
|
||
public override AcceptanceReport CanAcceptPawn(Pawn pawn)
|
||
{
|
||
if (pawn == null || pawn.Destroyed)
|
||
return "Invalid 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 (pawn == null || pawn.Destroyed)
|
||
return;
|
||
|
||
if (!CanAcceptPawn(pawn).Accepted)
|
||
{
|
||
return;
|
||
}
|
||
|
||
selectedPawn = pawn;
|
||
bool deselected = pawn.DeSpawnOrDeselect();
|
||
if (innerContainer.TryAddOrTransfer(pawn))
|
||
{
|
||
startTick = Find.TickManager.TicksGame;
|
||
SafeSetTickCount(pawn, 0); // 初始化伤害计数器
|
||
|
||
// 确保pawn不在死亡跟踪列表中
|
||
pawnsKilledByVat.Remove(pawn);
|
||
}
|
||
if (deselected)
|
||
{
|
||
Find.Selector.Select(pawn, playSound: false, forceDesignatorDeselect: false);
|
||
}
|
||
}
|
||
|
||
private void Finish()
|
||
{
|
||
if (selectedPawn != null)
|
||
{
|
||
try
|
||
{
|
||
// 检查pawn是否还活着,如果已经死亡且是被建筑杀死的,则跳过弹出
|
||
if (selectedPawn.Dead && pawnsKilledByVat.Contains(selectedPawn))
|
||
{
|
||
HandlePawnDeath(selectedPawn);
|
||
return;
|
||
}
|
||
|
||
Notify_PawnRemoved();
|
||
|
||
bool ejected = false;
|
||
string ejectionMethod = "None";
|
||
|
||
// 方法1:标准弹出 - 在交互单元格附近
|
||
if (innerContainer.Contains(selectedPawn) && !selectedPawn.Dead)
|
||
{
|
||
ejected = innerContainer.TryDrop(selectedPawn, InteractionCell, base.Map, ThingPlaceMode.Near, 1, out var _);
|
||
if (ejected) ejectionMethod = "Standard";
|
||
}
|
||
|
||
// 方法2:尝试随机相邻单元格
|
||
if (!ejected && innerContainer.Contains(selectedPawn) && !selectedPawn.Dead)
|
||
{
|
||
var adjacentCells = GenAdj.CellsAdjacent8Way(this).Where(c => c.Walkable(base.Map) && c.InBounds(base.Map)).ToList();
|
||
if (adjacentCells.Count > 0)
|
||
{
|
||
IntVec3 randomCell = adjacentCells.RandomElement();
|
||
ejected = innerContainer.TryDrop(selectedPawn, randomCell, base.Map, ThingPlaceMode.Direct, 1, out var _);
|
||
if (ejected) ejectionMethod = "Adjacent";
|
||
}
|
||
}
|
||
|
||
// 方法3:强制移除(仅对活着的pawn)
|
||
if (!ejected && innerContainer.Contains(selectedPawn) && !selectedPawn.Dead)
|
||
{
|
||
ArachnaeLog.Debug($"Forcing removal of pawn {selectedPawn} from RefuelingVat");
|
||
innerContainer.Remove(selectedPawn);
|
||
GenPlace.TryPlaceThing(selectedPawn, this.Position, base.Map, ThingPlaceMode.Near);
|
||
ejected = true;
|
||
ejectionMethod = "Forced";
|
||
}
|
||
|
||
if (ejected)
|
||
{
|
||
ArachnaeLog.Debug($"Successfully ejected {selectedPawn} using method: {ejectionMethod}");
|
||
}
|
||
else if (!selectedPawn.Dead) // 只有活着的pawn弹出失败才报错
|
||
{
|
||
ArachnaeLog.Debug($"Failed to eject {selectedPawn} from RefuelingVat");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ArachnaeLog.Debug($"Error during Finish() for {selectedPawn}: {ex}");
|
||
}
|
||
finally
|
||
{
|
||
OnStop();
|
||
}
|
||
}
|
||
}
|
||
|
||
private void OnStop()
|
||
{
|
||
if (selectedPawn != null)
|
||
{
|
||
// 安全地从所有字典中移除
|
||
SafeRemoveFromDictionaries(selectedPawn);
|
||
|
||
// 确保pawn不在容器中(除非是被建筑杀死的)
|
||
if (innerContainer.Contains(selectedPawn) && !(selectedPawn.Dead && pawnsKilledByVat.Contains(selectedPawn)))
|
||
{
|
||
ArachnaeLog.Debug($"Pawn {selectedPawn} still in container during OnStop, forcing removal.");
|
||
innerContainer.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 (p != null && !p.Destroyed && 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 (p != null && !p.Destroyed && 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 => p != null && !p.Destroyed && 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 && !selectedPawn.Destroyed)
|
||
{
|
||
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 (SafeTryGetTickCount(selectedPawn, out int tickCount))
|
||
{
|
||
float damageProgress = (float)tickCount / AcidDamageInterval;
|
||
stringBuilder.AppendLineIfNotEmpty().Append("AcidDamageProgress".Translate() + ": " + damageProgress.ToStringPercent());
|
||
}
|
||
}
|
||
else if (selectedPawn != null && !selectedPawn.Destroyed)
|
||
{
|
||
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 == null || selPawn.Destroyed)
|
||
yield break;
|
||
|
||
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 && !selectedPawn.Destroyed && 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 || prisoner.Destroyed || carrier == null || carrier.Destroyed)
|
||
return null;
|
||
if (!CanAcceptPawn(prisoner).Accepted)
|
||
return null;
|
||
// 创建搬运工作定义 - 使用新的 JobDef
|
||
JobDef carryJobDef = DefDatabase<JobDef>.GetNamed("ARA_CarryPrisonerToRefuelingVat");
|
||
if (carryJobDef == null)
|
||
{
|
||
ArachnaeLog.Debug("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 == null || pawn.Destroyed)
|
||
continue;
|
||
|
||
// 检查是否是虫群成员
|
||
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 || prisoner.Destroyed)
|
||
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)
|
||
{
|
||
if (carrier == null || carrier.Destroyed)
|
||
continue;
|
||
|
||
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);
|
||
Scribe_Collections.Look(ref pawnsKilledByVat, "pawnsKilledByVat", LookMode.Reference);
|
||
|
||
// 确保集合不为null
|
||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||
{
|
||
pawnsKilledByVat ??= new HashSet<Pawn>();
|
||
pawnTickCounters ??= new Dictionary<Pawn, int>();
|
||
|
||
// 更严格的清理可能已销毁的pawn引用
|
||
CleanupInvalidDictionaryEntries();
|
||
|
||
// 如果selectedPawn无效,重置状态
|
||
if (selectedPawn != null && (selectedPawn.Destroyed || selectedPawn.Dead))
|
||
{
|
||
SafeRemoveFromDictionaries(selectedPawn);
|
||
selectedPawn = null;
|
||
startTick = -1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|