code:
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using RJW_Onahole.Interfaces;
using RJW_Onahole.UI;
using RJW_Onahole.Utilities;
using UnityEngine;
using Verse;
using rjw;
using DubsBadHygiene;
namespace RJW_Onahole.Comps
{
    public class CompShower : CompConditionalBuildingOverlay
    {
        public override ResearchProjectDef RequiredResearch => DefDatabase<ResearchProjectDef>.GetNamed("Plumbing", true);
        private CompPipe pipe;
        public const float WaterPerRareTick = 0.065f * 250f;
        public const float HygienePerRareTick = 0.001f * 250f;
        public bool enabled;
        private bool coldShower;
        private Pawn showeringPawn;
        private ContaminationLevel waterQuality;

        public override void Initialize(CompProperties props)
        {
            base.Initialize(props);
            Props.graphicDatas[1].drawOffset.y = AltitudeLayer.ItemImportant.AltitudeFor() - 0.1f - Props.altitudeLayer.AltitudeFor();
        }
        public override void PostSpawnSetup(bool respawningAfterLoad)
        {
            base.PostSpawnSetup(respawningAfterLoad);
            pipe = this.parent.GetComp<CompPipe>();
        }
        public override void CompTickRare()
        {
            base.CompTickRare();
            if (this.enabled && this.pipe != null && this.ModularPower.PowerOn)
            {
                if (Bed.Pawn?.needs?.TryGetNeed<Need_Hygiene>() is Need_Hygiene need_Hygiene)
                {
                    if (this.showeringPawn == null)
                    {
                        if (need_Hygiene.CurLevel > 0.69f) { return; } // prevent spamming
                        this.StartShower(Bed.Pawn, need_Hygiene);
                        return;
                    }
                    else
                    {
                        if (!TryShower(need_Hygiene))
                        {
                            // No water
                            this.StopShower();
                        }
                        if (need_Hygiene.CurLevel >= 0.99f)
                        {
                            // Finished
                            this.StopShower();
                        }
                        return;
                    }
                }
            }
            this.StopShower();
        }
        private void StartShower(Pawn pawn, Need_Hygiene need_Hygiene)
        {
            // Start showering
            if (!TryShower(need_Hygiene)) { return; }
            this.showeringPawn = pawn;
            PrivacyUtil.BathingPrivacyLOS(pawn, 6f);
            pawn.health.hediffSet.AddDirect(HediffMaker.MakeHediff(DubDef.Washing, pawn, null), null, null);
            pawn.needs.mood?.thoughts?.memories?.RemoveMemoriesOfDef(DubDef.SoakingWet);
        }
        private bool TryShower(Need_Hygiene need_Hygiene)
        {
            if (this.pipe.pipeNet.PullHotWater(WaterPerRareTick))
            {
                need_Hygiene.CurLevel = Mathf.Min(1f, need_Hygiene.CurLevel + HygienePerRareTick);
                this.coldShower = false;
                return true;
            }
            else if (this.pipe.pipeNet.PullWater(WaterPerRareTick, out this.waterQuality))
            {
                need_Hygiene.CurLevel = Mathf.Min(1f, need_Hygiene.CurLevel + HygienePerRareTick / 2);
                this.coldShower = true;
                return true;
            }
            return false;
        }
        private void StopShower()
        {
            if (showeringPawn == null) { return; }
            if (showeringPawn.health.hediffSet.HasHediff(DubDef.Washing, false))
            {
                showeringPawn.health.RemoveHediff(showeringPawn.health.hediffSet.GetFirstHediffOfDef(DubDef.Washing, false));
            }
            PrivacyUtil.BathingPrivacyLOS(showeringPawn, 6f);
            SanitationUtil.WaterTempCheck(showeringPawn, this.coldShower, false);
            showeringPawn.CleanContamination();
            SanitationUtil.ContaminationCheckWater(showeringPawn, this.waterQuality);
            showeringPawn.filth.CarriedFilthListForReading.Clear();
            showeringPawn = null;
        }
        private static readonly FleckDef showerFleck = DefDatabase<FleckDef>.GetNamed("Onahole_ShowerVapor");
        private const int MoteInterval = 10;
        private int lastMoteTick = 0;

        public override void PostDraw()
        {
            base.PostDraw();
            if (this.enabled && RJW_Onahole_Settings.showExtraGraphics)
            {
                base.DrawGraphic(0);
                base.DrawGraphic(1);
                if (this.showeringPawn != null)
                {
                    DrawAnimatedTextureGroup("Shower");
                    if (Find.TickManager.TicksGame - lastMoteTick > MoteInterval)
                    {
                        lastMoteTick = Find.TickManager.TicksGame;
                        FleckCreationData dataStatic = FleckMaker.GetDataStatic(new Vector3(this.parent.DrawPos.x + Rand.Range(-0.3f, 0.3f), AltitudeLayer.Gas.AltitudeFor(), this.parent.DrawPos.z + Rand.Range(0.2f, 0.7f)), this.parent.Map, showerFleck, Rand.Range(1f, 2f));
                        dataStatic.velocityAngle = 0;
                        dataStatic.velocitySpeed = 0.2f;
                        this.parent.Map.flecks.CreateFleck(dataStatic);
                    }
                }
            }
        }
        public override void PostExposeData()
        {
            base.PostExposeData();
            Scribe_Values.Look(ref enabled, "showerEnabled", false);
            Scribe_Values.Look(ref coldShower, "coldShower", false);
            Scribe_Values.Look(ref waterQuality, "waterQuality", ContaminationLevel.Treated);
            Scribe_References.Look(ref showeringPawn, "showeringPawn");
        }
        protected override IEnumerable<Gizmo> GetGizmos()
        {
            yield return ToggleShower();
        }
        public override void PostDeSpawn(Map map)
        {
            base.PostDeSpawn(map);
            this.StopShower();
        }
        private Command_Toggle ToggleShower()
        {
            return new Command_Toggle
            {
                defaultLabel = Tr.Shower,
                defaultDesc = Tr.Shower_Desc,
                isActive = () => this.enabled,
                icon = CacheUtility.Tex.IconShower,
                toggleAction = delegate
                {
                    this.enabled = !this.enabled;
                }
            };
        }
    }
}


code:
using System;
using RimWorld;
using UnityEngine;
using UnityEngine.PlayerLoop;
using HarmonyLib;
using Verse;
using System.Linq;
using System.Collections.Generic;
using RJW_Onahole.Comps;
using Verse.AI;
using RJW_Onahole.Utilities;
using RJW_Onahole.Jobs;
using DubsBadHygiene;
using System.Reflection;
using System.Reflection.Emit;
using RJW_Onahole.Hediffs;

namespace RJW_Onahole
{
    [StaticConstructorOnStartup]
    public static class DubsBadHygiene
    {
        public static Harmony Harmony = new Harmony(nameof(RJW_Onahole) + nameof(DubsBadHygiene));
        public static RJW_Onahole_Settings Settings;
        static DubsBadHygiene()
        {
            Harmony.PatchAll();
        }
    }
    /// <summary>
    /// Store bed pan in onahole bed if possible.
    /// </summary>
    [HarmonyPatch]
    public static class Need_Bladder_Patch
    {
        private static readonly MethodInfo TryDropAndSetForbidden = AccessTools.Method(typeof(GenThing), "TryDropAndSetForbidden");
        static MethodBase TargetMethod()
        {
            return AccessTools.Method(typeof(Need_Bladder), nameof(Need_Bladder.NeedInterval));
        }
        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
        {
            List<CodeInstruction> codes = instructions.ToList();
            Label label = generator.DefineLabel();
            bool found = false;
            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].Calls(TryDropAndSetForbidden))
                {
                    codes[i].operand = AccessTools.Method(typeof(Need_Bladder_Patch), nameof(TryStoreOrDropAndSetForbidden));
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                "Failed to patch Need_Bladder.NeedInterval. No call to GenThing.TryDropAndSetForbidden found.".LogError();
            }
            return codes.AsEnumerable();
        }
        private const float WaterPerFlush = 14f;

        private static bool TryStoreOrDropAndSetForbidden(Thing th, IntVec3 pos, Map map, ThingPlaceMode mode, out Thing resultingThing, bool forbidden)
        {
            if (map.OnaholeBedWithin8Cells(pos) is Building_OnaholeBed bed)
            {
                // Try flush into sewage pipe
                if (bed.GetComp<CompPipe>() is CompPipe pipe && pipe.pipeNet.WaterStorage > WaterPerFlush)
                {
                    if (pipe.pipeNet.PushSewage(WaterPerFlush * ModOption.FlushSize.Val))
                    {
                        resultingThing = null;
                        pipe.pipeNet.PullWater(WaterPerFlush, out _);
                        return true;
                    }
                }
                // Store in bed
                if (bed.TryStoreInBed(th))
                {
                    resultingThing = th;
                    return true;
                }
            }
            return GenThing.TryDropAndSetForbidden(th, pos, map, mode, out resultingThing, forbidden);
        }
    }
    [HarmonyPatch(typeof(HediffComp_Autofeeder))]
    public static class HediffComp_Autofeeder_Patch
    {
        [HarmonyPrefix]
        [HarmonyPatch("Feed")]
        public static void Feed_Prefix(HediffComp_Autofeeder __instance)
        {
            if (!Settings.ThirstNeed || !(__instance.Pawn.needs.TryGetNeed<Need_Thirst>() is Need_Thirst need_Thirst)) { return; }
            CompPipe pipe = __instance.Pawn.OnaholeBed()?.GetComp<CompPipe>();
            if (pipe == null) { return; }
            float desired = Mathf.Min((need_Thirst.MaxLevel - need_Thirst.CurLevel) / 0.006f * 0.04f, 0.04f * __instance.Props.interval);
            if (pipe.pipeNet.PullWater(desired, out ContaminationLevel contamination))
            {
                // Drink water
                need_Thirst.Drink(0.006f * __instance.Props.interval);
                SanitationUtil.ContaminationCheckDrinking(__instance.Pawn, contamination);
            }

        }
    }
    // [HarmonyPatch(typeof(Utilities.GuestUtility))]
    // public static class Extensions_Patch
    // {
    //     /// <summary>
    //     /// Pawns less willing to use onahole if they are dirty.
    //     /// </summary>
    //     [HarmonyPostfix]
    //     [HarmonyPatch("IsOnahole")]
    //     public static void IsOnahole_Postfix(Pawn onahole, ref float __result)
    //     {
    //         __result *= Mathf.Max(0.2f, onahole.needs?.TryGetNeed<Need_Hygiene>()?.CurLevel ?? 1f);
    //     }
    // }
}

