This commit is contained in:
2026-02-25 17:30:59 +08:00
parent 0509f26c3c
commit fff40b0edb
70 changed files with 3951 additions and 1219 deletions

View File

@@ -0,0 +1,213 @@
// File: FloatMenuOptionProvider_EnterMech.cs
using RimWorld;
using System.Collections.Generic;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class FloatMenuOptionProvider_EnterMech : FloatMenuOptionProvider
{
// 检查Thing是否为机甲
private bool IsMech(Thing thing)
{
return thing is Wulamechunit || thing?.GetType()?.IsSubclassOf(typeof(Wulamechunit)) == true;
}
protected override bool Drafted => true; // 征召状态下不能进入机甲
protected override bool Undrafted => true; // 非征召状态下可以进入
protected override bool Multiselect => true; // 不支持多选
// 检查是否适用于当前上下文
protected override bool AppliesInt(FloatMenuContext context)
{
// 必须有选中的殖民者
if (context.FirstSelectedPawn == null)
return false;
// 检查点击的单元格中是否有机甲
var clickedThings = context.ClickedThings;
if (clickedThings == null || clickedThings.Count == 0)
return false;
// 查找第一个机甲
Thing mech = null;
foreach (var thing in clickedThings)
{
if (IsMech(thing))
{
mech = thing;
break;
}
}
if (mech == null)
return false;
// 检查机甲是否有驾驶员组件
var comp = mech.TryGetComp<CompMechPilotHolder>();
if (comp == null)
return false;
// 检查殖民者是否已经在机甲内
// 由于CompMechPilotHolder没有ContainsPilot方法我们需要通过其他方式检查
if (IsPawnInMech(context.FirstSelectedPawn, mech))
return false;
return true;
}
// 检查殖民者是否已经在机甲内替代ContainsPilot
private bool IsPawnInMech(Pawn pawn, Thing mech)
{
var comp = mech.TryGetComp<CompMechPilotHolder>();
if (comp == null)
return false;
// 尝试通过内部容器检查
var holder = comp as IThingHolder;
if (holder != null)
{
var things = holder.GetDirectlyHeldThings();
if (things != null && things.Contains(pawn))
return true;
}
// 或者尝试通过其他属性检查
// 这里假设CompMechPilotHolder有HasPilots属性
if (comp.HasPilots)
{
// 如果有必要,可以通过反射或其他方式检查具体驾驶员
// 暂时返回false假设不在机甲内
return false;
}
return false;
}
// 获取单个选项
protected override FloatMenuOption GetSingleOptionFor(Thing clickedThing, FloatMenuContext context)
{
if (clickedThing == null || context.FirstSelectedPawn == null)
return null;
// 如果不是机甲返回null
if (!IsMech(clickedThing))
return null;
// 获取机甲和组件
var mech = clickedThing as Wulamechunit;
var comp = mech?.TryGetComp<CompMechPilotHolder>();
if (mech == null || comp == null)
return null;
// 检查殖民者是否已经在机甲内
if (IsPawnInMech(context.FirstSelectedPawn, mech))
return null;
// 检查各种条件,生成相应的菜单选项
return CreateEnterMechOption(mech, context.FirstSelectedPawn, comp);
}
// 创建进入机甲的菜单选项
private FloatMenuOption CreateEnterMechOption(Wulamechunit mech, Pawn pawn, CompMechPilotHolder comp)
{
string label = "WULA_EnterMech".Translate(mech.LabelShort);
string disabledReason = "";
// 检查条件是否允许进入
bool canEnter = CanEnterMech(mech, pawn, comp, ref disabledReason);
// 如果条件允许,创建可点击的选项
if (canEnter)
{
return new FloatMenuOption(label, () =>
{
// 创建进入机甲的工作
Job job = JobMaker.MakeJob(Wula_JobDefOf.WULA_EnterMech, mech);
pawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
// 播放音效(如果有的话)
FleckMaker.Static(mech.DrawPos, mech.MapHeld, FleckDefOf.FeedbackEquip);
}, MenuOptionPriority.High);
}
else
{
// 创建禁用的选项,显示原因
return new FloatMenuOption(
"WULA_EnterMech".Translate(mech.LabelShort) + ": " + disabledReason,
null,
MenuOptionPriority.DisabledOption);
}
}
// 检查殖民者是否可以进入机甲
private bool CanEnterMech(Wulamechunit mech, Pawn pawn, CompMechPilotHolder comp, ref string disabledReason)
{
// 检查机甲是否已满
if (comp.IsFull)
{
disabledReason = "WULA_MechFull".Translate();
return false;
}
// 检查殖民者是否可以成为驾驶员
if (!comp.CanAddPilot(pawn))
{
disabledReason = "WULA_CannotBecomePilot".Translate();
return false;
}
// 检查距离
if (!pawn.CanReach(mech, PathEndMode.Touch, Danger.Deadly))
{
disabledReason = "NoPath".Translate();
return false;
}
// 检查殖民者状态
if (pawn.Downed)
{
disabledReason = "Downed".Translate();
return false;
}
if (pawn.Dead)
{
disabledReason = "Dead".Translate();
return false;
}
// 检查是否为囚犯
if (pawn.IsPrisoner)
{
disabledReason = "Prisoner".Translate();
return false;
}
// 检查是否为奴隶
if (pawn.IsSlave)
{
disabledReason = "Slave".Translate();
return false;
}
// 检查机甲状态
if (mech.Downed)
{
disabledReason = "Downed".Translate();
return false;
}
if (mech.Dead)
{
disabledReason = "Dead".Translate();
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,76 @@
// File: JobDriver_EnterMech.cs (不再保留机甲)
using RimWorld;
using System.Collections.Generic;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class JobDriver_EnterMech : JobDriver
{
private const TargetIndex MechIndex = TargetIndex.A;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
Pawn pawn = this.pawn;
LocalTargetInfo target = this.job.GetTarget(MechIndex);
// 不再保留机甲,这样多个殖民者可以同时被命令进入同一个机甲
// 只需要检查殖民者是否可以到达机甲
if (!pawn.CanReach(target, PathEndMode.Touch, Danger.Deadly))
{
return false;
}
return true;
}
protected override IEnumerable<Toil> MakeNewToils()
{
// 0. 初始检查
AddFailCondition(() =>
{
var mech = TargetThingA as Wulamechunit;
if (mech == null || mech.Destroyed)
{
return true;
}
var comp = mech.GetComp<CompMechPilotHolder>();
if (comp == null || comp.IsFull || !comp.CanAddPilot(pawn))
{
return true;
}
if (pawn.Downed || pawn.Dead)
return true;
return false;
});
// 1. 走到机甲旁边
yield return Toils_Goto.GotoThing(MechIndex, PathEndMode.Touch);
// 2. 检查是否仍然可以进入
yield return Toils_General.Wait(10).WithProgressBarToilDelay(MechIndex);
// 3. 进入机甲
Toil enterToil = new Toil();
enterToil.initAction = () =>
{
var mech = TargetThingA as Wulamechunit;
if (mech == null)
return;
var comp = mech.GetComp<CompMechPilotHolder>();
if (comp != null && comp.CanAddPilot(pawn))
{
comp.AddPilot(pawn);
Messages.Message("WULA_PilotEnteredMech".Translate(pawn.LabelShort, mech.LabelShort),
MessageTypeDefOf.PositiveEvent, false);
}
};
enterToil.defaultCompleteMode = ToilCompleteMode.Instant;
yield return enterToil;
}
}
}

View File

@@ -0,0 +1,188 @@
// File: WorkGiver_EnterMech.cs
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class WorkGiver_EnterMech : WorkGiver_Scanner
{
// 缓存机甲定义列表
private static List<ThingDef> cachedMechDefs = null;
public override PathEndMode PathEndMode => PathEndMode.Touch;
// 获取所有机甲定义的列表
private List<ThingDef> GetAllMechDefs()
{
if (cachedMechDefs == null)
{
cachedMechDefs = new List<ThingDef>();
// 搜索所有ThingDef找出继承自Wulamechunit的类
foreach (var def in DefDatabase<ThingDef>.AllDefs)
{
try
{
if (def.thingClass == typeof(Wulamechunit) ||
def.thingClass?.IsSubclassOf(typeof(Wulamechunit)) == true)
{
cachedMechDefs.Add(def);
}
}
catch (Exception ex)
{
// 忽略错误,继续搜索
Log.Warning($"[WULA] Error checking ThingDef {def.defName}: {ex.Message}");
}
}
}
return cachedMechDefs;
}
// 检查Thing是否为机甲
private bool IsMech(Thing thing)
{
return thing is Wulamechunit || thing?.GetType()?.IsSubclassOf(typeof(Wulamechunit)) == true;
}
public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
{
try
{
// 检查基本条件
if (t == null || pawn == null)
return false;
// 必须是Wulamechunit或其子类
if (!IsMech(t))
return false;
// 检查距离
if (!pawn.CanReach(t, PathEndMode, Danger.Deadly))
return false;
// 检查机甲是否有驾驶员槽位组件
var comp = t.TryGetComp<CompMechPilotHolder>();
if (comp == null)
return false;
// 检查是否已满
if (comp.IsFull)
return false;
// 检查殖民者是否可以成为驾驶员
if (!comp.CanAddPilot(pawn))
return false;
// 检查殖民者状态
if (pawn.Downed || pawn.Dead)
return false;
// 检查殖民者是否正在执行任务
if (pawn.CurJob != null && pawn.CurJob.def != JobDefOf.Wait)
return false;
// 检查是否被征召
if (pawn.Drafted)
return false;
// 检查是否为囚犯
if (pawn.IsPrisoner)
return false;
// 检查是否为奴隶
if (pawn.IsSlave)
return false;
return true;
}
catch (Exception ex)
{
Log.Error($"[WULA] Error in HasJobOnThing: {ex}");
return false;
}
}
public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
{
try
{
if (IsMech(t))
{
// 创建进入机甲的工作
return JobMaker.MakeJob(Wula_JobDefOf.WULA_EnterMech, t);
}
}
catch (Exception ex)
{
Log.Error($"[WULA] Error creating job: {ex}");
}
return null;
}
public override IEnumerable<Thing> PotentialWorkThingsGlobal(Pawn pawn)
{
try
{
// 只搜索玩家拥有的机甲
List<Thing> potentialMechs = new List<Thing>();
// 获取地图中的所有机甲
if (pawn.Map != null)
{
// 使用缓存的机甲定义列表
var mechDefs = GetAllMechDefs();
foreach (var def in mechDefs)
{
try
{
var allMechs = pawn.Map.listerThings.ThingsOfDef(def);
foreach (var mech in allMechs)
{
if (mech.Faction == Faction.OfPlayer &&
mech.TryGetComp<CompMechPilotHolder>() != null)
{
potentialMechs.Add(mech);
}
}
}
catch (Exception ex)
{
Log.Warning($"[WULA] Error getting mechs for def {def.defName}: {ex.Message}");
}
}
}
return potentialMechs;
}
catch (Exception ex)
{
Log.Error($"[WULA] Error in PotentialWorkThingsGlobal: {ex}");
return Enumerable.Empty<Thing>();
}
}
public override bool ShouldSkip(Pawn pawn, bool forced = false)
{
try
{
// 简化版本:只检查殖民者状态
if (pawn.Downed || pawn.Dead || pawn.Drafted || pawn.IsPrisoner || pawn.IsSlave)
return true;
return false;
}
catch (Exception ex)
{
Log.Error($"[WULA] Error in ShouldSkip: {ex}");
return true;
}
}
}
}

