code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using UnityEngine;
using Verse;

using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using RimWorld.Planet;
using Verse;

namespace rjw_CORE_EXPOSED
{
	[StaticConstructorOnStartup]
	public static class rjwHealthCardUtility
	{
		public static Vector2 scrollPosition = Vector2.zero;

		public static float scrollViewHeight = 0f;

		public static bool highlight = true;

		public static bool onOperationTab = false;

		public static Vector2 billsScrollPosition = Vector2.zero;

		public static float billsScrollHeight = 1000f;

		public static bool showAllHediffs = false;

		public static bool showHediffsDebugInfo = false;

		public const float TopPadding = 20f;

		public const float ThoughtLevelHeight = 25f;

		public const float ThoughtLevelSpacing = 4f;

		public const float IconSize = 20f;

		public static readonly Color HighlightColor = new Color(0.5f, 0.5f, 0.5f, 1f);

		public static readonly Color StaticHighlightColor = new Color(0.75f, 0.75f, 0.85f, 1f);

		public static readonly Color VeryPoorColor = new Color(0.4f, 0.4f, 0.4f);

		public static readonly Color PoorColor = new Color(0.55f, 0.55f, 0.55f);

		public static readonly Color WeakenedColor = new Color(0.7f, 0.7f, 0.7f);

		public static readonly Color EnhancedColor = new Color(0.5f, 0.5f, 0.9f);

		public static readonly Color MediumPainColor = new Color(0.9f, 0.9f, 0f);

		public static readonly Color SeverePainColor = new Color(0.9f, 0.5f, 0f);

		public static readonly Texture2D BleedingIcon = ContentFinder<Texture2D>.Get("UI/Icons/Medical/Bleeding", true);

		public static List<ThingDef> tmpMedicineBestToWorst = new List<ThingDef>();

		public static void DrawPawnHealthCard(Rect outRect, Pawn pawn, bool allowOperations, bool showBloodLoss, Thing thingForMedBills)
		{
			if (pawn.Dead && allowOperations)
			{
				Log.Error("Called DrawPawnHealthCard with a dead pawn and allowOperations=true. Operations are disallowed on corpses.", false);
				allowOperations = false;
			}
			outRect = outRect.Rounded();
			Rect rect = new Rect(outRect.x, outRect.y, outRect.width * 0.375f, outRect.height).Rounded();
			Rect rect2 = new Rect(rect.xMax, outRect.y, outRect.width - rect.width, outRect.height);
			rect.yMin += 11f;
			rjwHealthCardUtility.DrawHealthSummary(rect, pawn, allowOperations, thingForMedBills);
			rjwHealthCardUtility.DrawHediffListing(rect2.ContractedBy(10f), pawn, showBloodLoss);
		}

		public static void DrawHealthSummary(Rect rect, Pawn pawn, bool allowOperations, Thing thingForMedBills)
		{
			GUI.color = Color.white;
			if (!allowOperations)
			{
				rjwHealthCardUtility.onOperationTab = false;
			}
			Widgets.DrawMenuSection(rect);
			List<TabRecord> list = new List<TabRecord>();
			list.Add(new TabRecord("HealthOverview".Translate(), delegate
			{
				rjwHealthCardUtility.onOperationTab = false;
			}, !rjwHealthCardUtility.onOperationTab));
			if (allowOperations)
			{
				string label = (!pawn.RaceProps.IsMechanoid) ? "MedicalOperationsShort".Translate(pawn.BillStack.Count) : "MedicalOperationsMechanoidsShort".Translate(pawn.BillStack.Count);
				list.Add(new TabRecord(label, delegate
				{
					rjwHealthCardUtility.onOperationTab = true;
				}, rjwHealthCardUtility.onOperationTab));
			}
			TabDrawer.DrawTabs(rect, list, 200f);
			rect = rect.ContractedBy(9f);
			GUI.BeginGroup(rect);
			float curY = 0f;
			Text.Font = GameFont.Medium;
			GUI.color = Color.white;
			Text.Anchor = TextAnchor.UpperCenter;
			if (rjwHealthCardUtility.onOperationTab)
			{
				PlayerKnowledgeDatabase.KnowledgeDemonstrated(ConceptDefOf.MedicalOperations, KnowledgeAmount.FrameDisplayed);
				curY = rjwHealthCardUtility.DrawMedOperationsTab(rect, pawn, thingForMedBills, curY);
			}
			else
			{
				curY = rjwHealthCardUtility.DrawOverviewTab(rect, pawn, curY);
			}
			Text.Font = GameFont.Small;
			GUI.color = Color.white;
			Text.Anchor = TextAnchor.UpperLeft;
			GUI.EndGroup();
		}

		public static void DrawHediffListing(Rect rect, Pawn pawn, bool showBloodLoss)
		{
			GUI.color = Color.white;
			if (Prefs.DevMode && Current.ProgramState == ProgramState.Playing)
			{
				rjwHealthCardUtility.DoDebugOptions(rect, pawn);
			}
			GUI.BeginGroup(rect);
			float lineHeight = Text.LineHeight;
			Rect outRect = new Rect(0f, 0f, rect.width, rect.height - lineHeight);
			Rect viewRect = new Rect(0f, 0f, rect.width - 16f, rjwHealthCardUtility.scrollViewHeight);
			Rect rect2 = rect;
			if (viewRect.height > outRect.height)
			{
				rect2.width -= 16f;
			}
			Widgets.BeginScrollView(outRect, ref rjwHealthCardUtility.scrollPosition, viewRect, true);
			GUI.color = Color.white;
			float num = 0f;
			rjwHealthCardUtility.highlight = true;
			bool flag = false;
			foreach (IGrouping<BodyPartRecord, Hediff> current in rjwHealthCardUtility.VisibleHediffGroupsInOrder(pawn, showBloodLoss))
			{
				flag = true;
				rjwHealthCardUtility.DrawHediffRow(rect2, pawn, current, ref num);
			}
			if (!flag)
			{
				Widgets.NoneLabelCenteredVertically(new Rect(0f, 0f, viewRect.width, outRect.height), "(" + "NoHealthConditions".Translate() + ")");
				num = outRect.height - 1f;
			}
			if (Event.current.type == EventType.Layout)
			{
				rjwHealthCardUtility.scrollViewHeight = num;
			}
			Widgets.EndScrollView();
			float bleedRateTotal = pawn.health.hediffSet.BleedRateTotal;
			if (bleedRateTotal > 0.01f)
			{
				Rect rect3 = new Rect(0f, rect.height - lineHeight, rect.width, lineHeight);
				string text = string.Concat(new string[]
				{
					"BleedingRate".Translate(),
					": ",
					bleedRateTotal.ToStringPercent(),
					"/",
					"LetterDay".Translate()
				});
				int num2 = HealthUtility.TicksUntilDeathDueToBloodLoss(pawn);
				if (num2 < 60000)
				{
					text = text + " (" + "TimeToDeath".Translate(num2.ToStringTicksToPeriod()) + ")";
				}
				else
				{
					text = text + " (" + "WontBleedOutSoon".Translate() + ")";
				}
				Widgets.Label(rect3, text);
			}
			GUI.EndGroup();
			GUI.color = Color.white;
		}

		[DebuggerHidden]
		public static IEnumerable<IGrouping<BodyPartRecord, Hediff>> VisibleHediffGroupsInOrder(Pawn pawn, bool showBloodLoss)
		{
			foreach (IGrouping<BodyPartRecord, Hediff> group in from x in rjwHealthCardUtility.VisibleHediffs(pawn, showBloodLoss)
			group x by x.Part into x
			orderby rjwHealthCardUtility.GetListPriority(x.First<Hediff>().Part) descending
			select x)
			{
				yield return group;
			}
		}

		public static float GetListPriority(BodyPartRecord rec)
		{
			if (rec == null)
			{
				return 9999999f;
			}
			return (float)((int)rec.height * 10000) + rec.coverageAbsWithChildren;
		}

		[DebuggerHidden]
		public static IEnumerable<Hediff> VisibleHediffs(Pawn pawn, bool showBloodLoss)
		{
			if (!rjwHealthCardUtility.showAllHediffs)
			{
				List<Hediff_MissingPart> mpca = pawn.health.hediffSet.GetMissingPartsCommonAncestors();
				for (int i = 0; i < mpca.Count; i++)
				{
					yield return mpca[i];
				}
				IEnumerable<Hediff> visibleDiffs = from d in pawn.health.hediffSet.hediffs
				where !(d is Hediff_MissingPart) && d.Visible && (showBloodLoss || d.def != HediffDefOf.BloodLoss)
				select d;
				foreach (Hediff diff in visibleDiffs)
				{
					yield return diff;
				}
			}
			else
			{
				foreach (Hediff diff2 in pawn.health.hediffSet.hediffs)
				{
					yield return diff2;
				}
			}
		}

		public static float DrawMedOperationsTab(Rect leftRect, Pawn pawn, Thing thingForMedBills, float curY)
		{
			curY += 2f;
			Func<List<FloatMenuOption>> recipeOptionsMaker = delegate
			{
				List<FloatMenuOption> list = new List<FloatMenuOption>();
				foreach (RecipeDef current in thingForMedBills.def.AllRecipes)
				{
					if (current.AvailableNow)
					{
						IEnumerable<ThingDef> enumerable = current.PotentiallyMissingIngredients(null, thingForMedBills.Map);
						if (!enumerable.Any((ThingDef x) => x.isTechHediff))
						{
							if (!enumerable.Any((ThingDef x) => x.IsDrug))
							{
								if (!enumerable.Any<ThingDef>() || !current.dontShowIfAnyIngredientMissing)
								{
									if (current.targetsBodyPart)
									{
										foreach (BodyPartRecord current2 in current.Worker.GetPartsToApplyOn(pawn, current))
										{
											list.Add(rjwHealthCardUtility.GenerateSurgeryOption(pawn, thingForMedBills, current, enumerable, current2));
										}
									}
									else
									{
										list.Add(rjwHealthCardUtility.GenerateSurgeryOption(pawn, thingForMedBills, current, enumerable, null));
									}
								}
							}
						}
					}
				}
				return list;
			};
			Rect rect = new Rect(leftRect.x - 9f, curY, leftRect.width, leftRect.height - curY - 20f);
			((IBillGiver)thingForMedBills).BillStack.DoListing(rect, recipeOptionsMaker, ref rjwHealthCardUtility.billsScrollPosition, ref rjwHealthCardUtility.billsScrollHeight);
			return curY;
		}

		public static FloatMenuOption GenerateSurgeryOption(Pawn pawn, Thing thingForMedBills, RecipeDef recipe, IEnumerable<ThingDef> missingIngredients, BodyPartRecord part = null)
		{
			string text = recipe.Worker.GetLabelWhenUsedOn(pawn, part).CapitalizeFirst();
			if (part != null && !recipe.hideBodyPartNames)
			{
				text = text + " (" + part.Label + ")";
			}
			FloatMenuOption floatMenuOption;
			if (missingIngredients.Any<ThingDef>())
			{
				text += " (";
				bool flag = true;
				foreach (ThingDef current in missingIngredients)
				{
					if (!flag)
					{
						text += ", ";
					}
					flag = false;
					text += "MissingMedicalBillIngredient".Translate(current.label);
				}
				text += ")";
				floatMenuOption = new FloatMenuOption(text, null, MenuOptionPriority.Default, null, null, 0f, null, null);
			}
			else
			{
				Action action = delegate
				{
					Pawn pawn2 = thingForMedBills as Pawn;
					if (pawn2 == null)
					{
						return;
					}
					Bill_Medical bill_Medical = new Bill_Medical(recipe);
					pawn2.BillStack.AddBill(bill_Medical);
					bill_Medical.Part = part;
					if (recipe.conceptLearned != null)
					{
						PlayerKnowledgeDatabase.KnowledgeDemonstrated(recipe.conceptLearned, KnowledgeAmount.Total);
					}
					Map map = thingForMedBills.Map;
					if (!map.mapPawns.FreeColonists.Any((Pawn col) => recipe.PawnSatisfiesSkillRequirements(col)))
					{
						Bill.CreateNoPawnsWithSkillDialog(recipe);
					}
					if (!pawn2.InBed() && pawn2.RaceProps.IsFlesh)
					{
						if (pawn2.RaceProps.Humanlike)
						{
							if (!map.listerBuildings.allBuildingsColonist.Any((Building x) => x is Building_Bed && RestUtility.CanUseBedEver(pawn, x.def) && ((Building_Bed)x).Medical))
							{
								Messages.Message("MessageNoMedicalBeds".Translate(), pawn2, MessageTypeDefOf.CautionInput, false);
							}
						}
						else if (!map.listerBuildings.allBuildingsColonist.Any((Building x) => x is Building_Bed && RestUtility.CanUseBedEver(pawn, x.def)))
						{
							Messages.Message("MessageNoAnimalBeds".Translate(), pawn2, MessageTypeDefOf.CautionInput, false);
						}
					}
					if (pawn2.Faction != null && !pawn2.Faction.def.hidden && !pawn2.Faction.HostileTo(Faction.OfPlayer) && recipe.Worker.IsViolationOnPawn(pawn2, part, Faction.OfPlayer))
					{
						Messages.Message("MessageMedicalOperationWillAngerFaction".Translate(pawn2.Faction), pawn2, MessageTypeDefOf.CautionInput, false);
					}
					ThingDef minRequiredMedicine = rjwHealthCardUtility.GetMinRequiredMedicine(recipe);
					if (minRequiredMedicine != null && pawn2.playerSettings != null && !pawn2.playerSettings.medCare.AllowsMedicine(minRequiredMedicine))
					{
						Messages.Message("MessageTooLowMedCare".Translate(minRequiredMedicine.label, pawn2.LabelShort, pawn2.playerSettings.medCare.GetLabel(), pawn2.Named("PAWN")), pawn2, MessageTypeDefOf.CautionInput, false);
					}
				};
				floatMenuOption = new FloatMenuOption(text, action, MenuOptionPriority.Default, null, null, 0f, null, null);
			}
			floatMenuOption.extraPartWidth = 29f;
			floatMenuOption.extraPartOnGUI = ((Rect rect) => Widgets.InfoCardButton(rect.x + 5f, rect.y + (rect.height - 24f) / 2f, recipe));
			return floatMenuOption;
		}

