using RimWorld; using rjw; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using Verse; namespace RJW_Menstruation { public class Hediff_MultiplePregnancy : Hediff_BasePregnancy { protected Dictionary enzygoticSiblings = new Dictionary(); // Each pawn and who they split from public override void DiscoverPregnancy() { PregnancyThought(); base.DiscoverPregnancy(); } protected void PregnancyThought() { if (is_discovered || !xxx.is_human(pawn) || pawn.HasQuirk(QuirkUtility.Quirks.Breeder) || (pawn.Ideo?.HasPrecept(VariousDefOf.Pregnancy_Required) ?? false) || (pawn.relations?.DirectRelations?.Find(x => x.def.Equals(PawnRelationDefOf.Spouse) || x.def.Equals(PawnRelationDefOf.Fiance))) != null) return; if (pawn.WantsToGetPregnant() || pawn.relations?.DirectRelations?.Find(x => x.def.Equals(PawnRelationDefOf.Lover)) != null) { pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.UnwantedPregnancyMild); } else { pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.UnwantedPregnancy); } } public override void Miscarry() { this.GetMenstruationCompFromPregnancy().Pregnancy = null; base.Miscarry(); } public override void GiveBirth() { if (babies.NullOrEmpty()) { ModLog.Warning(" no babies (debug?) " + this.GetType().Name); if (father == null) { father = Trytogetfather(ref pawn); } Initialize(pawn, father, SelectDnaGivingParent(pawn, father)); } foreach (Pawn baby in babies) { if (xxx.is_animal(baby)) { BestialBirth(baby); } else { HumanlikeBirth(baby); } baby.ageTracker.AgeChronologicalTicks = 0; } pawn.health.RemoveHediff(this); HediffComp_Menstruation comp = this.GetMenstruationCompFromPregnancy(); if(comp != null) comp.Pregnancy = null; HediffComp_Breast breastcomp = pawn.GetBreastComp(); if (ModsConfig.BiotechActive && xxx.is_human(pawn) && breastcomp != null) pawn.health.AddHediff(HediffDefOf.Lactating); breastcomp?.GaveBirth(); } private void HumanlikeBirth(Pawn baby) { Pawn mother = pawn; Pawn father = Utility.GetFather(baby, pawn); //backup melanin, LastName for when baby reset by other mod on spawn/backstorychange //var skin_whiteness = baby.story.melanin; //var last_name = baby.story.birthLastName; PawnUtility.TrySpawnHatchedOrBornPawn(baby, mother); Need_Sex sex_need = mother.needs?.TryGetNeed(); if (mother.Faction != null && !(mother.Faction?.IsPlayer ?? false) && sex_need != null) { sex_need.CurLevel = 1.0f; } if (mother.Faction != null) { if (mother.Faction != baby.Faction) baby.SetFaction(mother.Faction); } if (mother.IsSlaveOfColony) { if (mother.SlaveFaction != null) baby.SetFaction(mother.SlaveFaction); else if (mother.HomeFaction != null) baby.SetFaction(mother.HomeFaction); else if (mother.Faction != null) baby.SetFaction(mother.Faction); else baby.SetFaction(Faction.OfPlayer); baby.guest.SetGuestStatus(Faction.OfPlayer, GuestStatus.Slave); } else if (mother.IsPrisonerOfColony) { if (mother.HomeFaction != null) baby.SetFaction(mother.HomeFaction); baby.guest.SetGuestStatus(Faction.OfPlayer, GuestStatus.Prisoner); } if (xxx.is_human(mother)) TaleRecorder.RecordTale(TaleDefOf.GaveBirth, new object[] { mother, baby }); if (ModsConfig.BiotechActive) { // Ugly, but it'll have to do OutcomeChance bestOutcome = RitualOutcomeEffectDefOf.ChildBirth.BestOutcome; string label = bestOutcome.label; string description = bestOutcome.description.Formatted(mother.Named("MOTHER")); baby.babyNamingDeadline = Find.TickManager.TicksGame + GenDate.TicksPerDay; ChoiceLetter_BabyBirth choiceLetter_BabyBirth = (ChoiceLetter_BabyBirth)LetterMaker.MakeLetter( label, description, LetterDefOf.BabyBirth, baby ); choiceLetter_BabyBirth.Start(); Find.LetterStack.ReceiveLetter(choiceLetter_BabyBirth); } PostBirth(mother, father, baby); } private void BestialBirth(Pawn baby) { Pawn mother = pawn; Pawn father = Utility.GetFather(baby, pawn); //backup melanin, LastName for when baby reset by other mod on spawn/backstorychange //var skin_whiteness = baby.story.melanin; //var last_name = baby.story.birthLastName; PawnUtility.TrySpawnHatchedOrBornPawn(baby, mother); Need_Sex sex_need = mother.needs?.TryGetNeed(); if (mother.Faction != null && !(mother.Faction?.IsPlayer ?? false) && sex_need != null) { sex_need.CurLevel = 1.0f; } if (mother.Faction != null) { if (mother.Faction != baby.Faction) baby.SetFaction(mother.Faction); } Train(baby, mother); PostBirth(mother, father, baby); //restore melanin, LastName for when baby reset by other mod on spawn/backstorychange //baby.story.melanin = skin_whiteness; //baby.story.birthLastName = last_name; } public override void PostBirth(Pawn mother, Pawn father, Pawn baby) { base.PostBirth(mother, father, baby); // Has to happen on birth since RJW redoes the genitals at birth if (!enzygoticSiblings.NullOrEmpty() && enzygoticSiblings.TryGetValue(baby, out Pawn original) && baby != original) PregnancyCommon.ProcessIdenticalSibling(baby, original); } // From RJW's trait code protected List GetInheritableTraits(Pawn mother, Pawn father) { List traitpool = new List(); List momtraits = new List(); List poptraits = new List(); List traits_to_inherit = new List(); float max_num_momtraits_inherited = RJWPregnancySettings.max_num_momtraits_inherited; float max_num_poptraits_inherited = RJWPregnancySettings.max_num_poptraits_inherited; float max_num_traits_inherited = max_num_momtraits_inherited + max_num_poptraits_inherited; int i = 1; int j = 1; if (xxx.has_traits(mother) && mother.RaceProps.Humanlike) { foreach (Trait momtrait in mother.story.traits.allTraits) { if (!non_genetic_traits.Contains(momtrait.def.defName) && !momtrait.ScenForced) momtraits.Add(momtrait); } } if (father != null && xxx.has_traits(father) && father.RaceProps.Humanlike) { foreach (Trait poptrait in father.story.traits.allTraits) { if (!non_genetic_traits.Contains(poptrait.def.defName) && !poptrait.ScenForced) poptraits.Add(poptrait); } } int rand_trait_index; if (!momtraits.NullOrEmpty()) { i = 1; while (momtraits.Count > 0 && i <= max_num_momtraits_inherited) { rand_trait_index = Rand.Range(0, momtraits.Count); traits_to_inherit.Add(momtraits[rand_trait_index]); momtraits.RemoveAt(rand_trait_index); } } if (!poptraits.NullOrEmpty()) { j = 1; while (poptraits.Count > 0 && j <= max_num_poptraits_inherited) { rand_trait_index = Rand.Range(0, poptraits.Count); traits_to_inherit.Add(poptraits[rand_trait_index]); poptraits.RemoveAt(rand_trait_index); } } if (poptraits.NullOrEmpty() || momtraits.NullOrEmpty()) { foreach (Trait traits in traits_to_inherit) { traitpool.Add(traits); } } else { if (traits_to_inherit.Count() != max_num_traits_inherited) { if (momtraits.Count != 0 && i != max_num_momtraits_inherited) { while (poptraits != null && momtraits.Count() > 0 && i <= max_num_momtraits_inherited) { rand_trait_index = Rand.Range(0, momtraits.Count); if (!traits_to_inherit.Contains(momtraits[rand_trait_index])) { traits_to_inherit.Add(momtraits[rand_trait_index]); } momtraits.RemoveAt(rand_trait_index); } } if (poptraits != null && poptraits.Count != 0 && j != max_num_poptraits_inherited) { while (poptraits.Count > 0 && i < max_num_poptraits_inherited) { rand_trait_index = Rand.Range(0, poptraits.Count); if (!traits_to_inherit.Contains(poptraits[rand_trait_index])) { traits_to_inherit.Add(poptraits[rand_trait_index]); } poptraits.RemoveAt(rand_trait_index); } } } foreach (Trait traits in traits_to_inherit) { traitpool.Add(traits); } } return traitpool; } public override void ExposeData() { base.ExposeData(); Scribe_Collections.Look(ref enzygoticSiblings, "enzygoticSiblings", keyLookMode: LookMode.Reference, valueLookMode: LookMode.Reference); } protected override void GenerateBabies(DnaGivingParent _) { AddNewBaby(pawn, father); } protected void Train(Pawn baby, Pawn mother) { if (xxx.is_human(baby) || baby.Faction != Faction.OfPlayer) return; if (xxx.is_human(mother) && baby.Faction == Faction.OfPlayer && baby.training.CanAssignToTrain(TrainableDefOf.Obedience, out _).Accepted) { baby.training.Train(TrainableDefOf.Obedience, mother); } if (xxx.is_human(mother) && baby.Faction == Faction.OfPlayer && baby.training.CanAssignToTrain(TrainableDefOf.Tameness, out _).Accepted) { baby.training.Train(TrainableDefOf.Tameness, mother); } } public bool AddNewBaby(Pawn mother, Pawn father) { string lastname; if (xxx.is_human(mother)) lastname = NameTriple.FromString(mother.Name.ToStringFull).Last; else if (xxx.is_human(father)) lastname = NameTriple.FromString(father.Name.ToStringFull).Last; else lastname = ""; PawnGenerationRequest request = new PawnGenerationRequest( developmentalStages: DevelopmentalStage.Newborn, allowDowned: true, faction: mother.Faction, canGeneratePawnRelations: false, forceGenerateNewPawn: true, colonistRelationChanceFactor: 0, allowFood: false, allowAddictions: false, relationWithExtraPawnChanceFactor: 0, fixedLastName: lastname, kind: PregnancyCommon.BabyPawnKindDecider(mother, father, false), //fixedIdeo: mother.Ideo, forbidAnyTitle: true, forceNoBackstory: true, forcedEndogenes: PregnancyUtility.GetInheritedGenes(father, mother), forcedXenotype: ModsConfig.BiotechActive ? XenotypeDefOf.Baseliner : null ); int division = 1; Pawn firstbaby = null; int traitSeed = Rand.Int; List parentTraits = GetInheritableTraits(mother, father); while (Rand.Chance(Configurations.EnzygoticTwinsChance) && division < Configurations.MaxEnzygoticTwins) division++; for (int i = 0; i < division; i++) { Pawn baby = GenerateBaby(request, mother, father, parentTraits, traitSeed); if (baby == null) break; PregnancyCommon.SetupBabyXenotype(mother, father, baby); // HAR and some xenotype mods don't randomize graphics until it's rendered // So poke it early baby.Drawer.renderer.graphics.ResolveAllGraphics(); 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; baby.story.HairColor = firstbaby.story.HairColor; baby.story.bodyType = firstbaby.story.bodyType; baby.story.furDef = firstbaby.story.furDef; baby.story.skinColorOverride = firstbaby.story.skinColorOverride; } if (baby.genes != null && ModsConfig.BiotechActive) { 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); if (Configurations.AnimalGeneticsActivated) AnimalGeneticsCompatibility.CopyGenes(baby, firstbaby); } } babies.Add(baby); } return true; } public Pawn GenerateBaby(PawnGenerationRequest request, Pawn mother, Pawn father, List parentTraits, int traitSeed) { if (Configurations.AnimalGeneticsActivated) AnimalGeneticsCompatibility.PreConception(mother, father); Pawn baby = PawnGenerator.GeneratePawn(request); if (Configurations.AnimalGeneticsActivated) AnimalGeneticsCompatibility.PostConception(); if (baby == null) { Log.Error("Baby not generated. Request: " + request.ToString()); return null; } if (xxx.is_human(baby) || (baby.relations != null && !RJWSettings.Disable_bestiality_pregnancy_relations)) { baby.SetMother(mother); if (mother != father) { if (father.gender != Gender.Female) baby.SetFather(father); else baby.relations.AddDirectRelation(PawnRelationDefOf.Parent, father); } } if (xxx.is_human(baby)) { // Ensure the same inherited traits are chosen each run // Has to happen right here so GeneratePawn up there still gets unique results Rand.PushState(traitSeed); // With a seed just to make sure that fraternal twins *don't* get trait-duped UpdateTraits(baby, parentTraits); Rand.PopState(); } return baby; } /// /// Copy from RJW /// /// /// /// public void UpdateTraits(Pawn pawn, List parentTraits) { if (pawn?.story?.traits == null) { return; } int traitLimit = pawn.story.traits.allTraits.Count; //Personal pool List personalTraitPool = new List(pawn.story.traits.allTraits); //Parents List parentTraitPool = new List(parentTraits); parentTraitPool.RemoveAll(x => x.ScenForced); int numberInherited; if (parentTraitPool != null) numberInherited = System.Math.Min(parentTraitPool.Count(), Rand.RangeInclusive(0, 2)); // Not 3; give a better chance for a natural trait to appear else numberInherited = 0; //Game suggested traits. IEnumerable forcedTraits = personalTraitPool .Where(x => x.ScenForced) .Distinct(new TraitComparer(ignoreDegree: true)); // result can be a mess, because game allows this mess to be created in scenario editor List selectedTraits = new List(); TraitComparer comparer = new TraitComparer(); // trait comparision implementation, because without game compares traits *by reference*, makeing them all unique. selectedTraits.AddRange(forcedTraits); // enforcing scenario forced traits for (int i = 0; i < numberInherited; i++) // add parent traits first { int index = Rand.Range(0, parentTraitPool.Count); Trait trait = parentTraitPool[index]; parentTraitPool.RemoveAt(index); if (!selectedTraits.Any(x => comparer.Equals(x, trait) || x.def.ConflictsWith(trait))) selectedTraits.Add(new Trait(trait.def, trait.Degree, false)); } while (selectedTraits.Count < traitLimit && personalTraitPool.Count > 0) { int index = Rand.Range(0, personalTraitPool.Count); // getting trait and removing from the pull Trait trait = personalTraitPool[index]; personalTraitPool.RemoveAt(index); if (!selectedTraits.Any(x => comparer.Equals(x, trait) || // skipping traits conflicting with already added x.def.ConflictsWith(trait))) selectedTraits.Add(new Trait(trait.def, trait.Degree, false)); } pawn.story.traits.allTraits = selectedTraits; } public override bool TryMergeWith(Hediff other) { return false; } } /// /// Copy from RJW /// public class TraitComparer : IEqualityComparer { readonly bool ignoreForced; readonly bool ignoreDegree; public TraitComparer(bool ignoreDegree = false, bool ignoreForced = true) { this.ignoreDegree = ignoreDegree; this.ignoreForced = ignoreForced; } public bool Equals(Trait x, Trait y) { return x.def == y.def && (ignoreDegree || (x.Degree == y.Degree)) && (ignoreForced || (x.ScenForced == y.ScenForced)); } public int GetHashCode(Trait obj) { return (obj.def.GetHashCode() << 5) + (ignoreDegree ? 0 : obj.Degree) + ((ignoreForced || obj.ScenForced) ? 0 : 0x10); } } public class RaceComparer : IEqualityComparer { public bool Equals(Pawn x, Pawn y) { return x.def.Equals(y.def); } public int GetHashCode(Pawn obj) { return obj.def.GetHashCode(); } } public class FatherComparer : IEqualityComparer { readonly Pawn mother; public FatherComparer(Pawn mother) { this.mother = mother; } public bool Equals(Pawn x, Pawn y) { if (Utility.GetFather(x, mother) == null && Utility.GetFather(y, mother) == null) return true; return Utility.GetFather(x, mother)?.Label.Equals(Utility.GetFather(y, mother)?.Label) ?? false; } public int GetHashCode(Pawn obj) { return obj.def.GetHashCode(); } } }