View File

@@ -0,0 +1,182 @@
// File: FloatMenuOptionProvider_ForceEjectPilot.cs
using RimWorld;
using System.Collections.Generic;
using System.Linq;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class FloatMenuOptionProvider_ForceEjectPilot : FloatMenuOptionProvider
{
// 征召状态下不能执行此工作
protected override bool Drafted => true;
// 非征召状态下可以执行
protected override bool Undrafted => true;
// 不支持多选
protected override bool Multiselect => false;
// 需要操纵能力
protected override bool RequiresManipulation => true;
// 检查Thing是否为机甲
private bool IsMech(Pawn thing)
{
return thing is Wulamechunit || thing?.GetType()?.IsSubclassOf(typeof(Wulamechunit)) == true;
}
// 检查是否适用于当前上下文
protected override bool AppliesInt(FloatMenuContext context)
{
// 必须有选中的殖民者
if (context.FirstSelectedPawn == null)
return false;
// 检查点击的单元格中是否有机甲
var ClickedPawns = context.ClickedPawns;
if (ClickedPawns == null || ClickedPawns.Count == 0)
return false;
// 查找第一个机甲
Pawn mech = null;
foreach (var thing in ClickedPawns)
{
if (IsMech(thing))
{
mech = thing;
break;
}
}
if (mech == null)
return false;
// 检查机甲是否有驾驶员组件
var comp = mech.TryGetComp<CompMechPilotHolder>();
if (comp == null)
return false;
// 检查机甲是否属于非玩家派系且Downed但未死亡并且有驾驶员
if (mech.Faction == Faction.OfPlayer || !mech.Downed || mech.Dead || !comp.HasPilots)
return false;
return true;
}
// 获取单个选项
protected override FloatMenuOption GetSingleOptionFor(Pawn clickedPawn, FloatMenuContext context)
{
if (clickedPawn == null || context.FirstSelectedPawn == null)
return null;
// 如果不是机甲返回null
if (!IsMech(clickedPawn))
return null;
// 获取机甲和组件
var mech = clickedPawn as Wulamechunit;
var comp = mech?.TryGetComp<CompMechPilotHolder>();
if (mech == null || comp == null)
return null;
// 检查机甲是否属于非玩家派系且Downed但未死亡并且有驾驶员
if (mech.Faction == Faction.OfPlayer || !mech.Downed || mech.Dead || !comp.HasPilots)
return null;
// 检查殖民者是否能够执行此工作
return CreateForceEjectOption(mech, context.FirstSelectedPawn, comp);
}
// 创建强制拉出驾驶员的菜单选项
private FloatMenuOption CreateForceEjectOption(Wulamechunit mech, Pawn pawn, CompMechPilotHolder comp)
{
string label = "WULA_ForceEjectPilot".Translate(mech.LabelShort);
string disabledReason = "";
// 检查条件是否允许执行强制拉出
bool canForceEject = CanForceEject(mech, pawn, comp, ref disabledReason);
if (canForceEject)
{
return new FloatMenuOption(label, () =>
{
// 创建强制拉出驾驶员的工作
Job job = JobMaker.MakeJob(Wula_JobDefOf.WULA_ForceEjectPilot, mech);
pawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
}, MenuOptionPriority.High);
}
else
{
// 创建禁用的选项,显示原因
return new FloatMenuOption(
"WULA_ForceEjectPilot".Translate(mech.LabelShort) + ": " + disabledReason,
null,
MenuOptionPriority.DisabledOption);
}
}
// 检查殖民者是否可以执行强制拉出
private bool CanForceEject(Wulamechunit mech, Pawn pawn, CompMechPilotHolder comp, ref string disabledReason)
{
// 检查殖民者是否能够到达机甲
if (!pawn.CanReach(mech, PathEndMode.Touch, Danger.Some))
{
disabledReason = "NoPath".Translate();
return false;
}
// 检查殖民者状态
if (pawn.Downed)
{
disabledReason = "Downed".Translate();
return false;
}
if (pawn.Dead)
{
disabledReason = "Dead".Translate();
return false;
}
// 检查是否为囚犯
if (pawn.IsPrisoner)
{
disabledReason = "Prisoner".Translate();
return false;
}
// 检查是否为奴隶
if (pawn.IsSlave)
{
disabledReason = "Slave".Translate();
return false;
}
// 检查机甲是否已经被玩家派系控制
if (mech.Faction == Faction.OfPlayer)
{
disabledReason = "WULA_AlreadyPlayerMech".Translate();
return false;
}
// 检查机甲是否Downed且未死亡
if (!mech.Downed || mech.Dead)
{
disabledReason = "WULA_MechNotDowned".Translate();
return false;
}
// 检查是否有驾驶员
if (!comp.HasPilots)
{
disabledReason = "WULA_NoPilot".Translate();
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,177 @@
// Jobs/JobDriver_ForceEjectPilot.cs
using RimWorld;
using System.Collections.Generic;
using Verse;
using Verse.AI;
using System.Linq;
namespace WulaFallenEmpire
{
public class JobDriver_ForceEjectPilot : JobDriver
{
private const int WorkDurationTicks = 600; // 10秒60帧/秒)
// 目标机甲
private Pawn TargetMech => job.targetA.Thing as Pawn;
// 工作进度属性
private float WorkProgress
{
get
{
if (TargetMech == null) return 0f;
return (float)ticksLeftThisToil / WorkDurationTicks;
}
}
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
// 为殖民者预留机甲的位置
return pawn.Reserve(TargetMech, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
// 目标验证
this.FailOnDespawnedNullOrForbidden(TargetIndex.A);
this.FailOn(() => !CanForceEject(TargetMech));
// Toil 1移动到机甲位置
yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.Touch)
.FailOnDespawnedOrNull(TargetIndex.A);
// Toil 2执行强制拉出工作
var workToil = new Toil();
workToil.initAction = () =>
{
pawn.rotationTracker.FaceCell(TargetMech.Position);
pawn.jobs.posture = PawnPosture.Standing;
};
workToil.tickAction = () =>
{
// 每帧工作进度
pawn.skills?.Learn(SkillDefOf.Melee, 0.1f);
// 显示工作条
if (pawn.IsColonistPlayerControlled)
{
TargetMech.Map.overlayDrawer.DrawOverlay(TargetMech, OverlayTypes.QuestionMark);
}
};
workToil.defaultCompleteMode = ToilCompleteMode.Delay;
workToil.WithEffect(EffecterDefOf.MechRepairing, TargetIndex.A);
workToil.defaultDuration = WorkDurationTicks;
workToil.WithProgressBar(TargetIndex.A, () => 1f - WorkProgress);
workToil.handlingFacing = true;
workToil.activeSkill = () => SkillDefOf.Construction;
yield return workToil;
// Toil 3完成工作
yield return new Toil
{
initAction = () =>
{
CompleteForceEject();
},
defaultCompleteMode = ToilCompleteMode.Instant
};
}
// 检查是否可以强制拉出
private bool CanForceEject(Pawn mech)
{
if (mech == null || mech.Dead)
return false;
// 必须是机甲
if (!(mech is Wulamechunit))
return false;
// 必须是玩家派系的目标
if (mech.Faction == Faction.OfPlayer)
return false;
// 必须失去行动能力但未死亡
if (!mech.Downed)
return false;
// 必须有驾驶员
var pilotComp = mech.TryGetComp<CompMechPilotHolder>();
if (pilotComp == null || !pilotComp.HasPilots)
return false;
// 殖民者必须能够接触机甲
if (!pawn.CanReach(mech, PathEndMode.Touch, Danger.Some))
return false;
// 殖民者不能是囚犯或已失去行动能力
if (pawn.Downed || pawn.Dead || pawn.IsPrisoner)
return false;
return true;
}
// 完成强制拉出
private void CompleteForceEject()
{
var mech = TargetMech;
if (mech == null) return;
var pilotComp = mech.TryGetComp<CompMechPilotHolder>();
if (pilotComp == null) return;
try
{
// 1. 弹出所有驾驶员
var ejectedPilots = pilotComp.GetPilots().ToList();
pilotComp.RemoveAllPilots();
// 2. 转换派系为玩家
mech.SetFaction(Faction.OfPlayer);
// 4. 发送消息
SendCompletionMessages(mech, ejectedPilots);
}
catch (System.Exception ex)
{
Log.Error($"[WULA] Error in ForceEjectPilot: {ex}");
}
}
// 发送完成消息
private void SendCompletionMessages(Pawn mech, List<Pawn> ejectedPilots)
{
string message;
if (ejectedPilots.Count > 0)
{
message = "WULA_ForceEjectComplete_WithPilots".Translate(
pawn.LabelShortCap,
mech.LabelShortCap,
ejectedPilots.Count
);
}
else
{
message = "WULA_ForceEjectComplete".Translate(
pawn.LabelShortCap,
mech.LabelShortCap
);
}
Messages.Message(message, MessageTypeDefOf.PositiveEvent);
// 如果弹出的是敌对派系驾驶员,添加额外消息
foreach (var pilot in ejectedPilots)
{
if (pilot.Faction != null && pilot.Faction.HostileTo(Faction.OfPlayer))
{
Messages.Message("WULA_HostilePilotEjected".Translate(
pilot.LabelShortCap,
pilot.Faction.Name
), MessageTypeDefOf.NeutralEvent);
}
}
}
}
}

View File

@@ -0,0 +1,112 @@
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class JobDriver_InspectBuilding : JobDriver
{
private const TargetIndex BuildingIndex = TargetIndex.A;
private int ticksLeft;
// 定义考察效果 - 可以使用现有的效果或创建自定义效果
private static readonly EffecterDef InspectEffect = EffecterDefOf.Research; // 使用研究效果作为临时替代
private static readonly SoundDef InspectSound = SoundDefOf.Interact_CleanFilth; // 使用建造声音作为临时替代
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
// 预订目标建筑
return pawn.Reserve(job.targetA, job, 1, 1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
// 失败条件
this.FailOnDestroyedOrNull(BuildingIndex);
this.FailOnDespawnedOrNull(BuildingIndex);
this.FailOn(() => !pawn.health.capacities.CapableOf(PawnCapacityDefOf.Moving));
// 第一步:前往目标建筑
yield return Toils_Goto.GotoCell(BuildingIndex, PathEndMode.Touch)
.FailOnSomeonePhysicallyInteracting(BuildingIndex);
// 第二步:进行考察(带动画效果)
Toil inspectToil = CreateInspectionToil();
yield return inspectToil;
}
/// <summary>
/// 创建带动画效果的考察工作
/// </summary>
private Toil CreateInspectionToil()
{
Toil inspectToil = new Toil();
inspectToil.initAction = () =>
{
ticksLeft = job.expiryInterval;
// 记录日志
if (Prefs.DevMode)
{
WulaLog.Debug($"[JobDriver_InspectBuilding] {pawn.Name} started inspecting {TargetThingA.Label} for {ticksLeft} ticks");
}
};
inspectToil.tickAction = () =>
{
ticksLeft--;
// 每 tick 检查是否完成
if (ticksLeft <= 0)
{
ReadyForNextToil();
}
};
inspectToil.defaultCompleteMode = ToilCompleteMode.Delay;
inspectToil.defaultDuration = job.expiryInterval;
// 添加动画效果
inspectToil.WithEffect(InspectEffect, BuildingIndex);
// 添加音效
inspectToil.PlaySustainerOrSound(() => InspectSound);
// 添加进度条(可选)
inspectToil.WithProgressBar(BuildingIndex,
() => 1f - ((float)ticksLeft / job.expiryInterval),
interpolateBetweenActorAndTarget: true);
inspectToil.AddFinishAction(() =>
{
// 考察完成后的处理
OnInspectionComplete();
});
return inspectToil;
}
/// <summary>
/// 考察完成时的处理
/// </summary>
private void OnInspectionComplete()
{
// 可以在这里添加考察完成后的效果
// 例如:增加技能经验、触发事件等
if (Prefs.DevMode)
{
WulaLog.Debug($"[JobDriver_InspectBuilding] {pawn.Name} completed inspection of {TargetThingA.Label}");
}
}
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref ticksLeft, "ticksLeft", 0);
}
}
}

