code:
/*
 *
 *
 * ALL THE CODE IN THIS FILE HAS JUST BEEN COPIED FROM THE DECOMPILED CORE ASSEMBLY.
 * used for nymphs generation, since CORE for some reason ignores gender preference
 *
 *
 */

using RimWorld;
using RimWorld.Planet;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using UnityEngine;
using Verse;
using Multiplayer.API;

namespace rjw
{
	[HasDebugOutput]
	public static class rjw_PawnGenerator
	{
		[StructLayout(LayoutKind.Sequential, Size = 1)]
		private struct PawnGenerationStatus
		{
			public Pawn Pawn
			{
				get;
				private set;
			}

			public List<Pawn> PawnsGeneratedInTheMeantime
			{
				get;
				private set;
			}

			public PawnGenerationStatus(Pawn pawn, List<Pawn> pawnsGeneratedInTheMeantime)
			{
				this = default(PawnGenerationStatus);
				Pawn = pawn;
				PawnsGeneratedInTheMeantime = pawnsGeneratedInTheMeantime;
			}
		}

		private static List<PawnGenerationStatus> pawnsBeingGenerated = new List<PawnGenerationStatus>();

		private static PawnRelationDef[] relationsGeneratableBlood = (from rel in DefDatabase<PawnRelationDef>.AllDefsListForReading
		where rel.familyByBloodRelation && rel.generationChanceFactor > 0f
		select rel).ToArray();

		private static PawnRelationDef[] relationsGeneratableNonblood = (from rel in DefDatabase<PawnRelationDef>.AllDefsListForReading
		where !rel.familyByBloodRelation && rel.generationChanceFactor > 0f
		select rel).ToArray();

		public const float MaxStartMentalBreakThreshold = 0.4f;

		private static SimpleCurve DefaultAgeGenerationCurve = new SimpleCurve
		{
			new CurvePoint(0.05f, 0f),
			new CurvePoint(0.1f, 100f),
			new CurvePoint(0.675f, 100f),
			new CurvePoint(0.75f, 30f),
			new CurvePoint(0.875f, 18f),
			new CurvePoint(1f, 10f),
			new CurvePoint(1.125f, 3f),
			new CurvePoint(1.25f, 0f)
		};

		public const float MaxGeneratedMechanoidAge = 2500f;

		private static readonly SimpleCurve AgeSkillMaxFactorCurve = new SimpleCurve
		{
			new CurvePoint(0f, 0f),
			new CurvePoint(10f, 0.7f),
			new CurvePoint(35f, 1f),
			new CurvePoint(60f, 1.6f)
		};

		private static readonly SimpleCurve LevelFinalAdjustmentCurve = new SimpleCurve
		{
			new CurvePoint(0f, 0f),
			new CurvePoint(10f, 10f),
			new CurvePoint(20f, 16f),
			new CurvePoint(27f, 20f)
		};

		private static readonly SimpleCurve LevelRandomCurve = new SimpleCurve
		{
			new CurvePoint(0f, 0f),
			new CurvePoint(0.5f, 150f),
			new CurvePoint(4f, 150f),
			new CurvePoint(5f, 25f),
			new CurvePoint(10f, 5f),
			new CurvePoint(15f, 0f)
		};

//		no idea wtf is that
//		[CompilerGenerated]
//		private static Func<Pawn, bool> _003C_003Ef__mg_0024cache0;
//
//		[CompilerGenerated]
//		private static Func<Pawn, float> _003C_003Ef__mg_0024cache1;
//
//		[CompilerGenerated]
//		private static Func<Pawn, float> _003C_003Ef__mg_0024cache2;
//
//		[CompilerGenerated]
//		private static Func<Pawn, float> _003C_003Ef__mg_0024cache3;
//
		public static void Reset()
		{
			relationsGeneratableBlood = (from rel in DefDatabase<PawnRelationDef>.AllDefsListForReading
			where rel.familyByBloodRelation && rel.generationChanceFactor > 0f
			select rel).ToArray();
			relationsGeneratableNonblood = (from rel in DefDatabase<PawnRelationDef>.AllDefsListForReading
			where !rel.familyByBloodRelation && rel.generationChanceFactor > 0f
			select rel).ToArray();
		}

		public static Pawn GeneratePawn(PawnKindDef kindDef, Faction faction = null)
		{
			return GeneratePawn(new PawnGenerationRequest(kindDef, faction));
		}

		public static Pawn GeneratePawn(PawnGenerationRequest request)
		{
			try
			{
				Pawn pawn = GenerateOrRedressPawnInternal(request);
				if (pawn != null && !request.AllowDead && pawn.health.hediffSet.hediffs.Any())
				{
					bool dead = pawn.Dead;
					bool downed = pawn.Downed;
					pawn.health.hediffSet.DirtyCache();
					pawn.health.CheckForStateChange(null, null);
					if (pawn.Dead)
					{
						Log.Error("Pawn was generated dead but the pawn generation request specified the pawn must be alive. This shouldn't ever happen even if we ran out of tries because null pawn should have been returned instead in this case. Resetting health...\npawn.Dead=" + pawn.Dead + " pawn.Downed=" + pawn.Downed + " deadBefore=" + dead + " downedBefore=" + downed + "\nrequest=" + request);
						pawn.health.Reset();
					}
				}
				if (pawn.Faction == Faction.OfPlayerSilentFail)
				{
					Find.StoryWatcher.watcherPopAdaptation.Notify_PawnEvent(pawn, PopAdaptationEvent.GainedColonist);
				}
				return pawn;
			}
			catch (Exception arg)
			{
				Log.Error("Error while generating pawn. Rethrowing. Exception: " + arg);
				throw;
			}
			finally
			{
			}
		}

