code:
using RimWorld;
using Verse;
using HarmonyLib;
using rjw;
using Verse.AI;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Linq;
using rjw.Modules.Shared.Enums;
using rjw.Modules.Interactions.Internals.Implementation;
using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.Objects;
using RJW_Onahole.Utilities;
using UnityEngine;
using RJW_Onahole.Jobs;
using RJW_Onahole.Comps;

namespace RJW_Onahole.HarmonyPatches
{
    [StaticConstructorOnStartup]
    static class BulkPatches
    {
        /// <summary>
        /// Initiates all patches
        /// </summary>
        static BulkPatches()
        {
            RoomRolePatches();
        }
        /// <summary>
        /// Patches all room role workers to ignore onahole beds
        /// </summary>
        private static void RoomRolePatches()
        {
            "Start room role patches".Log();
            MethodInfo transpiler = AccessTools.Method(typeof(BulkPatches), nameof(GetScore_Transpiler));
            foreach (MethodBase method in PatchUtility.GetMethods(typeof(RoomRoleWorker).GetSubTypes(), "GetScore"))
            {
                if (!method.IsAbstract && method.DeclaringType != typeof(RoomRoleWorker_PrisonCell) && method.DeclaringType != typeof(RoomRoleWorker_PrisonBarracks))
                {
                    RJW_Onahole.Harmony.Patch(method, transpiler: new HarmonyMethod(transpiler));
                }
            }
        }
        private static readonly MethodInfo get_ForColonists = AccessTools.Method(typeof(Building_Bed), "get_ForColonists");
        private static readonly MethodInfo get_ForPrisoners = AccessTools.Method(typeof(Building_Bed), "get_ForPrisoners");
        private static readonly MethodInfo get_ForSlaves = AccessTools.Method(typeof(Building_Bed), "get_ForSlaves");
        private static readonly MethodInfo forNonOnaholeColonists = AccessTools.Method(typeof(BulkPatches), nameof(ForNonOnaholeColonists));
        private static readonly MethodInfo forNonOnaholePrisoners = AccessTools.Method(typeof(BulkPatches), nameof(ForNonOnaholePrisoners));
        private static readonly MethodInfo forNonOnaholeSlaves = AccessTools.Method(typeof(BulkPatches), nameof(ForNonOnaholeSlaves));
        private static IEnumerable<CodeInstruction> GetScore_Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original)
        {
            List<CodeInstruction> codes = instructions.ToList();
            for (int i = 0; i < codes.Count; i++)
            {
                // If calls get_For_xxx and and executes something when true, bypass them if the building is a onahole bed
                if (codes[i].Calls(get_ForColonists) && codes[i + 1].opcode == OpCodes.Brfalse_S)
                {
                    codes[i].operand = forNonOnaholeColonists;
                    ("Patched Role Worker: " + original.DeclaringType.Name).Log();
                }
                if (codes[i].Calls(get_ForPrisoners) && codes[i + 1].opcode == OpCodes.Brfalse_S)
                {
                    codes[i].operand = forNonOnaholePrisoners;
                    ("Patched Role Worker: " + original.DeclaringType.Name).Log();
                }
                if (codes[i].Calls(get_ForSlaves) && codes[i + 1].opcode == OpCodes.Brfalse_S)
                {
                    codes[i].operand = forNonOnaholeSlaves;
                    ("Patched Role Worker: " + original.DeclaringType.Name).Log();
                }

            }
            return codes.AsEnumerable();
        }
        private static bool ForNonOnaholeColonists(Building_Bed bed)
        {
            if (bed is Building_OnaholeBed)
            {
                return false;
            }
            return bed.ForColonists;
        }
        private static bool ForNonOnaholePrisoners(Building_Bed bed)
        {
            if (bed is Building_OnaholeBed)
            {
                return false;
            }
            return bed.ForPrisoners;
        }
        private static bool ForNonOnaholeSlaves(Building_Bed bed)
        {
            if (bed is Building_OnaholeBed)
            {
                return false;
            }
            return bed.ForSlaves;
        }

    }

}


code:
using RimWorld;
using Verse;
using HarmonyLib;
using rjw;
using Verse.AI;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using rjw.Modules.Interactions.Objects;
using RJW_Onahole.Utilities;
using UnityEngine;
using RJW_Onahole.Jobs;
using RJW_Onahole.Comps;

namespace RJW_Onahole.HarmonyPatches
{
    /// <summary>
    /// Try to be compatible with mods that modifies prisoner think tree
    /// </summary>
    [HarmonyPatch(typeof(ThinkNode_ConditionalPrisonerInPrisonCell))]
    class Patch_ThinkNode_ConditionalPrisonerInPrisonCell
    {
        [HarmonyPrefix]
        [HarmonyPatch("Satisfied")]
        public static bool Satisfied_Prefix(Pawn pawn, ref bool __result)
        {
            if (pawn.IsOnahole())
            {
                __result = true;
                return false;
            }
            return true;
        }
    }
    /// <summary>
    /// Avoid null reference when using brothel colony
    /// </summary>
    [HarmonyPatch(typeof(Quirk), "CountSatisfiedQuirks")]
    class Patch_Quirk
    {
        private static readonly FieldInfo SexSatisfiesFunc = AccessTools.Field(typeof(Quirk), "SexSatisfiesFunc");
        [HarmonyPrefix]
        [HarmonyPriority(Priority.First)]
        public static bool CountSatisfiedQuirks_Prefix(ref int __result, Quirk __instance, SexProps props)
        {
            if (props.pawn.IsOnahole())
            {
                // Return vanilla to prevent null reference when brothel colony is loaded.
                __result = Enumerable.Count<Quirk>(Enumerable.Where<Quirk>(Quirk.All, (Quirk quirk) => SexSatisfiesFunc.GetValue(quirk) is Func<SexProps, bool> value && props.pawn.Has(quirk) && value(props)));
                return false;
            }
            return true;
        }
    }

    /// <summary>
    /// Filter interactions by blocked parts
    /// </summary>
    [HarmonyPatch(typeof(InteractionPawn))]
    public static class InteractionPawn_Patch
    {
        [HarmonyPostfix]
        [HarmonyPatch("get_BlockedParts")]
        public static void get_BlockedParts_Postfix(ref IList<rjw.Modules.Interactions.Enums.LewdablePartKind> __result, InteractionPawn __instance)
        {
            if (__instance.Pawn.IsOnahole())
            {
                __result.AddDistinct(rjw.Modules.Interactions.Enums.LewdablePartKind.Hand);
                __result.AddDistinct(rjw.Modules.Interactions.Enums.LewdablePartKind.Foot);
                if (RJW_Onahole_Settings.machineBlocksParts)
                {
                    if (__instance.Pawn.health.hediffSet.HasHediff(OnaholeDefOf.OnaholeVaginalSexMachine))
                    {
                        __result.AddDistinct(rjw.Modules.Interactions.Enums.LewdablePartKind.Vagina);
                    }
                    if (__instance.Pawn.health.hediffSet.HasHediff(OnaholeDefOf.OnaholeAnalSexMachine))
                    {
                        __result.AddDistinct(rjw.Modules.Interactions.Enums.LewdablePartKind.Anus);
                    }
                    if (__instance.Pawn.health.hediffSet.HasHediff(OnaholeDefOf.OnaholeMilkingMachine))
                    {
                        __result.AddDistinct(rjw.Modules.Interactions.Enums.LewdablePartKind.Breasts);
                    }
                    if (__instance.Pawn.health.hediffSet.HasHediff(OnaholeDefOf.OnaholeAutoFeeder))
                    {
                        __result.AddDistinct(rjw.Modules.Interactions.Enums.LewdablePartKind.Mouth);
                        __result.AddDistinct(rjw.Modules.Interactions.Enums.LewdablePartKind.Tongue);
                    }
                }
            }
        }
    }