View File

@@ -0,0 +1,307 @@
using RimWorld;
using RimWorld.Planet;
using System.Collections.Generic;
using System.Linq;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class JobGiver_InspectBuilding : ThinkNode_JobGiver
{
// 检查间隔ticks
private const int CheckInterval = 120; // 2秒检查一次
// 最大考察距离
private const float MaxDistance = 20f;
// 默认最小间隔ticks- 5分钟
private const int DefaultMinIntervalTicks = 300 * 60; // 5分钟 * 60秒/分钟 * 60ticks/秒
// 存储每个 Pawn 的最后考察时间
private static Dictionary<Pawn, int> lastInspectionTicks = new Dictionary<Pawn, int>();
protected override Job TryGiveJob(Pawn pawn)
{
// 检查 Pawn 是否有效
if (pawn == null || pawn.Destroyed || !pawn.Spawned || pawn.Map == null)
return null;
// 检查 Pawn 是否能够工作
if (pawn.Downed || pawn.InMentalState || !pawn.health.capacities.CanBeAwake)
return null;
// 检查 Pawn 是否能够移动
if (!pawn.health.capacities.CapableOf(PawnCapacityDefOf.Moving))
return null;
// 检查背景故事是否为军团背景
if (!HasLegionBackstory(pawn))
return null;
// 检查是否已经有工作
if (pawn.CurJob != null && pawn.CurJob.def == Wula_JobDefOf.WULA_InspectBuilding)
return null;
// 检查冷却时间
if (!CanInspectNow(pawn))
return null;
// 寻找合适的考察目标
Thing inspectionTarget = FindRandomInspectionTarget(pawn);
if (inspectionTarget == null)
return null;
// 创建考察工作
Job job = JobMaker.MakeJob(Wula_JobDefOf.WULA_InspectBuilding, inspectionTarget);
job.expiryInterval = Rand.Range(300, 600); // 5-10秒的随机时间
job.checkOverrideOnExpire = true;
// 记录开始考察时间
RecordInspectionStart(pawn);
// 记录调试信息
if (Prefs.DevMode)
{
WulaLog.Debug($"[JobGiver_InspectBuilding] Assigned inspection job to {pawn.Name} at {inspectionTarget.Label}");
}
return job;
}
/// <summary>
/// 检查 Pawn 是否具有军团背景故事
/// </summary>
private bool HasLegionBackstory(Pawn pawn)
{
if (pawn.story == null)
return false;
// 检查成年背景故事是否为军团背景
if (pawn.story.Adulthood != null && pawn.story.Adulthood.identifier == "WULA_Adult_Backstory_Legion")
return true;
return false;
}
/// <summary>
/// 检查 Pawn 是否可以开始新的考察(冷却时间检查)
/// </summary>
private bool CanInspectNow(Pawn pawn)
{
// 获取设置的最小间隔时间
int minIntervalTicks = GetMinInspectionIntervalTicks();
// 如果 Pawn 没有记录,说明可以立即开始
if (!lastInspectionTicks.ContainsKey(pawn))
return true;
int lastTick = lastInspectionTicks[pawn];
int currentTick = Find.TickManager.TicksGame;
int elapsedTicks = currentTick - lastTick;
// 检查是否已经过了最小间隔时间
bool canInspect = elapsedTicks >= minIntervalTicks;
if (Prefs.DevMode && !canInspect)
{
int remainingTicks = minIntervalTicks - elapsedTicks;
float remainingSeconds = remainingTicks / 60f;
WulaLog.Debug($"[JobGiver_InspectBuilding] {pawn.Name} must wait {remainingSeconds:F1} seconds before next inspection");
}
return canInspect;
}
/// <summary>
/// 记录 Pawn 开始考察的时间
/// </summary>
private void RecordInspectionStart(Pawn pawn)
{
lastInspectionTicks[pawn] = Find.TickManager.TicksGame;
if (Prefs.DevMode)
{
WulaLog.Debug($"[JobGiver_InspectBuilding] Recorded inspection start for {pawn.Name} at tick {lastInspectionTicks[pawn]}");
}
}
/// <summary>
/// 获取最小考察间隔时间ticks
/// </summary>
private int GetMinInspectionIntervalTicks()
{
// 这里可以从 Mod 设置中获取值
// 暂时返回默认值,您可以根据需要修改
return DefaultMinIntervalTicks;
}
/// <summary>
/// 随机寻找合适的考察目标
/// </summary>
private Thing FindRandomInspectionTarget(Pawn pawn)
{
// 获取地图上所有符合条件的建筑
List<Thing> validBuildings = new List<Thing>();
// 遍历地图上的所有建筑
foreach (Thing thing in pawn.Map.listerThings.ThingsInGroup(ThingRequestGroup.BuildingArtificial))
{
if (IsValidInspectionTarget(thing, pawn))
{
validBuildings.Add(thing);
}
}
// 如果没有找到合适的建筑返回null
if (validBuildings.Count == 0)
{
if (Prefs.DevMode)
{
WulaLog.Debug($"[JobGiver_InspectBuilding] No valid inspection targets found for {pawn.Name}");
}
return null;
}
// 随机选择一个建筑
Thing selectedBuilding = validBuildings.RandomElement();
if (Prefs.DevMode)
{
WulaLog.Debug($"[JobGiver_InspectBuilding] Randomly selected {selectedBuilding.Label} from {validBuildings.Count} valid targets");
}
return selectedBuilding;
}
/// <summary>
/// 检查是否有效的考察目标
/// </summary>
private bool IsValidInspectionTarget(Thing thing, Pawn pawn)
{
// 基本检查
if (thing == null || thing.Destroyed)
return false;
// 检查是否玩家拥有
if (thing.Faction != Faction.OfPlayer)
return false;
// 检查是否可到达
if (!pawn.CanReach(thing, PathEndMode.Touch, Danger.None))
return false;
// 排除一些不适合的建筑类型
if (thing.def.IsFrame || thing.def.IsBlueprint)
return false;
// 确保建筑是完整的
if (thing is Building building && (building.IsBurning() || building.IsBrokenDown()))
return false;
// 确保不是禁止进入的区域
if (thing.IsForbidden(pawn))
return false;
// 确保没有其他 Pawn 正在考察这个建筑
if (IsBeingInspectedByOther(thing, pawn))
return false;
// 排除墙壁建筑
if (IsWall(thing))
return false;
// 距离检查(可选,但为了性能考虑可以保留)
if (pawn.Position.DistanceTo(thing.Position) > MaxDistance)
return false;
return true;
}
/// <summary>
/// 检查是否为墙壁
/// </summary>
private bool IsWall(Thing thing)
{
// 检查建筑的 def 中是否有 isWall 标签
if (thing.def?.building != null && thing.def.building.isWall)
{
if (Prefs.DevMode)
{
WulaLog.Debug($"[JobGiver_InspectBuilding] Excluding wall: {thing.Label}");
}
return true;
}
// 额外的检查:通过 defName 或标签判断
if (thing.def?.defName == "Wall" ||
(thing.def?.thingCategories?.Any(c => c.defName == "Walls") ?? false))
{
return true;
}
return false;
}
/// <summary>
/// 检查是否有其他 Pawn 正在考察这个建筑
/// </summary>
private bool IsBeingInspectedByOther(Thing thing, Pawn currentPawn)
{
foreach (Pawn otherPawn in thing.Map.mapPawns.AllPawnsSpawned)
{
if (otherPawn != currentPawn &&
otherPawn.CurJob != null &&
otherPawn.CurJob.def == Wula_JobDefOf.WULA_InspectBuilding &&
otherPawn.CurJob.targetA.Thing == thing)
{
return true;
}
}
return false;
}
/// <summary>
/// 清理不再存在的 Pawn 的记录
/// </summary>
public static void CleanupInspectionRecords()
{
List<Pawn> toRemove = new List<Pawn>();
foreach (var pair in lastInspectionTicks)
{
if (pair.Key.Destroyed || !pair.Key.Spawned)
{
toRemove.Add(pair.Key);
}
}
foreach (Pawn pawn in toRemove)
{
lastInspectionTicks.Remove(pawn);
}
if (Prefs.DevMode && toRemove.Count > 0)
{
WulaLog.Debug($"[JobGiver_InspectBuilding] Cleaned up {toRemove.Count} inspection records");
}
}
public class InspectionCleanupComponent : WorldComponent
{
private int lastCleanupTick = 0;
private const int CleanupIntervalTicks = 6000; // 每100秒清理一次
public InspectionCleanupComponent(World world) : base(world) { }
public override void WorldComponentTick()
{
base.WorldComponentTick();
int currentTick = Find.TickManager.TicksGame;
if (currentTick - lastCleanupTick > CleanupIntervalTicks)
{
JobGiver_InspectBuilding.CleanupInspectionRecords();
lastCleanupTick = currentTick;
}
}
}
}
}

