mirror of
https://gitgud.io/lutepickle/rjw_menstruation.git
synced 2024-08-14 22:46:52 +00:00
397 lines
20 KiB
C#
397 lines
20 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.IsAnimal() && !Configurations.EnableAnimalCycle) 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.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) || partner.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) || 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.IsAnimal() && !Configurations.EnableAnimalCycle) return true;
|
|
HediffComp_Menstruation comp;
|
|
if (pawn.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) || partner.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) || 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 (!Configurations.EnableAnimalCycle && pawn.IsAnimal()) 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 == null || 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.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) || !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 == null || 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 == null || MinimumAttractivenessToHookup.FieldType != typeof(float)) throw new System.InvalidOperationException("MinimumAttractivenessToHookup not found");
|
|
if (MinimumRelationshipToHookup == null || 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) || pawn == partner) return;
|
|
if (sextype != xxx.rjwSextype.Vaginal && sextype != xxx.rjwSextype.DoublePenetration) return;
|
|
if (__instance.Sexprops.usedCondom) return;
|
|
if (!Impregnate_Patch.InteractionCanCausePregnancy(__instance.Sexprops)) 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), nameof(PawnCapacityWorker_Fertility.CalculateCapacityLevel))]
|
|
public static class PawnCapacityWorker_Fertility_Patch
|
|
{
|
|
private static float GetFertilityStatOrOne(Thing thing, StatDef stat, bool applyPostProcess, int cacheStaleAfterTicks)
|
|
{
|
|
Pawn pawn = (Pawn)thing;
|
|
if (pawn.GetMenstruationComps().Any(comp => comp.calculatingOvulationChance))
|
|
return 1.0f;
|
|
else return thing.GetStatValue(stat, applyPostProcess, cacheStaleAfterTicks);
|
|
}
|
|
private static readonly MethodInfo GetStatValue = AccessTools.Method(typeof(StatExtension), "GetStatValue", new System.Type[] { typeof(Thing), typeof(StatDef), typeof(bool), typeof(int) });
|
|
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
|
{
|
|
if (GetStatValue == null || GetStatValue.ReturnType != typeof(float)) throw new System.InvalidOperationException("GetStatValue not found");
|
|
foreach (CodeInstruction instruction in instructions)
|
|
{
|
|
if (instruction.Calls(GetStatValue))
|
|
yield return CodeInstruction.Call(typeof(PawnCapacityWorker_Fertility_Patch), nameof(GetFertilityStatOrOne));
|
|
else yield return instruction;
|
|
}
|
|
}
|
|
}
|
|
}
|