		public static ThingDef GetMinRequiredMedicine(RecipeDef recipe)
		{
			rjwHealthCardUtility.tmpMedicineBestToWorst.Clear();
			List<ThingDef> allDefsListForReading = DefDatabase<ThingDef>.AllDefsListForReading;
			for (int i = 0; i < allDefsListForReading.Count; i++)
			{
				if (allDefsListForReading[i].IsMedicine)
				{
					rjwHealthCardUtility.tmpMedicineBestToWorst.Add(allDefsListForReading[i]);
				}
			}
			rjwHealthCardUtility.tmpMedicineBestToWorst.SortByDescending((ThingDef x) => x.GetStatValueAbstract(StatDefOf.MedicalPotency, null));
			ThingDef thingDef = null;
			for (int j = 0; j < recipe.ingredients.Count; j++)
			{
				ThingDef thingDef2 = null;
				for (int k = 0; k < rjwHealthCardUtility.tmpMedicineBestToWorst.Count; k++)
				{
					if (recipe.ingredients[j].filter.Allows(rjwHealthCardUtility.tmpMedicineBestToWorst[k]))
					{
						thingDef2 = rjwHealthCardUtility.tmpMedicineBestToWorst[k];
					}
				}
				if (thingDef2 != null && (thingDef == null || thingDef2.GetStatValueAbstract(StatDefOf.MedicalPotency, null) > thingDef.GetStatValueAbstract(StatDefOf.MedicalPotency, null)))
				{
					thingDef = thingDef2;
				}
			}
			rjwHealthCardUtility.tmpMedicineBestToWorst.Clear();
			return thingDef;
		}

		public static float DrawOverviewTab(Rect leftRect, Pawn pawn, float curY)
		{
			curY += 4f;
			Text.Font = GameFont.Tiny;
			Text.Anchor = TextAnchor.UpperLeft;
			GUI.color = new Color(0.9f, 0.9f, 0.9f);
			string text = string.Empty;
			if (pawn.gender != Gender.None)
			{
				text = pawn.GetGenderLabel() + " ";
			}
			text = text + pawn.def.label + ", " + "AgeIndicator".Translate(pawn.ageTracker.AgeNumberString);
			Rect rect = new Rect(0f, curY, leftRect.width, 34f);
			Widgets.Label(rect, text.CapitalizeFirst());
			TooltipHandler.TipRegion(rect, () => pawn.ageTracker.AgeTooltipString, 73412);
			if (Mouse.IsOver(rect))
			{
				Widgets.DrawHighlight(rect);
			}
			GUI.color = Color.white;
			curY += 34f;
			if (pawn.foodRestriction != null && pawn.foodRestriction.Configurable)
			{
				Rect rect2 = new Rect(0f, curY, leftRect.width * 0.42f, 23f);
				Text.Anchor = TextAnchor.MiddleLeft;
				Widgets.Label(rect2, "FoodRestriction".Translate());
				GenUI.ResetLabelAlign();
				Rect rect3 = new Rect(rect2.width, curY, leftRect.width - rect2.width, 23f);
				if (Widgets.ButtonText(rect3, pawn.foodRestriction.CurrentFoodRestriction.label, true, false, true))
				{
					List<FloatMenuOption> list = new List<FloatMenuOption>();
					List<FoodRestriction> allFoodRestrictions = Current.Game.foodRestrictionDatabase.AllFoodRestrictions;
					for (int i = 0; i < allFoodRestrictions.Count; i++)
					{
						FoodRestriction localRestriction = allFoodRestrictions[i];
						list.Add(new FloatMenuOption(localRestriction.label, delegate
						{
							pawn.foodRestriction.CurrentFoodRestriction = localRestriction;
						}, MenuOptionPriority.Default, null, null, 0f, null, null));
					}
					list.Add(new FloatMenuOption("ManageFoodRestrictions".Translate(), delegate
					{
						Find.WindowStack.Add(new Dialog_ManageFoodRestrictions(null));
					}, MenuOptionPriority.Default, null, null, 0f, null, null));
					Find.WindowStack.Add(new FloatMenu(list));
				}
				curY += 23f;
			}
			if (pawn.IsColonist && !pawn.Dead)
			{
				bool selfTend = pawn.playerSettings.selfTend;
				Rect rect4 = new Rect(0f, curY, leftRect.width, 24f);
				Widgets.CheckboxLabeled(rect4, "SelfTend".Translate(), ref pawn.playerSettings.selfTend, false, null, null, false);
				if (pawn.playerSettings.selfTend && !selfTend)
				{
					if (pawn.story.WorkTypeIsDisabled(WorkTypeDefOf.Doctor))
					{
						pawn.playerSettings.selfTend = false;
						Messages.Message("MessageCannotSelfTendEver".Translate(pawn.LabelShort, pawn), MessageTypeDefOf.RejectInput, false);
					}
					else if (pawn.workSettings.GetPriority(WorkTypeDefOf.Doctor) == 0)
					{
						Messages.Message("MessageSelfTendUnsatisfied".Translate(pawn.LabelShort, pawn), MessageTypeDefOf.CautionInput, false);
					}
				}
				TooltipHandler.TipRegion(rect4, "SelfTendTip".Translate(Faction.OfPlayer.def.pawnsPlural, 0.7f.ToStringPercent()).CapitalizeFirst());
				curY += 28f;
			}
			if (pawn.playerSettings != null && !pawn.Dead && Current.ProgramState == ProgramState.Playing)
			{
				Rect rect5 = new Rect(0f, curY, 140f, 28f);
				MedicalCareUtility.MedicalCareSetter(rect5, ref pawn.playerSettings.medCare);
				if (Widgets.ButtonText(new Rect(leftRect.width - 70f, curY, 70f, 28f), "MedGroupDefaults".Translate(), true, false, true))
				{
					Find.WindowStack.Add(new Dialog_MedicalDefaults());
				}
				curY += 32f;
			}
			Text.Font = GameFont.Small;
			if (pawn.def.race.IsFlesh)
			{
				Pair<string, Color> painLabel = rjwHealthCardUtility.GetPainLabel(pawn);
				string painTip = rjwHealthCardUtility.GetPainTip(pawn);
				curY = rjwHealthCardUtility.DrawLeftRow(leftRect, curY, "PainLevel".Translate(), painLabel.First, painLabel.Second, painTip);
			}
			if (!pawn.Dead)
			{
				IEnumerable<PawnCapacityDef> source;
				if (pawn.def.race.Humanlike)
				{
					source = from x in DefDatabase<PawnCapacityDef>.AllDefs
					where x.showOnHumanlikes
					select x;
				}
				else if (pawn.def.race.Animal)
				{
					source = from x in DefDatabase<PawnCapacityDef>.AllDefs
					where x.showOnAnimals
					select x;
				}
				else
				{
					source = from x in DefDatabase<PawnCapacityDef>.AllDefs
					where x.showOnMechanoids
					select x;
				}
				foreach (PawnCapacityDef current in from act in source
				orderby act.listOrder
				select act)
				{
					if (PawnCapacityUtility.BodyCanEverDoCapacity(pawn.RaceProps.body, current))
					{
						PawnCapacityDef activityLocal = current;
						Pair<string, Color> efficiencyLabel = rjwHealthCardUtility.GetEfficiencyLabel(pawn, current);
						Func<string> textGetter = () => (!pawn.Dead) ? rjwHealthCardUtility.GetPawnCapacityTip(pawn, activityLocal) : string.Empty;
						curY = rjwHealthCardUtility.DrawLeftRow(leftRect, curY, current.GetLabelFor(pawn.RaceProps.IsFlesh, pawn.RaceProps.Humanlike).CapitalizeFirst(), efficiencyLabel.First, efficiencyLabel.Second, new TipSignal(textGetter, pawn.thingIDNumber ^ (int)current.index));
					}
				}
			}
			return curY;
		}

		public static float DrawLeftRow(Rect leftRect, float curY, string leftLabel, string rightLabel, Color rightLabelColor, TipSignal tipSignal)
		{
			Rect rect = new Rect(0f, curY, leftRect.width, 20f);
			if (Mouse.IsOver(rect))
			{
				GUI.color = rjwHealthCardUtility.HighlightColor;
				GUI.DrawTexture(rect, TexUI.HighlightTex);
			}
			GUI.color = Color.white;
			Widgets.Label(new Rect(0f, curY, leftRect.width * 0.65f, 30f), leftLabel);
			GUI.color = rightLabelColor;
			Widgets.Label(new Rect(leftRect.width * 0.65f, curY, leftRect.width * 0.35f, 30f), rightLabel);
			TooltipHandler.TipRegion(new Rect(0f, curY, leftRect.width, 20f), tipSignal);
			curY += 20f;
			return curY;
		}

		public static void DrawHediffRow(Rect rect, Pawn pawn, IEnumerable<Hediff> diffs, ref float curY)
		{
			float num = rect.width * 0.375f;
			float width = rect.width - num - 20f;
			BodyPartRecord part = diffs.First<Hediff>().Part;
			float a;
			if (part == null)
			{
				a = Text.CalcHeight("WholeBody".Translate(), num);
			}
			else
			{
				a = Text.CalcHeight(part.LabelCap, num);
			}
			float b = 0f;
			float num2 = curY;
			float num3 = 0f;
			foreach (IGrouping<int, Hediff> current in from x in diffs
			group x by x.UIGroupKey)
			{
				int num4 = current.Count<Hediff>();
				string text = current.First<Hediff>().LabelCap;
				if (num4 != 1)
				{
					text = text + " x" + num4.ToString();
				}
				num3 += Text.CalcHeight(text, width);
			}
			b = num3;
			Rect rect2 = new Rect(0f, curY, rect.width, Mathf.Max(a, b));
			rjwHealthCardUtility.DoRightRowHighlight(rect2);
			if (part != null)
			{
				GUI.color = HealthUtility.GetPartConditionLabel(pawn, part).Second;
				Widgets.Label(new Rect(0f, curY, num, 100f), part.LabelCap);
			}
			else
			{
				GUI.color = HealthUtility.DarkRedColor;
				Widgets.Label(new Rect(0f, curY, num, 100f), "WholeBody".Translate());
			}
			GUI.color = Color.white;
			foreach (IGrouping<int, Hediff> current2 in from x in diffs
			group x by x.UIGroupKey)
			{
				int num5 = 0;
				Hediff hediff = null;
				Texture2D texture2D = null;
				TextureAndColor textureAndColor = null;
				float num6 = 0f;
				foreach (Hediff current3 in current2)
				{
					if (num5 == 0)
					{
						hediff = current3;
					}
					textureAndColor = current3.StateIcon;
					if (current3.Bleeding)
					{
						texture2D = rjwHealthCardUtility.BleedingIcon;
					}
					num6 += current3.BleedRate;
					num5++;
				}
				string text2 = hediff.LabelCap;
				if (num5 != 1)
				{
					text2 = text2 + " x" + num5.ToStringCached();
				}
				GUI.color = hediff.LabelColor;
				float num7 = Text.CalcHeight(text2, width);
				Rect rect3 = new Rect(num, curY, width, num7);
				Widgets.Label(rect3, text2);
				GUI.color = Color.white;
				Rect rect4 = new Rect(rect2.xMax - 20f, curY, 20f, 20f);
				if (texture2D)
				{
					Rect position = rect4.ContractedBy(GenMath.LerpDouble(0f, 0.6f, 5f, 0f, Mathf.Min(num6, 1f)));
					GUI.DrawTexture(position, texture2D);
					rect4.x -= rect4.width;
				}
				if (textureAndColor.HasValue)
				{
					GUI.color = textureAndColor.Color;
					GUI.DrawTexture(rect4, textureAndColor.Texture);
					GUI.color = Color.white;
					rect4.x -= rect4.width;
				}
				curY += num7;
			}
			GUI.color = Color.white;
			curY = num2 + Mathf.Max(a, b);
			if (Widgets.ButtonInvisible(rect2, false))
			{
				rjwHealthCardUtility.EntryClicked(diffs, pawn);
			}
			TooltipHandler.TipRegion(rect2, new TipSignal(() => rjwHealthCardUtility.GetTooltip(diffs, pawn, part), (int)curY + 7857));
		}

		public static string GetPainTip(Pawn pawn)
		{
			return "PainLevel".Translate() + ": " + (pawn.health.hediffSet.PainTotal * 100f).ToString("F0") + "%";
		}

		public static string GetPawnCapacityTip(Pawn pawn, PawnCapacityDef capacity)
		{
			List<PawnCapacityUtility.CapacityImpactor> list = new List<PawnCapacityUtility.CapacityImpactor>();
			float num = PawnCapacityUtility.CalculateCapacityLevel(pawn.health.hediffSet, capacity, list);
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(capacity.LabelCap + ": " + (num * 100f).ToString("F0") + "%");
			if (list.Count > 0)
			{
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("AffectedBy".Translate());
				for (int i = 0; i < list.Count; i++)
				{
					if (list[i] is PawnCapacityUtility.CapacityImpactorHediff)
					{
						stringBuilder.AppendLine(string.Format("  {0}", list[i].Readable(pawn)));
					}
				}
				for (int j = 0; j < list.Count; j++)
				{
					if (list[j] is PawnCapacityUtility.CapacityImpactorBodyPartHealth)
					{
						stringBuilder.AppendLine(string.Format("  {0}", list[j].Readable(pawn)));
					}
				}
				for (int k = 0; k < list.Count; k++)
				{
					if (list[k] is PawnCapacityUtility.CapacityImpactorCapacity)
					{
						stringBuilder.AppendLine(string.Format("  {0}", list[k].Readable(pawn)));
					}
				}
				for (int l = 0; l < list.Count; l++)
				{
					if (list[l] is PawnCapacityUtility.CapacityImpactorPain)
					{
						stringBuilder.AppendLine(string.Format("  {0}", list[l].Readable(pawn)));
					}
				}
			}
			return stringBuilder.ToString();
		}