    /// <summary>
    /// Hediff can block genitals
    /// </summary>
    [HarmonyPatch(typeof(Genital_Helper))]
    class Genital_Helper_Patch
    {
        [HarmonyPostfix]
        [HarmonyPatch("vagina_blocked")]
        public static void vagina_blocked_Postfix(ref bool __result, Pawn pawn)
        {
            if (__result == false)
            {
                if (pawn.health.hediffSet.HasHediff(OnaholeDefOf.OnaholeVaginalSexMachine) && RJW_Onahole_Settings.machineBlocksParts)
                {
                    __result = true;
                }
            }
        }
        [HarmonyPostfix]
        [HarmonyPatch("anus_blocked")]
        public static void anus_blocked_Postfix(ref bool __result, Pawn pawn)
        {
            if (__result == false)
            {
                if (pawn.health.hediffSet.HasHediff(OnaholeDefOf.OnaholeAnalSexMachine) && RJW_Onahole_Settings.machineBlocksParts)
                {
                    __result = true;
                }
            }
        }
        [HarmonyPostfix]
        [HarmonyPatch("oral_blocked")]
        public static void oral_blocked_blocked_Postfix(ref bool __result, Pawn pawn)
        {
            if (__result == false)
            {
                if (pawn.health.hediffSet.HasHediff(OnaholeDefOf.OnaholeAutoFeeder) && RJW_Onahole_Settings.machineBlocksParts)
                {
                    __result = true;
                }
            }
        }
        [HarmonyPostfix]
        [HarmonyPatch("hands_blocked")]
        public static void hands_blocked_Postfix(ref bool __result, Pawn pawn)
        {
            if (__result == false)
            {
                if (pawn.IsOnahole())
                {
                    __result = true;
                }
            }
        }
    }
    /// <summary>
    /// Add null checks to prevent null reference
    /// </summary>
    [HarmonyPatch(typeof(Genital_Helper))]
    class Genital_Helper_Patch2
    {
        [HarmonyPrefix]
        [HarmonyPatch("get_PartsHediffList")]
        public static bool get_PartsHediffList_Prefix(ref List<Hediff> __result, Pawn pawn)
        {
            if (pawn == null)
            {
                __result = new List<Hediff>();
                return false;
            }
            return true;
        }
        [HarmonyPrefix]
        [HarmonyPatch("get_AllPartsHediffList")]
        public static bool get_AllPartsHediffList_Prefix(ref List<Hediff> __result, Pawn pawn)
        {
            if (pawn == null)
            {
                __result = new List<Hediff>();
                return false;
            }
            return true;
        }
    }
    /// <summary>
    /// Force Pawn to stay on onahole job. Prevent being interrupted by other jobs
    /// </summary>
    [HarmonyPatch(typeof(Pawn_JobTracker))]
    public static class Patch_Pawn_JobTracker
    {
        [HarmonyPrefix]
        [HarmonyPatch("EndCurrentJob")]
        public static bool EndCurrentJob_Prefix(Pawn_JobTracker __instance, JobCondition condition)
        {
            if (condition != JobCondition.Errored && __instance.curDriver is JobDriver_BeOnahole)
            {
                return false;
            }
            return true;
        }
        [HarmonyPrefix]
        [HarmonyPatch("StartJob")]
        public static bool StartJob_Prefix(Pawn_JobTracker __instance, JobCondition lastJobEndCondition)
        {
            if (lastJobEndCondition != JobCondition.Errored && __instance.curDriver is JobDriver_BeOnahole)
            {
                __instance.startingNewJob = false;
                return false;
            }
            return true;
        }
    }
    /// <summary>
    /// Compatibility with directly ordered jobs
    /// </summary>
    [HarmonyPatch]
    public static class RMB_Patch_541
    {
        public static bool Prepare()
        {
            return AccessTools.TypeByName("rjw.RMB_Menu") != null;
        }
        public static MethodBase TargetMethod()
        {
            //RJW 5.4.1
            Type type = AccessTools.TypeByName("rjw.RMB_Menu");
            return AccessTools.Method(type, "HaveSex");
        }
        private static bool Prefix(Pawn pawn, JobDef jobDef, LocalTargetInfo target, InteractionDef dictionaryKey)
        {
            if (pawn.IsOnahole()) { return true; }
            Pawn targetPawn = target.Pawn;
            if (!targetPawn.IsOnahole()) { return true; }
            targetPawn.ClearOnaholeReservation();
            Job job = JobMaker.MakeJob(OnaholeDefOf.UseOnahole, targetPawn, targetPawn.OnaholeBed());
            job.playerForced = true;
            SexProps sexProps = new SexProps
            {
                pawn = pawn,
                partner = targetPawn,
                sexType = SexUtility.rjwSextypeGet(dictionaryKey),
                isRape = true,
                isRapist = true,
                canBeGuilty = false,
                dictionaryKey = dictionaryKey,
                rulePack = SexUtility.SexRulePackGet(dictionaryKey)
            };
            pawn.GetRJWPawnData().SexProps = sexProps;
            pawn.jobs.TryTakeOrderedJob(job, new JobTag?(JobTag.Misc), false);
            return false;

        }
    }
    [HarmonyPatch]
    public static class RMB_Patch_550
    {
        public static bool Prepare()
        {
            return AccessTools.TypeByName("rjw.RMB.RMB_Menu") != null;
        }
        public static MethodBase TargetMethod()
        {
            //RJW 5.5.0
            Type type = AccessTools.TypeByName("rjw.RMB.RMB_Menu");
            return AccessTools.Method(type, "HaveSex");
        }
        private static bool Prefix(Pawn pawn, JobDef jobDef, LocalTargetInfo target, InteractionDef interactionDef)
        {
            if (pawn.IsOnahole()) { return true; }
            Pawn targetPawn = target.Pawn;
            if (!targetPawn.IsOnahole()) { return true; }
            targetPawn.ClearOnaholeReservation();
            Job job = JobMaker.MakeJob(OnaholeDefOf.UseOnahole, targetPawn, targetPawn.OnaholeBed());
            job.playerForced = true;
            SexProps sexProps = new SexProps
            {
                pawn = pawn,
                partner = targetPawn,
                sexType = SexUtility.rjwSextypeGet(interactionDef),
                isRape = true,
                isRapist = true,
                canBeGuilty = false,
                dictionaryKey = interactionDef,
                rulePack = SexUtility.SexRulePackGet(interactionDef)
            };
            pawn.GetRJWPawnData().SexProps = sexProps;
            pawn.jobs.TryTakeOrderedJob(job, new JobTag?(JobTag.Misc), false);
            return false;

        }
    }
    [HarmonyPatch(typeof(Hediff_InsectEgg))]
    public static class Patch_Hediff_InsectEgg
    {
        public static readonly FieldInfo contractions = AccessTools.Field(typeof(Hediff_InsectEgg), "contractions");
        [HarmonyPrefix]
        [HarmonyPatch("Tick")]
        public static void Tick_Prefix(Hediff_InsectEgg __instance)
        {
            JobDriver jobDriver = __instance.pawn.jobs.curDriver;
            int contractionsValue = (int)contractions.GetValue(__instance);
            if (__instance.GestationProgress >= 1f && contractionsValue == 0 && jobDriver is JobDriver_BeOnahole)
            {
                if (PawnUtility.ShouldSendNotificationAbout(__instance.pawn) && (__instance.pawn.IsColonist || __instance.pawn.IsPrisonerOfColony))
                {
                    Messages.Message("RJW_EggContractions".Translate(__instance.pawn.LabelIndefinite()), __instance.pawn, MessageTypeDefOf.NeutralEvent, true);
                }
                contractions.SetValue(__instance, contractionsValue + 1);
                if (!Genital_Helper.has_ovipositorF(__instance.pawn, null))
                {
                    __instance.pawn.health.AddHediff(xxx.submitting, null, null, null);
                }
            }
        }
    }
    /// <summary>
    /// Avoid Null reference OnGUI root level exception when using adaptive storage
    /// </summary>
    [HarmonyPatch]
    public static class AdaptiveStorage_Patch
    {
        public static bool Prepare()
        {
            return AccessTools.TypeByName("AdaptiveStorage.InspectTabUtility") != null;
        }
        public static MethodBase TargetMethod()
        {
            //RJW 5.5.0
            Type type = AccessTools.TypeByName("AdaptiveStorage.InspectTabUtility");
            return AccessTools.Method(type, "TryOpen");
        }
        public static bool Prefix(ISelectable selectable)
        {
            return !(selectable is Building_OnaholeBed);
        }

    }
    /// <summary>
    /// Avoid rimworld freaking out about a shader not in the "Can be used with mask" whitelist
    /// </summary>
    [HarmonyPatch(typeof(ShaderUtility))]
    public static class Patch_ShaderUtility
    {
        [HarmonyPrefix]
        [HarmonyPatch("SupportsMaskTex")]
        public static bool SupportsMaskTex_Prefix(ref bool __result, Shader shader)
        {
            __result = shader.FindPropertyIndex("_MaskTex") != -1;
            return false;
        }
    }
    /// <summary>
    /// Allow binding mechanoids to onahole buildings. Add null check for story, avoid error spam
    /// </summary>
    [HarmonyPatch(typeof(Graphic_PawnBodySilhouette))]
    public static class Patch_Graphic_PawnBodySilhouette
    {
        [HarmonyPrefix]
        [HarmonyPatch("DrawWorker")]
        public static bool DrawWorker_Prefix(Thing thing)
        {
            Mote mote = (Mote)thing;
            if (mote.link1.Target.Thing is Pawn pawn && pawn.story == null && pawn.IsOnahole())
            {
                return false;
            }
            return true;
        }
    }
    [HarmonyPatch(typeof(GenGuest))]
    public static class Patch_GenGuest
    {
        [HarmonyPostfix]
        [HarmonyPatch(nameof(GenGuest.TryEnslavePrisoner))]
        public static void TryEnslavePrisoner_Postfix(Pawn prisoner, bool __result)
        {
            if (__result == true && prisoner.OnaholeBed() is Building_OnaholeBed bed)
            {
                bed.TryGetComp<CompOnaholeBed>().Notify_PawnBind(prisoner);
            }
        }
    }
    [HarmonyPatch(typeof(RecruitUtility))]
    public static class Patch_RecruitUtility
    {
        [HarmonyPostfix]
        [HarmonyPatch(nameof(RecruitUtility.Recruit))]
        public static void Recruit_Postfix(Pawn pawn)
        {
            if (pawn.OnaholeBed() is Building_OnaholeBed bed)
            {
                bed.TryGetComp<CompOnaholeBed>().Notify_PawnBind(pawn);
            }
        }

    }
    /// <summary>
    /// Non-humanlike pawns that don't have mood
    /// </summary>
    [HarmonyPatch(typeof(AfterSexUtility))]
    public static class Patch_AfterSexUtility
    {
        [HarmonyPrefix]
        [HarmonyPatch(nameof(AfterSexUtility.think_about_sex_Victim))]
        public static bool think_about_sex_Victim_Prefix(Pawn pawn, ref ThoughtDef __result)
        {
            if (pawn?.needs?.mood == null) { __result = null; return false; }
            return true;
        }
    }

    /// <summary>
    /// Avoid issues with item teleporter and maybe other storage mods. //TODO Make patches less aggressive
    /// </summary>
    [HarmonyPatch(typeof(HaulDestinationManager))]
    public static class Patch_HaulDestinationManager
    {
        [HarmonyFinalizer]
        [HarmonyPatch(nameof(HaulDestinationManager.SlotGroupAt))]
        public static Exception SlotGroupAt_Finalizer(ref SlotGroup __result, Exception __exception)
        {
            if (__exception is IndexOutOfRangeException)
            {
                __result = null;
                return null;
            }
            return __exception;
        }
    }

    /// <summary>
    /// Avoid issues with item teleporter and maybe other storage mods. //TODO Make patches less aggressive
    /// </summary>
    [HarmonyPatch(typeof(GridsUtility))]
    public static class Patch_GridsUtility
    {
        [HarmonyFinalizer]
        [HarmonyPatch(nameof(GridsUtility.GetThingList))]
        public static Exception GetThingList_Finalizer(ref List<Thing> __result, Exception __exception)
        {
            if (__exception is NullReferenceException)
            {
                __result = __result ?? new List<Thing>();
                return null;
            }
            return __exception;
        }
    }

    /// <summary>
    /// Avoid issues with item teleporter and maybe other storage mods. //TODO Make patches less aggressive
    /// </summary>
    [HarmonyPatch(typeof(StoreUtility))]
    public static class Patch_StoreUtility
    {
        [HarmonyFinalizer]
        [HarmonyPatch(nameof(StoreUtility.IsGoodStoreCell))]
        public static Exception IsGoodStoreCell_Finalizer(ref bool __result, Exception __exception)
        {
            if (__exception is NullReferenceException)
            {
                __result = false;
                return null;
            }
            return __exception;
        }
    }

}

repo_name: atainer/rimjobworld

license: mit


code:
using RimWorld;
using Verse;
using HarmonyLib;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Linq;
using RJW_Onahole.Utilities;
using RimWorld.Planet;

namespace RJW_Onahole.HarmonyPatches
{
    /// <summary>
    /// Trigger instanced draw call events
    /// </summary>
    [HarmonyPatch(typeof(Map))]
    public static class Map_Patch
    {
        [HarmonyPrefix]
        [HarmonyPatch(nameof(Map.MapUpdate))]
        public static void MapUpdate_Prefix(Map __instance)
        {
            if (!WorldRendererUtility.WorldRenderedNow && Find.CurrentMap == __instance)
            {
                EventUtility.TriggerPreDraw();
            }
        }

        [HarmonyPostfix]
        [HarmonyPatch(nameof(Map.MapUpdate))]
        public static void MapUpdate_Postfix(Map __instance)
        {
            if (!WorldRendererUtility.WorldRenderedNow && Find.CurrentMap == __instance)
            {
                EventUtility.TriggerPostDraw();
            }
        }
    }
    /// <summary>
    /// Fix rotation, rotation angle issue for non-humanlike
    /// </summary>
    [HarmonyPatch(typeof(PawnRenderer))]
    public static class PawnRenderer_Patch
    {
        private static readonly MethodInfo getPosture = AccessTools.Method(typeof(PawnUtility), nameof(PawnUtility.GetPosture));
        private static readonly MethodInfo isNonStandingNonOnahole = AccessTools.Method(typeof(PawnRenderer_Patch), nameof(IsNonStandingNoneOnahole));
        [HarmonyTranspiler]
        [HarmonyPatch(nameof(PawnRenderer.BodyAngle))]
        public static IEnumerable<CodeInstruction> GetBodyPos_Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            List<CodeInstruction> codes = instructions.ToList();
            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].Calls(getPosture))
                {
                    codes.InsertRange(i + 1, new List<CodeInstruction>()
                    {
                        new CodeInstruction(OpCodes.Ldarg_0),
                        new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(PawnRenderer), "pawn")),
                        new CodeInstruction(OpCodes.Call, isNonStandingNonOnahole)
                    });
                    return codes.AsEnumerable();
                }
            }
            "Unable to patch PawnRenderer.BodyAngle, animal lying posture will be incorrect".LogError();
            return instructions;
        }
        private static bool IsNonStandingNoneOnahole(PawnPosture posture, Pawn pawn)
        {
            return posture != PawnPosture.Standing && !pawn.IsOnahole();
        }
    }
}


code:
using RimWorld;
using Verse;
using HarmonyLib;
using rjw;
using Verse.AI;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Linq;
using rjw.Modules.Shared.Enums;
using rjw.Modules.Interactions.Internals.Implementation;
using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.Objects;
using RJW_Onahole.Utilities;
using UnityEngine;
using RJW_Onahole.Comps;
using RJW_Onahole.Jobs;
using RJW_Onahole.Data;

