Files
WulaFallenEmpireRW/Source/WulaFallenEmpire/Pawn_Comps/MechPilotHolder/CompMechPilotHolder.cs
2026-02-28 11:50:54 +08:00

959 lines
30 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using WulaFallenEmpire;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class CompProperties_MechPilotHolder : CompProperties
{
public int maxPilots = 1;
public string pilotWorkTag = "MechPilot";
// 图标配置
public string summonPilotIcon = "Wula/UI/Commands/WULA_Enter_Mech";
public string ejectPilotIcon = "Wula/UI/Commands/WULA_Exit_Mech";
public string recallPilotIcon = "Wula/UI/Commands/WULA_Recall_Pilot";
// 快速登机配置
public bool enableQuickRecall = true;
public float recallRadius = 30f;
public float ejectPilotHealthPercentThreshold = 0.1f;
public bool allowEntryBelowThreshold = false;
// Hediff同步配置
public bool syncPilotHediffs = true;
public List<string> syncedHediffDefs = null;
public bool autoApplyHediffOnEntry = false;
public HediffDef autoHediffDef = null;
public float autoHediffSeverity = 0.5f;
public CompProperties_MechPilotHolder()
{
this.compClass = typeof(CompMechPilotHolder);
}
// 图标加载方法
public Texture2D GetSummonPilotIcon()
{
if (!string.IsNullOrEmpty(summonPilotIcon) && ContentFinder<Texture2D>.Get(summonPilotIcon, false) != null)
{
return ContentFinder<Texture2D>.Get(summonPilotIcon);
}
return ContentFinder<Texture2D>.Get("UI/Commands/SummonPilot", false) ??
BaseContent.BadTex;
}
public Texture2D GetEjectPilotIcon()
{
if (!string.IsNullOrEmpty(ejectPilotIcon) && ContentFinder<Texture2D>.Get(ejectPilotIcon, false) != null)
{
return ContentFinder<Texture2D>.Get(ejectPilotIcon);
}
return ContentFinder<Texture2D>.Get("UI/Commands/Eject", false) ??
BaseContent.BadTex;
}
public Texture2D GetRecallPilotIcon()
{
if (!string.IsNullOrEmpty(recallPilotIcon) && ContentFinder<Texture2D>.Get(recallPilotIcon, false) != null)
{
return ContentFinder<Texture2D>.Get(recallPilotIcon);
}
return ContentFinder<Texture2D>.Get("UI/Commands/SummonPilot", false) ??
BaseContent.BadTex;
}
}
public class CompMechPilotHolder : ThingComp, IThingHolder, ISuspendableThingHolder
{
public ThingOwner innerContainer;
private bool isProcessingDestruction = false;
private bool hasEjectedDueToLowHealth = false;
private Dictionary<Pawn, List<Hediff>> syncedHediffs = new Dictionary<Pawn, List<Hediff>>();
// 新增:记录上一次的驾驶员
private Pawn lastPilot = null;
public CompProperties_MechPilotHolder Props => (CompProperties_MechPilotHolder)props;
public int CurrentPilotCount => innerContainer.Count;
public bool HasPilots => innerContainer.Count > 0;
public bool HasRoom => innerContainer.Count < Props.maxPilots;
public bool IsFull => innerContainer.Count >= Props.maxPilots;
public bool IsContentsSuspended => true;
// 精神状态定义
private MentalStateDef MechNoPilotStateDef => WULA_MentalStateDefOf.WULA_MechNoPilot;
private void CheckAndUpdateMentalState()
{
var mech = parent as Pawn;
if (mech == null || mech.Dead || MechNoPilotStateDef == null)
return;
if (!HasPilots)
{
if (mech.MentalStateDef != MechNoPilotStateDef && !mech.InMentalState)
{
mech.mindState.mentalStateHandler.TryStartMentalState(MechNoPilotStateDef, null, true);
}
}
else
{
if (mech.MentalStateDef == MechNoPilotStateDef)
{
mech.mindState.mentalStateHandler.CurState?.RecoverFromState();
}
}
}
// 添加驾驶员
public void AddPilot(Pawn pawn)
{
if (!CanAddPilot(pawn))
return;
// 记录驾驶员
if (lastPilot != pawn)
{
lastPilot = pawn;
}
if (pawn.Spawned)
pawn.DeSpawnOrDeselect();
innerContainer.TryAdd(pawn, true);
pawn.pather?.StopDead();
pawn.jobs?.StopAll();
Notify_PilotAdded(pawn);
CheckAndUpdateMentalState();
if (Props.syncPilotHediffs)
{
SyncPilotHediffs(pawn);
}
if (Props.autoApplyHediffOnEntry && Props.autoHediffDef != null)
{
AddAutoHediff(pawn);
}
}
// 移除驾驶员
public void RemovePilot(Pawn pawn, IntVec3? exitPos = null)
{
if (Props.syncPilotHediffs)
{
UnsyncPilotHediffs(pawn);
}
if (innerContainer.Contains(pawn))
{
innerContainer.Remove(pawn);
TrySpawnPilotAtPosition(pawn, exitPos ?? parent.Position);
Notify_PilotRemoved(pawn);
StopMechJobs();
CheckAndUpdateMentalState();
}
}
private void SyncPilotHediffs(Pawn pawn)
{
if (pawn == null || !(parent is Wulamechunit mech))
return;
try
{
var hediffsToSync = new List<Hediff>();
foreach (var hediff in pawn.health.hediffSet.hediffs)
{
if (ShouldSyncHediff(hediff))
{
hediffsToSync.Add(hediff);
var syncComp = hediff.TryGetComp<HediffComp_SyncedWithMech>();
if (syncComp != null)
{
syncComp.OnPilotEnteredMech(mech);
}
}
}
if (hediffsToSync.Count > 0)
{
syncedHediffs[pawn] = hediffsToSync;
}
}
catch (Exception ex)
{
Log.Error($"[WULA] 同步Hediff时出错: {ex}");
}
}
private void UnsyncPilotHediffs(Pawn pawn)
{
if (pawn == null || !syncedHediffs.ContainsKey(pawn))
return;
try
{
foreach (var hediff in syncedHediffs[pawn])
{
var syncComp = hediff.TryGetComp<HediffComp_SyncedWithMech>();
if (syncComp != null)
{
syncComp.OnPilotExitedMech();
}
}
syncedHediffs.Remove(pawn);
}
catch (Exception ex)
{
Log.Error($"[WULA] 取消同步Hediff时出错: {ex}");
}
}
private bool ShouldSyncHediff(Hediff hediff)
{
if (hediff == null)
return false;
var syncComp = hediff.TryGetComp<HediffComp_SyncedWithMech>();
if (syncComp == null)
return false;
if (Props.syncedHediffDefs != null && Props.syncedHediffDefs.Count > 0)
{
return Props.syncedHediffDefs.Contains(hediff.def.defName);
}
return true;
}
private void AddAutoHediff(Pawn pawn)
{
try
{
var existingHediff = pawn.health.hediffSet.GetFirstHediffOfDef(Props.autoHediffDef);
if (existingHediff == null)
{
var hediff = HediffMaker.MakeHediff(Props.autoHediffDef, pawn);
hediff.Severity = Props.autoHediffSeverity;
pawn.health.AddHediff(hediff);
}
}
catch (Exception ex)
{
Log.Error($"[WULA] 自动添加Hediff时出错: {ex}");
}
}
public override void CompTick()
{
base.CompTick();
try
{
if (Find.TickManager.TicksGame % 60 == 0)
{
CheckLowHealth();
CheckAndUpdateMentalState();
}
if (Find.TickManager.TicksGame % 120 == 0)
{
CheckHediffSync();
}
var mech = parent as Pawn;
if (mech != null && mech.Dead && HasPilots)
{
EjectAllPilotsOnDeath();
return;
}
var pilotsToRemove = new List<Pawn>();
foreach (var thing in innerContainer)
{
if (thing is Pawn pawn && pawn.Dead)
{
pilotsToRemove.Add(pawn);
}
}
foreach (var pawn in pilotsToRemove)
{
RemovePilot(pawn);
}
foreach (var thing in innerContainer)
{
if (thing is Pawn pawn)
{
pawn.jobs?.StopAll();
pawn.pather?.StopDead();
}
}
}
catch (Exception ex)
{
Log.Error($"[WULA] CompTick error: {ex}");
}
}
private void CheckHediffSync()
{
if (!Props.syncPilotHediffs || !(parent is Wulamechunit))
return;
try
{
foreach (var pilot in GetPilots())
{
if (pilot == null || pilot.Dead || pilot.Destroyed)
continue;
SyncPilotHediffs(pilot);
if (syncedHediffs.ContainsKey(pilot))
{
var currentHediffs = pilot.health.hediffSet.hediffs
.Where(ShouldSyncHediff)
.ToList();
var removedHediffs = syncedHediffs[pilot]
.Where(h => !currentHediffs.Contains(h))
.ToList();
foreach (var hediff in removedHediffs)
{
var syncComp = hediff.TryGetComp<HediffComp_SyncedWithMech>();
if (syncComp != null)
{
syncComp.OnPilotExitedMech();
}
}
syncedHediffs[pilot] = currentHediffs;
}
}
}
catch (Exception ex)
{
Log.Error($"[WULA] 检查Hediff同步状态时出错: {ex}");
}
}
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
if (!(parent is Wulamechunit))
{
Log.Warning($"[WULA] CompMechPilotHolder attached to non-mech: {parent}");
}
if (innerContainer == null)
{
innerContainer = new ThingOwner<Pawn>(this);
}
CheckAndUpdateMentalState();
if (Props.syncPilotHediffs)
{
foreach (var pilot in GetPilots())
{
SyncPilotHediffs(pilot);
}
}
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Deep.Look(ref innerContainer, "innerContainer", this);
Scribe_Values.Look(ref isProcessingDestruction, "isProcessingDestruction", false);
Scribe_Values.Look(ref hasEjectedDueToLowHealth, "hasEjectedDueToLowHealth", false);
Scribe_Collections.Look(ref syncedHediffs, "syncedHediffs", LookMode.Reference, LookMode.Deep);
Scribe_References.Look(ref lastPilot, "lastPilot");
if (Scribe.mode == LoadSaveMode.PostLoadInit)
{
CheckAndUpdateMentalState();
if (Props.syncPilotHediffs)
{
foreach (var pilot in GetPilots())
{
SyncPilotHediffs(pilot);
}
}
}
}
private void StopMechJobs()
{
var mech = parent as Pawn;
if (mech == null)
return;
mech.jobs?.StopAll();
mech.pather?.StopDead();
var drafter = mech.drafter;
if (drafter != null && mech.Drafted)
{
mech.drafter.Drafted = false;
}
mech.jobs?.ClearQueuedJobs();
mech.mindState.enemyTarget = null;
}
public float CurrentHealthPercent
{
get
{
var mech = parent as Pawn;
if (mech == null || mech.health == null)
return 1.0f;
return mech.health.summaryHealth.SummaryHealthPercent;
}
}
public bool IsBelowHealthThreshold => CurrentHealthPercent < Props.ejectPilotHealthPercentThreshold;
public bool CanAddPilot(Pawn pawn)
{
if (pawn == null || pawn.Dead)
return false;
if (pawn.Downed)
return true;
if (!HasRoom)
return false;
if (innerContainer.Contains(pawn))
return false;
if (!string.IsNullOrEmpty(Props.pilotWorkTag))
{
WorkTags tag;
if (System.Enum.TryParse(Props.pilotWorkTag, out tag))
{
if (pawn.WorkTagIsDisabled(tag))
return false;
}
}
if (!Props.allowEntryBelowThreshold && IsBelowHealthThreshold)
{
return false;
}
return true;
}
private bool CanPawnMoveToMech(Pawn pawn, Wulamechunit mech)
{
if (pawn == null || mech == null)
return false;
if (pawn.Downed)
return false;
return pawn.CanReach(mech, PathEndMode.Touch, Danger.Deadly);
}
private void CheckLowHealth()
{
if (IsBelowHealthThreshold && HasPilots)
{
EjectPilotsDueToLowHealth();
}
else if (!IsBelowHealthThreshold)
{
hasEjectedDueToLowHealth = false;
}
}
private void EjectPilotsDueToLowHealth()
{
if (hasEjectedDueToLowHealth)
return;
RemoveAllPilots();
if (parent.Faction == Faction.OfPlayer)
{
Messages.Message("WULA_PilotsEjectedDueToLowHealth".Translate(parent.LabelShort,
(Props.ejectPilotHealthPercentThreshold * 100).ToString("F0")),
parent, MessageTypeDefOf.NegativeEvent);
}
hasEjectedDueToLowHealth = true;
}
public override void PostPostApplyDamage(DamageInfo dinfo, float totalDamageDealt)
{
base.PostPostApplyDamage(dinfo, totalDamageDealt);
var mech = parent as Pawn;
if (mech != null && mech.Dead)
{
EjectAllPilotsOnDeath();
}
else
{
CheckLowHealth();
}
}
// 修改Gizmo显示只在条件满足时显示快速登机按钮
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
if (!(parent is Wulamechunit mech) || mech.Faction != Faction.OfPlayer)
yield break;
// 快速登机按钮(只有在所有条件都满足时才显示)
if (Props.enableQuickRecall && HasRoom && !IsBelowHealthThreshold)
{
var recallGizmo = CreateRecallGizmo();
if (recallGizmo != null)
{
yield return recallGizmo;
}
}
// 召唤驾驶员按钮
if (HasRoom)
{
Command_Action summonCommand = new Command_Action
{
defaultLabel = "WULA_SummonPilot".Translate(),
defaultDesc = "WULA_SummonPilotDesc".Translate(),
icon = Props.GetSummonPilotIcon(),
action = () =>
{
ShowPilotSelectionMenu();
},
hotKey = KeyBindingDefOf.Misc2
};
if (!Props.allowEntryBelowThreshold && IsBelowHealthThreshold)
{
summonCommand.Disable("WULA_MechTooDamagedForEntry".Translate());
}
yield return summonCommand;
}
// 弹出所有驾驶员按钮
if (innerContainer.Count > 0)
{
yield return new Command_Action
{
defaultLabel = "WULA_EjectAllPilots".Translate(),
defaultDesc = "WULA_EjectAllPilotsDesc".Translate(),
icon = Props.GetEjectPilotIcon(),
action = () =>
{
RemoveAllPilots();
},
hotKey = KeyBindingDefOf.Misc1
};
}
}
// 创建快速登机Gizmo只有条件完全满足时才创建
private Command_Action CreateRecallGizmo()
{
// 检查是否有上次驾驶员
if (lastPilot == null)
return null;
// 检查驾驶员是否可用
if (!IsPilotAvailableForRecall(lastPilot))
return null;
// 创建并返回Gizmo
return new Command_Action
{
defaultLabel = "WULA_RecallLastPilot".Translate(),
defaultDesc = "WULA_RecallLastPilotDesc".Translate(),
icon = Props.GetRecallPilotIcon(),
action = () =>
{
RecallLastPilot();
},
hotKey = KeyBindingDefOf.Misc3
};
}
// 检查驾驶员是否可用于快速登机
private bool IsPilotAvailableForRecall(Pawn pilot)
{
if (pilot == null || pilot.Dead || pilot.Destroyed)
return false;
if (innerContainer.Contains(pilot))
return false;
if (!CanAddPilot(pilot))
return false;
if (parent.Map == null || !pilot.Spawned || pilot.Map != parent.Map)
return false;
if (pilot.Position.DistanceTo(parent.Position) > Props.recallRadius)
return false;
if (!pilot.CanReach(parent, PathEndMode.Touch, Danger.Deadly))
return false;
if (pilot.IsPrisoner || pilot.IsSlave)
return false;
return true;
}
// 召回上次驾驶员
private void RecallLastPilot()
{
if (lastPilot == null || IsFull || !IsPilotAvailableForRecall(lastPilot))
{
return;
}
Job job = JobMaker.MakeJob(Wula_JobDefOf.WULA_EnterMech, parent);
lastPilot.jobs.TryTakeOrderedJob(job, JobTag.Misc);
}
public CompMechPilotHolder()
{
innerContainer = new ThingOwner<Pawn>(this);
}
public void RemoveAllPilots(IntVec3? exitPos = null)
{
bool hadPilots = HasPilots;
var pilotsToRemove = innerContainer.ToList();
foreach (var thing in pilotsToRemove)
{
if (thing is Pawn pawn)
{
UnsyncPilotHediffs(pawn);
}
}
foreach (var thing in pilotsToRemove)
{
if (thing is Pawn pawn)
{
RemovePilot(pawn, exitPos);
}
}
if (hadPilots && parent is Pawn mech)
{
StopMechJobs();
}
}
public void EjectAllPilotsOnDeath()
{
if (isProcessingDestruction)
return;
try
{
isProcessingDestruction = true;
if (!HasPilots)
{
return;
}
var pilots = innerContainer.ToList();
foreach (var thing in pilots)
{
if (thing is Pawn pawn)
{
UnsyncPilotHediffs(pawn);
}
}
IntVec3 ejectPos = FindSafeEjectPosition();
foreach (var thing in pilots)
{
if (thing is Pawn pawn)
{
innerContainer.Remove(pawn);
TrySpawnPilotAtPosition(pawn, ejectPos);
}
}
}
catch (Exception ex)
{
Log.Error($"[WULA] 弹出驾驶员时发生错误: {ex}");
}
finally
{
isProcessingDestruction = false;
}
}
private IntVec3 FindSafeEjectPosition()
{
Map map = parent.Map;
if (map == null)
return parent.Position;
IntVec3 pos = parent.Position;
if (!pos.Walkable(map) || pos.Fogged(map))
{
for (int i = 1; i <= 5; i++)
{
foreach (IntVec3 cell in GenRadial.RadialCellsAround(pos, i, true))
{
if (cell.Walkable(map) && !cell.Fogged(map))
{
return cell;
}
}
}
}
if (!pos.Walkable(map) || pos.Fogged(map))
{
CellFinder.TryFindRandomCellNear(pos, map, 10,
cell => cell.Walkable(map) && !cell.Fogged(map),
out pos, 100);
}
return pos;
}
private bool TrySpawnPilotAtPosition(Pawn pawn, IntVec3 position)
{
Map map = parent.Map;
if (map == null)
{
Log.Error($"[WULA] 尝试在没有地图的情况下生成驾驶员: {pawn.LabelShort}");
return false;
}
try
{
if (GenGrid.InBounds(position, map) && position.Walkable(map) && !position.Fogged(map))
{
GenSpawn.Spawn(pawn, position, map, WipeMode.Vanish);
return true;
}
IntVec3 spawnPos;
if (RCellFinder.TryFindRandomCellNearWith(position,
cell => cell.Walkable(map) && !cell.Fogged(map),
map, out spawnPos, 1, 10))
{
GenSpawn.Spawn(pawn, spawnPos, map, WipeMode.Vanish);
return true;
}
CellFinder.TryFindRandomCellNear(position, map, 20,
cell => cell.Walkable(map) && !cell.Fogged(map),
out spawnPos);
GenSpawn.Spawn(pawn, spawnPos, map, WipeMode.Vanish);
return true;
}
catch (Exception ex)
{
Log.Error($"[WULA] 生成驾驶员时发生错误: {ex}");
return false;
}
}
public Pawn GetPrimaryPilot()
{
if (innerContainer.Count > 0)
{
foreach (var thing in innerContainer)
{
if (thing is Pawn pawn)
return pawn;
}
}
return null;
}
public IEnumerable<Pawn> GetPilots()
{
foreach (var thing in innerContainer)
{
if (thing is Pawn pawn)
yield return pawn;
}
}
public void Notify_PilotAdded(Pawn pilot)
{
if (pilot.Faction == Faction.OfPlayer)
{
Messages.Message("WULA_PilotEnteredMech".Translate(pilot.LabelShort, parent.LabelShort),
parent, MessageTypeDefOf.PositiveEvent);
}
}
public void Notify_PilotRemoved(Pawn pilot)
{
if (pilot.Faction == Faction.OfPlayer)
{
Messages.Message("WULA_PilotExitedMech".Translate(pilot.LabelShort, parent.LabelShort),
parent, MessageTypeDefOf.NeutralEvent);
}
}
public override void PostDestroy(DestroyMode mode, Map previousMap)
{
if (HasPilots)
{
EjectAllPilotsOnDeath();
}
base.PostDestroy(mode, previousMap);
}
public ThingOwner GetDirectlyHeldThings()
{
return innerContainer;
}
public void GetChildHolders(List<IThingHolder> outChildren)
{
ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings());
}
private void ShowPilotSelectionMenu()
{
if (!(parent is Wulamechunit mech))
return;
List<FloatMenuOption> options = new List<FloatMenuOption>();
var allColonists = mech.Map.mapPawns.FreeColonists
.Where(p => CanAddPilot(p))
.ToList();
var ableColonists = allColonists.Where(p => CanPawnMoveToMech(p, mech)).ToList();
var disabledColonists = allColonists.Where(p => !CanPawnMoveToMech(p, mech)).ToList();
if (ableColonists.Count == 0 && disabledColonists.Count == 0)
{
options.Add(new FloatMenuOption("WULA_NoAvailablePilots".Translate(), null));
}
else
{
foreach (var colonist in ableColonists)
{
string colonistLabel = colonist.LabelShortCap;
Action action = () => OrderColonistToEnterMech(colonist);
FloatMenuOption option = new FloatMenuOption(
colonistLabel,
action,
colonist,
Color.white,
MenuOptionPriority.Default
);
options.Add(option);
}
foreach (var colonist in disabledColonists)
{
string colonistLabel = colonist.LabelShortCap + " " + "WULA_DisabledColonistRequiresCarry".Translate();
Action action = () => OrderCarryDisabledColonistToMech(colonist);
FloatMenuOption option = new FloatMenuOption(
colonistLabel,
action,
colonist,
Color.yellow,
MenuOptionPriority.Default
);
options.Add(option);
}
}
Find.WindowStack.Add(new FloatMenu(options));
}
private void OrderColonistToEnterMech(Pawn colonist)
{
if (!(parent is Wulamechunit mech) || colonist == null)
return;
Job job = JobMaker.MakeJob(Wula_JobDefOf.WULA_EnterMech, mech);
colonist.jobs.TryTakeOrderedJob(job, JobTag.Misc);
}
private void OrderCarryDisabledColonistToMech(Pawn disabledColonist)
{
if (!(parent is Wulamechunit mech) || disabledColonist == null)
return;
Pawn carrier = FindClosestAvailableCarrier(disabledColonist, mech);
if (carrier == null)
{
Messages.Message("WULA_NoAvailableCarrier".Translate(disabledColonist.LabelShortCap),
parent, MessageTypeDefOf.RejectInput);
return;
}
Job job = JobMaker.MakeJob(Wula_JobDefOf.WULA_CarryToMech, disabledColonist, mech);
carrier.jobs.TryTakeOrderedJob(job, JobTag.Misc);
Messages.Message("WULA_CarrierAssigned".Translate(carrier.LabelShortCap, disabledColonist.LabelShortCap),
parent, MessageTypeDefOf.PositiveEvent);
}
private Pawn FindClosestAvailableCarrier(Pawn disabledColonist, Wulamechunit mech)
{
if (disabledColonist.Map == null)
return null;
var potentialCarriers = disabledColonist.Map.mapPawns.FreeColonists
.Where(p => p != disabledColonist && !p.Downed &&
p.CanReserveAndReach(disabledColonist, PathEndMode.OnCell, Danger.Deadly, 1, -1, null, false) &&
p.CanReserveAndReach(mech, PathEndMode.Touch, Danger.Deadly, 1, -1, null, false))
.ToList();
if (potentialCarriers.Count == 0)
return null;
return potentialCarriers
.OrderBy(p => p.Position.DistanceTo(disabledColonist.Position))
.FirstOrDefault();
}
}
}