		[SyncMethod]
		private static Pawn GenerateOrRedressPawnInternal(PawnGenerationRequest request)
		{
			Pawn result = null;
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			if (!request.Newborn && !request.ForceGenerateNewPawn)
			{
				if (request.ForceRedressWorldPawnIfFormerColonist)
				{
					IEnumerable<Pawn> validCandidatesToRedress = GetValidCandidatesToRedress(request);
					if (validCandidatesToRedress.Where(PawnUtility.EverBeenColonistOrTameAnimal).TryRandomElementByWeight(WorldPawnSelectionWeight, out result))
					{
						RedressPawn(result, request);
						Find.WorldPawns.RemovePawn(result);
					}
				}
				if (result == null && request.Inhabitant && request.Tile != -1)
				{
					SettlementBase settlement = Find.WorldObjects.WorldObjectAt<SettlementBase>(request.Tile);
					if (settlement != null && settlement.previouslyGeneratedInhabitants.Any())
					{
						IEnumerable<Pawn> validCandidatesToRedress2 = GetValidCandidatesToRedress(request);
						if ((from x in validCandidatesToRedress2
						where settlement.previouslyGeneratedInhabitants.Contains(x)
						select x).TryRandomElementByWeight(WorldPawnSelectionWeight, out result))
						{
							RedressPawn(result, request);
							Find.WorldPawns.RemovePawn(result);
						}
					}
				}
				if (result == null && Rand.Chance(ChanceToRedressAnyWorldPawn(request)))
				{
					IEnumerable<Pawn> validCandidatesToRedress3 = GetValidCandidatesToRedress(request);
					if (validCandidatesToRedress3.TryRandomElementByWeight(WorldPawnSelectionWeight, out result))
					{
						RedressPawn(result, request);
						Find.WorldPawns.RemovePawn(result);
					}
				}
			}
			bool redressed;
			if (result == null)
			{
				redressed = false;
				result = GenerateNewPawnInternal(ref request);
				if (result == null)
				{
					return null;
				}
				if (request.Inhabitant && request.Tile != -1)
				{
					Find.WorldObjects.WorldObjectAt<SettlementBase>(request.Tile)?.previouslyGeneratedInhabitants.Add(result);
				}
			}
			else
			{
				redressed = true;
			}
			if (Find.Scenario != null)
			{
				Find.Scenario.Notify_PawnGenerated(result, request.Context, redressed);
			}
			return result;
		}

		public static void RedressPawn(Pawn pawn, PawnGenerationRequest request)
		{
			try
			{
				pawn.ChangeKind(request.KindDef);
				GenerateGearFor(pawn, request);
				if (pawn.Faction != request.Faction)
				{
					pawn.SetFaction(request.Faction);
				}
				if (pawn.guest != null)
				{
					pawn.guest.SetGuestStatus(null);
				}
			}
			finally
			{
			}
		}