		public static string GetTooltip(IEnumerable<Hediff> diffs, Pawn pawn, BodyPartRecord part)
		{
			StringBuilder stringBuilder = new StringBuilder();
			if (part != null)
			{
				stringBuilder.Append(part.LabelCap + ": ");
				stringBuilder.AppendLine(" " + pawn.health.hediffSet.GetPartHealth(part).ToString() + " / " + part.def.GetMaxHealth(pawn).ToString());
				float num = PawnCapacityUtility.CalculatePartEfficiency(pawn.health.hediffSet, part, false, null);
				if (num != 1f)
				{
					stringBuilder.AppendLine("Efficiency".Translate() + ": " + num.ToStringPercent());
				}
			}
			else
			{
				stringBuilder.AppendLine("WholeBody".Translate());
			}
			stringBuilder.AppendLine("------------------");
			foreach (IGrouping<int, Hediff> current in from x in diffs
			group x by x.UIGroupKey)
			{
				foreach (Hediff current2 in current)
				{
					string severityLabel = current2.SeverityLabel;
					bool flag = rjwHealthCardUtility.showHediffsDebugInfo && !current2.DebugString().NullOrEmpty();
					if (!current2.Label.NullOrEmpty() || !severityLabel.NullOrEmpty() || !current2.CapMods.NullOrEmpty<PawnCapacityModifier>() || flag)
					{
						stringBuilder.Append(current2.LabelCap);
						if (!severityLabel.NullOrEmpty())
						{
							stringBuilder.Append(": " + severityLabel);
						}
						stringBuilder.AppendLine();
						string tipStringExtra = current2.TipStringExtra;
						if (!tipStringExtra.NullOrEmpty())
						{
							stringBuilder.AppendLine(tipStringExtra.TrimEndNewlines().Indented("    "));
						}
						if (flag)
						{
							stringBuilder.AppendLine(current2.DebugString().TrimEndNewlines());
						}
					}
				}
			}
			string s;
			LogEntry logEntry;
			if (rjwHealthCardUtility.GetCombatLogInfo(diffs, out s, out logEntry))
			{
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("Cause:");
				stringBuilder.AppendLine(s.Indented("    "));
			}
			return stringBuilder.ToString().TrimEnd(new char[0]);
		}

		public static void EntryClicked(IEnumerable<Hediff> diffs, Pawn pawn)
		{
			LogEntry combatLogEntry;
			string text;
			if (!rjwHealthCardUtility.GetCombatLogInfo(diffs, out text, out combatLogEntry) || combatLogEntry == null)
			{
				return;
			}
			if (!Find.BattleLog.Battles.Any((Battle b) => b.Concerns(pawn) && b.Entries.Any((LogEntry e) => e == combatLogEntry)))
			{
				return;
			}
			ITab_Pawn_Log tab_Pawn_Log = InspectPaneUtility.OpenTab(typeof(ITab_Pawn_Log)) as ITab_Pawn_Log;
			if (tab_Pawn_Log == null)
			{
				return;
			}
			tab_Pawn_Log.SeekTo(combatLogEntry);
			tab_Pawn_Log.Highlight(combatLogEntry);
		}

		public static bool GetCombatLogInfo(IEnumerable<Hediff> diffs, out string combatLogText, out LogEntry combatLogEntry)
		{
			combatLogText = null;
			combatLogEntry = null;
			foreach (Hediff current in diffs)
			{
				if ((current.combatLogEntry != null && current.combatLogEntry.Target != null) || (combatLogText.NullOrEmpty() && !current.combatLogText.NullOrEmpty()))
				{
					combatLogEntry = ((current.combatLogEntry == null) ? null : current.combatLogEntry.Target);
					combatLogText = current.combatLogText;
				}
				if (combatLogEntry != null)
				{
					return true;
				}
			}
			return false;
		}

		public static void DoRightRowHighlight(Rect rowRect)
		{
			if (rjwHealthCardUtility.highlight)
			{
				GUI.color = rjwHealthCardUtility.StaticHighlightColor;
				GUI.DrawTexture(rowRect, TexUI.HighlightTex);
			}
			rjwHealthCardUtility.highlight = !rjwHealthCardUtility.highlight;
			if (Mouse.IsOver(rowRect))
			{
				GUI.color = rjwHealthCardUtility.HighlightColor;
				GUI.DrawTexture(rowRect, TexUI.HighlightTex);
			}
		}

