code:
using HarmonyLib;
using RimWorld;
using rjw;
using System;
using Verse;

namespace LicentiaLabs
{
    public class CumflationHelper
	{
		public static void Cumflation(SexProps props)
		{
			if (!LicentiaHelper.GetPenetrator(props, out Pawn inflator, out Pawn inflated))
			{
				// If there is not penetrator involved, then there will be no inflation.
				return;
			}

			if (props.pawn != inflator)
			{
				// RJW calls SatisfyPersonal 2 times with props.pawn pointing to a different participant.
				return;
			}

			if (xxx.is_animal(inflated))
			{
				// Animals cannot get inflated.
				return;
			}

			Hediff cumflationHediff;
			switch (props.sexType)
			{
				case xxx.rjwSextype.Oral:
				case xxx.rjwSextype.Fellatio:
					cumflationHediff = GetCumflationHediff(inflated, Licentia.HediffDefs.Cumstuffed, "Stomach");
					break;
				case xxx.rjwSextype.Vaginal:
				case xxx.rjwSextype.Anal:
				case xxx.rjwSextype.DoublePenetration:
					cumflationHediff = GetCumflationHediff(inflated, Licentia.HediffDefs.Cumflation);
					break;
				default:
					return;
			}

			var holeSize = inflated.BodySize;
			float cumAmount = CalculateCumAmount(inflator);
			var severity = AdjustCumAmount(cumAmount, holeSize);

			float consumed = AdjustCumAmount(Math.Max(1f - cumflationHediff.Severity, severity), holeSize, true);
			float overflow = cumflationHediff.Severity + severity - 1f;
			cumflationHediff.Severity += severity;
			switch (props.sexType)
			{
				case xxx.rjwSextype.Oral:
				case xxx.rjwSextype.Fellatio:
					TransferNutrition(inflator, inflated, consumed);
					inflated.records.AddTo(Licentia.RecordDefs.AmountOfCumstuffed, consumed);
					break;
				case xxx.rjwSextype.Vaginal:
				case xxx.rjwSextype.Anal:
				case xxx.rjwSextype.DoublePenetration:
					cumflationHediff = GetCumflationHediff(inflated, Licentia.HediffDefs.Cumflation);
					inflated.records.AddTo(Licentia.RecordDefs.AmountOfCumflation, consumed);
					break;
				default:
					return;
			}

			if (cumflationHediff.Severity > 0.6)
			{
				GiveCumflationThoughts(inflated);
			}

			if (overflow > 0f)
			{
				GiveOverinflationThoughts(inflated, props.sexType);
				var secondaryOverflow = 0f;
				var tertiaryOverflow = 0f;
				switch (props.sexType)
				{
					case xxx.rjwSextype.Oral:
					case xxx.rjwSextype.Fellatio:
						secondaryOverflow = overflow;
						break;
					case xxx.rjwSextype.DoublePenetration:
						overflow /= 2f;
						tertiaryOverflow = overflow;
						goto case xxx.rjwSextype.Anal;
					case xxx.rjwSextype.Anal:
						var overflowHediff = GetCumflationHediff(inflated, Licentia.HediffDefs.Cumstuffed, "Stomach");
						float secondaryConsumed = AdjustCumAmount(Math.Max(1f - overflowHediff.Severity, severity), holeSize, true);
						TransferNutrition(inflator, inflated, secondaryConsumed);
						inflated.records.AddTo(Licentia.RecordDefs.AmountOfCumstuffed, secondaryConsumed);
						secondaryOverflow = overflowHediff.Severity + overflow - 1f;
						overflowHediff.Severity += overflow;
						break;
					case xxx.rjwSextype.Vaginal:
						tertiaryOverflow = overflow;
						break;
					default:
						break;
				}

				if (secondaryOverflow > 0f)
                {
					var vomitJob = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("VomitCum"), inflated);
					((JobDriver_VomitCum)vomitJob.GetCachedDriver(inflated)).sourceName = inflator.LabelIndefinite();
					inflated.jobs.jobQueue.EnqueueFirst(vomitJob);
					FilthMaker.TryMakeFilth(inflated.PositionHeld, inflated.MapHeld, Licentia.ThingDefs.FilthCum, inflator.LabelIndefinite(), (int)Math.Max(secondaryOverflow / 5, 10.0f));
				}
				if (tertiaryOverflow > 0f)
				{
					FilthMaker.TryMakeFilth(inflated.PositionHeld, inflated.MapHeld, Licentia.ThingDefs.FilthCum, inflator.LabelIndefinite(), (int)Math.Max(tertiaryOverflow / 5, 10.0f));
				}
			}
		}

		public static Hediff GetCumflationHediff(Pawn inflated, HediffDef hediffDef, string bodyPartRecordName = null)
		{
			BodyPartRecord bodyPartRecord = null;
			if (bodyPartRecordName != null)
            {
				bodyPartRecord = inflated.RaceProps.body.AllParts.Find(bpr => bpr.def.defName.Contains(bodyPartRecordName) || bpr.def.defName.Contains(bodyPartRecordName.ToLower()));
				if (bodyPartRecord == null)
                {
					return null;
				}
			}

			Hediff cumflationHediff = inflated.health.hediffSet.GetFirstHediffOfDef(hediffDef);
			if (cumflationHediff == null)
			{
				cumflationHediff = HediffMaker.MakeHediff(hediffDef, inflated, bodyPartRecord);
				cumflationHediff.Severity = 0;
				inflated.health.AddHediff(cumflationHediff, bodyPartRecord);
			}
			return cumflationHediff;
		}

		public static float CalculateCumAmount(Pawn giver)
		{
			float cumAmount = 0f;
			var hediffs = LicentiaHelper.TryGetPawnPenisHediff(giver);
			foreach (var hediff in hediffs)
			{
				CompHediffBodyPart rjwHediff = hediff.TryGetComp<CompHediffBodyPart>();
				if (rjwHediff != null)
				{
					cumAmount += rjwHediff.FluidAmmount * rjwHediff.FluidModifier;
				}
				else
				{
					cumAmount += hediff.Severity * giver.BodySize;
				}
			}

			if (cumAmount == 0)
			{
				cumAmount = giver.BodySize; //fallback for mechanoinds and w/e without hediffs
			}
			// Horniness will scale resulting output somewhere from 50% (0 horniness) to 100% (max horniness).
			Need sexNeed = giver?.needs?.AllNeeds?.Find(x => string.Equals(x.def.defName, "Sex"));
			float horniness = sexNeed == null ? 1f : 1f - 0.5f * sexNeed.CurLevel;
			float ageScale = Math.Min(80 / SexUtility.ScaleToHumanAge(giver), 1.0f); //calculation lifted from rjw
			return Settings.CumflationMultiplier * cumAmount * horniness * ageScale;
		}

		public static float AdjustCumAmount(float cumAmount, float holeSize, bool invert = false)
		{
			if (holeSize == 0)
			{
				holeSize = 1f; // we don't want to devide or multiply by zero
			}

			if (!invert)
			{
				float severity = cumAmount / holeSize;
				severity /= 100f; // severity is 0.0-1.0
				return severity;
			}

			float output = cumAmount * 100f; // invert the severity into cumAmount
			output *= holeSize;
			return output;
		}

		public static void TransferNutrition(Pawn giver, Pawn receiver, float cumAmount)
		{
			if (!Settings.IsCumflationNutritionEnabled)
            {
				return;
            }

			float nutrition = CalculateNutritionAmount(giver, cumAmount);
			Need_Food inflatorFood = giver.needs.TryGetNeed<Need_Food>();
			Need_Food inflatedFood = receiver.needs.TryGetNeed<Need_Food>();
			if (inflatorFood != null && inflatedFood != null)
			{
				inflatorFood.CurLevel -= nutrition;
				inflatedFood.CurLevel += nutrition;
			}

			// It is assumed that cum is less filling for thirst than for hunger (though really, the value of either is debatable).
			if (xxx.DubsBadHygieneIsActive)
			{
				NeedDef inflatedThirstDef = receiver.needs.AllNeeds.Find(x => x.def == xxx.DBHThirst)?.def;
				if (inflatedThirstDef != null)
				{
					receiver.needs.TryGetNeed(inflatedThirstDef).CurLevel += nutrition / 2;
				}
            }
		}

		public static float CalculateNutritionAmount(Pawn giver, float cumAmount)
		{
			Need_Food need = giver?.needs?.TryGetNeed<Need_Food>();
			if (need == null)
			{
				return 0f;
			}

			// Every unit of cum can be treated as 0.005 nutrition. That is, 200 cum units would fill an average pawn with a 1.0 nutrition cap.
			var nutritionAmount = cumAmount * 0.005f;
			return Settings.IsAllowCumflationNutritionToViolateThermodynamics ? nutritionAmount : Math.Min(nutritionAmount, need.CurLevel);
		}

		public static bool LikesCumflation(Pawn inflated)
		{
			bool likesCumflation = inflated?.story?.traits?.HasTrait(Licentia.TraitDefs.likesCumflation) ?? false;
			if (likesCumflation)
			{
				return likesCumflation;
			}

			string pawn_quirks = CompRJW.Comp(inflated).quirks.ToString();
			if (pawn_quirks.Contains("Impregnation fetish") ||
				pawn_quirks.Contains("Teratophile") ||
				pawn_quirks.Contains("Incubator") ||
				pawn_quirks.Contains("Breeder") ||
				pawn_quirks.Contains("Messy") ||
				xxx.is_zoophile(inflated))
			{
				return true;
			}
			return false;
		}

		public static void GiveOverinflationThoughts(Pawn inflated, xxx.rjwSextype sextype)
		{
			bool likesCumflation = LikesCumflation(inflated);

			switch (sextype)
			{
				case xxx.rjwSextype.Oral:
				case xxx.rjwSextype.Fellatio:
					if (likesCumflation)
					{
						inflated?.needs?.mood?.thoughts?.memories?.TryGainMemory(Licentia.ThoughtDefs.GotOverCumstuffedEnjoyed);
						return;
					}
					inflated?.needs?.mood?.thoughts?.memories?.TryGainMemory(Licentia.ThoughtDefs.GotOverCumstuffed);
					return;
				case xxx.rjwSextype.Vaginal:
				case xxx.rjwSextype.Anal:
				case xxx.rjwSextype.DoublePenetration:
					if (likesCumflation)
					{
						inflated?.needs?.mood?.thoughts?.memories?.TryGainMemory(Licentia.ThoughtDefs.GotOverCumflatedEnjoyed);
						return;
					}
					inflated?.needs?.mood?.thoughts?.memories?.TryGainMemory(Licentia.ThoughtDefs.GotOverCumflated);
					return;
				default:
					return;
			}
		}

		public static void GiveCumflationThoughts(Pawn inflated)
		{
			if (!LikesCumflation(inflated))
			{
				return;
			}

			inflated?.needs?.mood?.thoughts?.memories?.TryGainMemory(Licentia.ThoughtDefs.GotInflatedKinky);
		}
	}

	[HarmonyPatch(typeof(SexUtility))]
	[HarmonyPatch("TransferNutrition")]
	[HarmonyPatch(new Type[] { typeof(SexProps) })]
	public static class CumflationPatch
	{
		[HarmonyPrefix]
		public static bool TransferNutrition(SexProps props)
		{
			if (!props.pawn.Dead == true && !props.partner.Dead == true && Settings.IsCumflationEnabled)
			{
				CumflationHelper.Cumflation(props);
				SexUtility.TransferNutritionSucc(props);
			}
			else
            {
				return true;
            }

			return false;
		}
	}
}


code:
using RimWorld;
using System;
using Verse;

namespace LicentiaLabs.Hediffs
{
	public class HediffComp_VomitCumMtb : HediffComp
	{
		public HediffCompProperties_VomitCumMtb Props
		{
			get
			{
				return (HediffCompProperties_VomitCumMtb)this.props;
			}
		}