View File

@@ -0,0 +1,82 @@
// File: JobDriver_CarryToMech.cs (修复count问题)
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class JobDriver_CarryToMech : JobDriver
{
private const TargetIndex TakeeIndex = TargetIndex.A;
private const TargetIndex MechIndex = TargetIndex.B;
protected Pawn Takee => (Pawn)job.GetTarget(TakeeIndex).Thing;
protected Wulamechunit Mech => (Wulamechunit)job.GetTarget(MechIndex).Thing;
protected CompMechPilotHolder MechComp => Mech?.TryGetComp<CompMechPilotHolder>();
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
// 确保job.count是有效的至少为1
if (job.count <= 0)
{
job.count = 1;
}
// 保留目标和机甲明确指定数量为1
return pawn.Reserve(Takee, job, 1, -1, null, errorOnFailed)
&& pawn.Reserve(Mech, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
// 确保job.count是有效的
if (job.count <= 0)
{
job.count = 1;
}
// 标准失败条件
this.FailOnDestroyedOrNull(TakeeIndex);
this.FailOnDestroyedOrNull(MechIndex);
this.FailOn(() => MechComp == null);
this.FailOn(() => !Takee.Downed); // 确保被搬运者是Downed状态
// 1. 前往要被搬运的殖民者
yield return Toils_Goto.GotoThing(TakeeIndex, PathEndMode.ClosestTouch)
.FailOnDespawnedNullOrForbidden(TakeeIndex)
.FailOnDespawnedNullOrForbidden(MechIndex)
.FailOnSomeonePhysicallyInteracting(TakeeIndex);
// 2. 开始搬运殖民者 - 使用原版的StartCarryThing方法
yield return Toils_Haul.StartCarryThing(TakeeIndex, false, true, false);
// 3. 携带殖民者前往机甲
yield return Toils_Goto.GotoThing(MechIndex, PathEndMode.Touch);
// 4. 将殖民者放入机甲
yield return new Toil
{
initAction = () =>
{
if (MechComp != null && Takee != null && MechComp.CanAddPilot(Takee))
{
// 放下殖民者
if (pawn.carryTracker.CarriedThing == Takee)
{
pawn.carryTracker.TryDropCarriedThing(pawn.Position, ThingPlaceMode.Near, out Thing droppedThing);
}
// 将殖民者添加到机甲
MechComp.AddPilot(Takee);
}
else
{
Log.Warning($"[WULA] 无法将殖民者添加到机甲");
}
},
defaultCompleteMode = ToilCompleteMode.Instant
};
}
}
}

