public static void Postfix(Zone_Stockpile __instance) { KSLog.Message("[KanbanStockpile] Zone_Stockpile_PostDeregister_Patch.Postfix()"); if (State.Exists(__instance.ToString())) { KSLog.Message("[KanbanStockpile] Removing " + __instance.ToString()); State.Del(__instance.ToString()); } }
public static bool Prefix(Pawn p, Thing t, IntVec3 storeCell, bool fitInStoreCell, ref Job __result) { if (!storeCell.TryGetKanbanSettings(t.Map, out var ks, out var slotGroup)) { return(true); } int stackLimit = Math.Max(1, (int)(t.def.stackLimit * ks.srt / 100f)); KSLog.Message($"[UnloadHauledInventory] {t.LabelCap} x{t.stackCount} / limit = {stackLimit}"); int countToDrop = -1; List <Thing> things = t.Map.thingGrid.ThingsListAt(storeCell); for (int i = 0; i < things.Count; i++) { Thing t2 = things[i]; if (!t2.def.EverStorable(false)) { continue; // skip non-storable things as they aren't actually *in* the stockpile } if (!t2.CanStackWith(t)) { continue; // skip it if it cannot stack with thing to haul } if (t2.stackCount >= stackLimit) { continue; // no need to refill until count is below threshold } int needMax = stackLimit - t2.stackCount; countToDrop = Math.Min(t.stackCount, needMax); KSLog.Message($" drop to stack => stack: {t2.stackCount}, countToDrop: {countToDrop}"); break; } if (countToDrop > 0) { Job job = new Job(JobDefOf.HaulToCell, t, storeCell) { count = countToDrop, haulOpportunisticDuplicates = true, haulMode = HaulMode.ToCellStorage }; __result = job; KSLog.Message($" dispatch job1, thing={t},cell={storeCell},countToDrop={countToDrop}"); return(false); } Job job2 = new Job(JobDefOf.HaulToCell, t, storeCell); job2.count = t.stackCount > stackLimit ? stackLimit : t.stackCount; job2.haulOpportunisticDuplicates = false; job2.haulMode = HaulMode.ToCellStorage; __result = job2; KSLog.Message($" dispatch job2(empty cell), thing={t},cell={storeCell},countToDrop={job2.count}"); return(false); }
static KanbanStockpileLoader() { var harmony = new Harmony("net.ubergarm.rimworld.mods.kanbanstockpile"); harmony.PatchAll(); if (ModLister.GetActiveModWithIdentifier("LWM.DeepStorage") != null) { IsLWMDeepStorageLoaded = true; Log.Message("[KanbanStockpile] Detected LWM Deep Storage is loaded!"); } else { IsLWMDeepStorageLoaded = false; KSLog.Message("[KanbanStockpile] Did *NOT* detect LWM Deep Storage..."); } if (ModLister.GetActiveModWithIdentifier("Uuugggg.StockpileRanking") != null) { IsStockpileRankingLoaded = true; Log.Message("[KanbanStockpile] Detected Uuugggg's StockpileRanking is loaded!"); } else { IsStockpileRankingLoaded = false; KSLog.Message("[KanbanStockpile] Did *NOT* detect Uuugggg's StockpileRanking..."); } // Check for both the original and the re-uploaded one (which is basically the same) if ((ModLister.GetActiveModWithIdentifier("Mehni.PickUpAndHaul") != null) || (ModLister.GetActiveModWithIdentifier("Mlie.PickUpAndHaul") != null)) { IsPickUpAndHaulLoaded = true; Log.Message("[KanbanStockpile] Detected Mehni or Mlie PickUpAndHaul is loaded!"); if (KanbanStockpile.Settings.PreventPickUpAndHaulOverHauling) { PickUpAndHaul_WorkGiver_HaulToInventory_Patch.ApplyPatch(harmony); } } else { IsPickUpAndHaulLoaded = false; KSLog.Message("[KanbanStockpile] Did *NOT* detect Mehni or Mlie PickUpAndHaul..."); } if (MP.enabled) { MP.RegisterSyncMethod(typeof(State), nameof(State.Set)); MP.RegisterSyncMethod(typeof(State), nameof(State.Del)); MP.RegisterSyncWorker <KanbanSettings>(State.SyncKanbanSettings, typeof(KanbanSettings), false, false); } }
public static void Prefix(Zone ___zone, string name) { //private Zone zone; string oldName = ___zone?.label ?? "N/A"; KSLog.Message("[KanbanStockpile] Dialog_RenameZone.SetName() oldName: " + oldName); KSLog.Message("[KanbanStockpile] Dialog_RenameZone.SetName() newName: " + name); if (oldName == "N/A") { return; } State.Set(name, State.Get(oldName)); State.Del(oldName); }
//public void CopyFrom(StorageSettings other) public static void CopyFrom(StorageSettings __instance, StorageSettings other) { var st = new StackTrace(); if (st.FrameCount > 3 && st.GetFrame(2).GetMethod() == Building_Storage_PostMake) { return; // prevent copy settings when called from Building_Storage:PostMake, new storage } KSLog.Message("[KanbanStockpile] CopyFrom()"); string label = other?.owner?.ToString() ?? "___clipboard"; KanbanSettings ks = State.Get(label); label = __instance?.owner?.ToString() ?? "___clipboard"; State.Set(label, ks); }
public static void Postfix(StorageSettings __instance) { // The clipboard StorageSettings has no owner, so assume a null is the clipboard... string label = __instance?.owner?.ToString() ?? "___clipboard"; KSLog.Message("[KanbanStockpile] ExposeData() with owner name: " + label); KanbanSettings ks = State.Get(label); if (Scribe.mode == LoadSaveMode.Saving) { // this mode implicitly takes the value currently in srt and saves it out Scribe_Values.Look(ref ks.srt, "stackRefillThreshold", 100, true); Scribe_Values.Look(ref ks.ssl, "similarStackLimit", 0, true); } else if (Scribe.mode == LoadSaveMode.LoadingVars) { // this mode implicitly loads some other value into this instance of srt Scribe_Values.Look(ref ks.srt, "stackRefillThreshold", 100, false); Scribe_Values.Look(ref ks.ssl, "similarStackLimit", 0, false); State.Set(label, ks); } }
static bool CheckKanbanSettings(Map map, IntVec3 cell, ThingCount thingCount, ref int countToDrop) { if (!cell.TryGetKanbanSettings(map, out var ks, out var slotGroup)) { return(false); } var thing = thingCount.Thing; int stackLimit = Math.Max(1, (int)(thing.def.stackLimit * ks.srt / 100f)); KSLog.Message($"[UnloadHauledInventory] {thing.LabelCap} x{thing.stackCount} / limit = {stackLimit}"); List <Thing> things = map.thingGrid.ThingsListAt(cell); for (int i = 0; i < things.Count; i++) { Thing t = things[i]; if (!t.def.EverStorable(false)) { continue; // skip non-storable things as they aren't actually *in* the stockpile } if (!t.CanStackWith(thing)) { continue; // skip it if it cannot stack with thing to haul } if (t.stackCount >= stackLimit) { continue; // no need to refill until count is below threshold } int needMax = stackLimit - t.stackCount; countToDrop = Math.Min(thing.stackCount, needMax); KSLog.Message($" drop to stack => stack: {t.stackCount}, countToDrop: {countToDrop}"); return(true); } countToDrop = thing.stackCount > stackLimit ? stackLimit : thing.stackCount; KSLog.Message($" drop to empty cell => countToDrop: {countToDrop}"); return(true); }
public static void Postfix(ref bool __result, IntVec3 c, Map map, Thing thing) { // NOTE: Likely LWM Deep Storages Prefix() and Vanilla NoStorageBlockersIn() itself have already run // returning false means storage is "full" so do *not* try to haul the thing // returning true means storage still has space available for thing so try to haul it // storage already filled up so no need to try to limit it further if (__result == false) { return; } // make sure we have everything we need to continue if (!c.TryGetKanbanSettings(map, out var ks, out var slotGroup)) { return; } // Assuming JobDefOf.HaulToContainer for Building_Storage vs JobDefOf.HaulToCell otherwise bool isContainer = (slotGroup?.parent is Building_Storage); // StackRefillThreshold checks only here at cell c List <Thing> things = map.thingGrid.ThingsListAt(c); int numDuplicates = 0; int stackLimit = Math.Max(1, (int)(thing.def.stackLimit * ks.srt / 100f)); // TODO #5 consider re-ordering to prevent refilling an accidental/leftover duplicate stack // Design Decision: use for loops instead of foreach as they may be faster and similar to this vanilla function for (int i = 0; i < things.Count; i++) { Thing t = things[i]; if (!t.def.EverStorable(false)) { continue; // skip non-storable things as they aren't actually *in* the stockpile } if (!t.CanStackWith(thing)) { continue; // skip it if it cannot stack with thing to haul } if (t.stackCount >= stackLimit) { continue; // no need to refill until count is below threshold } if (!isContainer) { // pawns are smart enough to grab a partial stack for vanilla cell stockpiles so no need to explicitly check here // maybe this is a JobDefOf.HaulToCell job? KSLog.Message("[KanbanStockpile] YES HAUL PARTIAL STACK OF THING TO TOPOFF STACK IN CELL STOCKPILE!"); __result = true; return; } else if (t.stackCount < stackLimit) { // pawns seem to try to haul a full stack no matter what for HaulToContainer unlike HaulToCell CurJobDef's // so for here when trying to haul to deep storage explicitly ensure stack to haul is partial stack // maybe this is a JobDefOf.HaulToContainer job? KSLog.Message("[KanbanStockpile] YES HAUL EXISTING PARTIAL STACK OF THING TO BUILDING STORAGE CONTAINER!"); __result = true; return; } } if (ks.ssl == 0) { return; } // SimilarStackLimit check all cells in the slotgroup (potentially CPU intensive for big zones/limits) // SlotGroup.HeldThings for (int j = 0; j < slotGroup.CellsList.Count; j++) { IntVec3 cell = slotGroup.CellsList[j]; things = map.thingGrid.ThingsListAt(cell); for (int i = 0; i < things.Count; i++) { Thing t = things[i]; if (!t.def.EverStorable(false)) { continue; // skip non-storable things as they aren't actually *in* the stockpile } if (!t.CanStackWith(thing) && t.def != thing.def) { continue; // skip it if it cannot stack with thing to haul } // even a partial stack is a dupe so count it regardless numDuplicates += Math.Max(1, t.stackCount / stackLimit); if (numDuplicates >= ks.ssl) { KSLog.Message("[KanbanStockpile] NO DON'T HAUL AS THERE IS ALREADY TOO MANY OF THAT KIND OF STACK!"); __result = false; return; } } } // iterate over all outstanding reserved jobs to prevent hauling duplicate similar stacks if (KanbanStockpile.Settings.aggressiveSimilarStockpileLimiting == false) { return; } if (map.reservationManager == null) { return; } var reservations = map.reservationManager.reservations; if (reservations == null) { return; } ReservationManager.Reservation r; for (int i = 0; i < reservations.Count; i++) { r = reservations[i]; if (r == null) { continue; } if (r.Job == null) { continue; } if (!(r.Job.def == JobDefOf.HaulToCell || r.Job.def == JobDefOf.HaulToContainer)) { continue; } Thing t = r.Job.targetA.Thing; if (t == null) { continue; } if (t == thing) { continue; // no need to check against itself } if (!t.CanStackWith(thing)) { continue; // skip it if it cannot stack with thing to haul } IntVec3 dest; if (r.Job.def == JobDefOf.HaulToCell) { dest = r.Job.targetB.Cell; } else { // case of JobDefOf.HaulToContainer Thing container = r.Job.targetB.Thing; if (container == null) { continue; } dest = container.Position; } if (dest == null) { continue; } SlotGroup sg = dest.GetSlotGroup(map); if (sg == null) { continue; } if (sg != slotGroup) { continue; // skip it as the similar thing is being hauled to a different stockpile } // there is a thing that can stack with this thing and is already reserved for hauling to the desired stockpile: DUPE! numDuplicates++; if (numDuplicates >= ks.ssl) { KSLog.Message("[KanbanStockpile] NO DON'T HAUL AS THERE IS ALREADY SOMEONE RESERVING JOB TO DO IT!"); __result = false; return; } } // if we get here, haul that thing! return; }
public static void DrawKanbanSettings(ITab_Storage tab) { IHaulDestination haulDestination = SelStoreInfo.GetValue(tab, null) as IHaulDestination; if (haulDestination == null) { return; } StorageSettings settings = haulDestination.GetStoreSettings(); if (settings == null) { return; } //ITab_Storage.WinSize = 300 float buttonMargin = ITab_Storage_TopAreaHeight_Patch.extraHeight + 4; Rect rect = new Rect(0f, (float)GetTopAreaHeight.Invoke(tab, new object[] { }) - ITab_Storage_TopAreaHeight_Patch.extraHeight - 2, 280, ITab_Storage_TopAreaHeight_Patch.extraHeight); // if Stockpile Ranking is installed, scootch these widgets up so it doesn't overlap // https://github.com/alextd/RimWorld-StockpileRanking/blob/master/Source/RankSelection.cs#L18 if (KanbanStockpileLoader.IsStockpileRankingLoaded) { rect.y -= 26f; } rect.x += buttonMargin; rect.width -= buttonMargin * 3; Text.Font = GameFont.Small; KanbanSettings ks, tmp; ks = State.Get(settings.owner.ToString()); tmp.srt = ks.srt; tmp.ssl = ks.ssl; string stackRefillThresholdLabel = "KS.StackRefillThreshold".Translate(ks.srt); string similarStackLimitLabel; if (ks.ssl > 0) { similarStackLimitLabel = "KS.SimilarStackLimit".Translate(ks.ssl); } else { similarStackLimitLabel = "KS.SimilarStackLimitOff".Translate(); } //Stack Refill Threshold Slider tmp.srt = (int)Widgets.HorizontalSlider(new Rect(0f, rect.yMin + 10f, 150f, 15f), ks.srt, 0f, 100f, false, stackRefillThresholdLabel, null, null, 1f); //Similar Stack Limit Slider tmp.ssl = (int)Widgets.HorizontalSlider(new Rect(155, rect.yMin + 10f, 125f, 15f), ks.ssl, 0f, 8f, false, similarStackLimitLabel, null, null, 1f); if ((ks.srt != tmp.srt) || (ks.ssl != tmp.ssl)) { // Accept slider changes no faster than 4Hz (250ms) to prevent spamming multiplayer sync lag DateTime curTime = DateTime.Now; if ((curTime - lastUpdateTime).TotalMilliseconds < 250) { return; } lastUpdateTime = curTime; KSLog.Message("[KanbanStockpile] Changed Stack Refill Threshold for settings with haulDestination named: " + settings.owner.ToString()); ks.srt = tmp.srt; ks.ssl = tmp.ssl; State.Set(settings.owner.ToString(), ks); } }