namespace RJW_Onahole.HarmonyPatches
{
    /// <summary>
    /// Prevent default rjw logic from using Onahole
    /// </summary>
    [HarmonyPatch(typeof(SexAppraiser))]
    class Patch_SexAppraiser
    {
        [HarmonyPostfix]
        [HarmonyPatch("would_fuck", new Type[] { typeof(Pawn), typeof(Pawn), typeof(bool), typeof(bool), typeof(bool) })]
        public static void would_fuck_Postfix(ref float __result, Pawn fucked, Pawn fucker)
        {
            if (fucked.IsOnahole())
            {
                __result -= 1000;
            }
        }
    }
    /// <summary>
    /// Allows prisoners to use Onahole bed outside of prison
    /// </summary>
    [HarmonyPatch(typeof(RestUtility))]
    class Patch_RestUtility
    {
        [HarmonyPrefix]
        [HarmonyPatch(nameof(RestUtility.IsValidBedFor))]
        // Higher priority to prevent default or patched logic from using Onahole bed
        [HarmonyPriority(Priority.First)]
        public static bool IsValidBedFor_Prefix(ref bool __result, Thing bedThing, Pawn sleeper)
        {
            if (bedThing is Building_OnaholeBed bed && bed.BoundPawn != sleeper)
            {
                __result = false;
                return false;
            }
            return true;
        }
        [HarmonyPrefix]
        [HarmonyPatch(nameof(RestUtility.CanUseBedNow))]
        [HarmonyPriority(Priority.First)]
        public static bool CanUseBedNow_Prefix(Thing bedThing, Pawn sleeper, ref bool __result)
        {
            if (bedThing is Building_OnaholeBed bed && bedThing.Spawned && bed.BoundPawn == sleeper)
            {
                __result = true;
                return false;
            }
            return true;
        }
        // If using game logic to tuck, notify bind
        [HarmonyPostfix]
        [HarmonyPatch(nameof(RestUtility.TuckIntoBed))]
        public static void TuckIntoBed_Postfix(Building_Bed bed, Pawn takee)
        {
            if (bed is Building_OnaholeBed onaholeBed)
            {
                onaholeBed.GetComp<CompOnaholeBed>().Notify_PawnBind(takee);
            }
        }
    }
    /// <summary>
    /// Prevent onahole bed's ownership type from being changed by a change of bed type in the same room
    /// </summary>
    [HarmonyPatch]
    class Patch_Building_Bed_Internal
    {
        public static MethodBase TargetMethod()
        {
            Type internalClass = AccessTools.Inner(typeof(Building_Bed), "<>c__DisplayClass55_0");
            return AccessTools.Method(internalClass, "<SetBedOwnerTypeByInterface>b__0");
        }

        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            List<CodeInstruction> codes = instructions.ToList();
            MethodInfo getEnumeratorMethod = AccessTools.Method(typeof(List<Building_Bed>), "GetEnumerator");
            MethodInfo removeMethod = AccessTools.Method(typeof(Patch_Building_Bed_Internal), nameof(RemoveOnaholeBed));
            bool found = false;
            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].Calls(getEnumeratorMethod))
                {
                    codes.Insert(i, new CodeInstruction(OpCodes.Call, removeMethod));
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                "Could not find Building_Bed internal method to patch, bed assignment in the same room will cause issues".LogError();
            }
            return codes;
        }
        public static List<Building_Bed> RemoveOnaholeBed(List<Building_Bed> list)
        {
            list.RemoveAll(x => x is Building_OnaholeBed);
            return list;
        }
    }

    [HarmonyPatch(typeof(SexUtility))]
    public class Patch_SexUtility
    {
        /// <summary>
        /// Reduce resistance once beaten
        /// </summary>
        [HarmonyPrefix]
        [HarmonyPatch("Sex_Beatings_Dohit")]
        public static void Roll_to_hit_Prefix(Pawn pawn, Pawn partner)
        {
            if (partner.IsOnahole())
            {
                partner.ReduceResistance(pawn, UnityEngine.Random.Range(0.1f, 0.3f));
            }
        }
        /// <summary>
        /// Collect filth
        /// </summary>
        /// <param name="instructions"></param>
        /// <returns></returns>
        [HarmonyTranspiler]
        [HarmonyPatch("CumFilthGenerator")]
        public static IEnumerable<CodeInstruction> CumFilthGenerator_Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            List<CodeInstruction> codes = instructions.ToList();
            MethodInfo cumFilth = AccessTools.Method(typeof(FilthMaker), "TryMakeFilth", new Type[] { typeof(IntVec3), typeof(Map), typeof(ThingDef), typeof(string), typeof(int), typeof(FilthSourceFlags) });
            MethodInfo cumFilthOnahole = AccessTools.Method(typeof(Patch_SexUtility), nameof(CumFilthGeneratorOnahole));
            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].opcode == OpCodes.Call && (MethodInfo)codes[i].operand == cumFilth)
                {
                    codes[i].operand = cumFilthOnahole;
                    codes.Insert(i, new CodeInstruction(OpCodes.Ldarg_0)); // Load Pawn
                }
            }
            return codes.AsEnumerable();
        }
        public static bool CumFilthGeneratorOnahole(IntVec3 c, Map map, ThingDef filthDef, string source, int count, FilthSourceFlags flags, Pawn pawn)
        {
            if (pawn != null && count > 0)
            {
                CompCumCollector cumCollector = (pawn.OnaholeBed()?.GetComp<CompCumCollector>()) ?? (pawn.OnaholeBedWithin8Cells()?.GetComp<CompCumCollector>());
                if (cumCollector != null) { count = Mathf.FloorToInt(cumCollector.TryStoreCum(count, filthDef)); }
                if (count <= 0)
                {
                    return true;
                }
            }
            // Make filth for overflow or not collected
            return FilthMaker.TryMakeFilth(c, map, filthDef, source, count, flags);
        }
    }
    [HarmonyPatch(typeof(JobDriver_Sex))]
    class Patch_JobDriver_Sex
    {

        [HarmonyPrefix]
        [HarmonyPatch(nameof(JobDriver_Sex.Orgasm))]
        public static void Orgasm_Prefix(JobDriver_Sex __instance, out bool __state)
        {
            __state = false;
            // Force End Job on orgasm
            if (__instance.sex_ticks > __instance.orgasmstick)
            {
                return;
            }
            __state = true;
        }
        [HarmonyPostfix]
        [HarmonyPatch(nameof(JobDriver_Sex.Orgasm))]
        public static void Orgasm_Postfix(JobDriver_Sex __instance, bool __state)
        {
            if (__state)
            {
                if (__instance is JobDriver_UseOnahole jobDriver_UseOnahole)
                {
                    jobDriver_UseOnahole.finished = true;
                    jobDriver_UseOnahole.ReadyForNextToil();
                }
            }
        }

    }

    /// <summary>
    /// Make onahole must lay down
    /// </summary>
    [HarmonyPatch(typeof(ThinkNode_ConditionalMustKeepLyingDown))]
    class Patch_ThinkNode_ConditionalMustKeepLyingDown
    {
        [HarmonyPrefix]
        [HarmonyPatch("Satisfied")]
        public static bool Satisfied_Prefix(Pawn pawn, ref bool __result)
        {
            if (pawn.IsOnahole())
            {
                __result = true;
                return false;
            }
            return true;
        }
    }
    /// <summary>
    /// Prevent onaholes escape (IDK if it is needed)
    /// </summary>
    [HarmonyPatch(typeof(JobGiver_PrisonerEscape))]
    class Patch_JobGiver_PrisonerEscape
    {
        [HarmonyPrefix]
        [HarmonyPatch("ShouldStartEscaping")]
        public static bool ShouldStartEscaping_Prefix(Pawn pawn, ref bool __result)
        {
            if (pawn.IsOnahole())
            {
                __result = false;
                return false;
            }
            return true;
        }
    }

    // Prevents enemies from harming onaholes, so they can safely enjoy the onaholes
    [HarmonyPatch(typeof(JobGiver_AIFightEnemy))]
    class JobGiver_AIFightEnemy_Patch
    {
        [HarmonyPostfix]
        [HarmonyPatch("ExtraTargetValidator")]
        public static void ExtraTargetValidator_PostFix(Pawn pawn, Thing target, ref bool __result)
        {
            __result = __result && !(target is Building_OnaholeBed) && !(target as Pawn).IsOnahole();
        }
    }
    [HarmonyPatch]
    class KidnapAIUtility_Patch
    {
        private static MethodBase TargetMethod()
        {
            Type internalClass = AccessTools.Inner(typeof(KidnapAIUtility), "<>c__DisplayClass0_0");
            return AccessTools.Method(internalClass, "<TryFindGoodKidnapVictim>b__0");
        }
        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            List<CodeInstruction> codes = instructions.ToList();
            // Define labels for branching
            Label label = new Label();

            // Iterate through the IL codes and insert the if block
            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].opcode == OpCodes.Ldarg_1 && codes[i + 1].opcode == OpCodes.Isinst && (Type)codes[i + 1].operand == typeof(Pawn))
                {
                    // Insert the if block
                    List<CodeInstruction> newCodes = new List<CodeInstruction>
                    {
                        // Load the context
                        new CodeInstruction(OpCodes.Ldloc_0),
                        // Run if IsPartnerOnahole is true
                        new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(KidnapAIUtility_Patch), nameof(IsPartnerOnahole))),
                        // Skip if true
                        new CodeInstruction(OpCodes.Brtrue_S, label),
                        // Push false to stack
                        new CodeInstruction(OpCodes.Ldc_I4_0),
                        // Return false
                        new CodeInstruction(OpCodes.Ret),
                        // Skip if true
                        new CodeInstruction(OpCodes.Nop) { labels = new List<Label> { label } }
                    };

                    codes.InsertRange(i, newCodes);
                    break;
                }
            }
            return codes.AsEnumerable();
        }
        private static bool IsPartnerOnahole(Pawn pawn)
        {
            return pawn.IsOnahole();
        }
    }

    // Prevent room to be considered as prison cell if it contains only onahole bed
    [HarmonyPatch(typeof(RoomRoleWorker_PrisonCell))]
    public static class RoomRoleWorker_PrisonCell_Patch
    {
        [HarmonyTranspiler]
        [HarmonyPatch("GetScore")]
        public static IEnumerable<CodeInstruction> GetScore_Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            List<CodeInstruction> codes = instructions.ToList();
            MethodInfo get_ForPrisoners = AccessTools.Method(typeof(Building_Bed), "get_ForPrisoners");
            MethodInfo forPrisoners = AccessTools.Method(typeof(RoomRoleWorker_PrisonCell_Patch), nameof(ForPrisoners));
            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].Calls(get_ForPrisoners))
                {
                    codes[i].operand = forPrisoners;
                    break;
                }
            }
            return codes.AsEnumerable();
        }
        public static bool ForPrisoners(Building_Bed bed)
        {
            if (bed is Building_OnaholeBed)
            {
                return false;
            }
            return bed.ForPrisoners;
        }
    }
    [HarmonyPatch(typeof(Room))]
    public static class Room_Patch
    {
        [HarmonyTranspiler]
        [HarmonyPatch("Notify_RoomShapeChanged")]
        public static IEnumerable<CodeInstruction> Notify_RoomShapeChanged_Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            List<CodeInstruction> codes = instructions.ToList();
            MethodInfo get_ForPrisoners = AccessTools.Method(typeof(Building_Bed), "get_ForPrisoners");
            MethodInfo forPrisoners = AccessTools.Method(typeof(RoomRoleWorker_PrisonCell_Patch), nameof(RoomRoleWorker_PrisonCell_Patch.ForPrisoners));
            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].Calls(get_ForPrisoners))
                {
                    codes[i].operand = forPrisoners;
                    break;
                }
            }
            return codes.AsEnumerable();
        }
    }
    // Make colony mech impregnation friendly
    [HarmonyPatch(typeof(PregnancyHelper))]
    public static class PregnancyHelper_Patch
    {
        [HarmonyPostfix]
        [HarmonyPatch("impregnate")]
        public static void impregnate_Postfix(SexProps props)
        {
            if (props.pawn.IsColonyMech && props.partner.IsOnahole())
            {
                foreach (Hediff_MechanoidPregnancy pregnancy in PregnancyHelper.GetPregnancies(props.partner).Where(x => x is Hediff_MechanoidPregnancy).Cast<Hediff_MechanoidPregnancy>())
                {
                    if (pregnancy.father == props.pawn && pregnancy.pawn == props.partner)
                    {
                        pregnancy.Hack();
                    }
                }
            }
        }
    }
    // Force onaholes to be fed
    [HarmonyPatch(typeof(WardenFeedUtility))]
    public static class WardenFeedUtility_Patch
    {
        [HarmonyPostfix]
        [HarmonyPatch("ShouldBeFed")]
        public static void ShouldBeFed_Postfix(Pawn p, ref bool __result)
        {
            if (p.IsOnahole())
            {
                __result = true;
            }
        }
    }
    // Prevent arrest float menu include onaholes outside of prison
    [HarmonyPatch(typeof(TargetingParameters))]
    public static class TargetingParameters_Patch
    {
        [HarmonyPostfix]
        [HarmonyPatch(nameof(TargetingParameters.ForArrest))]
        public static void ForArrest_Postfix(ref TargetingParameters __result, Pawn arrester)
        {
            Predicate<TargetInfo> validator = __result.validator;
            __result.validator = (TargetInfo target) => { return validator(target) && !(target.Thing as Pawn).IsOnahole(); };
        }
    }
    // Allow hauling in prison
    [HarmonyPatch(typeof(SocialProperness))]
    public static class SocialProperness_Patch
    {
        [HarmonyPrefix]
        [HarmonyPatch(nameof(SocialProperness.IsSociallyProper), new Type[] { typeof(Thing), typeof(Pawn), typeof(bool), typeof(bool) })]
        public static bool IsSociallyProper_Prefix(Thing t, ref bool __result)
        {
            if (t.holdingOwner.Owner is Building_OnaholeBed)
            {
                __result = true;
                return false;
            }
            return true;
        }
    }
    [HarmonyPatch]
    public static class Building_Bed_GetGizmos_Patch
    {
        private static MethodInfo isTrue = AccessTools.Method(typeof(Building_Bed_GetGizmos_Patch), nameof(IsOnaholeBed));
        private static MethodBase TargetMethod()
        {
            Type internalClass = AccessTools.Inner(typeof(Building_Bed), "<GetGizmos>d__54");
            return AccessTools.Method(internalClass, "MoveNext");
        }
        // Skips generating assignment gizmos for onahole beds
        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator il)
        {
            List<CodeInstruction> codes = instructions.ToList();
            PropertyInfo get_Faction = AccessTools.Property(typeof(Thing), "Faction");

            // Use ILGenerator to create labels
            Label skipLabel = il.DefineLabel();
            Label leaveLabel = il.DefineLabel();
            bool found = false;

            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].Calls(get_Faction.GetGetMethod()) && !found)
                {
                    found = true;

                    // Insert new instructions before the method call
                    List<CodeInstruction> newCodes = new List<CodeInstruction>
                        {
                            new CodeInstruction(OpCodes.Call, isTrue),
                            new CodeInstruction(OpCodes.Brfalse_S, skipLabel), // Branch to skipLabel if isTrue is false
                            new CodeInstruction(OpCodes.Ldc_I4_0),
                            new CodeInstruction(OpCodes.Stloc_0),
                            new CodeInstruction(OpCodes.Leave, leaveLabel), // Leave to leaveLabel
                            new CodeInstruction(OpCodes.Ldloc_2) { labels = new List<Label> { skipLabel } }, // This is where the skipLabel should be placed
                        };

                    codes.InsertRange(i, newCodes);
                    i += newCodes.Count; // Move the index past the inserted instructions
                }

                if (codes[i].opcode == OpCodes.Ldloc_0 && codes[i + 1].opcode == OpCodes.Ret && found)
                {
                    // Mark the leaveLabel at this instruction (Ret)
                    codes[i].labels.Add(leaveLabel);
                    break;
                }
            }
            return codes.AsEnumerable();
        }
        public static bool IsOnaholeBed(Building_Bed bed)
        {
            return bed is Building_OnaholeBed;
        }
    }
    [HarmonyPatch(typeof(PrisonBreakUtility))]
    public static class PrisonBreakUtility_Patch
    {
        [HarmonyPostfix]
        [HarmonyPatch(nameof(PrisonBreakUtility.CanParticipateInPrisonBreak))]
        public static void CanParticipateInPrisonBreak_Postfix(ref bool __result, Pawn pawn)
        {
            __result = __result && !pawn.IsOnahole();
        }
    }
    [HarmonyPatch(typeof(CompAssignableToPawn))]
    public static class CompAssignableToPawn_Patch
    {
        // Skips generating assignment gizmos for onahole beds
        [HarmonyPrefix]
        [HarmonyPatch("ShouldShowAssignmentGizmo")]
        public static bool ShouldShowAssignmentGizmo_Prefix(ref bool __result, CompAssignableToPawn __instance)
        {
            if (__instance.parent is Building_OnaholeBed)
            {
                __result = false;
                return false;
            }
            return true;
        }
    }
    // Prevent vanilla refuel
    [HarmonyPatch(typeof(WorkGiver_Refuel))]
    public static class WorkGiver_Refuel_Patch
    {
        [HarmonyPrefix]
        [HarmonyPatch(nameof(WorkGiver_Refuel.CanRefuelThing))]
        public static bool CanRefuelThing_Prefix(ref bool __result, Thing t)
        {
            if (t is Building_OnaholeBed)
            {
                __result = false;
                return false;
            }
            return true;
        }
    }
    /// <summary>
    /// Trigger event at end of map click, allow loading newly selected things
    /// </summary>
    [HarmonyPatch(typeof(Selector), "HandleMapClicks")]
    public static class HandleMapClicks_Patch
    {
        [HarmonyPrefix]
        public static void HandleMapClicks_Prefix(ref bool __state)
        {
            __state = Event.current.type == EventType.MouseUp;
        }
        [HarmonyPostfix]
        public static void HandleMapClicks_Postfix(ref bool __state)
        {
            if (__state)
            {
                EventUtility.TriggerPostMapClicks();
            }
        }
    }
    /// <summary>
    /// Act like containment platform
    /// </summary>
    [HarmonyPatch(typeof(HediffComp_DisappearsAndKills_Shambler), "get_Paused")]
    public static class HediffComp_DisappearsAndKills_Shambler_Patch
    {
        [HarmonyPostfix]
        public static void Postfix(ref bool __result, HediffComp_DisappearsAndKills_Shambler __instance)
        {
            if (!__result)
            {
                __result = __instance.Pawn.IsOnahole();
            }
        }
    }
    [HarmonyPatch(typeof(FoodUtility))]
    public static class FoodUtility_Patch
    {
        [HarmonyPrefix]
        [HarmonyPatch(nameof(FoodUtility.IsAcceptablePreyFor))]
        public static bool IsAcceptablePreyFor_Prefix(Pawn prey, ref bool __result)
        {
            if (prey.IsOnahole())
            {
                __result = false;
                return false;
            }
            return true;
        }
    }
}


