2023-01-08 18:11:58 +00:00
using HarmonyLib ;
using RimWorld ;
2023-01-08 04:18:17 +00:00
using rjw ;
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Reflection ;
2023-01-08 18:11:58 +00:00
using System.Reflection.Emit ;
2023-01-08 04:18:17 +00:00
using Verse ;
namespace RJW_Menstruation
{
public class HediffComp_PregeneratedBabies : HediffComp
{
public List < Pawn > babies ;
// Unused, but can't hurt to track
protected Dictionary < Pawn , Pawn > enzygoticSiblings ;
protected static readonly MethodInfo RandomLastName = typeof ( PregnancyUtility ) . GetMethod ( "RandomLastName" , BindingFlags . Static | BindingFlags . NonPublic , null , new Type [ ] { typeof ( Pawn ) , typeof ( Pawn ) , typeof ( Pawn ) } , null ) ;
public bool HasBaby
{
get = > ! babies . NullOrEmpty ( ) ;
}
public Pawn PopBaby ( )
{
if ( babies . NullOrEmpty ( ) ) return null ;
Pawn firstBaby = babies . First ( ) ;
babies . Remove ( firstBaby ) ;
return firstBaby ;
}
public override void CompPostPostRemoved ( )
{
// At this point in the hediff removal process, the new hediff is already on the pawn
// But it is possible that there is no new hediff (be it a birth, miscarrage, or dev edit)
base . CompPostPostRemoved ( ) ;
2023-01-08 04:58:28 +00:00
// Send the babies from this comp to the new one
2023-01-08 04:18:17 +00:00
switch ( parent )
{
case Hediff_Pregnant hediff_Pregnant :
Hediff_Labor labor = ( Hediff_Labor ) Pawn . health . hediffSet . hediffs . Where ( hediff = > hediff is Hediff_Labor ) . MaxByWithFallback ( hediff = > hediff . loadID ) ;
HediffComp_PregeneratedBabies laborcomp = labor ? . TryGetComp < HediffComp_PregeneratedBabies > ( ) ;
if ( laborcomp = = null ) return ;
laborcomp . babies = this . babies ;
laborcomp . enzygoticSiblings = this . enzygoticSiblings ;
break ;
case Hediff_Labor hediff_Labor :
Hediff_LaborPushing pushing = ( Hediff_LaborPushing ) Pawn . health . hediffSet . hediffs . Where ( hediff = > hediff is Hediff_LaborPushing ) . MaxByWithFallback ( hediff = > hediff . loadID ) ;
HediffComp_PregeneratedBabies pushingcomp = pushing ? . TryGetComp < HediffComp_PregeneratedBabies > ( ) ;
if ( pushingcomp = = null ) return ;
pushingcomp . babies = this . babies ;
pushingcomp . enzygoticSiblings = this . enzygoticSiblings ;
break ;
case Hediff_LaborPushing hediff_LaborPushing :
// Nothing to do, the laborpushing transpiler will pick it up
break ;
}
}
public override void CompExposeData ( )
{
base . CompExposeData ( ) ;
Scribe_Collections . Look ( ref babies , "babies" , LookMode . Deep ) ;
Scribe_Collections . Look ( ref enzygoticSiblings , "enzygoticSiblings" , keyLookMode : LookMode . Reference , valueLookMode : LookMode . Reference ) ;
}
public void AddNewBaby ( Pawn mother , Pawn father )
{
if ( babies = = null ) babies = new List < Pawn > ( ) ;
2023-01-08 04:58:28 +00:00
PawnKindDef babyPawnKind = PregnancyCommon . BabyPawnKindDecider ( mother , father , true ) ;
2023-01-08 04:18:17 +00:00
PawnGenerationRequest request = new PawnGenerationRequest
(
kind : babyPawnKind ,
faction : mother . Faction ,
allowDowned : true ,
2023-03-06 06:57:23 +00:00
// fixedLastName seems not to actually do anything, as we eventually end up in PawnBioAndNameGenerator.NameResolvedFrom, which ignores its forcedLastName argument
2023-01-08 04:18:17 +00:00
fixedLastName : ( string ) RandomLastName . Invoke ( null , new object [ ] { mother , mother , xxx . is_human ( father ) ? father : null } ) ,
forceNoIdeo : true ,
forcedEndogenes : PregnancyUtility . GetInheritedGenes ( father , mother ) ,
forcedXenotype : XenotypeDefOf . Baseliner ,
developmentalStages : DevelopmentalStage . Newborn
) ;
int division = 1 ;
Pawn firstbaby = null ;
while ( Rand . Chance ( Configurations . EnzygoticTwinsChance ) & & division < Configurations . MaxEnzygoticTwins ) division + + ;
if ( division > 1 & & enzygoticSiblings = = null ) enzygoticSiblings = new Dictionary < Pawn , Pawn > ( ) ;
for ( int i = 0 ; i < division ; i + + )
{
Pawn baby = PawnGenerator . GeneratePawn ( request ) ;
if ( baby = = null ) break ;
2023-01-08 18:11:58 +00:00
PregnancyCommon . SetupBabyXenotype ( mother , father , baby ) ; // Probably redundant with Biotech post-birth xenotyping
2023-01-10 16:20:22 +00:00
baby . Drawer . renderer . graphics . ResolveAllGraphics ( ) ;
2023-01-08 04:18:17 +00:00
if ( division > 1 )
{
if ( i = = 0 )
{
firstbaby = baby ;
request . FixedGender = baby . gender ;
request . ForcedEndogenes = baby . genes ? . Endogenes . Select ( gene = > gene . def ) . ToList ( ) ;
}
else
{
enzygoticSiblings . Add ( baby , firstbaby ) ;
if ( baby . story ! = null )
{
baby . story . headType = firstbaby . story . headType ;
baby . story . hairDef = firstbaby . story . hairDef ;
2023-03-01 18:21:59 +00:00
baby . story . HairColor = firstbaby . story . HairColor ;
2023-01-08 04:18:17 +00:00
baby . story . bodyType = firstbaby . story . bodyType ;
baby . story . furDef = firstbaby . story . furDef ;
2023-01-10 13:46:16 +00:00
baby . story . skinColorOverride = firstbaby . story . skinColorOverride ;
2023-01-08 04:18:17 +00:00
}
2023-01-08 04:58:28 +00:00
if ( baby . genes ! = null )
2023-01-08 04:18:17 +00:00
{
baby . genes . SetXenotypeDirect ( firstbaby . genes . Xenotype ) ;
baby . genes . xenotypeName = firstbaby . genes . xenotypeName ;
baby . genes . iconDef = firstbaby . genes . iconDef ;
baby . genes . hybrid = firstbaby . genes . hybrid ;
}
if ( baby . IsHAR ( ) )
HARCompatibility . CopyHARProperties ( baby , firstbaby ) ;
// MultiplePregnancy calls this post-birth because RJW resets private parts
// So xenotype things shouldn't be shared
PregnancyCommon . ProcessIdenticalSibling ( baby , firstbaby ) ;
}
}
babies . Add ( baby ) ;
2023-01-09 16:50:37 +00:00
// These get cleared out later, but setting the relations now will help keep track of things.
baby . SetMother ( mother ) ;
if ( mother ! = father )
{
if ( father . gender ! = Gender . Female ) baby . SetFather ( father ) ;
else baby . relations . AddDirectRelation ( PawnRelationDefOf . Parent , father ) ;
}
2023-01-08 04:18:17 +00:00
}
}
}
2023-01-08 18:11:58 +00:00
[HarmonyPatch(typeof(PregnancyUtility), nameof(PregnancyUtility.ApplyBirthOutcome))]
public static class ApplyBirthOutcome_PregeneratedBabies_Patch
{
private static Pawn GetPregeneratedBaby ( PawnGenerationRequest request , Thing birtherThing )
{
// Don't test for the config set here. We can do it at the functions that call ApplyBirthOutcome
// Easier to work out twins that way
// From e.g. a vat
if ( ! ( birtherThing is Pawn mother ) | | ! xxx . is_human ( mother ) )
return PawnGenerator . GeneratePawn ( request ) ;
// No babies found. Could be an unmodified pregnancy
HediffComp_PregeneratedBabies comp = mother . health . hediffSet . GetFirstHediff < Hediff_LaborPushing > ( ) ? . TryGetComp < HediffComp_PregeneratedBabies > ( ) ;
if ( comp = = null | | ! comp . HasBaby )
return PawnGenerator . GeneratePawn ( request ) ;
Pawn baby = comp . PopBaby ( ) ;
if ( baby = = null ) return PawnGenerator . GeneratePawn ( request ) ; // Shouldn't happen
2023-01-10 15:05:13 +00:00
baby . ageTracker . AgeBiologicalTicks = 0 ;
baby . ageTracker . AgeChronologicalTicks = 0 ;
2023-03-02 00:29:58 +00:00
baby . babyNamingDeadline = Find . TickManager . TicksGame + GenDate . TicksPerDay ;
2023-01-08 18:11:58 +00:00
if ( request . ForceDead ) baby . Kill ( null , null ) ;
return baby ;
}
private static readonly MethodInfo ApplyBirthOutcome = typeof ( PregnancyUtility ) . GetMethod ( nameof ( PregnancyUtility . ApplyBirthOutcome ) ) ;
private static readonly int birtherThing = ApplyBirthOutcome . GetParameters ( ) . FirstIndexOf ( parameter = > parameter . Name = = "birtherThing" & & parameter . ParameterType = = typeof ( Thing ) ) ;
private static readonly MethodInfo GeneratePawn = typeof ( PawnGenerator ) . GetMethod ( nameof ( PawnGenerator . GeneratePawn ) , new Type [ ] { typeof ( PawnGenerationRequest ) } ) ;
public static IEnumerable < CodeInstruction > Transpiler ( IEnumerable < CodeInstruction > instructions )
{
if ( birtherThing < 0 ) throw new InvalidOperationException ( "Could not locate index of birtherThing" ) ;
2023-08-08 15:06:45 +00:00
if ( GeneratePawn ? . ReturnType ! = typeof ( Pawn ) ) throw new InvalidOperationException ( "GeneratePawn not found" ) ;
2023-01-08 18:11:58 +00:00
foreach ( CodeInstruction instruction in instructions )
{
if ( instruction . Calls ( GeneratePawn ) )
{
yield return new CodeInstruction ( OpCodes . Ldarg , birtherThing ) ;
yield return CodeInstruction . Call ( typeof ( ApplyBirthOutcome_PregeneratedBabies_Patch ) , nameof ( GetPregeneratedBaby ) ) ;
}
else yield return instruction ;
}
}
}
2023-01-08 19:29:21 +00:00
[HarmonyPatch(typeof(Hediff_LaborPushing), nameof(Hediff_LaborPushing.PreRemoved))]
public static class Hediff_LaborPushing_PreRemoved_Patch
{
private static Thing ApplyBirthLoop ( OutcomeChance outcome , float quality , Precept_Ritual ritual , List < GeneDef > genes , Pawn geneticMother , Thing birtherThing , Pawn father , Pawn doctor , LordJob_Ritual lordJobRitual , RitualRoleAssignments assignments )
{
if ( birtherThing is Pawn mother )
{
HediffComp_PregeneratedBabies comp = mother . health . hediffSet . GetFirstHediff < Hediff_LaborPushing > ( ) . TryGetComp < HediffComp_PregeneratedBabies > ( ) ;
if ( comp ? . HasBaby ? ? false )
{
OutcomeChance thisOutcome = outcome ;
2024-03-17 23:32:41 +00:00
Precept_Ritual precept_Ritual = ( Precept_Ritual ) comp . Pawn . Ideo . GetPrecept ( RimWorld . PreceptDefOf . ChildBirth ) ;
2023-01-08 19:29:21 +00:00
float birthQuality = PregnancyUtility . GetBirthQualityFor ( mother ) ;
do
{
2023-01-09 16:50:37 +00:00
Pawn baby = comp . babies [ 0 ] ;
2023-03-06 03:24:23 +00:00
Pawn thisFather = baby . GetFather ( ) ? ? father ;
2023-01-09 16:50:37 +00:00
baby . relations . ClearAllRelations ( ) ; // To keep ApplyBirthOutcome from erroring when it tries to set up relations
PregnancyUtility . ApplyBirthOutcome ( thisOutcome , quality , ritual , genes , geneticMother , birtherThing , thisFather , doctor , lordJobRitual , assignments ) ;
2023-01-08 19:29:21 +00:00
// No more babies if mom dies halfway through. Unrealistic maybe, but saves a lot of headache in ApplyBirthOutcome
2023-04-18 14:20:35 +00:00
if ( mother . Dead ) break ;
2023-03-17 16:34:45 +00:00
if ( xxx . is_human ( baby ) )
mother . records . Increment ( xxx . CountOfBirthHuman ) ;
else if ( xxx . is_animal ( baby ) )
mother . records . Increment ( xxx . CountOfBirthAnimal ) ;
2023-01-08 19:29:21 +00:00
thisOutcome = ( ( RitualOutcomeEffectWorker_ChildBirth ) precept_Ritual . outcomeEffect ) . GetOutcome ( birthQuality , null ) ;
} while ( comp . HasBaby ) ;
// PreRemoved doesn't use the return value
return null ;
}
}
return PregnancyUtility . ApplyBirthOutcome ( outcome , quality , ritual , genes , geneticMother , birtherThing , father , doctor , lordJobRitual , assignments ) ;
}
private static readonly MethodInfo ApplyBirthOutcome = typeof ( PregnancyUtility ) . GetMethod ( nameof ( PregnancyUtility . ApplyBirthOutcome ) ,
new Type [ ] { typeof ( OutcomeChance ) , typeof ( float ) , typeof ( Precept_Ritual ) , typeof ( List < GeneDef > ) , typeof ( Pawn ) , typeof ( Thing ) , typeof ( Pawn ) , typeof ( Pawn ) , typeof ( LordJob_Ritual ) , typeof ( RitualRoleAssignments ) } ) ;
public static IEnumerable < CodeInstruction > Transpiler ( IEnumerable < CodeInstruction > instructions )
{
2023-08-08 15:06:45 +00:00
if ( ApplyBirthOutcome ? . ReturnType ! = typeof ( Thing ) ) throw new InvalidOperationException ( "ApplyBirthOutcome not found" ) ;
2023-01-08 19:29:21 +00:00
foreach ( CodeInstruction instruction in instructions )
{
if ( instruction . Calls ( ApplyBirthOutcome ) )
yield return CodeInstruction . Call ( typeof ( Hediff_LaborPushing_PreRemoved_Patch ) , nameof ( Hediff_LaborPushing_PreRemoved_Patch . ApplyBirthLoop ) ) ;
else yield return instruction ;
}
}
}
[HarmonyPatch(typeof(RitualOutcomeEffectWorker_ChildBirth), nameof (RitualOutcomeEffectWorker_ChildBirth.Apply))]
public static class Ritual_ChildBirth_Apply_Patch
{
private static Thing ApplyBirthLoop ( OutcomeChance outcome , float quality , Precept_Ritual ritual , List < GeneDef > genes , Pawn geneticMother , Thing birtherThing , Pawn father , Pawn doctor , LordJob_Ritual lordJobRitual , RitualRoleAssignments assignments )
{
if ( birtherThing is Pawn mother )
{
HediffComp_PregeneratedBabies comp = mother . health . hediffSet . GetFirstHediff < Hediff_LaborPushing > ( ) . TryGetComp < HediffComp_PregeneratedBabies > ( ) ;
if ( comp ? . HasBaby ? ? false )
{
2023-01-09 16:50:37 +00:00
// Much the same as the other one
2023-01-08 19:29:21 +00:00
// Don't reroll the outcome every time, I think
// This is all one ritual, so every baby has the same ritual outcome
// I don't think this will add the ritual memory every time?
// Though even if it does, that's probably okay. More babies more memories after all
do
{
2023-01-09 16:50:37 +00:00
Pawn baby = comp . babies [ 0 ] ;
2023-03-06 03:24:23 +00:00
Pawn thisFather = baby . GetFather ( ) ? ? father ;
2023-01-09 16:50:37 +00:00
baby . relations . ClearAllRelations ( ) ;
PregnancyUtility . ApplyBirthOutcome ( outcome , quality , ritual , genes , geneticMother , birtherThing , thisFather , doctor , lordJobRitual , assignments ) ;
2023-04-18 14:20:35 +00:00
if ( mother . Dead ) break ;
2023-03-17 16:34:45 +00:00
if ( xxx . is_human ( baby ) )
mother . records . Increment ( xxx . CountOfBirthHuman ) ;
else if ( xxx . is_animal ( baby ) )
mother . records . Increment ( xxx . CountOfBirthAnimal ) ;
2023-01-08 19:29:21 +00:00
} while ( comp . HasBaby ) ;
2023-01-09 16:50:37 +00:00
// The ritual version doesn't use the return value, either
2023-01-08 19:29:21 +00:00
return null ;
}
}
return PregnancyUtility . ApplyBirthOutcome ( outcome , quality , ritual , genes , geneticMother , birtherThing , father , doctor , lordJobRitual , assignments ) ;
}
private static readonly MethodInfo ApplyBirthOutcome = typeof ( PregnancyUtility ) . GetMethod ( nameof ( PregnancyUtility . ApplyBirthOutcome ) ,
new Type [ ] { typeof ( OutcomeChance ) , typeof ( float ) , typeof ( Precept_Ritual ) , typeof ( List < GeneDef > ) , typeof ( Pawn ) , typeof ( Thing ) , typeof ( Pawn ) , typeof ( Pawn ) , typeof ( LordJob_Ritual ) , typeof ( RitualRoleAssignments ) } ) ;
public static IEnumerable < CodeInstruction > Transpiler ( IEnumerable < CodeInstruction > instructions )
{
2023-08-08 15:06:45 +00:00
if ( ApplyBirthOutcome ? . ReturnType ! = typeof ( Thing ) ) throw new InvalidOperationException ( "ApplyBirthOutcome not found" ) ;
2023-09-05 17:46:24 +00:00
foreach ( CodeInstruction instruction in instructions )
2023-01-08 19:29:21 +00:00
{
if ( instruction . Calls ( ApplyBirthOutcome ) )
yield return CodeInstruction . Call ( typeof ( Ritual_ChildBirth_Apply_Patch ) , nameof ( Ritual_ChildBirth_Apply_Patch . ApplyBirthLoop ) ) ;
else yield return instruction ;
}
}
}
2023-01-10 20:41:02 +00:00
// HAR patches ApplyBirthOutcome to produce multiple babies based on the mother's littersize. But the pregenerated babies system already makes multiple babies
// So make it always consider the mother to have one baby
public static class HAR_LitterSize_Undo
{
public static void Postfix ( ref int __result , Pawn mother )
{
if ( Configurations . PregnancySource ! = Configurations . PregnancyType . Biotech | | ! Configurations . EnableBiotechTwins ) return ;
2023-01-11 14:24:14 +00:00
__result = 0 ;
2023-01-10 20:41:02 +00:00
return ;
}
}
2023-01-08 04:18:17 +00:00
}