repo_name: atainer/rimjobworld

license: mit


code:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <AssemblyName>Onahole_DubsBadHygiene</AssemblyName>
  </PropertyGroup>

  <!-- Release build -->
  <PropertyGroup>
    <DebugType>none</DebugType>
    <Optimize>true</Optimize>
    <OutDir>../../Mods/DubsBadHygiene/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>
    <Reference Include="DubsBadHygiene">
      <HintPath>../../Libs/BadHygiene.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>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\RJW_Onahole\RJW_Onahole.csproj">
      <Private>false</Private>
    </ProjectReference>
  </ItemGroup>
</Project>


code:
using System;
using HarmonyLib;
using Verse;
using System.Linq;
using System.Collections.Generic;
using RJW_Onahole.Utilities;
using System.Reflection;
using System.Reflection.Emit;
using FacialAnimation;
namespace RJW_Onahole
{
    [StaticConstructorOnStartup]
    public static class FacialAnimation
    {
        public static Harmony Harmony = new Harmony(nameof(RJW_Onahole) + nameof(FacialAnimation));
        static FacialAnimation()
        {
            List<FaceAnimationDef> faceAnimationDefs = DefDatabase<FaceAnimationDef>.AllDefsListForReading;
            foreach (FaceAnimationDef faceAnimationDef in faceAnimationDefs)
            {
                if (faceAnimationDef.defName.ContainsCI("lovin") || faceAnimationDef.defName.ContainsCI("laydown"))
                {
                    faceAnimationDef.targetJobs.AddDistinct(OnaholeDefOf.BeOnahole.defName);
                    ("Added BeOnahole to targetJob of FacialAnimation " + faceAnimationDef.defName + "\nCurrent targetJobs: " + string.Join(", ", faceAnimationDef.targetJobs)).Log();
                }
            }
            Harmony.PatchAll();
        }
    }
    [HarmonyPatch(typeof(FacialAnimationControllerComp))]
    public static class FacialAnimationControllerComp_Patch
    {
        private static Type type = AccessTools.TypeByName("FacialAnimation.FAHelper");
        private static readonly MethodInfo FilterAnimationListWithCurrentStatus = AccessTools.Method(type, "FilterAnimationListWithCurrentStatus");
        private static readonly FieldInfo CurrentJobAnimationList = AccessTools.Field(typeof(FacialAnimationControllerComp), "currentJobAnimationList");
        private static readonly FieldInfo PawnField = AccessTools.Field(typeof(FacialAnimationControllerComp), "pawn");
        [HarmonyTranspiler]
        [HarmonyPatch("UpdateAnimation")]
        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
        {
            List<CodeInstruction> codes = instructions.ToList();
            bool found = false;
            for (int i = 2; i < codes.Count; i++)
            {
                if (codes[i - 2].Calls(FilterAnimationListWithCurrentStatus))
                {
                    List<CodeInstruction> newCodes = new List<CodeInstruction>
                    {
                        new CodeInstruction(OpCodes.Ldarg_0),
                        new CodeInstruction(OpCodes.Ldarg_0),
                        new CodeInstruction(OpCodes.Ldfld, PawnField),
                        new CodeInstruction(OpCodes.Ldarg_0),
                        new CodeInstruction(OpCodes.Ldfld, CurrentJobAnimationList),
                        new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(FacialAnimationControllerComp_Patch), nameof(FilterByOnaholeCondition))),
                        new CodeInstruction(OpCodes.Stfld, CurrentJobAnimationList)
                    };
                    codes.InsertRange(i, newCodes);
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                "Failed to patch FacialAnimationControllerComp.UpdateAnimation. No call to FAHelper.FilterAnimationListWithCurrentStatus found.".LogError();
            }
            return codes.AsEnumerable();
        }
        public static IEnumerable<FaceAnimation> FilterByOnaholeCondition(Pawn pawn, IEnumerable<FaceAnimation> animations)
        {
            if (pawn?.IsOnahole() == true && animations != null)
            {
                if (!pawn.jobs.curDriver.asleep)
                {
                    animations = animations.Where(x => x.animationDef.defName.ContainsCI("laydown") == false);
                }
                if (!pawn.IsOnaholeLovin() || pawn.jobs.curDriver.asleep)
                {
                    animations = animations.Where(x => x.animationDef.defName.ContainsCI("lovin") == false);
                }
            }
            return animations;
        }
    }
}


code:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <AssemblyName>Onahole_FacialAnimation</AssemblyName>
  </PropertyGroup>

  <!-- Release build -->
  <PropertyGroup>
    <DebugType>none</DebugType>
    <Optimize>true</Optimize>
    <OutDir>../../Mods/FacialAnimation/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>
    <Reference Include="FacialAnimation">
      <HintPath>../../Libs/FacialAnimation.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>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\RJW_Onahole\RJW_Onahole.csproj">
      <Private>false</Private>
    </ProjectReference>
  </ItemGroup>
</Project>


code:
using System;
using RimWorld;
using UnityEngine;
using UnityEngine.PlayerLoop;
using HarmonyLib;
using Verse;
using System.Linq;
using System.Collections.Generic;
using RJW_Onahole.Comps;
using AlienRace.ExtendedGraphics;
using Verse.AI;
using RJW_Onahole.Utilities;
using RJW_Onahole.Jobs;
using AlienRace;
using System.Reflection;

namespace RJW_Onahole
{
    [StaticConstructorOnStartup]
    public static class HAR
    {
        public static Harmony Harmony = new Harmony(nameof(RJW_Onahole) + nameof(HAR));
        public static RJW_Onahole_Settings Settings;
        static HAR()
        {
            Harmony.PatchAll();
        }

    }
    /// <summary>
    /// Show sleep graphics
    /// </summary>
    [HarmonyPatch(typeof(ConditionJob))]
    public static class ConditionJob_Patch
    {
        private static readonly HashSet<JobDef> sleepJobs = new HashSet<JobDef>
        {
            JobDefOf.Wait_Asleep,
            JobDefOf.Wait_AsleepDormancy,
            JobDefOf.LayDown,
            JobDefOf.LayDownResting,
        };
        private static readonly HashSet<JobDef> layingJobs = new HashSet<JobDef>
        {
            JobDefOf.LayDownAwake,
            JobDefOf.Wait_MaintainPosture
        };
        [HarmonyPrefix]
        [HarmonyPatch("Satisfied")]
        [HarmonyPriority(Priority.First)]
        public static bool Satisfied_Prefix(ExtendedGraphicsPawnWrapper pawn, ref ResolveData data, ConditionJob __instance, ref bool __result)
        {
            if (sleepJobs.Contains(__instance.job) && pawn.WrappedPawn.IsOnahole() && pawn.WrappedPawn.jobs.curDriver.asleep)
            {
                __result = true;
                return false;
            }
            if (layingJobs.Contains(__instance.job) && pawn.WrappedPawn.IsOnahole())
            {
                __result = true;
                return false;
            }
            return true;
        }
    }
    /// <summary>
    /// Trigger graphics update when sleep state changes
    /// </summary>
    [HarmonyPatch(typeof(JobDriver_BeOnahole), nameof(JobDriver_BeOnahole.SignalSleepStateChanged))]
    class JobDriver_BeOnahole_Patch
    {
        public static bool Prepare()
        {
            MethodBase method = null;
            try { method = AccessTools.Method(typeof(AlienPartGenerator.AlienComp), "RegenerateAddonGraphicsWithCondition", new Type[] { typeof(Pawn), typeof(HashSet<Type>) }); }
            catch { }
            if (method == null)
            {
                "Using outdated HAR, please consider updating Humanoid Alien Races".LogError();
            }
            return method != null;
        }
        public static void Postfix(JobDriver_BeOnahole __instance)
        {
            HashSet<Type> hashSet = new HashSet<Type> { typeof(ConditionJob) };
            AlienPartGenerator.AlienComp.RegenerateAddonGraphicsWithCondition(__instance.pawn, hashSet);
            __instance.pawn.Drawer.renderer.SetAllGraphicsDirty();
        }
    }
}


code:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <AssemblyName>Onahole_HAR</AssemblyName>
  </PropertyGroup>

  <!-- Release build -->
  <PropertyGroup>
    <DebugType>none</DebugType>
    <Optimize>true</Optimize>
    <OutDir>../../Mods/HAR/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>
    <Reference Include="HAR">
      <HintPath>../../Libs/AlienRace.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>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\RJW_Onahole\RJW_Onahole.csproj">
      <Private>false</Private>
    </ProjectReference>
  </ItemGroup>
</Project>


code:
using RimWorld;
using HarmonyLib;
using Verse;
using RJW_Onahole.Hediffs;
using PipeSystem;
using System.Collections.Generic;
using System.Linq;
using rjw;
using RJW_Onahole.Comps;
using RJW_Onahole.Utilities;
using RJW_Onahole.UI;
using System;
using UnityEngine;

