1
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
188
Source/WulaFallenEmpire/Work/EnterMech/WorkGiver_EnterMech.cs
Normal file
188
Source/WulaFallenEmpire/Work/EnterMech/WorkGiver_EnterMech.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Source/WulaFallenEmpire/Work/JobDriver_CarryToMech.cs
Normal file
82
Source/WulaFallenEmpire/Work/JobDriver_CarryToMech.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Source/WulaFallenEmpire/Work/JobGiver_NoPilot.cs
Normal file
18
Source/WulaFallenEmpire/Work/JobGiver_NoPilot.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
176
Source/WulaFallenEmpire/Work/RefuelMech/JobDriver_RefuelMech.cs
Normal file
176
Source/WulaFallenEmpire/Work/RefuelMech/JobDriver_RefuelMech.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
123
Source/WulaFallenEmpire/Work/RefuelMech/WorkGiver_RefuelMech.cs
Normal file
123
Source/WulaFallenEmpire/Work/RefuelMech/WorkGiver_RefuelMech.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
416
Source/WulaFallenEmpire/Work/RepairMech/JobDriver_RepairMech.cs
Normal file
416
Source/WulaFallenEmpire/Work/RepairMech/JobDriver_RepairMech.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user