		public override void CompPostTick(ref float severityAdjustment)
		{
			if (this.Props.mtbDaysPerStage[this.parent.CurStageIndex] > 0f && base.Pawn.IsHashIntervalTick(60) && Rand.MTBEventOccurs(this.Props.mtbDaysPerStage[this.parent.CurStageIndex], 60000f, 60f))
			{
				// If the Pawn likes cumflation, they're going to try to keep it in. Still, a little will leak out, so generate filth but do not vomit.
				if (this.Pawn.story.traits.HasTrait(Licentia.TraitDefs.likesCumflation))
				{
					FilthMaker.TryMakeFilth(this.Pawn.PositionHeld, this.Pawn.MapHeld, Licentia.ThingDefs.FilthCum, 1);
					return;
                }

				this.Pawn.jobs.StartJob(JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("VomitCum")), lastJobEndCondition: Verse.AI.JobCondition.InterruptForced, resumeCurJobAfterwards: true);
			}
		}
	}
}

repo_name: Jaaldabaoth/licentia

license: unknown


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

namespace LicentiaLabs.Hediffs
{
	/* public class HediffGiver_Genderbend : HediffGiver
	{
		public float severity;

		public override void OnIntervalPassed(Pawn pawn, Hediff cause)
		{
			if (!(cause.Severity < severity))
				{
				var newSize = 0;
				var genitals = Genital_Helper.get_genitals(pawn);

				var chest = Genital_Helper.get_breasts(pawn);
				var newBreastSize = 0;

				if (BreastSize_Helper.TryGetBreastSize(pawn, out var oldBreastSize, out var oldBreast))
				{
					if (oldBreastSize > 0)
					{
						newBreastSize = 0;
					}
					else
					{
						newBreastSize = 2;
					}

					var newBreast = BreastSize_Helper.GetHediffDef(newBreastSize);

					GenderHelper.ChangeSex(pawn, () =>
					{
						if (oldBreast != null)
						{
							pawn.health.RemoveHediff(oldBreast);
						}
						pawn.health.AddHediff(newBreast, chest);
					});

					var message = "LL_BreastsSwapped".Translate(pawn);
					Messages.Message(message, pawn, MessageTypeDefOf.SilentInput);

				}

				if (LicentiaHelper.TryGetGenitalSpectrumSize(pawn, out var oldSize, out var oldGenitals))
				{
					if (oldSize >= 5)
					{
						newSize = oldSize - 5;
					}
					else
					{
						newSize = oldSize + 5;
					}

					var newGenitals = LicentiaHelper.GetGenitalHediffDef(newSize);

					GenderHelper.ChangeSex(pawn, () =>
					{
						if (oldGenitals != null)
						{
							pawn.health.RemoveHediff(oldGenitals);
						}
						pawn.health.AddHediff(newGenitals, genitals);
					});

					var message = "LL_GenitalsSwapped".Translate(pawn);
					Messages.Message(message, pawn, MessageTypeDefOf.SilentInput);

				}
				else if (LicentiaHelper.TryGetOtherGenitaliaIndex(pawn, out oldSize, out oldGenitals))
				{
					if (Genital_Helper.has_penis(pawn))
					{
						newSize = oldSize + 1;
					}
					else
					{
						newSize = oldSize - 1;
					}

					var newGenitals = LicentiaHelper.GetGenitalHediffDef(newSize);

					GenderHelper.ChangeSex(pawn, () =>
					{
						if (oldGenitals != null)
						{
							pawn.health.RemoveHediff(oldGenitals);
						}
						pawn.health.AddHediff(newGenitals, genitals);
					});

					var message = "LL_GenitalsSwapped".Translate(pawn);
					Messages.Message(message, pawn, MessageTypeDefOf.SilentInput);
				}
				else
				{
					Messages.Message("LL_GenitalsNotAlterable".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);

					return;
				}
			}

			return;

		}
	}
	public class HediffGiver_RemoveThis : HediffGiver
	{
		public override void OnIntervalPassed(Pawn pawn, Hediff cause)
		{
			pawn.health.RemoveHediff(cause);
			return;
		}
	} */
}


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

namespace LicentiaLabs.Hediffs
{
	public class HediffCompProperties_VomitCumMtb : HediffCompProperties
	{
		public HediffCompProperties_VomitCumMtb()
		{
			this.compClass = typeof(HediffComp_VomitCumMtb);
		}

		public override IEnumerable<string> ConfigErrors(HediffDef parentDef)
		{
			foreach (string text in base.ConfigErrors(parentDef))
			{
				yield return text;
			}
			if (this.mtbDaysPerStage == null)
			{
				yield return "mtbDaysPerStage is not defined";
			}
			else if (this.mtbDaysPerStage.Count != parentDef.stages.Count)
			{
				yield return "mtbDaysPerStage count doesn't match Hediffs number of stages";
			}
			yield break;
		}

		public List<float> mtbDaysPerStage;
	}
}


code:
using LicentiaLabs;
using System.Collections.Generic;
using Verse;
using Verse.AI;
using UnityEngine;

namespace RimWorld
{
    class JobDriver_VomitCum : JobDriver_Vomit
    {
		public override bool CanBeginNowWhileLyingDown()
        {
			return true;
        }

		protected override IEnumerable<Toil> MakeNewToils()
		{
			Toil toil = new Toil();
			toil.initAction = delegate()
			{
				this.ticksLeft = Rand.Range(300, 900);
				int num = 0;
				IntVec3 c;
				for (;;)
				{
					c = this.pawn.Position + GenAdj.AdjacentCellsAndInside[Rand.Range(0, 9)];
					num++;
					if (num > 12)
					{
						break;
					}
					if (c.InBounds(this.pawn.Map) && c.Standable(this.pawn.Map))
					{
						goto IL_77;
					}
				}
				c = this.pawn.Position;
			IL_77:
				this.job.targetA = c;
				this.pawn.pather.StopDead();
			};
			toil.tickAction = delegate()
			{
				if (this.ticksLeft % 150 == 149)
				{
					if (!sourceName.NullOrEmpty())
					{
						FilthMaker.TryMakeFilth(this.job.targetA.Cell, base.Map, Licentia.ThingDefs.FilthCum, sourceName);
					}
					else
					{
						FilthMaker.TryMakeFilth(this.job.targetA.Cell, base.Map, Licentia.ThingDefs.FilthCum);
					}
					if (this.pawn.needs.food.CurLevelPercentage > 0.1f)
					{
						this.pawn.needs.food.CurLevel -= this.pawn.needs.food.MaxLevel * 0.005f;
					}
				}
				this.ticksLeft--;
				if (this.ticksLeft <= 0)
				{
					base.ReadyForNextToil();
					TaleRecorder.RecordTale(Licentia.TaleDefs.VomitedCum, new object[]
					{
						this.pawn
					});
				}
			};
			toil.defaultCompleteMode = ToilCompleteMode.Never;
			toil.WithEffect(EffecterDefOf.Vomit, TargetIndex.A, new Color(100f, 100f, 100f, 0.5f));
			toil.PlaySustainerOrSound(() => SoundDefOf.Vomit, 1f);
			yield return toil;
			yield break;
		}

		private int ticksLeft;

		public string sourceName;
	}
}


code:
using System.Collections.Generic;
using RimWorld;
using Verse;
using rjw;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Interactions.Helpers;
using rjw.Modules.Interactions.Enums;
using System.Linq;

namespace LicentiaLabs
{
	public static class Licentia
	{
		public static class TaleDefs
        {
			public static readonly TaleDef VomitedCum = DefDatabase<TaleDef>.GetNamed("VomitedCum");
        }

		public static class ThoughtDefs
		{
			public static readonly ThoughtDef GotInflatedKinky = DefDatabase<ThoughtDef>.GetNamed("KinkyGotInflated");
			public static readonly ThoughtDef GotOverCumflated = DefDatabase<ThoughtDef>.GetNamed("GotOverCumflated");
			public static readonly ThoughtDef GotOverCumflatedEnjoyed = DefDatabase<ThoughtDef>.GetNamed("GotOverCumflatedEnjoyed");
			public static readonly ThoughtDef GotOverCumstuffed = DefDatabase<ThoughtDef>.GetNamed("GotOverCumstuffed");
			public static readonly ThoughtDef GotOverCumstuffedEnjoyed = DefDatabase<ThoughtDef>.GetNamed("GotOverCumstuffedEnjoyed");
			public static readonly ThoughtDef GotStretched = DefDatabase<ThoughtDef>.GetNamed("GotStretched");
			public static readonly ThoughtDef GotStretchedKinky = DefDatabase<ThoughtDef>.GetNamed("KinkyGotStretched");
			public static readonly ThoughtDef GotStretchedMasochist = DefDatabase<ThoughtDef>.GetNamed("MasochistGotStretched");
		}
		public static class HediffDefs
		{
			public static readonly HediffDef CervixBruise = HediffDef.Named("CervixBruise");
			public static readonly HediffDef Cumflation = HediffDef.Named("Cumflation");
			public static readonly HediffDef Cumstuffed = HediffDef.Named("Cumstuffed");
			public static readonly HediffDef Diminished = HediffDef.Named("Diminished");
			public static readonly HediffDef Elasticised = HediffDef.Named("Elasticised");
			public static readonly HediffDef Enlarged = HediffDef.Named("Enlarged");
			public static readonly HediffDef ExtremeProlapse = HediffDef.Named("ExtremeProlapse");
			public static readonly HediffDef Stretched = HediffDef.Named("Stretched");
			public static readonly HediffDef StretchTear = HediffDef.Named("StretchTear");
			public static readonly HediffDef Prolapse = HediffDef.Named("Prolapse");
			public static readonly HediffDef Untearable = HediffDef.Named("Untearable");
		}

		public static class RecordDefs
        {
			public static readonly RecordDef AmountOfCumstuffed = DefDatabase<RecordDef>.GetNamed("AmountOfCumstuffed");
			public static readonly RecordDef AmountOfCumflation = DefDatabase<RecordDef>.GetNamed("AmountOfCumflation");
		}

		public static class ThingDefs
		{
			public static readonly ThingDef FilthCum = ThingDef.Named("FilthCum");
			public static readonly ThingDef HormonalSerum = ThingDef.Named("HormonalSerum");
		}
		public static class TraitDefs
		{
			public static readonly TraitDef likesCumflation = TraitDef.Named("LikesCumflation");
		}
	}

	public static class LicentiaHelper
	{
		public static readonly IDictionary<SexPartType, BodyPartDef> BodyPartDefBySexPartType = new Dictionary<SexPartType, BodyPartDef>();

		static LicentiaHelper() {
			BodyPartDefBySexPartType.Add(SexPartType.Anus, xxx.anusDef);
			BodyPartDefBySexPartType.Add(SexPartType.FemaleBreast, xxx.breastsDef);
			BodyPartDefBySexPartType.Add(SexPartType.FemaleGenital, xxx.genitalsDef);
			BodyPartDefBySexPartType.Add(SexPartType.MaleBreast, xxx.breastsDef);
			BodyPartDefBySexPartType.Add(SexPartType.MaleGenital, xxx.genitalsDef);
		}

		public static bool IsArtificial(Hediff hediff) {
			return !(hediff is Hediff_PartBaseNatural);
		}

		public static Hediff TryGetRJWPartHediff(Pawn pawn, SexPartType partType) {
			switch (partType) {
				case SexPartType.Anus:
					return TryGetPawnAnusHediff(pawn);
				case SexPartType.FemaleGenital:
					return TryGetPawnVaginaHediff(pawn);
				case SexPartType.MaleGenital:
					{
						var hediffs = TryGetPawnPenisHediff(pawn);
						if (hediffs.Count > 0)
                        {
							return hediffs[0];
                        }
						return null;
					}
			}

			// default value
			return TryGetPawnBreastHediff(pawn);
		}

