/// <summary> /// Similar to GetExcessThing though narrower in scope. If there is NOT a loadout which covers the equipped item, it should be dropped. /// </summary> /// <param name="pawn"></param> /// <param name="dropEquipment">Thing which should be unequiped.</param> /// <returns>bool, true if there is equipment that should be unequipped.</returns> static public bool GetExcessEquipment(this Pawn pawn, out ThingWithComps dropEquipment) { Loadout loadout = pawn.GetLoadout(); dropEquipment = null; if (loadout == null || (loadout != null && loadout.Slots.NullOrEmpty()) || pawn.equipment?.Primary == null) { return(false); } if (pawn.IsItemQuestLocked(pawn.equipment?.Primary)) { return(false); } //Check if equipment is part of the loadout LoadoutSlot eqSlot = loadout.Slots.FirstOrDefault(s => s.count >= 1 && ((s.thingDef != null && s.thingDef == pawn.equipment.Primary.def) || (s.genericDef != null && s.genericDef.lambda(pawn.equipment.Primary.def)))); //Check if equipment is in the forced pick-up items list HoldRecord eqRecord = pawn.GetHoldRecords()?.FirstOrDefault(s => s.count >= 1 && s.thingDef != null && s.thingDef == pawn.equipment.Primary.def); if (eqSlot == null && eqRecord == null) { dropEquipment = pawn.equipment.Primary; return(true); } return(false); }
/// <summary> /// Called when trying to find something to drop (ie coming back from a caravan). This is useful even on pawns without a loadout. /// </summary> /// <param name="dropThing">Thing to be dropped from inventory.</param> /// <param name="dropCount">Amount to drop from inventory.</param> /// <returns></returns> static public bool GetAnythingForDrop(this Pawn pawn, out Thing dropThing, out int dropCount) { dropThing = null; dropCount = 0; if (pawn.inventory == null || pawn.inventory.innerContainer == null) { return(false); } Loadout loadout = pawn.GetLoadout(); if (loadout == null || loadout.Slots.NullOrEmpty()) { List <HoldRecord> recs = LoadoutManager.GetHoldRecords(pawn); if (recs != null) { // hand out any inventory item not covered by a HoldRecord. foreach (Thing thing in pawn.inventory.innerContainer) { if (pawn.IsItemQuestLocked(thing)) { continue; //quest requirements prevent this from being dropped, skip to next thing in inventory } int numContained = pawn.inventory.innerContainer.TotalStackCountOfDef(thing.def); HoldRecord rec = recs.FirstOrDefault(hr => hr.thingDef == thing.def); if (rec == null) { // we don't have a HoldRecord for this thing, drop it. dropThing = thing; dropCount = numContained > dropThing.stackCount ? dropThing.stackCount : numContained; return(true); } if (numContained > rec.count) { dropThing = thing; dropCount = numContained - rec.count; dropCount = dropCount > dropThing.stackCount ? dropThing.stackCount : dropCount; return(true); } } } else { // we have nither a HoldTracker nor a Loadout that we can ask, so just pick a random non-quest item from Inventory. dropThing = pawn.inventory.innerContainer.Where(inventoryItem => !pawn.IsItemQuestLocked(inventoryItem))?.RandomElement(); dropCount = dropThing?.stackCount ?? 0; } } else { // hand out an item from GetExcessThing... return(GetExcessThing(pawn, out dropThing, out dropCount)); } return(false); }
/// <summary> /// Used when a pawn is about to be ordered to pickup a Thing. /// </summary> /// <param name="pawn"></param> /// <param name="job"></param> static public void Notify_HoldTrackerJob(this Pawn pawn, Job job) { // make sure it's the right kind of job. if (job.def != JobDefOf.TakeInventory) { throw new ArgumentException(); } // if the pawn doesn't have a normal loadout, nothing to do... if (pawn.GetLoadout().defaultLoadout) { return; } // find out if we are already remembering this thing on this pawn... List <HoldRecord> recs = LoadoutManager.GetHoldRecords(pawn); if (recs == null) { recs = new List <HoldRecord>(); LoadoutManager.AddHoldRecords(pawn, recs); } // could check isHeld but that tells us if there is a record AND it's been picked up. HoldRecord rec = recs.FirstOrDefault(hr => hr.thingDef == job.targetA.Thing.def); if (rec != null) { if (rec.pickedUp) { // modifying a record for which the pawn should have some of that thing in their inventory. CompInventory inventory = pawn.TryGetComp <CompInventory>(); if (inventory != null) { rec.count = inventory.container.TotalStackCountOfDef(rec.thingDef) + job.count; } else { rec.count += job.count; // probably won't generally follow this code path but good not to throw an error if possible. } } else { // modifying a record that hasn't been picked up... do it blind. rec.count += job.count; } // useful debug message... //Log.Message(string.Concat("Job was issued to pickup items for this existing record: ", rec)); return; } // if we got this far we know that there isn't a record being stored for this thingDef... rec = new HoldRecord(job.targetA.Thing.def, job.count); recs.Add(rec); // useful debug message... //Log.Message(string.Concat("Job was issued to pickup for this new record: ", rec)); }
/// <summary> /// Called when trying to find something to drop (ie coming back from a caravan). This is useful even on pawns without a loadout. /// </summary> /// <param name="dropThing">Thing to be dropped from inventory.</param> /// <param name="dropCount">Amount to drop from inventory.</param> /// <returns></returns> static public bool GetAnythingForDrop(this Pawn pawn, out Thing dropThing, out int dropCount) { dropThing = null; dropCount = 0; if (pawn.inventory == null || pawn.inventory.innerContainer == null) { return(false); } Loadout loadout = pawn.GetLoadout(); if (loadout == null || loadout.Slots.NullOrEmpty()) { List <HoldRecord> recs = LoadoutManager.GetHoldRecords(pawn); if (recs != null) { // hand out any inventory item not covered by a HoldRecord. foreach (Thing thing in pawn.inventory.innerContainer) { int numContained = pawn.inventory.innerContainer.TotalStackCountOfDef(thing.def); HoldRecord rec = recs.FirstOrDefault(hr => hr.thingDef == thing.def); if (rec == null) { // we don't have a HoldRecord for this thing, drop it. dropThing = thing; dropCount = numContained > dropThing.stackCount ? dropThing.stackCount : numContained; return(true); } if (numContained > rec.count) { dropThing = thing; dropCount = numContained - rec.count; dropCount = dropCount > dropThing.stackCount ? dropThing.stackCount : dropCount; return(true); } } } else { // we have nither a HoldTracker nor a Loadout that we can ask, so just pick stuff at random from Inventory. dropThing = pawn.inventory.innerContainer.RandomElement <Thing>(); dropCount = dropThing.stackCount; } } else { // hand out an item from GetExcessThing... return(GetExcessThing(pawn, out dropThing, out dropCount)); } return(false); }
/// <summary> /// Called when a pawn is instructed to drop something as well as if the user explicitly specifies the item should no longer be held onto. /// </summary> /// <param name="pawn"></param> /// <param name="thing">Thing who's def should be forgotten.</param> public static void HoldTrackerForget(this Pawn pawn, Thing thing) { List <HoldRecord> recs = LoadoutManager.GetHoldRecords(pawn); if (recs == null) { Log.Error(string.Concat(pawn.Name, " wasn't being tracked by HoldTracker and tried to forget a ThingDef ", thing.def, ".")); return; } HoldRecord rec = recs.FirstOrDefault(hr => hr.thingDef == thing.def); if (rec != null) { recs.RemoveAt(recs.IndexOf(rec)); } }
/// <summary> /// Used when a pawn is about to be ordered to pickup a Thing. /// </summary> /// <param name="pawn"></param> /// <param name="item"></param> /// <param name="count"></param> static public void Notify_HoldTrackerItem(this Pawn pawn, Thing item, int count) { // if the pawn doesn't have a normal loadout, nothing to do... if (pawn.GetLoadout().defaultLoadout) { return; } // find out if we are already remembering this thing on this pawn... List <HoldRecord> recs = LoadoutManager.GetHoldRecords(pawn); if (recs == null) { recs = new List <HoldRecord>(); LoadoutManager.AddHoldRecords(pawn, recs); } // could check isHeld but that tells us if there is a record AND it's been picked up. HoldRecord rec = recs.FirstOrDefault(hr => hr.thingDef == item.def); if (rec != null) { if (rec.pickedUp) { // modifying a record for which the pawn should have some of that thing in their inventory. rec.count = GetMagazineAwareStackCount(pawn, rec.thingDef) + count; } else { // modifying a record that hasn't been picked up... do it blind. rec.count += count; } // useful debug message... //Log.Message(string.Concat("Job was issued to pickup items for this existing record: ", rec)); return; } // if we got this far we know that there isn't a record being stored for this thingDef... rec = new HoldRecord(item.def, count); recs.Add(rec); // useful debug message... //Log.Message(string.Concat("Job was issued to pickup for this new record: ", rec)); }
/// <summary> /// Find an item that should be dropped from the pawn's inventory and how much to drop. /// </summary> /// <param name="pawn"></param> /// <param name="dropThing">The thing which should be dropped.</param> /// <param name="dropCount">The amount to drop.</param> /// <returns>bool, true indicates that the out variables are filled with something to do work on (drop).</returns> // NOTE (ProfoundDarkness): Ended up doing this by nibbling away at the pawn's inventory (or dictionary representation of ThingDefs/Count). // Probably not efficient but was easier to handle atm. static public bool GetExcessThing(this Pawn pawn, out Thing dropThing, out int dropCount) { //(ProfoundDarkness) Thanks to erdelf on the RimWorldMod discord for helping me figure out some dictionary stuff and C# concepts related to 'Primitives' (pass by Value). CompInventory inventory = pawn.TryGetComp <CompInventory>(); Loadout loadout = pawn.GetLoadout(); List <HoldRecord> records = LoadoutManager.GetHoldRecords(pawn); dropThing = null; dropCount = 0; if (inventory == null || inventory.container == null || loadout == null || loadout.Slots.NullOrEmpty()) { return(false); } Dictionary <ThingDef, Integer> listing = GetStorageByThingDef(pawn); // iterate over specifics and generics and Chip away at the dictionary. foreach (LoadoutSlot slot in loadout.Slots) { if (slot.thingDef != null && listing.ContainsKey(slot.thingDef)) { listing[slot.thingDef].value -= slot.count; if (listing[slot.thingDef].value <= 0) { listing.Remove(slot.thingDef); } } if (slot.genericDef != null) { List <ThingDef> killKeys = new List <ThingDef>(); int desiredCount = slot.count; // find dictionary entries which corespond to covered slot. foreach (ThingDef def in listing.Keys.Where(td => slot.genericDef.lambda(td))) { listing[def].value -= desiredCount; if (listing[def].value <= 0) { desiredCount = 0 - listing[def].value; killKeys.Add(def); // the thing in inventory is exausted, forget about it. } else { break; // we have satisifed this loadout so no need to keep enumerating. } } // cleanup dictionary. foreach (ThingDef def in killKeys) { listing.Remove(def); } } } // if there is something left in the dictionary, that is what is to be dropped. // Complicated by the fact that we now consider ammo in guns as part of the inventory... if (listing.Any()) { if (records != null && !records.NullOrEmpty()) { // look at each remaining 'uneaten' thingdef in pawn's inventory. foreach (ThingDef def in listing.Keys) { HoldRecord rec = records.FirstOrDefault(r => r.thingDef == def); if (rec == null) { // the item we have extra of has no HoldRecord, drop it. dropThing = inventory.container.FirstOrDefault(t => t.def == def && !pawn.IsItemQuestLocked(t)); if (dropThing != null) { dropCount = listing[def].value > dropThing.stackCount ? dropThing.stackCount : listing[def].value; return(true); } } else if (rec.count < listing[def].value) { // the item we have extra of HAS a HoldRecord but the amount carried is above the limit of the HoldRecord, drop extra. dropThing = pawn.inventory.innerContainer.FirstOrDefault(t => t.def == def && !pawn.IsItemQuestLocked(t)); if (dropThing != null) { dropCount = listing[def].value - rec.count; dropCount = dropCount > dropThing.stackCount ? dropThing.stackCount : dropCount; return(true); } } } } else { foreach (ThingDef def in listing.Keys) { dropThing = inventory.container.FirstOrDefault(t => t.GetInnerIfMinified().def == def && !pawn.IsItemQuestLocked(t)); if (dropThing != null) { dropCount = listing[def].value > dropThing.stackCount ? dropThing.stackCount : listing[def].value; return(true); } } } } // else return(false); }
/// <summary> /// Refreshes the cached bulk and weight. Call this whenever items are added/removed from inventory /// </summary> public void UpdateInventory() { if (parentPawn == null) { Log.Error("CompInventory on non-pawn " + parent.ToString()); return; } float newBulk = 0f; float newWeight = 0f; // Add equipped weapon if (parentPawn.equipment != null && parentPawn.equipment.Primary != null) { GetEquipmentStats(parentPawn.equipment.Primary, out newWeight, out newBulk); } // Add apparel if (parentPawn.apparel != null && parentPawn.apparel.WornApparelCount > 0) { foreach (Thing apparel in parentPawn.apparel.WornApparel) { float apparelBulk = apparel.GetStatValue(CE_StatDefOf.WornBulk); float apparelWeight = apparel.GetStatValue(StatDefOf.Mass); newBulk += apparelBulk; newWeight += apparelWeight; if (apparelBulk > 0 && parentPawn != null && parentPawn.IsColonist && parentPawn.Spawned) { LessonAutoActivator.TeachOpportunity(CE_ConceptDefOf.CE_WornBulk, OpportunityType.GoodToKnow); } } } // Add inventory items if (parentPawn.inventory != null && parentPawn.inventory.innerContainer != null) { ammoListCached.Clear(); meleeWeaponListCached.Clear(); rangedWeaponListCached.Clear(); List <HoldRecord> recs = LoadoutManager.GetHoldRecords(parentPawn); foreach (Thing thing in parentPawn.inventory.innerContainer) { // Check for weapons ThingWithComps eq = thing as ThingWithComps; CompEquippable compEq = thing.TryGetComp <CompEquippable>(); if (eq != null && compEq != null) { if (eq.def.IsRangedWeapon) { rangedWeaponListCached.Add(eq); } else { meleeWeaponListCached.Add(eq); } // Calculate equipment weight float eqWeight; float eqBulk; GetEquipmentStats(eq, out eqWeight, out eqBulk); newWeight += eqWeight * thing.stackCount; newBulk += eqBulk * thing.stackCount; } else { // Add item weight newBulk += thing.GetStatValue(CE_StatDefOf.Bulk) * thing.stackCount; newWeight += thing.GetStatValue(StatDefOf.Mass) * thing.stackCount; } // Update ammo list if (thing.def is AmmoDef) { ammoListCached.Add(thing); } if (recs != null) { HoldRecord rec = recs.FirstOrDefault(hr => hr.thingDef == thing.def); if (rec != null && !rec.pickedUp) { rec.pickedUp = true; } } } } currentBulkCached = newBulk; currentWeightCached = newWeight; }