		public static bool IsBeingGenerated(Pawn pawn)
		{
			for (int i = 0; i < pawnsBeingGenerated.Count; i++)
			{
				if (pawnsBeingGenerated[i].Pawn == pawn)
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsValidCandidateToRedress(Pawn pawn, PawnGenerationRequest request)
		{
			if (pawn.def != request.KindDef.race)
			{
				return false;
			}
			if (!request.WorldPawnFactionDoesntMatter && pawn.Faction != request.Faction)
			{
				return false;
			}
			if (!request.AllowDead && (pawn.Dead || pawn.Destroyed))
			{
				return false;
			}
			if (!request.AllowDowned && pawn.Downed)
			{
				return false;
			}
			if (pawn.health.hediffSet.BleedRateTotal > 0.001f)
			{
				return false;
			}
			if (!request.CanGeneratePawnRelations && pawn.RaceProps.IsFlesh && pawn.relations.RelatedToAnyoneOrAnyoneRelatedToMe)
			{
				return false;
			}
			if (!request.AllowGay && pawn.RaceProps.Humanlike && pawn.story.traits.HasTrait(TraitDefOf.Gay))
			{
				return false;
			}
			if (request.ValidatorPreGear != null && !request.ValidatorPreGear(pawn))
			{
				return false;
			}
			if (request.ValidatorPostGear != null && !request.ValidatorPostGear(pawn))
			{
				return false;
			}
			if (request.FixedBiologicalAge.HasValue && pawn.ageTracker.AgeBiologicalYearsFloat != request.FixedBiologicalAge)
			{
				return false;
			}
			if (request.FixedChronologicalAge.HasValue && (float)pawn.ageTracker.AgeChronologicalYears != request.FixedChronologicalAge)
			{
				return false;
			}
			if (request.FixedGender.HasValue && pawn.gender != request.FixedGender)
			{
				return false;
			}
			if (request.FixedLastName != null && ((NameTriple)pawn.Name).Last != request.FixedLastName)
			{
				return false;
			}
			if (request.FixedMelanin.HasValue && pawn.story != null && pawn.story.melanin != request.FixedMelanin)
			{
				return false;
			}
			if (request.Context == PawnGenerationContext.PlayerStarter && Find.Scenario != null && !Find.Scenario.AllowPlayerStartingPawn(pawn, tryingToRedress: true, request))
			{
				return false;
			}
			if (request.MustBeCapableOfViolence)
			{
				if (pawn.story != null && pawn.story.WorkTagIsDisabled(WorkTags.Violent))
				{
					return false;
				}
				if (pawn.RaceProps.ToolUser && !pawn.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation))
				{
					return false;
				}
			}
			return true;
		}

		private static Pawn GenerateNewPawnInternal(ref PawnGenerationRequest request)
		{
			Pawn pawn = null;
			string error = null;
			bool ignoreScenarioRequirements = false;
			bool ignoreValidator = false;
			for (int i = 0; i < 120; i++)
			{
				if (i == 70)
				{
					Log.Error("Could not generate a pawn after " + 70 + " tries. Last error: " + error + " Ignoring scenario requirements.");
					ignoreScenarioRequirements = true;
				}
				if (i == 100)
				{
					Log.Error("Could not generate a pawn after " + 100 + " tries. Last error: " + error + " Ignoring validator.");
					ignoreValidator = true;
				}
				PawnGenerationRequest request2 = request;
				pawn = TryGenerateNewPawnInternal(ref request2, out error, ignoreScenarioRequirements, ignoreValidator);
				if (pawn != null)
				{
					request = request2;
					break;
				}
			}
			if (pawn == null)
			{
				Log.Error("Pawn generation error: " + error + " Too many tries (" + 120 + "), returning null. Generation request: " + request);
				return null;
			}
			return pawn;
		}

		[SyncMethod]
		private static Pawn TryGenerateNewPawnInternal(ref PawnGenerationRequest request, out string error, bool ignoreScenarioRequirements, bool ignoreValidator)
		{
			error = null;
			Pawn pawn = (Pawn)ThingMaker.MakeThing(request.KindDef.race);
			pawnsBeingGenerated.Add(new PawnGenerationStatus(pawn, null));
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			try
			{
				pawn.kindDef = request.KindDef;
				pawn.SetFactionDirect(request.Faction);
				PawnComponentsUtility.CreateInitialComponents(pawn);
				if (request.FixedGender.HasValue)
				{
					pawn.gender = request.FixedGender.Value;
				}
				else if (pawn.RaceProps.hasGenders)
				{
					if (Rand.Value < 0.5f)
					{
						pawn.gender = Gender.Male;
					}
					else
					{
						pawn.gender = Gender.Female;
					}
				}
				else
				{
					pawn.gender = Gender.None;
				}
				GenerateRandomAge(pawn, request);
				pawn.needs.SetInitialLevels();
				if (!request.Newborn && request.CanGeneratePawnRelations)
				{
					GeneratePawnRelations(pawn, ref request);
				}
				if (pawn.RaceProps.Humanlike)
				{
					Faction faction;
					FactionDef factionType = (request.Faction != null) ? request.Faction.def : ((!Find.FactionManager.TryGetRandomNonColonyHumanlikeFaction(out faction, tryMedievalOrBetter: false, allowDefeated: true)) ? Faction.OfAncients.def : faction.def);
					pawn.story.melanin = ((!request.FixedMelanin.HasValue) ? PawnSkinColors.RandomMelanin(request.Faction) : request.FixedMelanin.Value);
					pawn.story.crownType = ((Rand.Value < 0.5f) ? CrownType.Average : CrownType.Narrow);
					pawn.story.hairColor = PawnHairColors.RandomHairColor(pawn.story.SkinColor, pawn.ageTracker.AgeBiologicalYears);
					PawnBioAndNameGenerator.GiveAppropriateBioAndNameTo(pawn, request.FixedLastName, factionType);
					pawn.story.hairDef = PawnHairChooser.RandomHairDefFor(pawn, factionType);
					GenerateTraits(pawn, request);
					GenerateBodyType(pawn);
					GenerateSkills(pawn);
				}
				if (pawn.RaceProps.Animal && request.Faction != null && request.Faction.IsPlayer)
				{
					pawn.training.SetWantedRecursive(TrainableDefOf.Tameness, checkOn: true);
					pawn.training.Train(TrainableDefOf.Tameness, null, complete: true);
				}
				GenerateInitialHediffs(pawn, request);
				if (pawn.workSettings != null && request.Faction != null && request.Faction.IsPlayer)
				{
					pawn.workSettings.EnableAndInitialize();
				}
				if (request.Faction != null && pawn.RaceProps.Animal)
				{
					pawn.GenerateNecessaryName();
				}
				if (Find.Scenario != null)
				{
					Find.Scenario.Notify_NewPawnGenerating(pawn, request.Context);
				}
				if (!request.AllowDead && (pawn.Dead || pawn.Destroyed))
				{
					DiscardGeneratedPawn(pawn);
					error = "Generated dead pawn.";
					return null;
				}
				if (!request.AllowDowned && pawn.Downed)
				{
					DiscardGeneratedPawn(pawn);
					error = "Generated downed pawn.";
					return null;
				}
				if (request.MustBeCapableOfViolence && ((pawn.story != null && pawn.story.WorkTagIsDisabled(WorkTags.Violent)) || (pawn.RaceProps.ToolUser && !pawn.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation))))
				{
					DiscardGeneratedPawn(pawn);
					error = "Generated pawn incapable of violence.";
					return null;
				}
				if (!ignoreScenarioRequirements && request.Context == PawnGenerationContext.PlayerStarter && Find.Scenario != null && !Find.Scenario.AllowPlayerStartingPawn(pawn, tryingToRedress: false, request))
				{
					DiscardGeneratedPawn(pawn);
					error = "Generated pawn doesn't meet scenario requirements.";
					return null;
				}
				if (!ignoreValidator && request.ValidatorPreGear != null && !request.ValidatorPreGear(pawn))
				{
					DiscardGeneratedPawn(pawn);
					error = "Generated pawn didn't pass validator check (pre-gear).";
					return null;
				}
				if (!request.Newborn)
				{
					GenerateGearFor(pawn, request);
				}
				if (!ignoreValidator && request.ValidatorPostGear != null && !request.ValidatorPostGear(pawn))
				{
					DiscardGeneratedPawn(pawn);
					error = "Generated pawn didn't pass validator check (post-gear).";
					return null;
				}
				for (int i = 0; i < pawnsBeingGenerated.Count - 1; i++)
				{
					if (pawnsBeingGenerated[i].PawnsGeneratedInTheMeantime == null)
					{
						pawnsBeingGenerated[i] = new PawnGenerationStatus(pawnsBeingGenerated[i].Pawn, new List<Pawn>());
					}
					pawnsBeingGenerated[i].PawnsGeneratedInTheMeantime.Add(pawn);
				}
				return pawn;
			}
			finally
			{
				pawnsBeingGenerated.RemoveLast();
			}
		}

		private static void DiscardGeneratedPawn(Pawn pawn)
		{
			if (Find.WorldPawns.Contains(pawn))
			{
				Find.WorldPawns.RemovePawn(pawn);
			}
			Find.WorldPawns.PassToWorld(pawn, PawnDiscardDecideMode.Discard);
			List<Pawn> pawnsGeneratedInTheMeantime = pawnsBeingGenerated.Last().PawnsGeneratedInTheMeantime;
			if (pawnsGeneratedInTheMeantime != null)
			{
				for (int i = 0; i < pawnsGeneratedInTheMeantime.Count; i++)
				{
					Pawn pawn2 = pawnsGeneratedInTheMeantime[i];
					if (Find.WorldPawns.Contains(pawn2))
					{
						Find.WorldPawns.RemovePawn(pawn2);
					}
					Find.WorldPawns.PassToWorld(pawn2, PawnDiscardDecideMode.Discard);
					for (int j = 0; j < pawnsBeingGenerated.Count; j++)
					{
						pawnsBeingGenerated[j].PawnsGeneratedInTheMeantime.Remove(pawn2);
					}
				}
			}
		}

