This commit is contained in:
2025-08-09 20:17:54 +08:00
parent 52d07b6a07
commit 0e63046eca
9 changed files with 124 additions and 90 deletions

Binary file not shown.

View File

@@ -22,4 +22,12 @@
<allowOpportunisticPrefix>true</allowOpportunisticPrefix>
</JobDef>
<JobDef>
<defName>WULA_HaulToMaintenancePod</defName>
<driverClass>WulaFallenEmpire.JobDriver_HaulToMaintenancePod</driverClass>
<reportString>正在将TargetA抬到TargetB。</reportString>
<allowOpportunisticPrefix>true</allowOpportunisticPrefix>
<casualInterruptible>false</casualInterruptible>
</JobDef>
</Defs>

View File

@@ -69,7 +69,8 @@
<targetFuelLevelConfigurable>true</targetFuelLevelConfigurable>
</li>
<li Class="WulaFallenEmpire.CompProperties_MaintenancePod">
<durationTicks>60000</durationTicks> <!-- 1 day -->
<baseDurationTicks>30000</baseDurationTicks> <!-- 0.5 days base time -->
<ticksPerSeverity>30000</ticksPerSeverity> <!-- 0.5 days per 1.0 severity -->
<powerConsumptionRunning>250</powerConsumptionRunning>
<powerConsumptionIdle>5</powerConsumptionIdle>
<hediffToRemove>WULA_Maintenance_Neglect</hediffToRemove>

View File