namespace RJW_Onahole
{
    [StaticConstructorOnStartup]
    public static class PipeNetwork
    {
        public static Harmony Harmony = new Harmony(nameof(RJW_Onahole) + nameof(PipeNetwork));
        public static Dictionary<ThingDef, PipeNetDef> pipeNets = new Dictionary<ThingDef, PipeNetDef>();
        static PipeNetwork()
        {
            Harmony.PatchAll();
            EventUtility.OnPostLoadLong += RegisterPipeNets;
        }
        public static void RegisterPipeNets()
        {
            List<ThingDef> OnaholeBeds = DefDatabase<ThingDef>.AllDefs.Where(x => x.comps.Any(y => y.compClass == typeof(CompOnaholeBed))).ToList();
            foreach (PipeNetDef def in DefDatabase<PipeNetDef>.AllDefs)
            {
                foreach (ThingDef thingDef in OnaholeBeds)
                {
                    thingDef.comps.Add(new CompProperties_Resource { pipeNet = def });
                }
            }
            foreach (CompProperties_ConvertThingToResource compProperties_ConvertThingToResource in DefDatabase<ThingDef>.AllDefs.SelectMany(x => x.comps).Where(x => x is CompProperties_ConvertThingToResource).Cast<CompProperties_ConvertThingToResource>())
            {
                ("Add pipe net conversion to " + compProperties_ConvertThingToResource.thing.defName + " using " + compProperties_ConvertThingToResource.pipeNet.defName).Log();
                pipeNets[compProperties_ConvertThingToResource.thing] = compProperties_ConvertThingToResource.pipeNet;
            }
            //Some only has convert to thing, like nutrient paste expanded
            foreach (CompProperties_ConvertResourceToThing compProperties_ConvertResourceToThing in DefDatabase<ThingDef>.AllDefs.SelectMany(x => x.comps).Where(x => x is CompProperties_ConvertResourceToThing).Cast<CompProperties_ConvertResourceToThing>())
            {
                if (pipeNets.ContainsKey(compProperties_ConvertResourceToThing.thing)) { continue; }
                ("Add pipe net conversion to " + compProperties_ConvertResourceToThing.thing.defName + " using " + compProperties_ConvertResourceToThing.pipeNet.defName).Log();
                pipeNets[compProperties_ConvertResourceToThing.thing] = compProperties_ConvertResourceToThing.pipeNet;
            }
        }
        public static bool IsConnectedToPipeNetStorage(this ThingWithComps thing, PipeNetDef def)
        {
            foreach (CompResource compResource in thing.GetComps<CompResource>())
            {
                if (compResource.PipeNet != null && compResource.PipeNet.def == def)
                {
                    return compResource.PipeNet.storages.Count > 0;
                }
            }
            return false;
        }
        public static PipeNet GetPipeNetForThing(this ThingWithComps thing, ThingDef thingDef)
        {
            return thing.GetComps<CompResource>().FirstOrDefault(x => x.PipeNet != null && x.PipeNet.def == pipeNets[thingDef])?.PipeNet;
        }
        public static PipeNet GetPipeNetForDef(this ThingWithComps thing, PipeNetDef def)
        {
            return thing.GetComps<CompResource>().FirstOrDefault(x => x.PipeNet != null && x.PipeNet.def == def)?.PipeNet;
        }
    }
    [HarmonyPatch(typeof(Building_OnaholeBed))]
    public static class Building_OnaholeBed_Patch
    {
        [HarmonyPrefix]
        [HarmonyPatch(nameof(Building_OnaholeBed.TryStoreInBed))]
        public static bool TryStoreInBed_Prefix(Building_OnaholeBed __instance, Thing thing)
        {
            PipeNet pipeNet = null;
            if (PipeNetwork.pipeNets.TryGetValue(thing.def, out PipeNetDef pipeNetDef))
            {
                pipeNet = __instance.GetPipeNetForDef(pipeNetDef);
            }
            if (pipeNet == null) { return true; }
            pipeNet.DistributeAmongStorage(thing.stackCount, out float stored);
            thing.stackCount -= (int)stored;
            if (thing.stackCount <= 0) { thing.Destroy(DestroyMode.Vanish); return false; }
            return true;
        }
        [HarmonyPrefix]
        [HarmonyPatch(nameof(Building_OnaholeBed.TickRare))]
        public static void TickRare_Prefix(Building_OnaholeBed __instance)
        {
            foreach (Thing thing in __instance.GetDirectlyHeldThings())
            {
                if (PipeNetwork.pipeNets.TryGetValue(thing.def, out PipeNetDef pipeNetDef))
                {
                    __instance.GetPipeNetForDef(pipeNetDef).DistributeAmongStorage(thing.stackCount, out float stored);
                    thing.stackCount -= (int)stored;
                    if (thing.stackCount <= 0) { thing.Destroy(DestroyMode.Vanish); }
                }
            }
        }
    }
    [HarmonyPatch(typeof(Gizmo_IngestibleFuelStatus))]
    public static class Gizmo_IngestibleFuelStatus_Patch
    {
        [HarmonyPostfix]
        [HarmonyPatch(nameof(Gizmo_IngestibleFuelStatus.IsAvailable))]
        public static void IsFoodAvailable_Postfix(Gizmo_IngestibleFuelStatus __instance, ThingDef thingDef, ref bool __result)
        {
            if (__result == false && PipeNetwork.pipeNets.TryGetValue(thingDef, out PipeNetDef pipeNetDef))
            {
                __result = __instance.compIngestibleRefuelable.parent.IsConnectedToPipeNetStorage(pipeNetDef);
                return;
            }
        }
    }
    [HarmonyPatch(typeof(CompIngestibleRefuelable))]
    public static class CompIngestibleRefuelable_Patch
    {
        [HarmonyPostfix]
        [HarmonyPatch(nameof(CompIngestibleRefuelable.CompTickRare))]
        public static void CompTickRare_Postfix(CompInfuserRefuelable __instance)
        {
            if (__instance.FuelThingDef == null) { return; }
            if (PipeNetwork.pipeNets.TryGetValue(__instance.FuelThingDef, out PipeNetDef pipeNetDef))
            {
                int needed = (int)(__instance.TargetFuelLevel - __instance.Fuel);
                PipeNet pipeNet = __instance.parent.GetPipeNetForDef(pipeNetDef);
                if (pipeNet != null)
                {
                    int amount = Mathf.Min(needed, (int)pipeNet.storages.Sum(x => x.AmountStored));
                    pipeNet.DrawAmongStorage(amount, pipeNet.storages);
                    __instance.Refuel(amount);
                }
            }
        }
        [HarmonyPrefix]
        [HarmonyPatch(nameof(CompIngestibleRefuelable.BarMat))]
        public static bool BarMat_Prefix(CompIngestibleRefuelable __instance, ref Material __result)
        {
            if (__instance.FuelThingDef == null) { return true; }
            if (PipeNetwork.pipeNets.TryGetValue(__instance.FuelThingDef, out PipeNetDef pipeNetDef))
            {
                if (MaterialCreator.materials.TryGetValue(pipeNetDef, out Material material))
                {
                    __result = material;
                    return false;
                }
            }
            return true;
        }
    }
    [HarmonyPatch(typeof(CompDrugInfuser))]
    public static class CompDrugInfuser_Patch
    {
        [HarmonyPrefix]
        [HarmonyPatch(nameof(CompDrugInfuser.GetColor))]
        public static bool GetPipeMat_Prefix(CompDrugInfuser __instance, ref Color __result)
        {
            if (__instance.infuserRefuelable?.FuelThingDef == null) { return true; }
            if (PipeNetwork.pipeNets.TryGetValue(__instance.infuserRefuelable?.FuelThingDef, out PipeNetDef pipeNetDef))
            {
                __result = pipeNetDef.resource.color;
                return false;
            }
            return true;
        }
    }
}


code:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <AssemblyName>Onahole_MilkPipe</AssemblyName>
  </PropertyGroup>

  <!-- Release build -->
  <PropertyGroup>
    <DebugType>none</DebugType>
    <Optimize>true</Optimize>
    <OutDir>../../Mods/PipeNetwork/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>
    <Reference Include="VEPipeSystem">
      <HintPath>../../Libs/PipeSystem.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>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\RJW_Onahole\RJW_Onahole.csproj">
      <Private>false</Private>
    </ProjectReference>
  </ItemGroup>
</Project>


code:
using HarmonyLib;
using System;
using System.Collections.Generic;
using RimWorld;
using Verse;
using RJW_Onahole.Utilities;
using System.Linq;
using rjw;
using System.Reflection;
using Privacy_Please;
namespace RJW_Onahole
{
    [StaticConstructorOnStartup]
    public static class Privacy
    {
        private static readonly Harmony harmony = new Harmony(nameof(RJW_Onahole) + "." + nameof(Privacy));
        static Privacy()
        {
            harmony.PatchAll();
        }
    }
    /// <summary>
    /// Shambler/other pawns with no ideo
    /// </summary>
    [HarmonyPatch(typeof(SexInteractionUtility))]
    public static class SexInteractionUtility_Patch
    {
        private static readonly MethodBase GetBestialityRolesMethod = AccessTools.Method(typeof(SexInteractionUtility), "getBestialityRoles");
        [HarmonyPrefix]
        [HarmonyPatch(nameof(SexInteractionUtility.SexActIsBestialityWithOrdinaryAnimal))]
        public static bool SexActIsBestialityWithOrdinaryAnimal_Prefix(ref bool __result, JobDriver_Sex jobDriver)
        {
            ValueTuple<bool, Pawn, Pawn> bestialityRoles = (ValueTuple<bool, Pawn, Pawn>)GetBestialityRolesMethod.Invoke(null, new object[] { jobDriver });
            if (!bestialityRoles.Item1)
            {
                __result = false;
                return false;
            }
            if (bestialityRoles.Item2.ideo == null || bestialityRoles.Item3.ideo == null)
            {
                __result = false;
                return false;
            }
            else
            {
                return true;
            }
        }

        [HarmonyPrefix]
        [HarmonyPatch(nameof(SexInteractionUtility.SexActIsBestialityWithSpecialAnimal))]
        public static bool SexActIsBestialityWithSpecialAnimal_Prefix(ref bool __result, JobDriver_Sex jobDriver)
        {
            ValueTuple<bool, Pawn, Pawn> bestialityRoles = (ValueTuple<bool, Pawn, Pawn>)GetBestialityRolesMethod.Invoke(null, new object[] { jobDriver });
            if (!bestialityRoles.Item1)
            {
                __result = false;
                return false;
            }
            if (bestialityRoles.Item2.ideo == null || bestialityRoles.Item3.ideo == null)
            {
                __result = false;
                return false;
            }
            else
            {
                return true;
            }
        }

    }
}


code:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <AssemblyName>Onahole_Privacy</AssemblyName>
  </PropertyGroup>

  <!-- Release build -->
  <PropertyGroup>
    <DebugType>none</DebugType>
    <Optimize>true</Optimize>
    <OutDir>../../Mods/Privacy/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>
    <Reference Include="Privacy-Please">
      <HintPath>../../Libs/Privacy-Please.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>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\RJW_Onahole\RJW_Onahole.csproj">
      <Private>false</Private>
    </ProjectReference>
  </ItemGroup>
