rjw_menstruation/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/RJW_Patch.cs

388 lines
18 KiB
C#

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<Hediff> 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<Hediff_InsectEgg>() == 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();
}
}
/// <summary>
/// Checks if pregnancy can happen based on the interaction def
/// This is needed for futanari sex, but should work for everyone
/// </summary>
/// <param name="props"></param>
/// <returns>Interaction can result in pregnancy</returns>
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<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> 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<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> 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<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> 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<CompHediffBodyPart>();
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;
}
}
}