		public static List<Hediff> TryGetPawnPenisHediff(Pawn pawn) // used for stretch and futa calculation
		{
			var hediffs = new HashSet<Hediff>();

			var genitalsHediffs = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_genitalsBPR(pawn));
			if (Genital_Helper.has_penis_fertile(pawn, genitalsHediffs) || Genital_Helper.has_penis_infertile(pawn, genitalsHediffs) || Genital_Helper.has_multipenis(pawn, genitalsHediffs))
			{
				hediffs.AddRange(genitalsHediffs.FindAll((Hediff hediff) => hediff.def.defName.ToLower().Contains("penis")));
				hediffs.AddRange(genitalsHediffs.FindAll((Hediff hediff) => hediff.def.defName.ToLower().Contains("ovipositorf")));
				hediffs.AddRange(genitalsHediffs.FindAll((Hediff hediff) => hediff.def.defName.ToLower().Contains("ovipositorm")));
				hediffs.AddRange(genitalsHediffs.FindAll((Hediff hediff) => hediff.def.defName.ToLower().Contains("tentacle")));
			}
			else if (Genital_Helper.has_ovipositorF(pawn, genitalsHediffs))
			{
				hediffs.AddRange(genitalsHediffs.FindAll((Hediff hediff) => hediff.def.defName.ToLower().Contains("ovipositorf")));
			}

			return hediffs.ToList();
		}

		public static Hediff TryGetPawnVaginaHediff(Pawn pawn) // used for stretch and futa calculation
		{
			Hediff hediff = null;
			var pawnparts = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_genitalsBPR(pawn));

			if (Genital_Helper.has_vagina(pawn, pawnparts))
			{
				hediff = pawnparts.FindAll((Hediff hed) => hed.def.defName.ToLower().Contains("vagina")).InRandomOrder().FirstOrDefault();

				if (hediff == null)
					hediff = pawnparts.FindAll((Hediff hed) => hed.def.defName.ToLower().Contains("ovipositorf")).InRandomOrder().FirstOrDefault();
			}
			else
			{
				hediff = null;
			}

			return hediff;
		}

		public static Hediff TryGetPawnAnusHediff(Pawn pawn)
		{
			BodyPartRecord Part = Genital_Helper.get_anusBPR(pawn);
			if (Part is null)
				return null;

			return pawn.health.hediffSet.hediffs.FindAll((Hediff hed) =>
					hed.Part == Part &&
					(hed is Hediff_PartBaseNatural || hed is Hediff_PartBaseArtifical) &&
					(hed.def.defName.ToLower().Contains("anus"))).InRandomOrder().FirstOrDefault();
		}

		public static Hediff TryGetPawnBreastHediff(Pawn pawn)
		{
			BodyPartRecord Part = Genital_Helper.get_breastsBPR(pawn);
			if (Part is null)
				return null;

			return pawn.health.hediffSet.hediffs.FindAll((Hediff hed) =>
					hed.Part == Part &&
					(hed is Hediff_PartBaseNatural || hed is Hediff_PartBaseArtifical) &&
					(hed.def.defName.ToLower().Contains("breasts") || hed.def.defName.ToLower().Contains("chest"))
					).InRandomOrder().FirstOrDefault();
		}

		public static bool GetPenetrator(SexProps props, out Pawn penetrator, out Pawn penetrated)
		{
			penetrator = null;
			penetrated = null;

			bool initiatorHasPenis = true;
			bool partnerHasPenis = true;
			if (TryGetPawnPenisHediff(props.pawn).EnumerableNullOrEmpty())
			{
				initiatorHasPenis = false;
			}
			if (TryGetPawnPenisHediff(props.partner).EnumerableNullOrEmpty())
			{
				partnerHasPenis = false;
			}

			if (!initiatorHasPenis && !partnerHasPenis){
				return false;
			}

			InteractionWithExtension interaction = InteractionHelper.GetWithExtension(props.dictionaryKey);
			bool initiatorUsesPenis;
			bool partnerUsesPenis;
			if (!props.isReceiver)
			{
				initiatorUsesPenis = interaction?.DominantHasTag(GenitalTag.CanPenetrate) ?? false;
				partnerUsesPenis = interaction?.SubmissiveHasTag(GenitalTag.CanPenetrate) ?? false;
			}
			else
			{
				initiatorUsesPenis = interaction?.SubmissiveHasTag(GenitalTag.CanPenetrate) ?? false;
				partnerUsesPenis = interaction?.DominantHasTag(GenitalTag.CanPenetrate) ?? false;
			}

			if (initiatorHasPenis && initiatorUsesPenis)
			{
				penetrator = props.pawn;
				penetrated = props.partner;
				return true;
			}
			else if (partnerHasPenis && partnerUsesPenis)
			{
				penetrator = props.partner;
				penetrated = props.pawn;
				return true;
			}

			return false;
		}

		public static bool AddSexPart(Pawn pawn, rjw.SexPartType partType, float severity = 0) {
			if(pawn == null) {
				return false;
			}

			if(PawnExtensions.TryAddRacePart(pawn, partType)) {

				Hediff hdf = TryGetRJWPartHediff(pawn, partType);
				CompHediffBodyPart chdf = hdf.TryGetComp<rjw.CompHediffBodyPart>();
				if (chdf != null) {
					chdf.updatesize(severity);
				}

				return true;
			}

			string defaultHediffName = "GenericBreasts";
			switch(partType) {
				case SexPartType.Anus:
					defaultHediffName = "GenericAnus";
					break;
				case SexPartType.FemaleGenital:
					defaultHediffName = "GenericVagina";
					break;
				case SexPartType.MaleGenital:
					defaultHediffName = "GenericPenis";
					break;
			}

			BodyPartRecord bpr = pawn.RaceProps.body.AllParts.Find(item => item.def == BodyPartDefBySexPartType[partType]);
			HediffDef hdd = DefDatabase<HediffDef>.GetNamed(defaultHediffName);
			Hediff hd = HediffMaker.MakeHediff(hdd, pawn, bpr);
			CompHediffBodyPart chd = hd.TryGetComp<CompHediffBodyPart>();

			if (chd == null) {
				return false;
			}

			chd.initComp(pawn);
			chd.updatesize(severity);
			pawn.health.AddHediff(hd);

			return true;
		}

		public static T Clamp<T>(T value, T min, T max) where T : System.IComparable<T> {
			if (value.CompareTo(min) < 0) return min;
			else if (value.CompareTo(max) > 0) return max;
			return value;
		}
	}
}


code:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{57515AAF-B37A-4912-A910-4C629FEC734F}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>LicentiaLabs</RootNamespace>
    <AssemblyName>LicentiaLabs</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
    <TargetFrameworkProfile />
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>false</DebugSymbols>
    <DebugType>none</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>..\..\..\Assemblies\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <PlatformTarget>x64</PlatformTarget>
    <Prefer32Bit>false</Prefer32Bit>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>..\..\..\Assemblies\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <Prefer32Bit>false</Prefer32Bit>
    <PlatformTarget>x64</PlatformTarget>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="0Harmony">
      <HintPath>..\..\..\..\..\..\..\workshop\content\294100\2009463077\Current\Assemblies\0Harmony.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="0MultiplayerAPI">
      <HintPath>..\..\..\..\RJW\1.4\Assemblies\0MultiplayerAPI.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Assembly-CSharp">
      <HintPath>..\..\..\..\..\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="HarmonyMod">
      <HintPath>..\..\..\..\..\..\..\workshop\content\294100\2009463077\Current\Assemblies\HarmonyMod.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJW">
      <HintPath>..\..\..\..\RJW\1.4\Assemblies\RJW.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Runtime.InteropServices.RuntimeInformation" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="System.Data" />
    <Reference Include="System.Xml" />
    <Reference Include="UnityEngine">
      <HintPath>..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.CoreModule">
      <HintPath>..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Cumflation.cs" />
    <Compile Include="Hediffs\HediffComp_VomitCumMtb.cs" />
    <Compile Include="Hediffs\Hediffs.cs" />
    <Compile Include="Hediffs\HediffsCompProperties_VomitCumMtb.cs" />
    <Compile Include="JobDriver_VomitCum.cs" />
    <Compile Include="LicentiaHelper.cs" />
    <Compile Include="Mutator.cs" />
    <Compile Include="Patch.cs" />
    <Compile Include="SerumProduction.cs" />
    <Compile Include="Serums.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="Settings.cs" />
    <Compile Include="Stretch.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="packages.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>


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

namespace LicentiaLabs
{
	public class Comp_MutagenicSerum : ThingComp
	{
		public CompProperties_MutagenicSerum Props => (CompProperties_MutagenicSerum)props;

		public override void PostIngested(Pawn pawn)
		{
			var partBPR = Genital_Helper.get_genitalsBPR(pawn);
			var oldParts = Genital_Helper.get_PartsHediffList(pawn, partBPR);
			var newVagina = Props.vaginaDef;
			var newPenis = Props.penisDef;

			if (!oldParts.NullOrEmpty())
			{
				Hediff hd;
				CompHediffBodyPart CompHediff;

				foreach (Hediff hed in oldParts)
				{
					if (LicentiaHelper.IsArtificial(hed))
						continue;

					hd = null;
					CompHediff = null;
					if (hed.def.defName.ToLower().Contains("penis") ||
						hed.def.defName.ToLower().Contains("ovipositorm") ||
						hed.def.defName.ToLower().Contains("tentacle")
						)
					{
						hd = HediffMaker.MakeHediff(newPenis, pawn, partBPR);
					}

					if (hed.def.defName.ToLower().Contains("vagina") ||
						hed.def.defName.ToLower().Contains("ovipositorf")
						)
					{
						hd = HediffMaker.MakeHediff(newVagina, pawn, partBPR);
					}

					if (hd != null)
					{
						CompHediff = hd.TryGetComp<rjw.CompHediffBodyPart>();
						if (CompHediff != null)
						{
							//Log.Message("SexPartAdder::PartMaker init comps");
							CompHediff.initComp(pawn);
							CompHediff.updatesize();
						}
						GenderHelper.ChangeSex(pawn, () =>
						{
							pawn.health.RemoveHediff(hed);
							pawn.health.AddHediff(hd, partBPR);
						});
					}
				}

				var message = "LL_GenitalsAltered".Translate(pawn);
				Messages.Message(message, pawn, MessageTypeDefOf.SilentInput);
			}
			else
			{
				Messages.Message("LL_GenitalsNotAlterable".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);

				return;
			}
		}
	}

	public class Comp_MutagenicSerumSpecial : ThingComp
	{

		public CompProperties_MutagenicSerumSpecial Props => (CompProperties_MutagenicSerumSpecial)props;

