diff --git a/1.4/Assemblies/RJW_Menstruation.dll b/1.4/Assemblies/RJW_Menstruation.dll index 325db98..0215954 100644 Binary files a/1.4/Assemblies/RJW_Menstruation.dll and b/1.4/Assemblies/RJW_Menstruation.dll differ diff --git a/1.4/Defs/GeneDefs/GeneDefs_Menstruation.xml b/1.4/Defs/GeneDefs/GeneDefs_Menstruation.xml new file mode 100644 index 0000000..d430d3f --- /dev/null +++ b/1.4/Defs/GeneDefs/GeneDefs_Menstruation.xml @@ -0,0 +1,107 @@ + + + + + Menstruation + + 402 + + + + Menstruation + +
  • Menstruation_EggLifetime
  • +
    +
    + + + Menstruation_ShortEggLifetime + + Unfertilized eggs with this gene last three-quarters as long. + UI/Genes/Placeholder + 1 + 10 + + + + Menstruation_DoubleEggLifetime + + Unfertilized eggs with this gene last twice as long. + UI/Genes/Placeholder + -1 + 12 + + + + Menstruation_QuadEggLifetime + + Eggs with this gene last four times as long. + UI/Genes/Placeholder + -2 + 1 + 16 + + + + Menstruation + +
  • Menstruation_Estrus
  • +
    +
    + + + Menstruation_NeverEstrus + + Carriers of this gene will never go into estrus. + UI/Genes/Placeholder + 1 + 20 + + + + Menstruation_FullEstrus + + Carriers of this gene will enter full estrus every menstrual cycle, regardless of vagina type. + UI/Genes/Placeholder + -1 + 1 + 25 + + + + Menstruation + +
  • Menstruation_Ovulation
  • +
    +
    + + + Menstruation_DoubleOvulation + + Carriers of this gene will ovulate twice as many eggs. + UI/Genes/Placeholder + -1 + 30 + + + + Menstruation_QuadOvulation + + Carriers of this gene will ovulate four times as many eggs. + UI/Genes/Placeholder + -1 + 35 + + + + Menstruation_NoBleeding + + Menstruation + Carriers of this gene will not bleed at the end of their cycle. + UI/Genes/Placeholder + 1 + 40 + + + +
    \ No newline at end of file diff --git a/1.4/Languages/ChineseTraditional/DefInjected/GeneDefs/GeneDefs_Menstruation.xml b/1.4/Languages/ChineseTraditional/DefInjected/GeneDefs/GeneDefs_Menstruation.xml new file mode 100644 index 0000000..377a6b5 --- /dev/null +++ b/1.4/Languages/ChineseTraditional/DefInjected/GeneDefs/GeneDefs_Menstruation.xml @@ -0,0 +1,20 @@ + + + 月經週期 + 較短卵細胞壽命 + 未受精卵細胞存活時長僅有原先的3/4 + 雙倍卵細胞壽命 + 未受精卵細胞可存活至原先的2倍之久 + 四倍卵細胞壽命 + 未受精卵細胞可存活至原先的4倍之久 + 永不發情 + 基因攜帶者永遠不會進入發情期。 + 始終發情 + 基因攜帶者的每一個月經週期均具備發情期,無論陰道類型為何。 + 雙倍排卵 + 基因攜帶者的子宮可以在每個排卵週期產生雙倍的卵子。 + 四倍排卵 + 基因攜帶者的子宮可以在每個排卵週期產生四倍的卵子。 + 無經血 + 基因攜帶者的子宮內膜不會脫落出血。 + diff --git a/1.4/Languages/ChineseTraditional/DefInjected/ThoughtDefs/Thoughts_sex.xml b/1.4/Languages/ChineseTraditional/DefInjected/ThoughtDefs/Thoughts_sex.xml index 7cd43fc..b1eeb56 100644 --- a/1.4/Languages/ChineseTraditional/DefInjected/ThoughtDefs/Thoughts_sex.xml +++ b/1.4/Languages/ChineseTraditional/DefInjected/ThoughtDefs/Thoughts_sex.xml @@ -8,4 +8,7 @@ 總算把這事了結了。 吃了避孕藥 我想要小孩! + + 卵母細胞再生術 + 我可以繼續繁衍一小段時間了! diff --git a/1.4/Languages/ChineseTraditional/Keyed/RJW_Menstruation.xml b/1.4/Languages/ChineseTraditional/Keyed/RJW_Menstruation.xml index 1f4b8e7..c436751 100644 --- a/1.4/Languages/ChineseTraditional/Keyed/RJW_Menstruation.xml +++ b/1.4/Languages/ChineseTraditional/Keyed/RJW_Menstruation.xml @@ -37,7 +37,7 @@ 月經加速 加快月經週期 除錯 - 顯示除錯資訊 + 顯示除錯資訊 啟用時會令「胎兒信息級別」選項調至「全部細節」。 子宮狀態 在狀態窗口中繪製子宮圖標 陰道狀態 @@ -62,7 +62,6 @@ 最大雙胞胎數量 設置最大雙胞胎數量 清洗陰道 - 絕經 乏情期 @@ -98,7 +97,7 @@ 本模組作用於: 這些小人的「RJW月經週期」工具欄對玩家可見。 使用進階雜交定義 - 複寫RJW和RaceSupport插件的雜交定義。Overrides RJW and RaceSupport's hybrid definition. + 覆寫RJW和RaceSupport插件的雜交定義。Overrides RJW and RaceSupport's hybrid definition. 主導混合擴展決定了首先使用誰的定義。不建議更改此設置。Dominant hybrid extension determines whose definition used first. Not recommended to change this. 主導混合擴展Dominant hybrid extension 母本 @@ -111,7 +110,7 @@ 乳頭在孕期間的改變會在孕期結束後保留多少? 客製化雜交細節 開啟雜交編輯器 -該選項會複寫XML文件中的雜交定義。 +該選項會覆寫XML文件中的雜交定義。 允許圖標縮小 允許圖標在特殊場合縮小。 卵細胞生命期乘數 @@ -123,7 +122,7 @@ 擴張力度 調節擴張力度。 啟用「擠出精液」按鈕 - 令「發情期」機制複寫RJW的濫交選項 + 令「發情期」機制覆寫RJW的濫交選項 啟用時,處於顯式發情期的小人將會使用以下選項來選定床伴。RJW的原始設定會被忽略。 所有數值與RJW的對應選項相同。 發情期床伴:最低fuckability @@ -139,4 +138,13 @@ {0}之雜交 當{0}與{1}產生後代,有{3}的概率生出{2}。 若兩個種族相互間皆存在雜交定義,則以父本的定義為準。 + + 使用RJW的簡單懷孕系統 + 使用本模組的多重懷孕 + 使用「生機」(Biotech)追加的懷孕機制 + 於「徵召」狀態下仍顯示子宮狀態 + 角色處於被徵召狀態時,子宮圖標不予隱藏。 + 沒有卵細胞 + 必須擁有子宮 + {PAWN_labelShort}完成了{PAWN_possessive}卵母細胞再生術(cycle) diff --git a/1.4/Languages/English/Keyed/RJW_Menstruation.xml b/1.4/Languages/English/Keyed/RJW_Menstruation.xml index f28203c..3df2d06 100644 --- a/1.4/Languages/English/Keyed/RJW_Menstruation.xml +++ b/1.4/Languages/English/Keyed/RJW_Menstruation.xml @@ -124,6 +124,8 @@ Use basic RJW pregnancy Use menstruation multiple pregnancy Use Biotech pregnancy + (EXPERIMENTAL) Enable multiple babies/twins in a single Biotech pregnancy. + Enabling this option will allow identical and hetero ovular twins with Biotech. Also allows the hybrid system, but two humanlikes cannot produce an animal. Show womb status when drafted Draw womb icon for drafted pawns Reset to default diff --git a/1.4/MilkModule/Assemblies/MilkModule.dll b/1.4/MilkModule/Assemblies/MilkModule.dll index 5961d35..5c953b2 100644 Binary files a/1.4/MilkModule/Assemblies/MilkModule.dll and b/1.4/MilkModule/Assemblies/MilkModule.dll differ diff --git a/1.4/Patches/Pregenerated_Babies.xml b/1.4/Patches/Pregenerated_Babies.xml new file mode 100644 index 0000000..07d05b3 --- /dev/null +++ b/1.4/Patches/Pregenerated_Babies.xml @@ -0,0 +1,16 @@ + + + + +
  • Biotech
  • +
    + + /Defs/HediffDef[defName="PregnantHuman" or defName="PregnancyLabor" or defName="PregnancyLaborPushing"]/comps + +
  • + RJW_Menstruation.HediffComp_PregeneratedBabies +
  • +
    +
    +
    +
    \ No newline at end of file diff --git a/1.4/Textures/Things/Item/Milkbottle/Milkbottle_a.png b/1.4/Textures/Things/Item/Milkbottle/Milkbottle_a.png deleted file mode 100644 index c9ac05f..0000000 Binary files a/1.4/Textures/Things/Item/Milkbottle/Milkbottle_a.png and /dev/null differ diff --git a/1.4/Textures/Things/Item/Milkbottle/Milkbottle_b.png b/1.4/Textures/Things/Item/Milkbottle/Milkbottle_b.png deleted file mode 100644 index 9fced73..0000000 Binary files a/1.4/Textures/Things/Item/Milkbottle/Milkbottle_b.png and /dev/null differ diff --git a/1.4/Textures/Things/Item/Milkbottle/Milkbottle_c.png b/1.4/Textures/Things/Item/Milkbottle/Milkbottle_c.png deleted file mode 100644 index 88a8899..0000000 Binary files a/1.4/Textures/Things/Item/Milkbottle/Milkbottle_c.png and /dev/null differ diff --git a/1.4/Textures/UI/Genes/Placeholder.png b/1.4/Textures/UI/Genes/Placeholder.png new file mode 100644 index 0000000..deaa2c0 Binary files /dev/null and b/1.4/Textures/UI/Genes/Placeholder.png differ diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Compatibility/HARCompatibility.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Compatibility/HARCompatibility.cs index add9b43..7eb1e66 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Compatibility/HARCompatibility.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Compatibility/HARCompatibility.cs @@ -1,4 +1,5 @@ using AlienRace; +using System; using System.Collections.Generic; using UnityEngine; using Verse; @@ -11,7 +12,7 @@ namespace RJW_Menstruation public static bool IsHAR(this Pawn pawn) { if (!Configurations.HARActivated) return false; - return pawn?.def is ThingDef_AlienRace; + return GenTypes.GetTypeInAnyAssembly("AlienRace.ThingDef_AlienRace").IsInstanceOfType(pawn?.def); } public static void CopyHARProperties(Pawn baby, Pawn original) diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Configurations.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Configurations.cs index 6a843bb..d450c4d 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Configurations.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Configurations.cs @@ -55,6 +55,7 @@ namespace RJW_Menstruation public static float EstrusAttractivenessToHookup = RJWHookupSettings.MinimumAttractivenessToHookup; public static float EstrusRelationshipToHookup = RJWHookupSettings.MinimumRelationshipToHookup; public static PregnancyType PregnancySource = PregnancyType.MultiplePregnancy; + public static bool EnableBiotechTwins = false; public static bool EnableHeteroOvularTwins = true; public static bool EnableEnzygoticTwins = true; public static float EnzygoticTwinsChance = EnzygoticTwinsChanceDefault; @@ -89,6 +90,7 @@ namespace RJW_Menstruation EstrusAttractivenessToHookup = RJWHookupSettings.MinimumAttractivenessToHookup; EstrusRelationshipToHookup = RJWHookupSettings.MinimumRelationshipToHookup; EnzygoticTwinsChanceAdjust = EnzygoticTwinsChanceAdjustDefault; + EnableBiotechTwins = false; EnableEnzygoticTwins = true; EnableHeteroOvularTwins = true; PregnancySource = PregnancyType.MultiplePregnancy; @@ -200,6 +202,7 @@ namespace RJW_Menstruation Scribe_Values.Look(ref EstrusAttractivenessToHookup, "EstrusAttractivenessToHookup", EstrusAttractivenessToHookup, true); Scribe_Values.Look(ref EstrusRelationshipToHookup, "EstrusRelationshipToHookup", EstrusRelationshipToHookup, true); Scribe_Values.Look(ref PregnancySource, "PregnancySource", PregnancySource, true); + Scribe_Values.Look(ref EnableBiotechTwins, "EnableBiotechTwins", EnableBiotechTwins, true); Scribe_Values.Look(ref EnableHeteroOvularTwins, "EnableHeteroOvularTwins", EnableHeteroOvularTwins, true); Scribe_Values.Look(ref EnableEnzygoticTwins, "EnableEnzygoticTwins", EnableEnzygoticTwins, true); Scribe_Values.Look(ref EnzygoticTwinsChance, "EnzygoticTwinsChance", EnzygoticTwinsChance, true); @@ -271,11 +274,12 @@ namespace RJW_Menstruation public override void DoSettingsWindowContents(Rect inRect) { Rect outRect = new Rect(0f, 30f, inRect.width, inRect.height - 30f); - float mainRectHeight = -3f + + float mainRectHeight = 30f + (Configurations.EnableWombIcon || Configurations.EnableButtonInHT ? 400f : 0f) + (Configurations.EstrusOverridesHookupSettings ? 144f : 0f) + - // TODO: Also for modified Biotech pregnancies (Configurations.PregnancySource == Configurations.PregnancyType.MultiplePregnancy ? (Configurations.EnableEnzygoticTwins ? 175f : 75f) : 0f) + + (Configurations.PregnancySource == Configurations.PregnancyType.Biotech ? 75f : 0f) + + (Configurations.PregnancySource == Configurations.PregnancyType.Biotech ? (Configurations.EnableBiotechTwins ? 175f : 75f) : 0f) + (Configurations.EnableBirthVaginaMorph ? 48f : 0f); Rect mainRect = new Rect(0f, 0f, inRect.width - 30f, Math.Max(inRect.height + mainRectHeight, 1f)); int Adjust; @@ -434,9 +438,11 @@ namespace RJW_Menstruation if (listmain.RadioButton(Translations.Option_PregnancyFromMultiplePregnancy_Label, Configurations.PregnancySource == Configurations.PregnancyType.MultiplePregnancy)) Configurations.PregnancySource = Configurations.PregnancyType.MultiplePregnancy; if (ModsConfig.BiotechActive && listmain.RadioButton(Translations.Option_PregnancyFromBiotech_Label, Configurations.PregnancySource == Configurations.PregnancyType.Biotech)) - Configurations.PregnancySource = Configurations.PregnancyType.Biotech; - // TODO: Also for modified Biotech pregnancy - if (Configurations.PregnancySource == Configurations.PregnancyType.MultiplePregnancy) + Configurations.PregnancySource = Configurations.PregnancyType.Biotech; + if (Configurations.PregnancySource == Configurations.PregnancyType.Biotech) + listmain.CheckboxLabeled(Translations.Option_EnableBiotechTwins_Label, ref Configurations.EnableBiotechTwins, Translations.Option_EnableBiotechTwins_Desc); + if (Configurations.PregnancySource == Configurations.PregnancyType.MultiplePregnancy || + (Configurations.PregnancySource == Configurations.PregnancyType.Biotech && Configurations.EnableBiotechTwins)) { float sectionheight = 75f; if (Configurations.EnableEnzygoticTwins) sectionheight += 100; diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Breast.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Breast.cs index 1d5eb60..71e930c 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Breast.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Breast.cs @@ -356,7 +356,7 @@ namespace RJW_Menstruation { baseAreola = Mathf.Clamp01(baseAreola + amount); UpdateNipples(); - } + } public void UpdateNipples() { @@ -365,7 +365,7 @@ namespace RJW_Menstruation cachedNipple = baseNipple + nippleProgress * nippleChange; // For some reason, Props can go null when RJW relocates the chest (e.g. some animals), so catch that - cachedColor = Colors.CMYKLerp(Pawn.story?.SkinColor ?? Color.white, (Props?.BlackNippleColor ?? CompProperties_Breast.DefaultBlacknippleColor.ToColor), Alpha); + cachedColor = Colors.CMYKLerp(Utility.SafeSkinColor(Pawn), (Props?.BlackNippleColor ?? CompProperties_Breast.DefaultBlacknippleColor.ToColor), Alpha); } public void CopyBreastProperties(HediffComp_Breast original) diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_InducedOvulator.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_InducedOvulator.cs index 55b7ecf..3bccc44 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_InducedOvulator.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_InducedOvulator.cs @@ -75,7 +75,7 @@ namespace RJW_Menstruation case Stage.Ovulatory: return true; case Stage.Luteal: - return IsEggExist && curStageHrs < Props.eggLifespanDays * 24; + return IsEggExist && curStageHrs < EggLifespanHours * 24; default: return false; } @@ -93,7 +93,7 @@ namespace RJW_Menstruation case Stage.Ovulatory: return true; case Stage.Luteal: - return IsEggExist && curStageHrs < Props.eggLifespanDays * 24; + return IsEggExist && curStageHrs < EggLifespanHours * 24; default: return false; } diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Menstruation.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Menstruation.cs index 5064dc2..070c6ef 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Menstruation.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Menstruation.cs @@ -126,6 +126,11 @@ namespace RJW_Menstruation // RJW pregnancy, or Biotech pregnancy/labor/laborpushing protected Hediff pregnancy = null; + protected int eggLifeSpanHours = 48; + protected EstrusLevel estrusLevel = EstrusLevel.Visible; + protected float ovulationFactor = 1f; + protected bool noBleeding = false; + private static readonly SimpleCurve SexFrequencyCurve = new SimpleCurve() { new CurvePoint(0.4f,0.05f), @@ -179,6 +184,7 @@ namespace RJW_Menstruation // Any exceptions in that will have been reported elsewhere in the code by now avglittersize = 1.0f; }; + avglittersize *= ovulationFactor; const float yearsBeforeMenopause = 6.0f; opcache = (int)(RaceCyclesPerYear() * avglittersize * @@ -470,6 +476,15 @@ namespace RJW_Menstruation return !eggs.NullOrEmpty(); } } + + public int EggLifespanHours + { + get + { + return eggLifeSpanHours; + } + } + public virtual bool IsDangerDay { get @@ -483,7 +498,7 @@ namespace RJW_Menstruation case Stage.Ovulatory: return true; case Stage.Luteal: - return curStageHrs < Props.eggLifespanDays * 24; + return curStageHrs < EggLifespanHours * 24; default: return false; } @@ -606,6 +621,28 @@ namespace RJW_Menstruation } } + public void Notify_UpdatedGenes() + { + eggLifeSpanHours = Props.eggLifespanDays * 24; + estrusLevel = Props.concealedEstrus ? EstrusLevel.Concealed : EstrusLevel.Visible; + ovulationFactor = 1f; + noBleeding = false; + + if (Pawn.genes == null || !ModsConfig.BiotechActive) return; + + if (Pawn.genes.HasGene(VariousDefOf.ShortEggLifetime)) eggLifeSpanHours = eggLifeSpanHours * 3 / 4; + else if (Pawn.genes.HasGene(VariousDefOf.DoubleEggLifetime)) eggLifeSpanHours *= 2; + else if (Pawn.genes.HasGene(VariousDefOf.QuadEggLifetime)) eggLifeSpanHours *= 4; + + if (Pawn.genes.HasGene(VariousDefOf.NeverEstrus)) estrusLevel = EstrusLevel.None; + else if (Pawn.genes.HasGene(VariousDefOf.FullEstrus)) estrusLevel = EstrusLevel.Visible; + + if (Pawn.genes.HasGene(VariousDefOf.DoubleOvulation)) ovulationFactor = 2f; + else if (Pawn.genes.HasGene(VariousDefOf.QuadOvulation)) ovulationFactor = 4f; + + noBleeding = Pawn.genes.HasGene(VariousDefOf.NoBleeding); + } + public bool ShouldSimulate() { if (!Configurations.EnableAnimalCycle && Pawn.IsAnimal()) return false; @@ -636,12 +673,12 @@ namespace RJW_Menstruation if (Pregnancy != null && curStage != Stage.Pregnant) { Log.Warning($"{Pawn}'s womb has a pregnancy, but was not in the pregnant stage"); - curStage = Stage.Pregnant; + GoNextStage(Stage.Pregnant); } BeforeSimulator(); - if (pregnancy == null && (Pawn.health.capacities.GetLevel(xxx.reproduction) <= 0 || EggHealth <= 0 || Pawn.SterileGenes())) curStage = Stage.Infertile; + if (pregnancy == null && (Pawn.health.capacities.GetLevel(xxx.reproduction) <= 0 || EggHealth <= 0 || Pawn.SterileGenes())) GoNextStage(Stage.Infertile); switch (curStage) { case Stage.Follicular: @@ -1029,6 +1066,8 @@ namespace RJW_Menstruation initError = true; Props = (CompProperties_Menstruation)props; + Notify_UpdatedGenes(); + if (Props.infertile) { if (cums == null) cums = new List(); @@ -1101,6 +1140,7 @@ namespace RJW_Menstruation { avglittersize = 1.0f; } + avglittersize *= ovulationFactor; float fertStartAge = Pawn.RaceProps.lifeStageAges?.Find(stage => stage.def.reproductive)?.minAge ?? 0.0f; float fertEndAge = Pawn.RaceProps.lifeExpectancy * (Pawn.IsAnimal() ? RJWPregnancySettings.fertility_endage_female_animal : RJWPregnancySettings.fertility_endage_female_humanlike); @@ -1160,7 +1200,7 @@ namespace RJW_Menstruation case Stage.Ovulatory: return true; case Stage.Luteal: - return curStageHrs < Props.eggLifespanDays * 24; + return curStageHrs < EggLifespanHours * 24; default: return false; } @@ -1169,12 +1209,13 @@ namespace RJW_Menstruation public EstrusLevel GetEstrusLevel() { if (!ShouldBeInEstrus()) return EstrusLevel.None; - else return Props.concealedEstrus ? EstrusLevel.Concealed : EstrusLevel.Visible; + else return estrusLevel; } public void SetEstrus() { - Hediff hediff = HediffMaker.MakeHediff(Props.concealedEstrus ? VariousDefOf.Hediff_Estrus_Concealed : VariousDefOf.Hediff_Estrus, Pawn); + if (estrusLevel == EstrusLevel.None) return; + Hediff hediff = HediffMaker.MakeHediff(estrusLevel == EstrusLevel.Concealed ? VariousDefOf.Hediff_Estrus_Concealed : VariousDefOf.Hediff_Estrus, Pawn); Pawn.health.AddHediff(hediff); } @@ -1256,8 +1297,19 @@ namespace RJW_Menstruation if (Configurations.Debug) Log.Message($"Implanting fertilized egg of {Pawn} into {parent}, father {egg.fertilizer}"); if (pregnancy != null) { - // TODO: Modified Biotech pregnancy - if (Configurations.PregnancySource == Configurations.PregnancyType.MultiplePregnancy && Configurations.EnableHeteroOvularTwins) + if (Configurations.PregnancySource == Configurations.PregnancyType.Biotech && Configurations.EnableBiotechTwins && Configurations.EnableHeteroOvularTwins) + { + if (Configurations.Debug) Log.Message($"Adding to existing Biotech pregnancy {pregnancy}"); + HediffComp_PregeneratedBabies comp = pregnancy.TryGetComp(); + if (comp == null) Log.Warning($"Trying to add Biotech egg to {Pawn}'s pregnancy without a pregenerated baby comp: {pregnancy}"); + else + { + comp.AddNewBaby(Pawn, egg.fertilizer); + pregnant = true; + deadeggs.Add(egg); + } + } + else if (Configurations.PregnancySource == Configurations.PregnancyType.MultiplePregnancy && Configurations.EnableHeteroOvularTwins) { if (pregnancy is Hediff_MultiplePregnancy h) { @@ -1299,8 +1351,12 @@ namespace RJW_Menstruation case Configurations.PregnancyType.Biotech: if (Configurations.Debug) Log.Message($"Creating new biotech pregnancy"); pregnancy = HediffMaker.MakeHediff(HediffDefOf.PregnantHuman, Pawn); + if(Configurations.EnableBiotechTwins) + pregnancy.TryGetComp().AddNewBaby(Pawn, egg.fertilizer); ((Hediff_Pregnant)pregnancy).SetParents(Pawn, egg.fertilizer, PregnancyUtility.GetInheritedGeneSet(egg.fertilizer, Pawn)); Pawn.health.AddHediff(pregnancy); + pregnant = true; + deadeggs.Add(egg); break; } if (pregnancy is Hediff_BasePregnancy rjw_preg) @@ -1320,7 +1376,9 @@ namespace RJW_Menstruation } } - if (pregnant && (Configurations.PregnancySource != Configurations.PregnancyType.MultiplePregnancy || !Configurations.EnableHeteroOvularTwins)) + if (pregnant && + (Configurations.PregnancySource != Configurations.PregnancyType.MultiplePregnancy || !Configurations.EnableHeteroOvularTwins) && + (Configurations.PregnancySource != Configurations.PregnancyType.Biotech || !Configurations.EnableBiotechTwins || !Configurations.EnableHeteroOvularTwins)) { eggs.Clear(); return true; @@ -1454,25 +1512,27 @@ namespace RJW_Menstruation protected virtual void OvulatoryAction() { estrusflag = false; - int eggnum; + float eggnum; + int ovulated; try { - eggnum = Math.Max((int)Rand.ByCurve(Pawn.def.race.litterSizeCurve), 1); + eggnum = Math.Max(Rand.ByCurve(Pawn.def.race.litterSizeCurve), 1f); } catch (NullReferenceException) { - eggnum = 1; + eggnum = 1f; } catch (ArgumentException e) { Log.Warning($"Invalid litterSizeCurve for {Pawn.def}: {e}"); - eggnum = 1; + eggnum = 1f; } - eggnum += eggstack; + eggnum *= ovulationFactor; + ovulated = (int)eggnum + eggstack; - for (int i = 0; i < eggnum; i++) - eggs.Add(new Egg((int)(Props.eggLifespanDays * 24 / CycleFactor))); - ovarypower -= eggnum; + for (int i = 0; i < ovulated; i++) + eggs.Add(new Egg((int)(EggLifespanHours * 24 / CycleFactor))); + ovarypower -= ovulated; eggstack = 0; if (EggHealth <= 0) @@ -1709,7 +1769,7 @@ namespace RJW_Menstruation protected void GoFollicularOrBleeding() { - if (Props.bleedingIntervalDays == 0) + if (Props.bleedingIntervalDays == 0 || noBleeding) { GoNextStage(Stage.Follicular); } @@ -1739,7 +1799,7 @@ namespace RJW_Menstruation return (int)(Props.recoveryIntervalDays * 24 * Rand.Range(0.95f, 1.05f)); case Stage.Pregnant: return (int)MenstruationUtility.GestationHours(pregnancy); - default: // Often unused + default: return 1; } } diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PeriodicOvulator.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PeriodicOvulator.cs index 11509de..b3fa2ac 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PeriodicOvulator.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PeriodicOvulator.cs @@ -33,7 +33,7 @@ namespace RJW_Menstruation // Make the cutoff halfway into cycle, just to be sure there isn't a double-cycle the first time if ((curStage == Stage.Follicular || curStage == Stage.Luteal || curStage == Stage.Bleeding) && (averageCycleIntervalHours - hoursToNextCycle) / 2 >= 24 * (Props.follicularIntervalDays + Props.lutealIntervalDays) / cycleSpeed) - curStage = Stage.Anestrus; + GoNextStage(Stage.Anestrus); } } @@ -75,14 +75,14 @@ namespace RJW_Menstruation base.PregnantAction(); if (curStage != Stage.Pregnant) // Go halfway into the cycle - hoursToNextCycle = (int)(averageCycleIntervalHours * Rand.Range(-cycleVariability, cycleVariability)) / 2; + hoursToNextCycle = (int)(averageCycleIntervalHours * (1 + Rand.Range(-cycleVariability, cycleVariability))) / 2; } protected override void AnestrusAction() { if (hoursToNextCycle <= 0) { - hoursToNextCycle = (int)(averageCycleIntervalHours * Rand.Range(-cycleVariability, cycleVariability)); + hoursToNextCycle = (int)(averageCycleIntervalHours * (1 + Rand.Range(-cycleVariability, cycleVariability))); if (IsBreedingSeason()) GoNextStage(Stage.Follicular); return; } diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PregeneratedBabies.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PregeneratedBabies.cs new file mode 100644 index 0000000..51b497a --- /dev/null +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PregeneratedBabies.cs @@ -0,0 +1,300 @@ +using HarmonyLib; +using Mono.Cecil.Cil; +using RimWorld; +using rjw; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using Verse; + +namespace RJW_Menstruation +{ + public class HediffComp_PregeneratedBabies : HediffComp + { + public List babies; + // Unused, but can't hurt to track + protected Dictionary 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(); + + // Send the babies from this comp to the new one + 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(); + 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(); + 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(); + PawnKindDef babyPawnKind = PregnancyCommon.BabyPawnKindDecider(mother, father, true); + PawnGenerationRequest request = new PawnGenerationRequest + ( + kind: babyPawnKind, + faction: mother.Faction, + allowDowned: true, + 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(); + for (int i = 0; i < division; i++) + { + Pawn baby = PawnGenerator.GeneratePawn(request); + if (baby == null) break; + PregnancyCommon.SetupBabyXenotype(mother, father, baby); // Probably redundant with Biotech post-birth xenotyping + 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) + { + 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); + // 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); + } + } + } + } + + [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()?.TryGetComp(); + if (comp == null || !comp.HasBaby) + return PawnGenerator.GeneratePawn(request); + + Pawn baby = comp.PopBaby(); + if (baby == null) return PawnGenerator.GeneratePawn(request); // Shouldn't happen + baby.ageTracker.AgeBiologicalTicks = 0; + baby.ageTracker.AgeChronologicalTicks = 0; + baby.babyNamingDeadline = Find.TickManager.TicksGame + GenDate.TicksPerDay; + 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 Transpiler(IEnumerable instructions) + { + if (birtherThing < 0) throw new InvalidOperationException("Could not locate index of birtherThing"); + if (GeneratePawn == null || GeneratePawn.ReturnType != typeof(Pawn)) throw new InvalidOperationException("GeneratePawn not found"); + 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; + } + } + } + + [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 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().TryGetComp(); + if (comp?.HasBaby ?? false) + { + OutcomeChance thisOutcome = outcome; + Precept_Ritual precept_Ritual = (Precept_Ritual)comp.Pawn.Ideo.GetPrecept(PreceptDefOf.ChildBirth); + float birthQuality = PregnancyUtility.GetBirthQualityFor(mother); + do + { + Pawn baby = comp.babies[0]; + Pawn thisFather = baby.GetFather(); + if (thisFather == null) thisFather = father; + 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); + // No more babies if mom dies halfway through. Unrealistic maybe, but saves a lot of headache in ApplyBirthOutcome + if (mother.health.Dead) break; + 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), typeof(Pawn), typeof(Thing), typeof(Pawn), typeof(Pawn), typeof(LordJob_Ritual), typeof(RitualRoleAssignments)}); + public static IEnumerable Transpiler(IEnumerable instructions) + { + if (ApplyBirthOutcome == null || ApplyBirthOutcome.ReturnType != typeof(Thing)) throw new InvalidOperationException("ApplyBirthOutcome not found"); + 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 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().TryGetComp(); + if (comp?.HasBaby ?? false) + { + // Much the same as the other one + + // 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 + { + Pawn baby = comp.babies[0]; + Pawn thisFather = baby.GetFather(); + if (thisFather == null) thisFather = father; + baby.relations.ClearAllRelations(); + + PregnancyUtility.ApplyBirthOutcome(outcome, quality, ritual, genes, geneticMother, birtherThing, thisFather, doctor, lordJobRitual, assignments); + if (mother.health.Dead) break; + } while (comp.HasBaby); + + // The ritual version doesn't use the return value, either + 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), typeof(Pawn), typeof(Thing), typeof(Pawn), typeof(Pawn), typeof(LordJob_Ritual), typeof(RitualRoleAssignments) }); + public static IEnumerable Transpiler(IEnumerable instructions) + { + if (ApplyBirthOutcome == null || ApplyBirthOutcome.ReturnType != typeof(Thing)) throw new InvalidOperationException("ApplyBirthOutcome not found"); + foreach (var instruction in instructions) + { + if (instruction.Calls(ApplyBirthOutcome)) + yield return CodeInstruction.Call(typeof(Ritual_ChildBirth_Apply_Patch), nameof(Ritual_ChildBirth_Apply_Patch.ApplyBirthLoop)); + else yield return instruction; + } + } + } + + // 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; + __result = 0; + return; + } + } +} diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/MenstruationUtility.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/MenstruationUtility.cs index 18a9604..43e2717 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/MenstruationUtility.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/MenstruationUtility.cs @@ -73,6 +73,29 @@ namespace RJW_Menstruation return null; } + [Obsolete("This method is obsolete and can cause ambiguity. Use GetMenstruationCompFromVagina or GetMenstruationCompFromPregnancy instead.", true)] + public static HediffComp_Menstruation GetMenstruationComp(Hediff hediff) + { + switch (hediff) + { + case Hediff_BasePregnancy rjwPreg: + return rjwPreg.GetMenstruationCompFromPregnancy(); + case Hediff_Pregnant vanillaPreg: + return vanillaPreg.GetMenstruationCompFromPregnancy(); + case Hediff_Labor vanillaLabor: + return vanillaLabor.GetMenstruationCompFromPregnancy(); + case Hediff_LaborPushing vanillaLaborPushing: + return vanillaLaborPushing.GetMenstruationCompFromPregnancy(); + case Hediff_PartBaseNatural rjwNatrual: + return rjwNatrual.GetMenstruationCompFromVagina(); + case Hediff_PartBaseArtifical rjwArtificial: + return rjwArtificial.GetMenstruationCompFromVagina(); + default: + Log.Warning("Obsolete GetMenstruationComp called with unknown hediff. Ensure your submods are up to date."); + return null; + } + } + public static HediffComp_Anus GetAnusComp(this Hediff hediff) { if (hediff is Hediff_PartBaseNatural || hediff is Hediff_PartBaseArtifical) @@ -92,13 +115,25 @@ namespace RJW_Menstruation if (hediff is Hediff_MechanoidPregnancy) return ContentFinder.Get(("Womb/Mechanoid_Fluid"), true); - ThingDef babydef = comp.Pawn.def; // TODO: Pregenerated babies float gestationProgress = comp.StageProgress; - int babycount = hediff is Hediff_BasePregnancy preg ? preg.babies.Count : 1; - if (hediff is Hediff_BasePregnancy h) + ThingDef babydef; + int babycount; + HediffComp_PregeneratedBabies babiescomp = hediff?.TryGetComp(); + if (hediff is Hediff_BasePregnancy preg) { - babydef = h.babies?.FirstOrDefault()?.def ?? ThingDefOf.Human; + babydef = preg.babies?.FirstOrDefault()?.def ?? ThingDefOf.Human; + babycount = preg.babies?.Count ?? 1; + } + else if (babiescomp?.HasBaby ?? false) + { + babydef = babiescomp.babies.First().def; + babycount = babiescomp.babies.Count; + } + else + { + babydef = comp.Pawn.def; + babycount = 1; } string fetustex = babydef.GetModExtension()?.fetusTexPath ?? "Fetus/Fetus_Default"; diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Hediff_MultiplePregnancy.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Hediff_MultiplePregnancy.cs index c1d995a..cab288a 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Hediff_MultiplePregnancy.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Hediff_MultiplePregnancy.cs @@ -13,9 +13,6 @@ namespace RJW_Menstruation { protected Dictionary enzygoticSiblings = new Dictionary(); // Each pawn and who they split from - protected readonly MethodInfo TryGetInheritedXenotype = typeof(PregnancyUtility).GetMethod("TryGetInheritedXenotype", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(Pawn), typeof(Pawn), typeof(XenotypeDef).MakeByRefType() }, null ); - protected readonly MethodInfo ShouldByHybrid = typeof(PregnancyUtility).GetMethod("ShouldByHybrid", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(Pawn), typeof(Pawn) }, null); - public override void DiscoverPregnancy() { PregnancyThought(); @@ -82,48 +79,6 @@ namespace RJW_Menstruation breastcomp?.GaveBirth(); } - public string GetBabyInfo() - { - if (babies.NullOrEmpty()) - return "Null"; - - StringBuilder res = new StringBuilder(); - - IEnumerable babiesdistinct = babies.Distinct(new RaceComparer()); - int iteration = 0; - foreach (Pawn baby in babiesdistinct) - { - int num = babies.Where(x => x.def.Equals(baby.def)).Count(); - if (iteration > 0) res.Append(", "); - res.AppendFormat("{0} {1}", num, baby.def.label); - iteration++; - } - res.AppendFormat(" {0}", Translations.Dialog_WombInfo02); - return res.ToString(); - } - - public string GetFatherInfo() - { - if (babies.NullOrEmpty()) - return "Null"; - - StringBuilder res = new StringBuilder(); - res.AppendFormat("{0}: ", Translations.Dialog_WombInfo03); - - if (!is_parent_known && Configurations.InfoDetail != Configurations.DetailLevel.All) - return res.Append(Translations.Dialog_FatherUnknown).ToString(); - - IEnumerable babiesdistinct = babies.Distinct(new FatherComparer(pawn)); - int iteration = 0; - foreach (Pawn baby in babiesdistinct) - { - if (iteration > 0) res.Append(", "); - res.Append(Utility.GetFather(baby, pawn)?.LabelShort ?? Translations.Dialog_FatherUnknown); - iteration++; - } - return res.ToString(); - } - private void HumanlikeBirth(Pawn baby) { Pawn mother = pawn; Pawn father = Utility.GetFather(baby, pawn); @@ -167,11 +122,12 @@ namespace RJW_Menstruation if (ModsConfig.BiotechActive) { // Ugly, but it'll have to do - OutcomeChance bestOutcome = RitualOutcomeEffectDefOf.ChildBirth.outcomeChances.Find(chance => chance.positivityIndex == 1); + 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 ); @@ -212,65 +168,12 @@ namespace RJW_Menstruation //baby.story.birthLastName = last_name; } - protected void CopyBodyPartProperties(Hediff part, Hediff originalPart) - { - CompHediffBodyPart comp = part.TryGetComp(); - CompHediffBodyPart originalComp = originalPart.TryGetComp(); - - if (comp != null && originalComp != null) - { - // the string properties should be the same between both pawns anyways, besides the name of the owner - part.Severity = originalPart.Severity; - comp.SizeBase = originalComp.SizeBase; - comp.SizeOwner = originalComp.SizeOwner; - comp.EffSize = originalComp.EffSize; - comp.FluidAmmount = originalComp.FluidAmmount; - comp.FluidModifier = originalComp.FluidModifier; - } - - HediffComp_Menstruation originalMenstruationComp = originalPart.GetMenstruationCompFromVagina(); - if (originalMenstruationComp != null) - { - part.GetMenstruationCompFromVagina()?.CopyCycleProperties(originalMenstruationComp); - } - HediffComp_Breast originalBreastComp = originalPart.GetBreastComp(); - if (originalBreastComp != null) - { - part.GetBreastComp()?.CopyBreastProperties(originalBreastComp); - } - } - - protected void CopyBodyPartRecord(Pawn baby, Pawn original, BodyPartRecord babyBPR, BodyPartRecord originalBPR) - { - if (babyBPR == null || originalBPR == null) return; - - RemoveBabyParts(baby, Genital_Helper.get_PartsHediffList(baby, babyBPR)); - foreach (Hediff originalPart in Genital_Helper.get_PartsHediffList(original, originalBPR)) - { - Hediff part = SexPartAdder.MakePart(originalPart.def, baby, babyBPR); - CopyBodyPartProperties(part, originalPart); - baby.health.AddHediff(part, babyBPR); - } - } - - // Baby is the sibling to be changed, original is the first of the set and the one to copy to the rest. - public virtual void ProcessIdenticalSibling(Pawn baby, Pawn original) - { - // They'll be the same pawnkind, which lets us make a lot of useful assumptions - // However, some RNG might still be involved in genital generation (e.g. futas), so the easiest method is to clear out and re-generate - // A bit wasteful since Hediff_BasePregnancy.PostBirth already redid the genitals - CopyBodyPartRecord(baby, original, Genital_Helper.get_genitalsBPR(baby), Genital_Helper.get_genitalsBPR(original)); - CopyBodyPartRecord(baby, original, Genital_Helper.get_breastsBPR(baby), Genital_Helper.get_breastsBPR(original)); - CopyBodyPartRecord(baby, original, Genital_Helper.get_uddersBPR(baby), Genital_Helper.get_uddersBPR(original)); - CopyBodyPartRecord(baby, original, Genital_Helper.get_anusBPR(baby), Genital_Helper.get_anusBPR(original)); - } - 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) - ProcessIdenticalSibling(baby, original); + PregnancyCommon.ProcessIdenticalSibling(baby, original); } // From RJW's trait code @@ -411,7 +314,7 @@ namespace RJW_Menstruation allowAddictions: false, relationWithExtraPawnChanceFactor: 0, fixedLastName: lastname, - kind: BabyPawnKindDecider(mother, father), + kind: PregnancyCommon.BabyPawnKindDecider(mother, father, false), //fixedIdeo: mother.Ideo, forbidAnyTitle: true, forceNoBackstory: true, @@ -428,31 +331,15 @@ namespace RJW_Menstruation { Pawn baby = GenerateBaby(request, mother, father, parentTraits, traitSeed); if (baby == null) break; - if (baby.genes != null && ModsConfig.BiotechActive) - { - if (GeneUtility.SameHeritableXenotype(mother, father) && mother.genes.UniqueXenotype) - { - baby.genes.xenotypeName = mother.genes.xenotypeName; - baby.genes.iconDef = mother.genes.iconDef; - } - - object[] args = new object[] { mother, father, null }; - if ((bool)TryGetInheritedXenotype.Invoke(null, args)) - { - baby.genes.SetXenotypeDirect((XenotypeDef)args[2]); - } - else if((bool)ShouldByHybrid.Invoke(null, new object[] { mother, father })) - { - baby.genes.hybrid = true; - baby.genes.xenotypeName = "Hybrid".Translate(); - } - } + 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) - { - if (baby.IsHAR()) // Have HAR determine the first baby's properties - baby.Drawer.renderer.graphics.ResolveAllGraphics(); + { + firstbaby = baby; request.FixedGender = baby.gender; request.ForcedEndogenes = baby.genes?.Endogenes.Select(gene => gene.def).ToList(); @@ -465,7 +352,10 @@ namespace RJW_Menstruation { 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) @@ -519,174 +409,6 @@ namespace RJW_Menstruation return baby; } - /// - /// Decide pawnkind from mother and father - /// Come from RJW - /// - /// - /// - /// - public PawnKindDef BabyPawnKindDecider(Pawn mother, Pawn father) - { - PawnKindDef motherKindDef = Utility.GetRacesPawnKind(mother); - PawnKindDef fatherKindDef = Utility.GetRacesPawnKind(father); - - PawnKindDef spawn_kind_def = motherKindDef; - - int flag = 0; - if (xxx.is_human(mother)) flag += 2; - if (xxx.is_human(father)) flag += 1; - //Mother - Father = Flag - //Human - Human = 3 - //Human - Animal = 2 - //Animal - Human = 1 - //Animal - Animal = 0 - - switch (flag) - { - case 3: - if (!Rand.Chance(RJWPregnancySettings.humanlike_DNA_from_mother)) spawn_kind_def = fatherKindDef; - break; - case 2: - if (RJWPregnancySettings.bestiality_DNA_inheritance == 0f) spawn_kind_def = fatherKindDef; - else if (!Rand.Chance(RJWPregnancySettings.bestial_DNA_from_mother)) spawn_kind_def = fatherKindDef; - break; - case 1: - if (RJWPregnancySettings.bestiality_DNA_inheritance == 1f) spawn_kind_def = fatherKindDef; - else if (!Rand.Chance(RJWPregnancySettings.bestial_DNA_from_mother)) spawn_kind_def = fatherKindDef; - break; - case 0: - if (!Rand.Chance(RJWPregnancySettings.bestial_DNA_from_mother)) spawn_kind_def = fatherKindDef; - break; - } - - bool IsAndroidmother = AndroidsCompatibility.IsAndroid(mother); - bool IsAndroidfather = AndroidsCompatibility.IsAndroid(father); - if (IsAndroidmother && !IsAndroidfather) - { - spawn_kind_def = fatherKindDef; - } - else if (!IsAndroidmother && IsAndroidfather) - { - spawn_kind_def = motherKindDef; - } - - string MotherRaceName = ""; - string FatherRaceName = ""; - MotherRaceName = motherKindDef?.race?.defName; - PawnKindDef non_hybrid_kind_def = spawn_kind_def; - if (father != null) - FatherRaceName = fatherKindDef?.race?.defName; - - - if (FatherRaceName != "" && Configurations.UseHybridExtention) - { - spawn_kind_def = GetHybrid(father, mother); - //Log.Message("pawnkind: " + spawn_kind_def?.defName); - } - - if (MotherRaceName != FatherRaceName && FatherRaceName != "") - { - if (!Configurations.UseHybridExtention || spawn_kind_def == null) - { - spawn_kind_def = non_hybrid_kind_def; - IEnumerable groups = DefDatabase.AllDefs.Where(x => !(x.hybridRaceParents.NullOrEmpty() || x.hybridChildKindDef.NullOrEmpty())); - - - //ModLog.Message(" found custom RaceGroupDefs " + groups.Count()); - foreach (RaceGroupDef t in groups) - { - if ((t.hybridRaceParents.Contains(MotherRaceName) && t.hybridRaceParents.Contains(FatherRaceName)) - || (t.hybridRaceParents.Contains("Any") && (t.hybridRaceParents.Contains(MotherRaceName) || t.hybridRaceParents.Contains(FatherRaceName)))) - { - //ModLog.Message(" has hybridRaceParents"); - if (t.hybridChildKindDef.Contains("MotherKindDef")) - spawn_kind_def = motherKindDef; - else if (t.hybridChildKindDef.Contains("FatherKindDef") && father != null) - spawn_kind_def = fatherKindDef; - else - { - //ModLog.Message(" trying hybridChildKindDef " + t.defName); - List child_kind_def_list = new List(); - child_kind_def_list.AddRange(DefDatabase.AllDefs.Where(x => t.hybridChildKindDef.Contains(x.defName))); - - //ModLog.Message(" found custom hybridChildKindDefs " + t.hybridChildKindDef.Count); - if (!child_kind_def_list.NullOrEmpty()) - spawn_kind_def = child_kind_def_list.RandomElement(); - } - } - } - } - - } - else if (!Configurations.UseHybridExtention || spawn_kind_def == null) - { - spawn_kind_def = mother.RaceProps?.AnyPawnKind ?? motherKindDef; - } - - if (spawn_kind_def.defName.Contains("Nymph")) - { - //child is nymph, try to find other PawnKindDef - List spawn_kind_def_list = new List(); - spawn_kind_def_list.AddRange(DefDatabase.AllDefs.Where(x => x.race == spawn_kind_def.race && !x.defName.Contains("Nymph"))); - //no other PawnKindDef found try mother - if (spawn_kind_def_list.NullOrEmpty()) - spawn_kind_def_list.AddRange(DefDatabase.AllDefs.Where(x => x.race == motherKindDef.race && !x.defName.Contains("Nymph"))); - //no other PawnKindDef found try father - if (spawn_kind_def_list.NullOrEmpty() && father != null) - spawn_kind_def_list.AddRange(DefDatabase.AllDefs.Where(x => x.race == fatherKindDef.race && !x.defName.Contains("Nymph"))); - //no other PawnKindDef found fallback to generic colonist - if (spawn_kind_def_list.NullOrEmpty()) - spawn_kind_def = PawnKindDefOf.Colonist; - - if (!spawn_kind_def_list.NullOrEmpty()) spawn_kind_def = spawn_kind_def_list.RandomElement(); - } - - return spawn_kind_def; - - } - - public PawnKindDef GetHybrid(Pawn first, Pawn second) - { - PawnKindDef res = null; - Pawn opposite = second; - HybridInformations info = null; - - - if (!Configurations.HybridOverride.NullOrEmpty()) - { - info = Configurations.HybridOverride.FirstOrDefault(x => x.DefName == first.def?.defName && (x.hybridExtension?.Exists(y => y.DefName == second.def?.defName) ?? false)); - if (info == null) - { - info = Configurations.HybridOverride.FirstOrDefault(x => x.DefName == second.def?.defName && (x.hybridExtension?.Exists(y => y.DefName == first.def?.defName) ?? false)); - opposite = first; - } - } - - if (info != null) - { - res = info.GetHybridWith(opposite.def.defName) ?? null; - } - if (res != null) return res; - - - PawnDNAModExtension dna; - dna = first.def.GetModExtension(); - if (dna != null) - { - res = dna.GetHybridWith(second.def.defName) ?? null; - } - else - { - dna = second.def.GetModExtension(); - if (dna != null) - { - res = dna.GetHybridWith(first.def.defName) ?? null; - } - } - return res; - } - /// /// Copy from RJW /// @@ -746,12 +468,6 @@ namespace RJW_Menstruation pawn.story.traits.allTraits = selectedTraits; } - public string DueDate() - { - if (pawn.Tile == -1) return ""; - return GenDate.DateFullStringWithHourAt(GenDate.TickGameToAbs((int)p_end_tick), Find.WorldGrid.LongLatOf(pawn.Tile)); - } - public override bool TryMergeWith(Hediff other) { return false; diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Biotech_Patch.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Biotech_Patch.cs index 4de0dfa..2ffe8b3 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Biotech_Patch.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Biotech_Patch.cs @@ -4,6 +4,8 @@ using RimWorld; using Verse; using System.Collections.Generic; using System.Reflection; +using System; +using System.Reflection.Emit; namespace RJW_Menstruation { @@ -24,8 +26,10 @@ namespace RJW_Menstruation public static void Postfix(Hediff_Pregnant __instance) { HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy(); + if (Configurations.Debug) Log.Message($"{comp.Pawn}'s labor starting, menstruation comp is {comp}"); if (comp == null) return; comp.Pregnancy = __instance.pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.PregnancyLabor); + if (Configurations.Debug) Log.Message($"New pregnancy Hediff is {comp.Pregnancy}"); } } @@ -35,8 +39,10 @@ namespace RJW_Menstruation public static void PostFix(Hediff_Labor __instance) { HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy(); + if (Configurations.Debug) Log.Message($"{comp.Pawn}'s initial labor ending, menstruation comp is {comp}"); if (comp == null) return; comp.Pregnancy = __instance.pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.PregnancyLaborPushing); + if (Configurations.Debug) Log.Message($"New pregnancy Hediff is {comp.Pregnancy}"); } } @@ -46,6 +52,7 @@ namespace RJW_Menstruation public static void PostFix(Hediff_LaborPushing __instance) { HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy(); + if (Configurations.Debug) Log.Message($"{comp.Pawn}'s labor pushing ending, menstruation comp is {comp}"); if (comp == null) return; comp.Pregnancy = null; } @@ -111,7 +118,7 @@ namespace RJW_Menstruation } [HarmonyPatch(typeof(PregnancyUtility), nameof(PregnancyUtility.ApplyBirthOutcome))] - public class ApplyBirthOutcome_Patch + public class ApplyBirthOutcome_Breast_Patch { public static void PostFix(Thing birtherThing) { @@ -120,9 +127,21 @@ namespace RJW_Menstruation } } - [HarmonyPatch(typeof(PregnancyUtility), nameof(PregnancyUtility.TryTerminatePregnancy))] - public class TryTerminatePregnancy_Patch + [HarmonyPatch] + public class TerminatePregnancy_Patch { + public static IEnumerable TargetMethods() + { + yield return AccessTools.Method(typeof(PregnancyUtility), nameof(PregnancyUtility.TryTerminatePregnancy)); + yield return AccessTools.Method(typeof(Recipe_TerminatePregnancy), nameof(Recipe_TerminatePregnancy.ApplyOnPawn)); + } + + private static PregnancyAttitude? GetAttitude(Hediff pregnancy) + { + if (pregnancy is Hediff_Pregnant preg) return preg.Attitude; + else return null; + } + private static Hediff GetEarliestPregnancy(Pawn pawn) { Hediff Earliest_Pregnancy = PregnancyUtility.GetPregnancyHediff(pawn); @@ -136,20 +155,30 @@ namespace RJW_Menstruation return Earliest_Pregnancy; } - private static readonly MethodInfo GetPregnancyHediff = AccessTools.Method(typeof(PregnancyUtility), nameof(PregnancyUtility.GetPregnancyHediff), new System.Type[] { typeof(Pawn) }); + private static readonly MethodInfo GetPregnancyHediff = AccessTools.Method(typeof(PregnancyUtility), nameof(PregnancyUtility.GetPregnancyHediff), new Type[] { typeof(Pawn) }); + private static readonly MethodInfo Get_Attitude = AccessTools.DeclaredPropertyGetter(typeof(Hediff_Pregnant), nameof(Hediff_Pregnant.Attitude)); - // Also called for Recipe_TerminatePregnancy.ApplyOnPawn public static IEnumerable Transpiler(IEnumerable instructions) { - if (GetPregnancyHediff == null || GetPregnancyHediff.ReturnType != typeof(Hediff)) throw new System.InvalidOperationException("GetPregnancyHediff not found"); + if (GetPregnancyHediff == null || GetPregnancyHediff.ReturnType != typeof(Hediff)) throw new InvalidOperationException("GetPregnancyHediff not found"); + if (Get_Attitude == null || Nullable.GetUnderlyingType(Get_Attitude.ReturnType) != typeof(PregnancyAttitude)) throw new InvalidOperationException("get_Attitude not found"); foreach (CodeInstruction instruction in instructions) { if (instruction.Calls(GetPregnancyHediff)) - yield return CodeInstruction.Call(typeof(TryTerminatePregnancy_Patch), nameof(TryTerminatePregnancy_Patch.GetEarliestPregnancy)); + yield return CodeInstruction.Call(typeof(TerminatePregnancy_Patch), nameof(TerminatePregnancy_Patch.GetEarliestPregnancy)); + // Menstruation pregnancies don't have an attitude, so skip the cast to Hediff_Pregnant and call a version that handles it + else if (instruction.opcode == OpCodes.Castclass && (Type)instruction.operand == typeof(Hediff_Pregnant)) + yield return new CodeInstruction(OpCodes.Nop); + else if (instruction.Calls(Get_Attitude)) + yield return CodeInstruction.Call(typeof(TerminatePregnancy_Patch), nameof(TerminatePregnancy_Patch.GetAttitude)); else yield return instruction; } } + } + [HarmonyPatch(typeof(PregnancyUtility), nameof(PregnancyUtility.TryTerminatePregnancy))] + public class PregnancyUtility_TryTerminatePregnancy_Patch + { public static void Postfix(bool __result, Pawn pawn) { if (__result) @@ -168,13 +197,51 @@ namespace RJW_Menstruation } } - [HarmonyPatch(typeof(Recipe_TerminatePregnancy), nameof(Recipe_TerminatePregnancy.ApplyOnPawn))] - public class TerminatePregnancy_ApplyOnPawn_Patch + [HarmonyPatch(typeof(Pawn_GeneTracker), "AddGene", new Type[] {typeof(Gene), typeof(bool)})] + public class AddGene_Patch { - public static IEnumerable Transpiler(IEnumerable instructions) + public static bool Prefix(ref Gene __result, Gene gene, Pawn ___pawn) { - return TryTerminatePregnancy_Patch.Transpiler(instructions); + if(VariousDefOf.WombGenes.Contains(gene.def) && !___pawn.GetMenstruationComps().Any()) + { + __result = null; + return false; + } + else return true; } } + [HarmonyPatch(typeof(Pawn_GeneTracker), "Notify_GenesChanged")] + public class Notify_GenesChanged_Patch + { + public static void Postfix(Pawn_GeneTracker __instance) + { + foreach (HediffComp_Menstruation comp in __instance.pawn.GetMenstruationComps()) + comp.Notify_UpdatedGenes(); + } + } + + //[HarmonyPatch(typeof(ChildcareUtility), nameof(ChildcareUtility.SuckleFromLactatingPawn))] + //public class GreedyConsume_Patch + //{ + // private static float ConsumeAndAdjustNipples(HediffComp_Chargeable instance, float desiredCharge) + // { + // // Pulling breast comp every tick might be too much for performance. + // const float averageNippleChangePerTick = 0.0025f / 50000f; + // instance.Pawn.GetBreastComp()?.AdjustNippleProgress(Rand.Range(0.0f, averageNippleChangePerTick * 2) * Configurations.MaxBreastIncrementFactor); + // return instance.GreedyConsume(desiredCharge); + // } + + // private static readonly MethodInfo GreedyConsume = AccessTools.Method(typeof(HediffComp_Chargeable), nameof(HediffComp_Chargeable.GreedyConsume), new Type[] { typeof(HediffComp_Chargeable), typeof(float) }); + // public static IEnumerable Transpiler(IEnumerable instructions) + // { + // if(GreedyConsume == null) throw new InvalidOperationException("GreedyConsume not found"); + // foreach (var instruction in instructions) + // { + // if (instruction.Calls(GreedyConsume)) + // yield return CodeInstruction.Call(typeof(GreedyConsume_Patch), nameof(GreedyConsume_Patch.ConsumeAndAdjustNipples)); + // yield return instruction; + // } + // } + //} } \ No newline at end of file diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Gizmo_Patch.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Gizmo_Patch.cs index e640659..7185ccf 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Gizmo_Patch.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Gizmo_Patch.cs @@ -48,8 +48,10 @@ namespace RJW_Menstruation { description .AppendFormat("{0}: {1}\n", comp.curStage, comp.curStageHrs); - if (comp.Pregnancy is Hediff_MultiplePregnancy preg) description - .AppendFormat("due: {0}\n", preg.DueDate()); + if (comp.Pregnancy is Hediff_BasePregnancy rjwpreg) description + .AppendFormat("due: {0}\n", rjwpreg.DueDate()); + else if (comp.Pregnancy is Hediff_Pregnant biopreg) description + .AppendFormat("due: {0}\n", biopreg.DueDate()); description .AppendFormat("fertcums: {0}\n" + "ovarypower: {1}\n" + diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Harmony.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Harmony.cs index d701d49..7194d0e 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Harmony.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Harmony.cs @@ -1,4 +1,5 @@ -using HarmonyLib; +using AlienRace; +using HarmonyLib; using rjw; using rjw.Modules.Interactions.Internals.Implementation; using rjw.Modules.Interactions.Rules.PartKindUsageRules; @@ -16,6 +17,11 @@ namespace RJW_Menstruation { Harmony har = new Harmony("RJW_Menstruation"); har.PatchAll(Assembly.GetExecutingAssembly()); + if (ModsConfig.IsActive("erdelf.HumanoidAlienRaces")) // Don't use the cached in Configurations, it isn't initialized yet + { + har.Patch(GenTypes.GetTypeInAnyAssembly("AlienRace.HarmonyPatches").GetMethod(nameof(AlienRace.HarmonyPatches.BirthOutcomeMultiplier)), + postfix: new HarmonyMethod(typeof(HAR_LitterSize_Undo).GetMethod(nameof(HAR_LitterSize_Undo.Postfix)))); + } InjectIntoRjwInteractionServices(); } diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/PregnancyCommon.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/PregnancyCommon.cs new file mode 100644 index 0000000..7cbf28c --- /dev/null +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/PregnancyCommon.cs @@ -0,0 +1,322 @@ +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 static class PregnancyCommon + { + private static readonly MethodInfo TryGetInheritedXenotype = typeof(PregnancyUtility).GetMethod("TryGetInheritedXenotype", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(Pawn), typeof(Pawn), typeof(XenotypeDef).MakeByRefType() }, null); + private static readonly MethodInfo ShouldByHybrid = typeof(PregnancyUtility).GetMethod("ShouldByHybrid", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(Pawn), typeof(Pawn) }, null); + + public static string GetBabyInfo(IEnumerable babies) + { + if (babies == null || !babies.Any()) return "Null"; + + StringBuilder res = new StringBuilder(); + + IEnumerable babiesdistinct = babies.Distinct(new RaceComparer()); + int iteration = 0; + foreach (Pawn baby in babiesdistinct) + { + int num = babies.Where(x => x.def.Equals(baby.def)).Count(); + if (iteration > 0) res.Append(", "); + res.AppendFormat("{0} {1}", num, baby.def.label); + iteration++; + } + res.AppendFormat(" {0}", Translations.Dialog_WombInfo02); + return res.ToString(); + } + + public static string GetFatherInfo(IEnumerable babies, Pawn mother, bool is_parent_known) + { + if (babies == null || !babies.Any()) return "Null"; + + StringBuilder res = new StringBuilder(); + res.AppendFormat("{0}: ", Translations.Dialog_WombInfo03); + + if (!is_parent_known && Configurations.InfoDetail != Configurations.DetailLevel.All) + return res.Append(Translations.Dialog_FatherUnknown).ToString(); + + IEnumerable babiesdistinct = babies.Distinct(new FatherComparer(mother)); + int iteration = 0; + foreach (Pawn baby in babiesdistinct) + { + if (iteration > 0) res.Append(", "); + res.Append(Utility.GetFather(baby, mother)?.LabelShort ?? Translations.Dialog_FatherUnknown); + iteration++; + } + return res.ToString(); + } + + /// + /// Decide pawnkind from mother and father + /// Come from RJW + /// + /// + /// + /// + + /// + public static PawnKindDef BabyPawnKindDecider(Pawn mother, Pawn father, bool noAnimalsFromHumanlikes) + { + PawnKindDef motherKindDef = Utility.GetRacesPawnKind(mother); + PawnKindDef fatherKindDef = Utility.GetRacesPawnKind(father); + + PawnKindDef spawn_kind_def = motherKindDef; + + int flag = 0; + if (xxx.is_human(mother)) flag += 2; + if (xxx.is_human(father)) flag += 1; + //Mother - Father = Flag + //Human - Human = 3 + //Human - Animal = 2 + //Animal - Human = 1 + //Animal - Animal = 0 + + switch (flag) + { + case 3: + if (!Rand.Chance(RJWPregnancySettings.humanlike_DNA_from_mother)) spawn_kind_def = fatherKindDef; + break; + case 2: + if (RJWPregnancySettings.bestiality_DNA_inheritance == 0f) spawn_kind_def = fatherKindDef; + else if (!Rand.Chance(RJWPregnancySettings.bestial_DNA_from_mother)) spawn_kind_def = fatherKindDef; + break; + case 1: + if (RJWPregnancySettings.bestiality_DNA_inheritance == 1f) spawn_kind_def = fatherKindDef; + else if (!Rand.Chance(RJWPregnancySettings.bestial_DNA_from_mother)) spawn_kind_def = fatherKindDef; + break; + case 0: + if (!Rand.Chance(RJWPregnancySettings.bestial_DNA_from_mother)) spawn_kind_def = fatherKindDef; + break; + } + + bool IsAndroidmother = AndroidsCompatibility.IsAndroid(mother); + bool IsAndroidfather = AndroidsCompatibility.IsAndroid(father); + if (IsAndroidmother && !IsAndroidfather) + { + spawn_kind_def = fatherKindDef; + } + else if (!IsAndroidmother && IsAndroidfather) + { + spawn_kind_def = motherKindDef; + } + + string MotherRaceName = ""; + string FatherRaceName = ""; + MotherRaceName = motherKindDef?.race?.defName; + PawnKindDef non_hybrid_kind_def = spawn_kind_def; + if (father != null) + FatherRaceName = fatherKindDef?.race?.defName; + + + if (FatherRaceName != "" && Configurations.UseHybridExtention) + { + spawn_kind_def = GetHybrid(father, mother); + //Log.Message("pawnkind: " + spawn_kind_def?.defName); + } + + if (MotherRaceName != FatherRaceName && FatherRaceName != "") + { + if (!Configurations.UseHybridExtention || spawn_kind_def == null) + { + spawn_kind_def = non_hybrid_kind_def; + IEnumerable groups = DefDatabase.AllDefs.Where(x => !(x.hybridRaceParents.NullOrEmpty() || x.hybridChildKindDef.NullOrEmpty())); + + + //ModLog.Message(" found custom RaceGroupDefs " + groups.Count()); + foreach (RaceGroupDef t in groups) + { + if ((t.hybridRaceParents.Contains(MotherRaceName) && t.hybridRaceParents.Contains(FatherRaceName)) + || (t.hybridRaceParents.Contains("Any") && (t.hybridRaceParents.Contains(MotherRaceName) || t.hybridRaceParents.Contains(FatherRaceName)))) + { + //ModLog.Message(" has hybridRaceParents"); + if (t.hybridChildKindDef.Contains("MotherKindDef")) + spawn_kind_def = motherKindDef; + else if (t.hybridChildKindDef.Contains("FatherKindDef") && father != null) + spawn_kind_def = fatherKindDef; + else + { + //ModLog.Message(" trying hybridChildKindDef " + t.defName); + List child_kind_def_list = new List(); + child_kind_def_list.AddRange(DefDatabase.AllDefs.Where(x => t.hybridChildKindDef.Contains(x.defName))); + + //ModLog.Message(" found custom hybridChildKindDefs " + t.hybridChildKindDef.Count); + if (!child_kind_def_list.NullOrEmpty()) + spawn_kind_def = child_kind_def_list.RandomElement(); + } + } + } + } + + } + else if (!Configurations.UseHybridExtention || spawn_kind_def == null) + { + spawn_kind_def = mother.RaceProps?.AnyPawnKind ?? motherKindDef; + } + + if (spawn_kind_def.defName.Contains("Nymph")) + { + //child is nymph, try to find other PawnKindDef + List spawn_kind_def_list = new List(); + spawn_kind_def_list.AddRange(DefDatabase.AllDefs.Where(x => x.race == spawn_kind_def.race && !x.defName.Contains("Nymph"))); + //no other PawnKindDef found try mother + if (spawn_kind_def_list.NullOrEmpty()) + spawn_kind_def_list.AddRange(DefDatabase.AllDefs.Where(x => x.race == motherKindDef.race && !x.defName.Contains("Nymph"))); + //no other PawnKindDef found try father + if (spawn_kind_def_list.NullOrEmpty() && father != null) + spawn_kind_def_list.AddRange(DefDatabase.AllDefs.Where(x => x.race == fatherKindDef.race && !x.defName.Contains("Nymph"))); + //no other PawnKindDef found fallback to generic colonist + if (spawn_kind_def_list.NullOrEmpty()) + spawn_kind_def = PawnKindDefOf.Colonist; + + if (!spawn_kind_def_list.NullOrEmpty()) spawn_kind_def = spawn_kind_def_list.RandomElement(); + } + + // If both parents are humanlike, Biotech will attempt to assign genes to the child + // Normally not a problem, but with the hybrid system, two humanlikes might produce an animal + // So override it and force the child to be human + if (noAnimalsFromHumanlikes && mother.genes != null && father?.genes != null && !spawn_kind_def.race.race.Humanlike) + spawn_kind_def = Rand.Chance(RJWPregnancySettings.humanlike_DNA_from_mother) ? motherKindDef : fatherKindDef; + + return spawn_kind_def; + + } + + public static void SetupBabyXenotype(Pawn mother, Pawn father, Pawn baby) + { + if (baby.genes == null || !ModsConfig.BiotechActive) return; + + if (GeneUtility.SameHeritableXenotype(mother, father) && mother.genes.UniqueXenotype) + { + baby.genes.xenotypeName = mother.genes.xenotypeName; + baby.genes.iconDef = mother.genes.iconDef; + } + + object[] args = new object[] { mother, father, null }; + if ((bool)TryGetInheritedXenotype.Invoke(null, args)) + { + baby.genes.SetXenotypeDirect((XenotypeDef)args[2]); + } + else if ((bool)ShouldByHybrid.Invoke(null, new object[] { mother, father })) + { + baby.genes.hybrid = true; + baby.genes.xenotypeName = "Hybrid".Translate(); + } + } + + public static PawnKindDef GetHybrid(Pawn first, Pawn second) + { + PawnKindDef res = null; + Pawn opposite = second; + HybridInformations info = null; + + + if (!Configurations.HybridOverride.NullOrEmpty()) + { + info = Configurations.HybridOverride.FirstOrDefault(x => x.DefName == first.def?.defName && (x.hybridExtension?.Exists(y => y.DefName == second.def?.defName) ?? false)); + if (info == null) + { + info = Configurations.HybridOverride.FirstOrDefault(x => x.DefName == second.def?.defName && (x.hybridExtension?.Exists(y => y.DefName == first.def?.defName) ?? false)); + opposite = first; + } + } + + if (info != null) + { + res = info.GetHybridWith(opposite.def.defName) ?? null; + } + if (res != null) return res; + + + PawnDNAModExtension dna; + dna = first.def.GetModExtension(); + if (dna != null) + { + res = dna.GetHybridWith(second.def.defName) ?? null; + } + else + { + dna = second.def.GetModExtension(); + if (dna != null) + { + res = dna.GetHybridWith(first.def.defName) ?? null; + } + } + return res; + } + + private static void CopyBodyPartProperties(Hediff part, Hediff originalPart) + { + CompHediffBodyPart comp = part.TryGetComp(); + CompHediffBodyPart originalComp = originalPart.TryGetComp(); + + if (comp != null && originalComp != null) + { + // the string properties should be the same between both pawns anyways, besides the name of the owner + part.Severity = originalPart.Severity; + comp.SizeBase = originalComp.SizeBase; + comp.SizeOwner = originalComp.SizeOwner; + comp.EffSize = originalComp.EffSize; + comp.FluidAmmount = originalComp.FluidAmmount; + comp.FluidModifier = originalComp.FluidModifier; + } + + HediffComp_Menstruation originalMenstruationComp = originalPart.GetMenstruationCompFromVagina(); + if (originalMenstruationComp != null) + { + part.GetMenstruationCompFromVagina()?.CopyCycleProperties(originalMenstruationComp); + } + HediffComp_Breast originalBreastComp = originalPart.GetBreastComp(); + if (originalBreastComp != null) + { + part.GetBreastComp()?.CopyBreastProperties(originalBreastComp); + } + } + + private static void CopyBodyPartRecord(Pawn baby, Pawn original, BodyPartRecord babyBPR, BodyPartRecord originalBPR) + { + if (babyBPR == null || originalBPR == null) return; + + Hediff_BasePregnancy.RemoveBabyParts(baby, Genital_Helper.get_PartsHediffList(baby, babyBPR)); + foreach (Hediff originalPart in Genital_Helper.get_PartsHediffList(original, originalBPR)) + { + Hediff part = SexPartAdder.MakePart(originalPart.def, baby, babyBPR); + CopyBodyPartProperties(part, originalPart); + baby.health.AddHediff(part, babyBPR); + } + } + + // Baby is the sibling to be changed, original is the first of the set and the one to copy to the rest. + public static void ProcessIdenticalSibling(Pawn baby, Pawn original) + { + // They'll be the same pawnkind, which lets us make a lot of useful assumptions + // However, some RNG might still be involved in genital generation (e.g. futas), so the easiest method is to clear out and re-generate + // A bit wasteful since Hediff_BasePregnancy.PostBirth already redid the genitals + CopyBodyPartRecord(baby, original, Genital_Helper.get_genitalsBPR(baby), Genital_Helper.get_genitalsBPR(original)); + CopyBodyPartRecord(baby, original, Genital_Helper.get_breastsBPR(baby), Genital_Helper.get_breastsBPR(original)); + CopyBodyPartRecord(baby, original, Genital_Helper.get_uddersBPR(baby), Genital_Helper.get_uddersBPR(original)); + CopyBodyPartRecord(baby, original, Genital_Helper.get_anusBPR(baby), Genital_Helper.get_anusBPR(original)); + } + + public static string DueDate(this Hediff_BasePregnancy preg) + { + if (preg.pawn.Tile == -1) return ""; + return GenDate.DateFullStringWithHourAt(GenDate.TickGameToAbs((int)preg.p_end_tick), Find.WorldGrid.LongLatOf(preg.pawn.Tile)); + } + + public static string DueDate(this Hediff_Pregnant preg) + { + if (preg.pawn.Tile == -1) return ""; + int ticksRemaining = (int)((1f - preg.GestationProgress) * preg.pawn.RaceProps.gestationPeriodDays * GenDate.TicksPerDay); + int dueTickAbs = GenTicks.TicksAbs + ticksRemaining; + return GenDate.DateFullStringWithHourAt(dueTickAbs, Find.WorldGrid.LongLatOf(preg.pawn.Tile)); + } + } +} diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/RJW_Menstruation.csproj b/1.4/source/RJW_Menstruation/RJW_Menstruation/RJW_Menstruation.csproj index 1a35c67..0266ab8 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/RJW_Menstruation.csproj +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/RJW_Menstruation.csproj @@ -69,12 +69,14 @@ + + diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Translations.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Translations.cs index 8534281..7b0862d 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Translations.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Translations.cs @@ -125,6 +125,8 @@ namespace RJW_Menstruation public static readonly string Option_PregnancyFromBaseRJW_Label = "Option_PregnancyFromBaseRJW_Label".Translate(); public static readonly string Option_PregnancyFromMultiplePregnancy_Label = "Option_PregnancyFromMultiplePregnancy_Label".Translate(); public static readonly string Option_PregnancyFromBiotech_Label = "Option_PregnancyFromBiotech_Label".Translate(); + public static readonly string Option_EnableBiotechTwins_Label = "Option_EnableBiotechTwins_Label".Translate(); + public static readonly string Option_EnableBiotechTwins_Desc = "Option_EnableBiotechTwins_Desc".Translate(); public static readonly string Option_EnableDraftedIcon_Label = "Option_EnableDraftedIcon_Label".Translate(); public static readonly string Option_EnableDraftedIcon_Desc = "Option_EnableDraftedIcon_Desc".Translate(); diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/UI/Dialog_WombStatus.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/UI/Dialog_WombStatus.cs index 8a8e380..4d428a9 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/UI/Dialog_WombStatus.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/UI/Dialog_WombStatus.cs @@ -172,8 +172,8 @@ namespace RJW_Menstruation Pawn fetus = comp.GetFetus(); if (fetus != null && Utility.ShowFetusInfo()) { - string feinfo = m.GetBabyInfo(); - string fainfo = m.GetFatherInfo() + " "; + string feinfo = PregnancyCommon.GetBabyInfo(m.babies); + string fainfo = PregnancyCommon.GetFatherInfo(m.babies, m.pawn, m.is_parent_known) + " "; if (feinfo.Length + fainfo.Length > 45) { preginfoheight = fontheight + 2; @@ -221,19 +221,36 @@ namespace RJW_Menstruation { if (p is Hediff_Pregnant hp && hp.Severity < 0.2f) cum = comp.GetCumIcon(); else cum = ContentFinder.Get("Womb/Empty", true); - // TODO: Pregenerated babies (base on multiplepregnancy) + HediffComp_PregeneratedBabies babiescomp = p.TryGetComp(); if (Utility.ShowFetusInfo()) { - preginfoheight = fontheight; + string feinfo = PregnancyCommon.GetBabyInfo(babiescomp?.babies); + string fainfo = PregnancyCommon.GetFatherInfo(babiescomp?.babies, babiescomp.Pawn, true) + " "; // Keep all parents known, for now + + if (feinfo == "Null") feinfo = "1 " + p.Mother.def.label + " " + Translations.Dialog_WombInfo02; + if (fainfo == "Null") + { + string father = p.Father?.LabelShort ?? Translations.Dialog_FatherUnknown; + fainfo = Translations.Dialog_WombInfo03 + ": " + father + " "; + } + + if (feinfo.Length + fainfo.Length > 45) + { + preginfoheight = fontheight + 2; + buttonstyle.alignment = TextAnchor.UpperLeft; + fontstyleright.alignment = TextAnchor.LowerRight; + } + else + { + preginfoheight = fontheight; + buttonstyle.alignment = TextAnchor.MiddleLeft; + + } + Rect preginfo = new Rect(0f, mainRect.yMax - wombRectHeight - 2, wombRectWidth, preginfoheight); fontstyleright.normal.textColor = Color.white; - fontstyleright.alignment = TextAnchor.MiddleRight; - buttonstyle.alignment = TextAnchor.MiddleLeft; - - string father = p.Father?.LabelShort ?? Translations.Dialog_FatherUnknown; - - GUI.Box(preginfo, "1 " + p.Mother.def.label + " " + Translations.Dialog_WombInfo02, buttonstyle); - GUI.Label(preginfo, Translations.Dialog_WombInfo03 + ": " + father + " ", fontstyleright); + GUI.Box(preginfo, feinfo, buttonstyle); + GUI.Label(preginfo, fainfo, fontstyleright); } } else cum = ContentFinder.Get(("Womb/Empty"), true); @@ -377,7 +394,7 @@ namespace RJW_Menstruation anal = pawn.GetAnalIcon(showOrigin); GUI.color = new Color(1.00f, 0.47f, 0.47f, 1); GUI.Box(rect, "", boxstyle); - GUI.color = pawn.story.SkinColor; + GUI.color = Utility.SafeSkinColor(pawn); //Widgets.DrawTextureFitted(genitalIconRect, anal, 1.0f); //Widgets.DrawTextureFitted(genitalIconRect, vagina, 1.0f); GUI.DrawTexture(genitalIconRect, anal, ScaleMode.ScaleToFit); diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Utility.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Utility.cs index 73cdf7e..406a54c 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Utility.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Utility.cs @@ -188,8 +188,8 @@ namespace RJW_Menstruation return null; } } - // TODO: Biotech pregenerated babies - return null; + HediffComp_PregeneratedBabies babiescomp = comp.Pregnancy.TryGetComp(); + return babiescomp?.babies?.FirstOrDefault(); } public static void DrawBreastIcon(this Pawn pawn, Rect rect) @@ -209,7 +209,7 @@ namespace RJW_Menstruation nipple = ContentFinder.Get("Breasts/Breast_Breast00_Nipple00", false); areola = ContentFinder.Get("Breasts/Breast_Breast00_Areola00", false); - GUI.color = pawn.story?.SkinColor ?? Color.white; + GUI.color = SafeSkinColor(pawn); GUI.DrawTexture(rect, breast, ScaleMode.ScaleToFit); GUI.color = Color.white; GUI.DrawTexture(rect, areola, ScaleMode.ScaleToFit); @@ -236,7 +236,7 @@ namespace RJW_Menstruation breast = ContentFinder.Get(icon, false); areola = ContentFinder.Get(areolaicon, false); nipple = ContentFinder.Get(nippleicon, false); - GUI.color = pawn.story.SkinColor; + GUI.color = SafeSkinColor(pawn); GUI.DrawTexture(rect, breast, ScaleMode.ScaleToFit); GUI.color = comp.NippleColor; @@ -255,7 +255,7 @@ namespace RJW_Menstruation nipple = ContentFinder.Get("Breasts/Breast_Breast00_Nipple00", false); areola = ContentFinder.Get("Breasts/Breast_Breast00_Areola00", false); - GUI.color = pawn.story.SkinColor; + GUI.color = SafeSkinColor(pawn); GUI.DrawTexture(rect, breast, ScaleMode.ScaleToFit); GUI.color = Color.white; GUI.DrawTexture(rect, areola, ScaleMode.ScaleToFit); @@ -454,5 +454,18 @@ namespace RJW_Menstruation if (pawn.IsAnimal() && !Configurations.EnableAnimalCycle) return false; return true; } + + // Apparently with some mods, even doing pawn.story?.SkinColor can throw a null reference, so we're stuck doing this + public static Color SafeSkinColor(Pawn pawn) + { + try + { + return pawn.story?.SkinColor ?? Color.white; + } + catch (NullReferenceException) + { + return Color.white; + } + } } } diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/VariousDefOf.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/VariousDefOf.cs index 5e00858..7f368cc 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/VariousDefOf.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/VariousDefOf.cs @@ -44,8 +44,24 @@ namespace RJW_Menstruation public static readonly RecordDef AmountofCreampied = DefDatabase.GetNamed("AmountofCreampied"); public static readonly RecordDef AmountofFertilizedEggs = DefDatabase.GetNamed("AmountofFertilizedEggs"); public static readonly TaleDef TaleCameInside = DefDatabase.GetNamed("CameInside"); + public static readonly GeneDef ShortEggLifetime = DefDatabase.GetNamed("Menstruation_ShortEggLifetime"); + public static readonly GeneDef DoubleEggLifetime = DefDatabase.GetNamed("Menstruation_DoubleEggLifetime"); + public static readonly GeneDef QuadEggLifetime = DefDatabase.GetNamed("Menstruation_QuadEggLifetime"); + public static readonly GeneDef NeverEstrus = DefDatabase.GetNamed("Menstruation_NeverEstrus"); + public static readonly GeneDef FullEstrus = DefDatabase.GetNamed("Menstruation_FullEstrus"); + public static readonly GeneDef DoubleOvulation = DefDatabase.GetNamed("Menstruation_DoubleOvulation"); + public static readonly GeneDef QuadOvulation = DefDatabase.GetNamed("Menstruation_QuadOvulation"); + public static readonly GeneDef NoBleeding = DefDatabase.GetNamed("Menstruation_NoBleeding"); - + public static readonly HashSet WombGenes = new HashSet() { + ShortEggLifetime, + DoubleEggLifetime, + QuadEggLifetime, + NeverEstrus, + FullEstrus, + DoubleOvulation, + QuadOvulation, + NoBleeding }; private static List allraces = null; private static List allkinds = null; diff --git a/About/Manifest.xml b/About/Manifest.xml index 13e6eed..015d8cc 100644 --- a/About/Manifest.xml +++ b/About/Manifest.xml @@ -1,7 +1,7 @@ RJW Menstruation - 1.0.8.5 + 1.0.8.6 diff --git a/changelogs.txt b/changelogs.txt index c14cc2c..b2c69dd 100644 --- a/changelogs.txt +++ b/changelogs.txt @@ -1,3 +1,10 @@ +Version 1.0.8.6 + - Updated Traditional Chinese translation by Hydrogen. + - Fix error when trying to terminate a non-Biotech pregnancy. + - Properly give the opportunity to name a newborn with Biotech and multiple pregnancy. + - Added several menstruation-related genes, with a placeholder graphic for now. + - Added experimental support for twins and hybrids with Biotech pregnancies, disabled by default. + Version 1.0.8.5 - Added biosculpter recipe to restore 1 year's worth of eggs, with icon by DestinyPlayer. - Vaginal sex with the "avoid pregnancy" relation will (usually) pull out prevent cum from entering the womb if there's a risk of pregnancy.