View File

@@ -0,0 +1,18 @@
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class JobGiver_NoPilot : ThinkNode_JobGiver
{
private const int WaitTime = 100;
protected override Job TryGiveJob(Pawn pawn)
{
Job job = JobMaker.MakeJob(JobDefOf.Wait);
job.expiryInterval = 100;
return job;
}
}
}

View File

@@ -0,0 +1,176 @@
// JobDriver_RefuelMech.cs
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using Verse;
using Verse.AI;
using UnityEngine;
namespace WulaFallenEmpire
{
public class JobDriver_RefuelMech : JobDriver
{
private const TargetIndex MechInd = TargetIndex.A;
private const TargetIndex FuelInd = TargetIndex.B;
private const int RefuelingDuration = 240; // 基础加注时间
protected Pawn Mech => job.GetTarget(MechInd).Thing as Pawn;
protected CompMechFuel FuelComp => Mech?.TryGetComp<CompMechFuel>();
protected Thing Fuel => job.GetTarget(FuelInd).Thing;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
Pawn pawn = this.pawn;
Job job = this.job;
LocalTargetInfo target = job.GetTarget(MechInd);
if (!pawn.Reserve(target, job, 1, -1, null, errorOnFailed))
{
return false;
}
if (!pawn.Reserve(job.GetTarget(FuelInd), job, 1, -1, null, errorOnFailed))
{
return false;
}
return true;
}
protected override IEnumerable<Toil> MakeNewToils()
{
// 检查目标是否有效
this.FailOnDespawnedNullOrForbidden(MechInd);
this.FailOn(() => FuelComp == null);
// 添加结束条件:燃料已满
AddEndCondition(() => {
if (FuelComp.IsFull)
return JobCondition.Succeeded;
return JobCondition.Ongoing;
});
// 如果不是玩家强制命令,检查是否应该自动加注
AddFailCondition(() => {
if (job.playerForced)
return false;
// 获取驾驶员组件
var pilotComp = Mech.TryGetComp<CompMechPilotHolder>();
bool hasPilot = pilotComp != null && pilotComp.HasPilots;
// 如果有驾驶员且不是玩家强制命令,不自动加注
if (hasPilot && !job.playerForced)
return true;
// 检查燃料组件是否允许自动加注
if (!FuelComp.Props.allowAutoRefuel && !job.playerForced)
return true;
return false;
});
// 第一步:计算需要多少燃料
yield return Toils_General.DoAtomic(delegate
{
if (FuelComp != null)
{
job.count = FuelComp.GetFuelCountToFullyRefuel();
}
});
// 第二步:预留燃料
Toil reserveFuel = Toils_Reserve.Reserve(FuelInd);
yield return reserveFuel;
// 第三步:前往燃料位置
yield return Toils_Goto.GotoThing(FuelInd, PathEndMode.ClosestTouch)
.FailOnDespawnedNullOrForbidden(FuelInd)
.FailOnSomeonePhysicallyInteracting(FuelInd);
// 第四步:拿起燃料
yield return Toils_Haul.StartCarryThing(FuelInd, putRemainderInQueue: false, subtractNumTakenFromJobCount: true)
.FailOnDestroyedNullOrForbidden(FuelInd);
// 第五步:检查是否有机会拿更多燃料
yield return Toils_Haul.CheckForGetOpportunityDuplicate(reserveFuel, FuelInd, TargetIndex.None, takeFromValidStorage: true);
// 第六步:前往机甲位置
yield return Toils_Goto.GotoThing(MechInd, PathEndMode.Touch);
// 第七步:等待加注(有进度条)
Toil refuelToil = Toils_General.Wait(RefuelingDuration)
.FailOnDestroyedNullOrForbidden(FuelInd)
.FailOnDestroyedNullOrForbidden(MechInd)
.FailOnCannotTouch(MechInd, PathEndMode.Touch)
.WithProgressBarToilDelay(MechInd);
// 调整加注时间基于燃料组件的速度因子
refuelToil.defaultDuration = Mathf.RoundToInt(RefuelingDuration / FuelComp.Props.refuelSpeedFactor);
yield return refuelToil;
// 第八步:完成加注 - 模仿 RimWorld 原版实现
yield return FinalizeRefueling(MechInd, FuelInd);
}
// 模仿 RimWorld.Toils_Refuel.FinalizeRefueling 的实现
private static Toil FinalizeRefueling(TargetIndex refuelableInd, TargetIndex fuelInd)
{
Toil toil = ToilMaker.MakeToil("FinalizeRefueling");
toil.initAction = delegate
{
Pawn actor = toil.actor;
Job curJob = actor.CurJob;
Thing refuelable = curJob.GetTarget(refuelableInd).Thing;
CompMechFuel fuelComp = refuelable.TryGetComp<CompMechFuel>();
if (fuelComp != null)
{
// 获取所有燃料物品
List<Thing> fuelThings;
if (actor.CurJob.placedThings.NullOrEmpty())
{
// 如果没有 placedThings则使用燃料目标
Thing fuel = curJob.GetTarget(fuelInd).Thing;
if (fuel != null)
{
fuelThings = new List<Thing> { fuel };
}
else
{
fuelThings = null;
}
}
else
{
// 使用 placedThings 中的所有燃料物品
fuelThings = actor.CurJob.placedThings.Select((ThingCountClass p) => p.thing).ToList();
}
if (fuelThings != null)
{
// 计算总燃料量并销毁燃料物品
float totalFuel = 0f;
foreach (Thing fuelThing in fuelThings)
{
if (fuelThing != null && fuelThing.def == fuelComp.FuelType)
{
totalFuel += fuelThing.stackCount;
fuelThing.Destroy(DestroyMode.Vanish);
}
}
// 添加燃料到机甲
if (totalFuel > 0)
{
fuelComp.Refuel(totalFuel);
}
}
}
};
toil.defaultCompleteMode = ToilCompleteMode.Instant;
return toil;
}
}
}

