整理一下

This commit is contained in:
2025-08-21 15:39:46 +08:00
parent 82b663c891
commit d0d125d095
65 changed files with 64 additions and 152 deletions

View 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);
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -0,0 +1,10 @@
using Verse;
namespace WulaFallenEmpire
{
public class JobGiverDefExtension_WulaPackEnergy : DefModExtension
{
public float packEnergyThreshold = 0.5f; // 默认打包能量阈值
public int packEnergyCount = 10; // 默认打包数量
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View 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();
}
}
}

View File

@@ -0,0 +1,9 @@
using Verse;
namespace WulaFallenEmpire
{
public class ThingDefExtension_EnergySource : DefModExtension
{
public float energyAmount = 1.0f; // Amount of energy this item provides
}
}

View File

@@ -0,0 +1,10 @@
using Verse;
namespace WulaFallenEmpire
{
public class WorkGiverDefExtension_FeedWula : DefModExtension
{
public float feedThreshold = 0.25f;
public ThingDef energySourceDef;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
using Verse;
namespace WulaFallenEmpire
{
public class WulaCaravanEnergyDef : Def
{
public float consumeThreshold = 0.9f;
public string energyItemDefName;
public string hediffDefNameToAdd;
}
}

View 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));
}
}
}