</Project>


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

namespace RJW_Onahole
{
	public class GroupAnimationContext_OnaholeSex : BaseGroupAnimationContext
	{
		public override bool CanAnimationBeUsed(List<Pawn> actors, int numActors)
		{
			return actors.Any(x => x.IsOnahole());
		}

        public override string DebugMessage()
        {
			return "Catch-all animation for Onahole";
        }
	}
}


code:
using HarmonyLib;
using System;
using System.Collections.Generic;
using RimWorld;
using Verse;
using Rimworld_Animations;
using RJW_Onahole.Utilities;
using System.Linq;
using rjw;
using RJW_Onahole.Jobs;
using RJW_Onahole.Comps;
namespace RJW_Onahole
{
    [StaticConstructorOnStartup]
    public static class RJW_Animation
    {
        private static Harmony harmony = new Harmony(nameof(global::RJW_Onahole.RJW_Onahole) + nameof(RJW_Animation));
        static RJW_Animation()
        {
            harmony.PatchAll();
        }
    }
    /// <summary>
    /// Stop group animation when finish using onahole，prevent animation bug on use onahole interrupted
    /// </summary>
    [HarmonyPatch(typeof(JobDriver_UseOnahole))]
    public static class JobDriver_UseOnahole_Patch
    {
        [HarmonyPrefix]
        [HarmonyPatch("OnaholeFinishAction")]
        public static void Prefix(JobDriver_UseOnahole __instance)
        {
            try { AnimationUtility.StopGroupAnimation(__instance.pawn); } catch { }
            try { AnimationUtility.StopGroupAnimation(__instance.Partner); } catch { }
        }
    }
    [HarmonyPatch(typeof(CompOnaholeBed))]
    public static class CompOnaholeBed_Patch
    {
        [HarmonyPostfix]
        [HarmonyPatch(nameof(CompOnaholeBed.Notify_PawnUnbind))]
        public static void Postfix(Pawn pawn)
        {
            try { AnimationUtility.StopGroupAnimation(pawn); } catch { }
            try { AnimationUtility.StopGroupAnimation(pawn.OnaholeUser()); } catch { }
        }
    }
}


code:
using HarmonyLib;
using System;
using System.Collections.Generic;
using RimWorld;
using Verse;
using Rimworld_Animations;
using RJW_Onahole.Utilities;
using System.Linq;
using rjw;
using RJW_Onahole.Jobs;
using RJW_Onahole.Comps;
namespace RJW_Onahole
{
    public class PawnTest_IsOnahole : BasePawnTest
    {
        public override bool PawnTest(Pawn pawn)
        {
            return pawn.IsOnahole();
        }
    }

}


code:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <AssemblyName>Onahole_RJW_Animation</AssemblyName>
  </PropertyGroup>

  <!-- Release build -->
  <PropertyGroup>
    <DebugType>none</DebugType>
    <Optimize>true</Optimize>
    <OutDir>../../Mods/Animations/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>
    <Reference Include="Rimworld-Animations">
      <HintPath>../../Libs/Rimworld-Animations.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>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\RJW_Onahole\RJW_Onahole.csproj">
      <Private>false</Private>
    </ProjectReference>
  </ItemGroup>
</Project>


code:
using HarmonyLib;
using System;
using System.Collections.Generic;
using RimWorld;
using Verse;
using RJW_Onahole.Utilities;
using System.Linq;
using rjw;
using BrothelColony;
using UnityEngine;
using RJW_Onahole.UI;
namespace RJW_Onahole
{
    [StaticConstructorOnStartup]
    public static class RJW_BrothelColony
    {
        private static Harmony harmony = new Harmony(nameof(RJW_Onahole) + nameof(RJW_BrothelColony));
        static RJW_BrothelColony()
        {
            harmony.PatchAll();
        }
        public static int BrothelOnaholePrice(Building_OnaholeBed bed)
        {
            if (bed.Pawn == null) { return 0; }
            if (!bed.Pawn.RaceProps.Humanlike)
            {
                return bed._price;
            }
            return (int)(WhoreBed_Utility.CalculatePriceFactor(bed) * WhoringHelper.WhoreMaxPrice(bed.Pawn));
        }
    }
    [HarmonyPatch(typeof(WhoreBed_Utility))]
    public static class WhoreBed_Utility_Patch
    {
        [HarmonyPatch(nameof(WhoreBed_Utility.CalculatePriceFactor))]
        [HarmonyPostfix]
        public static void GetAvailableWhoreBeds_Postfix(ref float __result, Building_Bed bed, float room_multiplier)
        {
            BedData bedData = WhoringBase.DataStore.GetBedData(bed);
            if (bed is Building_OnaholeBed && RJW_Onahole_Settings.OverrideBedScore)
            {
                __result = bedData.roomScore * RJW_Onahole_Settings.BedScore;
            }
        }
    }
    [HarmonyPatch(typeof(RJW_Onahole_Settings))]
    public static class RJW_Onahole_Settings_Patch
    {
        [HarmonyPatch("AddTabs")]
        [HarmonyPostfix]
        public static void AddTabs_Postfix()
        {
            RJW_Onahole_Settings.tabManager.AddTab(Tr.BrothelColony, BrothelTab);
        }
        public static void BrothelTab(Rect inRect)
        {
            Listing_Standard listing_Standard = new Listing_Standard();
            listing_Standard.Begin(inRect);
            listing_Standard.CheckboxLabeled(Tr.UseBrothelPrice, ref RJW_Onahole_Settings.UseBrothelPrice);
            if (RJW_Onahole_Settings.UseBrothelPrice)
            {
                listing_Standard.CheckboxLabeled(Tr.OverrideBedScore, ref RJW_Onahole_Settings.OverrideBedScore);
                if (RJW_Onahole_Settings.OverrideBedScore)
                {
                    string buffer = RJW_Onahole_Settings.BedScore.ToString();
                    listing_Standard.TextFieldNumericLabeled<float>(Tr.BedScore, ref RJW_Onahole_Settings.BedScore, ref buffer, 0f, 1000f);
                }
            }
            else
            {
                RJW_Onahole_Settings.OverrideBedScore = false;
            }
            listing_Standard.End();

        }
    }
    [HarmonyPatch(typeof(Widget_SetPrice))]
    public static class Window_SetPrice_Patch
    {
        [HarmonyPatch(nameof(Widget_SetPrice.DrawContents))]
        [HarmonyPrefix]
        public static bool Draw_Prefix(Widget_SetPrice __instance, Rect inRect)
        {
            if (RJW_Onahole_Settings.UseBrothelPrice && __instance.primarySelection.Pawn?.RaceProps.Humanlike != false)
            {
                Listing_Standard listing_Standard = new Listing_Standard();
                bool payWithGoodwill = __instance.primarySelection.payWithGoodwill;
                listing_Standard.Begin(inRect);
                Text.Font = GameFont.Medium;
                listing_Standard.Label(OTr.MarketValue);
                Text.Font = GameFont.Small;
                listing_Standard.CheckboxLabeled(Tr.PayGoodwill, ref __instance.primarySelection.payWithGoodwill);
                listing_Standard.Label(Tr.BedScore + ": " + WhoreBed_Utility.CalculatePriceFactor(__instance.primarySelection).ToString());
                if (__instance.primarySelection.Pawn != null)
                {
                    listing_Standard.Label(__instance.primarySelection.Pawn.LabelShort + ": $" + WhoringHelper.WhoreMaxPrice(__instance.primarySelection.Pawn).ToString());
                    listing_Standard.Label(Tr.FinalPrice + ": $" + RJW_BrothelColony.BrothelOnaholePrice(__instance.primarySelection));
                }
                listing_Standard.End();
                if (payWithGoodwill != __instance.primarySelection.payWithGoodwill)
                {
                    __instance.isChanged = true;
                }
                return false;
            }
            return true;
        }
        [HarmonyPrefix]
        [HarmonyPatch(nameof(Widget_SetPrice.PropagateChange))]
        public static bool PropagateChange_Prefix(Building_OnaholeBed source, Building_OnaholeBed target)
        {
            if (RJW_Onahole_Settings.UseBrothelPrice)
            {
                target.payWithGoodwill = source.payWithGoodwill;
                return false;
            }
            return true;
        }
    }

    [HarmonyPatch(typeof(Building_OnaholeBed))]
    public static class Building_OnaholeBed_Patch
    {

        [HarmonyPatch("get_Price")]
        [HarmonyPrefix]
        public static bool get_Price_Prefix(ref int __result, Building_OnaholeBed __instance)
        {
            if (RJW_Onahole_Settings.UseBrothelPrice)
            {
                if (__instance.payWithGoodwill)
                {
                    __result = 0;
                    return false;
                }
                __result = RJW_BrothelColony.BrothelOnaholePrice(__instance);
                return false;
            }
            return true;
        }
    }
    [HarmonyPatch(typeof(Utilities.GuestUtility))]
    public static class GuestUtility_Patch
    {
        [HarmonyPatch(nameof(Utilities.GuestUtility.WouldPayForOnahole))]
        [HarmonyPrefix]
        public static bool WouldPayForOnahole_Prefix(ref bool __result, Pawn pawn, Pawn onahole, ref int surplus)
        {
            if (RJW_Onahole_Settings.UseBrothelPrice && onahole.RaceProps.Humanlike != false)
            {
                __result = true;
                surplus = onahole.OnaholeBed().payWithGoodwill ? 0 : (int)(WhoreBed_Utility.CalculatePriceFactor(onahole.OnaholeBed()) * WhoringHelper.WhoreMaxPrice(onahole));
                return false;
            }
            return true;
        }

