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

namespace RJW_Onahole.UI
{
    public class Window_SetTransform : Window_DynamicMultiselect<Pawn>
    {
        private TransformCollection transform;
        private int selectedHediffIndex = 0;
        private string raceName;
        private string raceDefName;
        private const float LineHeight = 32;
        private Pawn pawn;
        private bool isPawn;
        private CompPawnTransform compPawnTransform;

        protected override void Notify_SelectionChanged()
        {
            if (pawn != null && selectedThings.FirstOrDefault() is Pawn p && p != pawn && p.IsOnahole())
            {
                this.ApplyChanges();
                this.InitPawn(p);
            }
        }
        public Window_SetTransform(Pawn pawn) : base()
        {
            this.InitPawn(pawn);
        }
        public Window_SetTransform(string raceName, string raceDefName = "") : base()
        {
            this.raceName = raceName;
            this.raceDefName = raceDefName;
            this.InitRace();
        }
        private void InitPawn(Pawn pawn)
        {
            if (pawn == null) { return; }
            this.pawn = pawn;
            this.raceName = pawn?.kindDef?.race?.label;
            this.raceDefName = pawn?.kindDef?.race?.defName;
            isPawn = true;
            compPawnTransform = pawn.TryGetComp<CompPawnTransform>();
            if (compPawnTransform.transformCollection == null)
            {
                compPawnTransform.transformCollection = ConfigUtility.GetRaceTransformCollectionValue(raceDefName);
            }
            transform = compPawnTransform.transformCollection;
            this.InitTransform();
        }
        private void InitRace()
        {
            if (raceDefName != "")
            {
                if (!RJW_Onahole_Settings.racesToTransforms.ContainsKey(raceDefName))
                {
                    RJW_Onahole_Settings.racesToTransforms.Add(raceDefName, ConfigUtility.GetDefaultTransform(raceDefName));
                }
                transform = RJW_Onahole_Settings.racesToTransforms[raceDefName];
                isPawn = false;
            }
            this.InitTransform();
        }
        private void InitTransform()
        {
            if (transform == null)
            {
                transform = RJW_Onahole_Settings.DefaultTransform;
            }
            // Remove any hediffs that are not in the config
            List<string> keysToRemove = new List<string>();
            foreach (string hediffDefName in transform.hediffsToTransforms.Keys)
            {
                if (!ConfigUtility.OnaholeHediffs.Contains(DefDatabase<HediffDef>.GetNamed(hediffDefName)))
                {
                    keysToRemove.Add(hediffDefName);
                }
            }
            foreach (string key in keysToRemove)
            {
                transform.hediffsToTransforms.Remove(key);
            }
            // Add any hediffs that are in the config but not in the transform
            foreach (HediffDef hediffDef in ConfigUtility.OnaholeHediffs)
            {
                if (!transform.hediffsToTransforms.ContainsKey(hediffDef.defName))
                {
                    transform.hediffsToTransforms.Add(hediffDef.defName, ConfigUtility.RefRaceTransformData(raceDefName, hediffDef));
                }
            }
            this.doCloseButton = true;
        }
        private void ApplyChanges()
        {
            RJW_Onahole_Mod.Settings.Write();
        }
        public override void PostClose()
        {
            base.PostClose();
            this.ApplyChanges();
        }
        public override void DoWindowContents(Rect inRect)
        {
            float y = 0;
            Widgets.Label(new Rect(0, 0, inRect.width, LineHeight).LeftHalf(), Tr.EditTransform + ":");
            if (isPawn)
            {
                // Click to toggle to race
                if (Widgets.ButtonText(new Rect(0, 0, inRect.width, LineHeight).RightHalf(), pawn.LabelShort))
                {
                    compPawnTransform.transformCollection = null;
                    this.InitRace();
                }
            }
            else if (pawn != null)
            {
                // Click to toggle to pawn
                if (Widgets.ButtonText(new Rect(0, 0, inRect.width, LineHeight).RightHalf(), raceName.Translate().CapitalizeFirst()))
                {
                    this.InitPawn(pawn);
                }
            }
            else
            {
                // Draw a label of the race
                Widgets.Label(new Rect(0, 0, inRect.width, LineHeight).RightHalf(), raceName.Translate().CapitalizeFirst());
            }
            y += LineHeight;
            Widgets.Label(new Rect(0, y, inRect.width, LineHeight).LeftHalf(), OTr.Choose + " Hediff");
            Widgets.Dropdown(new Rect(0, y, inRect.width, LineHeight).RightHalf(), selectedHediffIndex, (int i) => ConfigUtility.OnaholeHediffs[i].label.Translate(), (int i) => GetDropdownMenuElements(), ConfigUtility.OnaholeHediffs[selectedHediffIndex].label.Translate());
            y += LineHeight;
            UIWidgets.LabeledSliderNumericDisplay(new Rect(0, y, inRect.width, LineHeight * 2), "X " + Tr.Offset, ref transform.hediffsToTransforms[ConfigUtility.OnaholeHediffs[selectedHediffIndex].defName].Separation, -1, 1);
            y += LineHeight * 2;
            UIWidgets.LabeledSliderNumericDisplay(new Rect(0, y, inRect.width, LineHeight * 2), "Z " + Tr.Offset, ref transform.hediffsToTransforms[ConfigUtility.OnaholeHediffs[selectedHediffIndex].defName].Offset.z, -1, 1);
            y += LineHeight * 2;
            UIWidgets.LabeledSliderNumericDisplay(new Rect(0, y, inRect.width, LineHeight * 2), "X " + Tr.Scale, ref transform.hediffsToTransforms[ConfigUtility.OnaholeHediffs[selectedHediffIndex].defName].Scale.x, 0, 2);
            y += LineHeight * 2;
            UIWidgets.LabeledSliderNumericDisplay(new Rect(0, y, inRect.width, LineHeight * 2), "Z " + Tr.Scale, ref transform.hediffsToTransforms[ConfigUtility.OnaholeHediffs[selectedHediffIndex].defName].Scale.z, 0, 2);
            y += LineHeight * 2;
            if (Widgets.ButtonText(new Rect(0, y, inRect.width, LineHeight).LeftHalf(), OTr.Reset))
            {
                if (raceDefName == "")
                {
                    this.transform.hediffsToTransforms[ConfigUtility.OnaholeHediffs[selectedHediffIndex].defName].CopyFrom(ConfigUtility.GetDefaultTransform(raceDefName).hediffsToTransforms[ConfigUtility.OnaholeHediffs[selectedHediffIndex].defName]);
                }
                else
                {
                    this.transform.hediffsToTransforms[ConfigUtility.OnaholeHediffs[selectedHediffIndex].defName].CopyFrom(ConfigUtility.GetDefaultTransform(raceDefName).hediffsToTransforms[ConfigUtility.OnaholeHediffs[selectedHediffIndex].defName]);
                }
            }
            if (Widgets.ButtonText(new Rect(0, y, inRect.width, LineHeight).RightHalf(), OTr.ResetAll))
            {
                if (raceDefName == "")
                {
                    this.transform.CopyFrom(ConfigUtility.GetDefaultTransform(raceDefName));
                }
                else
                {
                    this.transform.CopyFrom(ConfigUtility.GetDefaultTransform(raceDefName));
                }
            }
        }
        public IEnumerable<Widgets.DropdownMenuElement<TaggedString>> GetDropdownMenuElements()
        {
            foreach (HediffDef hediffDef in ConfigUtility.OnaholeHediffs)
            {
                yield return new Widgets.DropdownMenuElement<TaggedString>
                {
                    option = new FloatMenuOption(hediffDef.label.Translate().CapitalizeFirst(), () => selectedHediffIndex = ConfigUtility.OnaholeHediffs.IndexOf(hediffDef)),
                    payload = hediffDef.label
                };
            }
        }

    }
}


code:
using Verse;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using HarmonyLib;
using System.Diagnostics;
using JetBrains.Annotations;
using UnityEngine;
using RJW_Onahole.Data;
using rjw;

namespace RJW_Onahole.Utilities;
[StaticConstructorOnStartup]
public static class CacheUtility
{

    public static IEnumerable<ThingDef> humanLikeDefs = (from def in DefDatabase<PawnKindDef>.AllDefs where def.RaceProps.Humanlike select def.race).Distinct().OrderBy((ThingDef def) => def.defName);
    public static IEnumerable<ThingDef> foodDefs = DefDatabase<ThingDef>.AllDefsListForReading.Where(x => x.IsNutritionGivingIngestible && !x.IsCorpse && !x.IsPlant);
    public static IEnumerable<ThingDef> drugDefs = DefDatabase<ThingDef>.AllDefsListForReading.Where(x => x.ingestible != null && (x.ingestible.drugCategory != DrugCategory.None || DrugStatsUtility.GetChemical(x) != null));

    static CacheUtility()
    {
        // Ensure all patches are complete. Including C# ones run at static constructor
        EventUtility.OnPostLoadLong += LateInit;
    }
    private static void LateInit()
    {
        ThingsToMaterials(); //Icons
        CollectableFilthDefs(); //Filth
    }
    private static readonly Dictionary<ThingDef, Material> thingsToMaterials = new Dictionary<ThingDef, Material>();

    private static void ThingsToMaterials()
    {
        HashSet<ThingDef> defs = new HashSet<ThingDef>();
        defs.AddRange(foodDefs);
        defs.AddRange(drugDefs);
        foreach (ThingDef def in defs)
        {
            if (def.uiIcon == null) { continue; }
            thingsToMaterials[def] = MaterialPool.MatFrom(def.uiIcon);
        }
    }

    public static Material MatFor(ThingDef def)
    {
        if (!thingsToMaterials.ContainsKey(def)) { thingsToMaterials[def] = MaterialPool.MatFrom(def.uiIcon); }
        return thingsToMaterials[def];
    }

    public static Dictionary<ThingDef, SexFluidDef> filthDefsToSexFluidDefs = new Dictionary<ThingDef, SexFluidDef>();
    public static HashSet<SexFluidDef> sexFluidDefs = new HashSet<SexFluidDef>();
    public static HashSet<SexFluidDef> usefulSexFluidDefs = new HashSet<SexFluidDef>();
    private static void CollectableFilthDefs()
    {
        foreach (SexFluidDef sexFluidDef in DefDatabase<SexFluidDef>.AllDefsListForReading)
        {
            if (DefDatabase<SexFluidBlacklistDef>.GetNamedSilentFail(sexFluidDef.defName) != null) { continue; }
            sexFluidDefs.Add(sexFluidDef);
            if (sexFluidDef.CanAutoConvert() || sexFluidDef.CanFeed()) { usefulSexFluidDefs.Add(sexFluidDef); }
            // Any def with a defined filth def will be collectable
            if (sexFluidDef.filth == null) { continue; }
            filthDefsToSexFluidDefs.Add(sexFluidDef.filth, sexFluidDef);
        }
        foreach (FilthToSexFluidDef filthToSexFluidDef in DefDatabase<FilthToSexFluidDef>.AllDefsListForReading)
        {
            ThingDef filth = DefDatabase<ThingDef>.GetNamedSilentFail(filthToSexFluidDef.defName);
            if (filth == null || filthToSexFluidDef.sexFluid == null) { continue; }
            // Priority is lower than filth defined in SexFluidDef
            if (!filthDefsToSexFluidDefs.ContainsKey(filth)) { filthDefsToSexFluidDefs.Add(filth, filthToSexFluidDef.sexFluid); }
        }
    }

