diff --git a/.gitignore b/.gitignore index 9e4e587..7dce504 100644 --- a/.gitignore +++ b/.gitignore @@ -259,3 +259,4 @@ __pycache__/ /1.4/Assemblies/HarmonyMod.dll /1.4/Assemblies/0MultiplayerAPI.dll /1.4/Assemblies/0Harmony.dll +/1.4/Assemblies/LitterBiotech.dll diff --git a/1.4/Defs/GeneDefs/LitteredBirths.xml b/1.4/Defs/GeneDefs/LitteredBirths.xml new file mode 100644 index 0000000..8f8e5a0 --- /dev/null +++ b/1.4/Defs/GeneDefs/LitteredBirths.xml @@ -0,0 +1,15 @@ + + + + + LitteredBirths + + Female carriers of this gene birth litters instead of just one baby, with a chance of having two to four babies per pregnancy. + Icons/Genes/Gene_LitteredBirths + + 110 + 1.25 + 0 + 1 + + diff --git a/1.4/Defs/LetterDefs/AnotherBaby.xml b/1.4/Defs/LetterDefs/AnotherBaby.xml new file mode 100644 index 0000000..d8f7c17 --- /dev/null +++ b/1.4/Defs/LetterDefs/AnotherBaby.xml @@ -0,0 +1,11 @@ + + + + AnotherBaby + (120, 176, 216) + (106, 179, 231) + 40 + LetterArrive_Good + MajorThreat + + \ No newline at end of file diff --git a/1.4/Languages/English/Keyed/SettingsKeys.xml b/1.4/Languages/English/Keyed/SettingsKeys.xml new file mode 100644 index 0000000..f5de00e --- /dev/null +++ b/1.4/Languages/English/Keyed/SettingsKeys.xml @@ -0,0 +1,8 @@ + + + Litter Biotech + Enable development logging + When enabled, this setting provides additional Litter Biotech related logging in the development mode log. + Disable "Regrets stealing lovin'" thought + By default, the limbic stimulator implant allows for pawns without the Rapist trait to steal lovin'. When this occurs, the non-rapist pawn will receive a -5 mood negative thought that stacks up to 3 times. When enabled, this setting disables generation of that thought. + \ No newline at end of file diff --git a/1.4/Textures/Icons/Genes/Gene_LitteredBirths.png b/1.4/Textures/Icons/Genes/Gene_LitteredBirths.png new file mode 100644 index 0000000..5b5a09e Binary files /dev/null and b/1.4/Textures/Icons/Genes/Gene_LitteredBirths.png differ diff --git a/About/About.xml b/About/About.xml new file mode 100644 index 0000000..804dfae --- /dev/null +++ b/About/About.xml @@ -0,0 +1,25 @@ + + Litters for Biotech + leboeuf +leboeuf.litters + +
  • 1.4
  • +
    + + +
  • rim.job.world
  • +
    + +Litter Biotech is a small RimWorld: Biotech expansion for RimJobWorld. + +Features: + + • Biotech pregnancy compatible multibirth system, allowing + pawns to have random twins with a default 1-in-100 chance. + • Littered births gene, where pawns can have guaranteed + litters of multiple babies. + +For all feature requests and bugs/issues: + https://gitgud.io/leboeuf/litter-biotech/-/issues + +
    diff --git a/LitterBiotech.sln b/LitterBiotech.sln new file mode 100644 index 0000000..c6e2ef0 --- /dev/null +++ b/LitterBiotech.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31205.134 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LitterBiotech", "Source\LitterBiotech.csproj", "{D7D21B4A-1DA7-41D8-B202-C58CA8FA62AA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D7D21B4A-1DA7-41D8-B202-C58CA8FA62AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7D21B4A-1DA7-41D8-B202-C58CA8FA62AA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {28CF9A73-5333-4EB3-BFCC-3FBEDDA19200} + EndGlobalSection +EndGlobal diff --git a/Source/DefOf/GeneDefOf.cs b/Source/DefOf/GeneDefOf.cs new file mode 100644 index 0000000..c5e2720 --- /dev/null +++ b/Source/DefOf/GeneDefOf.cs @@ -0,0 +1,11 @@ +using RimWorld; +using Verse; + +namespace LitterBiotech +{ + [RimWorld.DefOf] + public static class GeneDefOf + { + public static GeneDef LitteredBirths; + } +} \ No newline at end of file diff --git a/Source/DefOf/HediffDefOf.cs b/Source/DefOf/HediffDefOf.cs new file mode 100644 index 0000000..b1c4dfb --- /dev/null +++ b/Source/DefOf/HediffDefOf.cs @@ -0,0 +1,14 @@ +using RimWorld; +using Verse; + +namespace LitterBiotech +{ + [RimWorld.DefOf] + public static class HediffDefOf + { + // public static HediffDef Infatuo; + // public static HediffDef InfatuoCalibrating; + public static HediffDef OvaryAgitator; + public static HediffDef Bioscaffold; + } +} \ No newline at end of file diff --git a/Source/DefOf/LetterDefOf.cs b/Source/DefOf/LetterDefOf.cs new file mode 100644 index 0000000..d3f72e9 --- /dev/null +++ b/Source/DefOf/LetterDefOf.cs @@ -0,0 +1,11 @@ +using RimWorld; +using Verse; + +namespace LitterBiotech +{ + [RimWorld.DefOf] + public static class LetterDefOf + { + public static LetterDef AnotherBaby; + } +} diff --git a/Source/Hediffs/Infatuo.cs b/Source/Hediffs/Infatuo.cs new file mode 100644 index 0000000..d2aa986 --- /dev/null +++ b/Source/Hediffs/Infatuo.cs @@ -0,0 +1,57 @@ +// FOR ANYONE (THAT ISN"T ME (BOEUF)) THAT CARES +// ===================== +// This is an idea for an implant that I had that basically forces a pawn to romantically obsess about another pawn, but I'm taking a break from working on it +// because I'm too smoothbrain to work out relation defs and making custom relations, and the multibirth stuff was taking up my time as is +// If you want to mess with it, this code is here for shits and gigs - modify it, throw it out, make something new, idgaf + +/*using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LitterBiotech.Hediffs +{ + class Infatuo + { + public class PawnRelationWorker_InfatuoInfatuated : PawnRelationWorker + { + // BaseGenerationChanceFactor - should NEVER autogenerate on pawns (for now, anyway) + new public float BaseGenerationChanceFactor(Pawn generated, Pawn other, PawnGenerationRequest request) + { + return 0.0f; + } + + // CreateRelation - shouldn't be needed for now since we're not autogenerating infatuo relations, but may be used in future + public override void CreateRelation(Pawn generated, Pawn other, ref PawnGenerationRequest request) + { + return; + } + + // GenerationChance - should NEVER autogenerate on pawns (for now, anyway) + public override float GenerationChance(Pawn generated, Pawn other, PawnGenerationRequest request) + { + return 0.0f; + } + + public override bool InRelation(Pawn me, Pawn other) + { + return (me.health.hediffSet.GetFirstHediff().target == other); + } + } + + public class Hediff_Infatuo : Hediff + { + public Hediff_Infatuo(Pawn intTarget) + { + target = intTarget; + } + public Pawn target; + } + + public class Hediff_InfatuoCalibrating : Hediff + { + + } + } +}*/ diff --git a/Source/Helpers/LBTLogger.cs b/Source/Helpers/LBTLogger.cs new file mode 100644 index 0000000..9f03551 --- /dev/null +++ b/Source/Helpers/LBTLogger.cs @@ -0,0 +1,35 @@ +using Verse; + +namespace LitterBiotech.Helpers +{ + public static class LBTLogger + { + public static void Message(string message) + { + Log.Message("[INFO][LitterBT] - " + message); + } + + public static void Warning(string message) + { + Log.Message("[WARN][LitterBT] - " + message); + } + + public static void Error(string message) + { + Log.Message("[ ERR][LitterBT] - " + message); + } + + public static void MessageGroupHead(string message) + { + Log.Message("[INFO][LitterBT]╦═ " + message); + } + public static void MessageGroupBody(string message) + { + Log.Message("[INFO][LitterBT]╠═══ " + message); + } + public static void MessageGroupFoot(string message) + { + Log.Message("[INFO][LitterBT]╚═══ " + message); + } + } +} diff --git a/Source/Helpers/LaborState.cs b/Source/Helpers/LaborState.cs new file mode 100644 index 0000000..9a99f12 --- /dev/null +++ b/Source/Helpers/LaborState.cs @@ -0,0 +1,28 @@ +using System; +using RimWorld; +using Verse; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LitterBiotech +{ + class LaborState + { + public Pawn pawn; + public int birthTotal = 0; + public int birthCount = 1; + public bool hasOvaryAgitator = false; + public bool hasBioscaffold = false; + + public LaborState(Pawn pawn, int birthTotal) + { + this.pawn = pawn; + this.birthTotal = birthTotal; + this.birthCount = 0; + this.hasOvaryAgitator = pawn.health.hediffSet.HasHediff(HediffDef.Named("OvaryAgitator")); + this.hasBioscaffold = pawn.health.hediffSet.HasHediff(HediffDef.Named("OvaryAgitator")); + } + } +} diff --git a/Source/LitterBiotech.cs b/Source/LitterBiotech.cs new file mode 100644 index 0000000..c127eed --- /dev/null +++ b/Source/LitterBiotech.cs @@ -0,0 +1,161 @@ +using HarmonyLib; +using RimWorld; +using LitterBiotech.Helpers; +using System.Collections.Generic; +using Verse; + +namespace LitterBiotech +{ + [StaticConstructorOnStartup] + public class LitterBiotechMod + { + static Dictionary laborStateMap = new Dictionary(); + + static LitterBiotechMod() + { + Harmony harmony = new Harmony(id: "rimworld.leboeuf.litterbiotech"); + // Hediff_Labor state capture + harmony.Patch(AccessTools.Method(typeof(Hediff_Labor), nameof(Hediff_Labor.PostRemoved)), + postfix: new HarmonyMethod(typeof(LitterBiotechMod), nameof(Hediff_Labor_PostRemovedPostFix))); + + // Gene_LitteredBirths multibirth logic + harmony.Patch(AccessTools.Method(typeof(Hediff_LaborPushing), nameof(Hediff_LaborPushing.PostRemoved)), + postfix: new HarmonyMethod(typeof(LitterBiotechMod), nameof(Hediff_LaborPushing_PostRemovedPostFix))); + + LBTLogger.Message("Litter Biotech started successfully."); + + if (LBTSettings.devMode) + { + LBTLogger.Message("Notice: Developer logging for Litter Biotech is currently active - it can be disabled in the mod settings."); + } + } + + // Notes about Hediff_Labor_PostRemovedPostFix + // =========================================== + // Alright, didn't really want to make a patch for Labor as well as LaborPushing, but I have to because otherwise there's + // no way to assign a doctor on second/third/etc births if I only use LaborPushing, which can disproportionately cause + // more stillbirths than you might normally have + // + // I TRIED to modify the ChildBirth lord job and ritual code to allow the first doctor that was assigned and any childbirth + // attendees to just autoreassign themselves to the next births, but it wasn't working - for now, I'm not happy with how I'm + // doing this because multiple births now require: + // 1. (potentially several) forced pauses with letters telling the player to reassign a doctor to the mother who's giving birth to another child + // 2. A LOT of state carrying (that I likely overengineered :v) ) between hediff removes and adds + // + // I've got a dictionary now for storing state across hediffs and on multiple pawns, instead of what I was originally doing, which was using + // severity as a way of tracking state. LaborState class should help keep things a little more organized. + // + // I'll revisit this in the future (probably). Thanks for coming to my TED talk + static void Hediff_Labor_PostRemovedPostFix(ref Hediff_Labor __instance) + { + bool randomTwinsRoll; + int totalBirths; + bool laborStateIsNull = !laborStateMap.ContainsKey(__instance.pawn.ThingID); + bool hasLitteredBirthsGene = __instance.pawn.genes.HasGene(LitterBiotech.GeneDefOf.LitteredBirths); + + // we'll never do additional processing if this is the guaranteed last birth (eg birth #4) + if (!laborStateIsNull && laborStateMap.TryGetValue(__instance.pawn.ThingID).birthCount == 4) + { + return; + } + + // For now, littered birth overrides twin calculations, so if a LaborState already exists + // with littered births gene, move on + if (!laborStateIsNull && hasLitteredBirthsGene) + { + if (LBTSettings.devMode) + { + LBTLogger.MessageGroupHead("Found active LaborState and LitteredBirths gene - skipping additional Hediff_Labor_PostRemovedPostFix work"); + LBTLogger.MessageGroupBody("Pawn: " + __instance.pawn.NameShortColored + " (" + __instance.pawn.ThingID + ")"); + LBTLogger.MessageGroupFoot("birthCount: " + laborStateMap.TryGetValue(__instance.pawn.ThingID).birthCount); + } + + return; + } + + // Make a new LaborState for the null case with littered births + if (laborStateIsNull && hasLitteredBirthsGene) + { + LBTLogger.Message("Found littered births gene"); + int litteredBirthsTotalRoll = Rand.RangeInclusive(2, 4); + laborStateMap.SetOrAdd(__instance.pawn.ThingID, new LaborState(__instance.pawn, litteredBirthsTotalRoll)); + return; + } + + // Finally, regardless of littered births gene, we only want new state creation on + // pawns that don't already have state, so return if state is !null (STATE SHOULD ALWAYS BE CLEANED IN LABORPUSHING POSTFIX) + if (!laborStateIsNull) + { + if (LBTSettings.devMode) + { + LBTLogger.Warning("Labor state for pawn " + __instance.pawn.NameShortColored + " (" + __instance.pawn.ThingID + ") is not null despite all checks passing for determining first instance of Hediff_Labor - this warning should never occur, and may indicate a bug in Hediff_LaborPushing of lingering labor state from a previous pregnancy"); + } + return; + } + + // For everything else, we do random twin handling + // ------- + // If we fail a base chance twins roll, return without any additional processing and proceed with vanilla childbirth + // Notes on rolls: + // -> Chance with Littered Births gene: random between 2 and 4 (inclusive) + randomTwinsRoll = Rand.Chance(0.01f); + if (!randomTwinsRoll) + { + // We failed rolls - no additional processing, do vanilla single baby birth + if (LBTSettings.devMode) + { + LBTLogger.MessageGroupHead("Inside Hediff_Labor_PostRemovedPostFix random twins check fail"); + LBTLogger.MessageGroupBody("Pawn: " + __instance.pawn.NameShortColored); + LBTLogger.MessageGroupBody("Random twins roll outcome: " + randomTwinsRoll); + } + return; + } + + totalBirths = 2; + bool doTriplets = Rand.Chance(0.5f); + bool doQuadruplets = Rand.Chance(0.1f); + if (doTriplets) totalBirths = 3; + if (doTriplets && doQuadruplets) totalBirths = 4; + + // Set new LaborState + laborStateMap.Add(__instance.pawn.ThingID, new LaborState(__instance.pawn, totalBirths)); + } + + static void Hediff_LaborPushing_PostRemovedPostFix(ref Hediff_LaborPushing __instance) + { + bool hasLitteredBirthsGene = __instance.pawn.genes.HasGene(LitterBiotech.GeneDefOf.LitteredBirths); + bool laborStateIsNull = !laborStateMap.ContainsKey(__instance.pawn.ThingID); + LaborState currentLaborState; + laborStateMap.TryGetValue(__instance.pawn.ThingID, out currentLaborState); + + if (laborStateIsNull) + { + return; + } + + if (currentLaborState.birthTotal == currentLaborState.birthCount) + { + laborStateMap.Remove(__instance.pawn.ThingID); + if (__instance.pawn.health.hediffSet.HasHediff(HediffDef.Named("Bioscaffold"))) { + __instance.pawn.health.RemoveHediff(__instance.pawn.health.hediffSet.GetFirstHediffOfDef(LitterBiotech.HediffDefOf.Bioscaffold)); + } + return; + } + + ((Hediff_Labor)__instance.pawn.health.AddHediff(RimWorld.HediffDefOf.PregnancyLabor)).SetParents(__instance.pawn, __instance.Father, PregnancyUtility.GetInheritedGeneSet(__instance.Father, __instance.pawn)); + currentLaborState.birthCount++; + + if (!hasLitteredBirthsGene) + { + if (LBTSettings.devMode) + { + LBTLogger.Message("Pawn " + __instance.pawn.NameShortColored + " (" + __instance.pawn.ThingID + ") is having random twins"); + } + Find.LetterStack.ReceiveLetter("Twins!", __instance.pawn.NameShortColored + " is still in labor and is having twins!\n\nBe sure to gather your doctor and additional friends and family to ensure the other baby is also born healthy!", LitterBiotech.LetterDefOf.AnotherBaby, __instance.pawn); + return; + } + + Find.LetterStack.ReceiveLetter("Another baby!", __instance.pawn.NameShortColored + " is still in labor and is having another baby!\n\nBe sure to gather your doctor and additional friends and family to ensure the next baby is also born healthy!", LitterBiotech.LetterDefOf.AnotherBaby, __instance.pawn); + } + } +} diff --git a/Source/LitterBiotech.csproj b/Source/LitterBiotech.csproj new file mode 100644 index 0000000..b8c97c2 --- /dev/null +++ b/Source/LitterBiotech.csproj @@ -0,0 +1,70 @@ + + + + + Debug + AnyCPU + {D7D21B4A-1DA7-41D8-B202-C58CA8FA62AA} + Library + Properties + LitterBiotech + LitterBiotech + v4.7.2 + 512 + + + + none + true + ..\1.4\Assemblies\ + prompt + 4 + false + + + Always + + + + ..\..\..\..\..\workshop\content\294100\2009463077\Current\Assemblies\0Harmony.dll + + + ..\..\..\RimWorldLinux_Data\Managed\Assembly-CSharp.dll + False + + + ..\..\..\..\..\workshop\content\294100\2009463077\Current\Assemblies\HarmonyMod.dll + + + + + + + + + ..\..\..\RimWorldLinux_Data\Managed\UnityEngine.CoreModule.dll + False + + + + + + + + + + + + + + + + + + + + + cp "$(SolutionDir)Source/obj/Release/$(SolutionName).dll" "$(SolutionDir)1.4/Assemblies" + + + diff --git a/Source/Properties/AssemblyInfo.cs b/Source/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ffc8925 --- /dev/null +++ b/Source/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("LitterBiotech")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("LitterBiotech")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d7d21b4a-1da7-41d8-b202-c58ca8fa62aa")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Source/Settings/LBTSettings.cs b/Source/Settings/LBTSettings.cs new file mode 100644 index 0000000..f6243b1 --- /dev/null +++ b/Source/Settings/LBTSettings.cs @@ -0,0 +1,54 @@ +using UnityEngine; +using Verse; + +// If it isn't blatantly obvious, I unabashedly ripped this settings template from RJW, minus the fact that +// I won't be (personally) supporting multiplayer, and it's all in one file - but I digress ... +namespace LitterBiotech +{ + class LBTSettingsController : Mod + { + public LBTSettingsController(ModContentPack content) : base(content) + { + GetSettings(); + } + + public override string SettingsCategory() + { + return "LBTSettings".Translate(); + } + + public override void DoSettingsWindowContents(Rect inRect) + { + LBTSettings.DoWindowContents(inRect); + } + } + + public class LBTSettings : ModSettings + { + // For my own sanity, all now and future settings will have a default disabled/false state (at least, that's the plan), and + // the settings name and description should reflect that (not that I'm going to add that many settings, mind you) + public static bool devMode = false; + public static bool regretStealingLovinThoughtDisabled = false; + + public static void DoWindowContents(Rect inRect) + { + // Shrink the settings window a bit - don't need to be that w i d e + inRect.width = inRect.width - 400; + inRect.x = inRect.x + 200; + Listing_Standard listingStandard = new Listing_Standard(); + listingStandard.Begin(inRect); + listingStandard.Gap(4f); + listingStandard.CheckboxLabeled("EnableLBTDevLogging".Translate(), ref devMode, "EnableLBTDevLoggingDesc".Translate()); + listingStandard.Gap(4f); + listingStandard.CheckboxLabeled("RegretStealingLovinThoughtDisabled".Translate(), ref regretStealingLovinThoughtDisabled, "RegretStealingLovinThoughtDisabledDesc".Translate()); + listingStandard.End(); + } + + public override void ExposeData() + { + base.ExposeData(); + Scribe_Values.Look(ref devMode, "EnableLBTDevLogging", devMode, true); + Scribe_Values.Look(ref regretStealingLovinThoughtDisabled, "regretStealingLovinThoughtDisabled", regretStealingLovinThoughtDisabled, true); + } + } +}