        [HarmonyPatch(nameof(Utilities.GuestUtility.PaySilver))]
        [HarmonyPrefix]
        public static void PaySilver_Prefix(int amount, Building_OnaholeBed bed)
        {
            bed.Pawn?.records?.AddTo(BrothelColony.RecordDefOf.EarnedMoneyByWhore, (float)amount);
			bed.Pawn?.records?.Increment(BrothelColony.RecordDefOf.CountOfWhore);
        }
    }
    [HarmonyPatch(typeof(BrothelColony.MainTab.PawnColumnWorker_Mood))]
    public static class PawnColumnWorker_Mood_Patch
    {
        /// <summary>
        /// Fix null reference when shambler/anything that has no mood is onahole
        /// </summary>
        /// <param name="__result"></param>
        /// <param name="pawn"></param>
        /// <returns></returns>
        [HarmonyPatch("GetValueToCompare")]
        [HarmonyPrefix]
        public static bool GetValueToCompare_Prefix(ref float __result, Pawn pawn)
        {
            if (pawn?.needs?.mood == null)
            {
                __result = 0.5f;
                return false;
            }
            return true;
        }
    }
}


code:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <AssemblyName>Onahole_RJW_BrothelColony</AssemblyName>
  </PropertyGroup>

  <!-- Release build -->
  <PropertyGroup>
    <DebugType>none</DebugType>
    <Optimize>true</Optimize>
    <OutDir>../../Mods/BrothelColony/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>
    <Reference Include="RimJobWorldBrothelColony">
      <HintPath>../../Libs/RimJobWorldBrothelColony.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>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\RJW_Onahole\RJW_Onahole.csproj">
      <Private>false</Private>
    </ProjectReference>
  </ItemGroup>
</Project>


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

namespace RJW_Onahole
{
    [StaticConstructorOnStartup]
    internal static class RJW_Menstruation
    {
        private static readonly Harmony harmony = new Harmony(nameof(RJW_Onahole) + nameof(RJW_Menstruation));
        static RJW_Menstruation()
        {
            // Ensure patching after main mod loaded
            LongEventHandler.QueueLongEvent(() => { harmony.PatchAll(); }, "RJW_Onahole_RJW_Menstruation_LongEvent", false, null);
        }
    }
    [HarmonyPatch(typeof(HediffComp_Electrocute))]
    public static class HediffComp_Electrocute_Patch
    {
        private static Dictionary<Type, Dictionary<HediffComp_Menstruation.Stage, MethodInfo>> methodCache = new Dictionary<Type, Dictionary<HediffComp_Menstruation.Stage, MethodInfo>>();
        [HarmonyPrefix]
        [HarmonyPatch("ElectrocuteGenitals")]
        public static void ElectrocuteGenitals_Prefix(Pawn pawn)
        {
            if (Genital_Helper.has_vagina(pawn))
            {
                foreach (HediffComp_Menstruation menstruation in pawn.health.hediffSet.GetHediffComps<HediffComp_Menstruation>())
                {
                    if (MethodName(menstruation.curStage) == "") { continue; }
                    MethodInfo methodInfo = methodCache.TryGetValue(menstruation.GetType())?.TryGetValue(menstruation.curStage);
                    if (methodInfo == null)
                    {
                        methodInfo = AccessTools.Method(MethodName(menstruation.curStage));
                        methodCache.AddDistinct(menstruation.GetType(), new Dictionary<HediffComp_Menstruation.Stage, MethodInfo>());
                        methodCache[menstruation.GetType()].AddDistinct(menstruation.curStage, methodInfo);
                    }
                    methodInfo.Invoke(menstruation, new object[] { });
                }
            }
        }
        private static string MethodName(HediffComp_Menstruation.Stage stage)
        {
            string baseStr = "RJW_Menstruation.HediffComp_Menstruation:";
            switch (stage)
            {
                case HediffComp_Menstruation.Stage.Follicular:
                    return baseStr + "FollicularAction";
                case HediffComp_Menstruation.Stage.Luteal:
                    return baseStr + "LutealAction";
                case HediffComp_Menstruation.Stage.Bleeding:
                    return baseStr + "BleedingAction";
                case HediffComp_Menstruation.Stage.Recover:
                    return baseStr + "RecoverAction";
                case HediffComp_Menstruation.Stage.Anestrus:
                    return baseStr + "AnestrusAction";
                default:
                    break;
            }
            return "";
        }
    }
    [HarmonyPatch(typeof(HediffComp_Menstruation))]
    public static class FilthMaker_Colored_Patch
    {
        /// <summary>
        /// Collect forced out cum
        /// </summary>
        /// <param name="cum"></param>
        /// <param name="amount"></param>
        /// <param name="__instance"></param>
        /// <returns></returns>
        [HarmonyPrefix]
        [HarmonyPatch("MakeCumFilth")]
        public static bool MakeCumFilth_Prefix(Cum cum, ref float amount, HediffComp_Menstruation __instance)
        {
            if (cum.pawn.IsOnahole())
            {
                amount = __instance.Pawn.OnaholeBed()?.GetComp<CompCumCollector>()?.TryStoreCum(Mathf.CeilToInt(amount), cum.FilthDef) ?? amount;
            }
            if (amount <= 0)
            {
                return false;
            }
            return true;
        }
        /// <summary>
        /// Collect natural leaked cum/blood
        /// </summary>
        /// <param name="instructions"></param>
        /// <returns></returns>
        [HarmonyTranspiler]
        [HarmonyPatch(nameof(HediffComp_Menstruation.CumOut), new Type[] { })]
        public static IEnumerable<CodeInstruction> CumOut_Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            List<CodeInstruction> codes = instructions.ToList();
            MethodInfo collectCum = AccessTools.Method(typeof(FilthMaker_Colored_Patch), nameof(CollectCum));
            bool found = false;
            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].opcode == OpCodes.Add && codes[i + 2].opcode == OpCodes.Ldstr && codes[i + 2].operand.ToString() == "FilthLabelWithSource")
                {
                    codes.InsertRange(i, new CodeInstruction[] {
                        new CodeInstruction(OpCodes.Ldarg_0),
                        new CodeInstruction(OpCodes.Ldloc_S, 6),
                        new CodeInstruction(OpCodes.Call, collectCum),
                    });
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                "Failed to patch FilthMaker_Colored.CumOut. Failed to find target IL code.".LogError();
            }
            return codes;
        }
        private static float CollectCum(float remaining, HediffComp_Menstruation instance, Cum cum)
        {
            if (instance.Pawn.IsOnahole())
            {
                remaining = instance.Pawn.OnaholeBed()?.GetComp<CompCumCollector>()?.TryStoreCum(Mathf.CeilToInt(remaining), cum.FilthDef) ?? remaining;
            }
            return remaining;
        }

    }

}


code:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <AssemblyName>Onahole_RJW_Menstruation</AssemblyName>
  </PropertyGroup>

  <!-- Release build -->
  <PropertyGroup>
    <DebugType>none</DebugType>
    <Optimize>true</Optimize>
    <OutDir>../../Mods/Menstruation/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>
    <Reference Include="RJW_Menstruation">
      <HintPath>../../Libs/RJW_Menstruation.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>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\RJW_Onahole\RJW_Onahole.csproj">
      <Private>false</Private>
    </ProjectReference>
  </ItemGroup>
</Project>


code:
using Verse;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Collections;
using Verse.AI;
using System.Text;
using UnityEngine;
using rjw;
using System.Linq;
using rjw.Modules.Shared.Enums;
using RJW_Onahole.UI;
using RJW_Onahole.Hediffs;
using RJW_Onahole.Utilities;
using RJW_Onahole.Comps;
using RJW_Onahole.Interfaces;
using System.Reflection;
using RJW_Onahole.Data;
using static RJW_Onahole.Utilities.GuestUtility;