    [StaticConstructorOnStartup]
    public static class Tex
    {
        public static Texture2D IconBreastElectrodes = ContentFinder<Texture2D>.Get("Onahole/Icons/BreastElectrodes");
        public static Texture2D IconGenitalElectrodes = ContentFinder<Texture2D>.Get("Onahole/Icons/GenitalElectrodes");
        public static Texture2D IconCumCollector = ContentFinder<Texture2D>.Get("Onahole/Icons/CumCollector");
        public static Texture2D IconAnalSexMachine = ContentFinder<Texture2D>.Get("Onahole/Icons/AnalSexMachine");
        public static Texture2D IconVaginalSexMachine = ContentFinder<Texture2D>.Get("Onahole/Icons/VaginalSexMachine");
        public static Texture2D IconMilkingMachine = ContentFinder<Texture2D>.Get("Onahole/Icons/MilkingMachine");
        public static Texture2D IconShower = ContentFinder<Texture2D>.Get("Onahole/Icons/Shower", false);
        public static Texture2D IconOptions = ContentFinder<Texture2D>.Get("Onahole/Icons/Configure");
        public static Texture2D IconPrice = ContentFinder<Texture2D>.Get("ui/commands/trade");
        public static Texture2D IconOptionsVideo = ContentFinder<Texture2D>.Get("ui/icons/options/optionsvideo");
        public static Texture2D IconFood = ContentFinder<Texture2D>.Get("things/mote/thoughtsymbol/food");
        public static Texture2D IconDrug = ContentFinder<Texture2D>.Get("ui/commands/takedrug");
        public static Texture2D IconAssignOwner = ContentFinder<Texture2D>.Get("ui/commands/assignowner");
        public static Texture2D IconOpen = ContentFinder<Texture2D>.Get("ui/designators/open");
        public static Texture2D IconSelection = ContentFinder<Texture2D>.Get("ui/overlays/selectionbracketwhole");
        public static Texture2D IconSetLevel = ContentFinder<Texture2D>.Get("Onahole/Icons/SetLevel");
        public static Texture2D IconAutoLoad = ContentFinder<Texture2D>.Get("ui/commands/loadtransporter");
        public static Texture2D CheckOn = ContentFinder<Texture2D>.Get("UI/Widgets/CheckOn");
        public static Texture2D CheckOff = ContentFinder<Texture2D>.Get("UI/Widgets/CheckOff");
        public static Texture2D Transparent = ContentFinder<Texture2D>.Get("things/mote/transparent");
        public static Texture2D Cancel = ContentFinder<Texture2D>.Get("ui/designators/cancel");
        public static Texture2D Off = ContentFinder<Texture2D>.Get("ui/overlays/selfshutdown");
        public static Texture2D Low = ContentFinder<Texture2D>.Get("ui/commands/templower");
        public static Texture2D Medium = ContentFinder<Texture2D>.Get("ui/commands/tempreset");
        public static Texture2D High = ContentFinder<Texture2D>.Get("ui/commands/tempraise");
        public static Texture2D TargetArrow = ContentFinder<Texture2D>.Get("UI/Misc/BarInstantMarkerRotated");
    }
    [StaticConstructorOnStartup]
    public static class Graphic
    {
        public static Dictionary<string, Graphic_Single> forPath = new Dictionary<string, Graphic_Single>();
        static Graphic()
        {
            // Load graphics after Graphic Database and Shader Database has finished initialization\
            EventUtility.OnPostLoadLong += InitGraphics;
        }
        private static void InitGraphics()
        {
            foreach (HediffDef hediffDef in ConfigUtility.OnaholeHediffs)
            {
                if (!hediffDef.RenderNodeProperties.NullOrEmpty())
                {
                    foreach (PawnRenderNodeProperties renderNodeProperty in hediffDef.RenderNodeProperties)
                    {
                        if (renderNodeProperty.texPaths.NullOrEmpty()) { continue; }
                        foreach (string path in renderNodeProperty.texPaths)
                        {
                            if (forPath.ContainsKey(path)) { continue; }
                            "Cached graphic {0}, with shader {1}".Log(path, (renderNodeProperty?.shaderTypeDef ?? OnaholeDefOf.InheritMaskAlpha).defName);
                            forPath[path] = (Graphic_Single)GraphicDatabase.Get<Graphic_Single>(path, renderNodeProperty.shaderTypeDef?.Shader ?? OnaholeDefOf.InheritMaskAlpha.Shader, Vector2.one, Color.white, 0);
                        }
                    }
                }
            }
        }
    }
    [StaticConstructorOnStartup]
    public static class ColorMat
    {
        public static Color FeederColor = new(0.8f, 0.6f, 0.2f);
        public static Material FeederDefault = SolidColorMaterials.SimpleSolidColorMaterial(FeederColor);
        public static Color InfuserColor = new(0.6f, 0f, 0.42f);
        public static Material InfuserDefault = SolidColorMaterials.SimpleSolidColorMaterial(InfuserColor);
        public static Dictionary<Color, Texture2D> cachedColorTextures = new Dictionary<Color, Texture2D>();
        public static Texture2D ColorTex(Color color)
        {
            if (cachedColorTextures.TryGetValue(color, out Texture2D texture2D)) { return texture2D; }
            texture2D = SolidColorMaterials.NewSolidColorTexture(color);
            cachedColorTextures[color] = texture2D;
            return texture2D;
        }
    }
}

repo_name: atainer/rimjobworld

license: mit


code:
using Verse;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using HarmonyLib;
using System.Diagnostics;
using Verse.AI;
using UnityEngine;

namespace RJW_Onahole.Utilities
{
    [StaticConstructorOnStartup]
    public static class CompatibilityUtility
    {
        public static HashSet<Type> AllMilkableComps
        {
            get
            {
                if (_allMilkableComps != null && _allMilkableComps.Count() >= 1) { return _allMilkableComps; }
                _allMilkableComps = new HashSet<Type>();
                Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
                foreach (Assembly assembly in assemblies)
                {
                    try
                    {
                        foreach (Type thingComp in assembly.GetTypes().Where(t =>
                        t.IsClass && !t.IsAbstract
                        && t.IsSubClassesOf(typeof(ThingComp))))
                        {

                            // Look for the Props and check if it has milkDef or milkAmount
                            PropertyInfo prop = null;
                            try { prop = thingComp.GetProperty("Props"); } catch { continue; }
                            if (prop == null) { continue; }
                            Type propertyType = prop.PropertyType;
                            if (propertyType.Field("milkDef") != null && propertyType.Field("milkAmount") != null)
                            {
                                _allMilkableComps.Add(thingComp);
                            }
                        }
                    }
                    catch (ReflectionTypeLoadException e)
                    {
                        ("Error loading types from assembly: " + assembly.FullName).LogError();
                        foreach (Exception ex in e.LoaderExceptions)
                        {
                            ex.ToString().LogError();
                        }
                    }
                }
                "Milkable comps found: {0}".Log(string.Join(", ", _allMilkableComps.Select(t => t.Name)));
                return _allMilkableComps;
            }
        }
        private static HashSet<Type> _allMilkableComps = new HashSet<Type>();
        public static bool HasMilkableComp(this Pawn pawn, out ThingDef milkDef, out float milkAmount, out ThingComp comp, out FieldInfo fullnessFieldInfo, out bool isActive)
        {
            milkDef = null;
            milkAmount = 0;
            float milkAmountBase = 0;
            comp = null;
            fullnessFieldInfo = null;
            isActive = false;
            foreach (Type type in AllMilkableComps)
            {
                comp = pawn.AllComps.FirstOrDefault(c => c.GetType() == type);
                if (comp != null)
                {
                    isActive = type.PropertyGetter("Active")?.Invoke(comp, null) as bool? ?? false;
                    if (!isActive) { continue; }
                    milkDef = type.PropertyGetter("ResourceDef")?.Invoke(comp, null) as ThingDef;
                    if (milkDef == null) { continue; }
                    object milkAmountObj = type.PropertyGetter("ResourceAmount")?.Invoke(comp, null);
                    milkAmount = milkAmountObj as float? ?? (float?)(milkAmountObj as int?) ?? 0f;
                    milkAmountBase = type.PropertyGetter("ResourceAmountBase")?.Invoke(comp, null) as float? ?? 0f; // RJW-MC
                    if (milkAmount > 0 || milkAmountBase > 0)
                    {
                        fullnessFieldInfo = type.FieldOrParentField("fullness");
                        milkAmount = Mathf.Max(milkAmount, milkAmountBase);
                        return true;
                    }
                }
            }
            return false;
        }
    }
}


code:
using UnityEngine;
using Verse;
using System.Collections.Generic;
using RJW_Onahole.Data;
using System.Linq;
using RJW_Onahole.Comps;

namespace RJW_Onahole.Utilities
{
    [StaticConstructorOnStartup]
    public static class ConfigUtility
    {
        public static TransformCollection RefPawnTransformCollection(Pawn pawn)
        {
            if (pawn.TryGetComp<CompPawnTransform>() is CompPawnTransform compPawnTransform)
            {
                return compPawnTransform.transformCollection;
            }
            return GetRaceTransformCollection(pawn.kindDef.race.defName);
        }
        public static TransformCollection GetPawnTransformCollection(Pawn pawn)
        {
            TransformCollection transformCollection = new TransformCollection();
            transformCollection.CopyFrom(RefPawnTransformCollection(pawn));
            return transformCollection;
        }
        public static TransformCollection GetRaceTransformCollection(string raceDefName)
        {
            if (RJW_Onahole_Settings.racesToTransforms?.TryGetValue(raceDefName, out TransformCollection transformCollection) == true)
            {
                return transformCollection;
            }
            return GetNewDefaultTransform();
        }
        public static TransformCollection GetRaceTransformCollectionValue(string raceDefName)
        {
            TransformCollection transformCollection = new TransformCollection();
            transformCollection.CopyFrom(GetRaceTransformCollection(raceDefName));
            return transformCollection;
        }