		private static IEnumerable<Pawn> GetValidCandidatesToRedress(PawnGenerationRequest request)
		{
			IEnumerable<Pawn> enumerable = Find.WorldPawns.GetPawnsBySituation(WorldPawnSituation.Free);
			if (request.KindDef.factionLeader)
			{
				enumerable = enumerable.Concat(Find.WorldPawns.GetPawnsBySituation(WorldPawnSituation.FactionLeader));
			}
			return from x in enumerable
			where IsValidCandidateToRedress(x, request)
			select x;
		}

		private static float ChanceToRedressAnyWorldPawn(PawnGenerationRequest request)
		{
			int pawnsBySituationCount = Find.WorldPawns.GetPawnsBySituationCount(WorldPawnSituation.Free);
			float num = Mathf.Min(0.02f + 0.01f * ((float)pawnsBySituationCount / 10f), 0.8f);
			if (request.MinChanceToRedressWorldPawn.HasValue)
			{
				num = Mathf.Max(num, request.MinChanceToRedressWorldPawn.Value);
			}
			return num;
		}

		private static float WorldPawnSelectionWeight(Pawn p)
		{
			if (p.RaceProps.IsFlesh && !p.relations.everSeenByPlayer && p.relations.RelatedToAnyoneOrAnyoneRelatedToMe)
			{
				return 0.1f;
			}
			return 1f;
		}

		private static void GenerateGearFor(Pawn pawn, PawnGenerationRequest request)
		{
			PawnApparelGenerator.GenerateStartingApparelFor(pawn, request);
			PawnWeaponGenerator.TryGenerateWeaponFor(pawn);
			PawnInventoryGenerator.GenerateInventoryFor(pawn, request);
		}

		[SyncMethod]
		private static void GenerateInitialHediffs(Pawn pawn, PawnGenerationRequest request)
		{
			int num = 0;
			while (true)
			{
				AgeInjuryUtility.GenerateRandomOldAgeInjuries(pawn, !request.AllowDead);
				PawnTechHediffsGenerator.GenerateTechHediffsFor(pawn);
				PawnAddictionHediffsGenerator.GenerateAddictionsAndTolerancesFor(pawn);
				if ((request.AllowDead && pawn.Dead) || request.AllowDowned || !pawn.Downed)
				{
					break;
				}
				pawn.health.Reset();
				num++;
				if (num > 80)
				{
					Log.Warning("Could not generate old age injuries for " + pawn.ThingID + " of age " + pawn.ageTracker.AgeBiologicalYears + " that allow pawn to move after " + 80 + " tries. request=" + request);
					break;
				}
			}
			if (!pawn.Dead && (request.Faction == null || !request.Faction.IsPlayer))
			{
				int num2 = 0;
				while (true)
				{
					if (!pawn.health.HasHediffsNeedingTend())
					{
						return;
					}
					num2++;
					if (num2 > 10000)
					{
						break;
					}
					TendUtility.DoTend(null, pawn, null);
				}
				Log.Error("Too many iterations.");
			}
		}

		[SyncMethod]
		private static void GenerateRandomAge(Pawn pawn, PawnGenerationRequest request)
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			if (request.FixedBiologicalAge.HasValue && request.FixedChronologicalAge.HasValue)
			{
				float? fixedBiologicalAge = request.FixedBiologicalAge;
				bool hasValue = fixedBiologicalAge.HasValue;
				float? fixedChronologicalAge = request.FixedChronologicalAge;
				if ((hasValue & fixedChronologicalAge.HasValue) && fixedBiologicalAge.GetValueOrDefault() > fixedChronologicalAge.GetValueOrDefault())
				{
					Log.Warning("Tried to generate age for pawn " + pawn + ", but pawn generation request demands biological age (" + request.FixedBiologicalAge + ") to be greater than chronological age (" + request.FixedChronologicalAge + ").");
				}
			}
			if (request.Newborn)
			{
				pawn.ageTracker.AgeBiologicalTicks = 0L;
			}
			else if (request.FixedBiologicalAge.HasValue)
			{
				pawn.ageTracker.AgeBiologicalTicks = (long)(request.FixedBiologicalAge.Value * 3600000f);
			}
			else
			{
				float num = 0f;
				int num2 = 0;
				do
				{
					num = ((pawn.RaceProps.ageGenerationCurve != null) ? ((float)Mathf.RoundToInt(Rand.ByCurve(pawn.RaceProps.ageGenerationCurve))) : ((!pawn.RaceProps.IsMechanoid) ? (Rand.ByCurve(DefaultAgeGenerationCurve) * pawn.RaceProps.lifeExpectancy) : Rand.Range(0f, 2500f)));
					num2++;
					if (num2 > 300)
					{
						Log.Error("Tried 300 times to generate age for " + pawn);
						break;
					}
				}
				while (num > (float)pawn.kindDef.maxGenerationAge || num < (float)pawn.kindDef.minGenerationAge);
				pawn.ageTracker.AgeBiologicalTicks = (long)(num * 3600000f) + Rand.Range(0, 3600000);
			}
			if (request.Newborn)
			{
				pawn.ageTracker.AgeChronologicalTicks = 0L;
			}
			else if (request.FixedChronologicalAge.HasValue)
			{
				pawn.ageTracker.AgeChronologicalTicks = (long)(request.FixedChronologicalAge.Value * 3600000f);
			}
			else
			{
				int num3;
				if (request.CertainlyBeenInCryptosleep || Rand.Value < pawn.kindDef.backstoryCryptosleepCommonality)
				{
					float value = Rand.Value;
					if (value < 0.7f)
					{
						num3 = Rand.Range(0, 100);
					}
					else if (value < 0.95f)
					{
						num3 = Rand.Range(100, 1000);
					}
					else
					{
						int max = GenDate.Year(GenTicks.TicksAbs, 0f) - 2026 - pawn.ageTracker.AgeBiologicalYears;
						num3 = Rand.Range(1000, max);
					}
				}
				else
				{
					num3 = 0;
				}
				int ticksAbs = GenTicks.TicksAbs;
				long num4 = ticksAbs - pawn.ageTracker.AgeBiologicalTicks;
				num4 -= (long)num3 * 3600000L;
				pawn.ageTracker.BirthAbsTicks = num4;
			}
			if (pawn.ageTracker.AgeBiologicalTicks > pawn.ageTracker.AgeChronologicalTicks)
			{
				pawn.ageTracker.AgeChronologicalTicks = pawn.ageTracker.AgeBiologicalTicks;
			}
		}