namespace RJW_Onahole
{
    public class Building_OnaholeBed : Building_Bed, IThingHolder, IHaulSource, IThingHolderEvents<Thing>, IBeautyContainer
    {
        public Pawn Pawn { get => this.OwnersForReading.FirstOrDefault(); }
        public Pawn BoundPawn { get => this.CurOccupants.FirstOrDefault(); }
        public UseRestriction useRestriction;
        public int Price => _price;
        public int _price = 0;
        public ThingOwner innerContainer;
        public bool payWithGoodwill = false;
        private bool allowHaul = false;
        private int lastHaulTick = 0;
        // Show selector in 1x2 even if it's 1x1
        //public override CellRect? CustomRectForSelector => GenAdj.OccupiedRect(base.Position, this.Rotation.Opposite, new IntVec2(1, 2));
        public override void DrawGUIOverlay()
        {
            if (Find.CameraDriver.CurrentZoom == CameraZoomRange.Closest && this.useRestriction.AllowsAnyFromFaction(FactionType.Guest))
            {
                Color defaultThingLabelColor = payWithGoodwill ? Color.green : GenMapUI.DefaultThingLabelColor;
                string text = "$" + this.Price.ToString();
                if (this.Price == 0) { text = Tr.Free; }
                GenMapUI.DrawThingLabel(GenMapUI.LabelDrawPosFor(this, -0.5f), text, defaultThingLabelColor);
            }
        }
        public override void ExposeData()
        {
            base.ExposeData();
            Scribe_Deep.Look(ref useRestriction, "useRestriction");
            Scribe_Values.Look(ref _price, "price", 0);
            Scribe_Deep.Look(ref innerContainer, "innerContainer", new object[] { this });
            Scribe_Values.Look(ref payWithGoodwill, "payWithGoodwill", false);
            if (useRestriction == null) { useRestriction = new UseRestriction(); useRestriction.CopyFrom(RJW_Onahole_Settings.defaultUseRestriction); }
        }
        public override void TickRare()
        {
            base.TickRare();
            if (!this.allowHaul && Find.TickManager.TicksGame > this.lastHaulTick + RJW_Onahole_Settings.HaulCoolDownTicks)
            {
                this.allowHaul = true;
                this.Map.haulDestinationManager.AllHaulSourcesListForReading.AddDistinct(this);
                this.Map.listerHaulables.Notify_HaulSourceChanged(this);
            }
        }
        public override IEnumerable<Gizmo> GetGizmos()
        {
            if (useRestriction == null) { useRestriction = new UseRestriction(); }
            if (this.Faction?.IsPlayer == true)
            {
                yield return EditUseRestriction();
            }
            foreach (Gizmo gizmo in base.GetGizmos())
            {
                yield return gizmo;
            }
            if (this.Faction?.IsPlayer == true)
            {
                if (Pawn != null)
                {
                    yield return EditTransform();
                }
            }
        }
        public ICompAddsHediff GetControlComp(HediffDef hediffDef)
        {
            foreach (ThingComp thingComp in this.AllComps)
            {
                if (thingComp is ICompAddsHediff onaholeComp && onaholeComp.GetHediffDef().Contains(hediffDef))
                {
                    return onaholeComp;
                }
            }
            return null;
        }
        public StorageSettings GetStoreSettings()
        {
            StorageSettings settings = StorageSettings.EverStorableFixedSettings();
            settings.Priority = StoragePriority.Low;
            return settings;
        }
        public StorageSettings GetParentStoreSettings() { return null; }
        public void Notify_SettingsChanged() { }
        public bool StorageTabVisible { get => false; }
        public float BeautyOffset => CalculatePawnBeautyOffset(this.BoundPawn);
        public string BeautyOffsetExplanation => GetPawnBeautyOffsetExplanation(this.BoundPawn);
        private static readonly StatDef SexAbilityStat = DefDatabase<StatDef>.GetNamedSilentFail("SexAbility"); // Sexperience stat
        private static readonly RecordDef lustRecord = DefDatabase<RecordDef>.GetNamedSilentFail("Lust");// Sexperience record
        /// <summary>
        /// Calculate beauty based on pawn beauty
        /// </summary>
        /// <param name="pawn"></param>
        /// <returns></returns>
        public static float CalculatePawnBeautyOffset(Pawn pawn)
        {
            if (pawn == null) { return 0; }
            float offset = lustRecord != null ? pawn.records.GetValue(lustRecord) / 100 : 0;
            float mult = SexAbilityStat != null ? pawn.GetStatValue(SexAbilityStat, true) : 1;
            return (pawn.GetStatValue(StatDefOf.PawnBeauty, true) * 10 + offset) * mult;
        }
        /// <summary>
        /// Get explanation of pawn beauty offset
        /// </summary>
        /// <param name="pawn">Pawn currently restrained</param>
        /// <returns></returns>
        public static string GetPawnBeautyOffsetExplanation(Pawn pawn)
        {
            if (pawn == null) { return ""; }
            float offset = CalculatePawnBeautyOffset(pawn);
            string explanation = pawn.Name.ToStringShort + ": " + (offset > 0 ? "+" : "") + offset.ToString("0.0");
            float beauty = pawn.GetStatValue(StatDefOf.PawnBeauty, true) * 10;
            explanation += "\n    -" + StatDefOf.PawnBeauty.label + ": " + (beauty > 0 ? "+" : "") + beauty.ToString("0.0");
            if (lustRecord != null)
            {
                float lust = lustRecord != null ? pawn.records.GetValue(lustRecord) / 100 : 0;
                explanation += "\n    -" + lustRecord.label + ": " + (lust > 0 ? "+" : "") + lust.ToString("0.00");
            }
            if (SexAbilityStat != null)
            {
                explanation += "\n    -" + SexAbilityStat.label + ": x" + pawn.GetStatValue(SexAbilityStat, true).ToStringPercent();
            }
            return explanation;
        }
        public void GetChildHolders(List<IThingHolder> outChildren)
        {
            ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, this.GetDirectlyHeldThings());
        }
        public ThingOwner GetDirectlyHeldThings()
        {
            return this.innerContainer;
        }
        [Obsolete]
        public bool IsAllowed(Pawn pawn, out string reason) { return this.Allows(pawn, out reason); }
        public bool Allows(Pawn pawn, out string reason)
        {
            // Allow everyone to use onaholes owned by AI
            if (this.Faction != Faction.OfPlayer) { reason = ""; return true; }
            if (useRestriction == null) { useRestriction = new UseRestriction(); }
            // Check if use is allowed
            if (useRestriction.Allows(pawn, out reason))
            {
                if (pawn.Faction != Faction.OfPlayer) { return true; }
                // Process detailed settings
                CompMultiAssignable multiAssignable = this.TryGetComp<CompMultiAssignable>();
                if (multiAssignable == null) { return true; }
                if (multiAssignable.AssignedOrFreeToUse(pawn)) { return true; }
                reason = "{0} is not on the assigned list and the list is not empty";
            }
            return false;
        }
        public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish)
        {
            if (base.OwnersForReading.Count > 0)
            {
                foreach (Pawn item in base.OwnersForReading.ToList())
                {
                    this.GetComp<CompOnaholeBed>()?.Notify_PawnUnbind(item);
                }
            }
            this.innerContainer.TryDropAll(this.Position, this.Map, ThingPlaceMode.Near);
            // Make sure haul source is registered. Otherwise DeSpawn will error
            this.Map.haulDestinationManager.AllHaulSourcesListForReading.AddDistinct(this);
            base.DeSpawn(mode);
        }
        public override void SpawnSetup(Map map, bool respawningAfterLoad)
        {
            if (innerContainer == null) { this.innerContainer = new ThingOwner<Thing>(this); }
            if (this.Rotation != Rot4.North) { this.Rotation = Rot4.North; }
            base.SpawnSetup(map, respawningAfterLoad);
            if (!respawningAfterLoad) { base.ForPrisoners = true; }
            if (useRestriction == null) { useRestriction = new UseRestriction(); useRestriction.CopyFrom(RJW_Onahole_Settings.defaultUseRestriction); }
            base.Medical = false;
            this.lastHaulTick = Find.TickManager.TicksGame + Rand.Range(0, 2000);
            this.Map.listerHaulables.Notify_HaulSourceChanged(this);
        }
        /// <summary>
        /// Hook point for adding things to the bed
        /// </summary>
        /// <param name="thing"></param>
        /// <returns></returns>
        public bool TryStoreInBed(Thing thing)
        {
            return this.innerContainer.TryAddOrTransfer(thing);
        }
        private Command_Action EditTransform()
        {
            return new Command_Action
            {
                defaultLabel = Tr.EditTransform,
                defaultDesc = Tr.EditTransform_Desc,
                icon = CacheUtility.Tex.IconOptionsVideo,
                action = delegate
                {
                    WindowStack windowStack = Find.WindowStack;
                    windowStack.Add(new Window_SetTransform(this.Pawn));
                }
            };
        }
        private Command_Action EditUseRestriction()
        {
            return new Command_Action
            {
                defaultLabel = Tr.SetRestriction,
                icon = CacheUtility.Tex.IconOptions,
                action = delegate
                {
                    WindowStack windowStack = Find.WindowStack;
                    windowStack.Add(new Window_SetRestriction());
                }
            };
        }

        public override IEnumerable<FloatMenuOption> GetFloatMenuOptions(Pawn selectedPawn)
        {
            if (selectedPawn == null || selectedPawn.health?.Downed == true || selectedPawn.Dead) { yield break; }
            if (selectedPawn.IsFreeNonSlaveColonist && base.Faction == Faction.OfPlayer)
            {
                if (this.BoundPawn != null)
                {
                    Action action = delegate ()
                    {
                        Job job = JobMaker.MakeJob(OnaholeDefOf.UnbindOnahole, this, this.BoundPawn);
                        job.count = 1;
                        selectedPawn.jobs.TryTakeOrderedJob(job, new JobTag?(JobTag.Misc), false);
                        selectedPawn.mindState.ResetLastDisturbanceTick();
                    };
                    yield return new FloatMenuOption(Tr.Unbind + this.BoundPawn.Name.ToStringShort + ", " + this.BoundPawn.TitleOrRace(), action, MenuOptionPriority.High, null, null, 0f, null, null, true, 0);
                    yield break;
                }
                else if (this.Pawn == null)
                {
                    if (!selectedPawn.CanReserveAndReach(this, PathEndMode.ClosestTouch, Danger.Deadly, this.SleepingSlotsCount, -1, null, true))
                    {
                        yield return new FloatMenuOption(OTr.CannotReach, null, MenuOptionPriority.High, null, null, 0f, null, null, true, 0);
                        yield break;
                    }
                    BindablePawnList(selectedPawn, out List<Pawn> colonists, out List<Pawn> prisoners, out List<Pawn> slaves, out List<Pawn> animals, out List<Pawn> mechanoids, out List<Pawn> downedEnemies, out List<Pawn> others);
                    if (RJW_Onahole_Settings.showBindPrisonerMenu)
                    {
                        yield return new FloatMenuOption(Tr.Bind + ": " + OTr.Prisoner + " (" + prisoners.Count + ")", prisoners.Count == 0 ? null : new Action(() => BindablePawnOptions(prisoners, selectedPawn)), MenuOptionPriority.High, null, null, 0f, null, null, true, 0);
                    }
                    if (RJW_Onahole_Settings.showBindSlaveMenu)
                    {
                        yield return new FloatMenuOption(Tr.Bind + ": " + OTr.Slave + " (" + slaves.Count + ")", slaves.Count == 0 ? null : new Action(() => BindablePawnOptions(slaves, selectedPawn)), MenuOptionPriority.High, null, null, 0f, null, null, true, 0);
                    }
                    if (RJW_Onahole_Settings.showBindColonistMenu)
                    {
                        yield return new FloatMenuOption(Tr.Bind + ": " + OTr.Colonist + " (" + colonists.Count + ")", colonists.Count == 0 ? null : new Action(() => BindablePawnOptions(colonists, selectedPawn)), MenuOptionPriority.High, null, null, 0f, null, null, true, 0);
                    }
                    if (RJW_Onahole_Settings.showBindAnimalMenu)
                    {
                        yield return new FloatMenuOption(Tr.Bind + ": " + OTr.Animal + " (" + animals.Count + ")", animals.Count == 0 ? null : new Action(() => BindablePawnOptions(animals, selectedPawn)), MenuOptionPriority.High, null, null, 0f, null, null, true, 0);
                    }
                    if (RJW_Onahole_Settings.showBindMechanoidMenu)
                    {
                        yield return new FloatMenuOption(Tr.Bind + ": " + OTr.Mechanoid + " (" + mechanoids.Count + ")", mechanoids.Count == 0 ? null : new Action(() => BindablePawnOptions(mechanoids, selectedPawn)), MenuOptionPriority.High, null, null, 0f, null, null, true, 0);
                    }
                    if (RJW_Onahole_Settings.showBindDownedEnemyMenu)
                    {
                        yield return new FloatMenuOption(Tr.Bind + ": " + OTr.Enemy + " (" + OTr.Downed + ": " + downedEnemies.Count + ")", downedEnemies.Count == 0 ? null : new Action(() => BindablePawnOptions(downedEnemies, selectedPawn)), MenuOptionPriority.High, null, null, 0f, null, null, true, 0);
                    }
                    if (RJW_Onahole_Settings.showBindMiscMenu)
                    {
                        yield return new FloatMenuOption(Tr.Bind + ": " + OTr.Misc + " (" + others.Count + ")", others.Count == 0 ? null : new Action(() => BindablePawnOptions(others, selectedPawn)), MenuOptionPriority.High, null, null, 0f, null, null, true, 0);
                    }
                }
                else
                {
                    yield return new FloatMenuOption(OTr.ReservedBy.Replace("{1_labelShort}", this.Pawn.LabelShort + ", " + this.Pawn.TitleOrRace()), null, MenuOptionPriority.High, null, null, 0f, null, null, true, 0);
                    yield break;
                }
            }
        }
        private static void BindablePawnList(Pawn selectedPawn, out List<Pawn> colonists, out List<Pawn> prisoners, out List<Pawn> slaves, out List<Pawn> animals, out List<Pawn> mechanoids, out List<Pawn> downedEnemies, out List<Pawn> others)
        {
            colonists = new List<Pawn>();
            prisoners = new List<Pawn>();
            slaves = new List<Pawn>();
            animals = new List<Pawn>();
            mechanoids = new List<Pawn>();
            downedEnemies = new List<Pawn>();
            others = new List<Pawn>();
            foreach (Pawn p in selectedPawn.Map.mapPawns.AllPawnsSpawned.Where(x => x != selectedPawn))
            {
                if (!p.IsPlayerPawn() && !(p.health.Downed && p.RaceProps.Humanlike)) { continue; }
                if (!p.IsOnahole())
                {
                    switch (p.PawnCategory())
                    {
                        case PawnCategory.Colonist:
                            colonists.Add(p);
                            break;
                        case PawnCategory.Prisoner:
                            prisoners.Add(p);
                            break;
                        case PawnCategory.Slave:
                            slaves.Add(p);
                            break;
                        case PawnCategory.Animal:
                            animals.Add(p);
                            break;
                        case PawnCategory.Mechanoid:
                            mechanoids.Add(p);
                            break;
                        case PawnCategory.DownedEnemy:
                            downedEnemies.Add(p);
                            break;
                        default:
                            others.Add(p);
                            break;
                    }
                }
            }
            colonists = colonists.OrderBy(x => x.LabelShort).ToList();
            prisoners = prisoners.OrderBy(x => x.LabelShort).ToList();
            slaves = slaves.OrderBy(x => x.LabelShort).ToList();
            animals = animals.OrderBy(x => x.LabelShort).ToList();
            mechanoids = mechanoids.OrderBy(x => x.LabelShort).ToList();
        }
        private void BindablePawnOptions(List<Pawn> list, Pawn selectedPawn)
        {
            List<FloatMenuOption> options = new List<FloatMenuOption>();
            foreach (Pawn bindablePawn in list)
            {
                if (bindablePawn == null) { continue; }
                Action action = delegate ()
                {
                    if (selectedPawn.CanReserveAndReach(bindablePawn, PathEndMode.ClosestTouch, Danger.Deadly, 1, -1, null, true))
                    {
                        Job job = JobMaker.MakeJob(OnaholeDefOf.BindOnahole, bindablePawn, this);
                        job.count = 1;
                        selectedPawn.jobs.TryTakeOrderedJob(job, new JobTag?(JobTag.Misc), false);
                        selectedPawn.mindState.ResetLastDisturbanceTick();
                    }
                };
                options.Add(new FloatMenuOption(Tr.Bind + bindablePawn.GuestStatusLabel().InBrackets() + ": " + bindablePawn.LabelShort, action, MenuOptionPriority.High, null, null, 0f, null, null, true, 0));
            }
            FloatMenu floatMenu = new FloatMenu(options);
            Find.WindowStack.Add(floatMenu);
        }
        public void Notify_ItemAdded(Thing item)
        {
        }
        public void Notify_ItemRemoved(Thing item)
        {
            // Reset last haul tick if the container is empty
            if (this.innerContainer.Count == 0)
            {
                this.lastHaulTick = Find.TickManager.TicksGame;
                this.allowHaul = false;
                this.Map.haulDestinationManager.AllHaulSourcesListForReading.Remove(this);
            }
        }
    }
}


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

