diff --git a/CRIALactation/1.3/Assemblies/CRIALactation.dll b/CRIALactation/1.3/Assemblies/CRIALactation.dll
index 603de22..15d9f7e 100644
Binary files a/CRIALactation/1.3/Assemblies/CRIALactation.dll and b/CRIALactation/1.3/Assemblies/CRIALactation.dll differ
diff --git a/CRIALactation/CRIALactation.csproj b/CRIALactation/CRIALactation.csproj
index 05c1d6c..f1c7208 100644
--- a/CRIALactation/CRIALactation.csproj
+++ b/CRIALactation/CRIALactation.csproj
@@ -70,10 +70,14 @@
+
+
+
+
@@ -81,12 +85,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/CRIALactation/Defs/JobDefs/Jobs_CRIALactation.xml b/CRIALactation/Defs/JobDefs/Jobs_CRIALactation.xml
new file mode 100644
index 0000000..970f70b
--- /dev/null
+++ b/CRIALactation/Defs/JobDefs/Jobs_CRIALactation.xml
@@ -0,0 +1,9 @@
+
+
+
+ MassageBreasts
+ CRIALactation.JobDriver_MassageBreasts
+ stimulating TargetA's breasts.
+ true
+
+
diff --git a/CRIALactation/Defs/WorkGiverDefs/WorkGiver_MassageBreasts.xml b/CRIALactation/Defs/WorkGiverDefs/WorkGiver_MassageBreasts.xml
new file mode 100644
index 0000000..b1dee55
--- /dev/null
+++ b/CRIALactation/Defs/WorkGiverDefs/WorkGiver_MassageBreasts.xml
@@ -0,0 +1,15 @@
+
+
+
+ MassageBreasts
+
+ CRIALactation.WorkGiver_MassageBreasts
+ Handling
+ massage
+ stimulating the breasts of
+ 91
+
+ Manipulation
+
+
+
diff --git a/CRIALactation/Patches/Patch_LactationInduction.xml b/CRIALactation/Patches/Patch_LactationInduction.xml
new file mode 100644
index 0000000..fb63d15
--- /dev/null
+++ b/CRIALactation/Patches/Patch_LactationInduction.xml
@@ -0,0 +1,28 @@
+
+
+
+ Always
+
+
+ /Defs/ThingDef/comps
+ Always
+
+ /Defs/ThingDef
+
+
+
+
+
+
+
+ /Defs/ThingDef[@Name="BasePawn"]/comps
+
+
+ 15
+ 2.5
+
+
+
+
+
+
diff --git a/CRIALactation/Source/CompProperties/CompProperties_InduceLactation.cs b/CRIALactation/Source/CompProperties/CompProperties_InduceLactation.cs
new file mode 100644
index 0000000..9c119b2
--- /dev/null
+++ b/CRIALactation/Source/CompProperties/CompProperties_InduceLactation.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+using RimWorld;
+using rjw;
+using Milk;
+using UnityEngine;
+
+namespace CRIALactation
+{
+ public class CompProperties_InduceLactation : CompProperties
+ {
+ public CompProperties_InduceLactation()
+ {
+ this.compClass = typeof(CompInduceLactation);
+ }
+
+ public float DaysToLactating = 15;
+ public float TimesMassagedADay = 2.5f;
+ }
+}
diff --git a/CRIALactation/Source/Comps/CompInduceLactation.cs b/CRIALactation/Source/Comps/CompInduceLactation.cs
new file mode 100644
index 0000000..2695ffc
--- /dev/null
+++ b/CRIALactation/Source/Comps/CompInduceLactation.cs
@@ -0,0 +1,131 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+using RimWorld;
+using rjw;
+using Milk;
+using UnityEngine;
+
+namespace CRIALactation
+{
+ public class CompInduceLactation : ThingComp
+ {
+ private readonly int OneDayInTicks = 60000;
+ private int TicksSinceLastMassage = -60000;
+
+ private float InductionCompletionPercent = 0f;
+ public bool isActive = false;
+ public bool CanMassage = true;
+
+ public override void CompTick()
+ {
+ base.CompTick();
+ Pawn p = this.parent as Pawn;
+
+ if (!isActive)
+ {
+ return;
+ }
+ if (!p.IsHashIntervalTick(100))
+ {
+ return;
+ }
+
+ if (LactationUtility.IsLactating(p))
+ {
+ isActive = false;
+ return;
+ }
+
+ if(TicksSinceLastMassage + OneDayInTicks / Props.TimesMassagedADay < GenTicks.TicksGame)
+ {
+ CanMassage = true;
+ }
+
+ if(InductionCompletionPercent >= 1)
+ {
+ string main = p.Name.ToStringShort + "'s breasts have been stimulated enough to induce lactation! They can now begin producing milk for their colony's consumption.";
+
+ var letter = LetterMaker.MakeLetter(p.Name.ToStringShort + " induced lactation", main, LetterDefOf.PositiveEvent);
+ Find.LetterStack.ReceiveLetter(letter);
+
+ LactationUtility.StartLactating(p, true);
+ isActive = false;
+ InductionCompletionPercent = 0.6f; //start at 60% in case they ever lose lactating again
+ }
+ }
+
+ public void MassageBreasts()
+ {
+ InductionCompletionPercent += (float)1 / (Props.DaysToLactating * (Props.TimesMassagedADay + Rand.Value));
+ TicksSinceLastMassage = GenTicks.TicksGame;
+ CanMassage = false;
+ }
+
+ public CompProperties_InduceLactation Props
+ {
+ get
+ {
+ return (CompProperties_InduceLactation)props;
+ }
+ }
+
+ public override IEnumerable CompFloatMenuOptions(Pawn pawn)
+ {
+ if (pawn != this.parent as Pawn) yield break;
+ if (LactationUtility.IsLactating(pawn)) yield break;
+ if(LactationUtility.HasMilkableBreasts(this.parent as Pawn))
+ {
+ if(isActive)
+ {
+ //stop trying to induce lactation
+ yield return new FloatMenuOption("Undesignate induce lactation", () =>
+ {
+ isActive = false;
+ });
+ }
+ else
+ {
+ //induce lactation
+ yield return new FloatMenuOption("Designate induce lactation", () =>
+ {
+ isActive = true;
+ });
+ }
+ }
+ else
+ {
+ yield return new FloatMenuOption("Designate induce lactation (no milkable breasts)", null);
+ }
+
+ yield break;
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref this.InductionCompletionPercent, "InductionCompletionPercent", 0f);
+ Scribe_Values.Look(ref this.TicksSinceLastMassage, "TicksSinceLastMassage", -60000);
+
+ Scribe_Values.Look(ref this.isActive, "IsActive", false);
+ Scribe_Values.Look(ref this.CanMassage, "CanMassage", false);
+
+ }
+
+ public override string CompInspectStringExtra()
+ {
+ if (!isActive) return null;
+
+ string result = "Induce lactation completion: " + InductionCompletionPercent.ToStringPercent();
+
+ if(CanMassage)
+ {
+ result += "\nReady to massage.";
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/CRIALactation/Source/JobDefOf/JobDefOf_CRIALactation.cs b/CRIALactation/Source/JobDefOf/JobDefOf_CRIALactation.cs
new file mode 100644
index 0000000..7aa204d
--- /dev/null
+++ b/CRIALactation/Source/JobDefOf/JobDefOf_CRIALactation.cs
@@ -0,0 +1,21 @@
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace CRIALactation
+{
+ [DefOf]
+ public static class JobDefOf_CRIALactation
+ {
+ static JobDefOf_CRIALactation()
+ {
+ DefOfHelper.EnsureInitializedInCtor(typeof(HediffDefOf_Milk));
+ }
+
+ public static JobDef MassageBreasts;
+ }
+}
diff --git a/CRIALactation/Source/JobDrivers/JobDriver_MassageBreasts.cs b/CRIALactation/Source/JobDrivers/JobDriver_MassageBreasts.cs
new file mode 100644
index 0000000..4092ef0
--- /dev/null
+++ b/CRIALactation/Source/JobDrivers/JobDriver_MassageBreasts.cs
@@ -0,0 +1,79 @@
+using Milk;
+using rjw;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+using RimWorld;
+using Verse.AI;
+
+namespace CRIALactation
+{
+ public class JobDriver_MassageBreasts : JobDriver
+ {
+ private readonly float WorkTotal = 300f;
+
+ public override bool TryMakePreToilReservations(bool errorOnFailed)
+ {
+ LocalTargetInfo Target = job.GetTarget(TargetIndex.A);
+ return pawn.Reserve(Target, job, 1, -1, null, errorOnFailed);
+ }
+
+ protected override IEnumerable MakeNewToils()
+ {
+ this.FailOnDespawnedNullOrForbidden(TargetIndex.A);
+ yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.Touch);
+ Toil massage = new Toil();
+ massage.FailOnDespawnedOrNull(TargetIndex.A);
+ massage.FailOnAggroMentalStateAndHostile(TargetIndex.A);
+ massage.initAction = delegate
+ {
+ Pawn p = job.GetTarget(TargetIndex.A).Thing as Pawn;
+ pawn.pather.StopDead();
+ PawnUtility.ForceWait(p, 15000, null, true);
+ };
+ massage.tickAction = delegate ()
+ {
+ pawn.skills.Learn(SkillDefOf.Animals, 0.13f, false);
+ massageProgress += pawn.GetStatValue(StatDefOf.AnimalGatherSpeed, true);
+
+ };
+ massage.AddEndCondition(delegate
+ {
+ Pawn p = job.GetTarget(TargetIndex.A).Thing as Pawn;
+ if (massageProgress >= WorkTotal)
+ {
+ p.TryGetComp().MassageBreasts();
+ return JobCondition.Succeeded;
+ }
+
+ if (!(p.TryGetComp().isActive && p.TryGetComp().CanMassage))
+ {
+ return JobCondition.Incompletable;
+ }
+
+ return JobCondition.Ongoing;
+
+ });
+
+ massage.AddFinishAction(delegate {
+ Pawn pawn = this.job.GetTarget(TargetIndex.A).Thing as Pawn;
+ if (pawn != null && pawn.CurJobDef == JobDefOf.Wait_MaintainPosture)
+ {
+ pawn.jobs.EndCurrentJob(JobCondition.InterruptForced, true, true);
+ }
+ });
+ massage.defaultCompleteMode = ToilCompleteMode.Never;
+
+ massage.WithProgressBar(TargetIndex.A, () => massageProgress / WorkTotal);
+ massage.activeSkill = (() => SkillDefOf.Animals);
+ yield return massage;
+ yield break;
+
+ }
+
+ float massageProgress = 0f;
+ }
+}
diff --git a/CRIALactation/Source/LactationUtility.cs b/CRIALactation/Source/LactationUtility.cs
index 00e0246..12ee7d0 100644
--- a/CRIALactation/Source/LactationUtility.cs
+++ b/CRIALactation/Source/LactationUtility.cs
@@ -38,5 +38,17 @@ namespace CRIALactation
lactating.Severity = Rand.Value;
p.health.AddHediff(lactating, Genital_Helper.get_breastsBPR(p));
}
+
+ public static bool isMassageable(Pawn p)
+ {
+ CompInduceLactation c = p.TryGetComp();
+ if (c != null && c.isActive && c.CanMassage)
+ {
+ return true;
+ }
+
+ return false;
+
+ }
}
}
diff --git a/CRIALactation/Source/WorkGivers/WorkGiver_MassageBreasts.cs b/CRIALactation/Source/WorkGivers/WorkGiver_MassageBreasts.cs
new file mode 100644
index 0000000..db54550
--- /dev/null
+++ b/CRIALactation/Source/WorkGivers/WorkGiver_MassageBreasts.cs
@@ -0,0 +1,56 @@
+using Milk;
+using RimWorld;
+using RimWorld.Planet;
+using rjw;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+using Verse.AI;
+
+namespace CRIALactation
+{
+ public class WorkGiver_MassageBreasts : WorkGiver_Scanner
+ {
+ public override IEnumerable PotentialWorkThingsGlobal(Pawn pawn)
+ {
+ return pawn.Map.mapPawns.SpawnedPawnsInFaction(pawn.Faction);
+ }
+
+ public override bool ShouldSkip(Pawn pawn, bool forced = false)
+ {
+ List list = pawn.Map.mapPawns.SpawnedPawnsInFaction(pawn.Faction);
+ for(int i = 0; i < list.Count; i++)
+ {
+ if(LactationUtility.isMassageable(list[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public override bool HasJobOnThing(Pawn p, Thing t, bool forced = false)
+ {
+ Pawn pawn2 = t as Pawn;
+ if(pawn2?.TryGetComp() == null)
+ {
+ return false;
+ }
+ CompInduceLactation c = pawn2.TryGetComp();
+
+ return p != pawn2 && c.isActive && c.CanMassage && !pawn2.Downed && !pawn2.Drafted && !pawn2.InAggroMentalState && !pawn2.IsFormingCaravan() && pawn2.CanCasuallyInteractNow(false, true, false) && p.CanReserve(pawn2, 1, -1, null, forced);
+
+ }
+
+ public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
+ {
+ return JobMaker.MakeJob(JobDefOf_CRIALactation.MassageBreasts, t);
+ }
+
+
+ }
+}