		[SyncMethod]
		public static int RandomTraitDegree(TraitDef traitDef)
		{
			if (traitDef.degreeDatas.Count == 1)
			{
				return traitDef.degreeDatas[0].degree;
			}
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			return traitDef.degreeDatas.RandomElementByWeight((TraitDegreeData dd) => dd.commonality).degree;
		}

		[SyncMethod]
		private static void GenerateTraits(Pawn pawn, PawnGenerationRequest request)
		{
			if (pawn.story != null)
			{
				if (pawn.story.childhood.forcedTraits != null)
				{
					List<TraitEntry> forcedTraits = pawn.story.childhood.forcedTraits;
					for (int i = 0; i < forcedTraits.Count; i++)
					{
						TraitEntry traitEntry = forcedTraits[i];
						if (traitEntry.def == null)
						{
							Log.Error("Null forced trait def on " + pawn.story.childhood);
						}
						else if (!pawn.story.traits.HasTrait(traitEntry.def))
						{
							pawn.story.traits.GainTrait(new Trait(traitEntry.def, traitEntry.degree));
						}
					}
				}
				if (pawn.story.adulthood != null && pawn.story.adulthood.forcedTraits != null)
				{
					List<TraitEntry> forcedTraits2 = pawn.story.adulthood.forcedTraits;
					for (int j = 0; j < forcedTraits2.Count; j++)
					{
						TraitEntry traitEntry2 = forcedTraits2[j];
						if (traitEntry2.def == null)
						{
							Log.Error("Null forced trait def on " + pawn.story.adulthood);
						}
						else if (!pawn.story.traits.HasTrait(traitEntry2.def))
						{
							pawn.story.traits.GainTrait(new Trait(traitEntry2.def, traitEntry2.degree));
						}
					}
				}
				//Rand.PopState();
				//Rand.PushState(RJW_Multiplayer.PredictableSeed());
				int num = Rand.RangeInclusive(2, 3);
				if (request.AllowGay && (LovePartnerRelationUtility.HasAnyLovePartnerOfTheSameGender(pawn) || LovePartnerRelationUtility.HasAnyExLovePartnerOfTheSameGender(pawn)))
				{
					Trait trait = new Trait(TraitDefOf.Gay, RandomTraitDegree(TraitDefOf.Gay));
					pawn.story.traits.GainTrait(trait);
				}
				while (pawn.story.traits.allTraits.Count < num)
				{
					TraitDef newTraitDef = DefDatabase<TraitDef>.AllDefsListForReading.RandomElementByWeight((TraitDef tr) => tr.GetGenderSpecificCommonality(pawn.gender));
					if (!pawn.story.traits.HasTrait(newTraitDef) && (newTraitDef != TraitDefOf.Gay || (request.AllowGay && !LovePartnerRelationUtility.HasAnyLovePartnerOfTheOppositeGender(pawn) && !LovePartnerRelationUtility.HasAnyExLovePartnerOfTheOppositeGender(pawn))) && (request.Faction == null || Faction.OfPlayerSilentFail == null || !request.Faction.HostileTo(Faction.OfPlayer) || newTraitDef.allowOnHostileSpawn) && !pawn.story.traits.allTraits.Any((Trait tr) => newTraitDef.ConflictsWith(tr)) && (newTraitDef.conflictingTraits == null || !newTraitDef.conflictingTraits.Any((TraitDef tr) => pawn.story.traits.HasTrait(tr))) && (newTraitDef.requiredWorkTypes == null || !pawn.story.OneOfWorkTypesIsDisabled(newTraitDef.requiredWorkTypes)) && !pawn.story.WorkTagIsDisabled(newTraitDef.requiredWorkTags))
					{
						int degree = RandomTraitDegree(newTraitDef);
						if (!pawn.story.childhood.DisallowsTrait(newTraitDef, degree) && (pawn.story.adulthood == null || !pawn.story.adulthood.DisallowsTrait(newTraitDef, degree)))
						{
							Trait trait2 = new Trait(newTraitDef, degree);
							if (pawn.mindState != null && pawn.mindState.mentalBreaker != null)
							{
								float breakThresholdExtreme = pawn.mindState.mentalBreaker.BreakThresholdExtreme;
								breakThresholdExtreme += trait2.OffsetOfStat(StatDefOf.MentalBreakThreshold);
								breakThresholdExtreme *= trait2.MultiplierOfStat(StatDefOf.MentalBreakThreshold);
								if (breakThresholdExtreme > 0.4f)
								{
									continue;
								}
							}
							pawn.story.traits.GainTrait(trait2);
						}
					}
				}
			}
		}