namespace RJW_Onahole.Comps
{
    public class CompAnalSexMachine : CompOnaholeBase, ICompAddsHediff, IModularPower
    {
        public override ResearchProjectDef RequiredResearch => DefDatabase<ResearchProjectDef>.GetNamed("Electricity", true);
        public List<HediffDef> GetHediffDef() => new List<HediffDef> { OnaholeDefOf.OnaholeAnalSexMachine };
        public void Notify_PawnGraphicsChange() { }
        public void Notify_PowerStateChanged(bool state) { }

        public int TextureIndex { get => 0; set { } }
        public float GetPowerConsumption()
        {
            if (this.Enabled && Bed.Pawn != null)
            {
                return 10f;
            }
            return 0f;
        }

        public bool Enabled
        {
            get => _analSexMachine;
            set
            {
                _analSexMachine = value;
                if (!value)
                {
                    foreach (Pawn pawn in Bed.OwnersForReading)
                    {
                        if (pawn.health.hediffSet.HasHediff(OnaholeDefOf.OnaholeAnalSexMachine))
                        {
                            pawn.health.RemoveHediff(pawn.health.hediffSet.GetFirstHediffOfDef(OnaholeDefOf.OnaholeAnalSexMachine));
                            pawn.Drawer.renderer.SetAllGraphicsDirty();
                        }
                    }
                }
                else
                {
                    foreach (Pawn pawn in Bed.OwnersForReading)
                    {
                        if (!pawn.health.hediffSet.HasHediff(OnaholeDefOf.OnaholeAnalSexMachine) && Genital_Helper.has_anus(pawn))
                        {
                            pawn.health.AddHediff(OnaholeDefOf.OnaholeAnalSexMachine);
                            pawn.Drawer.renderer.SetAllGraphicsDirty();
                        }
                    }
                }
            }
        }
        private bool _analSexMachine = false;

        public override void PostExposeData()
        {
            base.PostExposeData();
            Scribe_Values.Look(ref _analSexMachine, "analSexMachineEnabled", false);
        }
        public void Notify_PawnBind(Pawn pawn)
        {
            if (Enabled && Genital_Helper.has_anus(pawn))
            {
                pawn.health.TryAddHediffOfDef(OnaholeDefOf.OnaholeAnalSexMachine);
            }
        }
        public void Notify_PawnUnbind(Pawn pawn)
        {
            pawn.health.TryRemoveHediffOfDef(OnaholeDefOf.OnaholeAnalSexMachine);
        }
        protected override IEnumerable<Gizmo> GetGizmos()
        {
            yield return ToggleAnalSexMachine();
        }