View File

@@ -0,0 +1,123 @@
// WorkGiver_RefuelMech.cs
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class WorkGiver_RefuelMech : WorkGiver_Scanner
{
public override PathEndMode PathEndMode => PathEndMode.Touch;
public override IEnumerable<Thing> PotentialWorkThingsGlobal(Pawn pawn)
{
// 返回所有需要燃料的机甲
// 修复:使用 LINQ 的 Where 方法而不是 FindAll
var mechs = pawn.Map.mapPawns.AllPawnsSpawned.Where(p =>
p.TryGetComp<CompMechFuel>() != null);
foreach (Pawn mech in mechs)
{
yield return mech;
}
}
public override bool ShouldSkip(Pawn pawn, bool forced = false)
{
// 如果没有需要燃料的机甲,跳过
return !PotentialWorkThingsGlobal(pawn).Any();
}
public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
{
if (!(t is Pawn mech))
return false;
var fuelComp = mech.TryGetComp<CompMechFuel>();
if (fuelComp == null)
return false;
// 检查机甲是否已加满燃料
if (fuelComp.IsFull)
return false;
// 检查是否有可用的燃料
if (FindFuel(pawn, fuelComp) == null)
return false;
// 检查是否能接触到机甲
if (!pawn.CanReserveAndReach(t, PathEndMode.Touch, Danger.Some))
return false;
// 检查机甲状态
var pilotComp = mech.TryGetComp<CompMechPilotHolder>();
bool hasPilot = pilotComp != null && pilotComp.HasPilots;
// 如果有驾驶员且不是强制命令,不自动加注
if (hasPilot && !forced)
return false;
// 检查燃料组件是否允许自动加注
if (!fuelComp.Props.allowAutoRefuel && !forced)
return false;
// 检查是否达到自动加注阈值
if (!forced && !fuelComp.NeedsRefueling)
return false;
return true;
}
public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
{
var fuelComp = t.TryGetComp<CompMechFuel>();
if (fuelComp == null)
return null;
// 寻找燃料
Thing fuel = FindFuel(pawn, fuelComp);
if (fuel == null)
return null;
// 创建加注工作
Job job = JobMaker.MakeJob(Wula_JobDefOf.WULA_RefuelMech, t, fuel);
job.count = fuelComp.GetFuelCountToFullyRefuel();
return job;
}
// 修改方法:返回 Thing 而不是 bool
private Thing FindFuel(Pawn pawn, CompMechFuel fuelComp)
{
if (fuelComp.FuelType == null)
return null;
// 在库存中寻找燃料
Thing fuel = FindFuelInInventory(pawn, fuelComp.FuelType);
if (fuel != null)
return fuel;
// 在地图上寻找燃料
fuel = GenClosest.ClosestThingReachable(
pawn.Position,
pawn.Map,
ThingRequest.ForDef(fuelComp.FuelType),
PathEndMode.ClosestTouch,
TraverseParms.For(pawn),
9999f,
validator: thing => !thing.IsForbidden(pawn) && pawn.CanReserve(thing)
);
return fuel;
}
private Thing FindFuelInInventory(Pawn pawn, ThingDef fuelType)
{
if (pawn.inventory == null)
return null;
return pawn.inventory.innerContainer.FirstOrDefault(t => t.def == fuelType);
}
}
}

