diff --git a/CHANGELOG.md b/CHANGELOG.md
index c177c58..5b63394 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
# 2.2.0 (dd-mm-2024)
-**Genetic Diseases**
+## Explanations
+
+**Genetic Diseases**:
This update introduces genetic diseases that are shared on sex.
Infection is handled when sex finishes, so a coitus-interruptus will not result in infections.
@@ -20,14 +22,65 @@ Most of the genes so far were positive or neutral,
so I got some fair requests to introduce negative genes to keep xenotypes balanced.
I know that this is some overlap with the STD mod, but well ... you are free to turn things off?
+**Genetic Infectors**:
+
+These Genes can apply a genetic disease, but are not genetic diseases themselves.
+A single infector gene can have multiple resulting diseases, see this extension example:
+
+```xml
+
+ 0.05
+
+
rjw_genes_size_blinded
+
rjw_genes_infectious_bisexuality
+
+
+```
+
+The infection-chance is applied per gene - for the example above there would be a 5% chance to apply `size_blinded` and 5% chance to apply `infectious_bisexuality`.
+Multiple infections can happen on a single sex.
+The `infectionGenes` can be any gene, this is not limited to genetic diseases (e.g. ugly or something).
+
+*Infectors* are always applied even if the genetic disease spread is turned off.
+The created genetic diseases will follow the logic outlined above.
+
+**Disease Immunity**:
+
+Pawns can be immune to genetic diseases.
+
+This is either done with a specialised gene (`rjw_genes_genetic_disease_immunity`)
+or by genes giving specific immunities.
+
+Any gene can give immunity against any genetic disease using an extension:
+
+```xml
+
+
+
rjw_genes_size_blinded
+
+
+```
+
+These extensions can be slapped on any gene,
+but they are meant mostly to have infectors immune against their own diseases.
+
+## Changelog
+
**Additions:**
-- Passive Gene: *Genetic Disease Immunity* - cannot get infected by any genetic diseases, and won't be affected by some other genes (see relevant genes)
+- Passive Gene: Genetic Disease Immunity. cannot get infected by any genetic diseases, and won't be affected by some other genes (see relevant genes)
- Disease Gene: Vulnerability. Pawn is likelier to be raped
- Disease Gene: Infectious Hypersexuality
- Disease Gene: Infectious Homosexuality & Bisexuality
+- Disease Gene: Infectious lower fertility
+- Disease Gene: Infectious higher sex need
- Disease Gene: Fluctual Sexual Need. (Configurable) Chance to reset sex-need to near-zero and gain a bit of rest-need.
- Disease Gene: Size Blinded. Pawns have a higher chance for hooking up with pawns with a big cock, lower chance for small cocks.
+- Infector Gene: Genetic Stretcher. Pawns can infect other pawns with *Size Blinded*
+- Gene: Hardwired Progenity. Pawns with this get a malus on having no-children, and bonus on having a lot.
+- Gene: Sexual Genetic Swap. Pawns have a chance to switch a random gene with their sexpartner.
+- (Archite) Gene: Sexual Genetic Thief. Pawns have a chance to steal a gene from their sexpartner. Genetic Disease Immunity shields against this.
+- Pawns will have negative thoughts about pawns with more genetic diseases than themselves.
**Fixes:**
diff --git a/Common/Defs/GeneDefs/GeneDefs_Breeding.xml b/Common/Defs/GeneDefs/GeneDefs_Breeding.xml
index bb6fbe4..b3a9844 100644
--- a/Common/Defs/GeneDefs/GeneDefs_Breeding.xml
+++ b/Common/Defs/GeneDefs/GeneDefs_Breeding.xml
@@ -115,4 +115,41 @@
-1
+
+ rjw_genes_hardwired_progenity
+
+ Carriers of this gene need to procreate. They suffer negative effects if they are childless, and have increased capabilities if they reach a high amount of offsprings.
+ Genes/Icons/RJW_Genes_PheromoneSpit
+ 70
+
+
+
+
+ -0.1
+ 1.2
+ 1.2
+ -0.15
+ -0.1
+
+
+
+
+ 0.05
+ 0.1
+ 0.1
+ 0.4
+
+
+
+
+ 0.15
+ 0.25
+ 0.25
+
+
+
+
+ 1
+ 0
+
\ No newline at end of file
diff --git a/Common/Defs/GeneDefs/GeneDefs_Diseases.xml b/Common/Defs/GeneDefs/GeneDefs_Diseases.xml
index ecc3ee8..b9c1a45 100644
--- a/Common/Defs/GeneDefs/GeneDefs_Diseases.xml
+++ b/Common/Defs/GeneDefs/GeneDefs_Diseases.xml
@@ -85,6 +85,69 @@
+
+ rjw_genes_infectious_low_fertility
+
+ Carriers of this genetic disease have lower fertility.
+ 1
+ 1
+ 0.9
+ UI/Icons/ColonistBar/Idle
+ 5
+
+
+ -0.15
+
+
+
+
+ 0.05
+
+
+
+
+
+ rjw_genes_infectious_increased_sex_need
+
+ Carriers of this genetic disease need more sex.
+ 0
+ 1
+ 0.9
+ UI/Icons/ColonistBar/Idle
+ 5
+
+
+ 0.15
+
+
+
+
+ 0.08
+
+
+
+
+
+ rjw_genes_infectious_major_increased_sex_need
+
+ Carriers of this genetic disease need a lot more sex.
+ 1
+ 2
+ 0.85
+ UI/Icons/ColonistBar/Idle
+ 5
+
+
+ 1.0
+
+
+
+
+ 0.03
+
+
+
+
rjw_genes_infectious_hypersexuality
@@ -202,4 +265,27 @@
+
+ rjw_genes_stretcher
+
+ Pawns with this gene have a chance to alter the genes of their sexual partners to prefer large cocks.
+ UI/Icons/ColonistBar/Idle
+ 1
+ 0
+ 12
+
+
+
+
rjw_genes_size_blinded
+
+
+
+ 0.05
+
+
rjw_genes_size_blinded
+
+
+
+
+
\ No newline at end of file
diff --git a/Common/Defs/GeneDefs/GeneDefs_SexSpecial.xml b/Common/Defs/GeneDefs/GeneDefs_SexSpecial.xml
index 189a6c8..0fe012a 100644
--- a/Common/Defs/GeneDefs/GeneDefs_SexSpecial.xml
+++ b/Common/Defs/GeneDefs/GeneDefs_SexSpecial.xml
@@ -140,7 +140,7 @@
rjw_genes_sex_tamer
-
+
sextamerBestiality has a chance to tame animals or advance their training.Genes/Icons/RJW_Genes_SexualTamer
@@ -149,4 +149,35 @@
-1
+
+ rjw_genes_sexual_genetic_swap
+
+ Carriers with this gene may switch a gene with their sex-partner. Switched Genes are always endogenes.
+ UI/Icons/Genes/Gene_PsychicBonding
+ 20
+ 3
+ 0
+
+
+ 0.1
+
+
+
+
+
+ rjw_genes_sexual_genetic_thief
+
+ Carriers with this gene may steal a gene from their sex-partner. Stolen genes are always xenogenes.
+ UI/Icons/Genes/Gene_PsychicBonding
+ 21
+ 5
+ -2
+ 1
+
+
+ 0.2
+
+
+
+
\ No newline at end of file
diff --git a/Common/Defs/ThoughtDefs/Thoughts_Disease.xml b/Common/Defs/ThoughtDefs/Thoughts_Disease.xml
index d0721ee..8997a52 100644
--- a/Common/Defs/ThoughtDefs/Thoughts_Disease.xml
+++ b/Common/Defs/ThoughtDefs/Thoughts_Disease.xml
@@ -24,4 +24,26 @@
+
+ rjw_genes_has_more_diseases
+ Thought_SituationalSocial
+ RJW_Genes.ThoughtWorker_HasMoreDiseasesThanMe_Social
+ true
+
+
+
+
+ -3
+
+
+
+ -8
+
+
+
+ -18
+
+
+
+
diff --git a/Common/Languages/English/Keyed/StatsReports.xml b/Common/Languages/English/Keyed/StatsReports.xml
new file mode 100644
index 0000000..715bccb
--- /dev/null
+++ b/Common/Languages/English/Keyed/StatsReports.xml
@@ -0,0 +1,8 @@
+
+
+
+ Pawn doesn't have any children.
+ Pawn has a decent amount of children.
+ Pawn has a lot of children.
+
+
diff --git a/Source/Common/Defs/ChanceExtension.cs b/Source/Common/Defs/ChanceExtension.cs
new file mode 100644
index 0000000..ff8b260
--- /dev/null
+++ b/Source/Common/Defs/ChanceExtension.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+ public class ChanceExtension : DefModExtension
+ {
+ public float chance;
+ }
+
+}
diff --git a/Source/GeneDefOf.cs b/Source/GeneDefOf.cs
index 2e59f20..873df6f 100644
--- a/Source/GeneDefOf.cs
+++ b/Source/GeneDefOf.cs
@@ -73,6 +73,7 @@ namespace RJW_Genes
public static readonly GeneDef rjw_genes_fervent_ovipositor;
public static readonly GeneDef rjw_genes_insectbreeder;
public static readonly GeneDef rjw_genes_insectincubator;
+ public static readonly GeneDef rjw_genes_hardwired_progenity;
// Cum
public static readonly GeneDef rjw_genes_no_cum;
@@ -104,6 +105,8 @@ namespace RJW_Genes
public static readonly GeneDef rjw_genes_hormonal_saliva;
public static readonly GeneDef rjw_genes_cocoonweaver;
public static readonly GeneDef rjw_genes_sex_tamer;
+ public static readonly GeneDef rjw_genes_sexual_genetic_swap;
+ public static readonly GeneDef rjw_genes_sexual_genetic_thief;
// Cosmetic
public static readonly GeneDef rjw_genes_succubus_tail;
diff --git a/Source/Genes/Breeding/ConditionalStatAffecters/ConditionalStatAffecter_ManyChildren.cs b/Source/Genes/Breeding/ConditionalStatAffecters/ConditionalStatAffecter_ManyChildren.cs
new file mode 100644
index 0000000..c483eb3
--- /dev/null
+++ b/Source/Genes/Breeding/ConditionalStatAffecters/ConditionalStatAffecter_ManyChildren.cs
@@ -0,0 +1,38 @@
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+ public class ConditionalStatAffecter_ManyChildren : ConditionalStatAffecter
+ {
+ public override string Label => (string)"StatsReport_ManyChildren".Translate();
+
+ public const int THRESHOLD_FOR_CHILDREN = 3;
+
+ public override bool Applies(StatRequest req)
+ {
+ if (req == null || req.Thing == null || !req.Thing.Spawned) return false;
+
+ if (req.Thing is Pawn pawn)
+ {
+ // Do nothing if Pawn is Baby or Child (#25)
+ if (!pawn.ageTracker.Adult)
+ return false;
+
+ if (GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_hardwired_progenity))
+ {
+ // This "middle" Conditional Stat Affecter only fires if the other one does not apply
+ return pawn.relations.ChildrenCount >= THRESHOLD_FOR_CHILDREN
+ && pawn.relations.ChildrenCount < ConditionalStatAffecter_VeryManyChildren.THRESHOLD_FOR_CHILDREN;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/Source/Genes/Breeding/ConditionalStatAffecters/ConditionalStatAffecter_NoChildren.cs b/Source/Genes/Breeding/ConditionalStatAffecters/ConditionalStatAffecter_NoChildren.cs
new file mode 100644
index 0000000..4f50994
--- /dev/null
+++ b/Source/Genes/Breeding/ConditionalStatAffecters/ConditionalStatAffecter_NoChildren.cs
@@ -0,0 +1,43 @@
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+ ///
+ /// This conditional stat affecter "fires" if the pawn has no children.
+ ///
+ /// DevNote: I salvaged this from 1.3.3 Halamyr Conditional Stat Affecters.
+ /// It seems that with RW 1.5 there was a change how these work, as the req.Pawn seems to be null.
+ /// Now, the pawn is in req.Thing.
+ ///
+ public class ConditionalStatAffecter_NoChildren : ConditionalStatAffecter
+ {
+ public override string Label => (string)"StatsReport_NoChildren".Translate();
+
+ public override bool Applies(StatRequest req)
+ {
+ if (req == null || req.Thing == null || !req.Thing.Spawned) return false;
+
+ if (req.Thing is Pawn pawn)
+ {
+ // Do nothing if Pawn is Baby or Child (#25)
+ if (!pawn.ageTracker.Adult)
+ return false;
+
+ if (GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_hardwired_progenity))
+ {
+ return pawn.relations.ChildrenCount == 0;
+ }
+ }
+
+ return false;
+ }
+
+ }
+
+}
diff --git a/Source/Genes/Breeding/ConditionalStatAffecters/ConditionalStatAffecter_VeryManyChildren.cs b/Source/Genes/Breeding/ConditionalStatAffecters/ConditionalStatAffecter_VeryManyChildren.cs
new file mode 100644
index 0000000..e14d4f1
--- /dev/null
+++ b/Source/Genes/Breeding/ConditionalStatAffecters/ConditionalStatAffecter_VeryManyChildren.cs
@@ -0,0 +1,36 @@
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+ public class ConditionalStatAffecter_VeryManyChildren : ConditionalStatAffecter
+ {
+ public override string Label => (string)"StatsReport_VeryManyChildren".Translate();
+
+ public const int THRESHOLD_FOR_CHILDREN = 8;
+
+ public override bool Applies(StatRequest req)
+ {
+ if (req == null || req.Thing == null || !req.Thing.Spawned) return false;
+
+ if (req.Thing is Pawn pawn)
+ {
+ // Do nothing if Pawn is Baby or Child (#25)
+ if (!pawn.ageTracker.Adult)
+ return false;
+
+ if (GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_hardwired_progenity))
+ {
+ return pawn.relations.ChildrenCount >= THRESHOLD_FOR_CHILDREN;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/Source/Genes/Diseases/Defs/GeneticInfectorExtension.cs b/Source/Genes/Diseases/Defs/GeneticInfectorExtension.cs
new file mode 100644
index 0000000..2f00ce5
--- /dev/null
+++ b/Source/Genes/Diseases/Defs/GeneticInfectorExtension.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+ public class GeneticInfectorExtension : DefModExtension
+ {
+ public float infectionChance;
+
+ public List infectionGenes;
+ }
+
+}
diff --git a/Source/Genes/Diseases/Defs/ImmunityAgainstGenesExtension.cs b/Source/Genes/Diseases/Defs/ImmunityAgainstGenesExtension.cs
new file mode 100644
index 0000000..18fe8e0
--- /dev/null
+++ b/Source/Genes/Diseases/Defs/ImmunityAgainstGenesExtension.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+ public class ImmunityAgainstGenesExtension : DefModExtension
+ {
+ ///
+ /// A list of the exact defnames of disease-genes that this extension will make immune against.
+ /// Must perfectly match!
+ ///
+ public List givesImmunityAgainst;
+ }
+
+}
diff --git a/Source/Genes/Diseases/DiseaseHelper.cs b/Source/Genes/Diseases/DiseaseHelper.cs
new file mode 100644
index 0000000..873a0e1
--- /dev/null
+++ b/Source/Genes/Diseases/DiseaseHelper.cs
@@ -0,0 +1,145 @@
+using rjw;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+ public static class DiseaseHelper
+ {
+
+ ///
+ /// Checks for a pawn if it is immune against a disease.
+ ///
+ /// The pawn for which immunity is checked
+ /// The genetic disease that is checked against
+ /// True if the pawn is immune, false if the pawn can be infected by it.
+ public static bool IsImmuneAgainstGeneticDisease(Pawn pawn, GeneDef disease)
+ {
+ // Case 1: Something is null / not working, return Immune (to have less follow up effects)
+ if (pawn == null || pawn.genes == null) return true;
+ if (disease == null) return true;
+ // Case 1.B: Dead people can spread, but not receive, diseases.
+ if (pawn.Dead) return true;
+
+ // Case 2: The pawn has general genetic immunity to diseases
+ if (GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_genetic_disease_immunity))
+ return true;
+
+ // Case 3: The pawn already has the genetic disease
+ if (GeneUtility.HasGeneNullCheck(pawn, disease))
+ return true;
+
+ // Case 4: Check all genes if one of them has the Immunity Extension that covers the GeneDef
+ List genes = pawn.genes.GenesListForReading;
+ genes = genes.Where(x => pawn.genes.HasActiveGene(x.def)).ToList();
+
+ foreach (Gene gene in genes)
+ {
+ ImmunityAgainstGenesExtension ext = gene.def.GetModExtension();
+ if (ext != null) {
+ foreach (string defname in ext.givesImmunityAgainst)
+ if (disease.defName == defname)
+ return true;
+ }
+ }
+
+ // Case 5: Nothing special happens, so return false (not immune)
+ return false;
+ }
+
+ ///
+ /// Returns all active Genes with the `GeneticDiseaseExtension`.
+ ///
+ ///
+ /// List of all active Genes with the `GeneticDiseaseExtension` in pawn
+ public static List GetGeneticDiseaseGenes(Pawn pawn)
+ {
+ if (pawn != null && pawn.genes != null)
+ {
+ return pawn.genes
+ .GenesListForReading
+ .ConvertAll(gene => gene.def)
+ .Where(genedef => pawn.genes.HasActiveGene(genedef))
+ .Where(IsGeneticDiseaseGene)
+ .ToList();
+ }
+
+ return new List() { };
+ }
+
+ public static List GetGeneticInfectorGenes(Pawn pawn)
+ {
+ if (pawn != null && pawn.genes != null)
+ {
+ return pawn.genes
+ .GenesListForReading
+ .ConvertAll(gene => gene.def)
+ .Where(genedef => pawn.genes.HasActiveGene(genedef))
+ .Where(IsGeneticInfectorGene)
+ .ToList();
+ }
+
+ return new List() { };
+ }
+
+ public static List LookupInfectionGeneDefs(GeneticInfectorExtension infectorExt)
+ {
+ if (infectorExt == null) new List();
+
+ return RimWorld.GeneUtility
+ .GenesInOrder
+ .Where(genedef => infectorExt.infectionGenes.Contains(genedef.defName))
+ .ToList();
+ }
+
+ ///
+ /// Checks if the performed sex was penetrative.
+ /// Condom check is not done here!
+ ///
+ /// The sexprops
+ ///
+ public static bool IsPenetrativeSex(SexProps props)
+ {
+ if (props == null) return false;
+
+ return props.sexType ==
+ xxx.rjwSextype.Vaginal
+ || props.sexType == xxx.rjwSextype.Anal
+ || props.sexType == xxx.rjwSextype.Oral
+ || props.sexType == xxx.rjwSextype.DoublePenetration
+ || props.sexType == xxx.rjwSextype.Fellatio
+ || props.sexType == xxx.rjwSextype.Sixtynine;
+ }
+
+ public static bool IsGeneticDiseaseGene(GeneDef geneDef)
+ {
+ if (geneDef == null) return false;
+ GeneticDiseaseExtension diseaseExt = geneDef.GetModExtension();
+ return diseaseExt != null;
+ }
+
+ public static bool IsGeneticInfectorGene(GeneDef geneDef)
+ {
+ if (geneDef == null) return false;
+ GeneticInfectorExtension infectorExt = geneDef.GetModExtension();
+ return infectorExt != null;
+ }
+
+ public static float LookupDiseaseInfectionChance(GeneDef geneDef)
+ {
+ if (IsGeneticDiseaseGene(geneDef))
+ {
+ GeneticDiseaseExtension diseaseExt = geneDef.GetModExtension();
+ return diseaseExt != null ? diseaseExt.infectionChance : 0.0f;
+ }
+ else
+ return 0.0f;
+ }
+
+
+ }
+}
diff --git a/Source/Genes/Diseases/Patches/Patch_AfterSexUtility_ApplyGeneticInfectors.cs b/Source/Genes/Diseases/Patches/Patch_AfterSexUtility_ApplyGeneticInfectors.cs
new file mode 100644
index 0000000..1ee1e9f
--- /dev/null
+++ b/Source/Genes/Diseases/Patches/Patch_AfterSexUtility_ApplyGeneticInfectors.cs
@@ -0,0 +1,57 @@
+using HarmonyLib;
+using rjw;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Remoting.Lifetime;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+
+ [HarmonyPatch(typeof(SexUtility), "Aftersex")]
+ public class Patch_AfterSexUtility_ApplyGeneticInfectors
+ {
+ public static void Postfix(SexProps props)
+ {
+ if (props == null || props.pawn == null || props.partner == null) return;
+
+ Pawn pawn = props.pawn;
+ Pawn partner = props.partner;
+
+ if (pawn == partner) return;
+ if (pawn.IsAnimal() || partner.IsAnimal()) return;
+ if (pawn.genes == null || partner.genes == null) return;
+ // No Infections on Condom Use
+ if (props.usedCondom) return;
+
+ // Exit early if settings require penetrative sex, but this is not penetrative sex
+ if (!DiseaseHelper.IsPenetrativeSex(props) && RJW_Genes_Settings.rjw_genes_genetic_disease_spread_only_on_penetrative_sex) return;
+
+ TryApplyGeneticInfections(pawn, partner);
+ TryApplyGeneticInfections(partner, pawn);
+ }
+
+ private static void TryApplyGeneticInfections(Pawn infector, Pawn partner)
+ {
+ foreach (GeneDef infectorGeneDef in DiseaseHelper.GetGeneticInfectorGenes(infector))
+ {
+ GeneticInfectorExtension diseaseExt = infectorGeneDef.GetModExtension();
+ if (diseaseExt == null) continue;
+ float application_chance = diseaseExt.infectionChance;
+
+ foreach (GeneDef diseaseGeneDef in DiseaseHelper.LookupInfectionGeneDefs(diseaseExt))
+ {
+ if (DiseaseHelper.IsImmuneAgainstGeneticDisease(partner, diseaseGeneDef))
+ continue;
+
+ if ((new Random()).NextDouble() < application_chance)
+ partner.genes.AddGene(diseaseGeneDef, !RJW_Genes_Settings.rjw_genes_genetic_disease_as_endogenes);
+ }
+ }
+ }
+
+ }
+}
diff --git a/Source/Genes/Diseases/Patches/Patch_AftersexUtility_TransferGeneticDiseases.cs b/Source/Genes/Diseases/Patches/Patch_AftersexUtility_TransferGeneticDiseases.cs
index 6bf8e88..e33079f 100644
--- a/Source/Genes/Diseases/Patches/Patch_AftersexUtility_TransferGeneticDiseases.cs
+++ b/Source/Genes/Diseases/Patches/Patch_AftersexUtility_TransferGeneticDiseases.cs
@@ -7,7 +7,7 @@ using System.Text;
using System.Threading.Tasks;
using Verse;
-namespace RJW_Genes.Genes.Diseases.Patches
+namespace RJW_Genes
{
[HarmonyPatch(typeof(SexUtility), "Aftersex")]
public class Patch_AftersexUtility_TransferGeneticDiseases
@@ -29,83 +29,28 @@ namespace RJW_Genes.Genes.Diseases.Patches
if (props.usedCondom) return;
// Exit early if settings require penetrative sex, but this is not penetrative sex
- if (!IsPenetrativeSex(props) && RJW_Genes_Settings.rjw_genes_genetic_disease_spread_only_on_penetrative_sex) return;
+ if (!DiseaseHelper.IsPenetrativeSex(props) && RJW_Genes_Settings.rjw_genes_genetic_disease_spread_only_on_penetrative_sex) return;
- ModLog.Debug($"Firing Patch_TransferGeneticDiseases for {pawn} and {partner}");
+ //ModLog.Debug($"Firing Patch_TransferGeneticDiseases for {pawn} and {partner}");
TryTransferGeneticDiseases(pawn, partner, props);
TryTransferGeneticDiseases(partner, pawn, props);
}
private static void TryTransferGeneticDiseases(Pawn infector, Pawn infected, SexProps props)
{
- if (infected.genes.HasActiveGene(GeneDefOf.rjw_genes_genetic_disease_immunity))
- {
- ModLog.Debug($"{infected} is immune to genetic diseases");
- return;
- }
- if (infected.Dead)
- {
- // Dead people can spread, but not receive, diseases.
- return;
- }
-
- foreach (GeneDef disease in GetGeneticDiseaseGenes(infector)) {
+
+ foreach (GeneDef disease in DiseaseHelper.GetGeneticDiseaseGenes(infector)) {
ModLog.Debug($"Found genetic disease {disease} in {infector}, trying to infect {infected}");
- if (infected.genes.HasActiveGene(disease))
+ if (DiseaseHelper.IsImmuneAgainstGeneticDisease(infected,disease))
continue;
- if ((new Random()).NextDouble() <= LookupDiseaseInfectionChance(disease))
+ if ((new Random()).NextDouble() <= DiseaseHelper.LookupDiseaseInfectionChance(disease))
{
infected.genes.AddGene(disease, !RJW_Genes_Settings.rjw_genes_genetic_disease_as_endogenes);
}
}
}
- private static List GetGeneticDiseaseGenes(Pawn pawn)
- {
- if (pawn != null && pawn.genes != null)
- {
- return pawn.genes
- .GenesListForReading
- .ConvertAll(gene => gene.def)
- .Where(genedef => pawn.genes.HasActiveGene(genedef))
- .Where(IsGeneticDiseaseGene)
- .ToList();
- }
-
- return new List() { };
- }
-
- private static bool IsPenetrativeSex(SexProps props)
- {
- if (props == null) return false;
-
- return props.sexType ==
- xxx.rjwSextype.Vaginal
- || props.sexType == xxx.rjwSextype.Anal
- || props.sexType == xxx.rjwSextype.Oral
- || props.sexType == xxx.rjwSextype.DoublePenetration
- || props.sexType == xxx.rjwSextype.Fellatio
- || props.sexType == xxx.rjwSextype.Sixtynine;
- }
-
- private static bool IsGeneticDiseaseGene(GeneDef geneDef)
- {
- if (geneDef == null) return false;
- GeneticDiseaseExtension diseaseExt = geneDef.GetModExtension();
- return diseaseExt != null;
- }
-
- private static float LookupDiseaseInfectionChance(GeneDef geneDef)
- {
- if (IsGeneticDiseaseGene(geneDef))
- {
- GeneticDiseaseExtension diseaseExt = geneDef.GetModExtension();
- return diseaseExt != null ? diseaseExt.infectionChance : 0.0f;
- }
- else
- return 0.0f;
- }
}
}
diff --git a/Source/Genes/Diseases/Thoughts/ThoughtWorker_HasMoreDiseasesThanMe_Social.cs b/Source/Genes/Diseases/Thoughts/ThoughtWorker_HasMoreDiseasesThanMe_Social.cs
new file mode 100644
index 0000000..7a85eb3
--- /dev/null
+++ b/Source/Genes/Diseases/Thoughts/ThoughtWorker_HasMoreDiseasesThanMe_Social.cs
@@ -0,0 +1,59 @@
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+ public class ThoughtWorker_HasMoreDiseasesThanMe_Social : ThoughtWorker
+ {
+ protected override ThoughtState CurrentSocialStateInternal(Pawn pawn, Pawn other)
+ {
+ // Return for trivial errors
+ if (pawn == null || other == null || pawn == other)
+ return (ThoughtState)false;
+ // Check for position-existance
+ if (pawn.Position == null || other.Position == null || pawn.Map == null || other.Map == null)
+ return (ThoughtState)false;
+ // Do nothing if pawn is carried
+ if (pawn.CarriedBy != null)
+ return (ThoughtState)false;
+ // Do nothing if Pawn is Baby or Child (#25)
+ if (!pawn.ageTracker.Adult)
+ return (ThoughtState)false;
+ if (!other.ageTracker.Adult)
+ return (ThoughtState)false;
+ // Only check if they are spawned humans
+ if (!pawn.Spawned || !other.Spawned)
+ return (ThoughtState)false;
+ if (!pawn.RaceProps.Humanlike)
+ return (ThoughtState)false;
+ if (!other.RaceProps.Humanlike)
+ return (ThoughtState)false;
+
+ // Pawns that have not "met" wont give each other Mali
+ // Known-Each-Other is a key-word for Rimworld that shows they have had any interaction and stored each other in relations.
+ if (!RelationsUtility.PawnsKnowEachOther(pawn, other))
+ return (ThoughtState)false;
+ // If the pawn is not on Map (e.g. caravan), no mali
+ if (!MapUtility.PawnIsOnHomeMap(pawn))
+ return (ThoughtState)false;
+
+ int pawn_diseases = DiseaseHelper.GetGeneticDiseaseGenes(pawn).Count();
+ int other_diseases = DiseaseHelper.GetGeneticDiseaseGenes(other).Count();
+ int disease_diff = other_diseases - pawn_diseases;
+
+ if (disease_diff >= 5)
+ return ThoughtState.ActiveAtStage(2);
+ else if (disease_diff >= 2)
+ return ThoughtState.ActiveAtStage(1);
+ else if (disease_diff >= 1)
+ return ThoughtState.ActiveAtStage(0);
+ else
+ return (ThoughtState)false;
+ }
+ }
+}
diff --git a/Source/Genes/Diseases/Thoughts/ThoughtWorker_SizeBlinded_Social.cs b/Source/Genes/Diseases/Thoughts/ThoughtWorker_SizeBlinded_Social.cs
index 871b276..61199a6 100644
--- a/Source/Genes/Diseases/Thoughts/ThoughtWorker_SizeBlinded_Social.cs
+++ b/Source/Genes/Diseases/Thoughts/ThoughtWorker_SizeBlinded_Social.cs
@@ -44,7 +44,6 @@ namespace RJW_Genes
if (!MapUtility.PawnIsOnHomeMap(pawn))
return (ThoughtState)false;
- //ModLog.Debug($"ThoughtWorker Checks Size Blinded {pawn} -> {other}");
// Do nothing if there is no size-blinded involved
if (!GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_size_blinded))
diff --git a/Source/Genes/Special/Patches/Patch_GeneticSexSwap.cs b/Source/Genes/Special/Patches/Patch_GeneticSexSwap.cs
new file mode 100644
index 0000000..377b71d
--- /dev/null
+++ b/Source/Genes/Special/Patches/Patch_GeneticSexSwap.cs
@@ -0,0 +1,69 @@
+using HarmonyLib;
+using rjw;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+
+ [HarmonyPatch(typeof(SexUtility), "Aftersex")]
+ public class Patch_GeneticSexSwap
+ {
+ public static void Postfix(SexProps props)
+ {
+ if (props == null || props.pawn == null || props.partner == null || props.partner.IsAnimal())
+ {
+ return;
+ }
+
+ Pawn pawn = props.pawn;
+ Pawn partner = props.partner;
+
+ if (pawn.genes == null || partner.genes == null) return;
+
+ // If both have the swap gene, nothing happens
+ if (GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_sexual_genetic_swap)
+ && GeneUtility.HasGeneNullCheck(partner, GeneDefOf.rjw_genes_sexual_genetic_swap))
+ return;
+ // If neither has the swap gene, nothing happens
+ if (!GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_sexual_genetic_swap)
+ && !GeneUtility.HasGeneNullCheck(partner, GeneDefOf.rjw_genes_sexual_genetic_swap))
+ return;
+
+ ChanceExtension chanceExt = GeneDefOf.rjw_genes_sexual_genetic_swap.GetModExtension();
+ if (chanceExt != null && (new Random()).NextDouble() < chanceExt.chance)
+ SwapOneRandomGene(pawn, partner);
+ }
+
+ ///
+ /// Removes a random gene from one pawn and adds it too the other as xenogene.
+ /// The "gene swap" gene cannot be swapped!
+ ///
+ private static void SwapOneRandomGene(Pawn a, Pawn b, bool AddAsXenogene = true)
+ {
+
+ var geneFromA = a.genes.GenesListForReading
+ .Where(gene => a.genes.HasActiveGene(gene.def))
+ .Where(gene => gene.def != GeneDefOf.rjw_genes_sexual_genetic_swap)
+ .RandomElement();
+ var geneFromB = b.genes.GenesListForReading
+ .Where(gene => b.genes.HasActiveGene(gene.def))
+ .Where(gene => gene.def != GeneDefOf.rjw_genes_sexual_genetic_swap)
+ .RandomElement();
+
+ if (geneFromA == null || geneFromB == null) return;
+
+ ModLog.Debug($"Sexual Genetic Swap: Swapping {geneFromA.def} from {a} with {geneFromB.def} from {b}");
+
+ a.genes.AddGene(geneFromB.def, AddAsXenogene);
+ b.genes.AddGene(geneFromA.def, AddAsXenogene);
+ a.genes.RemoveGene(geneFromA);
+ b.genes.RemoveGene(geneFromB);
+ }
+
+ }
+}
diff --git a/Source/Genes/Special/Patches/Patch_GeneticSexThief.cs b/Source/Genes/Special/Patches/Patch_GeneticSexThief.cs
new file mode 100644
index 0000000..ecc2201
--- /dev/null
+++ b/Source/Genes/Special/Patches/Patch_GeneticSexThief.cs
@@ -0,0 +1,68 @@
+using HarmonyLib;
+using rjw;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+
+ [HarmonyPatch(typeof(SexUtility), "Aftersex")]
+ public class Patch_GeneticSexThief
+ {
+ public static void Postfix(SexProps props)
+ {
+ if (props == null || props.pawn == null || props.partner == null || props.partner.IsAnimal())
+ {
+ return;
+ }
+
+ Pawn pawn = props.pawn;
+ Pawn partner = props.partner;
+
+ if (pawn.genes == null || partner.genes == null) return;
+
+ // If both have the swap gene, nothing happens
+ if (GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_sexual_genetic_thief)
+ && GeneUtility.HasGeneNullCheck(partner, GeneDefOf.rjw_genes_sexual_genetic_thief))
+ return;
+
+ if (GeneUtility.HasGeneNullCheck(pawn,GeneDefOf.rjw_genes_sexual_genetic_thief) &&
+ !GeneUtility.HasGeneNullCheck(partner, GeneDefOf.rjw_genes_genetic_disease_immunity))
+ {
+ ChanceExtension chanceExt = GeneDefOf.rjw_genes_sexual_genetic_thief.GetModExtension();
+ if (chanceExt != null && (new Random()).NextDouble() < chanceExt.chance)
+ StealRandomGene(pawn, partner);
+ }
+
+ if (GeneUtility.HasGeneNullCheck(partner, GeneDefOf.rjw_genes_sexual_genetic_thief) &&
+ !GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_genetic_disease_immunity))
+ {
+ ChanceExtension chanceExt = GeneDefOf.rjw_genes_sexual_genetic_thief.GetModExtension();
+ if (chanceExt != null && (new Random()).NextDouble() < chanceExt.chance)
+ StealRandomGene(partner, pawn);
+ }
+ }
+
+ ///
+ /// Removes a random gene from one pawn and adds it too the other as xenogene.
+ ///
+ private static void StealRandomGene(Pawn stealer, Pawn victim, bool AddAsXenogene = true)
+ {
+ var stolenGene = victim.genes.GenesListForReading
+ .Where(gene => victim.genes.HasActiveGene(gene.def))
+ .RandomElement();
+
+ if (stolenGene == null) return;
+
+ ModLog.Debug($"Sexual Gene Thief: {stealer} steals {stolenGene.def} from {victim}");
+
+ stealer.genes.AddGene(stolenGene.def, AddAsXenogene);
+ victim.genes.RemoveGene(stolenGene);
+ }
+
+ }
+}
diff --git a/Source/Genes/Special/Patches/Patch_HormonalSaliva.cs b/Source/Genes/Special/Patches/Patch_HormonalSaliva.cs
index 150eeec..fed69d7 100644
--- a/Source/Genes/Special/Patches/Patch_HormonalSaliva.cs
+++ b/Source/Genes/Special/Patches/Patch_HormonalSaliva.cs
@@ -12,7 +12,6 @@ namespace RJW_Genes
[HarmonyPatch(typeof(SexUtility), "Aftersex")]
public class Patch_HormonalSaliva
{
- // TODO: Reduce to 0.02 after debug.
const float SIZE_INCREMENT_FALLBACK = 0.02f;
const float MAX_BODY_SIZE_FALLBACK = 2.5f;
const float CUM_MULTIPLIER_FALLBACK = 1.05f;
diff --git a/Source/Rjw-Genes.csproj b/Source/Rjw-Genes.csproj
index 51b80a1..306bcb7 100644
--- a/Source/Rjw-Genes.csproj
+++ b/Source/Rjw-Genes.csproj
@@ -56,6 +56,7 @@
+
@@ -71,14 +72,22 @@
+
+
+
+
+
+
+
+
@@ -180,6 +189,8 @@
+
+
diff --git a/Source/ThoughtDefOf.cs b/Source/ThoughtDefOf.cs
index 50f2a00..596ecc0 100644
--- a/Source/ThoughtDefOf.cs
+++ b/Source/ThoughtDefOf.cs
@@ -16,6 +16,7 @@ namespace RJW_Genes
public static readonly ThoughtDef rjw_genes_pheromone_carrier_nearby;
public static readonly ThoughtDef rjw_genes_appealing_cock;
+ public static readonly ThoughtDef rjw_genes_has_more_diseases;
//Others with same names but other defs than in genedefof
public static readonly InteractionDef rjw_genes_flirt;