2022-10-24 01:02:02 +00:00
using HarmonyLib ;
2022-11-04 17:44:33 +00:00
using RimWorld ;
2022-10-24 01:02:02 +00:00
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 ;
2023-03-18 03:07:00 +00:00
if ( pawn . HasQuirk ( QuirkUtility . Quirks . ImpregnationFetish ) | | partner . HasQuirk ( QuirkUtility . Quirks . ImpregnationFetish ) | | partner . IsInEstrus ( ) )
2022-10-24 01:02:02 +00:00
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 ) )
{
2022-11-16 15:46:02 +00:00
PregnancyHelper . DoImpregnate ( pawn , partner ) ;
2022-10-24 01:02:02 +00:00
return false ;
}
else if ( Genital_Helper . has_ovipositorM ( pawn , pawnparts ) )
{
2022-12-25 17:18:51 +00:00
comp . CumIn ( pawn , Rand . Range ( 0.75f , 4.5f ) * pawn . BodySize , pawn . SterileGenes ( ) ? 0.0f : 1.0f ) ;
2022-10-24 01:02:02 +00:00
}
else comp . CumIn ( pawn , pawn . GetCumVolume ( pawnparts ) , 0 ) ;
return true ;
}
public static void Postfix ( SexProps props )
{
Pawn pawn = props . partner ;
2022-10-24 02:23:00 +00:00
if ( props . sexType ! = xxx . rjwSextype . MechImplant & & pawn . health . hediffSet . GetFirstHediff < Hediff_InsectEgg > ( ) = = null ) return ;
2022-10-24 01:02:02 +00:00
// 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>
2022-12-25 19:46:01 +00:00
public static bool InteractionCanCausePregnancy ( SexProps props )
2022-10-24 01:02:02 +00:00
{
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 ;
}
}
2022-11-16 15:46:02 +00:00
[HarmonyPatch(typeof(PregnancyHelper), nameof(PregnancyHelper.DoImpregnate))]
2022-12-14 23:56:40 +00:00
public static class DoImpregnate_Patch
2022-10-24 01:02:02 +00:00
{
public static bool Prefix ( Pawn pawn , Pawn partner ) // partner has vagina
{
if ( partner . IsAnimal ( ) & & ! Configurations . EnableAnimalCycle ) return true ;
HediffComp_Menstruation comp ;
2023-03-18 03:07:00 +00:00
if ( pawn . HasQuirk ( QuirkUtility . Quirks . ImpregnationFetish ) | | partner . HasQuirk ( QuirkUtility . Quirks . ImpregnationFetish ) | | partner . IsInEstrus ( ) )
2022-10-24 01:02:02 +00:00
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 ;
}
2022-11-04 17:44:33 +00:00
else comp . CumIn ( pawn , pawn . GetCumVolume ( ) , pawn . SterileGenes ( ) ? 0.0f : pawn . health . capacities . GetLevel ( xxx . reproduction ) ) ;
2022-10-24 01:02:02 +00:00
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 ( ) ;
}
2023-03-07 00:53:22 +00:00
private static readonly MethodInfo IsPregnant = AccessTools . Method ( typeof ( PawnExtensions ) , nameof ( PawnExtensions . IsPregnant ) , new System . Type [ ] { typeof ( Pawn ) , typeof ( bool ) } ) ;
2022-10-24 01:02:02 +00:00
public static IEnumerable < CodeInstruction > Transpiler ( IEnumerable < CodeInstruction > instructions )
{
2022-11-26 23:20:52 +00:00
if ( IsPregnant = = null | | IsPregnant . ReturnType ! = typeof ( bool ) ) throw new System . InvalidOperationException ( "IsPregnant not found" ) ;
2023-03-07 00:53:22 +00:00
foreach ( CodeInstruction instruction in instructions )
2022-10-24 01:02:02 +00:00
{
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
2023-03-06 03:24:23 +00:00
Hediff vagina = ( __instance . GetMenstruationCompFromPregnancy ( ) ? . parent ) ? ? mother . health . hediffSet . hediffs . FirstOrFallback ( x = > VariousDefOf . AllVaginas . Contains ( x . def ) ) ;
2022-10-24 01:02:02 +00:00
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 ;
2023-03-18 03:07:00 +00:00
if ( __result = = 0 | | ! pawn . HasQuirk ( QuirkUtility . Quirks . ImpregnationFetish ) | | ! props . hasPartner ( ) ) return ;
2022-10-24 01:02:02 +00:00
// 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 )
{
2022-11-26 23:20:52 +00:00
if ( MinimumFuckabilityToHookup = = null | | MinimumFuckabilityToHookup . FieldType ! = typeof ( float ) ) throw new System . InvalidOperationException ( "MinimumFuckabilityToHookup not found" ) ;
2022-10-24 01:02:02 +00:00
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 )
{
2022-11-26 23:20:52 +00:00
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" ) ;
2022-10-24 01:02:02 +00:00
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
}
}
2022-12-25 19:46:01 +00:00
[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 ) ;
}
}
2022-10-24 01:02:02 +00:00
[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 ;
}
}
}
2023-03-07 00:53:22 +00:00
[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 ;
2023-05-19 05:37:31 +00:00
if ( pawn . GetMenstruationComps ( ) . Any ( comp = > comp . CalculatingOvulationChance ) )
2023-03-07 00:53:22 +00:00
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 ;
}
}
}
2022-10-24 01:02:02 +00:00
}