        /// <summary>
        /// Gets a copy of the transform data
        /// </summary>
        /// <param name="pawn"></param>
        /// <param name="hediff"></param>
        /// <returns>New TransformData obj with copied values</returns>
        public static TransformData GetPawnTransformData(Pawn pawn, Hediff hediff)
        {
            return GetPawnTransformData(pawn, hediff.def);
        }
        /// <summary>
        /// Gets a copy of the transform data
        /// </summary>
        /// <param name="pawn"></param>
        /// <param name="hediffDef"></param>
        /// <returns>New TransformData obj with copied values</returns>
        public static TransformData GetPawnTransformData(Pawn pawn, HediffDef hediffDef)
        {
            TransformData data = RefPawnTransformData(pawn, hediffDef);
            TransformData copiedData = new TransformData();
            copiedData.CopyFrom(data);
            return copiedData;
        }
        /// <summary>
        /// Used for config only
        /// </summary>
        /// <param name="pawn"></param>
        /// <param name="hediffDef"></param>
        /// <returns></returns>
        public static TransformData RefPawnTransformData(Pawn pawn, HediffDef hediffDef)
        {
            if (pawn.TryGetComp<CompPawnTransform>() is CompPawnTransform compPawnTransform)
            {
                if (compPawnTransform.transformCollection?.hediffsToTransforms.TryGetValue(hediffDef.defName, out TransformData transformData) == true)
                {
                    return transformData;
                }
            }
            return RefRaceTransformData(pawn.kindDef.race.defName, hediffDef);
        }
        /// <summary>
        /// Used for config only
        /// </summary>
        /// <param name="raceDefName"></param>
        /// <param name="hediffDef"></param>
        /// <returns></returns>
        public static TransformData RefRaceTransformData(string raceDefName, HediffDef hediffDef)
        {
            // Race Override
            if (RJW_Onahole_Settings.racesToTransforms?.TryGetValue(raceDefName, out TransformCollection transformCollection) == true)
            {
                if (transformCollection?.hediffsToTransforms.TryGetValue(hediffDef.defName, out TransformData transformData) == true)
                {
                    return transformData;
                }
            }

            // Race Def
            if (DefDatabase<OnaholeRaceTransformDef>.GetNamedSilentFail(raceDefName) is OnaholeRaceTransformDef raceOffsetDef)
            {
                if (raceOffsetDef.HediffToTransforms.TryGetValue(hediffDef.defName, out TransformData transformData))
                {
                    return transformData;
                }
            }

            // Default
            if (RJW_Onahole_Settings.DefaultTransform.hediffsToTransforms.TryGetValue(hediffDef.defName, out TransformData defaultTransformData) == true)
            {
                return defaultTransformData;
            }
            return null;
        }
        public static TransformCollection GetDefaultTransform(string defName)
        {
            if (DefDatabase<OnaholeRaceTransformDef>.GetNamedSilentFail(defName) is OnaholeRaceTransformDef raceOffsetDef)
            {
                return new TransformCollection(raceOffsetDef.HediffToTransforms);
            }
            return GetNewDefaultTransform();
        }
        public static TransformCollection GetNewDefaultTransform()
        {
            return new TransformCollection(DefDatabase<OnaholeRaceTransformDef>.GetNamed("Default").HediffToTransforms);
        }
        public static List<HediffDef> OnaholeHediffs = new List<HediffDef>
        {
            OnaholeDefOf.OnaholeBreastElectrodes,
            OnaholeDefOf.OnaholeGenitalElectrodes,
            OnaholeDefOf.OnaholeMilkingMachine,
            OnaholeDefOf.OnaholeVaginalSexMachine,
            OnaholeDefOf.OnaholeAnalSexMachine,
            OnaholeDefOf.OnaholeAutoFeeder,
            OnaholeDefOf.OnaholeBond
        };
        public static bool IsOnaholeHediff(Hediff hediff)
        {
            return OnaholeHediffs.Contains(hediff.def);
        }
    }
}


code:
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using Verse;
namespace RJW_Onahole.Utilities;

[StaticConstructorOnStartup]
public static class CurveDrawer
{
    static CurveDrawer()
    {
        EventUtility.OnPostDraw += DrawInstanced;
    }
    private static readonly Mesh densePlane10 = MeshUtility.PlaneMeshDense(10, 1);
    private static readonly Dictionary<Material, CurveData> materialToData = new();
    public static void DrawCurve(Vector3 p0, Vector3 p1, Vector3 p2, float thickness, Color color, Material mat)
    {
        float distance = Vector3.Distance(p0, p2);
        if (distance > densePlane10.bounds.size.x / 2)
        {
            // Fix frustum culling
            densePlane10.bounds = new Bounds(Vector3.zero, new Vector3(distance * 2, distance * 2, distance * 2));
        }
        p1 -= p0;
        p2 -= p0;
        if (!materialToData.TryGetValue(mat, out CurveData data))
        {
            data = new CurveData(mat);
            materialToData.Add(mat, data);
        }
        data.Add(p0, p1, p2, thickness, color);
    }
    public static void DrawInstanced()
    {
        foreach (CurveData data in materialToData.Values)
        {
            if (data.matrices.Count > 0)
            {
                data.DrawInstanced();
            }
        }
    }

