diff --git a/CHANGELOG.md b/CHANGELOG.md
index ccab0fd..495d954 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,22 @@ Fixes:
- Cockeater now leaves a bite wound!
- Pythokin-Patch checks for Licentialabs (#30)
+**Queen & Caste Logic**
+
+There are 3 genes revolting around a new, hopefully flexible insect-caste system. Queens, Drones and Workers. These reproduce either through normal sex, or can utilize the insect birth once [this PR](https://gitgud.io/Ed86/rjw/-/merge_requests/266) has been merged in.
+
+In general, the logic is the following:
+
+- A queen can have sex with anyone. If the partner was a drone, there is chance for the baby to become a queen, drone or worker.
+- If the partner of the queen was not a drone, the baby will be a worker.
+- If the drone didn't mate with a queen but someone else, normal inheritance happens
+- The assignemnt is done by xenotypes for queen and drones. The baby will get all xenogenes of their parents xenotypes.
+- For workers, every queen can have a set of genes for their workers defined in [a special def](./Common/Defs/QueenWorkerMappingDefs/QueenWorkerMappingDefs_base.xml). These will be added as endogenes, so that pawns can still become xenotypes.
+- There is a default set for worker genes, making mentally dumb, sterile and servile pawns.
+
+I am not sure if I want to have a specific mapping defining that Queen can only mate with certain Drones, let me know how you feel about it.
+*Queens can be male*. I just used the female-term, but implementation is gender-neutral.
+
ToDo:
- Icons: Cocoon, Spelopede Spawn
diff --git a/Common/Defs/GeneDefs/GeneDefs_Hive.xml b/Common/Defs/GeneDefs/GeneDefs_Hive.xml
index 5b838eb..897851f 100644
--- a/Common/Defs/GeneDefs/GeneDefs_Hive.xml
+++ b/Common/Defs/GeneDefs/GeneDefs_Hive.xml
@@ -34,6 +34,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/Source/Genes/Hive/Defs/QueenWorkerMappingDef.cs b/Source/Genes/Hive/Defs/QueenWorkerMappingDef.cs
new file mode 100644
index 0000000..b825c7c
--- /dev/null
+++ b/Source/Genes/Hive/Defs/QueenWorkerMappingDef.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+ ///
+ /// This def covers the birth of workers for each queen-xenotype.
+ ///
+ /// It is used when a baby is born to a pawn with the queen-xenotype;
+ /// There is a random check for the type of the baby, and if the baby is born to be a worker,
+ /// additional genes are looked up here.
+ ///
+ public class QueenWorkerMappingDef : Def
+ {
+ public string queenXenotype;
+ public List workerGenes;
+ }
+}
diff --git a/Source/Genes/Hive/Genes/ConditionalStatAffecters/ConditionalStatAffecter_QueenAbsent.cs b/Source/Genes/Hive/Genes/ConditionalStatAffecters/ConditionalStatAffecter_QueenAbsent.cs
index 512ac13..9a468df 100644
--- a/Source/Genes/Hive/Genes/ConditionalStatAffecters/ConditionalStatAffecter_QueenAbsent.cs
+++ b/Source/Genes/Hive/Genes/ConditionalStatAffecters/ConditionalStatAffecter_QueenAbsent.cs
@@ -21,6 +21,9 @@ namespace RJW_Genes
{
if (req.Pawn == null || !req.Pawn.Spawned)
return false;
+ // If the pawn is not on Map (e.g. caravan), no mali
+ if (!HiveUtility.PawnIsOnHomeMap(req.Pawn))
+ return false;
if (GeneUtility.HasGeneNullCheck(req.Pawn, GeneDefOf.rjw_genes_zealous_loyalty))
{
diff --git a/Source/Genes/Hive/Helpers/HiveUtility.cs b/Source/Genes/Hive/Helpers/HiveUtility.cs
index e90a0cf..945db92 100644
--- a/Source/Genes/Hive/Helpers/HiveUtility.cs
+++ b/Source/Genes/Hive/Helpers/HiveUtility.cs
@@ -67,5 +67,142 @@ namespace RJW_Genes
&& pawn.Spawned
&& pawn.Map == homeMap;
}
+
+ ///
+ /// Returns the Xenotype of a pawn if the pawn has a xenotype with the queen gene.
+ /// Null otherwise.
+ ///
+ ///
+ /// A xenotype with a queen gene, or null.
+ public static XenotypeDef TryGetQueenXenotype(Pawn pawn)
+ {
+ if (pawn == null)
+ return null;
+
+ if (GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_queen))
+ {
+ var potentialXenotype = pawn.genes.Xenotype;
+ if (potentialXenotype != null && potentialXenotype.genes.Contains(GeneDefOf.rjw_genes_queen))
+ {
+ return potentialXenotype;
+ }
+ }
+
+ return null;
+ }
+
+
+ ///
+ /// Returns the Xenotype of a pawn if the pawn has a xenotype with the drone gene.
+ /// Null otherwise.
+ ///
+ ///
+ /// A xenotype with a drone gene, or null.
+ public static XenotypeDef TryGetDroneXenotype(Pawn pawn)
+ {
+ if (pawn == null)
+ return null;
+
+ if (GeneUtility.HasGeneNullCheck(pawn, GeneDefOf.rjw_genes_drone))
+ {
+ var potentialXenotype = pawn.genes.Xenotype;
+ if (potentialXenotype != null && potentialXenotype.genes.Contains(GeneDefOf.rjw_genes_drone))
+ {
+ return potentialXenotype;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Looks up the Queen-WorkerMappings and returns a cleaned / updated dictionary.
+ ///
+ /// This method takes care of genes maybe not existing (from other mods) or misspellings etc.
+ /// Prints a bigger piece of information when debug printing is enabled.
+ ///
+ /// A mapping which Queen-Xenotypes should produce which worker genes.
+
+ public static Dictionary> GetQueenWorkerMappings()
+ {
+ Dictionary> dict = new Dictionary>();
+ IEnumerable mappingDefs = DefDatabase.AllDefs;
+
+ if (RJW_Genes_Settings.rjw_genes_detailed_debug) ModLog.Message($"Found {mappingDefs.Count()} Queen-Worker mappings in defs");
+
+ // Dev-Note: I first a nice lambda here, but I used nesting in favour of logging.
+ foreach (QueenWorkerMappingDef mappingDef in mappingDefs)
+ {
+
+ if (mappingDef.defName == "rjw_genes_default_worker_genes")
+ {
+ // Do nothing, there is no lookup but this entry is fine and should not log a warning.
+ continue;
+ }
+ XenotypeDef queenDef = DefDatabase.GetNamed(mappingDef.queenXenotype);
+ if (queenDef != null)
+ {
+ List workerGenes = new List();
+ foreach (string geneName in mappingDef.workerGenes)
+ {
+ GeneDef workerGene = DefDatabase.GetNamed(geneName);
+ if (workerGene != null)
+ workerGenes.Add(workerGene);
+ else if(RJW_Genes_Settings.rjw_genes_detailed_debug)
+ ModLog.Warning($"Could not look up Gene {geneName} for {mappingDef.queenXenotype}.");
+ }
+ dict.Add(queenDef, workerGenes);
+ }
+ else {
+ if (RJW_Genes_Settings.rjw_genes_detailed_debug)
+ ModLog.Warning($"Did not find a matching xenotype for {mappingDef.queenXenotype}! Defaulting to rjw_genes_default_worker_genes.");
+ }
+ }
+
+ return dict;
+ }
+
+ ///
+ /// Looks up the default genes for any queen offspring that has no other definition for it.
+ /// This is done by looking for the mapping with *exactly* defName rjw_genes_default_worker_genes.
+ ///
+ /// The idea is that players can edit the default types, but that this is a protected keyword.
+ ///
+ /// A list of genes for workers that do not have specific mappings defined.
+ public static List LookupDefaultWorkerGenes()
+ {
+ IEnumerable mappingDefs = DefDatabase.AllDefs;
+
+ List workerGenes = new List();
+
+ var defaultMapping = mappingDefs.First(m => m.defName == "rjw_genes_default_worker_genes");
+ if (defaultMapping == null)
+ {
+ ModLog.Error("Did not find default worker genes for queen-offspring! Please make sure you did not rename the 'rjw_genes_default_worker_genes'.");
+ return workerGenes;
+ }
+
+ foreach (string geneName in defaultMapping.workerGenes)
+ {
+ GeneDef workerGene = DefDatabase.GetNamed(geneName);
+ if (workerGene != null)
+ workerGenes.Add(workerGene);
+ else if (RJW_Genes_Settings.rjw_genes_detailed_debug)
+ ModLog.Warning($"Could not look up gene {geneName} for rjw_genes_default_worker_genes.");
+ }
+
+ return workerGenes;
+ }
+
+ public static IEnumerable getQueenXenotypes()
+ {
+ return DefDatabase.AllDefs.Where(type => type.genes.Contains(GeneDefOf.rjw_genes_queen));
+ }
+
+ public static IEnumerable getDroneXenotypes()
+ {
+ return DefDatabase.AllDefs.Where(type => type.genes.Contains(GeneDefOf.rjw_genes_drone));
+ }
+
}
}
diff --git a/Source/Genes/Hive/Patches/Patch_BirthOutcome_SetHiveGenes.cs b/Source/Genes/Hive/Patches/Patch_BirthOutcome_SetHiveGenes.cs
new file mode 100644
index 0000000..c640118
--- /dev/null
+++ b/Source/Genes/Hive/Patches/Patch_BirthOutcome_SetHiveGenes.cs
@@ -0,0 +1,150 @@
+using HarmonyLib;
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace RJW_Genes
+{
+ ///
+ /// Patches the method `ApplyBirthOutcome` from `PregnancyUtility`.
+ ///
+ /// The 'ApplyBirthOutcome' returns the finished baby, for which we alter the pawn according to our xenotypes.
+ ///
+
+ [HarmonyPatch(typeof(PregnancyUtility), nameof(PregnancyUtility.ApplyBirthOutcome))]
+ public class Patch_BirthOutcome_SetHiveGenes
+ {
+ const double QUEEN_CHANCE = 0.01f;
+ const double DRONE_CHANCE = 0.49f;
+ const double WORKER_CHANCE = 1 - QUEEN_CHANCE - DRONE_CHANCE;
+
+
+ [HarmonyPostfix]
+ static void HandleHiveBasedInheritance(ref Thing __result)
+ {
+
+ // Check: Was the born thing a pawn?
+ if (__result == null || !(__result is Pawn))
+ {
+ if (RJW_Genes_Settings.rjw_genes_detailed_debug) ModLog.Message("There was a birth of something non-human - not entering logic for queen-drone-xenotype inheritance.");
+ return;
+ }
+
+ Pawn pawn = (Pawn)__result;
+
+ // Important: Not all pawns have mother/father. Some Pawns are born in Growth-Vats or born from mod.
+ bool hasQueenParent = TryFindParentQueenXenotype(pawn) != null;
+ bool hasDroneParent = TryFindParentDroneXenotype(pawn) != null;
+
+ if (hasQueenParent)
+ {
+ if (RJW_Genes_Settings.rjw_genes_detailed_debug) ModLog.Message($"PostFix PregnancyUtility::ApplyBirthOutcome - Checking Hive Inheritance because {pawn} has a queen parent.");
+
+ XenotypeDef queenDef = TryFindParentQueenXenotype(pawn);
+ // Case 1: Mother is Queen, Father is something else. Produce Worker.
+ if (!hasDroneParent)
+ {
+ if (RJW_Genes_Settings.rjw_genes_detailed_debug) ModLog.Message($"{pawn} was born as a worker, as it did not have Drone Father ({100}% chance)");
+ MakeWorker(pawn, queenDef);
+ }
+ // Case 2: Mother is Queen, Father is drone. Apply xenotype as per chance.
+ else
+ {
+ double roll = (new Random()).NextDouble();
+ // Case 2.a: New Queen born
+ if (roll < QUEEN_CHANCE)
+ {
+ pawn.genes.SetXenotype(queenDef);
+ if (RJW_Genes_Settings.rjw_genes_detailed_debug) ModLog.Message($"{pawn} born as a new queen with xenotype {queenDef.defName} ({QUEEN_CHANCE*100}% chance,rolled {roll})");
+ // TODO: Make a letter ?
+ }
+ // Case 2.b: New Drone born
+ else if (roll < DRONE_CHANCE + QUEEN_CHANCE)
+ {
+ XenotypeDef droneDef = TryFindParentDroneXenotype(pawn);
+ pawn.genes.SetXenotype(droneDef);
+ if (RJW_Genes_Settings.rjw_genes_detailed_debug) ModLog.Message($"{pawn} born as a new drone with xenotype {droneDef.defName} ({(DRONE_CHANCE + QUEEN_CHANCE) * 100}% chance,rolled {roll}))");
+ }
+ // Case 2.c: Worker
+ else {
+ if (RJW_Genes_Settings.rjw_genes_detailed_debug) ModLog.Message($"{pawn} born as a worker ({(WORKER_CHANCE) * 100}% chance,rolled {roll}))");
+ MakeWorker(pawn, queenDef);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Turns a given pawn into a worker, by looking up the relevant genes as per queen.
+ ///
+ /// If the queen xenotype has no mapping, the "rjw_genes_default_worker_xenotype" are used instead.
+ /// The genes are added as endogenes, so the worker can still become a xenotype.
+ ///
+ /// The pawn for which the genes are added.
+ /// The xenotype of the queen, used for lookup.
+ private static void MakeWorker(Pawn pawnTobeWorker, XenotypeDef queenDef)
+ {
+ if (pawnTobeWorker == null)
+ return;
+
+ var mappings = HiveUtility.GetQueenWorkerMappings();
+
+ var genes = mappings.TryGetValue(queenDef, HiveUtility.LookupDefaultWorkerGenes());
+
+ foreach (var gene in genes)
+ pawnTobeWorker.genes.AddGene(gene, false);
+
+ }
+
+ ///
+ /// Looks up if there is a Xenotype with Drone-Gene for the pawns parents.
+ /// This is to account that maybe father or mother are the drone (instead of hardcoding things for father).
+ /// If both are drones, the mothers is returned.
+ ///
+ /// The pawn for whichs parent the xenotypes is looked up.
+ /// The Drone-Xenotype of a parent or null. If both are drones, mothers are preferred.
+ private static XenotypeDef TryFindParentDroneXenotype(Pawn pawn)
+ {
+ if (pawn == null)
+ return null;
+
+ var motherXenotype = HiveUtility.TryGetDroneXenotype(pawn.GetMother());
+ var fatherXenotype = HiveUtility.TryGetDroneXenotype(pawn.GetFather());
+
+ if (motherXenotype != null)
+ return motherXenotype;
+ if (fatherXenotype != null)
+ return fatherXenotype;
+
+ return null;
+ }
+
+
+ ///
+ /// Looks up if there is a Xenotype with Queen-Gene for the pawns parents.
+ /// This is to account that maybe father or mother are the queen (instead of hardcoding things for father).
+ /// If both are queens, the mothers is returned.
+ ///
+ /// The pawn for whichs parent the xenotypes is looked up.
+ /// The Queen-Xenotype of a parent or null. If both are queens, mothers are preferred.
+ private static XenotypeDef TryFindParentQueenXenotype(Pawn pawn)
+ {
+ if (pawn == null)
+ return null;
+
+ var motherXenotype = HiveUtility.TryGetQueenXenotype(pawn.GetMother());
+ var fatherXenotype = HiveUtility.TryGetQueenXenotype(pawn.GetFather());
+
+ if (motherXenotype != null)
+ return motherXenotype;
+ if (fatherXenotype != null)
+ return fatherXenotype;
+
+ return null;
+ }
+ }
+}
diff --git a/Source/Genes/Hive/Thoughts/Thoughtworker_MultipleQueens_Mood.cs b/Source/Genes/Hive/Thoughts/Thoughtworker_MultipleQueens_Mood.cs
index 52ee842..3265a00 100644
--- a/Source/Genes/Hive/Thoughts/Thoughtworker_MultipleQueens_Mood.cs
+++ b/Source/Genes/Hive/Thoughts/Thoughtworker_MultipleQueens_Mood.cs
@@ -20,6 +20,9 @@ namespace RJW_Genes
// Queens cannot have loyalty thoughts
if (GeneUtility.HasGeneNullCheck(p, GeneDefOf.rjw_genes_queen))
return (ThoughtState)false;
+ // If the pawn is not on Map (e.g. caravan), no mali
+ if (!HiveUtility.PawnIsOnHomeMap(p))
+ return (ThoughtState)false;
if (GeneUtility.HasGeneNullCheck(p, GeneDefOf.rjw_genes_zealous_loyalty) && HiveUtility.QueensOnMap() >= 2)
{
diff --git a/Source/RJW_Genes.cs b/Source/RJW_Genes.cs
index c17c779..e25feef 100644
--- a/Source/RJW_Genes.cs
+++ b/Source/RJW_Genes.cs
@@ -1,10 +1,20 @@
-using Verse;
+using System.Linq;
+using Verse;
-namespace BTE_MMLA
+namespace RJW_Genes
{
[StaticConstructorOnStartup]
public static class RJW_Genes
{
- static RJW_Genes() => Log.Message("RJW-Genes loaded");
+ static RJW_Genes()
+ {
+ ModLog.Message("RJW-Genes loaded");
+ if (RJW_Genes_Settings.rjw_genes_detailed_debug)
+ {
+ ModLog.Message($"{HiveUtility.getQueenXenotypes().EnumerableCount()} Queen-Xenotypes ({string.Join(",", HiveUtility.getQueenXenotypes().Select(t => t.defName))})");
+ ModLog.Message($"{HiveUtility.getDroneXenotypes().EnumerableCount()} Drone-Xenotypes ({string.Join(",", HiveUtility.getDroneXenotypes().Select(t => t.defName))})");
+ ModLog.Message($"Found {HiveUtility.GetQueenWorkerMappings().Count} Queen-Worker Mappings ({string.Join(",", HiveUtility.GetQueenWorkerMappings().Keys.Select(t => t.defName))} + Default) ");
+ }
+ }
}
}
diff --git a/Source/Rjw-Genes.csproj b/Source/Rjw-Genes.csproj
index 928a8de..c5ccd19 100644
--- a/Source/Rjw-Genes.csproj
+++ b/Source/Rjw-Genes.csproj
@@ -95,6 +95,8 @@
+
+