View File

@@ -0,0 +1,416 @@
// JobDriver_RepairMech.cs
using RimWorld;
using System.Collections.Generic;
using UnityEngine;
using Verse;
using Verse.AI;
using System.Linq;
namespace WulaFallenEmpire
{
public class JobDriver_RepairMech : JobDriver
{
private const TargetIndex MechInd = TargetIndex.A;
protected int ticksToNextRepair;
protected Pawn Mech => (Pawn)job.GetTarget(TargetIndex.A).Thing;
protected virtual bool Remote => false;
protected CompMechRepairable RepairableComp => Mech?.TryGetComp<CompMechRepairable>();
// 使用配置的修复周期ticks数并根据MechRepairSpeed调整
protected int TicksPerRepairCycle
{
get
{
if (RepairableComp == null)
return 120;
int baseTicks = RepairableComp.Props.ticksPerRepairCycle;
return Mathf.RoundToInt(baseTicks / pawn.GetStatValue(StatDefOf.MechRepairSpeed));
}
}
// 每次修复的HP量
protected float RepairAmountPerCycle
{
get
{
if (RepairableComp == null)
return 1f;
return RepairableComp.Props.repairAmountPerCycle;
}
}
// 新增:缺失部位修复配置
protected float MissingPartRepairCostMultiplier => 2f;
protected HediffDef MissingPartReplacementInjury => HediffDefOf.Misc;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
return pawn.Reserve(Mech, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
this.FailOnDestroyedOrNull(TargetIndex.A);
this.FailOnForbidden(TargetIndex.A);
this.FailOn(() => !MechRepairable() || !MechNeedsRepair());
if (!Remote)
{
yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.Touch);
}
Toil repairToil = (Remote ? Toils_General.Wait(int.MaxValue) : Toils_General.WaitWith(TargetIndex.A, int.MaxValue, useProgressBar: true, maintainPosture: true, maintainSleep: true));
// 添加维修特效
if (RepairableComp?.Props.repairEffect != null)
{
repairToil.WithEffect(RepairableComp.Props.repairEffect, TargetIndex.A);
}
else
{
repairToil.WithEffect(EffecterDefOf.MechRepairing, TargetIndex.A);
}
// 添加维修音效
if (RepairableComp?.Props.repairSound != null)
{
repairToil.PlaySustainerOrSound(RepairableComp.Props.repairSound);
}
else
{
repairToil.PlaySustainerOrSound(Remote ? SoundDefOf.RepairMech_Remote : SoundDefOf.RepairMech_Touch);
}
repairToil.AddPreInitAction(delegate
{
ticksToNextRepair = TicksPerRepairCycle;
});
repairToil.handlingFacing = true;
repairToil.tickIntervalAction = delegate(int delta)
{
ticksToNextRepair -= delta;
if (ticksToNextRepair <= 0)
{
RepairTick(delta);
ticksToNextRepair = TicksPerRepairCycle;
}
pawn.rotationTracker.FaceTarget(Mech);
if (pawn.skills != null)
{
pawn.skills.Learn(SkillDefOf.Crafting, 0.05f * (float)delta);
}
};
repairToil.AddFinishAction(delegate
{
// 维修完成后,如果机甲被征召,恢复其工作
if (Mech.jobs?.curJob != null && job.playerForced)
{
Mech.jobs.EndCurrentJob(JobCondition.InterruptForced);
}
});
repairToil.AddEndCondition(() => MechNeedsRepair() ? JobCondition.Ongoing : JobCondition.Succeeded);
if (!Remote)
{
repairToil.activeSkill = () => SkillDefOf.Crafting;
}
yield return repairToil;
}
private bool MechRepairable()
{
return RepairableComp != null;
}
private bool MechNeedsRepair()
{
return RepairableComp?.NeedsRepair ?? false;
}
private void RepairTick(int delta)
{
if (Mech == null || Mech.health == null || Mech.Dead)
return;
// 计算本次修复的总HP量
float totalRepairAmount = RepairAmountPerCycle;
float originalRepairAmount = totalRepairAmount;
// 第一阶段:先修复现有伤口(非缺失部位)
totalRepairAmount = RepairExistingInjuries(totalRepairAmount);
// 第二阶段:如果还有修复量,并且机甲血量足够安全,再处理缺失部位
if (totalRepairAmount > 0f && IsSafeToRepairMissingParts())
{
// 直接尝试转换缺失部位,不消耗修复量
TryConvertMissingParts();
}
// 记录修复统计(只统计实际修复的伤口)
if (RepairableComp != null && totalRepairAmount < originalRepairAmount)
{
RepairableComp.RecordRepair(originalRepairAmount - totalRepairAmount);
}
}
// 修复现有伤口(非缺失部位)
private float RepairExistingInjuries(float totalRepairAmount)
{
float remainingAmount = totalRepairAmount;
// 获取所有非缺失部位的伤口
var existingInjuries = Mech.health.hediffSet.hediffs
.Where(h =>
(h is Hediff_Injury && h.Severity > 0f) ||
(h.def.tendable && h.Severity > 0f && !(h is Hediff_MissingPart))
)
.OrderByDescending(h => h.Severity) // 优先修复最严重的伤口
.ToList();
foreach (var injury in existingInjuries)
{
if (remainingAmount <= 0f)
break;
if (injury is Hediff_Injury injuryHediff)
{
// 修复伤害
float healAmount = Mathf.Min(remainingAmount, injuryHediff.Severity);
injuryHediff.Severity -= healAmount;
remainingAmount -= healAmount;
if (injuryHediff.Severity <= 0f)
{
Mech.health.RemoveHediff(injuryHediff);
}
}
else if (injury.def.tendable)
{
// 其他可治疗的hediff
float healAmount = Mathf.Min(remainingAmount, injury.Severity);
injury.Severity -= healAmount;
remainingAmount -= healAmount;
if (injury.Severity <= 0f)
{
Mech.health.RemoveHediff(injury);
}
}
}
return remainingAmount;
}
// 检查是否安全可以修复缺失部位
private bool IsSafeToRepairMissingParts()
{
// 获取机甲当前血量百分比
float currentHealthPercent = Mech.health.summaryHealth.SummaryHealthPercent;
// 如果血量低于30%,不安全修复缺失部位
if (currentHealthPercent < 0.3f)
return false;
// 如果有严重伤口严重性大于5先修复它们
bool hasCriticalInjuries = Mech.health.hediffSet.hediffs
.Any(h => h is Hediff_Injury && h.Severity > 5f);
if (hasCriticalInjuries)
return false;
// 检查缺失部位转换是否会致命
if (WouldMissingPartConversionBeFatal())
return false;
return true;
}
// 检查缺失部位转换是否会致命
private bool WouldMissingPartConversionBeFatal()
{
// 获取所有缺失部位
var missingParts = Mech.health.hediffSet.GetMissingPartsCommonAncestors();
if (!missingParts.Any())
return false;
// 计算转换后可能增加的总伤害量
float potentialAddedDamage = 0f;
foreach (var missingPart in missingParts)
{
float partMaxHealth = missingPart.Part.def.GetMaxHealth(Mech);
float injurySeverity = partMaxHealth - 1;
if (partMaxHealth <= 1)
injurySeverity = 0.5f;
potentialAddedDamage += injurySeverity;
}
// 获取当前总伤害量
float currentTotalInjurySeverity = Mech.health.hediffSet.hediffs
.Where(h => h is Hediff_Injury)
.Sum(h => h.Severity);
// 计算转换后的总伤害量
float projectedTotalInjurySeverity = currentTotalInjurySeverity + potentialAddedDamage;
// 获取致命伤害阈值
float lethalDamageThreshold = Mech.health.LethalDamageThreshold;
// 如果转换后的总伤害量超过或接近致命阈值,不安全
return projectedTotalInjurySeverity >= lethalDamageThreshold * 0.8f;
}
// 尝试转换缺失部位
private void TryConvertMissingParts()
{
// 获取所有缺失部位
var missingParts = Mech.health.hediffSet.GetMissingPartsCommonAncestors();
if (!missingParts.Any())
return;
// 选择最小的缺失部件进行转换(成本较低)
Hediff_MissingPart partToRepair = null;
float minHealth = float.MaxValue;
foreach (var missingPart in missingParts)
{
float partHealth = missingPart.Part.def.GetMaxHealth(Mech);
if (partHealth < minHealth)
{
minHealth = partHealth;
partToRepair = missingPart;
}
}
if (partToRepair != null)
{
// 直接转换缺失部位
if (ConvertMissingPartToInjury(partToRepair))
{
}
}
}
// 将缺失部件转换为伤害hediff
private bool ConvertMissingPartToInjury(Hediff_MissingPart missingPart)
{
try
{
float partMaxHealth = missingPart.Part.def.GetMaxHealth(Mech);
// 关键修复:确保转换后的损伤不会导致部位再次缺失
// 设置损伤严重性为最大健康值-1这样部位健康值至少为1
float injurySeverity = partMaxHealth - 1;
// 如果最大健康值为1则设置为0.5确保部位健康值大于0
if (partMaxHealth <= 1)
{
injurySeverity = 0.5f;
}
// 移除缺失部件hediff
Mech.health.RemoveHediff(missingPart);
// 添加指定的伤害hediff
HediffDef injuryDef = MissingPartReplacementInjury;
if (injuryDef == null)
{
Log.Error($"[WULA] 找不到指定的hediff定义: {MissingPartReplacementInjury?.defName ?? "null"}");
return false;
}
// 创建损伤
Hediff injury = HediffMaker.MakeHediff(injuryDef, Mech, missingPart.Part);
injury.Severity = injurySeverity;
Mech.health.AddHediff(injury);
return true;
}
catch (System.Exception ex)
{
Log.Error($"[WULA] 转换缺失部件 {missingPart.Part.def.defName} 时出错: {ex}");
return false;
}
}
// 获取所有可修复的hediff
private List<Hediff> GetAllRepairableHediffs()
{
var repairableHediffs = new List<Hediff>();
if (Mech.health?.hediffSet == null)
return repairableHediffs;
// 获取所有hediff
foreach (var hediff in Mech.health.hediffSet.hediffs)
{
if (CanRepairHediff(hediff))
{
repairableHediffs.Add(hediff);
}
}
return repairableHediffs;
}
// 检查hediff是否可修复
private bool CanRepairHediff(Hediff hediff)
{
// 缺失部位可以修复
if (hediff is Hediff_MissingPart)
return true;
// 伤害可以修复
if (hediff is Hediff_Injury)
return true;
// 可治疗的hediff可以修复
if (hediff.def.tendable && hediff.Severity > 0f)
return true;
// 跳过疾病
if (IsDisease(hediff))
return false;
return false;
}
// 检查是否是疾病
private bool IsDisease(Hediff hediff)
{
// 常见的疾病类型
string[] diseaseKeywords = {
"Disease", "Flu", "Plague", "Infection", "Malaria",
"SleepingSickness", "FibrousMechanites", "SensoryMechanites",
"WoundInfection", "FoodPoisoning", "GutWorms", "MuscleParasites"
};
foreach (string keyword in diseaseKeywords)
{
if (hediff.def.defName.Contains(keyword))
return true;
}
return false;
}
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref ticksToNextRepair, "ticksToNextRepair", 0);
}
}
}

