整理scoure
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.AI.Group;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompAutoMechCarrier : CompMechCarrier
|
||||
{
|
||||
#region Reflected Fields
|
||||
private static FieldInfo spawnedPawnsField;
|
||||
private static FieldInfo cooldownTicksRemainingField;
|
||||
private static FieldInfo innerContainerField;
|
||||
|
||||
private List<Pawn> SpawnedPawns
|
||||
{
|
||||
get
|
||||
{
|
||||
if (spawnedPawnsField == null)
|
||||
spawnedPawnsField = typeof(CompMechCarrier).GetField("spawnedPawns", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
return (List<Pawn>)spawnedPawnsField.GetValue(this);
|
||||
}
|
||||
}
|
||||
|
||||
private int CooldownTicksRemaining
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cooldownTicksRemainingField == null)
|
||||
cooldownTicksRemainingField = typeof(CompMechCarrier).GetField("cooldownTicksRemaining", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
return (int)cooldownTicksRemainingField.GetValue(this);
|
||||
}
|
||||
set
|
||||
{
|
||||
if (cooldownTicksRemainingField == null)
|
||||
cooldownTicksRemainingField = typeof(CompMechCarrier).GetField("cooldownTicksRemaining", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
cooldownTicksRemainingField.SetValue(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
private ThingOwner InnerContainer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (innerContainerField == null)
|
||||
innerContainerField = typeof(CompMechCarrier).GetField("innerContainer", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
return (ThingOwner)innerContainerField.GetValue(this);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public CompProperties_AutoMechCarrier AutoProps => (CompProperties_AutoMechCarrier)props;
|
||||
|
||||
private int TotalPawnCapacity => AutoProps.productionQueue.Sum(e => e.count);
|
||||
|
||||
private int LiveSpawnedPawnsCount(PawnKindDef kind)
|
||||
{
|
||||
SpawnedPawns.RemoveAll(p => p == null || p.Destroyed);
|
||||
return SpawnedPawns.Count(p => p.kindDef == kind);
|
||||
}
|
||||
|
||||
private AcceptanceReport CanSpawnNow(PawnKindDef kind)
|
||||
{
|
||||
if (parent is Pawn pawn && (pawn.IsSelfShutdown() || !pawn.Awake() || pawn.Downed || pawn.Dead || !pawn.Spawned))
|
||||
return false;
|
||||
if (CooldownTicksRemaining > 0)
|
||||
return "CooldownTime".Translate() + " " + CooldownTicksRemaining.ToStringSecondsFromTicks();
|
||||
|
||||
PawnProductionEntry entry = AutoProps.productionQueue.First(e => e.pawnKind == kind);
|
||||
int cost = entry.cost ?? Props.costPerPawn;
|
||||
|
||||
if (!AutoProps.freeProduction && InnerContainer.TotalStackCountOfDef(Props.fixedIngredient) < cost)
|
||||
return "MechCarrierNotEnoughResources".Translate();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TrySpawnPawn(PawnKindDef kind)
|
||||
{
|
||||
PawnGenerationRequest request = new PawnGenerationRequest(kind, parent.Faction, PawnGenerationContext.NonPlayer, -1, forceGenerateNewPawn: true);
|
||||
Pawn pawn = PawnGenerator.GeneratePawn(request);
|
||||
GenSpawn.Spawn(pawn, parent.Position, parent.Map);
|
||||
SpawnedPawns.Add(pawn);
|
||||
|
||||
if (parent is Pawn p && p.GetLord() != null)
|
||||
p.GetLord().AddPawn(pawn);
|
||||
|
||||
if (!AutoProps.freeProduction)
|
||||
{
|
||||
PawnProductionEntry entry = AutoProps.productionQueue.First(e => e.pawnKind == kind);
|
||||
int costLeft = entry.cost ?? Props.costPerPawn;
|
||||
|
||||
List<Thing> things = new List<Thing>(InnerContainer);
|
||||
for (int j = 0; j < things.Count; j++)
|
||||
{
|
||||
Thing thing = InnerContainer.Take(things[j], Mathf.Min(things[j].stackCount, costLeft));
|
||||
costLeft -= thing.stackCount;
|
||||
thing.Destroy();
|
||||
if (costLeft <= 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
PawnProductionEntry spawnEntry = AutoProps.productionQueue.First(e => e.pawnKind == kind);
|
||||
CooldownTicksRemaining = spawnEntry.cooldownTicks ?? Props.cooldownTicks;
|
||||
|
||||
if (Props.spawnedMechEffecter != null)
|
||||
EffecterTrigger(Props.spawnedMechEffecter, Props.attachSpawnedMechEffecter, pawn);
|
||||
if (Props.spawnEffecter != null)
|
||||
EffecterTrigger(Props.spawnEffecter, Props.attachSpawnedEffecter, parent);
|
||||
}
|
||||
|
||||
private void EffecterTrigger(EffecterDef effecterDef, bool attach, Thing target)
|
||||
{
|
||||
Effecter effecter = new Effecter(effecterDef);
|
||||
effecter.Trigger(attach ? ((TargetInfo)target) : new TargetInfo(target.Position, target.Map), TargetInfo.Invalid);
|
||||
effecter.Cleanup();
|
||||
}
|
||||
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
|
||||
if (parent.IsHashIntervalTick(60)) // 每秒检查一次
|
||||
{
|
||||
// 检查是否有抑制生产的Hediff
|
||||
if (AutoProps.disableHediff != null && (parent as Pawn)?.health.hediffSet.HasHediff(AutoProps.disableHediff) == true)
|
||||
{
|
||||
return; // 有Hediff,停止生产
|
||||
}
|
||||
|
||||
// 1. 先检查是否满员
|
||||
bool isFull = true;
|
||||
foreach (var entry in AutoProps.productionQueue)
|
||||
{
|
||||
if (LiveSpawnedPawnsCount(entry.pawnKind) < entry.count)
|
||||
{
|
||||
isFull = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isFull)
|
||||
{
|
||||
return; // 如果已满员,则不进行任何操作,包括冷却计时
|
||||
}
|
||||
|
||||
// 2. 如果未满员,才检查冷却时间
|
||||
if (CooldownTicksRemaining > 0) return;
|
||||
|
||||
// 3. 寻找空位并生产
|
||||
foreach (var entry in AutoProps.productionQueue)
|
||||
{
|
||||
if (LiveSpawnedPawnsCount(entry.pawnKind) < entry.count)
|
||||
{
|
||||
if (CanSpawnNow(entry.pawnKind).Accepted)
|
||||
{
|
||||
TrySpawnPawn(entry.pawnKind);
|
||||
break; // 每次只生产一个
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
||||
{
|
||||
// 移除所有Gizmo逻辑
|
||||
return Enumerable.Empty<Gizmo>();
|
||||
}
|
||||
|
||||
public override string CompInspectStringExtra()
|
||||
{
|
||||
SpawnedPawns.RemoveAll(p => p == null || p.Destroyed);
|
||||
string text = "Pawns: " + SpawnedPawns.Count + " / " + TotalPawnCapacity;
|
||||
|
||||
foreach (var entry in AutoProps.productionQueue)
|
||||
{
|
||||
text += $"\n- {entry.pawnKind.LabelCap}: {LiveSpawnedPawnsCount(entry.pawnKind)} / {entry.count}";
|
||||
}
|
||||
|
||||
if (CooldownTicksRemaining > 0)
|
||||
{
|
||||
text += "\n" + "CooldownTime".Translate() + ": " + CooldownTicksRemaining.ToStringSecondsFromTicks();
|
||||
}
|
||||
|
||||
if (!AutoProps.freeProduction)
|
||||
{
|
||||
text += "\n" + base.CompInspectStringExtra();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_AutoMechCarrier : CompProperties_MechCarrier
|
||||
{
|
||||
// XML中定义,生产是否消耗资源
|
||||
public bool freeProduction = false;
|
||||
|
||||
// 如果单位拥有这个Hediff,则停止生产
|
||||
public HediffDef disableHediff;
|
||||
|
||||
// 定义生产队列
|
||||
public List<PawnProductionEntry> productionQueue = new List<PawnProductionEntry>();
|
||||
|
||||
public CompProperties_AutoMechCarrier()
|
||||
{
|
||||
// 确保这个属性类指向我们新的功能实现类
|
||||
compClass = typeof(CompAutoMechCarrier);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> ConfigErrors(ThingDef parentDef)
|
||||
{
|
||||
foreach (string error in base.ConfigErrors(parentDef))
|
||||
{
|
||||
yield return error;
|
||||
}
|
||||
|
||||
if (productionQueue.NullOrEmpty())
|
||||
{
|
||||
yield return "CompProperties_AutoMechCarrier must have at least one entry in productionQueue.";
|
||||
}
|
||||
}
|
||||
|
||||
public override void ResolveReferences(ThingDef parentDef)
|
||||
{
|
||||
base.ResolveReferences(parentDef);
|
||||
// Prevent division by zero if costPerPawn is not set, which the base game AI might try to access.
|
||||
if (costPerPawn <= 0)
|
||||
{
|
||||
costPerPawn = 1;
|
||||
}
|
||||
|
||||
// 如果spawnPawnKind为空(因为我们用了新的队列系统),
|
||||
// 就从队列里取第一个作为“假”值,以防止基类方法在生成Gizmo标签时出错。
|
||||
if (spawnPawnKind == null && !productionQueue.NullOrEmpty())
|
||||
{
|
||||
spawnPawnKind = productionQueue[0].pawnKind;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
/// <summary>
|
||||
/// A data class to hold information about a pawn to be produced in a queue.
|
||||
/// Used in XML definitions.
|
||||
/// </summary>
|
||||
public class PawnProductionEntry
|
||||
{
|
||||
// The PawnKindDef of the unit to spawn.
|
||||
public PawnKindDef pawnKind;
|
||||
|
||||
// The maximum number of this kind of unit to maintain.
|
||||
public int count = 1;
|
||||
|
||||
// Optional: specific cooldown for this entry. If not set, the parent comp's cooldown is used.
|
||||
public int? cooldownTicks;
|
||||
|
||||
// Optional: specific cost for this entry. If not set, the parent comp's costPerPawn is used.
|
||||
public int? cost;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class MentalBreakWorker_BrokenPersonality : MentalBreakWorker
|
||||
{
|
||||
public override bool TryStart(Pawn pawn, string reason, bool causedByMood)
|
||||
{
|
||||
// 先尝试启动精神状态
|
||||
if (base.TryStart(pawn, reason, causedByMood))
|
||||
{
|
||||
// 成功启动后,执行附加逻辑
|
||||
var extension = def.mentalState.GetModExtension<MentalStateDefExtension_BrokenPersonality>();
|
||||
if (extension != null && extension.traitToAdd != null && !pawn.story.traits.HasTrait(extension.traitToAdd))
|
||||
{
|
||||
pawn.story.traits.GainTrait(new Trait(extension.traitToAdd));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class MentalStateDefExtension_BrokenPersonality : DefModExtension
|
||||
{
|
||||
public TraitDef traitToAdd;
|
||||
public FactionDef factionToJoin;
|
||||
public float skillLevelFactor = 1f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using Verse.AI.Group;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class MentalState_BrokenPersonality : MentalState
|
||||
{
|
||||
public override void PostStart(string reason)
|
||||
{
|
||||
base.PostStart(reason);
|
||||
|
||||
// 发送信件
|
||||
if (PawnUtility.ShouldSendNotificationAbout(pawn))
|
||||
{
|
||||
// 手动实现备用逻辑:如果信件标题(beginLetterLabel)为空,则使用精神状态的通用标签(label)
|
||||
string labelText = def.beginLetterLabel;
|
||||
if (string.IsNullOrEmpty(labelText))
|
||||
{
|
||||
labelText = def.label;
|
||||
}
|
||||
TaggedString letterLabel = labelText.Formatted(pawn.LabelShort, pawn.Named("PAWN")).CapitalizeFirst();
|
||||
TaggedString letterText = def.beginLetter.Formatted(pawn.LabelShort, pawn.Named("PAWN")).CapitalizeFirst();
|
||||
if (reason != null)
|
||||
{
|
||||
letterText += "\n\n" + reason;
|
||||
}
|
||||
Find.LetterStack.ReceiveLetter(letterLabel, letterText, LetterDefOf.ThreatBig, pawn);
|
||||
}
|
||||
|
||||
var extension = def.GetModExtension<MentalStateDefExtension_BrokenPersonality>();
|
||||
if (extension != null)
|
||||
{
|
||||
bool alreadyBroken = pawn.story.traits.HasTrait(extension.traitToAdd);
|
||||
|
||||
if (!alreadyBroken)
|
||||
{
|
||||
// 移除所有技能热情
|
||||
foreach (SkillRecord skill in pawn.skills.skills)
|
||||
{
|
||||
skill.passion = Passion.None;
|
||||
}
|
||||
|
||||
// 所有技能等级减半
|
||||
foreach (SkillRecord skill in pawn.skills.skills)
|
||||
{
|
||||
int currentLevel = skill.Level;
|
||||
skill.Level = (int)(currentLevel * extension.skillLevelFactor);
|
||||
}
|
||||
}
|
||||
|
||||
// 改变派系
|
||||
Faction newFaction = Find.FactionManager.FirstFactionOfDef(extension.factionToJoin);
|
||||
if (newFaction == null)
|
||||
{
|
||||
newFaction = Find.FactionManager.FirstFactionOfDef(FactionDefOf.AncientsHostile);
|
||||
}
|
||||
|
||||
if (newFaction != null)
|
||||
{
|
||||
pawn.SetFaction(newFaction, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 离开地图
|
||||
Lord lord = pawn.GetLord();
|
||||
if (lord == null)
|
||||
{
|
||||
LordJob_ExitMapBest lordJob = new LordJob_ExitMapBest(LocomotionUrgency.Jog, canDig: true, canDefendSelf: true);
|
||||
lord = LordMaker.MakeNewLord(pawn.Faction, lordJob, pawn.Map, Gen.YieldSingle(pawn));
|
||||
}
|
||||
else
|
||||
{
|
||||
lord.ReceiveMemo("PawnBroken");
|
||||
}
|
||||
|
||||
// 强制恢复以避免状态无限持续
|
||||
this.forceRecoverAfterTicks = 150;
|
||||
}
|
||||
|
||||
public override void MentalStateTick(int delta)
|
||||
{
|
||||
base.MentalStateTick(delta);
|
||||
// 确保在下一帧就恢复,因为所有效果都已经应用
|
||||
if (age > 0)
|
||||
{
|
||||
RecoverFromState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
Source/WulaFallenEmpire/Pawn/WULA_Energy/CompChargingBed.cs
Normal file
101
Source/WulaFallenEmpire/Pawn/WULA_Energy/CompChargingBed.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_ChargingBed : CompProperties
|
||||
{
|
||||
public HediffDef hediffDef;
|
||||
public string raceDefName;
|
||||
|
||||
public CompProperties_ChargingBed()
|
||||
{
|
||||
compClass = typeof(CompChargingBed);
|
||||
}
|
||||
}
|
||||
|
||||
public class CompChargingBed : ThingComp
|
||||
{
|
||||
public CompProperties_ChargingBed Props => (CompProperties_ChargingBed)props;
|
||||
|
||||
private List<Pawn> chargingPawns = new List<Pawn>();
|
||||
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
|
||||
var bed = (Building_Bed)parent;
|
||||
var powerComp = parent.GetComp<CompPowerTrader>();
|
||||
|
||||
// 如果床没电,停止所有充电
|
||||
if (powerComp is { PowerOn: false })
|
||||
{
|
||||
StopAllCharging();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentOccupants = new HashSet<Pawn>(bed.CurOccupants);
|
||||
|
||||
// 移除已经不在床上的 pawn 的充电效果
|
||||
for (int i = chargingPawns.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var pawn = chargingPawns[i];
|
||||
if (!currentOccupants.Contains(pawn))
|
||||
{
|
||||
StopCharging(pawn);
|
||||
chargingPawns.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
// 为床上的新 pawn 开始充电
|
||||
foreach (var pawn in currentOccupants)
|
||||
{
|
||||
if (ShouldCharge(pawn) && !chargingPawns.Contains(pawn))
|
||||
{
|
||||
StartCharging(pawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldCharge(Pawn pawn)
|
||||
{
|
||||
return pawn.def.defName == Props.raceDefName;
|
||||
}
|
||||
|
||||
private void StartCharging(Pawn pawn)
|
||||
{
|
||||
if (pawn.health.hediffSet.HasHediff(Props.hediffDef)) return;
|
||||
pawn.health.AddHediff(Props.hediffDef);
|
||||
if (!chargingPawns.Contains(pawn))
|
||||
{
|
||||
chargingPawns.Add(pawn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void StopCharging(Pawn pawn)
|
||||
{
|
||||
var hediff = pawn.health.hediffSet.GetFirstHediffOfDef(Props.hediffDef);
|
||||
if (hediff != null)
|
||||
{
|
||||
pawn.health.RemoveHediff(hediff);
|
||||
}
|
||||
}
|
||||
|
||||
private void StopAllCharging()
|
||||
{
|
||||
for (int i = chargingPawns.Count - 1; i >= 0; i--)
|
||||
{
|
||||
StopCharging(chargingPawns[i]);
|
||||
chargingPawns.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
Scribe_Collections.Look(ref chargingPawns, "chargingPawns", LookMode.Reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class HediffCompProperties_WulaCharging : HediffCompProperties
|
||||
{
|
||||
public float energyPerTick = 0.001f; // 每tick补充的能量量
|
||||
public int durationTicks = 600; // 持续时间,例如600ticks = 10秒
|
||||
|
||||
public HediffCompProperties_WulaCharging()
|
||||
{
|
||||
this.compClass = typeof(HediffComp_WulaCharging);
|
||||
}
|
||||
}
|
||||
|
||||
public class HediffComp_WulaCharging : HediffComp
|
||||
{
|
||||
public HediffCompProperties_WulaCharging Props => (HediffCompProperties_WulaCharging)this.props;
|
||||
|
||||
private int ticksPassed = 0;
|
||||
private Thing sourceThing; // 新增字段,用于存储能量核心物品
|
||||
|
||||
public void SetSourceThing(Thing thing)
|
||||
{
|
||||
this.sourceThing = thing;
|
||||
}
|
||||
|
||||
public override void CompPostTick(ref float severityAdjustment)
|
||||
{
|
||||
base.CompPostTick(ref severityAdjustment);
|
||||
|
||||
ticksPassed++;
|
||||
if (ticksPassed >= Props.durationTicks)
|
||||
{
|
||||
// 持续时间结束,移除Hediff
|
||||
this.parent.pawn.health.RemoveHediff(this.parent);
|
||||
return;
|
||||
}
|
||||
|
||||
Need_WulaEnergy energyNeed = this.parent.pawn.needs.TryGetNeed<Need_WulaEnergy>();
|
||||
if (energyNeed != null)
|
||||
{
|
||||
// 从sourceThing的ThingDefExtension_EnergySource获取能量量
|
||||
ThingDefExtension_EnergySource ext = sourceThing?.def.GetModExtension<ThingDefExtension_EnergySource>();
|
||||
if (ext != null)
|
||||
{
|
||||
energyNeed.CurLevel += ext.energyAmount / Props.durationTicks; // 将总能量量分摊到每个tick
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果没有找到能量来源扩展,则使用默认的energyPerTick
|
||||
energyNeed.CurLevel += Props.energyPerTick;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void CompExposeData()
|
||||
{
|
||||
base.CompExposeData();
|
||||
Scribe_Values.Look(ref ticksPassed, "ticksPassed", 0);
|
||||
Scribe_References.Look(ref sourceThing, "sourceThing"); // 保存sourceThing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using RimWorld;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class JobDriver_FeedWulaPatient : JobDriver
|
||||
{
|
||||
private const TargetIndex FoodSourceInd = TargetIndex.A;
|
||||
private const TargetIndex PatientInd = TargetIndex.B;
|
||||
|
||||
protected Thing Food => job.GetTarget(FoodSourceInd).Thing;
|
||||
protected Pawn Patient => (Pawn)job.GetTarget(PatientInd).Thing;
|
||||
|
||||
public override bool TryMakePreToilReservations(bool errorOnFailed)
|
||||
{
|
||||
if (!pawn.Reserve(Patient, job, 1, -1, null, errorOnFailed))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!pawn.Reserve(Food, job, 1, -1, null, errorOnFailed))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Toil> MakeNewToils()
|
||||
{
|
||||
this.FailOnDespawnedNullOrForbidden(PatientInd);
|
||||
// The job should fail if the patient is no longer in bed.
|
||||
this.FailOn(() => !Patient.InBed());
|
||||
|
||||
if (pawn.inventory != null && pawn.inventory.Contains(Food))
|
||||
{
|
||||
yield return Toils_Misc.TakeItemFromInventoryToCarrier(pawn, FoodSourceInd);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return Toils_Goto.GotoThing(FoodSourceInd, PathEndMode.ClosestTouch).FailOnForbidden(FoodSourceInd);
|
||||
yield return Toils_Ingest.PickupIngestible(FoodSourceInd, pawn);
|
||||
}
|
||||
|
||||
yield return Toils_Goto.GotoThing(PatientInd, PathEndMode.Touch);
|
||||
yield return Toils_Ingest.ChewIngestible(Patient, 1.5f, FoodSourceInd, TargetIndex.None).FailOnCannotTouch(PatientInd, PathEndMode.Touch);
|
||||
|
||||
// Custom Finalize Ingest Logic
|
||||
Toil finalizeToil = new Toil();
|
||||
finalizeToil.initAction = delegate
|
||||
{
|
||||
Pawn patient = Patient;
|
||||
Thing food = Food;
|
||||
if (patient == null || food == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's not an energy core, use the default vanilla method.
|
||||
if (food.def.defName != "WULA_Charge_Cube")
|
||||
{
|
||||
patient.needs.food.CurLevel += FoodUtility.GetNutrition(patient, food, food.def);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Our custom logic for energy core
|
||||
// 1. Apply the charging hediff
|
||||
Hediff hediff = HediffMaker.MakeHediff(HediffDef.Named("WULA_ChargingHediff"), patient);
|
||||
hediff.Severity = 1.0f;
|
||||
patient.health.AddHediff(hediff);
|
||||
|
||||
// 2. Spawn the used core
|
||||
Thing usedCore = ThingMaker.MakeThing(ThingDef.Named("WULA_Charge_Cube_No_Power"));
|
||||
GenPlace.TryPlaceThing(usedCore, pawn.Position, pawn.Map, ThingPlaceMode.Near);
|
||||
}
|
||||
|
||||
// Destroy the food item (it has been carried by the feeder)
|
||||
if (!food.Destroyed)
|
||||
{
|
||||
food.Destroy();
|
||||
}
|
||||
};
|
||||
finalizeToil.defaultCompleteMode = ToilCompleteMode.Instant;
|
||||
yield return finalizeToil;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using RimWorld;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class JobDriver_IngestWulaEnergy : JobDriver
|
||||
{
|
||||
private bool eatingFromInventory;
|
||||
|
||||
private const TargetIndex IngestibleSourceInd = TargetIndex.A;
|
||||
|
||||
private Thing IngestibleSource => job.GetTarget(IngestibleSourceInd).Thing;
|
||||
|
||||
private float ChewDurationMultiplier
|
||||
{
|
||||
get
|
||||
{
|
||||
Thing ingestibleSource = IngestibleSource;
|
||||
if (ingestibleSource.def.ingestible != null)
|
||||
{
|
||||
return 1f / pawn.GetStatValue(StatDefOf.EatingSpeed);
|
||||
}
|
||||
return 1f;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Values.Look(ref eatingFromInventory, "eatingFromInventory", defaultValue: false);
|
||||
}
|
||||
|
||||
public override void Notify_Starting()
|
||||
{
|
||||
base.Notify_Starting();
|
||||
eatingFromInventory = pawn.inventory != null && pawn.inventory.Contains(IngestibleSource);
|
||||
}
|
||||
|
||||
public override bool TryMakePreToilReservations(bool errorOnFailed)
|
||||
{
|
||||
if (pawn.Faction != null)
|
||||
{
|
||||
Thing ingestibleSource = IngestibleSource;
|
||||
int maxAmountToPickup = FoodUtility.GetMaxAmountToPickup(ingestibleSource, pawn, job.count);
|
||||
if (!pawn.Reserve(ingestibleSource, job, 10, maxAmountToPickup, null, errorOnFailed))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
job.count = maxAmountToPickup;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Toil> MakeNewToils()
|
||||
{
|
||||
this.FailOn(() => !IngestibleSource.Destroyed && !IngestibleSource.IngestibleNow);
|
||||
|
||||
Toil chew = Toils_Ingest.ChewIngestible(pawn, ChewDurationMultiplier, IngestibleSourceInd, TargetIndex.None)
|
||||
.FailOn((Toil x) => !IngestibleSource.Spawned && (pawn.carryTracker == null || pawn.carryTracker.CarriedThing != IngestibleSource))
|
||||
.FailOnCannotTouch(IngestibleSourceInd, PathEndMode.Touch);
|
||||
|
||||
foreach (Toil item in PrepareToIngestToils(chew))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
|
||||
yield return chew;
|
||||
|
||||
// Custom Finalize Ingest Logic
|
||||
Toil finalizeToil = new Toil();
|
||||
finalizeToil.initAction = delegate
|
||||
{
|
||||
Pawn ingester = pawn;
|
||||
Thing ingestible = IngestibleSource;
|
||||
if (ingester == null || ingestible == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's not an energy core, use the default vanilla method for safety, though this job should only target energy cores.
|
||||
if (ingestible.def.defName != "WULA_Charge_Cube")
|
||||
{
|
||||
ingester.needs.food.CurLevel += FoodUtility.GetNutrition(ingester, ingestible, ingestible.def);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Our custom logic for energy core
|
||||
// 1. Apply the charging hediff
|
||||
Hediff hediff = HediffMaker.MakeHediff(HediffDef.Named("WULA_ChargingHediff"), ingester);
|
||||
hediff.Severity = 1.0f;
|
||||
ingester.health.AddHediff(hediff);
|
||||
|
||||
// 2. Spawn the used core
|
||||
Thing usedCore = ThingMaker.MakeThing(ThingDef.Named("WULA_Charge_Cube_No_Power"));
|
||||
GenPlace.TryPlaceThing(usedCore, ingester.Position, ingester.Map, ThingPlaceMode.Near);
|
||||
}
|
||||
|
||||
// Destroy the original food item
|
||||
if (!ingestible.Destroyed)
|
||||
{
|
||||
ingestible.Destroy();
|
||||
}
|
||||
};
|
||||
finalizeToil.defaultCompleteMode = ToilCompleteMode.Instant;
|
||||
yield return finalizeToil;
|
||||
}
|
||||
|
||||
private IEnumerable<Toil> PrepareToIngestToils(Toil chewToil)
|
||||
{
|
||||
if (eatingFromInventory)
|
||||
{
|
||||
yield return Toils_Misc.TakeItemFromInventoryToCarrier(pawn, IngestibleSourceInd);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return Toils_Goto.GotoThing(IngestibleSourceInd, PathEndMode.ClosestTouch).FailOnDespawnedNullOrForbidden(IngestibleSourceInd);
|
||||
yield return Toils_Ingest.PickupIngestible(IngestibleSourceInd, pawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class JobGiverDefExtension_WulaPackEnergy : DefModExtension
|
||||
{
|
||||
public float packEnergyThreshold = 0.5f; // 默认打包能量阈值
|
||||
public int packEnergyCount = 10; // 默认打包数量
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
using RimWorld;
|
||||
using System.Linq;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class JobGiver_WulaGetEnergy : ThinkNode_JobGiver
|
||||
{
|
||||
public float minEnergyLevelPercentage = 0.3f;
|
||||
public float maxEnergyLevelPercentage = 1.0f;
|
||||
|
||||
public float emergencyPriority = 9.5f;
|
||||
|
||||
public override float GetPriority(Pawn pawn)
|
||||
{
|
||||
var energyNeed = pawn.needs.TryGetNeed<Need_WulaEnergy>();
|
||||
if (energyNeed == null)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// 如果能量已充满,则不需要充电
|
||||
if (energyNeed.CurLevel >= energyNeed.MaxLevel)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// 如果Pawn正在执行充电Job,并且能量尚未充满,则保持高优先级
|
||||
if ((pawn.CurJobDef == JobDefOf.LayDown ||
|
||||
pawn.CurJobDef == DefDatabase<JobDef>.GetNamed("WULA_IngestWulaEnergy")) &&
|
||||
energyNeed.CurLevel < energyNeed.MaxLevel)
|
||||
{
|
||||
return emergencyPriority; // 保持高优先级,直到充满
|
||||
}
|
||||
|
||||
// 如果能量低于阈值,则需要充电
|
||||
if (energyNeed.CurLevelPercentage < minEnergyLevelPercentage)
|
||||
{
|
||||
return emergencyPriority;
|
||||
}
|
||||
|
||||
return 0f; // 否则,不需要充电,返回0
|
||||
}
|
||||
|
||||
protected override Job TryGiveJob(Pawn pawn)
|
||||
{
|
||||
var energyNeed = pawn.needs.TryGetNeed<Need_WulaEnergy>();
|
||||
if (energyNeed == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (energyNeed.CurLevelPercentage >= maxEnergyLevelPercentage)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!TryFindBestEnergySourceFor(pawn, out var energySource))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (energySource is Building_Bed)
|
||||
{
|
||||
return JobMaker.MakeJob(JobDefOf.LayDown, energySource);
|
||||
}
|
||||
|
||||
var job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("WULA_IngestWulaEnergy"), energySource);
|
||||
job.count = 1;
|
||||
return job;
|
||||
}
|
||||
|
||||
private bool TryFindBestEnergySourceFor(Pawn pawn, out Thing energySource)
|
||||
{
|
||||
// 优先寻找可用的充电床
|
||||
energySource = FindChargingBed(pawn);
|
||||
if (energySource != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// No bed found, now consider consumables.
|
||||
// Check for the hediff BEFORE searching for consumables.
|
||||
if (pawn.health.hediffSet.HasHediff(HediffDef.Named("WULA_ChargingHediff")))
|
||||
{
|
||||
energySource = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 优先从背包中寻找
|
||||
Thing thing = pawn.inventory.innerContainer.FirstOrFallback(t => t.def.GetModExtension<ThingDefExtension_EnergySource>() != null && t.IngestibleNow);
|
||||
if (thing != null)
|
||||
{
|
||||
energySource = thing;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 否则,在地图上寻找
|
||||
energySource = GenClosest.ClosestThingReachable(
|
||||
pawn.Position,
|
||||
pawn.Map,
|
||||
ThingRequest.ForGroup(ThingRequestGroup.HaulableEver),
|
||||
PathEndMode.ClosestTouch,
|
||||
TraverseParms.For(pawn),
|
||||
9999f,
|
||||
t => t.def.GetModExtension<ThingDefExtension_EnergySource>() != null && t.IngestibleNow && !t.IsForbidden(pawn) && pawn.CanReserve(t)
|
||||
);
|
||||
|
||||
return energySource != null;
|
||||
}
|
||||
|
||||
private Building_Bed FindChargingBed(Pawn pawn)
|
||||
{
|
||||
// 寻找附近可用的 WULA_Charging_Station_Synth
|
||||
Building_Bed bed = (Building_Bed)GenClosest.ClosestThingReachable(
|
||||
pawn.Position,
|
||||
pawn.Map,
|
||||
ThingRequest.ForGroup(ThingRequestGroup.BuildingArtificial),
|
||||
PathEndMode.InteractionCell,
|
||||
TraverseParms.For(pawn),
|
||||
9999f,
|
||||
b =>
|
||||
{
|
||||
if (!(b is Building_Bed bed_internal)) return false;
|
||||
|
||||
if (bed_internal.GetComp<CompChargingBed>() == null) return false;
|
||||
|
||||
var powerComp = bed_internal.GetComp<CompPowerTrader>();
|
||||
|
||||
// A pawn can use a bed if:
|
||||
// 1. It has power.
|
||||
// 2. Its prisoner status matches the pawn's.
|
||||
// 3. It's not a medical bed.
|
||||
// 4. The pawn can reserve it (checks for ownership, forbidden, etc.)
|
||||
return powerComp != null &&
|
||||
powerComp.PowerOn &&
|
||||
bed_internal.ForPrisoners == pawn.IsPrisoner &&
|
||||
!bed_internal.Medical &&
|
||||
pawn.CanReserve(bed_internal);
|
||||
}
|
||||
);
|
||||
return bed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using RimWorld; // For JobDefOf, ThingDefOf, StatDefOf
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class JobGiver_WulaPackEnergy : ThinkNode_JobGiver
|
||||
{
|
||||
public float packEnergyThreshold = 0.5f; // 默认打包能量阈值
|
||||
public int packEnergyCount = 2; // 默认打包数量
|
||||
|
||||
// 定义乌拉能量核心的ThingDef
|
||||
private static ThingDef WULA_Charge_Cube_Def => ThingDef.Named("WULA_Charge_Cube");
|
||||
|
||||
public override ThinkNode DeepCopy(bool resolve = true)
|
||||
{
|
||||
JobGiver_WulaPackEnergy obj = (JobGiver_WulaPackEnergy)base.DeepCopy(resolve);
|
||||
obj.packEnergyThreshold = packEnergyThreshold;
|
||||
obj.packEnergyCount = packEnergyCount;
|
||||
return obj;
|
||||
}
|
||||
|
||||
protected override Job TryGiveJob(Pawn pawn)
|
||||
{
|
||||
if (pawn.inventory == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查背包中是否有足够的能量核心,这里可以根据Need_WulaEnergy的当前值来判断是否需要打包
|
||||
// 简化逻辑:如果能量低于某个阈值,并且背包中没有能量核心,则尝试打包
|
||||
Need_WulaEnergy energyNeed = pawn.needs.TryGetNeed<Need_WulaEnergy>();
|
||||
if (energyNeed == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 只有当能量低于阈值,并且背包中能量核心数量少于2个时,才尝试打包
|
||||
if (energyNeed.CurLevelPercentage > packEnergyThreshold || pawn.inventory.innerContainer.TotalStackCountOfDef(WULA_Charge_Cube_Def) >= 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查是否超重
|
||||
if (MassUtility.IsOverEncumbered(pawn))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 寻找地图上可触及的WULA_Charge_Cube
|
||||
Thing thing = GenClosest.ClosestThing_Regionwise_ReachablePrioritized(
|
||||
pawn.Position,
|
||||
pawn.Map,
|
||||
ThingRequest.ForDef(WULA_Charge_Cube_Def), // 只寻找WULA_Charge_Cube
|
||||
PathEndMode.ClosestTouch,
|
||||
TraverseParms.For(pawn),
|
||||
20f, // 搜索距离
|
||||
delegate(Thing t)
|
||||
{
|
||||
// 检查物品是否被禁止,是否可预留,是否社交得体
|
||||
return !t.IsForbidden(pawn) && pawn.CanReserve(t) && t.IsSociallyProper(pawn);
|
||||
},
|
||||
(Thing x) => 0f // 优先级,这里可以根据距离或其他因素调整
|
||||
);
|
||||
|
||||
if (thing == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 计算需要打包的数量,限制在1到2个
|
||||
int countToTake = Mathf.Min(thing.stackCount, 2); // 限制为最多2个
|
||||
if (WULA_Charge_Cube_Def != null)
|
||||
{
|
||||
countToTake = Mathf.Min(countToTake, WULA_Charge_Cube_Def.stackLimit);
|
||||
}
|
||||
|
||||
// 创建TakeInventory Job
|
||||
Job job = JobMaker.MakeJob(JobDefOf.TakeInventory, thing);
|
||||
job.count = countToTake;
|
||||
return job;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class NeedDefExtension_Energy : DefModExtension
|
||||
{
|
||||
// 能量每天的消耗值
|
||||
public float fallPerDay = 1.6f;
|
||||
// 能量上限
|
||||
public float maxLevel = 1.0f;
|
||||
// 运送能量的阈值
|
||||
public float deliverEnergyThreshold = 0.5f;
|
||||
// 自动摄取能量的阈值
|
||||
public float autoIngestThreshold = 0.5f;
|
||||
}
|
||||
}
|
||||
117
Source/WulaFallenEmpire/Pawn/WULA_Energy/Need_WulaEnergy.cs
Normal file
117
Source/WulaFallenEmpire/Pawn/WULA_Energy/Need_WulaEnergy.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using WulaFallenEmpire;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class Need_WulaEnergy : Need
|
||||
{
|
||||
private NeedDefExtension_Energy ext;
|
||||
|
||||
private NeedDefExtension_Energy Ext
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ext == null)
|
||||
{
|
||||
if (def == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ext = def.GetModExtension<NeedDefExtension_Energy>();
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
|
||||
private float EnergyFallPerTick
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Ext != null)
|
||||
{
|
||||
// 从XML读取每天消耗值,并转换为每tick消耗值,并应用StatDef因子
|
||||
return (Ext.fallPerDay / 60000f) * pawn.GetStatValue(WulaStatDefOf.WulaEnergyFallRateFactor);
|
||||
}
|
||||
// 如果XML中没有定义,则使用一个默认值
|
||||
return 2.6666667E-05f;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsShutdown => CurLevel <= 0.01f;
|
||||
|
||||
public override int GUIChangeArrow
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsFrozen) return 0;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public override float MaxLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Ext != null)
|
||||
{
|
||||
// 应用StatDef偏移量
|
||||
return Ext.maxLevel + pawn.GetStatValue(WulaStatDefOf.WulaEnergyMaxLevelOffset);
|
||||
}
|
||||
return 1f;
|
||||
}
|
||||
}
|
||||
|
||||
public Need_WulaEnergy(Pawn pawn) : base(pawn)
|
||||
{
|
||||
}
|
||||
|
||||
public override void NeedInterval()
|
||||
{
|
||||
if (!IsFrozen)
|
||||
{
|
||||
CurLevel -= EnergyFallPerTick * 150f;
|
||||
}
|
||||
|
||||
if (IsShutdown)
|
||||
{
|
||||
HealthUtility.AdjustSeverity(pawn, HediffDef.Named("WULA_Shutdown"), 0.05f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Hediff hediff = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("WULA_Shutdown"));
|
||||
if (hediff != null)
|
||||
{
|
||||
pawn.health.RemoveHediff(hediff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetInitialLevel()
|
||||
{
|
||||
CurLevelPercentage = 1.0f;
|
||||
}
|
||||
|
||||
public override string GetTipString()
|
||||
{
|
||||
return (LabelCap + ": " + CurLevelPercentage.ToStringPercent()).Colorize(ColoredText.TipSectionTitleColor) + "\n" +
|
||||
def.description;
|
||||
}
|
||||
|
||||
public override void DrawOnGUI(Rect rect, int maxThresholdMarkers = int.MaxValue, float customMargin = -1f, bool drawArrows = true, bool doTooltip = true, Rect? rectForTooltip = null, bool drawLabel = true)
|
||||
{
|
||||
if (threshPercents == null)
|
||||
{
|
||||
threshPercents = new System.Collections.Generic.List<float>();
|
||||
}
|
||||
threshPercents.Clear();
|
||||
base.DrawOnGUI(rect, maxThresholdMarkers, customMargin, drawArrows, doTooltip, rectForTooltip, drawLabel);
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class ThingDefExtension_EnergySource : DefModExtension
|
||||
{
|
||||
public float energyAmount = 1.0f; // Amount of energy this item provides
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class WorkGiverDefExtension_FeedWula : DefModExtension
|
||||
{
|
||||
public float feedThreshold = 0.25f;
|
||||
public ThingDef energySourceDef;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class WorkGiver_FeedWulaPatient : WorkGiver_Scanner
|
||||
{
|
||||
public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.Pawn);
|
||||
|
||||
public override PathEndMode PathEndMode => PathEndMode.ClosestTouch;
|
||||
|
||||
public override Danger MaxPathDanger(Pawn pawn) => Danger.Deadly;
|
||||
|
||||
public override IEnumerable<Thing> PotentialWorkThingsGlobal(Pawn pawn)
|
||||
{
|
||||
return pawn.Map.mapPawns.AllPawns.Where(p => p.needs.TryGetNeed<Need_WulaEnergy>() != null && p.InBed());
|
||||
}
|
||||
|
||||
public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
|
||||
{
|
||||
if (!(t is Pawn patient) || patient == pawn)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果病患正在充能,则不需要喂食
|
||||
if (patient.health.hediffSet.HasHediff(DefDatabase<HediffDef>.GetNamed("WULA_ChargingHediff")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Need_WulaEnergy energyNeed = patient.needs.TryGetNeed<Need_WulaEnergy>();
|
||||
var extension = def.GetModExtension<WorkGiverDefExtension_FeedWula>();
|
||||
if (energyNeed == null || energyNeed.CurLevelPercentage >= extension.feedThreshold)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// A Wula patient should be fed if they are in bed. If the job is not forced, they must also be unable to move.
|
||||
if (!patient.InBed() || (!forced && patient.health.capacities.CapableOf(PawnCapacityDefOf.Moving)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pawn.CanReserveAndReach(patient, PathEndMode.Touch, Danger.Deadly, 1, -1, null, forced))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryFindBestEnergySourceFor(pawn, patient, out _, out _))
|
||||
{
|
||||
JobFailReason.Is("NoWulaEnergyToFeed".Translate(patient.LabelShort, patient));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
|
||||
{
|
||||
Pawn patient = (Pawn)t;
|
||||
if (TryFindBestEnergySourceFor(pawn, patient, out Thing energySource, out _))
|
||||
{
|
||||
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("WULA_FeedWulaPatient"), energySource, patient);
|
||||
job.count = 1;
|
||||
return job;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool TryFindBestEnergySourceFor(Pawn getter, Pawn eater, out Thing energySource, out ThingDef energyDef)
|
||||
{
|
||||
energySource = null;
|
||||
energyDef = null;
|
||||
|
||||
var allowedThings = getter.Map.listerThings.ThingsInGroup(ThingRequestGroup.HaulableEver)
|
||||
.Where(x => x.def.GetModExtension<ThingDefExtension_EnergySource>() != null);
|
||||
|
||||
Thing thing = GenClosest.ClosestThing_Global(eater.Position, allowedThings, 99999f,
|
||||
t => t.IngestibleNow && !t.IsForbidden(getter) && getter.CanReserve(t));
|
||||
|
||||
if (thing != null)
|
||||
{
|
||||
energySource = thing;
|
||||
energyDef = thing.def;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class WorkGiver_Warden_DeliverEnergy : WorkGiver_Scanner
|
||||
{
|
||||
private WorkGiverDefExtension_FeedWula ext;
|
||||
|
||||
private WorkGiverDefExtension_FeedWula Ext
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ext == null)
|
||||
{
|
||||
ext = def.GetModExtension<WorkGiverDefExtension_FeedWula>();
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
|
||||
public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForDef(ThingDef.Named("WulaSpecies"));
|
||||
|
||||
public override PathEndMode PathEndMode => PathEndMode.ClosestTouch;
|
||||
|
||||
public override Danger MaxPathDanger(Pawn pawn) => Danger.Deadly;
|
||||
|
||||
public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
|
||||
{
|
||||
Pawn prisoner = t as Pawn;
|
||||
|
||||
if (prisoner == null || prisoner == pawn || !prisoner.IsPrisonerOfColony || !prisoner.guest.CanBeBroughtFood)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Need_WulaEnergy energyNeed = prisoner.needs.TryGetNeed<Need_WulaEnergy>();
|
||||
if (energyNeed == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (energyNeed.CurLevelPercentage > Ext.feedThreshold)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (WardenFeedUtility.ShouldBeFed(prisoner))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pawn.CanReserveAndReach(prisoner, PathEndMode.Touch, Danger.Deadly, 1, -1, null, forced))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Ext == null || Ext.energySourceDef == null)
|
||||
{
|
||||
Log.ErrorOnce("WorkGiver_Warden_DeliverEnergy is missing the DefModExtension with a valid energySourceDef.", def.GetHashCode());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FindBestEnergySourceFor(pawn, prisoner, out _, out _))
|
||||
{
|
||||
JobFailReason.Is("NoFood".Translate());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
|
||||
{
|
||||
Pawn prisoner = (Pawn)t;
|
||||
if (FindBestEnergySourceFor(pawn, prisoner, out Thing energySource, out _))
|
||||
{
|
||||
Job job = JobMaker.MakeJob(JobDefOf.DeliverFood, energySource, prisoner);
|
||||
job.count = 1;
|
||||
job.targetC = RCellFinder.SpotToChewStandingNear(prisoner, energySource);
|
||||
return job;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool FindBestEnergySourceFor(Pawn getter, Pawn eater, out Thing foodSource, out ThingDef foodDef)
|
||||
{
|
||||
foodSource = null;
|
||||
foodDef = null;
|
||||
|
||||
if (Ext == null || Ext.energySourceDef == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there's already an energy source in the eater's room that the eater can reach and use.
|
||||
Thing existingEnergyInRoom = GenClosest.ClosestThingReachable(
|
||||
eater.Position, // Start search from eater's position
|
||||
eater.Map,
|
||||
ThingRequest.ForDef(Ext.energySourceDef),
|
||||
PathEndMode.OnCell,
|
||||
TraverseParms.For(eater, Danger.Deadly, TraverseMode.ByPawn, false), // Use eater's traverse parms
|
||||
9999f,
|
||||
(Thing x) => !x.IsForbidden(eater) && eater.CanReserve(x) && x.GetRoom() == eater.GetRoom()
|
||||
);
|
||||
|
||||
if (existingEnergyInRoom != null)
|
||||
{
|
||||
// If there's already an energy source in the room, no need for the warden to bring another.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Search for an energy source anywhere, now that we've confirmed none are in the room.
|
||||
foodSource = GenClosest.ClosestThingReachable(
|
||||
getter.Position,
|
||||
getter.Map,
|
||||
ThingRequest.ForDef(Ext.energySourceDef),
|
||||
PathEndMode.OnCell,
|
||||
TraverseParms.For(getter),
|
||||
9999f,
|
||||
(Thing x) => !x.IsForbidden(getter) && getter.CanReserve(x)
|
||||
);
|
||||
|
||||
if (foodSource != null)
|
||||
{
|
||||
foodDef = foodSource.def;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using System.Linq;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class WorkGiver_Warden_FeedWula : WorkGiver_Scanner
|
||||
{
|
||||
public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.Pawn);
|
||||
|
||||
public override PathEndMode PathEndMode => PathEndMode.ClosestTouch;
|
||||
|
||||
public override Danger MaxPathDanger(Pawn pawn) => Danger.Deadly;
|
||||
|
||||
public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
|
||||
{
|
||||
if (!(t is Pawn prisoner) || pawn == prisoner)
|
||||
return false;
|
||||
|
||||
if (!ShouldFeed(pawn, prisoner))
|
||||
return false;
|
||||
|
||||
Need_WulaEnergy energyNeed = prisoner.needs.TryGetNeed<Need_WulaEnergy>();
|
||||
var extension = def.GetModExtension<WorkGiverDefExtension_FeedWula>();
|
||||
if (energyNeed == null || energyNeed.CurLevelPercentage >= extension.feedThreshold)
|
||||
return false;
|
||||
|
||||
if (prisoner.health.hediffSet.HasHediff(DefDatabase<HediffDef>.GetNamed("WULA_ChargingHediff")))
|
||||
return false;
|
||||
|
||||
if (!prisoner.InBed() || (!forced && prisoner.health.capacities.CapableOf(PawnCapacityDefOf.Moving)))
|
||||
return false;
|
||||
|
||||
if (!pawn.CanReserveAndReach(prisoner, PathEndMode.Touch, Danger.Deadly, 1, -1, null, forced))
|
||||
return false;
|
||||
|
||||
if (!TryFindBestEnergySourceFor(pawn, prisoner, out _, out _))
|
||||
{
|
||||
JobFailReason.Is("NoWulaEnergyToFeed".Translate(prisoner.LabelShort, prisoner));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
|
||||
{
|
||||
Pawn prisoner = (Pawn)t;
|
||||
if (TryFindBestEnergySourceFor(pawn, prisoner, out Thing energySource, out _))
|
||||
{
|
||||
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("WULA_FeedWulaPatient"), energySource, prisoner);
|
||||
job.count = 1;
|
||||
return job;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool ShouldFeed(Pawn warden, Pawn prisoner)
|
||||
{
|
||||
return prisoner.IsPrisonerOfColony && prisoner.guest.CanBeBroughtFood && prisoner.needs.TryGetNeed<Need_WulaEnergy>() != null;
|
||||
}
|
||||
|
||||
private bool TryFindBestEnergySourceFor(Pawn getter, Pawn eater, out Thing energySource, out ThingDef energyDef)
|
||||
{
|
||||
energySource = null;
|
||||
energyDef = null;
|
||||
|
||||
var allowedThings = getter.Map.listerThings.ThingsInGroup(ThingRequestGroup.HaulableEver)
|
||||
.Where(x => x.def.GetModExtension<ThingDefExtension_EnergySource>() != null);
|
||||
|
||||
Thing thing = GenClosest.ClosestThing_Global(eater.Position, allowedThings, 99999f,
|
||||
t => t.IngestibleNow && !t.IsForbidden(getter) && getter.CanReserve(t));
|
||||
|
||||
if (thing != null)
|
||||
{
|
||||
energySource = thing;
|
||||
energyDef = thing.def;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class WulaCaravanEnergyDef : Def
|
||||
{
|
||||
public float consumeThreshold = 0.9f;
|
||||
public string energyItemDefName;
|
||||
public string hediffDefNameToAdd;
|
||||
}
|
||||
}
|
||||
17
Source/WulaFallenEmpire/Pawn/WULA_Energy/WulaStatDefOf.cs
Normal file
17
Source/WulaFallenEmpire/Pawn/WULA_Energy/WulaStatDefOf.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
[DefOf]
|
||||
public static class WulaStatDefOf
|
||||
{
|
||||
public static StatDef WulaEnergyMaxLevelOffset;
|
||||
public static StatDef WulaEnergyFallRateFactor;
|
||||
|
||||
static WulaStatDefOf()
|
||||
{
|
||||
DefOfHelper.EnsureInitializedInCtor(typeof(WulaStatDefOf));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_MaintenanceCycle : CompProperties_BiosculpterPod_BaseCycle
|
||||
{
|
||||
public HediffDef hediffToRemove;
|
||||
|
||||
public CompProperties_MaintenanceCycle()
|
||||
{
|
||||
compClass = typeof(CompMaintenanceCycle);
|
||||
}
|
||||
}
|
||||
|
||||
public class CompMaintenanceCycle : CompBiosculpterPod_Cycle
|
||||
{
|
||||
public new CompProperties_MaintenanceCycle Props => (CompProperties_MaintenanceCycle)props;
|
||||
|
||||
public override void CycleCompleted(Pawn pawn)
|
||||
{
|
||||
if (pawn == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Hediff hediff = pawn.health.hediffSet.GetFirstHediffOfDef(Props.hediffToRemove);
|
||||
if (hediff != null)
|
||||
{
|
||||
hediff.Severity = 0f;
|
||||
Messages.Message("WULA_MaintenanceCycleComplete".Translate(pawn.Named("PAWN")), pawn, MessageTypeDefOf.PositiveEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
using RimWorld;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_MaintenancePod : CompProperties
|
||||
{
|
||||
public SoundDef enterSound;
|
||||
public SoundDef exitSound;
|
||||
public EffecterDef operatingEffecter;
|
||||
public int baseDurationTicks = 60000;
|
||||
public float ticksPerSeverity = 0f;
|
||||
public float powerConsumptionRunning = 250f;
|
||||
public float powerConsumptionIdle = 50f;
|
||||
public HediffDef hediffToRemove;
|
||||
public float componentCostPerSeverity = 1f;
|
||||
public int baseComponentCost = 0;
|
||||
public float minSeverityToMaintain = 0.75f;
|
||||
public float hediffSeverityAfterCycle = 0.01f;
|
||||
|
||||
public CompProperties_MaintenancePod()
|
||||
{
|
||||
compClass = typeof(CompMaintenancePod);
|
||||
}
|
||||
}
|
||||
|
||||
[StaticConstructorOnStartup]
|
||||
public class CompMaintenancePod : ThingComp, IThingHolder
|
||||
{
|
||||
// ===================== Fields =====================
|
||||
private ThingOwner innerContainer;
|
||||
private CompPowerTrader powerComp;
|
||||
private CompRefuelable refuelableComp;
|
||||
private int ticksRemaining;
|
||||
private MaintenancePodState state = MaintenancePodState.Idle;
|
||||
|
||||
private static readonly Texture2D CancelIcon = ContentFinder<Texture2D>.Get("UI/Designators/Cancel");
|
||||
private static readonly Texture2D EnterIcon = ContentFinder<Texture2D>.Get("UI/Commands/PodEject");
|
||||
|
||||
// ===================== Properties =====================
|
||||
public CompProperties_MaintenancePod Props => (CompProperties_MaintenancePod)props;
|
||||
public MaintenancePodState State => state;
|
||||
public Pawn Occupant => innerContainer.FirstOrDefault() as Pawn;
|
||||
public bool PowerOn => powerComp != null && powerComp.PowerOn;
|
||||
|
||||
public float RequiredComponents(Pawn pawn)
|
||||
{
|
||||
if (pawn == null || Props.hediffToRemove == null) return Props.baseComponentCost;
|
||||
Hediff hediff = pawn.health.hediffSet.GetFirstHediffOfDef(Props.hediffToRemove);
|
||||
if (hediff == null) return Props.baseComponentCost;
|
||||
return Props.baseComponentCost + (int)(hediff.Severity * Props.componentCostPerSeverity);
|
||||
}
|
||||
|
||||
public int RequiredDuration(Pawn pawn)
|
||||
{
|
||||
if (pawn == null || Props.hediffToRemove == null) return Props.baseDurationTicks;
|
||||
Hediff hediff = pawn.health.hediffSet.GetFirstHediffOfDef(Props.hediffToRemove);
|
||||
if (hediff == null) return Props.baseDurationTicks;
|
||||
return Props.baseDurationTicks + (int)(hediff.Severity * Props.ticksPerSeverity);
|
||||
}
|
||||
|
||||
// ===================== Setup =====================
|
||||
public CompMaintenancePod()
|
||||
{
|
||||
innerContainer = new ThingOwner<Thing>(this, false, LookMode.Deep);
|
||||
}
|
||||
|
||||
public override void PostSpawnSetup(bool respawningAfterLoad)
|
||||
{
|
||||
base.PostSpawnSetup(respawningAfterLoad);
|
||||
powerComp = parent.TryGetComp<CompPowerTrader>();
|
||||
refuelableComp = parent.TryGetComp<CompRefuelable>();
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
Scribe_Values.Look(ref state, "state", MaintenancePodState.Idle);
|
||||
Scribe_Values.Look(ref ticksRemaining, "ticksRemaining", 0);
|
||||
Scribe_Deep.Look(ref innerContainer, "innerContainer", this);
|
||||
}
|
||||
|
||||
public override void PostDestroy(DestroyMode mode, Map previousMap)
|
||||
{
|
||||
base.PostDestroy(mode, previousMap);
|
||||
// If the pod is deconstructed or destroyed, eject the occupant to prevent deletion.
|
||||
if (mode == DestroyMode.Deconstruct || mode == DestroyMode.KillFinalize)
|
||||
{
|
||||
Log.Warning($"[WulaPodDebug] Pod destroyed (mode: {mode}). Ejecting pawn.");
|
||||
EjectPawn();
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== IThingHolder Implementation =====================
|
||||
public void GetChildHolders(List<IThingHolder> outChildren)
|
||||
{
|
||||
ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings());
|
||||
}
|
||||
|
||||
public ThingOwner GetDirectlyHeldThings()
|
||||
{
|
||||
return innerContainer;
|
||||
}
|
||||
|
||||
// ===================== Core Logic =====================
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
if (!parent.Spawned) return;
|
||||
|
||||
if (state == MaintenancePodState.Running)
|
||||
{
|
||||
if (PowerOn)
|
||||
{
|
||||
ticksRemaining--;
|
||||
if (ticksRemaining <= 0)
|
||||
{
|
||||
CycleFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (powerComp != null)
|
||||
{
|
||||
powerComp.PowerOutput = -(state == MaintenancePodState.Running ? Props.powerConsumptionRunning : Props.powerConsumptionIdle);
|
||||
}
|
||||
}
|
||||
|
||||
public void StartCycle(Pawn pawn)
|
||||
{
|
||||
Log.Warning($"[WulaPodDebug] StartCycle called for pawn: {pawn.LabelShortCap}");
|
||||
float required = RequiredComponents(pawn);
|
||||
if (refuelableComp.Fuel < required)
|
||||
{
|
||||
Log.Error($"[WulaPodDebug] ERROR: Tried to start cycle for {pawn.LabelShort} without enough components.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (required > 0)
|
||||
{
|
||||
refuelableComp.ConsumeFuel(required);
|
||||
}
|
||||
|
||||
Log.Warning($"[WulaPodDebug] Pawn state before action: holdingOwner is {(pawn.holdingOwner == null ? "NULL" : "NOT NULL")}, Spawned is {pawn.Spawned}");
|
||||
|
||||
// THE ACTUAL FIX: A pawn, whether held or not, must be despawned before being put in a container.
|
||||
if (pawn.Spawned)
|
||||
{
|
||||
Log.Warning($"[WulaPodDebug] Pawn is spawned. Despawning...");
|
||||
pawn.DeSpawn(DestroyMode.Vanish);
|
||||
}
|
||||
Log.Warning($"[WulaPodDebug] Attempting to add/transfer pawn to container.");
|
||||
innerContainer.TryAddOrTransfer(pawn);
|
||||
|
||||
|
||||
state = MaintenancePodState.Running;
|
||||
ticksRemaining = RequiredDuration(pawn);
|
||||
Log.Warning($"[WulaPodDebug] Cycle started. Ticks remaining: {ticksRemaining}");
|
||||
}
|
||||
|
||||
private void CycleFinished()
|
||||
{
|
||||
Pawn occupant = Occupant;
|
||||
Log.Warning($"[WulaPodDebug] CycleFinished. Occupant: {(occupant == null ? "NULL" : occupant.LabelShortCap)}");
|
||||
if (occupant == null)
|
||||
{
|
||||
Log.Error("[WulaPodDebug] ERROR: Maintenance cycle finished, but no one was inside.");
|
||||
state = MaintenancePodState.Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Fix the maintenance hediff
|
||||
bool maintenanceDone = false;
|
||||
if (Props.hediffToRemove != null)
|
||||
{
|
||||
Hediff hediff = occupant.health.hediffSet.GetFirstHediffOfDef(Props.hediffToRemove);
|
||||
if (hediff != null)
|
||||
{
|
||||
hediff.Severity = Props.hediffSeverityAfterCycle;
|
||||
Messages.Message("WULA_MaintenanceComplete".Translate(occupant.Named("PAWN")), occupant, MessageTypeDefOf.PositiveEvent);
|
||||
maintenanceDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Heal all other injuries and missing parts
|
||||
int injuriesHealed = 0;
|
||||
// Loop until no more health conditions can be fixed
|
||||
while (HealthUtility.TryGetWorstHealthCondition(occupant, out var hediffToFix, out var _))
|
||||
{
|
||||
// Ensure we don't try to "heal" the maintenance hediff itself, as it's handled separately.
|
||||
if (hediffToFix != null && hediffToFix.def == Props.hediffToRemove)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Store the state before attempting to fix
|
||||
int initialHediffCount = occupant.health.hediffSet.hediffs.Count;
|
||||
var hediffsBefore = new HashSet<Hediff>(occupant.health.hediffSet.hediffs);
|
||||
|
||||
// Attempt to fix the worst condition
|
||||
HealthUtility.FixWorstHealthCondition(occupant);
|
||||
|
||||
// Check if a change actually occurred
|
||||
bool conditionFixed = initialHediffCount > occupant.health.hediffSet.hediffs.Count ||
|
||||
!hediffsBefore.SetEquals(occupant.health.hediffSet.hediffs);
|
||||
|
||||
if (conditionFixed)
|
||||
{
|
||||
injuriesHealed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If FixWorstHealthCondition did nothing, it means it can't handle
|
||||
// the current worst condition. We must break to avoid an infinite loop.
|
||||
Log.Warning($"[WulaPodDebug] Halting healing loop. FixWorstHealthCondition did not resolve: {hediffToFix?.LabelCap ?? "a missing part"}.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (injuriesHealed > 0)
|
||||
{
|
||||
Messages.Message("WULA_MaintenanceHealedAllWounds".Translate(occupant.Named("PAWN")), occupant, MessageTypeDefOf.PositiveEvent);
|
||||
}
|
||||
else if (!maintenanceDone)
|
||||
{
|
||||
// If nothing was done at all, give a neutral message
|
||||
Messages.Message("WULA_MaintenanceNoEffect".Translate(occupant.Named("PAWN")), occupant, MessageTypeDefOf.NeutralEvent);
|
||||
}
|
||||
|
||||
EjectPawn();
|
||||
}
|
||||
|
||||
public void EjectPawn(bool interrupted = false)
|
||||
{
|
||||
Pawn occupant = Occupant;
|
||||
Log.Warning($"[WulaPodDebug] EjectPawn. Occupant: {(occupant == null ? "NULL" : occupant.LabelShortCap)}");
|
||||
if (occupant != null)
|
||||
{
|
||||
Map mapToUse = parent.Map ?? Find.CurrentMap;
|
||||
if (mapToUse == null)
|
||||
{
|
||||
// Try to find the map from nearby things
|
||||
mapToUse = GenClosest.ClosestThing_Global(occupant.Position, Gen.YieldSingle(parent), 99999f, (thing) => thing.Map != null)?.Map;
|
||||
}
|
||||
|
||||
if (mapToUse != null)
|
||||
{
|
||||
innerContainer.TryDropAll(parent.InteractionCell, mapToUse, ThingPlaceMode.Near);
|
||||
if (Props.exitSound != null)
|
||||
{
|
||||
SoundStarter.PlayOneShot(Props.exitSound, new TargetInfo(parent.Position, mapToUse));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"[WulaPodDebug] EjectPawn FAILED: No valid map found to eject {occupant.LabelShortCap}. The pawn will be lost.");
|
||||
}
|
||||
|
||||
// Additional logic to handle occupant if needed
|
||||
if (interrupted)
|
||||
{
|
||||
occupant.needs?.mood.thoughts.memories.TryGainMemory(ThoughtDefOf.SoakingWet);
|
||||
occupant.health?.AddHediff(HediffDefOf.BiosculptingSickness);
|
||||
}
|
||||
}
|
||||
innerContainer.Clear();
|
||||
state = MaintenancePodState.Idle;
|
||||
Log.Warning($"[WulaPodDebug] EjectPawn finished. State set to Idle.");
|
||||
}
|
||||
|
||||
// ===================== UI & Gizmos =====================
|
||||
public override string CompInspectStringExtra()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("WULA_MaintenancePod_Status".Translate() + ": " + $"WULA_MaintenancePod_State_{state}".Translate());
|
||||
|
||||
if (state == MaintenancePodState.Running)
|
||||
{
|
||||
if (Occupant != null)
|
||||
{
|
||||
sb.AppendLine("Contains".Translate() + ": " + Occupant.NameShortColored.Resolve());
|
||||
}
|
||||
sb.AppendLine("TimeLeft".Translate() + ": " + ticksRemaining.ToStringTicksToPeriod());
|
||||
}
|
||||
|
||||
if (!PowerOn)
|
||||
{
|
||||
sb.AppendLine("NoPower".Translate().Colorize(Color.red));
|
||||
}
|
||||
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
||||
{
|
||||
foreach (var gizmo in base.CompGetGizmosExtra())
|
||||
{
|
||||
yield return gizmo;
|
||||
}
|
||||
|
||||
if (state == MaintenancePodState.Idle && PowerOn)
|
||||
{
|
||||
var enterCommand = new Command_Action
|
||||
{
|
||||
defaultLabel = "WULA_MaintenancePod_Enter".Translate(),
|
||||
defaultDesc = "WULA_MaintenancePod_EnterDesc".Translate(),
|
||||
icon = EnterIcon,
|
||||
action = () =>
|
||||
{
|
||||
List<FloatMenuOption> options = GetPawnOptions();
|
||||
if (options.Any())
|
||||
{
|
||||
Find.WindowStack.Add(new FloatMenu(options));
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages.Message("WULA_MaintenancePod_NoOneNeeds".Translate(), MessageTypeDefOf.RejectInput);
|
||||
}
|
||||
}
|
||||
};
|
||||
yield return enterCommand;
|
||||
}
|
||||
|
||||
if (state == MaintenancePodState.Running)
|
||||
{
|
||||
var cancelCommand = new Command_Action
|
||||
{
|
||||
defaultLabel = "CommandCancelConstructionLabel".Translate(),
|
||||
defaultDesc = "WULA_MaintenancePod_CancelDesc".Translate(),
|
||||
icon = CancelIcon,
|
||||
action = () =>
|
||||
{
|
||||
EjectPawn();
|
||||
Messages.Message("WULA_MaintenanceCanceled".Translate(), MessageTypeDefOf.NegativeEvent);
|
||||
}
|
||||
};
|
||||
yield return cancelCommand;
|
||||
}
|
||||
|
||||
// DEV GIZMO
|
||||
if (DebugSettings.godMode && state == MaintenancePodState.Running)
|
||||
{
|
||||
var finishCommand = new Command_Action
|
||||
{
|
||||
defaultLabel = "DEV: Finish Cycle",
|
||||
action = () =>
|
||||
{
|
||||
Log.Warning("[WulaPodDebug] DEV: Force finishing cycle.");
|
||||
CycleFinished();
|
||||
}
|
||||
};
|
||||
yield return finishCommand;
|
||||
}
|
||||
}
|
||||
|
||||
private List<FloatMenuOption> GetPawnOptions()
|
||||
{
|
||||
List<FloatMenuOption> options = new List<FloatMenuOption>();
|
||||
// Now iterates over all pawns on the map, not just colonists.
|
||||
foreach (Pawn p in parent.Map.mapPawns.AllPawns.Where(pawn => pawn.def.defName == "WulaSpecies" || pawn.def.defName == "WulaSpeciesReal"))
|
||||
{
|
||||
if (p.health.hediffSet.HasHediff(Props.hediffToRemove))
|
||||
{
|
||||
// If the pawn is downed or not a free colonist, they need to be brought to the pod.
|
||||
if (p.Downed || !p.IsFreeColonist)
|
||||
{
|
||||
float required = RequiredComponents(p);
|
||||
if (refuelableComp.Fuel < required)
|
||||
{
|
||||
options.Add(new FloatMenuOption(p.LabelShortCap + " (" + p.KindLabel + ", " + "WULA_MaintenancePod_NotEnoughComponents".Translate(required.ToString("F0")) + ")", null));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find colonists who can haul the pawn.
|
||||
var potentialHaulers = parent.Map.mapPawns.FreeColonistsSpawned.Where(colonist =>
|
||||
!colonist.Downed && colonist.CanReserveAndReach(p, PathEndMode.OnCell, Danger.Deadly) && colonist.CanReserveAndReach(parent, PathEndMode.InteractionCell, Danger.Deadly));
|
||||
|
||||
if (!potentialHaulers.Any())
|
||||
{
|
||||
// If no one can haul, then it's unreachable.
|
||||
options.Add(new FloatMenuOption(p.LabelShortCap + " (" + p.KindLabel + ", " + "CannotReach".Translate() + ")", null));
|
||||
}
|
||||
else
|
||||
{
|
||||
Action action = delegate
|
||||
{
|
||||
// Create a menu to select which colonist should do the hauling.
|
||||
var haulerOptions = new List<FloatMenuOption>();
|
||||
foreach (var hauler in potentialHaulers)
|
||||
{
|
||||
haulerOptions.Add(new FloatMenuOption(hauler.LabelCap, delegate
|
||||
{
|
||||
var haulJob = JobMaker.MakeJob(JobDefOf_WULA.WULA_HaulToMaintenancePod, p, parent);
|
||||
haulJob.count = 1;
|
||||
hauler.jobs.TryTakeOrderedJob(haulJob, JobTag.Misc);
|
||||
}));
|
||||
}
|
||||
Find.WindowStack.Add(new FloatMenu(haulerOptions));
|
||||
};
|
||||
options.Add(new FloatMenuOption(p.LabelShortCap + " (" + p.KindLabel + ")", action));
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the pawn is a free colonist and can walk, they can go on their own.
|
||||
else
|
||||
{
|
||||
if (!p.CanReach(parent, PathEndMode.InteractionCell, Danger.Deadly))
|
||||
{
|
||||
options.Add(new FloatMenuOption(p.LabelShortCap + " (" + "CannotReach".Translate() + ")", null));
|
||||
}
|
||||
else
|
||||
{
|
||||
float required = RequiredComponents(p);
|
||||
if (refuelableComp.Fuel >= required)
|
||||
{
|
||||
options.Add(new FloatMenuOption(p.LabelShortCap, () =>
|
||||
{
|
||||
Job job = JobMaker.MakeJob(JobDefOf_WULA.WULA_EnterMaintenancePod, parent);
|
||||
p.jobs.TryTakeOrderedJob(job, JobTag.Misc);
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
options.Add(new FloatMenuOption(p.LabelShortCap + " (" + "WULA_MaintenancePod_NotEnoughComponents".Translate(required.ToString("F0")) + ")", null));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
public enum MaintenancePodState
|
||||
{
|
||||
Idle,
|
||||
Running,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class HediffCompProperties_MaintenanceNeed : HediffCompProperties
|
||||
{
|
||||
public float severityPerDayBeforeThreshold = 0.0f;
|
||||
public float severityPerDayAfterThreshold = 0.0f;
|
||||
public float thresholdDays = 0.0f;
|
||||
|
||||
public HediffCompProperties_MaintenanceNeed()
|
||||
{
|
||||
compClass = typeof(HediffComp_MaintenanceNeed);
|
||||
}
|
||||
}
|
||||
|
||||
public class HediffComp_MaintenanceNeed : HediffComp
|
||||
{
|
||||
private HediffCompProperties_MaintenanceNeed Props => (HediffCompProperties_MaintenanceNeed)props;
|
||||
|
||||
public override void CompPostTick(ref float severityAdjustment)
|
||||
{
|
||||
base.CompPostTick(ref severityAdjustment);
|
||||
|
||||
// We adjust severity once per game day (60000 ticks)
|
||||
if (parent.ageTicks % 60000 == 0)
|
||||
{
|
||||
float ageInDays = (float)parent.ageTicks / 60000f;
|
||||
if (ageInDays < Props.thresholdDays)
|
||||
{
|
||||
severityAdjustment += Props.severityPerDayBeforeThreshold;
|
||||
}
|
||||
else
|
||||
{
|
||||
severityAdjustment += Props.severityPerDayAfterThreshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
[DefOf]
|
||||
public static class JobDefOf_WULA
|
||||
{
|
||||
public static JobDef WULA_EnterMaintenancePod;
|
||||
|
||||
public static JobDef WULA_HaulToMaintenancePod;
|
||||
|
||||
static JobDefOf_WULA()
|
||||
{
|
||||
DefOfHelper.EnsureInitializedInCtor(typeof(JobDefOf_WULA));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using RimWorld;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class JobDriver_EnterMaintenancePod : JobDriver
|
||||
{
|
||||
private const TargetIndex PodIndex = TargetIndex.A;
|
||||
|
||||
protected Thing Pod => job.GetTarget(PodIndex).Thing;
|
||||
|
||||
public override bool TryMakePreToilReservations(bool errorOnFailed)
|
||||
{
|
||||
return pawn.Reserve(Pod, job, 1, -1, null, errorOnFailed);
|
||||
}
|
||||
|
||||
protected override IEnumerable<Toil> MakeNewToils()
|
||||
{
|
||||
Log.Warning($"[WulaPodDebug] JobDriver_EnterMaintenancePod started for pawn: {pawn.LabelShortCap}");
|
||||
this.FailOnDespawnedNullOrForbidden(PodIndex);
|
||||
this.FailOnBurningImmobile(PodIndex);
|
||||
|
||||
var podComp = Pod.TryGetComp<CompMaintenancePod>();
|
||||
this.FailOn(() => podComp == null || podComp.State != MaintenancePodState.Idle || !podComp.PowerOn);
|
||||
|
||||
// Go to the pod's interaction cell
|
||||
Toil goToPod = Toils_Goto.GotoThing(PodIndex, PathEndMode.InteractionCell);
|
||||
goToPod.AddPreInitAction(() => Log.Warning($"[WulaPodDebug] EnterJob: Pawn {pawn.LabelShortCap} is going to the pod."));
|
||||
yield return goToPod;
|
||||
|
||||
// Enter the pod
|
||||
Toil enterToil = new Toil
|
||||
{
|
||||
initAction = () =>
|
||||
{
|
||||
Log.Warning($"[WulaPodDebug] EnterJob: Pawn {pawn.LabelShortCap} has arrived and is entering the pod.");
|
||||
podComp.StartCycle(pawn);
|
||||
},
|
||||
defaultCompleteMode = ToilCompleteMode.Instant
|
||||
};
|
||||
yield return enterToil;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class JobDriver_HaulToMaintenancePod : JobDriver
|
||||
{
|
||||
private const TargetIndex TakeeIndex = TargetIndex.A;
|
||||
private const TargetIndex PodIndex = TargetIndex.B;
|
||||
|
||||
protected Pawn Takee => (Pawn)job.GetTarget(TakeeIndex).Thing;
|
||||
protected Building Pod => (Building)job.GetTarget(PodIndex).Thing;
|
||||
protected CompMaintenancePod PodComp => Pod.TryGetComp<CompMaintenancePod>();
|
||||
|
||||
public override bool TryMakePreToilReservations(bool errorOnFailed)
|
||||
{
|
||||
return pawn.Reserve(Takee, job, 1, -1, null, errorOnFailed)
|
||||
&& pawn.Reserve(Pod, job, 1, -1, null, errorOnFailed);
|
||||
}
|
||||
|
||||
protected override IEnumerable<Toil> MakeNewToils()
|
||||
{
|
||||
Log.Warning($"[WulaPodDebug] JobDriver_HaulToMaintenancePod started. Hauler: {pawn.LabelShortCap}, Takee: {Takee.LabelShortCap}");
|
||||
// Standard failure conditions
|
||||
this.FailOnDestroyedOrNull(TakeeIndex);
|
||||
this.FailOnDestroyedOrNull(PodIndex);
|
||||
this.FailOnAggroMentalStateAndHostile(TakeeIndex);
|
||||
this.FailOn(() => PodComp == null);
|
||||
this.FailOn(() => !pawn.CanReach(Pod, PathEndMode.InteractionCell, Danger.Deadly));
|
||||
this.FailOn(() => !Takee.Downed);
|
||||
|
||||
// Go to the pawn to be rescued
|
||||
Toil goToTakee = Toils_Goto.GotoThing(TakeeIndex, PathEndMode.ClosestTouch)
|
||||
.FailOnDespawnedNullOrForbidden(TakeeIndex)
|
||||
.FailOnDespawnedNullOrForbidden(PodIndex)
|
||||
.FailOnSomeonePhysicallyInteracting(TakeeIndex);
|
||||
goToTakee.AddPreInitAction(() => Log.Warning($"[WulaPodDebug] HaulJob: {pawn.LabelShortCap} is going to pick up {Takee.LabelShortCap}."));
|
||||
yield return goToTakee;
|
||||
|
||||
// Start carrying the pawn
|
||||
Toil startCarrying = Toils_Haul.StartCarryThing(TakeeIndex, false, true, false);
|
||||
startCarrying.AddPreInitAction(() => Log.Warning($"[WulaPodDebug] HaulJob: {pawn.LabelShortCap} is now carrying {Takee.LabelShortCap}."));
|
||||
yield return startCarrying;
|
||||
|
||||
// Go to the maintenance pod
|
||||
Toil goToPod = Toils_Goto.GotoThing(PodIndex, PathEndMode.InteractionCell);
|
||||
goToPod.AddPreInitAction(() => Log.Warning($"[WulaPodDebug] HaulJob: {pawn.LabelShortCap} is hauling {Takee.LabelShortCap} to the pod."));
|
||||
yield return goToPod;
|
||||
|
||||
// Place the pawn inside the pod
|
||||
Toil placeInPod = ToilMaker.MakeToil("PlaceInPod");
|
||||
placeInPod.initAction = delegate
|
||||
{
|
||||
Log.Warning($"[WulaPodDebug] HaulJob: {pawn.LabelShortCap} has arrived and is placing {Takee.LabelShortCap} in the pod.");
|
||||
PodComp.StartCycle(Takee);
|
||||
};
|
||||
placeInPod.defaultCompleteMode = ToilCompleteMode.Instant;
|
||||
yield return placeInPod;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class WorkGiver_DoMaintenance : WorkGiver_Scanner
|
||||
{
|
||||
public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForDef(ThingDef.Named("WULA_MaintenancePod"));
|
||||
|
||||
public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
|
||||
{
|
||||
return pawn.CanReserve(t, 1, -1, null, forced);
|
||||
}
|
||||
|
||||
public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
|
||||
{
|
||||
return JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("WULA_EnterMaintenancePod"), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user