    private class CurveData
    {
        public Material material;
        public readonly List<Matrix4x4> matrices = new();
        private readonly List<Vector4> p1s = new();
        private readonly List<Vector4> p2s = new();
        private readonly List<Vector4> colors = new();
        private readonly List<float> thicknesses = new();
        private readonly MaterialPropertyBlock propertyBlock = new();
        public CurveData(Material material)
        {
            this.material = material;
            this.material.enableInstancing = true;
        }
        private void Clear()
        {
            matrices.Clear();
            p1s.Clear();
            p2s.Clear();
            colors.Clear();
            thicknesses.Clear();
            propertyBlock.Clear();
        }
        public void Add(Vector3 p0, Vector3 p1, Vector3 p2, float thickness, Color color)
        {
            matrices.Add(Matrix4x4.Translate(p0));
            p1s.Add(p1);
            p2s.Add(p2);
            colors.Add(color);
            thicknesses.Add(thickness);
        }
        private MaterialPropertyBlock SetupMPB()
        {
            propertyBlock.SetVectorArray("_P0", new Vector4[matrices.Count]);
            propertyBlock.SetVectorArray("_P1", p1s.ToArray());
            propertyBlock.SetVectorArray("_P2", p2s.ToArray());
            propertyBlock.SetVectorArray("_Color", colors.ToArray());
            propertyBlock.SetFloatArray("_Thickness", thicknesses.ToArray());
            return propertyBlock;
        }
        public void DrawInstanced()
        {
            material.enableInstancing = true;
            Graphics.DrawMeshInstanced(densePlane10, 0, material, matrices, SetupMPB(), ShadowCastingMode.On, true, 0);
            this.Clear();
        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;
using Verse;

namespace RJW_Onahole.Utilities
{
    [StaticConstructorOnStartup]
    public static class CustomShaderUtility
    {
        public static FieldInfo shaderLookupField = typeof(ShaderDatabase).GetField("lookup", BindingFlags.NonPublic | BindingFlags.Static);
        public static void LoadCustomShaders()
        {
            string curPlatform = "StandaloneWindows64";
            RuntimePlatform platform = Application.platform;
            bool flag = platform == RuntimePlatform.LinuxEditor || platform == RuntimePlatform.LinuxPlayer;
            if (flag)
            {
                curPlatform = "StandaloneLinux64";
            }
            bool flag2 = platform == RuntimePlatform.OSXEditor || platform == RuntimePlatform.OSXPlayer;
            if (flag2)
            {
                curPlatform = "StandaloneOSX";
            }
            string dir = LoadedModManager.GetMod<RJW_Onahole_Mod>().Content.RootDir;
            List<ShaderTypeDef> shaderTypeDefs = DefDatabase<ShaderTypeDef>.AllDefsListForReading;
            List<string> shaderPaths = new List<string>();
            foreach (ShaderTypeDef shaderTypeDef in shaderTypeDefs)
            {
                if (!shaderTypeDef.shaderPath.NullOrEmpty())
                {
                    if (shaderTypeDef.shaderPath.ContainsCI("onahole"))
                    {
                        shaderPaths.Add(shaderTypeDef.shaderPath);
                    }
                }
            }
            Dictionary<string, Shader> lookup = (Dictionary<string, Shader>)shaderLookupField.GetValue(null);
            if (lookup == null)
            {
                lookup = new Dictionary<string, Shader>();
                shaderLookupField.SetValue(null, lookup);
            }
            string assetFilePath = Path.Combine(new string[]
            {
                dir,
                "Materials",
                "Bundles",
                curPlatform,
                "onahole"
            });
            AssetBundle assetBundle = AssetBundle.LoadFromFile(assetFilePath);
            foreach (string name in assetBundle.GetAllAssetNames())
            {
                if (name.EndsWith(".shader"))
                {
                    string strippedName = name.Replace(".shader", string.Empty).Replace("assets/shaders/", string.Empty);
                    foreach (string shaderPath in shaderPaths)
                    {
                        if (shaderPath.ContainsCI(strippedName))
                        {
                            Shader shader = assetBundle.LoadAsset<Shader>(name);
                            "Registering shader: {0} to path: {1}".Log(strippedName, shaderPath);
                            if (!shader.isSupported)
                            {
                                (shader.ToString() + " is not supported, skipping").LogError();
                                continue;
                            }

                            lookup.SetOrAdd(shaderPath, shader);
                            break;
                        }
                    }
                }
            }
        }
        public static Shader GetShader(ShaderTypeDef shaderTypeDef)
        {
            return ShaderDatabase.LoadShader(shaderTypeDef.shaderPath);
        }
        public static Shader GetShader(string shaderPath)
        {
            return ShaderDatabase.LoadShader(shaderPath);
        }

    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using RimWorld;
using rjw;
using RJW_Onahole.Comps;
using RJW_Onahole.UI;
using UnityEngine;
using Verse;
namespace RJW_Onahole.Utilities;
[StaticConstructorOnStartup]
public static class DrawUtility
{
    public static readonly Material milkMat = SolidColorMaterials.SimpleSolidColorMaterial(new Color(1f, 1f, 0.9f));
    public static readonly Material whiteMat = SolidColorMaterials.SimpleSolidColorMaterial(new Color(1f, 1f, 1f));
    public static readonly Material transparentMat = SolidColorMaterials.SimpleSolidColorMaterial(new Color(1f, 1f, 1f, 0f));
    public static readonly Material blackMat = SolidColorMaterials.SimpleSolidColorMaterial(new Color(0f, 0f, 0f));
    public static void DrawFilledCircle(Vector3 center, float radius, Material material, Vector3 scale)
    {
        Matrix4x4 matrix = default;
        Vector3 s = new(scale.x * radius, scale.y, scale.z * radius);
        matrix.SetTRS(center, Quaternion.identity, s);
        Graphics.DrawMesh(MeshPool.circle, matrix, material, 0);
    }
    // Copy of simplified vanilla DrawFillableBar to avoid it being modified by other mods.
    public static void DrawFillableBar(Vector3 center, Vector2 size, float fillPercent, Rot4 rotation, Material material)
    {
        if (fillPercent > 0.001f)
        {
            rotation = rotation.Opposite;
            Vector3 s = new Vector3(size.x * fillPercent, 1f, size.y);
            Matrix4x4 matrix = default(Matrix4x4);
            Vector3 pos = center;
            if (!rotation.IsHorizontal)
            {
                pos.x -= size.x * 0.5f;
                pos.x += 0.5f * size.x * fillPercent;
            }
            else
            {
                pos.z -= size.x * 0.5f;
                pos.z += 0.5f * size.x * fillPercent;
            }

            matrix.SetTRS(pos, rotation.AsQuat, s);
            Graphics.DrawMesh(MeshPool.plane10, matrix, material, 0);
        }
    }
    public enum MaterialType
    {
        None,
        FluidPipe,
        PipeConnector,
        Chain,
    }
    public static Material GetMaterial(this MaterialType type)
    {
        switch (type)
        {
            case MaterialType.FluidPipe:
                return fluidPipeMat;
            case MaterialType.PipeConnector:
                return pipeConnectorMat;
            case MaterialType.Chain:
                return chainMat;
            default:
                return null;
        }
    }

    public static Material fluidPipeMat = MaterialPool.MatFrom("Onahole/Pipes/FluidPipe", OnaholeDefOf.BezierCurveShader.Shader);
    public static Material chainMat = MaterialPool.MatFrom("Onahole/Pipes/Chain", ShaderTypeDefOf.CutoutTiling.Shader, Color.white);
    public static Material pipeConnectorMat = MaterialPool.MatFrom("Onahole/Pipes/Connector", ShaderDatabase.Cutout, Color.white);
    public static Dictionary<Color, Material> cachedColorMaterials = new();
    public static void DrawFillableBarMultiThingDef(Vector3 center, Vector2 size, Rot4 rotation, Dictionary<string, float> fluidsToAmounts, int max)
    {
        rotation = rotation.Opposite;
        float offset = 0f;
        foreach (KeyValuePair<string, float> kvp in fluidsToAmounts)
        {
            if (kvp.Value == 0) { continue; }
            float width = kvp.Value / max * size.x;
            if (width < 0.001f) { continue; }

            Vector3 drawScale = new Vector3(width, 1f, size.y);
            Matrix4x4 matrix4x = default;
            Vector3 drawPos = center + Vector3.up * 0.01f;
            if (!rotation.IsHorizontal)
            {
                drawPos.x -= size.x * 0.5f;
                drawPos.x += width * 0.5f + offset;
            }
            else
            {
                drawPos.z -= size.x * 0.5f;
                drawPos.z += width * 0.5f + offset;
            }
            matrix4x.SetTRS(drawPos, rotation.AsQuat, drawScale);
            Color color = DefDatabase<SexFluidDef>.GetNamed(kvp.Key)?.filth?.graphicData?.color ?? Color.white;
            if (!cachedColorMaterials.TryGetValue(color, out Material mat))
            {
                mat = new Material(DrawUtility.whiteMat)
                {
                    color = DefDatabase<SexFluidDef>.GetNamed(kvp.Key)?.filth?.graphicData?.color ?? Color.white
                };
                cachedColorMaterials.Add(color, mat);
            }
            Graphics.DrawMesh(MeshPool.plane10, matrix4x, mat, 0);
            offset += width;
        }
    }

    private static readonly float pipeAltitude = AltitudeLayer.Projectile.AltitudeFor() + 0.1f;
    private static readonly float connectorAltitude = AltitudeLayer.PawnRope.AltitudeFor() + 0.1f;
    public static void DrawPipe(Vector3 p0, Vector3 p1, Vector3 p2, float thickness, Color color, Material pipeMat = null, Material connectorMat = null)
    {
        pipeMat ??= fluidPipeMat;
        p0.y = pipeAltitude;
        p1.y = pipeAltitude;
        p2.y = pipeAltitude;
        float distance = Vector3.Distance(p0, p2);
        if (distance >= 6f) { return; } // Pipe must be less than 6 units long
        CurveDrawer.DrawCurve(p0, p1, p2, thickness, color, pipeMat);
        Vector3 p0c = new(p0.x, connectorAltitude, p0.z);
        Vector3 p2c = new(p2.x, connectorAltitude, p2.z);

        // Draw connectors
        if (connectorMat == null) { return; }
        float connectorSize = thickness * 2f;
        Graphics.DrawMesh(MeshPool.plane10, Matrix4x4.Translate(p0c) * Matrix4x4.Scale(Vector3.one * connectorSize), connectorMat, -1);
        Graphics.DrawMesh(MeshPool.plane10, Matrix4x4.Translate(p2c) * Matrix4x4.Scale(Vector3.one * connectorSize), connectorMat, -1);
    }
    private const float SegmentCount = 3f;
    private const float StandardThickness = 0.03f;
    private static readonly Dictionary<float, Material> thicknesses = new();
    public static void DrawLine(Vector3 p0, Vector3 p1, Material material, float thickness, float? lengthOverride = null)
    {
        Vector3 direction = p1 - p0;
        float length = direction.magnitude;
        float normalizedThickness = Mathf.Round(Mathf.Sqrt((lengthOverride ?? length) * StandardThickness / thickness) * SegmentCount * 100f) / 100f;
        if (!thicknesses.TryGetValue(normalizedThickness, out Material newMaterial))
        {
            newMaterial = new Material(material)
            {
                mainTextureScale = new Vector2(Mathf.Sqrt((lengthOverride ?? length) * StandardThickness / thickness) * SegmentCount, 1)
            };
            thicknesses.TryAdd(normalizedThickness, newMaterial);
        }
        Graphics.DrawMesh(MeshPool.plane10, Matrix4x4.TRS((p1 + p0) / 2, Quaternion.LookRotation(direction) * Quaternion.Euler(0, 90, 0), new Vector3(length, 1, thickness)), newMaterial, 0);
    }

    public static Texture2D HediffTex(PawnRenderNodeProperties renderNodeProperties, int index = 0)
    {
        if (renderNodeProperties.texPath != null)
        {
            return ContentFinder<Texture2D>.Get(renderNodeProperties.texPath);
        }
        if (renderNodeProperties.texPaths != null && renderNodeProperties.texPaths.Count > 0)
        {
            return GetHediffFromList(renderNodeProperties.texPaths, index);
        }
        return transparentMat.mainTexture as Texture2D;
    }
    public static Texture2D GetHediffFromList(List<string> paths, int index)
    {
        if (index == 0) { return CacheUtility.Tex.Transparent; }
        return ContentFinder<Texture2D>.Get(paths[index - 1]);
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using RimWorld;
using rjw;
using RJW_Onahole.Comps;
using RJW_Onahole.UI;
using UnityEngine;
using Verse;
using static Verse.GenDraw;
namespace RJW_Onahole.Utilities
{

    public static class EventUtility
    {
        public delegate void VoidHandler();
        public static event VoidHandler OnPreDraw;
        public static event VoidHandler OnPostDraw;

        public static void TriggerPreDraw()
        {
            OnPreDraw?.Invoke();
        }
        public static void TriggerPostDraw()
        {
            OnPostDraw?.Invoke();
        }
        public static event VoidHandler OnPostLoad;

        public static void TriggerPostLoad()
        {
            OnPostLoad?.Invoke();
        }
        /// <summary>
        /// Don't use this for harmony patches!
        /// </summary>
        public static event VoidHandler OnPostLoadLong;

        public static void TriggerPostLoadLong()
        {
            OnPostLoadLong?.Invoke();
        }
        public static event VoidHandler OnPostMapClicks;

        public static void TriggerPostMapClicks()
        {
            OnPostMapClicks?.Invoke();
        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using RimWorld;
using rjw;
using RJW_Onahole.HarmonyPatches;
using RJW_Onahole.Hediffs;
using RJW_Onahole.Jobs;
using UnityEngine;
using Verse;
using Verse.AI;
namespace RJW_Onahole.Utilities
{

    public static class Extensions
    {
        public static bool ContainsCI(this string str, string sub)
        {
            return str.IndexOf(sub, StringComparison.OrdinalIgnoreCase) >= 0;
        }
        public static bool CanBeMilked(this Pawn pawn)
        {
            return Genital_Helper.has_breasts(pawn) || pawn.health.hediffSet.HasHediff(HediffDefOf.Lactating);
        }
        public static bool TryRemoveHediffOfDef(this Pawn_HealthTracker health, HediffDef hediffDef)
        {
            if (health.hediffSet.HasHediff(hediffDef))
            {
                health.RemoveHediff(health.hediffSet.GetFirstHediffOfDef(hediffDef));
                return true;
            }
            return false;
        }
        public static bool TryAddHediffOfDef(this Pawn_HealthTracker health, HediffDef hediffDef)
        {
            if (!health.hediffSet.HasHediff(hediffDef))
            {
                health.AddHediff(hediffDef);
                return true;
            }
            return false;
        }
        public static Building_OnaholeBed OnaholeBedWithin8Cells(this Map map, IntVec3 cell)
        {
            foreach (IntVec3 adjCell in GenAdj.AdjacentCellsAndInside)
            {
                IntVec3 cell2 = adjCell + cell;
                if (cell2.InBounds(map))
                {
                    Building building = cell2.GetFirstBuilding(map);

                    if (building is Building_OnaholeBed)
                    {
                        return building as Building_OnaholeBed;
                    }
                }
            }

            return null;
        }
        public static Building_OnaholeBed OnaholeBedWithin8Cells(this Pawn pawn)
        {
            return pawn.Map.OnaholeBedWithin8Cells(pawn.Position);
        }
        public static bool IsOnahole(this Pawn pawn)
        {
            return pawn?.OnaholeBed() != null;
        }
        public static Building_OnaholeBed OnaholeBed(this Pawn pawn)
        {
            return pawn?.CurrentBed() as Building_OnaholeBed ?? (pawn?.jobs?.curDriver as JobDriver_BeOnahole)?.Bed as Building_OnaholeBed;
        }
        public static bool IsUsingOnahole(this Pawn pawn)
        {
            return pawn?.jobs?.curDriver is JobDriver_UseOnahole;
        }
        public static Pawn OnaholeUser(this Pawn pawn)
        {
            return (pawn?.jobs?.curDriver as JobDriver_BeOnahole)?.Partner;
        }
        public static Pawn OnaholePartner(this Pawn pawn)
        {
            return (pawn?.jobs?.curDriver as JobDriver_UseOnahole)?.Partner;
        }
        public static bool IsFreeOnahole(this Pawn pawn, Pawn user = null)
        {
            // Safe check, remove all none existent partners
            if (!(pawn?.jobs?.curDriver is JobDriver_BeOnahole beOnahole)) { return false; }
            beOnahole.parteners.RemoveAll(x => x?.CurJob?.def != OnaholeDefOf.UseOnahole);
            if (beOnahole.PartnerPawn?.CurJob?.def != OnaholeDefOf.UseOnahole) { beOnahole.PartnerPawn = null; }
            return (beOnahole.Partner == user || beOnahole.Partner == null) && (beOnahole.parteners.Count == 0 || (beOnahole.parteners.Contains(user) && beOnahole.parteners.Count == 1));
        }
        public static bool IsFreeOnaholeFast(this Pawn pawn)
        {
            if (!(pawn?.jobs?.curDriver is JobDriver_BeOnahole beOnahole)) { return false; }
            return beOnahole.Partner == null && beOnahole.parteners.Count == 0;
        }
        public static void ClearOnaholeReservation(this Pawn pawn)
        {
            if (!(pawn?.jobs?.curDriver is JobDriver_BeOnahole beOnahole)) { return; }
            beOnahole.PartnerPawn = null;
            beOnahole.parteners.Clear();
            pawn.ClearAllReservations();
        }
        public static bool IsCurPartner(this Pawn pawn, Pawn target)
        {
            if (pawn == null || target == null) { return false; }
            if (pawn.OnaholePartner() == target) { return true; }
            if (pawn.jobs.curDriver is JobDriver_BeOnahole job)
            {
                return job.Partner == target || (job.parteners.Contains(target) && job.parteners.Count == 1);
            }
            return false;
        }
        public static bool IsOnaholeLovin(this Pawn pawn)
        {
            if (pawn.IsOnahole() && !pawn.IsFreeOnaholeFast() || (pawn.jobs.curDriver is JobDriver_BeOnahole jobDriver_BeOnahole && jobDriver_BeOnahole.IsBeingStimulated)) { return true; }
            return false;
        }
        public static bool IsPlayerPawn(this Pawn pawn)
        {
            return pawn?.Faction == Faction.OfPlayer || pawn.IsPrisonerOfColony || pawn.IsSlaveOfColony;
        }
        public static string TitleOrRace(this Pawn pawn)
        {
            if (pawn == null) { return ""; }
            string text = null;
            if (pawn.RaceProps.Humanlike)
            {
                text = pawn?.story?.TitleShort;
            }
            if (text.NullOrEmpty())
            {
                text = pawn?.kindDef?.LabelCap;
            }
            return text ?? "";
        }
        public static bool IsSubClassesOf(this Type type, Type baseType)
        {
            if (type.BaseType == null) { return false; }
            if (type.BaseType == baseType) { return true; }
            return type.BaseType.IsSubClassesOf(baseType);
        }
        public static FieldInfo FieldOrParentField(this Type type, string fieldName)
        {
            FieldInfo field = type.Field(fieldName);
            if (field != null) { return field; }
            if (type.BaseType == null) { return null; }
            return type.BaseType.FieldOrParentField(fieldName);
        }
        public static void AddDistinct<T>(this IList<T> list, T item)
        {
            if (!list.Contains(item))
            {
                list.Add(item);
            }
        }
        public static float WouldFuck(this Pawn pawn, Pawn target)
        {
            return SexAppraiser.would_fuck(pawn, target, true, true) + 1000f;// Offset Harmony patch penalty
        }
        public static List<Hediff> GetPregnancies(this Pawn pawn)
        {
            List<Hediff_InsectEgg> insectEggs = new List<Hediff_InsectEgg>();
            pawn.health.hediffSet.GetHediffs<Hediff_InsectEgg>(ref insectEggs);
            insectEggs.RemoveAll(x => !x.fertilized || x.Part != Genital_Helper.get_genitalsBPR(pawn));
            List<Hediff> result = new List<Hediff>();
            result.AddRange(insectEggs);
            result.AddRange(PregnancyHelper.GetPregnancies(pawn));
            return result;
        }
        public static float GetPregnancyProgress(this Hediff hediff)
        {
            if (hediff is Hediff_InsectEgg insectEgg)
            {
                return insectEgg.GestationProgress;
            }
            return PregnancyHelper.GetProgress(hediff);
        }
        public static int AsInt(this object obj)
        {
            if (obj == null) { return 0; }
            if (obj as int? != null) { return (int)obj; }
            if (obj as float? != null) { return (int)(float)obj; }
            return 0;
        }
        public static int AddOrReplace<T>(this List<T> list, int index, T item)
        {
            if (list.Count <= index) { list.Add(item); return list.Count - 1; }
            else { list[index] = item; return index; }
        }
        public static bool ShouldDraw(this HediffDef def)
        {
            if (def == null) { return false; }
            if (def == OnaholeDefOf.OnaholeAnalSexMachine)
            {
                return RJW_Onahole_Settings.showAnalSexMachine;
            }
            if (def == OnaholeDefOf.OnaholeVaginalSexMachine)
            {
                return RJW_Onahole_Settings.showVaginalSexMachine;
            }
            if (def == OnaholeDefOf.OnaholeMilkingMachine)
            {
                return RJW_Onahole_Settings.showMilkingMachine;
            }
            if (def == OnaholeDefOf.OnaholeAutoFeeder)
            {
                return RJW_Onahole_Settings.showFeeder;
            }
            if (def == OnaholeDefOf.OnaholeBreastElectrodes)
            {
                return RJW_Onahole_Settings.showBreastElectrodes;
            }
            if (def == OnaholeDefOf.OnaholeGenitalElectrodes)
            {
                return RJW_Onahole_Settings.showGenitalElectrodes;
            }
            return true;
        }
        public static GuestUtility.PawnCategory PawnCategory(this Pawn pawn)
        {
            if (pawn == null) { return GuestUtility.PawnCategory.Misc; }
            if (pawn.IsPrisonerOfColony)
            {
                return GuestUtility.PawnCategory.Prisoner;
            }
            if (ModsConfig.IdeologyActive && pawn.IsSlaveOfColony)
            {
                return GuestUtility.PawnCategory.Slave;
            }
            if (pawn.IsColonist)
            {
                return GuestUtility.PawnCategory.Colonist;
            }
            if (pawn.IsAnimal() && pawn.Faction == Faction.OfPlayer)
            {
                return GuestUtility.PawnCategory.Animal;
            }
            if (pawn.IsColonyMech)
            {
                return GuestUtility.PawnCategory.Mechanoid;
            }
            if (pawn.health.Downed && pawn.Faction != Faction.OfPlayer && pawn.HostileTo(Faction.OfPlayer))
            {
                return GuestUtility.PawnCategory.DownedEnemy;
            }
            return GuestUtility.PawnCategory.Misc;
        }
        public static string GuestStatusLabel(this Pawn pawn)
        {
            switch (pawn.PawnCategory())
            {
                case GuestUtility.PawnCategory.Prisoner: return OTr.Prisoner;
                case GuestUtility.PawnCategory.Slave: return OTr.Slave;
                case GuestUtility.PawnCategory.Colonist: return OTr.Colonist;
                case GuestUtility.PawnCategory.Animal: return OTr.Animal;
                case GuestUtility.PawnCategory.Mechanoid: return OTr.Mechanoid;
                case GuestUtility.PawnCategory.DownedEnemy: return OTr.Enemy;
                default: return OTr.Misc;
            }
        }
        public static void TryAdd<K, V>(this Dictionary<K, V> dict, K key, V value)
        {
            if (!dict.ContainsKey(key))
            {
                dict.Add(key, value);
            }
        }
        public static bool CanFeed(this SexFluidDef sexFluidDef) => sexFluidDef?.consumable?.ingestible != null || sexFluidDef?.baseNutrition != null && sexFluidDef.baseNutrition > 0;
        public static bool CanAutoConvert(this SexFluidDef sexFluidDef) => sexFluidDef?.consumable != null;
    }

}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using RimWorld;
using rjw;
using UnityEngine;
using Verse;
using RJW_Onahole.Data;
namespace RJW_Onahole.Utilities
{
    public static class GuestUtility
    {
        /// <summary>
        /// Used for categorizing pawns when filtering bindable pawns
        /// </summary>
        public enum PawnCategory
        {
            Misc,
            Colonist, // Humanlike, player faction, not prisoner, not slave
            Slave, // Slave of colony
            Prisoner, // Prisoner of colony
            Animal, // Animal of colony
            Mechanoid, // Mechanoid of colony
            DownedEnemy // Downed enemy of player faction
        }
        public static bool IsGuest(this Pawn pawn)
        {
            return pawn.GuestStatus == GuestStatus.Guest || (pawn.Faction != null && !pawn.Faction.IsPlayer && !pawn.HostileTo(Faction.OfPlayer) && !pawn.IsPrisonerOfColony && !pawn.IsSlaveOfColony);
        }
        public static bool IsEnemy(this Pawn pawn)
        {
            return pawn.HostileTo(Faction.OfPlayer);
        }
        public static void ReduceResistance(this Pawn pawn, Pawn initiator, float amount)
        {
            if (!RJW_Onahole_Settings.useReduceResistance) { return; }
            amount = Mathf.Min(amount, pawn.guest.resistance);
            float resistance = pawn.guest.resistance;
            pawn.guest.resistance -= amount;
            if (pawn.guest.resistance <= 0f)
            {
                pawn.guest.SetLastResistanceReduceData(initiator, amount, 0f, 0f, 0f);
            }
            float before = (resistance > 0f) ? Mathf.Max(0.1f, resistance) : 0f;
            float after = (pawn.guest.resistance > 0f) ? Mathf.Max(0.1f, pawn.guest.resistance) : 0f;
            if (resistance > 0f)
            {
                string text = "TextMote_ResistanceReduced".Translate(before.ToString("F1"), after.ToString("F1"));
                MoteMaker.ThrowText((initiator.DrawPos + pawn.DrawPos) / 2f, initiator.Map, text, 8f);
            }
            if (pawn.guest.resistance == 0f && resistance > 0f)
            {
                TaggedString taggedString = "MessagePrisonerResistanceBroken".Translate(pawn.LabelShort, initiator.LabelShort, initiator.Named("WARDEN"), pawn.Named("PRISONER"));
                if (pawn.guest.IsInteractionEnabled(PrisonerInteractionModeDefOf.AttemptRecruit))
                {
                    taggedString += " " + "MessagePrisonerResistanceBroken_RecruitAttempsWillBegin".Translate();
                }
                Messages.Message(taggedString, pawn, MessageTypeDefOf.PositiveEvent, true);
                return;
            }
        }
        public static void ReduceWill(this Pawn pawn, Pawn initiator, float amount)
        {
            if (!RJW_Onahole_Settings.orgasmReduceWill) { return; }
            if ((pawn?.guest?.will ?? 0f) <= 0f) { return; }
            float originalWill = pawn.guest.will;
            amount = Mathf.Min(amount, originalWill);
            pawn.guest.will -= amount;
            if (originalWill > 0f)
            {
                string text = "TextMote_WillReduced".Translate(originalWill.ToString("F1"), pawn.guest.will.ToString("F1"));
                MoteMaker.ThrowText((initiator.DrawPos + pawn.DrawPos) / 2f, initiator.Map, text, 8f);
            }
            if (pawn.guest.will == 0f && originalWill > 0f)
            {
                TaggedString taggedString = "MessagePrisonerWillBroken".Translate(initiator, pawn);
                if (pawn.guest.IsInteractionEnabled(PrisonerInteractionModeDefOf.AttemptRecruit))
                {
                    taggedString += " " + "MessagePrisonerWillBroken_RecruitAttempsWillBegin".Translate();
                }
                Messages.Message(taggedString, pawn, MessageTypeDefOf.PositiveEvent, true);
                return;
            }
        }
        public static void ReduceCertainty(this Pawn pawn, float amount)
        {
            if (!ModsConfig.IdeologyActive || !RJW_Onahole_Settings.orgasmReduceCertainty) { return; }
            if ((pawn?.ideo?.Certainty ?? 0f) <= 0.001f) { return; }
            amount *= pawn.GetStatValue(StatDefOf.CertaintyLossFactor, true, -1);
            pawn.ideo.OffsetCertainty(-amount);
        }
        public static bool CanAfford(this Pawn pawn, int amount)
        {
            int totalSilver = pawn.inventory.innerContainer.TotalStackCountOfDef(ThingDefOf.Silver) - PawnsToReservedAmounts(pawn).TryGetValue(pawn, 0);
            if (totalSilver >= amount) { return true; }
            foreach (Pawn factionPawn in pawn.Map.mapPawns.PawnsInFaction(pawn.Faction))
            {
                if (factionPawn.IsAnimal()) { totalSilver += factionPawn.inventory.innerContainer.TotalStackCountOfDef(ThingDefOf.Silver) - PawnsToReservedAmounts(factionPawn).TryGetValue(factionPawn, 0); }
            }
            return totalSilver >= amount;
        }
        public static bool ReserveSilver(this Pawn pawn, int amount)
        {
            if (pawn.CanAfford(amount))
            {
                amount -= TryReserverSilverOnPawn(pawn, amount);
                while (amount > 0)
                {
                    foreach (Pawn factionPawn in pawn.Map.mapPawns.PawnsInFaction(pawn.Faction))
                    {
                        if (factionPawn.IsAnimal()) { amount -= TryReserverSilverOnPawn(factionPawn, amount); }
                    }
                }
                return true;
            }
            return false;
        }
        private static int TryReserverSilverOnPawn(Pawn pawn, int amount)
        {
            int silver = pawn.inventory.innerContainer.TotalStackCountOfDef(ThingDefOf.Silver);
            if (PawnsToReservedAmounts(pawn).TryGetValue(pawn, out int reservedAmount))
            {
                silver -= reservedAmount;
            }
            else
            {
                PawnsToReservedAmounts(pawn).TryAdd(pawn, 0);
            }
            int reserve = Math.Min(amount, silver);
            PawnsToReservedAmounts(pawn)[pawn] += reserve;
            return reserve;
        }
        public static void PaySilver(this Pawn pawn, int amount, Building_OnaholeBed bed)
        {
            MoteMaker.ThrowText(pawn.DrawPos, pawn.Map, "+$" + amount, Color.green, 8f);
            amount -= DropSilver(pawn, amount, bed);
            List<Pawn> pawnsNotReserved = new List<Pawn>();
            foreach (Pawn factionPawn in pawn.Map.mapPawns.PawnsInFaction(pawn.Faction))
            {
                if (factionPawn.IsAnimal())
                {
                    if (PawnsToReservedAmounts(pawn).ContainsKey(factionPawn))
                    {
                        amount -= DropSilver(factionPawn, amount, bed);
                    }
                    else
                    {
                        pawnsNotReserved.Add(factionPawn);
                    }
                }
                if (amount <= 0) { return; }
            }
            foreach (Pawn factionPawn in pawnsNotReserved)
            {
                amount -= DropSilver(factionPawn, amount, bed);
                if (amount <= 0) { return; }
            }
            if (amount > 0)
            {
                Log.Warning(pawn.LabelShort + " Not enough silver to pay for onahole");
            }
        }
        private static int DropSilver(Pawn pawn, int amount, Building_OnaholeBed bed)
        {
            int totalDropped = 0;
            List<Thing> silverStacks = pawn.inventory.innerContainer.Where(x => x.def == ThingDefOf.Silver).ToList();
            foreach (Thing silver in silverStacks)
            {
                int toDrop = Mathf.Min(silver.stackCount, amount);
                pawn.inventory.innerContainer.TryTransferToContainer(silver, bed.innerContainer, toDrop, out Thing dropped);
                totalDropped += toDrop;
                amount -= toDrop;
                if (PawnsToReservedAmounts(pawn).TryGetValue(pawn, out int reservedAmount))
                {
                    PawnsToReservedAmounts(pawn)[pawn] -= toDrop;
                    if (PawnsToReservedAmounts(pawn)[pawn] <= 0)
                    {
                        PawnsToReservedAmounts(pawn).Remove(pawn);
                    }
                }
                if (amount <= 0) { return totalDropped; }
            }
            return totalDropped;
        }
        public static void PayFervor(this Pawn payer, int surplus)
        {
            int goodwill = surplus / 100;
            if (goodwill <= 0) { return; }
            payer.Faction.TryAffectGoodwillWith(Faction.OfPlayer, goodwill, true, true, null, null);
            string text = payer.Faction.Name + " " + "TextMote_GoodwillGained:".Translate() + ":" + goodwill;
            MoteMaker.ThrowText(payer.DrawPos, payer.Map, text, 8f);
        }
        public static GuestSilverReservationMapComponent GetMapDataOnahole(this Pawn pawn)
        {
            GuestSilverReservationMapComponent mapComponent = pawn.Map.GetComponent<GuestSilverReservationMapComponent>();
            if (mapComponent == null)
            {
                mapComponent = new GuestSilverReservationMapComponent(pawn.Map);
                pawn.Map.components.Add(mapComponent);
            }
            return mapComponent;
        }
        public static Dictionary<Pawn, int> PawnsToReservedAmounts(Pawn pawn)
        {
            return pawn.GetMapDataOnahole().PawnsToReservedAmounts;
        }
        public static float OnaholeScore(this Pawn pawn, Pawn onahole, float? score = null)
        {
            float baseScore = score ?? pawn.WouldFuck(onahole);
            baseScore *= RJW_Onahole_Settings.OnaholeAppraiseMultiplier;
            baseScore += RJW_Onahole_Settings.OnaholeAppraiseOffset;
            baseScore = Mathf.Max(baseScore, 0);
            "Onahole {0} has a score of {1} for {2}".Log(onahole.LabelShort, baseScore, pawn.LabelShort);
            return baseScore;
        }
        public static bool WouldPayForOnahole(this Pawn pawn, Pawn onahole, out int surplus, float? onaholeScore = null)
        {
            surplus = 0;
            if (pawn == null || onahole == null) { return false; }
            float score = onaholeScore ?? OnaholeScore(pawn, onahole);
            SimpleCurve curve = RJW_Onahole_Settings.PriceCurve;
            int price = (int)curve.Evaluate(score);
            surplus = price - onahole.OnaholeBed().Price;
            "Onahole {0} has a price of {1} for {2}, with surplus {3}".Log(onahole.LabelShort, price, pawn.LabelShort, surplus);
            return surplus >= 0 && score >= 0.1f;
        }
    }
}


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

namespace RJW_Onahole.Utilities
{
    [StaticConstructorOnStartup]
    public static class Logger
    {
        public static void Log(this string message, params object[] args)
        {
            if(!RJW_Onahole_Settings.isDevMode){return;}
            Verse.Log.Message($"[RJW_Onahole] {message.ParseString(args)}");
        }
        public static void LogWarning(this string message, params object[] args)
        {
            if(!RJW_Onahole_Settings.isDevMode){return;}
            Verse.Log.Warning($"[RJW_Onahole] {message.ParseString(args)}");
        }
        public static void LogError(this string message, params object[] args)
        {
            Verse.Log.Error($"[RJW_Onahole] {message.ParseString(args)}");
        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Profiling;
using Verse;

namespace RJW_Onahole.Utilities
{
    [StaticConstructorOnStartup]
    public static class MemoryProfilerUtility
    {
        static MemoryProfilerUtility()
        {
            // EventUtility.OnPostLoadLong += Init;
        }
        public static void Init()
        {
            UnityEngine.Object.DontDestroyOnLoad(new GameObject("MemoryProfiler", typeof(MemoryProfiler)));
        }
    }
    public class MemoryProfiler : MonoBehaviour
    {
        private Rect windowRect = new Rect(10, 10, 300, 400);
        private string memoryStats = "Loading...";
        private bool isUpdating = false;
        private Dictionary<object, long> allocations = new Dictionary<object, long>();
        private Dictionary<Type, long> allocationsByType_Main = new Dictionary<Type, long>();
        private Dictionary<Type, long> allocationsByType_Alt = new Dictionary<Type, long>();
        private bool useAlt = false;
        void Start()
        {
            // Start updating memory stats in the background
            UpdateMemoryStatsAsync();
        }

        async void UpdateMemoryStatsAsync()
        {
            isUpdating = true;
            while (isUpdating)
            {
                memoryStats = await GetMemoryStatsAsync();
                await Task.Delay(1000); // Update every 1 second
            }
        }

        async Task<string> GetMemoryStatsAsync()
        {
            return await Task.Run(() =>
            {
                useAlt = !useAlt;
                long totalAllocated = Profiler.GetTotalAllocatedMemoryLong() / (1024 * 1024);
                long totalReserved = Profiler.GetTotalReservedMemoryLong() / (1024 * 1024);
                long totalUnusedReserved = Profiler.GetTotalUnusedReservedMemoryLong() / (1024 * 1024);
                long managedHeap = System.GC.GetTotalMemory(false) / (1024 * 1024);
                Dictionary<Type, long> allocationsByType = useAlt ? allocationsByType_Alt : allocationsByType_Main;
                allocationsByType.Clear();
                foreach (UnityEngine.Object obj in Resources.FindObjectsOfTypeAll<UnityEngine.Object>())
                {
                    allocations[obj] = Profiler.GetRuntimeMemorySizeLong(obj) / (1024 * 1024);
                    Type type = obj.GetType();
                    if (!allocationsByType.ContainsKey(type))
                    {
                        allocationsByType[type] = 0;
                    }
                    allocationsByType[type] += Profiler.GetRuntimeMemorySizeLong(obj) / (1024 * 1024);
                }
                GC.Collect();
                return $"Total Allocated: {totalAllocated} MB\n" +
                       $"Total Reserved: {totalReserved} MB\n" +
                       $"Total Unused Reserved: {totalUnusedReserved} MB\n" +
                       $"Managed Heap: {managedHeap} MB" +
                       $"\n\nMost Allocated Objects:\n" +
                       string.Join("\n", allocationsByType.OrderByDescending(x => x.Value).Select(x => $"{x.Key.FullName} ({x.Value} MB)").Take(3));
            });
        }

        void OnGUI()
        {
            // Draw a draggable memory profiler window
            windowRect = GUI.Window(0, windowRect, DrawMemoryWindow, "Memory Profiler");
        }

        void DrawMemoryWindow(int windowID)
        {
            GUILayout.BeginVertical();
            GUILayout.Label(memoryStats);
            GUILayout.EndVertical();

            // Allow the window to be draggable
            GUI.DragWindow();
        }

        void OnDestroy()
        {
            // Stop updating memory stats when the object is destroyed
            isUpdating = false;
        }
    }
}


code:
using UnityEngine;
namespace RJW_Onahole.Utilities;
public static class MeshUtility
{
    public static Mesh PlaneMeshDense(int resolutionX, int resolutionY, float sizeX, float sizeY)
    {
        return PlaneMeshDense(new Vector2Int(resolutionX, resolutionY), new Vector2(sizeX, sizeY));
    }
    public static Mesh PlaneMeshDense(int resolutionX, int resolutionY, float size = 1f)
    {
        return PlaneMeshDense(new Vector2Int(resolutionX, resolutionY), new Vector2(size, size));
    }
    public static Mesh PlaneMeshDense(int resolution, float sizeX, float sizeY)
    {
        return PlaneMeshDense(new Vector2Int(resolution, resolution), new Vector2(sizeX, sizeY));
    }
    public static Mesh PlaneMeshDense(int resolution, float size = 1f)
    {
        return PlaneMeshDense(new Vector2Int(resolution, resolution), new Vector2(size, size));
    }
    public static Mesh PlaneMeshDense(Vector2Int resolution, Vector2? size = null)
    {
        Vector2 sizeValue = size ?? Vector2.one;
        int vertexCount = (resolution.x + 1) * (resolution.y + 1);
        int triangleCount = resolution.x * resolution.y * 6;

        Vector3[] vertices = new Vector3[vertexCount];
        Vector2[] uvs = new Vector2[vertexCount];
        int[] triangles = new int[triangleCount];

        float stepX = sizeValue.x / resolution.x;
        float stepZ = sizeValue.y / resolution.y;

        int vertIndex = 0;
        int triIndex = 0;

        // Generate vertices and UVs
        for (int z = 0; z <= resolution.y; z++)
        {
            for (int x = 0; x <= resolution.x; x++)
            {
                float xPos = -0.5f * sizeValue.x + x * stepX;
                float zPos = -0.5f * sizeValue.y + z * stepZ;
                vertices[vertIndex] = new Vector3(xPos, 0, zPos);
                uvs[vertIndex] = new Vector2((float)x / resolution.x, (float)z / resolution.y);
                vertIndex++;
            }
        }

        // Generate triangles
        for (int z = 0; z < resolution.y; z++)
        {
            for (int x = 0; x < resolution.x; x++)
            {
                int topLeft = z * (resolution.x + 1) + x;
                int topRight = topLeft + 1;
                int bottomLeft = topLeft + resolution.x + 1;
                int bottomRight = bottomLeft + 1;

                // First triangle
                triangles[triIndex++] = topLeft;
                triangles[triIndex++] = bottomLeft;
                triangles[triIndex++] = topRight;

                // Second triangle
                triangles[triIndex++] = topRight;
                triangles[triIndex++] = bottomLeft;
                triangles[triIndex++] = bottomRight;
            }
        }

        Mesh mesh = new()
        {
            name = "PlaneMesh" + resolution.x.ToString() + resolution.y.ToString() + sizeValue.x.ToString() + sizeValue.y.ToString(),
            vertices = vertices,
            uv = uvs
        };

        mesh.SetTriangles(triangles, 0);
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();

        return mesh;
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using RJW_Onahole.Data;
using UnityEngine;
using Verse;

namespace RJW_Onahole.Utilities
{
    public static class PatchUtility
    {
        public static List<Type> GetSubTypes(this Type type)
        {
            List<Type> types = new List<Type>();
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                try
                {
                    foreach (Type t in assembly.GetTypes())
                    {
                        try
                        {

                            if (t.IsSubclassOf(type))
                            {
                                types.Add(t);
                            }
                        }
                        catch { }
                    }
                }
                catch { }
            }
            return types;
        }
        public static IEnumerable<MethodInfo> GetMethods(this IEnumerable<Type> types, string methodName)
        {
            return types.SelectMany(t => t.GetMethods()).Where(m => m.Name == methodName);
        }
    }
}


code:
using System.Collections.Generic;
using RJW_Onahole.Data;
using UnityEngine;
using Verse;

namespace RJW_Onahole.Utilities
{
    public static class PositionUtility
    {
        private static readonly Dictionary<string, Vector3> keysToPositions = new Dictionary<string, Vector3>();
        private static string GetKey(Pawn pawn, HediffDef hediffDef, bool flipped) => pawn.ThingID + pawn.ageTracker.CurLifeStageIndex.ToString() + hediffDef.defName + flipped.ToString();
        public static void RegisterPosition(Pawn pawn, HediffDef hediffDef, Vector3 pos, bool flipped = false)
        {
            string key = GetKey(pawn, hediffDef, flipped);
            keysToPositions[key] = pos;
        }
        public static Vector3? GetPosition(Pawn pawn, HediffDef hediffDef, bool flipped = false)
        {
            string key = GetKey(pawn, hediffDef, flipped);
            if (keysToPositions.TryGetValue(key, out Vector3 pos))
            {
                return pos;
            }
            return null;
        }
        public static Vector2 GetDrawScale(Pawn pawn, Graphic graphic, PawnDrawParms parms)
        {
            float x = parms.matrix.lossyScale.x * graphic.drawSize.x * graphic.MeshAt(Rot4.South).bounds.size.x * pawn.DrawSize.x;
            float y = parms.matrix.lossyScale.z * graphic.drawSize.y * graphic.MeshAt(Rot4.South).bounds.size.z * pawn.DrawSize.y;
            return new Vector2(x, y);
        }
        public static Vector2 GetPreBodyMeshScale(Pawn pawn, Graphic graphic, PawnDrawParms parms)
        {
            pawn.Drawer.renderer.renderTree.rootNode.GetTransform(parms, out _, out _, out _, out Vector3 globalScale);
            Vector2 drawScale = GetDrawScale(pawn, graphic, parms);
            return new Vector2(drawScale.x * globalScale.x, drawScale.y * globalScale.z);
        }
        public static Matrix4x4 CurAnimationTransform(this Pawn pawn)
        {
            if (pawn?.Drawer?.renderer?.renderTree?.rootNode?.AnimationWorker is AnimationWorker_Keyframes keyframes)
            {
                int tick = pawn.Drawer.renderer.renderTree.AnimationTick;
                Vector3 offset = keyframes.OffsetAtTick(tick, PawnDrawParms.Default);
                // Offset the pawn as if it is laying down
                if (pawn.IsOnahole()) { offset.y += AltitudeLayer.Pawn.AltitudeFor() - AltitudeLayer.LayingPawn.AltitudeFor(); }
                Vector3 scale = keyframes.ScaleAtTick(tick, PawnDrawParms.Default);
                float angle = keyframes.AngleAtTick(tick, PawnDrawParms.Default);
                return Matrix4x4.TRS(offset, Quaternion.AngleAxis(angle, Vector3.up), scale);
            }
            return Matrix4x4.identity;
        }
        private static float time;
        public static int CalcCurFrame(float startTime, float fps, int totalFrames)
        {
            if (!Find.TickManager.Paused) { time = Time.time; }
            float curTime = time - startTime;
            return Mathf.FloorToInt(curTime * fps) % totalFrames;
        }
        public static Vector3 PositionAtFrame(Pawn pawn, float startTime, float fps, int totalFrames, InstancedAnimationDef instancedAnimationDef, HediffDef hediffDef)
        {
            Vector3 position = PositionUtility.GetPosition(pawn, hediffDef) ?? pawn.DrawPos;
            if ((instancedAnimationDef?.framePositions).NullOrEmpty()) { return position; }
            int curFrame = CalcCurFrame(startTime, fps, totalFrames);
            if (curFrame >= instancedAnimationDef.framePositions.Count) { return position; }
            Vector2 displacement = instancedAnimationDef.framePositions[curFrame];
            TransformData transformData = ConfigUtility.GetPawnTransformData(pawn, hediffDef);
            Vector3 scale = transformData.Scale;
            Vector3 rootScale = Vector3.one;
            pawn.Drawer?.renderer?.renderTree?.rootNode?.GetTransform(PawnDrawParms.Default, out _, out _, out _, out rootScale);
            Vector2 drawSize = pawn.Drawer?.renderer?.BodyGraphic?.drawSize ?? Vector2.one;

            displacement.x *= pawn.DrawSize.x * scale.x * drawSize.x * rootScale.x;
            displacement.y *= pawn.DrawSize.y * scale.z * drawSize.y * rootScale.z;
            return new Vector3(position.x + displacement.x, position.y, position.z + displacement.y);
        }
    }
}


code:
using Verse;
using RimWorld;
using RJW_Onahole.UI;
using RJW_Onahole.Data;
using rjw;
namespace RJW_Onahole.Utilities
{
    // Keyed translations
    [StaticConstructorOnStartup]
    public static class Tr
    {
        private static string KeyTranslate(this string key)
        {
            return ("RJW_Onahole_" + key).Translate();
        }
        public static string RJW_Onahole => nameof(RJW_Onahole).Translate();
        public static string CustomTransforms => nameof(CustomTransforms).KeyTranslate();
        public static string Free => nameof(Free).KeyTranslate();
        public static string ForGuestUse_Desc => nameof(ForGuestUse_Desc).KeyTranslate();
        public static string ForAnimalUse_Desc => nameof(ForAnimalUse_Desc).KeyTranslate();
        public static string ForInsectUse_Desc => nameof(ForInsectUse_Desc).KeyTranslate();
        public static string ForMechanoidUse_Desc => nameof(ForMechanoidUse_Desc).KeyTranslate();
        public static string GenitalElectrodes => nameof(GenitalElectrodes).KeyTranslate();
        public static string GenitalElectrodes_Desc => nameof(GenitalElectrodes_Desc).KeyTranslate();
        public static string BreastElectrodes => nameof(BreastElectrodes).KeyTranslate();
        public static string BreastElectrodes_Desc => nameof(BreastElectrodes_Desc).KeyTranslate();
        public static string VaginalSexMachine => nameof(VaginalSexMachine).KeyTranslate();
        public static string VaginalSexMachine_Desc => nameof(VaginalSexMachine_Desc).KeyTranslate();
        public static string AnalSexMachine => nameof(AnalSexMachine).KeyTranslate();
        public static string AnalSexMachine_Desc => nameof(AnalSexMachine_Desc).KeyTranslate();
        public static string MilkingMachine => nameof(MilkingMachine).KeyTranslate();
        public static string MilkingMachine_Desc => nameof(MilkingMachine_Desc).KeyTranslate();
        public static string CumCollector => nameof(CumCollector).KeyTranslate();
        public static string CumCollector_Desc => nameof(CumCollector_Desc).KeyTranslate();
        public static string AutoFeeder => nameof(AutoFeeder).KeyTranslate();
        public static string AutoFeeder_Desc => nameof(AutoFeeder_Desc).KeyTranslate();
        public static string DrugInfuser => nameof(DrugInfuser).KeyTranslate();
        public static string DrugInfuser_Desc => nameof(DrugInfuser_Desc).KeyTranslate();
        public static string AllowAutoRefill => nameof(AllowAutoRefill).KeyTranslate();
        public static string SetTargetLevel => nameof(SetTargetLevel).KeyTranslate();
        public static string EditTransform => nameof(EditTransform).KeyTranslate();
        public static string EditTransform_Desc => nameof(EditTransform_Desc).KeyTranslate();
        public static string DesignateColonyUsers => nameof(DesignateColonyUsers).KeyTranslate();
        public static string DesignateColonyUsers_Desc => nameof(DesignateColonyUsers_Desc).KeyTranslate();
        public static string Unbind => nameof(Unbind).KeyTranslate();
        public static string Bind => nameof(Bind).KeyTranslate();
        public static string Settings => nameof(Settings).KeyTranslate();
        public static string HaulCoolDownTicks => nameof(HaulCoolDownTicks).KeyTranslate();
        public static string MaxFluidStorage => nameof(MaxFluidStorage).KeyTranslate();
        public static string AppraiseMultiplier => nameof(AppraiseMultiplier).KeyTranslate();
        public static string AppraiseOffset => nameof(AppraiseOffset).KeyTranslate();
        public static string PriceAppraiseZero => nameof(PriceAppraiseZero).KeyTranslate();
        public static string PriceAppraiseOne => nameof(PriceAppraiseOne).KeyTranslate();
        public static string Offset => nameof(Offset).KeyTranslate();
        public static string Scale => nameof(Scale).KeyTranslate();
        public static string FluidStorage => nameof(FluidStorage).KeyTranslate();
        public static string Stimulate => nameof(Stimulate).KeyTranslate();
        public static string ChangeBindStyle => nameof(ChangeBindStyle).KeyTranslate();
        public static string OnaholeBedStorage => nameof(OnaholeBedStorage).KeyTranslate();
        public static string StripOnBind => nameof(StripOnBind).KeyTranslate();
        public static string UseReduceResistance => nameof(UseReduceResistance).KeyTranslate();
        public static string OrgasmReduceWill => nameof(OrgasmReduceWill).KeyTranslate();
        public static string OrgasmReduceCertainty => nameof(OrgasmReduceCertainty).KeyTranslate();
        public static string MachineBlocksParts => nameof(MachineBlocksParts).KeyTranslate();
        public static string ShowCrossSections => nameof(ShowCrossSections).KeyTranslate();
        public static string ShowExtraGraphics => nameof(ShowExtraGraphics).KeyTranslate();
        public static string Show => nameof(Show).KeyTranslate();
        public static string SetRestriction => nameof(SetRestriction).KeyTranslate();
        public static string Feed => nameof(Feed).KeyTranslate();
        public static string CantFeed => nameof(CantFeed).KeyTranslate();
        public static string AutoConvert => nameof(AutoConvert).KeyTranslate();
        public static string Destroy => nameof(Destroy).KeyTranslate();
        public static string NoConvertibleItem => nameof(NoConvertibleItem).KeyTranslate();

        //Enums
        public static string Factionless => nameof(Factionless).KeyTranslate();
        public static string Insect => nameof(Insect).KeyTranslate();

        //Messages
        public static string QuitRaiding => nameof(QuitRaiding).KeyTranslate();

        //Brothel Colony
        public static string BrothelColony => nameof(BrothelColony).KeyTranslate();
        public static string UseBrothelPrice => nameof(UseBrothelPrice).KeyTranslate();
        public static string OverrideBedScore => nameof(OverrideBedScore).KeyTranslate();
        public static string BedScore => nameof(BedScore).KeyTranslate();
        public static string PayGoodwill => nameof(PayGoodwill).KeyTranslate();
        public static string FinalPrice => nameof(FinalPrice).KeyTranslate();

        //DBH
        public static string Shower => nameof(Shower).KeyTranslate();
        public static string Shower_Desc => nameof(Shower_Desc).KeyTranslate();
    }
    [StaticConstructorOnStartup]
    public static class OTr
    {
        public static string Delete => "Delete".Translate();
        public static string Edit => "Edit".Translate();
        public static string Reset => "ResetBinding".Translate();
        public static string ResetAll => "ResetAll".Translate();
        public static string Choose => "WorldChooseButton".Translate();
        public static string Default => "Default".Translate();
        public static string ClickToEdit => "ClickToEdit".Translate();
        public static string Race => "Race".Translate();
        public static string DevelopmentMode => "DevelopmentMode".Translate();
        public static string MarketValue => "MarketValue".Translate();
        public static string Off => "Off".Translate();
        public static string Pain => "Pain".Translate();
        public static string Nutrition => "Nutrition".Translate();
        public static string Stockpiles => "StockpilePlural".Translate();
        public static string Disable => "Disable".Translate();
        public static string ClickToSelect => "ModClickToSelect".Translate();
        public static string Colonist => "Colonist".Translate();
        public static string Slave => "Slave".Translate();
        public static string Prisoner => "Prisoner".Translate();
        public static string Animal => "Animal".Translate();
        public static string Mechanoid => "Mechanoid".Translate();
        public static string Enemy => "Enemy".Translate().CapitalizeFirst();
        public static string Downed => "DownedLower".Translate().CapitalizeFirst();
        public static string Human => "TargetHuman".Translate();
        public static string CannotReach => "CannotReach".Translate();
        public static string ReservedBy => "ReservedBy".Translate();
        public static string Time => "TimeRecordsCategory".Translate();
        public static string Menu => "Menu".Translate();
        public static string Misc => "MiscRecordsCategory".Translate();
        public static string RestoreDefaults => "RestoreToDefaultSettings".Translate();
        public static string ChangeDefaults => "ChangeDefaults".Translate();
        public static string Allow => "CommandAllow".Translate();
        public static string Forbid => "CommandForbid".Translate();
        public static string Guest => "TabGuest".Translate();
        public static string Burn => "Burn".DefLabel<HediffDef>();
    }
    [StaticConstructorOnStartup]
    public static class TranslationUtility
    {
        public static string ParseString(this string message, params object[] args)
        {
            for (int i = 0; i < args.Length; i++)
            {
                message = message.Replace("{" + i + "}", args[i].ToStringSafe());
            }
            return message;
        }
        public static string ThingFullLabel(this string key)
        {
            return key.DefLabel<ThingDef>() + " (" + key + ")";
        }
        public static string DefLabel<T>(this string name) where T : Def => DefDatabase<T>.GetNamed(name)?.LabelCap ?? name;
        public static string Label(this ElectrocuteModes mode)
        {
            switch (mode)
            {
                case ElectrocuteModes.Removed:
                    return OTr.Disable;
                case ElectrocuteModes.Off:
                    return OTr.Off;
                case ElectrocuteModes.Stimulate:
                    return Tr.Stimulate;
                case ElectrocuteModes.Pain:
                    return OTr.Pain;
                case ElectrocuteModes.Burn:
                    return OTr.Burn;
                default:
                    return OTr.Off;
            }
        }

        public static string Label(this MilkingModes mode)
        {
            switch (mode)
            {
                case MilkingModes.Removed:
                    return OTr.Disable;
                case MilkingModes.Off:
                    return OTr.Off;
                case MilkingModes.Normal:
                    return "Research_Normal".DefLabel<PreceptDef>();
                case MilkingModes.Fast:
                    return "Research_Fast".DefLabel<PreceptDef>();
                case MilkingModes.VeryFast:
                    return "Research_VeryFast".DefLabel<PreceptDef>();
                default:
                    return OTr.Off;
            }
        }
        public static string Label(this FactionType type)
        {
            switch (type)
            {
                case FactionType.Colonist:
                    return OTr.Colonist;
                case FactionType.Prisoner:
                    return OTr.Prisoner;
                case FactionType.Slave:
                    return OTr.Slave;
                case FactionType.Guest:
                    return OTr.Guest;
                case FactionType.Factionless:
                    return Tr.Factionless;
                default:
                    return OTr.Colonist;
            }
        }
        public static string Label(this KindType type)
        {
            switch (type)
            {
                case KindType.Humanlike:
                    return OTr.Human;
                case KindType.Animal:
                    return OTr.Animal;
                case KindType.Insect:
                    return Tr.Insect;
                case KindType.Mechanoid:
                    return OTr.Mechanoid;
                default:
                    return OTr.Human;
            }
        }
        public static string InBrackets(this string str) => "(" + str + ")";
    }
}


code:
using Verse;
using RimWorld;
using Verse.AI;
using System;
using System.Collections.Generic;
using RJW_Onahole.Utilities;
using RJW_Onahole.Comps;
using System.Linq;
namespace RJW_Onahole.Works
{
    public class WorkGiver_RefillDrugInfuser : WorkGiver_RefuelIngestibleBase
    {
        public override CompIngestibleRefuelable GetTargetComp(Thing t)
        {
            return t.TryGetComp<CompInfuserRefuelable>();
        }

        public override JobDef GetJobDef()
        {
            return OnaholeDefOf.RefillDrugInfuser;
        }
    }
}


code:
using Verse;
using RimWorld;
using Verse.AI;
using System;
using System.Collections.Generic;
using RJW_Onahole.Utilities;
using RJW_Onahole.Comps;
using System.Linq;
namespace RJW_Onahole.Works
{
    public class WorkGiver_RefillFeederFood : WorkGiver_RefuelIngestibleBase
    {
        public override CompIngestibleRefuelable GetTargetComp(Thing t)
        {
            return t.TryGetComp<CompFeederRefuelable>();
        }

        public override JobDef GetJobDef()
        {
            return OnaholeDefOf.RefillFeederFood;
        }
    }
}


code:
using Verse;
using RimWorld;
using Verse.AI;
using System;
using System.Collections.Generic;
using RJW_Onahole.Utilities;
using RJW_Onahole.Comps;
using System.Linq;
namespace RJW_Onahole.Works
{
    public abstract class WorkGiver_RefuelIngestibleBase : WorkGiver_Scanner
    {
        public override ThingRequest PotentialWorkThingRequest
        {
            get
            {
                return ThingRequest.ForGroup(ThingRequestGroup.Refuelable);
            }
        }
        public override PathEndMode PathEndMode
        {
            get
            {
                return PathEndMode.Touch;
            }
        }
        public abstract CompIngestibleRefuelable GetTargetComp (Thing t);
        public abstract JobDef GetJobDef();
        public override IEnumerable<Thing> PotentialWorkThingsGlobal(Pawn pawn)
        {
            return pawn.Map.listerThings.ThingsInGroup(ThingRequestGroup.Bed).Where(thing => thing is Building_OnaholeBed onaholeBed);
        }
        public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
        {
            return t is Building_OnaholeBed && CanRefuel(pawn, t, forced);
        }
        public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
        {
            CompIngestibleRefuelable compFeederRefuelable = this.GetTargetComp(t);
            Job job = JobMaker.MakeJob(this.GetJobDef(), t, FindBestFuel(pawn, compFeederRefuelable));
            return job;
        }
        protected bool CanRefuel(Pawn pawn, Thing t, bool forced = false)
        {
            CompIngestibleRefuelable compRefuelable = this.GetTargetComp(t);
            if (compRefuelable == null || compRefuelable.IsFull || compRefuelable.FuelThingDef == null || (!forced && !compRefuelable.allowAutoRefill))
            {
                return false;
            }
            if (!forced && !compRefuelable.ShouldAutoRefuelNow)
            {
                return false;
            }
            if (t.IsForbidden(pawn) || !pawn.CanReserveAndReach(t, PathEndMode.OnCell, Danger.Deadly, 1, -1, null, false))
            {
                return false;
            }
            if (t.Faction != pawn.Faction)
            {
                return false;
            }
            if (FindBestFuel(pawn, compRefuelable) == null) { return false; }
            return true;
        }
        public static Thing FindBestFuel(Pawn pawn, CompIngestibleRefuelable refuelable)
        {
            ThingRequest request = ThingRequest.ForDef(refuelable.FuelThingDef);
            bool predicate(Thing x) => !x.IsForbidden(pawn) && pawn.CanReserve(x, 1, -1, null, false);
            return GenClosest.ClosestThingReachable(pawn.Position, pawn.Map, request, PathEndMode.ClosestTouch, TraverseParms.For(pawn, Danger.Deadly, TraverseMode.ByPawn, false, false, false), 9999f, predicate, null, 0, -1, false, RegionType.Set_Passable, false);
        }
    }
}


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 SizedApparel;
using RJW_Onahole.Render;
using UnityEngine;
using RJW_Onahole.Data;
using Verse.Noise;
namespace RJW_Onahole
{
    [StaticConstructorOnStartup]
    public static class RJW_SizedApparel
    {
        private static readonly Harmony harmony = new Harmony(nameof(RJW_Onahole) + "." + nameof(RJW_SizedApparel));
        static RJW_SizedApparel()
        {
            harmony.PatchAll();
        }
    }
    [HarmonyPatch(typeof(ConfigUtility))]
    public static class ConfigUtility_Patch
    {
        private const float BaseOffset = 0.1f;
        [HarmonyPostfix]
        [HarmonyPatch(nameof(ConfigUtility.GetPawnTransformData), new Type[] { typeof(Pawn), typeof(HediffDef) })]
        public static void GetTransformDataValue_Postfix(ref TransformData __result, Pawn pawn, HediffDef hediffDef)
        {
            if (hediffDef == OnaholeDefOf.OnaholeMilkingMachine || hediffDef == OnaholeDefOf.OnaholeBreastElectrodes)
            {
                //Get breast size
                Vector2 sizedOffset = SA0_Settings.GetScaleFor(pawn.kindDef.race.defName) * ((Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_breastsBPR(pawn)).FirstOrDefault()?.CurStage.minSeverity * BaseOffset) ?? 0);
                __result.Offset.z -= sizedOffset.y;
                __result.Separation += sizedOffset.x;
            }
        }

    }
    [HarmonyPatch(typeof(RJW_Onahole_Settings))]
    public static class RJW_Onahole_Settings_Patch
    {

        [HarmonyPostfix]
        [HarmonyPatch("AddTabs")]
        public static void AddTabs_Postfix()
        {
            RJW_Onahole_Settings.tabManager.AddTab("SA0", SA0_Settings.SA0_ConfigTab);
        }
    }
}


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

  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <AssemblyName>Onahole_RJW_SizedApparel</AssemblyName>
  </PropertyGroup>

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