public static void CheckIfPawnShouldUnloadInventory(Pawn pawn, bool forced = false) { Job job = JobMaker.MakeJob(PickUpAndHaulJobDefOf.UnloadYourHauledInventory, pawn); CompHauledToInventory itemsTakenToInventory = pawn.TryGetComp <CompHauledToInventory>(); if (itemsTakenToInventory == null) { return; } HashSet <Thing> carriedThing = itemsTakenToInventory.GetHashSet(); if (pawn.Faction != Faction.OfPlayer || !pawn.RaceProps.Humanlike) { return; } if (carriedThing == null || carriedThing.Count == 0 || pawn.inventory.innerContainer.Count == 0) { return; } if (forced) { if (job.TryMakePreToilReservations(pawn, false)) { pawn.jobs.jobQueue.EnqueueFirst(job, JobTag.Misc); return; } } if (MassUtility.EncumbrancePercent(pawn) >= 0.90f || carriedThing.Count >= 1) { if (job.TryMakePreToilReservations(pawn, false)) { pawn.jobs.jobQueue.EnqueueFirst(job, JobTag.Misc); return; } } if (pawn.inventory.innerContainer?.Count >= 1) { foreach (Thing rottable in pawn.inventory.innerContainer) { CompRottable compRottable = rottable.TryGetComp <CompRottable>(); if (compRottable?.TicksUntilRotAtCurrentTemp < 30000) { pawn.jobs.jobQueue.EnqueueFirst(job, JobTag.Misc); return; } } } if (Find.TickManager.TicksGame % 50 == 0 && pawn.inventory.innerContainer.Count < carriedThing.Count) { Verse.Log.Warning("[PickUpAndHaul] " + pawn + " inventory was found out of sync with haul index. Pawn will drop their inventory."); carriedThing.Clear(); pawn.inventory.UnloadEverything = true; } }
static ThingCount FirstUnloadableThing(Pawn pawn) { CompHauledToInventory itemsTakenToInventory = pawn.TryGetComp <CompHauledToInventory>(); HashSet <Thing> carriedThings = itemsTakenToInventory.GetHashSet(); //find the overlap. IEnumerable <Thing> potentialThingsToUnload = from t in pawn.inventory.innerContainer where carriedThings.Contains(t) select t; foreach (Thing thing in carriedThings.OrderBy(t => t.def.FirstThingCategory?.index)) { //merged partially picked up stacks get a different thingID in inventory if (!potentialThingsToUnload.Contains(thing)) { ThingDef stragglerDef = thing.def; //we have no method of grabbing the newly generated thingID. This is the solution to that. IEnumerable <Thing> dirtyStragglers = from straggler in pawn.inventory.innerContainer where straggler.def == stragglerDef select straggler; carriedThings.Remove(thing); foreach (Thing dirtyStraggler in dirtyStragglers) { return(new ThingCount(dirtyStraggler, dirtyStraggler.stackCount)); } } return(new ThingCount(thing, thing.stackCount)); } return(default(ThingCount)); }
private static bool AllowToolHaulUrgentlyJobOnThing_PreFix(ref Job __result, Pawn pawn, Thing t, bool forced = false) { if (ModCompatibilityCheck.AllowToolIsActive) { //allowTool HaulUrgently CompHauledToInventory takenToInventory = pawn.TryGetComp <CompHauledToInventory>(); if (pawn.RaceProps.Humanlike && pawn.Faction == Faction.OfPlayer && t is Corpse == false && takenToInventory != null && !(t.def.defName.Contains("Chunk")) //most of the time we don't have space for it ) { StoragePriority currentPriority = StoreUtility.CurrentStoragePriorityOf(t); if (!StoreUtility.TryFindBestBetterStoreCellFor(t, pawn, pawn.Map, currentPriority, pawn.Faction, out IntVec3 storeCell, true)) { JobFailReason.Is("NoEmptyPlaceLower".Translate()); return(false); } WorkGiver_HaulToInventory haulWG = (WorkGiver_HaulToInventory)pawn.workSettings.WorkGiversInOrderNormal.Find(wg => wg is WorkGiver_HaulToInventory); Job haul = haulWG.JobOnThing(pawn, t, forced); __result = haul; return(false); } } return(true); }
private static void JobDriver_HaulToCell_PostFix(JobDriver_HaulToCell __instance) { CompHauledToInventory takenToInventory = __instance.pawn.TryGetComp <CompHauledToInventory>(); if (takenToInventory == null) { return; } HashSet <Thing> carriedThing = takenToInventory.GetHashSet(); if (__instance.job.haulMode == HaulMode.ToCellStorage && __instance.pawn.Faction == Faction.OfPlayer && __instance.pawn.RaceProps.Humanlike && __instance.pawn.carryTracker.CarriedThing is Corpse == false && carriedThing != null && carriedThing.Count != 0) //deliberate hauling job. Should unload. { PawnUnloadChecker.CheckIfPawnShouldUnloadInventory(__instance.pawn, true); } //else //we could politely ask //{ // PawnUnloadChecker.CheckIfPawnShouldUnloadInventory(__instance.pawn); //} }
private static bool AllowToolHaulUrgentlyJobOnThing_PreFix(ref Job __result, Pawn pawn, Thing t, bool forced = false) { if (ModCompatibilityCheck.AllowToolIsActive) { //allowTool HaulUrgently CompHauledToInventory takenToInventory = pawn.TryGetComp <CompHauledToInventory>(); if (pawn.RaceProps.Humanlike && pawn.Faction == Faction.OfPlayer && t is Corpse == false && takenToInventory != null && !(t.def.defName.Contains("Chunk")) //most of the time we don't have space for it ) { StoragePriority currentPriority = HaulAIUtility.StoragePriorityAtFor(t.Position, t); if (!StoreUtility.TryFindBestBetterStoreCellFor(t, pawn, pawn.Map, currentPriority, pawn.Faction, out IntVec3 storeCell, true)) { JobFailReason.Is("NoEmptyPlaceLower".Translate()); return(false); } Job haul = new Job(PickUpAndHaulJobDefOf.HaulToInventory, t) { count = t.stackCount }; __result = haul; return(false); } } return(true); }
public static Color GetColorForHauled(Pawn pawn, Thing thing) { CompHauledToInventory comp = pawn.GetComp <CompHauledToInventory>(); if (comp.GetHashSet().Contains(thing)) { return(Color.Lerp(Color.grey, Color.red, 0.5f)); } return(Color.white); }
//pick up stuff until you can't anymore, //while you're up and about, pick up something and haul it //before you go out, empty your pockets public override Job JobOnThing(Pawn pawn, Thing thing, bool forced = false) { CompHauledToInventory takenToInventory = pawn.TryGetComp <CompHauledToInventory>(); if (takenToInventory == null) { return(null); } if (thing is Corpse) { return(null); } if (!HaulAIUtility.PawnCanAutomaticallyHaulFast(pawn, thing, forced)) { return(null); } if (thing.IsForbidden(pawn) || StoreUtility.IsInValidBestStorage(thing)) { return(null); } //bulky gear (power armor + minigun) so don't bother. if (MassUtility.GearMass(pawn) / MassUtility.Capacity(pawn) >= 0.8f) { return(null); } StoragePriority currentPriority = HaulAIUtility.StoragePriorityAtFor(thing.Position, thing); if (StoreUtility.TryFindBestBetterStoreCellFor(thing, pawn, pawn.Map, currentPriority, pawn.Faction, out IntVec3 storeCell, true)) { //since we've gone through all the effort of getting the loc, might as well use it. //Don't multi-haul food to hoppers. if (thing.def.IsNutritionGivingIngestible) { if (thing.def.ingestible.preferability == FoodPreferability.RawBad || thing.def.ingestible.preferability == FoodPreferability.RawTasty) { List <Thing> thingList = storeCell.GetThingList(thing.Map); for (int i = 0; i < thingList.Count; i++) { Thing thingAtCell = thingList[i]; if (thingAtCell.def == ThingDefOf.Hopper) { return(HaulAIUtility.HaulToStorageJob(pawn, thing)); } } } } }
private static bool Drop_Prefix(Pawn pawn, Thing thing) { CompHauledToInventory takenToInventory = pawn.TryGetComp <CompHauledToInventory>(); if (takenToInventory == null) { return(true); } HashSet <Thing> carriedThing = takenToInventory.GetHashSet(); return(!carriedThing.Contains(thing)); }
private static void Pawn_InventoryTracker_PostFix(Pawn_InventoryTracker __instance, Thing item) { CompHauledToInventory takenToInventory = __instance.pawn.TryGetComp <CompHauledToInventory>(); if (takenToInventory == null) { return; } HashSet <Thing> carriedThing = takenToInventory.GetHashSet(); if (carriedThing?.Count > 0) { if (carriedThing.Contains(item)) { carriedThing.Remove(item); } } }
//get next, goto, take, check for more. Branches off to "all over the place" protected override IEnumerable <Toil> MakeNewToils() { CompHauledToInventory takenToInventory = pawn.TryGetComp <CompHauledToInventory>(); Toil wait = Toils_General.Wait(2); Toil nextTarget = Toils_JobTransforms.ExtractNextTargetFromQueue(TargetIndex.A); //also does count yield return(nextTarget); //honestly the workgiver checks for encumbered, so until CE checks are in this is unnecessary //yield return CheckForOverencumbered();//Probably redundant without CE checks Toil gotoThing = new Toil { initAction = () => { pawn.pather.StartPath(TargetThingA, PathEndMode.ClosestTouch); }, defaultCompleteMode = ToilCompleteMode.PatherArrival }; gotoThing.FailOnDespawnedNullOrForbidden(TargetIndex.A); yield return(gotoThing); Toil takeThing = new Toil { initAction = () => { Pawn actor = pawn; Thing thing = actor.CurJob.GetTarget(TargetIndex.A).Thing; Toils_Haul.ErrorCheckForCarry(actor, thing); //get max we can pick up int countToPickUp = Mathf.Min(job.count, MassUtility.CountToPickUpUntilOverEncumbered(actor, thing)); Log.Message($"{actor} is hauling to inventory {thing}:{countToPickUp}"); // yo dawg, I heard you like delegates so I put delegates in your delegate, so you can delegate your delegates. // because compilers don't respect IF statements in delegates and toils are fully iterated over as soon as the job starts. try { ((Action)(() => { if (ModCompatibilityCheck.CombatExtendedIsActive) { //CombatExtended.CompInventory ceCompInventory = actor.GetComp<CombatExtended.CompInventory>(); //ceCompInventory.CanFitInInventory(thing, out countToPickUp); } }))(); } catch (TypeLoadException) { } if (countToPickUp > 0) { Thing splitThing = thing.SplitOff(countToPickUp); bool shouldMerge = takenToInventory.GetHashSet().Any(x => x.def == thing.def); actor.inventory.GetDirectlyHeldThings().TryAdd(splitThing, shouldMerge); takenToInventory.RegisterHauledItem(splitThing); try { ((Action)(() => { if (ModCompatibilityCheck.CombatExtendedIsActive) { //CombatExtended.CompInventory ceCompInventory = actor.GetComp<CombatExtended.CompInventory>(); //ceCompInventory.UpdateInventory(); } }))(); } catch (TypeLoadException) { } } //thing still remains, so queue up hauling if we can + end the current job (smooth/instant transition) //This will technically release the reservations in the queue, but what can you do if (thing.Spawned) { Job haul = HaulAIUtility.HaulToStorageJob(actor, thing); if (haul?.TryMakePreToilReservations(actor, false) ?? false) { actor.jobs.jobQueue.EnqueueFirst(haul, JobTag.Misc); } actor.jobs.curDriver.JumpToToil(wait); } } }; yield return(takeThing); yield return(Toils_Jump.JumpIf(nextTarget, () => !job.targetQueueA.NullOrEmpty())); //Find more to haul, in case things spawned while this was in progess yield return(new Toil { initAction = () => { List <Thing> haulables = pawn.Map.listerHaulables.ThingsPotentiallyNeedingHauling(); WorkGiver_HaulToInventory haulMoreWork = DefDatabase <WorkGiverDef> .AllDefsListForReading.First(wg => wg.Worker is WorkGiver_HaulToInventory).Worker as WorkGiver_HaulToInventory; Thing haulMoreThing = GenClosest.ClosestThing_Global(pawn.Position, haulables, 12, t => haulMoreWork.HasJobOnThing(pawn, t)); //WorkGiver_HaulToInventory found more work nearby if (haulMoreThing != null) { Log.Message($"{pawn} hauling again : {haulMoreThing}"); Job haulMoreJob = haulMoreWork.JobOnThing(pawn, haulMoreThing); if (haulMoreJob.TryMakePreToilReservations(pawn, false)) { pawn.jobs.jobQueue.EnqueueFirst(haulMoreJob, JobTag.Misc); EndJobWith(JobCondition.Succeeded); } } } }); //maintain cell reservations on the trip back //TODO: do that when we carry things //I guess that means TODO: implement carrying the rest of the items in this job instead of falling back on HaulToStorageJob yield return(Toils_Goto.GotoCell(TargetIndex.B, PathEndMode.ClosestTouch)); yield return(new Toil //Queue next job { initAction = () => { Pawn actor = pawn; Job curJob = actor.jobs.curJob; LocalTargetInfo storeCell = curJob.targetB; Job unloadJob = new Job(PickUpAndHaulJobDefOf.UnloadYourHauledInventory, storeCell); if (unloadJob.TryMakePreToilReservations(actor, false)) { actor.jobs.jobQueue.EnqueueFirst(unloadJob, JobTag.Misc); EndJobWith(JobCondition.Succeeded); //This will technically release the cell reservations in the queue, but what can you do } } }); yield return(wait); }
/// <summary> /// Find spot, reserve spot, pull thing out of inventory, go to spot, drop stuff, repeat. /// </summary> /// <returns></returns> protected override IEnumerable <Toil> MakeNewToils() { CompHauledToInventory takenToInventory = pawn.TryGetComp <CompHauledToInventory>(); HashSet <Thing> carriedThing = takenToInventory.GetHashSet(); if (ModCompatibilityCheck.ExtendedStorageIsActive) { //ES takes at least ~10 ticks to move from the feeder to the stockpile, so workaround ahoy UnloadDuration = 20; } Toil wait = Toils_General.Wait(UnloadDuration); Toil celebrate = Toils_General.Wait(UnloadDuration); yield return(wait); Toil findSpot = new Toil { initAction = () => { ThingCount unloadableThing = FirstUnloadableThing(pawn); if (unloadableThing.Count == 0 && carriedThing.Count == 0) { this.EndJobWith(JobCondition.Succeeded); } if (unloadableThing.Count != 0) { //StoragePriority currentPriority = StoreUtility.StoragePriorityAtFor(pawn.Position, unloadableThing.Thing); if (!StoreUtility.TryFindStoreCellNearColonyDesperate(unloadableThing.Thing, this.pawn, out IntVec3 c)) { this.pawn.inventory.innerContainer.TryDrop(unloadableThing.Thing, ThingPlaceMode.Near, unloadableThing.Thing.stackCount, out Thing thing); this.EndJobWith(JobCondition.Succeeded); } else { this.job.SetTarget(TargetIndex.A, unloadableThing.Thing); this.job.SetTarget(TargetIndex.B, c); this.countToDrop = unloadableThing.Thing.stackCount; } } } }; yield return(findSpot); yield return(Toils_Reserve.Reserve(TargetIndex.B)); yield return(new Toil { initAction = delegate { Thing thing = this.job.GetTarget(TargetIndex.A).Thing; if (thing == null || !this.pawn.inventory.innerContainer.Contains(thing)) { carriedThing.Remove(thing); pawn.jobs.curDriver.JumpToToil(wait); return; } if (!this.pawn.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation) || !thing.def.EverStorable(false)) { this.pawn.inventory.innerContainer.TryDrop(thing, ThingPlaceMode.Near, this.countToDrop, out thing); this.EndJobWith(JobCondition.Succeeded); carriedThing.Remove(thing); } else { this.pawn.inventory.innerContainer.TryTransferToContainer(thing, this.pawn.carryTracker.innerContainer, this.countToDrop, out thing); this.job.count = this.countToDrop; this.job.SetTarget(TargetIndex.A, thing); carriedThing.Remove(thing); } try { ((Action)(() => { if (ModCompatibilityCheck.CombatExtendedIsActive) { //CombatExtended.CompInventory ceCompInventory = pawn.GetComp<CombatExtended.CompInventory>(); //ceCompInventory.UpdateInventory(); } }))(); } catch (TypeLoadException) { } thing.SetForbidden(false, false); } }); Toil carryToCell = Toils_Haul.CarryHauledThingToCell(TargetIndex.B); yield return(Toils_Goto.GotoCell(TargetIndex.B, PathEndMode.Touch)); yield return(carryToCell); yield return(Toils_Haul.PlaceHauledThingInCell(TargetIndex.B, carryToCell, true)); //If the original cell is full, PlaceHauledThingInCell will set a different TargetIndex resulting in errors on yield return Toils_Reserve.Release. //We still gotta release though, mostly because of Extended Storage. Toil releaseReservation = new Toil { initAction = () => { if (pawn.Map.reservationManager.ReservedBy(this.job.targetB, pawn, pawn.CurJob) && !ModCompatibilityCheck.HCSKIsActive) { pawn.Map.reservationManager.Release(this.job.targetB, pawn, pawn.CurJob); } } }; yield return(releaseReservation); yield return(Toils_Jump.Jump(wait)); yield return(celebrate); }
//reserve, goto, take, check for more. Branches off to "all over the place" protected override IEnumerable <Toil> MakeNewToils() { CompHauledToInventory takenToInventory = pawn.TryGetComp <CompHauledToInventory>(); HashSet <Thing> carriedThings = takenToInventory.GetHashSet(); DesignationDef HaulUrgentlyDesignation = DefDatabase <DesignationDef> .GetNamed("HaulUrgentlyDesignation", false); //Thanks to AlexTD for the more dynamic search range float searchForOthersRangeFraction = 0.5f; float distanceToOthers = 0f; Toil wait = Toils_General.Wait(2); Toil reserveTargetA = Toils_Reserve.Reserve(TargetIndex.A, 1, -1, null); Toil calculateExtraDistanceToGo = new Toil { initAction = () => { if (StoreUtility.TryFindStoreCellNearColonyDesperate(this.job.targetA.Thing, this.pawn, out IntVec3 storeLoc)) { distanceToOthers = (storeLoc - job.targetA.Thing.Position).LengthHorizontal * searchForOthersRangeFraction; } } }; yield return(calculateExtraDistanceToGo); Toil checkForOtherItemsToHaulToInventory = CheckForOtherItemsToHaulToInventory(reserveTargetA, TargetIndex.A, distanceToOthers, null); Toil checkForOtherItemsToUrgentlyHaulToInventory = CheckForOtherItemsToHaulToInventory(reserveTargetA, TargetIndex.A, distanceToOthers, (Thing x) => pawn.Map.designationManager.DesignationOn(x)?.def == HaulUrgentlyDesignation); yield return(reserveTargetA); Toil gotoThing = new Toil { initAction = () => { this.pawn.pather.StartPath(this.TargetThingA, PathEndMode.ClosestTouch); }, defaultCompleteMode = ToilCompleteMode.PatherArrival, }; gotoThing.FailOnDespawnedNullOrForbidden(TargetIndex.A); yield return(gotoThing); Toil takeThing = new Toil { initAction = () => { Pawn actor = this.pawn; Thing thing = actor.CurJob.GetTarget(TargetIndex.A).Thing; Toils_Haul.ErrorCheckForCarry(actor, thing); //get max we can pick up int num = Mathf.Min(thing.stackCount, MassUtility.CountToPickUpUntilOverEncumbered(actor, thing)); // yo dawg, I heard you like delegates so I put delegates in your delegate, so you can delegate your delegates. // because compilers don't respect IF statements in delegates and toils are fully iterated over as soon as the job starts. try { ((Action)(() => { if (ModCompatibilityCheck.CombatExtendedIsActive) { CombatExtended.CompInventory ceCompInventory = actor.GetComp <CombatExtended.CompInventory>(); ceCompInventory.CanFitInInventory(thing, out num, false, false); } }))(); } catch (TypeLoadException) { } //can't store more, so queue up hauling if we can + end the current job (smooth/instant transition) if (num <= 0) { Job haul = HaulAIUtility.HaulToStorageJob(actor, thing); if (haul?.TryMakePreToilReservations(actor) ?? false) { actor.jobs.jobQueue.EnqueueFirst(haul, new JobTag?(JobTag.Misc)); } actor.jobs.curDriver.JumpToToil(wait); } else { bool isUrgent = false; if (ModCompatibilityCheck.AllowToolIsActive) { //check BEFORE absorbing the thing, designation disappears when it's in inventory :^) if (pawn.Map.designationManager.DesignationOn(thing)?.def == HaulUrgentlyDesignation) { isUrgent = true; } } actor.inventory.GetDirectlyHeldThings().TryAdd(thing.SplitOff(num), true); takenToInventory.RegisterHauledItem(thing); try { ((Action)(() => { if (ModCompatibilityCheck.CombatExtendedIsActive) { CombatExtended.CompInventory ceCompInventory = actor.GetComp <CombatExtended.CompInventory>(); ceCompInventory.UpdateInventory(); } }))(); } catch (TypeLoadException) { } if (isUrgent) { actor.jobs.curDriver.JumpToToil(checkForOtherItemsToUrgentlyHaulToInventory); } } } }; yield return(takeThing); yield return(checkForOtherItemsToHaulToInventory); //we end the job in there, so only one of the checks for duplicates gets called. yield return(checkForOtherItemsToUrgentlyHaulToInventory); yield return(wait); }