diff --git a/HaulToStack.csproj b/HaulToStack.csproj index f99e90d..a92162e 100644 --- a/HaulToStack.csproj +++ b/HaulToStack.csproj @@ -1,4 +1,5 @@ - + + Debug AnyCPU @@ -112,6 +113,7 @@ + diff --git a/Mods/HaulToStack/About/About.xml b/Mods/HaulToStack/About/About.xml index f175be1..5063c51 100644 --- a/Mods/HaulToStack/About/About.xml +++ b/Mods/HaulToStack/About/About.xml @@ -2,18 +2,24 @@ Haul to Stack ItsComcastic - 0.17.0 + 0.18.0 -Version 0.17 +Version .18 Instead of hauling to random tiles your pawn *should* always haul to a stack if it has room on it Features -Haulers will check for an existing stack to add it's item to. Haulers no longer reserve the haul-to tile meaning multiple pawns can haul to the same tile at once. +Haulers will check for an existing stack to add it's item to. +Haulers no longer reserve the haul-to tile meaning multiple pawns can haul to the same tile at once. + Issues -You may still see your pawns haul to different stacks, this will happen if there are no stacks, or full stacks in the stockpile and two or more pawns try to haul new items at once or if an animal is hauling. Example: If you get a pod drop with new loot and two pawns pickup some before any is placed, they might pick different spots in the stockpile to haul to. I'm working on a system where they can share with eachother which tile they plan on placing new loot on which should fix that issue and prevent the need for Stack Merger by Fluffy +You may still see your pawns haul to different stacks, this will happen under the following conditions: +there are no existing stacks, +the existing stacks are full + +Example: If you get a pod drop with new loot and two pawns pickup some before any is placed, they might pick different spots in the stockpile to haul to. I'm working on a system where they can share with eachother which tile they plan on placing new loot on which should fix that issue and prevent the need for Stack Merger by Fluffy Conflicts It looks like there might be a conflict with a wall light mod if your stash runs against the wall light. If you have wall lights against your stash take that tile out of your stockpile zone to prevent pawns from going into a loop of trying to haul over and over. This issue isn't fixable by changing mod order unfortunately, I'll have to work with the mod creater on a solution. @@ -24,9 +30,9 @@ If you want to remove or turn off this mod you need to first make sure no one is You can force this by pausing, drafting any pawns where their state says they're 'Hauling", save.. turn off the mod, then load that save and undraft the pawns. If you don't do this the pawn and what they were holding will 'disappear' and the game will throw errors. If you reload your 'broken' save with the mod they should reappear. Recommendations -Install Stack Merger by Fluffy in addition to this mod. On rare occasions pawns could haul items that don't have an existing stock pile to different tiles because both pawns look for a location to place the item at the same time and if nothing exists they may pick different tiles for placing. Fluffy's mod will handle those cases. +Install Stack Merger by Fluffy in addition to this mod. -Thanks to @Trips for the Haul to Stack preview picture. +Thanks to @Trips on the rimworld discord server for the Haul to Stack preview picture. Github https://github.com/jkluch/HaulToStack diff --git a/Mods/HaulToStack/Assemblies/$HugsLibChecker.dll b/Mods/HaulToStack/Assemblies/$HugsLibChecker.dll index e488fcb..f0496c4 100644 Binary files a/Mods/HaulToStack/Assemblies/$HugsLibChecker.dll and b/Mods/HaulToStack/Assemblies/$HugsLibChecker.dll differ diff --git a/Mods/HaulToStack/Assemblies/HaulToStack.dll b/Mods/HaulToStack/Assemblies/HaulToStack.dll index e981d9b..ba2b91b 100644 Binary files a/Mods/HaulToStack/Assemblies/HaulToStack.dll and b/Mods/HaulToStack/Assemblies/HaulToStack.dll differ diff --git a/Source/HaulToStack.cs b/Source/HaulToStack.cs index 727c4f4..da4b4ae 100644 --- a/Source/HaulToStack.cs +++ b/Source/HaulToStack.cs @@ -98,9 +98,12 @@ static bool TryFindBestBetterStoreCellForReplacement(Thing t, Pawn carrier, Map if (!(num3 <= num)) { +#if DEBUG + HaulToStack.Instance.Logger.Trace("Game usually doesn't attempt to haul on this condition"); +#endif if (CellIsReachable(intVec2, map, t, carrier, faction)) { - String stackSituation = CellCanStack(intVec2, map, t); + string stackSituation = CellCanStack(intVec2, map, t); if (stackSituation.Equals("stackable")) { //This is the ideal situation @@ -122,7 +125,7 @@ static bool TryFindBestBetterStoreCellForReplacement(Thing t, Pawn carrier, Map { if (CellIsReachable(intVec2, map, t, carrier, faction)) { - String stackSituation = CellCanStack(intVec2, map, t); + string stackSituation = CellCanStack(intVec2, map, t); if (stackSituation.Equals("clear")) { @@ -205,12 +208,12 @@ static bool CellIsReachable(IntVec3 c, Map map, Thing t, Pawn carrier, Faction f */ if (carrier != null) { - if (!carrier.CanReserve(c, 1, -1, null, false)) + if (!carrier.CanReserveNew(c)) { return false; } } - else if (map.reservationManager.IsReserved(c, faction)) + else if (faction != null && map.reservationManager.IsReservedByAnyoneOf(c, faction)) { return false; } @@ -218,7 +221,7 @@ static bool CellIsReachable(IntVec3 c, Map map, Thing t, Pawn carrier, Faction f } - static String CellCanStack(IntVec3 c, Map map, Thing thing) + static string CellCanStack(IntVec3 c, Map map, Thing thing) { List list = map.thingGrid.ThingsListAt(c); bool potentialStack = false; diff --git a/Source/HaulUtils.cs b/Source/HaulUtils.cs new file mode 100644 index 0000000..5701466 --- /dev/null +++ b/Source/HaulUtils.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; + +using Harmony; +using HugsLib; +using HugsLib.Utils; +using RimWorld; +using Verse; +using UnityEngine; +using System.Reflection; +using Verse.AI; + +namespace HaulToStack +{ + class HaulUtils + { + public static String CellCanStack(IntVec3 location, Map map, Thing thing) + { + List list = map.thingGrid.ThingsListAt(location); + bool potentialStack = false; + for (int i = 0; i < list.Count; i++) + { + Thing thing2 = list[i]; + + //HaulToStack.Instance.Logger.Trace("Item on tile is: " + thing2.def.defName); + //HaulToStack.Instance.Logger.Trace("Item on hand is: " + thing.def.defName); + + if (thing2.def.EverStoreable) + { + if (!thing2.CanStackWith(thing)) + { + //HaulToStack.Instance.Logger.Trace("Can't stack on eachother"); + return "unusable"; + } + if (thing2.stackCount >= thing.def.stackLimit) + { + //HaulToStack.Instance.Logger.Trace("Stack count issue"); + return "unusable"; + } + } + if (thing2.def.entityDefToBuild != null && thing2.def.entityDefToBuild.passability != Traversability.Standable) + { + //HaulToStack.Instance.Logger.Trace("impassible terrain"); + return "unusable"; + } + if (thing2.def.surfaceType == SurfaceType.None && thing2.def.passability != Traversability.Standable) + { + //HaulToStack.Instance.Logger.Trace("different impassible terrain"); + return "unusable"; + } + if (thing2.def.defName.Equals(thing.def.defName)) + { + potentialStack = true; + } + } + if (potentialStack) + { + return "stackable"; + } + return "clear"; + } + + internal static bool ShouldReserveHaulLocation(Thing thing, IntVec3 destination, Pawn pawn, Map map) + { + var destinationThing = map.thingGrid.ThingsListAt(destination).Find(x => x.def.defName == thing.def.defName); + +#if DEBUG + HaulToStack.Instance.Logger.Trace("Checking if we should reserve the destination"); + HaulToStack.Instance.Logger.Trace("Pawn grabbing stack of: " + thing.stackCount); +#endif + //If this is the case, we are carrying to a new (clear) location + //Technically we also would want to reserve if the max stack size of the item is one + //However we already are handling that case in TryMakePreToilReservations() + if (destinationThing == null) + { +#if DEBUG + HaulToStack.Instance.Logger.Trace("NOT RESERVING DESTINATION"); + HaulToStack.Instance.Logger.Trace("Destination empty, not reserving"); +#endif + return false; + } + + + //If pawn currently isn't holding anything just check the destination stack + hauling stack + if (pawn.carryTracker.CarriedThing == null) + { + if (destinationThing.stackCount + Math.Min(pawn.carryTracker.MaxStackSpaceEver(thing.def), thing.stackCount) >= thing.def.stackLimit) + { +#if DEBUG + HaulToStack.Instance.Logger.Trace("RESERVING DESTINATION"); + HaulToStack.Instance.Logger.Trace("Pawn not carrying anything but the stack they're grabbing is going to overfill the destination"); +#endif + return true; + } + else + { +#if DEBUG + HaulToStack.Instance.Logger.Trace("NOT RESERVING DESTINATION"); + HaulToStack.Instance.Logger.Trace("Pawn can't carry enough to fill the destination stack"); +#endif + return false; + } + } + + //Destination has a stack + //Pawn is holding items +#if DEBUG + HaulToStack.Instance.Logger.Trace("Pawn holding stack of: " + pawn.carryTracker.CarriedThing.stackCount); +#endif + + if (destinationThing.stackCount + pawn.carryTracker.CarriedThing.stackCount + thing.stackCount >= thing.def.stackLimit) + { +#if DEBUG + HaulToStack.Instance.Logger.Trace("RESERVING DESTINATION"); + HaulToStack.Instance.Logger.Trace("Pawn is going to overfill the destination"); +#endif + return true; + } + else + { +#if DEBUG + HaulToStack.Instance.Logger.Trace("NOT RESERVING DESTINATION"); + HaulToStack.Instance.Logger.Trace("Pawn isn't carrying enough to fill the destination stack"); +#endif + return false; + } + + } + + internal static Toil CheckForGetOpportunityDuplicateReplace(Toil getHaulTargetToil, TargetIndex haulableInd, TargetIndex storeCellInd, bool takeFromValidStorage = false, Predicate extraValidator = null) + { + Toil toil = new Toil(); + toil.initAction = delegate + { + Pawn actor = toil.actor; + Job curJob = actor.jobs.curJob; + if (actor.carryTracker.CarriedThing.def.stackLimit == 1) + { + return; + } + if (actor.carryTracker.Full) + { + return; + } + if (curJob.count <= 0) + { + return; + } + Predicate validator = (Thing t) => t.Spawned && t.def == actor.carryTracker.CarriedThing.def && t.CanStackWith(actor.carryTracker.CarriedThing) && !t.IsForbidden(actor) && (takeFromValidStorage || !t.IsInValidStorage()) && (storeCellInd == TargetIndex.None || curJob.GetTarget(storeCellInd).Cell.IsValidStorageFor(actor.Map, t)) && actor.CanReserve(t, 1, -1, null, false) && (extraValidator == null || extraValidator(t)); + Thing thing = GenClosest.ClosestThingReachable(actor.Position, actor.Map, ThingRequest.ForGroup(ThingRequestGroup.HaulableAlways), PathEndMode.ClosestTouch, TraverseParms.For(actor, Danger.Deadly, TraverseMode.ByPawn, false), 8f, validator, null, 0, -1, false, RegionType.Set_Passable, false); + if (thing != null) + { + curJob.SetTarget(haulableInd, thing); + actor.jobs.curDriver.JumpToToil(getHaulTargetToil); +#if DEBUG + HaulToStack.Instance.Logger.Trace("In opportunistic pickup"); +#endif + if ( ShouldReserveHaulLocation(curJob.targetA.Thing, curJob.targetB.Cell, actor, actor.Map) ) + actor.Reserve(curJob.GetTarget(storeCellInd), curJob); + } + }; + return toil; + } + } +} diff --git a/Source/JobDriver_HaulToStack.cs b/Source/JobDriver_HaulToStack.cs index c2c4149..76fbbf3 100644 --- a/Source/JobDriver_HaulToStack.cs +++ b/Source/JobDriver_HaulToStack.cs @@ -12,67 +12,101 @@ namespace HaulToStack { class JobDriver_HaulToStack : JobDriver { + + //Working vars + private bool forbiddenInitially; + //Constants private const TargetIndex HaulableInd = TargetIndex.A; private const TargetIndex StoreCellInd = TargetIndex.B; + public override void ExposeData() + { + base.ExposeData(); + + Scribe_Values.Look(ref forbiddenInitially, "forbiddenInitially"); + } + public override string GetReport() { - IntVec3 destLoc = pawn.jobs.curJob.targetB.Cell; + IntVec3 destLoc = job.targetB.Cell; Thing hauledThing = null; - if (pawn.carryTracker.CarriedThing != null) + if (pawn.CurJob == job && pawn.carryTracker.CarriedThing != null) hauledThing = pawn.carryTracker.CarriedThing; else hauledThing = TargetThingA; + if( hauledThing == null ) + return "ReportHaulingUnknown".Translate(); + string destName = null; var destGroup = destLoc.GetSlotGroup(Map); - if (destGroup != null) + if ( destGroup != null ) destName = destGroup.parent.SlotYielderLabel(); - string repString; if (destName != null) - repString = "ReportHaulingTo".Translate(hauledThing.LabelCap, destName); + return "ReportHaulingTo".Translate(hauledThing.LabelCap, destName); else - repString = "ReportHauling".Translate(hauledThing.LabelCap); + return "ReportHauling".Translate(hauledThing.LabelCap); + } - return repString; + public override bool TryMakePreToilReservations() + { + //if stacklimit for this item is 1, then reserve the tile + if ( job.targetA.Thing.def.stackLimit <= 1) + return pawn.Reserve(job.GetTarget(StoreCellInd), job) + && pawn.Reserve(job.GetTarget(HaulableInd), job); + else + return pawn.Reserve(job.GetTarget(HaulableInd), job); + } + + //Kluch: I might need to modify this code, not sure yet + public override void Notify_Starting() + { + base.Notify_Starting(); + + if (TargetThingA != null) + forbiddenInitially = TargetThingA.IsForbidden(pawn); + else + forbiddenInitially = false; } protected override IEnumerable MakeNewToils() { //Set fail conditions - this.FailOnDestroyedOrNull(HaulableInd); - this.FailOnBurningImmobile(StoreCellInd); + this.FailOnDestroyedOrNull( HaulableInd ); + this.FailOnBurningImmobile( StoreCellInd ); //Note we only fail on forbidden if the target doesn't start that way //This helps haul-aside jobs on forbidden items // // TODO instead of this, just use Job.ignoreForbidden where appropriate // - if (!TargetThingA.IsForbidden(pawn)) + if ( !forbiddenInitially ) this.FailOnForbidden(HaulableInd); - - //Reserve target storage cell - if(pawn.RaceProps.Animal) - { + //Reserve thing to be stored + //This is redundant relative to MakePreToilReservations(), but the redundancy doesn't hurt, and if we end up looping and grabbing more things, it's necessary + var reserveTargetA = Toils_Reserve.Reserve(HaulableInd); + yield return reserveTargetA; #if DEBUG - HaulToStack.Instance.Logger.Trace("Animal is hauling!"); + HaulToStack.Instance.Logger.Trace("Current Job: " + pawn.CurJob.def.defName); #endif - yield return Toils_Reserve.Reserve(StoreCellInd); + + //Reserve the location to store + //Only do this if (current stack size + what pawn is currently holding + targetA stack size) < things max stack size + if (HaulUtils.ShouldReserveHaulLocation(job.targetA.Thing, job.targetB.Cell, pawn, Map)) + { + var reserveTargetB = Toils_Reserve.Reserve(StoreCellInd); + yield return reserveTargetB; } - //yield return Toils_Reserve.Reserve(StoreCellInd); - //Reserve thing to be stored - Toil reserveTargetA = Toils_Reserve.Reserve(HaulableInd); - yield return reserveTargetA; Toil toilGoto = null; - toilGoto = Toils_Goto.GotoThing(HaulableInd, PathEndMode.ClosestTouch) + toilGoto = Toils_Goto.GotoThing( HaulableInd, PathEndMode.ClosestTouch ) .FailOnSomeonePhysicallyInteracting(HaulableInd) - .FailOn(() => + .FailOn( () => { //Note we don't fail on losing hauling designation //Because that's a special case anyway @@ -80,9 +114,9 @@ protected override IEnumerable MakeNewToils() //While hauling to cell storage, ensure storage dest is still valid Pawn actor = toilGoto.actor; Job curJob = actor.jobs.curJob; - if (curJob.haulMode == HaulMode.ToCellStorage) + if ( curJob.haulMode == HaulMode.ToCellStorage ) { - Thing haulThing = curJob.GetTarget(HaulableInd).Thing; + Thing haulThing = curJob.GetTarget( HaulableInd ).Thing; IntVec3 destLoc = actor.jobs.curJob.GetTarget(TargetIndex.B).Cell; if (!destLoc.IsValidStorageFor(Map, haulThing)) @@ -93,13 +127,28 @@ protected override IEnumerable MakeNewToils() }); yield return toilGoto; + yield return Toils_Haul.StartCarryThing( HaulableInd, subtractNumTakenFromJobCount: true ); - yield return Toils_Haul.StartCarryThing(HaulableInd, subtractNumTakenFromJobCount: true); - - if (CurJob.haulOpportunisticDuplicates) - yield return Toils_Haul.CheckForGetOpportunityDuplicate(reserveTargetA, HaulableInd, StoreCellInd); + if ( job.haulOpportunisticDuplicates ) + { + //yield return Toils_Haul.CheckForGetOpportunityDuplicate(reserveTargetA, HaulableInd, StoreCellInd); + yield return HaulUtils.CheckForGetOpportunityDuplicateReplace(reserveTargetA, HaulableInd, StoreCellInd); + +//#if DEBUG +// HaulToStack.Instance.Logger.Trace("Opportunistic pickup"); +// if (getDups == null) +// HaulToStack.Instance.Logger.Trace("There are no opportunistic dups"); +//#endif +// if (HaulUtils.ShouldReserveHaulLocation(nextJob.job.targetA.Thing, job.targetB.Cell, pawn, Map)) +// { +// var reserveTargetB = Toils_Reserve.Reserve(StoreCellInd); +// yield return reserveTargetB; +// } + + } + - Toil carryToCell = Toils_Haul.CarryHauledThingToCell(StoreCellInd); + Toil carryToCell = Toils_Haul.CarryHauledThingToCell( StoreCellInd ); yield return carryToCell; yield return Toils_Haul.PlaceHauledThingInCell(StoreCellInd, carryToCell, true); diff --git a/Source/PlannedHauls.cs b/Source/PlannedHauls.cs new file mode 100644 index 0000000..df02d07 --- /dev/null +++ b/Source/PlannedHauls.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Verse; + +namespace HaulToStack +{ + class PlannedHauls + { + Thing t; + private IntVec3 plannedTile; + private int count; + + public PlannedHauls(IntVec3 plannedTile, Thing t, int count) + { + this.plannedTile = plannedTile; + this.t = t; + this.count = count; + } + } +}