@@ -16,7 +16,8 @@ namespace WulaFallenEmpire
public SoundDef enterSound;
public SoundDef exitSound;
public EffecterDef operatingEffecter;
public int durationTicks = 60000; // Default to 1 day
public int baseDurationTicks = 60000;
public float ticksPerSeverity = 0f;
public float powerConsumptionRunning = 250f;
public float powerConsumptionIdle = 50f;
public HediffDef hediffToRemove;
@@ -57,6 +58,14 @@ namespace WulaFallenEmpire
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()
{
@@ -130,9 +139,12 @@ namespace WulaFallenEmpire
return;
}
refuelableComp.ConsumeFuel(required);
if (required > 0)
{
refuelableComp.ConsumeFuel(required);
}
state = MaintenancePodState.Running;
ticksRemaining = Props.durationTicks;
ticksRemaining = RequiredDuration(pawn);
// Move pawn inside
pawn.DeSpawn();
@@ -201,7 +213,13 @@ namespace WulaFallenEmpire
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
// Gizmo to order a pawn to enter
// Base gizmos
foreach (var gizmo in base.CompGetGizmosExtra())
{
yield return gizmo;
}
// Gizmo to order a pawn to enter (Right-click menu style)
if (state == MaintenancePodState.Idle && PowerOn)
{
var enterCommand = new Command_Action
@@ -211,26 +229,7 @@ namespace WulaFallenEmpire
icon = EnterIcon,
action = () =>
{
List<FloatMenuOption> options = new List<FloatMenuOption>();
foreach (Pawn p in parent.Map.mapPawns.FreeColonists)
{
if (Props.hediffToRemove != null && p.health.hediffSet.HasHediff(Props.hediffToRemove))
{
float required = RequiredComponents(p);
if (refuelableComp.Fuel >= required)
{
options.Add(new FloatMenuOption(p.LabelShort, () =>
{
Job job = JobMaker.MakeJob(JobDefOf_WULA.WULA_EnterMaintenancePod, parent);
p.jobs.TryTakeOrderedJob(job, JobTag.Misc);
}));
}
else
{
options.Add(new FloatMenuOption(p.LabelShort + " (" + "WULA_MaintenancePod_NotEnoughComponents".Translate(required.ToString("F0")) + ")", null));
}
}
}
List<FloatMenuOption> options = GetPawnOptions();
if (options.Any())
{
Find.WindowStack.Add(new FloatMenu(options));
@@ -261,6 +260,43 @@ namespace WulaFallenEmpire
yield return cancelCommand;
}
}
private List<FloatMenuOption> GetPawnOptions()
{
List<FloatMenuOption> options = new List<FloatMenuOption>();
foreach (Pawn p in parent.Map.mapPawns.FreeColonists.Where(pawn => pawn.def == ThingDefOf_WULA.Wula))
{
if (p.health.hediffSet.HasHediff(Props.hediffToRemove))
{
if (!p.CanReach(parent, PathEndMode.InteractionCell, Danger.Deadly))
{
options.Add(new FloatMenuOption(p.LabelShortCap + " (" + "CannotReach".Translate() + ")", null));
}
else if (p.Downed)
{
// This is handled by the WorkGiver, but we can add a note here if we want.
// options.Add(new FloatMenuOption(p.LabelShortCap + " (" + "Incapacitated".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

View File

@@ -8,6 +8,7 @@ namespace WulaFallenEmpire
{
public static JobDef WULA_LoadComponentsToMaintenancePod;
public static JobDef WULA_EnterMaintenancePod;
public static JobDef WULA_HaulToMaintenancePod;
static JobDefOf_WULA()
{

View File

@@ -1,5 +1,4 @@
using RimWorld;
using System;
using System.Collections.Generic;
using Verse;
using Verse.AI;
@@ -8,50 +7,46 @@ namespace WulaFallenEmpire
{
public class JobDriver_HaulToMaintenancePod : JobDriver
{
private const TargetIndex ComponentInd = TargetIndex.A;
private const TargetIndex PodInd = TargetIndex.B;
private const TargetIndex PatientIndex = TargetIndex.A;
private const TargetIndex PodIndex = TargetIndex.B;
protected Thing Component => job.GetTarget(ComponentInd).Thing;
protected Building Pod => (Building)job.GetTarget(PodInd).Thing;
protected Pawn Patient => (Pawn)job.GetTarget(PatientIndex).Thing;
protected Building_Bed Pod => (Building_Bed)job.GetTarget(PodIndex).Thing;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
return pawn.Reserve(Component, job, 1, -1, null, errorOnFailed) && pawn.Reserve(Pod, job, 1, -1, null, errorOnFailed);
return pawn.Reserve(Patient, job, 1, -1, null, errorOnFailed) &&
pawn.Reserve(Pod, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
this.FailOnDespawnedNullOrForbidden(PodInd);
this.FailOnBurningImmobile(PodInd);
this.FailOnDespawnedNullOrForbidden(PodIndex);
this.FailOnDespawnedNullOrForbidden(PatientIndex);
this.FailOnAggroMentalState(PatientIndex);
this.FailOn(() => !Patient.Downed);
yield return Toils_Goto.GotoThing(ComponentInd, PathEndMode.ClosestTouch)
.FailOnSomeonePhysicallyInteracting(ComponentInd);
var podComp = Pod.TryGetComp<CompMaintenancePod>();
this.FailOn(() => podComp == null || podComp.State != MaintenancePodState.Idle || !podComp.PowerOn);
yield return Toils_Haul.StartCarryThing(ComponentInd, false, true, false)
.FailOnDestroyedNullOrForbidden(ComponentInd);
// Go to the patient
yield return Toils_Goto.GotoThing(PatientIndex, PathEndMode.OnCell);
yield return Toils_Goto.GotoThing(PodInd, PathEndMode.Touch);
// Pick up the patient
yield return Toils_Haul.StartCarryThing(PatientIndex);
Toil findPlaceAndDrop = new Toil();
findPlaceAndDrop.initAction = delegate
// Carry the patient to the pod
yield return Toils_Goto.GotoThing(PodIndex, PathEndMode.InteractionCell);
// Place the patient in the pod
yield return new Toil
{
Pawn actor = findPlaceAndDrop.actor;
Job curJob = actor.jobs.curJob;
Thing carriedThing = curJob.GetTarget(ComponentInd).Thing;
CompMaintenancePod podComp = curJob.GetTarget(PodInd).Thing.TryGetComp<CompMaintenancePod>();
if (podComp != null)
initAction = () =>
{
podComp.AddComponents(carriedThing);
}
else
{
// Fallback if something goes wrong, just drop it near the pod
actor.carryTracker.TryDropCarriedThing(Pod.Position, ThingPlaceMode.Near, out Thing _);
}
podComp.StartCycle(Patient);
},
defaultCompleteMode = ToilCompleteMode.Instant
};
yield return findPlaceAndDrop;
}
}
}

View File

@@ -7,6 +7,7 @@ namespace WulaFallenEmpire
public static class ThingDefOf_WULA
{
public static ThingDef WULA_MaintenancePod;
public static ThingDef Wula;
static ThingDefOf_WULA()
{

View File

@@ -1,5 +1,4 @@
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using Verse;
@@ -11,61 +10,52 @@ namespace WulaFallenEmpire
{
public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForDef(ThingDefOf_WULA.WULA_MaintenancePod);
public override PathEndMode PathEndMode => PathEndMode.Touch;
public override Danger MaxPathDanger(Pawn pawn) => Danger.Deadly;
public override IEnumerable<Thing> PotentialWorkThingsGlobal(Pawn pawn)
{
return pawn.Map.listerBuildings.AllBuildingsColonistOfDef(ThingDefOf_WULA.WULA_MaintenancePod);
}
public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
{
if (!(t is Building building) || building.IsForbidden(pawn) || !pawn.CanReserve(building, 1, -1, null, forced))
if (!(t is Building pod) || !pawn.CanReserve(pod, 1, -1, null, forced))
{
return false;
}
CompMaintenancePod comp = building.GetComp<CompMaintenancePod>();
if (comp == null || comp.State != MaintenancePodState.Idle)
var podComp = pod.GetComp<CompMaintenancePod>();
if (podComp == null || podComp.State != MaintenancePodState.Idle || !podComp.PowerOn)
{
return false;
}
// Check if it needs more components
if (comp.storedComponents >= comp.Props.capacity)
{
return false;
}
if (FindBestComponent(pawn, comp) == null)
{
JobFailReason.Is("WULA_NoComponentsToHaul".Translate());
return false;
}
return true;
Pawn patient = FindPatientFor(pawn, podComp);
return patient != null && pawn.CanReserve(patient, 1, -1, null, forced);
}
public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
{
Building building = (Building)t;
CompMaintenancePod comp = building.GetComp<CompMaintenancePod>();
Thing component = FindBestComponent(pawn, comp);
if (component == null)
var pod = (Building)t;
var podComp = pod.GetComp<CompMaintenancePod>();
Pawn patient = FindPatientFor(pawn, podComp);
if (patient == null)
{
return null;
}
Job job = JobMaker.MakeJob(JobDefOf_WULA.WULA_LoadComponentsToMaintenancePod, component, t);
job.count = Math.Min(component.stackCount, (int)(comp.Props.capacity - comp.storedComponents));
return job;
return JobMaker.MakeJob(JobDefOf_WULA.WULA_HaulToMaintenancePod, patient, pod);
}
private Thing FindBestComponent(Pawn pawn, CompMaintenancePod podComp)
private Pawn FindPatientFor(Pawn rescuer, CompMaintenancePod podComp)
{
ThingFilter filter = podComp.GetStoreSettings().filter;
Predicate<Thing> validator = (Thing x) => !x.IsForbidden(pawn) && pawn.CanReserve(x) && filter.Allows(x);
return GenClosest.ClosestThingReachable(pawn.Position, pawn.Map, filter.BestThingRequest, PathEndMode.ClosestTouch, TraverseParms.For(pawn), 9999f, validator);
return rescuer.Map.mapPawns.AllPawnsSpawned
.Where(p => p.def == ThingDefOf_WULA.Wula &&
p.Faction == rescuer.Faction &&
!p.IsForbidden(rescuer) &&
p.Downed && // Key condition: pawn cannot walk
p.health.hediffSet.HasHediff(podComp.Props.hediffToRemove) &&
podComp.RequiredComponents(p) <= podComp.parent.GetComp<CompRefuelable>().Fuel &&
rescuer.CanReserve(p))
.OrderBy(p => p.Position.DistanceTo(rescuer.Position))
.FirstOrDefault();
}
}
}

View File

@@ -100,6 +100,7 @@
<Compile Include="IngestPatch.cs" />
<Compile Include="JobDriver_FeedWulaPatient.cs" />
<Compile Include="JobDriver_EnterMaintenancePod.cs" />
<Compile Include="JobDriver_HaulToMaintenancePod.cs" />
<Compile Include="JobDriver_IngestWulaEnergy.cs" />
<Compile Include="JobGiver_WulaGetEnergy.cs" />
<Compile Include="JobGiver_WulaPackEnergy.cs" />
@@ -135,6 +136,7 @@
<Compile Include="Verb\Verb_ShootBeamExplosive.cs" />
<Compile Include="Verb\VerbPropertiesExplosiveBeam.cs" />
<Compile Include="WorkGiver_FeedWulaPatient.cs" />
<Compile Include="WorkGiver_HaulToMaintenancePod.cs" />
<Compile Include="WorkGiver_Warden_DeliverEnergy.cs" />
<Compile Include="WorkGiver_Warden_FeedWula.cs" />
<Compile Include="WorkGiverDefExtension_FeedWula.cs" />