code:
using Verse;
using RimWorld;
using System.Collections.Generic;
using rjw;
using Verse.Sound;
using RJW_Onahole.Utilities;
using UnityEngine;
using RJW_Onahole.Comps;
using RJW_Onahole.Data;
using System.Linq;
using rjw.Modules.Interactions.Objects.Parts;
using rjw.Modules.Interactions.Exposable;
using rjw.Modules.Interactions.Extensions;

namespace RJW_Onahole.Hediffs
{
    public class HediffCompProperties_Autofeeder : HediffCompProperties_OnaholeBase
    {
        public float amount;
        public HediffCompProperties_Autofeeder()
        {
            compClass = typeof(HediffComp_Autofeeder);
        }
    }
    public class HediffComp_Autofeeder : HediffComp_OnaholeBase
    {
        public HediffCompProperties_Autofeeder Props => (HediffCompProperties_Autofeeder)props;
        public override void DoEffects()
        {
            this.Feed();
        }
        private void Feed()
        {
            this.FeedFluid();
            this.FeedFood();
        }
        private void FeedFluid()
        {
            CompCumCollector cumCollector = Bed?.TryGetComp<CompCumCollector>();
            // Hunger or max feed amount per tick
            float nutritionWanted = Mathf.Min(Props.amount, parent.pawn.needs.food.MaxLevel - parent.pawn.needs.food.CurLevel);
            if (cumCollector == null) { return; }
            foreach (FluidSetting fluidSetting in cumCollector.fluidsToSettings.Values)
            {
                if (!fluidSetting.AllowFeed) { continue; }
                if (fluidSetting.SexFluid.consumable != null)
                {
                    // Feed thing
                    Thing thing = ThingMaker.MakeThing(fluidSetting.SexFluid.consumable);
                    if (FoodUtility.NutritionForEater(Pawn, thing) <= 0f) { thing.Destroy(DestroyMode.Vanish); continue; }// Check for infinite nutrition
                    float fluidAmount = cumCollector.GetFluidAmount(fluidSetting.SexFluid);
                    thing.stackCount = Mathf.Min(fluidSetting.ConvertFromFluid(ref fluidAmount), Mathf.CeilToInt(nutritionWanted / fluidSetting.NutritionPerUnit));
                    nutritionWanted -= thing.stackCount * fluidSetting.NutritionPerUnit;
                    if (thing.stackCount > 0)
                    {
                        Pawn.needs.food.CurLevel += thing.Ingested(Pawn, nutritionWanted);
                    }
                    else
                    {
                        thing.Destroy(); // Free memory
                    }
                    cumCollector.SetFluidAmount(fluidSetting.SexFluid, fluidAmount);
                }
                else
                {
                    // Use global nutrition per ml
                    float took = cumCollector.TryTakeFluid(nutritionWanted / fluidSetting.NutritionPerUnit, fluidSetting.SexFluid);
                    Pawn.needs.food.CurLevel += Mathf.Max(took * fluidSetting.NutritionPerUnit, 0f);
                    if (fluidSetting.SexFluid.ingestionDoers != null)
                    {
                        foreach (SexFluidIngestionDoer sexFluidIngestionDoer in fluidSetting.SexFluid.ingestionDoers)
                        {
                            try { sexFluidIngestionDoer.Ingested(Pawn, fluidSetting.SexFluid, took, null); } catch { }
                        }
                    }
                    nutritionWanted -= took * fluidSetting.NutritionPerUnit;
                }
                if (nutritionWanted <= 0) { break; }
            }
        }
        private void FeedFood()
        {
            CompFeederRefuelable compFeederRefuelable = Bed?.TryGetComp<CompFeederRefuelable>();
            if (compFeederRefuelable.FuelThingDef != null && compFeederRefuelable.Fuel > 0)
            {
                if (Pawn.needs.food.CurLevelPercentage < Pawn.needs.food.PercentageThreshHungry + 0.1f)
                {
                    float nutritionWanted = Pawn.needs.food.MaxLevel - Pawn.needs.food.CurLevel;
                    Thing thing = ThingMaker.MakeThing(compFeederRefuelable.FuelThingDef);
                    thing.stackCount = 1;
                    float thingNutrition = FoodUtility.NutritionForEater(Pawn, thing);
                    if (thingNutrition <= 0) { return; }
                    Pawn.needs.food.CurLevel += thing.Ingested(Pawn, nutritionWanted) * Mathf.Min(1f, compFeederRefuelable.Fuel);
                    compFeederRefuelable.ConsumeFuel(Mathf.Min(nutritionWanted, thingNutrition) / thingNutrition);
                }
            }
        }
    }
}


code:
using Verse;
using RimWorld;
using System.Collections.Generic;
using rjw;
using Verse.Sound;
using RJW_Onahole.Utilities;
using System.Reflection;
using RJW_Onahole.UI;
using System;
using RJW_Onahole.Comps;
using System.Linq;
using Verse.AI;
using RJW_Onahole.Jobs;
using UnityEngine;