View File

@@ -0,0 +1,73 @@
// WorkGiver_RepairMech.cs
using RimWorld;
using System.Collections.Generic;
using System.Linq;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class WorkGiver_RepairMech : WorkGiver_Scanner
{
public override PathEndMode PathEndMode => PathEndMode.Touch;
public override Danger MaxPathDanger(Pawn pawn)
{
return Danger.Deadly;
}
public override IEnumerable<Thing> PotentialWorkThingsGlobal(Pawn pawn)
{
if (pawn.Faction != Faction.OfPlayer || pawn.Map == null)
return Enumerable.Empty<Thing>();
// 获取所有需要维修的玩家机甲
return pawn.Map.mapPawns.AllPawnsSpawned
.Where(p =>
p.Faction == Faction.OfPlayer &&
p.health != null &&
!p.Dead &&
p.TryGetComp<CompMechRepairable>()?.CanAutoRepair == true
)
.Cast<Thing>();
}
public override bool ShouldSkip(Pawn pawn, bool forced = false)
{
if (pawn.story != null && pawn.WorkTagIsDisabled(WorkTags.Crafting))
return true;
return false;
}
public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
{
if (!(t is Pawn mech) || mech.Dead)
return false;
var repairableComp = t.TryGetComp<CompMechRepairable>();
if (repairableComp == null || !repairableComp.CanAutoRepair)
return false;
if (!repairableComp.NeedsRepair)
return false;
if (pawn.Faction != Faction.OfPlayer)
return false;
if (!pawn.CanReserveAndReach(t, PathEndMode.Touch, Danger.Some, 1, -1, null, forced))
return false;
// 检查工作标签
if (pawn.story != null && pawn.WorkTagIsDisabled(WorkTags.Crafting))
return false;
return true;
}
public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
{
return JobMaker.MakeJob(Wula_JobDefOf.WULA_RepairMech, t);
}
}
}

View File

@@ -0,0 +1,39 @@
// ThinkNode_ConditionalMechHasPilot.cs (修复版)
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class ThinkNode_ConditionalMechHasPilot : ThinkNode_Conditional
{
// 可选的可以在XML中设置的参数
public int minPilotCount = 1; // 最少需要的驾驶员数量
protected override bool Satisfied(Pawn pawn)
{
if (pawn.Faction != Faction.OfPlayer)
return false; // 仅适用于玩家派系的机甲
var pilotComp = pawn.TryGetComp<CompMechPilotHolder>();
if (pilotComp == null)
return false; // 如果没有驾驶员组件,条件满足(允许执行)
int currentPilotCount = pilotComp.CurrentPilotCount;
// 检查是否满足最小驾驶员数量要求
bool hasEnoughPilots = currentPilotCount >= minPilotCount;
// 这意味着机甲可以正常工作
bool conditionSatisfied = hasEnoughPilots;
return !conditionSatisfied;
}
public override ThinkNode DeepCopy(bool resolve = true)
{
ThinkNode_ConditionalMechHasPilot thinkNode_ConditionalMechHasPilot = (ThinkNode_ConditionalMechHasPilot)base.DeepCopy(resolve);
thinkNode_ConditionalMechHasPilot.minPilotCount = minPilotCount;
return thinkNode_ConditionalMechHasPilot;
}
}
}