public static void SetLoadoutById(this Pawn pawn, int loadoutId)
        {
            Loadout loadout = LoadoutManager.GetLoadoutById(loadoutId);

            if (loadout == null)
            {
                throw new ArgumentNullException("loadout");
            }

            SetLoadout(pawn, loadout);
        }
Beispiel #2
0
        public static void RemoveLoadout(Loadout loadout)
        {
            Instance._loadouts.Remove(loadout);

            // assign default loadout to pawns that used to use this loadout
            var obsolete = AssignedLoadouts.Where(a => a.Value == loadout).Select(a => a.Key);

            foreach (var id in obsolete)
            {
                AssignedLoadouts[id] = DefaultLoadout;
            }
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
        public static string GetWeightTip(this Loadout loadout)
        {
            float baseWeightCapacity = ThingDefOf.Human.GetStatValueAbstract(CarryWeight);
            float moveSpeedFactor    = Mathf.Lerp(1f, 0.75f, loadout.Weight / baseWeightCapacity);
            float encumberPenalty    = loadout.Weight > baseWeightCapacity ?
                                       loadout.Weight / baseWeightCapacity - 1 :
                                       0f;

            return("CR.DetailedBaseWeightTip".Translate(CarryWeight.ValueToString(baseWeightCapacity, CarryWeight.toStringNumberSense),
                                                        CarryWeight.ValueToString(loadout.Weight, CarryWeight.toStringNumberSense),
                                                        moveSpeedFactor.ToStringPercent(),
                                                        encumberPenalty.ToStringPercent()));
        }
Beispiel #5
0
        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);
            }
        }
Beispiel #6
0
        protected override void DrawPawnRow(Rect rect, Pawn p)
        {
            // available space for row
            Rect rowRect = new Rect(rect.x + 175f, rect.y, rect.width - 175f, 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 / 3f) + (_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);

            // label + button for loadout
            Rect loadoutRect = new Rect(outfitRect.xMax,
                                        rowRect.yMin,
                                        rowRect.width * (1f / 3f) - (_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 / 6f) - _margin, rowRect.height).ContractedBy(_margin / 2f);
            Rect bulkRect   = new Rect(weightRect.xMax, rowRect.yMin, rowRect.width * (1f / 6f) - _margin, rowRect.height).ContractedBy(_margin / 2f);

            // OUTFITS
            // main button
            if (Widgets.TextButton(labelOutfitRect, p.outfits.CurrentOutfit.label, true, false))
            {
                List <FloatMenuOption> options = new List <FloatMenuOption>();
                foreach (Outfit outfit in Find.Map.outfitDatabase.AllOutfits)
                {
                    // need to create a local copy for delegate
                    Outfit localOutfit = outfit;
                    options.Add(new FloatMenuOption(localOutfit.label, delegate
                    {
                        p.outfits.CurrentOutfit = localOutfit;
                    }, MenuOptionPriority.Medium, null, null));
                }
                Find.WindowStack.Add(new FloatMenu(options, false));
            }

            // edit button
            TooltipHandler.TipRegion(editOutfitRect, "CR.EditX".Translate("CR.outfit".Translate() + " " + p.outfits.CurrentOutfit.label));
            if (Widgets.ImageButton(editOutfitRect, _iconEdit))
            {
                Find.WindowStack.Add(new Dialog_ManageOutfits(p.outfits.CurrentOutfit));
            }

            // clear forced button
            if (p.outfits.forcedHandler.SomethingIsForced)
            {
                TooltipHandler.TipRegion(forcedOutfitRect, "ClearForcedApparel".Translate());
                if (Widgets.ImageButton(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));
            }

            // LOADOUTS
            // main button
            if (Widgets.TextButton(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.Medium, null, null));
                }
                Find.WindowStack.Add(new FloatMenu(options, false));
            }

            // edit button
            TooltipHandler.TipRegion(editLoadoutRect, "CR.EditX".Translate("CR.loadout".Translate() + " " + p.GetLoadout().LabelCap));
            if (Widgets.ImageButton(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());
            }
        }
        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 deleteRect = new Rect(newRect.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);

            var loadouts = LoadoutManager.Loadouts;

            // DRAW CONTENTS
            // buttons
            // select loadout
            if (Widgets.ButtonText(selectRect, "CR.SelectLoadout".Translate()))
            {
                List <FloatMenuOption> options = new List <FloatMenuOption>();

                if (loadouts.Count == 0)
                {
                    options.Add(new FloatMenuOption("CR.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, "CR.NewLoadout".Translate()))
            {
                var loadout = new Loadout();
                LoadoutManager.AddLoadout(loadout);
                CurrentLoadout = loadout;
            }
            // delete loadout
            if (loadouts.Any(l => l.canBeDeleted) && Widgets.ButtonText(deleteRect, "CR.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 = null;
                        }
                        loadouts.Remove(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, "CR.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, StatDef.Named("CarryWeight").defaultBaseValue, "CR.Weight".Translate(), CurrentLoadout.GetWeightTip());
                Utility_Loadouts.DrawBar(bulkBarRect, CurrentLoadout.Bulk, StatDef.Named("CarryBulk").defaultBaseValue, "CR.Bulk".Translate(), CurrentLoadout.GetBulkTip());
            }

            // done!
        }
Beispiel #8
0
 public static string GetWeightAndBulkTip(this Loadout loadout)
 {
     return(loadout.GetWeightTip() + "\n\n" + loadout.GetBulkTip());
 }
Beispiel #9
0
 public static void AddLoadout(Loadout loadout)
 {
     Instance._loadouts.Add(loadout);
 }
Beispiel #10
0
        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);
        }
Beispiel #11
0
        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);
        }
Beispiel #12
0
        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.jobs.curJob != null && pawn.jobs.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 (ThingDef ammoDef in primaryammouser.Props.ammoSet.ammoTypes)
                {
                    Thing ammoThing;
                    ammoThing = pawn.TryGetComp <CompInventory>().ammoList.Find(thing => thing.def == ammoDef);
                    if (ammoThing != null)
                    {
                        ammocount += ammoThing.stackCount;
                    }
                }
                float atw = primaryammouser.currentAmmo.GetStatValueAbstract(CR_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);
            }

            else
            {
                return(WorkPriority.None);
            }
        }
        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, "CR.EditX".Translate("CR.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, "CR.EditX".Translate("CR.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, "CR.EditX".Translate("CR.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());
            }
        }