From 97c153f468c2df2cfdbcf601c1e91ada73a49158 Mon Sep 17 00:00:00 2001 From: Vegapnk Date: Sun, 16 Jul 2023 08:54:01 +0200 Subject: [PATCH] Added a Gene for Gender Fluid Pawns --- CHANGELOG.md | 8 +- .../GeneDefs/GeneDefs_SpecifiedGender.xml | 29 +++ Source/GeneDefOf.cs | 6 +- .../Genes/Gender/Defs/GenderFluidExtension.cs | 20 ++ Source/Genes/Gender/GenderUtility.cs | 3 +- Source/Genes/Gender/Gene_GenderFluid.cs | 235 ++++++++++++++++++ Source/Rjw-Genes.csproj | 6 +- 7 files changed, 302 insertions(+), 5 deletions(-) create mode 100644 Source/Genes/Gender/Defs/GenderFluidExtension.cs create mode 100644 Source/Genes/Gender/Gene_GenderFluid.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index d921f5c..a0529fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ -# 1.2.2 +# 1.3.0 **Changes:** - New Gene for Evergrowing Cocks. Be careful. +- New Gene for Genderfluid Pawns - daily chance to change sex. Futas just change "display" and keep genitalia, other pawns switch genitalia. **Fixes:** @@ -10,6 +11,11 @@ - Copy of Infis patch for eating cum from sexperience, #41 and #48 - Updated some Icons to have better backgrounds (thanks @WasmachenDennSachenSo #53) +*Notes*: +The pawns that are gender fluid can get pregnant. +However, with RJW 5.3.7 these pregnancies disappear. +This is a change needed upstream, but I will have a look. + # 1.2.1 **Fixes**: diff --git a/Common/Defs/GeneDefs/GeneDefs_SpecifiedGender.xml b/Common/Defs/GeneDefs/GeneDefs_SpecifiedGender.xml index fae1f90..4434560 100644 --- a/Common/Defs/GeneDefs/GeneDefs_SpecifiedGender.xml +++ b/Common/Defs/GeneDefs/GeneDefs_SpecifiedGender.xml @@ -46,4 +46,33 @@ + + + rjw_genes_gender_fluid + rjw_genes_gender + + Everyday carriers of this gene might change their biological sex. + 0 + Genes/Icons/Futa + RJW_Genes.Gene_GenderFluid + 2 + + +
  • AG_Gender
  • +
  • Gender
  • +
    + + +
  • + Genes/Icons/RJW_Genes_Endogene_Background + Genes/Icons/RJW_Genes_Xenogene_Background +
  • +
  • + + 1000 + 1 +
  • +
    +
    + \ No newline at end of file diff --git a/Source/GeneDefOf.cs b/Source/GeneDefOf.cs index 1bc9770..559c828 100644 --- a/Source/GeneDefOf.cs +++ b/Source/GeneDefOf.cs @@ -32,9 +32,10 @@ namespace RJW_Genes public static readonly GeneDef rjw_genes_extra_anus; public static readonly GeneDef rjw_genes_no_anus; public static readonly GeneDef rjw_genes_futa; + public static readonly GeneDef rjw_genes_featureless_chest; - // Genitalia Sizes - public static readonly GeneDef rjw_genes_big_male_genitalia; + // Genitalia Sizes + public static readonly GeneDef rjw_genes_big_male_genitalia; public static readonly GeneDef rjw_genes_small_male_genitalia; public static readonly GeneDef rjw_genes_loose_female_genitalia; public static readonly GeneDef rjw_genes_tight_female_genitalia; @@ -46,6 +47,7 @@ namespace RJW_Genes // Gender public static readonly GeneDef rjw_genes_female_only; public static readonly GeneDef rjw_genes_male_only; + public static readonly GeneDef rjw_genes_gender_fluid; // Breeding public static readonly GeneDef rjw_genes_mechbreeder; diff --git a/Source/Genes/Gender/Defs/GenderFluidExtension.cs b/Source/Genes/Gender/Defs/GenderFluidExtension.cs new file mode 100644 index 0000000..332015f --- /dev/null +++ b/Source/Genes/Gender/Defs/GenderFluidExtension.cs @@ -0,0 +1,20 @@ +using Verse; + +namespace RJW_Genes +{ + public class GenderFluidExtension : DefModExtension + { + /// + /// Number of ticks until the change can be triggered. + /// Just being "triggered" does not mean changing, see the changeChance below. + /// + public int changeInterval; + + /// + /// How high is the chance to change gender? + /// Set to 1 for "always", set to 0 for "never". + /// Everything else is a bit statistics, but e.g. when set to .5 the chances grow per day from [50%, 75%, 82.25%, ...] + /// + public float changeChance; + } +} \ No newline at end of file diff --git a/Source/Genes/Gender/GenderUtility.cs b/Source/Genes/Gender/GenderUtility.cs index 10b811d..700f3c9 100644 --- a/Source/Genes/Gender/GenderUtility.cs +++ b/Source/Genes/Gender/GenderUtility.cs @@ -31,7 +31,8 @@ namespace RJW_Genes } /// - /// Adjusts the Body Type to match the given target gender + /// Adjusts the Body Type to match the given target gender. + /// This is only for "drawing" attributes of the pawn, the genitalia are untouched at this point. /// (for male and female only, baby,child and hulks don't change) /// /// diff --git a/Source/Genes/Gender/Gene_GenderFluid.cs b/Source/Genes/Gender/Gene_GenderFluid.cs new file mode 100644 index 0000000..064a778 --- /dev/null +++ b/Source/Genes/Gender/Gene_GenderFluid.cs @@ -0,0 +1,235 @@ +using RimWorld; +using rjw; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Verse; +using Verse.AI; + +namespace RJW_Genes +{ + + /* + * Once per day (or slightly different per configuration) checks if the pawn changes gender. + * At the triggered tick, there is a random chance to change gender. + * This will swap genitalia, appearance and breasts. + * + * For some situations, the pawn better not change genitalia, e.g. while having vaginal sex. This would throw errors. + * For these cases a check is implemented, and if there was a block the change happens a bit later when "unblocked". + * + * TODO: Keep pregnancies. + * The pregnancies remove their things on Tick at the end, which kills it for male pawns. + * This seems to be an upstream RJW thing, but needs a bit of investigation. + */ + public class Gene_GenderFluid : RJW_Gene + { + + //public const int CHANGE_INTERVAL_FALLBACK = 1000; // Test value for Quick Trials + const int CHANGE_INTERVAL_FALLBACK = 60000; // 60k == 1 day + const float SWITCH_CHANCE_FALLBACK = 0.25f; + + int change_interval; + float switch_chance; + + List storedBreasts = new List(); + + private bool sexChangeWasBlocked = false; + + public Gene_GenderFluid() : base() { + GenderFluidExtension genderFluidExt = GeneDefOf.rjw_genes_gender_fluid.GetModExtension(); + change_interval = genderFluidExt?.changeInterval ?? CHANGE_INTERVAL_FALLBACK; + switch_chance = genderFluidExt?.changeChance ?? SWITCH_CHANCE_FALLBACK; + } + + public override void Tick() + { + base.Tick(); + + // Case 1: We had a blocked SexChange, now Pawn is free, apply sexchange a bit delayed. + if (pawn.IsHashIntervalTick(1500) && sexChangeWasBlocked && !SexChangeBlocked(pawn)){ + ChangeSex(); + sexChangeWasBlocked = false; + } + // Case 2: Check every interval if the Chance triggers + else if (pawn.IsHashIntervalTick(change_interval) && (new Random()).NextDouble() < switch_chance) + { + + // Case 2.A) SexChange was blocked, postpone it + if (SexChangeBlocked(pawn)) + { + sexChangeWasBlocked |= true; + return; + } + // Case 2.B) Nothing blocking, change the sex. + else { ChangeSex();} + } + + } + + private void ChangeSex() + { + if (rjw.Genital_Helper.is_futa(pawn)) + { + // Handle Futa Pawns - Keep Genitalia as is, just change RW Gender + pawn.gender = pawn.gender == Gender.Male? Gender.Female: Gender.Male; + } + // Handle Non-Futas - Change Genitalia and Store breasts. + else + { + if (pawn.gender == Gender.Female) + { + if (RJW_Genes_Settings.rjw_genes_detailed_debug) ModLog.Message($"genderfluid pawn {pawn} is changing from female to male"); + SwitchToMale(); + } + else if (pawn.gender == Gender.Male) + { + if (RJW_Genes_Settings.rjw_genes_detailed_debug) ModLog.Message($"genderfluid pawn {pawn} is changing from male to female"); + SwitchToFemale(); + } + } + GenderUtility.RemoveAllSexChangeThoughts(pawn); + } + + private void SwitchToFemale() + { + // Change Drawing + GenderUtility.AdjustBodyToTargetGender(pawn, Verse.Gender.Female); + // Change Gender + pawn.gender = Verse.Gender.Female; + + // Switch Penisses to Vaginas + var genitalsToRemove = pawn.GetGenitalsList().FindAll(g => Genital_Helper.is_penis(g) || Genital_Helper.is_vagina(g)); + foreach (var genital in genitalsToRemove) + { + var genitaliaHediffDef = GenitaliaUtility.GetVaginaForGene(GenitaliaUtility.GetGenitaliaTypeGeneForPawn(pawn)); + float size = genital.Severity; + pawn.health.RemoveHediff(genital); + + var newVagina = HediffMaker.MakeHediff(genitaliaHediffDef, pawn, Genital_Helper.get_genitalsBPR(pawn)); + pawn.health.AddHediff(newVagina); + newVagina.Severity = size; + } + + SwitchBreasts(); + } + + private void SwitchToMale() + { + // Change Drawing + GenderUtility.AdjustBodyToTargetGender(pawn, Verse.Gender.Male); + // Change Gender + pawn.gender = Verse.Gender.Male; + + // Switch Vaginas to Penisses + var genitalsToRemove = pawn.GetGenitalsList().FindAll(g => Genital_Helper.is_penis(g) || Genital_Helper.is_vagina(g)); + foreach (var genital in genitalsToRemove) + { + var genitaliaHediffDef = GenitaliaUtility.GetPenisForGene(GenitaliaUtility.GetGenitaliaTypeGeneForPawn(pawn)); + float size = genital.Severity; + pawn.health.RemoveHediff(genital); + + var newPenis = HediffMaker.MakeHediff(genitaliaHediffDef, pawn, Genital_Helper.get_genitalsBPR(pawn)); + pawn.health.AddHediff(newPenis); + newPenis.Severity = size; + } + + SwitchBreasts(); + } + + + private void SwitchBreasts() + { + List current_breasts = pawn.GetBreastList(); + + // Stored_Breasts can be empty when the pawn first ever switches gender! + if (storedBreasts.NullOrEmpty()) + { + foreach (var breasts in current_breasts) + { + // Is Male, and does not have the "no breast gene" + if (pawn.gender == Gender.Male && !GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_no_breasts)) + { + storedBreasts.Add(CreateNewBreasts()); + } + else if (pawn.gender == Gender.Female && !GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_featureless_chest) ) + { + storedBreasts.Add(CreateNewBreasts()); + } + } + } + + + foreach (var breast in current_breasts) + { + pawn.health.RemoveHediff(breast); + } + foreach (var breast in storedBreasts) + { + pawn.health.AddHediff(breast); + } + + storedBreasts.Clear(); + storedBreasts.AddRange(current_breasts); + } + + + internal Hediff CreateNewBreasts() + { + var correctGene = GenitaliaUtility.GetGenitaliaTypeGeneForPawn(pawn); + var breastDef = GenitaliaUtility.GetBreastsForGene(correctGene); + var partBPR = Genital_Helper.get_breastsBPR(pawn); + var additional_breasts = HediffMaker.MakeHediff(breastDef, pawn,partBPR); + + var CompHediff = additional_breasts.TryGetComp(); + if (CompHediff != null) + { + CompHediff.initComp(pawn); + CompHediff.updatesize(); + } + + return additional_breasts; + } + + /// + /// There are some actions that block sex change, + /// being drafted or having sex. + /// + /// The pawn that want to sexchange. + /// False if the SexChange is applicable, True if there needs to be a wait timer. + internal bool SexChangeBlocked(Pawn pawn) + { + // DEVNOTE: This list might extend on new cases, thus the explicit method. + return pawn == null + || pawn.health.Dead + || (pawn.jobs.curDriver is JobDriver_Masturbate) + || (pawn.jobs.curDriver is JobDriver_Sex) + || (pawn.jobs.curDriver is JobDriver_SexBaseReciever) + || (pawn.jobs.curDriver is JobDriver_SexBaseInitiator) + || (pawn.jobs.curDriver is JobDriver_JoinInBed) + + || (pawn.jobs.curDriver is JobDriver_SexQuick) + || (pawn.jobs.curDriver is JobDriver_SexBaseRecieverQuickie) + || (pawn.jobs.curDriver is JobDriver_SexOnSpot) + || (pawn.jobs.curDriver is JobDriver_SexOnSpotReciever) + + || (pawn.jobs.curDriver is JobDriver_Knotted) + || (pawn.jobs.curDriver is JobDriver_Mate) + || (pawn.jobs.curDriver is JobDriver_Mating) + || (pawn.jobs.curDriver is JobDriver_Breeding) + + || (pawn.jobs.curDriver is JobDriver_Rape) + || (pawn.jobs.curDriver is JobDriver_SexBaseRecieverRaped) + || (pawn.jobs.curDriver is JobDriver_RandomRape) + || (pawn.jobs.curDriver is JobDriver_RapeComfortPawn) + || (pawn.jobs.curDriver is JobDriver_RapeEnemy) + || pawn.jobs.curDriver is JobDriver_Lovin + + // This is a heavy check, but this is necessary because sometimes the pawns go somewhere to have sex and then they start despite missing genitalia! + || (pawn.jobs.curDriver is JobDriver_Goto) + + || pawn.Drafted; + } + } +} diff --git a/Source/Rjw-Genes.csproj b/Source/Rjw-Genes.csproj index c14c538..e7c8bc8 100644 --- a/Source/Rjw-Genes.csproj +++ b/Source/Rjw-Genes.csproj @@ -44,6 +44,8 @@ + + @@ -217,6 +219,8 @@ False - + + + \ No newline at end of file