		[SyncMethod]
		private static void GenerateBodyType(Pawn pawn)
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			if (pawn.story.adulthood != null)
			{
				pawn.story.bodyType = pawn.story.adulthood.BodyTypeFor(pawn.gender);
			}
			else if (Rand.Value < 0.5f)
			{
				pawn.story.bodyType = BodyTypeDefOf.Thin;
			}
			else
			{
				pawn.story.bodyType = ((pawn.gender != Gender.Female) ? BodyTypeDefOf.Male : BodyTypeDefOf.Female);
			}
		}

		[SyncMethod]
		private static void GenerateSkills(Pawn pawn)
		{
			List<SkillDef> allDefsListForReading = DefDatabase<SkillDef>.AllDefsListForReading;
			for (int i = 0; i < allDefsListForReading.Count; i++)
			{
				SkillDef skillDef = allDefsListForReading[i];
				int num = FinalLevelOfSkill(pawn, skillDef);
				SkillRecord skill = pawn.skills.GetSkill(skillDef);
				skill.Level = num;
				if (!skill.TotallyDisabled)
				{
					//Rand.PopState();
					//Rand.PushState(RJW_Multiplayer.PredictableSeed());
					float num2 = (float)num * 0.11f;
					float value = Rand.Value;
					if (value < num2)
					{
						if (value < num2 * 0.2f)
						{
							skill.passion = Passion.Major;
						}
						else
						{
							skill.passion = Passion.Minor;
						}
					}
					skill.xpSinceLastLevel = Rand.Range(skill.XpRequiredForLevelUp * 0.1f, skill.XpRequiredForLevelUp * 0.9f);
				}
			}
		}

		[SyncMethod]
		private static int FinalLevelOfSkill(Pawn pawn, SkillDef sk)
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float num = (!sk.usuallyDefinedInBackstories) ? Rand.ByCurve(LevelRandomCurve) : ((float)Rand.RangeInclusive(0, 4));
			foreach (Backstory item in from bs in pawn.story.AllBackstories
			where bs != null
			select bs)
			{
				foreach (KeyValuePair<SkillDef, int> item2 in item.skillGainsResolved)
				{
					if (item2.Key == sk)
					{
						num += (float)item2.Value * Rand.Range(1f, 1.4f);
					}
				}
			}
			for (int i = 0; i < pawn.story.traits.allTraits.Count; i++)
			{
				int value = 0;
				if (pawn.story.traits.allTraits[i].CurrentData.skillGains.TryGetValue(sk, out value))
				{
					num += (float)value;
				}
			}
			float num2 = Rand.Range(1f, AgeSkillMaxFactorCurve.Evaluate((float)pawn.ageTracker.AgeBiologicalYears));
			num *= num2;
			num = LevelFinalAdjustmentCurve.Evaluate(num);
			return Mathf.Clamp(Mathf.RoundToInt(num), 0, 20);
		}

		public static void PostProcessGeneratedGear(Thing gear, Pawn pawn)
		{
			gear.TryGetComp<CompQuality>()?.SetQuality(QualityUtility.GenerateQualityGeneratingPawn(pawn.kindDef), ArtGenerationContext.Outsider);
			if (gear.def.useHitPoints)
			{
				float randomInRange = pawn.kindDef.gearHealthRange.RandomInRange;
				if (randomInRange < 1f)
				{
					int b = Mathf.RoundToInt(randomInRange * (float)gear.MaxHitPoints);
					b = (gear.HitPoints = Mathf.Max(1, b));
				}
			}
		}

		[SyncMethod]
		private static void GeneratePawnRelations(Pawn pawn, ref PawnGenerationRequest request)
		{
			if (pawn.RaceProps.Humanlike)
			{
				//Rand.PopState();
				//Rand.PushState(RJW_Multiplayer.PredictableSeed());
				Pawn[] array = (from x in PawnsFinder.AllMapsWorldAndTemporary_AliveOrDead
				where x.def == pawn.def
				select x).ToArray();
				if (array.Length != 0)
				{
					int num = 0;
					Pawn[] array2 = array;
					foreach (Pawn pawn2 in array2)
					{
						if (pawn2.Discarded)
						{
							Log.Warning("Warning during generating pawn relations for " + pawn + ": Pawn " + pawn2 + " is discarded, yet he was yielded by PawnUtility. Discarding a pawn means that he is no longer managed by anything.");
						}
						else if (pawn2.Faction != null && pawn2.Faction.IsPlayer)
						{
							num++;
						}
					}
					float num2 = 45f;
					num2 += (float)num * 2.7f;
					PawnGenerationRequest localReq = request;
					Pair<Pawn, PawnRelationDef> pair = GenerateSamples(array, relationsGeneratableBlood, 40).RandomElementByWeightWithDefault((Pair<Pawn, PawnRelationDef> x) => x.Second.generationChanceFactor * x.Second.Worker.GenerationChance(pawn, x.First, localReq), num2 * 40f / (float)(array.Length * relationsGeneratableBlood.Length));
					if (pair.First != null)
					{
						pair.Second.Worker.CreateRelation(pawn, pair.First, ref request);
					}
					Pair<Pawn, PawnRelationDef> pair2 = GenerateSamples(array, relationsGeneratableNonblood, 40).RandomElementByWeightWithDefault((Pair<Pawn, PawnRelationDef> x) => x.Second.generationChanceFactor * x.Second.Worker.GenerationChance(pawn, x.First, localReq), num2 * 40f / (float)(array.Length * relationsGeneratableNonblood.Length));
					if (pair2.First != null)
					{
						pair2.Second.Worker.CreateRelation(pawn, pair2.First, ref request);
					}
				}
			}
		}

		[SyncMethod]
		private static Pair<Pawn, PawnRelationDef>[] GenerateSamples(Pawn[] pawns, PawnRelationDef[] relations, int count)
		{
			Pair<Pawn, PawnRelationDef>[] array = new Pair<Pawn, PawnRelationDef>[count];
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			for (int i = 0; i < count; i++)
			{
				array[i] = new Pair<Pawn, PawnRelationDef>(pawns[Rand.Range(0, pawns.Length)], relations[Rand.Range(0, relations.Length)]);
			}
			return array;
		}

		[DebugOutput]
		[Category("Performance")]
		public static void PawnGenerationHistogram()
		{
			DebugHistogram debugHistogram = new DebugHistogram((from x in Enumerable.Range(1, 20)
			select (float)x * 10f).ToArray());
			for (int i = 0; i < 100; i++)
			{
				long timestamp = Stopwatch.GetTimestamp();
				Pawn pawn = GeneratePawn(new PawnGenerationRequest(PawnKindDefOf.Colonist, null, PawnGenerationContext.NonPlayer, -1, forceGenerateNewPawn: true));
				debugHistogram.Add((float)((Stopwatch.GetTimestamp() - timestamp) * 1000 / Stopwatch.Frequency));
				pawn.Destroy();
			}
			debugHistogram.Display();
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Hugslib Utility Data object storing the RJW info on pawn
	/// also implements extensions to Pawn method
	/// is used as a static field in PawnData
	/// </summary>
	public class DataStore : UtilityWorldObject
	{
		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<int, PawnData>(ref PawnData, "Data", LookMode.Value, LookMode.Deep);
			Scribe_Collections.Look<int, PartsData>(ref PartsData, "Data", LookMode.Value, LookMode.Deep);
			if (Scribe.mode == LoadSaveMode.LoadingVars)
			{
				if (PawnData == null) PawnData = new Dictionary<int, PawnData>();
				if (PartsData == null) PartsData = new Dictionary<int, PartsData>();
			}
		}

		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);
		}

		public PartsData GetPartsData(Thing thing)
		{
			PartsData res;
			//--Log.Message("Getting data for " + thing);
			//--Log.Message("Pawn " + thing + " id " + thing.thingIDNumber);
			//--Log.Message("PartsData isn't null " + !(PartsData == null));
			var filled = PartsData.TryGetValue(thing.thingIDNumber, out res);
			//--Log.Message("Output is not null" + PartsData.TryGetValue(thing.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 " + thing);
					PartsData.Remove(thing.thingIDNumber);
				}
				//--Log.Message("PartsData missing, creating for " + thing);
				res = new PartsData(thing);
				PartsData.Add(thing.thingIDNumber, res);
			}
			//--Log.Message("Finishing");
			return res;
		}

		void SetPartsData(Thing thing, PartsData data)
		{
			PartsData.Add(thing.thingIDNumber, data);
		}
	}
}