namespace RJW_Onahole.Hediffs
{
    public class HediffCompProperties_Electrocute : HediffCompProperties_OnaholeBase
    {
        public List<IntRange> orgasmTick;
        public List<FloatRange> milkProduction; // fraction of 1 unit of milk
        public HediffCompProperties_Electrocute()
        {
            compClass = typeof(HediffComp_Electrocute);
        }
    }
    public class HediffComp_Electrocute : HediffComp_OnaholeBase
    {
        private static readonly StatDef lactatingEfficiencyFactor = DefDatabase<StatDef>.GetNamedSilentFail("EM_Lactating_Efficiency_Factor");
        private static HediffDef HediffDef_Broken => DefDatabase<HediffDef>.GetNamed("FeelingBroken");
        public HediffCompProperties_Electrocute Props => (HediffCompProperties_Electrocute)props;
        private static ImpactSoundTypeDef ImpactSound => DefDatabase<ImpactSoundTypeDef>.GetNamed("Electric");
        private static DamageInfo PseudoDinfo => new DamageInfo(DamageDefOf.Stun, 1f);
        public override string CompLabelInBracketsExtra => ((ElectrocuteModes)Enum.GetValues(typeof(ElectrocuteModes)).GetValue(parent.CurStageIndex + 1)).Label();
        public override void DoEffects()
        {
            if (parent.CurStageIndex == 0) { return; }
            Pawn pawn = this.parent.pawn;
            if (this.parent.Part == Genital_Helper.get_genitalsBPR(pawn))
            {
                ElectrocuteGenitals(pawn);
            }
            if (this.parent.Part == Genital_Helper.get_breastsBPR(pawn))
            {
                ElectrocuteBreasts(pawn);
            }
        }
        private static DamageInfo ElectrocuteDamage(BodyPartRecord part)
        {
            return new DamageInfo(DamageDefOf.Burn, 1f, 100f, -1f, null, part);
        }
        private static void ElectrocuteEffects(Pawn pawn)
        {
            if (xxx.is_masochist(pawn) || xxx.is_psychopath(pawn))
            {
                FleckMaker.ThrowMetaIcon(pawn.Position, pawn.Map, FleckDefOf.Heart);

                if (!pawn.health.hediffSet.HasHediff(HediffDef_Broken))
                {
                    pawn.health.AddHediff(HediffDef_Broken);
                }
                pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef_Broken).Severity += 0.002f;
            }
            else
            {
                FleckMaker.ThrowMetaIcon(pawn.Position, pawn.Map, xxx.mote_noheart);
                if (!pawn.health.hediffSet.HasHediff(HediffDef_Broken))
                {
                    pawn.health.AddHediff(HediffDef_Broken);
                }
                pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef_Broken).Severity += 0.005f;

            }
            ImpactSoundUtility.PlayImpactSound(pawn, ImpactSound, pawn.MapHeld);
            // TODO use sex voice
            LifeStageUtility.PlayNearestLifestageSound(pawn, (LifeStageAge lifeStage) => lifeStage.soundWounded, (GeneDef gene) => gene.soundWounded, (MutantDef mutantDef) => mutantDef.soundWounded, 1f);
            pawn.Drawer.Notify_DamageApplied(PseudoDinfo);
            EffecterDef damageEffecter = EffecterDefOf.DamageDiminished_Metal;
            if (pawn.health.woundedEffecter != null && pawn.health.woundedEffecter.def != damageEffecter)
            {
                pawn.health.woundedEffecter.Cleanup();
            }
            pawn.health.woundedEffecter = damageEffecter.Spawn();
            pawn.health.woundedEffecter.Trigger(pawn, PseudoDinfo.Instigator ?? pawn, -1);
            if (PseudoDinfo.Def.damageEffecter != null)
            {
                Effecter effecter = PseudoDinfo.Def.damageEffecter.Spawn();
                effecter.Trigger(pawn, pawn, -1);
                effecter.Cleanup();
            }
        }
        private void ElectrocuteGenitals(Pawn pawn)
        {
            if (!(pawn.jobs.curDriver is JobDriver_BeOnahole job)) { return; }
            ElectrocuteEffects(pawn);
            int addedOrgans = Props.orgasmTick[parent.CurStageIndex].RandomInRange;
            if (parent.CurStageIndex == 3)
            {
                pawn.TakeDamage(ElectrocuteDamage(Genital_Helper.get_genitalsBPR(pawn)));
                foreach (Hediff_Pregnant hediff in PregnancyHelper.GetPregnancies(pawn).Cast<Hediff_Pregnant>())
                {
                    try { hediff.Miscarry(); } catch { }
                }
            }
            if (xxx.is_masochist(pawn))
            {
                addedOrgans *= 3;
            }
            if (job.sex_ticks <= 0)
            {
                job.setup_ticks();
            }
            job.AddOrgasmProgress(addedOrgans);
        }
        private void ElectrocuteBreasts(Pawn pawn)
        {
            ElectrocuteEffects(pawn);
            if (parent.CurStageIndex == 3) { pawn.TakeDamage(ElectrocuteDamage(Genital_Helper.get_breastsBPR(pawn))); }

            float lactatingEfficiencyFactorValue = lactatingEfficiencyFactor?.Worker.GetValue(pawn) ?? 1f;
            float addedMilk = Props.milkProduction[parent.CurStageIndex].RandomInRange * lactatingEfficiencyFactorValue;

            if (pawn.HasMilkableComp(out ThingDef milkDef, out float milkAmount, out ThingComp milkable, out FieldInfo fullnessField, out bool isActive))
            {
                if (isActive)
                {
                    float fullness = fullnessField.GetValue(milkable) as float? ?? 0f;
                    float toAdd = Mathf.Min((1f - fullness) / milkAmount, addedMilk / milkAmount, pawn.needs.food.CurLevel);
                    pawn.needs.food.CurLevel -= toAdd;
                    fullnessField.SetValue(milkable, fullness + toAdd);
                    return;
                }
            }
            if (pawn.health.hediffSet.HasHediff(HediffDefOf.Lactating, true))
            {
                HediffComp_Lactating lactating = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.Lactating).TryGetComp<HediffComp_Lactating>();
                lactating.TryCharge(addedMilk * lactating.Props.fullChargeAmount / 2.5f);// Normalize to 1 full charge, full charge is 2.5 milk
                return;
            }
        }
    }
}


code:
using Verse;
using RimWorld;
using System.Collections.Generic;
using rjw;
using Verse.Sound;
using RJW_Onahole.Utilities;
using UnityEngine;
using System.Reflection;
using RJW_Onahole.UI;
using System;
using RJW_Onahole.Comps;
using rjw.Modules.Interactions.Extensions;
using System.Linq;

namespace RJW_Onahole.Hediffs
{
    public class HediffCompProperties_MilkingMachine : HediffCompProperties_OnaholeBase
    {
        public List<float> milkPerTick;
        public HediffCompProperties_MilkingMachine()
        {
            compClass = typeof(HediffComp_MilkingMachine);
        }
    }
    public class HediffComp_MilkingMachine : HediffComp_OnaholeBase
    {
        public HediffCompProperties_MilkingMachine Props => (HediffCompProperties_MilkingMachine)props;
        public override string CompLabelInBracketsExtra => ((MilkingModes)Enum.GetValues(typeof(MilkingModes)).GetValue(parent.CurStageIndex + 1)).Label();
        public override void DoEffects()
        {
            float pumpedMilk = Props.milkPerTick[this.parent.CurStageIndex];
            if (pumpedMilk == 0) { return; } // Optimization
            if (parent.pawn.HasMilkableComp(out ThingDef milkDef, out float milkAmount, out ThingComp milkable, out FieldInfo fullnessField, out _))
            {
                float fullness = fullnessField.GetValue(milkable) as float? ?? 0f;
                // Normalize to 1 unit of milk
                float reduction = Mathf.Min(pumpedMilk / milkAmount, fullness);
                fullness -= reduction;
                fullnessField.SetValue(milkable, fullness);
                PumpMilk(reduction * milkAmount, milkDef);
                // Keep severity at 1 if lactating
                MilkLactating(0);
            }
            else
            {
                MilkLactating(pumpedMilk);
            }
        }
        private void PumpMilk(float amount, ThingDef milkDef = null)
        {
            float fluidMultiplier = 1f;
            if (!Pawn.GetSexablePawnParts().Breasts.EnumerableNullOrEmpty())
            {
                fluidMultiplier = Pawn.GetSexablePawnParts().Breasts.MaxBy(b => b.Def.fluidMultiplier).Def.fluidMultiplier;
            }
            Pawn.OnaholeBed().GetComp<CompMilkingMachine>()?.PumpMilk(amount * fluidMultiplier, milkDef);
        }
        private void MilkLactating(float amount, ThingDef milkDef = null)
        {
            if (parent.pawn.health.hediffSet.HasHediff(HediffDefOf.Lactating))
            {
                HediffComp_Lactating lactating = parent.pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.Lactating).TryGetComp<HediffComp_Lactating>();
                if (lactating != null)
                {
                    if (amount > 0)
                    {
                        float reduction = amount * lactating.Props.fullChargeAmount / 2.5f;
                        PumpMilk(lactating.GreedyConsume(reduction) / lactating.Props.fullChargeAmount * 2.5f, milkDef);
                    }
                    // Maintain vanilla lactation severity
                    Hediff hediff = parent.pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.Lactating);
                    if (hediff.Severity < 1f)
                    {
                        hediff.Severity = Mathf.Min(0.9999f, hediff.Severity + (0.0001f * this.Props.interval));
                    }
                }
            }
        }
    }
}


code:
using Verse;
using RimWorld;
using System.Collections.Generic;
using rjw;
using Verse.Sound;
using RJW_Onahole.Utilities;
using RJW_Onahole.Comps;
using RJW_Onahole.Jobs;

namespace RJW_Onahole.Hediffs
{
    public class HediffCompProperties_SexMachines : HediffCompProperties_OnaholeBase
    {
        public IntRange orgasmTick;
        public HediffCompProperties_SexMachines()
        {
            compClass = typeof(HediffComp_SexMachines);
        }
    }
    public class HediffComp_SexMachines : HediffComp_OnaholeBase
    {
        public HediffCompProperties_SexMachines Props => (HediffCompProperties_SexMachines)props;
        public override void DoEffects()
        {
            if (!(this.parent.pawn.jobs.curDriver is JobDriver_BeOnahole job)) { return; }
            job.AddOrgasmProgress(this.Props.orgasmTick.RandomInRange);
        }
    }
}


code:
using Verse;
using RimWorld;
using System.Collections.Generic;
using rjw;
using Verse.Sound;
using RJW_Onahole.Utilities;
using UnityEngine;
using RJW_Onahole.Comps;
using RJW_Onahole.Data;
using System.Linq;

namespace RJW_Onahole.Hediffs
{
    public abstract class HediffCompProperties_OnaholeBase : HediffCompProperties
    {
        public int interval;
    }
    public abstract class HediffComp_OnaholeBase : HediffComp
    {
        public Building_OnaholeBed Bed => parent.pawn?.OnaholeBed();
        public bool IsPowerOn => Bed?.GetComp<CompModularPower>()?.PowerOn == true;
        public Matrix4x4 lastRenderMatrix = default;
        public float startTime = -1; // For animation
        public float startTick = -1; // For animation
        public override void CompPostPostAdd(DamageInfo? dinfo)
        {
            base.CompPostPostAdd(dinfo);
            this.startTime = Time.time;
            this.startTick = Find.TickManager.TicksGame;
        }
        public override void CompPostMake()
        {
            base.CompPostMake();
            this.startTime = Time.time;
            this.startTick = Find.TickManager.TicksGame;
        }
        public override void CompExposeData()
        {
            base.CompExposeData();
            if (Scribe.mode == LoadSaveMode.PostLoadInit)
            {
                startTime = Time.time;
                startTick = Find.TickManager.TicksGame;
            }
        }
        public override void CompPostTick(ref float severityAdjustment)
        {
            base.CompPostTick(ref severityAdjustment);
            if (parent.pawn.IsHashIntervalTick(((HediffCompProperties_OnaholeBase)props).interval))
            {
                if (!IsPowerOn) { return; }
                // Set sexprops if null
                if (this.parent.pawn.jobs.curDriver is JobDriver_SexBaseReciever job && job.Sexprops == null) { job.Sexprops = SexUtility.SelectSextype(this.parent.pawn, null, false, false); }
                // Apply effects
                DoEffects();
            }
        }
        public abstract void DoEffects();
    }
}