		public static void DoDebugOptions(Rect rightRect, Pawn pawn)
		{
			Widgets.CheckboxLabeled(new Rect(rightRect.x, rightRect.y - 25f, 110f, 30f), "Dev: AllDiffs", ref rjwHealthCardUtility.showAllHediffs, false, null, null, false);
			Widgets.CheckboxLabeled(new Rect(rightRect.x + 115f, rightRect.y - 25f, 120f, 30f), "DiffsDebugInfo", ref rjwHealthCardUtility.showHediffsDebugInfo, false, null, null, false);
			if (Widgets.ButtonText(new Rect(rightRect.x + 240f, rightRect.y - 27f, 115f, 25f), "Debug info", true, false, true))
			{
				List<FloatMenuOption> list = new List<FloatMenuOption>();
				list.Add(new FloatMenuOption("Parts hit chance (this part or any child node)", delegate
				{
					StringBuilder stringBuilder = new StringBuilder();
					foreach (BodyPartRecord current in from x in pawn.RaceProps.body.AllParts
					orderby x.coverageAbsWithChildren descending
					select x)
					{
						stringBuilder.AppendLine(current.LabelCap + " " + current.coverageAbsWithChildren.ToStringPercent());
					}
					Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				list.Add(new FloatMenuOption("Parts hit chance (exactly this part)", delegate
				{
					StringBuilder stringBuilder = new StringBuilder();
					float num = 0f;
					foreach (BodyPartRecord current in from x in pawn.RaceProps.body.AllParts
					orderby x.coverageAbs descending
					select x)
					{
						stringBuilder.AppendLine(current.LabelCap + " " + current.coverageAbs.ToStringPercent());
						num += current.coverageAbs;
					}
					stringBuilder.AppendLine();
					stringBuilder.AppendLine("Total " + num.ToStringPercent());
					Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				list.Add(new FloatMenuOption("Per-part efficiency", delegate
				{
					StringBuilder stringBuilder = new StringBuilder();
					foreach (BodyPartRecord current in pawn.RaceProps.body.AllParts)
					{
						stringBuilder.AppendLine(current.LabelCap + " " + PawnCapacityUtility.CalculatePartEfficiency(pawn.health.hediffSet, current, false, null).ToStringPercent());
					}
					Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				list.Add(new FloatMenuOption("BodyPartGroup efficiency (of only natural parts)", delegate
				{
					StringBuilder stringBuilder = new StringBuilder();
					foreach (BodyPartGroupDef current in from x in DefDatabase<BodyPartGroupDef>.AllDefs
					where pawn.RaceProps.body.AllParts.Any((BodyPartRecord y) => y.groups.Contains(x))
					select x)
					{
						stringBuilder.AppendLine(current.LabelCap + " " + PawnCapacityUtility.CalculateNaturalPartsAverageEfficiency(pawn.health.hediffSet, current).ToStringPercent());
					}
					Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				list.Add(new FloatMenuOption("IsSolid", delegate
				{
					StringBuilder stringBuilder = new StringBuilder();
					foreach (BodyPartRecord current in pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null))
					{
						stringBuilder.AppendLine(current.LabelCap + " " + current.def.IsSolid(current, pawn.health.hediffSet.hediffs));
					}
					Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				list.Add(new FloatMenuOption("IsSkinCovered", delegate
				{
					StringBuilder stringBuilder = new StringBuilder();
					foreach (BodyPartRecord current in pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null))
					{
						stringBuilder.AppendLine(current.LabelCap + " " + current.def.IsSkinCovered(current, pawn.health.hediffSet));
					}
					Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				list.Add(new FloatMenuOption("does have added parts", delegate
				{
					StringBuilder stringBuilder = new StringBuilder();
					foreach (BodyPartRecord current in pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null))
					{
						stringBuilder.AppendLine(current.LabelCap + " " + pawn.health.hediffSet.PartOrAnyAncestorHasDirectlyAddedParts(current));
					}
					Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				list.Add(new FloatMenuOption("GetNotMissingParts", delegate
				{
					StringBuilder stringBuilder = new StringBuilder();
					foreach (BodyPartRecord current in pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null))
					{
						stringBuilder.AppendLine(current.LabelCap);
					}
					Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				list.Add(new FloatMenuOption("GetCoverageOfNotMissingNaturalParts", delegate
				{
					StringBuilder stringBuilder = new StringBuilder();
					foreach (BodyPartRecord current in from x in pawn.RaceProps.body.AllParts
					orderby pawn.health.hediffSet.GetCoverageOfNotMissingNaturalParts(x) descending
					select x)
					{
						stringBuilder.AppendLine(current.LabelCap + ": " + pawn.health.hediffSet.GetCoverageOfNotMissingNaturalParts(current).ToStringPercent());
					}
					Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				list.Add(new FloatMenuOption("parts nutrition (assuming adult)", delegate
				{
					StringBuilder stringBuilder = new StringBuilder();
					float totalCorpseNutrition = StatDefOf.Nutrition.Worker.GetValueAbstract(pawn.RaceProps.corpseDef, null);
					foreach (BodyPartRecord current in from x in pawn.RaceProps.body.AllParts
					orderby FoodUtility.GetBodyPartNutrition(totalCorpseNutrition, pawn, x) descending
					select x)
					{
						stringBuilder.AppendLine(current.LabelCap + ": " + FoodUtility.GetBodyPartNutrition(totalCorpseNutrition, pawn, current));
					}
					Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				list.Add(new FloatMenuOption("test permanent injury pain factor probability", delegate
				{
					StringBuilder stringBuilder = new StringBuilder();
					int num = 0;
					int num2 = 0;
					int num3 = 0;
					float num4 = 0f;
					int num5 = 10000;
					for (int i = 0; i < num5; i++)
					{
						float num6 = Mathf.Max(0f, Rand.ByCurve(HealthTuning.PermanentInjuryPainFactorRandomCurve));
						if (num6 < 0f)
						{
							Log.Error("Pain factor < 0", false);
						}
						if (num6 == 0f)
						{
							num++;
						}
						if (num6 < 1f)
						{
							num2++;
						}
						if (num6 < 5f)
						{
							num3++;
						}
						if (num6 > num4)
						{
							num4 = num6;
						}
					}
					stringBuilder.AppendLine("total: " + num5);
					stringBuilder.AppendLine(string.Concat(new object[]
					{
						"0: ",
						num,
						" (",
						((float)num / (float)num5).ToStringPercent(),
						")"
					}));
					stringBuilder.AppendLine(string.Concat(new object[]
					{
						"< 1: ",
						num2,
						" (",
						((float)num2 / (float)num5).ToStringPercent(),
						")"
					}));
					stringBuilder.AppendLine(string.Concat(new object[]
					{
						"< 5: ",
						num3,
						" (",
						((float)num3 / (float)num5).ToStringPercent(),
						")"
					}));
					stringBuilder.AppendLine("max: " + num4);
					Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				list.Add(new FloatMenuOption("HediffGiver_Birthday chance at age", delegate
				{
					List<FloatMenuOption> list2 = new List<FloatMenuOption>();
					foreach (HediffGiverSetDef current in pawn.RaceProps.hediffGiverSets)
					{
						foreach (HediffGiver_Birthday current2 in current.hediffGivers.OfType<HediffGiver_Birthday>())
						{
							HediffGiver_Birthday hLocal = current2;
							FloatMenuOption item = new FloatMenuOption(current.defName + " - " + current2.hediff.defName, delegate
							{
								StringBuilder stringBuilder = new StringBuilder();
								stringBuilder.AppendLine("% of pawns which will have at least 1 " + hLocal.hediff.label + " at age X:");
								stringBuilder.AppendLine();
								int num = 1;
								while ((float)num < pawn.RaceProps.lifeExpectancy + 20f)
								{
									stringBuilder.AppendLine(num + ": " + hLocal.DebugChanceToHaveAtAge(pawn, num).ToStringPercent());
									num++;
								}
								Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
							}, MenuOptionPriority.Default, null, null, 0f, null, null);
							list2.Add(item);
						}
					}
					Find.WindowStack.Add(new FloatMenu(list2));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				list.Add(new FloatMenuOption("HediffGiver_Birthday count at age", delegate
				{
					StringBuilder stringBuilder = new StringBuilder();
					stringBuilder.AppendLine("Average hediffs count (from HediffGiver_Birthday) at age X:");
					stringBuilder.AppendLine();
					int num = 1;
					while ((float)num < pawn.RaceProps.lifeExpectancy + 20f)
					{
						float num2 = 0f;
						foreach (HediffGiverSetDef current in pawn.RaceProps.hediffGiverSets)
						{
							foreach (HediffGiver_Birthday current2 in current.hediffGivers.OfType<HediffGiver_Birthday>())
							{
								num2 += current2.DebugChanceToHaveAtAge(pawn, num);
							}
						}
						stringBuilder.AppendLine(num + ": " + num2.ToStringDecimalIfSmall());
						num++;
					}
					Find.WindowStack.Add(new Dialog_MessageBox(stringBuilder.ToString(), null, null, null, null, null, false, null, null));
				}, MenuOptionPriority.Default, null, null, 0f, null, null));
				Find.WindowStack.Add(new FloatMenu(list));
			}
		}

		public static Pair<string, Color> GetEfficiencyLabel(Pawn pawn, PawnCapacityDef activity)
		{
			float level = pawn.health.capacities.GetLevel(activity);
			string first = string.Empty;
			Color second = Color.white;
			if (level <= 0f)
			{
				first = "None".Translate();
				second = HealthUtility.DarkRedColor;
			}
			else if (level < 0.4f)
			{
				first = "VeryPoor".Translate();
				second = rjwHealthCardUtility.VeryPoorColor;
			}
			else if (level < 0.7f)
			{
				first = "Poor".Translate();
				second = rjwHealthCardUtility.PoorColor;
			}
			else if (level < 1f && !Mathf.Approximately(level, 1f))
			{
				first = "Weakened".Translate();
				second = rjwHealthCardUtility.WeakenedColor;
			}
			else if (Mathf.Approximately(level, 1f))
			{
				first = "GoodCondition".Translate();
				second = HealthUtility.GoodConditionColor;
			}
			else
			{
				first = "Enhanced".Translate();
				second = rjwHealthCardUtility.EnhancedColor;
			}
			return new Pair<string, Color>(first, second);
		}

		public static Pair<string, Color> GetPainLabel(Pawn pawn)
		{
			float painTotal = pawn.health.hediffSet.PainTotal;
			string first = string.Empty;
			Color second = Color.white;
			if (Mathf.Approximately(painTotal, 0f))
			{
				first = "NoPain".Translate();
				second = HealthUtility.GoodConditionColor;
			}
			else if (painTotal < 0.15f)
			{
				first = "LittlePain".Translate();
				second = Color.gray;
			}
			else if (painTotal < 0.4f)
			{
				first = "MediumPain".Translate();
				second = rjwHealthCardUtility.MediumPainColor;
			}
			else if (painTotal < 0.8f)
			{
				first = "SeverePain".Translate();
				second = rjwHealthCardUtility.SeverePainColor;
			}
			else
			{
				first = "ExtremePain".Translate();
				second = HealthUtility.DarkRedColor;
			}
			return new Pair<string, Color>(first, second);
		}
	}
}


code:
using System;
using System.Linq;
using Verse;

namespace rjw
{
	public class AgeConfigDef : Def
	{
		public SimpleCurve attractivenessByAgeFemale;
		public SimpleCurve attractivenessByAgeMale;
		public SimpleCurve lovinIntervalHoursByAge;
		public SimpleCurve rigidityByAge;
		public SimpleCurve whoringPriceByAge;

		static readonly Lazy<AgeConfigDef> instance = new Lazy<AgeConfigDef>(() => DefDatabase<AgeConfigDef>.AllDefs.Single());

		public static AgeConfigDef Instance
		{
			get
			{
				return instance.Value;
			}
		}
	}
}

repo_name: Timura/rjw

license: mit


code:
using System.Collections.Generic;
using Verse;
using RimWorld;
using RimWorld.Planet;

namespace rjw
{
	/// <summary>
	/// Hugslib Utility Data object storing the RJW info
	/// also implements extensions to Pawn method
	/// is used as a static field in PawnData
	/// </summary>
	public class DataStore : WorldComponent
	{
		public DataStore(World world) : base(world)
		{
		}

		public Dictionary<int, PawnData> PawnData = new Dictionary<int, PawnData> ();
		//public Dictionary<int, PartsData> PartsData = new Dictionary<int, PartsData> ();

		public override void ExposeData()
		{
			if (Scribe.mode == LoadSaveMode.Saving)
			{
				PawnData.RemoveAll(item => item.Value == null || !item.Value.IsValid);
			}

			base.ExposeData();
			Scribe_Collections.Look(ref PawnData, "Data", LookMode.Value, LookMode.Deep);
			if (Scribe.mode == LoadSaveMode.LoadingVars)
			{
				if (PawnData == null) PawnData = new Dictionary<int, PawnData>();
			}
		}

		public PawnData GetPawnData(Pawn pawn)
		{
			PawnData res;
			//--Log.Message("Getting data for " + pawn);
			//--Log.Message("Pawn " + pawn + " id " + pawn.thingIDNumber);
			//--Log.Message("PawnData isn't null " + !(PawnData == null));
			var filled = PawnData.TryGetValue(pawn.thingIDNumber, out res);
			//--Log.Message("Output is not null" + PawnData.TryGetValue(pawn.thingIDNumber, out res));
			//--Log.Message("Out is not null " + (res != null));
			//--Log.Message("Out is valid " + (res != null && res.IsValid));
			if ((res==null) || (!res.IsValid))
			{
				if (filled)
				{
					//--Log.Message("Clearing incorrect data for " + pawn);
					PawnData.Remove(pawn.thingIDNumber);
				}
				//--Log.Message("PawnData missing, creating for " + pawn);
				res = new PawnData(pawn);
				PawnData.Add(pawn.thingIDNumber, res);
			}
			//--Log.Message("Finishing");
			//--Log.Message("PawnData is " + res.Comfort + " " + res.Service + " " + res.Breeding);
			return res;
		}
		void SetPawnData(Pawn pawn, PawnData data)
		{
			PawnData.Add(pawn.thingIDNumber, data);
		}
	}
}


code:
using Verse;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using RimWorld.Planet;

namespace rjw
{
	/// <summary>
	/// Collection of pawn designators lists
	/// </summary>
	public class DesignatorsData : WorldComponent
	{
		public DesignatorsData(World world) : base(world)
		{
		}

		public static List<Pawn> rjwHero = new List<Pawn>();
		public static List<Pawn> rjwComfort = new List<Pawn>();
		public static List<Pawn> rjwService = new List<Pawn>();
		public static List<Pawn> rjwMilking = new List<Pawn>();
		public static List<Pawn> rjwBreeding = new List<Pawn>();
		public static List<Pawn> rjwBreedingAnimal = new List<Pawn>();

		//public static Dictionary<string, List<Pawn>> Designators = new Dictionary<string, List<Pawn>>();

		/// <summary>
		/// update designators on game load
		/// </summary>
		public void Update()
		{
			//if (RJWSettings.DevMode)
				//ModLog.Message("DesignatorsData::Update()");
			rjwHero = PawnsFinder.All_AliveOrDead.Where(p => p.IsDesignatedHero()).ToList();
			rjwComfort = PawnsFinder.All_AliveOrDead.Where(p => p.IsDesignatedComfort()).ToList();
			rjwService = PawnsFinder.All_AliveOrDead.Where(p => p.IsDesignatedService()).ToList();
			rjwMilking = PawnsFinder.All_AliveOrDead.Where(p => p.IsDesignatedMilking()).ToList();
			rjwBreeding = PawnsFinder.All_AliveOrDead.Where(p => p.IsDesignatedBreeding()).ToList();
			rjwBreedingAnimal = PawnsFinder.All_AliveOrDead.Where(p => p.IsDesignatedBreedingAnimal()).ToList();
			//ModLog.Message("rjwHero "+rjwHero.Count());
			//ModLog.Message("rjwComfort "+rjwComfort.Count());
			//ModLog.Message("rjwService "+rjwService.Count());
			//ModLog.Message("rjwMilking "+rjwMilking.Count());
			//ModLog.Message("rjwBreeding "+rjwBreeding.Count());
			//ModLog.Message("rjwBreedingAnimal "+rjwBreedingAnimal.Count());

			//Designators = new Dictionary<string, List<Pawn>>();
			//Designators.Add("rjwHero", rjwHero);
			//Designators.Add("rjwComfort", rjwComfort);
			//Designators.Add("rjwService", rjwService);
			//Designators.Add("rjwMilking", rjwMilking);
			//Designators.Add("rjwBreeding", rjwBreeding);
			//Designators.Add("rjwBreedingAnimal", rjwBreedingAnimal);
		}
	}
}


code:
using HugsLib;
using RimWorld;
using Verse;

namespace rjw
{
	/// <summary>
	/// Rjw settings store
	/// </summary>
	public class SaveStorage : ModBase
	{
		public override string ModIdentifier => ModId;
		public static string ModId => "SJW";

		public static DataStore DataStore;//reference to savegame data, hopefully
		public static DesignatorsData DesignatorsData;//reference to savegame data, hopefully

		public override void SettingsChanged()
		{
			ToggleTabIfNeeded();
		}

		public override void WorldLoaded()
		{
			DataStore = Find.World.GetComponent<DataStore>();
			DesignatorsData = Find.World.GetComponent<DesignatorsData>();
			DesignatorsData.Update();
			ToggleTabIfNeeded();
			FixRjwHediffsOnlLoad();
		}
		protected override bool HarmonyAutoPatch { get => false; }//first.cs creates harmony and does some convoulted stuff with it

		private void ToggleTabIfNeeded()
		{
			//DefDatabase<MainButtonDef>.GetNamed("RJW_Brothel").buttonVisible = RJWSettings.whoringtab_enabled;
		}
		private void FixRjwHediffsOnlLoad()
		{
			foreach (var pawn in PawnsFinder.All_AliveOrDead)
			{
				foreach (var hd in pawn.health.hediffSet.hediffs)
				{
					if (hd is Hediff_PartBaseNatural || hd is Hediff_PartBaseArtifical)
					{
						hd.TryGetComp<CompHediffBodyPart>().updatepartposition();
					}
				}
			}
		}
	}
}


code:
using System.Collections.Generic;
using Verse;
using System;
using System.ComponentModel;
using System.Linq;
using Verse.Noise;

namespace rjw
{
	/// <summary>
	/// Data about part sizes that is not tied to an individual hediffdef.
	/// </summary>
	public class PartStagesDef : Def
	{
		public float bandSizeBase;
		public float bandSizeInterval;
		public float cupSizeInterval;
		public List<string> cupSizeLabels;

		static readonly Lazy<PartStagesDef> instance = new Lazy<PartStagesDef>(() => DefDatabase<PartStagesDef>.AllDefs.Single());

		public static PartStagesDef Instance
		{
			get
			{
				return instance.Value;
			}
		}

		public static string GetCupSizeLabel(float size)
		{
			var i = Math.Max(0, Math.Min(Instance.cupSizeLabels.Count - 1, (int)size));
			return Instance.cupSizeLabels[i];
		}
	}
}


code:
using System;
using Verse;
using System.Linq;
using RimWorld;
using System.Collections.Generic;

namespace rjw
{
	/// <summary>
	/// Utility data object and a collection of extension methods for Pawn
	/// </summary>
	public class PawnData : IExposable
	{
		public Pawn Pawn = null;
		public bool Comfort = false;
		public bool Service = false;
		public bool Breeding = false;
		public bool Milking = false;
		public bool Hero = false;
		public bool Ironman = false;
		public string HeroOwner = "";
		public bool BreedingAnimal = false;

		public bool CanChangeDesignationColonist = false;
		public bool CanChangeDesignationPrisoner = false;
		public bool CanDesignateService = false;
		public bool CanDesignateMilking = false;
		public bool CanDesignateComfort = false;
		public bool CanDesignateBreedingAnimal = false;
		public bool CanDesignateBreeding = false;
		public bool CanDesignateHero = false;

		public bool isSlime = false;
		public bool isDemon = false;
		public bool oviPregnancy = false;

		public float raceSexDrive = 1.0f;

		public SexProps SexProps;// temp variable for rmb interactions cache

		public List<Hediff> genitals = new List<Hediff>();
		public List<Hediff> breasts = new List<Hediff>();
		public List<Hediff> udders = new List<Hediff>();
		public List<Hediff> anus = new List<Hediff>();
		public List<Hediff> torso = new List<Hediff>();

		public RaceGroupDef RaceSupportDef
		{
			get
			{
				if (raceSupportDef == null)
					RaceGroupDef_Helper.TryGetRaceGroupDef(Pawn, out var raceGroupDef);

				return raceSupportDef;
			}
		}

		public RaceGroupDef raceSupportDef = null;

		public PawnData() { }

		public PawnData(Pawn pawn)
		{
			//Log.Message("Creating pawndata for " + pawn);
			Pawn = pawn;
			//Log.Message("This data is valid " + this.IsValid);

			if (RaceGroupDef_Helper.TryGetRaceGroupDef(Pawn, out var raceGroupDef))
			{
				raceSupportDef = raceGroupDef;
				//Log.Message("RaceSupportDef " + RaceSupportDef);
				oviPregnancy = raceGroupDef.oviPregnancy;
				raceSexDrive = raceGroupDef.raceSexDrive;
			}

			isDemon = Pawn.Has(RaceTag.Demon);
			isSlime = Pawn.Has(RaceTag.Slime);
			//Log.Warning("PawnData:: Pawn:" + xxx.get_pawnname(pawn));
			//Log.Warning("PawnData:: isSlime:" + isSlime);
			//Log.Warning("PawnData:: isDemon:" + isDemon);
			//Log.Warning("PawnData:: oviPregnancy:" + oviPregnancy);
		}

		public void ExposeData()
		{
			Scribe_References.Look(ref Pawn, "Pawn");
			Scribe_Values.Look(ref Comfort, "Comfort", false, true);
			Scribe_Values.Look(ref Service, "Service", false, true);
			Scribe_Values.Look(ref Breeding, "Breeding", false, true);
			Scribe_Values.Look(ref Milking, "Milking", false, true);
			Scribe_Values.Look(ref Hero, "Hero", false, true);
			Scribe_Values.Look(ref Ironman, "Ironman", false, true);
			Scribe_Values.Look(ref HeroOwner, "HeroOwner", "", true);
			Scribe_Values.Look(ref BreedingAnimal, "BreedingAnimal", false, true);
			Scribe_Values.Look(ref CanChangeDesignationColonist, "CanChangeDesignationColonist", false, true);
			Scribe_Values.Look(ref CanChangeDesignationPrisoner, "CanChangeDesignationPrisoner", false, true);
			Scribe_Values.Look(ref CanDesignateService, "CanDesignateService", false, true);
			Scribe_Values.Look(ref CanDesignateMilking, "CanDesignateMilking", false, true);
			Scribe_Values.Look(ref CanDesignateComfort, "CanDesignateComfort", false, true);
			Scribe_Values.Look(ref CanDesignateBreedingAnimal, "CanDesignateBreedingAnimal", false, true);
			Scribe_Values.Look(ref CanDesignateBreeding, "CanDesignateBreeding", false, true);
			Scribe_Values.Look(ref CanDesignateHero, "CanDesignateHero", false, true);
			Scribe_Values.Look(ref isSlime, "isSlime", false, true);
			Scribe_Values.Look(ref isDemon, "isDemon", false, true);
			Scribe_Values.Look(ref oviPregnancy, "oviPregnancy", false, true);
			Scribe_Values.Look(ref raceSexDrive, "raceSexDrive", 1.0f, true);
			Scribe_Defs.Look(ref raceSupportDef, "RaceSupportDef");
	}

	public bool IsValid { get { return Pawn != null; } }
	}
}


code:
using System.Collections.Generic;
using Verse;
using System;

namespace rjw
{
	/// <summary>
	/// Defines all RJW configuration related to a specific race or group of races.
	/// Core races should have RaceGroupDefs in RJW.
	/// Non-core races should have RaceGroupDefs in the separate RJWRaceSupport mod.
	/// Technically any mod could add a RaceGroupDef for its own races but we expect that to be rare.
	/// </summary>
	public class RaceGroupDef : Def
	{
		public List<string> raceNames = null;
		public List<string> pawnKindNames = null;

		public List<PartAdder> partAdders;
		public List<string> anuses = null;
		public List<float> chanceanuses = null;
		public List<string> femaleBreasts = null;
		public List<float> chancefemaleBreasts = null;
		public List<string> femaleGenitals = null;
		public List<float> chancefemaleGenitals = null;
		public List<string> maleBreasts = null;
		public List<float> chancemaleBreasts = null;
		public List<string> maleGenitals = null;
		public List<float> chancemaleGenitals = null;

		public List<string> hybridRaceParents = null;
		public List<string> hybridChildKindDef = null;
		public List<string> backstoryChildTribal = null;
		public List<string> backstoryChildCivil = null;

		public List<string> tags = null;
		public bool hasSingleGender = false;
		public bool hasSexNeed = true;
		public bool hasFertility = true;
		public bool hasPregnancy = true;
		public bool oviPregnancy = false;
		public bool ImplantEggs = false;
		public bool HasUdder = false;
		public bool isDemon = false;
		public bool isSlime = false;
		public float raceSexDrive = 1.0f;

		public string eggFertilizedDef = "RJW_EggFertilized";
		public string eggUnfertilizedDef = "RJW_EggUnfertilized";
		public float eggProgressUnfertilizedMax = 1.0f;
		public float eggLayIntervalDays = 3.5f;

		/// <summary>
		/// limit age at which sex becomes available for humans/animals
		/// </summary>
		public int teenAge;
		public int adultAge;
		//public int pPregnancyGrowth = 12;

		//public float eggCountRange = 3.5f;
		//public float eggFertilizationCountMax = 3.5f;
		//public float eggLayFemaleOnly = 3.5f;

		public List<string> GetRacePartDefNames(SexPartType sexPartType)
		{
			return sexPartType switch
			{
				SexPartType.Anus => anuses,
				SexPartType.FemaleBreast => femaleBreasts,
				SexPartType.FemaleGenital => femaleGenitals,
				SexPartType.MaleBreast => maleBreasts,
				SexPartType.MaleGenital => maleGenitals,
				_ => throw new ApplicationException($"Unrecognized sexPartType: {sexPartType}"),
			};
		}

		public List<float> GetChances(SexPartType sexPartType)
		{
			return sexPartType switch
			{
				SexPartType.Anus => chanceanuses,
				SexPartType.FemaleBreast => chancefemaleBreasts,
				SexPartType.FemaleGenital => chancefemaleGenitals,
				SexPartType.MaleBreast => chancemaleBreasts,
				SexPartType.MaleGenital => chancemaleGenitals,
				_ => throw new ApplicationException($"Unrecognized sexPartType: {sexPartType}"),
			};
		}
	}
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace rjw
{
	/// <summary>
	/// Defines how to create a single sex part on a newly created pawn.
	/// </summary>
	public class RacePartDef : Def
	{
		public bool IsNone => string.IsNullOrEmpty(hediffName);
		public static readonly RacePartDef None = new RacePartDef();

		/// <summary>
		/// The name of the hediff to create.
		/// Doesn't use the hediff directly because that causes issues when another mod wants to
		/// create and reference custom parts inheriting from rjw base defs.
		/// </summary>
		public string hediffName;
		public string fluidType;
		public float? fluidModifier;
		public SimpleCurve severityCurve;

		public bool TryGetHediffDef(out HediffDef hediffDef)
		{
			hediffDef = DefDatabase<HediffDef>.GetNamedSilentFail(hediffName);
			if (hediffDef == null)
			{
				ModLog.Error($"Could not find a HediffDef named {hediffName} referenced by RacePartDef named {defName}.");
				return false;
			}
			else
			{
				return true;
			}
		}
	}
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RimWorld;
using Verse;

namespace rjw
{
	public class RaceTag
	{
		// I only created tags for RaceGroupDef properties that seemed like keywords (like slime) rather than behavior (like oviPregnancy).
		public readonly static RaceTag Chitin = new RaceTag("Chitin");
		public readonly static RaceTag Demon = new RaceTag("Demon");
		public readonly static RaceTag Feathers = new RaceTag("Feathers");
		public readonly static RaceTag Fur = new RaceTag("Fur");
		public readonly static RaceTag Plant = new RaceTag("Plant");
		public readonly static RaceTag Robot = new RaceTag("Robot");
		public readonly static RaceTag Scales = new RaceTag("Scales");
		public readonly static RaceTag Skin = new RaceTag("Skin");
		public readonly static RaceTag Slime = new RaceTag("Slime");

		public string Key { get; }

		RaceTag(string key)
		{
			Key = key;
		}

		/// <summary>
		/// For backwards compatability only. Shouldn't add more special cases here.
		/// </summary>
		public bool DefaultWhenNoRaceGroupDef(Pawn pawn)
		{
			if (this == Demon)
			{
				return xxx.is_demon(pawn);
			}
			else if (this == Slime)
			{
				return xxx.is_slime(pawn);
			}
			else if (this == Skin)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
	}
}


code:
using System.Collections.Generic;
using Verse;

namespace rjw
{
	/// <summary>
	/// Just a simplest form of passing data from xml to the code
	/// </summary>
	public class StringListDef : Def
	{
		public List<string> strings = new List<string>();
	}
}


code:
///Better not leave it be, this mod is not intended to reimpelement base functionality and break other mods by that.
//using Harmony;
//using RimWorld;
//using System;
//using System.Collections.Generic;
//using System.Linq;
//using System.Reflection;
//using System.Text;
//using UnityEngine;
//using Verse;
//using Verse.Sound;

//namespace rjw.Source.Common
//{
//	public static class ExpandedPawnCharacterCard
//	{
//		/// <summary>
//		/// Better to leave it be, since it doesn't affect anything much.
//		/// </summary>
//		public static float cardX = 570f;  // vanilla is 570

//		/// <summary>
//		/// Basically, you want to increase this value if you want character info card be taller (thus allowing to have more
//		/// traits and skills shown). Height is in pixels, increase by ~25 pixels for each additional row of skill/trait.
//		/// </summary>
//		public static float cardY = 470f;  // vanilla is 470

//		[HarmonyPatch(typeof(CharacterCardUtility), "DrawCharacterCard", new[] { typeof(Rect), typeof(Pawn), typeof(Action), typeof(Rect), })]
//		public static class CharacterCardUtilityPatch
//		{
//			private static IEnumerable<WorkTags> WorkTagsFrom(WorkTags tags)
//			{
//				foreach (WorkTags workTag in tags.GetAllSelectedItems<WorkTags>())
//				{
//					if (workTag != WorkTags.None)
//					{
//						yield return workTag;
//					}
//				}
//			}

//			[HarmonyPrefix]
//			public static bool DrawCharacterCardP(ref Rect rect, ref Pawn pawn, ref Action randomizeCallback, ref Rect creationRect)
//			{
//				rect.position = new Vector2(17f, 17f);
//				rect.size = new Vector2(cardX, cardY);
//				bool flag = randomizeCallback != null;
//				GUI.BeginGroup((!flag) ? rect : creationRect);
//				Rect rectLabel = new Rect(0f, 0f, 300f, 30f);
//				NameTriple nameTriple = pawn.Name as NameTriple;
//				if (flag && nameTriple != null)
//				{
//					Rect rect3 = new Rect(rectLabel);
//					rect3.width *= 0.333f;
//					Rect rect4 = new Rect(rectLabel);
//					rect4.width *= 0.333f;
//					rect4.x += rect4.width;
//					Rect rect5 = new Rect(rectLabel);
//					rect5.width *= 0.333f;
//					rect5.x += rect4.width * 2f;
//					string first = nameTriple.First;
//					string nick = nameTriple.Nick;
//					string last = nameTriple.Last;
//					CharacterCardUtility.DoNameInputRect(rect3, ref first, 12);
//					if (nameTriple.Nick == nameTriple.First || nameTriple.Nick == nameTriple.Last)
//					{
//						GUI.color = new Color(1f, 1f, 1f, 0.5f);
//					}
//					CharacterCardUtility.DoNameInputRect(rect4, ref nick, 9);
//					GUI.color = Color.white;
//					CharacterCardUtility.DoNameInputRect(rect5, ref last, 12);
//					if (nameTriple.First != first || nameTriple.Nick != nick || nameTriple.Last != last)
//					{
//						pawn.Name = new NameTriple(first, nick, last);
//					}
//					TooltipHandler.TipRegion(rect3, "FirstNameDesc".Translate());
//					TooltipHandler.TipRegion(rect4, "ShortIdentifierDesc".Translate());
//					TooltipHandler.TipRegion(rect5, "LastNameDesc".Translate());
//				}
//				else
//				{
//					rectLabel.width = 999f;
//					Text.Font = GameFont.Medium;
//					Widgets.Label(rectLabel, pawn.Name.ToStringFull);
//					Text.Font = GameFont.Small;
//				}
//				if (randomizeCallback != null)
//				{
//					Rect rectRandomise = new Rect(creationRect.width - 24f - 100f, 0f, 100f, rectLabel.height);
//					if (Widgets.ButtonText(rectRandomise, "Randomize".Translate(), true, false, true))
//					{
//						SoundDefOf.TickTiny.PlayOneShotOnCamera(null);
//						randomizeCallback();
//					}
//					UIHighlighter.HighlightOpportunity(rectRandomise, "RandomizePawn");
//				}
//				if (flag)
//				{
//					Widgets.InfoCardButton(creationRect.width - 24f, 0f, pawn);
//				}
//				else if (!pawn.health.Dead)
//				{
//					float num = CharacterCardUtility.PawnCardSize.x - 85f;
//					if ((pawn.Faction == Faction.OfPlayer || pawn.IsPrisonerOfColony) && pawn.Spawned)
//					{
//						Rect rectBanish = new Rect(num, 0f, 30f, 30f);
//						TooltipHandler.TipRegion(rectBanish, PawnBanishUtility.GetBanishButtonTip(pawn));
//						if (Widgets.ButtonImage(rectBanish, ContentFinder<Texture2D>.Get("UI/Buttons/Banish", true)))
//						{
//							if (pawn.Downed)
//							{
//								Messages.Message("MessageCantBanishDownedPawn".Translate(new object[]
//								{
//								pawn.LabelShort
//								}).AdjustedFor(pawn), pawn, MessageTypeDefOf.RejectInput);
//							}
//							else
//							{
//								PawnBanishUtility.ShowBanishPawnConfirmationDialog(pawn);
//							}
//						}
//						num -= 40f;
//					}
//					if (pawn.IsColonist)
//					{
//						Rect rect8 = new Rect(num, 0f, 30f, 30f);
//						TooltipHandler.TipRegion(rect8, "RenameColonist".Translate());
//						if (Widgets.ButtonImage(rect8, ContentFinder<Texture2D>.Get("UI/Buttons/Rename", true)))
//						{
//							Find.WindowStack.Add(new Dialog_ChangeNameTriple(pawn));
//						}
//						num -= 40f;
//					}
//				}
//				string label = pawn.MainDesc(true);
//				Rect rectMainDesc = new Rect(0f, 45f, rect.width, 60f);
//				Widgets.Label(rectMainDesc, label);
//				Pawn p = pawn;
//				TooltipHandler.TipRegion(rectMainDesc, () => p.ageTracker.AgeTooltipString, 6873641);
//				Rect positionLeftHalf = new Rect(0f, 100f, 250f, cardY);
//				Rect positionRightHalf = new Rect(positionLeftHalf.xMax, 100f, 258f, cardY);
//				GUI.BeginGroup(positionLeftHalf);
//				float currentY = 0f;
//				Text.Font = GameFont.Medium;
//				Widgets.Label(new Rect(0f, 0f, 200f, 30f), "Backstory".Translate());
//				currentY += 30f;
//				Text.Font = GameFont.Small;
//				foreach (BackstorySlot backstorySlot in Enum.GetValues(typeof(BackstorySlot)))
//				{
//					Backstory backstory = pawn.story.GetBackstory(backstorySlot);
//					if (backstory != null)
//					{
//						Rect rect10 = new Rect(0f, currentY, positionLeftHalf.width, 24f);
//						if (Mouse.IsOver(rect10))
//						{
//							Widgets.DrawHighlight(rect10);
//						}
//						TooltipHandler.TipRegion(rect10, backstory.FullDescriptionFor(pawn));
//						Text.Anchor = TextAnchor.MiddleLeft;
//						string str = (backstorySlot != BackstorySlot.Adulthood) ? "Childhood".Translate() : "Adulthood".Translate();
//						Widgets.Label(rect10, str + ":");
//						Text.Anchor = TextAnchor.UpperLeft;
//						Rect rect11 = new Rect(rect10);
//						rect11.x += 90f;
//						rect11.width -= 90f;
//						string title = backstory.Title;
//						Widgets.Label(rect11, title);
//						currentY += rect10.height + 2f;
//					}
//				}
//				currentY += 25f;
//				Text.Font = GameFont.Small;
//				Widgets.Label(new Rect(0f, currentY, 200f, 30f), "IncapableOf".Translate());
//				currentY += 30f;
//				Text.Font = GameFont.Small;
//				StringBuilder stringBuilder = new StringBuilder();
//				WorkTags combinedDisabledWorkTags = pawn.story.CombinedDisabledWorkTags;
//				if (combinedDisabledWorkTags == WorkTags.None)
//				{
//					stringBuilder.Append("(" + "NoneLower".Translate() + "), ");
//				}
//				else
//				{
//					List<WorkTags> list = WorkTagsFrom(combinedDisabledWorkTags).ToList<WorkTags>();
//					bool flag2 = true;
//					foreach (WorkTags current in list)
//					{
//						if (flag2)
//						{
//							stringBuilder.Append(current.LabelTranslated().CapitalizeFirst());
//						}
//						else
//						{
//							stringBuilder.Append(current.LabelTranslated());
//						}
//						stringBuilder.Append(", ");
//						flag2 = false;
//					}
//				}
//				string text = stringBuilder.ToString();
//				text = text.Substring(0, text.Length - 2);
//				Rect rect12 = new Rect(0f, currentY, positionLeftHalf.width, 999f);
//				Widgets.Label(rect12, text);
//				currentY += 100f;
//				Text.Font = GameFont.Medium;
//				Widgets.Label(new Rect(0f, currentY, 200f, 30f), "Traits".Translate());
//				currentY += 30f;
//				Text.Font = GameFont.Small;
//				for (int i = 0; i < pawn.story.traits.allTraits.Count; i++)
//				{
//					Trait trait = pawn.story.traits.allTraits[i];
//					Rect rectCurrentTrait = new Rect(0f, currentY, positionLeftHalf.width, 24f);
//					if (Mouse.IsOver(rectCurrentTrait))
//					{
//						Widgets.DrawHighlight(rectCurrentTrait);
//					}
//					Widgets.Label(rectCurrentTrait, trait.LabelCap);
//					currentY += rectCurrentTrait.height + 2f;
//					Trait trLocal = trait;
//					TipSignal tip = new TipSignal(() => trLocal.TipString(p), (int)currentY * 37);
//					TooltipHandler.TipRegion(rectCurrentTrait, tip);
//				}
//				GUI.EndGroup();
//				GUI.BeginGroup(positionRightHalf);
//				Text.Font = GameFont.Medium;
//				Widgets.Label(new Rect(0f, 0f, 200f, 30f), "Skills".Translate());
//				SkillUI.SkillDrawMode mode;
//				if (Current.ProgramState == ProgramState.Playing)
//				{
//					mode = SkillUI.SkillDrawMode.Gameplay;
//				}
//				else
//				{
//					mode = SkillUI.SkillDrawMode.Menu;
//				}
//				SkillUI.DrawSkillsOf(pawn, new Vector2(0f, 35f), mode);
//				GUI.EndGroup();
//				GUI.EndGroup();

//				return false;
//			}
//		}
//		[HarmonyPatch(typeof(ITab_Pawn_Character), "FillTab")]
//		public static class ITab_Pawn_CharacterPatch
//		{
//			[HarmonyPrefix]
//			public static bool FillTabP(ITab_Pawn_Character __instance)
//			{

//				FieldInfo fi = typeof(ITab_Pawn_Character).GetField("size", Unprivater.flags);
//				fi.SetValue(__instance, new Vector2(cardX+34f, cardY+34f));

//				var p = Unprivater.GetProtectedProperty<Pawn>("PawnToShowInfoAbout", __instance);

//				Rect rect = new Rect(17f, 17f, 570f, cardY);
//				CharacterCardUtility.DrawCharacterCard(rect, p, null, default(Rect));

//				return false;
//			}
//		}

//	}
//}


code:
using System.Linq;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
using Multiplayer.API;
using System.Collections.Generic;

namespace rjw
{
	public static class AfterSexUtility
	{
		//aftersex thoughts
		public static readonly ThoughtDef got_raped = DefDatabase<ThoughtDef>.GetNamed("GotRaped");
		public static readonly ThoughtDef got_anal_raped = DefDatabase<ThoughtDef>.GetNamed("GotAnalRaped");
		public static readonly ThoughtDef got_anal_raped_byfemale = DefDatabase<ThoughtDef>.GetNamed("GotAnalRapedByFemale");
		public static readonly ThoughtDef got_raped_unconscious = DefDatabase<ThoughtDef>.GetNamed("GotRapedUnconscious");
		public static readonly ThoughtDef masochist_got_raped_unconscious = DefDatabase<ThoughtDef>.GetNamed("MasochistGotRapedUnconscious");

		public static readonly ThoughtDef got_bred = DefDatabase<ThoughtDef>.GetNamed("GotBredByAnimal");
		public static readonly ThoughtDef got_anal_bred = DefDatabase<ThoughtDef>.GetNamed("GotAnalBredByAnimal");
		public static readonly ThoughtDef got_licked = DefDatabase<ThoughtDef>.GetNamed("GotLickedByAnimal");
		public static readonly ThoughtDef got_groped = DefDatabase<ThoughtDef>.GetNamed("GotGropedByAnimal");

		public static readonly ThoughtDef masochist_got_raped = DefDatabase<ThoughtDef>.GetNamed("MasochistGotRaped");
		public static readonly ThoughtDef masochist_got_anal_raped = DefDatabase<ThoughtDef>.GetNamed("MasochistGotAnalRaped");
		public static readonly ThoughtDef masochist_got_anal_raped_byfemale = DefDatabase<ThoughtDef>.GetNamed("MasochistGotAnalRapedByFemale");
		public static readonly ThoughtDef masochist_got_bred = DefDatabase<ThoughtDef>.GetNamed("MasochistGotBredByAnimal");
		public static readonly ThoughtDef masochist_got_anal_bred = DefDatabase<ThoughtDef>.GetNamed("MasochistGotAnalBredByAnimal");
		public static readonly ThoughtDef masochist_got_licked = DefDatabase<ThoughtDef>.GetNamed("MasochistGotLickedByAnimal");
		public static readonly ThoughtDef masochist_got_groped = DefDatabase<ThoughtDef>.GetNamed("MasochistGotGropedByAnimal");
		public static readonly ThoughtDef allowed_animal_to_breed = DefDatabase<ThoughtDef>.GetNamed("AllowedAnimalToBreed");
		public static readonly ThoughtDef allowed_animal_to_lick = DefDatabase<ThoughtDef>.GetNamed("AllowedAnimalToLick");
		public static readonly ThoughtDef allowed_animal_to_grope = DefDatabase<ThoughtDef>.GetNamed("AllowedAnimalToGrope");
		public static readonly ThoughtDef zoophile_got_bred = DefDatabase<ThoughtDef>.GetNamed("ZoophileGotBredByAnimal");
		public static readonly ThoughtDef zoophile_got_anal_bred = DefDatabase<ThoughtDef>.GetNamed("ZoophileGotAnalBredByAnimal");
		public static readonly ThoughtDef zoophile_got_licked = DefDatabase<ThoughtDef>.GetNamed("ZoophileGotLickedByAnimal");
		public static readonly ThoughtDef zoophile_got_groped = DefDatabase<ThoughtDef>.GetNamed("ZoophileGotGropedByAnimal");
		public static readonly ThoughtDef hate_my_rapist = DefDatabase<ThoughtDef>.GetNamed("HateMyRapist");
		public static readonly ThoughtDef kinda_like_my_rapist = DefDatabase<ThoughtDef>.GetNamed("KindaLikeMyRapist");
		public static readonly ThoughtDef allowed_me_to_get_raped = DefDatabase<ThoughtDef>.GetNamed("AllowedMeToGetRaped");
		public static readonly ThoughtDef stole_some_lovin = DefDatabase<ThoughtDef>.GetNamed("StoleSomeLovin");
		public static readonly ThoughtDef bloodlust_stole_some_lovin = DefDatabase<ThoughtDef>.GetNamed("BloodlustStoleSomeLovin");
		public static readonly ThoughtDef violated_corpse = DefDatabase<ThoughtDef>.GetNamed("ViolatedCorpse");
		public static readonly ThoughtDef gave_virginity = DefDatabase<ThoughtDef>.GetNamed("FortunateGaveVirginity");
		public static readonly ThoughtDef lost_virginity = DefDatabase<ThoughtDef>.GetNamed("UnfortunateLostVirginity");
		public static readonly ThoughtDef took_virginity = DefDatabase<ThoughtDef>.GetNamed("TookVirginity");

		//violent - mark true when pawn rape partner
		//Note: violent is not reliable, since either pawn could be the rapist. Check jobdrivers instead, they're still active since this is called before ending the job.
		public static void think_about_sex(SexProps props)
		{
			Pawn receiving = props.partner;//TODO: replace this shit at some point when interaction has reversed roles
			think_about_sex(props.pawn, props.partner, receiving == props.pawn, props, props.isWhoring);
			think_about_sex(props.partner, props.pawn, receiving == props.partner, props, false);
		}
		public static void think_about_sex(Pawn pawn, Pawn partner, bool isReceiving, SexProps props, bool whoring = false)
		{
			// Partner should never be null, but just in case something gets changed elsewhere..
			if (partner == null)
			{
				ModLog.Error("xxx::think-after_sex( ERROR: " + xxx.get_pawnname(pawn) + " has no partner. This should not be called from solo acts. Sextype: " + props.sexType);
				return;
			}

			// Both pawns are now checked individually, instead of giving thoughts to the partner.
			//Can just return if the currently checked pawn is dead or can't have thoughts, which simplifies the checks.
			if (pawn.Dead || !xxx.is_human(pawn))
				return;

			bool masochist = xxx.is_masochist(pawn);
			bool zoophile = xxx.is_zoophile(pawn);
			bool guilty = props.canBeGuilty;
			if (HasLoveEnhancer(pawn, partner))
			{
				masochist = true;
				zoophile = true;
			}
			if (masochist)
			{
				if (RJWSettings.rape_beating)
					pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDefWhereOtherPawnIs(ThoughtDefOf.HarmedMe, partner);
			}
			if (!masochist)
			{
				//TODO: someday rough sex?
				//foreach (DirectPawnRelation relation in pawn.relations.DirectRelations)
				//{
				//	if (relation.otherPawn == partner)
				//		if (relation.def == PawnRelationDefOf.Lover ||
				//			relation.def == PawnRelationDefOf.Fiance ||
				//			relation.def == PawnRelationDefOf.Spouse)
				//		{
				//			masochist = true;
				//			break;
				//		}
				//}
				//if (pawn.IsPrisoner || masochist || is_slave(pawn) || pawn.IsDesignatedComfort())
				if (pawn.IsPrisoner || xxx.is_slave(pawn) || pawn.IsDesignatedComfort())
				{
					guilty = false;
				}
			}
			else
				guilty = false;

			ThoughtDef thoughtgain = null;
			//unconscious pawns has no thoughts
			//and if they has sex, its probably rape, since they have no choice
			if (!pawn.health.capacities.CanBeAwake)
			{
				thoughtgain = xxx.is_masochist(pawn) ? masochist_got_raped_unconscious : got_raped_unconscious;
				pawn.needs.mood.thoughts.memories.TryGainMemory(thoughtgain);
				return;
			}

			// TODO: refactor all these repeated arguments to be a `ref struct`.
			// This would be a breaking change, though.

			thoughtgain = think_about_sex_Bestiality(pawn, partner, isReceiving, props, whoring, masochist, zoophile, guilty);

			if (thoughtgain == null)
				thoughtgain = think_about_sex_Rapist(pawn, partner, isReceiving, props, whoring, masochist, zoophile, guilty);

			if (thoughtgain == null)
				thoughtgain = think_about_sex_Victim(pawn, partner, isReceiving, props, whoring, masochist, zoophile, guilty);

			if (thoughtgain == null)
				thoughtgain = think_about_sex_Consensual(pawn, partner, isReceiving, props, whoring, masochist, zoophile, guilty);

			if (thoughtgain != null)
			{
				var newMemory = FinalizeThought(thoughtgain, pawn, partner, isReceiving, props, whoring, masochist, zoophile, guilty);
				pawn.needs.mood?.thoughts.memories.TryGainMemory(newMemory, partner);
			}
		}

		public static Thought_Memory FinalizeThought(
			ThoughtDef thoughtgain,
			Pawn pawn,
			Pawn partner,
			bool isReceiving,
			SexProps props,
			bool whoring,
			bool masochist,
			bool zoophile,
			bool guilty
		  )
		{
			var newMemory = (Thought_Memory)ThoughtMaker.MakeThought(thoughtgain);

			// Apply mood boost for vanilla lovin' from love enhancer.
			if (thoughtgain == ThoughtDefOf.GotSomeLovin && HasLoveEnhancer(pawn, partner))
				newMemory.moodPowerFactor = 1.5f;

			return newMemory;
		}

		public static ThoughtDef think_about_sex_Bestiality(
			Pawn pawn,
			Pawn partner,
			bool isReceiving,
			SexProps props,
			bool whoring,
			bool masochist,
			bool zoophile,
			bool guilty
			)
		{

			//ModLog.Message("xxx::think-after_sex( ERROR: 3");
			// Thoughts for animal-on-colonist.
			ThoughtDef thoughtDef = null;
			if (xxx.is_animal(partner) && isReceiving)
			{
				if (!zoophile && !props.isRape)
				{
					//ModLog.Message("xxx::think-after_sex( ERROR: 3.1");
					if (props.sexType == xxx.rjwSextype.Oral)
						thoughtDef = (allowed_animal_to_lick);
					else if (props.sexType == xxx.rjwSextype.Anal || props.sexType == xxx.rjwSextype.Vaginal)
						thoughtDef = (allowed_animal_to_breed);
					else //Other rarely seen sex types, such as fingering (by primates, monster girls, etc)
						thoughtDef = (allowed_animal_to_grope);
				}
				else
				{
					//ModLog.Message("xxx::think-after_sex( ERROR: 3.2");
					if (!zoophile)
					{
						if (props.sexType == xxx.rjwSextype.Oral)
							thoughtDef = (masochist ? masochist_got_licked : got_licked);
						else if (props.sexType == xxx.rjwSextype.Vaginal)
							thoughtDef = (masochist ? masochist_got_bred : got_bred);
						else if (props.sexType == xxx.rjwSextype.Anal)
							thoughtDef = (masochist ? masochist_got_anal_bred : got_anal_bred);
						else //Other types
							thoughtDef = (masochist ? masochist_got_groped : got_groped);
					}
					else
					{
						if (props.sexType == xxx.rjwSextype.Oral)
							thoughtDef = (zoophile_got_licked);
						else if (props.sexType == xxx.rjwSextype.Vaginal)
							thoughtDef = (zoophile_got_bred);
						else if (props.sexType == xxx.rjwSextype.Anal)
							thoughtDef = (zoophile_got_anal_bred);
						else //Other types
							thoughtDef = (zoophile_got_groped);
					}
				}
				think_about_sex_Bestiality_TameAttempt(pawn, partner, isReceiving, props, whoring, masochist, zoophile, guilty);
			}
			return thoughtDef;
		}

		public static void think_about_sex_Bestiality_TameAttempt(
			Pawn pawn,
			Pawn partner,
			bool isReceiving,
			SexProps props,
			bool whoring,
			bool masochist,
			bool zoophile,
			bool guilty
			)
		{
			if (pawn.CurJob != null)
				if (!(pawn.Downed && partner.Downed))
					if (!partner.Dead && zoophile && (pawn.CurJob.def != xxx.gettin_raped) && partner.Faction == null && pawn.Faction == Faction.OfPlayer)
					{
						InteractionDef intDef = !partner.AnimalOrWildMan() ? InteractionDefOf.RecruitAttempt : InteractionDefOf.TameAttempt;
						pawn.interactions.TryInteractWith(partner, intDef);
					}
		}

		public static ThoughtDef think_about_sex_Rapist(
			Pawn pawn,
			Pawn partner,
			bool isReceiving,
			SexProps props,
			bool whoring,
			bool masochist,
			bool zoophile,
			bool guilty
			)
		{
			ThoughtDef thoughtDef = null;
			if (partner.Dead || (partner.CurJob != null && partner.CurJob.def == xxx.gettin_raped) || pawn.jobs?.curDriver is JobDriver_Rape)
			{
				thoughtDef = (xxx.is_rapist(pawn) || xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn)) ? bloodlust_stole_some_lovin : stole_some_lovin;

				if ((xxx.is_necrophiliac(pawn) || xxx.is_psychopath(pawn)) && partner.Dead)
				{
					thoughtDef = (violated_corpse);
				}
			}
			return thoughtDef;
		}

		public static ThoughtDef think_about_sex_Victim(
			Pawn pawn,
			Pawn partner,
			bool isReceiving,
			SexProps props,
			bool whoring,
			bool masochist,
			bool zoophile,
			bool guilty
			)
		{
			ThoughtDef thoughtDef = null;
			if (pawn.CurJob != null && pawn.CurJob.def == xxx.gettin_raped || partner.jobs?.curDriver is JobDriver_Rape)
			{
				if (xxx.is_human(partner))
				{
					if (props.sexType == xxx.rjwSextype.Anal)
					{
						if (xxx.is_female(partner))
						{
							thoughtDef = masochist ? masochist_got_anal_raped_byfemale : got_anal_raped_byfemale;
						}
						else
						{
							thoughtDef = masochist ? masochist_got_anal_raped : got_anal_raped;
						}
					}
					else
					{
						thoughtDef = masochist ? masochist_got_raped : got_raped;
					}

					ThoughtDef pawn_thought_about_rapist = masochist ? kinda_like_my_rapist : hate_my_rapist;
					pawn.needs.mood.thoughts.memories.TryGainMemory(pawn_thought_about_rapist, partner);
					if (guilty)
						partner.guilt.Notify_Guilty(GenDate.TicksPerDay);

				}

				think_about_sex_Victim_Blame(pawn, partner, isReceiving, props, whoring, masochist, zoophile, guilty);
			}
			return thoughtDef;
		}
		public static void think_about_sex_Victim_Blame(
			Pawn pawn,
			Pawn partner,
			bool isReceiving,
			SexProps props,
			bool whoring,
			bool masochist,
			bool zoophile,
			bool guilty
			)
		{
			if (pawn.Faction != null && pawn.Map != null && !masochist && !(xxx.is_animal(partner) && zoophile))
			{
				foreach (Pawn bystander in pawn.Map.mapPawns.SpawnedPawnsInFaction(pawn.Faction).Where(x => !xxx.is_animal(x) && x != pawn && x != partner && !x.Downed && !x.Suspended))
				{
					// dont see through walls, dont see whole map, only 15 cells around
					if (pawn.CanSee(bystander) && pawn.Position.DistanceTo(bystander.Position) < 15)
					{
						pawn.needs.mood.thoughts.memories.TryGainMemory(allowed_me_to_get_raped, bystander);
					}
				}
			}
		}

		public static ThoughtDef think_about_sex_Consensual(
			Pawn pawn,
			Pawn partner,
			bool isReceiving,
			SexProps props,
			bool whoring,
			bool masochist,
			bool zoophile,
			bool guilty
			)
		{
			ThoughtDef thoughtDef = null;
			if (xxx.is_human(partner))
			{
				if (!props.isCoreLovin && !whoring)
				{
					// human partner and not part of rape or necrophilia. add the vanilla GotSomeLovin thought
					thoughtDef = ThoughtDefOf.GotSomeLovin;
				}
			}
			return thoughtDef;
		}

		public static bool HasLoveEnhancer(Pawn pawn) =>
			pawn.health.hediffSet.hediffs.Any((Hediff h) => h.def == HediffDefOf.LoveEnhancer);

		public static bool HasLoveEnhancer(Pawn pawn, Pawn partner) =>
			HasLoveEnhancer(pawn) || HasLoveEnhancer(partner);

		public static void UpdateRecords(SexProps props)
		{
			UpdateRecordsInitiator(props);
			if (props.sexType != xxx.rjwSextype.Masturbation)
				UpdateRecordsPartner(props);
		}

		private static void UpdateRecordsInitiator(SexProps props)
		{

			Pawn pawn = props.pawn;
			Pawn partner = props.partner;

			if (props.sexType == xxx.rjwSextype.Masturbation)
			{
				pawn.records.Increment(xxx.CountOfFappin);
				return;
			}

			//bool isVirginSex = xxx.is_Virgin(pawn); //need copy value before count increase.
			//ThoughtDef currentThought = null;

			pawn.records.Increment(xxx.CountOfSex);
			//bool masochist = xxx.is_masochist(pawn);
			//bool zoophile = xxx.is_zoophile(pawn);
			//if (HasLoveEnhancer(pawn, partner))
			//{
			//	masochist = true;
			//	zoophile = true;
			//}

			if (props.partner.Dead)
			{
				pawn.records.Increment(xxx.CountOfSexWithCorpse);
			}
			if (!props.isRape)
			{
				if (xxx.is_human(partner))
				{
					pawn.records.Increment(xxx.CountOfSexWithHumanlikes);
					//currentThought = isLoveSex ? xxx.gave_virginity : null;
				}
				else if (xxx.is_insect(partner))
				{
					pawn.records.Increment(xxx.CountOfSexWithInsects);
				}
				else if (xxx.is_animal(partner))
				{
					pawn.records.Increment(xxx.CountOfSexWithAnimals);
					//currentThought = zoophile ? xxx.gave_virginity : null;
				}
				else
				{
					pawn.records.Increment(xxx.CountOfSexWithOthers);
				}
			}
			else
			{
				//if (!pawnIsRaper)
				//{
				//	currentThought = masochist ? xxx.gave_virginity : xxx.lost_virginity;
				//}

				if (xxx.is_human(partner))
				{
					pawn.records.Increment(xxx.CountOfRapedHumanlikes);
					//if (pawnIsRaper && (xxx.is_rapist(pawn) || xxx.is_bloodlust(pawn)))
					//	currentThought = xxx.gave_virginity;
				}
				else if (xxx.is_insect(partner))
				{
					pawn.records.Increment(xxx.CountOfSexWithInsects);
					pawn.records.Increment(xxx.CountOfRapedInsects);
				}
				else if (xxx.is_animal(partner))
				{
					pawn.records.Increment(xxx.CountOfSexWithAnimals);
					pawn.records.Increment(xxx.CountOfRapedAnimals);
					//if (zoophile) currentThought = xxx.gave_virginity;
				}
				else
				{
					pawn.records.Increment(xxx.CountOfSexWithOthers);
					pawn.records.Increment(xxx.CountOfRapedOthers);
				}
			}

			//if (isVirginSex) //&& (sextype == rjwSextype.Vaginal || sextype == rjwSextype.DoublePenetration))
			//{
			//	Log.Message(xxx.get_pawnname(pawn) + " | " + xxx.get_pawnname(partner) + " | " + currentThought);
			//	Log.Message("1");
			//	if (!is_animal(partner))//passive
			//	{
			//		if (currentThought != null)
			//			partner.needs.mood.thoughts.memories.TryGainMemory(currentThought);
			//	}
			//	Log.Message("2");
			//	if (!is_animal(pawn))//active
			//	{
			//		currentThought = took_virginity;
			//		pawn.needs.mood.thoughts.memories.TryGainMemory(currentThought);
			//	}
			//}
		}

		private static void UpdateRecordsPartner(SexProps props)
		{

			if (props.partner == null || props.partner.Dead) // masturbation or corpse fucking
				return;

			Pawn pawn = props.partner;
			Pawn partner = props.pawn;

			//bool isVirginSex = xxx.is_Virgin(pawn); //need copy value before count increase.
			//ThoughtDef currentThought = null;

			pawn.records.Increment(xxx.CountOfSex);
			//bool masochist = xxx.is_masochist(pawn);
			//bool zoophile = xxx.is_zoophile(pawn);
			//if (HasLoveEnhancer(pawn, partner))
			//{
			//	masochist = true;
			//	zoophile = true;
			//}

			if (!props.isRape)
			{
				if (xxx.is_human(partner))
				{
					pawn.records.Increment(xxx.CountOfSexWithHumanlikes);
					//currentThought = isLoveSex ? xxx.gave_virginity : null;
				}
				else if (xxx.is_insect(partner))
				{
					pawn.records.Increment(xxx.CountOfSexWithInsects);
				}
				else if (xxx.is_animal(partner))
				{
					pawn.records.Increment(xxx.CountOfSexWithAnimals);
					//currentThought = zoophile ? xxx.gave_virginity : null;
				}
				else
				{
					pawn.records.Increment(xxx.CountOfSexWithOthers);
				}
			}
			else
			{
				//if (!pawnIsRaper)
				//{
				//	currentThought = masochist ? xxx.gave_virginity : xxx.lost_virginity;
				//}

				if (xxx.is_human(partner))
				{
					pawn.records.Increment(xxx.CountOfBeenRapedByHumanlikes);
					//if (pawnIsRaper && (xxx.is_rapist(pawn) || xxx.is_bloodlust(pawn)))
					//	currentThought = xxx.gave_virginity;
				}
				else if (xxx.is_insect(partner))
				{
					pawn.records.Increment(xxx.CountOfSexWithInsects);
					pawn.records.Increment(xxx.CountOfBeenRapedByInsects);
				}
				else if (xxx.is_animal(partner))
				{
					pawn.records.Increment(xxx.CountOfSexWithAnimals);
					pawn.records.Increment(xxx.CountOfBeenRapedByAnimals);
					//if (zoophile) currentThought = xxx.gave_virginity;
				}
				else
				{
					pawn.records.Increment(xxx.CountOfSexWithOthers);
					pawn.records.Increment(xxx.CountOfBeenRapedByOthers);
				}
			}

			//if (isVirginSex) //&& (sextype == rjwSextype.Vaginal || sextype == rjwSextype.DoublePenetration))
			//{
			//	Log.Message(xxx.get_pawnname(pawn) + " | " + xxx.get_pawnname(partner) + " | " + currentThought);
			//	Log.Message("1");
			//	if (!is_animal(partner))//passive
			//	{
			//		if (currentThought != null)
			//			partner.needs.mood.thoughts.memories.TryGainMemory(currentThought);
			//	}
			//	Log.Message("2");
			//	if (!is_animal(pawn))//active
			//	{
			//		currentThought = took_virginity;
			//		pawn.needs.mood.thoughts.memories.TryGainMemory(currentThought);
			//	}
			//}
		}

		//============↓======Section of processing the broken body system===============↓=============
		public static bool BodyIsBroken(Pawn pawn)
		{
			return pawn.health.hediffSet.HasHediff(xxx.feelingBroken);
		}

		[SyncMethod]
		public static bool BadlyBroken(Pawn pawn, out Trait addedTrait)
		{
			addedTrait = null;
			if (!BodyIsBroken(pawn))
			{
				return false;
			}

			int stage = pawn.health.hediffSet.GetFirstHediffOfDef(xxx.feelingBroken).CurStageIndex;
			if (stage >= 3)
			{
				//when broken make character masochist
				//todo remove/replace social/needs dubuffs
				if (RJWSettings.AddTrait_Masocist && !xxx.is_masochist(pawn))
				{
					var chance = 0.05f;
					if (Rand.Chance(chance))
					{
						if (xxx.is_rapist(pawn))
						{
							pawn.story.traits.allTraits.Remove(pawn.story.traits.GetTrait(xxx.rapist));
							//Log.Message(xxx.get_pawnname(pawn) + " BadlyBroken, switch rapist -> masochist");
						}
						addedTrait = new Trait(xxx.masochist);
						pawn.story.traits.GainTrait(addedTrait);
						//Log.Message(xxx.get_pawnname(pawn) + " BadlyBroken, not masochist, adding masochist trait");

						pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(got_raped);
						pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(got_licked);
						pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(hate_my_rapist);
						pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(allowed_me_to_get_raped);
						pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(got_anal_raped);
						pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(got_anal_raped_byfemale);
					}
				}
				if (pawn.IsPrisonerOfColony)
				{
					pawn.guest.resistance = Mathf.Max(pawn.guest.resistance - 1f, 0f);
					//Log.Message(xxx.get_pawnname(pawn) + " BadlyBroken, reduce prisoner resistance");
				}
			}
			return stage > 1;
		}

		//add variant for eggs?
		public static void processBrokenPawn(Pawn pawn, List<Trait> addedTraits)
		{
			// Called after rape/breed
			if (pawn is null)
				return;

			if (xxx.is_human(pawn) && !pawn.Dead && pawn.records != null)
			{
				if (xxx.has_traits(pawn))
				{
					if (xxx.is_slime(pawn))
						return;

					if (!BodyIsBroken(pawn))
						pawn.health.AddHediff(xxx.feelingBroken);
					else
					{
						float brokenSeverityGain = xxx.feelingBroken.initialSeverity;
						int feelingBrokenStage = pawn.health.hediffSet.GetFirstHediffOfDef(xxx.feelingBroken).CurStageIndex;

						if (xxx.RoMIsActive)
							if (pawn.story.traits.HasTrait(xxx.Succubus))
								brokenSeverityGain *= 0.25f;

						if (pawn.story.traits.HasTrait(TraitDefOf.Tough))
						{
							brokenSeverityGain *= 0.5f;
						}
						if (pawn.story.traits.HasTrait(TraitDefOf.Wimp))
						{
							brokenSeverityGain *= 2.0f;
						}
						if (pawn.story.traits.HasTrait(TraitDefOf.Nerves))
						{
							int currentNervesDegree = pawn.story.traits.DegreeOfTrait(TraitDefOf.Nerves);

							switch (currentNervesDegree)
							{
								case -2:
									brokenSeverityGain *= 2.0f;
									break;
								case -1:
									brokenSeverityGain *= 1.5f;
									break;
								case 1:
									brokenSeverityGain *= 0.5f;
									break;
								case 2:
									brokenSeverityGain *= 0.25f;
									break;
							}

							if (RJWSettings.AddTrait_Nerves)
							{
								int newNervesDegree = 0;
								if (feelingBrokenStage >= 3 && currentNervesDegree > -2)
								{
									newNervesDegree = -2;
								}
								else if (feelingBrokenStage >= 2 && currentNervesDegree > -1)
								{
									newNervesDegree = -1;
								}

								if (newNervesDegree < 0)
								{
									pawn.story.traits.RemoveTrait(pawn.story.traits.GetTrait(TraitDefOf.Nerves));
									Trait newNerves = new Trait(TraitDefOf.Nerves, newNervesDegree);
									pawn.story.traits.GainTrait(newNerves);
									addedTraits.Add(newNerves);
								}
							}
						}
						else if (RJWSettings.AddTrait_Nerves && feelingBrokenStage > 1)
						{
							pawn.story.traits.GainTrait(new Trait(TraitDefOf.Nerves, -1));
							Trait nervous = new Trait(TraitDefOf.Nerves, -1);
							pawn.story.traits.GainTrait(nervous);
							addedTraits.Add(nervous);

						}
						pawn.health.hediffSet.GetFirstHediffOfDef(xxx.feelingBroken).Severity += brokenSeverityGain;
					}

					BadlyBroken(pawn, out Trait newMasochistTrait);
					if (newMasochistTrait != null)
					{
						addedTraits.Add(newMasochistTrait);
					}
				}
			}
		}

		//============↑======Section of processing the broken body system===============↑=============
	}
}


code:
using Verse;
using Verse.AI;
using System.Linq;
using RimWorld;
using System;

namespace rjw
{
	public static class Bed_Utility
	{
		public static bool bed_has_at_least_two_occupants(Building_Bed bed)
		{
			return bed.CurOccupants.Count() >= 2;
		}

		public static bool in_same_bed(Pawn pawn, Pawn partner)
		{
			if (pawn.InBed() && partner.InBed())
			{
				if (pawn.CurrentBed() == partner.CurrentBed())
					return true;
			}
			return false;
		}

		public static bool is_laying_down_alone(Pawn pawn)
		{
			if (pawn == null || !pawn.InBed()) return false;

			if (pawn.CurrentBed() != null)
				return !bed_has_at_least_two_occupants(pawn.CurrentBed());
			return true;
		}

		public static IntVec3 SleepPosOfAssignedPawn(this Building_Bed bed, Pawn pawn)
		{
			int slotIndex = 0;

			if (bed.OwnersForReading.Contains(pawn))
			{
				for (byte i = 0; i < bed.OwnersForReading.Count; i++)
				{
					if (bed.OwnersForReading[i] == pawn)
					{
						slotIndex = i;
					}
				}
			}
			else
			{
				// get random position
				slotIndex = GenTicks.TicksGame % bed.SleepingSlotsCount;
			}

			return bed.GetSleepingSlotPos(slotIndex);
		}
		public static void FailOnBedNoLongerUsable(this Toil toil, TargetIndex bedIndex, Building_Bed bed)
		{
			if (toil == null)
			{
				throw new ArgumentNullException(nameof(toil));
			}

			toil.FailOnDespawnedOrNull(bedIndex);
			toil.FailOn(bed.IsBurning);
			toil.FailOn(() => HealthAIUtility.ShouldSeekMedicalRestUrgent(toil.actor));
			toil.FailOn(() => toil.actor.IsColonist && !toil.actor.CurJob.ignoreForbidden && !toil.actor.Downed && bed.IsForbidden(toil.actor));
		}
	}
}


code:
//#define TESTMODE // Uncomment to enable logging.

using Verse;
using Verse.AI;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using System.Diagnostics;
using Multiplayer.API;

namespace rjw
{
	/// <summary>
	/// Helper for sex with animals
	/// </summary>
	public class BreederHelper
	{
		public const int max_animals_at_once = 1; // lets not forget that the purpose is procreation, not sodomy

		[Conditional("TESTMODE")]
		private static void DebugText(string msg)
		{
			ModLog.Message(msg);
		}

		//animal try to find best designated pawn to breed
		[SyncMethod]
		public static Pawn find_designated_breeder(Pawn pawn, Map m)
		{
			if (!DesignatorsData.rjwBreeding.Any())
				return null;

			DebugText("BreederHelper::find_designated_breeder( " + xxx.get_pawnname(pawn) + " ) called");

			float min_fuckability = 0.10f;							// Don't rape pawns with <10% fuckability
			float avg_fuckability = 0f;								// Average targets fuckability, choose target higher than that
			var valid_targets = new Dictionary<Pawn, float>();		// Valid pawns and their fuckability
			Pawn chosentarget = null;                               // Final target pawn

			DebugText(" breeder.Faction( " + pawn.Faction + " )");
			if (pawn.Faction == null)// HostileTo causes error on fresh colony(w/o name)
				return null;

			DebugText(" DesignatorsData.rjwBreeding.Count( " + DesignatorsData.rjwBreeding.Count() + " )");
			IEnumerable<Pawn> targets = DesignatorsData.rjwBreeding.Where(x
				=> x != pawn
				&& xxx.is_not_dying(x)
				&& xxx.can_get_raped(x)
				&& !x.IsForbidden(pawn)
				&& !x.Suspended
				&& !x.HostileTo(pawn)
				&& !(x.IsPregnant() && xxx.is_animal(x))
				&& pawn.CanReserveAndReach(x, PathEndMode.Touch, Danger.Some, max_animals_at_once)
				&& ((RJWSettings.bestiality_enabled && xxx.is_human(x)) || (RJWSettings.animal_on_animal_enabled && xxx.is_animal(x)))
				);

			DebugText(" initial targets( " + targets.Count() + " )");

			var parts = pawn.GetGenitalsList();

			foreach (Pawn target in targets)
			{
				DebugText(" Checking target " + target.kindDef.race.defName.ToLower());
				if (!Pather_Utility.cells_to_target_rape(pawn, target.Position))
					continue;// too far

				var fuc = SexAppraiser.would_fuck(pawn, target, invert_opinion: true, ignore_gender: (Genital_Helper.has_male_bits(pawn, parts) || xxx.is_insect(pawn)));
				DebugText(" target score( " + xxx.get_pawnname(pawn) + " -> " + xxx.get_pawnname(target) + " (" + fuc.ToString() + " / " + min_fuckability.ToString() + ")");

				if (fuc > min_fuckability)
					if (Pather_Utility.can_path_to_target(pawn, target.Position))
						valid_targets.Add(target, fuc);
			}

			DebugText(" valid_targets( " + valid_targets.Count() + " )");

			if (valid_targets.Any())
			{
				//avg_fuckability = valid_targets.Average(x => x.Value);

				// choose pawns to fuck with above average fuckability
				var valid_targetsFilteredAnimals = valid_targets.Where(x => x.Value >= avg_fuckability);

				if (valid_targetsFilteredAnimals.Any())
					chosentarget = valid_targetsFilteredAnimals.RandomElement().Key;
			}

			DebugText(" chosentarget( " + xxx.get_pawnname(chosentarget) + " )");
			return chosentarget;
		}

		//animal or human try to find animals to breed (non designation)
		//public static Pawn find_breeder_animal(Pawn pawn, Map m, bool SameRace = true)
		[SyncMethod]
		public static Pawn find_breeder_animal(Pawn pawn, Map m)
		{
			DebugText("BreederHelper::find_breeder_animal( " + xxx.get_pawnname(pawn) + " ) called");

			float min_fuckability = 0.10f;							// Don't rape pawns with <10% fuckability
			float avg_fuckability = 0f;								// Average targets fuckability, choose target higher than that
			var valid_targets = new Dictionary<Pawn, float>();		// Valid pawns and their fuckability
			Pawn chosentarget = null;                               // Final target pawn

			DebugText(" m.mapPawns.AllPawnsSpawned.Count( " + m.mapPawns.AllPawnsSpawned.Count() + " )");
			//Pruning initial pawn list.
			IEnumerable<Pawn> targets = m.mapPawns.AllPawnsSpawned.Where(x
				=> x != pawn
				&& xxx.is_animal(x)
				&& xxx.can_get_raped(x)
				&& !x.IsForbidden(pawn)
				&& !x.Suspended
				&& !x.HostileTo(pawn)
				&& pawn.CanReserveAndReach(x, PathEndMode.Touch, Danger.Some, max_animals_at_once)
				&& ((RJWSettings.bestiality_enabled && xxx.is_human(pawn)) || (RJWSettings.animal_on_animal_enabled && xxx.is_animal(pawn)))
				//&& SameRace ? pawn.kindDef.race == x.kindDef.race : true
				);

			DebugText(" initial targets( " + targets.Count() + " )");

			if (targets.Any())
			{
				var parts = pawn.GetGenitalsList();

				//filter pawns for female, who can fuck her
				//not sure if faction check should be but w/e
				if (!Genital_Helper.has_male_bits(pawn, parts) && (Genital_Helper.has_vagina(pawn, parts) || Genital_Helper.has_anus(pawn)))
				{
					targets = targets.Where(x => xxx.can_fuck(x) && x.Faction == pawn.Faction);
				}

				//for humans, animals dont have need - always = 3f
				//if not horny, seek only targets with safe temp
				if (xxx.need_some_sex(pawn) < 3.0f)
				{
					targets = targets.Where(x => pawn.CanReach(x, PathEndMode.Touch, Danger.None));
				}

				//Used for interspecies animal-on-animal.
				//Animals will only go for targets they can see.
				if (xxx.is_animal(pawn))
				{
					targets = targets.Where(x => pawn.CanSee(x) && pawn.def.defName != x.def.defName);
				}
				else
				{
					// Pickier about the targets if the pawn has no prior experience.
					if (pawn.records.GetValue(xxx.CountOfSexWithAnimals) < 3 && !xxx.is_zoophile(pawn))
					{
						min_fuckability *= 2f;
					}

					if (xxx.is_frustrated(pawn))
					{   // Less picky when frustrated...
						min_fuckability *= 0.6f;
					}
					else if (!xxx.is_hornyorfrustrated(pawn))
					{   // ...and far more picky when satisfied.
						min_fuckability *= 2.5f;
					}
				}
			}

			if (!targets.Any())
			{
				DebugText(" valid_targets( " + targets.Count() + " )");
				return null; //None found.
			}

			foreach (Pawn target in targets)
			{
				DebugText(" Checking target " + target.kindDef.race.defName.ToLower());

				if (!Pather_Utility.cells_to_target_rape(pawn, target.Position))
					continue;// too far

				float fuc = SexAppraiser.would_fuck_animal(pawn, target); // 0.0 to ~3.0, orientation checks etc.
				DebugText(" target score( " + xxx.get_pawnname(pawn) + " -> " + xxx.get_pawnname(target) + " (" + fuc.ToString() + " / " + min_fuckability.ToString() + ")");

				if (fuc > min_fuckability)
					if (Pather_Utility.can_path_to_target(pawn, target.Position))
						valid_targets.Add(target, fuc);
			}

			DebugText(" valid_targets( " + valid_targets.Count() + " )");
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			if (valid_targets.Any())
			{
				avg_fuckability = valid_targets.Average(x => x.Value);

				// choose pawns to fuck with above average fuckability
				var valid_targetsFilteredAnimals = valid_targets.Where(x => x.Value >= avg_fuckability);

				if (valid_targetsFilteredAnimals.Any())
					chosentarget = valid_targetsFilteredAnimals.RandomElement().Key;
			}

			DebugText(" chosentarget( " + xxx.get_pawnname(chosentarget) + " )");
			return chosentarget;
		}
	}
}