repo_name: randomtyping/rjw

license: mit


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.Collections.Generic;
using RimWorld;
using Verse;
using System.Linq;

//This one is helper lib for handling all the trans surgery.
namespace rjw
{
	public static class GenderHelper
	{
		public enum Sex { male, female, trap, futa, none }//there is Verse,Gender but it is shit
		//These would probably be better packed in some enumerable structure, so that functions below weren't if-trees, but I don't know C#, sry.
		public static HediffDef was_boy = HediffDef.Named("hediff_was_boy");
		public static HediffDef was_girl = HediffDef.Named("hediff_was_girl");
		public static HediffDef was_futa = HediffDef.Named("hediff_was_futa");
		public static HediffDef was_trap = HediffDef.Named("hediff_was_trap");

		static List<HediffDef> old_sex_list = new List<HediffDef> { was_boy, was_girl, was_futa, was_trap };
		static Dictionary<Sex, HediffDef> sex_to_old_sex = new Dictionary<Sex, HediffDef>() {
			{Sex.male, was_boy },{Sex.female, was_girl},{Sex.trap, was_trap},{Sex.futa, was_futa}
		};
		static Dictionary<HediffDef,Sex> old_sex_to_sex = sex_to_old_sex.ToDictionary(x => x.Value, x => x.Key);

		public static HediffDef m2t = HediffDef.Named("hediff_male2trap");
		public static HediffDef m2f = HediffDef.Named("hediff_male2female");
		public static HediffDef m2h = HediffDef.Named("hediff_male2futa");

		public static HediffDef f2t = HediffDef.Named("hediff_female2trap");
		public static HediffDef f2m = HediffDef.Named("hediff_female2male");
		public static HediffDef f2h = HediffDef.Named("hediff_female2futa");

		public static HediffDef h2t = HediffDef.Named("hediff_futa2trap");
		public static HediffDef h2m = HediffDef.Named("hediff_futa2male");
		public static HediffDef h2f = HediffDef.Named("hediff_futa2female");

		public static HediffDef t2h = HediffDef.Named("hediff_trap2futa");
		public static HediffDef t2m = HediffDef.Named("hediff_trap2male");
		public static HediffDef t2f = HediffDef.Named("hediff_trap2female");

		static List<HediffDef> SexChangeThoughts = new List<HediffDef> { m2t, m2f, m2h, f2t, f2m, f2h, h2t, h2m, h2f };

		private static readonly SimpleCurve rigidity_from_age = new SimpleCurve//relative to max age
		{
			new CurvePoint(0f, 0.1f),
			new CurvePoint(0.2f, 0.1f),
			new CurvePoint(0.5f, 1f),
			new CurvePoint(10f, 1f)
		};

		public static Sex GetSex(Pawn pawn)
		{
			bool has_vagina = Genital_Helper.has_vagina(pawn);
			bool has_penis = Genital_Helper.has_penis(pawn);
			bool has_penis_infertile = Genital_Helper.has_penis_infertile(pawn);
			bool has_breasts = Genital_Helper.has_breasts(pawn);
			//BodyType? bt = pawn.story?.bodyType;
			//if (bt != null)
			//	bt = BodyType.Undefined;

			Sex res;
			if (has_vagina && !has_penis && !has_penis_infertile)
				res = Sex.female;
			else if (has_vagina && (has_penis || has_penis_infertile))
				res = Sex.futa;
			else if ((has_penis || has_penis_infertile) && has_breasts)
				res = Sex.trap;
			else if (has_penis || has_penis_infertile) //probably should change this later
				res = Sex.male;
			else if (pawn.gender == Gender.Male)
				res = Sex.male;
			else if (pawn.gender == Gender.Female)
				res = Sex.female;
			else
				res = Sex.none;
			return res;
		}
		/*
		public static HediffDef GetReactionHediff(Sex before, Sex after)
		{
			if (before == after)
				return null;

			if (before == Sex.male)
				return (after == Sex.female) ? m2f : m2t;
			else if (before == Sex.female)
			{
				if (after == Sex.male)
					return f2m;
				else if (after == Sex.trap)
					return f2t;
				else if (after == Sex.futa)
					return f2h;
				else
					return null;
			}
			else if (before == Sex.futa && (after == Sex.female || after == Sex.none))
				return h2f;
			else//trap to anything, futa to trap; probably won't even be reached ever
				return null;
		}
		*/