code:
using System.Collections.Generic;
using Verse;

namespace RJW_Onahole.Interfaces
{
    // Interface for components links to onahole hediffs
    public interface ICompAddsHediff
    {
        void Notify_PawnBind(Pawn pawn);
        void Notify_PawnUnbind(Pawn pawn);
        void Notify_PawnGraphicsChange();
        List<HediffDef> GetHediffDef();
        int TextureIndex { get; set;}
    }
}


code:
using System.Collections.Generic;
using Verse;

namespace RJW_Onahole.Interfaces
{
    // Interface for components links to onahole hediffs
    public interface IModularPower
    {
        float GetPowerConsumption();
        void Notify_PowerStateChanged(bool state);
    }
}


code:
using System;
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
using rjw;
using UnityEngine;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using RJW_Onahole.Utilities;
using RJW_Onahole.Comps;

namespace RJW_Onahole.Jobs
{
	public class JobDriver_BeOnahole : JobDriver_SexBaseRecieverRaped
	{
		private static readonly MethodInfo ApplyBedRelatedEffectsMethod = AccessTools.Method(typeof(Toils_LayDown), "ApplyBedRelatedEffects");
		private static readonly MethodInfo FinalizeLayingJobMethod = AccessTools.Method(typeof(Toils_LayDown), "FinalizeLayingJob");
		private bool lastSleepState = false;
		private int lastOrgasmProgress = 0;
		public bool IsBeingStimulated => Find.TickManager.TicksGame - lastStimulation < 266;
		private int lastStimulation = 0;
        public override Rot4 ForcedLayingRotation => Rot4.South;

        protected override IEnumerable<Toil> MakeNewToils()
		{
			DoSetupOnahole();
			yield return Toils_Bed.ClaimBedIfNonMedical(TargetIndex.B);
			pawn.Rotation = Rot4.South;
			Toil get_raped = new Toil
			{
				defaultCompleteMode = ToilCompleteMode.Never,
				handlingFacing = true,
				socialMode = RandomSocialMode.Off,

				initAction = () =>
				{
					this.neverendingsex = true;
					pawn.pather.StopDead();
					pawn.jobs.curDriver.asleep = false;
					pawn.mindState.lastBedDefSleptIn = Bed.def;
					PortraitsCache.SetDirty(pawn);
					if (pawn.mindState.applyBedThoughtsTick == 0)
					{
						pawn.mindState.applyBedThoughtsTick = Find.TickManager.TicksGame + Rand.Range(2500, 10000);
						pawn.mindState.applyBedThoughtsOnLeave = false;
					}
					if (pawn.ownership != null && pawn.CurrentBed() != pawn.ownership.OwnedBed)
					{
						ThoughtUtility.RemovePositiveBedroomThoughts(pawn);
					}
					this.lastOrgasmProgress = this.orgasmstick;
					pawn.GetComp<CompCanBeDormant>()?.ToSleep();
				}
			};
			get_raped.tickAction = delegate
			{
				if (pawn.IsHashIntervalTick(250))
				{
					Pawn actor2 = get_raped.actor;
					Job curJob = pawn.CurJob;
					if (orgasmstick != lastOrgasmProgress)
					{
						lastOrgasmProgress = orgasmstick;
						lastStimulation = Find.TickManager.TicksGame;
					}
					// Try fall asleep if no partners
					if (!pawn.jobs.curDriver.asleep)
					{
						if (this.parteners.Count == 0 && (RestUtility.CanFallAsleep(pawn) || curJob.forceSleep) && (pawn.ageTracker.CurLifeStage.canVoluntarilySleep || curJob.startInvoluntarySleep))
						{
							pawn.jobs.curDriver.asleep = true;
							curJob.startInvoluntarySleep = false;
						}
					}
					else if (RestUtility.ShouldWakeUp(pawn) && !curJob.forceSleep)
					{
						pawn.jobs.curDriver.asleep = false;
					}
				}
				if (pawn.jobs.curDriver.asleep)
				{
					ApplyBedRelatedEffectsMethod.Invoke(null, new object[] { pawn, pawn.CurrentBed(), pawn.jobs.curDriver.asleep, true, false });
				}
				if (pawn.jobs.curDriver.asleep != lastSleepState)
				{
					SignalSleepStateChanged();
				}
			};
			//get_raped.FailOnBedNoLongerUsable(TargetIndex.B);
			get_raped.AddEndCondition(() => { if ((Bed as Building_OnaholeBed).BoundPawn != pawn || !RestUtility.CanUseBedNow(Bed, pawn, false)) { return JobCondition.Errored; } else { return JobCondition.Ongoing; } });
			get_raped.AddFinishAction(() =>
			{
				FinalizeLayingJobMethod.Invoke(null, new object[] { pawn, Bed, false });
				Bed.GetComp<CompOnaholeBed>()?.Notify_PawnUnbind(pawn);
			});
			yield return get_raped;
		}
		public override bool CanBeginNowWhileLyingDown()
		{
			return true;
		}
        public override string GetReport()
        {
			if (this.Partner != null)
			{
				return base.GetReport() + $" ({this.Partner.LabelShort})";
			}
            return base.GetReport();
        }
        private void DoSetupOnahole()
		{
			pBed = this.TargetB.Thing as Building_Bed;
			pawn.jobs.posture = PawnPosture.LayingInBedFaceUp;
			this.Sexprops = SexUtility.SelectSextype(this.pawn, null, true, false);
		}
		public void AddPartner(Pawn partner)
		{
			parteners.AddDistinct(partner);
			this.Sexprops.partner = partner;
			this.PartnerPawn = partner;
		}
		public void RemovePartner(Pawn partner)
		{
			parteners.Remove(partner);
			if (this.PartnerPawn == partner)
			{
				this.PartnerPawn = null;
			}
			if (this.Sexprops.partner == partner)
			{
				this.Sexprops.partner = null;
			}
		}
		public void SignalSleepStateChanged()
		{
			lastSleepState = pawn.jobs.curDriver.asleep;
		}
		public void AddOrgasmProgress(int addedOrgasm)
		{
			this.orgasmstick += addedOrgasm;
			this.sex_ticks -= addedOrgasm;
			if (this.OrgasmProgress >= 1f)
			{
				this.MachineOrgasm();
			}
		}
		public void MachineOrgasm()
		{
			this.orgasms++;
			this.Sexprops.orgasms++;
			this.PlayOrgasmVoice();
			SexUtility.CumFilthGenerator(this.pawn);
			MachineSatisfy();
			this.SetupOrgasmTicks();
			this.ticks_left = this.duration;
		}
		protected override void SetupOrgasmTicks()
		{
			if (this.OrgasmProgress >= 1f)
			{
				pawn.ReduceWill(Partner ?? pawn, UnityEngine.Random.Range(0.1f, 0.3f));
				pawn.ReduceCertainty(UnityEngine.Random.Range(0.005f, 0.01f));
			}
			base.SetupOrgasmTicks();
		}
		public const float Satisfaction = 0.4f;
		public void MachineSatisfy()
		{
			float satisfaction = Satisfaction * Math.Max(xxx.get_sex_satisfaction(pawn), 0.1f);
			Need_Sex need_Sex = pawn?.needs?.TryGetNeed<Need_Sex>();
			if (need_Sex == null)
			{
				return;
			}
			need_Sex.CurLevel += satisfaction;
			Need_Joy need_Joy = pawn.needs.TryGetNeed<Need_Joy>();
			if (need_Joy == null)
			{
				return;
			}
			if (!(need_Sex.CurLevel >= need_Sex.thresh_ahegao()) || !(need_Joy.CurLevelPercentage > need_Sex.thresh_ahegao()))
			{
				if (!AfterSexUtility.BodyIsBroken(pawn))
				{
					return;
				}

				Hediff firstHediffOfDef = pawn.health.hediffSet.GetFirstHediffOfDef(xxx.feelingBroken);
				if (firstHediffOfDef == null || firstHediffOfDef.CurStageIndex < 3)
				{
					return;
				}
			}
			ThoughtDef named = DefDatabase<ThoughtDef>.GetNamed("RJW_Ahegao");
			pawn.needs.mood.thoughts.memories.TryGainMemory(named);
		}
	}
}


code:
using Verse;
using RimWorld;
using System.Collections.Generic;
using Verse.AI;
using System;
using RJW_Onahole.Comps;

namespace RJW_Onahole.Jobs
{
    public class JobDriver_BindOnahole : JobDriver_TakeToBed
    {
        protected Pawn Prisoner
        {
            get
            {
                return (Pawn)this.job.GetTarget(TargetIndex.A).Thing;
            }
        }

        private Building_OnaholeBed Bed
        {
            get
            {
                return (Building_OnaholeBed)this.job.GetTarget(TargetIndex.B).Thing;
            }
        }
        protected override IEnumerable<Toil> MakeNewToils()
        {
            this.FailOnDestroyedOrNull(TargetIndex.A);
            this.FailOnDestroyedOrNull(TargetIndex.B);
            this.FailOnAggroMentalStateAndHostile(TargetIndex.A);
            yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.ClosestTouch);
            yield return Toils_Haul.StartCarryThing(TargetIndex.A, false, false, false, true);
            yield return Toils_Goto.GotoThing(TargetIndex.B, PathEndMode.Touch).FailOnForbidden(TargetIndex.B);
            if (Prisoner.Dead)
            {
                yield break;
            }
            yield return Toils_General.WaitWith(TargetIndex.B, 60, true, true, false, TargetIndex.None);
            yield return Toils_Reserve.Release(TargetIndex.B);
            yield return Toils_Bed.TuckIntoBed(Bed, this.pawn, Prisoner, false);
            Toil toil = new Toil
            {
                initAction = delegate ()
                {
                    Bed.TryGetComp<CompOnaholeBed>()?.Notify_PawnBind(Prisoner);
                    if (RJW_Onahole_Settings.stripOnBind)
                    {
                        Prisoner.Strip();
                    }
                },
                defaultCompleteMode = ToilCompleteMode.Instant
            };
            yield return toil;
        }
    }
}


code:
using System;
using System.Collections.Generic;
using RimWorld;
using RJW_Onahole.Comps;
using RJW_Onahole.Utilities;
using Verse;
using Verse.AI;

namespace RJW_Onahole.Jobs
{
    public class JobDriver_RefillDrugInfuser : JobDriver_RefuelSpecific
    {
        protected override CompIngestibleRefuelable SpecificRefuelableComp()
        {
            return this.Refuelable.TryGetComp<CompInfuserRefuelable>();
        }
    }
}


code:
using System;
using System.Collections.Generic;
using RimWorld;
using RJW_Onahole.Comps;
using RJW_Onahole.Utilities;
using Verse;
using Verse.AI;