		public override void PostIngested(Pawn pawn)
		{
			if (Props.breast && GenderHelper.GetSex(pawn) != GenderHelper.Sex.male)
			{
				var partBPR = Genital_Helper.get_breastsBPR(pawn);
				var oldParts = Genital_Helper.get_PartsHediffList(pawn, partBPR);
				var newParts = Props.breastDef;

				if (!oldParts.NullOrEmpty())
				{
					Hediff hd;
					CompHediffBodyPart CompHediff;

					foreach (Hediff hed in oldParts)
					{
						if (LicentiaHelper.IsArtificial(hed))
							continue;

						hd = null;
						CompHediff = null;
						hd = HediffMaker.MakeHediff(newParts, pawn, partBPR);

						if (hd != null)
						{
							CompHediff = hd.TryGetComp<rjw.CompHediffBodyPart>();
							if (CompHediff != null)
							{
								//Log.Message("SexPartAdder::PartMaker init comps");
								CompHediff.initComp(pawn);
								CompHediff.updatesize();
							}
							GenderHelper.ChangeSex(pawn, () =>
							{
								pawn.health.RemoveHediff(hed);
								pawn.health.AddHediff(hd, partBPR);
							});
						}
					}

					var message = "LL_BreastsAltered".Translate(pawn);
					Messages.Message(message, pawn, MessageTypeDefOf.SilentInput);
				}
			}

			if (Props.anus)
			{
				var partBPR = Genital_Helper.get_anusBPR(pawn);
				var oldParts = Genital_Helper.get_PartsHediffList(pawn, partBPR);
				var newParts = Props.anusDef;

				if (!oldParts.NullOrEmpty())
				{
					Hediff hd;
					CompHediffBodyPart CompHediff;

					foreach (Hediff hed in oldParts)
					{
						if (LicentiaHelper.IsArtificial(hed))
							continue;

						hd = null;
						CompHediff = null;
						hd = HediffMaker.MakeHediff(newParts, pawn, partBPR);

						if (hd != null)
						{
							CompHediff = hd.TryGetComp<rjw.CompHediffBodyPart>();
							if (CompHediff != null)
							{
								//Log.Message("SexPartAdder::PartMaker init comps");
								CompHediff.initComp(pawn);
								CompHediff.updatesize();
							}
							GenderHelper.ChangeSex(pawn, () =>
							{
								pawn.health.RemoveHediff(hed);
								pawn.health.AddHediff(hd, partBPR);
							});
						}
					}

					var message = "LL_AnusAltered".Translate(pawn);
					Messages.Message(message, pawn, MessageTypeDefOf.SilentInput);
				}
			}
		}
	}

	public class Comp_ReverterSerum : ThingComp
	{
		//bool futa = false;
		public override void PostIngested(Pawn pawn)
		{
			if (GenderHelper.GetOriginalSex(pawn) == GenderHelper.Sex.none) {
				// what is happenning??
				return;
			}

			GenderHelper.ChangeSex(pawn, () =>
			{
				// trap, futa, female & male could all revert their anus
				var partBPR = Genital_Helper.get_anusBPR(pawn);
				var oldParts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

				if (!oldParts.NullOrEmpty())
				{
					foreach (Hediff hed in oldParts)
					{
						if (LicentiaHelper.IsArtificial(hed))
							continue;

						pawn.health.RemoveHediff(hed);
						LicentiaHelper.AddSexPart(pawn, SexPartType.Anus);
					}
				}

				partBPR = Genital_Helper.get_genitalsBPR(pawn);
				oldParts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

				// male, trap & futas gets a dic
				if (GenderHelper.GetOriginalSex(pawn) == GenderHelper.Sex.male
				|| GenderHelper.GetOriginalSex(pawn) == GenderHelper.Sex.trap
				|| GenderHelper.GetOriginalSex(pawn) == GenderHelper.Sex.futa)
				{
					if (!oldParts.NullOrEmpty())
					{
						foreach (Hediff hed in oldParts)
						{
							if (LicentiaHelper.IsArtificial(hed))
								continue;

							if (hed.def.defName.ToLower().Contains("penis") ||
								hed.def.defName.ToLower().Contains("ovipositorm") ||
								hed.def.defName.ToLower().Contains("tentacle")
								)
							{
								pawn.health.RemoveHediff(hed);
								LicentiaHelper.AddSexPart(pawn, SexPartType.MaleGenital);
							}
						}
					}
				}

				// female & futa gets a vagoo
				if(GenderHelper.GetOriginalSex(pawn) == GenderHelper.Sex.female
				|| GenderHelper.GetOriginalSex(pawn) == GenderHelper.Sex.futa)
				{
					if (!oldParts.NullOrEmpty())
					{
						foreach (Hediff hed in oldParts)
						{
							if (LicentiaHelper.IsArtificial(hed))
								continue;

							if (hed.def.defName.ToLower().Contains("vagina") ||
								hed.def.defName.ToLower().Contains("ovipositorf")
								)
							{
								pawn.health.RemoveHediff(hed);
								LicentiaHelper.AddSexPart(pawn, SexPartType.FemaleGenital);
							}
						}
					}
				}

				partBPR = Genital_Helper.get_breastsBPR(pawn);
				oldParts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

				// males have flat chest
				if (GenderHelper.GetOriginalSex(pawn) == GenderHelper.Sex.male)
				{
					foreach (Hediff hed in oldParts)
					{
						if (LicentiaHelper.IsArtificial(hed))
							continue;

						pawn.health.RemoveHediff(hed);
						LicentiaHelper.AddSexPart(pawn, SexPartType.MaleBreast, 0.01f);
					}
				}

				// females have classic chest
				if(GenderHelper.GetOriginalSex(pawn) == GenderHelper.Sex.female
				|| GenderHelper.GetOriginalSex(pawn) == GenderHelper.Sex.futa)
				{
					foreach (Hediff hed in oldParts)
					{
						if (LicentiaHelper.IsArtificial(hed))
							continue;

						pawn.health.RemoveHediff(hed);
						LicentiaHelper.AddSexPart(pawn, SexPartType.FemaleBreast);
					}
				}
			});

			Messages.Message("LL_Reverted".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);
		}
	}

	public class Comp_TraitSerum : ThingComp
	{
		public CompProperties_TraitSerum Props => (CompProperties_TraitSerum)props;
		public override void PostIngested(Pawn pawn)
		{
			if (Props.addTrait != null)
			{
				if (!Props.replaceTrait)
				{
					pawn.story.traits.GainTrait(new Trait(Props.addTrait));
				}
				else
				{
					pawn.story.traits.allTraits.Remove(pawn.story.traits.GetTrait(Props.removeTrait));
					pawn.story.traits.GainTrait(new Trait(Props.addTrait));
				}
			}
			else
			{
				pawn.story.traits.allTraits.Remove(pawn.story.traits.GetTrait(Props.removeTrait));
			}
			var message = "LL_PsycheAltered".Translate(pawn);
			Messages.Message(message, pawn, MessageTypeDefOf.SilentInput);
		}
	}

	public class CompProperties_MutagenicSerum : CompProperties
	{
		public HediffDef penisDef;
		public HediffDef vaginaDef;

		public CompProperties_MutagenicSerum()
		{
			compClass = typeof(Comp_MutagenicSerum);
		}
	}
	public class CompProperties_MutagenicSerumSpecial : CompProperties
	{
		public bool anus = false;
		public bool breast = false;
		public HediffDef anusDef;
		public HediffDef breastDef;

		public CompProperties_MutagenicSerumSpecial()
		{
			compClass = typeof(Comp_MutagenicSerumSpecial);
		}
	}
	public class CompProperties_ReverterSerum : CompProperties
	{
		public CompProperties_ReverterSerum()
		{
			compClass = typeof(Comp_ReverterSerum);
		}
	}
	public class CompProperties_TraitSerum : CompProperties
	{
		public bool replaceTrait = false;
		public TraitDef addTrait;
		public TraitDef removeTrait;
		public CompProperties_TraitSerum()
		{
			compClass = typeof(Comp_TraitSerum);
		}
	}
}


code:
using Verse;

namespace LicentiaLabs {

	public class Comp_LicentiaPart : HediffComp {
	}

	public class CompProperties_LicentiaPart : HediffCompProperties {
		public float stretchVariance = 0f;
		public float genitalSize = 0.5f;
		public float cumAmount = -1f;
		public HediffDef oppositeSexOrgan;
		public HediffDef oppositeBreasts;
		public HediffDef extrudedSexOrgan;
		public HediffDef retractedSexOrgan;

		public CompProperties_LicentiaPart()
		{
			compClass = typeof(Comp_LicentiaPart);
		}
	}
}


code:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("LicentiaLabs")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Hewlett-Packard")]
[assembly: AssemblyProduct("LicentiaLabs")]
[assembly: AssemblyCopyright("Copyright © Hewlett-Packard 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components.  If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("57515aaf-b37a-4912-a910-4c629fec734f")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]


code:
using Verse;

namespace LicentiaLabs
{
	internal class Hediff_ProducingHormonalSerum : HediffWithComps
	{
		public float SerumProgress
		{
			get => Severity;
			private set => Severity = value;
		}

		public override void Tick()
		{

			if (this.pawn.IsHashIntervalTick(1000))
			{
				if (SerumProgress == 1f)
				{
					GenSpawn.Spawn(Licentia.ThingDefs.HormonalSerum, this.pawn.Position, this.pawn.Map);
					this.Severity = 0f;
				}

			}
		}
	}
}


code:
using RimWorld;
using Verse;
using rjw;

namespace LicentiaLabs
{
	public class Comp_BreastIncrease : ThingComp
	{
		public override void PostIngested(Pawn pawn)
		{
			var partBPR = Genital_Helper.get_breastsBPR(pawn);
			var Parts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

			if (!Parts.NullOrEmpty())
			{
				foreach (Hediff hed in Parts)
				{
					if (LicentiaHelper.IsArtificial(hed))
						continue;

					GenderHelper.ChangeSex(pawn, () =>
					{
						hed.Severity += 0.1f;
					});
				}
				Messages.Message("LL_BreastsSizeIncrease".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);
			}
		}
	}

	public class Comp_BreastDecrease : ThingComp
	{
		public override void PostIngested(Pawn pawn)
		{
			var partBPR = Genital_Helper.get_breastsBPR(pawn);
			var Parts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

			if (!Parts.NullOrEmpty())
			{
				foreach (Hediff hed in Parts)
				{
					if (LicentiaHelper.IsArtificial(hed))
						continue;

					GenderHelper.ChangeSex(pawn, () =>
					{
						if (hed.Severity <= 0.11f)
						{
							hed.Severity = 0.01f;
						}
						else
						{
							hed.Severity -= 0.1f;
						}
					});
				}
				Messages.Message("LL_BreastsSizeDecrease".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);
			}
		}
	}

	public class Comp_GenitalIncrease : ThingComp
	{
		public override void PostIngested(Pawn pawn)
		{
			var partBPR = Genital_Helper.get_genitalsBPR(pawn);
			var oldParts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

			if (!oldParts.NullOrEmpty())
			{
				foreach (Hediff hed in oldParts)
				{
					if (LicentiaHelper.IsArtificial(hed))
						continue;

					if (hed.def.defName.ToLower().Contains("penis") ||
						hed.def.defName.ToLower().Contains("ovipositorm") ||
						hed.def.defName.ToLower().Contains("tentacle")
						)
					{
						hed.Severity += 0.1f;
					}

					if (hed.def.defName.ToLower().Contains("vagina") ||
						hed.def.defName.ToLower().Contains("ovipositorf")
						)
					{
						if (hed.Severity <= 0.11f)
						{
							hed.Severity = 0.01f;
						}
						else
						{
							hed.Severity -= 0.1f;
						}
					}
				}
				Messages.Message("LL_GenitalsAltered".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);
			}
		}
	}

	public class Comp_GenitalDecrease : ThingComp
	{
		public override void PostIngested(Pawn pawn) {

			var partBPR = Genital_Helper.get_genitalsBPR(pawn);
			var oldParts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

			if (!oldParts.NullOrEmpty())
			{
				foreach (Hediff hed in oldParts)
				{
					if (LicentiaHelper.IsArtificial(hed))
						continue;

					if (hed.def.defName.ToLower().Contains("penis") ||
						hed.def.defName.ToLower().Contains("ovipositorm") ||
						hed.def.defName.ToLower().Contains("tentacle")
						)
					{
						if (hed.Severity <= 0.11f)
						{
							hed.Severity = 0.01f;
						}
						else
						{
							hed.Severity -= 0.1f;
						}
					}

					if (hed.def.defName.ToLower().Contains("vagina") ||
						hed.def.defName.ToLower().Contains("ovipositorf")
						)
					{
						hed.Severity += 0.1f;
					}
				}
				Messages.Message("LL_GenitalsAltered".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);
			}
		}
	}

	public class Comp_CumIncrease : ThingComp
	{
		public override void PostIngested(Pawn pawn)
		{
			var partBPR = Genital_Helper.get_genitalsBPR(pawn);
			var oldParts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

			if (!oldParts.NullOrEmpty())
			{
				Hediff hd;
				CompHediffBodyPart CompHediff;

				foreach (Hediff hed in oldParts)
				{
					if (LicentiaHelper.IsArtificial(hed))
						continue;

					hd = null;
					CompHediff = null;
					if (hed.def.defName.ToLower().Contains("penis") ||
						hed.def.defName.ToLower().Contains("ovipositorm") ||
						hed.def.defName.ToLower().Contains("tentacle")
						)
					{
						hd = hed;
					}

					if (hed.def.defName.ToLower().Contains("vagina") ||
						hed.def.defName.ToLower().Contains("ovipositorf")
						)
					{
						hd = hed;
					}

					if (hd != null)
					{
						CompHediff = hed.TryGetComp<rjw.CompHediffBodyPart>();
						if (CompHediff != null)
						{
							CompHediff.FluidAmmount *= 1.1f;
						}
					}
				}
				Messages.Message("LL_CumAltered".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);
			}
		}
	}

	public class Comp_CumDecrease : ThingComp
	{
		public override void PostIngested(Pawn pawn)
		{
			var partBPR = Genital_Helper.get_genitalsBPR(pawn);
			var oldParts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

			if (!oldParts.NullOrEmpty())
			{
				Hediff hd;
				CompHediffBodyPart CompHediff;

				foreach (Hediff hed in oldParts)
				{
					if (LicentiaHelper.IsArtificial(hed))
						continue;

					hd = null;
					CompHediff = null;
					if (hed.def.defName.ToLower().Contains("penis") ||
						hed.def.defName.ToLower().Contains("ovipositorm") ||
						hed.def.defName.ToLower().Contains("tentacle")
						)
					{
						hd = hed;
					}

					if (hed.def.defName.ToLower().Contains("vagina") ||
						hed.def.defName.ToLower().Contains("ovipositorf")
						)
					{
						hd = hed;
					}

					if (hd != null)
					{
						CompHediff = hed.TryGetComp<rjw.CompHediffBodyPart>();
						if (CompHediff != null)
						{
							CompHediff.FluidAmmount *= 0.9f;
						}
					}
				}
				Messages.Message("LL_CumAltered".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);
			}
		}
	}

	public class Comp_AnalTighteningSerum : ThingComp
	{

		public override void PostIngested(Pawn pawn)
		{
			var partBPR = Genital_Helper.get_anusBPR(pawn);
			var Parts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

			if (!Parts.NullOrEmpty())
			{
				foreach (Hediff hed in Parts)
				{
					if (LicentiaHelper.IsArtificial(hed))
						continue;

					if (hed.Severity <= 0.11f)
					{
						hed.Severity = 0.01f;
					}
					else
					{
						hed.Severity -= 0.1f;
					}
				}
				var message = "LL_AnusAltered".Translate(pawn);
				Messages.Message(message, pawn, MessageTypeDefOf.SilentInput);
			}
		}
	}

	public class Comp_Genderbend : ThingComp
	{
		public override void PostIngested(Pawn pawn)
		{
			if (GenderHelper.GetOriginalSex(pawn) == GenderHelper.Sex.futa) //if futa
			{
				Messages.Message("LL_NoEffectOnFuta".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);
				return;
			}

			GenderHelper.ChangeSex(pawn, () =>
			{
				var partBPR = Genital_Helper.get_genitalsBPR(pawn);
				var Parts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

				if (!Genital_Helper.has_penis_fertile(pawn, Parts) && !Genital_Helper.has_penis_infertile(pawn, Parts))
				{
					LicentiaHelper.AddSexPart(pawn, SexPartType.MaleGenital);
				}
				else
				{
					if (!Parts.NullOrEmpty())
					{
						foreach (Hediff hed in Parts)
						{
							if (LicentiaHelper.IsArtificial(hed))
								continue;

							if (hed.def.defName.ToLower().Contains("penis") ||
								hed.def.defName.ToLower().Contains("ovipositorm") ||
								hed.def.defName.ToLower().Contains("tentacle")
								)
							{
								pawn.health.RemoveHediff(hed);
							}
						}
					}
				}

				if (!Genital_Helper.has_vagina(pawn, Parts))
				{
					LicentiaHelper.AddSexPart(pawn, SexPartType.FemaleGenital);
				}
				else
				{
					if (!Parts.NullOrEmpty())
					{
						foreach (Hediff hed in Parts)
						{
							if (LicentiaHelper.IsArtificial(hed))
								continue;

								if (hed.def.defName.ToLower().Contains("vagina") ||
									hed.def.defName.ToLower().Contains("ovipositorf")
									)
								{
								pawn.health.RemoveHediff(hed);
							}
						}
					}
				}
			});

			Messages.Message("LL_GenitalsSwapped".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);
		}
	}

	public class Comp_FutaSerum : ThingComp
	{
		public override void PostIngested(Pawn pawn)
		{
			if (GenderHelper.GetOriginalSex(pawn) == GenderHelper.Sex.futa) //if futa
			{
				Messages.Message("LL_NoEffectOnFuta".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);
				return;
			}

			GenderHelper.ChangeSex(pawn, () =>
			{
				var partBPR = Genital_Helper.get_genitalsBPR(pawn);
				var Parts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

				if (!Genital_Helper.has_penis_fertile(pawn, Parts) && !Genital_Helper.has_penis_infertile(pawn, Parts)) {
					LicentiaHelper.AddSexPart(pawn, SexPartType.MaleGenital);
				}

				if (!Genital_Helper.has_vagina(pawn, Parts))
				{
					LicentiaHelper.AddSexPart(pawn, SexPartType.FemaleGenital);
				}

				partBPR = Genital_Helper.get_breastsBPR(pawn);
				Parts = Genital_Helper.get_PartsHediffList(pawn, partBPR);
				if (Parts.NullOrEmpty() || (!Genital_Helper.has_breasts(pawn, Parts) && !Parts.Any((Hediff hed) => hed.def.defName.ToLower().Contains("featureless"))))
				{
					LicentiaHelper.AddSexPart(pawn, SexPartType.FemaleBreast);
				}
			});

			Messages.Message("LL_MadeFuta".Translate(pawn), pawn, MessageTypeDefOf.SilentInput);
		}
	}

	public class CompProperties_BreastIncrease : CompProperties
	{
		public CompProperties_BreastIncrease()
		{
			compClass = typeof(Comp_BreastIncrease);
		}
	}
	public class CompProperties_BreastDecrease : CompProperties
	{
		public CompProperties_BreastDecrease()
		{
			compClass = typeof(Comp_BreastDecrease);
		}
	}
	public class CompProperties_GenitalIncrease : CompProperties
	{
		public CompProperties_GenitalIncrease()
		{
			compClass = typeof(Comp_GenitalIncrease);
		}
	}
	public class CompProperties_GenitalDecrease : CompProperties
	{
		public CompProperties_GenitalDecrease()
		{
			compClass = typeof(Comp_GenitalDecrease);
		}
	}
	public class CompProperties_CumIncrease : CompProperties
	{
		public CompProperties_CumIncrease()
		{
			compClass = typeof(Comp_CumIncrease);
		}
	}
	public class CompProperties_CumDecrease : CompProperties
	{
		public CompProperties_CumDecrease()
		{
			compClass = typeof(Comp_CumDecrease);
		}
	}
	public class CompProperties_Genderbend: CompProperties
	{
		public CompProperties_Genderbend()
		{
			compClass = typeof(Comp_Genderbend);
		}
	}
	public class CompProperties_FutaSerum: CompProperties
	{
		public CompProperties_FutaSerum()
		{
			compClass = typeof(Comp_FutaSerum);
		}
	}

	public class CompProperties_AnalTighteningSerum : CompProperties
	{
		public CompProperties_AnalTighteningSerum()
		{
			compClass = typeof(Comp_AnalTighteningSerum);
		}
	}
}


code:
using UnityEngine;
using Verse;

namespace LicentiaLabs
{
	public class Settings : ModSettings
	{
		public static bool IsAllowMultiplePenetratorsPerOrifice = true;
		public static bool IsStretchingEnabled = true;
		public static bool IsPermanentStretchEnabled = true;
		public static bool IsStretchTextPlayerFactionVisibleOnly = true;

		public static bool IsCumflationEnabled = true;
		public static bool IsCumflationNutritionEnabled = true;
		public static bool IsAllowCumflationNutritionToViolateThermodynamics = false;
		public static float CumflationMultiplier = 1f;

		public static bool IsDamageEnabled = false;
		public static float DamageMultiplier = 1f;
		public static bool IsProlapseEnabled = false;

		public override void ExposeData()
		{
			Scribe_Values.Look(ref IsAllowMultiplePenetratorsPerOrifice, "IsAllowMultiplePenetratorsPerOrifice", true);
			Scribe_Values.Look(ref IsStretchingEnabled, "IsStretchingEnabled", true);
			Scribe_Values.Look(ref IsPermanentStretchEnabled, "IsPermanentStretchEnabled", true);
			Scribe_Values.Look(ref IsStretchTextPlayerFactionVisibleOnly, "IsStretchTextPlayerFactionVisibleOnly", true);

			Scribe_Values.Look(ref IsCumflationEnabled, "IsCumflationEnabled", true);
			Scribe_Values.Look(ref IsCumflationNutritionEnabled, "IsCumflationNutritionEnabled", true);
			Scribe_Values.Look(ref IsAllowCumflationNutritionToViolateThermodynamics, "IsAllowCumflationNutritionToViolateThermodynamics", false);
			Scribe_Values.Look(ref CumflationMultiplier, "CumflationMultiplier", 1f);

			Scribe_Values.Look(ref IsDamageEnabled, "IsDamageEnabled", false);
			Scribe_Values.Look(ref DamageMultiplier, "DamageMultiplier", 1f);
			Scribe_Values.Look(ref IsProlapseEnabled, "IsProlapseEnabled", false);
			base.ExposeData();
		}

		private static readonly float height_modifier = 100f;

		public static void DoWindowContents(Rect inRect)
		{
			Rect viewRect = new Rect(0f, 0f, inRect.width - 16f, inRect.height + height_modifier);

            Listing_Standard listingStandard = new Listing_Standard
            {
                maxOneColumn = true,
                ColumnWidth = viewRect.width / 2.05f
            };
            listingStandard.Begin(inRect);
			listingStandard.Gap(4f);
			listingStandard.CheckboxLabeled("Enable multiple penetrators per orifice", ref IsAllowMultiplePenetratorsPerOrifice, "Allow orifices to have a chance of being targeted by more than one penetrator at once, if more are available.");
			listingStandard.Gap(4f);
			listingStandard.CheckboxLabeled("Enable stretching effects", ref IsStretchingEnabled, "Allow orifices to be stretched, which affects colonists' moods.");
			listingStandard.Gap(4f);
			listingStandard.CheckboxLabeled("Enable permanent stretching", ref IsPermanentStretchEnabled, "Allow orifices to be permanently stretched by large penetrators (at least 1.5x size of orifice).");
			listingStandard.Gap(4f);
			listingStandard.CheckboxLabeled("Stretch messages for player faction only:", ref IsStretchTextPlayerFactionVisibleOnly, "Only show stretch messages for player faction");

			listingStandard.Gap(40f);
			listingStandard.CheckboxLabeled("Enable cumflation", ref IsCumflationEnabled, "Allow colonists to be inflated by excessive cum production.");
			if (IsCumflationEnabled)
			{
				listingStandard.Gap(4f);
				listingStandard.CheckboxLabeled("Enable nutrition gain through cum", ref IsCumflationNutritionEnabled,
					"Allow pawns to gain nutrition by having other pawns cum inside them orally. The giving pawn loses nutrition while the receiving pawn gains nutrition.");
				if (IsCumflationNutritionEnabled)
				{
					listingStandard.Gap(4f);
					listingStandard.CheckboxLabeled("Enable violation of thermodynamics", ref IsAllowCumflationNutritionToViolateThermodynamics,
						"Allows pawns to receive more nutrition than the giving pawn is able to give.");
				}
				listingStandard.Gap(4f);
				listingStandard.Label("Cumflation multiplier (ranges from 0.1x to 20x, affects only cumflation effects): " + CumflationMultiplier);
				CumflationMultiplier = listingStandard.Slider(CumflationMultiplier, 0.1f, 20f);
			}

			listingStandard.Gap(40f);
			listingStandard.CheckboxLabeled("Enable orifice damage", ref IsDamageEnabled, "Allow orifices to be damaged by overly large penetrations.");
			listingStandard.Gap(4f);
			listingStandard.Label("Damage multiplier (ranges from 0.1x to 2x): " + DamageMultiplier);
			DamageMultiplier = listingStandard.Slider(DamageMultiplier, 0.1f, 2f);
			listingStandard.Gap(4f);
			listingStandard.CheckboxLabeled("Enable orifice prolapsing", ref IsProlapseEnabled, "Allow orifices to be prolapsed, which, in extreme cases, may require surgery.");
			listingStandard.End();
		}
	}

	public class LicentiaLabs : Mod
	{
		private readonly Settings settings;

		public LicentiaLabs(ModContentPack content) : base(content)
		{
			settings = GetSettings<Settings>();
		}

		public override string SettingsCategory()
		{
			return "RJW Licentia Labs";
		}

		public override void DoSettingsWindowContents(Rect inRect)
		{
			Settings.DoWindowContents(inRect);
		}
	}
}


code:
using HarmonyLib;
using RimWorld;
using rjw;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Verse;

namespace LicentiaLabs
{
    public static class StretchHelper
	{
		public static void Stretch(SexProps props)
		{
			if (!LicentiaHelper.GetPenetrator(props, out Pawn penetrator, out Pawn recipient))
			{
				// If there is not penetrator involved, then there will be no inflation.
				return;
			}

			if (props.pawn != recipient)
			{
				// RJW calls SatisfyPersonal 2 times with props.pawn pointing to different participant.
				return;
			}

			if (IsElasticised(recipient))
			{
				// Elasticised recipients cannot be stretched.
				return;
			}

			// Collect some information about the act beforehand, namely the dimensions of the hediffs being
			// used for penetrating and for being penetrated.
			// Collect penetrator dimensions.
			var adjustedPenetratorDimensions = GetAdjustedHediffDimensions(penetrator, LicentiaHelper.TryGetPawnPenisHediff(penetrator));
			if (adjustedPenetratorDimensions.EnumerableNullOrEmpty())
            {
				return;
            }

			// Collect orifice dimensions.
			var pawnOrifices = new List<Hediff>();
			if (props.sexType == xxx.rjwSextype.DoublePenetration || props.sexType == xxx.rjwSextype.Vaginal)
            {
				pawnOrifices.Add(LicentiaHelper.TryGetPawnVaginaHediff(recipient));
			}
			if (props.sexType == xxx.rjwSextype.DoublePenetration || props.sexType == xxx.rjwSextype.Anal)
			{
				pawnOrifices.Add(LicentiaHelper.TryGetPawnAnusHediff(recipient));
			}
			if (pawnOrifices.EnumerableNullOrEmpty())
			{
				return;
            }
			var adjustedOrificeDimensions = GetAdjustedHediffDimensions(recipient, pawnOrifices, float.MaxValue / 2);
			if (adjustedOrificeDimensions.EnumerableNullOrEmpty())
			{
				return;
			}

			// Map penetrators (not their dimensions, just the penetrator Hediffs themselves) to orifices.
			var targetMappings = MapOrificesToPenetrators(adjustedPenetratorDimensions.Keys, pawnOrifices);

			// Determine what the new severity increase should be.
			foreach (var mapping in targetMappings)
            {
				var orifice = mapping.Key;
				if (LicentiaHelper.IsArtificial(orifice))
				{
					// Artificial orifices cannot be stretched.
					continue;
                }

				// Collect the penetrator dimensions.
				var penetrators = mapping.Value;
				var penetratorDimensions = CalculateTotalPenetratorDimensions(penetrators, adjustedPenetratorDimensions);
				var orificeDimensions = adjustedOrificeDimensions.TryGetValue(orifice);

				var isStretched = DoesStretchingOccur(penetratorDimensions, orificeDimensions, props.isRape, out var isPermanentlyStretched);
				if (!isStretched.Item1 && !isStretched.Item2)
				{
					// Orifice is larger than penetrators in all dimensions.
					continue;
				}

				// Find the larger of the permanent severities for the orifice to add to the real
				// orifice severity.
				var lengthPermSeverity = StretchOrifice(recipient, isPermanentlyStretched.Item1, isStretched.Item1, orifice, penetratorDimensions.Item1, orificeDimensions.Item1, out float lengthDamage);
				var girthPermSeverity = StretchOrifice(recipient, isPermanentlyStretched.Item2, isStretched.Item2, orifice, penetratorDimensions.Item2, orificeDimensions.Item2, out float girthDamage);
				var greaterPermSeverity = Math.Max(lengthPermSeverity, girthPermSeverity);
				if (greaterPermSeverity > 0f)
				{
					orifice.Severity += greaterPermSeverity;
				}

				GiveStretchThoughts(recipient);

				if (DamageHelper.IsPawnInvulnerable(recipient) || orifice.Part == null)
				{
					continue;
				}

				if (Settings.IsDamageEnabled)
                {
					if (lengthDamage > 0f)
					{
						if (Settings.IsDamageEnabled)
						{
							if (orifice.def.defName.Contains("Vagina"))
							{
								DamageHelper.ApplyDamage(recipient, orifice.Part, Licentia.HediffDefs.CervixBruise, lengthDamage);
							}
							else
							{
								DamageHelper.ApplyDamage(recipient, orifice.Part, Licentia.HediffDefs.StretchTear, lengthDamage);
							}
						}
					}
					if (girthDamage > 0f)
					{
						DamageHelper.ApplyDamage(recipient, orifice.Part, Licentia.HediffDefs.StretchTear, girthDamage);
					}
				}

				float higherDamage = Math.Max(lengthDamage, girthDamage);
				if (Settings.IsProlapseEnabled && higherDamage > 0f)
				{
					// When length damage is below 1.5f, apply a regular prolapse. If over 1.5f, apply an extreme
					// prolapse. Severity will scale linearly from 0f to 1f for regular prolapse severity, while
					// extreme prolapse severity will be fixed to 1.0f.
					if (higherDamage <= 1.5f)
					{
						DamageHelper.ApplyDamage(recipient, orifice.Part, Licentia.HediffDefs.Prolapse, higherDamage * 2f / 3f);
					}
					else if (higherDamage <= 2.5f)
                    {
						// To lessen the number of extreme prolapses, the threshold for damage can go up to 2.5f
						// but the receipient Pawn will receive a regular prolapse instead at max severity.
						DamageHelper.ApplyDamage(recipient, orifice.Part, Licentia.HediffDefs.Prolapse, 1f);
					}
					else
					{
						DamageHelper.ApplyDamage(recipient, orifice.Part, Licentia.HediffDefs.ExtremeProlapse, 1f);
					}
				}
			}
		}

		/// <summary>
		/// Checks to see if the given <c>Pawn</c> is a valid stretch target.
		/// </summary>
		/// <param name="stretchee">the <c>Pawn</c> to be checked</param>
		/// <returns><c>true</c> if the <paramref name="stretchee"/> has used an elasticise serum recently,
		/// <c>false</c> otherwise.</returns>
		public static bool IsElasticised(Pawn stretchee)
		{
			return stretchee?.health.hediffSet.GetFirstHediffOfDef(Licentia.HediffDefs.Elasticised) != null;
		}

		/// <summary>
		/// Calculates the lengths and girths of each genital <c>Hediff</c> specified in the <paramref name="hediffs"/>
		/// list. It is assumed that each <c>Hediff</c> belongs to the provided <paramref name="pawn"/>.
		/// If lengths and girths are not available, then <c>Hediff</c> severity will be used as the
		/// substitute.
		/// </summary>
		/// <param name="pawn">the <c>Pawn</c> that is assumed to own all of the <c>Hediff</c>s</param>
		/// <param name="hediffs">the <c>Hediff</c>s containing the <c>Hediff</c>s to be calculated</param>
		/// <param name="defaultValue">the value to be used if no dimension value could be obtained</param>
		/// <returns>A Dictionary of <c>Hediff</c>s to tuples of the <c>Hediff</c>s' lengths and girths in
		/// that order.</returns>
		public static Dictionary<Hediff, (float, float)> GetAdjustedHediffDimensions(Pawn pawn, IEnumerable<Hediff> hediffs, float defaultValue = float.MinValue)
        {
			Dictionary<Hediff, (float, float)> hediffDimensions = new Dictionary<Hediff, (float, float)>();

			// Theoretically, a Pawn could have both enlargement and diminishment effects active at the same time.
			var adjustmentValue = 0f;
			adjustmentValue += pawn?.health.hediffSet.GetFirstHediffOfDef(Licentia.HediffDefs.Enlarged) != null ? 0.2f : 0f;
			adjustmentValue += pawn?.health.hediffSet.GetFirstHediffOfDef(Licentia.HediffDefs.Diminished) != null ? -0.2f : 0f;

			// Calculate the length and girth of each Hediff.
			foreach (var hediff in hediffs)
			{
				// Some HediffComps have varying stretch amounts.
				var stretchVariance = hediff.def.CompProps<CompProperties_LicentiaPart>()?.stretchVariance;
				if (stretchVariance.HasValue)
				{
					adjustmentValue += Rand.Range(0, (float)stretchVariance) / 2f;
				}

				// Try to use length and girth if at all possible, and use Severity only as a fallback.
				CompHediffBodyPart rjwHediff = hediff.TryGetComp<CompHediffBodyPart>();
				if (rjwHediff != null)
                {
                    if (!PartSizeExtension.TryGetLength(hediff, out float length)) length = defaultValue == float.MinValue ? hediff.Severity : defaultValue;
                    length += adjustmentValue;
					if (!PartSizeExtension.TryGetGirth(hediff, out float girth)) girth = defaultValue == float.MinValue ? hediff.Severity : defaultValue;
					girth += adjustmentValue;
					if (length < 0 || girth < 0)
                    {
						continue;
                    }
					hediffDimensions.Add(hediff, (length, girth));
                }
				else
				{
					if (hediff.Severity < 0)
                    {
						continue;
                    }
					hediffDimensions.Add(hediff, (hediff.Severity, hediff.Severity));
				}
			}

			return hediffDimensions;
		}

		/// <summary>
		/// Tries to pair penetrators with orifices. If there are more penetrators than there are orifices,
		/// then the targets will be doubled up and later may have a chance of multiple penetrators targeting
		/// the same orifice, increasing effective girth when calculated for stretching.
		/// </summary>
		/// <param name="penetrators">the penetrator <c>Hediff</c>s</param>
		/// <param name="orifices">the orifice <c>Hediff</c>s; count must be greater than 0</param>
		/// <returns>A Dictionary mapping orifice <c>Hediff</c>s to one or more penetrator <c>Hediff</c>s</returns>
		public static Dictionary<Hediff, List<Hediff>> MapOrificesToPenetrators(IEnumerable<Hediff> penetrators, IList<Hediff> orifices)
		{
			var penetratorList = penetrators.ToList();
			penetratorList.Shuffle();

			var output = new Dictionary<Hediff, List<Hediff>>();
			var nOrifices = orifices.Count();
			for (int ii = 0; ii < penetratorList.Count(); ++ii)
			{
				var orifice = orifices[ii % nOrifices];
                if (!output.TryGetValue(orifice, out List<Hediff> mappedPenetrators))
                {
                    mappedPenetrators = new List<Hediff>();
                    output.Add(orifice, mappedPenetrators);
				}
				var penetrator = penetratorList[ii];
				mappedPenetrators.Add(penetrator);

				if (ii == nOrifices - 1 && !Settings.IsAllowMultiplePenetratorsPerOrifice)
                {
					break;
                }
			}
			return output;
		}

		/// <summary>
		/// Determines what the final total penetrator dimensions will be. The maximum penetrator length
		/// and some form of summation of girths will be used, meaning that the overall penetrator dimensions
		/// will be that of some aggregate penetrator roughly resembling all of the penetrators used.
		/// </summary>
		/// <param name="penetrators">the <c>Hediff</c>s performing the penetrating</param>
		/// <param name="penetratorDimensions">the Dictionary that will serve as a lookup for dimensions
		/// of each of the <paramref name="penetrators"/></param>
		/// <returns>A tuple of the aggregate penetrator's length and girth in that order.</returns>
		public static (float, float) CalculateTotalPenetratorDimensions(IEnumerable<Hediff> penetrators, Dictionary<Hediff, (float, float)> penetratorDimensions)
		{
			var length = 0f;
			var totalGirth = 0f;
			foreach (var penetrator in penetrators)
			{
				var dimensions = penetratorDimensions.TryGetValue(penetrator);
				if (length == 0f || totalGirth == 0f || true/*Rand.Range(0f, 1f) > 0.5f*/)
				{
					length = Math.Max(length, dimensions.Item1);
					totalGirth += dimensions.Item2;
				}
			}
			return (length, totalGirth);
		}

		/// <summary>
		/// Determines based on preset thresholds whether the orifice being penetrated should be stretched
		/// or not.
		/// </summary>
		/// <param name="penetrator">The aggregate length and girth of the penetrator</param>
		/// <param name="orifice">The length and girth of the receiving orifice</param>
		/// <param name="isRape">lowers the threshold for stretching effects from simple comparison to
		/// modifying the penetrating dimensions to 125% their actual dimensions to simulate the damaging
		/// effects of non-consensual intercourse.</param>
		/// <param name="isPermanentlyStretched">Contains whether the length or girth are valid stretching
		/// candidates</param>
		/// <returns>Whether there was any stretching at all in either length or girth directions.</returns>
		public static (bool, bool) DoesStretchingOccur((float, float) penetrator, (float, float) orifice, bool isRape, out (bool, bool) isPermanentlyStretched)
		{
			// Check for stretching length-wise.
			bool isLengthStretched = penetrator.Item1 * (isRape ? 1.25f : 1f) > orifice.Item1;
			bool isLengthStretchedPerm = Settings.IsPermanentStretchEnabled && penetrator.Item1 * (isRape ? 1.25f : 1f) > orifice.Item1 * 1.5;

			// Check for stretching girth-wise.
			bool isGirthStretched = penetrator.Item2 * (isRape ? 1.25f : 1f) > orifice.Item2;
			bool isGirthStretchedPerm = Settings.IsPermanentStretchEnabled && penetrator.Item2 * (isRape ? 1.25f : 1f) > orifice.Item2 * 1.5;

			isPermanentlyStretched = (isLengthStretchedPerm, isGirthStretchedPerm);
			return (isLengthStretched, isGirthStretched);
		}

		/// <summary>
		/// Adds a "Stretched" <c>Hediff</c> to the orifice currently being stretched. Also returns the
		/// severity increase for the orifice if permanent stretching has taken place and is enabled. One
		/// of the two parameters <paramref name="isPermanentlyStretched"/> or <paramref name="isStretched"/>
		/// must be true for this function to have any effect.
		/// </summary>
		/// <param name="penetratedPawn">the <c>Pawn</c> receiving the penetration</param>
		/// <param name="isPermanentlyStretched">if true, then permanent stretching is to be performed</param>
		/// <param name="isStretched">if true, then temporary stretching is to be performed</param>
		/// <param name="orifice">the orifice <c>Hediff</c> being stretched</param>
		/// <param name="penetratorDimension">the current dimension being checked (length or girth) for
		/// the penetrator</param>
		/// <param name="orificeDimension">the current dimension being checked (length or girth) for
		/// the receiving orifice</param>
		/// <param name="uniqueDamage">the damage to be uniquely received by the penetrated orifice; scales
		/// from 0f to 2f when the adjusted dimension ratio between penetrator and orifice was >150%.</param>
		/// <returns></returns>
		public static float StretchOrifice(Pawn penetratedPawn, bool isPermanentlyStretched, bool isStretched, Hediff orifice, float penetratorDimension, float orificeDimension, out float uniqueDamage)
		{
			float severityIncrease = 0f;
			Hediff hediff = null;
			if (isPermanentlyStretched)
			{
				// Attempt to increase the severity of the stretched orifice, as that is the only way that the
				// orifice can increase in size (and prevent stretching again).
				// TODO: Could use an algorithm for directly converting dimension differences into severity.
				var ratio = Math.Min(penetratorDimension / orificeDimension / 1.5f, 0.12f) * Rand.Range(0.85f, 1.15f); // The maximum an orifice can stretch by is 10%-14% its current severity.
				GenderHelper.ChangeSex(penetratedPawn, () =>
				{
					var stretchAmount = orifice.Severity * ratio;
					severityIncrease = LicentiaHelper.Clamp(stretchAmount, 0.05f, 0.25f); // Clamp the actual severity increase to prevent rapid changes.
				});

				// Stretching severity will scale from 25% (lasting one day) to 100% (lasting 4 days) depending
				// on how badly the orifice was stretched.
				hediff = HediffMaker.MakeHediff(Licentia.HediffDefs.Stretched, penetratedPawn);
				hediff.Severity = 0.25f + LicentiaHelper.Clamp(0.75f * (ratio / 0.12f), 0.25f, 0.75f);
				penetratedPawn.health.AddHediff(hediff, orifice.Part);

				if (!Settings.IsStretchTextPlayerFactionVisibleOnly || (penetratedPawn.Faction?.IsPlayer ?? false))
				{
					Messages.Message("LL_OrificeStretched".Translate(penetratedPawn, orifice.def.defName.ToLower()), penetratedPawn, MessageTypeDefOf.SilentInput);
				}
			}
			else if (isStretched)
			{
				// If any stretching occurred at all, there will be a minimum of 25% severity for stretching. Mild
				// stretching will last one day.
				hediff = HediffMaker.MakeHediff(Licentia.HediffDefs.Stretched, penetratedPawn);
				hediff.Severity = 0.25f;
				penetratedPawn.health.AddHediff(hediff, orifice.Part);
			}
            else
            {
				// For some reason, this function was called without any stretching taking place.
				uniqueDamage = 0f;
				return 0f;
            }

			// Stretch severity ranges between 0.25f and 1.0f, which means the severity of the damage can
			// range between 0f (regular stretching does not cause tears) and 2.0f, which is 25% of the orifice
			// HP for a >150% stretch.
			// Formula here is a linear relationship: y = 2.6666...x + 0.6666...
			uniqueDamage = Settings.DamageMultiplier * ((2f + 2f / 3f) * hediff?.Severity ?? 0f) - (2f / 3f);
			return severityIncrease;
		}

		/// <summary>
		/// Gives the <paramref name="stretchee"/> a thought about having been stretched. For <c>Pawn</c>s
		/// that enjoy pain or the act of stretching itself, a positive thought will be gained. Otherwise,
		/// the <c>Pawn</c> will gain a negative thought.
		/// </summary>
		/// <param name="stretchee">the <c>Pawn</c> that was stretched and will be receiving a thought</param>
		public static void GiveStretchThoughts(Pawn stretchee)
		{
			if (xxx.is_animal(stretchee))
			{
				return;
			}

			string pawn_quirks = CompRJW.Comp(stretchee).quirks.ToString();
			if (pawn_quirks.Contains("Impregnation fetish") ||
				pawn_quirks.Contains("Teratophile") ||
				pawn_quirks.Contains("Incubator") ||
				pawn_quirks.Contains("Breeder") ||
				xxx.is_zoophile(stretchee))
			{
				stretchee.needs.mood.thoughts.memories.TryGainMemory(Licentia.ThoughtDefs.GotStretchedKinky);
			}
			else if (xxx.is_masochist(stretchee))
			{
				stretchee.needs.mood.thoughts.memories.TryGainMemory(Licentia.ThoughtDefs.GotStretchedMasochist);
			}
			else
			{
				stretchee.needs.mood.thoughts.memories.TryGainMemory(Licentia.ThoughtDefs.GotStretched);
			}
		}
	}

	public static class DamageHelper
	{
		public static bool IsPawnInvulnerable(Pawn pawn)
		{
			return pawn?.health.hediffSet.GetFirstHediffOfDef(Licentia.HediffDefs.Untearable) != null;
		}

		public static void ApplyDamage(Pawn pawn, BodyPartRecord part, HediffDef damageType, float damage)
		{
			if (part == null)
			{
				return;
			}

			var hediff = HediffMaker.MakeHediff(damageType, pawn);
			hediff.Severity = damage;
			pawn.health.AddHediff(hediff, part);
		}
	}

	[StaticConstructorOnStartup]
	internal static class First
	{
		static First()
		{
			var har = new Harmony("LL");
			har.PatchAll(Assembly.GetExecutingAssembly());
		}
	}

	[HarmonyPatch(typeof(SexUtility))]
	[HarmonyPatch("SatisfyPersonal")]
	[HarmonyPatch(new Type[] { typeof(SexProps), typeof(float) })]
	public static class StretchPatch
	{
		[HarmonyPostfix]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Parameter is provided by RJW through Harmony.")]
		public static void SatisfyPersonal(SexProps props, float satisfaction)
		{
			if (props.isCoreLovin || !Settings.IsStretchingEnabled || props.pawn?.Dead == true || props.partner == null || props.partner.Dead == true)
            {
				return;
            }

			StretchHelper.Stretch(props);
		}
	}
}


code:
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Lib.Harmony" version="2.0.2" targetFramework="net472" />
</packages>


code:
### Compiled Python detritus
__pycache__/
*.py[cod]

### IDE's

## IntelliJ and related IDE's (RubyMine, PhpStorm, AppCode, PyCharm)
*.iml

# Directory-based project format:
.idea/

## Eclipse
*.pydevproject
.metadata
.gradle
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath

# Eclipse Core
.project

# External tool builders
.externalToolBuilders/

# Locally stored "Eclipse launch configurations"
*.launch

### Some rules to keep out installation-specific stuff that's generated when Hydrus is run from source
logs/
crash.log

### Virtual Env
venv/

path: .gitignore

license: wtfpl


code:
## Hydrus Network (REWRITE)

# PROJECT STATUS: UNBUILDABLE/UNRUNNABLE

This is a project I'm doing to warm up my Python and general programming skills. Currently I'm just reorganizing the various helper scripts into proper modules and compressing some code, but once that stage is complete I'm going to look into further reducing reused code and other little optimizations.

# TODO
- [ ] Refactor submodules out of Actions module
- [ ] See if GUI's submodules can be organized further
- [ ] Figure out if Local submodules are needed or if they can be merged into main modules
- [x] Seperate function/class imports from constants imports
- [ ] Put some constants into dictionaries
- [ ] Rename Hydrus module to Common, and refactor some submodules out of it
- [x] Fix the lib.GUI.GUI script
- [ ] Merge upstream patches
- [ ] Work on Test module
- [ ] Get program running

# MAYBE
- [ ] Create branch with unchanged files compared to upstream other than module references

## Attribution

Hydrus uses a number of the Silk Icons by Mark James at famfamfam.com.

Check out the original developer's pages:

* [homepage](http://hydrusnetwork.github.io/hydrus/)
* [email](mailto:hydrus.admin@gmail.com)
* [8chan board](https://8ch.net/hydrus/index.html)
* [twitter](https://twitter.com/hydrusnetwork)
* [tumblr](http://hydrus.tumblr.com/)
* [discord](https://discord.gg/3H8UTpb)
* [patreon](https://www.patreon.com/hydrus_dev)


code:
If you are reading this, you probably extracted rather than installed, so you were not prompted to read the help.

The hydrus client can do a lot, so do please go into the help folder and open up index.html so you don't get lost. Thanks!

I use a number of the Silk Icons by Mark James at famfamfam.com.


code:
FFMPEG is used by hydrus for many video purposes. Some releases of hydrus include an FFMPEG static executable ('ffmpeg' or 'ffmpeg.exe') in this directory. If hydrus does not find the executable here, it will attempt to run it from your normal path.

If you do not have FFMPEG installed, or would prefer to run it from this location, you can get a static build at http://ffmpeg.org/download.html


code:
.open client.db
.out my_options.sql
.print .open client.db\r\n
.print delete from options;\r\n
.print delete from json_dumps where dump_type = 22;\r\n
.mode insert options
select * from options;
.mode insert json_dumps
select * from json_dumps where dump_type = 22;


code:
.print The subscriptions will lose their tag import options, so make sure to check them once they are imported back in.
.open client.db
.out my_subscriptions.sql
.print .open client.db\r\n
.mode insert json_dumps_named
select * from json_dumps_named where dump_type = 3;


code:
If you are getting weird but serious-sounding errors like 'database image is malformed', your database may be corrupt!

Ninety-nine times out of a hundred, corrupted databases happen because of hardware failure, usually the hard drive writing bad sectors due to a power cut or old age. This is a serious problem.

first, shut everything down

Close the client immediately.

If you are on Windows, open task manager (Ctrl+Shift+Esc) and wait for client.exe to disappear from the list. If it takes more than a couple of minutes, forcibly close it with task manager.

check integrity

Then check your hard drive's integrity.

On Windows, go Start->Run (Win+R) and type 'cmd'. Type chkdsk into the new window and wait for it to scan your drive.

If it finds many problems, then your drive has been compromised in some way and you should view it as unreliable. If it is an old drive, you should think about buying a replacement. The exception to 'buy a new drive' is if the existing one is new, otherwise works well, and you can trace the error to a specific event, such as an unprotected power surge during a storm that violently reset your computer. The other exception is if you cannot afford it. :/

On Windows, tell chkdsk to fix the problems it found by running it again with the /F modifier, like 'chkdsk /F'.

Another good tool is CrystalDiskInfo, which checks hard drive health at the physical level. If your drive is having trouble reading or seeking or it is piling up uncorrectable sectors, it is time to move everything off!

If your hard drive is fine, please send me the details! If it could be my code breaking things, I want to know asap!

now what?

If you have a recent backup of your client, it is probably a good idea just to restore from that. A lot of hard drive errors cannot be recovered from, so if your backup is good, this may be the only way to recover the lost information. Make a copy of your corrupted database somewhere (just in case of other problems or it turns out the backup is also corrupt) and then restore your backup.

If you do not have a backup, you'll have to try recovering what data you can from the corrupted db.

First of all, make a _new_ backup of the corrupted db, just in case something goes wrong with the recovery and we need to try again. You can just copy the client*.db files, but having a copy of all your media files is a great idea if you have the time and space. Please check the help for more information on making backups--they are important!

fix the problem

One way to fix common small problems is to instruct the database to copy itself completely. When it comes across damaged data, it will attempt to cleanly correct or nullify the broken links. The new copy often works better than the original.

So: open the SQLite shell, which should be in the db directory, called sqlite3 or sqlite3.exe. Type:
.open client.db
PRAGMA integrity_check;

And when you are ready to close the shell cleanly, go:
.exit

The integrity check doesn't correct anything, but it lets you know the magnitude of the problem: if only a couple of issues are found, you may be in luck. There are several .db files in the database, and client.db may not be the one broken. If you do not know which file is already broken, try opening the other files in new shells to figure out the extent of the damage. This is the same as with client.db, like so:
(Do this in fresh new shells of sqlite3)

.open client.caches.db
PRAGMA integrity_check;
.exit

.open client.master.db
PRAGMA integrity_check;
.exit

.open client.mappings.db
PRAGMA integrity_check;
.exit

client.mappings.db is usually the largest and busiest file in most people's databases, so it is a common victim.

If the errors do not look too bad, then try cloning a fresh copy of the broken db file like so:
.open client.db
.clone client_new.db

And wait a bit. It'll report its progress as it tries to copy your db's info to a new, cleaner db. Remember to go '.exit' once it is done to close the shell neatly.

If the clone operation is successful, rename client.db to client_old.db and client_new.db to client.db. If the damage is in a different file, do a similar operation:
.open client.caches.db
.clone client.caches_new.db
.exit

.open client.master.db
.clone client.master_new.db
.exit

.open client.mappings.db
.clone client.mappings_new.db
.exit

Do not delete your original files for now. Just rename them to 'old' and move them somewhere safe. Make sure your new cloned files are all named right and then try running the client!

If the clone is unsuccessful or it still does not run correctly, contact me (Hydrus Dev) and I'll help you with the next steps. In the worst case, we will manually extract what you can to a new db.

If you recover ok but still get problems, please contact me. Check install_dir/help/contact.html for ways to do this.

path: db/help my db is broke.txt


code:
If your client seems to hang on the database startup phase, especially after a previously bad shutdown (like a power cut), it could be that the db needs to 'heal' itself a bit. It can take several minutes, sometimes even more, to clean the bad shutdown up.

So, if your client seems suddenly to take a very long time to start up, just sitting on the splash screen, go make a coffee and give it time--it is probably just sorting itself out, not broken.

You can double-check this by looking at the client executable in your OS's Task Manager (Ctrl+Shift+Esc on Windows). If it is doing some CPU/HDD, there is no need to kill the process.

Please contact hydrus_dev if it really does seem stuck (say, no progress after an hour, or if CPU/HDD activity completely drops to nothing), or if every startup is delayed like this.

One possible cause of delayed startup every time (usually a delay _before_ the splash screen appears) is overly paranoid virus scanners rechecking all of hydrus every time it starts. To relieve this, please check your anti-virus software's options and make sure your hard drive is defragged.

path: db/help my db is not booting.txt


code:
The databases are sqlite. You can look inside them with many programs. I like to use:

SQLiteStudio from http://sqlitestudio.pl/.
The SQLite command-line shell program from https://www.sqlite.org/download.html.

I include the command-line shell with my normal frozen releases. I used to bundle SQLiteStudio as well, but they rewrote their gui and the program got too large to tuck in.

Feel free to explore how hydrus works, and if you have any questions or comments, don't hesitate to contact me!

path: db/how to see inside the db.txt


code:
If your db is completely broken and you need to extract some important data, please check out the emergency extract scripts. To use them, put your old database, the sqlite3 executable, and the script in the same folder and feed the script into sqlite3, like so:

sqlite3 < extract_subscriptions.sql

This will connect to the database and copy your subscriptions to the new file my_subscriptions.sql, which you can then move and import to a new db folder in the same way:
sqlite3 < my_subscriptions.sql

Some things are difficult to copy over at this basic level. Your tag options and anything else service-specific will be lost or reset back to default.

path: db/using the extract scripts.txt


code:
If running the client executable does nothing or gives you an odd error before dumping out, here are some common fixes to try:

1. Look for a 'crash.log' in your 'db' directory. Failing that, is there a 'client - [date].log' file? Does it have an error in it? If there is something, please send it in to me, hydrus_dev (see contact.html in the help directory for my contact details).

2. Some anti-virus program updates falsely detect that one of the dlls or other files in the client is bad and quietly quarantine them. Please check your anti-virus logs or compare your install directory with the 'extract only' release archive to see if there are missing files. Avast has done this several times. Instances of this are useful to know about as several users usually get hit by the same thing at the same time. Please feel free to also start a conversation if you just want to double-check it is a false-positive after all.

3. Extract a fresh 'extract only' client to your desktop and try running it. If an empty and new client boots but your existing client doesn't, that suggests there is either a problem with your database or a conflict with some older dlls from a previous install. A database problem will typically be reported in one of the log files, so you might like to try making a 'clean' install.

Making a clean install:
Delete everything in your install directory except the 'db' directory. The 'db' directory is where your settings and files are kept, so leave it alone! Then just reinstall or reextract the latest release.

4. Check your permissions and hard drive health. For instance, sometimes when a hard drive has a fault, Windows sets it to 'dirty' mode, which causes all sorts of problems. Linux and OS X have presented their own permissions headaches. A default client works in 'portable' mode and needs read and write access to its 'db' folder.

5. Going forward, please consider making backups before you update the software. If a future update has a serious problem, it is then easy to rollback to the working version while we figure out a fix!

path: help my client will not boot.txt


code:
		<title>access keys</title>
		<link href="hydrus.ico" rel="shortcut icon" />
		<link href="style.css" rel="stylesheet" type="text/css" />

first off

I have purposely not pre-baked this into the client's install. You have to put it in yourself. The client won't connect anywhere until you tell it to.

access keys

If you don't understand this, you can just go help->i don't know what I am doing->just set up some repositories for me, please and you _should_ be all set up automatically.

I run a public tag repository that you are welcome to contribute to. I also run a read-only file repository, which is mostly just an old thing for fun, but you are welcome to search and download from it to get a feel for the interface. The files on my file repo are appropriately tagged in my tag repo.

To add or edit a new repository, hit services->manage services and click the add or edit button:

It is worth testing the address and access key just to make sure your firewall and key is all correct.

Here's the info so you can copy it:

public tag repository: 4a285629721ca442541ef2c15ea17d1f7f7578b0c3f4f5f2a05f8f0ab297786f@hydrus.no-ip.org:45871

read-only file repository: 8f8a3685abc19e78a92ba61d84a0482b1cfac176fd853f46d93fe437a95e40a5@hydrus.no-ip.org:45872

Tags are rich, cpu-intensive metadata. My repository has hundreds of millions of mappings, and your client will eventually download and index them all. It will take a few hundred MB and hours of total processing time to fully synchronise. It is best left to work on this in small pieces in the background, either during idle time or shutdown time, so unless you are an advanced user, just leave it to download and process on its own--it usually takes a couple of weeks to quietly catch up.

If you are an advanced user, you can manually export (services->review services->export updates if advanced mode is on) and import (services->import repository update files) the update files a repository gives your client to save you redownloading them elsewhere. If you want to set up a PTR client a bit quicker than usual (and you understand the processing routine and the delays it can expeience if it encounters thousands of new update files at once), check the update archive here.


code:
		<title>adding new downloaders</title>
		<link href="hydrus.ico" rel="shortcut icon" />
		<link href="style.css" rel="stylesheet" type="text/css" />

all downloaders are user-creatable and -shareable

Since the big downloader overhaul, all downloaders can be created, edited, and shared by any user. Creating one from scratch is not simple, and it takes a little technical knowledge, but importing what someone else has created is easy.

Hydrus objects like downloaders can sometimes be shared as data encoded into png files, like this:

This contains all the information needed for a client to add a realbooru tag search entry to the list you select from when you start a new download or subscription.

You can get these pngs from anyone who has experience in the downloader system. An archive is appearing here:

The main repo

The easy-import pngs

To 'add' the easy-import pngs to your client, hit network->downloaders->import downloaders. A little image-panel will appear onto which you can drag-and-drop these png files. The client will then decode and go through the png, looking for interesting new objects and automatically import and link them up without you having to do any more. Your only further input on your end is a 'does this look correct?' check right before the actual import, just to make sure there isn't some mistake or other glaring problem.

Objects imported this way will take precedence over existing functionality, so if one of your downloaders breaks due to a site change, importing a fixed png here will overwrite the broken entries and become the new default.