/// <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); }
public static void SetLoadoutById(this Pawn pawn, int loadoutId) { Loadout loadout = LoadoutManager.GetLoadoutById(loadoutId); if (loadout == null) { throw new ArgumentNullException("loadout"); } SetLoadout(pawn, loadout); }
public static void RemoveLoadout(Loadout loadout) { Instance._loadouts.Remove(loadout); // assign default loadout to pawns that used to use this loadout IEnumerable <Pawn> obsolete = AssignedLoadouts.Where(a => a.Value == loadout).Select(a => a.Key); foreach (Pawn id in obsolete) { AssignedLoadouts[id] = DefaultLoadout; } }
private bool CheckForExcessItems(Pawn pawn) { //if (pawn.CurJob != null && pawn.CurJob.def == JobDefOf.Tame) return false; CompInventory inventory = pawn.TryGetComp <CompInventory>(); Loadout loadout = pawn.GetLoadout(); if (inventory == null || inventory.container == null || loadout == null || loadout.Slots.NullOrEmpty()) { return(false); } if (inventory.container.Count > loadout.SlotCount + 1) { return(true); } // Check to see if there is at least one loadout slot specifying currently equipped weapon ThingWithComps equipment = ((pawn.equipment == null) ? null : pawn.equipment.Primary) ?? null; if (equipment != null && !loadout.Slots.Any(slot => slot.Def == equipment.def && slot.Count >= 1)) { return(true); } // Go through each item in the inventory and see if its part of our loadout bool allowDropRaw = Find.TickManager.TicksGame > pawn.mindState?.lastInventoryRawFoodUseTick + ticksBeforeDropRaw; foreach (Thing thing in inventory.container) { if (allowDropRaw || !thing.def.IsNutritionGivingIngestible || thing.def.ingestible.preferability > FoodPreferability.RawTasty) { LoadoutSlot slot = loadout.Slots.FirstOrDefault(x => x.Def == thing.def); if (slot == null) { return(true); } int numContained = inventory.container.TotalStackCountOfDef(thing.def); // Add currently equipped gun if (pawn.equipment != null && pawn.equipment.Primary != null) { if (pawn.equipment.Primary.def == slot.Def) { numContained++; } } if (slot.Count < numContained) { return(true); } } } return(false); }
public Dialog_ManageLoadouts(Loadout loadout) { CurrentLoadout = null; if (loadout != null && !loadout.defaultLoadout) { CurrentLoadout = loadout; } SetSource(SourceSelection.Ranged); doCloseX = true; //doCloseButton = true; //Close button is awkward closeOnClickedOutside = true; Utility_Loadouts.UpdateColonistCapacities(); }
public Dialog_ManageLoadouts(Loadout loadout) { CurrentLoadout = null; if (loadout != null && !loadout.defaultLoadout) { CurrentLoadout = loadout; } SetSource(SourceSelection.Ranged); doCloseX = true; closeOnClickedOutside = true; this.closeOnAccept = false; this.closeOnCancel = false; Utility_Loadouts.UpdateColonistCapacities(); }
public static void SetLoadout(this Pawn pawn, Loadout loadout) { if (pawn == null) { throw new ArgumentNullException("pawn"); } if (LoadoutManager.AssignedLoadouts.ContainsKey(pawn)) { LoadoutManager.AssignedLoadouts[pawn] = loadout; } else { LoadoutManager.AssignedLoadouts.Add(pawn, loadout); } }
public static void RemoveLoadout(Loadout loadout) { if (_current == null) { return; } // assign default loadout to pawns that used to use this loadout List <Pawn> obsolete = AssignedLoadouts.Where(kvp => kvp.Value == loadout).Select(kvp => kvp.Key).ToList(); // ToList separates this from the dictionary, ienumerable in this case would break as we change the relationship. foreach (Pawn id in obsolete) { AssignedLoadouts[id] = DefaultLoadout; } _current._loadouts.Remove(loadout); }
private IEnumerable <Widgets.DropdownMenuElement <Loadout> > Button_GenerateMenu(Pawn pawn) { using (List <Loadout> .Enumerator enu = LoadoutManager.Loadouts.GetEnumerator()) { while (enu.MoveNext()) { Loadout loadout = enu.Current; yield return(new Widgets.DropdownMenuElement <Loadout> { option = new FloatMenuOption(loadout.LabelCap, delegate() { pawn.SetLoadout(loadout); }), payload = loadout }); } } }
/// <summary> /// Basically counts the amount of ThingDef that a loadout can have. Assumes Pawn has a Loadout (caller checked this already). /// </summary> /// <param name="pawn">Pawn who's loadout and holdtracker is to be inspected.</param> /// <param name = "def">ThingDef to match</param> /// <param name = "amount">int amount of ThingDef desired to be covered by Loadout/HoldTracker</param> /// <returns>int amount of that def that can be had.</returns> private bool TrackingSatisfied(Pawn pawn, ThingDef def, int amount) { // first check loadouts... Loadout loadout = pawn.GetLoadout(); foreach (LoadoutSlot slot in loadout.Slots) { if (slot.thingDef != null) { if (slot.thingDef == def) { amount -= slot.count; } } else if (slot.genericDef != null) { if (slot.genericDef.lambda(def)) { amount -= slot.count; } } if (amount <= 0) { return(true); } } // if we got here, also check holdRecords. List <HoldRecord> records = pawn.GetHoldRecords(); if (!records.NullOrEmpty()) { foreach (HoldRecord rec in records) { if (rec.thingDef == def) { amount -= rec.count; } } } return(false); }
/// <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); } 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)))); if (eqSlot == null) { dropEquipment = pawn.equipment.Primary; return(true); } return(false); }
// Returns a copy of this loadout slot with a new unique ID and a label based on the original name. // LoadoutSlots need to be copied. /// <summary> /// Handles copying one Loadout to a new Loadout object. /// </summary> /// <param name="source">Loadout from which to copy properties/fields from.</param> /// <returns>new Loadout with copied properties from 'source'</returns> /// <remarks> /// uniqueID will be different as required. /// label will be different as required, but related to original. /// Slots are copied (not the same object) but have the same properties as source.Slots. /// </remarks> static Loadout Copy(Loadout source) { string newName = source.label; Regex reNum = new Regex(@"^(.*?)\d+$"); if (reNum.IsMatch(newName)) { newName = reNum.Replace(newName, @"$1"); } newName = LoadoutManager.GetUniqueLabel(newName); Loadout dest = new Loadout(newName); dest.defaultLoadout = source.defaultLoadout; dest.canBeDeleted = source.canBeDeleted; dest._slots = new List <LoadoutSlot>(); foreach (LoadoutSlot slot in source.Slots) { dest.AddSlot(slot.Copy()); } return(dest); }
/// <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); } //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); }
public static string GetWeightAndBulkTip(this Loadout loadout) { return(loadout.GetWeightTip() + "\n\n" + loadout.GetBulkTip()); }
/* Rough Algorithm * Need Things so the collection of ammo users that use magazines. Also need a collection of ammo (ThingDef is OK here). * For each weapon (that fits above), * -If we have no ammo in inventory that the gun is loaded with, check loadouts/holdtracker for a clip's worth of ammo that the gun contains. * --Find ammo the gun uses that we have a clip's worth in inventory (should check it against loadout/holdtracker as well) * -If weapon is low on ammo and we have enough in inventory to fill it up. * * If either of the above are true, trigger a reload. */ /// <summary> /// Check's the pawn's equipment and inventory for weapons that could use a reload. /// </summary> /// <param name="pawn">Pawn to check the equipment and inventory of.</param> /// <param name="reloadWeapon">Thing weapon which needs to be reloaded.</param> /// <param name="reloadAmmo">AmmoDef ammo to reload the gun with.</param> /// <returns>Bool value indicating if the job needs to be done.</returns> private bool DoReloadCheck(Pawn pawn, out ThingWithComps reloadWeapon, out AmmoDef reloadAmmo) { reloadWeapon = null; reloadAmmo = null; // First need to create the collections that will be searched. List <ThingWithComps> guns = new List <ThingWithComps>(); CompInventory inventory = pawn.TryGetComp <CompInventory>(); CompAmmoUser tmpComp; Loadout loadout = pawn.GetLoadout(); bool pawnHasLoadout = loadout != null && !loadout.Slots.NullOrEmpty(); if (inventory == null) { return(false); // There isn't any work to do since the pawn doesn't have a CE Inventory. } if ((tmpComp = pawn.equipment?.Primary?.TryGetComp <CompAmmoUser>()) != null && tmpComp.HasMagazine) { guns.Add(pawn.equipment.Primary); } // CompInventory doesn't track equipment and it's desired to check the pawn's equipped weapon before inventory items so need to copy stuff from Inventory Cache. guns.AddRange(inventory.rangedWeaponList.Where(t => t.TryGetComp <CompAmmoUser>() != null && t.GetComp <CompAmmoUser>().HasMagazine)); if (guns.NullOrEmpty()) { return(false); // There isn't any work to do since the pawn doesn't have any ammo using guns. } // look at each gun... foreach (ThingWithComps gun in guns) { // Get key stats of the weapon. tmpComp = gun.TryGetComp <CompAmmoUser>(); AmmoDef ammoType = tmpComp.CurrentAmmo; int ammoAmount = tmpComp.CurMagCount; int magazineSize = tmpComp.Props.magazineSize; // Is the gun loaded with ammo not in a Loadout/HoldTracker? if (tmpComp.UseAmmo && pawnHasLoadout && !TrackingSatisfied(pawn, ammoType, magazineSize)) { // Do we have ammo in the inventory that the gun uses which satisfies requirements? (expensive) AmmoDef matchAmmo = tmpComp.Props.ammoSet.ammoTypes .Where(al => al.ammo != ammoType) .Select(al => al.ammo) .FirstOrDefault(ad => TrackingSatisfied(pawn, ad, magazineSize) && inventory.AmmoCountOfDef(ad) >= magazineSize); if (matchAmmo != null) { reloadWeapon = gun; reloadAmmo = matchAmmo; return(true); } } // Is the gun low on ammo? if (tmpComp.CurMagCount < magazineSize) { // Do we have enough ammo in the inventory to top it off? if (!tmpComp.UseAmmo || inventory.AmmoCountOfDef(ammoType) >= (magazineSize - tmpComp.CurMagCount)) { reloadWeapon = gun; reloadAmmo = ammoType; return(true); //} else { // (ProfoundDarkness) I think the idea of this branch was that the JobGiver might initiate fetching ammo but it actually runs AFTER the one that fetches ammo for loadout. // There wasn't enough in the inventory to top it off. At this point we know the loadout is satisfied for this ammo... // We could do a more strict check to see if the pawn's loadout is satisfied to pick up ammo and if not swap to another ammo...? } } } return(false); }
public override void DoWindowContents(Rect canvas) { // fix weird zooming bug Text.Font = GameFont.Small; // SET UP RECTS // top buttons Rect selectRect = new Rect(0f, 0f, canvas.width * .2f, _topAreaHeight); Rect newRect = new Rect(selectRect.xMax + _margin, 0f, canvas.width * .2f, _topAreaHeight); Rect copyRect = new Rect(newRect.xMax + _margin, 0f, canvas.width * .2f, _topAreaHeight); Rect deleteRect = new Rect(copyRect.xMax + _margin, 0f, canvas.width * .2f, _topAreaHeight); // main areas Rect nameRect = new Rect( 0f, _topAreaHeight + _margin * 2, (canvas.width - _margin) / 2f, 24f); Rect slotListRect = new Rect( 0f, nameRect.yMax + _margin, (canvas.width - _margin) / 2f, canvas.height - _topAreaHeight - nameRect.height - _barHeight * 2 - _margin * 5); Rect weightBarRect = new Rect(slotListRect.xMin, slotListRect.yMax + _margin, slotListRect.width, _barHeight); Rect bulkBarRect = new Rect(weightBarRect.xMin, weightBarRect.yMax + _margin, weightBarRect.width, _barHeight); Rect sourceButtonRect = new Rect( slotListRect.xMax + _margin, _topAreaHeight + _margin * 2, (canvas.width - _margin) / 2f, 24f); Rect selectionRect = new Rect( slotListRect.xMax + _margin, sourceButtonRect.yMax + _margin, (canvas.width - _margin) / 2f, canvas.height - 24f - _topAreaHeight - _margin * 3); LoadoutManager.SortLoadouts(); List <Loadout> loadouts = LoadoutManager.Loadouts.Where(l => !l.defaultLoadout).ToList(); // DRAW CONTENTS // buttons // select loadout if (Widgets.ButtonText(selectRect, "CE_SelectLoadout".Translate())) { List <FloatMenuOption> options = new List <FloatMenuOption>(); if (loadouts.Count == 0) { options.Add(new FloatMenuOption("CE_NoLoadouts".Translate(), null)); } else { for (int i = 0; i < loadouts.Count; i++) { int local_i = i; options.Add(new FloatMenuOption(loadouts[i].LabelCap, delegate { CurrentLoadout = loadouts[local_i]; })); } } Find.WindowStack.Add(new FloatMenu(options)); } // create loadout if (Widgets.ButtonText(newRect, "CE_NewLoadout".Translate())) { Loadout loadout = new Loadout(); loadout.AddBasicSlots(); LoadoutManager.AddLoadout(loadout); CurrentLoadout = loadout; } // copy loadout if (CurrentLoadout != null && Widgets.ButtonText(copyRect, "CE_CopyLoadout".Translate())) { CurrentLoadout = CurrentLoadout.Copy(); LoadoutManager.AddLoadout(CurrentLoadout); } // delete loadout if (loadouts.Any(l => l.canBeDeleted) && Widgets.ButtonText(deleteRect, "CE_DeleteLoadout".Translate())) { List <FloatMenuOption> options = new List <FloatMenuOption>(); for (int i = 0; i < loadouts.Count; i++) { int local_i = i; // don't allow deleting the default loadout if (!loadouts[i].canBeDeleted) { continue; } options.Add(new FloatMenuOption(loadouts[i].LabelCap, delegate { if (CurrentLoadout == loadouts[local_i]) { CurrentLoadout = LoadoutManager.DefaultLoadout; } LoadoutManager.RemoveLoadout(loadouts[local_i]); })); } Find.WindowStack.Add(new FloatMenu(options)); } // draw notification if no loadout selected if (CurrentLoadout == null) { Text.Anchor = TextAnchor.MiddleCenter; GUI.color = Color.grey; Widgets.Label(canvas, "CE_NoLoadoutSelected".Translate()); GUI.color = Color.white; Text.Anchor = TextAnchor.UpperLeft; // and stop further drawing return; } // name DrawNameField(nameRect); // source selection DrawSourceSelection(sourceButtonRect); // selection area DrawSlotSelection(selectionRect); // current slots DrawSlotList(slotListRect); // bars if (CurrentLoadout != null) { Utility_Loadouts.DrawBar(weightBarRect, CurrentLoadout.Weight, Utility_Loadouts.medianWeightCapacity, "CE_Weight".Translate(), CurrentLoadout.GetWeightTip()); Utility_Loadouts.DrawBar(bulkBarRect, CurrentLoadout.Bulk, Utility_Loadouts.medianBulkCapacity, "CE_Bulk".Translate(), CurrentLoadout.GetBulkTip()); } // done! }
protected override void DrawPawnRow(Rect rect, Pawn p) { // available space for row Rect rowRect = new Rect(rect.x + 165f, rect.y, rect.width - 165f, rect.height); // response button rect Vector2 responsePos = new Vector2(rowRect.xMin, rowRect.yMin + (rowRect.height - 24f) / 2f); // offset rest of row for that button, so we don't have to mess with all the other rect calculations rowRect.xMin += 24f + _margin; // label + buttons for outfit Rect outfitRect = new Rect(rowRect.xMin, rowRect.yMin, rowRect.width * (1f / 4f) + (_margin + _buttonSize) / 2f, rowRect.height); Rect labelOutfitRect = new Rect(outfitRect.xMin, outfitRect.yMin, outfitRect.width - _margin * 3 - _buttonSize * 2, outfitRect.height) .ContractedBy(_margin / 2f); Rect editOutfitRect = new Rect(labelOutfitRect.xMax + _margin, outfitRect.yMin + ((outfitRect.height - _buttonSize) / 2), _buttonSize, _buttonSize); Rect forcedOutfitRect = new Rect(labelOutfitRect.xMax + _buttonSize + _margin * 2, outfitRect.yMin + ((outfitRect.height - _buttonSize) / 2), _buttonSize, _buttonSize); // drucg policy Rect drugRect = new Rect(outfitRect.xMax, rowRect.yMin, rowRect.width * (1f / 4f) - (_margin + _buttonSize) / 2f, rowRect.height); Rect labelDrugRect = new Rect(drugRect.xMin, drugRect.yMin, drugRect.width - _margin * 2 - _buttonSize, drugRect.height) .ContractedBy(_margin / 2f); Rect editDrugRect = new Rect(labelDrugRect.xMax + _margin, drugRect.yMin + ((drugRect.height - _buttonSize) / 2), _buttonSize, _buttonSize); // label + button for loadout Rect loadoutRect = new Rect(drugRect.xMax, rowRect.yMin, rowRect.width * (1f / 4f) - (_margin + _buttonSize) / 2f, rowRect.height); Rect labelLoadoutRect = new Rect(loadoutRect.xMin, loadoutRect.yMin, loadoutRect.width - _margin * 2 - _buttonSize, loadoutRect.height) .ContractedBy(_margin / 2f); Rect editLoadoutRect = new Rect(labelLoadoutRect.xMax + _margin, loadoutRect.yMin + ((loadoutRect.height - _buttonSize) / 2), _buttonSize, _buttonSize); // fight or flight button HostilityResponseModeUtility.DrawResponseButton(responsePos, p); // weight + bulk indicators Rect weightRect = new Rect(loadoutRect.xMax, rowRect.yMin, rowRect.width * (1f / 8f) - _margin, rowRect.height).ContractedBy(_margin / 2f); Rect bulkRect = new Rect(weightRect.xMax + _margin, rowRect.yMin, rowRect.width * (1f / 8f) - _margin, rowRect.height).ContractedBy(_margin / 2f); // OUTFITS // main button if (Widgets.ButtonText(labelOutfitRect, p.outfits.CurrentOutfit.label, true, false)) { List <FloatMenuOption> options = new List <FloatMenuOption>(); foreach (Outfit current in Current.Game.outfitDatabase.AllOutfits) { // need to create a local copy for delegate Outfit localOut = current; options.Add(new FloatMenuOption(localOut.label, delegate { p.outfits.CurrentOutfit = localOut; }, MenuOptionPriority.Default, null, null)); } Find.WindowStack.Add(new FloatMenu(options, optionalTitle, false)); } // edit button TooltipHandler.TipRegion(editOutfitRect, "CE_EditX".Translate("CE_outfit".Translate() + " " + p.outfits.CurrentOutfit.label)); if (Widgets.ButtonImage(editOutfitRect, _iconEdit)) { Text.Font = GameFont.Small; Find.WindowStack.Add(new Dialog_ManageOutfits(p.outfits.CurrentOutfit)); } // clear forced button if (p.outfits.forcedHandler.SomethingIsForced) { TooltipHandler.TipRegion(forcedOutfitRect, "ClearForcedApparel".Translate()); if (Widgets.ButtonImage(forcedOutfitRect, _iconClearForced)) { p.outfits.forcedHandler.Reset(); } TooltipHandler.TipRegion(forcedOutfitRect, new TipSignal(delegate { string text = "ForcedApparel".Translate() + ":\n"; foreach (Apparel current2 in p.outfits.forcedHandler.ForcedApparel) { text = text + "\n " + current2.LabelCap; } return(text); }, p.GetHashCode() * 612)); } // DRUG POLICY // main button string textDrug = p.drugs.CurrentPolicy.label; if (p.story != null && p.story.traits != null) { Trait trait = p.story.traits.GetTrait(TraitDefOf.DrugDesire); if (trait != null) { textDrug = textDrug + " (" + trait.Label + ")"; } } if (Widgets.ButtonText(labelDrugRect, textDrug, true, false, true)) { List <FloatMenuOption> list = new List <FloatMenuOption>(); foreach (DrugPolicy current in Current.Game.drugPolicyDatabase.AllPolicies) { DrugPolicy localAssignedDrugs = current; list.Add(new FloatMenuOption(current.label, delegate { p.drugs.CurrentPolicy = localAssignedDrugs; }, MenuOptionPriority.Default, null, null, 0f, null)); } Find.WindowStack.Add(new FloatMenu(list)); PlayerKnowledgeDatabase.KnowledgeDemonstrated(ConceptDefOf.DrugPolicies, KnowledgeAmount.Total); } // edit button TooltipHandler.TipRegion(editDrugRect, "CE_EditX".Translate("CE_drugs".Translate() + " " + p.drugs.CurrentPolicy.label)); if (Widgets.ButtonImage(editDrugRect, _iconEdit)) { Text.Font = GameFont.Small; Find.WindowStack.Add(new Dialog_ManageDrugPolicies(p.drugs.CurrentPolicy)); PlayerKnowledgeDatabase.KnowledgeDemonstrated(ConceptDefOf.DrugPolicies, KnowledgeAmount.Total); } // LOADOUTS // main button if (Widgets.ButtonText(labelLoadoutRect, p.GetLoadout().LabelCap, true, false)) { List <FloatMenuOption> options = new List <FloatMenuOption>(); foreach (Loadout loadout in LoadoutManager.Loadouts) { // need to create a local copy for delegate Loadout localLoadout = loadout; options.Add(new FloatMenuOption(localLoadout.LabelCap, delegate { p.SetLoadout(localLoadout); }, MenuOptionPriority.Default, null, null)); } Find.WindowStack.Add(new FloatMenu(options, optionalTitle, false)); } // edit button TooltipHandler.TipRegion(editLoadoutRect, "CE_EditX".Translate("CE_loadout".Translate() + " " + p.GetLoadout().LabelCap)); if (Widgets.ButtonImage(editLoadoutRect, _iconEdit)) { Find.WindowStack.Add(new Dialog_ManageLoadouts(p.GetLoadout())); } // STATUS BARS // fetch the comp CompInventory comp = p.TryGetComp <CompInventory>(); if (comp != null) { Utility_Loadouts.DrawBar(bulkRect, comp.currentBulk, comp.capacityBulk, "", p.GetBulkTip()); Utility_Loadouts.DrawBar(weightRect, comp.currentWeight, comp.capacityWeight, "", p.GetWeightTip()); } }
/// <summary> /// Tries to give the pawn a job related to picking up or dropping an item from their inventory. /// </summary> /// <param name="pawn">Pawn to which the job is given.</param> /// <returns>Job that the pawn was instructed to do, be it hauling a dropped Thing or going and getting a Thing.</returns> protected override Job TryGiveJob(Pawn pawn) { // Get inventory CompInventory inventory = pawn.TryGetComp <CompInventory>(); if (inventory == null) { return(null); } Loadout loadout = pawn.GetLoadout(); if (loadout != null) { ThingWithComps dropEq; if (pawn.GetExcessEquipment(out dropEq)) { ThingWithComps droppedEq; if (pawn.equipment.TryDropEquipment(pawn.equipment.Primary, out droppedEq, pawn.Position, false)) { if (droppedEq != null) { return(HaulAIUtility.HaulToStorageJob(pawn, droppedEq)); } Log.Error(string.Concat(pawn, " tried dropping ", dropEq, " from loadout but resulting thing is null")); } } Thing dropThing; int dropCount; if (pawn.GetExcessThing(out dropThing, out dropCount)) { Thing droppedThing; if (inventory.container.TryDrop(dropThing, pawn.Position, pawn.Map, ThingPlaceMode.Near, dropCount, out droppedThing)) { if (droppedThing != null) { return(HaulAIUtility.HaulToStorageJob(pawn, droppedThing)); } Log.Error(string.Concat(pawn, " tried dropping ", dropThing, " from loadout but resulting thing is null")); } } // Find missing items ItemPriority priority; Thing closestThing; int count; Pawn carriedBy; bool doEquip = false; LoadoutSlot prioritySlot = GetPrioritySlot(pawn, out priority, out closestThing, out count, out carriedBy); // moved logic to detect if should equip vs put in inventory here... if (closestThing != null) { if (closestThing.TryGetComp <CompEquippable>() != null && !(pawn.story != null && pawn.WorkTagIsDisabled(WorkTags.Violent)) && (pawn.health != null && pawn.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation)) && (pawn.equipment == null || pawn.equipment.Primary == null || !loadout.Slots.Any(s => s.thingDef == pawn.equipment.Primary.def || (s.genericDef != null && s.countType == LoadoutCountType.pickupDrop && s.genericDef.lambda(pawn.equipment.Primary.def))))) { doEquip = true; } if (carriedBy == null) { // Equip gun if unarmed or current gun is not in loadout if (doEquip) { return(new Job(JobDefOf.Equip, closestThing)); } return(new Job(JobDefOf.TakeInventory, closestThing) { count = Mathf.Min(closestThing.stackCount, count) }); } else { return(new Job(CE_JobDefOf.TakeFromOther, closestThing, carriedBy, doEquip ? pawn : null) { count = doEquip ? 1 : Mathf.Min(closestThing.stackCount, count) }); } } } return(null); }
/// <summary> /// This starts the work of finding something lacking that the pawn should pickup. /// </summary> /// <param name="pawn">Pawn who's inventory and loadout should be considered.</param> /// <param name="priority">Priority value for how important doing this job is.</param> /// <param name="closestThing">The thing found to be picked up.</param> /// <param name="count">The amount of closestThing to pickup. Already checked if inventory can hold it.</param> /// <param name="carriedBy">If unable to find something on the ground to pickup, the pawn (pack animal/prisoner) which has the item to take.</param> /// <returns>LoadoutSlot which has something that can be picked up.</returns> private LoadoutSlot GetPrioritySlot(Pawn pawn, out ItemPriority priority, out Thing closestThing, out int count, out Pawn carriedBy) { priority = ItemPriority.None; LoadoutSlot slot = null; closestThing = null; count = 0; carriedBy = null; CompInventory inventory = pawn.TryGetComp <CompInventory>(); if (inventory != null && inventory.container != null) { Loadout loadout = pawn.GetLoadout(); if (loadout != null && !loadout.Slots.NullOrEmpty()) { // Need to generate a dictionary and nibble like when dropping in order to allow for conflicting loadouts to work properly. Dictionary <ThingDef, Integer> listing = pawn.GetStorageByThingDef(); // process each loadout slot... (While the LoadoutSlot.countType property only really makes sense in the context of genericDef != null, it should be the correct value (pickupDrop) on .thingDef != null.) foreach (LoadoutSlot curSlot in loadout.Slots.Where(s => s.countType != LoadoutCountType.dropExcess)) { Thing curThing = null; ItemPriority curPriority = ItemPriority.None; Pawn curCarrier = null; int wantCount = curSlot.count; if (curSlot.thingDef != null) { if (listing.ContainsKey(curSlot.thingDef)) { int amount = listing[curSlot.thingDef].value >= wantCount ? wantCount : listing[curSlot.thingDef].value; listing[curSlot.thingDef].value -= amount; wantCount -= amount; if (listing[curSlot.thingDef].value <= 0) { listing.Remove(curSlot.thingDef); } } } if (curSlot.genericDef != null) { List <ThingDef> killKeys = new List <ThingDef>(); int amount; foreach (ThingDef def in listing.Keys.Where(td => curSlot.genericDef.lambda(td))) { amount = listing[def].value >= wantCount ? wantCount : listing[def].value; listing[def].value -= amount; wantCount -= amount; if (listing[def].value <= 0) { killKeys.Add(def); } if (wantCount <= 0) { break; } } foreach (ThingDef def in killKeys) // oddly enough removing a dictionary entry while enumerating hasn't caused a problem but this is the 'correct' way based on reading. { listing.Remove(def); } } if (wantCount > 0) { FindPickup(pawn, curSlot, wantCount, out curPriority, out curThing, out curCarrier); if (curPriority > priority && curThing != null && inventory.CanFitInInventory(curThing, out count)) { priority = curPriority; slot = curSlot; count = count >= wantCount ? wantCount : count; closestThing = curThing; if (curCarrier != null) { carriedBy = curCarrier; } } if (priority >= ItemPriority.LowStock) { break; } } } } } return(slot); }
protected override void FillTab() { // get the inventory comp CompInventory comp = SelPawn.TryGetComp <CompInventory>(); // set up rects Rect listRect = new Rect( _margin, _topPadding, size.x - 2 * _margin, size.y - _topPadding - _margin); if (comp != null) { PlayerKnowledgeDatabase.KnowledgeDemonstrated(CE_ConceptDefOf.CE_InventoryWeightBulk, KnowledgeAmount.FrameDisplayed); // adjust rects if comp found listRect.height -= (_margin / 2 + _barHeight) * 2; Rect weightRect = new Rect(_margin, listRect.yMax + _margin / 2, listRect.width, _barHeight); Rect bulkRect = new Rect(_margin, weightRect.yMax + _margin / 2, listRect.width, _barHeight); // draw bars Utility_Loadouts.DrawBar(bulkRect, comp.currentBulk, comp.capacityBulk, "CE_Bulk".Translate(), SelPawn.GetBulkTip()); Utility_Loadouts.DrawBar(weightRect, comp.currentWeight, comp.capacityWeight, "CE_Weight".Translate(), SelPawn.GetWeightTip()); // draw text overlays on bars Text.Font = GameFont.Small; Text.Anchor = TextAnchor.MiddleCenter; string currentBulk = CE_StatDefOf.CarryBulk.ValueToString(comp.currentBulk, CE_StatDefOf.CarryBulk.toStringNumberSense); string capacityBulk = CE_StatDefOf.CarryBulk.ValueToString(comp.capacityBulk, CE_StatDefOf.CarryBulk.toStringNumberSense); Widgets.Label(bulkRect, currentBulk + "/" + capacityBulk); string currentWeight = comp.currentWeight.ToString("0.#"); string capacityWeight = CE_StatDefOf.CarryWeight.ValueToString(comp.capacityWeight, CE_StatDefOf.CarryWeight.toStringNumberSense); Widgets.Label(weightRect, currentWeight + "/" + capacityWeight); Text.Anchor = TextAnchor.UpperLeft; } // start drawing list (rip from ITab_Pawn_Gear) GUI.BeginGroup(listRect); Text.Font = GameFont.Small; GUI.color = Color.white; Rect outRect = new Rect(0f, 0f, listRect.width, listRect.height); Rect viewRect = new Rect(0f, 0f, listRect.width - 16f, _scrollViewHeight); Widgets.BeginScrollView(outRect, ref _scrollPosition, viewRect); float num = 0f; TryDrawComfyTemperatureRange(ref num, viewRect.width); if (ShouldShowOverallArmor(SelPawnForGear)) { Widgets.ListSeparator(ref num, viewRect.width, "OverallArmor".Translate()); TryDrawOverallArmor(ref num, viewRect.width, StatDefOf.ArmorRating_Blunt, "ArmorBlunt".Translate(), " " + "CE_MPa".Translate()); TryDrawOverallArmor(ref num, viewRect.width, StatDefOf.ArmorRating_Sharp, "ArmorSharp".Translate(), "CE_mmRHA".Translate()); TryDrawOverallArmor(ref num, viewRect.width, StatDefOf.ArmorRating_Heat, "ArmorHeat".Translate(), "%"); } if (ShouldShowEquipment(SelPawnForGear)) { Widgets.ListSeparator(ref num, viewRect.width, "Equipment".Translate()); foreach (ThingWithComps current in SelPawnForGear.equipment.AllEquipmentListForReading) { DrawThingRow(ref num, viewRect.width, current); } } if (ShouldShowApparel(SelPawnForGear)) { Widgets.ListSeparator(ref num, viewRect.width, "Apparel".Translate()); foreach (Apparel current2 in from ap in SelPawnForGear.apparel.WornApparel orderby ap.def.apparel.bodyPartGroups[0].listOrder descending select ap) { DrawThingRow(ref num, viewRect.width, current2); } } if (ShouldShowInventory(SelPawnForGear)) { // get the loadout so we can make a decision to show a button. bool showMakeLoadout = false; Loadout curLoadout = SelPawnForGear.GetLoadout(); if (SelPawnForGear.IsColonist && (curLoadout == null || curLoadout.Slots.NullOrEmpty()) && (SelPawnForGear.inventory.innerContainer.Any() || SelPawnForGear.equipment?.Primary != null)) { showMakeLoadout = true; } if (showMakeLoadout) { num += 3; // Make a little room for the button. } float buttonY = num; // Could be accomplished with seperator being after the button... Widgets.ListSeparator(ref num, viewRect.width, "Inventory".Translate()); // only offer this button if the pawn has no loadout or has the default loadout and there are things/equipment... if (showMakeLoadout) { Rect loadoutButtonRect = new Rect(viewRect.width / 2, buttonY, viewRect.width / 2, 26f); // button is half the available width... if (Widgets.ButtonText(loadoutButtonRect, "CE_MakeLoadout".Translate())) { Loadout loadout = SelPawnForGear.GenerateLoadoutFromPawn(); LoadoutManager.AddLoadout(loadout); SelPawnForGear.SetLoadout(loadout); // Opening this window is the same way as if from the assign tab so should be correct. Find.WindowStack.Add(new Dialog_ManageLoadouts(SelPawnForGear.GetLoadout())); } } workingInvList.Clear(); workingInvList.AddRange(SelPawnForGear.inventory.innerContainer); for (int i = 0; i < workingInvList.Count; i++) { DrawThingRow(ref num, viewRect.width, workingInvList[i].GetInnerIfMinified(), true); } } if (Event.current.type == EventType.Layout) { _scrollViewHeight = num + 30f; } Widgets.EndScrollView(); GUI.EndGroup(); GUI.color = Color.white; Text.Anchor = TextAnchor.UpperLeft; }
/* (ProfoundDarkness) I've intentionally left some code remarked in the following code because it's a useful guide on how to create * and maintain the transpilers that will do nearly identical changes to RimWorld's code for the other 2 PawnColumnWorkers. */ public override void DoCell(Rect rect, Pawn pawn, PawnTable table) { if (pawn.outfits == null) { return; } //changed: int num = Mathf.FloorToInt((rect.width - 4f) * 0.714285731f); int num = Mathf.FloorToInt((rect.width - 4f) - IconSize); //changed: int num2 = Mathf.FloorToInt((rect.width - 4f) * 0.2857143f); int num2 = Mathf.FloorToInt(IconSize); float num3 = rect.x; //added: float num4 = rect.y + ((rect.height - IconSize) / 2); // Reduce width if we're adding a clear forced button bool somethingIsForced = pawn.HoldTrackerAnythingHeld(); Rect loadoutButtonRect = new Rect(num3, rect.y + 2f, (float)num, rect.height - 4f); if (somethingIsForced) { loadoutButtonRect.width -= 4f + (float)num2; } // Main loadout button string label = pawn.GetLoadout().label.Truncate(loadoutButtonRect.width, null); if (Widgets.ButtonText(loadoutButtonRect, label, true, false, true)) { LoadoutManager.SortLoadouts(); List <FloatMenuOption> options = new List <FloatMenuOption>(); foreach (Loadout loadout in LoadoutManager.Loadouts) { // need to create a local copy for delegate Loadout localLoadout = loadout; options.Add(new FloatMenuOption(localLoadout.LabelCap, delegate { pawn.SetLoadout(localLoadout); }, MenuOptionPriority.Default, null, null)); } Find.WindowStack.Add(new FloatMenu(options)); } // Clear forced button num3 += loadoutButtonRect.width; num3 += 4f; //changed: Rect forcedHoldRect = new Rect(num3, rect.y + 2f, (float)num2, rect.height - 4f); Rect forcedHoldRect = new Rect(num3, num4, (float)num2, (float)num2); if (somethingIsForced) { //changed: if (Widgets.ButtonText(forcedHoldRect, "ClearForcedApparel".Translate(), true, false, true)) // "Clear forced" is sufficient and that's what this is at the moment. if (Widgets.ButtonImage(forcedHoldRect, ClearImage)) { pawn.HoldTrackerClear(); // yes this will also delete records that haven't been picked up and thus not shown to the player... } TooltipHandler.TipRegion(forcedHoldRect, new TipSignal(delegate { string text = "CE_ForcedHold".Translate() + ":\n"; foreach (HoldRecord rec in LoadoutManager.GetHoldRecords(pawn)) { if (!rec.pickedUp) { continue; } text = text + "\n " + rec.thingDef.LabelCap + " x" + rec.count; } return(text); }, pawn.GetHashCode() * 613)); num3 += (float)num2; num3 += 4f; } //changed: Rect assignTabRect = new Rect(num3, rect.y + 2f, (float)num2, rect.height - 4f); Rect assignTabRect = new Rect(num3, num4, (float)num2, (float)num2); //changed: if (Widgets.ButtonText(assignTabRect, "AssignTabEdit".Translate(), true, false, true)) if (Widgets.ButtonImage(assignTabRect, EditImage)) { Find.WindowStack.Add(new Dialog_ManageLoadouts(pawn.GetLoadout())); } // Added this next line. TooltipHandler.TipRegion(assignTabRect, new TipSignal(textGetter("CE_Loadouts"), pawn.GetHashCode() * 613)); num3 += (float)num2; }
private WorkPriority GetPriorityWork(Pawn pawn) { CompAmmoUser primaryammouser = pawn.equipment.Primary.TryGetComp <CompAmmoUser>(); if (pawn.Faction.IsPlayer && pawn.equipment.Primary != null && pawn.equipment.Primary.TryGetComp <CompAmmoUser>() != null) { Loadout loadout = pawn.GetLoadout(); // if (loadout != null && !loadout.Slots.NullOrEmpty()) if (loadout != null && loadout.SlotCount > 0) { return(WorkPriority.None); } } if (pawn.kindDef.trader) { return(WorkPriority.None); } if (pawn.CurJob != null && pawn.CurJob.def == JobDefOf.Tame) { return(WorkPriority.None); } if (pawn.equipment.Primary == null) { if (Unload(pawn)) { return(WorkPriority.Unloading); } else { return(WorkPriority.Weapon); } } if (pawn.equipment.Primary != null && primaryammouser != null) { int ammocount = 0; foreach (AmmoLink link in primaryammouser.Props.ammoSet.ammoTypes) { Thing ammoThing; ammoThing = pawn.TryGetComp <CompInventory>().ammoList.Find(thing => thing.def == link.ammo); if (ammoThing != null) { ammocount += ammoThing.stackCount; } } float atw = primaryammouser.CurrentAmmo.GetStatValueAbstract(CE_StatDefOf.Bulk); if ((ammocount < (1.5f / atw)) && ((1.5f / atw) > 3)) { if (Unload(pawn)) { return(WorkPriority.Unloading); } else { return(WorkPriority.LowAmmo); } } if (!PawnUtility.EnemiesAreNearby(pawn, 30, true)) { if ((ammocount < (3.5f / atw)) && ((3.5f / atw) > 4)) { if (Unload(pawn)) { return(WorkPriority.Unloading); } else { return(WorkPriority.Ammo); } } } } /* * if (!pawn.Faction.IsPlayer && pawn.equipment.Primary != null * && !PawnUtility.EnemiesAreNearby(pawn, 30, true) || (!pawn.apparel.BodyPartGroupIsCovered(BodyPartGroupDefOf.Torso) || !pawn.apparel.BodyPartGroupIsCovered(BodyPartGroupDefOf.Legs))) ||{ || return WorkPriority.Apparel; ||} */ return(WorkPriority.None); }
protected override void FillTab() { // get the inventory comp CompInventory comp = SelPawn.TryGetComp <CompInventory>(); // set up rects Rect listRect = new Rect( _margin, _topPadding, size.x - 2 * _margin, size.y - _topPadding - _margin); if (comp != null) { PlayerKnowledgeDatabase.KnowledgeDemonstrated(CE_ConceptDefOf.CE_InventoryWeightBulk, KnowledgeAmount.FrameDisplayed); // adjust rects if comp found listRect.height -= (_margin / 2 + _barHeight) * 2; Rect weightRect = new Rect(_margin, listRect.yMax + _margin / 2, listRect.width, _barHeight); Rect bulkRect = new Rect(_margin, weightRect.yMax + _margin / 2, listRect.width, _barHeight); Utility_Loadouts.DrawBar(bulkRect, comp.currentBulk, comp.capacityBulk, "CE_Bulk".Translate(), SelPawn.GetBulkTip()); Utility_Loadouts.DrawBar(weightRect, comp.currentWeight, comp.capacityWeight, "CE_Weight".Translate(), SelPawn.GetWeightTip()); } // start drawing list (rip from ITab_Pawn_Gear) GUI.BeginGroup(listRect); Text.Font = GameFont.Small; GUI.color = Color.white; Rect outRect = new Rect(0f, 0f, listRect.width, listRect.height); Rect viewRect = new Rect(0f, 0f, listRect.width - 16f, _scrollViewHeight); Widgets.BeginScrollView(outRect, ref _scrollPosition, viewRect); float num = 0f; TryDrawComfyTemperatureRange(ref num, viewRect.width); if (SelPawnForGear.apparel != null) { bool flag = false; TryDrawAverageArmor(ref num, viewRect.width, StatDefOf.ArmorRating_Blunt, "ArmorBlunt".Translate(), ref flag); TryDrawAverageArmor(ref num, viewRect.width, StatDefOf.ArmorRating_Sharp, "ArmorSharp".Translate(), ref flag); TryDrawAverageArmor(ref num, viewRect.width, StatDefOf.ArmorRating_Heat, "ArmorHeat".Translate(), ref flag); TryDrawAverageArmor(ref num, viewRect.width, StatDefOf.ArmorRating_Electric, "ArmorElectric".Translate(), ref flag); } if (SelPawnForGear.equipment != null) { Widgets.ListSeparator(ref num, viewRect.width, "Equipment".Translate()); foreach (ThingWithComps current in SelPawnForGear.equipment.AllEquipmentListForReading) { DrawThingRow(ref num, viewRect.width, current); } } if (SelPawnForGear.apparel != null) { Widgets.ListSeparator(ref num, viewRect.width, "Apparel".Translate()); foreach (Apparel current2 in from ap in SelPawnForGear.apparel.WornApparel orderby ap.def.apparel.bodyPartGroups[0].listOrder descending select ap) { DrawThingRow(ref num, viewRect.width, current2); } } if (SelPawnForGear.inventory != null) { // get the loadout so we can make a decision to show a button. bool showMakeLoadout = false; Loadout curLoadout = SelPawnForGear.GetLoadout(); if (SelPawnForGear.IsColonist && (curLoadout == null || curLoadout.Slots.NullOrEmpty()) && (SelPawnForGear.inventory.innerContainer.Any() || SelPawnForGear.equipment?.Primary != null)) { showMakeLoadout = true; } if (showMakeLoadout) { num += 3; // Make a little room for the button. } float buttonY = num; // Could be accomplished with seperator being after the button... Widgets.ListSeparator(ref num, viewRect.width, "Inventory".Translate()); // only offer this button if the pawn has no loadout or has the default loadout and there are things/equipment... if (showMakeLoadout) { Rect loadoutButtonRect = new Rect(viewRect.width / 2, buttonY, viewRect.width / 2, 26f); // button is half the available width... if (Widgets.ButtonText(loadoutButtonRect, "Make Loadout")) { Loadout loadout = SelPawnForGear.GenerateLoadoutFromPawn(); LoadoutManager.AddLoadout(loadout); SelPawnForGear.SetLoadout(loadout); // UNDONE ideally we'd open the assign (MainTabWindow_OutfitsAndLoadouts) tab as if the user clicked on it here. // (ProfoundDarkness) But I have no idea how to do that just yet. The attempts I made seem to put the RimWorld UI into a bit of a bad state. // ie opening the tab like the dialog below. // Need to understand how RimWorld switches tabs and see if something similar can be done here // (or just remove the unfinished marker). // Opening this window is the same way as if from the assign tab so should be correct. Find.WindowStack.Add(new Dialog_ManageLoadouts(SelPawnForGear.GetLoadout())); } } workingInvList.Clear(); workingInvList.AddRange(SelPawnForGear.inventory.innerContainer); for (int i = 0; i < workingInvList.Count; i++) { DrawThingRow(ref num, viewRect.width, workingInvList[i].GetInnerIfMinified(), true); } } if (Event.current.type == EventType.Layout) { _scrollViewHeight = num + 30f; } Widgets.EndScrollView(); GUI.EndGroup(); GUI.color = Color.white; Text.Anchor = TextAnchor.UpperLeft; }
/// <summary> /// Generates a loadout from a pawn's current equipment and inventory. Attempts to put items which fit in Generics that are default/DropExcess into said Generic. /// </summary> /// <param name="pawn">Pawn to check equipment/inventory on and generate a Loadout from.</param> /// <returns>Loadout which was generated based on Pawn's inventory.</returns> public static Loadout GenerateLoadoutFromPawn(this Pawn pawn) { // generate the name for this new pawn based loadout. string newName = string.Concat(pawn.Name.ToStringShort, " ", "CE_DefaultLoadoutName".Translate()); Regex reNum = new Regex(@"^(.*?)\d+$"); if (reNum.IsMatch(newName)) { newName = reNum.Replace(newName, @"$1"); } newName = LoadoutManager.GetUniqueLabel(newName); // set basic loadout properties. Loadout loadout = new Loadout(newName); loadout.defaultLoadout = false; loadout.canBeDeleted = true; LoadoutSlot slot = null; // grab the pawn's current equipment as a loadoutslot. if (pawn.equipment?.Primary != null) { slot = new LoadoutSlot(pawn.equipment.Primary.def); loadout.AddSlot(slot); } // get a list of generics which are drop only. Assumes that anything that doesn't fit here is a Specific slot later. IEnumerable <LoadoutGenericDef> generics = DefDatabase <LoadoutGenericDef> .AllDefs.Where(gd => gd.defaultCountType == LoadoutCountType.dropExcess); // enumerate each item in the pawn's inventory and add appropriate slots. foreach (Thing thing in pawn.inventory.innerContainer) { LoadoutGenericDef foundGeneric = null; // first check if it's a generic-able item... foreach (LoadoutGenericDef generic in generics) { if (generic.lambda(thing.def)) { foundGeneric = generic; break; } } // assign a loadout slot that fits the thing. if (foundGeneric != null) { slot = new LoadoutSlot(foundGeneric, thing.stackCount); } else { slot = new LoadoutSlot(thing.def, thing.stackCount); } // add the slot (this also takes care of adding to existing slots) loadout.AddSlot(slot); } // finally check the loadout and make sure that it has sufficient generics like what happens with a new loadout in the management UI. foreach (LoadoutGenericDef generic in generics.Where(gd => gd.isBasic)) { slot = loadout.Slots.FirstOrDefault(s => s.genericDef == generic); if (slot != null) { if (slot.count < slot.genericDef.defaultCount) { slot.count = slot.genericDef.defaultCount; } } else { slot = new LoadoutSlot(generic); loadout.AddSlot(slot); } } return(loadout); }
private WorkPriority GetPriorityWork(Pawn pawn) { CompAmmoUser primaryammouser = pawn.equipment.Primary.TryGetComp <CompAmmoUser>(); CompInventory compammo = pawn.TryGetComp <CompInventory>(); if (pawn.Faction.IsPlayer && pawn.equipment.Primary != null && pawn.equipment.Primary.TryGetComp <CompAmmoUser>() != null) { Loadout loadout = pawn.GetLoadout(); // if (loadout != null && !loadout.Slots.NullOrEmpty()) if (loadout != null && loadout.SlotCount > 0) { return(WorkPriority.None); } } if (pawn.kindDef.trader) { return(WorkPriority.None); } if (pawn.CurJob != null && pawn.CurJob.def == JobDefOf.Tame) { return(WorkPriority.None); } if (pawn.equipment.Primary == null) { if (Unload(pawn)) { return(WorkPriority.Unloading); } else { return(WorkPriority.Weapon); } } if (pawn.equipment.Primary != null && primaryammouser != null) { int ammocount = 0; foreach (AmmoLink link in primaryammouser.Props.ammoSet.ammoTypes) { Thing ammoThing; ammoThing = compammo.ammoList.Find(thing => thing.def == link.ammo); if (ammoThing != null) { ammocount += ammoThing.stackCount; } } // current ammo bulk float currentAmmoBulk = primaryammouser.CurrentAmmo.GetStatValueAbstract(CE_StatDefOf.Bulk); // weapon magazine size float stackSize = primaryammouser.Props.magazineSize; // weight projectile ratio to free bulk with x1.5 reserve float weightProjectileRatio = Mathf.RoundToInt(((compammo.capacityBulk - compammo.currentBulk) / 1.5f) / currentAmmoBulk); if (ammocount < stackSize * 1 && (ammocount < weightProjectileRatio)) { if (Unload(pawn)) { return(WorkPriority.Unloading); } else { return(WorkPriority.LowAmmo); } } if (!PawnUtility.EnemiesAreNearby(pawn, 30, true)) { if (ammocount < stackSize * 2 && (ammocount < weightProjectileRatio)) { if (Unload(pawn)) { return(WorkPriority.Unloading); } else { return(WorkPriority.Ammo); } } } } /* * if (!pawn.Faction.IsPlayer && pawn.equipment.Primary != null * && !PawnUtility.EnemiesAreNearby(pawn, 30, true) || (!pawn.apparel.BodyPartGroupIsCovered(BodyPartGroupDefOf.Torso) || !pawn.apparel.BodyPartGroupIsCovered(BodyPartGroupDefOf.Legs))) ||{ || return WorkPriority.Apparel; ||} */ return(WorkPriority.None); }
protected override Job TryGiveJob(Pawn pawn) { // Get inventory CompInventory inventory = pawn.TryGetComp <CompInventory>(); if (inventory == null) { return(null); } Loadout loadout = pawn.GetLoadout(); if (loadout != null) { // Find and drop excess items foreach (LoadoutSlot slot in loadout.Slots) { int numContained = inventory.container.TotalStackCountOfDef(slot.Def); // Add currently equipped gun if (pawn.equipment != null && pawn.equipment.Primary != null) { if (pawn.equipment.Primary.def == slot.Def) { numContained++; } } // Drop excess items if (numContained > slot.Count) { Thing thing = inventory.container.FirstOrDefault(x => x.def == slot.Def); if (thing != null) { Thing droppedThing; if (inventory.container.TryDrop(thing, pawn.Position, pawn.Map, ThingPlaceMode.Near, numContained - slot.Count, out droppedThing)) { if (droppedThing != null) { return(HaulAIUtility.HaulToStorageJob(pawn, droppedThing)); } Log.Error(pawn + " tried dropping " + thing + " from loadout but resulting thing is null"); } } } } // Try drop currently equipped weapon if (pawn.equipment != null && pawn.equipment.Primary != null && !loadout.Slots.Any(slot => slot.Def == pawn.equipment.Primary.def && slot.Count >= 1)) { ThingWithComps droppedEq; if (pawn.equipment.TryDropEquipment(pawn.equipment.Primary, out droppedEq, pawn.Position, false)) { return(HaulAIUtility.HaulToStorageJob(pawn, droppedEq)); } } // Find excess items in inventory that are not part of our loadout bool allowDropRaw = Find.TickManager.TicksGame > pawn.mindState?.lastInventoryRawFoodUseTick + ticksBeforeDropRaw; Thing thingToRemove = inventory.container.FirstOrDefault(t => (allowDropRaw || !t.def.IsNutritionGivingIngestible || t.def.ingestible.preferability > FoodPreferability.RawTasty) && !loadout.Slots.Any(s => s.Def == t.def)); if (thingToRemove != null) { Thing droppedThing; if (inventory.container.TryDrop(thingToRemove, pawn.Position, pawn.Map, ThingPlaceMode.Near, thingToRemove.stackCount, out droppedThing)) { return(HaulAIUtility.HaulToStorageJob(pawn, droppedThing)); } Log.Error(pawn + " tried dropping " + thingToRemove + " from inventory but resulting thing is null"); } // Find missing items ItemPriority priority; Thing closestThing; int count; LoadoutSlot prioritySlot = GetPrioritySlot(pawn, out priority, out closestThing, out count); if (closestThing != null) { // Equip gun if unarmed or current gun is not in loadout if (closestThing.TryGetComp <CompEquippable>() != null && (pawn.health != null && pawn.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation)) && (pawn.equipment == null || pawn.equipment.Primary == null || !loadout.Slots.Any(s => s.Def == pawn.equipment.Primary.def))) { return(new Job(JobDefOf.Equip, closestThing)); } // Take items into inventory if needed int numContained = inventory.container.TotalStackCountOfDef(prioritySlot.Def); return(new Job(JobDefOf.TakeInventory, closestThing) { count = Mathf.Min(closestThing.stackCount, prioritySlot.Count - numContained, count) }); } } return(null); }
/// <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); }
private LoadoutSlot GetPrioritySlot(Pawn pawn, out ItemPriority priority, out Thing closestThing, out int count) { priority = ItemPriority.None; LoadoutSlot slot = null; closestThing = null; count = 0; CompInventory inventory = pawn.TryGetComp <CompInventory>(); if (inventory != null && inventory.container != null) { Loadout loadout = pawn.GetLoadout(); if (loadout != null && !loadout.Slots.NullOrEmpty()) { foreach (LoadoutSlot curSlot in loadout.Slots) { ItemPriority curPriority = ItemPriority.None; Thing curThing = null; int numCarried = inventory.container.TotalStackCountOfDef(curSlot.Def); // Add currently equipped gun if (pawn.equipment != null && pawn.equipment.Primary != null) { if (pawn.equipment.Primary.def == curSlot.Def) { numCarried++; } } if (numCarried < curSlot.Count) { curThing = GenClosest.ClosestThingReachable( pawn.Position, pawn.Map, ThingRequest.ForDef(curSlot.Def), PathEndMode.ClosestTouch, TraverseParms.For(pawn, Danger.None, TraverseMode.ByPawn), proximitySearchRadius, x => !x.IsForbidden(pawn) && pawn.CanReserve(x)); if (curThing != null) { curPriority = ItemPriority.Proximity; } else { curThing = GenClosest.ClosestThingReachable( pawn.Position, pawn.Map, ThingRequest.ForDef(curSlot.Def), PathEndMode.ClosestTouch, TraverseParms.For(pawn, Danger.None, TraverseMode.ByPawn), maximumSearchRadius, x => !x.IsForbidden(pawn) && pawn.CanReserve(x)); if (curThing != null) { if (!curSlot.Def.IsNutritionGivingIngestible && numCarried / curSlot.Count <= 0.5f) { curPriority = ItemPriority.LowStock; } else { curPriority = ItemPriority.Low; } } } } if (curPriority > priority && curThing != null && inventory.CanFitInInventory(curThing, out count)) { priority = curPriority; slot = curSlot; closestThing = curThing; } if (priority >= ItemPriority.LowStock) { break; } } } } return(slot); }
private WorkPriority GetPriorityWork(Pawn pawn) { #region Traders have no work priority if (pawn.kindDef.trader) { return(WorkPriority.None); } #endregion //#region Pawns with non-idle jobs have no work priority //bool hasCurJob = pawn.CurJob != null; //JobDef jobDef = hasCurJob ? pawn.CurJob.def : null; //if (hasCurJob && !jobDef.isIdle) //{ // return WorkPriority.None; //} //#endregion bool hasPrimary = (pawn.equipment != null && pawn.equipment.Primary != null); CompAmmoUser primaryAmmoUser = hasPrimary ? pawn.equipment.Primary.TryGetComp <CompAmmoUser>() : hasWeaponInInventory(pawn) ? weaponInInventory(pawn) : null; #region Colonists with primary ammo-user and a loadout have no work priority if (pawn.Faction.IsPlayer && primaryAmmoUser != null) { Loadout loadout = pawn.GetLoadout(); // if (loadout != null && !loadout.Slots.NullOrEmpty()) if (loadout != null && loadout.SlotCount > 0) { return(WorkPriority.None); } } #endregion // Pawns without weapon.. if (!hasPrimary) { // With inventory && non-colonist && not stealing && little space left if (Unload(pawn)) { return(WorkPriority.Unloading); } // Without inventory || colonist || stealing || lots of space left if (!hasWeaponInInventory(pawn)) { return(WorkPriority.Weapon); } } CompInventory compInventory = pawn.TryGetComp <CompInventory>(); // Pawn with ammo-using weapon.. if (primaryAmmoUser != null && primaryAmmoUser.UseAmmo) { // Magazine size FloatRange magazineSize = new FloatRange(1f, 2f); LoadoutPropertiesExtension loadoutPropertiesExtension = (LoadoutPropertiesExtension)(pawn.kindDef.modExtensions?.FirstOrDefault(x => x is LoadoutPropertiesExtension)); bool hasWeaponTags = pawn.kindDef.weaponTags?.Any() ?? false; if (hasWeaponTags && primaryAmmoUser.parent.def.weaponTags.Any(pawn.kindDef.weaponTags.Contains) && loadoutPropertiesExtension != null && loadoutPropertiesExtension.primaryMagazineCount != FloatRange.Zero) { magazineSize.min = loadoutPropertiesExtension.primaryMagazineCount.min; magazineSize.max = loadoutPropertiesExtension.primaryMagazineCount.max; } magazineSize.min *= primaryAmmoUser.Props.magazineSize; magazineSize.max *= primaryAmmoUser.Props.magazineSize; // Number of things in inventory that could be put in the weapon int viableAmmoCarried = 0; float viableAmmoBulk = 0; foreach (AmmoLink link in primaryAmmoUser.Props.ammoSet.ammoTypes) { var count = compInventory.AmmoCountOfDef(link.ammo); viableAmmoCarried += count; viableAmmoBulk += count * link.ammo.GetStatValueAbstract(CE_StatDefOf.Bulk); } // ~2/3rds of the inventory bulk minus non-usable and non-ammo bulk could be filled with ammo float potentialAmmoBulk = ammoFractionOfNonAmmoInventory * (compInventory.capacityBulk - compInventory.currentBulk + viableAmmoBulk); // There's less ammo [bulk] than fits the potential ammo bulk [bulk] if (viableAmmoBulk < potentialAmmoBulk) { // There's less ammo [nr] than fits a clip [nr] if (primaryAmmoUser.Props.magazineSize == 0 || viableAmmoCarried < magazineSize.min) { return(Unload(pawn) ? WorkPriority.Unloading : WorkPriority.LowAmmo); } // There's less ammo [nr] than fits two clips [nr] && no enemies are close if (viableAmmoCarried < magazineSize.max && !PawnUtility.EnemiesAreNearby(pawn, 20, true)) { return(Unload(pawn) ? WorkPriority.Unloading : WorkPriority.Ammo); } } } return(WorkPriority.None); }
public static void AddLoadout(Loadout loadout) { Instance._loadouts.Add(loadout); }