namespace RJW_Onahole.Jobs
{
    public class JobDriver_RefillFeederFood : JobDriver_RefuelSpecific
    {
        protected override CompIngestibleRefuelable SpecificRefuelableComp()
        {
            return this.Refuelable.TryGetComp<CompFeederRefuelable>();
        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using RimWorld;
using RJW_Onahole.Comps;
using RJW_Onahole.Utilities;
using RJW_Onahole.Works;
using Verse;
using Verse.AI;

namespace RJW_Onahole.Jobs
{
    public abstract class JobDriver_RefuelSpecific : JobDriver_Refuel
    {
        protected abstract CompIngestibleRefuelable SpecificRefuelableComp();
        protected override IEnumerable<Toil> MakeNewToils()
        {
            this.FailOnDespawnedNullOrForbidden(TargetIndex.A);
            AddEndCondition(() => (!SpecificRefuelableComp().IsFull) ? JobCondition.Ongoing : JobCondition.Succeeded);
            AddFailCondition(() => !job.playerForced && !SpecificRefuelableComp().ShouldAutoRefuelNowIgnoringFuelPct);
            AddFailCondition(() => !SpecificRefuelableComp().allowAutoRefill && !job.playerForced);
            yield return Toils_General.DoAtomic(delegate
            {
                job.count = SpecificRefuelableComp().GetFuelCountToFullyRefuel();
            });
            Toil reserveFuel = Toils_Reserve.Reserve(TargetIndex.B);
            yield return reserveFuel;
            yield return Toils_Goto.GotoThing(TargetIndex.B, PathEndMode.ClosestTouch).FailOnDespawnedNullOrForbidden(TargetIndex.B).FailOnSomeonePhysicallyInteracting(TargetIndex.B);
            yield return Toils_Haul.StartCarryThing(TargetIndex.B, putRemainderInQueue: false, subtractNumTakenFromJobCount: true).FailOnDestroyedNullOrForbidden(TargetIndex.B);
            yield return Toils_Haul.CheckForGetOpportunityDuplicate(reserveFuel, TargetIndex.B, TargetIndex.None, takeFromValidStorage: true);
            yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.Touch);
            yield return Toils_General.Wait(240).FailOnDestroyedNullOrForbidden(TargetIndex.B).FailOnDestroyedNullOrForbidden(TargetIndex.A)
                .FailOnCannotTouch(TargetIndex.A, PathEndMode.Touch)
                .WithProgressBarToilDelay(TargetIndex.A);
            yield return FinalizeSpecificRefueling(TargetIndex.A, TargetIndex.B, SpecificRefuelableComp());
        }
        public static Toil FinalizeSpecificRefueling(TargetIndex refuelableInd, TargetIndex fuelInd, CompRefuelable refuelableComp)
        {
            Toil toil = ToilMaker.MakeToil("FinalizeSpecificRefueling");
            toil.initAction = delegate
            {
                Job curJob = toil.actor.CurJob;
                Thing thing = curJob.GetTarget(refuelableInd).Thing;
                if (toil.actor.CurJob.placedThings.NullOrEmpty())
                {
                    refuelableComp.Refuel(new List<Thing> { curJob.GetTarget(fuelInd).Thing });
                }
                else
                {
                    refuelableComp.Refuel(toil.actor.CurJob.placedThings.Select((ThingCountClass p) => p.thing).ToList());
                }
            };
            toil.defaultCompleteMode = ToilCompleteMode.Instant;
            return toil;
        }
    }
}


code:
using Verse;
using RimWorld;
using System.Collections.Generic;
using Verse.AI;
using System;
using UnityEngine;
using RJW_Onahole.Comps;
using RJW_Onahole.Utilities;

namespace RJW_Onahole.Jobs
{
    public class JobDriver_UnbindOnahole : JobDriver
    {
        protected Thing Thing
        {
            get
            {
                return this.job.GetTarget(TargetIndex.A).Thing;
            }
        }
        protected Thing Target
        {
            get
            {
                return this.job.GetTarget(TargetIndex.B).Thing;
            }
        }

        public override bool TryMakePreToilReservations(bool errorOnFailed)
        {
            return this.pawn.Reserve(this.Target, this.job, 1, -1, null, errorOnFailed);
        }

        protected override IEnumerable<Toil> MakeNewToils()
        {
            this.FailOnDestroyedOrNull(TargetIndex.A);
            this.FailOnDestroyedOrNull(TargetIndex.B);
            this.FailOnDespawnedNullOrForbidden(TargetIndex.A);
            this.FailOnAggroMentalStateAndHostile(TargetIndex.B);
            yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.ClosestTouch).FailOnForbidden(TargetIndex.A);
            Building_OnaholeBed bondageBed = (Building_OnaholeBed)this.Thing;
            Pawn prisoner = (Pawn)this.Target;
            if (prisoner.Dead)
            {
                yield break;
            }
            yield return Toils_General.WaitWith(TargetIndex.A, 60, true, true, false, TargetIndex.None);
            yield return Toils_Reserve.Release(TargetIndex.B);
            yield return new Toil
            {
                initAction = delegate ()
                {
                    bondageBed.GetComp<CompOnaholeBed>().Notify_PawnUnbind(prisoner);
                    prisoner.jobs.EndCurrentJob(JobCondition.Errored, true, true);
                    prisoner.ownership.UnclaimBed();
                },
                defaultCompleteMode = ToilCompleteMode.Instant
            };
            yield break;
        }

    }
}


code:
using System;
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
using rjw;
using System.Diagnostics;
using RJW_Onahole.Utilities;
using Verse.AI.Group;

namespace RJW_Onahole.Jobs
{
    public class JobDriver_UseOnahole : JobDriver_Rape
    {
        private int price = 0;
        public bool finished = false;
        private Building_OnaholeBed bed;
        private bool isDamageTaken = false;
        public override bool TryMakePreToilReservations(bool errorOnFailed)
        {
            Pawn targetPawn = Target as Pawn;
            if (!targetPawn.IsFreeOnahole(this.pawn)) { return false; }
            bool canReserve = pawn.Reserve(Target, job, 1, -1, null, errorOnFailed, true);
            if (canReserve && targetPawn.jobs.curDriver is JobDriver_BeOnahole driver) { driver.AddPartner(pawn); }
            else
            {
                canReserve = false;
                pawn.ClearReservationsForJob(job);
            }
            return canReserve;
        }
        public override void Notify_DamageTaken(DamageInfo dinfo)
        {
            base.Notify_DamageTaken(dinfo);
            this.isDamageTaken = true;
        }
        protected override IEnumerable<Toil> MakeNewToils()
        {
            if (RJWSettings.DebugRape) ModLog.Message("" + this.GetType().ToString() + "::MakeNewToils() called");
            setup_ticks();
            this.bed = TargetB.Thing as Building_OnaholeBed;
            this.neverendingsex = false;
            this.price = bed.Price;
            this.FailOnDespawnedNullOrForbidden(iTarget);
            this.FailOn(() => !pawn.CurJob.playerForced && !bed.Allows(pawn, out string reason));
            this.FailOn(() => !pawn.CanReserve(Partner, 1, -1)); // Fail if someone else reserves the prisoner before the pawn arrives
            this.FailOn(() => !Partner.IsFreeOnahole(this.pawn));
            this.FailOn(() => pawn.Drafted);
            this.FailOn(() => !Partner.IsOnahole());
            this.FailOn(() => pawn.InMentalState); // TODO add exception for random rape
            this.FailOn(() => isDamageTaken);

            this.AddFinishAction((condition) => { OnaholeFinishAction(); });
            yield return Toils_Goto.GotoThing(iTarget, PathEndMode.OnCell).FailOnForbidden(TargetIndex.B);

            Toil StartPartnerJob = new Toil
            {
                defaultCompleteMode = ToilCompleteMode.Instant,
                socialMode = RandomSocialMode.Off,
                initAction = delegate
                {
                    // Wake Onahole up
                    if (Partner.jobs.curDriver is JobDriver_BeOnahole driver) { driver.AddPartner(pawn); driver.asleep = false; return; }
                    ;
                }
            };
            yield return StartPartnerJob;

            Toil SexToil = new Toil
            {
                defaultCompleteMode = ToilCompleteMode.Never,
                defaultDuration = duration,
                handlingFacing = true,
                initAction = delegate
                {
                    if (RJWSettings.rape_stripping && (Partner.IsColonist || pawn.IsColonist))
                        Partner.Strip();
                    if (Partner.jobs.curDriver is JobDriver_BeOnahole driver) { driver.AddPartner(pawn); }
                    Start();
                },
                tickAction = delegate
                {
                    if (pawn.IsHashIntervalTick(ticks_between_hearts))
                    {
                        ThrowMetaIconF(pawn.Position, pawn.Map, FleckDefOf.Heart);
                        // Partner Tick
                        if (xxx.is_masochist(Partner) || xxx.is_psychopath(Partner))
                        {
                            ThrowMetaIconF(Partner.Position, pawn.Map, FleckDefOf.Heart);
                        }
                        else
                        {
                            ThrowMetaIconF(Partner.Position, pawn.Map, xxx.mote_noheart);
                        }
                    }
                    SexUtility.reduce_rest(Partner, 1);
                    SexUtility.reduce_rest(pawn, 2);
                    try { SexTick(pawn, Partner); } catch (NullReferenceException) { } // Avoid null reference exception when the bed has been removed before tick completes
                    if (ticks_left <= 0)
                        ReadyForNextToil();
                }
            };
            this.Sexprops = this.Sexprops ?? this.pawn.GetRJWPawnData().SexProps ?? SexUtility.SelectSextype(pawn, Partner, true, false);
            if (Sexprops.dictionaryKey.defName.ContainsCI("reverse")) { Sexprops.isRevese = true; }
            this.neverendingsex = false;
            yield return SexToil;
            "{0} is using {1}, Sextype: {2}, is Reverse: {3}, InteractionDef: {4}".LogWarning(pawn, Partner, Sexprops.sexType, Sexprops.isRevese, Sexprops.dictionaryKey.defName);
        }
        private void OnaholeFinishAction()
        {
            // Process sex
            try
            {
                if (Sexprops != null)
                {
                    SexUtility.ProcessSex(Sexprops);
                }
            }
            finally
            {
                // Dress up pawn
                if (xxx.is_human(pawn))
                {
                    CompRJW compRJW = pawn.GetCompRJW();
                    if (compRJW != null)
                    {
                        compRJW.drawNude = false;
                        pawn.Drawer.renderer.SetAllGraphicsDirty();
                    }
                }
                if (finished)
                {
                    // Pay for the service
                    if (pawn.IsGuest())
                    {
                        pawn.PaySilver(price, Partner.OnaholeBed());
                        pawn.PayFervor(this.job.count);
                    }
                    // If the pawn is an enemy, they will quit raiding
                    if (pawn.IsEnemy() && pawn.Faction != null)
                    {
                        if (pawn.GetLord() is Lord lord)
                        {
                            lord.Notify_PawnLost(pawn, PawnLostCondition.ForcedByPlayerAction);
                            pawn.mindState.duty = new PawnDuty(DutyDefOf.ExitMapBest);
                            Messages.Message(Tr.QuitRaiding.ParseString(pawn.Faction.Name, pawn.LabelShort, Partner.LabelShort), pawn, MessageTypeDefOf.PositiveEvent);
                        }
                    }
                    // Reduce Onahole's resistance
                    if (Partner?.IsPrisonerOfColony == true)
                    {
                        Partner.ReduceResistance(pawn, UnityEngine.Random.Range(0.1f, 0.3f));
                    }
                    // Reduce pawn's resistance
                    if (pawn.IsPrisonerOfColony)
                    {
                        pawn.ReduceResistance(Partner, UnityEngine.Random.Range(0.1f, 0.3f));
                    }
                }
                if (Partner?.jobs.curDriver is JobDriver_BeOnahole driver)
                {
                    driver.RemovePartner(pawn);
                    if (xxx.is_human(Partner) && driver.parteners.Count == 0)
                    {
                        CompRJW comp = Partner.GetCompRJW();
                        if (comp != null)
                        {
                            comp.drawNude = false;
                            Partner.Drawer.renderer.SetAllGraphicsDirty();
                        }
                    }
                    GlobalTextureAtlasManager.TryMarkPawnFrameSetDirty(pawn);
                }
            }

        }
    }
}


code:
using RimWorld;
using Verse;
using Verse.AI;
using System;
using System.Collections.Generic;
using System.Linq;
using rjw;
using RJW_Onahole.Utilities;
using rjw.Modules.Shared.Helpers;
using UnityEngine;

namespace RJW_Onahole.Jobs
{
    public class JobGiver_UseOnahole : ThinkNode_JobGiver
    {