		//TODO: fix reactions
		public static HediffDef GetReactionHediff(Sex before, Sex after)
		{
			if (before == after)
				return null;
			else if (before == Sex.male)
			{
				if (after == Sex.female)
					return m2f;
				else if (after == Sex.trap)
					return m2t;
				else if (after == Sex.futa)
					return m2h;
				else
					return null;
			}
			else if (before == Sex.female)
			{
				if (after == Sex.male)
					return f2m;
				else if (after == Sex.trap)
					return f2t;
				else if (after == Sex.futa)
					return f2h;
				else
					return null;
			}
			else if (before == Sex.futa)
			{
				if (after == Sex.male)
					return h2m;
				else if (after == Sex.female)
					return h2f;
				else if (after == Sex.trap)
					return h2t;
				else
					return null;
			}
			else if (before == Sex.trap)
			{
				if (after == Sex.male)
					return t2m;
				else if (after == Sex.female)
					return t2f;
				else if (after == Sex.futa)
					return t2h;
				else
					return null;
			}
			else//unicorns?
				return null;
		}

		public static bool WasThisBefore(Pawn pawn, Sex after)
		{
			Hediff was = null;

			switch (after)
			{
				case Sex.male:
					was = pawn.health.hediffSet.GetFirstHediffOfDef(was_boy);
					break;
				case Sex.female:
					was = pawn.health.hediffSet.GetFirstHediffOfDef(was_girl);
					break;
				case Sex.trap:
					was = pawn.health.hediffSet.GetFirstHediffOfDef(was_trap);
					break;
				case Sex.futa:
					was = pawn.health.hediffSet.GetFirstHediffOfDef(was_futa);
					break;
			}
			return (was != null) ? true : false;
		}

		//Get one of the sexes that were on this pawn before
		public static Sex GetOriginalSex(Pawn pawn)
		{
			foreach (var os in old_sex_list)
			{
				if (pawn.health.hediffSet.GetFirstHediffOfDef(os) != null)
					return old_sex_to_sex[os];
			}
			return Sex.none;//it shouldnt reach here though
		}

		public static Hediff IsInDenial(Pawn pawn)
		{
			Hediff res = null;
			foreach (var h in SexChangeThoughts)
			{
				res = pawn.health.hediffSet.GetFirstHediffOfDef(h);
				if (res != null)
					break;
			}
			return res;
		}

		//roll how much gender fluid the pawn is.
		//In ideal world this would actually take into account from where to where transition is moving and so on.
		//Same applies to the thought hediffs themselves, but we get what we can get now
		static float RollSexChangeSeverity(Pawn pawn)
		{
			float res = 1;
			if (xxx.is_bisexual(pawn))
				res *= 0.5f;
			if (pawn.story != null && (pawn.story.bodyType == BodyTypeDefOf.Thin || pawn.story.bodyType == BodyTypeDefOf.Fat))
				res *= 0.8f;
			if (!pawn.ageTracker.CurLifeStage.reproductive)
				res *= 0.2f;
			else
				res *= rigidity_from_age.Evaluate(SexUtility.ScaleToHumanAge(pawn));

			return res;
		}

		//Quick hack to check if hediff is adding happiness
		static bool is_happy(this Hediff thought)
		{
			return thought.CurStageIndex == 0;
		}
		static void make_happy(this Hediff thought)
		{
			thought.Severity = 0.24f;//this is currently max severity for hediff, that is associated with positive mood
		}
		static void mix_thoughts(this Hediff newer, Hediff older)
		{
			newer.Severity = (newer.Severity + older.Severity) / 2f  ;
		}

		static void GiveThought(Pawn pawn, HediffDef thought, bool happy = false, Hediff old_thought=null)
		{
			pawn.health.AddHediff(thought);
			var new_thought = pawn.health.hediffSet.GetFirstHediffOfDef(thought);
			if (happy)
			{
				new_thought.make_happy();
				return;
			}
			new_thought.Severity = RollSexChangeSeverity(pawn);
			if (old_thought!=null)
			{
				new_thought.Severity = (new_thought.Severity + old_thought.Severity) / 2f;
			}
		}

		public static void ChangeSex(Pawn pawn, Sex oldsex, Sex newsex)
		{
			if (oldsex == newsex)
				return;

			// Sexualize droid after installation of genitals.
			if (oldsex == Sex.none
				&& (CompRJW.Comp(pawn).orientation == Orientation.Asexual || CompRJW.Comp(pawn).orientation == Orientation.None)
				&& pawn.kindDef.race.defName.ToLower().Contains("droid"))
			{
				CompRJW.Comp(pawn).Sexualize(pawn, true);
			}

			var old_thought = IsInDenial(pawn);
			var react = GetReactionHediff(oldsex, newsex);

			if (old_thought==null || old_thought.is_happy())//pawn either liked it or got used already
			{
				//Log.Message("ChangeSex 1 old_thought" + old_thought);
				//Log.Message("ChangeSex 1 react" + react);
				if (react!=null)
					GiveThought(pawn, react, pawn.IsDesignatedHero());//give unhappy, if not hero
				if (old_thought!=null)
					pawn.health.RemoveHediff(old_thought);

				//add tracking hediff
				pawn.health.AddHediff(sex_to_old_sex[oldsex]);
			}
			else//pawn was unhappy
			{
				if (WasThisBefore(pawn, newsex))//pawn is happy to be previous self
				{
					GiveThought(pawn, react, happy:true);
					pawn.health.RemoveHediff(old_thought);
				}
				else//pawn is still unhappy mix the unhappiness from two
				{
					react = GetReactionHediff(GetOriginalSex(pawn), newsex);//check reaction from original sex
					if (react!=null)
					{
						GiveThought(pawn, react, old_thought: old_thought);
						pawn.health.RemoveHediff(old_thought);
					}
					//else pawn keeps old unhappy thought
				}
			}
		//update ingame genders
			if (newsex == Sex.none)//this means that it was none in the vanilla. We do not touch that
				return;
			else if (newsex == Sex.male || newsex == Sex.trap)
				pawn.gender = Gender.Male;
			else
				pawn.gender = Gender.Female;
		}
	}
}