        private Command_Toggle ToggleAnalSexMachine()
        {
            return new Command_Toggle
            {
                defaultLabel = Tr.AnalSexMachine,
                defaultDesc = Tr.AnalSexMachine_Desc,
                isActive = () => this.Enabled,
                icon = CacheUtility.Tex.IconAnalSexMachine,
                toggleAction = delegate
                {
                    this.Enabled = !this.Enabled;
                }
            };
        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using rjw;
using RJW_Onahole.Data;
using RJW_Onahole.HarmonyPatches;
using RJW_Onahole.Hediffs;
using RJW_Onahole.Jobs;
using RJW_Onahole.UI;
using RJW_Onahole.Utilities;
using UnityEngine;
using Verse;
namespace RJW_Onahole.Comps
{
    public class CompAnusCrossSection : CompCrossSectionBase
    {
        private static Vector3 additionalOffset = new Vector3(0.91f, 0, 0);
        public override int DrawMatchedGraphic()
        {
            int i = -1;
            if (!Genital_Helper.has_anus(Bed.Pawn)) { return i; }
            Pawn partner = Bed.Pawn?.OnaholeUser();
            SexProps partnerSexProps = (partner?.jobs.curDriver as JobDriver_UseOnahole)?.Sexprops;
            bool isInPosition = partner != null && partner.Position == Bed.Pawn.Position;
            bool isReverse = partnerSexProps?.dictionaryKey.defName.Contains("Reverse") ?? false;
            if (partnerSexProps?.sexType == xxx.rjwSextype.Anal && isInPosition && !isReverse)
            {
                // Sex
                i = DrawAnal();
            }
            else if (partnerSexProps?.sexType == xxx.rjwSextype.Rimming && isInPosition && isReverse)
            {
                // Cunnilingus
                i = DrawRimming();
            }
            else
            {
                // Machine
                Hediff hediff = null;
                Bed.Pawn?.health.hediffSet.TryGetHediff(OnaholeDefOf.OnaholeAnalSexMachine, out hediff);
                if (hediff != null)
                {
                    HediffComp_SexMachines sexMachine = hediff.TryGetComp<HediffComp_SexMachines>();
                    if (sexMachine != null)
                    {
                        i = PositionUtility.CalcCurFrame(sexMachine.startTime, 16, 16);
                    }
                    i = DrawInstancedAnimation(OnaholeDefOf.PistonCrossSection, index: i, additionalOffset: additionalOffset);
                }
            }
            return i;
        }
        public override void DrawComponents(int i)
        {
            DrawGraphic(0);
            DrawInstancedAnimation(OnaholeDefOf.AnusCrossSection, index: i);
        }
        protected virtual int DrawAnal()
        {
            int i = TrySyncWithPawnAnimation(Bed.Pawn.OnaholeUser(), OnaholeDefOf.PenisCrossSection.totalFrames);
            if (Bed.Pawn.RaceProps.Insect)
            {
                i = DrawInstancedAnimation(OnaholeDefOf.InsectPenisCrossSection, index: i, additionalOffset: additionalOffset);
            }
            else if (Bed.Pawn.RaceProps.IsMechanoid)
            {
                i = DrawInstancedAnimation(OnaholeDefOf.MechPenisCrossSection, index: i, additionalOffset: additionalOffset);
            }
            else
            {
                i = DrawInstancedAnimation(OnaholeDefOf.PenisCrossSection, index: i, additionalOffset: additionalOffset);
            }
            return i;
        }
        protected virtual int DrawRimming()
        {
            DrawAnimatedTextureGroup("Tongue", -1);
            return 0;
        }
    }
}


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

namespace RJW_Onahole.Comps
{
    public class CompProperties_AssignableToPawn_OnaholeBed : CompProperties_AssignableToPawn
    {
        public CompProperties_AssignableToPawn_OnaholeBed()
        {
            compClass = typeof(CompAssignableToPawn_OnaholeBed);
            drawAssignmentOverlay = false;
        }
    }
    public class CompAssignableToPawn_OnaholeBed : CompAssignableToPawn
    {
        protected override bool ShouldShowAssignmentGizmo()
        {
            return false;
        }
        public override AcceptanceReport CanAssignTo(Pawn pawn)
        {
            return AcceptanceReport.WasAccepted;
        }
        public override IEnumerable<Pawn> AssigningCandidates
        {
            get
            {
                if (!parent.Spawned)
                {
                    return Enumerable.Empty<Pawn>();
                }
                return parent.Map.mapPawns.AllPawns.Where(p => p.IsPlayerPawn());
            }
        }
        public override bool AssignedAnything(Pawn pawn)
        {
            return pawn.ownership.OwnedBed != null;
        }

        public override void TryAssignPawn(Pawn pawn)
        {
            Building_Bed building_Bed = (Building_Bed)parent;
            pawn.ownership.ClaimBedIfNonMedical(building_Bed);
            building_Bed.NotifyRoomAssignedPawnsChanged();
            uninstalledAssignedPawns.Remove(pawn);
        }

        public override void TryUnassignPawn(Pawn pawn, bool sort = true, bool uninstall = false)
        {
            Building_Bed ownedBed = pawn.ownership.OwnedBed;
            pawn.ownership.UnclaimBed();
            ownedBed?.NotifyRoomAssignedPawnsChanged();
            if (uninstall && !uninstalledAssignedPawns.Contains(pawn))
            {
                uninstalledAssignedPawns.Add(pawn);
            }
        }

        protected override bool CanSetUninstallAssignedPawn(Pawn pawn)
        {
            if (pawn != null && !AssignedAnything(pawn) && (bool)CanAssignTo(pawn))
            {
                if (!pawn.IsPrisonerOfColony)
                {
                    return pawn.IsColonist;
                }

                return true;
            }
            return false;
        }

        public override void PostExposeData()
        {
            base.PostExposeData();
            if (Scribe.mode == LoadSaveMode.PostLoadInit && assignedPawns.RemoveAll((Pawn x) => x.ownership.OwnedBed != parent) > 0)
            {
                Log.Warning(parent.ToStringSafe() + " had pawns assigned that don't have it as an assigned bed. Removing.");
            }
        }
    }

}


code:
using System.Collections.Generic;
using RimWorld;
using Verse;
using rjw;
using RJW_Onahole.Utilities;
using Verse.AI;
using RJW_Onahole.Interfaces;
using UnityEngine;
using RJW_Onahole.UI;
using System.Linq;

namespace RJW_Onahole.Comps
{
    public class CompBondage : CompOnaholeBase, ICompAddsHediff
    {
        public List<HediffDef> GetHediffDef() => new List<HediffDef> { OnaholeDefOf.OnaholeBond };
        public void Notify_PawnGraphicsChange()
        {
            Bed.Pawn?.Drawer.renderer.SetAllGraphicsDirty();
        }
        public int TextureIndex { get => bondTextureIndex; set { bondTextureIndex = value; } }
        public int bondTextureIndex = 0;

        public override void PostExposeData()
        {
            base.PostExposeData();
            Scribe_Values.Look(ref bondTextureIndex, "bondTextureIndex", 0);
        }
        public void Notify_PawnBind(Pawn pawn)
        {
            pawn.health.TryAddHediffOfDef(OnaholeDefOf.OnaholeBond);
        }
        public void Notify_PawnUnbind(Pawn pawn)
        {
            pawn.health.TryRemoveHediffOfDef(OnaholeDefOf.OnaholeBond);
        }
        protected override IEnumerable<Gizmo> GetGizmos()
        {
            yield return new Gizmo_SelectTexture(this, this.GetHediffDef().First().RenderNodeProperties.First());
        }
        public override void CompTickRare()
        {
            // Slave Suppression
            if (!ModsConfig.IdeologyActive) { return; }
            if (this.parent.Faction?.IsPlayer != true) { return; }
            if (this.Bed.BoundPawn == null) { return; }
            if (this.Bed.BoundPawn.needs.TryGetNeed<Need_Suppression>() is Need_Suppression suppression)
            {
                suppression.CurLevel += 0.05f;
            }
            base.CompTickRare();
        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using RJW_Onahole.Data;
using RJW_Onahole.UI;
using RJW_Onahole.Utilities;
using UnityEngine;
using Verse;
namespace RJW_Onahole.Comps
{
    public enum AnimateMode
    {
        Off,
        Loop,
        BackAndForth,
        Random
    }
    public class CompProperties_ConditionalBuildingOverlay : CompProperties
    {
        //TODO use comp properties to load research in xml
        public AltitudeLayer altitudeLayer;
        public List<GraphicData> graphicDatas;
        public List<AnimatedTextureGroupData> animatedTextureGroups;
    }
    public abstract class CompOnaholeBase : ThingComp
    {
        public Building_OnaholeBed Bed => this.parent as Building_OnaholeBed;
        public virtual ResearchProjectDef RequiredResearch => null;
        public override IEnumerable<Gizmo> CompGetGizmosExtra()
        {
            if (this.parent.Faction?.IsPlayer != true) { yield break; }
            if (this.RequiredResearch?.IsFinished == false) { yield break; }
            foreach (Gizmo gizmo in GetGizmos())
            {
                yield return gizmo;
            }
        }
        protected virtual IEnumerable<Gizmo> GetGizmos() => Enumerable.Empty<Gizmo>();
    }
    public class CompConditionalBuildingOverlay : CompOnaholeBase
    {
        protected CompProperties_ConditionalBuildingOverlay Props => (CompProperties_ConditionalBuildingOverlay)props;
        protected CompModularPower ModularPower => this.parent.GetComp<CompModularPower>();
        protected Dictionary<InstancedAnimationDef, string> animationDefToKey = new Dictionary<InstancedAnimationDef, string>();
        public AnimatedTextureGroupCollection GroupCollection;
        public override void PostSpawnSetup(bool respawningAfterLoad)
        {
            base.PostSpawnSetup(respawningAfterLoad);
            GroupCollection = new AnimatedTextureGroupCollection(Props.animatedTextureGroups);
            if (this.Bed == null)
            {
                "Comp_ConditionalBuildingOverlay must be attached to a Building_OnaholeBed".LogError();
            }
        }
        protected virtual void DrawGraphic(int i, Color? color = null)
        {
            Graphic graphic = Props.graphicDatas[i].Graphic;
            DrawGraphic(graphic, color);
        }
        public virtual void DrawGraphic(Graphic graphic, Color? color = null)
        {
            if (color != null)
            {
                graphic = graphic.GetColoredVersion(graphic.Shader, color.Value, color.Value);
            }
            graphic.Draw(this.parent.DrawPos.WithY(Props.altitudeLayer.AltitudeFor()), Rot4.North, this.parent);
        }
        public virtual void DrawAnimatedTextureGroup(string label, int i = -1, Color? color = null)
        {
            GroupCollection.DrawGroup(label, this.parent.DrawPos.WithY(Props.altitudeLayer.AltitudeFor()), this.parent, i, color);
        }
        public virtual int DrawInstancedAnimation(InstancedAnimationDef def, int startingIndex = 0, Color? color = null, float overrideInterval = -1f, int index = -1, Vector3 additionalOffset = default)
        {
            string originalKey = animationDefToKey.TryGetValue(def, out originalKey) ? originalKey : null;
            string key;
            Matrix4x4 transform = Matrix4x4.TRS(parent.DrawPos.WithY(Props.altitudeLayer.AltitudeFor()) + def.drawOffset + additionalOffset, Quaternion.identity, new Vector3(def.drawSize.x, 1, def.drawSize.y));
            if (index >= 0)
            {
                key = def.Controller.DrawFrame(transform, originalKey, index, color);
            }
            else
            {
                key = def.Controller.Draw(transform, originalKey, startingIndex, overrideInterval, color);
            }
            if (key != originalKey)
            {
                animationDefToKey[def] = key;
            }
            return def.Controller.GetCurFrame(key);
        }
    }

}


code:
using System.Collections.Generic;
using RimWorld;
using Verse;
using rjw;
using RJW_Onahole.Utilities;
using Verse.AI;
using RJW_Onahole.Interfaces;
using UnityEngine;
using RJW_Onahole.Data;

namespace RJW_Onahole.Comps
{
    public abstract class CompCrossSectionBase : CompConditionalBuildingOverlay
    {
        public override void PostDraw()
        {
            base.PostDraw();
            if (!RJW_Onahole_Settings.showCrossSections || Bed.Pawn == null || !Bed.Pawn.RaceProps.Humanlike) { return; }
            int OrganTextureIndex = DrawMatchedGraphic();
            if (OrganTextureIndex >= 0)
            {
                this.DrawComponents(OrganTextureIndex);
            }
        }

        /// <summary>
        /// Draw matched graphic, if any match is found, i should be >= 0, thus display the background and other accessories
        /// </summary>
        /// <returns></returns>
        public abstract int DrawMatchedGraphic();
        /// <summary>
        /// Draw backgrounds and other accessories should be shown if is drawn
        /// </summary>
        /// <param name="textureIndex"></param>
        public abstract void DrawComponents(int textureIndex);
        protected virtual int TrySyncWithPawnAnimation(Pawn pawn, int totalFrames)
        {
            int i = -1;
            if (pawn?.Drawer.renderer.CurAnimation != null)
            {
                PawnRenderTree renderTree = pawn.Drawer.renderer.renderTree;
                i = Mathf.FloorToInt(totalFrames * ((float)renderTree.AnimationTick) / ((float)renderTree.currentAnimation.durationTicks));
                if (i >= totalFrames) { i = totalFrames - 1; }
            }
            return i;
        }
    }
}