        protected override Job TryGiveJob(Pawn pawn)
        {
            List<Tuple<Building_OnaholeBed, float>> targets = new List<Tuple<Building_OnaholeBed, float>>();
            foreach (Building_OnaholeBed bed in pawn.Map.listerThings.ThingsInGroup(ThingRequestGroup.Bed).OfType<Building_OnaholeBed>())
            {
                if (bed.Pawn == null) { continue; }
                if (CanUseOnaHole(pawn, bed, out string reason, out bool shouldSearchNext, out float score))
                {
                    targets.Add(new Tuple<Building_OnaholeBed, float>(bed, pawn.OnaholeScore(bed.Pawn, score)));
                }
                else
                {
                    reason.Log(pawn.LabelShort, bed.Pawn.LabelShort);
                    if (!shouldSearchNext) { return null; }
                }

            }
            Building_OnaholeBed selectedBed = SelectDesiredOnahole(pawn, targets, out int favor);
            if (selectedBed != null)
            {
                if (pawn.IsGuest() && !pawn.ReserveSilver(selectedBed.Price)) { return null; }
                Job job = JobMaker.MakeJob(OnaholeDefOf.UseOnahole, selectedBed.Pawn, selectedBed);
                job.count = favor;
                return job;
            }
            return null;
        }
        private static bool CanUseOnaHole(Pawn pawn, Building_OnaholeBed bed, out string reason, out bool shouldSearchNext, out float score)
        {
            shouldSearchNext = true;
            score = 0;
            if (!bed.Allows(pawn, out reason)) { return false; }
            return WantSexWith(pawn, bed.Pawn, out reason, out shouldSearchNext, out score);
        }
        /// <summary>
        /// Check if pawn want to fuck target
        /// </summary>
        /// <param name="pawn"></param>
        /// <param name="target"></param>
        /// <returns></returns>
        private static bool WantSexWith(Pawn pawn, Pawn target, out string reason, out bool shouldSearchNext, out float score)
        {
            shouldSearchNext = true;
            score = 0;
            if (pawn.RaceHasSexNeed() && !xxx.is_hornyorfrustrated(pawn)) { reason = "{0} is not horny"; shouldSearchNext = false; return false; }
            if (!pawn.RaceHasSexNeed() && (pawn.RaceProps.Insect || pawn.RaceProps.IsMechanoid))
            {
                if (!((Genital_Helper.has_penis_fertile(pawn) || Genital_Helper.has_ovipositorF(pawn) || Genital_Helper.has_ovipositorM(pawn)) || pawn.RaceProps.IsMechanoid)) { reason = "{0} cannot impregnate while being insect/mech"; shouldSearchNext = false; return false; }
                if (!(Genital_Helper.has_vagina(target) && !Genital_Helper.vagina_blocked(target) && !target.IsPregnant())) { reason = " {1} cannot be pregnant or already pregnant, cannot make {0} pregnant"; return false; }
            }
            if (!xxx.can_fuck(pawn) && pawn.IsAnimal()) { reason = "{0} is animal and cannot fuck"; shouldSearchNext = false; return false; }
            if (!xxx.can_be_fucked(target)) { reason = "{1} cannot be fucked"; return false; }
            if (!pawn.CanReserve(target, 1, -1, null, false) || !target.IsFreeOnahole()) { reason = "{0} cannot reserve {1}"; return false; }
            if (target.IsForbidden(pawn)) { reason = "{1} is in forbidden area of {0}"; return false; }
            if (!RJWSettings.WildMode)
            {
                if (pawn.IsDesignatedComfort() || pawn.IsDesignatedBreeding()) { reason = "{0} cannot initiate sex"; return false; }
                if (!xxx.is_healthy_enough(pawn)) { reason = "{0} is not healthy"; shouldSearchNext = false; return false; }
                if (pawn.RaceProps.Humanlike && xxx.is_asexual(pawn)) { reason = "{0} is asexual"; shouldSearchNext = false; return false; }
            }
            if (!pawn.CanReach(target, PathEndMode.OnCell, Danger.Some)) { reason = "{0} cannot reach {1}"; return false; }
            score = pawn.WouldFuck(target);
            if (score < 0.1f && !pawn.RaceProps.IsMechanoid)
            {
                reason = "{0} is not interested in fucking {1}, score:" + score.ToString(); return false;
            }
            reason = "";
            return true;
        }
        private const float AcceptableRange = 0.2f;
        private static Building_OnaholeBed SelectDesiredOnahole(Pawn pawn, List<Tuple<Building_OnaholeBed, float>> targets, out int favor)
        {
            favor = 0;
            if (targets.Count == 0) { return null; }
            if (pawn.IsGuest())
            {
                List<Tuple<Building_OnaholeBed, float>> newList = new List<Tuple<Building_OnaholeBed, float>>();
                foreach (Tuple<Building_OnaholeBed, float> target in targets)
                {
                    if (pawn.CanAfford(target.Item1.Price) && pawn.WouldPayForOnahole(target.Item1.Pawn, out int surplus, target.Item2))
                    {
                        newList.Add(new Tuple<Building_OnaholeBed, float>(target.Item1, (float)surplus / (float)target.Item1.Price));
                    }
                }
                targets = newList;
            }
            if (targets.Count == 0) { return null; }
            List<Tuple<Building_OnaholeBed, float>> sortedList = targets.OrderByDescending(tuple => tuple.Item2).ToList();
            float minScore = sortedList[0].Item2 - AcceptableRange;
            int i = -1;
            foreach (Tuple<Building_OnaholeBed, float> tuple in sortedList)
            {
                if (tuple.Item2 <= minScore)
                {
                    break;
                }
                i++;
            }
            int selectedIndex = new IntRange(0, i).RandomInRange;
            if (pawn.IsGuest())
            {
                favor = (int)(sortedList[selectedIndex].Item2 * sortedList[selectedIndex].Item1.Price);
            }
            return sortedList[selectedIndex].Item1;
        }
    }
}


code:
using RimWorld;
using RJW_Onahole.Data;
using UnityEngine;
using Verse;

namespace RJW_Onahole
{
    [RimWorld.DefOf]
    [StaticConstructorOnStartup]
    public static class OnaholeDefOf
    {

        public static HediffDef OnaholeBond;
        public static HediffDef OnaholeGenitalElectrodes;
        public static HediffDef OnaholeBreastElectrodes;

        public static HediffDef OnaholeMilkingMachine;
        public static HediffDef OnaholeVaginalSexMachine;
        public static HediffDef OnaholeAnalSexMachine;
        public static HediffDef OnaholeAutoFeeder;
        public static JobDef UseOnahole;
        public static JobDef BeOnahole;
        public static JobDef UnbindOnahole;
        public static JobDef BindOnahole;
        public static JobDef RefillFeederFood;
        public static JobDef RefillDrugInfuser;
        public static ShaderTypeDef SimpleAlpha;
        public static ShaderTypeDef InvertMaskAlpha;
        public static ShaderTypeDef InheritMaskAlpha;
        public static ShaderTypeDef InstancedAnimation;
        public static ShaderTypeDef BezierCurveShader;
        public static InstancedAnimationDef MilkingMachine;
        public static InstancedAnimationDef VaginalSexMachine;
        public static InstancedAnimationDef AnalSexMachine;
        public static InstancedAnimationDef VaginaCrossSection;
        public static InstancedAnimationDef AnusCrossSection;
        public static InstancedAnimationDef FistingCrossSection;
        public static InstancedAnimationDef PistonCrossSection;
        public static InstancedAnimationDef PenisCrossSection;
        public static InstancedAnimationDef MechPenisCrossSection;
        public static InstancedAnimationDef InsectPenisCrossSection;
    }
}


code:
using System;
using RimWorld;
using UnityEngine;
using UnityEngine.PlayerLoop;
using HarmonyLib;
using Verse;
using RJW_Onahole.Utilities;
using RJW_Onahole.Data;

namespace RJW_Onahole
{
    [StaticConstructorOnStartup]
    public static class RJW_Onahole
    {
        public static Harmony Harmony = new Harmony(nameof(RJW_Onahole));
        public static RJW_Onahole_Settings Settings;
        static RJW_Onahole()
        {
            RJW_Onahole.Harmony.PatchAll();
            Settings = LoadedModManager.GetMod<RJW_Onahole_Mod>().GetSettings<RJW_Onahole_Settings>();
            CustomShaderUtility.LoadCustomShaders();
            //Trigger long event event
            LongEventHandler.QueueLongEvent(() => { EventUtility.TriggerPostLoadLong(); }, "RJW_Onahole_LongEvent", false, null);
            foreach (InstancedAnimationDef instancedAnimation in DefDatabase<InstancedAnimationDef>.AllDefs)
            {
                // Initialize the texture
                Texture2D tex = instancedAnimation.Tex;
            }
            DefDatabase<RecordDef>.GetNamedSilentFail("BeOnaholeRecord_Time").label = OnaholeDefOf.BeOnahole.reportString.Trim();
            DefDatabase<RecordDef>.GetNamedSilentFail("BeOnaholeRecord_Time").description = OnaholeDefOf.BeOnahole.reportString.Trim() + OTr.Time.InBrackets();

            DefDatabase<RecordDef>.GetNamedSilentFail("UseOnaholeRecord_Time").label = OnaholeDefOf.UseOnahole.reportString.Replace("TargetA", "").Trim();
            DefDatabase<RecordDef>.GetNamedSilentFail("UseOnaholeRecord_Time").description = OnaholeDefOf.UseOnahole.reportString.Replace("TargetA", "").Trim() + OTr.Time.InBrackets();
        }
    }
}


code:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <AssemblyVersion>2.0.6</AssemblyVersion>
    <TargetFramework>net48</TargetFramework>
    <LangVersion>10.0</LangVersion>
  </PropertyGroup>

  <!-- Release build -->
  <PropertyGroup>
    <DebugType>none</DebugType>
    <Optimize>true</Optimize>
    <OutDir>../../Assemblies/</OutDir>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>3</WarningLevel>
    <Prefer32Bit>false</Prefer32Bit>
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="0Harmony">
      <HintPath>../../Libs/0Harmony.dll</HintPath>
      <Private>False</Private>
    </Reference>

    <Reference Include="RJW">
      <HintPath>../../Libs/RJW.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <!-- The main game code (RimWorld and Verse) -->
     <Reference Include="mscorlib">
      <HintPath>../../RimWorld_Data/Managed/mscorlib.dll</HintPath>
      <Private>False</Private>
     </Reference>
    <Reference Include="Assembly-CSharp">
      <HintPath>../../RimWorld_Data/Managed/Assembly-CSharp.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine">
      <HintPath>../../RimWorld_Data/Managed/UnityEngine.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.CoreModule">
      <HintPath>../../RimWorld_Data/Managed/UnityEngine.CoreModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.TextRenderingModule">
      <HintPath>../../RimWorld_Data/Managed/UnityEngine.TextRenderingModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.IMGUIModule">
      <HintPath>../../RimWorld_Data/Managed/UnityEngine.IMGUIModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.AssetBundleModule">
      <HintPath>../../RimWorld_Data/Managed/UnityEngine.AssetBundleModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>
</Project>


code:
using System;
using UnityEngine;
using Verse;
using Verse.AI;
using RimWorld;
using HarmonyLib;
using RJW_Onahole.Utilities;

namespace RJW_Onahole
{
	public class RJW_Onahole_Mod : Mod
	{
		public static RJW_Onahole_Settings Settings;
		public RJW_Onahole_Mod(ModContentPack content)
			: base(content)
		{
			Settings = base.GetSettings<RJW_Onahole_Settings>();
			EventUtility.TriggerPostLoad();
		}

		public override void DoSettingsWindowContents(Rect inRect)
		{
			Settings.DoWindowContents(inRect);
		}
		public override void WriteSettings()
		{
			RJW_Onahole.Settings.UpdateSettings();
			base.WriteSettings();
		}

		public override string SettingsCategory()
		{
			return Tr.RJW_Onahole;
		}

	}
}