using HarmonyLib; using RimWorld; using rjw; using rjw.Modules.Interactions.Enums; using rjw.Modules.Interactions.Objects; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using UnityEngine; using Verse; namespace RJW_Menstruation { [HarmonyPatch(typeof(PregnancyHelper), nameof(PregnancyHelper.impregnate))] public static class Impregnate_Patch { public static bool Prefix(SexProps props) { xxx.rjwSextype sextype = props.sexType; Pawn pawn = props.pawn; Pawn partner = props.partner; if (sextype != xxx.rjwSextype.Vaginal && sextype != xxx.rjwSextype.DoublePenetration) return true; if (!partner.ShouldCycle()) return true; if (!InteractionCanCausePregnancy(props)) return false; List pawnparts = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_genitalsBPR(pawn)); HediffComp_Menstruation comp; if (pawn.HasImpregnationFetish() || partner.HasImpregnationFetish() || partner.IsInEstrus()) comp = partner.GetFertileMenstruationComp(); else comp = partner.GetRandomMenstruationComp(); if (comp == null) return true; if (Genital_Helper.has_penis_fertile(pawn, pawnparts) && PregnancyHelper.CanImpregnate(pawn, partner, sextype)) { PregnancyHelper.DoImpregnate(pawn, partner); return false; } else if (Genital_Helper.has_ovipositorM(pawn, pawnparts)) { comp.CumIn(pawn, Rand.Range(0.75f, 4.5f) * pawn.BodySize, pawn.SterileGenes() ? 0.0f : 1.0f); } else comp.CumIn(pawn, pawn.GetCumVolume(pawnparts), 0); return true; } public static void Postfix(SexProps props) { Pawn pawn = props.partner; if (props.sexType != xxx.rjwSextype.MechImplant && pawn.health.hediffSet.GetFirstHediff() == null) return; // The existing pregnancies might have been destroyed, so go through see if any new mech pregnancies need to be picked up foreach (HediffComp_Menstruation comp in pawn.GetMenstruationComps()) { _ = comp.Pregnancy; // get_Pregnancy will do any removals comp.TakeLoosePregnancy(); } } /// /// Checks if pregnancy can happen based on the interaction def /// This is needed for futanari sex, but should work for everyone /// /// /// Interaction can result in pregnancy public static bool InteractionCanCausePregnancy(SexProps props) { InteractionWithExtension interaction = rjw.Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(props.dictionaryKey); if (!interaction.HasInteractionTag(InteractionTag.Fertilization)) return false; bool usesPawnsPenis; bool usesPartnersVagina; if (!props.isReceiver) { usesPawnsPenis = interaction.DominantHasTag(GenitalTag.CanPenetrate); usesPartnersVagina = interaction.SubmissiveHasFamily(GenitalFamily.Vagina); } else { usesPawnsPenis = interaction.SubmissiveHasTag(GenitalTag.CanPenetrate); usesPartnersVagina = interaction.DominantHasFamily(GenitalFamily.Vagina); } return usesPawnsPenis && usesPartnersVagina; } } [HarmonyPatch(typeof(PregnancyHelper), nameof(PregnancyHelper.DoImpregnate))] public static class DoImpregnate_Patch { public static bool Prefix(Pawn pawn, Pawn partner) // partner has vagina { if (!partner.ShouldCycle()) return true; HediffComp_Menstruation comp; if (pawn.HasImpregnationFetish() || partner.HasImpregnationFetish() || partner.IsInEstrus()) comp = partner.GetFertileMenstruationComp(); else comp = partner.GetRandomMenstruationComp(); if (comp == null) { if (Configurations.Debug) ModLog.Message("used original rjw method: Comp missing"); return true; } else if (AndroidsCompatibility.IsAndroid(pawn) && !AndroidsCompatibility.AndroidPenisFertility(pawn)) { comp.CumIn(pawn, pawn.GetCumVolume(), 0); return false; } else comp.CumIn(pawn, pawn.GetCumVolume(), pawn.SterileGenes() ? 0.0f : pawn.health.capacities.GetLevel(xxx.reproduction)); return false; } } [HarmonyPatch(typeof(PregnancyHelper), nameof(PregnancyHelper.CanImpregnate))] public static class CanImpregnate_Patch { private static bool PregnancyBlocksImpregnation(this Pawn pawn, bool _) { if (!pawn.ShouldCycle()) return pawn.IsPregnant(); else if (pawn.GetMenstruationComps().Any()) return false; else return pawn.IsPregnant(); } private static readonly MethodInfo IsPregnant = AccessTools.Method(typeof(PawnExtensions), nameof(PawnExtensions.IsPregnant), new System.Type[] { typeof(Pawn), typeof(bool) }); public static IEnumerable Transpiler(IEnumerable instructions) { if (IsPregnant?.ReturnType != typeof(bool)) throw new System.InvalidOperationException("IsPregnant not found"); foreach (CodeInstruction instruction in instructions) { if (instruction.Calls(IsPregnant)) yield return CodeInstruction.Call(typeof(CanImpregnate_Patch), nameof(PregnancyBlocksImpregnation)); else yield return instruction; } } } [HarmonyPatch(typeof(Hediff_BasePregnancy), nameof(Hediff_BasePregnancy.PostBirth))] public static class RJW_Patch_PostBirth { public static void Postfix(Hediff_BasePregnancy __instance, Pawn mother, Pawn baby) { if (Configurations.EnableBirthVaginaMorph) { // The comp still has the pregnancy attached at this point in the process Hediff vagina = (__instance.GetMenstruationCompFromPregnancy()?.parent) ?? mother.health.hediffSet.hediffs.FirstOrFallback(x => VariousDefOf.AllVaginas.Contains(x.def)); if (vagina == null) return; float morph = Mathf.Max(baby.BodySize - Mathf.Pow(vagina.Severity * mother.BodySize, 2), 0f); vagina.Severity += morph * Configurations.VaginaMorphPower; } } } [HarmonyPatch(typeof(Quirk), nameof(Quirk.IsSatisfiedBy))] public static class IsSatisfiedBy_Patch { public static void Postfix(Quirk __instance, ref bool __result, Pawn pawn, Pawn partner) { // This is stricter than can_impregnate, so quickly filter out scenarios that are negative anyways. if (__result == false || __instance != Quirk.ImpregnationFetish) return; __result = (PregnancyHelper.CanImpregnate(pawn, partner) && (partner.GetMenstruationComps()?.Any(comp => comp.IsDangerDay) ?? true)) || (PregnancyHelper.CanImpregnate(partner, pawn) && (pawn.GetMenstruationComps()?.Any(comp => comp.IsDangerDay) ?? true)); } } [HarmonyPatch(typeof(Quirk), nameof(Quirk.CountSatisfiedQuirks))] public static class CountSatisfiedQuirks_Patch { public static void Postfix(ref int __result, SexProps props) { // Awkward, but it'll have to do Pawn pawn = props.pawn; if (__result == 0 || !pawn.HasImpregnationFetish() || !props.hasPartner()) return; // Check if the existing code would have added the count Pawn partner = props.partner; if (!(PregnancyHelper.CanImpregnate(pawn, partner, props.sexType) || PregnancyHelper.CanImpregnate(partner, pawn, props.sexType))) return; else __result--; if ( (PregnancyHelper.CanImpregnate(pawn, partner, props.sexType) && (partner.GetMenstruationComps()?.Any(comp => comp.IsDangerDay) ?? true)) || (PregnancyHelper.CanImpregnate(partner, pawn, props.sexType) && (pawn.GetMenstruationComps()?.Any(comp => comp.IsDangerDay) ?? true))) __result++; } } [HarmonyPatch(typeof(SexAppraiser), "GetBodyFactor")] public static class GetBodyFactor_Patch { private static float GetNetFertility(Pawn fucker, Pawn fucked) { float fert = fucked.health.capacities.GetLevel(xxx.reproduction); if (fucker.def.defName != fucked.def.defName) { if (RJWPregnancySettings.complex_interspecies) fert *= SexUtility.BodySimilarity(fucker, fucked); else fert *= RJWPregnancySettings.interspecies_impregnation_modifier; } return fert; } public static void Postfix(ref float __result, Pawn fucker, Pawn fucked) { if (fucker.IsInEstrus(true) && PregnancyHelper.CanImpregnate(fucked, fucker)) { __result *= (1f + GetNetFertility(fucker, fucked) / 4); } else if (fucker.IsInEstrus(false) && PregnancyHelper.CanImpregnate(fucked, fucker)) { __result *= (1f + GetNetFertility(fucker, fucked) / 40); } else if (xxx.is_animal(fucker) && fucked.IsInEstrus(true) && PregnancyHelper.CanImpregnate(fucker, fucked)) { __result *= 1.25f; } } } [HarmonyPatch(typeof(CasualSex_Helper), nameof(CasualSex_Helper.roll_to_skip))] public static class Roll_To_Skip_Patch { private static float FuckabilityThreshold(Pawn pawn, Pawn partner) { return (Configurations.EstrusOverridesHookupSettings && pawn.IsInEstrus() && PregnancyHelper.CanImpregnate(partner, pawn)) ? Configurations.EstrusFuckabilityToHookup : RJWHookupSettings.MinimumFuckabilityToHookup; } private static readonly FieldInfo MinimumFuckabilityToHookup = AccessTools.Field(typeof(RJWHookupSettings), nameof(RJWHookupSettings.MinimumFuckabilityToHookup)); public static IEnumerable Transpiler(IEnumerable instructions) { if (MinimumFuckabilityToHookup?.FieldType != typeof(float)) throw new System.InvalidOperationException("MinimumFuckabilityToHookup not found"); bool first_fuckability = true; foreach (CodeInstruction instruction in instructions) { if (instruction.LoadsField(MinimumFuckabilityToHookup)) { // The first load will be for the estrus-haver considering a partner, the second for a pawn considering the estrus-haver yield return new CodeInstruction(first_fuckability ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1); yield return new CodeInstruction(first_fuckability ? OpCodes.Ldarg_1 : OpCodes.Ldarg_0); yield return CodeInstruction.Call(typeof(Roll_To_Skip_Patch), nameof(FuckabilityThreshold)); first_fuckability = false; } else yield return instruction; } } } [HarmonyPatch(typeof(CasualSex_Helper), nameof(CasualSex_Helper.FindBestPartner))] public static class FindBestPartner_Patch { private static float AttractivenessThreshold(Pawn pawn, Pawn partner) { return (Configurations.EstrusOverridesHookupSettings && pawn.IsInEstrus() && PregnancyHelper.CanImpregnate(partner, pawn)) ? Configurations.EstrusAttractivenessToHookup : RJWHookupSettings.MinimumAttractivenessToHookup; } private static float RelationshipThreshold(Pawn pawn, Pawn partner) { return (Configurations.EstrusOverridesHookupSettings && pawn.IsInEstrus() && PregnancyHelper.CanImpregnate(partner, pawn)) ? Configurations.EstrusRelationshipToHookup : RJWHookupSettings.MinimumRelationshipToHookup; } private static readonly FieldInfo MinimumAttractivenessToHookup = AccessTools.Field(typeof(RJWHookupSettings), nameof(RJWHookupSettings.MinimumAttractivenessToHookup)); private static readonly FieldInfo MinimumRelationshipToHookup = AccessTools.Field(typeof(RJWHookupSettings), nameof(RJWHookupSettings.MinimumRelationshipToHookup)); public static IEnumerable Transpiler(IEnumerable instructions) { if (MinimumAttractivenessToHookup?.FieldType != typeof(float)) throw new System.InvalidOperationException("MinimumAttractivenessToHookup not found"); if (MinimumRelationshipToHookup?.FieldType != typeof(float)) throw new System.InvalidOperationException("MinimumRelationshipToHookup not found"); LocalBuilder pawn_index = null; // Like in the last one, we switch the arguments around for the second load bool first_attractiveness = true; bool first_relationship = true; foreach (CodeInstruction instruction in instructions) { // Get where the compiler decided to index the pawn at if (pawn_index == null && instruction.opcode == OpCodes.Stloc_S) // the first stloc.s in the IL is the pawn being loaded out of the list { // a future RJW or compiler update might change this, or maybe another mod's patch pawn_index = (LocalBuilder)instruction.operand; yield return instruction; } else if (instruction.LoadsField(MinimumAttractivenessToHookup)) { if (pawn_index?.LocalType != typeof(Pawn)) throw new System.InvalidOperationException($"pawn_index is not a Pawn ({pawn_index?.LocalType})"); yield return first_attractiveness ? new CodeInstruction(OpCodes.Ldarg_0) : new CodeInstruction(OpCodes.Ldloc_S, pawn_index); yield return first_attractiveness ? new CodeInstruction(OpCodes.Ldloc_S, pawn_index) : new CodeInstruction(OpCodes.Ldarg_0); yield return CodeInstruction.Call(typeof(FindBestPartner_Patch), nameof(AttractivenessThreshold)); first_attractiveness = false; } else if (instruction.LoadsField(MinimumRelationshipToHookup)) { if (pawn_index?.LocalType != typeof(Pawn)) throw new System.InvalidOperationException($"pawn_index is not a Pawn ({pawn_index?.LocalType})"); yield return first_relationship ? new CodeInstruction(OpCodes.Ldarg_0) : new CodeInstruction(OpCodes.Ldloc_S, pawn_index); yield return first_relationship ? new CodeInstruction(OpCodes.Ldloc_S, pawn_index) : new CodeInstruction(OpCodes.Ldarg_0); yield return CodeInstruction.Call(typeof(FindBestPartner_Patch), nameof(RelationshipThreshold)); first_relationship = false; } else yield return instruction; } } } [HarmonyPatch(typeof(JobDriver_Sex), nameof(JobDriver_Sex.PlayCumSound))] public static class Orgasm_Patch { public static void Postfix(JobDriver_Sex __instance) { #if false Pawn pawn = __instance.pawn; foreach (HediffComp_Menstruation comp in pawn.GetMenstruationComps()) { comp.CumIn(pawn, (comp.parent.Severity / 10) * Rand.Range(0.75f, 1.25f), pawn.Label, -5.0f, VariousDefOf.GirlCumFilth); } #endif } } [HarmonyPatch(typeof(JobDriver_Sex), nameof(JobDriver_Sex.SexTick))] public static class SexTick_Patch { private const float fertilePrecummersPercentage = 0.33f; private const float precumRatio = 0.1f; // Relative to ejaculation volume private const float precumFertility = 0.5f; private const float expectedDurationTicks = 2000f * (0.9f - 0.5f) / 2; public static void Postfix(JobDriver_Sex __instance, Pawn pawn, Thing target) { if (!pawn.IsHashIntervalTick(__instance.ticks_between_thrusts)) return; xxx.rjwSextype sextype = __instance.Sexprops.sexType; if (!(target is Pawn partner)) return; if (sextype != xxx.rjwSextype.Vaginal && sextype != xxx.rjwSextype.DoublePenetration) return; if (__instance.Sexprops.usedCondom) return; if (AndroidsCompatibility.IsAndroid(pawn)) return; if (!Impregnate_Patch.InteractionCanCausePregnancy(__instance.Sexprops)) return; if (!partner.ShouldCycle()) return; // Archotech penises have more control. Or something. CompHediffBodyPart penisComp = pawn.GetGenitalsList()?.Find(genital => (genital as Hediff_PartBaseNatural)?.def.defName.ToLower().Contains("penis") ?? false)?.TryGetComp(); if (penisComp == null || Rand.ChanceSeeded(1.0f - fertilePrecummersPercentage, Gen.HashOffset(penisComp.parent.loadID))) return; HediffComp_Menstruation vaginaComp = partner.GetRandomMenstruationComp(); if (vaginaComp == null) return; float precumAmount = pawn.GetCumVolume(penisComp) * precumRatio * __instance.ticks_between_thrusts / expectedDurationTicks; vaginaComp.CumIn(pawn, precumAmount, pawn.SterileGenes() ? 0.0f : precumFertility * pawn.health.capacities.GetLevel(xxx.reproduction), true); } } [HarmonyPatch(typeof(CompHediffBodyPart), nameof(CompHediffBodyPart.updatesize))] public static class Updatesize_Patch { public static void Postfix(CompHediffBodyPart __instance) { HediffComp_Breast comp = __instance.parent.GetBreastComp(); if (comp != null) { __instance.parent.Severity += comp.BreastSizeIncreased; } } } [HarmonyPatch(typeof(PawnCapacityWorker_Fertility), "CalculateAgeImpact")] public static class PawnCapacityWorker_Fertility_Age_Patch { public static void Postfix(ref float __result, Pawn pawn) { if (__result <= 0.0f) return; if (pawn.GetMenstruationComps().Any(comp => comp.CalculatingOvulationChance)) __result = 1.0f; } } }